fast-png
A pure JavaScript PNG encoder and decoder supporting 16-bit depth, Adam7 interlacing, indexed color, and full APNG animation frames.
Repository Health
Technical Analysis
fast-png is a zero-native-dependency PNG codec for JavaScript and TypeScript, decoding and encoding images entirely in pure JS via the fflate zlib implementation and the iobuffer binary reader/writer. It handles the full range of PNG color types — greyscale, truecolour, indexed-palette, and their alpha variants — at both 8-bit and 16-bit depth, with support for Adam7 interlacing on both the decode and encode paths.
Beyond baseline PNG, it decodes APNG (Animated PNG) files frame by frame, correctly applying each frame’s dispose and blend operations, and reads ancillary chunks like embedded ICC color profiles (iCCP), physical pixel resolution (pHYs), and tEXt metadata. Maintained by Zakodium as part of the image-js ecosystem, it ships as an ESM-only TypeScript package with a small, typed API surface (decode, encode, decodeApng, hasPngSignature) suited to both Node.js and browser environments.
What You Get
- Full PNG color type support: greyscale, truecolour, indexed-palette, and their alpha variants at 8-bit or 16-bit depth
- APNG (Animated PNG) decoding with correct per-frame dispose and blend operator handling
- Adam7 interlacing on both decode and encode
- Ancillary chunk support: iCCP embedded color profiles, pHYs physical resolution, and tEXt metadata
- A pure-JS dependency chain (fflate + iobuffer) with no native bindings to compile
Common Use Cases
- Server-side image pipelines - decode uploaded PNGs to raw pixel arrays for processing without a native imaging library
- Browser-based image editors - encode canvas pixel data back to PNG bytes for download or upload
- Scientific/imaging tooling - part of the image-js suite, used to read/write PNGs carrying precise 16-bit pixel data
- Extracting APNG animation frames - decode animated PNGs frame-by-frame for preview or re-encoding
Under The Hood
Architecture
PngDecoder and PngEncoder each extend the IOBuffer class from the iobuffer package, gaining low-level big-endian read/write primitives (readUint32, writeByte, etc.) on which the entire codec is built. The decoder reads the PNG byte stream chunk by chunk (IHDR, PLTE, IDAT, IEND, tRNS, iCCP, tEXt, pHYs, plus the APNG chunks acTL/fcTL/fdAT) via a switch-based dispatcher in png_decoder.ts, feeding IDAT/fdAT payloads incrementally into a streaming zlib inflator (fflate’s Unzlib) rather than buffering the whole compressed stream first. Decompressed scanlines are then deinterlaced and unfiltered by decode_interlace_null.ts / decode_interlace_adam7.ts, which call into apply_unfilter.ts for the five PNG filter types. The encoder in png_encoder.ts mirrors this in reverse: it derives color type/depth from the input, writes chunks with computed CRCs (helpers/crc.ts), and compresses the assembled scanline buffer with zlibSync. Both classes are single, cohesive units per direction with no plugin system, so the abstraction that would break most if changed is the IOBuffer byte-cursor API every chunk reader/writer method depends on.
Tech Stack
Written entirely in TypeScript (99.75% of the codebase) as an ESM-only package ("type": "module") with a single export map entry. Its only two runtime dependencies are fflate (a pure-JS zlib/deflate implementation used for both streaming inflate and one-shot deflate) and iobuffer (a typed binary buffer reader/writer) — both zero-native-dependency, matching the package’s own “fast, pure-JS” positioning. The build uses tsc against tsconfig.build.json, linting runs through eslint-config-cheminfo-typescript (a shared Zakodium org config), formatting is enforced via Prettier, and tests run on Vitest with @vitest/coverage-v8. CI and releases are handled by reusable zakodium/workflows GitHub Actions (Node CI, npm release automation, and a TypeDoc publish step triggered on release).
Code Quality
The test suite is substantial for the package’s size — three spec files (decode.test.ts, encode.test.ts, convert_indexed_to_rgb.test.ts) totaling over 900 lines, exercising round-trip encode/decode, indexed-color palettes, interlacing, and edge cases, run under Vitest with coverage collection. Error handling is explicit rather than swallowed: invalid bit depths, unsupported compression/filter/interlace methods, chunk length mismatches, and malformed tRNS/palette data all throw descriptive errors. Typing is strict, with internal_types.ts modeling PNG enums as const objects plus derived union types, and switch statements marked exhaustive via targeted eslint-disable comments. The single test script chains type-checking, linting, formatting, and the test run together, so none of those gates can silently regress in CI.
API Design
The public surface is deliberately minimal: decode, encode, decodeApng, and hasPngSignature, each taking plain objects/typed arrays rather than requiring consumers to construct classes themselves (the PngDecoder/PngEncoder classes are internal implementation detail, not exported). Options objects (checkCrc, interlace, zlib) have sensible defaults so the common case needs no configuration at all, while power users can still reach zlib-level tuning. Return types are richly typed (DecodedPng, DecodedApng, per-frame types) so consumers get autocomplete on width/channels/palette/transparency without consulting docs, and the hosted TypeDoc site gives a fuller reference beyond the README’s quick-start.