Image decoding
import { decodePng, InvalidPngError } from "@jolly-pixel/pixel-draw.renderer";
const { width, height, pixels } = await decodePng(bytes);decodePng reads 8-bit, non-interlaced PNG images into pixels, an Uint8ClampedArray of RGBA8 samples laid out row-major from the top-left corner. Grayscale, truecolor, and indexed images are all expanded to RGBA; indexed images honour a tRNS table, and images without alpha become opaque. Chunk CRCs are not verified.
Anything it cannot read throws InvalidPngError: a payload that is not a PNG, a bit depth other than 8, an interlaced image, an unknown color type, an indexed image with no PLTE chunk, or an unknown scanline filter.
Why a hand-written decoder
It runs unchanged in both environments, because inflation goes through DecompressionStream rather than node:zlib. That covers the two places where the platform will not decode an image:
- The seed pipeline runs in Node before any browser exists, and the server has no
createImageBitmap. - Safari has no
ImageDecoder, and every canvas decoder premultiplies alpha, sorgba(200, 100, 50, 3)comes back asrgba(170, 85, 85, 3).
decodeRasterBlob and decodeRasterCanvas therefore try ImageDecoder first, fall back to decodePng for PNG payloads, and only then reach for a canvas. Both lossless paths return the file's own samples with no color management applied, which is what a pixel-art editor needs.
Encoding
There is no matching encoder. Exporting goes through canvas.toBlob (encodeSelectionPng), and the persisted document format is JSON rather than PNG, since it carries UV regions that an image format cannot. See the asset kind.