• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
40 using namespace std;
41 
gaussianKernelFactor()42 static inline float gaussianKernelFactor()
43 {
44     return 3 / 4.f * sqrtf(2 * piFloat);
45 }
46 
47 static const unsigned gMaxKernelSize = 1000;
48 
49 namespace WebCore {
50 
FEGaussianBlur(Filter * filter,float x,float y)51 FEGaussianBlur::FEGaussianBlur(Filter* filter, float x, float y)
52     : FilterEffect(filter)
53     , m_stdX(x)
54     , m_stdY(y)
55 {
56 }
57 
create(Filter * filter,float x,float y)58 PassRefPtr<FEGaussianBlur> FEGaussianBlur::create(Filter* filter, float x, float y)
59 {
60     return adoptRef(new FEGaussianBlur(filter, x, y));
61 }
62 
stdDeviationX() const63 float FEGaussianBlur::stdDeviationX() const
64 {
65     return m_stdX;
66 }
67 
setStdDeviationX(float x)68 void FEGaussianBlur::setStdDeviationX(float x)
69 {
70     m_stdX = x;
71 }
72 
stdDeviationY() const73 float FEGaussianBlur::stdDeviationY() const
74 {
75     return m_stdY;
76 }
77 
setStdDeviationY(float y)78 void FEGaussianBlur::setStdDeviationY(float y)
79 {
80     m_stdY = y;
81 }
82 
boxBlur(Uint8ClampedArray * srcPixelArray,Uint8ClampedArray * dstPixelArray,unsigned dx,int dxLeft,int dxRight,int stride,int strideLine,int effectWidth,int effectHeight,bool alphaImage)83 inline 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 
platformApplyGeneric(Uint8ClampedArray * srcPixelArray,Uint8ClampedArray * tmpPixelArray,unsigned kernelSizeX,unsigned kernelSizeY,IntSize & paintSize)110 inline 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 
platformApplyWorker(PlatformApplyParameters * parameters)156 void 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 
platformApply(Uint8ClampedArray * srcPixelArray,Uint8ClampedArray * tmpPixelArray,unsigned kernelSizeX,unsigned kernelSizeY,IntSize & paintSize)163 inline 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 
calculateUnscaledKernelSize(unsigned & kernelSizeX,unsigned & kernelSizeY,float stdX,float stdY)231 void 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 
calculateKernelSize(Filter * filter,unsigned & kernelSizeX,unsigned & kernelSizeY,float stdX,float stdY)250 void 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 
determineAbsolutePaintRect()258 void 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 
mapRect(const FloatRect & rect,bool)270 FloatRect 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 
applySoftware()283 void 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 
applySkia()310 bool 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 
createImageFilter(SkiaImageFilterBuilder * builder)338 PassRefPtr<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 
externalRepresentation(TextStream & ts,int indent) const347 TextStream& 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 
calculateStdDeviation(float radius)357 float 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