FileDocCategorySizeDatePackage
GcSnapshot.javaAPI DocAndroid 5.1 API31615Thu Mar 12 22:22:44 GMT 2015com.android.layoutlib.bridge.impl

GcSnapshot

public class GcSnapshot extends Object
Class representing a graphics context snapshot, as well as a context stack as a linked list.

This is based on top of {@link Graphics2D} but can operate independently if none are available yet when setting transforms and clip information.

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.

Fields Summary
private final GcSnapshot
mPrevious
private final int
mFlags
private final ArrayList
mLayers
list of layers. The first item in the list is always the
private AffineTransform
mTransform
temp transform in case transformation are set before a Graphics2D exists
private Area
mClip
temp clip in case clipping is set before a Graphics2D exists
private final Layer
mLocalLayer
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 android.graphics.Paint_Delegate
mLocalLayerPaint
private final android.graphics.Rect
mLayerBounds
Constructors Summary
private GcSnapshot()
Creates the root snapshot. {@link #setGraphics2D(Graphics2D)} will have to be called on it when possible.

        mPrevious = null;
        mFlags = 0;
        mLocalLayer = null;
        mLocalLayerPaint = null;
        mLayerBounds = null;
    
private GcSnapshot(GcSnapshot previous, android.graphics.RectF layerBounds, android.graphics.Paint_Delegate paint, int flags)
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.

        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 ?
                            BufferedImage.TYPE_INT_ARGB :
                                BufferedImage.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, BufferedImage.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;
    
Methods Summary
public booleanclip(java.awt.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);
        }

        assert area != null;

        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 booleanclipRect(float left, float top, float right, float bottom, int regionOp)

        return clip(new Rectangle2D.Float(left, top, right - left, bottom - top), regionOp);
    
private java.awt.Graphics2DcreateCustomGraphics(java.awt.Graphics2D original, android.graphics.Paint_Delegate paint, boolean compositeOnly, int forceMode)
Creates a new {@link Graphics2D} based on the {@link Paint} parameters.

The object must be disposed ({@link Graphics2D#dispose()}) after being used.

        // make new one graphics
        Graphics2D g = (Graphics2D) original.create();

        // 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.
        boolean customShader = false;
        if (!compositeOnly) {
            customShader = setShader(g, paint);
            // set the stroke
            g.setStroke(paint.getJavaStroke());
        }
        // set the composite.
        setComposite(g, paint, compositeOnly || customShader, forceMode);

        return g;
    
public static com.android.layoutlib.bridge.impl.GcSnapshotcreateDefaultSnapshot(android.graphics.Bitmap_Delegate bitmap)
Creates the root snapshot associating it with a given bitmap.

If bitmap 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

        GcSnapshot snapshot = new GcSnapshot();
        if (bitmap != null) {
            snapshot.setBitmap(bitmap);
        }

        return snapshot;
    
public voiddispose()

        for (Layer layer : mLayers) {
            layer.getGraphics().dispose();
        }

        if (mPrevious != null) {
            mPrevious.dispose();
        }
    
private com.android.layoutlib.bridge.impl.GcSnapshotdoRestore()

        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 com.android.layoutlib.bridge.impl.GcSnapshotdoRestoreTo(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);
        }
    
public voiddraw(com.android.layoutlib.bridge.impl.GcSnapshot$Drawable drawable)
Executes the Drawable's draw method, with a null paint delegate.

Note that the method can be called several times if there are more than one active layer.

        draw(drawable, null, false /*compositeOnly*/, false /*forceSrcMode*/);
    
public voiddraw(com.android.layoutlib.bridge.impl.GcSnapshot$Drawable drawable, android.graphics.Paint_Delegate paint, boolean compositeOnly, boolean forceSrcMode)
Executes the Drawable's draw method.

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

        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);
        }
    
private voiddrawInLayer(com.android.layoutlib.bridge.impl.GcSnapshot$Layer layer, com.android.layoutlib.bridge.impl.GcSnapshot$Drawable drawable, android.graphics.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;
            }

            int width = layer.getImage().getWidth();
            int height = layer.getImage().getHeight();

            // Create a temporary image to which the color filter will be applied.
            BufferedImage image = new BufferedImage(width, height,
                    BufferedImage.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);
            try {
                // The main draw operation.
                drawable.draw(imageGraphics, paint);

                // Apply the color filter.
                filter.applyFilter(imageGraphics, width, height);

                // Draw the tinted image on the main layer.
                configuredGraphics.drawImage(image, 0, 0, null);
                layer.change();
            } finally {
                // dispose Graphics2D objects
                imageGraphics.dispose();
                imageBaseGraphics.dispose();
                configuredGraphics.dispose();
            }
        }
    
private voiddrawOnGraphics(java.awt.Graphics2D g, com.android.layoutlib.bridge.impl.GcSnapshot$Drawable drawable, android.graphics.Paint_Delegate paint, com.android.layoutlib.bridge.impl.GcSnapshot$Layer layer)

        try {
            drawable.draw(g, paint);
            layer.change();
        } finally {
            g.dispose();
        }
    
public java.awt.ShapegetClip()
Returns the current clip, or null if none have been setup.

        if (mLayers.size() > 0) {
            // they all have the same clip
            return mLayers.get(0).getGraphics().getClip();
        } else {
            return mClip;
        }
    
public java.awt.geom.AffineTransformgetTransform()

        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;
        }
    
private voidmapRect(java.awt.geom.AffineTransform matrix, android.graphics.RectF dst, android.graphics.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]));
    
public com.android.layoutlib.bridge.impl.GcSnapshotrestore()
Restores the top {@link GcSnapshot}, and returns the next one.

        return doRestore();
    
private voidrestoreLayer(com.android.layoutlib.bridge.impl.GcSnapshot$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();
    
public com.android.layoutlib.bridge.impl.GcSnapshotrestoreTo(int saveCount)
Restores the {@link GcSnapshot} to saveCount.

param
saveCount the saveCount or -1 to only restore 1.
return
the new head of the Gc snapshot stack.

        return doRestoreTo(size(), saveCount);
    
public voidrotate(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 com.android.layoutlib.bridge.impl.GcSnapshotsave(int flags)
Saves the current state according to the given flags and returns the new current snapshot.

This is the equivalent of {@link Canvas#save(int)}

param
flags the save flags.
return
the new snapshot
see
Canvas#save(int)

        return new GcSnapshot(this, null /*layerbounds*/, null /*paint*/, flags);
    
public com.android.layoutlib.bridge.impl.GcSnapshotsaveLayer(android.graphics.RectF layerBounds, android.graphics.Paint_Delegate paint, int flags)
Saves the current state and creates a new layer, and returns the new current snapshot.

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)

        return new GcSnapshot(this, layerBounds, paint, flags);
    
public voidscale(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 voidsetBitmap(android.graphics.Bitmap_Delegate bitmap)
Link the snapshot to a Bitmap_Delegate.

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.

If any transform or clip information was set before, they are put into the Graphics object.

param
bitmap the bitmap to link to.

        // 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);

    
private voidsetComposite(java.awt.Graphics2D g, android.graphics.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;
        if (forceMode != 0) {
            g.setComposite(AlphaComposite.getInstance(forceMode, (float) alpha / 255.f));
            return;
        }
        Xfermode_Delegate xfermodeDelegate = paint.getXfermode();
        if (xfermodeDelegate != null) {
            if (xfermodeDelegate.isSupported()) {
                Composite composite = xfermodeDelegate.getComposite(alpha);
                assert composite != null;
                if (composite != null) {
                    g.setComposite(composite);
                    return;
                }
            } else {
                Bridge.getLog().fidelityWarning(LayoutLog.TAG_XFERMODE,
                        xfermodeDelegate.getSupportMessage(),
                        null /*throwable*/, null /*data*/);
            }
        }
        // if there was no custom xfermode, but we have alpha (due to a shader and a non
        // opaque alpha channel in the paint color), then we create an AlphaComposite anyway
        // that will handle the alpha.
        if (alpha != 0xFF) {
            g.setComposite(AlphaComposite.getInstance(
                    AlphaComposite.SRC_OVER, (float) alpha / 255.f));
        }
    
private booleansetShader(java.awt.Graphics2D g, android.graphics.Paint_Delegate paint)

        Shader_Delegate shaderDelegate = paint.getShader();
        if (shaderDelegate != null) {
            if (shaderDelegate.isSupported()) {
                java.awt.Paint shaderPaint = shaderDelegate.getJavaPaint();
                assert shaderPaint != null;
                if (shaderPaint != null) {
                    g.setPaint(shaderPaint);
                    return true;
                }
            } else {
                Bridge.getLog().fidelityWarning(LayoutLog.TAG_SHADER,
                        shaderDelegate.getSupportMessage(),
                        null /*throwable*/, null /*data*/);
            }
        }

        // if no shader, use the paint color
        g.setColor(new Color(paint.getColor(), true /*hasAlpha*/));

        return false;
    
public voidsetTransform(java.awt.geom.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 intsize()

        if (mPrevious != null) {
            return mPrevious.size() + 1;
        }

        return 1;
    
public voidtranslate(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);
        }