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