1 /*
2 * Copyright (C) 2004, 2005, 2006, 2007 Nikolas Zimmermann <zimmermann@kde.org>
3 * Copyright (C) 2004, 2005 Rob Buis <buis@kde.org>
4 * Copyright (C) 2005 Eric Seidel <eric@webkit.org>
5 * Copyright (C) 2009 Dirk Schulze <krit@webkit.org>
6 * Copyright (C) 2010 Zoltan Herczeg <zherczeg@webkit.org>
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Library General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Library General Public License for more details.
17 *
18 * You should have received a copy of the GNU Library General Public License
19 * along with this library; see the file COPYING.LIB. If not, write to
20 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 * Boston, MA 02110-1301, USA.
22 */
23
24 #include "config.h"
25
26 #if ENABLE(FILTERS)
27 #include "FEConvolveMatrix.h"
28
29 #include "Filter.h"
30 #include "RenderTreeAsText.h"
31 #include "TextStream.h"
32
33 #include <wtf/ByteArray.h>
34
35 namespace WebCore {
36
FEConvolveMatrix(Filter * filter,const IntSize & kernelSize,float divisor,float bias,const IntPoint & targetOffset,EdgeModeType edgeMode,const FloatPoint & kernelUnitLength,bool preserveAlpha,const Vector<float> & kernelMatrix)37 FEConvolveMatrix::FEConvolveMatrix(Filter* filter, const IntSize& kernelSize,
38 float divisor, float bias, const IntPoint& targetOffset, EdgeModeType edgeMode,
39 const FloatPoint& kernelUnitLength, bool preserveAlpha, const Vector<float>& kernelMatrix)
40 : FilterEffect(filter)
41 , m_kernelSize(kernelSize)
42 , m_divisor(divisor)
43 , m_bias(bias)
44 , m_targetOffset(targetOffset)
45 , m_edgeMode(edgeMode)
46 , m_kernelUnitLength(kernelUnitLength)
47 , m_preserveAlpha(preserveAlpha)
48 , m_kernelMatrix(kernelMatrix)
49 {
50 }
51
create(Filter * filter,const IntSize & kernelSize,float divisor,float bias,const IntPoint & targetOffset,EdgeModeType edgeMode,const FloatPoint & kernelUnitLength,bool preserveAlpha,const Vector<float> & kernelMatrix)52 PassRefPtr<FEConvolveMatrix> FEConvolveMatrix::create(Filter* filter, const IntSize& kernelSize,
53 float divisor, float bias, const IntPoint& targetOffset, EdgeModeType edgeMode,
54 const FloatPoint& kernelUnitLength, bool preserveAlpha, const Vector<float>& kernelMatrix)
55 {
56 return adoptRef(new FEConvolveMatrix(filter, kernelSize, divisor, bias, targetOffset, edgeMode, kernelUnitLength,
57 preserveAlpha, kernelMatrix));
58 }
59
60
kernelSize() const61 IntSize FEConvolveMatrix::kernelSize() const
62 {
63 return m_kernelSize;
64 }
65
setKernelSize(const IntSize & kernelSize)66 void FEConvolveMatrix::setKernelSize(const IntSize& kernelSize)
67 {
68 m_kernelSize = kernelSize;
69 }
70
kernel() const71 const Vector<float>& FEConvolveMatrix::kernel() const
72 {
73 return m_kernelMatrix;
74 }
75
setKernel(const Vector<float> & kernel)76 void FEConvolveMatrix::setKernel(const Vector<float>& kernel)
77 {
78 m_kernelMatrix = kernel;
79 }
80
divisor() const81 float FEConvolveMatrix::divisor() const
82 {
83 return m_divisor;
84 }
85
setDivisor(float divisor)86 bool FEConvolveMatrix::setDivisor(float divisor)
87 {
88 if (m_divisor == divisor)
89 return false;
90 m_divisor = divisor;
91 return true;
92 }
93
bias() const94 float FEConvolveMatrix::bias() const
95 {
96 return m_bias;
97 }
98
setBias(float bias)99 bool FEConvolveMatrix::setBias(float bias)
100 {
101 if (m_bias == bias)
102 return false;
103 m_bias = bias;
104 return true;
105 }
106
targetOffset() const107 IntPoint FEConvolveMatrix::targetOffset() const
108 {
109 return m_targetOffset;
110 }
111
setTargetOffset(const IntPoint & targetOffset)112 bool FEConvolveMatrix::setTargetOffset(const IntPoint& targetOffset)
113 {
114 if (m_targetOffset == targetOffset)
115 return false;
116 m_targetOffset = targetOffset;
117 return true;
118 }
119
edgeMode() const120 EdgeModeType FEConvolveMatrix::edgeMode() const
121 {
122 return m_edgeMode;
123 }
124
setEdgeMode(EdgeModeType edgeMode)125 bool FEConvolveMatrix::setEdgeMode(EdgeModeType edgeMode)
126 {
127 if (m_edgeMode == edgeMode)
128 return false;
129 m_edgeMode = edgeMode;
130 return true;
131 }
132
kernelUnitLength() const133 FloatPoint FEConvolveMatrix::kernelUnitLength() const
134 {
135 return m_kernelUnitLength;
136 }
137
setKernelUnitLength(const FloatPoint & kernelUnitLength)138 bool FEConvolveMatrix::setKernelUnitLength(const FloatPoint& kernelUnitLength)
139 {
140 if (m_kernelUnitLength == kernelUnitLength)
141 return false;
142 m_kernelUnitLength = kernelUnitLength;
143 return true;
144 }
145
preserveAlpha() const146 bool FEConvolveMatrix::preserveAlpha() const
147 {
148 return m_preserveAlpha;
149 }
150
setPreserveAlpha(bool preserveAlpha)151 bool FEConvolveMatrix::setPreserveAlpha(bool preserveAlpha)
152 {
153 if (m_preserveAlpha == preserveAlpha)
154 return false;
155 m_preserveAlpha = preserveAlpha;
156 return true;
157 }
158
159 /*
160 -----------------------------------
161 ConvolveMatrix implementation
162 -----------------------------------
163
164 The image rectangle is split in the following way:
165
166 +---------------------+
167 | A |
168 +---------------------+
169 | | | |
170 | B | C | D |
171 | | | |
172 +---------------------+
173 | E |
174 +---------------------+
175
176 Where region C contains those pixels, whose values
177 can be calculated without crossing the edge of the rectangle.
178
179 Example:
180 Image size: width: 10, height: 10
181
182 Order (kernel matrix size): width: 3, height 4
183 Target: x:1, y:3
184
185 The following figure shows the target inside the kernel matrix:
186
187 ...
188 ...
189 ...
190 .X.
191
192 The regions in this case are the following:
193 Note: (x1, y1) top-left and (x2, y2) is the bottom-right corner
194 Note: row x2 and column y2 is not part of the region
195 only those (x, y) pixels, where x1 <= x < x2 and y1 <= y < y2
196
197 Region A: x1: 0, y1: 0, x2: 10, y2: 3
198 Region B: x1: 0, y1: 3, x2: 1, y2: 10
199 Region C: x1: 1, y1: 3, x2: 9, y2: 10
200 Region D: x1: 9, y1: 3, x2: 10, y2: 10
201 Region E: x1: 0, y1: 10, x2: 10, y2: 10 (empty region)
202
203 Since region C (often) contains most of the pixels, we implemented
204 a fast algoritm to calculate these values, called fastSetInteriorPixels.
205 For other regions, fastSetOuterPixels is used, which calls getPixelValue,
206 to handle pixels outside of the image. In a rare situations, when
207 kernel matrix is bigger than the image, all pixels are calculated by this
208 function.
209
210 Although these two functions have lot in common, I decided not to make
211 common a template for them, since there are key differences as well,
212 and would make it really hard to understand.
213 */
214
clampRGBAValue(float channel,unsigned char max=255)215 static ALWAYS_INLINE unsigned char clampRGBAValue(float channel, unsigned char max = 255)
216 {
217 if (channel <= 0)
218 return 0;
219 if (channel >= max)
220 return max;
221 return channel;
222 }
223
224 template<bool preserveAlphaValues>
setDestinationPixels(ByteArray * image,int & pixel,float * totals,float divisor,float bias,ByteArray * src)225 ALWAYS_INLINE void setDestinationPixels(ByteArray* image, int& pixel, float* totals, float divisor, float bias, ByteArray* src)
226 {
227 unsigned char maxAlpha = preserveAlphaValues ? 255 : clampRGBAValue(totals[3] / divisor + bias);
228 for (int i = 0; i < 3; ++i)
229 image->set(pixel++, clampRGBAValue(totals[i] / divisor + bias, maxAlpha));
230
231 if (preserveAlphaValues) {
232 image->set(pixel, src->get(pixel));
233 ++pixel;
234 } else
235 image->set(pixel++, maxAlpha);
236 }
237
238 // Only for region C
239 template<bool preserveAlphaValues>
fastSetInteriorPixels(PaintingData & paintingData,int clipRight,int clipBottom)240 ALWAYS_INLINE void FEConvolveMatrix::fastSetInteriorPixels(PaintingData& paintingData, int clipRight, int clipBottom)
241 {
242 // edge mode does not affect these pixels
243 int pixel = (m_targetOffset.y() * paintingData.width + m_targetOffset.x()) * 4;
244 int startKernelPixel = 0;
245 int kernelIncrease = clipRight * 4;
246 int xIncrease = (m_kernelSize.width() - 1) * 4;
247 // Contains the sum of rgb(a) components
248 float totals[3 + (preserveAlphaValues ? 0 : 1)];
249
250 // m_divisor cannot be 0, SVGFEConvolveMatrixElement ensures this
251 ASSERT(m_divisor);
252
253 for (int y = clipBottom + 1; y > 0; --y) {
254 for (int x = clipRight + 1; x > 0; --x) {
255 int kernelValue = m_kernelMatrix.size() - 1;
256 int kernelPixel = startKernelPixel;
257 int width = m_kernelSize.width();
258
259 totals[0] = 0;
260 totals[1] = 0;
261 totals[2] = 0;
262 if (!preserveAlphaValues)
263 totals[3] = 0;
264
265 while (kernelValue >= 0) {
266 totals[0] += m_kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->get(kernelPixel++));
267 totals[1] += m_kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->get(kernelPixel++));
268 totals[2] += m_kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->get(kernelPixel++));
269 if (!preserveAlphaValues)
270 totals[3] += m_kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->get(kernelPixel));
271 ++kernelPixel;
272 --kernelValue;
273 if (!--width) {
274 kernelPixel += kernelIncrease;
275 width = m_kernelSize.width();
276 }
277 }
278
279 setDestinationPixels<preserveAlphaValues>(paintingData.dstPixelArray, pixel, totals, m_divisor, paintingData.bias, paintingData.srcPixelArray);
280 startKernelPixel += 4;
281 }
282 pixel += xIncrease;
283 startKernelPixel += xIncrease;
284 }
285 }
286
getPixelValue(PaintingData & paintingData,int x,int y)287 ALWAYS_INLINE int FEConvolveMatrix::getPixelValue(PaintingData& paintingData, int x, int y)
288 {
289 if (x >= 0 && x < paintingData.width && x >= 0 && y < paintingData.height)
290 return (y * paintingData.width + x) << 2;
291
292 switch (m_edgeMode) {
293 default: // EDGEMODE_NONE
294 return -1;
295 case EDGEMODE_DUPLICATE:
296 if (x < 0)
297 x = 0;
298 else if (x >= paintingData.width)
299 x = paintingData.width - 1;
300 if (y < 0)
301 y = 0;
302 else if (y >= paintingData.height)
303 y = paintingData.height - 1;
304 return (y * paintingData.width + x) << 2;
305 case EDGEMODE_WRAP:
306 while (x < 0)
307 x += paintingData.width;
308 x %= paintingData.width;
309 while (y < 0)
310 y += paintingData.height;
311 y %= paintingData.height;
312 return (y * paintingData.width + x) << 2;
313 }
314 }
315
316 // For other regions than C
317 template<bool preserveAlphaValues>
fastSetOuterPixels(PaintingData & paintingData,int x1,int y1,int x2,int y2)318 void FEConvolveMatrix::fastSetOuterPixels(PaintingData& paintingData, int x1, int y1, int x2, int y2)
319 {
320 int pixel = (y1 * paintingData.width + x1) * 4;
321 int height = y2 - y1;
322 int width = x2 - x1;
323 int beginKernelPixelX = x1 - m_targetOffset.x();
324 int startKernelPixelX = beginKernelPixelX;
325 int startKernelPixelY = y1 - m_targetOffset.y();
326 int xIncrease = (paintingData.width - width) * 4;
327 // Contains the sum of rgb(a) components
328 float totals[3 + (preserveAlphaValues ? 0 : 1)];
329
330 // m_divisor cannot be 0, SVGFEConvolveMatrixElement ensures this
331 ASSERT(m_divisor);
332
333 for (int y = height; y > 0; --y) {
334 for (int x = width; x > 0; --x) {
335 int kernelValue = m_kernelMatrix.size() - 1;
336 int kernelPixelX = startKernelPixelX;
337 int kernelPixelY = startKernelPixelY;
338 int width = m_kernelSize.width();
339
340 totals[0] = 0;
341 totals[1] = 0;
342 totals[2] = 0;
343 if (!preserveAlphaValues)
344 totals[3] = 0;
345
346 while (kernelValue >= 0) {
347 int pixelIndex = getPixelValue(paintingData, kernelPixelX, kernelPixelY);
348 if (pixelIndex >= 0) {
349 totals[0] += m_kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->get(pixelIndex));
350 totals[1] += m_kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->get(pixelIndex + 1));
351 totals[2] += m_kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->get(pixelIndex + 2));
352 }
353 if (!preserveAlphaValues && pixelIndex >= 0)
354 totals[3] += m_kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->get(pixelIndex + 3));
355 ++kernelPixelX;
356 --kernelValue;
357 if (!--width) {
358 kernelPixelX = startKernelPixelX;
359 ++kernelPixelY;
360 width = m_kernelSize.width();
361 }
362 }
363
364 setDestinationPixels<preserveAlphaValues>(paintingData.dstPixelArray, pixel, totals, m_divisor, paintingData.bias, paintingData.srcPixelArray);
365 ++startKernelPixelX;
366 }
367 pixel += xIncrease;
368 startKernelPixelX = beginKernelPixelX;
369 ++startKernelPixelY;
370 }
371 }
372
setInteriorPixels(PaintingData & paintingData,int clipRight,int clipBottom)373 ALWAYS_INLINE void FEConvolveMatrix::setInteriorPixels(PaintingData& paintingData, int clipRight, int clipBottom)
374 {
375 // Must be implemented here, since it refers another ALWAYS_INLINE
376 // function, which defined in this C++ source file as well
377 if (m_preserveAlpha)
378 fastSetInteriorPixels<true>(paintingData, clipRight, clipBottom);
379 else
380 fastSetInteriorPixels<false>(paintingData, clipRight, clipBottom);
381 }
382
setOuterPixels(PaintingData & paintingData,int x1,int y1,int x2,int y2)383 ALWAYS_INLINE void FEConvolveMatrix::setOuterPixels(PaintingData& paintingData, int x1, int y1, int x2, int y2)
384 {
385 // Although this function can be moved to the header, it is implemented here
386 // because setInteriorPixels is also implemented here
387 if (m_preserveAlpha)
388 fastSetOuterPixels<true>(paintingData, x1, y1, x2, y2);
389 else
390 fastSetOuterPixels<false>(paintingData, x1, y1, x2, y2);
391 }
392
apply()393 void FEConvolveMatrix::apply()
394 {
395 if (hasResult())
396 return;
397 FilterEffect* in = inputEffect(0);
398 in->apply();
399 if (!in->hasResult())
400 return;
401
402 ByteArray* resultImage;
403 if (m_preserveAlpha)
404 resultImage = createUnmultipliedImageResult();
405 else
406 resultImage = createPremultipliedImageResult();
407 if (!resultImage)
408 return;
409
410 IntRect effectDrawingRect = requestedRegionOfInputImageData(in->absolutePaintRect());
411
412 RefPtr<ByteArray> srcPixelArray;
413 if (m_preserveAlpha)
414 srcPixelArray = in->asUnmultipliedImage(effectDrawingRect);
415 else
416 srcPixelArray = in->asPremultipliedImage(effectDrawingRect);
417
418 IntSize paintSize = absolutePaintRect().size();
419 PaintingData paintingData;
420 paintingData.srcPixelArray = srcPixelArray.get();
421 paintingData.dstPixelArray = resultImage;
422 paintingData.width = paintSize.width();
423 paintingData.height = paintSize.height();
424 paintingData.bias = m_bias * 255;
425
426 // Drawing fully covered pixels
427 int clipRight = paintSize.width() - m_kernelSize.width();
428 int clipBottom = paintSize.height() - m_kernelSize.height();
429
430 if (clipRight >= 0 && clipBottom >= 0) {
431 setInteriorPixels(paintingData, clipRight, clipBottom);
432
433 clipRight += m_targetOffset.x() + 1;
434 clipBottom += m_targetOffset.y() + 1;
435 if (m_targetOffset.y() > 0)
436 setOuterPixels(paintingData, 0, 0, paintSize.width(), m_targetOffset.y());
437 if (clipBottom < paintSize.height())
438 setOuterPixels(paintingData, 0, clipBottom, paintSize.width(), paintSize.height());
439 if (m_targetOffset.x() > 0)
440 setOuterPixels(paintingData, 0, m_targetOffset.y(), m_targetOffset.x(), clipBottom);
441 if (clipRight < paintSize.width())
442 setOuterPixels(paintingData, clipRight, m_targetOffset.y(), paintSize.width(), clipBottom);
443 } else {
444 // Rare situation, not optimizied for speed
445 setOuterPixels(paintingData, 0, 0, paintSize.width(), paintSize.height());
446 }
447 }
448
dump()449 void FEConvolveMatrix::dump()
450 {
451 }
452
operator <<(TextStream & ts,const EdgeModeType & type)453 static TextStream& operator<<(TextStream& ts, const EdgeModeType& type)
454 {
455 switch (type) {
456 case EDGEMODE_UNKNOWN:
457 ts << "UNKNOWN";
458 break;
459 case EDGEMODE_DUPLICATE:
460 ts << "DUPLICATE";
461 break;
462 case EDGEMODE_WRAP:
463 ts << "WRAP";
464 break;
465 case EDGEMODE_NONE:
466 ts << "NONE";
467 break;
468 }
469 return ts;
470 }
471
externalRepresentation(TextStream & ts,int indent) const472 TextStream& FEConvolveMatrix::externalRepresentation(TextStream& ts, int indent) const
473 {
474 writeIndent(ts, indent);
475 ts << "[feConvolveMatrix";
476 FilterEffect::externalRepresentation(ts);
477 ts << " order=\"" << m_kernelSize << "\" "
478 << "kernelMatrix=\"" << m_kernelMatrix << "\" "
479 << "divisor=\"" << m_divisor << "\" "
480 << "bias=\"" << m_bias << "\" "
481 << "target=\"" << m_targetOffset << "\" "
482 << "edgeMode=\"" << m_edgeMode << "\" "
483 << "kernelUnitLength=\"" << m_kernelUnitLength << "\" "
484 << "preserveAlpha=\"" << m_preserveAlpha << "\"]\n";
485 inputEffect(0)->externalRepresentation(ts, indent + 1);
486 return ts;
487 }
488
489 }; // namespace WebCore
490
491 #endif // ENABLE(FILTERS)
492