1468eb61bbd777a603d43e5a018a634d665ad2fa
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 }
134 }
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;
168 }
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;
179 }
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;
187 }
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;
198 }
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;
209 }
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();
220 }
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;
296 }
298 void FilterBlend::set_input(int slot) {
299 _input = slot;
300 }
302 void FilterBlend::set_input(int input, int slot) {
303 if (input == 0) _input = slot;
304 if (input == 1) _input2 = slot;
305 }
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 }
314 }
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 :