zoukankan      html  css  js  c++  java
  • Delphi 7中对StretchBlt, StretchDIBits, DrawDibDraw, BitBlt 的性能测试

    我的天哪,上一篇博文是2年前的事情了。看来又虚度了2年光阴,继续学习。。。
    本文算是副产品,正品是利用 FFmpeg 从任意视频中生成 GIF 片段的小程序,等写完了再发。不为别的,只是为了给儿子做动图,且看不惯这种工具也要收费!

    V2G 正品已出炉,虽然不大像样,但好歹是能用,请见:用 Delphi 7 实现基于 FFMS2 的视频转 GIF 工具

    声明

    本文是首先看到了求比 Stretchblt 方法更快的缩放算法的帖子,请参看其中署名为“张辉明”的回复。我做了优化和一些修正,但 DrawDibDraw 部分的调用是原文照录的。(其实上文就是我 Bing 了 DrawDibDraw 时搜到的。)

    为什么要测试 StretchBlt、StretchDIBits、DrawDibDraw 的性能

    因为视频回放需要很高的显示性能,解码占了很多计算量,留给显示的时间不多,能优化则优化吧。

    其实现在的 CPU 跑个视频播放已经绰绰有余了,GPU 压根就不必用。即便是用 Delphi 自带的 TImage 控件,用 Bitmap 往里填也可以满足普通播放需求了。如果时光倒流到 10 年前,那可真是得去研究 DirectX、OpenGL了。可惜关于这哥俩,大部分都是 C、C++ 的资源,我啃了半天 SDL,觉得有点杀鸡用牛刀。所以就想着先实现需求吧,真的不行了再优化吧。在我的 Intel i3 3220 上,用 StretchDIBits 播放视频时最多也就跑了 22%。

    为什么还抱着 Delphi 不放?

    1. 性价比第一
      敢问性能、便捷、体积俱佳的 Windows 开发环境,谁敢和 Delphi 比?C#,Java 是优秀,可为了一个小功能就跑它个虚拟机,实在划不来啊。C++ 倒是够 sharp,可学习过程太痛苦了,代码还不容易写。
    2. 全能
      都说 Python 好,可我眼拙,实在看不出来好在哪里,局限性太大。唯一的好处是能让新手快速上手编程,还有一个好处是能让你忘记计算机是怎么运作的!
    3. 怀旧
      十几年前自学的东西,从 Delphi 3 开始用,有感情了。只要 Windows 不停止对 32 位程序的支持,我就会一直用下去。(关于这一点,我要狠狠鄙视 Apple 一下。)
    4. Delphi 7是经典
      和 Visual Studio、水果一样,当年 Borland 的产品也有大小年,逢单的版本就是稳定一些。虽然轮子有时候得从头开始造,但是“知其所以然”是乐在其中的事,相信我!

    测试结果

    如果只关心结果,或者对 Delphi 不屑,那您就不必往下看了,我先给出结果吧。为您节省点时间。严格意义上说,BitBlt不属于其他哥仨的阵营,因为不用缩放,所以速度当然快了。放在这里比较,就当是个 Baseline 吧。

    1. DrawDibDraw 最快(1ms 级别)。
      不到 StretchBlt和StretchDIBits 的一半,且不需要用 SetStretchBltMode 设置什么缩放模式,画质看不出分别。
    2. StretchBltStretchDIBits 难分伯仲。
      用了色彩拟合模式(HALFTONE)的话会大大增加计算量,耗时4倍,比 DrawDibDraw 慢1个数量级。建议缩小图像时可以用 COLORONCOLOR 模式,肉眼看不出区别,但可以比 HALFTONE 模式提速4倍!
    API COLORONCOLOR HALFTONE
    BitBlt 400 400
    DrawDibDraw 1125 1125
    StretchBlt 3000 11406
    StretchDIBits 3203 11576
    • 测试用机:CPU: Intel i3 3220,内存: 8G DDRIII 1333,显卡: AMD Radeon HD 7700 (对测试结果没影响吧),Windows 10专业版
    • 测试次数:1000次
    • 时间单位:millisecond(毫秒)
    • COLORONCOLOR:删除不需要的点。
      这是 SetStretchBltMode 的参数,指定目标设备(区域)的缩放模式。在用 StretchDIBits 和 StretchBlt 时必须得设置一个缩放模式,不然,嘿嘿,惨不忍睹。官方说明是:“Deletes the pixels. This mode deletes all eliminated lines of pixels without trying to preserve their information.”,中文意思大概就是:删除不需要的像素点。该模式删除所有无用的点阵,这些点的所有信息都不予保留。 参见 SetStretchBltMode
    • HALFTONE:将源区域的颜色溶入目标区域中去。
      作用同上。官方说明是:“Maps pixels from the source rectangle into blocks of pixels in the destination rectangle. The average color over the destination block of pixels approximates the color of the source pixels.”中文大概意思是:将源矩形区域的像素点信息拟合到目标区域周边的多个像素块中。目标区域多个像素块的颜色值会进行平均,以便最大程度地接近源像素的色彩。参见 SetStretchBltMode

    源码

    界面

    就放了几个按钮而已,名称末尾为C的表示用了 COLORONCOLOR 模式,为H的表示用了 HALFTONE 模式。还有一个 Timage 控件。

    常量

    FileName 定义了 Bmp 图片文件名,Count 定义了测试循环的次数。

    FileName='1.bmp';
    Count=1000;
    FontSize=20;
    

    BMP 文件读取

    因为 StretchBlt和BitBlt 只需要提供源 HDC,不需要用 tagBITMAPINFO 和原始 RGB 数据区作为参数,所以直接用了 TBitmap 控件载入图片文件。

    procedure TMainForm.StretchBltDisplay;
    var
      bmp : TBitmap ;
      i : Integer ;
      Start : DWORD ;
    begin
      Bmp:= TBitmap.Create ;
      bmp.LoadFromFile(FileName);
    
      Start := GetTickCount ;
      for i := 1 to count do
      begin
        StretchBlt(image1.Canvas.Handle, 0, 0, image1.ClientWidth, image1.ClientHeight,
                  bmp.Canvas.Handle, 0,0,bmp.Width,bmp.Height, SRCCOPY);
        image1.Canvas.TextOut(10,10,inttostr(i));
        image1.Refresh;
      end;
      MainForm.Caption := IntToStr(GetTickCount - Start);
    
      bmp.Free ;
    end;
    

    DrawDibDraw和DrawDibDraw都需要用到BMP原始信息做参数,所以只好写了个LoadBmp从文件中读取数据。
    因为要把原始信息带出去,所以带了var前缀。

    procedure LoadBmp(bmpFile: String; var bmpinfo:TBitmapInfo; var pBmpData:Pointer);
    var
      bmf: TBitmapFileHeader;
      imageSize: LongWord;
      Stream: TFileStream;
    begin
       try
        Stream:= TFileStream.Create(bmpFile, fmOpenRead or fmShareDenyWrite);
        Stream.Read(bmf, sizeof(Bmf));
        Stream.Read(bmpinfo, sizeof(bmpinfo));
        imageSize:= bmf.bfSize-bmf.bfOffBits;
        stream.Seek(bmf.bfOffBits,0);
    
        FreeMem(pBmpData);
        GetMem(pBmpData, imageSize);
    
        Stream.Read(pBmpData^, ImageSize);
      finally
        FreeAndNil(Stream);
      end;
    end;
    

    关于 var 前缀

    一开始以为,用指针就可以在函数内给外部的指针分配内存并传出结果了。但其实不对,外面的指针还一直是 nil。必须带上 var 前缀才行(指针的指针)。

    关于 VFW

    DrawDibDraw 是 VFW(Video for Windows)中的 API,关于 DrawDibDraw 的用法可以参考园子里的 DrawDibDraw函数的使用方法。封装文件 VFW.pas 来自一篇《delphi 摄像头编程 vfw》,出处已不可考,被署名 Tom Nuydens 的修改过。

    完整源码

    结论和建议

    • 单纯缩小画面的(源图一定比目标图大):StretchBlt、StretchDIBits 随便用,先用 SetStretchBltMode 选 COLORONCOLOR 模式,性能足够了。
    • 必须放大画面的(源图比目标图小):要用StretchBlt、StretchDIBits,用SetStretchBltMode必须选HALFTONE模式。性能无法接受可选 DrawDibDraw。
    • 图省事用 DrawDibDraw,可能要多耗些资源吧(没精确测算过)。
    • 图形性能要求更高的,啃DirectX、OpenGL、SDL去吧。代码不难,难的是要理解那么多图形学概念。
  • 相关阅读:
    改变原数组的filter
    fireEvent2
    Ajaxを勉強しよ
    javascript 地图
    fillZero函数
    window.onerror
    とある要素以下にある textNode で一致する textNode を XPath で高速に取り出す
    判定是否为表单元素
    Django中判断用户是否登陆
    【 如果你和我一样在一栋33层大厦的27层工作,在这栋大厦里发生了火灾,那么你该怎么办? 看看也许会保住你的性命!!!】
  • 原文地址:https://www.cnblogs.com/popapa/p/performance-DrawDibDraw.html
Copyright © 2011-2022 走看看