1. 65

  2. 31

    For dithered PNG images, you can improve the compression a good bit if you use an ordered dither rather than an error diffusion dither like Floyd-Steinberg. This is because the ordered dither tends to create regions of repeating pixels that the dictionary compression within PNG can identify.

    For example, using this command:

    convert COMountain.jpg -colorspace RGB -filter box -resize 25% -ordered-dither o8x8,3 -colorspace sRGB -quality 96 png24:COMountainIM3.png

    gives this result at 44,369 bytes after lossless recompression with optipng and then zopfli.

    Or we can go a step down in the palette size with this command (note the 2 instead of 3 on o8x8,3):

    convert COMountain.jpg -colorspace RGB -filter box -resize 25% -ordered-dither o8x8,2 -colorspace sRGB -quality 96 png24:COMountainIM2.png

    which gives this result for 26,163 bytes after recompression.

    Other details:

    Resampling to downscale assumes linearized colors, and dithering really ought to be done in a linearized colorspace as well. Hence the change to linear RGB as the first step and then back to sRGB for display at the end. Otherwise, you end up with odd color shift after dithering (i.e., it blows out and tends to look too bright).

    Ideally you’d use something like sync filter for the resampling (or at least a cubic filter). But that has a slightly sharpening effect and can introduce ringing which negatively impacts compression. Using a box filter does not sharpen and leans towards smoothing so it gives a tiny boost to compression.

    For the lossless recompression with optipng and zopfli, I used optipng -o7 -zm1-9 image.png; advpng -z4 image.png for both.

    1. 3

      Wow! Thank you for the added information. I was able to reproduce this, and am pushing an update to the post with images produced from these commands, with a big ol’ quote block including this comment.

      1. 11

        You’re very welcome.

        And for what it’s worth, while the dithering was a fun exercise, I agree with you that simply using a better format is the way to go. In my experiments in lossy compression, I haven’t been all that impressed with WebP. It’s usually better than classic JPG even from a clever encoder like mozjpeg, but I find that for equal size files both JPEG XL and AVIF give better results. For your image, with equal size encodings of around 61KB versus WebP, I find that JPEG XL keeps more of the texture of the trees on the left at the expense of ringing (i.e. mosquito artifacts) along the mountain edges, while AVIF has top-notch de-ringing at the expense of loss of texture through heavier smoothing. WebP has both heavier smoothing and all of the ringing. HEIF seems somehere between AVIF and JPEG XL, though closer to the AVIF end but with some odd smudging of edges.

        Overall, I’ve tended to like the look of AVIF a little better and quantitatively I’ve found it’s way ahead of everything else in terms of bit-rate vs. PSNR distortion on my test image corpus. (I haven’t extensively tested HEIF yet, however.)

        1. 7

          I don’t actually love WebP either. For broad categories of images (real life, anime, etc) I think my preference is HEIF or BPG - which is essentially the same thing. Unfortunately, support for HEIF is abysmal, requiring annoying to install packages from the Microsoft Store on Windows and the Linux decoders are rather slow, lacking acceleration (last I checked). My recommendation of WebP was more focused on the fact that it’s actually supported now, where as AVIF, JPEG XL, and HEIF all lack universal browser support.

          I dove down this rabbit hole pretty deep when looking to standardize all the images on my computer to one format instead of the mix of png, jpeg, etc. that I have now, so I was trying on my own mix of desktop backgrounds, screenshots, family pictures, and furry art- I ended up giving up due to the poor performance. This was a while ago though, so things may have changed

          1. 3

            Unfortunately, support for HEIF is abysmal, requiring annoying to install packages from the Microsoft Store on Windows and the Linux decoders are rather slow, lacking acceleration (last I checked).

            Yeah, this is still an issue. It takes like 2-3 seconds to decode pictures I took with my iPhone(!) Because I wrote the Imlib plugin myself I thought I must be doing something wrong, but after quite a bit of investigation it turned out … it’s just slow like that.

            WebP isn’t perfect, but overall it’s quite an improvement over the classic PNG/JPEG/GIF formats, with the biggest downside that support is still a bit iffy beyond “renders in a browser” (e.g. uploading a webp image on GitHub and many other platforms won’t work, although naming it img.web.png works on some platforms 🤷).

            1. 2

              AVIF, JPEG XL, and HEIF all lack universal browser support

              JXL was frozen, like, yesterday. (a few months ago or something.) Give it time! It’s already behind a flag in Firefox Nightly and Chromium. I even have a feeling that Apple might be more enthusiastic about it vs. AVIF due to it not being based on a free video codec that competes with their favorite patent-encumbered MPEG junk.

              AVIF is in such an awkward spot now… it was there earlier, it’s more “ready”, Mozilla slightly prefers it due to Rust implementations existing, but JPEG XL is now so clearly the one – being a purpose-built image format, it supports progressive loading and lots of other features.

            2. 5

              If JPEG XL actually delivers the ability to serve multiple screen sizes / resolutions from the same image as proposed in FUIF, instead of requiring storing many copies of the same image for responsive design, that would be a godsend and would immediately convert me to JPEG XL for graphics on websites. I don’t want the complication of generating, storing, and serving the right image to the right client, there should be one image and clients should only download the amount they want.

              I mean, in what world is HTML like this the right approach to responsive design?

              	srcset="/blog/2020/10/pictures/schuylkill_hu1966b96707887b7b85192359bda88cdb_1900414_576x0_resize_q75_box.jpg 576w,
              	/blog/2020/10/pictures/schuylkill_hu1966b96707887b7b85192359bda88cdb_1900414_720x0_resize_q75_box.jpg 720w,
              	/blog/2020/10/pictures/schuylkill_hu1966b96707887b7b85192359bda88cdb_1900414_900x0_resize_q75_box.jpg 900w,
              	/blog/2020/10/pictures/schuylkill_hu1966b96707887b7b85192359bda88cdb_1900414_1080x0_resize_q75_box.jpg 1080w,
              	/blog/2020/10/pictures/schuylkill_hu1966b96707887b7b85192359bda88cdb_1900414_1280x0_resize_q75_box.jpg 1280w,
              	/blog/2020/10/pictures/schuylkill_hu1966b96707887b7b85192359bda88cdb_1900414_1366x0_resize_q75_box.jpg 1366w,
              	/blog/2020/10/pictures/schuylkill_hu1966b96707887b7b85192359bda88cdb_1900414_1440x0_resize_q75_box.jpg 1440w,
              	/blog/2020/10/pictures/schuylkill_hu1966b96707887b7b85192359bda88cdb_1900414_1600x0_resize_q75_box.jpg 1600w,
              	/blog/2020/10/pictures/schuylkill_hu1966b96707887b7b85192359bda88cdb_1900414_1800x0_resize_q75_box.jpg 1800w,
              	/blog/2020/10/pictures/schuylkill_hu1966b96707887b7b85192359bda88cdb_1900414_1920x0_resize_q75_box.jpg 1920w,
              	/blog/2020/10/pictures/schuylkill_hu1966b96707887b7b85192359bda88cdb_1900414_2160x0_resize_q75_box.jpg 2160w,
              	/blog/2020/10/pictures/schuylkill_hu1966b96707887b7b85192359bda88cdb_1900414_2304x0_resize_q75_box.jpg 2304w,
              	/blog/2020/10/pictures/schuylkill_hu1966b96707887b7b85192359bda88cdb_1900414_2560x0_resize_q75_box.jpg 2560w,
              	/blog/2020/10/pictures/schuylkill_hu1966b96707887b7b85192359bda88cdb_1900414_2880x0_resize_q75_box.jpg 2880w,
              	/blog/2020/10/pictures/schuylkill_hu1966b96707887b7b85192359bda88cdb_1900414_3200x0_resize_q75_box.jpg 3200w,
              	/blog/2020/10/pictures/schuylkill_hu1966b96707887b7b85192359bda88cdb_1900414_3384x0_resize_q75_box.jpg 3384w,
              	/blog/2020/10/pictures/schuylkill_hu1966b96707887b7b85192359bda88cdb_1900414_3840x0_resize_q75_box.jpg 3840w"
              	alt="The Schuylkill River in winter. There&#39;s snow on the ground, the sun is setting, and the city lights are reflecting in the icy water. The river trail is visible through the trees on the left."
              1. 3

                in what world is HTML like this the right approach to responsive design?

                That’s a lot of steps.. I just use 1000, 2000 and 3000. Before that, I used to only generate a full-size-but-overcompressed (50% “quality” or so) one due to that post from 2012 :D

                1. 1

                  That is a good point, but there’s another side of it as well.

                  For …let’s say “smaller” websites and places where people code things by hand or some niches, this would be horrible. But I doubt that a blogger like our OP here would go in so many details - unless there’s a very specific need for this, why bother with so many?

                  And for bigger things, software like CMS (Like Wordpress, Publii, or any of the things we use in our apps), you’d have just one, biggest source file, and your dev pipeline would create all these formats and things itself. You’d rarely ever actually see this code.

            3. 1

              There’s also a cool online tool for ordered dithering here: https://seansleblanc.itch.io/ordered-dither-maker

            4. 7

              Awesome guide. I wish the article went into a bit more detail about the theory behind these optimizations (e.g. is there a theoretical lower bound on palette sizes, what knobs can I turn with dithering) or had some links, but otherwise great stuff.

              1. 6

                Thanks ^-^ I was actually originally planning to do that, but then when I realized that .webp is just so much better quality per bit, it no longer made sense to me to focus on that, as the dithering and palette based images are effectively only useful from an artistic perspective. I also wanted to avoid it being to long of a post to prevent readers from missing the punch line. Otherwise I would’ve mentioned things like HEIC/F (the image formats used by most phones now) and the patent nightmare around it, https://hific.github.io which is AI based compression, color depth and Tom Scott’s ’Why Dark Video is Such A Blocky Mess, and probably a bit about loading in images with progressively better quality with Progressive JPEG

                As for the knobs to turn for dithering, everything I did here was just poking with options until I got something I thought looked good. I found it interesting that with the black and white (not grayscale) image, adding noise before the dither seemed to improve the quality. The palette I used I made with Color Tool which is $15 on stream, which is actually why I didn’t link or mention it- There are plenty of free ways to make palettes.

                At some point I intend to do a more complete write up with links and put that into the art chapters, mostly https://opguides.info/design/digital-design/ which already has at least some of these links dropped in. For now though, there are other sections of my website I’ve been prioritizing. If you’re into this stuff, have knowledge to share, and want to contribute, the site is open source ;)

                1. 5

                  Have you tried pngquant? It should generate a more compressible dithering, although not as “artistic” looking. If you let it generate palettes, it may generate more optimized ones (if you want to change color scheme of an image, change it before quantization, not via forced palette). Better-fitting palette gives smaller files.

                  1. 2

                    Quantization is especially amazing for screenshots with lots of text/UI, OS built-in screenshot tools really should just do it by default..

                  2. 2

                    Awesome! Keep up the awesome work, and I agree that the pithy format works wonders. Short and to the point, a quick read that helped me come away with more knowledge than I started! Thanks!

                2. 7

                  Related question: where do you all store your images for serving a static site? I feel dirty every time I check another couple of Mb of images into my site’s git repo, but I haven’t found an elegant alternative

                  1. 4

                    Could use git-annexe or with some hosts git-lfs ?

                    1. 1

                      Mine are in the root of the repo on github, though there are two repos- the ‘dev’ repo and the actual public static site repo. This is normal for using github pages, with my website does. If I was actually paying for hosting I would probably find a better way to do it that didn’t involve checking the images into git at all.

                    2. 9

                      I didn’t make it all the way to 2021 to look at half-tone images.

                      1. 5

                        As much as I like less data, sometimes blind optimization is pretty bad. In the case of even the original comparison on my hiDPI laptop and phone go from crisp to unacceptable for photography. I also see a lot of tools strip the color profiles and push it into the limited sRGB spectrum. Along with, removing metadata that is either useful to photonerds or legally suspicious removing licence and attribution info all in the name of saving a few bytes. Sometimes this matters more that other times, but I think web developers can be too quick to remove all of this detail and data just to please the Google Lighthouse tool.

                        1. 2

                          My setup for making this site is 3x 4k monitors, so I definitely understand. However, I think the vast majority of images on the internet serve to break up the text and make site navigation easier, not to actually be dwelled on and appreciated. If my site were about photography, I would still probably convert to .webp or at least optipng my images as hard as I could, but I obviously wouldn’t degrade the quality like I do here.

                          I also super don’t care about Google Lighthose. What I do care about is avoiding elements shifting as things pop in after the initial load, and the easiest way to fix that is to just use lighter weight images where applicable.

                          1. 1

                            Never disagreed, though I’d pick .avif ha. The one that really springs to mind is Instagram and how everything is stripped, including massive cuts to image quality and converting from DCI-P3 if you aren’t using an Apple product. It’s depressing many photopgraphers feel compelled to upload in order to attract a casual audience.

                            By proxy this applies to PixelFed as well for copying this.

                        2. 3

                          OT: I really liked the guides at that website, guess I’ll read through the electronics section for a bit

                          1. 1

                            ^-^ thank you. Unfortunately, a lot of pages are more-or-less stubs. I’ve got basically everything I want to write down floating around in my head, just takes a lot of time to find the right way to put it into words