如何在不需要先绘制的情况下获得位图透明度?

时间:2011-10-11 20:59:48

标签: delphi bitmap transparency

默认情况下,新创建的位图似乎具有(白色)背景。至少,对Pixels属性的查询确认。但是,当Transparent设置为true时,为什么背景颜色不用作透明颜色?

考虑这个简单的测试代码:

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;

出于某种原因,整个位图表面必须先涂上才能显得透明,这听起来很奇怪。

尝试以下变体来消除对FillRect的调用,所有调用都具有相同的结果(没有透明度):

  • 仅设置Brush.Color
  • Brush.Handle := CreateSolidBrush(clWhite)
  • PixelFormat := pf32Bit
  • IgnorePalette := True
  • TransparantColor := clWhite
  • TransparantMode := tmFixed
  • Canvas.Pixels[0, 99] := clWhite只使像素透明,
  • Modified := True

因此,希望只绘制新创建的位图的一部分,并使剩余的表面透明。

使用:Delphi 7,Win 7/64。

3 个答案:

答案 0 :(得分:9)

在设置位图的尺寸之前,只需设置 TransparentColor和 Canvas.Brush.Color。

答案 1 :(得分:3)

我真的需要能够以32位RGBA格式创建任意大小的完全透明(以及其他空/空)TBitmap。多次。 Lazarus能够将这样的位图加载到TBitmap中,并且在加载之后,您可以使用scanline和不使用RGBA格式来操作它。但是,当您自己创建TBitmap时,它无法正常工作。像素格式似乎完全被忽略了。所以我所做的就是开箱即用,而且很简单,它几乎是AMUZING(!)。但它很实用,效果非常好,完全独立于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文件。 然后我比较了两个文件: compating two bmp files in vbindiff on Ubuntu 所以我发现唯一的区别是添加的像素(每个像素都是4个字节全部为RGBA),标头中的其他字节很少。由于我分享的链接格式文档,我发现这些是:FileSize(以字节为单位),BitmapWidth(以像素为单位),BitmapHeight(以像素为单位)和{{ 1}}(以字节为单位)。最后一个是BitmapDataSize,因为RGBA中的每个像素都是4个字节。所以现在,我可以生成整个字节序列,如alpha1p.bmp文件中所示,减去末尾的4个字节(BitmapWidth*BitmapHeight*4的第1个字节),然后为RGBA数据添加4个字节(全零)我要生成的BMP的每个像素,然后回到初始序列并更新变量部分:FileSize,width,height和BMP数据大小。它完美无瑕!我只需要为BigEndian添加测试,并在写入之前交换单词和双字。这将成为BigEndian工作的ARM平台的问题。

代码

BitmapData

注意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; 常量基本上是来自我的样本alpha1p.bmp文件的字节序列的副本,减去最后4个字节,即RGBA像素。 :d

另外,我正在使用C_BLANK_ALPHA_BMP32_PREFIX函数,如下所示:

IsBigEndian

这是从Lazarus Wiki复制的:http://wiki.freepascal.org/Writing_portable_code_regarding_the_processor_architecture如果你不处理BigEndian平台,你可以跳过这部分,或者你可以使用编译器IFDEF指令。问题是,如果你使用 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; 那么它就是编译器的情况,而函数实际上是测试系统的。这在链接的维基中解释。

示例用法

{$IFDEF ENDIAN_BIG}

答案 2 :(得分:1)

这将绘制一个红色方块,其余部分是透明的。

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;