package jp.wasabeef.picasso.transformations;
/**
* Copyright (C) 2017 Wasabeef, molexx
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.Log;
import com.squareup.picasso.Transformation;
/**
* Crops a rectangle, allowing its dimensions and positioning to be specified by a combination of:
* width/height in pixels
* width/height as a ratio of the source image
* aspect ratio
* offset from left/top in pixels
* horizontal and vertical gravity
*
* If aspect ratio is provided then both width and height should not be provided or the ratio wil
* be
* ignored.
* If neither width or height are provided then the aspect ratio is used to crop the largest
* possible image.
*
* Constructors accepting width and height expect pixels if the values are ints and ratio of source
* image if the
* values are floats.
*/
public class CropTransformation implements Transformation {
private static final String TAG = "PicassoTransformation";
public enum GravityHorizontal {
LEFT,
CENTER,
RIGHT
}
public enum GravityVertical {
TOP,
CENTER,
BOTTOM
}
private float mAspectRatio;
private int mLeft;
private int mTop;
private int mWidth;
private int mHeight;
private float mWidthRatio;
private float mHeightRatio;
private GravityHorizontal mGravityHorizontal = GravityHorizontal.CENTER;
private GravityVertical mGravityVertical = GravityVertical.CENTER;
/**
* Crops to the given size and offset in pixels.
* If either width or height is zero then the original image's dimension is used.
*
* @param left offset in pixels from the left edge of the source image
* @param top offset in pixels from the top of the source image
* @param width in pixels
* @param height in pixels
*/
public CropTransformation(int left, int top, int width, int height) {
mLeft = left;
mTop = top;
mWidth = width;
mHeight = height;
}
/**
* Crops to the given width and height (in pixels) using the given gravity.
* If either width or height is zero then the original image's dimension is used.
*
* @param width in pixels
* @param height in pixels
* @param gravityHorizontal position of the cropped area within the larger source image
* @param gravityVertical position of the cropped area within the larger source image
*/
public CropTransformation(int width, int height, GravityHorizontal gravityHorizontal,
GravityVertical gravityVertical) {
mWidth = width;
mHeight = height;
mGravityHorizontal = gravityHorizontal;
mGravityVertical = gravityVertical;
}
/**
* Crops to the given width and height (in pixels), defaulting gravity to CENTER/CENTER.
* If either width or height is zero then the original image's dimension is used.
*
* @param width in pixels
* @param height in pixels
*/
public CropTransformation(int width, int height) {
this(width, height, GravityHorizontal.CENTER, GravityVertical.CENTER);
}
/**
* Crops to a ratio of the source image's width/height.
*
* e.g. (0.5, 0.5, LEFT, TOP) will crop a quarter-sized box from the top left of the original.
*
* If widthRatio or heightRatio are zero then 100% of the original image's dimension will be
* used.
*
* @param widthRatio width of the target image relative to the width of the source image; 1 =
* 100%
* @param heightRatio height of the target image relative to the height of the source image; 1 =
* 100%
* @param gravityHorizontal position of the cropped area within the larger source image
* @param gravityVertical position of the cropped area within the larger source image
*/
public CropTransformation(float widthRatio, float heightRatio,
GravityHorizontal gravityHorizontal, GravityVertical gravityVertical) {
mWidthRatio = widthRatio;
mHeightRatio = heightRatio;
mGravityHorizontal = gravityHorizontal;
mGravityVertical = gravityVertical;
}
/**
* Crops to a ratio of the source image's width/height, defaulting to CENTER/CENTER gravity.
*
* e.g. (0.5, 0.5) will crop a quarter-sized box from the middle of the original.
*
* If widthRatio or heightRatio are zero then 100% of the original image's dimension will be
* used.
*
* @param widthRatio width of the target image relative to the width of the source image; 1 =
* 100%
* @param heightRatio height of the target image relative to the height of the source image; 1 =
* 100%
*/
public CropTransformation(float widthRatio, float heightRatio) {
this(widthRatio, heightRatio, GravityHorizontal.CENTER, GravityVertical.CENTER);
}
/**
* Crops to the desired aspectRatio driven by either width OR height in pixels.
* If one of width/height is greater than zero the other is calculated
* If width and height are both zero then the largest area matching the aspectRatio is returned
* If both width and height are specified then the aspectRatio is ignored
*
* If aspectRatio is 0 then the result will be the same as calling the version without
* aspectRatio.
*
* @param width in pixels, one of width/height should be zero
* @param height in pixels, one of width/height should be zero
* @param aspectRatio width/height: greater than 1 is landscape, less than 1 is portrait, 1 is
* square
* @param gravityHorizontal position of the cropped area within the larger source image
* @param gravityVertical position of the cropped area within the larger source image
*/
public CropTransformation(int width, int height, float aspectRatio,
GravityHorizontal gravityHorizontal, GravityVertical gravityVertical) {
mWidth = width;
mHeight = height;
mAspectRatio = aspectRatio;
mGravityHorizontal = gravityHorizontal;
mGravityVertical = gravityVertical;
}
/**
* Crops to the desired aspectRatio driven by either width OR height as a ratio to the original
* image's dimension.
* If one of width/height is greater than zero the other is calculated
* If width and height are both zero then the largest area matching the aspectRatio is returned
* If both width and height are specified then the aspectRatio is ignored
*
* If aspectRatio is 0 then the result will be the same as calling the version without
* aspectRatio.
*
* e.g. to get an image with a width of 50% of the source image's width and cropped to 16:9 from
* the center/center:
* CropTransformation(0.5, (float)0, (float)16/9, CENTER, CENTER);
*
* @param widthRatio width of the target image relative to the width of the source image; 1 =
* 100%
* @param heightRatio height of the target image relative to the height of the source image; 1 =
* 100%
* @param aspectRatio width/height: greater than 1 is landscape, less than 1 is portrait, 1 is
* square
* @param gravityHorizontal position of the cropped area within the larger source image
* @param gravityVertical position of the cropped area within the larger source image
*/
public CropTransformation(float widthRatio, float heightRatio, float aspectRatio,
GravityHorizontal gravityHorizontal, GravityVertical gravityVertical) {
mWidthRatio = widthRatio;
mHeightRatio = heightRatio;
mAspectRatio = aspectRatio;
mGravityHorizontal = gravityHorizontal;
mGravityVertical = gravityVertical;
}
/**
* Crops to the largest image that will fit the given aspectRatio.
* This will effectively chop off either the top/bottom or left/right of the source image.
*
* @param aspectRatio width/height: greater than 1 is landscape, less than 1 is portrait, 1 is
* square
* @param gravityHorizontal position of the cropped area within the larger source image
* @param gravityVertical position of the cropped area within the larger source image
*/
public CropTransformation(float aspectRatio, GravityHorizontal gravityHorizontal,
GravityVertical gravityVertical) {
mAspectRatio = aspectRatio;
mGravityHorizontal = gravityHorizontal;
mGravityVertical = gravityVertical;
}
@Override public Bitmap transform(Bitmap source) {
if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "transform(): called, " + key());
if (mWidth == 0 && mWidthRatio != 0) {
mWidth = (int) ((float) source.getWidth() * mWidthRatio);
}
if (mHeight == 0 && mHeightRatio != 0) {
mHeight = (int) ((float) source.getHeight() * mHeightRatio);
}
if (mAspectRatio != 0) {
if (mWidth == 0 && mHeight == 0) {
float sourceRatio = (float) source.getWidth() / (float) source.getHeight();
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG,
"transform(): mAspectRatio: " + mAspectRatio + ", sourceRatio: " + sourceRatio);
}
if (sourceRatio > mAspectRatio) {
// source is wider than we want, restrict by height
mHeight = source.getHeight();
} else {
// source is taller than we want, restrict by width
mWidth = source.getWidth();
}
}
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "transform(): before setting other of h/w: mAspectRatio: " + mAspectRatio
+ ", set one of width: " + mWidth + ", height: " + mHeight);
}
if (mWidth != 0) {
mHeight = (int) ((float) mWidth / mAspectRatio);
} else {
if (mHeight != 0) {
mWidth = (int) ((float) mHeight * mAspectRatio);
}
}
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG,
"transform(): mAspectRatio: " + mAspectRatio + ", set width: " + mWidth + ", height: "
+ mHeight);
}
}
if (mWidth == 0) {
mWidth = source.getWidth();
}
if (mHeight == 0) {
mHeight = source.getHeight();
}
if (mGravityHorizontal != null) {
mLeft = getLeft(source);
}
if (mGravityVertical != null) {
mTop = getTop(source);
}
Rect sourceRect = new Rect(mLeft, mTop, (mLeft + mWidth), (mTop + mHeight));
Rect targetRect = new Rect(0, 0, mWidth, mHeight);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG,
"transform(): created sourceRect with mLeft: " + mLeft + ", mTop: " + mTop + ", right: "
+ (mLeft + mWidth) + ", bottom: " + (mTop + mHeight));
}
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "transform(): created targetRect with width: " + mWidth + ", height: " + mHeight);
}
Bitmap bitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "transform(): copying from source with width: " + source.getWidth() + ", height: "
+ source.getHeight());
}
canvas.drawBitmap(source, sourceRect, targetRect, null);
source.recycle();
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "transform(): returning bitmap with width: " + bitmap.getWidth() + ", height: "
+ bitmap.getHeight());
}
return bitmap;
}
@Override public String key() {
return "CropTransformation(width=" + mWidth + ", height=" + mHeight + ", mWidthRatio="
+ mWidthRatio + ", mHeightRatio=" + mHeightRatio + ", mAspectRatio=" + mAspectRatio
+ ", gravityHorizontal=" + mGravityHorizontal + ", mGravityVertical=" + mGravityVertical
+ ")";
}
private int getTop(Bitmap source) {
switch (mGravityVertical) {
case TOP:
return 0;
case CENTER:
return (source.getHeight() - mHeight) / 2;
case BOTTOM:
return source.getHeight() - mHeight;
default:
return 0;
}
}
private int getLeft(Bitmap source) {
switch (mGravityHorizontal) {
case LEFT:
return 0;
case CENTER:
return (source.getWidth() - mWidth) / 2;
case RIGHT:
return source.getWidth() - mWidth;
default:
return 0;
}
}
}