GDI 汇编:
Windows是一个基于图形的操作系统,它的图形功能是通过一个称为GDI图形子系统来实现的。通过使用GDI,你可以向系统中的任何设备上画图比如:显示器、系统内存、打印机等等,因为GDI为我们作了很多幕后工作。
不幸的是,GDI在有些时候不能满足你性能上的要求,特别是游戏,对于图形功能要求是很高的,同时对于速度的要求也是很苛刻的,这就是DrectX出现的原因。我们首先来学习一下GDI,因为即使你要用DirectX,一些GDI知识也是必备的。
矩形和点:
在我们介绍GDI对象之前,我们先来看看两个非常有用的结构---点和矩形---以及作用于他们上的功能函数。矩形在DirectX中也是经常用的,在以后的学习过程中你将会发现这一点。
我们首先来看这两种结构的定义:
typedef struct tagPOINT {
LONG x;
LONG y;
} POINT, *PPOINT;
typedef struct _RECT {
LONG left;
LONG top;
LONG right;
LONG bottom;
} RECT, *PRECT;
矩形是由四个点来描述的:(左,上),(右,上),(左,下),(右,下)。在这里需要注意的是矩形的右和下两条边时不属于矩形的。
用于操作矩形和点的函数:
我把这些函数分为三类:第一类是用于创建或者赋值的,第二类是用于操作的,第三类是用于测试的。
函数名称 类型 作用
SetRect 创建赋值 用于设置RECT的成员变量
SetRectEmpty 同上 用于将RECT的所有成员置0的
CopyRect 同上 用于将一个矩形的成员值赋给另外一个矩形的
IntersectRect 操作 用于找到两个矩形的交集
UnionRect 同上 用于创建同时包含两个矩形的最小矩形
OffsetRect 同上 用于移动一个矩形
EqualRect 测试 测试两个矩形的成员是否相等
IsRectEmpty 同上 测试一个矩形的成员是否都为0
PtInRect 同上 测试一个点是否位于一个矩形之内
关于这些函数的具体定义请大家参阅MSDN.
窗口点滴:
我们都很清楚,一个窗口所包含的不只是我们能够绘画的区域.根据用处的不同,一个窗口包含最大化,最小化按钮,标题栏,一个关闭按钮,一个系统菜单.滚动条等等.所包含的元素取决于你所创建的窗口样式.不过这些都不用我们担心,系统会为我们很好的绘制他们,我们所要关心的就是在客户区如何把自己的东西绘制好.
正如上面所提到的,我们可以自由绘制的区域是"客户区".除客户区以外的任何区域都属于非客户区.任何时候我们想重新绘制客户区的话都可以通过发送WM_PAINT消息来实现.
GetClientRect()
这个函数用于得到客户区的大小,并把它保存到一个矩形里面.
GetWindowRect()
这个函数用于得到整个窗口的大小,并把结果村到一个矩形里面.
以上两个函数的具体定义请参阅MSDN.
AdjustWindowRect() & AdjustWindowRectEx()
这两个函数用于让客户区适应窗口的大小,使用哪个取决于你创建窗口时使用的是:CreateWindow()还是CreateWindowEx()
设备环境:
Windows可以向多种设备上绘制图形,比如显示器,打印机等.这是通过使用设备环境来实现的.设备环境是对具体设备的抽象.设备环境为我们屏蔽了诸多硬件的不同,让我们以同一种方式向所有的设备上绘制图形.项具体设备上转化的工作就交给了设备环境. 这就是所谓的设备无关性.
当然,设备无关性是有代价的.使用设备环境要比直接跟硬件交互慢得多,因为绘制命令要经过好几层才能到达所要求的硬件.这个问题解决的方案就是DirectX,它提供了一种更为底层的操作硬件的方式(DirectX直接跟设备驱动程序交互).
下面是有关设备环境的函数:
GetDC() 这个函数为特定的窗口向系统"借"设备环境,大家记清楚了,是借,要还的!
ReleaseDC() 这个函数是将设备环境还给系统.
以上函数的具体定义请参阅MSDN.
难道所有的设备环境都必须跟窗口相关吗?答案是否定的,内存设备环境将是这个问题的最好的例子:
内存设备环境:本质上就是一块内存,但你可以象是对待设备一样对待这块内存.大家可能会问这有什么用处呢?这当然有用处,大多数游戏都是使用双缓冲以消除闪烁,为显示器创建一块内存设备环境就能消除闪烁.在以后的学习中我们会逐步介绍.
相关函数:
CreateCompatibleDC() 创建内存环境,实质上就是申请一块内存
DeleteDC() 释放内存
对于这块刚刚申请的内存环境里面有什么呢?如果你非要问个究竟的话那我只能告诉你:里面只有一个1*1的单色位图.1*1单色位图有什么用处呢?答案是为了内存设备环境的存在,因为空的设备环境是没有办法存活的.
GDI对象
一提到对象大家马上就想到了OO,但是这些GDI对象跟现在的面向对象里面的对象是有区别的,当然也有相同的地方。我
们是通过句柄来操作对象的。
有五种GDI对象是需要我们学习的,他们分别是:HBITMAP,HBRUSH,HPEN,HFONT,HRGN,以下就是对他们的详细介绍:
.HBITMAP 位图对象 它是由一个二维的图像组成的,在Windows中,它通常是利用一个位图创建的。
.HBURSH 画刷对象 可以把它认为是一种沾满油漆的刷子,那么它的作用也就很显而易见了,就是把设备环境的某个特定区域刷成特定的颜色。
.HPEN 画笔对象 是具有一定颜色跟风格的画笔,它主要是用来绘制图元(线,矩形,椭圆等)
.HFONT 字体对象 可以认为它是具有各种风格的字符集,用来向设备环境上绘制文本。
.HRGN 区域对象 是可以被修剪,填充的一个具有某种形状的区域,可以使矩形,椭圆,多边形,或者你能想到的任何形状。
在一个特定时刻里一个设备环境里各种GDI对象只能存在一个实例,也就是说,在一个时刻里,一个设备环境里面只能有一个位图,一枝画笔,一个画刷,一种字体,一种特定区域形状。可以把它的工作用以下场景来描述:一个用于绘制图形的设备(设备环境),它首先需要一张纸(位图对象)来绘制图形,一枝笔(画笔对象)来画,一个刷子(画刷对象)来填充背景,一种字体(字体对象)来写字,以及一种风格(区域对象)来画图.
CreatePen() 用于创建画笔对象
CreateSolidBrush() 用于创建画刷对象
CreateBitmap() 用于创建位图对象
CreateFont 用于创建字体对象
DeleteObject() 销毁所创建的对象
SelectObject() 用于将指定的对象选入设备环境
切记:
从技术角度上来看,Windows为每个应用程序维护一个特定的内存区域,当这个应用程序结束时Windows会自动释放该应用程序所占有的内存区域,我们似乎不用把创建的对象删除掉,但是当这个程序要运行很长时间,并且不断向系统申请内存的时候我们就该傻眼了,因为我们的程序就会狂吃内存,这是我们所不愿见看到的,所以,作为一个良好的编程习惯,一定把自己所申请的内存给释放掉.
关于像素:
我们的显示器是由一个个点组成的,其中的一个点就是一个像素.这是我们所能操作的最小单元,当然点越多越小时我们就感觉越清晰.在我工作的大部分时间里我的显示器的分辨率都是1028*768,这意味着我在我的显示器上有超过750,000个点。除了显示器的宽和高你有一个颜色深度是你必须关心的,即一个像素的颜色是用几个比特来表示的,如果是一个比特,很显然只能够表示两种颜色,也就是单色显示器。最常用的是8,16,24和32比特颜色深度。8位色又称为时索引色,它涉及到调色板的相关知识,这里我就不详细介绍了,相关的介绍已经很多了。
对于16,24,32位色,它们都是通过RGB三原色来表示一个像素的颜色的----红,绿,蓝。
每个像素的表示方式在DirectX里特别关心的,在GDI里,我们可以把一切都看成是24比特的,系统会为我们作相应的转换工作的。
在GDI里,每种颜色都是由COLORREF来表示的。COLORREF在本质上就是一个整数。共24比特,每种颜色用8比特来表示。
下面是RGB宏定义:
#define RGB(r,g,b)
((COLORREF)(((BYTE)(r)|((WORD)((BYTE)(g))<<8))|(((DWORD)(BYTE)(b))<<16)))
有关像素的操作:
SetPixel() 将特定像素变成特定颜色
SetPixelV() 功能同上,但返回值不同
GetPixel() 返回指定像素的颜色值
以上函数详见MSDN
将这段代码防盗消息处理函数里面:
case WM_MOUSEMOVE
{
if(wParam & MK_LBUTTON)
{
int x=LOWORD(lParam);
int y=HIWORD(lParam);
HDC hdc=GetDC(hWindow);
SetPixelV(hdc,x,y,RGB(255,255,255));
ReleaseDC(hWindow,hdc);
}
return 0;
}
break;
你将看到什么呢?哈哈,这就是我们画图软件的雏形,鼠标所到之处都会留下痕迹,这也是让我们感到兴奋的地方.当然这里面有太多的不足,可别忘记了,这才刚刚开始啊!!
使用画笔:
在前面我们完成了一个画图软件的雏形,我们看到了能够操作像素给我们带来的快乐,但是如果我们只能够操作像素的话那就很不爽了,试想在一个场景多变的游戏里面,如果我们来一个像素一个像素的来处理的话那么游戏编程绝对是件痛苦的事情。好在我们有画笔,画刷,以及后面将要讲到的高级工具。
前面我们已经提到过关于画笔的创建工作,在这里我们再稍微提及一下:
CreatePen() 用于画笔的创建,它的参数决定了它的风格
详见MSDN
图形绘制函数:
在介绍图形绘制函数之前,我们先来讨论一下设备环境中的当前位置。设备环境本身维护着一个当前位置,在绘制的过程中它记录着你所走过的轨迹(点)。你可以通过以下函数来修改这个当前位置(CP):
MoveToEx() 用于设定当前位置CP
GetCurrentPosition() 用于获得当前位置CP
详见MSDN
现在我们已经可以改变和得到当前位置CP,但仅仅能做这些事是远远不够的。
LineTo() 用于画一条从CP开始到指定位置的直线
我们来看下面的代码片断:
case WM_LBUTTONDOWN
{
int x=LOWORD(lParam);
int y=HIWORD(lParam);
HDC hdc=GetDC(hWindow);
MoveToEx(hdc,x,y,NULL); //修改当前位置CP
ReleaseDC(hWindow,hdc);
return 0;
}
break;
在上面这段代码里面我们是根据鼠标左击的位置来设定设备的当前位置CP,接着看这段代码:
case WM_MOUSEMOVE
{
if(wParam & MK_LBUTTON)
{
int x=LOWORD(lParam);
int y=HIWORD(lParam);
HDC hdc=GetDC(hWindow);
LineTo(hdc,x,y)
ReleaseDC(hWindow,hdc);
}
return 0;
}
break;
在我们按下鼠标左键的时候,我们用鼠标的当前位置设置了CP,当鼠标移动的时候我们时刻记录着光标的当前位置并CP向这个位置画一条直线。
使用画刷:
在GDI里,我们用画笔对象来绘制我们想要得的图形,如果我们要想填充的话就要依靠画刷了,有很多创建画刷的函数,在这里我给大家介绍两个最常用的,其它的大家可以参阅MSDN作更为详细的了解:
CreateSolidBrush() 用于创建纯色的画刷
CreateHatchBrush() 用于创建具有特定风格的画刷
填充矩形区域:
FillRect() 用于填充特定的矩形区域
一起使用画笔跟画刷,图元的绘制:
现在我们可以使用画笔来画线,这对于我们来说是个好消息,能够用画刷来填充一个图形看起来也挺酷的。我们也需要功能更为强大的API以便来实现复杂图形的绘制,比如说圆,矩形,多边形等,幸运的是GDI为我们提供了相关的API。这里需要提出的是,GDI是使用默认的画笔来绘制这些图元的,也使用默认的画刷来填充这些图形的。
下面是这些图元的绘制函数:
Ellipse() 绘制椭圆
Rectangle() 绘制矩形
RoundRect() 绘制圆角矩形
Polygon() 绘制多边形
在这里我想重点说一下多边形的绘制,它是通过将多边形的各个点存储在一个点数组里来实现,然后依次连接这些点就实现了多边形的绘制。
我们还可以改变多边形的填风格:
SetPolyFillMode() 设置多边形的填充风格
GetPolyFillMode() 返回多边形的填充风格
以上API的详细含义及定义请参阅MSDN
总结:在这一章里面我们粗略的学习了GDI的几个方面,坦率地说,这些东西我们都不太经常用,但最好是了解这些,就像本章的主题“千里之行,始于足下”,在学会飞行之前我们要先会走路,如果我们连走路也不会,那我们要先学会爬行。