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