16番会議室「玉石混淆みんなで作るSample蔵」に寄せられたサンプル
"RE:ビットマップ誤差拡散で減色する"
この発言は #00922 河邦 正 さんのビットマップ誤差拡散で減色する に対するコメントです
【ビットマップの誤差拡散による固定パレットへの減色】
*:#922で紹介したサンプルコードの C++Bulder 版です。
誤差拡散法の原理については、該当する分野の解説書を探してください。
下記のサンプルでは、原理は誤差拡散ですが、変法(誤差収集?)を使用し
ています。
変法(誤差収集)のメリットは、
1:誤差データの記録が単純化する。
正統派(?)の誤差拡散では、周囲n点に誤差データを‘加算’する
ので、[誤差値の分割]+[読み出し]+[加算]+[書き込み]の3ステップ
をn回実行しますが、誤差収集法では計算した誤差値の[書き込み]だ
けで済みます。
2:誤差のバッファに書きこむ回数が減る。
その分、読み出す回数が増えるが、ライトキャッシュに対応する L1、
L2 は少数派なので、キャッシュメモリによる高速化が期待できます。
(1:の理由と関係大)
3:誤差を周囲に割り振る際に生じる値の切り捨てが少なくなる。
正統派の誤差拡散では、周囲n点に誤差データを割り振る際に端数の
切り捨てが生じます。これを元色データ側で吸収するためには元色の
データ型を拡張(例:BYTE型からInteger型)しなくてはならないが、
誤差収集法では、誤差バッファのデータ型を拡張して、元色のデータ
は型変換せずに利用できる。
でも、このメリットは肉眼では差が無いかもしれません(^^;。
でしょうか?
typedef struct{ int R, G, B; } TRGBDiff;
bool ReduceBitmapColors(Graphics::TBitmap *Dst, Graphics::TBitmap
*Src,
BYTE NumStepsR, BYTE NumStepsG, BYTE NumStepsB)
{
int i = NumStepsR * NumStepsG * NumStepsB;
bool Result =
(NumStepsR > 1)&&(NumStepsG > 1)&&(NumStepsB > 1)&&
(i <= 256)&&
(! Src->Empty) &&
(Src->PixelFormat >= pf15bit)&&(Src->PixelFormat <= pf32bit);
if(Result)
{
// 元ビットマップのフォーマットを(勝手に ^^;)変更
Src->PixelFormat = pf24bit;
// 減色したデータを入れるメモリストリームを作成する
TMemoryStream *Stream = new TMemoryStream;
try
{
// ストリームを減色済みビットマップの大きさにする
int DstLineSize = (Src->Width + 3)& -4;
Stream->Size = sizeof(TBitmapFileHeader) // ファイルヘッダ
+ sizeof(TBitmapInfoHeader) // ビットマップヘッダ
+ (sizeof(TRGBQuad) * i) // パレットデータ
+ (DstLineSize * Src->Height); // ビットデータ
ZeroMemory(Stream->Memory, Stream->Size);
// ファイルヘッダの書き込み
PBITMAPFILEHEADER pbfh = (PBITMAPFILEHEADER)Stream->Memory;
pbfh->bfType = 0x4d42;
pbfh->bfSize = Stream->Size;
pbfh->bfOffBits = sizeof(TBitmapFileHeader)
+ sizeof(TBitmapInfoHeader)
+ sizeof(TRGBQuad) * i;
// ビットマップヘッダの書き込み
PBITMAPINFO pbiDst =
(PBITMAPINFO)((int)Stream->Memory+sizeof(TBitmapFileHeader));
PBITMAPINFOHEADER pbih = (PBITMAPINFOHEADER)pbiDst;
pbih->biSize = sizeof(TBitmapInfoHeader);
pbih->biWidth = Src->Width;
pbih->biHeight = Src->Height;
pbih->biPlanes = 1;
pbih->biBitCount = 8;
pbih->biClrUsed = i;
// パレットデータの書き込み
RGBQUAD *pColors = pbiDst->bmiColors;
for(int R = 0; R < NumStepsR; R++)
for(int G = 0; G < NumStepsG; G++)
for(int B = 0; B < NumStepsB; B++)
{
i = (B * NumStepsG + G) * NumStepsR + R;
pColors[i].rgbRed = (BYTE)(R * 255 / (NumStepsR - 1));
pColors[i].rgbGreen = (BYTE)(G * 255 / (NumStepsG - 1));
pColors[i].rgbBlue = (BYTE)(B * 255 / (NumStepsB - 1));
}
// 誤差データ用バッファの確保
TRGBDiff* pDiffBuffer =
(TRGBDiff*)new BYTE[sizeof(TRGBDiff) * (Src->Width + 2)];
try
{
ZeroMemory(pDiffBuffer, sizeof(TRGBDiff)*(Src->Width+2));
for(int y = 0; y < Src->Height; y++)
{
// 減色したデータの書きこみ先(ポインタ)を計算する
BYTE* pDst = (BYTE*)(DWORD(Stream->Memory)
+ pbfh->bfOffBits
+ DWORD(DstLineSize * y));
// 元ビットマップの参照先(ポインタ)を記録する
RGBQUAD* pSrc = (RGBQUAD*)Src->ScanLine[Src->Height-y-1];
// 誤差データのポインタ(pDiff)をバッファの先頭にセット
TRGBDiff* pDiff = pDiffBuffer;
for(int x = 0; x < Src->Width; x++)
{
int R = pSrc->rgbRed+ //元の赤色に周辺3点の誤差を加算
((pDiff[0].R +pDiff[1].R)* 3 +pDiff[2].R * 2 +4)/ 8;
int G = pSrc->rgbGreen+ //元の緑色に周辺3点の誤差を加算
((pDiff[0].G +pDiff[1].G)* 3 +pDiff[2].G * 2 +4)/ 8;
int B = pSrc->rgbBlue+ //元の青色に周辺3点の誤差を加算
((pDiff[0].B +pDiff[1].B)* 3 +pDiff[2].B * 2 +4)/ 8;
// 周辺3点の誤差込みで近似色のパレットを計算する
*pDst = (BYTE)
(((B * (NumStepsB - 1) + 128) / 256 * NumStepsG +
(G * (NumStepsG - 1) + 128) / 256) * NumStepsR +
(R * (NumStepsR - 1) + 128) / 256);
// 理想の色とパレットの色との差を誤差として記録する
pDiff[1].R = R - pColors[*pDst].rgbRed;
pDiff[1].G = G - pColors[*pDst].rgbGreen;
pDiff[1].B = B - pColors[*pDst].rgbBlue;
// ポインタを次のピクセルに移動する
(int)pSrc += 3;
pDst++;
(int)pDiff += sizeof(TRGBDiff);
} // for(int x = 0;...
} // for(int y = 0;...
}__finally{
delete pDiffBuffer; // 誤差データ用バッファの解放
}
// 減色したデータを出力先の TBitmap にロードする
Stream->Position = 0;
Dst->LoadFromStream(Stream);
}__finally{
// 減色したデータを入れたメモリストリームの解放
delete Stream;
}
}
return Result;
}
実行例は、
if(OpenPictureDialog1->Execute())
{
Image1->Picture->Bitmap->LoadFromFile(
OpenPictureDialog1->FileName);
ReduceBitmapColors(
Image1->Picture->Bitmap, // 出力
Image1->Picture->Bitmap, // 入力
6, 8, 5); // この位がお勧め
}
です。
P.S.
最適化の癖なのか Delphi用のサンプルコードより実行速度が遅い
ようです。
1999/08/28、河邦 正(GCC02240@nifty.ne.jp)
(http://member.nifty.ne.jp/kht0000/ 自作ComponentのNifty外へ公開用)
Original document by 河邦 正 氏 ID:(GCC02240)
ここにあるドキュメントは NIFTY SERVEの Delphi Users' Forum の16番会議室「玉石混淆みんなで作るSample蔵」に投稿されたサンプルです。これらのサンプルはボーランド株式会社がサポートする公式のものではありません。また、必ずしも動作が検証されているものではありません。これらのサンプルを使用したことに起因するいかなる損害も投稿者、およびフォーラムスタッフはその責めを負いません。使用者のリスクの範疇でご使用下さい。
Copyright 1996-2002 Delphi Users' Forum
|