I am trying to create a TImage with alpha transparency using code. In this example, an anti-aliased circle.
I create an 8bit opacity map for the circle and then apply it to the TImage's TBitmap using this piece of code:
type
TOpacityMap = Array[0..4095] of PByteArray;
procedure DrawAntiAliasedCircle(srcBitmap: TBitmap; FillColor : TAlphaColor; CenterX, CenterY, Radius, LineWidth, Feather: single);
var
FillR : Integer;
FillG : Integer;
FillB : Integer;
FillRGB : Integer;
OpacityMap : TOpacityMap;
AlphaScanLine : Array[0..4095] of TAlphaColor;
bitmapData : FMX.Graphics.TBitmapData;
tmpScanLine : Pointer;
X,Y : Integer;
tmpMS : TMemoryStream;
begin
{Initialization}
FillR := TAlphaColorRec(FillColor).R;
FillG := TAlphaColorRec(FillColor).G;
FillB := TAlphaColorRec(FillColor).B;
CreateAntiAliasedCircleOpacityMap(OpacityMap, srcBitmap.Width, srcBitmap.Height, CenterX, CenterY, Radius, LineWidth, Feather);
{create image based on opacity map and free memory}
If srcBitmap.Map(TMapAccess.Write, bitmapData) then
try
FillRGB := (FillR shl 16)+(FillG shl 8)+FillB;
for Y := 0 to srcBitmap.Height-1 do
begin
for X := 0 to srcBitmap.Width-1 do
AlphaScanLine[X] := (OpacityMap[Y][X] shl 24)+FillRGB; // Opacity
tmpScanLine := bitmapData.GetScanline(Y);
AlphaColorToScanLine(@AlphaScanLine,tmpScanLine,srcBitmap.Width,srcBitmap.PixelFormat);
FreeMem(OpacityMap[Y]);
end;
finally
srcBitmap.Unmap(bitmapData);
end;
// Work-around fix
{tmpMS := TMemoryStream.Create;
srcBitmap.SaveToStream(tmpMS);
srcBitmap.LoadFromStream(tmpMS);
tmpMS.Free;}
end;
The result is the image on the left. The actual TBitmap seems to be good, calling "srcBitmap.SaveToFile('circle.png')" results in a PNG file with a valid alpha channel.
I can work-around this issue by simply saving/loading the bitmap using a TMemoryStream.
How do I get the desired image on the right without the performance penalty of passing the image through a TMemoryStream?
These screenshots are from the minimal example project demonstrating this issue :
https://github.com/bLightZP/AntiAliasedCircle
edit #1 :
The github code linked above has been updated with an optimized (about 25% faster) version of Tom Brunberg's suggested fix.
You can achieve your goal with applying alpha channel premultiplying to your overlay image. For example in the loop where you add the alpha channel:
for X := 0 to srcBitmap.Width-1 do
begin
AlphaScanLine[X] := (OpacityMap[Y][X] shl 24)+FillRGB;
AlphaScanLine[X] := PremultiplyAlpha(AlphaScanLine[X]); // Add this for premultiplied Alpha
end;
The result looks like this (ofcourse without the stream work-around)