1
votes

I'm struggling to be able to draw a TBitmap with transparency onto a TDirect2DCanvas without losing the transparency.

Having created a TBitmap which acts as the back-buffer for my drawing operation as follows:

bmp := TBitmap.Create;
bmp.Canvas.Brush.Handle := 0;
bmp.SetSize(100, 100);
bmp.Canvas.Brush.Color := clRed;
bmp.Transparent := true;
bmp.TransparentColor := clRed;
bmp.Canvas.Rectangle(bmp.Canvas.ClipRect);
bmp.Canvas.Pen.Color := clGreen;
bmp.Canvas.Ellipse(bmp.Canvas.ClipRect);

I then need to draw it onto my TDirect2DCanvas, however the following draws the TBitmap but removes all transparency - the background colour is drawn as red whereas if I just draw onto the TForm.Canvas then the background is transparent.

// Drawing onto the TDirect2DCanvas results in a red background
AEventArgs.Canvas.Draw(0, 0, bmp);

// Drawing onto the TForm.Canvas gives the correct result
Self.Canvas.Draw(0, 0, bmp);

My understanding now leads me on to ID2D1Bitmap and IWICBitmap interfaces, so, I can attempt to create an ID2D1Bitmap from the TBitmap using the following code (and assuming that the pixel format is copied across):

var
    bmp : TBitmap;
    temp : ID2D1Bitmap;
begin
    // Code to initialize the TBitmap goes here (from above)

    // Create an ID2D1Bitmap from a TBitmap
    temp := AEventArgs.Canvas.CreateBitmap(bmp);

    // Draw the ID2D1Bitmap onto the TDirect2DCanvas
    AEventArgs.Canvas.RenderTarget.DrawBitmap(temp);

Now that I have an ID2D1Bitmap, the result is still the same - a red background with no transparency. I guess its entirely feasible that the Direct2D side of things uses a different method for transparency but looking at the propertys of the ID2D1Bitmap provides no clues.

My next guess is to go down the IWICBitmap interface.

Ultimately, my question is: is there a more straightforward or obvious thing that I've missed from the above which would allow the transparent TBitmap to be drawn onto the TDirect2DCanvas surface? Or is all this pain necessary in order to maintain the transparency?

Update

Ok, so after doing a bit more digging around, I can now convert the TBitmap to an IWICBitmap and then onto an ID2D1Bitmap however the issue still remains - transparency which is present in the TBitmap is not copied through when rendering to the TDirect2DCanvas.

// Create the IWICBitmap from the TBitmap
GetWICFactory.CreateBitmapFromHBITMAP(bmp.Handle, bmp.Palette, WICBitmapUsePremultipliedAlpha, wic);
wic.GetPixelFormat(pif);

// The PixelFormat is correct as `GUID_WICPixelFormat32bppPBGRA` which is
// B8G8R8A8_UNORM and PREMULTIPLIED

// Create the IWICFormatConverter
GetWICFactory.CreateFormatConverter(fc);
fc.Initialize(wic, GUID_WICPixelFormat32bppPBGRA, WICBitmapDitherTypeNone, nil, 0.0, WICBitmapPaletteTypeCustom);

// Now, create the ID2D1Bitmap
AEventArgs.Canvas.RenderTarget.CreateBitmapFromWicBitmap(fc, nil, temp);
temp.GetPixelFormat(fmt);

// Here, PixelFormat is correct matching the PixelFormat from the IWICBitmap

// Draw the bitmap to the Canvas
AEventArgs.Canvas.RenderTarget.DrawBitmap(temp);

And the result is still a non-transparent bitmap.

So the final thing I've looked into is the PixelFormat of the ID2D1RenderTarget which is the underlying render target of the TDirect2DCanvas.

// Create the canvas
fCanvas := TDirect2DCanvas.Create(Self.Handle);
fCanvas.RenderTarget.GetPixelFormat(pf); 

// This gives me a PixelFormat of
// B8G8R8A8_UNORM but D2D1_ALPHA_MODE_IGNORE 

So I'm guessing that the real issue is to do with the fact that the ID2D1RenderTarget PixelFormat is ignoring the alpha.

2
> ".. transparency which is present in the TBitmap .." > That's what you've got wrong all along. That bitmap has no transparency. It's the VCL that draws it transparently with the information you supply through properties of the TBitmap (see TransparentStretchBlt in 'graphics').Sertac Akyuz

2 Answers

2
votes

The real issue is not in the methods you are calling but the shear fact that in VCL application by default TBitmap uses 24bit RGB pixel format which does not have an alpha channel needed for alpha transparency.

If you want to use alpha transparency with TBitmap you first need to set its pixel format to pf32bit.

https://stackoverflow.com/a/4680460/3636228

Also don't forget to set Alpha channel to 0 for every pixel that you want it to be transparent.

You see Direct2D does not support same transparency as it is used in VCL where you can simply set the transparent color and every pixel of that specific color is simply ignored.

1
votes

If you take a look at the source of TDirect2DCanvas.CreateBitmap, you'll see:

  ...
  if (Bitmap.PixelFormat <> pf32bit) or (Bitmap.AlphaFormat = afIgnored) then
    BitmapProperties.pixelFormat.alphaMode := D2D1_ALPHA_MODE_IGNORE
  else
    BitmapProperties.pixelFormat.alphaMode := D2D1_ALPHA_MODE_PREMULTIPLIED;

So to make it work, you have to match the conditions:

    bmp.PixelFormat := pf32bit;
    bmp.AlphaFormat := TAlphaFormat.afPremultiplied;

Then you have to prepare the alpha channel of every pixel. In your case, red is transparent, so you should do something like this:

for y := 0 to bmp.Height - 1 do begin
  Line := bmp.Scanline[y];
  for x := 0 to bmp.Width - 1 do begin
    if (line[x].r =255) and (line[x].g = 0) and (line[x].b = 0) then
    Line[x].A := 0
  else
    Line[x].A := 255;
end;

Then it comes:

temp := AEventArgs.Canvas.CreateBitmap(BMP);
AEventArgs.Canvas.RenderTarget.DrawBitmap(temp);

Took me entire weekend to figure it out myself, hope it helps you or someone else.