小结:主要的思想是,如果要显示图形,直接把该图形当作一个document,在改写document的时候,实际上就是改写这个位图。然后在View中显示的时候,直接把这个位图显示出来就可以了。这需要两个DC,一个DC是台面上的显示用的,一个DC是在内存中绘图用的。两者的关联点就在于这个bitmap对象。
需要明确几个概念,由于
MFC
的类封装了
windows
对象,所以由
MFC
类创建出来的对象就叫做“实例”,以与
windows
“对象”区分开来。
document/view
的经典办法是将图形的数据存储在
document
类里面,
view
类只是根据这些数据绘图。比如你要画个圆,只是将圆心和半径存在
document
里面,
view
类根据这个里面的数据在屏幕上面重新绘制。那么,我们只需要随机产生一次数据就可以了。
这样还是存在性能的问题,于是我们开始考虑另外的解决方法。我们知道,将内存中的图片原样输出到屏幕是很快的,这也是我们在 dos 时代经常做的事情,能不能在 windows 也重新利用呢?答案就是内存缓冲绘图,我们今天的主题。
我们还是回到 DC 上来,既然 DC 是绘图对象,我们也就可以自己来在内存里面造一个,让它等于我们想要的图,图( CBitmap )可以存储在 document 类里面,每一次刷新屏幕都只是将这个图输出到屏幕上面,每一次作图都是在内存里面绘制,保存在 document 的图里面,必要时还可以将图输出到外存保存。这样既保证了速度,也解决了随机的问题,在复杂作图的情况下对内存的开销也不大(总是一副图片的大小)。这是一个很好的解决办法,现在让我们来实现它们。
我们在 document 类里面保存一个图片
CBitmap m_bmpBuf;// 这里面保存了我们做的图,存在于内存中
在 view 类里面我们需要将这个图拷贝到屏幕上去
位于 OnDraw(CDC *pDC) 函数中:
这样还是存在性能的问题,于是我们开始考虑另外的解决方法。我们知道,将内存中的图片原样输出到屏幕是很快的,这也是我们在 dos 时代经常做的事情,能不能在 windows 也重新利用呢?答案就是内存缓冲绘图,我们今天的主题。
我们还是回到 DC 上来,既然 DC 是绘图对象,我们也就可以自己来在内存里面造一个,让它等于我们想要的图,图( CBitmap )可以存储在 document 类里面,每一次刷新屏幕都只是将这个图输出到屏幕上面,每一次作图都是在内存里面绘制,保存在 document 的图里面,必要时还可以将图输出到外存保存。这样既保证了速度,也解决了随机的问题,在复杂作图的情况下对内存的开销也不大(总是一副图片的大小)。这是一个很好的解决办法,现在让我们来实现它们。
我们在 document 类里面保存一个图片
CBitmap m_bmpBuf;// 这里面保存了我们做的图,存在于内存中
在 view 类里面我们需要将这个图拷贝到屏幕上去
位于 OnDraw(CDC *pDC) 函数中:
下面这一部分就是
CView
负责的工作,仅仅需要的是显示该
bitmap
|
CDC dcMem;//
以下是输出位图的标准操作
CBitmap *pOldBitmap = NULL;
dcMem.CreateCompatibleDC(NULL);
pOldBitmap = dcMem.SelectObject(&pDoc->m_bmpBuf);
BITMAP bmpinfo;
pDoc->m_bmpBuf.GetBitmap(&bmpinfo);
pDC->BitBlt(0,0,bmpinfo.bmWidth,bmpinfo.bmHeight,&dcMem,0,0,SRCCOPY);
dcMem.SelectObject(pOldBitmap);
dcMem.DeleteDC();
在我们需要画图的函数里面,我们完成绘图工作
下面就是设置 Doc 了
CBmpDrawDoc *pDoc = GetDocument(); // 得到 document 中的 bitmap 对象
CDC *pDC = GetDC();// 这个 GetDC 将得到当前客户区的 DC ,也就是说,现在 windows 的 DC 对象已经创建了,并且由 pDC 同一负责
CDC dcMem;// 前面已经讲过,用 CDC 创建 DC 实例,不会创建任何的 windows DC 对象。所以这个 dcMem 现在还没有任何的 windows 对象句柄
dcMem.CreateCompatibleDC(NULL);// 随后建立与屏幕显示兼容的内存显示设备。这个时候 dcMem 就有了 windows 对象的句柄了
注意此时 m_bmpBuf 还没有跟任何 windows 对象相关联。
CBitmap *pOldBitmap = NULL;
dcMem.CreateCompatibleDC(NULL);
pOldBitmap = dcMem.SelectObject(&pDoc->m_bmpBuf);
BITMAP bmpinfo;
pDoc->m_bmpBuf.GetBitmap(&bmpinfo);
pDC->BitBlt(0,0,bmpinfo.bmWidth,bmpinfo.bmHeight,&dcMem,0,0,SRCCOPY);
dcMem.SelectObject(pOldBitmap);
dcMem.DeleteDC();
在我们需要画图的函数里面,我们完成绘图工作
下面就是设置 Doc 了
CBmpDrawDoc *pDoc = GetDocument(); // 得到 document 中的 bitmap 对象
CDC *pDC = GetDC();// 这个 GetDC 将得到当前客户区的 DC ,也就是说,现在 windows 的 DC 对象已经创建了,并且由 pDC 同一负责
CDC dcMem;// 前面已经讲过,用 CDC 创建 DC 实例,不会创建任何的 windows DC 对象。所以这个 dcMem 现在还没有任何的 windows 对象句柄
dcMem.CreateCompatibleDC(NULL);// 随后建立与屏幕显示兼容的内存显示设备。这个时候 dcMem 就有了 windows 对象的句柄了
注意此时 m_bmpBuf 还没有跟任何 windows 对象相关联。
pDoc->m_bmpBuf.DeleteObject();
pDoc->m_bmpBuf.CreateCompatibleBitmap(pDC,100,100);//
依附
DC
创建
bitmap
执行了上面这句话之后,
bitmap
实例就依附于一个
windows
的
bitmap
对象了。那么为什么要用
pDC
作为参数,而不用
&dcMem
呢?下面来看看
MSDN
关于该函数的解释:
CBitmap::CreateCompatibleBitmap
BOOL
CreateCompatibleBitmap( CDC* pDC, int nWidth, int nHeight );
Return Value
Nonzero if successful; otherwise 0.
Parameters
pDC
Specifies the device context.
nWidth
Specifies the width (in pixels) of the bitmap.
nHeight
Specifies the height (in pixels) of the bitmap.
Remarks
Initializes a bitmap that is compatible with the device specified by pDC. The bitmap has the same number of color planes or the same bits-per-pixel format as the specified device context. It can be selected as the current bitmap for any memory device that is compatible with the one specified by pDC.
If pDC is a memory device context, the bitmap returned has the same format as the currently selected bitmap in that device context. A "memory device context" is a block of memory that represents a display surface. It can be used to prepare images in memory before copying them to the actual display surface of the compatible device.
When a memory device context is created, GDI automatically selects a monochrome stock bitmap for it.
Since a color memory device context can have either color or monochrome bitmaps selected, the format of the bitmap returned by the CreateCompatibleBitmap function is not always the same; however, the format of a compatible bitmap for a nonmemory device context is always in the format of the device.
When you finish with the CBitmap object created with the CreateCompatibleBitmap function, first select the bitmap out of the device context, then delete the CBitmap object.
|
也就是说,我们现在需要的是把我们的
bitmap
显示在屏幕上,因此需要和现在的客户区的
DC
(也就是
Pdc
来负责的)关联起来,这样
bitmap
的一些色彩属性才会和
pDC
的一样。但是内存
DC
的色彩没有那么多,这样的话,绘制这个
bitmap
实际上只有几种颜色了。可以作个实验,如果这里改成
&menDC
,那么绘出来的方块就是黑色的,而用
pDC
就是红色的,也是我们设想的。
CBitmap *pOldBitmap = dcMem.SelectObject(&pDoc->m_bmpBuf);// 调入了我们 bitmap 对象
dcMem.FillSolidRect(0,0,100,100,RGB(255,255,255));// 这些时绘图操作,随便你 ^_^
dcMem.TextOut(0,0,"Hello,world!");
dcMem.Rectangle(20,20,40,40);
dcMem.FillSolidRect(40,40,50,50,RGB(255,0,0));
pDC->BitBlt(0,0,100,100,&dcMem,0,0,SRCCOPY);// 第一次拷贝到屏幕
dcMem.SelectObject(pOldBitmap);
dcMem.DeleteDC();
全部的过程就是这样,很简单吧。以此为例子还可以实现 2 个缓冲或者多个缓冲等等,视具体情况而定。当然在缓冲区还可以实现很多高级的图形操作,比如透明,合成等等,取决于具体的算法,需要对内存直接操作(其实就是当年 dos 怎么做,现在还怎么做)。
再来解释一下前面说的为什么不能用全局变量保存 DC 问题。其实 DC 也是用句柄来标识的,所以也具有句柄的不确定性,就是只能随用随取,不同时间两次取得的是不同的(使用过文件句柄地话,应该很容易理解的)。那么我们用全局变量保存的 DC 就没什么意义了,下次使用只是什么也画不出来。(这一点的理解可以这样: DC 需要占用一定的内存,那么在频繁的页面调度中,位置难免改变,于是用来标志指针的句柄也就不同了)。
CBitmap *pOldBitmap = dcMem.SelectObject(&pDoc->m_bmpBuf);// 调入了我们 bitmap 对象
dcMem.FillSolidRect(0,0,100,100,RGB(255,255,255));// 这些时绘图操作,随便你 ^_^
dcMem.TextOut(0,0,"Hello,world!");
dcMem.Rectangle(20,20,40,40);
dcMem.FillSolidRect(40,40,50,50,RGB(255,0,0));
pDC->BitBlt(0,0,100,100,&dcMem,0,0,SRCCOPY);// 第一次拷贝到屏幕
dcMem.SelectObject(pOldBitmap);
dcMem.DeleteDC();
全部的过程就是这样,很简单吧。以此为例子还可以实现 2 个缓冲或者多个缓冲等等,视具体情况而定。当然在缓冲区还可以实现很多高级的图形操作,比如透明,合成等等,取决于具体的算法,需要对内存直接操作(其实就是当年 dos 怎么做,现在还怎么做)。
再来解释一下前面说的为什么不能用全局变量保存 DC 问题。其实 DC 也是用句柄来标识的,所以也具有句柄的不确定性,就是只能随用随取,不同时间两次取得的是不同的(使用过文件句柄地话,应该很容易理解的)。那么我们用全局变量保存的 DC 就没什么意义了,下次使用只是什么也画不出来。(这一点的理解可以这样: DC 需要占用一定的内存,那么在频繁的页面调度中,位置难免改变,于是用来标志指针的句柄也就不同了)。