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:
开发者_如何学JAVABmp := 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.
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
toafIgnored
; - 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;
精彩评论