将包含文本的 semi-transparent 个位图与 Graphics32 混合
Blending semi-transparent bitmaps containing text with Graphics32
我正在尝试在我们的一个内部组件中实现分层绘画系统,但我在混合包含文本的位图时遇到问题。
以下代码片段显示了问题:
uses
GR32;
procedure DrawBitmaps;
var
bmp1: TBitmap32;
bmp2: TBitmap32;
begin
bmp1 := TBitmap32.Create;
bmp1.Width := 100;
bmp1.Height := 100;
bmp1.FillRect(0, 0, 100, 100, clWhite32);
bmp1.FillRect(0, 0, 80, 80, clTrGreen32);
bmp1.Font.Size := -16;
bmp1.Font.Color := clBlack;
bmp1.TextOut(2, 10, 'Green');
bmp1.SaveToFile('c:[=11=]\bmp1.bmp');
bmp2 := TBitmap32.Create;
bmp2.Width := 80;
bmp2.Height := 80;
bmp2.FillRect(0, 0, 80, 80, clTrRed32);
bmp2.Font.Size := -16;
bmp2.Font.Color := clBlack;
bmp2.TextOut(2, 50, 'Red');
bmp2.SaveToFile('c:[=11=]\bmp2.bmp');
bmp2.DrawMode := dmBlend;
bmp2.DrawTo(bmp1, 20, 20);
bmp1.SaveToFile('c:[=11=]\bmpcombined.bmp');
bmp1.Free;
bmp2.Free;
end;
结果图像:
如您所见,文本在 bmp
和 bmp2
上涂成黑色,但在 bmpcombined
上显示为白色。
我猜问题出在映射到 Windows.ExtTextOut
的 TextOut
(通过 GR32_Backends_VCL.pas
、TGDIBackend.Textout
)。该方法不处理透明度并使用 alpha 00 绘制文本(颜色是 $000000 而不是 $FF000000)。
作为快速修复,将 bmp2.Font.Color
设置为 $FF000000
没有帮助。
bmp2.Font.Color := TColor(clBlack32);
我正在使用来自 GitHub
的新鲜资源
我应该如何在 semi-transparent 背景上绘制 non-transparent 文字,以便将其混合成更大的图片?
我能找到的最佳解决方案需要三个辅助函数。
TransparentToOpaque
将所有完全透明的像素更改为不透明。
procedure TransparentToOpaque(bmp: TCustomBitmap32);
var
I: Integer;
D: PColor32Entry;
begin
D := PColor32Entry(@bmp.Bits[0]);
for I := 0 to bmp.Width * bmp.Height - 1 do begin
if D.A = 0 then
D.A := $FF;
Inc(D);
end;
bmp.Changed;
end;
FlipTransparency
将所有完全透明的像素更改为不透明,反之亦然。
procedure FlipTransparency(bmp: TCustomBitmap32);
var
I: Integer;
D: PColor32Entry;
begin
D := PColor32Entry(@bmp.Bits[0]);
for I := 0 to bmp.Width * bmp.Height - 1 do begin
if D.A = 0 then
D.A := $FF
else if D.A = $FF then
D.A := 0;
Inc(D);
end;
bmp.Changed;
end;
MakeOpaque
将所有像素标记为不透明。
procedure MakeOpaque(bmp: TCustomBitmap32);
var
I: Integer;
D: PColor32Entry;
begin
D := PColor32Entry(@bmp.Bits[0]);
for I := 0 to bmp.Width * bmp.Height - 1 do begin
D.A := $FF;
Inc(D);
end;
bmp.Changed;
end;
然后可以应用以下技巧。
在不包含透明像素的主图像 bmp1
上绘制文本后,代码调用 TransparentToOpaque
以防止稍后出现混合问题。
在(半)透明位图 bmp2
上绘图时,代码会创建另一个位图 bmp3
并用该(半)透明位图的不透明版本填充它。这将确保字体别名以更正 TextOut 调用中的颜色。
TextOut bmp3
后包含不透明背景和透明文本。然后调用 FlipTransparency
在透明背景上生成不透明文本。
bmp3
混合到 bmp2
上。这放弃了(半)透明背景上的不透明文本。
bmp2
混合到 bmp1
.
示例代码:
procedure TForm53.DrawBitmaps;
var
bmp1: TBitmap32;
bmp2: TBitmap32;
bmp3: TBitmap32;
begin
bmp1 := TBitmap32.Create;
bmp1.Width := 100;
bmp1.Height := 100;
bmp1.FillRect(0, 0, 100, 100, clWhite32);
bmp1.FillRect(0, 0, 80, 80, clTrGreen32);
bmp1.Font.Size := -16;
bmp1.Font.Color := clBlack;
bmp1.TextOut(2, 10, 'Green');
//Mark all fully transparent pixels (generated with TextOut) as opaque.
TransparentToOpaque(bmp1);
SaveBitmap32ToPNG(bmp1, 'c:[=13=]\bmp1a.png');
bmp2 := TBitmap32.Create;
bmp2.Width := 80;
bmp2.Height := 80;
bmp2.FillRect(0, 0, 80, 80, clTrRed32);
//Create bitmap, large enough to contain drawn text (same size as original bitmap in this example).
bmp3 := TBitmap32.Create;
bmp3.Width := bmp2.Width;
bmp3.Height := bmp2.Height;
//Copy `bmp2` to `bmp3`.
bmp2.DrawMode := dmOpaque;
bmp2.DrawTo(bmp3, 0, 0);
//Mark all pixels as opaque (alpha = $FF)
MakeOpaque(bmp3);
//Draw text on `bmp3`. This will create proper aliasing.
bmp3.Font.Size := -16;
bmp3.Font.Color := clBlack;
bmp3.TextOut(2, 50, 'Red');
//Make all fully transparent pixels (TextOut) opaque and all fully opaque pixels
// (background coming from `bmp2`) transparent.
FlipTransparency(bmp3);
SaveBitmap32ToPNG(bmp3, 'c:[=13=]\bmp3a.png');
//Blend `bmp3` on semi-transparent background (`bmp2`).
bmp3.DrawMode := dmBlend;
bmp3.DrawTo(bmp2, 0, 0);
SaveBitmap32ToPNG(bmp2, 'c:[=13=]\bmp2a.png');
//Blend background + text onto main image.
bmp2.DrawMode := dmBlend;
bmp2.DrawTo(bmp1, 20, 20);
SaveBitmap32ToPNG(bmp1, 'c:[=13=]\bmpcombineda.png');
bmp1.Free;
bmp2.Free;
bmp3.Free;
end;
结果图像:
据我所知,TextOut 函数只是作为向位图添加一些文本的直接方式,缺少您上面提到的所有修复。
为了保持对透明度的完全控制,您可能需要使用
procedure TBitmap32.RenderText(X, Y: Integer; const Text: string; AALevel: Integer; Color: TColor32);
相反。
它使用了您在自己的回答中提到的技术,但在更复杂的层面上。它还允许您使用抗锯齿(基于过采样),但今天实际上不建议使用字体引擎输出以外的任何其他抗锯齿技术(以充分利用字体提示)。
由于您使用的是最新的源代码,您还可以考虑使用 VPR 来呈现文本(参见示例 'TextVPR')。它将文本的轮廓转换为矢量并使用 Graphics32 的矢量绘图功能(默认情况下使用引擎 'VPR',因此得名)将其渲染到屏幕上。还有一个精简的 AGG 引擎,它本身是基于 FreeType1 引擎的,它对于字体来说可能会稍微快一些。
谈到性能:请记住,除 TextOut 之外的所有其他方法都会明显降低性能。因此,如果您的目标是高性能,可能会更好地编写自己的代码(基于 TextOut)。
否则,TextVPR 方法会给您更多自由,尤其是在填充文本(例如使用渐变)或转换文本(曲线等)时。
我正在尝试在我们的一个内部组件中实现分层绘画系统,但我在混合包含文本的位图时遇到问题。
以下代码片段显示了问题:
uses
GR32;
procedure DrawBitmaps;
var
bmp1: TBitmap32;
bmp2: TBitmap32;
begin
bmp1 := TBitmap32.Create;
bmp1.Width := 100;
bmp1.Height := 100;
bmp1.FillRect(0, 0, 100, 100, clWhite32);
bmp1.FillRect(0, 0, 80, 80, clTrGreen32);
bmp1.Font.Size := -16;
bmp1.Font.Color := clBlack;
bmp1.TextOut(2, 10, 'Green');
bmp1.SaveToFile('c:[=11=]\bmp1.bmp');
bmp2 := TBitmap32.Create;
bmp2.Width := 80;
bmp2.Height := 80;
bmp2.FillRect(0, 0, 80, 80, clTrRed32);
bmp2.Font.Size := -16;
bmp2.Font.Color := clBlack;
bmp2.TextOut(2, 50, 'Red');
bmp2.SaveToFile('c:[=11=]\bmp2.bmp');
bmp2.DrawMode := dmBlend;
bmp2.DrawTo(bmp1, 20, 20);
bmp1.SaveToFile('c:[=11=]\bmpcombined.bmp');
bmp1.Free;
bmp2.Free;
end;
结果图像:
如您所见,文本在 bmp
和 bmp2
上涂成黑色,但在 bmpcombined
上显示为白色。
我猜问题出在映射到 Windows.ExtTextOut
的 TextOut
(通过 GR32_Backends_VCL.pas
、TGDIBackend.Textout
)。该方法不处理透明度并使用 alpha 00 绘制文本(颜色是 $000000 而不是 $FF000000)。
作为快速修复,将 bmp2.Font.Color
设置为 $FF000000
没有帮助。
bmp2.Font.Color := TColor(clBlack32);
我正在使用来自 GitHub
的新鲜资源我应该如何在 semi-transparent 背景上绘制 non-transparent 文字,以便将其混合成更大的图片?
我能找到的最佳解决方案需要三个辅助函数。
TransparentToOpaque
将所有完全透明的像素更改为不透明。
procedure TransparentToOpaque(bmp: TCustomBitmap32);
var
I: Integer;
D: PColor32Entry;
begin
D := PColor32Entry(@bmp.Bits[0]);
for I := 0 to bmp.Width * bmp.Height - 1 do begin
if D.A = 0 then
D.A := $FF;
Inc(D);
end;
bmp.Changed;
end;
FlipTransparency
将所有完全透明的像素更改为不透明,反之亦然。
procedure FlipTransparency(bmp: TCustomBitmap32);
var
I: Integer;
D: PColor32Entry;
begin
D := PColor32Entry(@bmp.Bits[0]);
for I := 0 to bmp.Width * bmp.Height - 1 do begin
if D.A = 0 then
D.A := $FF
else if D.A = $FF then
D.A := 0;
Inc(D);
end;
bmp.Changed;
end;
MakeOpaque
将所有像素标记为不透明。
procedure MakeOpaque(bmp: TCustomBitmap32);
var
I: Integer;
D: PColor32Entry;
begin
D := PColor32Entry(@bmp.Bits[0]);
for I := 0 to bmp.Width * bmp.Height - 1 do begin
D.A := $FF;
Inc(D);
end;
bmp.Changed;
end;
然后可以应用以下技巧。
在不包含透明像素的主图像
bmp1
上绘制文本后,代码调用TransparentToOpaque
以防止稍后出现混合问题。在(半)透明位图
bmp2
上绘图时,代码会创建另一个位图bmp3
并用该(半)透明位图的不透明版本填充它。这将确保字体别名以更正 TextOut 调用中的颜色。TextOut
bmp3
后包含不透明背景和透明文本。然后调用FlipTransparency
在透明背景上生成不透明文本。bmp3
混合到bmp2
上。这放弃了(半)透明背景上的不透明文本。bmp2
混合到bmp1
.
示例代码:
procedure TForm53.DrawBitmaps;
var
bmp1: TBitmap32;
bmp2: TBitmap32;
bmp3: TBitmap32;
begin
bmp1 := TBitmap32.Create;
bmp1.Width := 100;
bmp1.Height := 100;
bmp1.FillRect(0, 0, 100, 100, clWhite32);
bmp1.FillRect(0, 0, 80, 80, clTrGreen32);
bmp1.Font.Size := -16;
bmp1.Font.Color := clBlack;
bmp1.TextOut(2, 10, 'Green');
//Mark all fully transparent pixels (generated with TextOut) as opaque.
TransparentToOpaque(bmp1);
SaveBitmap32ToPNG(bmp1, 'c:[=13=]\bmp1a.png');
bmp2 := TBitmap32.Create;
bmp2.Width := 80;
bmp2.Height := 80;
bmp2.FillRect(0, 0, 80, 80, clTrRed32);
//Create bitmap, large enough to contain drawn text (same size as original bitmap in this example).
bmp3 := TBitmap32.Create;
bmp3.Width := bmp2.Width;
bmp3.Height := bmp2.Height;
//Copy `bmp2` to `bmp3`.
bmp2.DrawMode := dmOpaque;
bmp2.DrawTo(bmp3, 0, 0);
//Mark all pixels as opaque (alpha = $FF)
MakeOpaque(bmp3);
//Draw text on `bmp3`. This will create proper aliasing.
bmp3.Font.Size := -16;
bmp3.Font.Color := clBlack;
bmp3.TextOut(2, 50, 'Red');
//Make all fully transparent pixels (TextOut) opaque and all fully opaque pixels
// (background coming from `bmp2`) transparent.
FlipTransparency(bmp3);
SaveBitmap32ToPNG(bmp3, 'c:[=13=]\bmp3a.png');
//Blend `bmp3` on semi-transparent background (`bmp2`).
bmp3.DrawMode := dmBlend;
bmp3.DrawTo(bmp2, 0, 0);
SaveBitmap32ToPNG(bmp2, 'c:[=13=]\bmp2a.png');
//Blend background + text onto main image.
bmp2.DrawMode := dmBlend;
bmp2.DrawTo(bmp1, 20, 20);
SaveBitmap32ToPNG(bmp1, 'c:[=13=]\bmpcombineda.png');
bmp1.Free;
bmp2.Free;
bmp3.Free;
end;
结果图像:
据我所知,TextOut 函数只是作为向位图添加一些文本的直接方式,缺少您上面提到的所有修复。
为了保持对透明度的完全控制,您可能需要使用
procedure TBitmap32.RenderText(X, Y: Integer; const Text: string; AALevel: Integer; Color: TColor32);
相反。
它使用了您在自己的回答中提到的技术,但在更复杂的层面上。它还允许您使用抗锯齿(基于过采样),但今天实际上不建议使用字体引擎输出以外的任何其他抗锯齿技术(以充分利用字体提示)。
由于您使用的是最新的源代码,您还可以考虑使用 VPR 来呈现文本(参见示例 'TextVPR')。它将文本的轮廓转换为矢量并使用 Graphics32 的矢量绘图功能(默认情况下使用引擎 'VPR',因此得名)将其渲染到屏幕上。还有一个精简的 AGG 引擎,它本身是基于 FreeType1 引擎的,它对于字体来说可能会稍微快一些。
谈到性能:请记住,除 TextOut 之外的所有其他方法都会明显降低性能。因此,如果您的目标是高性能,可能会更好地编写自己的代码(基于 TextOut)。
否则,TextVPR 方法会给您更多自由,尤其是在填充文本(例如使用渐变)或转换文本(曲线等)时。