转自:http://blog.sina.com.cn/s/blog_6ccd0a1101012dy4.html
一、首先介绍Windows图像程序设计中几个重要的概念:
GDI(Graphics Device Interface,图形设备接口):这是Windows API的一个库。当Windows应用程序需要显示点、线、图像、文字等内容,在显示器或打印输入这些内容时,就需要用到GDI。Windows应用程序不能直接操作系统的硬件(比如显卡),GDI就为应用程序提供了相关的接口。
其相关的函数接口、数据类型等都在WinGDI.h中声明(已经由Windows.h引入),在程序开发时,需要链接到Gdi32.lib。
DC(Device Contexts,设备上下文):是GDI库中最基本也是最重要的概念。DC是一个对象,设定了图形输出的特性和属性。
系统中可以有多个DC,每一个DC都必须关联到一个特定的图像输出设备。这些设备可以是真实存在的物理设备(显示器、打印机、绘图仪等),也可以使虚拟设备。这些反应在DC的类型上,DC具有4种类型:“显示”、“打印机”、“内存”、“信息”。
其中显示类型DC是最常用的,它被关联到了显示设备上,所有的图像输出操作将直接反映在显示器上。
注:DC也可以只是设备全部输出范围的一部分。比如界面上某个窗口的客户区也可以有DC与之对应,对这样的DC进行操作只会影响到窗口客户区。
如果要将图像输出到特定的设备只需要创建相应类型的DC即可(注:对不同类型DC的操作是统一的),我们只关注获取显示器相关DC的操作:(以下的函数都是GDI库中的接口函数)
获取DC - GetDC(HWND hWnd):
调用该函数会返回hWnd参数所指定的窗口的客户区所对应的DC的句柄。
如果hWnd参数设置为NULL,那么函数会返回整个桌面的DC。
另一种获取DC的方法 - CreateDC
该函数也是用来获取DC句柄,与GetDC不同的是,CreateDC可以获取非显示器输出DC。只需指定不同的参数即可。
获取显示器的DC:HDC hScreenDC = CreateDC(“DISPLAY”, NULL, NULL, NULL);
若想获取打印机的DC,一般是将第一个参数改为"WINSPOOL"。
释放DC - ReleaseDC(HWND hWnd, HDC hDC)
该函数的作用是释放DC,使其他应用程序可以使用。
释放DC的相关系统资源 - DeleteDC(HDC hDC)
这个函数并不常用,一般做图结束后,我们只需要调用ReleaseDC释放DC即可。
也许看到这里,对DC具体是个什么东西还没有什么清楚的概念。下面大概讲一下。。。
DC实际上是包含了一系列的图形对象,比如位图(Bitmap)、画刷(Brushe)、画笔(Pen)、字体(Font)、逻辑调色板(Logical palette)等等,GDI库中还定义了一系列接口函数,应用程序通过调用这些接口函数来操作当前DC中的图形对象,完成期望的绘图操作,最终影响放映到对应设备的输出上。
比如先创建一个窗口,然后得到该窗口对应的DC,这时候系统会为DC创建默认的图形对象(位图和路径除外),此时如果不进行任何操作,那么显示的窗口是一片白(也就是说刚开始是一块白色画布)。然后我们可以调用GDI库中的接口函数进行画图操作,或者载入位图(相当于在白色画布上作画)。DC中就是提供了对画布进行作画的工具。
注:DC对应设备的显示信息都存储在位图对象中。
为了更清楚DC的作用,下面举个很简单的例子:在屏幕上画一条线
首先,获得整个显示器的DC:
HDC hdC = GetDC(NULL);
创建新的画笔对象:
COLORREF cPen = RGB(0,0,0); //指定画笔颜色为黑色
HPEN hpen = CreatePen(PS_SOLID, 10, cPen); //创建新的画笔,返回新画笔的句柄
将新创建的画笔指定为DC的当前画笔:(对同一种类型的图形对象,DC中只能有一个当前对象)
HPEN hpenOld = SelectObject(hdc, hpen);
画线:
LineTO(hdc, 500, 500);
画图操作结束,还原画笔:
SelectObject(hdc, hpenOld);
释放画笔资源:
DeleteObject(hpen);
释放DC:
ReleaseDC(NULL, hdc);
二、下面来讨论使用OpenGL来绘图的相关知识:
使用OpenGL绘图与使用GDI库绘图是不同的,主要体现在:OpenGL采用的是RC(Render Context,渲染上下文)绘图。
DC和RC的区别和联系:
下面来讲一下RC的创建。正如前面所说的,RC只能通过建立了位图格式的DC来创建。
**首先,我们需要创建一个窗口,然后使用GetDC函数得到这个窗口的DC。**我们这里讨论在MFC使用OpenGL,因此窗口类是由工程自动创建的COpenglDemoView类,由于我们做的是绘图前的窗口初始化工作,因此我们将之后所有的操作都在OnCreate函数中完成。(至于一些必要的成员数据初始化工作,应放在View类的构造函数中)
HWND hWnd = this->GetSafeHwnd();
HDC hDC = ::GetDC(hWnd);
**然后,我们需要指定DC中的位图像素格式。**这个工作是由GDI库中名为PIXELFORMATDESCRIPTOR的类来实现的。
第一步是填充这个数据结构:
PIXELFORMATDESCRIPTOR
pixelDesc={
sizeof(PIXELFORMATDESCRIPTOR),
//nsize:像素格式描述子结构的大小
1, //nVersion:PIXELFORMATDESCRIPTOR结构的版本,一般设为1
//dwFlags:一组表明像素缓冲特性的标志位
PFD_DRAW_TO_WINDOW | //使之能在窗口或者其他设备窗口画图
PFD_SUPPORT_OPENGL,// |
//使之能使用OpenGL函数
//PFD_DOUBLEBUFFER, //指明使用了双缓冲
PFD_TYPE_RGBA, //PixelType:定义了显示颜色的方法
24, //cColorBits:指定了一个颜色的位数
0,0, //cRedBits,
cRedShift:每个RGBA颜色缓冲区中红色位平面的数目和偏移数
0,0, //cGreenBits,
cGreenShift:每个RGBA颜色缓冲区中绿色位平面的数目和偏移数
0,0, //cBlueBits,
cBlutShift:每个RGBA颜色缓冲区中蓝色位平面的数目和偏移数
0,0, //cAlphaBits,
cAlphaShift:每个RGBA颜色缓冲区中Alpah位平面的数目和偏移数
0, //cAccumBits:累加缓冲区中全部位平面的数目
0,0,0,0, //cAccumRedBits, cAccumGreenBits, cAccumBlueBits,
cAccumAlphaBits
32, //cDepthBits:Z(深度)缓冲区的深度
0, //cStencilBits:模板缓冲区的深度
0, //cAuxBuffers:轴向缓冲区的数量(一般1.0版本不支持)
PFD_MAIN_PLANE, //iLayerType:忽略,为了一致性而包含的
0, //bReserved:表层和底层平面的数量
0,0,0 //dwLayerMask, dwVisibleMask, dwDamageMask
};
这个类中的大部分成员变量我们并不关心,最重要的是第三个参数dwFlags,其中指定的PFD_SUPPORT_OPENGL使得我们可以在这个窗口中使用OpenGL函数。
而指定的PFD_DOUBLEBUFFER则使得我们可以使用OpenGL中的双缓存机制(这个机制在制作动画时非常重要)。
第二步是将这个像素格式选为当前DC的使用像素格式:
this->m_GLPixelIndex = ChoosePixelFormat(hDC, &pixelDesc);
if(this->m_GLPixelIndex==0)
{
this->m_GLPixelIndex = 1;
if(DescribePixelFormat(hDC,this->m_GLPixelIndex,sizeof(PIXELFORMATDESCRIPTOR), &pixelDesc) == 0)
{
return false;
}
}
if(SetPixelFormat(hDC, this->m_GLPixelIndex, &pixelDesc) == false)
{
return false;
}
这段代码首先调用了ChoosePixelFormat函数来寻找OpenGL所支持的像素格式中,最接近所设置的像素格式,如果没有找到,就调用DescribePixelFormat函数来选择索引值为1的像素格式来填充设置的像素格式。这些操作完成后,保证了所设置的像素格式是OpenGL所支持的。
最后就可以调用SetPixelFormat函数来为指定当前DC的像素格式。
注:代码中的this->m_GLPixelIndex是我们自己添加的View类的protected型的成员变量,类型为int,用于保存所设置的像素格式在OpenGL所支持的像素格式列表中的索引值。
首先创建RC:
this->m_hGLContext = wglCreateContext(hDC);
**若创建RC失败,**通过GetLastError得到错误代码是2000,那么创建的像素格式是无效的,检测是否调用SetPixelFormat
然后选择新创建的RC成为当前DC对应的RC
wglMakeCurrent(hDC, this->m_hGLContext);
注:代码中的this->m_hGLContext是我们自己添加的View类的protected型的成员变量,类型为HGLRC,用于保存当前RC的句柄。
这样,RC就创建成功了。
RC创建成功后,我们就可以在View窗口中执行绘图操作了。其中,视窗、投影方式等设置放在OnSize函数中,而绘制操作则放在OnPaint函数中。
这里需要注意的是,MFC中没有提供类似GLUT中glutIdleFunc()的函数,而OnPaint函数只会在窗口创建或者窗口需要重绘的时候才会被调用。因此OnPaint函数中的绘图操作一般都是静态的,如果想绘制动画,则需要程序员自己写定时函数来控制窗口的重绘。
4.程序结束要释放资源,如果忘记释放,将会导致内存泄露,随着程序运行次数的增多,内存占有量越来越大。这里在WM_DESTORY的消息响应函数中释放资源:
// when the rendering context is no longer needed ...
// make the rendering context not current
wglMakeCurrent (NULL, NULL) ;
// delete the rendering context
wglDeleteContext (hglrc);