Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

220 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

██████╗ ██╗   ██╗██████╗ ███████╗         ██╗███████╗
██╔══██╗██║   ██║██╔══██╗██╔════╝         ██║██╔════╝
██████╔╝██║   ██║██████╔╝█████╗           ██║███████╗
██╔═══╝ ██║   ██║██╔══██╗██╔══╝      ██   ██║╚════██║
██║     ╚██████╔╝██║  ██║███████╗     ╚█████╔╝███████║
╚═╝      ╚═════╝ ╚═╝  ╚═╝╚══════╝      ╚════╝ ╚══════╝
                         I M A G E

First-party image codecs and low-memory raster processing in strict TypeScript

Portable reference engine · optional first-party acceleration · zero runtime dependencies

npm version CI status TypeScript version Node.js version

Zero runtime dependencies MIT license Strict TypeScript reference engine

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.

Install

npm install purejsimage

PureJsImage 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 →

Quick start

Common image pipeline

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.

TIFF with a scientific profile

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 rasters and explicit display mapping

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

Live browser demo

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.

Optional WASM acceleration

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 →

Supported codecs

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.

Beyond ordinary image conversion

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

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:

TIFF library comparison

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

Benchmarks

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.

Image workflow speed comparison across seven isolated engines, including default and WASM PureJsImage variants.

Image workflow output quality comparison across seven engines measured as premultiplied-RGBA PSNR against an exact-area reference.

Image workflow absolute peak memory comparison across seven isolated engines.

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 →

Lambda memory sizing

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.

Bundle size

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 →

Why PureJsImage?

  • 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.

When to use Sharp instead

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.

Read the practical guides →

Development

The repository uses TypeScript strict mode, Biome, and Vitest.

npm install
npm run check

Read the contributor guide →

See 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

Special thanks to Imazen for building the image corpus that made broad, real-world codec testing possible.

About

Low-memory, zero-dependency image codecs and processing in pure TypeScript for Node.js, browsers, and serverless runtimes. Bounded decode/resize pipelines plus deep TIFF, scientific raster, GeoTIFF, OME-TIFF, and whole-slide support.

Topics

Resources

Contributing

Security policy

Stars

82 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages