1/* 2 * Copyright (C) 2007 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 android.graphics; 18 19import java.io.InputStream; 20import java.io.OutputStream; 21 22/** 23 * A Picture records drawing calls (via the canvas returned by beginRecording) 24 * and can then play them back into Canvas (via {@link Picture#draw(Canvas)} or 25 * {@link Canvas#drawPicture(Picture)}).For most content (e.g. text, lines, rectangles), 26 * drawing a sequence from a picture can be faster than the equivalent API 27 * calls, since the picture performs its playback without incurring any 28 * method-call overhead. 29 * 30 * <p class="note"><strong>Note:</strong> Prior to API level 23 a picture cannot 31 * be replayed on a hardware accelerated canvas.</p> 32 */ 33public class Picture { 34 private PictureCanvas mRecordingCanvas; 35 private long mNativePicture; 36 private boolean mRequiresHwAcceleration; 37 38 private static final int WORKING_STREAM_STORAGE = 16 * 1024; 39 40 /** 41 * Creates an empty picture that is ready to record. 42 */ 43 public Picture() { 44 this(nativeConstructor(0)); 45 } 46 47 /** 48 * Create a picture by making a copy of what has already been recorded in 49 * src. The contents of src are unchanged, and if src changes later, those 50 * changes will not be reflected in this picture. 51 */ 52 public Picture(Picture src) { 53 this(nativeConstructor(src != null ? src.mNativePicture : 0)); 54 } 55 56 private Picture(long nativePicture) { 57 if (nativePicture == 0) { 58 throw new RuntimeException(); 59 } 60 mNativePicture = nativePicture; 61 } 62 63 @Override 64 protected void finalize() throws Throwable { 65 try { 66 nativeDestructor(mNativePicture); 67 mNativePicture = 0; 68 } finally { 69 super.finalize(); 70 } 71 } 72 73 /** 74 * To record a picture, call beginRecording() and then draw into the Canvas 75 * that is returned. Nothing we appear on screen, but all of the draw 76 * commands (e.g. {@link Canvas#drawRect(Rect, Paint)}) will be recorded. 77 * To stop recording, call endRecording(). After endRecording() the Canvas 78 * that was returned must no longer be used, and nothing should be drawn 79 * into it. 80 */ 81 public Canvas beginRecording(int width, int height) { 82 if (mRecordingCanvas != null) { 83 throw new IllegalStateException("Picture already recording, must call #endRecording()"); 84 } 85 long ni = nativeBeginRecording(mNativePicture, width, height); 86 mRecordingCanvas = new PictureCanvas(this, ni); 87 mRequiresHwAcceleration = false; 88 return mRecordingCanvas; 89 } 90 91 /** 92 * Call endRecording when the picture is built. After this call, the picture 93 * may be drawn, but the canvas that was returned by beginRecording must not 94 * be used anymore. This is automatically called if {@link Picture#draw} 95 * or {@link Canvas#drawPicture(Picture)} is called. 96 */ 97 public void endRecording() { 98 if (mRecordingCanvas != null) { 99 mRequiresHwAcceleration = mRecordingCanvas.mHoldsHwBitmap; 100 mRecordingCanvas = null; 101 nativeEndRecording(mNativePicture); 102 } 103 } 104 105 /** 106 * Get the width of the picture as passed to beginRecording. This 107 * does not reflect (per se) the content of the picture. 108 */ 109 public int getWidth() { 110 return nativeGetWidth(mNativePicture); 111 } 112 113 /** 114 * Get the height of the picture as passed to beginRecording. This 115 * does not reflect (per se) the content of the picture. 116 */ 117 public int getHeight() { 118 return nativeGetHeight(mNativePicture); 119 } 120 121 /** 122 * Indicates whether or not this Picture contains recorded commands that only work when 123 * drawn to a hardware-accelerated canvas. If this returns true then this Picture can only 124 * be drawn to another Picture or to a Canvas where canvas.isHardwareAccelerated() is true. 125 * 126 * Note this value is only updated after recording has finished by a call to 127 * {@link #endRecording()}. Prior to that it will be the default value of false. 128 * 129 * @return true if the Picture can only be drawn to a hardware-accelerated canvas, 130 * false otherwise. 131 */ 132 public boolean requiresHardwareAcceleration() { 133 return mRequiresHwAcceleration; 134 } 135 136 /** 137 * Draw this picture on the canvas. 138 * <p> 139 * Prior to {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this call could 140 * have the side effect of changing the matrix and clip of the canvas 141 * if this picture had imbalanced saves/restores. 142 * 143 * <p> 144 * <strong>Note:</strong> This forces the picture to internally call 145 * {@link Picture#endRecording()} in order to prepare for playback. 146 * 147 * @param canvas The picture is drawn to this canvas 148 */ 149 public void draw(Canvas canvas) { 150 if (mRecordingCanvas != null) { 151 endRecording(); 152 } 153 if (mRequiresHwAcceleration && !canvas.isHardwareAccelerated()) { 154 canvas.onHwBitmapInSwMode(); 155 } 156 nativeDraw(canvas.getNativeCanvasWrapper(), mNativePicture); 157 } 158 159 /** 160 * Create a new picture (already recorded) from the data in the stream. This 161 * data was generated by a previous call to writeToStream(). Pictures that 162 * have been persisted across device restarts are not guaranteed to decode 163 * properly and are highly discouraged. 164 * 165 * @see #writeToStream(java.io.OutputStream) 166 * @deprecated The recommended alternative is to not use writeToStream and 167 * instead draw the picture into a Bitmap from which you can persist it as 168 * raw or compressed pixels. 169 */ 170 @Deprecated 171 public static Picture createFromStream(InputStream stream) { 172 return new Picture(nativeCreateFromStream(stream, new byte[WORKING_STREAM_STORAGE])); 173 } 174 175 /** 176 * Write the picture contents to a stream. The data can be used to recreate 177 * the picture in this or another process by calling createFromStream(...) 178 * The resulting stream is NOT to be persisted across device restarts as 179 * there is no guarantee that the Picture can be successfully reconstructed. 180 * 181 * @see #createFromStream(java.io.InputStream) 182 * @deprecated The recommended alternative is to draw the picture into a 183 * Bitmap from which you can persist it as raw or compressed pixels. 184 */ 185 @Deprecated 186 public void writeToStream(OutputStream stream) { 187 // do explicit check before calling the native method 188 if (stream == null) { 189 throw new NullPointerException(); 190 } 191 if (!nativeWriteToStream(mNativePicture, stream, new byte[WORKING_STREAM_STORAGE])) { 192 throw new RuntimeException(); 193 } 194 } 195 196 // return empty picture if src is 0, or a copy of the native src 197 private static native long nativeConstructor(long nativeSrcOr0); 198 private static native long nativeCreateFromStream(InputStream stream, byte[] storage); 199 private static native int nativeGetWidth(long nativePicture); 200 private static native int nativeGetHeight(long nativePicture); 201 private static native long nativeBeginRecording(long nativeCanvas, int w, int h); 202 private static native void nativeEndRecording(long nativeCanvas); 203 private static native void nativeDraw(long nativeCanvas, long nativePicture); 204 private static native boolean nativeWriteToStream(long nativePicture, 205 OutputStream stream, byte[] storage); 206 private static native void nativeDestructor(long nativePicture); 207 208 private static class PictureCanvas extends Canvas { 209 private final Picture mPicture; 210 boolean mHoldsHwBitmap; 211 212 public PictureCanvas(Picture pict, long nativeCanvas) { 213 super(nativeCanvas); 214 mPicture = pict; 215 // Disable bitmap density scaling. This matches DisplayListCanvas. 216 mDensity = 0; 217 } 218 219 @Override 220 public void setBitmap(Bitmap bitmap) { 221 throw new RuntimeException("Cannot call setBitmap on a picture canvas"); 222 } 223 224 @Override 225 public void drawPicture(Picture picture) { 226 if (mPicture == picture) { 227 throw new RuntimeException("Cannot draw a picture into its recording canvas"); 228 } 229 super.drawPicture(picture); 230 } 231 232 @Override 233 protected void onHwBitmapInSwMode() { 234 mHoldsHwBitmap = true; 235 } 236 } 237} 238