我真的需要能够以 32 位 RGBA 格式创建一个任意大小的完全透明(否则为空/空白)的 TBitmap。多次。 Lazarus 能够将这样的位图加载到 TBitmap 中,加载后,您可以使用扫描线以及不使用 RGBA 格式的内容对其进行操作。但是当你自己创建 TBitmap 时它就不起作用了。像素格式似乎被完全忽略了。所以我所做的就是开箱即用,而且很简单,几乎令人惊叹(!)。但它很实用,效果超级好,并且完全独立于 LCL 和任何第三方库。甚至不依赖于 Graphics 单元,因为它生成实际的 32bit RGBA BMP 文件(我生成到 TMemoryStream,你可以生成不同的)。然后,一旦你有了它,你就可以在代码的其他地方使用 TBitmap.LoadFrom 源或 TPicture.LoadFrom 源来加载它。
背景故事
我最初想按照此处描述的格式生成格式正确的 BMP 文件:http://www.fileformat.info/format/bmp/egff.htm
但是 BMP 格式的变体很少,我不清楚我应该遵循哪一个。所以我决定采用逆向工程方法,但格式描述后来帮助了我。我使用图形编辑器(我使用 GIMP)创建了一个空的 1x1 像素 32 RGBA BMP 文件,并将其命名为 alpha1p.bmp,它只包含透明度,没有其他内容。
然后我将画布的大小调整为 10x10 像素,并保存为 alpha10p.bmp 文件。
然后我比较了两个文件:
compating two bmp files in vbindiff on Ubuntu
所以我发现唯一的区别是添加的像素(每个像素都是 4 个字节,全为零 RGBA),以及标题中的其他几个字节。由于我共享的链接中的格式文档,我发现它们是:FileSize(以字节为单位)、BitmapWidth(以像素为单位)、BitmapHeight(以像素为单位)和BitmapDataSize(以字节为单位)。最后一个是BitmapWidth*BitmapHeight*4,因为RGBA中的每个像素都是4字节。所以现在,我可以生成在 alpha1p.bmp 文件中看到的整个字节序列,从末尾减去 4 个字节(BitmapData 的第一个字节),然后为每个像素添加 4 个字节(全为零)的 RGBA 数据我要生成的 BMP,然后回到初始序列并更新可变部分:FileSize、宽度、高度和 BMP 数据大小。它完美无瑕!我只需要为 BigEndian 添加测试,并在写入之前交换 word 和 dword 数字。这将成为在 BigEndian 中工作的 ARM 平台上的问题。
代码
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;
注意C_BLANK_ALPHA_BMP32_PREFIX 常量基本上是我的示例 alpha1p.bmp 文件中字节序列的副本,减去最后 4 个字节,即 RGBA 像素。 :D
另外,我正在使用IsBigEndian 函数,如下所示:
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;
这是从 Lazarus Wiki 复制的:http://wiki.freepascal.org/Writing_portable_code_regarding_the_processor_architecture 如果您不处理 BigEndian 平台,则可以跳过此部分,或者您可以使用编译器 IFDEF 指令。问题是,如果您使用{$IFDEF ENDIAN_BIG},那么编译器就是这种情况,而函数实际上是测试系统。这在链接的 wiki 中有说明。
使用示例
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;