Windows9Xで8bitモノクロ画像を作る方法


8bpp(bits per pixel)のモノクロ画像を作る方法をまとめる. 24bppのフルカラー画像を8bppのモノクロ画像に変換すればファイルサイズは約3分の1になる (ビットマップ情報を格納したヘッダがくっつくため厳密に3分の1にはならない). 論文等に貼り込む画像がモノクロで構わないならこの方法は大変有効だ.

DelphiのTBitmapクラスを使えば8bppのモノクロ画像を簡単に作成することができる. 例えば,ColorImageに24bppフルカラー画像が保持されており, これを8bppモノクロに変換した画像をMono8Imageに表示する場合,以下のようにする.

procedure TForm1.Button1Click(Sender: TObject);
var
  i, j: Integer;
  Bmp: TBitmap;
  PSrc, PDest: PRGBTRIPLE;
  Tone: Byte;
begin
   // テンポラリのビットマップを作成
   Bmp := TBitmap.Create; 
   try
     // ビットマップの大きさを設定
     Bmp.Width := ColorImage.Picture.Bitmap.Width;
     Bmp.Height := ColorImage.Picture.Bitmap.Height;
     // 当然 DIB にする
     Bmp.HandleType := bmDIB;
     // 24bpp にする
     Bmp.PixelFormat := pf24bit;
     
     for i := 0 to ColorImage.Picture.Bitmap.Height - 1 do
     begin
        // フルカラー画像の第i行目のバイト列へのポインタを取得
        PSrc := ColorImage.Picture.Bitmap.ScanLine[ i ];
        // モノクロ画像の第i行目のバイト列へのポインタを取得
        PDest := Bmp.ScanLine[ i ];
        for j := 0 to ColorImage.Picture.Bitmap.Width - 1 do
        begin
           // フルカラー画像のRGB値からモノクロの階調値を計算
           Tone := Trunc( ( PSrc^.rgbtRed + PSrc^.rgbtGreen + PSrc^.rgbtBlue ) / 3 );
           // モノクロ画像のRGBを設定
           PDest^.rgbtRed := Tone;
           PDest^.rgbtGreen := Tone;
           PDest^.rgbtBlue := Tone;
           // ポインタを 3 バイト進める
           Inc( PSrc );
           Inc( PDest );
        end;
     end;
     //  最後に 8bpp にする! これが重要!!
     Bmp.PixelFormat := pf8bit;
     Mono8Image.Picture.Bitmap := Bmp;
   finally
     Bmp.Free;
   end;
end;

すなわち,まず原画像から24bppのモノクロ画像(RGB値がすべて等しい)を作成し,最後にPixelFormatをpf8bppに設定すればよい. 原画像のRGB値からモノクロ画像の階調値を計算するには, 上の例で用いた単純平均法以外にも NTSC加重平均法 がある. NTSC加重平均法を用いれば人間の視覚により近づけることが可能だ. RGB値を取得する際は間違ってもCanvas.Pixelsを使ってはいけない. 折角のDIBなのだから.

ただし上のコードには落とし穴があって, WindowsNT/2K上ではそれなりに動作するものの, Windows9Xではマトモな8bppモノクロ画像が得られない. Win2KおよびWin98SEでの実行結果は以下のようになる. 24bppモノクロを得るには上のコードでPixelFormatをpf8bitにしなければよい. これは2Kと98SEで同じ結果が得られる.

フルカラー画像
24bppモノクロ
8bppモノクロ(Win2K)
8bppモノクロ(Win98SE)

Win9Xで正常に動作しない理由はこうである. 24bppビットマップを保持しているTBitmapオブジェクトのPixelFormatがpf8bitに設定された場合, 24bppビットマップはパレットを持たないため, TBitmapは内部的にそのビットマップ(厳密言えばそのビットマップが割り当てられているメモリデバイスコンテキスト)に適合した論理パレットを作成し, 自身のPaletteプロパティに割り当てる. 論理パレットを作成する際は CreateHalftonePalette というAPIを呼び出すが, Win9XのCreateHalftonePaletteにはバグがあり,いい加減なパレットを作成するのである.

ちなみにこのバグはWindowsMeでも直っていない. このような重大なバグを残したまま万人に受け入れられ着実にバージョンアップしてきたWindowsはある意味凄いOSである.

NT/2K系のCreateHalftonePaletteはバグこそないものの,最大公約数的なパレットしか作成しないので24bppビットマップの減色には全く役に立たない (このことはここにも書いた). 上のコードがNT/2K上であまりボロが出ないのは, PixelFormatをpf8bitに変更する前のビットマップが24bppのモノクロのため使われている色数が少ないからである. 24bppと8bppの結果を凝視すれば画像の劣化が見て取れる.

さて,Win9X/Meで正しい8bppモノクロを作成するにはどうすればよいか? これはCreateHalftonePaletteに頼らずに自分で論理パレットを作成するより他に手は無い. 以下に示すCreateGrayScalePalette関数は256階調のモノクロパレットを作成するものである.

function CreateGrayScalePalette: HPALETTE;
var
  Palette: ^TLogPalette;
  i: Integer;
begin
   GetMem( Palette, SizeOf( TLogPalette ) + SizeOf( TPaletteEntry ) * 256 );
   Palette^.palNumEntries := 256;
   Palette^.palVersion := $0300;
   for i := 0 to 255 do begin
      Palette^.palPalEntry[ i ].peRed   := i;
      Palette^.palPalEntry[ i ].peGreen := i;
      Palette^.palPalEntry[ i ].peBlue  := i;
      Palette^.palPalEntry[ i ].peFlags := 0;
   end;
   Result := CreatePalette( Palette^ );
   FreeMem( Palette );
end;

TLogPalette構造体に必要な情報を格納し,CreatePalette APIに渡す. 成功すればパレットのハンドルが返ってくる. 詳細な説明は ここ にも書いた.

CreateGrayScalePaletteを使えば,8bppのモノクロ画像を作成するコードは以下のように書ける.

procedure TForm1.Button2Click(Sender: TObject);
var
  i, j: Integer;
  Bmp: TBitmap;
  PSrc: PRGBTRIPLE;
  PDest: PByte;
  Tone: Byte;
begin
   // テンポラリのビットマップを作成
   Bmp := TBitmap.Create;
   try
     // ビットマップのサイズを設定
     Bmp.Width := ColorImage.Picture.Bitmap.Width;
     Bmp.Height := ColorImage.Picture.Bitmap.Height;
     // 当然 DIB にする
     Bmp.HandleType := bmDIB;
     // 8bpp にする
     Bmp.PixelFormat := pf8bit;
     // 自作のパレットを割り当てる
     Bmp.Palette := CreateGrayScalePalette;
     
     for i := 0 to ColorImage.Picture.Bitmap.Height - 1 do
     begin
        // フルカラー画像の第i行目のバイト列へのポインタを取得
        PSrc := ColorImage.Picture.Bitmap.ScanLine[ i ];
        // モノクロ画像の第i行目のバイト列へのポインタを取得
        PDest := Bmp.ScanLine[ i ];
        for j := 0 to ColorImage.Picture.Bitmap.Width - 1 do
        begin
           // フルカラー画像のRGB値からモノクロの階調値を計算
           Tone := Trunc( ( PSrc^.rgbtRed + PSrc^.rgbtGreen + PSrc^.rgbtBlue ) / 3 );
           // モノクロ画像の階調値を設定
           PDest^ := Tone;
           // ポインタを 3 バイト進める
           Inc( PSrc );
           // ポインタを 1 バイト進める
           Inc( PDest );
        end;
     end;
     Mono8Image.Picture.Bitmap := Bmp;
   finally
     Bmp.Free;
   end;
end;

つまり,PixelFormatをpf8bitとした後自作の正しいパレットを割り当てておき, あとはポインタを動かしながら全ピクセルの階調値を計算すればよい. このコードでは9X/MeとNT/2Kでほぼ同じ結果が得られる.

フルカラー画像
24bppモノクロ
8bppモノクロ(Win2K)
8bppモノクロ(Win98SE)

CreateGrayScalePaletteで作成する自作パレットには24bppモノクロで使われている全ての色がエントリーされているので, 24bppから8bppへの変換において画像の劣化がほとんど無い. "ほとんど" というのは,論理パレットがシステムパレットにマッピングされる際に, システムパレットに予約された20色分が論理パレットにエントリーした256色から落ちてしまうからである. ちなみに元のフルカラー画像のファイルサイズは125638Byteであり, 8bppモノクロ画像は2K,98SEのいずれも43162Byteであった(ページ上の画像はJPEGに変換してある).


お問い合わせはメールにて: akasaka@klc.ac.jp

戻る
SEO [PR] 爆速!無料ブログ 無料ホームページ開設 無料ライブ放送