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/gm/lighting.cpp b/gm/lighting.cpp
new file mode 100644
index 0000000..2b41528
--- /dev/null
+++ b/gm/lighting.cpp
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "gm.h"
+#include "SkLightingImageFilter.h"
+
+#define WIDTH 330
+#define HEIGHT 220
+
+namespace skiagm {
+
+class ImageLightingGM : public GM {
+public:
+    ImageLightingGM() : fInitialized(false) {
+        this->setBGColor(0xFF000000);
+    }
+    
+protected:
+    virtual SkString onShortName() {
+        return SkString("lighting");
+    }
+
+    void make_bitmap() {
+        fBitmap.setConfig(SkBitmap::kARGB_8888_Config, 100, 100);
+        fBitmap.allocPixels();
+        SkDevice device(fBitmap);
+        SkCanvas canvas(&device);
+        canvas.clear(0x00000000);
+        SkPaint paint;
+        paint.setAntiAlias(true);
+        paint.setColor(0xFFFFFFFF);
+        paint.setTextSize(SkIntToScalar(96));
+        const char* str = "e";
+        canvas.drawText(str, strlen(str), SkIntToScalar(20), SkIntToScalar(70), paint);
+    }
+
+    virtual SkISize onISize() {
+        return make_isize(WIDTH, HEIGHT);
+    }
+
+    virtual void onDraw(SkCanvas* canvas) {
+        if (!fInitialized) {
+            make_bitmap();
+            fInitialized = true;
+        }
+        SkPoint3 pointLocation(0, 0, SkIntToScalar(10));
+        SkScalar azimuthRad = SkDegreesToRadians(SkIntToScalar(225));
+        SkScalar elevationRad = SkDegreesToRadians(SkIntToScalar(5));
+        SkPoint3 distantDirection(SkScalarMul(SkScalarCos(azimuthRad), SkScalarCos(elevationRad)),
+                                  SkScalarMul(SkScalarSin(azimuthRad), SkScalarCos(elevationRad)),
+                                  SkScalarSin(elevationRad));
+        SkPoint3 spotLocation(SkIntToScalar(-10), SkIntToScalar(-10), SkIntToScalar(20));
+        SkPoint3 spotTarget(SkIntToScalar(40), SkIntToScalar(40), 0);
+        SkScalar spotExponent = SK_Scalar1;
+        SkScalar cutoffAngle = SkIntToScalar(15);
+        SkScalar kd = SkIntToScalar(2);
+        SkScalar ks = SkIntToScalar(1);
+        SkScalar shininess = SkIntToScalar(8);
+        SkScalar surfaceScale = SkIntToScalar(1);
+        SkColor white(0xFFFFFFFF);
+        SkPaint paint;
+        paint.setImageFilter(SkLightingImageFilter::CreatePointLitDiffuse(pointLocation, white, surfaceScale, kd))->unref();
+        canvas->drawSprite(fBitmap, 0, 0, &paint);
+        paint.setImageFilter(SkLightingImageFilter::CreateDistantLitDiffuse(distantDirection, white, surfaceScale, kd))->unref();
+        canvas->drawSprite(fBitmap, 110, 0, &paint);
+        paint.setImageFilter(SkLightingImageFilter::CreateSpotLitDiffuse(spotLocation, spotTarget, spotExponent, cutoffAngle, white, surfaceScale, kd))->unref();
+        canvas->drawSprite(fBitmap, 220, 0, &paint);
+        paint.setImageFilter(SkLightingImageFilter::CreatePointLitSpecular(pointLocation, white, surfaceScale, ks, shininess))->unref();
+        canvas->drawSprite(fBitmap, 0, 110, &paint);
+        paint.setImageFilter(SkLightingImageFilter::CreateDistantLitSpecular(distantDirection, white, surfaceScale, ks, shininess))->unref();
+        canvas->drawSprite(fBitmap, 110, 110, &paint);
+        paint.setImageFilter(SkLightingImageFilter::CreateSpotLitSpecular(spotLocation, spotTarget, spotExponent, cutoffAngle, white, surfaceScale, ks, shininess))->unref();
+        canvas->drawSprite(fBitmap, 220, 110, &paint);
+    }
+    
+private:
+    typedef GM INHERITED;
+    SkBitmap fBitmap;
+    bool fInitialized;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+static GM* MyFactory(void*) { return new ImageLightingGM; }
+static GMRegistry reg(MyFactory);
+
+}
diff --git a/gyp/effects.gyp b/gyp/effects.gyp
index 7d0cd9a..fbccb40 100644
--- a/gyp/effects.gyp
+++ b/gyp/effects.gyp
@@ -28,6 +28,7 @@
         '../include/effects/SkKernel33MaskFilter.h',
         '../include/effects/SkLayerDrawLooper.h',
         '../include/effects/SkLayerRasterizer.h',
+        '../include/effects/SkLightingImageFilter.h',
         '../include/effects/SkMorphologyImageFilter.h',
         '../include/effects/SkPaintFlagsDrawFilter.h',
         '../include/effects/SkPixelXorXfermode.h',
@@ -66,6 +67,7 @@
         '../src/effects/SkKernel33MaskFilter.cpp',
         '../src/effects/SkLayerDrawLooper.cpp',
         '../src/effects/SkLayerRasterizer.cpp',
+        '../src/effects/SkLightingImageFilter.cpp',
         '../src/effects/SkMorphologyImageFilter.cpp',
         '../src/effects/SkPaintFlagsDrawFilter.cpp',
         '../src/effects/SkPixelXorXfermode.cpp',
diff --git a/gyp/gmslides.gypi b/gyp/gmslides.gypi
index d88ab0d..874c79a 100644
--- a/gyp/gmslides.gypi
+++ b/gyp/gmslides.gypi
@@ -29,6 +29,7 @@
     '../gm/gradtext.cpp',
     '../gm/hairmodes.cpp',
     '../gm/imageblur.cpp',
+    '../gm/lighting.cpp',
     '../gm/imagefiltersbase.cpp',
     '../gm/lcdtext.cpp',
     '../gm/linepaths.cpp',
diff --git a/include/effects/SkLightingImageFilter.h b/include/effects/SkLightingImageFilter.h
new file mode 100644
index 0000000..f8a5ccf
--- /dev/null
+++ b/include/effects/SkLightingImageFilter.h
@@ -0,0 +1,87 @@
+/*
+ * 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.
+ */
+
+
+#ifndef SkLightingImageFilter_DEFINED
+#define SkLightingImageFilter_DEFINED
+
+#include "SkImageFilter.h"
+#include "SkColor.h"
+
+class SK_API SkPoint3 {
+public:
+    SkPoint3() {}
+    SkPoint3(SkScalar x, SkScalar y, SkScalar z)
+      : fX(x), fY(y), fZ(z) {}
+    SkScalar dot(const SkPoint3& other) const {
+        return SkScalarMul(fX, other.fX)
+             + SkScalarMul(fY, other.fY)
+             + SkScalarMul(fZ, other.fZ);
+    }
+    SkScalar maxComponent() const {
+        return fX > fY ? (fX > fZ ? fX : fZ) : (fY > fZ ? fY : fZ);
+    }
+    void normalize() {
+        SkScalar scale = SkScalarInvert(SkScalarSqrt(dot(*this)));
+        fX = SkScalarMul(fX, scale);
+        fY = SkScalarMul(fY, scale);
+        fZ = SkScalarMul(fZ, scale);
+    }
+    SkPoint3 operator*(SkScalar scalar) const {
+        return SkPoint3(SkScalarMul(fX, scalar),
+                        SkScalarMul(fY, scalar),
+                        SkScalarMul(fZ, scalar));
+    }
+    SkPoint3 operator-(const SkPoint3& other) const {
+        return SkPoint3(fX - other.fX, fY - other.fY, fZ - other.fZ);
+    }
+    SkScalar fX, fY, fZ;
+};
+
+class SkLight;
+
+class SK_API SkLightingImageFilter : public SkImageFilter {
+public:
+    static SkImageFilter* CreateDistantLitDiffuse(const SkPoint3& direction,
+        const SkColor& lightColor, SkScalar surfaceScale, SkScalar kd);
+    static SkImageFilter* CreatePointLitDiffuse(SkPoint3& location,
+        const SkColor& lightColor, SkScalar surfaceScale, SkScalar kd);
+    static SkImageFilter* CreateSpotLitDiffuse(const SkPoint3& location,
+        const SkPoint3& target, SkScalar specularExponent, SkScalar cutoffAngle,
+        const SkColor& lightColor, SkScalar surfaceScale, SkScalar kd);
+    static SkImageFilter* CreateDistantLitSpecular(const SkPoint3& direction,
+        const SkColor& lightColor, SkScalar surfaceScale, SkScalar ks,
+        SkScalar shininess);
+    static SkImageFilter* CreatePointLitSpecular(SkPoint3& location,
+        const SkColor& lightColor, SkScalar surfaceScale, SkScalar ks,
+        SkScalar shininess);
+    static SkImageFilter* CreateSpotLitSpecular(const SkPoint3& location,
+        const SkPoint3& target, SkScalar specularExponent, SkScalar cutoffAngle,
+        const SkColor& lightColor, SkScalar surfaceScale, SkScalar ks,
+        SkScalar shininess);
+    ~SkLightingImageFilter();
+
+    SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkLightingImageFilter)
+
+protected:
+    SkLightingImageFilter(SkLight* light, const SkColor& lightColor,
+                          SkScalar surfaceScale);
+    explicit SkLightingImageFilter(SkFlattenableReadBuffer& buffer);
+    virtual void flatten(SkFlattenableWriteBuffer&) const SK_OVERRIDE;
+    const SkLight* light() const { return fLight; }
+    const SkPoint3& lightColor() const { return fLightColor; }
+    SkScalar surfaceScale() const { return fSurfaceScale; }
+
+private:
+    typedef SkImageFilter INHERITED;
+    SkLight* fLight;
+    SkPoint3 fLightColor;
+    SkScalar fSurfaceScale;
+};
+
+#endif
+
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)