Shopify / react-native-skia

High-performance React Native Graphics using Skia

Home Page:https://shopify.github.io/react-native-skia

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Headless Opacity

nahn20 opened this issue · comments

Description

Opacity seems to be broken when rendering in headless mode. Shapes and images (I'm sure many others too, but only tested on shapes and images) show with full opacity rather than a partial opacity. Opacity works correctly for values of 0 and 1. I've attempted it using both the opacity prop as well as using a group based on this example. Both of these appear to be functioning as expected on web.

Version

1.0.6

Steps to reproduce

Example output attached. Circles 1 (base case), 2 (not visible) and 4 (not visible) are behaving as expected. Circles 3 and 5 should have lower opacity.
image

Snack, code example, screenshot, or link to a repository

import {
	Circle,
	ColorMatrix,
	Group,
	OpacityMatrix,
	Paint,
	drawOffscreen,
	makeOffscreenSurface,
} from "@shopify/react-native-skia/lib/module/headless";
import { ImageFormat } from "@shopify/react-native-skia/lib/module/skia/types/Image/Image";
import { LoadSkiaWeb } from "@shopify/react-native-skia/lib/module/web/LoadSkiaWeb";
import fs from "fs/promises";

const outFile = "/tmp/image.jpg";
const render = () => {
	return (
		<>
			<Circle opacity={1} cx={200} cy={200} r={100} color="cyan" />
			<Circle opacity={0} cx={600} cy={200} r={100} color="cyan" />
			<Circle opacity={0.1} cx={1000} cy={200} r={100} color="cyan" />
			<Group
				layer={
					<Paint>
						<ColorMatrix matrix={OpacityMatrix(0)} />
					</Paint>
				}
			>
				<Circle opacity={0.1} cx={1400} cy={200} r={100} color="cyan" />
			</Group>
			<Group
				layer={
					<Paint>
						<ColorMatrix matrix={OpacityMatrix(0.1)} />
					</Paint>
				}
			>
				<Circle opacity={0.1} cx={1800} cy={200} r={100} color="cyan" />
			</Group>
		</>
	);
};

const runMemoryLeakTest = async () => {
	await LoadSkiaWeb();
	const surface = makeOffscreenSurface(1920, 1080);
	const image = drawOffscreen(surface, render());
	await fs.writeFile(outFile, image.encodeToBytes(ImageFormat.JPEG, 100));

	image.dispose();
	surface.dispose();
};

await runMemoryLeakTest();

This is actually the expected result.
The values are unpremultiplied when you encode it to a non transparent format, this is what you will get (only the rgb components are used).
If you want to save it as JPG, you can add a black background yourself and get the expected result.
If you save it as a PNG, the transparency will be preserved.

If you create the surface Premultiplied, you will get the expected result, which we could expose as an option but I am not sure if it's worth it 🤔

I'm closing it for now but if there is a use case for creating surfaces with more control over the color format, alpha type, and color space, we will reopen it.

That makes sense! I agree, this seems correct as the intended behavior. Thank you for clarifying for me!