跟我一起学图形编程
作者:姚明 联系方式:alanvincentmail@gmail.com 2011年1月24日 1:46:14
经过第一课的学习,我们应该了解了,如何在windows中创建一个窗口程序。这节课,让我们用这个窗口做一些有趣的事情,想像一下,在窗口中随机绘制无数个五颜六色的点会是什么样,为了让效果更好,绘制的过程应该是动态的,不应该一撮而就,所以,它应具有动画效果,这意味着要使用时间函数和双缓冲技术。
理论:
我们知道,屏幕上的画面是由像素组成的,这些像素在屏幕上,横纵排列成方阵,通常我们用分辨率来描述方阵,比如:1024X768的分辨率,代表横向上每排有1024个像素,纵向每列上有768个像素,像素个数=1024X768。以此类推,我们就知道640X480,1280X1024代表的意义。因此,分辨率越高,我们所看到的画面就越精细,但这并不代表分辨率越高,画面会越炫丽,因为,我们还不知道,每个像素能表达出多少种颜色,假如,用1bit位表示,它只有两种状态,即0或1来表示像素颜色,那么画面上最多只有2种颜色,2bit最多表示4种颜色,3bit有8种颜色,以此类推8bit、16bit、24bit、32bit。传说中的真彩色就是32bit的,因为它可以表达出2的32次方4X1024X1024X1024种颜色,它包含了自然界的所有颜色。为了简单,我们绘制的每1点就是1个像素,要想显示在窗口中,需要指定它在阵列中的位置(X,Y坐标表示),以及它的颜色。我们计划每1/100秒,绘制100个点,1秒钟生成10000个点,颜色值随机产生。
我们再谈谈双缓冲技术,它是为了解决动画闪烁现象而产生的。想象一下,我们只有一张纸,要想让你在这张纸上看到动画效果,我必须在上面画第一帧,然后擦掉,再画第二帧,再擦掉,再画第三帧,以此类推,如果速度足够快,你就看到动画效果了,但这个过程会产生闪烁。因为我绘制和擦除的过程都在动画中。为了避免闪烁,现在我换个方式,找到两张纸,同样快速的在第一张纸上画第一帧给你看,同时我在后台的第二张纸上画第二帧,画好后,快速的与第一张纸交换,再把第一张纸拿到后台,擦除重绘第三帧,这样你看到的就是连贯的动画,而始终看不到绘制和擦除的动作,于是消除了闪烁。
内容:
1/**//*------------------------------------------------------------------------
2 DOTS.CPP – 在窗口客户区绘制点的动画效果
3
4 (c) 姚明, 2010
5-----------------------------------------------------------------------*/
6#include <windows.h>
7
8#define ID_TIMER 1
9
10LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
11
12int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
13 PSTR szCmdLine, int iCmdShow)
14{
15static TCHAR szAppName[] = TEXT ("dots") ;
16 HWND hwnd ;
17 MSG msg ;
18 WNDCLASS wndclass ;
19 wndclass.style = CS_HREDRAW | CS_VREDRAW ;
20 wndclass.lpfnWndProc = WndProc ;
21 wndclass.cbClsExtra = 0 ;
22 wndclass.cbWndExtra = 0 ;
23 wndclass.hInstance = hInstance ;
24 wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
25 wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
26 wndclass.hbrBackground= (HBRUSH) GetStockObject (BLACK_BRUSH) ;
27 wndclass.lpszMenuName = NULL ;
28 wndclass.lpszClassName= szAppName ;
29 RegisterClass (&wndclass);
30 hwnd = CreateWindow( szAppName, // window class name
31 TEXT ("draw dots"), // window caption
32 WS_OVERLAPPEDWINDOW, // window style
33 CW_USEDEFAULT, // initial x position
34 CW_USEDEFAULT, // initial y position
35 CW_USEDEFAULT, // initial x size
36 CW_USEDEFAULT, // initial y size
37 NULL, // parent window handle
38 NULL, // window menu handle
39 hInstance, // program instance handle
40 NULL) ; // creation parameters
41 ShowWindow (hwnd, iCmdShow) ;
42 UpdateWindow (hwnd) ;
43 while (GetMessage (&msg, NULL, 0, 0))
44 {
45 TranslateMessage (&msg) ;
46 DispatchMessage (&msg) ;
47 }
48 return msg.wParam ;
49}
50LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
51{
52 HDC hdc ;
53 HDC hdcMem; //内存设备句柄
54 PAINTSTRUCT ps ;
55 RECT rect ;
56 int x,y;
57 HBITMAP hBitmap;
58
59 switch (message)
60 {
61 case WM_CREATE:
62 SetTimer (hwnd, ID_TIMER, 10, NULL) ; //创建定时器,每10微妙产生一个WM_TIMER消息
63 return 0 ;
64// case WM_PAINT:
65// hdc = BeginPaint (hwnd, &ps) ;
66// GetClientRect (hwnd, &rect) ;
67// DrawText (hdc, TEXT ("你好, 欢迎使用YM的图形学教程!"), -1, &rect,
68// DT_SINGLELINE | DT_CENTER | DT_VCENTER) ;
69// EndPaint (hwnd, &ps) ;
70// return 0 ;
71 case WM_TIMER:
72 hdc = GetDC (hwnd) ;
73 hdcMem = CreateCompatibleDC(NULL); //创建内存设备环境
74 GetClientRect (hwnd, &rect) ;
75 hBitmap = CreateCompatibleBitmap(hdc,
76 rect.right, rect.bottom); //创建内存设备环境相关的位图
77 SelectObject(hdcMem, hBitmap); //选择位图对象到内存设备环境
78
79 for(int i=0;i<100;i++)
80 {
81 x = rand()%rect.right; //随机产生点的X坐标
82 y = rand()%rect.bottom; //随机产生点的Y坐标
83 COLORREF crColor = RGB(rand()%256,rand()%256,rand()%256); //随机产生点的颜色值
84 SetPixel (hdc, x, y, crColor) ; //在显示设备环境中绘制点
85// SetPixel (hdcMem, x, y, crColor) ;//在内存设备环境中绘制点
86 }
87
88 BitBlt(hdc,0, 0, rect.right, rect.bottom, hdcMem, 0, 0, SRCINVERT); //将内存设备环境中的数据传到显示设备环境显示
89 DeleteObject(hBitmap); //释放位图对象
90 DeleteDC (hdcMem) ; //释放内存设备环境
91 ReleaseDC (hwnd, hdc) ; //释放显示设备环境
92 return 0 ;
93 case WM_DESTROY:
94 KillTimer (hwnd, ID_TIMER) ; //销毁定时器
95 PostQuitMessage (0) ;
96 return 0 ;
97 }
98 return DefWindowProc (hwnd, message, wParam, lParam) ;
99}
100
分析:
SetPixel函数在指定的x和y坐标以特定的颜色设定象素:
1SetPixel (hdc, x, y, crColor) ; //在显示设备环境中绘制点
如同在任何绘图函数中一样,第一个参数是设备内容的句柄。第二个和第三个参数指明了坐标位置。通常要获得窗口显示区域的设备内容,并且x和y相对于该显示区域的左上角。最后一个参数是COLORREF型态指定了颜色。如果在函数中指定的颜色视讯显示器不支持,则函数将图素设定为最接近的纯色并从函数传回该值。
你可以通过呼叫SetTimer函数为您的Windows程序分配一个定时器。SetTimer有一个时间间隔范围为1毫秒到4,294,967,295毫秒(将近50天)的整数型态参数,这个值指示Windows每隔多久时间给您的程序发送WM_TIMER消息。例如,如果间隔为1000毫秒,那么Windows将每秒给程序发送一个WM_TIMER消息。
当您的程序用完定时器时,它呼叫KillTimer函数来停止定时器消息。在处理WM_TIMER消息时,您可以通过呼叫KillTimer函数来编写一个「限用一次」的定时器。KillTimer呼叫清除消息队列中尚未被处理的WM_TIMER消息,从而使程序在呼叫KillTimer之后就不会再接收到WM_TIMER消息。
SetTimer函数让Windows把WM_TIMER消息发送到应用程序的窗口消息处理程序中:
1 case WM_CREATE:
2
3 SetTimer (hwnd, ID_TIMER, 10, NULL) ; //创建定时器,每10微妙产生一个WM_TIMER消息
4
5 return 0 ;
6
7 case WM_DESTROY:
8
9 KillTimer (hwnd, ID_TIMER) ; //销毁定时器
10
11 PostQuitMessage (0) ;
12
13 return 0 ;
SetTimer第一个参数是其窗口消息处理程序将接收WM_TIMER消息的窗口句柄。第二个参数是定时器ID,它是一个非0数值,在整个例子中假定为1。第三个参数是一个32位无正负号整数,以毫秒为单位指定一个时间间隔,一个60,000的值将使Windows每分钟发送一次WM_TIMER消息。
注意,我们在WM_CREATE消息中调用SetTimer创建定时器,WM_DESTROY消息中调用KillTimer销毁定时器,这两个消息分别是窗口创建和销毁时时产生,通常我们在WM_CREATE消息中做窗口初始化操作,WM_DESTORY中做窗口结束时的清理工作。
1 case WM_TIMER:
2 hdc = GetDC (hwnd) ;
3 hdcMem = CreateCompatibleDC(NULL); //创建内存设备环境
4 GetClientRect (hwnd, &rect) ;
5 hBitmap = CreateCompatibleBitmap(hdc,
6 rect.right, rect.bottom); //创建内存设备环境相关的位图
7 SelectObject(hdcMem, hBitmap); //选择位图对象到内存设备环境
8
9 for(int i=0;i<100;i++)
10 {
11 x = rand()%rect.right; //随机产生点的X坐标
12 y = rand()%rect.bottom; //随机产生点的Y坐标
13 COLORREF crColor = RGB(rand()%256,rand()%256,rand()%256); //随机产生点的颜色值
14 SetPixel (hdc, x, y, crColor) ; //在显示设备环境中绘制点
15// SetPixel (hdcMem, x, y, crColor) ;//在内存设备环境中绘制点
16 }
17
18 BitBlt(hdc,0, 0, rect.right, rect.bottom, hdcMem, 0, 0, SRCINVERT); //将内存设备环境中的数据传到显示设备环境显示
19 DeleteObject(hBitmap); //释放位图对象
20 DeleteDC (hdcMem) ; //释放内存设备环境
21 ReleaseDC (hwnd, hdc) ; //释放显示设备环境
22 return 0 ;
上面代码是本课程的核心部分,WM_TIMER消息的响应代码,每个10微秒会被调用1次。
1for(int i=0;i<100;i++)
2{
3 x = rand()%rect.right; //随机产生点的X坐标
4 y = rand()%rect.bottom; //随机产生点的Y坐标
5 //随机产生点的颜色值
6 COLORREF crColor = RGB(rand()%256,rand()%256,rand()%256);
7SetPixel (hdc, x, y, crColor) ; //在显示设备环境中绘制点
8}
我们用这段代码在屏幕上绘制100个点,每个点的位置的颜色用rand函数随机产生。
1 hdc = GetDC (hwnd) ;
2
3 hdcMem = CreateCompatibleDC(NULL); //创建内存设备环境
4
5 GetClientRect (hwnd, &rect) ;
6
7 hBitmap = CreateCompatibleBitmap(hdc,
8
9 rect.right, rect.bottom); //创建内存设备环境相关的位图
10
11 SelectObject(hdcMem, hBitmap); //选择位图对象到内存设备环境
这段代码的作用是产生两个缓冲区,就是所谓双缓冲,hdc是前台显示设备句柄,我们通过它操作前台缓冲区,hdcMem是后台绘制设备句柄。GetClientRect获取窗口大小,通过窗口大小CreateCompatibleBitmap创建后台缓冲区,然后通过SelectObject函数把这个缓冲区与hdcMem设备关联
1SetPixel (hdc, x, y, crColor) ; //在前台的纸上直接画点
2//SetPixel (hdcMem, x, y, crColor) ; //在后台的纸上画点
本例中,实际上我是直接向前台设备绘制点,并没用到双缓冲,由于绘制点,用和不用双缓冲看不出区别。要使用双缓冲,你只需要把上面代码的注释交换位置就可以了。
1//将内存设备环境中的数据传到显示设备环境显示
2BitBlt(hdc,0, 0, rect.right, rect.bottom, hdcMem, 0, 0, SRCINVERT);
这句代码作用就是把后台的缓冲区的内容复制到前台缓冲区显示给用户。这个函数很重要,下面,我们看看它的声明:
1BOOL BitBlt(
2 __in HDC hdcDest,
3 __in int nXDest,
4 __in int nYDest,
5 __in int nWidth,
6 __in int nHeight,
7 __in HDC hdcSrc,
8 __in int nXSrc,
9 __in int nYSrc,
10 __in DWORD dwRop
11);
参数说明:
下面列出了一些常见的光栅操作代码:
-
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有关的颜色填充目标矩形区域。(对于缺省物理调色板来说,这个颜色就是白色)。
最后,别忘了非常重要的操作,前面创建的设备句柄和资源,统统都要释放,否则会造成资源泄漏,切记,切记!
1 DeleteObject(hBitmap); //释放位图对象
2
3 DeleteDC (hdcMem) ; //释放内存设备环境
4
5 ReleaseDC (hwnd, hdc) ; //释放显示设备环境
6
7 return 0 ;
8
posted on 2011-01-24 01:15
姚明 阅读(661)
评论(1) 编辑 收藏 引用 所属分类:
原创教程