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