• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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