GDI+效率问题与优化

来源:互联网 发布:java 代理 编辑:程序博客网 时间:2024/05/16 06:36

GDI+效率问题与优化 .
分类: 技术笔记 2009-07-05 00:08 1329人阅读 评论(7) 收藏 举报 
这周在用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上的代码片段,希望对大家有所帮助。

[cpp] view plaincopyprint?
/*  
@ 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;    
}    
/* 
@ 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;  
}  

 

以上代码是从我自己的工程中抽取出来的,没有定义的变量请自行处理。

最近太忙没空也懒得去抽成一个简单的例子。但最重要的部分已经都在上面的代码中呈现。

 

再啰嗦下我的的使用流程:

1、InitDialog中用CreateDib创建出hBmpSection,并得到他的bmpData

2、程序的否个地方把你的东西画到gdi+的Bitmap上

3、调用FlushToDib,把你的Bitmap写到第一步的bmpData中

4、OnPaint中间hBmpSection用BitBlt到dc上

 

--- the end 2---


怎样提高GDI+的绘图效率(补充讨论)--22009-02-22 18:53        偶贴的上一个帖子名字是“GDI+的绘图效率问题,大家讨论一下吧!”。在其中解决的效率问题远远不够。现在工作中的模块做的差不多了,总结了一下最近得到的经验结论,贴出来大家进一步讨论下。。。也算是在CSDN上得到了诸多帮助的一个小小的回报:) 在交流中共同进步吧!

  不知道为什么MS把GDI+中的 DrawImage 这个函数效率做的这么低,(当然了,它的优点是使用方便、支持透明PNG格式等等,还有什么优点有经验的朋友不妨贴出来共享下:) 那么到底有多低呢?我也不知道,所以刚才做了个试验。让DrawImage和::BitBlt速度做了个比较!

  先说下我的机器配置:2*3.0G Intel CPU,1G的内存,Intel945的显卡。在一个窗口中用DrawImage画一个590X480大小的PNG图片,画了100次,用了.......长时间;用BitBlt画同样大小的bmp图片,画了1000次,用了...长时间,多少时间我不管了,帧速我是计算出来了大约是 DrawImage 16.5帧/秒,BitBlt 2169.2帧/秒 。没错!确实是这个数字。我数学学的不好,就用计算器算了一下BitBlt的绘图速度是DrawImage的131.5倍。。。

  代码也贴出来吧,写的比较粗糙:

void CCmpBitBltToDrawImageDlg::OnBnClickedButton1() 
{

Bitmap* ppng = NULL; 
CDC* pDC = GetDC (); 
Graphics* pGrp = Graphics::FromHDC (pDC-> GetSafeHdc ()); 
ppng = Bitmap::FromFile (L"res\\tray.png");

clock_t start = clock (); 
for (int i = 0; i < 100; i++) 

pGrp-> DrawImage (ppng, 0, 0); 

delete pGrp; 
ReleaseDC (pDC); 
delete ppng;

clock_t time = clock () - start; 
float ftime = time / 1000.0f; 
CString str; 
str.Format ("%.1f frame per second\n", 100 / ftime); 
AfxMessageBox (str); 
}

void CCmpBitBltToDrawImageDlg::OnBnClickedButton2() 

CDC memDC; 
CBitmap bmp;

if (0 == bmp.LoadBitmap (IDB_BITMAP1)) 

AfxMessageBox ("载入图片失败"); 
return; 
}

CDC* pDC = GetDC (); 
memDC.CreateCompatibleDC (pDC); 
memDC.SelectObject (&bmp);

clock_t start = clock (); 
for (int i = 0; i < 2000; i++) 

::BitBlt (pDC-> GetSafeHdc (), 0, 0, 590, 480, memDC.GetSafeHdc (), 
0, 0, SRCCOPY); 
}

ReleaseDC (pDC); 
clock_t time = clock () - start; 
float ftime = time / 1000.0f; 
CString str; 
str.Format ("%.1f frame per second\n", 2000 / ftime); 
AfxMessageBox (str); 
}


  对了,我没有在OnPaint用CPaintDC,是因为用CPaintDC DrawImage不出东西。不知道为什么?哪位高手知道烦请告诉我。


  前面罗嗦了半天,下面进入正题。 
  我做的界面模块之前用DrawImage方法来绘图,以我的机器配置有70多个帧吧,人眼是察觉不出来了。觉得速度可以了,但这个模块是跑在虚拟机是的,内存的使用是受限制的,限制到128M,另外可能也有模拟硬件的原因,速度由70多帧降到了9帧。大家都知道只有屏幕的刷新速度达到24帧/秒以上,人眼才会感觉画面流畅。

  所以,我得想办法解决问题呵呵。 
  因为利用了双缓冲,我在内存中建立了一个Graphics绘图平面m_pMemGraphics,先把零碎元素画到这个内存平面上,再一次性将它绘制到屏幕上。这个Graphics对象对象是利用FromImage方法创建的(也就是说,在这个绘图平面上绘图是把所有的东西都画到了这个Graphics对象所依赖的图片上,然后需要绘制到屏幕上的时候,只能用DrawImage的办法将这个图片画到屏幕上。偶也想到了用BitBlt的方法以提高效率,但用FromImage方法创建的Graphcis对象的DC是一片漆黑!找了半天利用它的DC的方法也不得要领,在codeproject上找到一个例子是VB.NET的,分析了半天还是没有办法。。。这又是一个疑问,希望知道的高手告诉俺怎么做,在这里先谢了),我就只好用DrawImage了。也想了其它的几个办法,包括GDI和GDI+混合使用,GDI+使用了GDI创建的DC。但反过来,就像上面我描述的"一片漆黑",行不通,我还是先说一下暂时没有使用BitBlt怎样改进的绘图效果吧:

  1. 使用SetClip限定你的绘制区域。

  2. 仅仅是限定的绘制区域也是不行的,还要把你所要绘制的图片剪切的尽量小,和SetClip配合使用。

  3. 多浪费点儿内存使用 CachedBitmap 吧,绘图速度会好很多,DrawCachedBitmap 要比 DrawImage 快一些。


  使用了以上几种方法我的程序绘制速度由70多帧提高到了200多帧。。。还可以哦,仍然是DrawImage和DrawCachedBitmap而没有使用BitBlt。嘿嘿好了,今天想到的就这么多,先写这些吧。我的结论是:绘图尽量使用BitBlt 来替代 DrawImage。

  贴段示例代码吧,也因为功能性的东西写的比较分散了,就贴一个函数好了,道理是相同的。。。

void CDesktopDlg::DrawRing () 

// 设置剪切区域 
m_pGraphics-> SetClip (m_pClipRgn);

// 内存中绘制背景 
m_pMemTrayGrp-> DrawCachedBitmap (m_pCachedTrayBmp, 0, 0); // <------注意m_pCachedTrayBmp和 // m_pTrayBmp是相同的图片,只是一份复 // 制,用来提高绘图速度。

if (m_lstShowBtnRing.size () > 0) 

// 内存中绘制按钮 
for (list <CEbankButtonAttr*> ::iterator itor = m_lstShowBtnRing.begin (); 
itor != m_lstShowBtnRing.end (); itor ++) 

m_pMemTrayGrp-> DrawCachedBitmap ((*itor)-> m_pCachedBmp, (*itor)-> x, 
(*itor)-> y)); 

}

// 绘制到屏幕 
m_pGraphics-> DrawImage (m_pTrayBmp, *m_pTrayRect);

 

0 0
原创粉丝点击