initial import
[vuplus_webkit] / 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
26 #if ENABLE(FILTERS)
27 #include "FEConvolveMatrix.h"
28
29 #include "Filter.h"
30 #include "RenderTreeAsText.h"
31 #include "TextStream.h"
32
33 #include <wtf/ByteArray.h>
34 #include <wtf/ParallelJobs.h>
35
36 namespace WebCore {
37
38 FEConvolveMatrix::FEConvolveMatrix(Filter* filter, const IntSize& kernelSize,
39     float divisor, float bias, const IntPoint& targetOffset, EdgeModeType edgeMode,
40     const FloatPoint& kernelUnitLength, bool preserveAlpha, const Vector<float>& kernelMatrix)
41     : FilterEffect(filter)
42     , m_kernelSize(kernelSize)
43     , m_divisor(divisor)
44     , m_bias(bias)
45     , m_targetOffset(targetOffset)
46     , m_edgeMode(edgeMode)
47     , m_kernelUnitLength(kernelUnitLength)
48     , m_preserveAlpha(preserveAlpha)
49     , m_kernelMatrix(kernelMatrix)
50 {
51 }
52
53 PassRefPtr<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
62 IntSize FEConvolveMatrix::kernelSize() const
63 {
64     return m_kernelSize;
65 }
66
67 void FEConvolveMatrix::setKernelSize(const IntSize& kernelSize)
68 {
69     m_kernelSize = kernelSize; 
70 }
71
72 const Vector<float>& FEConvolveMatrix::kernel() const
73 {
74     return m_kernelMatrix; 
75 }
76
77 void FEConvolveMatrix::setKernel(const Vector<float>& kernel)
78 {
79     m_kernelMatrix = kernel; 
80 }
81
82 float FEConvolveMatrix::divisor() const
83 {
84     return m_divisor; 
85 }
86
87 bool FEConvolveMatrix::setDivisor(float divisor)
88 {
89     if (m_divisor == divisor)
90         return false;
91     m_divisor = divisor;
92     return true;
93 }
94
95 float FEConvolveMatrix::bias() const
96 {
97     return m_bias; 
98 }
99
100 bool FEConvolveMatrix::setBias(float bias)
101 {
102     if (m_bias == bias)
103         return false;
104     m_bias = bias;
105     return true;
106 }
107
108 IntPoint FEConvolveMatrix::targetOffset() const
109 {
110     return m_targetOffset; 
111 }
112
113 bool FEConvolveMatrix::setTargetOffset(const IntPoint& targetOffset)
114 {
115     if (m_targetOffset == targetOffset)
116         return false;
117     m_targetOffset = targetOffset;
118     return true;
119 }
120
121 EdgeModeType FEConvolveMatrix::edgeMode() const
122 {
123     return m_edgeMode;
124 }
125
126 bool FEConvolveMatrix::setEdgeMode(EdgeModeType edgeMode)
127 {
128     if (m_edgeMode == edgeMode)
129         return false;
130     m_edgeMode = edgeMode;
131     return true;
132 }
133
134 FloatPoint FEConvolveMatrix::kernelUnitLength() const
135 {
136     return m_kernelUnitLength; 
137 }
138
139 bool FEConvolveMatrix::setKernelUnitLength(const FloatPoint& kernelUnitLength)
140 {
141     if (m_kernelUnitLength == kernelUnitLength)
142         return false;
143     m_kernelUnitLength = kernelUnitLength;
144     return true;
145 }
146
147 bool FEConvolveMatrix::preserveAlpha() const
148 {
149     return m_preserveAlpha; 
150 }
151
152 bool FEConvolveMatrix::setPreserveAlpha(bool preserveAlpha)
153 {
154     if (m_preserveAlpha == preserveAlpha)
155         return false;
156     m_preserveAlpha = preserveAlpha;
157     return true;
158 }
159
160 /*
161    -----------------------------------
162       ConvolveMatrix implementation
163    -----------------------------------
164
165    The image rectangle is split in the following way:
166
167       +---------------------+
168       |          A          |
169       +---------------------+
170       |   |             |   |
171       | B |      C      | D |
172       |   |             |   |
173       +---------------------+
174       |          E          |
175       +---------------------+
176
177    Where region C contains those pixels, whose values
178    can be calculated without crossing the edge of the rectangle.
179
180    Example:
181       Image size: width: 10, height: 10
182
183       Order (kernel matrix size): width: 3, height 4
184       Target: x:1, y:3
185
186       The following figure shows the target inside the kernel matrix:
187
188         ...
189         ...
190         ...
191         .X.
192
193    The regions in this case are the following:
194       Note: (x1, y1) top-left and (x2, y2) is the bottom-right corner
195       Note: row x2 and column y2 is not part of the region
196             only those (x, y) pixels, where x1 <= x < x2 and y1 <= y < y2
197
198       Region A: x1: 0, y1: 0, x2: 10, y2: 3
199       Region B: x1: 0, y1: 3, x2: 1, y2: 10
200       Region C: x1: 1, y1: 3, x2: 9, y2: 10
201       Region D: x1: 9, y1: 3, x2: 10, y2: 10
202       Region E: x1: 0, y1: 10, x2: 10, y2: 10 (empty region)
203
204    Since region C (often) contains most of the pixels, we implemented
205    a fast algoritm to calculate these values, called fastSetInteriorPixels.
206    For other regions, fastSetOuterPixels is used, which calls getPixelValue,
207    to handle pixels outside of the image. In a rare situations, when
208    kernel matrix is bigger than the image, all pixels are calculated by this
209    function.
210
211    Although these two functions have lot in common, I decided not to make
212    common a template for them, since there are key differences as well,
213    and would make it really hard to understand.
214 */
215
216 static ALWAYS_INLINE unsigned char clampRGBAValue(float channel, unsigned char max = 255)
217 {
218     if (channel <= 0)
219         return 0;
220     if (channel >= max)
221         return max;
222     return channel;
223 }
224
225 template<bool preserveAlphaValues>
226 ALWAYS_INLINE void setDestinationPixels(ByteArray* image, int& pixel, float* totals, float divisor, float bias, ByteArray* src)
227 {
228     unsigned char maxAlpha = preserveAlphaValues ? 255 : clampRGBAValue(totals[3] / divisor + bias);
229     for (int i = 0; i < 3; ++i)
230         image->set(pixel++, clampRGBAValue(totals[i] / divisor + bias, maxAlpha));
231
232     if (preserveAlphaValues) {
233         image->set(pixel, src->get(pixel));
234         ++pixel;
235     } else
236         image->set(pixel++, maxAlpha);
237 }
238
239 // Only for region C
240 template<bool preserveAlphaValues>
241 ALWAYS_INLINE void FEConvolveMatrix::fastSetInteriorPixels(PaintingData& paintingData, int clipRight, int clipBottom, int yStart, int yEnd)
242 {
243     // edge mode does not affect these pixels
244     int pixel = (m_targetOffset.y() * paintingData.width + m_targetOffset.x()) * 4;
245     int kernelIncrease = clipRight * 4;
246     int xIncrease = (m_kernelSize.width() - 1) * 4;
247     // Contains the sum of rgb(a) components
248     float totals[3 + (preserveAlphaValues ? 0 : 1)];
249
250     // m_divisor cannot be 0, SVGFEConvolveMatrixElement ensures this
251     ASSERT(m_divisor);
252
253     // Skip the first '(clipBottom - yEnd)' lines
254     pixel += (clipBottom - yEnd) * (xIncrease + (clipRight + 1) * (preserveAlphaValues ? 3 : 4));
255     int startKernelPixel = (clipBottom - yEnd) * (xIncrease + (clipRight + 1) * 4);
256
257     for (int y = yEnd + 1; y > yStart; --y) {
258         for (int x = clipRight + 1; x > 0; --x) {
259             int kernelValue = m_kernelMatrix.size() - 1;
260             int kernelPixel = startKernelPixel;
261             int width = m_kernelSize.width();
262
263             totals[0] = 0;
264             totals[1] = 0;
265             totals[2] = 0;
266             if (!preserveAlphaValues)
267                 totals[3] = 0;
268
269             while (kernelValue >= 0) {
270                 totals[0] += m_kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->get(kernelPixel++));
271                 totals[1] += m_kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->get(kernelPixel++));
272                 totals[2] += m_kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->get(kernelPixel++));
273                 if (!preserveAlphaValues)
274                     totals[3] += m_kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->get(kernelPixel));
275                 ++kernelPixel;
276                 --kernelValue;
277                 if (!--width) {
278                     kernelPixel += kernelIncrease;
279                     width = m_kernelSize.width();
280                 }
281             }
282
283             setDestinationPixels<preserveAlphaValues>(paintingData.dstPixelArray, pixel, totals, m_divisor, paintingData.bias, paintingData.srcPixelArray);
284             startKernelPixel += 4;
285         }
286         pixel += xIncrease;
287         startKernelPixel += xIncrease;
288     }
289 }
290
291 ALWAYS_INLINE int FEConvolveMatrix::getPixelValue(PaintingData& paintingData, int x, int y)
292 {
293     if (x >= 0 && x < paintingData.width && y >= 0 && y < paintingData.height)
294         return (y * paintingData.width + x) << 2;
295
296     switch (m_edgeMode) {
297     default: // EDGEMODE_NONE
298         return -1;
299     case EDGEMODE_DUPLICATE:
300         if (x < 0)
301             x = 0;
302         else if (x >= paintingData.width)
303             x = paintingData.width - 1;
304         if (y < 0)
305             y = 0;
306         else if (y >= paintingData.height)
307             y = paintingData.height - 1;
308         return (y * paintingData.width + x) << 2;
309     case EDGEMODE_WRAP:
310         while (x < 0)
311             x += paintingData.width;
312         x %= paintingData.width;
313         while (y < 0)
314             y += paintingData.height;
315         y %= paintingData.height;
316         return (y * paintingData.width + x) << 2;
317     }
318 }
319
320 // For other regions than C
321 template<bool preserveAlphaValues>
322 void FEConvolveMatrix::fastSetOuterPixels(PaintingData& paintingData, int x1, int y1, int x2, int y2)
323 {
324     int pixel = (y1 * paintingData.width + x1) * 4;
325     int height = y2 - y1;
326     int width = x2 - x1;
327     int beginKernelPixelX = x1 - m_targetOffset.x();
328     int startKernelPixelX = beginKernelPixelX;
329     int startKernelPixelY = y1 - m_targetOffset.y();
330     int xIncrease = (paintingData.width - width) * 4;
331     // Contains the sum of rgb(a) components
332     float totals[3 + (preserveAlphaValues ? 0 : 1)];
333
334     // m_divisor cannot be 0, SVGFEConvolveMatrixElement ensures this
335     ASSERT(m_divisor);
336
337     for (int y = height; y > 0; --y) {
338         for (int x = width; x > 0; --x) {
339             int kernelValue = m_kernelMatrix.size() - 1;
340             int kernelPixelX = startKernelPixelX;
341             int kernelPixelY = startKernelPixelY;
342             int width = m_kernelSize.width();
343
344             totals[0] = 0;
345             totals[1] = 0;
346             totals[2] = 0;
347             if (!preserveAlphaValues)
348                 totals[3] = 0;
349
350             while (kernelValue >= 0) {
351                 int pixelIndex = getPixelValue(paintingData, kernelPixelX, kernelPixelY);
352                 if (pixelIndex >= 0) {
353                     totals[0] += m_kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->get(pixelIndex));
354                     totals[1] += m_kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->get(pixelIndex + 1));
355                     totals[2] += m_kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->get(pixelIndex + 2));
356                 }
357                 if (!preserveAlphaValues && pixelIndex >= 0)
358                     totals[3] += m_kernelMatrix[kernelValue] * static_cast<float>(paintingData.srcPixelArray->get(pixelIndex + 3));
359                 ++kernelPixelX;
360                 --kernelValue;
361                 if (!--width) {
362                     kernelPixelX = startKernelPixelX;
363                     ++kernelPixelY;
364                     width = m_kernelSize.width();
365                 }
366             }
367
368             setDestinationPixels<preserveAlphaValues>(paintingData.dstPixelArray, pixel, totals, m_divisor, paintingData.bias, paintingData.srcPixelArray);
369             ++startKernelPixelX;
370         }
371         pixel += xIncrease;
372         startKernelPixelX = beginKernelPixelX;
373         ++startKernelPixelY;
374     }
375 }
376
377 ALWAYS_INLINE void FEConvolveMatrix::setInteriorPixels(PaintingData& paintingData, int clipRight, int clipBottom, int yStart, int yEnd)
378 {
379     // Must be implemented here, since it refers another ALWAYS_INLINE
380     // function, which defined in this C++ source file as well
381     if (m_preserveAlpha)
382         fastSetInteriorPixels<true>(paintingData, clipRight, clipBottom, yStart, yEnd);
383     else
384         fastSetInteriorPixels<false>(paintingData, clipRight, clipBottom, yStart, yEnd);
385 }
386
387 ALWAYS_INLINE void FEConvolveMatrix::setOuterPixels(PaintingData& paintingData, int x1, int y1, int x2, int y2)
388 {
389     // Although this function can be moved to the header, it is implemented here
390     // because setInteriorPixels is also implemented here
391     if (m_preserveAlpha)
392         fastSetOuterPixels<true>(paintingData, x1, y1, x2, y2);
393     else
394         fastSetOuterPixels<false>(paintingData, x1, y1, x2, y2);
395 }
396
397 #if ENABLE(PARALLEL_JOBS)
398 void FEConvolveMatrix::setInteriorPixelsWorker(InteriorPixelParameters* param)
399 {
400     param->filter->setInteriorPixels(*param->paintingData, param->clipRight, param->clipBottom, param->yStart, param->yEnd);
401 }
402 #endif
403
404 void FEConvolveMatrix::apply()
405 {
406     if (hasResult())
407         return;
408     FilterEffect* in = inputEffect(0);
409     in->apply();
410     if (!in->hasResult())
411         return;
412
413     ByteArray* resultImage;
414     if (m_preserveAlpha)
415         resultImage = createUnmultipliedImageResult();
416     else
417         resultImage = createPremultipliedImageResult();
418     if (!resultImage)
419         return;
420
421     IntRect effectDrawingRect = requestedRegionOfInputImageData(in->absolutePaintRect());
422
423     RefPtr<ByteArray> srcPixelArray;
424     if (m_preserveAlpha)
425         srcPixelArray = in->asUnmultipliedImage(effectDrawingRect);
426     else
427         srcPixelArray = in->asPremultipliedImage(effectDrawingRect);
428
429     IntSize paintSize = absolutePaintRect().size();
430     PaintingData paintingData;
431     paintingData.srcPixelArray = srcPixelArray.get();
432     paintingData.dstPixelArray = resultImage;
433     paintingData.width = paintSize.width();
434     paintingData.height = paintSize.height();
435     paintingData.bias = m_bias * 255;
436
437     // Drawing fully covered pixels
438     int clipRight = paintSize.width() - m_kernelSize.width();
439     int clipBottom = paintSize.height() - m_kernelSize.height();
440
441     if (clipRight >= 0 && clipBottom >= 0) {
442
443 #if ENABLE(PARALLEL_JOBS)
444         int optimalThreadNumber = (absolutePaintRect().width() * absolutePaintRect().height()) / s_minimalRectDimension;
445         if (optimalThreadNumber > 1) {
446             ParallelJobs<InteriorPixelParameters> parallelJobs(&WebCore::FEConvolveMatrix::setInteriorPixelsWorker, optimalThreadNumber);
447             const int numOfThreads = parallelJobs.numberOfJobs();
448             const int heightPerThread = clipBottom / numOfThreads;
449             int startY = 0;
450
451             for (int job = 0; job < numOfThreads; ++job) {
452                 InteriorPixelParameters& param = parallelJobs.parameter(job);
453                 param.filter = this;
454                 param.paintingData = &paintingData;
455                 param.clipRight = clipRight;
456                 param.clipBottom = clipBottom;
457                 param.yStart = startY;
458                 if (job < numOfThreads - 1) {
459                     startY += heightPerThread;
460                     param.yEnd = startY - 1;
461                 } else
462                     param.yEnd = clipBottom;
463             }
464
465             parallelJobs.execute();
466         } else
467             // Fallback to the default setInteriorPixels call.
468 #endif
469         setInteriorPixels(paintingData, clipRight, clipBottom, 0, clipBottom);
470
471         clipRight += m_targetOffset.x() + 1;
472         clipBottom += m_targetOffset.y() + 1;
473         if (m_targetOffset.y() > 0)
474             setOuterPixels(paintingData, 0, 0, paintSize.width(), m_targetOffset.y());
475         if (clipBottom < paintSize.height())
476             setOuterPixels(paintingData, 0, clipBottom, paintSize.width(), paintSize.height());
477         if (m_targetOffset.x() > 0)
478             setOuterPixels(paintingData, 0, m_targetOffset.y(), m_targetOffset.x(), clipBottom);
479         if (clipRight < paintSize.width())
480             setOuterPixels(paintingData, clipRight, m_targetOffset.y(), paintSize.width(), clipBottom);
481     } else {
482         // Rare situation, not optimizied for speed
483         setOuterPixels(paintingData, 0, 0, paintSize.width(), paintSize.height());
484     }
485 }
486
487 void FEConvolveMatrix::dump()
488 {
489 }
490
491 static TextStream& operator<<(TextStream& ts, const EdgeModeType& type)
492 {
493     switch (type) {
494     case EDGEMODE_UNKNOWN:
495         ts << "UNKNOWN";
496         break;
497     case EDGEMODE_DUPLICATE:
498         ts << "DUPLICATE";
499         break;
500     case EDGEMODE_WRAP:
501         ts << "WRAP";
502         break;
503     case EDGEMODE_NONE:
504         ts << "NONE";
505         break;
506     }
507     return ts;
508 }
509
510 TextStream& FEConvolveMatrix::externalRepresentation(TextStream& ts, int indent) const
511 {
512     writeIndent(ts, indent);
513     ts << "[feConvolveMatrix";
514     FilterEffect::externalRepresentation(ts);
515     ts << " order=\"" << m_kernelSize << "\" "
516        << "kernelMatrix=\"" << m_kernelMatrix  << "\" "
517        << "divisor=\"" << m_divisor << "\" "
518        << "bias=\"" << m_bias << "\" "
519        << "target=\"" << m_targetOffset << "\" "
520        << "edgeMode=\"" << m_edgeMode << "\" "
521        << "kernelUnitLength=\"" << m_kernelUnitLength << "\" "
522        << "preserveAlpha=\"" << m_preserveAlpha << "\"]\n";
523     inputEffect(0)->externalRepresentation(ts, indent + 1);
524     return ts;
525 }
526
527 }; // namespace WebCore
528
529 #endif // ENABLE(FILTERS)