如何获得位图透明度,而不需要先油漆



新创建的位图似乎默认具有(白色)背景。至少,对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只使thát像素透明,
  • Modified := True .

所以,我们的愿望是只绘制新创建的位图的一部分,并使剩余的表面透明。

使用:Delphi 7, Win 7/64

在设置位图尺寸之前先设置

TransparentColor和 Canvas.Brush.Color

我真的需要能够以32位RGBA格式创建一个完全透明(否则为空/空白)的任意大小的TBitmap。很多次了。Lazarus能够将这样的位图加载到TBitmap中,并且在加载之后,您可以使用扫描线和不使用RGBA格式的东西来操作它。但当你自己创建TBitmap时,它就不起作用了。像素格式似乎完全被忽略了。所以我所做的是如此的打破常规,简单,几乎是惊人的。但它是实用的,工作超级好,是完全独立于LCL和任何第三方库。甚至不依赖于图形单元,因为它生成实际的32位RGBA BMP文件(我生成它到TMemoryStream,你可以生成不同的)。一旦你有了它,你就可以在代码的其他地方使用TBitmap加载它。从源或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;

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

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;

相关内容

最新更新