/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 | 0 | if (k > INT_MAX / sizeof(float)) { |
264 | 0 | gs_throw(gs_error_limitcheck, "out of memory: range\n"); |
265 | 0 | return NULL; |
266 | 0 | } |
267 | | |
268 | 0 | domain = xps_alloc(ctx, 2 * sizeof(float)); |
269 | 0 | if (!domain) { |
270 | 0 | gs_throw(gs_error_VMerror, "out of memory: domain\n"); |
271 | 0 | return NULL; |
272 | 0 | } |
273 | 0 | domain[0] = 0.0; |
274 | 0 | domain[1] = 1.0; |
275 | 0 | sparams.m = 1; |
276 | 0 | sparams.Domain = domain; |
277 | |
|
278 | 0 | range = xps_alloc(ctx, 6 * sizeof(float)); |
279 | 0 | if (!range) { |
280 | 0 | gs_throw(gs_error_VMerror, "out of memory: range\n"); |
281 | 0 | return NULL; |
282 | 0 | } |
283 | 0 | range[0] = 0.0; |
284 | 0 | range[1] = 1.0; |
285 | 0 | range[2] = 0.0; |
286 | 0 | range[3] = 1.0; |
287 | 0 | range[4] = 0.0; |
288 | 0 | range[5] = 1.0; |
289 | 0 | sparams.Range = range; |
290 | |
|
291 | 0 | functions = xps_alloc(ctx, (size_t)k * sizeof(void*)); |
292 | 0 | if (!functions) { |
293 | 0 | gs_throw(gs_error_VMerror, "out of memory: functions.\n"); |
294 | 0 | return NULL; |
295 | 0 | } |
296 | 0 | bounds = xps_alloc(ctx, ((size_t)k - 1) * sizeof(float)); |
297 | 0 | if (!bounds) { |
298 | 0 | gs_throw(gs_error_VMerror, "out of memory: bounds.\n"); |
299 | 0 | return NULL; |
300 | 0 | } |
301 | 0 | encode = xps_alloc(ctx, ((size_t)k * 2) * sizeof(float)); |
302 | 0 | if (!encode) { |
303 | 0 | gs_throw(gs_error_VMerror, "out of memory: encode.\n"); |
304 | 0 | return NULL; |
305 | 0 | } |
306 | | |
307 | 0 | sparams.k = k; |
308 | 0 | sparams.Functions = functions; |
309 | 0 | sparams.Bounds = bounds; |
310 | 0 | sparams.Encode = encode; |
311 | |
|
312 | 0 | if (opacity_only) |
313 | 0 | { |
314 | 0 | sparams.n = 1; |
315 | 0 | lparams.n = 1; |
316 | 0 | } |
317 | 0 | else |
318 | 0 | { |
319 | 0 | sparams.n = 3; |
320 | 0 | lparams.n = 3; |
321 | 0 | } |
322 | |
|
323 | 0 | for (i = 0; i < k; i++) |
324 | 0 | { |
325 | 0 | domain = xps_alloc(ctx, 2 * sizeof(float)); |
326 | 0 | if (!domain) { |
327 | 0 | gs_throw(gs_error_VMerror, "out of memory: domain.\n"); |
328 | 0 | return NULL; |
329 | 0 | } |
330 | 0 | domain[0] = 0.0; |
331 | 0 | domain[1] = 1.0; |
332 | 0 | lparams.m = 1; |
333 | 0 | lparams.Domain = domain; |
334 | |
|
335 | 0 | range = xps_alloc(ctx, 6 * sizeof(float)); |
336 | 0 | if (!range) { |
337 | 0 | gs_throw(gs_error_VMerror, "out of memory: range.\n"); |
338 | 0 | return NULL; |
339 | 0 | } |
340 | 0 | range[0] = 0.0; |
341 | 0 | range[1] = 1.0; |
342 | 0 | range[2] = 0.0; |
343 | 0 | range[3] = 1.0; |
344 | 0 | range[4] = 0.0; |
345 | 0 | range[5] = 1.0; |
346 | 0 | lparams.Range = range; |
347 | |
|
348 | 0 | c0 = xps_alloc(ctx, 3 * sizeof(float)); |
349 | 0 | if (!c0) { |
350 | 0 | gs_throw(gs_error_VMerror, "out of memory: c0.\n"); |
351 | 0 | return NULL; |
352 | 0 | } |
353 | 0 | lparams.C0 = c0; |
354 | |
|
355 | 0 | c1 = xps_alloc(ctx, 3 * sizeof(float)); |
356 | 0 | if (!c1) { |
357 | 0 | gs_throw(gs_error_VMerror, "out of memory: c1.\n"); |
358 | 0 | return NULL; |
359 | 0 | } |
360 | 0 | lparams.C1 = c1; |
361 | |
|
362 | 0 | if (opacity_only) |
363 | 0 | { |
364 | 0 | c0[0] = stops[i].color[0]; |
365 | 0 | c1[0] = stops[i+1].color[0]; |
366 | 0 | } |
367 | 0 | else |
368 | 0 | { |
369 | 0 | c0[0] = stops[i].color[1]; |
370 | 0 | c0[1] = stops[i].color[2]; |
371 | 0 | c0[2] = stops[i].color[3]; |
372 | |
|
373 | 0 | c1[0] = stops[i+1].color[1]; |
374 | 0 | c1[1] = stops[i+1].color[2]; |
375 | 0 | c1[2] = stops[i+1].color[3]; |
376 | 0 | } |
377 | |
|
378 | 0 | lparams.N = 1; |
379 | |
|
380 | 0 | code = gs_function_ElIn_init(&lfunc, &lparams, ctx->memory); |
381 | 0 | if (code < 0) |
382 | 0 | { |
383 | 0 | gs_rethrow(code, "gs_function_ElIn_init failed"); |
384 | 0 | return NULL; |
385 | 0 | } |
386 | | |
387 | 0 | functions[i] = lfunc; |
388 | |
|
389 | 0 | if (i > 0) |
390 | 0 | bounds[i - 1] = stops[i].offset; |
391 | |
|
392 | 0 | encode[i * 2 + 0] = 0.0; |
393 | 0 | encode[i * 2 + 1] = 1.0; |
394 | 0 | } |
395 | | |
396 | 0 | code = gs_function_1ItSg_init(&sfunc, &sparams, ctx->memory); |
397 | 0 | if (code < 0) |
398 | 0 | { |
399 | 0 | gs_rethrow(code, "gs_function_1ItSg_init failed"); |
400 | 0 | return NULL; |
401 | 0 | } |
402 | | |
403 | 0 | return sfunc; |
404 | 0 | } |
405 | | |
406 | | /* |
407 | | * Shadings and functions are ghostscript type objects, |
408 | | * and as such rely on the garbage collector for cleanup. |
409 | | * We can't have none of that here, so we have to |
410 | | * write our own destructors. |
411 | | */ |
412 | | |
413 | | static void |
414 | | xps_free_gradient_stop_function(xps_context_t *ctx, gs_function_t *func) |
415 | 0 | { |
416 | 0 | gs_function_t *lfunc; |
417 | 0 | gs_function_1ItSg_params_t *sparams; |
418 | 0 | gs_function_ElIn_params_t *lparams; |
419 | 0 | int i; |
420 | |
|
421 | 0 | sparams = (gs_function_1ItSg_params_t*) &func->params; |
422 | 0 | xps_free(ctx, (void*)sparams->Domain); |
423 | 0 | xps_free(ctx, (void*)sparams->Range); |
424 | |
|
425 | 0 | for (i = 0; i < sparams->k; i++) |
426 | 0 | { |
427 | 0 | lfunc = (gs_function_t*) sparams->Functions[i]; /* discard const */ |
428 | 0 | lparams = (gs_function_ElIn_params_t*) &lfunc->params; |
429 | 0 | xps_free(ctx, (void*)lparams->Domain); |
430 | 0 | xps_free(ctx, (void*)lparams->Range); |
431 | 0 | xps_free(ctx, (void*)lparams->C0); |
432 | 0 | xps_free(ctx, (void*)lparams->C1); |
433 | 0 | xps_free(ctx, lfunc); |
434 | 0 | } |
435 | |
|
436 | 0 | xps_free(ctx, (void*)sparams->Bounds); |
437 | 0 | xps_free(ctx, (void*)sparams->Encode); |
438 | 0 | xps_free(ctx, (void*)sparams->Functions); |
439 | 0 | xps_free(ctx, func); |
440 | 0 | } |
441 | | |
442 | | /* |
443 | | * For radial gradients that have a cone drawing we have to |
444 | | * reverse the direction of the gradient because we draw |
445 | | * the shading in the opposite direction with the |
446 | | * big circle first. |
447 | | */ |
448 | | static gs_function_t * |
449 | | xps_reverse_function(xps_context_t *ctx, gs_function_t *func, float *fary, void *vary) |
450 | 0 | { |
451 | 0 | gs_function_1ItSg_params_t sparams; |
452 | 0 | gs_function_t *sfunc; |
453 | 0 | int code; |
454 | | |
455 | | /* take from stack allocated arrays that the caller provides */ |
456 | 0 | float *domain = fary + 0; |
457 | 0 | float *range = fary + 2; |
458 | 0 | float *encode = fary + 2 + 6; |
459 | 0 | const gs_function_t **functions = vary; |
460 | |
|
461 | 0 | domain[0] = 0.0; |
462 | 0 | domain[1] = 1.0; |
463 | |
|
464 | 0 | range[0] = 0.0; |
465 | 0 | range[1] = 1.0; |
466 | 0 | range[2] = 0.0; |
467 | 0 | range[3] = 1.0; |
468 | 0 | range[4] = 0.0; |
469 | 0 | range[5] = 1.0; |
470 | |
|
471 | 0 | functions[0] = func; |
472 | |
|
473 | 0 | encode[0] = 1.0; |
474 | 0 | encode[1] = 0.0; |
475 | |
|
476 | 0 | sparams.m = 1; |
477 | 0 | sparams.Domain = domain; |
478 | 0 | sparams.Range = range; |
479 | 0 | sparams.k = 1; |
480 | 0 | sparams.Functions = functions; |
481 | 0 | sparams.Bounds = NULL; |
482 | 0 | sparams.Encode = encode; |
483 | |
|
484 | 0 | if (ctx->opacity_only) |
485 | 0 | sparams.n = 1; |
486 | 0 | else |
487 | 0 | sparams.n = 3; |
488 | |
|
489 | 0 | code = gs_function_1ItSg_init(&sfunc, &sparams, ctx->memory); |
490 | 0 | if (code < 0) |
491 | 0 | { |
492 | 0 | gs_rethrow(code, "gs_function_1ItSg_init failed"); |
493 | 0 | return NULL; |
494 | 0 | } |
495 | | |
496 | 0 | return sfunc; |
497 | 0 | } |
498 | | |
499 | | /* |
500 | | * Radial gradients map more or less to Radial shadings. |
501 | | * The inner circle is always a point. |
502 | | * The outer circle is actually an ellipse, |
503 | | * mess with the transform to squash the circle into the right aspect. |
504 | | */ |
505 | | |
506 | | static int |
507 | | xps_draw_one_radial_gradient(xps_context_t *ctx, |
508 | | gs_function_t *func, int extend, |
509 | | float x0, float y0, float r0, |
510 | | float x1, float y1, float r1) |
511 | 0 | { |
512 | 0 | gs_memory_t *mem = ctx->memory; |
513 | 0 | gs_shading_t *shading; |
514 | 0 | gs_shading_R_params_t params; |
515 | 0 | int code; |
516 | |
|
517 | 0 | gs_shading_R_params_init(¶ms); |
518 | 0 | { |
519 | 0 | if (ctx->opacity_only) |
520 | 0 | params.ColorSpace = ctx->gray_lin; |
521 | 0 | else |
522 | 0 | params.ColorSpace = ctx->srgb; |
523 | |
|
524 | 0 | params.Coords[0] = x0; |
525 | 0 | params.Coords[1] = y0; |
526 | 0 | params.Coords[2] = r0; |
527 | 0 | params.Coords[3] = x1; |
528 | 0 | params.Coords[4] = y1; |
529 | 0 | params.Coords[5] = r1; |
530 | |
|
531 | 0 | params.Extend[0] = extend; |
532 | 0 | params.Extend[1] = extend; |
533 | |
|
534 | 0 | params.Function = func; |
535 | 0 | } |
536 | |
|
537 | 0 | code = gs_shading_R_init(&shading, ¶ms, mem); |
538 | 0 | if (code < 0) |
539 | 0 | return gs_rethrow(code, "gs_shading_R_init failed"); |
540 | | |
541 | 0 | gs_setsmoothness(ctx->pgs, 0.02); |
542 | |
|
543 | 0 | code = gs_shfill(ctx->pgs, shading); |
544 | 0 | if (code < 0) |
545 | 0 | { |
546 | 0 | gs_free_object(mem, shading, "gs_shading_R"); |
547 | 0 | return gs_rethrow(code, "gs_shfill failed"); |
548 | 0 | } |
549 | | |
550 | 0 | gs_free_object(mem, shading, "gs_shading_R"); |
551 | |
|
552 | 0 | return 0; |
553 | 0 | } |
554 | | |
555 | | /* |
556 | | * Linear gradients map to Axial shadings. |
557 | | */ |
558 | | |
559 | | static int |
560 | | xps_draw_one_linear_gradient(xps_context_t *ctx, |
561 | | gs_function_t *func, int extend, |
562 | | float x0, float y0, float x1, float y1) |
563 | 0 | { |
564 | 0 | gs_memory_t *mem = ctx->memory; |
565 | 0 | gs_shading_t *shading; |
566 | 0 | gs_shading_A_params_t params; |
567 | 0 | int code; |
568 | |
|
569 | 0 | gs_shading_A_params_init(¶ms); |
570 | 0 | { |
571 | 0 | if (ctx->opacity_only) |
572 | 0 | params.ColorSpace = ctx->gray_lin; |
573 | 0 | else |
574 | 0 | params.ColorSpace = ctx->srgb; |
575 | |
|
576 | 0 | params.Coords[0] = x0; |
577 | 0 | params.Coords[1] = y0; |
578 | 0 | params.Coords[2] = x1; |
579 | 0 | params.Coords[3] = y1; |
580 | |
|
581 | 0 | params.Extend[0] = extend; |
582 | 0 | params.Extend[1] = extend; |
583 | |
|
584 | 0 | params.Function = func; |
585 | 0 | } |
586 | |
|
587 | 0 | code = gs_shading_A_init(&shading, ¶ms, mem); |
588 | 0 | if (code < 0) |
589 | 0 | return gs_rethrow(code, "gs_shading_A_init failed"); |
590 | | |
591 | 0 | gs_setsmoothness(ctx->pgs, 0.02); |
592 | |
|
593 | 0 | code = gs_shfill(ctx->pgs, shading); |
594 | 0 | if (code < 0) |
595 | 0 | { |
596 | 0 | gs_free_object(mem, shading, "gs_shading_A"); |
597 | 0 | return gs_rethrow(code, "gs_shfill failed"); |
598 | 0 | } |
599 | | |
600 | 0 | gs_free_object(mem, shading, "gs_shading_A"); |
601 | |
|
602 | 0 | return 0; |
603 | 0 | } |
604 | | |
605 | | /* |
606 | | * We need to loop and create many shading objects to account |
607 | | * for the Repeat and Reflect SpreadMethods. |
608 | | * I'm not smart enough to calculate this analytically |
609 | | * so we iterate and check each object until we |
610 | | * reach a reasonable limit for infinite cases. |
611 | | */ |
612 | | |
613 | | static inline int point_inside_circle(float px, float py, float x, float y, float r) |
614 | 0 | { |
615 | 0 | float dx = px - x; |
616 | 0 | float dy = py - y; |
617 | 0 | return (dx * dx + dy * dy) <= (r * r); |
618 | 0 | } |
619 | | |
620 | | static int |
621 | | xps_draw_radial_gradient(xps_context_t *ctx, xps_item_t *root, int spread, gs_function_t *func) |
622 | 0 | { |
623 | 0 | gs_rect bbox; |
624 | 0 | float x0 = 0, y0 = 0, r0; |
625 | 0 | float x1 = 0, y1 = 0, r1; |
626 | 0 | float xrad = 1; |
627 | 0 | float yrad = 1; |
628 | 0 | float invscale; |
629 | 0 | float dx, dy; |
630 | 0 | int code; |
631 | 0 | int i; |
632 | 0 | int done; |
633 | |
|
634 | 0 | char *center_att = xps_att(root, "Center"); |
635 | 0 | char *origin_att = xps_att(root, "GradientOrigin"); |
636 | 0 | char *radius_x_att = xps_att(root, "RadiusX"); |
637 | 0 | char *radius_y_att = xps_att(root, "RadiusY"); |
638 | |
|
639 | 0 | if (origin_att) |
640 | 0 | xps_get_point(origin_att, &x0, &y0); |
641 | 0 | if (center_att) |
642 | 0 | xps_get_point(center_att, &x1, &y1); |
643 | 0 | if (radius_x_att) |
644 | 0 | xrad = atof(radius_x_att); |
645 | 0 | if (radius_y_att) |
646 | 0 | yrad = atof(radius_y_att); |
647 | |
|
648 | 0 | gs_gsave(ctx->pgs); |
649 | | |
650 | | /* scale the ctm to make ellipses */ |
651 | 0 | if (xrad != 0) |
652 | 0 | gs_scale(ctx->pgs, 1.0, yrad / xrad); |
653 | |
|
654 | 0 | if (yrad != 0) |
655 | 0 | { |
656 | 0 | invscale = xrad / yrad; |
657 | 0 | y0 = y0 * invscale; |
658 | 0 | y1 = y1 * invscale; |
659 | 0 | } |
660 | |
|
661 | 0 | r0 = 0.0; |
662 | 0 | r1 = xrad; |
663 | |
|
664 | 0 | dx = x1 - x0; |
665 | 0 | dy = y1 - y0; |
666 | |
|
667 | 0 | xps_bounds_in_user_space(ctx, &bbox); |
668 | |
|
669 | 0 | if (spread == SPREAD_PAD) |
670 | 0 | { |
671 | 0 | if (!point_inside_circle(x0, y0, x1, y1, r1)) |
672 | 0 | { |
673 | 0 | gs_function_t *reverse; |
674 | 0 | float in[1]; |
675 | 0 | float out[4]; |
676 | 0 | float fary[10]; |
677 | 0 | void *vary[1]; |
678 | | |
679 | | /* PDF shadings with extend doesn't work the same way as XPS |
680 | | * gradients when the radial shading is a cone. In this case |
681 | | * we fill the background ourselves. |
682 | | */ |
683 | |
|
684 | 0 | in[0] = 1.0; |
685 | 0 | out[0] = 1.0; |
686 | 0 | out[1] = 0.0; |
687 | 0 | out[2] = 0.0; |
688 | 0 | out[3] = 0.0; |
689 | 0 | if (ctx->opacity_only) |
690 | 0 | { |
691 | 0 | gs_function_evaluate(func, in, out); |
692 | 0 | xps_set_color(ctx, ctx->gray_lin, out); |
693 | 0 | } |
694 | 0 | else |
695 | 0 | { |
696 | 0 | gs_function_evaluate(func, in, out + 1); |
697 | 0 | xps_set_color(ctx, ctx->srgb, out); |
698 | 0 | } |
699 | |
|
700 | 0 | gs_moveto(ctx->pgs, bbox.p.x, bbox.p.y); |
701 | 0 | gs_lineto(ctx->pgs, bbox.q.x, bbox.p.y); |
702 | 0 | gs_lineto(ctx->pgs, bbox.q.x, bbox.q.y); |
703 | 0 | gs_lineto(ctx->pgs, bbox.p.x, bbox.q.y); |
704 | 0 | gs_closepath(ctx->pgs); |
705 | 0 | gs_fill(ctx->pgs); |
706 | | |
707 | | /* We also have to reverse the direction so the bigger circle |
708 | | * comes first or the graphical results do not match. We also |
709 | | * have to reverse the direction of the function to compensate. |
710 | | */ |
711 | |
|
712 | 0 | reverse = xps_reverse_function(ctx, func, fary, vary); |
713 | 0 | if (!reverse) |
714 | 0 | { |
715 | 0 | gs_grestore(ctx->pgs); |
716 | 0 | return gs_rethrow(-1, "could not create the reversed function"); |
717 | 0 | } |
718 | | |
719 | 0 | code = xps_draw_one_radial_gradient(ctx, reverse, 1, x1, y1, r1, x0, y0, r0); |
720 | 0 | if (code < 0) |
721 | 0 | { |
722 | 0 | xps_free(ctx, reverse); |
723 | 0 | gs_grestore(ctx->pgs); |
724 | 0 | return gs_rethrow(code, "could not draw radial gradient"); |
725 | 0 | } |
726 | | |
727 | 0 | xps_free(ctx, reverse); |
728 | 0 | } |
729 | 0 | else |
730 | 0 | { |
731 | 0 | code = xps_draw_one_radial_gradient(ctx, func, 1, x0, y0, r0, x1, y1, r1); |
732 | 0 | if (code < 0) |
733 | 0 | { |
734 | 0 | gs_grestore(ctx->pgs); |
735 | 0 | return gs_rethrow(code, "could not draw radial gradient"); |
736 | 0 | } |
737 | 0 | } |
738 | 0 | } |
739 | 0 | else |
740 | 0 | { |
741 | 0 | for (i = 0; i < 100; i++) |
742 | 0 | { |
743 | | /* Draw current circle */ |
744 | |
|
745 | 0 | if (!point_inside_circle(x0, y0, x1, y1, r1)) |
746 | 0 | dmputs(ctx->memory, "xps: we should reverse gradient here too\n"); |
747 | |
|
748 | 0 | if (spread == SPREAD_REFLECT && (i & 1)) |
749 | 0 | code = xps_draw_one_radial_gradient(ctx, func, 0, x1, y1, r1, x0, y0, r0); |
750 | 0 | else |
751 | 0 | code = xps_draw_one_radial_gradient(ctx, func, 0, x0, y0, r0, x1, y1, r1); |
752 | 0 | if (code < 0) |
753 | 0 | { |
754 | 0 | gs_grestore(ctx->pgs); |
755 | 0 | return gs_rethrow(code, "could not draw axial gradient"); |
756 | 0 | } |
757 | | |
758 | | /* Check if circle encompassed the entire bounding box (break loop if we do) */ |
759 | | |
760 | 0 | done = 1; |
761 | 0 | if (!point_inside_circle(bbox.p.x, bbox.p.y, x1, y1, r1)) done = 0; |
762 | 0 | if (!point_inside_circle(bbox.p.x, bbox.q.y, x1, y1, r1)) done = 0; |
763 | 0 | if (!point_inside_circle(bbox.q.x, bbox.q.y, x1, y1, r1)) done = 0; |
764 | 0 | if (!point_inside_circle(bbox.q.x, bbox.p.y, x1, y1, r1)) done = 0; |
765 | 0 | if (done) |
766 | 0 | break; |
767 | | |
768 | | /* Prepare next circle */ |
769 | | |
770 | 0 | r0 = r1; |
771 | 0 | r1 += xrad; |
772 | |
|
773 | 0 | x0 += dx; |
774 | 0 | y0 += dy; |
775 | 0 | x1 += dx; |
776 | 0 | y1 += dy; |
777 | 0 | } |
778 | 0 | } |
779 | | |
780 | 0 | gs_grestore(ctx->pgs); |
781 | |
|
782 | 0 | return 0; |
783 | 0 | } |
784 | | |
785 | | /* |
786 | | * Calculate how many iterations are needed to cover |
787 | | * the bounding box. |
788 | | */ |
789 | | |
790 | | static int |
791 | | xps_draw_linear_gradient(xps_context_t *ctx, xps_item_t *root, int spread, gs_function_t *func) |
792 | 0 | { |
793 | 0 | gs_rect bbox; |
794 | 0 | float x0, y0, x1, y1; |
795 | 0 | float dx, dy; |
796 | 0 | int code; |
797 | 0 | int i; |
798 | |
|
799 | 0 | char *start_point_att = xps_att(root, "StartPoint"); |
800 | 0 | char *end_point_att = xps_att(root, "EndPoint"); |
801 | |
|
802 | 0 | x0 = 0; |
803 | 0 | y0 = 0; |
804 | 0 | x1 = 0; |
805 | 0 | y1 = 1; |
806 | |
|
807 | 0 | if (start_point_att) |
808 | 0 | xps_get_point(start_point_att, &x0, &y0); |
809 | 0 | if (end_point_att) |
810 | 0 | xps_get_point(end_point_att, &x1, &y1); |
811 | |
|
812 | 0 | dx = x1 - x0; |
813 | 0 | dy = y1 - y0; |
814 | |
|
815 | 0 | xps_bounds_in_user_space(ctx, &bbox); |
816 | |
|
817 | 0 | if (spread == SPREAD_PAD) |
818 | 0 | { |
819 | 0 | code = xps_draw_one_linear_gradient(ctx, func, 1, x0, y0, x1, y1); |
820 | 0 | if (code < 0) |
821 | 0 | return gs_rethrow(code, "could not draw axial gradient"); |
822 | 0 | } |
823 | 0 | else |
824 | 0 | { |
825 | 0 | float len; |
826 | 0 | float a, b; |
827 | 0 | float dist[4]; |
828 | 0 | float d0, d1; |
829 | 0 | int i0, i1; |
830 | |
|
831 | 0 | len = sqrt(dx * dx + dy * dy); |
832 | 0 | a = dx / len; |
833 | 0 | b = dy / len; |
834 | |
|
835 | 0 | dist[0] = a * (bbox.p.x - x0) + b * (bbox.p.y - y0); |
836 | 0 | dist[1] = a * (bbox.p.x - x0) + b * (bbox.q.y - y0); |
837 | 0 | dist[2] = a * (bbox.q.x - x0) + b * (bbox.q.y - y0); |
838 | 0 | dist[3] = a * (bbox.q.x - x0) + b * (bbox.p.y - y0); |
839 | |
|
840 | 0 | d0 = dist[0]; |
841 | 0 | d1 = dist[0]; |
842 | 0 | for (i = 1; i < 4; i++) |
843 | 0 | { |
844 | 0 | if (dist[i] < d0) d0 = dist[i]; |
845 | 0 | if (dist[i] > d1) d1 = dist[i]; |
846 | 0 | } |
847 | |
|
848 | 0 | i0 = (int)floor(d0 / len); |
849 | 0 | i1 = (int)ceil(d1 / len); |
850 | |
|
851 | 0 | for (i = i0; i < i1; i++) |
852 | 0 | { |
853 | 0 | if (spread == SPREAD_REFLECT && (i & 1)) |
854 | 0 | { |
855 | 0 | code = xps_draw_one_linear_gradient(ctx, func, 0, |
856 | 0 | x1 + dx * i, y1 + dy * i, |
857 | 0 | x0 + dx * i, y0 + dy * i); |
858 | 0 | } |
859 | 0 | else |
860 | 0 | { |
861 | 0 | code = xps_draw_one_linear_gradient(ctx, func, 0, |
862 | 0 | x0 + dx * i, y0 + dy * i, |
863 | 0 | x1 + dx * i, y1 + dy * i); |
864 | 0 | } |
865 | 0 | if (code < 0) |
866 | 0 | return gs_rethrow(code, "could not draw axial gradient"); |
867 | 0 | } |
868 | 0 | } |
869 | | |
870 | 0 | return 0; |
871 | 0 | } |
872 | | |
873 | | /* |
874 | | * Parse XML tag and attributes for a gradient brush, create color/opacity |
875 | | * function objects and call gradient drawing primitives. |
876 | | */ |
877 | | |
878 | | static int |
879 | | xps_parse_gradient_brush(xps_context_t *ctx, char *base_uri, xps_resource_t *dict, xps_item_t *root, |
880 | | int (*draw)(xps_context_t *, xps_item_t *, int, gs_function_t *)) |
881 | 0 | { |
882 | 0 | xps_item_t *node; |
883 | |
|
884 | 0 | char *opacity_att; |
885 | | /*char *interpolation_att;*/ |
886 | 0 | char *spread_att; |
887 | | /*char *mapping_att;*/ |
888 | 0 | char *transform_att; |
889 | |
|
890 | 0 | xps_item_t *transform_tag = NULL; |
891 | 0 | xps_item_t *stop_tag = NULL; |
892 | |
|
893 | 0 | struct stop stop_list[MAX_STOPS]; |
894 | 0 | int stop_count; |
895 | 0 | gs_matrix transform; |
896 | 0 | int spread_method; |
897 | 0 | int code; |
898 | |
|
899 | 0 | gs_rect bbox; |
900 | |
|
901 | 0 | gs_function_t *color_func; |
902 | 0 | gs_function_t *opacity_func; |
903 | 0 | int has_opacity = 0; |
904 | |
|
905 | 0 | opacity_att = xps_att(root, "Opacity"); |
906 | | /*interpolation_att = xps_att(root, "ColorInterpolationMode");*/ |
907 | 0 | spread_att = xps_att(root, "SpreadMethod"); |
908 | | /*mapping_att = xps_att(root, "MappingMode");*/ |
909 | 0 | transform_att = xps_att(root, "Transform"); |
910 | |
|
911 | 0 | for (node = xps_down(root); node; node = xps_next(node)) |
912 | 0 | { |
913 | 0 | if (!strcmp(xps_tag(node), "LinearGradientBrush.Transform")) |
914 | 0 | transform_tag = xps_down(node); |
915 | 0 | if (!strcmp(xps_tag(node), "RadialGradientBrush.Transform")) |
916 | 0 | transform_tag = xps_down(node); |
917 | 0 | if (!strcmp(xps_tag(node), "LinearGradientBrush.GradientStops")) |
918 | 0 | stop_tag = xps_down(node); |
919 | 0 | if (!strcmp(xps_tag(node), "RadialGradientBrush.GradientStops")) |
920 | 0 | stop_tag = xps_down(node); |
921 | 0 | } |
922 | |
|
923 | 0 | xps_resolve_resource_reference(ctx, dict, &transform_att, &transform_tag, NULL); |
924 | |
|
925 | 0 | spread_method = SPREAD_PAD; |
926 | 0 | if (spread_att) |
927 | 0 | { |
928 | 0 | if (!strcmp(spread_att, "Pad")) |
929 | 0 | spread_method = SPREAD_PAD; |
930 | 0 | if (!strcmp(spread_att, "Reflect")) |
931 | 0 | spread_method = SPREAD_REFLECT; |
932 | 0 | if (!strcmp(spread_att, "Repeat")) |
933 | 0 | spread_method = SPREAD_REPEAT; |
934 | 0 | } |
935 | |
|
936 | 0 | gs_make_identity(&transform); |
937 | 0 | if (transform_att) |
938 | 0 | xps_parse_render_transform(ctx, transform_att, &transform); |
939 | 0 | if (transform_tag) |
940 | 0 | xps_parse_matrix_transform(ctx, transform_tag, &transform); |
941 | |
|
942 | 0 | if (!stop_tag) |
943 | 0 | return gs_throw(-1, "missing gradient stops tag"); |
944 | | |
945 | 0 | stop_count = xps_parse_gradient_stops(ctx, base_uri, stop_tag, stop_list, MAX_STOPS); |
946 | 0 | if (stop_count == 0) |
947 | 0 | return gs_throw(-1, "no gradient stops found"); |
948 | | |
949 | 0 | color_func = xps_create_gradient_stop_function(ctx, stop_list, stop_count, 0); |
950 | 0 | if (!color_func) |
951 | 0 | return gs_rethrow(-1, "could not create color gradient function"); |
952 | | |
953 | 0 | opacity_func = xps_create_gradient_stop_function(ctx, stop_list, stop_count, 1); |
954 | 0 | if (!opacity_func) |
955 | 0 | return gs_rethrow(-1, "could not create opacity gradient function"); |
956 | | |
957 | 0 | has_opacity = xps_gradient_has_transparent_colors(stop_list, stop_count); |
958 | |
|
959 | 0 | xps_clip(ctx); |
960 | |
|
961 | 0 | gs_gsave(ctx->pgs); |
962 | 0 | gs_concat(ctx->pgs, &transform); |
963 | |
|
964 | 0 | xps_bounds_in_user_space(ctx, &bbox); |
965 | |
|
966 | 0 | code = xps_begin_opacity(ctx, base_uri, dict, opacity_att, NULL, false, false); |
967 | 0 | if (code) |
968 | 0 | { |
969 | 0 | gs_grestore(ctx->pgs); |
970 | 0 | return gs_rethrow(code, "cannot create transparency group"); |
971 | 0 | } |
972 | | |
973 | 0 | if (ctx->opacity_only) |
974 | 0 | { |
975 | 0 | code = draw(ctx, root, spread_method, opacity_func); |
976 | 0 | if (code) |
977 | 0 | { |
978 | 0 | gs_grestore(ctx->pgs); |
979 | 0 | return gs_rethrow(code, "cannot draw gradient opacity"); |
980 | 0 | } |
981 | 0 | } |
982 | 0 | else |
983 | 0 | { |
984 | 0 | if (has_opacity) |
985 | 0 | { |
986 | 0 | gs_transparency_mask_params_t params; |
987 | 0 | gs_transparency_group_params_t tgp; |
988 | |
|
989 | 0 | gs_setblendmode(ctx->pgs, BLEND_MODE_Normal); |
990 | 0 | gs_trans_mask_params_init(¶ms, TRANSPARENCY_MASK_Luminosity); |
991 | 0 | params.ColorSpace = gs_currentcolorspace_inline(ctx->pgs); |
992 | 0 | gs_begin_transparency_mask(ctx->pgs, ¶ms, &bbox, 0); |
993 | | /* I dont like this, but dont want to change interface of draw */ |
994 | | /* For the opacity case, we want to make sure the functions |
995 | | are set up for gray only */ |
996 | 0 | ctx->opacity_only = true; |
997 | 0 | code = draw(ctx, root, spread_method, opacity_func); |
998 | 0 | ctx->opacity_only = false; |
999 | 0 | if (code) |
1000 | 0 | { |
1001 | 0 | gs_end_transparency_mask(ctx->pgs, TRANSPARENCY_CHANNEL_Opacity); |
1002 | 0 | gs_grestore(ctx->pgs); |
1003 | 0 | return gs_rethrow(code, "cannot draw gradient opacity"); |
1004 | 0 | } |
1005 | 0 | gs_end_transparency_mask(ctx->pgs, TRANSPARENCY_CHANNEL_Opacity); |
1006 | |
|
1007 | 0 | gs_trans_group_params_init(&tgp, 1.0); |
1008 | 0 | gs_begin_transparency_group(ctx->pgs, &tgp, &bbox, PDF14_BEGIN_TRANS_GROUP); |
1009 | 0 | code = draw(ctx, root, spread_method, color_func); |
1010 | 0 | if (code) |
1011 | 0 | { |
1012 | 0 | gs_end_transparency_group(ctx->pgs); |
1013 | 0 | gs_grestore(ctx->pgs); |
1014 | 0 | return gs_rethrow(code, "cannot draw gradient color"); |
1015 | 0 | } |
1016 | 0 | gs_end_transparency_group(ctx->pgs); |
1017 | | /* Need to remove the soft mask from the graphic state. Otherwise |
1018 | | we may end up using it in subsequent drawings. Note that there |
1019 | | is not a push of the state made since there is already a soft |
1020 | | mask present from gs_end_transparency_mask. In this case, |
1021 | | we are removing the mask with this forced pop. */ |
1022 | 0 | gs_pop_transparency_state(ctx->pgs, true); |
1023 | 0 | } |
1024 | 0 | else |
1025 | 0 | { |
1026 | 0 | code = draw(ctx, root, spread_method, color_func); |
1027 | 0 | if (code) |
1028 | 0 | { |
1029 | 0 | gs_grestore(ctx->pgs); |
1030 | 0 | return gs_rethrow(code, "cannot draw gradient color"); |
1031 | 0 | } |
1032 | 0 | } |
1033 | 0 | } |
1034 | | |
1035 | 0 | xps_end_opacity(ctx, base_uri, dict, opacity_att, NULL); |
1036 | |
|
1037 | 0 | gs_grestore(ctx->pgs); |
1038 | |
|
1039 | 0 | xps_free_gradient_stop_function(ctx, opacity_func); |
1040 | 0 | xps_free_gradient_stop_function(ctx, color_func); |
1041 | |
|
1042 | 0 | return 0; |
1043 | 0 | } |
1044 | | |
1045 | | int |
1046 | | xps_parse_linear_gradient_brush(xps_context_t *ctx, char *base_uri, xps_resource_t *dict, xps_item_t *root) |
1047 | 0 | { |
1048 | 0 | int code; |
1049 | 0 | code = xps_parse_gradient_brush(ctx, base_uri, dict, root, xps_draw_linear_gradient); |
1050 | 0 | if (code < 0) |
1051 | 0 | return gs_rethrow(code, "cannot parse linear gradient brush"); |
1052 | 0 | return gs_okay; |
1053 | 0 | } |
1054 | | |
1055 | | int |
1056 | | xps_parse_radial_gradient_brush(xps_context_t *ctx, char *base_uri, xps_resource_t *dict, xps_item_t *root) |
1057 | 0 | { |
1058 | 0 | int code; |
1059 | 0 | code = xps_parse_gradient_brush(ctx, base_uri, dict, root, xps_draw_radial_gradient); |
1060 | 0 | if (code < 0) |
1061 | 0 | return gs_rethrow(code, "cannot parse radial gradient brush"); |
1062 | 0 | return gs_okay; |
1063 | 0 | } |