node-pnglib

Pure JavaScript PNG encoder for Node.js. Port of PNGlib.
Zero runtime dependencies. Generates indexed-color (palette) PNGs with no external tooling.
Compatibility
| Platform | Node.js versions | Status |
|---|---|---|
| Linux | 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 20, 22, 24 | CI-passed |
| macOS | 18, 20, 22, 24 | CI-passed |
- Engine requirement:
>=4.4.0 - No native dependencies — works on any platform Node.js runs on
- Tested across 17 Node.js major versions on Linux and 4 on macOS
- Pure JS with no external runtime deps (only
color-namefor string parsing)
Installation
npm install node-pnglib
Quick Start
Minimal 1×1 red pixel:
const PNGlib = require('node-pnglib');
const png = new PNGlib(1, 1);
png.buffer[png.index(0, 0)] = png.color('red');
require('fs').writeFileSync('out.png', png.getBuffer());
300×300 blue square (multi-block stress test):
const PNGlib = require('node-pnglib');
const png = new PNGlib(300, 300, undefined, 'white');
for (let y = 0; y < 300; y++)
for (let x = 0; x < 300; x++)
png.setPixel(x, y, 'blue');
require('fs').writeFileSync('blue.png', png.getBuffer());
API Reference
new PNGlib(width, height, depth?, backgroundColor?)
Creates a new PNG canvas.
| Param | Type | Default | Description |
|---|---|---|---|
width |
number |
— | Width in pixels (must be ≥ 1) |
height |
number |
— | Height in pixels (must be ≥ 1) |
depth |
number |
8 |
Color palette depth (max colors = 2^depth) |
backgroundColor |
ColorArg |
[0, 0, 0, 0] |
Transparent black by default |
Throws if width < 1 or height < 1 or types are not numbers.
png.index(x, y) → number
Returns the buffer index for a pixel position.
x: column (-1 = filter byte for the row)y: row- Internal use typically; exposed for direct buffer manipulation.
png.color(colorArg) → number
Resolves a color to a palette index. Adds the color to the palette if unseen.
| Input format | Examples |
|---|---|
[R, G, B, A] |
[255, 0, 0, 255] (values ≥ 0, clamped to 255) |
| Named color | 'red', 'blue', 'transparent' |
| Hex | '#ff0000', '#f00', '#ff000080' |
| rgb/rgba | 'rgb(255,0,0)', 'rgba(255,0,0,0.5)' |
| hsl/hsla percentages | 'hsl(0,100%,50%)' |
Throws if the color string is invalid or if R/G/B is negative.
png.setPixel(x, y, colorArg)
Sets a pixel. Silently ignores out-of-bounds coordinates.
png.setBgColor(colorArg) → number
Sets the background color (palette index 0). Overrides the default transparent black.
png.getBuffer() → Buffer
Returns the complete PNG as a Buffer (ready to write to disk or send over HTTP).
png.getBase64() → string
Returns the PNG as a Base64-encoded string.
png.deflate() → Buffer
Same as getBuffer() — computes CRC32 and adler32 checksums, finalizes the PNG.
Color Formats
All color inputs support these formats — strings are cached internally:
png.color([255, 0, 0, 255]); // Array RGBA (clamped to [0, 255])
png.color('red'); // Named color
png.color('#ff0000'); // Hex 6-digit
png.color('#f00'); // Hex 3-digit
png.color('#ff000080'); // Hex 8-digit (with alpha)
png.color('rgb(255, 0, 0)'); // rgb()
png.color('rgba(255, 0, 0, 0.5)'); // rgba()
png.color('hsl(0, 100%, 50%)'); // hsl()
png.color('hsla(0, 100%, 50%, 0.5)');// hsla()
png.color('transparent'); // [0, 0, 0, 0]
Internal Architecture
node-pnglib constructs the PNG binary manually:
- Header: PNG signature + IHDR chunk
- Palette: PLTE chunk + tRNS transparency chunk
- Pixel data: IDAT chunk containing raw DEFLATE stored blocks (no compression level)
- Footer: IEND chunk
The image data uses row filter byte 0 (None) for every row. When the pixel data exceeds 65,535 bytes, it's split across multiple DEFLATE stored blocks with automatic adler32 and CRC32 checksums.
Benchmark
150×50 image, drawing a horizontal line of 75 pixels (magenta #F0F), then serializing to PNG buffer.
| Package | Color mode | ops/sec | vs fastest |
|---|---|---|---|
| node-pnglib | indexed palette | 35,270 | 100% |
| pnglib-es6 | indexed palette | 17,127 | 48.6% |
| pnglib | indexed palette | 12,652 | 35.9% |
| fast-png | RGBA truecolor | 5,640 | 16.0% |
| pngjs | RGBA truecolor | 5,545 | 15.7% |
Node.js 24 · Apple M-series MacBook Pro, 2026
node-pnglib is the fastest primarily because it uses indexed palette (1 byte/pixel) with stored (no-compression) DEFLATE blocks, while general-purpose PNG libraries output RGBA truecolor (4 bytes/pixel) with full zlib compression.
Full benchmark source at bench/compare.js. Historical results on older hardware are in bench/test.js.
License
BSD-2-Clause