抖动:有序和Floyd-Steinberg,单色 & 彩色





5.00/5 (7投票s)
图像抖动算法集
引言
本文提供了一些抖动算法的简单、快速、实用的实现。
在互联网上,有很多理论,但缺乏实现。所以我决定提供相反的内容 - 实用的、快速的实现,而无需理论。
背景
作为一名程序员的职业生涯早期,我不得不实现一个“HTML 到 WAP 转换器”插件。该插件必须作为代理服务器的一部分工作,以便允许手机(我用我的诺基亚 3330 测试 WAP 转码器)浏览常规网站。由于当时的手机有单色显示屏,我必须在运行时将所有图像转换为黑/白。
因此,我需要一个快速的算法来实现高质量的单色图像抖动。
最后,我实现了Floyd-Steinberg和我的有序抖动算法。后来,我发现了Bayer算法,它更好,更快。
后来,作为我在另一个项目中的工作的一部分,我实现了三个平面(红、绿、蓝)的抖动算法,这些算法生成了6bpp、12bpp和15bpp的图像。这些抖动算法的实现也包含在本文中。
这里有一些使用演示项目生成的示例
有序单色(1 bpp)
有序 3bpp
有序 6bpp
Using the Code
项目中有一对文件: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 个带(从黑到白);6 bpp 抖动使用 3 个带(每个颜色平面 3 位);12 bpp 抖动使用 4 个带(每个颜色平面 4 位)等。
因此,为了执行简单的抖动,您应该执行以下操作
#include "Dither.h"
...
// This will convert the color image to black/white using 16x16 matrix
makeDitherBayer16( pixels, width, height );
共有 3 个函数块
- 有序抖动 - 使用不同阈值矩阵的黑/白有序抖动函数:2x2、3x3、4x4、8x8、16x16。
- 彩色有序抖动 - 将彩色图像转换为对其每个颜色平面分别应用抖动的图像(红、绿、蓝)。这意味着在红色平面、绿色平面和蓝色平面上应用抖动。
- Floyd-Steinberg抖动 - 误差扩散算法的函数:Floyd-Steinberg、Sierra(3 行)和Sierra Lite,以及它们的彩色版本。
/////////////////////////////////////////////////////////////////////////////
// Ordered dither using matrix
/////////////////////////////////////////////////////////////////////////////
void makeDitherBayer16( BYTE* pixels, int width, int height );
void makeDitherBayer8 ( BYTE* pixels, int width, int height );
void makeDitherBayer4 ( BYTE* pixels, int width, int height );
void makeDitherBayer3 ( BYTE* pixels, int width, int height );
void makeDitherBayer2 ( BYTE* pixels, int width, int height );
/////////////////////////////////////////////////////////////////////////////
// Colored Ordered dither, using 16x16 matrix
// (dither is applied on all color planes (r,g,b)
/////////////////////////////////////////////////////////////////////////////
void makeDitherBayerRgbNbpp
( BYTE* pixels, int width, int height, int ncolors ) noexcept;
void makeDitherBayerRgb3bpp ( BYTE* pixels, int width, int height ) noexcept;
void makeDitherBayerRgb6bpp ( BYTE* pixels, int width, int height ) noexcept;
void makeDitherBayerRgb9bpp ( BYTE* pixels, int width, int height ) noexcept;
void makeDitherBayerRgb12bpp( BYTE* pixels, int width, int height ) noexcept;
void makeDitherBayerRgb15bpp( BYTE* pixels, int width, int height ) noexcept;
void makeDitherBayerRgb18bpp( BYTE* pixels, int width, int height ) noexcept;
/////////////////////////////////////////////////////////////////////////////
// Floyd-Steinberg dither
/////////////////////////////////////////////////////////////////////////////
void makeDitherFS ( BYTE* pixels, int width, int height ) noexcept;
void makeDitherFSRgbNbpp
( BYTE* pixels, int width, int height, int ncolors ) noexcept;
void makeDitherFSRgb3bpp ( BYTE* pixels, int width, int height ) noexcept;
void makeDitherFSRgb6bpp ( BYTE* pixels, int width, int height ) noexcept;
void makeDitherFSRgb9bpp ( BYTE* pixels, int width, int height ) noexcept;
void makeDitherFSRgb12bpp( BYTE* pixels, int width, int height ) noexcept;
void makeDitherFSRgb15bpp( BYTE* pixels, int width, int height ) noexcept;
void makeDitherFSRgb18bpp( BYTE* pixels, int width, int height ) noexcept;
void makeDitherSierraLiteRgbNbpp
( BYTE* pixels, int width, int height, int ncolors ) noexcept;
void makeDitherSierraRgbNbpp
( BYTE* pixels, int width, int height, int ncolors ) noexcept;
void makeDitherSierraLite ( BYTE* pixels, int width, int height ) noexcept;
void makeDitherSierra ( BYTE* pixels, int width, int height ) noexcept;
/////////////////////////////////////////////////////////////////////////////
代码本身很简单,而且易于阅读。例如
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; // y % 16
for( int x = 0; x < width; x++ )
{
col = x & 15; // x % 16
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”的滤波器。
请注意,在源代码中,有一些被注释掉的函数,它们被标记为“快速但不准确”。
它们的不准确之处仅在于接近白色和接近黑色的颜色细微差别。但这些算法仍然很好,可以使用。我把它留给你去玩代码并检查它们是如何工作的(看看测试图像上的灰度和色带)。
历史
- 2020年2月27日:初始版本