diff 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
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mupdf-source/source/fitz/test-device.c	Mon Sep 15 11:43:07 2025 +0200
@@ -0,0 +1,556 @@
+// Copyright (C) 2004-2024 Artifex Software, Inc.
+//
+// This file is part of MuPDF.
+//
+// MuPDF is free software: you can redistribute it and/or modify it under the
+// terms of the GNU Affero General Public License as published by the Free
+// Software Foundation, either version 3 of the License, or (at your option)
+// any later version.
+//
+// MuPDF is distributed in the hope that it will be useful, but WITHOUT ANY
+// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+// FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+// details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with MuPDF. If not, see <https://www.gnu.org/licenses/agpl-3.0.en.html>
+//
+// Alternative licensing terms are available from the licensor.
+// For commercial licensing, see <https://www.artifex.com/> or contact
+// Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco,
+// CA 94129, USA, for further information.
+
+#include "mupdf/fitz.h"
+
+#include "color-imp.h"
+
+typedef struct
+{
+	fz_device super;
+	int *is_color;
+	float threshold;
+	int options;
+	fz_device *passthrough;
+	int resolved;
+} fz_test_device;
+
+static int
+is_rgb_color(float threshold, float r, float g, float b)
+{
+	float rg_diff = fz_abs(r - g);
+	float rb_diff = fz_abs(r - b);
+	float gb_diff = fz_abs(g - b);
+	return rg_diff > threshold || rb_diff > threshold || gb_diff > threshold;
+}
+
+static int
+is_rgb_color_u8(int threshold_u8, int r, int g, int b)
+{
+	int rg_diff = fz_absi(r - g);
+	int rb_diff = fz_absi(r - b);
+	int gb_diff = fz_absi(g - b);
+	return rg_diff > threshold_u8 || rb_diff > threshold_u8 || gb_diff > threshold_u8;
+}
+
+static void
+fz_test_color(fz_context *ctx, fz_test_device *t, fz_colorspace *colorspace, const float *color, fz_color_params color_params)
+{
+	if (!*t->is_color && colorspace && fz_colorspace_type(ctx, colorspace) != FZ_COLORSPACE_GRAY)
+	{
+		if (colorspace == fz_device_rgb(ctx))
+		{
+			if (is_rgb_color(t->threshold, color[0], color[1], color[2]))
+			{
+				*t->is_color = 2;
+				t->resolved = 1;
+				if (t->passthrough == NULL)
+					fz_throw(ctx, FZ_ERROR_ABORT, "Page found as color; stopping interpretation");
+			}
+		}
+		else
+		{
+			float rgb[3];
+			fz_convert_color(ctx, colorspace, color, fz_device_rgb(ctx), rgb, NULL, color_params);
+			if (is_rgb_color(t->threshold, rgb[0], rgb[1], rgb[2]))
+			{
+				*t->is_color = 2;
+				t->resolved = 1;
+				if (t->passthrough == NULL)
+				{
+					fz_throw(ctx, FZ_ERROR_ABORT, "Page found as color; stopping interpretation");
+				}
+			}
+		}
+	}
+}
+
+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)
+{
+	fz_test_device *dev = (fz_test_device*)dev_;
+
+	if (dev->resolved == 0 && alpha != 0.0f)
+		fz_test_color(ctx, dev, colorspace, color, color_params);
+	if (dev->passthrough)
+		fz_fill_path(ctx, dev->passthrough, path, even_odd, ctm, colorspace, color, alpha, color_params);
+}
+
+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)
+{
+	fz_test_device *dev = (fz_test_device*)dev_;
+
+	if (dev->resolved == 0 && alpha != 0.0f)
+		fz_test_color(ctx, dev, colorspace, color, color_params);
+	if (dev->passthrough)
+		fz_stroke_path(ctx, dev->passthrough, path, stroke, ctm, colorspace, color, alpha, color_params);
+}
+
+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)
+{
+	fz_test_device *dev = (fz_test_device*)dev_;
+
+	if (dev->resolved == 0 && alpha != 0.0f)
+		fz_test_color(ctx, dev, colorspace, color, color_params);
+	if (dev->passthrough)
+		fz_fill_text(ctx, dev->passthrough, text, ctm, colorspace, color, alpha, color_params);
+}
+
+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)
+{
+	fz_test_device *dev = (fz_test_device*)dev_;
+
+	if (dev->resolved == 0 && alpha != 0.0f)
+		fz_test_color(ctx, dev, colorspace, color, color_params);
+	if (dev->passthrough)
+		fz_stroke_text(ctx, dev->passthrough, text, stroke, ctm, colorspace, color, alpha, color_params);
+}
+
+struct shadearg
+{
+	fz_test_device *dev;
+	fz_shade *shade;
+	fz_color_params color_params;
+};
+
+static void
+prepare_vertex(fz_context *ctx, void *arg_, fz_vertex *v, const float *color)
+{
+	struct shadearg *arg = arg_;
+	fz_test_device *dev = arg->dev;
+	fz_shade *shade = arg->shade;
+	if (shade->function_stride == 0)
+		fz_test_color(ctx, dev, shade->colorspace, color, arg->color_params);
+}
+
+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)
+{
+	fz_test_device *dev = (fz_test_device*)dev_;
+
+	if (dev->resolved == 0)
+	{
+		if ((dev->options & FZ_TEST_OPT_SHADINGS) == 0)
+		{
+			if (fz_colorspace_type(ctx, shade->colorspace) != FZ_COLORSPACE_GRAY)
+			{
+				/* Don't test every pixel. Upgrade us from "black and white" to "probably color" */
+				if (*dev->is_color == 0)
+					*dev->is_color = 1;
+				dev->resolved = 1;
+				if (dev->passthrough == NULL)
+					fz_throw(ctx, FZ_ERROR_ABORT, "Page found as color; stopping interpretation");
+			}
+		}
+		else
+		{
+			int stride = shade->function_stride;
+			if (stride)
+			{
+				int i;
+				for (i = 0; i < 256; i++)
+					fz_test_color(ctx, dev, shade->colorspace, &shade->function[i*stride], color_params);
+			}
+			else
+			{
+				struct shadearg arg;
+				arg.dev = dev;
+				arg.shade = shade;
+				arg.color_params = color_params;
+				fz_process_shade(ctx, shade, ctm, fz_device_current_scissor(ctx, dev_), prepare_vertex, NULL, &arg);
+			}
+		}
+	}
+	if (dev->passthrough)
+		fz_fill_shade(ctx, dev->passthrough, shade, ctm, alpha, color_params);
+}
+
+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)
+{
+	unsigned int count = (unsigned int)image->w * (unsigned int)image->h;
+	unsigned int i;
+
+	if (image->colorspace == fz_device_rgb(ctx))
+	{
+		int threshold_u8 = dev->threshold * 255;
+		for (i = 0; i < count; i++)
+		{
+			int r = fz_read_byte(ctx, stream);
+			int g = fz_read_byte(ctx, stream);
+			int b = fz_read_byte(ctx, stream);
+			if (is_rgb_color_u8(threshold_u8, r, g, b))
+			{
+				*dev->is_color = 1;
+				dev->resolved = 1;
+				if (dev->passthrough == NULL)
+					fz_throw(ctx, FZ_ERROR_ABORT, "Page found as color; stopping interpretation");
+				break;
+			}
+		}
+	}
+	else
+	{
+		fz_color_converter cc;
+		unsigned int n = (unsigned int)image->n;
+
+		fz_init_cached_color_converter(ctx, &cc, image->colorspace, fz_device_rgb(ctx), NULL, NULL, color_params);
+
+		fz_try(ctx)
+		{
+			for (i = 0; i < count; i++)
+			{
+				float cs[FZ_MAX_COLORS];
+				float ds[FZ_MAX_COLORS];
+				unsigned int k;
+
+				for (k = 0; k < n; k++)
+					cs[k] = fz_read_byte(ctx, stream) / 255.0f;
+
+				cc.convert(ctx, &cc, ds, cs);
+
+				if (is_rgb_color(dev->threshold, ds[0], ds[1], ds[2]))
+				{
+					*dev->is_color = 1;
+					dev->resolved = 1;
+					if (dev->passthrough == NULL)
+						fz_throw(ctx, FZ_ERROR_ABORT, "Page found as color; stopping interpretation");
+					break;
+				}
+			}
+		}
+		fz_always(ctx)
+			fz_fin_cached_color_converter(ctx, &cc);
+		fz_catch(ctx)
+			fz_rethrow(ctx);
+	}
+}
+
+static void
+fz_test_fill_other_image(fz_context *ctx, fz_test_device *dev, fz_pixmap *pix, fz_color_params color_params)
+{
+	unsigned int count, i, k, h, sa;
+	size_t ss;
+	unsigned char *s;
+
+	count = pix->w;
+	h = pix->h;
+	s = pix->samples;
+	sa = pix->alpha;
+	ss = pix->stride - pix->w * (size_t)pix->n;
+
+	if (pix->colorspace == fz_device_rgb(ctx))
+	{
+		int threshold_u8 = dev->threshold * 255;
+		while (h--)
+		{
+			for (i = 0; i < count; i++)
+			{
+				if ((!sa || s[3] != 0) && is_rgb_color_u8(threshold_u8, s[0], s[1], s[2]))
+				{
+					*dev->is_color = 1;
+					dev->resolved = 1;
+					if (dev->passthrough == NULL)
+						fz_throw(ctx, FZ_ERROR_ABORT, "Page found as color; stopping interpretation");
+					break;
+				}
+				s += 3 + sa;
+			}
+			s += ss;
+		}
+	}
+	else
+	{
+		fz_color_converter cc;
+		unsigned int n = (unsigned int)pix->n-1;
+
+		fz_init_cached_color_converter(ctx, &cc, pix->colorspace, fz_device_rgb(ctx), NULL, NULL, color_params);
+
+		fz_try(ctx)
+		{
+			while (h--)
+			{
+				for (i = 0; i < count; i++)
+				{
+					float cs[FZ_MAX_COLORS];
+					float ds[FZ_MAX_COLORS];
+
+					for (k = 0; k < n; k++)
+						cs[k] = (*s++) / 255.0f;
+					if (sa && *s++ == 0)
+						continue;
+
+					cc.convert(ctx, &cc, ds, cs);
+
+					if (is_rgb_color(dev->threshold, ds[0], ds[1], ds[2]))
+					{
+						*dev->is_color = 1;
+						dev->resolved = 1;
+						if (dev->passthrough == NULL)
+							fz_throw(ctx, FZ_ERROR_ABORT, "Page found as color; stopping interpretation");
+						break;
+					}
+				}
+				s += ss;
+			}
+		}
+		fz_always(ctx)
+			fz_fin_cached_color_converter(ctx, &cc);
+		fz_catch(ctx)
+			fz_rethrow(ctx);
+	}
+}
+
+
+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)
+{
+	fz_test_device *dev = (fz_test_device*)dev_;
+
+	while (dev->resolved == 0) /* So we can break out */
+	{
+		fz_compressed_buffer *buffer;
+
+		if (*dev->is_color || !image->colorspace || fz_colorspace_is_gray(ctx, image->colorspace))
+			break;
+
+		if ((dev->options & FZ_TEST_OPT_IMAGES) == 0)
+		{
+			/* Don't test every pixel. Upgrade us from "black and white" to "probably color" */
+			if (*dev->is_color == 0)
+				*dev->is_color = 1;
+			dev->resolved = 1;
+			if (dev->passthrough == NULL)
+				fz_throw(ctx, FZ_ERROR_ABORT, "Page found as color; stopping interpretation");
+			break;
+		}
+
+		buffer = fz_compressed_image_buffer(ctx, image);
+		if (buffer && image->bpc == 8)
+		{
+			fz_stream *stream = fz_open_compressed_buffer(ctx, buffer);
+			fz_try(ctx)
+				fz_test_fill_compressed_8bpc_image(ctx, dev, image, stream, color_params);
+			fz_always(ctx)
+				fz_drop_stream(ctx, stream);
+			fz_catch(ctx)
+				fz_rethrow(ctx);
+		}
+		else
+		{
+			fz_pixmap *pix = fz_get_pixmap_from_image(ctx, image, NULL, NULL, 0, 0);
+			if (pix == NULL) /* Should never happen really, but... */
+				break;
+
+			fz_try(ctx)
+				fz_test_fill_other_image(ctx, dev, pix, color_params);
+			fz_always(ctx)
+				fz_drop_pixmap(ctx, pix);
+			fz_catch(ctx)
+				fz_rethrow(ctx);
+		}
+		break;
+	}
+	if (dev->passthrough)
+		fz_fill_image(ctx, dev->passthrough, image, ctm, alpha, color_params);
+}
+
+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)
+{
+	fz_test_device *dev = (fz_test_device*)dev_;
+
+	if (dev->resolved == 0)
+	{
+		/* We assume that at least some of the image pixels are non-zero */
+		fz_test_color(ctx, dev, colorspace, color, color_params);
+	}
+	if (dev->passthrough)
+		fz_fill_image_mask(ctx, dev->passthrough, image, ctm, colorspace, color, alpha, color_params);
+}
+
+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)
+{
+	fz_test_device *dev = (fz_test_device*)dev_;
+
+	if (dev->passthrough)
+		fz_clip_path(ctx, dev->passthrough, path, even_odd, ctm, scissor);
+}
+
+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)
+{
+	fz_test_device *dev = (fz_test_device*)dev_;
+
+	if (dev->passthrough)
+		fz_clip_stroke_path(ctx, dev->passthrough, path, stroke, ctm, scissor);
+}
+
+static void
+fz_test_clip_text(fz_context *ctx, fz_device *dev_, const fz_text *text, fz_matrix ctm, fz_rect scissor)
+{
+	fz_test_device *dev = (fz_test_device*)dev_;
+
+	if (dev->passthrough)
+		fz_clip_text(ctx, dev->passthrough, text, ctm, scissor);
+}
+
+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)
+{
+	fz_test_device *dev = (fz_test_device*)dev_;
+
+	if (dev->passthrough)
+		fz_clip_stroke_text(ctx, dev->passthrough, text, stroke, ctm, scissor);
+}
+
+static void
+fz_test_ignore_text(fz_context *ctx, fz_device *dev_, const fz_text *text, fz_matrix ctm)
+{
+	fz_test_device *dev = (fz_test_device*)dev_;
+
+	if (dev->passthrough)
+		fz_ignore_text(ctx, dev->passthrough, text, ctm);
+}
+
+static void
+fz_test_clip_image_mask(fz_context *ctx, fz_device *dev_, fz_image *img, fz_matrix ctm, fz_rect scissor)
+{
+	fz_test_device *dev = (fz_test_device*)dev_;
+
+	if (dev->passthrough)
+		fz_clip_image_mask(ctx, dev->passthrough, img, ctm, scissor);
+}
+
+static void
+fz_test_pop_clip(fz_context *ctx, fz_device *dev_)
+{
+	fz_test_device *dev = (fz_test_device*)dev_;
+
+	if (dev->passthrough)
+		fz_pop_clip(ctx, dev->passthrough);
+}
+
+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)
+{
+	fz_test_device *dev = (fz_test_device*)dev_;
+
+	if (dev->passthrough)
+		fz_begin_mask(ctx, dev->passthrough, rect, luminosity, cs, bc, color_params);
+}
+
+static void
+fz_test_end_mask(fz_context *ctx, fz_device *dev_, fz_function *tr)
+{
+	fz_test_device *dev = (fz_test_device*)dev_;
+
+	if (dev->passthrough)
+		fz_end_mask_tr(ctx, dev->passthrough, tr);
+}
+
+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)
+{
+	fz_test_device *dev = (fz_test_device*)dev_;
+
+	if (dev->passthrough)
+		fz_begin_group(ctx, dev->passthrough, rect, cs, isolated, knockout, blendmode, alpha);
+}
+
+static void
+fz_test_end_group(fz_context *ctx, fz_device *dev_)
+{
+	fz_test_device *dev = (fz_test_device*)dev_;
+
+	if (dev->passthrough)
+		fz_end_group(ctx, dev->passthrough);
+}
+
+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, int doc_id)
+{
+	fz_test_device *dev = (fz_test_device*)dev_;
+
+	if (dev->passthrough)
+		return fz_begin_tile_tid(ctx, dev->passthrough, area, view, xstep, ystep, ctm, id, doc_id);
+	else
+		return 0;
+}
+
+static void
+fz_test_end_tile(fz_context *ctx, fz_device *dev_)
+{
+	fz_test_device *dev = (fz_test_device*)dev_;
+
+	if (dev->passthrough)
+		fz_end_tile(ctx, dev->passthrough);
+}
+
+fz_device *
+fz_new_test_device(fz_context *ctx, int *is_color, float threshold, int options, fz_device *passthrough)
+{
+	fz_test_device *dev = fz_new_derived_device(ctx, fz_test_device);
+
+	dev->super.fill_path = fz_test_fill_path;
+	dev->super.stroke_path = fz_test_stroke_path;
+	dev->super.fill_text = fz_test_fill_text;
+	dev->super.stroke_text = fz_test_stroke_text;
+	dev->super.fill_shade = fz_test_fill_shade;
+	dev->super.fill_image = fz_test_fill_image;
+	dev->super.fill_image_mask = fz_test_fill_image_mask;
+
+	if (passthrough)
+	{
+		dev->super.clip_path = fz_test_clip_path;
+		dev->super.clip_stroke_path = fz_test_clip_stroke_path;
+		dev->super.clip_text = fz_test_clip_text;
+		dev->super.clip_stroke_text = fz_test_clip_stroke_text;
+		dev->super.ignore_text = fz_test_ignore_text;
+		dev->super.clip_image_mask = fz_test_clip_image_mask;
+		dev->super.pop_clip = fz_test_pop_clip;
+		dev->super.begin_mask = fz_test_begin_mask;
+		dev->super.end_mask = fz_test_end_mask;
+		dev->super.begin_group = fz_test_begin_group;
+		dev->super.end_group = fz_test_end_group;
+		dev->super.begin_tile = fz_test_begin_tile;
+		dev->super.end_tile = fz_test_end_tile;
+	}
+
+	dev->is_color = is_color;
+	dev->options = options;
+	dev->threshold = threshold;
+	dev->passthrough = passthrough;
+	dev->resolved = 0;
+
+	*dev->is_color = 0;
+
+	return (fz_device*)dev;
+}