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