Android
Bitmap
Image Resizing
Scaled Output
Android Development

Resize a large bitmap file to scaled output file on Android

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Resizing large bitmaps on Android is mostly a memory-management problem, not just an image-processing problem. Loading a full-resolution image into memory and scaling it afterward can trigger OutOfMemoryError on older or lower-memory devices. The safe pattern is decode bounds first, choose a sample size, then decode and optionally write a scaled output file.

Decode Dimensions Without Loading the Full Bitmap

Android gives you a way to inspect image size before decoding pixel data. Use inJustDecodeBounds to read metadata only.

kotlin
1import android.graphics.BitmapFactory
2
3val options = BitmapFactory.Options().apply {
4    inJustDecodeBounds = true
5}
6
7BitmapFactory.decodeFile(inputPath, options)
8
9val srcWidth = options.outWidth
10val srcHeight = options.outHeight
11println("source size = ${srcWidth}x${srcHeight}")

This is essential for large images because it avoids allocating the full bitmap just to discover dimensions.

Compute a Reasonable Sample Size

inSampleSize reduces decode size by powers of two. It is the first line of defense against memory blowups.

kotlin
1fun calculateInSampleSize(
2    options: BitmapFactory.Options,
3    reqWidth: Int,
4    reqHeight: Int
5): Int {
6    val height = options.outHeight
7    val width = options.outWidth
8    var inSampleSize = 1
9
10    while (height / inSampleSize > reqHeight || width / inSampleSize > reqWidth) {
11        inSampleSize *= 2
12    }
13
14    return inSampleSize
15}

This does not guarantee exact output size, but it makes decoding much safer.

In other words, inSampleSize is about decoding economically, not about producing the final exact dimensions. Exact sizing usually comes in the next scaling step.

Decode a Smaller Bitmap and Scale Precisely

After choosing sample size, decode the bitmap and then scale to the exact target dimensions if needed.

kotlin
1import android.graphics.Bitmap
2import android.graphics.BitmapFactory
3
4val bounds = BitmapFactory.Options().apply {
5    inJustDecodeBounds = true
6}
7BitmapFactory.decodeFile(inputPath, bounds)
8
9val decodeOptions = BitmapFactory.Options().apply {
10    inSampleSize = calculateInSampleSize(bounds, 1080, 1080)
11}
12
13val decoded = BitmapFactory.decodeFile(inputPath, decodeOptions)
14val scaled = Bitmap.createScaledBitmap(decoded, 1080, 1080, true)

This two-step process balances memory safety and output control.

Write the Scaled Bitmap to an Output File

Once you have the scaled bitmap, compress and write it to disk.

kotlin
1import java.io.File
2import java.io.FileOutputStream
3
4fun writeJpeg(bitmap: Bitmap, outputFile: File, quality: Int = 90) {
5    FileOutputStream(outputFile).use { stream ->
6        bitmap.compress(Bitmap.CompressFormat.JPEG, quality, stream)
7        stream.flush()
8    }
9}

Compression quality affects file size and visual fidelity. For photos, 85 to 92 is often a practical range.

Keep Aspect Ratio Intentionally

Blindly forcing width and height can distort the image. Often you want to scale to fit within a bounding box while preserving aspect ratio.

kotlin
1fun scaledSize(srcWidth: Int, srcHeight: Int, maxWidth: Int, maxHeight: Int): Pair<Int, Int> {
2    val ratio = minOf(maxWidth.toFloat() / srcWidth, maxHeight.toFloat() / srcHeight)
3    return Pair((srcWidth * ratio).toInt(), (srcHeight * ratio).toInt())
4}

Use this before createScaledBitmap when preserving proportions matters.

Avoid UI Thread Work

Large bitmap decode and file write operations should not happen on the main thread. Run them in a background coroutine or worker thread.

kotlin
1import kotlinx.coroutines.Dispatchers
2import kotlinx.coroutines.withContext
3
4suspend fun resizeOffMainThread(block: () -> Unit) {
5    withContext(Dispatchers.IO) {
6        block()
7    }
8}

Heavy image work on the UI thread causes dropped frames and application-not-responding behavior.

Clean Up Memory When Needed

On modern Android, you generally rely on garbage collection, but large temporary bitmaps still deserve lifecycle attention. Drop references as soon as you are done with them and avoid holding multiple large versions in memory at once.

If the pipeline supports it, decode, scale, write, and release in one narrow scope rather than keeping the bitmap around for unrelated UI work.

That becomes especially important when processing several images in sequence, because one seemingly small leak can multiply into repeated bitmap pressure.

Common Pitfalls

  • Decoding the full original image before checking dimensions.
  • Setting inSampleSize too late, after the expensive decode already happened.
  • Forcing a fixed width and height and distorting the aspect ratio.
  • Running large decode or compression work on the main thread.
  • Writing huge PNG output when JPEG would be smaller and acceptable.

Summary

  • Read image bounds first before loading bitmap pixels.
  • Use inSampleSize to reduce memory pressure during decode.
  • Scale precisely only after a safe decode step.
  • Preserve aspect ratio unless distortion is intentional.
  • Write the resized bitmap from a background thread and keep the memory lifetime short.

Course illustration
Course illustration

All Rights Reserved.