windows程序设计(九)图形基础(1)

图形装置介面(GDI:Graphics Device Interface)是 Windows 的子系统, 它负责在视讯显示器和印表机上显示图形。GDI 是 Windows 非常重要的部分。

GDI原理

图形输出设备分为两大类:位元映射设备和向量设备。大多数 PC 的输出设备是位元映射设备,这意味著它们以图点构成的阵列来表示图像。

GDI函数呼叫

GDI函数呼叫方式分为以下几类:
1.取得(或者建立)和释放(或者清除)装置内容的函式 :即绘图时所需要的装置代号如GetDC 和RealseDC
2.取得有关装置内容资讯的函数:GetTextMetrics 函数来取得装置内容中所选字体的尺寸资讯
3.绘图函数:如TextOut函数在显示区域显示一些字
4.设定和取得装置内容参数的函数
5.使用 GDI 物件的函式

GDI基本图形

1.直线和曲线 : 线条是所有向量图形绘制系统的基础。GDI 支援直线、矩形、 椭圆、椭圆圆周上的部分曲线以及贝塞尔曲线。
2.填入区域: 当一系列直线或者曲线封闭了一个区域时,该区域可以使用目前 GDI 画刷物件进行填图。
3.点阵图: 点阵图是位元的矩形阵列,这些位元对应于显示设备上的图素,它们是位元映射图形的基础工具。
4.文字:用于定义 GDI 字体物件和取得字体资讯的资料结构是 Windows 中最庞大的部分之一。

装置内容

当我们想在一个图形输出设备上绘图时,首先必须获得一个装置内容的代号。将代号传回给程序时,Windows 就给 了您使用设备的许可权。然後您在 GDI 函数中将这个代号作为一个参数,向 Windows 标识你想在其上进行绘图的设备。
最常用的取得装置内容代号的方法就是在处理WM_PAINT讯息时,使用BeginPaint和EndPaint函数。

hdc = BeginPaint(hwnd, &ps);
EndPaint(hwnd, &ps);

变量 ps 是型态为 PAINTSTRUCT 的结构,该结构的 hdc 栏位是 BeginPaint 传回的装置内容代号。 PAINTSTRUCT 结构又包含一个名为 rcPaint 的 RECT(矩 形)结构,rcPaint 定义一个包围视窗显示区域无效范围的矩形。使用从 BeginPaint 获得的装置内容代号,只能在这个区域内绘图。BeginPaint 呼叫使 该区域有效。
除此之外,还可以在非处理WM_PAINT讯息时取得装置内容代号:

hdc = GetDC(hwnd);
ReleaseDC(hwnd, hdc);

这个装置内容适用于视窗代号为 hwnd 的显示区域。这些呼叫与 BeginPaint 和 EndPaint 的组合之间的基本区别是,利用从 GetDC 传回的代号可以在整个显示区域上绘图。当然, GetDC 和 ReleaseDC 不使显示区域中任何可能的无效区域变成有效。
Windows 程序还可以取得适用于整个视窗(而不仅限于视窗的显示区域)的装置内容代号:

hdc = GetWindowDC (hwnd) ;
ReleaseDC (hwnd, hdc) ; 

这个装置内容除了显示区域之外,还包括视窗的标题列、功能表、卷动列和框架。该函数很少使用。
取得装置内容代号的一个更加通用的函数为CreateDC:

hdc = CreateDC(pszDriver,pszDevice,pszOutput,pData);
DeleteDC(hdc);

有时只是需要取得关于某装置内容的一些资讯而并不进行任何绘画,在 这种情况下,您可以使用 CreateIC 来取得一个咨询内容的代号:

hdc = CreateIC (TEXT ("DISPLAY"), NULL, NULL, NULL) ;

装置的大小

在此使用解析度来严格定义每度量单位(一般为英寸)内的像素数。使用像素大小或像素尺寸表示设备水平或垂直显示的总像素数。度量大小或度量尺寸是以英寸或毫米为单位的设备显示区域的大小。图素大小除以度量大小就得到解析度。
可以使用 GetDeviceCaps函数来检索相关的指定设备的设备特定的信息。使用HORZSIZE和VERTSIZE可以得到实际萤幕的高度和宽度,从英寸转化为毫米的公式如下:
在这里插入图片描述

关于色彩

如果仅显示黑色图素和白色图素,则只需要一位来存储。
Full-Color显示的解析度是每个像素 24 位元(8 位元红色、8 位元绿色以及 8 位元蓝色)。
High-Colorr显示的解析度是每个像素需要16位元(5 位元红色、6 位元绿 色以及 5 位元蓝色)。
在大多数 GDI 函式呼叫中,使用 COLORREF 值来表示一种色彩,它是一个32位元的无正负号的长整型数。COLORREF 值按照红、绿和蓝色的亮度指定了一种 颜色,通常叫做RGB色彩。以下为COLORREF 值得设定图:
在这里插入图片描述

画点和线

在理论上,只要提供 SetPixel 和 GetPixel 函式, 就可以使用图形装置驱动程式绘制一切东西了。其余的一切都可以使用 GDI 模 组中实作的更高阶的常式来处理。
SetPixel 函数将指定坐标处的像素设为指定颜色。

COLORREF SetPixel(
  HDC      hdc,
  int      x,
  int      y,
  COLORREF color
);

SetPixel 函数将获取指定坐标处像素的颜色

COLORREF GetPixel(
  HDC hdc,
  int x,
  int y
);

windows可以画很多中类型的线,下面是一些常用的画线函数:

函数名 功能
LineTo 画直线
Polyline PolylineTo 画一系列相连的直线
PolyPolyline 画多组相连的线
Arc 画椭圆线
PolyBezier PolyBezierTo 画贝塞尔曲线
ArcTo AngleArc 画椭圆线(win98不支持)
PolyDraw 画一系列相连的线以及贝塞尔曲线(win98不支持)
Rectangle 画矩形
Ellipse 画椭圆
RoundRect 画圆角的矩形
Pie 画椭圆的一部分,看起来像扇形

装置内容中的五个属性影响了这些函数的外观::目前画笔的位置(仅用于 LineTo、PolylineTo、PolyBezierTo 和 ArcTo )、画笔、背景方式、背景色和绘图模式。
如果要画一条直线,必须呼叫两个函数,第一函数定义了线的开始点,第二个函数定义了线的终点。

MoveToEx (hdc, xBeg, yBeg, NULL) ; 
LineTo (hdc, xEnd, yEnd) ; 

MoveToEx 并不是画线,只是定义了装置内的目前位置。然后用LineTo函数从目前位置到它所指定的点画一条直线。目前位置只是用于其他几个 GDI 函式的开始点。在内定的装置内容中,目前位置最初设定在点 (0,0)。如果在呼叫 LineTo 之前没有设定目前位置,那么它将从显示区域的左上角开始画线。
MoveToEx的最後一个参数是指向POINT结构的指标。 从该函数传回后,POINT 结构的 x 和 y 栏位指出了先前的目前位置,不需要可以设置为空。

/*
例1:MoveToEx和LineTo函数画直线
*/
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) {
	HDC hdc;
	PAINTSTRUCT ps;
	RECT rect;

	switch (message) {
	case WM_CREATE:
		return 0;
	case WM_PAINT:
		hdc = BeginPaint(hwnd, &ps);
		GetClientRect(hwnd, &rect);
		MoveToEx(hdc, 150, 150, NULL);
		LineTo(hdc, 150, 450);
		EndPaint(hwnd, &ps);
		return 0;
	case WM_DESTROY:  
		::PostQuitMessage(0);
		return 0;
	}
	return ::DefWindowProc(hwnd, message, wparam, lparam);
}

运行的结果如下:
在这里插入图片描述
如果需要获得当前的位置,可以调用函数GetCurrentPositionEx (hdc, &pt)
下面的一个小例子,是在窗口中画网格:

/*
例2:MoveToEx和LineTo函数组和画网格
*/
case WM_PAINT:
		hdc = BeginPaint(hwnd, &ps);
		GetClientRect(hwnd, &rect);
		for (int x = 0; x < rect.right; x += 100)
		{
			MoveToEx(hdc, x, 0,NULL);
			LineTo(hdc, x, rect.bottom);
		}
		for (int y = 0; y < rect.bottom; y += 100)
		{
			MoveToEx(hdc, 0, y, NULL);
			LineTo(hdc, rect.right, y);
		}
		EndPaint(hwnd, &ps);
		return 0;

在该例子中,rect所存储的是窗口中除标题菜单栏以外的位置,第一个for循环负责画垂直方向的网格,第二个for循环负责画水平方向的网格。
运行结果如下所示:
在这里插入图片描述
下面的一个例子是通过上面的函数画矩形边框的

/*
例3:使用存储点矩阵的方式画矩形
*/
POINT apt[5] = { 100, 100, 200, 100, 200, 200, 100, 200, 100, 100 };

case WM_PAINT:
		hdc = BeginPaint(hwnd, &ps);
		GetClientRect(hwnd, &rect);
		MoveToEx(hdc, apt[0].x, apt[0].y, NULL); 
		for (int i = 1; i < 5; i++) {
			LineTo(hdc, apt[i].x, apt[i].y);
		}
		EndPaint(hwnd, &ps);
		return 0;

如上图所示,apt是一个存放点的数组,通过for循环调用每一个点,最后画出一个矩形,运行程序后的结果如下所示:
在这里插入图片描述
下面一个例子是在视窗中画sin函数的函数图象:

/*
例4:使用polyline函数画sin函数的图象
*/

#include<stdio.h>
#include<math.h>
#include<Windows.h>

#define NUM 1000
#define TWOPI  (2 * 3.14159)

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPreveInstance,
	LPSTR szCmdLine, int nCmdShow) {
	static TCHAR szAppName[] = TEXT("HelloWin");
	HWND hwnd;
	MSG msg;

	//char szClassName[] = "MainWClass";
	WNDCLASSEX wndclass; //用描述主窗口的参数填充WNDCLASSEX结构
	wndclass.cbSize = sizeof(wndclass); //结构的大小(必须定义结构大小)
	wndclass.style = CS_HREDRAW | CS_VREDRAW; //重绘
	wndclass.lpfnWndProc = WndProc; //窗口函数指针
	wndclass.cbClsExtra = 0; //没有额外的类内存
	wndclass.cbWndExtra = 0; //没有额外的窗体内存
	wndclass.hInstance = hInstance;  //实例句柄
	wndclass.hIcon = ::LoadIcon(NULL, IDI_APPLICATION);//使用预定义的图标
	wndclass.hCursor = ::LoadCursor(NULL, IDC_ARROW); //使用预定义光标
	wndclass.hbrBackground = (HBRUSH)::GetStockObject(WHITE_BRUSH);//使用白色背景画刷	
	wndclass.lpszMenuName = NULL;  //不指定菜单
	wndclass.lpszClassName = szAppName;  //窗口类的名称
	wndclass.hIconSm = NULL; //没有类的小图标
	::RegisterClassEx(&wndclass);//注册这个窗口类

								 //创建主窗口
	hwnd = ::CreateWindowEx(
		0,  //dwExStyle, 扩展样式 
		szAppName, //lpClassName,类名
		"WindowsName",  //lpWindowName窗口名称
		WS_OVERLAPPEDWINDOW, //dwStyle, 窗口风格
		CW_USEDEFAULT,  //X,初始X坐标
		CW_USEDEFAULT,  //Y,初始Y坐标
		CW_USEDEFAULT,  //nWight, 宽度
		CW_USEDEFAULT,  //nHight, 高度
		NULL,  //hWndParent, 父窗口句柄
		NULL,  //hMenu, 菜单句柄
		hInstance,  //hInstance, 程序实例句柄
		NULL   //ipParam,用户数据
	);
	if (NULL == hwnd) {
		::MessageBox(NULL, "创建窗口失败", "error", MB_OK);
		return -1;
	}

	//显示窗口
	::ShowWindow(hwnd, nCmdShow);
	::UpdateWindow(hwnd);

	//从操作系统的消息队列中不断的捡取消息
	while (::GetMessage(&msg, NULL, 0, 0)) {
		::TranslateMessage(&msg); //软键盘转化消息
		::DispatchMessage(&msg);//将消息发送到窗体中
	}
	return	msg.wParam;//当GetMessage返回0时程序结束
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) {

	HDC hdc;
	static int cxClient, cyClient;
	int i;
	PAINTSTRUCT ps;
	POINT apt[NUM];
	RECT rect;

	switch (message)
	{
	case WM_SIZE:
		cxClient = LOWORD(lparam);
		cyClient = HIWORD(lparam);
		return 0;
	case WM_PAINT:
		hdc = BeginPaint(hwnd, &ps);
		GetClientRect(hwnd, &rect);

		MoveToEx(hdc, 0, cyClient / 2, NULL);  //画直线
		LineTo(hdc, cxClient, cyClient  /  2);

		for (i = 0; i < NUM; i++) 
		{
			apt[i].x = i * cxClient / NUM;
			apt[i].y = (int)(cyClient / 2 * (1 - sin(TWOPI * i / NUM)));  //将点描出来后用polyline函数连接
		}
		Polyline(hdc, apt, NUM);
		EndPaint(hwnd, &ps);
		return 0;

	case WM_DESTROY:  //正在销毁窗口
					  //向消息序列投递一个WM_QUIT消息,促使GetMessage函数返回0,结束消息循环
		::PostQuitMessage(0);
		return 0;
	}
	return ::DefWindowProc(hwnd, message, wparam, lparam);
}

在Point数组apt中存储的都是sin函数线上的点,最后通过Polyline函数将他们连接起来,就成了sin函数曲线。

发布了20 篇原创文章 · 获赞 0 · 访问量 749

猜你喜欢

转载自blog.csdn.net/LeavingBook/article/details/104675515
今日推荐