Home | History | Annotate | Download | only in impl
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.layoutlib.bridge.impl;
     18 
     19 import com.android.ide.common.rendering.api.LayoutLog;
     20 import com.android.layoutlib.bridge.Bridge;
     21 
     22 import android.graphics.Bitmap_Delegate;
     23 import android.graphics.Canvas;
     24 import android.graphics.ColorFilter_Delegate;
     25 import android.graphics.Paint;
     26 import android.graphics.Paint_Delegate;
     27 import android.graphics.Rect;
     28 import android.graphics.RectF;
     29 import android.graphics.Region;
     30 import android.graphics.Region_Delegate;
     31 import android.graphics.Shader_Delegate;
     32 import android.graphics.Xfermode_Delegate;
     33 
     34 import java.awt.AlphaComposite;
     35 import java.awt.Color;
     36 import java.awt.Composite;
     37 import java.awt.Graphics2D;
     38 import java.awt.Rectangle;
     39 import java.awt.RenderingHints;
     40 import java.awt.Shape;
     41 import java.awt.geom.AffineTransform;
     42 import java.awt.geom.Area;
     43 import java.awt.geom.Rectangle2D;
     44 import java.awt.image.BufferedImage;
     45 import java.util.ArrayList;
     46 
     47 /**
     48  * Class representing a graphics context snapshot, as well as a context stack as a linked list.
     49  * <p>
     50  * This is based on top of {@link Graphics2D} but can operate independently if none are available
     51  * yet when setting transforms and clip information.
     52  * <p>
     53  * This allows for drawing through {@link #draw(Drawable, Paint_Delegate, boolean, boolean)} and
     54  * {@link #draw(Drawable)}
     55  *
     56  * Handling of layers (created with {@link Canvas#saveLayer(RectF, Paint, int)}) is handled through
     57  * a list of Graphics2D for each layers. The class actually maintains a list of {@link Layer}
     58  * for each layer. Doing a save() will duplicate this list so that each graphics2D object
     59  * ({@link Layer#getGraphics()}) is configured only for the new snapshot.
     60  */
     61 public class GcSnapshot {
     62 
     63     private final GcSnapshot mPrevious;
     64     private final int mFlags;
     65 
     66     /** list of layers. The first item in the list is always the  */
     67     private final ArrayList<Layer> mLayers = new ArrayList<Layer>();
     68 
     69     /** temp transform in case transformation are set before a Graphics2D exists */
     70     private AffineTransform mTransform = null;
     71     /** temp clip in case clipping is set before a Graphics2D exists */
     72     private Area mClip = null;
     73 
     74     // local layer data
     75     /** a local layer created with {@link Canvas#saveLayer(RectF, Paint, int)}.
     76      * If this is null, this does not mean there's no layer, just that the snapshot is not the
     77      * one that created the layer.
     78      * @see #getLayerSnapshot()
     79      */
     80     private final Layer mLocalLayer;
     81     private final Paint_Delegate mLocalLayerPaint;
     82     private final Rect mLayerBounds;
     83 
     84     public interface Drawable {
     85         void draw(Graphics2D graphics, Paint_Delegate paint);
     86     }
     87 
     88     /**
     89      * Class containing information about a layer.
     90      *
     91      * This contains graphics, bitmap and layer information.
     92      */
     93     private static class Layer {
     94         private final Graphics2D mGraphics;
     95         private final Bitmap_Delegate mBitmap;
     96         private final BufferedImage mImage;
     97         /** the flags that were used to configure the layer. This is never changed, and passed
     98          * as is when {@link #makeCopy()} is called */
     99         private final int mFlags;
    100         /** the original content of the layer when the next object was created. This is not
    101          * passed in {@link #makeCopy()} and instead is recreated when a new layer is added
    102          * (depending on its flags) */
    103         private BufferedImage mOriginalCopy;
    104 
    105         /**
    106          * Creates a layer with a graphics and a bitmap. This is only used to create
    107          * the base layer.
    108          *
    109          * @param graphics the graphics
    110          * @param bitmap the bitmap
    111          */
    112         Layer(Graphics2D graphics, Bitmap_Delegate bitmap) {
    113             mGraphics = graphics;
    114             mBitmap = bitmap;
    115             mImage = mBitmap.getImage();
    116             mFlags = 0;
    117         }
    118 
    119         /**
    120          * Creates a layer with a graphics and an image. If the image belongs to a
    121          * {@link Bitmap_Delegate} (case of the base layer), then
    122          * {@link Layer#Layer(Graphics2D, Bitmap_Delegate)} should be used.
    123          *
    124          * @param graphics the graphics the new graphics for this layer
    125          * @param image the image the image from which the graphics came
    126          * @param flags the flags that were used to save this layer
    127          */
    128         Layer(Graphics2D graphics, BufferedImage image, int flags) {
    129             mGraphics = graphics;
    130             mBitmap = null;
    131             mImage = image;
    132             mFlags = flags;
    133         }
    134 
    135         /** The Graphics2D, guaranteed to be non null */
    136         Graphics2D getGraphics() {
    137             return mGraphics;
    138         }
    139 
    140         /** The BufferedImage, guaranteed to be non null */
    141         BufferedImage getImage() {
    142             return mImage;
    143         }
    144 
    145         /** Returns the layer save flags. This is only valid for additional layers.
    146          * For the base layer this will always return 0;
    147          * For a given layer, all further copies of this {@link Layer} object in new snapshots
    148          * will always return the same value.
    149          */
    150         int getFlags() {
    151             return mFlags;
    152         }
    153 
    154         Layer makeCopy() {
    155             if (mBitmap != null) {
    156                 return new Layer((Graphics2D) mGraphics.create(), mBitmap);
    157             }
    158 
    159             return new Layer((Graphics2D) mGraphics.create(), mImage, mFlags);
    160         }
    161 
    162         /** sets an optional copy of the original content to be used during restore */
    163         void setOriginalCopy(BufferedImage image) {
    164             mOriginalCopy = image;
    165         }
    166 
    167         BufferedImage getOriginalCopy() {
    168             return mOriginalCopy;
    169         }
    170 
    171         void change() {
    172             if (mBitmap != null) {
    173                 mBitmap.change();
    174             }
    175         }
    176 
    177         /**
    178          * Sets the clip for the graphics2D object associated with the layer.
    179          * This should be used over the normal Graphics2D setClip method.
    180          *
    181          * @param clipShape the shape to use a the clip shape.
    182          */
    183         void setClip(Shape clipShape) {
    184             // because setClip is only guaranteed to work with rectangle shape,
    185             // first reset the clip to max and then intersect the current (empty)
    186             // clip with the shap.
    187             mGraphics.setClip(null);
    188             mGraphics.clip(clipShape);
    189         }
    190 
    191         /**
    192          * Clips the layer with the given shape. This performs an intersect between the current
    193          * clip shape and the given shape.
    194          * @param shape the new clip shape.
    195          */
    196         public void clip(Shape shape) {
    197             mGraphics.clip(shape);
    198         }
    199     }
    200 
    201     /**
    202      * Creates the root snapshot associating it with a given bitmap.
    203      * <p>
    204      * If <var>bitmap</var> is null, then {@link GcSnapshot#setBitmap(Bitmap_Delegate)} must be
    205      * called before the snapshot can be used to draw. Transform and clip operations are permitted
    206      * before.
    207      *
    208      * @param bitmap the image to associate to the snapshot or null.
    209      * @return the root snapshot
    210      */
    211     public static GcSnapshot createDefaultSnapshot(Bitmap_Delegate bitmap) {
    212         GcSnapshot snapshot = new GcSnapshot();
    213         if (bitmap != null) {
    214             snapshot.setBitmap(bitmap);
    215         }
    216 
    217         return snapshot;
    218     }
    219 
    220     /**
    221      * Saves the current state according to the given flags and returns the new current snapshot.
    222      * <p/>
    223      * This is the equivalent of {@link Canvas#save(int)}
    224      *
    225      * @param flags the save flags.
    226      * @return the new snapshot
    227      *
    228      * @see Canvas#save(int)
    229      */
    230     public GcSnapshot save(int flags) {
    231         return new GcSnapshot(this, null /*layerbounds*/, null /*paint*/, flags);
    232     }
    233 
    234     /**
    235      * Saves the current state and creates a new layer, and returns the new current snapshot.
    236      * <p/>
    237      * This is the equivalent of {@link Canvas#saveLayer(RectF, Paint, int)}
    238      *
    239      * @param layerBounds the layer bounds
    240      * @param paint the Paint information used to blit the layer back into the layers underneath
    241      *          upon restore
    242      * @param flags the save flags.
    243      * @return the new snapshot
    244      *
    245      * @see Canvas#saveLayer(RectF, Paint, int)
    246      */
    247     public GcSnapshot saveLayer(RectF layerBounds, Paint_Delegate paint, int flags) {
    248         return new GcSnapshot(this, layerBounds, paint, flags);
    249     }
    250 
    251     /**
    252      * Creates the root snapshot.
    253      * {@link #setGraphics2D(Graphics2D)} will have to be called on it when possible.
    254      */
    255     private GcSnapshot() {
    256         mPrevious = null;
    257         mFlags = 0;
    258         mLocalLayer = null;
    259         mLocalLayerPaint = null;
    260         mLayerBounds = null;
    261     }
    262 
    263     /**
    264      * Creates a new {@link GcSnapshot} on top of another one, with a layer data to be restored
    265      * into the main graphics when {@link #restore()} is called.
    266      *
    267      * @param previous the previous snapshot head.
    268      * @param layerBounds the region of the layer. Optional, if null, this is a normal save()
    269      * @param paint the Paint information used to blit the layer back into the layers underneath
    270      *          upon restore
    271      * @param flags the flags regarding what should be saved.
    272      */
    273     private GcSnapshot(GcSnapshot previous, RectF layerBounds, Paint_Delegate paint, int flags) {
    274         assert previous != null;
    275         mPrevious = previous;
    276         mFlags = flags;
    277 
    278         // make a copy of the current layers before adding the new one.
    279         // This keeps the same BufferedImage reference but creates new Graphics2D for this
    280         // snapshot.
    281         // It does not copy whatever original copy the layers have, as they will be done
    282         // only if the new layer doesn't clip drawing to itself.
    283         for (Layer layer : mPrevious.mLayers) {
    284             mLayers.add(layer.makeCopy());
    285         }
    286 
    287         if (layerBounds != null) {
    288             // get the current transform
    289             AffineTransform matrix = mLayers.get(0).getGraphics().getTransform();
    290 
    291             // transform the layerBounds with the current transform and stores it into a int rect
    292             RectF rect2 = new RectF();
    293             mapRect(matrix, rect2, layerBounds);
    294             mLayerBounds = new Rect();
    295             rect2.round(mLayerBounds);
    296 
    297             // get the base layer (always at index 0)
    298             Layer baseLayer = mLayers.get(0);
    299 
    300             // create the image for the layer
    301             BufferedImage layerImage = new BufferedImage(
    302                     baseLayer.getImage().getWidth(),
    303                     baseLayer.getImage().getHeight(),
    304                     (mFlags & Canvas.HAS_ALPHA_LAYER_SAVE_FLAG) != 0 ?
    305                             BufferedImage.TYPE_INT_ARGB :
    306                                 BufferedImage.TYPE_INT_RGB);
    307 
    308             // create a graphics for it so that drawing can be done.
    309             Graphics2D layerGraphics = layerImage.createGraphics();
    310 
    311             // because this layer inherits the current context for transform and clip,
    312             // set them to one from the base layer.
    313             AffineTransform currentMtx = baseLayer.getGraphics().getTransform();
    314             layerGraphics.setTransform(currentMtx);
    315 
    316             // create a new layer for this new layer and add it to the list at the end.
    317             mLayers.add(mLocalLayer = new Layer(layerGraphics, layerImage, flags));
    318 
    319             // set the clip on it.
    320             Shape currentClip = baseLayer.getGraphics().getClip();
    321             mLocalLayer.setClip(currentClip);
    322 
    323             // if the drawing is not clipped to the local layer only, we save the current content
    324             // of all other layers. We are only interested in the part that will actually
    325             // be drawn, so we create as small bitmaps as we can.
    326             // This is so that we can erase the drawing that goes in the layers below that will
    327             // be coming from the layer itself.
    328             if ((mFlags & Canvas.CLIP_TO_LAYER_SAVE_FLAG) == 0) {
    329                 int w = mLayerBounds.width();
    330                 int h = mLayerBounds.height();
    331                 for (int i = 0 ; i < mLayers.size() - 1 ; i++) {
    332                     Layer layer = mLayers.get(i);
    333                     BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
    334                     Graphics2D graphics = image.createGraphics();
    335                     graphics.drawImage(layer.getImage(),
    336                             0, 0, w, h,
    337                             mLayerBounds.left, mLayerBounds.top,
    338                                     mLayerBounds.right, mLayerBounds.bottom,
    339                             null);
    340                     graphics.dispose();
    341                     layer.setOriginalCopy(image);
    342                 }
    343             }
    344         } else {
    345             mLocalLayer = null;
    346             mLayerBounds = null;
    347         }
    348 
    349         mLocalLayerPaint  = paint;
    350     }
    351 
    352     public void dispose() {
    353         for (Layer layer : mLayers) {
    354             layer.getGraphics().dispose();
    355         }
    356 
    357         if (mPrevious != null) {
    358             mPrevious.dispose();
    359         }
    360     }
    361 
    362     /**
    363      * Restores the top {@link GcSnapshot}, and returns the next one.
    364      */
    365     public GcSnapshot restore() {
    366         return doRestore();
    367     }
    368 
    369     /**
    370      * Restores the {@link GcSnapshot} to <var>saveCount</var>.
    371      * @param saveCount the saveCount or -1 to only restore 1.
    372      *
    373      * @return the new head of the Gc snapshot stack.
    374      */
    375     public GcSnapshot restoreTo(int saveCount) {
    376         return doRestoreTo(size(), saveCount);
    377     }
    378 
    379     public int size() {
    380         if (mPrevious != null) {
    381             return mPrevious.size() + 1;
    382         }
    383 
    384         return 1;
    385     }
    386 
    387     /**
    388      * Link the snapshot to a Bitmap_Delegate.
    389      * <p/>
    390      * This is only for the case where the snapshot was created with a null image when calling
    391      * {@link #createDefaultSnapshot(Bitmap_Delegate)}, and is therefore not yet linked to
    392      * a previous snapshot.
    393      * <p/>
    394      * If any transform or clip information was set before, they are put into the Graphics object.
    395      * @param bitmap the bitmap to link to.
    396      */
    397     public void setBitmap(Bitmap_Delegate bitmap) {
    398         // create a new Layer for the bitmap. This will be the base layer.
    399         Graphics2D graphics2D = bitmap.getImage().createGraphics();
    400         Layer baseLayer = new Layer(graphics2D, bitmap);
    401 
    402         // Set the current transform and clip which can either come from mTransform/mClip if they
    403         // were set when there was no bitmap/layers or from the current base layers if there is
    404         // one already.
    405 
    406         graphics2D.setTransform(getTransform());
    407         // reset mTransform in case there was one.
    408         mTransform = null;
    409 
    410         baseLayer.setClip(getClip());
    411         // reset mClip in case there was one.
    412         mClip = null;
    413 
    414         // replace whatever current layers we have with this.
    415         mLayers.clear();
    416         mLayers.add(baseLayer);
    417 
    418     }
    419 
    420     public void translate(float dx, float dy) {
    421         if (mLayers.size() > 0) {
    422             for (Layer layer : mLayers) {
    423                 layer.getGraphics().translate(dx, dy);
    424             }
    425         } else {
    426             if (mTransform == null) {
    427                 mTransform = new AffineTransform();
    428             }
    429             mTransform.translate(dx, dy);
    430         }
    431     }
    432 
    433     public void rotate(double radians) {
    434         if (mLayers.size() > 0) {
    435             for (Layer layer : mLayers) {
    436                 layer.getGraphics().rotate(radians);
    437             }
    438         } else {
    439             if (mTransform == null) {
    440                 mTransform = new AffineTransform();
    441             }
    442             mTransform.rotate(radians);
    443         }
    444     }
    445 
    446     public void scale(float sx, float sy) {
    447         if (mLayers.size() > 0) {
    448             for (Layer layer : mLayers) {
    449                 layer.getGraphics().scale(sx, sy);
    450             }
    451         } else {
    452             if (mTransform == null) {
    453                 mTransform = new AffineTransform();
    454             }
    455             mTransform.scale(sx, sy);
    456         }
    457     }
    458 
    459     public AffineTransform getTransform() {
    460         if (mLayers.size() > 0) {
    461             // all graphics2D in the list have the same transform
    462             return mLayers.get(0).getGraphics().getTransform();
    463         } else {
    464             if (mTransform == null) {
    465                 mTransform = new AffineTransform();
    466             }
    467             return mTransform;
    468         }
    469     }
    470 
    471     public void setTransform(AffineTransform transform) {
    472         if (mLayers.size() > 0) {
    473             for (Layer layer : mLayers) {
    474                 layer.getGraphics().setTransform(transform);
    475             }
    476         } else {
    477             if (mTransform == null) {
    478                 mTransform = new AffineTransform();
    479             }
    480             mTransform.setTransform(transform);
    481         }
    482     }
    483 
    484     public boolean clip(Shape shape, int regionOp) {
    485         // Simple case of intersect with existing layers.
    486         // Because Graphics2D#setClip works a bit peculiarly, we optimize
    487         // the case of clipping by intersection, as it's supported natively.
    488         if (regionOp == Region.Op.INTERSECT.nativeInt && mLayers.size() > 0) {
    489             for (Layer layer : mLayers) {
    490                 layer.clip(shape);
    491             }
    492 
    493             Shape currentClip = getClip();
    494             return currentClip != null && currentClip.getBounds().isEmpty() == false;
    495         }
    496 
    497         Area area = null;
    498 
    499         if (regionOp == Region.Op.REPLACE.nativeInt) {
    500             area = new Area(shape);
    501         } else {
    502             area = Region_Delegate.combineShapes(getClip(), shape, regionOp);
    503         }
    504 
    505         assert area != null;
    506 
    507         if (mLayers.size() > 0) {
    508             if (area != null) {
    509                 for (Layer layer : mLayers) {
    510                     layer.setClip(area);
    511                 }
    512             }
    513 
    514             Shape currentClip = getClip();
    515             return currentClip != null && currentClip.getBounds().isEmpty() == false;
    516         } else {
    517             if (area != null) {
    518                 mClip = area;
    519             } else {
    520                 mClip = new Area();
    521             }
    522 
    523             return mClip.getBounds().isEmpty() == false;
    524         }
    525     }
    526 
    527     public boolean clipRect(float left, float top, float right, float bottom, int regionOp) {
    528         return clip(new Rectangle2D.Float(left, top, right - left, bottom - top), regionOp);
    529     }
    530 
    531     /**
    532      * Returns the current clip, or null if none have been setup.
    533      */
    534     public Shape getClip() {
    535         if (mLayers.size() > 0) {
    536             // they all have the same clip
    537             return mLayers.get(0).getGraphics().getClip();
    538         } else {
    539             return mClip;
    540         }
    541     }
    542 
    543     private GcSnapshot doRestoreTo(int size, int saveCount) {
    544         if (size <= saveCount) {
    545             return this;
    546         }
    547 
    548         // restore the current one first.
    549         GcSnapshot previous = doRestore();
    550 
    551         if (size == saveCount + 1) { // this was the only one that needed restore.
    552             return previous;
    553         } else {
    554             return previous.doRestoreTo(size - 1, saveCount);
    555         }
    556     }
    557 
    558     /**
    559      * Executes the Drawable's draw method, with a null paint delegate.
    560      * <p/>
    561      * Note that the method can be called several times if there are more than one active layer.
    562      */
    563     public void draw(Drawable drawable) {
    564         draw(drawable, null, false /*compositeOnly*/, false /*forceSrcMode*/);
    565     }
    566 
    567     /**
    568      * Executes the Drawable's draw method.
    569      * <p/>
    570      * Note that the method can be called several times if there are more than one active layer.
    571      * @param compositeOnly whether the paint is used for composite only. This is typically
    572      *          the case for bitmaps.
    573      * @param forceSrcMode if true, this overrides the composite to be SRC
    574      */
    575     public void draw(Drawable drawable, Paint_Delegate paint, boolean compositeOnly,
    576             boolean forceSrcMode) {
    577         int forceMode = forceSrcMode ? AlphaComposite.SRC : 0;
    578         // the current snapshot may not have a mLocalLayer (ie it was created on save() instead
    579         // of saveLayer(), but that doesn't mean there's no layer.
    580         // mLayers however saves all the information we need (flags).
    581         if (mLayers.size() == 1) {
    582             // no layer, only base layer. easy case.
    583             drawInLayer(mLayers.get(0), drawable, paint, compositeOnly, forceMode);
    584         } else {
    585             // draw in all the layers until the layer save flags tells us to stop (ie drawing
    586             // in that layer is limited to the layer itself.
    587             int flags;
    588             int i = mLayers.size() - 1;
    589 
    590             do {
    591                 Layer layer = mLayers.get(i);
    592 
    593                 drawInLayer(layer, drawable, paint, compositeOnly, forceMode);
    594 
    595                 // then go to previous layer, only if there are any left, and its flags
    596                 // doesn't restrict drawing to the layer itself.
    597                 i--;
    598                 flags = layer.getFlags();
    599             } while (i >= 0 && (flags & Canvas.CLIP_TO_LAYER_SAVE_FLAG) == 0);
    600         }
    601     }
    602 
    603     private void drawInLayer(Layer layer, Drawable drawable, Paint_Delegate paint,
    604             boolean compositeOnly, int forceMode) {
    605         Graphics2D originalGraphics = layer.getGraphics();
    606         if (paint == null) {
    607             drawOnGraphics((Graphics2D) originalGraphics.create(), drawable,
    608                     null /*paint*/, layer);
    609         } else {
    610             ColorFilter_Delegate filter = paint.getColorFilter();
    611             if (filter == null || !filter.isSupported()) {
    612                 // get a Graphics2D object configured with the drawing parameters.
    613                 Graphics2D configuredGraphics = createCustomGraphics(originalGraphics, paint,
    614                         compositeOnly, forceMode);
    615                 drawOnGraphics(configuredGraphics, drawable, paint, layer);
    616                 return;
    617             }
    618 
    619             int x = 0;
    620             int y = 0;
    621             int width;
    622             int height;
    623             Rectangle clipBounds = originalGraphics.getClipBounds();
    624             if (clipBounds != null) {
    625                 if (clipBounds.width == 0 || clipBounds.height == 0) {
    626                     // Clip is 0 so no need to paint anything.
    627                     return;
    628                 }
    629                 // If we have clipBounds available, use them as they will always be
    630                 // smaller than the full layer size.
    631                 x = clipBounds.x;
    632                 y = clipBounds.y;
    633                 width = clipBounds.width;
    634                 height = clipBounds.height;
    635             } else {
    636                 width = layer.getImage().getWidth();
    637                 height = layer.getImage().getHeight();
    638             }
    639 
    640             // Create a temporary image to which the color filter will be applied.
    641             BufferedImage image = new BufferedImage(width, height,
    642                     BufferedImage.TYPE_INT_ARGB);
    643             Graphics2D imageBaseGraphics = (Graphics2D) image.getGraphics();
    644             // Configure the Graphics2D object with drawing parameters and shader.
    645             Graphics2D imageGraphics = createCustomGraphics(
    646                     imageBaseGraphics, paint, compositeOnly,
    647                     AlphaComposite.SRC_OVER);
    648             // get a Graphics2D object configured with the drawing parameters, but no shader.
    649             Graphics2D configuredGraphics = createCustomGraphics(originalGraphics, paint,
    650                     true /*compositeOnly*/, forceMode);
    651             try {
    652                 // The main draw operation.
    653                 // We translate the operation to take into account that the rendering does not
    654                 // know about the clipping area.
    655                 imageGraphics.translate(-x, -y);
    656                 drawable.draw(imageGraphics, paint);
    657 
    658                 // Apply the color filter.
    659                 // Restore the original coordinates system and apply the filter only to the
    660                 // clipped area.
    661                 imageGraphics.translate(x, y);
    662                 filter.applyFilter(imageGraphics, width, height);
    663 
    664                 // Draw the tinted image on the main layer using as start point the clipping
    665                 // upper left coordinates.
    666                 configuredGraphics.drawImage(image, x, y, null);
    667                 layer.change();
    668             } finally {
    669                 // dispose Graphics2D objects
    670                 imageGraphics.dispose();
    671                 imageBaseGraphics.dispose();
    672                 configuredGraphics.dispose();
    673             }
    674         }
    675     }
    676 
    677     private void drawOnGraphics(Graphics2D g, Drawable drawable, Paint_Delegate paint,
    678             Layer layer) {
    679         try {
    680             drawable.draw(g, paint);
    681             layer.change();
    682         } finally {
    683             g.dispose();
    684         }
    685     }
    686 
    687     private GcSnapshot doRestore() {
    688         if (mPrevious != null) {
    689             if (mLocalLayer != null) {
    690                 // prepare to blit the layers in which we have draw, in the layer beneath
    691                 // them, starting with the top one (which is the current local layer).
    692                 int i = mLayers.size() - 1;
    693                 int flags;
    694                 do {
    695                     Layer dstLayer = mLayers.get(i - 1);
    696 
    697                     restoreLayer(dstLayer);
    698 
    699                     flags = dstLayer.getFlags();
    700                     i--;
    701                 } while (i > 0 && (flags & Canvas.CLIP_TO_LAYER_SAVE_FLAG) == 0);
    702             }
    703 
    704             // if this snapshot does not save everything, then set the previous snapshot
    705             // to this snapshot content
    706 
    707             // didn't save the matrix? set the current matrix on the previous snapshot
    708             if ((mFlags & Canvas.MATRIX_SAVE_FLAG) == 0) {
    709                 AffineTransform mtx = getTransform();
    710                 for (Layer layer : mPrevious.mLayers) {
    711                     layer.getGraphics().setTransform(mtx);
    712                 }
    713             }
    714 
    715             // didn't save the clip? set the current clip on the previous snapshot
    716             if ((mFlags & Canvas.CLIP_SAVE_FLAG) == 0) {
    717                 Shape clip = getClip();
    718                 for (Layer layer : mPrevious.mLayers) {
    719                     layer.setClip(clip);
    720                 }
    721             }
    722         }
    723 
    724         for (Layer layer : mLayers) {
    725             layer.getGraphics().dispose();
    726         }
    727 
    728         return mPrevious;
    729     }
    730 
    731     private void restoreLayer(Layer dstLayer) {
    732 
    733         Graphics2D baseGfx = dstLayer.getImage().createGraphics();
    734 
    735         // if the layer contains an original copy this means the flags
    736         // didn't restrict drawing to the local layer and we need to make sure the
    737         // layer bounds in the layer beneath didn't receive any drawing.
    738         // so we use the originalCopy to erase the new drawings in there.
    739         BufferedImage originalCopy = dstLayer.getOriginalCopy();
    740         if (originalCopy != null) {
    741             Graphics2D g = (Graphics2D) baseGfx.create();
    742             g.setComposite(AlphaComposite.Src);
    743 
    744             g.drawImage(originalCopy,
    745                     mLayerBounds.left, mLayerBounds.top, mLayerBounds.right, mLayerBounds.bottom,
    746                     0, 0, mLayerBounds.width(), mLayerBounds.height(),
    747                     null);
    748             g.dispose();
    749         }
    750 
    751         // now draw put the content of the local layer onto the layer,
    752         // using the paint information
    753         Graphics2D g = createCustomGraphics(baseGfx, mLocalLayerPaint,
    754                 true /*alphaOnly*/, 0 /*forceMode*/);
    755 
    756         g.drawImage(mLocalLayer.getImage(),
    757                 mLayerBounds.left, mLayerBounds.top, mLayerBounds.right, mLayerBounds.bottom,
    758                 mLayerBounds.left, mLayerBounds.top, mLayerBounds.right, mLayerBounds.bottom,
    759                 null);
    760         g.dispose();
    761 
    762         baseGfx.dispose();
    763     }
    764 
    765     /**
    766      * Creates a new {@link Graphics2D} based on the {@link Paint} parameters.
    767      * <p/>The object must be disposed ({@link Graphics2D#dispose()}) after being used.
    768      */
    769     private Graphics2D createCustomGraphics(Graphics2D original, Paint_Delegate paint,
    770             boolean compositeOnly, int forceMode) {
    771         // make new one graphics
    772         Graphics2D g = (Graphics2D) original.create();
    773 
    774         // configure it
    775 
    776         if (paint.isAntiAliased()) {
    777             g.setRenderingHint(
    778                     RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    779             g.setRenderingHint(
    780                     RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
    781         }
    782 
    783         // set the shader first, as it'll replace the color if it can be used it.
    784         boolean customShader = false;
    785         if (!compositeOnly) {
    786             customShader = setShader(g, paint);
    787             // set the stroke
    788             g.setStroke(paint.getJavaStroke());
    789         }
    790         // set the composite.
    791         setComposite(g, paint, compositeOnly || customShader, forceMode);
    792 
    793         return g;
    794     }
    795 
    796     private boolean setShader(Graphics2D g, Paint_Delegate paint) {
    797         Shader_Delegate shaderDelegate = paint.getShader();
    798         if (shaderDelegate != null) {
    799             if (shaderDelegate.isSupported()) {
    800                 java.awt.Paint shaderPaint = shaderDelegate.getJavaPaint();
    801                 assert shaderPaint != null;
    802                 if (shaderPaint != null) {
    803                     g.setPaint(shaderPaint);
    804                     return true;
    805                 }
    806             } else {
    807                 Bridge.getLog().fidelityWarning(LayoutLog.TAG_SHADER,
    808                         shaderDelegate.getSupportMessage(),
    809                         null /*throwable*/, null /*data*/);
    810             }
    811         }
    812 
    813         // if no shader, use the paint color
    814         g.setColor(new Color(paint.getColor(), true /*hasAlpha*/));
    815 
    816         return false;
    817     }
    818 
    819     private void setComposite(Graphics2D g, Paint_Delegate paint, boolean usePaintAlpha,
    820             int forceMode) {
    821         // the alpha for the composite. Always opaque if the normal paint color is used since
    822         // it contains the alpha
    823         int alpha = usePaintAlpha ? paint.getAlpha() : 0xFF;
    824         if (forceMode != 0) {
    825             g.setComposite(AlphaComposite.getInstance(forceMode, (float) alpha / 255.f));
    826             return;
    827         }
    828         Xfermode_Delegate xfermodeDelegate = paint.getXfermode();
    829         if (xfermodeDelegate != null) {
    830             if (xfermodeDelegate.isSupported()) {
    831                 Composite composite = xfermodeDelegate.getComposite(alpha);
    832                 assert composite != null;
    833                 if (composite != null) {
    834                     g.setComposite(composite);
    835                     return;
    836                 }
    837             } else {
    838                 Bridge.getLog().fidelityWarning(LayoutLog.TAG_XFERMODE,
    839                         xfermodeDelegate.getSupportMessage(),
    840                         null /*throwable*/, null /*data*/);
    841             }
    842         }
    843         // if there was no custom xfermode, but we have alpha (due to a shader and a non
    844         // opaque alpha channel in the paint color), then we create an AlphaComposite anyway
    845         // that will handle the alpha.
    846         if (alpha != 0xFF) {
    847             g.setComposite(AlphaComposite.getInstance(
    848                     AlphaComposite.SRC_OVER, (float) alpha / 255.f));
    849         }
    850     }
    851 
    852     private void mapRect(AffineTransform matrix, RectF dst, RectF src) {
    853         // array with 4 corners
    854         float[] corners = new float[] {
    855                 src.left, src.top,
    856                 src.right, src.top,
    857                 src.right, src.bottom,
    858                 src.left, src.bottom,
    859         };
    860 
    861         // apply the transform to them.
    862         matrix.transform(corners, 0, corners, 0, 4);
    863 
    864         // now put the result in the rect. We take the min/max of Xs and min/max of Ys
    865         dst.left = Math.min(Math.min(corners[0], corners[2]), Math.min(corners[4], corners[6]));
    866         dst.right = Math.max(Math.max(corners[0], corners[2]), Math.max(corners[4], corners[6]));
    867 
    868         dst.top = Math.min(Math.min(corners[1], corners[3]), Math.min(corners[5], corners[7]));
    869         dst.bottom = Math.max(Math.max(corners[1], corners[3]), Math.max(corners[5], corners[7]));
    870     }
    871 
    872 }
    873