The change says: "Fedora’s default wallpaper long relied on PNG to preserve design integrity, but its large file size increased packaging and animation costs. The Design team adopted JXL, slashing file sizes without compromising quality. " To me, I automatically thought this meant that the images would be JXL lossless, but when I checked on my F42 system using jxlinfo, the images were all lossy? Is this working as intended? I suppose lossy is good enough, but if you were wanting to get the same quality as PNG, wouldn’t you use lossless?
It appears we used JXL lossy to work around some issues. I think @ferdnyc is tracking when we can use lossless JXL again.
Depends on the amount of compression.
A good algorithm will take into account what picture data can be removed without the human brain noticing.
That is what JPEG and MPEG do as well.
This is really confusing, but while Fedora 42 has indeed just switched to JXL, GNOME 48 has also just switched from JXL to standard JPEG due to performance issues with JXL. Yes, in the same release. So don’t expect consistency here.
Is there a performance issue? I had read the links that @ngompa so kindly provided and I just noticed the issue with the alpha channel regarding the animated transition blends (due to an outstanding GNOME bug) - and it looks like there is now a workaround for that
The performance issue is documented here. I haven’t been following it closely.
I think (though I don’t claim to be a JPEG-XL expert myself) there may be some confusion here about the nature of JXL lossless encoding. Many things about JPEG-XL are, in my opinion, confusing, including how they define “lossless”.
When a file is encoded to JPEG-XL, a --quality
level can be applied to that encoding process, and at --quality 100
(using the cjxl
reference encoder) the encoding becomes “lossless”. There are two paths the process can take from there:
- If the source image is a JPEG file (an inherently lossy format), a special transcoding algorithm is activated that embeds what they call “JPEG bitstream reconstruction data” into the JXL. This allows very fast and completely lossless output of the original JPEG file — which means, of course, that it comes out at its original (lossy) quality level. On my system, JXL->JPEG decoding when bitstream reconstruction data is available takes
0.62s
for desktop-backgrounds-sized images. - If the file is, say, a PNG file (an inherently lossLESS format), regardless of the quality level there will be no JPEG bitstream reconstruction data (how could there be?) which means decoding will be much slower. The speed is inversely proportional to the quality level selected, and the file size is directly proportional.
Using--quality 100
especially results in very large JXL files, which take a very long time to decode. The--quality 100
JXL version of thef42-01-day.jxl
source image is 2 MB in size, and on my system takes2.15s
to decode to JPG or2.50s
to decode to PNG. The packaged, lossy version (despite having no visible difference from the source image) is only 1.1 MB in size, and decodes to JPG in0.82s
or to PNG in1.33s
.
JPEG-XL’s support for lossless transcoding is impressive, but requires the input data to be a lossy JPEG image. When using non-JPEG input data, lossless JXL imposes some serious penalties: Much bigger files and slower decoding. That’s a crappy combo.
The actual reason the F42 background files aren’t lossless is a pretty dumb one: we forgot to add -quality 100
to an ImageMagick command used in the package-build Makefile
. But based on the numbers above, that may have been a happy accident. As it turns out, by going technically-lossy we saved 50% on the file size and sped up decoding quite a bit. Without impacting image quality, at least not that anyone noticed (including the creator of the images).
I’m not up to speed on the performance issues that led GNOME away from JXL, but if they were using “lossless” JXL that may have had something to do with it.
Thanks for the link! If I’m reading this correctly it appears the issue has been resolved:
" There was a difference between libjxl loading
adwaita-l.jxl
and loading thevnc-l.png
but it was in the realm of statistical noise.Login time is now around 5 seconds. I think we can do better but JXL is no longer the top culprit."
Well, with a lossy file, you are starting with a smaller file, so it would make sense if you start with a truly lossless file you are going to have a bigger result. I guess I was confused by “Fedora’s default wallpaper long relied on PNG to preserve design integrity”. Were we shipping lossy jpg of original PNG or were we shipping PNG? If we were shipping the actual PNG, then I would think we would ship the lossless jxl of the png version going forward; or change the wording of the proposal so we aren’t being misleading.
That… doesn’t entirely follow; for the purposes of the discussion, the starting point for an encoding is the uncompressed image data, which is always the same size.
When you start with a lossy format, you possibly get more compressible data as input. But you end up with the same number of raw pixels, each of which is stored in N bytes of memory. (3, typically, for 8bpp RGB. 4 if there’s an alpha channel.)
Nothing I wrote is related to any proposals, but in terms of the past Fedora has been shipping PNG background images. It was suggested we switch to JXL which allows for smaller files, and I think one thing that wasn’t considered in the adoption of that suggestion was performance relative to size.
So I whipped up some benchmarks. I’ll provide the source at the end. The files being tested are:
f42-NIGHT.png
, a lossless PNG export created directly from the original Krita source artworkf42-NIGHT.jpg
, a lossy JPEG (but quality=95, so minimal degredation) also exported from Kritaf42-NIGHT_frompng_q100.jxl
, a lossless JPEG-XL encoding of the PNG file. This represents the… I’ll say “originally intended” version of the background.f42-NIGHT_frompng_q90.jxl
, a lossy (--quality 90
) JPEG-XL encoding of the PNG file. This represents the currently-shipped version of the background.f42-NIGHT_fromjpg.jxl
, a losslessly-transcoded JPEG-XL version of the JPEG, just to include JXL’s bitstream reconstruction mode in the testing
The code uses typical Python timeit.repeat
benchmarking, timing 10 repetitions of loading the source file into a GdkPixbuf.Pixbuf
, then taking the fastest of 3 runs. Here’s how that plays out on my system:
$ ./jxl_benchies.py
1.53 - f42-NIGHT.png (3.8 MB; rgb, 8bpp, 34.9 MB uncompressed)
2.47 - f42-NIGHT_frompng_q90.jxl (985.6 KB; rgb, 8bpp, 34.9 MB uncompressed)
5.44 - f42-NIGHT_frompng_q100.jxl (1.9 MB; rgb, 8bpp, 34.9 MB uncompressed)
1.52 - f42-NIGHT.jpg (1.4 MB; rgb, 8bpp, 34.9 MB uncompressed)
1.22 - f42-NIGHT_fromjpg.jxl (1.2 MB; rgb, 8bpp, 34.9 MB uncompressed)
JPEG and PNG have almost identical loading performance, despite their size differences. Since the PNG is lossless, it’s a no-brainer to prefer that format for any distributed images.
JPEG-XL from the JPEG original is both smaller and faster to load, making it a no-brainer for converting JPEG source images to JXL for savings on both axes.
But JPEG-XL from the PNG source (so, without the bitstream reconstruction), while saving some disk space, performs much worse than any of the alternatives. And if its lossless mode is used, MUCH worse. It takes over half a second for my machine to load f42-NIGHT_frompng_q100.jxl
into a GdkPixbuf.Pixbuf
, more than 3× as long as it takes to load the PNG or JPG versions! That’s an awfully stiff performance penalty to pay just for a 50% smaller file.
The _q90
version of the JXL (as I said, roughly the one we’re shipping), OTOH, saves us 75% of the file size, but only requires a small additional loading time, making it a pretty good balance. Differences from the PNG image (as determined by overlaying one on the other in GIMP, and setting the upper layer to “Subtract” compositing mode) are minimal. I’ll include the resulting image, it’s mostly black (no differences) with some faint differences at feature outlines.
But if the requirement to use a lossless format is absolute, then I’d argue that switching to JXL is insane; the performance hit is way, way too severe for a 50% filesize reduction.
The code:
#!/bin/env python3
from os import stat
from timeit import repeat
from fs import filesize
import gi
gi.require_version('GdkPixbuf', '2.0')
from gi.repository import GdkPixbuf
def load_img(filename):
img = GdkPixbuf.Pixbuf.new_from_file(filename)
return img
def img_info(pixbuf):
cs = pixbuf.get_colorspace().value_nick
a = 'a' if pixbuf.get_has_alpha() else ''
bpp = pixbuf.get_bits_per_sample()
blen = pixbuf.get_byte_length()
fsize = filesize.traditional(blen)
return f'{cs}{a}, {bpp}bpp, {fsize} uncompressed'
def run_benchies():
FILENAMES = [
'f42-NIGHT.png',
'f42-NIGHT_frompng_q90.jxl',
'f42-NIGHT_frompng_q100.jxl',
'f42-NIGHT.jpg',
'f42-NIGHT_fromjpg.jxl',
]
for f in FILENAMES:
fsize = filesize.traditional(stat(f).st_size)
buf= load_img(f)
finf = img_info(buf)
del buf
times = repeat(
f'imgdata = load_img("{f}"); imgbytes = imgdata.get_pixels()',
number=10,
repeat=3,
globals=globals(),
)
print(f'{min(times):6.2f} - {f} ({fsize}; {finf})')
if __name__ == '__main__':
run_benchies()
The “image diff” of f42-NIGHT_frompng_q90.jxl
vs. the original f42-NIGHT.png
:
That’s a pretty serious loss of detail and not something you would want for a default wallpaper.
Without agreeing or disagreeing with your assessment.. i will say this…
I’m gonna save that image diff and use it as an alternative dark mode wallpaper.
Ah, I think we might be talking about slightly different things! You’re right that both formats output the same pixel dimensions—but my point was really about the data fidelity and how that affects file size.
A lossy JPEG is smaller because it discards data, so when you recompress it losslessly (even with something efficient like JXL), you’re still working from a reduced-quality source. That’s why the result is smaller than a true lossless encode—not because of resolution, but because there’s simply less original detail left to preserve.
The pixel count stays the same, sure, but the actual pixel data is different (lossy vs. lossless), which is what impacts file size.
Thanks for running these benchmarks—this is really useful data! I see the trade-offs much more clearly now. A few thoughts:
Lossy JXL (q90) seems like a reasonable middle ground
The 75% size reduction with minimal visual difference and only a slight performance hit makes a strong case for this as a default, especially if storage/bandwidth is a priority.
Lossless JXL’s performance penalty is concerning
If Fedora’s policy mandates true lossless (not just “visually lossless”), then the current PNG might still be the better choice despite its larger size, since the JXL decode time is unexpectedly high.
The JPEG-origin case is interesting but likely irrelevant
While f42-NIGHT_fromjpg.jxl is fast and small, it’s still bound by the original JPEG’s lossy artifacts. If design integrity is a hard requirement, this probably isn’t an option.
Proposal:
Could we clarify Fedora’s actual needs? For example:
Is this strictly about storage efficiency, or is pixel-perfect preservation non-negotiable?
If minor quality loss is acceptable, q90 JXL seems ideal.
If not, we might need to stick with PNG (or push for JXL decoder optimizations).
Your benchmarks highlight that there’s no free lunch—just trade-offs. Maybe the change proposal wording should reflect that?
Edit: Also would be worthwhile to read what @veluca posted. You can still get size reductions and respectable decode times (at least on my machine) by using --effort 1 to 3.
Hi all,
JPEG XL dev here
For lossless mode, a recent PR (Rebalanced Faster Decoding and fixed Progressive for Lossless by jonnyawsom3 · Pull Request #4201 · libjxl/libjxl · GitHub) has significantly improved the --faster_decoding
encoder flag (no decoder changes needed), and that might provide a meaningful speedup for decoding.
Relatedly, it might be worth trying out encoding images with lower efforts – efforts 1 to 3 are still pretty effective and should be significantly faster to decode than the default.
Thanks for the info. Hopefully that will improve the situation. I believe the original intent was to convert the lossless png to lossless jxl, so hopefully that will fit the bill!