2.1.1 GDI设备环境
如果不使用OpenGL,我们在Windows窗口中进行绘图时一般就要借助GDI(Graphics Device Interface,图形设备接口)来完成。每个窗口都有一个设备环境,用于接收实际图形输出。每个GDI函数都接受一个设备环境作为参数,它决定这个函数的操作将影响哪个窗口。你可以使用多个设备环境,但每个窗口只能使用一个。
下面我们来分析一个简单的GDI程序Rectangle.c,它用来绘制一个普通的蓝色背景的窗口,中间有一个红色的方块。所有的Windows API程序都需按这个步骤来编写:
定义窗口类型―>注册该窗口类型―>创建一个窗口―>启动消息泵―>编写相应的回调函数。
Rectangle.c程序也不例外,其代码如下:
程序的22~33行为定义窗口类型部分;
程序的35~37行为窗口类型注册部分;
程序的39~49行为窗口创建部分;
程序的59~65行为消息泵运行部分。
程序的74~111行为消息响应的处理部分。
第6行声明了两个画刷,正如其名所示,它们将用于后面的填充和绘图。然后,在第19和20行,我们在WinMain函数中先后创建了一个蓝色的和红色的实心画刷。注意,在定义窗口类型部分的第31行,我们指定用刚才定义的那个蓝色画刷去填充窗口的背景。这样,当窗口显示出来时,它的背景是蓝色的。
在第39~49行为窗口创建部分,我们使用Windows API的CreateWindow程序创建窗口并指定窗口的大小和位置。如果使用glut库那么请使用glutPositionWindow和glutReshapeWindow函数,但glut库中窗口管理部分的作用仅仅用于辅助学习,若想在Windows平台上进行实际的开发时,你必须使用Windows API。
最后,窗口的实际绘图是在WndProc函数的第83~103行即WM_PAINT消息处理代码中完成。这段代码中先调用BeginPaint函数,将PAINTSTRUCT结构体变量ps与窗口句柄hWnd关联,从而将ps的hdc成员设置为当前窗口的设备环境句柄(Handle of Device Context,HDC),以便在窗口中绘图。我们需要记住,设备环境句柄是所有GDI绘图函数的第一个参数,用于标识在哪个窗口中进行操作。然后调用GDI函数SelectObject,将当前窗口的设备环境句柄即ps的hdc成员作为其第一个参数,将红色画刷句柄hRedBrush作为其第二个参数,从而选择红色画刷为当前画刷进行绘图操作。此后红色画刷将一直为当前画刷,直到我们再指定新的画刷为止——这个特性与OpenGL中的状态设置完全一样,这就是有限状态机机制。画刷设置完毕后,我们使用GDI函数Rectangle绘制了一个矩形,并用当前画刷颜色填充之,所以该矩形是红色的。绘制完毕后,我们使用SelectObject函数将原来的画刷设置为当前画刷,即恢复为绘图前的环境状态设置,这种用法在OpenGL编程(www.cppentry.com)中也非常重要,而且OpenGL中还有其他方式更方便地保存和还原状态变量的设置。最后调用EndPaint函数结束绘图操作。注意BeginPaint和EndPaint只能用在WM_PAINT消息处理代码中,若在代码的其他地方编写绘图程序,则有许多其他的方法。
据此,我们讲解完了GDI绘图程序的一般原理。你可能会认为OpenGL的工作方式应该与此类似。但是,你应该记住GDI是Windows特有的,其他环境并不存在像设备环境、窗口句柄这类东西,尽管它们的实现思路可能类似。而且,Windows操作系统的设计目标主要是用于实现对2D图形的应用支持,因此,它的设备环境对3D图形处理的支持十分有限。在Windows中,你为一个特定的窗口申请一个设备环境,则该设备环境的本质将取决于当前机器相关硬件设备的本质。如果你的桌面设置为16位的颜色,那么Windows所提供的设备环境就只知道和理解16位颜色。作为程序员,你对GDI设备环境的内在特征无法施加控制。