1 /* Copyright (C) 2010 The Android Open Source Project 2 * 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16 package android.graphics; 17 18 import android.annotation.NonNull; 19 import android.annotation.Nullable; 20 import android.compat.annotation.UnsupportedAppUsage; 21 import android.content.res.AssetManager; 22 import android.os.Build; 23 import android.os.ParcelFileDescriptor; 24 25 import java.io.FileDescriptor; 26 import java.io.FileInputStream; 27 import java.io.IOException; 28 import java.io.InputStream; 29 30 /** 31 * BitmapRegionDecoder can be used to decode a rectangle region from an image. 32 * BitmapRegionDecoder is particularly useful when an original image is large and 33 * you only need parts of the image. 34 * 35 * <p>To create a BitmapRegionDecoder, call newInstance(...). 36 * Given a BitmapRegionDecoder, users can call decodeRegion() repeatedly 37 * to get a decoded Bitmap of the specified region. 38 * 39 */ 40 @android.ravenwood.annotation.RavenwoodKeepWholeClass 41 public final class BitmapRegionDecoder { 42 private long mNativeBitmapRegionDecoder; 43 private boolean mRecycled; 44 // ensures that the native decoder object exists and that only one decode can 45 // occur at a time. 46 private final Object mNativeLock = new Object(); 47 48 /** 49 * Create a BitmapRegionDecoder from the specified byte array. 50 * Currently only the JPEG, PNG, WebP and HEIF formats are supported. 51 * 52 * @param data byte array of compressed image data. 53 * @param offset offset into data for where the decoder should begin 54 * parsing. 55 * @param length the number of bytes, beginning at offset, to parse 56 * @param isShareable This field has been ignored since 57 * {@link Build.VERSION_CODES#GINGERBREAD}. 58 * @throws IOException if the image format is not supported or can not be decoded. 59 * @deprecated In favor of {@link #newInstance(byte[], int, int)} 60 */ 61 @Deprecated 62 @NonNull newInstance(@onNull byte[] data, int offset, int length, boolean isShareable)63 public static BitmapRegionDecoder newInstance(@NonNull byte[] data, 64 int offset, int length, boolean isShareable) throws IOException { 65 return newInstance(data, offset, length); 66 } 67 68 /** 69 * Create a BitmapRegionDecoder from the specified byte array. 70 * Currently only the JPEG, PNG, WebP and HEIF formats are supported. 71 * 72 * @param data byte array of compressed image data. 73 * @param offset offset into data for where the decoder should begin 74 * parsing. 75 * @param length the number of bytes, beginning at offset, to parse 76 * @throws IOException if the image format is not supported or can not be decoded. 77 */ 78 @NonNull newInstance(@onNull byte[] data, int offset, int length)79 public static BitmapRegionDecoder newInstance(@NonNull byte[] data, 80 int offset, int length) throws IOException { 81 if ((offset | length) < 0 || data.length < offset + length) { 82 throw new ArrayIndexOutOfBoundsException(); 83 } 84 return nativeNewInstance(data, offset, length); 85 } 86 87 /** 88 * Create a BitmapRegionDecoder from the file descriptor. 89 * The position within the descriptor will not be changed when 90 * this returns, so the descriptor can be used again as is. 91 * Currently only the JPEG, PNG, WebP and HEIF formats are supported. 92 * 93 * @param fd The file descriptor containing the data to decode 94 * @param isShareable This field has been ignored since 95 * {@link Build.VERSION_CODES#KITKAT}. 96 * @throws IOException if the image format is not supported or can not be decoded. 97 * @deprecated In favor of {@link #newInstance(ParcelFileDescriptor)} 98 */ 99 @Deprecated 100 @NonNull newInstance( @onNull FileDescriptor fd, boolean isShareable)101 public static BitmapRegionDecoder newInstance( 102 @NonNull FileDescriptor fd, boolean isShareable) throws IOException { 103 return nativeNewInstance(fd); 104 } 105 106 /** 107 * Create a BitmapRegionDecoder from the file descriptor. 108 * The position within the descriptor will not be changed when 109 * this returns, so the descriptor can be used again as is. 110 * Currently only the JPEG, PNG, WebP and HEIF formats are supported. 111 * 112 * @param pfd The parcel file descriptor containing the data to decode 113 * @throws IOException if the image format is not supported or can not be decoded. 114 */ 115 @NonNull newInstance( @onNull ParcelFileDescriptor pfd)116 public static BitmapRegionDecoder newInstance( 117 @NonNull ParcelFileDescriptor pfd) throws IOException { 118 return nativeNewInstance(pfd.getFileDescriptor()); 119 } 120 121 /** 122 * Create a BitmapRegionDecoder from an input stream. 123 * The stream's position will be where ever it was after the encoded data 124 * was read. 125 * Currently only the JPEG, PNG, WebP and HEIF formats are supported. 126 * 127 * @param is The input stream that holds the raw data to be decoded into a 128 * BitmapRegionDecoder. 129 * @param isShareable This field has always been ignored. 130 * @return A new BitmapRegionDecoder, or {@code null} if {@code is} is {@code null}. 131 * @throws IOException if the image format is not supported or can not be decoded. 132 * @deprecated In favor of {@link #newInstance(InputStream)} 133 * 134 * <p class="note">Prior to {@link Build.VERSION_CODES#KITKAT}, 135 * if {@link InputStream#markSupported is.markSupported()} returns true, 136 * <code>is.mark(1024)</code> would be called. As of 137 * {@link Build.VERSION_CODES#KITKAT}, this is no longer the case.</p> 138 */ 139 @Deprecated 140 @Nullable newInstance(@onNull InputStream is, boolean isShareable)141 public static BitmapRegionDecoder newInstance(@NonNull InputStream is, 142 boolean isShareable) throws IOException { 143 return newInstance(is); 144 } 145 146 /** 147 * Create a BitmapRegionDecoder from an input stream. 148 * The stream's position will be where ever it was after the encoded data 149 * was read. 150 * Currently only the JPEG, PNG, WebP and HEIF formats are supported. 151 * 152 * @param is The input stream that holds the raw data to be decoded into a 153 * BitmapRegionDecoder. 154 * @return A new BitmapRegionDecoder, or {@code null} if {@code is} is {@code null}. 155 * @throws IOException if the image format is not supported or can not be decoded. 156 */ 157 @Nullable newInstance(@onNull InputStream is)158 public static BitmapRegionDecoder newInstance(@NonNull InputStream is) throws IOException { 159 if (is instanceof AssetManager.AssetInputStream) { 160 return nativeNewInstance( 161 ((AssetManager.AssetInputStream) is).getNativeAsset()); 162 } else { 163 // pass some temp storage down to the native code. 1024 is made up, 164 // but should be large enough to avoid too many small calls back 165 // into is.read(...). 166 byte [] tempStorage = new byte[16 * 1024]; 167 return nativeNewInstance(is, tempStorage); 168 } 169 } 170 171 /** 172 * Create a BitmapRegionDecoder from a file path. 173 * Currently only the JPEG, PNG, WebP and HEIF formats are supported. 174 * 175 * @param pathName complete path name for the file to be decoded. 176 * @param isShareable This field has always been ignored. 177 * @throws IOException if the image format is not supported or can not be decoded. 178 * @deprecated In favor of {@link #newInstance(String)} 179 */ 180 @Deprecated 181 @NonNull newInstance(@onNull String pathName, boolean isShareable)182 public static BitmapRegionDecoder newInstance(@NonNull String pathName, 183 boolean isShareable) throws IOException { 184 return newInstance(pathName); 185 } 186 187 /** 188 * Create a BitmapRegionDecoder from a file path. 189 * Currently only the JPEG, PNG, WebP and HEIF formats are supported. 190 * 191 * @param pathName complete path name for the file to be decoded. 192 * @throws IOException if the image format is not supported or can not be decoded. 193 */ 194 @NonNull newInstance(@onNull String pathName)195 public static BitmapRegionDecoder newInstance(@NonNull String pathName) throws IOException { 196 BitmapRegionDecoder decoder = null; 197 InputStream stream = null; 198 199 try { 200 stream = new FileInputStream(pathName); 201 decoder = newInstance(stream); 202 } finally { 203 if (stream != null) { 204 try { 205 stream.close(); 206 } catch (IOException e) { 207 // do nothing here 208 } 209 } 210 } 211 return decoder; 212 } 213 214 /* Private constructor that must receive an already allocated native 215 region decoder int (pointer). 216 217 This can be called from JNI code. 218 */ 219 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) BitmapRegionDecoder(long decoder)220 private BitmapRegionDecoder(long decoder) { 221 mNativeBitmapRegionDecoder = decoder; 222 mRecycled = false; 223 } 224 225 /** 226 * Decodes a rectangle region in the image specified by rect. 227 * 228 * @param rect The rectangle that specified the region to be decode. 229 * @param options null-ok; Options that control downsampling. 230 * inPurgeable is not supported. 231 * @return The decoded bitmap, or null if the image data could not be 232 * decoded. 233 * @throws IllegalArgumentException if {@link BitmapFactory.Options#inPreferredConfig} 234 * is {@link android.graphics.Bitmap.Config#HARDWARE} 235 * and {@link BitmapFactory.Options#inMutable} is set, if the specified color space 236 * is not {@link ColorSpace.Model#RGB RGB}, or if the specified color space's transfer 237 * function is not an {@link ColorSpace.Rgb.TransferParameters ICC parametric curve} 238 */ decodeRegion(Rect rect, BitmapFactory.Options options)239 public Bitmap decodeRegion(Rect rect, BitmapFactory.Options options) { 240 BitmapFactory.Options.validate(options); 241 synchronized (mNativeLock) { 242 checkRecycled("decodeRegion called on recycled region decoder"); 243 if (rect.right <= 0 || rect.bottom <= 0 || rect.left >= getWidth() 244 || rect.top >= getHeight()) 245 throw new IllegalArgumentException("rectangle is outside the image"); 246 return nativeDecodeRegion(mNativeBitmapRegionDecoder, rect.left, rect.top, 247 rect.right - rect.left, rect.bottom - rect.top, options, 248 BitmapFactory.Options.nativeInBitmap(options), 249 BitmapFactory.Options.nativeColorSpace(options)); 250 } 251 } 252 253 /** Returns the original image's width */ getWidth()254 public int getWidth() { 255 synchronized (mNativeLock) { 256 checkRecycled("getWidth called on recycled region decoder"); 257 return nativeGetWidth(mNativeBitmapRegionDecoder); 258 } 259 } 260 261 /** Returns the original image's height */ getHeight()262 public int getHeight() { 263 synchronized (mNativeLock) { 264 checkRecycled("getHeight called on recycled region decoder"); 265 return nativeGetHeight(mNativeBitmapRegionDecoder); 266 } 267 } 268 269 /** 270 * Frees up the memory associated with this region decoder, and mark the 271 * region decoder as "dead", meaning it will throw an exception if decodeRegion(), 272 * getWidth() or getHeight() is called. 273 * 274 * <p>This operation cannot be reversed, so it should only be called if you are 275 * sure there are no further uses for the region decoder. This is an advanced call, 276 * and normally need not be called, since the normal GC process will free up this 277 * memory when there are no more references to this region decoder. 278 */ recycle()279 public void recycle() { 280 synchronized (mNativeLock) { 281 if (!mRecycled) { 282 nativeClean(mNativeBitmapRegionDecoder); 283 mRecycled = true; 284 } 285 } 286 } 287 288 /** 289 * Returns true if this region decoder has been recycled. 290 * If so, then it is an error to try use its method. 291 * 292 * @return true if the region decoder has been recycled 293 */ isRecycled()294 public final boolean isRecycled() { 295 return mRecycled; 296 } 297 298 /** 299 * Called by methods that want to throw an exception if the region decoder 300 * has already been recycled. 301 */ checkRecycled(String errorMessage)302 private void checkRecycled(String errorMessage) { 303 if (mRecycled) { 304 throw new IllegalStateException(errorMessage); 305 } 306 } 307 308 @Override finalize()309 protected void finalize() throws Throwable { 310 try { 311 recycle(); 312 } finally { 313 super.finalize(); 314 } 315 } 316 nativeDecodeRegion(long lbm, int start_x, int start_y, int width, int height, BitmapFactory.Options options, long inBitmapHandle, long colorSpaceHandle)317 private static native Bitmap nativeDecodeRegion(long lbm, 318 int start_x, int start_y, int width, int height, 319 BitmapFactory.Options options, long inBitmapHandle, 320 long colorSpaceHandle); nativeGetWidth(long lbm)321 private static native int nativeGetWidth(long lbm); nativeGetHeight(long lbm)322 private static native int nativeGetHeight(long lbm); nativeClean(long lbm)323 private static native void nativeClean(long lbm); 324 nativeNewInstance( byte[] data, int offset, int length)325 private static native BitmapRegionDecoder nativeNewInstance( 326 byte[] data, int offset, int length); nativeNewInstance( FileDescriptor fd)327 private static native BitmapRegionDecoder nativeNewInstance( 328 FileDescriptor fd); nativeNewInstance( InputStream is, byte[] storage)329 private static native BitmapRegionDecoder nativeNewInstance( 330 InputStream is, byte[] storage); nativeNewInstance( long asset)331 private static native BitmapRegionDecoder nativeNewInstance( 332 long asset); 333 } 334