Last active
October 16, 2023 15:42
-
-
Save bjornson/3ff8888c09908d5c6cc345d0a8e1f6a7 to your computer and use it in GitHub Desktop.
Custom Glide CropTransformation that allows top/center/bottom left/center/right crop with percentages
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.extendedvision.futurehistory.images; | |
import android.content.Context; | |
import android.graphics.Bitmap; | |
import android.graphics.Canvas; | |
import android.graphics.Matrix; | |
import android.graphics.Paint; | |
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool; | |
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation; | |
import com.bumptech.glide.load.resource.bitmap.TransformationUtils; | |
import static com.bumptech.glide.load.resource.bitmap.TransformationUtils.PAINT_FLAGS; | |
/** | |
* Created by bjornson on 27.09.16. | |
*/ | |
public class PositionedCropTransformation extends BitmapTransformation { | |
private float xPercentage = 0.5f; | |
private float yPercentage = 0.5f; | |
public PositionedCropTransformation(Context context) { | |
super(context); | |
} | |
public PositionedCropTransformation(Context context, @FloatRange(from = 0.0, to = 1.0)float xPercentage, @FloatRange(from = 0.0, to = 1.0)float yPercentage) { | |
super(context); | |
this.xPercentage = xPercentage; | |
this.yPercentage = yPercentage; | |
} | |
public PositionedCropTransformation(BitmapPool bitmapPool) { | |
super(bitmapPool); | |
} | |
// Bitmap doesn't implement equals, so == and .equals are equivalent here. | |
@SuppressWarnings("PMD.CompareObjectsWithEquals") | |
@Override | |
protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) { | |
final Bitmap toReuse = pool.get(outWidth, outHeight, toTransform.getConfig() != null | |
? toTransform.getConfig() : Bitmap.Config.ARGB_8888); | |
Bitmap transformed = crop(toReuse, toTransform, outWidth, outHeight, xPercentage, yPercentage); | |
if (toReuse != null && toReuse != transformed && !pool.put(toReuse)) { | |
toReuse.recycle(); | |
} | |
return transformed; | |
} | |
@Override | |
public String getId() { | |
return "PositionedCropTransformation.com.bumptech.glide.load.resource.bitmap.x:" + xPercentage + ".y:" + yPercentage; | |
} | |
/** | |
* 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. | |
* @param xPercentage The horizontal percentage of the crop. 0.0f => left, 0.5f => center, 1.0f => right or anything in between 0 and 1 | |
* @param yPercentage The vertical percentage of the crop. 0.0f => top, 0.5f => center, 1.0f => bottom or anything in between 0 and 1 | |
* @return The resized Bitmap (will be recycled if recycled is not null). | |
*/ | |
private static Bitmap crop(Bitmap recycled, Bitmap toCrop, int width, int height, float xPercentage, float yPercentage) { | |
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); | |
dx *= xPercentage; | |
} else { | |
scale = (float) width / (float) toCrop.getWidth(); | |
dy = (height - toCrop.getHeight() * scale); | |
dy *= yPercentage; | |
} | |
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, getSafeConfig(toCrop)); | |
} | |
// 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; | |
} | |
private static Bitmap.Config getSafeConfig(Bitmap bitmap) { | |
return bitmap.getConfig() != null ? bitmap.getConfig() : Bitmap.Config.ARGB_8888; | |
} | |
} |
@bjornson will this work on Glide 4.7.1 ?
not working on Glide 4.11.0
Not working
I converted code to support for Glide v4.x
For my use case its set to top-right, change accordingly
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Matrix
import android.graphics.Paint
import androidx.annotation.FloatRange
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
import com.bumptech.glide.load.resource.bitmap.TransformationUtils
import com.bumptech.glide.load.resource.bitmap.TransformationUtils.PAINT_FLAGS
import java.nio.charset.Charset
import java.security.MessageDigest
class TopRightCropTransformation(
val context: Context,
@FloatRange(from = 0.0, to = 1.0) val xPercentage: Float = 0.0f,
@FloatRange(from = 0.0, to = 1.0) val yPercentage: Float = 0.0f
) : BitmapTransformation() {
companion object {
private const val id = "com.testbook.tbapp.base.utils.TopRightCropTransformation"
private val idBytes = id.toByteArray(Charset.forName("UTF-8"))
}
override fun updateDiskCacheKey(messageDigest: MessageDigest) {
messageDigest.update(idBytes)
}
override fun transform(
pool: BitmapPool,
toTransform: Bitmap,
outWidth: Int,
outHeight: Int
): Bitmap? {
val toReuse =
pool[outWidth, outHeight, if (toTransform.config != null) toTransform.config else Bitmap.Config.ARGB_8888]
val transformed: Bitmap? =
crop(toReuse, toTransform, outWidth, outHeight, xPercentage, yPercentage)
if (toReuse != transformed) {
pool.put(toReuse)
}
return transformed
}
/**
* 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.
* @param xPercentage The horizontal percentage of the crop. 0.0f => left, 0.5f => center, 1.0f => right or anything in between 0 and 1
* @param yPercentage The vertical percentage of the crop. 0.0f => top, 0.5f => center, 1.0f => bottom or anything in between 0 and 1
* @return The resized Bitmap (will be recycled if recycled is not null).
*/
private fun crop(
recycled: Bitmap?,
toCrop: Bitmap?,
width: Int,
height: Int,
xPercentage: Float,
yPercentage: Float
): Bitmap? {
if (toCrop == null) {
return null
} else if (toCrop.width == width && toCrop.height == height) {
return toCrop
}
// From ImageView/Bitmap.createScaledBitmap.
val scale: Float
var dx = 0f
var dy = 0f
val m = Matrix()
if (toCrop.width * height > width * toCrop.height) {
scale = height.toFloat() / toCrop.height.toFloat()
dx = width - toCrop.width * scale
dx *= xPercentage
} else {
scale = width.toFloat() / toCrop.width.toFloat()
dy = height - toCrop.height * scale
dy *= yPercentage
}
m.setScale(scale, scale)
m.postTranslate((dx + 0.5f), (dy + 0.5f))
val result: Bitmap = recycled ?: Bitmap.createBitmap(width, height, getSafeConfig(toCrop)!!)
// We don't add or remove alpha, so keep the alpha setting of the Bitmap we were given.
TransformationUtils.setAlpha(toCrop, result)
val canvas = Canvas(result)
val paint = Paint(PAINT_FLAGS)
canvas.drawBitmap(toCrop, m, paint)
return result
}
private fun getSafeConfig(bitmap: Bitmap): Bitmap.Config? {
return if (bitmap.config != null) bitmap.config else Bitmap.Config.ARGB_8888
}
}
the xPercentage and yPercentage changed but the image still in same position show from top and left, cropped at right and bottom.
any solutions?
Could the author please state a license for the above code? Thank you!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Use it just like any other bitmap transformation:
Commonly needed values:
top-left:
0, 0
top-right:
0, 1
bottom-left:
1, 0
bottom-right:
1, 1
Use 0.5f for center