如何在TMetaFileCanvas上透明地绘制PNG图片

8

我需要在MetaFileCanvas上透明地绘制PNG图像。当我绘制到位图(在屏幕上显示时使用)时,这很好用,但是对于打印,我需要一个MetaFile。我该如何做到这一点?

这里有一些演示问题的代码。将附加的PNG放在某个地方并更新代码中的路径。

procedure TForm1.Test;
var
  vPNG : TPNGImage;

  vBMP : TBitmap;
  vMeta : TMetafile;
  vMetaCanvas : TMetafileCanvas;
  LDC : HDC;
begin
  vPNG := TPNGImage.Create;
  try
    vPNG.LoadFromFile('c:\temp\pic\pic.png'); 

    //1. Draw to bitmap. Draw stuff behind PNG to demonstrate transparency
    vBMP := TBitmap.Create;
    try
      vBMP.SetSize(vPNG.Width + 20,vPNG.Height + 20);
      vBMP.Canvas.Pen.Color := clLime;
      vBMP.Canvas.Pen.Width := 5;
      vBMP.Canvas.MoveTo(0,0);
      vBMP.Canvas.LineTo(vBMP.Width,vBMP.Height);
      vBMP.Canvas.MoveTo(vBMP.Width,0);
      vBMP.Canvas.LineTo(0,vBMP.Height);
      vBMP.Canvas.Draw(10,10,vPNG);  //This is drawn correctly and transparently

      Canvas.Draw(10,10,vBMP); //Draw to demo form
    finally
      vBMP.Free;
    end;

    //2. Draw to metafile
    vMeta := TMetaFile.Create;
    try
      LDC := GetDC(0);
      vMetaCanvas := TMetafileCanvas.Create(vMeta,LDC);
      try
        vMetaCanvas.Pen.Color := clLime;
        vMetaCanvas.Pen.Width := 5;
        vMetaCanvas.MoveTo(0,0);
        vMetaCanvas.LineTo(vPNG.Width+20,vPNG.Height+20);
        vMetaCanvas.MoveTo(vPNG.Width+20,0);
        vMetaCanvas.LineTo(0,vPNG.Height+20);

        vMetaCanvas.Draw(10,10,vPNG); //Not correct. Can't see the green line
      finally
        vMetaCanvas.Free;
      end;

      //Demonstrate that resizing works fine:
      Canvas.Draw(10,130,vMeta);
      vMeta.MMWidth := vMeta.MMWidth * 2;
      Canvas.Draw(150,130,vMeta);
      vMeta.MMHeight := vMeta.MMHeight * 2;
      Canvas.Draw(400,130,vMeta);
    finally
      vMeta.Free;
      ReleaseDC(0,LDC);
    end;
  finally
    vPNG.Free;
  end;
end;

Sample picture with transparent areas


5
非常感谢您对问题的出色阐述,我给您点赞。 - Andreas Rejbrand
1
必须从目标设备上下文中读取pngimage才能在其上透明地绘制源设备上下文中的内容。元文件画布不是实际的绘图表面,它代表可播放的记录。例如,“Pixels[x, y]”将返回“-1”,无法从元文件画布中读取像素值。 - Sertac Akyuz
@Sertac:我就怕会得到这样的答案。这意味着它无法完成,我们将不得不在我们的程序中进行大量更改 :-/ 哎,好吧。无论如何还是谢谢 :-) - Svein Bringsli
跳过元文件..您可以始终打印位图图像而不是元文件。 - PA.
@PA:是的,我们可能不得不采取那种方式,但我希望不要。有太多未知因素,主要是涉及扩展问题。(生成的元文件在报告中使用,我们不知道它是否填满整个页面还是只占很小一部分) - Svein Bringsli
@Svein - 如果您可以确定元文件的 光栅 部分始终非常小,那么您可以假定 PNG 的背景为白色,将光栅部分从临时位图(其中已经绘制了背景和 PNG)转移到元文件中,并保留其余部分矢量化。如果光栅部分稍微大一些,那么生成的元文件可能会看起来很糟糕... - Sertac Akyuz
2个回答

0

可能唯一的方法是使用AlphaBlend() Windows函数。当您将PNG转换为32位RGBA文件时,需要执行以下操作:

  1. 将PNG加载到32位BMP中

  2. 使用位图中的A值对RGB值进行预乘(这是AlphaBlend调用的先决条件)

              for y := 0 to FHeight - 1 do begin
                Src  := BM.ScanLine[ y ];
                Dest := FBitmap.ScanLine[ y ];
                for x := 0 to FWidth - 1 do begin
                  A := Src^; Inc( Src );
                  Dest^ := MulDiv( Dest^, A, 255 ); Inc( Dest );
                  Dest^ := MulDiv( Dest^, A, 255 ); Inc( Dest );
                  Dest^ := MulDiv( Dest^, A, 255 ); Inc( Dest );
                  Dest^ := A;                       Inc( Dest );
                end;
              end;
    
  3. 使用源HDC作为预乘位图的Canvas.Handle,目标句柄作为元文件画布句柄来调用Alphablend。

    BF.BlendOp             := AC_SRC_OVER;
    BF.BlendFlags          := 0;
    BF.SourceConstantAlpha := 255;
    BF.AlphaFormat         := AC_SRC_ALPHA;
    AlphaBlend( Bitmap.Canvas.Handle, X, Y, BM.Width, BM.Height, BM.Canvas.Handle, 0, 0, BM.Width, BM.Height, BF );
    

0

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