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