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.ColorFilter_Delegate;
25import android.graphics.Paint;
26import android.graphics.Paint_Delegate;
27import android.graphics.Rect;
28import android.graphics.RectF;
29import android.graphics.Region;
30import android.graphics.Region_Delegate;
31import android.graphics.Shader_Delegate;
32import android.graphics.Xfermode_Delegate;
33
34import java.awt.AlphaComposite;
35import java.awt.Color;
36import java.awt.Composite;
37import java.awt.Graphics2D;
38import java.awt.RenderingHints;
39import java.awt.Shape;
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, boolean, boolean)} and
53 * {@link #draw(Drawable)}
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         * Sets the clip for the graphics2D object associated with the layer.
178         * This should be used over the normal Graphics2D setClip method.
179         *
180         * @param clipShape the shape to use a the clip shape.
181         */
182        void setClip(Shape clipShape) {
183            // because setClip is only guaranteed to work with rectangle shape,
184            // first reset the clip to max and then intersect the current (empty)
185            // clip with the shap.
186            mGraphics.setClip(null);
187            mGraphics.clip(clipShape);
188        }
189
190        /**
191         * Clips the layer with the given shape. This performs an intersect between the current
192         * clip shape and the given shape.
193         * @param shape the new clip shape.
194         */
195        public void clip(Shape shape) {
196            mGraphics.clip(shape);
197        }
198    }
199
200    /**
201     * Creates the root snapshot associating it with a given bitmap.
202     * <p>
203     * If <var>bitmap</var> is null, then {@link GcSnapshot#setBitmap(Bitmap_Delegate)} must be
204     * called before the snapshot can be used to draw. Transform and clip operations are permitted
205     * before.
206     *
207     * @param bitmap the image to associate to the snapshot or null.
208     * @return the root snapshot
209     */
210    public static GcSnapshot createDefaultSnapshot(Bitmap_Delegate bitmap) {
211        GcSnapshot snapshot = new GcSnapshot();
212        if (bitmap != null) {
213            snapshot.setBitmap(bitmap);
214        }
215
216        return snapshot;
217    }
218
219    /**
220     * Saves the current state according to the given flags and returns the new current snapshot.
221     * <p/>
222     * This is the equivalent of {@link Canvas#save(int)}
223     *
224     * @param flags the save flags.
225     * @return the new snapshot
226     *
227     * @see Canvas#save(int)
228     */
229    public GcSnapshot save(int flags) {
230        return new GcSnapshot(this, null /*layerbounds*/, null /*paint*/, flags);
231    }
232
233    /**
234     * Saves the current state and creates a new layer, and returns the new current snapshot.
235     * <p/>
236     * This is the equivalent of {@link Canvas#saveLayer(RectF, Paint, int)}
237     *
238     * @param layerBounds the layer bounds
239     * @param paint the Paint information used to blit the layer back into the layers underneath
240     *          upon restore
241     * @param flags the save flags.
242     * @return the new snapshot
243     *
244     * @see Canvas#saveLayer(RectF, Paint, int)
245     */
246    public GcSnapshot saveLayer(RectF layerBounds, Paint_Delegate paint, int flags) {
247        return new GcSnapshot(this, layerBounds, paint, flags);
248    }
249
250    /**
251     * Creates the root snapshot.
252     * {@link #setGraphics2D(Graphics2D)} will have to be called on it when possible.
253     */
254    private GcSnapshot() {
255        mPrevious = null;
256        mFlags = 0;
257        mLocalLayer = null;
258        mLocalLayerPaint = null;
259        mLayerBounds = null;
260    }
261
262    /**
263     * Creates a new {@link GcSnapshot} on top of another one, with a layer data to be restored
264     * into the main graphics when {@link #restore()} is called.
265     *
266     * @param previous the previous snapshot head.
267     * @param layerBounds the region of the layer. Optional, if null, this is a normal save()
268     * @param paint the Paint information used to blit the layer back into the layers underneath
269     *          upon restore
270     * @param flags the flags regarding what should be saved.
271     */
272    private GcSnapshot(GcSnapshot previous, RectF layerBounds, Paint_Delegate paint, int flags) {
273        assert previous != null;
274        mPrevious = previous;
275        mFlags = flags;
276
277        // make a copy of the current layers before adding the new one.
278        // This keeps the same BufferedImage reference but creates new Graphics2D for this
279        // snapshot.
280        // It does not copy whatever original copy the layers have, as they will be done
281        // only if the new layer doesn't clip drawing to itself.
282        for (Layer layer : mPrevious.mLayers) {
283            mLayers.add(layer.makeCopy());
284        }
285
286        if (layerBounds != null) {
287            // get the current transform
288            AffineTransform matrix = mLayers.get(0).getGraphics().getTransform();
289
290            // transform the layerBounds with the current transform and stores it into a int rect
291            RectF rect2 = new RectF();
292            mapRect(matrix, rect2, layerBounds);
293            mLayerBounds = new Rect();
294            rect2.round(mLayerBounds);
295
296            // get the base layer (always at index 0)
297            Layer baseLayer = mLayers.get(0);
298
299            // create the image for the layer
300            BufferedImage layerImage = new BufferedImage(
301                    baseLayer.getImage().getWidth(),
302                    baseLayer.getImage().getHeight(),
303                    (mFlags & Canvas.HAS_ALPHA_LAYER_SAVE_FLAG) != 0 ?
304                            BufferedImage.TYPE_INT_ARGB :
305                                BufferedImage.TYPE_INT_RGB);
306
307            // create a graphics for it so that drawing can be done.
308            Graphics2D layerGraphics = layerImage.createGraphics();
309
310            // because this layer inherits the current context for transform and clip,
311            // set them to one from the base layer.
312            AffineTransform currentMtx = baseLayer.getGraphics().getTransform();
313            layerGraphics.setTransform(currentMtx);
314
315            // create a new layer for this new layer and add it to the list at the end.
316            mLayers.add(mLocalLayer = new Layer(layerGraphics, layerImage, flags));
317
318            // set the clip on it.
319            Shape currentClip = baseLayer.getGraphics().getClip();
320            mLocalLayer.setClip(currentClip);
321
322            // if the drawing is not clipped to the local layer only, we save the current content
323            // of all other layers. We are only interested in the part that will actually
324            // be drawn, so we create as small bitmaps as we can.
325            // This is so that we can erase the drawing that goes in the layers below that will
326            // be coming from the layer itself.
327            if ((mFlags & Canvas.CLIP_TO_LAYER_SAVE_FLAG) == 0) {
328                int w = mLayerBounds.width();
329                int h = mLayerBounds.height();
330                for (int i = 0 ; i < mLayers.size() - 1 ; i++) {
331                    Layer layer = mLayers.get(i);
332                    BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
333                    Graphics2D graphics = image.createGraphics();
334                    graphics.drawImage(layer.getImage(),
335                            0, 0, w, h,
336                            mLayerBounds.left, mLayerBounds.top,
337                                    mLayerBounds.right, mLayerBounds.bottom,
338                            null);
339                    graphics.dispose();
340                    layer.setOriginalCopy(image);
341                }
342            }
343        } else {
344            mLocalLayer = null;
345            mLayerBounds = null;
346        }
347
348        mLocalLayerPaint  = paint;
349    }
350
351    public void dispose() {
352        for (Layer layer : mLayers) {
353            layer.getGraphics().dispose();
354        }
355
356        if (mPrevious != null) {
357            mPrevious.dispose();
358        }
359    }
360
361    /**
362     * Restores the top {@link GcSnapshot}, and returns the next one.
363     */
364    public GcSnapshot restore() {
365        return doRestore();
366    }
367
368    /**
369     * Restores the {@link GcSnapshot} to <var>saveCount</var>.
370     * @param saveCount the saveCount or -1 to only restore 1.
371     *
372     * @return the new head of the Gc snapshot stack.
373     */
374    public GcSnapshot restoreTo(int saveCount) {
375        return doRestoreTo(size(), saveCount);
376    }
377
378    public int size() {
379        if (mPrevious != null) {
380            return mPrevious.size() + 1;
381        }
382
383        return 1;
384    }
385
386    /**
387     * Link the snapshot to a Bitmap_Delegate.
388     * <p/>
389     * This is only for the case where the snapshot was created with a null image when calling
390     * {@link #createDefaultSnapshot(Bitmap_Delegate)}, and is therefore not yet linked to
391     * a previous snapshot.
392     * <p/>
393     * If any transform or clip information was set before, they are put into the Graphics object.
394     * @param bitmap the bitmap to link to.
395     */
396    public void setBitmap(Bitmap_Delegate bitmap) {
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        // Set the current transform and clip which can either come from mTransform/mClip if they
402        // were set when there was no bitmap/layers or from the current base layers if there is
403        // one already.
404
405        graphics2D.setTransform(getTransform());
406        // reset mTransform in case there was one.
407        mTransform = null;
408
409        baseLayer.setClip(getClip());
410        // reset mClip in case there was one.
411        mClip = null;
412
413        // replace whatever current layers we have with this.
414        mLayers.clear();
415        mLayers.add(baseLayer);
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     */
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 compositeOnly whether the paint is used for composite only. This is typically
571     *          the case for bitmaps.
572     * @param forceSrcMode if true, this overrides the composite to be SRC
573     */
574    public void draw(Drawable drawable, Paint_Delegate paint, boolean compositeOnly,
575            boolean forceSrcMode) {
576        int forceMode = forceSrcMode ? AlphaComposite.SRC : 0;
577        // the current snapshot may not have a mLocalLayer (ie it was created on save() instead
578        // of saveLayer(), but that doesn't mean there's no layer.
579        // mLayers however saves all the information we need (flags).
580        if (mLayers.size() == 1) {
581            // no layer, only base layer. easy case.
582            drawInLayer(mLayers.get(0), drawable, paint, compositeOnly, forceMode);
583        } else {
584            // draw in all the layers until the layer save flags tells us to stop (ie drawing
585            // in that layer is limited to the layer itself.
586            int flags;
587            int i = mLayers.size() - 1;
588
589            do {
590                Layer layer = mLayers.get(i);
591
592                drawInLayer(layer, drawable, paint, compositeOnly, forceMode);
593
594                // then go to previous layer, only if there are any left, and its flags
595                // doesn't restrict drawing to the layer itself.
596                i--;
597                flags = layer.getFlags();
598            } while (i >= 0 && (flags & Canvas.CLIP_TO_LAYER_SAVE_FLAG) == 0);
599        }
600    }
601
602    private void drawInLayer(Layer layer, Drawable drawable, Paint_Delegate paint,
603            boolean compositeOnly, int forceMode) {
604        Graphics2D originalGraphics = layer.getGraphics();
605        if (paint == null) {
606            drawOnGraphics((Graphics2D) originalGraphics.create(), drawable,
607                    null /*paint*/, layer);
608        } else {
609            ColorFilter_Delegate filter = paint.getColorFilter();
610            if (filter == null || !filter.isSupported()) {
611                // get a Graphics2D object configured with the drawing parameters.
612                Graphics2D configuredGraphics = createCustomGraphics(originalGraphics, paint,
613                        compositeOnly, forceMode);
614                drawOnGraphics(configuredGraphics, drawable, paint, layer);
615                return;
616            }
617
618            int width = layer.getImage().getWidth();
619            int height = layer.getImage().getHeight();
620
621            // Create a temporary image to which the color filter will be applied.
622            BufferedImage image = new BufferedImage(width, height,
623                    BufferedImage.TYPE_INT_ARGB);
624            Graphics2D imageBaseGraphics = (Graphics2D) image.getGraphics();
625            // Configure the Graphics2D object with drawing parameters and shader.
626            Graphics2D imageGraphics = createCustomGraphics(
627                    imageBaseGraphics, paint, compositeOnly,
628                    AlphaComposite.SRC_OVER);
629            // get a Graphics2D object configured with the drawing parameters, but no shader.
630            Graphics2D configuredGraphics = createCustomGraphics(originalGraphics, paint,
631                    true /*compositeOnly*/, forceMode);
632            try {
633                // The main draw operation.
634                drawable.draw(imageGraphics, paint);
635
636                // Apply the color filter.
637                filter.applyFilter(imageGraphics, width, height);
638
639                // Draw the tinted image on the main layer.
640                configuredGraphics.drawImage(image, 0, 0, null);
641                layer.change();
642            } finally {
643                // dispose Graphics2D objects
644                imageGraphics.dispose();
645                imageBaseGraphics.dispose();
646                configuredGraphics.dispose();
647            }
648        }
649    }
650
651    private void drawOnGraphics(Graphics2D g, Drawable drawable, Paint_Delegate paint,
652            Layer layer) {
653        try {
654            drawable.draw(g, paint);
655            layer.change();
656        } finally {
657            g.dispose();
658        }
659    }
660
661    private GcSnapshot doRestore() {
662        if (mPrevious != null) {
663            if (mLocalLayer != null) {
664                // prepare to blit the layers in which we have draw, in the layer beneath
665                // them, starting with the top one (which is the current local layer).
666                int i = mLayers.size() - 1;
667                int flags;
668                do {
669                    Layer dstLayer = mLayers.get(i - 1);
670
671                    restoreLayer(dstLayer);
672
673                    flags = dstLayer.getFlags();
674                    i--;
675                } while (i > 0 && (flags & Canvas.CLIP_TO_LAYER_SAVE_FLAG) == 0);
676            }
677
678            // if this snapshot does not save everything, then set the previous snapshot
679            // to this snapshot content
680
681            // didn't save the matrix? set the current matrix on the previous snapshot
682            if ((mFlags & Canvas.MATRIX_SAVE_FLAG) == 0) {
683                AffineTransform mtx = getTransform();
684                for (Layer layer : mPrevious.mLayers) {
685                    layer.getGraphics().setTransform(mtx);
686                }
687            }
688
689            // didn't save the clip? set the current clip on the previous snapshot
690            if ((mFlags & Canvas.CLIP_SAVE_FLAG) == 0) {
691                Shape clip = getClip();
692                for (Layer layer : mPrevious.mLayers) {
693                    layer.setClip(clip);
694                }
695            }
696        }
697
698        for (Layer layer : mLayers) {
699            layer.getGraphics().dispose();
700        }
701
702        return mPrevious;
703    }
704
705    private void restoreLayer(Layer dstLayer) {
706
707        Graphics2D baseGfx = dstLayer.getImage().createGraphics();
708
709        // if the layer contains an original copy this means the flags
710        // didn't restrict drawing to the local layer and we need to make sure the
711        // layer bounds in the layer beneath didn't receive any drawing.
712        // so we use the originalCopy to erase the new drawings in there.
713        BufferedImage originalCopy = dstLayer.getOriginalCopy();
714        if (originalCopy != null) {
715            Graphics2D g = (Graphics2D) baseGfx.create();
716            g.setComposite(AlphaComposite.Src);
717
718            g.drawImage(originalCopy,
719                    mLayerBounds.left, mLayerBounds.top, mLayerBounds.right, mLayerBounds.bottom,
720                    0, 0, mLayerBounds.width(), mLayerBounds.height(),
721                    null);
722            g.dispose();
723        }
724
725        // now draw put the content of the local layer onto the layer,
726        // using the paint information
727        Graphics2D g = createCustomGraphics(baseGfx, mLocalLayerPaint,
728                true /*alphaOnly*/, 0 /*forceMode*/);
729
730        g.drawImage(mLocalLayer.getImage(),
731                mLayerBounds.left, mLayerBounds.top, mLayerBounds.right, mLayerBounds.bottom,
732                mLayerBounds.left, mLayerBounds.top, mLayerBounds.right, mLayerBounds.bottom,
733                null);
734        g.dispose();
735
736        baseGfx.dispose();
737    }
738
739    /**
740     * Creates a new {@link Graphics2D} based on the {@link Paint} parameters.
741     * <p/>The object must be disposed ({@link Graphics2D#dispose()}) after being used.
742     */
743    private Graphics2D createCustomGraphics(Graphics2D original, Paint_Delegate paint,
744            boolean compositeOnly, int forceMode) {
745        // make new one graphics
746        Graphics2D g = (Graphics2D) original.create();
747
748        // configure it
749
750        if (paint.isAntiAliased()) {
751            g.setRenderingHint(
752                    RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
753            g.setRenderingHint(
754                    RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
755        }
756
757        // set the shader first, as it'll replace the color if it can be used it.
758        boolean customShader = false;
759        if (!compositeOnly) {
760            customShader = setShader(g, paint);
761            // set the stroke
762            g.setStroke(paint.getJavaStroke());
763        }
764        // set the composite.
765        setComposite(g, paint, compositeOnly || customShader, forceMode);
766
767        return g;
768    }
769
770    private boolean setShader(Graphics2D g, Paint_Delegate paint) {
771        Shader_Delegate shaderDelegate = paint.getShader();
772        if (shaderDelegate != null) {
773            if (shaderDelegate.isSupported()) {
774                java.awt.Paint shaderPaint = shaderDelegate.getJavaPaint();
775                assert shaderPaint != null;
776                if (shaderPaint != null) {
777                    g.setPaint(shaderPaint);
778                    return true;
779                }
780            } else {
781                Bridge.getLog().fidelityWarning(LayoutLog.TAG_SHADER,
782                        shaderDelegate.getSupportMessage(),
783                        null /*throwable*/, null /*data*/);
784            }
785        }
786
787        // if no shader, use the paint color
788        g.setColor(new Color(paint.getColor(), true /*hasAlpha*/));
789
790        return false;
791    }
792
793    private void setComposite(Graphics2D g, Paint_Delegate paint, boolean usePaintAlpha,
794            int forceMode) {
795        // the alpha for the composite. Always opaque if the normal paint color is used since
796        // it contains the alpha
797        int alpha = usePaintAlpha ? paint.getAlpha() : 0xFF;
798        if (forceMode != 0) {
799            g.setComposite(AlphaComposite.getInstance(forceMode, (float) alpha / 255.f));
800            return;
801        }
802        Xfermode_Delegate xfermodeDelegate = paint.getXfermode();
803        if (xfermodeDelegate != null) {
804            if (xfermodeDelegate.isSupported()) {
805                Composite composite = xfermodeDelegate.getComposite(alpha);
806                assert composite != null;
807                if (composite != null) {
808                    g.setComposite(composite);
809                    return;
810                }
811            } else {
812                Bridge.getLog().fidelityWarning(LayoutLog.TAG_XFERMODE,
813                        xfermodeDelegate.getSupportMessage(),
814                        null /*throwable*/, null /*data*/);
815            }
816        }
817        // if there was no custom xfermode, but we have alpha (due to a shader and a non
818        // opaque alpha channel in the paint color), then we create an AlphaComposite anyway
819        // that will handle the alpha.
820        if (alpha != 0xFF) {
821            g.setComposite(AlphaComposite.getInstance(
822                    AlphaComposite.SRC_OVER, (float) alpha / 255.f));
823        }
824    }
825
826    private void mapRect(AffineTransform matrix, RectF dst, RectF src) {
827        // array with 4 corners
828        float[] corners = new float[] {
829                src.left, src.top,
830                src.right, src.top,
831                src.right, src.bottom,
832                src.left, src.bottom,
833        };
834
835        // apply the transform to them.
836        matrix.transform(corners, 0, corners, 0, 4);
837
838        // now put the result in the rect. We take the min/max of Xs and min/max of Ys
839        dst.left = Math.min(Math.min(corners[0], corners[2]), Math.min(corners[4], corners[6]));
840        dst.right = Math.max(Math.max(corners[0], corners[2]), Math.max(corners[4], corners[6]));
841
842        dst.top = Math.min(Math.min(corners[1], corners[3]), Math.min(corners[5], corners[7]));
843        dst.bottom = Math.max(Math.max(corners[1], corners[3]), Math.max(corners[5], corners[7]));
844    }
845
846}
847