Bitruvius Imagery & 3D Suite / TurboJXL Drop-in for libjxl Shipping · Private Beta

Same JPEG XL. 1.8×–4.5× faster to write. 3.5×–5.0× faster to read.

JPEG XL is the image standard that stores map tiles exactly at 8, 16 and 32 bits: elevation, radar, multispectral, masks. TurboJXL writes and reads the same files as the reference library, faster, in memory-safe code, and every file it writes opens in any JPEG XL reader.

Swaps in for libjxl. Native on servers, and in the browser too.

1.8×–4.5×
faster to write, at libjxl’s own setting
Per data type, 8 and 16-bit tiles, one core each side. 2.6× pooled.
3.5×–5.0×
faster to read the tiles back
Our tile and decoder against libjxl’s tile and decoder, per data type.
within 9%
from libjxl’s size, at its densest setting
Ours over libjxl’s: 0.84–1.09× per data type. Complex SAR comes out 16% smaller.
784 of 784
files open exactly in libjxl
And libjxl’s files open exactly in TurboJXL. Same standard, same files.

Per core, our effort against libjxl's same effort, on Intel Core Ultra 9 · Windows · AVX2. Geometric means over 256² to 1024² tiles.

The 60-second version

Map tiles are written once and read millions of times.

The format

JPEG XL

The ISO image standard after JPEG. It keeps every value exactly at 8, 16 and 32 bits, which is why GDAL and Cloud-Optimized GeoTIFFs support it for scientific imagery.

The incumbent

libjxl

The reference library, written in C++. Every JPEG XL pipeline links it today, so it is the bar any replacement has to clear, on the same files.

The bill

Write once, read always

A tile pyramid is built once from each new image, then read on every map request. Build time is compute you pay for once; read speed is servers you pay for every month.

The fix

Faster both ways

TurboJXL writes and reads the same files, faster in both directions, in memory-safe code. No migration: the files stay standard JPEG XL.

Watch it

Race it yourself, in this tab.

The same decoder compiles to WebAssembly with no fork of libjxl in the loop. Race TurboJXL against libjxl's WebAssembly build on the same tiles in your own browser, geospatial types at 256² and 512², lossless or visually lossless, and see the real per-tile decode on your machine.

Image Codec Race: TurboJXL vs libjxl · live in your browser open full screen ↗
loading demo…

What it means for the bill

Faster builds, fewer servers, no migration.

Codec time is machine time. Here is the pooled result, translated.

3.8 hours
for a tile build that used to take 10
8 and 16-bit imagery at the default setting, one core per tile on each side.
75%
fewer servers for the same map traffic
Tiles read back 4.1× faster, pooled over every data type, where reading tiles is the main cost.
0
C++ toolchains on your build machines
No libjxl to build or patch, no cmake on CI or customer build farms.

Illustrative, from the measured Intel figures. A pilot measures yours.

Already at work

The JPEG XL inside Spatial Engine.

Spatial Engine, our imagery engine, converts and reprojects imagery, and TurboJXL is the JPEG XL inside it. Writing lossless JPEG XL at the same setting GDAL uses, it finishes several times sooner in a fraction of the memory. And JPEG XL stores the elevation model in the smallest file of any codec measured on it.

See Spatial Engine
5.25×
faster to convert than GDAL
3.37× on a Mac
4.66×
faster to reproject than GDAL
3.31× on a Mac
52%
less memory to reproject
65% less on a Mac
26 MB
elevation model, smallest measured
71 MB as LERC v4, zero error both

Spatial Engine against GDAL 3.13.3, lossless JPEG XL at effort 5 on both sides, median over the whole test set on the same machine; Windows leads. Our files are 3–5% larger than GDAL's at that effort, and the Mac reprojection figure is an upper bound. Size: USGS 3DEP 1 m DEM.

The full board

Every data type, every tile size, both machines.

676 tiles of real geospatial imagery from 256² to 2048², every one round-tripped lossless, written and read by both libraries at the same setting, one core each. Switch machines; the charts below follow.

Write

Faster than libjxl at its own setting, on every data type.

Single-band radar and quicklooks gain the most, 4.1×–4.5×; three-band imagery runs 1.8×–2.0×. Float elevation is the one data type near parity on writing, 1.0×, and it is the one where the file comes out 43% smaller instead.

Encode speedup vs cjxl -e3 · per core

AI masks (8-bit · 1 band)SAR quicklook (8-bit · 1 band)RGB orthophoto (8-bit · 3 bands)SAR detected (16-bit · 1 band)Hyperspectral (16-bit · 3 bands)SAR complex (16-bit · 3 bands)Elevation (DSM) (32-bit float · 1 band) — — 1× parity with cjxl
0×1×2×3×4×5×6×TurboJXL / cjxl encode MP/s256²512²1024²2048²Tile size (log scale)1×AI masks (8-bit · 1 band) · 256²: 3.50×AI masks (8-bit · 1 band) · 512²: 2.32×AI masks (8-bit · 1 band) · 1024²: 2.15×AI masks (8-bit · 1 band) · 2048²: 3.25×SAR quicklook (8-bit · 1 band) · 256²: 5.39×SAR quicklook (8-bit · 1 band) · 512²: 4.15×SAR quicklook (8-bit · 1 band) · 1024²: 4.06×SAR quicklook (8-bit · 1 band) · 2048²: 3.95×RGB orthophoto (8-bit · 3 bands) · 256²: 2.33×RGB orthophoto (8-bit · 3 bands) · 512²: 2.01×RGB orthophoto (8-bit · 3 bands) · 1024²: 1.75×RGB orthophoto (8-bit · 3 bands) · 2048²: 3.33×SAR detected (16-bit · 1 band) · 256²: 4.79×SAR detected (16-bit · 1 band) · 512²: 3.98×SAR detected (16-bit · 1 band) · 1024²: 3.61×SAR detected (16-bit · 1 band) · 2048²: 3.64×Hyperspectral (16-bit · 3 bands) · 256²: 2.10×Hyperspectral (16-bit · 3 bands) · 512²: 1.89×Hyperspectral (16-bit · 3 bands) · 1024²: 1.67×Hyperspectral (16-bit · 3 bands) · 2048²: 2.89×SAR complex (16-bit · 3 bands) · 256²: 2.00×SAR complex (16-bit · 3 bands) · 512²: 1.78×SAR complex (16-bit · 3 bands) · 1024²: 1.64×SAR complex (16-bit · 3 bands) · 2048²: 2.18×Elevation (DSM) (32-bit float · 1 band) · 256²: 1.26×Elevation (DSM) (32-bit float · 1 band) · 512²: 0.95×Elevation (DSM) (32-bit float · 1 band) · 1024²: 0.96×Elevation (DSM) (32-bit float · 1 band) · 2048²: 3.32×

Intel Core Ultra 9 · Windows · AVX2, one thread on both sides. Our effort 3 against cjxl -d 0 -e 3. In-process encode calls, cjxl's own --num_reps figure, median of 3 for ours. Geometric mean of per-tile ratios per class and size; every tile lossless.

Encode speedup vs cjxl -e7 · per core

AI masks (8-bit · 1 band)SAR quicklook (8-bit · 1 band)RGB orthophoto (8-bit · 3 bands)SAR detected (16-bit · 1 band)Hyperspectral (16-bit · 3 bands)SAR complex (16-bit · 3 bands)Elevation (DSM) (32-bit float · 1 band) — — 1× parity with cjxl
0×1×2×3×4×TurboJXL / cjxl encode MP/s256²512²1024²2048²Tile size (log scale)1×AI masks (8-bit · 1 band) · 256²: 2.69×AI masks (8-bit · 1 band) · 512²: 2.40×AI masks (8-bit · 1 band) · 1024²: 1.16×AI masks (8-bit · 1 band) · 2048²: 1.51×SAR quicklook (8-bit · 1 band) · 256²: 3.60×SAR quicklook (8-bit · 1 band) · 512²: 3.33×SAR quicklook (8-bit · 1 band) · 1024²: 2.35×SAR quicklook (8-bit · 1 band) · 2048²: 2.80×RGB orthophoto (8-bit · 3 bands) · 256²: 2.17×RGB orthophoto (8-bit · 3 bands) · 512²: 3.02×RGB orthophoto (8-bit · 3 bands) · 1024²: 1.97×RGB orthophoto (8-bit · 3 bands) · 2048²: 2.63×SAR detected (16-bit · 1 band) · 256²: 2.38×SAR detected (16-bit · 1 band) · 512²: 1.49×SAR detected (16-bit · 1 band) · 1024²: 1.60×SAR detected (16-bit · 1 band) · 2048²: 1.99×Hyperspectral (16-bit · 3 bands) · 256²: 3.11×Hyperspectral (16-bit · 3 bands) · 512²: 3.29×Hyperspectral (16-bit · 3 bands) · 1024²: 2.81×Hyperspectral (16-bit · 3 bands) · 2048²: 3.65×SAR complex (16-bit · 3 bands) · 256²: 2.65×SAR complex (16-bit · 3 bands) · 512²: 2.88×SAR complex (16-bit · 3 bands) · 1024²: 2.07×SAR complex (16-bit · 3 bands) · 2048²: 2.59×Elevation (DSM) (32-bit float · 1 band) · 256²: 1.60×Elevation (DSM) (32-bit float · 1 band) · 512²: 1.51×Elevation (DSM) (32-bit float · 1 band) · 1024²: 1.08×Elevation (DSM) (32-bit float · 1 band) · 2048²: 1.57×

Effort 7 against effort 7, the densest setting on both sides. Sizes at this effort are within 9% of cjxl's per class, see below.

Encode throughput at 512² · default effort · MP/s per core

TurboJXLcjxl -e3
020406080100MP/sAI masks · TurboJXL: 7070AI masks · cjxl -e3: 3030AI masks8-bit · 1 bandSAR quicklook · TurboJXL: 9494SAR quicklook · cjxl -e3: 2323SAR quicklook8-bit · 1 bandRGB orthophoto · TurboJXL: 1818RGB orthophoto · cjxl -e3: 99RGB orthophoto8-bit · 3 bandsSAR detected · TurboJXL: 8888SAR detected · cjxl -e3: 2222SAR detected16-bit · 1 bandHyperspectral · TurboJXL: 1515Hyperspectral · cjxl -e3: 88Hyperspectral16-bit · 3 bandsSAR complex · TurboJXL: 1515SAR complex · cjxl -e3: 88SAR complex16-bit · 3 bandsElevation (DSM) · TurboJXL: 2222Elevation (DSM) · cjxl -e3: 2424Elevation (DSM)32-bit float · 1 band

Absolute throughput on the tile size pyramids are built from. Megapixels per second of source pixels, one core.

Read

Faster to read: on its own files, and on libjxl's reader too.

Two questions, kept apart. What a user sees: TurboJXL reading the tile it wrote, against libjxl reading the tile libjxl wrote, 3.5×–5.0× faster. And the reader alone: libjxl reading our file, against our reader on the same file, 1.6×–2.7× in our favour at 256² to 1024².

Decode · pipeline view · default effort · per core

AI masks (8-bit · 1 band)SAR quicklook (8-bit · 1 band)RGB orthophoto (8-bit · 3 bands)SAR detected (16-bit · 1 band)Hyperspectral (16-bit · 3 bands)SAR complex (16-bit · 3 bands)Elevation (DSM) (32-bit float · 1 band) — — 1× parity with libjxl
0×1×2×3×4×5×6×7×TurboJXL on our tile / djxl on cjxl's tile256²512²1024²2048²Tile size (log scale)1×AI masks (8-bit · 1 band) · 256²: 6.11×AI masks (8-bit · 1 band) · 512²: 4.66×AI masks (8-bit · 1 band) · 1024²: 4.30×AI masks (8-bit · 1 band) · 2048²: 2.73×SAR quicklook (8-bit · 1 band) · 256²: 5.39×SAR quicklook (8-bit · 1 band) · 512²: 4.35×SAR quicklook (8-bit · 1 band) · 1024²: 3.99×SAR quicklook (8-bit · 1 band) · 2048²: 3.29×RGB orthophoto (8-bit · 3 bands) · 256²: 4.02×RGB orthophoto (8-bit · 3 bands) · 512²: 3.36×RGB orthophoto (8-bit · 3 bands) · 1024²: 3.40×RGB orthophoto (8-bit · 3 bands) · 2048²: 3.20×SAR detected (16-bit · 1 band) · 256²: 5.79×SAR detected (16-bit · 1 band) · 512²: 4.84×SAR detected (16-bit · 1 band) · 1024²: 4.01×SAR detected (16-bit · 1 band) · 2048²: 3.27×Hyperspectral (16-bit · 3 bands) · 256²: 3.93×Hyperspectral (16-bit · 3 bands) · 512²: 3.56×Hyperspectral (16-bit · 3 bands) · 1024²: 3.24×Hyperspectral (16-bit · 3 bands) · 2048²: 2.74×SAR complex (16-bit · 3 bands) · 256²: 3.73×SAR complex (16-bit · 3 bands) · 512²: 3.43×SAR complex (16-bit · 3 bands) · 1024²: 3.25×SAR complex (16-bit · 3 bands) · 2048²: 2.46×Elevation (DSM) (32-bit float · 1 band) · 256²: 4.55×Elevation (DSM) (32-bit float · 1 band) · 512²: 4.02×Elevation (DSM) (32-bit float · 1 band) · 1024²: 3.86×Elevation (DSM) (32-bit float · 1 band) · 2048²: 3.05×

Each side decodes the tile its own encoder produced at effort 3. Planar output for us (the raster API), djxl's own --num_reps figure with output disabled.

Decode · identical bytes · default effort · per core

AI masks (8-bit · 1 band)SAR quicklook (8-bit · 1 band)RGB orthophoto (8-bit · 3 bands)SAR detected (16-bit · 1 band)Hyperspectral (16-bit · 3 bands)SAR complex (16-bit · 3 bands)Elevation (DSM) (32-bit float · 1 band) — — 1× parity with libjxl
0×1×2×3×TurboJXL / djxl on the same file256²512²1024²2048²Tile size (log scale)1×AI masks (8-bit · 1 band) · 256²: 3.19×AI masks (8-bit · 1 band) · 512²: 2.50×AI masks (8-bit · 1 band) · 1024²: 2.42×AI masks (8-bit · 1 band) · 2048²: 0.88×SAR quicklook (8-bit · 1 band) · 256²: 1.87×SAR quicklook (8-bit · 1 band) · 512²: 1.56×SAR quicklook (8-bit · 1 band) · 1024²: 1.35×SAR quicklook (8-bit · 1 band) · 2048²: 1.16×RGB orthophoto (8-bit · 3 bands) · 256²: 2.03×RGB orthophoto (8-bit · 3 bands) · 512²: 1.74×RGB orthophoto (8-bit · 3 bands) · 1024²: 1.90×RGB orthophoto (8-bit · 3 bands) · 2048²: 1.00×SAR detected (16-bit · 1 band) · 256²: 2.01×SAR detected (16-bit · 1 band) · 512²: 1.64×SAR detected (16-bit · 1 band) · 1024²: 1.43×SAR detected (16-bit · 1 band) · 2048²: 1.19×Hyperspectral (16-bit · 3 bands) · 256²: 1.88×Hyperspectral (16-bit · 3 bands) · 512²: 1.69×Hyperspectral (16-bit · 3 bands) · 1024²: 1.73×Hyperspectral (16-bit · 3 bands) · 2048²: 0.88×SAR complex (16-bit · 3 bands) · 256²: 2.17×SAR complex (16-bit · 3 bands) · 512²: 1.93×SAR complex (16-bit · 3 bands) · 1024²: 2.07×SAR complex (16-bit · 3 bands) · 2048²: 1.12×Elevation (DSM) (32-bit float · 1 band) · 256²: 2.44×Elevation (DSM) (32-bit float · 1 band) · 512²: 2.07×Elevation (DSM) (32-bit float · 1 band) · 1024²: 2.37×Elevation (DSM) (32-bit float · 1 band) · 2048²: 0.84×

Both decoders read our effort-3 stream. In 3 of 28 cells djxl is ahead on our bytes (AI masks at 2048², Hyperspectral at 2048², Elevation (DSM) at 2048², down to 0.84×); they are on the chart.

Decode throughput at 512² · default effort · MP/s per core

TurboJXL · our tiledjxl · cjxl's tiledjxl · our tile
050100150200250MP/sAI masks · TurboJXL · our tile: 224224AI masks · djxl · cjxl's tile: 4848AI masks · djxl · our tile: 8989AI masks8-bit · 1 bandSAR quicklook · TurboJXL · our tile: 161161SAR quicklook · djxl · cjxl's tile: 3737SAR quicklook · djxl · our tile: 103103SAR quicklook8-bit · 1 bandRGB orthophoto · TurboJXL · our tile: 4646RGB orthophoto · djxl · cjxl's tile: 1414RGB orthophoto · djxl · our tile: 2626RGB orthophoto8-bit · 3 bandsSAR detected · TurboJXL · our tile: 162162SAR detected · djxl · cjxl's tile: 3434SAR detected · djxl · our tile: 9999SAR detected16-bit · 1 bandHyperspectral · TurboJXL · our tile: 4040Hyperspectral · djxl · cjxl's tile: 1111Hyperspectral · djxl · our tile: 2424Hyperspectral16-bit · 3 bandsSAR complex · TurboJXL · our tile: 4040SAR complex · djxl · cjxl's tile: 1212SAR complex · djxl · our tile: 2121SAR complex16-bit · 3 bandsElevation (DSM) · TurboJXL · our tile: 148148Elevation (DSM) · djxl · cjxl's tile: 3737Elevation (DSM) · djxl · our tile: 7272Elevation (DSM)32-bit float · 1 band

The three columns behind the two views, at 512². Megapixels per second of decoded pixels, one core.

Size

Close to libjxl's size at its densest. Much faster at its default.

At effort 7, the densest setting, every data type lands within 9% of libjxl's file, complex SAR 16% under it, and writing is still 1.4×–3.1× faster. The default is built for the tile server: it never trades read speed for size, so it spends some bytes on masks and RGB against libjxl's default and buys 3.5×–5.0× faster reads on every request. Complex SAR and float elevation come out 22% and 43% smaller even there.

Size at effort 7 · ours / cjxl -e7

TurboJXL effort 7 / cjxl -e7 — — 1.0 = same bytes as cjxl
0.00×0.20×0.40×0.60×0.80×1.00×1.20×Size ratioAI masks · TurboJXL effort 7 / cjxl -e7: 1.05×1.05×AI masks8-bit · 1 bandSAR quicklook · TurboJXL effort 7 / cjxl -e7: 1.04×1.04×SAR quicklook8-bit · 1 bandRGB orthophoto · TurboJXL effort 7 / cjxl -e7: 1.05×1.05×RGB orthophoto8-bit · 3 bandsSAR detected · TurboJXL effort 7 / cjxl -e7: 1.02×1.02×SAR detected16-bit · 1 bandHyperspectral · TurboJXL effort 7 / cjxl -e7: 1.09×1.09×Hyperspectral16-bit · 3 bandsSAR complex · TurboJXL effort 7 / cjxl -e7: 0.84×0.84×SAR complex16-bit · 3 bandsElevation (DSM) · TurboJXL effort 7 / cjxl -e7: 1.07×1.07×Elevation (DSM)32-bit float · 1 band

Geometric mean over 256² to 2048². Size does not depend on the thread count, and the AVX2 and NEON boards agree on every ratio here. On the single-band classes efforts 5 and 7 emit the same bytes; 5 is the cheaper setting to reach them.

The default · what the bytes buy

Default effort against cjxl -e3, per core, 256² to 1024².

ClassBytesDecodeEncode
AI masks8-bit · 1 band+30%5.0×2.6×
SAR quicklook8-bit · 1 band+9%4.5×4.5×
RGB orthophoto8-bit · 3 bands+30%3.6×2.0×
SAR detected16-bit · 1 band+2%4.8×4.1×
Hyperspectral16-bit · 3 bands+6%3.6×1.9×
SAR complex16-bit · 3 bands−22%3.5×1.8×
Elevation (DSM)32-bit float · 1 band−43%4.1×1.0×

Bytes are ours over cjxl -e3; decode is our tile against the reference pipeline; encode is per core at the same effort. A tile is encoded once and decoded on every request.

Drops into the pipeline

A library swap. Not a migration.

Same files

Bit-exact both ways with libjxl 0.12.0: our files open exactly in libjxl, and libjxl's open exactly in TurboJXL. GDAL, Pillow and every JPEG XL reader take the output unchanged.

Memory-safe

The parser rules out the buffer-overflow, use-after-free and out-of-bounds bugs that C and C++ image decoders keep shipping, at compile time rather than by audit.

Scales across tiles

Every figure here is per core, because a pyramid is thousands of independent tiles: a server with N cores keeps N in flight, and each one runs at the speed shown.

For the engineers

  • - Every raster type through one API: 8, 16 and 32-bit float samples, one band or many, planar in and planar out, so a nodata mask, a SAR band and an elevation surface take the same call.
  • - One effort knob, 1 to 9, on the same scale as cjxl -e, on every surface: the native API, the C API, and the prebuilt library. Efforts 1 to 4 are the fast default; 5 and up learn a context tree and ship it only when it is at least 2% smaller than the default's stream.
  • - Interop is gated at release against libjxl 0.12.0 in both directions, on AVX2 and NEON, and on the ISO conformance corpus. 784 of 784 default and effort-7 streams from the single-band corpus decode to exact pixels in djxl 0.12.0 (checked at the v1.2.9 tag).
  • - Three surfaces: the native API, the turbojxl.h C API, and a prebuilt library that also exports the libjxl decoder ABI (JxlDecoder*) for existing integrations.
  • - No C toolchain, no libjxl runtime dependency, no cmake on customer build farms or CI. AVX2, NEON and WASM (relaxed-simd) from one source.
  • - ISAs: AVX2 (x86-64), NEON (ARM64), and WASM (relaxed-simd), one source, three SIMD backends; every chart above switches between the Intel AVX2 board and the Apple M3 Max NEON board, same protocol, same corpus.
  • - One process-wide thread pool; TJXL_THREADS=1 keeps each encode or decode on the thread that called it, for pipelines that parallelise across tiles.

How this was measured

  • Machines: Intel Core Ultra 9 · Windows · AVX2 and Apple M3 Max · macOS · NEON. Measured on TurboJXL v1.2.10 (2026-09-03/04) against libjxl 0.12.0.
  • Our effort N against cjxl and djxl at effort N, both in-process, one thread each. 676 tiles of the Bitruvius labeled corpus from 256² to 2048², every one round-tripped lossless. Headlines are geometric means over 256² to 1024².
  • Decode is reported two ways and never merged: each library reading the file it wrote, and libjxl reading our file. Size is ours over cjxl's at the same effort; below 1.0 is a smaller file.
  • The live race decodes in your browser against libjxl's published WebAssembly build, so its numbers are yours, not ours.

Bring five of your tile sets.

A pilot runs both libraries on your own imagery, on your own machine: same files in, same JPEG XL out. You keep the numbers.

See pricing

TurboJXL is licensed through the Bitruvius Developer Hub and ships as compiled binaries.

JPEG XL is an ISO/IEC standard (18181). libjxl, cjxl, and djxl are the reference implementation, published by the JPEG XL Project / Google under a BSD-3-Clause license. References here identify the products and formats being compared; no affiliation, sponsorship, or endorsement is implied. TurboJXL is an independent, memory-safe implementation that interoperates with the standard JPEG XL bitstream. The live demo runs the unmodified libjxl decoder under its BSD-3-Clause license. GDAL is an open-source project, named solely to identify the software compared; the elevation sample is USGS 3D Elevation Program data. Apple, Intel, Intel Core and Windows are trademarks of their respective owners and identify the test machines. Performance comparisons reflect our own measurements under the stated methodology; results vary by workload and hardware. Full third-party notices: Attributions.