What is a good, optimized C/C++ algorithm for converting a 24-bit bitmap to 16-bit with dithering?

Viewed 13517

I've been looking for an optimized (i.e., quick) algorithm that converts a 24-bit RGB bitmap to a 16-bit (RGB565) bitmap using dithering. I'm looking for something in C/C++ where I can actually control how the dithering is applied. GDI+ seems to provide some methods, but I can't tell if they dither or not. And, if they do dither, what mechanism are they using (Floyd-Steinberg?)

Does anyone have a good example of bitmap color-depth conversion with dithering?

4 Answers

Here is my code that does ordered (Bayer) dithering with 16x16 matrix. It produces 15 bits per pixel output. Both input and output are 3 bytes per pixel. The output uses 32 values, scaled to range 0..255 for visualising purposes. You can easily change the output as you wish by replacing these 3 lines:

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

The code is optimized for speed and can be used for realtime processing.

The left picture is the original and the right is dithered. Bayer dither 15bpp color

Here is the code:

#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;
    }
}

You can check this article for more dithering algorythms:

Dithering implementations and demo

Related