ImageMagick 使用示例 -- 色彩量化与抖动
- ImageMagick 使用示例 前言与索引
- 减色概述 (涉及哪些内容)
- 图像中的颜色 (图像使用了哪些颜色)
- 提取图像颜色
-
色彩量化 (减少图像中的颜色数量)
- 色彩量化与色彩空间
- 量化不会精确保留颜色
-
误差校正抖动 (即伪随机化抖动)
- E-Dither 的工作原理
- E-Dither 对变化敏感
- E-Dither 的像素斑点
- 单色抖动位图图像
- 双色量化
- 使用预定义颜色映射表进行抖动
- 常用(或"最佳")颜色映射表
- 使用 Web 安全色
- 均匀颜色映射表
- 均匀 332 颜色映射表
- TrueColor 16位即 556 颜色映射表
- 经伽马校正的均匀颜色映射表
-
有序图案抖动 (使用平铺阈值图)
- 数字半色调抖动
- 半色调偏移抖动
- XML 阈值图
- 使用均匀色阶的有序抖动
-
自制抖动图案与阈值图 (以自己的方式对图像做抖动)
- 自制有序抖动阈值图
- 自制水平线抖动
- 用符号图案做抖动
减少色彩数量或替换特定颜色,是图像处理中非常复杂且困难的一个步骤,这正是本示例页面所要讨论的主题。这包括确定要使用哪些颜色(色彩量化),以及如何将这些颜色分布到图像上(抖动与图案化)。它还包括位图或双色图像的生成,甚至涉及布尔型(开/关)透明度的处理。这一点非常重要,以至于减色或量化经常会自动地在幕后发生,正是为了让 ImageMagick 能够完成它最初的主要任务,即把图像从一种文件格式转换为色彩较少的另一种格式,例如 GIF、XPixmap 和 XBitmap 格式。了解这一机制的运作方式,可以让你更好地掌控这个过程,从而改善存储在特定图像文件格式中的最终结果。
减色概述
减色是 ImageMagick 中一个非常重要的方面。例如,要把一张包含数百万种颜色的 JPEG 或 PNG 图像转换成最多只有 256 种颜色的 GIF 图像,你确实需要能够以高效且有效的方式减少颜色。在图像格式转换过程中,这通常会自动在幕后发生,但有时你也需要手动进行。减少图像中的颜色数量通常是一个三步过程,
- 首先,你通常需要调查一幅图像使用了哪些颜色。不仅要看实际用了多少种颜色,还要看某种特定颜色被使用的频率。如果只有一个像素在用某种颜色,那么保留这种颜色就没有多大意义——不过有时你仍然需要这样做。
- 接下来,你需要以某种方式决定要把图像限制到哪一组最终颜色。你可能希望 IM 尝试为特定图像确定"最佳"的颜色组合。有时你可能想要更通用、更全局化的东西,能用于任何图像。你甚至可能想从将要使用的颜色集中专门增加或去掉某种颜色。
- 最后,你需要修改图像,使其只使用你所选定的颜色。最好是让结果看起来不错,或者也许你希望它能够很好地压缩、比较或优化。
更复杂的是,这几个步骤往往是相互关联的,因为某种替换颜色的方法,通常只能应用于特定的颜色组合。而如果你使用的是特定的颜色组合,那就不需要做任何颜色调查了,或者你可能需要为特定颜色设置例外。基本上,虽然减色经常在幕后自动处理,但至少了解一下正在发生什么、以及会产生什么效果,还是很有好处的。
颜色调查
这可能是最不重要的部分,虽然 IM 提供了执行调查的方法,但用户很少出于减色的目的去做这件事。关于这部分的进一步讨论,留给相关的章节 提取图像颜色。
颜色选择(量化)
关于一个不错的初步概述,请参见 维基百科,色彩量化。选择颜色的基本方法有四种。这四种颜色控制方法:量化、预定义颜色映射表、均匀颜色和阈值,正如你将看到的,都各有其局限性。下面是这四种方法各自的示例……
magick colorwheel.png +dither -colors 32 color_quantize.gif
magick colorwheel.png +dither -remap colortable.gif color_predefined.gif
magick colorwheel.png +dither -posterize 3 color_uniform.gif
magick colorwheel.png \
-separate -threshold 50% -combine color_threshold.gif
每张最终图像中的颜色数量只是一个代表性的集合,但在每种情况下大约都是 32 种颜色(阈值处理的那张除外,它只有 8 种)。由此你可以大致了解每种方法能得到什么样的结果。除第一种方法之外,其他所有方法都拥有一组固定的颜色(依据运算符的参数而定),与正在被减色的图像本身无关。只有第一种方法("[-colors](https://imagemagick.org/command-line-options/#colors)")才会真正根据当前图像的内容来挑选颜色。由于测试图像以白色为主,因此选中了很多较浅的颜色。它使用一种称为"自适应空间细分(Adaptive Spatial Subdivision)"的技术,借助八叉树来调查图像中的颜色。然后在给定限制范围内,尝试选出一组能最好地匹配特定图像的颜色。参见下文的色彩量化运算符。"[-remap](https://imagemagick.org/command-line-options/#remap)"让你可以给 IM 提供自己预定义的一组颜色(参见用户自定义颜色映射表)。上面用到的颜色映射表 "[colortable.gif](../static/img/images/colortable.gif)",是专门为一个旧的 X Window 图标库挑选的 32 种颜色的集合,其设计考虑的是卡通风格的图标。(详情参见 AIcon Library, X Icon Color Selection)使用"[-posterize](https://imagemagick.org/command-line-options/#posterize)"还可以在数学上把每个颜色通道划分为一组色阶或强度级别,从而生成"均匀颜色映射表"。也就是说,每个通道都被设置为一组固定的取值或强度的颜色映射表。最后,还可以对图像的全部或特定颜色通道进行"[-threshold](https://imagemagick.org/command-line-options/#threshold)"处理,本质上使每个颜色通道变成纯粹的布尔值,即开或关。也就是说,每个颜色通道只能取零或 MaxRGB(取决于 IM 的 'Q' 级别)这两个值。不过,这样做只能产生大约 8 种颜色的极小集合,是非常有限的一个颜色集。阈值处理其实也等同于选取 2 种颜色的 "[-posterize](https://imagemagick.org/command-line-options/#posterize)" 级别 '1'。
应用一组颜色
一旦你有了一组颜色,接下来的问题就是如何将这些颜色应用到图像上,使现有的颜色被替换为所选定的这组颜色。这被称为"抖动(Dithering)",之所以这样命名,是因为它具有"应该选这个,还是应该选那个?"这种非此即彼的性质。抖动的基本思路,是把不同颜色的像素以某种方式彼此靠近地放置,从而欺骗人眼,让人觉得图像中使用的颜色比实际使用的更多。也就是说,由于人眼会把相邻的颜色"融合"在一起,图像该区域的颜色在视觉上会更接近原始图像的颜色。关于抖动,最好的入门介绍之一在 维基百科上,不过你需要跳过开头的"音频抖动"部分。它给出了一组很好的示例,展示了在颜色数量有限的情况下,使用抖动像素图案的好处。颜色替换的基本方式包括……
- 直接颜色映射(阈值处理和色调分离)
- 随机抖动(纯随机的像素放置)
- 误差校正抖动(伪随机化的像素图案)
- 有序扩散像素抖动(规则的像素图案)
- 数字半色调(不同大小的点)
上面展示的直接映射方式,就是在给定的颜色集中选取最接近的颜色。基本上,你会得到一块块颜色不变的纯色区域。当这种方式应用到颜色缓慢变化的图像上时,例如现实生活中天空的照片,图像上就会出现一条条色带,尤其是在原本应该是平滑渐变的区域,比如天空区域。这样的结果通常被认为不太好。直接颜色映射通常被认为可以接受的场合,只有标志、符号、图标和卡通类图像。它实际上很少是一个可行的选项。这也是为什么,如果你不想让图像中的颜色被直接映射,通常必须关掉常规的抖动方法的原因。然而抖动本身也有它自己的问题。图像一旦经过抖动处理,一种颜色图案就成了图像的一部分。这种图案一旦存在,就极难去除。而且,对图像多次重复应用抖动通常也不是好主意,因为那只会让图像质量下降。正因如此,下面大多数量化示例通常都会展示如何为每种技术生成未经抖动处理的版本。这样做是为了让你能在抖动掩盖这些信息之前,看清楚究竟做出了怎样的颜色选择。随机抖动是最早出现、最简单的抖动方法,同时也被认为是最差的一种抖动方法。不过它也有一些特殊用途。在 IM 中它只适用于双色情形,因此通常仅限于特殊场合下的位图抖动。更多内容参见下文的带阈值处理的随机抖动。误差校正抖动通常被认为是在图像上进行颜色抖动的最佳通用方法,因为它能产生最接近图像各区域原始颜色的近似结果。目前它也是唯一能够对_任意_一组颜色进行抖动的方法,因此可以用于全部四种减色技术。更多细节参见下文的 E-Dither 的工作原理。不过误差校正抖动也存在一些严重的问题,尤其是在图像动画方面。最后两种抖动技术有序扩散像素和数字半色调也被认为是不错的方法,并且能很好地适用于动画,但目前它们无法使用任意一组颜色,只能使用固定的一组均匀颜色。不过它们提供了一种用图案为图像着色的手段,能产生原本不容易做出的有趣效果。
所有这些减色的方方面面都是重要的技术,理解它们可以让你把图像处理的效果,提升到超越 IM 所提供的泛化默认设置的水平。这非常值得花时间研究。
图像中的颜色
关于图像的信息,例如所使用的颜色数量以及总体分布情况,对于那些试图判断最佳处理技术的程序和脚本来说,可能非常重要。这里我会介绍几种可用来获取此类信息的方法,而不仅限于减色这一方面。
提取图像颜色
提取颜色表
你可以使用详细模式的 "[identify](basics.html#identify)" 从图像中提取调色板,下面这几种方法基本上都做的是完全相同的事情。
magick identify -verbose image.png
magick image.png miff:- | identify -verbose -
magick image.png -verbose -identify null:
magick image.png -verbose info:
| 如果颜色数超过 1024 种,上述任何一种详细识别输出都不会返回颜色表或直方图!因此,对于色彩丰富的大图像来说,这种方式能否奏效全凭运气,并不推荐使用,不过它有时仍然是有用的。
---|---
不过更好的方式,是生成图像的 "[histogram:](files.html#histogram)",然后提取结果中所包含的注释信息。 | |
magick tree.gif -format %c -depth 8 histogram:info:-
| "info:" 这种输出格式是在 IM v6.2.4 中新增的。对于此前的 IM 版本,请使用…… |
magick tree.gif histogram:- | identify -depth 8 -format %c -
这些方法的问题在于,你得到的是颜色的纯文本输出,需要自行按需解析。不过从 IM v6.2.8-8 开始,"[-unique-colors](https://imagemagick.org/command-line-options/#unique-colors)" 运算符可以把一幅图像转换成一幅更小的图像,其中每种在原图中出现的独特颜色只对应一个像素,全部排在一行中。这意味着你可以把一幅图像转换成一个更简单的颜色表图像,列出其中出现的每一种颜色。图像的宽度就是颜色数量,如果你需要实际列出这些颜色,可以把它输出为 "[txt:](files.html#txt)" 图像格式。例如,这是 tree 图像的颜色表。 |
magick tree.gif -unique-colors -scale 1000% tree_colors.gif
magick tree.gif -unique-colors -depth 16 txt:-
这种精简后的颜色表,作为一种把生成的颜色映射表以非常小的文件保存下来的方式,同样非常重要。这类映射表对于 "[-remap](https://imagemagick.org/command-line-options/#remap)" 这个减色运算符来说尤为重要。(参见下文的预定义颜色映射表) 如果你想得到一幅不仅包含图像中的颜色、还包含颜色计数的图像,这里有一种颜色直方图的解决方案,它源自一次 IM 论坛讨论。
magick rose: -colors 256 -format %c histogram:info:- |
sed 's/:.*#/ #/' |
while read count color colorname; do
magick -size 1x$count xc:$color miff:-
done |
magick - -alpha set -gravity south -background none +append \
unique_color_histogram.png
请注意,我不得不对图像做减色处理,因为内置的 "rose:" 图像包含 3020 种独特颜色,那样处理起来耗时很长,而且会生成一张非常长的图像。上面展示的那张 rose 的 GIF 图像,采用了同一组减色设置。得到的图像仍然包含相同数量的像素,只不过填充了额外的透明像素;正如你所见,它显示出以偏绿的灰色、浓重的红色为主,并有一个非常明显的纯白色峰值。这或许不是最理想的通用颜色直方图方法,但对这张图像来说效果不错。 |
对于 "[histogram:](files.html#histogram)" 和 "[-unique-colors](https://imagemagick.org/command-line-options/#unique-colors)" 运算符而言,颜色的排列顺序都是未定义的,但看起来是按红、然后绿、最后蓝通道的值来排序的。这对于某一张具体的图像来说,也许不是最好的排序方式,但要把三维的颜色统一排序成一维顺序,本来就是不可能做到的。 |
|---|---|
提取平均颜色
使用 "[-scale](https://imagemagick.org/command-line-options/#scale)" 把图像缩小成单个像素,可以非常快速地求出图像的平均颜色。例如,下面是内置的 "rose:" 图像的平均颜色。我用 FX 转义格式来输出颜色,它返回的颜色字符串可以直接在 IM 中使用,无需改动。
magick rose: -scale 1x1\! -format '%[pixel:s]' info:-
使用 "%[pixel:...]" 这个 FX 转义的问题在于,它可能返回的是像 'white' 或 'silver' 这样的颜色名,而不是 RGB 值。不过你可以用三个 FX 转义来模拟这一点,从而以想要的位深度返回实际的 RGB 值。例如……
magick rose: -scale 1x1\! \
-format '%[fx:int(255*r+.5)],%[fx:int(255*g+.5)],%[fx:int(255*b+.5)]' info:-
从 IM v6.3.9 开始,有一些新的 "[-format](https://imagemagick.org/command-line-options/#format)" 转义符,可以在不用解析详细的 "[identify](basics.html#identify)" 或 "[info:](files.html#info)" 输出的情况下,直接提取有关图像的更具体信息。例如,你可以通过获取图像红色通道图像的灰度值 '%[mean]',来得到红色通道的平均颜色。
magick rose: -channel R -separate -format '%[mean]' info:
提取特定颜色
从命令行中提取图像某个特定像素颜色的方法,基本上有两种。一种是在特定的像素位置上使用像 "%[pixel:...]" 或 "%[fx:...]" 这样的 FX 转义(参见上文)……
magick rose: -format '%[pixel:p{40,30}]' info:-
另外,你也可以用 "[-crop](https://imagemagick.org/command-line-options/#crop)" 剪切出你感兴趣的单个像素,从而简化图像,然后使用前面介绍的任何一种方法。例如……
magick rose: -crop 1x1+40+30 -depth 8 txt:-
统计特定(或近似)颜色的数量
这可以用来获取某种特定颜色的像素数量或所占比例。做法是:把不是该颜色的部分都变成黑色,再把该颜色变成白色。举例来说,让我们统计一下 'tree' 图像中 "yellow" 太阳所占的颜色数量。
magick tree.gif -fill black +opaque yellow \
-fill white -opaque yellow \
-print "yellow sun pixels = %[fx:w*h*mean]\n" null:
有一点需要注意,如果被测试的颜色本身就是黑色,这个方法就不管用了。要处理黑色(或非常暗的颜色),可以把 fill 互换一下,把非黑色映射为白色,然后对结果做反相处理,从而生成所有黑色像素的白色遮罩。请记住,"[-print](https://imagemagick.org/command-line-options/#print)" 选项等价于使用 "-format ... -write info:",可以在图像处理过程中的任何位置使用。然后我用特殊的 "[null:](files.html#null)" 文件格式把不需要的图像丢弃掉。你也可以把图像保存下来,留作以后当作遮罩使用。请注意,虽然这种方法对小图像来说效果不错,但对于(比如高分辨率数码照片这样)大得多的图像,'mean' 的精度不足以得到精确的像素数!基本上,上面这种使用 'mean' 的方式,适合用来生成一个比例,但不适合用来得到精确的像素计数。要得到精确的像素计数,最好使用带有精确像素计数的直方图 '注释' 输出(参见上文)。上面的方法也可以使用模糊系数选项 "[-fuzz](https://imagemagick.org/command-line-options/#fuzz)",放在 "[-opaque](https://imagemagick.org/command-line-options/#opaque)" 运算符之前,从而把"相近"的颜色也纳入进来。
比较两种颜色
假设你有两种特定的颜色,想要对它们进行比较。你可以用 "magick compare " 来获取 RMSE(基于标准误差)……
magick compare -metric RMSE xc:Navy xc:blue null:
这种方法的好处在于,它既能给出两种颜色之间的数值距离,也能给出从黑到白的距离的归一化百分比。但是,这种方法无法妥善处理透明度。例如,比较"完全透明的黑色"和"完全透明的白色"时……
magick compare -metric RMSE xc:'#0000' xc:'#FFF0' null:
透明的颜色实际上应该有着零距离,因为完全透明的话,不管底层颜色是什么,结果都是一样的。然而我们得到的却是一个 4 维超立方体的距离。因此,上述这种颜色距离的方法,仅适用于比较完全不透明的颜色。
除了获取实际距离之外,你还可以用模糊系数来检查两种颜色是否接近。
magick compare -fuzz 20% -metric AE xc:Navy xc:Blue null:
magick compare -fuzz 30% -metric AE xc:Navy xc:Blue null:
不过要记住,如果像素不匹配(错误像素数),结果会是 '1'。要获得区分这些值的实际"模糊"系数距离,可以使用 'FUZZ' 这个度量。
magick compare -metric FUZZ xc:Navy xc:Blue null:
这个"归一化"的值表明,实际距离是 28.7%。使用模糊系数,与在涉及透明度时计算 RMSE 是不同的。也就是说,因为模糊系数在设计上,会把任意两个完全透明的颜色视为相等。因此,"完全透明的黑色"和"完全透明的白色"是完全等价的(得到的值为 0,即没有错误像素)……
magick compare -metric FUZZ xc:'#0000' xc:'#FFF0' null:
另一种比较颜色的方法,是尝试用替换颜色,并配合适当的模糊系数百分比。例如……
magick xc:Navy -fuzz 20% -fill Blue -opaque Blue txt:
由于 'Navy' 没有变成 'Blue',说明它与 'Blue' 的差异超过了 20%。而
magick xc:Navy -fuzz 30% -fill Blue -opaque Blue txt:
这次颜色确实变成了 'Blue',所以现在我们知道,'Navy' 与它之间的距离介于 20% 到 30% 之间。要在脚本中做到这一点,可以使用类似下面这样的方式……
fuzz=%1
color1="red"
color2="#e00"
color2=`magick xc:"$color2" -format '%[pixel:s]' info:`
result=`magick xc:"$color1" -alpha set -channel RGBA -fuzz $fuzz \
-fill $color2 -opaque $color2 -format '%[pixel:s]' info:`
if [ "$result" = "$color2" ]; then
echo "Colors match according to Fuzz Factor"
else
echo "Colors DO NOT match"
fi
特殊选项 "-alpha set -channel RGBA" 很重要,它使我们能够对透明及接近透明的颜色进行模糊匹配。
色彩量化
色彩量化运算符
色彩量化的主力工具,也是所有自动减色内部使用的工具,就是 "[-colors](https://imagemagick.org/command-line-options/#colors)" 运算符。它实现了一种"自适应空间细分(Adaptive Spatial Subdivision)"减色算法,是一个非常出色的减色算法。下面是一个典型示例,我有一张包含大量颜色的 'colorwheel' 图像,我们让 IM 使用各种抖动方法把颜色数量减少到仅 64 种。
magick colorwheel.png -dither None -colors 64 colors_64_no.gif
magick colorwheel.png -dither Riemersma -colors 64 colors_64_rm.gif
magick colorwheel.png -dither FloydSteinberg \
-colors 64 colors_64_fs.gif
IM 默认会使用"抖动"在图像上晕染颜色。这可以防止在平滑渐变的区域出现突兀的颜色变化。如果关闭抖动(使用 'None' 或 "[+dither](https://imagemagick.org/command-line-options/#dither)" 设置),你就能清楚地看到,为了给这张特定图像生成 IM 认为最合适的一组颜色,哪些颜色被合并到了一起。你也能看到,如果不做抖动处理,颜色渐变会产生怎样突兀的颜色变化。当然,这张图像使用的颜色比大多数图像所用的要多得多。因此,虽然 64 色的限制对许多图像来说往往是可以接受的,但对这张图像来说完全无法接受。换句话说,色彩量化会尝试为某一张具体图像找出最合适的一组颜色。下面是针对 IM 徽标一部分、使用极少数量颜色的色彩量化示例。
magick logo: -resize 40% -crop 100x100+105+50\! -normalize logo.png
magick logo.png +dither -colors 8 colors_8_no.gif
magick logo.png -dither Riemersma -colors 8 colors_8_rm.gif
magick logo.png -dither FloydSteinberg \
-colors 8 colors_8_fs.gif
把它和内置的 "rose:" 照片图像的一些结果比较一下。
magick rose: +dither -colors 16 colors_16_no.gif
magick rose: -dither Riemersma -colors 16 colors_16_rm.gif
magick rose: -dither FloydSteinberg \
-colors 16 colors_16_fs.gif
正如你所见,卡通类图像相比真实照片,需要的颜色要少得多,就能产生不错的效果。 | _目前 IM 中只实现了一种色彩量化算法,即"自适应空间细分",由于它效果很好,所以几乎没有必要再添加其他算法。不过,随着用户反馈的积累,这个算法也在不断地稳步改进中。
题外话:作为参考,"[Gifsicle](http://www.lcdf.org/gifsicle/)" 程序(通过它的 "--color-method" 选项)列出了若干其他的色彩量化方法。我并不清楚这些色彩量化方法与 IM 相比效果如何。如果你找到关于不同色彩量化方法的好资料,请给我写信告知。_
---|---
色彩量化内部原理
在图像中选择要使用的有限数量颜色的过程称为色彩量化,这是一个涉及众多因素的非常复杂的过程。关于它的完整技术说明,请参见 ImageMagick 网站上的 Color Reduction Algorithm。不过这里我会尝试举例说明其中一些较为重要的方面。可能最大的因素是图像中实际使用的颜色。如果某个特定颜色在图像中'接近'该颜色的像素非常少,那么为图像选取这个颜色就没有意义。因此,颜色的选择不仅取决于图像中使用的颜色,还取决于'接近'该颜色的像素数量。通过尝试把两幅不同的双色图像缩减为单一的共同颜色,可以很容易地演示这一点。
magick -size 4x1 xc:blue -draw 'fill red point 0,0' \
-scale 20 colors_rb.gif
magick -size 4x1 xc:red -draw 'fill blue point 3,0' \
-scale 20 colors_br.gif
magick colors_rb.gif -colors 1 colors_rb2.gif
magick colors_br.gif -colors 1 colors_br2.gif
如你所见,最终的单一颜色不仅取决于图像中存在的颜色,还取决于每种颜色的数量。
magick -size 20x640 gradient: -rotate 90 gradient.png
magick gradient.png +dither -colors 5 colors_gradient.gif
请注意,色彩量化在当前色彩空间内是均匀进行的。
FUTURE: "[-treedepth](https://imagemagick.org/command-line-options/#treedepth)" 设置到底有什么效果?
如果你知道,请给我写邮件
色彩量化与色彩空间
影响颜色选择的另一个重要因素,是精确定义什么叫做'接近'或'邻近'的颜色。这由用于量化(颜色选择)的色彩空间来定义,并且(从 IM v6.2.8-6 起)由"[-quantize](https://imagemagick.org/command-line-options/#quantize)"色彩空间设置来控制。当选取的颜色数量非常少时,"[-quantize](https://imagemagick.org/command-line-options/#quantize)"设置就变得尤为重要。为了演示这一点,让我们使用不同的色彩空间并定义不同的'颜色距离',来对标准的'colorwheel'(色轮)图像进行缩减。
for S in RGB CMY sRGB GRAY \
XYZ LAB LUV \
HSL HSB HWB \
YIQ YUV OHTA ; do \
magick colorwheel.png -quantize $S +dither -colors 16 \
-fill black -gravity SouthWest -annotate +2+2 $S \
colors_space_$S.gif; \
done
如你所见,选出的颜色很大程度上取决于色彩空间的组织方式。sRGB(红、绿、蓝)色彩立方体通常会使选出的颜色至少接近三原色。sRGB 色彩空间尤其擅长为卡通风格图像和图标选取颜色,但对于一般的照片类图像而言其实是个较差的色彩空间。CMY 色彩空间与 sRGB 色彩空间完全相同,因为在 sRGB 与 CMY 色彩空间之间转换时,颜色通道只是简单地取反。因此量化得到的颜色结果大致相同。 | _CMYK 色彩空间(未展示)出于不同的原因也会产生相同的结果。因为在内部,'K'通道与图像的'颜色映射表'使用同一个数据指针(参见调色板通道),IM 在量化之前会把它转换回 CMY。
---|---
sRGB 色彩空间正如预期那样产生了与 RGB 类似的结果,但经过了扭曲以减少色彩空间中接近黑色的颜色数量。因此色轮中心可供选择的颜色变少,产生了一块更大的'不那么黑'的斑点。XYZ 色彩空间也与线性 RGB 色彩空间非常相似。这里最大的区别在于色轴发生了偏移,以更好地涵盖我们能看到的(甚至通常看不到的)所有可能的颜色,因此色轮中的颜色数据被压缩得更紧密一些,结果量化后的颜色显得更加分散。LAB 和 LUV 色彩空间彼此基于不同但相似的色轴。这导致颜色量化的排布方式不同。涉及'色相'通道的特殊色彩空间,例如 HSL(色相、饱和度、明度)、HSB(色相、饱和度、亮度)和 HWB(色相、白、黑),都在其色彩空间中包含了循环的色轮表示。实际上,生成这个色轮正是用的 HSL 色彩空间。参见生成色轮。 | _在撰写本文时,IM 使用的颜色距离算法并未考虑色彩空间中'色相'的循环特性。这方面的算法非常不同。因此在'色相'环绕回到起点的'红色'路径上会出现明显的不连续,导致色彩量化过程中很少选中红色。
---|---
YIQ、YUV 旨在生成更自然的'柔和色调'和'中间调'颜色,更适合表现照片和现实世界的图像,特别是那些包含细微色调(尤其是肤色)的图像。Helmut Dersch 在他的网站上指出,进行扭曲变形时应考虑使用 LAB 色彩空间。 | _在旧版本的 IM(具体来说是 IM 5 版本)中,用于量化的色彩空间是通过"[-colorspace](https://imagemagick.org/command-line-options/#colorspace)"选项设置的。但在 IM 6 版本中,该运算符用于修改图像在内存中的存储方式,因此不再是色彩量化的设置项。
因此在 IM v6.2.8-6 中,提供了"[-quantize](https://imagemagick.org/command-line-options/#quantize)"设置来完成这项工作。不过它仅仅是"[-colors](https://imagemagick.org/command-line-options/#colors)"(色彩量化)处理过程的一个设置。对于使用"[-remap](https://imagemagick.org/command-line-options/#remap)"、"[-posterize](https://imagemagick.org/command-line-options/#posterize)"等运算符进行的颜色替换和抖动,或是其他各种抖动技术,它都不会产生任何作用。_
---|---
关于可用色彩空间的完整列表,请参见"[-colorspace](https://imagemagick.org/command-line-options/#colorspace)"运算符。你可以在随机纯色斑点的示例中看到色彩空间对颜色选择的更多影响。那里使用色彩量化,在各种色彩空间下缩减随机化图像的颜色数量。 量化不会保留颜色请注意,在上面所有的图像中,纯黑色实际上都没有被色彩量化选中。当然,图像中原本也只有一个纯黑像素,而且近黑色的颜色本来就不多。因此,最终图像中出现的黑色只是后来作为图像标注的一部分添加进去的。即便是'GRAY'色彩空间的图像,也没有产生纯黑色。事实上,没有一幅图像包含红、蓝、绿、青、洋红等任何一种三原色或二次色!唯一的例外是白色,因为这些图像确实包含相当多的纯白色,使其成为一种'优先颜色'(见下文)。不过这种情况并非 bug!首先,在上面的示例中,'black'这种颜色通常没有被选中,一般是因为原始图像中黑色本来就很少,所以色彩量化通常不太会去关注深色。实际上,由于浅色在图像中更为常见,它反而生成了更多浅色。具体示例请参见上一节。其次,由于量化试图选取与图像中现有像素数量最多相匹配的颜色,而要做到这一点,最好的办法恰恰是_不去_匹配'纯'原色或二次色,因为这些颜色总是位于所用色彩空间的最极端位置。相比'原色','偏色'往往能匹配更多的颜色,因此更常被选中。所以让我说清楚…… 色彩量化(" -colors")通常会避免选取原色!
从 IM 6.3 版本起,色彩量化功能经过修改,会尝试包含原始图像中非常常见的颜色。因此,如果图像中包含一大片单一颜色的区域(比如上面例子中的'white'),该颜色通常会被包含进最终的颜色映射表中。这在一定程度上改善了情况,对'卡通'风格的图像或纯色背景上的图像尤为如此。这种'纯'色通常会被选中,以帮助避免下文将要讨论的抖动斑点。在颜色映射表中保留特定颜色的解决办法 目前只有为数不多的几种方法,可以保证某个'特定颜色'确实会被包含进后续抖动所用的选定颜色中。一种方法是照常对图像进行量化,然后输出生成的颜色映射表(使用"[-unique-colors](https://imagemagick.org/command-line-options/#unique-colors)")。这样你就可以调整这个颜色映射表,使你要的特定颜色真正变成那个颜色。最后,你可以使用重映射颜色运算符,用提供的颜色映射表对图像进行抖动处理。这个颜色映射表可能不再是该图像的最佳选择,其他一些颜色可能也需要相应调整,但它会接近你想要的颜色映射表。另外,你也可以在使用"[-colors](https://imagemagick.org/command-line-options/#colors)"之前,先在图像中追加(从而扩大图像)大块想要保留的特定颜色色块。添加一大块特定颜色的'色样',会让该颜色在最终的颜色映射表中更容易被选中(同时其他所有颜色也会自动调整以更好地适配这个颜色映射表)。如果奏效,你添加的色样应当保持不变(不被抖动)。之后你可以裁剪图像以去掉添加的色样。如果不奏效,那么 IM 至少应该已经添加了一个接近你想要的'特定颜色'的颜色,这样在对原始图像使用重映射颜色之前,只需对生成的颜色映射表做少许调整即可。无论成功与否,如果你尝试过这个方法,请告诉我结果如何。 理想情况下,我希望能有一种方法,可以指定少量必须包含在最终颜色映射表中的特定颜色,然后让 IM 针对某个具体图像自动为颜色映射表中其余的颜色选取最佳方案。
色彩量化与透明度
ImageMagick 默认不仅会生成完全不透明的颜色,还会尝试生成半透明的颜色。这样一来,包含透明阴影或其他叠加效果的图像就不会丢失这些效果。不过从 IM v6.2.6 起,涉及透明度的色彩量化经过修改,会把所有完全透明的颜色都当作同一种颜色处理。这是一种线性调整,因此半透明的颜色也会被认为比完全不透明时彼此更接近。由于这一调整,IM 的色彩量化仍然会生成半透明颜色,但会更专注于图像中的不透明颜色,而较少关注完全透明的颜色。举例来说,这里我生成了一个彩虹渐变颜色的图像,图像顶部完全不透明,底部完全透明。我把图像显示在一个背景图案上,这样你就能看出图像到底有多透明。
magick xc:red xc:yellow xc:green1 xc:cyan xc:blue \
+append -filter Cubic -resize 100x100\! -size 100x100 \
gradient: -alpha off -compose CopyOpacity -composite alpha_gradient.png
magick alpha_gradient.png +dither -colors 256 alpha_colors_256.png
magick alpha_gradient.png +dither -colors 64 alpha_colors_64.png
magick alpha_gradient.png +dither -colors 15 alpha_colors_15.png
如你所见,当我们要求 IM 减少这幅图像所需的颜色数量时,它生成了更多的不透明颜色,而对更半透明的部分则使用了更少的高透明度颜色。结果是所选颜色分布得相当均匀,在颜色数量非常少的情况下尤为明显。然而正如上文所指出的那样,不仅原色不会被选中,出于完全相同的原因,完全透明的颜色也不会被选中。事实上,连完全不透明的颜色都不会被选中!换句话说,前面示例中经过色彩量化处理的图像里,每一种颜色都是半透明的。让我把这一点说清楚。
当涉及透明度时,IM 的色彩量化
可能不会选取任何完全不透明、甚至完全透明的颜色!
当然,从 IM v6.3 起,由于修复了'常见颜色'的 bug(参见上文的量化不会保留颜色),如果图像包含大量不透明颜色和完全透明颜色(这是常见情况),这种情况发生的可能性就降低了。由于某些图像可能包含大量半透明颜色,比如涉及烟雾或阴影效果的图像,你不妨先做一次试运行,确认最终结果图像中确实选中了一种完全透明的颜色。然后你可以把最接近透明的颜色映射为完全透明,自行进行重映射颜色处理。如果你确实希望在结果图像中同时保证完全不透明和完全透明的颜色都存在,可以对 alpha 通道进行归一化或对比度拉伸。举例来说,这里我使用"[-contrast-stretch](https://imagemagick.org/command-line-options/#contrast-stretch)"来确保主要选中的颜色变为不透明。不过对于更常见的情况,这样做可能有点过于生硬。
magick alpha_gradient.png +dither -colors 15 \
-channel A -contrast-stretch 10% alpha_colors_15n.png
对于不允许半透明颜色的 GIF 图像、不允许透明度的 JPG 图像,乃至无需量化即可正确保存的 PNG 图像来说,这都不是问题。只有在某些特殊情况下,当涉及大量半透明颜色的图像被强制进行减色时,这才会成为问题。请记住,对于 GIF 格式来说,保存半透明颜色是徒劳无功的。因此,如果你打算为这类图像格式自行进行色彩量化,就需要告诉 IM 在生成缩减后的颜色集时忽略图像的透明度。你可以通过使用特殊的"[-quantize](https://imagemagick.org/command-line-options/#quantize)"色彩空间设置'transparent'来做到这一点。
magick alpha_gradient.png -quantize transparent \
+dither -colors 15 alpha_colors_15qt.png
请注意,色彩量化完全忽略了颜色的透明度,也完全没有触及图像的 alpha 通道。这意味着你可以用更适合你图像的方式,完全独立于其他颜色地处理 alpha 通道。实际上,你可以在使用"[-colors](https://imagemagick.org/command-line-options/#colors)"之前或之后进行这项处理,都不会有问题,结果不会有任何差异。因此,当你打算把图像保存为布尔透明或不支持透明度的格式(例如 GIF 或 XPM 图像格式)并需要减色时,推荐使用这种量化色彩空间。如果你数一数生成的颜色数量,还会发现它恰好生成了所要求的颜色数目。因此,如果你还需要一种完全透明的颜色(很可能需要),就需要把"[-colors](https://imagemagick.org/command-line-options/#colors)"的参数至少减少一个,以便在图像最终的颜色表中留出空间。因此,要处理 GIF 文件格式 256 色颜色表的限制,你需要把颜色数减少到 255 而不是 256,为完全透明的颜色索引(由"[-transparent-color](https://imagemagick.org/command-line-options/#transparent-color)"设置定义)留出额外空间。对于更小的颜色表大小,请相应调整。当 IM 保存为 GIF 文件格式时,这种量化行为是自动的,但当你需要在生成全局或共享颜色表时自行进行 DIY 量化时,这一点就很重要。当然,你仍然需要处理半透明像素,使其符合你想要的图像效果。
FUTURE: 最后这部分内容大概会移到一个关于'alpha 通道抖动'
的新章节中,该章节会在不久后创建。并在这里添加对该
章节的引用。
下面是一些示例,展示如何只对 alpha 通道进行抖动,使其变为布尔值即开/关设置,而不影响图像中其余的颜色通道。
magick alpha_gradient.png \
-channel A -threshold 50% alpha_dither_threshold.gif
magick alpha_gradient.png \
-channel A -ordered-dither checks alpha_dither_checks.gif
magick alpha_gradient.png \
-channel A -ordered-dither o8x8 alpha_dither_ordered.gif
magick alpha_gradient.png \
-channel A -ordered-dither h8x8a alpha_dither_halftone.gif
magick alpha_gradient.png -channel RGBA -separate \
\( +clone -monochrome \) \
+swap +delete -combine alpha_dither_monochrome.gif
magick alpha_gradient.png -channel RGBA -separate \
\( +clone -dither FloydSteinberg -monochrome \) \
+swap +delete -combine alpha_dither_monochrome_fs.gif
magick alpha_gradient.png -channel RGBA -separate \
\( +clone -remap pattern:gray50 \) \
+swap +delete -combine alpha_dither_map.gif
magick alpha_gradient.png -channel RGBA -separate \
\( +clone -dither FloydSteinberg -remap pattern:gray50 \) \
+swap +delete -combine alpha_dither_map_fs.gif
| _在对 alpha 通道的副本进行抖动处理、以便用"[-monochrome](https://imagemagick.org/command-line-options/#monochrome)"或"[-remap](https://imagemagick.org/command-line-options/#remap)"来抖动时,请确保该图像是纯灰阶图像,而不是包含透明度的形状蒙版。否则,你很可能会因为 alpha 通道仍然存在而产生非线性的效果。
从图像中提取并恢复 alpha 通道作为灰阶蒙版以便进行抖动,有多种方法。上面使用了通道分离和合并来实现这一点。其他方法则使用alpha 提取配合CopyOpacity 合成。_
---|---
误差校正抖动
正如引言中所讨论的,误差校正抖动通常被认为是在使用缩减后的颜色集时,最能真实还原原始图像的最佳选择。它还会把自身限制在预先定义好的调色板范围内,无论这个调色板是用户提供的,还是由 IM 色彩量化例程确定的。正因如此,它是"[-colors](https://imagemagick.org/command-line-options/#colors)"、"[-remap](https://imagemagick.org/command-line-options/#remap)"、"[-posterize](https://imagemagick.org/command-line-options/#posterize)"和"[-monochrome](https://imagemagick.org/command-line-options/#monochrome)"这些 IM 运算符所提供的一般减色处理的逻辑默认选择。
E-Dither(误差抖动)方法
从 6.4.2-9 版本起,IM 现在提供了不止一种抖动风格或方法,可以通过"[-dither](https://imagemagick.org/command-line-options/#dither)"设置来选择。在此之前,IM 仅限于Riemersma 抖动(即希尔伯特曲线抖动)的一种变体,可以用"-dither Riemersma"来设置。现在你还可以使用"-dither FloydSteinberg"来选择 Floyd-Steinberg 抖动。你可以用以下命令查看你所用 IM 版本中实现了哪些抖动方法……
magick -list dither
举例来说,下面是使用不同抖动方法对色轮进行抖动处理的效果。
magick colorwheel.png -dither Riemersma -colors 16 dither_riemersma.gif
magick colorwheel.png -dither FloydSteinberg -colors 16 dither_floyd.gif
如你所见,Floyd-Steinberg 抖动产生的抖动图案比默认的 Riemersma 抖动更加均匀。两者之间最大的差异在于各自如何把'颜色误差'分配给相邻像素。那么现在来看看 E-Dither 到底是如何运作的。
E-Dither 的工作原理
改写工作进行中
IM 用于一般抖动处理的具体方法,是"希尔伯特曲线误差校正抖动"的一种变体。这实际上是一种非常好的抖动技术,定义得相当清晰,速度也相当快。完整的说明(以及一种非常相似的变体),请参见……Riemersma 抖动。基本上,图像中的每个像素都是按照一条被称为'希尔伯特曲线'的非常复杂的路径来查看的。该像素被赋予与其原始值最接近的颜色,而该像素原始颜色与所选颜色之间的任何差异都会被保存下来,并加到下一个像素(总是相邻的像素)的颜色值上,然后再次选取新的颜色。这样一来,所选颜色与图像原始颜色之间的任何色差,都会被分散到同一区域内的其他像素上。结果是,虽然最终图像只会被赋予特定的几种颜色,但该区域的整体基本色调仍会与原始图像非常接近。举例来说,这里有一幅小的灰色图像,我要求 IM 用一组不包含原始颜色的颜色对其进行抖动处理。结果图像被放大了,这样你就能看到每个像素被赋予的具体颜色。
magick -size 10x10 xc:'#999999' -scale 80x80 dither_not.gif
magick -size 10x10 xc:'#999999' \
-remap colortable.gif -scale 80x80 dither.gif
如你所见,由于原始图像的颜色不在指定的颜色映射表中,原始颜色是通过给定颜色表中最接近的三种颜色所构成的图案来近似表现的。 如果我们把上面抖动图案生成的颜色取平均值,会得到
这个颜色,它与图像原始平均颜色
非常接近,而这正是所生成的抖动图案的意义所在。然而,由于用于分配颜色的'路径'相当复杂(尽管通常仍局限在局部区域内),颜色的分配会产生一种本质上随机的图案。不过严格来说这并非真正的随机,因为同一幅图像会产生相同的图案,但结果基本上可以视为随机,或者至少是伪随机的。"F-S"抖动实际上只是自 20 世纪 70 年代初问世以来开发出的几种'光栅化 E-Dither'之一(而且是最早的一种)。它也可能是实现得最广泛的一种,尽管并不被认为是最好的一种。关于此类算法更完整的总结,请参见论文Dithering Algorithms。从 IM v6.4.3 起,它也直接在 IM 中提供,并且实现为按照从图像顶部到底部逐行的'蛇形'路径进行。
magick -size 10x10 xc:'#999999' -dither FloydSteinberg \
-remap colortable.gif -scale 80x80 dither_fs.gif
我发现,"Floyd-Steinberg 抖动"生成的像素图案比"希尔伯特曲线抖动"更像'杂点'状,而实际上它就是被设计成如此。这样一种规则的图案,能让对小型彩色图标图像进行低层次的手动清理工作变得容易得多。这正是我过去在制作Anthony's Icon Library时经常做的事,不过如今这类工作已经不太需要了,除非是针对小型单色图像。
E-Dither 的问题——对变化敏感
使用误差校正抖动时面临的最大问题之一,是你得到的像素图案本质上是随机的,而且对变化高度敏感。举例来说,这里拿原始的灰色图像,在再次抖动之前把其中一个像素替换成不同的颜色。结果是,沿着希尔伯特曲线抖动所走路径、位于该变化点之后的每一个像素,其抖动图案都发生了彻底的偏移。
magick -size 10x10 xc:'#999999' -draw 'fill #C28 point 2,2' \
-remap colortable.gif -scale 80x80 dither_modified.gif
magick compare dither.gif dither_modified.gif dither_difference.gif
原始抖动 |
一个像素的改动 |
|
改动对比
---|---|---|---
如你所见,仅仅在图像中添加一个像素,就导致抖动图案发生了剧烈变化!哪怕只改动一个比特,得到的图像也会变得不同,尽管图像整体的观感(在未放大的情况下)基本保持不变(而这正是一个好的抖动算法所追求的目标)。"magick compare"图像也展示了抖动图案变化的程度。在这个例子中,大约 80% 的像素被赋予了完全不同的颜色。在希尔伯特曲线抖动中,单个像素的改动实际上会导致其后所有的像素都有可能不同,也就是说抖动图案中可能有 0% 到 100% 的部分发生变化。这具体取决于变化发生在复杂的希尔伯特曲线的哪个位置。而 Floyd-Steinberg 抖动只沿一个方向遍历图像,因此单个像素的改动只会影响变化点一侧的图案。
magick -size 10x10 xc:'#999999' -draw 'fill #C28 point 2,2' \
-dither FloydSteinberg -remap colortable.gif \
-scale 80x80 dither_fs_modified.gif
magick compare dither_fs.gif dither_fs_modified.gif dither_fs_difference.gif
FS 抖动 |
一个像素的改动 |
|
改动对比
---|---|---|---
如你所见,它同样存在完全一样的问题。单个像素的改动几乎会使该像素之后处理的图像区域(也就是从那一行往下)的抖动图案发生完全不同的变化。对于单张图像而言,抖动出的颜色图案本身并不重要,因为该图案的平均颜色应该会让图像该区域呈现出恰当的颜色。但是,当你处理的是一段动画,其中一幅图像后面跟着非常相似的其他图像,而且存在大面积恒定颜色的区域时,不断变化的抖动图案就会变得非常明显,并作为一种低层次的背景'噪声'而令人厌烦。举例来说,这里我生成了一段包含 3 幅图像的动画,它们使用相同的抖动颜色,但每一帧中都有一个像素被改动。我还放大了中心区域,让你能更清楚地看到这种变化的图案。
magick -size 80x80 xc:'#999999' \
\( +clone -draw 'fill #C28 point 2,2' \) \
\( +clone -draw 'fill #28C point 2,2' \) \
-remap colortable.gif -set delay 50 -loop 0 dither_anim.gif
magick dither_anim.gif -crop 10x10+40+40 +repage \
-scale 80x80 dither_anim_magnify.gif
| 如你所见,由 E-Dither 产生的伪随机性会让图像背景产生一种类似搅动的效果。在大多数情况下,所用的颜色足够接近,以至于这种'抖动噪声'并不明显。但当抖动颜色明显不同时(在这个例子中是通过强制使用颜色映射表造成的),这肯定会成为一个问题。关于展示这种'抖动噪声'的更实际的动画示例,请参见视频颜色优化。图案的变化也会给动画优化带来问题。也就是说,不同的图案会导致简单的帧优化无法缩减帧叠加层的大小。关于一种解决方案,请参见模糊颜色优化,不过它只在搅动使用的是非常相似的颜色时才有效。 | 与其他抖动方法(例如阈值和有序抖动)不同,"[-channel](https://imagemagick.org/command-line-options/#channel)"设置不会影响色彩量化或误差校正抖动。基本上,在这些图像操作的运作机制中,它根本没有用武之地。 |
|---|---|
| 有序抖动没有这些问题,它把变化局限在改动点紧邻的局部区域内。遗憾的是,有序抖动通常也仅限于使用数学推导出的颜色集(参见下文的使用均匀颜色映射表的有序抖动)。 |
E-Dither 的像素斑点问题
E-Dither 的另一个问题,是它可能会在原本颜色相当均匀的区域中,偶尔产生一些颜色奇怪的像素。例如,灰阶图像中偶尔出现绿色像素,或者像下面的例子那样,原本纯净的平面蓝色区域中出现白色像素。这种情况在大图像中尤其容易发生,这类图像中既包含颜色数量众多的对象,又包含纯色不变的其他区域。这在图表和绘图中尤其典型,因为常常会有彩色对象叠加在平面颜色背景之上。你可以在上面测试示例的放大图中看到这样一个颜色奇怪的像素:在离那个微小的单像素改动相当远的地方,多出了一个淡紫色的像素。上面添加的那个奇怪颜色的像素并不算特别显眼,而且颜色映射表对图像的覆盖相当不错,所以那个奇怪的像素与用于抖动图像的通常三种颜色相当接近。为了给出一个更极端的例子,这里我用了一幅模糊的渐变背景图,并把颜色大幅缩减到 64 种,以真正给误差校正抖动施加压力。 |
magick -size 100x60 xc:SkyBlue \
-fill DodgerBlue -draw 'circle 50,70 15,35' \
-fill RoyalBlue -draw 'circle 50,70 30,45' \
-blur 0x5 -colors 64 speckle_gradient.gif
![[IM Output]](../static/img/quantize/speckle_gradient.gif)
如你所见,即便使用这个大幅缩减的颜色映射表,误差校正抖动依然相当好地呈现了原始的渐变效果。但如果我们在上图中加入一块纯白色区域…… |
magick -size 100x60 xc:SkyBlue \
-fill DodgerBlue -draw 'circle 50,70 15,35' \
-fill RoyalBlue -draw 'circle 50,70 30,45' -blur 0x5 \
-fill white -draw 'rectangle 40,40 60,55' \
-colors 64 speckle_problem.gif
![[IM Output]](../static/img/quantize/speckle_problem.gif)
可以看到,E-Dither 突然开始在图像上方原本没有白色像素的区域,散布出一些星星点点的白色像素。 这里放大一小块区域,让你能更清楚地看到这些像素…… |
magick speckle_problem.gif -crop 15x15+75+0 +repage \
-scale 90x90 speckle_prob_mag.gif
![[IM Output]](../static/img/quantize/speckle_prob_mag.gif)
这个奇怪颜色的像素是由两个因素造成的。首先,色彩量化被迫在图像最终的颜色映射表中包含了一种纯白色(但没有包含其他白蓝过渡的抗锯齿颜色),从而使抖动过程能够使用这种额外的颜色。但由于 E-Dither 会缓慢地累积误差,尤其是在颜色极端的区域(例如上图上方区域)更是如此。最终,误差会累积到足够大的程度,使得那一个额外的颜色成为最接近的匹配。因此,每隔一段时间,就会在一个伪随机的位置输出一个对比度极强的白色像素,以'纠正误差'。结果就是白色像素形成的一层非常淡的斑点。误差累积得越慢,这些白色像素就分布得越分散,也就显得越不协调。最佳解决方案是切换到另一种没有有限颜色表限制的图像格式。例如,把你的 GIF 格式图像转换为 PNG。这样就无需进行色彩量化(减色),因而也就无需对缩减后的颜色进行抖动。次优的解决方案是,把使用 E-Dither 换成某种能把误差'局部化'的其他抖动方法,例如有序抖动。不过目前在 IM 中要应用这种方法并不容易。关于一种这样的方法,在找到更通用的方案之前,请参见有序抖动的更好效果。如果切换到另一种图像格式或使用不同的抖动方法都不实际(而且往往确实不实际),那么就只能尝试针对这张具体的图像来改善这种状况了。最好的修补办法,是想办法确保在导致 E-Dither 误差累积的那一大片颜色群组的边缘之外,存在其他颜色。然而,普通的色彩量化并不会这样做。它倾向于挑选一组代表各个颜色群组的平均颜色。这里需要的,是能像'栅栏'一样围住大片颜色群组边缘的额外颜色,而不是简单的平均颜色。举例来说,这里我用了一个圆形而不是正方形,这样不仅加入了纯白色,还加入了若干白蓝过渡色。这些颜色是由于圆形边缘的抗锯齿处理而自动加入的,目的是让边缘看起来更平滑。 |
magick -size 100x60 xc:SkyBlue \
-fill DodgerBlue -draw 'circle 50,70 15,35' \
-fill RoyalBlue -draw 'circle 50,70 30,45' -blur 0x5 \
-fill white -draw 'circle 50,45 40,40' \
-colors 64 speckle_fixed.gif
magick speckle_fixed.gif -crop 15x15+85+0 +repage \
-scale 90x90 speckle_fix_mag.gif
![[IM Output]](../static/img/quantize/speckle_fix_mag.gif)
如你所见,这些额外的颜色在蓝青渐变的外侧紧邻处提供了额外的颜色。虽然这些额外的颜色意味着实际渐变可用的颜色变少了,但它们提供了其他蓝白色调,使 E-Dither 能够在累积误差变得过大之前更早、更频繁地自我纠正。也就是说,这并不能说我们防止了 E-Dither 斑点的产生,只是为抖动算法提供了更好的可用颜色。如果你仔细观察图像放大的部分,仍然能看到斑点图案,但这些颜色更接近背景色,而且数量也多得多,产生了分布更均匀的斑点。另一种方法是生成我们自己的颜色表,或许基于 IM 生成的颜色表,并添加适当的颜色以防止误差累积。不过这并非易事,尤其是在三维色彩空间中。就这个特定的图像示例而言,防止'斑点'的一种方法是,先单独生成并抖动背景,使用比实际需要略少一些的颜色,然后再叠加白色方块及其额外颜色。 |
magick -size 100x60 xc:SkyBlue \
-fill DodgerBlue -draw 'circle 50,70 15,35' \
-fill RoyalBlue -draw 'circle 50,70 30,45' -blur 0x5 \
-colors 63 \
-fill white -draw 'rectangle 40,40 60,55' speckle_perfect.gif
![[IM Output]](../static/img/quantize/speckle_perfect.gif)
这样会在图像中添加一种'白色',但由于在使用误差校正抖动时白色尚不可用,背景不会产生任何斑点效果。结果得到一幅恰好包含 64 种颜色、完全没有斑点的图像。不过这非常依赖于具体图像以及你想要达成的效果,因此并非解决斑点问题的通用方案。
除了添加额外颜色之外,另一种更通用的替代方法是尝试从最终抖动后的图像中去除斑点,也就是以某种方式对图像进行清理。然而这本身也是个棘手的问题,因为你并不想去掉属于正常抖动图案一部分的像素。我们需要找出那些与周围所有颜色都有某种明显差异、但同时又与所有其他类似颜色保持一定距离、相当孤立的颜色像素。 你有更好的图像滤镜解决方案吗? 小结 对我来说,斑点是个相当恼人的问题,尤其是对于使用非常有限的颜色表的桌面图标图像而言。我自己就经常编辑较小的'图标'图像,以去除斑点或修复其他一些抖动效果,比如垂直条纹。如果你知道更好的解决方案,请告诉我。
单色抖动位图图像
"[-monochrome](https://imagemagick.org/command-line-options/#monochrome)"运算符是"[-colors](https://imagemagick.org/command-line-options/#colors)"运算符的一种特化形式,用于生成位图图像。正因如此,它不仅是演示'希尔伯特曲线抖动'的理想运算符,也非常适合用来更仔细地观察颜色选择过程。下面是一个典型的示例。 |
magick logo.png -monochrome monochrome.gif
![[IM Output]](../static/img/quantize/monochrome.gif)
该运算符仅根据灰阶亮度的'强度'或'级别'对图像进行抖动处理,不过它并不会直接对整个灰阶范围进行抖动,而是把最极端的值阈值化为其最大值。我们可以通过让 IM 对一幅渐变图像进行抖动来验证这一点。
magick -size 15x640 gradient: -rotate 90 \
-monochrome monochrome_gradient.gif
如你所见,这个渐变图像只有中间大约 50% 的颜色被"[-monochrome](https://imagemagick.org/command-line-options/#monochrome)"运算符抖动处理了。特别感谢 Ivanova flamingivanova@punkass.com 指出 IM 工作方式中这一有趣的事实。如果你想在整个灰阶范围内进行抖动处理,可以使用"[-remap](https://imagemagick.org/command-line-options/#remap)"运算符,配合一个纯黑白颜色映射表(由内置的图案图像提供)。 |
magick logo.png -remap pattern:gray50 mono_remap.gif
magick -size 15x640 gradient: -rotate 90 \
-remap pattern:gray50 mono_remap_gradient.gif
通过使用"[-remap](https://imagemagick.org/command-line-options/#remap)"更细致地挑选颜色,你可以有效地生成与"[-monochrome](https://imagemagick.org/command-line-options/#monochrome)"运算符相同的'阈值'范围,或是你喜欢的任何其他阈值范围。 |
magick xc:gray20 xc:white +append ctrl_colors.gif
magick logo.png -colorspace Gray \
-remap ctrl_colors.gif -normalize mono_remap_ctrl.gif
magick -size 15x640 gradient: -rotate 90 \
-remap ctrl_colors.gif -normalize mono_remap_grad_ctrl.gif
"[-monochrome](https://imagemagick.org/command-line-options/#monochrome)"实际上所做的,是先把给定的图像转换为灰阶图像,然后执行一次双色的色彩量化,以决定用于抖动图像的阈值颜色。这正是下一节示例要探讨的内容。 |
"[+dither](https://imagemagick.org/command-line-options/#dither)"设置目前对"[-monochrome](https://imagemagick.org/command-line-options/#monochrome)"的结果没有影响。不过这一点日后可能会改变,所以在脚本中使用这个运算符时,请确保没有把它关闭。 |
|---|---|
二色量化
你可以不自己挑选两种控制颜色,而是使用 "[-colors](https://imagemagick.org/command-line-options/#colors)" 运算符,通过颜色量化来挑选图像中最佳的两种颜色。 |
magick logo.png -colors 2 -colorspace gray -normalize \
colors_monochrome.gif
![[IM Output]](../static/img/quantize/colors_monochrome.gif)
不过结果与使用 "[-monochrome](https://imagemagick.org/command-line-options/#monochrome)" 并不相同,因为我们没有先把图像转换为灰度。图像是直接在所选的两种非灰色值之间进行抖动的。也就是说,选出的是最适合用来抖动图像的两种颜色,而不是两种灰度亮度级别。因此,对于只使用大致相同灰度"级别"颜色的图像,它会产生更好的结果。例如,这里我们对一幅红蓝渐变图像使用 "[-colors](https://imagemagick.org/command-line-options/#colors)" 以及 "[-monochrome](https://imagemagick.org/command-line-options/#monochrome)" 位图抖动运算符。可以看到,两者的结果并不相同。
magick -size 20x640 gradient:red-blue -rotate 90 gradient_rb.png
magick gradient_rb.png -colors 2 -colorspace gray \
-normalize colors_threshold.gif
magick gradient_rb.png -monochrome mono_threshold.gif
上面的 "[-monochrome](https://imagemagick.org/command-line-options/#monochrome)" 运算符未能找到任何可用于位图抖动的差异,因为蓝色和红色的强度几乎相同。然而使用 "[-colors](https://imagemagick.org/command-line-options/#colors)" 量化方法,找到可接受的抖动颜色对却毫无问题。你还可以看到,只有颜色的中间部分被抖动了。这是因为颜色量化选取的是所选两个颜色"簇"中间的颜色。所选颜色"外侧"的颜色因而被直接阈值处理为该颜色,不做抖动。这说明量化色彩空间外侧的颜色不会被抖动,尽管这个事实很难以实际方式加以利用。在量化之前把 "[-colorspace](https://imagemagick.org/command-line-options/#colorspace)" 设为灰度,就能重现 "[-monochrome](https://imagemagick.org/command-line-options/#monochrome)" 运算符的内部操作。 |
magick logo.png -colorspace gray -colors 2 -normalize \
monochrome_equivelent.gif
![[IM Output]](../static/img/quantize/monochrome_equivelent.gif)
最后,通过关闭抖动,与使用固定的 "[-threshold](https://imagemagick.org/command-line-options/#threshold)" 设置相比,可以更自动地将图像中的颜色分离开来。 |
magick logo.png -colorspace gray +dither -colors 2 -normalize \
threshold_two_grays.gif
![[IM Output]](../static/img/quantize/threshold_two_grays.gif)
| 请记住,"[-monochrome](https://imagemagick.org/command-line-options/#monochrome)" 目前会忽略 "[+dither](https://imagemagick.org/command-line-options/#dither)" 设置,因此不能只靠该运算符来做"智能阈值化"。
---|---
如果在图像处理的颜色量化阶段去掉 "[-colorspace](https://imagemagick.org/command-line-options/#colorspace)",就可以基于该图像可能达到的最佳颜色分离(而不是灰度色彩分离)来对图像进行阈值化。 |
magick logo.png +dither -colors 2 -colorspace gray -normalize \
threshold_two_color.gif
使用预定义颜色映射表进行抖动
如上所示,"[-colors](https://imagemagick.org/command-line-options/#colors)" 会尝试挑选一组有限的最优颜色来表现图像。而使用 "[-remap](https://imagemagick.org/command-line-options/#remap)",无论你打算对这些颜色进行抖动,还是仅用最邻近的颜色替换,都可以向 IM 提供你想要用于该图像的最终颜色集。该参数以一幅包含你想使用的所有颜色的图像形式给出。如果你想把一幅色彩繁多的大图像缩减为其颜色列表,可以使用 "[-unique-colors](https://imagemagick.org/command-line-options/#unique-colors)",再保存起来供之后 "[-remap](https://imagemagick.org/command-line-options/#remap)" 使用。 | _请注意,虽然 "[-remap](https://imagemagick.org/command-line-options/#remap)" 运算符可以接受任意图像作为映射表使用,但请不要为此使用 JPEG 图像,否则由于其"有损压缩"会产生大量额外颜色。
另一方面,利用 JPEG 产生额外颜色,说不定反而有助于解决之前提到的"斑点"问题!_
---|---
例如,这里我们把 IM 徽标使用的颜色限制为一张预定义的、命名的 X window 颜色映射表。默认使用 'Riemersma' 抖动,但从 IM v6.4.4 起,"[-dither](https://imagemagick.org/command-line-options/#dither)" 已扩展为可以选择其他抖动方式,例如 'FloydSteinberg'。当然,你仍然可以使用 "[+dither](https://imagemagick.org/command-line-options/#dither)" 选项关闭抖动。
magick logo.png -dither None -remap colortable.gif remap_logo_no.gif
magick logo.png -dither Riemersma -remap colortable.gif remap_logo_rm.gif
magick logo.png -dither FloydSteinberg \
-remap colortable.gif remap_logo_fs.gif
可以看到,IM 尝试仅用给定的颜色合理地表现图像,但结果远不如让 IM 自行挑选所用颜色集时那样好。不过,这张 "[colortable.gif](../static/img/images/colortable.gif)" 图像最初的设计目的并不是用于对图像抖动,而是作为在更老、更原始的 X window 彩色显示器上设计卡通风格彩色图标的颜色集(详情参见 Anthony's X Window Icon Library 和 AIcons Color Selection)。另外请注意,最终图像并未用到这张映射表提供的全部 32 种颜色,不过在启用某种抖动时(分别见
和
),会比关闭抖动时(
)用到映射表中更多的颜色。最后这个例子表明,挑选一份好的颜色映射表有多么重要。因此,除非有更紧迫的理由不这样做,我建议你让 IM 用 "[-colors](https://imagemagick.org/command-line-options/#colors)" 运算符优化图像所用的颜色选择,再根据需要进行修改。还有一点:虽然你可以为 "[-colors](https://imagemagick.org/command-line-options/#colors)" 指定用于寻找最佳颜色集的色彩空间,但目前无法为颜色映射或抖动阶段定义色彩空间。我做的所有实验似乎都表明,颜色集的应用(无论是误差校正抖动还是最近颜色替换)都是基于 RGB 空间进行的。"[-quantize](https://imagemagick.org/command-line-options/#quantize)" 色彩空间设置只用于颜色选择,不用于映射。既然使用颜色映射表是个如此糟糕的主意,那为什么还要用它呢?原因有不少,通常是因为你需要对图像所用的具体调色板有更精细的控制。还有一位用户把颜色映射表单独提取出来,以便在 Risograph(一种数码印刷系统)上使用。如果你知道下面未提到的其他使用 "[-remap](https://imagemagick.org/command-line-options/#remap)" 运算符的理由,请告诉我。
通用(或"最佳")颜色映射表
处理多幅图像时的另一种技术是为所有相关图像生成一张通用的颜色表。基本做法是把所有图像连接成一幅大图像,然后用 "[-colors](https://imagemagick.org/command-line-options/#colors)" 运算符找出一张适用于所有图像的良好颜色映射表。得到这张颜色映射表图像后,就可以用它,借助刚生成的预定义颜色映射表,对每幅原始图像重新着色。或者,你也可以使用特殊的 "[+remap](https://imagemagick.org/command-line-options/#remap)" 运算符,它对 255 色的颜色映射表做同样的事:统计颜色数量,进行颜色量化以生成一张良好的通用颜色映射表,然后按需对图像进行抖动以使用该映射表。不过,"[-remap](https://imagemagick.org/command-line-options/#remap)" 和 "[+remap](https://imagemagick.org/command-line-options/#remap)" 这两种形式对 GIF 动画都有一个非常重要的特性:它们会把所有图像转换为图像 "[-type](https://imagemagick.org/command-line-options/#type)" 为 'Palette',且所有图像共用同一套颜色调色板。原因在于,写入 GIF 图像时,第一幅图像的调色板会被用作该文件格式的"全局颜色映射表"。之后每写入一幅图像时,它会发现这些图像使用的是同一组颜色,因此不会创建"局部颜色映射表"。这样最终 GIF 文件中每一幅图像最多可以节省 256 × 3,即 768 字节的颜色映射表空间。只有 "[-remap](https://imagemagick.org/command-line-options/#remap)" 这一系列运算符能做到这一点。因此在处理 GIF,尤其是 GIF 动画时,这是一个值得记住的重点。更多细节和示例,请参见 GIF 动画、全局颜色表。
Web 安全色
在 WWW 刚问世时,计算机显示器可用的颜色范围有限,网页浏览器通常也为图像使用一套更简单的颜色集。因此,把图像重新着色为这套颜色集是很常见的做法,既能减小图像体积,又能保证在用户的浏览器上看起来正常。更多细节参见 Web Style Guide, Dithering。为此,IM 提供了一张内置的颜色映射表图像,即这张特殊的 216 色表,称为 "netscape:"。那么让我们看看,用这些颜色时,我们的测试图像在老式网页浏览器显示器上会是什么样子。
magick logo.png -remap netscape: remap_netscape.gif
magick logo.png +dither -remap netscape: remap_netscape_nd.gif
这套颜色集是由显示器和计算机工程师、而非美术设计师通过数学方法确定的调色板,虽然它足够通用,对照片这类真实图像效果尚可,但对于包含大面积纯色区域的图像——例如徽标、背景、图表这类计算机生成图像,以及卡通风格图像——效果很差。基本上,它在颜色高度多变的区域表现良好,但对于大面积恒定颜色的区域,(通常)会应用三种颜色的抖动,比如上面 IM 徽标测试图像中那件偏蓝衬衫的颜色。换句话说,如果你在为网页设计图像或徽标,通常会尽量在大面积纯色区域使用这套调色板中的颜色,只在颜色深浅有变化的区域使用抖动颜色。上述命令可以让你测试自己的图像在更原始的计算机显示器上会是什么样子,并编辑图像以使用这些颜色,从而使其显示效果良好。这对图标和导航图像尤其重要。当然,如今得益于游戏和网页用户的需求,可以相当确定大多数用户使用的是不再受那些老旧颜色限制的现代显示器,不过这种"Web 安全调色板"的使用仍然存在,因为它还有其他好处,比如图像压缩。关于现代环境下使用 Web 安全色的讨论,请参见 Death of the Web-safe Color Palette?,以及可能更重要的、最早提出这张颜色映射表的平面设计师 Lynda Weinman 的看法。
生成颜色映射表
为任意图像,或某组特定图像确定一张良好的颜色映射表,可能非常重要。这在处理将用于 GIF 动画的一系列图像时尤为重要。基本上,你希望做到只需要一张颜色表就能用于动画的所有帧,而不是每一帧都要单独一张颜色表。换句话说,你希望所有图像使用同一张颜色映射表。这种情况下你实际上只有两种选择:要么尝试创建一张对任意图像都适用的颜色映射表,要么针对你要应用的特定图像组去优化一张颜色映射表。
Web 安全颜色映射表
第一种方法通常是通过数学方式生成颜色映射表,例如 IM 内置的 "netscape:" 颜色映射表。它提供 216 种颜色,可以很好地放入 GIF 格式 256 色的限制中,同时仍留有空间处理图像透明度,甚至可以为特殊用途(如阴影或文字叠加)添加一些额外颜色。这张颜色映射表是通过为三个颜色通道各生成 6 个色阶而生成的,产生 6×6×6 种颜色,即 216 种颜色——即所谓"野兽的数字"。由于实际只用到 219 种颜色,(对 GIF 图像而言)仍有空间为特定用途在颜色映射表中添加更多颜色,例如透明色,或更多灰度色阶。老版本的 Macintosh 版 Web 安全映射表实际上正是这样做的,试图改善整体效果,但仅在 Macintosh 网页客户端上使用。得益于其简单性以及在万维网上的广泛使用,这大概是目前最常见的"均匀"(即数学推导得出的)颜色映射表。
均匀 332 颜色映射表
另一种常用的均匀颜色映射方式是"332 RGB 颜色映射表"。这个数字指的是在一个 8 位颜色索引中,用于表示每种颜色所用的位数。也就是红色 3 位(即 8 个色阶),绿色 3 位,而由于人眼对蓝色不太敏感,蓝色只用 2 位(即 4 个色阶)。这样一共是 3+3+2 位,即一个 8 位颜色索引,也就是 256 种颜色,正好适合有限的 GIF 颜色表。但这样就没有空间留给 GIF 透明色或其他特殊用途的颜色了。下面介绍一种让 IM 生成这张颜色映射表的方法…… |
magick -size 16x16 xc: -channel R -fx '(i%8)/7' \
-channel G -fx '(j%8)/7' \
-channel B -fx '((i>>3&1)|(j>>2&2))/3' \
-scale 600% colormap_332.png
![[IM Output]](../static/img/quantize/colormap_332.png)
| 位移运算符 '>>' 和 '<<' 在 IM 6.2.9-2 版本之前一直未被 "[-fx](https://imagemagick.org/command-line-options/#fx)" 运算符支持。
---|---
实现同样效果更简单的方法,是使用 "-ordered-dither threshold,8,8,4" 这一操作,即使用均匀色阶的有序抖动(参见该示例部分)。这比上面的 DIY FX 方法简便快捷得多,而且还能让你使用其他内置抖动映射表来更好地处理渐变。这张映射表唯一的缺点是完全没有提供任何"灰色"。不过,在使用抖动时,这个缺点反而可能成为优点:细微的颜色差异会削弱灰度渐变中色彩边界变化带来的影响,使画面看起来更加平滑一些。
TrueColor 16 位颜色映射表
与上述"332 颜色映射表"类似的一种均匀颜色映射表,被 X windows 用在一种很少使用的 16 位视觉类中。此时,颜色索引使用 16 位,分为红色 5 位、绿色 5 位、蓝色 6 位。换句话说,这张颜色映射表更像是"556 颜色映射表",最好使用 'threshold' 抖动映射表的使用均匀色阶的有序抖动来实现,具体操作为 "-ordered-dither threshold,32,32,64"。不过,由于使用颜色映射表的图像通常需要 8 位颜色表,16 位颜色映射表很少见到。因此本文不再进一步讨论。
经过 Gamma 校正的均匀颜色映射表
目前,IM 并不直接支持经过 Gamma 校正的颜色映射表。你应该做的是(假设你使用的是编译时品质为 Q16 或更高的 IM 版本),在进行抖动之前,先把图像从 sRGB 或图像本身所具有的任何 Gamma 级别转换为线性 RGB 模型。这一点对许多其他图像处理操作也同样适用,比如缩放、模糊等。示例请参见带 Gamma 校正的缩放。
色调分离,使用均匀颜色映射表重新着色
该运算符最初的用途(参数为 '2' 时)是仅用 8 种基本颜色对图像重新着色,就好像图像是用一种简单、廉价、只用基本颜色的海报印刷方式生成的一样。这个运算符的名字(posterize,色调分离/海报化)也由此而来。"[-posterize](https://imagemagick.org/command-line-options/#posterize)" 运算符实际上是一种特殊的颜色缩减运算符,它根据为图像各颜色通道给定的颜色"级数"生成一张颜色映射表,并使用误差校正抖动对图像进行抖动。
magick netscape: -scale 50% +dither -posterize 2 posterize_2_ns.gif
magick netscape: -scale 50% +dither -posterize 3 posterize_3_ns.gif
magick netscape: -scale 50% +dither -posterize 6 posterize_6_ns.gif
可以看到,"[-posterize](https://imagemagick.org/command-line-options/#posterize)" 参数为 '2' 意味着每个颜色通道只提供 2 种颜色,对于上面这种 3 通道 RGB 图像,就会生成一张仅含 8 种颜色的映射表。基本上,它会用这 8 种阈值颜色对图像重新着色。参数为 '3' 时,会基于一张包含中间调颜色在内的 27 色映射表来映射图像颜色;参数为 '4' 时会生成一张 64 色颜色表;'5' 则生成一张 125 色颜色映射表。当然,如上所述,参数为 '6' 时会重现与内置 "netscape:" 图像所提供的相同的那套 216 色。请注意,"[-posterize](https://imagemagick.org/command-line-options/#posterize)" 参数为 '0' 或 '1' 是没有意义的,在最新的 IM 版本中只会把图像转换为纯黑色(虽然合乎逻辑,但毫无用处)。其结果是,图像使用了一张经数学推导得出、即"均匀"的颜色映射表重新着色。在渐变图像上可以更清楚地看到这一点,它会产生均匀分布的色调分离灰度级别。
#magick -size 20x640 gradient: -rotate 90 gradient.png
magick gradient.png +dither -posterize 5 posterize_gradient.gif
例如,让我们对 IM 徽标图像做几种不同级别的色调分离………
magick logo.png +dither -posterize 2 posterize_logo.gif
magick logo.png -posterize 2 posterize_logo_dither.gif
magick logo.png -posterize 6 posterize_6_logo.gif
作为更好的测试,让我们对带阴影的 "colorwheel" 图像做色调分离。
magick colorwheel.png +dither -posterize 2 posterize_2_cw.gif
magick colorwheel.png +dither -posterize 3 posterize_3_cw.gif
magick colorwheel.png +dither -posterize 6 posterize_6_cw.gif
下面是启用抖动后的同一组效果…
当然,我们在下一节将要讲到的许多位图抖动方法,也可以用各种不同的抖动风格生成 2 级有序抖动。但很少有方法能使用更多的灰度级别。从 IM v6.2.9 起,有序抖动由于目前在使用均匀颜色映射表抖动方面的限制,也算是一种色调分离方法。不过,与 "[-posterize](https://imagemagick.org/command-line-options/#posterize)" 生成的伪随机抖动相比,它的抖动图案更加均匀,且风格选择更丰富。将下面这些结果与上面经过抖动的 "[-posterize](https://imagemagick.org/command-line-options/#posterize)" 版本进行比较。
magick colorwheel.png -ordered-dither o8x8,2 posterize_2_od.gif
magick colorwheel.png -ordered-dither o8x8,3 posterize_3_od.gif
magick colorwheel.png -ordered-dither o8x8,6 posterize_6_od.gif
'threshold' 抖动映射表(取代上面使用的 'o8x8')实际上会把 "[-ordered-dither](https://imagemagick.org/command-line-options/#ordered-dither)" 变成一种不带抖动的色调分离方法。最后,有序抖动允许你为每个颜色通道单独指定不同的色阶数。这是 "[-posterize](https://imagemagick.org/command-line-options/#posterize)" 运算符目前所不允许的。
阈值抖动方法
阈值图像
将图像转换为黑白位图(颜色)图像最简单的方法,是使用 "[-threshold](https://imagemagick.org/command-line-options/#threshold)"。这实际上是一个简单的数学运算符,只需提供一个分界值:等于或低于该值的一切都变为黑色,而大于该值的都变为白色。
magick logo.png -threshold -1 threshold_0.gif
magick logo.png -threshold 25% threshold_25.gif
magick logo.png -threshold 50% threshold_50.gif
magick logo.png -threshold 75% threshold_75.gif
magick logo.png -threshold 100% threshold_100.gif
可以看到,值为 '-1' 会把所有颜色变为白色,而 '100%' 会把所有颜色转换为黑色。'50%' 当然是最常用的值。值为 '0' 是一种特殊情况,它会把所有非纯黑色变为白色。当然,如果图像中没有纯黑色,那么结果只会是一张纯白图像! |
magick logo.png -threshold 0 threshold_black.gif
![[IM Output]](../static/img/quantize/threshold_black.gif)
如果你确实想把所有非纯白色变为黑色,那么我建议对取反后的图像做阈值处理,而不是去费力算出正确的阈值(即比 IM 当前 'MaxRGB' 小 1 的值,而这个值取决于你所使用 IM 具体编译时的品质,即 'Q'设置)。 |
magick logo.png -negate -threshold 0 -negate threshold_white.gif
![[IM Output]](../static/img/quantize/threshold_white.gif)
"[-threshold](https://imagemagick.org/command-line-options/#threshold)" 运算符可以归为一种终极的"对比度"运算符,它通过阈值级别最大化颜色间的差异。不过,它是一个灰度运算符,也就是说可以用 "[-channel](https://imagemagick.org/command-line-options/#channel)" 设置来调整该运算符所应用的颜色通道。例如,你可以分别对图像各个颜色通道做阈值处理,产生与不带抖动的 2 级 "[-posterize](https://imagemagick.org/command-line-options/#posterize)" 操作相同的效果。 |
magick logo.png -channel R -threshold 50% \
-channel G -threshold 50% \
-channel B -threshold 50% threshold_posterize.gif
![[IM Output]](../static/img/quantize/threshold_posterize.gif)
| 请注意,"[-threshold](https://imagemagick.org/command-line-options/#threshold)" 把图像中的任何透明度都当作蒙版(matte)通道处理,而非 alpha 通道(这正是 IM 内部存储的方式)。因此,如果你打算把该运算符应用于 alpha 通道,需要格外小心。更多细节请参见蒙版通道。
---|---
对于更自动化的阈值处理技术,可以使用前面介绍过的二色量化技术。例如,下面这样会基于图像中找到的最佳两种颜色对图像进行阈值处理。这两种颜色未必是灰度色或互补色,只是最能代表整幅图像的两种颜色。然后(借助 "[-normalize](https://imagemagick.org/command-line-options/#normalize)")把这两种颜色映射为纯黑和纯白。 |
magick logo.png +dither -colors 2 -colorspace gray -normalize \
threshold_two_color.gif
随机抖动与阈值处理
"[-random-threshold](https://imagemagick.org/command-line-options/#random-threshold)" 运算符是位图图像转换器的一种特殊形式。在这种情况下,它使用一种非常简单的"随机抖动"来决定某个像素应变为白色像素还是黑色像素。与 "[-threshold](https://imagemagick.org/command-line-options/#threshold)" 或 "[-monochrome](https://imagemagick.org/command-line-options/#monochrome)" 运算符、甚至上一节中的各种变体不同,所选通道(通过 "[-channels](https://imagemagick.org/command-line-options/#channels)" 设置)不会被合并为单一灰度通道再作为一个整体进行抖动。相反,"[-random-threshold](https://imagemagick.org/command-line-options/#random-threshold)" 会完全独立地处理每一个所选通道。当然,直接使用该运算符会得到一张使用随机抖动的 2 级色调分离图像。 |
magick logo.png -random-threshold 0x100% random_posterize.gif
![[IM Output]](../static/img/quantize/random_posterize.gif)
转换为灰度会在抖动之前把图像中的所有通道均等化。但由于各通道是各自独立、随机地进行抖动的,结果并不是你所期望的那种位图图像。相反,你会得到彩色像素的飞溅效果,尤其是在中间调颜色处。 |
magick logo.png -colorspace Gray -random-threshold 0x100% \
random_greyscale.gif
magick logo.png -colorspace Gray -channel B \
-random-threshold 0x100% -separate random_monochome.gif
![[IM Output]](../static/img/quantize/random_monochome.gif)
基本上,它所做的就是只对灰度化图像的一个通道进行抖动,然后使用 "[-separate](https://imagemagick.org/command-line-options/#separate)" 通道运算符将该通道提取为最终的位图图像。有点取巧,但很有效。作为该运算符的一项特殊功能,当使用特殊的 "[-channels](https://imagemagick.org/command-line-options/#channels)" 选项 'All' 时,IM 会确保生成一张位图图像。 |
magick logo.png -channel All -random-threshold 0x100% random_all.gif
![[IM Output]](../static/img/quantize/random_all.gif)
不过请注意,用这种方法时任何 alpha 通道都会被忽略并丢失,因此通常不推荐使用。我自己也是偶然从源代码中发现这个古老功能的。既然现在你已经知道如何正确使用该运算符从彩色图像生成位图,那么让我们来看看参数是如何影响抖动范围的。这也清楚地展示了这种抖动所产生的像素"聚团"现象。
#magick -size 20x640 gradient: -rotate 90 gradient.png
magick gradient.png -channel All \
-random-threshold 0x100% random_grad_0x100.gif
magick gradient.png -channel All \
-random-threshold 10x90% random_grad_10x90.gif
magick gradient.png -channel All \
-random-threshold 25x75% random_grad_25x75.gif
magick gradient.png -channel All \
-random-threshold 50x50% random_grad_50x50.gif
"[-random-threshold](https://imagemagick.org/command-line-options/#random-threshold)" 设置为 '0x100%' 会对图像产生一种纯粹的"随机抖动"。如果把两个边界设为相同的值(甚至互相越过),就只会产生一张纯粹的 "[-threshold](https://imagemagick.org/command-line-options/#threshold)" 图像。使用其他任何一组边界值(通常以百分比指定),会把位图在给定范围之外的部分做阈值处理,而在给定范围之内的值则产生随机抖动图案。就像使用 "[-monochrome](https://imagemagick.org/command-line-options/#monochrome)" 运算符时那样,使用稍窄一些的范围可以获得最好的效果。对大多数情况来说,'30x80%' 左右的值大概会得到最好的结果。 |
magick logo.png -channel All -random-threshold 30x80% random_30x80.gif
![[IM Output]](../static/img/quantize/random_30x80.gif)
当然,结果仍然不太理想。不过这是你能得到的最简单、也是最差的一种抖动形式。实际发生的情况是,随机化的抖动图案往往会产生像素的"聚团",而不是平滑的抖动图案,这是由随机数生成器中的高频"噪声"造成的。但在非常高的分辨率下,只要足够随机,随机抖动已被证明能产生极好的效果。IM 使用的是加密级别的随机性,因此很可能会非常随机,只是图像很少会在足以发挥这种优势的高分辨率下使用。有人提出过针对这种抖动的一种"修正"方法,即使用随机的"蓝噪声"发生器(一种高频滤波器,与声音制作中使用的低频"粉红噪声"滤波器相对)。这理应能消除像素聚团现象,但在数字实现上非常困难。目前尚未发现"蓝噪声随机抖动"的已知实现,也不太可能会有人去做。
有序抖动
随机抖动会产生像素的随机聚团,而各种误差校正抖动会产生本质上随机的点状图案,有序抖动则基本上与之相反。它被设计得尽可能在数学上具有确定性——确定到你实际上需要指定用于图像抖动的图案。"[-ordered-dither](https://imagemagick.org/command-line-options/#ordered-dither)" 运算符会用给定的预定义图案,对图像中所选的每个 "[-channels](https://imagemagick.org/command-line-options/#channels)" 进行抖动。参数定义了所用的图案(称为阈值图)。这些阈值图基本上分为三种风格:扩散像素抖动,即像素被尽可能彼此远离地放置,以避免"聚团"和平铺伪影;或者将像素聚拢为紧密的点,以便于机械印刷,这种技术称为数字半色调。此外还有一些我们也会介绍的特殊的艺术风格阈值图,甚至可以设计自己的抖动图案或阈值图。在每种情况下,阈值图中开启或关闭的像素数量,都取决于要被抖动为位图的图像(或单个颜色通道)的灰度强度。该映射表以一致的方式添加像素阈值级别,因此一旦某个像素在特定的"阈值"处开启,对于任何更亮的灰色,它都会保持开启状态。这种一致性非常重要,否则会在抖动图案变化的边界处产生伪影。这里重要的一点是,图像中每个像素的结果,都是纯粹通过数学方式独立于图像中任何其他像素而确定的。因此,对原始图像的任何微小改动,都完全不会影响图像其他区域,而这正是我们上面看到的误差校正抖动所存在的问题。这一点对于视频图像以及优化后动画的一致抖动至关重要。
扩散像素抖动
有序抖动最初的目的,也是大多数图形程序员在使用有序抖动时所期望得到的效果,有时更准确地被称为"扩散像素有序抖动"。这意味着,随着阈值强度的增加,像素会被添加到平铺映射表中,使它们尽可能彼此远离并均匀分布。这会产生一种高度一致的图案,在大多数现代显示器上看起来相当平滑,几乎不可见。这类图案已经针对 2 的幂次平铺尺寸计算出来,即平铺尺寸为 2、4、8。不过 IM 也为 3×3 阈值图平铺提供了一种还算合理的阈值图案。下面是 IM 目前提供的这套内置有序抖动。请记住,参数反映的是有序抖动的平铺尺寸。
magick logo.png -ordered-dither o2x2 logo_o2x2.gif
magick logo.png -ordered-dither o3x3 logo_o3x3.gif
magick logo.png -ordered-dither o4x4 logo_o4x4.gif
magick logo.png -ordered-dither o8x8 logo_o8x8.gif
请注意,更大的平铺尺寸能让你模拟更多的"色阶",但同时也会在某些级别产生更明显的瑕疵或矩形像素阵列。 | _'o8x8' 有序抖动长期以来一直是 IM 核心代码的一部分,但一直未被使用。直到 IM Examples 开始详细介绍这一运算符的用法后,它才在 IM v6.2.9 中作为 "[-ordered-dither](https://imagemagick.org/command-line-options/#ordered-dither)" 运算符的一个选项被加入。
在这个时候,为了便于进一步扩展 "[-ordered-dither](https://imagemagick.org/command-line-options/#ordered-dither)" 运算符,这些映射表被赋予了更明确的名称,不过较早的、向后兼容的"平铺尺寸"名称,仍作为新名称的别名保留了下来。
此外,生成 'o3x3' 和 'o4x4' 的"映射表"也经过了彻底修订,以产生更好的"扩散像素"抖动图案。在此之前,这些映射表会产生明显的像素"聚团"。
关于修正之前旧图案的示例,以及在 IM v6.3.0 正式发布升级过程中所做的其他更改,请参见有序抖动升级说明页面。_
---|---
当然,你需要先把图像转换为灰度,才能生成图像中所有通道的正确位图。不过,由于这个过程不是随机的,你不需要像使用 -random-threshold 运算符那样对图像进行后处理,这大大简化了操作。
magick logo.png -colorspace Gray -ordered-dither o2x2 logo_bw_o2x2.gif
magick logo.png -colorspace Gray -ordered-dither o3x3 logo_bw_o3x3.gif
magick logo.png -colorspace Gray -ordered-dither o4x4 logo_bw_o4x4.gif
magick logo.png -colorspace Gray -ordered-dither o8x8 logo_bw_o8x8.gif
作为参考,下面把每一种 "[-ordered-dither](https://imagemagick.org/command-line-options/#ordered-dither)" "扩散像素"图案应用于一张灰度渐变图像,让你能清楚地看到它们各自的样子。
# Threshold Non-Dither / Minimal Checkerboard Dither
magick gradient.png -ordered-dither threshold od_threshold.gif
magick gradient.png -ordered-dither checks od_checks.gif
# Diffused Pixel Dither
magick gradient.png -ordered-dither o2x2 od_o2x2.gif
magick gradient.png -ordered-dither o3x3 od_o3x3.gif
magick gradient.png -ordered-dither o4x4 od_o4x4.gif
magick gradient.png -ordered-dither o8x8 od_o8x8.gif
特定有序抖动所产生的有效(或称伪)色阶图案数量,通常(但并非总是)等于图案中像素数加一。因此,'o3x3' 有序抖动会在结果图像中,每个通道产生 3×3+1,即 10 个有效灰度级别(黑、白,以及 8 种人工灰度图案)。上面还展示了两种特殊的最小抖动阈值图:
- 直接的 '50% 阈值' 非抖动,它不会产生任何额外的灰度级别;以及
- 一种 'checks',即棋盘格抖动图案,它只插入单一图案,为结果渐变增加一个额外的"伪级别"。
数字半色调抖动
"[-ordered-dither](https://imagemagick.org/command-line-options/#ordered-dither)" 在 IM v6.2.8-6 中扩展了一套数字半色调抖动图案(感谢 Glenn Randers-Pehrson)。这些图案全部被设置为生成简单的 45 度点状图案。到了 IM v6.3.0,又进一步扩展了一套类似的、更大的无角度半色调图案。 |
在 IM v6.3.0 发布之前,半色调网屏是通过 '{number}x1' 这种形式的参数来选择的。随着有序抖动的重新开发,这一限制被取消,选定了更好的命名方式,并新增了额外的半色调网屏(正交形式)(参见下面的示例参数)。 |
|---|---|
| 请注意,数字半色调严格来说并不是真正的半色调网屏,后者是为处理机械沉积在纸张、卡纸乃至金属等介质上的圆形墨点而设计的。这类墨点在印刷过程中可能会重叠、洇开,因而需要某种非线性的级别调整。而对于纯数字化的半色调效果,这一点是不需要的。有关该过程的更多细节,请参见文档 Dithering and Halftoning (PDF)。尽管如此,有序抖动的数字半色调图案确实能提供与报纸和廉价印刷杂志中所见相同的基本效果。 |
# Halftone Screen (45 degree angle)
magick logo.png -ordered-dither h4x4a logo_h4x4a.gif
magick logo.png -ordered-dither h6x6a logo_h6x6a.gif
magick logo.png -ordered-dither h8x8a logo_h8x8a.gif
# Halftone Screen (orthogonal)
magick logo.png -ordered-dither h4x4o logo_h4x4o.gif
magick logo.png -ordered-dither h6x6o logo_h6x6o.gif
magick logo.png -ordered-dither h8x8o logo_h8x8o.gif
同样也可以使用"[-colorspace](https://imagemagick.org/command-line-options/#colorspace)"运算符来生成图像的真正位图抖动。
# Halftone Screen (45 degree angle)
magick logo.png -colorspace Gray -ordered-dither h4x4a logo_bw_h4x4a.gif
magick logo.png -colorspace Gray -ordered-dither h6x6a logo_bw_h6x6a.gif
magick logo.png -colorspace Gray -ordered-dither h8x8a logo_bw_h8x8a.gif
# Halftone Screen (orthogonal)
magick logo.png -colorspace Gray -ordered-dither h4x4o logo_bw_h4x4o.gif
magick logo.png -colorspace Gray -ordered-dither h6x6o logo_bw_h6x6o.gif
magick logo.png -colorspace Gray -ordered-dither h8x8o logo_bw_h8x8o.gif
最后再用一张渐变参考图,清晰展示半色调抖动图案,以及随着灰度级变化,抖动图案中的像素簇是如何彼此靠拢生长的。
# Halftone Screen (45 degree angle)
magick gradient.png -ordered-dither h4x4a od_h4x4a.gif
magick gradient.png -ordered-dither h6x6a od_h6x6a.gif
magick gradient.png -ordered-dither h8x8a od_h8x8a.gif
# Halftone Screen (orthogonal)
magick gradient.png -ordered-dither h4x4o od_h4x4o.gif
magick gradient.png -ordered-dither h6x6o od_h6x6o.gif
magick gradient.png -ordered-dither h8x8o od_h8x8o.gif
magick gradient.png -ordered-dither h16x16o od_h16x16o.gif
# Circle Halftones (black and white)
magick gradient.png -ordered-dither c7x7b od_c7x7b.gif
magick gradient.png -ordered-dither c7x7w od_c7x7w.gif
一直到 ImageMagick 6.2.9 版本,上面这些阈值有序抖动图都是 IM 所能提供的全部内容。现在情况有所改变,用户可以添加自己的图案,甚至贡献给 IM 社区。'圆形'半色调阈值是由 Glenn Randers-Pehrson 在 IM v6.6.5-6 中加入的。
偏移半色调抖动
上述半色调抖动唯一的问题在于,完全相同的阈值图(网格)以相同的方式应用于所有颜色通道。这意味着同一组原色以相同的"中心"排列成点阵。要实现所谓的"胶印"效果,需要以特定方式旋转阈值图案,使各颜色形成小规模的"玫瑰图案",从而消除本来可能出现的更难看的干涉(莫尔)图案。这张图基本上说明了这个过程,Wikipedia 页面Halftone对此有非常详细的解释。
![[IM Output]](../static/img/img_diagrams/cmyk_offset.png)
不过要注意,旋转后的网屏平铺效果并不好,因此最好的做法其实是直接生成旋转后的图案,而不是使用平铺的阈值图案。这里介绍一种给图像赋予偏移半色调印刷效果的方法,使用一个小的旋转 2x2 像素棋盘图案,这大概是能用到的最小"网屏"了。 |
magick colorwheel.png -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 60 \) +swap \
\( +clone -distort SRT 30 \) +swap \
\( +clone -distort SRT 45 \) +swap \
\( +clone -distort SRT 0 \) +swap +delete \
-compose Overlay -layers composite \
-set colorspace CMYK -combine -colorspace RGB \
offset_colorwheel.png
![[IM Output]](../static/img/quantize/offset_colorwheel.png)
注意这四个旋转的"网屏"是作为一个整体应用到图像上的,只有在 CMYK 色彩空间中执行"-combine"这一步,才会真正从经过网屏处理的图像中提取出 4 个不同的颜色通道。另外,对最后"black"通道使用"无操作"的扭曲也很重要,因为它会按照其他通道旋转时所用的高斯滤镜对输入的棋盘图案进行模糊处理,即便该网屏本身并没有旋转。这里我还利用了SRT 扭曲的缩放功能(用于生成旋转的网格图案),来生成一个稍大且更模糊的"网屏图案"。
magick parrots_med.png -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,60 \) +swap \
\( +clone -distort SRT 2,30 \) +swap \
\( +clone -distort SRT 2,45 \) +swap \
\( +clone -distort SRT 2,0 -blur 0x0.7 \) +swap +delete \
-compose Overlay -layers composite \
-set colorspace CMYK -combine -colorspace RGB \
offset_parrots.png
注意图案依然显得非常"方块状",尤其是其他网屏都由其衍生出来的黑色网屏。未来可能的改进:把上面的 2 像素棋盘替换为像素级的棋盘图案"pattern:gray50"。可以用高斯滤镜选项来调整缩放图案的模糊程度。或者,也可以先对图案做模糊缩放,再进行阈值处理,得到更圆的点。这样处理后可以像之前那样旋转,生成 4 个颜色网屏。如果能用六边形点的图案(而不是我上面用的棋盘图案)来生成更大的网屏,效果会更好。
需要说明的是,这种方法并不是真正像胶印那样生成彩色圆点,而只是简单地把颜色网屏与原图相乘来伪造效果。你可以从红色鹦鹉与绿色背景交界处出现的锐利颜色变化看出这一点。真正只用纯色点组成的胶印图像,在点的内部不会出现颜色变化。应该变化的是纯色点的大小,其大小取决于原图中该点所代表区域内颜色的平均值。要真正生成一幅在每个颜色通道中都只包含大小合适的圆点的胶印图像,需要做更多的工作。需要确定每个颜色通道中每个点的平均颜色,再据此生成大小合适的彩色点(经过抗锯齿处理的圆)。有人愿意试试吗? 以上内容来自 IM 论坛讨论CMYK Halftone Effect,该讨论探讨了 Photoshop 是如何"伪造"这种效果的,以及 ImageMagick 如何实现同样的效果。这个讨论也和B/W Halftone Dither相关,后者更深入地探讨了如何用真正大小合适的点来生成真正的半色调网屏。不过那次讨论并没有进一步涉及使用偏移(旋转)网屏这一步。这样的网屏可能需要先旋转图像来生成点,然后再针对特定颜色通道把点图案旋转回去。
XML 阈值图
从 IM 6.3.0 版本开始,不再使用内置于 IM 源代码中的固定图集(如前所示),而是改为从程序外部的一组 XML 数据文件中读取。作为这次改动的一部分,现在你可以列出"[-ordered-dither](https://imagemagick.org/command-line-options/#ordered-dither)"运算符可以使用的全部"阈值图"。
magick identify -list threshold
上面的列表不仅展示了可用的阈值图,还展示了为向后兼容或提供替代命名而设置的别名,以及我自己在个人"thresholds.xml"XML 数据文件(保存在我主目录下的".magick"子目录中)中定义的内容。当"[-ordered-dither](https://imagemagick.org/command-line-options/#ordered-dither)"寻找某个图时,会使用在上述列表中第一个找到的图。因此你无法覆盖系统定义的阈值图案。系统文件"thresholds.xml"(其路径由上面的"[-list](https://imagemagick.org/command-line-options/#list)"选项给出)包含了 XML 文件格式的完整说明。这个格式足够简单(并有 IM 的错误检查),用户可以据此定义并创建自己的有序抖动阈值图。例如,下面是'diag5x5'阈值图的副本,我把它定义在个人"threshold.xml"文件中。
可以看到,这创建了一个简单的 5x5 图,是一条随阈值等级增加而逐渐变粗的单一对角线。图中的等级数字从 0 到 5,比除数少 1,除数声明了需要把颜色渐变分成多少个"灰度"级别。下面是使用这个个人阈值图抖动处理后的渐变图。
magick gradient.png -ordered-dither diag od_diag.gif
下面是使用该阈值图抖动一张简单阴影图像 alpha 通道的例子,这正是我设计它的目的。
magick -size 70x60 xc:none -font Candice -pointsize 50 \
-fill black -annotate +10+45 'A' -channel RGBA -blur 0x5 \
-fill white -stroke black -draw "text 5,40 'A'" shadow.png
magick shadow.png -channel A -ordered-dither diag shadow_diag.gif
挺酷的吧!关于 alpha 通道的抖动,后面还会详细介绍。首先我需要展示如何使用扩展后的"[-ordered-dither](https://imagemagick.org/command-line-options/#ordered-dither)"运算符的着色能力。
使用均匀色阶的有序抖动
随着 IM v6.3.0 的发布,不仅"[-ordered-dither](https://imagemagick.org/command-line-options/#ordered-dither)"所使用的阈值图改为从外部文件读取,其内部运算也得到了增强,可以使用数学方式定义的"色调分离"颜色映射表。这意味着你可以生成比"误差校正抖动"更具确定性的图像抖动效果。对于涉及动画的减色处理,这一点尤其重要,因为不会因帧与帧之间的颜色差异而产生问题。色调分离等级是通过在所用阈值图名称后附加一个逗号分隔的数字列表,传给"[-ordered-dither](https://imagemagick.org/command-line-options/#ordered-dither)"参数的。如果没有提供数字,该运算符就退回到普通的双色(即色调分离等级 1)颜色映射表。例如,参数'checks,6'会使用经典的Web 安全颜色映射表(色调分离等级 6,也是由内置的'netscape:'颜色映射表图像所定义)。不过,由于使用的是最小抖动图'checks',会在每两个色阶之间额外添加一层抖动,从而在图像每个通道中产生 11 个伪等级的颜色。换句话说,尽管每个通道只用了 6 级颜色(产生 6^3 即 216 种颜色),但各等级之间的单一抖动图案会把有效抖动提升到 11 级(产生有效的 11^3 即 1331 种颜色)。例如,下面是使用 6 个灰度级和不同阈值图抖动处理的灰度渐变图。第一张图'threshold'是一个特殊的非抖动有序抖动阈值图,只显示所用的颜色。
magick gradient.png -ordered-dither threshold,6 od_threshold_6.gif
magick gradient.png -ordered-dither checks,6 od_checks_6.gif
magick gradient.png -ordered-dither o2x2,6 od_o2x2_6.gif
magick gradient.png -ordered-dither o4x4,6 od_o4x4_6.gif
magick gradient.png -ordered-dither o8x8,6 od_o8x8_6.gif
可以看到,虽然只用了 6 种颜色,但有序抖动却提高了用于表现渐变的有效颜色数,以至于很难看出实际使用的颜色究竟有多少!不仅可以为所有通道定义色调分离的等级数,而且与"[-posterize](https://imagemagick.org/command-line-options/#posterize)"误差校正抖动选项不同,你还可以为每个通道分别指定等级。这些数字按照"[-channels](https://imagemagick.org/command-line-options/#channels)"设置分配给各个通道。例如,下面我们使用一个特殊的 332 颜色映射表(红色和绿色各 8 级,蓝色 4 级)对渐变图进行了抖动处理,该映射表共定义了 256 种颜色。
magick gradient.png -ordered-dither o8x8,8,8,4 od_o8x8_884.gif
由于各通道颜色等级数不同,上面这张图像并不只包含纯灰色,还有一些偏蓝和偏黄的像素,它们相互抵消,从而产生了额外的灰度级别。现在,把这张有序抖动图像与使用色调分离等级 2 和 6、以及"332 颜色映射表"(红色和绿色各 8 级、蓝色 4 级)进行误差校正抖动后的图像做个对比。
magick logo.png -ordered-dither o8x8 logo_o8x8_2.gif
magick logo.png -posterize 2 logo_posterize_2.gif
magick logo.png -ordered-dither o8x8,6 logo_o8x8_6.gif
magick logo.png -posterize 6 logo_posterize_6.gif
magick logo.png -ordered-dither o8x8,8,8,4 logo_o8x8_332.gif
magick logo.png -remap colormap_332.png logo_remap_332.gif
上面每一对图中的第一张是经过数学方式有序抖动处理的,第二张则是经过伪随机的"误差校正"抖动处理的。最后一对使用的是特殊的"332 颜色映射表"(参见生成颜色映射表),它被认为是在 256 色限制下,用于一般图像的最佳色调分离颜色映射表之一。各通道等级数的这种不均匀差异,让这张卡通风格图像的颜色渐变效果略微更好一些。正是为了实现这个"332 颜色映射表","[-ordered-dither](https://imagemagick.org/command-line-options/#ordered-dither)"运算符才加入了为每个颜色通道分别指定等级的能力。
更好的有序抖动效果
我们来仔细看看刚才生成的等级 6有序抖动结果。
magick logo.png -ordered-dither o8x8,6 -format %k info:
可以看到,就这张图像而言,我们远没有填满 GIF 颜色表(256 色的上限)。基本上,由于这张图像主要由蓝色构成,等级 6 均匀颜色映射表中的红色或绿色色调几乎没怎么用到。不过,通过增加色调分离等级数,我们可以更好地填满 GIF 颜色表,从而生成更好的有序抖动图像。
magick logo.png -ordered-dither o8x8,13 -format %k info:
这样生成的颜色数量已经非常接近 GIF 颜色表的上限了。随着颜色数量的增加,效果比简单标准均匀颜色映射表的结果要好得多。 |
magick logo.png -ordered-dither o8x8,13 logo_o8x8_13.gif
![[IM Output]](../static/img/quantize/logo_o8x8_13.gif)
可以看到,在"等级"值较高时,"[-ordered-dither](https://imagemagick.org/command-line-options/#ordered-dither)"生成的图像效果可以与颜色量化、以及颜色量化配合误差校正抖动所生成的特定选色效果相媲美。这些图像的重点并不在于画质有多高——毕竟,完整的颜色量化本来就更容易为图像生成更好的颜色映射表。重点在于,图像内部的低层次抖动图案是固定的,不会因为可能出现的任何微小变化而改变。在有序抖动图像中,只有区域本身发生变化时,抖动结果才会随之变化。也就是说,它们没有会造成变化的误差抖动敏感性,不会给 GIF 动画的帧优化带来问题(参见优化问题)。当然,对于动画来说,在检查实际用了多少种颜色之前,你需要先用"[-append](https://imagemagick.org/command-line-options/#append)"把所有图像拼接在一起。而且,即使已经完成了减色和抖动处理,你仍然需要使用特殊的"[+remap](https://imagemagick.org/command-line-options/#remap)"选项,并把它放在使用"[-ordered-dither](https://imagemagick.org/command-line-options/#ordered-dither)"之后,以强制 IM 为所有图像生成一个"全局共用颜色映射表"。确定色阶数量的这个方法并不简单,但确实有效。我希望能找到一种方法,让 IM 自动确定最佳等级,尤其是对 GIF 动画而言。
自制抖动图案与阈值图
前面我展示了新的"[-ordered-dither](https://imagemagick.org/command-line-options/#ordered-dither)"运算符可以接受用户自定义的抖动图案。这里我将展示如何创建你自己的抖动图案,具体来说,是一种我发现对于生成由水平线组成的阴影很有用的特殊图案。
多图像抖动图案
首先要做的是创建一组图像来定义你想要生成的图案。图案应以一张尺寸合适的纯黑图像作为第一张(所有像素都关闭),并在另一端以一张纯白图像结束(所有像素都开启)。接下来的图像应该是中间 50% 灰的图案,用来定义你想要实现的抖动的基本风格。举例来说,这是我最初的自制抖动图案,我把它保存到了一个多图像 GIF 文件中(不是 GIF 动画)……
magick -size 2x2 xc:black \
\( +clone -draw 'fill white line 0,0 1,0' \) \
xc:white dpat_hlines2x2.gif
montage dpat_hlines2x2.gif -tile x1 -background none -frame 2 \
-filter box -geometry 32x32+5+0 dpat_hlines2x2_imgs.gif
这大概是你能得到的最简单的一套抖动图案图像了,它和'checks'(棋盘抖动)非常相似,只不过用的是水平线而不是棋盘图案。为了让你直观看到这种抖动图案的效果,下面是一个相当简单的、直接使用阈值抖动图像集的自制有序抖动。
magick gradient.png dpat_hlines2x2.gif \
-virtual-pixel tile -fx 'u[(floor((n-1)*u)+1) % n]' dgrad_hlines2x2.gif
可以看到,这个抖动图案并没有什么特别之处。"[-fx](https://imagemagick.org/command-line-options/#fx)"函数是颜色查找表函数的一种变体,也就是 IM 的"抖动查找图案"类函数。而且,当"[-virtual-pixel](https://imagemagick.org/command-line-options/#virtual-pixel)"设置为'tile'时,该函数甚至不需要知道你使用的抖动图案图像的尺寸。 |
像这样由"[-virtual-pixel](https://imagemagick.org/command-line-options/#virtual-pixel)"配合"[-fx](https://imagemagick.org/command-line-options/#fx)"运算符使用计算索引,在 IM 6.2.9-2 版本之前是有问题的。 |
|---|---|
| 我们再用这套抖动图案,配合一张简单的阴影图像试一次…… |
magick shadow.png dpat_hlines2x2.gif -channel A \
-virtual-pixel tile -fx 'u[floor((n-1)*u)+1].g' \
shadow_dpat_hlines2x2.gif
自制有序抖动阈值图
上面这个自制抖动图案已经是能做到的最简单的抖动图案了,所以我们可以直接把它转换成 XML 阈值图,这样速度更快的内置"[-ordered-dither](https://imagemagick.org/command-line-options/#ordered-dither)"运算符就能使用它。下面是最终的 XML 定义,我把它保存在了"$HOME"目录下我个人的阈值图文件"~/.magick/thresholds.xml"中。
这个 XML 格式非常简单,定义了一张 2x2 像素的图。第一张黑色图像被赋值为零,且没有像素,所以不会出现零值。中间图像中开启(变白)的像素被设为'1',剩下的第二张图像的像素则被赋值为'2'。'divisor='定义了这个抖动图案所代表的图像数量,也就是伪色阶(虚拟色阶)的数量,因此其值为'3'。它把像素值划分开,用来定义某个像素应在哪个色阶被开启。因此,上面两个像素在颜色大于 1/3 时被开启,而下面两个像素则在颜色值大于 2/3 时被开启。也就是说,每个像素值代表一个"阈值"级别,这也正是抖动图案又被称为阈值图的原因。定义的其余部分定义了名称(以及可选的别名),供有序抖动运算符引用该阈值图时使用。那我们来试试看……
magick gradient.png -ordered-dither hlines2x2 od_hlines2x2.gif
magick shadow.png -channel A \
-ordered-dither hlines2x2 shadow_hlines2x2.gif
可以看到,效果已经相当不错了,不过我们还可以做一些改进。通过调整图中的阈值,我们可以改变边界,使其不再把色彩空间平均分成 3 个相等的区域……
注意我把除数增大到了'10',从而把颜色等级划分成十个相等的部分。然后我调整了阈值设置,使图案从透明端(黑色)的 30% 阈值开始,到完全不透明(白色)时为 90%。下面是修改阈值图后的效果。
magick gradient.png -ordered-dither hlines2x2a od_hlines2x2a.gif
magick shadow.png -channel A \
-ordered-dither hlines2x2a shadow_hlines2x2a.gif
可以看到,这拓宽了使用纯水平线作为抖动图案的半透明像素范围。这样能带来更好的阴影效果,不过这里用的例子可能应该配合一个不那么模糊的阴影使用。不过要注意,这种对阈值的修改方式很不常见,只是在这里用于既定用途才算合理。基本上,它并没有正确地定义一个渐变,也没有为图案提供由浅到深的层次。要做到这一点,我们需要制作一个复杂得多的阈值图,包含更多的像素和更多的图案。
自制水平线抖动
这里我把上面创建的简单水平线抖动图案扩展成一套图案,以便从"关"到"开"产生更平滑的渐变。结果如下。
montage dpat_hlines.gif -filter box -geometry 60x20+2+0 \
-tile x1 -background none -frame 2 dpat_hlines_images.gif
magick gradient.png dpat_hlines.gif \
-virtual-pixel tile -fx 'u[(floor((n-1)*u)+1) % n]' \
dgrad_dpat_hlines.gif
magick shadow.png dpat_hlines.gif -channel A \
-virtual-pixel tile -fx 'u[floor((n-1)*u)+1].g' \
shadow_dpat_hlines.gif
可以看到,现在它由 9 张 12x4 像素的图像组成。它并没有涵盖你可能用到的所有像素图案,但它增强了线条的效果。同时,为了让线条之间的间隙适当地错开,我把它的高度加倍了。下面是使用这套抖动图案的另一个例子……
magick -size 120x55 xc:white -draw 'fill #777 ellipse 50,43 30,5 0,360' \
-motion-blur 0x15+180 -blur 0x2 sphere_shadow.png
magick sphere_shadow.png dpat_hlines.gif \
-virtual-pixel tile -fx 'u[(floor((n-1)*u)+1) % n]' \
sphere_shadow_dither.gif
magick sphere_shadow_dither.gif -fill red -stroke firebrick \
-draw 'circle 35,25 35,5' sphere_shadow_hlines.gif
下一步是把这套抖动图案转换成单张阈值图图像,而不是一组多张图像。这需要用一些精巧的图像处理手段,把所有图像合并到一起来实现。 ![[IM Output]](../static/img/quantize/od_hlines2x2a.gif)
|
magick -size 1x10 gradient: -flip -crop 1x1 +repage -delete 0,-1 \
-scale 12x4\! null: \( dpat_hlines.gif -delete 0 \) \
-alpha off -compose CopyOpacity -layers Composite \
-reverse -compose Over -flatten -alpha off dmap_hlines.png
![[IM Output]](../static/img/quantize/dmap_hlines_mag.png)
值'10'比抖动图案中图像的数量多 1,而"-scale 12x4\!"是要转换成阈值图的抖动图案的尺寸。得到的结果是一张灰度图,不包含纯黑或纯白的颜色。某个像素所用的灰度级别意味着:如果颜色级别达到该灰度值或更高,那么该像素就应被开启。也就是说,每个灰度级别就是颜色值从黑变白的"阈值"级别。如果换一个角度来看这张图像,暗的像素通常意味着这些像素会在更多的颜色级别下被开启;而亮的像素只有在图像颜色变得非常亮时才会被开启。这几乎与图像实际外观相反,但仔细想想是说得通的。我这里用的是 PNG 图像而不是 GIF 图像来保存这张图,因为只需要保存一张图像,更重要的是,这样可以尽量保留阈值的 16 位精度。GIF 只能处理 8 位色阶。现在,我们可以只用一张图像,通过对每个像素与该抖动阈值图像(或图)进行简单直接的比较,来对图像进行抖动处理。
magick gradient.png dmap_hlines.png \
-virtual-pixel tile -fx 'u>=v' dgrad_dmap_hlines.gif
| 看看阈值图能有多简单。你只需要一张图像,对每个正被抖动处理的通道,每个像素只需做一次直接比较。这使得使用阈值图进行抖动处理速度非常非常快,远快于完整的颜色量化。正是这种简单性,使得 ImageMagick 以及大多数图形软件都采用阈值图来保存各种抖动图案。 | 大于等于('>=')测试直到 IM 6.2.9-2 版本才被加入"[-fx](https://imagemagick.org/command-line-options/#fx)"运算符。如果这造成了问题,可以改用上面反转后的测试'v<u'。 |
|---|---|
不过,如果用户想用多个颜色等级进行抖动处理,这种简单性就会变得复杂很多。这方面的概念验证最早是在色调分离有序抖动这一页的示例中摸索出来的,之后才被纳入 IM 的核心功能。现在我们已经有了合并后的阈值图像,接下来需要把上面这张图像转换成一个 IM 能直接读取、并被"[-ordered-dither](https://imagemagick.org/command-line-options/#ordered-dither)"运算符使用的 XML 阈值图。为此,我们需要把图像输出为代表其所含 9 个灰度级别的数字。最好的方法是使用NetPBM 或 PBMplus图像格式,并配合"NetPbm"图像处理软件进行深度调整。这个软件包通常是标准 Linux 安装的一部分,所以大多数人应该已经有了,或者可以从常用的软件发行渠道安装。"pnmdepth"后面的数字同样是阈值图像所包含的灰度级别数。 |
magick dmap_hlines.png pgm:- | pnmdepth 9 | pnmnoraw > dmap_hlines.pgm
上面所有的数字(除了'P2'这个图像魔数标识符之外)都是生成相应"阈值图"所需要的数字,你可以把它添加到你个人的"thresholds.xml"文件中。举例来说,下面就是根据上面内容生成的阈值图条目。
下面是使用这个阈值图的一个例子。
magick shadow.png -channel A -ordered-dither hlines shadow_hlines.gif
使用符号图案进行抖动
虽然大多数抖动操作都可以用单张阈值图或阈值图像来代替多图像图案集,但这并不意味着多图像图就没有自己的用途。你可以使用一组查找图像,一次性平铺多个区域,而不是一次只处理一个。举例来说,可以通过缩放一张简单的图像,然后用特定符号替换图像中的每个像素来实现这一点。这里我用一张非常小的"eyes"图像
,把每个像素替换成各种符号,从而为原图中的每个像素生成这样一种图案。
montage dpat_symbols.gif -geometry +5+0 \
-tile x1 -background none -mattecolor blue -frame 3 \
dpat_syms_images.gif
magick eyes.gif -alpha off -colorspace sRGB -grayscale Average \
-alpha off -scale 1600% -negate \
dpat_symbols.gif -virtual-pixel tile -fx 'u[floor(15.9999*u)+1]' \
eyes_syms.gif
montage用于展开这张多图像 GIF 图像,这样你就能看到它的内容,而不会看到"动画效果"。你可以调整想要使用的"[-grayscale](https://imagemagick.org/command-line-options/#grayscale)"强度方式,从常规的'Rec709Luminance'到较暗的'Rec709Luma',或者使用'average',对非线性'sRGB'色彩空间或线性'RGB'色彩空间求平均。你甚至可以调整数值的"[-gamma](https://imagemagick.org/command-line-options/#gamma)"缩放,以获得最佳的色彩分布效果。可能性有很多,效果好坏更多取决于你的符号排列方式,而不是所选的具体方法。上述方法的关键在于,要设法确保输入图像中的每种颜色都产生一个独一无二的符号,而这做起来可能相当棘手。这个例子可以用来制作十字绣或编织图案指南,供爱好者跟着做,从而由较小的电脑图像生成更大规模的手工艺作品。你可以用这种技术,用一组平铺用的彩色图像来平铺灰度图像。效果有点像许多老式电脑战争游戏中看到的那种地形地图。
montage dpat_map.gif -geometry +5+0 -tile x1 -background none \
dpat_map_images.gif
magick -seed 100 \
-size 200x200 plasma:'gray(50%)-gray(50%)' -blur 0x15 \
-channel G -auto-level +channel -set colorspace sRGB \
dpat_map.gif -virtual-pixel tile -fx 'u[floor(5.999*u.g)+1]' \
map.gif
注意,我需要确保 IM 把这张灰度图像当作已经处于最终的 sRGB 色彩空间中(就像平铺用的图像那样),即使用于 FX 索引查找的实际上是线性 RGB 数据。如果不这样做,生成的"地图"就会偏向林地地形,几乎没有出现水域的机会。可以看到,任何一组图像都可以用作平铺图块,这些图像甚至不需要彼此对齐,也不需要是相同尺寸的图块。当然,如果图块尺寸相同且彼此关系密切(就像三张蓝色"海洋"图块的情况那样),平铺图案就可以从一个平铺区域"流动"到另一个区域。把图块换成数字图像,你还可以生成一种按数字填色的指南。不过,可能需要一些额外的处理来给不同区域加上边界。这里就留作练习吧,把你的解决方案发邮件给我,你的名字就可以作为这项技术的作者出现在 IM Examples 中。
有序抖动的杂项说明与未来可能性
建设中
使用少量颜色的有序抖动
在图像很小、颜色数量也很少的情况下,无论是 IM 那种伪随机的希尔伯特曲线误差校正抖动,还是更简单的 Floyd-Steinberg 误差校正抖动(参见上面的误差校正抖动),效果都会很难看。理想情况下,对于颜色少、类似图标的小图像,应该使用有序抖动来生成效果好得多的结果。然而目前,IM 的有序抖动只能使用"固定"的数学生成色彩表,而不能只使用一组"最优"颜色。
使用任意颜色映射表的有序抖动
存在某种算法,能够让你使用一组特定颜色来进行有序抖动。基本思路是,把有序抖动算法能够生成的"伪颜色"(可能涉及三色抖动)加入到给定的颜色映射表中,以"填补"它。然后,你就可以把图像中的每个像素"映射"到这个"扩展后的颜色映射表"上。从最初映射到某个特定伪颜色出发,可以从生成该伪颜色的阈值图中挑选出实际颜色,从而对该颜色所在的区域、进而对整幅图像,按照给定的一组颜色进行有序抖动。由于我做过图标方面的工作,现在又做 GIF 动画,我确实很想看到任意颜色的有序抖动能够实现,但目前我还没有找到关于如何用固定颜色集做有序抖动的实用参考资料。
![[IM Output]](../static/img/images/colorwheel.png)
![[IM Output]](../static/img/quantize/color_quantize.gif)
![[IM Output]](../static/img/quantize/color_predefined.gif)
![[IM Output]](../static/img/quantize/color_uniform.gif)
![[IM Output]](../static/img/quantize/color_threshold.gif)
![[IM Output]](../static/img/images/tree.gif)
![[IM Text]](../static/img/quantize/tree_histogram.txt.gif)
![[IM Output]](../static/img/quantize/tree_colors.gif)
![[IM Text]](../static/img/quantize/tree_colors.txt.gif)
![[IM Output]](../static/img/images/rose.gif)
![[IM Output]](../static/img/quantize/unique_color_histogram.png)
![[IM Text]](../static/img/quantize/rose_average.txt.gif)
![[IM Text]](../static/img/quantize/rose_fx_rgb.txt.gif)
![[IM Text]](../static/img/quantize/rose_red_mean.txt.gif)
![[IM Text]](../static/img/quantize/compare_navy.txt.gif)
![[IM Text]](../static/img/quantize/compare_transparency.txt.gif)
![[IM Text]](../static/img/quantize/compare_blue-navy.txt.gif)
![[IM Text]](../static/img/quantize/compare_blue-navy_fuzz.txt.gif)
![[IM Text]](../static/img/quantize/compare_fuzz_trans.txt.gif)
![[IM Text]](../static/img/quantize/fuzz_navy.txt.gif)
![[IM Text]](../static/img/quantize/fuzz_navy2.txt.gif)
![[IM Output]](../static/img/quantize/colors_64_no.gif)
![[IM Output]](../static/img/quantize/colors_64_rm.gif)
![[IM Output]](../static/img/quantize/colors_64_fs.gif)
![[IM Output]](../static/img/quantize/logo.png)
![[IM Output]](../static/img/quantize/colors_8_no.gif)
![[IM Output]](../static/img/quantize/colors_8_rm.gif)
![[IM Output]](../static/img/quantize/colors_8_fs.gif)
![[IM Output]](../static/img/quantize/colors_16_no.gif)
![[IM Output]](../static/img/quantize/colors_16_rm.gif)
![[IM Output]](../static/img/quantize/colors_16_fs.gif)
![[IM Output]](../static/img/quantize/colors_rb.gif)
![[IM Output]](../static/img/quantize/colors_rb2.gif)
![[IM Output]](../static/img/quantize/colors_br.gif)
![[IM Output]](../static/img/quantize/colors_br2.gif)
![[IM Output]](../static/img/quantize/colors_gradient.gif)
![[IM Output]](../static/img/quantize/colors_space_sRGB.gif)
![[IM Output]](../static/img/quantize/colors_space_CMY.gif)
![[IM Output]](../static/img/quantize/colors_space_RGB.gif)
![[IM Output]](../static/img/quantize/colors_space_GRAY.gif)
![[IM Output]](../static/img/quantize/colors_space_XYZ.gif)
![[IM Output]](../static/img/quantize/colors_space_LAB.gif)
![[IM Output]](../static/img/quantize/colors_space_LUV.gif)
![[IM Output]](../static/img/quantize/colors_space_HSL.gif)
![[IM Output]](../static/img/quantize/colors_space_HSB.gif)
![[IM Output]](../static/img/quantize/colors_space_HWB.gif)
![[IM Output]](../static/img/quantize/colors_space_YIQ.gif)
![[IM Output]](../static/img/quantize/colors_space_YUV.gif)
![[IM Output]](../static/img/quantize/colors_space_OHTA.gif)
![[IM Output]](../static/img/quantize/alpha_gradient.png)
![[IM Output]](../static/img/quantize/alpha_colors_256.png)
![[IM Output]](../static/img/quantize/alpha_colors_64.png)
![[IM Output]](../static/img/quantize/alpha_colors_15.png)
![[IM Output]](../static/img/quantize/alpha_colors_15n.png)
![[IM Output]](../static/img/quantize/alpha_colors_15qt.png)
![[IM Output]](../static/img/quantize/alpha_dither_threshold.gif)
![[IM Output]](../static/img/quantize/alpha_dither_checks.gif)
![[IM Output]](../static/img/quantize/alpha_dither_ordered.gif)
![[IM Output]](../static/img/quantize/alpha_dither_halftone.gif)
![[IM Output]](../static/img/quantize/alpha_dither_monochrome.gif)
![[IM Output]](../static/img/quantize/alpha_dither_monochrome_fs.gif)
![[IM Output]](../static/img/quantize/alpha_dither_map.gif)
![[IM Output]](../static/img/quantize/alpha_dither_map_fs.gif)
![[IM Text]](../static/img/quantize/dithers.txt.gif)
![[IM Output]](../static/img/quantize/dither_riemersma.gif)
![[IM Output]](../static/img/quantize/dither_floyd.gif)
![[IM Output]](../static/img/quantize/dither_not.gif)
![[IM Output]](../static/img/quantize/dither_anim.gif)
![[IM Output]](../static/img/quantize/dither_anim_magnify.gif)
![[IM Output]](../static/img/quantize/speckle_fixed.gif)
![[IM Output]](../static/img/quantize/monochrome_gradient.gif)
![[IM Output]](../static/img/quantize/mono_remap.gif)
![[IM Output]](../static/img/quantize/mono_remap_gradient.gif)
![[IM Output]](../static/img/quantize/mono_remap_ctrl.gif)
![[IM Output]](../static/img/quantize/mono_remap_grad_ctrl.gif)
![[IM Output]](../static/img/quantize/gradient_rb.png)
![[IM Output]](../static/img/quantize/colors_threshold.gif)
![[IM Output]](../static/img/quantize/mono_threshold.gif)
![[IM Output]](../static/img/quantize/threshold_two_color.gif)
![[IM Output]](../static/img/images/colortable.gif)
![[IM Output]](../static/img/quantize/remap_logo_no.gif)
![[IM Output]](../static/img/quantize/remap_logo_rm.gif)
![[IM Output]](../static/img/quantize/remap_logo_fs.gif)
![[IM Output]](../static/img/quantize/remap_netscape.gif)
![[IM Output]](../static/img/quantize/remap_netscape_nd.gif)
![[IM Output]](../static/img/quantize/posterize_2_ns.gif)
![[IM Output]](../static/img/quantize/posterize_3_ns.gif)
![[IM Output]](../static/img/quantize/posterize_6_ns.gif)
![[IM Output]](../static/img/quantize/posterize_gradient.gif)
![[IM Output]](../static/img/quantize/posterize_logo.gif)
![[IM Output]](../static/img/quantize/posterize_logo_dither.gif)
![[IM Output]](../static/img/quantize/posterize_6_logo.gif)
![[IM Output]](../static/img/quantize/posterize_2_cw.gif)
![[IM Output]](../static/img/quantize/posterize_3_cw.gif)
![[IM Output]](../static/img/quantize/posterize_6_cw.gif)
![[IM Output]](../static/img/quantize/posterize_2_dither.gif)
![[IM Output]](../static/img/quantize/posterize_3_dither.gif)
![[IM Output]](../static/img/quantize/posterize_6_dither.gif)
![[IM Output]](../static/img/quantize/posterize_2_od.gif)
![[IM Output]](../static/img/quantize/posterize_3_od.gif)
![[IM Output]](../static/img/quantize/posterize_6_od.gif)
![[IM Output]](../static/img/quantize/threshold_0.gif)
![[IM Output]](../static/img/quantize/threshold_25.gif)
![[IM Output]](../static/img/quantize/threshold_50.gif)
![[IM Output]](../static/img/quantize/threshold_75.gif)
![[IM Output]](../static/img/quantize/threshold_100.gif)
![[IM Output]](../static/img/quantize/random_greyscale.gif)
![[IM Output]](../static/img/quantize/random_grad_0x100.gif)
![[IM Output]](../static/img/quantize/random_grad_10x90.gif)
![[IM Output]](../static/img/quantize/random_grad_25x75.gif)
![[IM Output]](../static/img/quantize/random_grad_50x50.gif)
![[IM Output]](../static/img/quantize/logo_o2x2.gif)
![[IM Output]](../static/img/quantize/logo_o3x3.gif)
![[IM Output]](../static/img/quantize/logo_o4x4.gif)
![[IM Output]](../static/img/quantize/logo_bw_o2x2.gif)
![[IM Output]](../static/img/quantize/logo_bw_o3x3.gif)
![[IM Output]](../static/img/quantize/logo_bw_o4x4.gif)
![[IM Output]](../static/img/quantize/logo_bw_o8x8.gif)
![[IM Output]](../static/img/quantize/od_threshold.gif)
![[IM Output]](../static/img/quantize/od_checks.gif)
![[IM Output]](../static/img/quantize/od_o2x2.gif)
![[IM Output]](../static/img/quantize/od_o3x3.gif)
![[IM Output]](../static/img/quantize/od_o4x4.gif)
![[IM Output]](../static/img/quantize/od_o8x8.gif)
![[IM Output]](../static/img/quantize/logo_h4x4a.gif)
![[IM Output]](../static/img/quantize/logo_h6x6a.gif)
![[IM Output]](../static/img/quantize/logo_h8x8a.gif)
![[IM Output]](../static/img/quantize/logo_h4x4o.gif)
![[IM Output]](../static/img/quantize/logo_h6x6o.gif)
![[IM Output]](../static/img/quantize/logo_h8x8o.gif)
![[IM Output]](../static/img/quantize/logo_bw_h4x4a.gif)
![[IM Output]](../static/img/quantize/logo_bw_h6x6a.gif)
![[IM Output]](../static/img/quantize/logo_bw_h8x8a.gif)
![[IM Output]](../static/img/quantize/logo_bw_h4x4o.gif)
![[IM Output]](../static/img/quantize/logo_bw_h6x6o.gif)
![[IM Output]](../static/img/quantize/logo_bw_h8x8o.gif)
![[IM Output]](../static/img/quantize/od_h4x4a.gif)
![[IM Output]](../static/img/quantize/od_h6x6a.gif)
![[IM Output]](../static/img/quantize/od_h8x8a.gif)
![[IM Output]](../static/img/quantize/od_h4x4o.gif)
![[IM Output]](../static/img/quantize/od_h6x6o.gif)
![[IM Output]](../static/img/quantize/od_h8x8o.gif)
![[IM Output]](../static/img/quantize/od_h16x16o.gif)
![[IM Output]](../static/img/quantize/od_c7x7b.gif)
![[IM Output]](../static/img/quantize/od_c7x7w.gif)
![[IM Output]](../static/img/quantize/offset_parrots.png)
![[IM Output]](../static/img/quantize/tmaps_list.txt.gif)
![[IM Output]](../static/img/quantize/tmap_diag.txt.gif)
![[IM Output]](../static/img/quantize/od_diag.gif)
![[IM Output]](../static/img/quantize/shadow.png)
![[IM Output]](../static/img/quantize/shadow_diag.gif)
![[IM Output]](../static/img/quantize/od_threshold_6.gif)
![[IM Output]](../static/img/quantize/od_checks_6.gif)
![[IM Output]](../static/img/quantize/od_o2x2_6.gif)
![[IM Output]](../static/img/quantize/od_o4x4_6.gif)
![[IM Output]](../static/img/quantize/od_o8x8_6.gif)
![[IM Output]](../static/img/quantize/od_o8x8_884.gif)
![[IM Output]](../static/img/quantize/logo_o8x8_2.gif)
![[IM Output]](../static/img/quantize/logo_posterize_2.gif)
![[IM Output]](../static/img/quantize/logo_o8x8_6.gif)
![[IM Output]](../static/img/quantize/logo_posterize_6.gif)
![[IM Output]](../static/img/quantize/logo_o8x8_332.gif)
![[IM Output]](../static/img/quantize/logo_remap_332.gif)
![[IM Text]](../static/img/quantize/logo_color_6.txt.gif)
![[IM Text]](../static/img/quantize/logo_color_13.txt.gif)
![[IM Output]](../static/img/quantize/dpat_hlines2x2_imgs.gif)
![[IM Output]](../static/img/quantize/dgrad_hlines2x2.gif)
![[IM Output]](../static/img/quantize/shadow_dpat_hlines2x2.gif)
![[IM Output]](../static/img/quantize/tmap_hlines2x2.txt.gif)
![[IM Output]](../static/img/quantize/od_hlines2x2.gif)
![[IM Output]](../static/img/quantize/shadow_hlines2x2.gif)
![[IM Output]](../static/img/quantize/tmap_hlines2x2a.txt.gif)
![[IM Output]](../static/img/quantize/dpat_hlines_images.gif)
![[IM Output]](../static/img/quantize/dgrad_dpat_hlines.gif)
![[IM Output]](../static/img/quantize/shadow_dpat_hlines.gif)
![[IM Output]](../static/img/quantize/sphere_shadow.png)
![[IM Output]](../static/img/quantize/sphere_shadow_dither.gif)
![[IM Output]](../static/img/quantize/sphere_shadow_hlines.gif)
![[IM Output]](../static/img/quantize/dgrad_dmap_hlines.gif)
![[IM Text]](../static/img/quantize/dmap_hlines.pgm.gif)
![[IM Output]](../static/img/quantize/tmap_hlines.txt.gif)
![[IM Output]](../static/img/quantize/shadow_hlines.gif)
![[IM Output]](../static/img/quantize/dpat_syms_images.gif)
![[IM Output]](../static/img/quantize/eyes_syms.gif)
![[IM Output]](../static/img/quantize/dpat_map_images.gif)
![[IM Output]](../static/img/quantize/map.gif)