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
17package com.android.layoutlib.bridge.impl;
18
19import com.android.ide.common.rendering.api.LayoutLog;
20import com.android.layoutlib.bridge.Bridge;
21
22import android.graphics.Bitmap_Delegate;
23import android.graphics.Canvas;
24import android.graphics.Paint;
25import android.graphics.Paint_Delegate;
26import android.graphics.Rect;
27import android.graphics.RectF;
28import android.graphics.Region;
29import android.graphics.Region_Delegate;
30import android.graphics.Shader_Delegate;
31import android.graphics.Xfermode_Delegate;
32
33import java.awt.AlphaComposite;
34import java.awt.Color;
35import java.awt.Composite;
36import java.awt.Graphics2D;
37import java.awt.RenderingHints;
38import java.awt.Shape;
39import java.awt.geom.AffineTransform;
40import java.awt.geom.Area;
41import java.awt.geom.Rectangle2D;
42import java.awt.image.BufferedImage;
43import 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 */
59public 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        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         */
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         */
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     */
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     */
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     */
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     */
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     */
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
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     */
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     */
373    public GcSnapshot restoreTo(int saveCount) {
374        return doRestoreTo(size(), saveCount);
375    }
376
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     */
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
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
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
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
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
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
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
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     */
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
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     */
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     */
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
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
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
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     */
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
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