| /* |
| * Copyright (c) 2012 Bump Technologies Inc. All rights reserved. |
| */ |
| package com.bumptech.glide.load.resource.bitmap; |
| |
| 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.util.Log; |
| import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool; |
| |
| /** |
| * A class for storing methods to resize Bitmaps |
| */ |
| public 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; |
| |
| /** |
| * 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 of the final Bitmap |
| * @param height The height 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 |
| //https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/widget/ImageView.java |
| //https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/graphics/java/android/graphics/Bitmap.java |
| 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()); |
| } |
| 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 the final image will fit within. |
| * @param height The height 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) { |
| return toFit; |
| } |
| final float widthPercentage = width / (float) toFit.getWidth(); |
| final float heightPercentage = height / (float) toFit.getHeight(); |
| final float minPercentage = Math.min(widthPercentage, heightPercentage); |
| |
| final int targetWidth = Math.round(minPercentage * toFit.getWidth()); |
| final int targetHeight = Math.round(minPercentage * toFit.getHeight()); |
| |
| 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); |
| } |
| 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; |
| } |
| |
| /** |
| * Returns a matrix with rotation set based on Exif orientation tag. |
| * If the orientation is undefined or 0 null is returned. |
| * |
| * @param pathToOriginal Path to original image file that may have exif data. |
| * @return A rotation in degrees based on exif orientation |
| */ |
| 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); |
| if (orientation == ExifInterface.ORIENTATION_ROTATE_90){ |
| degreesToRotate = 90; |
| } else if (orientation == ExifInterface.ORIENTATION_ROTATE_180){ |
| degreesToRotate = 180; |
| } else if (orientation == ExifInterface.ORIENTATION_ROTATE_270){ |
| degreesToRotate = 270; |
| } |
| } 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. |
| * |
| * @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. |
| */ |
| @SuppressWarnings("unused") |
| 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) { |
| try{ |
| if(degreesToRotate != 0) { |
| Matrix matrix = new Matrix(); |
| matrix.setRotate(degreesToRotate); |
| imageToOrient = 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); |
| } |
| e.printStackTrace(); |
| } |
| return imageToOrient; |
| } |
| |
| /** |
| * 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) { |
| final Matrix matrix = new 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: //case ExifInterface.ORIENTATION_NORMAL |
| return toOrient; |
| } |
| |
| // 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; |
| } |
| } |