Reputation: 458
I'm trying to add an overlay color on top of a PNG image (with a transparent background) using BlendMode.SRC_IN but the background becomes black instead of the set background color as if masking out the background pixels as well.
@Composable
fun Icon(
fraction: Float,
image: ImageAsset,
defaultColor: Color = Color(0xFFEEEEEE),
progressColor: Color = Color(0xFF888888),
size: Dp = image.width.dp
) {
Box(modifier = Modifier.size(size = size)) {
Canvas(modifier = Modifier.fillMaxSize()) {
drawImage(
image = image,
dstSize = IntSize(
width = size.toIntPx(),
height = size.toIntPx()
),
colorFilter = ColorFilter.tint(
color = defaultColor
),
blendMode = BlendMode.Src
)
drawIntoCanvas {
val paint = Paint().apply {
color = progressColor
blendMode = BlendMode.SrcIn
}
it.restore()
it.drawRect(
rect = Rect(
offset = Offset.Zero,
size = Size(
width = size.toPx() * fraction,
height = size.toPx()
)
),
paint = paint
)
it.save()
}
}
}
}
Here is how it looks when I line up multiple Icon()
on top of a Screen(color = Color.WHITE)
like,
Surface(color = Color.White) {
Row {
listOf(
R.drawable.anemo,
R.drawable.cryo,
R.drawable.dendro,
R.drawable.electro,
R.drawable.geo,
R.drawable.hydro,
R.drawable.pyro
).forEachIndexed { index, imageRes ->
val from = 100f/7 * index
val to = 100f/7 * (index + 1)
val fraction = when {
progress > to -> 1f
progress > from -> (progress - from)/(to - from)
else -> 0f
}
Icon(
fraction = fraction,
image = imageResource(id = imageRes),
size = 50.dp
)
}
}
}
Am I doing something wrong here?
Here is the Github repository where I'm trying this out.
Upvotes: 13
Views: 7374
Reputation: 1
To display the effect picture of "The desired result I want is", you should put image in ShaderBrush
, then paint it, and draw a rectangle with this ShaderBrush
and BlendMode
, such as:
val imageBrush = ShaderBrush(ImageShader(ImageBitmap.imageResource(id = R.drawable.xxx)))
modifier = modifier.paint(painter = painterResource(id = R.drawable.xxx))
.drawWithCache {
onDrawWithContent {
drawRect(
brush = imageBrush,
colorFilter = ColorFilter.tint(Color.Gray),
blendMode = BlendMode.Difference
)
}
}
Upvotes: 0
Reputation: 588
The original answer given by @LN-12 is valid and relies on the internal logic of the framework. By applying the .graphicsLayer(alpha = 0.99f)
modifier, Compose
changes the CompositingStrategy
used when rendering.
However, changing the alpha
value can be an undesirable effect and requires a comment to explain the purpose of the modifier.
Fortunately, starting with compose 1.4.0
, the same outcome can be achieved in a much more elegant way using: .graphicsLayer(compositingStrategy = CompositingStrategy.Offscreen)
Upvotes: 6
Reputation: 151
.graphicsLayer(alpha = 0.99f)
is a valid workaround, however, it changes the alpha of the content which is not necessary.
To redirect drawing to an offscreen rendering target, android.graphics.Canvas#saveLayer(android.graphics.RectF, android.graphics.Paint)
can be utilised, a simple modifier can be like:
fun Modifier.drawOffscreen(): Modifier = this.drawWithContent {
with(drawContext.canvas.nativeCanvas) {
val checkPoint = saveLayer(null, null)
drawContent()
restoreToCount(checkPoint)
}
}
And the content will be drawn offscreen before restoreToCount
is called.
Upvotes: 6
Reputation: 1049
In my case, I could solve the issue of having black pixels where they should be transparent pixels by adding the graphicsLayer
modifier like this:
Box(
modifier = Modifier.fillMaxSize()
) {
Canvas(modifier = Modifier
.fillMaxSize()
.graphicsLayer(alpha = 0.99f)
) {
drawRect(
color = Color.Black,
size = size,
blendMode = BlendMode.Xor
)
drawCircle(
color = Color.Black,
radius = 300f,
blendMode = BlendMode.Xor
)
}
}
Without the modifier:
With modifier:
I took that idea from here: https://gist.github.com/nadewad/14f04c788aea43bd6d31d94cd8100ab5 (note that drawLayer
was renamed to graphicsLayer
.
Upvotes: 8