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