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 }