Delphi,GR32 + PngObject:转换为Bitmap32无法正常工作

时间:2011-11-21 14:33:29

标签: delphi png transparency graphics32

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

Problem with TBitmap32 alpha channel

- 11月22日增加

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

2 个答案:

答案 0 :(得分: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通道。但这不是必需的,因为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来了,并在其上添加了另一层透明度。

答案 1 :(得分: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: 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. 使用可以将PNG本机加载到TBitmap32 https://sourceforge.net/projects/gr32pnglibrary/中的GR32 PNG库 由于您现在掌握了有关此问题的所有信息,因此您可以获得正确的解决方案。
  4. 如何一次加载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;