12

A newly created bitmap seems to have a (white) background by default. At least, a query on the Pixels property confirms. But why is that background color not used as the transparent color when Transparent is set true?

Consider this simple test code:

procedure TForm1.Button1Click(Sender: TObject);
var
  Bmp: TBitmap;
begin
  Bmp := TBitmap.Create;
  try
    Bmp.Width := 100;
    Bmp.Height := 100;
    Bmp.Transparent := True;
    Canvas.Draw(0, 0, Bmp);                              // A white block is drawn
    Bmp.Canvas.Brush.Color := Bmp.Canvas.Pixels[0, 99];  // = 'clWhite'
    Bmp.Canvas.FillRect(Rect(0, 0, 100, 100));
    Canvas.Draw(0, 100, Bmp);                            // "Nothing" is drawn
  finally
    Bmp.Free;
  end;
end;

For some reason, the entire bitmap surface has to be painted before it can appear transparent, which sounds kind of odd.

The following variations are tried to eliminate the call to FillRect, all with the same outcome (no transparancy):

  • Only setting Brush.Color,
  • Brush.Handle := CreateSolidBrush(clWhite),
  • PixelFormat := pf32Bit,
  • IgnorePalette := True,
  • TransparantColor := clWhite,
  • TransparantMode := tmFixed,
  • Canvas.Pixels[0, 99] := clWhite which makes only thát pixel transparent,
  • Modified := True.

So, the wish is to paint only a portion of a newly created bitmap and get the remaining surface transparent.

Using: Delphi 7, Win 7/64.

NGLN
  • 43,011
  • 8
  • 105
  • 200

3 Answers3

12

Just set TransparentColor and Canvas.Brush.Color before setting dimensions of the bitmap.

NGLN
  • 43,011
  • 8
  • 105
  • 200
Torbins
  • 2,111
  • 14
  • 16
  • Yes! And no need for setting TransparentColor. Setting Canvas.Brush.Color befóre giving dimensions does the trick. Thank you. – NGLN Oct 12 '11 at 08:07
  • 5
    Anyone know why this is so? Do you even need to set the brush color? Is it enough just to force the brush handle to exist? – David Heffernan Oct 12 '11 at 08:11
  • 1
    @David Good point. `Canvas.Brush.Handle := 0` also works, strangely enough. – NGLN Oct 12 '11 at 08:15
  • 5
    @NGLN I doubt you even need to assign it. Just write `Canvas.Brush.Handle;` so that it is created. – David Heffernan Oct 12 '11 at 08:26
  • I just wanted to add that this answer helped me with Delphi 11.3, so whatever the reason for this; it still applies. – MarkF Mar 09 '23 at 14:01
3

I really needed to be able to create a completely transparent (and otherwise empty/blank) TBitmap of an arbitrary size in 32bit RGBA format. Many times. Lazarus is able to load such a bitmap into TBitmap and after it's loaded, you can manipulate it with scanline and what not using RGBA format. But it just doesn't work when you create TBitmap yourself. Pixel format seems to be completely ignored. So what I did is so out-of-the box, and simple, that it's almost AMUZING (!). But it is practical, works super nice, and is completely independent of LCL and any 3rd party libraries. Even does not depend on Graphics unit, becasue it generates the actual 32bit RGBA BMP file (I generate it to TMemoryStream, you can generate differently). Then once you have it, elsewhere in your code you can just load it using TBitmap.LoadFrom source or TPicture.LoadFrom source.

The background story I initially wanted to generate properly formatted BMP file following the format as described here: http://www.fileformat.info/format/bmp/egff.htm But there were few variants of BMP format, and I was not clear on which one I was supposed to follow. So I decided to go with a reverse engineering approach, but the format description helped me later on. I used a graphical editor (I used GIMP) to create an empty 1x1 pixel 32 RGBA BMP file, and called it alpha1p.bmp, it only contained transparency nothing else. Then I resized the canvas to 10x10 pixels, and saved as alpha10p.bmp file. Then I compared the two files: compating two bmp files in vbindiff on Ubuntu So I found out that the only differences were the added pixels (every one was 4 bytes all zeros RGBA), and few other bytes in the header. Because of the format documentation at the linked I shared, I figured out that these were: FileSize (in bytes), BitmapWidth (in pixels), BitmapHeight (in pixels) and BitmapDataSize (in bytes). The last one was BitmapWidth*BitmapHeight*4, because each pixel in RGBA is 4 bytes. So now, I could just generate that entire sequence of bytes as seen inside alpha1p.bmp files, minus 4 bytes from the end (the 1st of the BitmapData), then add 4 bytes (all zeroes) of RGBA data for each pixel of the BMP I want to generate, then come back to the initial sequence and update the variable parts: FileSize, width, height and BMP data size. And it works flawlessly! I just had to add test for BigEndian and would swap word and dword numbers before writting. That would become issue on ARM platforms working in BigEndian.

The code

const
  C_BLANK_ALPHA_BMP32_PREFIX : array[0..137]of byte
  = ($42, $4D, $00, $00, $00, $00, $00, $00,    $00, $00, $8A, $00, $00, $00, $7C, $00,
     $00, $00, $0A, $00, $00, $00, $0A, $00,    $00, $00, $01, $00, $20, $00, $03, $00,
     $00, $00, $90, $01, $00, $00, $13, $0B,    $00, $00, $13, $0B, $00, $00, $00, $00,
     $00, $00, $00, $00, $00, $00, $00, $00,    $00, $FF, $00, $00, $FF, $00, $00, $FF,
     $00, $00, $FF, $00, $00, $00, $42, $47,    $52, $73, $00, $00, $00, $00, $00, $00,
     $00, $00, $00, $00, $00, $00, $00, $00,    $00, $00, $00, $00, $00, $00, $00, $00,
     $00, $00, $00, $00, $00, $00, $00, $00,    $00, $00, $00, $00, $00, $00, $00, $00,
     $00, $00, $00, $00, $00, $00, $00, $00,    $00, $00, $02, $00, $00, $00, $00, $00,
     $00, $00, $00, $00, $00, $00, $00, $00,    $00, $00 );

(...)

  Function  RenderEmptyAlphaBitmap(AWidth,AHeight: integer): TMemoryStream;
   var
     buf : array[1..4096]of byte;
     i,p : int64;
     w   : word;
     dw  : dword;
     BE  : Boolean;
   begin
     buf[low(buf)] := $00; //this is jyst to prevent compiler warning about not initializing buf variable
     Result := TMemoryStream.Create;
     if(AWidth <1)then AWidth  := 1;
     if(AHeight<1)then AHeight := 1;
   //Write File Header:
     Result.Write(C_BLANK_ALPHA_BMP32_PREFIX, SizeOf(C_BLANK_ALPHA_BMP32_PREFIX));
   //Now start writing the pixels:
     FillChar(buf[Low(buf)],Length(buf),$00);
     p := Result.Position;
     Result.Size := Result.Size+int64(AWidth)*int64(AHeight)*4;
     Result.Position := p;
     i := int64(AWidth)*int64(AHeight)*4; //4 because RGBA has 4 bytes
     while(i>0)do
       begin
         if(i>Length(buf))
           then w := Length(buf)
           else w := i;
         Result.Write(buf[Low(buf)], w);
         dec(i,w);
       end;
   //Go back to the original header and update FileSize, Width, Height, and offset fields:
     BE := IsBigEndian;
     Result.Position :=  2; dw := Result.Size;
     if BE then SwapEndian(dw); Result.Write(dw, SizeOf(dw));
     Result.Position := 18; dw := AWidth;
     if BE then SwapEndian(dw); Result.Write(dw, SizeOf(dw));
     Result.Position := 22; dw := AHeight;
     if BE then SwapEndian(dw); Result.Write(dw, SizeOf(dw));
     Result.Position := 34; dw := AWidth*AHeight*4;
     if BE then SwapEndian(dw); Result.Write(dw, SizeOf(dw));
   //Done:
     Result.Position := 0;
   end;

Notice how C_BLANK_ALPHA_BMP32_PREFIX constant is basically the copy of byte sequence from my sample alpha1p.bmp file, minus last 4 bytes, which were the RGBA pixel. :D

Also, I am using IsBigEndian function which goes like this:

  Function  IsBigEndian: Boolean;
   type
    Q = record case Boolean of
          True  : (i: Integer);
          False : (p: array[1..4] of Byte);
        end;
   var
     x : ^Q;
   begin
     New(x);
     x^.i := 5;
     Result := (x^.p[4]=5);
     Dispose(x);
   end;

This is copied from Lazarus Wiki: http://wiki.freepascal.org/Writing_portable_code_regarding_the_processor_architecture you can skip this part if you don't deal with BigEndian platforms, or you can use a compilers IFDEF directive. The thing is that if you use {$IFDEF ENDIAN_BIG} then it is what compiler things is the case, whereas the function actually tests the system. This is explained in the linked wiki.

Sample usage

Procedure TForm1.Button1Click(Sender: TObject);
var
  MS  : TMemoryStream;
begin
  MS := RenderEmptyAlphaBitmap(Image1.Width, Image1.Height);
  try
    if Assigned(MS)then Image1.Picture.LoadFromStream(MS);
  //you can also MS.SaveToFile('my_file.bmp'); if you want
  finally
    FreeAndNil(MS);
  end;
end;
NGLN
  • 43,011
  • 8
  • 105
  • 200
Kris Jace
  • 69
  • 1
  • 5
  • on Quora I suggested adopting this method with SDL: https://www.quora.com/A-simple-way-to-create-a-transparent-surface-with-SDL-Pascal/answer/Krzysztof-Kamil-Jacewicz – Kris Jace Dec 09 '17 at 15:06
  • I don't think the question is about 32bit *alpha* transparent bitmaps. in any case, nice idea. but doesn't this work as expected?: https://pastebin.com/EXDRu3zG – kobik Dec 10 '17 at 07:32
  • @kobik : your method did not work for me on some occasions. I think where it did not work was on Linux using gtk widgetset. – Kris Jace Dec 12 '17 at 04:31
  • @NGLN - you report it as you see fit, however: - I clearly see 32bit bitmap reference in the question in the "PixelFormat := pf32Bit" part. - using my method you can achieve what asker is describing without having to paint (fillrect) first. I'm just showing an alternative method to what the asker mentioned that he has already tired (including 32bit bitmap) *BTW - I just now noticed you ARE the asker. Well, you then have all the privilege to mark my answer as offtopic. But I will never try to serve any advice to you again. – Kris Jace Dec 12 '17 at 04:33
2

This will draw a Red square and the rest is transparent.

procedure TForm1.btnDrawClick(Sender: TObject);
var
  Bmp: TBitmap;
begin
  Bmp := TBitmap.Create;
  try
    Bmp.Width := 100;
    Bmp.Height := 100;
    Bmp.Transparent := TRUE;
    Bmp.TransparentColor := clWhite;

    Bmp.Canvas.Brush.Color := clWhite;
    Bmp.Canvas.FillRect(Rect(0, 0, Bmp.Width, Bmp.Height));
    Bmp.Canvas.Brush.Color := clRed;
    Bmp.Canvas.FillRect(Rect(42, 42, 20, 20));
    Canvas.Draw(12, 12, Bmp);
  finally
    Bmp.Free;
  end;
end;
Shambhala
  • 1,159
  • 3
  • 13
  • 31
  • The wish is to get this result without the first FillRect, as clearly stated in the question. -1 – NGLN Oct 12 '11 at 05:11