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
}