SoFunction
Updated on 2025-03-11

Android crops image codes in any proportion

A friend of the company wrote it and can crop the pictures in any proportion. I think it's very useful. I will record it here briefly and will definitely be used in the future.

public class SeniorCropImageView extends ImageView implements , 
 { 
/* For drawing color field start */ 
private static final int LINE_COLOR = ; 
private static final int OUTER_MASK_COLOR = (191, 0, 0, 0); 
private static final int LINE_WIDTH_IN_DP = 1; 
private final float[] mMatrixValues = new float[9]; 
protected Matrix mSupportMatrix; 
protected ScaleGestureDetector mScaleGestureDetector; 
/* For drawing color field end */ 
protected Paint mPaint; 
/*
 * Height aspect ratio
 */ 
protected float mRatio = 1.0f; 
protected RectF mCropRect; 
//RectFPadding is to adapt to product needs, set padding for the crop box mCropRect -- chenglin April 18, 2016protected float RectFPadding = 0; 
protected int mLastX; 
protected int mLastY; 
protected OPERATION mOperation; 
private onBitmapLoadListener iBitmapLoading = null; 
private boolean mEnableDrawCropWidget = true; 
/* 
For scale and drag 
*/ 
private Matrix mBaseMatrix; 
private Matrix mDrawMatrix; 
private AccelerateDecelerateInterpolator sInterpolator = new AccelerateDecelerateInterpolator(); 
private Path mPath; 
private int mLineWidth; 
private float mScaleMax = 3.0f; 
private RectF mBoundaryRect; 
private int mRotation = 0; 
private int mImageWidth; 
private int mImageHeight; 
private int mDisplayW; 
private int mDisplayH; 
public SeniorCropImageView(Context context) { 
this(context, null); 
} 
public SeniorCropImageView(Context context, AttributeSet attrs) { 
this(context, attrs, 0); 
} 
public SeniorCropImageView(Context context, AttributeSet attrs, int defStyleAttr) { 
super(context, attrs, defStyleAttr); 
if (attrs != null) { 
TypedArray a = (attrs, .Life_CropImage); 
mRatio = (.Life_CropImage_life_Crop_ratio, 1.0f); 
(); 
} 
init(); 
} 
public static void decodeImageForCropping(final String path, final IDecodeCallback callback) { 
new Thread(new Runnable() { 
@Override 
public void run() { 
int rotation = 0; 
// Read the rotation in exiftry { 
ExifInterface exif = new ExifInterface(path); 
final int rotate = (ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED); 
switch (rotate) { 
case ExifInterface.ORIENTATION_ROTATE_90: 
rotation = 90; 
break; 
case ExifInterface.ORIENTATION_ROTATE_180: 
rotation = 180; 
break; 
case ExifInterface.ORIENTATION_ROTATE_270: 
rotation = 270; 
break; 
} 
} catch (IOException e) { 
(); 
} 
final  options = new (); 
 = true; 
(path, options); 
final int textureLimit = getMaxTextureSize(); 
int scale = 1; 
while ( / scale >= textureLimit) { 
scale *= 2; 
} 
while ( / scale >= textureLimit) { 
scale *= 2; 
} 
 = scale; 
 = false; 
Bitmap bitmap = null; 
try { 
bitmap = (path, options); 
} catch (OutOfMemoryError e) { 
(); 
} 
final Bitmap bimapDecoded = bitmap; 
if (bimapDecoded == null) { 
return; 
} 
if (callback != null) { 
(rotation, bimapDecoded); 
} 
} 
}).start(); 
} 
private static int getMaxTextureSize() { 
EGL10 egl = (EGL10) (); 
EGLDisplay display = (EGL10.EGL_DEFAULT_DISPLAY); 
// Initialise 
int[] version = new int[2]; 
(display, version); 
// Query total number of configurations 
int[] totalConfigurations = new int[1]; 
(display, null, 0, totalConfigurations); 
// Query actual list configurations 
EGLConfig[] configurationsList = new EGLConfig[totalConfigurations[0]]; 
(display, configurationsList, totalConfigurations[0], totalConfigurations); 
int[] textureSize = new int[1]; 
int maximumTextureSize = 0; 
// Iterate through all the configurations to located the maximum texture size 
for (int i = 0; i < totalConfigurations[0]; i++) { 
// Only need to check for width since opengl textures are always squared 
(display, configurationsList[i], EGL10.EGL_MAX_PBUFFER_WIDTH, textureSize); 
// Keep track of the maximum texture size 
if (maximumTextureSize < textureSize[0]) { 
maximumTextureSize = textureSize[0]; 
} 
} 
// Release 
(display); 
return maximumTextureSize; 
} 
@Override 
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { 
mDisplayW = right - left; 
mDisplayH = bottom - top; 
if (getDrawable() != null && ((BitmapDrawable) getDrawable()).getBitmap() != null) { 
calculateProperties(((BitmapDrawable) getDrawable()).getBitmap()); 
} 
} 
private void init() { 
mScaleGestureDetector = new ScaleGestureDetector(getContext(), this); 
mBaseMatrix = new Matrix(); 
mDrawMatrix = new Matrix(); 
mSupportMatrix = new Matrix(); 
mLineWidth = (int) dipToPixels(LINE_WIDTH_IN_DP); 
mPaint = new Paint(); 
// Indicates that the first solid line segment is long dashOnWidth, and the first dotted line segment is long dashOffWidthmPath = new Path(); 
mCropRect = new RectF(); 
mBoundaryRect = new RectF(); 
setScaleType(); 
setClickable(true); 
} 
private float dipToPixels(float dip) { 
return (TypedValue.COMPLEX_UNIT_DIP, dip, 
getResources().getDisplayMetrics()); 
} 
@Override 
protected void onAttachedToWindow() { 
(); 
addOnLayoutChangeListener(this); 
} 
@Override 
protected void onDetachedFromWindow() { 
(); 
removeOnLayoutChangeListener(this); 
} 
/**
 * Set the crop ratio of the picture, for example, 3:4 is 0.75
 *
 * @param ratio
 */ 
public void setCropRatio(final float ratio) { 
if (mRatio == ratio) { 
return; 
} 
mRatio = ratio; 
//After reselecting the rotation angle, restore the rotation angle//setImageRotation(0); 
if (getDrawable() == null) { 
return; 
} 
calculateProperties(((BitmapDrawable) getDrawable()).getBitmap()); 
postInvalidate(); 
} 
public void setImageRotation(int rotation) { 
if (mRotation == rotation) { 
return; 
} 
mRotation = rotation; 
if (getDrawable() == null) { 
return; 
} 
calculateProperties(((BitmapDrawable) getDrawable()).getBitmap()); 
postInvalidate(); 
} 
public void setCropRectPadding(float padding) { 
RectFPadding = padding; 
} 
public void setImagePath(final String path) { 
if ((path)) { 
return; 
} 
if (iBitmapLoading != null) { 
(); 
} 
decodeImageForCropping(path, new IDecodeCallback() { 
@Override 
public void onDecoded(final int rotation, final Bitmap bitmap) { 
post(new Runnable() { 
@Override 
public void run() { 
mRotation = rotation; 
setImageBitmap(bitmap); 
if (iBitmapLoading != null) { 
(); 
} 
} 
}); 
} 
}); 
} 
@Override 
public void setImageBitmap(Bitmap bm) { 
calculateProperties(bm); 
(bm); 
} 
public void setBitmapLoadingListener(onBitmapLoadListener iBitmapLoad) { 
iBitmapLoading = iBitmapLoad; 
} 
protected void calculateProperties(Bitmap bm) { 
(); 
(); 
int widthSize = mDisplayW; 
int heightSize = mDisplayH; 
generateCropRect(widthSize, heightSize); 
mImageWidth = (); 
mImageHeight = (); 
final boolean rotated = isImageRotated(); 
final int bitmapWidth = rotated ? mImageHeight : mImageWidth; 
final int bitmapHeight = rotated ? mImageWidth : mImageHeight; 
(0, 0, bitmapWidth, bitmapHeight); 
final float widthScale = () / bitmapWidth; 
final float heightScale = () / bitmapHeight; 
final float scale = (widthScale, heightScale); 
final float scaledHeight = scale * bitmapHeight; 
final float scaledWidth = scale * bitmapWidth; 
// Move to the center pointfinal int translateX = (int) ( + () / 2 - scaledWidth / 2); 
final int translateY = (int) ( + () / 2 - scaledHeight / 2); 
(scale, scale); 
(translateX, translateY); 
(mBoundaryRect); 
setImageMatrix(getDrawMatrix()); 
} 
private boolean isImageRotated() { 
return ((mRotation % 360) == 90) || ((mRotation % 360) == 270); 
} 
private void generateCropRect(int boundaryWidth, int boundaryHeight) { 
//RectFPadding is to adapt to product needs, set padding for the crop box mCropRect -- chenglin April 18, 2016boundaryWidth = boundaryWidth - (int)(RectFPadding * 2); 
boundaryHeight = boundaryHeight - (int)(RectFPadding * 2); 
int left; 
int top; 
int right; 
int bottom; 
boolean vertical; 
// If width/height is greater than the proportion, it means that the cutting frame is "vertical"vertical = (float) boundaryWidth / boundaryHeight > mRatio; 
final int rectH = (int) (boundaryWidth / mRatio); 
final int rectW = (int) (boundaryHeight * mRatio); 
if (vertical) { 
left = (boundaryWidth - rectW) / 2; 
top = 0; 
right = (boundaryWidth + rectW) / 2; 
bottom = boundaryHeight; 
} else { 
left = 0; 
top = (boundaryHeight - rectH) / 2; 
right = boundaryWidth; 
bottom = (boundaryHeight + rectH) / 2; 
} 
//RectFPadding is to adapt to product needs, set padding for the crop box mCropRect -- chenglin April 18, 2016(left + RectFPadding, top + RectFPadding, right + RectFPadding, bottom + RectFPadding); 
} 
@Override 
protected void onDraw(Canvas canvas) { 
(canvas); 
if (!mEnableDrawCropWidget) { 
return; 
} 
if (getDrawable() == null) { 
return; 
} 
(); 
(true); 
(LINE_COLOR); 
(mLineWidth); 
(); 
(); 
// superior(, ); 
(, ); 
// Left(, ); 
(, ); 
// Right(, ); 
(, ); 
// Down(, ); 
(, ); 
(mPath, mPaint); 
// Draw the external shadow part(); 
(true); 
(("#B3333333")); 
(); 
//The four rectangles below are decorative, which are the four shadows around the cropped framefinal int lineOffset = mLineWidth; 
if ( > 0) { 
(0, 0, getMeasuredWidth(),  - lineOffset, mPaint); 
} 
if ( > 0) { 
( - lineOffset - RectFPadding, RectFPadding - lineOffset,  - lineOffset,  + lineOffset, mPaint); 
} 
if ( < getMeasuredWidth()) { 
( + lineOffset,  - lineOffset, getMeasuredWidth(),  + lineOffset, mPaint); 
} 
if ( < getMeasuredHeight()) { 
(0,  + lineOffset, getMeasuredWidth(), getMeasuredHeight(), mPaint); 
} 
} 
public boolean onTouchEvent(MotionEvent ev) { 
if (() > 1) { 
mOperation = ; 
return (ev); 
} 
final int action = (); 
final int x = (int) (); 
final int y = (int) (); 
switch (action) { 
case MotionEvent.ACTION_DOWN: 
mOperation = ; 
mLastX = x; 
mLastY = y; 
break; 
case MotionEvent.ACTION_MOVE: 
if (mOperation == ) { 
int deltaX = x - mLastX; 
int deltaY = y - mLastY; 
RectF boundary = getDrawBoundary(getDrawMatrix()); 
if ( + deltaX > ) { 
deltaX = (int) ( - ); 
} else if ( + deltaX < ) { 
deltaX = (int) ( - ); 
} 
if ( + deltaY > ) { 
deltaY = (int) ( - ); 
} else if ( + deltaY < ) { 
deltaY = (int) ( - ); 
} 
(deltaX, deltaY); 
setImageMatrix(getDrawMatrix()); 
mLastX = x; 
mLastY = y; 
} 
break; 
case MotionEvent.ACTION_CANCEL: 
case MotionEvent.ACTION_POINTER_UP: 
case MotionEvent.ACTION_UP: 
mLastX = 0; 
mLastY = 0; 
mOperation = null; 
break; 
} 
return (ev); 
} 
public Bitmap getOriginBitmap() { 
BitmapDrawable drawable = (BitmapDrawable) getDrawable(); 
return drawable == null ? null : (); 
} 
/**
 * Save the image as bitmap
 */ 
public Bitmap saveCrop() throws OutOfMemoryError { 
if (getDrawable() == null) { 
return null; 
} 
Bitmap origin = getOriginBitmap(); 
Matrix drawMatrix = getDrawMatrix(); 
// Invert the matrixMatrix inverse = new Matrix(); 
(inverse); 
// Correlate the cropping frame to the original imageRectF cropMapped = new RectF(); 
(cropMapped, mCropRect); 
clampCropRect(cropMapped, (), ()); 
// If a rotation is generated, a rotation matrix is ​​requiredMatrix rotationM = new Matrix(); 
if (mRotation % 360 != 0) { 
(mRotation, () / 2, () / 2); 
} 
Bitmap cropped = ( 
origin, (int) , (int) , (int) (), (int) (), rotationM, true 
); 
return cropped; 
} 
private void clampCropRect(RectF cropRect, int borderW, int borderH) { 
if ( < 0) { 
 = 0; 
} 
if ( < 0) { 
 = 0; 
} 
if ( > borderW) { 
 = borderW; 
} 
if ( > borderH) { 
 = borderH; 
} 
} 
@Override 
public boolean onScale(ScaleGestureDetector detector) { 
float scale = (); 
if (scale == 1.0f) { 
return true; 
} 
final float currentScale = getScale(mSupportMatrix); 
final float centerX = (); 
final float centerY = (); 
if ((currentScale <= 1.0f && scale < 1.0f) 
|| (currentScale >= mScaleMax && scale > 1.0f)) { 
return true; 
} 
if (currentScale * scale < 1.0f) { 
scale = 1.0f / currentScale; 
} else if (currentScale * scale > mScaleMax) { 
scale = mScaleMax / currentScale; 
} 
(scale, scale, centerX, centerY); 
RectF boundary = getDrawBoundary(getDrawMatrix()); 
float translateX = 0; 
if ( > ) { 
translateX =  - ; 
} else if ( < ) { 
translateX =  - ; 
} 
("scale", "x==>" + translateX); 
float translateY = 0; 
if ( > ) { 
translateY =  - ; 
} else if ( < ) { 
translateY =  - ; 
} 
(translateX, translateY); 
setImageMatrix(getDrawMatrix()); 
return true; 
} 
protected Matrix getDrawMatrix() { 
(); 
if (mRotation % 360 != 0) { 
final boolean rotated = isImageRotated(); 
final int width = rotated ? mImageHeight : mImageWidth; 
final int height = rotated ? mImageWidth : mImageHeight; 
(mRotation, mImageWidth / 2, mImageHeight / 2); 
if (rotated) { 
final int translateX = (width - mImageWidth) / 2; 
final int translateY = (height - mImageHeight) / 2; 
(translateX, translateY); 
} 
} 
(mBaseMatrix); 
(mSupportMatrix); 
return mDrawMatrix; 
} 
@Override 
public boolean onScaleBegin(ScaleGestureDetector detector) { 
return true; 
} 
@Override 
public void onScaleEnd(ScaleGestureDetector detector) { 
final float currentScale = getScale(mSupportMatrix); 
if (currentScale < 1.0f) { 
("onScaleEnd", "currentScale==>" + currentScale); 
RectF boundary = getDrawBoundary(getDrawMatrix()); 
post(new AnimatedZoomRunnable(currentScale, 1.0f, (), ())); 
} 
} 
protected RectF getDrawBoundary(Matrix matrix) { 
Drawable drawable = getDrawable(); 
if (drawable == null) { 
return mBoundaryRect; 
} 
final int bitmapWidth = (); 
final int bitmapHeight = (); 
(0, 0, bitmapWidth, bitmapHeight); 
(mBoundaryRect); 
return mBoundaryRect; 
} 
public float getScale(Matrix matrix) { 
return (float) ((float) (getValue(matrix, Matrix.MSCALE_X), 2) + (float) (getValue(matrix, Matrix.MSKEW_Y), 2)); 
} 
/** 
* Helper method that 'unpacks' a Matrix and returns the required value 
* 
* @param matrix - Matrix to unpack 
* @param whichValue - Which value from * to return 
* @return float - returned value 
*/ 
private float getValue(Matrix matrix, int whichValue) { 
(mMatrixValues); 
return mMatrixValues[whichValue]; 
} 
public void enableDrawCropWidget(boolean enable) { 
mEnableDrawCropWidget = enable; 
} 
protected enum OPERATION { 
DRAG, SCALE 
} 
public enum Type { 
CENTER_CROP, CENTER_INSIDE 
} 
public interface IDecodeCallback { 
void onDecoded(final int rotation, final Bitmap bitmap); 
} 
//The setImagePath method takes time and needs to display the progress bar, which is the monitorpublic interface onBitmapLoadListener { 
void onLoadPrepare(); 
void onLoadFinish(); 
} 
private class AnimatedZoomRunnable implements Runnable { 
private final float mFocalX, mFocalY; 
private final long mStartTime; 
private final float mZoomStart, mZoomEnd; 
public AnimatedZoomRunnable(final float currentZoom, final float targetZoom, 
final float focalX, final float focalY) { 
mFocalX = focalX; 
mFocalY = focalY; 
mStartTime = (); 
mZoomStart = currentZoom; 
mZoomEnd = targetZoom; 
} 
@Override 
public void run() { 
float t = interpolate(); 
float scale = mZoomStart + t * (mZoomEnd - mZoomStart); 
float deltaScale = scale / getScale(mSupportMatrix); 
(deltaScale, deltaScale, mFocalX, mFocalY); 
setImageMatrix(getDrawMatrix()); 
// We haven't hit our target scale yet, so post ourselves again 
if (t < 1f) { 
postOnAnimation(this); 
} 
} 
private float interpolate() { 
float t = 1f * (() - mStartTime) / 200; 
t = (1f, t); 
t = (t); 
return t; 
} 
} 
} 
<declare-styleable name="Life_CropImage"> 
<attr name="life_Crop_ratio" format="float" /> 
<attr name="life_Crop_scale_type" format="enum"> 
<enum name="life_center_crop" value="0" /> 
<enum name="life_center_inside" value="1" /> 
</attr> 
</declare-styleable>

1. Let this crop box display the picture:

(path);

2. Save the cropped pictures:

Bitmap imageViewBitmap = null; 
try { 
imageViewBitmap = (); 
} catch (OutOfMemoryError e) { 
imageViewBitmap = (); 
(mActivity, .life_image_crop_topbar_crop_error, Toast.LENGTH_LONG).show(); 
}

3. Set the crop ratio:

(3f / 4f);

4. Set the padding of the cropping box:

(0f);

5. The setImagePath method is time-consuming and requires displaying the progress bar. This is listening:

(new () { 
@Override 
public void onLoadPrepare() { 
(); 
} 
@Override 
public void onLoadFinish() { 
(); 
} 
});

The above is the Android code that I bring to you. I hope it will be helpful to you.