Unreviewed, rolling out r156510.
[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 <runtime/Operations.h>
37 #include <runtime/TypedArrayInlines.h>
38 #include <runtime/Uint8ClampedArray.h>
39 #include <wtf/MathExtras.h>
40 #include <wtf/ParallelJobs.h>
41
42 using namespace std;
43
44 static inline float gaussianKernelFactor()
45 {
46     return 3 / 4.f * sqrtf(2 * piFloat);
47 }
48
49 static const unsigned gMaxKernelSize = 1000;
50
51 namespace WebCore {
52
53 FEGaussianBlur::FEGaussianBlur(Filter* filter, float x, float y, EdgeModeType edgeMode)
54     : FilterEffect(filter)
55     , m_stdX(x)
56     , m_stdY(y)
57     , m_edgeMode(edgeMode)
58 {
59 }
60
61 PassRefPtr<FEGaussianBlur> FEGaussianBlur::create(Filter* filter, float x, float y, EdgeModeType edgeMode)
62 {
63     return adoptRef(new FEGaussianBlur(filter, x, y, edgeMode));
64 }
65
66 float FEGaussianBlur::stdDeviationX() const
67 {
68     return m_stdX;
69 }
70
71 void FEGaussianBlur::setStdDeviationX(float x)
72 {
73     m_stdX = x;
74 }
75
76 float FEGaussianBlur::stdDeviationY() const
77 {
78     return m_stdY;
79 }
80
81 void FEGaussianBlur::setStdDeviationY(float y)
82 {
83     m_stdY = y;
84 }
85
86 EdgeModeType FEGaussianBlur::edgeMode() const
87 {
88     return m_edgeMode;
89 }
90
91 void FEGaussianBlur::setEdgeMode(EdgeModeType edgeMode)
92 {
93     m_edgeMode = edgeMode;
94 }
95
96 inline void boxBlur(Uint8ClampedArray* srcPixelArray, Uint8ClampedArray* dstPixelArray,
97     unsigned dx, int dxLeft, int dxRight, int stride, int strideLine, int effectWidth, int effectHeight, bool alphaImage, EdgeModeType edgeMode)
98 {
99     for (int y = 0; y < effectHeight; ++y) {
100         int line = y * strideLine;
101         for (int channel = 3; channel >= 0; --channel) {
102             int sum = 0;
103             // The code for edgeMode='none' is the common case and highly optimized.
104             // Furthermore, this code path affects more than just the input area.
105             if (edgeMode == EDGEMODE_NONE) {
106                 // Fill the kernel
107                 int maxKernelSize = min(dxRight, effectWidth);
108                 for (int i = 0; i < maxKernelSize; ++i)
109                     sum += srcPixelArray->item(line + i * stride + channel);
110
111                 // Blurring
112                 for (int x = 0; x < effectWidth; ++x) {
113                     int pixelByteOffset = line + x * stride + channel;
114                     dstPixelArray->set(pixelByteOffset, static_cast<unsigned char>(sum / dx));
115                     // Shift kernel.
116                     if (x >= dxLeft)
117                         sum -= srcPixelArray->item(pixelByteOffset - dxLeft * stride);
118                     if (x + dxRight < effectWidth)
119                         sum += srcPixelArray->item(pixelByteOffset + dxRight * stride);
120                 }
121             } else {
122                 // FIXME: Add support for 'wrap' here.
123                 // Get edge values for edgeMode 'duplicate'.
124                 int edgeValueLeft = srcPixelArray->item(line + channel);
125                 int edgeValueRight = srcPixelArray->item(line + (effectWidth - 1) * stride + channel);
126                 // Fill the kernel
127                 for (int i = dxLeft * -1; i < dxRight; ++i) {
128                     if (i < 0)
129                         sum += edgeValueLeft;
130                     else if (i >= effectWidth)
131                         sum += edgeValueRight;
132                     else
133                         sum += srcPixelArray->item(line + i * stride + channel);
134                 }
135                 // Blurring
136                 for (int x = 0; x < effectWidth; ++x) {
137                     int pixelByteOffset = line + x * stride + channel;
138                     dstPixelArray->set(pixelByteOffset, static_cast<unsigned char>(sum / dx));
139                     // Shift kernel.
140                     if (x < dxLeft)
141                         sum -= edgeValueLeft;
142                     else
143                         sum -= srcPixelArray->item(pixelByteOffset - dxLeft * stride);
144                     if (x + dxRight >= effectWidth)
145                         sum += edgeValueRight;
146                     else
147                         sum += srcPixelArray->item(pixelByteOffset + dxRight * stride);
148                 }
149             }
150             if (alphaImage) // Source image is black, it just has different alpha values
151                 break;
152         }
153     }
154 }
155
156 inline void FEGaussianBlur::platformApplyGeneric(Uint8ClampedArray* srcPixelArray, Uint8ClampedArray* tmpPixelArray, unsigned kernelSizeX, unsigned kernelSizeY, IntSize& paintSize)
157 {
158     int stride = 4 * paintSize.width();
159     int dxLeft = 0;
160     int dxRight = 0;
161     int dyLeft = 0;
162     int dyRight = 0;
163     Uint8ClampedArray* src = srcPixelArray;
164     Uint8ClampedArray* dst = tmpPixelArray;
165
166     for (int i = 0; i < 3; ++i) {
167         if (kernelSizeX) {
168             kernelPosition(i, kernelSizeX, dxLeft, dxRight);
169 #if HAVE(ARM_NEON_INTRINSICS)
170             if (!isAlphaImage())
171                 boxBlurNEON(src, dst, kernelSizeX, dxLeft, dxRight, 4, stride, paintSize.width(), paintSize.height());
172             else
173                 boxBlur(src, dst, kernelSizeX, dxLeft, dxRight, 4, stride, paintSize.width(), paintSize.height(), true, m_edgeMode);
174 #else
175             boxBlur(src, dst, kernelSizeX, dxLeft, dxRight, 4, stride, paintSize.width(), paintSize.height(), isAlphaImage(), m_edgeMode);
176 #endif
177             swap(src, dst);
178         }
179
180         if (kernelSizeY) {
181             kernelPosition(i, kernelSizeY, dyLeft, dyRight);
182 #if HAVE(ARM_NEON_INTRINSICS)
183             if (!isAlphaImage())
184                 boxBlurNEON(src, dst, kernelSizeY, dyLeft, dyRight, stride, 4, paintSize.height(), paintSize.width());
185             else
186                 boxBlur(src, dst, kernelSizeY, dyLeft, dyRight, stride, 4, paintSize.height(), paintSize.width(), true, m_edgeMode);
187 #else
188             boxBlur(src, dst, kernelSizeY, dyLeft, dyRight, stride, 4, paintSize.height(), paintSize.width(), isAlphaImage(), m_edgeMode);
189 #endif
190             swap(src, dst);
191         }
192     }
193
194     // The final result should be stored in srcPixelArray.
195     if (dst == srcPixelArray) {
196         ASSERT(src->length() == dst->length());
197         memcpy(dst->data(), src->data(), src->length());
198     }
199
200 }
201
202 void FEGaussianBlur::platformApplyWorker(PlatformApplyParameters* parameters)
203 {
204     IntSize paintSize(parameters->width, parameters->height);
205     parameters->filter->platformApplyGeneric(parameters->srcPixelArray.get(), parameters->dstPixelArray.get(),
206         parameters->kernelSizeX, parameters->kernelSizeY, paintSize);
207 }
208
209 inline void FEGaussianBlur::platformApply(Uint8ClampedArray* srcPixelArray, Uint8ClampedArray* tmpPixelArray, unsigned kernelSizeX, unsigned kernelSizeY, IntSize& paintSize)
210 {
211     int scanline = 4 * paintSize.width();
212     int extraHeight = 3 * kernelSizeY * 0.5f;
213     int optimalThreadNumber = (paintSize.width() * paintSize.height()) / (s_minimalRectDimension + extraHeight * paintSize.width());
214
215     if (optimalThreadNumber > 1) {
216         WTF::ParallelJobs<PlatformApplyParameters> parallelJobs(&platformApplyWorker, optimalThreadNumber);
217
218         int jobs = parallelJobs.numberOfJobs();
219         if (jobs > 1) {
220             // Split the job into "blockHeight"-sized jobs but there a few jobs that need to be slightly larger since
221             // blockHeight * jobs < total size. These extras are handled by the remainder "jobsWithExtra".
222             const int blockHeight = paintSize.height() / jobs;
223             const int jobsWithExtra = paintSize.height() % jobs;
224
225             int currentY = 0;
226             for (int job = 0; job < jobs; job++) {
227                 PlatformApplyParameters& params = parallelJobs.parameter(job);
228                 params.filter = this;
229
230                 int startY = !job ? 0 : currentY - extraHeight;
231                 currentY += job < jobsWithExtra ? blockHeight + 1 : blockHeight;
232                 int endY = job == jobs - 1 ? currentY : currentY + extraHeight;
233
234                 int blockSize = (endY - startY) * scanline;
235                 if (!job) {
236                     params.srcPixelArray = srcPixelArray;
237                     params.dstPixelArray = tmpPixelArray;
238                 } else {
239                     params.srcPixelArray = Uint8ClampedArray::createUninitialized(blockSize);
240                     params.dstPixelArray = Uint8ClampedArray::createUninitialized(blockSize);
241                     memcpy(params.srcPixelArray->data(), srcPixelArray->data() + startY * scanline, blockSize);
242                 }
243
244                 params.width = paintSize.width();
245                 params.height = endY - startY;
246                 params.kernelSizeX = kernelSizeX;
247                 params.kernelSizeY = kernelSizeY;
248             }
249
250             parallelJobs.execute();
251
252             // Copy together the parts of the image.
253             currentY = 0;
254             for (int job = 1; job < jobs; job++) {
255                 PlatformApplyParameters& params = parallelJobs.parameter(job);
256                 int sourceOffset;
257                 int destinationOffset;
258                 int size;
259                 int adjustedBlockHeight = job < jobsWithExtra ? blockHeight + 1 : blockHeight;
260
261                 currentY += adjustedBlockHeight;
262                 sourceOffset = extraHeight * scanline;
263                 destinationOffset = currentY * scanline;
264                 size = adjustedBlockHeight * scanline;
265
266                 memcpy(srcPixelArray->data() + destinationOffset, params.srcPixelArray->data() + sourceOffset, size);
267             }
268             return;
269         }
270         // Fallback to single threaded mode.
271     }
272
273     // The selection here eventually should happen dynamically on some platforms.
274     platformApplyGeneric(srcPixelArray, tmpPixelArray, kernelSizeX, kernelSizeY, paintSize);
275 }
276
277 void FEGaussianBlur::calculateUnscaledKernelSize(unsigned& kernelSizeX, unsigned& kernelSizeY, float stdX, float stdY)
278 {
279     ASSERT(stdX >= 0 && stdY >= 0);
280
281     kernelSizeX = 0;
282     if (stdX)
283         kernelSizeX = max<unsigned>(2, static_cast<unsigned>(floorf(stdX * gaussianKernelFactor() + 0.5f)));
284     kernelSizeY = 0;
285     if (stdY)
286         kernelSizeY = max<unsigned>(2, static_cast<unsigned>(floorf(stdY * gaussianKernelFactor() + 0.5f)));
287     
288     // Limit the kernel size to 1000. A bigger radius won't make a big difference for the result image but
289     // inflates the absolute paint rect to much. This is compatible with Firefox' behavior.
290     if (kernelSizeX > gMaxKernelSize)
291         kernelSizeX = gMaxKernelSize;
292     if (kernelSizeY > gMaxKernelSize)
293         kernelSizeY = gMaxKernelSize;
294 }
295
296 void FEGaussianBlur::calculateKernelSize(Filter* filter, unsigned& kernelSizeX, unsigned& kernelSizeY, float stdX, float stdY)
297 {
298     stdX = filter->applyHorizontalScale(stdX);
299     stdY = filter->applyVerticalScale(stdY);
300
301     calculateUnscaledKernelSize(kernelSizeX, kernelSizeY, stdX, stdY);
302 }
303
304 void FEGaussianBlur::determineAbsolutePaintRect()
305 {
306     unsigned kernelSizeX = 0;
307     unsigned kernelSizeY = 0;
308     calculateKernelSize(filter(), kernelSizeX, kernelSizeY, m_stdX, m_stdY);
309
310     FloatRect absolutePaintRect = inputEffect(0)->absolutePaintRect();
311     // Edge modes other than 'none' do not inflate the affected paint rect.
312     if (m_edgeMode != EDGEMODE_NONE) {
313         setAbsolutePaintRect(enclosingIntRect(absolutePaintRect));
314         return;
315     }
316
317     // We take the half kernel size and multiply it with three, because we run box blur three times.
318     absolutePaintRect.inflateX(3 * kernelSizeX * 0.5f);
319     absolutePaintRect.inflateY(3 * kernelSizeY * 0.5f);
320
321     if (clipsToBounds())
322         absolutePaintRect.intersect(maxEffectRect());
323     else
324         absolutePaintRect.unite(maxEffectRect());
325
326     setAbsolutePaintRect(enclosingIntRect(absolutePaintRect));
327 }
328
329 void FEGaussianBlur::platformApplySoftware()
330 {
331     FilterEffect* in = inputEffect(0);
332
333     Uint8ClampedArray* srcPixelArray = createPremultipliedImageResult();
334     if (!srcPixelArray)
335         return;
336
337     setIsAlphaImage(in->isAlphaImage());
338
339     IntRect effectDrawingRect = requestedRegionOfInputImageData(in->absolutePaintRect());
340     in->copyPremultipliedImage(srcPixelArray, effectDrawingRect);
341
342     if (!m_stdX && !m_stdY)
343         return;
344
345     unsigned kernelSizeX = 0;
346     unsigned kernelSizeY = 0;
347     calculateKernelSize(filter(), kernelSizeX, kernelSizeY, m_stdX, m_stdY);
348
349     IntSize paintSize = absolutePaintRect().size();
350     RefPtr<Uint8ClampedArray> tmpImageData = Uint8ClampedArray::createUninitialized(paintSize.width() * paintSize.height() * 4);
351     Uint8ClampedArray* tmpPixelArray = tmpImageData.get();
352
353     platformApply(srcPixelArray, tmpPixelArray, kernelSizeX, kernelSizeY, paintSize);
354 }
355
356 void FEGaussianBlur::dump()
357 {
358 }
359
360 TextStream& FEGaussianBlur::externalRepresentation(TextStream& ts, int indent) const
361 {
362     writeIndent(ts, indent);
363     ts << "[feGaussianBlur";
364     FilterEffect::externalRepresentation(ts);
365     ts << " stdDeviation=\"" << m_stdX << ", " << m_stdY << "\"]\n";
366     inputEffect(0)->externalRepresentation(ts, indent + 1);
367     return ts;
368 }
369
370 float FEGaussianBlur::calculateStdDeviation(float radius)
371 {
372     // Blur radius represents 2/3 times the kernel size, the dest pixel is half of the radius applied 3 times
373     return max((radius * 2 / 3.f - 0.5f) / gaussianKernelFactor(), 0.f);
374 }
375
376 } // namespace WebCore
377
378 #endif // ENABLE(FILTERS)