2 * Copyright (C) 2010 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.layoutlib.bridge.impl;
19 import com.android.ide.common.rendering.api.LayoutLog;
20 import com.android.layoutlib.bridge.Bridge;
22 import android.graphics.Bitmap_Delegate;
23 import android.graphics.Canvas;
24 import android.graphics.Paint;
25 import android.graphics.Paint_Delegate;
26 import android.graphics.Rect;
27 import android.graphics.RectF;
28 import android.graphics.Region;
29 import android.graphics.Region_Delegate;
30 import android.graphics.Shader_Delegate;
31 import android.graphics.Xfermode_Delegate;
33 import java.awt.AlphaComposite;
34 import java.awt.Color;
35 import java.awt.Composite;
36 import java.awt.Graphics2D;
37 import java.awt.RenderingHints;
38 import java.awt.Shape;
39 import java.awt.geom.AffineTransform;
40 import java.awt.geom.Area;
41 import java.awt.geom.Rectangle2D;
42 import java.awt.image.BufferedImage;
43 import java.util.ArrayList;
46 * Class representing a graphics context snapshot, as well as a context stack as a linked list.
48 * This is based on top of {@link Graphics2D} but can operate independently if none are available
49 * yet when setting transforms and clip information.
51 * This allows for drawing through {@link #draw(Drawable, Paint_Delegate)} and
52 * {@link #draw(Drawable, Paint_Delegate)}
54 * Handling of layers (created with {@link Canvas#saveLayer(RectF, Paint, int)}) is handled through
55 * a list of Graphics2D for each layers. The class actually maintains a list of {@link Layer}
56 * for each layer. Doing a save() will duplicate this list so that each graphics2D object
57 * ({@link Layer#getGraphics()}) is configured only for the new snapshot.
59 public class GcSnapshot {
61 private final GcSnapshot mPrevious;
62 private final int mFlags;
64 /** list of layers. The first item in the list is always the */
65 private final ArrayList<Layer> mLayers = new ArrayList<Layer>();
67 /** temp transform in case transformation are set before a Graphics2D exists */
68 private AffineTransform mTransform = null;
69 /** temp clip in case clipping is set before a Graphics2D exists */
70 private Area mClip = null;
73 /** a local layer created with {@link Canvas#saveLayer(RectF, Paint, int)}.
74 * If this is null, this does not mean there's no layer, just that the snapshot is not the
75 * one that created the layer.
76 * @see #getLayerSnapshot()
78 private final Layer mLocalLayer;
79 private final Paint_Delegate mLocalLayerPaint;
80 private final Rect mLayerBounds;
82 public interface Drawable {
83 void draw(Graphics2D graphics, Paint_Delegate paint);
87 * Class containing information about a layer.
89 * This contains graphics, bitmap and layer information.
91 private static class Layer {
92 private final Graphics2D mGraphics;
93 private final Bitmap_Delegate mBitmap;
94 private final BufferedImage mImage;
95 /** the flags that were used to configure the layer. This is never changed, and passed
96 * as is when {@link #makeCopy()} is called */
97 private final int mFlags;
98 /** the original content of the layer when the next object was created. This is not
99 * passed in {@link #makeCopy()} and instead is recreated when a new layer is added
100 * (depending on its flags) */
101 private BufferedImage mOriginalCopy;
104 * Creates a layer with a graphics and a bitmap. This is only used to create
107 * @param graphics the graphics
108 * @param bitmap the bitmap
110 Layer(Graphics2D graphics, Bitmap_Delegate bitmap) {
111 mGraphics = graphics;
113 mImage = mBitmap.getImage();
118 * Creates a layer with a graphics and an image. If the image belongs to a
119 * {@link Bitmap_Delegate} (case of the base layer), then
120 * {@link Layer#Layer(Graphics2D, Bitmap_Delegate)} should be used.
122 * @param graphics the graphics the new graphics for this layer
123 * @param image the image the image from which the graphics came
124 * @param flags the flags that were used to save this layer
126 Layer(Graphics2D graphics, BufferedImage image, int flags) {
127 mGraphics = graphics;
133 /** The Graphics2D, guaranteed to be non null */
134 Graphics2D getGraphics() {
138 /** The BufferedImage, guaranteed to be non null */
139 BufferedImage getImage() {
143 /** Returns the layer save flags. This is only valid for additional layers.
144 * For the base layer this will always return 0;
145 * For a given layer, all further copies of this {@link Layer} object in new snapshots
146 * will always return the same value.
153 if (mBitmap != null) {
154 return new Layer((Graphics2D) mGraphics.create(), mBitmap);
157 return new Layer((Graphics2D) mGraphics.create(), mImage, mFlags);
160 /** sets an optional copy of the original content to be used during restore */
161 void setOriginalCopy(BufferedImage image) {
162 mOriginalCopy = image;
165 BufferedImage getOriginalCopy() {
166 return mOriginalCopy;
170 * Sets the clip for the graphics2D object associated with the layer.
171 * This should be used over the normal Graphics2D setClip method.
173 * @param clipShape the shape to use a the clip shape.
175 void setClip(Shape clipShape) {
176 // because setClip is only guaranteed to work with rectangle shape,
177 // first reset the clip to max and then intersect the current (empty)
178 // clip with the shap.
179 mGraphics.setClip(null);
180 mGraphics.clip(clipShape);
184 * Clips the layer with the given shape. This performs an intersect between the current
185 * clip shape and the given shape.
186 * @param shape the new clip shape.
188 public void clip(Shape shape) {
189 mGraphics.clip(shape);
194 * Creates the root snapshot associating it with a given bitmap.
196 * If <var>bitmap</var> is null, then {@link GcSnapshot#setBitmap(Bitmap_Delegate)} must be
197 * called before the snapshot can be used to draw. Transform and clip operations are permitted
200 * @param image the image to associate to the snapshot or null.
201 * @return the root snapshot
203 public static GcSnapshot createDefaultSnapshot(Bitmap_Delegate bitmap) {
204 GcSnapshot snapshot = new GcSnapshot();
205 if (bitmap != null) {
206 snapshot.setBitmap(bitmap);
213 * Saves the current state according to the given flags and returns the new current snapshot.
215 * This is the equivalent of {@link Canvas#save(int)}
217 * @param flags the save flags.
218 * @return the new snapshot
220 * @see Canvas#save(int)
222 public GcSnapshot save(int flags) {
223 return new GcSnapshot(this, null /*layerbounds*/, null /*paint*/, flags);
227 * Saves the current state and creates a new layer, and returns the new current snapshot.
229 * This is the equivalent of {@link Canvas#saveLayer(RectF, Paint, int)}
231 * @param layerBounds the layer bounds
232 * @param paint the Paint information used to blit the layer back into the layers underneath
234 * @param flags the save flags.
235 * @return the new snapshot
237 * @see Canvas#saveLayer(RectF, Paint, int)
239 public GcSnapshot saveLayer(RectF layerBounds, Paint_Delegate paint, int flags) {
240 return new GcSnapshot(this, layerBounds, paint, flags);
244 * Creates the root snapshot.
245 * {@link #setGraphics2D(Graphics2D)} will have to be called on it when possible.
247 private GcSnapshot() {
251 mLocalLayerPaint = null;
256 * Creates a new {@link GcSnapshot} on top of another one, with a layer data to be restored
257 * into the main graphics when {@link #restore()} is called.
259 * @param previous the previous snapshot head.
260 * @param layerBounds the region of the layer. Optional, if null, this is a normal save()
261 * @param paint the Paint information used to blit the layer back into the layers underneath
263 * @param flags the flags regarding what should be saved.
265 private GcSnapshot(GcSnapshot previous, RectF layerBounds, Paint_Delegate paint, int flags) {
266 assert previous != null;
267 mPrevious = previous;
270 // make a copy of the current layers before adding the new one.
271 // This keeps the same BufferedImage reference but creates new Graphics2D for this
273 // It does not copy whatever original copy the layers have, as they will be done
274 // only if the new layer doesn't clip drawing to itself.
275 for (Layer layer : mPrevious.mLayers) {
276 mLayers.add(layer.makeCopy());
279 if (layerBounds != null) {
280 // get the current transform
281 AffineTransform matrix = mLayers.get(0).getGraphics().getTransform();
283 // transform the layerBounds with the current transform and stores it into a int rect
284 RectF rect2 = new RectF();
285 mapRect(matrix, rect2, layerBounds);
286 mLayerBounds = new Rect();
287 rect2.round(mLayerBounds);
289 // get the base layer (always at index 0)
290 Layer baseLayer = mLayers.get(0);
292 // create the image for the layer
293 BufferedImage layerImage = new BufferedImage(
294 baseLayer.getImage().getWidth(),
295 baseLayer.getImage().getHeight(),
296 (mFlags & Canvas.HAS_ALPHA_LAYER_SAVE_FLAG) != 0 ?
297 BufferedImage.TYPE_INT_ARGB :
298 BufferedImage.TYPE_INT_RGB);
300 // create a graphics for it so that drawing can be done.
301 Graphics2D layerGraphics = layerImage.createGraphics();
303 // because this layer inherits the current context for transform and clip,
304 // set them to one from the base layer.
305 AffineTransform currentMtx = baseLayer.getGraphics().getTransform();
306 layerGraphics.setTransform(currentMtx);
308 // create a new layer for this new layer and add it to the list at the end.
309 mLayers.add(mLocalLayer = new Layer(layerGraphics, layerImage, flags));
311 // set the clip on it.
312 Shape currentClip = baseLayer.getGraphics().getClip();
313 mLocalLayer.setClip(currentClip);
315 // if the drawing is not clipped to the local layer only, we save the current content
316 // of all other layers. We are only interested in the part that will actually
317 // be drawn, so we create as small bitmaps as we can.
318 // This is so that we can erase the drawing that goes in the layers below that will
319 // be coming from the layer itself.
320 if ((mFlags & Canvas.CLIP_TO_LAYER_SAVE_FLAG) == 0) {
321 int w = mLayerBounds.width();
322 int h = mLayerBounds.height();
323 for (int i = 0 ; i < mLayers.size() - 1 ; i++) {
324 Layer layer = mLayers.get(i);
325 BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
326 Graphics2D graphics = image.createGraphics();
327 graphics.drawImage(layer.getImage(),
329 mLayerBounds.left, mLayerBounds.top,
330 mLayerBounds.right, mLayerBounds.bottom,
333 layer.setOriginalCopy(image);
341 mLocalLayerPaint = paint;
344 public void dispose() {
345 for (Layer layer : mLayers) {
346 layer.getGraphics().dispose();
349 if (mPrevious != null) {
355 * Restores the top {@link GcSnapshot}, and returns the next one.
357 public GcSnapshot restore() {
362 * Restores the {@link GcSnapshot} to <var>saveCount</var>.
363 * @param saveCount the saveCount or -1 to only restore 1.
365 * @return the new head of the Gc snapshot stack.
367 public GcSnapshot restoreTo(int saveCount) {
368 return doRestoreTo(size(), saveCount);
372 if (mPrevious != null) {
373 return mPrevious.size() + 1;
380 * Link the snapshot to a Bitmap_Delegate.
382 * This is only for the case where the snapshot was created with a null image when calling
383 * {@link #createDefaultSnapshot(Bitmap_Delegate)}, and is therefore not yet linked to
384 * a previous snapshot.
386 * If any transform or clip information was set before, they are put into the Graphics object.
387 * @param bitmap the bitmap to link to.
389 public void setBitmap(Bitmap_Delegate bitmap) {
390 // create a new Layer for the bitmap. This will be the base layer.
391 Graphics2D graphics2D = bitmap.getImage().createGraphics();
392 Layer baseLayer = new Layer(graphics2D, bitmap);
394 // Set the current transform and clip which can either come from mTransform/mClip if they
395 // were set when there was no bitmap/layers or from the current base layers if there is
398 graphics2D.setTransform(getTransform());
399 // reset mTransform in case there was one.
402 baseLayer.setClip(getClip());
403 // reset mClip in case there was one.
406 // replace whatever current layers we have with this.
408 mLayers.add(baseLayer);
412 public void translate(float dx, float dy) {
413 if (mLayers.size() > 0) {
414 for (Layer layer : mLayers) {
415 layer.getGraphics().translate(dx, dy);
418 if (mTransform == null) {
419 mTransform = new AffineTransform();
421 mTransform.translate(dx, dy);
425 public void rotate(double radians) {
426 if (mLayers.size() > 0) {
427 for (Layer layer : mLayers) {
428 layer.getGraphics().rotate(radians);
431 if (mTransform == null) {
432 mTransform = new AffineTransform();
434 mTransform.rotate(radians);
438 public void scale(float sx, float sy) {
439 if (mLayers.size() > 0) {
440 for (Layer layer : mLayers) {
441 layer.getGraphics().scale(sx, sy);
444 if (mTransform == null) {
445 mTransform = new AffineTransform();
447 mTransform.scale(sx, sy);
451 public AffineTransform getTransform() {
452 if (mLayers.size() > 0) {
453 // all graphics2D in the list have the same transform
454 return mLayers.get(0).getGraphics().getTransform();
456 if (mTransform == null) {
457 mTransform = new AffineTransform();
463 public void setTransform(AffineTransform transform) {
464 if (mLayers.size() > 0) {
465 for (Layer layer : mLayers) {
466 layer.getGraphics().setTransform(transform);
469 if (mTransform == null) {
470 mTransform = new AffineTransform();
472 mTransform.setTransform(transform);
476 public boolean clip(Shape shape, int regionOp) {
477 // Simple case of intersect with existing layers.
478 // Because Graphics2D#setClip works a bit peculiarly, we optimize
479 // the case of clipping by intersection, as it's supported natively.
480 if (regionOp == Region.Op.INTERSECT.nativeInt && mLayers.size() > 0) {
481 for (Layer layer : mLayers) {
485 Shape currentClip = getClip();
486 return currentClip != null && currentClip.getBounds().isEmpty() == false;
491 if (regionOp == Region.Op.REPLACE.nativeInt) {
492 area = new Area(shape);
494 area = Region_Delegate.combineShapes(getClip(), shape, regionOp);
499 if (mLayers.size() > 0) {
501 for (Layer layer : mLayers) {
506 Shape currentClip = getClip();
507 return currentClip != null && currentClip.getBounds().isEmpty() == false;
515 return mClip.getBounds().isEmpty() == false;
519 public boolean clipRect(float left, float top, float right, float bottom, int regionOp) {
520 return clip(new Rectangle2D.Float(left, top, right - left, bottom - top), regionOp);
524 * Returns the current clip, or null if none have been setup.
526 public Shape getClip() {
527 if (mLayers.size() > 0) {
528 // they all have the same clip
529 return mLayers.get(0).getGraphics().getClip();
535 private GcSnapshot doRestoreTo(int size, int saveCount) {
536 if (size <= saveCount) {
540 // restore the current one first.
541 GcSnapshot previous = doRestore();
543 if (size == saveCount + 1) { // this was the only one that needed restore.
546 return previous.doRestoreTo(size - 1, saveCount);
551 * Executes the Drawable's draw method, with a null paint delegate.
553 * Note that the method can be called several times if there are more than one active layer.
556 public void draw(Drawable drawable) {
557 draw(drawable, null, false /*compositeOnly*/, false /*forceSrcMode*/);
561 * Executes the Drawable's draw method.
563 * Note that the method can be called several times if there are more than one active layer.
566 * @param compositeOnly whether the paint is used for composite only. This is typically
567 * the case for bitmaps.
568 * @param forceSrcMode if true, this overrides the composite to be SRC
570 public void draw(Drawable drawable, Paint_Delegate paint, boolean compositeOnly,
571 boolean forceSrcMode) {
572 // the current snapshot may not have a mLocalLayer (ie it was created on save() instead
573 // of saveLayer(), but that doesn't mean there's no layer.
574 // mLayers however saves all the information we need (flags).
575 if (mLayers.size() == 1) {
576 // no layer, only base layer. easy case.
577 drawInLayer(mLayers.get(0), drawable, paint, compositeOnly, forceSrcMode);
579 // draw in all the layers until the layer save flags tells us to stop (ie drawing
580 // in that layer is limited to the layer itself.
582 int i = mLayers.size() - 1;
585 Layer layer = mLayers.get(i);
587 drawInLayer(layer, drawable, paint, compositeOnly, forceSrcMode);
589 // then go to previous layer, only if there are any left, and its flags
590 // doesn't restrict drawing to the layer itself.
592 flags = layer.getFlags();
593 } while (i >= 0 && (flags & Canvas.CLIP_TO_LAYER_SAVE_FLAG) == 0);
597 private void drawInLayer(Layer layer, Drawable drawable, Paint_Delegate paint,
598 boolean compositeOnly, boolean forceSrcMode) {
599 Graphics2D originalGraphics = layer.getGraphics();
600 // get a Graphics2D object configured with the drawing parameters.
601 Graphics2D configuredGraphics2D =
603 createCustomGraphics(originalGraphics, paint, compositeOnly, forceSrcMode) :
604 (Graphics2D) originalGraphics.create();
606 if (configuredGraphics2D != null) {
608 drawable.draw(configuredGraphics2D, paint);
610 // dispose Graphics2D object
611 configuredGraphics2D.dispose();
616 private GcSnapshot doRestore() {
617 if (mPrevious != null) {
618 if (mLocalLayer != null) {
619 // prepare to blit the layers in which we have draw, in the layer beneath
620 // them, starting with the top one (which is the current local layer).
621 int i = mLayers.size() - 1;
624 Layer dstLayer = mLayers.get(i - 1);
626 restoreLayer(dstLayer);
628 flags = dstLayer.getFlags();
630 } while (i > 0 && (flags & Canvas.CLIP_TO_LAYER_SAVE_FLAG) == 0);
633 // if this snapshot does not save everything, then set the previous snapshot
634 // to this snapshot content
636 // didn't save the matrix? set the current matrix on the previous snapshot
637 if ((mFlags & Canvas.MATRIX_SAVE_FLAG) == 0) {
638 AffineTransform mtx = getTransform();
639 for (Layer layer : mPrevious.mLayers) {
640 layer.getGraphics().setTransform(mtx);
644 // didn't save the clip? set the current clip on the previous snapshot
645 if ((mFlags & Canvas.CLIP_SAVE_FLAG) == 0) {
646 Shape clip = getClip();
647 for (Layer layer : mPrevious.mLayers) {
653 for (Layer layer : mLayers) {
654 layer.getGraphics().dispose();
660 private void restoreLayer(Layer dstLayer) {
662 Graphics2D baseGfx = dstLayer.getImage().createGraphics();
664 // if the layer contains an original copy this means the flags
665 // didn't restrict drawing to the local layer and we need to make sure the
666 // layer bounds in the layer beneath didn't receive any drawing.
667 // so we use the originalCopy to erase the new drawings in there.
668 BufferedImage originalCopy = dstLayer.getOriginalCopy();
669 if (originalCopy != null) {
670 Graphics2D g = (Graphics2D) baseGfx.create();
671 g.setComposite(AlphaComposite.Src);
673 g.drawImage(originalCopy,
674 mLayerBounds.left, mLayerBounds.top, mLayerBounds.right, mLayerBounds.bottom,
675 0, 0, mLayerBounds.width(), mLayerBounds.height(),
680 // now draw put the content of the local layer onto the layer,
681 // using the paint information
682 Graphics2D g = createCustomGraphics(baseGfx, mLocalLayerPaint,
683 true /*alphaOnly*/, false /*forceSrcMode*/);
686 g.drawImage(mLocalLayer.getImage(),
687 mLayerBounds.left, mLayerBounds.top, mLayerBounds.right, mLayerBounds.bottom,
688 mLayerBounds.left, mLayerBounds.top, mLayerBounds.right, mLayerBounds.bottom,
697 * Creates a new {@link Graphics2D} based on the {@link Paint} parameters.
698 * <p/>The object must be disposed ({@link Graphics2D#dispose()}) after being used.
700 private Graphics2D createCustomGraphics(Graphics2D original, Paint_Delegate paint,
701 boolean compositeOnly, boolean forceSrcMode) {
702 // make new one graphics
703 Graphics2D g = (Graphics2D) original.create();
707 if (paint.isAntiAliased()) {
709 RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
711 RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
714 boolean customShader = false;
716 // get the shader first, as it'll replace the color if it can be used it.
717 if (compositeOnly == false) {
718 Shader_Delegate shaderDelegate = paint.getShader();
719 if (shaderDelegate != null) {
720 if (shaderDelegate.isSupported()) {
721 // shader could have a local matrix that's not valid (for instance 0 scaling).
722 if (shaderDelegate.isValid()) {
723 java.awt.Paint shaderPaint = shaderDelegate.getJavaPaint();
724 assert shaderPaint != null;
725 if (shaderPaint != null) {
726 g.setPaint(shaderPaint);
734 Bridge.getLog().fidelityWarning(LayoutLog.TAG_SHADER,
735 shaderDelegate.getSupportMessage(),
736 null /*throwable*/, null /*data*/);
740 // if no shader, use the paint color
741 if (customShader == false) {
742 g.setColor(new Color(paint.getColor(), true /*hasAlpha*/));
746 g.setStroke(paint.getJavaStroke());
749 // the alpha for the composite. Always opaque if the normal paint color is used since
750 // it contains the alpha
751 int alpha = (compositeOnly || customShader) ? paint.getAlpha() : 0xFF;
754 g.setComposite(AlphaComposite.getInstance(
755 AlphaComposite.SRC, alpha / 255.f));
757 boolean customXfermode = false;
758 Xfermode_Delegate xfermodeDelegate = paint.getXfermode();
759 if (xfermodeDelegate != null) {
760 if (xfermodeDelegate.isSupported()) {
761 Composite composite = xfermodeDelegate.getComposite(alpha);
762 assert composite != null;
763 if (composite != null) {
764 g.setComposite(composite);
765 customXfermode = true;
768 Bridge.getLog().fidelityWarning(LayoutLog.TAG_XFERMODE,
769 xfermodeDelegate.getSupportMessage(),
770 null /*throwable*/, null /*data*/);
774 // if there was no custom xfermode, but we have alpha (due to a shader and a non
775 // opaque alpha channel in the paint color), then we create an AlphaComposite anyway
776 // that will handle the alpha.
777 if (customXfermode == false && alpha != 0xFF) {
778 g.setComposite(AlphaComposite.getInstance(
779 AlphaComposite.SRC_OVER, alpha / 255.f));
786 private void mapRect(AffineTransform matrix, RectF dst, RectF src) {
787 // array with 4 corners
788 float[] corners = new float[] {
791 src.right, src.bottom,
792 src.left, src.bottom,
795 // apply the transform to them.
796 matrix.transform(corners, 0, corners, 0, 4);
798 // now put the result in the rect. We take the min/max of Xs and min/max of Ys
799 dst.left = Math.min(Math.min(corners[0], corners[2]), Math.min(corners[4], corners[6]));
800 dst.right = Math.max(Math.max(corners[0], corners[2]), Math.max(corners[4], corners[6]));
802 dst.top = Math.min(Math.min(corners[1], corners[3]), Math.min(corners[5], corners[7]));
803 dst.bottom = Math.max(Math.max(corners[1], corners[3]), Math.max(corners[5], corners[7]));