Delphi, GR32 + PngObject: converting to Bitmap32 doesn't work as expected

后端 未结 2 1304
南方客
南方客 2021-01-03 02:25

I\'m using GR32 for drawing multiple semi-transparent PNG images. So far I\'ve been using the following method:

  png:= TPNGObject.Create;
  png.LoadFromFile         


        
2条回答
  •  -上瘾入骨i
    2021-01-03 02:49

    The problem may be that the PNG has incorrectly converted to TBitmap32, losing the transparency information in transit. It is a common case with paletted PNG images. Otherwise, you would not had to use „Bitmap.DrawMode := dmTransparent” and the „OuterColor”. If the transparencry information from PNG would have correctly transferred to TBitmpa32, DrawMode := dmBlend would have worked, without the need to set the OuterColor.

    What matters most is how did you load a PNG into the TBitmap32. The TPngImage from Vcl.Imaging.pngimage unit (implemented in Delphi XE2 and later) can draw transparently on bitmaps, preserving what was on that bitmaps, combining colors using the PNG alpha layer, etc, but it doesn’t allow to easily convert various formats of PNG transparency (including paletted) into the alpha component of each pixel of TBitmap32. Once TPngImage have drawn an image, you get the combined RGB for each pixel, but the alpha component is not transferred to the target bitmap.

    There are helper routines available that try to load a PNG into a TBitmap32 with transparency, but they have drawbacks:

    (1) “LoadPNGintoBitmap32” from http://graphics32.org/wiki/FAQ/ImageFormatRelated - it applies the transparency twice, so the images with alpha values other than 0 or 255 will look differently than in other software (most noticeable on translucent images with glass effects). This code will first apply alpha to RGB and then sets alpha as a separate layer, so when you paint, alpha will be applied again. You can find more information on this issue here: Delphi, GR32 + PngObject: converting to Bitmap32 doesn't work as expected . Besides that, it doesn't convert correctly transparency from paletted images into the alpha layer of TBitmap32. They manually set alpha transparency for the pixels of a certain color of the output bitmap (rendered to RGB) rather doing that before rendering to RGB, so the actual transparency is lost as on your sample image when all white pixels are transparent.

    (2) “LoadBitmap32FromPNG” from gr32ex library: https://code.google.com/archive/p/gr32ex/ - a slightly different implementation of the same algorithm as (1), and has the same issues as (1).

    So, the solutions are:

    1. Do not use TBitmap32; use Vcl.Imaging.pngimage.TPngImage do draw directly on target bitmap (screen, etc.) – this is the most compatible way that deals correctly with various PNG formats.
    2. Use a helper routing to transfer transparency information from Vcl.Imaging.pngimage.TPngImage to TBitmap32.
    3. Use the GR32 PNG library that can natively load a PNG into TBitmap32 https://sourceforge.net/projects/gr32pnglibrary/ Since you now have all the information on this issue, you may get the right solution for you.

    How to load the alpha layer in one pass

    Heinrich Ulbricht made a nice suggestion to remove the transparency layer before paining and then to read the image again. To avoid loading the image twice, you can save the alpha layer before calling PNGObject.RemoveTransparency. Here is the code that correctly applies the alpha layer and loads the image only once. Unfortunately, it does not work with paletted images. If you know how to correctly fill the alpha layer of TBitmap32 from any paletted image, without the effects described at Transparent Png to TBitmap32 please let me know.

    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;
    

提交回复
热议问题