逐梦旅程学习笔记 Windows/GDI 示例02:双缓冲贴图/绘制自由线条

继续GDI绘图,这个示例主要包括两个方面的核心内容:

1、双缓冲绘图

2、绘制自由线条


运行效果如下:



本示例已经将所需资源嵌入到应用程序中


其中:

背景图片 IDB_BITMAP1

音效(WAVE) IDR_WAV1

图标 IDI_ICON1


在窗口内单击鼠标左键并按住不放,拖动鼠标就可以绘制自由线条,

释放鼠标按键停止绘制

再次按下鼠标左键则可以开始绘制新的线条


这个示例中使用了“双缓冲绘图”技术,大致来讲就是:

在内存(“缓冲画布”)中完成所有复杂的回吐操作,最后将缓冲画布内容“贴”到

目标位置(窗口显示区域)


有时候,因为绘图元素较多,GDI方式绘图效率也不高(相对于DirectX等绘图技术

而言),在执行时刻会出现闪烁现象

为了避免或者减少这种“闪烁”现象,就可以采取“双缓冲”的方式

出现闪烁是因为窗口上的内容发生了变化(重绘),但是重绘过程不够迅速,所以

个人视觉感受不太连贯

现在,使用“双缓冲”绘图技术,所有的绘图操作都在内存缓冲进行(在绘制完成

之前是不会拷贝到窗口上)

即使是在大量地重绘也不会影响到视觉效果,因为最终“贴”到窗口上的内容是已经

绘制完成了的

除非绘制工作量实在太大,在内存中绘图消耗了较长时间然后才贴到窗口上,这样

看起来也不够连贯

不过总体效果要比直接在窗口上进行绘制来的要顺畅一些


在“贴图”那一步使用了一个关键的函数

StretchBlt(hDC, 0, 0, w, h, hMemDC, 0, 0, bmpWidth, bmpHeight, SRCCOPY);

此函数的原型为

BOOL  StretchBlt(HDC hdcDest, int xDest, int yDest, int wDest, 
		int hDest, HDC hdcSrc, int xSrc, int ySrc, int wSrc, int hSrc, DWORD rop);

功能:从源矩形中复制一个位图到目标矩形,必要时按目标设备设置的模式进行

图像的拉伸或压缩。

其中各参数的含义如下

hdcDest:指向目标设备环境的句柄。
xDest:指定目标矩形左上角的X轴坐标,按逻辑单位表示坐标。
yDest:指定目标矩形左上角的Y轴坐标,按逻辑单位表示坐标。
wDest:指定目标矩形的宽度,按逻辑单位表示宽度。
hDest:指定目标矩形的高度,按逻辑单位表示高度。
hdcSrc:指向源设备环境的句柄。
xSrc:指向源矩形区域左上角的X轴坐标,按逻辑单位表示坐标。
ySrc:指向源矩形区域左上角的Y轴坐标,按逻辑单位表示坐标。
wSrc:指定源矩形的宽度,按逻辑单位表示宽度。
hSrc:指定源矩形的高度,按逻辑单位表示高度。
rop:指定要进行的光栅操作。光栅操作码定义了系统如何在输出操作中组合颜色,

这些操作包括刷子、源位图和目标位图等对象。

光栅操作SRCCOPY表示将源矩形区域直接拷贝到目标矩形区域。


至于“绘制自由线条”,也很简单

首先是当按下鼠标左键时,记录下当前位置Point0,

然后移动鼠标到一个新的位置Point1,绘制一条Point0到Point1的线段

(注意因为系统处理速度很快,Point1和Point0距离很近)


然后继续移动鼠标的过程中不断刷新位置Point(k),

并且绘制Point(k-1)到Point(k)的线段


在鼠标不断移动的过程中,系统捕获到WM_MOUSEMOVE的消息并且迅速处理

因此相邻的点之间距离很近,这样整个过程中逐步绘制的线段看起来像是一个

自然过渡的曲线


注意在此(移动鼠标)过程中要按住鼠标左键不放,一旦释放鼠标左键就表示停止

当前的线条了


再次按下说表左键则表示要回至一段新的线条了,或许过程同上,不再赘述


完整代码(包含详细注释但不包含资源文件)如下

#include <windows.h>
#pragma comment(lib,"winmm.lib")
#include "resource.h"

HINSTANCE hInst = NULL;
HDC  hDC = NULL;  // 目标设备DC
HDC  hMemDC = NULL;  // 内存缓冲DC
HBITMAP  hBitmap = NULL;  // 位图句柄
HPEN  hPen = NULL;  // 绘制线条用的画笔
BOOL  bNewLine = FALSE;  // 是否开始绘制新的线条

const int  bmpWidth = 800; // 图片尺寸.宽
const int  bmpHeight = 600;  // 图片尺寸.高

// “安全模式”删除
#define Safe_DeleteObject(p)  if(p) { DeleteObject(p); }

// 窗口过程
LRESULT CALLBACK	WndProc(HWND hwnd, UINT message,WPARAM wParam, LPARAM lParam);

VOID  OnInit(HWND hwnd);  // 初始化
VOID StartLine(int x, int y);    // 线条起点
VOID DrawLine(int x, int y);   // 绘制线条
VOID  Render(HWND hwnd);  // “贴图”:将内存缓冲“贴”到窗口上
VOID  CleanUp();  // 清理

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
	LPCSTR lpszTitle = TEXT("Drawing");		// 窗口标题
	LPCSTR lpszName = TEXT("MainWindow");    // 窗口类的名字
	LPCSTR lpszIconFile = TEXT("steam_dota.ico");  // 程序图标

	hInst = hInstance;
	WNDCLASSEX wndClass;
	ZeroMemory(&wndClass, sizeof(wndClass));
	wndClass.cbSize = sizeof(WNDCLASSEX);
	wndClass.style = CS_HREDRAW | CS_VREDRAW;
	wndClass.lpfnWndProc = WndProc;
	wndClass.cbClsExtra = 0;
	wndClass.cbWndExtra = 0;
	wndClass.hInstance = hInstance;
	wndClass.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON1));
	wndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
	wndClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
	wndClass.lpszMenuName = NULL;
	wndClass.lpszClassName = lpszName;

	if (!RegisterClassEx(&wndClass))
	{
		return -1;
	}

	int scw = GetSystemMetrics(SM_CXSCREEN); // 屏幕宽度
	int sch = GetSystemMetrics(SM_CYSCREEN);  // 屏幕高度

	//int left = scw / 10;  // 10%
	//int top = sch / 10;
	//int width = scw * 4 / 5;  // 80%
	//int height = sch * 4 / 5;

	int left = (scw - bmpWidth) / 2;
	int top = (sch - bmpHeight) / 2;

	HWND hwnd = CreateWindow(lpszName, lpszTitle,
		WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
		bmpWidth, bmpHeight, NULL, NULL, hInstance, NULL);

	MoveWindow(hwnd, left, top, bmpWidth, bmpHeight, true);
	ShowWindow(hwnd, nShowCmd);
	UpdateWindow(hwnd);

	MSG msg;
	ZeroMemory(&msg, sizeof(msg));
	while (msg.message != WM_QUIT)
	{
		if (PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		else
		{
			// 若消息队列中没有任何消息,则执行其他操作,比如窗口内容的绘制
			// Render Loop
		}
	}

	UnregisterClass(lpszName, wndClass.hInstance);
	return 0;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message)
	{
	case WM_CREATE:
		OnInit(hwnd);
		break;

	case WM_PAINT:   // 每当窗口需要重绘时执行此操作
		Render(hwnd);
		break;

	case WM_KEYDOWN:
		if (wParam == VK_ESCAPE)    // 如果被按下的键是ESC
		{
			SendMessage(hwnd, WM_CLOSE, NULL, NULL);
		}
		else
		{
			PlaySound(MAKEINTRESOURCE(IDR_WAVE1),hInst ,SND_ASYNC | SND_RESOURCE | SND_NODEFAULT);
		}
		break;

	case WM_LBUTTONDOWN:  // 鼠标左键按下,开始绘制线条
		bNewLine = TRUE;
		StartLine(LOWORD(lParam), HIWORD(lParam));
		break;

	case WM_LBUTTONUP:  // 鼠标左键释放,停止绘制
		bNewLine = FALSE;
		break;

	case WM_MOUSEMOVE:
		if (bNewLine)   // 移动鼠标绘制曲线
		{
			DrawLine(LOWORD(lParam), HIWORD(lParam));
		}
		break;

	case WM_DESTROY:
		CleanUp();
		PostQuitMessage(0);
		break;

	default:
		return DefWindowProc(hwnd, message, wParam, lParam);
	}

	return 0;	//正常退出
}

VOID OnInit(HWND hwnd)
{
	hDC = GetDC(hwnd);
	hMemDC = CreateCompatibleDC(hDC);
	hBitmap = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BITMAP1));
	SelectObject(hMemDC, hBitmap);
	hPen = CreatePen(PS_SOLID, 5, RGB(255, 255, 0));
	SelectObject(hMemDC, hPen);
}

VOID StartLine(int x, int y)
{
	MoveToEx(hMemDC, x,y, NULL);
}

VOID DrawLine(int x, int y)
{
	LineTo(hMemDC, x,y);
}

VOID  Render(HWND hwnd)
{
	RECT rect;
	GetClientRect(hwnd, &rect);
	int w = rect.right - rect.left;
	int h = rect.bottom -rect.top;
	// Stretch表示将要“缩放贴图”
	StretchBlt(hDC, 0, 0, w, h, hMemDC, 0, 0, bmpWidth, bmpHeight, SRCCOPY);
}

VOID CleanUp()
{
	Safe_DeleteObject(hDC);
	Safe_DeleteObject(hMemDC);
	Safe_DeleteObject(hBitmap);
	Safe_DeleteObject(hPen);
}


如果此文中某些细节处理解起来有些难度,可以参考一些基础的分析,例如

Windows应用程序的基本框架浅析系列


本文原创,原始地址

http://blog.csdn.net/fengyhack/article/details/39372599





转载于:https://www.cnblogs.com/fengyhack/p/10603669.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值