Unofficial bindings for node to libpng.
Please also refer to the Documentation.
- node-libpng
This is a native Addon to NodeJS which delivers prebuilt binaries. Only some environments are supported:
Node Version | Windows 64-Bit | Windows 32-Bit | Linux 64-Bit | Linux 32-Bit | OSX |
---|---|---|---|---|---|
Earlier | ✗ | ✗ | ✗ | ✗ | ✗ |
Node 10 (Abi 64) | ✓ | ✓ | ✓ | ✗ | ✓ |
Node 11 (Abi 67) | ✗ | ✗ | ✗ | ✗ | ✗ |
Node 12 (Abi 72) | ✓ | ✓ | ✓ | ✗ | ✓ |
Node 13 (Abi 79) | ✗ | ✗ | ✗ | ✗ | ✗ |
Node 14 (Abi 83) | ✓ | ✓ | ✓ | ✗ | ✓ |
Node 15 (Abi 88) | ✓ | ✓ | ✓ | ✗ | ✓ |
Node 16 (Abi 93) | ✓ | ✓ | ✓ | ✗ | ✓ |
Multiple ways of reading and decoding PNG encoded images exist:
- readPngFile Reads a PNG file and returns a PngImage instance with the decoded data.
- readPngFileSync Will read a PNG file synchroneously and return a PngImage instance with the decoded image. Example
- decode Will decode a Buffer of raw PNG file data and return a PngImage instance. Example
In order to use the Promise-based API, simply omit the third argument.
import { readPngFile } from "node-libpng";
async function readMyFile() {
const image = await readPngFile("path/to/image.png");
console.log(`Reading was successful. The dimensions of the image are ${image.width}x${image.height}.`);
}
If an error occured while reading the file or decoding the buffer, the Promise which writePngFile
returns will reject with the error.
In order to use the callback-based API, simply provide a callback as the third argument.
import { readPngFile } from "node-libpng";
function readMyFile() {
readPngFile("path/to/image.png", (error, image) => {
if (error !== null) {
// TODO: Check what `error` contains.
}
console.log(`Reading was successful. The dimensions of the image are ${image.width}x${image.height}.`);
});
}
If an error occured while reading the file or decoding the buffer, it will be passed as the first argument to the callback.
Otherwise null
will be passed. The PngImage instance will be passed as the second argument. If an error occured,
it will be undefined
.
It is possible to read the image from the disk in a blocking way, using Node's readFileSync
:
import { readPngFileSync } from "node-libpng";
function readMyFile() {
const image = readPngFileSync("path/to/image.png");
console.log(`Reading was successful. The dimensions of the image are ${image.width}x${image.height}.`);
}
If an error occured while reading the file or decoding the buffer, it will be throw
n.
Buffers can be decoded directly into a PngImage instance:
import { decode } from "node-libpng";
function decodeMyBuffer() {
const buffer = ...; // Some buffer containing the raw PNG file's data.
const image = decode(buffer);
console.log(`Decoding was successful. The dimensions of the image are ${image.width}x${image.height}.`);
}
If an error occured while decoding the buffer, it will be throw
n.
The decoding happens synchroneously.
Multiple ways for encoding and writing raw image data exist:
- writePngFile Writes the raw data into a PNG file using Promises or a callback.
- writePngFileSync Writes the raw data into a PNG file synchroneously. Example
- encode Encodes the raw data into a Buffer containing the PNG file's data. Example
- PngImage contains methods for encoding and writing modified image data:
- PngImage.encode The same as calling the free function encode with
PngImage.data
. - PngImage.write The same as calling the free function writePngFile with
PngImage.data
. - PngImage.writeSync The same as calling the free function writePngFileSync with
PngImage.data
.
- PngImage.encode The same as calling the free function encode with
In order to use the Promise-based API, simply omit the 4th argument.
import { writePngFile } from "node-libpng";
async function writeMyFile() {
// Let's write a 100x60 pixel PNG file.
const imageData = Buffer.alloc(100 * 60 * 3);
// TODO: Manipulate the image data somehow.
const options = {
width: 100,
height: 60
};
await writePngFile("path/to/image.png", imageData, options);
console.log("File successfully written.");
}
In this example, a 100x60 pixel image will be encoded and written to disk. Based on the length of the buffer and the provided dimensions the presence of an alpha channel will be automatically calculated.
It is possible to omit either width
or height
from the options.
If an error occured while writing the file or encoding the buffer, the Promise which writePngFile
returns will
reject with the error.
In order to use the callback-based API, provide a callback as the 4th argument.
import { writePngFile } from "node-libpng";
function writeMyFile() {
// Let's write a 100x60 pixel PNG file.
const imageData = Buffer.alloc(100 * 60 * 3);
// TODO: Manipulate the image data somehow.
const options = {
width: 100,
height: 60
};
await writePngFile("path/to/image.png", imageData, options, (error) => {
if (error !== null) {
// TODO: Check what `error` contains.
}
console.log("File successfully written.");
});
}
In this example, a 100x60 pixel image will be encoded and written to disk. Based on the length of the buffer and the provided dimensions the presence of an alpha channel will be automatically calculated.
It is possible to omit either width
or height
from the options.
If an error occured while writing the file or encoding the buffer, it will be passed as the first and only argument
in the callback. Otherwise null
will be passed.
It is possible to write the image to disk in a blocking way, using Node's writeFileSync
:
import { writePngFileSync } from "node-libpng";
function writeMyFile() {
// Let's write a 100x60 pixel PNG file.
const imageData = Buffer.alloc(100 * 60 * 3);
// TODO: Manipulate the image data somehow.
const options = {
width: 100,
height: 60
};
writePngFileSync("path/to/image.png", imageData, options);
console.log("File successfully written.");
}
In this example, a 100x60 pixel image will be encoded and written to disk. Based on the length of the buffer and the provided dimensions the presence of an alpha channel will be automatically calculated.
It is possible to omit either width
or height
from the options.
If an error occured while writing the file or encoding the buffer, it will be throw
n.
Buffers can be encoded directly from a buffer containing the raw pixel data:
import { encode } from "node-libpng";
function encodeMyBuffer() {
const buffer = ...; // Some buffer containing the raw pixel data.
const options = {
width: 100,
height: 100
};
const encodedPngData = encode(buffer);
// The Buffer `encodedPngData` now contains the raw PNG-encoded data.
console.log("File successfully encoded.");
}
Based on the length of the buffer and the provided dimensions the presence of an alpha channel will be automatically calculated.
If an error occured while encoding the buffer, it will be throw
n.
The encoding happens synchroneously.
PNG specifies five different types of colors:
In the PngImage.data buffer the colors are stored the way they were encoded in the PNG image. This library provides utilities for accessing the pixels in both the native format as well as rgba format.
In order to retrieve the color in the image's native format at a given position PngImage.at can be used.
import { readPngFileSync } from "node-libpng";
const image = readPngFileSync("path/to/grayscale-image.png");
const color = image.at(10, 10);
const colorType = image.colorType;
// Will log: "The color type of the image is gray-scale. Pixel at 10,10 is of color 168."
console.log(`The color type of the image is ${colorType}. Pixel at 10,10 is of color ${color.join(", ")}.`);
const image = readPngFileSync("path/to/rgb-image.png");
const color = image.at(10, 10);
const colorType = image.colorType;
// Will log: "The color type of the image is rgb. Pixel at 10,10 is of color 100, 150, 200."
console.log(`The color type of the image is ${colorType}. Pixel at 10,10 is of color ${color.join(", ")}.`);
Dealing with all of these different color formats can be quite irritating. A set of conversion utilities as for example a utility for converting any color format to rgba (convertToRGBA) exist.
It is also possible to do this automatically.
A method for retrieving the automatically converted color exists: PngImage.rgbaAt.
It will convert any color into rgba format. Unlike other implementations, all color formats are supported.
const image = readPngFileSync("path/to/any-color-format-image.png");
const color = image.rgbaAt(10, 10);
const colorType = image.colorType;
// Will log: "The color type of the image is palette. Pixel at 10,10 is of color 100, 150, 200, 255."
console.log(`The color type of the image is ${colorType}. Pixel at 10,10 is of color ${color.join(", ")}.`);
Several basic utilities for modifying an image exist.
A simple utility for cropping an image to a sub-rectangle exists: PngImage.crop. It's a simplified version of PngImage.resizeCanvas.
It will reduce the image in-place to the specified rectangle:
import { readPngFileSync, rect } from "node-libpng";
const image = readPngFileSync("path/to/image.png");
image.crop(rect(10, 10, 100, 100));
// Will log: "New dimensions: 100x100".
console.log(`New dimensions: ${image.width}x${image.height}.`);
Use PngImage.resizeCanvas for advanced cropping operations.
Take a look at ResizeCanvasArguments.
It takes a configuration object which makes it possible to provide:
- An offset to the top left (a padding).
- A fill color for uncovered regions.
- A subrectangle of the image to use.
- The new dimensions for the image.
In the following example, a 10 pixel margin is applied to the top and to the left and a 50x50 pixel area is copied from the image at offset 20,20. The image is resized to 100x100, so a 40 pixel margin will exist to the right and to the bottom. The background is filled in red:
import { readPngFileSync, rect, xy, colorRGB } from "node-libpng";
const image = readPngFileSync("path/to/image.png");
image.resizeCanvas({
offset: xy(10, 10),
clip: rect(20, 20, 50, 50),
dimensions: xy(100, 100),
fillColor: colorRGB(255, 0, 0),
});
// Will log: "New dimensions: 100x100".
console.log(`New dimensions: ${image.width}x${image.height}.`);
Use PngImage.copyFrom to copy an area of one image into another one:
import { readPngFileSync, xy, rect } from "node-libpng";
const source = readPngFileSync("path/to/source-image.png");
const target = readPngFileSync("path/to/target-image.png");
target.copyFrom(source, xy(10, 10), rect(100, 100, 50, 50));
The above example will copy a 50x50 rectangle from the source image at position 100,100 to the target image at position 10,10. The offset and the subrectangle can be omitted to copy the whole source image to the top left corner of the target image.
Use PngImage.fill to fill an area with a specified color:
import { readPngFileSync, colorRGB, rect } from "node-libpng";
const image = readPngFileSync("path/to/source-image.png");
// Change a 100x100 pixel area at offset 10,10 to red:
image.fill(colorRGB(255, 0, 0), rect(10, 10, 100, 100));
With PngImage.set an individual pixel's color can be changed:
import { readPngFileSync, colorRGB, xy } from "node-libpng";
const image = readPngFileSync("path/to/source-image.png");
// Change the pixel at 10,10 to red:
image.set(colorRGB(255, 0, 0), xy(10, 10));
As it is a native addon, node-libpng is much faster than libraries like pngjs:
The chart below shows the comparison of decoding an image between pngjs (sync api) and node-libpng. The time to fully decode a 4096x4096 image is measured (Higher is better).
(The x-axis scale shows the amount of fully decoded images per second.)
The chart below shows the comparison of encoding an image between pngjs (sync api) and node-libpng. The time to fully encode a 4096x4096 image is measured (Higher is better).
(The x-axis scale shows the amount of fully encoded images per second.)
The chart below shows the comparison of accessing all pixels in a decoded image between pngjs and node-libpng. The time to fully access every pixel in the raw data is measured (Higher is better).
(The x-axis scale shows the amount of fully accessed images per second.)
Yarn is used instead of npm, so make sure it is installed, probably: npm install -g yarn
.
Generally, it should be enough to just run:
make
which will install all node dependencies, compile the dependencies and C++ code, compile typescript, execute all test, lint the sources and build the docs.
Libpng requires an OS specific configuration headerfile pnglibconf.h
.
This can be generated by executing:
git submodule update --init --recursive
cd deps/libpng
mkdir build
cd build
cmake ..
make
cp pnglibconf.h ../../config/linux/
- Frederick Gnodtke