@radzivon.bartoshyk/pic-scale
v0.7.9
Published
High-performance image resampling
Downloads
620
Maintainers
Readme
@radzivon.bartoshyk/pic-scale
High-performance image resizing for Node.js and the browser, powered by the pic-scale Rust engine with SIMD acceleration (AVX2+FMA on x86-64, NEON on AArch64).
Two delivery modes, one package:
| Mode | Import | When to use |
|------------------|--------------------------------------------|------------------------------|
| Native addon | require('@radzivon.bartoshyk/pic-scale') | Node.js — fastest, file I/O |
| WASM | import ... from '.../pic-scale/wasm' | Browser, Deno, edge runtimes |
Installation
npm install @radzivon.bartoshyk/pic-scaleNative addon (Node.js)
All heavy operations (resize, toBuffer, save) run on Tokio's thread pool
and return Promises, so the event loop is never blocked.
import { Image } from '@radzivon.bartoshyk/pic-scale'
// Open from file — metadata (ICC, EXIF orientation) extracted automatically
const img = await Image.open('photo.jpg')
console.log(img.width, img.height, img.channels)
// Resize
const small = await img.resize(800, 600, {
filter: 'lanczos', // default
mode: 'cover', // fill box, crop from centre
autoOrient: true, // default — bake EXIF rotation into pixels
withIcc: true, // default — copy ICC profile to output
withExif: true, // default — copy EXIF, orientation reset to 1
withXmp: false, // default
workers: 0, // 0 = adaptive (all cores)
})
// Save — format inferred from extension, ICC + EXIF injected automatically
await small.save('small.jpg', { quality: 85 })
// Or encode to a Buffer
const buf = await small.toBuffer('webp', { quality: 90 })
// Sync variants — blocks the event loop, useful in scripts or worker threads
const small2 = img.resizeSync(800, 600, { mode: 'fit', bgColor: [0, 0, 0, 255] })
const png = small2.toBufferSync('png')Resize modes
| Mode | Description |
|----------------|---------------------------------------------------------------------|
| "fill" | Stretch to exact (width, height) — ignores aspect ratio (default) |
| "cover" | Scale to fill the box, crop excess from the centre |
| "fit" | Scale to fit inside the box, pad edges with bgColor |
| "fit_width" | Scale to match width, height adjusts proportionally |
| "fit_height" | Scale to match height, width adjusts proportionally |
Resampling filters
| Filter | Notes |
|---------------|-------------------------------------------------|
| nearest | Fastest, blocky |
| bilinear | Fast, smooth |
| bicubic | Keys cubic |
| lanczos | Window-3 sinc — best general quality (default) |
| lanczos2 | Faster, slightly softer |
| lanczos4 | Slower, very sharp |
| box | Area average — best for heavy downscaling |
| hamming | |
| mitchell | Mitchell-Netravali — balanced sharpness/ringing |
| catmull_rom | Sharper than Mitchell |
| gaussian | |
| hann | |
Metadata
Metadata is extracted automatically when you call Image.open() or
Image.fromBuffer() and travels with the image through resize operations.
- Auto-orient (
autoOrient: true, default) — EXIF orientation is baked into pixels before resizing. The orientation tag is reset to 1 in the output so viewers don't rotate twice. - ICC profile (
withIcc: true, default) — colour profile is injected into the output JPEG, PNG, or WebP. - EXIF (
withExif: true, default) — EXIF block is injected into the output with the orientation tag reset to 1. - XMP (
withXmp: false, default) — opt in if you need creative metadata (title, copyright, keywords).
To strip all metadata:
const clean = await img.resize(800, 600, {
withIcc: false, withExif: false, withXmp: false, autoOrient: true,
})WASM (browser / Deno / edge)
WASM is single-threaded — all operations are synchronous. For non-blocking behaviour in the browser, run inside a Web Worker:
// worker.js
import init, { Image } from '@radzivon.bartoshyk/pic-scale/wasm'
await init()
self.onmessage = ({ data }) => {
const img = Image.fromBytes(data.bytes)
const small = img.resize(data.width, data.height, 'lanczos', 'cover')
const out = small.toBytes('jpeg', 85)
self.postMessage(out, [out.buffer]) // zero-copy transfer
}// main.js
const worker = new Worker('./worker.js', { type: 'module' })
const bytes = new Uint8Array(await (await fetch('photo.jpg')).arrayBuffer())
worker.postMessage({ bytes, width: 800, height: 600 }, [bytes.buffer])
worker.onmessage = ({ data }) => {
const blob = new Blob([data], { type: 'image/jpeg' })
document.querySelector('img').src = URL.createObjectURL(blob)
}WASM API
import init, { Image } from '@radzivon.bartoshyk/pic-scale/wasm'
await init()
const bytes = new Uint8Array(await file.arrayBuffer())
const img = Image.fromBytes(bytes) // decode, extract ICC/EXIF
console.log(img.width, img.height, img.channels)
const small = img.resize(
800, 600,
'lanczos', // filter (default 'lanczos')
'cover', // mode (default 'fill')
true, // premultiplyAlpha (default true)
1, // workers (default 1)
null, // bgColor [r,g,b,a] for 'fit' mode
true, // autoOrient (default true)
)
const out = small.toBytes(
'jpeg', // format (default 'png')
85, // quality (default 85)
true, // withIcc (default true)
true, // withExif (default true)
false, // withXmp (default false)
)
// out is a Uint8ArraySupported formats
| Format | Native decode | Native encode | WASM decode | WASM encode | |------------------------|---------------|---------------|-------------|-------------| | JPEG | ✓ | ✓ | ✓ | ✓ | | PNG | ✓ | ✓ | ✓ | ✓ | | WebP | ✓ | ✓ | ✓ | ✓ | | TIFF | ✓ | ✓ | ✓ | ✓ | | BMP | ✓ | ✓ | ✓ | ✓ | | ICO | ✓ | — | ✓ | — | | QOI | ✓ | ✓ | ✓ | ✓ | | AVIF | ✓ | ✓ | — | — | | HEIC/HEIF | ✓ | ✓ | — | — | | JPEG XL (JXL) | ✓ | ✓ | ✓ | ✓ |
Native API reference
Image.open(path): Promise<Image>
Decode from a file path. ICC, EXIF, and XMP are extracted automatically.
Image.fromBuffer(data: Buffer | Uint8Array): Image
Decode from memory (sync).
img.width / img.height / img.channels
Image dimensions and channel count (read-only).
img.resize(width, height, opts?): Promise<Image>
Async resize. Returns a new Image — the original is unchanged.
interface ResizeOptions {
filter?: string // default 'lanczos'
mode?: string // default 'fill'
premultiplyAlpha?: boolean // default true
workers?: number // default 1, 0 = adaptive
bgColor?: number[] // [r,g,b,a], default [0,0,0,0]
withIcc?: boolean // default true
withExif?: boolean // default true
withXmp?: boolean // default false
autoOrient?: boolean // default true
}img.resizeSync(width, height, opts?): Image
Sync resize — same options as resize.
img.toBuffer(format?, opts?): Promise<Buffer>
Encode to a Buffer. Injects ICC + EXIF by default.
img.toBufferSync(format?, opts?): Buffer
Sync encode.
img.save(path, opts?): Promise<void>
Write to a file. Format inferred from extension. Injects ICC + EXIF.
interface EncodeOpts { quality?: number } // default 85License
BSD 3-Clause
