Using alpha mask over a group of radialgradient shaders leads to some weird compositing
Description
I have a white to transparent radial gradient in a circle that will act as a mask over three other coloured radial gradients in circles in a group. When it's not a mask everything looks like I expect:
When the white circle is defined as a Mask the shapes within the group that should be masked seem to get composited as solid objects with the background color appearing where they are transparent (it is not doing this when they are not masked).
I would say it appears as though one solid circle is sitting on top of the other except that you can see the visible edge of both circles under the masked area where if one was on top you'd only see one edge - so I'm struggling to figure out what's going on!
Relevant piece of code is:
<Mask
mode="alpha"
mask={
<Circle key="rs" cx={width / 2} cy={height / 2} r={Math.max(width, height)}>
<RadialGradient
c={vec(width / 2, height / 2)}
r={Math.max(width / 2, height / 2)}
colors={['rgba(255,255,255,1)', 'rgba(255,255,255,0)']}
/>
</Circle>
}
>
<Group>
{colorBlobs.map((blob) => (
<Circle key={blob.id} cx={blob.cx} cy={blob.cy} r={blob.radius}>
<RadialGradient
c={vec(blob.cx, blob.cy)}
r={blob.radius}
colors={[
`rgba(${blob.color[0]},${blob.color[1]},${blob.color[2]},1)`,
`rgba(${blob.color[0]},${blob.color[1]},${blob.color[2]},0)`
]}
/>
</Circle>
))}
</Group>
</Mask>
with the colorBlobs defined thus:
const colorBlobs = useMemo(() => {
const hues = [
[0, 255, 224],
[255, 181, 245],
[59, 255, 251]
];
const positions = [
[width / 2, height / 2],
[width / 4, height / 4],
[(width / 4) * 3, (height / 4) * 3]
];
return Array.from({ length: NUM_COLORS }).map((_, i) => {
const centerX = positions[i][0];
const centerY = positions[i][1];
const radius = width * 0.5;
return {
id: `color-${i}`,
cx: centerX,
cy: centerY,
radius,
color: hues[i % hues.length]
};
});
}, []);
I have played around with blendMode both on the Group and in the JSX style <Blend mode= wrapped around the RadialGradient - and while some modes would change how they looked none fundamentally fixed it.
React Native Skia Version
2.0.0-next.4
React Native Version
0.79.3
Using New Architecture
- [x] Enabled
Steps to Reproduce
Create a canvas (web or native but my screenshots are from web using the latest wasm). and render the markup as described above.
Expect the gradient to mask a correctly composited flat group of three coloured blobs. Actual result is that the gradient coloured blobs are getting composited weirdly (technical term).
Snack, Code Example, Screenshot, or Link to Repository
https://github.com/rsouthgate/skia-bug-repro
Minimal expo-router based repro.
npm start... View on web or through Expo Go - behaviour is the same, toggle mask on or off to see how the two circles blend when not masked vs how they (don't) blend when masked
React Native Skia Version
2.2.14
React Native Version
0.79.5
Using New Architecture
- [x] Enabled
Description
Same here, I also add more information about this bug, as I can see main problem here is opacity of children's inside of
<Mask /> element.
Screenshots and some explains
That's how it's "works" for now, if inside mask is used any element with opacity, Element which should be transparent ~~fills with white color.~~ somehow I guess lost alpha channel and use closets color? added how it's looks on dark theme.
<Mask
mode="luminance"
mask={
<MaskCard cardWidth={cardWidth} cardHeight={cardHeight} width={width} height={height} />
}
>
<Box box={rrect(rect(0, 0, cardWidth, cardHeight), 0, 0)} antiAlias={true}>
<Shader source={lavaLampShaderSource} uniforms={uniformsInner} />
{children}
</Box>
</Mask>
{/* <Mask
mode="luminance"
mask={
<MaskCard cardWidth={cardWidth} cardHeight={cardHeight} width={width} height={height} />
}
> */}
<Box box={rrect(rect(0, 0, cardWidth, cardHeight), 0, 0)} antiAlias={true}>
<Shader source={lavaLampShaderSource} uniforms={uniformsInner} />
{children}
</Box>
{/* </Mask> */}
@rsouthgate @tim7on these examples look very cool and I want you to succeed. It would take me a bit of time to untangle it.
Maybe one thing that would help a lot with the testing debugging is to try the examples on figma. We build the example on figma. And that gives us the expected results we can put in our test suite. What do you think?
@tim7on I see you are using <Box /> instead of <RoundedRect />, could that be the issue?
Here is Figma examples @wcandillon, @rsouthgate am I get right what you trying to achieve with Mask ? Figma
@wcandillon about <Box /> instead of <RoundedRect /> I jus simply use <BoxShadow dx={0} dy={0} blur={7} color="#00000088" /> in another element which affects only <Box />
Shadow applies to every **RoundedRect**
<RoundedRect x={0} y={0} width={cardWidth} height={cardHeight} r={PARAGRAPH_PADDING}>
<Shader source={lavaLampShaderSource} uniforms={uniformsInner} />
{children}
<Shadow dx={-12} dy={-12} blur={25} color="#000" />
</RoundedRect>
PS for me is currently easiest way just put {children} outside of mask
@tim7on perfect for the Figma file, I'll just add them as test cases in the repo. Now I definitely recommend not using BoxShadow and Box. I want to decouple what might be a mask issue first and then what might be a Box issue. If we put the two together, it will be too hard to debug. Just to give a bit of context, most drawing primitives in RN Skia are 1 to 1 to Skia except for a few... ...like Mask and Box so this is why they might be so "unstable".
@tim7on Yes that alpha mask over the radial gradients is exactly what I would like to achieve