Coverage Report

Created: 2024-05-20 06:23

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