Delphi2007以来久しぶりにDelphi 11 Community Editionを使っているものです。
情報が少ないVCLのDirectWriteを四苦八苦しながら使っているのですが、下記コードで躓いています。
やりたいことは……
1. アルファ情報付きBitmapを作成し背景を透明色で塗りつぶす
2. DirectWriteでBitmap.Canvasに文字を描画する
3. BitmapをPngにコピーする
4. Pngをファイルに保存する
ということでとりあえず動作しているですが、よく見ると背景を透明色にしているにもかかわらず文字の縁に黒色が残ってしまっています。透明な背景に文字が綺麗に馴染むようにしたいのですが何かやり方を間違えていますでしょうか。英語でググってみたりChatGPT先生にも聞いてみましたが有益な情報は得られず挫けそうです。ご教授ください。
uses
Winapi.D2D1, Vcl.Direct2D, Vcl.Graphics, Vcl.Imaging.pngimage;
procedure TForm1.Button1Click(Sender: TObject);
const
FileName = 'C:\Users\xxxx\ドキュメント\test.png';
Text = '文字サンプル';
w = 500;
h = 300;
var
D2DCanvas: TDirect2DCanvas;
TextFormat: IDWriteTextFormat;
TextLayout: IDWriteTextLayout;
Bmp: TBitmap;
Png: TPngImage;
p: TD2D1Point2F;
r: TRect;
begin
Bmp := TBitmap.Create;
Bmp.AlphaFormat := afDefined;
Bmp.Transparent := true;
Bmp.SetSize(w,h);
r := Rect(0,0,w,h);
D2DCanvas := TDirect2DCanvas.Create(Bmp.Canvas,r);
D2DCanvas.BeginDraw;
D2DCanvas.RenderTarget.Clear(D2D1ColorF(0.0,0.0,0.0,0.0));
DWriteFactory.CreateTextFormat(PWideChar('メイリオ'),nil,DWRITE_FONT_WEIGHT_NORMAL,DWRITE_FONT_STYLE_NORMAL,DWRITE_FONT_STRETCH_NORMAL,40,'ja-jp',TextFormat);
DWriteFactory.CreateTextLayout(PWideChar(Text),Length(Text),TextFormat,0,0,TextLayout);
TextLayout.SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP);
D2DCanvas.Font.Brush.Color := clBlue;
p.x := 100;
p.y := 100;
D2DCanvas.RenderTarget.DrawTextLayout(p,TextLayout,D2DCanvas.Font.Brush.Handle,D2D1_DRAW_TEXT_OPTIONS_NONE);
D2DCanvas.EndDraw;
D2DCanvas.Free;
Png := TPngImage.Create;
Png.Assign(Bmp);
Png.SaveToFile(FileName);
Png.Free;
Bmp.Free;
end;
文字描画でアンチエイリアスが利いてるからだと思う
返信ありがとうございます。やはりアンチエイリアスがかかるときの背景色にアルファ成分は加味されないということでしょうか。できないとしたら残念です……。
PixelFormat := pf32bit; でやれば黒色は残らない
AAAAA様が解決策を既に提示されていますが、Direct2Dキャンバスの使い方サンプルを公開しています。
https://mam-mam.net/delphi/vcl_direct2d_canvas.html
AAAAA様 解決策のご提示ありがとうございます。ただ残念ながらBmp.PixelFormat:=pf32bit;をBmp.Createのあとに追加しても結果は変わりませんでした。私のやり方の問題のような気がしますので、引き続きいろいろ探ってみます。
mam様 Direct2Dキャンバスの使い方サンプルありがとうございます。実はすでにこのページは見つけております。有益な情報を公開していただき改めてお礼申し上げます。まだ解決には至っておりませんが引き続き参考にさせていただきます。
参考までに出力した文字を拡大した画像をアップロードしました(※広告がうるさくてすいません)。上が今回のコードで吐き出したもの、下はPowerPointで文字を「図として保存」でPNGに吐き出したものです。なんとか下のようにしたいのですが……。
本文にURLを記入するのを忘れました。
https://ibb.co/7JyTf8C
通常の所は AlpheBalend 255 で Canvas 描画
アンチエイリアス の所は Canvas と Font.Color で計算でなく そのまま Canvas に描画して アンチエイリアスを AlphaBlend で表現
テキスト以外は AlphaBlend 0
これやらんと無理だね
ScanLine ならできるだろうけど
AAAAA様 力業となると白旗ですね(涙)。DirectWriteの仕様ということなのでしょうか。透明色背景はやっかいなものです。気長に情報を集めてみようと思います。回答にお時間を割いていただきありがとうございました。
D2DCanvas.RenderTarget.Clear(D2D1ColorF(0.0,0.0,0.0,0.0));
の行がダメみたいですね。
D2DCanvas.RenderTarget.Clear(D2D1ColorF(1.0, 1.0, 1.0, 0.0));
としてもD2DCanvas.RenderTarget.Clear(D2D1ColorF(0.0,0.0,0.0,0.0));と同じ挙動でダメでした。
この部分だけScanLine使うとうまくいきました。(Delphi XE10.2で確認)
uses
Winapi.D2D1, Vcl.Direct2D, Vcl.Imaging.pngimage;
procedure TForm1.Button1Click(Sender: TObject);
type
TRGBQArray = array [0..High(Integer) div 4 - 1] of RGBQUAD;
PRGBQArray = ^TRGBQArray;
const
//FileName:String = 'C:\Users\xxxx\ドキュメント\test.png';
FileName:String='a.png';
Text:String ='文字サンプル';
w:Integer=500;
h:Integer=300;
var
D2DCanvas: TDirect2DCanvas;
TextFormat: IDWriteTextFormat;
TextLayout: IDWriteTextLayout;
Bmp: TBitmap;
Png: TPngImage;
p: TD2D1Point2F;
r: TRect;
i,ii:Integer;
PQ: PRGBQArray;
begin
Bmp := TBitmap.Create;
Bmp.PixelFormat:=pf32bit;
Bmp.Transparent := true;
Bmp.AlphaFormat := afDefined;
Bmp.SetSize(w,h);
r := Rect(0,0,w,h);
{1ピクセルずつアルファチャンネルの値を設定}
for i := 0 to BMP.Height - 1 do
begin
PQ := BMP.ScanLine[i];//RGBQuadの配列へのポインタを代入する
for ii := 0 to BMP.Width - 1 do
begin
TRGBQuad(PQ[ii]).rgbReserved := 0;//アルファチャンネル
TRGBQuad(PQ[ii]).rgbBlue:=255;
TRGBQuad(PQ[ii]).rgbGreen:=255;
TRGBQuad(PQ[ii]).rgbRed:=255;
end;
end;
D2DCanvas := TDirect2DCanvas.Create(Bmp.Canvas,r);
D2DCanvas.BeginDraw;
//D2DCanvas.RenderTarget.Clear(D2D1ColorF(0.0,0.0,0.0,0.0));//ここがダメみたいです
DWriteFactory.CreateTextFormat(PWideChar('メイリオ'),nil,DWRITE_FONT_WEIGHT_NORMAL,DWRITE_FONT_STYLE_NORMAL,DWRITE_FONT_STRETCH_NORMAL,40,'ja-jp',TextFormat);
DWriteFactory.CreateTextLayout(PWideChar(Text),Length(Text),TextFormat,0,0,TextLayout);
TextLayout.SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP);
D2DCanvas.Font.Brush.Color := clBlue;
p.x := 100;
p.y := 100;
D2DCanvas.RenderTarget.DrawTextLayout(p,TextLayout,D2DCanvas.Font.Brush.Handle,D2D1_DRAW_TEXT_OPTIONS_NONE);
D2DCanvas.EndDraw;
D2DCanvas.Free;
Png := TPngImage.Create;
Png.Assign(Bmp);
Png.SaveToFile(FileName);
Png.Free;
Bmp.Free;
end;
すいません、以下のほうが良いのかもです。外していたらすいません。
{1ピクセルずつアルファチャンネルの値を設定}
for i := 0 to BMP.Height - 1 do
begin
PQ := BMP.ScanLine[i];//RGBQuadの配列へのポインタを代入する
for ii := 0 to BMP.Width - 1 do
begin
TRGBQuad(PQ[ii]).rgbReserved := 0;//アルファチャンネル
TRGBQuad(PQ[ii]).rgbBlue:=255;
TRGBQuad(PQ[ii]).rgbGreen:=1;
TRGBQuad(PQ[ii]).rgbRed:=1;
end;
end;
すいません、アルファチャネルは自力でPNGに入れる必要がありますね。
uses
Winapi.D2D1, Vcl.Direct2D, Vcl.Imaging.pngimage;
procedure TForm1.Button1Click(Sender: TObject);
type
TRGBQArray = array [0..High(Integer) div 4 - 1] of RGBQUAD;
PRGBQArray = ^TRGBQArray;
const
//FileName:String = 'C:\Users\xxxx\ドキュメント\test.png';
FileName:String='a.png';
Text:String ='文字サンプル';
w:Integer=500;
h:Integer=300;
var
D2DCanvas: TDirect2DCanvas;
TextFormat: IDWriteTextFormat;
TextLayout: IDWriteTextLayout;
Bmp: TBitmap;
Png: TPngImage;
p: TD2D1Point2F;
r: TRect;
y,x:Integer;
PQ: PRGBQArray;
AlphaRow: PByteArray;
begin
Bmp := TBitmap.Create;
Bmp.PixelFormat:=pf32bit;
//Bmp.Transparent := true;
Bmp.AlphaFormat := afDefined;
Bmp.SetSize(w,h);
r := Rect(0,0,w,h);
{1ピクセルずつアルファチャンネルの値を設定}
for y := 0 to BMP.Height - 1 do
begin
PQ := BMP.ScanLine[y];//RGBQuadの配列へのポインタを代入する
for x := 0 to BMP.Width - 1 do
begin
TRGBQuad(PQ[x]).rgbReserved := 0;//アルファチャンネル
TRGBQuad(PQ[x]).rgbBlue:=255;
TRGBQuad(PQ[x]).rgbGreen:=0;
TRGBQuad(PQ[x]).rgbRed:=0;
end;
end;
D2DCanvas := TDirect2DCanvas.Create(Bmp.Canvas,r);
D2DCanvas.BeginDraw;
DWriteFactory.CreateTextFormat(PWideChar('メイリオ'),nil,DWRITE_FONT_WEIGHT_NORMAL,DWRITE_FONT_STYLE_NORMAL,DWRITE_FONT_STRETCH_NORMAL,40,'ja-jp',TextFormat);
DWriteFactory.CreateTextLayout(PWideChar(Text),Length(Text),TextFormat,0,0,TextLayout);
TextLayout.SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP);
D2DCanvas.Font.Brush.Color := clBlue;
p.x := 100;
p.y := 100;
D2DCanvas.RenderTarget.DrawTextLayout(p,TextLayout,D2DCanvas.Font.Brush.Handle,D2D1_DRAW_TEXT_OPTIONS_NONE);
D2DCanvas.EndDraw;
D2DCanvas.Free;
Png := TPngImage.Create;
//Png.Transparent:=True;
Png.Assign(Bmp);
//アルファ チャネル情報を追加
Png.CreateAlpha;
//アルファチャンネルはPNGに自力で入れる
for y := 0 to BMP.Height - 1 do
begin
PQ := BMP.ScanLine[y];
AlphaRow:=Png.AlphaScanline[y];
for x := 0 to BMP.Width - 1 do
begin
AlphaRow[x]:=TRGBQuad(PQ[x]).rgbReserved;//アルファチャンネル
end;
end;
Png.SaveToFile(FileName);
Png.Free;
Bmp.Free;
end;
Direct2D いらないよねw
mam様 大変申し訳ありません! 実は……先程とあるサイトを参考にすることで目的を果たすことができたのです。すいません、もっと早くこの掲示板を確認しておけばよかったです。対策コーディングを頂きまして本当に感謝いたします。まだ目を通していませんが、高い技術力に圧倒されております。必ず試させていただきます。
私のほうで出た結論をいいますと最初に提示したサンプルコードでDirectWriteはちゃんと機能していました。機能していなかったのはこれです。
Png.Assign(Bmp);
まさにmam様の「アルファチャネルは自力でPNGに入れる必要がある」が正解のようです。
ネットを巡り巡ってたどりついたのがオランダのサイトで、そこに衝撃の一文がありました。
「唯一の問題は PngImage.Assign(Bitmap) を使用して TPngImage にビットマップを割り当てると、アルファチャネルが失われることです」
https://www.bverhue.nl/delphisvg/2016/09/26/save-bitmap-with-transparency-as-png-in-vcl/
……ズッコケました。なんのためのアルファ付きビットマップなのかと。なにはともあれオランダの方が対策コードも載せてくれていたので、ありがたく使用させていただいたところ無事目的を果たすことができました。下記の画像がその結果です。吐き出したPNGを適当な静止画の上にのせたものですが、アンチエイリアス部分がしっかり馴染んでいます。
https://ibb.co/jhcmgBC
本当にお騒がせして申し訳ありませんでした。また、さまざまなご助言をいただきましてありがとうございました。下記私のほうで完成させたコードです。素人コーディングなので間違いがありましたらご指導ください。
uses
Winapi.D2D1, Vcl.Direct2D, Vcl.Imaging.pngimage;
// アルファ付きBitmapからアルファ付きPNGを生成する(by BVerhue氏)
function PNG4TransparentBitMap(aBitmap: TBitmap): TPNGImage;
const
CMaxSize = 8000;
type
TRGB = packed record B, G, R: byte end;
TRGBA = packed record B, G, R, A: byte end;
TRGBAArray = array [0..CMaxSize] of TRGBA;
var
BmpRGBA: ^TRGBAArray;
PngRGB: ^TRGB;
X, Y: Integer;
begin
Result := TPNGImage.CreateBlank(COLOR_RGBALPHA, 8, aBitmap.Width, aBitmap.Height);
Result.CreateAlpha;
Result.Canvas.CopyMode := cmSrcCopy;
Result.Canvas.Draw(0, 0, aBitmap);
for Y := 0 to Pred(aBitmap.Height) do
begin
BmpRGBA := aBitmap.ScanLine[Y];
PngRGB := Result.ScanLine[Y];
for X := 0 to Pred(aBitmap.Width) do
begin
Result.AlphaScanline[Y][X] := BmpRGBA[X].A;
if aBitmap.AlphaFormat in [afDefined, afPremultiplied] then
begin
if BmpRGBA[X].A <> 0 then
begin
// ※引用元が間違っていると思われるので修正しました。
PngRGB^.B := Round(BmpRGBA[X].B * BmpRGBA[X].A / 255);
PngRGB^.R := Round(BmpRGBA[X].R * BmpRGBA[X].A / 255);
PngRGB^.G := Round(BmpRGBA[X].G * BmpRGBA[X].A / 255);
end
else
begin
PngRGB^.B := Round(BmpRGBA[X].B * 255);
PngRGB^.R := Round(BmpRGBA[X].R * 255);
PngRGB^.G := Round(BmpRGBA[X].G * 255);
end;
end;
Inc(PngRGB);
end;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
const
CFileName = 'C:\Users\xxx\ドキュメント\Test.png';
CText = '文字サンプル';
CWidth = 310;
CHeight = 80;
var
D2DCanvas: TDirect2DCanvas;
TextFormat: IDWriteTextFormat;
TextLayout: IDWriteTextLayout;
Bmp: TBitmap;
Png: TPNGImage;
pf: TD2D1Point2F;
r: TRect;
begin
Bmp := TBitmap.Create;
Png := nil;
try
// アルファ付きBitmapとする
Bmp.PixelFormat := pf32bit;
Bmp.AlphaFormat := afDefined;
Bmp.Transparent := true;
Bmp.SetSize(CWidth, CHeight);
// DirectWriteによる文字描画
r := Rect(0, 0, CWidth, CHeight);
D2DCanvas := TDirect2DCanvas.Create(Bmp.Canvas, r);
try
D2DCanvas.BeginDraw;
D2DCanvas.RenderTarget.Clear(D2D1ColorF(0.0, 0.0, 0.0, 0.0));
DWriteFactory.CreateTextFormat(PWideChar('メイリオ'), nil,
DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
DWRITE_FONT_STRETCH_NORMAL, 47, 'ja-jp', TextFormat);
DWriteFactory.CreateTextLayout(PWideChar(CText), Length(CText), TextFormat, 0,
0, TextLayout);
TextLayout.SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP);
D2DCanvas.Font.Brush.Color := clBlue;
pf.X := 10;
pf.Y := 10;
D2DCanvas.RenderTarget.DrawTextLayout(pf, TextLayout,
D2DCanvas.Font.Brush.Handle, D2D1_DRAW_TEXT_OPTIONS_NONE);
D2DCanvas.EndDraw;
finally
D2DCanvas.Free;
end;
// アルファ付きBitmapからアルファ付きPNGを生成する
Png := PNG4TransparentBitMap(Bmp);
Png.SaveToFile(CFileName);
finally
Png.Free;
Bmp.Free;
end;
end;
PngRGB^.B := Round(BmpRGBA[X].B * BmpRGBA[X].A / 255);
PngRGB^.R := Round(BmpRGBA[X].R * BmpRGBA[X].A / 255);
PngRGB^.G := Round(BmpRGBA[X].G * BmpRGBA[X].A / 255);
この計算は実際に描画する時の計算で オブジェクト的なのを作る場合には RGB に AlphaBlend 加味しちゃダメなんじゃない?
soi様が示されました
>吐き出したPNGを適当な静止画の上にのせたもの
のurlの画像を拡大して確認しましたら、文字の周りに黒色の縁が有りまして馴染んでないような、、、
お節介ですいません。
AAAAA様の言う通りかもと思います。
ご指南ありがとうございます。修正しておきます。
ツイート | ![]() |