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      */
81     private final Layer mLocalLayer;
82     private final Paint_Delegate mLocalLayerPaint;
83     private final Rect mLayerBounds;
84 
85     public interface Drawable {
draw(Graphics2D graphics, Paint_Delegate paint)86         void draw(Graphics2D graphics, Paint_Delegate paint);
87     }
88 
89     /**
90      * Class containing information about a layer.
91      *
92      * This contains graphics, bitmap and layer information.
93      */
94     private static class Layer {
95         private final Graphics2D mGraphics;
96         private final Bitmap_Delegate mBitmap;
97         private final BufferedImage mImage;
98         /** the flags that were used to configure the layer. This is never changed, and passed
99          * as is when {@link #makeCopy()} is called */
100         private final int mFlags;
101         /** the original content of the layer when the next object was created. This is not
102          * passed in {@link #makeCopy()} and instead is recreated when a new layer is added
103          * (depending on its flags) */
104         private BufferedImage mOriginalCopy;
105 
106         /**
107          * Creates a layer with a graphics and a bitmap. This is only used to create
108          * the base layer.
109          *
110          * @param graphics the graphics
111          * @param bitmap the bitmap
112          */
Layer(Graphics2D graphics, Bitmap_Delegate bitmap)113         Layer(Graphics2D graphics, Bitmap_Delegate bitmap) {
114             mGraphics = graphics;
115             mBitmap = bitmap;
116             mImage = mBitmap.getImage();
117             mFlags = 0;
118         }
119 
120         /**
121          * Creates a layer with a graphics and an image. If the image belongs to a
122          * {@link Bitmap_Delegate} (case of the base layer), then
123          * {@link Layer#Layer(Graphics2D, Bitmap_Delegate)} should be used.
124          *
125          * @param graphics the graphics the new graphics for this layer
126          * @param image the image the image from which the graphics came
127          * @param flags the flags that were used to save this layer
128          */
Layer(Graphics2D graphics, BufferedImage image, int flags)129         Layer(Graphics2D graphics, BufferedImage image, int flags) {
130             mGraphics = graphics;
131             mBitmap = null;
132             mImage = image;
133             mFlags = flags;
134         }
135 
136         /** The Graphics2D, guaranteed to be non null */
getGraphics()137         Graphics2D getGraphics() {
138             return mGraphics;
139         }
140 
141         /** The BufferedImage, guaranteed to be non null */
getImage()142         BufferedImage getImage() {
143             return mImage;
144         }
145 
146         /** Returns the layer save flags. This is only valid for additional layers.
147          * For the base layer this will always return 0;
148          * For a given layer, all further copies of this {@link Layer} object in new snapshots
149          * will always return the same value.
150          */
getFlags()151         int getFlags() {
152             return mFlags;
153         }
154 
makeCopy()155         Layer makeCopy() {
156             if (mBitmap != null) {
157                 return new Layer((Graphics2D) mGraphics.create(), mBitmap);
158             }
159 
160             return new Layer((Graphics2D) mGraphics.create(), mImage, mFlags);
161         }
162 
163         /** sets an optional copy of the original content to be used during restore */
setOriginalCopy(BufferedImage image)164         void setOriginalCopy(BufferedImage image) {
165             mOriginalCopy = image;
166         }
167 
getOriginalCopy()168         BufferedImage getOriginalCopy() {
169             return mOriginalCopy;
170         }
171 
change()172         void change() {
173             if (mBitmap != null) {
174                 mBitmap.change();
175             }
176         }
177 
178         /**
179          * Sets the clip for the graphics2D object associated with the layer.
180          * This should be used over the normal Graphics2D setClip method.
181          *
182          * @param clipShape the shape to use a the clip shape.
183          */
setClip(Shape clipShape)184         void setClip(Shape clipShape) {
185             // because setClip is only guaranteed to work with rectangle shape,
186             // first reset the clip to max and then intersect the current (empty)
187             // clip with the shap.
188             mGraphics.setClip(null);
189             mGraphics.clip(clipShape);
190         }
191 
192         /**
193          * Clips the layer with the given shape. This performs an intersect between the current
194          * clip shape and the given shape.
195          * @param shape the new clip shape.
196          */
clip(Shape shape)197         public void clip(Shape shape) {
198             mGraphics.clip(shape);
199         }
200     }
201 
202     /**
203      * Creates the root snapshot associating it with a given bitmap.
204      * <p>
205      * If <var>bitmap</var> is null, then {@link GcSnapshot#setBitmap(Bitmap_Delegate)} must be
206      * called before the snapshot can be used to draw. Transform and clip operations are permitted
207      * before.
208      *
209      * @param bitmap the image to associate to the snapshot or null.
210      * @return the root snapshot
211      */
createDefaultSnapshot(Bitmap_Delegate bitmap)212     public static GcSnapshot createDefaultSnapshot(Bitmap_Delegate bitmap) {
213         GcSnapshot snapshot = new GcSnapshot();
214         if (bitmap != null) {
215             snapshot.setBitmap(bitmap);
216         }
217 
218         return snapshot;
219     }
220 
221     /**
222      * Saves the current state according to the given flags and returns the new current snapshot.
223      * <p/>
224      * This is the equivalent of {@link Canvas#save(int)}
225      *
226      * @param flags the save flags.
227      * @return the new snapshot
228      *
229      * @see Canvas#save(int)
230      */
save(int flags)231     public GcSnapshot save(int flags) {
232         return new GcSnapshot(this, null /*layerbounds*/, null /*paint*/, flags);
233     }
234 
235     /**
236      * Saves the current state and creates a new layer, and returns the new current snapshot.
237      * <p/>
238      * This is the equivalent of {@link Canvas#saveLayer(RectF, Paint, int)}
239      *
240      * @param layerBounds the layer bounds
241      * @param paint the Paint information used to blit the layer back into the layers underneath
242      *          upon restore
243      * @param flags the save flags.
244      * @return the new snapshot
245      *
246      * @see Canvas#saveLayer(RectF, Paint, int)
247      */
saveLayer(RectF layerBounds, Paint_Delegate paint, int flags)248     public GcSnapshot saveLayer(RectF layerBounds, Paint_Delegate paint, int flags) {
249         return new GcSnapshot(this, layerBounds, paint, flags);
250     }
251 
252     /**
253      * Creates the root snapshot.
254      */
GcSnapshot()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      */
GcSnapshot(GcSnapshot previous, RectF layerBounds, Paint_Delegate paint, int flags)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 
dispose()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      */
restore()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      */
restoreTo(int saveCount)375     public GcSnapshot restoreTo(int saveCount) {
376         return doRestoreTo(size(), saveCount);
377     }
378 
size()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      */
setBitmap(Bitmap_Delegate bitmap)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 
translate(float dx, float dy)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 
rotate(double radians)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 
scale(float sx, float sy)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 
getTransform()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 
setTransform(AffineTransform transform)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 
clip(Shape shape, int regionOp)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 
clipRect(float left, float top, float right, float bottom, int regionOp)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      */
getClip()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 
doRestoreTo(int size, int saveCount)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      */
draw(Drawable drawable)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      */
draw(Drawable drawable, Paint_Delegate paint, boolean compositeOnly, boolean forceSrcMode)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 
drawInLayer(Layer layer, Drawable drawable, Paint_Delegate paint, boolean compositeOnly, int forceMode)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             Rectangle clipBounds = originalGraphics.getClip() != null ? originalGraphics
620                     .getClipBounds() : null;
621             if (clipBounds != null) {
622                 if (clipBounds.width == 0 || clipBounds.height == 0) {
623                     // Clip is 0 so no need to paint anything.
624                     return;
625                 }
626             }
627 
628             // b/63692596:
629             // Don't use the size of clip bound because it may be smaller than original size.
630             // Which makes Vector Drawable pixelized.
631             int width = layer.getImage().getWidth();
632             int height = layer.getImage().getHeight();
633 
634             // Create a temporary image to which the color filter will be applied.
635             BufferedImage image = new BufferedImage(width, height,
636                     BufferedImage.TYPE_INT_ARGB);
637             Graphics2D imageBaseGraphics = (Graphics2D) image.getGraphics();
638             // Configure the Graphics2D object with drawing parameters and shader.
639             Graphics2D imageGraphics = createCustomGraphics(
640                     imageBaseGraphics, paint, compositeOnly,
641                     AlphaComposite.SRC_OVER);
642 
643             // get a Graphics2D object configured with the drawing parameters, but no shader.
644             Graphics2D configuredGraphics = createCustomGraphics(originalGraphics, paint,
645                     true /*compositeOnly*/, forceMode);
646             configuredGraphics.setTransform(new AffineTransform());
647             try {
648                 // The main draw operation.
649                 // We translate the operation to take into account that the rendering does not
650                 // know about the clipping area.
651                 imageGraphics.setTransform(originalGraphics.getTransform());
652                 drawable.draw(imageGraphics, paint);
653 
654                 // Apply the color filter.
655                 // Restore the original coordinates system and apply the filter only to the
656                 // clipped area.
657                 imageGraphics.setTransform(new AffineTransform());
658                 filter.applyFilter(imageGraphics, width, height);
659 
660                 // Draw the tinted image on the main layer using as start point the clipping
661                 // upper left coordinates.
662                 configuredGraphics.drawImage(image, 0, 0, null);
663                 layer.change();
664             } finally {
665                 // dispose Graphics2D objects
666                 imageGraphics.dispose();
667                 imageBaseGraphics.dispose();
668                 configuredGraphics.dispose();
669             }
670         }
671     }
672 
drawOnGraphics(Graphics2D g, Drawable drawable, Paint_Delegate paint, Layer layer)673     private void drawOnGraphics(Graphics2D g, Drawable drawable, Paint_Delegate paint,
674             Layer layer) {
675         try {
676             drawable.draw(g, paint);
677             layer.change();
678         } finally {
679             g.dispose();
680         }
681     }
682 
doRestore()683     private GcSnapshot doRestore() {
684         if (mPrevious != null) {
685             if (mLocalLayer != null) {
686                 // prepare to blit the layers in which we have draw, in the layer beneath
687                 // them, starting with the top one (which is the current local layer).
688                 int i = mLayers.size() - 1;
689                 int flags;
690                 do {
691                     Layer dstLayer = mLayers.get(i - 1);
692 
693                     restoreLayer(dstLayer);
694 
695                     flags = dstLayer.getFlags();
696                     i--;
697                 } while (i > 0 && (flags & Canvas.CLIP_TO_LAYER_SAVE_FLAG) == 0);
698             }
699 
700             // if this snapshot does not save everything, then set the previous snapshot
701             // to this snapshot content
702 
703             // didn't save the matrix? set the current matrix on the previous snapshot
704             if ((mFlags & Canvas.MATRIX_SAVE_FLAG) == 0) {
705                 AffineTransform mtx = getTransform();
706                 for (Layer layer : mPrevious.mLayers) {
707                     layer.getGraphics().setTransform(mtx);
708                 }
709             }
710 
711             // didn't save the clip? set the current clip on the previous snapshot
712             if ((mFlags & Canvas.CLIP_SAVE_FLAG) == 0) {
713                 Shape clip = getClip();
714                 for (Layer layer : mPrevious.mLayers) {
715                     layer.setClip(clip);
716                 }
717             }
718         }
719 
720         for (Layer layer : mLayers) {
721             layer.getGraphics().dispose();
722         }
723 
724         return mPrevious;
725     }
726 
restoreLayer(Layer dstLayer)727     private void restoreLayer(Layer dstLayer) {
728 
729         Graphics2D baseGfx = dstLayer.getImage().createGraphics();
730 
731         // if the layer contains an original copy this means the flags
732         // didn't restrict drawing to the local layer and we need to make sure the
733         // layer bounds in the layer beneath didn't receive any drawing.
734         // so we use the originalCopy to erase the new drawings in there.
735         BufferedImage originalCopy = dstLayer.getOriginalCopy();
736         if (originalCopy != null) {
737             Graphics2D g = (Graphics2D) baseGfx.create();
738             g.setComposite(AlphaComposite.Src);
739 
740             g.drawImage(originalCopy,
741                     mLayerBounds.left, mLayerBounds.top, mLayerBounds.right, mLayerBounds.bottom,
742                     0, 0, mLayerBounds.width(), mLayerBounds.height(),
743                     null);
744             g.dispose();
745         }
746 
747         // now draw put the content of the local layer onto the layer,
748         // using the paint information
749         Graphics2D g = createCustomGraphics(baseGfx, mLocalLayerPaint,
750                 true /*alphaOnly*/, 0 /*forceMode*/);
751 
752         g.drawImage(mLocalLayer.getImage(),
753                 mLayerBounds.left, mLayerBounds.top, mLayerBounds.right, mLayerBounds.bottom,
754                 mLayerBounds.left, mLayerBounds.top, mLayerBounds.right, mLayerBounds.bottom,
755                 null);
756         g.dispose();
757 
758         baseGfx.dispose();
759     }
760 
761     /**
762      * Creates a new {@link Graphics2D} based on the {@link Paint} parameters.
763      * <p/>The object must be disposed ({@link Graphics2D#dispose()}) after being used.
764      */
createCustomGraphics(Graphics2D original, Paint_Delegate paint, boolean compositeOnly, int forceMode)765     private Graphics2D createCustomGraphics(Graphics2D original, Paint_Delegate paint,
766             boolean compositeOnly, int forceMode) {
767         // make new one graphics
768         Graphics2D g = (Graphics2D) original.create();
769 
770         if (paint == null) {
771             return g;
772         }
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         if (!compositeOnly) {
785             setShader(g, paint);
786             // set the stroke
787             g.setStroke(paint.getJavaStroke());
788         }
789         // set the composite.
790         setComposite(g, paint, compositeOnly, forceMode);
791 
792         return g;
793     }
794 
setShader(Graphics2D g, Paint_Delegate paint)795     private void setShader(Graphics2D g, Paint_Delegate paint) {
796         Shader_Delegate shaderDelegate = paint.getShader();
797         if (shaderDelegate != null) {
798             if (shaderDelegate.isSupported()) {
799                 java.awt.Paint shaderPaint = shaderDelegate.getJavaPaint();
800                 assert shaderPaint != null;
801                 g.setPaint(shaderPaint);
802                 return;
803             } else {
804                 Bridge.getLog().fidelityWarning(LayoutLog.TAG_SHADER,
805                         shaderDelegate.getSupportMessage(), null, null, null);
806             }
807         }
808 
809         // if no shader, use the paint color
810         g.setColor(new Color(paint.getColor(), true /*hasAlpha*/));
811     }
812 
setComposite(Graphics2D g, Paint_Delegate paint, boolean usePaintAlpha, int forceMode)813     private void setComposite(Graphics2D g, Paint_Delegate paint, boolean usePaintAlpha,
814             int forceMode) {
815         // the alpha for the composite. Always opaque if the normal paint color is used since
816         // it contains the alpha
817         int alpha = usePaintAlpha ? paint.getAlpha() : 0xFF;
818         Shader_Delegate shader = paint.getShader();
819         if (shader != null) {
820             alpha = (int)(alpha * shader.getAlpha());
821         }
822         if (forceMode != 0) {
823             g.setComposite(AlphaComposite.getInstance(forceMode, (float) alpha / 255.f));
824             return;
825         }
826         Mode mode = PorterDuff.intToMode(paint.getPorterDuffMode());
827         Composite composite = PorterDuffUtility.getComposite(mode, alpha);
828         g.setComposite(composite);
829     }
830 
mapRect(AffineTransform matrix, RectF dst, RectF src)831     private void mapRect(AffineTransform matrix, RectF dst, RectF src) {
832         // array with 4 corners
833         float[] corners = new float[] {
834                 src.left, src.top,
835                 src.right, src.top,
836                 src.right, src.bottom,
837                 src.left, src.bottom,
838         };
839 
840         // apply the transform to them.
841         matrix.transform(corners, 0, corners, 0, 4);
842 
843         // now put the result in the rect. We take the min/max of Xs and min/max of Ys
844         dst.left = Math.min(Math.min(corners[0], corners[2]), Math.min(corners[4], corners[6]));
845         dst.right = Math.max(Math.max(corners[0], corners[2]), Math.max(corners[4], corners[6]));
846 
847         dst.top = Math.min(Math.min(corners[1], corners[3]), Math.min(corners[5], corners[7]));
848         dst.bottom = Math.max(Math.max(corners[1], corners[3]), Math.max(corners[5], corners[7]));
849     }
850 
851     /**
852      * Returns the clip of the oldest snapshot of the stack, appropriately translated to be
853      * expressed in the coordinate system of the latest snapshot.
854      */
getOriginalClip()855     public Rectangle getOriginalClip() {
856         GcSnapshot originalSnapshot = this;
857         while (originalSnapshot.mPrevious != null) {
858             originalSnapshot = originalSnapshot.mPrevious;
859         }
860         if (originalSnapshot.mLayers.isEmpty()) {
861             return null;
862         }
863         Graphics2D graphics2D = originalSnapshot.mLayers.get(0).getGraphics();
864         Rectangle bounds = graphics2D.getClipBounds();
865         if (bounds == null) {
866             return null;
867         }
868         try {
869             AffineTransform originalTransform =
870                     ((Graphics2D) graphics2D.create()).getTransform().createInverse();
871             AffineTransform latestTransform = getTransform().createInverse();
872             bounds.x += latestTransform.getTranslateX() - originalTransform.getTranslateX();
873             bounds.y += latestTransform.getTranslateY() - originalTransform.getTranslateY();
874         } catch (NoninvertibleTransformException e) {
875             Bridge.getLog().warning(null, "Non invertible transformation", null);
876         }
877         return bounds;
878     }
879 
880 }
881