delphipngc++builderimagelisttimagelist

Saving transparent (alpha channel) PNG from TImageList


I have a TImageList which contains transparent icons (32bit, with alpha channel). What I want to do is to save individual icons based on image index as PNG file(s), while preserving alpha channel transparency. Using RAD Studio 2010 so it has TPngImage support, no need for third party libraries. Images are loaded into TImageList from PNG "sprite" image using the method here - Add a png image to a imagelist in runtime using Delphi XE - so the transparency is preserved upon loading. Now I need to save them out individually, in other words, extract individual images from sprite images which is already loaded into TImageList.

My code so far:

int imageindex = 123;
boost::scoped_ptr<TPngImage>         png(new TPngImage);
boost::scoped_ptr<Graphics::TBitmap> bmp(new Graphics::TBitmap);

MyImageList->GetBitmap(imageindex, bmp.get()); // Using GetBitmap to copy TImageList image into separate TBitmap

png->Assign(bmp.get()); // Assign that bitmap to TPngImage
png->SaveToFile("C:\\filename.png");

The above works but it saves with the white background (transparency is not preserved after saving). I am probably missing a simple step but can't figure it out.

Delphi code is also welcome, shouldn't be hard to translate.


Solution

  • Yes, you can obtain PNG-image from TImageList where it was added. Code below allows you to do this!
    Firstly, add PngImage to your uses clause.

    procedure LoadPNGFromImageList(AImageList: TCustomImageList; AIndex: Integer; var ADestPNG: TPngImage);
    const
      PixelsQuad = MaxInt div SizeOf(TRGBQuad) - 1;
    type
      TRGBAArray = Array [0..PixelsQuad - 1] of TRGBQuad;
      PRGBAArray = ^TRGBAArray;
    var
      ContentBmp: TBitmap;
      RowInOut: PRGBAArray;
      RowAlpha: PByteArray;
      X: Integer;
      Y: Integer;
    begin
      if not Assigned(AImageList) or (AIndex < 0) or
         (AIndex > AImageList.Count - 1) or not Assigned(ADestPNG)
      then
        Exit;
    
      ContentBmp := TBitmap.Create;
      try
        ContentBmp.SetSize(ADestPNG.Width, ADestPNG.Height);
        ContentBmp.PixelFormat := pf32bit;
    
        // Allocate zero alpha-channel
        for Y:=0 to ContentBmp.Height - 1 do
          begin
            RowInOut := ContentBmp.ScanLine[Y];
            for X:=0 to ContentBmp.Width - 1 do
              RowInOut[X].rgbReserved := 0;
          end;
        ContentBmp.AlphaFormat := afDefined;
    
        // Copy image
        AImageList.Draw(ContentBmp.Canvas, 0, 0, AIndex, true);
    
        // Now ContentBmp has premultiplied alpha value, but it will
        // make bitmap too dark after converting it to PNG. Setting
        // AlphaFormat property to afIgnored helps to unpremultiply
        // alpha value of each pixel in bitmap.
        ContentBmp.AlphaFormat := afIgnored;
    
        // Copy graphical data and alpha-channel values
        ADestPNG.Assign(ContentBmp);
        ADestPNG.CreateAlpha;
        for Y:=0 to ContentBmp.Height - 1 do
          begin
            RowInOut := ContentBmp.ScanLine[Y];
            RowAlpha := ADestPNG.AlphaScanline[Y];
            for X:=0 to ContentBmp.Width - 1 do
              RowAlpha[X] := RowInOut[X].rgbReserved;
          end;
      finally
        ContentBmp.Free;
      end;
    end;
    

    Look at the picture. It is depicts what will happen if we set or not set such line of code:

    ContentBmp.AlphaFormat := afIgnored;
    

    enter image description here
    Figure 1 is a result of setting afIgnored and the second one figure is a result of not setting afIgnored, allowing to use previously set afDefined.

    Original image is an image named Figure 1

    Using of code above in application:

    procedure TForm1.aButton1Click(Sender: TObject);
    var
      DestPNG: TPngImage;
    begin
      DestPNG := TPNGImage.Create;
      try
        // Initialize PNG
        DestPNG.CreateBlank(COLOR_RGBALPHA, 8, 60, 60);
    
        // Obtain PNG from image list
        LoadPNGFromImageList(ImageList1, 0, DestPNG);
    
        // Output PNG onto Canvas
        DestPNG.Draw(Canvas, Rect(0, 0, 60, 60));
        DestPNG.SaveToFile('C:\MyPNGIcon.png');
      finally
        DestPNG.Free;
      end;
    end;