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