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