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