Filters need to affect visual overflow
[WebKit-https.git] / Source / WebCore / platform / graphics / filters / FEGaussianBlur.cpp
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 Igalia, S.L.
7  * Copyright (C) Research In Motion Limited 2010. All rights reserved.
8  *
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.
13  *
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.
18  *
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.
23  */
24
25 #include "config.h"
26
27 #if ENABLE(FILTERS)
28 #include "FEGaussianBlur.h"
29
30 #include "FEGaussianBlurNEON.h"
31 #include "Filter.h"
32 #include "GraphicsContext.h"
33 #include "RenderTreeAsText.h"
34 #include "TextStream.h"
35
36 #include <wtf/ByteArray.h>
37 #include <wtf/MathExtras.h>
38 #include <wtf/ParallelJobs.h>
39
40 using namespace std;
41
42 static inline float gaussianKernelFactor()
43 {
44     return 3 / 4.f * sqrtf(2 * piFloat);
45 }
46
47 static const unsigned gMaxKernelSize = 1000;
48
49 namespace WebCore {
50
51 FEGaussianBlur::FEGaussianBlur(Filter* filter, float x, float y)
52     : FilterEffect(filter)
53     , m_stdX(x)
54     , m_stdY(y)
55 {
56 }
57
58 PassRefPtr<FEGaussianBlur> FEGaussianBlur::create(Filter* filter, float x, float y)
59 {
60     return adoptRef(new FEGaussianBlur(filter, x, y));
61 }
62
63 float FEGaussianBlur::stdDeviationX() const
64 {
65     return m_stdX;
66 }
67
68 void FEGaussianBlur::setStdDeviationX(float x)
69 {
70     m_stdX = x;
71 }
72
73 float FEGaussianBlur::stdDeviationY() const
74 {
75     return m_stdY;
76 }
77
78 void FEGaussianBlur::setStdDeviationY(float y)
79 {
80     m_stdY = y;
81 }
82
83 inline void boxBlur(ByteArray* srcPixelArray, ByteArray* dstPixelArray,
84                     unsigned dx, int dxLeft, int dxRight, int stride, int strideLine, int effectWidth, int effectHeight, bool alphaImage)
85 {
86     for (int y = 0; y < effectHeight; ++y) {
87         int line = y * strideLine;
88         for (int channel = 3; channel >= 0; --channel) {
89             int sum = 0;
90             // Fill the kernel
91             int maxKernelSize = min(dxRight, effectWidth);
92             for (int i = 0; i < maxKernelSize; ++i)
93                 sum += srcPixelArray->get(line + i * stride + channel);
94
95             // Blurring
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));
99                 if (x >= dxLeft)
100                     sum -= srcPixelArray->get(pixelByteOffset - dxLeft * stride);
101                 if (x + dxRight < effectWidth)
102                     sum += srcPixelArray->get(pixelByteOffset + dxRight * stride);
103             }
104             if (alphaImage) // Source image is black, it just has different alpha values
105                 break;
106         }
107     }
108 }
109
110 inline void FEGaussianBlur::platformApplyGeneric(ByteArray* srcPixelArray, ByteArray* tmpPixelArray, unsigned kernelSizeX, unsigned kernelSizeY, IntSize& paintSize)
111 {
112     int stride = 4 * paintSize.width();
113     int dxLeft = 0;
114     int dxRight = 0;
115     int dyLeft = 0;
116     int dyRight = 0;
117     ByteArray* src = srcPixelArray;
118     ByteArray* dst = tmpPixelArray;
119
120     for (int i = 0; i < 3; ++i) {
121         if (kernelSizeX) {
122             kernelPosition(i, kernelSizeX, dxLeft, dxRight);
123             boxBlur(src, dst, kernelSizeX, dxLeft, dxRight, 4, stride, paintSize.width(), paintSize.height(), isAlphaImage());
124             swap(src, dst);
125         }
126
127         if (kernelSizeY) {
128             kernelPosition(i, kernelSizeY, dyLeft, dyRight);
129             boxBlur(src, dst, kernelSizeY, dyLeft, dyRight, stride, 4, paintSize.height(), paintSize.width(), isAlphaImage());
130             swap(src, dst);
131         }
132     }
133
134     // The final result should be stored in srcPixelArray.
135     if (dst == srcPixelArray) {
136         ASSERT(src->length() == dst->length());
137         memcpy(dst->data(), src->data(), src->length());
138     }
139
140 }
141
142 void FEGaussianBlur::platformApplyWorker(PlatformApplyParameters* parameters)
143 {
144     IntSize paintSize(parameters->width, parameters->height);
145 #if CPU(ARM_NEON) && COMPILER(GCC)
146     parameters->filter->platformApplyNeon(parameters->srcPixelArray.get(), parameters->dstPixelArray.get(),
147         parameters->kernelSizeX, parameters->kernelSizeY, paintSize);
148 #else
149     parameters->filter->platformApplyGeneric(parameters->srcPixelArray.get(), parameters->dstPixelArray.get(),
150         parameters->kernelSizeX, parameters->kernelSizeY, paintSize);
151 #endif
152 }
153
154 inline void FEGaussianBlur::platformApply(ByteArray* srcPixelArray, ByteArray* tmpPixelArray, unsigned kernelSizeX, unsigned kernelSizeY, IntSize& paintSize)
155 {
156     int scanline = 4 * paintSize.width();
157     int extraHeight = 3 * kernelSizeY * 0.5f;
158     int optimalThreadNumber = (paintSize.width() * paintSize.height()) / (s_minimalRectDimension + extraHeight * paintSize.width());
159
160     if (optimalThreadNumber > 1) {
161         WTF::ParallelJobs<PlatformApplyParameters> parallelJobs(&platformApplyWorker, optimalThreadNumber);
162
163         int jobs = parallelJobs.numberOfJobs();
164         if (jobs > 1) {
165             int blockHeight = paintSize.height() / jobs;
166             --jobs;
167             for (int job = jobs; job >= 0; --job) {
168                 PlatformApplyParameters& params = parallelJobs.parameter(job);
169                 params.filter = this;
170
171                 int startY;
172                 int endY;
173                 if (!job) {
174                     startY = 0;
175                     endY = blockHeight + extraHeight;
176                     params.srcPixelArray = srcPixelArray;
177                     params.dstPixelArray = tmpPixelArray;
178                 } else {
179                     if (job == jobs) {
180                         startY = job * blockHeight - extraHeight;
181                         endY = paintSize.height();
182                     } else {
183                         startY = job * blockHeight - extraHeight;
184                         endY = (job + 1) * blockHeight + extraHeight;
185                     }
186
187                     int blockSize = (endY - startY) * scanline;
188                     params.srcPixelArray = ByteArray::create(blockSize);
189                     params.dstPixelArray = ByteArray::create(blockSize);
190                     memcpy(params.srcPixelArray->data(), srcPixelArray->data() + startY * scanline, blockSize);
191                 }
192
193                 params.width = paintSize.width();
194                 params.height = endY - startY;
195                 params.kernelSizeX = kernelSizeX;
196                 params.kernelSizeY = kernelSizeY;
197             }
198
199             parallelJobs.execute();
200
201             // Copy together the parts of the image.
202             for (int job = jobs; job >= 1; --job) {
203                 PlatformApplyParameters& params = parallelJobs.parameter(job);
204                 int sourceOffset;
205                 int destinationOffset;
206                 int size;
207                 if (job == jobs) {
208                     sourceOffset = extraHeight * scanline;
209                     destinationOffset = job * blockHeight * scanline;
210                     size = (paintSize.height() - job * blockHeight) * scanline;
211                 } else {
212                     sourceOffset = extraHeight * scanline;
213                     destinationOffset = job * blockHeight * scanline;
214                     size = blockHeight * scanline;
215                 }
216                 memcpy(srcPixelArray->data() + destinationOffset, params.srcPixelArray->data() + sourceOffset, size);
217             }
218             return;
219         }
220         // Fallback to single threaded mode.
221     }
222
223     // The selection here eventually should happen dynamically on some platforms.
224 #if CPU(ARM_NEON) && COMPILER(GCC)
225     platformApplyNeon(srcPixelArray, tmpPixelArray, kernelSizeX, kernelSizeY, paintSize);
226 #else
227     platformApplyGeneric(srcPixelArray, tmpPixelArray, kernelSizeX, kernelSizeY, paintSize);
228 #endif
229 }
230
231 void FEGaussianBlur::calculateUnscaledKernelSize(unsigned& kernelSizeX, unsigned& kernelSizeY, float stdX, float stdY)
232 {
233     kernelSizeX = 0;
234     if (stdX)
235         kernelSizeX = max<unsigned>(2, static_cast<unsigned>(floorf(stdX * gaussianKernelFactor() + 0.5f)));
236     kernelSizeY = 0;
237     if (stdY)
238         kernelSizeY = max<unsigned>(2, static_cast<unsigned>(floorf(stdY * gaussianKernelFactor() + 0.5f)));
239     
240     // Limit the kernel size to 1000. A bigger radius won't make a big difference for the result image but
241     // inflates the absolute paint rect to much. This is compatible with Firefox' behavior.
242     if (kernelSizeX > gMaxKernelSize)
243         kernelSizeX = gMaxKernelSize;
244     if (kernelSizeY > gMaxKernelSize)
245         kernelSizeY = gMaxKernelSize;
246 }
247
248 void FEGaussianBlur::calculateKernelSize(Filter* filter, unsigned& kernelSizeX, unsigned& kernelSizeY, float stdX, float stdY)
249 {
250     stdX = filter->applyHorizontalScale(stdX);
251     stdY = filter->applyVerticalScale(stdY);
252
253     calculateUnscaledKernelSize(kernelSizeX, kernelSizeY, stdX, stdY);
254 }
255
256 void FEGaussianBlur::determineAbsolutePaintRect()
257 {
258     FloatRect absolutePaintRect = inputEffect(0)->absolutePaintRect();
259     if (clipsToBounds())
260         absolutePaintRect.intersect(maxEffectRect());
261     else
262         absolutePaintRect.unite(maxEffectRect());
263
264     unsigned kernelSizeX = 0;
265     unsigned kernelSizeY = 0;
266     calculateKernelSize(filter(), kernelSizeX, kernelSizeY, m_stdX, m_stdY);
267
268     // We take the half kernel size and multiply it with three, because we run box blur three times.
269     absolutePaintRect.inflateX(3 * kernelSizeX * 0.5f);
270     absolutePaintRect.inflateY(3 * kernelSizeY * 0.5f);
271     setAbsolutePaintRect(enclosingIntRect(absolutePaintRect));
272 }
273
274 void FEGaussianBlur::platformApplySoftware()
275 {
276 #if USE(SKIA)
277     if (filter()->renderingMode() == Accelerated) {
278         platformApplySkia();
279         return;
280     }
281 #endif
282
283     FilterEffect* in = inputEffect(0);
284
285     ByteArray* srcPixelArray = createPremultipliedImageResult();
286     if (!srcPixelArray)
287         return;
288
289     setIsAlphaImage(in->isAlphaImage());
290
291     IntRect effectDrawingRect = requestedRegionOfInputImageData(in->absolutePaintRect());
292     in->copyPremultipliedImage(srcPixelArray, effectDrawingRect);
293
294     if (!m_stdX && !m_stdY)
295         return;
296
297     unsigned kernelSizeX = 0;
298     unsigned kernelSizeY = 0;
299     calculateKernelSize(filter(), kernelSizeX, kernelSizeY, m_stdX, m_stdY);
300
301     IntSize paintSize = absolutePaintRect().size();
302     RefPtr<ByteArray> tmpImageData = ByteArray::create(paintSize.width() * paintSize.height() * 4);
303     ByteArray* tmpPixelArray = tmpImageData.get();
304
305     platformApply(srcPixelArray, tmpPixelArray, kernelSizeX, kernelSizeY, paintSize);
306 }
307
308 void FEGaussianBlur::dump()
309 {
310 }
311
312 TextStream& FEGaussianBlur::externalRepresentation(TextStream& ts, int indent) const
313 {
314     writeIndent(ts, indent);
315     ts << "[feGaussianBlur";
316     FilterEffect::externalRepresentation(ts);
317     ts << " stdDeviation=\"" << m_stdX << ", " << m_stdY << "\"]\n";
318     inputEffect(0)->externalRepresentation(ts, indent + 1);
319     return ts;
320 }
321
322 float FEGaussianBlur::calculateStdDeviation(float radius)
323 {
324     // Blur radius represents 2/3 times the kernel size, the dest pixel is half of the radius applied 3 times
325     return max((radius * 2 / 3.f - 0.5f) / gaussianKernelFactor(), 0.f);
326 }
327
328 } // namespace WebCore
329
330 #endif // ENABLE(FILTERS)