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>
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.
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.
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.
27 #include "FEConvolveMatrix.h"
30 #include "RenderTreeAsText.h"
31 #include "TextStream.h"
33 #include <wtf/ByteArray.h>
34 #include <wtf/ParallelJobs.h>
38 FEConvolveMatrix::FEConvolveMatrix(Filter* filter, const IntSize& kernelSize,
39 float divisor, float bias, const IntPoint& targetOffset, EdgeModeType edgeMode,
40 const FloatPoint& kernelUnitLength, bool preserveAlpha, const Vector<float>& kernelMatrix)
41 : FilterEffect(filter)
42 , m_kernelSize(kernelSize)
45 , m_targetOffset(targetOffset)
46 , m_edgeMode(edgeMode)
47 , m_kernelUnitLength(kernelUnitLength)
48 , m_preserveAlpha(preserveAlpha)
49 , m_kernelMatrix(kernelMatrix)
53 PassRefPtr<FEConvolveMatrix> FEConvolveMatrix::create(Filter* filter, const IntSize& kernelSize,
54 float divisor, float bias, const IntPoint& targetOffset, EdgeModeType edgeMode,
55 const FloatPoint& kernelUnitLength, bool preserveAlpha, const Vector<float>& kernelMatrix)
57 return adoptRef(new FEConvolveMatrix(filter, kernelSize, divisor, bias, targetOffset, edgeMode, kernelUnitLength,
58 preserveAlpha, kernelMatrix));
62 IntSize FEConvolveMatrix::kernelSize() const
67 void FEConvolveMatrix::setKernelSize(const IntSize& kernelSize)
69 m_kernelSize = kernelSize;
72 const Vector<float>& FEConvolveMatrix::kernel() const
74 return m_kernelMatrix;
77 void FEConvolveMatrix::setKernel(const Vector<float>& kernel)
79 m_kernelMatrix = kernel;
82 float FEConvolveMatrix::divisor() const
87 bool FEConvolveMatrix::setDivisor(float divisor)
89 if (m_divisor == divisor)
95 float FEConvolveMatrix::bias() const
100 bool FEConvolveMatrix::setBias(float bias)
108 IntPoint FEConvolveMatrix::targetOffset() const
110 return m_targetOffset;
113 bool FEConvolveMatrix::setTargetOffset(const IntPoint& targetOffset)
115 if (m_targetOffset == targetOffset)
117 m_targetOffset = targetOffset;
121 EdgeModeType FEConvolveMatrix::edgeMode() const
126 bool FEConvolveMatrix::setEdgeMode(EdgeModeType edgeMode)
128 if (m_edgeMode == edgeMode)
130 m_edgeMode = edgeMode;
134 FloatPoint FEConvolveMatrix::kernelUnitLength() const
136 return m_kernelUnitLength;
139 bool FEConvolveMatrix::setKernelUnitLength(const FloatPoint& kernelUnitLength)
141 if (m_kernelUnitLength == kernelUnitLength)
143 m_kernelUnitLength = kernelUnitLength;
147 bool FEConvolveMatrix::preserveAlpha() const
149 return m_preserveAlpha;
152 bool FEConvolveMatrix::setPreserveAlpha(bool preserveAlpha)
154 if (m_preserveAlpha == preserveAlpha)
156 m_preserveAlpha = preserveAlpha;
161 -----------------------------------
162 ConvolveMatrix implementation
163 -----------------------------------
165 The image rectangle is split in the following way:
167 +---------------------+
169 +---------------------+
173 +---------------------+
175 +---------------------+
177 Where region C contains those pixels, whose values
178 can be calculated without crossing the edge of the rectangle.
181 Image size: width: 10, height: 10
183 Order (kernel matrix size): width: 3, height 4
186 The following figure shows the target inside the kernel matrix:
193 The regions in this case are the following:
194 Note: (x1, y1) top-left and (x2, y2) is the bottom-right corner
195 Note: row x2 and column y2 is not part of the region
196 only those (x, y) pixels, where x1 <= x < x2 and y1 <= y < y2
198 Region A: x1: 0, y1: 0, x2: 10, y2: 3
199 Region B: x1: 0, y1: 3, x2: 1, y2: 10
200 Region C: x1: 1, y1: 3, x2: 9, y2: 10
201 Region D: x1: 9, y1: 3, x2: 10, y2: 10
202 Region E: x1: 0, y1: 10, x2: 10, y2: 10 (empty region)
204 Since region C (often) contains most of the pixels, we implemented
205 a fast algoritm to calculate these values, called fastSetInteriorPixels.
206 For other regions, fastSetOuterPixels is used, which calls getPixelValue,
207 to handle pixels outside of the image. In a rare situations, when
208 kernel matrix is bigger than the image, all pixels are calculated by this
211 Although these two functions have lot in common, I decided not to make
212 common a template for them, since there are key differences as well,
213 and would make it really hard to understand.
216 static ALWAYS_INLINE unsigned char clampRGBAValue(float channel, unsigned char max = 255)
225 template<bool preserveAlphaValues>
226 ALWAYS_INLINE void setDestinationPixels(ByteArray* image, int& pixel, float* totals, float divisor, float bias, ByteArray* src)
228 unsigned char maxAlpha = preserveAlphaValues ? 255 : clampRGBAValue(totals[3] / divisor + bias);
229 for (int i = 0; i < 3; ++i)
230 image->set(pixel++, clampRGBAValue(totals[i] / divisor + bias, maxAlpha));
232 if (preserveAlphaValues) {
233 image->set(pixel, src->get(pixel));
236 image->set(pixel++, maxAlpha);
240 template<bool preserveAlphaValues>
241 ALWAYS_INLINE void FEConvolveMatrix::fastSetInteriorPixels(PaintingData& paintingData, int clipRight, int clipBottom, int yStart, int yEnd)
243 // edge mode does not affect these pixels
244 int pixel = (m_targetOffset.y() * paintingData.width + m_targetOffset.x()) * 4;
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)];
250 // m_divisor cannot be 0, SVGFEConvolveMatrixElement ensures this
253 // Skip the first '(clipBottom - yEnd)' lines
254 pixel += (clipBottom - yEnd) * (xIncrease + (clipRight + 1) * (preserveAlphaValues ? 3 : 4));
255 int startKernelPixel = (clipBottom - yEnd) * (xIncrease + (clipRight + 1) * 4);
257 for (int y = yEnd + 1; y > yStart; --y) {
258 for (int x = clipRight + 1; x > 0; --x) {
259 int kernelValue = m_kernelMatrix.size() - 1;
260 int kernelPixel = startKernelPixel;
261 int width = m_kernelSize.width();
266 if (!preserveAlphaValues)
269 while (kernelValue >= 0) {
270 totals[0] += m_kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->get(kernelPixel++));
271 totals[1] += m_kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->get(kernelPixel++));
272 totals[2] += m_kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->get(kernelPixel++));
273 if (!preserveAlphaValues)
274 totals[3] += m_kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->get(kernelPixel));
278 kernelPixel += kernelIncrease;
279 width = m_kernelSize.width();
283 setDestinationPixels<preserveAlphaValues>(paintingData.dstPixelArray, pixel, totals, m_divisor, paintingData.bias, paintingData.srcPixelArray);
284 startKernelPixel += 4;
287 startKernelPixel += xIncrease;
291 ALWAYS_INLINE int FEConvolveMatrix::getPixelValue(PaintingData& paintingData, int x, int y)
293 if (x >= 0 && x < paintingData.width && y >= 0 && y < paintingData.height)
294 return (y * paintingData.width + x) << 2;
296 switch (m_edgeMode) {
297 default: // EDGEMODE_NONE
299 case EDGEMODE_DUPLICATE:
302 else if (x >= paintingData.width)
303 x = paintingData.width - 1;
306 else if (y >= paintingData.height)
307 y = paintingData.height - 1;
308 return (y * paintingData.width + x) << 2;
311 x += paintingData.width;
312 x %= paintingData.width;
314 y += paintingData.height;
315 y %= paintingData.height;
316 return (y * paintingData.width + x) << 2;
320 // For other regions than C
321 template<bool preserveAlphaValues>
322 void FEConvolveMatrix::fastSetOuterPixels(PaintingData& paintingData, int x1, int y1, int x2, int y2)
324 int pixel = (y1 * paintingData.width + x1) * 4;
325 int height = y2 - y1;
327 int beginKernelPixelX = x1 - m_targetOffset.x();
328 int startKernelPixelX = beginKernelPixelX;
329 int startKernelPixelY = y1 - m_targetOffset.y();
330 int xIncrease = (paintingData.width - width) * 4;
331 // Contains the sum of rgb(a) components
332 float totals[3 + (preserveAlphaValues ? 0 : 1)];
334 // m_divisor cannot be 0, SVGFEConvolveMatrixElement ensures this
337 for (int y = height; y > 0; --y) {
338 for (int x = width; x > 0; --x) {
339 int kernelValue = m_kernelMatrix.size() - 1;
340 int kernelPixelX = startKernelPixelX;
341 int kernelPixelY = startKernelPixelY;
342 int width = m_kernelSize.width();
347 if (!preserveAlphaValues)
350 while (kernelValue >= 0) {
351 int pixelIndex = getPixelValue(paintingData, kernelPixelX, kernelPixelY);
352 if (pixelIndex >= 0) {
353 totals[0] += m_kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->get(pixelIndex));
354 totals[1] += m_kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->get(pixelIndex + 1));
355 totals[2] += m_kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->get(pixelIndex + 2));
357 if (!preserveAlphaValues && pixelIndex >= 0)
358 totals[3] += m_kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->get(pixelIndex + 3));
362 kernelPixelX = startKernelPixelX;
364 width = m_kernelSize.width();
368 setDestinationPixels<preserveAlphaValues>(paintingData.dstPixelArray, pixel, totals, m_divisor, paintingData.bias, paintingData.srcPixelArray);
372 startKernelPixelX = beginKernelPixelX;
377 ALWAYS_INLINE void FEConvolveMatrix::setInteriorPixels(PaintingData& paintingData, int clipRight, int clipBottom, int yStart, int yEnd)
379 // Must be implemented here, since it refers another ALWAYS_INLINE
380 // function, which defined in this C++ source file as well
382 fastSetInteriorPixels<true>(paintingData, clipRight, clipBottom, yStart, yEnd);
384 fastSetInteriorPixels<false>(paintingData, clipRight, clipBottom, yStart, yEnd);
387 ALWAYS_INLINE void FEConvolveMatrix::setOuterPixels(PaintingData& paintingData, int x1, int y1, int x2, int y2)
389 // Although this function can be moved to the header, it is implemented here
390 // because setInteriorPixels is also implemented here
392 fastSetOuterPixels<true>(paintingData, x1, y1, x2, y2);
394 fastSetOuterPixels<false>(paintingData, x1, y1, x2, y2);
397 #if ENABLE(PARALLEL_JOBS)
398 void FEConvolveMatrix::setInteriorPixelsWorker(InteriorPixelParameters* param)
400 param->filter->setInteriorPixels(*param->paintingData, param->clipRight, param->clipBottom, param->yStart, param->yEnd);
404 void FEConvolveMatrix::apply()
408 FilterEffect* in = inputEffect(0);
410 if (!in->hasResult())
413 ByteArray* resultImage;
415 resultImage = createUnmultipliedImageResult();
417 resultImage = createPremultipliedImageResult();
421 IntRect effectDrawingRect = requestedRegionOfInputImageData(in->absolutePaintRect());
423 RefPtr<ByteArray> srcPixelArray;
425 srcPixelArray = in->asUnmultipliedImage(effectDrawingRect);
427 srcPixelArray = in->asPremultipliedImage(effectDrawingRect);
429 IntSize paintSize = absolutePaintRect().size();
430 PaintingData paintingData;
431 paintingData.srcPixelArray = srcPixelArray.get();
432 paintingData.dstPixelArray = resultImage;
433 paintingData.width = paintSize.width();
434 paintingData.height = paintSize.height();
435 paintingData.bias = m_bias * 255;
437 // Drawing fully covered pixels
438 int clipRight = paintSize.width() - m_kernelSize.width();
439 int clipBottom = paintSize.height() - m_kernelSize.height();
441 if (clipRight >= 0 && clipBottom >= 0) {
443 #if ENABLE(PARALLEL_JOBS)
444 int optimalThreadNumber = (absolutePaintRect().width() * absolutePaintRect().height()) / s_minimalRectDimension;
445 if (optimalThreadNumber > 1) {
446 ParallelJobs<InteriorPixelParameters> parallelJobs(&WebCore::FEConvolveMatrix::setInteriorPixelsWorker, optimalThreadNumber);
447 const int numOfThreads = parallelJobs.numberOfJobs();
448 const int heightPerThread = clipBottom / numOfThreads;
451 for (int job = 0; job < numOfThreads; ++job) {
452 InteriorPixelParameters& param = parallelJobs.parameter(job);
454 param.paintingData = &paintingData;
455 param.clipRight = clipRight;
456 param.clipBottom = clipBottom;
457 param.yStart = startY;
458 if (job < numOfThreads - 1) {
459 startY += heightPerThread;
460 param.yEnd = startY - 1;
462 param.yEnd = clipBottom;
465 parallelJobs.execute();
467 // Fallback to the default setInteriorPixels call.
469 setInteriorPixels(paintingData, clipRight, clipBottom, 0, clipBottom);
471 clipRight += m_targetOffset.x() + 1;
472 clipBottom += m_targetOffset.y() + 1;
473 if (m_targetOffset.y() > 0)
474 setOuterPixels(paintingData, 0, 0, paintSize.width(), m_targetOffset.y());
475 if (clipBottom < paintSize.height())
476 setOuterPixels(paintingData, 0, clipBottom, paintSize.width(), paintSize.height());
477 if (m_targetOffset.x() > 0)
478 setOuterPixels(paintingData, 0, m_targetOffset.y(), m_targetOffset.x(), clipBottom);
479 if (clipRight < paintSize.width())
480 setOuterPixels(paintingData, clipRight, m_targetOffset.y(), paintSize.width(), clipBottom);
482 // Rare situation, not optimizied for speed
483 setOuterPixels(paintingData, 0, 0, paintSize.width(), paintSize.height());
487 void FEConvolveMatrix::dump()
491 static TextStream& operator<<(TextStream& ts, const EdgeModeType& type)
494 case EDGEMODE_UNKNOWN:
497 case EDGEMODE_DUPLICATE:
510 TextStream& FEConvolveMatrix::externalRepresentation(TextStream& ts, int indent) const
512 writeIndent(ts, indent);
513 ts << "[feConvolveMatrix";
514 FilterEffect::externalRepresentation(ts);
515 ts << " order=\"" << m_kernelSize << "\" "
516 << "kernelMatrix=\"" << m_kernelMatrix << "\" "
517 << "divisor=\"" << m_divisor << "\" "
518 << "bias=\"" << m_bias << "\" "
519 << "target=\"" << m_targetOffset << "\" "
520 << "edgeMode=\"" << m_edgeMode << "\" "
521 << "kernelUnitLength=\"" << m_kernelUnitLength << "\" "
522 << "preserveAlpha=\"" << m_preserveAlpha << "\"]\n";
523 inputEffect(0)->externalRepresentation(ts, indent + 1);
527 }; // namespace WebCore
529 #endif // ENABLE(FILTERS)