How to distort an image to any quadrangle?

匿名 (未验证) 提交于 2019-12-03 01:05:01

问题:

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. Anyone has an idea how to do that? I use and write stuff in android for a while now, but it does not seem like android has a function for that. I don't really feel like writing a new math library :).

Greetings, Can

回答1:

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.



回答2:

@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) 


回答3:

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.



回答4:

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) 

}



回答5:

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



标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!