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