我真的需要能够创建任意大小的32位RGBA格式完全透明(以及其他空/空白)TBitmap。很多时候。 Lazarus能够将这样的位图加载到TBitmap中,加载后,您可以使用RGBA格式使用scanline等进行操作。但是当您自己创建TBitmap时,它就无法正常工作。像素格式似乎被完全忽略。所以我所做的是如此独特和简单,以至于它几乎是有趣的!但它是实用的,非常好用的,并且完全独立于LCL和任何第三方库。甚至不依赖于Graphics单元,因为它生成实际的32位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文件。
然后我比较了这两个文件:
在Ubuntu上使用vbindiff比较两个bmp文件
所以我发现唯一的区别是添加的像素(每个像素都是4字节全零RGBA),以及头部中的几个其他字节。由于我分享的链接中的格式文档,我发现这些是:FileSize
(以字节为单位),BitmapWidth
(以像素为单位),BitmapHeight
(以像素为单位)和BitmapDataSize
(以字节为单位)。最后一个是BitmapWidth * BitmapHeight * 4
,因为RGBA中的每个像素都是4个字节。因此,现在,我只需生成alpha1p.bmp文件中看到的整个字节序列,减去末尾的4个字节(BitmapData
的第一个字节),然后为要生成的BMP的每个像素添加4字节(全零)RGBA数据,然后回到初始序列并更新可变部分:FileSize、width、height和BMP数据大小。它可以完美地工作!我只需要添加BigEndian的测试,并在写入之前交换字和双字号码。这将成为在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;
Result := TMemoryStream.Create;
if(AWidth <1)then AWidth := 1;
if(AHeight<1)then AHeight := 1;
Result.Write(C_BLANK_ALPHA_BMP32_PREFIX, SizeOf(C_BLANK_ALPHA_BMP32_PREFIX));
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;
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;
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));
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);
finally
FreeAndNil(MS);
end;
end;