LayoutLib: Support PorterDuffColorFilter

Add support for select modes for PorterDuffColorFilter.

Change-Id: Ia7b4a6a92c326be977dd87a70f54c1f8954b546d
diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
index a417479..80f3b18 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
@@ -627,8 +627,7 @@
                 // set the color
                 graphics.setColor(new Color(color, true /*alpha*/));
 
-                Composite composite = PorterDuffXfermode_Delegate.getComposite(
-                        PorterDuffXfermode_Delegate.getPorterDuffMode(mode), 0xFF);
+                Composite composite = PorterDuffXfermode_Delegate.getComposite(mode, 0xFF);
                 if (composite != null) {
                     graphics.setComposite(composite);
                 }
diff --git a/tools/layoutlib/bridge/src/android/graphics/ColorFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/ColorFilter_Delegate.java
index bf03a5e..bd934d0 100644
--- a/tools/layoutlib/bridge/src/android/graphics/ColorFilter_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/ColorFilter_Delegate.java
@@ -19,6 +19,8 @@
 import com.android.layoutlib.bridge.impl.DelegateManager;
 import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
 
+import java.awt.Graphics2D;
+
 /**
  * Delegate implementing the native methods of android.graphics.ColorFilter
  *
@@ -50,9 +52,17 @@
         return sManager.getDelegate(nativeShader);
     }
 
-    public abstract boolean isSupported();
     public abstract String getSupportMessage();
 
+    public boolean isSupported() {
+        return false;
+    }
+
+    public void applyFilter(Graphics2D g, int width, int height) {
+        // This should never be called directly. If supported, the sub class should override this.
+        assert false;
+    }
+
     // ---- native methods ----
 
     @LayoutlibDelegate
diff --git a/tools/layoutlib/bridge/src/android/graphics/ColorMatrixColorFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/ColorMatrixColorFilter_Delegate.java
index 9aac2bd..6739484 100644
--- a/tools/layoutlib/bridge/src/android/graphics/ColorMatrixColorFilter_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/ColorMatrixColorFilter_Delegate.java
@@ -43,11 +43,6 @@
     // ---- Public Helper methods ----
 
     @Override
-    public boolean isSupported() {
-        return false;
-    }
-
-    @Override
     public String getSupportMessage() {
         return "ColorMatrix Color Filters are not supported.";
     }
diff --git a/tools/layoutlib/bridge/src/android/graphics/LightingColorFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/LightingColorFilter_Delegate.java
index 501d55c..0dd9703 100644
--- a/tools/layoutlib/bridge/src/android/graphics/LightingColorFilter_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/LightingColorFilter_Delegate.java
@@ -43,11 +43,6 @@
     // ---- Public Helper methods ----
 
     @Override
-    public boolean isSupported() {
-        return false;
-    }
-
-    @Override
     public String getSupportMessage() {
         return "Lighting Color Filters are not supported.";
     }
diff --git a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java
index c7c2e97..5adf4ca 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java
@@ -827,8 +827,8 @@
 
         delegate.mColorFilter = ColorFilter_Delegate.getDelegate(filter);
 
-        // since none of those are supported, display a fidelity warning right away
-        if (delegate.mColorFilter != null && delegate.mColorFilter.isSupported() == false) {
+        // Log warning if it's not supported.
+        if (delegate.mColorFilter != null && !delegate.mColorFilter.isSupported()) {
             Bridge.getLog().fidelityWarning(LayoutLog.TAG_COLORFILTER,
                     delegate.mColorFilter.getSupportMessage(), null, null /*data*/);
         }
@@ -873,7 +873,7 @@
         delegate.mMaskFilter = MaskFilter_Delegate.getDelegate(maskfilter);
 
         // since none of those are supported, display a fidelity warning right away
-        if (delegate.mMaskFilter != null && delegate.mMaskFilter.isSupported() == false) {
+        if (delegate.mMaskFilter != null && !delegate.mMaskFilter.isSupported()) {
             Bridge.getLog().fidelityWarning(LayoutLog.TAG_MASKFILTER,
                     delegate.mMaskFilter.getSupportMessage(), null, null /*data*/);
         }
@@ -906,7 +906,7 @@
         delegate.mRasterizer = Rasterizer_Delegate.getDelegate(rasterizer);
 
         // since none of those are supported, display a fidelity warning right away
-        if (delegate.mRasterizer != null && delegate.mRasterizer.isSupported() == false) {
+        if (delegate.mRasterizer != null && !delegate.mRasterizer.isSupported()) {
             Bridge.getLog().fidelityWarning(LayoutLog.TAG_RASTERIZER,
                     delegate.mRasterizer.getSupportMessage(), null, null /*data*/);
         }
diff --git a/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java
index 1bc3033..0543f4f 100644
--- a/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java
@@ -19,6 +19,15 @@
 import com.android.layoutlib.bridge.impl.DelegateManager;
 import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
 
+import android.graphics.PorterDuff.Mode;
+
+import java.awt.AlphaComposite;
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+
+import static com.android.layoutlib.bridge.impl.PorterDuffUtility.getAlphaCompositeRule;
+import static com.android.layoutlib.bridge.impl.PorterDuffUtility.getPorterDuffMode;
+
 /**
  * Delegate implementing the native methods of android.graphics.PorterDuffColorFilter
  *
@@ -40,25 +49,80 @@
 
     // ---- delegate data ----
 
+    private final int mSrcColor;
+    private final Mode mMode;
+    private int mWidth;
+    private int mHeight;
+    private BufferedImage mImage;
+
+
     // ---- Public Helper methods ----
 
     @Override
     public boolean isSupported() {
+        switch (mMode) {
+        case CLEAR:
+        case SRC:
+        case SRC_IN:
+        case DST_IN:
+        case SRC_ATOP:
+            return true;
+        }
+
         return false;
     }
 
     @Override
     public String getSupportMessage() {
-        return "PorterDuff Color Filters are not supported.";
+        return "PorterDuff Color Filter is not supported for mode: " + mMode.name() + ".";
+    }
+
+    @Override
+    public void applyFilter(Graphics2D g, int width, int height) {
+        createFilterImage(width, height);
+        g.setComposite(getComposite());
+        g.drawImage(mImage, 0, 0, null);
     }
 
     // ---- native methods ----
 
     @LayoutlibDelegate
     /*package*/ static long native_CreatePorterDuffFilter(int srcColor, int porterDuffMode) {
-        PorterDuffColorFilter_Delegate newDelegate = new PorterDuffColorFilter_Delegate();
+        PorterDuffColorFilter_Delegate newDelegate =
+                new PorterDuffColorFilter_Delegate(srcColor, porterDuffMode);
         return sManager.addNewDelegate(newDelegate);
     }
 
+
     // ---- Private delegate/helper methods ----
+
+    private PorterDuffColorFilter_Delegate(int srcColor, int mode) {
+        mSrcColor = srcColor;
+        mMode = getPorterDuffMode(mode);
+    }
+
+    private void createFilterImage(int width, int height) {
+        if (mWidth == width && mHeight == height && mImage != null) {
+            return;
+        }
+        mWidth = width;
+        mHeight = height;
+        mImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
+        Graphics2D graphics = mImage.createGraphics();
+        try {
+            graphics.setColor(new java.awt.Color(mSrcColor, true /* hasAlpha */));
+            graphics.fillRect(0, 0, width, height);
+        } finally {
+            graphics.dispose();
+        }
+    }
+
+    private AlphaComposite getComposite() {
+        return AlphaComposite.getInstance(getAlphaCompositeRule(mMode),
+                getAlpha() / 255f);
+    }
+
+    private int getAlpha() {
+        return mSrcColor >>> 24;
+    }
 }
diff --git a/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java
index a89fd57..f6c36b6 100644
--- a/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java
@@ -21,9 +21,14 @@
 import com.android.layoutlib.bridge.impl.DelegateManager;
 import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
 
+import android.graphics.PorterDuff.Mode;
+
 import java.awt.AlphaComposite;
 import java.awt.Composite;
 
+import static com.android.layoutlib.bridge.impl.PorterDuffUtility.getAlphaCompositeRule;
+import static com.android.layoutlib.bridge.impl.PorterDuffUtility.getPorterDuffMode;
+
 /**
  * Delegate implementing the native methods of android.graphics.PorterDuffXfermode
  *
@@ -43,17 +48,17 @@
 
     // ---- delegate data ----
 
-    private final int mMode;
+    private final Mode mMode;
 
     // ---- Public Helper methods ----
 
-    public PorterDuff.Mode getMode() {
-        return getPorterDuffMode(mMode);
+    public Mode getMode() {
+        return mMode;
     }
 
     @Override
     public Composite getComposite(int alpha) {
-        return getComposite(getPorterDuffMode(mMode), alpha);
+        return getComposite(mMode, alpha);
     }
 
     @Override
@@ -67,61 +72,8 @@
         return null;
     }
 
-    public static PorterDuff.Mode getPorterDuffMode(int mode) {
-        for (PorterDuff.Mode m : PorterDuff.Mode.values()) {
-            if (m.nativeInt == mode) {
-                return m;
-            }
-        }
-
-        Bridge.getLog().error(LayoutLog.TAG_BROKEN,
-                String.format("Unknown PorterDuff.Mode: %d", mode), null /*data*/);
-        assert false;
-        return PorterDuff.Mode.SRC_OVER;
-    }
-
-    public static Composite getComposite(PorterDuff.Mode mode, int alpha) {
-        float falpha = alpha != 0xFF ? (float)alpha / 255.f : 1.f;
-        switch (mode) {
-            case CLEAR:
-                return AlphaComposite.getInstance(AlphaComposite.CLEAR, falpha);
-            case DARKEN:
-                break;
-            case DST:
-                return AlphaComposite.getInstance(AlphaComposite.DST, falpha);
-            case DST_ATOP:
-                return AlphaComposite.getInstance(AlphaComposite.DST_ATOP, falpha);
-            case DST_IN:
-                return AlphaComposite.getInstance(AlphaComposite.DST_IN, falpha);
-            case DST_OUT:
-                return AlphaComposite.getInstance(AlphaComposite.DST_OUT, falpha);
-            case DST_OVER:
-                return AlphaComposite.getInstance(AlphaComposite.DST_OVER, falpha);
-            case LIGHTEN:
-                break;
-            case MULTIPLY:
-                break;
-            case SCREEN:
-                break;
-            case SRC:
-                return AlphaComposite.getInstance(AlphaComposite.SRC, falpha);
-            case SRC_ATOP:
-                return AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, falpha);
-            case SRC_IN:
-                return AlphaComposite.getInstance(AlphaComposite.SRC_IN, falpha);
-            case SRC_OUT:
-                return AlphaComposite.getInstance(AlphaComposite.SRC_OUT, falpha);
-            case SRC_OVER:
-                return AlphaComposite.getInstance(AlphaComposite.SRC_OVER, falpha);
-            case XOR:
-                return AlphaComposite.getInstance(AlphaComposite.XOR, falpha);
-        }
-
-        Bridge.getLog().fidelityWarning(LayoutLog.TAG_BROKEN,
-                String.format("Unsupported PorterDuff Mode: %s", mode.name()),
-                null, null /*data*/);
-
-        return AlphaComposite.getInstance(AlphaComposite.SRC_OVER, falpha);
+    public static Composite getComposite(int mode, int alpha) {
+        return getComposite(getPorterDuffMode(mode), alpha);
     }
 
     // ---- native methods ----
@@ -135,6 +87,20 @@
     // ---- Private delegate/helper methods ----
 
     private PorterDuffXfermode_Delegate(int mode) {
-        mMode = mode;
+        mMode = getPorterDuffMode(mode);
+    }
+
+    private static Composite getComposite(Mode mode, int alpha255) {
+        float alpha1 = alpha255 != 0xFF ? alpha255 / 255.f : 1.f;
+        int rule = getAlphaCompositeRule(mode);
+        if (rule >= 0) {
+            return AlphaComposite.getInstance(rule, alpha1);
+        }
+
+        Bridge.getLog().fidelityWarning(LayoutLog.TAG_BROKEN,
+                String.format("Unsupported PorterDuff Mode: %1$s", mode.name()),
+                null, null /*data*/);
+
+        return AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha1);
     }
 }
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java
index 21d6b1a..3a0321a 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java
@@ -21,6 +21,7 @@
 
 import android.graphics.Bitmap_Delegate;
 import android.graphics.Canvas;
+import android.graphics.ColorFilter_Delegate;
 import android.graphics.Paint;
 import android.graphics.Paint_Delegate;
 import android.graphics.Rect;
@@ -48,8 +49,8 @@
  * This is based on top of {@link Graphics2D} but can operate independently if none are available
  * yet when setting transforms and clip information.
  * <p>
- * This allows for drawing through {@link #draw(Drawable, Paint_Delegate)} and
- * {@link #draw(Drawable, Paint_Delegate)}
+ * This allows for drawing through {@link #draw(Drawable, Paint_Delegate, boolean, boolean)} and
+ * {@link #draw(Drawable)}
  *
  * Handling of layers (created with {@link Canvas#saveLayer(RectF, Paint, int)}) is handled through
  * a list of Graphics2D for each layers. The class actually maintains a list of {@link Layer}
@@ -203,7 +204,7 @@
      * called before the snapshot can be used to draw. Transform and clip operations are permitted
      * before.
      *
-     * @param image the image to associate to the snapshot or null.
+     * @param bitmap the image to associate to the snapshot or null.
      * @return the root snapshot
      */
     public static GcSnapshot createDefaultSnapshot(Bitmap_Delegate bitmap) {
@@ -557,7 +558,6 @@
      * Executes the Drawable's draw method, with a null paint delegate.
      * <p/>
      * Note that the method can be called several times if there are more than one active layer.
-     * @param drawable
      */
     public void draw(Drawable drawable) {
         draw(drawable, null, false /*compositeOnly*/, false /*forceSrcMode*/);
@@ -567,20 +567,19 @@
      * Executes the Drawable's draw method.
      * <p/>
      * Note that the method can be called several times if there are more than one active layer.
-     * @param drawable
-     * @param paint
      * @param compositeOnly whether the paint is used for composite only. This is typically
      *          the case for bitmaps.
      * @param forceSrcMode if true, this overrides the composite to be SRC
      */
     public void draw(Drawable drawable, Paint_Delegate paint, boolean compositeOnly,
             boolean forceSrcMode) {
+        int forceMode = forceSrcMode ? AlphaComposite.SRC : 0;
         // the current snapshot may not have a mLocalLayer (ie it was created on save() instead
         // of saveLayer(), but that doesn't mean there's no layer.
         // mLayers however saves all the information we need (flags).
         if (mLayers.size() == 1) {
             // no layer, only base layer. easy case.
-            drawInLayer(mLayers.get(0), drawable, paint, compositeOnly, forceSrcMode);
+            drawInLayer(mLayers.get(0), drawable, paint, compositeOnly, forceMode);
         } else {
             // draw in all the layers until the layer save flags tells us to stop (ie drawing
             // in that layer is limited to the layer itself.
@@ -590,7 +589,7 @@
             do {
                 Layer layer = mLayers.get(i);
 
-                drawInLayer(layer, drawable, paint, compositeOnly, forceSrcMode);
+                drawInLayer(layer, drawable, paint, compositeOnly, forceMode);
 
                 // then go to previous layer, only if there are any left, and its flags
                 // doesn't restrict drawing to the layer itself.
@@ -601,20 +600,61 @@
     }
 
     private void drawInLayer(Layer layer, Drawable drawable, Paint_Delegate paint,
-            boolean compositeOnly, boolean forceSrcMode) {
+            boolean compositeOnly, int forceMode) {
         Graphics2D originalGraphics = layer.getGraphics();
-        // get a Graphics2D object configured with the drawing parameters.
-        Graphics2D configuredGraphics2D =
-            paint != null ?
-                    createCustomGraphics(originalGraphics, paint, compositeOnly, forceSrcMode) :
-                        (Graphics2D) originalGraphics.create();
+        if (paint == null) {
+            drawOnGraphics((Graphics2D) originalGraphics.create(), drawable,
+                    null /*paint*/, layer);
+        } else {
+            ColorFilter_Delegate filter = paint.getColorFilter();
+            if (filter == null || !filter.isSupported()) {
+                // get a Graphics2D object configured with the drawing parameters.
+                Graphics2D configuredGraphics = createCustomGraphics(originalGraphics, paint,
+                        compositeOnly, forceMode);
+                drawOnGraphics(configuredGraphics, drawable, paint, layer);
+                return;
+            }
 
+            int width = layer.getImage().getWidth();
+            int height = layer.getImage().getHeight();
+
+            // Create a temporary image to which the color filter will be applied.
+            BufferedImage image = new BufferedImage(width, height,
+                    BufferedImage.TYPE_INT_ARGB);
+            Graphics2D imageBaseGraphics = (Graphics2D) image.getGraphics();
+            // Configure the Graphics2D object with drawing parameters and shader.
+            Graphics2D imageGraphics = createCustomGraphics(
+                    imageBaseGraphics, paint, compositeOnly,
+                    AlphaComposite.SRC_OVER);
+            // get a Graphics2D object configured with the drawing parameters, but no shader.
+            Graphics2D configuredGraphics = createCustomGraphics(originalGraphics, paint,
+                    true /*compositeOnly*/, forceMode);
+            try {
+                // The main draw operation.
+                drawable.draw(imageGraphics, paint);
+
+                // Apply the color filter.
+                filter.applyFilter(imageGraphics, width, height);
+
+                // Draw the tinted image on the main layer.
+                configuredGraphics.drawImage(image, 0, 0, null);
+                layer.change();
+            } finally {
+                // dispose Graphics2D objects
+                imageGraphics.dispose();
+                imageBaseGraphics.dispose();
+                configuredGraphics.dispose();
+            }
+        }
+    }
+
+    private void drawOnGraphics(Graphics2D g, Drawable drawable, Paint_Delegate paint,
+            Layer layer) {
         try {
-            drawable.draw(configuredGraphics2D, paint);
+            drawable.draw(g, paint);
             layer.change();
         } finally {
-            // dispose Graphics2D object
-            configuredGraphics2D.dispose();
+            g.dispose();
         }
     }
 
@@ -685,7 +725,7 @@
         // now draw put the content of the local layer onto the layer,
         // using the paint information
         Graphics2D g = createCustomGraphics(baseGfx, mLocalLayerPaint,
-                true /*alphaOnly*/, false /*forceSrcMode*/);
+                true /*alphaOnly*/, 0 /*forceMode*/);
 
         g.drawImage(mLocalLayer.getImage(),
                 mLayerBounds.left, mLayerBounds.top, mLayerBounds.right, mLayerBounds.bottom,
@@ -701,7 +741,7 @@
      * <p/>The object must be disposed ({@link Graphics2D#dispose()}) after being used.
      */
     private Graphics2D createCustomGraphics(Graphics2D original, Paint_Delegate paint,
-            boolean compositeOnly, boolean forceSrcMode) {
+            boolean compositeOnly, int forceMode) {
         // make new one graphics
         Graphics2D g = (Graphics2D) original.create();
 
@@ -714,70 +754,73 @@
                     RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
         }
 
+        // set the shader first, as it'll replace the color if it can be used it.
         boolean customShader = false;
-
-        // get the shader first, as it'll replace the color if it can be used it.
-        if (compositeOnly == false) {
-            Shader_Delegate shaderDelegate = paint.getShader();
-            if (shaderDelegate != null) {
-                if (shaderDelegate.isSupported()) {
-                    java.awt.Paint shaderPaint = shaderDelegate.getJavaPaint();
-                    assert shaderPaint != null;
-                    if (shaderPaint != null) {
-                        g.setPaint(shaderPaint);
-                        customShader = true;
-                    }
-                } else {
-                    Bridge.getLog().fidelityWarning(LayoutLog.TAG_SHADER,
-                            shaderDelegate.getSupportMessage(),
-                            null /*throwable*/, null /*data*/);
-                }
-            }
-
-            // if no shader, use the paint color
-            if (customShader == false) {
-                g.setColor(new Color(paint.getColor(), true /*hasAlpha*/));
-            }
-
+        if (!compositeOnly) {
+            customShader = setShader(g, paint);
             // set the stroke
             g.setStroke(paint.getJavaStroke());
         }
+        // set the composite.
+        setComposite(g, paint, compositeOnly || customShader, forceMode);
 
-        // the alpha for the composite. Always opaque if the normal paint color is used since
-        // it contains the alpha
-        int alpha = (compositeOnly || customShader) ? paint.getAlpha() : 0xFF;
+        return g;
+    }
 
-        if (forceSrcMode) {
-            g.setComposite(AlphaComposite.getInstance(
-                    AlphaComposite.SRC, (float) alpha / 255.f));
-        } else {
-            boolean customXfermode = false;
-            Xfermode_Delegate xfermodeDelegate = paint.getXfermode();
-            if (xfermodeDelegate != null) {
-                if (xfermodeDelegate.isSupported()) {
-                    Composite composite = xfermodeDelegate.getComposite(alpha);
-                    assert composite != null;
-                    if (composite != null) {
-                        g.setComposite(composite);
-                        customXfermode = true;
-                    }
-                } else {
-                    Bridge.getLog().fidelityWarning(LayoutLog.TAG_XFERMODE,
-                            xfermodeDelegate.getSupportMessage(),
-                            null /*throwable*/, null /*data*/);
+    private boolean setShader(Graphics2D g, Paint_Delegate paint) {
+        Shader_Delegate shaderDelegate = paint.getShader();
+        if (shaderDelegate != null) {
+            if (shaderDelegate.isSupported()) {
+                java.awt.Paint shaderPaint = shaderDelegate.getJavaPaint();
+                assert shaderPaint != null;
+                if (shaderPaint != null) {
+                    g.setPaint(shaderPaint);
+                    return true;
                 }
-            }
-
-            // if there was no custom xfermode, but we have alpha (due to a shader and a non
-            // opaque alpha channel in the paint color), then we create an AlphaComposite anyway
-            // that will handle the alpha.
-            if (customXfermode == false && alpha != 0xFF) {
-                g.setComposite(AlphaComposite.getInstance(
-                        AlphaComposite.SRC_OVER, (float) alpha / 255.f));
+            } else {
+                Bridge.getLog().fidelityWarning(LayoutLog.TAG_SHADER,
+                        shaderDelegate.getSupportMessage(),
+                        null /*throwable*/, null /*data*/);
             }
         }
 
-        return g;
+        // if no shader, use the paint color
+        g.setColor(new Color(paint.getColor(), true /*hasAlpha*/));
+
+        return false;
+    }
+
+    private void setComposite(Graphics2D g, Paint_Delegate paint, boolean usePaintAlpha,
+            int forceMode) {
+        // the alpha for the composite. Always opaque if the normal paint color is used since
+        // it contains the alpha
+        int alpha = usePaintAlpha ? paint.getAlpha() : 0xFF;
+        if (forceMode != 0) {
+            g.setComposite(AlphaComposite.getInstance(forceMode, (float) alpha / 255.f));
+            return;
+        }
+        Xfermode_Delegate xfermodeDelegate = paint.getXfermode();
+        if (xfermodeDelegate != null) {
+            if (xfermodeDelegate.isSupported()) {
+                Composite composite = xfermodeDelegate.getComposite(alpha);
+                assert composite != null;
+                if (composite != null) {
+                    g.setComposite(composite);
+                    return;
+                }
+            } else {
+                Bridge.getLog().fidelityWarning(LayoutLog.TAG_XFERMODE,
+                        xfermodeDelegate.getSupportMessage(),
+                        null /*throwable*/, null /*data*/);
+            }
+        }
+        // if there was no custom xfermode, but we have alpha (due to a shader and a non
+        // opaque alpha channel in the paint color), then we create an AlphaComposite anyway
+        // that will handle the alpha.
+        if (alpha != 0xFF) {
+            g.setComposite(AlphaComposite.getInstance(
+                    AlphaComposite.SRC_OVER, (float) alpha / 255.f));
+        }
     }
 
     private void mapRect(AffineTransform matrix, RectF dst, RectF src) {
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java
new file mode 100644
index 0000000..bc53e93
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2014 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.android.layoutlib.bridge.impl;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+
+import android.graphics.PorterDuff.Mode;
+import android.graphics.PorterDuffColorFilter_Delegate;
+import android.graphics.PorterDuffXfermode_Delegate;
+
+import java.awt.AlphaComposite;
+
+/**
+ * Provides various utility methods for {@link PorterDuffColorFilter_Delegate} and {@link
+ * PorterDuffXfermode_Delegate}.
+ */
+public final class PorterDuffUtility {
+
+    // Make the class non-instantiable.
+    private PorterDuffUtility() {
+    }
+
+    /**
+     * Convert the porterDuffMode from the framework to its corresponding enum. This defaults to
+     * {@link Mode#SRC_OVER} for invalid modes.
+     */
+    public static Mode getPorterDuffMode(int porterDuffMode) {
+        Mode[] values = Mode.values();
+        if (porterDuffMode >= 0 && porterDuffMode < values.length) {
+            return values[porterDuffMode];
+        }
+        Bridge.getLog().error(LayoutLog.TAG_BROKEN,
+                String.format("Unknown PorterDuff.Mode: %1$d", porterDuffMode), null /*data*/);
+        assert false;
+        return Mode.SRC_OVER;
+    }
+
+    /**
+     * A utility method to convert the porterDuffMode to an int to be used as a rule for {@link
+     * AlphaComposite}. If {@code AlphaComposite} doesn't support the mode, -1 is returned.
+     */
+    public static int getAlphaCompositeRule(Mode porterDuffMode) {
+        switch (porterDuffMode) {
+            case CLEAR:
+                return AlphaComposite.CLEAR;
+            case DARKEN:
+                break;
+            case DST:
+                return AlphaComposite.DST;
+            case DST_ATOP:
+                return AlphaComposite.DST_ATOP;
+            case DST_IN:
+                return AlphaComposite.DST_IN;
+            case DST_OUT:
+                return AlphaComposite.DST_OUT;
+            case DST_OVER:
+                return AlphaComposite.DST_OVER;
+            case LIGHTEN:
+                break;
+            case MULTIPLY:
+                break;
+            case SCREEN:
+                break;
+            case SRC:
+                return AlphaComposite.SRC;
+            case SRC_ATOP:
+                return AlphaComposite.SRC_ATOP;
+            case SRC_IN:
+                return AlphaComposite.SRC_IN;
+            case SRC_OUT:
+                return AlphaComposite.SRC_OUT;
+            case SRC_OVER:
+                return AlphaComposite.SRC_OVER;
+            case XOR:
+                return AlphaComposite.XOR;
+        }
+        // This is an unsupported mode.
+        return -1;
+
+    }
+}