PureJsImage · JPEG XL

JPEG XL JavaScript library comparison

Dated public-API measurements across PureJsImage, jSquash, jxl-oxide-wasm and wasm-vips. Compare practical workflows and their limits.

These tools use the current repository build. Repository functionality may be newer than the npm release. Static lossy encoding is qualified; lossy animation remains Experimental.

Quick answer

PureJsImage now decodes all 16 pinned lossless inputs exactly, including the seven that previously failed. It has the smallest loaded toolkit among these tested APIs and exposes native sample and exact JPEG workflows. The WASM alternatives are faster on several workloads; most lossy quality comparisons remain unresolved under the finite sampling budget.

Measured 2026-10-01 on one Linux workstation. PureJsImage base revision 590bbf08968f0f12c32bb4aa5c84627ba5dbeab4 with local codec changes. Codec source SHA-256: 088ba519315f3498cd388eaf0c7bef92c0a15c6702107e04219ed486760332ca. These results measure a local repository build.

Additional fixture checks now cover multi-group global Palette and Squeeze streams, all 27 VarDCT transforms, grouped progressive alpha, and ordinary ICC and structured SDR conversion through 16-bit integer precision. Relative custom white points and primaries also convert to sRGB. Ordinary IEEE float input preserves source samples, converts supported ICC or structured SDR color to straight sRGB16, and supports explicit PQ/HLG/linear HDR conversion. Ordinary lossless output preserves binary32 samples and matching retained ICC profiles. Native Modular float and CMYK frames now compose before display conversion, including selected animation frames. Separate libjxl, LittleCMS and FFmpeg references pass in Node and three browsers. Further independent checks cover 24 mixed-alpha and wide-integer layouts, custom float fields, RGB LUT8/LUT16 XYZ profiles, relative custom HDR, integer ICC preservation, Float32 animation writing and selected HDR VarDCT frames. HLG and PQ forward writing support associated alpha and nondefault luminance targets. HDR VarDCT reference frames compose in linear color before the output transfer curve. Explicit lossy Float32 output uses Modular mantissa rounding with exact alpha. Independent native groups decode cropped bands. Remaining gaps include transformed implicit-palette prefix layouts, nonrelative custom color, wider ICC/CMYK profile families, wider integer VarDCT display, float VarDCT writing and progressive float-alpha or animation output. The speed and size comparison was rerun on the current source with the same pinned inputs and comparator versions. These additional capability checks cover cases beyond that shared comparison cohort. See the codec support checklist for the supported scope.

Correctness and API coverage

Chromium main set: 16 still inputs, with 13 lossy decode fixtures. Counts retain failures. Encode counts include multiple settings. “Incorrect” means the declared exact-sample or RGB8 tolerance contract failed, not a claim that the library is generally broken.

Verified / attempted public workflows; all other outcomes retained
LibraryOperationVerified / totalAPI absentUnsupportedIncorrectExecution failureUnavailable / not tested
PureJsImage repository builddecode-lossless16 / 1600000 / 0
PureJsImage repository builddecode-lossy13 / 1300000 / 0
PureJsImage repository buildencode45 / 4500000 / 0
PureJsImage repository buildroundtrip1 / 100000 / 0
@jsquash/jxl 1.3.0decode-lossless12 / 1630100 / 0
@jsquash/jxl 1.3.0decode-lossy4 / 1300900 / 0
@jsquash/jxl 1.3.0encode42 / 4530000 / 0
@jsquash/jxl 1.3.0roundtrip1 / 100000 / 0
jxl-oxide-wasm 0.12.6decode-lossless16 / 1600000 / 0
jxl-oxide-wasm 0.12.6decode-lossy13 / 1300000 / 0
jxl-oxide-wasm 0.12.6encode0 / 45450000 / 0
jxl-oxide-wasm 0.12.6roundtrip0 / 110000 / 0
wasm-vips 0.0.19decode-lossless16 / 1600000 / 0
wasm-vips 0.0.19decode-lossy13 / 1300000 / 0
wasm-vips 0.0.19encode45 / 4500000 / 0
wasm-vips 0.0.19roundtrip1 / 100000 / 0
Native, animation, metadata and access probes
LibraryFeatureStatusScope / evidence
purejsimageordinary wide integer preservationverifiednode · benchmark/jpegxl/comparison/results/specialized-node.json
purejsimageordinary integer color with floating alphaverifiednode · benchmark/jpegxl/comparison/results/specialized-node.json
purejsimageordinary Float32 lossless encodingverifiednode · benchmark/jpegxl/comparison/results/specialized-node.json
purejsimageordinary integer ICC preservationverifiednode · benchmark/jpegxl/comparison/results/specialized-node.json
purejsimagefloating VarDCT and linear HDR reference blendsverifiednode · benchmark/jpegxl/comparison/results/specialized-node.json
purejsimagenative float samplesverifiednode · benchmark/jpegxl/comparison/results/specialized-node.json
purejsimageexplicit display conversionverifiednode · benchmark/jpegxl/comparison/results/specialized-node.json
purejsimageHDR headroom 1/2/4verifiednode · benchmark/jpegxl/comparison/results/specialized-node.json
purejsimageextra channel samples and meaningverifiednode · benchmark/jpegxl/comparison/results/specialized-node.json
purejsimageICC preservationverifiednode · benchmark/jpegxl/comparison/results/specialized-node.json
purejsimageanimation encodeverifiednode · benchmark/jpegxl/comparison/results/specialized-node.json
purejsimageanimation decodeverifiednode · benchmark/jpegxl/comparison/results/specialized-node.json
purejsimageanimation compositionverifiednode · benchmark/jpegxl/comparison/results/specialized-node.json
purejsimageframe timingverifiednode · benchmark/jpegxl/comparison/results/specialized-node.json
purejsimageframe accessverifiednode · benchmark/jpegxl/comparison/results/specialized-node.json
purejsimageexact JPEG recompressionverifiednode · benchmark/jpegxl/comparison/results/specialized-node.json
purejsimageoriginal-byte reconstructionverifiednode · benchmark/jpegxl/comparison/results/specialized-node.json
purejsimageincremental input (known-length read-at source)verifiednode · benchmark/jpegxl/comparison/results/specialized-node.json
purejsimageregion reconstructionverifiednode · benchmark/jpegxl/comparison/results/specialized-node.json
purejsimagereduced resolutionverifiednode · benchmark/jpegxl/comparison/results/specialized-node.json
purejsimageearly progressive outputverifiednode · benchmark/jpegxl/comparison/results/specialized-node.json
purejsimageselective source readsverifiednode · benchmark/jpegxl/comparison/results/specialized-node.json
purejsimageExif/XMP preservationnot testednode/browser · unavailable
purejsimageorientation renderingnot testednode/browser · unavailable
purejsimagearbitrary animation blend modesnot testednode/browser · unavailable
purejsimagemultithread speedupnot testednode/browser · unavailable
purejsimagelossy native 16-bit encode/decodenot testednode/browser · unavailable
jsquashnative integer above 8-bitAPI not exposedpublic API inspection · survey.json
jsquashnative floatAPI not exposedpublic API inspection · survey.json
jsquashextra channelsAPI not exposedpublic API inspection · survey.json
jsquashanimation decodeAPI not exposedpublic API inspection · survey.json
jsquashframe timingAPI not exposedpublic API inspection · survey.json
jsquashframe accessAPI not exposedpublic API inspection · survey.json
jsquashanimation encodeAPI not exposedpublic API inspection · survey.json
jsquashICCAPI not exposedpublic API inspection · survey.json
jsquashmetadata accessAPI not exposedpublic API inspection · survey.json
jsquashincremental inputAPI not exposedpublic API inspection · survey.json
jsquashearly progressive outputAPI not exposedpublic API inspection · survey.json
jsquashreduced resolutionAPI not exposedpublic API inspection · survey.json
jsquashregion reconstructionAPI not exposedpublic API inspection · survey.json
jsquashselective source readsAPI not exposedpublic API inspection · survey.json
jsquashexact JPEG recompressionAPI not exposedpublic API inspection · survey.json
jsquashoriginal-byte reconstructionAPI not exposedpublic API inspection · survey.json
jsquashExif/XMP preservationnot testednode/browser · unavailable
jsquashorientation renderingnot testednode/browser · unavailable
jsquasharbitrary animation blend modesnot testednode/browser · unavailable
jsquashmultithread speedupnot testednode/browser · unavailable
jsquashlossy native 16-bit encode/decodenot testednode/browser · unavailable
oxideframe timingincorrect outputnode · benchmark/jpegxl/comparison/results/specialized-node.json
oxideanimation decodeverifiednode · benchmark/jpegxl/comparison/results/specialized-node.json
oxideanimation compositionverifiednode · benchmark/jpegxl/comparison/results/specialized-node.json
oxideframe accessverifiednode · benchmark/jpegxl/comparison/results/specialized-node.json
oxideincremental input (sequential feed)verifiednode · benchmark/jpegxl/comparison/results/specialized-node.json
oxideearly progressive outputverifiednode · benchmark/jpegxl/comparison/results/specialized-node.json
oxideregion reconstructionverifiednode · benchmark/jpegxl/comparison/results/specialized-node.json
oxideICC accessverifiednode · benchmark/jpegxl/comparison/results/specialized-node.json
oxidestatic encodeAPI not exposedpublic API inspection · survey.json
oxideanimation encodeAPI not exposedpublic API inspection · survey.json
oxidenative raw integer planesAPI not exposedpublic API inspection · survey.json
oxidenative raw float planesAPI not exposedpublic API inspection · survey.json
oxideextra channelsAPI not exposedpublic API inspection · survey.json
oxidereduced resolutionAPI not exposedpublic API inspection · survey.json
oxideselective source readsAPI not exposedpublic API inspection · survey.json
oxideexact JPEG recompressionAPI not exposedpublic API inspection · survey.json
oxideoriginal-byte reconstructionAPI not exposedpublic API inspection · survey.json
oxideExif/XMP preservationnot testednode/browser · unavailable
oxideorientation renderingnot testednode/browser · unavailable
oxidearbitrary animation blend modesnot testednode/browser · unavailable
oxidemultithread speedupnot testednode/browser · unavailable
oxidelossy native 16-bit encode/decodenot testednode/browser · unavailable
vipsnative float samplesverifiednode · benchmark/jpegxl/comparison/results/specialized-node.json
vipsnative-light HDRverifiednode · benchmark/jpegxl/comparison/results/specialized-node.json
vipsanimation decodeverifiednode · benchmark/jpegxl/comparison/results/specialized-node.json
vipsframe timingverifiednode · benchmark/jpegxl/comparison/results/specialized-node.json
vipsanimation compositionverifiednode · benchmark/jpegxl/comparison/results/specialized-node.json
vipsframe accessverifiednode · benchmark/jpegxl/comparison/results/specialized-node.json
vipsICC accessverifiednode · benchmark/jpegxl/comparison/results/specialized-node.json
vipsanimation encode with exact 3/5 ms delaysincorrect outputnode · benchmark/jpegxl/comparison/results/specialized-node.json
vipsanimation encode at ordinary 30/50 ms delaysverifiednode · benchmark/jpegxl/comparison/results/specialized-node.json
vipsincremental inputAPI not exposedpublic API inspection · survey.json
vipsearly progressive outputAPI not exposedpublic API inspection · survey.json
vipsreduced-resolution JPEG XL reconstructionAPI not exposedpublic API inspection · survey.json
vipsselective source readsAPI not exposedpublic API inspection · survey.json
vipsexact JPEG recompressionAPI not exposedpublic API inspection · survey.json
vipsoriginal-byte reconstructionAPI not exposedpublic API inspection · survey.json
vipsExif/XMP preservationnot testednode/browser · unavailable
vipsorientation renderingnot testednode/browser · unavailable
vipsarbitrary animation blend modesnot testednode/browser · unavailable
vipsmultithread speedupnot testednode/browser · unavailable
vipslossy native 16-bit encode/decodenot testednode/browser · unavailable
vipsregion reconstructionnot testednode/browser · unavailable

All four initialized in Node and Chromium, with representative Firefox and WebKit checks. Lossy animation in PureJsImage remains Experimental. Exact 3/5 ms timing is a separate edge case: wasm-vips normalizes it to 100/100 ms; its 30/50 ms control passes. Oxide timing fields did not match the expected timeline; its frame access and composition passed.

Quality and compressed size

1 of 24 PureJsImage/comparator/target comparisons have adequate measured brackets on both sides. The rest remain unresolved. Four diagnostics use SSIMULACRA2 70/80/90 and retain Butteraugli coordinates, monotonicity evidence and missing intervals. Each engine/source has at most six lossy points. Alpha is scored on black and white; comparator default alpha distortion remains visible.

At SSIMULACRA2 80, im26-1030-diagnostic: PureJsImage / vips size is 1.212×. This capped photo does not establish original-size, HDR or transparency quality.

Actual source crop from the measured photo
source · normalized source
Actual purejsimage crop from the measured photo
purejsimage · measured SSIMULACRA2 80.037, setting 3.2469
Actual vips crop from the measured photo
vips · measured SSIMULACRA2 79.983, setting 1.7586

Identical 256 × 192 crop coordinates, shown without rescaling. Result crops use actual nearby measured points, not synthesized target-80 images. Source: lilith, PD-own, public domain dedication in imazen-26. Crop hashes and provenance.

Paired lossless size and warm-time ratios
Runtime / comparatorScope / effortPairsMedian bytes ratioMedian warm-time ratio
chromium / jsquashcapped / 181.04711.641
chromium / jsquashcapped / 781.2425.586
chromium / jsquashoriginal / 150.9932.922
chromium / vipscapped / 181.15125.360
chromium / vipscapped / 781.1319.574
chromium / vipsoriginal / 151.0186.378
chromium / vipsspecialized / 130.7081.211
node / jsquashcapped / 181.04713.360
node / jsquashcapped / 781.2426.742
node / jsquashoriginal / 150.9933.141
node / vipscapped / 181.15129.190
node / vipscapped / 781.13111.503
node / vipsoriginal / 151.0188.387
node / vipsspecialized / 130.7081.130

PureJsImage divided by comparator, verified pairs only. Complete originals, capped diagnostics and precision fixtures stay separate. Equal effort numbers do not mean equal work.

Warm encoding and startup

Representative capped photo, lossless effort 1, Node. Three warm operations after initialization, outputs materialized and independently checked. These are exact-output comparisons, not equal numeric lossy distances.

purejsimage713.0 ms [703.8, 716.9]
jsquash54.5 ms [53.2, 55.3]
vips27.8 ms [26.6, 28.2]

Oxide exposes no encoding API, so it has no encoding time.

Cold initialization plus first encode

Same Node photo workflow; median [minimum, maximum], three fresh processes
LibraryStatusCold millisecondsPeak process RSS, MiB
purejsimageverified773.4 [763.8, 774.9] (n=3)210.2 [177.2, 212.3] (n=3)
jsquashverified131.0 [124.0, 143.5] (n=3)179.7 [179.6, 179.7] (n=3)
oxideAPI not exposedunavailableunavailable
vipsverified200.0 [197.2, 203.7] (n=3)168.2 [167.5, 170.1] (n=3)

Cold reports separately include initialization and first operation, with three fresh observations for selected cells. Process/worker harness startup and input fetching are excluded. Warm operations reuse initialized modules. Small differences on this interactive host are not durable wins.

Oxide's decoder returns PNG, so its measured workflow includes mandatory PNG export. No raw-decode speed ranking equates that workflow with the other outputs.

Startup assets

Loaded encode/decode toolkit, required JS, WASM and worker assets counted once. Includes the common benchmark worker; these are not minimum decode-only application bundles. Brotli quality 11 is an offline estimate; actual requests used uncompressed no-store responses.

purejsimage124.3 KiB Brotli · 464.1 KiB raw
jsquash623.9 KiB Brotli · 2215.1 KiB raw
oxide422.7 KiB Brotli · 1674.1 KiB raw
vips2202.0 KiB Brotli · 7294.1 KiB raw

wasm-vips uses concurrency(1), but still starts its pthread runtime and requires cross-origin isolation. Actual repeated worker-script transfers remain in the raw report. Native libjxl is an oracle only and never enters JavaScript runtime comparisons.

Preview I/O and memory

PureJsImage's scaled region and oxide's full-frame PNG preview are different workflows; their latency is not ranked. The report retains first nonempty output, requested source bytes and transferred bytes. First-preview visual fidelity was not independently scored. Inspect actual stages in the local tool.

First nonempty preview, Chromium, median [minimum, maximum]. Different outputs, no speed ranking.
LibraryMillisecondsTransferred source bytesWorkflow
purejsimage59.9 [59.8, 60.6] (n=3)20544.0 [20544.0, 20544.0] (n=3)HTTP Range source, 32x32 region at scale 2; first nonempty pixel block; no canvas upload; geometry-only preview check
jsquashunavailableunavailablePublic incremental preview API not exposed
oxide61.9 [59.1, 66.9] (n=3)4096.0 [4096.0, 4096.0] (n=3)Sequential 4096-byte HTTP chunks; first render exported to PNG; full frame, no reduced-resolution API, no canvas upload
vipsunavailableunavailablePublic incremental preview API not exposed

Node peak RSS is isolated per subject process and includes the runtime, prepared source, output, WASM and worker infrastructure. It is not encoder-managed storage. WASM linear memory was not separately instrumented. Browser memory is unavailable across the tested isolation combinations. Canvas memory was not measured.

Methodology, uncertainty and downloads

Common inputs are pinned sRGB8 straight-alpha samples with orientation 1; high-depth samples are separate. Lossless validation includes color beneath zero alpha. PureJsImage's default lossy alpha must be exact; other wrappers' public lossy defaults may alter alpha, with that error retained. Native float probes and headroom 1/2/4 are not a perceptual HDR compression study.

Missing brackets, API boundaries, untested features and reference-limited comparisons remain visible. These inspected development sources are regression evidence, not an unseen holdout. The full comparison was rerun after the sample, ICC/HDR, animation and native crop updates. The dated library survey retains the pinned comparator versions.