Some FilterEffect cleanup and logging
[WebKit-https.git] / Source / WebCore / platform / graphics / filters / FEConvolveMatrix.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 Zoltan Herczeg <zherczeg@webkit.org>
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public License
19  * along with this library; see the file COPYING.LIB.  If not, write to
20  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21  * Boston, MA 02110-1301, USA.
22  */
23
24 #include "config.h"
25 #include "FEConvolveMatrix.h"
26
27 #include "Filter.h"
28 #include <wtf/text/TextStream.h>
29
30 #include <runtime/Uint8ClampedArray.h>
31 #include <wtf/ParallelJobs.h>
32 #include <wtf/WorkQueue.h>
33
34 namespace WebCore {
35
36 FEConvolveMatrix::FEConvolveMatrix(Filter& filter, const IntSize& kernelSize,
37     float divisor, float bias, const IntPoint& targetOffset, EdgeModeType edgeMode,
38     const FloatPoint& kernelUnitLength, bool preserveAlpha, const Vector<float>& kernelMatrix)
39     : FilterEffect(filter)
40     , m_kernelSize(kernelSize)
41     , m_divisor(divisor)
42     , m_bias(bias)
43     , m_targetOffset(targetOffset)
44     , m_edgeMode(edgeMode)
45     , m_kernelUnitLength(kernelUnitLength)
46     , m_preserveAlpha(preserveAlpha)
47     , m_kernelMatrix(kernelMatrix)
48 {
49     ASSERT(m_kernelSize.width() > 0);
50     ASSERT(m_kernelSize.height() > 0);
51 }
52
53 Ref<FEConvolveMatrix> FEConvolveMatrix::create(Filter& filter, const IntSize& kernelSize,
54     float divisor, float bias, const IntPoint& targetOffset, EdgeModeType edgeMode,
55     const FloatPoint& kernelUnitLength, bool preserveAlpha, const Vector<float>& kernelMatrix)
56 {
57     return adoptRef(*new FEConvolveMatrix(filter, kernelSize, divisor, bias, targetOffset, edgeMode, kernelUnitLength,
58         preserveAlpha, kernelMatrix));
59 }
60
61 void FEConvolveMatrix::setKernelSize(const IntSize& kernelSize)
62 {
63     ASSERT(kernelSize.width() > 0);
64     ASSERT(kernelSize.height() > 0);
65     m_kernelSize = kernelSize;
66 }
67
68 void FEConvolveMatrix::setKernel(const Vector<float>& kernel)
69 {
70     m_kernelMatrix = kernel; 
71 }
72
73 bool FEConvolveMatrix::setDivisor(float divisor)
74 {
75     ASSERT(divisor);
76     if (m_divisor == divisor)
77         return false;
78     m_divisor = divisor;
79     return true;
80 }
81
82 bool FEConvolveMatrix::setBias(float bias)
83 {
84     if (m_bias == bias)
85         return false;
86     m_bias = bias;
87     return true;
88 }
89
90 bool FEConvolveMatrix::setTargetOffset(const IntPoint& targetOffset)
91 {
92     if (m_targetOffset == targetOffset)
93         return false;
94     m_targetOffset = targetOffset;
95     return true;
96 }
97
98 bool FEConvolveMatrix::setEdgeMode(EdgeModeType edgeMode)
99 {
100     if (m_edgeMode == edgeMode)
101         return false;
102     m_edgeMode = edgeMode;
103     return true;
104 }
105
106 bool FEConvolveMatrix::setKernelUnitLength(const FloatPoint& kernelUnitLength)
107 {
108     ASSERT(kernelUnitLength.x() > 0);
109     ASSERT(kernelUnitLength.y() > 0);
110     if (m_kernelUnitLength == kernelUnitLength)
111         return false;
112     m_kernelUnitLength = kernelUnitLength;
113     return true;
114 }
115
116 bool FEConvolveMatrix::setPreserveAlpha(bool preserveAlpha)
117 {
118     if (m_preserveAlpha == preserveAlpha)
119         return false;
120     m_preserveAlpha = preserveAlpha;
121     return true;
122 }
123
124 /*
125    -----------------------------------
126       ConvolveMatrix implementation
127    -----------------------------------
128
129    The image rectangle is split in the following way:
130
131       +---------------------+
132       |          A          |
133       +---------------------+
134       |   |             |   |
135       | B |      C      | D |
136       |   |             |   |
137       +---------------------+
138       |          E          |
139       +---------------------+
140
141    Where region C contains those pixels, whose values
142    can be calculated without crossing the edge of the rectangle.
143
144    Example:
145       Image size: width: 10, height: 10
146
147       Order (kernel matrix size): width: 3, height 4
148       Target: x:1, y:3
149
150       The following figure shows the target inside the kernel matrix:
151
152         ...
153         ...
154         ...
155         .X.
156
157    The regions in this case are the following:
158       Note: (x1, y1) top-left and (x2, y2) is the bottom-right corner
159       Note: row x2 and column y2 is not part of the region
160             only those (x, y) pixels, where x1 <= x < x2 and y1 <= y < y2
161
162       Region A: x1: 0, y1: 0, x2: 10, y2: 3
163       Region B: x1: 0, y1: 3, x2: 1, y2: 10
164       Region C: x1: 1, y1: 3, x2: 9, y2: 10
165       Region D: x1: 9, y1: 3, x2: 10, y2: 10
166       Region E: x1: 0, y1: 10, x2: 10, y2: 10 (empty region)
167
168    Since region C (often) contains most of the pixels, we implemented
169    a fast algoritm to calculate these values, called fastSetInteriorPixels.
170    For other regions, fastSetOuterPixels is used, which calls getPixelValue,
171    to handle pixels outside of the image. In a rare situations, when
172    kernel matrix is bigger than the image, all pixels are calculated by this
173    function.
174
175    Although these two functions have lot in common, I decided not to make
176    common a template for them, since there are key differences as well,
177    and would make it really hard to understand.
178 */
179
180 static ALWAYS_INLINE unsigned char clampRGBAValue(float channel, unsigned char max = 255)
181 {
182     if (channel <= 0)
183         return 0;
184     if (channel >= max)
185         return max;
186     return channel;
187 }
188
189 template<bool preserveAlphaValues>
190 ALWAYS_INLINE void setDestinationPixels(Uint8ClampedArray* image, int& pixel, float* totals, float divisor, float bias, Uint8ClampedArray* src)
191 {
192     unsigned char maxAlpha = preserveAlphaValues ? 255 : clampRGBAValue(totals[3] / divisor + bias);
193     for (int i = 0; i < 3; ++i)
194         image->set(pixel++, clampRGBAValue(totals[i] / divisor + bias, maxAlpha));
195
196     if (preserveAlphaValues) {
197         image->set(pixel, src->item(pixel));
198         ++pixel;
199     } else
200         image->set(pixel++, maxAlpha);
201 }
202
203 #if COMPILER(MSVC)
204 // Incorrectly diagnosing overwrite of stack in |totals| due to |preserveAlphaValues|.
205 #pragma warning(push)
206 #pragma warning(disable: 4789)
207 #endif
208
209 // Only for region C
210 template<bool preserveAlphaValues>
211 ALWAYS_INLINE void FEConvolveMatrix::fastSetInteriorPixels(PaintingData& paintingData, int clipRight, int clipBottom, int yStart, int yEnd)
212 {
213     // edge mode does not affect these pixels
214     int pixel = (m_targetOffset.y() * paintingData.width + m_targetOffset.x()) * 4;
215     int kernelIncrease = clipRight * 4;
216     int xIncrease = (m_kernelSize.width() - 1) * 4;
217     // Contains the sum of rgb(a) components
218     float totals[3 + (preserveAlphaValues ? 0 : 1)];
219
220     // m_divisor cannot be 0, SVGFEConvolveMatrixElement ensures this
221     ASSERT(m_divisor);
222
223     // Skip the first '(clipBottom - yEnd)' lines
224     pixel += (clipBottom - yEnd) * (xIncrease + (clipRight + 1) * 4);
225     int startKernelPixel = (clipBottom - yEnd) * (xIncrease + (clipRight + 1) * 4);
226
227     for (int y = yEnd + 1; y > yStart; --y) {
228         for (int x = clipRight + 1; x > 0; --x) {
229             int kernelValue = paintingData.kernelMatrix.size() - 1;
230             int kernelPixel = startKernelPixel;
231             int width = m_kernelSize.width();
232
233             totals[0] = 0;
234             totals[1] = 0;
235             totals[2] = 0;
236             if (!preserveAlphaValues)
237                 totals[3] = 0;
238
239             while (kernelValue >= 0) {
240                 totals[0] += paintingData.kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->item(kernelPixel++));
241                 totals[1] += paintingData.kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->item(kernelPixel++));
242                 totals[2] += paintingData.kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->item(kernelPixel++));
243                 if (!preserveAlphaValues)
244                     totals[3] += paintingData.kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->item(kernelPixel));
245                 ++kernelPixel;
246                 --kernelValue;
247                 if (!--width) {
248                     kernelPixel += kernelIncrease;
249                     width = m_kernelSize.width();
250                 }
251             }
252
253             setDestinationPixels<preserveAlphaValues>(paintingData.dstPixelArray, pixel, totals, m_divisor, paintingData.bias, paintingData.srcPixelArray);
254             startKernelPixel += 4;
255         }
256         pixel += xIncrease;
257         startKernelPixel += xIncrease;
258     }
259 }
260
261 ALWAYS_INLINE int FEConvolveMatrix::getPixelValue(PaintingData& paintingData, int x, int y)
262 {
263     if (x >= 0 && x < paintingData.width && y >= 0 && y < paintingData.height)
264         return (y * paintingData.width + x) << 2;
265
266     switch (m_edgeMode) {
267     default: // EDGEMODE_NONE
268         return -1;
269     case EDGEMODE_DUPLICATE:
270         if (x < 0)
271             x = 0;
272         else if (x >= paintingData.width)
273             x = paintingData.width - 1;
274         if (y < 0)
275             y = 0;
276         else if (y >= paintingData.height)
277             y = paintingData.height - 1;
278         return (y * paintingData.width + x) << 2;
279     case EDGEMODE_WRAP:
280         while (x < 0)
281             x += paintingData.width;
282         x %= paintingData.width;
283         while (y < 0)
284             y += paintingData.height;
285         y %= paintingData.height;
286         return (y * paintingData.width + x) << 2;
287     }
288 }
289
290 // For other regions than C
291 template<bool preserveAlphaValues>
292 void FEConvolveMatrix::fastSetOuterPixels(PaintingData& paintingData, int x1, int y1, int x2, int y2)
293 {
294     int pixel = (y1 * paintingData.width + x1) * 4;
295     int height = y2 - y1;
296     int width = x2 - x1;
297     int beginKernelPixelX = x1 - m_targetOffset.x();
298     int startKernelPixelX = beginKernelPixelX;
299     int startKernelPixelY = y1 - m_targetOffset.y();
300     int xIncrease = (paintingData.width - width) * 4;
301     // Contains the sum of rgb(a) components
302     float totals[3 + (preserveAlphaValues ? 0 : 1)];
303
304     // m_divisor cannot be 0, SVGFEConvolveMatrixElement ensures this
305     ASSERT(m_divisor);
306
307     for (int y = height; y > 0; --y) {
308         for (int x = width; x > 0; --x) {
309             int kernelValue = paintingData.kernelMatrix.size() - 1;
310             int kernelPixelX = startKernelPixelX;
311             int kernelPixelY = startKernelPixelY;
312             int width = m_kernelSize.width();
313
314             totals[0] = 0;
315             totals[1] = 0;
316             totals[2] = 0;
317             if (!preserveAlphaValues)
318                 totals[3] = 0;
319
320             while (kernelValue >= 0) {
321                 int pixelIndex = getPixelValue(paintingData, kernelPixelX, kernelPixelY);
322                 if (pixelIndex >= 0) {
323                     totals[0] += paintingData.kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->item(pixelIndex));
324                     totals[1] += paintingData.kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->item(pixelIndex + 1));
325                     totals[2] += paintingData.kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->item(pixelIndex + 2));
326                 }
327                 if (!preserveAlphaValues && pixelIndex >= 0)
328                     totals[3] += paintingData.kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->item(pixelIndex + 3));
329                 ++kernelPixelX;
330                 --kernelValue;
331                 if (!--width) {
332                     kernelPixelX = startKernelPixelX;
333                     ++kernelPixelY;
334                     width = m_kernelSize.width();
335                 }
336             }
337
338             setDestinationPixels<preserveAlphaValues>(paintingData.dstPixelArray, pixel, totals, m_divisor, paintingData.bias, paintingData.srcPixelArray);
339             ++startKernelPixelX;
340         }
341         pixel += xIncrease;
342         startKernelPixelX = beginKernelPixelX;
343         ++startKernelPixelY;
344     }
345 }
346
347 #if COMPILER(MSVC)
348 #pragma warning(pop) // Disable of 4789
349 #endif
350
351 ALWAYS_INLINE void FEConvolveMatrix::setInteriorPixels(PaintingData& paintingData, int clipRight, int clipBottom, int yStart, int yEnd)
352 {
353     // Must be implemented here, since it refers another ALWAYS_INLINE
354     // function, which defined in this C++ source file as well
355     if (m_preserveAlpha)
356         fastSetInteriorPixels<true>(paintingData, clipRight, clipBottom, yStart, yEnd);
357     else
358         fastSetInteriorPixels<false>(paintingData, clipRight, clipBottom, yStart, yEnd);
359 }
360
361 ALWAYS_INLINE void FEConvolveMatrix::setOuterPixels(PaintingData& paintingData, int x1, int y1, int x2, int y2)
362 {
363     // Although this function can be moved to the header, it is implemented here
364     // because setInteriorPixels is also implemented here
365     if (m_preserveAlpha)
366         fastSetOuterPixels<true>(paintingData, x1, y1, x2, y2);
367     else
368         fastSetOuterPixels<false>(paintingData, x1, y1, x2, y2);
369 }
370
371 void FEConvolveMatrix::platformApplySoftware()
372 {
373     FilterEffect* in = inputEffect(0);
374
375     Uint8ClampedArray* resultImage;
376     if (m_preserveAlpha)
377         resultImage = createUnmultipliedImageResult();
378     else
379         resultImage = createPremultipliedImageResult();
380     if (!resultImage)
381         return;
382
383     IntRect effectDrawingRect = requestedRegionOfInputImageData(in->absolutePaintRect());
384
385     RefPtr<Uint8ClampedArray> srcPixelArray;
386     if (m_preserveAlpha)
387         srcPixelArray = in->unmultipliedResult(effectDrawingRect);
388     else
389         srcPixelArray = in->premultipliedResult(effectDrawingRect);
390
391     IntSize paintSize = absolutePaintRect().size();
392     PaintingData paintingData;
393     paintingData.srcPixelArray = srcPixelArray.get();
394     paintingData.dstPixelArray = resultImage;
395     paintingData.width = paintSize.width();
396     paintingData.height = paintSize.height();
397     paintingData.bias = m_bias * 255;
398     paintingData.kernelMatrix = normalizedFloats(m_kernelMatrix);
399
400     // Drawing fully covered pixels
401     int clipRight = paintSize.width() - m_kernelSize.width();
402     int clipBottom = paintSize.height() - m_kernelSize.height();
403
404     if (clipRight < 0 || clipBottom < 0) {
405         // Rare situation, not optimizied for speed
406         setOuterPixels(paintingData, 0, 0, paintSize.width(), paintSize.height());
407         return;
408     }
409
410     if (int iterations = (absolutePaintRect().width() * absolutePaintRect().height()) / s_minimalRectDimension) {
411         int stride = clipBottom / iterations;
412         int chunkCount = (clipBottom + stride - 1) / stride;
413
414         WorkQueue::concurrentApply(chunkCount, [&](size_t index) {
415             int yStart = (stride * index);
416             int yEnd = std::min<int>(stride * (index + 1), clipBottom);
417
418             setInteriorPixels(paintingData, clipRight, clipBottom, yStart, yEnd);
419         });
420     } else
421         setInteriorPixels(paintingData, clipRight, clipBottom, 0, clipBottom);
422
423     clipRight += m_targetOffset.x() + 1;
424     clipBottom += m_targetOffset.y() + 1;
425     if (m_targetOffset.y() > 0)
426         setOuterPixels(paintingData, 0, 0, paintSize.width(), m_targetOffset.y());
427     if (clipBottom < paintSize.height())
428         setOuterPixels(paintingData, 0, clipBottom, paintSize.width(), paintSize.height());
429     if (m_targetOffset.x() > 0)
430         setOuterPixels(paintingData, 0, m_targetOffset.y(), m_targetOffset.x(), clipBottom);
431     if (clipRight < paintSize.width())
432         setOuterPixels(paintingData, clipRight, m_targetOffset.y(), paintSize.width(), clipBottom);
433 }
434
435 static TextStream& operator<<(TextStream& ts, const EdgeModeType& type)
436 {
437     switch (type) {
438     case EDGEMODE_UNKNOWN:
439         ts << "UNKNOWN";
440         break;
441     case EDGEMODE_DUPLICATE:
442         ts << "DUPLICATE";
443         break;
444     case EDGEMODE_WRAP:
445         ts << "WRAP";
446         break;
447     case EDGEMODE_NONE:
448         ts << "NONE";
449         break;
450     }
451     return ts;
452 }
453
454 TextStream& FEConvolveMatrix::externalRepresentation(TextStream& ts, int indent) const
455 {
456     writeIndent(ts, indent);
457     ts << "[feConvolveMatrix";
458     FilterEffect::externalRepresentation(ts);
459     ts << " order=\"" << m_kernelSize << "\" "
460        << "kernelMatrix=\"" << m_kernelMatrix  << "\" "
461        << "divisor=\"" << m_divisor << "\" "
462        << "bias=\"" << m_bias << "\" "
463        << "target=\"" << m_targetOffset << "\" "
464        << "edgeMode=\"" << m_edgeMode << "\" "
465        << "kernelUnitLength=\"" << m_kernelUnitLength << "\" "
466        << "preserveAlpha=\"" << m_preserveAlpha << "\"]\n";
467     inputEffect(0)->externalRepresentation(ts, indent + 1);
468     return ts;
469 }
470
471 }; // namespace WebCore