【问题标题】:Saving ImgView32 transparent layers to PNG将 ImgView32 透明图层保存为 PNG
【发布时间】:2015-06-17 09:30:09
【问题描述】:

我在将 ImgView32 图层保存为透明 PNG 时遇到问题。 我使用来自this question 的代码进行保存。 但是,图像以白色背景保存。

这是我如何初始化 ImgView32,在其上创建一个图层,然后在其上画一条线:

procedure TputLine.FormCreate(Sender: TObject);
var
  P: TPoint;
  W, H: Single;
  imwidth: integer;
  imheight: integer;
begin
  imwidth := Iv1.Width;
  imheight := Iv1.Height;
  with iv1 do
  begin
    Selection := nil;
    Layers.Clear;
    Scale := 1;
    Scaled := True;
    Bitmap.DrawMode := dmTransparent;
    Bitmap.SetSize(imwidth, imheight);
    Bitmap.Canvas.Pen.Width := 4;
  end;
  BL := TBitmapLayer.Create(iv1.Layers);
  try
    BL.Bitmap.DrawMode := dmTransparent;
    BL.Bitmap.SetSize(imwidth,imheight);
    BL.Bitmap.Canvas.Pen.Width := penwidth;
    BL.Bitmap.Canvas.Pen.Color := pencolor;
    BL.Location := GR32.FloatRect(0, 0, imwidth, imheight);
    BL.Scaled := False;
  except
    BL.Free;
    raise;
  end;
end;

所以 iv1 是我的 ImgView32 的名称。 然后我用这段代码在上面画一条线:

var 
  bm32:TBitmapLayer;
  ...
begin
  bm32:=(iv1.Layers[0] as TBitmapLayer).Bitmap;
  bm32.canvas.pen.color:=clwhite;
  bm32.canvas.brush.color:=clwhite;
  bm32.canvas.rectangle(0,0,bm32.width-1, bm32.height-1);

  bm32.canvas.Pen.Color:=WinColor(ColorPickerGTK1.SelectedColor);
  bm32.canvas.brush.color:=clWhite;
  bm32.Canvas.Pen.Width:=3;
  bm32.Canvas.MoveTo(0,bm32.Height);
  bm32.Canvas.LineTo(0+150,bm32.Height-250);
end;

如果我在绘制矩形时对上述代码使用clWhite32,那么在保存PNG时,imgView的背景会变黑......所以我真的不明白这个问题。

我是这样保存的:

procedure TputLine.Button2Click(Sender: TObject);
var
  myLay:TBitmapLayer;
begin
  mylay := iv1.Layers.Items[0] as TBitmapLayer;
  SavePNGTransparentX(mylay.Bitmap);
end;

以及实际的保存代码(来自上述链接)

procedure TPutLine.SavePNGTransparentX(bm32:TBitmap32);
var
  Y: Integer;
  X: Integer;
  Png: TPortableNetworkGraphic32;

  function IsWhite(Color32: TColor32): Boolean;
  begin
    Result:= (TColor32Entry(Color32).B = 255) and
             (TColor32Entry(Color32).G = 255) and
             (TColor32Entry(Color32).R = 255);
  end;

begin
    bm32.ResetAlpha;
    for Y := 0 to bm32.Height-1 do
      for X := 0 to bm32.Width-1 do
      begin
        if IsWhite(bm32.Pixel[X, Y]) then
          bm32.Pixel[X,Y]:=Color32(255,255,255,0);
      end;
    Png:= TPortableNetworkGraphic32.Create;
    Png.Assign(bm32);
    Png.SaveToFile('C:\ThisShouldBeTransparent.png');
    Png.Free;
end;

我不明白为什么它不将图层保存为透明 PNG。 我该如何解决?欢迎任何想法。

您可以使用上面的代码复制我的问题。它使用 GR32_PNG 和 GR32_PortableNetworkGraphic。您只需在您的表单中添加一个TImgView32控件并添加上面列出的代码即可。

【问题讨论】:

    标签: delphi delphi-xe graphics32


    【解决方案1】:

    问题的原因似乎有两个。

    • 首先,当您在单元GR32_PNG 中调用Png.Assign(bm32); 时,它会尝试找出存储图像的最小格式。如果图像的不同颜色少于256 种,它会创建调色板格式,并且根据它找到多少颜色,位深度可以变为 1、2、4 或 8。据我所知,只有具有 TrueColor 和 Alpha 的图像可以保存为可变透明度png 图像。
    • 其次,您只使用一种颜色进行绘制,这会触发上述问题。这当然不是你的错,IMO上述分析应该可以绕过。

    TPortableNetworkGraphic32 类有两个属性,BitDepthColorType,它们控制png 图像的格式,如果它们是可设置的,那将很有用!尝试在代码中将它们设置为:

    Png.BitDepth := 8;
    Png.ColorType := ctTrueColorAlpha;
    

    导致异常

    EPngError 带有消息“可能尚未直接指定位深度!
    带有消息“颜色类型可能尚未直接指定!”的 EPngError!

    从措辞我们可以假设未来会有一些进一步的发展。

    治愈方法

    要绕过上述图像分析,您可以更改 GR32_PNG.pas 中的第 459 行。

    procedure TPortableNetworkGraphic32.AssignPropertiesFromBitmap32()
    var
      ...
    begin
      ...
       IsPalette := True; // <--- change to False
      ...
    

    如果少于 256 种颜色,这将负责位深度分析并防止创建调色板。 在此 hack 之后,您可以使用 SavePNGTransparentX() 过程将 TBitmap32 保存到 .png 文件并保持透明度。

    然后,关于SavePNGTransparentX() 过程,还有一个您可能感兴趣的更改。如您所见,它要求绘图表面的背景为白色,因为它专门将所有白色像素的Alpha 通道设置为零。然而,TBitmapLayer 将所有像素 (RGBA) 初始化为全零,因此每个像素的颜色分量构成黑色(由于 alpha 通道为零,因此不可见)。因此,您需要用白色填充绘图层,使其不透明,再次覆盖所有较低层(在绘图层下方)。

    您可以对此进行更正

    • 移除绘图层的初始化为全白
    • IsWhite 函数更改为IsBlack 函数
    • 更改透明像素的分配

    代码会变成

    procedure TForm8.SavePNGTransparentX(bm32:TBitmap32);
    var
      Y: Integer;
      X: Integer;
      Png: TPortableNetworkGraphic32;
    
      function IsBlack(Color32: TColor32): Boolean;
      begin
        Result:= (TColor32Entry(Color32).B = 0) and
                 (TColor32Entry(Color32).G = 0) and
                 (TColor32Entry(Color32).R = 0);
      end;
    
      function IsWhite(Color32: TColor32): Boolean;
      begin
        Result:= (TColor32Entry(Color32).B = 255) and
                 (TColor32Entry(Color32).G = 255) and
                 (TColor32Entry(Color32).R = 255);
      end;
    
    begin
        bm32.ResetAlpha;
        for Y := 0 to bm32.Height-1 do
          for X := 0 to bm32.Width-1 do
          begin
    //        if IsWhite(bm32.Pixel[X, Y]) then
    //          bm32.Pixel[X,Y]:=Color32(255,255,255,  0);
            if IsBlack(bm32.Pixel[X, Y]) then
              bm32.Pixel[X,Y]:=Color32(  0,  0,  0,  0);
          end;
    
        Png:= TPortableNetworkGraphic32.Create;
        Png.Assign(bm32);
        Png.SaveToFile('C:\tmp\imgs\ThisShouldBeTransparent3.png');
        Png.Free;
    end;
    

    通过此更改,您可以看到绘图图层下方的图层,并且绘图图层的生成文件将使所有未绘制的像素透明。 然而,上述过程总体上仍然存在一个问题,即部分透明的像素会失去透明度,但这将在以后的练习中保留。

    这里有几张图片,首先是在底层加载了位图的 ImageView:

    然后,我基本上使用Button1Click() 中的代码在BL 层(参考您的代码)上画了一个蓝色十字,但没有白色矩形。

    然后我将BL 层保存到.png 文件并使用Windows Explorer“预览”查看它:

    【讨论】:

    • Tom,感谢您提供如此明确而复杂的答案。我现在会尝试您的建议,之后再回复您
    • 很漂亮。非常感谢您提供出色的答案和解决方案。我也很高兴这个问题和代码会帮助很多其他人。如您所见,自从昨天提出问题以来,已经有 66 个可视化
    猜你喜欢
    • 2015-06-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-11
    • 2014-10-05
    • 1970-01-01
    • 2023-03-08
    • 2015-04-17
    相关资源
    最近更新 更多