-1

I am trying to crate custom image cropping like whatsapp, where user upload profile picture and then user gets rectangle shape cropper which let user crop image as they like.I don't want to use any library

According to this answer i successfully get dragable imageview over imageview and it's working fine but this code has some issue which i am not able to fix. I comment over his github but he is no more maintain this code.

What i have tried so far:

Init points at the constructor

public DrawView(Context context, AttributeSet attrs) {
        super(context, attrs);
        paint = new Paint();
        setFocusable(true); // necessary for getting the touch events
        canvas = new Canvas();
points[0] = new Point();
points[0].x = 150;
points[0].y = 20;
points[1] = new Point();
points[1].x = 150;
points[1].y = 20;
points[2] = new Point();
points[2].x = 150;
points[2].y = 20;
points[3] = new Point();
points[3].x = 150;
points[3].y = 20;
    }

Manually Set value at onDraw of left,right,top,bottom variable

// the method that draws the balls

    @Override
    protected void onDraw(Canvas canvas) {
        if(points[3]==null) //point4 null when user did not touch and move on screen.
            return;
        int left, top, right, bottom;
        left = 150;
        top = 50;
        right = 150;
        bottom = 50;
        for (int i = 1; i < points.length; i++) {
            left = left > points[i].x ? points[i].x:left;
            top = top > points[i].y ? points[i].y:top;
            right = right < points[i].x ? points[i].x:right;
            bottom = bottom < points[i].y ? points[i].y:bottom;
        }
        paint.setAntiAlias(true);
        paint.setDither(true);
        paint.setStrokeJoin(Paint.Join.ROUND);
        paint.setStrokeWidth(5);

        //draw stroke
        paint.setStyle(Paint.Style.STROKE);
        paint.setColor(Color.parseColor("#AADB1255"));
        paint.setStrokeWidth(2);
        canvas.drawRect(
                    left + colorballs.get(0).getWidthOfBall() / 2,
                    top + colorballs.get(0).getWidthOfBall() / 2, 
                    right + colorballs.get(2).getWidthOfBall() / 2, 
                    bottom + colorballs.get(2).getWidthOfBall() / 2, paint);
        //fill the rectangle
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(Color.parseColor("#55DB1255"));
        paint.setStrokeWidth(0);
        canvas.drawRect(
                left + colorballs.get(0).getWidthOfBall() / 2,
                top + colorballs.get(0).getWidthOfBall() / 2, 
                right + colorballs.get(2).getWidthOfBall() / 2, 
                bottom + colorballs.get(2).getWidthOfBall() / 2, paint);

        //draw the corners
        BitmapDrawable bitmap = new BitmapDrawable();
        // draw the balls on the canvas
        paint.setColor(Color.BLUE);
        paint.setTextSize(18);
        paint.setStrokeWidth(0);
        for (int i =0; i < colorballs.size(); i ++) {
            ColorBall ball = colorballs.get(i);
            canvas.drawBitmap(ball.getBitmap(), ball.getX(), ball.getY(),
                    paint);

            canvas.drawText("" + (i+1), ball.getX(), ball.getY(), paint);
        }
    }

So i need to fix few issue in this. Please help

  1. All 4 dots overlapping each other at first launch and only after visible after user touch the screen. I tried to make 4 dots stretch at 50% of background ImageView but failed to do so.

  2. After successfully selecting particular area to crop. How to get background imageview of that region(please just give me idea how to do this).

Ritu
  • 518
  • 2
  • 12
  • 35

1 Answers1

2

1. All 4 dots overlapping each other at first launch and only after visible after user touch the screen. I tried to make 4 dots stretch at 50% of background ImageView but failed to do so.

Answer: you should set a proper init value for points array, the origin answer code logic is assign points array when finger touch screen int onTouchEvent() MotionEvent.ACTION_DOWN type and invalidate the view then the view will execute onDraw() to draw rectangle.

So if you want to show the crop rectangle before you touch screen, you should set a proper value for points array (maybe the center of view with fixed width and height square, you should calculate) and manually add draw the rectangle code in onDraw() method.

2. After successfully selecting particular area to crop. How to get background imageview of that region(please just give me idea how to do this).

Answer: when you got a particular area to crop, then you must know the area coordinate info.

(1) get source bitmap, you have showed a picture in your background imageview, so you can get a source bitmap, or you can decode a bitmap from file or resource

(2) execute crop operation

Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.xyz);

//you also can use other logic to get a bitmap, this according to your app logic

//create a crop area rectangle
//this crop area rectangle should calculate from the points array
Bitmap croppedBitmap = Bitmap.createBitmap(bmp, x, y, cropWidth, cropHeight);

then you can get a cropped bitmap.

Hope these tips may give you an idea to archive what you want.

Here is the Demo link and GIF link


package com.image.crop;

import java.util.ArrayList;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;


public class DrawView extends View {

    Point[] points = new Point[4];

    /**
     * point1 and point 3 are of same group and same as point 2 and point4
     */
    int groupId = -1;
    private ArrayList<ColorBall> colorballs = new ArrayList<>();

    private int mStrokeColor = Color.parseColor("#AADB1255");
    private int mFillColor = Color.parseColor("#55DB1255");
    private Rect mCropRect = new Rect();

    // array that holds the balls
    private int balID = 0;
    // variable to know what ball is being dragged
    Paint paint;

    public DrawView(Context context) {
        this(context, null);
    }

    public DrawView(Context context, AttributeSet attrs) {
        this(context, attrs, -1);
    }

    public DrawView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    private void init() {
        paint = new Paint();
        setFocusable(true); // necessary for getting the touch events
    }

    private void initRectangle(int X, int Y) {
        //initialize rectangle.
        points[0] = new Point();
        points[0].x = X;
        points[0].y = Y;

        points[1] = new Point();
        points[1].x = X;
        points[1].y = Y + 30;

        points[2] = new Point();
        points[2].x = X + 30;
        points[2].y = Y + 30;

        points[3] = new Point();
        points[3].x = X +30;
        points[3].y = Y;

        balID = 2;
        groupId = 1;
        // declare each ball with the ColorBall class
        for (int i = 0; i < points.length; i++) {
            colorballs.add(new ColorBall(getContext(), R.drawable.gray_circle, points[i], i));
        }
    }

    // the method that draws the balls
    @Override
    protected void onDraw(Canvas canvas) {
        if(points[3]==null) {
            //point4 null when view first create
            initRectangle(getWidth() / 2, getHeight() / 2);
        }

        int left, top, right, bottom;
        left = points[0].x;
        top = points[0].y;
        right = points[0].x;
        bottom = points[0].y;
        for (int i = 1; i < points.length; i++) {
            left = left > points[i].x ? points[i].x : left;
            top = top > points[i].y ? points[i].y : top;
            right = right < points[i].x ? points[i].x : right;
            bottom = bottom < points[i].y ? points[i].y : bottom;
        }
        paint.setAntiAlias(true);
        paint.setDither(true);
        paint.setStrokeJoin(Paint.Join.ROUND);
        paint.setStrokeWidth(5);

        //draw stroke
        paint.setStyle(Paint.Style.STROKE);
        paint.setColor(mStrokeColor);
        paint.setStrokeWidth(2);

        mCropRect.left = left + colorballs.get(0).getWidthOfBall() / 2;
        mCropRect.top = top + colorballs.get(0).getWidthOfBall() / 2;
        mCropRect.right = right + colorballs.get(2).getWidthOfBall() / 2;
        mCropRect.bottom = bottom + colorballs.get(3).getWidthOfBall() / 2;
        canvas.drawRect(mCropRect, paint);

        //fill the rectangle
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(mFillColor);
        paint.setStrokeWidth(0);
        canvas.drawRect(mCropRect, paint);

        // draw the balls on the canvas
        paint.setColor(Color.RED);
        paint.setTextSize(18);
        paint.setStrokeWidth(0);
        for (int i =0; i < colorballs.size(); i ++) {
            ColorBall ball = colorballs.get(i);
            canvas.drawBitmap(ball.getBitmap(), ball.getX(), ball.getY(),
                    paint);

            canvas.drawText("" + (i+1), ball.getX(), ball.getY(), paint);
        }
    }

    // events when touching the screen
    public boolean onTouchEvent(MotionEvent event) {
        int eventAction = event.getAction();

        int X = (int) event.getX();
        int Y = (int) event.getY();

        switch (eventAction) {

            case MotionEvent.ACTION_DOWN: // touch down so check if the finger is on
                // a ball
                if (points[0] == null) {
                    initRectangle(X, Y);
                } else {
                    //resize rectangle
                    balID = -1;
                    groupId = -1;
                    for (int i = colorballs.size()-1; i>=0; i--) {
                        ColorBall ball = colorballs.get(i);
                        // check if inside the bounds of the ball (circle)
                        // get the center for the ball
                        int centerX = ball.getX() + ball.getWidthOfBall();
                        int centerY = ball.getY() + ball.getHeightOfBall();
                        paint.setColor(Color.CYAN);
                        // calculate the radius from the touch to the center of the
                        // ball
                        double radCircle = Math
                                .sqrt((double) (((centerX - X) * (centerX - X)) + (centerY - Y)
                                        * (centerY - Y)));

                        if (radCircle < ball.getWidthOfBall()) {

                            balID = ball.getID();
                            if (balID == 1 || balID == 3) {
                                groupId = 2;
                            } else {
                                groupId = 1;
                            }
                            invalidate();
                            break;
                        }
                        invalidate();
                    }
                }
                break;

            case MotionEvent.ACTION_MOVE: // touch drag with the ball

                if (balID > -1) {
                    // move the balls the same as the finger
                    colorballs.get(balID).setX(X);
                    colorballs.get(balID).setY(Y);

                    paint.setColor(Color.CYAN);
                    if (groupId == 1) {
                        colorballs.get(1).setX(colorballs.get(0).getX());
                        colorballs.get(1).setY(colorballs.get(2).getY());
                        colorballs.get(3).setX(colorballs.get(2).getX());
                        colorballs.get(3).setY(colorballs.get(0).getY());
                    } else {
                        colorballs.get(0).setX(colorballs.get(1).getX());
                        colorballs.get(0).setY(colorballs.get(3).getY());
                        colorballs.get(2).setX(colorballs.get(3).getX());
                        colorballs.get(2).setY(colorballs.get(1).getY());
                    }

                    invalidate();
                }

                break;

            case MotionEvent.ACTION_UP:
                // touch drop - just do things here after dropping
                // doTheCrop()
                break;
        }
        // redraw the canvas
        invalidate();
        return true;
    }

    public void doTheCrop() {
        Bitmap sourceBitmap = null;
        Drawable backgroundDrawable = getBackground();
        if (backgroundDrawable instanceof BitmapDrawable) {
            BitmapDrawable bitmapDrawable = (BitmapDrawable) backgroundDrawable;
            if(bitmapDrawable.getBitmap() != null) {
                sourceBitmap = bitmapDrawable.getBitmap();
            }
        }
        if (sourceBitmap != null) {
            //source bitmap was scaled, you should calculate the rate
            float widthRate = ((float) sourceBitmap.getWidth()) / getWidth();
            float heightRate =  ((float) sourceBitmap.getHeight()) / getHeight();

            //crop the source bitmap with rate value
            int left = (int) (mCropRect.left * widthRate);
            int top = (int) (mCropRect.top * heightRate);
            int right = (int) (mCropRect.right * widthRate);
            int bottom = (int) (mCropRect.bottom * heightRate);
            Bitmap croppedBitmap = Bitmap.createBitmap(sourceBitmap, left, top, right - left, bottom - top);
            BitmapDrawable drawable = new BitmapDrawable(getResources(), croppedBitmap);
            setBackground(drawable);
        }
    }

    public static class ColorBall {

        Bitmap bitmap;
        Context mContext;
        Point point;
        int id;

        public ColorBall(Context context, int resourceId, Point point, int id) {
            this.id = id;
            bitmap = BitmapFactory.decodeResource(context.getResources(),
                    resourceId);
            mContext = context;
            this.point = point;
        }

        public int getWidthOfBall() {
            return bitmap.getWidth();
        }

        public int getHeightOfBall() {
            return bitmap.getHeight();
        }

        public Bitmap getBitmap() {
            return bitmap;
        }

        public int getX() {
            return point.x;
        }

        public int getY() {
            return point.y;
        }

        public int getID() {
            return id;
        }

        public void setX(int x) {
            point.x = x;
        }

        public void setY(int y) {
            point.y = y;
        }
    }
}

Jeffery Ma
  • 3,051
  • 1
  • 23
  • 26
  • thankyou for your answer but i as you can see in question that i already tried in `onDraw` method but its not working at all. It would be helpful for me if you can let me know how to do it correctly. – Ritu May 13 '19 at 19:43
  • @Magic i tried above solution it gives me result as expected but cropping image with above method is not working cuz code above never satisfy `if (backgroundDrawable instanceof BitmapDrawable)` – androidXP May 15 '19 at 14:59
  • @androidXP you can check the demo code, the picture was set as a view background, so `doTheCrop()` method get the BitmapDrawable from the view's background. You can customize the logic as you wish decode a bitmap or something else, but the idea is get a bitmap and according the crop rectangle to create a cropped bitmap. – Jeffery Ma May 15 '19 at 15:32
  • Thanks for responding – androidXP May 15 '19 at 15:33