Android photo selection area function implementation example

Time:2021-10-17

Realize the photo selection area function of Android

Mainly refer to pqpo / smartcropper

1. Display

Show four edges and eight points,

Eight points: the midpoint of four corners and four sides

/*Crop region,
0, top left - > lefttop, 
1. Top right - > righttop,
2. Lower right - > rightbottom, 
3. Bottom left - > leftbottom
*/
Point[] mCropPoints;

//Midpoint of 4 sides
Point[] mEdgeMidPoints;

draw

protected void onDrawCropPoint(Canvas canvas) {
        //Draw mask
        onDrawMask(canvas);
        //Draw guides
        onDrawGuideLine(canvas);
        //Draw selection line
        onDrawLines(canvas);
        //Draw anchor
        onDrawPoints(canvas);
        //Draw magnifier
        // ...
    }

Specific drawing part:

Draw eight points

protected void onDrawPoints(Canvas canvas) {
        if (!checkPoints(mCropPoints)) {
            return;
        }
        //Draw 4 corners
        for (Point point : mCropPoints) {
            canvas.drawCircle(getViewPointX(point), getViewPointY(point), dp2px(POINT_RADIUS), mPointFillPaint);
            canvas.drawCircle(getViewPointX(point), getViewPointY(point), dp2px(POINT_RADIUS), mPointPaint);
        }
        if (mShowEdgeMidPoint) {
            setEdgeMidPoints();
            //Intermediate anchor
            //Draw the midpoint on the 4 edges
            for (Point point : mEdgeMidPoints){
                canvas.drawCircle(getViewPointX(point), getViewPointY(point), dp2px(POINT_RADIUS), mPointFillPaint);
                canvas.drawCircle(getViewPointX(point), getViewPointY(point), dp2px(POINT_RADIUS), mPointPaint);
            }
        }
    }

Before drawing the midpoint on the 4 edges,

First calculate the position of the midpoint on the current 4 edges

public void setEdgeMidPoints(){
        //If the midpoint does not exist, create a new one
        if (mEdgeMidPoints == null){
            mEdgeMidPoints = new Point[4];
            for (int i = 0; i < mEdgeMidPoints.length; i++){
                mEdgeMidPoints[i] = new Point();
            }
        }
        //Maintain the position of 4 vertices,
        //Calculate the position of the midpoint on the edge through the position of the vertex
        
        int len = mCropPoints.length;
        for (int i = 0; i < len; i++){
            //To avoid extremes,
            //In the way of (coordinate + half of the distance)
            
            mEdgeMidPoints[i].set(mCropPoints[i].x + (mCropPoints[(i+1)%len].x - mCropPoints[i].x)/2,
                                    mCropPoints[i].y + (mCropPoints[(i+1)%len].y - mCropPoints[i].y)/2);
        }
    }

2. Drag

Dragging is divided into two cases: corner dragging and midpoint translation

8 types, 4 corner drag, 4 midpoint translation

enum DragPointType{
        LEFT_TOP,
        RIGHT_TOP,
        RIGHT_BOTTOM,
        LEFT_BOTTOM,
        TOP,
        RIGHT,
        BOTTOM,
        LEFT;
        
        //Judge whether it is corner drag, not midpoint translation
        public static boolean isEdgePoint(DragPointType type){
            return type == TOP || type == RIGHT || type == BOTTOM || type == LEFT;
        }
    }

Mobile processing

@Override
    public boolean onTouchEvent(MotionEvent event) {
        int action = event.getAction();
        boolean handle = true;
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                //Identified, current point
                mDraggingPoint = getNearbyPoint(event);
                if (mDraggingPoint == null) {
                    handle = false;
                }
                break;
            case MotionEvent.ACTION_MOVE:
                //Move
                toImagePointSize(mDraggingPoint, event);
                break;
            case MotionEvent.ACTION_UP:
                //Fingers up,
                //Operation cancelled
                mDraggingPoint = null;
                break;
        }
        //Draw
        //After updating the position, refresh the drawing
        invalidate();
        return handle || super.onTouchEvent(event);
    }

Identified, current point

private Point getNearbyPoint(MotionEvent event) {
        //Judge 4 corners, available
        if (checkPoints(mCropPoints)) {
            for (Point p : mCropPoints) {
                //Find the current point
                if (isTouchPoint(p, event)) return p;
            }
        }
        //Judge 4 mid points available
        if (checkPoints(mEdgeMidPoints)) {
            for (Point p : mEdgeMidPoints){
                //Find the current point
                if (isTouchPoint(p, event)) return p;
            }
        }
        return null;
    }

Find out the current point and find out the method

private static final float TOUCH_POINT_CATCH_DISTANCE = 15;

private boolean isTouchPoint(Point p, MotionEvent event){
        float x = event.getX();
        float y = event.getY();
        float px = getViewPointX(p);
        float py = getViewPointY(p);
        double distance =  Math.sqrt(Math.pow(x - px, 2) + Math.pow(y - py, 2));
        
        //That is, judge the distance
        if (distance < dp2px(TOUCH_POINT_CATCH_DISTANCE)) {
            return true;
        }
        return false;
    }

2.1 corner drag

First introduce the four corner drag

private void toImagePointSize(Point dragPoint, MotionEvent event) {
        if (dragPoint == null) {
            return;
        }
        //Find the current movement type,
        //Corner drag or midpoint translation
        DragPointType pointType = getPointType(dragPoint);

        int x = (int) ((Math.min(Math.max(event.getX(), mActLeft), mActLeft + mActWidth) - mActLeft) / mScaleX);
        int y = (int) ((Math.min(Math.max(event.getY(), mActTop), mActTop + mActHeight) - mActTop) / mScaleY);

        //Judge can move
        // ...
        
       
        if (DragPointType.isEdgePoint(pointType)){
            // ...
            //Midpoint translation
        } else {
            //Corner drag
            //The implementation is simple,
            //Just update it
            dragPoint.y = y;
            dragPoint.x = x;
        }
    }

Find the current movement type,

Corner drag or midpoint translation

//Take the collected points and find out the corresponding type
private DragPointType getPointType(Point dragPoint){
        if (dragPoint == null) return null;

        DragPointType type;
        //Look, is it a vertex / corner
        if (checkPoints(mCropPoints)) {
            for (int i = 0; i < mCropPoints.length; i++) {
                if (dragPoint == mCropPoints[i]) {
                    //Found, go straight back
                    type = DragPointType.values()[i];
                    return type;
                }
            }
        }
        //Look, is it the midpoint
        if (checkPoints(mEdgeMidPoints)) {
            for (int i = 0; i < mEdgeMidPoints.length; i++){
                if (dragPoint == mEdgeMidPoints[i]){
                    //Found, go straight back
                    type = DragPointType.values()[4+i];
                    return type;
                }
            }
        }
        return null;
    }

2.2, midpoint translation

private void toImagePointSize(Point dragPoint, MotionEvent event) {
        if (dragPoint == null) {
            return;
        }

        DragPointType pointType = getPointType(dragPoint);

        int x = // ...
        int y = // ...

        //Judge can move
        // ...
       
        if (DragPointType.isEdgePoint(pointType)){
            //Midpoint translation,
            //So what I get is an offset vector
            int xoff = x - dragPoint.x;
            int yoff = y - dragPoint.y;
            moveEdge(pointType, xoff, yoff);
        } else {
            //Corner drag
            // ...
        }
    }

Get the offset vector and modify the coordinates of the corresponding two vertices

private void moveEdge(DragPointType type, int xoff, int yoff){
        switch (type){
            case TOP:
                //The translation here is relatively simple
                //Find the midpoint, next to the two focal points
                //Re move position
                movePoint(mCropPoints[P_LT], 0, yoff);
                movePoint(mCropPoints[P_RT], 0, yoff);
                break;
            case RIGHT:
                //Shift right processing
                movePoint(mCropPoints[P_RT], xoff, 0);
                movePoint(mCropPoints[P_RB], xoff, 0);
                break;
            case BOTTOM:
                //Move down processing
                // ...
            case LEFT:
                //Shift left processing
                // ...
            default: break;
        }
    }

Simple translation code

Get the offset vector, modify the coordinates, done

private void movePoint(Point point, int xoff, int yoff){
        if (point == null) return;
        int x = point.x + xoff;
        int y = point.y + yoff;
        //Check boundary
        if (x < 0 || x > getDrawable().getIntrinsicWidth()) return;
        if (y < 0 || y > getDrawable().getIntrinsicHeight()) return;
        point.x = x;
        point.y = y;
    }

Midpoint translation enhancement

The midpoint here translates, and when you get the translation vector,

Add directly to the two corners next to the midpoint

The effect is enhanced to translate the midpoint, and the two corners next to the midpoint translate along both sides
The calculation is a little complicated,

Know the position before and after the midpoint,

Know the slope of the edge where the midpoint and a corner are located,

Know the slope of the other side of this corner

Know the corner, the position before translation,

Solve the corner and the position after translation

This is an equation solving website

It may be easier to transform the coordinate system
Related GitHub

Gradle configuration of demo

This is the end of this article about the implementation example of Android photo selection area function. For more information about Android photo selection area, please search for previous articles of developeppaer or continue to browse the relevant articles below. I hope you will support developeppaer in the future!