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