可用MinGW编译的win32绘图框架
来源:互联网 发布:禾赛科技李一帆 知乎 编辑:程序博客网 时间:2024/05/23 23:31
鉴于Microsoft Visual Studio体积巨大,我坚持不愿意安装,故在windows下编程一直都用MinGW和TCC作为编译器,用Codebocks作为开发环境。MinGW对于C/C++标准库支持很好,但是对于windows API就没那么好了。由于windows家族从win32API-> MFC->WPF->.net,体积庞大演变复杂,第三方库显然无法承载。
对于我个人工作,console下的操作使用标准库函数已经足够,文件操作使用.bat批处理,网路操作使用Python,唯一需要win32系统函数的地方就是图形界面了。这次在编写botzone的贪吃蛇程序时,平台规定用cpp+jsoncpp,为了方便调试,只好使用MinGW调用win32API库做一个简单的绘图框架。
由于GDI库过于底层,使用起来很庞杂,我的思路是写一个固定的框架代码,每隔30ms显示某内存中的固定图片(别忘了加锁),其他所有的绘制过程都归结到对于图片的图形绘制上去。这样最大限度地隔离了win32系统。
首先建立基本的主入口和消息循环函数WinMain:
#include <windows.h>int WINAPI WinMain (HINSTANCE hThisInstance, HINSTANCE hPrevInstance, LPSTR lpszArgument, int nCmdShow){ HWND hwnd; /* This is the handle for our window */ MSG messages; /* Here messages to the application are saved */ WNDCLASSEX wincl; /* Data structure for the windowclass */ /* The Window structure */ wincl.hInstance = hThisInstance; wincl.lpszClassName = szClassName; wincl.lpfnWndProc = WindowProcedure; /* This function is called by windows */ wincl.style = CS_DBLCLKS; /* Catch double-clicks */ wincl.cbSize = sizeof (WNDCLASSEX); /* Use default icon and mouse-pointer */ wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION); wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION); wincl.hCursor = LoadCursor (NULL, IDC_ARROW); wincl.lpszMenuName = NULL; /* No menu */ wincl.cbClsExtra = 0; /* No extra bytes after the window class */ wincl.cbWndExtra = 0; /* structure or the window instance */ /* Use Windows's default colour as the background of the window */ wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND; /* Register the window class, and if it fails quit the program */ if (!RegisterClassEx (&wincl)) return 0; /* The class is registered, let's create the program*/ hwnd = CreateWindowEx ( 0, /* Extended possibilites for variation */ szClassName, /* Classname */ "Double Snake Game Simulator", /* Title Text */ WS_OVERLAPPEDWINDOW, /* default window */ CW_USEDEFAULT, /* Windows decides the position */ CW_USEDEFAULT, /* where the window ends up on the screen */ 800, /* The programs width */ 600, /* and height in pixels */ HWND_DESKTOP, /* The window is a child-window to desktop */ NULL, /* No menu */ hThisInstance, /* Program Instance handler */ NULL /* No Window Creation data */ ); /* Make the window visible on the screen */ ShowWindow (hwnd, nCmdShow); /* Run the message loop. It will run until GetMessage() returns 0 */ while (GetMessage (&messages, NULL, 0, 0)) { /* Translate virtual-key messages into character messages */ TranslateMessage(&messages); /* Send message to WindowProcedure */ DispatchMessage(&messages); } /* The program return-value is 0 - The value that PostQuitMessage() gave */ return messages.wParam;}
消息处理函数
LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam){ switch (message) /* handle the messages */ { case WM_TIMER: RedrawWindow(hwnd,NULL,NULL,RDW_ERASE|RDW_INVALIDATE); case WM_PAINT: { PAINTSTRUCT ps; HDC hdc; RECT r; GetClientRect(hwnd,&r); EnterCriticalSection(&displock); hdc=BeginPaint(hwnd,&ps); DrawText(hdc,"Hello world",-1,&r,DT_SINGLELINE|DT_CENTER|DT_VCENTER); EndPaint(hwnd,&ps); LeaveCriticalSection(&displock); printf("Paint%d!\n",num); break; } case WM_DESTROY: PostQuitMessage (0); /* send a WM_QUIT to the message queue */ break; default: /* for messages that we don't deal with */ return DefWindowProc (hwnd, message, wParam, lParam); } return 0;}
为了不断重绘,建立定时器
UINT_PTR WINAPI SetTimer( _In_opt_ HWND hWnd, //窗口句柄 _In_ UINT_PTR nIDEvent, //定时器ID 用户指定 _In_ UINT uElapse, //定时时间ms _In_opt_ TIMERPROC lpTimerFunc //回调函数 NULL则送入消息循环 );
若在新建定时器时指定了nIDEvent,则可在消息循环中判断wParam的取值来区分定时器消息源。本程序没有用到这一特性。本程序代码为:
SetTimer(hwnd,1,20,NULL);
并在消息处理中添加一行
case WM_TIMER: //定时器操作
重绘窗口使用
BOOL WINAPI RedrawWindow( HWND hwnd, //窗口句柄 CONST RECT* lprcUpdate, //重绘区域 HRGN hrgnUpdate, //重绘区域,优先 UINT flags //重绘标志位);
标志位的设置大有讲究。Win32的重绘概念比较复杂,将窗口标为invalid和生成WM_PAINT消息是两个过程,此外还有WM_ERASEBKGND消息作为前导。
本程序中使用
RedrawWindow(hwnd,NULL,NULL,RDW_ERASE|RDW_INVALIDATE)RedrawWindow(hwnd,NULL,NULL,RDW_ERASE|RDW_INVALIDATE|RDW_UPDATENOW)
结果一样。但是如果漏掉了前两个参数,则WM_PAINT消息仍会产生并执行相应重绘代码,但重绘后窗口界面不会更新,只有最小化或被遮挡后才行。
创建线程使用
HANDLE WINAPI CreateThread( _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes=NULL, _In_ SIZE_T dwStackSize=0, _In_ LPTHREAD_START_ROUTINE lpStartAddress, _In_opt_ LPVOID lpParameter=NULL, _In_ DWORD dwCreationFlags=0, _Out_opt_ LPDWORD lpThreadId=NULL);
若调用了C库函数,则应使用
uintptr_t _beginthread( // NATIVE CODE void( __cdecl *start_address )( void * ), unsigned stack_size, void *arglist );
本程序中,我使用
_beginthread(ThreadFunction,0,NULL);
其中执行函数ThreadFunction形式为
void ThreadFunction(void* pParam){ //running code}
退出线程使用
ExitThread();_endthread();
创建临界区使用
CRITICAL_SECTION displock;
初始化
InitializeCriticalSection(&displock);
进入临界区
EnterCriticalSection(&displock);
离开临界区
LeaveCriticalSection(&displock);
释放资源
DeleteCriticalSection(&displock);
在WM_PAINT中:
- 清除背景
FillRect(hdc, &ps.rcPaint, (HBRUSH) (COLOR_WINDOW));
- 选择画笔(用于画线)
HPEN hBluePen = CreatePen(PS_SOLID, 1, RGB(200, 0, 80)); //笔型 粗细 颜色HPEN hOldPen = (HPEN)SelectObject(hdc, hBluePen); //返回老画笔/********使用画笔**********/SelectObject(hdc, hOldPen); //设置回原画笔DeleteObject(hBluePen); //释放资源
- 选择画刷(用于填充)
HBRUSH hPurpleBrush = CreateSolidBrush(RGB(255, 0, 255)); /********使用画刷***********/ FillRect(hdc, &lprc, hPurpleBrush);DeleteObject(hPurpleBrush);
画线
hdc = BeginPaint(hWnd, &ps);MoveToEx(hdc, 60, 20, NULL);LineTo(hdc, 264, 122);EndPaint(hWnd, &ps);
折线
BOOL Polyline(HDC hdc, CONST POINT *lppt, int cPoints);
POINT Pt[7];Pt[0].x = 20; Pt[0].y = 50;Pt[1].x = 180; Pt[1].y = 50;Pt[2].x = 180; Pt[2].y = 20;hdc = BeginPaint(hWnd, &ps);Polyline(hdc, Pt, 3);EndPaint(hWnd, &ps);
绘制矩形
PAINTSTRUCT ps;HDC hdc;RECT lprc;//GetClientRect(hwnd,&lprc); 获取窗口矩形大小SetRect(&lprc,1,1,800,600); //left up right downhdc=BeginPaint(hwnd,&ps);FillRect(hdc, &lprc, (HBRUSH) (1));EndPaint(hwnd,&ps);
绘制内存中的图片
需要使用Bitmap,概念也比较复杂。Bitmap可分为设备无关DIB和设备相关DDB两种,DDB是和dc相关的位图,不同情况下用CreateBMP(),CreateCompatibleBMP(),LoadBMP(),LoadImage()等创建的就是DDB。DIB就是一片内存,里面存储着位图掐头去尾,只留下RGB,或者像素+色板的信息。
Bitmap属于一种GDI对象,同Brushes Fonts Paths Pens Regions等对象一样,可以被指定到GDI目标设备context上。
以下函数从图像像素矩阵创建DDB句柄
HBITMAP CreateBitmap( _In_ int nWidth, //位图宽度 _In_ int nHeight, //位图高度 _In_ UINT cPlanes, //该设备使用的颜色位面数目 _In_ UINT cBitsPerPel, //点颜色位数 _In_ const VOID *lpvBits //数据指针 );
对于彩色图像,用以下函数不需要类型转换步骤,速度更快:
HBITMAP CreateCompatibleBitmap(HDC hdc,int nWidth,int nHeight);
为了防止屏幕闪烁,新建DC上下文应用双缓冲机制。
HDC CreateCompatibleDC( HDC hdc);
将DC与Bitmap绑定
HDC memdc=CreateCompatibleDC(hdc);HBITMAP memBmp=CreateCompatibleBitmap(hdc, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN));SelectObject(memdc,memBmp);
将缓冲区数据复制到DIB
int SetDIBits( _In_ HDC hdc, //设备句柄 _In_ HBITMAP hbmp, //位图句柄 _In_ UINT uStartScan, //开始行=0 _In_ UINT cScanLines, //行数 _In_ const VOID *lpvBits, //像素数据数组 _In_ const BITMAPINFO *lpbmi, //指向DIB信息的数据结构 _In_ UINT fuColorUse=DIB_RGB_COLORS);
从DIB复制到缓冲区数据.
若lpvBits=NULL且BITMAPINFO中的biBitCount成员被初始化为零,binfo.bmiHeader.biSize=sizeof(BITMAPINFOHEADER),则当前显示设备属性将被输出。
int GetDIBits( _In_ HDC hdc, _In_ HBITMAP hbmp, _In_ UINT uStartScan, _In_ UINT cScanLines, _Out_ LPVOID lpvBits, _Inout_ LPBITMAPINFO lpbi, _In_ UINT uUsage);
其中,BITMAPINFO定义了DIB的维度和颜色信息
typedef struct tagBITMAPINFO { BITMAPINFOHEADER bmiHeader; RGBQUAD bmiColors[1];} BITMAPINFO, *PBITMAPINFO;typedef struct tagBITMAPINFOHEADER { DWORD biSize; //自身大小 LONG biWidth; //宽度 LONG biHeight; //高度 WORD biPlanes=1; WORD biBitCount; //位深度 DWORD biCompression; //压缩方式 一般BI_RGB DWORD biSizeImage; //假如不是BI_JPEG或BI_PNG,设为0即可 LONG biXPelsPerMeter; //像素密度 LONG biYPelsPerMeter; //像素密度 DWORD biClrUsed=0; DWORD biClrImportant=0;} BITMAPINFOHEADER, *PBITMAPINFOHEADER;typedef struct tagRGBQUAD { BYTE rgbBlue; BYTE rgbGreen; BYTE rgbRed; BYTE rgbReserved;} RGBQUAD;
以下是绘制图片的代码
HDC memdc=CreateCompatibleDC(hdc); HBITMAP memBmp=CreateCompatibleBitmap(hdc, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN)); SelectObject(memdc,memBmp); BITMAPINFO binfo; ZeroMemory(&binfo,sizeof(BITMAPINFO)); binfo.bmiHeader.biSize=sizeof(BITMAPINFOHEADER); binfo.bmiHeader.biBitCount=32; binfo.bmiHeader.biCompression=0; binfo.bmiHeader.biWidth=SCREEN_W; binfo.bmiHeader.biHeight=SCREEN_H; binfo.bmiHeader.biPlanes=1; binfo.bmiHeader.biSizeImage=0; BYTE *pBuf=new BYTE[SCREEN_H*SCREEN_W*4]; int row_size=SCREEN_W*4; for(int i=0;i<SCREEN_H;i++) pBuf[i*row_size+i*4]=200; SetDIBits(memdc,memBmp,0, binfo.bmiHeader.biHeight, pBuf,(BITMAPINFO*)&binfo, DIB_RGB_COLORS); BitBlt(hdc, 0, 0, binfo.bmiHeader.biHeight, binfo.bmiHeader.biWidth,memdc, 0, 0, SRCCOPY); DeleteDC(memdc); DeleteObject(memBmp);
按照以上代码运行,发现重绘闪烁严重。这个网站给出了全面的解决办法。
主要问题在于windows的重绘分为两个阶段
- WM_ERASEBKGND消息:清除背景
- WM_PAINT: 绘制新内容
这样,在响应两个消息之间的间隔,屏幕为白板,从而发生闪烁。解决方法有以下两种:
- 在WM_ERASEBKGND响应代码中返回非零值
case WM_ERASEBKGND: return 1;
- 将窗口的背景刷设为NULL
wincl.hbrBackground=0;
此外,当绘制对象过多时,必须添加双缓冲,否则将可见图像刷新过程。典型的双缓冲过程如下:
HDC hdcMem;HBITMAP hbmMem;HANDLE hOld;PAINTSTRUCT ps;HDC hdc;....case WM_PAINT: // Get DC for window hdc = BeginPaint(hwnd, &ps); // Create an off-screen DC for double-buffering hdcMem = CreateCompatibleDC(hdc); hbmMem = CreateCompatibleBitmap(hdc, win_width, win_height); hOld = SelectObject(hdcMem, hbmMem); /*********Draw into hdcMem here*************/ /*********Draw into hdcMem here*************/ // Transfer the off-screen DC to the screen BitBlt(hdc, 0, 0, win_width, win_height, hdcMem, 0, 0, SRCCOPY); // Free-up the off-screen DC SelectObject(hdcMem, hOld); DeleteObject(hbmMem); DeleteDC (hdcMem); EndPaint(hwnd, &ps);
至此基本解决了闪烁问题
键盘相应
case WM_KEYDOWN: switch (wParam) { case VK_UP: SendMessage(hwnd, WM_VSCROLL, SB_LINEUP, 0); break; case VK_DOWN: SendMessage(hwnd, WM_VSCROLL, SB_LINEDOWN, 0); break; case VK_LEFT: SendMessage(hwnd, WM_HSCROLL, SB_PAGEUP, 0); break; case VK_RIGHT: SendMessage(hwnd, WM_HSCROLL, SB_PAGEDOWN, 0); break; } break;
以下是总体代码
BYTE *pBuf=new BYTE[SCREEN_H*SCREEN_W*4];int row_size=SCREEN_W*4;LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam){ switch (message) /* handle the messages */ { case WM_CREATE: ZeroMemory(pBuf,SCREEN_H*SCREEN_W*4); for(int i=0;i<SCREEN_H;i++) pBuf[i*row_size+i*4]=200; break; case WM_TIMER: RedrawWindow(hwnd,NULL,NULL,RDW_ERASE|RDW_INVALIDATE); break; case WM_PAINT: { PAINTSTRUCT ps; HDC hdc; RECT lprc; EnterCriticalSection(&displock); hdc=BeginPaint(hwnd,&ps); //清除背景 FillRect(hdc, &ps.rcPaint, (HBRUSH) (COLOR_WINDOW)); HPEN hBluePen = CreatePen(PS_SOLID, 10, RGB(200, 0, 80)); HPEN hPen = (HPEN)SelectObject(hdc, hBluePen); MoveToEx(hdc, 100, 100, NULL); LineTo(hdc, num, 2*num); SelectObject(hdc, hPen); DeleteObject(hBluePen); HBRUSH hPurpleBrush = CreateSolidBrush(RGB(255-num, num, 20)); SetRect(&lprc,600,400,650,450); FillRect(hdc, &lprc, hPurpleBrush); DeleteObject(hPurpleBrush); //checkScreenProperty(hdc); HDC memdc=CreateCompatibleDC(hdc); HBITMAP memBmp=CreateCompatibleBitmap(hdc, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN)); SelectObject(memdc,memBmp); BITMAPINFO binfo; ZeroMemory(&binfo,sizeof(BITMAPINFO)); binfo.bmiHeader.biSize=sizeof(BITMAPINFOHEADER); binfo.bmiHeader.biBitCount=32; binfo.bmiHeader.biCompression=0; binfo.bmiHeader.biWidth=SCREEN_W; binfo.bmiHeader.biHeight=SCREEN_H; binfo.bmiHeader.biPlanes=1; binfo.bmiHeader.biSizeImage=0; SetDIBits(memdc,memBmp,0, binfo.bmiHeader.biHeight, pBuf,(BITMAPINFO*)&binfo, DIB_RGB_COLORS); BitBlt(hdc, 0, 0, binfo.bmiHeader.biWidth, binfo.bmiHeader.biHeight,memdc, 0, 0, SRCCOPY); DeleteDC(memdc); DeleteObject(memBmp); GetClientRect(hwnd,&lprc); DrawText(hdc,"Hello World",-1,&lprc,DT_SINGLELINE|DT_CENTER|DT_VCENTER); EndPaint(hwnd,&ps); LeaveCriticalSection(&displock); break; } case WM_DESTROY: running=false; PostQuitMessage (0); /* send a WM_QUIT to the message queue */ break; default: /* for messages that we don't deal with */ return DefWindowProc (hwnd, message, wParam, lParam); } return 0;}
总结与吐槽
微软网页上的这段典型代码体现出GDI有多么冗繁而反人类。
由于从一开始就没有面向对象的概念,所以各种变量数据裸露在外,大量名称交错使用,很容易造成混乱。给程序员的记忆负担也很重。
- 可用MinGW编译的win32绘图框架
- win32 + windows + mingw + msys 编译ACE
- 用MinGW编译Win32 GDI应用程序
- mingw编译Qt5正式版[亲测可用]
- mingw 编译openvpn 编译tap-win32时报错
- eclipse + cdt + mingw 一个Javaer的Win32
- OpenAL-soft编译,MinGW Win32和Win64,动态和静态
- MinGW编译的DLL versuffix
- win32绘图
- win32 绘图
- android的绘图框架
- Tap-Win32的编译
- 使用win32编写的二维绘图软件
- win32下的双缓冲绘图技术
- win32下的双缓冲绘图技术
- WIN32 的框架代码
- win32-apps-with-mingw
- 可用于Win32平台的flex/bison
- Swift-字符串
- Linux学习-高级shell脚本编程(二)初识sed和gawk
- Qt绘制贝塞尔曲线例程
- openwrt Makefile理解
- (半翻译)篡改mac应用后,如何resign签名,重新获得mac系统的信任?
- 可用MinGW编译的win32绘图框架
- 集合框架
- openwrt Makefile 理解
- 最少拦截系统(1257)
- SQL应用与开发:(九)提高效率的索引
- ectouch 微信支付成功后订单状态未改变的解决办法
- 三菱FX系列PLC编程口通信协议总览
- 关于asyncqueryhandler 开发技巧
- ecshop调用指定的广告位的方法