{"record":{"id":"8bc789329d3d2ece","repo":"immich-app/immich","slug":"native-rotate-failed-for-orientation-orientation","errorCode":null,"errorMessage":"native rotate failed for orientation $orientation","messagePattern":"native rotate failed for orientation \\$orientation","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"mobile/android/app/src/main/kotlin/app/alextran/immich/images/RawOrientation.kt","lineNumber":58,"sourceCode":"  } catch (_: Exception) {\n    return ExifInterface.ORIENTATION_NORMAL\n  }\n}\n\nfun Bitmap.toArgb8888(): Bitmap {\n  if (config == Bitmap.Config.ARGB_8888) return this\n  val converted = copy(Bitmap.Config.ARGB_8888, false)\n  recycle()\n  return converted ?: throw IOException(\"could not convert bitmap to ARGB_8888\")\n}\n\n// Force ARGB_8888 first: the native rotate needs a lockable 8888 buffer and allocates w*h*4.\nfun rotateToNativeBuffer(bitmap: Bitmap, orientation: Int): Map<String, Long> {\n  val src = bitmap.toArgb8888()\n  try {\n    val info = IntArray(3)\n    val pointer = NativeImage.rotate(src, orientation, info)\n    if (pointer == 0L) throw IOException(\"native rotate failed for orientation $orientation\")\n    return mapOf(\n      \"pointer\" to pointer,\n      \"width\" to info[0].toLong(),\n      \"height\" to info[1].toLong(),\n      \"rowBytes\" to info[2].toLong()\n    )\n  } finally {\n    if (!src.isRecycled) src.recycle()\n  }\n}\n","sourceCodeStart":40,"sourceCodeEnd":69,"githubUrl":"https://github.com/immich-app/immich/blob/e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c/mobile/android/app/src/main/kotlin/app/alextran/immich/images/RawOrientation.kt#L40-L69","documentation":"rotateToNativeBuffer calls NativeImage.rotate to rotate a bitmap natively for a given EXIF orientation. A returned pointer of 0 signals native allocation/rotation failure, surfaced as IOException 'native rotate failed for orientation $orientation'.","triggerScenarios":"Native rotate returning nullptr because the ARGB_8888 buffer could not be locked or w*h*4 bytes could not be allocated (huge image, memory pressure), or an unsupported orientation value.","commonSituations":"Very large RAW photos on memory-constrained devices; corrupted orientation metadata; multiple simultaneous rotations exhausting native heap.","solutions":["Downscale the bitmap before rotation so w*h*4 native allocation succeeds.","Serialize/limit concurrent native rotations to reduce peak native memory.","Catch the IOException and fall back to a non-rotated (or Matrix-based) rendering for the asset.","Validate the orientation value against supported EXIF orientations before calling."],"exampleFix":"// before\nval out = rotateToNativeBuffer(bitmap, orientation)\n// after\nval out = try {\n  rotateToNativeBuffer(bitmap, orientation)\n} catch (e: IOException) {\n  fallbackWithoutRotation(bitmap, orientation) // or downscaled retry\n}","handlingStrategy":"try-catch","validationCode":"// Kotlin: preflight native allocation size\nval needed = bitmap.width.toLong() * bitmap.height * 4\nif (needed > MAX_NATIVE_ALLOC) downsampleFirst()","typeGuard":null,"tryCatchPattern":"try {\n  val meta = rotateToNativeBuffer(bitmap, orientation)\n} catch (e: IOException) {\n  fallbackToMatrixRotate(bitmap, orientation)\n}","preventionTips":["Downscale huge images before native rotation.","Limit concurrent native image operations.","Sanity-check orientation values before calling native code."],"tags":["android","jni","image","memory","exif"],"backgroundTag":"internal-invariant-violation","analyzedSha":"e55ac299a4ec7cb372e35dbf2c6c05ee9ce77f6c","analyzedAt":"2026-09-15T07:20:19.675Z","contentChangedAt":"2026-09-15T07:20:19.675Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}