DisplayingBitmaps / src / com.example.android.displayingbitmaps / util /

ImageResizer.java

1
/*
2
 * Copyright (C) 2012 The Android Open Source Project
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *      http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
 
17
package com.example.android.displayingbitmaps.util;
18
 
19
import android.annotation.TargetApi;
20
import android.content.Context;
21
import android.content.res.Resources;
22
import android.graphics.Bitmap;
23
import android.graphics.BitmapFactory;
24
import android.os.Build;
25
 
26
import com.example.android.common.logger.Log;
27
import com.example.android.displayingbitmaps.BuildConfig;
28
 
29
import java.io.FileDescriptor;
30
 
31
/**
32
 * A simple subclass of {@link ImageWorker} that resizes images from resources given a target width
33
 * and height. Useful for when the input images might be too large to simply load directly into
34
 * memory.
35
 */
36
public class ImageResizer extends ImageWorker {
37
    private static final String TAG = "ImageResizer";
38
    protected int mImageWidth;
39
    protected int mImageHeight;
40
 
41
    /**
42
     * Initialize providing a single target image size (used for both width and height);
43
     *
44
     * @param context
45
     * @param imageWidth
46
     * @param imageHeight
47
     */
48
    public ImageResizer(Context context, int imageWidth, int imageHeight) {
49
        super(context);
50
        setImageSize(imageWidth, imageHeight);
51
    }
52
 
53
    /**
54
     * Initialize providing a single target image size (used for both width and height);
55
     *
56
     * @param context
57
     * @param imageSize
58
     */
59
    public ImageResizer(Context context, int imageSize) {
60
        super(context);
61
        setImageSize(imageSize);
62
    }
63
 
64
    /**
65
     * Set the target image width and height.
66
     *
67
     * @param width
68
     * @param height
69
     */
70
    public void setImageSize(int width, int height) {
71
        mImageWidth = width;
72
        mImageHeight = height;
73
    }
74
 
75
    /**
76
     * Set the target image size (width and height will be the same).
77
     *
78
     * @param size
79
     */
80
    public void setImageSize(int size) {
81
        setImageSize(size, size);
82
    }
83
 
84
    /**
85
     * The main processing method. This happens in a background task. In this case we are just
86
     * sampling down the bitmap and returning it from a resource.
87
     *
88
     * @param resId
89
     * @return
90
     */
91
    private Bitmap processBitmap(int resId) {
92
        if (BuildConfig.DEBUG) {
93
            Log.d(TAG, "processBitmap - " + resId);
94
        }
95
        return decodeSampledBitmapFromResource(mResources, resId, mImageWidth,
96
                mImageHeight, getImageCache());
97
    }
98
 
99
    @Override
100
    protected Bitmap processBitmap(Object data) {
101
        return processBitmap(Integer.parseInt(String.valueOf(data)));
102
    }
103
 
104
    /**
105
     * Decode and sample down a bitmap from resources to the requested width and height.
106
     *
107
     * @param res The resources object containing the image data
108
     * @param resId The resource id of the image data
109
     * @param reqWidth The requested width of the resulting bitmap
110
     * @param reqHeight The requested height of the resulting bitmap
111
     * @param cache The ImageCache used to find candidate bitmaps for use with inBitmap
112
     * @return A bitmap sampled down from the original with the same aspect ratio and dimensions
113
     *         that are equal to or greater than the requested width and height
114
     */
115
    public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
116
            int reqWidth, int reqHeight, ImageCache cache) {
117
 
119
        // First decode with inJustDecodeBounds=true to check dimensions
120
        final BitmapFactory.Options options = new BitmapFactory.Options();
121
        options.inJustDecodeBounds = true;
122
        BitmapFactory.decodeResource(res, resId, options);
123
 
124
        // Calculate inSampleSize
125
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
127
 
128
        // If we're running on Honeycomb or newer, try to use inBitmap
129
        if (Utils.hasHoneycomb()) {
130
            addInBitmapOptions(options, cache);
131
        }
132
 
133
        // Decode bitmap with inSampleSize set
134
        options.inJustDecodeBounds = false;
135
        return BitmapFactory.decodeResource(res, resId, options);
136
    }
137
 
138
    /**
139
     * Decode and sample down a bitmap from a file to the requested width and height.
140
     *
141
     * @param filename The full path of the file to decode
142
     * @param reqWidth The requested width of the resulting bitmap
143
     * @param reqHeight The requested height of the resulting bitmap
144
     * @param cache The ImageCache used to find candidate bitmaps for use with inBitmap
145
     * @return A bitmap sampled down from the original with the same aspect ratio and dimensions
146
     *         that are equal to or greater than the requested width and height
147
     */
148
    public static Bitmap decodeSampledBitmapFromFile(String filename,
149
            int reqWidth, int reqHeight, ImageCache cache) {
150
 
151
        // First decode with inJustDecodeBounds=true to check dimensions
152
        final BitmapFactory.Options options = new BitmapFactory.Options();
153
        options.inJustDecodeBounds = true;
154
        BitmapFactory.decodeFile(filename, options);
155
 
156
        // Calculate inSampleSize
157
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
158
 
159
        // If we're running on Honeycomb or newer, try to use inBitmap
160
        if (Utils.hasHoneycomb()) {
161
            addInBitmapOptions(options, cache);
162
        }
163
 
164
        // Decode bitmap with inSampleSize set
165
        options.inJustDecodeBounds = false;
166
        return BitmapFactory.decodeFile(filename, options);
167
    }
168
 
169
    /**
170
     * Decode and sample down a bitmap from a file input stream to the requested width and height.
171
     *
172
     * @param fileDescriptor The file descriptor to read from
173
     * @param reqWidth The requested width of the resulting bitmap
174
     * @param reqHeight The requested height of the resulting bitmap
175
     * @param cache The ImageCache used to find candidate bitmaps for use with inBitmap
176
     * @return A bitmap sampled down from the original with the same aspect ratio and dimensions
177
     *         that are equal to or greater than the requested width and height
178
     */
179
    public static Bitmap decodeSampledBitmapFromDescriptor(
180
            FileDescriptor fileDescriptor, int reqWidth, int reqHeight, ImageCache cache) {
181
 
182
        // First decode with inJustDecodeBounds=true to check dimensions
183
        final BitmapFactory.Options options = new BitmapFactory.Options();
184
        options.inJustDecodeBounds = true;
185
        BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
186
 
187
        // Calculate inSampleSize
188
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
189
 
190
        // Decode bitmap with inSampleSize set
191
        options.inJustDecodeBounds = false
;
192
 
193
        // If we're running on Honeycomb or newer, try to use inBitmap
194
        if (Utils.hasHoneycomb()) {
195
            addInBitmapOptions(options, cache);
196
        }
197
 
198
        return BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
199
    }
200
 
201
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
202
    private static void addInBitmapOptions(BitmapFactory.Options options, ImageCache cache) {
204
        // inBitmap only works with mutable bitmaps so force the decoder to
205
        // return mutable bitmaps.
206
        options.inMutable = true;
207
 
208
        if (cache != null) {
209
            // Try and find a bitmap to use for inBitmap
210
            Bitmap inBitmap = cache.getBitmapFromReusableSet(options);
211
 
212
            if (inBitmap != null) {
213
                options.inBitmap = inBitmap;
214
            }
215
        }
217
    }
218
 
219
    /**
220
     * Calculate an inSampleSize for use in a {@link android.graphics.BitmapFactory.Options} object when decoding
221
     * bitmaps using the decode* methods from {@link android.graphics.BitmapFactory}. This implementation calculates
222
     * the closest inSampleSize that is a power of 2 and will result in the final decoded bitmap
223
     * having a width and height equal to or larger than the requested width and height.
224
     *
225
     * @param options An options object with out* params already populated (run through a decode*
226
     *            method with inJustDecodeBounds==true
227
     * @param reqWidth The requested width of the resulting bitmap
228
     * @param reqHeight The requested height of the resulting bitmap
229
     * @return The value to be used for inSampleSize
230
     */
231
    public static int calculateInSampleSize(BitmapFactory.Options options,
232
            int reqWidth, int reqHeight) {
234
        // Raw height and width of image
235
        final int height = options.outHeight;
236
        final int width = options.outWidth;
237
        int inSampleSize = 1;
238
 
239
        if (height > reqHeight || width > reqWidth) {
240
 
241
            final int halfHeight = height / 2;
242
            final int halfWidth = width / 2;
243
 
244
            // Calculate the largest inSampleSize value that is a power of 2 and keeps both
245
            // height and width larger than the requested height and width.
246
            while ((halfHeight / inSampleSize) > reqHeight
247
                    && (halfWidth / inSampleSize) > reqWidth) {
248
                inSampleSize *= 2;
249
            }
250
 
251
            // This offers some additional logic in case the image has a strange
252
            // aspect ratio. For example, a panorama may have a much larger
253
            // width than height. In these cases the total pixels might still
254
            // end up being too large to fit comfortably in memory, so we should
255
            // be more aggressive with sample down the image (=larger inSampleSize).
256
 
257
            long totalPixels = width * height / inSampleSize;
258
 
259
            // Anything more than 2x the requested pixels we'll sample down further
260
            final long totalReqPixelsCap = reqWidth * reqHeight * 2;
261
 
262
            while (totalPixels > totalReqPixelsCap) {
263
                inSampleSize *= 2;
264
                totalPixels /= 2;
265
            }
266
        }
267
        return inSampleSize;
269
    }
270
}