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.photomanager; 18import android.graphics.Bitmap; 19import android.graphics.BitmapFactory; 20import android.graphics.Matrix; 21 22import com.android.mail.utils.LogUtils; 23 24/** 25 * Provides static functions to decode bitmaps at the optimal size 26 */ 27public 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