/* -*- 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 <pixman-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 void
radial_write_color (double a,
double b,
double c,
double inva,
double dr,
double mindr,
pixman_gradient_walker_t *walker,
pixman_repeat_t repeat,
int Bpp,
pixman_gradient_walker_write_t write_pixel,
uint32_t *buffer)
{
/*
* 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 )
{
memset (buffer, 0 , Bpp);
return ;
}
t = pixman_fixed_1 / 2 * c / b;
if (repeat == PIXMAN_REPEAT_NONE)
{
if (0 <= t && t <= pixman_fixed_1)
{
write_pixel (walker, t, buffer);
return ;
}
}
else
{
if (t * dr >= mindr)
{
write_pixel (walker, t, buffer);
return ;
}
}
memset (buffer, 0 , Bpp);
return ;
}
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)
{
write_pixel (walker, t0, buffer);
return ;
}
else if (0 <= t1 && t1 <= pixman_fixed_1)
{
write_pixel (walker, t1, buffer);
return ;
}
}
else
{
if (t0 * dr >= mindr)
{
write_pixel (walker, t0, buffer);
return ;
}
else if (t1 * dr >= mindr)
{
write_pixel (walker, t1, buffer);
return ;
}
}
}
memset (buffer, 0 , Bpp);
return ;
}
static uint32_t *
radial_get_scanline (pixman_iter_t *iter,
const uint32_t *mask,
int Bpp,
pixman_gradient_walker_write_t write_pixel)
{
/*
* 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 ² - 2 t · cdx · pdx + pdx ² + t ² cdy ² - 2 t · 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 ² - 2 Bt + 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 * (Bpp / 4 );
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++)
{
radial_write_color (radial->a, b, c,
radial->inva,
radial->delta.radius,
radial->mindr,
&walker,
image->common.repeat,
Bpp,
write_pixel,
buffer);
}
b += db;
c += dc;
dc += ddc;
buffer += (Bpp / 4 );
}
}
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 */
radial_write_color (radial->a, b, c,
radial->inva,
radial->delta.radius,
radial->mindr,
&walker,
image->common.repeat,
Bpp,
write_pixel,
buffer);
}
else
{
memset (buffer, 0 , Bpp);
}
}
buffer += (Bpp / 4 );
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_narrow (pixman_iter_t *iter, const uint32_t *mask)
{
return radial_get_scanline (iter, mask, 4 ,
_pixman_gradient_walker_write_narrow);
}
static uint32_t *
radial_get_scanline_wide (pixman_iter_t *iter, const uint32_t *mask)
{
return radial_get_scanline (iter, NULL, 16 ,
_pixman_gradient_walker_write_wide);
}
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;
}
Messung V0.5 in Prozent C=97 H=96 G=96
¤ Dauer der Verarbeitung: 0.20 Sekunden
(vorverarbeitet am 2026-08-25)
¤
*© Formatika GbR, Deutschland