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