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