Fix inversion of radial gradients when r2 > r1
authorChris Wilson <chris@chris-wilson.co.uk>
Thu, 23 Jul 2009 18:08:40 +0000 (19:08 +0100)
committerChris Wilson <chris@chris-wilson.co.uk>
Thu, 23 Jul 2009 18:08:40 +0000 (19:08 +0100)
Fixes: Bug 22908 -- Invalid output of radial gradient
http://bugs.freedesktop.org/show_bug.cgi?id=22908

We also include a modified patch by André Tupinambá <andrelrt@gmail.com>,
to pull constant expressions out of the inner radial gradient walker.

pixman/pixman-radial-gradient.c

index a3b5916..04c170e 100644 (file)
@@ -155,6 +155,9 @@ radial_gradient_get_scanline_32 (pixman_image_t *image,
      * for t:
      *
      * t = (-2·B ± ⎷(B² - 4·A·C)) / 2·A
+     *
+     * When computing t over a scanline, we notice that some expressions are
+     * constant so we can compute them just once.
      */
 
     gradient_t *gradient = (gradient_t *)image;
@@ -198,41 +201,37 @@ radial_gradient_get_scanline_32 (pixman_image_t *image,
 
     if (affine)
     {
+       double r1   = radial->c1.radius / 65536.;
+       double r1sq = r1 * r1;
+       double pdx  = rx - radial->c1.x / 65536.;
+       double pdy  = ry - radial->c1.y / 65536.;
+       double A = radial->A;
+       double invA = -65536. / (2. * A);
+       double A4 = -4. * A;
+       double B  = -2. * (pdx*radial->cdx + pdy*radial->cdy + r1*radial->dr);
+       double cB = -2. *  (cx*radial->cdx +  cy*radial->cdy);
+       pixman_bool_t invert = A * radial->dr < 0;
+
        while (buffer < end)
        {
            if (!mask || *mask++ & mask_bits)
            {
-               double pdx, pdy;
-               double B, C;
-               double det;
-               double c1x = radial->c1.x / 65536.0;
-               double c1y = radial->c1.y / 65536.0;
-               double r1  = radial->c1.radius / 65536.0;
                pixman_fixed_48_16_t t;
-
-               pdx = rx - c1x;
-               pdy = ry - c1y;
-
-               B = -2 * (pdx * radial->cdx +
-                         pdy * radial->cdy +
-                         r1 * radial->dr);
-               C = pdx * pdx + pdy * pdy - r1 * r1;
-
-               det = (B * B) - (4 * radial->A * C);
-               if (det < 0.0)
-                   det = 0.0;
-
-               if (radial->A < 0)
-                   t = (pixman_fixed_48_16_t) ((-B - sqrt (det)) / (2.0 * radial->A) * 65536);
+               double det = B * B + A4 * (pdx * pdx + pdy * pdy - r1sq);
+               if (det <= 0.)
+                   t = (pixman_fixed_48_16_t) (B * invA);
+               else if (invert)
+                   t = (pixman_fixed_48_16_t) ((B + sqrt (det)) * invA);
                else
-                   t = (pixman_fixed_48_16_t) ((-B + sqrt (det)) / (2.0 * radial->A) * 65536);
+                   t = (pixman_fixed_48_16_t) ((B - sqrt (det)) * invA);
 
                *buffer = _pixman_gradient_walker_pixel (&walker, t);
            }
            ++buffer;
 
-           rx += cx;
-           ry += cy;
+           pdx += cx;
+           pdy += cy;
+           B += cB;
        }
     }
     else
@@ -273,7 +272,7 @@ radial_gradient_get_scanline_32 (pixman_image_t *image,
                if (det < 0.0)
                    det = 0.0;
 
-               if (radial->A < 0)
+               if (radial->A * radial->dr < 0)
                    t = (pixman_fixed_48_16_t) ((-B - sqrt (det)) / (2.0 * radial->A) * 65536);
                else
                    t = (pixman_fixed_48_16_t) ((-B + sqrt (det)) / (2.0 * radial->A) * 65536);