【问题标题】:Get bitmap before using in a Composable with Coil在 Composable with Coil 中使用之前获取位图
【发布时间】:2022-12-09 14:19:26
【问题描述】:

我试图从一个 url 获取两张图片,然后我有一个 Composable 需要两个位图才能在 Canvas 中绘制它们,我试过了但是画布没有画,我错过了什么吗?

val overlayImage =
            "https://st2.depositphotos.com/1400069/5999/i/600/depositphotos_59995765-stock-photo-abstract-galaxy-background.jpg"
        val baseImage =
            "https://www.vitrinesdocomercio.com/uploads/1/3/9/4/13943900/1278180_orig.jpg"

        val overlayImageLoaded = rememberAsyncImagePainter(
            model = overlayImage,
        )
        val baseImageLoaded = rememberAsyncImagePainter(
            model = baseImage
        )

        var overlayBitmap = remember<Bitmap?> {
            null
        }
        var baseBitmap = remember<Bitmap?> {
            null
        }

        val overlayImageLoadedState = overlayImageLoaded.state
        if (overlayImageLoadedState is AsyncImagePainter.State.Success) {
            overlayBitmap = overlayImageLoadedState.result.drawable.toBitmap()
        }

        val baseImageLoadedState = baseImageLoaded.state
        if (baseImageLoadedState is AsyncImagePainter.State.Success) {
            baseBitmap = baseImageLoadedState.result.drawable.toBitmap()
        }

        MyCanvasComposable(baseBitmap, overlayBitmap)

【问题讨论】:

  • 你如何使用画布中的图像?

标签: android android-jetpack-compose coil android-jetpack-compose-canvas


【解决方案1】:

您应该assign size 才能创建画家,否则会返回错误

val overlayPainter = rememberAsyncImagePainter(
    model = ImageRequest.Builder(LocalContext.current)
        .data(overlayImage)
        .size(coil.size.Size.ORIGINAL) // Set the target size to load the image at.
        .build()
)
val basePainter = rememberAsyncImagePainter(
    model = ImageRequest.Builder(LocalContext.current)
        .data(baseImage)
        .size(coil.size.Size.ORIGINAL) // Set the target size to load the image at.
        .build()
)

结果

当两个状态都成功时,您可以发送两个 ImageBitmaps

@Composable
private fun MyComposable() {

    val overlayImage =
        "https://st2.depositphotos.com/1400069/5999/i/600/depositphotos_59995765-stock-photo-abstract-galaxy-background.jpg"
    val baseImage =
        "https://www.vitrinesdocomercio.com/uploads/1/3/9/4/13943900/1278180_orig.jpg"


    val overlayPainter = rememberAsyncImagePainter(
        model = ImageRequest.Builder(LocalContext.current)
            .data(overlayImage)
            .size(coil.size.Size.ORIGINAL) // Set the target size to load the image at.
            .build()
    )
    val basePainter = rememberAsyncImagePainter(
        model = ImageRequest.Builder(LocalContext.current)
            .data(baseImage)
            .size(coil.size.Size.ORIGINAL) // Set the target size to load the image at.
            .build()
    )

    val overlayImageLoadedState = overlayPainter.state
    val baseImageLoadedState = basePainter.state

    if (
        baseImageLoadedState is AsyncImagePainter.State.Success &&
        overlayImageLoadedState is AsyncImagePainter.State.Success
    ) {

        SideEffect {
            println("? COMPOSING...")
        }

        val baseImageBitmap =
            baseImageLoadedState.result.drawable.toBitmap()
                .asImageBitmap()
        val overlayImageBitmap =
            overlayImageLoadedState.result.drawable
                .toBitmap()
                .asImageBitmap()

        EraseBitmapSample(
            baseImageBitmap = baseImageBitmap,
            overlayImageBitmap = overlayImageBitmap,
            modifier = Modifier
                .fillMaxWidth()
                .aspectRatio(4 / 3f)
        )
    }
}

以及你希望达到的目标

@Composable
fun EraseBitmapSample(
    overlayImageBitmap: ImageBitmap,
    baseImageBitmap: ImageBitmap,
    modifier: Modifier
) {

    var matchPercent by remember {
        mutableStateOf(100f)
    }

    BoxWithConstraints(modifier) {

        // Path used for erasing. In this example erasing is faked by drawing with canvas color
        // above draw path.
        val erasePath = remember { Path() }

        var motionEvent by remember { mutableStateOf(MotionEvent.Idle) }
        // This is our motion event we get from touch motion
        var currentPosition by remember { mutableStateOf(Offset.Unspecified) }
        // This is previous motion event before next touch is saved into this current position
        var previousPosition by remember { mutableStateOf(Offset.Unspecified) }

        val imageWidth = constraints.maxWidth
        val imageHeight = constraints.maxHeight


        val drawImageBitmap = remember {
            Bitmap.createScaledBitmap(
                overlayImageBitmap.asAndroidBitmap(),
                imageWidth,
                imageHeight,
                false
            )
                .asImageBitmap()
        }

        // Pixels of scaled bitmap, we scale it to composable size because we will erase
        // from Composable on screen
        val originalPixels: IntArray = remember {
            val buffer = IntArray(imageWidth * imageHeight)
            drawImageBitmap
                .readPixels(
                    buffer = buffer,
                    startX = 0,
                    startY = 0,
                    width = imageWidth,
                    height = imageHeight
                )

            buffer
        }

        val erasedBitmap: ImageBitmap = remember {
            Bitmap.createBitmap(imageWidth, imageHeight, Bitmap.Config.ARGB_8888).asImageBitmap()
        }

        val canvas: Canvas = remember {
            Canvas(erasedBitmap)
        }

        val paint = remember {
            Paint()
        }

        val erasePaint = remember {
            Paint().apply {
                blendMode = BlendMode.Clear
                this.style = PaintingStyle.Stroke
                strokeWidth = 30f
            }
        }


        canvas.apply {
            val nativeCanvas = this.nativeCanvas
            val canvasWidth = nativeCanvas.width.toFloat()
            val canvasHeight = nativeCanvas.height.toFloat()


            when (motionEvent) {

                MotionEvent.Down -> {
                    erasePath.moveTo(currentPosition.x, currentPosition.y)
                    previousPosition = currentPosition

                }
                MotionEvent.Move -> {

                    erasePath.quadraticBezierTo(
                        previousPosition.x,
                        previousPosition.y,
                        (previousPosition.x + currentPosition.x) / 2,
                        (previousPosition.y + currentPosition.y) / 2

                    )
                    previousPosition = currentPosition
                }

                MotionEvent.Up -> {
                    erasePath.lineTo(currentPosition.x, currentPosition.y)
                    currentPosition = Offset.Unspecified
                    previousPosition = currentPosition
                    motionEvent = MotionEvent.Idle

                    matchPercent = compareBitmaps(
                        originalPixels,
                        erasedBitmap,
                        imageWidth,
                        imageHeight
                    )
                }
                else -> Unit
            }

            with(canvas.nativeCanvas) {
                drawColor(android.graphics.Color.TRANSPARENT, PorterDuff.Mode.CLEAR)

                drawImageRect(
                    image = drawImageBitmap,
                    dstSize = IntSize(canvasWidth.toInt(), canvasHeight.toInt()),
                    paint = paint
                )

                drawPath(
                    path = erasePath,
                    paint = erasePaint
                )
            }
        }

        val canvasModifier = Modifier.pointerMotionEvents(
            Unit,
            onDown = { pointerInputChange ->
                motionEvent = MotionEvent.Down
                currentPosition = pointerInputChange.position
                pointerInputChange.consume()
            },
            onMove = { pointerInputChange ->
                motionEvent = MotionEvent.Move
                currentPosition = pointerInputChange.position
                pointerInputChange.consume()
            },
            onUp = { pointerInputChange ->
                motionEvent = MotionEvent.Up
                pointerInputChange.consume()
            },
            delayAfterDownInMillis = 20
        )

        Image(
            bitmap = baseImageBitmap,
            contentDescription = null
        )

        Image(
            modifier = canvasModifier
                .clipToBounds()
                .matchParentSize()
                .border(2.dp, Color.Green),
            bitmap = erasedBitmap,
            contentDescription = null,
            contentScale = ContentScale.FillBounds
        )

    }

    Text(
        text = "Bitmap match ${matchPercent}%",
        color = Color.Red,
        fontSize = 22.sp,
    )
}

【讨论】:

  • 是的,这对我有用,谢谢 Thracian,有什么办法可以随时隐藏已擦除的图像吗?
  • 当然。在要擦除的图像上创建一个带有 matchPercent 的 if。 if(matchPercent)&gt;=70){Image(erasedBitmap)}就是这样。你可以放任何旗帜。只要声明为真,您的 Image 可组合项就在合成中,如果不再为真,则退出合成
  • 有时在拖动时没有绘制路径,一旦你执行了onUp,它就会渲染线,你知道为什么吗?
  • 如果工作正常,你应该使用 Modiifer.pointerInputFilter,我不确定。我发布手势代码进行演示,因为它很容易使用。如果您的手势代码遇到同样的问题,请尝试不比较像素。如果是这种情况,请在另一个线程中调用该函数
  • 让我试试看,二次方的东西可以改为:val offset = Offset( previousPosition.x, previousPosition.y, ) erasePath.addOval( oval = Rect(offset, 100f) ) 来代替二次方画一个椭圆?
猜你喜欢
  • 2020-09-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-07
  • 2023-02-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多