| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556 |
- // Copyright (C) 2004-2024 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 <https://www.gnu.org/licenses/agpl-3.0.en.html>
- //
- // Alternative licensing terms are available from the licensor.
- // For commercial licensing, see <https://www.artifex.com/> or contact
- // Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco,
- // CA 94129, USA, 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->function_stride == 0)
- 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
- {
- int stride = shade->function_stride;
- if (stride)
- {
- int i;
- for (i = 0; i < 256; i++)
- fz_test_color(ctx, dev, shade->colorspace, &shade->function[i*stride], 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, 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, 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_function *tr)
- {
- fz_test_device *dev = (fz_test_device*)dev_;
- if (dev->passthrough)
- fz_end_mask_tr(ctx, dev->passthrough, tr);
- }
- 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;
- }
|