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) Research In Motion Limited 2010. All rights reserved.
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 "FEMorphology.h"
28
29#include "Filter.h"
30#include "RenderTreeAsText.h"
31#include "TextStream.h"
32
33#include <wtf/ParallelJobs.h>
34#include <wtf/Uint8ClampedArray.h>
35#include <wtf/Vector.h>
36
37using std::min;
38using std::max;
39
40namespace WebCore {
41
42FEMorphology::FEMorphology(Filter* filter, MorphologyOperatorType type, float radiusX, float radiusY)
43    : FilterEffect(filter)
44    , m_type(type)
45    , m_radiusX(radiusX)
46    , m_radiusY(radiusY)
47{
48}
49
50PassRefPtr<FEMorphology> FEMorphology::create(Filter* filter, MorphologyOperatorType type, float radiusX, float radiusY)
51{
52    return adoptRef(new FEMorphology(filter, type, radiusX, radiusY));
53}
54
55MorphologyOperatorType FEMorphology::morphologyOperator() const
56{
57    return m_type;
58}
59
60bool FEMorphology::setMorphologyOperator(MorphologyOperatorType type)
61{
62    if (m_type == type)
63        return false;
64    m_type = type;
65    return true;
66}
67
68float FEMorphology::radiusX() const
69{
70    return m_radiusX;
71}
72
73bool FEMorphology::setRadiusX(float radiusX)
74{
75    if (m_radiusX == radiusX)
76        return false;
77    m_radiusX = radiusX;
78    return true;
79}
80
81float FEMorphology::radiusY() const
82{
83    return m_radiusY;
84}
85
86void FEMorphology::determineAbsolutePaintRect()
87{
88    FloatRect paintRect = inputEffect(0)->absolutePaintRect();
89    Filter* filter = this->filter();
90    paintRect.inflateX(filter->applyHorizontalScale(m_radiusX));
91    paintRect.inflateY(filter->applyVerticalScale(m_radiusY));
92    if (clipsToBounds())
93        paintRect.intersect(maxEffectRect());
94    else
95        paintRect.unite(maxEffectRect());
96    setAbsolutePaintRect(enclosingIntRect(paintRect));
97}
98
99bool FEMorphology::setRadiusY(float radiusY)
100{
101    if (m_radiusY == radiusY)
102        return false;
103    m_radiusY = radiusY;
104    return true;
105}
106
107void FEMorphology::platformApplyGeneric(PaintingData* paintingData, int yStart, int yEnd)
108{
109    Uint8ClampedArray* srcPixelArray = paintingData->srcPixelArray;
110    Uint8ClampedArray* dstPixelArray = paintingData->dstPixelArray;
111    const int width = paintingData->width;
112    const int height = paintingData->height;
113    const int effectWidth = width * 4;
114    const int radiusX = paintingData->radiusX;
115    const int radiusY = paintingData->radiusY;
116
117    Vector<unsigned char> extrema;
118    for (int y = yStart; y < yEnd; ++y) {
119        int extremaStartY = max(0, y - radiusY);
120        int extremaEndY = min(height - 1, y + radiusY);
121        for (unsigned int clrChannel = 0; clrChannel < 4; ++clrChannel) {
122            extrema.clear();
123            // Compute extremas for each columns
124            for (int x = 0; x <= radiusX; ++x) {
125                unsigned char columnExtrema = srcPixelArray->item(extremaStartY * effectWidth + 4 * x + clrChannel);
126                for (int eY = extremaStartY + 1; eY < extremaEndY; ++eY) {
127                    unsigned char pixel = srcPixelArray->item(eY * effectWidth + 4 * x + clrChannel);
128                    if ((m_type == FEMORPHOLOGY_OPERATOR_ERODE && pixel <= columnExtrema)
129                        || (m_type == FEMORPHOLOGY_OPERATOR_DILATE && pixel >= columnExtrema)) {
130                        columnExtrema = pixel;
131                    }
132                }
133
134                extrema.append(columnExtrema);
135            }
136
137            // Kernel is filled, get extrema of next column
138            for (int x = 0; x < width; ++x) {
139                const int endX = min(x + radiusX, width - 1);
140                unsigned char columnExtrema = srcPixelArray->item(extremaStartY * effectWidth + endX * 4 + clrChannel);
141                for (int i = extremaStartY + 1; i <= extremaEndY; ++i) {
142                    unsigned char pixel = srcPixelArray->item(i * effectWidth + endX * 4 + clrChannel);
143                    if ((m_type == FEMORPHOLOGY_OPERATOR_ERODE && pixel <= columnExtrema)
144                        || (m_type == FEMORPHOLOGY_OPERATOR_DILATE && pixel >= columnExtrema))
145                        columnExtrema = pixel;
146                }
147                if (x - radiusX >= 0)
148                    extrema.remove(0);
149                if (x + radiusX <= width)
150                    extrema.append(columnExtrema);
151
152                unsigned char entireExtrema = extrema[0];
153                for (unsigned kernelIndex = 1; kernelIndex < extrema.size(); ++kernelIndex) {
154                    if ((m_type == FEMORPHOLOGY_OPERATOR_ERODE && extrema[kernelIndex] <= entireExtrema)
155                        || (m_type == FEMORPHOLOGY_OPERATOR_DILATE && extrema[kernelIndex] >= entireExtrema))
156                        entireExtrema = extrema[kernelIndex];
157                }
158                dstPixelArray->set(y * effectWidth + 4 * x + clrChannel, entireExtrema);
159            }
160        }
161    }
162}
163
164void FEMorphology::platformApplyWorker(PlatformApplyParameters* param)
165{
166    param->filter->platformApplyGeneric(param->paintingData, param->startY, param->endY);
167}
168
169void FEMorphology::platformApply(PaintingData* paintingData)
170{
171    int optimalThreadNumber = (paintingData->width * paintingData->height) / s_minimalArea;
172    if (optimalThreadNumber > 1) {
173        ParallelJobs<PlatformApplyParameters> parallelJobs(&WebCore::FEMorphology::platformApplyWorker, optimalThreadNumber);
174        int numOfThreads = parallelJobs.numberOfJobs();
175        if (numOfThreads > 1) {
176            // Split the job into "jobSize"-sized jobs but there a few jobs that need to be slightly larger since
177            // jobSize * jobs < total size. These extras are handled by the remainder "jobsWithExtra".
178            const int jobSize = paintingData->height / numOfThreads;
179            const int jobsWithExtra = paintingData->height % numOfThreads;
180            int currentY = 0;
181            for (int job = numOfThreads - 1; job >= 0; --job) {
182                PlatformApplyParameters& param = parallelJobs.parameter(job);
183                param.filter = this;
184                param.startY = currentY;
185                currentY += job < jobsWithExtra ? jobSize + 1 : jobSize;
186                param.endY = currentY;
187                param.paintingData = paintingData;
188            }
189            parallelJobs.execute();
190            return;
191        }
192        // Fallback to single thread model
193    }
194
195    platformApplyGeneric(paintingData, 0, paintingData->height);
196}
197
198
199void FEMorphology::platformApplySoftware()
200{
201    FilterEffect* in = inputEffect(0);
202
203    Uint8ClampedArray* dstPixelArray = createPremultipliedImageResult();
204    if (!dstPixelArray)
205        return;
206
207    setIsAlphaImage(in->isAlphaImage());
208    if (m_radiusX <= 0 || m_radiusY <= 0) {
209        dstPixelArray->zeroFill();
210        return;
211    }
212
213    Filter* filter = this->filter();
214    int radiusX = static_cast<int>(floorf(filter->applyHorizontalScale(m_radiusX)));
215    int radiusY = static_cast<int>(floorf(filter->applyVerticalScale(m_radiusY)));
216
217    IntRect effectDrawingRect = requestedRegionOfInputImageData(in->absolutePaintRect());
218    RefPtr<Uint8ClampedArray> srcPixelArray = in->asPremultipliedImage(effectDrawingRect);
219
220    PaintingData paintingData;
221    paintingData.srcPixelArray = srcPixelArray.get();
222    paintingData.dstPixelArray = dstPixelArray;
223    paintingData.width = effectDrawingRect.width();
224    paintingData.height = effectDrawingRect.height();
225    paintingData.radiusX = min(effectDrawingRect.width() - 1, radiusX);
226    paintingData.radiusY = min(effectDrawingRect.height() - 1, radiusY);
227
228    platformApply(&paintingData);
229}
230
231void FEMorphology::dump()
232{
233}
234
235static TextStream& operator<<(TextStream& ts, const MorphologyOperatorType& type)
236{
237    switch (type) {
238    case FEMORPHOLOGY_OPERATOR_UNKNOWN:
239        ts << "UNKNOWN";
240        break;
241    case FEMORPHOLOGY_OPERATOR_ERODE:
242        ts << "ERODE";
243        break;
244    case FEMORPHOLOGY_OPERATOR_DILATE:
245        ts << "DILATE";
246        break;
247    }
248    return ts;
249}
250
251TextStream& FEMorphology::externalRepresentation(TextStream& ts, int indent) const
252{
253    writeIndent(ts, indent);
254    ts << "[feMorphology";
255    FilterEffect::externalRepresentation(ts);
256    ts << " operator=\"" << morphologyOperator() << "\" "
257       << "radius=\"" << radiusX() << ", " << radiusY() << "\"]\n";
258    inputEffect(0)->externalRepresentation(ts, indent + 1);
259    return ts;
260}
261
262} // namespace WebCore
263
264#endif // ENABLE(FILTERS)
265