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