前言:
一年来做了一些图像相关的工作,最开始也没什么基础,所以本着简单质朴的理念没有试用那些第三方图像处理库,而是选用了windows提供的GDI/GDI+,结果也能胜任需要。
这玩意嘛,上手也不难,用几天后,说实话,真感觉像网上一些人说的那样,“挺垃圾的”。
由于接下来很可能转行了,一些一直没想清楚也没机会求解答的问题就只能存疑了,比如:
1,到底是我对GDI/GDI+的了解不够深入,导致自认为它“垃圾”,还是它确实很差?
2,那些第三方图像库的实现机制是怎样的?它们在windows系统对底层的封闭态度下,如何做到类似控制显存等?
这个系列的文章,记录个人一些浅薄的经验心得,不算太透彻,甚至都不够准确,仅供参考罢了。
一、效率及“双缓冲”
只要用几天GDI+,我想几乎所有人都会遇到类似“画面闪烁”、“帧率低”等问题,然后上网一搜,就会搜到一堆“双缓冲”、“提高效率”的字眼。
这里就存在一个问题,因为你的“闪烁”和我的“闪烁”,其实是两种现象,而甲的“双缓冲”和乙的“双缓冲”,又完全是两码事。这里理一理:
A.第一种类型的“闪烁”及“双缓冲”:
比如你要在白的客户端背景上移动一幅图片。
很明显,需要擦除、重绘,如此循环实现。但是这样做就会产生一个问题,移动的过程中图片一闪一闪的,眼都花了。这是因为擦除完毕后白板状态也实时被呈现出来,你眼睛看到的是白板-图片-白板-图片,自然闪烁。
解决的方法,是在内存开辟一幅草稿图,每次擦除重绘,都在草稿上进行,而前台客户端呢,仅仅是周期的贴上草稿纸。这样你眼睛看到的是 草稿纸-新的草稿纸-新的草稿纸……这样就不闪了。
B.第二种类型的“闪烁”及“双缓冲”:
第二种闪烁发生在直接用Graphics对象的DrawImage方法绘制屏幕时,当然Graphics对象根据客户端窗口句柄生成的。
你会发现屏幕也在闪,但是不同于前一种,是那种感觉屏幕上的图像被分割成一条一条的“闪烁”。
我曾经以为这是因为DrawImage的绘制效率太低导致,然后改用GDI的BitBlt函数自己封了一个DrawScreen绘图函数,“成功”解决了此“闪烁”。
(在此插一句,我做过一个测试,先都预加载好图片,然后DrawImage和BitBlt均绘制几百次,对比其效率,发现相差有4倍左右)
昨天我重新对比测试了DrawScreen和DrawImage的效率,发现DrawScreen反而慢了近六倍。
实际上,真正的“闪烁”原因,这篇文章给出的解释比较合理:使用bitblt提高GDI+绘图的效率(转)
“GDI+画图,是将所有的图形先换入显卡的缓存中看,显卡会每隔一段时间将显存中的内容输出到显示器中,如果在这个显示周期内,无法将所有的图形都放到显存中,就会出现闪烁的现象”
它提出的解决方案,又是一种概念的“双缓冲”,这里我贴出我自己的DrawScreen,原理类似。
void DrawScreen(Image *image)
{
bitmap = (Bitmap *)image; //image转为bitmap
bitmap->GetHBITMAP(Color(0,0,0), &hBitmap); //获取bitmap对应GDI位图句柄
CDC *cdc = GetDC(); //创建目标dc
HDC srchdc = CreateCompatibleDC(cdc->m_hDC); //创建源dc
SelectObject(srchdc, hBitmap); //将源位图句柄选入源dc
BitBlt(cdc->m_hDC, 0, 0, CLIENT_WIDTH, CLIENT_HEIGHT, srchdc, 0, 0, SRCCOPY);//绘图
DeleteObject(hBitmap);
DeleteDC(srchdc);
ReleaseDC(cdc);
}
所以实质上是牺牲了效率解决了闪烁问题,至于典型的利用BitBlt方法提高效率的例子,我还真给不出。
最后再贴篇我觉得比较专业的文章供参考:陈灯WGF工作内容及特点
这个叫陈灯的博主的blog中,还有其他论述此问题的文章,自己找吧。