GcSnapshot.java revision 918aaa5717fce6081557c82ce1c439b6922737d5
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        assert mLayers.size() == 0;
397
398        // create a new Layer for the bitmap. This will be the base layer.
399        Graphics2D graphics2D = bitmap.getImage().createGraphics();
400        Layer baseLayer = new Layer(graphics2D, bitmap);
401
402        // add it to the list.
403        mLayers.add(baseLayer);
404
405        // if transform and clip where modified before, get the information and give it to the
406        // layer.
407
408        if (mTransform != null) {
409            graphics2D.setTransform(mTransform);
410            mTransform = null;
411        }
412
413        if (mClip != null) {
414            baseLayer.setClip(mClip);
415            mClip = null;
416        }
417    }
418
419    public void translate(float dx, float dy) {
420        if (mLayers.size() > 0) {
421            for (Layer layer : mLayers) {
422                layer.getGraphics().translate(dx, dy);
423            }
424        } else {
425            if (mTransform == null) {
426                mTransform = new AffineTransform();
427            }
428            mTransform.translate(dx, dy);
429        }
430    }
431
432    public void rotate(double radians) {
433        if (mLayers.size() > 0) {
434            for (Layer layer : mLayers) {
435                layer.getGraphics().rotate(radians);
436            }
437        } else {
438            if (mTransform == null) {
439                mTransform = new AffineTransform();
440            }
441            mTransform.rotate(radians);
442        }
443    }
444
445    public void scale(float sx, float sy) {
446        if (mLayers.size() > 0) {
447            for (Layer layer : mLayers) {
448                layer.getGraphics().scale(sx, sy);
449            }
450        } else {
451            if (mTransform == null) {
452                mTransform = new AffineTransform();
453            }
454            mTransform.scale(sx, sy);
455        }
456    }
457
458    public AffineTransform getTransform() {
459        if (mLayers.size() > 0) {
460            // all graphics2D in the list have the same transform
461            return mLayers.get(0).getGraphics().getTransform();
462        } else {
463            if (mTransform == null) {
464                mTransform = new AffineTransform();
465            }
466            return mTransform;
467        }
468    }
469
470    public void setTransform(AffineTransform transform) {
471        if (mLayers.size() > 0) {
472            for (Layer layer : mLayers) {
473                layer.getGraphics().setTransform(transform);
474            }
475        } else {
476            if (mTransform == null) {
477                mTransform = new AffineTransform();
478            }
479            mTransform.setTransform(transform);
480        }
481    }
482
483    public boolean clip(Shape shape, int regionOp) {
484        // Simple case of intersect with existing layers.
485        // Because Graphics2D#setClip works a bit peculiarly, we optimize
486        // the case of clipping by intersection, as it's supported natively.
487        if (regionOp == Region.Op.INTERSECT.nativeInt && mLayers.size() > 0) {
488            for (Layer layer : mLayers) {
489                layer.clip(shape);
490            }
491
492            Shape currentClip = getClip();
493            return currentClip != null && currentClip.getBounds().isEmpty() == false;
494        }
495
496        Area area = null;
497
498        if (regionOp == Region.Op.REPLACE.nativeInt) {
499            area = new Area(shape);
500        } else {
501            area = Region_Delegate.combineShapes(getClip(), shape, regionOp);
502        }
503
504        assert area != null;
505
506        if (mLayers.size() > 0) {
507            if (area != null) {
508                for (Layer layer : mLayers) {
509                    layer.setClip(area);
510                }
511            }
512
513            Shape currentClip = getClip();
514            return currentClip != null && currentClip.getBounds().isEmpty() == false;
515        } else {
516            if (area != null) {
517                mClip = area;
518            } else {
519                mClip = new Area();
520            }
521
522            return mClip.getBounds().isEmpty() == false;
523        }
524    }
525
526    public boolean clipRect(float left, float top, float right, float bottom, int regionOp) {
527        return clip(new Rectangle2D.Float(left, top, right - left, bottom - top), regionOp);
528    }
529
530    /**
531     * Returns the current clip, or null if none have been setup.
532     */
533    public Shape getClip() {
534        if (mLayers.size() > 0) {
535            // they all have the same clip
536            return mLayers.get(0).getGraphics().getClip();
537        } else {
538            return mClip;
539        }
540    }
541
542    private GcSnapshot doRestoreTo(int size, int saveCount) {
543        if (size <= saveCount) {
544            return this;
545        }
546
547        // restore the current one first.
548        GcSnapshot previous = doRestore();
549
550        if (size == saveCount + 1) { // this was the only one that needed restore.
551            return previous;
552        } else {
553            return previous.doRestoreTo(size - 1, saveCount);
554        }
555    }
556
557    /**
558     * Executes the Drawable's draw method, with a null paint delegate.
559     * <p/>
560     * Note that the method can be called several times if there are more than one active layer.
561     * @param drawable
562     */
563    public void draw(Drawable drawable) {
564        draw(drawable, null, false /*compositeOnly*/, false /*forceSrcMode*/);
565    }
566
567    /**
568     * Executes the Drawable's draw method.
569     * <p/>
570     * Note that the method can be called several times if there are more than one active layer.
571     * @param drawable
572     * @param paint
573     * @param compositeOnly whether the paint is used for composite only. This is typically
574     *          the case for bitmaps.
575     * @param forceSrcMode if true, this overrides the composite to be SRC
576     */
577    public void draw(Drawable drawable, Paint_Delegate paint, boolean compositeOnly,
578            boolean forceSrcMode) {
579        // the current snapshot may not have a mLocalLayer (ie it was created on save() instead
580        // of saveLayer(), but that doesn't mean there's no layer.
581        // mLayers however saves all the information we need (flags).
582        if (mLayers.size() == 1) {
583            // no layer, only base layer. easy case.
584            drawInLayer(mLayers.get(0), drawable, paint, compositeOnly, forceSrcMode);
585        } else {
586            // draw in all the layers until the layer save flags tells us to stop (ie drawing
587            // in that layer is limited to the layer itself.
588            int flags;
589            int i = mLayers.size() - 1;
590
591            do {
592                Layer layer = mLayers.get(i);
593
594                drawInLayer(layer, drawable, paint, compositeOnly, forceSrcMode);
595
596                // then go to previous layer, only if there are any left, and its flags
597                // doesn't restrict drawing to the layer itself.
598                i--;
599                flags = layer.getFlags();
600            } while (i >= 0 && (flags & Canvas.CLIP_TO_LAYER_SAVE_FLAG) == 0);
601        }
602    }
603
604    private void drawInLayer(Layer layer, Drawable drawable, Paint_Delegate paint,
605            boolean compositeOnly, boolean forceSrcMode) {
606        Graphics2D originalGraphics = layer.getGraphics();
607        // get a Graphics2D object configured with the drawing parameters.
608        Graphics2D configuredGraphics2D =
609            paint != null ?
610                    createCustomGraphics(originalGraphics, paint, compositeOnly, forceSrcMode) :
611                        (Graphics2D) originalGraphics.create();
612
613        try {
614            drawable.draw(configuredGraphics2D, paint);
615            layer.change();
616        } finally {
617            // dispose Graphics2D object
618            configuredGraphics2D.dispose();
619        }
620    }
621
622    private GcSnapshot doRestore() {
623        if (mPrevious != null) {
624            if (mLocalLayer != null) {
625                // prepare to blit the layers in which we have draw, in the layer beneath
626                // them, starting with the top one (which is the current local layer).
627                int i = mLayers.size() - 1;
628                int flags;
629                do {
630                    Layer dstLayer = mLayers.get(i - 1);
631
632                    restoreLayer(dstLayer);
633
634                    flags = dstLayer.getFlags();
635                    i--;
636                } while (i > 0 && (flags & Canvas.CLIP_TO_LAYER_SAVE_FLAG) == 0);
637            }
638
639            // if this snapshot does not save everything, then set the previous snapshot
640            // to this snapshot content
641
642            // didn't save the matrix? set the current matrix on the previous snapshot
643            if ((mFlags & Canvas.MATRIX_SAVE_FLAG) == 0) {
644                AffineTransform mtx = getTransform();
645                for (Layer layer : mPrevious.mLayers) {
646                    layer.getGraphics().setTransform(mtx);
647                }
648            }
649
650            // didn't save the clip? set the current clip on the previous snapshot
651            if ((mFlags & Canvas.CLIP_SAVE_FLAG) == 0) {
652                Shape clip = getClip();
653                for (Layer layer : mPrevious.mLayers) {
654                    layer.setClip(clip);
655                }
656            }
657        }
658
659        for (Layer layer : mLayers) {
660            layer.getGraphics().dispose();
661        }
662
663        return mPrevious;
664    }
665
666    private void restoreLayer(Layer dstLayer) {
667
668        Graphics2D baseGfx = dstLayer.getImage().createGraphics();
669
670        // if the layer contains an original copy this means the flags
671        // didn't restrict drawing to the local layer and we need to make sure the
672        // layer bounds in the layer beneath didn't receive any drawing.
673        // so we use the originalCopy to erase the new drawings in there.
674        BufferedImage originalCopy = dstLayer.getOriginalCopy();
675        if (originalCopy != null) {
676            Graphics2D g = (Graphics2D) baseGfx.create();
677            g.setComposite(AlphaComposite.Src);
678
679            g.drawImage(originalCopy,
680                    mLayerBounds.left, mLayerBounds.top, mLayerBounds.right, mLayerBounds.bottom,
681                    0, 0, mLayerBounds.width(), mLayerBounds.height(),
682                    null);
683            g.dispose();
684        }
685
686        // now draw put the content of the local layer onto the layer,
687        // using the paint information
688        Graphics2D g = createCustomGraphics(baseGfx, mLocalLayerPaint,
689                true /*alphaOnly*/, false /*forceSrcMode*/);
690
691        g.drawImage(mLocalLayer.getImage(),
692                mLayerBounds.left, mLayerBounds.top, mLayerBounds.right, mLayerBounds.bottom,
693                mLayerBounds.left, mLayerBounds.top, mLayerBounds.right, mLayerBounds.bottom,
694                null);
695        g.dispose();
696
697        baseGfx.dispose();
698    }
699
700    /**
701     * Creates a new {@link Graphics2D} based on the {@link Paint} parameters.
702     * <p/>The object must be disposed ({@link Graphics2D#dispose()}) after being used.
703     */
704    private Graphics2D createCustomGraphics(Graphics2D original, Paint_Delegate paint,
705            boolean compositeOnly, boolean forceSrcMode) {
706        // make new one graphics
707        Graphics2D g = (Graphics2D) original.create();
708
709        // configure it
710
711        if (paint.isAntiAliased()) {
712            g.setRenderingHint(
713                    RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
714            g.setRenderingHint(
715                    RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
716        }
717
718        boolean customShader = false;
719
720        // get the shader first, as it'll replace the color if it can be used it.
721        if (compositeOnly == false) {
722            Shader_Delegate shaderDelegate = paint.getShader();
723            if (shaderDelegate != null) {
724                if (shaderDelegate.isSupported()) {
725                    java.awt.Paint shaderPaint = shaderDelegate.getJavaPaint();
726                    assert shaderPaint != null;
727                    if (shaderPaint != null) {
728                        g.setPaint(shaderPaint);
729                        customShader = true;
730                    }
731                } else {
732                    Bridge.getLog().fidelityWarning(LayoutLog.TAG_SHADER,
733                            shaderDelegate.getSupportMessage(),
734                            null);
735                }
736            }
737
738            // if no shader, use the paint color
739            if (customShader == false) {
740                g.setColor(new Color(paint.getColor(), true /*hasAlpha*/));
741            }
742
743            // set the stroke
744            g.setStroke(paint.getJavaStroke());
745        }
746
747        // the alpha for the composite. Always opaque if the normal paint color is used since
748        // it contains the alpha
749        int alpha = (compositeOnly || customShader) ? paint.getAlpha() : 0xFF;
750
751        if (forceSrcMode) {
752            g.setComposite(AlphaComposite.getInstance(
753                    AlphaComposite.SRC, (float) alpha / 255.f));
754        } else {
755            boolean customXfermode = false;
756            Xfermode_Delegate xfermodeDelegate = paint.getXfermode();
757            if (xfermodeDelegate != null) {
758                if (xfermodeDelegate.isSupported()) {
759                    Composite composite = xfermodeDelegate.getComposite(alpha);
760                    assert composite != null;
761                    if (composite != null) {
762                        g.setComposite(composite);
763                        customXfermode = true;
764                    }
765                } else {
766                    Bridge.getLog().fidelityWarning(LayoutLog.TAG_XFERMODE,
767                            xfermodeDelegate.getSupportMessage(),
768                            null);
769                }
770            }
771
772            // if there was no custom xfermode, but we have alpha (due to a shader and a non
773            // opaque alpha channel in the paint color), then we create an AlphaComposite anyway
774            // that will handle the alpha.
775            if (customXfermode == false && alpha != 0xFF) {
776                g.setComposite(AlphaComposite.getInstance(
777                        AlphaComposite.SRC_OVER, (float) alpha / 255.f));
778            }
779        }
780
781        return g;
782    }
783
784    private void mapRect(AffineTransform matrix, RectF dst, RectF src) {
785        // array with 4 corners
786        float[] corners = new float[] {
787                src.left, src.top,
788                src.right, src.top,
789                src.right, src.bottom,
790                src.left, src.bottom,
791        };
792
793        // apply the transform to them.
794        matrix.transform(corners, 0, corners, 0, 4);
795
796        // now put the result in the rect. We take the min/max of Xs and min/max of Ys
797        dst.left = Math.min(Math.min(corners[0], corners[2]), Math.min(corners[4], corners[6]));
798        dst.right = Math.max(Math.max(corners[0], corners[2]), Math.max(corners[4], corners[6]));
799
800        dst.top = Math.min(Math.min(corners[1], corners[3]), Math.min(corners[5], corners[7]));
801        dst.bottom = Math.max(Math.max(corners[1], corners[3]), Math.max(corners[5], corners[7]));
802    }
803
804}
805