65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (7投票s)

2020 年 2 月 27 日

公共领域

3分钟阅读

viewsIcon

30043

downloadIcon

1687

图像抖动算法集

引言

本文提供了一些抖动算法的简单、快速、实用的实现。
在互联网上,有很多理论,但缺乏实现。所以我决定提供相反的内容 - 实用的、快速的实现,而无需理论。

背景

作为一名程序员的职业生涯早期,我不得不实现一个“HTML 到 WAP 转换器”插件。该插件必须作为代理服务器的一部分工作,以便允许手机(我用我的诺基亚 3330 测试 WAP 转码器)浏览常规网站。由于当时的手机有单色显示屏,我必须在运行时将所有图像转换为黑/白。

因此,我需要一个快速的算法来实现高质量的单色图像抖动。

最后,我实现了Floyd-Steinberg和我的有序抖动算法。后来,我发现了Bayer算法,它更好,更快。

后来,作为我在另一个项目中的工作的一部分,我实现了三个平面(红、绿、蓝)的抖动算法,这些算法生成了6bpp、12bpp和15bpp的图像。这些抖动算法的实现也包含在本文中。

这里有一些使用演示项目生成的示例

有序单色(1 bpp)

有序 3bpp

有序 6bpp

Using the Code

项目中有一对文件:Dither.hDither.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 个函数块

  1. 有序抖动 - 使用不同阈值矩阵的黑/白有序抖动函数:2x2、3x3、4x4、8x8、16x16。
  2. 彩色有序抖动 - 将彩色图像转换为对其每个颜色平面分别应用抖动的图像(红、绿、蓝)。这意味着在红色平面、绿色平面和蓝色平面上应用抖动。
  3. 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日:初始版本
© . All rights reserved.