Question

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;
Was it helpful?

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.

OTHER TIPS

If I understand correctly:
You are getting a completely transparent bitmap every time.

Suggestions
You could rewrite the DrawContours function to set an alpha value to each area of the canvas it draws on. I would suggest this route but I think you mentioned somewhere that you didn't want to rewrite these functions.

The only other options I can think of is to (since your beginning bmp is all black and all transparent) after you pass it to the DrawContrours function inspect each pixel and if it is no longer black then change the alpha to 255. But this has the same result as simply using the transparent color and property which you have already said you did not want to use.

Thoughts:
I do not know of a way (and am skeptical that there even is one) to change the bitmap such that whatever drawing routine you pass it to would treat it differently than the routine was designed.

Final Answer:
Rewrite the drawing routine DrawContrours or use Transparent color

After fiddling with several options, I got the following to work:

  • Set the pixel format to 32-bits;
  • Set the AlphaFormat to afIgnored;
  • Set Transparent to True; (this one is important)
  • Clear the alpha values;
  • Call the drawing routine;
  • Then assign the bitmap to PNG, and save that.

In code:

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;
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top