Delphi, Direct2D, TBitmap и прозрачность

Я изо всех сил пытаюсь нарисовать TBitmap с прозрачностью на TDirect2DCanvas, не теряя прозрачности.

Создав TBitmap, который действует как задний буфер для моей операции рисования, следующим образом:

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);

Затем мне нужно нарисовать его на моем TDirect2DCanvas, однако следующее рисует TBitmap, но удаляет всю прозрачность - цвет фона рисуется красным, тогда как если я просто рисую на TForm.Canvas, тогда фон становится прозрачным.

// 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);

Мое понимание теперь приводит меня к интерфейсам ID2D1Bitmap и IWICBitmap, поэтому я могу попытаться создать ID2D1Bitmap из TBitmap, используя следующий код (и предполагая, что формат пикселей копируется):

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);

Теперь, когда у меня есть ID2D1Bitmap, результат все тот же — красный фон без прозрачности. Я предполагаю, что вполне возможно, что сторона вещей Direct2D использует другой метод для прозрачности, но просмотр свойств ID2D1Bitmap не дает никаких подсказок.

Моя следующая догадка — спуститься по интерфейсу IWICBitmap.

В конечном счете, мой вопрос: есть ли более простая или очевидная вещь, которую я упустил из вышеизложенного, которая позволила бы нарисовать прозрачную TBitmap на поверхности TDirect2DCanvas? Или вся эта боль необходима для того, чтобы сохранить прозрачность?

Обновить

Итак, немного покопавшись, я теперь могу преобразовать TBitmap в IWICBitmap, а затем в ID2D1Bitmap, однако проблема все еще остается - прозрачность, присутствующая в TBitmap, не копируется при рендеринге в 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);

И в результате получается непрозрачное растровое изображение.

Итак, последнее, что я изучил, — это PixelFormat ID2D1RenderTarget, который является базовой целью рендеринга 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 

Итак, я предполагаю, что реальная проблема связана с тем фактом, что ID2D1RenderTarget PixelFormat игнорирует альфу.


person weblar83    schedule 19.07.2016    source источник
comment
.. прозрачность, которая присутствует в TBitmap.. › Это то, что вы все время ошибались. Это растровое изображение не имеет прозрачности. Это VCL рисует его прозрачно с помощью информации, которую вы предоставляете через свойства TBitmap (см. TransparentStretchBlt в «графике»).   -  person Sertac Akyuz    schedule 19.07.2016


Ответы (2)


Настоящая проблема заключается не в методах, которые вы вызываете, а в том факте, что в приложении VCL по умолчанию TBitmap использует 24-битный формат пикселей RGB, который не имеет альфа-канала, необходимого для альфа-прозрачности.

Если вы хотите использовать альфа-прозрачность с TBitmap, вам сначала нужно установить его формат пикселей на pf32bit.

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

Также не забудьте установить альфа-канал на 0 для каждого пикселя, который вы хотите сделать прозрачным.

Вы видите, что Direct2D не поддерживает ту же прозрачность, что и в VCL, где вы можете просто установить прозрачный цвет, и каждый пиксель этого конкретного цвета просто игнорируется.

person SilverWarior    schedule 19.07.2016
comment
@SilverWarrior спасибо за ваши комментарии. Любые указатели относительно того, как я могу переключаться между VCL TBitmap и Direct2D, сохраняя при этом прозрачность? Я рассмотрел использование совместимой с GDI цели рендеринга, но это все еще не работает, хотя альфа-канал правильно настроен как на TBitmap, так и на цели рендеринга - он все равно игнорируется. Кажется, вокруг не так много полезной информации. Спасибо - person weblar83; 28.07.2016
comment
Возможно, вам следует попробовать использовать FMX.Graphics.TBitmap вместо обычного VCL TBitmap, как я предложил в другом вопросе для разных целей, которые вы можете найти здесь: stackoverflow.com/a/38551815/3636228 Обратите внимание, что я никогда не пробовал это или видел, как это делают другие, но на основании того факта, что FireMonkey использует DirectX для рендеринга в Windows, это может действительно работать. Я проведу тесты, когда приду домой. - person SilverWarior; 28.07.2016

Если вы посмотрите на исходный код TDirect2DCanvas.CreateBitmap, вы увидите:

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

Итак, чтобы заставить его работать, вы должны соответствовать условиям:

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

Затем вам нужно подготовить альфа-канал каждого пикселя. В вашем случае красный цвет прозрачен, поэтому вы должны сделать что-то вроде этого:

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;

Потом приходит:

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

Мне потребовались все выходные, чтобы понять это самостоятельно, надеюсь, это поможет вам или кому-то еще.

person Kiril Hadjiev    schedule 04.04.2021