react-native-skia icon indicating copy to clipboard operation
react-native-skia copied to clipboard

Using alpha mask over a group of radialgradient shaders leads to some weird compositing

Open rsouthgate opened this issue 5 months ago • 5 comments

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:

Image The solid white in the center is the center of the radial gradient - defined colors rgba(255,255,255,1), rgba(255,255,255,0). The three colour circles behind overlap each other and have their own transparency gradients also defined as rgba(r,g,b,1) to rgba(r,g,b,0).

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).

Image

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

rsouthgate avatar Aug 02 '25 03:08 rsouthgate

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>
Image_with_mask Image_with_mask_darkTheme
		{/* <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> */}
Image_without_mask

tim7on avatar Sep 12 '25 18:09 tim7on

@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?

wcandillon avatar Sep 12 '25 19:09 wcandillon

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>
Image

PS for me is currently easiest way just put {children} outside of mask

tim7on avatar Sep 13 '25 07:09 tim7on

@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".

wcandillon avatar Sep 13 '25 07:09 wcandillon

@tim7on Yes that alpha mask over the radial gradients is exactly what I would like to achieve

rsouthgate avatar Sep 13 '25 07:09 rsouthgate