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