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

  1. Retry the operation after freeing memory — recycle other bitmaps and reduce concurrent image jobs.
  2. Downscale the image (sample size / inSampleSize) before conversion so the 8888 copy fits in memory.
  3. 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

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


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)