NEON intrinsics should be used with gaussian blur filter
[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/MathExtras.h>
37 #include <wtf/ParallelJobs.h>
38 #include <wtf/Uint8ClampedArray.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(Uint8ClampedArray* srcPixelArray, Uint8ClampedArray* 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->item(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->item(pixelByteOffset - dxLeft * stride);
101                 if (x + dxRight < effectWidth)
102                     sum += srcPixelArray->item(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(Uint8ClampedArray* srcPixelArray, Uint8ClampedArray* 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     Uint8ClampedArray* src = srcPixelArray;
118     Uint8ClampedArray* dst = tmpPixelArray;
119
120     for (int i = 0; i < 3; ++i) {
121         if (kernelSizeX) {
122             kernelPosition(i, kernelSizeX, dxLeft, dxRight);
123 #if CPU(ARM_NEON) && COMPILER(GCC)
124             if (!isAlphaImage())
125                 boxBlurNEON(src, dst, kernelSizeX, dxLeft, dxRight, 4, stride, paintSize.width(), paintSize.height());
126             else
127                 boxBlur(src, dst, kernelSizeX, dxLeft, dxRight, 4, stride, paintSize.width(), paintSize.height(), true);
128 #else
129             boxBlur(src, dst, kernelSizeX, dxLeft, dxRight, 4, stride, paintSize.width(), paintSize.height(), isAlphaImage());
130 #endif
131             swap(src, dst);
132         }
133
134         if (kernelSizeY) {
135             kernelPosition(i, kernelSizeY, dyLeft, dyRight);
136 #if CPU(ARM_NEON) && COMPILER(GCC)
137             if (!isAlphaImage())
138                 boxBlurNEON(src, dst, kernelSizeY, dyLeft, dyRight, stride, 4, paintSize.height(), paintSize.width());
139             else
140                 boxBlur(src, dst, kernelSizeY, dyLeft, dyRight, stride, 4, paintSize.height(), paintSize.width(), true);
141 #else
142             boxBlur(src, dst, kernelSizeY, dyLeft, dyRight, stride, 4, paintSize.height(), paintSize.width(), isAlphaImage());
143 #endif
144             swap(src, dst);
145         }
146     }
147
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());
152     }
153
154 }
155
156 void FEGaussianBlur::platformApplyWorker(PlatformApplyParameters* parameters)
157 {
158     IntSize paintSize(parameters->width, parameters->height);
159     parameters->filter->platformApplyGeneric(parameters->srcPixelArray.get(), parameters->dstPixelArray.get(),
160         parameters->kernelSizeX, parameters->kernelSizeY, paintSize);
161 }
162
163 inline void FEGaussianBlur::platformApply(Uint8ClampedArray* srcPixelArray, Uint8ClampedArray* tmpPixelArray, unsigned kernelSizeX, unsigned kernelSizeY, IntSize& paintSize)
164 {
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());
168
169     if (optimalThreadNumber > 1) {
170         WTF::ParallelJobs<PlatformApplyParameters> parallelJobs(&platformApplyWorker, optimalThreadNumber);
171
172         int jobs = parallelJobs.numberOfJobs();
173         if (jobs > 1) {
174             int blockHeight = paintSize.height() / jobs;
175             --jobs;
176             for (int job = jobs; job >= 0; --job) {
177                 PlatformApplyParameters& params = parallelJobs.parameter(job);
178                 params.filter = this;
179
180                 int startY;
181                 int endY;
182                 if (!job) {
183                     startY = 0;
184                     endY = blockHeight + extraHeight;
185                     params.srcPixelArray = srcPixelArray;
186                     params.dstPixelArray = tmpPixelArray;
187                 } else {
188                     if (job == jobs) {
189                         startY = job * blockHeight - extraHeight;
190                         endY = paintSize.height();
191                     } else {
192                         startY = job * blockHeight - extraHeight;
193                         endY = (job + 1) * blockHeight + extraHeight;
194                     }
195
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);
200                 }
201
202                 params.width = paintSize.width();
203                 params.height = endY - startY;
204                 params.kernelSizeX = kernelSizeX;
205                 params.kernelSizeY = kernelSizeY;
206             }
207
208             parallelJobs.execute();
209
210             // Copy together the parts of the image.
211             for (int job = jobs; job >= 1; --job) {
212                 PlatformApplyParameters& params = parallelJobs.parameter(job);
213                 int sourceOffset;
214                 int destinationOffset;
215                 int size;
216                 if (job == jobs) {
217                     sourceOffset = extraHeight * scanline;
218                     destinationOffset = job * blockHeight * scanline;
219                     size = (paintSize.height() - job * blockHeight) * scanline;
220                 } else {
221                     sourceOffset = extraHeight * scanline;
222                     destinationOffset = job * blockHeight * scanline;
223                     size = blockHeight * scanline;
224                 }
225                 memcpy(srcPixelArray->data() + destinationOffset, params.srcPixelArray->data() + sourceOffset, size);
226             }
227             return;
228         }
229         // Fallback to single threaded mode.
230     }
231
232     // The selection here eventually should happen dynamically on some platforms.
233     platformApplyGeneric(srcPixelArray, tmpPixelArray, kernelSizeX, kernelSizeY, paintSize);
234 }
235
236 void FEGaussianBlur::calculateUnscaledKernelSize(unsigned& kernelSizeX, unsigned& kernelSizeY, float stdX, float stdY)
237 {
238     kernelSizeX = 0;
239     if (stdX)
240         kernelSizeX = max<unsigned>(2, static_cast<unsigned>(floorf(stdX * gaussianKernelFactor() + 0.5f)));
241     kernelSizeY = 0;
242     if (stdY)
243         kernelSizeY = max<unsigned>(2, static_cast<unsigned>(floorf(stdY * gaussianKernelFactor() + 0.5f)));
244     
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;
251 }
252
253 void FEGaussianBlur::calculateKernelSize(Filter* filter, unsigned& kernelSizeX, unsigned& kernelSizeY, float stdX, float stdY)
254 {
255     stdX = filter->applyHorizontalScale(stdX);
256     stdY = filter->applyVerticalScale(stdY);
257
258     calculateUnscaledKernelSize(kernelSizeX, kernelSizeY, stdX, stdY);
259 }
260
261 void FEGaussianBlur::determineAbsolutePaintRect()
262 {
263     FloatRect absolutePaintRect = inputEffect(0)->absolutePaintRect();
264     if (clipsToBounds())
265         absolutePaintRect.intersect(maxEffectRect());
266     else
267         absolutePaintRect.unite(maxEffectRect());
268
269     unsigned kernelSizeX = 0;
270     unsigned kernelSizeY = 0;
271     calculateKernelSize(filter(), kernelSizeX, kernelSizeY, m_stdX, m_stdY);
272
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));
277 }
278
279 void FEGaussianBlur::platformApplySoftware()
280 {
281     FilterEffect* in = inputEffect(0);
282
283     Uint8ClampedArray* srcPixelArray = createPremultipliedImageResult();
284     if (!srcPixelArray)
285         return;
286
287     setIsAlphaImage(in->isAlphaImage());
288
289     IntRect effectDrawingRect = requestedRegionOfInputImageData(in->absolutePaintRect());
290     in->copyPremultipliedImage(srcPixelArray, effectDrawingRect);
291
292     if (!m_stdX && !m_stdY)
293         return;
294
295     unsigned kernelSizeX = 0;
296     unsigned kernelSizeY = 0;
297     calculateKernelSize(filter(), kernelSizeX, kernelSizeY, m_stdX, m_stdY);
298
299     IntSize paintSize = absolutePaintRect().size();
300     RefPtr<Uint8ClampedArray> tmpImageData = Uint8ClampedArray::createUninitialized(paintSize.width() * paintSize.height() * 4);
301     Uint8ClampedArray* tmpPixelArray = tmpImageData.get();
302
303     platformApply(srcPixelArray, tmpPixelArray, kernelSizeX, kernelSizeY, paintSize);
304 }
305
306 void FEGaussianBlur::dump()
307 {
308 }
309
310 TextStream& FEGaussianBlur::externalRepresentation(TextStream& ts, int indent) const
311 {
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);
317     return ts;
318 }
319
320 float FEGaussianBlur::calculateStdDeviation(float radius)
321 {
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);
324 }
325
326 } // namespace WebCore
327
328 #endif // ENABLE(FILTERS)