Home | History | Annotate | Download | only in util
      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 
    118         // BEGIN_INCLUDE (read_bitmap_dimensions)
    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);
    126         // END_INCLUDE (read_bitmap_dimensions)
    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) {
    203         //BEGIN_INCLUDE(add_bitmap_options)
    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         }
    216         //END_INCLUDE(add_bitmap_options)
    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) {
    233         // BEGIN_INCLUDE (calculate_sample_size)
    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;
    268         // END_INCLUDE (calculate_sample_size)
    269     }
    270 }
    271