██████╗ ██╗ ██╗██████╗ ███████╗ ██╗███████╗
██╔══██╗██║ ██║██╔══██╗██╔════╝ ██║██╔════╝
██████╔╝██║ ██║██████╔╝█████╗ ██║███████╗
██╔═══╝ ██║ ██║██╔══██╗██╔══╝ ██ ██║╚════██║
██║ ╚██████╔╝██║ ██║███████╗ ╚█████╔╝███████║
╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚════╝ ╚══════╝
I M A G E
Portable reference engine · optional first-party acceleration · zero runtime dependencies
Documentation · Live browser demo · Whole-slide demo · Scientific explorer · Install · Quick start · Codecs · Roadmap · Benchmarks
PureJsImage is building a broad suite of first-party image codecs in strict TypeScript. Each codec grows from a checked capability contract and conformance corpus toward broader decode coverage, practical encoding, bounded-memory execution, and optional first-party acceleration.
A shared lazy image and raster pipeline makes those codecs useful across two workload families:
- Application images: inspect, orient, crop, resize, and transcode common formats.
- Large and native rasters: GSF surfaces, ENVI hyperspectral cubes and classification maps, FITS and MRC volumes, CBF detector frames, TIFF, OME-TIFF, GeoTIFF/COG, whole-slide images, remote regions, and N-channel numeric data.
The same source, codec, and pipeline architecture targets Node.js, modern browsers, serverless, edge, and restricted deployments. It is not a canvas, drawing, or graphics-effects toolkit.
The strict TypeScript reference engine is the permanent portable path. Optional first-party JPEG and PNG WASM accelerators preserve its public behavior and are explicitly registered; they do not replace it. Memory behavior remains codec- and operation-specific: bounded rows or tiles are used where implemented, and full-frame or larger-state fallbacks are documented.
npm install purejsimagePureJsImage requires Node.js 22 or newer. Browser applications use the
purejsimage/browser entry. Installing it adds no runtime dependencies, native
addons, or external programs. Optional first-party JPEG and PNG WASM accelerators
use separate explicit entries.
Pre-1.0: Codec behavior is heavily tested, but public APIs may receive breaking refinements before 1.0.
Read the installation and browser guide →
Register only the codecs the application needs, then build a processing pipeline:
import { createImageLibrary } from 'purejsimage'
import { jpegCodec } from 'purejsimage/codecs/jpeg'
import { pngCodec } from 'purejsimage/codecs/png'
// Optional first-party WASM accelerators:
import { wasmJpegAccelerator } from 'purejsimage/accelerators/wasm/jpeg'
import { wasmPngAccelerator } from 'purejsimage/accelerators/wasm/png'
const images = createImageLibrary({
codecs: [jpegCodec, pngCodec],
accelerators: [wasmJpegAccelerator, wasmPngAccelerator], // Optional
})
const image = await images.open('input.jpg')
await image
.autoOrient()
.resize({ width: 1200, withoutEnlargement: true })
.jpeg({ quality: 80, background: '#ffffff' })
.toFile('output.jpg')In a browser, import from purejsimage/browser and use toBlob() or
toUint8Array() for output. Tools that need every default codec can register
allCodecs from purejsimage/codecs/all.
Register the TIFF codec alone, then opt into the OME-TIFF profile:
import { createImageLibrary, FileSource } from 'purejsimage'
import { tiffCodec } from 'purejsimage/codecs/tiff'
import { omeTiffProfile } from 'purejsimage/scientific'
import { createTiffProfileRegistry, openTiffDocument } from 'purejsimage/tiff'
const images = createImageLibrary([tiffCodec])
const source = await FileSource.open('input.ome.tif')
const document = await openTiffDocument(source)
const profiles = createTiffProfileRegistry([omeTiffProfile])
const dataset = await profiles.openWith(document, omeTiffProfile)Scientific readers are separate from photographic codecs. The dataset remains numeric until an application requests display pixels:
import { FileSource } from 'purejsimage'
import { openFits, renderScientificPlane } from 'purejsimage/scientific'
const fits = await openFits(await FileSource.open('observation.fits'))
const dataset = await fits.openImage(0)
const display = await renderScientificPlane(dataset, {
plane: { z: 0, c: 0, t: 0 },
range: { mode: 'percentile', low: 1, high: 99 },
palette: 'viridis',
})MRC and FITS volumes share lazy cross-section and projection operations:
import { FileSource } from 'purejsimage'
import { openMrc, projectScientificVolume, sliceScientificVolume } from 'purejsimage/scientific'
const volume = await openMrc(await FileSource.open('reconstruction.mrc'))
const xz = sliceScientificVolume(volume, { axis: 'xz', index: 128 })
const maximum = projectScientificVolume(volume, { axis: 'z', mode: 'max' })ENVI · GSF · FITS · MRC2014 / CCP4 · CBF / imgCIF · Volume operations · Scientific API · Client-side explorer
Open the client-side image converter →
Open the client-side Scientific Raster Explorer →
Upload an image, let PureJsImage detect its actual format, apply optional orientation, resize, rotation, and flip transforms, then download JPEG, PNG, WebP, BMP, TIFF, Radiance HDR, QOI, PBM, PGM, PPM, PAM, PFM, or TGA output. The demo runs entirely in the browser, makes no image-upload request, and reports conversion time plus the browser memory measurements it can honestly observe.
Browse a 2.12 GB pathology slide after fetching a fraction of a percent →
The zero-dependency whole-slide demo reads native Aperio SVS pyramid tiles directly from static object storage with HTTP Range. It measures real requests and transferred bytes, cancels offscreen tile work in a Web Worker, and requires no conversion, tile server, or sidecar index.
JPEG and PNG have optional first-party WebAssembly accelerators. They are never loaded unless you explicitly register them, and unsupported work continues to use the default TypeScript codecs.
See WASM setup, options, and supported workflows →
| Format | Read | Write |
|---|---|---|
| JPEG | Yes | Yes |
| PNG | Yes | Yes |
| WebP | Yes | Yes |
| BMP | Yes | Yes |
| TIFF | Yes | Yes |
| GIF | Static / explicit frame 0 | No |
| ICO | Yes | No |
| JPEG 2000 / JP2 | Yes | No |
| AVIF | Yes | Limited |
| HEIF / HEIC (experimental) | Experimental | No |
| JPEG XL | Limited | No |
| Radiance HDR / RGBE | Yes | Yes |
| QOI | Yes | Yes |
| Netpbm and PFM | Yes | Yes |
| TGA / TARGA | Yes | Yes |
“Limited” means PureJsImage supports a useful subset and clearly rejects files
outside it.
“Experimental” means the codec is excluded from allCodecs and requires an
explicit direct import and registration.
See the exact codec support matrix →
Detailed codec compatibility roadmaps: JPEG, PNG, WebP, BMP, TIFF, GIF, ICO, JPEG 2000 / JP2, AVIF, HEIF / HEIC (experimental), JPEG XL, Radiance HDR / RGBE, QOI, Netpbm and PFM, and TGA / TARGA.
HEIF/HEIC is experimental, excluded from allCodecs, and available only through
purejsimage/codecs/experimental/heic. Its support contract
includes the HEVC patent notice for users and distributors.
AVIF is a first-party codec, not a wrapper around libavif or a third-party runtime. Common still-image decode is supported in Node.js and modern browsers; the checked capability contract records the explicit boundaries for uncommon AV1 syntax and dependent animation. The public encoder is intentionally constrained to opaque 8-bit YUV 4:2:0 still images.
The raster APIs preserve native numeric data instead of forcing every source through RGB:
- Scientific: GSF surfaces, ENVI hyperspectral cubes and classification maps, FITS and MRC volumes, CBF detector frames, N-channel rasters, and OME-TIFF.
- Geospatial: GeoTIFF and remote COG region reads.
- Pathology: whole-slide pyramids, Aperio SVS, and vendor profiles.
The Image pipeline uses display-ready PixelBlocks for ordinary transformations
and encoding. Scientific TIFF workflows expose native numeric, N-channel
RasterBlocks and map them to display pixels only when requested.
The same explicit scientific renderer handles GSF, ENVI, FITS, MRC, CBF, and OME-TIFF planes with declared, dataset, or bounded-sample percentile ranges; linear, logarithmic, square-root, or asinh scaling; five first-party palettes; and optional three-row scalar relief. Quantitative inputs are never mutated by display mapping.
TIFF support spans display images, native scientific rasters, OME-TIFF, whole-slide pyramids, extensible vendor profiles, and canonical RGB/RGBA output. The complete support list, memory model, examples, and remaining boundaries live on the dedicated TIFF page:
- Complete TIFF support →
- TIFF output options →
- Scientific TIFF and OME-TIFF → · Third-party TIFF profiles →
- Zstandard decompression API →
A capability is Yes only when upstream documentation or source supports it; measured decode coverage is reported separately against independent RGBA output. “Not verified” is not treated as unsupported.
| Library | Runtime model | Browser | BigTIFF | Tiles | Region decode | Native scientific raster | OME / whole-slide semantics | Decode coverage |
|---|---|---|---|---|---|---|---|---|
| PureJsImage benchmark snapshot · a1f20da | Strict TypeScript | Yes | Yes | Yes | Yes | Yes | Yes | 104/106 decoded 57 exact 47 pixel mismatches 2 oracle-unavailable cases |
| GeoTIFF.js 3.0.5 | Pure JavaScript | Yes | Partial | Yes | Yes | Yes | No | 84/106 decoded 32 exact 52 pixel mismatches 11 unsupported · 7 errors · 2 oracle-unavailable cases · 2 crashes |
| UTIF.js (utif2) 4.1.0 | Pure JavaScript | Yes | No | Yes | No | Partial | No | 74/106 decoded 49 exact 25 pixel mismatches 28 errors · 2 oracle-unavailable cases · 2 timeouts · 3 crashes |
| image-js/tiff 7.1.3 | Pure JavaScript | Yes | No | Yes | No | Yes | No | 41/106 decoded 27 exact 14 pixel mismatches 51 unsupported · 12 errors · 2 oracle-unavailable cases |
| image-js 1.7.0 | Pure JavaScript | Yes | No | Yes | No | Partial | No | 39/106 decoded 33 exact 6 pixel mismatches 51 unsupported · 14 errors · 2 oracle-unavailable cases |
| Jimp 1.6.0 | Pure JavaScript | Yes | No | Yes | No | No | No | 74/106 decoded 49 exact 25 pixel mismatches 28 errors · 2 oracle-unavailable cases · 2 timeouts · 3 crashes |
| Sharp / libvips 0.35.3 | Native wrapper | No | Partial | Yes | Partial | Partial | No | Not run |
“Oracle unavailable” means the independent Sharp/ImageMagick ground-truth path could not decode the fixture, not that the listed JavaScript engine failed. Every measured engine has the same two unavailable cases. PureJsImage's 47 non-exact decodes comprise 38 at or above 40 dB PSNR, 6 from 20 to below 30 dB, and 3 below 10 dB, derived from recorded RMSE. Jimp uses utif2 for TIFF internally, so its matching aggregate outcomes are expected.
Full grouped capability matrix, methods, sources, and per-library results
The seven-engine competitor profile measures the default PureJsImage TypeScript codecs and the explicitly registered PureJsImage JPEG/PNG WASM accelerators as separate variants alongside Jimp, Sharp, Sharp configured for one processing thread, image-js, and jSquash. Sharp uses native libvips; PureJsImage WASM and jSquash use WebAssembly; default PureJsImage, Jimp, and image-js are pure JavaScript. Each engine received the same files, used its public default resize kernel, and ran in a separate process. A result appears only when its output passed validation.
Resize workflows use engine defaults: PureJsImage and Sharp use Lanczos 3 while
Jimp uses bilinear. The quality chart reports premultiplied-RGBA PSNR against an
independently decoded exact-area reference; exact means every visible color
and alpha channel matched. This exposes quality differences, but cross-kernel
timings remain default-experience measurements rather than matched-quality
comparisons.
Benchmark snapshot: PureJsImage 0.8.0, August 10, 2026, Node.js 24.16.0. Against the default TypeScript path, the opt-in WASM variant reduced median wall time by 53.0% for JPEG-to-PNG, 38.9% for the 100-megapixel PNG downscale, and 11.6% for the large PNG resize while returning the same measured output quality. On the 24-megapixel photo workflow, default PureJsImage used 86.7% less peak memory than Jimp and 87.6% less than image-js. Timing, memory, and quality vary by image, operation, machine, and library version.
The focused nine-run TIFF profile recorded:
| Workflow | PureJsImage wall | PureJsImage RSS | Jimp wall | Jimp RSS |
|---|---|---|---|---|
| 4000×3000 TIFF metadata | 0.4 ms | 134.4 MiB | 150.4 ms | 289.8 MiB |
| 4000×3000 TIFF → 1000px JPEG | 144.4 ms | 214.9 MiB | 663.0 ms | 372.1 MiB |
| 7795×3122 LZW TIFF → 1000px PNG | 1,026.2 ms | 133.0 MiB | 753.2 ms | 284.8 MiB |
| PNG → Deflate TIFF | 24.5 ms | 111.4 MiB | 97.3 ms | 152.8 MiB |
PureJsImage passed all 18 TIFF workflows. Jimp passed seven, lacked the eight bounded raw/region workflows, and produced invalid pixels in three decode-to-PNG cases. The LZW row is the measured exception to the speed advantage: PureJsImage was slower there but used 53.3% less peak RSS.
Raw competitor report · PureJsImage TIFF report · Jimp TIFF report
See the complete benchmark report and methodology →
A 256 MiB Lambda completed every measured 12-megapixel resize/conversion workflow and used 121–156 MiB at peak. That does not make 256 MiB the fastest setting: Lambda also allocates CPU with memory. For JPEG → WebP, warm operation time fell from 10,601 ms at 256 MiB to 5,261 ms at 512 MiB and 2,533 ms at 1024 MiB, while peak use stayed at 120–122 MiB. For latency-sensitive endpoints, start at 1024 MiB even when the process only consumes about 150 MiB; use 256 MiB when its lower CPU allocation and roughly 10-second latency are acceptable. Re-measure with your own images and concurrency.
JPEG and PNG form the matched set because all five libraries support them. PureJsImage and jSquash can assemble only that set; the normal Jimp, image-js, and Sharp imports include the additional codecs shown.
Measured on Linux x64 with Node.js 24.16.0 using the repository's reproducible esbuild, gzip, and Brotli settings:
| Import | Version | Codecs included | Minified JS | gzip | Brotli |
|---|---|---|---|---|---|
| PureJsImage matched | 0.9.0 | JPEG, PNG | 154.6 KiB | 49.6 KiB | 41.6 KiB |
| PureJsImage all codecs | 0.9.0 | 13 codecs | 814.9 KiB | 288.0 KiB | 239.0 KiB |
| Jimp | 1.6.0 | JPEG, PNG, TIFF, BMP, GIF | 577.4 KiB | 174.6 KiB | 139.5 KiB |
| image-js | 1.7.0 | JPEG, PNG, TIFF, BMP | 361.5 KiB | 111.2 KiB | 94.3 KiB |
| jSquash | JPEG 1.6.0; PNG 3.1.1; resize 2.1.1 | JPEG, PNG | 52.4 KiB | 16.0 KiB | 13.2 KiB |
| Sharp JS wrapper | 0.35.3 | JPEG, PNG, TIFF, WebP, GIF, AVIF | 128.4 KiB | 38.3 KiB | 33.5 KiB |
Sharp's JavaScript bundle is only a wrapper around native code, while jSquash's JavaScript bundle is glue around its WebAssembly codecs and resizer. The complete installed deployment tells the other half of the story:
| Package | Version | Installed footprint | Production packages |
|---|---|---|---|
| PureJsImage | 0.9.0 | 2.9 MiB | 1 |
| Jimp | 1.6.0 | 29.3 MiB | 70 |
| image-js | 1.7.0 | 17.0 MiB | 46 |
| jSquash JPEG + PNG + resize | JPEG 1.6.0; PNG 3.1.1; resize 2.1.1 | 1.0 MiB | 3 |
| Sharp, including native libvips | 0.35.3 | 18.9 MiB | 6 |
See bundle details and reproduction commands →
- First-party codecs implemented in this repository, with strict TypeScript as the permanent portable reference engine.
- Zero runtime dependencies and no required native image stack, WebAssembly, or external binaries.
- Codec-native bounded execution where the format permits it, with explicit memory classes and documented full-frame fallbacks.
- The same reference behavior across Node.js and modern browsers.
- A native whole-slide browser demo that opens Aperio SVS tiles through HTTP Range without conversion, a tile server, or a sidecar index.
- Explicit unsupported boundaries instead of plausible corruption.
- Permanent conformance corpora, hostile-input tests, and reproducible performance and memory measurements.
- Optional first-party acceleration that preserves the reference contract rather than replacing it.
If you can deploy native libvips and throughput or latency is the main constraint, use Sharp. It was 1.9×–12.2× faster than the default TypeScript path across the five commonly supported benchmark workflows. PureJsImage is the better fit when the same code must run in Node.js and browsers or edge workers, native addons or WASM are prohibited, or a zero-dependency deployment materially simplifies an air-gapped or supply-chain-restricted build. The measured installed footprint was 2.9 MiB and one package for PureJsImage versus 18.9 MiB and six production packages for Sharp.
The repository uses TypeScript strict mode, Biome, and Vitest.
npm install
npm run checkSee CONTRIBUTING.md for the repository checklist and the project specification for the architecture and implementation principles.
The detailed implementation and acceleration plans remain in the project roadmap.
Special thanks to Imazen for building the image corpus that made broad, real-world codec testing possible.


