将包含文本的半透明位图与Graphics32混合

时间:2017-06-05 11:49:05

标签: delphi 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:\0\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:\0\bmp2.bmp');

  bmp2.DrawMode := dmBlend;
  bmp2.DrawTo(bmp1, 20, 20);

  bmp1.SaveToFile('c:\0\bmpcombined.bmp');

  bmp1.Free;
  bmp2.Free;
end;

产生的图像:

bmp1:bmp1 bmp2:bmp2 bmpcombined:combined

如您所见,文字在bmpbmp2上涂成黑色,但在bmpcombined上显示为白色。

我猜测问题出在TextOut,映射到Windows.ExtTextOut(通过GR32_Backends_VCL.pasTGDIBackend.Textout)。该方法不处理透明度并使用alpha 00绘制文本(颜色为$ 000000而不是$ FF000000)。

作为快速解决方法,将bmp2.Font.Color设置为$FF000000并不会有帮助。

bmp2.Font.Color := TColor(clBlack32);

我正在使用来自GitHub

的新资料

如何在半透明背景上绘制不透明的文字,以便将其混合成更大的图片?

2 个答案:

答案 0 :(得分:4)

据我所知,TextOut函数仅作为向位图添加一些文本的直接方式,缺少上面提到的所有修复。

为了保持对透明度的完全控制,您可能需要使用

procedure TBitmap32.RenderText(X, Y: Integer; const Text: string; AALevel: Integer; Color: TColor32);

代替。

它使用您在自己的答案中提到的技术,但是在更复杂的层面上。它还允许你使用抗锯齿(基于过采样),但今天它实际上并不建议使用除字体引擎输出之外的任何其他抗锯齿技术(以充分利用字体提示) )。

当您使用最新的源代码时,您还可以考虑使用VPR来呈现文本(请参阅示例' TextVPR')。它将文本轮廓转换为矢量,并使用Graphics32的矢量绘图功能(默认使用引擎' VPR'因此名称)将其渲染到屏幕上。还有一个包含AGG的精简引擎,它本身基于FreeType1引擎,对于字体来说可能稍快一些。

说到性能:请记住,除TextOut之外的所有其他方法都会明显降低性能。因此,如果您的目标是高性能,可以更好地烹饪您自己的代码(基于TextOut)。

否则,TextVPR方法会让您获得更多自由,特别是在填充文本(例如使用渐变)或转换文本(转换为曲线等)时。

答案 1 :(得分:3)

我能找到的最佳解决方案需要三个辅助函数。

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:\0\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:\0\bmp3a.png');

  //Blend `bmp3` on semi-transparent background (`bmp2`).
  bmp3.DrawMode := dmBlend;
  bmp3.DrawTo(bmp2, 0, 0);

  SaveBitmap32ToPNG(bmp2, 'c:\0\bmp2a.png');

  //Blend background + text onto main image.
  bmp2.DrawMode := dmBlend;
  bmp2.DrawTo(bmp1, 20, 20);

  SaveBitmap32ToPNG(bmp1, 'c:\0\bmpcombineda.png');

  bmp1.Free;
  bmp2.Free;
  bmp3.Free;
end;

产生的图像:

bmp1a:bmp1a bmp2a:bmp2a bmp3a:bmp3a bmpcombineda:bmpcombineda