/* -*- Mode: c; c-basic-offset: 4; tab-width: 8; indent-tabs-mode: t; -*- */ /* * * Copyright © 2000 Keith Packard, member of The XFree86 Project, Inc. * Copyright © 2000 SuSE, Inc. * 2005 Lars Knoll & Zack Rusin, Trolltech * Copyright © 2007 Red Hat, Inc. * * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that * the above copyright notice appear in all copies and that both that * copyright notice and this permission notice appear in supporting * documentation, and that the name of Keith Packard not be used in * advertising or publicity pertaining to distribution of the software without * specific, written prior permission. Keith Packard makes no * representations about the suitability of this software for any purpose. It * is provided "as is" without express or implied warranty. * * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS * SOFTWARE. */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include "pixman-private.h" static inline pixman_fixed_32_32_t dot (pixman_fixed_48_16_t x1, pixman_fixed_48_16_t y1, pixman_fixed_48_16_t z1, pixman_fixed_48_16_t x2, pixman_fixed_48_16_t y2, pixman_fixed_48_16_t z2) { /* * Exact computation, assuming that the input values can * be represented as pixman_fixed_16_16_t */ return x1 * x2 + y1 * y2 + z1 * z2; } static inline double fdot (double x1, double y1, double z1, double x2, double y2, double z2) { /* * Error can be unbound in some special cases. * Using clever dot product algorithms (for example compensated * dot product) would improve this but make the code much less * obvious */ return x1 * x2 + y1 * y2 + z1 * z2; } static uint32_t radial_compute_color (double a, double b, double c, double inva, double dr, double mindr, pixman_gradient_walker_t *walker, pixman_repeat_t repeat) { /* * In this function error propagation can lead to bad results: * - discr can have an unbound error (if b*b-a*c is very small), * potentially making it the opposite sign of what it should have been * (thus clearing a pixel that would have been colored or vice-versa) * or propagating the error to sqrtdiscr; * if discr has the wrong sign or b is very small, this can lead to bad * results * * - the algorithm used to compute the solutions of the quadratic * equation is not numerically stable (but saves one division compared * to the numerically stable one); * this can be a problem if a*c is much smaller than b*b * * - the above problems are worse if a is small (as inva becomes bigger) */ double discr; if (a == 0) { double t; if (b == 0) return 0; t = pixman_fixed_1 / 2 * c / b; if (repeat == PIXMAN_REPEAT_NONE) { if (0 <= t && t <= pixman_fixed_1) return _pixman_gradient_walker_pixel (walker, t); } else { if (t * dr >= mindr) return _pixman_gradient_walker_pixel (walker, t); } return 0; } discr = fdot (b, a, 0, b, -c, 0); if (discr >= 0) { double sqrtdiscr, t0, t1; sqrtdiscr = sqrt (discr); t0 = (b + sqrtdiscr) * inva; t1 = (b - sqrtdiscr) * inva; /* * The root that must be used is the biggest one that belongs * to the valid range ([0,1] for PIXMAN_REPEAT_NONE, any * solution that results in a positive radius otherwise). * * If a > 0, t0 is the biggest solution, so if it is valid, it * is the correct result. * * If a < 0, only one of the solutions can be valid, so the * order in which they are tested is not important. */ if (repeat == PIXMAN_REPEAT_NONE) { if (0 <= t0 && t0 <= pixman_fixed_1) return _pixman_gradient_walker_pixel (walker, t0); else if (0 <= t1 && t1 <= pixman_fixed_1) return _pixman_gradient_walker_pixel (walker, t1); } else { if (t0 * dr >= mindr) return _pixman_gradient_walker_pixel (walker, t0); else if (t1 * dr >= mindr) return _pixman_gradient_walker_pixel (walker, t1); } } return 0; } static uint32_t * radial_get_scanline_narrow (pixman_iter_t *iter, const uint32_t *mask) { /* * Implementation of radial gradients following the PDF specification. * See section 8.7.4.5.4 Type 3 (Radial) Shadings of the PDF Reference * Manual (PDF 32000-1:2008 at the time of this writing). * * In the radial gradient problem we are given two circles (c₁,r₁) and * (c₂,r₂) that define the gradient itself. * * Mathematically the gradient can be defined as the family of circles * * ((1-t)·c₁ + t·(c₂), (1-t)·r₁ + t·r₂) * * excluding those circles whose radius would be < 0. When a point * belongs to more than one circle, the one with a bigger t is the only * one that contributes to its color. When a point does not belong * to any of the circles, it is transparent black, i.e. RGBA (0, 0, 0, 0). * Further limitations on the range of values for t are imposed when * the gradient is not repeated, namely t must belong to [0,1]. * * The graphical result is the same as drawing the valid (radius > 0) * circles with increasing t in [-inf, +inf] (or in [0,1] if the gradient * is not repeated) using SOURCE operator composition. * * It looks like a cone pointing towards the viewer if the ending circle * is smaller than the starting one, a cone pointing inside the page if * the starting circle is the smaller one and like a cylinder if they * have the same radius. * * What we actually do is, given the point whose color we are interested * in, compute the t values for that point, solving for t in: * * length((1-t)·c₁ + t·(c₂) - p) = (1-t)·r₁ + t·r₂ * * Let's rewrite it in a simpler way, by defining some auxiliary * variables: * * cd = c₂ - c₁ * pd = p - c₁ * dr = r₂ - r₁ * length(t·cd - pd) = r₁ + t·dr * * which actually means * * hypot(t·cdx - pdx, t·cdy - pdy) = r₁ + t·dr * * or * * ⎷((t·cdx - pdx)² + (t·cdy - pdy)²) = r₁ + t·dr. * * If we impose (as stated earlier) that r₁ + t·dr >= 0, it becomes: * * (t·cdx - pdx)² + (t·cdy - pdy)² = (r₁ + t·dr)² * * where we can actually expand the squares and solve for t: * * t²cdx² - 2t·cdx·pdx + pdx² + t²cdy² - 2t·cdy·pdy + pdy² = * = r₁² + 2·r₁·t·dr + t²·dr² * * (cdx² + cdy² - dr²)t² - 2(cdx·pdx + cdy·pdy + r₁·dr)t + * (pdx² + pdy² - r₁²) = 0 * * A = cdx² + cdy² - dr² * B = pdx·cdx + pdy·cdy + r₁·dr * C = pdx² + pdy² - r₁² * At² - 2Bt + C = 0 * * The solutions (unless the equation degenerates because of A = 0) are: * * t = (B ± ⎷(B² - A·C)) / A * * The solution we are going to prefer is the bigger one, unless the * radius associated to it is negative (or it falls outside the valid t * range). * * Additional observations (useful for optimizations): * A does not depend on p * * A < 0 <=> one of the two circles completely contains the other one * <=> for every p, the radiuses associated with the two t solutions * have opposite sign */ pixman_image_t *image = iter->image; int x = iter->x; int y = iter->y; int width = iter->width; uint32_t *buffer = iter->buffer; gradient_t *gradient = (gradient_t *)image; radial_gradient_t *radial = (radial_gradient_t *)image; uint32_t *end = buffer + width; pixman_gradient_walker_t walker; pixman_vector_t v, unit; /* reference point is the center of the pixel */ v.vector[0] = pixman_int_to_fixed (x) + pixman_fixed_1 / 2; v.vector[1] = pixman_int_to_fixed (y) + pixman_fixed_1 / 2; v.vector[2] = pixman_fixed_1; _pixman_gradient_walker_init (&walker, gradient, image->common.repeat); if (image->common.transform) { if (!pixman_transform_point_3d (image->common.transform, &v)) return iter->buffer; unit.vector[0] = image->common.transform->matrix[0][0]; unit.vector[1] = image->common.transform->matrix[1][0]; unit.vector[2] = image->common.transform->matrix[2][0]; } else { unit.vector[0] = pixman_fixed_1; unit.vector[1] = 0; unit.vector[2] = 0; } if (unit.vector[2] == 0 && v.vector[2] == pixman_fixed_1) { /* * Given: * * t = (B ± ⎷(B² - A·C)) / A * * where * * A = cdx² + cdy² - dr² * B = pdx·cdx + pdy·cdy + r₁·dr * C = pdx² + pdy² - r₁² * det = B² - A·C * * Since we have an affine transformation, we know that (pdx, pdy) * increase linearly with each pixel, * * pdx = pdx₀ + n·ux, * pdy = pdy₀ + n·uy, * * we can then express B, C and det through multiple differentiation. */ pixman_fixed_32_32_t b, db, c, dc, ddc; /* warning: this computation may overflow */ v.vector[0] -= radial->c1.x; v.vector[1] -= radial->c1.y; /* * B and C are computed and updated exactly. * If fdot was used instead of dot, in the worst case it would * lose 11 bits of precision in each of the multiplication and * summing up would zero out all the bit that were preserved, * thus making the result 0 instead of the correct one. * This would mean a worst case of unbound relative error or * about 2^10 absolute error */ b = dot (v.vector[0], v.vector[1], radial->c1.radius, radial->delta.x, radial->delta.y, radial->delta.radius); db = dot (unit.vector[0], unit.vector[1], 0, radial->delta.x, radial->delta.y, 0); c = dot (v.vector[0], v.vector[1], -((pixman_fixed_48_16_t) radial->c1.radius), v.vector[0], v.vector[1], radial->c1.radius); dc = dot (2 * (pixman_fixed_48_16_t) v.vector[0] + unit.vector[0], 2 * (pixman_fixed_48_16_t) v.vector[1] + unit.vector[1], 0, unit.vector[0], unit.vector[1], 0); ddc = 2 * dot (unit.vector[0], unit.vector[1], 0, unit.vector[0], unit.vector[1], 0); while (buffer < end) { if (!mask || *mask++) { *buffer = radial_compute_color (radial->a, b, c, radial->inva, radial->delta.radius, radial->mindr, &walker, image->common.repeat); } b += db; c += dc; dc += ddc; ++buffer; } } else { /* projective */ /* Warning: * error propagation guarantees are much looser than in the affine case */ while (buffer < end) { if (!mask || *mask++) { if (v.vector[2] != 0) { double pdx, pdy, invv2, b, c; invv2 = 1. * pixman_fixed_1 / v.vector[2]; pdx = v.vector[0] * invv2 - radial->c1.x; /* / pixman_fixed_1 */ pdy = v.vector[1] * invv2 - radial->c1.y; /* / pixman_fixed_1 */ b = fdot (pdx, pdy, radial->c1.radius, radial->delta.x, radial->delta.y, radial->delta.radius); /* / pixman_fixed_1 / pixman_fixed_1 */ c = fdot (pdx, pdy, -radial->c1.radius, pdx, pdy, radial->c1.radius); /* / pixman_fixed_1 / pixman_fixed_1 */ *buffer = radial_compute_color (radial->a, b, c, radial->inva, radial->delta.radius, radial->mindr, &walker, image->common.repeat); } else { *buffer = 0; } } ++buffer; v.vector[0] += unit.vector[0]; v.vector[1] += unit.vector[1]; v.vector[2] += unit.vector[2]; } } iter->y++; return iter->buffer; } static uint32_t * radial_get_scanline_wide (pixman_iter_t *iter, const uint32_t *mask) { uint32_t *buffer = radial_get_scanline_narrow (iter, NULL); pixman_expand_to_float ( (argb_t *)buffer, buffer, PIXMAN_a8r8g8b8, iter->width); return buffer; } void _pixman_radial_gradient_iter_init (pixman_image_t *image, pixman_iter_t *iter) { if (iter->iter_flags & ITER_NARROW) iter->get_scanline = radial_get_scanline_narrow; else iter->get_scanline = radial_get_scanline_wide; } PIXMAN_EXPORT pixman_image_t * pixman_image_create_radial_gradient (const pixman_point_fixed_t * inner, const pixman_point_fixed_t * outer, pixman_fixed_t inner_radius, pixman_fixed_t outer_radius, const pixman_gradient_stop_t *stops, int n_stops) { pixman_image_t *image; radial_gradient_t *radial; image = _pixman_image_allocate (); if (!image) return NULL; radial = &image->radial; if (!_pixman_init_gradient (&radial->common, stops, n_stops)) { free (image); return NULL; } image->type = RADIAL; radial->c1.x = inner->x; radial->c1.y = inner->y; radial->c1.radius = inner_radius; radial->c2.x = outer->x; radial->c2.y = outer->y; radial->c2.radius = outer_radius; /* warning: this computations may overflow */ radial->delta.x = radial->c2.x - radial->c1.x; radial->delta.y = radial->c2.y - radial->c1.y; radial->delta.radius = radial->c2.radius - radial->c1.radius; /* computed exactly, then cast to double -> every bit of the double representation is correct (53 bits) */ radial->a = dot (radial->delta.x, radial->delta.y, -radial->delta.radius, radial->delta.x, radial->delta.y, radial->delta.radius); if (radial->a != 0) radial->inva = 1. * pixman_fixed_1 / radial->a; radial->mindr = -1. * pixman_fixed_1 * radial->c1.radius; return image; }