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