[LFC][Painting] Add Display::Run cleanup to TextPainter::clearGlyphDisplayLists
[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 <JavaScriptCore/JSCInlines.h>
39 #include <JavaScriptCore/TypedArrayInlines.h>
40 #include <JavaScriptCore/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     const uint8_t* srcData = srcPixelArray.data();
114     uint8_t* 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             const uint8_t* 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             uint8_t* dstPtr = dstData + pixelByteOffset;
133             *dstPtr = static_cast<uint8_t>(sum / dx);
134
135             // Shift kernel.
136             if (x >= dxLeft) {
137                 unsigned leftOffset = pixelByteOffset - dxLeft * stride;
138                 const uint8_t* srcPtr = srcData + leftOffset;
139                 sum -= *srcPtr;
140             }
141
142             if (x + dxRight < effectWidth) {
143                 unsigned rightOffset = pixelByteOffset + dxRight * stride;
144                 const uint8_t* 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,  effectWidth, effectHeight, maxKernelSize);
157
158     const uint8_t* srcData = srcPixelArray.data();
159     uint8_t* dstData = dstPixelArray.data();
160
161     // Concerning the array width/length: it is Element size + Margin + Border. The number of pixels will be
162     // P = width * height * channels.
163     for (int y = 0; y < effectHeight; ++y) {
164         int line = y * strideLine;
165         int sumR = 0, sumG = 0, sumB = 0, sumA = 0;
166
167         if (edgeMode == EDGEMODE_NONE) {
168             // Fill the kernel.
169             for (int i = 0; i < maxKernelSize; ++i) {
170                 unsigned offset = line + i * stride;
171                 const uint8_t* srcPtr = srcData + offset;
172                 sumR += *srcPtr++;
173                 sumG += *srcPtr++;
174                 sumB += *srcPtr++;
175                 sumA += *srcPtr;
176             }
177
178             // Blurring.
179             for (int x = 0; x < effectWidth; ++x) {
180                 unsigned pixelByteOffset = line + x * stride;
181                 uint8_t* dstPtr = dstData + pixelByteOffset;
182
183                 *dstPtr++ = static_cast<uint8_t>(sumR / dx);
184                 *dstPtr++ = static_cast<uint8_t>(sumG / dx);
185                 *dstPtr++ = static_cast<uint8_t>(sumB / dx);
186                 *dstPtr = static_cast<uint8_t>(sumA / dx);
187
188                 // Shift kernel.
189                 if (x >= dxLeft) {
190                     unsigned leftOffset = pixelByteOffset - dxLeft * stride;
191                     const uint8_t* srcPtr = srcData + leftOffset;
192                     sumR -= srcPtr[0];
193                     sumG -= srcPtr[1];
194                     sumB -= srcPtr[2];
195                     sumA -= srcPtr[3];
196                 }
197
198                 if (x + dxRight < effectWidth) {
199                     unsigned rightOffset = pixelByteOffset + dxRight * stride;
200                     const uint8_t* srcPtr = srcData + rightOffset;
201                     sumR += srcPtr[0];
202                     sumG += srcPtr[1];
203                     sumB += srcPtr[2];
204                     sumA += srcPtr[3];
205                 }
206             }
207
208         } else {
209             // FIXME: Add support for 'wrap' here.
210             // Get edge values for edgeMode 'duplicate'.
211             const uint8_t* edgeValueLeft = srcData + line;
212             const uint8_t* edgeValueRight  = srcData + (line + (effectWidth - 1) * stride);
213
214             // Fill the kernel.
215             for (int i = dxLeft * -1; i < dxRight; ++i) {
216                 // Is this right for negative values of 'i'?
217                 unsigned offset = line + i * stride;
218                 const uint8_t* srcPtr = srcData + offset;
219
220                 if (i < 0) {
221                     sumR += edgeValueLeft[0];
222                     sumG += edgeValueLeft[1];
223                     sumB += edgeValueLeft[2];
224                     sumA += edgeValueLeft[3];
225                 } else if (i >= effectWidth) {
226                     sumR += edgeValueRight[0];
227                     sumG += edgeValueRight[1];
228                     sumB += edgeValueRight[2];
229                     sumA += edgeValueRight[3];
230                 } else {
231                     sumR += *srcPtr++;
232                     sumG += *srcPtr++;
233                     sumB += *srcPtr++;
234                     sumA += *srcPtr;
235                 }
236             }
237
238             // Blurring.
239             for (int x = 0; x < effectWidth; ++x) {
240                 unsigned pixelByteOffset = line + x * stride;
241                 uint8_t* dstPtr = dstData + pixelByteOffset;
242
243                 *dstPtr++ = static_cast<uint8_t>(sumR / dx);
244                 *dstPtr++ = static_cast<uint8_t>(sumG / dx);
245                 *dstPtr++ = static_cast<uint8_t>(sumB / dx);
246                 *dstPtr = static_cast<uint8_t>(sumA / dx);
247
248                 // Shift kernel.
249                 if (x < dxLeft) {
250                     sumR -= edgeValueLeft[0];
251                     sumG -= edgeValueLeft[1];
252                     sumB -= edgeValueLeft[2];
253                     sumA -= edgeValueLeft[3];
254                 } else {
255                     unsigned leftOffset = pixelByteOffset - dxLeft * stride;
256                     const uint8_t* srcPtr = srcData + leftOffset;
257                     sumR -= srcPtr[0];
258                     sumG -= srcPtr[1];
259                     sumB -= srcPtr[2];
260                     sumA -= srcPtr[3];
261                 }
262
263                 if (x + dxRight >= effectWidth) {
264                     sumR += edgeValueRight[0];
265                     sumG += edgeValueRight[1];
266                     sumB += edgeValueRight[2];
267                     sumA += edgeValueRight[3];
268                 } else {
269                     unsigned rightOffset = pixelByteOffset + dxRight * stride;
270                     const uint8_t* srcPtr = srcData + rightOffset;
271                     sumR += srcPtr[0];
272                     sumG += srcPtr[1];
273                     sumB += srcPtr[2];
274                     sumA += srcPtr[3];
275                 }
276             }
277         }
278     }
279 }
280
281 #if USE(ACCELERATE)
282 inline void accelerateBoxBlur(Uint8ClampedArray& ioBuffer, Uint8ClampedArray& tempBuffer, unsigned kernelSize, int stride, int effectWidth, int effectHeight)
283 {
284     if (!ioBuffer.data() || !tempBuffer.data()) {
285         ASSERT_NOT_REACHED();
286         return;
287     }
288
289     if (effectWidth <= 0 || effectHeight <= 0 || stride <= 0) {
290         ASSERT_NOT_REACHED();
291         return;
292     }
293
294     // We must always use an odd radius.
295     if (kernelSize % 2 != 1)
296         kernelSize += 1;
297
298     vImage_Buffer effectInBuffer;
299     effectInBuffer.data = static_cast<void*>(ioBuffer.data());
300     effectInBuffer.width = effectWidth;
301     effectInBuffer.height = effectHeight;
302     effectInBuffer.rowBytes = stride;
303
304     vImage_Buffer effectOutBuffer;
305     effectOutBuffer.data = tempBuffer.data();
306     effectOutBuffer.width = effectWidth;
307     effectOutBuffer.height = effectHeight;
308     effectOutBuffer.rowBytes = stride;
309
310     // Determine the size of a temporary buffer by calling the function first with a special flag. vImage will return
311     // the size needed, or an error (which are all negative).
312     size_t tmpBufferSize = vImageBoxConvolve_ARGB8888(&effectInBuffer, &effectOutBuffer, 0, 0, 0, kernelSize, kernelSize, 0, kvImageEdgeExtend | kvImageGetTempBufferSize);
313     if (tmpBufferSize <= 0)
314         return;
315
316     void* tmpBuffer = fastMalloc(tmpBufferSize);
317     vImageBoxConvolve_ARGB8888(&effectInBuffer, &effectOutBuffer, tmpBuffer, 0, 0, kernelSize, kernelSize, 0, kvImageEdgeExtend);
318     vImageBoxConvolve_ARGB8888(&effectOutBuffer, &effectInBuffer, tmpBuffer, 0, 0, kernelSize, kernelSize, 0, kvImageEdgeExtend);
319     vImageBoxConvolve_ARGB8888(&effectInBuffer, &effectOutBuffer, tmpBuffer, 0, 0, kernelSize, kernelSize, 0, kvImageEdgeExtend);
320     WTF::fastFree(tmpBuffer);
321
322     // The final result should be stored in ioBuffer.
323     ASSERT(ioBuffer.length() == tempBuffer.length());
324     memcpy(ioBuffer.data(), tempBuffer.data(), ioBuffer.length());
325 }
326 #endif
327
328 inline void standardBoxBlur(Uint8ClampedArray& ioBuffer, Uint8ClampedArray& tempBuffer, unsigned kernelSizeX, unsigned kernelSizeY, int stride, IntSize& paintSize, bool isAlphaImage, EdgeModeType edgeMode)
329 {
330     int dxLeft = 0;
331     int dxRight = 0;
332     int dyLeft = 0;
333     int dyRight = 0;
334     
335     Uint8ClampedArray* fromBuffer = &ioBuffer;
336     Uint8ClampedArray* toBuffer = &tempBuffer;
337
338     for (int i = 0; i < 3; ++i) {
339         if (kernelSizeX) {
340             kernelPosition(i, kernelSizeX, dxLeft, dxRight);
341 #if HAVE(ARM_NEON_INTRINSICS)
342             if (!isAlphaImage)
343                 boxBlurNEON(*fromBuffer, *toBuffer, kernelSizeX, dxLeft, dxRight, 4, stride, paintSize.width(), paintSize.height());
344             else
345                 boxBlur(*fromBuffer, *toBuffer, kernelSizeX, dxLeft, dxRight, 4, stride, paintSize.width(), paintSize.height(), true, edgeMode);
346 #else
347             boxBlur(*fromBuffer, *toBuffer, kernelSizeX, dxLeft, dxRight, 4, stride, paintSize.width(), paintSize.height(), isAlphaImage, edgeMode);
348 #endif
349             std::swap(fromBuffer, toBuffer);
350         }
351
352         if (kernelSizeY) {
353             kernelPosition(i, kernelSizeY, dyLeft, dyRight);
354 #if HAVE(ARM_NEON_INTRINSICS)
355             if (!isAlphaImage)
356                 boxBlurNEON(*fromBuffer, *toBuffer, kernelSizeY, dyLeft, dyRight, stride, 4, paintSize.height(), paintSize.width());
357             else
358                 boxBlur(*fromBuffer, *toBuffer, kernelSizeY, dyLeft, dyRight, stride, 4, paintSize.height(), paintSize.width(), true, edgeMode);
359 #else
360             boxBlur(*fromBuffer, *toBuffer, kernelSizeY, dyLeft, dyRight, stride, 4, paintSize.height(), paintSize.width(), isAlphaImage, edgeMode);
361 #endif
362             std::swap(fromBuffer, toBuffer);
363         }
364     }
365
366     // The final result should be stored in ioBuffer.
367     if (&ioBuffer != fromBuffer) {
368         ASSERT(ioBuffer.length() == fromBuffer->length());
369         memcpy(ioBuffer.data(), fromBuffer->data(), ioBuffer.length());
370     }
371 }
372
373 inline void FEGaussianBlur::platformApplyGeneric(Uint8ClampedArray& ioBuffer, Uint8ClampedArray& tmpPixelArray, unsigned kernelSizeX, unsigned kernelSizeY, IntSize& paintSize)
374 {
375     int stride = 4 * paintSize.width();
376
377 #if USE(ACCELERATE)
378     if (kernelSizeX == kernelSizeY && (m_edgeMode == EDGEMODE_NONE || m_edgeMode == EDGEMODE_DUPLICATE)) {
379         accelerateBoxBlur(ioBuffer, tmpPixelArray, kernelSizeX, stride, paintSize.width(), paintSize.height());
380         return;
381     }
382 #endif
383
384     standardBoxBlur(ioBuffer, tmpPixelArray, kernelSizeX, kernelSizeY, stride, paintSize, isAlphaImage(), m_edgeMode);
385 }
386
387 void FEGaussianBlur::platformApplyWorker(PlatformApplyParameters* parameters)
388 {
389     IntSize paintSize(parameters->width, parameters->height);
390     parameters->filter->platformApplyGeneric(*parameters->ioPixelArray, *parameters->tmpPixelArray, parameters->kernelSizeX, parameters->kernelSizeY, paintSize);
391 }
392
393 inline void FEGaussianBlur::platformApply(Uint8ClampedArray& ioBuffer, Uint8ClampedArray& tmpPixelArray, unsigned kernelSizeX, unsigned kernelSizeY, IntSize& paintSize)
394 {
395 #if !USE(ACCELERATE)
396     int scanline = 4 * paintSize.width();
397     int extraHeight = 3 * kernelSizeY * 0.5f;
398     int optimalThreadNumber = (paintSize.width() * paintSize.height()) / (s_minimalRectDimension + extraHeight * paintSize.width());
399
400     if (optimalThreadNumber > 1) {
401         WTF::ParallelJobs<PlatformApplyParameters> parallelJobs(&platformApplyWorker, optimalThreadNumber);
402
403         int jobs = parallelJobs.numberOfJobs();
404         if (jobs > 1) {
405             // Split the job into "blockHeight"-sized jobs but there a few jobs that need to be slightly larger since
406             // blockHeight * jobs < total size. These extras are handled by the remainder "jobsWithExtra".
407             const int blockHeight = paintSize.height() / jobs;
408             const int jobsWithExtra = paintSize.height() % jobs;
409
410             int currentY = 0;
411             for (int job = 0; job < jobs; job++) {
412                 PlatformApplyParameters& params = parallelJobs.parameter(job);
413                 params.filter = this;
414
415                 int startY = !job ? 0 : currentY - extraHeight;
416                 currentY += job < jobsWithExtra ? blockHeight + 1 : blockHeight;
417                 int endY = job == jobs - 1 ? currentY : currentY + extraHeight;
418
419                 int blockSize = (endY - startY) * scanline;
420                 if (!job) {
421                     params.ioPixelArray = &ioBuffer;
422                     params.tmpPixelArray = &tmpPixelArray;
423                 } else {
424                     params.ioPixelArray = Uint8ClampedArray::createUninitialized(blockSize);
425                     params.tmpPixelArray = Uint8ClampedArray::createUninitialized(blockSize);
426                     memcpy(params.ioPixelArray->data(), ioBuffer.data() + startY * scanline, blockSize);
427                 }
428
429                 params.width = paintSize.width();
430                 params.height = endY - startY;
431                 params.kernelSizeX = kernelSizeX;
432                 params.kernelSizeY = kernelSizeY;
433             }
434
435             parallelJobs.execute();
436
437             // Copy together the parts of the image.
438             currentY = 0;
439             for (int job = 1; job < jobs; job++) {
440                 PlatformApplyParameters& params = parallelJobs.parameter(job);
441                 int sourceOffset;
442                 int destinationOffset;
443                 int size;
444                 int adjustedBlockHeight = job < jobsWithExtra ? blockHeight + 1 : blockHeight;
445
446                 currentY += adjustedBlockHeight;
447                 sourceOffset = extraHeight * scanline;
448                 destinationOffset = currentY * scanline;
449                 size = adjustedBlockHeight * scanline;
450
451                 memcpy(ioBuffer.data() + destinationOffset, params.ioPixelArray->data() + sourceOffset, size);
452             }
453             return;
454         }
455         // Fallback to single threaded mode.
456     }
457 #endif
458
459     // The selection here eventually should happen dynamically on some platforms.
460     platformApplyGeneric(ioBuffer, tmpPixelArray, kernelSizeX, kernelSizeY, paintSize);
461 }
462
463 static int clampedToKernelSize(float value)
464 {
465     // Limit the kernel size to 500. A bigger radius won't make a big difference for the result image but
466     // inflates the absolute paint rect too much. This is compatible with Firefox' behavior.
467     unsigned size = std::max<unsigned>(2, static_cast<unsigned>(floorf(value * gaussianKernelFactor() + 0.5f)));
468     return clampTo<int>(std::min(size, static_cast<unsigned>(gMaxKernelSize)));
469 }
470     
471 IntSize FEGaussianBlur::calculateUnscaledKernelSize(FloatSize stdDeviation)
472 {
473     ASSERT(stdDeviation.width() >= 0 && stdDeviation.height() >= 0);
474     IntSize kernelSize;
475
476     if (stdDeviation.width())
477         kernelSize.setWidth(clampedToKernelSize(stdDeviation.width()));
478
479     if (stdDeviation.height())
480         kernelSize.setHeight(clampedToKernelSize(stdDeviation.height()));
481
482     return kernelSize;
483 }
484
485 IntSize FEGaussianBlur::calculateKernelSize(const Filter& filter, FloatSize stdDeviation)
486 {
487     return calculateUnscaledKernelSize(filter.scaledByFilterResolution(stdDeviation));
488 }
489
490 IntSize FEGaussianBlur::calculateOutsetSize(FloatSize stdDeviation)
491 {
492     auto kernelSize = calculateUnscaledKernelSize(stdDeviation);
493
494     // We take the half kernel size and multiply it with three, because we run box blur three times.
495     return { 3 * kernelSize.width() / 2, 3 * kernelSize.height() / 2 };
496 }
497
498 void FEGaussianBlur::determineAbsolutePaintRect()
499 {
500     IntSize kernelSize = calculateKernelSize(filter(), { m_stdX, m_stdY });
501
502     FloatRect absolutePaintRect = inputEffect(0)->absolutePaintRect();
503     // Edge modes other than 'none' do not inflate the affected paint rect.
504     if (m_edgeMode != EDGEMODE_NONE) {
505         setAbsolutePaintRect(enclosingIntRect(absolutePaintRect));
506         return;
507     }
508
509     // We take the half kernel size and multiply it with three, because we run box blur three times.
510     absolutePaintRect.inflateX(3 * kernelSize.width() * 0.5f);
511     absolutePaintRect.inflateY(3 * kernelSize.height() * 0.5f);
512
513     if (clipsToBounds())
514         absolutePaintRect.intersect(maxEffectRect());
515     else
516         absolutePaintRect.unite(maxEffectRect());
517
518     setAbsolutePaintRect(enclosingIntRect(absolutePaintRect));
519 }
520
521 void FEGaussianBlur::platformApplySoftware()
522 {
523     FilterEffect* in = inputEffect(0);
524
525     Uint8ClampedArray* resultPixelArray = createPremultipliedImageResult();
526     if (!resultPixelArray)
527         return;
528
529     setIsAlphaImage(in->isAlphaImage());
530
531     IntRect effectDrawingRect = requestedRegionOfInputImageData(in->absolutePaintRect());
532     in->copyPremultipliedResult(*resultPixelArray, effectDrawingRect);
533
534     if (!m_stdX && !m_stdY)
535         return;
536
537     IntSize kernelSize = calculateKernelSize(filter(), { m_stdX, m_stdY });
538     kernelSize.scale(filter().filterScale());
539
540     IntSize paintSize = absolutePaintRect().size();
541     paintSize.scale(filter().filterScale());
542     auto tmpImageData = Uint8ClampedArray::tryCreateUninitialized((paintSize.area() * 4).unsafeGet());
543     if (!tmpImageData)
544         return;
545
546     platformApply(*resultPixelArray, *tmpImageData, kernelSize.width(), kernelSize.height(), paintSize);
547 }
548
549 IntOutsets FEGaussianBlur::outsets() const
550 {
551     IntSize outsetSize = calculateOutsetSize({ m_stdX, m_stdY });
552     return { outsetSize.height(), outsetSize.width(), outsetSize.height(), outsetSize.width() };
553 }
554
555 TextStream& FEGaussianBlur::externalRepresentation(TextStream& ts, RepresentationType representation) const
556 {
557     ts << indent << "[feGaussianBlur";
558     FilterEffect::externalRepresentation(ts, representation);
559     ts << " stdDeviation=\"" << m_stdX << ", " << m_stdY << "\"]\n";
560
561     TextStream::IndentScope indentScope(ts);
562     inputEffect(0)->externalRepresentation(ts, representation);
563     return ts;
564 }
565
566 } // namespace WebCore