【问题标题】:How to get bitmap transparancy without the need to paint first?如何在不需要先绘制的情况下获得位图透明度?
【发布时间】:2011-12-05 15:43:46
【问题描述】:

默认情况下,新创建的位图似乎具有(白色)背景。至少,对 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。

【问题讨论】:

  • 如果您关注this 示例会发生什么?
  • 你只想画什么吗?是否要绘制一个完全透明的位图?
  • @Shamballa 不,我只想画一个(小)部分。
  • @LURD 这不是空位图。
  • 注意:您的第 10 行绘制了一个带有 undefined 光栅内容的位图(其 合理的默认值为 RGB($FF, $FF, $FF ))。先初始化!

标签: delphi bitmap transparency


【解决方案1】:

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

【讨论】:

  • 是的!并且不需要设置透明色。在给出尺寸之前设置 Canvas.Brush.Color 可以解决问题。谢谢。
  • 有人知道为什么会这样吗?你甚至需要设置画笔颜色吗?只强制刷柄存在就够了吗?
  • @David 好点。 Canvas.Brush.Handle := 0 也有效,很奇怪。
  • @NGLN 我怀疑你甚至需要分配它。只需写 Canvas.Brush.Handle; 即可创建。
【解决方案2】:

我真的需要能够以 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;

【讨论】:

  • 在 Quora 上我建议在 SDL 中采用这种方法:quora.com/…
  • 我认为问题不在于 32 位 alpha 透明位图。无论如何,好主意。但这不是按预期工作吗?:pastebin.com/EXDRu3zG
  • @kobik :您的方法在某些情况下对我不起作用。我认为它不起作用的地方是在 Linux 上使用 gtk widgetset。
  • @NGLN - 您可以根据需要报告它,但是: - 我在“PixelFormat := pf32Bit”部分的问题中清楚地看到了 32 位位图参考。 - 使用我的方法,您可以实现提问者所描述的内容,而无需先绘制(填充矩形)。我只是展示了提问者提到他已经厌倦的另一种方法(包括 32 位位图)*顺便说一句 - 我刚刚注意到你是提问者。那么,您将有权将我的答案标记为离题。但我再也不会尝试为您提供任何建议了。
【解决方案3】:

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

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
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-14
  • 2018-04-02
  • 2011-08-11
  • 1970-01-01
  • 1970-01-01
  • 2014-02-06
相关资源
最近更新 更多