Jetpack Compose 将 PorterDuffMode 应用于图像

Jetpack Compose Applying PorterDuffMode to Image

基于 this page

中的图像和 PorterDuffModes

我下载了图片,最初虽然它们是 png,但它们有不透明的浅灰色和深灰色矩形,所以我删除了它们。

并使用 this sample code 检查,将原始代码中的可绘制对象替换为下面的可绘制对象,我得到了结果

在 Android View 中它似乎可以正常工作,但是当我使用 Jetpack Canvas as

androidx.compose.foundation.Canvas(modifier = Modifier.size(500.dp),
    onDraw = {

        drawImage(imageBitmapDst)
        drawImage(imageBitmapSrc, blendMode = BlendMode.SrcIn)

    })

BlendMode.SrcIn 在黑色矩形上绘制蓝色矩形,其他模式也不会 return 正确结果。 BlendMode.SrcOut return黑屏。

并使用 Box

堆叠在一起的 2 张图片
val imageBitmapSrc: ImageBitmap = imageResource(id = R.drawable.c_src)
val imageBitmapDst: ImageBitmap = imageResource(id = R.drawable.c_dst)

Box {
    Image(bitmap = imageBitmapSrc)
    Image(
        bitmap = imageBitmapDst,
        colorFilter = ColorFilter(color = Color.Unspecified, blendMode = BlendMode.SrcOut)
    )
}

只有蓝色 src 矩形可见。

也试过 Painter,也无法正常工作

val imageBitmapSrc: ImageBitmap = imageResource(id = R.drawable.c_src)
val imageBitmapDst: ImageBitmap = imageResource(id = R.drawable.c_dst)

val blendPainter = remember {
    object : Painter() {

        override val intrinsicSize: Size
            get() = Size(imageBitmapSrc.width.toFloat(), imageBitmapSrc.height.toFloat())

        override fun DrawScope.onDraw() {
            drawImage(imageBitmapDst, blendMode = BlendMode.SrcOut)
            drawImage(imageBitmapSrc)
        }
    }
}

Image(blendPainter)

Jetpack Compose 应如何使用 BlendPorterDuff 模式?

整整一周我都因为类似的问题而感到沮丧,但是你的问题帮助我找到了如何让它工作的解决方案。

编辑1

我正在使用 compose 1.0.0

在我的例子中,我使用双缓冲之类的东西而不是直接在 canva 上绘图 - 只是作为一种解决方法。

Canvas(modifier = Modifier.fillMaxWidth().fillMaxHeight()) {

    // First I create bitmap with real canva size
    val bitmap = ImageBitmap(size.width.toInt(), size.height.toInt())

    // here I'm creating canvas of my bitmap
    Canvas(bitmap).apply {
       // here I'm driving on canvas
    }
   
    // here I'm drawing my buffered image
    drawImage(bitmap)
}

Canvas(bitmap) 内部,我正在使用 drawPathdrawText 等油漆:

val colorPaint = Paint().apply {
    color = Color.Red
    blendMode = BlendMode.SrcAtop
}

这样 BlendMode 可以正常工作 - 我已经尝试了很多模式并且一切都按预期工作。

我不知道为什么这不能直接在 Composable 的 canvas 上工作,但我的解决方法对我来说很好。

编辑2

在研究了 Image 的 Painter 源代码后,我发现 Android 团队还使用 alpha 技巧来决定是否创建图层

Painter

private fun configureAlpha(alpha: Float) {
    if (this.alpha != alpha) {
        val consumed = applyAlpha(alpha)
        if (!consumed) {
            if (alpha == DefaultAlpha) {
                // Only update the paint parameter if we had it allocated before
                layerPaint?.alpha = alpha
                useLayer = false
            } else {
                obtainPaint().alpha = alpha
                useLayer = true
            }
        }
        this.alpha = alpha
    }
}

并在此处申请

    fun DrawScope.draw(
        size: Size,
        alpha: Float = DefaultAlpha,
        colorFilter: ColorFilter? = null
    ) {
        configureAlpha(alpha)
        configureColorFilter(colorFilter)
        configureLayoutDirection(layoutDirection)

        // b/156512437 to expose saveLayer on DrawScope
        inset(
            left = 0.0f,
            top = 0.0f,
            right = this.size.width - size.width,
            bottom = this.size.height - size.height
        ) {

            if (alpha > 0.0f && size.width > 0 && size.height > 0) {
                if (useLayer) {
                    val layerRect = Rect(Offset.Zero, Size(size.width, size.height))
                    // TODO (b/154550724) njawad replace with RenderNode/Layer API usage
                    drawIntoCanvas { canvas ->
                        canvas.withSaveLayer(layerRect, obtainPaint()) {
                            onDraw()
                        }
                    }
                } else {
                    onDraw()
                }
            }
        }
    }
}

解决问题最简单的方法是添加 .graphicsLayer(alpha = 0.99f)Modifier 以确保屏幕外缓冲区

@Composable
fun DrawWithBlendMode() {


    val imageBitmapSrc = ImageBitmap.imageResource(
        LocalContext.current.resources,
        R.drawable.composite_src
    )
    val imageBitmapDst = ImageBitmap.imageResource(
        LocalContext.current.resources,
        R.drawable.composite_dst
    )


    Canvas(
        modifier = Modifier
            .fillMaxSize()
            // Provide a slight opacity to for compositing into an
            // offscreen buffer to ensure blend modes are applied to empty pixel information
            // By default any alpha != 1.0f will use a compositing layer by default
            .graphicsLayer(alpha = 0.99f)
    ) {


        val dimension = (size.height.coerceAtMost(size.width) / 2f).toInt()

        drawImage(
            image = imageBitmapDst,
            dstSize = IntSize(dimension, dimension)
        )
        drawImage(
            image = imageBitmapSrc,
            dstSize = IntSize(dimension, dimension),
            blendMode = BlendMode.SrcOut
        )
    }
}

结果

或者在 Canvas 中添加一层就可以了

    with(drawContext.canvas.nativeCanvas) {
        val checkPoint = saveLayer(null, null)

    // Destination
    drawImage(
        image = dstImage,
        srcSize = IntSize(canvasWidth / 2, canvasHeight / 2),
        dstSize = IntSize(canvasWidth, canvasHeight),
    )

    // Source
    drawImage(
        image = srcImage,
        srcSize = IntSize(canvasWidth / 2, canvasHeight / 2),
        dstSize = IntSize(canvasWidth, canvasHeight),
        blendMode = blendMode
    )
    restoreToCount(checkPoint)
}

我创建了一些应用混合模式的教程here