1bcf4a0dae04a4ad14287eeb34069a97c96fe9bb1Sam Juddpackage com.bumptech.glide.load.resource.bitmap; 2c27229a159bfc992874609270ab8b57981fef339Sam Judd 3c27229a159bfc992874609270ab8b57981fef339Sam Juddimport android.annotation.TargetApi; 4c27229a159bfc992874609270ab8b57981fef339Sam Juddimport android.graphics.Bitmap; 5c27229a159bfc992874609270ab8b57981fef339Sam Juddimport android.graphics.BitmapFactory; 6c27229a159bfc992874609270ab8b57981fef339Sam Juddimport android.os.Build; 7e81c2c3c8fe1533de8ba8137ea831a42378b35a9Sam Juddimport android.util.Log; 8e7319b67364bd0ac6306bc7470a43d4a31600c1aRobert Papp 99fc12334a7d14347cd6951d0653264b2597bd3a0Sam Juddimport com.bumptech.glide.load.DecodeFormat; 10aed5a1923b6add5101689ca462107cc16877b05eSam Juddimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool; 110ae32dc10d668a04f9f0484d587aefe5a7210e1cSam Juddimport com.bumptech.glide.util.ByteArrayPool; 12526ae88675e01956aa14415b0cba527cf4f1cb0aSam Juddimport com.bumptech.glide.util.ExceptionCatchingInputStream; 13e8b5d67302f343bfa262c8c90fda5145813f292aRobert Pappimport com.bumptech.glide.util.Util; 140ae32dc10d668a04f9f0484d587aefe5a7210e1cSam Judd 15c27229a159bfc992874609270ab8b57981fef339Sam Juddimport java.io.IOException; 160ae32dc10d668a04f9f0484d587aefe5a7210e1cSam Juddimport java.io.InputStream; 17c24284108320cf3d613497ddd67ba4e1b232ce74Savvas Dalkitsisimport java.util.EnumSet; 18aed5a1923b6add5101689ca462107cc16877b05eSam Juddimport java.util.Queue; 19c24284108320cf3d613497ddd67ba4e1b232ce74Savvas Dalkitsisimport java.util.Set; 20c27229a159bfc992874609270ab8b57981fef339Sam Judd 21c27229a159bfc992874609270ab8b57981fef339Sam Judd/** 22c27229a159bfc992874609270ab8b57981fef339Sam Judd * A base class with methods for loading and decoding images from InputStreams. 23c27229a159bfc992874609270ab8b57981fef339Sam Judd */ 240ae32dc10d668a04f9f0484d587aefe5a7210e1cSam Juddpublic abstract class Downsampler implements BitmapDecoder<InputStream> { 25e81c2c3c8fe1533de8ba8137ea831a42378b35a9Sam Judd private static final String TAG = "Downsampler"; 26c24284108320cf3d613497ddd67ba4e1b232ce74Savvas Dalkitsis 279fc12334a7d14347cd6951d0653264b2597bd3a0Sam Judd private static final Set<ImageHeaderParser.ImageType> TYPES_THAT_USE_POOL = EnumSet.of( 289fc12334a7d14347cd6951d0653264b2597bd3a0Sam Judd ImageHeaderParser.ImageType.JPEG, ImageHeaderParser.ImageType.PNG_A, ImageHeaderParser.ImageType.PNG); 290ae32dc10d668a04f9f0484d587aefe5a7210e1cSam Judd 30e8b5d67302f343bfa262c8c90fda5145813f292aRobert Papp private static final Queue<BitmapFactory.Options> OPTIONS_QUEUE = Util.createQueue(0); 31aed5a1923b6add5101689ca462107cc16877b05eSam Judd 32c27229a159bfc992874609270ab8b57981fef339Sam Judd /** 33c27229a159bfc992874609270ab8b57981fef339Sam Judd * Load and scale the image uniformly (maintaining the image's aspect ratio) so that the dimensions of the image 34c27229a159bfc992874609270ab8b57981fef339Sam Judd * will be greater than or equal to the given width and height. 35c27229a159bfc992874609270ab8b57981fef339Sam Judd */ 36fe090f50f3040f4d478143a3e0ffa8cdf813fefcSam Judd public static final Downsampler AT_LEAST = new Downsampler() { 37c27229a159bfc992874609270ab8b57981fef339Sam Judd @Override 38c27229a159bfc992874609270ab8b57981fef339Sam Judd protected int getSampleSize(int inWidth, int inHeight, int outWidth, int outHeight) { 39c27229a159bfc992874609270ab8b57981fef339Sam Judd return Math.min(inHeight / outHeight, inWidth / outWidth); 40c27229a159bfc992874609270ab8b57981fef339Sam Judd } 41423bc54484b4be962955b2c194cf72edf705a935Sam Judd 42423bc54484b4be962955b2c194cf72edf705a935Sam Judd @Override 43423bc54484b4be962955b2c194cf72edf705a935Sam Judd public String getId() { 44423bc54484b4be962955b2c194cf72edf705a935Sam Judd return "AT_LEAST.com.bumptech.glide.load.data.bitmap"; 45423bc54484b4be962955b2c194cf72edf705a935Sam Judd } 46c27229a159bfc992874609270ab8b57981fef339Sam Judd }; 47c27229a159bfc992874609270ab8b57981fef339Sam Judd 48c27229a159bfc992874609270ab8b57981fef339Sam Judd /** 49c27229a159bfc992874609270ab8b57981fef339Sam Judd * Load and scale the image uniformly (maintaining the image's aspect ratio) so that the dimensions of the image 50c27229a159bfc992874609270ab8b57981fef339Sam Judd * will be less than or equal to the given width and height. 51c27229a159bfc992874609270ab8b57981fef339Sam Judd * 52c27229a159bfc992874609270ab8b57981fef339Sam Judd */ 53fe090f50f3040f4d478143a3e0ffa8cdf813fefcSam Judd public static final Downsampler AT_MOST = new Downsampler() { 54c27229a159bfc992874609270ab8b57981fef339Sam Judd @Override 55c27229a159bfc992874609270ab8b57981fef339Sam Judd protected int getSampleSize(int inWidth, int inHeight, int outWidth, int outHeight) { 56c27229a159bfc992874609270ab8b57981fef339Sam Judd return Math.max(inHeight / outHeight, inWidth / outWidth); 57c27229a159bfc992874609270ab8b57981fef339Sam Judd } 58423bc54484b4be962955b2c194cf72edf705a935Sam Judd 59423bc54484b4be962955b2c194cf72edf705a935Sam Judd @Override 60423bc54484b4be962955b2c194cf72edf705a935Sam Judd public String getId() { 61423bc54484b4be962955b2c194cf72edf705a935Sam Judd return "AT_MOST.com.bumptech.glide.load.data.bitmap"; 62423bc54484b4be962955b2c194cf72edf705a935Sam Judd } 63c27229a159bfc992874609270ab8b57981fef339Sam Judd }; 64c27229a159bfc992874609270ab8b57981fef339Sam Judd 65c27229a159bfc992874609270ab8b57981fef339Sam Judd /** 66fe090f50f3040f4d478143a3e0ffa8cdf813fefcSam Judd * Load the image at its original size. 67c27229a159bfc992874609270ab8b57981fef339Sam Judd */ 68fe090f50f3040f4d478143a3e0ffa8cdf813fefcSam Judd public static final Downsampler NONE = new Downsampler() { 69c27229a159bfc992874609270ab8b57981fef339Sam Judd @Override 70c27229a159bfc992874609270ab8b57981fef339Sam Judd protected int getSampleSize(int inWidth, int inHeight, int outWidth, int outHeight) { 71c27229a159bfc992874609270ab8b57981fef339Sam Judd return 0; 72c27229a159bfc992874609270ab8b57981fef339Sam Judd } 73423bc54484b4be962955b2c194cf72edf705a935Sam Judd 74423bc54484b4be962955b2c194cf72edf705a935Sam Judd @Override 75423bc54484b4be962955b2c194cf72edf705a935Sam Judd public String getId() { 76423bc54484b4be962955b2c194cf72edf705a935Sam Judd return "NONE.com.bumptech.glide.load.data.bitmap"; 77423bc54484b4be962955b2c194cf72edf705a935Sam Judd } 78c27229a159bfc992874609270ab8b57981fef339Sam Judd }; 79c27229a159bfc992874609270ab8b57981fef339Sam Judd 800ae32dc10d668a04f9f0484d587aefe5a7210e1cSam Judd // 5MB. This is the max image header size we can handle, we preallocate a much smaller buffer but will resize up to 810ae32dc10d668a04f9f0484d587aefe5a7210e1cSam Judd // this amount if necessary. 820ae32dc10d668a04f9f0484d587aefe5a7210e1cSam Judd private static final int MARK_POSITION = 5 * 1024 * 1024; 830ae32dc10d668a04f9f0484d587aefe5a7210e1cSam Judd 84c27229a159bfc992874609270ab8b57981fef339Sam Judd 85c27229a159bfc992874609270ab8b57981fef339Sam Judd /** 86c27229a159bfc992874609270ab8b57981fef339Sam Judd * Load the image for the given InputStream. If a recycled Bitmap whose dimensions exactly match those of the image 87c27229a159bfc992874609270ab8b57981fef339Sam Judd * for the given InputStream is available, the operation is much less expensive in terms of memory. 88c27229a159bfc992874609270ab8b57981fef339Sam Judd * 89ae05603c3800df74390089d6a6c1562708cbca12Robert Papp * <p> 90ae05603c3800df74390089d6a6c1562708cbca12Robert Papp * Note - this method will throw an exception of a Bitmap with dimensions not matching 91ae05603c3800df74390089d6a6c1562708cbca12Robert Papp * those of the image for the given InputStream is provided. 92ae05603c3800df74390089d6a6c1562708cbca12Robert Papp * </p> 93c27229a159bfc992874609270ab8b57981fef339Sam Judd * 94051069671413f8b66a1b237a8f682aa6c1b50275Sam Judd * @param is An {@link InputStream} to the data for the image. 95051069671413f8b66a1b237a8f682aa6c1b50275Sam Judd * @param pool A pool of recycled bitmaps. 96051069671413f8b66a1b237a8f682aa6c1b50275Sam Judd * @param outWidth The width the final image should be close to. 97051069671413f8b66a1b237a8f682aa6c1b50275Sam Judd * @param outHeight The height the final image should be close to. 98051069671413f8b66a1b237a8f682aa6c1b50275Sam Judd * @return A new bitmap containing the image from the given InputStream, or recycle if recycle is not null. 99c27229a159bfc992874609270ab8b57981fef339Sam Judd */ 100ae05603c3800df74390089d6a6c1562708cbca12Robert Papp @SuppressWarnings("resource") 101ae05603c3800df74390089d6a6c1562708cbca12Robert Papp // see BitmapDecoder.decode 1020ae32dc10d668a04f9f0484d587aefe5a7210e1cSam Judd @Override 10376fbad3dbce72240e9f5b82c826e3229c1176fb6Sam Judd public Bitmap decode(InputStream is, BitmapPool pool, int outWidth, int outHeight, DecodeFormat decodeFormat) { 1040ae32dc10d668a04f9f0484d587aefe5a7210e1cSam Judd final ByteArrayPool byteArrayPool = ByteArrayPool.get(); 105ae05603c3800df74390089d6a6c1562708cbca12Robert Papp final byte[] bytesForOptions = byteArrayPool.getBytes(); 106ae05603c3800df74390089d6a6c1562708cbca12Robert Papp final byte[] bytesForStream = byteArrayPool.getBytes(); 107ae05603c3800df74390089d6a6c1562708cbca12Robert Papp final BitmapFactory.Options options = getDefaultOptions(); 1082007440c380df454db15f83b019a5b4c55ca4b72Sam Judd 109526ae88675e01956aa14415b0cba527cf4f1cb0aSam Judd // TODO(#126): when the framework handles exceptions better, consider removing. 110526ae88675e01956aa14415b0cba527cf4f1cb0aSam Judd final ExceptionCatchingInputStream stream = 111526ae88675e01956aa14415b0cba527cf4f1cb0aSam Judd ExceptionCatchingInputStream.obtain(new RecyclableBufferedInputStream(is, bytesForStream)); 112d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd try { 113526ae88675e01956aa14415b0cba527cf4f1cb0aSam Judd stream.mark(MARK_POSITION); 114ae05603c3800df74390089d6a6c1562708cbca12Robert Papp int orientation = 0; 115ae05603c3800df74390089d6a6c1562708cbca12Robert Papp try { 116526ae88675e01956aa14415b0cba527cf4f1cb0aSam Judd orientation = new ImageHeaderParser(stream).getOrientation(); 117ae05603c3800df74390089d6a6c1562708cbca12Robert Papp } catch (IOException e) { 118ae05603c3800df74390089d6a6c1562708cbca12Robert Papp if (Log.isLoggable(TAG, Log.WARN)) { 119ae05603c3800df74390089d6a6c1562708cbca12Robert Papp Log.w(TAG, "Cannot determine the image orientation from header", e); 120ae05603c3800df74390089d6a6c1562708cbca12Robert Papp } 121ae05603c3800df74390089d6a6c1562708cbca12Robert Papp } finally { 122ae05603c3800df74390089d6a6c1562708cbca12Robert Papp try { 123526ae88675e01956aa14415b0cba527cf4f1cb0aSam Judd stream.reset(); 124ae05603c3800df74390089d6a6c1562708cbca12Robert Papp } catch (IOException e) { 125ae05603c3800df74390089d6a6c1562708cbca12Robert Papp if (Log.isLoggable(TAG, Log.WARN)) { 126ae05603c3800df74390089d6a6c1562708cbca12Robert Papp Log.w(TAG, "Cannot reset the input stream", e); 127ae05603c3800df74390089d6a6c1562708cbca12Robert Papp } 128ae05603c3800df74390089d6a6c1562708cbca12Robert Papp } 129ae05603c3800df74390089d6a6c1562708cbca12Robert Papp } 130d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd 131ae05603c3800df74390089d6a6c1562708cbca12Robert Papp options.inTempStorage = bytesForOptions; 132c27229a159bfc992874609270ab8b57981fef339Sam Judd 133526ae88675e01956aa14415b0cba527cf4f1cb0aSam Judd final int[] inDimens = getDimensions(stream, options); 134ae05603c3800df74390089d6a6c1562708cbca12Robert Papp final int inWidth = inDimens[0]; 135ae05603c3800df74390089d6a6c1562708cbca12Robert Papp final int inHeight = inDimens[1]; 136d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd 137ae05603c3800df74390089d6a6c1562708cbca12Robert Papp final int degreesToRotate = TransformationUtils.getExifOrientationDegrees(orientation); 13888868380fb22a5dae89d8664f2daa8c99522bc74Sam Judd final int sampleSize = getRoundedSampleSize(degreesToRotate, inWidth, inHeight, outWidth, outHeight); 139ae05603c3800df74390089d6a6c1562708cbca12Robert Papp 140ae05603c3800df74390089d6a6c1562708cbca12Robert Papp final Bitmap downsampled = 1412007440c380df454db15f83b019a5b4c55ca4b72Sam Judd downsampleWithSize(stream, options, pool, inWidth, inHeight, sampleSize, 1422007440c380df454db15f83b019a5b4c55ca4b72Sam Judd decodeFormat); 143526ae88675e01956aa14415b0cba527cf4f1cb0aSam Judd 1442007440c380df454db15f83b019a5b4c55ca4b72Sam Judd // BitmapFactory swallows exceptions during decodes and in some cases when inBitmap is non null, may catch 145526ae88675e01956aa14415b0cba527cf4f1cb0aSam Judd // and log a stack trace but still return a non null bitmap. To avoid displaying partially decoded bitmaps, 146526ae88675e01956aa14415b0cba527cf4f1cb0aSam Judd // we catch exceptions reading from the stream in our ExceptionCatchingInputStream and throw them here. 147526ae88675e01956aa14415b0cba527cf4f1cb0aSam Judd final Exception streamException = stream.getException(); 148526ae88675e01956aa14415b0cba527cf4f1cb0aSam Judd if (streamException != null) { 149526ae88675e01956aa14415b0cba527cf4f1cb0aSam Judd throw new RuntimeException(streamException); 150526ae88675e01956aa14415b0cba527cf4f1cb0aSam Judd } 151dbb67f826b0e76645c809be6d589e9dcb8271324Sam Judd 152ae05603c3800df74390089d6a6c1562708cbca12Robert Papp Bitmap rotated = null; 153ae05603c3800df74390089d6a6c1562708cbca12Robert Papp if (downsampled != null) { 154ae05603c3800df74390089d6a6c1562708cbca12Robert Papp rotated = TransformationUtils.rotateImageExif(downsampled, pool, orientation); 155ae05603c3800df74390089d6a6c1562708cbca12Robert Papp 1565ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd if (!downsampled.equals(rotated) && !pool.put(downsampled)) { 157ae05603c3800df74390089d6a6c1562708cbca12Robert Papp downsampled.recycle(); 158ae05603c3800df74390089d6a6c1562708cbca12Robert Papp } 159dbb67f826b0e76645c809be6d589e9dcb8271324Sam Judd } 160d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd 161ae05603c3800df74390089d6a6c1562708cbca12Robert Papp return rotated; 162ae05603c3800df74390089d6a6c1562708cbca12Robert Papp } finally { 163ae05603c3800df74390089d6a6c1562708cbca12Robert Papp byteArrayPool.releaseBytes(bytesForOptions); 164ae05603c3800df74390089d6a6c1562708cbca12Robert Papp byteArrayPool.releaseBytes(bytesForStream); 165526ae88675e01956aa14415b0cba527cf4f1cb0aSam Judd stream.release(); 166ae05603c3800df74390089d6a6c1562708cbca12Robert Papp releaseOptions(options); 167ae05603c3800df74390089d6a6c1562708cbca12Robert Papp } 168c27229a159bfc992874609270ab8b57981fef339Sam Judd } 169c27229a159bfc992874609270ab8b57981fef339Sam Judd 17088868380fb22a5dae89d8664f2daa8c99522bc74Sam Judd private int getRoundedSampleSize(int degreesToRotate, int inWidth, int inHeight, int outWidth, int outHeight) { 17188868380fb22a5dae89d8664f2daa8c99522bc74Sam Judd final int exactSampleSize; 17288868380fb22a5dae89d8664f2daa8c99522bc74Sam Judd if (degreesToRotate == 90 || degreesToRotate == 270) { 17388868380fb22a5dae89d8664f2daa8c99522bc74Sam Judd // If we're rotating the image +-90 degrees, we need to downsample accordingly so the image width is 17488868380fb22a5dae89d8664f2daa8c99522bc74Sam Judd // decreased to near our target's height and the image height is decreased to near our target width. 17588868380fb22a5dae89d8664f2daa8c99522bc74Sam Judd exactSampleSize = getSampleSize(inHeight, inWidth, outWidth, outHeight); 17688868380fb22a5dae89d8664f2daa8c99522bc74Sam Judd } else { 17788868380fb22a5dae89d8664f2daa8c99522bc74Sam Judd exactSampleSize = getSampleSize(inWidth, inHeight, outWidth, outHeight); 17888868380fb22a5dae89d8664f2daa8c99522bc74Sam Judd } 17988868380fb22a5dae89d8664f2daa8c99522bc74Sam Judd 18088868380fb22a5dae89d8664f2daa8c99522bc74Sam Judd // BitmapFactory only accepts powers of 2, so it will round down to the nearest power of two that is less than 18188868380fb22a5dae89d8664f2daa8c99522bc74Sam Judd // or equal to the sample size we provide. Because we need to estimate the final image width and height to 18288868380fb22a5dae89d8664f2daa8c99522bc74Sam Judd // re-use Bitmaps, we mirror BitmapFactory's calculation here. For bug, see issue #224. For algorithm see 18388868380fb22a5dae89d8664f2daa8c99522bc74Sam Judd // http://stackoverflow.com/a/17379704/800716. 18488868380fb22a5dae89d8664f2daa8c99522bc74Sam Judd final int powerOfTwoSampleSize = exactSampleSize == 0 ? 0 : Integer.highestOneBit(exactSampleSize - 1); 18588868380fb22a5dae89d8664f2daa8c99522bc74Sam Judd 18688868380fb22a5dae89d8664f2daa8c99522bc74Sam Judd // Although functionally equivalent to 0 for BitmapFactory, 1 is a safer default for our code than 0. 18788868380fb22a5dae89d8664f2daa8c99522bc74Sam Judd return Math.max(1, powerOfTwoSampleSize); 18888868380fb22a5dae89d8664f2daa8c99522bc74Sam Judd } 18988868380fb22a5dae89d8664f2daa8c99522bc74Sam Judd 1902007440c380df454db15f83b019a5b4c55ca4b72Sam Judd private Bitmap downsampleWithSize(ExceptionCatchingInputStream is, BitmapFactory.Options options, BitmapPool pool, 1912007440c380df454db15f83b019a5b4c55ca4b72Sam Judd int inWidth, int inHeight, int sampleSize, DecodeFormat decodeFormat) { 192da25daffc2047b70f7cf3eb1f6e7ae53043d9337Sam Judd // Prior to KitKat, the inBitmap size must exactly match the size of the bitmap we're decoding. 193526ae88675e01956aa14415b0cba527cf4f1cb0aSam Judd Bitmap.Config config = getConfig(is, decodeFormat); 194da25daffc2047b70f7cf3eb1f6e7ae53043d9337Sam Judd options.inSampleSize = sampleSize; 19576fbad3dbce72240e9f5b82c826e3229c1176fb6Sam Judd options.inPreferredConfig = config; 1965ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd if ((options.inSampleSize == 1 || Build.VERSION_CODES.KITKAT <= Build.VERSION.SDK_INT) && shouldUsePool(is)) { 19788868380fb22a5dae89d8664f2daa8c99522bc74Sam Judd int targetWidth = (int) Math.ceil(inWidth / (double) sampleSize); 19888868380fb22a5dae89d8664f2daa8c99522bc74Sam Judd int targetHeight = (int) Math.ceil(inHeight / (double) sampleSize); 1995ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd // BitmapFactory will clear out the Bitmap before writing to it, so getDirty is safe. 20088868380fb22a5dae89d8664f2daa8c99522bc74Sam Judd setInBitmap(options, pool.getDirty(targetWidth, targetHeight, config)); 201293fdc0fc9203e2286beef092ac4a1fcec55cd0dSam Judd } 202526ae88675e01956aa14415b0cba527cf4f1cb0aSam Judd return decodeStream(is, options); 203293fdc0fc9203e2286beef092ac4a1fcec55cd0dSam Judd } 204293fdc0fc9203e2286beef092ac4a1fcec55cd0dSam Judd 205526ae88675e01956aa14415b0cba527cf4f1cb0aSam Judd private static boolean shouldUsePool(InputStream is) { 206da25daffc2047b70f7cf3eb1f6e7ae53043d9337Sam Judd // On KitKat+, any bitmap can be used to decode any other bitmap. 207e7319b67364bd0ac6306bc7470a43d4a31600c1aRobert Papp if (Build.VERSION_CODES.KITKAT <= Build.VERSION.SDK_INT) { 208da25daffc2047b70f7cf3eb1f6e7ae53043d9337Sam Judd return true; 209da25daffc2047b70f7cf3eb1f6e7ae53043d9337Sam Judd } 210da25daffc2047b70f7cf3eb1f6e7ae53043d9337Sam Judd 211526ae88675e01956aa14415b0cba527cf4f1cb0aSam Judd is.mark(1024); 2122310318f0ff9ad1919ec61878a43cb8dc3bd5086Savvas Dalkitsis try { 213526ae88675e01956aa14415b0cba527cf4f1cb0aSam Judd final ImageHeaderParser.ImageType type = new ImageHeaderParser(is).getType(); 214da25daffc2047b70f7cf3eb1f6e7ae53043d9337Sam Judd // cannot reuse bitmaps when decoding images that are not PNG or JPG. 215da25daffc2047b70f7cf3eb1f6e7ae53043d9337Sam Judd // look at : https://groups.google.com/forum/#!msg/android-developers/Mp0MFVFi1Fo/e8ZQ9FGdWdEJ 216c24284108320cf3d613497ddd67ba4e1b232ce74Savvas Dalkitsis return TYPES_THAT_USE_POOL.contains(type); 2172310318f0ff9ad1919ec61878a43cb8dc3bd5086Savvas Dalkitsis } catch (IOException e) { 218ae05603c3800df74390089d6a6c1562708cbca12Robert Papp if (Log.isLoggable(TAG, Log.WARN)) { 219ae05603c3800df74390089d6a6c1562708cbca12Robert Papp Log.w(TAG, "Cannot determine the image type from header", e); 220ae05603c3800df74390089d6a6c1562708cbca12Robert Papp } 2212310318f0ff9ad1919ec61878a43cb8dc3bd5086Savvas Dalkitsis } finally { 2222310318f0ff9ad1919ec61878a43cb8dc3bd5086Savvas Dalkitsis try { 223526ae88675e01956aa14415b0cba527cf4f1cb0aSam Judd is.reset(); 2242310318f0ff9ad1919ec61878a43cb8dc3bd5086Savvas Dalkitsis } catch (IOException e) { 225ae05603c3800df74390089d6a6c1562708cbca12Robert Papp if (Log.isLoggable(TAG, Log.WARN)) { 226ae05603c3800df74390089d6a6c1562708cbca12Robert Papp Log.w(TAG, "Cannot reset the input stream", e); 227ae05603c3800df74390089d6a6c1562708cbca12Robert Papp } 2282310318f0ff9ad1919ec61878a43cb8dc3bd5086Savvas Dalkitsis } 2292310318f0ff9ad1919ec61878a43cb8dc3bd5086Savvas Dalkitsis } 2302310318f0ff9ad1919ec61878a43cb8dc3bd5086Savvas Dalkitsis return false; 2312310318f0ff9ad1919ec61878a43cb8dc3bd5086Savvas Dalkitsis } 2322310318f0ff9ad1919ec61878a43cb8dc3bd5086Savvas Dalkitsis 233526ae88675e01956aa14415b0cba527cf4f1cb0aSam Judd private static Bitmap.Config getConfig(InputStream is, DecodeFormat format) { 234b007bfcc492551550c1566a21c2eb7a402514776Sam Judd // Changing configs can cause skewing on 4.1, see issue #128. 235b007bfcc492551550c1566a21c2eb7a402514776Sam Judd if (format == DecodeFormat.ALWAYS_ARGB_8888 || Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN) { 23676fbad3dbce72240e9f5b82c826e3229c1176fb6Sam Judd return Bitmap.Config.ARGB_8888; 23776fbad3dbce72240e9f5b82c826e3229c1176fb6Sam Judd } 23876fbad3dbce72240e9f5b82c826e3229c1176fb6Sam Judd 23976fbad3dbce72240e9f5b82c826e3229c1176fb6Sam Judd boolean hasAlpha = false; 240fe090f50f3040f4d478143a3e0ffa8cdf813fefcSam Judd // We probably only need 25, but this is safer (particularly since the buffer size is > 1024). 241526ae88675e01956aa14415b0cba527cf4f1cb0aSam Judd is.mark(1024); 242525d50359e27ca73eeeba96a994155c684d05292Sam Judd try { 243526ae88675e01956aa14415b0cba527cf4f1cb0aSam Judd hasAlpha = new ImageHeaderParser(is).hasAlpha(); 244525d50359e27ca73eeeba96a994155c684d05292Sam Judd } catch (IOException e) { 245ae05603c3800df74390089d6a6c1562708cbca12Robert Papp if (Log.isLoggable(TAG, Log.WARN)) { 246ae05603c3800df74390089d6a6c1562708cbca12Robert Papp Log.w(TAG, "Cannot determine whether the image has alpha or not from header for format " + format, e); 247ae05603c3800df74390089d6a6c1562708cbca12Robert Papp } 248525d50359e27ca73eeeba96a994155c684d05292Sam Judd } finally { 249525d50359e27ca73eeeba96a994155c684d05292Sam Judd try { 250526ae88675e01956aa14415b0cba527cf4f1cb0aSam Judd is.reset(); 251525d50359e27ca73eeeba96a994155c684d05292Sam Judd } catch (IOException e) { 252ae05603c3800df74390089d6a6c1562708cbca12Robert Papp if (Log.isLoggable(TAG, Log.WARN)) { 253ae05603c3800df74390089d6a6c1562708cbca12Robert Papp Log.w(TAG, "Cannot reset the input stream", e); 254ae05603c3800df74390089d6a6c1562708cbca12Robert Papp } 255525d50359e27ca73eeeba96a994155c684d05292Sam Judd } 256525d50359e27ca73eeeba96a994155c684d05292Sam Judd } 25776fbad3dbce72240e9f5b82c826e3229c1176fb6Sam Judd 25876fbad3dbce72240e9f5b82c826e3229c1176fb6Sam Judd return hasAlpha ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565; 259525d50359e27ca73eeeba96a994155c684d05292Sam Judd } 260525d50359e27ca73eeeba96a994155c684d05292Sam Judd 261c27229a159bfc992874609270ab8b57981fef339Sam Judd /** 262c27229a159bfc992874609270ab8b57981fef339Sam Judd * Determine the amount of downsampling to use for a load given the dimensions of the image to be downsampled and 263c27229a159bfc992874609270ab8b57981fef339Sam Judd * the dimensions of the view/target the image will be displayed in. 264c27229a159bfc992874609270ab8b57981fef339Sam Judd * 26553c16e03081b659c2c9009721b1a50728d4fae80Sam Judd * @see android.graphics.BitmapFactory.Options#inSampleSize 266c27229a159bfc992874609270ab8b57981fef339Sam Judd * 267fe090f50f3040f4d478143a3e0ffa8cdf813fefcSam Judd * @param inWidth The width of the image to be downsampled. 268fe090f50f3040f4d478143a3e0ffa8cdf813fefcSam Judd * @param inHeight The height of the image to be downsampled. 269fe090f50f3040f4d478143a3e0ffa8cdf813fefcSam Judd * @param outWidth The width of the view/target the image will be displayed in. 270fe090f50f3040f4d478143a3e0ffa8cdf813fefcSam Judd * @param outHeight The height of the view/target the imag will be displayed in. 271fe090f50f3040f4d478143a3e0ffa8cdf813fefcSam Judd * @return An integer to pass in to {@link BitmapFactory#decodeStream(java.io.InputStream, android.graphics.Rect, 272fe090f50f3040f4d478143a3e0ffa8cdf813fefcSam Judd * android.graphics.BitmapFactory.Options)}. 273c27229a159bfc992874609270ab8b57981fef339Sam Judd */ 274c27229a159bfc992874609270ab8b57981fef339Sam Judd protected abstract int getSampleSize(int inWidth, int inHeight, int outWidth, int outHeight); 275c27229a159bfc992874609270ab8b57981fef339Sam Judd 276c27229a159bfc992874609270ab8b57981fef339Sam Judd /** 277fe090f50f3040f4d478143a3e0ffa8cdf813fefcSam Judd * A method for getting the dimensions of an image from the given InputStream. 278c27229a159bfc992874609270ab8b57981fef339Sam Judd * 279526ae88675e01956aa14415b0cba527cf4f1cb0aSam Judd * @param is The InputStream representing the image. 280fe090f50f3040f4d478143a3e0ffa8cdf813fefcSam Judd * @param options The options to pass to 281fe090f50f3040f4d478143a3e0ffa8cdf813fefcSam Judd * {@link BitmapFactory#decodeStream(java.io.InputStream, android.graphics.Rect, 282fe090f50f3040f4d478143a3e0ffa8cdf813fefcSam Judd * android.graphics.BitmapFactory.Options)}. 283fe090f50f3040f4d478143a3e0ffa8cdf813fefcSam Judd * @return an array containing the dimensions of the image in the form {width, height}. 284c27229a159bfc992874609270ab8b57981fef339Sam Judd */ 2852007440c380df454db15f83b019a5b4c55ca4b72Sam Judd public int[] getDimensions(ExceptionCatchingInputStream is, BitmapFactory.Options options) { 286c27229a159bfc992874609270ab8b57981fef339Sam Judd options.inJustDecodeBounds = true; 287526ae88675e01956aa14415b0cba527cf4f1cb0aSam Judd decodeStream(is, options); 288c27229a159bfc992874609270ab8b57981fef339Sam Judd options.inJustDecodeBounds = false; 289c27229a159bfc992874609270ab8b57981fef339Sam Judd return new int[] { options.outWidth, options.outHeight }; 290c27229a159bfc992874609270ab8b57981fef339Sam Judd } 291c27229a159bfc992874609270ab8b57981fef339Sam Judd 2922007440c380df454db15f83b019a5b4c55ca4b72Sam Judd private static Bitmap decodeStream(ExceptionCatchingInputStream is, BitmapFactory.Options options) { 293c27229a159bfc992874609270ab8b57981fef339Sam Judd if (options.inJustDecodeBounds) { 294fe090f50f3040f4d478143a3e0ffa8cdf813fefcSam Judd // This is large, but jpeg headers are not size bounded so we need something large enough to minimize 295fe090f50f3040f4d478143a3e0ffa8cdf813fefcSam Judd // the possibility of not being able to fit enough of the header in the buffer to get the image size so 296fe090f50f3040f4d478143a3e0ffa8cdf813fefcSam Judd // that we don't fail to load images. The BufferedInputStream will create a new buffer of 2x the 297fe090f50f3040f4d478143a3e0ffa8cdf813fefcSam Judd // original size each time we use up the buffer space without passing the mark so this is a maximum 298fe090f50f3040f4d478143a3e0ffa8cdf813fefcSam Judd // bound on the buffer size, not a default. Most of the time we won't go past our pre-allocated 16kb. 299526ae88675e01956aa14415b0cba527cf4f1cb0aSam Judd is.mark(MARK_POSITION); 3002007440c380df454db15f83b019a5b4c55ca4b72Sam Judd } else { 3012007440c380df454db15f83b019a5b4c55ca4b72Sam Judd // Once we've read the image header, we no longer need to allow the buffer to expand in size. To avoid 3022007440c380df454db15f83b019a5b4c55ca4b72Sam Judd // unnecessary allocations reading image data, we fix the mark limit so that it is no larger than our 3032007440c380df454db15f83b019a5b4c55ca4b72Sam Judd // current buffer size here. See issue #225. 3042007440c380df454db15f83b019a5b4c55ca4b72Sam Judd is.fixMarkLimit(); 305c27229a159bfc992874609270ab8b57981fef339Sam Judd } 306c27229a159bfc992874609270ab8b57981fef339Sam Judd 307526ae88675e01956aa14415b0cba527cf4f1cb0aSam Judd final Bitmap result = BitmapFactory.decodeStream(is, null, options); 308c27229a159bfc992874609270ab8b57981fef339Sam Judd 309c27229a159bfc992874609270ab8b57981fef339Sam Judd try { 310c27229a159bfc992874609270ab8b57981fef339Sam Judd if (options.inJustDecodeBounds) { 311526ae88675e01956aa14415b0cba527cf4f1cb0aSam Judd is.reset(); 312c27229a159bfc992874609270ab8b57981fef339Sam Judd } 313c27229a159bfc992874609270ab8b57981fef339Sam Judd } catch (IOException e) { 314e81c2c3c8fe1533de8ba8137ea831a42378b35a9Sam Judd if (Log.isLoggable(TAG, Log.ERROR)) { 315e81c2c3c8fe1533de8ba8137ea831a42378b35a9Sam Judd Log.e(TAG, "Exception loading inDecodeBounds=" + options.inJustDecodeBounds 316e81c2c3c8fe1533de8ba8137ea831a42378b35a9Sam Judd + " sample=" + options.inSampleSize, e); 317e81c2c3c8fe1533de8ba8137ea831a42378b35a9Sam Judd } 318c27229a159bfc992874609270ab8b57981fef339Sam Judd } 319c27229a159bfc992874609270ab8b57981fef339Sam Judd 320c27229a159bfc992874609270ab8b57981fef339Sam Judd return result; 321c27229a159bfc992874609270ab8b57981fef339Sam Judd } 322c27229a159bfc992874609270ab8b57981fef339Sam Judd 323e7319b67364bd0ac6306bc7470a43d4a31600c1aRobert Papp @TargetApi(Build.VERSION_CODES.HONEYCOMB) 324c27229a159bfc992874609270ab8b57981fef339Sam Judd private static void setInBitmap(BitmapFactory.Options options, Bitmap recycled) { 325e7319b67364bd0ac6306bc7470a43d4a31600c1aRobert Papp if (Build.VERSION_CODES.HONEYCOMB <= Build.VERSION.SDK_INT) { 326c27229a159bfc992874609270ab8b57981fef339Sam Judd options.inBitmap = recycled; 327c27229a159bfc992874609270ab8b57981fef339Sam Judd } 328c27229a159bfc992874609270ab8b57981fef339Sam Judd } 3295ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd 3305ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd @TargetApi(Build.VERSION_CODES.HONEYCOMB) 3315ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd private static synchronized BitmapFactory.Options getDefaultOptions() { 3325ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd BitmapFactory.Options decodeBitmapOptions; 3335ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd synchronized (OPTIONS_QUEUE) { 3345ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd decodeBitmapOptions = OPTIONS_QUEUE.poll(); 3355ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd } 3365ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd if (decodeBitmapOptions == null) { 3375ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd decodeBitmapOptions = new BitmapFactory.Options(); 3385ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd resetOptions(decodeBitmapOptions); 3395ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd } 3405ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd 3415ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd return decodeBitmapOptions; 3425ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd } 3435ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd 3445ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd private static void releaseOptions(BitmapFactory.Options decodeBitmapOptions) { 3455ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd resetOptions(decodeBitmapOptions); 3465ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd synchronized (OPTIONS_QUEUE) { 3475ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd OPTIONS_QUEUE.offer(decodeBitmapOptions); 3485ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd } 3495ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd } 3505ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd 3515ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd @TargetApi(Build.VERSION_CODES.HONEYCOMB) 3525ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd private static void resetOptions(BitmapFactory.Options decodeBitmapOptions) { 3535ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd decodeBitmapOptions.inTempStorage = null; 3545ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd decodeBitmapOptions.inDither = false; 3555ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd decodeBitmapOptions.inScaled = false; 3565ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd decodeBitmapOptions.inSampleSize = 1; 3575ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd decodeBitmapOptions.inPreferredConfig = null; 3585ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd decodeBitmapOptions.inJustDecodeBounds = false; 3595ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd 3605ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd if (Build.VERSION_CODES.HONEYCOMB <= Build.VERSION.SDK_INT) { 3615ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd decodeBitmapOptions.inBitmap = null; 3625ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd decodeBitmapOptions.inMutable = true; 3635ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd } 3645ba19a0e69ad3a651b8f13ba45de48a56b56ce36Sam Judd } 365c27229a159bfc992874609270ab8b57981fef339Sam Judd} 366