How would I use an image as a mask in compose? I h...
# compose
s
How would I use an image as a mask in compose? I have an effect that I’m drawing as a Shader, but I only want it to be applied there there are non-transparent pixels for an image. How would I do that?
r
It will work with an ALPHA_8 bitmap
One way to do this is create an ALPHA_8 bitmap, create a Canvas for it, draw your original image into it
Now you have an alpha mask
And this can be combined with other shaders to work like you want, as a mask
s
create an ALPHA_8 bitmap, create a Canvas for it, draw your original image into it
How would I do this? I’ve never heard of an Alpha 8 bitmap
create a Canvas for it
Is this an Android Canvas? Technically I’m doing this on Compose Desktop
I see - Compose ImageBitmap has an Alpha 8 option.
I understand generally what you are saying, but I’m struggling to apply it. I don’t suppose you have a sample?
k
Your shader has access to every single pixel in the image. So it can look at the input alpha, and based on that output either transparent color or apply the filter on that pixel.
s
@Kirill Grouchnikov So here’s what I have:
Copy code
val svgPainter: Painter
val shader = Shader.makeFractalNoise(
    baseFrequencyX = 8.21f,
    baseFrequencyY = 8.21f,
    numOctaves = 3,
    seed = 0.0f,
)
val shaderBrush = ShaderBrush(shader)

Canvas(
    modifier = Modifier.size(width = maxWidth.dp, height = maxHeight.dp)
) {
    drawRect(shaderBrush)
}
I’m not understanding how this all comes together. I want to use the shader as a mask so that I can draw the
svgPainter
where the
shaderBrush
draws something.
I’ve looked at
BlendMode
and it sounds like it could work, but there aren’t a ton of examples of this online.
r
I should check compose desktop
But the equivalent of Paint.setShader will use a bitmap as an alpha mask
Ie draw bitmap(theMask, thePaint) will do what you want
BlendMode will only work when you render into an intermediate transparent layer
s
But the equivalent of Paint.setShader will use a bitmap as an alpha mask
So both Skia
Paint
and androidx
Paint
look like there is a
setShader
method. How does the bitmap come into play?
It looks like part of my problem is that I need to somehow get my svg
Painter
to a bitmap or drawn on the canvas.
ok - I found out how to draw the svg painter on the canvas. I’m still not understanding how to use the shader as a mask.
Copy code
val shader = Shader.makeFractalNoise(
    baseFrequencyX = 8.21f,
    baseFrequencyY = 8.21f,
    numOctaves = 3,
    seed = 0.0f,
)
val shaderBrush = ShaderBrush(shader)

val svgSize = Size(width = maxWidth.toFloat(), height = maxHeight.toFloat())
Canvas(
    modifier = Modifier.size(width = maxWidth.dp, height = maxHeight.dp)
) {
    drawRect(shaderBrush, size = svgSize)

    with(svgPainter) {
        draw(size = svgSize)
    }
}
r
Is the noise your mask?
s
yes
The SVG is something like this.
I need to produce something like this
This was achieved by using the fractal noise as a mask on the SVG
I should be able to create the same thing in compose
r
Note that you probably don’t even need the SVG for this. You could just use gradients. Anyway, there are two ways you can do this:
• Create an ARGB8888 bitmap, draw SVG, then draw the noise as a rect using the appropriate BlendMode
• Create an ALPHA_8 bitmap, draw the noise in it. Create another bitmap, draw the SVG in it. Draw the first bitmap, using the second bitmap as its brush
Since your SVG doesn’t use colors, the second method is a bit wasteful
s
@romainguy I do need the SVG for this. This is being applied to a templating system on a JVM server that uses Compose to render images for customers on the fly. This is the shadow for 1 of 8 image templates that’s being rendered currently.
we were using html to generate the images, but compose can do in millis what html can do in 8-15 seconds.
r
If your goal is generating images then solution #1 I highlighted above is probably best
s
I think I’ve got #1 working correctly
r
Create an ARGB8888 image, draw your SVG, then apply the mask with
DST_IN
(or
CLEAR
but I don’t remember if
CLEAR
modulates by alpha)
s
Copy code
val bitmap = ImageBitmap(width = width, height = height, config = ImageBitmapConfig.Alpha8)
    .also { bitmap ->
        val canvas = Canvas(bitmap)

        val shaderBrush = ShaderBrush(
            Shader.makeFractalNoise(
                baseFrequencyX = 8.21f,
                baseFrequencyY = 8.21f,
                numOctaves = 3,
                seed = 0.0f,
            )
        )

        val svgSize = Size(width = width.toFloat(), height = height.toFloat())
        CanvasDrawScope().draw(LocalDensity.current, LayoutDirection.Ltr, canvas, svgSize) {
            with(painter) {
                draw(size = svgSize)
            }

            drawRect(brush = shaderBrush, size = svgSize, blendMode = BlendMode.DstIn)
        }
    }
It looks like the shadow it’s creating is black now though
It’s not maintaining the original color of the shadow
r
Yeah because you are creating an
ALPHA_8
bitmap
Make it ARGB888 or whatever is the equivalent
s
ah
that’s what I was about to try
Argb8888
Works perfectly. I love compose so much.
Thanks @romainguy and thanks @Kirill Grouchnikov for your blog posts on Skia shaders. They really helped me out here!
s
I can't help but think what you're doing here Scott sounds super interesting but very hard to follow from a slack thread. But I feel like I'd love to see exactly what you're trying to do. If you ever have some final snippet lying around of what you're doing or you're gonna write up a blog about this I'd be super interested in reading it!
💯 1
3