share
Stack Overflowon Firemonkey, how to draw masked bitmap on canvas?
[+5] [2] user8614758
[2017-09-15 14:25:33]
[ delphi firemonkey ]
[ https://stackoverflow.com/questions/46241917/on-firemonkey-how-to-draw-masked-bitmap-on-canvas ]

I have a bitmap and a mask (a bitmap also). I would like to draw the bitmap on the mask (like on the picture below)

drawing bitmap on a mask

How to do this on Delphi with Firemonkey ?

[+9] [2017-09-15 15:20:33] Tom Brunberg [ACCEPTED]

Use TBitmap.CreateFromBitmapAndMask()

constructor CreateFromBitmapAndMask(const Bitmap, Mask: TBitmap);

The documentation says:

The created TBitmap has the value of the Alpha channel of each color pixel equal with the value of the Red channel in the Mask.

And further:

Tip: For a better result, use a grayscale image for Mask. It has an equal amount of green, red, and blue.

Tip: The mask and the base bitmap must have the same dimensions. Otherwise the new TBitmap will have the dimensions equal to 0.

In a simple test like:

procedure TForm19.Button1Click(Sender: TObject);
var
  bmp, msk: TBitmap;
begin
  bmp := nil;
  msk := nil;
  try
    bmp := TBitmap.Create;
    msk := TBitmap.Create;
    bmp.LoadFromFile('C:\tmp\Imgs\4.bmp');
    msk.LoadFromFile('C:\tmp\Imgs\TestImage04.bmp');
    Image1.Bitmap := bmp;
    Image2.Bitmap := msk;
    Image3.Bitmap.CreateFromBitmapAndMask(bmp, msk);
  finally
    bmp.Free;
    msk.Free;
  end;
end;

the result looks like this:

enter image description here

Edit

In order for the result of CreateFromBitmapAndMask(bmp, msk); to be drawn transparently on the form, it must be premultiplied before assigned to Image3. We need the following procedure,

procedure PremultiplyBitmapAlpha(bmp:TBitmap);
var
  X, Y: Integer;
  M: TBitmapData;
  C: PAlphaColorRec;
begin
  if bmp.Map(TMapAccess.ReadWrite, M) then
  try
    for Y := 0 to bmp.Height - 1 do
      for X := 0 to bmp.Width - 1 do
      begin
        C := @PAlphaColorArray(M.Data)[Y * (M.Pitch div 4) + X];
        C^.Color := PremultiplyAlpha(C^.Color);
      end;
  finally
    bmp.Unmap(M);
  end;
end;

and another temporary bitmap res for the purpose. The test code looks now as follows:

procedure TForm14.Button1Click(Sender: TObject);
var
  bmp, msk, res: TBitmap;
begin
  bmp := nil;
  msk := nil;
  res := nil;
  try
    bmp := TBitmap.Create;
    msk := TBitmap.Create;
    bmp.LoadFromFile('C:\tmp\Imgs\4.bmp');
    msk.LoadFromFile('C:\tmp\Imgs\TestImage04.bmp');

    Image1.Bitmap := bmp;
    Image2.Bitmap := msk;

    res := TBitmap.Create;
    res.CreateFromBitmapAndMask(bmp, msk);

    PremultiplyBitmapAlpha(res);
    Image3.Bitmap := res;
  finally
    bmp.Free;
    msk.Free;
    res.Free;
  end;
end;

And the image (with a modified bg color for better demonstration):

enter image description here


Just one remark, my mask is transparent + dark. when i do CreateFromBitmapAndMask the resulting image is fully transparent :( what did i miss ? - user8614758
ok, i found, it's must be white for the area where we want the picture and dark where we want transparency ... - user8614758
i don't know if you have still the demo of the app, but did you notice that in fact black are not replaced by transparent but by white? I don't find a way to have transparent in the result image .... it's output only white - user8614758
@john , I'll take a second look tomorrow, it's now about midnight where I live, OK? - Tom Brunberg
:) was about midnight for me too :) maybe we leave close each other ;) of course it's ok, i m already grateful for your help ! - user8614758
@john If the mask has only black and white colors, the result of CreateFromBitmapAndMask() has an alpha channel according to black and white pixels in the mask. Black pixels in mask results in alpha=0 and white pixels in mask results in alpha=255. I checked this with in-memory bitmaps. When you paint or draw the result on the display, the pixels from the result image are merged with the background. The white color may come from e.g. the initialization of a TImage? - Tom Brunberg
yes about the alfa, black pixels in masks results with alpha = 0 BUT the problem is that for example red color with alpha = 0 will be draw as white and not transparent :( only black with alpha = 0 (ie: $00000000) is paint in transparent :( - user8614758
...the problem is that for example red color with alpha = 0 will be draw as white and not transparent Are you talking about original image or mask or result and what method of drawing? - Tom Brunberg
i speak about result, i use canvas.drawbitmap(resultbnp) - user8614758
@alitrun I will take a look later today, a busy time right now. - Tom Brunberg
1
[+2] [2018-09-28 12:57:35] alitrun

enter image description here

Result image - star with transparent background. Use white color in your mask to show the visible part of image.
Checked in Delphi Berlin and Windows.

procedure TForm1.Button1Click(Sender: TObject);
var
  ImageRes: TResourceStream;
  Result: TBitmap;
  tmpMS : TMemoryStream;
begin
  ImageRes := TResourceStream.Create(HInstance, 'IMAGE', RT_RCDATA);
  try
    Image1.Bitmap.CreateFromStream(ImageRes);
    Image2.Bitmap.LoadFromFile('c:\temp\MaskedBitmap\Images\Mask.png');

    Result := TBitmap.Create;
    Result.CreateFromBitmapAndMask(Image1.Bitmap, Image2.Bitmap);

    // applying alpha channel to Bitmap - workaround. If you can improve write here how
    tmpMS := TMemoryStream.Create;
    Result.SaveToStream(tmpMS);
    Result.LoadFromStream(tmpMS);
    tmpMS.Free;

    Image3.Bitmap.Assign(Result);
  finally
    ImageRes.Free;
    Result.Free;
  end;
end;

Sample project [1]

[1] http://fire-monkey.ru/applications/core/interface/file/attachment.php?id=4161

I found that I had made some tests and modifications a year ago, but somehow got interrupted and never finalized it. Therefore I now edited my answer with what I intended to publish back then, which is based on a premultiplication of the pixels and alpha. Interesting that saving to a stream would have the same effect, how did you think of that? - Tom Brunberg
2