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