Downsampler.java revision 525d50359e27ca73eeeba96a994155c684d05292
198f8035b44e9c120e25d663c9ec21eee0773645eSam Juddpackage com.bumptech.glide.resize.load; 2c27229a159bfc992874609270ab8b57981fef339Sam Judd 3c27229a159bfc992874609270ab8b57981fef339Sam Juddimport android.annotation.TargetApi; 4c27229a159bfc992874609270ab8b57981fef339Sam Juddimport android.graphics.Bitmap; 5c27229a159bfc992874609270ab8b57981fef339Sam Juddimport android.graphics.BitmapFactory; 6c27229a159bfc992874609270ab8b57981fef339Sam Juddimport android.os.Build; 798f8035b44e9c120e25d663c9ec21eee0773645eSam Juddimport com.bumptech.glide.resize.RecyclableBufferedInputStream; 8c27229a159bfc992874609270ab8b57981fef339Sam Juddimport com.bumptech.glide.resize.bitmap_recycle.BitmapPool; 9525d50359e27ca73eeeba96a994155c684d05292Sam Juddimport com.bumptech.glide.util.Log; 10c27229a159bfc992874609270ab8b57981fef339Sam Judd 11c27229a159bfc992874609270ab8b57981fef339Sam Juddimport java.io.IOException; 12c27229a159bfc992874609270ab8b57981fef339Sam Judd 13c27229a159bfc992874609270ab8b57981fef339Sam Judd/** 14c27229a159bfc992874609270ab8b57981fef339Sam Judd * A base class with methods for loading and decoding images from InputStreams. 15c27229a159bfc992874609270ab8b57981fef339Sam Judd */ 16c27229a159bfc992874609270ab8b57981fef339Sam Juddpublic abstract class Downsampler { 17c27229a159bfc992874609270ab8b57981fef339Sam Judd private final String id = getClass().toString(); 18c27229a159bfc992874609270ab8b57981fef339Sam Judd 19c27229a159bfc992874609270ab8b57981fef339Sam Judd /** 20c27229a159bfc992874609270ab8b57981fef339Sam Judd * Load and scale the image uniformly (maintaining the image's aspect ratio) so that the dimensions of the image 21c27229a159bfc992874609270ab8b57981fef339Sam Judd * will be greater than or equal to the given width and height. 22c27229a159bfc992874609270ab8b57981fef339Sam Judd * 23c27229a159bfc992874609270ab8b57981fef339Sam Judd */ 24c27229a159bfc992874609270ab8b57981fef339Sam Judd public static Downsampler AT_LEAST = new Downsampler() { 25c27229a159bfc992874609270ab8b57981fef339Sam Judd @Override 26c27229a159bfc992874609270ab8b57981fef339Sam Judd protected int getSampleSize(int inWidth, int inHeight, int outWidth, int outHeight) { 27c27229a159bfc992874609270ab8b57981fef339Sam Judd return Math.min(inHeight / outHeight, inWidth / outWidth); 28c27229a159bfc992874609270ab8b57981fef339Sam Judd } 29c27229a159bfc992874609270ab8b57981fef339Sam Judd }; 30c27229a159bfc992874609270ab8b57981fef339Sam Judd 31c27229a159bfc992874609270ab8b57981fef339Sam Judd /** 32c27229a159bfc992874609270ab8b57981fef339Sam Judd * Load and scale the image uniformly (maintaining the image's aspect ratio) so that the dimensions of the image 33c27229a159bfc992874609270ab8b57981fef339Sam Judd * will be less than or equal to the given width and height. 34c27229a159bfc992874609270ab8b57981fef339Sam Judd * 35c27229a159bfc992874609270ab8b57981fef339Sam Judd */ 36c27229a159bfc992874609270ab8b57981fef339Sam Judd public static Downsampler AT_MOST = new Downsampler() { 37c27229a159bfc992874609270ab8b57981fef339Sam Judd @Override 38c27229a159bfc992874609270ab8b57981fef339Sam Judd protected int getSampleSize(int inWidth, int inHeight, int outWidth, int outHeight) { 39c27229a159bfc992874609270ab8b57981fef339Sam Judd return Math.max(inHeight / outHeight, inWidth / outWidth); 40c27229a159bfc992874609270ab8b57981fef339Sam Judd } 41c27229a159bfc992874609270ab8b57981fef339Sam Judd }; 42c27229a159bfc992874609270ab8b57981fef339Sam Judd 43c27229a159bfc992874609270ab8b57981fef339Sam Judd /** 44c27229a159bfc992874609270ab8b57981fef339Sam Judd * Load the image at its original size 45c27229a159bfc992874609270ab8b57981fef339Sam Judd * 46c27229a159bfc992874609270ab8b57981fef339Sam Judd */ 47c27229a159bfc992874609270ab8b57981fef339Sam Judd public static Downsampler NONE = new Downsampler() { 48c27229a159bfc992874609270ab8b57981fef339Sam Judd @Override 49c27229a159bfc992874609270ab8b57981fef339Sam Judd protected int getSampleSize(int inWidth, int inHeight, int outWidth, int outHeight) { 50c27229a159bfc992874609270ab8b57981fef339Sam Judd return 0; 51c27229a159bfc992874609270ab8b57981fef339Sam Judd } 52c27229a159bfc992874609270ab8b57981fef339Sam Judd }; 53c27229a159bfc992874609270ab8b57981fef339Sam Judd 54c27229a159bfc992874609270ab8b57981fef339Sam Judd private static final int MARK_POSITION = 1024 * 1024; //1mb 55c27229a159bfc992874609270ab8b57981fef339Sam Judd 56c27229a159bfc992874609270ab8b57981fef339Sam Judd /** 57c27229a159bfc992874609270ab8b57981fef339Sam Judd * Load the image for the given InputStream. If a recycled Bitmap whose dimensions exactly match those of the image 58c27229a159bfc992874609270ab8b57981fef339Sam Judd * for the given InputStream is available, the operation is much less expensive in terms of memory. 59c27229a159bfc992874609270ab8b57981fef339Sam Judd * 60c27229a159bfc992874609270ab8b57981fef339Sam Judd * Note - this method will throw an exception of a Bitmap with dimensions not matching those of the image for the 61c27229a159bfc992874609270ab8b57981fef339Sam Judd * given InputStream is provided. 62c27229a159bfc992874609270ab8b57981fef339Sam Judd * 63c27229a159bfc992874609270ab8b57981fef339Sam Judd * @param bis An InputStream to the data for the image 64c27229a159bfc992874609270ab8b57981fef339Sam Judd * @param options The options to pass to {@link BitmapFactory#decodeStream(java.io.InputStream, android.graphics.Rect, android.graphics.BitmapFactory.Options)} 65c27229a159bfc992874609270ab8b57981fef339Sam Judd * @param pool A pool of recycled bitmaps 66c27229a159bfc992874609270ab8b57981fef339Sam Judd * @param outWidth The width the final image should be close to 67c27229a159bfc992874609270ab8b57981fef339Sam Judd * @param outHeight The height the final image should be close to 68c27229a159bfc992874609270ab8b57981fef339Sam Judd * @return A new bitmap containing the image from the given InputStream, or recycle if recycle is not null 69c27229a159bfc992874609270ab8b57981fef339Sam Judd */ 70c27229a159bfc992874609270ab8b57981fef339Sam Judd public Bitmap downsample(RecyclableBufferedInputStream bis, BitmapFactory.Options options, BitmapPool pool, int outWidth, int outHeight) { 71d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd bis.mark(MARK_POSITION); 72d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd int orientation = 0; 73d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd try { 74525d50359e27ca73eeeba96a994155c684d05292Sam Judd orientation = new ExifOrientationParser(bis).getOrientation(); 75d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd } catch (IOException e) { 76d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd e.printStackTrace(); 77d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd } 78d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd try { 79d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd bis.reset(); 80d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd } catch (IOException e) { 81293fdc0fc9203e2286beef092ac4a1fcec55cd0dSam Judd e.printStackTrace(); 82d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd } 83d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd 84c27229a159bfc992874609270ab8b57981fef339Sam Judd final int[] inDimens = getDimensions(bis, options); 85293fdc0fc9203e2286beef092ac4a1fcec55cd0dSam Judd final int inWidth = inDimens[0]; 86293fdc0fc9203e2286beef092ac4a1fcec55cd0dSam Judd final int inHeight = inDimens[1]; 87d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd 88d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd final int degreesToRotate = ImageResizer.getExifOrientationDegrees(orientation); 89d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd final int sampleSize; 90d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd if (degreesToRotate == 90 || degreesToRotate == 270) { 91293fdc0fc9203e2286beef092ac4a1fcec55cd0dSam Judd //if we're rotating the image +-90 degrees, we need to downsample accordingly so the image width is 92293fdc0fc9203e2286beef092ac4a1fcec55cd0dSam Judd //decreased to near our target's height and the image height is decreased to near our target width 93293fdc0fc9203e2286beef092ac4a1fcec55cd0dSam Judd sampleSize = getSampleSize(inHeight, inWidth, outWidth, outHeight); 94d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd } else { 95d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd sampleSize = getSampleSize(inWidth, inHeight, outWidth, outHeight); 96d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd } 97c27229a159bfc992874609270ab8b57981fef339Sam Judd 98293fdc0fc9203e2286beef092ac4a1fcec55cd0dSam Judd final Bitmap downsampled = downsampleWithSize(bis, options, pool, inWidth, inHeight, sampleSize); 99d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd final Bitmap rotated = ImageResizer.rotateImageExif(downsampled, pool, orientation); 100d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd 101d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd if (downsampled != rotated) { 102d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd pool.put(downsampled); 103d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd } 104d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd 105d7efd9c46ebd8872640bb4b4efb564968905507dSam Judd return rotated; 106c27229a159bfc992874609270ab8b57981fef339Sam Judd } 107c27229a159bfc992874609270ab8b57981fef339Sam Judd 108293fdc0fc9203e2286beef092ac4a1fcec55cd0dSam Judd protected Bitmap downsampleWithSize(RecyclableBufferedInputStream bis, BitmapFactory.Options options, BitmapPool pool, int inWidth, int inHeight, int sampleSize) { 109293fdc0fc9203e2286beef092ac4a1fcec55cd0dSam Judd if (sampleSize > 1) { 110293fdc0fc9203e2286beef092ac4a1fcec55cd0dSam Judd options.inSampleSize = sampleSize; 111293fdc0fc9203e2286beef092ac4a1fcec55cd0dSam Judd } else { 112525d50359e27ca73eeeba96a994155c684d05292Sam Judd setInBitmap(options, pool.get(inWidth, inHeight, getConfig(bis))); 113293fdc0fc9203e2286beef092ac4a1fcec55cd0dSam Judd } 114293fdc0fc9203e2286beef092ac4a1fcec55cd0dSam Judd return decodeStream(bis, options); 115293fdc0fc9203e2286beef092ac4a1fcec55cd0dSam Judd } 116293fdc0fc9203e2286beef092ac4a1fcec55cd0dSam Judd 117525d50359e27ca73eeeba96a994155c684d05292Sam Judd private Bitmap.Config getConfig(RecyclableBufferedInputStream bis) { 118525d50359e27ca73eeeba96a994155c684d05292Sam Judd Bitmap.Config result = Bitmap.Config.RGB_565; 119525d50359e27ca73eeeba96a994155c684d05292Sam Judd bis.mark(1024); //we probably only need 25, but this is safer (particularly since the buffer size is > 1024) 120525d50359e27ca73eeeba96a994155c684d05292Sam Judd try { 121525d50359e27ca73eeeba96a994155c684d05292Sam Judd result = new ExifOrientationParser(bis).hasAlpha() ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565; 122525d50359e27ca73eeeba96a994155c684d05292Sam Judd } catch (IOException e) { 123525d50359e27ca73eeeba96a994155c684d05292Sam Judd e.printStackTrace(); 124525d50359e27ca73eeeba96a994155c684d05292Sam Judd } finally { 125525d50359e27ca73eeeba96a994155c684d05292Sam Judd try { 126525d50359e27ca73eeeba96a994155c684d05292Sam Judd bis.reset(); 127525d50359e27ca73eeeba96a994155c684d05292Sam Judd } catch (IOException e) { 128525d50359e27ca73eeeba96a994155c684d05292Sam Judd e.printStackTrace(); 129525d50359e27ca73eeeba96a994155c684d05292Sam Judd } 130525d50359e27ca73eeeba96a994155c684d05292Sam Judd } 131525d50359e27ca73eeeba96a994155c684d05292Sam Judd return result; 132525d50359e27ca73eeeba96a994155c684d05292Sam Judd } 133525d50359e27ca73eeeba96a994155c684d05292Sam Judd 134c27229a159bfc992874609270ab8b57981fef339Sam Judd /** 135c27229a159bfc992874609270ab8b57981fef339Sam Judd * Get some id that uniquely identifies the downsample for use as part of a cache key 136c27229a159bfc992874609270ab8b57981fef339Sam Judd * @return A unique String 137c27229a159bfc992874609270ab8b57981fef339Sam Judd */ 138c27229a159bfc992874609270ab8b57981fef339Sam Judd public String getId() { 139c27229a159bfc992874609270ab8b57981fef339Sam Judd return id; 140c27229a159bfc992874609270ab8b57981fef339Sam Judd } 141c27229a159bfc992874609270ab8b57981fef339Sam Judd 142c27229a159bfc992874609270ab8b57981fef339Sam Judd /** 143c27229a159bfc992874609270ab8b57981fef339Sam Judd * Determine the amount of downsampling to use for a load given the dimensions of the image to be downsampled and 144c27229a159bfc992874609270ab8b57981fef339Sam Judd * the dimensions of the view/target the image will be displayed in. 145c27229a159bfc992874609270ab8b57981fef339Sam Judd * 146c27229a159bfc992874609270ab8b57981fef339Sam Judd * @see BitmapFactory.Options#inSampleSize 147c27229a159bfc992874609270ab8b57981fef339Sam Judd * 148c27229a159bfc992874609270ab8b57981fef339Sam Judd * @param inWidth The width of the image to be downsampled 149c27229a159bfc992874609270ab8b57981fef339Sam Judd * @param inHeight The height of the image to be downsampled 150c27229a159bfc992874609270ab8b57981fef339Sam Judd * @param outWidth The width of the view/target the image will be displayed in 151c27229a159bfc992874609270ab8b57981fef339Sam Judd * @param outHeight The height of the view/target the imag will be displayed in 152c27229a159bfc992874609270ab8b57981fef339Sam Judd * @return An integer to pass in to {@link BitmapFactory#decodeStream(java.io.InputStream, android.graphics.Rect, android.graphics.BitmapFactory.Options)} 153c27229a159bfc992874609270ab8b57981fef339Sam Judd */ 154c27229a159bfc992874609270ab8b57981fef339Sam Judd protected abstract int getSampleSize(int inWidth, int inHeight, int outWidth, int outHeight); 155c27229a159bfc992874609270ab8b57981fef339Sam Judd 156c27229a159bfc992874609270ab8b57981fef339Sam Judd /** 157c27229a159bfc992874609270ab8b57981fef339Sam Judd * A method for getting the dimensions of an image from the given InputStream 158c27229a159bfc992874609270ab8b57981fef339Sam Judd * 159c27229a159bfc992874609270ab8b57981fef339Sam Judd * @param bis The InputStream representing the image 160c27229a159bfc992874609270ab8b57981fef339Sam Judd * @param options The options to pass to {@link BitmapFactory#decodeStream(java.io.InputStream, android.graphics.Rect, android.graphics.BitmapFactory.Options)} 161c27229a159bfc992874609270ab8b57981fef339Sam Judd * @return an array containing the dimensions of the image in the form {width, height} 162c27229a159bfc992874609270ab8b57981fef339Sam Judd */ 163c27229a159bfc992874609270ab8b57981fef339Sam Judd public int[] getDimensions(RecyclableBufferedInputStream bis, BitmapFactory.Options options) { 164c27229a159bfc992874609270ab8b57981fef339Sam Judd options.inJustDecodeBounds = true; 165c27229a159bfc992874609270ab8b57981fef339Sam Judd decodeStream(bis, options); 166c27229a159bfc992874609270ab8b57981fef339Sam Judd options.inJustDecodeBounds = false; 167c27229a159bfc992874609270ab8b57981fef339Sam Judd return new int[] { options.outWidth, options.outHeight }; 168c27229a159bfc992874609270ab8b57981fef339Sam Judd } 169c27229a159bfc992874609270ab8b57981fef339Sam Judd 170c27229a159bfc992874609270ab8b57981fef339Sam Judd 171c27229a159bfc992874609270ab8b57981fef339Sam Judd private Bitmap decodeStream(RecyclableBufferedInputStream bis, BitmapFactory.Options options) { 172c27229a159bfc992874609270ab8b57981fef339Sam Judd if (options.inJustDecodeBounds) { 173c27229a159bfc992874609270ab8b57981fef339Sam Judd bis.mark(MARK_POSITION); //this is large, but jpeg headers are not size bounded so we need 174c27229a159bfc992874609270ab8b57981fef339Sam Judd //something large enough to minimize the possibility of not being able to fit 175c27229a159bfc992874609270ab8b57981fef339Sam Judd //enough of the header in the buffer to get the image size so that we don't fail 176c27229a159bfc992874609270ab8b57981fef339Sam Judd //to load images. The BufferedInputStream will create a new buffer of 2x the 177c27229a159bfc992874609270ab8b57981fef339Sam Judd //original size each time we use up the buffer space without passing the mark so 178c27229a159bfc992874609270ab8b57981fef339Sam Judd //this is a maximum bound on the buffer size, not a default. Most of the time we 179c27229a159bfc992874609270ab8b57981fef339Sam Judd //won't go past our pre-allocated 16kb 180c27229a159bfc992874609270ab8b57981fef339Sam Judd } 181c27229a159bfc992874609270ab8b57981fef339Sam Judd 182c27229a159bfc992874609270ab8b57981fef339Sam Judd final Bitmap result = BitmapFactory.decodeStream(bis, null, options); 183525d50359e27ca73eeeba96a994155c684d05292Sam Judd if (result == null && !options.inJustDecodeBounds) { 184525d50359e27ca73eeeba96a994155c684d05292Sam Judd throw new IllegalArgumentException("IP: null result, sampleSize=" + options.inSampleSize); 185525d50359e27ca73eeeba96a994155c684d05292Sam Judd } 186c27229a159bfc992874609270ab8b57981fef339Sam Judd 187c27229a159bfc992874609270ab8b57981fef339Sam Judd try { 188c27229a159bfc992874609270ab8b57981fef339Sam Judd if (options.inJustDecodeBounds) { 189c27229a159bfc992874609270ab8b57981fef339Sam Judd bis.reset(); 190c27229a159bfc992874609270ab8b57981fef339Sam Judd bis.clearMark(); 191c27229a159bfc992874609270ab8b57981fef339Sam Judd } else { 192c27229a159bfc992874609270ab8b57981fef339Sam Judd bis.close(); 193c27229a159bfc992874609270ab8b57981fef339Sam Judd } 194c27229a159bfc992874609270ab8b57981fef339Sam Judd } catch (IOException e) { 195525d50359e27ca73eeeba96a994155c684d05292Sam Judd Log.d("Downsampler: exception loading inDecodeBounds=" + options.inJustDecodeBounds + " sample=" + options.inSampleSize); 196c27229a159bfc992874609270ab8b57981fef339Sam Judd e.printStackTrace(); 197c27229a159bfc992874609270ab8b57981fef339Sam Judd } 198c27229a159bfc992874609270ab8b57981fef339Sam Judd 199c27229a159bfc992874609270ab8b57981fef339Sam Judd return result; 200c27229a159bfc992874609270ab8b57981fef339Sam Judd } 201c27229a159bfc992874609270ab8b57981fef339Sam Judd 202c27229a159bfc992874609270ab8b57981fef339Sam Judd @TargetApi(11) 203c27229a159bfc992874609270ab8b57981fef339Sam Judd private static void setInBitmap(BitmapFactory.Options options, Bitmap recycled) { 204c27229a159bfc992874609270ab8b57981fef339Sam Judd if (Build.VERSION.SDK_INT >= 11) { 205c27229a159bfc992874609270ab8b57981fef339Sam Judd options.inBitmap = recycled; 206c27229a159bfc992874609270ab8b57981fef339Sam Judd } 207c27229a159bfc992874609270ab8b57981fef339Sam Judd } 208c27229a159bfc992874609270ab8b57981fef339Sam Judd} 209