5

我正在使用 GR32 绘制多个半透明 PNG 图像。到目前为止,我一直在使用以下方法:

  png:= TPNGObject.Create;
  png.LoadFromFile(...);
  PaintBox321.Buffer.Canvas.Draw(120, 20, png);

但是我想切换到 GR32 网站(http://graphics32.org/wiki/FAQ/ImageFormatRelated)上提出的方法:

  tmp:= TBitmap32.Create;
  LoadPNGintoBitmap32(tmp, ..., foo);
  tmp.DrawMode:= dmBlend;
  PaintBox321.Buffer.Draw(Rect(20, 20, 20+ tmp.Width, 20+tmp.Height),
   tmp.ClipRect, tmp);

虽然第一种方法工作得很好,但第二种方法 - 应该给出相同的结果 - 导致 alpha 通道出现非常奇怪的问题,请查看图像(它还显示了与 Paint.NET 中“排列”的相同图像的比较 - 背景和图标在编辑器的图层上打开)。该图像描述了 Bitmap32 加载或绘制不正确。有小费吗?

TBitmap32 alpha 通道的问题

-- 11 月 22 日添加

我发现这不是关于绘图,而是关于将 PNG 加载到 BMP32。从 BMP32 保存回 PNG 会生成不正确的“白化”(左侧)PNG 图像。

4

2 回答 2

9

原因似乎是在加载时对图像应用了两次透明度LoadPNGintoBitmap32,使其看起来更加透明和灰色(稍后会详细介绍)。

首先是透明度:

这是原始代码LoadPNGintoBitmap32,关键部分标有注释:

 PNGObject := TPngObject.Create;
 PNGObject.LoadFromStream(srcStream);

 destBitmap.Assign(PNGObject);  // <--- paint to destBitmap's canvas with transparency (!)
 destBitmap.ResetAlpha;         

 case PNGObject.TransparencyMode of  // <--- the following code sets the transparency again for the TBitmap32
 { ... }

内部的destBitmap.Assign做法与您之前的方法相同:它让 PNG 图像将自己绘制到其画布上。此操作尊重 PNG 的 alpha 通道。但这不是必需的,因为在TBitmap32第二步中将 alpha 通道分配给 's 像素!

现在将代码更改如下,关键部分再次用注释标记:

 PNGObject := TPngObject.Create;
 PNGObject.LoadFromStream(srcStream);

 PNGObject.RemoveTransparency;  // <--- paint PNG without any transparency...
 destBitmap.Assign(PNGObject);  // <--- ...here
 destBitmap.ResetAlpha;

 srcStream.Position:=0;
 PNGObject.LoadFromStream(srcStream); // <--- read the image again to get the alpha channel back

 case PNGObject.TransparencyMode of   // <--- this is ok now, the alpha channel now only exists in the TBitmap32
 { ... }

上述解决方案效率低下,因为它读取图像两次。但这说明了为什么您的第二种方法会产生更透明的图像。

对于灰色:原始代码中还有一个问题:destBitmap.Assign首先用 填充背景clWhite32,然后将图像透明地绘制到它上面。然后LoadPNGintoBitmap32来并在其上添加另一层透明度。

于 2011-11-21T17:07:52.347 回答
1

问题可能是 PNG 错误地转换为 TBitmap32,在传输过程中丢失了透明度信息。这是调色板PNG图像的常见情况。否则,您不必使用“Bitmap.DrawMode := dmTransparent”和“OuterColor”。如果来自 PNG 的透明信息可以正确传输到 TBitmpa32,DrawMode := dmBlend 就可以工作,而无需设置 OuterColor。

最重要的是如何将 PNG 加载到 TBitmap32 中。Vcl.Imaging.pngimage 单元中的 TPngImage(在 Delphi XE2 及更高版本中实现)可以在位图上透明绘制,保留位图上的内容,使用 PNG alpha 层组合颜色等,但它不允许轻松转换各种将 PNG 透明度(包括调色板)格式转换为 TBitmap32 的每个像素的 alpha 分量。一旦 TPngImage 绘制了图像,您将获得每个像素的组合 RGB,但 alpha 分量不会传输到目标位图。

有一些辅助例程可以尝试将 PNG 加载到具有透明度的 TBitmap32 中,但它们有缺点:

(1) 来自http://graphics32.org/wiki/FAQ/ImageFormatRelated的“LoadPNGintoBitmap32” - 它应用了两次透明度,因此 alpha 值不是 0 或 255 的图像看起来与其他软件中的不同(半透明时最明显具有玻璃效果的图像)。此代码将首先将 alpha 应用于 RGB,然后将 alpha 设置为单独的图层,因此当您绘制时,将再次应用 alpha。您可以在此处找到有关此问题的更多信息:Delphi,GR32 + PngObject:转换为 Bitmap32 无法按预期工作 . 除此之外,它不会正确地将调色板图像的透明度转换为 TBitmap32 的 alpha 层。他们为输出位图(渲染为 RGB)的某种颜色的像素手动设置 alpha 透明度,而不是在渲染为 RGB 之前执行此操作,因此当所有白色像素都是透明的时,实际透明度会像您的示例图像一样丢失。

(2) 来自 gr32ex 库的“LoadBitmap32FromPNG”:https ://code.google.com/archive/p/gr32ex/ - 与 (1) 相同算法的实现略有不同,并且具有与 (1) 相同的问题。

因此,解决方案是:

  1. 不要使用 TBitmap32;使用 Vcl.Imaging.pngimage.TPngImage 直接在目标位图(屏幕等)上绘制 - 这是正确处理各种 PNG 格式的最兼容的方式。
  2. 使用辅助路由将透明度信息从 Vcl.Imaging.pngimage.TPngImage 传输到 TBitmap32。
  3. 使用可以将 PNG 本地加载到 TBitmap32 的 GR32 PNG 库https://sourceforge.net/projects/gr32pnglibrary/ 由于您现在拥有有关此问题的所有信息,因此您可能会获得适合您的解决方案。

如何一次性加载 Alpha 层

Heinrich Ulbricht 提出了一个很好的建议,即在绘画之前移除透明层,然后再次读取图像。为避免两次加载图像,您可以在调用 PNGObject.RemoveTransparency 之前保存 Alpha 层。这是正确应用 alpha 层并仅加载图像一次的代码。不幸的是,它不适用于调色板图像。如果您知道如何从任何调色板图像中正确填充 TBitmap32 的 alpha 层,但没有透明 Png 到 TBitmap32中描述的效果,请告诉我。

procedure LoadPNGintoBitmap32(DstBitmap: TBitmap32; SrcStream: TStream; out AlphaChannelUsed: Boolean);
var
  PNGObject: TPngImage;
  PixelPtr: PColor32;
  AlphaPtr: PByte;
  SaveAlpha: PByte;
  I, AlphaSize: Integer;
begin
  AlphaChannelUsed := False;
  PNGObject := TPngImage.Create;
  try
    PNGObject.LoadFromStream(SrcStream);
    AlphaPtr := PByte(PNGObject.AlphaScanline[0]);
    if Assigned(AlphaPtr) then
    begin
      AlphaSize := PNGObject.Width * PNGObject.Height;
      if AlphaSize <= 0 then raise Exception.Create('PNG files with zero dimensions are not supported to be loaded to TBitmap32');
      GetMem(SaveAlpha, AlphaSize);
      try
        Move(AlphaPtr^, SaveAlpha^, AlphaSize);
        PNGObject.RemoveTransparency;
        DstBitmap.Assign(PNGObject);
        DstBitmap.ResetAlpha;
        PixelPtr := PColor32(@DstBitmap.Bits[0]);
        AlphaPtr := SaveAlpha;
        for I := 0 to AlphaSize-1 do
        begin
          PixelPtr^ := (PixelPtr^ and $00FFFFFF) or (TColor32(AlphaPtr^) shl 24);
          Inc(PixelPtr);
          Inc(AlphaPtr);
        end;
      finally
        FreeMem(SaveAlpha, AlphaSize);
      end;
      AlphaChannelUsed := True;
    end else
    if PNGObject.TransparencyMode = ptmNone then
    begin
      DstBitmap.Assign(PNGObject);
    end else
    begin
      raise Exception.Create('Paletted PNG images are not supported in LoadPNGintoBitmap32, transparency cannot be stored to TBitmap32');
    end;
  finally
    FreeAndNil(PNGObject);
  end;
end;
于 2016-10-29T14:51:26.127 回答