3
votes

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?

Bad_AlphaWorkaround

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.

1

1 Answers

3
votes

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)

enter image description here