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 * Jasper van de Gronde <th.v.d.gronde@hccnet.nl>
12 *
13 * Copyright (C) 2007-2008 authors
14 *
15 * Released under GNU GPL, read the file 'COPYING' for more information
16 */
18 #include "display/nr-filter-blend.h"
19 #include "display/nr-filter-pixops.h"
20 #include "display/nr-filter-primitive.h"
21 #include "display/nr-filter-slot.h"
22 #include "display/nr-filter-types.h"
23 #include "display/nr-filter-units.h"
24 #include "libnr/nr-pixblock.h"
25 #include "libnr/nr-blit.h"
26 #include "libnr/nr-pixops.h"
28 namespace Inkscape {
29 namespace Filters {
31 /*
32 * From http://www.w3.org/TR/SVG11/filters.html#feBlend
33 *
34 * For all feBlend modes, the result opacity is computed as follows:
35 * qr = 1 - (1-qa)*(1-qb)
36 *
37 * For the compositing formulas below, the following definitions apply:
38 * cr = Result color (RGB) - premultiplied
39 * qa = Opacity value at a given pixel for image A
40 * qb = Opacity value at a given pixel for image B
41 * ca = Color (RGB) at a given pixel for image A - premultiplied
42 * cb = Color (RGB) at a given pixel for image B - premultiplied
43 */
45 /*
46 * These blending equations given in SVG standard are for color values
47 * in the range 0..1. As these values are stored as unsigned char values,
48 * they need some reworking. An unsigned char value can be thought as
49 * 0.8 fixed point representation of color value. This is how I've
50 * ended up with these equations here.
51 */
53 // Set alpha / opacity. This line is same for all the blending modes,
54 // so let's save some copy-pasting.
55 #define SET_ALPHA r[3] = NR_NORMALIZE_21((255 * 255) - (255 - a[3]) * (255 - b[3]))
57 // cr = (1 - qa) * cb + ca
58 inline void
59 blend_normal(unsigned char *r, unsigned char const *a, unsigned char const *b)
60 {
61 r[0] = NR_COMPOSEPPP_1111(a[0],a[3],b[0]);
62 r[1] = NR_COMPOSEPPP_1111(a[1],a[3],b[1]);
63 r[2] = NR_COMPOSEPPP_1111(a[2],a[3],b[2]);
64 SET_ALPHA;
65 }
67 // cr = (1-qa)*cb + (1-qb)*ca + ca*cb
68 inline void
69 blend_multiply(unsigned char *r, unsigned char const *a, unsigned char const *b)
70 {
71 r[0] = NR_NORMALIZE_21((255 - a[3]) * b[0] + (255 - b[3]) * a[0]
72 + a[0] * b[0]);
73 r[1] = NR_NORMALIZE_21((255 - a[3]) * b[1] + (255 - b[3]) * a[1]
74 + a[1] * b[1]);
75 r[2] = NR_NORMALIZE_21((255 - a[3]) * b[2] + (255 - b[3]) * a[2]
76 + a[2] * b[2]);
77 SET_ALPHA;
78 }
80 // cr = cb + ca - ca * cb
81 inline void
82 blend_screen(unsigned char *r, unsigned char const *a, unsigned char const *b)
83 {
84 r[0] = NR_NORMALIZE_21((b[0] + a[0]) * 255 - a[0] * b[0]);
85 r[1] = NR_NORMALIZE_21((b[1] + a[1]) * 255 - a[1] * b[1]);
86 r[2] = NR_NORMALIZE_21((b[2] + a[2]) * 255 - a[2] * b[2]);
87 SET_ALPHA;
88 }
90 // cr = Min ((1 - qa) * cb + ca, (1 - qb) * ca + cb)
91 inline void
92 blend_darken(unsigned char *r, unsigned char const *a, unsigned char const *b)
93 {
94 r[0] = NR_NORMALIZE_21(std::min(NR_COMPOSEPPP_1112(a[0],a[3],b[0]),
95 NR_COMPOSEPPP_1112(b[0],b[3],a[0])));
96 r[1] = NR_NORMALIZE_21(std::min(NR_COMPOSEPPP_1112(a[1],a[3],b[1]),
97 NR_COMPOSEPPP_1112(b[1],b[3],a[1])));
98 r[2] = NR_NORMALIZE_21(std::min(NR_COMPOSEPPP_1112(a[2],a[3],b[2]),
99 NR_COMPOSEPPP_1112(b[2],b[3],a[2])));
100 SET_ALPHA;
101 }
103 // cr = Max ((1 - qa) * cb + ca, (1 - qb) * ca + cb)
104 inline void
105 blend_lighten(unsigned char *r, unsigned char const *a, unsigned char const *b)
106 {
107 r[0] = NR_NORMALIZE_21(std::max(NR_COMPOSEPPP_1112(a[0],a[3],b[0]),
108 NR_COMPOSEPPP_1112(b[0],b[3],a[0])));
109 r[1] = NR_NORMALIZE_21(std::max(NR_COMPOSEPPP_1112(a[1],a[3],b[1]),
110 NR_COMPOSEPPP_1112(b[1],b[3],a[1])));
111 r[2] = NR_NORMALIZE_21(std::max(NR_COMPOSEPPP_1112(a[2],a[3],b[2]),
112 NR_COMPOSEPPP_1112(b[2],b[3],a[2])));
113 SET_ALPHA;
114 }
116 FilterBlend::FilterBlend()
117 : _blend_mode(BLEND_NORMAL),
118 _input2(NR_FILTER_SLOT_NOT_SET)
119 {}
121 FilterPrimitive * FilterBlend::create() {
122 return new FilterBlend();
123 }
125 FilterBlend::~FilterBlend()
126 {}
128 int FilterBlend::render(FilterSlot &slot, FilterUnits const & /*units*/) {
129 NRPixBlock *in1 = slot.get(_input);
130 NRPixBlock *in2 = slot.get(_input2);
131 NRPixBlock *original_in1 = in1;
132 NRPixBlock *original_in2 = in2;
133 NRPixBlock *out;
135 // Bail out if either one of source images is missing
136 if (!in1 || !in2) {
137 g_warning("Missing source image for feBlend (in=%d in2=%d)", _input, _input2);
138 return 1;
139 }
141 out = new NRPixBlock;
142 NRRectL out_area;
143 nr_rect_l_union(&out_area, &in1->area, &in2->area);
144 nr_pixblock_setup_fast(out, NR_PIXBLOCK_MODE_R8G8B8A8P,
145 out_area.x0, out_area.y0, out_area.x1, out_area.y1,
146 true);
148 // Blending modes are defined for premultiplied RGBA values,
149 // thus convert them to that format before blending
150 if (in1->mode != NR_PIXBLOCK_MODE_R8G8B8A8P) {
151 in1 = new NRPixBlock;
152 nr_pixblock_setup_fast(in1, NR_PIXBLOCK_MODE_R8G8B8A8P,
153 original_in1->area.x0, original_in1->area.y0,
154 original_in1->area.x1, original_in1->area.y1,
155 false);
156 nr_blit_pixblock_pixblock(in1, original_in1);
157 }
158 if (in2->mode != NR_PIXBLOCK_MODE_R8G8B8A8P) {
159 in2 = new NRPixBlock;
160 nr_pixblock_setup_fast(in2, NR_PIXBLOCK_MODE_R8G8B8A8P,
161 original_in2->area.x0, original_in2->area.y0,
162 original_in2->area.x1, original_in2->area.y1,
163 false);
164 nr_blit_pixblock_pixblock(in2, original_in2);
165 }
167 /* pixops_mix is defined in display/nr-filter-pixops.h
168 * It mixes the two input images with the function given as template
169 * and places the result in output image.
170 */
171 switch (_blend_mode) {
172 case BLEND_MULTIPLY:
173 pixops_mix<blend_multiply>(*out, *in1, *in2);
174 break;
175 case BLEND_SCREEN:
176 pixops_mix<blend_screen>(*out, *in1, *in2);
177 break;
178 case BLEND_DARKEN:
179 pixops_mix<blend_darken>(*out, *in1, *in2);
180 break;
181 case BLEND_LIGHTEN:
182 pixops_mix<blend_lighten>(*out, *in1, *in2);
183 break;
184 case BLEND_NORMAL:
185 default:
186 pixops_mix<blend_normal>(*out, *in1, *in2);
187 break;
188 }
190 if (in1 != original_in1) {
191 nr_pixblock_release(in1);
192 delete in1;
193 }
194 if (in2 != original_in2) {
195 nr_pixblock_release(in2);
196 delete in2;
197 }
199 out->empty = FALSE;
200 slot.set(_output, out);
202 return 0;
203 }
205 void FilterBlend::set_input(int slot) {
206 _input = slot;
207 }
209 void FilterBlend::set_input(int input, int slot) {
210 if (input == 0) _input = slot;
211 if (input == 1) _input2 = slot;
212 }
214 void FilterBlend::set_mode(FilterBlendMode mode) {
215 if (mode == BLEND_NORMAL || mode == BLEND_MULTIPLY ||
216 mode == BLEND_SCREEN || mode == BLEND_DARKEN ||
217 mode == BLEND_LIGHTEN)
218 {
219 _blend_mode = mode;
220 }
221 }
223 } /* namespace Filters */
224 } /* namespace Inkscape */
226 /*
227 Local Variables:
228 mode:c++
229 c-file-style:"stroustrup"
230 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
231 indent-tabs-mode:nil
232 fill-column:99
233 End:
234 */
235 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :