将包含文本的 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;

结果图像:

bmp1: bmp2: bmp 组合:

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

我猜问题出在映射到 Windows.ExtTextOutTextOut(通过 GR32_Backends_VCL.pasTGDIBackend.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;

结果图像:

bmp1a: bmp2a: bmp3a: bmp 组合:

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

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

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

相反。

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

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

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

否则,TextVPR 方法会给您更多自由,尤其是在填充文本(例如使用渐变)或转换文本(曲线等)时。