2

Loading an immutable image to canvas crashes with

java.lang.IllegalStateException: Immutable bitmap passed to Canvas constructor

both in classic Android Canvas and Compose Canvas.

Using the snippet below is the cause for crash in Jetpack Compose.

val deferredResource: DeferredResource<ImageBitmap> =
    loadImageResource(id = R.drawable.landscape2)

deferredResource.resource.resource?.let { imageBitmap ->
    val paint = Paint().apply {
        style = PaintingStyle.Stroke
        strokeWidth = 1f
        color = Color(0xffFFEE58)

    }
    Canvas(image = imageBitmap).drawRect(0f, 0f, 100f, 100f, paint)
}

Which is solved with Bitmap as can be seen here with

Bitmap workingBitmap = Bitmap.createBitmap(chosenFrame);
Bitmap mutableBitmap = workingBitmap.copy(Bitmap.Config.ARGB_8888, true);
Canvas canvas = new Canvas(mutableBitmap);

I can convert ImageBitmap to Android Bitmap using

        val bitmap = imageBitmap.asAndroidBitmap().copy(Bitmap.Config.ARGB_8888, true)

Found that it's also possible to convert Bitmap back to ImageBitmap using

 val newImageBitmap = bitmap.asImageBitmap()

And as result i get after drawing on that Bitmap with snippet below

  val canvas = Canvas(newImageBitmap)

    canvas.drawRect(0f, 0f, 200f, 200f, paint = paint)
    canvas.drawCircle(
        Offset(
            newImageBitmap.width / 2 - 75f,
            newImageBitmap.height / 2 + 75f
        ), 150.0f, paint
    )

    Image(bitmap = newImageBitmap)

Is there a less convoluted way to draw on ImageBitmap with Canvas without converting back and forth between Bitmap and ImageBitmap?

Thracian
  • 43,021
  • 16
  • 133
  • 222
  • Are you using correct `Canvas`? You should probably use `androidx.compose.foundation.Canvas` that has `onDraw` parameter. – Nikola Despotoski Jan 10 '21 at 13:00
  • I'm not using `androidx.compose.foundation.Canvas` for this one, using `fun Canvas(image: ImageBitmap): Canvas = ActualCanvas(image)`. The purpose here is to send Image as parameter, modify that Bitmap and return it with drawings on top of that bitmap. `drawImage` function of `androidx.compose.foundation.Canvas` draws an image only. – Thracian Jan 10 '21 at 13:04

1 Answers1

1

loadImageResource() is using AndroidImageBitmap implementation with Bitmap.decodeResource(resources, drawableId) which requests calling it without options.

This is probably limitation of the compose. You'll probably need to write your own loadingImageResource() that will call your own ImageBitmap implementation with mutable Bitmap.

fun imageFromResource(res: Resources, resId: Int): ImageBitmap {
    return MutableAndroidImageBitmap(BitmapFactory.decodeResource(res, resId, BitmapFactory.Options().apply { inMutable = true }))
}
class MutableAndroidImageBitmap(internal val bitmap: Bitmap) : ImageBitmap 

Note that drawaing of this will fail since, conversion asAndroidBitmap() checks for implementation of ImageBitmap when drawing the ImageBitmap to the foundation Canvas.

I guess you should stick with the steps you have stated in the question. asImageBitmap() does not convert ImageBitmap to Bitmap it just return the wrapped internal property. Converting Bitmap to ImageBitmap does reading the of the pixel data and creates copy of it.

suspend fun ImageBitmap.mutate(context: CoroutineContext = EmptyCoroutineContext, config: Bitmap.Config) = withContext(context) {
    val workingBitmap = asAndroidBitmap() //this is just access to `bitmap` property
    val mutableBitmap = workingBitmap.copy(config, true)
    workingBitmap.recycle()
    mutableBitmap.asImageBitmap()
}

Opened bug on issue tracker https://issuetracker.google.com/issues/177129056

Nikola Despotoski
  • 49,966
  • 15
  • 119
  • 148