Device Indenendent Bitmap(以下DIB)は形式がきちんと定義されたビットマップである. DIBのバイトデータはアプリケーションから自由にアクセスできるユーザーモードのメモリ上にあるため, 高速な処理が可能である. 32ビットのメモリ空間が使えるようになってからは,DDBと言えどもDIBと同じ形式を採用していることが多いらしい. DIBと同じ形式を採用することでGDIを使った描画が可能になり,ドライバ開発の労力が軽減されるからである.
DelphiのTBitmapでは特に何も指定しなければDDBが作られる. これはver.2.0までは内部形式がDDBだったので,それとの互換性を保つためらしい. 内部形式をDIBに変更するには,HandleType プロパティでbmDIBに指定すればよい. ただし,中村さんの解説によると, LoadFromFileメソッドでファイルからビットマップを読み込んだ場合は 自動的にbmDIBになる. ファイルの形式は常にDIBであるため,読み込んだ後にわざわざDDBに変換しないらしい.
DIBを作るには,ビットマップの幅,高さ,1ピクセルあたりのビット数等を格納したBITMAPINFO構造体と
バイトデータを格納した配列が必要である.
CreateDIBitmapはこれらの情報からDIBを作成し,そのハンドル(ビットマップハンドル)を返す.
例えば以下のように使う.
procedure TForm1.Button5Click(Sender: TObject);
var
BmpInfo: TBitmapInfo;
ImageWidth, ImageHeight, ImageSize, X, Y, Index: Integer;
ColorBits: PRGBTriple;
Bmp: HBITMAP;
P: Pointer;
begin
// ビットマップの幅,高さ,サイズ
ImageWidth := 256;
ImageHeight := 256;
ImageSize := ImageWidth * ImageHeight * 3;
// BmpInfoの各メンバを0で初期化
FillChar( BmpInfo, SizeOf( BmpInfo ), 0 );
// 必要な情報を格納
with BmpInfo.bmiHeader do
begin
biSize := SizeOf( TBitmapInfoHeader );
biWidth := ImageWidth;
biHeight := ImageHeight;
biPlanes := 1;
biBitCount := 24; // R,G,B各8ビットの24ビットとする.これが一番簡単.
biCompression := BI_RGB;
biSizeImage := ImageSize;
biClrUsed := 0;
end;
// ピクセルの色情報を格納する配列
GetMem( P, ImageSize );
ColorBits := PRGBTRIPLE( P );
Index := 0;
for Y := 0 to ImageWidth - 1 do
begin
for X := 0 to ImageHeight - 1 do
begin
ColorBits^.rgbtBlue := X;
ColorBits^.rgbtGreen := X;
ColorBits^.rgbtRed := X;
Inc( ColorBits );
end;
end;
// DIBを作成
Bmp := CreateDIBitmap( Image.Canvas.Handle, BmpInfo.bmiHeader,
CBM_INIT, P, BmpInfo, DIB_RGB_COLORS );
if Bmp <> 0 then
begin
Image.Picture.Bitmap.Handle := Bmp;
Image.Refresh;
end;
FreeMem( P );
end;
StretchDIBitsを使えばビットマップハンドルを生成せずに直接
デバイスコンテキストに描くことができる.
上の例なら,CreateDIBitmapを呼び出す代わりに,
StretchDIBits( Image.Canvas.Handle, 0, 0, ImageWidth, ImageHeight,
0, 0, ImageWidth, ImageHeight, P, BmpInfo, DIB_RGB_COLORS, SRCCOPY );
また,GetObjectを使えば,すでにメモリ上にロードされたDIBのバイトデータへのポインタを
得ることができる.
例えば以下のようにする.
procedure TForm1.Button2Click(Sender: TObject);
var
DIBInfo: TDIBSection;
Capacity, Pos: Integer;
RGBInfo: PRGBTriple;
Tone: Byte;
begin
with Image.Picture do
begin
Bitmap.HandleType := bmDIB;
Bitmap.PixelFormat := pf24bit;
GetObject( Bitmap.Handle, SizeOf( DIBInfo ), @DIBInfo );
with DIBInfo.dsBm do
begin
Capacity := bmWidth * bmHeight;
Pointer( RGBInfo ) := bmBits;
end;
end;
Pos := 0;
while Pos < Capacity do
begin
Tone := Round( ( RGBInfo^.rgbtRed + RGBInfo^.rgbtGreen + RGBInfo^.rgbtBlue ) / 3 );
RGBInfo^.rgbtRed := Tone;
RGBInfo^.rgbtGreen := Tone;
RGBInfo^.rgbtBlue := Tone;
Inc( RGBInfo );
Inc( Pos );
end;
Image.Refresh;
end;
ちなみに,CanvasのPixelsプロパティを使えば簡単にピクセルの色情報を取得することができる. このプロパティは内部的にGetPixelおよびSetPixelを呼び出しているだけだ. これらのAPIは8bppや24bppといったビットマップの形式に拘わらずピクセルのRGB値を 返したり設定したりしてくれるので大変ありがたいのだが,その分オーバーヘッドも大きい. グレースケール変換などの画像処理では一度に大量のピクセルを処理しなければならないから, バイトデータに直接アクセスする方がはるかに高速だ.
試しに上のグレースケール変換を,Pixelsプロパティを使って
procedure TForm1.Button7Click(Sender: TObject);
var
ThePixel: TColor;
Started: DWORD;
X, Y: Integer;
R, G, B, Tone: Byte;
begin
Started := GetCurrentTime;
with Image.Picture do
begin
Bitmap.HandleType := bmDIB;
Bitmap.PixelFormat := pf24bit;
for Y := 0 to Bitmap.Height - 1 do
begin
for X := 0 to Bitmap.Width - 1 do
begin
ThePixel := Bitmap.Canvas.Pixels[ X, Y ];
R := GetRValue( ThePixel );
G := GetGValue( ThePixel );
B := GetBValue( ThePixel );
Tone := Round( ( R + G + B ) / 3 );
Bitmap.Canvas.Pixels[ X, Y ] := RGB( Tone, Tone, Tone );
end;
end;
end;
Image.Refresh;
ShowMessage( IntToStr( GetCurrentTime - Started ) );
end;
さて,以上のようにDIBは各ピクセルへの高速なアクセスを可能にする大変結構なビットマップなのだが,
逆に言えばピクセル単位でのアクセスしかできないので,複雑な図形の描画には全く不便である.
DIBSectionは,デバイスコンテキスト(DC)とDIBとを関連付け,
各種GDIを使ってDIBに自由に描画することができる手段を提供するものである.
CreateDIBSectionを使ってDIBを作成し,これをSelectObjectで
DCに選択すれば,以降のDCに対する描画がDIBにも反映される.
以下に具体例を示す.
procedure TForm1.Button4Click(Sender: TObject);
var
BmpInfo: TBitmapInfo;
DIB, OldBmp: HBITMAP;
DC: HDC;
P: Pointer;
OldBrush, NewBrush: HBRUSH;
ImageWidth, ImageHeight, ImageSize, X, Y, Count: Integer;
R, G, B: Byte;
begin
ImageWidth := 256;
ImageHeight := 256;
ImageSize := ImageWidth * ImageHeight * 3;
FillChar( BmpInfo, SizeOf( BmpInfo ), 0 );
with BmpInfo.bmiHeader do
begin
biSize := SizeOf( TBitmapInfoHeader );
biWidth := ImageWidth;
biHeight := ImageHeight;
biPlanes := 1;
biBitCount := 24;
biCompression := BI_RGB;
biSizeImage := ImageSize;
biClrUsed := 0;
end;
// ImageのCanvasと互換性のあるメモリデバイスコンテキストを作成
DC := CreateCompatibleDC( Image.Canvas.Handle );
// DIBSectionを作成
DIB := CreateDIBSection( DC, BmpInfo, DIB_RGB_COLORS, P, 0, 0 );
// DCに選択
OldBmp := SelectObject( DC, DIB );
// DC に描画
NewBrush := CreateSolidBrush( clYellow );
OldBrush := SelectObject( DC, NewBrush );
Rectangle( DC, 0, 0, ImageWidth, ImageHeight );
SelectObject( DC, OldBrush );
DeleteObject( NewBrush );
for Count := 0 to 999 do
begin
R := Round( Random * 255 );
G := Round( Random * 255 );
B := Round( Random * 255 );
NewBrush := CreateSolidBrush( RGB( R, G, B ) );
OldBrush := SelectObject( DC, NewBrush );
X := Round( Random * ImageWidth );
Y := Round( Random * ImageHeight );
Ellipse( DC, X - 10, Y - 10, X + 10, Y + 10 );
SelectObject( DC, OldBrush );
DeleteObject( NewBrush );
end;
// DIB を転送
StretchDIBits( Image.Canvas.Handle, 0, 0, ImageWidth, ImageHeight,
0, 0, ImageWidth, ImageHeight, P, BmpInfo, DIB_RGB_COLORS, SRCCOPY );
Image.Refresh;
// DCへの選択を解除
SelectObject( DC, OldBmp );
DeleteObject( DIB );
DeleteDC( DC );
end;
描画自体はGDIを使って行うが,Imageへの描画はStretchDIBitsを使っている. DCへの描画結果がDIBSectionのバイトデータに反映されていることがわかる.
SEO | [PR] 爆速!無料ブログ 無料ホームページ開設 無料ライブ放送 | ||