@alberteinshutoin/lazy-image
v0.16.0
Published
Web image optimization engine for Node.js - smaller JPEG files in simple canonical benchmarks, powered by Rust + mozjpeg
Maintainers
Readme
lazy-image 🦀
Web image optimization engine for Node.js. Rust core, smaller JPEG outputs, bounded memory.
In current canonical benchmarks, lazy-image produces 17-20% smaller JPEG outputs than sharp for the two simple PNG → JPEG cases: no-resize conversion and resize-to-800px. AVIF/WebP size and speed, and multi-operation JPEG pipelines, are workload-specific; benchmark your target image mix before assuming a win.
- Not a drop-in replacement for sharp — use sharp if you need its full API or maximum throughput.
- Security-first: Metadata stripped by default;
keepMetadata()to preserve. File-path inputs (fromPath()/processBatch()→toFile()) bypass the V8 heap — small/medium files (≤ 256 MB) are read into Rust-owned memory for SIGBUS safety; only files larger than 256 MB use mmap with advisory locks. See docs/ZERO_COPY.md. - Japanese: README.ja.md. mmap (files > 256 MB): do not modify or delete source files while processing; use a copy or
from(Buffer)for mutable inputs. - Lazy contract: docs/LAZY_SEMANTICS.md explains what is deferred vs eager.
- Metadata matrix: docs/METADATA_SUPPORT.md.
- Philosophy and non-goals: docs/PROJECT_PHILOSOPHY.md.
Quick Start (5 lines)
const { ImageEngine } = require('@alberteinshutoin/lazy-image');
const bytesWritten = await ImageEngine.fromPath('input.png')
.resize(800)
.toFile('output.jpg', 'jpeg', 80);
console.log(`Wrote ${bytesWritten} bytes`);Architecture Overview
src/ # selected high-level Rust core modules
├── engine/
│ ├── api/ # ImageEngine public API + NAPI-facing operations
│ ├── pipeline/ # Operation application, color state, capabilities, optimization
│ ├── tasks/ # NAPI async task contexts and encode/batch/write execution
│ ├── memory/ # Cgroup detection, memory estimates, weighted semaphore
│ ├── io/ # File sources, mmap, ICC/EXIF extraction and embedding
│ ├── resize.rs # Dimension calc, fast_image_resize dispatch
│ ├── encoder.rs # JPEG/PNG/WebP/AVIF encoding + ICC/EXIF embedding
│ ├── decoder.rs # Format detection + decoding
│ ├── firewall.rs # Input sanitization policies
│ ├── metadata.rs # Metadata policy and preservation state
│ └── validation.rs # Input, operation, and output validation helpers
├── ops.rs # Operation enum, presets, output formats
├── error.rs # 4-tier error taxonomy (E1xx–E9xx)
└── codecs/avif_safe.rs # Safe libavif FFI wrappers
lib/helpers.js # Encoding profiles, target-bytes binary search
streaming/pipeline.js # Disk-backed bounded-memory streamingKey design decisions: lazy execution (ops queue until output), file-path inputs bypass the V8 heap (read-into-memory for ≤ 256 MB, mmap with advisory locks for > 256 MB), memory-bounded concurrency via weighted semaphore, panic guards on all codec entry points. See docs/ARCHITECTURE.md for details.
Choose lazy-image if / Choose sharp if
| Choose lazy-image if | Choose sharp if | |---|---| | You pay for bandwidth (CDN, S3, CloudFront) | You need maximum encoding throughput | | You run in serverless / memory-constrained envs | You need a broad image editing API | | You want AVIF with safe defaults | You need drop-in API compatibility | | You want smaller JPEG outputs over broader codec throughput | You need GIF, SVG, or TIFF support |
Key differentiators:
- JPEG file size optimization — mozjpeg produces 17-20% smaller JPEG outputs in the canonical simple PNG → JPEG no-resize and resize benchmarks
- Memory efficiency — file-path inputs are not copied into the V8 heap; decoded pixels stay in Rust memory
- Security-first — GPS auto-strip, Image Firewall, Rust memory safety
- AVIF output support — libavif encoder with quality-tuned defaults and ICC support; benchmark AVIF workloads for size/speed
What lazy-image does NOT compete on: raw encoding speed (sharp/libvips is faster), feature breadth (no drawing, compositing, or GIF), API compatibility with sharp.
Project philosophy: optimize for smaller web outputs, bounded memory, and safe defaults first. Any speed claims must be codec- and workload-specific, not a blanket "faster than sharp" promise.
Benchmarks and details: docs/PERFORMANCE.md. Full compatibility matrix: docs/COMPATIBILITY.md.
Recommended Paths
Start with one of these workflows:
- Web delivery optimization:
fromPath() -> resize()/crop() -> toFile()for the lowest heap usage and the clearest operational path - Upload sanitization:
fromPath() -> sanitize({ policy: 'strict' }) -> toFile()/toBuffer()for untrusted user uploads - Build-time / batch generation:
processBatch()orclone()for static sites, media pipelines, and multi-output generation - Final optimization after editing: use sharp/ImageMagick for compositing, filters, or animation, then pass the result through lazy-image for final JPEG/WebP/AVIF optimization
Scenario guide: docs/ADOPTION_GUIDE.md.
Cost Savings Example (ROI)
Assume:
- average image size before optimization: 1.0 MB
- average JPEG-oriented reduction with lazy-image: 18% (example only; not universal across all codecs)
- CDN transfer rate: $0.085/GB (example: CloudFront pay-as-you-go, US/EU next 9TB tier)
| Scenario | Transfer with sharp | Transfer with lazy-image | Monthly savings | |---|---:|---:|---:| | 1M image deliveries / month | 976.6 GB | 800.8 GB | $14.94 | | 10M image deliveries / month | 9.54 TB | 7.82 TB | $149.41 | | 100M image deliveries / month | 95.37 TB | 78.20 TB | $1,494.14 |
Break-even intuition:
- Encoding overhead is paid once per generated image
- Bandwidth savings are realized every time the image is delivered
- If each generated image is viewed multiple times, lazy-image generally wins quickly
Use the interactive calculator:
- docs/roi-calculator.html
- Methodology and formulas: docs/ROI_CALCULATOR.md
Installation
npm install @alberteinshutoin/lazy-imagePlatform-specific binaries (~6–9 MB per platform) are installed automatically. Build from source: npm run build. See docs/PERFORMANCE.md for package size comparison with sharp.
Basic Usage
Resize and save (recommended: file-to-file)
await ImageEngine.fromPath('photo.jpg')
.resize({ width: 800, fit: 'inside' }) // positional args also supported
.toFile('thumb.jpg', 'jpeg', 85);Format conversion (e.g. PNG → WebP/AVIF)
const buffer = await ImageEngine.fromPath('input.png')
.resize({ width: 600 })
.toBuffer('webp', 80);Metadata without decoding
const { inspectFile } = require('@alberteinshutoin/lazy-image');
const meta = inspectFile('input.jpg'); // { width, height, format }lazy-image defers full pixel decode, transforms, and encoding until toBuffer() / toFile(). Constructors still do source setup and metadata extraction; see docs/LAZY_SEMANTICS.md.
More: batch processing, presets, metrics, streaming — docs/API.md.
Documentation
| Topic | Link | |-------|------| | Examples | examples/ | | TypeScript guide | docs/TYPESCRIPT.md | | Full API reference | docs/API.md | | Migration from sharp | docs/MIGRATION_FROM_SHARP.md | | Performance & when to use | docs/PERFORMANCE.md | | Architecture & security | docs/ARCHITECTURE.md | | Troubleshooting | docs/TROUBLESHOOTING.md | | Security policy & reporting | SECURITY.md | | Roadmap & scope | docs/ROADMAP.md | | Adoption guide / recommended workflows | docs/ADOPTION_GUIDE.md | | Wasm / browser / Edge strategy | docs/WASM_STRATEGY.md | | Wasm package and policy API | docs/WASM_PACKAGE_API.md | | Wasm upload benchmark guidance | docs/WASM_BENCHMARKING.md | | Metadata support matrix | docs/METADATA_SUPPORT.md | | ROI calculator methodology | docs/ROI_CALCULATOR.md | | Version history | docs/VERSION_HISTORY.md | | Specification (spec/) | spec/pipeline.md, spec/resize.md, spec/errors.md, spec/limits.md, spec/quality.md, spec/metadata.md | | Error codes | docs/ERROR_CODES.md | | Benchmarks (raw data) | docs/TRUE_BENCHMARKS.md | | Binary size comparison (AVIF on/off) | docs/BINARY_SIZE.md | | Benchmark snapshots log | docs/BENCHMARK_RESULTS.md | | Benchmark operations | docs/BENCHMARK_OPERATIONS.md |
Features (summary)
Smaller JPEG (mozjpeg) · WebP (libwebp) · AVIF (libavif) · ICC profiles · EXIF auto-orient · Bypass-V8-heap file I/O (read-into-memory ≤ 256 MB, mmap > 256 MB) · Disk-backed bounded-memory pipeline (createStreamingPipeline(), not true chunked transform streaming) · Image Firewall · GPS auto-strip · Fluent API · Rust core (NAPI-RS) · Cross-platform (macOS, Windows, Linux). Design choices and limits: docs/ROADMAP.md and docs/ARCHITECTURE.md.
Development
npm install && npm run build
npm testSee CONTRIBUTING.md for workflow, test commands, and contribution guidelines. Benchmark testing: lazy-image-test Docker environment. Fuzzing: FUZZING.md.
License
MIT
Credits
mozjpeg · libwebp · libavif · fast_image_resize · img-parts · napi-rs
Ship it. 🚀
