#include "config.h"
#if ENABLE(FILTERS)
#include "FELighting.h"
#include "FELightingNEON.h"
#include <wtf/ParallelJobs.h>
namespace WebCore {
FELighting::FELighting(Filter* filter, LightingType lightingType, const Color& lightingColor, float surfaceScale,
float diffuseConstant, float specularConstant, float specularExponent,
float kernelUnitLengthX, float kernelUnitLengthY, PassRefPtr<LightSource> lightSource)
: FilterEffect(filter)
, m_lightingType(lightingType)
, m_lightSource(lightSource)
, m_lightingColor(lightingColor)
, m_surfaceScale(surfaceScale)
, m_diffuseConstant(diffuseConstant)
, m_specularConstant(specularConstant)
, m_specularExponent(specularExponent)
, m_kernelUnitLengthX(kernelUnitLengthX)
, m_kernelUnitLengthY(kernelUnitLengthY)
{
}
const static int cPixelSize = 4;
const static int cAlphaChannelOffset = 3;
const static unsigned char cOpaqueAlpha = static_cast<unsigned char>(0xff);
const static float cFactor1div2 = -1 / 2.f;
const static float cFactor1div3 = -1 / 3.f;
const static float cFactor1div4 = -1 / 4.f;
const static float cFactor2div3 = -2 / 3.f;
inline void FELighting::LightingData::topLeft(int offset, IntPoint& normalVector)
{
int center = static_cast<int>(pixels->item(offset + cAlphaChannelOffset));
int right = static_cast<int>(pixels->item(offset + cPixelSize + cAlphaChannelOffset));
offset += widthMultipliedByPixelSize;
int bottom = static_cast<int>(pixels->item(offset + cAlphaChannelOffset));
int bottomRight = static_cast<int>(pixels->item(offset + cPixelSize + cAlphaChannelOffset));
normalVector.setX(-(center << 1) + (right << 1) - bottom + bottomRight);
normalVector.setY(-(center << 1) - right + (bottom << 1) + bottomRight);
}
inline void FELighting::LightingData::topRow(int offset, IntPoint& normalVector)
{
int left = static_cast<int>(pixels->item(offset - cPixelSize + cAlphaChannelOffset));
int center = static_cast<int>(pixels->item(offset + cAlphaChannelOffset));
int right = static_cast<int>(pixels->item(offset + cPixelSize + cAlphaChannelOffset));
offset += widthMultipliedByPixelSize;
int bottomLeft = static_cast<int>(pixels->item(offset - cPixelSize + cAlphaChannelOffset));
int bottom = static_cast<int>(pixels->item(offset + cAlphaChannelOffset));
int bottomRight = static_cast<int>(pixels->item(offset + cPixelSize + cAlphaChannelOffset));
normalVector.setX(-(left << 1) + (right << 1) - bottomLeft + bottomRight);
normalVector.setY(-left - (center << 1) - right + bottomLeft + (bottom << 1) + bottomRight);
}
inline void FELighting::LightingData::topRight(int offset, IntPoint& normalVector)
{
int left = static_cast<int>(pixels->item(offset - cPixelSize + cAlphaChannelOffset));
int center = static_cast<int>(pixels->item(offset + cAlphaChannelOffset));
offset += widthMultipliedByPixelSize;
int bottomLeft = static_cast<int>(pixels->item(offset - cPixelSize + cAlphaChannelOffset));
int bottom = static_cast<int>(pixels->item(offset + cAlphaChannelOffset));
normalVector.setX(-(left << 1) + (center << 1) - bottomLeft + bottom);
normalVector.setY(-left - (center << 1) + bottomLeft + (bottom << 1));
}
inline void FELighting::LightingData::leftColumn(int offset, IntPoint& normalVector)
{
int center = static_cast<int>(pixels->item(offset + cAlphaChannelOffset));
int right = static_cast<int>(pixels->item(offset + cPixelSize + cAlphaChannelOffset));
offset -= widthMultipliedByPixelSize;
int top = static_cast<int>(pixels->item(offset + cAlphaChannelOffset));
int topRight = static_cast<int>(pixels->item(offset + cPixelSize + cAlphaChannelOffset));
offset += widthMultipliedByPixelSize << 1;
int bottom = static_cast<int>(pixels->item(offset + cAlphaChannelOffset));
int bottomRight = static_cast<int>(pixels->item(offset + cPixelSize + cAlphaChannelOffset));
normalVector.setX(-top + topRight - (center << 1) + (right << 1) - bottom + bottomRight);
normalVector.setY(-(top << 1) - topRight + (bottom << 1) + bottomRight);
}
inline void FELighting::LightingData::interior(int offset, IntPoint& normalVector)
{
int left = static_cast<int>(pixels->item(offset - cPixelSize + cAlphaChannelOffset));
int right = static_cast<int>(pixels->item(offset + cPixelSize + cAlphaChannelOffset));
offset -= widthMultipliedByPixelSize;
int topLeft = static_cast<int>(pixels->item(offset - cPixelSize + cAlphaChannelOffset));
int top = static_cast<int>(pixels->item(offset + cAlphaChannelOffset));
int topRight = static_cast<int>(pixels->item(offset + cPixelSize + cAlphaChannelOffset));
offset += widthMultipliedByPixelSize << 1;
int bottomLeft = static_cast<int>(pixels->item(offset - cPixelSize + cAlphaChannelOffset));
int bottom = static_cast<int>(pixels->item(offset + cAlphaChannelOffset));
int bottomRight = static_cast<int>(pixels->item(offset + cPixelSize + cAlphaChannelOffset));
normalVector.setX(-topLeft + topRight - (left << 1) + (right << 1) - bottomLeft + bottomRight);
normalVector.setY(-topLeft - (top << 1) - topRight + bottomLeft + (bottom << 1) + bottomRight);
}
inline void FELighting::LightingData::rightColumn(int offset, IntPoint& normalVector)
{
int left = static_cast<int>(pixels->item(offset - cPixelSize + cAlphaChannelOffset));
int center = static_cast<int>(pixels->item(offset + cAlphaChannelOffset));
offset -= widthMultipliedByPixelSize;
int topLeft = static_cast<int>(pixels->item(offset - cPixelSize + cAlphaChannelOffset));
int top = static_cast<int>(pixels->item(offset + cAlphaChannelOffset));
offset += widthMultipliedByPixelSize << 1;
int bottomLeft = static_cast<int>(pixels->item(offset - cPixelSize + cAlphaChannelOffset));
int bottom = static_cast<int>(pixels->item(offset + cAlphaChannelOffset));
normalVector.setX(-topLeft + top - (left << 1) + (center << 1) - bottomLeft + bottom);
normalVector.setY(-topLeft - (top << 1) + bottomLeft + (bottom << 1));
}
inline void FELighting::LightingData::bottomLeft(int offset, IntPoint& normalVector)
{
int center = static_cast<int>(pixels->item(offset + cAlphaChannelOffset));
int right = static_cast<int>(pixels->item(offset + cPixelSize + cAlphaChannelOffset));
offset -= widthMultipliedByPixelSize;
int top = static_cast<int>(pixels->item(offset + cAlphaChannelOffset));
int topRight = static_cast<int>(pixels->item(offset + cPixelSize + cAlphaChannelOffset));
normalVector.setX(-top + topRight - (center << 1) + (right << 1));
normalVector.setY(-(top << 1) - topRight + (center << 1) + right);
}
inline void FELighting::LightingData::bottomRow(int offset, IntPoint& normalVector)
{
int left = static_cast<int>(pixels->item(offset - cPixelSize + cAlphaChannelOffset));
int center = static_cast<int>(pixels->item(offset + cAlphaChannelOffset));
int right = static_cast<int>(pixels->item(offset + cPixelSize + cAlphaChannelOffset));
offset -= widthMultipliedByPixelSize;
int topLeft = static_cast<int>(pixels->item(offset - cPixelSize + cAlphaChannelOffset));
int top = static_cast<int>(pixels->item(offset + cAlphaChannelOffset));
int topRight = static_cast<int>(pixels->item(offset + cPixelSize + cAlphaChannelOffset));
normalVector.setX(-topLeft + topRight - (left << 1) + (right << 1));
normalVector.setY(-topLeft - (top << 1) - topRight + left + (center << 1) + right);
}
inline void FELighting::LightingData::bottomRight(int offset, IntPoint& normalVector)
{
int left = static_cast<int>(pixels->item(offset - cPixelSize + cAlphaChannelOffset));
int center = static_cast<int>(pixels->item(offset + cAlphaChannelOffset));
offset -= widthMultipliedByPixelSize;
int topLeft = static_cast<int>(pixels->item(offset - cPixelSize + cAlphaChannelOffset));
int top = static_cast<int>(pixels->item(offset + cAlphaChannelOffset));
normalVector.setX(-topLeft + top - (left << 1) + (center << 1));
normalVector.setY(-topLeft - (top << 1) + left + (center << 1));
}
inline void FELighting::inlineSetPixel(int offset, LightingData& data, LightSource::PaintingData& paintingData,
int lightX, int lightY, float factorX, float factorY, IntPoint& normal2DVector)
{
m_lightSource->updatePaintingData(paintingData, lightX, lightY, static_cast<float>(data.pixels->item(offset + cAlphaChannelOffset)) * data.surfaceScale);
float lightStrength;
if (!normal2DVector.x() && !normal2DVector.y()) {
if (m_lightingType == FELighting::DiffuseLighting)
lightStrength = m_diffuseConstant * paintingData.lightVector.z() / paintingData.lightVectorLength;
else {
FloatPoint3D halfwayVector = paintingData.lightVector;
halfwayVector.setZ(halfwayVector.z() + paintingData.lightVectorLength);
float halfwayVectorLength = halfwayVector.length();
if (m_specularExponent == 1)
lightStrength = m_specularConstant * halfwayVector.z() / halfwayVectorLength;
else
lightStrength = m_specularConstant * powf(halfwayVector.z() / halfwayVectorLength, m_specularExponent);
}
} else {
FloatPoint3D normalVector;
normalVector.setX(factorX * static_cast<float>(normal2DVector.x()) * data.surfaceScale);
normalVector.setY(factorY * static_cast<float>(normal2DVector.y()) * data.surfaceScale);
normalVector.setZ(1);
float normalVectorLength = normalVector.length();
if (m_lightingType == FELighting::DiffuseLighting)
lightStrength = m_diffuseConstant * (normalVector * paintingData.lightVector) / (normalVectorLength * paintingData.lightVectorLength);
else {
FloatPoint3D halfwayVector = paintingData.lightVector;
halfwayVector.setZ(halfwayVector.z() + paintingData.lightVectorLength);
float halfwayVectorLength = halfwayVector.length();
if (m_specularExponent == 1)
lightStrength = m_specularConstant * (normalVector * halfwayVector) / (normalVectorLength * halfwayVectorLength);
else
lightStrength = m_specularConstant * powf((normalVector * halfwayVector) / (normalVectorLength * halfwayVectorLength), m_specularExponent);
}
}
if (lightStrength > 1)
lightStrength = 1;
if (lightStrength < 0)
lightStrength = 0;
data.pixels->set(offset, static_cast<unsigned char>(lightStrength * paintingData.colorVector.x()));
data.pixels->set(offset + 1, static_cast<unsigned char>(lightStrength * paintingData.colorVector.y()));
data.pixels->set(offset + 2, static_cast<unsigned char>(lightStrength * paintingData.colorVector.z()));
}
void FELighting::setPixel(int offset, LightingData& data, LightSource::PaintingData& paintingData,
int lightX, int lightY, float factorX, float factorY, IntPoint& normalVector)
{
inlineSetPixel(offset, data, paintingData, lightX, lightY, factorX, factorY, normalVector);
}
inline void FELighting::platformApplyGenericPaint(LightingData& data, LightSource::PaintingData& paintingData, int startY, int endY)
{
IntPoint normalVector;
int offset = 0;
for (int y = startY; y < endY; ++y) {
offset = y * data.widthMultipliedByPixelSize + cPixelSize;
for (int x = 1; x < data.widthDecreasedByOne; ++x, offset += cPixelSize) {
data.interior(offset, normalVector);
inlineSetPixel(offset, data, paintingData, x, y, cFactor1div4, cFactor1div4, normalVector);
}
}
}
void FELighting::platformApplyGenericWorker(PlatformApplyGenericParameters* parameters)
{
parameters->filter->platformApplyGenericPaint(parameters->data, parameters->paintingData, parameters->yStart, parameters->yEnd);
}
inline void FELighting::platformApplyGeneric(LightingData& data, LightSource::PaintingData& paintingData)
{
int optimalThreadNumber = ((data.widthDecreasedByOne - 1) * (data.heightDecreasedByOne - 1)) / s_minimalRectDimension;
if (optimalThreadNumber > 1) {
WTF::ParallelJobs<PlatformApplyGenericParameters> parallelJobs(&platformApplyGenericWorker, optimalThreadNumber);
int job = parallelJobs.numberOfJobs();
if (job > 1) {
const int yStep = (data.heightDecreasedByOne - 1) / job;
const int jobsWithExtra = (data.heightDecreasedByOne - 1) % job;
int yStart = 1;
for (--job; job >= 0; --job) {
PlatformApplyGenericParameters& params = parallelJobs.parameter(job);
params.filter = this;
params.data = data;
params.paintingData = paintingData;
params.yStart = yStart;
yStart += job < jobsWithExtra ? yStep + 1 : yStep;
params.yEnd = yStart;
}
parallelJobs.execute();
return;
}
}
platformApplyGenericPaint(data, paintingData, 1, data.heightDecreasedByOne);
}
inline void FELighting::platformApply(LightingData& data, LightSource::PaintingData& paintingData)
{
#if CPU(ARM_NEON) && CPU(ARM_TRADITIONAL) && COMPILER(GCC)
platformApplyNeon(data, paintingData);
#else
platformApplyGeneric(data, paintingData);
#endif
}
bool FELighting::drawLighting(Uint8ClampedArray* pixels, int width, int height)
{
LightSource::PaintingData paintingData;
LightingData data;
if (!m_lightSource)
return false;
if (width <= 2 || height <= 2)
return false;
data.pixels = pixels;
data.surfaceScale = m_surfaceScale / 255.0f;
data.widthMultipliedByPixelSize = width * cPixelSize;
data.widthDecreasedByOne = width - 1;
data.heightDecreasedByOne = height - 1;
paintingData.colorVector = FloatPoint3D(m_lightingColor.red(), m_lightingColor.green(), m_lightingColor.blue());
m_lightSource->initPaintingData(paintingData);
IntPoint normalVector;
int offset = 0;
data.topLeft(offset, normalVector);
setPixel(offset, data, paintingData, 0, 0, cFactor2div3, cFactor2div3, normalVector);
offset = data.widthMultipliedByPixelSize - cPixelSize;
data.topRight(offset, normalVector);
setPixel(offset, data, paintingData, data.widthDecreasedByOne, 0, cFactor2div3, cFactor2div3, normalVector);
offset = data.heightDecreasedByOne * data.widthMultipliedByPixelSize;
data.bottomLeft(offset, normalVector);
setPixel(offset, data, paintingData, 0, data.heightDecreasedByOne, cFactor2div3, cFactor2div3, normalVector);
offset = height * data.widthMultipliedByPixelSize - cPixelSize;
data.bottomRight(offset, normalVector);
setPixel(offset, data, paintingData, data.widthDecreasedByOne, data.heightDecreasedByOne, cFactor2div3, cFactor2div3, normalVector);
if (width >= 3) {
offset = cPixelSize;
for (int x = 1; x < data.widthDecreasedByOne; ++x, offset += cPixelSize) {
data.topRow(offset, normalVector);
inlineSetPixel(offset, data, paintingData, x, 0, cFactor1div3, cFactor1div2, normalVector);
}
offset = data.heightDecreasedByOne * data.widthMultipliedByPixelSize + cPixelSize;
for (int x = 1; x < data.widthDecreasedByOne; ++x, offset += cPixelSize) {
data.bottomRow(offset, normalVector);
inlineSetPixel(offset, data, paintingData, x, data.heightDecreasedByOne, cFactor1div3, cFactor1div2, normalVector);
}
}
if (height >= 3) {
offset = data.widthMultipliedByPixelSize;
for (int y = 1; y < data.heightDecreasedByOne; ++y, offset += data.widthMultipliedByPixelSize) {
data.leftColumn(offset, normalVector);
inlineSetPixel(offset, data, paintingData, 0, y, cFactor1div2, cFactor1div3, normalVector);
}
offset = (data.widthMultipliedByPixelSize << 1) - cPixelSize;
for (int y = 1; y < data.heightDecreasedByOne; ++y, offset += data.widthMultipliedByPixelSize) {
data.rightColumn(offset, normalVector);
inlineSetPixel(offset, data, paintingData, data.widthDecreasedByOne, y, cFactor1div2, cFactor1div3, normalVector);
}
}
if (width >= 3 && height >= 3) {
platformApply(data, paintingData);
}
int lastPixel = data.widthMultipliedByPixelSize * height;
if (m_lightingType == DiffuseLighting) {
for (int i = cAlphaChannelOffset; i < lastPixel; i += cPixelSize)
data.pixels->set(i, cOpaqueAlpha);
} else {
for (int i = 0; i < lastPixel; i += cPixelSize) {
unsigned char a1 = data.pixels->item(i);
unsigned char a2 = data.pixels->item(i + 1);
unsigned char a3 = data.pixels->item(i + 2);
data.pixels->set(i + 3, a1 >= a2 ? (a1 >= a3 ? a1 : a3) : (a2 >= a3 ? a2 : a3));
}
}
return true;
}
void FELighting::platformApplySoftware()
{
FilterEffect* in = inputEffect(0);
Uint8ClampedArray* srcPixelArray = createPremultipliedImageResult();
if (!srcPixelArray)
return;
setIsAlphaImage(false);
IntRect effectDrawingRect = requestedRegionOfInputImageData(in->absolutePaintRect());
in->copyPremultipliedImage(srcPixelArray, effectDrawingRect);
IntSize absolutePaintSize = absolutePaintRect().size();
drawLighting(srcPixelArray, absolutePaintSize.width(), absolutePaintSize.height());
}
}
#endif // ENABLE(FILTERS)