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