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.bitmapfun.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 import android.util.Log; 26 27 import com.example.android.bitmapfun.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 118 // First decode with inJustDecodeBounds=true to check dimensions 119 final BitmapFactory.Options options = new BitmapFactory.Options(); 120 options.inJustDecodeBounds = true; 121 BitmapFactory.decodeResource(res, resId, options); 122 123 // Calculate inSampleSize 124 options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); 125 126 // If we're running on Honeycomb or newer, try to use inBitmap 127 if (Utils.hasHoneycomb()) { 128 addInBitmapOptions(options, cache); 129 } 130 131 // Decode bitmap with inSampleSize set 132 options.inJustDecodeBounds = false; 133 return BitmapFactory.decodeResource(res, resId, options); 134 } 135 136 /** 137 * Decode and sample down a bitmap from a file to the requested width and height. 138 * 139 * @param filename The full path of the file to decode 140 * @param reqWidth The requested width of the resulting bitmap 141 * @param reqHeight The requested height of the resulting bitmap 142 * @param cache The ImageCache used to find candidate bitmaps for use with inBitmap 143 * @return A bitmap sampled down from the original with the same aspect ratio and dimensions 144 * that are equal to or greater than the requested width and height 145 */ 146 public static Bitmap decodeSampledBitmapFromFile(String filename, 147 int reqWidth, int reqHeight, ImageCache cache) { 148 149 // First decode with inJustDecodeBounds=true to check dimensions 150 final BitmapFactory.Options options = new BitmapFactory.Options(); 151 options.inJustDecodeBounds = true; 152 BitmapFactory.decodeFile(filename, options); 153 154 // Calculate inSampleSize 155 options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); 156 157 // If we're running on Honeycomb or newer, try to use inBitmap 158 if (Utils.hasHoneycomb()) { 159 addInBitmapOptions(options, cache); 160 } 161 162 // Decode bitmap with inSampleSize set 163 options.inJustDecodeBounds = false; 164 return BitmapFactory.decodeFile(filename, options); 165 } 166 167 /** 168 * Decode and sample down a bitmap from a file input stream to the requested width and height. 169 * 170 * @param fileDescriptor The file descriptor to read from 171 * @param reqWidth The requested width of the resulting bitmap 172 * @param reqHeight The requested height of the resulting bitmap 173 * @param cache The ImageCache used to find candidate bitmaps for use with inBitmap 174 * @return A bitmap sampled down from the original with the same aspect ratio and dimensions 175 * that are equal to or greater than the requested width and height 176 */ 177 public static Bitmap decodeSampledBitmapFromDescriptor( 178 FileDescriptor fileDescriptor, int reqWidth, int reqHeight, ImageCache cache) { 179 180 // First decode with inJustDecodeBounds=true to check dimensions 181 final BitmapFactory.Options options = new BitmapFactory.Options(); 182 options.inJustDecodeBounds = true; 183 BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options); 184 185 // Calculate inSampleSize 186 options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); 187 188 // Decode bitmap with inSampleSize set 189 options.inJustDecodeBounds = false; 190 191 // If we're running on Honeycomb or newer, try to use inBitmap 192 if (Utils.hasHoneycomb()) { 193 addInBitmapOptions(options, cache); 194 } 195 196 return BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options); 197 } 198 199 @TargetApi(Build.VERSION_CODES.HONEYCOMB) 200 private static void addInBitmapOptions(BitmapFactory.Options options, ImageCache cache) { 201 // inBitmap only works with mutable bitmaps so force the decoder to 202 // return mutable bitmaps. 203 options.inMutable = true; 204 205 if (cache != null) { 206 // Try and find a bitmap to use for inBitmap 207 Bitmap inBitmap = cache.getBitmapFromReusableSet(options); 208 209 if (inBitmap != null) { 210 if (BuildConfig.DEBUG) { 211 Log.d(TAG, "Found bitmap to use for inBitmap"); 212 } 213 options.inBitmap = inBitmap; 214 } 215 } 216 } 217 218 /** 219 * Calculate an inSampleSize for use in a {@link BitmapFactory.Options} object when decoding 220 * bitmaps using the decode* methods from {@link BitmapFactory}. This implementation calculates 221 * the closest inSampleSize that will result in the final decoded bitmap having a width and 222 * height equal to or larger than the requested width and height. This implementation does not 223 * ensure a power of 2 is returned for inSampleSize which can be faster when decoding but 224 * results in a larger bitmap which isn't as useful for caching purposes. 225 * 226 * @param options An options object with out* params already populated (run through a decode* 227 * method with inJustDecodeBounds==true 228 * @param reqWidth The requested width of the resulting bitmap 229 * @param reqHeight The requested height of the resulting bitmap 230 * @return The value to be used for inSampleSize 231 */ 232 public static int calculateInSampleSize(BitmapFactory.Options options, 233 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 // Calculate ratios of height and width to requested height and width 242 final int heightRatio = Math.round((float) height / (float) reqHeight); 243 final int widthRatio = Math.round((float) width / (float) reqWidth); 244 245 // Choose the smallest ratio as inSampleSize value, this will guarantee a final image 246 // with both dimensions larger than or equal to the requested height and width. 247 inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio; 248 249 // This offers some additional logic in case the image has a strange 250 // aspect ratio. For example, a panorama may have a much larger 251 // width than height. In these cases the total pixels might still 252 // end up being too large to fit comfortably in memory, so we should 253 // be more aggressive with sample down the image (=larger inSampleSize). 254 255 final float totalPixels = width * height; 256 257 // Anything more than 2x the requested pixels we'll sample down further 258 final float totalReqPixelsCap = reqWidth * reqHeight * 2; 259 260 while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) { 261 inSampleSize++; 262 } 263 } 264 return inSampleSize; 265 } 266 } 267