在半透明的TBitmap上绘制不透明的线条

时间:2011-04-27 11:35:45

标签: delphi bitmap drawing alpha-transparency translucency

我有一个空的位图,我有一个接收TCanvas的绘图程序。绘图例程是一个更大的库的一部分,因此实际上是我无法控制的。

简单地说:如果绘图程序影响它们,我希望像素是不透明的;未触摸的每个像素应保持透明。由于我无法控制绘图程序将使用的颜色,我宁愿不必使用TransparentColor属性。

有没有办法实现这个目标?我可以使用某种设置来指定画布应该影响它绘制的像素的alpha层吗?


更新:我正在使用Delphi 2010,并按照我尝试过的代码进行操作:

Bmp := TBitmap.Create;
try
  Bmp.PixelFormat := pf32bit;
  Bmp.Transparent := False;
  Bmp.SetSize(Width, Height);

  // Ensure all pixels are black with opacity 0 (= fully transparent)
  ScanlineWidth := Width * SizeOf(TRGBQuad);
  for y := 0 to Bmp.Height - 1 do begin
    ZeroMemory(Bmp.ScanLine[y], ScanlineWidth);
  end;

  // call drawing routines here
  DrawContours(Bmp.Canvas, Width, Height);

  {$IFDEF DEBUG}
  Bmp.SaveToFile(OwnPath + 'Contours-' + IntToStr(GetTickCount) + '.bmp');
  {$ENDIF}

  Result := Bmp.ReleaseHandle;
finally
  Bmp.Free;
end;

3 个答案:

答案 0 :(得分:1)

如果我理解正确
你每次都得到一个完全透明的位图。

<强>建议
您可以重写DrawContours函数,为其绘制的画布的每个区域设置一个alpha值。我会建议这条路线,但我想你曾提到某个地方,你不想重写这些功能。

我能想到的唯一其他选项是(因为你的bmp是全黑的并且都是透明的)在你将它传递给DrawContrours函数后检查每个像素,如果它不再是黑色,那么改变alpha到255.但这与使用您已经说过不想使用的透明颜色和属性的结果相同。

<强>思想
我不知道一种方式(我怀疑甚至有一种方法)改变位图,这样你传递给它的任何绘图程序都会以不同于例程设计的方式对待它。

最终答案
重写绘图例程DrawContrours或使用Transparent color

答案 1 :(得分:1)

如果绘图并不昂贵,可以选择以下方法。绘制三次,一次放到透明位图上,一次放到带有白色背景的1位位图,一次放到带有黑色背景的1位位图,以获得需要对alpha通道进行的更改(因为Tim是正确的 - 位图最初是完全透明的;我们需要找到不透明的像素。

编辑:第一个版本仅使用一个具有白色背景的1位位图。遗憾的是,这并未发现较轻的画作。让我们更进一步,使其成为两个1位位图。虽然有越来越多的位图,但同样不那么优雅。

var
  Bmp: TBitmap;
  ScanlineWidth: Integer;
  x: Integer;
  y: Integer;
  // this one will track dark paintings
  BmpBitMaskWhite:TBitmap;
  // this one will track light paintings
  BmpBitMaskBlack:TBitmap;
  Row32bit, Row1bitWhite, Row1bitBlack:PByteArray;
  ByteAccess:Byte;

  // Init bitmaps needed for tracking pixels we need to change the alpha channel for
  procedure InitAlphaMaskBitmaps;
  begin
    BmpBitMaskWhite.PixelFormat:=pf1bit;          // <= one bit is enough
    BmpBitMaskWhite.Transparent:=False;
    BmpBitMaskWhite.SetSize(Width, Height);
    BmpBitMaskWhite.Canvas.Brush.Color:=clWhite;  // <= fill white; changes can then be seen as black pixels
    BmpBitMaskWhite.Canvas.FillRect(Rect(0,0,Width, Height));

    BmpBitMaskBlack.PixelFormat:=pf1bit;          // <= one bit is enough
    BmpBitMaskBlack.Transparent:=False;
    BmpBitMaskBlack.SetSize(Width, Height);
    BmpBitMaskBlack.Canvas.Brush.Color:=clBlack;  // <= fill black; changes can then be seen as white pixels
    BmpBitMaskBlack.Canvas.FillRect(Rect(0,0,Width, Height));
  end;

begin
  Bmp := TBitmap.Create;
  BmpBitMaskWhite:=TBitmap.Create;
  BmpBitMaskBlack:=TBitmap.Create;
  try
    Bmp.PixelFormat := pf32bit;
    Bmp.Transparent := False;
    Bmp.SetSize(Width, Height);

    InitAlphaMaskBitmaps;

    // ensure all pixels are black with opacity 0 (= fully transparent)
    ScanlineWidth := Width * SizeOf(TRGBQuad);
    for y := 0 to Bmp.Height - 1 do
    begin
      ZeroMemory(Bmp.ScanLine[y], ScanlineWidth);
    end;

    // call drawing routines here
    DrawContours(Bmp.Canvas, Width, Height);
    // call again to get areas where we need to un-transparent the Bmp (this is for dark paintings)
    DrawContours(BmpBitMaskWhite.Canvas, Width, Height);
    // call again to get areas where we need to un-transparent the Bmp  (this is for light paintings)
    DrawContours(BmpBitMaskBlack.Canvas, Width, Height);

    // modify alpha channel of Bmp by checking changed pixels of BmpBitMaskWhite and BmpBitMaskBlack
    // iterate all lines
    for y := 0 to Bmp.Height - 1 do
    begin
      // iterate all pixels
      for x := 0 to Bmp.Width - 1 do
      begin
        Row32bit:=PByteArray(Bmp.ScanLine[y]);
        Row1bitWhite:=PByteArray(BmpBitMaskWhite.ScanLine[y]);
        Row1bitBlack:=PByteArray(BmpBitMaskBlack.ScanLine[y]);

        // Now we need to find the changed bits in BmpBitMaskWhite and BmpBitMaskBlack to modify the corresponding
        // alpha-byte in Bmp. Black areas (Bit=0) in BmpBitMaskWhite are the ones that
        // have been drawn to, as well as white areas (Bit=1) in BmpBitMaskBlack.
        // Not pretty, but works.
        ByteAccess:=1 shl (7-x mod 8);
        if ((Row1bitWhite[x div 8] and ByteAccess)=0) or
           ((Row1bitBlack[x div 8] and ByteAccess)<>0) then
        begin
          Row32bit[x*4+3]:=255;
        end;
      end;
    end;

    {$IFDEF DEBUG}
    Bmp.SaveToFile('C:\Temp\Contours-' + IntToStr(GetTickCount) + '.bmp');
    BmpBitMaskWhite.SaveToFile('C:\Temp\Contours-' + IntToStr(GetTickCount) + '_BitMaskWhite.bmp');
    BmpBitMaskBlack.SaveToFile('C:\Temp\Contours-' + IntToStr(GetTickCount) + '_BitMaskBlack.bmp');
    {$ENDIF}

//    Result := Bmp.ReleaseHandle;
  finally
    Bmp.Free;
    BmpBitMaskWhite.Free;
    BmpBitMaskBlack.Free;
  end;

顺便说一下:唯一能够正确显示这些位图透明度的程序是PixelFormer。其他(Gimp,IrfanView,Windows Fax thingy,FastStone图像查看器,MS Paint)都将透明区域着色为黑色。

答案 2 :(得分:0)

在摆弄了几个选项后,我得到了以下工作:

  • 将像素格式设置为32位;
  • AlphaFormat设置为afIgnored;
  • Transparent设为True; (这个很重要)
  • 清除Alpha值;
  • 调用绘图程序;
  • 然后将位图分配给PNG,并保存。

在代码中:

Bmp := TBitmap.Create;
try
  Bmp.PixelFormat := pf32bit;
  Bmp.AlphaFormat := afIgnored;
  Bmp.Transparent := True;

  Bmp.SetSize(Width, Height);

  // Make bitmap fully transparent
  ScanlineWidth := Width * SizeOf(TRGBQuad);
  for y := 0 to Bmp.Height - 1 do begin
    ZeroMemory(Bmp.ScanLine[y], ScanlineWidth);
  end;

  DrawContours(Bmp.Canvas);

  PNG := TPNGImage.Create;
  try
    PNG.Assign(Bmp);
    PNG.SaveToFile('contours.png'); // => this one has the wanted transparency!
  finally
    PNG.Free;
  end;
finally
  Bmp.Free;
end;