/src/ghostpdl/xps/xpsgradient.c
Line | Count | Source |
1 | | /* Copyright (C) 2001-2026 Artifex Software, Inc. |
2 | | All Rights Reserved. |
3 | | |
4 | | This software is provided AS-IS with no warranty, either express or |
5 | | implied. |
6 | | |
7 | | This software is distributed under license and may not be copied, |
8 | | modified or distributed except as expressly authorized under the terms |
9 | | of the license contained in the file LICENSE in this distribution. |
10 | | |
11 | | Refer to licensing information at http://www.artifex.com or contact |
12 | | Artifex Software, Inc., 39 Mesa Street, Suite 108A, San Francisco, |
13 | | CA 94129, USA, for further information. |
14 | | */ |
15 | | |
16 | | |
17 | | /* XPS interpreter - gradient support */ |
18 | | |
19 | | #include "ghostxps.h" |
20 | | |
21 | 0 | #define MAX_STOPS 256 |
22 | | |
23 | | enum { SPREAD_PAD, SPREAD_REPEAT, SPREAD_REFLECT }; |
24 | | |
25 | | /* |
26 | | * Parse a list of GradientStop elements. |
27 | | * Fill the offset and color arrays, and |
28 | | * return the number of stops parsed. |
29 | | */ |
30 | | |
31 | | struct stop |
32 | | { |
33 | | float offset; |
34 | | float color[4]; |
35 | | int index; |
36 | | }; |
37 | | |
38 | | static int cmp_stop(const void *a, const void *b) |
39 | 0 | { |
40 | 0 | const struct stop *astop = a; |
41 | 0 | const struct stop *bstop = b; |
42 | 0 | float diff = astop->offset - bstop->offset; |
43 | 0 | if (diff < 0) |
44 | 0 | return -1; |
45 | 0 | if (diff > 0) |
46 | 0 | return 1; |
47 | 0 | return astop->index - bstop->index; |
48 | 0 | } |
49 | | |
50 | | static inline float lerp(float a, float b, float x) |
51 | 0 | { |
52 | 0 | return a + (b - a) * x; |
53 | 0 | } |
54 | | |
55 | | static int |
56 | | xps_parse_gradient_stops(xps_context_t *ctx, char *base_uri, xps_item_t *node, |
57 | | struct stop *stops, int maxcount) |
58 | 0 | { |
59 | 0 | unsigned short sample_in[XPS_MAX_COLORS], sample_out[XPS_MAX_COLORS]; /* XPS allows up to 8 bands */ |
60 | 0 | gsicc_rendering_param_t rendering_params; |
61 | 0 | gsicc_link_t *icclink = 0; |
62 | 0 | float sample[XPS_MAX_COLORS]; |
63 | 0 | int before, after; |
64 | 0 | int count; |
65 | 0 | int i, k; |
66 | | |
67 | | /* We may have to insert 2 extra stops when postprocessing */ |
68 | 0 | maxcount -= 2; |
69 | |
|
70 | 0 | count = 0; |
71 | 0 | while (node && count < maxcount) |
72 | 0 | { |
73 | 0 | if (!strcmp(xps_tag(node), "GradientStop")) |
74 | 0 | { |
75 | 0 | char *offset = xps_att(node, "Offset"); |
76 | 0 | char *color = xps_att(node, "Color"); |
77 | 0 | if (offset && color) |
78 | 0 | { |
79 | 0 | gs_color_space *colorspace; |
80 | |
|
81 | 0 | stops[count].offset = atof(offset); |
82 | 0 | stops[count].index = count; |
83 | |
|
84 | 0 | xps_parse_color(ctx, base_uri, color, &colorspace, sample); |
85 | | |
86 | | /* Set the rendering parameters */ |
87 | 0 | rendering_params.black_point_comp = gsBLACKPTCOMP_ON; |
88 | 0 | rendering_params.graphics_type_tag = GS_VECTOR_TAG; |
89 | 0 | rendering_params.override_icc = false; |
90 | 0 | rendering_params.preserve_black = gsBKPRESNOTSPECIFIED; |
91 | 0 | rendering_params.rendering_intent = gsPERCEPTUAL; |
92 | 0 | rendering_params.cmm = gsCMM_DEFAULT; |
93 | | /* Get link to map from source to sRGB */ |
94 | 0 | icclink = gsicc_get_link(ctx->pgs, NULL, colorspace, |
95 | 0 | ctx->srgb, &rendering_params, ctx->memory); |
96 | |
|
97 | 0 | if (icclink != NULL && !icclink->is_identity) |
98 | 0 | { |
99 | | /* Transform the color */ |
100 | 0 | int num_colors = gsicc_getsrc_channel_count(colorspace->cmm_icc_profile_data); |
101 | 0 | for (i = 0; i < num_colors; i++) |
102 | 0 | { |
103 | 0 | sample_in[i] = (unsigned short)(sample[i+1]*65535); |
104 | 0 | } |
105 | 0 | gscms_transform_color((gx_device *)(ctx->pgs->device), |
106 | 0 | icclink, sample_in, sample_out, 2); |
107 | |
|
108 | 0 | stops[count].color[0] = sample[0]; /* Alpha */ |
109 | 0 | stops[count].color[1] = (float) sample_out[0] / 65535.0; /* sRGB */ |
110 | 0 | stops[count].color[2] = (float) sample_out[1] / 65535.0; |
111 | 0 | stops[count].color[3] = (float) sample_out[2] / 65535.0; |
112 | 0 | } |
113 | 0 | else |
114 | 0 | { |
115 | 0 | stops[count].color[0] = sample[0]; |
116 | 0 | stops[count].color[1] = sample[1]; |
117 | 0 | stops[count].color[2] = sample[2]; |
118 | 0 | stops[count].color[3] = sample[3]; |
119 | 0 | } |
120 | 0 | rc_decrement(colorspace, "xps_parse_gradient_stops"); |
121 | |
|
122 | 0 | count ++; |
123 | 0 | } |
124 | 0 | } |
125 | |
|
126 | 0 | if (icclink != NULL) |
127 | 0 | gsicc_release_link(icclink); |
128 | 0 | icclink = NULL; |
129 | 0 | node = xps_next(node); |
130 | |
|
131 | 0 | } |
132 | |
|
133 | 0 | if (count == 0) |
134 | 0 | { |
135 | 0 | gs_warn("gradient brush has no gradient stops"); |
136 | 0 | stops[0].offset = 0; |
137 | 0 | stops[0].color[0] = 1; |
138 | 0 | stops[0].color[1] = 0; |
139 | 0 | stops[0].color[2] = 0; |
140 | 0 | stops[0].color[3] = 0; |
141 | 0 | stops[1].offset = 1; |
142 | 0 | stops[1].color[0] = 1; |
143 | 0 | stops[1].color[1] = 1; |
144 | 0 | stops[1].color[2] = 1; |
145 | 0 | stops[1].color[3] = 1; |
146 | 0 | return 2; |
147 | 0 | } |
148 | | |
149 | 0 | if (count == maxcount) |
150 | 0 | gs_warn("gradient brush exceeded maximum number of gradient stops"); |
151 | | |
152 | | /* Postprocess to make sure the range of offsets is 0.0 to 1.0 */ |
153 | |
|
154 | 0 | qsort(stops, count, sizeof(struct stop), cmp_stop); |
155 | |
|
156 | 0 | before = -1; |
157 | 0 | after = -1; |
158 | |
|
159 | 0 | for (i = 0; i < count; i++) |
160 | 0 | { |
161 | 0 | if (stops[i].offset < 0) |
162 | 0 | before = i; |
163 | 0 | if (stops[i].offset > 1) |
164 | 0 | { |
165 | 0 | after = i; |
166 | 0 | break; |
167 | 0 | } |
168 | 0 | } |
169 | | |
170 | | /* Remove all stops < 0 except the largest one */ |
171 | 0 | if (before > 0) |
172 | 0 | { |
173 | 0 | memmove(stops, stops + before, (count - before) * sizeof(struct stop)); |
174 | 0 | count -= before; |
175 | 0 | } |
176 | | |
177 | | /* Remove all stops > 1 except the smallest one */ |
178 | 0 | if (after >= 0) |
179 | 0 | count = after + 1; |
180 | | |
181 | | /* Expand single stop to 0 .. 1 */ |
182 | 0 | if (count == 1) |
183 | 0 | { |
184 | 0 | stops[1] = stops[0]; |
185 | 0 | stops[0].offset = 0; |
186 | 0 | stops[1].offset = 1; |
187 | 0 | return 2; |
188 | 0 | } |
189 | | |
190 | | /* First stop < 0 -- interpolate value to 0 */ |
191 | 0 | if (stops[0].offset < 0) |
192 | 0 | { |
193 | 0 | float d = -stops[0].offset / (stops[1].offset - stops[0].offset); |
194 | 0 | stops[0].offset = 0; |
195 | 0 | for (k = 0; k < 4; k++) |
196 | 0 | stops[0].color[k] = lerp(stops[0].color[k], stops[1].color[k], d); |
197 | 0 | } |
198 | | |
199 | | /* Last stop > 1 -- interpolate value to 1 */ |
200 | 0 | if (stops[count-1].offset > 1) |
201 | 0 | { |
202 | 0 | float d = (1 - stops[count-2].offset) / (stops[count-1].offset - stops[count-2].offset); |
203 | 0 | stops[count-1].offset = 1; |
204 | 0 | for (k = 0; k < 4; k++) |
205 | 0 | stops[count-1].color[k] = lerp(stops[count-2].color[k], stops[count-1].color[k], d); |
206 | 0 | } |
207 | | |
208 | | /* First stop > 0 -- insert a duplicate at 0 */ |
209 | 0 | if (stops[0].offset > 0) |
210 | 0 | { |
211 | 0 | memmove(stops + 1, stops, count * sizeof(struct stop)); |
212 | 0 | stops[0] = stops[1]; |
213 | 0 | stops[0].offset = 0; |
214 | 0 | count++; |
215 | 0 | } |
216 | | |
217 | | /* Last stop < 1 -- insert a duplicate at 1 */ |
218 | 0 | if (stops[count-1].offset < 1) |
219 | 0 | { |
220 | 0 | stops[count] = stops[count-1]; |
221 | 0 | stops[count].offset = 1; |
222 | 0 | count++; |
223 | 0 | } |
224 | |
|
225 | 0 | return count; |
226 | 0 | } |
227 | | |
228 | | static int |
229 | | xps_gradient_has_transparent_colors(struct stop *stops, int count) |
230 | 0 | { |
231 | 0 | int i; |
232 | 0 | for (i = 0; i < count; i++) |
233 | 0 | if (stops[i].color[0] < 1) |
234 | 0 | return 1; |
235 | 0 | return 0; |
236 | 0 | } |
237 | | |
238 | | /* |
239 | | * Create a Function object to map [0..1] to RGB colors |
240 | | * based on the gradient stop arrays. |
241 | | * |
242 | | * We do this by creating a stitching function that joins |
243 | | * a series of linear functions (one linear function |
244 | | * for each gradient stop-pair). |
245 | | */ |
246 | | |
247 | | static gs_function_t * |
248 | | xps_create_gradient_stop_function(xps_context_t *ctx, struct stop *stops, int count, int opacity_only) |
249 | 0 | { |
250 | 0 | gs_function_1ItSg_params_t sparams; |
251 | 0 | gs_function_ElIn_params_t lparams; |
252 | 0 | gs_function_t *sfunc; |
253 | 0 | gs_function_t *lfunc; |
254 | |
|
255 | 0 | float *domain, *range, *c0, *c1, *bounds, *encode; |
256 | 0 | const gs_function_t **functions; |
257 | |
|
258 | 0 | int code; |
259 | 0 | int k; |
260 | 0 | int i; |
261 | |
|
262 | 0 | k = count - 1; /* number of intervals / functions */ |
263 | |
|
264 | 0 | domain = xps_alloc(ctx, 2 * sizeof(float)); |
265 | 0 | if (!domain) { |
266 | 0 | gs_throw(gs_error_VMerror, "out of memory: domain\n"); |
267 | 0 | return NULL; |
268 | 0 | } |
269 | 0 | domain[0] = 0.0; |
270 | 0 | domain[1] = 1.0; |
271 | 0 | sparams.m = 1; |
272 | 0 | sparams.Domain = domain; |
273 | |
|
274 | 0 | range = xps_alloc(ctx, 6 * sizeof(float)); |
275 | 0 | if (!range) { |
276 | 0 | gs_throw(gs_error_VMerror, "out of memory: range\n"); |
277 | 0 | return NULL; |
278 | 0 | } |
279 | 0 | range[0] = 0.0; |
280 | 0 | range[1] = 1.0; |
281 | 0 | range[2] = 0.0; |
282 | 0 | range[3] = 1.0; |
283 | 0 | range[4] = 0.0; |
284 | 0 | range[5] = 1.0; |
285 | 0 | sparams.Range = range; |
286 | |
|
287 | 0 | functions = xps_alloc(ctx, (size_t)k * sizeof(void*)); |
288 | 0 | if (!functions) { |
289 | 0 | gs_throw(gs_error_VMerror, "out of memory: functions.\n"); |
290 | 0 | return NULL; |
291 | 0 | } |
292 | 0 | bounds = xps_alloc(ctx, ((size_t)k - 1) * sizeof(float)); |
293 | 0 | if (!bounds) { |
294 | 0 | gs_throw(gs_error_VMerror, "out of memory: bounds.\n"); |
295 | 0 | return NULL; |
296 | 0 | } |
297 | 0 | encode = xps_alloc(ctx, ((size_t)k * 2) * sizeof(float)); |
298 | 0 | if (!encode) { |
299 | 0 | gs_throw(gs_error_VMerror, "out of memory: encode.\n"); |
300 | 0 | return NULL; |
301 | 0 | } |
302 | | |
303 | 0 | sparams.k = k; |
304 | 0 | sparams.Functions = functions; |
305 | 0 | sparams.Bounds = bounds; |
306 | 0 | sparams.Encode = encode; |
307 | |
|
308 | 0 | if (opacity_only) |
309 | 0 | { |
310 | 0 | sparams.n = 1; |
311 | 0 | lparams.n = 1; |
312 | 0 | } |
313 | 0 | else |
314 | 0 | { |
315 | 0 | sparams.n = 3; |
316 | 0 | lparams.n = 3; |
317 | 0 | } |
318 | |
|
319 | 0 | for (i = 0; i < k; i++) |
320 | 0 | { |
321 | 0 | domain = xps_alloc(ctx, 2 * sizeof(float)); |
322 | 0 | if (!domain) { |
323 | 0 | gs_throw(gs_error_VMerror, "out of memory: domain.\n"); |
324 | 0 | return NULL; |
325 | 0 | } |
326 | 0 | domain[0] = 0.0; |
327 | 0 | domain[1] = 1.0; |
328 | 0 | lparams.m = 1; |
329 | 0 | lparams.Domain = domain; |
330 | |
|
331 | 0 | range = xps_alloc(ctx, 6 * sizeof(float)); |
332 | 0 | if (!range) { |
333 | 0 | gs_throw(gs_error_VMerror, "out of memory: range.\n"); |
334 | 0 | return NULL; |
335 | 0 | } |
336 | 0 | range[0] = 0.0; |
337 | 0 | range[1] = 1.0; |
338 | 0 | range[2] = 0.0; |
339 | 0 | range[3] = 1.0; |
340 | 0 | range[4] = 0.0; |
341 | 0 | range[5] = 1.0; |
342 | 0 | lparams.Range = range; |
343 | |
|
344 | 0 | c0 = xps_alloc(ctx, 3 * sizeof(float)); |
345 | 0 | if (!c0) { |
346 | 0 | gs_throw(gs_error_VMerror, "out of memory: c0.\n"); |
347 | 0 | return NULL; |
348 | 0 | } |
349 | 0 | lparams.C0 = c0; |
350 | |
|
351 | 0 | c1 = xps_alloc(ctx, 3 * sizeof(float)); |
352 | 0 | if (!c1) { |
353 | 0 | gs_throw(gs_error_VMerror, "out of memory: c1.\n"); |
354 | 0 | return NULL; |
355 | 0 | } |
356 | 0 | lparams.C1 = c1; |
357 | |
|
358 | 0 | if (opacity_only) |
359 | 0 | { |
360 | 0 | c0[0] = stops[i].color[0]; |
361 | 0 | c1[0] = stops[i+1].color[0]; |
362 | 0 | } |
363 | 0 | else |
364 | 0 | { |
365 | 0 | c0[0] = stops[i].color[1]; |
366 | 0 | c0[1] = stops[i].color[2]; |
367 | 0 | c0[2] = stops[i].color[3]; |
368 | |
|
369 | 0 | c1[0] = stops[i+1].color[1]; |
370 | 0 | c1[1] = stops[i+1].color[2]; |
371 | 0 | c1[2] = stops[i+1].color[3]; |
372 | 0 | } |
373 | |
|
374 | 0 | lparams.N = 1; |
375 | |
|
376 | 0 | code = gs_function_ElIn_init(&lfunc, &lparams, ctx->memory); |
377 | 0 | if (code < 0) |
378 | 0 | { |
379 | 0 | gs_rethrow(code, "gs_function_ElIn_init failed"); |
380 | 0 | return NULL; |
381 | 0 | } |
382 | | |
383 | 0 | functions[i] = lfunc; |
384 | |
|
385 | 0 | if (i > 0) |
386 | 0 | bounds[i - 1] = stops[i].offset; |
387 | |
|
388 | 0 | encode[i * 2 + 0] = 0.0; |
389 | 0 | encode[i * 2 + 1] = 1.0; |
390 | 0 | } |
391 | | |
392 | 0 | code = gs_function_1ItSg_init(&sfunc, &sparams, ctx->memory); |
393 | 0 | if (code < 0) |
394 | 0 | { |
395 | 0 | gs_rethrow(code, "gs_function_1ItSg_init failed"); |
396 | 0 | return NULL; |
397 | 0 | } |
398 | | |
399 | 0 | return sfunc; |
400 | 0 | } |
401 | | |
402 | | /* |
403 | | * Shadings and functions are ghostscript type objects, |
404 | | * and as such rely on the garbage collector for cleanup. |
405 | | * We can't have none of that here, so we have to |
406 | | * write our own destructors. |
407 | | */ |
408 | | |
409 | | static void |
410 | | xps_free_gradient_stop_function(xps_context_t *ctx, gs_function_t *func) |
411 | 0 | { |
412 | 0 | gs_function_t *lfunc; |
413 | 0 | gs_function_1ItSg_params_t *sparams; |
414 | 0 | gs_function_ElIn_params_t *lparams; |
415 | 0 | int i; |
416 | |
|
417 | 0 | sparams = (gs_function_1ItSg_params_t*) &func->params; |
418 | 0 | xps_free(ctx, (void*)sparams->Domain); |
419 | 0 | xps_free(ctx, (void*)sparams->Range); |
420 | |
|
421 | 0 | for (i = 0; i < sparams->k; i++) |
422 | 0 | { |
423 | 0 | lfunc = (gs_function_t*) sparams->Functions[i]; /* discard const */ |
424 | 0 | lparams = (gs_function_ElIn_params_t*) &lfunc->params; |
425 | 0 | xps_free(ctx, (void*)lparams->Domain); |
426 | 0 | xps_free(ctx, (void*)lparams->Range); |
427 | 0 | xps_free(ctx, (void*)lparams->C0); |
428 | 0 | xps_free(ctx, (void*)lparams->C1); |
429 | 0 | xps_free(ctx, lfunc); |
430 | 0 | } |
431 | |
|
432 | 0 | xps_free(ctx, (void*)sparams->Bounds); |
433 | 0 | xps_free(ctx, (void*)sparams->Encode); |
434 | 0 | xps_free(ctx, (void*)sparams->Functions); |
435 | 0 | xps_free(ctx, func); |
436 | 0 | } |
437 | | |
438 | | /* |
439 | | * For radial gradients that have a cone drawing we have to |
440 | | * reverse the direction of the gradient because we draw |
441 | | * the shading in the opposite direction with the |
442 | | * big circle first. |
443 | | */ |
444 | | static gs_function_t * |
445 | | xps_reverse_function(xps_context_t *ctx, gs_function_t *func, float *fary, void *vary) |
446 | 0 | { |
447 | 0 | gs_function_1ItSg_params_t sparams; |
448 | 0 | gs_function_t *sfunc; |
449 | 0 | int code; |
450 | | |
451 | | /* take from stack allocated arrays that the caller provides */ |
452 | 0 | float *domain = fary + 0; |
453 | 0 | float *range = fary + 2; |
454 | 0 | float *encode = fary + 2 + 6; |
455 | 0 | const gs_function_t **functions = vary; |
456 | |
|
457 | 0 | domain[0] = 0.0; |
458 | 0 | domain[1] = 1.0; |
459 | |
|
460 | 0 | range[0] = 0.0; |
461 | 0 | range[1] = 1.0; |
462 | 0 | range[2] = 0.0; |
463 | 0 | range[3] = 1.0; |
464 | 0 | range[4] = 0.0; |
465 | 0 | range[5] = 1.0; |
466 | |
|
467 | 0 | functions[0] = func; |
468 | |
|
469 | 0 | encode[0] = 1.0; |
470 | 0 | encode[1] = 0.0; |
471 | |
|
472 | 0 | sparams.m = 1; |
473 | 0 | sparams.Domain = domain; |
474 | 0 | sparams.Range = range; |
475 | 0 | sparams.k = 1; |
476 | 0 | sparams.Functions = functions; |
477 | 0 | sparams.Bounds = NULL; |
478 | 0 | sparams.Encode = encode; |
479 | |
|
480 | 0 | if (ctx->opacity_only) |
481 | 0 | sparams.n = 1; |
482 | 0 | else |
483 | 0 | sparams.n = 3; |
484 | |
|
485 | 0 | code = gs_function_1ItSg_init(&sfunc, &sparams, ctx->memory); |
486 | 0 | if (code < 0) |
487 | 0 | { |
488 | 0 | gs_rethrow(code, "gs_function_1ItSg_init failed"); |
489 | 0 | return NULL; |
490 | 0 | } |
491 | | |
492 | 0 | return sfunc; |
493 | 0 | } |
494 | | |
495 | | /* |
496 | | * Radial gradients map more or less to Radial shadings. |
497 | | * The inner circle is always a point. |
498 | | * The outer circle is actually an ellipse, |
499 | | * mess with the transform to squash the circle into the right aspect. |
500 | | */ |
501 | | |
502 | | static int |
503 | | xps_draw_one_radial_gradient(xps_context_t *ctx, |
504 | | gs_function_t *func, int extend, |
505 | | float x0, float y0, float r0, |
506 | | float x1, float y1, float r1) |
507 | 0 | { |
508 | 0 | gs_memory_t *mem = ctx->memory; |
509 | 0 | gs_shading_t *shading; |
510 | 0 | gs_shading_R_params_t params; |
511 | 0 | int code; |
512 | |
|
513 | 0 | gs_shading_R_params_init(¶ms); |
514 | 0 | { |
515 | 0 | if (ctx->opacity_only) |
516 | 0 | params.ColorSpace = ctx->gray_lin; |
517 | 0 | else |
518 | 0 | params.ColorSpace = ctx->srgb; |
519 | |
|
520 | 0 | params.Coords[0] = x0; |
521 | 0 | params.Coords[1] = y0; |
522 | 0 | params.Coords[2] = r0; |
523 | 0 | params.Coords[3] = x1; |
524 | 0 | params.Coords[4] = y1; |
525 | 0 | params.Coords[5] = r1; |
526 | |
|
527 | 0 | params.Extend[0] = extend; |
528 | 0 | params.Extend[1] = extend; |
529 | |
|
530 | 0 | params.Function = func; |
531 | 0 | } |
532 | |
|
533 | 0 | code = gs_shading_R_init(&shading, ¶ms, mem); |
534 | 0 | if (code < 0) |
535 | 0 | return gs_rethrow(code, "gs_shading_R_init failed"); |
536 | | |
537 | 0 | gs_setsmoothness(ctx->pgs, 0.02); |
538 | |
|
539 | 0 | code = gs_shfill(ctx->pgs, shading); |
540 | 0 | if (code < 0) |
541 | 0 | { |
542 | 0 | gs_free_object(mem, shading, "gs_shading_R"); |
543 | 0 | return gs_rethrow(code, "gs_shfill failed"); |
544 | 0 | } |
545 | | |
546 | 0 | gs_free_object(mem, shading, "gs_shading_R"); |
547 | |
|
548 | 0 | return 0; |
549 | 0 | } |
550 | | |
551 | | /* |
552 | | * Linear gradients map to Axial shadings. |
553 | | */ |
554 | | |
555 | | static int |
556 | | xps_draw_one_linear_gradient(xps_context_t *ctx, |
557 | | gs_function_t *func, int extend, |
558 | | float x0, float y0, float x1, float y1) |
559 | 0 | { |
560 | 0 | gs_memory_t *mem = ctx->memory; |
561 | 0 | gs_shading_t *shading; |
562 | 0 | gs_shading_A_params_t params; |
563 | 0 | int code; |
564 | |
|
565 | 0 | gs_shading_A_params_init(¶ms); |
566 | 0 | { |
567 | 0 | if (ctx->opacity_only) |
568 | 0 | params.ColorSpace = ctx->gray_lin; |
569 | 0 | else |
570 | 0 | params.ColorSpace = ctx->srgb; |
571 | |
|
572 | 0 | params.Coords[0] = x0; |
573 | 0 | params.Coords[1] = y0; |
574 | 0 | params.Coords[2] = x1; |
575 | 0 | params.Coords[3] = y1; |
576 | |
|
577 | 0 | params.Extend[0] = extend; |
578 | 0 | params.Extend[1] = extend; |
579 | |
|
580 | 0 | params.Function = func; |
581 | 0 | } |
582 | |
|
583 | 0 | code = gs_shading_A_init(&shading, ¶ms, mem); |
584 | 0 | if (code < 0) |
585 | 0 | return gs_rethrow(code, "gs_shading_A_init failed"); |
586 | | |
587 | 0 | gs_setsmoothness(ctx->pgs, 0.02); |
588 | |
|
589 | 0 | code = gs_shfill(ctx->pgs, shading); |
590 | 0 | if (code < 0) |
591 | 0 | { |
592 | 0 | gs_free_object(mem, shading, "gs_shading_A"); |
593 | 0 | return gs_rethrow(code, "gs_shfill failed"); |
594 | 0 | } |
595 | | |
596 | 0 | gs_free_object(mem, shading, "gs_shading_A"); |
597 | |
|
598 | 0 | return 0; |
599 | 0 | } |
600 | | |
601 | | /* |
602 | | * We need to loop and create many shading objects to account |
603 | | * for the Repeat and Reflect SpreadMethods. |
604 | | * I'm not smart enough to calculate this analytically |
605 | | * so we iterate and check each object until we |
606 | | * reach a reasonable limit for infinite cases. |
607 | | */ |
608 | | |
609 | | static inline int point_inside_circle(float px, float py, float x, float y, float r) |
610 | 0 | { |
611 | 0 | float dx = px - x; |
612 | 0 | float dy = py - y; |
613 | 0 | return (dx * dx + dy * dy) <= (r * r); |
614 | 0 | } |
615 | | |
616 | | static int |
617 | | xps_draw_radial_gradient(xps_context_t *ctx, xps_item_t *root, int spread, gs_function_t *func) |
618 | 0 | { |
619 | 0 | gs_rect bbox; |
620 | 0 | float x0 = 0, y0 = 0, r0; |
621 | 0 | float x1 = 0, y1 = 0, r1; |
622 | 0 | float xrad = 1; |
623 | 0 | float yrad = 1; |
624 | 0 | float invscale; |
625 | 0 | float dx, dy; |
626 | 0 | int code; |
627 | 0 | int i; |
628 | 0 | int done; |
629 | |
|
630 | 0 | char *center_att = xps_att(root, "Center"); |
631 | 0 | char *origin_att = xps_att(root, "GradientOrigin"); |
632 | 0 | char *radius_x_att = xps_att(root, "RadiusX"); |
633 | 0 | char *radius_y_att = xps_att(root, "RadiusY"); |
634 | |
|
635 | 0 | if (origin_att) |
636 | 0 | xps_get_point(origin_att, &x0, &y0); |
637 | 0 | if (center_att) |
638 | 0 | xps_get_point(center_att, &x1, &y1); |
639 | 0 | if (radius_x_att) |
640 | 0 | xrad = atof(radius_x_att); |
641 | 0 | if (radius_y_att) |
642 | 0 | yrad = atof(radius_y_att); |
643 | |
|
644 | 0 | gs_gsave(ctx->pgs); |
645 | | |
646 | | /* scale the ctm to make ellipses */ |
647 | 0 | if (xrad != 0) |
648 | 0 | gs_scale(ctx->pgs, 1.0, yrad / xrad); |
649 | |
|
650 | 0 | if (yrad != 0) |
651 | 0 | { |
652 | 0 | invscale = xrad / yrad; |
653 | 0 | y0 = y0 * invscale; |
654 | 0 | y1 = y1 * invscale; |
655 | 0 | } |
656 | |
|
657 | 0 | r0 = 0.0; |
658 | 0 | r1 = xrad; |
659 | |
|
660 | 0 | dx = x1 - x0; |
661 | 0 | dy = y1 - y0; |
662 | |
|
663 | 0 | xps_bounds_in_user_space(ctx, &bbox); |
664 | |
|
665 | 0 | if (spread == SPREAD_PAD) |
666 | 0 | { |
667 | 0 | if (!point_inside_circle(x0, y0, x1, y1, r1)) |
668 | 0 | { |
669 | 0 | gs_function_t *reverse; |
670 | 0 | float in[1]; |
671 | 0 | float out[4]; |
672 | 0 | float fary[10]; |
673 | 0 | void *vary[1]; |
674 | | |
675 | | /* PDF shadings with extend doesn't work the same way as XPS |
676 | | * gradients when the radial shading is a cone. In this case |
677 | | * we fill the background ourselves. |
678 | | */ |
679 | |
|
680 | 0 | in[0] = 1.0; |
681 | 0 | out[0] = 1.0; |
682 | 0 | out[1] = 0.0; |
683 | 0 | out[2] = 0.0; |
684 | 0 | out[3] = 0.0; |
685 | 0 | if (ctx->opacity_only) |
686 | 0 | { |
687 | 0 | gs_function_evaluate(func, in, out); |
688 | 0 | xps_set_color(ctx, ctx->gray_lin, out); |
689 | 0 | } |
690 | 0 | else |
691 | 0 | { |
692 | 0 | gs_function_evaluate(func, in, out + 1); |
693 | 0 | xps_set_color(ctx, ctx->srgb, out); |
694 | 0 | } |
695 | |
|
696 | 0 | gs_moveto(ctx->pgs, bbox.p.x, bbox.p.y); |
697 | 0 | gs_lineto(ctx->pgs, bbox.q.x, bbox.p.y); |
698 | 0 | gs_lineto(ctx->pgs, bbox.q.x, bbox.q.y); |
699 | 0 | gs_lineto(ctx->pgs, bbox.p.x, bbox.q.y); |
700 | 0 | gs_closepath(ctx->pgs); |
701 | 0 | gs_fill(ctx->pgs); |
702 | | |
703 | | /* We also have to reverse the direction so the bigger circle |
704 | | * comes first or the graphical results do not match. We also |
705 | | * have to reverse the direction of the function to compensate. |
706 | | */ |
707 | |
|
708 | 0 | reverse = xps_reverse_function(ctx, func, fary, vary); |
709 | 0 | if (!reverse) |
710 | 0 | { |
711 | 0 | gs_grestore(ctx->pgs); |
712 | 0 | return gs_rethrow(-1, "could not create the reversed function"); |
713 | 0 | } |
714 | | |
715 | 0 | code = xps_draw_one_radial_gradient(ctx, reverse, 1, x1, y1, r1, x0, y0, r0); |
716 | 0 | if (code < 0) |
717 | 0 | { |
718 | 0 | xps_free(ctx, reverse); |
719 | 0 | gs_grestore(ctx->pgs); |
720 | 0 | return gs_rethrow(code, "could not draw radial gradient"); |
721 | 0 | } |
722 | | |
723 | 0 | xps_free(ctx, reverse); |
724 | 0 | } |
725 | 0 | else |
726 | 0 | { |
727 | 0 | code = xps_draw_one_radial_gradient(ctx, func, 1, x0, y0, r0, x1, y1, r1); |
728 | 0 | if (code < 0) |
729 | 0 | { |
730 | 0 | gs_grestore(ctx->pgs); |
731 | 0 | return gs_rethrow(code, "could not draw radial gradient"); |
732 | 0 | } |
733 | 0 | } |
734 | 0 | } |
735 | 0 | else |
736 | 0 | { |
737 | 0 | for (i = 0; i < 100; i++) |
738 | 0 | { |
739 | | /* Draw current circle */ |
740 | |
|
741 | 0 | if (!point_inside_circle(x0, y0, x1, y1, r1)) |
742 | 0 | dmputs(ctx->memory, "xps: we should reverse gradient here too\n"); |
743 | |
|
744 | 0 | if (spread == SPREAD_REFLECT && (i & 1)) |
745 | 0 | code = xps_draw_one_radial_gradient(ctx, func, 0, x1, y1, r1, x0, y0, r0); |
746 | 0 | else |
747 | 0 | code = xps_draw_one_radial_gradient(ctx, func, 0, x0, y0, r0, x1, y1, r1); |
748 | 0 | if (code < 0) |
749 | 0 | { |
750 | 0 | gs_grestore(ctx->pgs); |
751 | 0 | return gs_rethrow(code, "could not draw axial gradient"); |
752 | 0 | } |
753 | | |
754 | | /* Check if circle encompassed the entire bounding box (break loop if we do) */ |
755 | | |
756 | 0 | done = 1; |
757 | 0 | if (!point_inside_circle(bbox.p.x, bbox.p.y, x1, y1, r1)) done = 0; |
758 | 0 | if (!point_inside_circle(bbox.p.x, bbox.q.y, x1, y1, r1)) done = 0; |
759 | 0 | if (!point_inside_circle(bbox.q.x, bbox.q.y, x1, y1, r1)) done = 0; |
760 | 0 | if (!point_inside_circle(bbox.q.x, bbox.p.y, x1, y1, r1)) done = 0; |
761 | 0 | if (done) |
762 | 0 | break; |
763 | | |
764 | | /* Prepare next circle */ |
765 | | |
766 | 0 | r0 = r1; |
767 | 0 | r1 += xrad; |
768 | |
|
769 | 0 | x0 += dx; |
770 | 0 | y0 += dy; |
771 | 0 | x1 += dx; |
772 | 0 | y1 += dy; |
773 | 0 | } |
774 | 0 | } |
775 | | |
776 | 0 | gs_grestore(ctx->pgs); |
777 | |
|
778 | 0 | return 0; |
779 | 0 | } |
780 | | |
781 | | /* |
782 | | * Calculate how many iterations are needed to cover |
783 | | * the bounding box. |
784 | | */ |
785 | | |
786 | | static int |
787 | | xps_draw_linear_gradient(xps_context_t *ctx, xps_item_t *root, int spread, gs_function_t *func) |
788 | 0 | { |
789 | 0 | gs_rect bbox; |
790 | 0 | float x0, y0, x1, y1; |
791 | 0 | float dx, dy; |
792 | 0 | int code; |
793 | 0 | int i; |
794 | |
|
795 | 0 | char *start_point_att = xps_att(root, "StartPoint"); |
796 | 0 | char *end_point_att = xps_att(root, "EndPoint"); |
797 | |
|
798 | 0 | x0 = 0; |
799 | 0 | y0 = 0; |
800 | 0 | x1 = 0; |
801 | 0 | y1 = 1; |
802 | |
|
803 | 0 | if (start_point_att) |
804 | 0 | xps_get_point(start_point_att, &x0, &y0); |
805 | 0 | if (end_point_att) |
806 | 0 | xps_get_point(end_point_att, &x1, &y1); |
807 | |
|
808 | 0 | dx = x1 - x0; |
809 | 0 | dy = y1 - y0; |
810 | |
|
811 | 0 | xps_bounds_in_user_space(ctx, &bbox); |
812 | |
|
813 | 0 | if (spread == SPREAD_PAD) |
814 | 0 | { |
815 | 0 | code = xps_draw_one_linear_gradient(ctx, func, 1, x0, y0, x1, y1); |
816 | 0 | if (code < 0) |
817 | 0 | return gs_rethrow(code, "could not draw axial gradient"); |
818 | 0 | } |
819 | 0 | else |
820 | 0 | { |
821 | 0 | float len; |
822 | 0 | float a, b; |
823 | 0 | float dist[4]; |
824 | 0 | float d0, d1; |
825 | 0 | int i0, i1; |
826 | |
|
827 | 0 | len = sqrt(dx * dx + dy * dy); |
828 | 0 | a = dx / len; |
829 | 0 | b = dy / len; |
830 | |
|
831 | 0 | dist[0] = a * (bbox.p.x - x0) + b * (bbox.p.y - y0); |
832 | 0 | dist[1] = a * (bbox.p.x - x0) + b * (bbox.q.y - y0); |
833 | 0 | dist[2] = a * (bbox.q.x - x0) + b * (bbox.q.y - y0); |
834 | 0 | dist[3] = a * (bbox.q.x - x0) + b * (bbox.p.y - y0); |
835 | |
|
836 | 0 | d0 = dist[0]; |
837 | 0 | d1 = dist[0]; |
838 | 0 | for (i = 1; i < 4; i++) |
839 | 0 | { |
840 | 0 | if (dist[i] < d0) d0 = dist[i]; |
841 | 0 | if (dist[i] > d1) d1 = dist[i]; |
842 | 0 | } |
843 | |
|
844 | 0 | i0 = (int)floor(d0 / len); |
845 | 0 | i1 = (int)ceil(d1 / len); |
846 | |
|
847 | 0 | for (i = i0; i < i1; i++) |
848 | 0 | { |
849 | 0 | if (spread == SPREAD_REFLECT && (i & 1)) |
850 | 0 | { |
851 | 0 | code = xps_draw_one_linear_gradient(ctx, func, 0, |
852 | 0 | x1 + dx * i, y1 + dy * i, |
853 | 0 | x0 + dx * i, y0 + dy * i); |
854 | 0 | } |
855 | 0 | else |
856 | 0 | { |
857 | 0 | code = xps_draw_one_linear_gradient(ctx, func, 0, |
858 | 0 | x0 + dx * i, y0 + dy * i, |
859 | 0 | x1 + dx * i, y1 + dy * i); |
860 | 0 | } |
861 | 0 | if (code < 0) |
862 | 0 | return gs_rethrow(code, "could not draw axial gradient"); |
863 | 0 | } |
864 | 0 | } |
865 | | |
866 | 0 | return 0; |
867 | 0 | } |
868 | | |
869 | | /* |
870 | | * Parse XML tag and attributes for a gradient brush, create color/opacity |
871 | | * function objects and call gradient drawing primitives. |
872 | | */ |
873 | | |
874 | | static int |
875 | | xps_parse_gradient_brush(xps_context_t *ctx, char *base_uri, xps_resource_t *dict, xps_item_t *root, |
876 | | int (*draw)(xps_context_t *, xps_item_t *, int, gs_function_t *)) |
877 | 0 | { |
878 | 0 | xps_item_t *node; |
879 | |
|
880 | 0 | char *opacity_att; |
881 | | /*char *interpolation_att;*/ |
882 | 0 | char *spread_att; |
883 | | /*char *mapping_att;*/ |
884 | 0 | char *transform_att; |
885 | |
|
886 | 0 | xps_item_t *transform_tag = NULL; |
887 | 0 | xps_item_t *stop_tag = NULL; |
888 | |
|
889 | 0 | struct stop stop_list[MAX_STOPS]; |
890 | 0 | int stop_count; |
891 | 0 | gs_matrix transform; |
892 | 0 | int spread_method; |
893 | 0 | int code; |
894 | |
|
895 | 0 | gs_rect bbox; |
896 | |
|
897 | 0 | gs_function_t *color_func; |
898 | 0 | gs_function_t *opacity_func; |
899 | 0 | int has_opacity = 0; |
900 | |
|
901 | 0 | opacity_att = xps_att(root, "Opacity"); |
902 | | /*interpolation_att = xps_att(root, "ColorInterpolationMode");*/ |
903 | 0 | spread_att = xps_att(root, "SpreadMethod"); |
904 | | /*mapping_att = xps_att(root, "MappingMode");*/ |
905 | 0 | transform_att = xps_att(root, "Transform"); |
906 | |
|
907 | 0 | for (node = xps_down(root); node; node = xps_next(node)) |
908 | 0 | { |
909 | 0 | if (!strcmp(xps_tag(node), "LinearGradientBrush.Transform")) |
910 | 0 | transform_tag = xps_down(node); |
911 | 0 | if (!strcmp(xps_tag(node), "RadialGradientBrush.Transform")) |
912 | 0 | transform_tag = xps_down(node); |
913 | 0 | if (!strcmp(xps_tag(node), "LinearGradientBrush.GradientStops")) |
914 | 0 | stop_tag = xps_down(node); |
915 | 0 | if (!strcmp(xps_tag(node), "RadialGradientBrush.GradientStops")) |
916 | 0 | stop_tag = xps_down(node); |
917 | 0 | } |
918 | |
|
919 | 0 | xps_resolve_resource_reference(ctx, dict, &transform_att, &transform_tag, NULL); |
920 | |
|
921 | 0 | spread_method = SPREAD_PAD; |
922 | 0 | if (spread_att) |
923 | 0 | { |
924 | 0 | if (!strcmp(spread_att, "Pad")) |
925 | 0 | spread_method = SPREAD_PAD; |
926 | 0 | if (!strcmp(spread_att, "Reflect")) |
927 | 0 | spread_method = SPREAD_REFLECT; |
928 | 0 | if (!strcmp(spread_att, "Repeat")) |
929 | 0 | spread_method = SPREAD_REPEAT; |
930 | 0 | } |
931 | |
|
932 | 0 | gs_make_identity(&transform); |
933 | 0 | if (transform_att) |
934 | 0 | xps_parse_render_transform(ctx, transform_att, &transform); |
935 | 0 | if (transform_tag) |
936 | 0 | xps_parse_matrix_transform(ctx, transform_tag, &transform); |
937 | |
|
938 | 0 | if (!stop_tag) |
939 | 0 | return gs_throw(-1, "missing gradient stops tag"); |
940 | | |
941 | 0 | stop_count = xps_parse_gradient_stops(ctx, base_uri, stop_tag, stop_list, MAX_STOPS); |
942 | 0 | if (stop_count == 0) |
943 | 0 | return gs_throw(-1, "no gradient stops found"); |
944 | | |
945 | 0 | color_func = xps_create_gradient_stop_function(ctx, stop_list, stop_count, 0); |
946 | 0 | if (!color_func) |
947 | 0 | return gs_rethrow(-1, "could not create color gradient function"); |
948 | | |
949 | 0 | opacity_func = xps_create_gradient_stop_function(ctx, stop_list, stop_count, 1); |
950 | 0 | if (!opacity_func) |
951 | 0 | return gs_rethrow(-1, "could not create opacity gradient function"); |
952 | | |
953 | 0 | has_opacity = xps_gradient_has_transparent_colors(stop_list, stop_count); |
954 | |
|
955 | 0 | xps_clip(ctx); |
956 | |
|
957 | 0 | gs_gsave(ctx->pgs); |
958 | 0 | gs_concat(ctx->pgs, &transform); |
959 | |
|
960 | 0 | xps_bounds_in_user_space(ctx, &bbox); |
961 | |
|
962 | 0 | code = xps_begin_opacity(ctx, base_uri, dict, opacity_att, NULL, false, false); |
963 | 0 | if (code) |
964 | 0 | { |
965 | 0 | gs_grestore(ctx->pgs); |
966 | 0 | return gs_rethrow(code, "cannot create transparency group"); |
967 | 0 | } |
968 | | |
969 | 0 | if (ctx->opacity_only) |
970 | 0 | { |
971 | 0 | code = draw(ctx, root, spread_method, opacity_func); |
972 | 0 | if (code) |
973 | 0 | { |
974 | 0 | gs_grestore(ctx->pgs); |
975 | 0 | return gs_rethrow(code, "cannot draw gradient opacity"); |
976 | 0 | } |
977 | 0 | } |
978 | 0 | else |
979 | 0 | { |
980 | 0 | if (has_opacity) |
981 | 0 | { |
982 | 0 | gs_transparency_mask_params_t params; |
983 | 0 | gs_transparency_group_params_t tgp; |
984 | |
|
985 | 0 | gs_setblendmode(ctx->pgs, BLEND_MODE_Normal); |
986 | 0 | gs_trans_mask_params_init(¶ms, TRANSPARENCY_MASK_Luminosity); |
987 | 0 | params.ColorSpace = gs_currentcolorspace_inline(ctx->pgs); |
988 | 0 | gs_begin_transparency_mask(ctx->pgs, ¶ms, &bbox, 0); |
989 | | /* I dont like this, but dont want to change interface of draw */ |
990 | | /* For the opacity case, we want to make sure the functions |
991 | | are set up for gray only */ |
992 | 0 | ctx->opacity_only = true; |
993 | 0 | code = draw(ctx, root, spread_method, opacity_func); |
994 | 0 | ctx->opacity_only = false; |
995 | 0 | if (code) |
996 | 0 | { |
997 | 0 | gs_end_transparency_mask(ctx->pgs, TRANSPARENCY_CHANNEL_Opacity); |
998 | 0 | gs_grestore(ctx->pgs); |
999 | 0 | return gs_rethrow(code, "cannot draw gradient opacity"); |
1000 | 0 | } |
1001 | 0 | gs_end_transparency_mask(ctx->pgs, TRANSPARENCY_CHANNEL_Opacity); |
1002 | |
|
1003 | 0 | gs_trans_group_params_init(&tgp, 1.0); |
1004 | 0 | gs_begin_transparency_group(ctx->pgs, &tgp, &bbox, PDF14_BEGIN_TRANS_GROUP); |
1005 | 0 | code = draw(ctx, root, spread_method, color_func); |
1006 | 0 | if (code) |
1007 | 0 | { |
1008 | 0 | gs_end_transparency_group(ctx->pgs); |
1009 | 0 | gs_grestore(ctx->pgs); |
1010 | 0 | return gs_rethrow(code, "cannot draw gradient color"); |
1011 | 0 | } |
1012 | 0 | gs_end_transparency_group(ctx->pgs); |
1013 | | /* Need to remove the soft mask from the graphic state. Otherwise |
1014 | | we may end up using it in subsequent drawings. Note that there |
1015 | | is not a push of the state made since there is already a soft |
1016 | | mask present from gs_end_transparency_mask. In this case, |
1017 | | we are removing the mask with this forced pop. */ |
1018 | 0 | gs_pop_transparency_state(ctx->pgs, true); |
1019 | 0 | } |
1020 | 0 | else |
1021 | 0 | { |
1022 | 0 | code = draw(ctx, root, spread_method, color_func); |
1023 | 0 | if (code) |
1024 | 0 | { |
1025 | 0 | gs_grestore(ctx->pgs); |
1026 | 0 | return gs_rethrow(code, "cannot draw gradient color"); |
1027 | 0 | } |
1028 | 0 | } |
1029 | 0 | } |
1030 | | |
1031 | 0 | xps_end_opacity(ctx, base_uri, dict, opacity_att, NULL); |
1032 | |
|
1033 | 0 | gs_grestore(ctx->pgs); |
1034 | |
|
1035 | 0 | xps_free_gradient_stop_function(ctx, opacity_func); |
1036 | 0 | xps_free_gradient_stop_function(ctx, color_func); |
1037 | |
|
1038 | 0 | return 0; |
1039 | 0 | } |
1040 | | |
1041 | | int |
1042 | | xps_parse_linear_gradient_brush(xps_context_t *ctx, char *base_uri, xps_resource_t *dict, xps_item_t *root) |
1043 | 0 | { |
1044 | 0 | int code; |
1045 | 0 | code = xps_parse_gradient_brush(ctx, base_uri, dict, root, xps_draw_linear_gradient); |
1046 | 0 | if (code < 0) |
1047 | 0 | return gs_rethrow(code, "cannot parse linear gradient brush"); |
1048 | 0 | return gs_okay; |
1049 | 0 | } |
1050 | | |
1051 | | int |
1052 | | xps_parse_radial_gradient_brush(xps_context_t *ctx, char *base_uri, xps_resource_t *dict, xps_item_t *root) |
1053 | 0 | { |
1054 | 0 | int code; |
1055 | 0 | code = xps_parse_gradient_brush(ctx, base_uri, dict, root, xps_draw_radial_gradient); |
1056 | 0 | if (code < 0) |
1057 | 0 | return gs_rethrow(code, "cannot parse radial gradient brush"); |
1058 | 0 | return gs_okay; |
1059 | 0 | } |