FELighting.cpp revision cad810f21b803229eb11403f9209855525a25d57
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 "LightSource.h"
33
34namespace WebCore {
35
36FELighting::FELighting(Filter* filter, LightingType lightingType, const Color& lightingColor, float surfaceScale,
37    float diffuseConstant, float specularConstant, float specularExponent,
38    float kernelUnitLengthX, float kernelUnitLengthY, PassRefPtr<LightSource> lightSource)
39    : FilterEffect(filter)
40    , m_lightingType(lightingType)
41    , m_lightSource(lightSource)
42    , m_lightingColor(lightingColor)
43    , m_surfaceScale(surfaceScale)
44    , m_diffuseConstant(diffuseConstant)
45    , m_specularConstant(specularConstant)
46    , m_specularExponent(specularExponent)
47    , m_kernelUnitLengthX(kernelUnitLengthX)
48    , m_kernelUnitLengthY(kernelUnitLengthY)
49{
50}
51
52const static int cPixelSize = 4;
53const static int cAlphaChannelOffset = 3;
54const static unsigned char cOpaqueAlpha = static_cast<unsigned char>(0xff);
55const static float cFactor1div2 = -1 / 2.f;
56const static float cFactor1div3 = -1 / 3.f;
57const static float cFactor1div4 = -1 / 4.f;
58const static float cFactor2div3 = -2 / 3.f;
59
60// << 1 is signed multiply by 2
61inline void FELighting::LightingData::topLeft(int offset, IntPoint& normalVector)
62{
63    int center = static_cast<int>(pixels->get(offset + cAlphaChannelOffset));
64    int right = static_cast<int>(pixels->get(offset + cPixelSize + cAlphaChannelOffset));
65    offset += widthMultipliedByPixelSize;
66    int bottom = static_cast<int>(pixels->get(offset + cAlphaChannelOffset));
67    int bottomRight = static_cast<int>(pixels->get(offset + cPixelSize + cAlphaChannelOffset));
68    normalVector.setX(-(center << 1) + (right << 1) - bottom + bottomRight);
69    normalVector.setY(-(center << 1) - right + (bottom << 1) + bottomRight);
70}
71
72inline void FELighting::LightingData::topRow(int offset, IntPoint& normalVector)
73{
74    int left = static_cast<int>(pixels->get(offset - cPixelSize + cAlphaChannelOffset));
75    int center = static_cast<int>(pixels->get(offset + cAlphaChannelOffset));
76    int right = static_cast<int>(pixels->get(offset + cPixelSize + cAlphaChannelOffset));
77    offset += widthMultipliedByPixelSize;
78    int bottomLeft = static_cast<int>(pixels->get(offset - cPixelSize + cAlphaChannelOffset));
79    int bottom = static_cast<int>(pixels->get(offset + cAlphaChannelOffset));
80    int bottomRight = static_cast<int>(pixels->get(offset + cPixelSize + cAlphaChannelOffset));
81    normalVector.setX(-(left << 1) + (right << 1) - bottomLeft + bottomRight);
82    normalVector.setY(-left - (center << 1) - right + bottomLeft + (bottom << 1) + bottomRight);
83}
84
85inline void FELighting::LightingData::topRight(int offset, IntPoint& normalVector)
86{
87    int left = static_cast<int>(pixels->get(offset - cPixelSize + cAlphaChannelOffset));
88    int center = static_cast<int>(pixels->get(offset + cAlphaChannelOffset));
89    offset += widthMultipliedByPixelSize;
90    int bottomLeft = static_cast<int>(pixels->get(offset - cPixelSize + cAlphaChannelOffset));
91    int bottom = static_cast<int>(pixels->get(offset + cAlphaChannelOffset));
92    normalVector.setX(-(left << 1) + (center << 1) - bottomLeft + bottom);
93    normalVector.setY(-left - (center << 1) + bottomLeft + (bottom << 1));
94}
95
96inline void FELighting::LightingData::leftColumn(int offset, IntPoint& normalVector)
97{
98    int center = static_cast<int>(pixels->get(offset + cAlphaChannelOffset));
99    int right = static_cast<int>(pixels->get(offset + cPixelSize + cAlphaChannelOffset));
100    offset -= widthMultipliedByPixelSize;
101    int top = static_cast<int>(pixels->get(offset + cAlphaChannelOffset));
102    int topRight = static_cast<int>(pixels->get(offset + cPixelSize + cAlphaChannelOffset));
103    offset += widthMultipliedByPixelSize << 1;
104    int bottom = static_cast<int>(pixels->get(offset + cAlphaChannelOffset));
105    int bottomRight = static_cast<int>(pixels->get(offset + cPixelSize + cAlphaChannelOffset));
106    normalVector.setX(-top + topRight - (center << 1) + (right << 1) - bottom + bottomRight);
107    normalVector.setY(-(top << 1) - topRight + (bottom << 1) + bottomRight);
108}
109
110inline void FELighting::LightingData::interior(int offset, IntPoint& normalVector)
111{
112    int left = static_cast<int>(pixels->get(offset - cPixelSize + cAlphaChannelOffset));
113    int right = static_cast<int>(pixels->get(offset + cPixelSize + cAlphaChannelOffset));
114    offset -= widthMultipliedByPixelSize;
115    int topLeft = static_cast<int>(pixels->get(offset - cPixelSize + cAlphaChannelOffset));
116    int top = static_cast<int>(pixels->get(offset + cAlphaChannelOffset));
117    int topRight = static_cast<int>(pixels->get(offset + cPixelSize + cAlphaChannelOffset));
118    offset += widthMultipliedByPixelSize << 1;
119    int bottomLeft = static_cast<int>(pixels->get(offset - cPixelSize + cAlphaChannelOffset));
120    int bottom = static_cast<int>(pixels->get(offset + cAlphaChannelOffset));
121    int bottomRight = static_cast<int>(pixels->get(offset + cPixelSize + cAlphaChannelOffset));
122    normalVector.setX(-topLeft + topRight - (left << 1) + (right << 1) - bottomLeft + bottomRight);
123    normalVector.setY(-topLeft - (top << 1) - topRight + bottomLeft + (bottom << 1) + bottomRight);
124}
125
126inline void FELighting::LightingData::rightColumn(int offset, IntPoint& normalVector)
127{
128    int left = static_cast<int>(pixels->get(offset - cPixelSize + cAlphaChannelOffset));
129    int center = static_cast<int>(pixels->get(offset + cAlphaChannelOffset));
130    offset -= widthMultipliedByPixelSize;
131    int topLeft = static_cast<int>(pixels->get(offset - cPixelSize + cAlphaChannelOffset));
132    int top = static_cast<int>(pixels->get(offset + cAlphaChannelOffset));
133    offset += widthMultipliedByPixelSize << 1;
134    int bottomLeft = static_cast<int>(pixels->get(offset - cPixelSize + cAlphaChannelOffset));
135    int bottom = static_cast<int>(pixels->get(offset + cAlphaChannelOffset));
136    normalVector.setX(-topLeft + top - (left << 1) + (center << 1) - bottomLeft + bottom);
137    normalVector.setY(-topLeft - (top << 1) + bottomLeft + (bottom << 1));
138}
139
140inline void FELighting::LightingData::bottomLeft(int offset, IntPoint& normalVector)
141{
142    int center = static_cast<int>(pixels->get(offset + cAlphaChannelOffset));
143    int right = static_cast<int>(pixels->get(offset + cPixelSize + cAlphaChannelOffset));
144    offset -= widthMultipliedByPixelSize;
145    int top = static_cast<int>(pixels->get(offset + cAlphaChannelOffset));
146    int topRight = static_cast<int>(pixels->get(offset + cPixelSize + cAlphaChannelOffset));
147    normalVector.setX(-top + topRight - (center << 1) + (right << 1));
148    normalVector.setY(-(top << 1) - topRight + (center << 1) + right);
149}
150
151inline void FELighting::LightingData::bottomRow(int offset, IntPoint& normalVector)
152{
153    int left = static_cast<int>(pixels->get(offset - cPixelSize + cAlphaChannelOffset));
154    int center = static_cast<int>(pixels->get(offset + cAlphaChannelOffset));
155    int right = static_cast<int>(pixels->get(offset + cPixelSize + cAlphaChannelOffset));
156    offset -= widthMultipliedByPixelSize;
157    int topLeft = static_cast<int>(pixels->get(offset - cPixelSize + cAlphaChannelOffset));
158    int top = static_cast<int>(pixels->get(offset + cAlphaChannelOffset));
159    int topRight = static_cast<int>(pixels->get(offset + cPixelSize + cAlphaChannelOffset));
160    normalVector.setX(-topLeft + topRight - (left << 1) + (right << 1));
161    normalVector.setY(-topLeft - (top << 1) - topRight + left + (center << 1) + right);
162}
163
164inline void FELighting::LightingData::bottomRight(int offset, IntPoint& normalVector)
165{
166    int left = static_cast<int>(pixels->get(offset - cPixelSize + cAlphaChannelOffset));
167    int center = static_cast<int>(pixels->get(offset + cAlphaChannelOffset));
168    offset -= widthMultipliedByPixelSize;
169    int topLeft = static_cast<int>(pixels->get(offset - cPixelSize + cAlphaChannelOffset));
170    int top = static_cast<int>(pixels->get(offset + cAlphaChannelOffset));
171    normalVector.setX(-topLeft + top - (left << 1) + (center << 1));
172    normalVector.setY(-topLeft - (top << 1) + left + (center << 1));
173}
174
175inline void FELighting::inlineSetPixel(int offset, LightingData& data, LightSource::PaintingData& paintingData,
176                                       int lightX, int lightY, float factorX, float factorY, IntPoint& normal2DVector)
177{
178    m_lightSource->updatePaintingData(paintingData, lightX, lightY, static_cast<float>(data.pixels->get(offset + cAlphaChannelOffset)) * data.surfaceScale);
179
180    float lightStrength;
181    if (!normal2DVector.x() && !normal2DVector.y()) {
182        // Normal vector is (0, 0, 1). This is a quite frequent case.
183        if (m_lightingType == FELighting::DiffuseLighting)
184            lightStrength = m_diffuseConstant * paintingData.lightVector.z() / paintingData.lightVectorLength;
185        else {
186            FloatPoint3D halfwayVector = paintingData.lightVector;
187            halfwayVector.setZ(halfwayVector.z() + paintingData.lightVectorLength);
188            float halfwayVectorLength = halfwayVector.length();
189            if (m_specularExponent == 1)
190                lightStrength = m_specularConstant * halfwayVector.z() / halfwayVectorLength;
191            else
192                lightStrength = m_specularConstant * powf(halfwayVector.z() / halfwayVectorLength, m_specularExponent);
193        }
194    } else {
195        FloatPoint3D normalVector;
196        normalVector.setX(factorX * static_cast<float>(normal2DVector.x()) * data.surfaceScale);
197        normalVector.setY(factorY * static_cast<float>(normal2DVector.y()) * data.surfaceScale);
198        normalVector.setZ(1);
199        float normalVectorLength = normalVector.length();
200
201        if (m_lightingType == FELighting::DiffuseLighting)
202            lightStrength = m_diffuseConstant * (normalVector * paintingData.lightVector) / (normalVectorLength * paintingData.lightVectorLength);
203        else {
204            FloatPoint3D halfwayVector = paintingData.lightVector;
205            halfwayVector.setZ(halfwayVector.z() + paintingData.lightVectorLength);
206            float halfwayVectorLength = halfwayVector.length();
207            if (m_specularExponent == 1)
208                lightStrength = m_specularConstant * (normalVector * halfwayVector) / (normalVectorLength * halfwayVectorLength);
209            else
210                lightStrength = m_specularConstant * powf((normalVector * halfwayVector) / (normalVectorLength * halfwayVectorLength), m_specularExponent);
211        }
212    }
213
214    if (lightStrength > 1)
215        lightStrength = 1;
216    if (lightStrength < 0)
217        lightStrength = 0;
218
219    data.pixels->set(offset, static_cast<unsigned char>(lightStrength * paintingData.colorVector.x()));
220    data.pixels->set(offset + 1, static_cast<unsigned char>(lightStrength * paintingData.colorVector.y()));
221    data.pixels->set(offset + 2, static_cast<unsigned char>(lightStrength * paintingData.colorVector.z()));
222}
223
224void FELighting::setPixel(int offset, LightingData& data, LightSource::PaintingData& paintingData,
225                          int lightX, int lightY, float factorX, float factorY, IntPoint& normalVector)
226{
227    inlineSetPixel(offset, data, paintingData, lightX, lightY, factorX, factorY, normalVector);
228}
229
230bool FELighting::drawLighting(ByteArray* pixels, int width, int height)
231{
232    LightSource::PaintingData paintingData;
233    LightingData data;
234
235    if (!m_lightSource)
236        return false;
237
238    // FIXME: do something if width or height (or both) is 1 pixel.
239    // The W3 spec does not define this case. Now the filter just returns.
240    if (width <= 2 || height <= 2)
241        return false;
242
243    data.pixels = pixels;
244    data.surfaceScale = m_surfaceScale / 255.0f;
245    data.widthMultipliedByPixelSize = width * cPixelSize;
246    data.widthDecreasedByOne = width - 1;
247    data.heightDecreasedByOne = height - 1;
248    paintingData.colorVector = FloatPoint3D(m_lightingColor.red(), m_lightingColor.green(), m_lightingColor.blue());
249    m_lightSource->initPaintingData(paintingData);
250
251    // Top/Left corner
252    IntPoint normalVector;
253    int offset = 0;
254    data.topLeft(offset, normalVector);
255    setPixel(offset, data, paintingData, 0, 0, cFactor2div3, cFactor2div3, normalVector);
256
257    // Top/Right pixel
258    offset = data.widthMultipliedByPixelSize - cPixelSize;
259    data.topRight(offset, normalVector);
260    setPixel(offset, data, paintingData, data.widthDecreasedByOne, 0, cFactor2div3, cFactor2div3, normalVector);
261
262    // Bottom/Left pixel
263    offset = data.heightDecreasedByOne * data.widthMultipliedByPixelSize;
264    data.bottomLeft(offset, normalVector);
265    setPixel(offset, data, paintingData, 0, data.heightDecreasedByOne, cFactor2div3, cFactor2div3, normalVector);
266
267    // Bottom/Right pixel
268    offset = height * data.widthMultipliedByPixelSize - cPixelSize;
269    data.bottomRight(offset, normalVector);
270    setPixel(offset, data, paintingData, data.widthDecreasedByOne, data.heightDecreasedByOne, cFactor2div3, cFactor2div3, normalVector);
271
272    if (width >= 3) {
273        // Top row
274        offset = cPixelSize;
275        for (int x = 1; x < data.widthDecreasedByOne; ++x, offset += cPixelSize) {
276            data.topRow(offset, normalVector);
277            inlineSetPixel(offset, data, paintingData, x, 0, cFactor1div3, cFactor1div2, normalVector);
278        }
279        // Bottom row
280        offset = data.heightDecreasedByOne * data.widthMultipliedByPixelSize + cPixelSize;
281        for (int x = 1; x < data.widthDecreasedByOne; ++x, offset += cPixelSize) {
282            data.bottomRow(offset, normalVector);
283            inlineSetPixel(offset, data, paintingData, x, data.heightDecreasedByOne, cFactor1div3, cFactor1div2, normalVector);
284        }
285    }
286
287    if (height >= 3) {
288        // Left column
289        offset = data.widthMultipliedByPixelSize;
290        for (int y = 1; y < data.heightDecreasedByOne; ++y, offset += data.widthMultipliedByPixelSize) {
291            data.leftColumn(offset, normalVector);
292            inlineSetPixel(offset, data, paintingData, 0, y, cFactor1div2, cFactor1div3, normalVector);
293        }
294        // Right column
295        offset = (data.widthMultipliedByPixelSize << 1) - cPixelSize;
296        for (int y = 1; y < data.heightDecreasedByOne; ++y, offset += data.widthMultipliedByPixelSize) {
297            data.rightColumn(offset, normalVector);
298            inlineSetPixel(offset, data, paintingData, data.widthDecreasedByOne, y, cFactor1div2, cFactor1div3, normalVector);
299        }
300    }
301
302    if (width >= 3 && height >= 3) {
303        // Interior pixels
304        for (int y = 1; y < data.heightDecreasedByOne; ++y) {
305            offset = y * data.widthMultipliedByPixelSize + cPixelSize;
306            for (int x = 1; x < data.widthDecreasedByOne; ++x, offset += cPixelSize) {
307                data.interior(offset, normalVector);
308                inlineSetPixel(offset, data, paintingData, x, y, cFactor1div4, cFactor1div4, normalVector);
309            }
310        }
311    }
312
313    int lastPixel = data.widthMultipliedByPixelSize * height;
314    if (m_lightingType == DiffuseLighting) {
315        for (int i = cAlphaChannelOffset; i < lastPixel; i += cPixelSize)
316            data.pixels->set(i, cOpaqueAlpha);
317    } else {
318        for (int i = 0; i < lastPixel; i += cPixelSize) {
319            unsigned char a1 = data.pixels->get(i);
320            unsigned char a2 = data.pixels->get(i + 1);
321            unsigned char a3 = data.pixels->get(i + 2);
322            // alpha set to set to max(a1, a2, a3)
323            data.pixels->set(i + 3, a1 >= a2 ? (a1 >= a3 ? a1 : a3) : (a2 >= a3 ? a2 : a3));
324        }
325    }
326
327    return true;
328}
329
330void FELighting::apply()
331{
332    if (hasResult())
333        return;
334    FilterEffect* in = inputEffect(0);
335    in->apply();
336    if (!in->hasResult())
337        return;
338
339    ByteArray* srcPixelArray = createUnmultipliedImageResult();
340    if (!srcPixelArray)
341        return;
342
343    setIsAlphaImage(false);
344
345    IntRect effectDrawingRect = requestedRegionOfInputImageData(in->absolutePaintRect());
346    in->copyUnmultipliedImage(srcPixelArray, effectDrawingRect);
347
348    // FIXME: support kernelUnitLengths other than (1,1). The issue here is that the W3
349    // standard has no test case for them, and other browsers (like Firefox) has strange
350    // output for various kernelUnitLengths, and I am not sure they are reliable.
351    // Anyway, feConvolveMatrix should also use the implementation
352
353    IntSize absolutePaintSize = absolutePaintRect().size();
354    drawLighting(srcPixelArray, absolutePaintSize.width(), absolutePaintSize.height());
355}
356
357} // namespace WebCore
358
359#endif // ENABLE(FILTERS)
360