comparison mupdf-source/source/xps/xps-gradient.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-2021 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 #include "xps-imp.h"
25
26 #include <string.h>
27 #include <math.h>
28 #include <float.h>
29 #include <stdlib.h>
30
31 #define MAX_STOPS 256
32
33 enum { SPREAD_PAD, SPREAD_REPEAT, SPREAD_REFLECT };
34
35 /*
36 * Parse a list of GradientStop elements.
37 * Fill the offset and color arrays, and
38 * return the number of stops parsed.
39 */
40
41 struct stop
42 {
43 float offset;
44 float r, g, b, a;
45 int index;
46 };
47
48 static int cmp_stop(const void *a, const void *b)
49 {
50 const struct stop *astop = a;
51 const struct stop *bstop = b;
52 float diff = astop->offset - bstop->offset;
53 if (diff < 0)
54 return -1;
55 if (diff > 0)
56 return 1;
57 return astop->index - bstop->index;
58 }
59
60 static inline float lerp(float a, float b, float x)
61 {
62 return a + (b - a) * x;
63 }
64
65 static int
66 xps_parse_gradient_stops(fz_context *ctx, xps_document *doc, char *base_uri, fz_xml *node,
67 struct stop *stops, int maxcount)
68 {
69 fz_colorspace *colorspace;
70 float sample[FZ_MAX_COLORS];
71 float rgb[3];
72 int before, after;
73 int count;
74 int i;
75
76 /* We may have to insert 2 extra stops when postprocessing */
77 maxcount -= 2;
78
79 count = 0;
80 while (node && count < maxcount)
81 {
82 if (fz_xml_is_tag(node, "GradientStop"))
83 {
84 char *offset = fz_xml_att(node, "Offset");
85 char *color = fz_xml_att(node, "Color");
86 if (offset && color)
87 {
88 stops[count].offset = fz_atof(offset);
89 stops[count].index = count;
90
91 xps_parse_color(ctx, doc, base_uri, color, &colorspace, sample);
92
93 fz_convert_color(ctx, colorspace, sample+1, fz_device_rgb(ctx), rgb, NULL, fz_default_color_params);
94
95 stops[count].r = rgb[0];
96 stops[count].g = rgb[1];
97 stops[count].b = rgb[2];
98 stops[count].a = sample[0];
99
100 count ++;
101 }
102 }
103 node = fz_xml_next(node);
104 }
105
106 if (count == 0)
107 {
108 fz_warn(ctx, "gradient brush has no gradient stops");
109 stops[0].offset = 0;
110 stops[0].r = 0;
111 stops[0].g = 0;
112 stops[0].b = 0;
113 stops[0].a = 1;
114 stops[1].offset = 1;
115 stops[1].r = 1;
116 stops[1].g = 1;
117 stops[1].b = 1;
118 stops[1].a = 1;
119 return 2;
120 }
121
122 if (count == maxcount)
123 fz_warn(ctx, "gradient brush exceeded maximum number of gradient stops");
124
125 /* Postprocess to make sure the range of offsets is 0.0 to 1.0 */
126
127 qsort(stops, count, sizeof(struct stop), cmp_stop);
128
129 before = -1;
130 after = -1;
131
132 for (i = 0; i < count; i++)
133 {
134 if (stops[i].offset < 0)
135 before = i;
136 if (stops[i].offset > 1)
137 {
138 after = i;
139 break;
140 }
141 }
142
143 /* Remove all stops < 0 except the largest one */
144 if (before > 0)
145 {
146 memmove(stops, stops + before, (count - before) * sizeof(struct stop));
147 count -= before;
148 }
149
150 /* Remove all stops > 1 except the smallest one */
151 if (after >= 0)
152 count = after + 1;
153
154 /* Expand single stop to 0 .. 1 */
155 if (count == 1)
156 {
157 stops[1] = stops[0];
158 stops[0].offset = 0;
159 stops[1].offset = 1;
160 return 2;
161 }
162
163 /* First stop < 0 -- interpolate value to 0 */
164 if (stops[0].offset < 0)
165 {
166 float d = -stops[0].offset / (stops[1].offset - stops[0].offset);
167 stops[0].offset = 0;
168 stops[0].r = lerp(stops[0].r, stops[1].r, d);
169 stops[0].g = lerp(stops[0].g, stops[1].g, d);
170 stops[0].b = lerp(stops[0].b, stops[1].b, d);
171 stops[0].a = lerp(stops[0].a, stops[1].a, d);
172 }
173
174 /* Last stop > 1 -- interpolate value to 1 */
175 if (stops[count-1].offset > 1)
176 {
177 float d = (1 - stops[count-2].offset) / (stops[count-1].offset - stops[count-2].offset);
178 stops[count-1].offset = 1;
179 stops[count-1].r = lerp(stops[count-2].r, stops[count-1].r, d);
180 stops[count-1].g = lerp(stops[count-2].g, stops[count-1].g, d);
181 stops[count-1].b = lerp(stops[count-2].b, stops[count-1].b, d);
182 stops[count-1].a = lerp(stops[count-2].a, stops[count-1].a, d);
183 }
184
185 /* First stop > 0 -- insert a duplicate at 0 */
186 if (stops[0].offset > 0)
187 {
188 memmove(stops + 1, stops, count * sizeof(struct stop));
189 stops[0] = stops[1];
190 stops[0].offset = 0;
191 count++;
192 }
193
194 /* Last stop < 1 -- insert a duplicate at 1 */
195 if (stops[count-1].offset < 1)
196 {
197 stops[count] = stops[count-1];
198 stops[count].offset = 1;
199 count++;
200 }
201
202 return count;
203 }
204
205 static void
206 xps_sample_gradient_stops(fz_context *ctx, xps_document *doc, fz_shade *shade, struct stop *stops, int count)
207 {
208 float offset, d;
209 int i, k;
210
211 shade->function = fz_malloc(ctx, sizeof(float) * 256 * 4);
212
213 k = 0;
214 for (i = 0; i < 256; i++)
215 {
216 offset = i / 255.0f;
217 while (k + 1 < count && offset > stops[k+1].offset)
218 k++;
219
220 d = (offset - stops[k].offset) / (stops[k+1].offset - stops[k].offset);
221
222 shade->function[4*i + 0] = lerp(stops[k].r, stops[k+1].r, d);
223 shade->function[4*i + 1] = lerp(stops[k].g, stops[k+1].g, d);
224 shade->function[4*i + 2] = lerp(stops[k].b, stops[k+1].b, d);
225 shade->function[4*i + 3] = lerp(stops[k].a, stops[k+1].a, d);
226 }
227 }
228
229 /*
230 * Radial gradients map more or less to Radial shadings.
231 * The inner circle is always a point.
232 * The outer circle is actually an ellipse,
233 * mess with the transform to squash the circle into the right aspect.
234 */
235
236 static void
237 xps_draw_one_radial_gradient(fz_context *ctx, xps_document *doc, fz_matrix ctm,
238 struct stop *stops, int count,
239 int extend,
240 float x0, float y0, float r0,
241 float x1, float y1, float r1)
242 {
243 fz_device *dev = doc->dev;
244 fz_shade *shade;
245
246 shade = fz_malloc_struct(ctx, fz_shade);
247 FZ_INIT_STORABLE(shade, 1, fz_drop_shade_imp);
248 shade->colorspace = fz_keep_colorspace(ctx, fz_device_rgb(ctx));
249 shade->bbox = fz_infinite_rect;
250 shade->matrix = fz_identity;
251 shade->use_background = 0;
252 shade->function_stride = 4;
253 shade->type = FZ_RADIAL;
254 shade->u.l_or_r.extend[0] = extend;
255 shade->u.l_or_r.extend[1] = extend;
256
257 shade->u.l_or_r.coords[0][0] = x0;
258 shade->u.l_or_r.coords[0][1] = y0;
259 shade->u.l_or_r.coords[0][2] = r0;
260 shade->u.l_or_r.coords[1][0] = x1;
261 shade->u.l_or_r.coords[1][1] = y1;
262 shade->u.l_or_r.coords[1][2] = r1;
263
264 fz_try(ctx)
265 {
266 xps_sample_gradient_stops(ctx, doc, shade, stops, count);
267 fz_fill_shade(ctx, dev, shade, ctm, 1, fz_default_color_params);
268 }
269 fz_always(ctx)
270 fz_drop_shade(ctx, shade);
271 fz_catch(ctx)
272 fz_rethrow(ctx);
273 }
274
275 /*
276 * Linear gradients.
277 */
278
279 static void
280 xps_draw_one_linear_gradient(fz_context *ctx, xps_document *doc, fz_matrix ctm,
281 struct stop *stops, int count,
282 int extend,
283 float x0, float y0, float x1, float y1)
284 {
285 fz_device *dev = doc->dev;
286 fz_shade *shade;
287
288 shade = fz_malloc_struct(ctx, fz_shade);
289 FZ_INIT_STORABLE(shade, 1, fz_drop_shade_imp);
290 shade->colorspace = fz_keep_colorspace(ctx, fz_device_rgb(ctx));
291 shade->bbox = fz_infinite_rect;
292 shade->matrix = fz_identity;
293 shade->use_background = 0;
294 shade->function_stride = 4;
295 shade->type = FZ_LINEAR;
296 shade->u.l_or_r.extend[0] = extend;
297 shade->u.l_or_r.extend[1] = extend;
298
299 shade->u.l_or_r.coords[0][0] = x0;
300 shade->u.l_or_r.coords[0][1] = y0;
301 shade->u.l_or_r.coords[0][2] = 0;
302 shade->u.l_or_r.coords[1][0] = x1;
303 shade->u.l_or_r.coords[1][1] = y1;
304 shade->u.l_or_r.coords[1][2] = 0;
305
306 fz_try(ctx)
307 {
308 xps_sample_gradient_stops(ctx, doc, shade, stops, count);
309 fz_fill_shade(ctx, dev, shade, ctm, doc->opacity[doc->opacity_top], fz_default_color_params);
310 }
311 fz_always(ctx)
312 fz_drop_shade(ctx, shade);
313 fz_catch(ctx)
314 fz_rethrow(ctx);
315 }
316
317 /*
318 * We need to loop and create many shading objects to account
319 * for the Repeat and Reflect SpreadMethods.
320 * I'm not smart enough to calculate this analytically
321 * so we iterate and check each object until we
322 * reach a reasonable limit for infinite cases.
323 */
324
325 static void
326 xps_draw_radial_gradient(fz_context *ctx, xps_document *doc, fz_matrix ctm, fz_rect area,
327 struct stop *stops, int count,
328 fz_xml *root, int spread)
329 {
330 float x0, y0, r0;
331 float x1, y1, r1;
332 float xrad = 1;
333 float yrad = 1;
334 float invscale;
335 int i, ma = 1;
336 fz_matrix inv;
337
338 char *center_att = fz_xml_att(root, "Center");
339 char *origin_att = fz_xml_att(root, "GradientOrigin");
340 char *radius_x_att = fz_xml_att(root, "RadiusX");
341 char *radius_y_att = fz_xml_att(root, "RadiusY");
342
343 x0 = y0 = 0.0f;
344 x1 = y1 = 1.0f;
345 xrad = 1.0f;
346 yrad = 1.0f;
347
348 if (origin_att)
349 xps_parse_point(ctx, doc, origin_att, &x0, &y0);
350 if (center_att)
351 xps_parse_point(ctx, doc, center_att, &x1, &y1);
352 if (radius_x_att)
353 xrad = fz_atof(radius_x_att);
354 if (radius_y_att)
355 yrad = fz_atof(radius_y_att);
356
357 xrad = fz_max(0.01f, xrad);
358 yrad = fz_max(0.01f, yrad);
359
360 /* scale the ctm to make ellipses */
361 if (fz_abs(xrad) > FLT_EPSILON)
362 {
363 ctm = fz_pre_scale(ctm, 1, yrad/xrad);
364 }
365
366 if (yrad != 0.0f)
367 {
368 invscale = xrad / yrad;
369 y0 = y0 * invscale;
370 y1 = y1 * invscale;
371 }
372
373 r0 = 0;
374 r1 = xrad;
375
376 inv = fz_invert_matrix(ctm);
377 area = fz_transform_rect(area, inv);
378 ma = fz_maxi(ma, ceilf(hypotf(area.x0 - x0, area.y0 - y0) / xrad));
379 ma = fz_maxi(ma, ceilf(hypotf(area.x1 - x0, area.y0 - y0) / xrad));
380 ma = fz_maxi(ma, ceilf(hypotf(area.x0 - x0, area.y1 - y0) / xrad));
381 ma = fz_maxi(ma, ceilf(hypotf(area.x1 - x0, area.y1 - y0) / xrad));
382
383 if (spread == SPREAD_REPEAT)
384 {
385 for (i = ma - 1; i >= 0; i--)
386 xps_draw_one_radial_gradient(ctx, doc, ctm, stops, count, 0, x0, y0, r0 + i * xrad, x1, y1, r1 + i * xrad);
387 }
388 else if (spread == SPREAD_REFLECT)
389 {
390 if ((ma % 2) != 0)
391 ma++;
392 for (i = ma - 2; i >= 0; i -= 2)
393 {
394 xps_draw_one_radial_gradient(ctx, doc, ctm, stops, count, 0, x0, y0, r0 + i * xrad, x1, y1, r1 + i * xrad);
395 xps_draw_one_radial_gradient(ctx, doc, ctm, stops, count, 0, x0, y0, r0 + (i + 2) * xrad, x1, y1, r1 + i * xrad);
396 }
397 }
398 else
399 {
400 xps_draw_one_radial_gradient(ctx, doc, ctm, stops, count, 1, x0, y0, r0, x1, y1, r1);
401 }
402 }
403
404 /*
405 * Calculate how many iterations are needed to cover
406 * the bounding box.
407 */
408
409 static void
410 xps_draw_linear_gradient(fz_context *ctx, xps_document *doc, fz_matrix ctm, fz_rect area,
411 struct stop *stops, int count,
412 fz_xml *root, int spread)
413 {
414 float x0, y0, x1, y1;
415 int i, mi, ma;
416 float dx, dy, x, y, k;
417 fz_point p1, p2;
418 fz_matrix inv;
419
420 char *start_point_att = fz_xml_att(root, "StartPoint");
421 char *end_point_att = fz_xml_att(root, "EndPoint");
422
423 x0 = y0 = 0;
424 x1 = y1 = 1;
425
426 if (start_point_att)
427 xps_parse_point(ctx, doc, start_point_att, &x0, &y0);
428 if (end_point_att)
429 xps_parse_point(ctx, doc, end_point_att, &x1, &y1);
430
431 p1.x = x0; p1.y = y0; p2.x = x1; p2.y = y1;
432 inv = fz_invert_matrix(ctm);
433 area = fz_transform_rect(area, inv);
434 x = p2.x - p1.x; y = p2.y - p1.y;
435 k = ((area.x0 - p1.x) * x + (area.y0 - p1.y) * y) / (x * x + y * y);
436 mi = floorf(k); ma = ceilf(k);
437 k = ((area.x1 - p1.x) * x + (area.y0 - p1.y) * y) / (x * x + y * y);
438 mi = fz_mini(mi, floorf(k)); ma = fz_maxi(ma, ceilf(k));
439 k = ((area.x0 - p1.x) * x + (area.y1 - p1.y) * y) / (x * x + y * y);
440 mi = fz_mini(mi, floorf(k)); ma = fz_maxi(ma, ceilf(k));
441 k = ((area.x1 - p1.x) * x + (area.y1 - p1.y) * y) / (x * x + y * y);
442 mi = fz_mini(mi, floorf(k)); ma = fz_maxi(ma, ceilf(k));
443 dx = x1 - x0; dy = y1 - y0;
444
445 if (spread == SPREAD_REPEAT)
446 {
447 for (i = mi; i < ma; i++)
448 xps_draw_one_linear_gradient(ctx, doc, ctm, stops, count, 0, x0 + i * dx, y0 + i * dy, x1 + i * dx, y1 + i * dy);
449 }
450 else if (spread == SPREAD_REFLECT)
451 {
452 if ((mi % 2) != 0)
453 mi--;
454 for (i = mi; i < ma; i += 2)
455 {
456 xps_draw_one_linear_gradient(ctx, doc, ctm, stops, count, 0, x0 + i * dx, y0 + i * dy, x1 + i * dx, y1 + i * dy);
457 xps_draw_one_linear_gradient(ctx, doc, ctm, stops, count, 0, x0 + (i + 2) * dx, y0 + (i + 2) * dy, x1 + i * dx, y1 + i * dy);
458 }
459 }
460 else
461 {
462 xps_draw_one_linear_gradient(ctx, doc, ctm, stops, count, 1, x0, y0, x1, y1);
463 }
464 }
465
466 /*
467 * Parse XML tag and attributes for a gradient brush, create color/opacity
468 * function objects and call gradient drawing primitives.
469 */
470
471 static void
472 xps_parse_gradient_brush(fz_context *ctx, xps_document *doc, fz_matrix ctm, fz_rect area,
473 char *base_uri, xps_resource *dict, fz_xml *root,
474 void (*draw)(fz_context *ctx, xps_document *, fz_matrix, fz_rect, struct stop *, int, fz_xml *, int))
475 {
476 fz_xml *node;
477
478 char *opacity_att;
479 char *spread_att;
480 char *transform_att;
481
482 fz_xml *transform_tag = NULL;
483 fz_xml *stop_tag = NULL;
484
485 struct stop stop_list[MAX_STOPS];
486 int stop_count;
487 int spread_method;
488
489 opacity_att = fz_xml_att(root, "Opacity");
490 spread_att = fz_xml_att(root, "SpreadMethod");
491 transform_att = fz_xml_att(root, "Transform");
492
493 for (node = fz_xml_down(root); node; node = fz_xml_next(node))
494 {
495 if (fz_xml_is_tag(node, "LinearGradientBrush.Transform"))
496 transform_tag = fz_xml_down(node);
497 if (fz_xml_is_tag(node, "RadialGradientBrush.Transform"))
498 transform_tag = fz_xml_down(node);
499 if (fz_xml_is_tag(node, "LinearGradientBrush.GradientStops"))
500 stop_tag = fz_xml_down(node);
501 if (fz_xml_is_tag(node, "RadialGradientBrush.GradientStops"))
502 stop_tag = fz_xml_down(node);
503 }
504
505 xps_resolve_resource_reference(ctx, doc, dict, &transform_att, &transform_tag, NULL);
506
507 spread_method = SPREAD_PAD;
508 if (spread_att)
509 {
510 if (!strcmp(spread_att, "Pad"))
511 spread_method = SPREAD_PAD;
512 if (!strcmp(spread_att, "Reflect"))
513 spread_method = SPREAD_REFLECT;
514 if (!strcmp(spread_att, "Repeat"))
515 spread_method = SPREAD_REPEAT;
516 }
517
518 ctm = xps_parse_transform(ctx, doc, transform_att, transform_tag, ctm);
519
520 if (!stop_tag) {
521 fz_warn(ctx, "missing gradient stops tag");
522 return;
523 }
524
525 stop_count = xps_parse_gradient_stops(ctx, doc, base_uri, stop_tag, stop_list, MAX_STOPS);
526 if (stop_count == 0)
527 {
528 fz_warn(ctx, "no gradient stops found");
529 return;
530 }
531
532 xps_begin_opacity(ctx, doc, ctm, area, base_uri, dict, opacity_att, NULL);
533
534 draw(ctx, doc, ctm, area, stop_list, stop_count, root, spread_method);
535
536 xps_end_opacity(ctx, doc, base_uri, dict, opacity_att, NULL);
537 }
538
539 void
540 xps_parse_linear_gradient_brush(fz_context *ctx, xps_document *doc, fz_matrix ctm, fz_rect area,
541 char *base_uri, xps_resource *dict, fz_xml *root)
542 {
543 xps_parse_gradient_brush(ctx, doc, ctm, area, base_uri, dict, root, xps_draw_linear_gradient);
544 }
545
546 void
547 xps_parse_radial_gradient_brush(fz_context *ctx, xps_document *doc, fz_matrix ctm, fz_rect area,
548 char *base_uri, xps_resource *dict, fz_xml *root)
549 {
550 xps_parse_gradient_brush(ctx, doc, ctm, area, base_uri, dict, root, xps_draw_radial_gradient);
551 }