Code

fix compositing for premultiplication and non-alpha cases
[inkscape.git] / src / libnr / nr-gradient.cpp
1 #define __NR_GRADIENT_C__
3 /*
4  * Pixel buffer rendering library
5  *
6  * Authors:
7  *   Lauris Kaplinski <lauris@kaplinski.com>
8  *   MenTaLguY <mental@rydia.net>
9  *
10  * Copyright (C) 2007 MenTaLguY 
11  * Copyright (C) 2001-2002 Lauris Kaplinski
12  * Copyright (C) 2001-2002 Ximian, Inc.
13  *
14  * Released under GNU GPL, read the file 'COPYING' for more information
15  */
17 /*
18  * Derived in part from public domain code by Lauris Kaplinski
19  */
21 #include <cstring>
22 #include <libnr/nr-pixops.h>
23 #include <libnr/nr-pixblock-pixel.h>
24 #include <libnr/nr-blit.h>
25 #include <libnr/nr-gradient.h>
26 #include <glib/gtypes.h>
27 #include <stdio.h>
29 /* Common */
31 #define NRG_MASK (NR_GRADIENT_VECTOR_LENGTH - 1)
32 #define NRG_2MASK ((long long) ((NR_GRADIENT_VECTOR_LENGTH << 1) - 1))
34 namespace {
35 inline unsigned char const *vector_index(int idx,
36                                          unsigned char const *vector)
37 {
38   return vector + 4 * idx;
39 }
41 template <NRGradientSpread spread> struct Spread;
43 template <>
44 struct Spread<NR_GRADIENT_SPREAD_PAD> {
45 static unsigned char const *color_at(NR::Coord r,
46                                      unsigned char const *vector)
47 {
48   return vector_index((int)CLAMP(r, 0, (double)(NR_GRADIENT_VECTOR_LENGTH - 1)), vector);
49 }
50 };
52 template <>
53 struct Spread<NR_GRADIENT_SPREAD_REPEAT> {
54 static unsigned char const *color_at(NR::Coord r,
55                                      unsigned char const *vector)
56 {
57   return vector_index((int)((long long)r & NRG_MASK), vector);
58 }
59 };
61 template <>
62 struct Spread<NR_GRADIENT_SPREAD_REFLECT> {
63 static unsigned char const *color_at(NR::Coord r,
64                                      unsigned char const *vector)
65 {
66   int idx = (int) ((long long)r & NRG_2MASK);
67   if (idx > NRG_MASK) idx = NRG_2MASK - idx;
68   return vector_index(idx, vector);
69 }
70 };
72 template <NR_PIXBLOCK_MODE mode> struct ModeTraits;
74 template <>
75 struct ModeTraits<NR_PIXBLOCK_MODE_R8G8B8A8N> {
76 static const unsigned bpp=4;
77 };
79 template <>
80 struct ModeTraits<NR_PIXBLOCK_MODE_R8G8B8A8P> {
81 static const unsigned bpp=4;
82 };
84 template <>
85 struct ModeTraits<NR_PIXBLOCK_MODE_R8G8B8> {
86 static const unsigned bpp=3;
87 };
89 template <>
90 struct ModeTraits<NR_PIXBLOCK_MODE_A8> {
91 static const unsigned bpp=1;
92 };
94 template <NR_PIXBLOCK_MODE mode, bool empty>
95 struct Compose {
96 static const unsigned bpp=ModeTraits<mode>::bpp;
97 static void compose(NRPixBlock *pb, unsigned char *dest,
98                     NRPixBlock *spb, unsigned char const *src)
99 {
100     nr_compose_pixblock_pixblock_pixel(pb, dest, spb, src);
102 };
104 template <>
105 struct Compose<NR_PIXBLOCK_MODE_R8G8B8A8N, true> {
106 static const unsigned bpp=4;
107 static void compose(NRPixBlock *pb, unsigned char *dest,
108                     NRPixBlock *spb, unsigned char const *src)
110     std::memcpy(dest, src, 4);
112 };
114 template <>
115 struct Compose<NR_PIXBLOCK_MODE_R8G8B8A8P, true> {
116 static const unsigned bpp=4;
117 static void compose(NRPixBlock *pb, unsigned char *dest,
118                     NRPixBlock *spb, unsigned char const *src)
120     dest[0] = NR_PREMUL_111(src[0], src[3]);
121     dest[1] = NR_PREMUL_111(src[1], src[3]);
122     dest[2] = NR_PREMUL_111(src[2], src[3]);
123     dest[3] = src[3];
125 };
127 template <>
128 struct Compose<NR_PIXBLOCK_MODE_R8G8B8, true> {
129 static const unsigned bpp=3;
130 static void compose(NRPixBlock *pb, unsigned char *dest,
131                     NRPixBlock *spb, unsigned char const *src)
133     dest[0] = NR_COMPOSEN11_1111(src[0], src[3], 255);
134     dest[1] = NR_COMPOSEN11_1111(src[1], src[3], 255);
135     dest[2] = NR_COMPOSEN11_1111(src[2], src[3], 255);
137 };
139 template <>
140 struct Compose<NR_PIXBLOCK_MODE_A8, true> {
141 static const unsigned bpp=1;
142 static void compose(NRPixBlock *pb, unsigned char *dest,
143                     NRPixBlock *spb, unsigned char const *src)
145     dest[0] = src[3];
147 };
149 template <>
150 struct Compose<NR_PIXBLOCK_MODE_R8G8B8A8N, false> {
151 static const unsigned bpp=4;
152 static void compose(NRPixBlock *pb, unsigned char *dest,
153                     NRPixBlock *spb, unsigned char const *src)
155     unsigned int ca;
156     ca = NR_COMPOSEA_112(src[3], dest[3]);
157     dest[0] = NR_COMPOSENNN_111121(src[0], src[3], dest[0], dest[3], ca);
158     dest[1] = NR_COMPOSENNN_111121(src[1], src[3], dest[1], dest[3], ca);
159     dest[2] = NR_COMPOSENNN_111121(src[2], src[3], dest[2], dest[3], ca);
160     dest[3] = NR_NORMALIZE_21(ca);
162 };
164 template <>
165 struct Compose<NR_PIXBLOCK_MODE_R8G8B8A8P, false> {
166 static const unsigned bpp=4;
167 static void compose(NRPixBlock *pb, unsigned char *dest,
168                     NRPixBlock *spb, unsigned char const *src)
170     dest[0] = NR_COMPOSENPP_1111(src[0], src[3], dest[0]);
171     dest[1] = NR_COMPOSENPP_1111(src[1], src[3], dest[1]);
172     dest[2] = NR_COMPOSENPP_1111(src[2], src[3], dest[2]);
173     dest[3] = NR_COMPOSEA_111(src[3], dest[3]);
175 };
177 template <>
178 struct Compose<NR_PIXBLOCK_MODE_R8G8B8, false> {
179 static const unsigned bpp=3;
180 static void compose(NRPixBlock *pb, unsigned char *dest,
181                     NRPixBlock *spb, unsigned char const *src)
183     dest[0] = NR_COMPOSEN11_1111(src[0], src[3], dest[0]);
184     dest[1] = NR_COMPOSEN11_1111(src[1], src[3], dest[1]);
185     dest[2] = NR_COMPOSEN11_1111(src[2], src[3], dest[2]);
187 };
189 template <typename Subtype, typename spread>
190 static void
191 render_spread(NRGradientRenderer *gr, NRPixBlock *pb)
193     switch (pb->mode) {
194     case NR_PIXBLOCK_MODE_R8G8B8A8N:
195         if (pb->empty) {
196             typedef Compose<NR_PIXBLOCK_MODE_R8G8B8A8N, true> compose;
197             Subtype::template render<compose, spread>(gr, pb);
198         } else {
199             typedef Compose<NR_PIXBLOCK_MODE_R8G8B8A8N, false> compose;
200             Subtype::template render<compose, spread>(gr, pb);
201         }
202         break;
203     case NR_PIXBLOCK_MODE_R8G8B8A8P:
204         if (pb->empty) {
205             typedef Compose<NR_PIXBLOCK_MODE_R8G8B8A8P, true> compose;
206             Subtype::template render<compose, spread>(gr, pb);
207         } else {
208             typedef Compose<NR_PIXBLOCK_MODE_R8G8B8A8P, false> compose;
209             Subtype::template render<compose, spread>(gr, pb);
210         }
211         break;
212     case NR_PIXBLOCK_MODE_R8G8B8:
213         if (pb->empty) {
214             typedef Compose<NR_PIXBLOCK_MODE_R8G8B8, true> compose;
215             Subtype::template render<compose, spread>(gr, pb);
216         } else {
217             typedef Compose<NR_PIXBLOCK_MODE_R8G8B8, false> compose;
218             Subtype::template render<compose, spread>(gr, pb);
219         }
220         break;
221     case NR_PIXBLOCK_MODE_A8:
222         if (pb->empty) {
223             typedef Compose<NR_PIXBLOCK_MODE_A8, true> compose;
224             Subtype::template render<compose, spread>(gr, pb);
225         } else {
226             typedef Compose<NR_PIXBLOCK_MODE_A8, false> compose;
227             Subtype::template render<compose, spread>(gr, pb);
228         }
229         break;
230     }
233 template <typename Subtype>
234 static void
235 render(NRRenderer *r, NRPixBlock *pb, NRPixBlock *m)
237     NRGradientRenderer *gr;
239     gr = static_cast<NRGradientRenderer *>(r);
241     switch (gr->spread) {
242     case NR_GRADIENT_SPREAD_REPEAT:
243         render_spread<Subtype, Spread<NR_GRADIENT_SPREAD_REPEAT> >(gr, pb);
244         break;
245     case NR_GRADIENT_SPREAD_REFLECT:
246         render_spread<Subtype, Spread<NR_GRADIENT_SPREAD_REFLECT> >(gr, pb);
247         break;
248     case NR_GRADIENT_SPREAD_PAD:
249     default:
250         render_spread<Subtype, Spread<NR_GRADIENT_SPREAD_PAD> >(gr, pb);
251     }
255 /* Linear */
257 namespace {
259 struct Linear {
260 template <typename compose, typename spread>
261 static void render(NRGradientRenderer *gr, NRPixBlock *pb) {
262     NRLGradientRenderer *lgr = static_cast<NRLGradientRenderer *>(gr);
264     int x, y;
265     unsigned char *d;
266     double pos;
267     NRPixBlock spb;
268     int x0, y0, width, height, rs;
270     x0 = pb->area.x0;
271     y0 = pb->area.y0;
272     width = pb->area.x1 - pb->area.x0;
273     height = pb->area.y1 - pb->area.y0;
274     rs = pb->rs;
276     nr_pixblock_setup_extern(&spb, NR_PIXBLOCK_MODE_R8G8B8A8N,
277                              0, 0, NR_GRADIENT_VECTOR_LENGTH, 1,
278                              (unsigned char *) lgr->vector,
279                              4 * NR_GRADIENT_VECTOR_LENGTH, 0, 0);
281     for (y = 0; y < height; y++) {
282         d = NR_PIXBLOCK_PX(pb) + y * rs;
283         pos = (y + y0 - lgr->y0) * lgr->dy + (0 + x0 - lgr->x0) * lgr->dx;
284         for (x = 0; x < width; x++) {
285             unsigned char const *s=spread::color_at(pos, lgr->vector);
286             compose::compose(pb, d, &spb, s);
287             d += compose::bpp;
288             pos += lgr->dx;
289         }
290     }
292     nr_pixblock_release(&spb);
294 };
298 NRRenderer *
299 nr_lgradient_renderer_setup (NRLGradientRenderer *lgr,
300                              const unsigned char *cv, 
301                              unsigned int spread, 
302                              const NRMatrix *gs2px,
303                              float x0, float y0,
304                              float x1, float y1)
306         NRMatrix n2gs, n2px, px2n;
308         lgr->render = &render<Linear>;
310         lgr->vector = cv;
311         lgr->spread = spread;
313         n2gs.c[0] = x1 - x0;
314         n2gs.c[1] = y1 - y0;
315         n2gs.c[2] = y1 - y0;
316         n2gs.c[3] = x0 - x1;
317         n2gs.c[4] = x0;
318         n2gs.c[5] = y0;
320         nr_matrix_multiply (&n2px, &n2gs, gs2px);
321         nr_matrix_invert (&px2n, &n2px);
323         lgr->x0 = n2px.c[4] - 0.5;
324         lgr->y0 = n2px.c[5] - 0.5;
325         lgr->dx = px2n.c[0] * NR_GRADIENT_VECTOR_LENGTH;
326         lgr->dy = px2n.c[2] * NR_GRADIENT_VECTOR_LENGTH;
328         return (NRRenderer *) lgr;
331 /* Radial */
333 /*
334  * The archetype is following
335  *
336  * gx gy - pixel coordinates
337  * Px Py - coordinates, where Fx Fy - gx gy line intersects with circle
338  *
339  * (1)  (gx - fx) * (Py - fy) = (gy - fy) * (Px - fx)
340  * (2)  (Px - cx) * (Px - cx) + (Py - cy) * (Py - cy) = r * r
341  *
342  * (3)   Py = (Px - fx) * (gy - fy) / (gx - fx) + fy
343  * (4)  (gy - fy) / (gx - fx) = D
344  * (5)   Py = D * Px - D * fx + fy
345  *
346  * (6)   D * fx - fy + cy = N
347  * (7)   Px * Px - 2 * Px * cx + cx * cx + (D * Px) * (D * Px) - 2 * (D * Px) * N + N * N = r * r
348  * (8)  (D * D + 1) * (Px * Px) - 2 * (cx + D * N) * Px + cx * cx + N * N = r * r
349  *
350  * (9)   A = D * D + 1
351  * (10)  B = -2 * (cx + D * N)
352  * (11)  C = cx * cx + N * N - r * r
353  *
354  * (12)  Px = (-B +- SQRT(B * B - 4 * A * C)) / 2 * A
355  */
357 namespace {
359 struct SymmetricRadial {
360 template <typename compose, typename spread>
361 static void render(NRGradientRenderer *gr, NRPixBlock *pb)
363     NRRGradientRenderer *rgr = static_cast<NRRGradientRenderer *>(gr);
365     NR::Coord const dx = rgr->px2gs.c[0];
366     NR::Coord const dy = rgr->px2gs.c[1];
368     NRPixBlock spb;
369     nr_pixblock_setup_extern(&spb, NR_PIXBLOCK_MODE_R8G8B8A8N,
370                              0, 0, NR_GRADIENT_VECTOR_LENGTH, 1,
371                              (unsigned char *) rgr->vector,
372                              4 * NR_GRADIENT_VECTOR_LENGTH,
373                              0, 0);
375     for (int y = pb->area.y0; y < pb->area.y1; y++) {
376         unsigned char *d = NR_PIXBLOCK_PX(pb) + (y - pb->area.y0) * pb->rs;
377         NR::Coord gx = rgr->px2gs.c[0] * pb->area.x0 + rgr->px2gs.c[2] * y + rgr->px2gs.c[4];
378         NR::Coord gy = rgr->px2gs.c[1] * pb->area.x0 + rgr->px2gs.c[3] * y + rgr->px2gs.c[5];
379         for (int x = pb->area.x0; x < pb->area.x1; x++) {
380             NR::Coord const pos = sqrt(((gx*gx) + (gy*gy)));
381             unsigned char const *s=spread::color_at(pos, rgr->vector);
382             compose::compose(pb, d, &spb, s);
383             d += compose::bpp;
384             gx += dx;
385             gy += dy;
386         }
387     }
389     nr_pixblock_release(&spb);
391 };
393 struct Radial {
394 template <typename compose, typename spread>
395 static void render(NRGradientRenderer *gr, NRPixBlock *pb)
397     NRRGradientRenderer *rgr = static_cast<NRRGradientRenderer *>(gr);
398     int const x0 = pb->area.x0;
399     int const y0 = pb->area.y0;
400     int const x1 = pb->area.x1;
401     int const y1 = pb->area.y1;
402     int const rs = pb->rs;
404     NRPixBlock spb;
405     nr_pixblock_setup_extern(&spb, NR_PIXBLOCK_MODE_R8G8B8A8N,
406                              0, 0, NR_GRADIENT_VECTOR_LENGTH, 1,
407                              (unsigned char *) rgr->vector,
408                              4 * NR_GRADIENT_VECTOR_LENGTH,
409                              0, 0);
411     for (int y = y0; y < y1; y++) {
412         unsigned char *d = NR_PIXBLOCK_PX(pb) + (y - y0) * rs;
413         NR::Coord gx = rgr->px2gs.c[0] * x0 + rgr->px2gs.c[2] * y + rgr->px2gs.c[4];
414         NR::Coord gy = rgr->px2gs.c[1] * x0 + rgr->px2gs.c[3] * y + rgr->px2gs.c[5];
415         NR::Coord const dx = rgr->px2gs.c[0];
416         NR::Coord const dy = rgr->px2gs.c[1];
417         for (int x = x0; x < x1; x++) {
418             NR::Coord const gx2 = gx * gx;
419             NR::Coord const gxy2 = gx2 + gy * gy;
420             NR::Coord const qgx2_4 = gx2 - rgr->C * gxy2;
421             /* INVARIANT: qgx2_4 >= 0.0 */
422             /* qgx2_4 = MAX(qgx2_4, 0.0); */
423             NR::Coord const pxgx = gx + sqrt(qgx2_4);
424             /* We can safely divide by 0 here */
425             /* If we are sure pxgx cannot be -0 */
426             NR::Coord const pos = gxy2 / pxgx * NR_GRADIENT_VECTOR_LENGTH;
428             unsigned char const *s;
429             if (pos < (1U << 31)) {
430                 s = spread::color_at(pos, rgr->vector);
431             } else {
432                 s = vector_index(NR_GRADIENT_VECTOR_LENGTH - 1, rgr->vector);
433             }
434             
435             compose::compose(pb, d, &spb, s);
436             d += compose::bpp;
438             gx += dx;
439             gy += dy;
440         }
441     }
443     nr_pixblock_release(&spb);
445 };
449 static void nr_rgradient_render_block_end(NRRenderer *r, NRPixBlock *pb, NRPixBlock *m);
451 NRRenderer *
452 nr_rgradient_renderer_setup(NRRGradientRenderer *rgr,
453                             unsigned char const *cv,
454                             unsigned spread,
455                             NRMatrix const *gs2px,
456                             float cx, float cy,
457                             float fx, float fy,
458                             float r)
460     rgr->vector = cv;
461     rgr->spread = spread;
463     if (r < NR_EPSILON) {
464         rgr->render = nr_rgradient_render_block_end;
465     } else if (NR_DF_TEST_CLOSE(cx, fx, NR_EPSILON) &&
466                NR_DF_TEST_CLOSE(cy, fy, NR_EPSILON)) {
467         rgr->render = render<SymmetricRadial>;
469         nr_matrix_invert(&rgr->px2gs, gs2px);
470         rgr->px2gs.c[0] *= (NR_GRADIENT_VECTOR_LENGTH / r);
471         rgr->px2gs.c[1] *= (NR_GRADIENT_VECTOR_LENGTH / r);
472         rgr->px2gs.c[2] *= (NR_GRADIENT_VECTOR_LENGTH / r);
473         rgr->px2gs.c[3] *= (NR_GRADIENT_VECTOR_LENGTH / r);
474         rgr->px2gs.c[4] -= cx;
475         rgr->px2gs.c[5] -= cy;
476         rgr->px2gs.c[4] *= (NR_GRADIENT_VECTOR_LENGTH / r);
477         rgr->px2gs.c[5] *= (NR_GRADIENT_VECTOR_LENGTH / r);
479         rgr->cx = 0.0;
480         rgr->cy = 0.0;
481         rgr->fx = rgr->cx;
482         rgr->fy = rgr->cy;
483         rgr->r = 1.0;
484     } else {
485         rgr->render = render<Radial>;
487         NR::Coord const df = hypot(fx - cx, fy - cy);
488         if (df >= r) {
489             fx = cx + (fx - cx ) * r / (float) df;
490             fy = cy + (fy - cy ) * r / (float) df;
491         }
493         NRMatrix n2gs;
494         n2gs.c[0] = cx - fx;
495         n2gs.c[1] = cy - fy;
496         n2gs.c[2] = cy - fy;
497         n2gs.c[3] = fx - cx;
498         n2gs.c[4] = fx;
499         n2gs.c[5] = fy;
501         NRMatrix n2px;
502         nr_matrix_multiply(&n2px, &n2gs, gs2px);
503         nr_matrix_invert(&rgr->px2gs, &n2px);
505         rgr->cx = 1.0;
506         rgr->cy = 0.0;
507         rgr->fx = 0.0;
508         rgr->fy = 0.0;
509         rgr->r = r / (float) hypot(fx - cx, fy - cy);
510         rgr->C = 1.0F - rgr->r * rgr->r;
511         /* INVARIANT: C < 0 */
512         rgr->C = MIN(rgr->C, -NR_EPSILON);
513     }
515     return (NRRenderer *) rgr;
518 static void
519 nr_rgradient_render_block_end(NRRenderer *r, NRPixBlock *pb, NRPixBlock *m)
521     unsigned char const *c = ((NRRGradientRenderer *) r)->vector + 4 * (NR_GRADIENT_VECTOR_LENGTH - 1);
523     nr_blit_pixblock_mask_rgba32(pb, m, (c[0] << 24) | (c[1] << 16) | (c[2] << 8) | c[3]);
526 /*
527   Local Variables:
528   mode:c++
529   c-file-style:"stroustrup"
530   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
531   indent-tabs-mode:nil
532   fill-column:99
533   End:
534 */
535 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :