位图疑难解析

  有次接到一个关于图形操作的任务,在概念上纠结了很久,幸而得到很多前辈的指定。

整理如下:

此处是原帖1:http://topic.csdn.net/u/20100809/23/651b2840-2643-49c3-a77d-8acb7f9758a7.html

此处是原帖2:http://topic.csdn.net/u/20100820/15/ed5d9990-e44e-4fd3-9ff3-0ba15f5c30c7.html

在此处感谢xxd_qd同学的耐心指导和详细解答。


1双缓冲概念解析
好吧,你先弄清几个概念。既然你前面用了“黑板”这个比喻,很好,那我们继续用这个比喻好了。

首先,DC的含义是“设备关联”,用你那个“黑板”的比喻的话,DC其实就是个“黑板框”,并不是黑板本身。Windows的DC大体可以分为两类,一类是跟硬件相关联的设备DC,如屏幕、打印机等等。这些设备相当于真实的“黑板”。由于设备的物理特性的限制,这类“黑板”有特定的属性,例如分辨率、颜色位数等等。而设备DC,就是把这些真实“黑板”框起来的“黑板框”,你所绘制的图像,并不是画在DC上,而是画在设备上,DC不过是一个渠道而已。
另一类DC是内存DC,它没有跟任何设备向关联,仅仅是为了画图方便,而用它来在内存里模拟真实的设备。由于它没有对应任何真实的设备,因此它没有什么固定的属性。为了保证能模拟某个特定设备,内存DC在生成的时候必须指定它模拟哪个设备,因此它必须用CreateCompatibleDC来生成,其参数就是待模拟的设备DC。由于内存DC没有任何固定属性,因此将一个内存DC作为参数送入CreateCompatibleDC来生成另一个内存DC是不行的。
生成了内存DC之后,我们实际上仅仅拥有了一个黑板框。与设备DC不同,作为真实设备的“黑板”总是存在的,但内存DC的黑板则不然,它必须靠我们专门地在内存里制作一个出来才行。能够作为内存DC的黑板的东西就是Bitmap(你所谓的CBitmap仅仅是MFC对HBITMAP的封装,因此下面只谈HBITMAP)。由于没有了设备的物理限制,这个黑板你愿意做多大都行。为了能够更好地模拟真实的设备,这块黑板的生成,最好使用CreateCompatibleBitmap,并将需要模拟的设备DC传入其中。同样,不应该把一个内存DC作为参数送入CreateCompatibleBitmap,否则你会得到某些意料之外的结果。
生成了黑板之后,还需要把黑板“安装”进黑板框,才能进行画图。“安装”的方法就是使用SelectObject,把你生成的黑板放进你的内存DC。SelectObject返回的结果是从这个DC里被换下来的黑板。第一次安装黑板的时候,被换下来的是一块“NULL 黑板”。之后,你可以按照你的意愿随时给黑板框更换黑板。顺便说一句,由于设备DC所关联的是真实的设备黑板,因此对一个设备DC进行安装黑板(把一个Bitmap送入SelectObject)的操作是无效的。
装好黑板之后,就可以在黑板上画图了。如果装进黑板框的是“NULL 黑板”,那么你的一切绘图操作也都会成功,但没有任何效果(想象一下拿着粉笔在空气里画图。。。)。当你画了一些图形之后,你可以把另一块黑板换上去,再去画另一些图形。当然,黑板被换下来之后,其内容不会发生任何变化。如果你愿意的话,你可以生成N块黑板,不断地换进同一个黑板框,画上各种不同的图形,然后再换下来。
最后,如果你已经画完了,你可以通过DeleteDC销毁那个内存DC(黑板框)。尽管销毁黑板框的操作并不会损坏当前安装在其中的黑板,但仍然建议在销毁DC之前,用最初换下来的那块“NULL 黑板”把你自己的黑板换下来。至于你自己的黑板,你可以在你不需要它的时候,用DeleteObject来销毁。不过要注意,不要试图在这块黑板还装在某个黑板框里的时候去销毁它。


剩下的问题就很简单了。什么叫双缓冲呢?
1、做个内存里的黑板框。
2、做个黑板装进去。
3、在黑板上面画图。
4、把画好的图贴到真实的设备黑板上。
5、把新做的黑板换下来销毁。
6、把黑板框销毁。


这个流程可以很容易地改成:
1、预先画好固定的图形:
  1.1、做个内存里的黑板框。
  1.2、做个黑板装进去。
  1.3、在黑板上面画图。
  1.4、把新做的黑板换下来,但不要销毁(如果你自己做的黑板你都没有弄个变量存起来,那我就无话可说了)。
  1.5、把黑板框销毁。
2、当需要往屏幕上画的时候(例如响应WM_PAINT消息):
  2.1、做个内存里的黑板框。
  2.2、把你上次存起来的黑板装进去。
  2.3、把黑板上的内存贴到真实的设备黑板上。
  2.4、把黑板换下来。
  2.5、把黑板框销毁。
3、作为一个良好习惯,当你的程序退出时,记得把你存起来的黑板销毁。

如果你愿意的话,上述流程还可以进一步变化,例如你把好几种不同固定图形预先画在几块黑板上,按需要选用,或者你同时制作两个黑板框,把一块新做的黑板装进黑板框1,然后把预先做好的黑板1至黑板N轮流装进黑板框2,并往黑板框1中的新黑板上贴,最后再把最终做好的新黑板中的内容贴到真实的设备黑板上,诸如此类。


2位图缩放解析
从你的问题描述中可以看出,你要显示和处理的东西, 本身是一个矢量图。事实上,对此,我觉得用一个保留的位图来支持放缩并不是一个好主意,因为位图毕竟不是矢量图。比如你在保留位图上画了一根单像素线,如果最终贴图的时候缩小了一点,哪怕只缩小10%,你的这根线也会有断开或者模糊的地方,因为单像素线缩小10%以后是0.9像素宽,0.9像素怎么显示?同样,放大的话,即使仅放大10%,那么线的某些地方也会长个“疙瘩”。这还仅仅是10%的情况,如果放缩几倍的话,那就完全惨不忍睹了。因此,我觉得一旦缩放比变化,就应该按新的缩放比重新生成保留位图,保留的位图向屏幕永远是BitBlt(如果全用CompatibleBitmap的话,这是GDI中最快的贴图操作),绝不要用StretchBlt。这个位图之所以要保留,就是为了在那些不需要改变缩放比的操作中快速显示用,比如窗口被遮挡过后的WM_PAINT,或者拉橡皮筋,或者用户抓住卷屏杠甚至图像本身快速拖拽,等等等等。这样,你的这些操作,速度会快很多(比StretchBlt快不少),而仅仅是缩放操作本身会慢一些。不过,从界面设计来说,缩放操作慢一点是可以接受的,因为:1、缩放是一个单独的操作,而不是某项操作的附带操作(比如画橡皮筋时的附带重画);2、这个操作不是连续性的,用户按一下缩放,就会在那里等待结果,而不像拖拽平移操作一样是鼠标一直按下一直移动,因此缩放所要求的实时性并不强。实际上,也很少有软件会支持拖拽式的实时缩放,除非它要显示的东西很简单,计算和绘制方面速度完全跟得上。

当然,如果你完全重画一次所花的时间实在太长,而你的软件操作过程中又确实需要频繁地缩放的话,那么可以再折中一下:
1、对于非缩放操作,还是用Bitblt(区分缩放与非缩放操作还是必要的,因为用Bitblt代替StretchBlt会大大加快你的非缩放操作)。
2、对于缩放操作,如果缩放比与预留位图的缩放比差距不大的话(比如原来是60%显示,现在要求80%显示),用StretchBlt。如果差距太大(比如现在要求150%),那么还是按新的缩放比重绘保留位图,再BitBlt。至于什么程度算“差距太大”,这看你自己觉得什么样的效果可以忍受了。

3调色板颜色解析
typedef struct tagBITMAPINFO {  
  BITMAPINFOHEADER bmiHeader;  
  RGBQUAD bmiColors[1];  
} BITMAPINFO, *PBITMAPINFO;  
这里的bmiColors就是调色板。对于不同位数的位图,调色板的入口数是不同的,8位以下是(2^位数)个入口,24位没有调色板,16位和32位根据不同情况可能没有或者有3个。因此,在定义结构的时候没有办法明确指定bmiColors这个数组的大小。既然没办法事先确定数组大小,看起来这里似乎不应该用数组,而是应该用个RGBQUAD的指针更合理些,但实际上,如果用指针的话,那就需要另开辟一块内存来存放调色板,而且更重要的是:这块内存跟BITMAPINFO本身所占用的内存不见得连在一起。这会带来不少的麻烦。为了保证调色板所占的内存与BITMAPINFO的内存完全连在一起,微软用了点小技巧,就是用一个数组而不是指针来定义这个bmiColors。此时数组大小并不是我们所关心的问题(因为它是无法事先确定的),所以用个1就行,这样就可以保证bmiColors的存储空间会紧跟在bmiHeader之后。

正因为这个bmiColors数组的大小是随便写的,因此,除非是没有调色板数据的位图,否则在使用BITMAPINFO的时候,是不可能直接定义变量的(那样你将没地方存调色板数据),而只能动态分配:
C/C++ code
BITMAPINFO *pbmi = (BITMAPINFO *)(new BYTE [sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 调色板入口数]);

对于8位以下的位图,位图数据实际上是调色板的索引。例如对于1位位图,一共只有0和1两个取值,至于0和1分别是什么颜色,那看你把bmiColors[0]和bmiColors[1]分别设成什么颜色了。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值