Code

1468eb61bbd777a603d43e5a018a634d665ad2fa
[inkscape.git] / src / display / nr-filter-blend.cpp
1 /*
2  * SVG feBlend renderer
3  *
4  * "This filter composites two objects together using commonly used
5  * imaging software blending modes. It performs a pixel-wise combination
6  * of two input images." 
7  * http://www.w3.org/TR/SVG11/filters.html#feBlend
8  *
9  * Authors:
10  *   Niko Kiirala <niko@kiirala.com>
11  *
12  * Copyright (C) 2007 authors
13  *
14  * Released under GNU GPL, read the file 'COPYING' for more information
15  */
17 #include "display/nr-filter-blend.h"
18 #include "display/nr-filter-primitive.h"
19 #include "display/nr-filter-slot.h"
20 #include "display/nr-filter-types.h"
21 #include "libnr/nr-pixblock.h"
22 #include "libnr/nr-matrix.h"
23 #include "libnr/nr-blit.h"
24 #include "libnr/nr-pixops.h"
26 /*
27 static inline int clamp(const int val, const int min, const int max) {
28     if (val < min) return min;
29     if (val > max) return max;
30     return val;
31 }
32 */
34 template <void(*blend)(unsigned char *cr, unsigned char const *ca, unsigned char const *cb)>
35 static void _render(NRPixBlock &out, NRPixBlock &in1, NRPixBlock &in2) {
36     unsigned char *in1_data = NR_PIXBLOCK_PX(&in1);
37     unsigned char *in2_data = NR_PIXBLOCK_PX(&in2);
38     unsigned char *out_data = NR_PIXBLOCK_PX(&out);
39     unsigned char zero_rgba[4] = {0, 0, 0, 0};
41     if (in1.area.y0 < in2.area.y0) {
42         // in1 begins before in2 on y-axis
43         for (int y = in1.area.y0 ; y < in2.area.y0 ; y++) {
44             int out_line = (y - out.area.y0) * out.rs;
45             int in_line = (y - in1.area.y0) * in1.rs;
46             for (int x = in1.area.x0 ; x < in1.area.x1 ; x++) {
47                 blend(out_data + out_line + 4 * (x - out.area.x0),
48                       in1_data + in_line + 4 * (x - in1.area.x0),
49                       zero_rgba);
50             }
51         }
52     } else if (in1.area.y0 > in2.area.y0) {
53         // in2 begins before in1 on y-axis
54         for (int y = in2.area.y0 ; y < in1.area.y0 ; y++) {
55             int out_line = (y - out.area.y0) * out.rs;
56             int in_line = (y - in2.area.y0) * in2.rs;
57             for (int x = in2.area.x0 ; x < in2.area.x1 ; x++) {
58                 blend(out_data + out_line + 4 * (x - out.area.x0),
59                       zero_rgba,
60                       in2_data + in_line + 4 * (x - in2.area.x0));
61             }
62         }
63     }
65     for (int y = std::max(in1.area.y0, in2.area.y0) ;
66          y < std::min(in1.area.y1, in2.area.y1) ; ++y) {
67         int out_line = (y - out.area.y0) * out.rs;
68         int in1_line = (y - in1.area.y0) * in1.rs;
69         int in2_line = (y - in2.area.y0) * in2.rs;
71         if (in1.area.x0 < in2.area.x0) {
72             // in1 begins before in2 on x-axis
73             for (int x = in1.area.x0 ; x < in2.area.x0 ; ++x) {
74                 blend(out_data + out_line + 4 * (x - out.area.x0),
75                       in1_data + in1_line + 4 * (x - in1.area.x0),
76                       zero_rgba);
77             }
78         } else if (in1.area.x0 > in2.area.x0) {
79             // in2 begins before in1 on x-axis
80             for (int x = in2.area.x0 ; x < in1.area.x0 ; ++x) {
81                 blend(out_data + out_line + 4 * (x - out.area.x0),
82                       zero_rgba,
83                       in2_data + in2_line + 4 * (x - in2.area.x0));
84             }
85         }
87         for (int x = std::max(in1.area.x0, in2.area.x0) ;
88              x < std::min(in1.area.x1, in2.area.x1) ; ++x) {
89             blend(out_data + out_line + 4 * (x - out.area.x0),
90                   in1_data + in1_line + 4 * (x - in1.area.x0),
91                   in2_data + in2_line + 4 * (x - in2.area.x0));
92         }
94         if (in1.area.x1 > in2.area.x1) {
95             // in1 ends after in2 on x-axis
96             for (int x = in2.area.x1 ; x < in1.area.x1 ; ++x) {
97                 blend(out_data + out_line + 4 * (x - out.area.x0),
98                       in1_data + in1_line + 4 * (x - in1.area.x0),
99                       zero_rgba);
100             }
101         } else if (in1.area.x1 < in2.area.x1) {
102             // in2 ends after in1 on x-axis
103             for (int x = in1.area.x1 ; x < in2.area.x1 ; ++x) {
104                 blend(out_data + out_line + 4 * (x - out.area.x0),
105                       zero_rgba,
106                       in2_data + in2_line + 4 * (x - in2.area.x0));
107             }
108         }
109     }
111     if (in1.area.y1 > in2.area.y1) {
112         // in1 ends after in2 on y-axis
113         for (int y = in2.area.y1 ; y < in1.area.y1 ; y++) {
114             int out_line = (y - out.area.y0) * out.rs;
115             int in_line = (y - in1.area.y0) * in1.rs;
116             for (int x = in1.area.x0 ; x < in1.area.x1 ; x++) {
117                 blend(out_data + out_line + 4 * (x - out.area.x0),
118                       in1_data + in_line + 4 * (x - in1.area.x0),
119                       zero_rgba);
120             }
121         }
122     } else if (in1.area.y1 < in2.area.y1) {
123         // in2 ends after in1 on y-axis
124         for (int y = in1.area.y1 ; y < in2.area.y1 ; y++) {
125             int out_line = (y - out.area.y0) * out.rs;
126             int in_line = (y - in2.area.y0) * in2.rs;
127             for (int x = in2.area.x0 ; x < in2.area.x1 ; x++) {
128                 blend(out_data + out_line + 4 * (x - out.area.x0),
129                       zero_rgba,
130                       in2_data + in_line + 4 * (x - in2.area.x0));
131             }
132         }
133     }
136 /*
137  * From http://www.w3.org/TR/SVG11/filters.html#feBlend
138  *
139  * For all feBlend modes, the result opacity is computed as follows:
140  * qr = 1 - (1-qa)*(1-qb)
141  *
142  * For the compositing formulas below, the following definitions apply:
143  * cr = Result color (RGB) - premultiplied
144  * qa = Opacity value at a given pixel for image A
145  * qb = Opacity value at a given pixel for image B
146  * ca = Color (RGB) at a given pixel for image A - premultiplied
147  * cb = Color (RGB) at a given pixel for image B - premultiplied
148  */
150 /*
151  * These blending equations given in SVG standard are for color values
152  * in the range 0..1. As these values are stored as unsigned char values,
153  * they need some reworking. An unsigned char value can be thought as
154  * 0.8 fixed point representation of color value. This is how I've
155  * ended up with these equations here.
156  */
158 // Set alpha / opacity. This line is same for all the blending modes,
159 // so let's save some copy-pasting.
160 #define SET_ALPHA r[3] = NR_NORMALIZE_21((255 * 255) - (255 - a[3]) * (255 - b[3]))
162 // cr = (1 - qa) * cb + ca
163 inline void blend_normal(unsigned char *r, unsigned char const *a, unsigned char const *b) {
164     r[0] = NR_NORMALIZE_21((255 - a[3]) * b[0]) + a[0];
165     r[1] = NR_NORMALIZE_21((255 - a[3]) * b[1]) + a[1];
166     r[2] = NR_NORMALIZE_21((255 - a[3]) * b[2]) + a[2];
167     SET_ALPHA;
170 // cr = (1-qa)*cb + (1-qb)*ca + ca*cb
171 inline void blend_multiply(unsigned char *r, unsigned char const *a, unsigned char const *b) {
172     r[0] = NR_NORMALIZE_21((255 - a[3]) * b[0] + (255 - b[3]) * a[0]
173                            + a[0] * b[0]);
174     r[1] = NR_NORMALIZE_21((255 - a[3]) * b[1] + (255 - b[3]) * a[1]
175                            + a[1] * b[1]);
176     r[2] = NR_NORMALIZE_21((255 - a[3]) * b[2] + (255 - b[3]) * a[2]
177                            + a[2] * b[2]);
178     SET_ALPHA;
181 // cr = cb + ca - ca * cb
182 inline void blend_screen(unsigned char *r, unsigned char const *a, unsigned char const *b) {
183     r[0] = NR_NORMALIZE_21(b[0] * 255 + a[0] * 255 - a[0] * b[0]);
184     r[1] = NR_NORMALIZE_21(b[1] * 255 + a[1] * 255 - a[1] * b[1]);
185     r[2] = NR_NORMALIZE_21(b[2] * 255 + a[2] * 255 - a[2] * b[2]);
186     SET_ALPHA;
189 // cr = Min ((1 - qa) * cb + ca, (1 - qb) * ca + cb)
190 inline void blend_darken(unsigned char *r, unsigned char const *a, unsigned char const *b) {
191     r[0] = std::min(NR_NORMALIZE_21((255 - a[3]) * b[0]) + a[0],
192                     NR_NORMALIZE_21((255 - b[3]) * a[0]) + b[0]);
193     r[1] = std::min(NR_NORMALIZE_21((255 - a[3]) * b[1]) + a[1],
194                     NR_NORMALIZE_21((255 - b[3]) * a[1]) + b[1]);
195     r[2] = std::min(NR_NORMALIZE_21((255 - a[3]) * b[2]) + a[2],
196                     NR_NORMALIZE_21((255 - b[3]) * a[2]) + b[2]);
197     SET_ALPHA;
200 // cr = Max ((1 - qa) * cb + ca, (1 - qb) * ca + cb)
201 inline void blend_lighten(unsigned char *r, unsigned char const *a, unsigned char const *b) {
202     r[0] = std::max(NR_NORMALIZE_21((255 - a[3]) * b[0]) + a[0],
203                     NR_NORMALIZE_21((255 - b[3]) * a[0]) + b[0]);
204     r[1] = std::max(NR_NORMALIZE_21((255 - a[3]) * b[1]) + a[1],
205                     NR_NORMALIZE_21((255 - b[3]) * a[1]) + b[1]);
206     r[2] = std::max(NR_NORMALIZE_21((255 - a[3]) * b[2]) + a[2],
207                     NR_NORMALIZE_21((255 - b[3]) * a[2]) + b[2]);
208     SET_ALPHA;
211 namespace NR {
213 FilterBlend::FilterBlend() 
214     : _blend_mode(BLEND_NORMAL),
215       _input2(NR_FILTER_SLOT_NOT_SET)
216 {}
218 FilterPrimitive * FilterBlend::create() {
219     return new FilterBlend();
222 FilterBlend::~FilterBlend()
223 {}
225 int FilterBlend::render(FilterSlot &slot, Matrix const &trans) {
226     NRPixBlock *in1 = slot.get(_input);
227     NRPixBlock *in2 = slot.get(_input2);
228     NRPixBlock *original_in1 = in1;
229     NRPixBlock *original_in2 = in2;
230     NRPixBlock *out;
232     // Bail out if either one of source images is missing
233     if (!in1 || !in2) {
234         g_warning("Missing source image for feBlend (in=%d in2=%d)", _input, _input2);
235         return 1;
236     }
238     out = new NRPixBlock;
239     NRRectL out_area;
240     nr_rect_l_union(&out_area, &in1->area, &in2->area);
241     nr_pixblock_setup_fast(out, NR_PIXBLOCK_MODE_R8G8B8A8P,
242                            out_area.x0, out_area.y0, out_area.x1, out_area.y1,
243                            true);
245     // Blending modes are defined for premultiplied RGBA values,
246     // thus convert them to that format before blending
247     if (in1->mode != NR_PIXBLOCK_MODE_R8G8B8A8P) {
248         in1 = new NRPixBlock;
249         nr_pixblock_setup_fast(in1, NR_PIXBLOCK_MODE_R8G8B8A8P,
250                                original_in1->area.x0, original_in1->area.y0,
251                                original_in1->area.x1, original_in1->area.y1,
252                                false);
253         nr_blit_pixblock_pixblock(in1, original_in1);
254     }
255     if (in2->mode != NR_PIXBLOCK_MODE_R8G8B8A8P) {
256         in2 = new NRPixBlock;
257         nr_pixblock_setup_fast(in2, NR_PIXBLOCK_MODE_R8G8B8A8P,
258                                original_in2->area.x0, original_in2->area.y0,
259                                original_in2->area.x1, original_in2->area.y1,
260                                false);
261         nr_blit_pixblock_pixblock(in2, original_in2);
262     }
264     switch (_blend_mode) {
265         case BLEND_MULTIPLY:
266             _render<blend_multiply>(*out, *in1, *in2);
267             break;
268         case BLEND_SCREEN:
269             _render<blend_screen>(*out, *in1, *in2);
270             break;
271         case BLEND_DARKEN:
272             _render<blend_darken>(*out, *in1, *in2);
273             break;
274         case BLEND_LIGHTEN:
275             _render<blend_lighten>(*out, *in1, *in2);
276             break;
277         case BLEND_NORMAL:
278         default:
279             _render<blend_normal>(*out, *in1, *in2);
280             break;
281     }
283     if (in1 != original_in1) {
284         nr_pixblock_release(in1);
285         delete in1;
286     }
287     if (in2 != original_in2) {
288         nr_pixblock_release(in2);
289         delete in2;
290     }
292     out->empty = FALSE;
293     slot.set(_output, out);
295     return 0;
298 void FilterBlend::set_input(int slot) {
299     _input = slot;
302 void FilterBlend::set_input(int input, int slot) {
303     if (input == 0) _input = slot;
304     if (input == 1) _input2 = slot;
307 void FilterBlend::set_mode(FilterBlendMode mode) {
308     if (mode == BLEND_NORMAL || mode == BLEND_MULTIPLY ||
309         mode == BLEND_SCREEN || mode == BLEND_DARKEN ||
310         mode == BLEND_LIGHTEN)
311     {
312         _blend_mode = mode;
313     }
316 } /* namespace NR */
318 /*
319   Local Variables:
320   mode:c++
321   c-file-style:"stroustrup"
322   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
323   indent-tabs-mode:nil
324   fill-column:99
325   End:
326 */
327 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :