可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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