Delphi,GR32 + PngObject:转换为Bitmap32未按预期工作

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被错误地加载或绘制的情况。有什么提示吗?
--添加于11月22日
我已经发现这不是关于绘图的问题,而是关于将PNG加载到BMP32中。从BMP32保存回PNG会生成不正确的“变白”的(左侧的)PNG图像。

1
我猜这是由于“透明度”,并且你设置了“tmp.DrawMode:=dmBlend;”。我没有用过GR32,但我猜测差异是由于透明度造成的。 - user497849
1
@Dorin Duminica,不是这样的。他们网站上的例子显示,如果已加载的PNG图像中有任何透明度,则模式应该是dmBlend。由于我知道我的所有图像都是透明的,所以我不必进行检查。 - migajek
2个回答

10
原因似乎是当使用“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通道,但是这并不是必要的,因为alpha通道在第二步被赋给了 TBitmap32 的像素值!

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

 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添加了另一层透明度。

谢谢,这应该是完美答案的范例 - 不仅包括修正后的代码,还有非常详细的解释错误的原因 :) - migajek
1
呵呵,不客气 :) 这是一个有趣且表述清晰的问题! - Heinrich Ulbricht

2
问题可能在于PNG图像被错误地转换为TBitmap32,在传输过程中丢失了透明度信息。这是调色板PNG图像的常见情况。否则,您不需要使用“Bitmap.DrawMode:= dmTransparent”和“OuterColor”。如果来自PNG的透明信息能够正确传输到TBitmpa32,则DrawMode:= dmBlend将起作用,而无需设置OuterColor。
最重要的是,您如何将PNG加载到TBitmap32中。来自Vcl.Imaging.pngimage单元(在Delphi XE2及更高版本中实现)的TPngImage可以在位图上透明地绘制,保留了该位图上的内容,使用PNG alpha层组合颜色等,但它不允许轻松地将各种格式的PNG透明度(包括调色板)转换为每个像素的alpha分量TBitmap32。一旦TPngImage绘制了图像,您就会得到每个像素的组合RGB,但是alpha分量不会传输到目标位图。
有可用的辅助程序例程尝试以透明方式将PNG加载到TBitmap32中,但它们具有缺点:

(1) “LoadPNGintoBitmap32” 来自 http://graphics32.org/wiki/FAQ/ImageFormatRelated - 它会将透明度应用两次,因此具有介于0或255之间的Alpha值的图像与其他软件中的外观不同(在具有玻璃效果的半透明图像上最为明显)。该代码将首先将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. 使用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;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接