Brooooooklyn/Image

Image processing library.

Rust

417

261 commits

updated Oct 6, 2026

See the code

README

@napi-rs/image

Rust-powered image processing for Node.js: resize, convert and optimize JPEG, PNG, WebP, AVIF and more. Prebuilt native addons handle server and build-script workloads; a WebAssembly build powers browser applications with a Worker and cross-origin isolation.

Documentation · Browser playground · API reference · Releases

npm version Downloads CI

Install and run

npm install @napi-rs/image

Use a current Node.js LTS release. Save this as transform.mjs, put a JPEG at input.jpg, and run node transform.mjs:

import { readFile, writeFile } from 'node:fs/promises'
import { Transformer, ResizeFilterType } from '@napi-rs/image'

const input = await readFile('./input.jpg')
const output = await new Transformer(input)
  .rotate() // apply EXIF orientation
  .resize(800, null, ResizeFilterType.Lanczos3)
  .webp(80)

await writeFile('./output.webp', output)

Transformer accepts encoded bytes, chains transforms, and returns a Buffer from an encoder. Read and write files with Node.js APIs. Async encoders run on a background thread pool; *Sync variants block the calling thread.

Optimize without changing formats

Use the standalone optimizers for PNG or JPEG input. They return new bytes; they do not modify your source file. Output size depends on the image and settings.

import { readFile, writeFile } from 'node:fs/promises'
import { compressJpeg, losslessCompressPng, pngQuantize } from '@napi-rs/image'

const png = await readFile('./input.png')
const jpeg = await readFile('./input.jpg')

await writeFile('./optimized.png', await losslessCompressPng(png))
await writeFile('./palette.png', await pngQuantize(png, { maxQuality: 75 })) // lossy
await writeFile('./optimized.jpg', await compressJpeg(jpeg)) // lossless coefficient optimization
await writeFile('./smaller.jpg', await compressJpeg(jpeg, { quality: 75 })) // lossy re-encode

For SVG rasterization, AVIF settings, compositing and batch processing, see the recipes, format guides and repository example. Complete TypeScript signatures ship with the package in index.d.ts.

Format support

The following describes the published 1.15.0 API. It processes still images; it does not expose an animation pipeline. Color-type support varies by encoder.

FormatInputOutputNotes
JPEGYesYesBaseline/progressive input; baseline output
PNGYesYesIncludes alpha and 16-bit input
WebPYesYesLossy or lossless output
AVIFYesYesDecoded to 8-bit pixels
HEIC / HEIFmacOS / WindowsmacOS / WindowsHEVC-in-HEIF; requires an OS codec
TIFFYesYesBaseline, LZW and PackBits input; no fax support
BMPYesYes
ICOYesYes
PNMYesYesPBM, PGM, PPM and PAM input
TGANoYesInput cannot be auto-detected by Transformer
DDSYesNoDXT1, DXT3 and DXT5 input
HDR (Radiance)YesNo
SVGTransformer.fromSvg()NoRasterized before further transforms
Raw pixelsTransformer.fromRgbaPixels()rawPixels()Input: RGBA8; output: native-endian bytes in the image's color type

GIF, OpenEXR and farbfeld codecs are not enabled in the published build. Although farbfeld() / farbfeldSync() appear in the API, they currently reject with an unsupported-format error.

Platforms

Prebuilt native packages are published for:

PlatformArchitectures
macOSx64, arm64
Windowsx64, arm64, ia32 (MSVC)
Linux, glibcx64, arm64, armv7 (gnueabihf)
Linux, muslx64, arm64
Androidarm64
FreeBSDx64

Keep npm optional dependencies enabled so the matching native package can be installed. Browser applications need the separate @napi-rs/image-wasm32-wasi package and the setup below; installing the native package alone does not make every runtime supported.

HEIC on macOS and Windows

HEIC decoding and encoding use ImageIO on macOS and Windows Imaging Component (WIC) on Windows. The package does not bundle a HEVC codec. HEIC is unavailable on Linux, Android, FreeBSD and in the browser/WASM build.

  • Windows: the HEIF Image Extensions and HEVC Video Extensions must be available to WIC. Do not assume they are installed on Windows Server or CI hosts; operations fail when the codec is missing. Decoding produces RGBA8, including for 10-bit sources. Encoding produces opaque 8-bit images; alpha is flattened and bitDepth: 10 is rejected.
  • macOS: 8-bit sources decode to RGBA8 and 10-bit sources to RGBA16. Encoding supports 8 or 10 bits; by default, 16-bit input selects 10-bit output. Quality 90–100 shares the same encoder ceiling; HEIC encoding has no lossless mode.
  • HEIC input is normalized to sRGB without preserving an ICC profile. HDR gain maps are not reconstructed. Use .rotate() to apply orientation tags where the OS decoder has not already applied them.
// macOS or Windows with the required codec; input is encoded image bytes.
const jpeg = await new Transformer(heicBytes).rotate().jpeg(80)
const heic = await new Transformer(pngBytes).heic({ quality: 80 })

Browser WebAssembly

The playground processes images locally in a dedicated Worker. Its setup requires:

  • @napi-rs/image-wasm32-wasi, module Workers and a bundler that emits the nested WASI worker and .wasm asset.
  • A Buffer polyfill initialized before importing the WASM module, and async image methods so the worker can service thread requests.
  • A secure context (HTTPS or localhost), SharedArrayBuffer, and cross-origin isolation: Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp on the document, with compatible policies on workers and assets.

Follow the browser setup guide and the working Vite configuration, Worker and response headers. HEIC is not available in WASM.

Performance

These are historical results already recorded in this repository, not a new benchmark of 1.15.0. The benchmark script uses one EXIF JPEG (with-exif.jpg), applies orientation, resizes to width 225, and encodes WebP (quality 75) or AVIF (quality 70, 4:2:0). Work is submitted concurrently up to the CPU count.

Recorded hardware: Apple M1 Max, macOS 12.3.1, arm64. Node.js and package versions were not recorded with the results.

Thread poolOutput@napi-rs/imagesharp
DefaultWebP202 ops/s169 ops/s
DefaultAVIF26 ops/s24 ops/s
UV_THREADPOOL_SIZE=10WebP431 ops/s238 ops/s
UV_THREADPOOL_SIZE=10AVIF36 ops/s32 ops/s

The 1.8× ratio applies only to the recorded WebP pipeline with a thread pool of 10. Equal numeric quality settings do not establish equal visual quality across encoders. Measure your own images, output sizes, quality targets and deployment hardware before choosing a library or concurrency setting.

To repeat the workload from a development checkout with dependencies and a native binding installed:

node bench/bench.mjs
UV_THREADPOOL_SIZE=10 node bench/bench.mjs

Moving from sharp

Both libraries can express a resize-and-encode pipeline, but their APIs and defaults differ. In @napi-rs/image, new Transformer(bytes) takes the input, .webp(80) returns the output directly, and .rotate() applies EXIF orientation. There is no trailing .toBuffer() call. See the migration recipe for a small example and behavior differences to check.

License and credits

MIT. See LICENSE and codec credits.

Brooooooklyn/Image

Image processing library.

Rust

417

261 commits

updated Oct 6, 2026

See the code

README

@napi-rs/image

Rust-powered image processing for Node.js: resize, convert and optimize JPEG, PNG, WebP, AVIF and more. Prebuilt native addons handle server and build-script workloads; a WebAssembly build powers browser applications with a Worker and cross-origin isolation.

Documentation · Browser playground · API reference · Releases

npm version Downloads CI

Install and run

npm install @napi-rs/image

Use a current Node.js LTS release. Save this as transform.mjs, put a JPEG at input.jpg, and run node transform.mjs:

import { readFile, writeFile } from 'node:fs/promises'
import { Transformer, ResizeFilterType } from '@napi-rs/image'

const input = await readFile('./input.jpg')
const output = await new Transformer(input)
  .rotate() // apply EXIF orientation
  .resize(800, null, ResizeFilterType.Lanczos3)
  .webp(80)

await writeFile('./output.webp', output)

Transformer accepts encoded bytes, chains transforms, and returns a Buffer from an encoder. Read and write files with Node.js APIs. Async encoders run on a background thread pool; *Sync variants block the calling thread.

Optimize without changing formats

Use the standalone optimizers for PNG or JPEG input. They return new bytes; they do not modify your source file. Output size depends on the image and settings.

import { readFile, writeFile } from 'node:fs/promises'
import { compressJpeg, losslessCompressPng, pngQuantize } from '@napi-rs/image'

const png = await readFile('./input.png')
const jpeg = await readFile('./input.jpg')

await writeFile('./optimized.png', await losslessCompressPng(png))
await writeFile('./palette.png', await pngQuantize(png, { maxQuality: 75 })) // lossy
await writeFile('./optimized.jpg', await compressJpeg(jpeg)) // lossless coefficient optimization
await writeFile('./smaller.jpg', await compressJpeg(jpeg, { quality: 75 })) // lossy re-encode

For SVG rasterization, AVIF settings, compositing and batch processing, see the recipes, format guides and repository example. Complete TypeScript signatures ship with the package in index.d.ts.

Format support

The following describes the published 1.15.0 API. It processes still images; it does not expose an animation pipeline. Color-type support varies by encoder.

FormatInputOutputNotes
JPEGYesYesBaseline/progressive input; baseline output
PNGYesYesIncludes alpha and 16-bit input
WebPYesYesLossy or lossless output
AVIFYesYesDecoded to 8-bit pixels
HEIC / HEIFmacOS / WindowsmacOS / WindowsHEVC-in-HEIF; requires an OS codec
TIFFYesYesBaseline, LZW and PackBits input; no fax support
BMPYesYes
ICOYesYes
PNMYesYesPBM, PGM, PPM and PAM input
TGANoYesInput cannot be auto-detected by Transformer
DDSYesNoDXT1, DXT3 and DXT5 input
HDR (Radiance)YesNo
SVGTransformer.fromSvg()NoRasterized before further transforms
Raw pixelsTransformer.fromRgbaPixels()rawPixels()Input: RGBA8; output: native-endian bytes in the image's color type

GIF, OpenEXR and farbfeld codecs are not enabled in the published build. Although farbfeld() / farbfeldSync() appear in the API, they currently reject with an unsupported-format error.

Platforms

Prebuilt native packages are published for:

PlatformArchitectures
macOSx64, arm64
Windowsx64, arm64, ia32 (MSVC)
Linux, glibcx64, arm64, armv7 (gnueabihf)
Linux, muslx64, arm64
Androidarm64
FreeBSDx64

Keep npm optional dependencies enabled so the matching native package can be installed. Browser applications need the separate @napi-rs/image-wasm32-wasi package and the setup below; installing the native package alone does not make every runtime supported.

HEIC on macOS and Windows

HEIC decoding and encoding use ImageIO on macOS and Windows Imaging Component (WIC) on Windows. The package does not bundle a HEVC codec. HEIC is unavailable on Linux, Android, FreeBSD and in the browser/WASM build.

  • Windows: the HEIF Image Extensions and HEVC Video Extensions must be available to WIC. Do not assume they are installed on Windows Server or CI hosts; operations fail when the codec is missing. Decoding produces RGBA8, including for 10-bit sources. Encoding produces opaque 8-bit images; alpha is flattened and bitDepth: 10 is rejected.
  • macOS: 8-bit sources decode to RGBA8 and 10-bit sources to RGBA16. Encoding supports 8 or 10 bits; by default, 16-bit input selects 10-bit output. Quality 90–100 shares the same encoder ceiling; HEIC encoding has no lossless mode.
  • HEIC input is normalized to sRGB without preserving an ICC profile. HDR gain maps are not reconstructed. Use .rotate() to apply orientation tags where the OS decoder has not already applied them.
// macOS or Windows with the required codec; input is encoded image bytes.
const jpeg = await new Transformer(heicBytes).rotate().jpeg(80)
const heic = await new Transformer(pngBytes).heic({ quality: 80 })

Browser WebAssembly

The playground processes images locally in a dedicated Worker. Its setup requires:

  • @napi-rs/image-wasm32-wasi, module Workers and a bundler that emits the nested WASI worker and .wasm asset.
  • A Buffer polyfill initialized before importing the WASM module, and async image methods so the worker can service thread requests.
  • A secure context (HTTPS or localhost), SharedArrayBuffer, and cross-origin isolation: Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp on the document, with compatible policies on workers and assets.

Follow the browser setup guide and the working Vite configuration, Worker and response headers. HEIC is not available in WASM.

Performance

These are historical results already recorded in this repository, not a new benchmark of 1.15.0. The benchmark script uses one EXIF JPEG (with-exif.jpg), applies orientation, resizes to width 225, and encodes WebP (quality 75) or AVIF (quality 70, 4:2:0). Work is submitted concurrently up to the CPU count.

Recorded hardware: Apple M1 Max, macOS 12.3.1, arm64. Node.js and package versions were not recorded with the results.

Thread poolOutput@napi-rs/imagesharp
DefaultWebP202 ops/s169 ops/s
DefaultAVIF26 ops/s24 ops/s
UV_THREADPOOL_SIZE=10WebP431 ops/s238 ops/s
UV_THREADPOOL_SIZE=10AVIF36 ops/s32 ops/s

The 1.8× ratio applies only to the recorded WebP pipeline with a thread pool of 10. Equal numeric quality settings do not establish equal visual quality across encoders. Measure your own images, output sizes, quality targets and deployment hardware before choosing a library or concurrency setting.

To repeat the workload from a development checkout with dependencies and a native binding installed:

node bench/bench.mjs
UV_THREADPOOL_SIZE=10 node bench/bench.mjs

Moving from sharp

Both libraries can express a resize-and-encode pipeline, but their APIs and defaults differ. In @napi-rs/image, new Transformer(bytes) takes the input, .webp(80) returns the output directly, and .rotate() applies EXIF orientation. There is no trailing .toBuffer() call. See the migration recipe for a small example and behavior differences to check.

License and credits

MIT. See LICENSE and codec credits.

Significant stargazers

JounQin

677 followers · starred Apr 2025

Marvin Hagemeister

1,712 followers · starred Feb 2026

Shu Ding

8,346 followers · starred Apr 2022

Kainoa Kanter

598 followers · starred Jun 2026