Java tutorial
/* * Copyright (C) 2007 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 android.graphics; import android.annotation.UnsupportedAppUsage; import java.io.InputStream; import java.io.OutputStream; /** * A Picture records drawing calls (via the canvas returned by beginRecording) * and can then play them back into Canvas (via {@link Picture#draw(Canvas)} or * {@link Canvas#drawPicture(Picture)}).For most content (e.g. text, lines, rectangles), * drawing a sequence from a picture can be faster than the equivalent API * calls, since the picture performs its playback without incurring any * method-call overhead. * * <p class="note"><strong>Note:</strong> Prior to API level 23 a picture cannot * be replayed on a hardware accelerated canvas.</p> */ public class Picture { private PictureCanvas mRecordingCanvas; @UnsupportedAppUsage private long mNativePicture; private boolean mRequiresHwAcceleration; private static final int WORKING_STREAM_STORAGE = 16 * 1024; /** * Creates an empty picture that is ready to record. */ public Picture() { this(nativeConstructor(0)); } /** * Create a picture by making a copy of what has already been recorded in * src. The contents of src are unchanged, and if src changes later, those * changes will not be reflected in this picture. */ public Picture(Picture src) { this(nativeConstructor(src != null ? src.mNativePicture : 0)); } private Picture(long nativePicture) { if (nativePicture == 0) { throw new RuntimeException(); } mNativePicture = nativePicture; } @Override protected void finalize() throws Throwable { try { nativeDestructor(mNativePicture); mNativePicture = 0; } finally { super.finalize(); } } /** * To record a picture, call beginRecording() and then draw into the Canvas * that is returned. Nothing we appear on screen, but all of the draw * commands (e.g. {@link Canvas#drawRect(Rect, Paint)}) will be recorded. * To stop recording, call endRecording(). After endRecording() the Canvas * that was returned must no longer be used, and nothing should be drawn * into it. */ public Canvas beginRecording(int width, int height) { if (mRecordingCanvas != null) { throw new IllegalStateException("Picture already recording, must call #endRecording()"); } long ni = nativeBeginRecording(mNativePicture, width, height); mRecordingCanvas = new PictureCanvas(this, ni); mRequiresHwAcceleration = false; return mRecordingCanvas; } /** * Call endRecording when the picture is built. After this call, the picture * may be drawn, but the canvas that was returned by beginRecording must not * be used anymore. This is automatically called if {@link Picture#draw} * or {@link Canvas#drawPicture(Picture)} is called. */ public void endRecording() { if (mRecordingCanvas != null) { mRequiresHwAcceleration = mRecordingCanvas.mHoldsHwBitmap; mRecordingCanvas = null; nativeEndRecording(mNativePicture); } } /** * Get the width of the picture as passed to beginRecording. This * does not reflect (per se) the content of the picture. */ public int getWidth() { return nativeGetWidth(mNativePicture); } /** * Get the height of the picture as passed to beginRecording. This * does not reflect (per se) the content of the picture. */ public int getHeight() { return nativeGetHeight(mNativePicture); } /** * Indicates whether or not this Picture contains recorded commands that only work when * drawn to a hardware-accelerated canvas. If this returns true then this Picture can only * be drawn to another Picture or to a Canvas where canvas.isHardwareAccelerated() is true. * * Note this value is only updated after recording has finished by a call to * {@link #endRecording()}. Prior to that it will be the default value of false. * * @return true if the Picture can only be drawn to a hardware-accelerated canvas, * false otherwise. */ public boolean requiresHardwareAcceleration() { return mRequiresHwAcceleration; } /** * Draw this picture on the canvas. * <p> * Prior to {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this call could * have the side effect of changing the matrix and clip of the canvas * if this picture had imbalanced saves/restores. * * <p> * <strong>Note:</strong> This forces the picture to internally call * {@link Picture#endRecording()} in order to prepare for playback. * * @param canvas The picture is drawn to this canvas */ public void draw(Canvas canvas) { if (mRecordingCanvas != null) { endRecording(); } if (mRequiresHwAcceleration && !canvas.isHardwareAccelerated()) { canvas.onHwBitmapInSwMode(); } nativeDraw(canvas.getNativeCanvasWrapper(), mNativePicture); } /** * Create a new picture (already recorded) from the data in the stream. This * data was generated by a previous call to writeToStream(). Pictures that * have been persisted across device restarts are not guaranteed to decode * properly and are highly discouraged. * * @see #writeToStream(java.io.OutputStream) * @deprecated The recommended alternative is to not use writeToStream and * instead draw the picture into a Bitmap from which you can persist it as * raw or compressed pixels. */ @Deprecated public static Picture createFromStream(InputStream stream) { return new Picture(nativeCreateFromStream(stream, new byte[WORKING_STREAM_STORAGE])); } /** * Write the picture contents to a stream. The data can be used to recreate * the picture in this or another process by calling createFromStream(...) * The resulting stream is NOT to be persisted across device restarts as * there is no guarantee that the Picture can be successfully reconstructed. * * @see #createFromStream(java.io.InputStream) * @deprecated The recommended alternative is to draw the picture into a * Bitmap from which you can persist it as raw or compressed pixels. */ @Deprecated public void writeToStream(OutputStream stream) { // do explicit check before calling the native method if (stream == null) { throw new NullPointerException(); } if (!nativeWriteToStream(mNativePicture, stream, new byte[WORKING_STREAM_STORAGE])) { throw new RuntimeException(); } } // return empty picture if src is 0, or a copy of the native src private static native long nativeConstructor(long nativeSrcOr0); private static native long nativeCreateFromStream(InputStream stream, byte[] storage); private static native int nativeGetWidth(long nativePicture); private static native int nativeGetHeight(long nativePicture); private static native long nativeBeginRecording(long nativeCanvas, int w, int h); private static native void nativeEndRecording(long nativeCanvas); private static native void nativeDraw(long nativeCanvas, long nativePicture); private static native boolean nativeWriteToStream(long nativePicture, OutputStream stream, byte[] storage); private static native void nativeDestructor(long nativePicture); private static class PictureCanvas extends Canvas { private final Picture mPicture; boolean mHoldsHwBitmap; public PictureCanvas(Picture pict, long nativeCanvas) { super(nativeCanvas); mPicture = pict; // Disable bitmap density scaling. This matches DisplayListCanvas. mDensity = 0; } @Override public void setBitmap(Bitmap bitmap) { throw new RuntimeException("Cannot call setBitmap on a picture canvas"); } @Override public void drawPicture(Picture picture) { if (mPicture == picture) { throw new RuntimeException("Cannot draw a picture into its recording canvas"); } super.drawPicture(picture); } @Override protected void onHwBitmapInSwMode() { mHoldsHwBitmap = true; } } }