LayoutLib: support for layers.

Layers require that drawing methods potentially
draw in more than one bitmaps.

To handle this this patch offers the following:
- move all drawing methods to use Drawable
- Drawables are now handled by GcSnapshot since
  its the one handling the layers
- moved Canvas_Delegate.createCustomGraphics to
  GcSnapshot which does not expose the Graphics2D
  objects anymore so its draw() methods are the only
  way to draw.
- handles creating layers in GcSnapshot.save() and
  blitting them in restore()

Other changes:
- Clean up the create/save API in GcSnapshot
- Fixed drawing bitmaps with alpha and other
  composite modes.

Change-Id: I1e230087493d044a10de71f4b6d29083e3f3bf64
diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
index 2720b61..df0b997 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
@@ -23,14 +23,12 @@
 import android.graphics.Paint_Delegate.FontInfo;
 import android.text.TextUtils;
 
-import java.awt.BasicStroke;
 import java.awt.Color;
 import java.awt.Composite;
 import java.awt.Graphics2D;
 import java.awt.Rectangle;
 import java.awt.RenderingHints;
 import java.awt.Shape;
-import java.awt.Stroke;
 import java.awt.geom.AffineTransform;
 import java.awt.image.BufferedImage;
 import java.util.List;
@@ -57,13 +55,9 @@
 
     // ---- delegate helper data ----
 
-    private interface Drawable {
-        void draw(Graphics2D graphics, Paint_Delegate paint);
-    }
-
     // ---- delegate data ----
     private BufferedImage mBufferedImage;
-    private GcSnapshot mSnapshot = new GcSnapshot();
+    private GcSnapshot mSnapshot;
 
     // ---- Public Helper methods ----
 
@@ -84,7 +78,7 @@
     /**
      * Returns the current {@link Graphics2D} used to draw.
      */
-    public GcSnapshot getGcSnapshot() {
+    public GcSnapshot getSnapshot() {
         return mSnapshot;
     }
 
@@ -125,7 +119,7 @@
             return;
         }
 
-        canvasDelegate.getGcSnapshot().translate(dx, dy);
+        canvasDelegate.getSnapshot().translate(dx, dy);
     }
 
     /*package*/ static void rotate(Canvas thisCanvas, float degrees) {
@@ -136,7 +130,7 @@
             return;
         }
 
-        canvasDelegate.getGcSnapshot().rotate(Math.toRadians(degrees));
+        canvasDelegate.getSnapshot().rotate(Math.toRadians(degrees));
     }
 
     /*package*/ static void scale(Canvas thisCanvas, float sx, float sy) {
@@ -147,7 +141,7 @@
             return;
         }
 
-        canvasDelegate.getGcSnapshot().scale(sx, sy);
+        canvasDelegate.getSnapshot().scale(sx, sy);
     }
 
     /*package*/ static void skew(Canvas thisCanvas, float kx, float ky) {
@@ -159,7 +153,7 @@
         }
 
         // get the current top graphics2D object.
-        GcSnapshot g = canvasDelegate.getGcSnapshot();
+        GcSnapshot g = canvasDelegate.getSnapshot();
 
         // get its current matrix
         AffineTransform currentTx = g.getTransform();
@@ -235,7 +229,7 @@
             return 0;
         }
 
-        return canvasDelegate.getGcSnapshot().size();
+        return canvasDelegate.getSnapshot().size();
     }
 
     /*package*/ static void restoreToCount(Canvas thisCanvas, int saveCount) {
@@ -260,33 +254,18 @@
         throw new UnsupportedOperationException();
     }
 
-    /*package*/ static void drawLines(Canvas thisCanvas, float[] pts, int offset, int count,
+    /*package*/ static void drawLines(Canvas thisCanvas,
+            final float[] pts, final int offset, final int count,
             Paint paint) {
-        // get the delegate from the native int.
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas);
-        if (canvasDelegate == null) {
-            assert false;
-            return;
-        }
-
-        Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint.mNativePaint);
-        if (paintDelegate == null) {
-            assert false;
-            return;
-        }
-
-        // get a Graphics2D object configured with the drawing parameters.
-        Graphics2D g = canvasDelegate.createCustomGraphics(paintDelegate);
-
-        try {
-            for (int i = 0 ; i < count ; i += 4) {
-                g.drawLine((int)pts[i + offset], (int)pts[i + offset + 1],
-                        (int)pts[i + offset + 2], (int)pts[i + offset + 3]);
-            }
-        } finally {
-            // dispose Graphics2D object
-            g.dispose();
-        }
+        draw(thisCanvas.mNativeCanvas, paint.mNativePaint, false /*compositeOnly*/,
+                new GcSnapshot.Drawable() {
+                    public void draw(Graphics2D graphics, Paint_Delegate paint) {
+                        for (int i = 0 ; i < count ; i += 4) {
+                            graphics.drawLine((int)pts[i + offset], (int)pts[i + offset + 1],
+                                    (int)pts[i + offset + 2], (int)pts[i + offset + 3]);
+                        }
+                    }
+                });
     }
 
     /*package*/ static void freeCaches() {
@@ -331,29 +310,66 @@
 
     /*package*/ static int native_saveLayer(int nativeCanvas, RectF bounds,
                                                int paint, int layerFlags) {
-        // FIXME
-        throw new UnsupportedOperationException();
+        // get the delegate from the native int.
+        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+        if (canvasDelegate == null) {
+            assert false;
+            return 0;
+        }
+
+        Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint);
+        if (paintDelegate == null) {
+            assert false;
+            return 0;
+        }
+
+        return canvasDelegate.saveLayer(bounds, paintDelegate, layerFlags);
     }
 
     /*package*/ static int native_saveLayer(int nativeCanvas, float l,
                                                float t, float r, float b,
                                                int paint, int layerFlags) {
-        // FIXME
-        throw new UnsupportedOperationException();
+        // get the delegate from the native int.
+        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+        if (canvasDelegate == null) {
+            assert false;
+            return 0;
+        }
+
+        Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint);
+        if (paintDelegate == null) {
+            assert false;
+            return 0;
+        }
+
+        return canvasDelegate.saveLayer(new RectF(l, t, r, b),
+                paintDelegate, layerFlags);
     }
 
     /*package*/ static int native_saveLayerAlpha(int nativeCanvas,
                                                     RectF bounds, int alpha,
                                                     int layerFlags) {
-        // FIXME
-        throw new UnsupportedOperationException();
+        // get the delegate from the native int.
+        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+        if (canvasDelegate == null) {
+            assert false;
+            return 0;
+        }
+
+        return canvasDelegate.saveLayerAlpha(bounds, alpha, layerFlags);
     }
 
     /*package*/ static int native_saveLayerAlpha(int nativeCanvas, float l,
                                                     float t, float r, float b,
                                                     int alpha, int layerFlags) {
-        // FIXME
-        throw new UnsupportedOperationException();
+        // get the delegate from the native int.
+        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+        if (canvasDelegate == null) {
+            assert false;
+            return 0;
+        }
+
+        return canvasDelegate.saveLayerAlpha(new RectF(l, t, r, b), alpha, layerFlags);
     }
 
 
@@ -372,7 +388,7 @@
         }
 
         // get the current top graphics2D object.
-        GcSnapshot snapshot = canvasDelegate.getGcSnapshot();
+        GcSnapshot snapshot = canvasDelegate.getSnapshot();
 
         // get its current matrix
         AffineTransform currentTx = snapshot.getTransform();
@@ -399,7 +415,7 @@
         }
 
         // get the current top graphics2D object.
-        GcSnapshot snapshot = canvasDelegate.getGcSnapshot();
+        GcSnapshot snapshot = canvasDelegate.getSnapshot();
 
         // get the AffineTransform of the given matrix
         AffineTransform matrixTx = matrixDelegate.getAffineTransform();
@@ -458,7 +474,7 @@
             return false;
         }
 
-        Rectangle rect = canvasDelegate.getGcSnapshot().getClip().getBounds();
+        Rectangle rect = canvasDelegate.getSnapshot().getClip().getBounds();
         if (rect != null) {
             bounds.left = rect.x;
             bounds.top = rect.y;
@@ -511,7 +527,7 @@
         native_drawColor(nativeCanvas, color, PorterDuff.Mode.SRC_OVER.nativeInt);
     }
 
-    /*package*/ static void native_drawColor(int nativeCanvas, int color, int mode) {
+    /*package*/ static void native_drawColor(int nativeCanvas, final int color, final int mode) {
         // get the delegate from the native int.
         Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
         if (canvasDelegate == null) {
@@ -519,27 +535,26 @@
             return;
         }
 
-        // get a new graphics context.
-        Graphics2D graphics = (Graphics2D)canvasDelegate.getGcSnapshot().create();
-        try {
-            // reset its transform just in case
-            graphics.setTransform(new AffineTransform());
+        final int w = canvasDelegate.mBufferedImage.getWidth();
+        final int h = canvasDelegate.mBufferedImage.getHeight();
+        draw(nativeCanvas, new GcSnapshot.Drawable() {
 
-            // set the color
-            graphics.setColor(new Color(color, true /*alpha*/));
+            public void draw(Graphics2D graphics, Paint_Delegate paint) {
+                // reset its transform just in case
+                graphics.setTransform(new AffineTransform());
 
-            Composite composite = PorterDuffXfermode_Delegate.getComposite(
-                    PorterDuffXfermode_Delegate.getPorterDuffMode(mode), 0xFF);
-            if (composite != null) {
-                graphics.setComposite(composite);
+                // set the color
+                graphics.setColor(new Color(color, true /*alpha*/));
+
+                Composite composite = PorterDuffXfermode_Delegate.getComposite(
+                        PorterDuffXfermode_Delegate.getPorterDuffMode(mode), 0xFF);
+                if (composite != null) {
+                    graphics.setComposite(composite);
+                }
+
+                graphics.fillRect(0, 0, w, h);
             }
-
-            graphics.fillRect(0, 0, canvasDelegate.mBufferedImage.getWidth(),
-                    canvasDelegate.mBufferedImage.getHeight());
-        } finally {
-            // dispose Graphics2D object
-            graphics.dispose();
-        }
+        });
     }
 
     /*package*/ static void native_drawPaint(int nativeCanvas, int paint) {
@@ -547,32 +562,15 @@
         throw new UnsupportedOperationException();
     }
 
-    /*package*/ static void native_drawLine(int nativeCanvas, float startX,
-                                               float startY, float stopX,
-                                               float stopY, int paint) {
-        // get the delegate from the native int.
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
-        if (canvasDelegate == null) {
-            assert false;
-            return;
-        }
+    /*package*/ static void native_drawLine(int nativeCanvas,
+            final float startX, final float startY, final float stopX, final float stopY,
+            int paint) {
 
-        // get the delegate from the native int.
-        Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint);
-        if (paintDelegate == null) {
-            assert false;
-            return;
-        }
-
-        // get a Graphics2D object configured with the drawing parameters.
-        Graphics2D g = canvasDelegate.createCustomGraphics(paintDelegate);
-
-        try {
-            g.drawLine((int)startX, (int)startY, (int)stopX, (int)stopY);
-        } finally {
-            // dispose Graphics2D object
-            g.dispose();
-        }
+        draw(nativeCanvas, paint, false /*compositeOnly*/, new GcSnapshot.Drawable() {
+            public void draw(Graphics2D graphics, Paint_Delegate paint) {
+                graphics.drawLine((int)startX, (int)startY, (int)stopX, (int)stopY);
+            }
+        });
     }
 
     /*package*/ static void native_drawRect(int nativeCanvas, RectF rect,
@@ -580,82 +578,47 @@
         native_drawRect(nativeCanvas, rect.left, rect.top, rect.right, rect.bottom, paint);
     }
 
-    /*package*/ static void native_drawRect(int nativeCanvas, float left,
-                                               float top, float right,
-                                               float bottom, int paint) {
-        // get the delegate from the native int.
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
-        if (canvasDelegate == null) {
-            assert false;
-            return;
-        }
+    /*package*/ static void native_drawRect(int nativeCanvas,
+            final float left, final float top, final float right, final float bottom, int paint) {
 
-        // get the delegate from the native int.
-        Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint);
-        if (paintDelegate == null) {
-            assert false;
-            return;
-        }
-
-        if (right > left && bottom > top) {
-            // get a Graphics2D object configured with the drawing parameters.
-            Graphics2D g = canvasDelegate.createCustomGraphics(paintDelegate);
-
-            try {
-                int style = paintDelegate.getStyle();
+        draw(nativeCanvas, paint, false /*compositeOnly*/, new GcSnapshot.Drawable() {
+            public void draw(Graphics2D graphics, Paint_Delegate paint) {
+                int style = paint.getStyle();
 
                 // draw
                 if (style == Paint.Style.FILL.nativeInt ||
                         style == Paint.Style.FILL_AND_STROKE.nativeInt) {
-                    g.fillRect((int)left, (int)top, (int)(right-left), (int)(bottom-top));
+                    graphics.fillRect((int)left, (int)top, (int)(right-left), (int)(bottom-top));
                 }
 
                 if (style == Paint.Style.STROKE.nativeInt ||
                         style == Paint.Style.FILL_AND_STROKE.nativeInt) {
-                    g.drawRect((int)left, (int)top, (int)(right-left), (int)(bottom-top));
+                    graphics.drawRect((int)left, (int)top, (int)(right-left), (int)(bottom-top));
                 }
-            } finally {
-                // dispose Graphics2D object
-                g.dispose();
             }
-        }
+        });
     }
 
-    /*package*/ static void native_drawOval(int nativeCanvas, RectF oval,
-                                               int paint) {
-        // get the delegate from the native int.
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
-        if (canvasDelegate == null) {
-            assert false;
-            return;
-        }
-
-        // get the delegate from the native int.
-        Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint);
-        if (paintDelegate == null) {
-            assert false;
-            return;
-        }
-
+    /*package*/ static void native_drawOval(int nativeCanvas, final RectF oval, int paint) {
         if (oval.right > oval.left && oval.bottom > oval.top) {
-            // get a Graphics2D object configured with the drawing parameters.
-            Graphics2D g = canvasDelegate.createCustomGraphics(paintDelegate);
+            draw(nativeCanvas, paint, false /*compositeOnly*/, new GcSnapshot.Drawable() {
+                public void draw(Graphics2D graphics, Paint_Delegate paint) {
+                    int style = paint.getStyle();
 
-            int style = paintDelegate.getStyle();
+                    // draw
+                    if (style == Paint.Style.FILL.nativeInt ||
+                            style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+                        graphics.fillOval((int)oval.left, (int)oval.top,
+                                (int)oval.width(), (int)oval.height());
+                    }
 
-            // draw
-            if (style == Paint.Style.FILL.nativeInt ||
-                    style == Paint.Style.FILL_AND_STROKE.nativeInt) {
-                g.fillOval((int)oval.left, (int)oval.top, (int)oval.width(), (int)oval.height());
-            }
-
-            if (style == Paint.Style.STROKE.nativeInt ||
-                    style == Paint.Style.FILL_AND_STROKE.nativeInt) {
-                g.drawOval((int)oval.left, (int)oval.top, (int)oval.width(), (int)oval.height());
-            }
-
-            // dispose Graphics2D object
-            g.dispose();
+                    if (style == Paint.Style.STROKE.nativeInt ||
+                            style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+                        graphics.drawOval((int)oval.left, (int)oval.top,
+                                (int)oval.width(), (int)oval.height());
+                    }
+                }
+            });
         }
     }
 
@@ -675,46 +638,29 @@
     }
 
     /*package*/ static void native_drawRoundRect(int nativeCanvas,
-                                                    RectF rect, float rx,
-                                                    float ry, int paint) {
-        // get the delegate from the native int.
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
-        if (canvasDelegate == null) {
-            assert false;
-            return;
-        }
+            final RectF rect, final float rx, final float ry, int paint) {
 
-        // get the delegate from the native int.
-        Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint);
-        if (paintDelegate == null) {
-            assert false;
-            return;
-        }
+        draw(nativeCanvas, paint, false /*compositeOnly*/, new GcSnapshot.Drawable() {
 
-        if (rect.right > rect.left && rect.bottom > rect.top) {
-            // get a Graphics2D object configured with the drawing parameters.
-            Graphics2D g = canvasDelegate.createCustomGraphics(paintDelegate);
+            public void draw(Graphics2D graphics, Paint_Delegate paint) {
+                int style = paint.getStyle();
 
-            int style = paintDelegate.getStyle();
+                // draw
+                if (style == Paint.Style.FILL.nativeInt ||
+                        style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+                    graphics.fillRoundRect(
+                            (int)rect.left, (int)rect.top, (int)rect.width(), (int)rect.height(),
+                            (int)rx, (int)ry);
+                }
 
-            // draw
-            if (style == Paint.Style.FILL.nativeInt ||
-                    style == Paint.Style.FILL_AND_STROKE.nativeInt) {
-                g.fillRoundRect(
-                        (int)rect.left, (int)rect.top, (int)rect.width(), (int)rect.height(),
-                        (int)rx, (int)ry);
+                if (style == Paint.Style.STROKE.nativeInt ||
+                        style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+                    graphics.drawRoundRect(
+                            (int)rect.left, (int)rect.top, (int)rect.width(), (int)rect.height(),
+                            (int)rx, (int)ry);
+                }
             }
-
-            if (style == Paint.Style.STROKE.nativeInt ||
-                    style == Paint.Style.FILL_AND_STROKE.nativeInt) {
-                g.drawRoundRect(
-                        (int)rect.left, (int)rect.top, (int)rect.width(), (int)rect.height(),
-                        (int)rx, (int)ry);
-            }
-
-            // dispose Graphics2D object
-            g.dispose();
-        }
+        });
     }
 
     /*package*/ static void native_drawPath(int nativeCanvas, int path,
@@ -725,7 +671,7 @@
             return;
         }
 
-        draw(nativeCanvas, paint, new Drawable() {
+        draw(nativeCanvas, paint, false /*compositeOnly*/, new GcSnapshot.Drawable() {
             public void draw(Graphics2D graphics, Paint_Delegate paint) {
                 Shape shape = pathDelegate.getJavaShape();
                 int style = paint.getStyle();
@@ -846,116 +792,103 @@
         throw new UnsupportedOperationException();
     }
 
-    /*package*/ static void native_drawText(int nativeCanvas, char[] text,
-                                               int index, int count, float x,
-                                               float y, int flags, int paint) {
-        // WARNING: the logic in this method is similar to Paint.measureText.
-        // Any change to this method should be reflected in Paint.measureText
+    /*package*/ static void native_drawText(int nativeCanvas,
+            final char[] text, final int index, final int count,
+            final float startX, final float startY, int flags, int paint) {
+        draw(nativeCanvas, paint, false /*compositeOnly*/, new GcSnapshot.Drawable() {
 
-        // get the delegate from the native int.
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
-        if (canvasDelegate == null) {
-            assert false;
-            return;
-        }
-
-        // get the delegate from the native int.
-        Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint);
-        if (paintDelegate == null) {
-            assert false;
-            return;
-        }
-
-        Graphics2D g = (Graphics2D) canvasDelegate.createCustomGraphics(paintDelegate);
-        try {
-            // Paint.TextAlign indicates how the text is positioned relative to X.
-            // LEFT is the default and there's nothing to do.
-            if (paintDelegate.getTextAlign() != Paint.Align.LEFT.nativeInt) {
-                float m = paintDelegate.measureText(text, index, count);
-                if (paintDelegate.getTextAlign() == Paint.Align.CENTER.nativeInt) {
-                    x -= m / 2;
-                } else if (paintDelegate.getTextAlign() == Paint.Align.RIGHT.nativeInt) {
-                    x -= m;
-                }
-            }
-
-            List<FontInfo> fonts = paintDelegate.getFonts();
-
-            if (fonts.size() > 0) {
-                FontInfo mainFont = fonts.get(0);
-                int i = index;
-                int lastIndex = index + count;
-                while (i < lastIndex) {
-                    // always start with the main font.
-                    int upTo = mainFont.mFont.canDisplayUpTo(text, i, lastIndex);
-                    if (upTo == -1) {
-                        // draw all the rest and exit.
-                        g.setFont(mainFont.mFont);
-                        g.drawChars(text, i, lastIndex - i, (int)x, (int)y);
-                        return;
-                    } else if (upTo > 0) {
-                        // draw what's possible
-                        g.setFont(mainFont.mFont);
-                        g.drawChars(text, i, upTo - i, (int)x, (int)y);
-
-                        // compute the width that was drawn to increase x
-                        x += mainFont.mMetrics.charsWidth(text, i, upTo - i);
-
-                        // move index to the first non displayed char.
-                        i = upTo;
-
-                        // don't call continue at this point. Since it is certain the main font
-                        // cannot display the font a index upTo (now ==i), we move on to the
-                        // fallback fonts directly.
+            public void draw(Graphics2D graphics, Paint_Delegate paint) {
+                // WARNING: the logic in this method is similar to Paint.measureText.
+                // Any change to this method should be reflected in Paint.measureText
+                // Paint.TextAlign indicates how the text is positioned relative to X.
+                // LEFT is the default and there's nothing to do.
+                float x = startX;
+                float y = startY;
+                if (paint.getTextAlign() != Paint.Align.LEFT.nativeInt) {
+                    float m = paint.measureText(text, index, count);
+                    if (paint.getTextAlign() == Paint.Align.CENTER.nativeInt) {
+                        x -= m / 2;
+                    } else if (paint.getTextAlign() == Paint.Align.RIGHT.nativeInt) {
+                        x -= m;
                     }
+                }
 
-                    // no char supported, attempt to read the next char(s) with the
-                    // fallback font. In this case we only test the first character
-                    // and then go back to test with the main font.
-                    // Special test for 2-char characters.
-                    boolean foundFont = false;
-                    for (int f = 1 ; f < fonts.size() ; f++) {
-                        FontInfo fontInfo = fonts.get(f);
+                List<FontInfo> fonts = paint.getFonts();
 
-                        // need to check that the font can display the character. We test
-                        // differently if the char is a high surrogate.
-                        int charCount = Character.isHighSurrogate(text[i]) ? 2 : 1;
-                        upTo = fontInfo.mFont.canDisplayUpTo(text, i, i + charCount);
+                if (fonts.size() > 0) {
+                    FontInfo mainFont = fonts.get(0);
+                    int i = index;
+                    int lastIndex = index + count;
+                    while (i < lastIndex) {
+                        // always start with the main font.
+                        int upTo = mainFont.mFont.canDisplayUpTo(text, i, lastIndex);
                         if (upTo == -1) {
-                            // draw that char
-                            g.setFont(fontInfo.mFont);
-                            g.drawChars(text, i, charCount, (int)x, (int)y);
+                            // draw all the rest and exit.
+                            graphics.setFont(mainFont.mFont);
+                            graphics.drawChars(text, i, lastIndex - i, (int)x, (int)y);
+                            return;
+                        } else if (upTo > 0) {
+                            // draw what's possible
+                            graphics.setFont(mainFont.mFont);
+                            graphics.drawChars(text, i, upTo - i, (int)x, (int)y);
 
-                            // update x
-                            x += fontInfo.mMetrics.charsWidth(text, i, charCount);
+                            // compute the width that was drawn to increase x
+                            x += mainFont.mMetrics.charsWidth(text, i, upTo - i);
 
-                            // update the index in the text, and move on
+                            // move index to the first non displayed char.
+                            i = upTo;
+
+                            // don't call continue at this point. Since it is certain the main font
+                            // cannot display the font a index upTo (now ==i), we move on to the
+                            // fallback fonts directly.
+                        }
+
+                        // no char supported, attempt to read the next char(s) with the
+                        // fallback font. In this case we only test the first character
+                        // and then go back to test with the main font.
+                        // Special test for 2-char characters.
+                        boolean foundFont = false;
+                        for (int f = 1 ; f < fonts.size() ; f++) {
+                            FontInfo fontInfo = fonts.get(f);
+
+                            // need to check that the font can display the character. We test
+                            // differently if the char is a high surrogate.
+                            int charCount = Character.isHighSurrogate(text[i]) ? 2 : 1;
+                            upTo = fontInfo.mFont.canDisplayUpTo(text, i, i + charCount);
+                            if (upTo == -1) {
+                                // draw that char
+                                graphics.setFont(fontInfo.mFont);
+                                graphics.drawChars(text, i, charCount, (int)x, (int)y);
+
+                                // update x
+                                x += fontInfo.mMetrics.charsWidth(text, i, charCount);
+
+                                // update the index in the text, and move on
+                                i += charCount;
+                                foundFont = true;
+                                break;
+
+                            }
+                        }
+
+                        // in case no font can display the char, display it with the main font.
+                        // (it'll put a square probably)
+                        if (foundFont == false) {
+                            int charCount = Character.isHighSurrogate(text[i]) ? 2 : 1;
+
+                            graphics.setFont(mainFont.mFont);
+                            graphics.drawChars(text, i, charCount, (int)x, (int)y);
+
+                            // measure it to advance x
+                            x += mainFont.mMetrics.charsWidth(text, i, charCount);
+
+                            // and move to the next chars.
                             i += charCount;
-                            foundFont = true;
-                            break;
-
                         }
                     }
-
-                    // in case no font can display the char, display it with the main font.
-                    // (it'll put a square probably)
-                    if (foundFont == false) {
-                        int charCount = Character.isHighSurrogate(text[i]) ? 2 : 1;
-
-                        g.setFont(mainFont.mFont);
-                        g.drawChars(text, i, charCount, (int)x, (int)y);
-
-                        // measure it to advance x
-                        x += mainFont.mMetrics.charsWidth(text, i, charCount);
-
-                        // and move to the next chars.
-                        i += charCount;
-                    }
                 }
             }
-        } finally {
-            g.dispose();
-        }
+        });
     }
 
     /*package*/ static void native_drawText(int nativeCanvas, String text,
@@ -1042,8 +975,11 @@
 
     /**
      * Executes a {@link Drawable} with a given canvas and paint.
+     * <p>Note that the drawable may actually be executed several times if there are
+     * layers involved (see {@link #saveLayer(RectF, int, int)}.
      */
-    private static void draw(int nCanvas, int nPaint, Drawable drawable) {
+    private static void draw(int nCanvas, int nPaint, boolean compositeOnly,
+            GcSnapshot.Drawable drawable) {
         // get the delegate from the native int.
         Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
         if (canvasDelegate == null) {
@@ -1051,28 +987,42 @@
             return;
         }
 
-        Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nPaint);
-        if (paintDelegate == null) {
+        // paint could be 0 meaning no paint
+        Paint_Delegate paintDelegate = null;
+        if (nPaint > 0) {
+            paintDelegate = Paint_Delegate.getDelegate(nPaint);
+            if (paintDelegate == null) {
+                assert false;
+                return;
+            }
+        }
+
+        canvasDelegate.getSnapshot().draw(drawable, paintDelegate, compositeOnly);
+    }
+
+    /**
+     * Executes a {@link Drawable} with a given canvas. No paint object will be provided
+     * to {@link GcSnapshot.Drawable#draw(Graphics2D, Paint_Delegate)}.
+     * <p>Note that the drawable may actually be executed several times if there are
+     * layers involved (see {@link #saveLayer(RectF, int, int)}.
+     */
+    private static void draw(int nCanvas, GcSnapshot.Drawable drawable) {
+        // get the delegate from the native int.
+        Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
+        if (canvasDelegate == null) {
             assert false;
             return;
         }
 
-        // get a Graphics2D object configured with the drawing parameters.
-        Graphics2D g = canvasDelegate.createCustomGraphics(paintDelegate);
-
-        try {
-            drawable.draw(g, paintDelegate);
-        } finally {
-            // dispose Graphics2D object
-            g.dispose();
-        }
+        canvasDelegate.mSnapshot.draw(drawable);
     }
 
     private Canvas_Delegate(BufferedImage image) {
-        setBitmap(image);
+        mSnapshot = GcSnapshot.createDefaultSnapshot(mBufferedImage = image);
     }
 
     private Canvas_Delegate() {
+        mSnapshot = GcSnapshot.createDefaultSnapshot(null /*image*/);
     }
 
     /**
@@ -1086,8 +1036,23 @@
         // get the current save count
         int count = mSnapshot.size();
 
-        // create a new snapshot and add it to the stack
-        mSnapshot = new GcSnapshot(mSnapshot, saveFlags);
+        mSnapshot = mSnapshot.save(saveFlags);
+
+        // return the old save count
+        return count;
+    }
+
+    private int saveLayerAlpha(RectF rect, int alpha, int saveFlags) {
+        Paint_Delegate paint = new Paint_Delegate();
+        paint.setAlpha(alpha);
+        return saveLayer(rect, paint, saveFlags);
+    }
+
+    private int saveLayer(RectF rect, Paint_Delegate paint, int saveFlags) {
+        // get the current save count
+        int count = mSnapshot.size();
+
+        mSnapshot = mSnapshot.saveLayer(rect, paint, saveFlags);
 
         // return the old save count
         return count;
@@ -1116,126 +1081,16 @@
     private void setBitmap(BufferedImage image) {
         mBufferedImage = image;
         assert mSnapshot.size() == 1;
-        mSnapshot.setGraphics2D(mBufferedImage.createGraphics());
-    }
-
-    /**
-     * Creates a new {@link Graphics2D} based on the {@link Paint} parameters.
-     * <p/>The object must be disposed ({@link Graphics2D#dispose()}) after being used.
-     */
-    /*package*/ Graphics2D createCustomGraphics(Paint_Delegate paint) {
-        // make new one
-        Graphics2D g = getGcSnapshot().create();
-
-        // configure it
-
-        if (paint.isAntiAliased()) {
-            g.setRenderingHint(
-                    RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
-            g.setRenderingHint(
-                    RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
-        }
-
-        // get the shader first, as it'll replace the color if it can be used it.
-        boolean customShader = false;
-        int nativeShader = paint.getShader();
-        if (nativeShader > 0) {
-            Shader_Delegate shaderDelegate = Shader_Delegate.getDelegate(nativeShader);
-            assert shaderDelegate != null;
-            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(null,
-                            shaderDelegate.getSupportMessage(),
-                            null);
-                }
-            }
-        }
-
-        // if no shader, use the paint color
-        if (customShader == false) {
-            g.setColor(new Color(paint.getColor(), true /*hasAlpha*/));
-        }
-
-        boolean customStroke = false;
-        int pathEffect = paint.getPathEffect();
-        if (pathEffect > 0) {
-            PathEffect_Delegate effectDelegate = PathEffect_Delegate.getDelegate(pathEffect);
-            assert effectDelegate != null;
-            if (effectDelegate != null) {
-                if (effectDelegate.isSupported()) {
-                    Stroke stroke = effectDelegate.getStroke(paint);
-                    assert stroke != null;
-                    if (stroke != null) {
-                        g.setStroke(stroke);
-                        customStroke = true;
-                    }
-                } else {
-                    Bridge.getLog().fidelityWarning(null,
-                            effectDelegate.getSupportMessage(),
-                            null);
-                }
-            }
-        }
-
-        // if no custom stroke as been set, set the default one.
-        if (customStroke == false) {
-            g.setStroke(new BasicStroke(
-                    paint.getStrokeWidth(),
-                    paint.getJavaCap(),
-                    paint.getJavaJoin(),
-                    paint.getJavaStrokeMiter()));
-        }
-
-        // the alpha for the composite. Always opaque if the normal paint color is used since
-        // it contains the alpha
-        int alpha = customShader ? paint.getAlpha() : 0xFF;
-
-        boolean customXfermode = false;
-        int xfermode = paint.getXfermode();
-        if (xfermode > 0) {
-            Xfermode_Delegate xfermodeDelegate = Xfermode_Delegate.getDelegate(paint.getXfermode());
-            assert xfermodeDelegate != null;
-            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(null,
-                            xfermodeDelegate.getSupportMessage(),
-                            null);
-                }
-            }
-        }
-
-        // 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(PorterDuffXfermode_Delegate.getComposite(
-                    PorterDuff.Mode.SRC_OVER, alpha));
-        }
-
-        return g;
+        mSnapshot.setImage(mBufferedImage);
     }
 
     private static void drawBitmap(
             int nativeCanvas,
-            BufferedImage image,
-            Bitmap.Config mBitmapConfig,
+            final BufferedImage image,
+            final Bitmap.Config mBitmapConfig,
             int nativePaintOrZero,
-            int sleft, int stop, int sright, int sbottom,
-            int dleft, int dtop, int dright, int dbottom) {
+            final int sleft, final int stop, final int sright, final int sbottom,
+            final int dleft, final int dtop, final int dright, final int dbottom) {
         // get the delegate from the native int.
         Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
         if (canvasDelegate == null) {
@@ -1253,47 +1108,35 @@
             }
         }
 
-        drawBitmap(canvasDelegate, image, mBitmapConfig, paintDelegate,
-                sleft, stop, sright, sbottom,
-                dleft, dtop, dright, dbottom);
-    }
+        // if the bitmap config is alpha_8, then we erase all color value from it
+        // before drawing it.
+        if (mBitmapConfig == Bitmap.Config.ALPHA_8) {
+            int w = image.getWidth();
+            int h = image.getHeight();
+            int[] argb = new int[w*h];
+            image.getRGB(0, 0, image.getWidth(), image.getHeight(),
+                    argb, 0, image.getWidth());
 
-    private static void drawBitmap(
-            Canvas_Delegate canvasDelegate,
-            BufferedImage image,
-            Bitmap.Config mBitmapConfig,
-            Paint_Delegate paintDelegate,
-            int sleft, int stop, int sright, int sbottom,
-            int dleft, int dtop, int dright, int dbottom) {
-        //FIXME add support for canvas, screen and bitmap densities.
-
-        Graphics2D g = canvasDelegate.getGcSnapshot().create();
-        try {
-            if (paintDelegate != null && paintDelegate.isFilterBitmap()) {
-                g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
-                        RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+            final int length = argb.length;
+            for (int i = 0 ; i < length; i++) {
+                argb[i] &= 0xFF000000;
             }
-
-            // if the bitmap config is alpha_8, then we erase all color value from it
-            // before drawing it.
-            if (mBitmapConfig == Bitmap.Config.ALPHA_8) {
-                int w = image.getWidth();
-                int h = image.getHeight();
-                int[] argb = new int[w*h];
-                image.getRGB(0, 0, image.getWidth(), image.getHeight(), argb, 0, image.getWidth());
-
-                final int length = argb.length;
-                for (int i = 0 ; i < length; i++) {
-                    argb[i] &= 0xFF000000;
-                }
-                image.setRGB(0, 0, w, h, argb, 0, w);
-            }
-
-            g.drawImage(image, dleft, dtop, dright, dbottom,
-                    sleft, stop, sright, sbottom, null);
-        } finally {
-            g.dispose();
+            image.setRGB(0, 0, w, h, argb, 0, w);
         }
+
+        draw(nativeCanvas, nativePaintOrZero, true /*compositeOnly*/,
+                new GcSnapshot.Drawable() {
+                    public void draw(Graphics2D graphics, Paint_Delegate paint) {
+                        if (paint != null && paint.isFilterBitmap()) {
+                            graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
+                                    RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+                        }
+
+                        //FIXME add support for canvas, screen and bitmap densities.
+                        graphics.drawImage(image, dleft, dtop, dright, dbottom,
+                                sleft, stop, sright, sbottom, null);
+                    }
+        });
     }
 }
 
diff --git a/tools/layoutlib/bridge/src/android/graphics/ComposePathEffect_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/ComposePathEffect_Delegate.java
index 954c658..a6844d4 100644
--- a/tools/layoutlib/bridge/src/android/graphics/ComposePathEffect_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/ComposePathEffect_Delegate.java
@@ -55,7 +55,7 @@
 
     @Override
     public String getSupportMessage() {
-        return "Composte Path Effects are not supported in Layout Preview mode.";
+        return "Compose Path Effects are not supported in Layout Preview mode.";
     }
 
     // ---- native methods ----
diff --git a/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java
index 2853222..b4baa6f 100644
--- a/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java
@@ -55,7 +55,7 @@
 
     @Override
     public String getSupportMessage() {
-        return "Compose Shader are not supported in Layout Preview mode.";
+        return "Compose Shaders are not supported in Layout Preview mode.";
     }
 
 
diff --git a/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java
index f6cee5e..463f4e9 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java
@@ -106,6 +106,45 @@
         return true;
     }
 
+    public static Matrix_Delegate make(AffineTransform matrix) {
+        float[] values = new float[MATRIX_SIZE];
+        values[0] = (float) matrix.getScaleX();
+        values[1] = (float) matrix.getShearX();
+        values[2] = (float) matrix.getTranslateX();
+        values[3] = (float) matrix.getShearY();
+        values[4] = (float) matrix.getScaleY();
+        values[5] = (float) matrix.getTranslateY();
+        values[6] = 0.f;
+        values[7] = 0.f;
+        values[8] = 1.f;
+
+        return new Matrix_Delegate(values);
+    }
+
+    public boolean mapRect(RectF dst, RectF src) {
+        // array with 4 corners
+        float[] corners = new float[] {
+                src.left, src.top,
+                src.right, src.top,
+                src.right, src.bottom,
+                src.left, src.bottom,
+        };
+
+        // apply the transform to them.
+        mapPoints(corners);
+
+        // now put the result in the rect. We take the min/max of Xs and min/max of Ys
+        dst.left = Math.min(Math.min(corners[0], corners[2]), Math.min(corners[4], corners[6]));
+        dst.right = Math.max(Math.max(corners[0], corners[2]), Math.max(corners[4], corners[6]));
+
+        dst.top = Math.min(Math.min(corners[1], corners[3]), Math.min(corners[5], corners[7]));
+        dst.bottom = Math.max(Math.max(corners[1], corners[3]), Math.max(corners[5], corners[7]));
+
+
+        return (computeTypeMask() & kRectStaysRect_Mask) != 0;
+    }
+
+
     /**
      * Returns an {@link AffineTransform} matching the matrix.
      */
@@ -655,26 +694,7 @@
             return false;
         }
 
-        // array with 4 corners
-        float[] corners = new float[] {
-                src.left, src.top,
-                src.right, src.top,
-                src.right, src.bottom,
-                src.left, src.bottom,
-        };
-
-        // apply the transform to them.
-        d.mapPoints(corners);
-
-        // now put the result in the rect. We take the min/max of Xs and min/max of Ys
-        dst.left = Math.min(Math.min(corners[0], corners[2]), Math.min(corners[4], corners[6]));
-        dst.right = Math.max(Math.max(corners[0], corners[2]), Math.max(corners[4], corners[6]));
-
-        dst.top = Math.min(Math.min(corners[1], corners[3]), Math.min(corners[5], corners[7]));
-        dst.bottom = Math.max(Math.max(corners[1], corners[3]), Math.max(corners[5], corners[7]));
-
-
-        return (d.computeTypeMask() & kRectStaysRect_Mask) != 0;
+        return d.mapRect(dst, src);
     }
 
     /*package*/ static float native_mapRadius(int native_object, float radius) {
@@ -740,7 +760,6 @@
                 matrix[4], matrix[2], matrix[5]);
     }
 
-
     /**
      * Reset a matrix to the identity
      */
@@ -830,6 +849,10 @@
         reset();
     }
 
+    private Matrix_Delegate(float[] values) {
+        System.arraycopy(values, 0, mValues, 0, MATRIX_SIZE);
+    }
+
     /**
      * Adds the given transformation to the current Matrix
      * <p/>This in effect does this = this*matrix
diff --git a/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java
index b8f76a6..e64a69a 100644
--- a/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java
@@ -18,6 +18,7 @@
 
 import com.android.layoutlib.bridge.Bridge;
 import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.layoutlib.bridge.impl.GcSnapshot;
 import com.android.ninepatch.NinePatchChunk;
 
 import android.graphics.drawable.NinePatchDrawable;
@@ -123,11 +124,11 @@
     }
 
    private static void draw(int canvas_instance,
-           int left, int top, int right, int bottom,
+           final int left, final int top, final int right, final int bottom,
            int bitmap_instance, byte[] c, int paint_instance_or_null,
-           int destDensity, int srcDensity) {
+           final int destDensity, final int srcDensity) {
        // get the delegate from the native int.
-       Bitmap_Delegate bitmap_delegate = Bitmap_Delegate.getDelegate(bitmap_instance);
+       final Bitmap_Delegate bitmap_delegate = Bitmap_Delegate.getDelegate(bitmap_instance);
        if (bitmap_delegate == null) {
            assert false;
            return;
@@ -143,7 +144,7 @@
            return;
        }
 
-       NinePatchChunk chunkObject = getChunk(c);
+       final NinePatchChunk chunkObject = getChunk(c);
        assert chunkObject != null;
        if (chunkObject == null) {
            return;
@@ -158,19 +159,13 @@
        // this one can be null
        Paint_Delegate paint_delegate = Paint_Delegate.getDelegate(paint_instance_or_null);
 
-       Graphics2D graphics;
-       if (paint_delegate != null) {
-           graphics = canvas_delegate.createCustomGraphics(paint_delegate);
-       } else {
-           graphics = canvas_delegate.getGcSnapshot().create();
-       }
+       canvas_delegate.getSnapshot().draw(new GcSnapshot.Drawable() {
+               public void draw(Graphics2D graphics, Paint_Delegate paint) {
+                   chunkObject.draw(bitmap_delegate.getImage(), graphics,
+                           left, top, right - left, bottom - top, destDensity, srcDensity);
+               }
+           }, paint_delegate, true /*compositeOnly*/);
 
-       try {
-           chunkObject.draw(bitmap_delegate.getImage(), graphics,
-                   left, top, right - left, bottom - top, destDensity, srcDensity);
-       } finally {
-           graphics.dispose();
-       }
     }
 
     /*package*/ static int nativeGetTransparentRegion(int bitmap, byte[] chunk, Rect location) {
diff --git a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java
index 0a597ca..7cb30dd 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java
@@ -118,6 +118,10 @@
         return mColor >>> 24;
     }
 
+    public void setAlpha(int alpha) {
+        mColor = (alpha << 24) | (mColor & 0x00FFFFFF);
+    }
+
     public int getTextAlign() {
         return mTextAlign;
     }
@@ -130,7 +134,11 @@
      * returns the value of stroke miter needed by the java api.
      */
     public float getJavaStrokeMiter() {
-        return mStrokeMiter * mStrokeWidth;
+        float miter = mStrokeMiter * mStrokeWidth;
+        if (miter < 1.f) {
+            miter = 1.f;
+        }
+        return miter;
     }
 
     public int getJavaCap() {
@@ -274,7 +282,7 @@
             return;
         }
 
-        delegate.mColor = (a << 24) | (delegate.mColor & 0x00FFFFFF);
+        delegate.setAlpha(a);
     }
 
     /*package*/ static float getStrokeWidth(Paint thisPaint) {
@@ -835,7 +843,7 @@
 
     // ---- Private delegate/helper methods ----
 
-    private Paint_Delegate() {
+    /*package*/ Paint_Delegate() {
         reset();
     }
 
@@ -866,14 +874,14 @@
 
     private void reset() {
         mFlags = Paint.DEFAULT_PAINT_FLAGS;
-        mColor = 0;
+        mColor = 0xFF000000;
         mStyle = Paint.Style.FILL.nativeInt;
         mCap = Paint.Cap.BUTT.nativeInt;
         mJoin = Paint.Join.MITER.nativeInt;
         mTextAlign = 0;
         mTypeface = Typeface.sDefaults[0].native_instance;
         mStrokeWidth = 1.f;
-        mStrokeMiter = 2.f;
+        mStrokeMiter = 4.f;
         mTextSize = 20.f;
         mTextScaleX = 1.f;
         mTextSkewX = 0.f;
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
index a09524c..6ebc56c 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
@@ -107,7 +107,8 @@
             Bridge.prepareThread();
             mLastResult = mSession.acquire(Params.DEFAULT_TIMEOUT);
             if (mLastResult.isSuccess()) {
-                mLastResult = mSession.insertChild((ViewGroup) parentView, childXml, index, listener);
+                mLastResult = mSession.insertChild((ViewGroup) parentView, childXml, index,
+                        listener);
             }
         } finally {
             mSession.release();
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 8c6b1be..103e262 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
@@ -16,20 +16,42 @@
 
 package com.android.layoutlib.bridge.impl;
 
-import android.graphics.Canvas;
-import android.graphics.Region;
+import com.android.layoutlib.bridge.Bridge;
 
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Paint_Delegate;
+import android.graphics.PathEffect_Delegate;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode_Delegate;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region;
+import android.graphics.Shader_Delegate;
+import android.graphics.Xfermode_Delegate;
+
+import java.awt.AlphaComposite;
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Composite;
 import java.awt.Graphics2D;
+import java.awt.RenderingHints;
 import java.awt.Shape;
+import java.awt.Stroke;
 import java.awt.geom.AffineTransform;
 import java.awt.geom.Area;
 import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import java.util.ArrayList;
 
 /**
  * Class representing a graphics context snapshot, as well as a context stack as a linked list.
  * <p>
  * 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)}
  *
  */
 public class GcSnapshot {
@@ -37,38 +59,209 @@
     private final GcSnapshot mPrevious;
     private final int mFlags;
 
-    private Graphics2D mGraphics2D = null;
+    /** list of layers. */
+    private final ArrayList<Layer> mLayers = new ArrayList<Layer>();
+
     /** temp transform in case transformation are set before a Graphics2D exists */
     private AffineTransform mTransform = null;
     /** temp clip in case clipping is set before a Graphics2D exists */
     private Area mClip = null;
 
+    // local layer data
+    private final Layer mLocalLayer;
+    private final Paint_Delegate mLocalLayerPaint;
+    private Rect mLocalLayerRegion;
+
+    public interface Drawable {
+        void draw(Graphics2D graphics, Paint_Delegate paint);
+    }
+
     /**
-     * Creates a new {@link GcSnapshot} on top of another one.
-     * <p/>
-     * This is basically the equivalent of {@link Canvas#save(int)}
-     * @param previous the previous snapshot head.
-     * @param flags the flags regarding what should be saved.
+     * class containing information about a layer.
      */
-    public GcSnapshot(GcSnapshot previous, int flags) {
-        assert previous != null;
-        mPrevious = previous;
-        mFlags = flags;
-        mGraphics2D = (Graphics2D) previous.mGraphics2D.create();
+    private static class Layer {
+        private final Graphics2D mGraphics;
+        private final BufferedImage mImage;
+        private BufferedImage mOriginalCopy;
+
+        Layer(Graphics2D graphics, BufferedImage image) {
+            mGraphics = graphics;
+            mImage = image;
+        }
+
+        /** The Graphics2D, guaranteed to be non null */
+        Graphics2D getGraphics() {
+            return mGraphics;
+        }
+
+        /** The BufferedImage, guaranteed to be non null */
+        BufferedImage getImage() {
+            return mImage;
+        }
+
+        Layer makeCopy() {
+            return new Layer((Graphics2D) mGraphics.create(), mImage);
+        }
+
+        /** sets an optional copy of the original content to be used during restore */
+        void setOriginalCopy(BufferedImage image) {
+            mOriginalCopy = image;
+        }
+
+        BufferedImage getOriginalCopy() {
+            return mOriginalCopy;
+        }
+    }
+
+    /**
+     * Creates the root snapshot associating it with a given image.
+     * <p>
+     * If <var>image</var> is null, then {@link GcSnapshot#setImage(BufferedImage)} must be
+     * 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.
+     * @return the root snapshot
+     */
+    public static GcSnapshot createDefaultSnapshot(BufferedImage image) {
+        GcSnapshot snapshot = new GcSnapshot();
+        if (image != null) {
+            snapshot.setImage(image);
+        }
+
+        return snapshot;
+    }
+
+    /**
+     * Saves the current state according to the given flags and returns the new current snapshot.
+     * <p/>
+     * This is the equivalent of {@link Canvas#save(int)}
+     *
+     * @param flags the save flags.
+     * @return the new snapshot
+     *
+     * @see Canvas#save(int)
+     */
+    public GcSnapshot save(int flags) {
+        return new GcSnapshot(this, null /*layerbounds*/, null /*paint*/, flags);
+    }
+
+    /**
+     * Saves the current state and creates a new layer, and returns the new current snapshot.
+     * <p/>
+     * This is the equivalent of {@link Canvas#saveLayer(RectF, Paint, int)}
+     *
+     * @param layerBounds the layer bounds
+     * @param paint the Paint information used to blit the layer back into the layers underneath
+     *          upon restore
+     * @param flags the save flags.
+     * @return the new snapshot
+     *
+     * @see Canvas#saveLayer(RectF, Paint, int)
+     */
+    public GcSnapshot saveLayer(RectF layerBounds, Paint_Delegate paint, int flags) {
+        return new GcSnapshot(this, layerBounds, paint, flags);
     }
 
     /**
      * Creates the root snapshot.
      * {@link #setGraphics2D(Graphics2D)} will have to be called on it when possible.
      */
-    public GcSnapshot() {
+    private GcSnapshot() {
         mPrevious = null;
         mFlags = 0;
+        mLocalLayer = null;
+        mLocalLayerPaint = null;
+    }
+
+    /**
+     * Creates a new {@link GcSnapshot} on top of another one, with a layer data to be restored
+     * into the main graphics when {@link #restore()} is called.
+     *
+     * @param previous the previous snapshot head.
+     * @param layerBounds the region of the layer. Optional, if null, this is a normal save()
+     * @param paint the Paint information used to blit the layer back into the layers underneath
+     *          upon restore
+     * @param flags the flags regarding what should be saved.
+     */
+    private GcSnapshot(GcSnapshot previous, RectF layerBounds, Paint_Delegate paint, int flags) {
+        assert previous != null;
+        mPrevious = previous;
+        mFlags = flags;
+
+        // make a copy of the current layers before adding the new one.
+        // This keeps the same BufferedImage reference but creates new Graphics2D for this
+        // snapshot.
+        // It does not copy whatever original copy the layers have, as they will be done
+        // only if the new layer doesn't clip drawing to itself.
+        for (Layer layer : mPrevious.mLayers) {
+            mLayers.add(layer.makeCopy());
+        }
+
+        if (layerBounds != null) {
+            // get the current transform
+            AffineTransform matrix = mLayers.get(0).getGraphics().getTransform();
+
+            // transform the layerBounds and puts it into a int rect
+            RectF rect2 = new RectF();
+            mapRect(matrix, rect2, layerBounds);
+            mLocalLayerRegion = new Rect();
+            rect2.round(mLocalLayerRegion);
+
+            // get the base layer (always at index 0)
+            Layer baseLayer = mLayers.get(0);
+
+            // create the image for the layer
+            BufferedImage layerImage = new BufferedImage(
+                    baseLayer.getImage().getWidth(),
+                    baseLayer.getImage().getHeight(),
+                    BufferedImage.TYPE_INT_ARGB);
+
+            // create a graphics for it so that drawing can be done.
+            Graphics2D layerGraphics = layerImage.createGraphics();
+
+            // because this layer inherits the current context for transform and clip,
+            // set them to one from the base layer.
+            AffineTransform currentMtx = baseLayer.getGraphics().getTransform();
+            layerGraphics.setTransform(currentMtx);
+
+            Shape currentClip = baseLayer.getGraphics().getClip();
+            layerGraphics.setClip(currentClip);
+
+            // create a new layer for this new layer and add it to the list at the end.
+            mLayers.add(mLocalLayer = new Layer(layerGraphics, layerImage));
+
+            // if the drawing is not clipped to the local layer only, we save the current content
+            // of all other layers. We are only interested in the part that will actually
+            // be drawn, so we create as small bitmaps as we can.
+            // This is so that we can erase the drawing that goes in the layers below that will
+            // be coming from the layer itself.
+            if ((mFlags & Canvas.CLIP_TO_LAYER_SAVE_FLAG) == 0) {
+                int w = mLocalLayerRegion.width();
+                int h = mLocalLayerRegion.height();
+                for (int i = 0 ; i < mLayers.size() - 1 ; i++) {
+                    Layer layer = mLayers.get(i);
+                    BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
+                    Graphics2D graphics = image.createGraphics();
+                    graphics.drawImage(layer.getImage(),
+                            0, 0, w, h,
+                            mLocalLayerRegion.left, mLocalLayerRegion.top,
+                                    mLocalLayerRegion.right, mLocalLayerRegion.bottom,
+                            null);
+                    graphics.dispose();
+                    layer.setOriginalCopy(image);
+                }
+            }
+        } else {
+            mLocalLayer = null;
+        }
+
+        mLocalLayerPaint  = paint;
     }
 
     public void dispose() {
-        if (mGraphics2D != null) {
-            mGraphics2D.dispose();
+        for (Layer layer : mLayers) {
+            layer.getGraphics().dispose();
         }
 
         if (mPrevious != null) {
@@ -102,35 +295,35 @@
     }
 
     /**
-     * Sets the Graphics2D object for this snapshot if it was created through {@link #GcSnapshot()}.
+     * Link the snapshot to a BufferedImage.
+     * <p/>
+     * This is only for the case where the snapshot was created with a null image when calling
+     * {@link #createDefaultSnapshot(BufferedImage)}, and is therefore not yet linked to
+     * a previous snapshot.
+     * <p/>
      * If any transform or clip information was set before, they are put into the Graphics object.
-     * @param graphics2D the graphics object to set.
+     * @param image the buffered image to link to.
      */
-    public void setGraphics2D(Graphics2D graphics2D) {
-        mGraphics2D = graphics2D;
+    public void setImage(BufferedImage image) {
+        assert mLayers.size() == 0;
+        Graphics2D graphics2D = image.createGraphics();
+        mLayers.add(new Layer(graphics2D, image));
         if (mTransform != null) {
-            mGraphics2D.setTransform(mTransform);
+            graphics2D.setTransform(mTransform);
             mTransform = null;
         }
 
         if (mClip != null) {
-            mGraphics2D.setClip(mClip);
+            graphics2D.setClip(mClip);
             mClip = null;
         }
     }
 
-    /**
-     * Creates and return a copy of the current {@link Graphics2D}.
-     * @return a new {@link Graphics2D}.
-     */
-    public Graphics2D create() {
-        assert mGraphics2D != null;
-        return (Graphics2D) mGraphics2D.create();
-    }
-
     public void translate(float dx, float dy) {
-        if (mGraphics2D != null) {
-            mGraphics2D.translate(dx, dy);
+        if (mLayers.size() > 0) {
+            for (Layer layer : mLayers) {
+                layer.getGraphics().translate(dx, dy);
+            }
         } else {
             if (mTransform == null) {
                 mTransform = new AffineTransform();
@@ -140,8 +333,10 @@
     }
 
     public void rotate(double radians) {
-        if (mGraphics2D != null) {
-            mGraphics2D.rotate(radians);
+        if (mLayers.size() > 0) {
+            for (Layer layer : mLayers) {
+                layer.getGraphics().rotate(radians);
+            }
         } else {
             if (mTransform == null) {
                 mTransform = new AffineTransform();
@@ -151,8 +346,10 @@
     }
 
     public void scale(float sx, float sy) {
-        if (mGraphics2D != null) {
-            mGraphics2D.scale(sx, sy);
+        if (mLayers.size() > 0) {
+            for (Layer layer : mLayers) {
+                layer.getGraphics().scale(sx, sy);
+            }
         } else {
             if (mTransform == null) {
                 mTransform = new AffineTransform();
@@ -162,8 +359,9 @@
     }
 
     public AffineTransform getTransform() {
-        if (mGraphics2D != null) {
-            return mGraphics2D.getTransform();
+        if (mLayers.size() > 0) {
+            // all graphics2D in the list have the same transform
+            return mLayers.get(0).getGraphics().getTransform();
         } else {
             if (mTransform == null) {
                 mTransform = new AffineTransform();
@@ -173,8 +371,10 @@
     }
 
     public void setTransform(AffineTransform transform) {
-        if (mGraphics2D != null) {
-            mGraphics2D.setTransform(transform);
+        if (mLayers.size() > 0) {
+            for (Layer layer : mLayers) {
+                layer.getGraphics().setTransform(transform);
+            }
         } else {
             if (mTransform == null) {
                 mTransform = new AffineTransform();
@@ -184,40 +384,52 @@
     }
 
     public boolean clipRect(float left, float top, float right, float bottom, int regionOp) {
-        if (mGraphics2D != null) {
+        if (mLayers.size() > 0) {
+            Shape clip = null;
             if (regionOp == Region.Op.DIFFERENCE.nativeInt) {
-                Area newClip = new Area(mGraphics2D.getClip());
+                Area newClip = new Area(getClip());
                 newClip.subtract(new Area(
                         new Rectangle2D.Float(left, top, right - left, bottom - top)));
-                mGraphics2D.setClip(newClip);
+                clip = newClip;
 
             } else if (regionOp == Region.Op.INTERSECT.nativeInt) {
-                mGraphics2D.clipRect((int) left, (int) top,
-                        (int) (right - left), (int) (bottom - top));
+                for (Layer layer : mLayers) {
+                    layer.getGraphics().clipRect(
+                            (int) left, (int) top, (int) (right - left), (int) (bottom - top));
+                }
 
             } else if (regionOp == Region.Op.UNION.nativeInt) {
-                Area newClip = new Area(mGraphics2D.getClip());
+                Area newClip = new Area(getClip());
                 newClip.add(new Area(
                         new Rectangle2D.Float(left, top, right - left, bottom - top)));
-                mGraphics2D.setClip(newClip);
+                clip = newClip;
 
             } else if (regionOp == Region.Op.XOR.nativeInt) {
-                Area newClip = new Area(mGraphics2D.getClip());
+                Area newClip = new Area(getClip());
                 newClip.exclusiveOr(new Area(
                         new Rectangle2D.Float(left, top, right - left, bottom - top)));
-                mGraphics2D.setClip(newClip);
+                clip = newClip;
 
             } else if (regionOp == Region.Op.REVERSE_DIFFERENCE.nativeInt) {
                 Area newClip = new Area(
                         new Rectangle2D.Float(left, top, right - left, bottom - top));
-                newClip.subtract(new Area(mGraphics2D.getClip()));
-                mGraphics2D.setClip(newClip);
+                newClip.subtract(new Area(getClip()));
+                clip = newClip;
+
             } else if (regionOp == Region.Op.REPLACE.nativeInt) {
-                mGraphics2D.setClip((int) left, (int) top,
-                        (int) (right - left), (int) (bottom - top));
+                for (Layer layer : mLayers) {
+                    layer.getGraphics().setClip(
+                            (int) left, (int) top, (int) (right - left), (int) (bottom - top));
+                }
             }
 
-            return mGraphics2D.getClip().getBounds().isEmpty() == false;
+            if (clip != null) {
+                for (Layer layer : mLayers) {
+                    layer.getGraphics().setClip(clip);
+                }
+            }
+
+            return getClip().getBounds().isEmpty() == false;
         } else {
             if (mClip == null) {
                 mClip = new Area();
@@ -238,8 +450,9 @@
     }
 
     public Shape getClip() {
-        if (mGraphics2D != null) {
-            return mGraphics2D.getClip();
+        if (mLayers.size() > 0) {
+            // they all have the same clip
+            return mLayers.get(0).getGraphics().getClip();
         } else {
             if (mClip == null) {
                 mClip = new Area();
@@ -263,26 +476,261 @@
         }
     }
 
+    /**
+     * 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*/);
+    }
+
+    /**
+     * 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.
+     */
+    public void draw(Drawable drawable, Paint_Delegate paint, boolean compositeOnly) {
+        // if we clip to the layer, then we only draw in the layer
+        if (mLocalLayer != null && (mFlags & Canvas.CLIP_TO_LAYER_SAVE_FLAG) != 0) {
+            drawInLayer(mLocalLayer, drawable, paint, compositeOnly);
+        } else {
+            // draw in all the layers
+            for (Layer layer : mLayers) {
+                drawInLayer(layer, drawable, paint, compositeOnly);
+            }
+        }
+    }
+
+    private void drawInLayer(Layer layer, Drawable drawable, Paint_Delegate paint,
+            boolean compositeOnly) {
+        Graphics2D originalGraphics = layer.getGraphics();
+        // get a Graphics2D object configured with the drawing parameters.
+        Graphics2D configuredGraphics2D =
+            paint != null ?
+                    createCustomGraphics(originalGraphics, paint, compositeOnly) :
+                        (Graphics2D) originalGraphics.create();
+
+        try {
+            drawable.draw(configuredGraphics2D, paint);
+        } finally {
+            // dispose Graphics2D object
+            configuredGraphics2D.dispose();
+        }
+    }
+
     private GcSnapshot doRestore() {
-        // if this snapshot does not save everything, then set the previous snapshot
-        // to this snapshot content
         if (mPrevious != null) {
+            boolean forceAllSave = false;
+            if (mLocalLayer != null) {
+                forceAllSave = true; // layers always save both clip and transform
+
+                // prepare to draw the current layer in the previous layers, ie all layers but
+                // the last one, since the last one is the local layer
+                for (int i = 0 ; i < mLayers.size() - 1 ; i++) {
+                    Layer layer = mLayers.get(i);
+
+                    Graphics2D baseGfx = layer.getImage().createGraphics();
+
+                    // if the layer contains an original copy this means the flags
+                    // didn't restrict drawing to the local layer and we need to make sure the
+                    // layer bounds in the layer beneath didn't receive any drawing.
+                    // so we use the originalCopy to erase the new drawings in there.
+                    BufferedImage originalCopy = layer.getOriginalCopy();
+                    if (originalCopy != null) {
+                        Graphics2D g = (Graphics2D) baseGfx.create();
+                        g.setComposite(AlphaComposite.Src);
+
+                        g.drawImage(originalCopy,
+                                mLocalLayerRegion.left, mLocalLayerRegion.top,
+                                        mLocalLayerRegion.right, mLocalLayerRegion.bottom,
+                                0, 0, mLocalLayerRegion.width(), mLocalLayerRegion.height(),
+                                null);
+                        g.dispose();
+                    }
+
+                    // now draw put the content of the local layer onto the layer, using the paint
+                    // information
+                    Graphics2D g = createCustomGraphics(baseGfx, mLocalLayerPaint,
+                            true /*alphaOnly*/);
+
+                    g.drawImage(mLocalLayer.getImage(),
+                            mLocalLayerRegion.left, mLocalLayerRegion.top,
+                                    mLocalLayerRegion.right, mLocalLayerRegion.bottom,
+                            mLocalLayerRegion.left, mLocalLayerRegion.top,
+                                    mLocalLayerRegion.right, mLocalLayerRegion.bottom,
+                            null);
+                    g.dispose();
+
+                    baseGfx.dispose();
+                }
+            }
+
+            // if this snapshot does not save everything, then set the previous snapshot
+            // to this snapshot content
+
             // didn't save the matrix? set the current matrix on the previous snapshot
-            if ((mFlags & Canvas.MATRIX_SAVE_FLAG) == 0) {
-                mPrevious.mGraphics2D.setTransform(getTransform());
+            if (forceAllSave == false && (mFlags & Canvas.MATRIX_SAVE_FLAG) == 0) {
+                AffineTransform mtx = getTransform();
+                for (Layer layer : mPrevious.mLayers) {
+                    layer.getGraphics().setTransform(mtx);
+                }
             }
 
             // didn't save the clip? set the current clip on the previous snapshot
-            if ((mFlags & Canvas.CLIP_SAVE_FLAG) == 0) {
-                mPrevious.mGraphics2D.setClip(mGraphics2D.getClip());
+            if (forceAllSave == false && (mFlags & Canvas.CLIP_SAVE_FLAG) == 0) {
+                Shape clip = getClip();
+                for (Layer layer : mPrevious.mLayers) {
+                    layer.getGraphics().setClip(clip);
+                }
             }
         }
 
-        if (mGraphics2D != null) {
-            mGraphics2D.dispose();
+        for (Layer layer : mLayers) {
+            layer.getGraphics().dispose();
         }
 
         return mPrevious;
     }
 
+    /**
+     * Creates a new {@link Graphics2D} based on the {@link Paint} parameters.
+     * <p/>The object must be disposed ({@link Graphics2D#dispose()}) after being used.
+     */
+    private Graphics2D createCustomGraphics(Graphics2D original, Paint_Delegate paint,
+            boolean compositeOnly) {
+        // make new one graphics
+        Graphics2D g = (Graphics2D) original.create();
+
+        // configure it
+
+        if (paint.isAntiAliased()) {
+            g.setRenderingHint(
+                    RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+            g.setRenderingHint(
+                    RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+        }
+
+        boolean customShader = false;
+
+        // get the shader first, as it'll replace the color if it can be used it.
+        if (compositeOnly == false) {
+            int nativeShader = paint.getShader();
+            if (nativeShader > 0) {
+                Shader_Delegate shaderDelegate = Shader_Delegate.getDelegate(nativeShader);
+                assert shaderDelegate != null;
+                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(null,
+                                shaderDelegate.getSupportMessage(),
+                                null);
+                    }
+                }
+            }
+
+            // if no shader, use the paint color
+            if (customShader == false) {
+                g.setColor(new Color(paint.getColor(), true /*hasAlpha*/));
+            }
+
+            boolean customStroke = false;
+            int pathEffect = paint.getPathEffect();
+            if (pathEffect > 0) {
+                PathEffect_Delegate effectDelegate = PathEffect_Delegate.getDelegate(pathEffect);
+                assert effectDelegate != null;
+                if (effectDelegate != null) {
+                    if (effectDelegate.isSupported()) {
+                        Stroke stroke = effectDelegate.getStroke(paint);
+                        assert stroke != null;
+                        if (stroke != null) {
+                            g.setStroke(stroke);
+                            customStroke = true;
+                        }
+                    } else {
+                        Bridge.getLog().fidelityWarning(null,
+                                effectDelegate.getSupportMessage(),
+                                null);
+                    }
+                }
+            }
+
+            // if no custom stroke as been set, set the default one.
+            if (customStroke == false) {
+                g.setStroke(new BasicStroke(
+                        paint.getStrokeWidth(),
+                        paint.getJavaCap(),
+                        paint.getJavaJoin(),
+                        paint.getJavaStrokeMiter()));
+            }
+        }
+
+        // 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;
+
+        boolean customXfermode = false;
+        int xfermode = paint.getXfermode();
+        if (xfermode > 0) {
+            Xfermode_Delegate xfermodeDelegate = Xfermode_Delegate.getDelegate(paint.getXfermode());
+            assert xfermodeDelegate != null;
+            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(null,
+                            xfermodeDelegate.getSupportMessage(),
+                            null);
+                }
+            }
+        }
+
+        // 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(PorterDuffXfermode_Delegate.getComposite(
+                    PorterDuff.Mode.SRC_OVER, alpha));
+        }
+
+        return g;
+    }
+
+
+    private void mapRect(AffineTransform matrix, RectF dst, RectF src) {
+        // array with 4 corners
+        float[] corners = new float[] {
+                src.left, src.top,
+                src.right, src.top,
+                src.right, src.bottom,
+                src.left, src.bottom,
+        };
+
+        // apply the transform to them.
+        matrix.transform(corners, 0, corners, 0, 4);
+
+        // now put the result in the rect. We take the min/max of Xs and min/max of Ys
+        dst.left = Math.min(Math.min(corners[0], corners[2]), Math.min(corners[4], corners[6]));
+        dst.right = Math.max(Math.max(corners[0], corners[2]), Math.max(corners[4], corners[6]));
+
+        dst.top = Math.min(Math.min(corners[1], corners[3]), Math.min(corners[5], corners[7]));
+        dst.bottom = Math.max(Math.max(corners[1], corners[3]), Math.max(corners[5], corners[7]));
+    }
+
 }