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 BitmapUtil()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 */ decodeByteArray(byte[] src, int w, int h)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 */ decodeByteArrayWithCenterCrop(byte[] src, int w, int h)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 */ centerCrop(final Bitmap src, final int w, final int h)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 */ crop(final Bitmap src, final int w, final int h, final float horizontalCenterPercent, final float verticalCenterPercent)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