| package com.bumptech.glide.load.resource.bitmap; |
| |
| import android.annotation.TargetApi; |
| import android.graphics.Bitmap; |
| import android.graphics.Canvas; |
| import android.graphics.Matrix; |
| import android.graphics.Paint; |
| import android.graphics.RectF; |
| import android.media.ExifInterface; |
| import android.os.Build; |
| import android.util.Log; |
| |
| import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool; |
| |
| /** |
| * A class with methods to efficiently resize Bitmaps. |
| */ |
| public final class TransformationUtils { |
| private static final String TAG = "TransformationUtils"; |
| public static final int PAINT_FLAGS = Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG | Paint.FILTER_BITMAP_FLAG; |
| |
| private TransformationUtils() { |
| // Utility class. |
| } |
| |
| /** |
| * A potentially expensive operation to crop the given Bitmap so that it fills the given dimensions. This operation |
| * is significantly less expensive in terms of memory if a mutable Bitmap with the given dimensions is passed in |
| * as well. |
| * |
| * @param recycled A mutable Bitmap with dimensions width and height that we can load the cropped portion of toCrop |
| * into. |
| * @param toCrop The Bitmap to resize. |
| * @param width The width in pixels of the final Bitmap. |
| * @param height The height in pixels of the final Bitmap. |
| * @return The resized Bitmap (will be recycled if recycled is not null). |
| */ |
| public static Bitmap centerCrop(Bitmap recycled, Bitmap toCrop, int width, int height) { |
| if (toCrop == null) { |
| return null; |
| } else if (toCrop.getWidth() == width && toCrop.getHeight() == height) { |
| return toCrop; |
| } |
| // From ImageView/Bitmap.createScaledBitmap. |
| final float scale; |
| float dx = 0, dy = 0; |
| Matrix m = new Matrix(); |
| if (toCrop.getWidth() * height > width * toCrop.getHeight()) { |
| scale = (float) height / (float) toCrop.getHeight(); |
| dx = (width - toCrop.getWidth() * scale) * 0.5f; |
| } else { |
| scale = (float) width / (float) toCrop.getWidth(); |
| dy = (height - toCrop.getHeight() * scale) * 0.5f; |
| } |
| |
| m.setScale(scale, scale); |
| m.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f)); |
| final Bitmap result; |
| if (recycled != null) { |
| result = recycled; |
| } else { |
| result = Bitmap.createBitmap(width, height, toCrop.getConfig() == null |
| ? Bitmap.Config.ARGB_8888 : toCrop.getConfig()); |
| } |
| |
| // We don't add or remove alpha, so keep the alpha setting of the Bitmap we were given. |
| TransformationUtils.setAlpha(toCrop, result); |
| |
| Canvas canvas = new Canvas(result); |
| Paint paint = new Paint(PAINT_FLAGS); |
| canvas.drawBitmap(toCrop, m, paint); |
| return result; |
| } |
| |
| /** |
| * An expensive operation to resize the given Bitmap down so that it fits within the given dimensions maintain |
| * the original proportions. |
| * |
| * @param toFit The Bitmap to shrink. |
| * @param pool The BitmapPool to try to reuse a bitmap from. |
| * @param width The width in pixels the final image will fit within. |
| * @param height The height in pixels the final image will fit within. |
| * @return A new Bitmap shrunk to fit within the given dimensions, or toFit if toFit's width or height matches the |
| * given dimensions and toFit fits within the given dimensions |
| */ |
| public static Bitmap fitCenter(Bitmap toFit, BitmapPool pool, int width, int height) { |
| if (toFit.getWidth() == width && toFit.getHeight() == height) { |
| if (Log.isLoggable(TAG, Log.VERBOSE)) { |
| Log.v(TAG, "requested target size matches input, returning input"); |
| } |
| return toFit; |
| } |
| final float widthPercentage = width / (float) toFit.getWidth(); |
| final float heightPercentage = height / (float) toFit.getHeight(); |
| final float minPercentage = Math.min(widthPercentage, heightPercentage); |
| |
| // take the floor of the target width/height, not round. If the matrix |
| // passed into drawBitmap rounds differently, we want to slightly |
| // overdraw, not underdraw, to avoid artifacts from bitmap reuse. |
| final int targetWidth = (int) (minPercentage * toFit.getWidth()); |
| final int targetHeight = (int) (minPercentage * toFit.getHeight()); |
| |
| if (toFit.getWidth() == targetWidth && toFit.getHeight() == targetHeight) { |
| if (Log.isLoggable(TAG, Log.VERBOSE)) { |
| Log.v(TAG, "adjusted target size matches input, returning input"); |
| } |
| return toFit; |
| } |
| |
| Bitmap.Config config = toFit.getConfig() != null ? toFit.getConfig() : Bitmap.Config.ARGB_8888; |
| Bitmap toReuse = pool.get(targetWidth, targetHeight, config); |
| if (toReuse == null) { |
| toReuse = Bitmap.createBitmap(targetWidth, targetHeight, config); |
| } |
| // We don't add or remove alpha, so keep the alpha setting of the Bitmap we were given. |
| TransformationUtils.setAlpha(toFit, toReuse); |
| |
| if (Log.isLoggable(TAG, Log.VERBOSE)) { |
| Log.v(TAG, "request: " + width + "x" + height); |
| Log.v(TAG, "toFit: " + toFit.getWidth() + "x" + toFit.getHeight()); |
| Log.v(TAG, "toReuse: " + toReuse.getWidth() + "x" + toReuse.getHeight()); |
| Log.v(TAG, "minPct: " + minPercentage); |
| } |
| |
| Canvas canvas = new Canvas(toReuse); |
| Matrix matrix = new Matrix(); |
| matrix.setScale(minPercentage, minPercentage); |
| Paint paint = new Paint(PAINT_FLAGS); |
| canvas.drawBitmap(toFit, matrix, paint); |
| |
| return toReuse; |
| } |
| |
| /** |
| * Sets the alpha of the Bitmap we're going to re-use to the alpha of the Bitmap we're going to transform. This |
| * keeps {@link android.graphics.Bitmap#hasAlpha()}} consistent before and after the transformation for |
| * transformations that don't add or remove transparent pixels. |
| * |
| * @param toTransform The {@link android.graphics.Bitmap} that will be transformed. |
| * @param outBitmap The {@link android.graphics.Bitmap} that will be returned from the transformation. |
| */ |
| @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1) |
| public static void setAlpha(Bitmap toTransform, Bitmap outBitmap) { |
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1 && outBitmap != null) { |
| outBitmap.setHasAlpha(toTransform.hasAlpha()); |
| } |
| } |
| |
| /** |
| * Returns a matrix with rotation set based on Exif orientation tag. |
| * If the orientation is undefined or 0 null is returned. |
| * |
| * @deprecated No longer used by Glide, scheduled to be removed in Glide 4.0 |
| * @param pathToOriginal Path to original image file that may have exif data. |
| * @return A rotation in degrees based on exif orientation |
| */ |
| @TargetApi(Build.VERSION_CODES.ECLAIR) |
| @Deprecated |
| public static int getOrientation(String pathToOriginal) { |
| int degreesToRotate = 0; |
| try { |
| ExifInterface exif = new ExifInterface(pathToOriginal); |
| int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED); |
| return getExifOrientationDegrees(orientation); |
| } catch (Exception e) { |
| if (Log.isLoggable(TAG, Log.ERROR)) { |
| Log.e(TAG, "Unable to get orientation for image with path=" + pathToOriginal, e); |
| } |
| } |
| return degreesToRotate; |
| } |
| |
| /** |
| * This is an expensive operation that copies the image in place with the pixels rotated. |
| * If possible rather use getOrientationMatrix, and set that as the imageMatrix on an ImageView. |
| * |
| * @deprecated No longer used by Glide, scheduled to be removed in Glide 4.0 |
| * @param pathToOriginal Path to original image file that may have exif data. |
| * @param imageToOrient Image Bitmap to orient. |
| * @return The oriented bitmap. May be the imageToOrient without modification, or a new Bitmap. |
| */ |
| @Deprecated |
| public static Bitmap orientImage(String pathToOriginal, Bitmap imageToOrient) { |
| int degreesToRotate = getOrientation(pathToOriginal); |
| return rotateImage(imageToOrient, degreesToRotate); |
| } |
| |
| /** |
| * This is an expensive operation that copies the image in place with the pixels rotated. |
| * If possible rather use getOrientationMatrix, and set that as the imageMatrix on an ImageView. |
| * |
| * @param imageToOrient Image Bitmap to orient. |
| * @param degreesToRotate number of degrees to rotate the image by. If zero the original image is returned |
| * unmodified. |
| * @return The oriented bitmap. May be the imageToOrient without modification, or a new Bitmap. |
| */ |
| public static Bitmap rotateImage(Bitmap imageToOrient, int degreesToRotate) { |
| Bitmap result = imageToOrient; |
| try { |
| if (degreesToRotate != 0) { |
| Matrix matrix = new Matrix(); |
| matrix.setRotate(degreesToRotate); |
| result = Bitmap.createBitmap( |
| imageToOrient, |
| 0, |
| 0, |
| imageToOrient.getWidth(), |
| imageToOrient.getHeight(), |
| matrix, |
| true); |
| } |
| } catch (Exception e) { |
| if (Log.isLoggable(TAG, Log.ERROR)) { |
| Log.e(TAG, "Exception when trying to orient image", e); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * Get the # of degrees an image must be rotated to match the given exif orientation. |
| * |
| * @param exifOrientation The exif orientation [1-8] |
| * @return the number of degrees to rotate |
| */ |
| public static int getExifOrientationDegrees(int exifOrientation) { |
| final int degreesToRotate; |
| switch (exifOrientation) { |
| case ExifInterface.ORIENTATION_TRANSPOSE: |
| case ExifInterface.ORIENTATION_ROTATE_90: |
| degreesToRotate = 90; |
| break; |
| case ExifInterface.ORIENTATION_ROTATE_180: |
| case ExifInterface.ORIENTATION_FLIP_VERTICAL: |
| degreesToRotate = 180; |
| break; |
| case ExifInterface.ORIENTATION_TRANSVERSE: |
| case ExifInterface.ORIENTATION_ROTATE_270: |
| degreesToRotate = 270; |
| break; |
| default: |
| degreesToRotate = 0; |
| |
| } |
| return degreesToRotate; |
| } |
| |
| /** |
| * Rotate and/or flip the image to match the given exif orientation. |
| * |
| * @param toOrient The bitmap to rotate/flip. |
| * @param pool A pool that may or may not contain an image of the necessary dimensions. |
| * @param exifOrientation the exif orientation [1-8]. |
| * @return The rotated and/or flipped image or toOrient if no rotation or flip was necessary. |
| */ |
| public static Bitmap rotateImageExif(Bitmap toOrient, BitmapPool pool, int exifOrientation) { |
| if (exifOrientation == ExifInterface.ORIENTATION_NORMAL |
| || exifOrientation == ExifInterface.ORIENTATION_UNDEFINED) { |
| return toOrient; |
| } |
| final Matrix matrix = new Matrix(); |
| initializeMatrixForRotation(exifOrientation, matrix); |
| |
| // From Bitmap.createBitmap. |
| final RectF newRect = new RectF(0, 0, toOrient.getWidth(), toOrient.getHeight()); |
| matrix.mapRect(newRect); |
| |
| final int newWidth = Math.round(newRect.width()); |
| final int newHeight = Math.round(newRect.height()); |
| |
| Bitmap result = pool.get(newWidth, newHeight, toOrient.getConfig()); |
| if (result == null) { |
| result = Bitmap.createBitmap(newWidth, newHeight, toOrient.getConfig()); |
| } |
| |
| matrix.postTranslate(-newRect.left, -newRect.top); |
| |
| final Canvas canvas = new Canvas(result); |
| final Paint paint = new Paint(PAINT_FLAGS); |
| canvas.drawBitmap(toOrient, matrix, paint); |
| |
| return result; |
| } |
| |
| // Visible for testing. |
| static void initializeMatrixForRotation(int exifOrientation, Matrix matrix) { |
| switch (exifOrientation) { |
| case ExifInterface.ORIENTATION_FLIP_HORIZONTAL: |
| matrix.setScale(-1, 1); |
| break; |
| case ExifInterface.ORIENTATION_ROTATE_180: |
| matrix.setRotate(180); |
| break; |
| case ExifInterface.ORIENTATION_FLIP_VERTICAL: |
| matrix.setRotate(180); |
| matrix.postScale(-1, 1); |
| break; |
| case ExifInterface.ORIENTATION_TRANSPOSE: |
| matrix.setRotate(90); |
| matrix.postScale(-1, 1); |
| break; |
| case ExifInterface.ORIENTATION_ROTATE_90: |
| matrix.setRotate(90); |
| break; |
| case ExifInterface.ORIENTATION_TRANSVERSE: |
| matrix.setRotate(-90); |
| matrix.postScale(-1, 1); |
| break; |
| case ExifInterface.ORIENTATION_ROTATE_270: |
| matrix.setRotate(-90); |
| break; |
| default: |
| // Do nothing. |
| } |
| } |
| } |