我的天哪,上一篇博文是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 不放?
- 性價比第一
敢問性能、便捷、體積俱佳的 Windows 開發環境,誰敢和 Delphi 比?C#,Java 是優秀,可為了一個小功能就跑它個虛擬機,實在划不來啊。C++ 倒是夠 sharp,可學習過程太痛苦了,代碼還不容易寫。 - 全能
都說 Python 好,可我眼拙,實在看不出來好在哪里,局限性太大。唯一的好處是能讓新手快速上手編程,還有一個好處是能讓你忘記計算機是怎么運作的! - 懷舊
十幾年前自學的東西,從 Delphi 3 開始用,有感情了。只要 Windows 不停止對 32 位程序的支持,我就會一直用下去。(關於這一點,我要狠狠鄙視 Apple 一下。) - Delphi 7是經典
和 Visual Studio、水果一樣,當年 Borland 的產品也有大小年,逢單的版本就是穩定一些。雖然輪子有時候得從頭開始造,但是“知其所以然”是樂在其中的事,相信我!
測試結果
如果只關心結果,或者對 Delphi 不屑,那您就不必往下看了,我先給出結果吧。為您節省點時間。嚴格意義上說,BitBlt不屬於其他哥仨的陣營,因為不用縮放,所以速度當然快了。放在這里比較,就當是個 Baseline 吧。
- DrawDibDraw 最快(1ms 級別)。
不到 StretchBlt和StretchDIBits 的一半,且不需要用 SetStretchBltMode 設置什么縮放模式,畫質看不出分別。 - StretchBlt 和 StretchDIBits 難分伯仲。
用了色彩擬合模式(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去吧。代碼不難,難的是要理解那么多圖形學概念。