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

12

新创建的位图似乎默认具有白色背景。至少,通过查询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。

1
如果您按照此示例进行操作,会发生什么? - LU RD
你只是想画一个空白的图像吗?还是想要绘制一个完全透明的位图? - Shambhala
@Shamballa 不,我只想画一个(小)部分。 - NGLN
@LURD 这不是一个空位图。 - NGLN
注意:您的第10行绘制了一个具有未定义光栅内容的位图(其合理默认值为RGB($FF,$FF,$FF))。请先进行初始化! - Premature Optimization
3个回答

12

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


是的!而且不需要设置TransparentColor。在给出尺寸之前设置Canvas.Brush.Color就可以了。谢谢。 - NGLN
5
为什么会这样?你是否需要设置刷子的颜色?只是强制刷子句柄存在就可以了吗? - David Heffernan
1
@David 好观点。奇怪的是,Canvas.Brush.Handle := 0 也可以起作用。 - NGLN
5
我怀疑你甚至不需要分配它。只需编写 Canvas.Brush.Handle; 即可创建它。 - David Heffernan
我只想补充一下,这个答案对我在 Delphi 11.3 中有帮助,所以无论出于什么原因,它仍然适用。 - MarkF

3
我真的需要能够创建任意大小的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; //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;

我在Quora上建议采用SDL的这种方法:https://www.quora.com/A-simple-way-to-create-a-transparent-surface-with-SDL-Pascal/answer/Krzysztof-Kamil-Jacewicz - Kris Jace
我认为这个问题不是关于32位alpha透明位图的。无论如何,这是一个好主意。但是这是否按预期工作?:https://pastebin.com/EXDRu3zG - kobik
@kobik:你的方法在某些情况下对我不起作用。我认为它无法在使用gtk widgetset的Linux上工作。 - Kris Jace
@NGLN - 你可以按照你的看法进行报告,但是:
  • 我在“PixelFormat:= pf32Bit”部分中清楚地看到了32位位图引用。
  • 使用我的方法,您可以实现提问者所描述的内容,而无需首先绘制(fillrect)。我只是展示了一种与提问者提到的已经尝试过的方法不同的替代方法(包括32位位图)
*顺便说一句-我刚刚注意到你就是提问者。那么,您有权将我的答案标记为离题。但是,我将永远不会再向您提供任何建议。
- Kris Jace

2

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

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;

希望在没有第一个FillRect的情况下获得此结果,正如问题中明确说明的那样。-1 - NGLN

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接