TransformationUtils.java revision 5ba19a0e69ad3a651b8f13ba45de48a56b56ce36
1package com.bumptech.glide.load.resource.bitmap; 2 3import android.annotation.TargetApi; 4import android.graphics.Bitmap; 5import android.graphics.Canvas; 6import android.graphics.Matrix; 7import android.graphics.Paint; 8import android.graphics.RectF; 9import android.media.ExifInterface; 10import android.os.Build; 11import android.util.Log; 12import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool; 13 14/** 15 * A class with methods to efficiently resize Bitmaps. 16 */ 17public final class TransformationUtils { 18 private static final String TAG = "TransformationUtils"; 19 public static final int PAINT_FLAGS = Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG | Paint.FILTER_BITMAP_FLAG; 20 21 private TransformationUtils() { 22 // Utility class. 23 } 24 25 /** 26 * A potentially expensive operation to crop the given Bitmap so that it fills the given dimensions. This operation 27 * is significantly less expensive in terms of memory if a mutable Bitmap with the given dimensions is passed in 28 * as well. 29 * 30 * @param recycled A mutable Bitmap with dimensions width and height that we can load the cropped portion of toCrop 31 * into. 32 * @param toCrop The Bitmap to resize. 33 * @param width The width of the final Bitmap. 34 * @param height The height of the final Bitmap. 35 * @return The resized Bitmap (will be recycled if recycled is not null). 36 */ 37 public static Bitmap centerCrop(Bitmap recycled, Bitmap toCrop, int width, int height) { 38 if (toCrop == null) { 39 return null; 40 } else if (toCrop.getWidth() == width && toCrop.getHeight() == height) { 41 return toCrop; 42 } 43 // From ImageView/Bitmap.createScaledBitmap. 44 final float scale; 45 float dx = 0, dy = 0; 46 Matrix m = new Matrix(); 47 if (toCrop.getWidth() * height > width * toCrop.getHeight()) { 48 scale = (float) height / (float) toCrop.getHeight(); 49 dx = (width - toCrop.getWidth() * scale) * 0.5f; 50 } else { 51 scale = (float) width / (float) toCrop.getWidth(); 52 dy = (height - toCrop.getHeight() * scale) * 0.5f; 53 } 54 55 m.setScale(scale, scale); 56 m.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f)); 57 final Bitmap result; 58 if (recycled != null) { 59 result = recycled; 60 } else { 61 result = Bitmap.createBitmap(width, height, toCrop.getConfig() == null 62 ? Bitmap.Config.ARGB_8888 : toCrop.getConfig()); 63 } 64 65 // We don't add or remove alpha, so keep the alpha setting of the Bitmap we were given. 66 TransformationUtils.setAlpha(toCrop, result); 67 68 Canvas canvas = new Canvas(result); 69 Paint paint = new Paint(PAINT_FLAGS); 70 canvas.drawBitmap(toCrop, m, paint); 71 return result; 72 } 73 74 /** 75 * An expensive operation to resize the given Bitmap down so that it fits within the given dimensions maintain 76 * the original proportions. 77 * 78 * @param toFit The Bitmap to shrink. 79 * @param pool The BitmapPool to try to reuse a bitmap from. 80 * @param width The width the final image will fit within. 81 * @param height The height the final image will fit within. 82 * @return A new Bitmap shrunk to fit within the given dimensions, or toFit if toFit's width or height matches the 83 * given dimensions and toFit fits within the given dimensions 84 */ 85 public static Bitmap fitCenter(Bitmap toFit, BitmapPool pool, int width, int height) { 86 if (toFit.getWidth() == width && toFit.getHeight() == height) { 87 return toFit; 88 } 89 final float widthPercentage = width / (float) toFit.getWidth(); 90 final float heightPercentage = height / (float) toFit.getHeight(); 91 final float minPercentage = Math.min(widthPercentage, heightPercentage); 92 93 final int targetWidth = Math.round(minPercentage * toFit.getWidth()); 94 final int targetHeight = Math.round(minPercentage * toFit.getHeight()); 95 96 Bitmap.Config config = toFit.getConfig() != null ? toFit.getConfig() : Bitmap.Config.ARGB_8888; 97 Bitmap toReuse = pool.get(targetWidth, targetHeight, config); 98 if (toReuse == null) { 99 toReuse = Bitmap.createBitmap(targetWidth, targetHeight, config); 100 } 101 102 // We don't add or remove alpha, so keep the alpha setting of the Bitmap we were given. 103 TransformationUtils.setAlpha(toFit, toReuse); 104 105 Canvas canvas = new Canvas(toReuse); 106 Matrix matrix = new Matrix(); 107 matrix.setScale(minPercentage, minPercentage); 108 Paint paint = new Paint(PAINT_FLAGS); 109 canvas.drawBitmap(toFit, matrix, paint); 110 111 return toReuse; 112 } 113 114 /** 115 * Sets the alpha of the Bitmap we're going to re-use to the alpha of the Bitmap we're going to transform. This 116 * keeps {@link android.graphics.Bitmap#hasAlpha()}} consistent before and after the transformation for 117 * transformations that don't add or remove transparent pixels. 118 * 119 * @param toTransform The {@link android.graphics.Bitmap} that will be transformed. 120 * @param outBitmap The {@link android.graphics.Bitmap} that will be returned from the transformation. 121 */ 122 @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1) 123 public static void setAlpha(Bitmap toTransform, Bitmap outBitmap) { 124 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1 && outBitmap != null) { 125 outBitmap.setHasAlpha(toTransform.hasAlpha()); 126 } 127 } 128 129 /** 130 * Returns a matrix with rotation set based on Exif orientation tag. 131 * If the orientation is undefined or 0 null is returned. 132 * 133 * @param pathToOriginal Path to original image file that may have exif data. 134 * @return A rotation in degrees based on exif orientation 135 */ 136 @TargetApi(Build.VERSION_CODES.ECLAIR) 137 public static int getOrientation(String pathToOriginal) { 138 int degreesToRotate = 0; 139 try { 140 ExifInterface exif = new ExifInterface(pathToOriginal); 141 int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED); 142 if (orientation == ExifInterface.ORIENTATION_ROTATE_90) { 143 degreesToRotate = 90; 144 } else if (orientation == ExifInterface.ORIENTATION_ROTATE_180) { 145 degreesToRotate = 180; 146 } else if (orientation == ExifInterface.ORIENTATION_ROTATE_270) { 147 degreesToRotate = 270; 148 } 149 } catch (Exception e) { 150 if (Log.isLoggable(TAG, Log.ERROR)) { 151 Log.e(TAG, "Unable to get orientation for image with path=" + pathToOriginal, e); 152 } 153 } 154 return degreesToRotate; 155 } 156 157 /** 158 * This is an expensive operation that copies the image in place with the pixels rotated. 159 * If possible rather use getOrientationMatrix, and set that as the imageMatrix on an ImageView. 160 * 161 * @param pathToOriginal Path to original image file that may have exif data. 162 * @param imageToOrient Image Bitmap to orient. 163 * @return The oriented bitmap. May be the imageToOrient without modification, or a new Bitmap. 164 */ 165 public static Bitmap orientImage(String pathToOriginal, Bitmap imageToOrient) { 166 int degreesToRotate = getOrientation(pathToOriginal); 167 return rotateImage(imageToOrient, degreesToRotate); 168 } 169 170 /** 171 * This is an expensive operation that copies the image in place with the pixels rotated. 172 * If possible rather use getOrientationMatrix, and set that as the imageMatrix on an ImageView. 173 * 174 * @param imageToOrient Image Bitmap to orient. 175 * @param degreesToRotate number of degrees to rotate the image by. If zero the original image is returned 176 * unmodified. 177 * @return The oriented bitmap. May be the imageToOrient without modification, or a new Bitmap. 178 */ 179 public static Bitmap rotateImage(Bitmap imageToOrient, int degreesToRotate) { 180 Bitmap result = imageToOrient; 181 try { 182 if (degreesToRotate != 0) { 183 Matrix matrix = new Matrix(); 184 matrix.setRotate(degreesToRotate); 185 result = Bitmap.createBitmap( 186 imageToOrient, 187 0, 188 0, 189 imageToOrient.getWidth(), 190 imageToOrient.getHeight(), 191 matrix, 192 true); 193 } 194 } catch (Exception e) { 195 if (Log.isLoggable(TAG, Log.ERROR)) { 196 Log.e(TAG, "Exception when trying to orient image", e); 197 } 198 } 199 return result; 200 } 201 202 /** 203 * Get the # of degrees an image must be rotated to match the given exif orientation. 204 * 205 * @param exifOrientation The exif orientation [1-8] 206 * @return the number of degrees to rotate 207 */ 208 public static int getExifOrientationDegrees(int exifOrientation) { 209 final int degreesToRotate; 210 switch (exifOrientation) { 211 case ExifInterface.ORIENTATION_TRANSPOSE: 212 case ExifInterface.ORIENTATION_ROTATE_90: 213 degreesToRotate = 90; 214 break; 215 case ExifInterface.ORIENTATION_ROTATE_180: 216 case ExifInterface.ORIENTATION_FLIP_VERTICAL: 217 degreesToRotate = 180; 218 break; 219 case ExifInterface.ORIENTATION_TRANSVERSE: 220 case ExifInterface.ORIENTATION_ROTATE_270: 221 degreesToRotate = 270; 222 break; 223 default: 224 degreesToRotate = 0; 225 226 } 227 return degreesToRotate; 228 } 229 230 /** 231 * Rotate and/or flip the image to match the given exif orientation. 232 * 233 * @param toOrient The bitmap to rotate/flip. 234 * @param pool A pool that may or may not contain an image of the necessary dimensions. 235 * @param exifOrientation the exif orientation [1-8]. 236 * @return The rotated and/or flipped image or toOrient if no rotation or flip was necessary. 237 */ 238 public static Bitmap rotateImageExif(Bitmap toOrient, BitmapPool pool, int exifOrientation) { 239 final Matrix matrix = new Matrix(); 240 switch (exifOrientation) { 241 case ExifInterface.ORIENTATION_FLIP_HORIZONTAL: 242 matrix.setScale(-1, 1); 243 break; 244 case ExifInterface.ORIENTATION_ROTATE_180: 245 matrix.setRotate(180); 246 break; 247 case ExifInterface.ORIENTATION_FLIP_VERTICAL: 248 matrix.setRotate(180); 249 matrix.postScale(-1, 1); 250 break; 251 case ExifInterface.ORIENTATION_TRANSPOSE: 252 matrix.setRotate(90); 253 matrix.postScale(-1, 1); 254 break; 255 case ExifInterface.ORIENTATION_ROTATE_90: 256 matrix.setRotate(90); 257 break; 258 case ExifInterface.ORIENTATION_TRANSVERSE: 259 matrix.setRotate(-90); 260 matrix.postScale(-1, 1); 261 break; 262 case ExifInterface.ORIENTATION_ROTATE_270: 263 matrix.setRotate(-90); 264 break; 265 // case ExifInterface.ORIENTATION_NORMAL 266 default: 267 return toOrient; 268 } 269 270 // From Bitmap.createBitmap. 271 final RectF newRect = new RectF(0, 0, toOrient.getWidth(), toOrient.getHeight()); 272 matrix.mapRect(newRect); 273 274 final int newWidth = Math.round(newRect.width()); 275 final int newHeight = Math.round(newRect.height()); 276 277 Bitmap result = pool.get(newWidth, newHeight, toOrient.getConfig()); 278 if (result == null) { 279 result = Bitmap.createBitmap(newWidth, newHeight, toOrient.getConfig()); 280 } 281 282 matrix.postTranslate(-newRect.left, -newRect.top); 283 284 final Canvas canvas = new Canvas(result); 285 final Paint paint = new Paint(PAINT_FLAGS); 286 canvas.drawBitmap(toOrient, matrix, paint); 287 288 return result; 289 } 290} 291