@sorabito-takano/wasm-image-optimization
v1.2.2
Published
WebP image optimization with Lanczos resampling on edge runtime
Downloads
30
Maintainers
Readme
wasm-image-optimization
WebAssembly-based image optimization library with a custom minimal resize core (OpenCV runtime removed for smaller wasm size) using extracted high-quality Lanczos resampling logic from pillow-resize. Primary target is WebP (auto lossless for PNG/WebP inputs, lossy otherwise) with optional JPEG output and a pass-through ("none") mode that returns the original bytes (useful when only resizing info or EXIF-based orientation handling is needed).
Frontend
- Next.js (Multithreading support)
- React Router (Multithreading support)
Backend
- Cloudflare Workers
- Deno Deploy
- Node.js (Multithreading support)
Supported Conversions
- Input formats (auto-detected): JPEG / PNG / WebP
- Output formats:
webp– High-quality Lanczos resize using pillow-resize implementation, auto lossless for PNG/WebP sources, lossy otherwisejpeg– Always lossy JPEG (RGB → YCbCr), ignores lossless flagnone– Returns original bytes untouched (width/height/EXIF orientation still processed)
Example
https://next-image-convert.vercel.app/
API
Core Functions
Image conversion (all functions return Promises):
optimizeImage({
image: ArrayBuffer | Uint8Array | string,
width?: number,
height?: number,
quality?: number, // 0-100 (default 100)
format?: "webp" | "jpeg" | "none" // default: webp
}): Promise<Uint8Array>
optimizeImageExt({
image: ArrayBuffer | Uint8Array | string,
width?: number,
height?: number,
quality?: number,
format?: "webp" | "jpeg" | "none"
}): Promise<{
data: Uint8Array,
originalWidth: number,
originalHeight: number,
width: number,
height: number
}>
Multi-thread / Worker Control
waitAll(): Promise<void>
waitReady(retryTime?: number): Promise<void>
close(): void
launchWorker(): Promise<void>
setLimit(maxWorkers: number): void // (exported but previously undocumented)WASM Path Configuration (PWA / Offline Support)
For PWA applications or custom deployment scenarios, you can configure the WebAssembly binary location:
import { setWasmUrl, setWasmBinary } from 'wasm-image-optimization/vite';
// Option 1: Set custom WASM URL (useful for PWA Service Worker caching)
setWasmUrl('/assets/libImage.wasm');
// Option 2: Provide pre-loaded WASM binary
const wasmBinary = await fetch('/assets/libImage.wasm').then(r => r.arrayBuffer());
setWasmBinary(wasmBinary);
// Reset to default behavior
import { resetWasmConfig } from 'wasm-image-optimization/vite';
resetWasmConfig();Available in: vite, next, workers, node entry points.
Vite / Web Worker Integration
For Vite usage include the plugin (adds proper asset copying and worker wiring):
- vite.config.ts
import wasmImageOptimizationPlugin from "wasm-image-optimization/vite-plugin";
export default defineConfig(() => ({
plugins: [
wasmImageOptimizationPlugin(),
//wasmImageOptimizationPlugin("build/client/assets") // optional: assetsPath
],
}));Build
Architecture-aware Docker Build
Automatic host architecture detection (x86_64 / arm64) is handled by the helper script:
# Automatic architecture detection and build
pnpm build:wasm:auto
# Or use the architecture detection script directly
./scripts/docker-build.sh all
# Manual architecture selection (override detection)
pnpm docker:arm64 # Force ARM64 build
pnpm docker:x86 # Force x86_64 buildIncremental Development (Docker)
# Build environment setup
pnpm docker:build-env
# Start development environment
pnpm docker:dev
# Run make in Docker
pnpm docker:make
# Open shell in Docker
pnpm docker:shell-dev
# Clean up Docker resources
pnpm docker:stopDirect One-shot Build (manual override)
# For ARM64 (Apple Silicon)
DOCKERFILE=./docker/Dockerfile.arm64 docker compose -f docker/docker-compose.auto.yml run --rm dev make all
# For x86_64
DOCKERFILE=./docker/Dockerfile docker compose -f docker/docker-compose.auto.yml run --rm dev make allSupported Environments & Entry Points
| Environment / Use Case | Import Path |
|---------------------------------------|----------------------------------------------------|
| Cloudflare Workers / Edge (ESM) | wasm-image-optimization |
| Next.js (SSR + Web Worker mode) | wasm-image-optimization/next |
| Next.js API Route (explicit) | wasm-image-optimization/next-api |
| Generic ESM / Deno Deploy | wasm-image-optimization |
| Node.js (single thread) | wasm-image-optimization |
| Node.js (multi thread pool) | wasm-image-optimization/node-worker |
| Vite (bundled browser main thread) | wasm-image-optimization/vite |
| Vite / Generic Web Worker (multi) | wasm-image-optimization/web-worker |
| Raw Worker (CJS fallback) | wasm-image-optimization/node |
Multi-thread variants expose
waitReady,waitAll,launchWorker,setLimit,close.
Samples
Repository with usage examples: https://github.com/SoraKumo001/wasm-image-optimization-samples
Behavior Notes
- EXIF orientation is automatically normalized before resizing/encoding.
- WebP encoding switches to lossless when input is PNG or WebP and output format is
webp. format: "none"returns the original bytes (useful when you only need metadata or want to defer encoding).qualityonly affects lossy paths (WebP lossy / JPEG). Lossless WebP ignores the numeric quality parameter.
Image Processing Details
This library integrates high-quality image resampling algorithms extracted from pillow-resize for superior image quality compared to typical naive or bilinear scaling approaches. OpenCV is no longer shipped to drastically reduce WASM size.
Minimal Resize Core & Lanczos Resampling
| Aspect | Before (OpenCV based) | Now (Custom minimal core) | |--------|-----------------------|---------------------------| | WASM size (typical) | ~1.5 MB | ~ (significantly smaller)* | | Dependency | OpenCV (core/imgproc subset) | Hand-extracted resize pipeline | | Filter | OpenCV Lanczos / fallback logic | Direct Lanczos-3 implementation | | Fallback path | OpenCV INTER_LANCZOS4 | Removed (deterministic path) |
*Exact size depends on build flags; OpenCV code paths, alloc helpers, and unused kernels removed.
Key Points
- Only the math & buffer ops needed for Lanczos-3 down/upsampling are compiled.
- No dynamic dispatch / no unused interpolation kernels.
- Deterministic single path (no runtime fallback → smaller + predictable output).
- Implements horizontal + vertical separable filtering with windowed sinc (Lanczos radius=3).
- Designed for future extension (e.g. optional Mitchell / Catmull-Rom) without pulling large frameworks.
Why remove OpenCV?
- Large binary footprint (initial builds ~1.5MB → unacceptable for some edge/PWA budgets)
- Only resize + color conversion were used.
- Custom path eliminates build complexity (Python + CMake toolchain for OpenCV JS).
- Faster cold start under Service Worker / edge runtime due to reduced instantiation cost.
Quality
Lanczos-3 maintains sharpness while minimizing ringing for photographic sources. For heavy downscales (<25%) you may still apply an external pre-blur if aggressive aliasing in source exists.
If you previously relied on OpenCV-specific behavior (e.g. other interpolation modes), migrate by calling
optimizeImagewith the same resize parameters — behavior is now unified.
Roadmap / TODO
- Expose pluggable resize kernels (Mitchell, Catmull-Rom, Lanczos-2).
- Optional AVIF output (investigation phase).
- Update published TypeScript types to include
"jpeg"(if not already updated in release at read time). - Optional prefilter for extreme downscale scenarios.
MIT License
