ImageMagick 示例 -- 抗锯齿
抗锯齿是 ImageMagick 中所有绘图操作的一个主要组成部分。遗憾的是它也会引发许多问题。本页试图涵盖这些问题并给出相应的解决方案。
抗锯齿简介 ImageMagick 在绘制图像时,采用一种非常特别的方式。它以一种称为“抗锯齿”的操作来绘制。为了演示,我会在透明背景上绘制一幅图像,然后放大图像的一小部分,让你能看清正在发生什么。
magick -size 80x80 xc:none \
-fill white -draw "circle 40,40 15,20" \
-fill black -draw "line 5,30 78,2" drawn.png
magick drawn.png -crop 10x10+50+5 +repage -scale 80x80 drawn_mag.png
你可能会以为上面的图像只有 'white'、'black' 和 'transparent' 三种颜色,因为我们要求 IM 使用的只有这些。但正如你在放大图像时所看到的,它拥有一整个色域。ImageMagick 通过这样做,使用一种称为 '抗锯齿' 的技术让图像看起来更平滑、更美观。这是一个花哨的术语,意思是它用多种颜色乃至透明度混合的结果去填充对象边缘的像素,让对象看起来更平滑。如果不做抗锯齿,那么所有绘制对象的边缘就会出现一种称为 '锯齿化(aliasing)' 的阶梯状效果,不过它更常被称作 '锯齿(jaggies)'。这里我们再次绘制该图像,但这次要求 IM 用 "[+antialias](https://imagemagick.org/command-line-options/#antialias)" 关闭其自动抗锯齿操作。
magick -size 80x80 xc:none +antialias \
-fill white -draw "circle 40,40 15,20" \
-fill black -draw "line 5,30 78,2" drawn_jaggies.png
magick drawn_jaggies.png -crop 10x10+50+5 +repage -scale 80x80 \
drawn_jaggies_mag.png
这次图像确实只有三种颜色。但结果一点也不好看。在最新的 IM 上会以阶梯状绘制出一条单像素宽的线。在较旧的 IM 上,这条线在外观上还会相当粗,看起来更糟。基本上这不是你通常想要的效果。'锯齿化' 的阶梯效果,也常被称作 '锯齿',正是 IM 试图避免的东西。但如果你想要特定的颜色,那就得接受它,或者使用其他技术(例如颜色量化)来确保只使用某些颜色。请注意实际上这里发生了两种形式的抗锯齿。第一种是图像中白色与黑色的混合,产生各种色调,本例中是灰色。另一种是颜色与透明度的混合,在图像中生成半透明像素。后者是你需要记在心里的,因为许多图像格式(例如 GIF)无法处理半透明像素,会把这类像素变成完全不透明或完全透明。GIF 布尔透明度 中的示例演示了在保存为这类格式时控制半透明像素处理方式的方法。
小结
抗锯齿在任何形式的图像绘制中都非常重要,是你应该记在心里的东西。如果不考虑 IM 抗锯齿所生成的混合颜色与半透明像素,你自己创作的图像在某些格式下可能会显得非常糟糕。当你在一种不允许半透明像素的图像格式(例如广泛使用的 "GIF" 格式)中创作图像时,这一点就更为重要。处理该问题的方法请参见 GIF 布尔透明度。IM 非常擅长对颜色和透明度做抗锯齿,但实际上很不擅长仅绘制纯粹的 '锯齿化' 像素(例如为了匹配特定的颜色映射表)。我听说这将是 IM 后续版本的重点。
只使用特定颜色绘图
建设中
在不使用抗锯齿的情况下更好的绘图方式,以生成具有精确颜色的图像。也就是说,针对 '索引图像'。具体来说,就是绘制到一块透明画布上,对 alpha 通道做阈值处理,然后再叠加,这样就只会绘制完全不透明的像素。
绘制细的位图圆
这里我们来看如何尝试用 IM 绘制位图式的 '细线' 圆。通常这是用一种位图圆绘制算法完成的,它一般被称为 Bresenham 圆算法,但更准确的名称是 中点圆算法。遗憾的是,ImageMagick 中没有这个算法,而且可能永远不会有,因为在一个完全抗锯齿的绘图环境中并不需要它。绘制圆的另一种替代办法,我们稍后就会看到,是使用 形态学,用特殊的 环形核 对单个像素进行 '膨胀'。
例如,IM 通常绘制圆的方式会生成许多灰色的抗锯齿颜色,以让圆看起来平滑。 |
magick -size 15x15 xc: -fill none -stroke black \
-draw 'translate 7,7 circle 0,0 5,0' \
-scale 500% circle_antialiased.gif
![[IM Output]](../static/img/antialiasing/circle_antialiased.gif)
然而,仅仅关闭抗锯齿会生成并非漂亮细 '位图' 线的圆和线。 |
magick -size 15x15 xc: -fill none -stroke black +antialias \
-draw 'translate 7,7 circle 0,0 5,0' \
-scale 500% circle_aliased.gif
![[IM Output]](../static/img/antialiasing/circle_aliased.gif)
你需要做的是同时调整 "[-strokewidth](https://imagemagick.org/command-line-options/#strokewidth)",它默认为 1 像素宽,把它调到更小的值,例如 0.5 像素宽。 |
magick -size 15x15 xc: -fill none -stroke black +antialias \
-strokewidth 0.5 -draw 'translate 7,7 circle 0,0 5,0' \
-scale 500% circle_thin_stroke.gif
![[IM Output]](../static/img/antialiasing/circle_thin_stroke.gif)
好多了,但还不太对。不过你也可能把描边宽度设得太小,尤其是在半径为奇数尺寸时。 |
magick -size 15x15 xc: -fill none -stroke black +antialias \
-strokewidth 0 -draw 'translate 7,7 circle 0,0 5,0' \
-scale 500% circle_zero_stroke.gif
![[IM Output]](../static/img/antialiasing/circle_zero_stroke.gif)
而这里是一个针对以整数实际像素位置为中心的 5 像素圆的良好解决方案。 |
magick -size 15x15 xc: -fill none -stroke black +antialias \
-strokewidth 0.4 -draw 'translate 7,7 circle 0,0 5,0' \
-scale 500% circle_perfect.gif
![[IM Output]](../static/img/antialiasing/circle_perfect.gif)
然而,经过多次试验,我找不到一个对所有半径和圆心都适用的 "[-strokewidth](https://imagemagick.org/command-line-options/#strokewidth)"。尤其是稍微偏离中心的圆。
并不存在适用于所有情况的理想解决方案
例如这个圆,它的中心既不在某个像素上,也不在像素边界上,不仅顶部有缺口,底部还太粗了!真难看! |
magick -size 15x15 xc: -fill none -stroke black +antialias \
-strokewidth 0.47 -draw 'translate 7,7.3 circle 0,0 5,0' \
-scale 500% circle_bad_stroke.gif
![[IM Output]](../static/img/antialiasing/circle_bad_stroke.gif)
下面是一张良好 "[-strokewidth](https://imagemagick.org/command-line-options/#strokewidth)" 的表格,用于生成特定半径的细单像素宽圆。请注意,最佳值会根据圆心是位于 实际 像素(例如 ' 5 , 5 ')上,还是位于 半 像素边界(例如 ' 5.5 , 5.5 ')上而有所不同。 圆半径 | SW 实际像素 | SW 半像素
---|---|---
1 | 0.3 | 0.3 ¶
1.5 | 0.5 ¶ | 0.3
2 | 0.3 | 0.3 §
2.5 | 0.5 ¶ | 0.3 ¤
3 | 0.3 ¤ | 0.3
3.5 | 0.5 | 0.3 ¤
4 | 0.5 § | 0.3
4.5 | 0.5 | 0.3
5 | 0.4 | 0.3
5.5 | 0.5 ¶ | 0.3
6 | 0.3 | 0.5 §
6.5 | 0.5 | 0.43
7 | 0.5 | 0.434
7.5 | 0.5 § | 0.5 §
8 | 0.4 | 0.5
¤ 非常好的小圆
§ 未找到理想的宽度
¶ 圆非常糟糕
旁注: 要在图像上让圆居中,用绘图坐标(像素坐标)表示就是 (size-1)/2
抗锯齿与漫水填充的问题 由于 IM 的抗锯齿特性,漫水填充("[-draw](https://imagemagick.org/command-line-options/#threshold) color floodfill")在带有抗锯齿效果的图像上使用时会出现问题。对于从 "JPG" 图像格式读入的图像,它也存在类似的问题。基本上,由于 IM 中的大多数对象都经过抗锯齿(或从 "JPG" 格式图像文件读入),绘制对象边缘附近的颜色很少正好是你用漫水填充去替换的那个特定颜色。这意味着,除非你完全避免了抗锯齿,否则漫水填充不会填到你想填充区域的最边缘。本质上,漫水填充,甚至颜色替换,都不理解抗锯齿,它自身也不使用抗锯齿技术。因此漫水填充通常会漏掉你所填充区域最边缘的像素。例如,这里我们做一次典型的漫水填充操作。画一个圆,然后试着用一个图案去填充它…… |
magick -size 60x60 xc:lightblue -strokewidth 2 \
-fill none -stroke red -draw "circle 30,30 5,30" \
-tile tile_weave.gif -draw "color 30,30 floodfill" \
tile_fill_1.gif
magick tile_fill_1.gif -crop 10x10+35+4 +repage -scale 80x80 \
tile_fill_1_mag.gif
![[IM Output]](../static/img/antialiasing/tile_fill_1_mag.gif)
正如你在图像的放大部分中所看到的,一整条 '偏色' 像素被漫水填充操作完全漏掉了,因为这些像素的颜色与你要填充的区域并不完全相同。改善这一点的一种方法是,预先用与所用图案相配的颜色填充你打算填充的区域。图案仍然不会把区域填满,但至少不会看起来那么糟。 |
magick -size 60x60 xc:lightblue -strokewidth 2 \
-fill black -stroke red -draw "circle 30,30 5,30" \
-tile tile_weave.gif -draw "color 30,30 floodfill" \
tile_fill_2.gif
magick tile_fill_2.gif -crop 10x10+35+4 +repage -scale 60x60 \
tile_fill_2_mag.gif
![[IM Output]](../static/img/antialiasing/tile_fill_2_mag.gif)
另一种做法是用较高的 模糊系数(Fuzz) 用你的图案去填充该区域,强制图案把区域完全填满,一直填到最边缘,而不漏掉边缘像素。 |
magick -size 60x60 xc:lightblue -strokewidth 2 \
-fill none -stroke red -draw "circle 30,30 5,30" \
-fuzz 35% -tile tile_weave.gif -draw "color 30,30 floodfill" \
tile_fill_3.gif
magick tile_fill_3.gif -crop 10x10+35+4 +repage -scale 60x60 \
tile_fill_3_mag.gif
![[IM Output]](../static/img/antialiasing/tile_fill_3_mag.gif)
| 请注意,像这样过高的 '模糊系数',或者过细的边界,都可能导致填充图案从所定义的区域中 '渗漏' 出去。使用漫水填充操作时始终需要一些谨慎。正因如此,我实际上并不推荐把它作为通用的解决方案。
---|---
这样做的问题在于,漫水填充就其本质而言并不使用抗锯齿,所以填充区域的边缘会受到 '锯齿' 即锯齿化效果的困扰。你可以通过把图像绘制拆分为若干独立步骤来改善这种情况。先创建一个有颜色的圆,填充它,然后再绘制边界。 |
magick -size 60x60 xc:lightblue -fill black -draw "circle 30,30 5,30" \
-tile tile_weave.gif -draw "color 30,30 floodfill" +tile \
-fill none -stroke red -strokewidth 2 -draw "circle 30,30 5,30" \
tile_fill_4.gif
magick tile_fill_4.gif -crop 10x10+35+4 +repage -scale 60x60 \
tile_fill_4_mag.gif
![[IM Output]](../static/img/antialiasing/tile_fill_4_mag.gif)
这是改善漫水填充的一种简单方法。其他方法还有使用形状叠加,但那可能是一种较难琢磨的方法。稍后我会研究对现有图像做类似的修改。当然,如果你是自己绘制被漫水填充的区域,而不是使用现有图像,那么理想的解决方案是在原始绘图操作中指定填充图案,从而避免漫水填充。 |
magick -size 60x60 xc:lightblue -strokewidth 2 \
-tile tile_weave.gif -stroke red -draw "circle 30,30 5,30" \
tile_fill_5.gif
magick tile_fill_5.gif -crop 10x10+35+4 +repage -scale 60x60 \
tile_fill_5_mag.gif
将来: 现有图像(尤其是 JPG 格式)上的抗锯齿问题。
例如,把文本或图示图像重新着色后,叠加到某种颜色
或背景之上。
还有为 GIF 文件重新添加透明度,以及为图标用途重新缩放的 JPEG。
**对色彩有限的图像进行平滑或抗锯齿**
具体来说是位图(纯黑白)图像。
首先,抗锯齿在位图图像上不起作用。
抗锯齿是通过混合颜色与透明度,来尝试平滑斜线和颜色边界的
'阶梯状' 或 '锯齿' 效果。如果只有两种颜色可用,
那么抗锯齿就不可能发生!
在使用抗锯齿之前,图像至少必须先从黑白或灰度
转换过来。
平滑边缘的一种简单方法是,在读入黑白图像或调色板尺寸极小的图像后,
施加少量的模糊。
例: magick image.xbm -blur 0x.3 smoothed_image.png
![[IM Output]](../static/img/antialiasing/drawn.png)
![[IM Output]](../static/img/antialiasing/drawn_mag.png)
![[IM Output]](../static/img/antialiasing/drawn_jaggies.png)
![[IM Output]](../static/img/antialiasing/drawn_jaggies_mag.png)
![[IM Output]](../static/img/antialiasing/tile_fill_5.gif)
![[IM Output]](../static/img/antialiasing/tile_fill_5_mag.gif)