1#include <mupdf/fitz.h>
2
3typedef struct fz_test_device_s
4{
5 fz_device super;
6 int *is_color;
7 float threshold;
8 int options;
9 fz_device *passthrough;
10 int resolved;
11} fz_test_device;
12
13static int
14is_rgb_color(float threshold, float r, float g, float b)
15{
16 float rg_diff = fz_abs(r - g);
17 float rb_diff = fz_abs(r - b);
18 float gb_diff = fz_abs(g - b);
19 return rg_diff > threshold || rb_diff > threshold || gb_diff > threshold;
20}
21
22static int
23is_rgb_color_u8(int threshold_u8, int r, int g, int b)
24{
25 int rg_diff = fz_absi(r - g);
26 int rb_diff = fz_absi(r - b);
27 int gb_diff = fz_absi(g - b);
28 return rg_diff > threshold_u8 || rb_diff > threshold_u8 || gb_diff > threshold_u8;
29}
30
31static void
32fz_test_color(fz_context *ctx, fz_test_device *t, fz_colorspace *colorspace, const float *color, fz_color_params color_params)
33{
34 if (!*t->is_color && colorspace && fz_colorspace_type(ctx, colorspace) != FZ_COLORSPACE_GRAY)
35 {
36 if (colorspace == fz_device_rgb(ctx))
37 {
38 if (is_rgb_color(t->threshold, color[0], color[1], color[2]))
39 {
40 *t->is_color = 2;
41 t->resolved = 1;
42 if (t->passthrough == NULL)
43 fz_throw(ctx, FZ_ERROR_ABORT, "Page found as color; stopping interpretation");
44 }
45 }
46 else
47 {
48 float rgb[3];
49 fz_convert_color(ctx, colorspace, color, fz_device_rgb(ctx), rgb, NULL, color_params);
50 if (is_rgb_color(t->threshold, rgb[0], rgb[1], rgb[2]))
51 {
52 *t->is_color = 2;
53 t->resolved = 1;
54 if (t->passthrough == NULL)
55 {
56 fz_throw(ctx, FZ_ERROR_ABORT, "Page found as color; stopping interpretation");
57 }
58 }
59 }
60 }
61}
62
63static void
64fz_test_fill_path(fz_context *ctx, fz_device *dev_, const fz_path *path, int even_odd, fz_matrix ctm,
65 fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params)
66{
67 fz_test_device *dev = (fz_test_device*)dev_;
68
69 if (dev->resolved == 0 && alpha != 0.0f)
70 fz_test_color(ctx, dev, colorspace, color, color_params);
71 if (dev->passthrough)
72 fz_fill_path(ctx, dev->passthrough, path, even_odd, ctm, colorspace, color, alpha, color_params);
73}
74
75static void
76fz_test_stroke_path(fz_context *ctx, fz_device *dev_, const fz_path *path, const fz_stroke_state *stroke,
77 fz_matrix ctm, fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params)
78{
79 fz_test_device *dev = (fz_test_device*)dev_;
80
81 if (dev->resolved == 0 && alpha != 0.0f)
82 fz_test_color(ctx, dev, colorspace, color, color_params);
83 if (dev->passthrough)
84 fz_stroke_path(ctx, dev->passthrough, path, stroke, ctm, colorspace, color, alpha, color_params);
85}
86
87static void
88fz_test_fill_text(fz_context *ctx, fz_device *dev_, const fz_text *text, fz_matrix ctm,
89 fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params)
90{
91 fz_test_device *dev = (fz_test_device*)dev_;
92
93 if (dev->resolved == 0 && alpha != 0.0f)
94 fz_test_color(ctx, dev, colorspace, color, color_params);
95 if (dev->passthrough)
96 fz_fill_text(ctx, dev->passthrough, text, ctm, colorspace, color, alpha, color_params);
97}
98
99static void
100fz_test_stroke_text(fz_context *ctx, fz_device *dev_, const fz_text *text, const fz_stroke_state *stroke,
101 fz_matrix ctm, fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params)
102{
103 fz_test_device *dev = (fz_test_device*)dev_;
104
105 if (dev->resolved == 0 && alpha != 0.0f)
106 fz_test_color(ctx, dev, colorspace, color, color_params);
107 if (dev->passthrough)
108 fz_stroke_text(ctx, dev->passthrough, text, stroke, ctm, colorspace, color, alpha, color_params);
109}
110
111struct shadearg
112{
113 fz_test_device *dev;
114 fz_shade *shade;
115 fz_color_params color_params;
116};
117
118static void
119prepare_vertex(fz_context *ctx, void *arg_, fz_vertex *v, const float *color)
120{
121 struct shadearg *arg = arg_;
122 fz_test_device *dev = arg->dev;
123 fz_shade *shade = arg->shade;
124 if (!shade->use_function)
125 fz_test_color(ctx, dev, shade->colorspace, color, arg->color_params);
126}
127
128static void
129fz_test_fill_shade(fz_context *ctx, fz_device *dev_, fz_shade *shade, fz_matrix ctm, float alpha, fz_color_params color_params)
130{
131 fz_test_device *dev = (fz_test_device*)dev_;
132
133 if (dev->resolved == 0)
134 {
135 if ((dev->options & FZ_TEST_OPT_SHADINGS) == 0)
136 {
137 if (fz_colorspace_type(ctx, shade->colorspace) != FZ_COLORSPACE_GRAY)
138 {
139 /* Don't test every pixel. Upgrade us from "black and white" to "probably color" */
140 if (*dev->is_color == 0)
141 *dev->is_color = 1;
142 dev->resolved = 1;
143 if (dev->passthrough == NULL)
144 fz_throw(ctx, FZ_ERROR_ABORT, "Page found as color; stopping interpretation");
145 }
146 }
147 else
148 {
149 if (shade->use_function)
150 {
151 int i;
152 for (i = 0; i < 256; i++)
153 fz_test_color(ctx, dev, shade->colorspace, shade->function[i], color_params);
154 }
155 else
156 {
157 struct shadearg arg;
158 arg.dev = dev;
159 arg.shade = shade;
160 arg.color_params = color_params;
161 fz_process_shade(ctx, shade, ctm, fz_device_current_scissor(ctx, dev_), prepare_vertex, NULL, &arg);
162 }
163 }
164 }
165 if (dev->passthrough)
166 fz_fill_shade(ctx, dev->passthrough, shade, ctm, alpha, color_params);
167}
168
169static 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)
170{
171 unsigned int count = (unsigned int)image->w * (unsigned int)image->h;
172 unsigned int i;
173
174 if (image->colorspace == fz_device_rgb(ctx))
175 {
176 int threshold_u8 = dev->threshold * 255;
177 for (i = 0; i < count; i++)
178 {
179 int r = fz_read_byte(ctx, stream);
180 int g = fz_read_byte(ctx, stream);
181 int b = fz_read_byte(ctx, stream);
182 if (is_rgb_color_u8(threshold_u8, r, g, b))
183 {
184 *dev->is_color = 1;
185 dev->resolved = 1;
186 if (dev->passthrough == NULL)
187 fz_throw(ctx, FZ_ERROR_ABORT, "Page found as color; stopping interpretation");
188 break;
189 }
190 }
191 }
192 else
193 {
194 fz_color_converter cc;
195 unsigned int n = (unsigned int)image->n;
196
197 fz_init_cached_color_converter(ctx, &cc, image->colorspace, fz_device_rgb(ctx), NULL, color_params);
198
199 fz_try(ctx)
200 {
201 for (i = 0; i < count; i++)
202 {
203 float cs[FZ_MAX_COLORS];
204 float ds[FZ_MAX_COLORS];
205 unsigned int k;
206
207 for (k = 0; k < n; k++)
208 cs[k] = fz_read_byte(ctx, stream) / 255.0f;
209
210 cc.convert(ctx, &cc, ds, cs);
211
212 if (is_rgb_color(dev->threshold, ds[0], ds[1], ds[2]))
213 {
214 *dev->is_color = 1;
215 dev->resolved = 1;
216 if (dev->passthrough == NULL)
217 fz_throw(ctx, FZ_ERROR_ABORT, "Page found as color; stopping interpretation");
218 break;
219 }
220 }
221 }
222 fz_always(ctx)
223 fz_fin_cached_color_converter(ctx, &cc);
224 fz_catch(ctx)
225 fz_rethrow(ctx);
226 }
227}
228
229static void
230fz_test_fill_other_image(fz_context *ctx, fz_test_device *dev, fz_pixmap *pix, fz_color_params color_params)
231{
232 unsigned int count, i, k, h, sa, ss;
233 unsigned char *s;
234
235 count = pix->w;
236 h = pix->h;
237 s = pix->samples;
238 sa = pix->alpha;
239 ss = pix->stride - pix->w * pix->n;
240
241 if (pix->colorspace == fz_device_rgb(ctx))
242 {
243 int threshold_u8 = dev->threshold * 255;
244 while (h--)
245 {
246 for (i = 0; i < count; i++)
247 {
248 if ((!sa || s[3] != 0) && is_rgb_color_u8(threshold_u8, s[0], s[1], s[2]))
249 {
250 *dev->is_color = 1;
251 dev->resolved = 1;
252 if (dev->passthrough == NULL)
253 fz_throw(ctx, FZ_ERROR_ABORT, "Page found as color; stopping interpretation");
254 break;
255 }
256 s += 3 + sa;
257 }
258 s += ss;
259 }
260 }
261 else
262 {
263 fz_color_converter cc;
264 unsigned int n = (unsigned int)pix->n-1;
265
266 fz_init_cached_color_converter(ctx, &cc, pix->colorspace, fz_device_rgb(ctx), NULL, color_params);
267
268 fz_try(ctx)
269 {
270 while (h--)
271 {
272 for (i = 0; i < count; i++)
273 {
274 float cs[FZ_MAX_COLORS];
275 float ds[FZ_MAX_COLORS];
276
277 for (k = 0; k < n; k++)
278 cs[k] = (*s++) / 255.0f;
279 if (sa && *s++ == 0)
280 continue;
281
282 cc.convert(ctx, &cc, ds, cs);
283
284 if (is_rgb_color(dev->threshold, ds[0], ds[1], ds[2]))
285 {
286 *dev->is_color = 1;
287 dev->resolved = 1;
288 if (dev->passthrough == NULL)
289 fz_throw(ctx, FZ_ERROR_ABORT, "Page found as color; stopping interpretation");
290 break;
291 }
292 }
293 s += ss;
294 }
295 }
296 fz_always(ctx)
297 fz_fin_cached_color_converter(ctx, &cc);
298 fz_catch(ctx)
299 fz_rethrow(ctx);
300 }
301}
302
303
304static void
305fz_test_fill_image(fz_context *ctx, fz_device *dev_, fz_image *image, fz_matrix ctm, float alpha, fz_color_params color_params)
306{
307 fz_test_device *dev = (fz_test_device*)dev_;
308
309 while (dev->resolved == 0) /* So we can break out */
310 {
311 fz_compressed_buffer *buffer;
312
313 if (*dev->is_color || !image->colorspace || fz_colorspace_is_gray(ctx, image->colorspace))
314 break;
315
316 if ((dev->options & FZ_TEST_OPT_IMAGES) == 0)
317 {
318 /* Don't test every pixel. Upgrade us from "black and white" to "probably color" */
319 if (*dev->is_color == 0)
320 *dev->is_color = 1;
321 dev->resolved = 1;
322 if (dev->passthrough == NULL)
323 fz_throw(ctx, FZ_ERROR_ABORT, "Page found as color; stopping interpretation");
324 break;
325 }
326
327 buffer = fz_compressed_image_buffer(ctx, image);
328 if (buffer && image->bpc == 8)
329 {
330 fz_stream *stream = fz_open_compressed_buffer(ctx, buffer);
331 fz_try(ctx)
332 fz_test_fill_compressed_8bpc_image(ctx, dev, image, stream, color_params);
333 fz_always(ctx)
334 fz_drop_stream(ctx, stream);
335 fz_catch(ctx)
336 fz_rethrow(ctx);
337 }
338 else
339 {
340 fz_pixmap *pix = fz_get_pixmap_from_image(ctx, image, NULL, NULL, 0, 0);
341 if (pix == NULL) /* Should never happen really, but... */
342 break;
343
344 fz_try(ctx)
345 fz_test_fill_other_image(ctx, dev, pix, color_params);
346 fz_always(ctx)
347 fz_drop_pixmap(ctx, pix);
348 fz_catch(ctx)
349 fz_rethrow(ctx);
350 }
351 break;
352 }
353 if (dev->passthrough)
354 fz_fill_image(ctx, dev->passthrough, image, ctm, alpha, color_params);
355}
356
357static void
358fz_test_fill_image_mask(fz_context *ctx, fz_device *dev_, fz_image *image, fz_matrix ctm,
359 fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params)
360{
361 fz_test_device *dev = (fz_test_device*)dev_;
362
363 if (dev->resolved == 0)
364 {
365 /* We assume that at least some of the image pixels are non-zero */
366 fz_test_color(ctx, dev, colorspace, color, color_params);
367 }
368 if (dev->passthrough)
369 fz_fill_image_mask(ctx, dev->passthrough, image, ctm, colorspace, color, alpha, color_params);
370}
371
372static void
373fz_test_clip_path(fz_context *ctx, fz_device *dev_, const fz_path *path, int even_odd, fz_matrix ctm, fz_rect scissor)
374{
375 fz_test_device *dev = (fz_test_device*)dev_;
376
377 if (dev->passthrough)
378 fz_clip_path(ctx, dev->passthrough, path, even_odd, ctm, scissor);
379}
380
381static void
382fz_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)
383{
384 fz_test_device *dev = (fz_test_device*)dev_;
385
386 if (dev->passthrough)
387 fz_clip_stroke_path(ctx, dev->passthrough, path, stroke, ctm, scissor);
388}
389
390static void
391fz_test_clip_text(fz_context *ctx, fz_device *dev_, const fz_text *text, fz_matrix ctm, fz_rect scissor)
392{
393 fz_test_device *dev = (fz_test_device*)dev_;
394
395 if (dev->passthrough)
396 fz_clip_text(ctx, dev->passthrough, text, ctm, scissor);
397}
398
399static void
400fz_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)
401{
402 fz_test_device *dev = (fz_test_device*)dev_;
403
404 if (dev->passthrough)
405 fz_clip_stroke_text(ctx, dev->passthrough, text, stroke, ctm, scissor);
406}
407
408static void
409fz_test_ignore_text(fz_context *ctx, fz_device *dev_, const fz_text *text, fz_matrix ctm)
410{
411 fz_test_device *dev = (fz_test_device*)dev_;
412
413 if (dev->passthrough)
414 fz_ignore_text(ctx, dev->passthrough, text, ctm);
415}
416
417static void
418fz_test_clip_image_mask(fz_context *ctx, fz_device *dev_, fz_image *img, fz_matrix ctm, fz_rect scissor)
419{
420 fz_test_device *dev = (fz_test_device*)dev_;
421
422 if (dev->passthrough)
423 fz_clip_image_mask(ctx, dev->passthrough, img, ctm, scissor);
424}
425
426static void
427fz_test_pop_clip(fz_context *ctx, fz_device *dev_)
428{
429 fz_test_device *dev = (fz_test_device*)dev_;
430
431 if (dev->passthrough)
432 fz_pop_clip(ctx, dev->passthrough);
433}
434
435static void
436fz_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)
437{
438 fz_test_device *dev = (fz_test_device*)dev_;
439
440 if (dev->passthrough)
441 fz_begin_mask(ctx, dev->passthrough, rect, luminosity, cs, bc, color_params);
442}
443
444static void
445fz_test_end_mask(fz_context *ctx, fz_device *dev_)
446{
447 fz_test_device *dev = (fz_test_device*)dev_;
448
449 if (dev->passthrough)
450 fz_end_mask(ctx, dev->passthrough);
451}
452
453static void
454fz_test_begin_group(fz_context *ctx, fz_device *dev_, fz_rect rect, fz_colorspace *cs, int isolated, int knockout, int blendmode, float alpha)
455{
456 fz_test_device *dev = (fz_test_device*)dev_;
457
458 if (dev->passthrough)
459 fz_begin_group(ctx, dev->passthrough, rect, cs, isolated, knockout, blendmode, alpha);
460}
461
462static void
463fz_test_end_group(fz_context *ctx, fz_device *dev_)
464{
465 fz_test_device *dev = (fz_test_device*)dev_;
466
467 if (dev->passthrough)
468 fz_end_group(ctx, dev->passthrough);
469}
470
471static int
472fz_test_begin_tile(fz_context *ctx, fz_device *dev_, fz_rect area, fz_rect view, float xstep, float ystep, fz_matrix ctm, int id)
473{
474 fz_test_device *dev = (fz_test_device*)dev_;
475
476 if (dev->passthrough)
477 return fz_begin_tile_id(ctx, dev->passthrough, area, view, xstep, ystep, ctm, id);
478 else
479 return 0;
480}
481
482static void
483fz_test_end_tile(fz_context *ctx, fz_device *dev_)
484{
485 fz_test_device *dev = (fz_test_device*)dev_;
486
487 if (dev->passthrough)
488 fz_end_tile(ctx, dev->passthrough);
489}
490
491/*
492 Create a device to test for features.
493
494 Currently only tests for the presence of non-grayscale colors.
495
496 is_color: Possible values returned:
497 0: Definitely greyscale
498 1: Probably color (all colors were grey, but there
499 were images or shadings in a non grey colorspace).
500 2: Definitely color
501
502 threshold: The difference from grayscale that will be tolerated.
503 Typical values to use are either 0 (be exact) and 0.02 (allow an
504 imperceptible amount of slop).
505
506 options: A set of bitfield options, from the FZ_TEST_OPT set.
507
508 passthrough: A device to pass all calls through to, or NULL.
509 If set, then the test device can both test and pass through to
510 an underlying device (like, say, the display list device). This
511 means that a display list can be created and at the end we'll
512 know if it's colored or not.
513
514 In the absence of a passthrough device, the device will throw
515 an exception to stop page interpretation when color is found.
516*/
517fz_device *
518fz_new_test_device(fz_context *ctx, int *is_color, float threshold, int options, fz_device *passthrough)
519{
520 fz_test_device *dev = fz_new_derived_device(ctx, fz_test_device);
521
522 dev->super.fill_path = fz_test_fill_path;
523 dev->super.stroke_path = fz_test_stroke_path;
524 dev->super.fill_text = fz_test_fill_text;
525 dev->super.stroke_text = fz_test_stroke_text;
526 dev->super.fill_shade = fz_test_fill_shade;
527 dev->super.fill_image = fz_test_fill_image;
528 dev->super.fill_image_mask = fz_test_fill_image_mask;
529
530 if (passthrough)
531 {
532 dev->super.clip_path = fz_test_clip_path;
533 dev->super.clip_stroke_path = fz_test_clip_stroke_path;
534 dev->super.clip_text = fz_test_clip_text;
535 dev->super.clip_stroke_text = fz_test_clip_stroke_text;
536 dev->super.ignore_text = fz_test_ignore_text;
537 dev->super.clip_image_mask = fz_test_clip_image_mask;
538 dev->super.pop_clip = fz_test_pop_clip;
539 dev->super.begin_mask = fz_test_begin_mask;
540 dev->super.end_mask = fz_test_end_mask;
541 dev->super.begin_group = fz_test_begin_group;
542 dev->super.end_group = fz_test_end_group;
543 dev->super.begin_tile = fz_test_begin_tile;
544 dev->super.end_tile = fz_test_end_tile;
545 }
546
547 dev->is_color = is_color;
548 dev->options = options;
549 dev->threshold = threshold;
550 dev->passthrough = passthrough;
551 dev->resolved = 0;
552
553 *dev->is_color = 0;
554
555 return (fz_device*)dev;
556}
557