Jetpack Compose 将 PorterDuffMode 应用于图像

Thr*_*ian 7 android canvas porter-duff android-jetpack-compose

基于本页中的图像和 PorterDuffModes

我下载了图像,最初即使它们是png,它们也有浅灰色和深灰色的矩形,这些矩形不透明并删除了它们。

目的地 来源

并使用此示例代码进行检查,将原始代码中的可绘制对象替换为下面的可绘制对象,我得到结果

在此输入图像描述

看起来它可以与 Android View 一起使用,但是当我使用 Jetpack Canvas 作为

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

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

    })
Run Code Online (Sandbox Code Playgroud)

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

并使用 2 张图像堆叠在一起Box

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)
    )
}
Run Code Online (Sandbox Code Playgroud)

仅蓝色 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)
Run Code Online (Sandbox Code Playgroud)

Jetpack Compose应该如何使用Blend或模式?PorterDuff

Thr*_*ian 7

编辑

从 1.4.1 或更高版本开始,您可以分配

Modifier.graphicsLayer { 
    compositingStrategy = CompositingStrategy.Offscreen
}
Run Code Online (Sandbox Code Playgroud)

Canvas在graphicsLayer之后的任何绘制修改器能够正确使用BlendModes

解决问题的另一种方法是添加 .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
        )
    }
}
Run Code Online (Sandbox Code Playgroud)

结果

在此输入图像描述

或者在 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)
}
Run Code Online (Sandbox Code Playgroud)

我在这里创建了一些应用混合模式的教程


Kry*_*ski 2

我整整一周都因类似的问题而感到沮丧,但是您的问题帮助我找到了如何使其发挥作用的解决方案。

编辑1

我正在使用撰写1.0.0

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

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)
}
Run Code Online (Sandbox Code Playgroud)

在里面Canvas(bitmap)我使用drawPathdrawText等与油漆:

val colorPaint = Paint().apply {
    color = Color.Red
    blendMode = BlendMode.SrcAtop
}
Run Code Online (Sandbox Code Playgroud)

以这种方式BlendMode工作正常 - 我已经尝试了很多模式,一切都按预期工作。

我不知道为什么这不能直接在可组合画布上工作,但我的解决方法对我来说效果很好。

编辑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
    }
}
Run Code Online (Sandbox Code Playgroud)

并适用于此

    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()
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)