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;
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.