Какой хороший, оптимизированный алгоритм на C/C++ для преобразования 24-битного растрового изображения в 16 бит с дизерингом?

Я искал оптимизированный (т. е. быстрый) алгоритм, который преобразует 24-битную растровую карту RGB в 16-битную (RGB565) растровую карту с помощью сглаживания. Я ищу что-то в C/C++, где я могу контролировать то, как применяется дизеринг. GDI+, похоже, предоставляет некоторые методы, но я не могу сказать, дрожат они или нет. И, если они действительно дрожат, какой механизм они используют (Флойд-Штейнберг?)

У кого-нибудь есть хороший пример преобразования глубины цвета растрового изображения с дизеринг?

3 ответов


Как вы упомянули, метод Флойда-Штейнберга популярен, потому что он прост и быстр. Для тонких различий между 24-битным и 16-битным цветом результаты будут почти оптимальными визуально.

было предложено использовать образец изображения Лена

OriginalFloyd-Steinberg Dithered RGB565

и код, в 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);
    }
}

Я не гарантирую, что этот код идеален, мне уже пришлось исправить одну из тех тонких ошибок, на которые я ссылался в другом комментарии. Однако это дало вышеуказанные результаты. Он принимает 24-битные пиксели в порядке BGR, как используется Windows, и производит 16-битные пиксели R5G6B5 в маленьком эндианском порядке.


Я предложил использовать заказал дизеринг (http://en.wikipedia.org/wiki/Ordered_dithering), поскольку Флойд-Штейнберг нужно больше обработки и вычисление и работает только на неподвижном изображении / не работает хорошо для анимированных или постоянных изменений на дисплее.

Я создал свой собственный оптимизированный упорядоченный сглаживание от 24 / 32bit RGB цвета до 16bit RGB565 цвета, что отдельный tresshold в субпиксель (используется в my аромат проект). Это было намного быстрее, чем Флойд-Штейнберг, потому что нет дорогостоящих вычислений (особенно нет умножает и divs расчеты), и может использоваться на анимации, потому что он использовал фиксированный tresshold.

это качество также намного лучше, чем упорядоченный алгоритм смешивания, определенный на wiki.

вот пример результата смешивания:

enter image description here

и вот источник. Наслаждайтесь!

/* 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]);
    }
  }
}

другой результат (верхний 24bit-Нижний приказал RGB565-16bit): enter image description here Просмотр изображения в полном разрешении


Флойда–Стейнберга сглаживание

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

Я ставлю доллар, что вы можете легко реализовать это!