继续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);
}
如果此文中某些细节处理解起来有些难度,可以参考一些基础的分析,例如
本文原创,原始地址
http://blog.csdn.net/fengyhack/article/details/39372599