diff mupdf-source/source/fitz/draw-mesh.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/draw-mesh.c	Mon Sep 15 11:43:07 2025 +0200
@@ -0,0 +1,517 @@
+// Copyright (C) 2004-2021 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"
+#include "draw-imp.h"
+#include "pixmap-imp.h"
+
+#include <assert.h>
+#include <math.h>
+
+enum { MAXN = 2 + FZ_MAX_COLORS };
+
+static void paint_scan(fz_pixmap *FZ_RESTRICT pix, int y, int fx0, int fx1, int cx0, int cx1, const int *FZ_RESTRICT v0, const int *FZ_RESTRICT v1, int n)
+{
+	unsigned char *p;
+	int c[MAXN], dc[MAXN];
+	int k, w;
+	float div, mul;
+	int x0, x1, pa;
+
+	/* Ensure that fx0 is left edge, and fx1 is right */
+	if (fx0 > fx1)
+	{
+		const int *v;
+		int t = fx0; fx0 = fx1; fx1 = t;
+		v = v0; v0 = v1; v1 = v;
+	}
+	else if (fx0 == fx1)
+		return;
+
+	/* Clip fx0, fx1 to range */
+	if (fx0 >= cx1)
+		return;
+	if (fx1 <= cx0)
+		return;
+	x0 = (fx0 > cx0 ? fx0 : cx0);
+	x1 = (fx1 < cx1 ? fx1 : cx1);
+
+	w = x1 - x0;
+	if (w == 0)
+		return;
+
+	div = 1.0f / (fx1 - fx0);
+	mul = (x0 - fx0);
+	for (k = 0; k < n; k++)
+	{
+		dc[k] = (v1[k] - v0[k]) * div;
+		c[k] = v0[k] + dc[k] * mul;
+	}
+
+	p = pix->samples + ((x0 - pix->x) * pix->n) + ((y - pix->y) * pix->stride);
+	pa = pix->alpha;
+	do
+	{
+		for (k = 0; k < n; k++)
+		{
+			*p++ = c[k]>>16;
+			c[k] += dc[k];
+		}
+		if (pa)
+			*p++ = 255;
+	}
+	while (--w);
+}
+
+typedef struct
+{
+	float x;
+	float dx;
+	int v[2*MAXN];
+} edge_data;
+
+static inline void prepare_edge(const float *FZ_RESTRICT vtop, const float *FZ_RESTRICT vbot, edge_data *FZ_RESTRICT edge, float y, int n)
+{
+	float r = 1.0f / (vbot[1] - vtop[1]);
+	float t = (y - vtop[1]) * r;
+	float diff = vbot[0] - vtop[0];
+	int i;
+
+	edge->x = vtop[0] + diff * t;
+	edge->dx = diff * r;
+
+	for (i = 0; i < n; i++)
+	{
+		diff = vbot[i+2] - vtop[i+2];
+		edge->v[i] = (int)(65536.0f * (vtop[i+2] + diff * t));
+		edge->v[i+MAXN] = (int)(65536.0f * diff * r);
+	}
+}
+
+static inline void step_edge(edge_data *edge, int n)
+{
+	int i;
+
+	edge->x += edge->dx;
+
+	for (i = 0; i < n; i++)
+	{
+		edge->v[i] += edge->v[i + MAXN];
+	}
+}
+
+static void
+fz_paint_triangle(fz_pixmap *pix, float *v[3], int n, fz_irect bbox)
+{
+	edge_data e0, e1;
+	int top, mid, bot;
+	float y, y1;
+	int minx, maxx;
+
+	top = bot = 0;
+	if (v[1][1] < v[0][1]) top = 1; else bot = 1;
+	if (v[2][1] < v[top][1]) top = 2;
+	else if (v[2][1] > v[bot][1]) bot = 2;
+	if (v[top][1] == v[bot][1]) return;
+
+	/* Test if the triangle is completely outside the scissor rect */
+	if (v[bot][1] < bbox.y0) return;
+	if (v[top][1] > bbox.y1) return;
+
+	/* Magic! Ensure that mid/top/bot are all different */
+	mid = 3^top^bot;
+
+	assert(top != bot && top != mid && mid != bot);
+
+	minx = fz_maxi(bbox.x0, pix->x);
+	maxx = fz_mini(bbox.x1, pix->x + pix->w);
+
+	y = ceilf(fz_max(bbox.y0, v[top][1]));
+	y1 = ceilf(fz_min(bbox.y1, v[mid][1]));
+
+	n -= 2;
+	prepare_edge(v[top], v[bot], &e0, y, n);
+	if (y < y1)
+	{
+		prepare_edge(v[top], v[mid], &e1, y, n);
+
+		do
+		{
+			paint_scan(pix, y, (int)e0.x, (int)e1.x, minx, maxx, &e0.v[0], &e1.v[0], n);
+			step_edge(&e0, n);
+			step_edge(&e1, n);
+			y ++;
+		}
+		while (y < y1);
+	}
+
+	y1 = ceilf(fz_min(bbox.y1, v[bot][1]));
+	if (y < y1)
+	{
+		prepare_edge(v[mid], v[bot], &e1, y, n);
+
+		do
+		{
+			paint_scan(pix, y, (int)e0.x, (int)e1.x, minx, maxx, &e0.v[0], &e1.v[0], n);
+			y ++;
+			if (y >= y1)
+				break;
+			step_edge(&e0, n);
+			step_edge(&e1, n);
+		}
+		while (1);
+	}
+}
+
+struct paint_tri_data
+{
+	const fz_shade *shade;
+	fz_pixmap *dest;
+	fz_irect bbox;
+	fz_color_converter cc;
+};
+
+static void
+prepare_mesh_vertex(fz_context *ctx, void *arg, fz_vertex *v, const float *input)
+{
+	struct paint_tri_data *ptd = (struct paint_tri_data *)arg;
+	const fz_shade *shade = ptd->shade;
+	fz_pixmap *dest = ptd->dest;
+	float *output = v->c;
+	int i;
+
+	if (shade->function_stride)
+	{
+		float f = input[0];
+		if (shade->type >= 4 && shade->type <= 7)
+			f = (f - shade->u.m.c0[0]) / (shade->u.m.c1[0] - shade->u.m.c0[0]);
+		output[0] = f * 255;
+	}
+	else
+	{
+		int n = fz_colorspace_n(ctx, dest->colorspace);
+		int a = dest->alpha;
+		int m = dest->n - a;
+		for (i = n; i < m; i++)
+			output[i] = 0;
+		if (ptd->cc.convert)
+			ptd->cc.convert(ctx, &ptd->cc, input, output);
+		for (i = 0; i < m; i++)
+			output[i] *= 255;
+		if (a)
+			output[m] = 255;
+	}
+}
+
+static void
+do_paint_tri(fz_context *ctx, void *arg, fz_vertex *av, fz_vertex *bv, fz_vertex *cv)
+{
+	struct paint_tri_data *ptd = (struct paint_tri_data *)arg;
+	float *vertices[3];
+	fz_pixmap *dest;
+
+	vertices[0] = (float *)av;
+	vertices[1] = (float *)bv;
+	vertices[2] = (float *)cv;
+
+	dest = ptd->dest;
+	fz_paint_triangle(dest, vertices, 2 + dest->n - dest->alpha, ptd->bbox);
+}
+
+struct fz_shade_color_cache
+{
+	fz_colorspace *src;
+	fz_colorspace *dst;
+	fz_color_params params;
+	int full;
+	fz_color_converter cached;
+	fz_colorspace *src2;
+	fz_colorspace *dst2;
+	fz_color_params params2;
+	int full2;
+	fz_color_converter cached2;
+};
+
+void
+fz_drop_shade_color_cache(fz_context *ctx, fz_shade_color_cache *cache)
+{
+	if (cache == NULL)
+		return;
+
+	fz_drop_colorspace(ctx, cache->src);
+	fz_drop_colorspace(ctx, cache->dst);
+	if (cache->full)
+		fz_fin_cached_color_converter(ctx, &cache->cached);
+
+	fz_drop_colorspace(ctx, cache->src2);
+	fz_drop_colorspace(ctx, cache->dst2);
+	if (cache->full2)
+		fz_drop_color_converter(ctx, &cache->cached2);
+
+	fz_free(ctx, cache);
+}
+
+void
+fz_paint_shade(fz_context *ctx, fz_shade *shade, fz_colorspace *colorspace, fz_matrix ctm, fz_pixmap *dest, fz_color_params color_params, fz_irect bbox, const fz_overprint *eop, fz_shade_color_cache **color_cache)
+{
+	unsigned char clut[256][FZ_MAX_COLORS];
+	fz_pixmap *temp = NULL;
+	fz_pixmap *conv = NULL;
+	fz_color_converter cc = { 0 };
+	float color[FZ_MAX_COLORS];
+	struct paint_tri_data ptd = { 0 };
+	int i, k;
+	fz_matrix local_ctm;
+	fz_shade_color_cache *cache = NULL;
+	int recache = 0;
+	int recache2 = 0;
+	int stride = shade->function_stride;
+
+	fz_var(temp);
+	fz_var(conv);
+	fz_var(recache);
+	fz_var(recache2);
+	fz_var(cc);
+
+	if (colorspace == NULL)
+		colorspace = shade->colorspace;
+
+	if (color_cache)
+	{
+		cache = *color_cache;
+		if (cache == NULL)
+			*color_cache = cache = fz_malloc_struct(ctx, fz_shade_color_cache);
+	}
+
+	fz_try(ctx)
+	{
+		local_ctm = fz_concat(shade->matrix, ctm);
+
+		if (stride)
+		{
+			/* We need to use alpha = 1 here, because the shade might not fill the bbox. */
+			temp = fz_new_pixmap_with_bbox(ctx, fz_device_gray(ctx), bbox, NULL, 1);
+			fz_clear_pixmap(ctx, temp);
+		}
+		else
+		{
+			temp = dest;
+		}
+
+		ptd.dest = temp;
+		ptd.shade = shade;
+		ptd.bbox = bbox;
+
+		if (temp->colorspace)
+		{
+			if (cache && cache->full && cache->src == colorspace && cache->dst == temp->colorspace &&
+				cache->params.op == color_params.op &&
+				cache->params.opm == color_params.opm &&
+				cache->params.ri == color_params.ri)
+			{
+				ptd.cc = cache->cached;
+				cache->full = 0;
+			}
+			else
+				fz_init_cached_color_converter(ctx, &ptd.cc, colorspace, temp->colorspace, temp->seps, NULL, color_params);
+
+			/* Drop the existing contents of the cache. */
+			if (cache)
+			{
+				fz_drop_colorspace(ctx, cache->src);
+				cache->src = NULL;
+				fz_drop_colorspace(ctx, cache->dst);
+				cache->dst = NULL;
+				if (cache->full)
+					fz_fin_cached_color_converter(ctx, &cache->cached);
+				cache->full = 0;
+
+				/* Remember that we can put stuff back into the cache. */
+				recache = 1;
+			}
+		}
+
+		fz_process_shade(ctx, shade, local_ctm, fz_rect_from_irect(bbox), prepare_mesh_vertex, &do_paint_tri, &ptd);
+
+		if (stride)
+		{
+			/* If the shade is defined in a deviceN (or separation,
+			 * which is the same internally to MuPDF) space, then
+			 * we need to render it in deviceN before painting it
+			 * to the destination. If not, we are free to render it
+			 * direct to the target. */
+			if (fz_colorspace_is_device_n(ctx, colorspace))
+			{
+				/* We've drawn it as greyscale, with the values being
+				 * the input to the function. Now make DevN version
+				 * by mapping that greyscale through the function.
+				 * This seems inefficient, but it's actually required,
+				 * because we need to apply the function lookup POST
+				 * interpolation in the do_paint_tri routines, not
+				 * before it to avoid problems with some test files
+				 * (tests/GhentV3.0/061_Shading_x1a.pdf for example).
+				 */
+				unsigned char *s = temp->samples;
+				unsigned char *d;
+				int hh = temp->h;
+				int n = fz_colorspace_n(ctx, colorspace);
+
+				/* alpha = 1 here for the same reason as earlier */
+				conv = fz_new_pixmap_with_bbox(ctx, colorspace, bbox, NULL, 1);
+				d = conv->samples;
+				while (hh--)
+				{
+					int len = temp->w;
+					while (len--)
+					{
+						int v = *s++;
+						int a = *s++;
+						const float *f = &shade->function[v*stride];
+						for (k = 0; k < n; k++)
+							*d++ = fz_clampi(255 * f[k], 0, 255);
+						*d++ = a;
+					}
+					d += conv->stride - conv->w * (size_t)conv->n;
+					s += temp->stride - temp->w * (size_t)temp->n;
+				}
+				fz_drop_pixmap(ctx, temp);
+				temp = conv;
+				conv = NULL;
+
+				/* Now Change from our device_n colorspace into the target colorspace/spots. */
+				conv = fz_clone_pixmap_area_with_different_seps(ctx, temp, NULL, dest->colorspace, dest->seps, color_params, NULL);
+			}
+			else
+			{
+				unsigned char *s = temp->samples;
+				unsigned char *d;
+				int da;
+				int sa = temp->alpha;
+				int hh = temp->h;
+				int cn = fz_colorspace_n(ctx, colorspace);
+				int m = dest->n - dest->alpha;
+				int n = fz_colorspace_n(ctx, dest->colorspace);
+
+				if (dest->colorspace)
+				{
+					if (cache && cache->full2 && cache->src2 == colorspace && cache->dst2 == dest->colorspace &&
+						cache->params2.op == color_params.op &&
+						cache->params2.opm == color_params.opm &&
+						cache->params2.ri == color_params.ri)
+					{
+						cc = cache->cached2;
+						cache->full2 = 0;
+					}
+					else
+						fz_find_color_converter(ctx, &cc, colorspace, dest->colorspace, dest->seps, NULL, color_params);
+
+					/* Drop the existing contents of the cache */
+					if (cache)
+					{
+						fz_drop_colorspace(ctx, cache->src2);
+						cache->src2 = NULL;
+						fz_drop_colorspace(ctx, cache->dst2);
+						cache->dst2 = NULL;
+						if (cache->full2)
+							fz_drop_color_converter(ctx, &cache->cached2);
+						cache->full2 = 0;
+
+						/* Remember that we can put stuff back into the cache. */
+						recache2 = 1;
+					}
+					for (i = 0; i < 256; i++)
+					{
+						cc.convert(ctx, &cc, &shade->function[i*stride], color);
+						for (k = 0; k < n; k++)
+							clut[i][k] = color[k] * 255;
+						for (; k < m; k++)
+							clut[i][k] = 0;
+						clut[i][k] = shade->function[i*stride + cn] * 255;
+					}
+				}
+				else
+				{
+					for (i = 0; i < 256; i++)
+					{
+						for (k = 0; k < m; k++)
+							clut[i][k] = 0;
+						clut[i][k] = shade->function[i*stride + cn] * 255;
+					}
+				}
+
+				conv = fz_new_pixmap_with_bbox(ctx, dest->colorspace, bbox, dest->seps, 1);
+				d = conv->samples;
+				da = conv->alpha;
+				while (hh--)
+				{
+					int len = temp->w;
+					while (len--)
+					{
+						int v = *s++;
+						int a = (da ? clut[v][conv->n - 1] : 255);
+						if (sa)
+							a = fz_mul255(*s++, a);
+						for (k = 0; k < conv->n - da; k++)
+							*d++ = fz_mul255(clut[v][k], a);
+						if (da)
+							*d++ = a;
+					}
+					d += conv->stride - conv->w * (size_t)conv->n;
+					s += temp->stride - temp->w * (size_t)temp->n;
+				}
+			}
+			fz_paint_pixmap_with_overprint(dest, conv, eop);
+		}
+	}
+	fz_always(ctx)
+	{
+		if (recache)
+		{
+			cache->src = fz_keep_colorspace(ctx, colorspace);
+			cache->dst = fz_keep_colorspace(ctx, temp->colorspace);
+			cache->params = color_params;
+			cache->cached = ptd.cc;
+			cache->full = 1;
+		}
+		else
+			fz_fin_cached_color_converter(ctx, &ptd.cc);
+		if (stride)
+		{
+			if (recache2)
+			{
+				cache->src2 = fz_keep_colorspace(ctx, colorspace);
+				cache->dst2 = fz_keep_colorspace(ctx, dest->colorspace);
+				cache->params2 = color_params;
+				cache->cached2 = cc;
+				cache->full2 = 1;
+			}
+			else
+				fz_drop_color_converter(ctx, &cc);
+			fz_drop_pixmap(ctx, temp);
+			fz_drop_pixmap(ctx, conv);
+		}
+	}
+	fz_catch(ctx)
+		fz_rethrow(ctx);
+}