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 Igalia, S.L.
7 * Copyright (C) Research In Motion Limited 2010. All rights reserved.
9 * This library is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Library General Public
11 * License as published by the Free Software Foundation; either
12 * version 2 of the License, or (at your option) any later version.
14 * This library is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Library General Public License for more details.
19 * You should have received a copy of the GNU Library General Public License
20 * along with this library; see the file COPYING.LIB. If not, write to
21 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22 * Boston, MA 02110-1301, USA.
28 #include "FEGaussianBlur.h"
30 #include "FEGaussianBlurNEON.h"
32 #include "GraphicsContext.h"
33 #include "RenderTreeAsText.h"
34 #include "TextStream.h"
36 #include <wtf/MathExtras.h>
37 #include <wtf/ParallelJobs.h>
38 #include <wtf/Uint8ClampedArray.h>
42 static inline float gaussianKernelFactor()
44 return 3 / 4.f * sqrtf(2 * piFloat);
47 static const unsigned gMaxKernelSize = 1000;
51 FEGaussianBlur::FEGaussianBlur(Filter* filter, float x, float y)
52 : FilterEffect(filter)
58 PassRefPtr<FEGaussianBlur> FEGaussianBlur::create(Filter* filter, float x, float y)
60 return adoptRef(new FEGaussianBlur(filter, x, y));
63 float FEGaussianBlur::stdDeviationX() const
68 void FEGaussianBlur::setStdDeviationX(float x)
73 float FEGaussianBlur::stdDeviationY() const
78 void FEGaussianBlur::setStdDeviationY(float y)
83 inline void boxBlur(Uint8ClampedArray* srcPixelArray, Uint8ClampedArray* dstPixelArray,
84 unsigned dx, int dxLeft, int dxRight, int stride, int strideLine, int effectWidth, int effectHeight, bool alphaImage)
86 for (int y = 0; y < effectHeight; ++y) {
87 int line = y * strideLine;
88 for (int channel = 3; channel >= 0; --channel) {
91 int maxKernelSize = min(dxRight, effectWidth);
92 for (int i = 0; i < maxKernelSize; ++i)
93 sum += srcPixelArray->item(line + i * stride + channel);
96 for (int x = 0; x < effectWidth; ++x) {
97 int pixelByteOffset = line + x * stride + channel;
98 dstPixelArray->set(pixelByteOffset, static_cast<unsigned char>(sum / dx));
100 sum -= srcPixelArray->item(pixelByteOffset - dxLeft * stride);
101 if (x + dxRight < effectWidth)
102 sum += srcPixelArray->item(pixelByteOffset + dxRight * stride);
104 if (alphaImage) // Source image is black, it just has different alpha values
110 inline void FEGaussianBlur::platformApplyGeneric(Uint8ClampedArray* srcPixelArray, Uint8ClampedArray* tmpPixelArray, unsigned kernelSizeX, unsigned kernelSizeY, IntSize& paintSize)
112 int stride = 4 * paintSize.width();
117 Uint8ClampedArray* src = srcPixelArray;
118 Uint8ClampedArray* dst = tmpPixelArray;
120 for (int i = 0; i < 3; ++i) {
122 kernelPosition(i, kernelSizeX, dxLeft, dxRight);
123 #if CPU(ARM_NEON) && COMPILER(GCC)
125 boxBlurNEON(src, dst, kernelSizeX, dxLeft, dxRight, 4, stride, paintSize.width(), paintSize.height());
127 boxBlur(src, dst, kernelSizeX, dxLeft, dxRight, 4, stride, paintSize.width(), paintSize.height(), true);
129 boxBlur(src, dst, kernelSizeX, dxLeft, dxRight, 4, stride, paintSize.width(), paintSize.height(), isAlphaImage());
135 kernelPosition(i, kernelSizeY, dyLeft, dyRight);
136 #if CPU(ARM_NEON) && COMPILER(GCC)
138 boxBlurNEON(src, dst, kernelSizeY, dyLeft, dyRight, stride, 4, paintSize.height(), paintSize.width());
140 boxBlur(src, dst, kernelSizeY, dyLeft, dyRight, stride, 4, paintSize.height(), paintSize.width(), true);
142 boxBlur(src, dst, kernelSizeY, dyLeft, dyRight, stride, 4, paintSize.height(), paintSize.width(), isAlphaImage());
148 // The final result should be stored in srcPixelArray.
149 if (dst == srcPixelArray) {
150 ASSERT(src->length() == dst->length());
151 memcpy(dst->data(), src->data(), src->length());
156 void FEGaussianBlur::platformApplyWorker(PlatformApplyParameters* parameters)
158 IntSize paintSize(parameters->width, parameters->height);
159 parameters->filter->platformApplyGeneric(parameters->srcPixelArray.get(), parameters->dstPixelArray.get(),
160 parameters->kernelSizeX, parameters->kernelSizeY, paintSize);
163 inline void FEGaussianBlur::platformApply(Uint8ClampedArray* srcPixelArray, Uint8ClampedArray* tmpPixelArray, unsigned kernelSizeX, unsigned kernelSizeY, IntSize& paintSize)
165 int scanline = 4 * paintSize.width();
166 int extraHeight = 3 * kernelSizeY * 0.5f;
167 int optimalThreadNumber = (paintSize.width() * paintSize.height()) / (s_minimalRectDimension + extraHeight * paintSize.width());
169 if (optimalThreadNumber > 1) {
170 WTF::ParallelJobs<PlatformApplyParameters> parallelJobs(&platformApplyWorker, optimalThreadNumber);
172 int jobs = parallelJobs.numberOfJobs();
174 int blockHeight = paintSize.height() / jobs;
176 for (int job = jobs; job >= 0; --job) {
177 PlatformApplyParameters& params = parallelJobs.parameter(job);
178 params.filter = this;
184 endY = blockHeight + extraHeight;
185 params.srcPixelArray = srcPixelArray;
186 params.dstPixelArray = tmpPixelArray;
189 startY = job * blockHeight - extraHeight;
190 endY = paintSize.height();
192 startY = job * blockHeight - extraHeight;
193 endY = (job + 1) * blockHeight + extraHeight;
196 int blockSize = (endY - startY) * scanline;
197 params.srcPixelArray = Uint8ClampedArray::createUninitialized(blockSize);
198 params.dstPixelArray = Uint8ClampedArray::createUninitialized(blockSize);
199 memcpy(params.srcPixelArray->data(), srcPixelArray->data() + startY * scanline, blockSize);
202 params.width = paintSize.width();
203 params.height = endY - startY;
204 params.kernelSizeX = kernelSizeX;
205 params.kernelSizeY = kernelSizeY;
208 parallelJobs.execute();
210 // Copy together the parts of the image.
211 for (int job = jobs; job >= 1; --job) {
212 PlatformApplyParameters& params = parallelJobs.parameter(job);
214 int destinationOffset;
217 sourceOffset = extraHeight * scanline;
218 destinationOffset = job * blockHeight * scanline;
219 size = (paintSize.height() - job * blockHeight) * scanline;
221 sourceOffset = extraHeight * scanline;
222 destinationOffset = job * blockHeight * scanline;
223 size = blockHeight * scanline;
225 memcpy(srcPixelArray->data() + destinationOffset, params.srcPixelArray->data() + sourceOffset, size);
229 // Fallback to single threaded mode.
232 // The selection here eventually should happen dynamically on some platforms.
233 platformApplyGeneric(srcPixelArray, tmpPixelArray, kernelSizeX, kernelSizeY, paintSize);
236 void FEGaussianBlur::calculateUnscaledKernelSize(unsigned& kernelSizeX, unsigned& kernelSizeY, float stdX, float stdY)
240 kernelSizeX = max<unsigned>(2, static_cast<unsigned>(floorf(stdX * gaussianKernelFactor() + 0.5f)));
243 kernelSizeY = max<unsigned>(2, static_cast<unsigned>(floorf(stdY * gaussianKernelFactor() + 0.5f)));
245 // Limit the kernel size to 1000. A bigger radius won't make a big difference for the result image but
246 // inflates the absolute paint rect to much. This is compatible with Firefox' behavior.
247 if (kernelSizeX > gMaxKernelSize)
248 kernelSizeX = gMaxKernelSize;
249 if (kernelSizeY > gMaxKernelSize)
250 kernelSizeY = gMaxKernelSize;
253 void FEGaussianBlur::calculateKernelSize(Filter* filter, unsigned& kernelSizeX, unsigned& kernelSizeY, float stdX, float stdY)
255 stdX = filter->applyHorizontalScale(stdX);
256 stdY = filter->applyVerticalScale(stdY);
258 calculateUnscaledKernelSize(kernelSizeX, kernelSizeY, stdX, stdY);
261 void FEGaussianBlur::determineAbsolutePaintRect()
263 FloatRect absolutePaintRect = inputEffect(0)->absolutePaintRect();
265 absolutePaintRect.intersect(maxEffectRect());
267 absolutePaintRect.unite(maxEffectRect());
269 unsigned kernelSizeX = 0;
270 unsigned kernelSizeY = 0;
271 calculateKernelSize(filter(), kernelSizeX, kernelSizeY, m_stdX, m_stdY);
273 // We take the half kernel size and multiply it with three, because we run box blur three times.
274 absolutePaintRect.inflateX(3 * kernelSizeX * 0.5f);
275 absolutePaintRect.inflateY(3 * kernelSizeY * 0.5f);
276 setAbsolutePaintRect(enclosingIntRect(absolutePaintRect));
279 void FEGaussianBlur::platformApplySoftware()
281 FilterEffect* in = inputEffect(0);
283 Uint8ClampedArray* srcPixelArray = createPremultipliedImageResult();
287 setIsAlphaImage(in->isAlphaImage());
289 IntRect effectDrawingRect = requestedRegionOfInputImageData(in->absolutePaintRect());
290 in->copyPremultipliedImage(srcPixelArray, effectDrawingRect);
292 if (!m_stdX && !m_stdY)
295 unsigned kernelSizeX = 0;
296 unsigned kernelSizeY = 0;
297 calculateKernelSize(filter(), kernelSizeX, kernelSizeY, m_stdX, m_stdY);
299 IntSize paintSize = absolutePaintRect().size();
300 RefPtr<Uint8ClampedArray> tmpImageData = Uint8ClampedArray::createUninitialized(paintSize.width() * paintSize.height() * 4);
301 Uint8ClampedArray* tmpPixelArray = tmpImageData.get();
303 platformApply(srcPixelArray, tmpPixelArray, kernelSizeX, kernelSizeY, paintSize);
306 void FEGaussianBlur::dump()
310 TextStream& FEGaussianBlur::externalRepresentation(TextStream& ts, int indent) const
312 writeIndent(ts, indent);
313 ts << "[feGaussianBlur";
314 FilterEffect::externalRepresentation(ts);
315 ts << " stdDeviation=\"" << m_stdX << ", " << m_stdY << "\"]\n";
316 inputEffect(0)->externalRepresentation(ts, indent + 1);
320 float FEGaussianBlur::calculateStdDeviation(float radius)
322 // Blur radius represents 2/3 times the kernel size, the dest pixel is half of the radius applied 3 times
323 return max((radius * 2 / 3.f - 0.5f) / gaussianKernelFactor(), 0.f);
326 } // namespace WebCore
328 #endif // ENABLE(FILTERS)