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

Delphi FMX中TImage波浪扭曲动画的性能优化咨询

FireMonkey下TImage波浪扭曲动画的性能优化方案

在Delphi 10.2 FireMonkey框架开发Windows 10桌面应用时,为TImage实现基于波浪函数(或柏林噪声函数)的扭曲动画,已预计算波浪数据存储于三维数组WaveData,通过TTimer(间隔32ms)调用DrawWaveDistortion过程实现动画,但存在帧率低、动画卡顿的问题,添加其他效果时情况更严重。以下是针对性的性能优化方案:

原始代码

procedure DrawWaveDistortion;
var
  SourceBitmap, TargetBitmap: TBitmap;
  SourceBitmapData, TargetBitmapData: TBitmapData;
  MapAccessOk: Boolean;
  BMWidth, BMHeight: Integer;
  ix, iy: Integer;
  Shift: Integer;
  Shiftedix, Shiftediy: Integer;
  Color: TAlphaColor;
begin
  SourceBitmap:=Image1.Bitmap;
  BMWidth:=SourceBitmap.Width;
  BMHeight:=SourceBitmap.Height;
  TargetBitmap:=TBitmap.Create(BMWidth, BMHeight);
  try
    MapAccessOk:=SourceBitmap.Map(TMapAccess.Read, SourceBitmapData);
    try
      if MapAccessOk then
      begin
        MapAccessOk:=TargetBitmap.Map(TMapAccess.Write, TargetBitmapData);
        try
          if MapAccessOk then
          begin
            for ix := 0 to BMWidth-1 do
            begin
              for iy := 0 to BMHeight-1 do
              begin
                Shift:=WaveData[StepCounter, ix, iy];
                Shiftedix:=EnsureRange(ix+Shift, 0, BMWidth-1);
                Shiftediy:=EnsureRange(iy+Shift, 0, BMHeight-1);
                Color:=SourceBitmapData.GetPixel(Shiftedix, Shiftediy);
                TargetBitmapData.SetPixel(ix, iy, Color);
              end;
            end;
          end;
        finally
          TargetBitmap.Unmap(TargetBitmapData);
        end;
      end;
    finally
      SourceBitmap.Unmap(SourceBitmapData);
    end;
    Image1.Bitmap.Assign(TargetBitmap);
    Inc(StepCounter);
    if StepCounter>=MaxNumOfFunctionValues then
    begin
      StepCounter:=0;
    end;
  finally
    FTargetBitmap.Free;
  end;
end;

优化方案

  • 复用TargetBitmap,避免频繁创建销毁
    将TargetBitmap声明为窗体类的成员变量,在窗体创建时初始化其尺寸与Image1.Bitmap一致,后续仅在Image1.Bitmap尺寸变化时重新创建。避免每次调用都执行TBitmap.Create和Free,减少内存分配开销。

  • 替换GetPixel/SetPixel为直接内存访问
    GetPixel和SetPixel是封装后的逐像素调用,性能极低。直接通过BitmapData.Scanline获取每行像素的内存指针,结合像素格式计算内存偏移,直接读写颜色值。例如:

    var
      SourceRow, TargetRow: PAlphaColorArray;
    begin
      // ...
      SourceRow := SourceBitmapData.Scanline[Shiftediy];
      TargetRow := TargetBitmapData.Scanline[iy];
      TargetRow[ix] := SourceRow[Shiftedix];
      // ...
    end;
    
  • 调整循环顺序,优化CPU缓存命中率
    FireMonkey的位图数据按行存储,原始代码先遍历x轴再y轴会导致内存非连续访问,触发频繁缓存失效。改为先遍历y轴(行)再遍历x轴(列),让内存访问连续,充分利用CPU缓存:

    for iy := 0 to BMHeight-1 do
    begin
      for ix := 0 to BMWidth-1 do
      begin
        // 像素处理逻辑
      end;
    end;
    
  • 预计算边界限制,减少运行时计算量
    在预计算WaveData时,直接将偏移值Shift处理为ix+Shift不会超出0到BMWidth-1、iy+Shift不会超出0到BMHeight-1的范围,运行时无需再调用EnsureRange函数,节省计算开销。

  • 改用GPU加速(自定义Shader)
    利用FireMonkey的Shader渲染能力,将波浪扭曲的计算转移到GPU执行,充分发挥GPU并行计算的优势。编写自定义Fragment Shader,在Shader中根据WaveData(可通过Uniform变量传递)计算像素偏移,直接渲染到TImage,性能提升显著。

  • 优化动画触发机制
    替换TTimer为后台线程计算+UI线程同步更新的方式,避免UI线程被计算任务阻塞。或者使用FireMonkey内置的TAnimation组件体系,让动画调度由框架优化处理。

优化后示例代码(核心部分)

// 先在类中声明成员变量:FTargetBitmap: TBitmap;
procedure TForm1.DrawWaveDistortion;
var
  SourceBitmap: TBitmap;
  SourceBitmapData, TargetBitmapData: TBitmapData;
  MapAccessOk: Boolean;
  BMWidth, BMHeight: Integer;
  ix, iy: Integer;
  Shift: Integer;
  Shiftedix, Shiftediy: Integer;
  SourceRow, TargetRow: PAlphaColorArray;
begin
  SourceBitmap := Image1.Bitmap;
  BMWidth := SourceBitmap.Width;
  BMHeight := SourceBitmap.Height;

  // 初始化或更新TargetBitmap尺寸
  if (FTargetBitmap = nil) or (FTargetBitmap.Width <> BMWidth) or (FTargetBitmap.Height <> BMHeight) then
  begin
    FTargetBitmap.Free;
    FTargetBitmap := TBitmap.Create(BMWidth, BMHeight);
    FTargetBitmap.PixelFormat := SourceBitmap.PixelFormat;
  end;

  MapAccessOk := SourceBitmap.Map(TMapAccess.Read, SourceBitmapData);
  try
    if MapAccessOk then
    begin
      MapAccessOk := FTargetBitmap.Map(TMapAccess.Write, TargetBitmapData);
      try
        if MapAccessOk then
        begin
          // 调整循环顺序:先y后x
          for iy := 0 to BMHeight - 1 do
          begin
            TargetRow := TargetBitmapData.Scanline[iy];
            for ix := 0 to BMWidth - 1 do
            begin
              Shift := WaveData[StepCounter, ix, iy];
              // 假设WaveData已预计算好边界限制,无需EnsureRange
              Shiftedix := ix + Shift;
              Shiftediy := iy + Shift;

              SourceRow := SourceBitmapData.Scanline[Shiftediy];
              TargetRow[ix] := SourceRow[Shiftedix];
            end;
          end;
        end;
      finally
        FTargetBitmap.Unmap(TargetBitmapData);
      end;
    end;
  finally
    SourceBitmap.Unmap(SourceBitmapData);
  end;

  Image1.Bitmap.Assign(FTargetBitmap);
  Inc(StepCounter);
  if StepCounter >= MaxNumOfFunctionValues then
    StepCounter := 0;
end;

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 08:50:04