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 应如何使用 Blend
或 PorterDuff
模式?
整整一周我都因为类似的问题而感到沮丧,但是你的问题帮助我找到了如何让它工作的解决方案。
编辑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)
内部,我正在使用 drawPath
、drawText
等油漆:
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
基于 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
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 应如何使用 Blend
或 PorterDuff
模式?
整整一周我都因为类似的问题而感到沮丧,但是你的问题帮助我找到了如何让它工作的解决方案。
编辑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)
内部,我正在使用 drawPath
、drawText
等油漆:
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