我正在使用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图像。
答案 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)相同的问题。
所以,解决方案是:
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;