【问题标题】:Delphi, GR32 + PngObject: converting to Bitmap32 doesn't work as expectedDelphi、GR32 + PngObject:转换为 Bitmap32 无法按预期工作
【发布时间】:2012-01-03 01:10:54
【问题描述】:

我正在使用 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 加载或绘制不正确。有什么建议吗?

-- 11 月 22 日添加

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

【问题讨论】:

  • 我认为这是由于“不透明度”,您也设置了“tmp.DrawMode:= dmBlend;”,我没有使用过 GR32,但我猜这个差异是由于不透明度。
  • @Dorin Duminica,不是。他们网站上的示例显示,如果加载的 PNG 图像中有任何透明度,则模式应为 dmBlend。因为我知道我所有的图像都是透明的,所以我不必检查。

标签: delphi png transparency graphics32


【解决方案1】:

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

首先是透明度:

这是原LoadPNGintoBitmap32的代码,关键部分用cmets标记:

 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 通道。但这不是必需的,因为 alpha 通道是在第二步中分配给TBitmap32 的像素的!

现在改代码如下,关键部分再次用cmets标记:

 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 出现并在其上添加了另一层透明度。

【讨论】:

  • 谢谢,这应该是一种完美的答案——不仅是更正的代码,还有对错误的非常详细的解释:)
  • 呵呵,不客气 :) 这是一个有趣且很好的问题!
【解决方案2】:

问题可能是 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) “LoadPNGintoBitmap32”来自http://graphics32.org/wiki/FAQ/ImageFormatRelated - 它应用了两次透明度,因此 alpha 值不是 0 或 255 的图像看起来与其他软件中的不同(在具有玻璃效果的半透明图像上最为明显)。此代码将首先将 alpha 应用于 RGB,然后将 alpha 设置为单独的图层,因此当您绘制时,将再次应用 alpha。您可以在此处找到有关此问题的更多信息:Delphi, GR32 + PngObject: converting to Bitmap32 doesn't work as expected .除此之外,它不会正确地将调色板图像的透明度转换为 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. 使用 GR32 PNG 库,可以将 PNG 本地加载到 TBitmap32 https://sourceforge.net/projects/gr32pnglibrary/ 由于您现在掌握了有关此问题的所有信息,因此您可能会得到适合您的解决方案。

如何一次性加载 alpha 层

Heinrich Ulbricht 提出了一个很好的建议,即在绘制之前移除透明层,然后再次读取图像。为避免两次加载图像,您可以在调用 PNGObject.RemoveTransparency 之前保存 Alpha 层。这是正确应用 alpha 层并仅加载图像一次的代码。不幸的是,它不适用于调色板图像。如果您知道如何从任何调色图像中正确填充 TBitmap32 的 alpha 层,但没有 Transparent Png to 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;

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-07-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-12
    相关资源
    最近更新 更多