How to draw a PNG transparently on a TMetaFileCanvas
Asked Answered
B

2

9

I need to draw a PNG transparently onto a TMetafileCanvas. This works fine when I draw to a bitmap (which I use when displaying on screen), but for printing I need a MetaFile. How can I achieve this?

Here's some code that demonstrates the problem. Put the attached PNG somewhere and update the paths in the code.

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

Briton answered 5/4, 2012 at 12:22 Comment(6)
+1 for the excellent presentation of the problem.Nakashima
A pngimage have to read from the target device context to be able to draw transparently on it what's in the source device context. A meta file canvas is not an actual drawing surface, it represents playable records. You can't read pixel values from a meta file canvas, for instance 'Pixels[x, y] will return '-1';Tharp
@Sertac: I was afraid I was going to get an answer like that. That means it can't be done, and we'll have to change a LOT in our program :-/ Ah, well. Thanks anyway :-)Briton
skip metafiles.. you may always print your bitmap image and not your metafile.Bors
@PA: Yes, we might have to resort to that, but I hope not. Too many unknowns, mostly concerning scaling. (The resulting metafile was used in reports where we didn't know if it filled an entire page or just a small portion)Briton
@Svein - If you can be sure that the raster part of metafile will always be really small, you can assume a white background for the png, transfer the raster part from a temporary bitmap (which you have drawn the background and then the png) to metafile, and leave the rest vectorized. If the raster part is any bigger then perhaps tiny, the resulting metafile might look terrible though..Tharp
M
1

Probably the only way to do this is using the AlphaBlend() Windows function. When you convert the PNG to a 32 bit RGBA file. What you need to do:

  1. Load PNG into a 32bit BMP

  2. Premultiply the RGB values with the A value in the bitmap (is an prerequisite of the AlphaBlend call)

    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. Call AlphaBlend, with a source HDC as the Canvas.Handle of the premultiplied bitmap and a destination handle as the metafile canvas handle.

    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 );
    
Magnetometer answered 6/4, 2012 at 13:42 Comment(0)
B
0

I think you can simply use TransparentBlt function

Barcelona answered 15/12, 2017 at 9:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.