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