Add support for ColorFilters.

Color filters are fully supported and can be used with shaders.

Change-Id: Id90ccf1c81cb462f2431f366f3f8f710d7971e04
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index 21e6793..63a0c4c 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -18,6 +18,7 @@
 
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
+import android.graphics.ColorFilter;
 import android.graphics.DrawFilter;
 import android.graphics.Matrix;
 import android.graphics.Paint;
@@ -376,9 +377,11 @@
     @Override
     public void drawPatch(Bitmap bitmap, byte[] chunks, RectF dst, Paint paint) {
         // Shaders are ignored when drawing patches
+        boolean hasColorFilter = paint != null && setupColorFilter(paint);
         final int nativePaint = paint == null ? 0 : paint.mNativePaint;
         nDrawPatch(mRenderer, bitmap.mNativeBitmap, chunks, dst.left, dst.top,
                 dst.right, dst.bottom, nativePaint);
+        if (hasColorFilter) nResetModifiers(mRenderer);
     }
 
     private native void nDrawPatch(int renderer, int bitmap, byte[] chunks, float left, float top,
@@ -387,8 +390,10 @@
     @Override
     public void drawBitmap(Bitmap bitmap, float left, float top, Paint paint) {
         // Shaders are ignored when drawing bitmaps
+        boolean hasColorFilter = paint != null && setupColorFilter(paint);
         final int nativePaint = paint == null ? 0 : paint.mNativePaint;
         nDrawBitmap(mRenderer, bitmap.mNativeBitmap, left, top, nativePaint);
+        if (hasColorFilter) nResetModifiers(mRenderer);
     }
 
     private native void nDrawBitmap(int renderer, int bitmap, float left, float top, int paint);
@@ -396,8 +401,10 @@
     @Override
     public void drawBitmap(Bitmap bitmap, Matrix matrix, Paint paint) {
         // Shaders are ignored when drawing bitmaps
+        boolean hasColorFilter = paint != null && setupColorFilter(paint);
         final int nativePaint = paint == null ? 0 : paint.mNativePaint;
         nDrawBitmap(mRenderer, bitmap.mNativeBitmap, matrix.native_instance, nativePaint);
+        if (hasColorFilter) nResetModifiers(mRenderer);
     }
 
     private native void nDrawBitmap(int renderer, int bitmap, int matrix, int paint);
@@ -405,6 +412,7 @@
     @Override
     public void drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) {
         // Shaders are ignored when drawing bitmaps
+        boolean hasColorFilter = paint != null && setupColorFilter(paint);
         final int nativePaint = paint == null ? 0 : paint.mNativePaint;
 
         int left, top, right, bottom;
@@ -421,14 +429,17 @@
 
         nDrawBitmap(mRenderer, bitmap.mNativeBitmap, left, top, right, bottom,
                 dst.left, dst.top, dst.right, dst.bottom, nativePaint);
+        if (hasColorFilter) nResetModifiers(mRenderer);
     }
 
     @Override
     public void drawBitmap(Bitmap bitmap, Rect src, RectF dst, Paint paint) {
         // Shaders are ignored when drawing bitmaps
+        boolean hasColorFilter = paint != null && setupColorFilter(paint);
         final int nativePaint = paint == null ? 0 : paint.mNativePaint;
         nDrawBitmap(mRenderer, bitmap.mNativeBitmap, src.left, src.top, src.right, src.bottom,
                 dst.left, dst.top, dst.right, dst.bottom, nativePaint);
+        if (hasColorFilter) nResetModifiers(mRenderer);
     }
 
     private native void nDrawBitmap(int renderer, int bitmap,
@@ -439,11 +450,13 @@
     public void drawBitmap(int[] colors, int offset, int stride, float x, float y,
             int width, int height, boolean hasAlpha, Paint paint) {
         // Shaders are ignored when drawing bitmaps
+        boolean hasColorFilter = paint != null && setupColorFilter(paint);
         final Bitmap.Config config = hasAlpha ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565;
         final Bitmap b = Bitmap.createBitmap(colors, offset, stride, width, height, config);
         final int nativePaint = paint == null ? 0 : paint.mNativePaint;
         nDrawBitmap(mRenderer, b.mNativeBitmap, x, y, nativePaint);
         b.recycle();
+        if (hasColorFilter) nResetModifiers(mRenderer);
     }
 
     @Override
@@ -556,9 +569,9 @@
 
     @Override
     public void drawRect(float left, float top, float right, float bottom, Paint paint) {
-        boolean hasShader = setupShader(paint);
+        boolean hasModifier = setupModifiers(paint);
         nDrawRect(mRenderer, left, top, right, bottom, paint.mNativePaint);
-        if (hasShader) nResetShader(mRenderer);
+        if (hasModifier) nResetModifiers(mRenderer);
     }
 
     private native void nDrawRect(int renderer, float left, float top, float right, float bottom,
@@ -589,9 +602,9 @@
         if ((index | count | (index + count) | (text.length - index - count)) < 0) {
             throw new IndexOutOfBoundsException();
         }
-        boolean hasShader = setupShader(paint);
+        boolean hasModifier = setupModifiers(paint);
         nDrawText(mRenderer, text, index, count, x, y, paint.mBidiFlags, paint.mNativePaint);
-        if (hasShader) nResetShader(mRenderer);
+        if (hasModifier) nResetModifiers(mRenderer);
     }
     
     private native void nDrawText(int renderer, char[] text, int index, int count, float x, float y,
@@ -599,7 +612,7 @@
 
     @Override
     public void drawText(CharSequence text, int start, int end, float x, float y, Paint paint) {
-        boolean hasShader = setupShader(paint);
+        boolean hasModifier = setupModifiers(paint);
         if (text instanceof String || text instanceof SpannedString ||
                 text instanceof SpannableString) {
             nDrawText(mRenderer, text.toString(), start, end, x, y, paint.mBidiFlags,
@@ -613,7 +626,7 @@
             nDrawText(mRenderer, buf, 0, end - start, x, y, paint.mBidiFlags, paint.mNativePaint);
             TemporaryBuffer.recycle(buf);
         }
-        if (hasShader) nResetShader(mRenderer);
+        if (hasModifier) nResetModifiers(mRenderer);
     }
 
     @Override
@@ -621,9 +634,9 @@
         if ((start | end | (end - start) | (text.length() - end)) < 0) {
             throw new IndexOutOfBoundsException();
         }
-        boolean hasShader = setupShader(paint);
+        boolean hasModifier = setupModifiers(paint);
         nDrawText(mRenderer, text, start, end, x, y, paint.mBidiFlags, paint.mNativePaint);
-        if (hasShader) nResetShader(mRenderer);
+        if (hasModifier) nResetModifiers(mRenderer);
     }
 
     private native void nDrawText(int renderer, String text, int start, int end, float x, float y,
@@ -631,9 +644,9 @@
 
     @Override
     public void drawText(String text, float x, float y, Paint paint) {
-        boolean hasShader = setupShader(paint);
+        boolean hasModifier = setupModifiers(paint);
         nDrawText(mRenderer, text, 0, text.length(), x, y, paint.mBidiFlags, paint.mNativePaint);
-        if (hasShader) nResetShader(mRenderer);
+        if (hasModifier) nResetModifiers(mRenderer);
     }
 
     @Override
@@ -666,15 +679,34 @@
         // TODO: Implement
     }
 
-    private boolean setupShader(Paint paint) {
+    private boolean setupModifiers(Paint paint) {
+        boolean hasModifier = false;
+
         final Shader shader = paint.getShader();
         if (shader != null) {
             nSetupShader(mRenderer, shader.native_shader);
+            hasModifier = true;
+        }
+
+        final ColorFilter filter = paint.getColorFilter();
+        if (filter != null) {
+            nSetupColorFilter(mRenderer, filter.nativeColorFilter);
+            hasModifier = true;
+        }
+
+        return hasModifier;
+    }
+    
+    private boolean setupColorFilter(Paint paint) {
+        final ColorFilter filter = paint.getColorFilter();
+        if (filter != null) {
+            nSetupColorFilter(mRenderer, filter.nativeColorFilter);
             return true;
         }
-        return false;
+        return false;        
     }
-
+    
     private native void nSetupShader(int renderer, int shader);
-    private native void nResetShader(int renderer);
+    private native void nSetupColorFilter(int renderer, int colorFilter);
+    private native void nResetModifiers(int renderer);
 }
diff --git a/core/jni/android/graphics/ColorFilter.cpp b/core/jni/android/graphics/ColorFilter.cpp
index ebfb209..848234f 100644
--- a/core/jni/android/graphics/ColorFilter.cpp
+++ b/core/jni/android/graphics/ColorFilter.cpp
@@ -23,28 +23,38 @@
 #include "SkColorMatrixFilter.h"
 #include "SkPorterDuff.h"
 
+#include <SkiaColorFilter.h>
+
 namespace android {
 
+using namespace uirenderer;
+
 class SkColorFilterGlue {
 public:
-
-    static void finalizer(JNIEnv* env, jobject clazz, SkColorFilter* obj) {
+    static void finalizer(JNIEnv* env, jobject clazz, SkColorFilter* obj, SkiaColorFilter* f) {
+        delete f;
         obj->safeUnref();
     }
 
-    static SkColorFilter* CreatePorterDuffFilter(JNIEnv* env, jobject,
-                            jint srcColor, SkPorterDuff::Mode mode) {
-        return SkColorFilter::CreateModeFilter(srcColor,
-                                           SkPorterDuff::ToXfermodeMode(mode));
+    static SkColorFilter* CreatePorterDuffFilter(JNIEnv* env, jobject, jint srcColor,
+            SkPorterDuff::Mode mode) {
+        return SkColorFilter::CreateModeFilter(srcColor, SkPorterDuff::ToXfermodeMode(mode));
     }
  
-    static SkColorFilter* CreateLightingFilter(JNIEnv* env, jobject,
-                                               jint mul, jint add) {
+    static SkiaColorFilter* glCreatePorterDuffFilter(JNIEnv* env, jobject, jint srcColor,
+            SkPorterDuff::Mode mode) {
+        return new SkiaBlendFilter(srcColor, SkPorterDuff::ToXfermodeMode(mode));
+    }
+
+    static SkColorFilter* CreateLightingFilter(JNIEnv* env, jobject, jint mul, jint add) {
         return SkColorFilter::CreateLightingFilter(mul, add);
     }
     
-    static SkColorFilter* CreateColorMatrixFilter(JNIEnv* env, jobject,
-                                                  jfloatArray jarray) {
+    static SkiaColorFilter* glCreateLightingFilter(JNIEnv* env, jobject, jint mul, jint add) {
+        return new SkiaLightingFilter(mul, add);
+    }
+
+    static SkColorFilter* CreateColorMatrixFilter(JNIEnv* env, jobject, jfloatArray jarray) {
         AutoJavaFloatArray autoArray(env, jarray, 20);
         const float* src = autoArray.ptr();
 
@@ -58,26 +68,44 @@
         return new SkColorMatrixFilter(src);
 #endif
     }
- 
+
+    static SkiaColorFilter* glCreateColorMatrixFilter(JNIEnv* env, jobject, jfloatArray jarray) {
+        AutoJavaFloatArray autoArray(env, jarray, 20);
+        const float* src = autoArray.ptr();
+
+        float* colorMatrix = new float[16];
+        memcpy(colorMatrix, src, 4 * sizeof(float));
+        memcpy(&colorMatrix[4], &src[5], 4 * sizeof(float));
+        memcpy(&colorMatrix[8], &src[10], 4 * sizeof(float));
+        memcpy(&colorMatrix[12], &src[15], 4 * sizeof(float));
+
+        float* colorVector = new float[4];
+        colorVector[0] = src[4];
+        colorVector[1] = src[9];
+        colorVector[2] = src[14];
+        colorVector[3] = src[19];
+
+        return new SkiaColorMatrixFilter(colorMatrix, colorVector);
+    }
 };
 
 static JNINativeMethod colorfilter_methods[] = {
-    {"finalizer", "(I)V", (void*) SkColorFilterGlue::finalizer}
+    {"finalizer", "(II)V", (void*) SkColorFilterGlue::finalizer}
 };
 
 static JNINativeMethod porterduff_methods[] = {
-    {"native_CreatePorterDuffFilter","(II)I",
-        (void*) SkColorFilterGlue::CreatePorterDuffFilter}
+    { "native_CreatePorterDuffFilter", "(II)I", (void*) SkColorFilterGlue::CreatePorterDuffFilter   },
+    { "nCreatePorterDuffFilter",       "(II)I", (void*) SkColorFilterGlue::glCreatePorterDuffFilter }
 };
 
 static JNINativeMethod lighting_methods[] = {
-    {"native_CreateLightingFilter","(II)I",
-        (void*) SkColorFilterGlue::CreateLightingFilter}
+    { "native_CreateLightingFilter", "(II)I", (void*) SkColorFilterGlue::CreateLightingFilter   },
+    { "nCreateLightingFilter",       "(II)I", (void*) SkColorFilterGlue::glCreateLightingFilter },
 };
 
 static JNINativeMethod colormatrix_methods[] = {
-    {"nativeColorMatrixFilter","([F)I",
-        (void*) SkColorFilterGlue::CreateColorMatrixFilter}
+    { "nativeColorMatrixFilter", "([F)I", (void*) SkColorFilterGlue::CreateColorMatrixFilter   },
+    { "nColorMatrixFilter",      "([F)I", (void*) SkColorFilterGlue::glCreateColorMatrixFilter }
 };
 
 #define REG(env, name, array) \
@@ -85,7 +113,6 @@
                                                     SK_ARRAY_COUNT(array));  \
     if (result < 0) return result
 
-
 int register_android_graphics_ColorFilter(JNIEnv* env) {
     int result;
     
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index ece9636..142e194 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -31,6 +31,7 @@
 
 #include <OpenGLRenderer.h>
 #include <SkiaShader.h>
+#include <SkiaColorFilter.h>
 #include <Rect.h>
 #include <ui/Rect.h>
 
@@ -228,12 +229,13 @@
 }
 
 // ----------------------------------------------------------------------------
-// Shaders
+// Shaders and color filters
 // ----------------------------------------------------------------------------
 
-static void android_view_GLES20Canvas_resetShader(JNIEnv* env, jobject canvas,
+static void android_view_GLES20Canvas_resetModifiers(JNIEnv* env, jobject canvas,
         OpenGLRenderer* renderer) {
     renderer->resetShader();
+    renderer->resetColorFilter();
 }
 
 static void android_view_GLES20Canvas_setupShader(JNIEnv* env, jobject canvas,
@@ -241,6 +243,11 @@
     renderer->setupShader(shader);
 }
 
+static void android_view_GLES20Canvas_setupColorFilter(JNIEnv* env, jobject canvas,
+        OpenGLRenderer* renderer, SkiaColorFilter* filter) {
+    renderer->setupColorFilter(filter);
+}
+
 // ----------------------------------------------------------------------------
 // Text
 // ----------------------------------------------------------------------------
@@ -311,8 +318,9 @@
     {   "nDrawColor",         "(III)V",          (void*) android_view_GLES20Canvas_drawColor },
     {   "nDrawRect",          "(IFFFFI)V",       (void*) android_view_GLES20Canvas_drawRect },
 
-    {   "nResetShader",       "(I)V",            (void*) android_view_GLES20Canvas_resetShader },
+    {   "nResetModifiers",    "(I)V",            (void*) android_view_GLES20Canvas_resetModifiers },
     {   "nSetupShader",       "(II)V",           (void*) android_view_GLES20Canvas_setupShader },
+    {   "nSetupColorFilter",  "(II)V",           (void*) android_view_GLES20Canvas_setupColorFilter },
 
     {   "nDrawText",          "(I[CIIFFII)V",    (void*) android_view_GLES20Canvas_drawTextArray },
     {   "nDrawText",          "(ILjava/lang/String;IIFFII)V",
diff --git a/graphics/java/android/graphics/ColorFilter.java b/graphics/java/android/graphics/ColorFilter.java
index 76f2c7f..e5cf830 100644
--- a/graphics/java/android/graphics/ColorFilter.java
+++ b/graphics/java/android/graphics/ColorFilter.java
@@ -23,12 +23,20 @@
 
 
 public class ColorFilter {
+    int native_instance;
+
+    /**
+     * @hide
+     */
+    public int nativeColorFilter;
 
     protected void finalize() throws Throwable {
-        finalizer(native_instance);
+        try {
+            super.finalize();
+        } finally {
+            finalizer(native_instance, nativeColorFilter);
+        }
     }
 
-    private static native void finalizer(int native_instance);
-
-    int native_instance;
+    private static native void finalizer(int native_instance, int nativeColorFilter);
 }
diff --git a/graphics/java/android/graphics/ColorMatrixColorFilter.java b/graphics/java/android/graphics/ColorMatrixColorFilter.java
index 5d73cff..245c615 100644
--- a/graphics/java/android/graphics/ColorMatrixColorFilter.java
+++ b/graphics/java/android/graphics/ColorMatrixColorFilter.java
@@ -25,7 +25,9 @@
      *               is constructed will not be reflected in the filter.
      */
     public ColorMatrixColorFilter(ColorMatrix matrix) {
-        native_instance = nativeColorMatrixFilter(matrix.getArray());
+        final float[] colorMatrix = matrix.getArray();
+        native_instance = nativeColorMatrixFilter(colorMatrix);
+        nativeColorFilter = nColorMatrixFilter(colorMatrix);
     }
 
     /**
@@ -40,7 +42,9 @@
             throw new ArrayIndexOutOfBoundsException();
         }
         native_instance = nativeColorMatrixFilter(array);
+        nativeColorFilter = nColorMatrixFilter(array);
     }
 
     private static native int nativeColorMatrixFilter(float[] array);
+    private static native int nColorMatrixFilter(float[] array);
 }
diff --git a/graphics/java/android/graphics/LightingColorFilter.java b/graphics/java/android/graphics/LightingColorFilter.java
index 5562389..715ce86 100644
--- a/graphics/java/android/graphics/LightingColorFilter.java
+++ b/graphics/java/android/graphics/LightingColorFilter.java
@@ -30,7 +30,9 @@
      */
     public LightingColorFilter(int mul, int add) {
         native_instance = native_CreateLightingFilter(mul, add);
+        nativeColorFilter = nCreateLightingFilter(mul, add);
     }
 
     private static native int native_CreateLightingFilter(int mul, int add);
+    private static native int nCreateLightingFilter(int mul, int add);
 }
diff --git a/graphics/java/android/graphics/PorterDuffColorFilter.java b/graphics/java/android/graphics/PorterDuffColorFilter.java
index 06724bd..b02dab1 100644
--- a/graphics/java/android/graphics/PorterDuffColorFilter.java
+++ b/graphics/java/android/graphics/PorterDuffColorFilter.java
@@ -25,10 +25,10 @@
      * @param mode           The porter-duff mode that is applied
      */
     public PorterDuffColorFilter(int srcColor, PorterDuff.Mode mode) {
-        native_instance = native_CreatePorterDuffFilter(srcColor,
-                                                        mode.nativeInt);
+        native_instance = native_CreatePorterDuffFilter(srcColor, mode.nativeInt);
+        nativeColorFilter = nCreatePorterDuffFilter(srcColor, mode.nativeInt);
     }
 
-    private static native int native_CreatePorterDuffFilter(int srcColor,
-                                                            int porterDuffMode);
+    private static native int native_CreatePorterDuffFilter(int srcColor, int porterDuffMode);
+    private static native int nCreatePorterDuffFilter(int srcColor, int porterDuffMode);
 }
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index fe1b524..8f28612 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -11,6 +11,7 @@
 	PatchCache.cpp \
 	Program.cpp \
 	ProgramCache.cpp \
+	SkiaColorFilter.cpp \
 	SkiaShader.cpp \
 	TextureCache.cpp
 
diff --git a/libs/hwui/Extensions.h b/libs/hwui/Extensions.h
index 99b34dd..7778290 100644
--- a/libs/hwui/Extensions.h
+++ b/libs/hwui/Extensions.h
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "OpenGLRenderer"
-
 #ifndef ANDROID_UI_EXTENSIONS_H
 #define ANDROID_UI_EXTENSIONS_H
 
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 187e9d8..d694039 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -132,6 +132,7 @@
 
     mCurrentProgram = NULL;
     mShader = NULL;
+    mColorFilter = NULL;
 
     memcpy(mMeshVertices, gMeshVertices, sizeof(gMeshVertices));
 
@@ -460,7 +461,6 @@
     const float u2 = srcRight / width;
     const float v2 = srcBottom / height;
 
-    // TODO: Do this in the shader
     resetDrawTextureTexCoords(u1, v1, u2, v2);
 
     drawTextureRect(dstLeft, dstTop, dstRight, dstBottom, texture, paint);
@@ -552,6 +552,8 @@
     mModelView.loadIdentity();
 
     GLuint textureUnit = 0;
+    // Needs to be set prior to calling FontRenderer::getTexture()
+    glActiveTexture(gTextureUnits[textureUnit]);
 
     ProgramDescription description;
     description.hasTexture = true;
@@ -559,10 +561,14 @@
     if (mShader) {
         mShader->describe(description, mExtensions);
     }
+    if (mColorFilter) {
+        mColorFilter->describe(description, mExtensions);
+    }
 
     useProgram(mProgramCache.get(description));
     mCurrentProgram->set(mOrthoMatrix, mModelView, mSnapshot->transform);
 
+    // Text is always blended, no need to check the shader
     chooseBlending(true, mode);
     bindTexture(mFontRenderer.getTexture(), GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, textureUnit);
     glUniform1i(mCurrentProgram->getUniform("sampler"), textureUnit);
@@ -578,6 +584,9 @@
     if (mShader) {
         mShader->setupProgram(mCurrentProgram, mModelView, *mSnapshot, &textureUnit);
     }
+    if (mColorFilter) {
+        mColorFilter->setupProgram(mCurrentProgram);
+    }
 
     // TODO: Implement scale properly
     const Rect& clip = mSnapshot->getLocalClip();
@@ -604,6 +613,18 @@
 }
 
 ///////////////////////////////////////////////////////////////////////////////
+// Color filters
+///////////////////////////////////////////////////////////////////////////////
+
+void OpenGLRenderer::resetColorFilter() {
+    mColorFilter = NULL;
+}
+
+void OpenGLRenderer::setupColorFilter(SkiaColorFilter* filter) {
+    mColorFilter = filter;
+}
+
+///////////////////////////////////////////////////////////////////////////////
 // Drawing implementation
 ///////////////////////////////////////////////////////////////////////////////
 
@@ -631,6 +652,9 @@
     if (mShader) {
         mShader->describe(description, mExtensions);
     }
+    if (mColorFilter) {
+        mColorFilter->describe(description, mExtensions);
+    }
 
     // Build and use the appropriate shader
     useProgram(mProgramCache.get(description));
@@ -654,6 +678,9 @@
     if (mShader) {
         mShader->setupProgram(mCurrentProgram, mModelView, *mSnapshot, &textureUnit);
     }
+    if (mColorFilter) {
+        mColorFilter->setupProgram(mCurrentProgram);
+    }
 
     // Draw the mesh
     glDrawArrays(GL_TRIANGLE_STRIP, 0, gMeshCount);
@@ -680,6 +707,9 @@
         GLvoid* vertices, GLvoid* texCoords, GLvoid* indices, GLsizei elementsCount) {
     ProgramDescription description;
     description.hasTexture = true;
+    if (mColorFilter) {
+        mColorFilter->describe(description, mExtensions);
+    }
 
     mModelView.loadTranslate(left, top, 0.0f);
     mModelView.scale(right - left, bottom - top, 1.0f);
@@ -703,6 +733,11 @@
             gMeshStride, vertices);
     glVertexAttribPointer(texCoordsSlot, 2, GL_FLOAT, GL_FALSE, gMeshStride, texCoords);
 
+    // Color filter
+    if (mColorFilter) {
+        mColorFilter->setupProgram(mCurrentProgram);
+    }
+
     if (!indices) {
         glDrawArrays(GL_TRIANGLE_STRIP, 0, gMeshCount);
     } else {
@@ -712,8 +747,6 @@
 }
 
 void OpenGLRenderer::chooseBlending(bool blend, SkXfermode::Mode mode, bool isPremultiplied) {
-    // In theory we should not blend if the mode is Src, but it's rare enough
-    // that it's not worth it
     blend = blend || mode != SkXfermode::kSrcOver_Mode;
     if (blend) {
         if (!mBlend) {
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index dc0f50f..d2a291f 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -43,6 +43,7 @@
 #include "FontRenderer.h"
 #include "ProgramCache.h"
 #include "SkiaShader.h"
+#include "SkiaColorFilter.h"
 
 namespace android {
 namespace uirenderer {
@@ -95,6 +96,9 @@
     void resetShader();
     void setupShader(SkiaShader* shader);
 
+    void resetColorFilter();
+    void setupColorFilter(SkiaColorFilter* filter);
+
     void drawText(const char* text, int bytesCount, int count, float x, float y, SkPaint* paint);
 
 private:
@@ -281,6 +285,9 @@
     Program* mCurrentProgram;
     SkiaShader* mShader;
 
+    // Color filters
+    SkiaColorFilter* mColorFilter;
+
     // Used to draw textured quads
     TextureVertex mMeshVertices[4];
 
diff --git a/libs/hwui/ProgramCache.cpp b/libs/hwui/ProgramCache.cpp
index 23923f6..3205258 100644
--- a/libs/hwui/ProgramCache.cpp
+++ b/libs/hwui/ProgramCache.cpp
@@ -27,8 +27,6 @@
 // Vertex shaders snippets
 ///////////////////////////////////////////////////////////////////////////////
 
-// TODO: Implement BitmapShader, implement repeat/mirror for npot
-
 const char* gVS_Header_Attributes =
         "attribute vec4 position;\n";
 const char* gVS_Header_Attributes_TexCoords =
@@ -85,10 +83,10 @@
         "uniform mat4 colorMatrix;\n"
         "uniform vec4 colorMatrixVector;\n",
         // Lighting
-        "uniform float lightingMul;\n"
-        "uniform float lightingAdd;\n",
+        "uniform vec4 lightingMul;\n"
+        "uniform vec4 lightingAdd;\n",
         // PorterDuff
-        "uniform vec4 colorBLend;\n"
+        "uniform vec4 colorBlend;\n"
 };
 const char* gFS_Main =
         "\nvoid main(void) {\n"
@@ -121,11 +119,14 @@
         // None
         "",
         // Matrix
+        // TODO: Fix premultiplied alpha computations for color matrix
         "    fragColor *= colorMatrix;\n"
-        "    fragColor += colorMatrixVector;\n",
+        "    fragColor += colorMatrixVector;\n"
+        "    fragColor.rgb *= fragColor.a;\n",
         // Lighting
-        "    fragColor *= lightingMul;\n"
-        "    fragColor += lightingAdd;\n",
+        "    float lightingAlpha = fragColor.a;\n"
+        "    fragColor = min(fragColor * lightingMul + (lightingAdd * lightingAlpha), lightingAlpha);\n"
+        "    fragColor.a = lightingAlpha;\n",
         // PorterDuff
         "    fragColor = blendColors(colorBlend, fragColor);\n"
 };
@@ -345,7 +346,11 @@
     // End the shader
     shader.append(gFS_Footer);
 
-    PROGRAM_LOGD("*** Generated fragment shader:\n\n%s", shader.string());
+    if (DEBUG_PROGRAM_CACHE) {
+        PROGRAM_LOGD("*** Generated fragment shader:\n\n");
+        printLongString(shader);
+    }
+
     return shader;
 }
 
@@ -391,5 +396,19 @@
     shader.append("}\n");
 }
 
+void ProgramCache::printLongString(const String8& shader) const {
+    ssize_t index = 0;
+    ssize_t lastIndex = 0;
+    const char* str = shader.string();
+    while ((index = shader.find("\n", index)) > -1) {
+        String8 line(str, index - lastIndex);
+        if (line.length() == 0) line.append("\n");
+        PROGRAM_LOGD("%s", line.string());
+        index++;
+        str += (index - lastIndex);
+        lastIndex = index;
+    }
+}
+
 }; // namespace uirenderer
 }; // namespace android
diff --git a/libs/hwui/ProgramCache.h b/libs/hwui/ProgramCache.h
index d60f6ce..a1a4a0e 100644
--- a/libs/hwui/ProgramCache.h
+++ b/libs/hwui/ProgramCache.h
@@ -35,7 +35,7 @@
 ///////////////////////////////////////////////////////////////////////////////
 
 // Debug
-#define DEBUG_PROGRAM_CACHE 1
+#define DEBUG_PROGRAM_CACHE 0
 
 // Debug
 #if DEBUG_PROGRAM_CACHE
@@ -180,6 +180,8 @@
     void generatePorterDuffBlend(String8& shader, const char* name, SkXfermode::Mode mode);
     void generateTextureWrap(String8& shader, GLenum wrapS, GLenum wrapT);
 
+    void printLongString(const String8& shader) const;
+
     KeyedVector<programid, Program*> mCache;
 
 }; // class ProgramCache
diff --git a/libs/hwui/SkiaColorFilter.cpp b/libs/hwui/SkiaColorFilter.cpp
new file mode 100644
index 0000000..fe57ae7
--- /dev/null
+++ b/libs/hwui/SkiaColorFilter.cpp
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "SkiaColorFilter.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Base color filter
+///////////////////////////////////////////////////////////////////////////////
+
+SkiaColorFilter::SkiaColorFilter(Type type, bool blend): mType(type), mBlend(blend) {
+}
+
+SkiaColorFilter::~SkiaColorFilter() {
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Color matrix filter
+///////////////////////////////////////////////////////////////////////////////
+
+SkiaColorMatrixFilter::SkiaColorMatrixFilter(float* matrix, float* vector):
+        SkiaColorFilter(kColorMatrix, true), mMatrix(matrix), mVector(vector) {
+}
+
+SkiaColorMatrixFilter::~SkiaColorMatrixFilter() {
+    delete[] mMatrix;
+    delete[] mVector;
+}
+
+void SkiaColorMatrixFilter::describe(ProgramDescription& description,
+        const Extensions& extensions) {
+    description.colorOp = ProgramDescription::kColorMatrix;
+}
+
+void SkiaColorMatrixFilter::setupProgram(Program* program) {
+    glUniformMatrix4fv(program->getUniform("colorMatrix"), 1, GL_FALSE, &mMatrix[0]);
+    glUniform4fv(program->getUniform("colorMatrixVector"), 1, mVector);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Lighting color filter
+///////////////////////////////////////////////////////////////////////////////
+
+SkiaLightingFilter::SkiaLightingFilter(int multiply, int add):
+        SkiaColorFilter(kLighting, true) {
+    mMulR = ((multiply >> 16) & 0xFF) / 255.0f;
+    mMulG = ((multiply >>  8) & 0xFF) / 255.0f;
+    mMulB = ((multiply      ) & 0xFF) / 255.0f;
+
+    mAddR = ((add >> 16) & 0xFF) / 255.0f;
+    mAddG = ((add >>  8) & 0xFF) / 255.0f;
+    mAddB = ((add      ) & 0xFF) / 255.0f;
+}
+
+void SkiaLightingFilter::describe(ProgramDescription& description, const Extensions& extensions) {
+    description.colorOp = ProgramDescription::kColorLighting;
+}
+
+void SkiaLightingFilter::setupProgram(Program* program) {
+    glUniform4f(program->getUniform("lightingMul"), mMulR, mMulG, mMulB, 1.0f);
+    glUniform4f(program->getUniform("lightingAdd"), mAddR, mAddG, mAddB, 0.0f);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Blend color filter
+///////////////////////////////////////////////////////////////////////////////
+
+SkiaBlendFilter::SkiaBlendFilter(int color, SkXfermode::Mode mode):
+        SkiaColorFilter(kBlend, true), mMode(mode) {
+    const int alpha = (color >> 24) & 0xFF;
+    mA = alpha / 255.0f;
+    mR = mA * ((color >> 16) & 0xFF) / 255.0f;
+    mG = mA * ((color >>  8) & 0xFF) / 255.0f;
+    mB = mA * ((color      ) & 0xFF) / 255.0f;
+}
+
+void SkiaBlendFilter::describe(ProgramDescription& description, const Extensions& extensions) {
+    description.colorOp = ProgramDescription::kColorBlend;
+    description.colorMode = mMode;
+}
+
+void SkiaBlendFilter::setupProgram(Program* program) {
+    glUniform4f(program->getUniform("colorBlend"), mR, mG, mB, mA);
+}
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/SkiaColorFilter.h b/libs/hwui/SkiaColorFilter.h
new file mode 100644
index 0000000..865b6f0
--- /dev/null
+++ b/libs/hwui/SkiaColorFilter.h
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_UI_SKIA_COLOR_FILTER_H
+#define ANDROID_UI_SKIA_COLOR_FILTER_H
+
+#include <GLES2/gl2.h>
+
+#include "ProgramCache.h"
+#include "Extensions.h"
+
+namespace android {
+namespace uirenderer {
+
+///////////////////////////////////////////////////////////////////////////////
+// Base color filter
+///////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Represents a Skia color filter. A color filter modifies a ProgramDescription
+ * and sets uniforms on the resulting shaders.
+ */
+struct SkiaColorFilter {
+    /**
+     * Type of Skia color filter in use.
+     */
+    enum Type {
+        kNone,
+        kColorMatrix,
+        kLighting,
+        kBlend,
+    };
+
+    SkiaColorFilter(Type type, bool blend);
+    virtual ~SkiaColorFilter();
+
+    virtual void describe(ProgramDescription& description, const Extensions& extensions) = 0;
+    virtual void setupProgram(Program* program) = 0;
+
+    inline bool blend() const {
+        return mBlend;
+    }
+
+    Type type() const {
+        return mType;
+    }
+
+protected:
+    Type mType;
+    bool mBlend;
+}; // struct SkiaColorFilter
+
+///////////////////////////////////////////////////////////////////////////////
+// Implementations
+///////////////////////////////////////////////////////////////////////////////
+
+/**
+ * A color filter that multiplies the source color with a matrix and adds a vector.
+ */
+struct SkiaColorMatrixFilter: public SkiaColorFilter {
+    SkiaColorMatrixFilter(float* matrix, float* vector);
+    ~SkiaColorMatrixFilter();
+
+    void describe(ProgramDescription& description, const Extensions& extensions);
+    void setupProgram(Program* program);
+
+private:
+    float* mMatrix;
+    float* mVector;
+}; // struct SkiaColorMatrixFilter
+
+/**
+ * A color filters that multiplies the source color with a fixed value and adds
+ * another fixed value. Ignores the alpha channel of both arguments.
+ */
+struct SkiaLightingFilter: public SkiaColorFilter {
+    SkiaLightingFilter(int multiply, int add);
+
+    void describe(ProgramDescription& description, const Extensions& extensions);
+    void setupProgram(Program* program);
+
+private:
+    GLfloat mMulR, mMulG, mMulB;
+    GLfloat mAddR, mAddG, mAddB;
+}; // struct SkiaLightingFilter
+
+/**
+ * A color filters that blends the source color with a specified destination color
+ * and PorterDuff blending mode.
+ */
+struct SkiaBlendFilter: public SkiaColorFilter {
+    SkiaBlendFilter(int color, SkXfermode::Mode mode);
+
+    void describe(ProgramDescription& description, const Extensions& extensions);
+    void setupProgram(Program* program);
+
+private:
+    SkXfermode::Mode mMode;
+    GLfloat mR, mG, mB, mA;
+}; // struct SkiaBlendFilter
+
+}; // namespace uirenderer
+}; // namespace android
+
+#endif // ANDROID_UI_SKIA_COLOR_FILTER_H
diff --git a/libs/hwui/SkiaShader.h b/libs/hwui/SkiaShader.h
index b5e6aeb..c19eac3 100644
--- a/libs/hwui/SkiaShader.h
+++ b/libs/hwui/SkiaShader.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef SKIA_SHADER_H
-#define SKIA_SHADER_H
+#ifndef ANDROID_UI_SKIA_SHADER_H
+#define ANDROID_UI_SKIA_SHADER_H
 
 #include <SkShader.h>
 #include <SkXfermode.h>
@@ -160,4 +160,4 @@
 }; // namespace uirenderer
 }; // namespace android
 
-#endif // SKIA_SHADER_H
+#endif // ANDROID_UI_SKIA_SHADER_H
diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml
index f917dd5..b615657 100644
--- a/tests/HwAccelerationTest/AndroidManifest.xml
+++ b/tests/HwAccelerationTest/AndroidManifest.xml
@@ -133,6 +133,15 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
+
+        <activity
+                android:name="ColorFiltersActivity"
+                android:label="_ColorFilters">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
                 
     </application>
 </manifest>
diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/ColorFiltersActivity.java b/tests/HwAccelerationTest/src/com/google/android/test/hwui/ColorFiltersActivity.java
new file mode 100644
index 0000000..49e1eaa
--- /dev/null
+++ b/tests/HwAccelerationTest/src/com/google/android/test/hwui/ColorFiltersActivity.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.android.test.hwui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.LightingColorFilter;
+import android.graphics.Paint;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.os.Bundle;
+import android.view.View;
+
+@SuppressWarnings({"UnusedDeclaration"})
+public class ColorFiltersActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        final BitmapsView view = new BitmapsView(this);
+        setContentView(view);
+    }
+
+    static class BitmapsView extends View {
+        private final Bitmap mBitmap1;
+        private final Bitmap mBitmap2;
+        private final Paint mColorMatrixPaint;
+        private final Paint mLightingPaint;
+        private final Paint mBlendPaint;
+
+        BitmapsView(Context c) {
+            super(c);
+
+            mBitmap1 = BitmapFactory.decodeResource(c.getResources(), R.drawable.sunset1);
+            mBitmap2 = BitmapFactory.decodeResource(c.getResources(), R.drawable.sunset2);
+
+            mColorMatrixPaint = new Paint();
+            final ColorMatrix colorMatrix = new ColorMatrix();
+            colorMatrix.setSaturation(0);
+            mColorMatrixPaint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
+
+            mLightingPaint = new Paint();
+            mLightingPaint.setColorFilter(new LightingColorFilter(0x0060ffff, 0x00101030));
+
+            mBlendPaint = new Paint();
+            mBlendPaint.setColorFilter(new PorterDuffColorFilter(0x7f990040,
+                    PorterDuff.Mode.SRC_OVER));
+        }
+
+        @Override
+        protected void onDraw(Canvas canvas) {
+            super.onDraw(canvas);
+
+            canvas.drawARGB(255, 255, 255, 255);
+            
+            canvas.save();
+            canvas.translate(120.0f, 50.0f);
+            canvas.drawBitmap(mBitmap1, 0.0f, 0.0f, mColorMatrixPaint);
+
+            canvas.translate(0.0f, 50.0f + mBitmap1.getHeight());
+            canvas.drawBitmap(mBitmap1, 0.0f, 0.0f, mLightingPaint);
+            
+            canvas.translate(0.0f, 50.0f + mBitmap1.getHeight());
+            canvas.drawBitmap(mBitmap1, 0.0f, 0.0f, mBlendPaint);
+            canvas.restore();
+
+            canvas.save();
+            canvas.translate(120.0f + mBitmap1.getWidth() + 120.0f, 50.0f);
+            canvas.drawBitmap(mBitmap2, 0.0f, 0.0f, mColorMatrixPaint);
+
+            canvas.translate(0.0f, 50.0f + mBitmap2.getHeight());
+            canvas.drawBitmap(mBitmap2, 0.0f, 0.0f, mLightingPaint);
+            
+            canvas.translate(0.0f, 50.0f + mBitmap2.getHeight());
+            canvas.drawBitmap(mBitmap2, 0.0f, 0.0f, mBlendPaint);
+            canvas.restore();            
+        }
+    }
+}
diff --git a/tests/HwAccelerationTest/src/com/google/android/test/hwui/MoreShadersActivity.java b/tests/HwAccelerationTest/src/com/google/android/test/hwui/MoreShadersActivity.java
index cbf34a0..f43eeba 100644
--- a/tests/HwAccelerationTest/src/com/google/android/test/hwui/MoreShadersActivity.java
+++ b/tests/HwAccelerationTest/src/com/google/android/test/hwui/MoreShadersActivity.java
@@ -23,7 +23,9 @@
 import android.graphics.BitmapShader;
 import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.ColorFilter;
 import android.graphics.ComposeShader;
+import android.graphics.LightingColorFilter;
 import android.graphics.LinearGradient;
 import android.graphics.Matrix;
 import android.graphics.Paint;
@@ -54,6 +56,7 @@
         private ComposeShader mCompose2Shader;
         private Paint mLargePaint;
         private BitmapShader mScaled2Shader;
+        private ColorFilter mColorFilter;
 
         ShadersView(Context c) {
             super(c);
@@ -87,6 +90,8 @@
             mCompose2Shader = new ComposeShader(mHorGradient, mScaledShader,
                     PorterDuff.Mode.SRC_OUT);
 
+            mColorFilter = new LightingColorFilter(0x0060ffff, 0x00101030);
+ 
             mLargePaint = new Paint();
             mLargePaint.setAntiAlias(true);
             mLargePaint.setTextSize(36.0f);
@@ -122,7 +127,9 @@
             canvas.drawText("OpenGL rendering", 0.0f, 60.0f, mLargePaint);
             
             mLargePaint.setShader(mCompose2Shader);
+            mLargePaint.setColorFilter(mColorFilter);
             canvas.drawText("OpenGL rendering", 0.0f, 100.0f, mLargePaint);
+            mLargePaint.setColorFilter(null);
             
             canvas.translate(0.0f, 40.0f + mDrawHeight);
             mLargePaint.setShader(mVertGradient);