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.content.Context; 20 import android.content.res.Resources; 21 import android.graphics.Bitmap; 22 import android.graphics.BitmapFactory; 23 import android.util.Log; 24 25 import com.example.android.bitmapfun.BuildConfig; 26 27 /** 28 * A simple subclass of {@link ImageWorker} that resizes images from resources given a target width 29 * and height. Useful for when the input images might be too large to simply load directly into 30 * memory. 31 */ 32 public class ImageResizer extends ImageWorker { 33 private static final String TAG = "ImageWorker"; 34 protected int mImageWidth; 35 protected int mImageHeight; 36 37 /** 38 * Initialize providing a single target image size (used for both width and height); 39 * 40 * @param context 41 * @param imageWidth 42 * @param imageHeight 43 */ ImageResizer(Context context, int imageWidth, int imageHeight)44 public ImageResizer(Context context, int imageWidth, int imageHeight) { 45 super(context); 46 setImageSize(imageWidth, imageHeight); 47 } 48 49 /** 50 * Initialize providing a single target image size (used for both width and height); 51 * 52 * @param context 53 * @param imageSize 54 */ ImageResizer(Context context, int imageSize)55 public ImageResizer(Context context, int imageSize) { 56 super(context); 57 setImageSize(imageSize); 58 } 59 60 /** 61 * Set the target image width and height. 62 * 63 * @param width 64 * @param height 65 */ setImageSize(int width, int height)66 public void setImageSize(int width, int height) { 67 mImageWidth = width; 68 mImageHeight = height; 69 } 70 71 /** 72 * Set the target image size (width and height will be the same). 73 * 74 * @param size 75 */ setImageSize(int size)76 public void setImageSize(int size) { 77 setImageSize(size, size); 78 } 79 80 /** 81 * The main processing method. This happens in a background task. In this case we are just 82 * sampling down the bitmap and returning it from a resource. 83 * 84 * @param resId 85 * @return 86 */ processBitmap(int resId)87 private Bitmap processBitmap(int resId) { 88 if (BuildConfig.DEBUG) { 89 Log.d(TAG, "processBitmap - " + resId); 90 } 91 return decodeSampledBitmapFromResource( 92 mContext.getResources(), resId, mImageWidth, mImageHeight); 93 } 94 95 @Override processBitmap(Object data)96 protected Bitmap processBitmap(Object data) { 97 return processBitmap(Integer.parseInt(String.valueOf(data))); 98 } 99 100 /** 101 * Decode and sample down a bitmap from resources to the requested width and height. 102 * 103 * @param res The resources object containing the image data 104 * @param resId The resource id of the image data 105 * @param reqWidth The requested width of the resulting bitmap 106 * @param reqHeight The requested height of the resulting bitmap 107 * @return A bitmap sampled down from the original with the same aspect ratio and dimensions 108 * that are equal to or greater than the requested width and height 109 */ decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight)110 public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, 111 int reqWidth, int reqHeight) { 112 113 // First decode with inJustDecodeBounds=true to check dimensions 114 final BitmapFactory.Options options = new BitmapFactory.Options(); 115 options.inJustDecodeBounds = true; 116 BitmapFactory.decodeResource(res, resId, options); 117 118 // Calculate inSampleSize 119 options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); 120 121 // Decode bitmap with inSampleSize set 122 options.inJustDecodeBounds = false; 123 return BitmapFactory.decodeResource(res, resId, options); 124 } 125 126 /** 127 * Decode and sample down a bitmap from a file to the requested width and height. 128 * 129 * @param filename The full path of the file to decode 130 * @param reqWidth The requested width of the resulting bitmap 131 * @param reqHeight The requested height of the resulting bitmap 132 * @return A bitmap sampled down from the original with the same aspect ratio and dimensions 133 * that are equal to or greater than the requested width and height 134 */ decodeSampledBitmapFromFile(String filename, int reqWidth, int reqHeight)135 public static synchronized Bitmap decodeSampledBitmapFromFile(String filename, 136 int reqWidth, int reqHeight) { 137 138 // First decode with inJustDecodeBounds=true to check dimensions 139 final BitmapFactory.Options options = new BitmapFactory.Options(); 140 options.inJustDecodeBounds = true; 141 BitmapFactory.decodeFile(filename, options); 142 143 // Calculate inSampleSize 144 options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); 145 146 // Decode bitmap with inSampleSize set 147 options.inJustDecodeBounds = false; 148 return BitmapFactory.decodeFile(filename, options); 149 } 150 151 /** 152 * Calculate an inSampleSize for use in a {@link BitmapFactory.Options} object when decoding 153 * bitmaps using the decode* methods from {@link BitmapFactory}. This implementation calculates 154 * the closest inSampleSize that will result in the final decoded bitmap having a width and 155 * height equal to or larger than the requested width and height. This implementation does not 156 * ensure a power of 2 is returned for inSampleSize which can be faster when decoding but 157 * results in a larger bitmap which isn't as useful for caching purposes. 158 * 159 * @param options An options object with out* params already populated (run through a decode* 160 * method with inJustDecodeBounds==true 161 * @param reqWidth The requested width of the resulting bitmap 162 * @param reqHeight The requested height of the resulting bitmap 163 * @return The value to be used for inSampleSize 164 */ calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight)165 public static int calculateInSampleSize(BitmapFactory.Options options, 166 int reqWidth, int reqHeight) { 167 // Raw height and width of image 168 final int height = options.outHeight; 169 final int width = options.outWidth; 170 int inSampleSize = 1; 171 172 if (height > reqHeight || width > reqWidth) { 173 if (width > height) { 174 inSampleSize = Math.round((float) height / (float) reqHeight); 175 } else { 176 inSampleSize = Math.round((float) width / (float) reqWidth); 177 } 178 179 // This offers some additional logic in case the image has a strange 180 // aspect ratio. For example, a panorama may have a much larger 181 // width than height. In these cases the total pixels might still 182 // end up being too large to fit comfortably in memory, so we should 183 // be more aggressive with sample down the image (=larger 184 // inSampleSize). 185 186 final float totalPixels = width * height; 187 188 // Anything more than 2x the requested pixels we'll sample down 189 // further. 190 final float totalReqPixelsCap = reqWidth * reqHeight * 2; 191 192 while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) { 193 inSampleSize++; 194 } 195 } 196 return inSampleSize; 197 } 198 } 199