blob: 959ba77f6af07a58e2ab7c33db106d21df335f43 [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.layoutlib.bridge.impl;
import static java.awt.image.BufferedImage.TYPE_INT_ARGB;
import static java.awt.image.BufferedImage.TYPE_INT_RGB;
import static java.lang.Math.min;
import static java.lang.Math.max;
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.layoutlib.bridge.Bridge;
import android.graphics.Bitmap_Delegate;
import android.graphics.Canvas;
import android.graphics.ColorFilter_Delegate;
import android.graphics.Paint;
import android.graphics.Paint_Delegate;
import android.graphics.PorterDuff;
import android.graphics.PorterDuff.Mode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.Region_Delegate;
import android.graphics.Shader_Delegate;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
/**
* Class representing a graphics context snapshot, as well as a context stack as a linked list.
* <p>
* This is based on top of {@link Graphics2D} but can operate independently if none are available
* yet when setting transforms and clip information.
* <p>
* This allows for drawing through {@link #draw(Drawable, Paint_Delegate, boolean, boolean)} and
* {@link #draw(Drawable)}
*
* Handling of layers (created with {@link Canvas#saveLayer(RectF, Paint, int)}) is handled through
* a list of Graphics2D for each layers. The class actually maintains a list of {@link Layer}
* for each layer. Doing a save() will duplicate this list so that each graphics2D object
* ({@link Layer#getGraphics()}) is configured only for the new snapshot.
*/
public class GcSnapshot {
private static final AffineTransform IDENTITY_TRANSFORM = new AffineTransform();
private final GcSnapshot mPrevious;
private final int mFlags;
/** list of layers. The first item in the list is always the */
private final ArrayList<Layer> mLayers = new ArrayList<Layer>();
/** temp transform in case transformation are set before a Graphics2D exists */
private AffineTransform mTransform = null;
/** temp clip in case clipping is set before a Graphics2D exists */
private Area mClip = null;
// local layer data
/** a local layer created with {@link Canvas#saveLayer(RectF, Paint, int)}.
* If this is null, this does not mean there's no layer, just that the snapshot is not the
* one that created the layer.
*/
private final Layer mLocalLayer;
private final Paint_Delegate mLocalLayerPaint;
private final Rect mLayerBounds;
public interface Drawable {
void draw(Graphics2D graphics, Paint_Delegate paint);
}
/**
* Class containing information about a layer.
*
* This contains graphics, bitmap and layer information.
*/
private static class Layer {
private final Graphics2D mGraphics;
private final Bitmap_Delegate mBitmap;
private final BufferedImage mImage;
/** the flags that were used to configure the layer. This is never changed, and passed
* as is when {@link #makeCopy()} is called */
private final int mFlags;
/** the original content of the layer when the next object was created. This is not
* passed in {@link #makeCopy()} and instead is recreated when a new layer is added
* (depending on its flags) */
private BufferedImage mOriginalCopy;
/**
* Creates a layer with a graphics and a bitmap. This is only used to create
* the base layer.
*
* @param graphics the graphics
* @param bitmap the bitmap
*/
Layer(Graphics2D graphics, Bitmap_Delegate bitmap) {
mGraphics = graphics;
mBitmap = bitmap;
mImage = mBitmap.getImage();
mFlags = 0;
}
/**
* Creates a layer with a graphics and an image. If the image belongs to a
* {@link Bitmap_Delegate} (case of the base layer), then
* {@link Layer#Layer(Graphics2D, Bitmap_Delegate)} should be used.
*
* @param graphics the graphics the new graphics for this layer
* @param image the image the image from which the graphics came
* @param flags the flags that were used to save this layer
*/
Layer(Graphics2D graphics, BufferedImage image, int flags) {
mGraphics = graphics;
mBitmap = null;
mImage = image;
mFlags = flags;
}
/** The Graphics2D, guaranteed to be non null */
Graphics2D getGraphics() {
return mGraphics;
}
/** The BufferedImage, guaranteed to be non null */
BufferedImage getImage() {
return mImage;
}
/** Returns the layer save flags. This is only valid for additional layers.
* For the base layer this will always return 0;
* For a given layer, all further copies of this {@link Layer} object in new snapshots
* will always return the same value.
*/
int getFlags() {
return mFlags;
}
Layer makeCopy() {
if (mBitmap != null) {
return new Layer((Graphics2D) mGraphics.create(), mBitmap);
}
return new Layer((Graphics2D) mGraphics.create(), mImage, mFlags);
}
/** sets an optional copy of the original content to be used during restore */
void setOriginalCopy(BufferedImage image) {
mOriginalCopy = image;
}
BufferedImage getOriginalCopy() {
return mOriginalCopy;
}
void change() {
if (mBitmap != null) {
mBitmap.change();
}
}
/**
* Sets the clip for the graphics2D object associated with the layer.
* This should be used over the normal Graphics2D setClip method.
*
* @param clipShape the shape to use a the clip shape.
*/
void setClip(Shape clipShape) {
// because setClip is only guaranteed to work with rectangle shape,
// first reset the clip to max and then intersect the current (empty)
// clip with the shap.
mGraphics.setClip(null);
mGraphics.clip(clipShape);
}
/**
* Clips the layer with the given shape. This performs an intersect between the current
* clip shape and the given shape.
* @param shape the new clip shape.
*/
public void clip(Shape shape) {
mGraphics.clip(shape);
}
}
/**
* Creates the root snapshot associating it with a given bitmap.
* <p>
* If <var>bitmap</var> is null, then {@link GcSnapshot#setBitmap(Bitmap_Delegate)} must be
* called before the snapshot can be used to draw. Transform and clip operations are permitted
* before.
*
* @param bitmap the image to associate to the snapshot or null.
* @return the root snapshot
*/
public static GcSnapshot createDefaultSnapshot(Bitmap_Delegate bitmap) {
GcSnapshot snapshot = new GcSnapshot();
if (bitmap != null) {
snapshot.setBitmap(bitmap);
}
return snapshot;
}
/**
* Saves the current state according to the given flags and returns the new current snapshot.
* <p/>
* This is the equivalent of {@link Canvas#save(int)}
*
* @param flags the save flags.
* @return the new snapshot
*
* @see Canvas#save(int)
*/
public GcSnapshot save(int flags) {
return new GcSnapshot(this, null /*layerbounds*/, null /*paint*/, flags);
}
/**
* Saves the current state and creates a new layer, and returns the new current snapshot.
* <p/>
* This is the equivalent of {@link Canvas#saveLayer(RectF, Paint, int)}
*
* @param layerBounds the layer bounds
* @param paint the Paint information used to blit the layer back into the layers underneath
* upon restore
* @param flags the save flags.
* @return the new snapshot
*
* @see Canvas#saveLayer(RectF, Paint, int)
*/
public GcSnapshot saveLayer(RectF layerBounds, Paint_Delegate paint, int flags) {
return new GcSnapshot(this, layerBounds, paint, flags);
}
/**
* Creates the root snapshot.
*/
private GcSnapshot() {
mPrevious = null;
mFlags = 0;
mLocalLayer = null;
mLocalLayerPaint = null;
mLayerBounds = null;
}
/**
* Creates a new {@link GcSnapshot} on top of another one, with a layer data to be restored
* into the main graphics when {@link #restore()} is called.
*
* @param previous the previous snapshot head.
* @param layerBounds the region of the layer. Optional, if null, this is a normal save()
* @param paint the Paint information used to blit the layer back into the layers underneath
* upon restore
* @param flags the flags regarding what should be saved.
*/
private GcSnapshot(GcSnapshot previous, RectF layerBounds, Paint_Delegate paint, int flags) {
assert previous != null;
mPrevious = previous;
mFlags = flags;
// make a copy of the current layers before adding the new one.
// This keeps the same BufferedImage reference but creates new Graphics2D for this
// snapshot.
// It does not copy whatever original copy the layers have, as they will be done
// only if the new layer doesn't clip drawing to itself.
for (Layer layer : mPrevious.mLayers) {
mLayers.add(layer.makeCopy());
}
if (layerBounds != null) {
// get the current transform
AffineTransform matrix = mLayers.get(0).getGraphics().getTransform();
// transform the layerBounds with the current transform and stores it into a int rect
RectF rect2 = new RectF();
mapRect(matrix, rect2, layerBounds);
mLayerBounds = new Rect();
rect2.round(mLayerBounds);
// get the base layer (always at index 0)
Layer baseLayer = mLayers.get(0);
// create the image for the layer
BufferedImage layerImage = new BufferedImage(
baseLayer.getImage().getWidth(),
baseLayer.getImage().getHeight(),
(mFlags & Canvas.HAS_ALPHA_LAYER_SAVE_FLAG) != 0 ?
TYPE_INT_ARGB :
TYPE_INT_RGB);
// create a graphics for it so that drawing can be done.
Graphics2D layerGraphics = layerImage.createGraphics();
// because this layer inherits the current context for transform and clip,
// set them to one from the base layer.
AffineTransform currentMtx = baseLayer.getGraphics().getTransform();
layerGraphics.setTransform(currentMtx);
// create a new layer for this new layer and add it to the list at the end.
mLayers.add(mLocalLayer = new Layer(layerGraphics, layerImage, flags));
// set the clip on it.
Shape currentClip = baseLayer.getGraphics().getClip();
mLocalLayer.setClip(currentClip);
// if the drawing is not clipped to the local layer only, we save the current content
// of all other layers. We are only interested in the part that will actually
// be drawn, so we create as small bitmaps as we can.
// This is so that we can erase the drawing that goes in the layers below that will
// be coming from the layer itself.
if ((mFlags & Canvas.CLIP_TO_LAYER_SAVE_FLAG) == 0) {
int w = mLayerBounds.width();
int h = mLayerBounds.height();
for (int i = 0 ; i < mLayers.size() - 1 ; i++) {
Layer layer = mLayers.get(i);
BufferedImage image = new BufferedImage(w, h, TYPE_INT_ARGB);
Graphics2D graphics = image.createGraphics();
graphics.drawImage(layer.getImage(),
0, 0, w, h,
mLayerBounds.left, mLayerBounds.top,
mLayerBounds.right, mLayerBounds.bottom,
null);
graphics.dispose();
layer.setOriginalCopy(image);
}
}
} else {
mLocalLayer = null;
mLayerBounds = null;
}
mLocalLayerPaint = paint;
}
public void dispose() {
for (Layer layer : mLayers) {
layer.getGraphics().dispose();
}
if (mPrevious != null) {
mPrevious.dispose();
}
}
/**
* Restores the top {@link GcSnapshot}, and returns the next one.
*/
public GcSnapshot restore() {
return doRestore();
}
/**
* Restores the {@link GcSnapshot} to <var>saveCount</var>.
* @param saveCount the saveCount or -1 to only restore 1.
*
* @return the new head of the Gc snapshot stack.
*/
public GcSnapshot restoreTo(int saveCount) {
return doRestoreTo(size(), saveCount);
}
public int size() {
if (mPrevious != null) {
return mPrevious.size() + 1;
}
return 1;
}
/**
* Link the snapshot to a Bitmap_Delegate.
* <p/>
* This is only for the case where the snapshot was created with a null image when calling
* {@link #createDefaultSnapshot(Bitmap_Delegate)}, and is therefore not yet linked to
* a previous snapshot.
* <p/>
* If any transform or clip information was set before, they are put into the Graphics object.
* @param bitmap the bitmap to link to.
*/
public void setBitmap(Bitmap_Delegate bitmap) {
// create a new Layer for the bitmap. This will be the base layer.
Graphics2D graphics2D = bitmap.getImage().createGraphics();
Layer baseLayer = new Layer(graphics2D, bitmap);
// Set the current transform and clip which can either come from mTransform/mClip if they
// were set when there was no bitmap/layers or from the current base layers if there is
// one already.
graphics2D.setTransform(getTransform());
// reset mTransform in case there was one.
mTransform = null;
baseLayer.setClip(getClip());
// reset mClip in case there was one.
mClip = null;
// replace whatever current layers we have with this.
mLayers.clear();
mLayers.add(baseLayer);
}
public void translate(float dx, float dy) {
if (mLayers.size() > 0) {
for (Layer layer : mLayers) {
layer.getGraphics().translate(dx, dy);
}
} else {
if (mTransform == null) {
mTransform = new AffineTransform();
}
mTransform.translate(dx, dy);
}
}
public void rotate(double radians) {
if (mLayers.size() > 0) {
for (Layer layer : mLayers) {
layer.getGraphics().rotate(radians);
}
} else {
if (mTransform == null) {
mTransform = new AffineTransform();
}
mTransform.rotate(radians);
}
}
public void scale(float sx, float sy) {
if (mLayers.size() > 0) {
for (Layer layer : mLayers) {
layer.getGraphics().scale(sx, sy);
}
} else {
if (mTransform == null) {
mTransform = new AffineTransform();
}
mTransform.scale(sx, sy);
}
}
public AffineTransform getTransform() {
if (mLayers.size() > 0) {
// all graphics2D in the list have the same transform
return mLayers.get(0).getGraphics().getTransform();
} else {
if (mTransform == null) {
mTransform = new AffineTransform();
}
return mTransform;
}
}
public void setTransform(AffineTransform transform) {
if (mLayers.size() > 0) {
for (Layer layer : mLayers) {
layer.getGraphics().setTransform(transform);
}
} else {
if (mTransform == null) {
mTransform = new AffineTransform();
}
mTransform.setTransform(transform);
}
}
public boolean clip(Shape shape, int regionOp) {
// Simple case of intersect with existing layers.
// Because Graphics2D#setClip works a bit peculiarly, we optimize
// the case of clipping by intersection, as it's supported natively.
if (regionOp == Region.Op.INTERSECT.nativeInt && mLayers.size() > 0) {
for (Layer layer : mLayers) {
layer.clip(shape);
}
Shape currentClip = getClip();
return currentClip != null && currentClip.getBounds().isEmpty() == false;
}
Area area = null;
if (regionOp == Region.Op.REPLACE.nativeInt) {
area = new Area(shape);
} else {
area = Region_Delegate.combineShapes(getClip(), shape, regionOp);
}
if (mLayers.size() > 0) {
if (area != null) {
for (Layer layer : mLayers) {
layer.setClip(area);
}
}
Shape currentClip = getClip();
return currentClip != null && currentClip.getBounds().isEmpty() == false;
} else {
if (area != null) {
mClip = area;
} else {
mClip = new Area();
}
return mClip.getBounds().isEmpty() == false;
}
}
public boolean clipRect(float left, float top, float right, float bottom, int regionOp) {
return clip(new Rectangle2D.Float(left, top, right - left, bottom - top), regionOp);
}
/**
* Returns the current clip, or null if none have been setup.
*/
public Shape getClip() {
if (mLayers.size() > 0) {
// they all have the same clip
return mLayers.get(0).getGraphics().getClip();
} else {
return mClip;
}
}
private GcSnapshot doRestoreTo(int size, int saveCount) {
if (size <= saveCount) {
return this;
}
// restore the current one first.
GcSnapshot previous = doRestore();
if (size == saveCount + 1) { // this was the only one that needed restore.
return previous;
} else {
return previous.doRestoreTo(size - 1, saveCount);
}
}
/**
* Executes the Drawable's draw method, with a null paint delegate.
* <p/>
* Note that the method can be called several times if there are more than one active layer.
*/
public void draw(Drawable drawable) {
draw(drawable, null, false /*compositeOnly*/, false /*forceSrcMode*/);
}
/**
* Executes the Drawable's draw method.
* <p/>
* Note that the method can be called several times if there are more than one active layer.
* @param compositeOnly whether the paint is used for composite only. This is typically
* the case for bitmaps.
* @param forceSrcMode if true, this overrides the composite to be SRC
*/
public void draw(Drawable drawable, Paint_Delegate paint, boolean compositeOnly,
boolean forceSrcMode) {
int forceMode = forceSrcMode ? AlphaComposite.SRC : 0;
// the current snapshot may not have a mLocalLayer (ie it was created on save() instead
// of saveLayer(), but that doesn't mean there's no layer.
// mLayers however saves all the information we need (flags).
if (mLayers.size() == 1) {
// no layer, only base layer. easy case.
drawInLayer(mLayers.get(0), drawable, paint, compositeOnly, forceMode);
} else {
// draw in all the layers until the layer save flags tells us to stop (ie drawing
// in that layer is limited to the layer itself.
int flags;
int i = mLayers.size() - 1;
do {
Layer layer = mLayers.get(i);
drawInLayer(layer, drawable, paint, compositeOnly, forceMode);
// then go to previous layer, only if there are any left, and its flags
// doesn't restrict drawing to the layer itself.
i--;
flags = layer.getFlags();
} while (i >= 0 && (flags & Canvas.CLIP_TO_LAYER_SAVE_FLAG) == 0);
}
}
/**
* This function calculates a minimum (in area) integer rectangle that contains the input
* rectangle after applying to it the affine transform
*
* @param rect input rectangle
* @param transform affine transform applied to the input rectangle
*
* Returns an output rectangle
*/
private static Rectangle transformRect(Rectangle rect, AffineTransform transform) {
double[] coords = new double[16];
coords[0] = rect.x;
coords[1] = rect.y;
coords[2] = rect.x + rect.width;
coords[3] = rect.y + rect.height;
coords[4] = rect.x;
coords[5] = rect.y + rect.height;
coords[6] = rect.x + rect.width;
coords[7] = rect.y;
transform.transform(coords, 0, coords, 8, 4);
// From 4 transformed vertices of the input rectangle we search for the minimum and maximum
// for both coordinates. We round the found extrema to the closest integer, smaller of equal
// for the minimums and larger or equal for the maximums. These values represent the border
// or the minimum rectangle with sides parallel to the coordinate axis that contains
// the transformed rectangle
int x = (int) Math.floor(min(min(coords[8], coords[10]), min(coords[12], coords[14])));
int y = (int) Math.floor(min(min(coords[9], coords[11]), min(coords[13], coords[15])));
int w = (int) Math.ceil(max(max(coords[8], coords[10]), max(coords[12], coords[14]))) - x;
int h = (int) Math.ceil(max(max(coords[9], coords[11]), max(coords[13], coords[15]))) - y;
return new Rectangle(x, y, w, h);
}
private void drawInLayer(Layer layer, Drawable drawable, Paint_Delegate paint,
boolean compositeOnly, int forceMode) {
Graphics2D originalGraphics = layer.getGraphics();
if (paint == null) {
drawOnGraphics((Graphics2D) originalGraphics.create(), drawable,
null /*paint*/, layer);
} else {
ColorFilter_Delegate filter = paint.getColorFilter();
if (filter == null || !filter.isSupported()) {
// get a Graphics2D object configured with the drawing parameters.
Graphics2D configuredGraphics = createCustomGraphics(originalGraphics, paint,
compositeOnly, forceMode);
drawOnGraphics(configuredGraphics, drawable, paint, layer);
return;
}
Rectangle clipBounds = originalGraphics.getClip() != null ? originalGraphics
.getClipBounds() : null;
AffineTransform transform = originalGraphics.getTransform();
Rectangle imgRect;
if (clipBounds != null) {
if (clipBounds.width == 0 || clipBounds.height == 0) {
// Clip is 0 so no need to paint anything.
return;
}
// Calculate integer rectangle that contains clipBounds after the transform, that is
// the minimum image size we can use to render the drawable
imgRect = transformRect(clipBounds, transform);
transform = new AffineTransform(
transform.getScaleX(),
transform.getShearY(),
transform.getShearX(),
transform.getScaleY(),
transform.getTranslateX() - imgRect.x,
transform.getTranslateY() - imgRect.y);
} else {
imgRect =
new Rectangle(
0, 0, layer.getImage().getWidth(), layer.getImage().getHeight());
}
// Create a temporary image to which the color filter will be applied.
BufferedImage image = new BufferedImage(imgRect.width, imgRect.height, TYPE_INT_ARGB);
Graphics2D imageBaseGraphics = (Graphics2D) image.getGraphics();
// Configure the Graphics2D object with drawing parameters and shader.
Graphics2D imageGraphics = createCustomGraphics(
imageBaseGraphics, paint, compositeOnly,
AlphaComposite.SRC_OVER);
// get a Graphics2D object configured with the drawing parameters, but no shader.
Graphics2D configuredGraphics = createCustomGraphics(originalGraphics, paint,
true /*compositeOnly*/, forceMode);
configuredGraphics.setTransform(IDENTITY_TRANSFORM);
try {
// The main draw operation.
// We translate the operation to take into account that the rendering does not
// know about the clipping area.
imageGraphics.setTransform(transform);
drawable.draw(imageGraphics, paint);
// Apply the color filter.
// Restore the original coordinates system and apply the filter only to the
// clipped area.
imageGraphics.setTransform(IDENTITY_TRANSFORM);
filter.applyFilter(imageGraphics, imgRect.width, imgRect.height);
// Draw the tinted image on the main layer using as start point the clipping
// upper left coordinates.
configuredGraphics.drawImage(image, imgRect.x, imgRect.y, null);
layer.change();
} finally {
// dispose Graphics2D objects
imageGraphics.dispose();
imageBaseGraphics.dispose();
configuredGraphics.dispose();
}
}
}
private void drawOnGraphics(Graphics2D g, Drawable drawable, Paint_Delegate paint,
Layer layer) {
try {
drawable.draw(g, paint);
layer.change();
} finally {
g.dispose();
}
}
private GcSnapshot doRestore() {
if (mPrevious != null) {
if (mLocalLayer != null) {
// prepare to blit the layers in which we have draw, in the layer beneath
// them, starting with the top one (which is the current local layer).
int i = mLayers.size() - 1;
int flags;
do {
Layer dstLayer = mLayers.get(i - 1);
restoreLayer(dstLayer);
flags = dstLayer.getFlags();
i--;
} while (i > 0 && (flags & Canvas.CLIP_TO_LAYER_SAVE_FLAG) == 0);
}
// if this snapshot does not save everything, then set the previous snapshot
// to this snapshot content
// didn't save the matrix? set the current matrix on the previous snapshot
if ((mFlags & Canvas.MATRIX_SAVE_FLAG) == 0) {
AffineTransform mtx = getTransform();
for (Layer layer : mPrevious.mLayers) {
layer.getGraphics().setTransform(mtx);
}
}
// didn't save the clip? set the current clip on the previous snapshot
if ((mFlags & Canvas.CLIP_SAVE_FLAG) == 0) {
Shape clip = getClip();
for (Layer layer : mPrevious.mLayers) {
layer.setClip(clip);
}
}
}
for (Layer layer : mLayers) {
layer.getGraphics().dispose();
}
return mPrevious;
}
private void restoreLayer(Layer dstLayer) {
Graphics2D baseGfx = dstLayer.getImage().createGraphics();
// if the layer contains an original copy this means the flags
// didn't restrict drawing to the local layer and we need to make sure the
// layer bounds in the layer beneath didn't receive any drawing.
// so we use the originalCopy to erase the new drawings in there.
BufferedImage originalCopy = dstLayer.getOriginalCopy();
if (originalCopy != null) {
Graphics2D g = (Graphics2D) baseGfx.create();
g.setComposite(AlphaComposite.Src);
g.drawImage(originalCopy,
mLayerBounds.left, mLayerBounds.top, mLayerBounds.right, mLayerBounds.bottom,
0, 0, mLayerBounds.width(), mLayerBounds.height(),
null);
g.dispose();
}
// now draw put the content of the local layer onto the layer,
// using the paint information
Graphics2D g = createCustomGraphics(baseGfx, mLocalLayerPaint,
true /*alphaOnly*/, 0 /*forceMode*/);
g.drawImage(mLocalLayer.getImage(),
mLayerBounds.left, mLayerBounds.top, mLayerBounds.right, mLayerBounds.bottom,
mLayerBounds.left, mLayerBounds.top, mLayerBounds.right, mLayerBounds.bottom,
null);
g.dispose();
baseGfx.dispose();
}
/**
* Creates a new {@link Graphics2D} based on the {@link Paint} parameters.
* <p/>The object must be disposed ({@link Graphics2D#dispose()}) after being used.
*/
private Graphics2D createCustomGraphics(Graphics2D original, Paint_Delegate paint,
boolean compositeOnly, int forceMode) {
// make new one graphics
Graphics2D g = (Graphics2D) original.create();
if (paint == null) {
return g;
}
// configure it
if (paint.isAntiAliased()) {
g.setRenderingHint(
RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(
RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
}
// set the shader first, as it'll replace the color if it can be used it.
if (!compositeOnly) {
setShader(g, paint);
// set the stroke
g.setStroke(paint.getJavaStroke());
}
// set the composite.
setComposite(g, paint, compositeOnly, forceMode);
return g;
}
private void setShader(Graphics2D g, Paint_Delegate paint) {
Shader_Delegate shaderDelegate = paint.getShader();
if (shaderDelegate != null) {
if (shaderDelegate.isSupported()) {
java.awt.Paint shaderPaint = shaderDelegate.getJavaPaint();
assert shaderPaint != null;
g.setPaint(shaderPaint);
return;
} else {
Bridge.getLog().fidelityWarning(LayoutLog.TAG_SHADER,
shaderDelegate.getSupportMessage(), null, null, null);
}
}
// if no shader, use the paint color
g.setColor(new Color(paint.getColor(), true /*hasAlpha*/));
}
private void setComposite(Graphics2D g, Paint_Delegate paint, boolean usePaintAlpha,
int forceMode) {
// the alpha for the composite. Always opaque if the normal paint color is used since
// it contains the alpha
int alpha = usePaintAlpha ? paint.getAlpha() : 0xFF;
Shader_Delegate shader = paint.getShader();
if (shader != null) {
alpha = (int)(alpha * shader.getAlpha());
}
if (forceMode != 0) {
g.setComposite(AlphaComposite.getInstance(forceMode, (float) alpha / 255.f));
return;
}
Mode mode = PorterDuff.intToMode(paint.getPorterDuffMode());
Composite composite = PorterDuffUtility.getComposite(mode, alpha);
g.setComposite(composite);
}
private void mapRect(AffineTransform matrix, RectF dst, RectF src) {
// array with 4 corners
float[] corners = new float[] {
src.left, src.top,
src.right, src.top,
src.right, src.bottom,
src.left, src.bottom,
};
// apply the transform to them.
matrix.transform(corners, 0, corners, 0, 4);
// now put the result in the rect. We take the min/max of Xs and min/max of Ys
dst.left = Math.min(Math.min(corners[0], corners[2]), Math.min(corners[4], corners[6]));
dst.right = Math.max(Math.max(corners[0], corners[2]), Math.max(corners[4], corners[6]));
dst.top = Math.min(Math.min(corners[1], corners[3]), Math.min(corners[5], corners[7]));
dst.bottom = Math.max(Math.max(corners[1], corners[3]), Math.max(corners[5], corners[7]));
}
/**
* Returns the clip of the oldest snapshot of the stack, appropriately translated to be
* expressed in the coordinate system of the latest snapshot.
*/
public Rectangle getOriginalClip() {
GcSnapshot originalSnapshot = this;
while (originalSnapshot.mPrevious != null) {
originalSnapshot = originalSnapshot.mPrevious;
}
if (originalSnapshot.mLayers.isEmpty()) {
return null;
}
Graphics2D graphics2D = originalSnapshot.mLayers.get(0).getGraphics();
Rectangle bounds = graphics2D.getClipBounds();
if (bounds == null) {
return null;
}
try {
AffineTransform originalTransform =
((Graphics2D) graphics2D.create()).getTransform().createInverse();
AffineTransform latestTransform = getTransform().createInverse();
bounds.x += latestTransform.getTranslateX() - originalTransform.getTranslateX();
bounds.y += latestTransform.getTranslateY() - originalTransform.getTranslateY();
} catch (NoninvertibleTransformException e) {
Bridge.getLog().warning(null, "Non invertible transformation", null, null);
}
return bounds;
}
}