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