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