【问题标题】:How to distort an image to any quadrangle?如何将图像扭曲为任何四边形?
【发布时间】:2012-12-05 18:46:18
【问题描述】:

你们有没有一个想法,如何在任何四边形中扭曲图像? 我想实现一个图像,你可以在任何方向拉任何角落,扭曲图像。任何人都知道如何做到这一点?我现在在 android 中使用和编写东西有一段时间了,但似乎 android 没有这样的功能。我真的不想写一个新的数学库:)。

您好, 可以

【问题讨论】:

    标签: android image clip distortion


    【解决方案1】:

    matrix.setPolyToPolycanvas.drawBitmap 不能解决所有的矩阵变换。在这里,我找到了使用canvas.drawBitmapMesh 的解决方案。

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

    【讨论】:

      【解决方案2】:

      @donmj 。我修复了你的代码。

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

      【讨论】:

      【解决方案3】:

      我希望这会有所帮助。左上角、右上角除了左下角和右下角外都在工作。有人可以添加它。我不知道如何做底部部分。 :)

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

      }

      【讨论】:

        【解决方案4】:

        您的代码有问题。尽管它是正确的方法,但您已经反转了 float[] 参数,如 Android 文档中所示:

        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)
        

        因此,根据您的代码,矩阵应创建为:

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

        这样,应用就可以正常工作了,如图:

        另一方面,关于 user2498079 在他的评论中关于低端设备中的计算问题的评论,您可以使用一些易于操作的技术来减少源图像的大小(例如颜色深度)矩阵转换计算。让低端手机更容易实现这个任务。

        【讨论】:

          【解决方案5】:

          看起来你需要Canvas.drawBitmapMesh 。 Android SDK 中有一个示例展示了如何使用它。

          您需要使用MatrixCanvas 上绘制位图。您可以使用Matrix.polyToPoly 方法轻松创建这样的转换,使您的位图图像适合任何四边形。它看起来像这样:

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

          其中 x0-x3, y0-y3 是您的四边形顶点坐标。

          【讨论】:

          • 非常感谢您的快速答复。我觉得在矩阵类中没有看到 setPolytoPoly 方法很愚蠢,但这应该可以。明天我会在工作中测试它:-)!
          • 这种方法的问题是计算量很大,因此在低端设备上速度很慢。
          • 是的,但我们实际上别无选择。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2022-01-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-05-31
          相关资源
          最近更新 更多