comparison mupdf-source/source/fitz/test-device.c @ 2:b50eed0cc0ef upstream

ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4. The directory name has changed: no version number in the expanded directory now.
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 15 Sep 2025 11:43:07 +0200
parents
children
comparison
equal deleted inserted replaced
1:1d09e1dec1d9 2:b50eed0cc0ef
1 // Copyright (C) 2004-2024 Artifex Software, Inc.
2 //
3 // This file is part of MuPDF.
4 //
5 // MuPDF is free software: you can redistribute it and/or modify it under the
6 // terms of the GNU Affero General Public License as published by the Free
7 // Software Foundation, either version 3 of the License, or (at your option)
8 // any later version.
9 //
10 // MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY
11 // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 // FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
13 // details.
14 //
15 // You should have received a copy of the GNU Affero General Public License
16 // along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html>
17 //
18 // Alternative licensing terms are available from the licensor.
19 // For commercial licensing, see <https://www.artifex.com/> or contact
20 // Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco,
21 // CA 94129, USA, for further information.
22
23 #include "mupdf/fitz.h"
24
25 #include "color-imp.h"
26
27 typedef struct
28 {
29 fz_device super;
30 int *is_color;
31 float threshold;
32 int options;
33 fz_device *passthrough;
34 int resolved;
35 } fz_test_device;
36
37 static int
38 is_rgb_color(float threshold, float r, float g, float b)
39 {
40 float rg_diff = fz_abs(r - g);
41 float rb_diff = fz_abs(r - b);
42 float gb_diff = fz_abs(g - b);
43 return rg_diff > threshold || rb_diff > threshold || gb_diff > threshold;
44 }
45
46 static int
47 is_rgb_color_u8(int threshold_u8, int r, int g, int b)
48 {
49 int rg_diff = fz_absi(r - g);
50 int rb_diff = fz_absi(r - b);
51 int gb_diff = fz_absi(g - b);
52 return rg_diff > threshold_u8 || rb_diff > threshold_u8 || gb_diff > threshold_u8;
53 }
54
55 static void
56 fz_test_color(fz_context *ctx, fz_test_device *t, fz_colorspace *colorspace, const float *color, fz_color_params color_params)
57 {
58 if (!*t->is_color && colorspace && fz_colorspace_type(ctx, colorspace) != FZ_COLORSPACE_GRAY)
59 {
60 if (colorspace == fz_device_rgb(ctx))
61 {
62 if (is_rgb_color(t->threshold, color[0], color[1], color[2]))
63 {
64 *t->is_color = 2;
65 t->resolved = 1;
66 if (t->passthrough == NULL)
67 fz_throw(ctx, FZ_ERROR_ABORT, "Page found as color; stopping interpretation");
68 }
69 }
70 else
71 {
72 float rgb[3];
73 fz_convert_color(ctx, colorspace, color, fz_device_rgb(ctx), rgb, NULL, color_params);
74 if (is_rgb_color(t->threshold, rgb[0], rgb[1], rgb[2]))
75 {
76 *t->is_color = 2;
77 t->resolved = 1;
78 if (t->passthrough == NULL)
79 {
80 fz_throw(ctx, FZ_ERROR_ABORT, "Page found as color; stopping interpretation");
81 }
82 }
83 }
84 }
85 }
86
87 static void
88 fz_test_fill_path(fz_context *ctx, fz_device *dev_, const fz_path *path, int even_odd, 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_path(ctx, dev->passthrough, path, even_odd, ctm, colorspace, color, alpha, color_params);
97 }
98
99 static void
100 fz_test_stroke_path(fz_context *ctx, fz_device *dev_, const fz_path *path, 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_path(ctx, dev->passthrough, path, stroke, ctm, colorspace, color, alpha, color_params);
109 }
110
111 static void
112 fz_test_fill_text(fz_context *ctx, fz_device *dev_, const fz_text *text, fz_matrix ctm,
113 fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params)
114 {
115 fz_test_device *dev = (fz_test_device*)dev_;
116
117 if (dev->resolved == 0 && alpha != 0.0f)
118 fz_test_color(ctx, dev, colorspace, color, color_params);
119 if (dev->passthrough)
120 fz_fill_text(ctx, dev->passthrough, text, ctm, colorspace, color, alpha, color_params);
121 }
122
123 static void
124 fz_test_stroke_text(fz_context *ctx, fz_device *dev_, const fz_text *text, const fz_stroke_state *stroke,
125 fz_matrix ctm, fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params)
126 {
127 fz_test_device *dev = (fz_test_device*)dev_;
128
129 if (dev->resolved == 0 && alpha != 0.0f)
130 fz_test_color(ctx, dev, colorspace, color, color_params);
131 if (dev->passthrough)
132 fz_stroke_text(ctx, dev->passthrough, text, stroke, ctm, colorspace, color, alpha, color_params);
133 }
134
135 struct shadearg
136 {
137 fz_test_device *dev;
138 fz_shade *shade;
139 fz_color_params color_params;
140 };
141
142 static void
143 prepare_vertex(fz_context *ctx, void *arg_, fz_vertex *v, const float *color)
144 {
145 struct shadearg *arg = arg_;
146 fz_test_device *dev = arg->dev;
147 fz_shade *shade = arg->shade;
148 if (shade->function_stride == 0)
149 fz_test_color(ctx, dev, shade->colorspace, color, arg->color_params);
150 }
151
152 static void
153 fz_test_fill_shade(fz_context *ctx, fz_device *dev_, fz_shade *shade, fz_matrix ctm, float alpha, fz_color_params color_params)
154 {
155 fz_test_device *dev = (fz_test_device*)dev_;
156
157 if (dev->resolved == 0)
158 {
159 if ((dev->options & FZ_TEST_OPT_SHADINGS) == 0)
160 {
161 if (fz_colorspace_type(ctx, shade->colorspace) != FZ_COLORSPACE_GRAY)
162 {
163 /* Don't test every pixel. Upgrade us from "black and white" to "probably color" */
164 if (*dev->is_color == 0)
165 *dev->is_color = 1;
166 dev->resolved = 1;
167 if (dev->passthrough == NULL)
168 fz_throw(ctx, FZ_ERROR_ABORT, "Page found as color; stopping interpretation");
169 }
170 }
171 else
172 {
173 int stride = shade->function_stride;
174 if (stride)
175 {
176 int i;
177 for (i = 0; i < 256; i++)
178 fz_test_color(ctx, dev, shade->colorspace, &shade->function[i*stride], color_params);
179 }
180 else
181 {
182 struct shadearg arg;
183 arg.dev = dev;
184 arg.shade = shade;
185 arg.color_params = color_params;
186 fz_process_shade(ctx, shade, ctm, fz_device_current_scissor(ctx, dev_), prepare_vertex, NULL, &arg);
187 }
188 }
189 }
190 if (dev->passthrough)
191 fz_fill_shade(ctx, dev->passthrough, shade, ctm, alpha, color_params);
192 }
193
194 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)
195 {
196 unsigned int count = (unsigned int)image->w * (unsigned int)image->h;
197 unsigned int i;
198
199 if (image->colorspace == fz_device_rgb(ctx))
200 {
201 int threshold_u8 = dev->threshold * 255;
202 for (i = 0; i < count; i++)
203 {
204 int r = fz_read_byte(ctx, stream);
205 int g = fz_read_byte(ctx, stream);
206 int b = fz_read_byte(ctx, stream);
207 if (is_rgb_color_u8(threshold_u8, r, g, b))
208 {
209 *dev->is_color = 1;
210 dev->resolved = 1;
211 if (dev->passthrough == NULL)
212 fz_throw(ctx, FZ_ERROR_ABORT, "Page found as color; stopping interpretation");
213 break;
214 }
215 }
216 }
217 else
218 {
219 fz_color_converter cc;
220 unsigned int n = (unsigned int)image->n;
221
222 fz_init_cached_color_converter(ctx, &cc, image->colorspace, fz_device_rgb(ctx), NULL, NULL, color_params);
223
224 fz_try(ctx)
225 {
226 for (i = 0; i < count; i++)
227 {
228 float cs[FZ_MAX_COLORS];
229 float ds[FZ_MAX_COLORS];
230 unsigned int k;
231
232 for (k = 0; k < n; k++)
233 cs[k] = fz_read_byte(ctx, stream) / 255.0f;
234
235 cc.convert(ctx, &cc, ds, cs);
236
237 if (is_rgb_color(dev->threshold, ds[0], ds[1], ds[2]))
238 {
239 *dev->is_color = 1;
240 dev->resolved = 1;
241 if (dev->passthrough == NULL)
242 fz_throw(ctx, FZ_ERROR_ABORT, "Page found as color; stopping interpretation");
243 break;
244 }
245 }
246 }
247 fz_always(ctx)
248 fz_fin_cached_color_converter(ctx, &cc);
249 fz_catch(ctx)
250 fz_rethrow(ctx);
251 }
252 }
253
254 static void
255 fz_test_fill_other_image(fz_context *ctx, fz_test_device *dev, fz_pixmap *pix, fz_color_params color_params)
256 {
257 unsigned int count, i, k, h, sa;
258 size_t ss;
259 unsigned char *s;
260
261 count = pix->w;
262 h = pix->h;
263 s = pix->samples;
264 sa = pix->alpha;
265 ss = pix->stride - pix->w * (size_t)pix->n;
266
267 if (pix->colorspace == fz_device_rgb(ctx))
268 {
269 int threshold_u8 = dev->threshold * 255;
270 while (h--)
271 {
272 for (i = 0; i < count; i++)
273 {
274 if ((!sa || s[3] != 0) && is_rgb_color_u8(threshold_u8, s[0], s[1], s[2]))
275 {
276 *dev->is_color = 1;
277 dev->resolved = 1;
278 if (dev->passthrough == NULL)
279 fz_throw(ctx, FZ_ERROR_ABORT, "Page found as color; stopping interpretation");
280 break;
281 }
282 s += 3 + sa;
283 }
284 s += ss;
285 }
286 }
287 else
288 {
289 fz_color_converter cc;
290 unsigned int n = (unsigned int)pix->n-1;
291
292 fz_init_cached_color_converter(ctx, &cc, pix->colorspace, fz_device_rgb(ctx), NULL, NULL, color_params);
293
294 fz_try(ctx)
295 {
296 while (h--)
297 {
298 for (i = 0; i < count; i++)
299 {
300 float cs[FZ_MAX_COLORS];
301 float ds[FZ_MAX_COLORS];
302
303 for (k = 0; k < n; k++)
304 cs[k] = (*s++) / 255.0f;
305 if (sa && *s++ == 0)
306 continue;
307
308 cc.convert(ctx, &cc, ds, cs);
309
310 if (is_rgb_color(dev->threshold, ds[0], ds[1], ds[2]))
311 {
312 *dev->is_color = 1;
313 dev->resolved = 1;
314 if (dev->passthrough == NULL)
315 fz_throw(ctx, FZ_ERROR_ABORT, "Page found as color; stopping interpretation");
316 break;
317 }
318 }
319 s += ss;
320 }
321 }
322 fz_always(ctx)
323 fz_fin_cached_color_converter(ctx, &cc);
324 fz_catch(ctx)
325 fz_rethrow(ctx);
326 }
327 }
328
329
330 static void
331 fz_test_fill_image(fz_context *ctx, fz_device *dev_, fz_image *image, fz_matrix ctm, float alpha, fz_color_params color_params)
332 {
333 fz_test_device *dev = (fz_test_device*)dev_;
334
335 while (dev->resolved == 0) /* So we can break out */
336 {
337 fz_compressed_buffer *buffer;
338
339 if (*dev->is_color || !image->colorspace || fz_colorspace_is_gray(ctx, image->colorspace))
340 break;
341
342 if ((dev->options & FZ_TEST_OPT_IMAGES) == 0)
343 {
344 /* Don't test every pixel. Upgrade us from "black and white" to "probably color" */
345 if (*dev->is_color == 0)
346 *dev->is_color = 1;
347 dev->resolved = 1;
348 if (dev->passthrough == NULL)
349 fz_throw(ctx, FZ_ERROR_ABORT, "Page found as color; stopping interpretation");
350 break;
351 }
352
353 buffer = fz_compressed_image_buffer(ctx, image);
354 if (buffer && image->bpc == 8)
355 {
356 fz_stream *stream = fz_open_compressed_buffer(ctx, buffer);
357 fz_try(ctx)
358 fz_test_fill_compressed_8bpc_image(ctx, dev, image, stream, color_params);
359 fz_always(ctx)
360 fz_drop_stream(ctx, stream);
361 fz_catch(ctx)
362 fz_rethrow(ctx);
363 }
364 else
365 {
366 fz_pixmap *pix = fz_get_pixmap_from_image(ctx, image, NULL, NULL, 0, 0);
367 if (pix == NULL) /* Should never happen really, but... */
368 break;
369
370 fz_try(ctx)
371 fz_test_fill_other_image(ctx, dev, pix, color_params);
372 fz_always(ctx)
373 fz_drop_pixmap(ctx, pix);
374 fz_catch(ctx)
375 fz_rethrow(ctx);
376 }
377 break;
378 }
379 if (dev->passthrough)
380 fz_fill_image(ctx, dev->passthrough, image, ctm, alpha, color_params);
381 }
382
383 static void
384 fz_test_fill_image_mask(fz_context *ctx, fz_device *dev_, fz_image *image, fz_matrix ctm,
385 fz_colorspace *colorspace, const float *color, float alpha, fz_color_params color_params)
386 {
387 fz_test_device *dev = (fz_test_device*)dev_;
388
389 if (dev->resolved == 0)
390 {
391 /* We assume that at least some of the image pixels are non-zero */
392 fz_test_color(ctx, dev, colorspace, color, color_params);
393 }
394 if (dev->passthrough)
395 fz_fill_image_mask(ctx, dev->passthrough, image, ctm, colorspace, color, alpha, color_params);
396 }
397
398 static void
399 fz_test_clip_path(fz_context *ctx, fz_device *dev_, const fz_path *path, int even_odd, fz_matrix ctm, fz_rect scissor)
400 {
401 fz_test_device *dev = (fz_test_device*)dev_;
402
403 if (dev->passthrough)
404 fz_clip_path(ctx, dev->passthrough, path, even_odd, ctm, scissor);
405 }
406
407 static void
408 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)
409 {
410 fz_test_device *dev = (fz_test_device*)dev_;
411
412 if (dev->passthrough)
413 fz_clip_stroke_path(ctx, dev->passthrough, path, stroke, ctm, scissor);
414 }
415
416 static void
417 fz_test_clip_text(fz_context *ctx, fz_device *dev_, const fz_text *text, fz_matrix ctm, fz_rect scissor)
418 {
419 fz_test_device *dev = (fz_test_device*)dev_;
420
421 if (dev->passthrough)
422 fz_clip_text(ctx, dev->passthrough, text, ctm, scissor);
423 }
424
425 static void
426 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)
427 {
428 fz_test_device *dev = (fz_test_device*)dev_;
429
430 if (dev->passthrough)
431 fz_clip_stroke_text(ctx, dev->passthrough, text, stroke, ctm, scissor);
432 }
433
434 static void
435 fz_test_ignore_text(fz_context *ctx, fz_device *dev_, const fz_text *text, fz_matrix ctm)
436 {
437 fz_test_device *dev = (fz_test_device*)dev_;
438
439 if (dev->passthrough)
440 fz_ignore_text(ctx, dev->passthrough, text, ctm);
441 }
442
443 static void
444 fz_test_clip_image_mask(fz_context *ctx, fz_device *dev_, fz_image *img, fz_matrix ctm, fz_rect scissor)
445 {
446 fz_test_device *dev = (fz_test_device*)dev_;
447
448 if (dev->passthrough)
449 fz_clip_image_mask(ctx, dev->passthrough, img, ctm, scissor);
450 }
451
452 static void
453 fz_test_pop_clip(fz_context *ctx, fz_device *dev_)
454 {
455 fz_test_device *dev = (fz_test_device*)dev_;
456
457 if (dev->passthrough)
458 fz_pop_clip(ctx, dev->passthrough);
459 }
460
461 static void
462 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)
463 {
464 fz_test_device *dev = (fz_test_device*)dev_;
465
466 if (dev->passthrough)
467 fz_begin_mask(ctx, dev->passthrough, rect, luminosity, cs, bc, color_params);
468 }
469
470 static void
471 fz_test_end_mask(fz_context *ctx, fz_device *dev_, fz_function *tr)
472 {
473 fz_test_device *dev = (fz_test_device*)dev_;
474
475 if (dev->passthrough)
476 fz_end_mask_tr(ctx, dev->passthrough, tr);
477 }
478
479 static void
480 fz_test_begin_group(fz_context *ctx, fz_device *dev_, fz_rect rect, fz_colorspace *cs, int isolated, int knockout, int blendmode, float alpha)
481 {
482 fz_test_device *dev = (fz_test_device*)dev_;
483
484 if (dev->passthrough)
485 fz_begin_group(ctx, dev->passthrough, rect, cs, isolated, knockout, blendmode, alpha);
486 }
487
488 static void
489 fz_test_end_group(fz_context *ctx, fz_device *dev_)
490 {
491 fz_test_device *dev = (fz_test_device*)dev_;
492
493 if (dev->passthrough)
494 fz_end_group(ctx, dev->passthrough);
495 }
496
497 static int
498 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, int doc_id)
499 {
500 fz_test_device *dev = (fz_test_device*)dev_;
501
502 if (dev->passthrough)
503 return fz_begin_tile_tid(ctx, dev->passthrough, area, view, xstep, ystep, ctm, id, doc_id);
504 else
505 return 0;
506 }
507
508 static void
509 fz_test_end_tile(fz_context *ctx, fz_device *dev_)
510 {
511 fz_test_device *dev = (fz_test_device*)dev_;
512
513 if (dev->passthrough)
514 fz_end_tile(ctx, dev->passthrough);
515 }
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 }