3

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.

bLight
  • 803
  • 7
  • 23

1 Answers1

3

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

Tom Brunberg
  • 20,312
  • 8
  • 37
  • 54
  • Thanks Tom, this works and it's a lot faster than passing through a memory stream but still has a lot of overhead. Before I mark this as the answer, can you explain why the RGB values need to be modified (this is what calling PremultiplyAlpha does) when only the alpha channel should control the per-pixel's opacity? – bLight Feb 25 '18 at 10:41
  • 1
    The additional question you present is too broad to be answered in depth here in comments, and I already answered your actual question. The difference is that the formula in the framework combining (blending) the two images, expect precalculated pixels. Precalculated images are slightly faster to show on another image. – Tom Brunberg Feb 25 '18 at 13:34