This post describes how to crop and rotate image.
Create your layout in activity_main.xml.
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="PICK" android:layout_margin="4dp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <ImageView android:id="@+id/imageView1" android:layout_width="match_parent" android:layout_height="0dp" android:scaleType="fitCenter" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/button1" tools:srcCompat="@tools:sample/avatars" /> <Button android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="ROTATE" android:layout_margin="4dp" app:layout_constraintStart_toEndOf="@id/button1" app:layout_constraintTop_toTopOf="parent" /> <Button android:id="@+id/button3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="CROP" android:layout_margin="4dp" app:layout_constraintStart_toEndOf="@id/button2" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
In MainActivity.java use following codes.
package com.example.mathfragments; import androidx.activity.result.ActivityResultCallback; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.FileProvider; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; import android.content.ContentResolver; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.ImageView; import java.io.File; import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; public class MainActivity extends AppCompatActivity { ImageView imageview1; String image_uri = ""; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button button1 = findViewById(R.id.button1); Button button2 = findViewById(R.id.button2); Button button3 = findViewById(R.id.button3); imageview1 = findViewById(R.id.imageView1); button1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mGetContent.launch("image/*"); } }); button2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { try { Bitmap showBitmap = getBitmapFromCache(); Bitmap rotatedImage = rotateBitmap(showBitmap); saveBitmapToCache(rotatedImage); imageview1.setImageBitmap(getBitmapFromCache()); } catch (IOException e){ Log.e("tag", e.toString()); } } }); button3.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(MainActivity.this, CropActivity.class); startActivity(intent); } }); } ActivityResultLauncher<String> mGetContent = registerForActivityResult(new ActivityResultContracts.GetContent(), new ActivityResultCallback<Uri>() { @Override public void onActivityResult(Uri uri) { image_uri = uri.toString(); try { Bitmap showBitmap = getBitmapFromUri(uri); saveBitmapToCache(showBitmap); imageview1.setImageBitmap(showBitmap); } catch (IOException e){ Log.e("tag", e.toString()); } } }); @Override protected void onStart() { super.onStart(); if (!image_uri.equals("")){ imageview1.setImageBitmap(getBitmapFromCache()); } } private Bitmap getBitmapFromUri(Uri uri) throws IOException { ParcelFileDescriptor parcelFileDescriptor = getContentResolver().openFileDescriptor(uri, "r"); FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor(); Bitmap image = BitmapFactory.decodeFileDescriptor(fileDescriptor); parcelFileDescriptor.close(); return image; } public void saveBitmapToCache(Bitmap bitmap) throws IOException { String filename = "final_image.jpg"; File cacheFile = new File(getApplicationContext().getCacheDir(), filename); OutputStream out = new FileOutputStream(cacheFile); bitmap.compress(Bitmap.CompressFormat.JPEG, (int)100, out); out.flush(); out.close(); } public Bitmap getBitmapFromCache(){ File cacheFile = new File(getApplicationContext().getCacheDir(), "final_image.jpg"); Bitmap myBitmap = BitmapFactory.decodeFile(cacheFile.getAbsolutePath()); return myBitmap; } public Bitmap rotateBitmap(Bitmap bitmap){ android.graphics.Matrix matrix = new android.graphics.Matrix(); matrix.postScale((float)1, (float)1); matrix.postRotate(90); Bitmap bitmap2 = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); return bitmap2; } }
Create new java file CropUtils.java.
package com.example.mathfragments; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.PointF; import android.graphics.RectF; import android.net.Uri; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import com.bumptech.glide.Glide; public class CropUtils { public static class CropImageView extends androidx.appcompat.widget.AppCompatImageView { @SuppressWarnings("unused") private static final String TAG = CropImageView.class.getName(); @SuppressWarnings("unused") public static final int GUIDELINES_OFF = 0; public static final int GUIDELINES_ON_TOUCH = 1; public static final int GUIDELINES_ON = 2; private Paint mBorderPaint; private Paint mGuidelinePaint; private Paint mCornerPaint; private Paint mSurroundingAreaOverlayPaint; private float mHandleRadius; private float mSnapRadius; private float mCornerThickness; private float mBorderThickness; private float mCornerLength; private RectF mBitmapRect = new RectF(); private final PointF mTouchOffset = new PointF(); private Handle mPressedHandle; private boolean mFixAspectRatio; private int mAspectRatioX = 1; private int mAspectRatioY = 1; private int mGuidelinesMode = 1; public CropImageView(Context context) { super(context); init(context, null); } public CropImageView(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } public CropImageView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs); } private void init(Context context, AttributeSet attrs) { //final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CropImageView, 0, 0); mGuidelinesMode = 1; mFixAspectRatio = false; mAspectRatioX = 1; mAspectRatioY = 1; final android.content.res.Resources resources = context.getResources(); mBorderPaint = PaintUtil.newBorderPaint(resources); mGuidelinePaint = PaintUtil.newGuidelinePaint(resources); mSurroundingAreaOverlayPaint = PaintUtil.newSurroundingAreaOverlayPaint(resources); mCornerPaint = PaintUtil.newCornerPaint(resources); mHandleRadius = 24; mSnapRadius = 3; mBorderThickness = 3; mCornerThickness = 5; mCornerLength = 20; } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); mBitmapRect = getBitmapRect(); initCropWindow(mBitmapRect); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); drawDarkenedSurroundingArea(canvas); drawGuidelines(canvas); drawBorder(canvas); drawCorners(canvas); } @Override public boolean onTouchEvent(MotionEvent event) { if (!isEnabled()) { return false; } switch (event.getAction()) { case MotionEvent.ACTION_DOWN: onActionDown(event.getX(), event.getY()); return true; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: getParent().requestDisallowInterceptTouchEvent(false); onActionUp(); return true; case MotionEvent.ACTION_MOVE: onActionMove(event.getX(), event.getY()); getParent().requestDisallowInterceptTouchEvent(true); return true; default: return false; } } public void setGuidelines(int guidelinesMode) { mGuidelinesMode = guidelinesMode; invalidate(); } public void setFixedAspectRatio(boolean fixAspectRatio) { mFixAspectRatio = fixAspectRatio; requestLayout(); } public void setAspectRatio(int aspectRatioX, int aspectRatioY) { if (aspectRatioX <= 0 || aspectRatioY <= 0) { throw new IllegalArgumentException("Cannot set aspect ratio value to a number less than or equal to 0."); } mAspectRatioX = aspectRatioX; mAspectRatioY = aspectRatioY; if (mFixAspectRatio) { requestLayout(); } } public Bitmap getCroppedImage() { final android.graphics.drawable.Drawable drawable = getDrawable(); if (drawable == null || !(drawable instanceof android.graphics.drawable.BitmapDrawable)) { return null; } final float[] matrixValues = new float[9]; getImageMatrix().getValues(matrixValues); final float scaleX = matrixValues[Matrix.MSCALE_X]; final float scaleY = matrixValues[Matrix.MSCALE_Y]; final float transX = matrixValues[Matrix.MTRANS_X]; final float transY = matrixValues[Matrix.MTRANS_Y]; final float bitmapLeft = (transX < 0) ? Math.abs(transX) : 0; final float bitmapTop = (transY < 0) ? Math.abs(transY) : 0; final Bitmap originalBitmap = ((android.graphics.drawable.BitmapDrawable) drawable).getBitmap(); final float cropX = (bitmapLeft + Edge.LEFT.getCoordinate()) / scaleX; final float cropY = (bitmapTop + Edge.TOP.getCoordinate()) / scaleY; final float cropWidth = Math.min(Edge.getWidth() / scaleX, originalBitmap.getWidth() - cropX); final float cropHeight = Math.min(Edge.getHeight() / scaleY, originalBitmap.getHeight() - cropY); return Bitmap.createBitmap(originalBitmap, (int) cropX, (int) cropY, (int) cropWidth, (int) cropHeight); } private RectF getBitmapRect() { final android.graphics.drawable.Drawable drawable = getDrawable(); if (drawable == null) { return new RectF(); } final float[] matrixValues = new float[9]; getImageMatrix().getValues(matrixValues); final float scaleX = matrixValues[Matrix.MSCALE_X]; final float scaleY = matrixValues[Matrix.MSCALE_Y]; final float transX = matrixValues[Matrix.MTRANS_X]; final float transY = matrixValues[Matrix.MTRANS_Y]; final int drawableIntrinsicWidth = drawable.getIntrinsicWidth(); final int drawableIntrinsicHeight = drawable.getIntrinsicHeight(); final int drawableDisplayWidth = Math.round(drawableIntrinsicWidth * scaleX); final int drawableDisplayHeight = Math.round(drawableIntrinsicHeight * scaleY); final float left = Math.max(transX, 0); final float top = Math.max(transY, 0); final float right = Math.min(left + drawableDisplayWidth, getWidth()); final float bottom = Math.min(top + drawableDisplayHeight, getHeight()); return new RectF(left, top, right, bottom); } private void initCropWindow(RectF bitmapRect) { if (mFixAspectRatio) { initCropWindowWithFixedAspectRatio(bitmapRect); } else { final float horizontalPadding = 0.1f * bitmapRect.width(); final float verticalPadding = 0.1f * bitmapRect.height(); Edge.LEFT.setCoordinate(bitmapRect.left + horizontalPadding); Edge.TOP.setCoordinate(bitmapRect.top + verticalPadding); Edge.RIGHT.setCoordinate(bitmapRect.right - horizontalPadding); Edge.BOTTOM.setCoordinate(bitmapRect.bottom - verticalPadding); } } private void initCropWindowWithFixedAspectRatio(RectF bitmapRect) { if (AspectRatioUtil.calculateAspectRatio(bitmapRect) > getTargetAspectRatio()) { final float cropWidth = AspectRatioUtil.calculateWidth(bitmapRect.height(), getTargetAspectRatio()); Edge.LEFT.setCoordinate(bitmapRect.centerX() - cropWidth / 2f); Edge.TOP.setCoordinate(bitmapRect.top); Edge.RIGHT.setCoordinate(bitmapRect.centerX() + cropWidth / 2f); Edge.BOTTOM.setCoordinate(bitmapRect.bottom); } else { final float cropHeight = AspectRatioUtil.calculateHeight(bitmapRect.width(), getTargetAspectRatio()); Edge.LEFT.setCoordinate(bitmapRect.left); Edge.TOP.setCoordinate(bitmapRect.centerY() - cropHeight / 2f); Edge.RIGHT.setCoordinate(bitmapRect.right); Edge.BOTTOM.setCoordinate(bitmapRect.centerY() + cropHeight / 2f); } } private void drawDarkenedSurroundingArea(Canvas canvas) { final RectF bitmapRect = mBitmapRect; final float left = Edge.LEFT.getCoordinate(); final float top = Edge.TOP.getCoordinate(); final float right = Edge.RIGHT.getCoordinate(); final float bottom = Edge.BOTTOM.getCoordinate(); canvas.drawRect(bitmapRect.left, bitmapRect.top, bitmapRect.right, top, mSurroundingAreaOverlayPaint); canvas.drawRect(bitmapRect.left, bottom, bitmapRect.right, bitmapRect.bottom, mSurroundingAreaOverlayPaint); canvas.drawRect(bitmapRect.left, top, left, bottom, mSurroundingAreaOverlayPaint); canvas.drawRect(right, top, bitmapRect.right, bottom, mSurroundingAreaOverlayPaint); } private void drawGuidelines(Canvas canvas) { if (!shouldGuidelinesBeShown()) { return; } final float left = Edge.LEFT.getCoordinate(); final float top = Edge.TOP.getCoordinate(); final float right = Edge.RIGHT.getCoordinate(); final float bottom = Edge.BOTTOM.getCoordinate(); final float oneThirdCropWidth = Edge.getWidth() / 3; final float x1 = left + oneThirdCropWidth; canvas.drawLine(x1, top, x1, bottom, mGuidelinePaint); final float x2 = right - oneThirdCropWidth; canvas.drawLine(x2, top, x2, bottom, mGuidelinePaint); final float oneThirdCropHeight = Edge.getHeight() / 3; final float y1 = top + oneThirdCropHeight; canvas.drawLine(left, y1, right, y1, mGuidelinePaint); final float y2 = bottom - oneThirdCropHeight; canvas.drawLine(left, y2, right, y2, mGuidelinePaint); } private void drawBorder(Canvas canvas) { canvas.drawRect(Edge.LEFT.getCoordinate(), Edge.TOP.getCoordinate(), Edge.RIGHT.getCoordinate(), Edge.BOTTOM.getCoordinate(), mBorderPaint); } private void drawCorners(Canvas canvas) { final float left = Edge.LEFT.getCoordinate(); final float top = Edge.TOP.getCoordinate(); final float right = Edge.RIGHT.getCoordinate(); final float bottom = Edge.BOTTOM.getCoordinate(); final float lateralOffset = (mCornerThickness - mBorderThickness) / 2f; final float startOffset = mCornerThickness - (mBorderThickness / 2f); canvas.drawLine(left - lateralOffset, top - startOffset, left - lateralOffset, top + mCornerLength, mCornerPaint); canvas.drawLine(left - startOffset, top - lateralOffset, left + mCornerLength, top - lateralOffset, mCornerPaint); canvas.drawLine(right + lateralOffset, top - startOffset, right + lateralOffset, top + mCornerLength, mCornerPaint); canvas.drawLine(right + startOffset, top - lateralOffset, right - mCornerLength, top - lateralOffset, mCornerPaint); canvas.drawLine(left - lateralOffset, bottom + startOffset, left - lateralOffset, bottom - mCornerLength, mCornerPaint); canvas.drawLine(left - startOffset, bottom + lateralOffset, left + mCornerLength, bottom + lateralOffset, mCornerPaint); canvas.drawLine(right + lateralOffset, bottom + startOffset, right + lateralOffset, bottom - mCornerLength, mCornerPaint); canvas.drawLine(right + startOffset, bottom + lateralOffset, right - mCornerLength, bottom + lateralOffset, mCornerPaint); } private boolean shouldGuidelinesBeShown() { return ((mGuidelinesMode == GUIDELINES_ON) || ((mGuidelinesMode == GUIDELINES_ON_TOUCH) && (mPressedHandle != null))); } private float getTargetAspectRatio() { return mAspectRatioX / (float) mAspectRatioY; } private void onActionDown(float x, float y) { final float left = Edge.LEFT.getCoordinate(); final float top = Edge.TOP.getCoordinate(); final float right = Edge.RIGHT.getCoordinate(); final float bottom = Edge.BOTTOM.getCoordinate(); mPressedHandle = HandleUtil.getPressedHandle(x, y, left, top, right, bottom, mHandleRadius); if (mPressedHandle != null) { HandleUtil.getOffset(mPressedHandle, x, y, left, top, right, bottom, mTouchOffset); invalidate(); } } private void onActionUp() { if (mPressedHandle != null) { mPressedHandle = null; invalidate(); } } private void onActionMove(float x, float y) { if (mPressedHandle == null) { return; } x += mTouchOffset.x; y += mTouchOffset.y; if (mFixAspectRatio) { mPressedHandle.updateCropWindow(x, y, getTargetAspectRatio(), mBitmapRect, mSnapRadius); } else { mPressedHandle.updateCropWindow(x, y, mBitmapRect, mSnapRadius); } invalidate(); } } public static class PaintUtil { public static Paint newBorderPaint(android.content.res.Resources resources) { final Paint paint = new Paint(); paint.setStyle(Paint.Style.STROKE); paint.setStrokeWidth(3); paint.setColor(Color.parseColor("#AAFFFFFF")); return paint; } public static Paint newGuidelinePaint(android.content.res.Resources resources) { final Paint paint = new Paint(); paint.setStyle(Paint.Style.STROKE); paint.setStrokeWidth(1); paint.setColor(Color.parseColor("#AAFFFFFF")); return paint; } public static Paint newSurroundingAreaOverlayPaint(android.content.res.Resources resources) { final Paint paint = new Paint(); paint.setStyle(Paint.Style.FILL); paint.setColor(Color.parseColor("#B0000000")); return paint; } public static Paint newCornerPaint(android.content.res.Resources resources) { final Paint paint = new Paint(); paint.setStyle(Paint.Style.STROKE); paint.setStrokeWidth(5); paint.setColor(Color.parseColor("#FFFFFF")); return paint; } } public static class MathUtil { public static float calculateDistance(float x1, float y1, float x2, float y2) { final float side1 = x2 - x1; final float side2 = y2 - y1; return (float) Math.sqrt(side1 * side1 + side2 * side2); } } public static class HandleUtil { public static Handle getPressedHandle(float x, float y, float left, float top, float right, float bottom, float targetRadius) { Handle closestHandle = null; float closestDistance = Float.POSITIVE_INFINITY; final float distanceToTopLeft = MathUtil.calculateDistance(x, y, left, top); if (distanceToTopLeft < closestDistance) { closestDistance = distanceToTopLeft; closestHandle = Handle.TOP_LEFT; } final float distanceToTopRight = MathUtil.calculateDistance(x, y, right, top); if (distanceToTopRight < closestDistance) { closestDistance = distanceToTopRight; closestHandle = Handle.TOP_RIGHT; } final float distanceToBottomLeft = MathUtil.calculateDistance(x, y, left, bottom); if (distanceToBottomLeft < closestDistance) { closestDistance = distanceToBottomLeft; closestHandle = Handle.BOTTOM_LEFT; } final float distanceToBottomRight = MathUtil.calculateDistance(x, y, right, bottom); if (distanceToBottomRight < closestDistance) { closestDistance = distanceToBottomRight; closestHandle = Handle.BOTTOM_RIGHT; } if (closestDistance <= targetRadius) { return closestHandle; } if (HandleUtil.isInHorizontalTargetZone(x, y, left, right, top, targetRadius)) { return Handle.TOP; } else if (HandleUtil.isInHorizontalTargetZone(x, y, left, right, bottom, targetRadius)) { return Handle.BOTTOM; } else if (HandleUtil.isInVerticalTargetZone(x, y, left, top, bottom, targetRadius)) { return Handle.LEFT; } else if (HandleUtil.isInVerticalTargetZone(x, y, right, top, bottom, targetRadius)) { return Handle.RIGHT; } if (isWithinBounds(x, y, left, top, right, bottom)) { return Handle.CENTER; } return null; } public static void getOffset(Handle handle, float x, float y, float left, float top, float right, float bottom, PointF touchOffsetOutput) { float touchOffsetX = 0; float touchOffsetY = 0; switch (handle) { case TOP_LEFT: touchOffsetX = left - x; touchOffsetY = top - y; break; case TOP_RIGHT: touchOffsetX = right - x; touchOffsetY = top - y; break; case BOTTOM_LEFT: touchOffsetX = left - x; touchOffsetY = bottom - y; break; case BOTTOM_RIGHT: touchOffsetX = right - x; touchOffsetY = bottom - y; break; case LEFT: touchOffsetX = left - x; touchOffsetY = 0; break; case TOP: touchOffsetX = 0; touchOffsetY = top - y; break; case RIGHT: touchOffsetX = right - x; touchOffsetY = 0; break; case BOTTOM: touchOffsetX = 0; touchOffsetY = bottom - y; break; case CENTER: final float centerX = (right + left) / 2; final float centerY = (top + bottom) / 2; touchOffsetX = centerX - x; touchOffsetY = centerY - y; break; } touchOffsetOutput.x = touchOffsetX; touchOffsetOutput.y = touchOffsetY; } private static boolean isInHorizontalTargetZone(float x, float y, float handleXStart, float handleXEnd, float handleY, float targetRadius) { return (x > handleXStart && x < handleXEnd && Math.abs(y - handleY) <= targetRadius); } private static boolean isInVerticalTargetZone(float x, float y, float handleX, float handleYStart, float handleYEnd, float targetRadius) { return (Math.abs(x - handleX) <= targetRadius && y > handleYStart && y < handleYEnd); } private static boolean isWithinBounds(float x, float y, float left, float top, float right, float bottom) { return x >= left && x <= right && y >= top && y <= bottom; } } public static class AspectRatioUtil { public static float calculateAspectRatio(float left, float top, float right, float bottom) { final float width = right - left; final float height = bottom - top; return width / height; } public static float calculateAspectRatio(RectF rect) { return rect.width() / rect.height(); } public static float calculateLeft(float top, float right, float bottom, float targetAspectRatio) { final float height = bottom - top; return right - (targetAspectRatio * height); } public static float calculateTop(float left, float right, float bottom, float targetAspectRatio) { final float width = right - left; return bottom - (width / targetAspectRatio); } public static float calculateRight(float left, float top, float bottom, float targetAspectRatio) { final float height = bottom - top; return (targetAspectRatio * height) + left; } public static float calculateBottom(float left, float top, float right, float targetAspectRatio) { final float width = right - left; return (width / targetAspectRatio) + top; } public static float calculateWidth(float height, float targetAspectRatio) { return targetAspectRatio * height; } public static float calculateHeight(float width, float targetAspectRatio) { return width / targetAspectRatio; } } public enum Handle { TOP_LEFT(new CornerHandleHelper(Edge.TOP, Edge.LEFT)), TOP_RIGHT(new CornerHandleHelper(Edge.TOP, Edge.RIGHT)), BOTTOM_LEFT(new CornerHandleHelper(Edge.BOTTOM, Edge.LEFT)), BOTTOM_RIGHT(new CornerHandleHelper(Edge.BOTTOM, Edge.RIGHT)), LEFT(new VerticalHandleHelper(Edge.LEFT)), TOP(new HorizontalHandleHelper(Edge.TOP)), RIGHT(new VerticalHandleHelper(Edge.RIGHT)), BOTTOM(new HorizontalHandleHelper(Edge.BOTTOM)), CENTER(new CenterHandleHelper()); private final HandleHelper mHelper; Handle(HandleHelper helper) { mHelper = helper; } public void updateCropWindow(float x, float y, RectF imageRect, float snapRadius) { mHelper.updateCropWindow(x, y, imageRect, snapRadius); } public void updateCropWindow(float x, float y, float targetAspectRatio, RectF imageRect, float snapRadius) { mHelper.updateCropWindow(x, y, targetAspectRatio, imageRect, snapRadius); } } static abstract class HandleHelper { private static final float UNFIXED_ASPECT_RATIO_CONSTANT = 1; private final Edge mHorizontalEdge; private final Edge mVerticalEdge; private final EdgePair mActiveEdges; HandleHelper(Edge horizontalEdge, Edge verticalEdge) { mHorizontalEdge = horizontalEdge; mVerticalEdge = verticalEdge; mActiveEdges = new EdgePair(mHorizontalEdge, mVerticalEdge); } void updateCropWindow(float x, float y, RectF imageRect, float snapRadius) { final EdgePair activeEdges = getActiveEdges(); final Edge primaryEdge = activeEdges.primary; final Edge secondaryEdge = activeEdges.secondary; if (primaryEdge != null) primaryEdge.adjustCoordinate(x, y, imageRect, snapRadius, UNFIXED_ASPECT_RATIO_CONSTANT); if (secondaryEdge != null) secondaryEdge.adjustCoordinate(x, y, imageRect, snapRadius, UNFIXED_ASPECT_RATIO_CONSTANT); } abstract void updateCropWindow(float x, float y, float targetAspectRatio, RectF imageRect, float snapRadius); EdgePair getActiveEdges() { return mActiveEdges; } EdgePair getActiveEdges(float x, float y, float targetAspectRatio) { final float potentialAspectRatio = getAspectRatio(x, y); if (potentialAspectRatio > targetAspectRatio) { mActiveEdges.primary = mVerticalEdge; mActiveEdges.secondary = mHorizontalEdge; } else { mActiveEdges.primary = mHorizontalEdge; mActiveEdges.secondary = mVerticalEdge; } return mActiveEdges; } private float getAspectRatio(float x, float y) { final float left = (mVerticalEdge == Edge.LEFT) ? x : Edge.LEFT.getCoordinate(); final float top = (mHorizontalEdge == Edge.TOP) ? y : Edge.TOP.getCoordinate(); final float right = (mVerticalEdge == Edge.RIGHT) ? x : Edge.RIGHT.getCoordinate(); final float bottom = (mHorizontalEdge == Edge.BOTTOM) ? y : Edge.BOTTOM.getCoordinate(); return AspectRatioUtil.calculateAspectRatio(left, top, right, bottom); } } static class HorizontalHandleHelper extends HandleHelper { private Edge mEdge; HorizontalHandleHelper(Edge edge) { super(edge, null); mEdge = edge; } @Override void updateCropWindow(float x, float y, float targetAspectRatio, RectF imageRect, float snapRadius) { mEdge.adjustCoordinate(x, y, imageRect, snapRadius, targetAspectRatio); float left = Edge.LEFT.getCoordinate(); float right = Edge.RIGHT.getCoordinate(); final float targetWidth = AspectRatioUtil.calculateWidth(Edge.getHeight(), targetAspectRatio); final float difference = targetWidth - Edge.getWidth(); final float halfDifference = difference / 2; left -= halfDifference; right += halfDifference; Edge.LEFT.setCoordinate(left); Edge.RIGHT.setCoordinate(right); if (Edge.LEFT.isOutsideMargin(imageRect, snapRadius) && !mEdge.isNewRectangleOutOfBounds(Edge.LEFT, imageRect, targetAspectRatio)) { final float offset = Edge.LEFT.snapToRect(imageRect); Edge.RIGHT.offset(-offset); mEdge.adjustCoordinate(targetAspectRatio); } if (Edge.RIGHT.isOutsideMargin(imageRect, snapRadius) && !mEdge.isNewRectangleOutOfBounds(Edge.RIGHT, imageRect, targetAspectRatio)) { final float offset = Edge.RIGHT.snapToRect(imageRect); Edge.LEFT.offset(-offset); mEdge.adjustCoordinate(targetAspectRatio); } } } static class VerticalHandleHelper extends HandleHelper { private Edge mEdge; VerticalHandleHelper(Edge edge) { super(null, edge); mEdge = edge; } @Override void updateCropWindow(float x, float y, float targetAspectRatio, RectF imageRect, float snapRadius) { mEdge.adjustCoordinate(x, y, imageRect, snapRadius, targetAspectRatio); float top = Edge.TOP.getCoordinate(); float bottom = Edge.BOTTOM.getCoordinate(); final float targetHeight = AspectRatioUtil.calculateHeight(Edge.getWidth(), targetAspectRatio); final float difference = targetHeight - Edge.getHeight(); final float halfDifference = difference / 2; top -= halfDifference; bottom += halfDifference; Edge.TOP.setCoordinate(top); Edge.BOTTOM.setCoordinate(bottom); if (Edge.TOP.isOutsideMargin(imageRect, snapRadius) && !mEdge.isNewRectangleOutOfBounds(Edge.TOP, imageRect, targetAspectRatio)) { final float offset = Edge.TOP.snapToRect(imageRect); Edge.BOTTOM.offset(-offset); mEdge.adjustCoordinate(targetAspectRatio); } if (Edge.BOTTOM.isOutsideMargin(imageRect, snapRadius) && !mEdge.isNewRectangleOutOfBounds(Edge.BOTTOM, imageRect, targetAspectRatio)) { final float offset = Edge.BOTTOM.snapToRect(imageRect); Edge.TOP.offset(-offset); mEdge.adjustCoordinate(targetAspectRatio); } } } static class CenterHandleHelper extends HandleHelper { CenterHandleHelper() { super(null, null); } @Override void updateCropWindow(float x, float y, RectF imageRect, float snapRadius) { float left = Edge.LEFT.getCoordinate(); float top = Edge.TOP.getCoordinate(); float right = Edge.RIGHT.getCoordinate(); float bottom = Edge.BOTTOM.getCoordinate(); final float currentCenterX = (left + right) / 2; final float currentCenterY = (top + bottom) / 2; final float offsetX = x - currentCenterX; final float offsetY = y - currentCenterY; Edge.LEFT.offset(offsetX); Edge.TOP.offset(offsetY); Edge.RIGHT.offset(offsetX); Edge.BOTTOM.offset(offsetY); if (Edge.LEFT.isOutsideMargin(imageRect, snapRadius)) { final float offset = Edge.LEFT.snapToRect(imageRect); Edge.RIGHT.offset(offset); } else if (Edge.RIGHT.isOutsideMargin(imageRect, snapRadius)) { final float offset = Edge.RIGHT.snapToRect(imageRect); Edge.LEFT.offset(offset); } if (Edge.TOP.isOutsideMargin(imageRect, snapRadius)) { final float offset = Edge.TOP.snapToRect(imageRect); Edge.BOTTOM.offset(offset); } else if (Edge.BOTTOM.isOutsideMargin(imageRect, snapRadius)) { final float offset = Edge.BOTTOM.snapToRect(imageRect); Edge.TOP.offset(offset); } } @Override void updateCropWindow(float x, float y, float targetAspectRatio, RectF imageRect, float snapRadius) { updateCropWindow(x, y, imageRect, snapRadius); } } static class CornerHandleHelper extends HandleHelper { CornerHandleHelper(Edge horizontalEdge, Edge verticalEdge) { super(horizontalEdge, verticalEdge); } @Override void updateCropWindow(float x, float y, float targetAspectRatio, RectF imageRect, float snapRadius) { final EdgePair activeEdges = getActiveEdges(x, y, targetAspectRatio); final Edge primaryEdge = activeEdges.primary; final Edge secondaryEdge = activeEdges.secondary; primaryEdge.adjustCoordinate(x, y, imageRect, snapRadius, targetAspectRatio); secondaryEdge.adjustCoordinate(targetAspectRatio); if (secondaryEdge.isOutsideMargin(imageRect, snapRadius)) { secondaryEdge.snapToRect(imageRect); primaryEdge.adjustCoordinate(targetAspectRatio); } } } public enum Edge { LEFT, TOP, RIGHT, BOTTOM; public static final int MIN_CROP_LENGTH_PX = 40; private float mCoordinate; public void setCoordinate(float coordinate) { mCoordinate = coordinate; } public void offset(float distance) { mCoordinate += distance; } public float getCoordinate() { return mCoordinate; } public void adjustCoordinate(float x, float y, RectF imageRect, float imageSnapRadius, float aspectRatio) { switch (this) { case LEFT: mCoordinate = adjustLeft(x, imageRect, imageSnapRadius, aspectRatio); break; case TOP: mCoordinate = adjustTop(y, imageRect, imageSnapRadius, aspectRatio); break; case RIGHT: mCoordinate = adjustRight(x, imageRect, imageSnapRadius, aspectRatio); break; case BOTTOM: mCoordinate = adjustBottom(y, imageRect, imageSnapRadius, aspectRatio); break; } } public void adjustCoordinate(float aspectRatio) { final float left = Edge.LEFT.getCoordinate(); final float top = Edge.TOP.getCoordinate(); final float right = Edge.RIGHT.getCoordinate(); final float bottom = Edge.BOTTOM.getCoordinate(); switch (this) { case LEFT: mCoordinate = AspectRatioUtil.calculateLeft(top, right, bottom, aspectRatio); break; case TOP: mCoordinate = AspectRatioUtil.calculateTop(left, right, bottom, aspectRatio); break; case RIGHT: mCoordinate = AspectRatioUtil.calculateRight(left, top, bottom, aspectRatio); break; case BOTTOM: mCoordinate = AspectRatioUtil.calculateBottom(left, top, right, aspectRatio); break; } } public boolean isNewRectangleOutOfBounds(Edge edge, RectF imageRect, float aspectRatio) { final float offset = edge.snapOffset(imageRect); switch (this) { case LEFT: if (edge.equals(Edge.TOP)) { final float top = imageRect.top; final float bottom = Edge.BOTTOM.getCoordinate() - offset; final float right = Edge.RIGHT.getCoordinate(); final float left = AspectRatioUtil.calculateLeft(top, right, bottom, aspectRatio); return isOutOfBounds(top, left, bottom, right, imageRect); } else if (edge.equals(Edge.BOTTOM)) { final float bottom = imageRect.bottom; final float top = Edge.TOP.getCoordinate() - offset; final float right = Edge.RIGHT.getCoordinate(); final float left = AspectRatioUtil.calculateLeft(top, right, bottom, aspectRatio); return isOutOfBounds(top, left, bottom, right, imageRect); } break; case TOP: if (edge.equals(Edge.LEFT)) { final float left = imageRect.left; final float right = Edge.RIGHT.getCoordinate() - offset; final float bottom = Edge.BOTTOM.getCoordinate(); final float top = AspectRatioUtil.calculateTop(left, right, bottom, aspectRatio); return isOutOfBounds(top, left, bottom, right, imageRect); } else if (edge.equals(Edge.RIGHT)) { final float right = imageRect.right; final float left = Edge.LEFT.getCoordinate() - offset; final float bottom = Edge.BOTTOM.getCoordinate(); final float top = AspectRatioUtil.calculateTop(left, right, bottom, aspectRatio); return isOutOfBounds(top, left, bottom, right, imageRect); } break; case RIGHT: if (edge.equals(Edge.TOP)) { final float top = imageRect.top; final float bottom = Edge.BOTTOM.getCoordinate() - offset; final float left = Edge.LEFT.getCoordinate(); final float right = AspectRatioUtil.calculateRight(left, top, bottom, aspectRatio); return isOutOfBounds(top, left, bottom, right, imageRect); } else if (edge.equals(Edge.BOTTOM)) { final float bottom = imageRect.bottom; final float top = Edge.TOP.getCoordinate() - offset; final float left = Edge.LEFT.getCoordinate(); final float right = AspectRatioUtil.calculateRight(left, top, bottom, aspectRatio); return isOutOfBounds(top, left, bottom, right, imageRect); } break; case BOTTOM: if (edge.equals(Edge.LEFT)) { final float left = imageRect.left; final float right = Edge.RIGHT.getCoordinate() - offset; final float top = Edge.TOP.getCoordinate(); final float bottom = AspectRatioUtil.calculateBottom(left, top, right, aspectRatio); return isOutOfBounds(top, left, bottom, right, imageRect); } else if (edge.equals(Edge.RIGHT)) { final float right = imageRect.right; final float left = Edge.LEFT.getCoordinate() - offset; final float top = Edge.TOP.getCoordinate(); final float bottom = AspectRatioUtil.calculateBottom(left, top, right, aspectRatio); return isOutOfBounds(top, left, bottom, right, imageRect); } break; } return true; } private boolean isOutOfBounds(float top, float left, float bottom, float right, RectF imageRect) { return (top < imageRect.top || left < imageRect.left || bottom > imageRect.bottom || right > imageRect.right); } public float snapToRect(RectF imageRect) { final float oldCoordinate = mCoordinate; switch (this) { case LEFT: mCoordinate = imageRect.left; break; case TOP: mCoordinate = imageRect.top; break; case RIGHT: mCoordinate = imageRect.right; break; case BOTTOM: mCoordinate = imageRect.bottom; break; } return mCoordinate - oldCoordinate; } public float snapOffset(RectF imageRect) { final float oldCoordinate = mCoordinate; final float newCoordinate; switch (this) { case LEFT: newCoordinate = imageRect.left; break; case TOP: newCoordinate = imageRect.top; break; case RIGHT: newCoordinate = imageRect.right; break; default: // BOTTOM newCoordinate = imageRect.bottom; break; } return newCoordinate - oldCoordinate; } public static float getWidth() { return Edge.RIGHT.getCoordinate() - Edge.LEFT.getCoordinate(); } public static float getHeight() { return Edge.BOTTOM.getCoordinate() - Edge.TOP.getCoordinate(); } public boolean isOutsideMargin(RectF rect, float margin) { final boolean result; switch (this) { case LEFT: result = mCoordinate - rect.left < margin; break; case TOP: result = mCoordinate - rect.top < margin; break; case RIGHT: result = rect.right - mCoordinate < margin; break; default: // BOTTOM result = rect.bottom - mCoordinate < margin; break; } return result; } private static float adjustLeft(float x, RectF imageRect, float imageSnapRadius, float aspectRatio) { final float resultX; if (x - imageRect.left < imageSnapRadius) { resultX = imageRect.left; } else { float resultXHoriz = Float.POSITIVE_INFINITY; float resultXVert = Float.POSITIVE_INFINITY; if (x >= Edge.RIGHT.getCoordinate() - MIN_CROP_LENGTH_PX) { resultXHoriz = Edge.RIGHT.getCoordinate() - MIN_CROP_LENGTH_PX; } if (((Edge.RIGHT.getCoordinate() - x) / aspectRatio) <= MIN_CROP_LENGTH_PX) { resultXVert = Edge.RIGHT.getCoordinate() - (MIN_CROP_LENGTH_PX * aspectRatio); } resultX = Math.min(x, Math.min(resultXHoriz, resultXVert)); } return resultX; } private static float adjustRight(float x, RectF imageRect, float imageSnapRadius, float aspectRatio) { final float resultX; if (imageRect.right - x < imageSnapRadius) { resultX = imageRect.right; } else { float resultXHoriz = Float.NEGATIVE_INFINITY; float resultXVert = Float.NEGATIVE_INFINITY; if (x <= Edge.LEFT.getCoordinate() + MIN_CROP_LENGTH_PX) { resultXHoriz = Edge.LEFT.getCoordinate() + MIN_CROP_LENGTH_PX; } if (((x - Edge.LEFT.getCoordinate()) / aspectRatio) <= MIN_CROP_LENGTH_PX) { resultXVert = Edge.LEFT.getCoordinate() + (MIN_CROP_LENGTH_PX * aspectRatio); } resultX = Math.max(x, Math.max(resultXHoriz, resultXVert)); } return resultX; } private static float adjustTop(float y, RectF imageRect, float imageSnapRadius, float aspectRatio) { final float resultY; if (y - imageRect.top < imageSnapRadius) { resultY = imageRect.top; } else { float resultYVert = Float.POSITIVE_INFINITY; float resultYHoriz = Float.POSITIVE_INFINITY; if (y >= Edge.BOTTOM.getCoordinate() - MIN_CROP_LENGTH_PX) resultYHoriz = Edge.BOTTOM.getCoordinate() - MIN_CROP_LENGTH_PX; if (((Edge.BOTTOM.getCoordinate() - y) * aspectRatio) <= MIN_CROP_LENGTH_PX) resultYVert = Edge.BOTTOM.getCoordinate() - (MIN_CROP_LENGTH_PX / aspectRatio); resultY = Math.min(y, Math.min(resultYHoriz, resultYVert)); } return resultY; } private static float adjustBottom(float y, RectF imageRect, float imageSnapRadius, float aspectRatio) { final float resultY; if (imageRect.bottom - y < imageSnapRadius) { resultY = imageRect.bottom; } else { float resultYVert = Float.NEGATIVE_INFINITY; float resultYHoriz = Float.NEGATIVE_INFINITY; if (y <= Edge.TOP.getCoordinate() + MIN_CROP_LENGTH_PX) { resultYVert = Edge.TOP.getCoordinate() + MIN_CROP_LENGTH_PX; } if (((y - Edge.TOP.getCoordinate()) * aspectRatio) <= MIN_CROP_LENGTH_PX) { resultYHoriz = Edge.TOP.getCoordinate() + (MIN_CROP_LENGTH_PX / aspectRatio); } resultY = Math.max(y, Math.max(resultYHoriz, resultYVert)); } return resultY; } } public static class EdgePair { public Edge primary; public Edge secondary; public EdgePair(Edge edge1, Edge edge2) { primary = edge1; secondary = edge2; } } }
Create layout file crop.xml.
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto"> <view class="com.example.mathfragments.CropUtils$CropImageView" android:layout_width="match_parent" android:layout_height="0dp" android:id="@+id/cropimageview" android:background="@color/purple_200" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toTopOf="@id/done_button"/> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/done_button" android:layout_margin="8dp" android:text="Done" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent"/> </androidx.constraintlayout.widget.ConstraintLayout>
In CropActivity.java use following codes.
package com.example.mathfragments; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; public class CropActivity extends AppCompatActivity { CropUtils.CropImageView imageview1; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.crop); Button button1 = findViewById(R.id.done_button); imageview1 = findViewById(R.id.cropimageview); imageview1.setImageBitmap(getBitmapFromCache()); button1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Bitmap bitmap = imageview1.getCroppedImage(); try { saveBitmapToCache(bitmap); } catch (IOException e) { Log.e("tag", e.toString()); } finish(); } }); } public void saveBitmapToCache(Bitmap bitmap) throws IOException { String filename = "final_image.jpg"; File cacheFile = new File(getApplicationContext().getCacheDir(), filename); OutputStream out = new FileOutputStream(cacheFile); bitmap.compress(Bitmap.CompressFormat.JPEG, (int)100, out); out.flush(); out.close(); } public Bitmap getBitmapFromCache(){ File cacheFile = new File(getApplicationContext().getCacheDir(), "final_image.jpg"); Bitmap myBitmap = BitmapFactory.decodeFile(cacheFile.getAbsolutePath()); return myBitmap; } }
Now run the app