Upcoming Switch to JXL format for Default Wallpaper

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.

2 Likes

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.

2 Likes

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.

1 Like

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:

  1. 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.
  2. 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 the f42-01-day.jxl source image is 2 MB in size, and on my system takes 2.15s to decode to JPG or 2.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 in 0.82s or to PNG in 1.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.

3 Likes

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 the vnc-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 artwork
  • f42-NIGHT.jpg, a lossy JPEG (but quality=95, so minimal degredation) also exported from Krita
  • f42-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.

1 Like

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 :slight_smile:
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.

1 Like

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!

I just said it on the old Gnome issue but I believe this may be the cause of the performance issues.

Faster decoding is still a good idea, but when the multithreading is fixed it should easily match or beat PNG.

2 Likes