【问题标题】:Graphics32 - saving transparent drawing layer to pngGraphics32 - 将透明绘图层保存为 png
【发布时间】:2015-06-24 15:55:26
【问题描述】:

我在一个 ImgView32 的图层上画了一条虚线。稍后,我想将每一层保存为透明的 PNG。 对于我拥有的任何其他图层,保存效果很好。但是对于绘图层,它没有。

为了使问题更易于理解,请使用 gr32 库中的示例代码,更具体地说是 Layers 示例。其主菜单中的选项之一是添加自定义绘图层(新建自定义层 -> 简单绘图层)。 然后尝试将该图层保存为透明的 PNG 图像,您最终会得到一个损坏的 PNG 文件(您无法使用任何其他图片查看器打开它,例如 Paint.net 或 Microsoft Photo Viewer)。如果您尝试将图层的 bitmap32 保存为位图,则会发生同样的情况,如下面的代码所示...

我尝试了两种将Bitmap32保存为透明PNG的方法,所以第一种如下:

procedure TMainForm.SavePNGTransparentX(bm32:TBitmap32; dest:string);
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;
    try
      Png.Assign(bm32);
      Png.SaveToFile(dest);
    finally
      Png.Free;
    end;

end;

因此,如果我将 PNG 加载到这样的图层中,则上述方法有效:

mypng := TPortableNetworkGraphic32.Create;
mypng.LoadFromStream(myStream);
B := TBitmapLayer.Create(ImgView.Layers);
with B do
   try
      mypng.AssignTo(B.Bitmap);
      ...

但是,一旦我尝试保存使用图层示例中的代码创建的图层,结果就会损坏。 即使我尝试将图层保存为这样的位图(尽管这不是我的意图,因为我需要它们是 PNG):

mylay := TBitmapLayer(ImgView.Layers.Items[i]);
mylay.Bitmap.SaveToFile('C:\tmp\Layer'+IntToStr(i)+'.bmp');

同样的损坏发生了。 所以,这不像我收到异常或任何东西......它只是以某种方式被保存损坏;

我还尝试了其他方法将 Bitmap32 保存为透明 PNG,例如 GR32_PNG 方法:

function SaveBitmap32ToPNG (sourceBitmap: TBitmap32;transparent: Boolean;bgColor32: TColor32;filename: String;compressionLevel: TCompressionLevel = 9;interlaceMethod: TInterlaceMethod = imNone): boolean;
var  png: TPNGImage;
begin
  result := false;
  try
    png := Bitmap32ToPNG (sourceBitmap,false,transparent,WinColor(bgColor32),compressionLevel,interlaceMethod);
    try
      png.SaveToFile (filename);
      result := true;
    finally
      png.Free;
    end;
  except
    result := false;
  end;
end;

在哪里

function Bitmap32ToPNG (sourceBitmap: TBitmap32;paletted, transparent: Boolean;bgColor: TColor;compressionLevel: TCompressionLevel = 9;interlaceMethod: TInterlaceMethod = imNone): TPNGImage; // TPNGObject
var
  bm: TBitmap;
  png: TPNGImage;//TPngObject;
  TRNS: TCHUNKtRNS;
  p: pngImage.PByteArray;
  x, y: Integer;
begin
  Result := nil;
  png := TPngImage.Create; // TPNGObject
  try
    bm := TBitmap.Create;
    try
      bm.Assign (sourceBitmap);        // convert data into bitmap
      // force paletted on TBitmap, transparent for the web must be 8bit
      if paletted then
        bm.PixelFormat := pf8bit;
      png.interlaceMethod := interlaceMethod;
      png.compressionLevel := compressionLevel;
      png.Assign(bm);                  // convert bitmap into PNG
                                       // this is where the access violation occurs
    finally
      FreeAndNil(bm);
    end;
    if transparent then begin
      if png.Header.ColorType in [COLOR_PALETTE] then begin
        if (png.Chunks.ItemFromClass(TChunktRNS) = nil) then png.CreateAlpha;
        TRNS := png.Chunks.ItemFromClass(TChunktRNS) as TChunktRNS;
        if Assigned(TRNS) then TRNS.TransparentColor := bgColor;
      end;
      if png.Header.ColorType in [COLOR_RGB, COLOR_GRAYSCALE] then png.CreateAlpha;
      if png.Header.ColorType in [COLOR_RGBALPHA, COLOR_GRAYSCALEALPHA] then
      begin
        for y := 0 to png.Header.Height - 1 do begin
          p := png.AlphaScanline[y];
          for x := 0 to png.Header.Width - 1
          do p[x] := AlphaComponent(sourceBitmap.Pixel[x,y]);  // TARGB(bm.Pixel[x,y]).a;
        end;
      end;
    end;
    Result := png;
  except
    png.Free;
  end;
end;

但使用这种方法时,我在尝试保存此特定图层时会收到 EAccessViolation。对于任何其他图层(不是绘图图层),它不会使我的项目崩溃,除了这个自定义绘图。 访问冲突发生在这一行:

png.Assign(bm);

Bitmap32ToPNG 函数内部

您知道为什么会发生这种情况吗?我该如何防止这种情况发生?

编辑

我尝试改用 TBitmapLayer,因为 TPositionedLayer 可能由于某种原因缺少 Bitmap32。 所以我的代码是这样的:

// adding a BitmapLayer and setting it's onPaint event to my handler
procedure TMainForm.Mynewlayer1Click(Sender: TObject);
var
  B: TBitmapLayer;
  P: TPoint;
  W, H: Single;
begin
      B := TBitmapLayer.Create(ImgView.Layers);
      with B do
      try
        Bitmap.SetSize(100,200);
        Bitmap.DrawMode := dmBlend;

        with ImgView.GetViewportRect do
          P := ImgView.ControlToBitmap(GR32.Point((Right + Left) div 2, (Top + Bottom) div 2));

        W := Bitmap.Width * 0.5;
        H := Bitmap.Height * 0.5;

        with ImgView.Bitmap do
          Location := GR32.FloatRect(P.X - W, P.Y - H, P.X + W, P.Y + H);

        Scaled := True;
        OnMouseDown := LayerMouseDown;
        OnPaint := PaintMy3Handler;
      except
        Free;
        raise;
      end;
      Selection := B;
end;

// and the PaintHandler is as follows:
procedure TMainForm.PaintMy3Handler(Sender: TObject;Buffer: TBitmap32);
var
  Cx, Cy: Single;
  W2, H2: Single;
const
  CScale = 1 / 200;
begin

  if Sender is TBitmapLayer then
    with TBitmapLayer(Sender).GetAdjustedLocation do
    begin
      // Five black pixels, five white pixels since width of the line is 5px
      Buffer.SetStipple([clBlack32, clBlack32, clBlack32, clBlack32, clBlack32,
        clWhite32, clWhite32, clWhite32, clWhite32, clWhite32]);

      W2 := (Right - Left) * 0.5;
      H2 := (Bottom - Top) * 0.5;

      Cx := Left + W2;
      Cy := Top + H2;
      W2 := W2 * CScale;
      H2 := H2 * CScale;
      Buffer.PenColor := clRed32;

      Buffer.StippleCounter := 0;
      Buffer.MoveToF(Cx-2,Top);
      Buffer.LineToFSP(Cx-2 , Bottom);

      Buffer.StippleCounter := 0;
      Buffer.MoveToF(Cx-1,Top);
      Buffer.LineToFSP(Cx-1 , Bottom);

      Buffer.StippleCounter := 0;
      Buffer.MoveToF(Cx,Top);
      Buffer.LineToFSP(Cx , Bottom);

      Buffer.StippleCounter := 0;
      Buffer.MoveToF(Cx+1,Top);
      Buffer.LineToFSP(Cx+1 , Bottom);

      Buffer.StippleCounter := 0;
      Buffer.MoveToF(Cx+2,Top);
      Buffer.LineToFSP(Cx+2 , Bottom);
    end;
end;

请记住,我使用的是默认图层演示应用程序。所以这只是添加的代码。我没有删除或更改演示代码中的任何内容。 所以我创建了一个新图层(TBitmapLayer)并在 onPaint 上绘制。最后我想将该图层的内容保存为PNG。但似乎 onPaint 可能会在其他地方而不是实际图层上绘制。否则我不明白为什么保存的图像是空的。 所以这次生成的 PNG 没有损坏,但它是空的......

【问题讨论】:

  • 我认为问题可能在于,通过创建 TPositionedLayer,永远不会创建 Bitmap32,所以它必须是我只有一个空容器(层)用于 bitmap32。这一定是我不能将图层的位图分配给任何东西的原因......?!?!这听起来像是一个可能的原因吗?
  • 当我注意到您的评论时,我刚刚发布了一个答案。是的,你的猜测是正确的。

标签: delphi delphi-xe graphics32


【解决方案1】:

错误在于示例创建了不包含位图的TPositionedLayer 层。您不能将此图层类型转换为 TBitmapLayer 并期望它创建图层的位图图像,就像您在此代码中所做的那样:

  mylay := TBitmapLayer(ImgView.Layers.Items[i]);
  mylay.Bitmap.SaveToFile('C:\tmp\Layer'+IntToStr(i)+'.bmp');

我假设您执行了类似于保存到 .png 文件的操作,尽管您没有显示该代码。

示例(带有TPositionedLayer 层)使用ImgView.Buffer 在屏幕上绘图。您可以将其保存为 .png 文件,如下所示:

  SavePNGTransparentX(ImgView.Buffer, 'c:\tmp\imgs\buffer.png');

但我不希望它对您的单独图层图像令人满意。

你没有像以前那样使用TBitmapLayers的原因是什么?


由 user1137313 在 cmets 之后编辑

受您自己找到的解决方案的启发(参考您的评论),我建议以下内容,仅在需要保存时将图层绘制到额外的位图。

从一个菜单项开始

procedure TMainForm.mnFileSaveClick(Sender: TObject);
begin
  SaveLayerToPng(ImgView.Layers[ImgView.Layers.Count-1], 'c:\tmp\imgs\buffer.png');
end;

如果您同时保存多个图层,您可能希望循环调用SaveLayerToPng(),并根据需要更改文件名。

然后是SaveLayerToPng() 过程

procedure TMainForm.SaveLayerToPng(L: TCustomLayer; FileName: string);
var
  bm32: TBitmap32;
begin
  bm32:= TBitmap32.Create;
  try
    bm32.SetSizeFrom(ImgView.Buffer);
    PaintSimpleDrawingHandler(L, bm32);
    SavePNGTransparentX(bm32, FileName);
  finally
    bm32.Free;
  end;
end;

它调用现有的PaintSimpleDrawingHandler(Sender: TObject; buffer: TBitmap32) 过程来绘制bm32,然后将其传递给`SavePNGTransparentX() 以进行实际保存。

我使用了 Graphics32 示例的绘制处理程序,但您的 PaintMy3Handler() 也可以使用。

最终结果与您的解决方案相同,只是在要保存文件时才绘制额外的TBitmap32

【讨论】:

  • 代码中显示了TPositionedLayer的用法并不代表我没有尝试使用TBitmapLayer。实际上我当前的代码创建了 TBitmapLayer。但是错误是一样的。最后一层的位图仍然损坏,所以仍然有问题。请检查我更新的问题
  • 那么请随意给我一个解决方案...现在我添加了一些代码行来将缓冲区(来自 onPaint)分配给 OnPaint 末尾的图层位图。当然这不是一个解决方案,但我想测试 TBitmapLayer 是否在 onPaint 中接收到实际的位图,以及我是否可以通过保存它来测试它。是的,现在我看到了一个包含内容的 PNG。现在我的问题是:如何将实际绘图放在图层上,而不是递归。因此,除了 onPaint 之外,我可能还需要图层的其他事件,我应该将 ImgView.Buffer 分配给 Layer.Bitmap ......并且也只是 Buffer 的一部分。
  • 我自己解决了。在 onPaint Handler 中,我在 2 个 Bitmaps32 上并行绘制。我在缓冲区中绘制,我也在 bmp32:Tbitmap32 上绘制。在 onPaint 事件结束时,我这样做(Sender as TBitmapLayer).Bitmap.Assign(bmp32); 这样,图层就会被绘制,所以当我将图层保存为 PNG 时,图层位图中会有内容
  • 尽管我自己发现了我的问题,然后你只确认我的假设是正确的,但我还是决定给你正确的答案
  • @user1137313 谢谢!我编辑了我的答案以包含您的解决方案,并稍作修改。
猜你喜欢
  • 1970-01-01
  • 2015-06-17
  • 2015-04-18
  • 2015-04-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-11
  • 2014-10-05
相关资源
最近更新 更多