PureJsImage is an open-source image processing library written in strict TypeScript. It runs in Node.js and modern browsers, has no runtime dependency tree, and focuses on low peak memory use for image resize, crop, conversion, scientific raster, geographic raster, and whole-slide workflows.
The project started because a small uploaded JPEG could push one of my AWS Lambda image jobs past its memory limit. The compressed file was only a few megabytes, but decoding it into one or more full-size bitmaps was enough to make the process use more than 1 GiB.
That problem led me to build first-party JavaScript codecs that can work in bounded rows, scanlines, strips, tiles, regions, or compact coefficient storage when the format allows it.
Quick links
- PureJsImage website and documentation
- PureJsImage on GitHub
- PureJsImage on npm
- Live image demo
- Performance results and methodology
- Codec support matrix
- Scientific format reference
Install it with:
npm install purejsimage
PureJsImage v0.16.0 requires Node.js 22 or newer. Browser applications use the purejsimage/browser entry point. The public APIs are still pre-1.0, so they may receive breaking refinements.
Why image processing can consume so much memory
Compressed file size is not the same as decoded image size.
A 6000 × 4000 image has 24 million pixels. One RGBA bitmap for that image needs about 96 MB before accounting for the source bytes, decoder state, resize intermediates, output buffers, JavaScript runtime, and allocator overhead. A pipeline that retains several full-frame buffers can quickly exceed a 256 MiB or 512 MiB serverless limit.
The usual sequence can look like this:
decode the complete source image
→ create a full crop buffer
→ create a full resize buffer
→ convert color data
→ encode the result
PureJsImage tries to reduce the amount of work and memory before optimizing individual instructions. Depending on the codec and operation, it can:
- push crop and resize requirements toward the decoder
- retain bounded MCU rows, scanlines, strips, tiles, or regions
- reuse typed-array scratch buffers
- avoid duplicate full-frame buffers at codec boundaries
- read only selected byte ranges from remote TIFF, whole-slide, scientific, and geographic sources
- preserve native scientific sample types until an application explicitly renders them
Some formats and operations still require larger state. PureJsImage documents those cases instead of presenting every path as constant-memory.
Current low-memory benchmark results
The current web-codec snapshot is dated August 18, 2026. It contains 122 validated passes and 25 explicit unsupported rows across JPEG, PNG, WebP, TIFF, and AVIF workflows, with no invalid outputs or benchmark errors in that snapshot.
On the shared 24-megapixel photo pipeline, the default TypeScript path used 84.9% less absolute peak RSS than Jimp: 180.1 MiB versus 1189.3 MiB. That ratio belongs to one pinned corpus, harness, environment fingerprint, and date. The performance page publishes the charts, methodology, and result artifacts.
Median wall time from the current validated web-codec snapshot. Sharp uses native libvips and is included as a native reference, not as a pure JavaScript implementation.
Absolute process peak RSS from the same validated snapshot.
I also ran separately dated AWS Lambda measurements. A 256 MiB Lambda completed all four measured 4000 × 3000 resize and conversion workflows with 121–156 MiB maximum use. Memory was only part of the sizing decision: the warm JPEG-to-WebP workflow took 10,601 ms at 256 MiB, 5,261 ms at 512 MiB, and 2,533 ms at 1024 MiB while maximum use stayed near 120–122 MiB. AWS allocates more CPU with more Lambda memory, so a larger tier can still be the right choice for latency.
See the dated AWS Lambda results and Lambda usage guide before choosing a production memory tier. Re-measure with your images, concurrency, architecture, and latency requirements.
If native libvips is available and maximum throughput is the main goal, use Sharp. PureJsImage is aimed at projects where portable TypeScript, browser parity, explicit dependency boundaries, native scientific data, or lower-memory JavaScript execution are important.
Pure TypeScript by default, optional WASM by explicit import
The ordinary codec implementations are first-party TypeScript. The published package has no runtime dependency tree, native addon, required system executable, or automatic WASM download.
JPEG and PNG also have optional WASM accelerators. They live behind separate imports and explicit registration. Applications that do not opt in continue to use the TypeScript implementations.
You can register only the codecs an application needs:
import { createImageLibrary } from "purejsimage";
import { jpegCodec } from "purejsimage/codecs/jpeg";
import { pngCodec } from "purejsimage/codecs/png";
const images = createImageLibrary({ codecs: [jpegCodec, pngCodec] });
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, inputs can be File, Blob, ArrayBuffer, Uint8Array, fetched bytes, or an explicit ImageSource. Browser outputs can be Uint8Array, Blob, or an explicit ImageSink.
Complete ordinary image codec support
PureJsImage currently has 14 stable ordinary codecs. The generated codec matrix contains the detailed feature and limitation checklists.
| Format | Decode | Encode | Current boundary |
|---|---|---|---|
| JPEG | Yes | Yes | Optional explicit WASM accelerator |
| PNG | Yes | Yes | Optional explicit WASM accelerator |
| WebP | Yes | Yes | Static images |
| BMP | Yes | Yes | Portable TypeScript codec |
| TIFF | Yes | Yes | Includes BigTIFF plus GeoTIFF and Cloud Optimized GeoTIFF workflows |
| GIF | Yes | No | Static decode or explicitly selected frame 0 |
| ICO | Yes | No | Selects and decodes embedded icon images |
| JPEG 2000 / JP2 | Yes | No | Decode only |
| AVIF | Yes | Limited | Broad decode support and a documented encode subset |
| JPEG XL | Limited | No | Documented decode subset with explicit rejection outside it |
| Radiance HDR / RGBE | Yes | Yes | HDR decode and encode |
| QOI | Yes | Yes | Decode and encode |
| Netpbm and PFM | Yes | Yes | PBM, PGM, PPM, PAM, and PFM |
| TGA / TARGA | Yes | Yes | Decode and encode |
“Limited” means the project implements a useful, tested subset and rejects unsupported syntax explicitly. An unsupported file should not silently become plausible-looking corrupted output.
Experimental HEIF and HEIC support
HEIF/HEIC decoding is experimental and available only through purejsimage/codecs/experimental/heic. It is excluded from allCodecs, is never selected automatically, and has no encoder.
HEIC commonly carries HEVC/H.265 content that may be subject to third-party patent rights. The project code is MIT licensed, but that license grants no third-party patent rights. Users and distributors, including commercial products and services, must evaluate their own licensing obligations.
Scientific, microscopy, medical, and whole-slide image support
Ordinary image libraries usually convert everything into RGB or RGBA. That is a poor default for a hyperspectral cube, electron microscopy dataset, medical volume, diffraction pattern, elevation grid, or pathology slide.
PureJsImage has a separate scientific dataset API that keeps labeled axes, calibration, native numeric sample types, and selected-resource semantics. An application chooses when and how to map those samples into display pixels.
The v0.16.0 package exposes 33 scientific readers:
- Common raster and whole-slide (9): PNG, JPEG, WebP, BMP, JPEG 2000/JP2, TIFF, OME-TIFF, OME-Zarr, and Aperio SVS
- Electron microscopy (7): Gatan DigitalMicrograph DM3/DM4, FEI/Thermo TIA SER, TIA EMI, NCEM EMD 0.2, FEI/Thermo Velox EMD, NanoMegas ASTAR blockfile, and Quantum Detectors Merlin MIB
- AFM, SPM, and surface metrology (5): Gwyddion Simple Field, Nanonis SXM, Igor Binary Wave v5, Digital Surf SUR/PRO, and X3P surface exchange
- Medical and volume interchange (5): MRC/CCP4, NRRD, MetaImage MHD/MHA, NIfTI-1/2, and DICOM Part 10 Image
- Spectroscopy and detector interchange (6): ENVI, FITS, CBF/imgCIF, Lispix RPL/RAW, EMSA/MAS spectrum, and ANG/CTF orientation maps
- Raw numeric interchange (1): NumPy NPY
Direct-range readers can request only the source spans needed for metadata, a region, a spectrum, a volume plane, or a whole-slide tile. The exact per-reader imports and boundaries are listed in the scientific format reference.
The Scientific Raster Explorer maps native ENVI samples to display pixels in the browser. It does not rewrite the source cube as RGB.
The whole-slide demo reads visible pyramid tiles from a 1.98 GiB Aperio SVS file through HTTP Range requests.
There is also PureJsImage Lab for exploring supported microscopy and scientific files locally in the browser.
Geographic raster support
PureJsImage also exposes lazy geographic raster readers through purejsimage/geo/readers. The current surface includes:
- GeoTIFF and Cloud Optimized GeoTIFF
- GeoZarr v2 and v3, including supported v3 shards and multiscales
- TIFF, JPEG, and PNG with world files
- georeferenced ENVI
- Esri ASCII Grid
- SRTM HGT
- classic NetCDF CDF-1 and CDF-2 with regular rectilinear CF coordinates
These readers use a shared geographic raster contract for spatial reference evidence, affine transforms, bounds, nodata, selectable axes, region reads, and reprojection where supported. The Geo demos and documentation show the current boundaries.
Compatibility is part of the implementation
A new image codec needs evidence against real files, not only hand-built happy-path fixtures. PureJsImage uses pinned corpora, including the excellent Imazen codec corpus, plus focused hostile-input, oracle, browser, and round-trip checks.
Benchmark rows count only when the output passes the relevant correctness or quality gate. Unsupported syntax is recorded as unsupported. Invalid output is a failed result regardless of speed.
The benchmark reports include more than wall time. Depending on the suite, they also record:
- absolute peak RSS
- output validity and quality evidence
- cold and warm behavior
- initialization time
- external and
ArrayBuffermemory - source request count and bytes read
- overfetch for range-backed data
- package and bundle footprint
That evidence is published in the benchmark documentation and on the performance page.
Why I am building it
I wanted a JavaScript image library for a narrow practical job: resize, crop, convert, and inspect images without creating avoidable memory pressure in Lambda or a low-memory browser.
Once the codec foundation worked, TIFF led me toward scientific and medical imaging. My bachelor’s degree is in chemistry, and I worked in a 3D NMR lab in college. I have used spectroscopy instruments and know how awkward it can be to move real scientific data between specialized tools.
The project has grown into a portable TypeScript implementation for ordinary images, scientific rasters, microscopy, medical volumes, whole-slide pathology, and geographic rasters. It is MIT licensed and open to contributions.
If you want to help, start with the GitHub repository, read the contributing guide, try the browser demos, or open an issue with a real file and a clearly described unsupported workflow.
The original version of this announcement is on ard.ninja.




Top comments (0)