2

I'm trying to draw using onTouch events on a Canvas over an Imageview that is pinchable/zoomable. It works perfectly if the image is fullscreen (and proportioned to the size of the screen), but once you zoom in on the image, the drawing point is not the same place as where you touch the screen. Something like this

I am creating a canvas and an imageview like so:

                    canvas = new Canvas(bitmap);
                    image.setImageBitmap(bitmap);

and then performing a lot of pinch/zoom transformations by using setMatrix on image. By looking at bitmap.getHeight() and bitmap.getWidth(), it's evident that throughout all the transformations, neither Bitmap nor Image actually change in size. Image, for instance, stays at a constant 2560x1454 while Bitmap stays at 3264x1836 (using image.getHeight()/width() and bitmap.getHeight()/width() for those values).

For the drawing part, I'm converting MotionEvent to a series of Point objects (literally just int x, int y) and then translating that value through a formula I found somewhere on the internet. I really have no idea why it works, which is probably my first problem.

   List<Point> points = new ArrayList<>();
   private boolean handleDraw(View v, MotionEvent event) {
    if (event.getAction() != MotionEvent.ACTION_UP) {
        Point p = new Point();
        p.x = event.getX();
        p.y = event.getY();

        convertPoint(p, v);
        points.add(p);

        //draw the converted points
        drawOnBitmap();
        return true;
    } else if (event.getAction() == MotionEvent.ACTION_UP) {
        points.clear();
        return true;
    }
    return true;
}

and then they are converted into bitmap values here

 //v here is the view passed by the ontouch listener.
 private void convertPoint(Point point, View v) {

    double divisor = ((double) bitmap.getWidth() / (double) v.getWidth());

    point.x = (int) ((double) point.x * divisor);
    point.y = (int) ((double) point.y * divisor);
 }

and drawn onto the canvas here:

private void drawOnBitmap() {
    Path path = new Path();

    boolean first = true;
    //uses quaddraw
    for (int i = 0; i < points.size(); i += 2) {
        Point point = points.get(i);
        if (first) {
            first = false;
            path.moveTo(point.x, point.y);
        } else if (i < points.size() - 1) {
            Point next = points.get(i + 1);
            path.quadTo(point.x, point.y, next.x, next.y);
        } else {
            path.lineTo(point.x, point.y);
        }
    }

    canvas.drawPath(path, redPaint);
    image.invalidate();

}

with Paint being

    redPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    redPaint.setStyle(Paint.Style.STROKE);
    redPaint.setStrokeWidth(STROKE_WIDTH);
    redPaint.setColor(Color.RED);

In general, what's happening is that the touch event is being scaled from the size of the screen to the size of the bitmap (see the above image for an example). However, the answer isn't to just take the original getRaw() values; those are always incorrect.

I guess the question is, how do I get the canvas to respond to the ABSOLUTE position of the touch event instead of the proportional one? Failing that, how can I transform the base canvas with the same values as the imageview and get them both to scale proportionally?

Matter Cat
  • 1,538
  • 1
  • 14
  • 23

1 Answers1

2

You need to recalculate your coords using Matrix with code like this:

public class DrawableImageView extends ImageView implements OnTouchListener 
{
    float downx = 0;
    float downy = 0;
    float upx = 0;
    float upy = 0;

    Canvas canvas;
    Paint paint;
    Matrix matrix;

    public DrawableImageView(Context context) 
    {
        super(context);
        setOnTouchListener(this);
    }

    public DrawableImageView(Context context, AttributeSet attrs) 
    {
        super(context, attrs);
        setOnTouchListener(this);
    }

    public DrawableImageView(Context context, AttributeSet attrs,
            int defStyleAttr) 
    {
        super(context, attrs, defStyleAttr);
        setOnTouchListener(this);
    }

    public void setNewImage(Bitmap alteredBitmap, Bitmap bmp)
    {
        canvas = new Canvas(alteredBitmap );
        paint = new Paint();
        paint.setColor(Color.GREEN);
        paint.setStrokeWidth(5);
        matrix = new Matrix();
        canvas.drawBitmap(bmp, matrix, paint);

        setImageBitmap(alteredBitmap);
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) 
    {
        int action = event.getAction();

        switch (action) 
        {
        case MotionEvent.ACTION_DOWN:
            downx = getPointerCoords(event)[0];//event.getX();
            downy = getPointerCoords(event)[1];//event.getY();
            break;
        case MotionEvent.ACTION_MOVE:
            upx = getPointerCoords(event)[0];//event.getX();
            upy = getPointerCoords(event)[1];//event.getY();
            canvas.drawLine(downx, downy, upx, upy, paint);
            invalidate();
            downx = upx;
            downy = upy;
            break;
        case MotionEvent.ACTION_UP:
            upx = getPointerCoords(event)[0];//event.getX();
            upy = getPointerCoords(event)[1];//event.getY();
            canvas.drawLine(downx, downy, upx, upy, paint);
            invalidate();
            break;
        case MotionEvent.ACTION_CANCEL:
            break;
        default:
            break;
        }
        return true;
    }

    final float[] getPointerCoords(MotionEvent e)
    {
        final int index = e.getActionIndex();
        final float[] coords = new float[] { e.getX(index), e.getY(index) };
        Matrix matrix = new Matrix();
        getImageMatrix().invert(matrix);
        matrix.postTranslate(getScrollX(), getScrollY());
        matrix.mapPoints(coords);
        return coords;
    }
}

The full sample you can get here.

Community
  • 1
  • 1
anil
  • 2,083
  • 21
  • 37