/src/cairo/subprojects/pixman-0.44.2/pixman/pixman-radial-gradient.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: c; c-basic-offset: 4; tab-width: 8; indent-tabs-mode: t; -*- */ |
2 | | /* |
3 | | * |
4 | | * Copyright © 2000 Keith Packard, member of The XFree86 Project, Inc. |
5 | | * Copyright © 2000 SuSE, Inc. |
6 | | * 2005 Lars Knoll & Zack Rusin, Trolltech |
7 | | * Copyright © 2007 Red Hat, Inc. |
8 | | * |
9 | | * |
10 | | * Permission to use, copy, modify, distribute, and sell this software and its |
11 | | * documentation for any purpose is hereby granted without fee, provided that |
12 | | * the above copyright notice appear in all copies and that both that |
13 | | * copyright notice and this permission notice appear in supporting |
14 | | * documentation, and that the name of Keith Packard not be used in |
15 | | * advertising or publicity pertaining to distribution of the software without |
16 | | * specific, written prior permission. Keith Packard makes no |
17 | | * representations about the suitability of this software for any purpose. It |
18 | | * is provided "as is" without express or implied warranty. |
19 | | * |
20 | | * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS |
21 | | * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND |
22 | | * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY |
23 | | * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
24 | | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN |
25 | | * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING |
26 | | * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS |
27 | | * SOFTWARE. |
28 | | */ |
29 | | |
30 | | #ifdef HAVE_CONFIG_H |
31 | | #include <pixman-config.h> |
32 | | #endif |
33 | | #include <stdlib.h> |
34 | | #include <math.h> |
35 | | #include "pixman-private.h" |
36 | | |
37 | | static inline pixman_fixed_32_32_t |
38 | | dot (pixman_fixed_48_16_t x1, |
39 | | pixman_fixed_48_16_t y1, |
40 | | pixman_fixed_48_16_t z1, |
41 | | pixman_fixed_48_16_t x2, |
42 | | pixman_fixed_48_16_t y2, |
43 | | pixman_fixed_48_16_t z2) |
44 | 0 | { |
45 | | /* |
46 | | * Exact computation, assuming that the input values can |
47 | | * be represented as pixman_fixed_16_16_t |
48 | | */ |
49 | 0 | return x1 * x2 + y1 * y2 + z1 * z2; |
50 | 0 | } |
51 | | |
52 | | static inline double |
53 | | fdot (double x1, |
54 | | double y1, |
55 | | double z1, |
56 | | double x2, |
57 | | double y2, |
58 | | double z2) |
59 | 0 | { |
60 | | /* |
61 | | * Error can be unbound in some special cases. |
62 | | * Using clever dot product algorithms (for example compensated |
63 | | * dot product) would improve this but make the code much less |
64 | | * obvious |
65 | | */ |
66 | 0 | return x1 * x2 + y1 * y2 + z1 * z2; |
67 | 0 | } |
68 | | |
69 | | static void |
70 | | radial_write_color (double a, |
71 | | double b, |
72 | | double c, |
73 | | double inva, |
74 | | double dr, |
75 | | double mindr, |
76 | | pixman_gradient_walker_t *walker, |
77 | | pixman_repeat_t repeat, |
78 | | int Bpp, |
79 | | pixman_gradient_walker_write_t write_pixel, |
80 | | uint32_t *buffer) |
81 | 0 | { |
82 | | /* |
83 | | * In this function error propagation can lead to bad results: |
84 | | * - discr can have an unbound error (if b*b-a*c is very small), |
85 | | * potentially making it the opposite sign of what it should have been |
86 | | * (thus clearing a pixel that would have been colored or vice-versa) |
87 | | * or propagating the error to sqrtdiscr; |
88 | | * if discr has the wrong sign or b is very small, this can lead to bad |
89 | | * results |
90 | | * |
91 | | * - the algorithm used to compute the solutions of the quadratic |
92 | | * equation is not numerically stable (but saves one division compared |
93 | | * to the numerically stable one); |
94 | | * this can be a problem if a*c is much smaller than b*b |
95 | | * |
96 | | * - the above problems are worse if a is small (as inva becomes bigger) |
97 | | */ |
98 | 0 | double discr; |
99 | |
|
100 | 0 | if (a == 0) |
101 | 0 | { |
102 | 0 | double t; |
103 | |
|
104 | 0 | if (b == 0) |
105 | 0 | { |
106 | 0 | memset (buffer, 0, Bpp); |
107 | 0 | return; |
108 | 0 | } |
109 | | |
110 | 0 | t = pixman_fixed_1 / 2 * c / b; |
111 | 0 | if (repeat == PIXMAN_REPEAT_NONE) |
112 | 0 | { |
113 | 0 | if (0 <= t && t <= pixman_fixed_1) |
114 | 0 | { |
115 | 0 | write_pixel (walker, t, buffer); |
116 | 0 | return; |
117 | 0 | } |
118 | 0 | } |
119 | 0 | else |
120 | 0 | { |
121 | 0 | if (t * dr >= mindr) |
122 | 0 | { |
123 | 0 | write_pixel (walker, t, buffer); |
124 | 0 | return; |
125 | 0 | } |
126 | 0 | } |
127 | | |
128 | 0 | memset (buffer, 0, Bpp); |
129 | 0 | return; |
130 | 0 | } |
131 | | |
132 | 0 | discr = fdot (b, a, 0, b, -c, 0); |
133 | 0 | if (discr >= 0) |
134 | 0 | { |
135 | 0 | double sqrtdiscr, t0, t1; |
136 | |
|
137 | 0 | sqrtdiscr = sqrt (discr); |
138 | 0 | t0 = (b + sqrtdiscr) * inva; |
139 | 0 | t1 = (b - sqrtdiscr) * inva; |
140 | | |
141 | | /* |
142 | | * The root that must be used is the biggest one that belongs |
143 | | * to the valid range ([0,1] for PIXMAN_REPEAT_NONE, any |
144 | | * solution that results in a positive radius otherwise). |
145 | | * |
146 | | * If a > 0, t0 is the biggest solution, so if it is valid, it |
147 | | * is the correct result. |
148 | | * |
149 | | * If a < 0, only one of the solutions can be valid, so the |
150 | | * order in which they are tested is not important. |
151 | | */ |
152 | 0 | if (repeat == PIXMAN_REPEAT_NONE) |
153 | 0 | { |
154 | 0 | if (0 <= t0 && t0 <= pixman_fixed_1) |
155 | 0 | { |
156 | 0 | write_pixel (walker, t0, buffer); |
157 | 0 | return; |
158 | 0 | } |
159 | 0 | else if (0 <= t1 && t1 <= pixman_fixed_1) |
160 | 0 | { |
161 | 0 | write_pixel (walker, t1, buffer); |
162 | 0 | return; |
163 | 0 | } |
164 | 0 | } |
165 | 0 | else |
166 | 0 | { |
167 | 0 | if (t0 * dr >= mindr) |
168 | 0 | { |
169 | 0 | write_pixel (walker, t0, buffer); |
170 | 0 | return; |
171 | 0 | } |
172 | 0 | else if (t1 * dr >= mindr) |
173 | 0 | { |
174 | 0 | write_pixel (walker, t1, buffer); |
175 | 0 | return; |
176 | 0 | } |
177 | 0 | } |
178 | 0 | } |
179 | | |
180 | 0 | memset (buffer, 0, Bpp); |
181 | 0 | return; |
182 | 0 | } |
183 | | |
184 | | static uint32_t * |
185 | | radial_get_scanline (pixman_iter_t *iter, |
186 | | const uint32_t *mask, |
187 | | int Bpp, |
188 | | pixman_gradient_walker_write_t write_pixel) |
189 | 0 | { |
190 | | /* |
191 | | * Implementation of radial gradients following the PDF specification. |
192 | | * See section 8.7.4.5.4 Type 3 (Radial) Shadings of the PDF Reference |
193 | | * Manual (PDF 32000-1:2008 at the time of this writing). |
194 | | * |
195 | | * In the radial gradient problem we are given two circles (c₁,r₁) and |
196 | | * (c₂,r₂) that define the gradient itself. |
197 | | * |
198 | | * Mathematically the gradient can be defined as the family of circles |
199 | | * |
200 | | * ((1-t)·c₁ + t·(c₂), (1-t)·r₁ + t·r₂) |
201 | | * |
202 | | * excluding those circles whose radius would be < 0. When a point |
203 | | * belongs to more than one circle, the one with a bigger t is the only |
204 | | * one that contributes to its color. When a point does not belong |
205 | | * to any of the circles, it is transparent black, i.e. RGBA (0, 0, 0, 0). |
206 | | * Further limitations on the range of values for t are imposed when |
207 | | * the gradient is not repeated, namely t must belong to [0,1]. |
208 | | * |
209 | | * The graphical result is the same as drawing the valid (radius > 0) |
210 | | * circles with increasing t in [-inf, +inf] (or in [0,1] if the gradient |
211 | | * is not repeated) using SOURCE operator composition. |
212 | | * |
213 | | * It looks like a cone pointing towards the viewer if the ending circle |
214 | | * is smaller than the starting one, a cone pointing inside the page if |
215 | | * the starting circle is the smaller one and like a cylinder if they |
216 | | * have the same radius. |
217 | | * |
218 | | * What we actually do is, given the point whose color we are interested |
219 | | * in, compute the t values for that point, solving for t in: |
220 | | * |
221 | | * length((1-t)·c₁ + t·(c₂) - p) = (1-t)·r₁ + t·r₂ |
222 | | * |
223 | | * Let's rewrite it in a simpler way, by defining some auxiliary |
224 | | * variables: |
225 | | * |
226 | | * cd = c₂ - c₁ |
227 | | * pd = p - c₁ |
228 | | * dr = r₂ - r₁ |
229 | | * length(t·cd - pd) = r₁ + t·dr |
230 | | * |
231 | | * which actually means |
232 | | * |
233 | | * hypot(t·cdx - pdx, t·cdy - pdy) = r₁ + t·dr |
234 | | * |
235 | | * or |
236 | | * |
237 | | * ⎷((t·cdx - pdx)² + (t·cdy - pdy)²) = r₁ + t·dr. |
238 | | * |
239 | | * If we impose (as stated earlier) that r₁ + t·dr >= 0, it becomes: |
240 | | * |
241 | | * (t·cdx - pdx)² + (t·cdy - pdy)² = (r₁ + t·dr)² |
242 | | * |
243 | | * where we can actually expand the squares and solve for t: |
244 | | * |
245 | | * t²cdx² - 2t·cdx·pdx + pdx² + t²cdy² - 2t·cdy·pdy + pdy² = |
246 | | * = r₁² + 2·r₁·t·dr + t²·dr² |
247 | | * |
248 | | * (cdx² + cdy² - dr²)t² - 2(cdx·pdx + cdy·pdy + r₁·dr)t + |
249 | | * (pdx² + pdy² - r₁²) = 0 |
250 | | * |
251 | | * A = cdx² + cdy² - dr² |
252 | | * B = pdx·cdx + pdy·cdy + r₁·dr |
253 | | * C = pdx² + pdy² - r₁² |
254 | | * At² - 2Bt + C = 0 |
255 | | * |
256 | | * The solutions (unless the equation degenerates because of A = 0) are: |
257 | | * |
258 | | * t = (B ± ⎷(B² - A·C)) / A |
259 | | * |
260 | | * The solution we are going to prefer is the bigger one, unless the |
261 | | * radius associated to it is negative (or it falls outside the valid t |
262 | | * range). |
263 | | * |
264 | | * Additional observations (useful for optimizations): |
265 | | * A does not depend on p |
266 | | * |
267 | | * A < 0 <=> one of the two circles completely contains the other one |
268 | | * <=> for every p, the radiuses associated with the two t solutions |
269 | | * have opposite sign |
270 | | */ |
271 | 0 | pixman_image_t *image = iter->image; |
272 | 0 | int x = iter->x; |
273 | 0 | int y = iter->y; |
274 | 0 | int width = iter->width; |
275 | 0 | uint32_t *buffer = iter->buffer; |
276 | |
|
277 | 0 | gradient_t *gradient = (gradient_t *)image; |
278 | 0 | radial_gradient_t *radial = (radial_gradient_t *)image; |
279 | 0 | uint32_t *end = buffer + width * (Bpp / 4); |
280 | 0 | pixman_gradient_walker_t walker; |
281 | 0 | pixman_vector_t v, unit; |
282 | | |
283 | | /* reference point is the center of the pixel */ |
284 | 0 | v.vector[0] = pixman_int_to_fixed (x) + pixman_fixed_1 / 2; |
285 | 0 | v.vector[1] = pixman_int_to_fixed (y) + pixman_fixed_1 / 2; |
286 | 0 | v.vector[2] = pixman_fixed_1; |
287 | |
|
288 | 0 | _pixman_gradient_walker_init (&walker, gradient, image->common.repeat); |
289 | |
|
290 | 0 | if (image->common.transform) |
291 | 0 | { |
292 | 0 | if (!pixman_transform_point_3d (image->common.transform, &v)) |
293 | 0 | return iter->buffer; |
294 | | |
295 | 0 | unit.vector[0] = image->common.transform->matrix[0][0]; |
296 | 0 | unit.vector[1] = image->common.transform->matrix[1][0]; |
297 | 0 | unit.vector[2] = image->common.transform->matrix[2][0]; |
298 | 0 | } |
299 | 0 | else |
300 | 0 | { |
301 | 0 | unit.vector[0] = pixman_fixed_1; |
302 | 0 | unit.vector[1] = 0; |
303 | 0 | unit.vector[2] = 0; |
304 | 0 | } |
305 | | |
306 | 0 | if (unit.vector[2] == 0 && v.vector[2] == pixman_fixed_1) |
307 | 0 | { |
308 | | /* |
309 | | * Given: |
310 | | * |
311 | | * t = (B ± ⎷(B² - A·C)) / A |
312 | | * |
313 | | * where |
314 | | * |
315 | | * A = cdx² + cdy² - dr² |
316 | | * B = pdx·cdx + pdy·cdy + r₁·dr |
317 | | * C = pdx² + pdy² - r₁² |
318 | | * det = B² - A·C |
319 | | * |
320 | | * Since we have an affine transformation, we know that (pdx, pdy) |
321 | | * increase linearly with each pixel, |
322 | | * |
323 | | * pdx = pdx₀ + n·ux, |
324 | | * pdy = pdy₀ + n·uy, |
325 | | * |
326 | | * we can then express B, C and det through multiple differentiation. |
327 | | */ |
328 | 0 | pixman_fixed_32_32_t b, db, c, dc, ddc; |
329 | | |
330 | | /* warning: this computation may overflow */ |
331 | 0 | v.vector[0] -= radial->c1.x; |
332 | 0 | v.vector[1] -= radial->c1.y; |
333 | | |
334 | | /* |
335 | | * B and C are computed and updated exactly. |
336 | | * If fdot was used instead of dot, in the worst case it would |
337 | | * lose 11 bits of precision in each of the multiplication and |
338 | | * summing up would zero out all the bit that were preserved, |
339 | | * thus making the result 0 instead of the correct one. |
340 | | * This would mean a worst case of unbound relative error or |
341 | | * about 2^10 absolute error |
342 | | */ |
343 | 0 | b = dot (v.vector[0], v.vector[1], radial->c1.radius, |
344 | 0 | radial->delta.x, radial->delta.y, radial->delta.radius); |
345 | 0 | db = dot (unit.vector[0], unit.vector[1], 0, |
346 | 0 | radial->delta.x, radial->delta.y, 0); |
347 | |
|
348 | 0 | c = dot (v.vector[0], v.vector[1], |
349 | 0 | -((pixman_fixed_48_16_t) radial->c1.radius), |
350 | 0 | v.vector[0], v.vector[1], radial->c1.radius); |
351 | 0 | dc = dot (2 * (pixman_fixed_48_16_t) v.vector[0] + unit.vector[0], |
352 | 0 | 2 * (pixman_fixed_48_16_t) v.vector[1] + unit.vector[1], |
353 | 0 | 0, |
354 | 0 | unit.vector[0], unit.vector[1], 0); |
355 | 0 | ddc = 2 * dot (unit.vector[0], unit.vector[1], 0, |
356 | 0 | unit.vector[0], unit.vector[1], 0); |
357 | |
|
358 | 0 | while (buffer < end) |
359 | 0 | { |
360 | 0 | if (!mask || *mask++) |
361 | 0 | { |
362 | 0 | radial_write_color (radial->a, b, c, |
363 | 0 | radial->inva, |
364 | 0 | radial->delta.radius, |
365 | 0 | radial->mindr, |
366 | 0 | &walker, |
367 | 0 | image->common.repeat, |
368 | 0 | Bpp, |
369 | 0 | write_pixel, |
370 | 0 | buffer); |
371 | 0 | } |
372 | |
|
373 | 0 | b += db; |
374 | 0 | c += dc; |
375 | 0 | dc += ddc; |
376 | 0 | buffer += (Bpp / 4); |
377 | 0 | } |
378 | 0 | } |
379 | 0 | else |
380 | 0 | { |
381 | | /* projective */ |
382 | | /* Warning: |
383 | | * error propagation guarantees are much looser than in the affine case |
384 | | */ |
385 | 0 | while (buffer < end) |
386 | 0 | { |
387 | 0 | if (!mask || *mask++) |
388 | 0 | { |
389 | 0 | if (v.vector[2] != 0) |
390 | 0 | { |
391 | 0 | double pdx, pdy, invv2, b, c; |
392 | |
|
393 | 0 | invv2 = 1. * pixman_fixed_1 / v.vector[2]; |
394 | |
|
395 | 0 | pdx = v.vector[0] * invv2 - radial->c1.x; |
396 | | /* / pixman_fixed_1 */ |
397 | |
|
398 | 0 | pdy = v.vector[1] * invv2 - radial->c1.y; |
399 | | /* / pixman_fixed_1 */ |
400 | |
|
401 | 0 | b = fdot (pdx, pdy, radial->c1.radius, |
402 | 0 | radial->delta.x, radial->delta.y, |
403 | 0 | radial->delta.radius); |
404 | | /* / pixman_fixed_1 / pixman_fixed_1 */ |
405 | |
|
406 | 0 | c = fdot (pdx, pdy, -radial->c1.radius, |
407 | 0 | pdx, pdy, radial->c1.radius); |
408 | | /* / pixman_fixed_1 / pixman_fixed_1 */ |
409 | |
|
410 | 0 | radial_write_color (radial->a, b, c, |
411 | 0 | radial->inva, |
412 | 0 | radial->delta.radius, |
413 | 0 | radial->mindr, |
414 | 0 | &walker, |
415 | 0 | image->common.repeat, |
416 | 0 | Bpp, |
417 | 0 | write_pixel, |
418 | 0 | buffer); |
419 | 0 | } |
420 | 0 | else |
421 | 0 | { |
422 | 0 | memset (buffer, 0, Bpp); |
423 | 0 | } |
424 | 0 | } |
425 | |
|
426 | 0 | buffer += (Bpp / 4); |
427 | |
|
428 | 0 | v.vector[0] += unit.vector[0]; |
429 | 0 | v.vector[1] += unit.vector[1]; |
430 | 0 | v.vector[2] += unit.vector[2]; |
431 | 0 | } |
432 | 0 | } |
433 | |
|
434 | 0 | iter->y++; |
435 | 0 | return iter->buffer; |
436 | 0 | } |
437 | | |
438 | | static uint32_t * |
439 | | radial_get_scanline_narrow (pixman_iter_t *iter, const uint32_t *mask) |
440 | 0 | { |
441 | 0 | return radial_get_scanline (iter, mask, 4, |
442 | 0 | _pixman_gradient_walker_write_narrow); |
443 | 0 | } |
444 | | |
445 | | static uint32_t * |
446 | | radial_get_scanline_wide (pixman_iter_t *iter, const uint32_t *mask) |
447 | 0 | { |
448 | 0 | return radial_get_scanline (iter, NULL, 16, |
449 | 0 | _pixman_gradient_walker_write_wide); |
450 | 0 | } |
451 | | |
452 | | void |
453 | | _pixman_radial_gradient_iter_init (pixman_image_t *image, pixman_iter_t *iter) |
454 | 0 | { |
455 | 0 | if (iter->iter_flags & ITER_NARROW) |
456 | 0 | iter->get_scanline = radial_get_scanline_narrow; |
457 | 0 | else |
458 | 0 | iter->get_scanline = radial_get_scanline_wide; |
459 | 0 | } |
460 | | |
461 | | PIXMAN_EXPORT pixman_image_t * |
462 | | pixman_image_create_radial_gradient (const pixman_point_fixed_t * inner, |
463 | | const pixman_point_fixed_t * outer, |
464 | | pixman_fixed_t inner_radius, |
465 | | pixman_fixed_t outer_radius, |
466 | | const pixman_gradient_stop_t *stops, |
467 | | int n_stops) |
468 | 0 | { |
469 | 0 | pixman_image_t *image; |
470 | 0 | radial_gradient_t *radial; |
471 | |
|
472 | 0 | image = _pixman_image_allocate (); |
473 | |
|
474 | 0 | if (!image) |
475 | 0 | return NULL; |
476 | | |
477 | 0 | radial = &image->radial; |
478 | |
|
479 | 0 | if (!_pixman_init_gradient (&radial->common, stops, n_stops)) |
480 | 0 | { |
481 | 0 | free (image); |
482 | 0 | return NULL; |
483 | 0 | } |
484 | | |
485 | 0 | image->type = RADIAL; |
486 | |
|
487 | 0 | radial->c1.x = inner->x; |
488 | 0 | radial->c1.y = inner->y; |
489 | 0 | radial->c1.radius = inner_radius; |
490 | 0 | radial->c2.x = outer->x; |
491 | 0 | radial->c2.y = outer->y; |
492 | 0 | radial->c2.radius = outer_radius; |
493 | | |
494 | | /* warning: this computations may overflow */ |
495 | 0 | radial->delta.x = radial->c2.x - radial->c1.x; |
496 | 0 | radial->delta.y = radial->c2.y - radial->c1.y; |
497 | 0 | radial->delta.radius = radial->c2.radius - radial->c1.radius; |
498 | | |
499 | | /* computed exactly, then cast to double -> every bit of the double |
500 | | representation is correct (53 bits) */ |
501 | 0 | radial->a = dot (radial->delta.x, radial->delta.y, -radial->delta.radius, |
502 | 0 | radial->delta.x, radial->delta.y, radial->delta.radius); |
503 | 0 | if (radial->a != 0) |
504 | 0 | radial->inva = 1. * pixman_fixed_1 / radial->a; |
505 | |
|
506 | 0 | radial->mindr = -1. * pixman_fixed_1 * radial->c1.radius; |
507 | |
|
508 | 0 | return image; |
509 | 0 | } |