immich-app/immich · error · IOException
could not convert bitmap to ARGB_8888
Error message
could not convert bitmap to ARGB_8888
What it means
Bitmap.toArgb8888() converts a bitmap to ARGB_8888 (required by the native rotate code, which locks an 8888 buffer). It recycles the source bitmap and returns the converted copy; if Bitmap.copy returns null (allocation failure), it throws IOException 'could not convert bitmap to ARGB_8888'.
Solutions
- Retry the operation after freeing memory — recycle other bitmaps and reduce concurrent image jobs.
- Downscale the image (sample size / inSampleSize) before conversion so the 8888 copy fits in memory.
- Catch the IOException and mark the asset as failed instead of crashing the thumbnail pipeline.
Example fix
// before val converted = copy(Bitmap.Config.ARGB_8888, false) // after (caller-side guard) opts.inSampleSize = computeSampleSize(bounds, targetPx) val bmp = decodeBoundsAware(opts) // smaller bitmap before toArgb8888()
Defensive patterns
Strategy: try-catch
Validate before calling
// Kotlin: check dimensions/heap before conversion if (bitmap.width.toLong() * bitmap.height > 40_000_000L) downsampleFirst()
Try / catch
try {
val argb = bitmap.toArgb8888()
} catch (e: IOException) {
logger.warn("conversion failed, retrying downscaled", e)
processDownscaled(bitmap)
} Prevention
- Cap concurrent bitmap jobs on low-memory devices.
- Use inSampleSize to decode at target resolution.
- Recycle intermediates promptly (as the code already does).
When it happens
Trigger: Bitmap.copy(CONFIG_ARGB_8888, false) returning null, which happens on allocation failure for very large images or under heavy memory pressure in the process.
Common situations: Processing huge RAW/megapixel photos on low-memory devices; many concurrent thumbnail jobs exhausting the Java heap; greedy allocation after other bitmap work hasn't been recycled.
Understand the failure class
Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.
Related errors
- native rotate failed for orientation $orientation
- Could not read image data for $assetId
- Cannot open original stream for asset $assetId
- Cannot unlink Android motion photos
- EOFException
AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15).
Data as JSON: /api/errors/3e697d95c3660956.
Report an issue: GitHub.
Appendix: source
Thrown at mobile/android/app/src/main/kotlin/app/alextran/immich/images/RawOrientation.kt:49
for (i in 0 until entries) {
val p = start + 2 + i * 12
if (p + 12 > len) return ExifInterface.ORIENTATION_NORMAL
if ((buffer.getShort(p).toInt() and 0xFFFF) == 0x0112) {
val value = buffer.getShort(p + 8).toInt() and 0xFFFF
return if (value in 1..8) value else ExifInterface.ORIENTATION_NORMAL
}
}
return ExifInterface.ORIENTATION_NORMAL
} catch (_: Exception) {
return ExifInterface.ORIENTATION_NORMAL
}
}
fun Bitmap.toArgb8888(): Bitmap {
if (config == Bitmap.Config.ARGB_8888) return this
val converted = copy(Bitmap.Config.ARGB_8888, false)
recycle()
return converted ?: throw IOException("could not convert bitmap to ARGB_8888")
}
// Force ARGB_8888 first: the native rotate needs a lockable 8888 buffer and allocates w*h*4.
fun rotateToNativeBuffer(bitmap: Bitmap, orientation: Int): Map<String, Long> {
val src = bitmap.toArgb8888()
try {
val info = IntArray(3)
val pointer = NativeImage.rotate(src, orientation, info)
if (pointer == 0L) throw IOException("native rotate failed for orientation $orientation")
return mapOf(
"pointer" to pointer,
"width" to info[0].toLong(),
"height" to info[1].toLong(),
"rowBytes" to info[2].toLong()
)
} finally {
if (!src.isRecycled) src.recycle()
}View on GitHub (pinned to e55ac299a4)