Search code examples
delphibitmapdrawingalpha-transparencytranslucency

Draw opaque lines onto translucent TBitmap


I’ve got an empty bitmap, and I’ve got a drawing routine that receives a TCanvas. The drawing routine is part of a larger library, and so effectively out of my control.

Simply put: I want the pixels to be opaque if the drawing routines affect them; each pixel that isn’t touched should be left transparent. Since I have no control over the colours that will be used by the drawing routines, I would prefer not having to use the TransparentColor property.

Is there a way of achieving this? Some kind of setting I can use to specify that the canvas should affect the alpha layer of the pixels it’s drawing onto?


Update: I’m using Delphi 2010, and here follows the code I’ve tried:

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;

Solution

  • The following approach could be an option if drawing is not expensive. Draw three times, once onto your transparent bitmap, once to a 1-bit bitmap with white background and once to a 1-bit bitmap with black background to obtain the changes you need to make to the alpha channel (because Tim is right - the bitmap initially is completely transparent; we need to find the pixels to make opaque).

    Edit: The first version used only one 1-bit bitmap which had a white background. This unfortunately didn't detect lighter paintings. So lets go one step further and make it two 1-bit bitmaps. Although with more and more bitmaps it is getting equally less elegant.

    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;
    

    By the way: the only program which showed me the transparency in these bitmaps properly was PixelFormer. The others (Gimp, IrfanView, Windows Fax thingy, FastStone Image Viewer, MS Paint) all colored the transparent area black.