我的图像是抖动处理的
How My Images Are Dithered

原始链接: https://dead.garden/blog/how-my-images-are-dithered.html

这篇文章探讨了作者如何利用类似抖动(dithering)的技术,为网页图片营造出一种“印刷”质感。传统的抖动技术旨在减小文件大小,而作者的方法则专注于模拟物理印刷中使用的“调幅网点”(AM grid)效果,即通过点阵图案来呈现不同的色彩和明暗。 作者使用 ImageMagick 将 RGB 图像转换为 CMYK,对颜色通道进行分离,应用自定义的网点图案(通过将各通道旋转特定角度来防止出现摩尔纹),最后再重新合成。通过实验不同的调色板——包括一种特定的“粉色”单色美学——作者在视觉风格与文件大小优化之间取得了平衡。 作者承认该过程计算成本较高,并非标准意义上的抖动技术。然而,他们认为这种方法带来了一种独特的艺术性“印刷感”,提升了网站的设计水平。总的来说,本文为希望在数字环境中复刻模拟印刷质感的开发者提供了一份技术指南,并指出虽然这可能并非网页性能的最优解,但作为一种风格化的选择,它非常成功。

这篇 Hacker News 讨论围绕着一篇题为“我的图像是如何进行抖动处理的(How My Images Are Dithered)”的文章展开,探讨了抖动(dithering)和半色调(halftoning)的美学与技术应用。 讨论帖的主要观点包括: * **美学与技术:** 一些用户认为,WebP 等现代压缩格式使得抖动在减小文件大小方面已不再必要;但支持者坚持认为,抖动主要是为了模仿物理印刷媒介外观的一种艺术选择。 * **术语辨析:** 一个反复出现的争论点是“抖动”与“半色调”的区别。许多评论者指出,文中讨论的涉及点阵模式的技术,从技术层面讲属于半色调。 * **实际应用:** 参与者分享了抖动工具(如 Didder 和各类在线生成器),讨论了其在防止 8 位渐变色带方面的必要性,并探索了“去网纹”(descreening/undithering)这一细分领域。 * **怀旧与行业:** 前印刷业专业人士分享了对大规模凹版印刷这一“禅意”但要求苛刻的工作的见解,强调了从触觉式模拟流程向自动化数字工作流的转变。 * **人工智能与机器学习:** 社区讨论了通过抖动图像训练神经网络,是否能够提高模型忽略纹理并专注于高级视觉抽象的能力。
相关文章

原文

I don't know much about dithering. But when I visit other people's sites and they dither their images in cool ways I always wonder how they do it. So in case anyone is wondering, here's my current method for dithering these pink images.

Edit: Almost like I know myself too well — the pictures now look like this instead: my friends at a restaurant, the colors are normal-ish (not pink), except it's a bunch of dots

Which is covered in the post.


Dithering, beside making a picture look (to put it professionally) cool as fuck, can also reduce file size (by using less colors while maintaining details) and thus needed storage (if using only the reduced images) and the weight of your website for the client. That's why sites like Low Tech Magazine use it, for example.

The idea of pink images came from a post a while back, when I tried this before. The post Designing without color introduces the idea of the current design, where I try to get a sort of black and white "printed" vibe, using color only for emphasis. (This makes sense only when you are a light mode user like me).

The image back then, with the old method, looked like this: The brightest color here is now a mid-range pink, the one I use for marking text on the site

The key difference here being that I limited the picture's palette to true monochrome: black and this pink. Also, the weird "dithered" dots are much bigger.

So what's this

Zoom in on the Chrome logo being displayed on an LCD screen My goal was to immitate a printed image. While individual pixels on a screen may have the luxury of setting variable values of red, green and blue — making the grid of repeating RGB lights on your screen light up with different intensity — things work a little differently on paper (and other print substrates).

Getting the limited palette of colors your printer is working with to give the illusion of more colors requires using a grid/matrix of dots. You can go about this in three different ways: AM, FM and hybrid grids. The amplitude here being the size of the dot and the frequency, well … the frequency. Making a dark spot with AM grids means big dots, in FM grids it's lotsa dots.

The top shows an FM print: the dots look chaotic.

The bottom shows AM: all dots show up in a predictable pattern and light spots have smaller dots

The problem with amplitude modulated dots is that if you approach this naively you will end up getting unwanted patterns in your images: a so-called Moiré.

Example of unwanted pattern resulting from stacking two grids on top of eachother

For this reason, there is a rule (DIN 16547) about how exactly the colors are to be offset in an AM print to try and avoid them getting in each others way. Since FM grids are random they do not suffer from this problem.

Yellow at 0°. Cyan at 15°. Key (Black) at 45°. Magenta at 75°

Simulating AM pattern on your digital image

I access my server via the command line — so I use a command line tool to quickly edit images. The tool is called imagemagick and is called using the "convert" command.

I found the method to create this illusion online (can't find the link) and adapted it a little. So I can't claim to be the expert on what each individual argument does, but I will try to explain it.


convert "oldfile" -resize 800 -set option:distort:viewport '%wx%h+0+0' \
                 -colorspace CMYK -separate null: \
                 \( -size 2x2 xc: \( +clone -negate \) \
                +append \( +clone -negate \) -append \)  \
                -virtual-pixel tile -filter gaussian  \
                \( +clone -distort SRT 2,0 \) +swap  \
                \( +clone -distort SRT 2,15 \) +swap   \
                 \( +clone -distort SRT 2,45 \) +swap  \
                \( +clone -distort SRT 2,75  \) +swap +delete \
                -compose Overlay -layers composite -set colorspace CMYK -combine \
                "newfile"

Here's what it does generally: resizes the image to 800px in width (who needs more?), sets the color to CMYK, applies a background to fill the empty space left by rotating (and sets a gaussian blur to filter noise), splits up into the colors, scales them up a bit (for bigger dots) and distorts them (in this case: rotates them) and at the end it combines the 4 images into one again.

The image we generate by running this on the original file looks like this:

there is a resemblance to the targeted goal

Before combining them, these are the individual colors dot grids:

This sets C at 0°, M at 15° etc. But whatever. Zooming in and out doesn't make any weird artifacts apparent so it's good enough.

If we increase the size of the dots to a ridiculous degree (8x8), you can get a better look at what is happening.

square soup

Edit: The perfect route to CMYK

After sleeping on it and reading the post back, the solution for a pic limited to truly CMYK colors in an AM grid is apparent. For reasons I get into later this is NOT a good way of reducing file size (apart from the fact that limiting colors and resizing the image to 800 in width always reduces the size, everything else is pretty much stacked against that goal). It does, however, look cool.

With the above method, the dots do not vary in size. Why should they? As we already discussed, a digital image (unless I have a GIF or PNG-8 situation somewhere) can give varying intensity of light for R, G and B per pixel. So when we look at the big image above we can cleary see some of the squares are just darker than others. For a true immitation this will not do!

All we need to do is to limit each channel's colors to 2 before combining them again. This is very obvious in hindsight. Idk why the person I got the original command from didn't do this.

convert "oldfile" -resize 800 -set option:distort:viewport '%wx%h+0+0' \
                 -colorspace CMYK -separate null: \
                 \( -size 2x2 xc: \( +clone -negate \) \
                +append \( +clone -negate \) -append \)  \
                -virtual-pixel tile -filter gaussian  \
                \( +clone -distort SRT 2,0 \) +swap  \
                \( +clone -distort SRT 2,15 \) +swap   \
                 \( +clone -distort SRT 2,45 \) +swap  \
                \( +clone -distort SRT 2,75  \) +swap +delete \
                -compose Overlay -layers composite -colors 2 -set colorspace CMYK -combine \
                "newfile"

This will produce an image that looks like this:

now that's what I'm talking about

In retrospect I wonder if I might try out images like this instead of pink. If all I want is the general vibe of the grid then pink is fine. Though it does have more colors (shades of pink) for less colors (actually useful distinct ones). Who said blogging about your stupid idea isn't useful?

Pink — old method

The script for the old way of doing it had the values of -distort SRT set to "2 (rotation)", meaning it scaled the colors up to 2, making bigger dots.

After running the script above, the colorspace was once again converted, to Gray this time, after which the colors were leveled to monochrome: black and this pink. The whole command for those who want to try:


convert "oldfile" -resize 800 -set option:distort:viewport '%wx%h+0+0' \
                 -colorspace CMYK -separate null: \
                 \( -size 2x2 xc: \( +clone -negate \) \
                +append \( +clone -negate \) -append \)  \
                -virtual-pixel tile -filter gaussian  \
                \( +clone -distort SRT 2,0 \) +swap  \
                \( +clone -distort SRT 2,15 \) +swap   \
                 \( +clone -distort SRT 2,45 \) +swap  \
                \( +clone -distort SRT 2,75  \) +swap +delete \
                -compose Overlay -layers composite -set colorspace CMYK -combine \
                -colorspace Gray -colors 2   +level-colors  black,#A2719B \ 
        "newfile"

If we zoom in on the image before and after leveling the colors, we can see the dots do seem more like they vary in size. I omitted the -resize flag to give you a better view of the details.

While not entirely accurate, I think this method gets the most printy feel out of an image. Sadly it can swallow a lot of detail, even if the dots aren't scaled up. So I abandoned this after a while (also I got sick of looking at it).

Edit: now compare to the real deal:

the new method limits the colors to CMYK for real, making the dots vary in size across four channels

Pink – new method

it is made up only of shades of pink, as well as black and white. the vague pattern from the method I got online can be seen but the dots do not look like they vary in size

Monochrome is a cool idea but just has limited use for the blog (and other pics on the website). I need some more depth. What I need is more values of pink. I get these by utilizing the -remap flag.

A linear gradient going through this site's whole color palette: white, black, pink and darker pink


convert "oldfile" -resize 800 -set option:distort:viewport '%wx%h+0+0' \
                 -colorspace CMYK -separate null: \
                 \( -size 2x2 xc: \( +clone -negate \) \
                +append \( +clone -negate \) -append \)  \
                -virtual-pixel tile -filter gaussian  \
                \( +clone -distort SRT 1,0 \) +swap  \
                \( +clone -distort SRT 1,15 \) +swap   \
                 \( +clone -distort SRT 1,45 \) +swap  \
                \( +clone -distort SRT 1,75 \) +swap +delete \
                -compose Overlay -layers composite -set colorspace CMYK -combine \
                -remap "$palette" -colors 32 "newfile"

Problematic images

For images like the one I've been using everything works beautifully. But with images that are very overwhelmingly light, there can be issues.

Here's an example:

The edit in question: increasing brightness to 300%, Saturation to 200%, inverting colors and remaping them to pink

convert "oldfile" -modulate 300,200,100 -negate -remap $palette "newfile" 

This is possibly because my color palette is kinda bad.

Is this even dithering?

If all you care about is the result then you could say the image has been dithered, especially in the old method. Using only two colors we created the illusion of semitones (is this appropriate usage in English?)*. The same is basically true for the new method as well.

But if you care about the method this is not dithering. Or at least it's the most unnecessarily computationally expensive form of dithering I've come across. Let's walk through the steps one more time:

  1. Convert RBG to CYMK, adding one channel
  2. Split the image into four images and transform each one (starting with scaling them up and inverting them)
  3. limit colors to 2 on each and then overlay four images on top of eachother
  4. set colorspace to CMYK again
  5. limit the colors to 32/64/not sure yet where I want this (overlaying the 4 grids will create new colors)

This whole thing, on my laptop with an 11 year old CPU, can take 10 seconds if the image is big. The file size is not exactly "small" (though still smaller than unscaled pics with more than 32 colors). I can imagine the pattern isn't doing compression algorithms a huge favour. If you value your time or care about actually reducing an image's size do not do this.

Script

#!/bin/bash
temp="/tmp/images"
palette="/path/to/palette.png"
rm $temp
if [[ $1 = "" ]]; then
if [ -d smol ]; then 
echo must run with argument
else 
mkdir smol big
ls -r *.webp > $temp
ls -r *.png >> $temp
ls -r *.jpg >> $temp
ls -r *.jpeg >> $temp
ls -r *.gif >> $temp
fi
else 
ls -r *$1 > $temp
cp *$1 big
cat $temp
cp $(cat $temp) big/.
fi
i=$(cat "$temp" | wc -l)
echo $i
    while [[ $i -gt 0 ]]
    do
name="$(head -n $i $temp | tail -n +$i)"
echo "doing $name"
convert "$name" -set option:distort:viewport '%wx%h+0+0' \
         -colorspace CMYK -separate null: \
         \( -size 2x2 xc: \( +clone -negate \) \
        +append \( +clone -negate \) -append \)  \
        -virtual-pixel tile -filter gaussian  \
        \( +clone -distort SRT 2,0 \) +swap  \
        \( +clone -distort SRT 2,15 \) +swap   \
         \( +clone -distort SRT 2,45 \) +swap  \
        \( +clone -distort SRT 2,75 \) +swap +delete \
        -compose Overlay -layers composite -colors 2 \
        -set colorspace CMYK -combine \
        -colors 64 "smol/$name"
(( i-- ))

echo $i
done

To end with, here is the original image with actual dithering (FloydSteinberg)

This file is smaller than the new (pink) method (but bigger than the old one).

Edit: the true method will produce a smaller file for this image :^} (though this is not a rule!)

(Edit:) RGB

You can ofc do the same thing with RGB instead of CMYK. That wouldn't really be as accurate for a "print vibe"!! but here's the RGB AM grid version:

the image has darker, more intense colors and more details at some places

📍 Posted from Erfurt, DE

written by human, not AI

Tags: meta, code

联系我们 contact @ memedata.com