1 /*
2 * feConvolveMatrix filter primitive renderer
3 *
4 * Authors:
5 * Felipe CorrĂȘa da Silva Sanches <juca@members.fsf.org>
6 * Jasper van de Gronde <th.v.d.gronde@hccnet.nl>
7 *
8 * Copyright (C) 2007,2009 authors
9 *
10 * Released under GNU GPL, read the file 'COPYING' for more information
11 */
13 #include "display/nr-filter-convolve-matrix.h"
14 #include "display/nr-filter-units.h"
15 #include "display/nr-filter-utils.h"
16 #include <vector>
18 namespace Inkscape {
19 namespace Filters {
21 FilterConvolveMatrix::FilterConvolveMatrix()
22 {}
24 FilterPrimitive * FilterConvolveMatrix::create() {
25 return new FilterConvolveMatrix();
26 }
28 FilterConvolveMatrix::~FilterConvolveMatrix()
29 {}
31 template<bool PREMULTIPLIED, bool PRESERVE_ALPHA, bool X_LOWER, bool X_UPPER, bool Y_LOWER, bool Y_UPPER>
32 static inline void convolve2D_XY(unsigned int const x, unsigned int const y, unsigned char *const out_data, unsigned char const *const in_data, unsigned int const width, unsigned int const height, double const *const kernel, unsigned int const orderX, unsigned int const orderY, unsigned int const targetX, unsigned int const targetY, double const bias) {
33 double result_R = 0;
34 double result_G = 0;
35 double result_B = 0;
36 double result_A = 0;
38 unsigned int iBegin = Y_LOWER ? targetY-y : 0; // Note that to prevent signed/unsigned problems this requires that y<=targetY (which is true)
39 unsigned int iEnd = Y_UPPER ? height+targetY-y : orderY; // And this requires that y<=height+targetY (which is trivially true), in addition it should be true that height+targetY-y<=orderY (or equivalently y>=height+targetY-orderY, which is true)
40 unsigned int jBegin = X_LOWER ? targetX-x : 0;
41 unsigned int jEnd = X_UPPER ? width+targetX-x : orderX;
43 for (unsigned int i=iBegin; i<iEnd; i++){
44 for (unsigned int j=jBegin; j<jEnd; j++){
45 unsigned int index = 4*( x - targetX + j + width*(y - targetY + i) );
46 unsigned int kernel_index = orderX-j-1 + orderX*(orderY-i-1);
47 double k = PREMULTIPLIED ? kernel[kernel_index] : in_data[index+3] * kernel[kernel_index];
48 result_R += in_data[index+0] * k;
49 result_G += in_data[index+1] * k;
50 result_B += in_data[index+2] * k;
51 result_A += in_data[index+3] * kernel[kernel_index];
52 }
53 }
55 unsigned int const out_index = 4*( x + width*y );
56 if (PRESERVE_ALPHA) {
57 out_data[out_index+3] = in_data[out_index+3];
58 } else if (PREMULTIPLIED) {
59 out_data[out_index+3] = CLAMP_D_TO_U8(result_A + 255*bias);
60 } else {
61 out_data[out_index+3] = CLAMP_D_TO_U8(result_A + bias);
62 }
63 if (PREMULTIPLIED) {
64 out_data[out_index+0] = CLAMP_D_TO_U8_ALPHA(result_R + out_data[out_index+3]*bias, out_data[out_index+3]); // CLAMP includes rounding!
65 out_data[out_index+1] = CLAMP_D_TO_U8_ALPHA(result_G + out_data[out_index+3]*bias, out_data[out_index+3]);
66 out_data[out_index+2] = CLAMP_D_TO_U8_ALPHA(result_B + out_data[out_index+3]*bias, out_data[out_index+3]);
67 } else if (out_data[out_index+3]==0) {
68 out_data[out_index+0] = 0; // TODO: Is there a more sensible value that can be used here?
69 out_data[out_index+1] = 0;
70 out_data[out_index+2] = 0;
71 } else {
72 out_data[out_index+0] = CLAMP_D_TO_U8(result_R / out_data[out_index+3] + bias); // CLAMP includes rounding!
73 out_data[out_index+1] = CLAMP_D_TO_U8(result_G / out_data[out_index+3] + bias);
74 out_data[out_index+2] = CLAMP_D_TO_U8(result_B / out_data[out_index+3] + bias);
75 }
76 }
78 template<bool PREMULTIPLIED, bool PRESERVE_ALPHA, bool Y_LOWER, bool Y_UPPER>
79 static inline void convolve2D_Y(unsigned int const y, unsigned char *const out_data, unsigned char const *const in_data, unsigned int const width, unsigned int const height, double const *const kernel, unsigned int const orderX, unsigned int const orderY, unsigned int const targetX, unsigned int const targetY, double const bias) {
80 // See convolve2D below for rationale.
82 unsigned int const lowerEnd = std::min(targetX,width);
83 unsigned int const upperBegin = width - std::min<unsigned int>(width,orderX - 1u - targetX);
84 unsigned int const midXBegin = std::min(lowerEnd,upperBegin);
85 unsigned int const midXEnd = std::max(lowerEnd,upperBegin);
87 for (unsigned int x=0; x<midXBegin; x++) {
88 convolve2D_XY<PREMULTIPLIED,PRESERVE_ALPHA,true,false,Y_LOWER,Y_UPPER>(x, y, out_data, in_data, width, height, kernel, orderX, orderY, targetX, targetY, bias);
89 }
90 if (lowerEnd==upperBegin) {
91 // Do nothing, empty mid section
92 } else if (lowerEnd<upperBegin) {
93 // In the middle no bounds have to be adjusted
94 for (unsigned int x=midXBegin; x<midXEnd; x++) {
95 convolve2D_XY<PREMULTIPLIED,PRESERVE_ALPHA,false,false,Y_LOWER,Y_UPPER>(x, y, out_data, in_data, width, height, kernel, orderX, orderY, targetX, targetY, bias);
96 }
97 } else {
98 // In the middle both bounds have to be adjusted
99 for (unsigned int x=midXBegin; x<midXEnd; x++) {
100 convolve2D_XY<PREMULTIPLIED,PRESERVE_ALPHA,true,true,Y_LOWER,Y_UPPER>(x, y, out_data, in_data, width, height, kernel, orderX, orderY, targetX, targetY, bias);
101 }
102 }
103 for (unsigned int x=midXEnd; x<width; x++) {
104 convolve2D_XY<PREMULTIPLIED,PRESERVE_ALPHA,false,true,Y_LOWER,Y_UPPER>(x, y, out_data, in_data, width, height, kernel, orderX, orderY, targetX, targetY, bias);
105 }
106 }
108 template<bool PREMULTIPLIED, bool PRESERVE_ALPHA>
109 static void convolve2D(unsigned char *const out_data, unsigned char const *const in_data, unsigned int const width, unsigned int const height, double const *const kernel, unsigned int const orderX, unsigned int const orderY, unsigned int const targetX, unsigned int const targetY, double const _bias) {
110 double const bias = PREMULTIPLIED ? _bias : 255*_bias; // If we're using non-premultiplied values the bias is always multiplied by 255.
112 // For the middle section it should hold that (for all i such that 0<=i<orderY):
113 // 0 <= y - targetY + i < height
114 // targetY <= y && y < height + targetY - orderY + 1
115 // In other words, for y<targetY i's lower bound needs to be adjusted and for y>=height+targetY-orderY+1 i's upper bound needs to be adjusted.
117 unsigned int const lowerEnd = std::min(targetY,height);
118 unsigned int const upperBegin = height - std::min<unsigned int>(height,orderY - 1u - targetY);
119 unsigned int const midYBegin = std::min(lowerEnd,upperBegin);
120 unsigned int const midYEnd = std::max(lowerEnd,upperBegin);
122 for (unsigned int y=0; y<midYBegin; y++) {
123 convolve2D_Y<PREMULTIPLIED,PRESERVE_ALPHA,true,false>(y, out_data, in_data, width, height, kernel, orderX, orderY, targetX, targetY, bias);
124 }
125 if (lowerEnd==upperBegin) {
126 // Do nothing, empty mid section
127 } else if (lowerEnd<upperBegin) {
128 // In the middle no bounds have to be adjusted
129 for (unsigned int y=midYBegin; y<midYEnd; y++) {
130 convolve2D_Y<PREMULTIPLIED,PRESERVE_ALPHA,false,false>(y, out_data, in_data, width, height, kernel, orderX, orderY, targetX, targetY, bias);
131 }
132 } else {
133 // In the middle both bounds have to be adjusted
134 for (unsigned int y=midYBegin; y<midYEnd; y++) {
135 convolve2D_Y<PREMULTIPLIED,PRESERVE_ALPHA,true,true>(y, out_data, in_data, width, height, kernel, orderX, orderY, targetX, targetY, bias);
136 }
137 }
138 for (unsigned int y=midYEnd; y<height; y++) {
139 convolve2D_Y<PREMULTIPLIED,PRESERVE_ALPHA,false,true>(y, out_data, in_data, width, height, kernel, orderX, orderY, targetX, targetY, bias);
140 }
141 }
143 int FilterConvolveMatrix::render(FilterSlot &slot, FilterUnits const &/*units*/) {
144 NRPixBlock *in = slot.get(_input);
145 if (!in) {
146 g_warning("Missing source image for feConvolveMatrix (in=%d)", _input);
147 return 1;
148 }
149 if (orderX<=0 || orderY<=0) {
150 g_warning("Empty kernel!");
151 return 1;
152 }
153 if (targetX<0 || targetX>=orderX || targetY<0 || targetY>=orderY) {
154 g_warning("Invalid target!");
155 return 1;
156 }
157 if (kernelMatrix.size()!=(unsigned int)(orderX*orderY)) {
158 g_warning("kernelMatrix does not have orderX*orderY elements!");
159 return 1;
160 }
162 if (bias!=0) {
163 g_warning("It is unknown whether Inkscape's implementation of bias in feConvolveMatrix is correct!");
164 // The SVG specification implies that feConvolveMatrix is defined for premultiplied colors (which makes sense).
165 // It also says that bias should simply be added to the result for each color (without taking the alpha into account)
166 // However, it also says that one purpose of bias is "to have .5 gray value be the zero response of the filter".
167 // It seems sensible to indeed support the latter behaviour instead of the former, but this does appear to go against the standard.
168 // Note that Batik simply does not support bias!=0
169 }
170 if (edgeMode!=CONVOLVEMATRIX_EDGEMODE_NONE) {
171 g_warning("Inkscape only supports edgeMode=\"none\" (and a filter uses a different one)!");
172 // Note that to properly support edgeMode the interaction with area_enlarge should be well understood (and probably something needs to change)
173 // area_enlarge should NOT let Inkscape enlarge the area beyond the filter area, it should only enlarge the rendered area if a part of the object is rendered to make it overlapping (enough) with adjacent parts.
174 }
176 NRPixBlock *out = new NRPixBlock;
178 nr_pixblock_setup_fast(out, in->mode,
179 in->area.x0, in->area.y0, in->area.x1, in->area.y1,
180 true);
182 unsigned char *in_data = NR_PIXBLOCK_PX(in);
183 unsigned char *out_data = NR_PIXBLOCK_PX(out);
185 unsigned int const width = in->area.x1 - in->area.x0;
186 unsigned int const height = in->area.y1 - in->area.y0;
188 // Set up predivided kernel matrix
189 std::vector<double> kernel(kernelMatrix);
190 for(size_t i=0; i<kernel.size(); i++) {
191 kernel[i] /= divisor; // The code that creates this object makes sure that divisor != 0
192 }
194 if (in->mode==NR_PIXBLOCK_MODE_R8G8B8A8P) {
195 if (preserveAlpha) {
196 convolve2D<true,true>(out_data, in_data, width, height, &kernel.front(), orderX, orderY, targetX, targetY, bias);
197 } else {
198 convolve2D<true,false>(out_data, in_data, width, height, &kernel.front(), orderX, orderY, targetX, targetY, bias);
199 }
200 } else {
201 if (preserveAlpha) {
202 convolve2D<false,true>(out_data, in_data, width, height, &kernel.front(), orderX, orderY, targetX, targetY, bias);
203 } else {
204 convolve2D<false,false>(out_data, in_data, width, height, &kernel.front(), orderX, orderY, targetX, targetY, bias);
205 }
206 }
208 out->empty = FALSE;
209 slot.set(_output, out);
210 return 0;
211 }
213 void FilterConvolveMatrix::set_targetX(int coord) {
214 targetX = coord;
215 }
217 void FilterConvolveMatrix::set_targetY(int coord) {
218 targetY = coord;
219 }
221 void FilterConvolveMatrix::set_orderX(int coord) {
222 orderX = coord;
223 }
225 void FilterConvolveMatrix::set_orderY(int coord) {
226 orderY = coord;
227 }
229 void FilterConvolveMatrix::set_divisor(double d) {
230 divisor = d;
231 }
233 void FilterConvolveMatrix::set_bias(double b) {
234 bias = b;
235 }
237 void FilterConvolveMatrix::set_kernelMatrix(std::vector<gdouble> &km) {
238 kernelMatrix = km;
239 }
241 void FilterConvolveMatrix::set_edgeMode(FilterConvolveMatrixEdgeMode mode){
242 edgeMode = mode;
243 }
245 void FilterConvolveMatrix::set_preserveAlpha(bool pa){
246 preserveAlpha = pa;
247 }
249 void FilterConvolveMatrix::area_enlarge(NRRectL &area, Geom::Matrix const &/*trans*/)
250 {
251 //Seems to me that since this filter's operation is resolution dependent,
252 // some spurious pixels may still appear at the borders when low zooming or rotating. Needs a better fix.
253 area.x0 -= targetX;
254 area.y0 -= targetY;
255 area.x1 += orderX - targetX - 1; // This makes sure the last row/column in the original image corresponds to the last row/column in the new image that can be convolved without adjusting the boundary conditions).
256 area.y1 += orderY - targetY - 1;
257 }
259 FilterTraits FilterConvolveMatrix::get_input_traits() {
260 return TRAIT_PARALLER;
261 }
263 } /* namespace Filters */
264 } /* namespace Inkscape */
266 /*
267 Local Variables:
268 mode:c++
269 c-file-style:"stroustrup"
270 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
271 indent-tabs-mode:nil
272 fill-column:99
273 End:
274 */
275 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :