Last updated:
So, our 32 KiB image processing experiment was a lot of fun, and people really liked it. But there's a few things we left out, and a few things we learned, since we put that up … so, let's go. Here's some more dithers.
For a reminder: to get a basic ordered dither, you use -ordered-dither [threshold map name], and each channel will be converted into binary on-or-off with dithering.
magick noexif.png -resize 11.5% -ordered-dither o2x2 colorp2-o2.png
This binary-ness is what we were talking about when we said ordered dithers require specific palettes – almost every ordered dither algorithm works by setting different thresholds for each pixel in the map (in the case of the o2x2 map, those thresholds are 20%, 40%, 60%, and 80%), and the only way you can get that algorithm to work on a color image is by dithering each channel separately.
(Aside: for those demo images with their custom thresholds on each channel, the command we used was -separate -threshold X% -combine.)
So, punchline: we were wrong about the binary part. Even using only that same algorithm, you can first split the 0-100% spectrum into chunks and then for each pixel (a) find which chunk you're in and (b) dither within that chunk according to the map. And ImageMagick supports this - you just add ,n after the threshold map name.
magick noexif.png -resize 8.6% -ordered-dither o2x2,3 colorp3-o2.png
Or, ,n,m,o if you want different numbers of divisions for each channel. (Although as of ImageMagick 7.1.1-26, the order seems to be blue-green-red and not red-green-blue as we personally would expect.)
magick noexif.png -resize 9.6% -ordered-dither o2x2,2,2,3 colorp223-o2.png
(The part where the color components are out of order makes us think that maybe there's a bug in there somewhere? It might be safer to use -channel to specify channels and write different processing chains for each, but we haven't learned how to use that yet.)
Again, you can get some real cool weird effects if you change colorspace – and for a lot of colorspaces, you really want the extra color resolution in at least some channels – but we'll let y'all play with CMYK and OkLab and HSV on y'all's own. We want to get to the other thing you might want extra color resolution for.
As we said, you cannot (as of this writing) specify a custom color palette for an ordered dither in ImageMagick. You would need a different algorithm.
…but we have to give a shoutout to Phillip Burgess's article for Adafruit, because even if you don't have an arbitrary-palette ordered-dither algorithm, you can still do a -remap command on your dithered image. It's not going to capture the colors as accurately as an error-diffusion dither, but it can work.
So, let's hit up the CGA color palette again and mess around.
magick noexif.png -resize x200 -ordered-dither o2x2 +dither -remap Cga_palette_color_test_chart.png cga-o2x2-2.png
Lettuce break this command down a touch.
You can see when we increase the number of levels what happens if we forget to turn off error diffusion dither.
magick noexif.png -resize x200 -ordered-dither o2x2,3 -remap Cga_palette_color_test_chart.png cga-o2x2-3-ri.png
What you're seeing here is the 2x2 ordered dither and ImageMagick's default Riemersma dithering algorithm both working at the same time, and, just our ioho, but it looks awful. It's educational – when this pattern shows up, you know you have more colors than your target colormap – but it's also not what we want. Hence the +dither.
magick noexif.png -resize x200 -ordered-dither o2x2,3 +dither -remap Cga_palette_color_test_chart.png cga-o2x2-3.png
This looks much better. This is entirely usable.
Now, we said up top that this is not as accurate. What does that mean in practice?
Well, here's what you get if you don't do the remap step:
magick noexif.png -resize x200 -ordered-dither o2x2,3 nocga-o2x2-3.png
...and here's what you get if you just make a JPEG:
magick noexif.png -resize x200 resize-x200.jpg
Immediately, you can see in the remap that a bunch of colors changed - some got lighter, some got darker. The kind of salmon-y red of the trim on the toes of the white shoes turned stark orange, the brown-gray of the column is much paler, etc., etc. It's simply not as faithful a reproduction of the original image.
A subtler inaccuracy – and it is very subtle in this particular case, we could only find it in the middle part of the skirt – is that we lose some colors. If we go to four levels before remapping:
magick noexif.png -resize x200 -ordered-dither o2x2,4 +dither -remap Cga_palette_color_test_chart.png cga-o2x2-4.png
...then it's not subtle – a lot of colors are vanishing into near-uniformity. For example, there's almost no yellow anywhere, just gray and brown. There was yellow, but it got rounded away. And so did a lot of the darker blues in the skirt.
The fact of the matter is, ordered-dither-plus-remap is just going to be a compromise, no matter what you do. But it's a compromise that you might want. And hey, maybe you like the effect when there's less dithering and more uniform areas.
Actually, speaking of less dithering:
magick noexif.png -resize x200 -dither FloydSteinberg -remap Cga_palette_color_test_chart.png cga-fs.gif
Our complaint about Floyd-Steinberg was that it was all over dither artifacts. At small sizes like this, we find that distracting.
…...but as it happens, ImageMagick has a thing called "defines", with which you can change how various aspects of various algorithms work. And one of those defines is dither:diffusion-amount=X%, which lets you reel back the amount of error diffusion going on in your image.
magick noexif.png -resize x200 -define dither:diffusion-amount=85% -dither FloydSteinberg -remap Cga_palette_color_test_chart.png cga-fs-85.gif
magick noexif.png -resize x200 -define dither:diffusion-amount=65% -dither FloydSteinberg -remap Cga_palette_color_test_chart.png cga-fs-65.gif
magick noexif.png -resize x200 -define dither:diffusion-amount=25% -dither FloydSteinberg -remap Cga_palette_color_test_chart.png cga-fs-25.gif
As you probably expected, less dithering means less color fidelity, but also less speckle. It's a dial, like threshold percentage in threshold dithers, and as you play with it you can figure out where to settle it to get the impact you want.
It helps if you do some preprocessing, too.
Let's kick it over to the black-and-white side of things. Here's the 2x2 ordered dither we had last time, that didn't capture the gradients very well.
magick noexif.png -resize 16.7% -colorspace gray -ordered-dither o2x2 bw-o2.png
On that page, we suggested -contrast-stretch 11x9%. What does that mean?
Well, the way contrast stretching works is this: the first number represents how much of the picture should be crushed to black:
magick noexif.png -resize 16.2% -colorspace Gray -contrast-stretch 10x0% -ordered-dither o2x2 bw-o2-cs10x00.png
…and the second number represents how much should be blown out to white:
magick noexif.png -resize 16.6% -colorspace Gray -contrast-stretch 0x10% -ordered-dither o2x2 bw-o2-cs00x10.png
…and in both cases, the rest of the image is also darkened or lightened proportionately. Which is really good for dithering, because steeper color gradients are easier to capture, because the proportions of bright and dark pixels are changing more.
(By the way, don't forget the percent sign. Without it, these numbers are how many pixels should be made black or white, which almost certainly isn't what you meant.)
Now, every picture is going to be different, but for this one we found ourselves considering two sides of it:
…and … yeah, there's not one right answer. We didn't even go with the -contrast-stretch 11x9% that we said in the first post. We went with 2x8%:
magick noexif.png -resize 16.1% -colorspace Gray -contrast-stretch 2x8% -ordered-dither o2x2 bw-o2-cs02x08.png
This looks better than without the preprocessing.
Like, yes, the premise of the article is that we want to share this specific picture of shoes but make it 32 KiB … but at some point we should, y'know, make sure that we're coming up with techniques that work on more than one image.
…I mean, show off that these techniques work on more than one image! Yes. *coughs*.
(Using JPEG versions for the reference images for compatibility.)
magick IMG_202309-gravel.png -resize 13.4% gravel.jpg
We saw this and we thought the contrast in size between the larger orange rock and the small blue-gray gravel was really striking - so we started dithering and tried to play that up.
magick IMG_202309-gravel.png -resize 35.9% -contrast-stretch 30x0% -colorspace CMYK -ordered-dither o2x2 gravel-cmyk-o2-cs30x00.gif
magick IMG_202310-blossoms.png -resize 11.9% blossoms.jpg
Look at these pretty flowers! They're so nice. The subtle gradient of the colors in the petals is so good.
magick IMG_202310-blossoms.png -resize 21.6% -contrast-stretch 40x1% -define dither:diffusion-amount=90% -dither FloydSteinberg -remap RGB_3bits_palette_color_test_chart.png blossoms-rgb-fs-cs40x01-90.gif
magick IMG_202311-yard.png -resize 13.3% yard.jpg
Trees are the best. Look at all these colors! We love this.
magick IMG_202311-yard.png -resize 22.7% -contrast-stretch 4x1% -ordered-dither o2x2,3 yard-o2-3-cs4x1.gif
magick "IMG_20240131_131145 -- corner self-portrait.png" -resize 21.1% corner-portrait.jpg
This was kind of an accident – we had our phone on a stand on a timer and we were kinda estimating where the frame was – but it works. It has a real portrait-of-the-artist vibe.
magick "IMG_20240131_131145 -- corner self-portrait.png" -resize 31.3% -dither Riemersma -colors 15 -auto-level corner-portrait-color15-ri-autolevel.png
magick -background '#ff77ab' -gravity center -size 720x100 -fill '#1d2b53' -font "L'Internationale" -pointsize 72 label:'Thanks for reading!' thanks2.gif
(We don't know who Loki Gwynbleidd is but Loki Gwynbleidd's website has some downloadable fonts – this one is L'Internationale.)