Crop image example in Sketchware

1. Create a new project in Sketchware.

2. In main.xml

* Add a Button btn_pick

* Below this, add an ImageView imageview1.

* Below this add a Linear Horizontal linear_buttons. Inside linear_buttons, add three Buttons: btn_rotate, btn_crop, btn_download.

3. Switch ON AppCompat and design.

4. In Java/Kotlin Manager add a new java file BitmapCacheUtils.java and put following codes in it.


package com.my.newproject10;

import android.content.ContentValues;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.MediaStore;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

public class BitmapCacheUtils {

    private static final String CACHE_FILENAME = "final_image.jpg";

    // Save bitmap to cache
    public static void saveBitmapToCache(Context context, Bitmap bitmap) throws IOException {
        String filename = CACHE_FILENAME;
        File cacheFile = new File(context.getCacheDir(), filename);
        OutputStream out = new FileOutputStream(cacheFile);
        bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out);
        out.flush();
        out.close();
    }

    // Get bitmap from cache
    public static Bitmap getBitmapFromCache(Context context) {
        File cacheFile = new File(context.getCacheDir(), CACHE_FILENAME);
        if (cacheFile.exists()) {
            return BitmapFactory.decodeFile(cacheFile.getAbsolutePath());
        }
        return null;
    }

    // Check if bitmap exists in cache
    public static boolean isBitmapInCache(Context context) {
        File cacheFile = new File(context.getCacheDir(), CACHE_FILENAME);
        return cacheFile.exists() && cacheFile.length() > 0;
    }

    // Delete bitmap from cache
    public static boolean deleteBitmapFromCache(Context context) {
        File cacheFile = new File(context.getCacheDir(), CACHE_FILENAME);
        if (cacheFile.exists()) {
            return cacheFile.delete();
        }
        return false;
    }

    // Get cache file path (useful for sharing or other operations)
    public static String getCacheFilePath(Context context) {
        File cacheFile = new File(context.getCacheDir(), CACHE_FILENAME);
        return cacheFile.getAbsolutePath();
    }

    // Get cache file size
    public static long getCacheFileSize(Context context) {
        File cacheFile = new File(context.getCacheDir(), CACHE_FILENAME);
        if (cacheFile.exists()) {
            return cacheFile.length();
        }
        return 0;
    }

    // Clear entire cache directory (optional - use with caution)
    public static void clearEntireCache(Context context) {
        File cacheDir = context.getCacheDir();
        if (cacheDir != null && cacheDir.isDirectory()) {
            File[] files = cacheDir.listFiles();
            if (files != null) {
                for (File file : files) {
                    file.delete();
                }
            }
        }
    }

    // Save bitmap from cache to Downloads folder (Android 10+ compatible)
    public static String saveBitmapToDownloads(Context context) throws IOException {
        if (!isBitmapInCache(context)) {
            throw new IOException("No cached bitmap found");
        }

        File cacheFile = new File(context.getCacheDir(), CACHE_FILENAME);
        
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            // For Android 10+ (API 29+) - Use MediaStore
            return saveToDownloadsUsingMediaStore(context, cacheFile);
        } else {
            // For Android 9 and below - Use direct file system access
            return saveToDownloadsLegacy(context, cacheFile);
        }
    }

    // Method for Android 10+ using MediaStore
    private static String saveToDownloadsUsingMediaStore(Context context, File sourceFile) throws IOException {
        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date());
        String fileName = "Cropped_Image_" + timeStamp + ".jpg";

        ContentValues contentValues = new ContentValues();
        contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);
        contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg");
        contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS);

        Uri uri = context.getContentResolver().insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues);
        
        if (uri == null) {
            throw new IOException("Failed to create new MediaStore record");
        }

        try (InputStream inputStream = new FileInputStream(sourceFile);
             OutputStream outputStream = context.getContentResolver().openOutputStream(uri)) {
            
            if (outputStream == null) {
                throw new IOException("Failed to open output stream");
            }

            byte[] buffer = new byte[1024];
            int length;
            while ((length = inputStream.read(buffer)) > 0) {
                outputStream.write(buffer, 0, length);
            }
        }

        return fileName;
    }

    // Method for Android 9 and below
    private static String saveToDownloadsLegacy(Context context, File sourceFile) throws IOException {
        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date());
        String fileName = "Cropped_Image_" + timeStamp + ".jpg";

        File downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
        if (!downloadsDir.exists() && !downloadsDir.mkdirs()) {
            throw new IOException("Failed to create Downloads directory");
        }

        File destinationFile = new File(downloadsDir, fileName);

        try (InputStream inputStream = new FileInputStream(sourceFile);
             OutputStream outputStream = new FileOutputStream(destinationFile)) {
            
            byte[] buffer = new byte[1024];
            int length;
            while ((length = inputStream.read(buffer)) > 0) {
                outputStream.write(buffer, 0, length);
            }
        }

        return fileName;
    }
    
    //Rotate bitmap
    public static 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;
    }
}

5. In MainActivity onCreate, make linearButtons GONE, and add block ImageView set image url (This block will add Glide dependencies to the project).

6. Add Components

* Intent intent

* FilePicker fp:image/*

7. In onStart event, put following codes to display Bitmap image from app cache.


if (BitmapCacheUtils.isBitmapInCache(getApplicationContext())){
    binding.imageview1.setImageBitmap(
    BitmapCacheUtils.getBitmapFromCache(getApplicationContext())
    );
}

8. In onDestroy event, put following codes to delete Bitmap image from app cache.


if (BitmapCacheUtils.isBitmapInCache(getApplicationContext())){
    BitmapCacheUtils.deleteBitmapFromCache(getApplicationContext());
}

9. In btn_pick onClick event, use FilePicker fp pick files block.

10. Add a Custom Variable private Uri uri.


11. In FilePicker fp onFilesPicked event, make linearButtons VISIBLE and put following codes to save picked image in cache and display it in imageview1.


if (_data != null) {
    if (_data.getClipData() != null) {
        for (int _index = 0; _index < _data.getClipData().getItemCount(); _index++) {
            ClipData.Item _item = _data.getClipData().getItemAt(_index);
			uri = _item.getUri();
		}
	}
	else {
		uri = _data.getData();
	}
    
    if (uri != null) {
        try {
            Bitmap showBitmap = ImageDecoder.decodeBitmap(ImageDecoder.createSource(getContentResolver(), uri));
            BitmapCacheUtils.saveBitmapToCache(getApplicationContext(), showBitmap);
            binding.imageview1.setImageBitmap(showBitmap);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
           
}

12. In btn_rotate onClick event, use following codes to rotate the image.


try {
    Bitmap showBitmap = BitmapCacheUtils.getBitmapFromCache(getApplicationContext());
    Bitmap rotatedImage = BitmapCacheUtils.rotateBitmap(showBitmap);
    BitmapCacheUtils.saveBitmapToCache(getApplicationContext(), rotatedImage);
    binding.imageview1.setImageBitmap(BitmapCacheUtils.getBitmapFromCache(getApplicationContext()));
} catch (IOException e){
    Log.e("tag", e.toString());
}

13. Create a new page crop.xml.

14. In btn_crop onClick event, use intent to move to CropActivity.


15. In btn_download onClick event, use following codes to save the Bitmap image from cache to Downloads folder.


try {
    String savedFileName = BitmapCacheUtils.saveBitmapToDownloads(getApplicationContext());
    Toast.makeText(getApplicationContext(), "Image saved to Downloads: " + savedFileName, Toast.LENGTH_LONG).show();
} catch (IOException e) {
    e.printStackTrace();
    Toast.makeText(getApplicationContext(), "Failed to save image: " + e.getMessage(), Toast.LENGTH_SHORT).show();
}

16. In Java/Kotlin manager, add a new file CropUtils.java and put following codes in it.


package com.my.newproject10;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
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 java.io.IOException;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

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

}

17. In crop.xml,

* Add a LinearLayout linear1 with gravity CENTER.

* Below this add a Linear Horizontal containing a Spinner spinner1 and a Button button_done.

* Set weight of linear1 to 1.

* Inside linear1 add a LinearLayout linear2 with padding 0, width wrap_content and height wrap_content. (To linear2 we will add CropImageView programmatically in onCreate).


18. Add a Custom Variable private CropUtils.CropImageView crp.

19. Add a String List aspect_ratio.

20. In CropActivity onCreate, add different aspect ratios to the list aspect_ratio. Then display it in spinner1.

After that use codes to define CropImageView crp and add it to linear2.


crp = new CropUtils.CropImageView(this);
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT);
lp.setMargins(0, 0, 0, 0);
crp.setLayoutParams(lp);
crp.setScaleType(ImageView.ScaleType.FIT_CENTER);
crp.setAdjustViewBounds(true);
binding.linear2.addView(crp, 0);

crp.setImageBitmap(
    BitmapCacheUtils.getBitmapFromCache(
    getApplicationContext())
   );

21. In spinner1 onItemSelected (position) event, use following codes to set the aspect ratio of CropImageView crp.


if (_position == 0) {
    crp.setFixedAspectRatio(false);
} else {
    crp.setFixedAspectRatio(true);
    String ratio = aspect_ratio.get(_position);
    String[] parts = ratio.split(":");
    if (parts.length == 2) {
        try {
            int x = Integer.parseInt(parts[0]);
            int y = Integer.parseInt(parts[1]);
            crp.setAspectRatio(x, y);
        } catch (NumberFormatException e) {
            e.printStackTrace();
            // Fallback to default ratio
            crp.setAspectRatio(1, 1);
        }
    }
}

22. In button_done onClick event, use following codes to save the cropped image to cache and then finish Activity.


Bitmap bitmap = crp.getCroppedImage();
try {
    BitmapCacheUtils.saveBitmapToCache(getApplicationContext(), bitmap);
} catch (IOException e) {
    Log.e("tag", e.toString());
}
finish();

23. Save and run the project.




Comments

  1. This was honestly such a clear explanation of how to crop images in Sketchware I had been struggling with aspect ratios and fitting the image properly but this helped a lot. Also as a side note when I’m slammed with assignments, I sometimes use take my online class services just to keep balanced. No shame in outsourcing when needed same as using tools that make coding easier. Appreciate the straightforward writing and real practical value here!

    ReplyDelete

Post a Comment

Popular posts from this blog

Simple car racing android game in Sketchware

How to enable upload from webview in Sketchware?

Simple Audio recorder app in Sketchware

Retrieve contact list in Sketchware

Creating a Drawing View in Sketchware