How to distort an image to any quadrangle?

前端 未结 5 1272
执念已碎
执念已碎 2020-12-16 05:19

Do any of you have an idea, how to distort an image in any quadrangle? I want to implement an image, which you can pull any corner in any direction, distorting the image. A

相关标签:
5条回答
  • 2020-12-16 05:59

    Looks like you need Canvas.drawBitmapMesh . There is a sample in Android SDK showing how to use it.

    You need to use Matrix for drawing your bitmap on Canvas. You can easily create such transformation which will fit your bitmap image into any quadrangle with Matrix.polyToPoly method. It will look like this:

    matrix.setPolyToPoly(
            new float[] { 
                0, 0, 
                bitmap.getWidth(), 0
                0, bitmap.getHeight(),
                bitmap.getWidth(), bitmap.getHeight() 
            }, 0, 
            new float[] { 
                x0, y0, 
                x1, y1, 
                x2, y2,
                x3, y3
            }, 0, 
            4);
    
    canvas.drawBitmap(bitmap, matrix, paint);
    

    Where x0-x3, y0-y3 are your quadrangle vertex coordinates.

    0 讨论(0)
  • 2020-12-16 06:01

    matrix.setPolyToPoly and canvas.drawBitmap can't solve all matrix transformations. Here I found a solution using canvas.drawBitmapMesh.

    Distorting an image to a quadrangle fails in some cases on Android

    0 讨论(0)
  • 2020-12-16 06:07

    There is an issue with your code. Although it is the correct method, you have inverted the float[] parameters, as appears in Android documentation:

    public boolean setPolyToPoly (float[] src, int srcIndex, float[] dst, int dstIndex, int pointCount)
    WHERE
    src: The array of src [x,y] pairs (points)
    ...
    dst: The array of dst [x,y] pairs (points)
    

    So, according with your code, the matrix should be created as:

    matrix.setPolyToPoly(
            new float[] {
                x0, y0,
                x1, y1,
                x2, y2,
                x3, y3},
        0,
    new float[] { 
            0, 0, 
            bitmap.getWidth(), 0
            0, bitmap.getHeight(),
            bitmap.getWidth(), bitmap.getHeight() 
        }, 0, 
        4);
    

    In this way, the app works fine, as shown in the image:

    By the other hand, regarding of what user2498079 says in his comment about the computational problem in low end devices, you can use some easy-to-do techniques to reduce the source image size (and color depth, for example) before matrix conversion calculation. It'd make it easier for the low end phone to realize this task.

    0 讨论(0)
  • 2020-12-16 06:10

    @donmj . I fixed your code.

    public class PerspectiveDistortView extends View implements View.OnTouchListener {
    
    private Paint paintRect, paintCircle;
    public int LEFT;
    public int TOP;
    public int RIGHT;
    public int BOTTOM;
    Point CIRCLE_TOP_LEFT;
    Point CIRCLE_TOP_RIGHT;
    Point CIRCLE_BOTTOM_LEFT;
    Point CIRCLE_BOTTOM_RIGHT;
    private int lastX, lastY;
    Bitmap image;
    Matrix matrix2;
    boolean isTouchCirclePoints = true;
    
    public PerspectiveDistortView(Context context) {
        super(context);
        // TODO Auto-generated constructor stub
        init();
    }
    
    public PerspectiveDistortView(Context context, AttributeSet attrs) {
        super(context, attrs);
        // TODO Auto-generated constructor stub
        init();
    }
    
    public PerspectiveDistortView(Context context, AttributeSet attrs,
                                  int defStyle) {
        super(context, attrs, defStyle);
        // TODO Auto-generated constructor stub
        init();
    }
    
    private void init() {
        this.setOnTouchListener(this);
        paintRect = new Paint();
        paintRect.setColor(0xffff0000);
        paintRect.setAntiAlias(true);
        paintRect.setDither(true);
        paintRect.setStyle(Paint.Style.STROKE);
        paintRect.setStrokeJoin(Paint.Join.BEVEL);
        paintRect.setStrokeCap(Paint.Cap.BUTT);
        paintRect.setStrokeWidth(3);
        paintCircle = new Paint();
        paintCircle.setColor(0xff000000);
        paintCircle.setAntiAlias(true);
        paintCircle.setDither(true);
        paintCircle.setStyle(Paint.Style.FILL_AND_STROKE);
        paintCircle.setStrokeJoin(Paint.Join.BEVEL);
        paintCircle.setStrokeCap(Paint.Cap.BUTT);
    
        LEFT = 90;
        TOP = 40;
        RIGHT = 500;
        BOTTOM = 700;
        CIRCLE_TOP_LEFT = new Point(LEFT, TOP);
        CIRCLE_TOP_RIGHT = new Point(RIGHT, TOP);
        CIRCLE_BOTTOM_LEFT = new Point(LEFT, BOTTOM);
        CIRCLE_BOTTOM_RIGHT = new Point(RIGHT, BOTTOM);
    
        image = BitmapFactory.decodeResource(getResources(), R.drawable.penguins);
    
        matrix2 = new Matrix();
    }
    
    @Override
    protected void onDraw(Canvas canvas) {
        // Free Transform bitmap
        int bw = image.getWidth();
        int bh = image.getHeight();
    
        float[] pts = {
                // source
                0, 0,
                0, bh,
                bw, bh,
                bw, 0,
                // destination
                0, 0,
                0, 0,
                0, 0,
                0, 0};
        pts[8] = CIRCLE_TOP_LEFT.x;
        pts[9] = CIRCLE_TOP_LEFT.y;
        pts[10] = CIRCLE_BOTTOM_LEFT.x;
        pts[11] = CIRCLE_BOTTOM_LEFT.y;
        pts[12] = CIRCLE_BOTTOM_RIGHT.x;
        pts[13] = CIRCLE_BOTTOM_RIGHT.y;
        pts[14] = CIRCLE_TOP_RIGHT.x;
        pts[15] = CIRCLE_TOP_RIGHT.y;
    
        matrix2.setPolyToPoly(pts, 0, pts, 8, 4);
        canvas.drawBitmap(image, matrix2, null);
        isTouchCirclePoints = false;
    
        // line left
        canvas.drawLine(CIRCLE_TOP_LEFT.x, CIRCLE_TOP_LEFT.y, CIRCLE_BOTTOM_LEFT.x, CIRCLE_BOTTOM_LEFT.y, paintRect);
        // line top
        canvas.drawLine(CIRCLE_TOP_LEFT.x, CIRCLE_TOP_LEFT.y, CIRCLE_TOP_RIGHT.x, CIRCLE_TOP_RIGHT.y, paintRect);
        // line right
        canvas.drawLine(CIRCLE_TOP_RIGHT.x, CIRCLE_TOP_RIGHT.y, CIRCLE_BOTTOM_RIGHT.x, CIRCLE_BOTTOM_RIGHT.y, paintRect);
        // line bottom
        canvas.drawLine(CIRCLE_BOTTOM_LEFT.x, CIRCLE_BOTTOM_LEFT.y, CIRCLE_BOTTOM_RIGHT.x, CIRCLE_BOTTOM_RIGHT.y, paintRect);
        // circle top left
        canvas.drawCircle(CIRCLE_TOP_LEFT.x, CIRCLE_TOP_LEFT.y, 10, paintCircle);
        // circle top right
        canvas.drawCircle(CIRCLE_TOP_RIGHT.x, CIRCLE_TOP_RIGHT.y, 10, paintCircle);
        // circle bottom left
        canvas.drawCircle(CIRCLE_BOTTOM_LEFT.x, CIRCLE_BOTTOM_LEFT.y, 10, paintCircle);
        // circle bottom right
        canvas.drawCircle(CIRCLE_BOTTOM_RIGHT.x, CIRCLE_BOTTOM_RIGHT.y, 10, paintCircle);
    }
    
    @Override
    public boolean onTouch(View view, MotionEvent event) {
        lastX = (int) event.getX();
        lastY = (int) event.getY();
    
        if (inCircle(lastX, lastY, CIRCLE_TOP_LEFT.x, CIRCLE_TOP_LEFT.y, 40)) {
            isTouchCirclePoints = true;
            CIRCLE_TOP_LEFT.set(lastX, lastY);
        } else if (inCircle(lastX, lastY, CIRCLE_TOP_RIGHT.x, CIRCLE_TOP_RIGHT.y, 40)) {
            isTouchCirclePoints = true;
            CIRCLE_TOP_RIGHT.set(lastX, lastY);
        } else if (inCircle(lastX, lastY, CIRCLE_BOTTOM_LEFT.x, CIRCLE_BOTTOM_LEFT.y, 40)) {
            isTouchCirclePoints = true;
            CIRCLE_BOTTOM_LEFT.set(lastX, lastY);
        } else if (inCircle(lastX, lastY, CIRCLE_BOTTOM_RIGHT.x, CIRCLE_BOTTOM_RIGHT.y, 40)) {
            isTouchCirclePoints = true;
            CIRCLE_BOTTOM_RIGHT.set(lastX, lastY);
        }
        invalidate();
        return true;
    }
    
    private boolean inCircle(float x, float y, float circleCenterX, float circleCenterY, float circleRadius) {
        double dx = Math.pow(x - circleCenterX, 2);
        double dy = Math.pow(y - circleCenterY, 2);
    
        if ((dx + dy) < Math.pow(circleRadius, 2)) {
            return true;
        } else {
            return false;
        }
    }
    }
    
    0 讨论(0)
  • 2020-12-16 06:12

    I hope this helps. The corner top-left, top-right is working except for bottom-left and bottom-right. Can somebody add it. I can't figure out how to do the bottom parts. :)

    public class PerspectiveDistortView extends View  implements OnTouchListener {
    
    private Paint paintRect, paintCircle;
    public int LEFT;
    public int TOP;
    public int RIGHT;
    public int BOTTOM;
    Point CIRCLE_TOP_LEFT;
    Point CIRCLE_TOP_RIGHT;
    Point CIRCLE_BOTTOM_LEFT;
    Point CIRCLE_BOTTOM_RIGHT;
    private int lastX, lastY;
    Bitmap image;
    Rect src, dst;
    Matrix matrix2;
    boolean isTouchCirclePoints = true;
    float deform2 = 5f;
    
    public PerspectiveDistortView(Context context) {
        super(context);
        // TODO Auto-generated constructor stub
        init();
    }
    
    public PerspectiveDistortView(Context context, AttributeSet attrs) {
        super(context, attrs);
        // TODO Auto-generated constructor stub
        init();
    }
    
    public PerspectiveDistortView(Context context, AttributeSet attrs,
            int defStyle) {
        super(context, attrs, defStyle);
        // TODO Auto-generated constructor stub
        init();
    }
    
    private void init(){
        this.setOnTouchListener(this);
        paintRect = new Paint();
        paintRect.setColor(0xffff0000);
        paintRect.setAntiAlias(true);
        paintRect.setDither(true);
        paintRect.setStyle(Paint.Style.STROKE);
        paintRect.setStrokeJoin(Paint.Join.BEVEL);
        paintRect.setStrokeCap(Paint.Cap.BUTT);
        paintRect.setStrokeWidth(3);
        paintCircle = new Paint();
        paintCircle.setColor(0xff000000);
        paintCircle.setAntiAlias(true);
        paintCircle.setDither(true);
        paintCircle.setStyle(Paint.Style.FILL_AND_STROKE);
        paintCircle.setStrokeJoin(Paint.Join.BEVEL);
        paintCircle.setStrokeCap(Paint.Cap.BUTT);
    
        LEFT = 90;
        TOP = 40;
        RIGHT = 500;
        BOTTOM = 700;
        CIRCLE_TOP_LEFT = new Point(LEFT, TOP);
        CIRCLE_TOP_RIGHT = new Point(RIGHT, TOP);
        CIRCLE_BOTTOM_LEFT = new Point(LEFT, BOTTOM);
        CIRCLE_BOTTOM_RIGHT = new Point(RIGHT, BOTTOM);
    
        image = BitmapFactory.decodeResource(getResources(), R.drawable.ai);
    
        src = new Rect();
        dst = new Rect();
    
        matrix2 = new Matrix();
    }
    
    @Override
    protected void onDraw(Canvas canvas) {
        // draw image
        src.left = LEFT;
        src.top = TOP;
        src.right = RIGHT;
        src.bottom = BOTTOM + image.getHeight();
    
        dst.left = CIRCLE_TOP_LEFT.x;
        dst.top = CIRCLE_TOP_LEFT.y;
        dst.right = CIRCLE_TOP_RIGHT.x;
        dst.bottom = CIRCLE_BOTTOM_RIGHT.y;
    
        // Free Transform bitmap
            int bw = image.getWidth();
            int bh = image.getHeight();
            RectF src = new RectF(LEFT, TOP, bw, bh);
            RectF dst = new RectF(CIRCLE_TOP_LEFT.x + 35, CIRCLE_TOP_LEFT.y + 30, CIRCLE_TOP_RIGHT.x, CIRCLE_BOTTOM_RIGHT.y);
            matrix2.setRectToRect(src, dst, ScaleToFit.FILL);
    
            float[] pts = {
                           // source
                           0, 0, 
                           0, bh, 
                           bw, bh, 
                           bw, 0,
                           // destination
                           0, 0,
                           0, 0, 
                           0, 0, 
                           0, 0};
            matrix2.mapPoints(pts, 8, pts, 0, 4);
            int DX = 100;
            pts[10] -= CIRCLE_TOP_LEFT.x - LEFT; 
            pts[12] -= CIRCLE_TOP_RIGHT.x - RIGHT;
            pts[13] += 0;
            pts[14] += 0;
            pts[15] += CIRCLE_TOP_RIGHT.y - CIRCLE_TOP_LEFT.y;
    
            matrix2.setPolyToPoly(pts, 0, pts, 8, 4);
            canvas.drawBitmap(image, matrix2, null);
            isTouchCirclePoints = false;
    
        // line left
        canvas.drawLine(CIRCLE_TOP_LEFT.x, CIRCLE_TOP_LEFT.y, CIRCLE_BOTTOM_LEFT.x, CIRCLE_BOTTOM_LEFT.y, paintRect);
        // line top
        canvas.drawLine(CIRCLE_TOP_LEFT.x, CIRCLE_TOP_LEFT.y, CIRCLE_TOP_RIGHT.x, CIRCLE_TOP_RIGHT.y, paintRect);
        // line right
        canvas.drawLine(CIRCLE_TOP_RIGHT.x, CIRCLE_TOP_RIGHT.y, CIRCLE_BOTTOM_RIGHT.x, CIRCLE_BOTTOM_RIGHT.y, paintRect);
        // line bottom
        canvas.drawLine(CIRCLE_BOTTOM_LEFT.x, CIRCLE_BOTTOM_LEFT.y, CIRCLE_BOTTOM_RIGHT.x, CIRCLE_BOTTOM_RIGHT.y, paintRect);
        // circle top left
        canvas.drawCircle(CIRCLE_TOP_LEFT.x, CIRCLE_TOP_LEFT.y, 10, paintCircle);
        // circle top right
        canvas.drawCircle(CIRCLE_TOP_RIGHT.x, CIRCLE_TOP_RIGHT.y, 10, paintCircle);
        // circle bottom left
        canvas.drawCircle(CIRCLE_BOTTOM_LEFT.x, CIRCLE_BOTTOM_LEFT.y, 10, paintCircle);
        // circle bottom right
        canvas.drawCircle(CIRCLE_BOTTOM_RIGHT.x, CIRCLE_BOTTOM_RIGHT.y, 10, paintCircle);
    }
    
    @Override
    public boolean onTouch(View view, MotionEvent event) {
        lastX = (int) event.getX();
        lastY = (int)event.getY();
        if (inCircle(lastX, lastY, CIRCLE_TOP_LEFT.x, CIRCLE_TOP_LEFT.y, 40))
        {
            isTouchCirclePoints = true;
            CIRCLE_TOP_LEFT.set(lastX, lastY);
        } else if (inCircle(lastX, lastY, CIRCLE_TOP_RIGHT.x, CIRCLE_TOP_RIGHT.y, 40))
        {
            isTouchCirclePoints = true;
            CIRCLE_TOP_RIGHT.set(lastX, lastY);
        } else if (inCircle(lastX, lastY, CIRCLE_BOTTOM_LEFT.x, CIRCLE_BOTTOM_LEFT.y, 40))
        {
            isTouchCirclePoints = true;
            CIRCLE_BOTTOM_LEFT.set(lastX, lastY);
        } else if (inCircle(lastX, lastY, CIRCLE_BOTTOM_RIGHT.x, CIRCLE_BOTTOM_RIGHT.y, 40))
        {
            isTouchCirclePoints = true;
            CIRCLE_BOTTOM_RIGHT.set(lastX, lastY);
        }
        invalidate();
        return true;
    }
    
    private boolean inCircle(float x, float y, float circleCenterX, float circleCenterY, float circleRadius) {
        double dx = Math.pow(x - circleCenterX, 2);
        double dy = Math.pow(y - circleCenterY, 2);
    
        if ((dx + dy) < Math.pow(circleRadius, 2)) {
            return true;
        } else {
            return false;
        }
    }
    

    }

    0 讨论(0)
提交回复
热议问题