more 32kib image processing silliness by the Packbats

Last updated:

(Back to index)

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.

I. Ordered dither color depth

(To top)

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

The shoes picture: two pairs of feet in shoes - one pair red-and-black, the other dirty white - standing on a concrete slab next to a pillar. The person with red shoes has a blue skirt, the other has black pants. A clear grid dither pattern fills the image, capturing the vibes but with substantial color banding.

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

The shoes picture. The grid pattern is subtler now as the individual colors are closer together, and the image as a whole is more naturalistic with greater color fidelity.

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 shoes picture. The whole image has a red tinge to it, but it is again a little smoother than the basic 2-by-2 ordered dither version, particularly in the bright red areas.

(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.

II. Ordered dither and custom palettes

(To top)

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

The shoes picture. The dither grid is prominent with the small image resolution, and most of the colors are quite muted.

Lettuce break this command down a touch.

magick noexif.png
ImageMagick, here's the image to process.
-resize x200
First, resize it so the vertical resolution is 200 pixels. (The incorrect size, we're realizing now - the full 16-color mode was 160 by 100. Oops.) We didn't specify a `-filter` option so ImageMagick will just use its (very good) default.
-ordered-dither o2x2
We're using the default diffused-pixel 2x2 threshold map for an ordered dither. Because we didn't specify levels, it's going to use 2 levels per channel.
+dither -remap Cga_palette_color_test_chart.png
Now that we have the dithered image, we're going to turn off error diffusion dither (+dither works the same as -dither None) and then remap all colors to the nearest CGA colors.
cga-o2x2-2.png
And, finally, here's the filename to save it out to.

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

The shoes picture. The color resolution is fairly good, but the ordered dither we expect has an incredibly splotchy Riemersma dither pattern overlayed on top of it, and it's very distracting.

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

The shoes picture. There's a little weirdness to it, but it's fairly accurate, with good color detail, while still showing the clean grid artifacts of a 2x2 ordered dither.

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

The shoes picture. Low-resolution, very clear 2x2 ordered dither grid, but an accurate representation of the original colors.

...and here's what you get if you just make a JPEG:

magick noexif.png -resize x200 resize-x200.jpg

The shoes picture. Low-resolution, full color, absolutely clean render.

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

The shoes picture. Much of the image is either black and gray or is bright, saturated colors.

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

III. Floyd-Steinberg dithering with reduced error diffusion

(To top)

magick noexif.png -resize x200 -dither FloydSteinberg -remap Cga_palette_color_test_chart.png cga-fs.gif

The shoes picture, still in CGA palette. It's definitely more color-accurate, but instead of the grid patterns of the ordered dithers, there's just a lot of noisiness all over.

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

The shoes picture. There's still plenty of dithering noise, but everything is just a little smoothed out - there's no green pixels in the pillar, for example.

magick noexif.png -resize x200 -define dither:diffusion-amount=65% -dither FloydSteinberg -remap Cga_palette_color_test_chart.png cga-fs-65.gif

The shoes picture. Large areas of the picture are now uniform colors or nearly so - the yellow in the pavement is gone again, for example - but there's still a lot of dithering and the shades and gradients of the image are mostly present.

magick noexif.png -resize x200 -define dither:diffusion-amount=25% -dither FloydSteinberg -remap Cga_palette_color_test_chart.png cga-fs-25.gif

The shoes picture. Most of the image is made up of solid areas of gray or black, but a handful of regions are dithered, as are the transitions between colors.

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.

IV. Contrast stretching

(To top)

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

The shoes picture. The spectrum of colors have been posterized into a handful of grays, dithered with a neat grid, but the details of shoelaces, chain links, and so forth are clear.

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

The shoes picture. The dark parts of the picture (the shadows, the red shoes, the blue skirt) are now much darker, often mostly black, and the middle and light grays are a little darker as well.

…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

The shoes picture. The light parts of the picture (the white shoes, the highlights on the bolts in the pillar, the concrete floor toward the top of the image) are now stark white, and the middle and light grays are brightened as well.

…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

The shoes picture. The image is full of detail - the lighter tips of the red shoes, the bright side of the one in the daylight; the markings on the white shoes; the bolts in the pillar with their washers; the various shades and shadows of blue in the skirt. It's brighter than the original image, but not intensely brighter, and the details are clear.

This looks better than without the preprocessing.

V. Not the shoes picture

(To top)

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.)

Gravel and rock

magick IMG_202309-gravel.png -resize 13.4% gravel.jpg

Gravel-covered dirt. Sticking out of the gravel is a orange granite rock, much bigger than the gravel around it.

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

The gravel picture. It has been reduced to a handful of colors and the contrast has been increased, making the gravel into gray-black chunks and the granite and dirt into big pops of orange and yellow. The grid pattern of the ordered dither gives it another layer of texture.

Blossoms

magick IMG_202310-blossoms.png -resize 11.9% blossoms.jpg

A pair of pink blossoms, petals fading to white towards the center, in front of leaves and bushes and the wall of a brick house.

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

The blossoms picture. The contrast has been greatly increased, so rich pink-and-white blossoms leap out in front of the dark leaves behind them. Dense irregular patterns of dots create areas and gradients of color, like careful stippling.

Yard

magick IMG_202311-yard.png -resize 13.3% yard.jpg

A low-angle shot looking across a suburban yard at the multicolored autumn foliage of rows and rows and rows of trees. Dappled shadows cover the ground.

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

The yard picture. The colors of the whole thing, captured in the grid pattern of the dither with twenty-seven colors, are just a little bit brighter and richer.

Corner portrait

magick "IMG_20240131_131145 -- corner self-portrait.png" -resize 21.1% corner-portrait.jpg

The Packbats, a light-skinned Black plural system with big dark messy dreadlocks, standing below a vent in the corner of a room with pale blue walls and white ceiling. They are wearing a dark brown jacket and black sunglasses, and they have a neutral expression.

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

The corner portrait. Everything is painted in messy blobs of solid color - a handful of gray-blues for the wall, a handful of warm browns for the Packbats' face, the jacket now almost black, the ceiling fading to brown-grays in contrast to the blue wall.

Thanks for reading!

magick -background '#ff77ab' -gravity center -size 720x100 -fill '#1d2b53' -font "L'Internationale" -pointsize 72 label:'Thanks for reading!' thanks2.gif

The words 'Thanks for reading!' in a stylish font with thick bases to the letters and big dangling serifs, dark blue on a pink background.

(We don't know who Loki Gwynbleidd is but Loki Gwynbleidd's website has some downloadable fonts – this one is L'Internationale.)


Back to index.