1 | #include <mupdf/fitz.h> |
2 | |
3 | typedef 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 | |
13 | static int |
14 | is_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 | |
22 | static int |
23 | is_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 | |
31 | static void |
32 | fz_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 | |
63 | static void |
64 | fz_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 | |
75 | static void |
76 | fz_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 | |
87 | static void |
88 | fz_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 | |
99 | static void |
100 | fz_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 | |
111 | struct shadearg |
112 | { |
113 | fz_test_device *dev; |
114 | fz_shade *shade; |
115 | fz_color_params color_params; |
116 | }; |
117 | |
118 | static void |
119 | prepare_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 | |
128 | static void |
129 | fz_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 | |
169 | 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) |
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 | |
229 | static void |
230 | fz_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 | |
304 | static void |
305 | fz_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 | |
357 | static void |
358 | fz_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 | |
372 | static void |
373 | fz_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 | |
381 | static void |
382 | 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) |
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 | |
390 | static void |
391 | fz_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 | |
399 | static void |
400 | 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) |
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 | |
408 | static void |
409 | fz_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 | |
417 | static void |
418 | fz_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 | |
426 | static void |
427 | fz_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 | |
435 | static void |
436 | 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) |
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 | |
444 | static void |
445 | fz_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 | |
453 | static void |
454 | fz_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 | |
462 | static void |
463 | fz_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 | |
471 | static int |
472 | 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) |
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 | |
482 | static void |
483 | fz_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 | */ |
517 | fz_device * |
518 | fz_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 | |