Jetpack Compose: "java.lang.IllegalStateException: Software rendering doesn't support hardware bitmaps"

32 views Asked by At

I have such function

/**
 * Extends the [Modifier] to capture and save the composable content into a [Picture].
 * This function uses the [drawWithCache] to intercept the drawing process. It redirects
 * the drawing commands into a [Picture], which can be used later for operations like
 * creating a bitmap or drawing onto a canvas. This is particularly useful for capturing
 * a snapshot of the composable content without needing to display it on the screen.
 *
 * @param picture The [Picture] instance where the composable content will be recorded.
 *                It acts as a recording canvas to capture the drawing commands.
 *                Make sure to initialize this [Picture] before passing it to the modifier.
 *                The content captured by this [Picture] can be used for various purposes,
 *                such as generating a bitmap for saving or sharing the content.
 *
 * @param keys Optional array of keys that trigger recomposition and re-capture of the content
 *             when any of them changes. This is useful when you want to capture the content
 *             again due to changes in the state or data that affects the appearance or
 *             composition of the captured content. The capture process will be reinvoked
 *             upon any change in the values of these keys, ensuring the captured [Picture]
 *             is up to date. This mechanism is similar to the way keys are used in
 *             [LaunchedEffect] for controlling effects based on state changes.
 *
 * Example usage:
 * ```
 * val picture = remember { Picture() }
 *
 * Box(modifier = Modifier.captureDrawableContent(picture)) {
 *     Text("Hello, World!")
 * }
 * ```
 *
 * Note: The capturing happens during the drawing phase of the composable. Therefore,
 * modifications to the [Picture] after the composable is drawn will not reflect in the
 * captured content. If you need to update the captured content, you should trigger a
 * recomposition with an updated [Picture] instance.
 *
 * based on https://developer.android.com/jetpack/compose/graphics/draw/modifiers#composable-to-bitmap
 */
public fun Modifier.captureDrawableContent(picture: Picture, vararg keys: Any?): Modifier = composed {
    val keyHash = keys.contentHashCode()

    remember(keyHash) {
        Modifier.drawWithCache {
            val width = size.width.toInt()
            val height = size.height.toInt()

            onDrawWithContent {
                val pictureCanvas = Canvas(picture.beginRecording(width, height))
                draw(this, layoutDirection, pictureCanvas, size) {
                    [email protected]()
                }
                picture.endRecording()

                drawIntoCanvas { canvas -> canvas.nativeCanvas.drawPicture(picture) }
            }
        }
    }
}

This works well on every device except these:

enter image description here

  • devices with Android 8. I've tried to reproduce this bug on Android 7.1.1 and it was unsuccessful, there aren't this bug.

This code produce exception:

Fatal Exception: java.lang.IllegalStateException: Software rendering doesn't support hardware bitmaps

inside this block:

draw(this, layoutDirection, pictureCanvas, size) {
                    [email protected]()
                }

Do anybody know how to fix it for devices with Android 8?

I expect to find some way to draw content without Hardware Bitmaps on Android Jetpack Compose

1

There are 1 answers

0
Serhii Buvaka On

I've found the problem. In my app I use Coil lib for pictures. When I use Modifier.drawWithCache with screen which coil.compose.AsyncImage it produce error Fatal Exception: java.lang.IllegalStateException: Software rendering doesn't support hardware bitmaps.

Adding allowHardware(false) for Android 8 and below fixed this problem.

AsyncImage(
     model = ImageRequest.Builder(LocalContext.current)
         .data(iconUrl)
         .allowHardware(Build.VERSION.SDK_INT > Build.VERSION_CODES.O)
         .build(),
     contentDescription = null
)