这周在用GDI+实现图片的动画效果,起初每画一帧都在30-40ms左右,动画效果自然不能令人满意。
困惑了良久,后面在高人的指点下,效率有了显著地提高,最主要是清楚了时间都花在了那里,优化起来也就方便很多。
我现在知道的影响GDI+效率的主要有:
1、DrawImage,比起BitBlt实在差的不是一点点,解决的方法就是用BitBlt替换DrawImage。
2、PixelFormat ,原来没想到这个也会影响效率,但事实证明确实是这样。
Bitmap在new出来的时候是和图片本身的格式有关的,但在最终画到DC上的可不一定是这个格式,具体的格式请查阅MSDN:Image Pixel Format Constants
如果格式不对,DrawImage时会自己做一次图片格式转换,这个也会浪费一定的时间。
一个做法就是Bitmap在new出来后用clone到一个新的 Bitmap,这时是可以转换成指定的PixelFormat的:)
统一成一种格式,特别是大图的时候,效果比较明显。
3、尽量用CachedBitmap替代直接的Bitmap使用也能优化一些效率。
4、每次都全部重画是一种浪费,能不重画的就不重画,刷新的区域也是优化的一个有效方式。
5、对象的构造比较费时,比如用于双缓冲的内存Bitmap和Graphics最好设成成员变量,而不是在OnPaint中每次新建
6、减小图片大小 :)
……
关于GDI和GDI+
GDI的效率更高,而GDI+无疑更加易用,鱼与熊掌不可兼得。
我现在的做法是用GDI+画到内存Bitmap上,最后用GDI BitBlt画到DC上。
小结:
最开始很郁闷,怎么优化都不见效果,感觉乱成一团,于是不断地msdn,google,找人……
终于,在理清大部分的细节后发现:答案其实也很简单。
事情还没结束,今天也太晚了,就先写个大概,备忘下,下次有空时再整理个例子出来。希望大家批评指正。
感谢老师给我的帮助:)
2009-07-05 0:31 --THE END--
// 添加于2009/7/31
把gdi+转成gdi来画到dc上的代码片段,希望对大家有所帮助。
<textarea cols="50" rows="15" name="code" class="cpp">/* @ Function : CreateDib @ brief : 创建内存位图 @ parameter : w @ parameter : h @ parameter : [out] hBmpSection OnPaint中会把这个位图直接往dc上贴 @ parameter : [out] bmpData 位图的实际数据,gdiplus的会把东西都话到上面 @ return : @ remark : @*/ BOOL CreateDib(int w, int h, OUT HBITMAP& hBmpSection, OUT BYTE** bmpData) { BITMAPINFO info = {0}; info.bmiHeader.biSize = sizeof(info.bmiHeader); info.bmiHeader.biWidth = w; info.bmiHeader.biHeight = -h; info.bmiHeader.biPlanes = 1; info.bmiHeader.biBitCount = 32; info.bmiHeader.biCompression = BI_RGB; info.bmiHeader.biSizeImage = w * h * 32 / 8; HDC hdc = ::GetDC(NULL); hBmpSection = ::CreateDIBSection(hdc, &info, DIB_RGB_COLORS, (void**)bmpData, NULL, 0); ::ReleaseDC(NULL, hdc); return hBmpSection != NULL; } /* @ Function : FlushToDib @ brief : 将Gdiplus的Bitamp转存到gdi的内存位图上 @ parameter : [in] pMemBitmap @ parameter : [in, out] bmpData 就是上面的函数创建处理的内存位图的数据区 @ return : @ remark : @*/ void FlushToDib(IN Bitmap* pMemBitmap, IN OUT BYTE** bmpData) { BitmapData data; data.Height = pMemBitmap->GetHeight(); data.PixelFormat = pMemBitmap->GetPixelFormat(); data.Scan0 = *bmpData; data.Stride = (pMemBitmap->GetWidth() * 32 / 8); data.Width = pMemBitmap->GetHeight(); Rect rct(0, 0, pMemBitmap->GetWidth(), pMemBitmap->GetHeight()); pMemBitmap->LockBits(&rct, ImageLockModeRead | ImageLockModeUserInputBuf, pMemBitmap->GetPixelFormat(), &data); pMemBitmap->UnlockBits(&data); } // m_hBmpSection是InitDialog时用CreateDib创建出来的 LRESULT OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) { CPaintDC dc(m_hWnd); HDC hdcMen = ::CreateCompatibleDC(dc.m_hDC); HBITMAP hOldBmp = (HBITMAP)::SelectObject(hdcMen, m_hBmpSection); // 宽度和高度自己取下 ::BitBlt(dc.m_hDC, 0, 0, m_width, m_height, hdcMen, 0, 0, SRCCOPY); ::SelectObject(hdcMen, hOldBmp); ::DeleteDC(hdcMen); return TRUE; } </textarea>
以上代码是从我自己的工程中抽取出来的,没有定义的变量请自行处理。
最近太忙没空也懒得去抽成一个简单的例子。但最重要的部分已经都在上面的代码中呈现。
再啰嗦下我的的使用流程:
1、InitDialog中用CreateDib创建出hBmpSection,并得到他的bmpData
2、程序的否个地方把你的东西画到gdi+的Bitmap上
3、调用FlushToDib,把你的Bitmap写到第一步的bmpData中
4、OnPaint中间hBmpSection用BitBlt到dc上
--- the end 2---