从资源文件加载的透明 PNG 图像,使用 Grapics32 调整大小并在 Canvas 上绘制

Transparent PNG image loaded from resource file, resized with Grapics32 and drawn on the Canvas

我需要一点帮助...

我的应用程序资源中有一个透明的 PNG 图像。到目前为止,我一直在 TPngImage 中加载它并使用 Canvas.Draw(X, Y, PngImage); 在屏幕上绘制它。它是透明绘制的。现在我将我的应用程序更新为 DpiAware,我需要缩放所有图像。我需要一个质量重采样器,我选择使用 Graphics32。我设法进行了重采样,但我不知道如何保持透明度……我尝试了所有我能想到的……以下代码的结果是在透明区域用黑色绘制的图像。 .

Foto32, Buff: TBitmap32;
FotoPng: TPngImage;

constructor TForm.Create(AOwner: TComponent);
const BkgHeight = 380;
var Res: TKernelResampler;
    SRect, DRect: TRect;
    ImgWidth: Integer;
begin
 inherited;
 Buff:= TBitmap32.Create;
 Res:= TKernelResampler.Create;
 Res.Kernel:= TLanczosKernel.Create;

 FotoPng:= TPngImage.Create;
 FotoPng.Transparent:= True;
 FotoPng.TransparentColor:= clBlack;
 FotoPng.LoadFromResourceName(HInstance, 'BKG_FOTO');
 Foto32:= TBitmap32.Create;
 Foto32.DrawMode:= dmBlend;
 Foto32.CombineMode:= cmMerge;
 Foto32.OuterColor:= clBlack;
 Foto32.Canvas.Brush.Style:= bsClear;
 Foto32.SetSize(FotoPng.Width, FotoPng.Height);
 FotoPng.Draw(Foto32.Canvas, Rect(0, 0, FotoPng.Width, FotoPng.Height));

 ImgWidth:= Round(Real(Foto32.Width / Foto32.Height) * BkgHeight);
 SRect:= Rect(0, 0, Foto32.Width, Foto32.Height);
 Buff.DrawMode:= dmBlend;
 Buff.CombineMode:= cmMerge;
 Buff.OuterColor:= clBlack;
 Buff.Canvas.Brush.Style:= bsClear;
 Buff.SetSize(Scale(ImgWidth), Scale(BkgHeight));
 DRect:= Rect(0, 0, Buff.Width, Buff.Height);
 Res.Resample(Buff, DRect, DRect, Foto32, SRect, dmTransparent {dmBlend}, nil);
end;

procedure TForm.Paint;
begin
 // ....
 Buff.DrawTo(Canvas.Handle, X, Y);
end;

这是我编译成资源的透明PNG图片: https://postimg.cc/3yy3wrJB

我在这里找到了一个similar question,但是我没有使用带有TImage的图像,我直接在canvas上绘制它。在单一答案中,大卫说:

Anyway, if that is so, I would combine the transparency support of TImage with the re-sampling ability of TBitmap32 to build a solution that way. Keep the original image in a TBitmap32 instance. Whenever you need to load it into the TImage component, for example when re-sizing, use TBitmap32 to perform an in-memory re-size and load that re-sized image.

这正是我想要做的,但我不知道为什么透明度不起作用。有任何想法吗 ?

您的问题似乎与将缓冲区绘制到屏幕有关。 Bitmap32 使用 StretchDIBits 进行绘画,忽略 alpha 通道。

您可以使用 AlphaBlend 函数来绘制图像:

procedure TForm1.FormPaint(Sender: TObject);
var
  BF: TBlendFunction;
begin
  BF.BlendOp := AC_SRC_OVER;
  BF.BlendFlags := 0;
  BF.SourceConstantAlpha := 255;
  BF.AlphaFormat := AC_SRC_ALPHA;

  Winapi.Windows.AlphaBlend(Canvas.Handle, 0, 0, Buff.Width, Buff.Height,
    Buff.Canvas.Handle, 0, 0, Buff.Width, Buff.Height, BF);
end;

或者将您的 TBitmap32 转换为 Delphi TBitmap 并使用 VCL 对其进行绘制:

procedure TForm1.FormPaint(Sender: TObject);
var
  Bmp: TBitmap;
  I: Integer;
begin
  Bmp := TBitmap.Create;
  try
    Bmp.PixelFormat := pf32bit;
    Bmp.AlphaFormat := afDefined;
    Bmp.SetSize(Buff.Width, Buff.Height);
    for I := 0 to Buff.Height - 1 do
      Move(Buff.ScanLine[I]^, Bmp.ScanLine[I]^, Buff.Width * 4);
    Canvas.Draw(0, 0, Bmp);
  finally
    Bmp.Free;
  end;
end;