Scaled Bitmap maintaining aspect ratio

AndroidBitmapScaling

Android Problem Overview


I would like to scale a Bitmap to a runtime dependant width and height, where the aspect ratio is maintained and the Bitmap fills the entire width and centers the image vertically, either cropping the excess or filling in the gap with 0 alpha pixels.

I'm currently redrawing the bitmap myself by creating a Bitmap of all 0 alpha pixels and drawing the image Bitmap on top of it, scaling to the exact specified width and maintaining the aspect ratio, however, it ends up losing/screwing up the pixel data.

Here is how I'm doing it:

Bitmap background = Bitmap.createBitmap((int)width, (int)height, Config.ARGB_8888);
float originalWidth = originalImage.getWidth(), originalHeight = originalImage.getHeight();
Canvas canvas = new Canvas(background);
float scale = width/originalWidth;
float xTranslation = 0.0f, yTranslation = (height - originalHeight * scale)/2.0f;
Matrix transformation = new Matrix();
transformation.postTranslate(xTranslation, yTranslation);
transformation.preScale(scale, scale);
canvas.drawBitmap(originalImage, transformation, null);
return background;

Is there a library out there or some better code that can do this better? I would like the image to look as crisp as possible, but I knew that my function wouldn't provide a great result.

I know I could have the image stay fine by using integer scaling, instead of float scaling, but I need the width to be 100% filled.

Also, I know about an ImageView's Gravity.CENTER_CROP capability, however, that also uses integer scaling, so it cuts off the width of the image when it shouldn't.

Android Solutions


Solution 1 - Android

This will respect maxWidth and maxHeight, which means the resulting bitmap will never have dimensions larger then those:

 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 > ratioBitmap) {
            finalWidth = (int) ((float)maxHeight * ratioBitmap);
        } else {
            finalHeight = (int) ((float)maxWidth / ratioBitmap);
        }
        image = Bitmap.createScaledBitmap(image, finalWidth, finalHeight, true);
        return image;
    } else {
        return image;
    }
}

Solution 2 - Android

What about this:

Bitmap background = Bitmap.createBitmap((int)width, (int)height, Config.ARGB_8888);

float originalWidth = originalImage.getWidth(); 
float originalHeight = originalImage.getHeight();

Canvas canvas = new Canvas(background);

float scale = width / originalWidth;

float xTranslation = 0.0f;
float yTranslation = (height - originalHeight * scale) / 2.0f;

Matrix transformation = new Matrix();
transformation.postTranslate(xTranslation, yTranslation);
transformation.preScale(scale, scale);

Paint paint = new Paint();
paint.setFilterBitmap(true);

canvas.drawBitmap(originalImage, transformation, paint);

return background;

I added a paint to filter the scaled bitmap.

Solution 3 - Android

here is a method from my Utils class, that does the job:

public static Bitmap scaleBitmapAndKeepRation(Bitmap targetBmp,int reqHeightInPixels,int reqWidthInPixels)
    {
        Matrix matrix = new Matrix();
        matrix .setRectToRect(new RectF(0, 0, targetBmp.getWidth(), targetBmp.getHeight()), new RectF(0, 0, reqWidthInPixels, reqHeightInPixels), Matrix.ScaleToFit.CENTER);
        Bitmap scaledBitmap = Bitmap.createBitmap(targetBmp, 0, 0, targetBmp.getWidth(), targetBmp.getHeight(), matrix, true);
        return scaledBitmap;
    }

Solution 4 - Android

Here I have a tested solution where I create a scaled Bitmap out of a bitmap file:

    int scaleSize =1024;

    public Bitmap resizeImageForImageView(Bitmap bitmap) {
        Bitmap resizedBitmap = null;
        int originalWidth = bitmap.getWidth();
        int originalHeight = bitmap.getHeight();
        int newWidth = -1;
        int newHeight = -1;
        float multFactor = -1.0F;
        if(originalHeight > originalWidth) {
            newHeight = scaleSize ;
            multFactor = (float) originalWidth/(float) originalHeight;
            newWidth = (int) (newHeight*multFactor);
        } else if(originalWidth > originalHeight) {
            newWidth = scaleSize ;
            multFactor = (float) originalHeight/ (float)originalWidth;
            newHeight = (int) (newWidth*multFactor);
        } else if(originalHeight == originalWidth) {
            newHeight = scaleSize ;
            newWidth = scaleSize ;
        }
        resizedBitmap = Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, false);
        return resizedBitmap;
    }

Notice that I need scaled Bitmaps which have a maximum size of 4096x4096 Pixels but the aspect ratio needs to be kept while resizing. If you need other values for width or height just replace the values "4096".

This is just an addition to the answer of Coen but the problem in his code is the line where he calculates the ratio. Dividing two Integers gives an Integer and if the result is < 1 it will be rounded to 0. So this throws the "divide by zero" exception.

Solution 5 - Android

None of the above answers were worked for me and I just created a method which sets all of the dimensions into the desired ones with painting the empty area to black. Here is my method:

/**
 * Scale the image preserving the ratio
 * @param imageToScale Image to be scaled
 * @param destinationWidth Destination width after scaling
 * @param destinationHeight Destination height after scaling
 * @return New scaled bitmap preserving the ratio
 */
public static Bitmap scalePreserveRatio(Bitmap imageToScale, int destinationWidth,
        int destinationHeight) {
    if (destinationHeight > 0 && destinationWidth > 0 && imageToScale != null) {
        int width = imageToScale.getWidth();
        int height = imageToScale.getHeight();

        //Calculate the max changing amount and decide which dimension to use
        float widthRatio = (float) destinationWidth / (float) width;
        float heightRatio = (float) destinationHeight / (float) height;

        //Use the ratio that will fit the image into the desired sizes
        int finalWidth = (int)Math.floor(width * widthRatio);
        int finalHeight = (int)Math.floor(height * widthRatio);
        if (finalWidth > destinationWidth || finalHeight > destinationHeight) {
            finalWidth = (int)Math.floor(width * heightRatio);
            finalHeight = (int)Math.floor(height * heightRatio);
        }

        //Scale given bitmap to fit into the desired area
        imageToScale = Bitmap.createScaledBitmap(imageToScale, finalWidth, finalHeight, true);

        //Created a bitmap with desired sizes
        Bitmap scaledImage = Bitmap.createBitmap(destinationWidth, destinationHeight, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(scaledImage);

        //Draw background color
        Paint paint = new Paint();
        paint.setColor(Color.BLACK);
        paint.setStyle(Paint.Style.FILL);
        canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), paint);

        //Calculate the ratios and decide which part will have empty areas (width or height)
        float ratioBitmap = (float)finalWidth / (float)finalHeight;
        float destinationRatio = (float) destinationWidth / (float) destinationHeight;
        float left = ratioBitmap >= destinationRatio ? 0 : (float)(destinationWidth - finalWidth) / 2;
        float top = ratioBitmap < destinationRatio ? 0: (float)(destinationHeight - finalHeight) / 2;
        canvas.drawBitmap(imageToScale, left, top, null);

        return scaledImage;
    } else {
        return imageToScale;
    }
}

For example;

Let's say you have an image as 100 x 100 but the desired size is 300x50, then this method will convert your image to 50 x 50 and paint it into a new image which has dimensions as 300 x 50 (and empty fileds will be black).

Another example: let's say you have an image as 600 x 1000 and the desired sizes are 300 x 50 again, then your image will be converted into 30 x 50 and painted into a newly created image which has sizes as 300 x 50.

I think this is what it must be, Rs.

Solution 6 - Android

simpler solution : note we set the width to 500 pixels

 public void scaleImageKeepAspectRatio()
    {
        int imageWidth = scaledGalleryBitmap.getWidth();
        int imageHeight = scaledGalleryBitmap.getHeight();
        int newHeight = (imageHeight * 500)/imageWidth;
        scaledGalleryBitmap = Bitmap.createScaledBitmap(scaledGalleryBitmap, 500, newHeight, false);

    }

Solution 7 - Android

It can also be done by calculating the ratio yourself, like this.

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
		int ratio = width / maxWidth;
		width = maxWidth;
		height = height / ratio;
	} else if (height > width) {
		// portrait
		int ratio = height / maxHeight;
		height = maxHeight;
		width = 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;
}

Solution 8 - Android

Added RESIZE_CROP to Gowrav's answer.

   enum RequestSizeOptions {
    RESIZE_FIT,
    RESIZE_INSIDE,
    RESIZE_EXACT,
    RESIZE_CENTRE_CROP
}
static Bitmap resizeBitmap(Bitmap bitmap, int reqWidth, int reqHeight, RequestSizeOptions options) {
    try {
        if (reqWidth > 0 && reqHeight > 0 && (options == RequestSizeOptions.RESIZE_FIT ||
                options == RequestSizeOptions.RESIZE_INSIDE ||
                options == RequestSizeOptions.RESIZE_EXACT || options == RequestSizeOptions.RESIZE_CENTRE_CROP)) {

            Bitmap resized = null;
            if (options == RequestSizeOptions.RESIZE_EXACT) {
                resized = Bitmap.createScaledBitmap(bitmap, reqWidth, reqHeight, false);
            } else {
                int width = bitmap.getWidth();
                int height = bitmap.getHeight();
                float scale = Math.max(width / (float) reqWidth, height / (float) reqHeight);
                if (scale > 1 || options == RequestSizeOptions.RESIZE_FIT) {
                    resized = Bitmap.createScaledBitmap(bitmap, (int) (width / scale), (int) (height / scale), false);
                }
                if (scale > 1 || options == RequestSizeOptions.RESIZE_CENTRE_CROP) {
                    int smaller_side = (height-width)>0?width:height;
                    int half_smaller_side = smaller_side/2;
                    Rect initialRect = new Rect(0,0,width,height);
                    Rect finalRect = new Rect(initialRect.centerX()-half_smaller_side,initialRect.centerY()-half_smaller_side,
                            initialRect.centerX()+half_smaller_side,initialRect.centerY()+half_smaller_side);
                    bitmap = Bitmap.createBitmap(bitmap,  finalRect.left, finalRect.top, finalRect.width(), finalRect.height(), null, true);
                    //keep in mind we have square as request for cropping, otherwise - it is useless
                    resized = Bitmap.createScaledBitmap(bitmap, reqWidth, reqHeight, false);
                }

            }
            if (resized != null) {
                if (resized != bitmap) {
                    bitmap.recycle();
                }
                return resized;
            }
        }
    } catch (Exception e) {
        Log.w("AIC", "Failed to resize cropped image, return bitmap before resize", e);
    }
    return bitmap;
}

Solution 9 - Android

Kotlin extension function version based on joaomgcd's answer

private fun Bitmap.resize(maxWidth: Int, maxHeight: Int): Bitmap {
    return if (maxHeight > 0 && maxWidth > 0) {
        val width = this.width
        val height = this.height
        val ratioBitmap = width.toFloat() / height.toFloat()
        val ratioMax = maxWidth.toFloat() / maxHeight.toFloat()
        var finalWidth = maxWidth
        var finalHeight = maxHeight
        if (ratioMax > ratioBitmap) {
            finalWidth = (maxHeight.toFloat() * ratioBitmap).toInt()
        } else {
            finalHeight = (maxWidth.toFloat() / ratioBitmap).toInt()
        }
        Bitmap.createScaledBitmap(this, finalWidth, finalHeight, true)
    } else this
}

Solution 10 - Android

This is an awesome library from ArthurHub to handle the image crops both programmatically and interactively if you don't want to reinvent the wheel.

But if you prefer a non bloated version like me.., the internal function shown here is a rather sophisticated to perform Image Scaling with few standard options

/**
 * Resize the given bitmap to the given width/height by the given option.<br>
 */

enum RequestSizeOptions {
    RESIZE_FIT,
    RESIZE_INSIDE,
    RESIZE_EXACT
}

static Bitmap resizeBitmap(Bitmap bitmap, int reqWidth, int reqHeight, RequestSizeOptions options) {
    try {
        if (reqWidth > 0 && reqHeight > 0 && (options == RequestSizeOptions.RESIZE_FIT ||
                options == RequestSizeOptions.RESIZE_INSIDE ||
                options == RequestSizeOptions.RESIZE_EXACT)) {

            Bitmap resized = null;
            if (options == RequestSizeOptions.RESIZE_EXACT) {
                resized = Bitmap.createScaledBitmap(bitmap, reqWidth, reqHeight, false);
            } else {
                int width = bitmap.getWidth();
                int height = bitmap.getHeight();
                float scale = Math.max(width / (float) reqWidth, height / (float) reqHeight);
                if (scale > 1 || options == RequestSizeOptions.RESIZE_FIT) {
                    resized = Bitmap.createScaledBitmap(bitmap, (int) (width / scale), (int) (height / scale), false);
                }
            }
            if (resized != null) {
                if (resized != bitmap) {
                    bitmap.recycle();
                }
                return resized;
            }
        }
    } catch (Exception e) {
        Log.w("AIC", "Failed to resize cropped image, return bitmap before resize", e);
    }
    return bitmap;
}

Solution 11 - Android

public static Bitmap scaleBitmap(Bitmap bitmap, int wantedWidth, int wantedHeight) {
	float originalWidth = bitmap.getWidth();
	float originalHeight = bitmap.getHeight();
    Bitmap output = Bitmap.createBitmap(wantedWidth, wantedHeight, Config.ARGB_8888);
    Canvas canvas = new Canvas(output);
    Matrix m = new Matrix();
   
    float scalex = wantedWidth/originalWidth;
    float scaley = wantedHeight/originalHeight;
	float xTranslation = 0.0f, yTranslation = (wantedHeight - originalHeight * scaley)/2.0f;
	
	m.postTranslate(xTranslation, yTranslation);
	m.preScale(scalex, scaley);
	// m.setScale((float) wantedWidth / bitmap.getWidth(), (float) wantedHeight / bitmap.getHeight());
    Paint paint = new Paint();
	paint.setFilterBitmap(true);
    canvas.drawBitmap(bitmap, m, paint);

    return output;
}

Solution 12 - Android

My solution was this, which maintains aspect ratio, and requires only one size, for example if you have a 19201080 and an 10801920 image and you want to resize it to 1280, the first will be 1280720 and the second will be 7201280

public static Bitmap resizeBitmap(final Bitmap temp, final int size) {
		if (size > 0) {
			int width = temp.getWidth();
			int height = temp.getHeight();
			float ratioBitmap = (float) width / (float) height;
			int finalWidth = size;
			int finalHeight = size;
			if (ratioBitmap < 1) {
				finalWidth = (int) ((float) size * ratioBitmap);
			} else {
				finalHeight = (int) ((float) size / ratioBitmap);
			}
			return Bitmap.createScaledBitmap(temp, finalWidth, finalHeight, true);
		} else {
			return temp;
		}
	}

Solution 13 - Android

There is simple math involved in rescaling the image, consider the following snippet and follow along,

  1. Suppose, you have Imaan image with 720x1280 and you want to to be fit in 420 width, get the percentage of reduction required by given math,

    originalWidth = 720; wP = 720/100; /* wP = 7.20 is a percentage value */

  2. Now subtract the required width from original width and then multiply the outcome by wP. You will get the percentage of width being reduced.

difference = originalWidth - 420; dP = difference/wP;

Here dP will be 41.66, means you are reducing the size 41.66%. So you have to reduce the height by 41.66(dP) to maintain the ration or scale of that image. Calculate the height as given below,

hP = originalHeight / 100;
//here height percentage will be 1280/100 = 12.80
height = originalHeight - ( hp * dP);
// here 1280 - (12.80 * 41.66) = 746.75

Here is your fitting scale, you can resize image/Bitmap in 420x747. It will return the resized image without losing the ratio/scale.

Example

public static Bitmap scaleToFit(Bitmap image, int width, int height, bool isWidthReference) {
    if (isWidthReference) {
        int originalWidth = image.getWidth();
        float wP = width / 100;
        float dP = ( originalWidth - width) / wP;
        int originalHeight = image.getHeight();
        float hP = originalHeight / 100;
        int height = originalHeight - (hP * dP);
        image = Bitmap.createScaledBitmap(image, width, height, true);
    } else {
        int originalHeight = image.getHeight();
        float hP = height / 100;
        float dP = ( originalHeight - height) / hP;
        int originalWidth = image.getWidth();
        float wP = originalWidth / 100;
        int width = originalWidth - (wP * dP);
        image = Bitmap.createScaledBitmap(image, width, height, true);
    }
    return image;
}

here you are simply scaling the image with reference of height or width parameter to fit into required criteria.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionRileyEView Question on Stackoverflow
Solution 1 - AndroidjoaomgcdView Answer on Stackoverflow
Solution 2 - AndroidStreets Of BostonView Answer on Stackoverflow
Solution 3 - AndroidGal RomView Answer on Stackoverflow
Solution 4 - AndroidChristopher ReichelView Answer on Stackoverflow
Solution 5 - AndroidBahadir TasdemirView Answer on Stackoverflow
Solution 6 - AndroidyehyattView Answer on Stackoverflow
Solution 7 - AndroidCoen DamenView Answer on Stackoverflow
Solution 8 - AndroidVodyanikov AndrewView Answer on Stackoverflow
Solution 9 - AndroidVahidView Answer on Stackoverflow
Solution 10 - AndroidGowravView Answer on Stackoverflow
Solution 11 - AndroidPradeep SodhiView Answer on Stackoverflow
Solution 12 - AndroidKiskunkView Answer on Stackoverflow
Solution 13 - AndroidKiran ManiyaView Answer on Stackoverflow