Code

Refactor gradient rendering to facilitate future changes, since the simple
[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 <libnr/nr-pixops.h>
22 #include <libnr/nr-pixblock-pixel.h>
23 #include <libnr/nr-blit.h>
24 #include <libnr/nr-gradient.h>
25 #include <glib/gtypes.h>
26 #include <stdio.h>
28 /* Common */
30 #define NRG_MASK (NR_GRADIENT_VECTOR_LENGTH - 1)
31 #define NRG_2MASK ((long long) ((NR_GRADIENT_VECTOR_LENGTH << 1) - 1))
33 namespace {
34 inline unsigned char const *vector_index(int idx,
35                                          unsigned char const *vector)
36 {
37   return vector + 4 * idx;
38 }
40 template <NRGradientSpread spread> struct Spread;
42 template <>
43 struct Spread<NR_GRADIENT_SPREAD_PAD> {
44 static unsigned char const *color_at(NR::Coord r,
45                                      unsigned char const *vector)
46 {
47   return vector_index((int)CLAMP(r, 0, (double)(NR_GRADIENT_VECTOR_LENGTH - 1)), vector);
48 }
49 };
51 template <>
52 struct Spread<NR_GRADIENT_SPREAD_REPEAT> {
53 static unsigned char const *color_at(NR::Coord r,
54                                      unsigned char const *vector)
55 {
56   return vector_index((int)((long long)r & NRG_MASK), vector);
57 }
58 };
60 template <>
61 struct Spread<NR_GRADIENT_SPREAD_REFLECT> {
62 static unsigned char const *color_at(NR::Coord r,
63                                      unsigned char const *vector)
64 {
65   int idx = (int) ((long long)r & NRG_2MASK);
66   if (idx > NRG_MASK) idx = NRG_2MASK - idx;
67   return vector_index(idx, vector);
68 }
69 };
71 template <NR_PIXBLOCK_MODE mode, bool empty=false>
72 struct Compose {
73 static void compose(NRPixBlock *pb, unsigned char *dest,
74                     NRPixBlock *spb, unsigned char const *src)
75 {
76     nr_compose_pixblock_pixblock_pixel(pb, dest, spb, src);
77 }
78 };
80 template <>
81 struct Compose<NR_PIXBLOCK_MODE_R8G8B8A8N, true> {
82 static void compose(NRPixBlock *pb, unsigned char *dest,
83                     NRPixBlock *spb, unsigned char const *src)
84 {
85     dest[0] = src[0];
86     dest[1] = src[1];
87     dest[2] = src[2];
88     dest[3] = src[3];
89 }
90 };
92 template <>
93 struct Compose<NR_PIXBLOCK_MODE_R8G8B8A8N, false> {
94 static void compose(NRPixBlock *pb, unsigned char *dest,
95                     NRPixBlock *spb, unsigned char const *src)
96 {
97     unsigned int ca;
98     ca = NR_COMPOSEA_112(src[3], dest[3]);
99     dest[0] = NR_COMPOSENNN_111121(src[0], src[3], dest[0], dest[3], ca);
100     dest[1] = NR_COMPOSENNN_111121(src[1], src[3], dest[1], dest[3], ca);
101     dest[2] = NR_COMPOSENNN_111121(src[2], src[3], dest[2], dest[3], ca);
102     dest[3] = NR_NORMALIZE_21(ca);
104 };
106 template <>
107 struct Compose<NR_PIXBLOCK_MODE_R8G8B8A8P, false> {
108 static void compose(NRPixBlock *pb, unsigned char *dest,
109                     NRPixBlock *spb, unsigned char const *src)
111     dest[0] = NR_COMPOSENPP_1111(src[0], src[3], dest[0]);
112     dest[1] = NR_COMPOSENPP_1111(src[1], src[3], dest[1]);
113     dest[2] = NR_COMPOSENPP_1111(src[2], src[3], dest[2]);
114     dest[3] = NR_COMPOSEA_111(src[3], dest[3]);
116 };
118 template <>
119 struct Compose<NR_PIXBLOCK_MODE_R8G8B8, false> {
120 static void compose(NRPixBlock *pb, unsigned char *dest,
121                     NRPixBlock *spb, unsigned char const *src)
123     dest[0] = NR_COMPOSEN11_1111(src[0], src[3], dest[0]);
124     dest[1] = NR_COMPOSEN11_1111(src[1], src[3], dest[1]);
125     dest[2] = NR_COMPOSEN11_1111(src[2], src[3], dest[2]);
127 };
129 template <typename Subtype, typename spread>
130 static void
131 render_spread(NRGradientRenderer *gr, NRPixBlock *pb)
133     switch (pb->mode) {
134     case NR_PIXBLOCK_MODE_R8G8B8A8N:
135         if (pb->empty) {
136             typedef Compose<NR_PIXBLOCK_MODE_R8G8B8A8N, true> compose;
137             Subtype::template render<compose, spread, 4>(gr, pb);
138         } else {
139             typedef Compose<NR_PIXBLOCK_MODE_R8G8B8A8N, false> compose;
140             Subtype::template render<compose, spread, 4>(gr, pb);
141         }
142         break;
143     case NR_PIXBLOCK_MODE_R8G8B8A8P:
144         if (pb->empty) {
145             typedef Compose<NR_PIXBLOCK_MODE_R8G8B8A8P, true> compose;
146             Subtype::template render<compose, spread, 4>(gr, pb);
147         } else {
148             typedef Compose<NR_PIXBLOCK_MODE_R8G8B8A8P, false> compose;
149             Subtype::template render<compose, spread, 4>(gr, pb);
150         }
151         break;
152     case NR_PIXBLOCK_MODE_R8G8B8:
153         if (pb->empty) {
154             typedef Compose<NR_PIXBLOCK_MODE_R8G8B8, true> compose;
155             Subtype::template render<compose, spread, 3>(gr, pb);
156         } else {
157             typedef Compose<NR_PIXBLOCK_MODE_R8G8B8, false> compose;
158             Subtype::template render<compose, spread, 3>(gr, pb);
159         }
160         break;
161     case NR_PIXBLOCK_MODE_A8:
162         if (pb->empty) {
163             typedef Compose<NR_PIXBLOCK_MODE_A8, true> compose;
164             Subtype::template render<compose, spread, 1>(gr, pb);
165         } else {
166             typedef Compose<NR_PIXBLOCK_MODE_A8, false> compose;
167             Subtype::template render<compose, spread, 1>(gr, pb);
168         }
169         break;
170     }
173 template <typename Subtype>
174 static void
175 render(NRRenderer *r, NRPixBlock *pb, NRPixBlock *m)
177     NRGradientRenderer *gr;
179     gr = static_cast<NRGradientRenderer *>(r);
181     switch (gr->spread) {
182     case NR_GRADIENT_SPREAD_REPEAT:
183         render_spread<Subtype, Spread<NR_GRADIENT_SPREAD_REPEAT> >(gr, pb);
184         break;
185     case NR_GRADIENT_SPREAD_REFLECT:
186         render_spread<Subtype, Spread<NR_GRADIENT_SPREAD_REFLECT> >(gr, pb);
187         break;
188     case NR_GRADIENT_SPREAD_PAD:
189     default:
190         render_spread<Subtype, Spread<NR_GRADIENT_SPREAD_PAD> >(gr, pb);
191     }
195 /* Linear */
197 namespace {
199 struct Linear {
200 template <typename compose, typename spread, unsigned bpp>
201 static void render(NRGradientRenderer *gr, NRPixBlock *pb) {
202     NRLGradientRenderer *lgr = static_cast<NRLGradientRenderer *>(gr);
204     int x, y;
205     unsigned char *d;
206     double pos;
207     NRPixBlock spb;
208     int x0, y0, width, height, rs;
210     x0 = pb->area.x0;
211     y0 = pb->area.y0;
212     width = pb->area.x1 - pb->area.x0;
213     height = pb->area.y1 - pb->area.y0;
214     rs = pb->rs;
216     nr_pixblock_setup_extern(&spb, NR_PIXBLOCK_MODE_R8G8B8A8N,
217                              0, 0, NR_GRADIENT_VECTOR_LENGTH, 1,
218                              (unsigned char *) lgr->vector,
219                              4 * NR_GRADIENT_VECTOR_LENGTH, 0, 0);
221     for (y = 0; y < height; y++) {
222         d = NR_PIXBLOCK_PX(pb) + y * rs;
223         pos = (y + y0 - lgr->y0) * lgr->dy + (0 + x0 - lgr->x0) * lgr->dx;
224         for (x = 0; x < width; x++) {
225             unsigned char const *s=spread::color_at(pos, lgr->vector);
226             compose::compose(pb, d, &spb, s);
227             d += bpp;
228             pos += lgr->dx;
229         }
230     }
232     nr_pixblock_release(&spb);
234 };
238 NRRenderer *
239 nr_lgradient_renderer_setup (NRLGradientRenderer *lgr,
240                              const unsigned char *cv, 
241                              unsigned int spread, 
242                              const NRMatrix *gs2px,
243                              float x0, float y0,
244                              float x1, float y1)
246         NRMatrix n2gs, n2px, px2n;
248         lgr->render = &render<Linear>;
250         lgr->vector = cv;
251         lgr->spread = spread;
253         n2gs.c[0] = x1 - x0;
254         n2gs.c[1] = y1 - y0;
255         n2gs.c[2] = y1 - y0;
256         n2gs.c[3] = x0 - x1;
257         n2gs.c[4] = x0;
258         n2gs.c[5] = y0;
260         nr_matrix_multiply (&n2px, &n2gs, gs2px);
261         nr_matrix_invert (&px2n, &n2px);
263         lgr->x0 = n2px.c[4] - 0.5;
264         lgr->y0 = n2px.c[5] - 0.5;
265         lgr->dx = px2n.c[0] * NR_GRADIENT_VECTOR_LENGTH;
266         lgr->dy = px2n.c[2] * NR_GRADIENT_VECTOR_LENGTH;
268         return (NRRenderer *) lgr;
271 /* Radial */
273 /*
274  * The archetype is following
275  *
276  * gx gy - pixel coordinates
277  * Px Py - coordinates, where Fx Fy - gx gy line intersects with circle
278  *
279  * (1)  (gx - fx) * (Py - fy) = (gy - fy) * (Px - fx)
280  * (2)  (Px - cx) * (Px - cx) + (Py - cy) * (Py - cy) = r * r
281  *
282  * (3)   Py = (Px - fx) * (gy - fy) / (gx - fx) + fy
283  * (4)  (gy - fy) / (gx - fx) = D
284  * (5)   Py = D * Px - D * fx + fy
285  *
286  * (6)   D * fx - fy + cy = N
287  * (7)   Px * Px - 2 * Px * cx + cx * cx + (D * Px) * (D * Px) - 2 * (D * Px) * N + N * N = r * r
288  * (8)  (D * D + 1) * (Px * Px) - 2 * (cx + D * N) * Px + cx * cx + N * N = r * r
289  *
290  * (9)   A = D * D + 1
291  * (10)  B = -2 * (cx + D * N)
292  * (11)  C = cx * cx + N * N - r * r
293  *
294  * (12)  Px = (-B +- SQRT(B * B - 4 * A * C)) / 2 * A
295  */
297 namespace {
299 struct SymmetricRadial {
300 template <typename compose, typename spread, unsigned bpp>
301 static void render(NRGradientRenderer *gr, NRPixBlock *pb)
303     NRRGradientRenderer *rgr = static_cast<NRRGradientRenderer *>(gr);
305     NR::Coord const dx = rgr->px2gs.c[0];
306     NR::Coord const dy = rgr->px2gs.c[1];
308     NRPixBlock spb;
309     nr_pixblock_setup_extern(&spb, NR_PIXBLOCK_MODE_R8G8B8A8N,
310                              0, 0, NR_GRADIENT_VECTOR_LENGTH, 1,
311                              (unsigned char *) rgr->vector,
312                              4 * NR_GRADIENT_VECTOR_LENGTH,
313                              0, 0);
315     for (int y = pb->area.y0; y < pb->area.y1; y++) {
316         unsigned char *d = NR_PIXBLOCK_PX(pb) + (y - pb->area.y0) * pb->rs;
317         NR::Coord gx = rgr->px2gs.c[0] * pb->area.x0 + rgr->px2gs.c[2] * y + rgr->px2gs.c[4];
318         NR::Coord gy = rgr->px2gs.c[1] * pb->area.x0 + rgr->px2gs.c[3] * y + rgr->px2gs.c[5];
319         for (int x = pb->area.x0; x < pb->area.x1; x++) {
320             NR::Coord const pos = sqrt(((gx*gx) + (gy*gy)));
321             unsigned char const *s=spread::color_at(pos, rgr->vector);
322             compose::compose(pb, d, &spb, s);
323             d += bpp;
324             gx += dx;
325             gy += dy;
326         }
327     }
329     nr_pixblock_release(&spb);
331 };
333 struct Radial {
334 template <typename compose, typename spread, unsigned bpp>
335 static void render(NRGradientRenderer *gr, NRPixBlock *pb)
337     NRRGradientRenderer *rgr = static_cast<NRRGradientRenderer *>(gr);
338     int const x0 = pb->area.x0;
339     int const y0 = pb->area.y0;
340     int const x1 = pb->area.x1;
341     int const y1 = pb->area.y1;
342     int const rs = pb->rs;
344     NRPixBlock spb;
345     nr_pixblock_setup_extern(&spb, NR_PIXBLOCK_MODE_R8G8B8A8N,
346                              0, 0, NR_GRADIENT_VECTOR_LENGTH, 1,
347                              (unsigned char *) rgr->vector,
348                              4 * NR_GRADIENT_VECTOR_LENGTH,
349                              0, 0);
351     for (int y = y0; y < y1; y++) {
352         unsigned char *d = NR_PIXBLOCK_PX(pb) + (y - y0) * rs;
353         NR::Coord gx = rgr->px2gs.c[0] * x0 + rgr->px2gs.c[2] * y + rgr->px2gs.c[4];
354         NR::Coord gy = rgr->px2gs.c[1] * x0 + rgr->px2gs.c[3] * y + rgr->px2gs.c[5];
355         NR::Coord const dx = rgr->px2gs.c[0];
356         NR::Coord const dy = rgr->px2gs.c[1];
357         for (int x = x0; x < x1; x++) {
358             NR::Coord const gx2 = gx * gx;
359             NR::Coord const gxy2 = gx2 + gy * gy;
360             NR::Coord const qgx2_4 = gx2 - rgr->C * gxy2;
361             /* INVARIANT: qgx2_4 >= 0.0 */
362             /* qgx2_4 = MAX(qgx2_4, 0.0); */
363             NR::Coord const pxgx = gx + sqrt(qgx2_4);
364             /* We can safely divide by 0 here */
365             /* If we are sure pxgx cannot be -0 */
366             NR::Coord const pos = gxy2 / pxgx * NR_GRADIENT_VECTOR_LENGTH;
368             unsigned char const *s;
369             if (pos < (1U << 31)) {
370                 s = spread::color_at(pos, rgr->vector);
371             } else {
372                 s = vector_index(NR_GRADIENT_VECTOR_LENGTH - 1, rgr->vector);
373             }
374             
375             compose::compose(pb, d, &spb, s);
377             d += bpp;
379             gx += dx;
380             gy += dy;
381         }
382     }
384     nr_pixblock_release(&spb);
386 };
390 static void nr_rgradient_render_block_end(NRRenderer *r, NRPixBlock *pb, NRPixBlock *m);
392 NRRenderer *
393 nr_rgradient_renderer_setup(NRRGradientRenderer *rgr,
394                             unsigned char const *cv,
395                             unsigned spread,
396                             NRMatrix const *gs2px,
397                             float cx, float cy,
398                             float fx, float fy,
399                             float r)
401     rgr->vector = cv;
402     rgr->spread = spread;
404     if (r < NR_EPSILON) {
405         rgr->render = nr_rgradient_render_block_end;
406     } else if (NR_DF_TEST_CLOSE(cx, fx, NR_EPSILON) &&
407                NR_DF_TEST_CLOSE(cy, fy, NR_EPSILON)) {
408         rgr->render = render<SymmetricRadial>;
410         nr_matrix_invert(&rgr->px2gs, gs2px);
411         rgr->px2gs.c[0] *= (NR_GRADIENT_VECTOR_LENGTH / r);
412         rgr->px2gs.c[1] *= (NR_GRADIENT_VECTOR_LENGTH / r);
413         rgr->px2gs.c[2] *= (NR_GRADIENT_VECTOR_LENGTH / r);
414         rgr->px2gs.c[3] *= (NR_GRADIENT_VECTOR_LENGTH / r);
415         rgr->px2gs.c[4] -= cx;
416         rgr->px2gs.c[5] -= cy;
417         rgr->px2gs.c[4] *= (NR_GRADIENT_VECTOR_LENGTH / r);
418         rgr->px2gs.c[5] *= (NR_GRADIENT_VECTOR_LENGTH / r);
420         rgr->cx = 0.0;
421         rgr->cy = 0.0;
422         rgr->fx = rgr->cx;
423         rgr->fy = rgr->cy;
424         rgr->r = 1.0;
425     } else {
426         rgr->render = render<Radial>;
428         NR::Coord const df = hypot(fx - cx, fy - cy);
429         if (df >= r) {
430             fx = cx + (fx - cx ) * r / (float) df;
431             fy = cy + (fy - cy ) * r / (float) df;
432         }
434         NRMatrix n2gs;
435         n2gs.c[0] = cx - fx;
436         n2gs.c[1] = cy - fy;
437         n2gs.c[2] = cy - fy;
438         n2gs.c[3] = fx - cx;
439         n2gs.c[4] = fx;
440         n2gs.c[5] = fy;
442         NRMatrix n2px;
443         nr_matrix_multiply(&n2px, &n2gs, gs2px);
444         nr_matrix_invert(&rgr->px2gs, &n2px);
446         rgr->cx = 1.0;
447         rgr->cy = 0.0;
448         rgr->fx = 0.0;
449         rgr->fy = 0.0;
450         rgr->r = r / (float) hypot(fx - cx, fy - cy);
451         rgr->C = 1.0F - rgr->r * rgr->r;
452         /* INVARIANT: C < 0 */
453         rgr->C = MIN(rgr->C, -NR_EPSILON);
454     }
456     return (NRRenderer *) rgr;
459 static void
460 nr_rgradient_render_block_end(NRRenderer *r, NRPixBlock *pb, NRPixBlock *m)
462     unsigned char const *c = ((NRRGradientRenderer *) r)->vector + 4 * (NR_GRADIENT_VECTOR_LENGTH - 1);
464     nr_blit_pixblock_mask_rgba32(pb, m, (c[0] << 24) | (c[1] << 16) | (c[2] << 8) | c[3]);
467 /*
468   Local Variables:
469   mode:c++
470   c-file-style:"stroustrup"
471   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
472   indent-tabs-mode:nil
473   fill-column:99
474   End:
475 */
476 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :