Android image view matrix scale + translate
Asked Answered
C

4

37

I am trying to manually get an image inside an imageview centered and fitting the screen. I need to do it with a matrix (I will later dynamically change the matrix transformation).

Problem is I can't get the image centered in the view (scale is appropriate). Here is the code:

// Compute the scale to choose (this works)
float scaleX = (float) displayWidth / (float) imageWidth;
float scaleY = (float) displayHeight / (float) imageHeight;
float minScale = Math.min(scaleX, scaleY);

// tx, ty should be the translation to take the image back to the screen center
float tx = Math.max(0, 
        0.5f * ((float) displayWidth - (minScale * imageWidth)));
float ty = Math.max(0, 
        0.5f * ((float) displayHeight - (minScale * imageHeight)));

// Compute the matrix
Matrix m = new Matrix();
m.reset();

// Middle of the image should be the scale pivot
m.postScale(minScale, imageWidth/2, imageHeight/2);

// Translate
m.postTranslate(tx, ty);

imageView.setImageMatrix(m);

The above code works if I don't center the scale on the image center (but I will need to do it later so I need to figure out the formula now).

I thought doing the following would correct the issue, but the image is still offset (towards bottom and right).

tx += 0.5*imageWidth*minScale;
ty += 0.5*imageHeight*minScale;

Some values I have: - image: 200x133 - display: 800x480 - minScale: 2.4 - final top-left corner of the image: 100, 67 (should be 17, 0)

Cotoneaster answered 20/5, 2011 at 17:8 Comment(1)
Hello, I am having the same kind of requirement. can you please share me the sample here. That would save my time.Monkhood
T
75

There's a convenient method called Matrix.setRectToRect(RectF, RectF, ScaleToFit) to help you here.

Matrix m = imageView.getImageMatrix();
RectF drawableRect = new RectF(0, 0, imageWidth, imageHeight);
RectF viewRect = new RectF(0, 0, imageView.getWidth(), imageView.getHeight());
m.setRectToRect(drawableRect, viewRect, Matrix.ScaleToFit.CENTER);
imageView.setImageMatrix(m);

That should set the matrix m to have combo of scaling and translate values that is needed to show the drawable centered and fit within the ImageView widget.

Trihedron answered 30/5, 2011 at 4:15 Comment(8)
Thanks, I had solved the problem in the meantime by a combination of matrices. +1 for making me notice that useful function.Cotoneaster
Care to share your solution? I have a corner case where a combination of matrices could prove useful.Trihedron
@joakime image doesn't show. Then, when I tap the image once, it will show but not centered and fitted against its view. I am using TouchImageView (source code here github.com/gabu/AndroidSDK-RecipeBook/blob/master/Recipe060/src/…)Boiler
@joakime, what do you replace "imageWidth" and "imageHeight" with (line 2 in your comment above)? I'm having the same exact problem right now but I'm stuck on this part. Thanks for your help!Belvia
Careful with the above, getImageMatrix() docs state "Do not change this matrix in place. If you want a different matrix applied to the drawable, be sure to call setImageMatrix()". However setImageMatrix() does a check and will not invalidate the view if the local matrix, mMatrix.equals(the matrix passed in). Hence I would recommend: Matrix m = new Matrix(); m.reset(); over getImageMatrix() in this instance.Lubric
hey how can i use this in scalling bitmap draw in center of canvas on surfaceviewStephi
how can i use scaletofit after image rotation? i have tried your code but it removes my rotation effectWhenas
Very useful for me. ThanksSqueegee
C
5

Here is how I solved my problem using matrices (requested by joakime in the other answer):

private void setImageTransformation(float tx, float ty, float scale) {
    savedMatrix.reset();
    savedMatrix.postTranslate(-imageWidth / 2f, -imageHeight / 2f);
    savedMatrix.postScale(scale, scale);
    savedMatrix.postTranslate(tx, ty);
    imageView.setImageMatrix(savedMatrix);
}

public void resetImageMatrix() {
    if (!isImageLoaded()) return;

    imageWidth = imageView.getDrawable().getIntrinsicWidth();
    imageHeight = imageView.getDrawable().getIntrinsicHeight();

    float scaleX = (float) displayWidth / (float) imageWidth;
    float scaleY = (float) displayHeight / (float) imageHeight;
    minScale = Math.min(scaleX, scaleY);
    maxScale = 2.5f * minScale;

    initialTranslation.set(
              Math.max(0, 
                minScale * imageWidth / 2f 
                + 0.5f * (displayWidth - (minScale * imageWidth))), 
              Math.max(0, 
                minScale * imageHeight / 2f
                + 0.5f * (displayHeight - (minScale * imageHeight))));

    currentScale = minScale;
    currentTranslation.set(initialTranslation);
    initialImageRect.set(0, 0, imageWidth, imageHeight);

    setImageTransformation(initialTranslation.x, initialTranslation.y, 
                minScale);
}

I am cheating here a bit because the pinch is not really centered between the user fingers, which is acceptable in my case.

Cotoneaster answered 30/5, 2011 at 15:38 Comment(2)
what is initialTranslation, savedMatrix, displayWidth, displayHeight?Boiler
initialTranslation and savedMatrix are just reused Matrix instances. The code doesn't depend on their state because it entirely resets it before reading. displayWidth/Height are the dimensions of the target box into which the image must be placed.Ciscaucasia
W
3

Or using

    m.postScale(minScale, minScale); //keep aspect ratio

    float tx = (getWidth() - bitmap1.getWidth()* scale) * 0.5f ;
    float ty = (getHeight() - bitmap1.getHeight()* scale) * 0.5f ;

    matrix.postTranslate(tx, ty );

where getWidth is the width of your imageView. Works perfectly for me

Wakeful answered 12/1, 2017 at 2:33 Comment(0)
P
1

Hey I was having the same issue, and you were oh so close! It was an order of operations issue, at least for me. The following worked for me:

tx = (width - imgWidth) * 0.5f * scale;
Paronymous answered 21/7, 2012 at 0:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.