szpliter
szpliter

Reputation: 87

How to use correctly TBitmap object to save a file with transparency?

Below is my sample code:

var    lBitmap: TBitmap;
begin
    lBitmap := TBitmap.Create;
    lBitmap.PixelFormat := TPixelFormat.pf32bit;
    lBitmap.Transparent := TRUE; // !

    lBitmap.LoadFromFile( 'd:\temp\bmp32b_300dpi_transparent_400x250.bmp' ); 
    // Bitmap RGB+Alpha created with GIMP  

    // modifications on pixels

    Canvas.Draw(100, 0, lBitmap);
    // Up to this point it is correct, the drawing is painted with transparency

    lBitmap.SaveToFile( 'd:\tmp\after.bmp' ); // after this -> I have lost transparency

    lBitmap.Free;
end;

How to use correctly TBitmap object to save a file with transparency?

Upvotes: 4

Views: 1693

Answers (2)

ZHN
ZHN

Reputation: 33

Saving 32-bit bitmaps with alpha channels needs a workaround as @Andreas Rejbrand has pointed out. There also seems to be some more confusion about the BMP file format, what the TBitmap.Transparent property does, and how you draw bitmaps transparently with the VCL.

32-bit bitmaps are the only bitmaps that carry information about transparency in the files. They have that information in the alpha channel and nowhere else. In the alpha channel, every pixel has its own 0-255 alpha value in the RGBA structure. This is often referred to as partial transparency.

When you draw/display 32-bit bitmaps, you have to pay attention to the TBitmap.AlphaFormat property. It defaults to afIgnore, which means that the bitmap is drawn without transparency. Use afPremultiplied or afDefined to draw with transparency. The latter is probably what you want.

The TBitmap.Transparent property is specific to the VCL TBitmap, and there is nothing in the BMP file format that corresponds to it. It's just a simple way to display bitmaps transparently, where a color defines which pixels should be fully transparent. The application must be familiar with the bitmaps to be able to use this method. It's also important to be aware of how the TBitmap.TransparentMode property works. It defaults to tmAuto, which sets the color of the bottom-leftmost pixel of the bitmap as TBitmap.TransparentColor. When TransparentMode is set to tmFixed, the TBitmap.TransparentColor you have specified is used. This method can also be used on 32-bit bitmaps.

Note that when you draw with the standard VCL TCanvas drawing routines on a 32-bit bitmap with transparency in the alpha channel, the transparency will be lost where you have drawn.

It seems that in your sample code, you have ignored AlphaFormat and TransparentMode. You should also decide if you want to use the transparency in the alpha channel or the TBitmap.Transparent method. But we have no bitmap in order to check if that is the real problem.

Upvotes: 2

Andreas Rejbrand
Andreas Rejbrand

Reputation: 109158

It seems to me like TBitmap doesn't support saving bitmaps with alpha channels. And maybe we shouldn't blame the VCL for this, because BMPs with alpha transparency are uncommon. Many applications don't support transparent BMPs.

This being said, I "reverse-engineered" a BMP with alpha channel created in GIMP and wrote the following Delphi routine to produce the very same bitmap:

procedure SaveTransparentBitmap(ABitmap: TBitmap; const AFileName: string);
var
  FS: TFileStream;
  BFH: TBitmapFileHeader;
  BIH: TBitmapV5Header;
  y: Integer;
  sl: PUInt64;
begin

  // ABitmap MUST have the GIMP BGRA format.

  FS := TFileStream.Create(AFileName, fmOpenWrite);
  try

    // Bitmap file header
    FillChar(BFH, SizeOf(BFH), 0);
    BFH.bfType := $4D42;  // BM
    BFH.bfSize := 4 * ABitmap.Width * ABitmap.Height + SizeOf(BFH) + SizeOf(BIH);
    BFH.bfOffBits := SizeOf(BFH) + SizeOf(BIH);
    FS.Write(BFH, SizeOf(BFH));

    // Bitmap info header
    FillChar(BIH, SizeOf(BIH), 0);
    BIH.bV5Size := SizeOf(BIH);
    BIH.bV5Width := ABitmap.Width;
    BIH.bV5Height := ABitmap.Height;
    BIH.bV5Planes := 1;
    BIH.bV5BitCount := 32;
    BIH.bV5Compression := BI_BITFIELDS;
    BIH.bV5SizeImage := 4 * ABitmap.Width * ABitmap.Height;
    BIH.bV5XPelsPerMeter := 11811;
    BIH.bV5YPelsPerMeter := 11811;
    BIH.bV5ClrUsed := 0;
    BIH.bV5ClrImportant := 0;
    BIH.bV5RedMask :=   $00FF0000;
    BIH.bV5GreenMask := $0000FF00;
    BIH.bV5BlueMask :=  $000000FF;
    BIH.bV5AlphaMask := $FF000000;
    BIH.bV5CSType := $73524742; // BGRs
    BIH.bV5Intent := LCS_GM_GRAPHICS;
    FS.Write(BIH, SizeOf(BIH));

    // Pixels
    for y := ABitmap.Height - 1 downto 0 do
    begin
      sl := ABitmap.ScanLine[y];
      FS.Write(sl^, 4 * ABitmap.Width);
    end;

  finally
    FS.Free;
  end;

end;

This write a BITMAPFILEHEADER followed by a BITMAPV5HEADER and the pixel data in BGRA format.

I omit all kinds of error checking. For instance, I don't verify that ABitmap actually has the required format.

Test:

procedure TForm1.FormCreate(Sender: TObject);
var
  bm: TBitmap;
begin
  bm := TBitmap.Create;
  try
    bm.LoadFromFile('C:\Users\Andreas Rejbrand\Desktop\Test.bmp');
    SaveTransparentBitmap(bm, 'C:\Users\Andreas Rejbrand\Desktop\Test2.bmp');
  finally
    bm.Free;
  end;
end;

After this, Test.bmp and Test2.bmp are binary equal.

Upvotes: 10

Related Questions