Skip to content

Instantly share code, notes, and snippets.

@bjornson
Last active October 16, 2023 15:42
Show Gist options
  • Save bjornson/3ff8888c09908d5c6cc345d0a8e1f6a7 to your computer and use it in GitHub Desktop.
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
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;
}
}
@bernatdelgado87
Copy link

not working on Glide 4.11.0

@galaxi76
Copy link

Not working

@aniketbhoite
Copy link

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
    }
}

@arieftb
Copy link

arieftb commented Nov 4, 2021

the xPercentage and yPercentage changed but the image still in same position show from top and left, cropped at right and bottom.

any solutions?

@bodorjzsolt
Copy link

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