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