Shawn
Shawn

Reputation: 402

Set zoom ratio of a displayed image in an android canvas

I am trying to write an android app that lets me draw graphics on top of an image then scale and zoom the image with the graphics staying over the same place on the image that have been drawn on top of it while changing the graphics in real time.

However I have been having a lot of issues actually getting it to zoom in while maintaining the center of the image. I have written code where I have a thread that updates the image. Updates are passed in using a class that I created called "PendingUpdate" through an ArrayBlockingQueue. This update contains a desired zoom level which is supposed to be the ratio of the image pixels to the canvas pixels and an image center. However the following code makes it pan while I am zooming which confuses me.

//Scale the image
canvas.scale(pendingUpdate.getZoom(), pendingUpdate.getZoom());

//Translate the image
double updateCx = pendingUpdate.getCenter().getX();
double updateCy = pendingUpdate.getCenter().getY();
double halfCanvasWidthInImagePixels = pendingUpdate.getZoom()*(canvas.getWidth()/2);
double halfCanvasHeightInImagePixels = pendingUpdate.getZoom()*(canvas.getHeight()/2);
double imageTranslateX = updateCx - halfCanvasWidthInImagePixels;
double imageTranslateY = updateCy - halfCanvasHeightInImagePixels;
canvas.translate(-(float)imageTranslateX, -(float)imageTranslateY);
canvas.drawBitmap(pendingUpdate.getImage(), matrix, new Paint());

Thank you for the help!

Edit: here is the full function, I can also post PendingUpdate if this helps, however its just a data class.

    private void doDraw(Canvas canvas, PendingUpdate pendingUpdate) {
        int iWidth = pendingUpdate.getImage().getWidth();
        int iHeight = pendingUpdate.getImage().getHeight();
        Matrix matrix = new Matrix();

        canvas.drawColor(Color.BLACK);
        //TODO: add scrolling functionality to this
        if(pendingUpdate.getZoom()>0) {

            //Scale the image
            canvas.scale(pendingUpdate.getZoom(), pendingUpdate.getZoom());

            //Translate the image
            double updateCx = pendingUpdate.getCenter().getX();
            double updateCy = pendingUpdate.getCenter().getY();
            double halfCanvasWidthInImagePixels = pendingUpdate.getZoom()*(canvas.getWidth()/2);
            double halfCanvasHeightInImagePixels = pendingUpdate.getZoom()*(canvas.getHeight()/2);
            double imageTranslateX = updateCx - halfCanvasWidthInImagePixels;
            double imageTranslateY = updateCy - halfCanvasHeightInImagePixels;
            canvas.translate(-(float)imageTranslateX, -(float)imageTranslateY);
            canvas.drawBitmap(pendingUpdate.getImage(), matrix, new Paint());

        }else {
            //matrix.postTranslate(canvas.getWidth()-iWidth/2, canvas.getWidth()-iHeight/2);
            canvas.drawBitmap(pendingUpdate.getImage(),
                    (canvas.getWidth()-iWidth)/2,
                    (canvas.getHeight()-iHeight)/2, null);
        }
        //TODO: draw other stuff on canvas here such as current location

    }

edit 2: This is how I finally got it to work, it was simply a matter of scaling it before translating it.

    private void doDraw(Canvas canvas, PendingUpdate pendingUpdate) {
        int iWidth = pendingUpdate.getImage().getWidth();
        int iHeight = pendingUpdate.getImage().getHeight();

        canvas.drawColor(Color.BLACK);
        //TODO: add scrolling functionality to this
        if(pendingUpdate.getZoom()>0) {

            //Scale the image
            canvas.save();
            double updateCx = pendingUpdate.getCenter().getX();
            double updateCy = pendingUpdate.getCenter().getY();
            double halfCanvasWidthInImagePixels = (canvas.getWidth()/2);
            double halfCanvasHeightInImagePixels = (canvas.getHeight()/2);
            double imageTranslateX = updateCx - halfCanvasWidthInImagePixels;
            double imageTranslateY = updateCy - halfCanvasHeightInImagePixels;
            //canvas.scale(pendingUpdate.getZoom(), pendingUpdate.getZoom(), (float)pendingUpdate.getCenter().getX(), (float)pendingUpdate.getCenter().getY());

            canvas.scale(pendingUpdate.getZoom(),
                    pendingUpdate.getZoom(),
                    canvas.getWidth()/2,
                    canvas.getHeight()/2);

            canvas.translate(-(float)imageTranslateX,
                    -(float)imageTranslateY);
            canvas.drawBitmap(pendingUpdate.getImage(), 0, 0, null);

            canvas.restore();
        }else {
            //TODO: update this so it displays image scaled to screen and updates current zoom somehow
            canvas.drawBitmap(pendingUpdate.getImage(),
                    (canvas.getWidth()-iWidth)/2,
                    (canvas.getHeight()-iHeight)/2, null);
        }
        //TODO: draw other stuff on canvas here such as current location    
    }
}

Upvotes: 0

Views: 1213

Answers (1)

andr
andr

Reputation: 16064

If I were you, I'd use the Canvas.scale(float sx, float sy, float px, float py) method which does exactly what you want.

However looking at your code I think you might be messing with too many transformations at once, which is harder to debug.

  1. Always (and I mean always) call Canvas.save() and Canvas.restore() on the initial matrix you're getting in Canvas if you plan to alter it. This is because the Canvas that you get to draw on may be the canvas for e.g. the whole window with just clipping set to the boundaries of the control that is currently drawing itself.

  2. Use matrix transformation method provided by the Canvas method and draw bitmap using the simplest invocation.

Following these two advices look at the whole View I have just made up, that scales the bitmap by a factor of 3 with point (16,16) set as the pivot (unchanged point - center of scaling). Tested - working.

public class DrawingView extends View {
    Bitmap bitmap;

    public DrawingView(Context context) {
        super(context);
        bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_launcher);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        float sx = 3;
        float sy = 3;
        float px = 16;
        float py = 16;
        canvas.save();
        canvas.scale(sx, sy, px, py);
        canvas.drawBitmap(bitmap, 0, 0, null);
        canvas.restore();
    }
}

Upvotes: 1

Related Questions