tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

pixman-radial-gradient.c (15029B)


      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 {
     45    /*
     46     * Exact computation, assuming that the input values can
     47     * be represented as pixman_fixed_16_16_t
     48     */
     49    return x1 * x2 + y1 * y2 + z1 * z2;
     50 }
     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 {
     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    return x1 * x2 + y1 * y2 + z1 * z2;
     67 }
     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 {
     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    double discr;
     99 
    100    if (a == 0)
    101    {
    102 double t;
    103 
    104 if (b == 0)
    105 {
    106     memset (buffer, 0, Bpp);
    107     return;
    108 }
    109 
    110 t = pixman_fixed_1 / 2 * c / b;
    111 if (repeat == PIXMAN_REPEAT_NONE)
    112 {
    113     if (0 <= t && t <= pixman_fixed_1)
    114     {
    115 	write_pixel (walker, t, buffer);
    116 	return;
    117     }
    118 }
    119 else
    120 {
    121     if (t * dr >= mindr)
    122     {
    123 	write_pixel (walker, t, buffer);
    124 	return;
    125     }
    126 }
    127 
    128 memset (buffer, 0, Bpp);
    129 return;
    130    }
    131 
    132    discr = fdot (b, a, 0, b, -c, 0);
    133    if (discr >= 0)
    134    {
    135 double sqrtdiscr, t0, t1;
    136 
    137 sqrtdiscr = sqrt (discr);
    138 t0 = (b + sqrtdiscr) * inva;
    139 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 if (repeat == PIXMAN_REPEAT_NONE)
    153 {
    154     if (0 <= t0 && t0 <= pixman_fixed_1)
    155     {
    156 	write_pixel (walker, t0, buffer);
    157 	return;
    158     }
    159     else if (0 <= t1 && t1 <= pixman_fixed_1)
    160     {
    161 	write_pixel (walker, t1, buffer);
    162 	return;
    163           }
    164 }
    165 else
    166 {
    167     if (t0 * dr >= mindr)
    168     {
    169 	write_pixel (walker, t0, buffer);
    170 	return;
    171     }
    172     else if (t1 * dr >= mindr)
    173     {
    174 	write_pixel (walker, t1, buffer);
    175 	return;
    176     }
    177 }
    178    }
    179 
    180    memset (buffer, 0, Bpp);
    181    return;
    182 }
    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 {
    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    pixman_image_t *image = iter->image;
    272    int x = iter->x;
    273    int y = iter->y;
    274    int width = iter->width;
    275    uint32_t *buffer = iter->buffer;
    276 
    277    gradient_t *gradient = (gradient_t *)image;
    278    radial_gradient_t *radial = (radial_gradient_t *)image;
    279    uint32_t *end = buffer + width * (Bpp / 4);
    280    pixman_gradient_walker_t walker;
    281    pixman_vector_t v, unit;
    282 
    283    /* reference point is the center of the pixel */
    284    v.vector[0] = pixman_int_to_fixed (x) + pixman_fixed_1 / 2;
    285    v.vector[1] = pixman_int_to_fixed (y) + pixman_fixed_1 / 2;
    286    v.vector[2] = pixman_fixed_1;
    287 
    288    _pixman_gradient_walker_init (&walker, gradient, image->common.repeat);
    289 
    290    if (image->common.transform)
    291    {
    292 if (!pixman_transform_point_3d (image->common.transform, &v))
    293     return iter->buffer;
    294 
    295 unit.vector[0] = image->common.transform->matrix[0][0];
    296 unit.vector[1] = image->common.transform->matrix[1][0];
    297 unit.vector[2] = image->common.transform->matrix[2][0];
    298    }
    299    else
    300    {
    301 unit.vector[0] = pixman_fixed_1;
    302 unit.vector[1] = 0;
    303 unit.vector[2] = 0;
    304    }
    305 
    306    if (unit.vector[2] == 0 && v.vector[2] == pixman_fixed_1)
    307    {
    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 pixman_fixed_32_32_t b, db, c, dc, ddc;
    329 
    330 /* warning: this computation may overflow */
    331 v.vector[0] -= radial->c1.x;
    332 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 b = dot (v.vector[0], v.vector[1], radial->c1.radius,
    344 	 radial->delta.x, radial->delta.y, radial->delta.radius);
    345 db = dot (unit.vector[0], unit.vector[1], 0,
    346 	  radial->delta.x, radial->delta.y, 0);
    347 
    348 c = dot (v.vector[0], v.vector[1],
    349 	 -((pixman_fixed_48_16_t) radial->c1.radius),
    350 	 v.vector[0], v.vector[1], radial->c1.radius);
    351 dc = dot (2 * (pixman_fixed_48_16_t) v.vector[0] + unit.vector[0],
    352 	  2 * (pixman_fixed_48_16_t) v.vector[1] + unit.vector[1],
    353 	  0,
    354 	  unit.vector[0], unit.vector[1], 0);
    355 ddc = 2 * dot (unit.vector[0], unit.vector[1], 0,
    356 	       unit.vector[0], unit.vector[1], 0);
    357 
    358 while (buffer < end)
    359 {
    360     if (!mask || *mask++)
    361     {
    362 	radial_write_color (radial->a, b, c,
    363 			    radial->inva,
    364 			    radial->delta.radius,
    365 			    radial->mindr,
    366 			    &walker,
    367 			    image->common.repeat,
    368 			    Bpp,
    369 			    write_pixel,
    370 			    buffer);
    371     }
    372 
    373     b += db;
    374     c += dc;
    375     dc += ddc;
    376     buffer += (Bpp / 4);
    377 }
    378    }
    379    else
    380    {
    381 /* projective */
    382 /* Warning:
    383  * error propagation guarantees are much looser than in the affine case
    384  */
    385 while (buffer < end)
    386 {
    387     if (!mask || *mask++)
    388     {
    389 	if (v.vector[2] != 0)
    390 	{
    391 	    double pdx, pdy, invv2, b, c;
    392 
    393 	    invv2 = 1. * pixman_fixed_1 / v.vector[2];
    394 
    395 	    pdx = v.vector[0] * invv2 - radial->c1.x;
    396 	    /*    / pixman_fixed_1 */
    397 
    398 	    pdy = v.vector[1] * invv2 - radial->c1.y;
    399 	    /*    / pixman_fixed_1 */
    400 
    401 	    b = fdot (pdx, pdy, radial->c1.radius,
    402 		      radial->delta.x, radial->delta.y,
    403 		      radial->delta.radius);
    404 	    /*  / pixman_fixed_1 / pixman_fixed_1 */
    405 
    406 	    c = fdot (pdx, pdy, -radial->c1.radius,
    407 		      pdx, pdy, radial->c1.radius);
    408 	    /*  / pixman_fixed_1 / pixman_fixed_1 */
    409 
    410 	    radial_write_color (radial->a, b, c,
    411 				radial->inva,
    412 				radial->delta.radius,
    413 				radial->mindr,
    414 				&walker,
    415 				image->common.repeat,
    416 				Bpp,
    417 				write_pixel,
    418 				buffer);
    419 	}
    420 	else
    421 	{
    422 	    memset (buffer, 0, Bpp);
    423 	}
    424     }
    425 
    426     buffer += (Bpp / 4);
    427 
    428     v.vector[0] += unit.vector[0];
    429     v.vector[1] += unit.vector[1];
    430     v.vector[2] += unit.vector[2];
    431 }
    432    }
    433 
    434    iter->y++;
    435    return iter->buffer;
    436 }
    437 
    438 static uint32_t *
    439 radial_get_scanline_narrow (pixman_iter_t *iter, const uint32_t *mask)
    440 {
    441    return radial_get_scanline (iter, mask, 4,
    442 			_pixman_gradient_walker_write_narrow);
    443 }
    444 
    445 static uint32_t *
    446 radial_get_scanline_wide (pixman_iter_t *iter, const uint32_t *mask)
    447 {
    448    return radial_get_scanline (iter, NULL, 16,
    449 			_pixman_gradient_walker_write_wide);
    450 }
    451 
    452 void
    453 _pixman_radial_gradient_iter_init (pixman_image_t *image, pixman_iter_t *iter)
    454 {
    455    if (iter->iter_flags & ITER_NARROW)
    456 iter->get_scanline = radial_get_scanline_narrow;
    457    else
    458 iter->get_scanline = radial_get_scanline_wide;
    459 }
    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 {
    469    pixman_image_t *image;
    470    radial_gradient_t *radial;
    471 
    472    image = _pixman_image_allocate ();
    473 
    474    if (!image)
    475 return NULL;
    476 
    477    radial = &image->radial;
    478 
    479    if (!_pixman_init_gradient (&radial->common, stops, n_stops))
    480    {
    481 free (image);
    482 return NULL;
    483    }
    484 
    485    image->type = RADIAL;
    486 
    487    radial->c1.x = inner->x;
    488    radial->c1.y = inner->y;
    489    radial->c1.radius = inner_radius;
    490    radial->c2.x = outer->x;
    491    radial->c2.y = outer->y;
    492    radial->c2.radius = outer_radius;
    493 
    494    /* warning: this computations may overflow */
    495    radial->delta.x = radial->c2.x - radial->c1.x;
    496    radial->delta.y = radial->c2.y - radial->c1.y;
    497    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    radial->a = dot (radial->delta.x, radial->delta.y, -radial->delta.radius,
    502 	     radial->delta.x, radial->delta.y, radial->delta.radius);
    503    if (radial->a != 0)
    504 radial->inva = 1. * pixman_fixed_1 / radial->a;
    505 
    506    radial->mindr = -1. * pixman_fixed_1 * radial->c1.radius;
    507 
    508    return image;
    509 }