Raster implementation of diffuse and specular lighting filters.  Externally,
the caller instantiates a light (distant, point or spot), and an
SkDiffuseLightingFilter or SkSpecularLightingImageFilter with that light.  A
Sobel edge detection filter is applied to the alpha of the incoming bitmap, and
the result is used as a height map for lighting calculations.

Review URL:  http://codereview.appspot.com/6302101/



git-svn-id: http://skia.googlecode.com/svn/trunk@4314 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/src/effects/SkLightingImageFilter.cpp b/src/effects/SkLightingImageFilter.cpp
new file mode 100644
index 0000000..c2551d1
--- /dev/null
+++ b/src/effects/SkLightingImageFilter.cpp
@@ -0,0 +1,599 @@
+/*
+ * Copyright 2012 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkLightingImageFilter.h"
+#include "SkBitmap.h"
+#include "SkColorPriv.h"
+
+// FIXME:  Eventually, this should be implemented properly, and put in
+// SkScalar.h.
+#define SkScalarPow(x, y) SkFloatToScalar(powf(SkScalarToFloat(x), SkScalarToFloat(y)))
+namespace {
+
+const SkScalar gOneThird = SkScalarInvert(SkIntToScalar(3));
+const SkScalar gTwoThirds = SkScalarDiv(SkIntToScalar(2), SkIntToScalar(3));
+const SkScalar gOneHalf = SkFloatToScalar(0.5f);
+const SkScalar gOneQuarter = SkFloatToScalar(0.25f);
+
+// Shift matrix components to the left, as we advance pixels to the right.
+inline void shiftMatrixLeft(int m[9]) {
+    m[0] = m[1];
+    m[3] = m[4];
+    m[6] = m[7];
+    m[1] = m[2];
+    m[4] = m[5];
+    m[7] = m[8];
+}
+
+class DiffuseLightingType {
+public:
+    DiffuseLightingType(SkScalar kd)
+        : fKD(kd) {}
+    SkPMColor light(const SkPoint3& normal, const SkPoint3& surfaceTolight, const SkPoint3& lightColor) const {
+        SkScalar colorScale = SkScalarMul(fKD, normal.dot(surfaceTolight));
+        colorScale = SkScalarClampMax(colorScale, SK_Scalar1);
+        SkPoint3 color(lightColor * colorScale);
+        return SkPackARGB32(255,
+                            SkScalarFloorToInt(color.fX),
+                            SkScalarFloorToInt(color.fY),
+                            SkScalarFloorToInt(color.fZ));
+    }
+private:
+    SkScalar fKD;
+};
+
+class SpecularLightingType {
+public:
+    SpecularLightingType(SkScalar ks, SkScalar shininess)
+        : fKS(ks), fShininess(shininess) {}
+    SkPMColor light(const SkPoint3& normal, const SkPoint3& surfaceTolight, const SkPoint3& lightColor) const {
+        SkPoint3 halfDir(surfaceTolight);
+        halfDir.fZ += SK_Scalar1;        // eye position is always (0, 0, 1)
+        halfDir.normalize();
+        SkScalar colorScale = SkScalarMul(fKS,
+            SkScalarPow(normal.dot(halfDir), fShininess));
+        colorScale = SkScalarClampMax(colorScale, SK_Scalar1);
+        SkPoint3 color(lightColor * colorScale);
+        return SkPackARGB32(SkScalarFloorToInt(color.maxComponent()),
+                            SkScalarFloorToInt(color.fX),
+                            SkScalarFloorToInt(color.fY),
+                            SkScalarFloorToInt(color.fZ));
+    }
+private:
+    SkScalar fKS;
+    SkScalar fShininess;
+};
+
+inline SkScalar sobel(int a, int b, int c, int d, int e, int f, SkScalar scale) {
+    return SkScalarMul(SkIntToScalar(-a + b - 2 * c + 2 * d -e + f), scale);
+}
+
+inline SkPoint3 pointToNormal(SkScalar x, SkScalar y, SkScalar surfaceScale) {
+    SkPoint3 vector(SkScalarMul(-x, surfaceScale),
+                    SkScalarMul(-y, surfaceScale),
+                    SK_Scalar1);
+    vector.normalize();
+    return vector;
+}
+
+inline SkPoint3 topLeftNormal(int m[9], SkScalar surfaceScale) {
+    return pointToNormal(sobel(0, 0, m[4], m[5], m[7], m[8], gTwoThirds),
+                         sobel(0, 0, m[4], m[7], m[5], m[8], gTwoThirds),
+                         surfaceScale);
+}
+
+inline SkPoint3 topNormal(int m[9], SkScalar surfaceScale) {
+    return pointToNormal(sobel(   0,    0, m[3], m[5], m[6], m[8], gOneThird),
+                         sobel(m[3], m[6], m[4], m[7], m[5], m[8], gOneHalf),
+                         surfaceScale);
+}
+
+inline SkPoint3 topRightNormal(int m[9], SkScalar surfaceScale) {
+    return pointToNormal(sobel(   0,    0, m[3], m[4], m[6], m[7], gTwoThirds),
+                         sobel(m[3], m[6], m[4], m[7],    0,    0, gTwoThirds),
+                         surfaceScale);
+}
+
+inline SkPoint3 leftNormal(int m[9], SkScalar surfaceScale) {
+    return pointToNormal(sobel(m[1], m[2], m[4], m[5], m[7], m[8], gOneHalf),
+                         sobel(   0,    0, m[1], m[7], m[2], m[8], gOneThird),
+                         surfaceScale);
+}
+
+
+inline SkPoint3 interiorNormal(int m[9], SkScalar surfaceScale) {
+    return pointToNormal(sobel(m[0], m[2], m[3], m[5], m[6], m[8], gOneQuarter),
+                         sobel(m[0], m[6], m[1], m[7], m[2], m[8], gOneQuarter),
+                         surfaceScale);
+}
+
+inline SkPoint3 rightNormal(int m[9], SkScalar surfaceScale) {
+    return pointToNormal(sobel(m[0], m[1], m[3], m[4], m[6], m[7], gOneHalf),
+                         sobel(m[0], m[6], m[1], m[7],    0,    0, gOneThird),
+                         surfaceScale);
+}
+
+inline SkPoint3 bottomLeftNormal(int m[9], SkScalar surfaceScale) {
+    return pointToNormal(sobel(m[1], m[2], m[4], m[5],    0,    0, gTwoThirds),
+                         sobel(   0,    0, m[1], m[4], m[2], m[5], gTwoThirds),
+                         surfaceScale);
+}
+
+inline SkPoint3 bottomNormal(int m[9], SkScalar surfaceScale) {
+    return pointToNormal(sobel(m[0], m[2], m[3], m[5],    0,    0, gOneThird),
+                         sobel(m[0], m[3], m[1], m[4], m[2], m[5], gOneHalf),
+                         surfaceScale);
+}
+
+inline SkPoint3 bottomRightNormal(int m[9], SkScalar surfaceScale) {
+    return pointToNormal(sobel(m[0], m[1], m[3], m[4], 0,  0, gTwoThirds),
+                         sobel(m[0], m[3], m[1], m[4], 0,  0, gTwoThirds),
+                         surfaceScale);
+}
+
+template <class LightingType, class LightType> void lightBitmap(const LightingType& lightingType, const SkLight* light, const SkBitmap& src, SkBitmap* dst, const SkPoint3& lightColor, SkScalar surfaceScale) {
+    const LightType* l = static_cast<const LightType*>(light);
+    int y = 0;
+    {
+        const SkPMColor* row1 = src.getAddr32(0, 0);
+        const SkPMColor* row2 = src.getAddr32(0, 1);
+        SkPMColor* dptr = dst->getAddr32(0, 0);
+        int m[9];
+        int x = 0;
+        m[4] = SkGetPackedA32(*row1++);
+        m[5] = SkGetPackedA32(*row1++);
+        m[7] = SkGetPackedA32(*row2++);
+        m[8] = SkGetPackedA32(*row2++);
+        SkPoint3 surfaceToLight = l->surfaceToLight(x, y, m[4], surfaceScale);
+        *dptr++ = lightingType.light(topLeftNormal(m, surfaceScale), surfaceToLight, lightColor * l->lightColorScale(surfaceToLight));
+        for (x = 1; x < src.width() - 1; ++x)
+        {
+            shiftMatrixLeft(m);
+            m[5] = SkGetPackedA32(*row1++);
+            m[8] = SkGetPackedA32(*row2++);
+            surfaceToLight = l->surfaceToLight(x, 0, m[4], surfaceScale);
+            *dptr++ = lightingType.light(topNormal(m, surfaceScale), surfaceToLight, lightColor * l->lightColorScale(surfaceToLight));
+        }
+        shiftMatrixLeft(m);
+        surfaceToLight = l->surfaceToLight(x, y, m[4], surfaceScale);
+        *dptr++ = lightingType.light(topRightNormal(m, surfaceScale), surfaceToLight, lightColor * l->lightColorScale(surfaceToLight));
+    }
+
+    for (++y; y < src.height() - 1; ++y) {
+        const SkPMColor* row0 = src.getAddr32(0, y - 1);
+        const SkPMColor* row1 = src.getAddr32(0, y);
+        const SkPMColor* row2 = src.getAddr32(0, y + 1);
+        SkPMColor* dptr = dst->getAddr32(0, y);
+        int m[9];
+        int x = 0;
+        m[1] = SkGetPackedA32(*row0++);
+        m[2] = SkGetPackedA32(*row0++);
+        m[4] = SkGetPackedA32(*row1++);
+        m[5] = SkGetPackedA32(*row1++);
+        m[7] = SkGetPackedA32(*row2++);
+        m[8] = SkGetPackedA32(*row2++);
+        SkPoint3 surfaceToLight = l->surfaceToLight(x, y, m[4], surfaceScale);
+        *dptr++ = lightingType.light(leftNormal(m, surfaceScale), surfaceToLight, lightColor * l->lightColorScale(surfaceToLight));
+        for (x = 1; x < src.width() - 1; ++x) {
+            shiftMatrixLeft(m);
+            m[2] = SkGetPackedA32(*row0++);
+            m[5] = SkGetPackedA32(*row1++);
+            m[8] = SkGetPackedA32(*row2++);
+            surfaceToLight = l->surfaceToLight(x, y, m[4], surfaceScale);
+            *dptr++ = lightingType.light(interiorNormal(m, surfaceScale), surfaceToLight, lightColor * l->lightColorScale(surfaceToLight));
+        }
+        shiftMatrixLeft(m);
+        surfaceToLight = l->surfaceToLight(x, y, m[4], surfaceScale);
+        *dptr++ = lightingType.light(rightNormal(m, surfaceScale), surfaceToLight, lightColor * l->lightColorScale(surfaceToLight));
+    }
+
+    {
+        const SkPMColor* row0 = src.getAddr32(0, src.height() - 2);
+        const SkPMColor* row1 = src.getAddr32(0, src.height() - 1);
+        int x = 0;
+        SkPMColor* dptr = dst->getAddr32(0, src.height() - 1);
+        int m[9];
+        m[1] = SkGetPackedA32(*row0++);
+        m[2] = SkGetPackedA32(*row0++);
+        m[4] = SkGetPackedA32(*row1++);
+        m[5] = SkGetPackedA32(*row1++);
+        SkPoint3 surfaceToLight = l->surfaceToLight(x, y, m[4], surfaceScale);
+        *dptr++ = lightingType.light(bottomLeftNormal(m, surfaceScale), surfaceToLight, lightColor * l->lightColorScale(surfaceToLight));
+        for (x = 1; x < src.width() - 1; ++x)
+        {
+            shiftMatrixLeft(m);
+            m[2] = SkGetPackedA32(*row0++);
+            m[5] = SkGetPackedA32(*row1++);
+            surfaceToLight = l->surfaceToLight(x, y, m[4], surfaceScale);
+            *dptr++ = lightingType.light(bottomNormal(m, surfaceScale), surfaceToLight, lightColor * l->lightColorScale(surfaceToLight));
+        }
+        shiftMatrixLeft(m);
+        surfaceToLight = l->surfaceToLight(x, y, m[4], surfaceScale);
+        *dptr++ = lightingType.light(bottomRightNormal(m, surfaceScale), surfaceToLight, lightColor * l->lightColorScale(surfaceToLight));
+    }
+}
+
+SkPoint3 readPoint3(SkFlattenableReadBuffer& buffer) {
+    SkPoint3 point;
+    point.fX = buffer.readScalar();
+    point.fY = buffer.readScalar();
+    point.fZ = buffer.readScalar();
+    return point;
+};
+
+void writePoint3(const SkPoint3& point, SkFlattenableWriteBuffer& buffer) {
+    buffer.writeScalar(point.fX);
+    buffer.writeScalar(point.fY);
+    buffer.writeScalar(point.fZ);
+};
+
+class SkDiffuseLightingImageFilter : public SkLightingImageFilter {
+public:
+    SkDiffuseLightingImageFilter(SkLight* light, const SkColor& lightColor,
+                                 SkScalar surfaceScale, SkScalar kd);
+    SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkDiffuseLightingImageFilter)
+
+protected:
+    explicit SkDiffuseLightingImageFilter(SkFlattenableReadBuffer& buffer);
+    virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE;
+    virtual bool onFilterImage(Proxy*, const SkBitmap& src, const SkMatrix&,
+                               SkBitmap* result, SkIPoint* offset) SK_OVERRIDE;
+
+
+private:
+    typedef SkLightingImageFilter INHERITED;
+    SkScalar fKD;
+};
+
+class SkSpecularLightingImageFilter : public SkLightingImageFilter {
+public:
+    SkSpecularLightingImageFilter(SkLight* light, const SkColor& lightColor,
+                                  SkScalar surfaceScale, SkScalar ks, SkScalar shininess);
+    SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkSpecularLightingImageFilter)
+
+protected:
+    explicit SkSpecularLightingImageFilter(SkFlattenableReadBuffer& buffer);
+    virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE;
+    virtual bool onFilterImage(Proxy*, const SkBitmap& src, const SkMatrix&,
+                               SkBitmap* result, SkIPoint* offset) SK_OVERRIDE;
+
+private:
+    typedef SkLightingImageFilter INHERITED;
+    SkScalar fKS;
+    SkScalar fShininess;
+};
+
+};
+
+class SkLight : public SkFlattenable {
+public:
+    enum LightType {
+        kDistant_LightType,
+        kPoint_LightType,
+        kSpot_LightType,
+    };
+    virtual LightType type() const = 0;
+
+    static SkLight* Create(SkFlattenableReadBuffer& buffer);
+    virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE {
+        buffer.write32(type());
+    }
+};
+
+class SkDistantLight : public SkLight {
+public:
+    SkDistantLight(const SkPoint3& direction) : fDirection(direction) {
+    }
+    SkDistantLight(SkFlattenableReadBuffer& buffer) {
+        fDirection = readPoint3(buffer);
+    }
+    SkPoint3 surfaceToLight(int x, int y, int z, SkScalar surfaceScale) const {
+        return fDirection;
+    };
+    SkScalar lightColorScale(const SkPoint3&) const { return SK_Scalar1; }
+    virtual LightType type() const { return kDistant_LightType; }
+    virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE {
+        SkLight::flatten(buffer);
+        writePoint3(fDirection, buffer);
+    }
+
+    SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkDistantLight)
+
+private:
+    SkPoint3 fDirection;
+};
+
+class SkPointLight : public SkLight {
+public:
+    SkPointLight(const SkPoint3& location)
+     : fLocation(location) {}
+    SkPointLight(SkFlattenableReadBuffer& buffer) {
+        fLocation = readPoint3(buffer);
+    }
+    SkPoint3 surfaceToLight(int x, int y, int z, SkScalar surfaceScale) const {
+        SkPoint3 direction(fLocation.fX - SkIntToScalar(x),
+                           fLocation.fY - SkIntToScalar(y),
+                           fLocation.fZ - SkScalarMul(SkIntToScalar(z), surfaceScale));
+        direction.normalize();
+        return direction;
+    };
+    SkScalar lightColorScale(const SkPoint3&) const { return SK_Scalar1; }
+    virtual LightType type() const { return kPoint_LightType; }
+    virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE {
+        SkLight::flatten(buffer);
+        writePoint3(fLocation, buffer);
+    }
+
+    SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkPointLight)
+
+private:
+    SkPoint3 fLocation;
+};
+
+class SkSpotLight : public SkLight {
+public:
+    SkSpotLight(const SkPoint3& location, const SkPoint3& target, SkScalar specularExponent, SkScalar cutoffAngle)
+     : fLocation(location),
+       fTarget(target),
+       fSpecularExponent(specularExponent)
+    {
+       fS = target - location;
+       fS.normalize();
+       fCosOuterConeAngle = SkScalarCos(SkDegreesToRadians(cutoffAngle));
+       const SkScalar antiAliasThreshold = SkFloatToScalar(0.016f);
+       fCosInnerConeAngle = fCosOuterConeAngle + antiAliasThreshold;
+       fConeScale = SkScalarInvert(antiAliasThreshold);
+    }
+    SkSpotLight(SkFlattenableReadBuffer& buffer) {
+        fLocation = readPoint3(buffer);
+        fTarget = readPoint3(buffer);
+        fSpecularExponent = buffer.readScalar();
+        fCosOuterConeAngle = buffer.readScalar();
+        fCosInnerConeAngle = buffer.readScalar();
+        fConeScale = buffer.readScalar();
+        fS = readPoint3(buffer);
+    }
+    virtual void flatten(SkFlattenableWriteBuffer& buffer) const SK_OVERRIDE {
+        SkLight::flatten(buffer);
+        writePoint3(fLocation, buffer);
+        writePoint3(fTarget, buffer);
+        buffer.writeScalar(fSpecularExponent);
+        buffer.writeScalar(fCosOuterConeAngle);
+        buffer.writeScalar(fCosInnerConeAngle);
+        buffer.writeScalar(fConeScale);
+        writePoint3(fS, buffer);
+    }
+    SkPoint3 surfaceToLight(int x, int y, int z, SkScalar surfaceScale) const {
+        SkPoint3 direction(fLocation.fX - SkIntToScalar(x),
+                           fLocation.fY - SkIntToScalar(y),
+                           fLocation.fZ - SkScalarMul(SkIntToScalar(z), surfaceScale));
+        direction.normalize();
+        return direction;
+    };
+    SkScalar lightColorScale(const SkPoint3& surfaceToLight) const {
+        SkScalar cosAngle = -surfaceToLight.dot(fS);
+        if (cosAngle < fCosOuterConeAngle) {
+            return 0;
+        }
+        SkScalar scale = SkScalarPow(cosAngle, fSpecularExponent);
+        if (cosAngle < fCosInnerConeAngle) {
+            scale = SkScalarMul(scale, cosAngle - fCosOuterConeAngle);
+            return SkScalarMul(scale, fConeScale);
+        }
+        return scale;
+    }
+
+    SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkSpotLight)
+
+    virtual LightType type() const { return kSpot_LightType; }
+private:
+    SkPoint3 fLocation;
+    SkPoint3 fTarget;
+    SkScalar fSpecularExponent;
+    SkScalar fCosOuterConeAngle;
+    SkScalar fCosInnerConeAngle;
+    SkScalar fConeScale;
+    SkPoint3 fS;
+};
+
+SkLight* SkLight::Create(SkFlattenableReadBuffer& buffer) {
+    LightType type = static_cast<LightType>(buffer.readU32());
+    switch (type) {
+        case kDistant_LightType:
+            return new SkDistantLight(buffer);
+        case kPoint_LightType:
+            return new SkPointLight(buffer);
+        case kSpot_LightType:
+            return new SkSpotLight(buffer);
+        default:
+            SkASSERT(false);
+            return 0;
+    }
+}
+
+SkLightingImageFilter::SkLightingImageFilter(SkLight* light, const SkColor& lightColor, SkScalar surfaceScale)
+  : fLight(light),
+    fLightColor(SkIntToScalar(SkColorGetR(lightColor)),
+                SkIntToScalar(SkColorGetG(lightColor)),
+                SkIntToScalar(SkColorGetB(lightColor))),
+    fSurfaceScale(SkScalarDiv(surfaceScale, SkIntToScalar(255)))
+{
+    SkASSERT(fLight);
+    fLight->ref();
+}
+
+SkImageFilter* SkLightingImageFilter::CreateDistantLitDiffuse(
+    const SkPoint3& direction, const SkColor& lightColor, SkScalar surfaceScale,
+    SkScalar kd) {
+    return new SkDiffuseLightingImageFilter(
+        new SkDistantLight(direction), lightColor, surfaceScale, kd);
+}
+
+SkImageFilter* SkLightingImageFilter::CreatePointLitDiffuse(
+    SkPoint3& location, const SkColor& lightColor, SkScalar surfaceScale,
+    SkScalar kd) {
+    return new SkDiffuseLightingImageFilter(
+        new SkPointLight(location), lightColor, surfaceScale, kd);
+}
+
+SkImageFilter* SkLightingImageFilter::CreateSpotLitDiffuse(
+    const SkPoint3& location, const SkPoint3& target, SkScalar specularExponent,
+    SkScalar cutoffAngle, const SkColor& lightColor, SkScalar surfaceScale,
+    SkScalar kd) {
+    return new SkDiffuseLightingImageFilter(
+        new SkSpotLight(location, target, specularExponent, cutoffAngle),
+        lightColor, surfaceScale, kd);
+}
+
+SkImageFilter* SkLightingImageFilter::CreateDistantLitSpecular(
+    const SkPoint3& direction, const SkColor& lightColor, SkScalar surfaceScale,
+    SkScalar ks, SkScalar shininess) {
+    return new SkSpecularLightingImageFilter(
+        new SkDistantLight(direction), lightColor, surfaceScale, ks, shininess);
+}
+
+SkImageFilter* SkLightingImageFilter::CreatePointLitSpecular(
+    SkPoint3& location,
+    const SkColor& lightColor, SkScalar surfaceScale, SkScalar ks,
+    SkScalar shininess) {
+    return new SkSpecularLightingImageFilter(
+        new SkPointLight(location), lightColor, surfaceScale, ks, shininess);
+}
+
+SkImageFilter* SkLightingImageFilter::CreateSpotLitSpecular(
+    const SkPoint3& location,
+    const SkPoint3& target, SkScalar specularExponent, SkScalar cutoffAngle,
+    const SkColor& lightColor, SkScalar surfaceScale, SkScalar ks,
+    SkScalar shininess) {
+    return new SkSpecularLightingImageFilter(
+        new SkSpotLight(location, target, specularExponent, cutoffAngle),
+        lightColor, surfaceScale, ks, shininess);
+}
+
+SkLightingImageFilter::~SkLightingImageFilter() {
+    fLight->unref();
+}
+
+SkLightingImageFilter::SkLightingImageFilter(SkFlattenableReadBuffer& buffer)
+  : INHERITED(buffer)
+{
+    fLight = SkLight::Create(buffer);
+    fLightColor = readPoint3(buffer);
+    fSurfaceScale = buffer.readScalar();
+}
+
+void SkLightingImageFilter::flatten(SkFlattenableWriteBuffer& buffer) const {
+    this->INHERITED::flatten(buffer);
+    fLight->flatten(buffer);
+    writePoint3(fLightColor, buffer);
+    buffer.writeScalar(fSurfaceScale);
+}
+
+SkDiffuseLightingImageFilter::SkDiffuseLightingImageFilter(SkLight* light, const SkColor& lightColor, SkScalar surfaceScale, SkScalar kd)
+  : SkLightingImageFilter(light, lightColor, surfaceScale),
+    fKD(kd)
+{
+}
+
+SkDiffuseLightingImageFilter::SkDiffuseLightingImageFilter(SkFlattenableReadBuffer& buffer)
+  : INHERITED(buffer)
+{
+    fKD = buffer.readScalar();
+}
+
+void SkDiffuseLightingImageFilter::flatten(SkFlattenableWriteBuffer& buffer) const {
+    this->INHERITED::flatten(buffer);
+    buffer.writeScalar(fKD);
+}
+
+bool SkDiffuseLightingImageFilter::onFilterImage(Proxy*,
+                                                 const SkBitmap& src,
+                                                 const SkMatrix&,
+                                                 SkBitmap* dst,
+                                                 SkIPoint*) {
+    if (src.config() != SkBitmap::kARGB_8888_Config) {
+        return false;
+    }
+    SkAutoLockPixels alp(src);
+    if (!src.getPixels()) {
+        return false;
+    }
+    if (src.width() < 2 || src.height() < 2) {
+        return false;
+    }
+    dst->setConfig(src.config(), src.width(), src.height());
+    dst->allocPixels();
+
+    DiffuseLightingType lightingType(fKD);
+    switch (light()->type()) {
+        case SkLight::kDistant_LightType:
+            lightBitmap<DiffuseLightingType, SkDistantLight>(lightingType, light(), src, dst, lightColor(), surfaceScale());
+            break;
+        case SkLight::kPoint_LightType:
+            lightBitmap<DiffuseLightingType, SkPointLight>(lightingType, light(), src, dst, lightColor(), surfaceScale());
+            break;
+        case SkLight::kSpot_LightType:
+            lightBitmap<DiffuseLightingType, SkSpotLight>(lightingType, light(), src, dst, lightColor(), surfaceScale());
+            break;
+    }
+    return true;
+}
+
+SkSpecularLightingImageFilter::SkSpecularLightingImageFilter(SkLight* light, const SkColor& lightColor, SkScalar surfaceScale, SkScalar ks, SkScalar shininess)
+  : SkLightingImageFilter(light, lightColor, surfaceScale),
+    fKS(ks),
+    fShininess(shininess)
+{
+}
+
+SkSpecularLightingImageFilter::SkSpecularLightingImageFilter(SkFlattenableReadBuffer& buffer)
+  : INHERITED(buffer)
+{
+    fKS = buffer.readScalar();
+    fShininess = buffer.readScalar();
+}
+
+void SkSpecularLightingImageFilter::flatten(SkFlattenableWriteBuffer& buffer) const {
+    this->INHERITED::flatten(buffer);
+    buffer.writeScalar(fKS);
+    buffer.writeScalar(fShininess);
+}
+
+bool SkSpecularLightingImageFilter::onFilterImage(Proxy*,
+                                                  const SkBitmap& src,
+                                                  const SkMatrix&,
+                                                  SkBitmap* dst,
+                                                  SkIPoint*) {
+    if (src.config() != SkBitmap::kARGB_8888_Config) {
+        return false;
+    }
+    SkAutoLockPixels alp(src);
+    if (!src.getPixels()) {
+        return false;
+    }
+    if (src.width() < 2 || src.height() < 2) {
+        return false;
+    }
+    dst->setConfig(src.config(), src.width(), src.height());
+    dst->allocPixels();
+
+    SpecularLightingType lightingType(fKS, fShininess);
+    switch (light()->type()) {
+        case SkLight::kDistant_LightType:
+            lightBitmap<SpecularLightingType, SkDistantLight>(lightingType, light(), src, dst, lightColor(), surfaceScale());
+            break;
+        case SkLight::kPoint_LightType:
+            lightBitmap<SpecularLightingType, SkPointLight>(lightingType, light(), src, dst, lightColor(), surfaceScale());
+            break;
+        case SkLight::kSpot_LightType:
+            lightBitmap<SpecularLightingType, SkSpotLight>(lightingType, light(), src, dst, lightColor(), surfaceScale());
+            break;
+    }
+    return true;
+}
+
+SK_DEFINE_FLATTENABLE_REGISTRAR(SkLightingImageFilter)