NATIVE NODE ADDON · POWERED BY RUST

Fast image processing
in Rust.

Resize, convert and optimize JPEG, PNG, WebP, AVIF and more. Rust-powered native addons for Node.js, with WebAssembly for browser Workers.

$vp add @napi-rs/image
transform.ts
import { readFile, writeFile } from 'node:fs/promises'
import {
  Transformer,
  ChromaSubsampling,
} from '@napi-rs/image'

const input = await readFile('./input.jpg')
const webp = await new Transformer(input)
  .rotate()
  .resize(225)
  .webp(75)

const avif = await new Transformer(input)
  .rotate()
  .resize(225)
  .avif({
    quality: 70,
    chromaSubsampling: ChromaSubsampling.Yuv420,
  })

await writeFile('./output.webp', webp)
await writeFile('./output.avif', avif)
1.8×
recorded WebP pipeline vs sharp
14
formats & raw pixels
−44%
best lossless sample saving

The speed ratio is from the recorded M1 Max benchmark with a thread pool of 10; savings are from the sample images below. Results depend on your workload.

01 — BENCHMARK

A measured workload

Historical single-JPEG results · Apple M1 Max · macOS 12.3.1. Pipeline: EXIF rotate → resize(225) → WebP or AVIF. Node.js and package versions were not recorded.

recorded WebP throughput ratio

1.8×

WebP pipeline throughput relative to sharp with UV_THREADPOOL_SIZE=10 in this recorded run.

defaultbaseline
WebP1.20× sharp
napi
202 ops/s
sharp
169 ops/s
AVIF1.08× sharp
napi
26 ops/s
sharp
24 ops/s
UV_THREADPOOL_SIZE=1010 threads
WebP1.81× sharp
napi
431 ops/s
sharp
238 ops/s
AVIF1.13× sharp
napi
36 ops/s
sharp
32 ops/s

The script uses one EXIF JPEG, WebP quality 75, and AVIF quality 70 with 4:2:0 chroma. Equal quality numbers do not establish equal visual quality across encoders. These historical results are not a current-release comparison or a prediction for other workloads. Inspect the benchmark and measure your own images, quality targets and hardware.

02 — COMPRESSION

See the bytes disappear

Compare these sample outputs. Sizes and visible differences depend on the source image and encoder settings.

new Transformer(PNG).webp(75)original
new Transformer(PNG).webp(75)Lossy
1192 KB → 83 KB−93%
new Transformer(PNG).avif({ quality: 75 })original
new Transformer(PNG).avif({ quality: 75 })Lossy
1192 KB → 108 KB−91%
pngQuantize({ maxQuality: 75 })original
pngQuantize({ maxQuality: 75 })Lossy
1192 KB → 216 KB−82%
new Transformer(PNG).avif({ quality: 100 })original
new Transformer(PNG).avif({ quality: 100 })Quality 100
1192 KB → 581 KB−51%
new Transformer(PNG).webpLossless()original
new Transformer(PNG).webpLossless()Lossless
1192 KB → 673 KB−44%
losslessCompressPng()original
losslessCompressPng()Lossless
1192 KB → 873 KB−27%
compressJpeg(JPEG, { quality: 75 })original
compressJpeg(JPEG, { quality: 75 })Lossy
192 KB → 100 KB−48%
compressJpeg()original
compressJpeg()Lossless
192 KB → 182 KB−5%
03 — FORMATS

Supported formats

WebP and AVIF decode and encode. HEIC requires macOS or Windows with an OS codec; it is unavailable in the browser. GIF, OpenEXR and farbfeld are not enabled in the published build.

FormatDecodeEncodeNotes
JPEGrw✓✓
PNGrw✓✓
WebPrw✓✓
AVIFrw✓✓
TIFFrw✓✓
BMPrw✓✓
ICOrw✓✓
TGA✗✓input is not auto-detected
PNMrw✓✓
HEIC / HEIFrw✓✓macOS / Windows with OS codec only
Raw pixelsrw✓✓RGBA8 input; native-endian output
SVG✓✗rasterize with fromSvg()
DDS (DXT1/3/5)✓✗decode only
HDR (Radiance)✓✗decode only

14 formats and raw pixel I/O · 10 bidirectional · 3 decode-only

Read the format and platform limitations before choosing a pipeline.

04 — FILTERS

Built-in filters

grayscale, blur, hue-rotate, contrast and more — applied natively in Rust.

grayscale
grayscale
invert
invert
blur
blur
huerotate
huerotate
contrast
contrast
brighten
brighten
crop
crop
05 — PIPELINE

One pipeline

Read image bytes, optimize PNG, or resize and convert to WebP and AVIF.

optimize.ts
import { readFileSync, writeFileSync } from 'node:fs'
import {
  Transformer,
  losslessCompressPng,
  ResizeFilterType,
  ChromaSubsampling,
} from '@napi-rs/image'

const PNG = readFileSync('./input.png')

writeFileSync('out.png', await losslessCompressPng(PNG))
writeFileSync('out.webp', await new Transformer(PNG)
  .resize(800, null, ResizeFilterType.Lanczos3)
  .webp(75))
writeFileSync('out.avif', await new Transformer(PNG)
  .resize(800, null, ResizeFilterType.Lanczos3)
  .avif({ quality: 75, chromaSubsampling: ChromaSubsampling.Yuv420 }))

PLAYGROUND

Try it in your browser

Try the WASM build in a supported browser — images stay on your device.