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 
34 namespace WebCore {
35 
FELighting(Filter * filter,LightingType lightingType,const Color & lightingColor,float surfaceScale,float diffuseConstant,float specularConstant,float specularExponent,float kernelUnitLengthX,float kernelUnitLengthY,PassRefPtr<LightSource> lightSource)36 FELighting::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 
52 const static int cPixelSize = 4;
53 const static int cAlphaChannelOffset = 3;
54 const static unsigned char cOpaqueAlpha = static_cast<unsigned char>(0xff);
55 const static float cFactor1div2 = -1 / 2.f;
56 const static float cFactor1div3 = -1 / 3.f;
57 const static float cFactor1div4 = -1 / 4.f;
58 const static float cFactor2div3 = -2 / 3.f;
59 
60 // << 1 is signed multiply by 2
topLeft(int offset,IntPoint & normalVector)61 inline 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 
topRow(int offset,IntPoint & normalVector)72 inline 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 
topRight(int offset,IntPoint & normalVector)85 inline 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 
leftColumn(int offset,IntPoint & normalVector)96 inline 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 
interior(int offset,IntPoint & normalVector)110 inline 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 
rightColumn(int offset,IntPoint & normalVector)126 inline 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 
bottomLeft(int offset,IntPoint & normalVector)140 inline 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 
bottomRow(int offset,IntPoint & normalVector)151 inline 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 
bottomRight(int offset,IntPoint & normalVector)164 inline 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 
inlineSetPixel(int offset,LightingData & data,LightSource::PaintingData & paintingData,int lightX,int lightY,float factorX,float factorY,IntPoint & normal2DVector)175 inline 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 
setPixel(int offset,LightingData & data,LightSource::PaintingData & paintingData,int lightX,int lightY,float factorX,float factorY,IntPoint & normalVector)224 void 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 
platformApplyGeneric(LightingData & data,LightSource::PaintingData & paintingData)230 inline void FELighting::platformApplyGeneric(LightingData& data, LightSource::PaintingData& paintingData)
231 {
232     IntPoint normalVector;
233     int offset = 0;
234 
235     for (int y = 1; y < data.heightDecreasedByOne; ++y) {
236         offset = y * data.widthMultipliedByPixelSize + cPixelSize;
237         for (int x = 1; x < data.widthDecreasedByOne; ++x, offset += cPixelSize) {
238             data.interior(offset, normalVector);
239             inlineSetPixel(offset, data, paintingData, x, y, cFactor1div4, cFactor1div4, normalVector);
240         }
241     }
242 }
243 
platformApply(LightingData & data,LightSource::PaintingData & paintingData)244 inline void FELighting::platformApply(LightingData& data, LightSource::PaintingData& paintingData)
245 {
246     // The selection here eventually should happen dynamically on some platforms.
247 #if CPU(ARM_NEON) && COMPILER(GCC)
248     platformApplyNeon(data, paintingData);
249 #else
250     platformApplyGeneric(data, paintingData);
251 #endif
252 }
253 
drawLighting(ByteArray * pixels,int width,int height)254 bool FELighting::drawLighting(ByteArray* pixels, int width, int height)
255 {
256     LightSource::PaintingData paintingData;
257     LightingData data;
258 
259     if (!m_lightSource)
260         return false;
261 
262     // FIXME: do something if width or height (or both) is 1 pixel.
263     // The W3 spec does not define this case. Now the filter just returns.
264     if (width <= 2 || height <= 2)
265         return false;
266 
267     data.pixels = pixels;
268     data.surfaceScale = m_surfaceScale / 255.0f;
269     data.widthMultipliedByPixelSize = width * cPixelSize;
270     data.widthDecreasedByOne = width - 1;
271     data.heightDecreasedByOne = height - 1;
272     paintingData.colorVector = FloatPoint3D(m_lightingColor.red(), m_lightingColor.green(), m_lightingColor.blue());
273     m_lightSource->initPaintingData(paintingData);
274 
275     // Top/Left corner.
276     IntPoint normalVector;
277     int offset = 0;
278     data.topLeft(offset, normalVector);
279     setPixel(offset, data, paintingData, 0, 0, cFactor2div3, cFactor2div3, normalVector);
280 
281     // Top/Right pixel.
282     offset = data.widthMultipliedByPixelSize - cPixelSize;
283     data.topRight(offset, normalVector);
284     setPixel(offset, data, paintingData, data.widthDecreasedByOne, 0, cFactor2div3, cFactor2div3, normalVector);
285 
286     // Bottom/Left pixel.
287     offset = data.heightDecreasedByOne * data.widthMultipliedByPixelSize;
288     data.bottomLeft(offset, normalVector);
289     setPixel(offset, data, paintingData, 0, data.heightDecreasedByOne, cFactor2div3, cFactor2div3, normalVector);
290 
291     // Bottom/Right pixel.
292     offset = height * data.widthMultipliedByPixelSize - cPixelSize;
293     data.bottomRight(offset, normalVector);
294     setPixel(offset, data, paintingData, data.widthDecreasedByOne, data.heightDecreasedByOne, cFactor2div3, cFactor2div3, normalVector);
295 
296     if (width >= 3) {
297         // Top row.
298         offset = cPixelSize;
299         for (int x = 1; x < data.widthDecreasedByOne; ++x, offset += cPixelSize) {
300             data.topRow(offset, normalVector);
301             inlineSetPixel(offset, data, paintingData, x, 0, cFactor1div3, cFactor1div2, normalVector);
302         }
303         // Bottom row.
304         offset = data.heightDecreasedByOne * data.widthMultipliedByPixelSize + cPixelSize;
305         for (int x = 1; x < data.widthDecreasedByOne; ++x, offset += cPixelSize) {
306             data.bottomRow(offset, normalVector);
307             inlineSetPixel(offset, data, paintingData, x, data.heightDecreasedByOne, cFactor1div3, cFactor1div2, normalVector);
308         }
309     }
310 
311     if (height >= 3) {
312         // Left column.
313         offset = data.widthMultipliedByPixelSize;
314         for (int y = 1; y < data.heightDecreasedByOne; ++y, offset += data.widthMultipliedByPixelSize) {
315             data.leftColumn(offset, normalVector);
316             inlineSetPixel(offset, data, paintingData, 0, y, cFactor1div2, cFactor1div3, normalVector);
317         }
318         // Right column.
319         offset = (data.widthMultipliedByPixelSize << 1) - cPixelSize;
320         for (int y = 1; y < data.heightDecreasedByOne; ++y, offset += data.widthMultipliedByPixelSize) {
321             data.rightColumn(offset, normalVector);
322             inlineSetPixel(offset, data, paintingData, data.widthDecreasedByOne, y, cFactor1div2, cFactor1div3, normalVector);
323         }
324     }
325 
326     if (width >= 3 && height >= 3) {
327         // Interior pixels.
328         platformApply(data, paintingData);
329     }
330 
331     int lastPixel = data.widthMultipliedByPixelSize * height;
332     if (m_lightingType == DiffuseLighting) {
333         for (int i = cAlphaChannelOffset; i < lastPixel; i += cPixelSize)
334             data.pixels->set(i, cOpaqueAlpha);
335     } else {
336         for (int i = 0; i < lastPixel; i += cPixelSize) {
337             unsigned char a1 = data.pixels->get(i);
338             unsigned char a2 = data.pixels->get(i + 1);
339             unsigned char a3 = data.pixels->get(i + 2);
340             // alpha set to set to max(a1, a2, a3)
341             data.pixels->set(i + 3, a1 >= a2 ? (a1 >= a3 ? a1 : a3) : (a2 >= a3 ? a2 : a3));
342         }
343     }
344 
345     return true;
346 }
347 
apply()348 void FELighting::apply()
349 {
350     if (hasResult())
351         return;
352     FilterEffect* in = inputEffect(0);
353     in->apply();
354     if (!in->hasResult())
355         return;
356 
357     ByteArray* srcPixelArray = createUnmultipliedImageResult();
358     if (!srcPixelArray)
359         return;
360 
361     setIsAlphaImage(false);
362 
363     IntRect effectDrawingRect = requestedRegionOfInputImageData(in->absolutePaintRect());
364     in->copyUnmultipliedImage(srcPixelArray, effectDrawingRect);
365 
366     // FIXME: support kernelUnitLengths other than (1,1). The issue here is that the W3
367     // standard has no test case for them, and other browsers (like Firefox) has strange
368     // output for various kernelUnitLengths, and I am not sure they are reliable.
369     // Anyway, feConvolveMatrix should also use the implementation
370 
371     IntSize absolutePaintSize = absolutePaintRect().size();
372     drawLighting(srcPixelArray, absolutePaintSize.width(), absolutePaintSize.height());
373 }
374 
375 } // namespace WebCore
376 
377 #endif // ENABLE(FILTERS)
378