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