おっしゃる通り、フロイド・スタインバーグのディザ法は簡単で早いので人気があります。24 ビット カラーと 16 ビット カラーの微妙な違いについては、視覚的にほぼ最適な結果が得られます。
サンプル画像のレナを使用するよう提案されましたが、使用しないことにしました。テスト画像としての長い歴史にもかかわらず、現代の感性には性差別的すぎると思います. 代わりに、私は自分の写真を提示します。最初はオリジナルで、ディザリングされた 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);
}
}
このコードが完璧であることは保証しません。別のコメントでほのめかした微妙なエラーの 1 つを修正する必要がありました。しかし、それは上記の結果を生成しました。これは、Windows で使用される BGR 順序で 24 ビット ピクセルを取り、リトル エンディアン順序で R5G6B5 16 ビット ピクセルを生成します。