1/*
2 * Copyright (C) 2010 University of Szeged
3 * Copyright (C) 2010 Zoltan Herczeg
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY UNIVERSITY OF SZEGED ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL UNIVERSITY OF SZEGED OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "config.h"
28
29#if ENABLE(FILTERS)
30#include "FELighting.h"
31
32#include "FELightingNEON.h"
33#include <wtf/ParallelJobs.h>
34
35namespace WebCore {
36
37FELighting::FELighting(Filter* filter, LightingType lightingType, const Color& lightingColor, float surfaceScale,
38    float diffuseConstant, float specularConstant, float specularExponent,
39    float kernelUnitLengthX, float kernelUnitLengthY, PassRefPtr<LightSource> lightSource)
40    : FilterEffect(filter)
41    , m_lightingType(lightingType)
42    , m_lightSource(lightSource)
43    , m_lightingColor(lightingColor)
44    , m_surfaceScale(surfaceScale)
45    , m_diffuseConstant(diffuseConstant)
46    , m_specularConstant(specularConstant)
47    , m_specularExponent(specularExponent)
48    , m_kernelUnitLengthX(kernelUnitLengthX)
49    , m_kernelUnitLengthY(kernelUnitLengthY)
50{
51}
52
53const static int cPixelSize = 4;
54const static int cAlphaChannelOffset = 3;
55const static unsigned char cOpaqueAlpha = static_cast<unsigned char>(0xff);
56const static float cFactor1div2 = -1 / 2.f;
57const static float cFactor1div3 = -1 / 3.f;
58const static float cFactor1div4 = -1 / 4.f;
59const static float cFactor2div3 = -2 / 3.f;
60
61// << 1 is signed multiply by 2
62inline void FELighting::LightingData::topLeft(int offset, IntPoint& normalVector)
63{
64    int center = static_cast<int>(pixels->item(offset + cAlphaChannelOffset));
65    int right = static_cast<int>(pixels->item(offset + cPixelSize + cAlphaChannelOffset));
66    offset += widthMultipliedByPixelSize;
67    int bottom = static_cast<int>(pixels->item(offset + cAlphaChannelOffset));
68    int bottomRight = static_cast<int>(pixels->item(offset + cPixelSize + cAlphaChannelOffset));
69    normalVector.setX(-(center << 1) + (right << 1) - bottom + bottomRight);
70    normalVector.setY(-(center << 1) - right + (bottom << 1) + bottomRight);
71}
72
73inline void FELighting::LightingData::topRow(int offset, IntPoint& normalVector)
74{
75    int left = static_cast<int>(pixels->item(offset - cPixelSize + cAlphaChannelOffset));
76    int center = static_cast<int>(pixels->item(offset + cAlphaChannelOffset));
77    int right = static_cast<int>(pixels->item(offset + cPixelSize + cAlphaChannelOffset));
78    offset += widthMultipliedByPixelSize;
79    int bottomLeft = static_cast<int>(pixels->item(offset - cPixelSize + cAlphaChannelOffset));
80    int bottom = static_cast<int>(pixels->item(offset + cAlphaChannelOffset));
81    int bottomRight = static_cast<int>(pixels->item(offset + cPixelSize + cAlphaChannelOffset));
82    normalVector.setX(-(left << 1) + (right << 1) - bottomLeft + bottomRight);
83    normalVector.setY(-left - (center << 1) - right + bottomLeft + (bottom << 1) + bottomRight);
84}
85
86inline void FELighting::LightingData::topRight(int offset, IntPoint& normalVector)
87{
88    int left = static_cast<int>(pixels->item(offset - cPixelSize + cAlphaChannelOffset));
89    int center = static_cast<int>(pixels->item(offset + cAlphaChannelOffset));
90    offset += widthMultipliedByPixelSize;
91    int bottomLeft = static_cast<int>(pixels->item(offset - cPixelSize + cAlphaChannelOffset));
92    int bottom = static_cast<int>(pixels->item(offset + cAlphaChannelOffset));
93    normalVector.setX(-(left << 1) + (center << 1) - bottomLeft + bottom);
94    normalVector.setY(-left - (center << 1) + bottomLeft + (bottom << 1));
95}
96
97inline void FELighting::LightingData::leftColumn(int offset, IntPoint& normalVector)
98{
99    int center = static_cast<int>(pixels->item(offset + cAlphaChannelOffset));
100    int right = static_cast<int>(pixels->item(offset + cPixelSize + cAlphaChannelOffset));
101    offset -= widthMultipliedByPixelSize;
102    int top = static_cast<int>(pixels->item(offset + cAlphaChannelOffset));
103    int topRight = static_cast<int>(pixels->item(offset + cPixelSize + cAlphaChannelOffset));
104    offset += widthMultipliedByPixelSize << 1;
105    int bottom = static_cast<int>(pixels->item(offset + cAlphaChannelOffset));
106    int bottomRight = static_cast<int>(pixels->item(offset + cPixelSize + cAlphaChannelOffset));
107    normalVector.setX(-top + topRight - (center << 1) + (right << 1) - bottom + bottomRight);
108    normalVector.setY(-(top << 1) - topRight + (bottom << 1) + bottomRight);
109}
110
111inline void FELighting::LightingData::interior(int offset, IntPoint& normalVector)
112{
113    int left = static_cast<int>(pixels->item(offset - cPixelSize + cAlphaChannelOffset));
114    int right = static_cast<int>(pixels->item(offset + cPixelSize + cAlphaChannelOffset));
115    offset -= widthMultipliedByPixelSize;
116    int topLeft = static_cast<int>(pixels->item(offset - cPixelSize + cAlphaChannelOffset));
117    int top = static_cast<int>(pixels->item(offset + cAlphaChannelOffset));
118    int topRight = static_cast<int>(pixels->item(offset + cPixelSize + cAlphaChannelOffset));
119    offset += widthMultipliedByPixelSize << 1;
120    int bottomLeft = static_cast<int>(pixels->item(offset - cPixelSize + cAlphaChannelOffset));
121    int bottom = static_cast<int>(pixels->item(offset + cAlphaChannelOffset));
122    int bottomRight = static_cast<int>(pixels->item(offset + cPixelSize + cAlphaChannelOffset));
123    normalVector.setX(-topLeft + topRight - (left << 1) + (right << 1) - bottomLeft + bottomRight);
124    normalVector.setY(-topLeft - (top << 1) - topRight + bottomLeft + (bottom << 1) + bottomRight);
125}
126
127inline void FELighting::LightingData::rightColumn(int offset, IntPoint& normalVector)
128{
129    int left = static_cast<int>(pixels->item(offset - cPixelSize + cAlphaChannelOffset));
130    int center = static_cast<int>(pixels->item(offset + cAlphaChannelOffset));
131    offset -= widthMultipliedByPixelSize;
132    int topLeft = static_cast<int>(pixels->item(offset - cPixelSize + cAlphaChannelOffset));
133    int top = static_cast<int>(pixels->item(offset + cAlphaChannelOffset));
134    offset += widthMultipliedByPixelSize << 1;
135    int bottomLeft = static_cast<int>(pixels->item(offset - cPixelSize + cAlphaChannelOffset));
136    int bottom = static_cast<int>(pixels->item(offset + cAlphaChannelOffset));
137    normalVector.setX(-topLeft + top - (left << 1) + (center << 1) - bottomLeft + bottom);
138    normalVector.setY(-topLeft - (top << 1) + bottomLeft + (bottom << 1));
139}
140
141inline void FELighting::LightingData::bottomLeft(int offset, IntPoint& normalVector)
142{
143    int center = static_cast<int>(pixels->item(offset + cAlphaChannelOffset));
144    int right = static_cast<int>(pixels->item(offset + cPixelSize + cAlphaChannelOffset));
145    offset -= widthMultipliedByPixelSize;
146    int top = static_cast<int>(pixels->item(offset + cAlphaChannelOffset));
147    int topRight = static_cast<int>(pixels->item(offset + cPixelSize + cAlphaChannelOffset));
148    normalVector.setX(-top + topRight - (center << 1) + (right << 1));
149    normalVector.setY(-(top << 1) - topRight + (center << 1) + right);
150}
151
152inline void FELighting::LightingData::bottomRow(int offset, IntPoint& normalVector)
153{
154    int left = static_cast<int>(pixels->item(offset - cPixelSize + cAlphaChannelOffset));
155    int center = static_cast<int>(pixels->item(offset + cAlphaChannelOffset));
156    int right = static_cast<int>(pixels->item(offset + cPixelSize + cAlphaChannelOffset));
157    offset -= widthMultipliedByPixelSize;
158    int topLeft = static_cast<int>(pixels->item(offset - cPixelSize + cAlphaChannelOffset));
159    int top = static_cast<int>(pixels->item(offset + cAlphaChannelOffset));
160    int topRight = static_cast<int>(pixels->item(offset + cPixelSize + cAlphaChannelOffset));
161    normalVector.setX(-topLeft + topRight - (left << 1) + (right << 1));
162    normalVector.setY(-topLeft - (top << 1) - topRight + left + (center << 1) + right);
163}
164
165inline void FELighting::LightingData::bottomRight(int offset, IntPoint& normalVector)
166{
167    int left = static_cast<int>(pixels->item(offset - cPixelSize + cAlphaChannelOffset));
168    int center = static_cast<int>(pixels->item(offset + cAlphaChannelOffset));
169    offset -= widthMultipliedByPixelSize;
170    int topLeft = static_cast<int>(pixels->item(offset - cPixelSize + cAlphaChannelOffset));
171    int top = static_cast<int>(pixels->item(offset + cAlphaChannelOffset));
172    normalVector.setX(-topLeft + top - (left << 1) + (center << 1));
173    normalVector.setY(-topLeft - (top << 1) + left + (center << 1));
174}
175
176inline void FELighting::inlineSetPixel(int offset, LightingData& data, LightSource::PaintingData& paintingData,
177                                       int lightX, int lightY, float factorX, float factorY, IntPoint& normal2DVector)
178{
179    m_lightSource->updatePaintingData(paintingData, lightX, lightY, static_cast<float>(data.pixels->item(offset + cAlphaChannelOffset)) * data.surfaceScale);
180
181    float lightStrength;
182    if (!normal2DVector.x() && !normal2DVector.y()) {
183        // Normal vector is (0, 0, 1). This is a quite frequent case.
184        if (m_lightingType == FELighting::DiffuseLighting)
185            lightStrength = m_diffuseConstant * paintingData.lightVector.z() / paintingData.lightVectorLength;
186        else {
187            FloatPoint3D halfwayVector = paintingData.lightVector;
188            halfwayVector.setZ(halfwayVector.z() + paintingData.lightVectorLength);
189            float halfwayVectorLength = halfwayVector.length();
190            if (m_specularExponent == 1)
191                lightStrength = m_specularConstant * halfwayVector.z() / halfwayVectorLength;
192            else
193                lightStrength = m_specularConstant * powf(halfwayVector.z() / halfwayVectorLength, m_specularExponent);
194        }
195    } else {
196        FloatPoint3D normalVector;
197        normalVector.setX(factorX * static_cast<float>(normal2DVector.x()) * data.surfaceScale);
198        normalVector.setY(factorY * static_cast<float>(normal2DVector.y()) * data.surfaceScale);
199        normalVector.setZ(1);
200        float normalVectorLength = normalVector.length();
201
202        if (m_lightingType == FELighting::DiffuseLighting)
203            lightStrength = m_diffuseConstant * (normalVector * paintingData.lightVector) / (normalVectorLength * paintingData.lightVectorLength);
204        else {
205            FloatPoint3D halfwayVector = paintingData.lightVector;
206            halfwayVector.setZ(halfwayVector.z() + paintingData.lightVectorLength);
207            float halfwayVectorLength = halfwayVector.length();
208            if (m_specularExponent == 1)
209                lightStrength = m_specularConstant * (normalVector * halfwayVector) / (normalVectorLength * halfwayVectorLength);
210            else
211                lightStrength = m_specularConstant * powf((normalVector * halfwayVector) / (normalVectorLength * halfwayVectorLength), m_specularExponent);
212        }
213    }
214
215    if (lightStrength > 1)
216        lightStrength = 1;
217    if (lightStrength < 0)
218        lightStrength = 0;
219
220    data.pixels->set(offset, static_cast<unsigned char>(lightStrength * paintingData.colorVector.x()));
221    data.pixels->set(offset + 1, static_cast<unsigned char>(lightStrength * paintingData.colorVector.y()));
222    data.pixels->set(offset + 2, static_cast<unsigned char>(lightStrength * paintingData.colorVector.z()));
223}
224
225void FELighting::setPixel(int offset, LightingData& data, LightSource::PaintingData& paintingData,
226                          int lightX, int lightY, float factorX, float factorY, IntPoint& normalVector)
227{
228    inlineSetPixel(offset, data, paintingData, lightX, lightY, factorX, factorY, normalVector);
229}
230
231inline void FELighting::platformApplyGenericPaint(LightingData& data, LightSource::PaintingData& paintingData, int startY, int endY)
232{
233    IntPoint normalVector;
234    int offset = 0;
235
236    for (int y = startY; y < endY; ++y) {
237        offset = y * data.widthMultipliedByPixelSize + cPixelSize;
238        for (int x = 1; x < data.widthDecreasedByOne; ++x, offset += cPixelSize) {
239            data.interior(offset, normalVector);
240            inlineSetPixel(offset, data, paintingData, x, y, cFactor1div4, cFactor1div4, normalVector);
241        }
242    }
243}
244
245void FELighting::platformApplyGenericWorker(PlatformApplyGenericParameters* parameters)
246{
247    parameters->filter->platformApplyGenericPaint(parameters->data, parameters->paintingData, parameters->yStart, parameters->yEnd);
248}
249
250inline void FELighting::platformApplyGeneric(LightingData& data, LightSource::PaintingData& paintingData)
251{
252    int optimalThreadNumber = ((data.widthDecreasedByOne - 1) * (data.heightDecreasedByOne - 1)) / s_minimalRectDimension;
253    if (optimalThreadNumber > 1) {
254        // Initialize parallel jobs
255        WTF::ParallelJobs<PlatformApplyGenericParameters> parallelJobs(&platformApplyGenericWorker, optimalThreadNumber);
256
257        // Fill the parameter array
258        int job = parallelJobs.numberOfJobs();
259        if (job > 1) {
260            // Split the job into "yStep"-sized jobs but there a few jobs that need to be slightly larger since
261            // yStep * jobs < total size. These extras are handled by the remainder "jobsWithExtra".
262            const int yStep = (data.heightDecreasedByOne - 1) / job;
263            const int jobsWithExtra = (data.heightDecreasedByOne - 1) % job;
264
265            int yStart = 1;
266            for (--job; job >= 0; --job) {
267                PlatformApplyGenericParameters& params = parallelJobs.parameter(job);
268                params.filter = this;
269                params.data = data;
270                params.paintingData = paintingData;
271                params.yStart = yStart;
272                yStart += job < jobsWithExtra ? yStep + 1 : yStep;
273                params.yEnd = yStart;
274            }
275            parallelJobs.execute();
276            return;
277        }
278        // Fallback to single threaded mode.
279    }
280
281    platformApplyGenericPaint(data, paintingData, 1, data.heightDecreasedByOne);
282}
283
284inline void FELighting::platformApply(LightingData& data, LightSource::PaintingData& paintingData)
285{
286    // The selection here eventually should happen dynamically on some platforms.
287#if CPU(ARM_NEON) && CPU(ARM_TRADITIONAL) && COMPILER(GCC)
288    platformApplyNeon(data, paintingData);
289#else
290    platformApplyGeneric(data, paintingData);
291#endif
292}
293
294bool FELighting::drawLighting(Uint8ClampedArray* pixels, int width, int height)
295{
296    LightSource::PaintingData paintingData;
297    LightingData data;
298
299    if (!m_lightSource)
300        return false;
301
302    // FIXME: do something if width or height (or both) is 1 pixel.
303    // The W3 spec does not define this case. Now the filter just returns.
304    if (width <= 2 || height <= 2)
305        return false;
306
307    data.pixels = pixels;
308    data.surfaceScale = m_surfaceScale / 255.0f;
309    data.widthMultipliedByPixelSize = width * cPixelSize;
310    data.widthDecreasedByOne = width - 1;
311    data.heightDecreasedByOne = height - 1;
312    paintingData.colorVector = FloatPoint3D(m_lightingColor.red(), m_lightingColor.green(), m_lightingColor.blue());
313    m_lightSource->initPaintingData(paintingData);
314
315    // Top/Left corner.
316    IntPoint normalVector;
317    int offset = 0;
318    data.topLeft(offset, normalVector);
319    setPixel(offset, data, paintingData, 0, 0, cFactor2div3, cFactor2div3, normalVector);
320
321    // Top/Right pixel.
322    offset = data.widthMultipliedByPixelSize - cPixelSize;
323    data.topRight(offset, normalVector);
324    setPixel(offset, data, paintingData, data.widthDecreasedByOne, 0, cFactor2div3, cFactor2div3, normalVector);
325
326    // Bottom/Left pixel.
327    offset = data.heightDecreasedByOne * data.widthMultipliedByPixelSize;
328    data.bottomLeft(offset, normalVector);
329    setPixel(offset, data, paintingData, 0, data.heightDecreasedByOne, cFactor2div3, cFactor2div3, normalVector);
330
331    // Bottom/Right pixel.
332    offset = height * data.widthMultipliedByPixelSize - cPixelSize;
333    data.bottomRight(offset, normalVector);
334    setPixel(offset, data, paintingData, data.widthDecreasedByOne, data.heightDecreasedByOne, cFactor2div3, cFactor2div3, normalVector);
335
336    if (width >= 3) {
337        // Top row.
338        offset = cPixelSize;
339        for (int x = 1; x < data.widthDecreasedByOne; ++x, offset += cPixelSize) {
340            data.topRow(offset, normalVector);
341            inlineSetPixel(offset, data, paintingData, x, 0, cFactor1div3, cFactor1div2, normalVector);
342        }
343        // Bottom row.
344        offset = data.heightDecreasedByOne * data.widthMultipliedByPixelSize + cPixelSize;
345        for (int x = 1; x < data.widthDecreasedByOne; ++x, offset += cPixelSize) {
346            data.bottomRow(offset, normalVector);
347            inlineSetPixel(offset, data, paintingData, x, data.heightDecreasedByOne, cFactor1div3, cFactor1div2, normalVector);
348        }
349    }
350
351    if (height >= 3) {
352        // Left column.
353        offset = data.widthMultipliedByPixelSize;
354        for (int y = 1; y < data.heightDecreasedByOne; ++y, offset += data.widthMultipliedByPixelSize) {
355            data.leftColumn(offset, normalVector);
356            inlineSetPixel(offset, data, paintingData, 0, y, cFactor1div2, cFactor1div3, normalVector);
357        }
358        // Right column.
359        offset = (data.widthMultipliedByPixelSize << 1) - cPixelSize;
360        for (int y = 1; y < data.heightDecreasedByOne; ++y, offset += data.widthMultipliedByPixelSize) {
361            data.rightColumn(offset, normalVector);
362            inlineSetPixel(offset, data, paintingData, data.widthDecreasedByOne, y, cFactor1div2, cFactor1div3, normalVector);
363        }
364    }
365
366    if (width >= 3 && height >= 3) {
367        // Interior pixels.
368        platformApply(data, paintingData);
369    }
370
371    int lastPixel = data.widthMultipliedByPixelSize * height;
372    if (m_lightingType == DiffuseLighting) {
373        for (int i = cAlphaChannelOffset; i < lastPixel; i += cPixelSize)
374            data.pixels->set(i, cOpaqueAlpha);
375    } else {
376        for (int i = 0; i < lastPixel; i += cPixelSize) {
377            unsigned char a1 = data.pixels->item(i);
378            unsigned char a2 = data.pixels->item(i + 1);
379            unsigned char a3 = data.pixels->item(i + 2);
380            // alpha set to set to max(a1, a2, a3)
381            data.pixels->set(i + 3, a1 >= a2 ? (a1 >= a3 ? a1 : a3) : (a2 >= a3 ? a2 : a3));
382        }
383    }
384
385    return true;
386}
387
388void FELighting::platformApplySoftware()
389{
390    FilterEffect* in = inputEffect(0);
391
392    Uint8ClampedArray* srcPixelArray = createPremultipliedImageResult();
393    if (!srcPixelArray)
394        return;
395
396    setIsAlphaImage(false);
397
398    IntRect effectDrawingRect = requestedRegionOfInputImageData(in->absolutePaintRect());
399    in->copyPremultipliedImage(srcPixelArray, effectDrawingRect);
400
401    // FIXME: support kernelUnitLengths other than (1,1). The issue here is that the W3
402    // standard has no test case for them, and other browsers (like Firefox) has strange
403    // output for various kernelUnitLengths, and I am not sure they are reliable.
404    // Anyway, feConvolveMatrix should also use the implementation
405
406    IntSize absolutePaintSize = absolutePaintRect().size();
407    drawLighting(srcPixelArray, absolutePaintSize.width(), absolutePaintSize.height());
408}
409
410} // namespace WebCore
411
412#endif // ENABLE(FILTERS)
413