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