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