【问题标题】:What is a good, optimized C/C++ algorithm for converting a 24-bit bitmap to 16-bit with dithering?什么是用于将 24 位位图转换为 16 位抖动的良好优化 C/C++ 算法?
【发布时间】:2012-07-23 07:28:15
【问题描述】:

我一直在寻找一种优化(即快速)算法,该算法使用抖动将 24 位 RGB 位图转换为 16 位 (RGB565) 位图。我正在寻找 C/C++ 中的一些东西,我可以在其中实际控制如何应用抖动。 GDI+ 似乎提供了一些方法,但我不知道它们是否抖动。而且,如果他们做抖动,他们使用什么机制(弗洛伊德-斯坦伯格?)

有没有人有一个使用抖动进行位图颜色深度转换的好例子?

【问题讨论】:

  • 565 看起来仍然很糟糕,即使使用了最佳抖动。只是出于好奇,为什么要这样做?
  • @stark,我不同意,看这个例子:stackoverflow.com/a/3963150/5987
  • @stark - 这是因为我正在输出到仅支持 RGB565 的硬件。它不会显示在用户的显示器上。
  • @Mark 我注意到这是一张单色图像。
  • @stark 我很高兴在您喜欢的任何图像上重复实验。

标签: c++ c bitmap gdi+ dithering


【解决方案1】:

弗洛伊德-斯坦伯格抖动

for each y from top to bottom
   for each x from left to right
      oldpixel := pixel[x][y]
      newpixel := find_closest_palette_color(oldpixel)
      pixel[x][y] := newpixel
      quant_error := oldpixel - newpixel
      pixel[x+1][y] := pixel[x+1][y] + 7/16 * quant_error
      pixel[x-1][y+1] := pixel[x-1][y+1] + 3/16 * quant_error
      pixel[x][y+1] := pixel[x][y+1] + 5/16 * quant_error
      pixel[x+1][y+1] := pixel[x+1][y+1] + 1/16 * quant_error

我敢打赌,你可以轻松实现这一点!

【讨论】:

  • 是的,我也在维基百科文章中看到了该算法。但是,我的直觉告诉我,那里可能有更优化的 RGB565 抖动算法(即使它们不是 Floyd-Steinberg)。这个好像有点贵。如果没有人想出一个,我会把它标记为答案。
  • 这是一种很容易犯细微错误的算法。 @JacobJ,今晚我将尝试为您提供 C/C++ 实现。
  • @MarkRansom,谢谢!我现在正在尝试自己的实现,但我很想与您的想法进行比较!我刚刚注意到我实际上是从 32 位 ARGB 缓冲区 (UINT32*) 到 RGB565 缓冲区 (UINT16*)。最后 4 行似乎最具挑战性。我需要小心地移位。如果你今晚这样做,请张贴作为答案! ;-)
  • 我想知道您是否需要小心以线性 RGB 进行计算,然后在最后转换为 sRGB。你认为这会有很大的不同吗?
  • @AdrianMcCarthy,任何涉及加法或减法的 RGB 计算在线性空间中都是最正确的,但我发现直接使用 sRGB 在大多数情况下“足够接近”。
【解决方案2】:

正如您所提到的,Floyd-Steinberg 抖动方法很受欢迎,因为它简单快速。对于 24 位和 16 位颜色之间的细微差别,结果在视觉上几乎是最佳的。

有人建议我使用示例图片Lena,但我决定不这样做;尽管它作为测试图像的历史悠久,但我认为它对于现代情感来说过于性别歧视。相反,我展示了我自己的照片。首先是原始的,然后转换为抖动的 RGB565(并转换回 24 位进行显示)。

还有 C++ 中的代码:

inline BYTE Clamp(int n)
{
    n = n>255 ? 255 : n;
    return n<0 ? 0 : n;
}

struct RGBTriplet
{
    int r;
    int g;
    int b;
    RGBTriplet(int _r = 0, int _g = 0, int _b = 0) : r(_r), g(_g), b(_b) {};
};

void RGB565Dithered(const BYTE * pIn, int width, int height, int strideIn, BYTE * pOut, int strideOut)
{
    std::vector<RGBTriplet> oldErrors(width + 2);
    for (int y = 0;  y < height;  ++y)
    {
        std::vector<RGBTriplet> newErrors(width + 2);
        RGBTriplet errorAhead;
        for (int x = 0;  x < width;  ++x)
        {
            int b = (int)(unsigned int)pIn[3*x] + (errorAhead.b + oldErrors[x+1].b) / 16;
            int g = (int)(unsigned int)pIn[3*x + 1] + (errorAhead.g + oldErrors[x+1].g) / 16;
            int r = (int)(unsigned int)pIn[3*x + 2] + (errorAhead.r + oldErrors[x+1].r) / 16;
            int bAfter = Clamp(b) >> 3;
            int gAfter = Clamp(g) >> 2;
            int rAfter = Clamp(r) >> 3;
            int pixel16 = (rAfter << 11) | (gAfter << 5) | bAfter;
            pOut[2*x] = (BYTE) pixel16;
            pOut[2*x + 1] = (BYTE) (pixel16 >> 8);
            int error = r - ((rAfter * 255) / 31);
            errorAhead.r = error * 7;
            newErrors[x].r += error * 3;
            newErrors[x+1].r += error * 5;
            newErrors[x+2].r = error * 1;
            error = g - ((gAfter * 255) / 63);
            errorAhead.g = error * 7;
            newErrors[x].g += error * 3;
            newErrors[x+1].g += error * 5;
            newErrors[x+2].g = error * 1;
            error = b - ((bAfter * 255) / 31);
            errorAhead.b = error * 7;
            newErrors[x].b += error * 3;
            newErrors[x+1].b += error * 5;
            newErrors[x+2].b = error * 1;
        }
        pIn += strideIn;
        pOut += strideOut;
        oldErrors.swap(newErrors);
    }
}

我不能保证这段代码是完美的,我已经不得不修复我在另一条评论中提到的那些细微错误。但是它确实产生了上述结果。它采用 Windows 使用的 BGR 顺序的 24 位像素,并以小端顺序生成 R5G6B5 16 位像素。

【讨论】:

    【解决方案3】:

    我建议使用 有序抖动 (http://en.wikipedia.org/wiki/Ordered_dithering),因为 Floyd-Steinberg 需要更多处理 并且只能在静止状态下工作image / 不适用于动画或不断更改的显示。

    我创建了我自己的优化有序抖动,从 24/32 位 RGB 颜色到 16 位 RGB565 颜色,将阈值分成子像素(在我的AROMA project 中使用)。它比 Floyd-Steinberg 快得多,因为没有昂贵的计算(特别是没有乘法和 div 计算),并且能够用于动画,因为它使用了固定的门槛。

    它的质量也比维基上定义的有序抖动算法好得多。

    这里是抖动结果的示例:

    这里是来源。享受吧!

    /* Dither Tresshold for Red Channel */
    static const BYTE dither_tresshold_r[64] = {
      1, 7, 3, 5, 0, 8, 2, 6,
      7, 1, 5, 3, 8, 0, 6, 2,
      3, 5, 0, 8, 2, 6, 1, 7,
      5, 3, 8, 0, 6, 2, 7, 1,
    
      0, 8, 2, 6, 1, 7, 3, 5,
      8, 0, 6, 2, 7, 1, 5, 3,
      2, 6, 1, 7, 3, 5, 0, 8,
      6, 2, 7, 1, 5, 3, 8, 0
    };
    
    /* Dither Tresshold for Green Channel */
    static const BYTE dither_tresshold_g[64] = {
      1, 3, 2, 2, 3, 1, 2, 2,
      2, 2, 0, 4, 2, 2, 4, 0,
      3, 1, 2, 2, 1, 3, 2, 2,
      2, 2, 4, 0, 2, 2, 0, 4,
    
      1, 3, 2, 2, 3, 1, 2, 2,
      2, 2, 0, 4, 2, 2, 4, 0,
      3, 1, 2, 2, 1, 3, 2, 2,
      2, 2, 4, 0, 2, 2, 0, 4
    };
    
    /* Dither Tresshold for Blue Channel */
    static const BYTE dither_tresshold_b[64] = {
      5, 3, 8, 0, 6, 2, 7, 1,
      3, 5, 0, 8, 2, 6, 1, 7,
      8, 0, 6, 2, 7, 1, 5, 3,
      0, 8, 2, 6, 1, 7, 3, 5,
    
      6, 2, 7, 1, 5, 3, 8, 0,
      2, 6, 1, 7, 3, 5, 0, 8,
      7, 1, 5, 3, 8, 0, 6, 2,
      1, 7, 3, 5, 0, 8, 2, 6
    };
    
    /* Get 16bit closest color */
    BYTE closest_rb(BYTE c) { 
      return (c >> 3 << 3); /* red & blue */
    }
    BYTE closest_g(BYTE c) {
      return (c >> 2 << 2); /* green */
    }
    
    /* RGB565 */
    WORD RGB16BIT(BYTE r, BYTE g, BYTE b) {
      return ((WORD)((r>>3)<<11)|((g>>2)<<5)|(b>>3));
    }
    
    /* Dithering by individual subpixel */
    WORD dither_xy(
      int x, 
      int y, 
      BYTE r, 
      BYTE g, 
      BYTE b
    ){
      /* Get Tresshold Index */
      BYTE tresshold_id = ((y & 7) << 3) + (x & 7);
    
      r = closest_rb(
              MIN(r + dither_tresshold_r[tresshold_id], 0xff)
           );
      g = closest_g(
              MIN(g + dither_tresshold_g[tresshold_id], 0xff)
           );
      b = closest_rb(
              MIN(b + dither_tresshold_b[tresshold_id], 0xff)
           );
      return RGB16BIT(r, g, b);
    }
    
    /* Dithering Pixel from 32/24bit RGB 
     *
     * GetR, GetG, GetB -> Function to get individual color in pixel
     *
     */
    WORD dither_color_xy(int x, int y, DWORD col) {
      return dither_xy(x, y, GetR(col), GetG(col), GetB(col));
    }
    
    /* EXAMPLES */
    void ExampleDither1(WORD * dest, DWORD * src, int width, int height){
      int x, y;
      for (y=0; y<height; y++){
        for (x=0; x<width; x++){
          int pos = y * width + x;
          dest[pos] = dither_color_xy(x,y,src[pos]);
        }
      }
    }
    void ExampleDither2(WORD * dest, BYTE * src, int width, int height){
      int x, y;
      for (y=0; y<height; y++){
        for (x=0; x<width; x++){
          int pos = y * width + x;
          dest[pos] = dither_xy(x,y,src[pos*3],src[pos*3+1],src[pos*3+2]);
        }
      }
    }
    

    另一个结果(顶部 24 位 - 底部排序 RGB565-16 位): View full resolution image

    【讨论】:

      【解决方案4】:

      这是我使用 16x16 矩阵进行有序(拜耳)抖动的代码。 它产生每像素输出 15 位。 输入和输出都是每像素 3 个字节。输出使用 32 个值,缩放到范围 0..255 以进行可视化。您可以通过替换以下 3 行来轻松更改输出:

      pixels[x * 3 + 0]   = i1 * 8;
      pixels[x * 3 + 1]   = i2 * 8;
      pixels[x * 3 + 2]   = i3 * 8;
      

      代码针对速度进行了优化,可用于实时处理。

      左图是原图,右图是抖动的。

      代码如下:

      #ifndef MIN
      #define MIN(a,b)            (((a) < (b)) ? (a) : (b))
      #endif
      
      #ifndef MAX
      #define MAX(a,b)            (((a) > (b)) ? (a) : (b))
      #endif
      
      #ifndef CLAMP
      //  This produces faster code without jumps
      #define     CLAMP( x, xmin, xmax )      (x) = MAX( (xmin), (x) );   \
                                              (x) = MIN( (xmax), (x) )
      #endif
      
      const   int BAYER_PATTERN_16X16[16][16] =   {   //  16x16 Bayer Dithering Matrix.  Color levels: 256
                                                      {     0, 191,  48, 239,  12, 203,  60, 251,   3, 194,  51, 242,  15, 206,  63, 254  }, 
                                                      {   127,  64, 175, 112, 139,  76, 187, 124, 130,  67, 178, 115, 142,  79, 190, 127  },
                                                      {    32, 223,  16, 207,  44, 235,  28, 219,  35, 226,  19, 210,  47, 238,  31, 222  },
                                                      {   159,  96, 143,  80, 171, 108, 155,  92, 162,  99, 146,  83, 174, 111, 158,  95  },
                                                      {     8, 199,  56, 247,   4, 195,  52, 243,  11, 202,  59, 250,   7, 198,  55, 246  },
                                                      {   135,  72, 183, 120, 131,  68, 179, 116, 138,  75, 186, 123, 134,  71, 182, 119  },
                                                      {    40, 231,  24, 215,  36, 227,  20, 211,  43, 234,  27, 218,  39, 230,  23, 214  },
                                                      {   167, 104, 151,  88, 163, 100, 147,  84, 170, 107, 154,  91, 166, 103, 150,  87  },
                                                      {     2, 193,  50, 241,  14, 205,  62, 253,   1, 192,  49, 240,  13, 204,  61, 252  },
                                                      {   129,  66, 177, 114, 141,  78, 189, 126, 128,  65, 176, 113, 140,  77, 188, 125  },
                                                      {    34, 225,  18, 209,  46, 237,  30, 221,  33, 224,  17, 208,  45, 236,  29, 220  },
                                                      {   161,  98, 145,  82, 173, 110, 157,  94, 160,  97, 144,  81, 172, 109, 156,  93  },
                                                      {    10, 201,  58, 249,   6, 197,  54, 245,   9, 200,  57, 248,   5, 196,  53, 244  },
                                                      {   137,  74, 185, 122, 133,  70, 181, 118, 136,  73, 184, 121, 132,  69, 180, 117  },
                                                      {    42, 233,  26, 217,  38, 229,  22, 213,  41, 232,  25, 216,  37, 228,  21, 212  },
                                                      {   169, 106, 153,  90, 165, 102, 149,  86, 168, 105, 152,  89, 164, 101, 148,  85  }
                                                  };
      
      //  Color ordered dither using 15 bits per pixel (5 bit per color plane)
      void    makeDitherBayerRgb15bpp( BYTE* pixels, int width, int height )  noexcept
      {
          for( int y = 0; y < height; y++ )
          {
              int row = y & 15;   //  y % 16
              
              for( int x = 0; x < width; x++ )
              {
                  int col = x & 15;   //  x % 16
      
                  const int   t       = BAYER_PATTERN_16X16[col][row];
                  const int   corr    = (t / 31);
      
                  const int   blue    = pixels[x * 3 + 0];
                  const int   green   = pixels[x * 3 + 1];
                  const int   red     = pixels[x * 3 + 2];
      
                  int i1  = (blue  + corr) / 8;       CLAMP( i1, 0, 31 );
                  int i2  = (green + corr) / 8;       CLAMP( i2, 0, 31 );
                  int i3  = (red   + corr) / 8;       CLAMP( i3, 0, 31 );
      
                  pixels[x * 3 + 0]   = i1 * 8;   // Scale blue  back to 0..255
                  pixels[x * 3 + 1]   = i2 * 8;   // Scale green back to 0..255
                  pixels[x * 3 + 2]   = i3 * 8;   // Scale red   back to 0..255
              }
      
              pixels  += width * 3;
          }
      }
      

      您可以查看这篇文章了解更多抖动算法:

      Dithering implementations and demo

      【讨论】:

        猜你喜欢
        • 2011-01-23
        • 2015-05-16
        • 1970-01-01
        • 1970-01-01
        • 2011-01-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-08-21
        相关资源
        最近更新 更多