Bitmap
Uri
Android Development
Image Processing
Android Programming

How to get Bitmap from an Uri?

ML System Design practice on Codemia

Design recommenders, ranking systems and training pipelines the way ML interviews actually ask for them, with worked solutions.

Practice ML system design

Introduction

Loading an image from a Uri into a Bitmap is one of the most common tasks in Android development. Whether the user picks a photo from the gallery, your app receives a content URI from another app, or you download an image and store it locally, you will eventually need to decode that URI into pixel data you can display or manipulate. Android offers several APIs for this, and the right choice depends on your minimum API level and whether you need fine-grained control over decoding.

BitmapFactory with ContentResolver

The classic approach uses BitmapFactory.decodeStream() together with ContentResolver.openInputStream(). This works on all API levels and gives you direct access to the raw bitmap.

kotlin
1fun getBitmapFromUri(context: Context, uri: Uri): Bitmap? {
2    return try {
3        val inputStream = context.contentResolver.openInputStream(uri)
4        val bitmap = BitmapFactory.decodeStream(inputStream)
5        inputStream?.close()
6        bitmap
7    } catch (e: IOException) {
8        e.printStackTrace()
9        null
10    }
11}

For large images, decoding the full resolution into memory can cause an OutOfMemoryError. You can sample down the image by reading its dimensions first and then setting inSampleSize:

kotlin
1fun getScaledBitmap(context: Context, uri: Uri, reqWidth: Int, reqHeight: Int): Bitmap? {
2    val options = BitmapFactory.Options()
3
4    // First pass: read dimensions only
5    options.inJustDecodeBounds = true
6    context.contentResolver.openInputStream(uri)?.use {
7        BitmapFactory.decodeStream(it, null, options)
8    }
9
10    // Calculate the largest inSampleSize that keeps both dimensions
11    // greater than or equal to the requested size
12    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight)
13    options.inJustDecodeBounds = false
14
15    // Second pass: decode the sampled bitmap
16    return context.contentResolver.openInputStream(uri)?.use {
17        BitmapFactory.decodeStream(it, null, options)
18    }
19}
20
21fun calculateInSampleSize(
22    options: BitmapFactory.Options,
23    reqWidth: Int,
24    reqHeight: Int
25): Int {
26    val (height, width) = options.outHeight to options.outWidth
27    var inSampleSize = 1
28    if (height > reqHeight || width > reqWidth) {
29        val halfHeight = height / 2
30        val halfWidth = width / 2
31        while (halfHeight / inSampleSize >= reqHeight &&
32               halfWidth / inSampleSize >= reqWidth) {
33            inSampleSize *= 2
34        }
35    }
36    return inSampleSize
37}

ImageDecoder (API 28+)

Starting with Android 9 (API 28), ImageDecoder provides a modern, more capable API. It supports animated images (GIF, WebP), color space management, and post-processing in a single pipeline.

kotlin
1fun getBitmapWithImageDecoder(context: Context, uri: Uri): Bitmap {
2    val source = ImageDecoder.createSource(context.contentResolver, uri)
3    return ImageDecoder.decodeBitmap(source) { decoder, info, _ ->
4        // Scale down if the image is larger than 1024px on either axis
5        if (info.size.width > 1024 || info.size.height > 1024) {
6            decoder.setTargetSampleSize(2)
7        }
8        // Ensure a mutable bitmap if you plan to draw on it
9        decoder.isMutableRequired = true
10    }
11}

ImageDecoder also handles HEIF images natively, which BitmapFactory does not support on all devices. If your minimum SDK is 28 or higher, prefer this API.

Loading with Glide or Coil

In practice, most Android apps use an image loading library rather than decoding bitmaps manually. Glide and Coil handle caching, downsampling, lifecycle awareness, and background threading automatically.

Glide example:

kotlin
1Glide.with(context)
2    .asBitmap()
3    .load(uri)
4    .into(object : CustomTarget<Bitmap>() {
5        override fun onResourceReady(
6            resource: Bitmap,
7            transition: Transition<in Bitmap>?
8        ) {
9            // Use the bitmap here
10            imageView.setImageBitmap(resource)
11        }
12
13        override fun onLoadCleared(placeholder: Drawable?) {
14            // Clean up if needed
15        }
16    })

Coil example (Kotlin-first):

kotlin
1val request = ImageRequest.Builder(context)
2    .data(uri)
3    .target { drawable ->
4        val bitmap = (drawable as BitmapDrawable).bitmap
5        // Use the bitmap
6    }
7    .build()
8context.imageLoader.enqueue(request)

Libraries like these are the right choice for display in UI. They decode on a background thread, respect the view's dimensions for automatic downsampling, and manage a disk and memory cache so you do not decode the same image twice.

Handling Rotation with ExifInterface

Photos taken with a camera often have an EXIF orientation tag that tells you the image is rotated 90, 180, or 270 degrees. BitmapFactory ignores this tag, so the decoded bitmap may appear sideways. You need to read the orientation and apply a matrix rotation manually.

kotlin
1fun getCorrectlyOrientedBitmap(context: Context, uri: Uri): Bitmap? {
2    val bitmap = getBitmapFromUri(context, uri) ?: return null
3
4    val inputStream = context.contentResolver.openInputStream(uri) ?: return bitmap
5    val exif = ExifInterface(inputStream)
6    inputStream.close()
7
8    val orientation = exif.getAttributeInt(
9        ExifInterface.TAG_ORIENTATION,
10        ExifInterface.ORIENTATION_NORMAL
11    )
12
13    val matrix = Matrix()
14    when (orientation) {
15        ExifInterface.ORIENTATION_ROTATE_90 -> matrix.postRotate(90f)
16        ExifInterface.ORIENTATION_ROTATE_180 -> matrix.postRotate(180f)
17        ExifInterface.ORIENTATION_ROTATE_270 -> matrix.postRotate(270f)
18        ExifInterface.ORIENTATION_FLIP_HORIZONTAL -> matrix.preScale(-1f, 1f)
19        ExifInterface.ORIENTATION_FLIP_VERTICAL -> matrix.preScale(1f, -1f)
20    }
21
22    return Bitmap.createBitmap(
23        bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true
24    )
25}

Note that ImageDecoder and libraries like Glide handle EXIF orientation automatically, so you only need this fix when using BitmapFactory directly.

Common Pitfalls

  • Decoding on the main thread: BitmapFactory.decodeStream() performs disk I/O and can block the UI. Always decode on a background thread using coroutines, AsyncTask, or a library.
  • Not closing the InputStream: Failing to close the stream returned by openInputStream() leaks file descriptors. Use Kotlin's .use {} extension or a try-finally block.
  • Ignoring inSampleSize for large images: A 12-megapixel photo decoded at full resolution consumes roughly 48 MB of heap. Without downsampling, loading a few images will crash the app with OutOfMemoryError.
  • Forgetting EXIF rotation with BitmapFactory: The bitmap decodes successfully but displays rotated. Users see sideways photos and assume the app is broken. Always check EXIF orientation when using BitmapFactory.
  • Using MediaStore.Images.Media.getBitmap() (deprecated): This convenience method was deprecated in API 29 because it decodes at full resolution with no sampling control. Replace it with ImageDecoder or manual BitmapFactory decoding.

Summary

  • BitmapFactory.decodeStream() with ContentResolver is the universal approach that works on all API levels. Use inSampleSize to avoid out-of-memory errors.
  • ImageDecoder (API 28+) is the modern replacement with built-in support for animated formats, HEIF, and automatic EXIF handling.
  • Glide and Coil handle caching, threading, and downsampling automatically and are the best choice for displaying images in UI.
  • ExifInterface is required when using BitmapFactory to correct photo rotation; ImageDecoder and image libraries handle this for you.
  • Always decode images on a background thread and close input streams to avoid UI freezes and resource leaks.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

ML System Design practice on Codemia

Design recommenders, ranking systems and training pipelines the way ML interviews actually ask for them, with worked solutions.

Practice ML system design

All Rights Reserved.