15

I'm looking for a solution for the following problem: how to change the size of a Bitmapto a fixed size (for example 512x128). The aspect ratio of the bitmap content must be preserved.

I think it should be something like this:

  • create an empty 512x128 bitmap

  • scale the original bitmap down to fit the 512x128 pixels with keeping the aspect ratio

  • copy the scaled into the empty bitmap (centered)

What is the simplest way to achieve this?

The reason for all this is, that the GridView messes the layout up when the aspect ratio of an image differs from the other. Here is a screenshot (all images except the last one have the aspect ratio of 4:1):

screenshot

Dark.Rider
  • 381
  • 1
  • 4
  • 17

5 Answers5

42

Try this, calculate the ratio and then rescale.

private Bitmap scaleBitmap(Bitmap bm) {
    int width = bm.getWidth();
    int height = bm.getHeight();

    Log.v("Pictures", "Width and height are " + width + "--" + height);

    if (width > height) {
        // landscape
        float ratio = (float) width / maxWidth;
        width = maxWidth;
        height = (int)(height / ratio);
    } else if (height > width) {
        // portrait
        float ratio = (float) height / maxHeight;
        height = maxHeight;
        width = (int)(width / ratio);
    } else {
        // square
        height = maxHeight;
        width = maxWidth;
    }

    Log.v("Pictures", "after scaling Width and height are " + width + "--" + height);

    bm = Bitmap.createScaledBitmap(bm, width, height, true);
    return bm;
}
Community
  • 1
  • 1
Coen Damen
  • 2,009
  • 5
  • 29
  • 51
  • 2
    plus one added for a simple solution. But I would change int ratio to float/double ratio as int/int might produce exceptions when it tries do divide by 0. – just_user Aug 05 '14 at 15:00
  • Awesome simple solution! Using floats instead of ints is a must though. The result may be wrong most of the time otherwise (e.g., ratio becomes 1 when it should be 1.4). – xip Nov 10 '14 at 20:42
  • @Hikaru you are right, wanted to do that but never got to it. Thanks for the edit and the reminder -) – Coen Damen Nov 11 '14 at 08:21
  • 1
    Third clause doesnt seem aspect ratio. dont use height = maxHeight; with width = maxWidth; Remove last clause and change else if (height > width) { to else{ – user3307005 Jan 29 '17 at 00:41
22

The answer by Coen Damen doesn't always respect Max Height and Max Width. Here's an answer that does:

 private static Bitmap resize(Bitmap image, int maxWidth, int maxHeight) {
    if (maxHeight > 0 && maxWidth > 0) {
        int width = image.getWidth();
        int height = image.getHeight();
        float ratioBitmap = (float) width / (float) height;
        float ratioMax = (float) maxWidth / (float) maxHeight;

        int finalWidth = maxWidth;
        int finalHeight = maxHeight;
        if (ratioMax > 1) {
            finalWidth = (int) ((float)maxHeight * ratioBitmap);
        } else {
            finalHeight = (int) ((float)maxWidth / ratioBitmap);
        }
        image = Bitmap.createScaledBitmap(image, finalWidth, finalHeight, true);
        return image;
    } else {
        return image;
    }
}
joaomgcd
  • 5,287
  • 4
  • 28
  • 39
  • 6
    stealing my answer is fine. of course the maxWidth and maxHeight are class variables instead of method params. – Coen Damen Jan 11 '17 at 09:36
2

I have stumbled upon the same problem a number of times in my projects and each time due to lack of time (and laziness) I would be satisfied with a less than optimum solution. But recently I found some time to crack down this particular issue. Here is my solution and I hope it helps someone down the line.

Bitmap scaleDownLargeImageWithAspectRatio(Bitmap image)
            {
                int imageVerticalAspectRatio,imageHorizontalAspectRatio;
                float bestFitScalingFactor=0;
                float percesionValue=(float) 0.2;
    
                //getAspect Ratio of Image
                int imageHeight=(int) (Math.ceil((double) image.getHeight()/100)*100);
                int imageWidth=(int) (Math.ceil((double) image.getWidth()/100)*100);
                int GCD=BigInteger.valueOf(imageHeight).gcd(BigInteger.valueOf(imageWidth)).intValue();
                imageVerticalAspectRatio=imageHeight/GCD;
                imageHorizontalAspectRatio=imageWidth/GCD;
                Log.i("scaleDownLargeImageWIthAspectRatio","Image Dimensions(W:H): "+imageWidth+":"+imageHeight);
                Log.i("scaleDownLargeImageWIthAspectRatio","Image AspectRatio(W:H): "+imageHorizontalAspectRatio+":"+imageVerticalAspectRatio);
    
                //getContainer Dimensions
                int displayWidth = getWindowManager().getDefaultDisplay().getWidth();
                int displayHeight = getWindowManager().getDefaultDisplay().getHeight();
               //I wanted to show the image to fit the entire device, as a best case. So my ccontainer dimensions were displayWidth & displayHeight. For your case, you will need to fetch container dimensions at run time or you can pass static values to these two parameters 
    
                int leftMargin = 0;
                int rightMargin = 0;
                int topMargin = 0;
                int bottomMargin = 0;
                int containerWidth = displayWidth - (leftMargin + rightMargin);
                int containerHeight = displayHeight - (topMargin + bottomMargin);
                Log.i("scaleDownLargeImageWIthAspectRatio","Container dimensions(W:H): "+containerWidth+":"+containerHeight);
    
                //iterate to get bestFitScaleFactor per constraints
                while((imageHorizontalAspectRatio*bestFitScalingFactor <= containerWidth) && 
                        (imageVerticalAspectRatio*bestFitScalingFactor<= containerHeight))
                {
                    bestFitScalingFactor+=percesionValue;
                }
    
                //return bestFit bitmap
                int bestFitHeight=(int) (imageVerticalAspectRatio*bestFitScalingFactor);
                int bestFitWidth=(int) (imageHorizontalAspectRatio*bestFitScalingFactor);
                Log.i("scaleDownLargeImageWIthAspectRatio","bestFitScalingFactor: "+bestFitScalingFactor);
                Log.i("scaleDownLargeImageWIthAspectRatio","bestFitOutPutDimesions(W:H): "+bestFitWidth+":"+bestFitHeight);
                image=Bitmap.createScaledBitmap(image, bestFitWidth,bestFitHeight, true);
    
                //Position the bitmap centre of the container
                int leftPadding=(containerWidth-image.getWidth())/2;
                int topPadding=(containerHeight-image.getHeight())/2;
                Bitmap backDrop=Bitmap.createBitmap(containerWidth, containerHeight, Bitmap.Config.RGB_565);
                Canvas can = new Canvas(backDrop);
                can.drawBitmap(image, leftPadding, topPadding, null);
    
                return backDrop;
            }
Aniruddh Parihar
  • 3,072
  • 3
  • 21
  • 39
Parth Kapoor
  • 1,494
  • 12
  • 23
1

https://developer.android.com/reference/android/graphics/Bitmap.html#createScaledBitmap(android.graphics.Bitmap, int, int, boolean)

and make sure that both the dstWidth and dstHeight are obtained from src.getWidth()*scale and src.getHeight()*scale, where scale is a value that you need to determine to make sure the scaled bitmap fits inside 512x128.

Streets Of Boston
  • 12,576
  • 2
  • 25
  • 28
  • `createScaledBitmap` doesn't solve my problem. That's what I already use. In the example the last image has been scaled that way. – Dark.Rider Feb 27 '13 at 23:32
  • If you already have a scaled down bitmap (width less than 512 or height less than 128 in your example), just set these attributes in the ImageView of your GridView item to android:layout_width="512dp" and android:layout_height="128dp" and android:scaleType="center" or android:scaleType="centerInside" – Streets Of Boston Feb 28 '13 at 00:38
  • According to my explanation the last image in the example is already scaled down, so it is already max. 128px high. Despite that, the image view displays it larger than the other images. Also I don't want an image view with a 512x128dp grid. – Dark.Rider Feb 28 '13 at 23:14
  • In your screen-shot, for the last/bottom row of 3 images: The last one is bigger (higher). Do you want to make the first two higher (to match the last one) or do you want to make the last one less high (to match the first two)? – Streets Of Boston Feb 28 '13 at 23:19
  • If you want all your GridView items to have the same height, set the layout_height of the ImageView to 128dp (or any other fixed value) and the layout_width to wrap_content. Set the scaleType to fitEnd, fitStart, fitXY or fitCenter. – Streets Of Boston Feb 28 '13 at 23:22
1

I think @Coen's answer is not right solution for this question. I also needed a method like this but I wanted to square image.

Here is my solution for square image;

public static Bitmap resizeBitmapImageForFitSquare(Bitmap image, int maxResolution) {

    if (maxResolution <= 0)
        return image;

    int width = image.getWidth();
    int height = image.getHeight();
    float ratio = (width >= height) ? (float)maxResolution/width :(float)maxResolution/height;

    int finalWidth = (int) ((float)width * ratio);
    int finalHeight = (int) ((float)height * ratio);

    image = Bitmap.createScaledBitmap(image, finalWidth, finalHeight, true);

    if (image.getWidth() == image.getHeight())
        return image;
    else {
        //fit height and width
        int left = 0;
        int top = 0;

        if(image.getWidth() != maxResolution)
            left = (maxResolution - image.getWidth()) / 2;

        if(image.getHeight() != maxResolution)
            top = (maxResolution - image.getHeight()) / 2;

        Bitmap bitmap = Bitmap.createBitmap(maxResolution, maxResolution, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        canvas.drawBitmap(image, left, top, null);
        canvas.save();
        canvas.restore();

        return  bitmap;
    }
}
Savas Adar
  • 4,083
  • 3
  • 46
  • 54