Home | History | Annotate | Download | only in photomanager
      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.android.mail.photomanager;
     18 import android.graphics.Bitmap;
     19 import android.graphics.BitmapFactory;
     20 import android.graphics.Matrix;
     21 
     22 import com.android.mail.utils.LogUtils;
     23 
     24 /**
     25  * Provides static functions to decode bitmaps at the optimal size
     26  */
     27 public class BitmapUtil {
     28 
     29     private static final boolean DEBUG = false;
     30 
     31     private BitmapUtil() {
     32     }
     33 
     34     /**
     35      * Decode an image into a Bitmap, using sub-sampling if the hinted dimensions call for it.
     36      * Does not crop to fit the hinted dimensions.
     37      *
     38      * @param src an encoded image
     39      * @param w hint width in px
     40      * @param h hint height in px
     41      * @return a decoded Bitmap that is not exactly sized to the hinted dimensions.
     42      */
     43     public static Bitmap decodeByteArray(byte[] src, int w, int h) {
     44         try {
     45             // calculate sample size based on w/h
     46             final BitmapFactory.Options opts = new BitmapFactory.Options();
     47             opts.inJustDecodeBounds = true;
     48             BitmapFactory.decodeByteArray(src, 0, src.length, opts);
     49             if (opts.mCancel || opts.outWidth == -1 || opts.outHeight == -1) {
     50                 return null;
     51             }
     52             opts.inSampleSize = Math.min(opts.outWidth / w, opts.outHeight / h);
     53             opts.inJustDecodeBounds = false;
     54             return BitmapFactory.decodeByteArray(src, 0, src.length, opts);
     55         } catch (Throwable t) {
     56             LogUtils.w(PhotoManager.TAG, t, "unable to decode image");
     57             return null;
     58         }
     59     }
     60 
     61     /**
     62      * Decode an image into a Bitmap, using sub-sampling if the desired dimensions call for it.
     63      * Also applies a center-crop a la {@link android.widget.ImageView.ScaleType#CENTER_CROP}.
     64      *
     65      * @param src an encoded image
     66      * @param w desired width in px
     67      * @param h desired height in px
     68      * @return an exactly-sized decoded Bitmap that is center-cropped.
     69      */
     70     public static Bitmap decodeByteArrayWithCenterCrop(byte[] src, int w, int h) {
     71         try {
     72             final Bitmap decoded = decodeByteArray(src, w, h);
     73             return centerCrop(decoded, w, h);
     74 
     75         } catch (Throwable t) {
     76             LogUtils.w(PhotoManager.TAG, t, "unable to crop image");
     77             return null;
     78         }
     79     }
     80 
     81     /**
     82      * Returns a new Bitmap copy with a center-crop effect a la
     83      * {@link android.widget.ImageView.ScaleType#CENTER_CROP}. May return the input bitmap if no
     84      * scaling is necessary.
     85      *
     86      * @param src original bitmap of any size
     87      * @param w desired width in px
     88      * @param h desired height in px
     89      * @return a copy of src conforming to the given width and height, or src itself if it already
     90      *         matches the given width and height
     91      */
     92     public static Bitmap centerCrop(final Bitmap src, final int w, final int h) {
     93         return crop(src, w, h, 0.5f, 0.5f);
     94     }
     95 
     96     /**
     97      * Returns a new Bitmap copy with a crop effect depending on the crop anchor given. 0.5f is like
     98      * {@link android.widget.ImageView.ScaleType#CENTER_CROP}. The crop anchor will be be nudged
     99      * so the entire cropped bitmap will fit inside the src. May return the input bitmap if no
    100      * scaling is necessary.
    101      *
    102      *
    103      * Example of changing verticalCenterPercent:
    104      *   _________            _________
    105      *  |         |          |         |
    106      *  |         |          |_________|
    107      *  |         |          |         |/___0.3f
    108      *  |---------|          |_________|\
    109      *  |         |<---0.5f  |         |
    110      *  |---------|          |         |
    111      *  |         |          |         |
    112      *  |         |          |         |
    113      *  |_________|          |_________|
    114      *
    115      * @param src original bitmap of any size
    116      * @param w desired width in px
    117      * @param h desired height in px
    118      * @param horizontalCenterPercent determines which part of the src to crop from. Range from 0
    119      *                                .0f to 1.0f. The value determines which part of the src
    120      *                                maps to the horizontal center of the resulting bitmap.
    121      * @param verticalCenterPercent determines which part of the src to crop from. Range from 0
    122      *                              .0f to 1.0f. The value determines which part of the src maps
    123      *                              to the vertical center of the resulting bitmap.
    124      * @return a copy of src conforming to the given width and height, or src itself if it already
    125      *         matches the given width and height
    126      */
    127     public static Bitmap crop(final Bitmap src, final int w, final int h,
    128             final float horizontalCenterPercent, final float verticalCenterPercent) {
    129         if (horizontalCenterPercent < 0 || horizontalCenterPercent > 1 || verticalCenterPercent < 0
    130                 || verticalCenterPercent > 1) {
    131             throw new IllegalArgumentException(
    132                     "horizontalCenterPercent and verticalCenterPercent must be between 0.0f and "
    133                             + "1.0f, inclusive.");
    134         }
    135         final int srcWidth = src.getWidth();
    136         final int srcHeight = src.getHeight();
    137 
    138         // exit early if no resize/crop needed
    139         if (w == srcWidth && h == srcHeight) {
    140             return src;
    141         }
    142 
    143         final Matrix m = new Matrix();
    144         final float scale = Math.max(
    145                 (float) w / srcWidth,
    146                 (float) h / srcHeight);
    147         m.setScale(scale, scale);
    148 
    149         final int srcCroppedW, srcCroppedH;
    150         int srcX, srcY;
    151 
    152         srcCroppedW = Math.round(w / scale);
    153         srcCroppedH = Math.round(h / scale);
    154         srcX = (int) (srcWidth * horizontalCenterPercent - srcCroppedW / 2);
    155         srcY = (int) (srcHeight * verticalCenterPercent - srcCroppedH / 2);
    156 
    157         // Nudge srcX and srcY to be within the bounds of src
    158         srcX = Math.max(Math.min(srcX, srcWidth - srcCroppedW), 0);
    159         srcY = Math.max(Math.min(srcY, srcHeight - srcCroppedH), 0);
    160 
    161         final Bitmap cropped = Bitmap.createBitmap(src, srcX, srcY, srcCroppedW, srcCroppedH, m,
    162                 true /* filter */);
    163 
    164         if (DEBUG) LogUtils.i(PhotoManager.TAG,
    165                 "IN centerCrop, srcW/H=%s/%s desiredW/H=%s/%s srcX/Y=%s/%s" +
    166                 " innerW/H=%s/%s scale=%s resultW/H=%s/%s",
    167                 srcWidth, srcHeight, w, h, srcX, srcY, srcCroppedW, srcCroppedH, scale,
    168                 cropped.getWidth(), cropped.getHeight());
    169         if (DEBUG && (w != cropped.getWidth() || h != cropped.getHeight())) {
    170             LogUtils.e(PhotoManager.TAG, new Error(), "last center crop violated assumptions.");
    171         }
    172 
    173         return cropped;
    174     }
    175 
    176 }
    177