f9ead8f096e676a15cc8bb5a6269d3fd3f526787
[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     void* dataOrig = src->data();
167     void* dataDone = dst->data();
168     size_t pixelCount = paintSize.height() * paintSize.width() * 4;
169     DIBPixelData original(paintSize, dataOrig, pixelCount, stride, 32);
170     if (0)
171         original.writeToFile(L"C:\\Public\\ImageTest\\original.bmp");
172
173     for (int i = 0; i < 3; ++i) {
174         if (kernelSizeX) {
175             kernelPosition(i, kernelSizeX, dxLeft, dxRight);
176 #if HAVE(ARM_NEON_INTRINSICS)
177             if (!isAlphaImage())
178                 boxBlurNEON(src, dst, kernelSizeX, dxLeft, dxRight, 4, stride, paintSize.width(), paintSize.height());
179             else
180                 boxBlur(src, dst, kernelSizeX, dxLeft, dxRight, 4, stride, paintSize.width(), paintSize.height(), true, m_edgeMode);
181 #else
182             boxBlur(src, dst, kernelSizeX, dxLeft, dxRight, 4, stride, paintSize.width(), paintSize.height(), isAlphaImage(), m_edgeMode);
183 #endif
184             swap(src, dst);
185         }
186
187         if (kernelSizeY) {
188             kernelPosition(i, kernelSizeY, dyLeft, dyRight);
189 #if HAVE(ARM_NEON_INTRINSICS)
190             if (!isAlphaImage())
191                 boxBlurNEON(src, dst, kernelSizeY, dyLeft, dyRight, stride, 4, paintSize.height(), paintSize.width());
192             else
193                 boxBlur(src, dst, kernelSizeY, dyLeft, dyRight, stride, 4, paintSize.height(), paintSize.width(), true, m_edgeMode);
194 #else
195             boxBlur(src, dst, kernelSizeY, dyLeft, dyRight, stride, 4, paintSize.height(), paintSize.width(), isAlphaImage(), m_edgeMode);
196 #endif
197             swap(src, dst);
198         }
199     }
200
201     DIBPixelData blurred(paintSize, dataDone, pixelCount, stride, 32);
202     if (0)
203         blurred.writeToFile(L"C:\\Public\\ImageTest\\blurred.bmp");
204
205     // The final result should be stored in srcPixelArray.
206     if (dst == srcPixelArray) {
207         ASSERT(src->length() == dst->length());
208         memcpy(dst->data(), src->data(), src->length());
209     }
210
211 }
212
213 void FEGaussianBlur::platformApplyWorker(PlatformApplyParameters* parameters)
214 {
215     IntSize paintSize(parameters->width, parameters->height);
216     parameters->filter->platformApplyGeneric(parameters->srcPixelArray.get(), parameters->dstPixelArray.get(),
217         parameters->kernelSizeX, parameters->kernelSizeY, paintSize);
218 }
219
220 inline void FEGaussianBlur::platformApply(Uint8ClampedArray* srcPixelArray, Uint8ClampedArray* tmpPixelArray, unsigned kernelSizeX, unsigned kernelSizeY, IntSize& paintSize)
221 {
222     int scanline = 4 * paintSize.width();
223     int extraHeight = 3 * kernelSizeY * 0.5f;
224     int optimalThreadNumber = (paintSize.width() * paintSize.height()) / (s_minimalRectDimension + extraHeight * paintSize.width());
225
226     size_t pixelCount = paintSize.width() * paintSize.height() * 4;
227     void* dataOrig = srcPixelArray->data();
228     void* tmpOrig = tmpPixelArray->data();
229     DIBPixelData original(paintSize, dataOrig, pixelCount, scanline, 32);
230     if (0)
231         original.writeToFile(L"C:\\Public\\ImageTest\\original.bmp");
232
233     if (optimalThreadNumber > 1) {
234         WTF::ParallelJobs<PlatformApplyParameters> parallelJobs(&platformApplyWorker, optimalThreadNumber);
235
236         int jobs = parallelJobs.numberOfJobs();
237         if (jobs > 1) {
238             // Split the job into "blockHeight"-sized jobs but there a few jobs that need to be slightly larger since
239             // blockHeight * jobs < total size. These extras are handled by the remainder "jobsWithExtra".
240             const int blockHeight = paintSize.height() / jobs;
241             const int jobsWithExtra = paintSize.height() % jobs;
242
243             int currentY = 0;
244             for (int job = 0; job < jobs; job++) {
245                 PlatformApplyParameters& params = parallelJobs.parameter(job);
246                 params.filter = this;
247
248                 int startY = !job ? 0 : currentY - extraHeight;
249                 currentY += job < jobsWithExtra ? blockHeight + 1 : blockHeight;
250                 int endY = job == jobs - 1 ? currentY : currentY + extraHeight;
251
252                 int blockSize = (endY - startY) * scanline;
253                 if (!job) {
254                     params.srcPixelArray = srcPixelArray;
255                     params.dstPixelArray = tmpPixelArray;
256                 } else {
257                     params.srcPixelArray = Uint8ClampedArray::createUninitialized(blockSize);
258                     params.dstPixelArray = Uint8ClampedArray::createUninitialized(blockSize);
259                     memcpy(params.srcPixelArray->data(), srcPixelArray->data() + startY * scanline, blockSize);
260                 }
261
262                 params.width = paintSize.width();
263                 params.height = endY - startY;
264                 params.kernelSizeX = kernelSizeX;
265                 params.kernelSizeY = kernelSizeY;
266             }
267
268             parallelJobs.execute();
269
270             // Copy together the parts of the image.
271             currentY = 0;
272             for (int job = 1; job < jobs; job++) {
273                 PlatformApplyParameters& params = parallelJobs.parameter(job);
274                 int sourceOffset;
275                 int destinationOffset;
276                 int size;
277                 int adjustedBlockHeight = job < jobsWithExtra ? blockHeight + 1 : blockHeight;
278
279                 currentY += adjustedBlockHeight;
280                 sourceOffset = extraHeight * scanline;
281                 destinationOffset = currentY * scanline;
282                 size = adjustedBlockHeight * scanline;
283
284                 memcpy(srcPixelArray->data() + destinationOffset, params.srcPixelArray->data() + sourceOffset, size);
285             }
286
287             // Dump the bitmap to disk.
288             DIBPixelData blurred(paintSize, dataOrig, pixelCount, scanline, 32);
289             if (0)
290                 blurred.writeToFile(L"C:\\Public\\ImageTest\\blurred.bmp");
291
292             return;
293         }
294         // Fallback to single threaded mode.
295     }
296
297     // The selection here eventually should happen dynamically on some platforms.
298     platformApplyGeneric(srcPixelArray, tmpPixelArray, kernelSizeX, kernelSizeY, paintSize);
299 }
300
301 void FEGaussianBlur::calculateUnscaledKernelSize(unsigned& kernelSizeX, unsigned& kernelSizeY, float stdX, float stdY)
302 {
303     ASSERT(stdX >= 0 && stdY >= 0);
304
305     kernelSizeX = 0;
306     if (stdX)
307         kernelSizeX = max<unsigned>(2, static_cast<unsigned>(floorf(stdX * gaussianKernelFactor() + 0.5f)));
308     kernelSizeY = 0;
309     if (stdY)
310         kernelSizeY = max<unsigned>(2, static_cast<unsigned>(floorf(stdY * gaussianKernelFactor() + 0.5f)));
311     
312     // Limit the kernel size to 1000. A bigger radius won't make a big difference for the result image but
313     // inflates the absolute paint rect to much. This is compatible with Firefox' behavior.
314     if (kernelSizeX > gMaxKernelSize)
315         kernelSizeX = gMaxKernelSize;
316     if (kernelSizeY > gMaxKernelSize)
317         kernelSizeY = gMaxKernelSize;
318 }
319
320 void FEGaussianBlur::calculateKernelSize(Filter* filter, unsigned& kernelSizeX, unsigned& kernelSizeY, float stdX, float stdY)
321 {
322     stdX = filter->applyHorizontalScale(stdX);
323     stdY = filter->applyVerticalScale(stdY);
324
325     calculateUnscaledKernelSize(kernelSizeX, kernelSizeY, stdX, stdY);
326 }
327
328 void FEGaussianBlur::determineAbsolutePaintRect()
329 {
330     unsigned kernelSizeX = 0;
331     unsigned kernelSizeY = 0;
332     calculateKernelSize(filter(), kernelSizeX, kernelSizeY, m_stdX, m_stdY);
333
334     FloatRect absolutePaintRect = inputEffect(0)->absolutePaintRect();
335     // Edge modes other than 'none' do not inflate the affected paint rect.
336     if (m_edgeMode != EDGEMODE_NONE) {
337         setAbsolutePaintRect(enclosingIntRect(absolutePaintRect));
338         return;
339     }
340
341     // We take the half kernel size and multiply it with three, because we run box blur three times.
342     absolutePaintRect.inflateX(3 * kernelSizeX * 0.5f);
343     absolutePaintRect.inflateY(3 * kernelSizeY * 0.5f);
344
345     if (clipsToBounds())
346         absolutePaintRect.intersect(maxEffectRect());
347     else
348         absolutePaintRect.unite(maxEffectRect());
349
350     setAbsolutePaintRect(enclosingIntRect(absolutePaintRect));
351 }
352
353 void FEGaussianBlur::platformApplySoftware()
354 {
355     FilterEffect* in = inputEffect(0);
356
357     Uint8ClampedArray* srcPixelArray = createPremultipliedImageResult();
358     if (!srcPixelArray)
359         return;
360
361     setIsAlphaImage(in->isAlphaImage());
362
363     IntRect effectDrawingRect = requestedRegionOfInputImageData(in->absolutePaintRect());
364     in->copyPremultipliedImage(srcPixelArray, effectDrawingRect);
365
366     if (!m_stdX && !m_stdY)
367         return;
368
369     unsigned kernelSizeX = 0;
370     unsigned kernelSizeY = 0;
371     calculateKernelSize(filter(), kernelSizeX, kernelSizeY, m_stdX, m_stdY);
372
373     IntSize paintSize = absolutePaintRect().size();
374     RefPtr<Uint8ClampedArray> tmpImageData = Uint8ClampedArray::createUninitialized(paintSize.width() * paintSize.height() * 4);
375     Uint8ClampedArray* tmpPixelArray = tmpImageData.get();
376
377     platformApply(srcPixelArray, tmpPixelArray, kernelSizeX, kernelSizeY, paintSize);
378 }
379
380 void FEGaussianBlur::dump()
381 {
382 }
383
384 TextStream& FEGaussianBlur::externalRepresentation(TextStream& ts, int indent) const
385 {
386     writeIndent(ts, indent);
387     ts << "[feGaussianBlur";
388     FilterEffect::externalRepresentation(ts);
389     ts << " stdDeviation=\"" << m_stdX << ", " << m_stdY << "\"]\n";
390     inputEffect(0)->externalRepresentation(ts, indent + 1);
391     return ts;
392 }
393
394 float FEGaussianBlur::calculateStdDeviation(float radius)
395 {
396     // Blur radius represents 2/3 times the kernel size, the dest pixel is half of the radius applied 3 times
397     return max((radius * 2 / 3.f - 0.5f) / gaussianKernelFactor(), 0.f);
398 }
399
400 } // namespace WebCore
401
402 #endif // ENABLE(FILTERS)