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