GcSnapshot.java revision a7cac5e0542779cadf0f5ccf71584e4b4425f7a6
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.layoutlib.bridge.Bridge;
20
21import android.graphics.Bitmap_Delegate;
22import android.graphics.Canvas;
23import android.graphics.Paint;
24import android.graphics.Paint_Delegate;
25import android.graphics.Rect;
26import android.graphics.RectF;
27import android.graphics.Region;
28import android.graphics.Region_Delegate;
29import android.graphics.Shader_Delegate;
30import android.graphics.Xfermode_Delegate;
31
32import java.awt.AlphaComposite;
33import java.awt.Color;
34import java.awt.Composite;
35import java.awt.Graphics2D;
36import java.awt.RenderingHints;
37import java.awt.Shape;
38import java.awt.geom.AffineTransform;
39import java.awt.geom.Area;
40import java.awt.geom.Rectangle2D;
41import java.awt.image.BufferedImage;
42import java.util.ArrayList;
43
44/**
45 * Class representing a graphics context snapshot, as well as a context stack as a linked list.
46 * <p>
47 * This is based on top of {@link Graphics2D} but can operate independently if none are available
48 * yet when setting transforms and clip information.
49 * <p>
50 * This allows for drawing through {@link #draw(Drawable, Paint_Delegate)} and
51 * {@link #draw(Drawable, Paint_Delegate)}
52 *
53 * Handling of layers (created with {@link Canvas#saveLayer(RectF, Paint, int)}) is handled through
54 * a list of Graphics2D for each layers. The class actually maintains a list of {@link Layer}
55 * for each layer. Doing a save() will duplicate this list so that each graphics2D object
56 * ({@link Layer#getGraphics()}) is configured only for the new snapshot.
57 */
58public class GcSnapshot {
59
60    private final GcSnapshot mPrevious;
61    private final int mFlags;
62
63    /** list of layers. The first item in the list is always the  */
64    private final ArrayList<Layer> mLayers = new ArrayList<Layer>();
65
66    /** temp transform in case transformation are set before a Graphics2D exists */
67    private AffineTransform mTransform = null;
68    /** temp clip in case clipping is set before a Graphics2D exists */
69    private Area mClip = null;
70
71    // local layer data
72    /** a local layer created with {@link Canvas#saveLayer(RectF, Paint, int)}.
73     * If this is null, this does not mean there's no layer, just that the snapshot is not the
74     * one that created the layer.
75     * @see #getLayerSnapshot()
76     */
77    private final Layer mLocalLayer;
78    private final Paint_Delegate mLocalLayerPaint;
79    private final Rect mLayerBounds;
80
81    public interface Drawable {
82        void draw(Graphics2D graphics, Paint_Delegate paint);
83    }
84
85    /**
86     * Class containing information about a layer.
87     *
88     * This contains graphics, bitmap and layer information.
89     */
90    private static class Layer {
91        private final Graphics2D mGraphics;
92        private final Bitmap_Delegate mBitmap;
93        private final BufferedImage mImage;
94        /** the flags that were used to configure the layer. This is never changed, and passed
95         * as is when {@link #makeCopy()} is called */
96        private final int mFlags;
97        /** the original content of the layer when the next object was created. This is not
98         * passed in {@link #makeCopy()} and instead is recreated when a new layer is added
99         * (depending on its flags) */
100        private BufferedImage mOriginalCopy;
101
102        /**
103         * Creates a layer with a graphics and a bitmap. This is only used to create
104         * the base layer.
105         *
106         * @param graphics the graphics
107         * @param bitmap the bitmap
108         */
109        Layer(Graphics2D graphics, Bitmap_Delegate bitmap) {
110            mGraphics = graphics;
111            mBitmap = bitmap;
112            mImage = mBitmap.getImage();
113            mFlags = 0;
114        }
115
116        /**
117         * Creates a layer with a graphics and an image. If the image belongs to a
118         * {@link Bitmap_Delegate} (case of the base layer), then
119         * {@link Layer#Layer(Graphics2D, Bitmap_Delegate)} should be used.
120         *
121         * @param graphics the graphics the new graphics for this layer
122         * @param image the image the image from which the graphics came
123         * @param flags the flags that were used to save this layer
124         */
125        Layer(Graphics2D graphics, BufferedImage image, int flags) {
126            mGraphics = graphics;
127            mBitmap = null;
128            mImage = image;
129            mFlags = flags;
130        }
131
132        /** The Graphics2D, guaranteed to be non null */
133        Graphics2D getGraphics() {
134            return mGraphics;
135        }
136
137        /** The BufferedImage, guaranteed to be non null */
138        BufferedImage getImage() {
139            return mImage;
140        }
141
142        /** Returns the layer save flags. This is only valid for additional layers.
143         * For the base layer this will always return 0;
144         * For a given layer, all further copies of this {@link Layer} object in new snapshots
145         * will always return the same value.
146         */
147        int getFlags() {
148            return mFlags;
149        }
150
151        Layer makeCopy() {
152            if (mBitmap != null) {
153                return new Layer((Graphics2D) mGraphics.create(), mBitmap);
154            }
155
156            return new Layer((Graphics2D) mGraphics.create(), mImage, mFlags);
157        }
158
159        /** sets an optional copy of the original content to be used during restore */
160        void setOriginalCopy(BufferedImage image) {
161            mOriginalCopy = image;
162        }
163
164        BufferedImage getOriginalCopy() {
165            return mOriginalCopy;
166        }
167
168        void change() {
169            if (mBitmap != null) {
170                mBitmap.change();
171            }
172        }
173
174        /**
175         * Sets the clip for the graphics2D object associated with the layer.
176         * This should be used over the normal Graphics2D setClip method.
177         *
178         * @param clipShape the shape to use a the clip shape.
179         */
180        void setClip(Shape clipShape) {
181            // because setClip is only guaranteed to work with rectangle shape,
182            // first reset the clip to max and then intersect the current (empty)
183            // clip with the shap.
184            mGraphics.setClip(null);
185            mGraphics.clip(clipShape);
186        }
187
188        /**
189         * Clips the layer with the given shape. This performs an intersect between the current
190         * clip shape and the given shape.
191         * @param shape the new clip shape.
192         */
193        public void clip(Shape shape) {
194            mGraphics.clip(shape);
195        }
196    }
197
198    /**
199     * Creates the root snapshot associating it with a given bitmap.
200     * <p>
201     * If <var>bitmap</var> is null, then {@link GcSnapshot#setBitmap(Bitmap_Delegate)} must be
202     * called before the snapshot can be used to draw. Transform and clip operations are permitted
203     * before.
204     *
205     * @param image the image to associate to the snapshot or null.
206     * @return the root snapshot
207     */
208    public static GcSnapshot createDefaultSnapshot(Bitmap_Delegate bitmap) {
209        GcSnapshot snapshot = new GcSnapshot();
210        if (bitmap != null) {
211            snapshot.setBitmap(bitmap);
212        }
213
214        return snapshot;
215    }
216
217    /**
218     * Saves the current state according to the given flags and returns the new current snapshot.
219     * <p/>
220     * This is the equivalent of {@link Canvas#save(int)}
221     *
222     * @param flags the save flags.
223     * @return the new snapshot
224     *
225     * @see Canvas#save(int)
226     */
227    public GcSnapshot save(int flags) {
228        return new GcSnapshot(this, null /*layerbounds*/, null /*paint*/, flags);
229    }
230
231    /**
232     * Saves the current state and creates a new layer, and returns the new current snapshot.
233     * <p/>
234     * This is the equivalent of {@link Canvas#saveLayer(RectF, Paint, int)}
235     *
236     * @param layerBounds the layer bounds
237     * @param paint the Paint information used to blit the layer back into the layers underneath
238     *          upon restore
239     * @param flags the save flags.
240     * @return the new snapshot
241     *
242     * @see Canvas#saveLayer(RectF, Paint, int)
243     */
244    public GcSnapshot saveLayer(RectF layerBounds, Paint_Delegate paint, int flags) {
245        return new GcSnapshot(this, layerBounds, paint, flags);
246    }
247
248    /**
249     * Creates the root snapshot.
250     * {@link #setGraphics2D(Graphics2D)} will have to be called on it when possible.
251     */
252    private GcSnapshot() {
253        mPrevious = null;
254        mFlags = 0;
255        mLocalLayer = null;
256        mLocalLayerPaint = null;
257        mLayerBounds = null;
258    }
259
260    /**
261     * Creates a new {@link GcSnapshot} on top of another one, with a layer data to be restored
262     * into the main graphics when {@link #restore()} is called.
263     *
264     * @param previous the previous snapshot head.
265     * @param layerBounds the region of the layer. Optional, if null, this is a normal save()
266     * @param paint the Paint information used to blit the layer back into the layers underneath
267     *          upon restore
268     * @param flags the flags regarding what should be saved.
269     */
270    private GcSnapshot(GcSnapshot previous, RectF layerBounds, Paint_Delegate paint, int flags) {
271        assert previous != null;
272        mPrevious = previous;
273        mFlags = flags;
274
275        // make a copy of the current layers before adding the new one.
276        // This keeps the same BufferedImage reference but creates new Graphics2D for this
277        // snapshot.
278        // It does not copy whatever original copy the layers have, as they will be done
279        // only if the new layer doesn't clip drawing to itself.
280        for (Layer layer : mPrevious.mLayers) {
281            mLayers.add(layer.makeCopy());
282        }
283
284        if (layerBounds != null) {
285            // get the current transform
286            AffineTransform matrix = mLayers.get(0).getGraphics().getTransform();
287
288            // transform the layerBounds with the current transform and stores it into a int rect
289            RectF rect2 = new RectF();
290            mapRect(matrix, rect2, layerBounds);
291            mLayerBounds = new Rect();
292            rect2.round(mLayerBounds);
293
294            // get the base layer (always at index 0)
295            Layer baseLayer = mLayers.get(0);
296
297            // create the image for the layer
298            BufferedImage layerImage = new BufferedImage(
299                    baseLayer.getImage().getWidth(),
300                    baseLayer.getImage().getHeight(),
301                    (mFlags & Canvas.HAS_ALPHA_LAYER_SAVE_FLAG) != 0 ?
302                            BufferedImage.TYPE_INT_ARGB :
303                                BufferedImage.TYPE_INT_RGB);
304
305            // create a graphics for it so that drawing can be done.
306            Graphics2D layerGraphics = layerImage.createGraphics();
307
308            // because this layer inherits the current context for transform and clip,
309            // set them to one from the base layer.
310            AffineTransform currentMtx = baseLayer.getGraphics().getTransform();
311            layerGraphics.setTransform(currentMtx);
312
313            // create a new layer for this new layer and add it to the list at the end.
314            mLayers.add(mLocalLayer = new Layer(layerGraphics, layerImage, flags));
315
316            // set the clip on it.
317            Shape currentClip = baseLayer.getGraphics().getClip();
318            mLocalLayer.setClip(currentClip);
319
320            // if the drawing is not clipped to the local layer only, we save the current content
321            // of all other layers. We are only interested in the part that will actually
322            // be drawn, so we create as small bitmaps as we can.
323            // This is so that we can erase the drawing that goes in the layers below that will
324            // be coming from the layer itself.
325            if ((mFlags & Canvas.CLIP_TO_LAYER_SAVE_FLAG) == 0) {
326                int w = mLayerBounds.width();
327                int h = mLayerBounds.height();
328                for (int i = 0 ; i < mLayers.size() - 1 ; i++) {
329                    Layer layer = mLayers.get(i);
330                    BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
331                    Graphics2D graphics = image.createGraphics();
332                    graphics.drawImage(layer.getImage(),
333                            0, 0, w, h,
334                            mLayerBounds.left, mLayerBounds.top,
335                                    mLayerBounds.right, mLayerBounds.bottom,
336                            null);
337                    graphics.dispose();
338                    layer.setOriginalCopy(image);
339                }
340            }
341        } else {
342            mLocalLayer = null;
343            mLayerBounds = null;
344        }
345
346        mLocalLayerPaint  = paint;
347    }
348
349    public void dispose() {
350        for (Layer layer : mLayers) {
351            layer.getGraphics().dispose();
352        }
353
354        if (mPrevious != null) {
355            mPrevious.dispose();
356        }
357    }
358
359    /**
360     * Restores the top {@link GcSnapshot}, and returns the next one.
361     */
362    public GcSnapshot restore() {
363        return doRestore();
364    }
365
366    /**
367     * Restores the {@link GcSnapshot} to <var>saveCount</var>.
368     * @param saveCount the saveCount or -1 to only restore 1.
369     *
370     * @return the new head of the Gc snapshot stack.
371     */
372    public GcSnapshot restoreTo(int saveCount) {
373        return doRestoreTo(size(), saveCount);
374    }
375
376    public int size() {
377        if (mPrevious != null) {
378            return mPrevious.size() + 1;
379        }
380
381        return 1;
382    }
383
384    /**
385     * Link the snapshot to a Bitmap_Delegate.
386     * <p/>
387     * This is only for the case where the snapshot was created with a null image when calling
388     * {@link #createDefaultSnapshot(Bitmap_Delegate)}, and is therefore not yet linked to
389     * a previous snapshot.
390     * <p/>
391     * If any transform or clip information was set before, they are put into the Graphics object.
392     * @param bitmap the bitmap to link to.
393     */
394    public void setBitmap(Bitmap_Delegate bitmap) {
395        assert mLayers.size() == 0;
396
397        // create a new Layer for the bitmap. This will be the base layer.
398        Graphics2D graphics2D = bitmap.getImage().createGraphics();
399        Layer baseLayer = new Layer(graphics2D, bitmap);
400
401        // add it to the list.
402        mLayers.add(baseLayer);
403
404        // if transform and clip where modified before, get the information and give it to the
405        // layer.
406
407        if (mTransform != null) {
408            graphics2D.setTransform(mTransform);
409            mTransform = null;
410        }
411
412        if (mClip != null) {
413            baseLayer.setClip(mClip);
414            mClip = null;
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(null,
732                            shaderDelegate.getSupportMessage(),
733                            null);
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(null,
766                            xfermodeDelegate.getSupportMessage(),
767                            null);
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