今天在学习用内存DC画图,终于有了初步了解。现将收集的关于内存DC介绍及其相关操作的资料贴出来共享一下。
DC 即Device Context,是GDI内部的一个资料结构,一个DC会和某个特定的显示设备(如打印机、屏幕等)产生关联。我们如果能取得该DC的handle 那我们便可以在这显示设备上写字、画图。
在Form 或Picturebox中都有一个hdc的属性,指的便是这东西,但是,怎么又会有一个Memory DC呢?这是一个存在记忆体内的 dc ,它除了不像form picturebox能将图形、文字显示出来之外,其他的几乎都相同,它也可以用在所有的 GDI API 呼叫之上,其实我们在VB中早就有使用上这 Memory DC 了,只是没有自觉。当我们设 form picturebox的AutoRedraw = True时, hdc所指的便是Momoory DC,这时我们在其上作绘图动作,都没有显示在form上,这便是先前说的,它只是在记忆体中,不会真的画出图。而我们下 refresh指令时,便是将这MemoryDC上的图,copy到 form/PictureBox上。
另外我们也可以使用CreateCompatibleDC() API 它传入一个 hDc ,代表产生的 Memory DC和 hdc相容,若传0则是与屏幕相容的 Memory DC hMemDC = CreateCompatibleDC(0)
这时候,该hMemDC所指的绘图区有多大呢?其实只有一个单色Pixel,直到我们使用SelectObject( hMemDC, hBitmap)
那hMemDC显示区就会有和hBitmap一样的宽度、高度、颜色选择等。 而且我们在hMemDC上的任何绘图,也都会反映在 hBitMap上,也就是说,原本hBitMap所指的图,在SelectObject(hMemDC, hBitMap)后,我们使用GDI函式在hMemDC上画一条线,那么该hBitmap所指的图也会有一条线了。
那么Memory DC又有什么作用呢?
我们知道, 在使用VC开发图形相关的应用程序时,常常需要使用MFC的CDC类直接把图形画在窗口上。这通常是通过响应Windows的WM_PAINT消息实现的。如果要画的图形比较复杂,或者比较大,那么画图过程可能会造成窗口的闪烁。当窗口调整大小时,这种闪烁尤为明显。有时候不画图,也会这样做!但不是因为会造成闪烁,是为了达到更重要的目的。
解决窗口闪烁问题的有效办法就是使用内存DC,也称为缓冲DC。在内存中准备一个和窗口DC相同属性的DC,在这个内存DC上执行画图操作。完成画图以后,把画图输出的内容整体复制到目标窗口DC上。因为画图操作不在窗口DC上进行,所以在画图的过程中窗口可以保持原来的内容。当画好的内容被复制到窗口DC时,因为复制操作执行的非常快,所以用户感觉窗口仿佛被立刻被画好,从而消除了从旧画面到白板再到新画面的闪烁现象。
生成内存DC主要用到以下四个函数:
CreateCompatibleDC(CDC* pDC )。CDC类的成员函数,用于创建一个和pDC指向的DC兼容的内存DC。
CreateDiscardableBitmap( CDC* pDC, int nWidth, int nHeight)。CBitmap类的成员函数,用于按指定尺寸创建一个和pDC指向的DC兼容的位图,这种位图是已淘汰的,它具有与设备一样的位/像素格式和颜色调色板。应用程序可以选择这种位图作为与指定设备兼容的内存设备的当前位图。
SelectObject(CBitmap * pBitmap)。CDC类的成员函数,执行以后,所有在该DC上的图像输出都将被画到pBitmap指向的位图上。
BOOL BitBlt (int x, int y, int nWidth, int nHeight, CDC* pSrcDC, int xSrc, int ySrc, DWORD dwRop )。CDC类的成员函数,用于从源DC(pSrcDC)复制一个矩形的图象到当前DC中。该函数对指定的源设备环境区域中的像素进行位块(bit_block)转换,以传送到目标设备环境。
函数BitBlt的参数解释:
x:指定目标矩形区域左上角的X轴逻辑坐标。
y:指定目标矩形区域左上角的Y轴逻辑坐标。
nWidth:指定源在目标矩形区域的逻辑宽度。
nHeight:指定源在目标矩形区域的逻辑高度。
hSrcDC:指向源设备环境的句柄。
xSrc:指定源矩形区域左上角的X轴逻辑坐标。
ySrc:指定源矩形区域左上角的Y轴逻辑坐标。
dwRop:指定光栅操作代码。这些代码将定义源矩形区域的颜色数据,如何与目标矩形区域的颜色数据组合以完成最后的颜色。
下面列出了一些常见的光栅操作代码:
BLACKNESS:表示使用与物理调色板的索引0相关的色彩来填充目标矩形区域,(对缺省的物理调色板而言,该颜色为黑色)。
DSTINVERT:表示使目标矩形区域颜色取反。
MERGECOPY:表示使用布尔型的AND(与)操作符将源矩形区域的颜色与特定模式组合一起。
MERGEPAINT:通过使用布尔型的OR(或)操作符将反向的源矩形区域的颜色与目标矩形区域的颜色合并。
NOTSRCCOPY:将源矩形区域颜色取反,于拷贝到目标矩形区域。
NOTSRCERASE:使用布尔类型的OR(或)操作符组合源和目标矩形区域的颜色值,然后将合成的颜色取反。
PATCOPY:将特定的模式拷贝到目标位图上。
PATPAINT:通过使用布尔OR(或)操作符将源矩形区域取反后的颜色值与特定模式的颜色合并。然后使用OR(或)操作符将该操作的结果与目标矩形区域内的颜色合并。
PATINVERT:通过使用XOR(异或)操作符将源和目标矩形区域内的颜色合并。
SRCAND:通过使用AND(与)操作符来将源和目标矩形区域内的颜色合并。
SRCCOPY:将源矩形区域直接拷贝到目标矩形区域。
SRCERASE:通过使用AND(与)操作符将目标矩形区域颜色取反后与源矩形区域的颜色值合并。
SRCINVERT:通过使用布尔型的XOR(异或)操作符将源和目标矩形区域的颜色合并。
SRCPAINT:通过使用布尔型的OR(或)操作符将源和目标矩形区域的颜色合并。
WHITENESS:使用与物理调色板中索引1有关的颜色填充目标矩形区域。(对于缺省物理调色板来说,这个颜色就是白色)。
如果函数成功,那么返回值非零;如果函数失败,则返回值为零。
Windows NT:若想获取更多错误信息,请调用GetLastError函数。
备注:如果在源设备环境中可以实行旋转或剪切变换,那么函数BitBlt返回一个错误。如果存在其他变换(并且目标设备环境中匹配变换无效),那么目标设备环境中的矩形区域将在需要时进行拉伸、压缩或旋转。
如果源和目标设备环境的颜色格式不匹配,那么BitBlt函数将源场景的颜色格式转换成能与目标格式匹配的格式。当正在记录一个增强型图元文件时,如果源设备环境标识为一个增强型图元文件设备环境,那么会出现错误。如果源和目标设备环境代表不同的设备,那么BitBlt函数返回错误。
好了,目前我所能理解到的原理就是上面的样子,下面来看看具体应该怎样操作吧。
对于一个窗口,我们可以用下面的代码来创建内存DC,在内存DC上输出,并最终复制到窗口DC上。
void PaintWnd(CWnd * pWnd) { CDC * pWndDC = pWnd->GetWindowDC(); CRect WndRect = pWnd->GetWindowRect(); CDC MemDC; CBitMap MemBitmap; MemDC.CreateCompatibleDC(pWndDC); // 创建内存DC MemBitmap.CreateCompatibleBitmap( // 创建兼容的位图 pWndDC, WndRect.Width(), WndRect.Height()); MemDC.SelectObject(MemBitmap); // 让内存DC输出到位图(我的理解就是选择画布) // 使用MemDC画图 …… pWndDC->BitBlt(// 从内存DC复制到窗口DC 0,0, WndRect.Width(), WndRect.Height(), &MemDC, 0,0, SRCCOPY); }
当然,实际的情况下,我们需要考虑的更多,因为内存DC、位图的创建都可能会失败。为了简化代码,此处定义了一个类CMemoryDC,包装了内存DC创建过程中的出错处理,内存DC的事后清理等操作,并自动复制内存DC的内容到目标DC上。
声明CMemoryDC类的头文件MemoryDC.h如下:
#pragma once #include "Afxwin.h" class CMemoryDC { public: CMemoryDC(CDC *dc, RECT * rect,bool autoRender = false); ~CMemoryDC(void); bool IsOK(); void Render(CDC * p_objectDC = NULL); CDC* GetMemoryDC(); operator CDC * (); private: bool m_bAutoRender; CRect m_DCRect; CDC* m_pOriginalDC; CDC m_MemoryDC; CBitmap m_MemoryBmp; };
类的实现文件CMemoryDC.cpp如下:
#include ".MemoryDC.h" CMemoryDC::CMemoryDC(CDC *dc, RECT * rect, bool autoRender) { m_bAutoRender = autoRender; m_pOriginalDC = dc; if (dc==NULL || rect==NULL) return; if (!m_MemoryDC.CreateCompatibleDC(dc)) return; m_DCRect.SetRect(rect->left, rect->top, rect->right, rect->bottom); if (!m_MemoryBmp.CreateCompatibleBitmap(dc, m_DCRect.Width(), m_DCRect.Height())) return; m_MemoryDC.SelectObject(m_MemoryBmp); } CMemoryDC::~CMemoryDC(void) { if (m_bAutoRender) Render(); if (m_MemoryDC.m_hDC!=NULL) m_MemoryDC.DeleteDC(); if (m_MemoryBmp.m_hObject!=NULL) m_MemoryBmp.DeleteObject(); } bool CMemoryDC::IsOK() { return m_MemoryDC.m_hDC!=NULL && m_MemoryBmp.m_hObject != NULL; } void CMemoryDC::Render(CDC * p_objectDC) { if (!IsOK()) return; CDC * pDC = (p_objectDC==NULL ? m_pOriginalDC : p_objectDC); CSize Size = m_MemoryDC.GetViewportExt() ; pDC->BitBlt( m_DCRect.left, m_DCRect.top, m_DCRect.Width(), m_DCRect.Height(), &m_MemoryDC, 0,0, SRCCOPY); } CDC* CMemoryDC::GetMemoryDC() { return & m_MemoryDC; } CMemoryDC::operator CDC * () { return & m_MemoryDC; }
使用这个类可以大大简化内存DC的创建操作。如果我们在窗口消息WM_PAINT的响应函数中使用内存DC,只要用如下这样简便的代码便可实现:
CRect Rect; GetClientRect(Rect); CPaintDC dc(this); // device context for painting CMemoryDC MemDC(&dc, Rect, true); if (MemDC.IsOK()) { // 使用MemDC画窗口 } // MemDC析构时会自动把图像复制到dc,无需其它操作
使用CMemoryDC创建内存DC防止窗口闪烁,编程的代码和不使用内存DC时相比,数量和复杂性几乎没有增加。
下一篇文章进行整理:见VC中的MFC里面来使用内存DC画图(续)