/* * 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; } }