You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Delphi 10.4中带透明背景PNG图片裁剪绘制性能优化求助

Delphi 10.4下PNG透明图片裁剪绘制性能优化问题

我用Delphi 10.4在Windows 10上开发,碰到带透明背景的PNG图片裁剪绘制速度极慢的问题,核心原因是当前基于Scanline实现的裁剪逻辑性能太差。

原程序使用无透明背景的BMP时,通过CopyRect绘制裁剪区域仅耗时0.415ms,符合预期;但改用仅含全透明背景(无半透明像素)的PNG后,裁剪绘制耗时飙升至13.5ms。我尝试过用带Alpha通道的32位BMP结合AlphaBlend函数,但未成功实现。现寻求优化Scanline性能的方法,或合适的第三方库解决方案。


原BMP绘制代码

procedure TfrmMain.btnDrawBMPClick(Sender: TObject);
var
frontsigns : TBitmap;
begin

frontsigns := TBitmap.Create;
frontsigns.LoadFromFile('E:\frontsigns.bmp');

frmMain.Canvas.CopyRect(Rect(0,0,302,869), frontsigns.Canvas, Rect(0, yStartPos, 302, yEndPos)); // yStartPos and yEndPos are variables
end;

PNG绘制代码

procedure TfrmMain.btnDrawBMPClick(Sender: TObject);
var
background, tmpbackground : TBitmap;
frontsigns, CroppedPng : TPngImage;
begin

background := TBitmap.Create;
background.LoadFromFile('E:\back.bmp');
frontsigns := TPngImage.Create;
frontsigns.LoadFromFile('E:\frontsigns.png');
tmpbackground := TBitmap.Create(303, 870);

tmpbackground.Canvas.CopyRect(Rect(0, 0, 302, 869), background.Canvas, Rect(0, 0, 302, 869));
CropPng(frontsigns, 0, yStartPos, 302, yEndPos, CroppedPng); // yStartPos and yEndPos are variables
tmpbackground.Canvas.Draw(0, 0, CroppedPng);

end;

CropPng裁剪函数代码

const
    ColorTabMax = 10;
    ColorTab : array[0..ColorTabMax-1] of TColor =
        (ClBlack, ClMaroon, ClRed, ClWebDarkOrange, ClYellow, ClGreen, ClBlue, ClPurple, ClGray, ClWhite);

procedure CropPng(Source: TPngImage; Left, Top, Width, Height: Integer; out Target : TPngImage);

function ColorToTriple(Color: TColor): TRGBTriple;
begin
    Color := ColorToRGB(Color);
    Result.rgbtBlue := Color shr 16 and $FF;
    Result.rgbtGreen := Color shr 8 and $FF;
    Result.rgbtRed := Color and $FF;
end;

var
X, Y : Integer;
Bitmap : TBitmap;
BitmapLine : PRGBLine;
AlphaLineA, AlphaLineB : pngImage.PByteArray;
begin
if (Source.Width < (Left + Width)) or (Source.Height < (Top + Height)) then
    raise Exception.Create('Invalid position/size');

Bitmap := TBitmap.Create;
try
Bitmap.Width := Width;
Bitmap.Height := Height;
Bitmap.PixelFormat := pf24bit;

for Y := 0 to Bitmap.Height - 1 do
begin
    BitmapLine := Bitmap.Scanline[Y];
    for X := 0 to Bitmap.Width - 1 do
        BitmapLine^[X] := ColorToTriple(Source.Pixels[Left + X, Top + Y]);
end;

Target := TPngImage.Create;
Target.Assign(Bitmap);

if Source.Header.ColorType in [COLOR_GRAYSCALEALPHA, COLOR_RGBALPHA] then
begin
    Target.CreateAlpha;
    for Y := 0 to Target.Height - 1 do
    begin
        AlphaLineA := Source.AlphaScanline[Top + Y];
        AlphaLineB := Target.AlphaScanline[Y];
        for X := 0 to Target.Width - 1 do
        AlphaLineB^[X] := AlphaLineA^[X + Left];
    end;
end;
finally
    Bitmap.Free;
end;
end;

优化方案

1. 替换Source.Pixels调用(核心性能提升)

当前裁剪函数中Source.Pixels[Left + X, Top + Y]是性能瓶颈——TPngImage.Pixels每次调用都会执行颜色转换、坐标校验等冗余操作,循环逐像素调用会放大开销。直接读取PNG原始扫描线数据,用内存拷贝代替逐像素赋值:

// 优化后的CropPng函数
procedure CropPng(Source: TPngImage; Left, Top, Width, Height: Integer; out Target : TPngImage);
var
  X, Y: Integer;
  SourceRGBLine: PRGBLine;
  TargetRGBLine: PRGBLine;
  SourceAlphaLine, TargetAlphaLine: pngImage.PByteArray;
begin
  if (Source.Width < (Left + Width)) or (Source.Height < (Top + Height)) then
    raise Exception.Create('Invalid position/size');

  // 直接创建目标PNG,跳过TBitmap中转
  Target := TPngImage.CreateBlank(COLOR_RGBALPHA, 8, Width, Height);

  for Y := 0 to Height - 1 do
  begin
    // 获取源和目标的RGB扫描线指针
    SourceRGBLine := Source.Scanline[Top + Y];
    TargetRGBLine := Target.Scanline[Y];
    // 内存拷贝整行RGB数据,代替逐像素赋值
    Move(SourceRGBLine^[Left], TargetRGBLine^[0], Width * SizeOf(TRGBTriple));

    // 处理Alpha通道
    if Source.Header.ColorType in [COLOR_GRAYSCALEALPHA, COLOR_RGBALPHA] then
    begin
      SourceAlphaLine := Source.AlphaScanline[Top + Y];
      TargetAlphaLine := Target.AlphaScanline[Y];
      Move(SourceAlphaLine^[Left], TargetAlphaLine^[0], Width);
    end;
  end;
end;

2. 用32位BMP结合AlphaBlend直接绘制裁剪区域

由于你的PNG只有全透明/不透明像素,可直接将PNG转为32位带Alpha的BMP,用AlphaBlend一步完成裁剪+绘制,避免先裁剪再绘制的冗余步骤:

procedure TfrmMain.btnDrawPNGOptimizedClick(Sender: TObject);
var
  background, frontBmp: TBitmap;
  frontsigns: TPngImage;
  srcRect, destRect: TRect;
  blendFunc: BLENDFUNCTION;
begin
  background := TBitmap.Create;
  try
    background.LoadFromFile('E:\back.bmp');
    background.PixelFormat := pf32bit;

    frontsigns := TPngImage.Create;
    try
      frontsigns.LoadFromFile('E:\frontsigns.png');
      frontBmp := TBitmap.Create;
      try
        frontBmp.Assign(frontsigns);
        frontBmp.PixelFormat := pf32bit;

        srcRect := Rect(0, yStartPos, 302, yEndPos);
        destRect := Rect(0, 0, srcRect.Width, srcRect.Height);

        // 配置Alpha混合参数
        blendFunc.BlendOp := AC_SRC_OVER;
        blendFunc.BlendFlags := 0;
        blendFunc.SourceConstantAlpha := 255;
        blendFunc.AlphaFormat := AC_SRC_ALPHA;

        // 直接将裁剪区域绘制到背景
        AlphaBlend(background.Canvas.Handle, destRect.Left, destRect.Top,
          destRect.Width, destRect.Height,
          frontBmp.Canvas.Handle, srcRect.Left, srcRect.Top,
          srcRect.Width, srcRect.Height, blendFunc);

        // 绘制最终结果到窗体
        frmMain.Canvas.Draw(0, 0, background);
      finally
        frontBmp.Free;
      end;
    finally
      frontsigns.Free;
    end;
  finally
    background.Free;
  end;
end;

3. 第三方库选择

如果不想手动优化底层逻辑,可选用成熟的图形库:

  • Graphics32:Delphi高性能图形处理库,支持带Alpha位图的快速裁剪、绘制,底层用内存拷贝替代逐像素操作,性能远超原生VCL。
  • ImageEn:功能全面的图形库,对PNG的裁剪、绘制做了大量优化,API简洁易用,适合快速开发。

内容的提问来源于stack exchange,提问作者brane

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.23 15:06:29