我的图片是如何进行抖动处理的
摘要
作者解释了使用 Imagemagick 进行图片抖动处理的方法,模拟调幅和调频打印模式,以减小文件大小并获得特定美学效果,灵感来源于 Low Tech Magazine 和粉色单色设计。
<p><a href="https://lobste.rs/s/2gbb6l/how_my_images_are_dithered">评论</a></p>
查看缓存全文
缓存时间: 2026/07/14 16:18
# 我的图片是如何进行抖动的
来源:https://dead.garden/blog/how-my-images-are-dithered.html
我对抖动其实了解不多。但每次去别人的网站,看到他们用很酷的方式抖动图片,我总会好奇他们是怎么做到的。所以,如果有人也想知道,这里分享一下我目前对这类粉色图片的抖动方法。
按照惯例,放一张示例图片——两个月后我要是改掉了这个做法,没人知道我到底在说啥:
图片是我和朋友在餐厅的合影,整张图只由粉色系、黑色和白色构成。
**编辑:** 我真是太了解自己了——现在的图片变成了这样:
同样一张图,但颜色变成了接近正常的色调,不过是一堆点
具体内容在文中会提到。
---
抖动除了让图片看起来(专业点说)酷毙了之外,还能减小文件体积,从而节省存储空间(如果只使用缩减后的图片),也能减轻网站的加载负担。比如像 [Low Tech Magazine](https://solar.lowtechmagazine.com/) 这样的网站就会使用它。
粉色图片的想法源于之前的一篇文章,当时我就尝试过这个做法。那篇 [Designing without color](https://dead.garden/blog/designing-without-color.html) 文章介绍了现有设计理念——我试图营造一种黑白“印刷”感,仅用颜色来强调重点。(这只有像我一样喜欢浅色模式的人才懂。)
当时用老方法处理出来的图片长这样:最亮的颜色现在是一种中等的粉色,就是我用来标记网站文本的那种。
关键区别在于,我把图片的调色板限制成了真正的单色:黑色和这个粉色。另外,那些奇怪的“抖动”点也变大了很多。
### 那么这到底是什么
放大看液晶屏幕上显示的 Chrome 标志
我的目标是模仿印刷图像。屏幕上的每个像素可以自由设置红、绿、蓝的亮度值——让你屏幕上的 RGB 光栅格子以不同强度发光——但在纸上(以及其他印刷基材上)情况就不同了。
要用打印机有限的颜色调色板来模拟更多颜色的错觉,需要用到点阵/矩阵。有三种方式可以实现:调幅(AM)、调频(FM)和混合网点。这里的“振幅”指网点的大小,“频率”嘛……就是频率。用 AM 网点生成暗部意味着大点,FM 网点则是很多小点。
上方展示的是 **FM** 印刷:网点看上去很杂乱。
下方展示的是 **AM**:所有网点按规律排列,亮部有更小的点
调幅网点的问题在于,如果你天真地去实现它,图片中会出现不想要的图案——所谓的 [摩尔纹](https://en.wikipedia.org/wiki/Moir%C3%A9_pattern)。
两个网格叠在一起产生的多余图案示例
因此,有个标准(DIN 16547)规定了 AM 印刷中各种颜色的偏移角度,以避免互相干扰。而 FM 网点是随机的,所以没有这个问题。
黄色 0°,青色 15%,黑色 45°,洋红 75°
### 在数字图像上模拟 AM 图案
我通过命令行访问服务器——所以用命令行工具快速处理图片。这个工具叫 imagemagick,通过 `convert` 命令调用。
我在网上找到了实现这种效果的方法(找不到链接了),并做了些调整。所以我不敢说自己精通每个参数的作用,但我会尽量解释。
```bash
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"
```
大致流程如下:先把图片宽度缩放到 800px(谁需要更大的?);转为 CMYK 色彩空间;添加背景填充旋转留下的空白(并设置高斯模糊来滤除噪点);分离出各通道;稍微放大(产生更大的点);然后扭曲(这里是旋转);最后再把四张图合并回一张。
对原图执行这个命令后生成的图片如下:
与目标效果有几分相似
合并之前,各通道的网点网格如下:
C 设置为 0°,M 设置为 15° 等等。不过无所谓。缩放查看时没有出现怪异伪影,所以够用了。
如果我们把网点大小增加到离谱的程度(8x8),就能更好地看清发生的情况:
方形汤
#### 编辑:通往 CMYK 的完美路径
睡了一觉,再重新读了一遍帖子,显然有一个方法能让图片在 AM 网格中真正限制为 CMYK 颜色。后面我会解释为什么这**不是**减小文件大小的好方法(除了限制颜色和把图片缩放到 800px 宽本身能减小体积外,其他步骤几乎都与此目标背道而驰)。不过,它确实看起来很酷。
用上述方法,网点的大小**并不会**变化。为什么要变化呢?我们已经讨论过,数字图像(除非我遇到了 GIF 或 PNG-8 的情况)可以给每个像素的 R、G、B 赋予不同的亮度。所以当看到上面的大图时,我们很清楚看到有些方块确实比其他的暗。对于真正的模仿来说,这可不行!
我们只需要在合并之前,把每个通道的颜色限制为 2 种。事后看来这很明显。不知道我最初命令来源的那个人为什么没这么做。
```bash
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"
```
这将生成这样的图片:
这才是我想要的
回想起来,我可能会考虑用这种图片代替粉色。如果我只想要网格的大致感觉,那么粉色就够了。虽然它拥有更多颜色(粉色调)却换来更少(实际有用的不同颜色)。谁说过写博客吐槽自己的馊主意没用呢?
#### 粉色——老方法
老方法的脚本中,`-distort SRT` 的值设置为 `2 (旋转)`,意思是把颜色放大到 2 倍,产生更大的网点。
运行上面的脚本后,色彩空间会再次转换,这次是转为灰度,然后颜色被限制为单色:黑色和**这个粉色**。完整命令如下,供想尝试的人:
```bash
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"
```
如果我们放大查看颜色限制前后的图片,可以看到网点确实看起来大小不一。我省略了 `-resize` 标志,以便更好地查看细节。
CMYK 细节 粉色细节
虽然并不完全精确,但我认为这种方法能让图片获得最接近印刷感的效果。遗憾的是它可能会吞噬很多细节,即使网点**没有**被放大。所以一段时间后我放弃了这个方法(也因为我厌倦了它的样子)。
**编辑:** 现在和**真正的**效果比较一下:
新方法真正将颜色限制为 CMYK,使四个通道的网点大小各不相同
### 粉色——新方法
单色是个好主意,但对博客(以及网站上的其他图片)用途有限。我需要更多层次。我需要的是更多粉色值。通过使用 `-remap` 标志来实现。
一个线性渐变,覆盖了整个网站的颜色调色板:白色、黑色、粉色和深粉色
```bash
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"
```
### 有问题的图片
对于像我一直在用的那张示例图片,一切都很完美。但对于整体非常亮的图片,可能会有问题。
例如:
原图
脚本处理后的图
手动编辑后的图
手动的操作:亮度增加到 300%,饱和度增加到 200%,反转颜色,然后重新映射到粉色
```bash
convert "oldfile" -modulate 300,200,100 -negate -remap $palette "newfile"
```
这可能是因为我的颜色调色板不太好。
### 这算是抖动吗?
如果你只关心结果,那么可以说图片已经被抖动了,尤其是老方法。仅用两种颜色,我们创造了**半色调**(这样用英语合适吗?)的错觉。新方法也基本同理。
但如果你关注方法,这并非抖动。或者至少是我见过的计算成本最高、最不必要的抖动形式。让我们重新梳理一下步骤:
1. 将 RGB 转换为 CMYK,增加一个通道
2. 将图片拆分为四张图,分别对每张进行变换(先放大再反转)
3. 将每张图的颜色限制为 2 种,然后把四张图叠加在一起
4. 再次设置色彩空间为 CMYK
5. 将颜色限制为 32/64/还没确定具体数值(叠加四个网格会产生新颜色)
整个过程在我那台用了 11 年的老 CPU 笔记本上,如果图片较大可能需要 10 秒。文件体积并不算“小”(尽管仍小于未缩放且超过 32 色的图片)。可以想象,这种图案对压缩算法并不友好。如果你珍惜时间或关心**实际**减小图片体积,**请勿尝试**。
### 脚本
```bash
#!/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
```
最后,放上原图用**真正**的抖动(FloydSteinberg)处理后的效果:
这个文件**比**新(粉色)方法**小**(但比老方法大)。
**编辑:** 真正的方法会对这张图产生更小的文件 :^} (虽然这**不是**一个规则!)
### (编辑:)RGB
当然也可以用 RGB 代替 CMYK 做同样的事。那就不那么精确了!!下面是 RGB AM 网格版本:
图片颜色更暗、更浓,某些地方有更多细节
📍 发布于 德国 爱尔福特
由人类撰写,非 AI 生成 (https://notbyai.fyi/)
标签:[meta](https://dead.garden/blog/tag_meta.html),[code](https://dead.garden/blog/tag_code.html)
相似文章
使用CSS进行抖动处理
本文演示如何使用CSS滤镜和SVG feTurbulence对图像应用抖动效果,以保持一致的视觉风格。
抖动二维码
本文介绍了一个抖动二维码生成器,它允许你在保持二维码可扫描的同时嵌入照片,并解释了相应的底层技术,如功能图案、拜耳抖动和弗洛伊德-斯坦伯格误差扩散。
单次调色板优化与有序抖动
一种单次方法将在线 k-means 调色板优化与 Bayer 有序抖动结合,省去了独立的像素映射步骤,带来轻微提速并生成视觉上更有趣的结果。
重新审视Yliluoma的有序抖动算法
本文重新审视了Joel Yliluoma在2011年提出的有序抖动算法,详细解释了其内部原理,介绍了简化变体,并将结果与最新技术进行了比较。附带了源代码。
JPEG 是如何工作的:交互式探索 JPEG 的有损压缩方法
一篇交互式文章,解释 JPEG 有损压缩的工作原理,涵盖色彩空间转换、频域、量化和编码步骤。