Code

radial gradients faster by about 10%
[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  *
9  * This code is in public domain
10  */
12 #include <libnr/nr-pixops.h>
13 #include <libnr/nr-pixblock-pixel.h>
14 #include <libnr/nr-blit.h>
15 #include <libnr/nr-gradient.h>
17 #define NRG_MASK (NR_GRADIENT_VECTOR_LENGTH - 1)
18 #define NRG_2MASK ((long long) ((NR_GRADIENT_VECTOR_LENGTH << 1) - 1))
20 /* Radial */
22 static void nr_rgradient_render_block_symmetric(NRRenderer *r, NRPixBlock *pb, NRPixBlock *m);
23 static void nr_rgradient_render_block_optimized(NRRenderer *r, NRPixBlock *pb, NRPixBlock *m);
24 static void nr_rgradient_render_block_end(NRRenderer *r, NRPixBlock *pb, NRPixBlock *m);
25 static void nr_rgradient_render_generic_symmetric(NRRGradientRenderer *rgr, NRPixBlock *pb);
26 static void nr_rgradient_render_generic_optimized(NRRGradientRenderer *rgr, NRPixBlock *pb);
28 NRRenderer *
29 nr_rgradient_renderer_setup(NRRGradientRenderer *rgr,
30                             unsigned char const *cv,
31                             unsigned spread,
32                             NRMatrix const *gs2px,
33                             float cx, float cy,
34                             float fx, float fy,
35                             float r)
36 {
37     rgr->vector = cv;
38     rgr->spread = spread;
40     if (r < NR_EPSILON) {
41         rgr->renderer.render = nr_rgradient_render_block_end;
42     } else if (NR_DF_TEST_CLOSE(cx, fx, NR_EPSILON) &&
43                NR_DF_TEST_CLOSE(cy, fy, NR_EPSILON)) {
44         rgr->renderer.render = nr_rgradient_render_block_symmetric;
46         nr_matrix_invert(&rgr->px2gs, gs2px);
47         rgr->px2gs.c[0] *= (NR_GRADIENT_VECTOR_LENGTH / r);
48         rgr->px2gs.c[1] *= (NR_GRADIENT_VECTOR_LENGTH / r);
49         rgr->px2gs.c[2] *= (NR_GRADIENT_VECTOR_LENGTH / r);
50         rgr->px2gs.c[3] *= (NR_GRADIENT_VECTOR_LENGTH / r);
51         rgr->px2gs.c[4] -= cx;
52         rgr->px2gs.c[5] -= cy;
53         rgr->px2gs.c[4] *= (NR_GRADIENT_VECTOR_LENGTH / r);
54         rgr->px2gs.c[5] *= (NR_GRADIENT_VECTOR_LENGTH / r);
56         rgr->cx = 0.0;
57         rgr->cy = 0.0;
58         rgr->fx = rgr->cx;
59         rgr->fy = rgr->cy;
60         rgr->r = 1.0;
61     } else {
62         rgr->renderer.render = nr_rgradient_render_block_optimized;
64         NR::Coord const df = hypot(fx - cx, fy - cy);
65         if (df >= r) {
66             fx = cx + (fx - cx ) * r / (float) df;
67             fy = cy + (fy - cy ) * r / (float) df;
68         }
70         NRMatrix n2gs;
71         n2gs.c[0] = cx - fx;
72         n2gs.c[1] = cy - fy;
73         n2gs.c[2] = cy - fy;
74         n2gs.c[3] = fx - cx;
75         n2gs.c[4] = fx;
76         n2gs.c[5] = fy;
78         NRMatrix n2px;
79         nr_matrix_multiply(&n2px, &n2gs, gs2px);
80         nr_matrix_invert(&rgr->px2gs, &n2px);
82         rgr->cx = 1.0;
83         rgr->cy = 0.0;
84         rgr->fx = 0.0;
85         rgr->fy = 0.0;
86         rgr->r = r / (float) hypot(fx - cx, fy - cy);
87         rgr->C = 1.0F - rgr->r * rgr->r;
88         /* INVARIANT: C < 0 */
89         rgr->C = MIN(rgr->C, -NR_EPSILON);
90     }
92     return (NRRenderer *) rgr;
93 }
95 static void
96 nr_rgradient_render_block_symmetric(NRRenderer *r, NRPixBlock *pb, NRPixBlock *m)
97 {
98     NRRGradientRenderer *rgr = (NRRGradientRenderer *) r;
99     nr_rgradient_render_generic_symmetric(rgr, pb);
102 static void
103 nr_rgradient_render_block_optimized(NRRenderer *r, NRPixBlock *pb, NRPixBlock *m)
105     NRRGradientRenderer *rgr = (NRRGradientRenderer *) r;
106     nr_rgradient_render_generic_optimized(rgr, pb);
109 static void
110 nr_rgradient_render_block_end(NRRenderer *r, NRPixBlock *pb, NRPixBlock *m)
112     unsigned char const *c = ((NRRGradientRenderer *) r)->vector + 4 * (NR_GRADIENT_VECTOR_LENGTH - 1);
114     nr_blit_pixblock_mask_rgba32(pb, m, (c[0] << 24) | (c[1] << 16) | (c[2] << 8) | c[3]);
117 float fast_sqrt (float in)
119     float x = in;
120     float xhalf = 0.5f*x;
121     int i = *(int*)&x;
122     i = 0x5f3759df - (i >> 1); // This line hides a LOT of math!
123     x = *(float*)&i;
124     x = x*(1.5f - xhalf*x*x); // repeat this statement for a better approximation
125     return x * in;
128 /*
129  * The archetype is following
130  *
131  * gx gy - pixel coordinates
132  * Px Py - coordinates, where Fx Fy - gx gy line intersects with circle
133  *
134  * (1)  (gx - fx) * (Py - fy) = (gy - fy) * (Px - fx)
135  * (2)  (Px - cx) * (Px - cx) + (Py - cy) * (Py - cy) = r * r
136  *
137  * (3)   Py = (Px - fx) * (gy - fy) / (gx - fx) + fy
138  * (4)  (gy - fy) / (gx - fx) = D
139  * (5)   Py = D * Px - D * fx + fy
140  *
141  * (6)   D * fx - fy + cy = N
142  * (7)   Px * Px - 2 * Px * cx + cx * cx + (D * Px) * (D * Px) - 2 * (D * Px) * N + N * N = r * r
143  * (8)  (D * D + 1) * (Px * Px) - 2 * (cx + D * N) * Px + cx * cx + N * N = r * r
144  *
145  * (9)   A = D * D + 1
146  * (10)  B = -2 * (cx + D * N)
147  * (11)  C = cx * cx + N * N - r * r
148  *
149  * (12)  Px = (-B +- SQRT(B * B - 4 * A * C)) / 2 * A
150  */
152 static void
153 nr_rgradient_render_generic_symmetric(NRRGradientRenderer *rgr, NRPixBlock *pb)
155     NR::Coord const dx = rgr->px2gs.c[0];
156     NR::Coord const dy = rgr->px2gs.c[1];
158     if (pb->mode == NR_PIXBLOCK_MODE_R8G8B8A8P) {
159         for (int y = pb->area.y0; y < pb->area.y1; y++) {
160             unsigned char *d = NR_PIXBLOCK_PX(pb) + (y - pb->area.y0) * pb->rs;
161             NR::Coord gx = rgr->px2gs.c[0] * pb->area.x0 + rgr->px2gs.c[2] * y + rgr->px2gs.c[4];
162             NR::Coord gy = rgr->px2gs.c[1] * pb->area.x0 + rgr->px2gs.c[3] * y + rgr->px2gs.c[5];
163             for (int x = pb->area.x0; x < pb->area.x1; x++) {
164                 float const pos = fast_sqrt((gx*gx) + (gy*gy));
165                 int idx;
166                 if (rgr->spread == NR_GRADIENT_SPREAD_REFLECT) {
167                     idx = (int) ((long long) pos & NRG_2MASK);
168                     if (idx > NRG_MASK) idx = NRG_2MASK - idx;
169                 } else if (rgr->spread == NR_GRADIENT_SPREAD_REPEAT) {
170                     idx = (int) ((long long) pos & NRG_MASK);
171                 } else {
172                     idx = (int) CLAMP(pos, 0, (double) NRG_MASK);
173                 }
174                 unsigned char const *s = rgr->vector + 4 * idx;
175                 d[0] = NR_COMPOSENPP(s[0], s[3], d[0], d[3]);
176                 d[1] = NR_COMPOSENPP(s[1], s[3], d[1], d[3]);
177                 d[2] = NR_COMPOSENPP(s[2], s[3], d[2], d[3]);
178                 d[3] = (255*255 - (255 - s[3]) * (255 - d[3]) + 127) / 255;
179                 d += 4;
180                 gx += dx;
181                 gy += dy;
182             }
183         }
184     } else if (pb->mode == NR_PIXBLOCK_MODE_R8G8B8A8N) {
185         for (int y = pb->area.y0; y < pb->area.y1; y++) {
186             unsigned char *d = NR_PIXBLOCK_PX(pb) + (y - pb->area.y0) * pb->rs;
187             NR::Coord gx = rgr->px2gs.c[0] * pb->area.x0 + rgr->px2gs.c[2] * y + rgr->px2gs.c[4];
188             NR::Coord gy = rgr->px2gs.c[1] * pb->area.x0 + rgr->px2gs.c[3] * y + rgr->px2gs.c[5];
189             for (int x = pb->area.x0; x < pb->area.x1; x++) {
190                 float pos = fast_sqrt((gx*gx) + (gy*gy));
191                 int idx;
192                 if (rgr->spread == NR_GRADIENT_SPREAD_REFLECT) {
193                     idx = (int) ((long long) pos & NRG_2MASK);
194                     if (idx > NRG_MASK) idx = NRG_2MASK - idx;
195                 } else if (rgr->spread == NR_GRADIENT_SPREAD_REPEAT) {
196                     idx = (int) ((long long) pos & NRG_MASK);
197                 } else {
198                     idx = (int) CLAMP(pos, 0, (double) NRG_MASK);
199                 }
200                 unsigned char const *s = rgr->vector + 4 * idx;
201                 if (s[3] == 255) {
202                     d[0] = s[0];
203                     d[1] = s[1];
204                     d[2] = s[2];
205                     d[3] = 255;
206                 } else if (s[3] != 0) {
207                     unsigned ca = 255*255 - (255 - s[3]) * (255 - d[3]);
208                     d[0] = NR_COMPOSENNN_A7(s[0], s[3], d[0], d[3], ca);
209                     d[1] = NR_COMPOSENNN_A7(s[1], s[3], d[1], d[3], ca);
210                     d[2] = NR_COMPOSENNN_A7(s[2], s[3], d[2], d[3], ca);
211                     d[3] = (ca + 127) / 255;
212                 }
213                 d += 4;
214                 gx += dx;
215                 gy += dy;
216             }
217         }
218     } else {
219         NRPixBlock spb;
220         nr_pixblock_setup_extern(&spb, NR_PIXBLOCK_MODE_R8G8B8A8N, 0, 0, NR_GRADIENT_VECTOR_LENGTH, 1,
221                                  (unsigned char *) rgr->vector,
222                                  4 * NR_GRADIENT_VECTOR_LENGTH,
223                                  0, 0);
224         int const bpp = ( pb->mode == NR_PIXBLOCK_MODE_A8
225                           ? 1
226                           : pb->mode == NR_PIXBLOCK_MODE_R8G8B8
227                           ? 3
228                           : 4 );
230         for (int y = pb->area.y0; y < pb->area.y1; y++) {
231             unsigned char *d = NR_PIXBLOCK_PX(pb) + (y - pb->area.y0) * pb->rs;
232             NR::Coord gx = rgr->px2gs.c[0] * pb->area.x0 + rgr->px2gs.c[2] * y + rgr->px2gs.c[4];
233             NR::Coord gy = rgr->px2gs.c[1] * pb->area.x0 + rgr->px2gs.c[3] * y + rgr->px2gs.c[5];
234             for (int x = pb->area.x0; x < pb->area.x1; x++) {
235                 NR::Coord const pos = fast_sqrt((gx*gx) + (gy*gy));
236                 int idx;
237                 if (rgr->spread == NR_GRADIENT_SPREAD_REFLECT) {
238                     idx = (int) ((long long) pos & NRG_2MASK);
239                     if (idx > NRG_MASK) idx = NRG_2MASK - idx;
240                 } else if (rgr->spread == NR_GRADIENT_SPREAD_REPEAT) {
241                     idx = (int) ((long long) pos & NRG_MASK);
242                 } else {
243                     idx = (int) CLAMP(pos, 0, (double) NRG_MASK);
244                 }
245                 unsigned char const *s = rgr->vector + 4 * idx;
246                 nr_compose_pixblock_pixblock_pixel(pb, d, &spb, s);
247                 d += bpp;
248                 gx += dx;
249                 gy += dy;
250             }
251         }
253         nr_pixblock_release(&spb);
254     }
257 static void
258 nr_rgradient_render_generic_optimized(NRRGradientRenderer *rgr, NRPixBlock *pb)
260     int const x0 = pb->area.x0;
261     int const y0 = pb->area.y0;
262     int const x1 = pb->area.x1;
263     int const y1 = pb->area.y1;
264     int const rs = pb->rs;
266     NRPixBlock spb;
267     nr_pixblock_setup_extern(&spb, NR_PIXBLOCK_MODE_R8G8B8A8N, 0, 0, NR_GRADIENT_VECTOR_LENGTH, 1,
268                              (unsigned char *) rgr->vector,
269                              4 * NR_GRADIENT_VECTOR_LENGTH,
270                              0, 0);
271     int const bpp = ( pb->mode == NR_PIXBLOCK_MODE_A8
272                       ? 1
273                       : pb->mode == NR_PIXBLOCK_MODE_R8G8B8
274                       ? 3
275                       : 4 );
277     for (int y = y0; y < y1; y++) {
278         unsigned char *d = NR_PIXBLOCK_PX(pb) + (y - y0) * rs;
279         NR::Coord gx = rgr->px2gs.c[0] * x0 + rgr->px2gs.c[2] * y + rgr->px2gs.c[4];
280         NR::Coord gy = rgr->px2gs.c[1] * x0 + rgr->px2gs.c[3] * y + rgr->px2gs.c[5];
281         NR::Coord const dx = rgr->px2gs.c[0];
282         NR::Coord const dy = rgr->px2gs.c[1];
283         for (int x = x0; x < x1; x++) {
284             NR::Coord const gx2 = gx * gx;
285             NR::Coord const gxy2 = gx2 + gy * gy;
286             NR::Coord const qgx2_4 = gx2 - rgr->C * gxy2;
287             /* INVARIANT: qgx2_4 >= 0.0 */
288             /* qgx2_4 = MAX(qgx2_4, 0.0); */
289             NR::Coord const pxgx = gx + sqrt(qgx2_4);
290             /* We can safely divide by 0 here */
291             /* If we are sure pxgx cannot be -0 */
292             NR::Coord const pos = gxy2 / pxgx * NR_GRADIENT_VECTOR_LENGTH;
293             int idx;
294             if (pos < (1U << 31)) {
295                 if (rgr->spread == NR_GRADIENT_SPREAD_REFLECT) {
296                     idx = (int) ((long long) pos & NRG_2MASK);
297                     if (idx > NRG_MASK) idx = NRG_2MASK - idx;
298                 } else if (rgr->spread == NR_GRADIENT_SPREAD_REPEAT) {
299                     idx = (int) ((long long) pos & NRG_MASK);
300                 } else {
301                     idx = (int) CLAMP(pos, 0, (double) (NR_GRADIENT_VECTOR_LENGTH - 1));
302                 }
303             } else {
304                 idx = NR_GRADIENT_VECTOR_LENGTH - 1;
305             }
306             unsigned char const *s = rgr->vector + 4 * idx;
307             nr_compose_pixblock_pixblock_pixel(pb, d, &spb, s);
308             d += bpp;
310             gx += dx;
311             gy += dy;
312         }
313     }
315     nr_pixblock_release(&spb);
319 /*
320   Local Variables:
321   mode:c++
322   c-file-style:"stroustrup"
323   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
324   indent-tabs-mode:nil
325   fill-column:99
326   End:
327 */
328 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :