在互联网上,关于图像抖动算法的理论资料非常丰富,但实际的编程实现却相对匮乏。因此,本文旨在提供一些快速且实用的图像抖动算法实现,而省略了理论部分的介绍。
在编程生涯早期,曾需要实现一个“HTML到WAP转换器”插件。这个插件需要作为代理服务器的一部分工作,以便让移动电话(使用Nokia 3330进行WAP转码器测试)能够浏览常规网站。由于当时的移动电话大多使用单色显示屏,需要实时将所有图像转换为黑白。
因此,需要一个快速且高质量的单色图像抖动算法。最终,实现了Floyd-Steinberg算法和有序抖动算法。后来,发现了Bayer算法,它更好且更快。
后来,在另一个项目中,实现了在三个平面(红、绿、蓝)上的抖动算法,这些算法生成了6bpp、12bpp和15bpp的图像。这些抖动算法的实现也包含在本文中。
项目中包含两个文件:Dither.h和Dither.cpp。要使用这些代码,只需要将这些文件放入项目中,并包含Dither.h文件。
这些文件包含了所有算法的实现。所有函数接收以下参数:
BYTE* pixels - 图像的像素数组,格式为24位BGR(b,g,r, b,g,r, ..., b,g,r)
int width - 图像的宽度(像素)
int height - 图像的高度(像素)
需要额外参数ncolors的函数,实际上指定了将应用于图像的抖动带数量。例如,黑白使用1个带(从黑到白);6bpp抖动使用3个带(每个颜色平面3位);12bpp抖动使用4个带(每个颜色平面4位),等等。
为了执行简单的抖动,应该这样做:
#include "Dither.h"
...
// 这将使用16x16矩阵将彩色图像转换为黑白
makeDitherBayer16(pixels, width, height);
有三组函数:
代码本身简单且易于阅读。例如:
void makeDitherBayer16(BYTE* pixels, int width, int height) noexcept {
int col = 0;
int row = 0;
for (int y = 0; y < height; y++) {
row = y & 15;
for (int x = 0; x < width; x++) {
col = x & 15;
const pixel blue = pixels[x * 3 + 0];
const pixel green = pixels[x * 3 + 1];
const pixel red = pixels[x * 3 + 2];
pixel color = ((red + green + blue) / 3 < BAYER_PATTERN_16X16[col][row] ? 0 : 255);
pixels[x * 3 + 0] = color; // blue
pixels[x * 3 + 1] = color; // green
pixels[x * 3 + 2] = color; // red
}
pixels += width * 3;
}
}
对于需要更多速度的人,他们可以定义自定义表,其中包含预乘以3的值,从而消除在将RGB转换为灰度时的除法。
请注意,抖动算法中使用的颜色量化会改变图像的平均对比度。对于彩色抖动,颜色本身变得更亮。这是正常的。
使用特定N(带数)进行抖动的函数会产生错误的误差扩散分布,其中一些值大于20。对于其他值,亮度会降低。这是由于算法不完善(使用基本离散化不当)所致。因此,建议使用特定算法,而不是使用N参数的算法。
彩色抖动可以用来“压缩”图像,同时保持相对良好的质量。
例如,RGB 6位方法为每个像素的颜色分量使用2位,因此每个像素使用6位,而不是原始的24位。为了恢复质量,可以应用“平均2x2”或“平均3x3”滤波器。
请注意,在源代码中,有注释的函数被标记为“快速但不准确”。