e2eefae986762e56d3307b57954f23acc8cfcb73
[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 #include "FEGaussianBlur.h"
27
28 #include "FEGaussianBlurNEON.h"
29 #include "Filter.h"
30 #include "GraphicsContext.h"
31 #include "TextStream.h"
32
33 #include <runtime/JSCInlines.h>
34 #include <runtime/TypedArrayInlines.h>
35 #include <runtime/Uint8ClampedArray.h>
36 #include <wtf/MathExtras.h>
37 #include <wtf/ParallelJobs.h>
38
39 static inline float gaussianKernelFactor()
40 {
41     return 3 / 4.f * sqrtf(2 * piFloat);
42 }
43
44 static const int gMaxKernelSize = 500;
45
46 namespace WebCore {
47
48 FEGaussianBlur::FEGaussianBlur(Filter* filter, float x, float y, EdgeModeType edgeMode)
49     : FilterEffect(filter)
50     , m_stdX(x)
51     , m_stdY(y)
52     , m_edgeMode(edgeMode)
53 {
54 }
55
56 PassRefPtr<FEGaussianBlur> FEGaussianBlur::create(Filter* filter, float x, float y, EdgeModeType edgeMode)
57 {
58     return adoptRef(new FEGaussianBlur(filter, x, y, edgeMode));
59 }
60
61 float FEGaussianBlur::stdDeviationX() const
62 {
63     return m_stdX;
64 }
65
66 void FEGaussianBlur::setStdDeviationX(float x)
67 {
68     m_stdX = x;
69 }
70
71 float FEGaussianBlur::stdDeviationY() const
72 {
73     return m_stdY;
74 }
75
76 void FEGaussianBlur::setStdDeviationY(float y)
77 {
78     m_stdY = y;
79 }
80
81 EdgeModeType FEGaussianBlur::edgeMode() const
82 {
83     return m_edgeMode;
84 }
85
86 void FEGaussianBlur::setEdgeMode(EdgeModeType edgeMode)
87 {
88     m_edgeMode = edgeMode;
89 }
90
91 // This function only operates on Alpha channel.
92 inline void boxBlurAlphaOnly(const Uint8ClampedArray* srcPixelArray, Uint8ClampedArray* dstPixelArray,
93     unsigned dx, int& dxLeft, int& dxRight, int& stride, int& strideLine, int& effectWidth, int& effectHeight, const int& maxKernelSize)
94 {
95     unsigned char* srcData = srcPixelArray->data();
96     unsigned char* dstData = dstPixelArray->data();
97     // Memory alignment is: RGBA, zero-index based.
98     const int channel = 3;
99
100     for (int y = 0; y < effectHeight; ++y) {
101         int line = y * strideLine;
102         int sum = 0;
103
104         // Fill the kernel.
105         for (int i = 0; i < maxKernelSize; ++i) {
106             unsigned offset = line + i * stride;
107             unsigned char* srcPtr = srcData + offset;
108             sum += srcPtr[channel];
109         }
110
111         // Blurring.
112         for (int x = 0; x < effectWidth; ++x) {
113             unsigned pixelByteOffset = line + x * stride + channel;
114             unsigned char* dstPtr = dstData + pixelByteOffset;
115             *dstPtr = static_cast<unsigned char>(sum / dx);
116
117             // Shift kernel.
118             if (x >= dxLeft) {
119                 unsigned leftOffset = pixelByteOffset - dxLeft * stride;
120                 unsigned char* srcPtr = srcData + leftOffset;
121                 sum -= *srcPtr;
122             }
123
124             if (x + dxRight < effectWidth) {
125                 unsigned rightOffset = pixelByteOffset + dxRight * stride;
126                 unsigned char* srcPtr = srcData + rightOffset;
127                 sum += *srcPtr;
128             }
129         }
130     }
131 }
132
133 inline void boxBlur(const Uint8ClampedArray* srcPixelArray, Uint8ClampedArray* dstPixelArray,
134     unsigned dx, int dxLeft, int dxRight, int stride, int strideLine, int effectWidth, int effectHeight, bool alphaImage, EdgeModeType edgeMode)
135 {
136     const int maxKernelSize = std::min(dxRight, effectWidth);
137     if (alphaImage) {
138         return boxBlurAlphaOnly(srcPixelArray, dstPixelArray, dx, dxLeft, dxRight, stride, strideLine,
139             effectWidth, effectHeight, maxKernelSize);
140     }
141
142     unsigned char* srcData = srcPixelArray->data();
143     unsigned char* dstData = dstPixelArray->data();
144
145     // Concerning the array width/length: it is Element size + Margin + Border. The number of pixels will be
146     // P = width * height * channels.
147     for (int y = 0; y < effectHeight; ++y) {
148         int line = y * strideLine;
149         int sumR = 0, sumG = 0, sumB = 0, sumA = 0;
150
151         if (edgeMode == EDGEMODE_NONE) {
152             // Fill the kernel.
153             for (int i = 0; i < maxKernelSize; ++i) {
154                 unsigned offset = line + i * stride;
155                 unsigned char* srcPtr = srcData + offset;
156                 sumR += *srcPtr++;
157                 sumG += *srcPtr++;
158                 sumB += *srcPtr++;
159                 sumA += *srcPtr;
160             }
161
162             // Blurring.
163             for (int x = 0; x < effectWidth; ++x) {
164                 unsigned pixelByteOffset = line + x * stride;
165                 unsigned char* dstPtr = dstData + pixelByteOffset;
166
167                 *dstPtr++ = static_cast<unsigned char>(sumR / dx);
168                 *dstPtr++ = static_cast<unsigned char>(sumG / dx);
169                 *dstPtr++ = static_cast<unsigned char>(sumB / dx);
170                 *dstPtr = static_cast<unsigned char>(sumA / dx);
171
172                 // Shift kernel.
173                 if (x >= dxLeft) {
174                     unsigned leftOffset = pixelByteOffset - dxLeft * stride;
175                     unsigned char* srcPtr = srcData + leftOffset;
176                     sumR -= srcPtr[0];
177                     sumG -= srcPtr[1];
178                     sumB -= srcPtr[2];
179                     sumA -= srcPtr[3];
180                 }
181
182                 if (x + dxRight < effectWidth) {
183                     unsigned rightOffset = pixelByteOffset + dxRight * stride;
184                     unsigned char* srcPtr = srcData + rightOffset;
185                     sumR += srcPtr[0];
186                     sumG += srcPtr[1];
187                     sumB += srcPtr[2];
188                     sumA += srcPtr[3];
189                 }
190             }
191
192         } else {
193             // FIXME: Add support for 'wrap' here.
194             // Get edge values for edgeMode 'duplicate'.
195             unsigned char* edgeValueLeft = srcData + line;
196             unsigned char* edgeValueRight  = srcData + (line + (effectWidth - 1) * stride);
197
198             // Fill the kernel.
199             for (int i = dxLeft * -1; i < dxRight; ++i) {
200                 // Is this right for negative values of 'i'?
201                 unsigned offset = line + i * stride;
202                 unsigned char* srcPtr = srcData + offset;
203
204                 if (i < 0) {
205                     sumR += edgeValueLeft[0];
206                     sumG += edgeValueLeft[1];
207                     sumB += edgeValueLeft[2];
208                     sumA += edgeValueLeft[3];
209                 } else if (i >= effectWidth) {
210                     sumR += edgeValueRight[0];
211                     sumG += edgeValueRight[1];
212                     sumB += edgeValueRight[2];
213                     sumA += edgeValueRight[3];
214                 } else {
215                     sumR += *srcPtr++;
216                     sumG += *srcPtr++;
217                     sumB += *srcPtr++;
218                     sumA += *srcPtr;
219                 }
220             }
221
222             // Blurring.
223             for (int x = 0; x < effectWidth; ++x) {
224                 unsigned pixelByteOffset = line + x * stride;
225                 unsigned char* dstPtr = dstData + pixelByteOffset;
226
227                 *dstPtr++ = static_cast<unsigned char>(sumR / dx);
228                 *dstPtr++ = static_cast<unsigned char>(sumG / dx);
229                 *dstPtr++ = static_cast<unsigned char>(sumB / dx);
230                 *dstPtr = static_cast<unsigned char>(sumA / dx);
231
232                 // Shift kernel.
233                 if (x < dxLeft) {
234                     sumR -= edgeValueLeft[0];
235                     sumG -= edgeValueLeft[1];
236                     sumB -= edgeValueLeft[2];
237                     sumA -= edgeValueLeft[3];
238                 } else {
239                     unsigned leftOffset = pixelByteOffset - dxLeft * stride;
240                     unsigned char* srcPtr = srcData + leftOffset;
241                     sumR -= srcPtr[0];
242                     sumG -= srcPtr[1];
243                     sumB -= srcPtr[2];
244                     sumA -= srcPtr[3];
245                 }
246
247                 if (x + dxRight >= effectWidth) {
248                     sumR += edgeValueRight[0];
249                     sumG += edgeValueRight[1];
250                     sumB += edgeValueRight[2];
251                     sumA += edgeValueRight[3];
252                 } else {
253                     unsigned rightOffset = pixelByteOffset + dxRight * stride;
254                     unsigned char* srcPtr = srcData + rightOffset;
255                     sumR += srcPtr[0];
256                     sumG += srcPtr[1];
257                     sumB += srcPtr[2];
258                     sumA += srcPtr[3];
259                 }
260             }
261         }
262     }
263 }
264
265 inline void FEGaussianBlur::platformApplyGeneric(Uint8ClampedArray* srcPixelArray, Uint8ClampedArray* tmpPixelArray, unsigned kernelSizeX, unsigned kernelSizeY, IntSize& paintSize)
266 {
267     int stride = 4 * paintSize.width();
268     int dxLeft = 0;
269     int dxRight = 0;
270     int dyLeft = 0;
271     int dyRight = 0;
272     Uint8ClampedArray* src = srcPixelArray;
273     Uint8ClampedArray* dst = tmpPixelArray;
274
275     for (int i = 0; i < 3; ++i) {
276         if (kernelSizeX) {
277             kernelPosition(i, kernelSizeX, dxLeft, dxRight);
278 #if HAVE(ARM_NEON_INTRINSICS)
279             if (!isAlphaImage())
280                 boxBlurNEON(src, dst, kernelSizeX, dxLeft, dxRight, 4, stride, paintSize.width(), paintSize.height());
281             else
282                 boxBlur(src, dst, kernelSizeX, dxLeft, dxRight, 4, stride, paintSize.width(), paintSize.height(), true, m_edgeMode);
283 #else
284             boxBlur(src, dst, kernelSizeX, dxLeft, dxRight, 4, stride, paintSize.width(), paintSize.height(), isAlphaImage(), m_edgeMode);
285 #endif
286             std::swap(src, dst);
287         }
288
289         if (kernelSizeY) {
290             kernelPosition(i, kernelSizeY, dyLeft, dyRight);
291 #if HAVE(ARM_NEON_INTRINSICS)
292             if (!isAlphaImage())
293                 boxBlurNEON(src, dst, kernelSizeY, dyLeft, dyRight, stride, 4, paintSize.height(), paintSize.width());
294             else
295                 boxBlur(src, dst, kernelSizeY, dyLeft, dyRight, stride, 4, paintSize.height(), paintSize.width(), true, m_edgeMode);
296 #else
297             boxBlur(src, dst, kernelSizeY, dyLeft, dyRight, stride, 4, paintSize.height(), paintSize.width(), isAlphaImage(), m_edgeMode);
298 #endif
299             std::swap(src, dst);
300         }
301     }
302
303     // The final result should be stored in srcPixelArray.
304     if (dst == srcPixelArray) {
305         ASSERT(src->length() == dst->length());
306         memcpy(dst->data(), src->data(), src->length());
307     }
308
309 }
310
311 void FEGaussianBlur::platformApplyWorker(PlatformApplyParameters* parameters)
312 {
313     IntSize paintSize(parameters->width, parameters->height);
314     parameters->filter->platformApplyGeneric(parameters->srcPixelArray.get(), parameters->dstPixelArray.get(),
315         parameters->kernelSizeX, parameters->kernelSizeY, paintSize);
316 }
317
318 inline void FEGaussianBlur::platformApply(Uint8ClampedArray* srcPixelArray, Uint8ClampedArray* tmpPixelArray, unsigned kernelSizeX, unsigned kernelSizeY, IntSize& paintSize)
319 {
320     int scanline = 4 * paintSize.width();
321     int extraHeight = 3 * kernelSizeY * 0.5f;
322     int optimalThreadNumber = (paintSize.width() * paintSize.height()) / (s_minimalRectDimension + extraHeight * paintSize.width());
323
324     if (optimalThreadNumber > 1) {
325         WTF::ParallelJobs<PlatformApplyParameters> parallelJobs(&platformApplyWorker, optimalThreadNumber);
326
327         int jobs = parallelJobs.numberOfJobs();
328         if (jobs > 1) {
329             // Split the job into "blockHeight"-sized jobs but there a few jobs that need to be slightly larger since
330             // blockHeight * jobs < total size. These extras are handled by the remainder "jobsWithExtra".
331             const int blockHeight = paintSize.height() / jobs;
332             const int jobsWithExtra = paintSize.height() % jobs;
333
334             int currentY = 0;
335             for (int job = 0; job < jobs; job++) {
336                 PlatformApplyParameters& params = parallelJobs.parameter(job);
337                 params.filter = this;
338
339                 int startY = !job ? 0 : currentY - extraHeight;
340                 currentY += job < jobsWithExtra ? blockHeight + 1 : blockHeight;
341                 int endY = job == jobs - 1 ? currentY : currentY + extraHeight;
342
343                 int blockSize = (endY - startY) * scanline;
344                 if (!job) {
345                     params.srcPixelArray = srcPixelArray;
346                     params.dstPixelArray = tmpPixelArray;
347                 } else {
348                     params.srcPixelArray = Uint8ClampedArray::createUninitialized(blockSize);
349                     params.dstPixelArray = Uint8ClampedArray::createUninitialized(blockSize);
350                     memcpy(params.srcPixelArray->data(), srcPixelArray->data() + startY * scanline, blockSize);
351                 }
352
353                 params.width = paintSize.width();
354                 params.height = endY - startY;
355                 params.kernelSizeX = kernelSizeX;
356                 params.kernelSizeY = kernelSizeY;
357             }
358
359             parallelJobs.execute();
360
361             // Copy together the parts of the image.
362             currentY = 0;
363             for (int job = 1; job < jobs; job++) {
364                 PlatformApplyParameters& params = parallelJobs.parameter(job);
365                 int sourceOffset;
366                 int destinationOffset;
367                 int size;
368                 int adjustedBlockHeight = job < jobsWithExtra ? blockHeight + 1 : blockHeight;
369
370                 currentY += adjustedBlockHeight;
371                 sourceOffset = extraHeight * scanline;
372                 destinationOffset = currentY * scanline;
373                 size = adjustedBlockHeight * scanline;
374
375                 memcpy(srcPixelArray->data() + destinationOffset, params.srcPixelArray->data() + sourceOffset, size);
376             }
377             return;
378         }
379         // Fallback to single threaded mode.
380     }
381
382     // The selection here eventually should happen dynamically on some platforms.
383     platformApplyGeneric(srcPixelArray, tmpPixelArray, kernelSizeX, kernelSizeY, paintSize);
384 }
385
386 IntSize FEGaussianBlur::calculateUnscaledKernelSize(const FloatPoint& stdDeviation)
387 {
388     ASSERT(stdDeviation.x() >= 0 && stdDeviation.y() >= 0);
389     IntSize kernelSize;
390
391     // Limit the kernel size to 500. A bigger radius won't make a big difference for the result image but
392     // inflates the absolute paint rect too much. This is compatible with Firefox' behavior.
393     if (stdDeviation.x()) {
394         int size = std::max<unsigned>(2, static_cast<unsigned>(floorf(stdDeviation.x() * gaussianKernelFactor() + 0.5f)));
395         kernelSize.setWidth(std::min(size, gMaxKernelSize));
396     }
397
398     if (stdDeviation.y()) {
399         int size = std::max<unsigned>(2, static_cast<unsigned>(floorf(stdDeviation.y() * gaussianKernelFactor() + 0.5f)));
400         kernelSize.setHeight(std::min(size, gMaxKernelSize));
401     }
402
403     return kernelSize;
404 }
405
406 IntSize FEGaussianBlur::calculateKernelSize(const Filter& filter, const FloatPoint& stdDeviation)
407 {
408     FloatPoint stdFilterScaled(filter.applyHorizontalScale(stdDeviation.x()), filter.applyVerticalScale(stdDeviation.y()));
409     return calculateUnscaledKernelSize(stdFilterScaled);
410 }
411
412 void FEGaussianBlur::determineAbsolutePaintRect()
413 {
414     IntSize kernelSize = calculateKernelSize(filter(), FloatPoint(m_stdX, m_stdY));
415
416     FloatRect absolutePaintRect = inputEffect(0)->absolutePaintRect();
417     // Edge modes other than 'none' do not inflate the affected paint rect.
418     if (m_edgeMode != EDGEMODE_NONE) {
419         setAbsolutePaintRect(enclosingIntRect(absolutePaintRect));
420         return;
421     }
422
423     // We take the half kernel size and multiply it with three, because we run box blur three times.
424     absolutePaintRect.inflateX(3 * kernelSize.width() * 0.5f);
425     absolutePaintRect.inflateY(3 * kernelSize.height() * 0.5f);
426
427     if (clipsToBounds())
428         absolutePaintRect.intersect(maxEffectRect());
429     else
430         absolutePaintRect.unite(maxEffectRect());
431
432     setAbsolutePaintRect(enclosingIntRect(absolutePaintRect));
433 }
434
435 void FEGaussianBlur::platformApplySoftware()
436 {
437     FilterEffect* in = inputEffect(0);
438
439     Uint8ClampedArray* srcPixelArray = createPremultipliedImageResult();
440     if (!srcPixelArray)
441         return;
442
443     setIsAlphaImage(in->isAlphaImage());
444
445     IntRect effectDrawingRect = requestedRegionOfInputImageData(in->absolutePaintRect());
446     in->copyPremultipliedImage(srcPixelArray, effectDrawingRect);
447
448     if (!m_stdX && !m_stdY)
449         return;
450
451     IntSize kernelSize = calculateKernelSize(filter(), FloatPoint(m_stdX, m_stdY));
452     kernelSize.scale(filter().filterScale());
453
454     IntSize paintSize = absolutePaintRect().size();
455     paintSize.scale(filter().filterScale());
456     RefPtr<Uint8ClampedArray> tmpImageData = Uint8ClampedArray::createUninitialized(paintSize.width() * paintSize.height() * 4);
457     Uint8ClampedArray* tmpPixelArray = tmpImageData.get();
458
459     platformApply(srcPixelArray, tmpPixelArray, kernelSize.width(), kernelSize.height(), paintSize);
460 }
461
462 void FEGaussianBlur::dump()
463 {
464 }
465
466 TextStream& FEGaussianBlur::externalRepresentation(TextStream& ts, int indent) const
467 {
468     writeIndent(ts, indent);
469     ts << "[feGaussianBlur";
470     FilterEffect::externalRepresentation(ts);
471     ts << " stdDeviation=\"" << m_stdX << ", " << m_stdY << "\"]\n";
472     inputEffect(0)->externalRepresentation(ts, indent + 1);
473     return ts;
474 }
475
476 } // namespace WebCore