blob: 2714b6a6e1594de0c0c8aac0436800dfd1eb93a9 [file] [log] [blame]
/*
* 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;
}
}