feat(🖼️): add full control over image encoding quality#3952
Conversation
|
Thank You for submitting this. It looks really cool. For image encoding on the Web, I think we can use Web APIs directly for this. Or is your use-case headless? |
|
Thanks William! No, the CanvasKit patch has not been submitted upstream yet. I wanted to validate the API and encoding semantics here first. If keeping the CanvasKit encoder is the preferred direction, I can extract the patch and submit it to Skia upstream instead of maintaining a permanent React Native Skia-specific fork. The goal is not limited to a browser-only use case. It also includes the existing DOM-free headless/Jest path and preserving the synchronous Browser APIs can cover lossy JPEG/WebP quality, but they do not expose the complete set of controls provided by this PR:
Using Web APIs would therefore require different asynchronous Web semantics, reduced PNG/WebP controls, or separate browser and headless implementations. That said, I agree that bundling and maintaining a custom CanvasKit build has a significant maintenance and distribution cost. I am happy to adapt the PR in either direction:
Which direction would you prefer? |
Why
Image encoding options were not handled consistently across Native and Web.
In particular:
quality={0}could be lost on Web because it was treated as a falsy value;quality={100};0...100,NaN, andundefinedwere not normalized consistently;As a result, applications could not reliably control encoded image size, encoding effort, or lossy/lossless output across platforms.
What changed
This PR extends both
encodeToBytes()andencodeToBase64()with an optionallosslessargument:Encoding behavior
0...100.NaNis treated asundefined.100;losslessargument.6;0to maximum compression and100to no zlib compression;losslessargument.lossless={false}always produces lossyVP8;lossless={true}always produces losslessVP8L;losslesspreserves the existing behavior: quality100or omitted quality selects lossless WebP with effort75, while lower values select lossy WebP.Existing two-argument calls remain backward-compatible.
Web / CanvasKit
The required encoder options are implemented in a custom CanvasKit build because stock CanvasKit does not expose them.
The package now includes a matching CanvasKit JavaScript/WASM pair and validates their encoding ABI during initialization. Loading a stock or mismatched WASM binary fails with an actionable error instead of invoking an incompatible binding.
The custom build is reproducible with:
The CanvasKit patch, build metadata, bundled runtime, setup script, example application, Remotion configuration, Jest environment, headless loader, and Web documentation have been updated accordingly.
Web applications must copy and deploy the
canvaskit.wasmbundled with the same version of@shopify/react-native-skia. The JavaScript and WASM files must be upgraded and cached together.Test plan
-Infinity, values below0,0;100, values above100,Infinity;NaNandundefined.Web / Native parity results
VP8orVP8L) for every tested option.0;Infinity.Raw container sizes differ because Web currently tags images as sRGB while Native raw
ImageInfodoes not carry a color space:+13bytes;+474bytes;+482bytes.This is an existing color-space metadata difference and does not affect encoded pixels, quality, or lossless behavior. Color-space propagation is intentionally outside the scope of this PR.