4

When using classic Views it is easy to obtain a bitmap from a view without displaying it. I create the view class through a LayoutInflater, and then, since it hasn't been attached to a view, I measure it first. I have the following extension function which measures it and draws the view on a bitmap:

fun View.toBitmap(width, height): Bitmap {
    this.measure(
        View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
        View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY),
    )
    val bitmap = Bitmap.createBitmap(this.measuredWidth, this.measuredHeight, Bitmap.Config.ARGB_8888)
    val canvas = Canvas(bitmap)
    this.layout(0, 0, this.measuredWidth, this.measuredHeight)
    this.draw(canvas)
    return bitmap
}

When using Composables I can't succeed in exporting a bitmap from a view.

I imagined something like this:

class MyComposableView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
): AbstractComposeView(context, attrs, defStyleAttr) {
    @Composable
    override fun Content() {
        MyComposable()
    }
}

What I did is instancing a MyComposableView with the application context, and then I tried to obtain a bitmap with the extension function toBitmap. The result is the following exception:

java.lang.IllegalStateException: Cannot locate windowRecomposer; View io.myapp.MyComposableView{f66ecdd V.E...... ......I. 0,0-0,0} is not attached to a window

What I can't understand is why the exception is thrown for the AbstractComposeView but is not thrown for the view obtained through the inflater.


EDIT: on 09 Apr. 2022 it seems there's not a solution other than using a classic XML layout.

Massimo
  • 3,436
  • 4
  • 40
  • 68

1 Answers1

0

You can do that without xml layout like this by flowing below steps and implement functions :

BitmapComposable(
    onBitmapped = { bitmap ->
        // Do your operation
    },
    intSize = IntSize(500, 700) // Pixel size for output bitmap
) {
    // Composable that you want to convert to a bitmap
    // This scope is @Composable
    YourComposable()
}

1 - Copy this functions :

Note that this composable will not display anything on screen !

@Composable
fun BitmapComposable(
    onBitmapped: (bitmap: Bitmap) -> Unit = { _ -> },
    backgroundColor: Color = Color.Transparent,
    dpSize : DpSize,
    composable: @Composable () -> Unit
) {
    Column(modifier = Modifier
        .size(0.dp, 0.dp)
        .verticalScroll(
            rememberScrollState(), enabled = false
        )
        .horizontalScroll(
            rememberScrollState(), enabled = false
        )) {

        Box(modifier = Modifier.size(dpSize)) {
            AndroidView(factory = {
                ComposeView(it).apply {
                    setContent {
                        Box(modifier = Modifier.background(backgroundColor).fillMaxSize()) {
                            composable()
                        }
                    }
                }
            }, modifier = Modifier.fillMaxSize(), update = {
                it.run {
                    doOnLayout {
                        onBitmapped(drawToBitmap())
                    }
                }
            })
        }
    }

}

@Composable
fun BitmapComposable(
    onBitmapped: (bitmap: Bitmap) -> Unit = { _ -> },
    backgroundColor: Color = Color.Transparent,
    intSize : IntSize, // Pixel size for output bitmap
    composable: @Composable () -> Unit
) {
    val renderComposableSize = LocalDensity.current.run { intSize.toSize().toDpSize() }
    BitmapComposable(onBitmapped,backgroundColor,renderComposableSize,composable)
}

2 - And use like this :

BitmapComposable(
    onBitmapped = { bitmap ->
        // Do your operation
    },
    backgroundColor = Color.White,
    intSize = IntSize(500, 700) // Pixel size for output bitmap
) {
    // Composable that you want to convert to a bitmap
    // This scope is @Composable
    YourComposable()
}
MH-Rouhani
  • 81
  • 1
  • 7