Repository navigation
Benchmarks
PJA 0.9.6 against other lossless JPEG compressors, file by file, and inside archivers against an archiver that recompresses JPEG itself.
One file at a time: packJPG 5.0g and
JPEG XL's lossless transcoding of a JPEG (cjxl/djxl 0.11.2), which both
give the JPEG back byte for byte too, Lepton and brunsli, against pja
0.9.6. Measured on 07/11/2026 (how): each set's whole output against its input, every
file decompressed and compared with the original; a file a tool does not
take, or that does not come back the same, counts at its own size.
| set | files | size | packJPG 5.0g | JPEG XL | Lepton | brunsli | PJA 0.9.6 |
|---|---|---|---|---|---|---|---|
| photos (Huffman JPEG) | 151 | 78.4 MB | 76.62% | 79.80% | 79.42% (takes 136) | 77.98% | 72.60% |
| corpus of JPEGs of every kind | 78 | 33.0 MB | 77.23% (takes 65) | 86.03% (takes 56) | 85.11% (takes 55) | 75.36% (takes 64) | 70.39% |
| MPO (stereo pairs) | 6 | 41.0 MB | 86.54% | takes none | 87.65% | 87.71% | 73.38% |
| arithmetic-coded JPEG | 244 | 6.7 MB | 78.52% | takes none | takes none | takes none | 74.59% |
| generated JPEGs | 640 | 18.3 MB | 77.48% | 85.35% (takes 528) | 83.95% (takes 511) | 79.69% (takes 604) | 73.72% |
| 12-bit JPEG, small | 10 | 83 KB | takes none | takes none | takes none | takes none | 88.14% |
| 12-bit JPEG from camera RAWs | 12 | 27.7 MB | takes none | takes none | takes none | takes none | 84.80% |
| lossless JPEG | 36 | 390 KB | takes none | takes none | takes none | takes none | 96.04% |
And how fast, on the 151 photos (two Xeon E5-2697A v4, 64 threads, with
other work running). "8 at once": pja -th 8; the others, 8 processes of
one file each, best of three runs for one thread (Lepton and brunsli:
05/11/2026's runs). PJA, packJPG and Lepton verify what they write; cjxl
and cbrunsli do not. PJA also flushes each file it writes to the disk
before giving it its name (since 0.9.5: a power cut leaves the old file
or the whole new one); --no-sync skips that, about 12% off its
one-thread extraction here. Since 0.9.7 the flush runs beside the next
file's work, which takes back most of it (x -th 1 of these photos
40.7 -> 36.2 s). Lepton (Dropbox, commit f34c7f4,
-singlethread -allowprogressive) and brunsli (Google, commit c18b962)
were built from their repositories.
| packJPG 5.0g | JPEG XL | Lepton | brunsli | PJA 0.9.6 | |
|---|---|---|---|---|---|
| compress, 1 thread | 68.1 s | 26.7 s | 40.8 s | 13.3 s | 68.3 s |
| decompress, 1 thread | 34.9 s | 10.7 s | 15.3 s | 9.0 s | 37.0 s |
| compress, 8 at once | 10.3 s | 4.1 s | 5.8 s | 1.9 s | 9.0 s |
| decompress, 8 at once | 5.2 s | 1.5 s | 2.2 s | 1.4 s | 5.1 s |
PJA is the smallest on every set and the only one that takes every file. On photos it is 5.5% smaller than packJPG, as fast on one thread and 13% faster to compress at 8 at once; 7.4% smaller than brunsli, the fastest (3.6 to 5.1 times PJA's speed); 9.4% smaller than Lepton (1.6 to 2.4 times as fast, and it leaves 15 of the 151 photos as they are); 9.9% smaller than JPEG XL (2.2 to 3.5 times as fast). None of the four takes 12-bit or lossless JPEG, and only packJPG takes arithmetic-coded ones.
One archive per set, 8 threads, on 07/11/2026; every file extracted and compared, and every one came back. Times are to write the archive / to extract it.
-
7-Zip + PJA: 7-Zip 25.01 with PJA 0.9.6's plugin,
7z a -mmt=8 -m0=PJA -m1=LZMA2(Library and 7-Zip). -
zpaq-std + PJA: zpaq-std
2.6.0 with the PJA 0.9.6 plugin it ships,
zpaq-std a set.zpaq FILES -sa -t8(stream version 7, the command's model; JPEG 2000 throughpja2k); it keeps the files a codec takes in one block, and its z codecs put the PJA streams of a block's files through an LZ77 that reaches back into the files before. -
Aqueous: Aqueous (
aqr, commit30f9a4e),aqr -c -T 8 -l high. - For reference, 7-Zip (
7z a -mmt=8, LZMA2) and zpaq-std (-t8, its default method) without PJA.
| set | 7-Zip + PJA | zpaq-std + PJA | Aqueous | 7-Zip | zpaq-std |
|---|---|---|---|---|---|
| photos (151) | 72.57%, 9.3 / 4.7 s | 72.87%, 16.3 / 5.5 s | 80.46%, 2.3 / 0.7 s | 97.04%, 6.9 / 1.1 s | 97.84%, 0.6 / 0.4 s |
| corpus (78) | 66.86%, 6.1 / 2.9 s | 68.36%, 12.9 / 2.7 s | 71.98%, 1.5 / 0.4 s | 82.24%, 2.7 / 0.5 s | 85.28%, 0.4 / 0.3 s |
| MPO (6) | 73.39%, 4.6 / 2.3 s | 73.75%, 13.8 / 3.9 s | 78.95%, 1.4 / 0.4 s | 99.57%, 3.6 / 0.8 s | 100.04%, 0.3 / 0.2 s |
| arithmetic-coded (244) | 17.25%, 1.7 / 0.8 s | 18.10%, 5.0 / 0.9 s | 85.74%, 0.2 / 0.0 s | 74.49%, 0.6 / 0.1 s | 75.43%, 0.1 / 0.1 s |
| generated (640) | 16.40%, 2.7 / 1.4 s | 16.94%, 5.8 / 1.6 s | 33.97%, 0.7 / 0.3 s | 62.85%, 1.3 / 0.4 s | 67.22%, 0.4 / 0.3 s |
| 12-bit, small (10) | 58.28%, 0.2 / 0.2 s | 95.81%, 0.2 / 0.0 s | 92.68%, 0.0 / 0.0 s | 90.38%, 0.0 / 0.0 s | 95.55%, 0.0 / 0.0 s |
| 12-bit from RAWs (12) | 84.80%, 3.6 / 2.1 s | 84.98%, 11.5 / 2.9 s | 100.00%, 0.2 / 0.0 s | 100.00%, 2.3 / 0.1 s | 100.07%, 0.3 / 0.2 s |
| lossless JPEG (36) | 27.37%, 0.2 / 0.2 s | 25.08%, 0.3 / 0.2 s | 96.59%, 0.0 / 0.0 s | 95.64%, 0.1 / 0.0 s | 97.82%, 0.0 / 0.0 s |
| JPEG 2000 (113 codestreams, 150 MB) | 99.82%, 12.4 / 0.7 s | 92.51%, 126.3 / 94.5 s | 100.00%, 1.2 / 0.1 s | 99.82%, 12.7 / 0.5 s | 99.97%, 0.6 / 0.5 s |
7-Zip + PJA is smaller than Aqueous on every JPEG set, and the smallest of
all on every one but the lossless one; zpaq-std + PJA is smaller than Aqueous
on all but the small 12-bit one, and the only archiver here that takes
JPEG 2000: pja2k saves 7.5% on 113 real codestreams (the pja command
alone, with its pixel modes, 8.4%; 7-Zip's plugin does not take JPEG 2000
yet), at about 1.2 to 1.6 MB/s on 8 threads. The small 12-bit set is 10 small files (8 made with
cjpeg, 2 from the sample corpus), 83 KB in all: each zpaq-std block
carries pjax78's ZPAQL decoder (33 KB), more than PJA saves on so few
bytes, so zpaq-std rightly stores them its usual way (and two are
arithmetic-coded 12-bit, which no plugin codec takes). 12-bit files of
real size pay the decoder back: on 12 made from camera RAWs (NEF and DNG
from the sample corpus, developed to 16 bits, about 2 to 5 megapixels,
cjpeg 3.0.4 -precision 12 -quality 90, baseline, optimized and
progressive), 350 KB to 4 MB each, zpaq-std + PJA is 0.18 points behind
7-Zip + PJA, and nothing else takes them. On the arithmetic-coded and
generated sets,
whose files are variants of a few images, both keep each image about
once: 16 to 18% of the input, where Aqueous writes 34 to 86%. zpaq-std +
PJA writes stream version 7, as the command does, and keeps a codec's
files in one block: on JPEG it is 0.2 to 1.5 points behind 7-Zip + PJA,
1.8 to 3.2 times slower to write and up to 1.7 times slower to extract; what it spends
buys an archive any zpaq can extract without the plugin, through the
ZPAQL decoder each block carries. Aqueous is 3 to 18 times faster than
7-Zip + PJA to write.
No other recompressor takes JPEG 2000. On 113 real JPEG 2000 codestreams (150 MB from the Internet Archive, the Library of Congress, OSGeo, Helioviewer, Sentinel-2 and DICOM; one tar for the general-purpose compressors, every file compared after extraction), on 06/11/2026:
| size | saved | |
|---|---|---|
| bzip2 -9 | 100.18% | it grows |
| bzip3 -b 511 | 100.05% | it grows |
| zip -9 | 99.81% | 0.19% |
| gzip -9 | 99.80% | 0.20% |
| xz -9e | 99.78% | 0.22% |
| 7-Zip -mx=9 (LZMA2) | 99.76% | 0.24% |
| zstd --ultra -22 | 99.72% | 0.28% |
| brotli -q 11 -w 24 | 99.67% | 0.33% |
| zpaq -m5 | 99.56% | 0.44% (18 min to write) |
| PJA 0.9.6 | 91.58% | 8.42% |
By source, with PJA: Internet Archive book pages 17% (scanned 1-bit pages alone about 70%, the pixels through a bitonal model, better than JBIG1's 64.6%), OSGeo 9.8%, Library of Congress 9.4%, Helioviewer 6.9%, Sentinel-2 7%, DICOM 6.2%. The codec's notes (doc/CODEC.md, the JPEG 2000 stages) have the details, the per-encoder rules and the speeds.
- The sets. Photos: 151 camera JPEGs. Corpus: the 78 JPEGs of a public corpus of sample files, of every kind and encoder. MPO: 6 MPO files (stereo pairs). 244 arithmetic-coded JPEGs; 640 JPEGs made from 12 small images by several encoders and settings (sizes, subsampling, quality, progressive, restarts); 10 small 12-bit JPEGs (2 from the corpus, 8 made with libjpeg-turbo); 12 12-bit JPEGs of real size, made with libjpeg-turbo 3.0.4 from the corpus's camera RAWs (NEF, DNG) developed to 16 bits with LibRaw (rawpy); 36 synthetic lossless JPEGs.
- Every file back. Each tool's output was decompressed and compared byte for byte with the original: every file came back with every tool (a file a compressor did not take counts at its own size).
-
The commands. packJPG:
packJPG a -np -o -ver -th1(the-vercheck),packJPG x. JPEG XL:cjxl --lossless_jpeg=1 --num_threads=1,djxl --num_threads=1back to the JPEG. Lepton:lepton -singlethread -allowprogressiveboth ways (it verifies by default). brunsli:cbrunsli,dbrunsli. PJA:pja a -th N(it verifies by default),pja x -th N. Archivers: as listed above,x(oraqr -d -T 8 -o DIR) to extract, and every file compared. - Not compared: JPEG-LS, which none of the others takes (PJA: 84.4% on 16 JPEG-LS photos), and other formats.
The scripts are vs3.py and vs4.py (compressors) and arch10.sh (archivers) in the
author's lab; the codec's own measurements,
and everything tried along the way, are in
doc/CODEC.md.