我的图片是如何进行抖动处理的

Lobsters Hottest 工具

摘要

作者解释了使用 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进行抖动处理

Hacker News Top

本文演示如何使用CSS滤镜和SVG feTurbulence对图像应用抖动效果,以保持一致的视觉风格。

抖动二维码

Hacker News Top

本文介绍了一个抖动二维码生成器,它允许你在保持二维码可扫描的同时嵌入照片,并解释了相应的底层技术,如功能图案、拜耳抖动和弗洛伊德-斯坦伯格误差扩散。

单次调色板优化与有序抖动

Lobsters Hottest

一种单次方法将在线 k-means 调色板优化与 Bayer 有序抖动结合,省去了独立的像素映射步骤,带来轻微提速并生成视觉上更有趣的结果。

重新审视Yliluoma的有序抖动算法

Lobsters Hottest

本文重新审视了Joel Yliluoma在2011年提出的有序抖动算法,详细解释了其内部原理,介绍了简化变体,并将结果与最新技术进行了比较。附带了源代码。