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