はじめに
Delphiに関するpngファイルの質問でアルファデータがうまく反映出来ないとう悩みを良くみかけるのですが、そのような問題を解決するには、GDI+がおすすめです
ファイルを読み込む時に自動で画像形式を判別して、Canvasに描画するときもアルファデータを適切に処理します
関連クラス
主なクラスは以下の2つになります。
- TGPBitmap
- TGPGraphics
ユニットに以下を追加して使用します
uses
GDIPAPI, GDIPOBJ, GDIPUTIL;
GDIPUTIL
はGetEncoderClsid関数を使用しなければ不要です
TGPBitmap
-
概要
GDI+ における画像データを表現するクラスです。
PNG・JPEG・BMP などをサポートし、アルファチャンネル(透過情報)も保持可能。
pngファイルをbmpファイルで保存する例
uses
GDIPAPI, GDIPOBJ, GDIPUTIL;
var
bmp: TGPBitmap;
EncoderClsid: TGUID;
begin
// ファイル形式から自動で画像種類を判別して画像を読み込む
bmp := TGPBitmap.Create('sample.png');
try
if GetEncoderClsid('image/bmp', EncoderClsid) >= 0 then
bmp.Save('output.bmp', EncoderClsid)
else
raise Exception.Create('BMP encoder not found');
finally
bmp.Free;
end;
end;
TGPGraphics
-
概要
GDI+ の描画処理を担うクラスです。キャンバス (TCanvas.Handle
) に関連付けて使うことで、文字や図形、画像を高品質に描画可能。 -
主な用途
- Delphi の
TCanvas
に GDI+ の画像を描画する - 高解像度やアンチエイリアスを利用した描画
- Delphi の
-
特徴
-
DrawImage
を使ってTGPBitmap
をキャンバスに転送可能 - 高度な描画オプションを提供(補間モード、スムージングなど)
-
Canvas に描画する例
uses
GDIPAPI, GDIPOBJ;
procedure DrawOnCanvas(Canvas: TCanvas; GPBitmap: TGPBitmap; X, Y: Integer);
var
graphics: TGPGraphics;
begin
graphics := TGPGraphics.Create(Canvas.Handle);
try
graphics.DrawImage(GPBitmap, X, Y);
finally
graphics.Free;
end;
end;
// 使用例
drawOnCanvas(Form1.Canvas, bmp, 10, 10);
保存できるファイル形式
以下のような形式のファイルで保存が可能です。
- image/bmp
- image/jpeg
- image/gif
- image/tiff
- image/png
汎用的に使えるようにいくつかの便利な関数を考えました
変換元 | 変換先 | 関数 |
---|---|---|
HBITMAP | TGPBitmap | CreateTGPBitmapFromHBITMAP |
TBitmap | TGPBitmap | CreateTGPBitmapFromTBitmap |
TGPBitmap | TBitmap | CreateTBitmapFromTGPBitmap |
クリップボード | TGPBitmap | CreateTGPBitmapFromClipboard |
TGPBitmap | クリップボード | CopyGPBitmapToClipboard |
コードを見る
const
CLSID_BMP: TGUID = '{557CF400-1A04-11D3-9A73-0000F81EF32E}';
CLSID_PNG: TGUID = '{557CF406-1A04-11D3-9A73-0000F81EF32E}';
CLSID_JPG: TGUID = '{557CF401-1A04-11D3-9A73-0000F81EF32E}';
CLSID_GIF: TGUID = '{557CF402-1A04-11D3-9A73-0000F81EF32E}';
CLSID_TIF: TGUID = '{557CF405-1A04-11D3-9A73-0000F81EF32E}';
uses
GDIPAPI, GDIPOBJ, Clipbrd;
function CreateTGPBitmapFromHBITMAP(hBmp: HBITMAP): TGPBitmap;
var
BmpInfo: BITMAPINFO;
BmpData: TBitmapData;
Pixels: TArray<ARGB>;
DC: HDC;
Width, Height: Integer;
// アルファ値がない場合、不透明化する
procedure MakeOpaqueIfNoAlpha();
var
i: Integer;
begin
for i := 0 to High(Pixels) do
if GetAlpha(Pixels[i]) <> 0 then Exit;
for i := 0 to High(Pixels) do
Pixels[i] := Pixels[i] or $FF000000; // アルファ値にFFを設定
end;
begin
DC := CreateCompatibleDC(0);
try
FillChar(BmpInfo, SizeOf(BmpInfo), 0);
BmpInfo.bmiHeader.biSize := SizeOf(BITMAPINFOHEADER);
// ビットマップの幅と高さを取得
GetDIBits(DC, hBmp, 0, 0, nil, BmpInfo, DIB_RGB_COLORS);
Width := BmpInfo.bmiHeader.biWidth;
Height := Abs(BmpInfo.bmiHeader.biHeight);
// ピクセルデータを取得
BmpInfo.bmiHeader.biHeight := -Height; // トップダウン形式に変更
BmpInfo.bmiHeader.biPlanes := 1;
BmpInfo.bmiHeader.biBitCount := 32;
BmpInfo.bmiHeader.biCompression := BI_RGB;
SetLength(Pixels, Width * Height);
// ピクセルデータを読み出し
GetDIBits(DC, hBmp, 0, Height, @Pixels[0], BmpInfo, DIB_RGB_COLORS);
finally
DeleteDC(DC);
end;
// アルファ値がない場合、不透明化する
MakeOpaqueIfNoAlpha();
Result := TGPBitmap.Create(Width, Height, PixelFormat32bppARGB);
Result.LockBits(MakeRect(0, 0, Width, Height), ImageLockModeWrite, PixelFormat32bppARGB, BmpData);
try
// ピクセルデータを書き込み
Move(Pixels[0], BmpData.Scan0^, Width * Height * 4);
finally
Result.UnlockBits(BmpData);
end;
end;
function CreateTGPBitmapFromTBitmap(Bitmap: TBitmap): TGPBitmap;
begin
Result := CreateTGPBitmapFromHBITMAP(Bitmap.Handle);
end;
function CreateTBitmapFromTGPBitmap(GPBitmap: TGPBitmap): TBitmap;
var
hBmp: HBITMAP;
begin
// 透明の背景色を白色にしてビットマップハンドルを取得
GPBitmap.GetHBITMAP(MakeColor(255, 255, 255, 255), hBmp);
Result := TBitmap.Create;
Result.Handle := hBmp;
end;
function CreateTGPBitmapFromClipboard(): TGPBitmap;
var
CF_PNG: UINT;
hMem: HGLOBAL;
Data: Pointer;
Stream: TMemoryStream;
begin
CF_PNG := RegisterClipboardFormat('PNG');
if Clipboard.HasFormat(CF_PNG) then
begin
hMem := Clipboard.GetAsHandle(CF_PNG);
Data := GlobalLock(hMem);
try
Stream := TMemoryStream.Create;
Stream.WriteBuffer(Data^, GlobalSize(hMem));
Stream.Position := 0;
// soOwnedを指定して、Streamの開放はIStream に任せる(Freeは不要)
Result := TGPBitmap.Create(TStreamAdapter.Create(Stream, soOwned));
finally
GlobalUnlock(hMem);
end;
end
else if Clipboard.HasFormat(CF_BITMAP) then
Result := CreateTGPBitmapFromHBITMAP(Clipboard.GetAsHandle(CF_BITMAP))
else
raise Exception.Create('Clipboard does not contain a bitmap');
end;
procedure CopyGPBitmapToClipboard(GPBitmap: TGPBitmap);
var
CF_PNG: UINT;
Stream: TStream;
hMem: HGLOBAL;
Data: Pointer;
Bitmap: TBitmap;
begin
Clipboard.Open;
try
Clipboard.Clear;
//png形式でコピー
CF_PNG := RegisterClipboardFormat('PNG');
Stream := TMemoryStream.Create;
try
GPBitmap.Save(TStreamAdapter.Create(Stream, soReference), CLSID_PNG);
Stream.Position := 0;
hMem := GlobalAlloc(GHND, Stream.Size);
try
Data := GlobalLock(hMem);
try
Stream.Read(Data^, Stream.Size);
finally
GlobalUnlock(hMem);
end;
Clipboard.SetAsHandle(CF_PNG, hMem);
except
GlobalFree(hMem);
raise;
end;
finally
Stream.Free;
end;
//BITMAP形式でコピー
Bitmap := CreateTBitmapFromTGPBitmap(GPBitmap);
try
Clipboard.Assign(Bitmap);
finally
Bitmap.Free;
end;
finally
Clipboard.Close;
end;
end;
CreateTGPBitmapFromHBITMAP(hBmp: HBITMAP): TGPBitmap
-
役割
Windows API の HBITMAP から GDI+ の TGPBitmap を生成する。 -
引数
hBmp: HBITMAP — 変換元となるビットマップハンドル。 -
補足
もっと簡潔に記述できるメソッドなどが用意されていますが、アルファ値を持つHBITMAPに対してアルファ値を喪失させずに変換するには、この方法が最善でした
CreateTGPBitmapFromTBitmap(Bitmap: TBitmap): TGPBitmap
-
役割
Delphi の TBitmap から GDI+ の TGPBitmap を生成する。 -
引数
Bitmap: TBitmap — 変換元となる Delphi のビットマップ。
CreateTBitmapFromTGPBitmap(GPBitmap: TGPBitmap): TBitmap
-
役割
GDI+ の TGPBitmap から Delphi の TBitmap を生成する。 -
引数
GPBitmap: TGPBitmap — 変換元となる GDI+ のビットマップ。
CreateTGPBitmapFromClipboard(): TGPBitmap
-
役割
クリップボードの画像データから GDI+ の TGPBitmap を生成する。
png形式があればpng形式から取得して、アルファデータも保持する
CopyGPBitmapToClipboard(GPBitmap: TGPBitmap)
-
役割
指定した GDI+ の TGPBitmap を PNG および BITMAP 形式でクリップボードにコピーする。 -
引数
GPBitmap: TGPBitmap — コピーする GDI+ のビットマップ。
クリップボードの画像を PNG 形式で保存するサンプルコード
procedure SaveClipboardImageAsPng(const FileName: string);
var
GPBitmap: TGPBitmap;
begin
GPBitmap := CreateTGPBitmapFromClipboard;
try
GPBitmap.Save(FileName, CLSID_PNG)
finally
GPBitmap.Free;
end;
end;
// 使用例
begin
SaveClipboardImageAsPng('clipboard.png');
end;
最後に
過去の私の記事からGDI+でアルファ値を扱ったサンプルプログラムを紹介します