OSDN Git Service

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