Field Notes
Why I Started Building PureJsImage
Quick Summary
tl;dr: I wanted to build a fast, low memory pressure pure javascript image processing library that can work in things like lambdas, or browsers on computers with little memory. Then I expanded into more codec compatibility and started tackling science, geo, and medical support.
- Website: purejsimage.com
- GitHub: a-r-d/PureJsImage
- npm: purejsimage
Why I started
Recently I was looking through my logs in AWS on lambdas and I noticed a couple OOM errors, which I traced down to Jimp. Now, yes, let me be super clear: I know you are supposed to use Sharp if you care about performance, but it’s an 18 MB binary so not ideal to ship into a lambda (cold boot times and all). So I was using Jimp. But what I also didn’t realize was that Jimp is a memory hog on its JPEG codec because it loads the entire bitmap into memory before doing transforms.
So even trying to resize a few-megabyte JPEG (which customers frequently upload for icons), it was using 1000 megabytes of memory!
Now, of course the Jimp team does call this out in their docs if you bother to read them, which I apparently never did. I’m not trying to disparage Jimp, they made a great library that supports all kinds of text image manipulation features which I don’t plan to add. I just want to efficiently resize, convert and crop images. It’s my fault for using it in a way it wasn’t meant to be used.
Anyway, I looked at another pure javascript alternative, image-js, and it had the same issue! High peak memory usage. I even looked at jSquash, a WASM version, and it again had the same issue!
So anyway, I have been coding for 15 years professionally, but I adopted the new agentic tools very early. I have a good feeling now for what kind of engineering LLMs can accomplish if you can put hard constraints with verifiable output on them. And re-writing image codecs to hillclimb various benchmarks is perfect with the right tooling around them.
I was also inspired by PGRust, where they are painstakingly rebuilding Postgres in Rust by doing a lot of engineering around test cases.
Current state: both faster and lower peak memory
The live charts are on the performance page. You can now use this library for simple transforms in lambdas that only have a 256 mb memory limit without crashing, I tested it.
Median wall time from the current validated web codec snapshot. Sharp is native libvips, not pure JS.
Absolute process peak RSS from the same snapshot.
Codec compatibility
After I accomplished the goals I wanted on performance, I started turning my eye toward compatibility.
The problem with a new codec project is that it’s not battle tested. Obviously though, again you can put engineering constraints on this and build tooling to automate it. So that’s what I did.
I found things like the Imazen codec corpus, which had hundreds of unique test cases (thank you btw, hugely useful repo).
As of today our JPEG codec passes all but 2. Same with the TIFF codec, and same with the AVIF codec. The unsupported features are for things like ThunderScan and animated AVIF, things outside of the scope of this project (for now).
Science, geo, and medical - Beyond web codecs
Web codecs are great, but when I started adding support for TIFF features I started to get interested in scientific & medical imaging uses.
Btw, my bachelor’s is in Chemistry. I worked in a 3D NMR lab in college so I have used a ton of various spectroscopy instruments.
Now there are a ton of libraries of scientific imaging, but what I thought would be nice to do is this:
- Implement a large corpus of scientific raster imaging support in one library
- Do it in a performant way
- Do it in a portable way, in a javascript library that can run anywhere (not Python, not Java, not C)
- Build demos and showcase them widely
- And of course it’s an MIT licensed open source project so it’s free and anyone can use it for any purpose.
So that’s been my focus where things have ended up.
The scientific explorer maps native samples to display pixels in the tab.
The whole-slide demo fetches only the visible pyramid tiles from a 1.98 GiB Aperio SVS file.
There’s also PureJsImage Lab if you want to poke at electron microscopy files locally in the browser.
Contributing?
This is the coolest part: it should be fairly easy to contribute in some respects.
I don’t expect it will be easy for many people to add codecs; for now they can request me to do that. But on the other hand, helping contribute by hillclimbing performance benchmarks is somewhat easy.
You can basically fork the repo and pull into Codex on your computer and ask SOL 5.6 on MAX mode to e.g.:
please hillclimb the performance benchmarks on the AVIF codec
And it will follow the skill file in there to painstakingly run a baseline benchmark on your computer and then start trying to unroll loops, refactor things, and come up with optimizations until it finds something with a real benefit that doesn’t have quality or correctness regressions.
Your help & support is appreciated!
Currently supported codecs
If you just want the list: PureJsImage currently has 14 stable ordinary codecs, one experimental HEIF/HEIC decoder, and 33 scientific readers. The live matrix is on the codec support page and the scientific format reference.
Ordinary image codecs
- JPEG - decode and encode, plus an optional WASM accelerator
- PNG - decode and encode, plus an optional WASM accelerator
- WebP - decode and encode (static)
- BMP - decode and encode
- TIFF - decode and encode, including BigTIFF, GeoTIFF, and Cloud Optimized GeoTIFF
- GIF - static decode / explicit frame 0
- ICO - decode
- JPEG 2000 / JP2 - decode
- AVIF - decode, limited encode
- JPEG XL - limited decode
- Radiance HDR / RGBE - decode and encode
- QOI - decode and encode
- Netpbm and PFM - decode and encode (PBM, PGM, PPM, PAM, PFM)
- TGA / TARGA - decode and encode
Experimental codecs
- HEIF / HEIC - experimental decode only, explicit import, not in the default codec set
Optional WASM accelerators
JPEG and PNG have optional WASM accelerators. They never load or activate unless you explicitly import and register them. The default path stays pure TypeScript. More accelerators will land over time.
Scientific, geo, and medical formats
Common raster and whole-slide: TIFF, OME-TIFF, OME-Zarr, Aperio SVS, JPEG 2000 / JP2
Electron microscopy: Gatan DigitalMicrograph (DM3 / DM4), FEI/Thermo TIA SER, FEI/Thermo TIA EMI, NCEM EMD, FEI/Thermo Velox EMD, NanoMegas ASTAR blockfile, Quantum Detectors Merlin MIB
AFM, SPM, and surface metrology: Gwyddion Simple Field (GSF), Nanonis SXM, Igor Binary Wave v5, Digital Surf SUR/PRO, X3P
Medical and volume interchange: MRC / CCP4, NRRD, MetaImage MHD / MHA, NIfTI-1 / NIfTI-2, DICOM Part 10
Spectroscopy and detector interchange: ENVI, FITS, CBF / imgCIF, Lispix RPL / RAW, EMSA / MAS spectrum, ANG / CTF orientation map
Raw numeric interchange: NumPy NPY
That’s the current surface. It will keep growing.