I did an experment and modified a part of libjxl encoder and I would like to hear your feedback. My intention was to improve texture handling and chroma retention.
Links to comparisons are images are below. There are originals and encoded images size-matched to distance 2.0 at effort 3. Standard libjxl encodes at effort 3 and 7 are included as well. If you would like to see other distances (e.g. 1.0 or 3.0), let me know.
Hi r/jpegxl, I'm the developer of JXL Studio, a JPEG XL converter and viewer for Android. It runs libjxl on the phone, so nothing is uploaded.
What I think this sub will care about:
Space saver uses lossless JPEG recompression. After each conversion the app rebuilds the JPEG from the JXL and compares it with the original byte for byte. Only verified files are offered for moving to the trash. On my real camera photos it saves about 15–20%.
Get the original back: any recompressed file can be exported as the exact original JPEG, or as JPEG, PNG or WebP for apps that can't open JXL yet.
A real viewer for .jxl files: swipe, zoom, EXIF details, HDR and Ultra HDR gain maps.
New in 1.2, Wi-Fi access: open the phone's JXL library in a browser on your computer, download the JXL or the original JPEG, or drop photos from the computer to convert them on the phone. It pairs with a one-time code and stays on your local network.
Automate (Pro): watches a folder and converts new photos in the background, with a log of every run.
The free version converts up to 8 photos per batch. Pro is a one-time purchase.
I'd really like feedback from people who know the format: encoder defaults, files that fail, and anything you wish a JXL app on Android did. Happy to answer questions about how the verification works.
I have a few free Pro codes for people who'd like to try everything and tell me what works and what breaks. Comment or send me a message and I'll send you one. No review needed.
O Jake Archibald publicou esta semana uma comparação longa de JPEG XL e AVIF para uso na web, e hoje o Jyrki Alakuijala, co-designer do JPEG XL, escreveu no X uma posição diferente em parte. Rodei um teste pequeno antes de postar e o resultado ajuda a entender por que os dois podem ter razão.
Contexto
Segundo o caniuse, o JPEG XL tem suporte no Chrome 155+ e no Firefox 158+, e parcial no Safari. O artigo do Jake parte disso: "JPEG XL has now landed in all major browsers".
Ele comparou AVIF (avifenc 1.4.2), JPEG XL por dois encoders (libjxl 0.12.0 e o jixel 0.4.2, mais novo) e WebP, medindo qualidade com SSIMULACRA2 e ColorVideoVDP.
O que o Jake diz
Fotos com perda: o AVIF quase sempre gera arquivos menores. No esforço padrão, o melhor JPEG XL fica tipicamente 15% a 25% maior; no esforço máximo, 6% a 20%.
Screenshots, ilustrações e transparência: vantagem maior do AVIF. Sem perda: JPEG XL vence com folga.
Decode: JPEG XL leva 2 a 3x mais tempo em qualidade média, diferença que cai em qualidade muito alta.
JPEG existente: o JPEG XL reduz de 9% a 20% sem perda.
Recomendação dele: AVIF 4:4:4 como padrão, no maior esforço viável; olhar o JPEG XL via jixel quando o AVIF sair grande demais. E não usar 4:2:0 como padrão.
O que o Jyrki diz
"AVIF can work well at quality 60 and below." Acima de 85, que ele chama de qualidade mediana da internet, o JPEG XL "works far more reliably".
O que eu conferi
24 fotos do conjunto Kodak (768x512), q de 20 a 99, 1.539 encodes, esforço padrão: avifenc -s 6 -y 444, cjxl -e 7 e jixel com a velocidade padrão. Tamanho interpolado na mesma nota SSIMULACRA2, mediana por imagem; "melhor JPEG XL" é o menor entre libjxl e jixel.
SSIMULACRA2 50 a 85: o AVIF ganhou por pouco. O melhor JPEG XL saiu de 0,7% a 7,6% maior (mediana).
SSIMULACRA2 90, que o Jake classifica como visualmente sem perda: o JPEG XL ficou menor em 19 de 24 fotos, mediana de 10,6% a menos.
O mesmo q 80 deu nota mediana 84 no AVIF, 77 no libjxl e 64 no jixel. Em q 90: 89, 86 e 82.
JPEG q85 convertido para JPEG XL sem perda: mediana de 14,1% menor, e as 24 voltaram idênticas byte a byte (sha256).
O que isso é (e o que não é)
É um indício de que a resposta depende da qualidade-alvo, coerente com o que o próprio Jake mostra num exemplo ("JPEG XL catches up at very high quality").
Não é um contra-benchmark. Usei só o esforço padrão, fotos pequenas e antigas (scans de filme), uma métrica e uma máquina. No conjunto do Jake, com imagens de 1598 px, a diferença a favor do AVIF foi maior.
O "quality 85/90" do Jyrki é configuração de encoder, não nota SSIMULACRA2. Não dá para ligar uma coisa à outra direto, e o item do q 80 mostra o tamanho do problema.
Onde eu fico Para foto em qualidade média ou alta, o AVIF 4:4:4 continua sendo um padrão razoável. Se o site serve imagem muito perto do original, ou se o acervo já é JPEG, vale medir JPEG XL. Em qualquer caso, a comparação tem que ser na mesma nota de uma métrica, não no mesmo número de quality.
On 8 October they release Microsoft Edge 155, based on Chromium v155 which came with JPEG XL support. Microsoft even mentioned JPEG XL support in the release notes for Edge 155.
It didn't appear to work, however. It appeared the support wasn't (entirely) there because on tests such as this one it showed that Edge 155 didn't support it. Brave browser, which also follows Chromium closely, updated to Chromium v155 a day later and that introduced JPEG XL support, without even any mention in their release notes.
Is the code there in the browser but just disabled behind a flag? Are they waiting on Webview2 to also support JPEG XL decoding across all Windows apps? The release notes for Webview2 Runtime 155.0.4283.45 do not mention JPEG XL support, even though presumably it is based on Chromium 155. Does Webview2 v155 already support JXL?
The next Edge update is scheduled for around 22 October. Any chance it might be enabled then?
I have recently a comment that indicated that some commit(s) in libjxl history had changed its behavior and affected retention of detail in textured areas. So I decided to investigate and to evaluate how several metrics (SSIMULACRA2, Butteraugli, CVVDP) evolved in time.
Also, there seem several interesting libjxl forks which tried to address the impact of changes since libjxl 0.8 release. Notably, https://github.com/jonnyawsom3/libjxl/tree/v0.8RevertAttempt seems to provide a nice increase in image quality metrics.
Some of the commits changing libjxl behavior introduced changes in an edge-preserving filter, so I tested also with EPF and Gaborish filters disabled to evaluate their impact. Interestingly, disabling them results in a noticeable gain in measured image quality in some cases. It seems that the high content of high frequency details is the main factor which decides whether turning them of is beneficial or not.
I like the windows fast image browser and editor called "fast stone" and i use it a lot for correcting dslr photos, i tried linux lately and really missed that program - so i created a similar s/w for linux called slow rock.
It is GPU acceleration optimized too and of course supports raw and jpeg-xl too
I would be happy to hear some feed back and ready to add more features upon requests.
I was looking into a comparison of several encoder and setting and checking how they compare in image quality. One of the interesting parts is how they handle areas with fine textures. I was interested in how you would rate them relative to each other if you don't see the reference uncompressed image.
Columns correspond to encoders that I used. All images come from a mirrorless camera, they were exported from RAW to PNG, then downsized to 50% of linear dimension and then compressed to 0.6 bpp. At the end, I cropped selected parts and put them into one image.
I will reveal what encoders I used later, but there definitely is libjxl in the comparison. Feel free to share your guesses which encoders were used!
Are there any tunes or presets for encoding JPEG XL that would change what the encoder optimizes for? As I understand it, current encoder in libjxl optimizes for Butteraugli distance but is there any encoder or setting that would provide a different target?
I am the creator of KeyPDF it is browser based local pdf editor that I was making from scratch since 2025 and I was always open for new standards so when pdf association announced JPEG XL support I tried my best to bring it so now KeyPDF can both read and and write JPEG XL in pdf files we also conducted a test on 1000 files and brotli with JPEG XL helped reducing total size by 32% so I believe JPEG XL can become a golden standard for PDF in future and KeyPDF adopts it now.
Note: KeyPDF will give an option for losless convertion in case if you want your pdf to be universally accepted otherwise very few viewers be able to parse it.
Small update for the JPEG XL folks. You can now upload .jxl files straight into the media library on EmDash CMS.
For those who haven't come across it, EmDash is an open source CMS for Astro, from Cloudflare. Think of it as a spiritual successor to WordPress, but fully TypeScript and full-stack. Content types are defined in the admin and typed from your schema, plugins run in isolated sandboxes, and every site ships with an MCP server so you can manage content with an AI agent if you want.
If you're on Astro and want to serve JPEG XL, worth a try. There's a playground at try.emdashcms.com or run on your machine that use SQLite database by default if you just want to poke around.
Just did a huge 3.0.0 update on my 3D media player, Rendepth. One of the major new features is native import and export for modern image file formats: JXL, AVIF, and WebP. The app is mainly used to convert regular photos into stereoscopic 3D (with a wide range of hardware support) but the 3D features can also simply be disabled and the app will function like a standard desktop media player. There are some advanced features, like Blu-ray 3D disc playback, that require a Pro License, but almost all the photo/image features are available on the base free version, and you can also play 2D video files for free as well. Note that I've integrated the JPEG-XL libraries natively, so this does not depend on the OS having JXL support on the system. Cross-platform and works on macOS, Linux, and Windows. Hope y'all enjoy. https://rendepth.com
I maintain jxl-photo, a toolset built around one idea: JPEG XL can replace TIFF as an archival format for photography. My archive is 16-bit ProPhoto TIFFs out of Nikon bodies (Z8,Z7, Zf, D810, D700, D200), stored as lossy JXL at d=0.05–0.10 — which on my files survives aggressive editing (Shadows +100, Blacks +100 in Capture One) and pixel-peeping. The toolkit is built for Capture One / Lightroom export folders and keeps EXIF and ICC intact (the lossy encoder works in XYB, so the ICC rides along in metadata).
When storage runs short — or when I want to hand copies to friends on CD/DVD/pendrive — the move is to recompress an existing JXL smaller (d=0.05 → 1.0) instead of re-exporting from RAW. So the toolkit got a recompressor, and the recompressor got policies: it reads the encode parameters recorded in each file (now an append-only gen=N lineage chain) and refuses, copies, or asks before paying a lossy generation for nothing.
Then I wondered: How bad is JXL generation loss, actually? I assumed this would take an afternoon. It took two weekends and thousands of encodes.
The short answer for my own use case was reassuring: drift at the archival distances is controlled (2.65 dB over 17 re-saves at d=0.1, 5.5 dB over 500), and a one-shot recompression from an archived d=0.1 to d=1.0 costs 0.02 dB versus encoding d=1.0 from the start. The answer for re-encoding again and again was not reassuring at all, and that's the rest of this post.
This is the third post in this series. The first mapped per-pixel SNR across distances; the second compared 16-bit vs 8-bit vs JPEG. Both measured direct encodes - check them out if you're curious. The first link explains where the d=0.05 choice came from.
Setup:
One 44 MP photo as a 16-bit ProPhoto TIFF master, plus 8-bit and JPEG-q95 derivatives and a 5-photo sRGB batch for reproduction (and an extra 24 MP photo used for the close-up visual below). cjxl/djxl v0.12.0 (build 4128790), effort 7, output verified bit-identical across thread counts. PSNR + butteraugli (intensity target 255, matching the encoder's API path — the CLI binary defaults to 80) + SSIMULACRA2 on every point, all calibrated against direct encodes of the same master.
Results:
1. Chains lose badly at the same file size
Black = a single direct TIFF→JXL encode at each distance. Each colored line is a chain of lossy re-encodes, every step shrinking the file by a fixed percentage, ending at the same byte budget as a direct d=1.0 (dotted line). Same file size, up to 9 dB worse.
There's a feedback loop driving this. A lossy re-encode decodes to pixels slightly smoother than the source (decoder-side EPF/gaborish), so the next encode needs fewer bits for them. However, the accumulated noise is expensive to code, so hitting a shrinking target forces the distance up, which adds noise, and so on. In measured values: the −10%/step chain needs nominal d=1.48 to reach the file size a direct encode reaches at d=1.0.
2. Isolating the generation count
Same final file size (5.17 MB), reached in N generations with equal log-size steps. N=1 lands at 43.14 dB (direct d=1.0 encode) N=19 lands at 34.05 dB. Cost per extra generation: 0.2–0.6 dB, rising on average.
This is the cleanest experiment in the study: only the generation count varies, with a fixed endpoint. Marginal cost per generation runs 0.31, 0.55, 0.18, 0.41, 0.63, 0.58 dB. It rises on average but with dips, so the result is the 0.2–0.6 band, not a monotone progression.
Note the left end of that curve, because most people will not do a lot of lossy chains anyway: going to the final size in two generations instead of one costs 0.31 dB (with the intermediate at ~14 MB — the equal-log-step version of "one extra save"). Starting from an actual archived copy is cheaper still: recompressing a d=0.1 file (27.7 MB) to this same 5.2 MB in one shot costs 0.02 dB. The damage is in iterating, not in recompressing.
500 re-encodes at a fixed distance (center crop, log x-axis; dynamics validated against the full frame). File size equilibrates by ~gen 20; quality never does — the loss rate decays ~15× and stays positive (0.011–0.018 dB/gen even between generations 400 and 500). The worst long-run case is a mid-range distance: d=0.5 falls below the d=1.0 curve at gen ~88 and ends 5.7 dB below it.
Total loss over 500 generations is U-shaped in distance: d=0.1 loses 5.5 dB, d=0.3 loses 23.1, d=0.5 loses 24.9, d=1.0 loses 15.4, d=2.0 loses 13.9.
The mechanism ties to the size drift. At d≤0.5 the file size barely moves across generations — that's the absence of contraction: the encode<->decode operator isn't pulling the image anywhere, so it drifts freely. At d≥0.7 the shrink (−10% over 17 generations) is the operator contracting toward its fixed point, which bounds the damage. Turning off the decoder's smoothing confirms it: with --epf=0 or --gaborish=0 the 17-generation shrink at d=1.0 drops from −10.5% to −3.7% / −3.3%. Very low distances (0.1) escape for the opposite reason — each encode barely touches the pixels.
Worth noting: for every lossy distance, the image always degrade with more generations. Across 3 masters × 20 distances × 5 photos, no generation is bit-identical to the previous one — not even at d=0.05, where the PSNR drift is 0.001 dB/gen.
4. Seeing the damage in actual photos (direct conversion vs 17-gen chain)
A different camera and sensor (Nikon Zf, 24 MP) run through the same experiment, shown at 200%. Top: one direct encode at the same byte size (d=1.05, 4.55 MB, 36.3 dB). Bottom: the final frame of a 17-generation −10%/step chain (4.56 MB, 26.6 dB) — nominal d=2.1, measured effective d=8.9 by butteraugli (6.2 by SSIMULACRA2). The crop is the worst-damage window, picked automatically; local PSNR there is 31.2 vs 20.3 dB. The small leaves at the back — slightly out of focus in the original and direct encode images — come back with fake sharpening and false colors: accumulated error lives in the high frequencies.
You can see the gap, not just measure it. The small leaves at the back are slight out of focus and soft in the direct encode (and in the original TIFF also), but in the chain frame they come back with fake sharpening and false colors, and the smooth areas fill with colored speckle.
Quite ugly artifacts, but keep in mind this is 17 reconversions reducing the file size 10% in each - quite an extreme scenario.
Something interesting to note: To get the same file size as a direct conversion at d=1.05, we need to choose a distance of d=2.1, and get in the final an effective distance of d=8.9 (butteraugli) or d=6.2 (SIMMULACRA2) - depends on how you measure it. A nominal distance of 2.1 is needed here, instead of 1.05 (as the direct conversion), because the chain introduces a lot of noise that requires a higher distance to compress to the same file size.
5. PSNR underestimates how bad the compression artifacts look
The same gen-17 files, scored by three metrics. 17 re-encodes at d=1.0 look like a direct d=1.7 encode by PSNR — but d≈5.7 by butteraugli (and d≈3.75 by SSIMULACRA2). The metrics agree at small damage and diverge systematically as it accumulates.
Chains accumulate fine, uniform, high-frequency noise: low energy and evenly spread, so it's nearly free in MSE, but it's exactly what perceptual metrics are built to catch.
Caveats, stated up front: butteraugli and SSIMULACRA2 values of 7–15 are far outside their calibrated range (~1–3 ≈ JND), and the SSIMULACRA2 scale is content-dependent (this grainy 44 MP master scores ~89 even at a direct d=0.05, verified across four independent color pipelines). Both are used strictly as ordinal comparisons against direct encodes of the same master.
What this means if you archive in JXL
Archiving at low distance is safe. At d=0.05–0.1 the drift per re-save is controlled, and even 500 re-saves cost ~5.5 dB. Replacing TIFF with JXL does not quietly rot your archive.
Recompress directly from the highest-quality copy you have, in one shot, to the final distance. The measured cost of that one recompression is 0.02 dB (from an archived d=0.1); the detours are what cost 9.
If a file must be re-saved repeatedly at a fixed distance, use the lowest distance you can afford. d≈0.5 is the measured worst case for long-run drift — worse than distances that start well below it.
Never walk an archive's file sizes down in steps. The −10%/step chain needs 19 generations to do what one direct encode does far better.
JXL-PHOTO: record encode parameters in metadata, and refuse or at least surface recompressions that are counterproductive. That's what the recompressor in jxl-photo does — I just hadn't measured why until now.
Notes
The test harness and the full dataset aren't public yet — the repo is still a pile of one-off scripts from a side project that got out of hand, and I want to refactor it before putting it up.
I started this expecting to finish this project in one day. I spent 2 weekends on it, and the files are still way to messy to publish. I'll publish it on GitHub when i organize all the files, maybe also alongside the full tables, the JPEG-vs-JXL comparison at matched byte budgets, per-photo batch statistics and all the charts (i did those tests too, but it is still not organized enough for sharing with others).
TL;DR. Re-encoding a lossy JPEG XL repeatedly is far worse than one direct encode at the final setting, and PSNR badly understates the damage. At an identical byte budget, each extra generation costs 0.2–0.6 dB beyond what the size reduction alone costs — 19 small-step generations land ~9 dB below a single direct encode. Re-encoding at a fixed distance stabilizes file size within ~20 generations but quality never converges, and the worst long-run case is a mid-range distance like d=0.5, not a high one.