// Copyright (C) 2004-2021 Artifex Software, Inc. // // This file is part of MuPDF. // // MuPDF is free software: you can redistribute it and/or modify it under the // terms of the GNU Affero General Public License as published by the Free // Software Foundation, either version 3 of the License, or (at your option) // any later version. // // MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more // details. // // You should have received a copy of the GNU Affero General Public License // along with MuPDF. If not, see // // Alternative licensing terms are available from the licensor. // For commercial licensing, see or contact // Artifex Software, Inc., 1305 Grant Avenue - Suite 200, Novato, // CA 94945, U.S.A., +1(415)492-9861, for further information. #include "mupdf/fitz.h" #include "color-imp.h" typedef struct { fz_device super; int *is_color; float threshold; int options; fz_device *passthrough; int resolved; } fz_test_device; static int is_rgb_color(float threshold, float r, float g, float b) { float rg_diff = fz_abs(r - g); float rb_diff = fz_abs(r - b); float gb_diff = fz_abs(g - b); return rg_diff > threshold || rb_diff > threshold || gb_diff > threshold; } static int is_rgb_color_u8(int threshold_u8, int r, int g, int b) { int rg_diff = fz_absi(r - g); int rb_diff = fz_absi(r - b); int gb_diff = fz_absi(g - b); return rg_diff > threshold_u8 || rb_diff > threshold_u8 || gb_diff > threshold_u8; } static void fz_test_color(fz_context *ctx, fz_test_device *t, fz_colorspace *colorspace, const float *color, fz_color_params color_params) { if (!*t->is_color && colorspace && fz_colorspace_type(ctx, colorspace) != FZ_COLORSPACE_GRAY) { if (colorspace == fz_device_rgb(ctx)) { if (is_rgb_color(t->threshold, color[0], color[1], color[2])) { *t->is_color = 2; t->resolved = 1; if (t->passthrough == NULL) fz_throw(ctx, FZ_ERROR_ABORT, "Page found as color; stopping interpretation"); } } else { float rgb[3]; fz_convert_color(ctx, colorspace, color, fz_device_rgb(ctx), rgb, NULL, color_params); if (is_rgb_color(t->threshold, rgb[0], rgb[1], rgb[2])) { *t->is_color = 2; t->resolved = 1; if (t->passthrough == NULL) { fz_throw(ctx, FZ_ERROR_ABORT, "Page found as color; stopping interpretation"); } } } } } static void fz_test_fill_path(fz_context *ctx, fz_device *dev_, const fz_path *path, int even_odd, fz_matrix ctm, fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params) { fz_test_device *dev = (fz_test_device*)dev_; if (dev->resolved == 0 && alpha != 0.0f) fz_test_color(ctx, dev, colorspace, color, color_params); if (dev->passthrough) fz_fill_path(ctx, dev->passthrough, path, even_odd, ctm, colorspace, color, alpha, color_params); } static void fz_test_stroke_path(fz_context *ctx, fz_device *dev_, const fz_path *path, const fz_stroke_state *stroke, fz_matrix ctm, fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params) { fz_test_device *dev = (fz_test_device*)dev_; if (dev->resolved == 0 && alpha != 0.0f) fz_test_color(ctx, dev, colorspace, color, color_params); if (dev->passthrough) fz_stroke_path(ctx, dev->passthrough, path, stroke, ctm, colorspace, color, alpha, color_params); } static void fz_test_fill_text(fz_context *ctx, fz_device *dev_, const fz_text *text, fz_matrix ctm, fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params) { fz_test_device *dev = (fz_test_device*)dev_; if (dev->resolved == 0 && alpha != 0.0f) fz_test_color(ctx, dev, colorspace, color, color_params); if (dev->passthrough) fz_fill_text(ctx, dev->passthrough, text, ctm, colorspace, color, alpha, color_params); } static void fz_test_stroke_text(fz_context *ctx, fz_device *dev_, const fz_text *text, const fz_stroke_state *stroke, fz_matrix ctm, fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params) { fz_test_device *dev = (fz_test_device*)dev_; if (dev->resolved == 0 && alpha != 0.0f) fz_test_color(ctx, dev, colorspace, color, color_params); if (dev->passthrough) fz_stroke_text(ctx, dev->passthrough, text, stroke, ctm, colorspace, color, alpha, color_params); } struct shadearg { fz_test_device *dev; fz_shade *shade; fz_color_params color_params; }; static void prepare_vertex(fz_context *ctx, void *arg_, fz_vertex *v, const float *color) { struct shadearg *arg = arg_; fz_test_device *dev = arg->dev; fz_shade *shade = arg->shade; if (!shade->use_function) fz_test_color(ctx, dev, shade->colorspace, color, arg->color_params); } static void fz_test_fill_shade(fz_context *ctx, fz_device *dev_, fz_shade *shade, fz_matrix ctm, float alpha, fz_color_params color_params) { fz_test_device *dev = (fz_test_device*)dev_; if (dev->resolved == 0) { if ((dev->options & FZ_TEST_OPT_SHADINGS) == 0) { if (fz_colorspace_type(ctx, shade->colorspace) != FZ_COLORSPACE_GRAY) { /* Don't test every pixel. Upgrade us from "black and white" to "probably color" */ if (*dev->is_color == 0) *dev->is_color = 1; dev->resolved = 1; if (dev->passthrough == NULL) fz_throw(ctx, FZ_ERROR_ABORT, "Page found as color; stopping interpretation"); } } else { if (shade->use_function) { int i; for (i = 0; i < 256; i++) fz_test_color(ctx, dev, shade->colorspace, shade->function[i], color_params); } else { struct shadearg arg; arg.dev = dev; arg.shade = shade; arg.color_params = color_params; fz_process_shade(ctx, shade, ctm, fz_device_current_scissor(ctx, dev_), prepare_vertex, NULL, &arg); } } } if (dev->passthrough) fz_fill_shade(ctx, dev->passthrough, shade, ctm, alpha, color_params); } static void fz_test_fill_compressed_8bpc_image(fz_context *ctx, fz_test_device *dev, fz_image *image, fz_stream *stream, fz_color_params color_params) { unsigned int count = (unsigned int)image->w * (unsigned int)image->h; unsigned int i; if (image->colorspace == fz_device_rgb(ctx)) { int threshold_u8 = dev->threshold * 255; for (i = 0; i < count; i++) { int r = fz_read_byte(ctx, stream); int g = fz_read_byte(ctx, stream); int b = fz_read_byte(ctx, stream); if (is_rgb_color_u8(threshold_u8, r, g, b)) { *dev->is_color = 1; dev->resolved = 1; if (dev->passthrough == NULL) fz_throw(ctx, FZ_ERROR_ABORT, "Page found as color; stopping interpretation"); break; } } } else { fz_color_converter cc; unsigned int n = (unsigned int)image->n; fz_init_cached_color_converter(ctx, &cc, image->colorspace, fz_device_rgb(ctx), NULL, color_params); fz_try(ctx) { for (i = 0; i < count; i++) { float cs[FZ_MAX_COLORS]; float ds[FZ_MAX_COLORS]; unsigned int k; for (k = 0; k < n; k++) cs[k] = fz_read_byte(ctx, stream) / 255.0f; cc.convert(ctx, &cc, ds, cs); if (is_rgb_color(dev->threshold, ds[0], ds[1], ds[2])) { *dev->is_color = 1; dev->resolved = 1; if (dev->passthrough == NULL) fz_throw(ctx, FZ_ERROR_ABORT, "Page found as color; stopping interpretation"); break; } } } fz_always(ctx) fz_fin_cached_color_converter(ctx, &cc); fz_catch(ctx) fz_rethrow(ctx); } } static void fz_test_fill_other_image(fz_context *ctx, fz_test_device *dev, fz_pixmap *pix, fz_color_params color_params) { unsigned int count, i, k, h, sa; size_t ss; unsigned char *s; count = pix->w; h = pix->h; s = pix->samples; sa = pix->alpha; ss = pix->stride - pix->w * (size_t)pix->n; if (pix->colorspace == fz_device_rgb(ctx)) { int threshold_u8 = dev->threshold * 255; while (h--) { for (i = 0; i < count; i++) { if ((!sa || s[3] != 0) && is_rgb_color_u8(threshold_u8, s[0], s[1], s[2])) { *dev->is_color = 1; dev->resolved = 1; if (dev->passthrough == NULL) fz_throw(ctx, FZ_ERROR_ABORT, "Page found as color; stopping interpretation"); break; } s += 3 + sa; } s += ss; } } else { fz_color_converter cc; unsigned int n = (unsigned int)pix->n-1; fz_init_cached_color_converter(ctx, &cc, pix->colorspace, fz_device_rgb(ctx), NULL, color_params); fz_try(ctx) { while (h--) { for (i = 0; i < count; i++) { float cs[FZ_MAX_COLORS]; float ds[FZ_MAX_COLORS]; for (k = 0; k < n; k++) cs[k] = (*s++) / 255.0f; if (sa && *s++ == 0) continue; cc.convert(ctx, &cc, ds, cs); if (is_rgb_color(dev->threshold, ds[0], ds[1], ds[2])) { *dev->is_color = 1; dev->resolved = 1; if (dev->passthrough == NULL) fz_throw(ctx, FZ_ERROR_ABORT, "Page found as color; stopping interpretation"); break; } } s += ss; } } fz_always(ctx) fz_fin_cached_color_converter(ctx, &cc); fz_catch(ctx) fz_rethrow(ctx); } } static void fz_test_fill_image(fz_context *ctx, fz_device *dev_, fz_image *image, fz_matrix ctm, float alpha, fz_color_params color_params) { fz_test_device *dev = (fz_test_device*)dev_; while (dev->resolved == 0) /* So we can break out */ { fz_compressed_buffer *buffer; if (*dev->is_color || !image->colorspace || fz_colorspace_is_gray(ctx, image->colorspace)) break; if ((dev->options & FZ_TEST_OPT_IMAGES) == 0) { /* Don't test every pixel. Upgrade us from "black and white" to "probably color" */ if (*dev->is_color == 0) *dev->is_color = 1; dev->resolved = 1; if (dev->passthrough == NULL) fz_throw(ctx, FZ_ERROR_ABORT, "Page found as color; stopping interpretation"); break; } buffer = fz_compressed_image_buffer(ctx, image); if (buffer && image->bpc == 8) { fz_stream *stream = fz_open_compressed_buffer(ctx, buffer); fz_try(ctx) fz_test_fill_compressed_8bpc_image(ctx, dev, image, stream, color_params); fz_always(ctx) fz_drop_stream(ctx, stream); fz_catch(ctx) fz_rethrow(ctx); } else { fz_pixmap *pix = fz_get_pixmap_from_image(ctx, image, NULL, NULL, 0, 0); if (pix == NULL) /* Should never happen really, but... */ break; fz_try(ctx) fz_test_fill_other_image(ctx, dev, pix, color_params); fz_always(ctx) fz_drop_pixmap(ctx, pix); fz_catch(ctx) fz_rethrow(ctx); } break; } if (dev->passthrough) fz_fill_image(ctx, dev->passthrough, image, ctm, alpha, color_params); } static void fz_test_fill_image_mask(fz_context *ctx, fz_device *dev_, fz_image *image, fz_matrix ctm, fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params) { fz_test_device *dev = (fz_test_device*)dev_; if (dev->resolved == 0) { /* We assume that at least some of the image pixels are non-zero */ fz_test_color(ctx, dev, colorspace, color, color_params); } if (dev->passthrough) fz_fill_image_mask(ctx, dev->passthrough, image, ctm, colorspace, color, alpha, color_params); } static void fz_test_clip_path(fz_context *ctx, fz_device *dev_, const fz_path *path, int even_odd, fz_matrix ctm, fz_rect scissor) { fz_test_device *dev = (fz_test_device*)dev_; if (dev->passthrough) fz_clip_path(ctx, dev->passthrough, path, even_odd, ctm, scissor); } static void fz_test_clip_stroke_path(fz_context *ctx, fz_device *dev_, const fz_path *path, const fz_stroke_state *stroke, fz_matrix ctm, fz_rect scissor) { fz_test_device *dev = (fz_test_device*)dev_; if (dev->passthrough) fz_clip_stroke_path(ctx, dev->passthrough, path, stroke, ctm, scissor); } static void fz_test_clip_text(fz_context *ctx, fz_device *dev_, const fz_text *text, fz_matrix ctm, fz_rect scissor) { fz_test_device *dev = (fz_test_device*)dev_; if (dev->passthrough) fz_clip_text(ctx, dev->passthrough, text, ctm, scissor); } static void fz_test_clip_stroke_text(fz_context *ctx, fz_device *dev_, const fz_text *text, const fz_stroke_state *stroke, fz_matrix ctm, fz_rect scissor) { fz_test_device *dev = (fz_test_device*)dev_; if (dev->passthrough) fz_clip_stroke_text(ctx, dev->passthrough, text, stroke, ctm, scissor); } static void fz_test_ignore_text(fz_context *ctx, fz_device *dev_, const fz_text *text, fz_matrix ctm) { fz_test_device *dev = (fz_test_device*)dev_; if (dev->passthrough) fz_ignore_text(ctx, dev->passthrough, text, ctm); } static void fz_test_clip_image_mask(fz_context *ctx, fz_device *dev_, fz_image *img, fz_matrix ctm, fz_rect scissor) { fz_test_device *dev = (fz_test_device*)dev_; if (dev->passthrough) fz_clip_image_mask(ctx, dev->passthrough, img, ctm, scissor); } static void fz_test_pop_clip(fz_context *ctx, fz_device *dev_) { fz_test_device *dev = (fz_test_device*)dev_; if (dev->passthrough) fz_pop_clip(ctx, dev->passthrough); } static void fz_test_begin_mask(fz_context *ctx, fz_device *dev_, fz_rect rect, int luminosity, fz_colorspace *cs, const float *bc, fz_color_params color_params) { fz_test_device *dev = (fz_test_device*)dev_; if (dev->passthrough) fz_begin_mask(ctx, dev->passthrough, rect, luminosity, cs, bc, color_params); } static void fz_test_end_mask(fz_context *ctx, fz_device *dev_) { fz_test_device *dev = (fz_test_device*)dev_; if (dev->passthrough) fz_end_mask(ctx, dev->passthrough); } static void fz_test_begin_group(fz_context *ctx, fz_device *dev_, fz_rect rect, fz_colorspace *cs, int isolated, int knockout, int blendmode, float alpha) { fz_test_device *dev = (fz_test_device*)dev_; if (dev->passthrough) fz_begin_group(ctx, dev->passthrough, rect, cs, isolated, knockout, blendmode, alpha); } static void fz_test_end_group(fz_context *ctx, fz_device *dev_) { fz_test_device *dev = (fz_test_device*)dev_; if (dev->passthrough) fz_end_group(ctx, dev->passthrough); } static int fz_test_begin_tile(fz_context *ctx, fz_device *dev_, fz_rect area, fz_rect view, float xstep, float ystep, fz_matrix ctm, int id) { fz_test_device *dev = (fz_test_device*)dev_; if (dev->passthrough) return fz_begin_tile_id(ctx, dev->passthrough, area, view, xstep, ystep, ctm, id); else return 0; } static void fz_test_end_tile(fz_context *ctx, fz_device *dev_) { fz_test_device *dev = (fz_test_device*)dev_; if (dev->passthrough) fz_end_tile(ctx, dev->passthrough); } fz_device * fz_new_test_device(fz_context *ctx, int *is_color, float threshold, int options, fz_device *passthrough) { fz_test_device *dev = fz_new_derived_device(ctx, fz_test_device); dev->super.fill_path = fz_test_fill_path; dev->super.stroke_path = fz_test_stroke_path; dev->super.fill_text = fz_test_fill_text; dev->super.stroke_text = fz_test_stroke_text; dev->super.fill_shade = fz_test_fill_shade; dev->super.fill_image = fz_test_fill_image; dev->super.fill_image_mask = fz_test_fill_image_mask; if (passthrough) { dev->super.clip_path = fz_test_clip_path; dev->super.clip_stroke_path = fz_test_clip_stroke_path; dev->super.clip_text = fz_test_clip_text; dev->super.clip_stroke_text = fz_test_clip_stroke_text; dev->super.ignore_text = fz_test_ignore_text; dev->super.clip_image_mask = fz_test_clip_image_mask; dev->super.pop_clip = fz_test_pop_clip; dev->super.begin_mask = fz_test_begin_mask; dev->super.end_mask = fz_test_end_mask; dev->super.begin_group = fz_test_begin_group; dev->super.end_group = fz_test_end_group; dev->super.begin_tile = fz_test_begin_tile; dev->super.end_tile = fz_test_end_tile; } dev->is_color = is_color; dev->options = options; dev->threshold = threshold; dev->passthrough = passthrough; dev->resolved = 0; *dev->is_color = 0; return (fz_device*)dev; }