kolibrios/contrib/sdk/sources/pixman/pixman-radial-gradient.c

472 lines
14 KiB
C
Raw Permalink Normal View History

/* -*- 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 <config.h>
#endif
#include <stdlib.h>
#include <math.h>
#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;
}