npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@sdxc/png

v2026.10.23

Published

PNG encoder and decoder over Web Streams, with no WebAssembly

Readme

@sdxc/png

PNG encoder and decoder over Web Streams, with no WebAssembly.

Installation

npm add @sdxc/png

Decode failures are values from @sdxc/result, which installs alongside. Compression goes through CompressionStream and DecompressionStream, so it runs in Workers, Bun, Node and the browser.

Usage

Encode pixels

import { encode } from "@sdxc/png";

let data = new Uint8ClampedArray(width * height * 4); // straight RGBA, top row first
let bytes = await encode({ width, height, data });

return new Response(bytes, { headers: { "content-type": "image/png" } });

An image with no transparent pixel is written as RGB, a quarter smaller before compression. Pass { alpha: "keep" } to always write RGBA.

Decode a file

import { decode } from "@sdxc/png";
import { isFailure } from "@sdxc/result";

let image = await decode(bytes);
if (isFailure(image)) return new Response(image.error.message, { status: 422 });
image.data.data; // Uint8ClampedArray, RGBA, 8 bits per channel

Every color type and bit depth, palettes with transparency, 16-bit samples and Adam7 interlacing read back as the same 8-bit RGBA layout.

API

encode(image: PNG.Image, options?: PNG.EncodeOptions): Promise<Uint8Array>

The complete PNG file. Each scanline takes the filter that compresses best by libpng's heuristic, at zlib's default level. Rejects with a RangeError when data is not width * height * 4 bytes, a dimension is not a positive integer, or a text entry is not valid tEXt.

  • options.alpha: "auto" (default) drops the alpha channel when every pixel is opaque; "keep" writes it anyway
  • options.text: tEXt entries, such as { Software: "My App" }; keywords are 1–79 Latin-1 characters

decode(bytes: Uint8Array): Promise<Result<PNG.Image, PNGError>>

The image as 8-bit straight RGBA. 16-bit samples round to 8 bits; ancillary chunks such as gAMA, iCCP and tEXt are read past.

PNGError

An Error with a code:

| Code | When | | ------------- | ------------------------------------------------------------------------ | | signature | The bytes do not start with the PNG signature | | crc | A chunk's CRC-32 disagrees with its contents | | header | IHDR, PLTE or chunk order breaks the specification | | unsupported | An unknown critical chunk, filter or method, or an image over 2²⁸ pixels | | truncated | The file or its pixel data ends early | | inflate | The compressed pixel data is corrupt |

Types

  • PNG.Image: { width: number; height: number; data: Uint8ClampedArray }, the layout of the web's ImageData
  • PNG.EncodeOptions: { alpha?: "auto" | "keep"; text?: Record<string, string> }
  • PNGError.Code: "signature" | "crc" | "header" | "unsupported" | "truncated" | "inflate"

Pattern: Round-Trip Assertions in Tests

Decoding what a renderer encoded lets a test assert on pixels rather than on file bytes.

import { decode, encode } from "@sdxc/png";
import { unwrap } from "@sdxc/result";
import { expect, test } from "vitest";

test("the first pixel is red", async () => {
	let data = new Uint8ClampedArray(4 * 4 * 4).fill(255);
	data.set([255, 0, 0, 255], 0);

	let image = unwrap(await decode(await encode({ width: 4, height: 4, data })));
	expect([...image.data.subarray(0, 4)]).toEqual([255, 0, 0, 255]);
});

Versioning

Releases are dated rather than semantic. A version is the UTC date it was published, written YYYY.M.D, so 2026.9.4 is the release from 4 September 2026. At most one release goes out per day.

Those numbers say when, not what: a later date means a later release and carries no compatibility promise. Any release may change or remove an export.

Depend on one exact date, and move it when you are ready to take the change:

{
	"dependencies": {
		"@sdxc/png": "2026.9.4"
	}
}

A caret or tilde range reads the date as major, minor and patch, so it accepts every later release in the same year. An exact version keeps the upgrade yours to schedule.

License

MIT

Author

Sergio Xalambrí