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;