Code

more unreffing temporary styles properly
[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 template <void(*blend)(unsigned char *cr, unsigned char const *ca, unsigned char const *cb)>
27 static void _render(NRPixBlock &out, NRPixBlock &in1, NRPixBlock &in2) {
28     unsigned char *in1_data = NR_PIXBLOCK_PX(&in1);
29     unsigned char *in2_data = NR_PIXBLOCK_PX(&in2);
30     unsigned char *out_data = NR_PIXBLOCK_PX(&out);
31     unsigned char zero_rgba[4] = {0, 0, 0, 0};
33     if (in1.area.y0 < in2.area.y0) {
34         // in1 begins before in2 on y-axis
35         for (int y = in1.area.y0 ; y < in2.area.y0 ; y++) {
36             int out_line = (y - out.area.y0) * out.rs;
37             int in_line = (y - in1.area.y0) * in1.rs;
38             for (int x = in1.area.x0 ; x < in1.area.x1 ; x++) {
39                 blend(out_data + out_line + 4 * (x - out.area.x0),
40                       in1_data + in_line + 4 * (x - in1.area.x0),
41                       zero_rgba);
42             }
43         }
44     } else if (in1.area.y0 > in2.area.y0) {
45         // in2 begins before in1 on y-axis
46         for (int y = in2.area.y0 ; y < in1.area.y0 ; y++) {
47             int out_line = (y - out.area.y0) * out.rs;
48             int in_line = (y - in2.area.y0) * in2.rs;
49             for (int x = in2.area.x0 ; x < in2.area.x1 ; x++) {
50                 blend(out_data + out_line + 4 * (x - out.area.x0),
51                       zero_rgba,
52                       in2_data + in_line + 4 * (x - in2.area.x0));
53             }
54         }
55     }
57     for (int y = std::max(in1.area.y0, in2.area.y0) ;
58          y < std::min(in1.area.y1, in2.area.y1) ; ++y) {
59         int out_line = (y - out.area.y0) * out.rs;
60         int in1_line = (y - in1.area.y0) * in1.rs;
61         int in2_line = (y - in2.area.y0) * in2.rs;
63         if (in1.area.x0 < in2.area.x0) {
64             // in1 begins before in2 on x-axis
65             for (int x = in1.area.x0 ; x < in2.area.x0 ; ++x) {
66                 blend(out_data + out_line + 4 * (x - out.area.x0),
67                       in1_data + in1_line + 4 * (x - in1.area.x0),
68                       zero_rgba);
69             }
70         } else if (in1.area.x0 > in2.area.x0) {
71             // in2 begins before in1 on x-axis
72             for (int x = in2.area.x0 ; x < in1.area.x0 ; ++x) {
73                 blend(out_data + out_line + 4 * (x - out.area.x0),
74                       zero_rgba,
75                       in2_data + in2_line + 4 * (x - in2.area.x0));
76             }
77         }
79         for (int x = std::max(in1.area.x0, in2.area.x0) ;
80              x < std::min(in1.area.x1, in2.area.x1) ; ++x) {
81             blend(out_data + out_line + 4 * (x - out.area.x0),
82                   in1_data + in1_line + 4 * (x - in1.area.x0),
83                   in2_data + in2_line + 4 * (x - in2.area.x0));
84         }
86         if (in1.area.x1 > in2.area.x1) {
87             // in1 ends after in2 on x-axis
88             for (int x = in2.area.x1 ; x < in1.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                       zero_rgba);
92             }
93         } else if (in1.area.x1 < in2.area.x1) {
94             // in2 ends after in1 on x-axis
95             for (int x = in1.area.x1 ; x < in2.area.x1 ; ++x) {
96                 blend(out_data + out_line + 4 * (x - out.area.x0),
97                       zero_rgba,
98                       in2_data + in2_line + 4 * (x - in2.area.x0));
99             }
100         }
101     }
103     if (in1.area.y1 > in2.area.y1) {
104         // in1 ends after in2 on y-axis
105         for (int y = in2.area.y1 ; y < in1.area.y1 ; y++) {
106             int out_line = (y - out.area.y0) * out.rs;
107             int in_line = (y - in1.area.y0) * in1.rs;
108             for (int x = in1.area.x0 ; x < in1.area.x1 ; x++) {
109                 blend(out_data + out_line + 4 * (x - out.area.x0),
110                       in1_data + in_line + 4 * (x - in1.area.x0),
111                       zero_rgba);
112             }
113         }
114     } else if (in1.area.y1 < in2.area.y1) {
115         // in2 ends after in1 on y-axis
116         for (int y = in1.area.y1 ; y < in2.area.y1 ; y++) {
117             int out_line = (y - out.area.y0) * out.rs;
118             int in_line = (y - in2.area.y0) * in2.rs;
119             for (int x = in2.area.x0 ; x < in2.area.x1 ; x++) {
120                 blend(out_data + out_line + 4 * (x - out.area.x0),
121                       zero_rgba,
122                       in2_data + in_line + 4 * (x - in2.area.x0));
123             }
124         }
125     }
128 /*
129  * From http://www.w3.org/TR/SVG11/filters.html#feBlend
130  *
131  * For all feBlend modes, the result opacity is computed as follows:
132  * qr = 1 - (1-qa)*(1-qb)
133  *
134  * For the compositing formulas below, the following definitions apply:
135  * cr = Result color (RGB) - premultiplied
136  * qa = Opacity value at a given pixel for image A
137  * qb = Opacity value at a given pixel for image B
138  * ca = Color (RGB) at a given pixel for image A - premultiplied
139  * cb = Color (RGB) at a given pixel for image B - premultiplied
140  */
142 /*
143  * These blending equations given in SVG standard are for color values
144  * in the range 0..1. As these values are stored as unsigned char values,
145  * they need some reworking. An unsigned char value can be thought as
146  * 0.8 fixed point representation of color value. This is how I've
147  * ended up with these equations here.
148  */
150 // Set alpha / opacity. This line is same for all the blending modes,
151 // so let's save some copy-pasting.
152 #define SET_ALPHA r[3] = NR_NORMALIZE_21((255 * 255) - (255 - a[3]) * (255 - b[3]))
154 // cr = (1 - qa) * cb + ca
155 inline void blend_normal(unsigned char *r, unsigned char const *a, unsigned char const *b) {
156     r[0] = NR_NORMALIZE_21((255 - a[3]) * b[0]) + a[0];
157     r[1] = NR_NORMALIZE_21((255 - a[3]) * b[1]) + a[1];
158     r[2] = NR_NORMALIZE_21((255 - a[3]) * b[2]) + a[2];
159     SET_ALPHA;
162 // cr = (1-qa)*cb + (1-qb)*ca + ca*cb
163 inline void blend_multiply(unsigned char *r, unsigned char const *a, unsigned char const *b) {
164     r[0] = NR_NORMALIZE_21((255 - a[3]) * b[0] + (255 - b[3]) * a[0]
165                            + a[0] * b[0]);
166     r[1] = NR_NORMALIZE_21((255 - a[3]) * b[1] + (255 - b[3]) * a[1]
167                            + a[1] * b[1]);
168     r[2] = NR_NORMALIZE_21((255 - a[3]) * b[2] + (255 - b[3]) * a[2]
169                            + a[2] * b[2]);
170     SET_ALPHA;
173 // cr = cb + ca - ca * cb
174 inline void blend_screen(unsigned char *r, unsigned char const *a, unsigned char const *b) {
175     r[0] = NR_NORMALIZE_21(b[0] * 255 + a[0] * 255 - a[0] * b[0]);
176     r[1] = NR_NORMALIZE_21(b[1] * 255 + a[1] * 255 - a[1] * b[1]);
177     r[2] = NR_NORMALIZE_21(b[2] * 255 + a[2] * 255 - a[2] * b[2]);
178     SET_ALPHA;
181 // cr = Min ((1 - qa) * cb + ca, (1 - qb) * ca + cb)
182 inline void blend_darken(unsigned char *r, unsigned char const *a, unsigned char const *b) {
183     r[0] = std::min(NR_NORMALIZE_21((255 - a[3]) * b[0]) + a[0],
184                     NR_NORMALIZE_21((255 - b[3]) * a[0]) + b[0]);
185     r[1] = std::min(NR_NORMALIZE_21((255 - a[3]) * b[1]) + a[1],
186                     NR_NORMALIZE_21((255 - b[3]) * a[1]) + b[1]);
187     r[2] = std::min(NR_NORMALIZE_21((255 - a[3]) * b[2]) + a[2],
188                     NR_NORMALIZE_21((255 - b[3]) * a[2]) + b[2]);
189     SET_ALPHA;
192 // cr = Max ((1 - qa) * cb + ca, (1 - qb) * ca + cb)
193 inline void blend_lighten(unsigned char *r, unsigned char const *a, unsigned char const *b) {
194     r[0] = std::max(NR_NORMALIZE_21((255 - a[3]) * b[0]) + a[0],
195                     NR_NORMALIZE_21((255 - b[3]) * a[0]) + b[0]);
196     r[1] = std::max(NR_NORMALIZE_21((255 - a[3]) * b[1]) + a[1],
197                     NR_NORMALIZE_21((255 - b[3]) * a[1]) + b[1]);
198     r[2] = std::max(NR_NORMALIZE_21((255 - a[3]) * b[2]) + a[2],
199                     NR_NORMALIZE_21((255 - b[3]) * a[2]) + b[2]);
200     SET_ALPHA;
203 namespace NR {
205 FilterBlend::FilterBlend() 
206     : _blend_mode(BLEND_NORMAL),
207       _input2(NR_FILTER_SLOT_NOT_SET)
208 {}
210 FilterPrimitive * FilterBlend::create() {
211     return new FilterBlend();
214 FilterBlend::~FilterBlend()
215 {}
217 int FilterBlend::render(FilterSlot &slot, Matrix const &trans) {
218     NRPixBlock *in1 = slot.get(_input);
219     NRPixBlock *in2 = slot.get(_input2);
220     NRPixBlock *original_in1 = in1;
221     NRPixBlock *original_in2 = in2;
222     NRPixBlock *out;
224     // Bail out if either one of source images is missing
225     if (!in1 || !in2) {
226         g_warning("Missing source image for feBlend (in=%d in2=%d)", _input, _input2);
227         return 1;
228     }
230     out = new NRPixBlock;
231     NRRectL out_area;
232     nr_rect_l_union(&out_area, &in1->area, &in2->area);
233     nr_pixblock_setup_fast(out, NR_PIXBLOCK_MODE_R8G8B8A8P,
234                            out_area.x0, out_area.y0, out_area.x1, out_area.y1,
235                            true);
237     // Blending modes are defined for premultiplied RGBA values,
238     // thus convert them to that format before blending
239     if (in1->mode != NR_PIXBLOCK_MODE_R8G8B8A8P) {
240         in1 = new NRPixBlock;
241         nr_pixblock_setup_fast(in1, NR_PIXBLOCK_MODE_R8G8B8A8P,
242                                original_in1->area.x0, original_in1->area.y0,
243                                original_in1->area.x1, original_in1->area.y1,
244                                false);
245         nr_blit_pixblock_pixblock(in1, original_in1);
246     }
247     if (in2->mode != NR_PIXBLOCK_MODE_R8G8B8A8P) {
248         in2 = new NRPixBlock;
249         nr_pixblock_setup_fast(in2, NR_PIXBLOCK_MODE_R8G8B8A8P,
250                                original_in2->area.x0, original_in2->area.y0,
251                                original_in2->area.x1, original_in2->area.y1,
252                                false);
253         nr_blit_pixblock_pixblock(in2, original_in2);
254     }
256     switch (_blend_mode) {
257         case BLEND_MULTIPLY:
258             _render<blend_multiply>(*out, *in1, *in2);
259             break;
260         case BLEND_SCREEN:
261             _render<blend_screen>(*out, *in1, *in2);
262             break;
263         case BLEND_DARKEN:
264             _render<blend_darken>(*out, *in1, *in2);
265             break;
266         case BLEND_LIGHTEN:
267             _render<blend_lighten>(*out, *in1, *in2);
268             break;
269         case BLEND_NORMAL:
270         default:
271             _render<blend_normal>(*out, *in1, *in2);
272             break;
273     }
275     if (in1 != original_in1) {
276         nr_pixblock_release(in1);
277         delete in1;
278     }
279     if (in2 != original_in2) {
280         nr_pixblock_release(in2);
281         delete in2;
282     }
284     out->empty = FALSE;
285     slot.set(_output, out);
287     return 0;
290 void FilterBlend::set_input(int slot) {
291     _input = slot;
294 void FilterBlend::set_input(int input, int slot) {
295     if (input == 0) _input = slot;
296     if (input == 1) _input2 = slot;
299 void FilterBlend::set_mode(FilterBlendMode mode) {
300     if (mode == BLEND_NORMAL || mode == BLEND_MULTIPLY ||
301         mode == BLEND_SCREEN || mode == BLEND_DARKEN ||
302         mode == BLEND_LIGHTEN)
303     {
304         _blend_mode = mode;
305     }
308 } /* namespace NR */
310 /*
311   Local Variables:
312   mode:c++
313   c-file-style:"stroustrup"
314   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
315   indent-tabs-mode:nil
316   fill-column:99
317   End:
318 */
319 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :