Code

4553eb598d84a4f2a8437903e8e50b14b241465a
[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 /*
118  * The archetype is following
119  *
120  * gx gy - pixel coordinates
121  * Px Py - coordinates, where Fx Fy - gx gy line intersects with circle
122  *
123  * (1)  (gx - fx) * (Py - fy) = (gy - fy) * (Px - fx)
124  * (2)  (Px - cx) * (Px - cx) + (Py - cy) * (Py - cy) = r * r
125  *
126  * (3)   Py = (Px - fx) * (gy - fy) / (gx - fx) + fy
127  * (4)  (gy - fy) / (gx - fx) = D
128  * (5)   Py = D * Px - D * fx + fy
129  *
130  * (6)   D * fx - fy + cy = N
131  * (7)   Px * Px - 2 * Px * cx + cx * cx + (D * Px) * (D * Px) - 2 * (D * Px) * N + N * N = r * r
132  * (8)  (D * D + 1) * (Px * Px) - 2 * (cx + D * N) * Px + cx * cx + N * N = r * r
133  *
134  * (9)   A = D * D + 1
135  * (10)  B = -2 * (cx + D * N)
136  * (11)  C = cx * cx + N * N - r * r
137  *
138  * (12)  Px = (-B +- SQRT(B * B - 4 * A * C)) / 2 * A
139  */
141 static void
142 nr_rgradient_render_generic_symmetric(NRRGradientRenderer *rgr, NRPixBlock *pb)
144     NR::Coord const dx = rgr->px2gs.c[0];
145     NR::Coord const dy = rgr->px2gs.c[1];
147     if (pb->mode == NR_PIXBLOCK_MODE_R8G8B8A8P) {
148         for (int y = pb->area.y0; y < pb->area.y1; y++) {
149             unsigned char *d = NR_PIXBLOCK_PX(pb) + (y - pb->area.y0) * pb->rs;
150             NR::Coord gx = rgr->px2gs.c[0] * pb->area.x0 + rgr->px2gs.c[2] * y + rgr->px2gs.c[4];
151             NR::Coord gy = rgr->px2gs.c[1] * pb->area.x0 + rgr->px2gs.c[3] * y + rgr->px2gs.c[5];
152             for (int x = pb->area.x0; x < pb->area.x1; x++) {
153                 NR::Coord const pos = sqrt(((gx*gx) + (gy*gy)));
154                 int idx;
155                 if (rgr->spread == NR_GRADIENT_SPREAD_REFLECT) {
156                     idx = (int) ((long long) pos & NRG_2MASK);
157                     if (idx > NRG_MASK) idx = NRG_2MASK - idx;
158                 } else if (rgr->spread == NR_GRADIENT_SPREAD_REPEAT) {
159                     idx = (int) ((long long) pos & NRG_MASK);
160                 } else {
161                     idx = (int) CLAMP(pos, 0, (double) NRG_MASK);
162                 }
163                 unsigned char const *s = rgr->vector + 4 * idx;
164                 d[0] = NR_COMPOSENPP(s[0], s[3], d[0], d[3]);
165                 d[1] = NR_COMPOSENPP(s[1], s[3], d[1], d[3]);
166                 d[2] = NR_COMPOSENPP(s[2], s[3], d[2], d[3]);
167                 d[3] = (255*255 - (255 - s[3]) * (255 - d[3]) + 127) / 255;
168                 d += 4;
169                 gx += dx;
170                 gy += dy;
171             }
172         }
173     } else if (pb->mode == NR_PIXBLOCK_MODE_R8G8B8A8N) {
174         for (int y = pb->area.y0; y < pb->area.y1; y++) {
175             unsigned char *d = NR_PIXBLOCK_PX(pb) + (y - pb->area.y0) * pb->rs;
176             NR::Coord gx = rgr->px2gs.c[0] * pb->area.x0 + rgr->px2gs.c[2] * y + rgr->px2gs.c[4];
177             NR::Coord gy = rgr->px2gs.c[1] * pb->area.x0 + rgr->px2gs.c[3] * y + rgr->px2gs.c[5];
178             for (int x = pb->area.x0; x < pb->area.x1; x++) {
179                 NR::Coord const pos = sqrt(((gx*gx) + (gy*gy)));
180                 int idx;
181                 if (rgr->spread == NR_GRADIENT_SPREAD_REFLECT) {
182                     idx = (int) ((long long) pos & NRG_2MASK);
183                     if (idx > NRG_MASK) idx = NRG_2MASK - idx;
184                 } else if (rgr->spread == NR_GRADIENT_SPREAD_REPEAT) {
185                     idx = (int) ((long long) pos & NRG_MASK);
186                 } else {
187                     idx = (int) CLAMP(pos, 0, (double) NRG_MASK);
188                 }
189                 unsigned char const *s = rgr->vector + 4 * idx;
190                 if (s[3] == 255) {
191                     d[0] = s[0];
192                     d[1] = s[1];
193                     d[2] = s[2];
194                     d[3] = 255;
195                 } else if (s[3] != 0) {
196                     unsigned ca = 255*255 - (255 - s[3]) * (255 - d[3]);
197                     d[0] = NR_COMPOSENNN_A7(s[0], s[3], d[0], d[3], ca);
198                     d[1] = NR_COMPOSENNN_A7(s[1], s[3], d[1], d[3], ca);
199                     d[2] = NR_COMPOSENNN_A7(s[2], s[3], d[2], d[3], ca);
200                     d[3] = (ca + 127) / 255;
201                 }
202                 d += 4;
203                 gx += dx;
204                 gy += dy;
205             }
206         }
207     } else {
208         NRPixBlock spb;
209         nr_pixblock_setup_extern(&spb, NR_PIXBLOCK_MODE_R8G8B8A8N, 0, 0, NR_GRADIENT_VECTOR_LENGTH, 1,
210                                  (unsigned char *) rgr->vector,
211                                  4 * NR_GRADIENT_VECTOR_LENGTH,
212                                  0, 0);
213         int const bpp = ( pb->mode == NR_PIXBLOCK_MODE_A8
214                           ? 1
215                           : pb->mode == NR_PIXBLOCK_MODE_R8G8B8
216                           ? 3
217                           : 4 );
219         for (int y = pb->area.y0; y < pb->area.y1; y++) {
220             unsigned char *d = NR_PIXBLOCK_PX(pb) + (y - pb->area.y0) * pb->rs;
221             NR::Coord gx = rgr->px2gs.c[0] * pb->area.x0 + rgr->px2gs.c[2] * y + rgr->px2gs.c[4];
222             NR::Coord gy = rgr->px2gs.c[1] * pb->area.x0 + rgr->px2gs.c[3] * y + rgr->px2gs.c[5];
223             for (int x = pb->area.x0; x < pb->area.x1; x++) {
224                 NR::Coord const pos = sqrt(((gx*gx) + (gy*gy)));
225                 int idx;
226                 if (rgr->spread == NR_GRADIENT_SPREAD_REFLECT) {
227                     idx = (int) ((long long) pos & NRG_2MASK);
228                     if (idx > NRG_MASK) idx = NRG_2MASK - idx;
229                 } else if (rgr->spread == NR_GRADIENT_SPREAD_REPEAT) {
230                     idx = (int) ((long long) pos & NRG_MASK);
231                 } else {
232                     idx = (int) CLAMP(pos, 0, (double) NRG_MASK);
233                 }
234                 unsigned char const *s = rgr->vector + 4 * idx;
235                 nr_compose_pixblock_pixblock_pixel(pb, d, &spb, s);
236                 d += bpp;
237                 gx += dx;
238                 gy += dy;
239             }
240         }
242         nr_pixblock_release(&spb);
243     }
246 static void
247 nr_rgradient_render_generic_optimized(NRRGradientRenderer *rgr, NRPixBlock *pb)
249     int const x0 = pb->area.x0;
250     int const y0 = pb->area.y0;
251     int const x1 = pb->area.x1;
252     int const y1 = pb->area.y1;
253     int const rs = pb->rs;
255     NRPixBlock spb;
256     nr_pixblock_setup_extern(&spb, NR_PIXBLOCK_MODE_R8G8B8A8N, 0, 0, NR_GRADIENT_VECTOR_LENGTH, 1,
257                              (unsigned char *) rgr->vector,
258                              4 * NR_GRADIENT_VECTOR_LENGTH,
259                              0, 0);
260     int const bpp = ( pb->mode == NR_PIXBLOCK_MODE_A8
261                       ? 1
262                       : pb->mode == NR_PIXBLOCK_MODE_R8G8B8
263                       ? 3
264                       : 4 );
266     for (int y = y0; y < y1; y++) {
267         unsigned char *d = NR_PIXBLOCK_PX(pb) + (y - y0) * rs;
268         NR::Coord gx = rgr->px2gs.c[0] * x0 + rgr->px2gs.c[2] * y + rgr->px2gs.c[4];
269         NR::Coord gy = rgr->px2gs.c[1] * x0 + rgr->px2gs.c[3] * y + rgr->px2gs.c[5];
270         NR::Coord const dx = rgr->px2gs.c[0];
271         NR::Coord const dy = rgr->px2gs.c[1];
272         for (int x = x0; x < x1; x++) {
273             NR::Coord const gx2 = gx * gx;
274             NR::Coord const gxy2 = gx2 + gy * gy;
275             NR::Coord const qgx2_4 = gx2 - rgr->C * gxy2;
276             /* INVARIANT: qgx2_4 >= 0.0 */
277             /* qgx2_4 = MAX(qgx2_4, 0.0); */
278             NR::Coord const pxgx = gx + sqrt(qgx2_4);
279             /* We can safely divide by 0 here */
280             /* If we are sure pxgx cannot be -0 */
281             NR::Coord const pos = gxy2 / pxgx * NR_GRADIENT_VECTOR_LENGTH;
282             int idx;
283             if (pos < (1U << 31)) {
284                 if (rgr->spread == NR_GRADIENT_SPREAD_REFLECT) {
285                     idx = (int) ((long long) pos & NRG_2MASK);
286                     if (idx > NRG_MASK) idx = NRG_2MASK - idx;
287                 } else if (rgr->spread == NR_GRADIENT_SPREAD_REPEAT) {
288                     idx = (int) ((long long) pos & NRG_MASK);
289                 } else {
290                     idx = (int) CLAMP(pos, 0, (double) (NR_GRADIENT_VECTOR_LENGTH - 1));
291                 }
292             } else {
293                 idx = NR_GRADIENT_VECTOR_LENGTH - 1;
294             }
295             unsigned char const *s = rgr->vector + 4 * idx;
296             nr_compose_pixblock_pixblock_pixel(pb, d, &spb, s);
297             d += bpp;
299             gx += dx;
300             gy += dy;
301         }
302     }
304     nr_pixblock_release(&spb);
308 /*
309   Local Variables:
310   mode:c++
311   c-file-style:"stroustrup"
312   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
313   indent-tabs-mode:nil
314   fill-column:99
315   End:
316 */
317 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :