Documentation
LessBytes is an advanced client-side image compression suite designed to deliver visually lossless outputs. Instead of hardcoding a quality setting and making you guess, LessBytes treats quality as an optimization search. It binary-searches the quality space, measures structural similarity (SSIM) against the original, and converges on the smallest file size that is perceptually identical to the human eye.
- Introduction
- The Pipeline
- Website Guide
- NPM Installation
- JavaScript API
- Node.js CLI
- Compatibility
- Design Decisions
Perceptual Optimization vs Fixed-Quality
| Feature | Fixed-Quality Compression | LessBytes |
|---|---|---|
| Quality Decision | You guess upfront | Per-image binary search |
| Measurement | None — you eyeballing the output | SSIM delta against the original |
| Format Selection | Whatever you specified | AVIF, WebP, JPEG encoded in parallel; winner by size |
| Portraits / High Details | May degrade visibly | Holds quality where the perceptual cost is high |
| Solid Fills / Flat Art | Wastes bytes on invisible headroom | Strips what the eye cannot distinguish |
| Typical Result | Smaller, sometimes ugly | ~84% smaller, looks identical |
From the test suite: 235 KB JPEG → 37 KB WebP at SSIM 0.992 — the empirically-tuned threshold for photographic losslessness.
The Compression Pipeline
LessBytes operates through a structured pipeline consisting of five stages:
- Decoding: Read input files (as Canvas element, Blob, File, or URL string) and load pixel data.
- Downscaling: Instead of a single bilinear pass (which introduces ringing artifacts), LessBytes steps down dimensions in multiple stages to preserve fine details.
- Format Competition: In
automode, all viable formats are encoded in parallel. The smallest one meeting the quality target wins. - The Search: In each iteration of the search loop: encode at candidate quality → decode back to pixel data → compute SSIM against original. If it passes, quality is lowered; if not, it backs off. 7 iterations are enough to converge within 1% quality resolution.
- Output: Ships the final winning blob.
SSIM Target Calibration
SSIM (Structural Similarity) determines how closely the compressed image matches the original structure:
| SSIM Score | Visual Fidelity / Meaning |
|---|---|
1.000 |
Bit-for-bit identical |
≥ 0.998 |
Below human perceptual threshold |
~0.970 |
Subtle difference, only visible on direct side-by-side comparison |
< 0.950 |
Clearly degraded details or artifacts present |
The default target SSIM is 0.992, chosen empirically against diverse image datasets to be the lowest value where no degradation is visible to the human eye without direct pixel differentiation.
Website Guide
The web-based compressor at lessbytes.vercel.app runs entirely in your browser using the HTML5 Canvas API. No images are uploaded to external servers, providing 100% data privacy.
Core Web Features
- Bulk uploads: Drag and drop up to 50 images simultaneously.
- Comparison Slider: Inspect compressed outputs against originals side-by-side in real-time.
- Aspect-locked Resizing: Scale dimensions and lock layout structures locally.
- Adjustable Formats: Select from JPEG, PNG, WebP, or GIF outputs.
- Offline Availability: Runs fully client-side and continues functioning without an active internet connection.
Using the interface
- Drag and drop images onto the dashboard or click Choose Files.
- Under Conversion Settings, set global export settings, or configure individual cards to override them.
- Use the slide divider on individual image cards to review visual quality differences.
- Click Download All to export a unified zip folder of all processed images, or click download on a specific card.
NPM Installation
Integrate the core LessBytes compression engine directly into your JavaScript projects or use the command-line interface to batch process folders locally.
# Install package locally via npm
npm install lessbytes
Or load directly inside browser templates without any bundler or build steps by referencing the unpkg CDN:
<!-- Load browser UMD bundle -->
<script src="https://unpkg.com/lessbytes"></script>
<script>
lessbytes.compress(file).then(result => {
console.log(`Compressed size saving: ` + (result.ratio * 100).toFixed(1) + "% smaller");
});
</script>
JavaScript API Reference
The package is published under npmjs.com/package/lessbytes and ships with ESM, CommonJS, and UMD exports, fully typed out of the box.
// ESM / bundlers (Vite, webpack, Rollup, esbuild)
import { compress, compressBatch, computeSSIM, isFormatSupported } from 'lessbytes';
// CommonJS
const { compress } = require('lessbytes');
compress(input, options?) → Promise<CompressResult>
Compresses a single image file or canvas input. Returns a Promise resolving to a CompressResult.
input can be a File | Blob | HTMLImageElement | URL string.
const result = await compress(file, {
format: 'webp', // 'webp' | 'auto' | 'jpeg' | 'png' | 'avif' (default 'webp')
targetSSIM: 0.992, // raise for more fidelity; lower to push smaller
maxWidth: 1920, // aspect ratio preserved
maxHeight: null,
targetSize: 100 * 1024, // hard ceiling in bytes — useful for upload quotas
background: '#ffffff', // fill used when flattening alpha into a lossy format
keepSmallest: true // returns original if compression would make it larger
});
result.blob // Blob — the output image blob
result.format // 'webp' (the format converged upon)
result.ssim // 0.9924 (actual SSIM of final encode)
result.ratio // 0.84 (84% smaller)
result.quality // 0.93 (the quality the search converged on)
result.toFile('upload.webp') // wraps blob as a File object
A realistic upload handler integration:
input.addEventListener('change', async () => {
const { blob, toFile } = await compress(input.files[0], { maxWidth: 1920 });
preview.src = URL.createObjectURL(blob);
formData.append('photo', toFile('photo.webp'));
});
compressBatch(inputs, options?) → Promise<CompressResult[]>
Processes a collection of files with bounded concurrency and progress callbacks. Extremely useful for drag-and-drop lists or album processing.
const results = await compressBatch(files, {
concurrency: 3,
maxWidth: 1600,
onProgress: (done, total, last) => {
console.log(`${done}/${total} — last: ` + (last.ratio * 100).toFixed(0) + "% smaller");
}
});
computeSSIM(a: ImageData, b: ImageData) → number
Exposes the raw SSIM calculation function used by LessBytes. Both buffers must have the same dimensions. Returns a structural similarity score between 0.0 and 1.0.
isFormatSupported(mime: string) → boolean
Checks whether the execution environment (browser canvas) supports encoding the specified mime type (e.g. image/avif). Used internally for runtime check-and-fallback setups.
Node.js CLI
The command-line tool utilizes high-performance sharp (libvips) underneath to execute search logic on folders and files asynchronously. It is included as an optional peer dependency.
For performance and security, LessBytes must be installed globally. Interactive CLI execution via npx is blocked.
# Install package globally
npm install -g lessbytes
# Run interactive shell or single-command usage
lessbytes [files...|dir] [options]
Interactive CLI Interface
Run lessbytes without any file or folder arguments to launch the step-by-step interactive configuration prompt, equipped with smart defaults you can accept by hitting Enter:
lessbytes
The CLI will prompt you interactively:
Image file or folder › photo.jpg
Output format
1 Auto — compete AVIF / WebP / JPEG, keep the smallest (default)
2 WebP
3 AVIF
4 JPEG
5 PNG
› 1
Compression mode
1 Smart — visually lossless (SSIM-guided search) (default)
2 Target file size
3 Fixed quality
› 1
Max width in px (blank = no limit) › 1920
Output path (blank = beside source) › build/img
The three interactive options map to CLI modes as follows:
| Interactive Option | Equivalent Flag | Behavior |
|---|---|---|
| Smart | (Default) | SSIM-guided search for the smallest visually-lossless file |
| Target file size | --max-size |
Searches quality, then downscales until under the budget |
| Fixed quality | --quality |
Encodes once at the quality you provide (no search loop) |
CLI Direct Usage & Flags
You can directly script or batch process files using command line arguments:
# Optimize a single file
lessbytes photo.jpg
# Batch convert a glob of pngs into a specific folder
lessbytes *.png -o out/
# Force webp output and static quality 80
lessbytes hero.png --format webp --quality 80
# Force compression under a hard size budget
lessbytes banner.jpg --max-size 100kb
# Cap dimensions (lock aspect ratio)
lessbytes huge.jpg --max-width 1920
# Traverse directory recursively
lessbytes ./assets -r -o build/img
# Compress targeting higher SSIM visual threshold
lessbytes shot.png --format avif --ssim 0.995
# Always write output even if file size is larger
lessbytes icon.png --keep-larger
CLI Command Reference
| Flag | Description | Default |
|---|---|---|
-o, --output <path> |
Output file or directory path. trailing / creates a folder. |
*.min.* beside source |
-f, --format <fmt> |
Target format: auto · jpeg · webp · png · avif |
webp |
-q, --quality <1-100> |
Forced static quality, skipping SSIM search entirely. | — |
--max-size <size> |
Hard size ceiling (e.g. 100kb, 1.5mb, 512b). |
— |
--max-width <px> |
Cap output image width. Aspect ratio is preserved. | — |
--max-height <px> |
Cap output image height. Aspect ratio is preserved. | — |
--ssim <0-1> |
Perceptual SSIM score target for quality search. | 0.992 |
-r, --recursive |
Recurse into subdirectories when scanning folders. | off |
--suffix <str> |
Suffix appended to input filename to avoid overwrite. | .min |
--keep-larger |
Write outputs even if the compressed size exceeds source file. | off |
-s, --silent |
Suppress progress outputs, displaying errors only. | off |
-i, --interactive |
Forces interactive interface prompts to open. | off |
--logo |
Print only the logo banner ASCII art and exit. | — |
-h, --help |
Show the command line help manual. | — |
-v, --version |
Print current version number. | — |
Format Selection & --max-size Ceilings
- Format Selection: If format is set to
auto, opaque images compete WebP, AVIF, and JPEG, and alpha-transparent images compete WebP, AVIF, and PNG. The smallest resulting file that passes target SSIM selection will win. AVIF is skipped automatically if your sharp/libvips build cannot support encoding it. - Size budget logic:
--max-sizeis a genuine hard ceiling. The CLI first binary-searches the quality settings. If the lowest quality setting still exceeds the size budget, it progressively downscales the dimensions in 80% steps, re-searching until the file fits under the budget.
CLI Outputs & Exit Codes
CLI output logs individual files and their statistics:
lessbytes v1.1.0 compressing 1 image
✓ photo.jpg 235.8 KB → 36.9 KB -84% webp q93 ssim 0.992
Done. 235.8 KB → 36.9 KB (84.4% smaller)
The CLI process returns standard exit status codes:
0: All images processed successfully or no actions needed.1: Invalid arguments, no images found, sharp dependency missing, or all operations failed.
Compatibility
- Browser: Works in any browser supporting Canvas API and
toBlob. If AVIF encoding isn't supported, it transparently falls back to WebP; JPEG serves as the universal fallback. - Node.js: Node 16+ is required for sharp-based CLI execution. The browser library has zero Node dependency.
- Bundlers & Environments: LessBytes exports clean ESM, CommonJS, and UMD bundles. Fully tree-shakeable, compatible out-of-the-box with Vite, Webpack, Rollup, and Esbuild without custom configurations.
Design Decisions
Why Canvas Binary Search instead of Perceptual Codecs?
Perceptual optimization tools like Squoosh use advanced encoder perceptual optimizations (e.g. Butteraugli). While precise, they require WebAssembly and a massive bundle size. LessBytes' SSIM Canvas search loop is lightweight, uses native browser features, requires no Wasm, has zero browser dependencies, and can be easily audited.
Why is 0.992 the default SSIM?
Empirical testing across photograph and illustration datasets shows 0.992 is the lower boundary where A/B testers cannot reliably distinguish compression degradation. Lowering the target introduces color banding or artifacting on portraits/high-frequency areas, while higher targets increase file sizes exponentially for visual differences that are indistinguishable.
Why return the original if it is smaller?
For low-byte assets (like small 2KB PNG icons), compression can occasionally result in a larger size due to header overhead. LessBytes transparently returns the original file unchanged rather than forcing developers to implement additional size checking logic.