windows图形编程 学习杂谈 之 高效率窗口背景

来源:互联网 发布:南昌seo外包 编辑:程序博客网 时间:2024/05/16 23:42

刚开始学习windows下的图形编程,只会用API创建窗口和最简单的消息函数。

总想给窗口画个背景图片,那么就开始吧。编程只看不动手是不会提高的。

开始从网上找资料,主要看的是GDI+_SDK参考手册。看了画图片的部分,很简单的嘛。

做了个最简单的OnPaint函数,在WM_WM_PAINT消息处理中调用。

[cpp] view plaincopy
  1. void OnPaint(HWND hWnd)  
  2. {  
  3.     Bitmap bmp(TEXT("i:\\background.jpg"));  //用图片文件创建Bitmap对象  
  4.     HDC hdc(GetDC(hWnd));  //取得设备环境句柄  
  5.   
  6.     RECT rect;  
  7.     GetClientRect(hWnd, &rect);   //获取窗口坐标  
  8.     UINT clientWidth = rect.right - rect.left;  //计算宽度  
  9.     UINT clientHeight = rect.bottom - rect.top; //计算高度  
  10.       
  11.     Graphics gs(hdc); //创建Graphics对象  
  12.     gs.DrawImage(&bmp, 0, 0, clientWidth, clientHeight); //画图  
  13.   
  14.     ReleaseDC(hWnd, hdc); //释放设备环境句柄  
  15. }  

编译运行,效果还不错。好的开始是成功的第一步。

欣喜过后,发现了一些缺点:

1、重绘过程中有可能出现背景颜色的闪现(我的窗口用的是淡绿的眼睛保护色)

2、窗口放大缩小后,图片比例变形。这怎么可以,美女变胖妞。(绝对无法容忍)

3、加入一个计时器计算时间,1024X768绘图的FPS只有25。速度太慢。


好吧,开始优化,又找资料又查MSDN,发现网上几乎都是很简单的demo,只能自己想办法。

折腾了好几天,最终优化版出炉。

优化在以下几个方面:

1、图片要一次读取,不能每次OnPaint从文件读取,效率太低。把文件读取的代码放在窗口创建的消息处。

2、读取图片用gdi+容易,绘图用gdi更快。

3、图片的缩放在WM_SIZE消息下处理,这部分很花时间,OnPaint只负责画调整好的,就快多了。

4、添加WM_ERASEBKGND消息处理,简单的return 1就可以不让windows刷新背景(背景我自己画)。

5、采用缓冲机制,处理好的图片是放在内存中,OnPaint才刷到屏幕上。

6、OnPaint的时候,不画全部窗口,只画无效部分。

7、这些功能函数封装成一个类,便于使用。


设计了一个BackGroundBmp类,用于创建背景图片,调整图片尺寸,画到窗口中。

[cpp] view plaincopy
  1. Bitmap* m_pbmp;  //gdi+ 的Bitmap对象,用于从文件中读取各种类型的图片(gdi只能读bmp),并且用于缩放。  
  2. HBITMAP m_membmp;  //gdi的内存图,是m_pbmp缩放处理后产生的。用于屏幕输出。  
  3. int m_width;   //内存图宽  
  4. int m_height; //内存图高  

成员变量很简单,4个

类的重要函数有3个,分别是

[cpp] view plaincopy
  1. bool Create(const WCHAR* filename);  
  2. bool FixSize(HWND hWnd, int cxWidth, int cxHeight);  
  3. bool Paint(HDC hdc, const RECT& rect);  

核心部分是FixSize,用gdi+对象实现缩放,画在gdi的内存图中。(完整代码最后附上)

简单解说一下这个函数的部分代码:

[cpp] view plaincopy
  1. double xscale = static_cast<double>(m_width) / bmpwidth;  
  2. double yscale = static_cast<double>(m_height) / bmpheight;  
bmpwidth、bmpheight是原图像的尺寸

m_width、m_height = 窗口客户区的尺寸

计算2种尺寸之间的比例关系。

下面就是缩放的关键代码:

[cpp] view plaincopy
  1. double fixscale = xscale > yscale ? xscale : yscale;  
  2. Rect rect(bmpwidth * (xscale - fixscale) / 2,  
  3.           bmpheight * (yscale - fixscale) / 2,  
  4.           bmpwidth * fixscale,  
  5.           bmpheight * fixscale);  
就是短短的几行,折腾了我好半天。(没办法,我的数学是体育老师教的)

怎么调整参数都不对,图片有变形。

后来才发现gdi+的Rect和gdi的RECT后2个参数居然代表的意义不一样。吐血。

RECT是gdi的一个结构,4个参数表示左上角坐标和右下角坐标。

Rect是gdi+的一个对象,前2个参数表示左上角的坐标,后面1个是宽度,1个是高度。

坐标调整好,接下来就简单了,创建一个内存DC,基于这个DC建立Graphics对象,用DrawImage函数把原始图像画到内存图中,传入刚才的Rect实现缩放。(代码略)

做完FixSize函数,试着运行下,效果非常棒,缩放没有一点失真。嘿嘿。很满意。

唯一缺点是速度比较慢(FPS才十几),好在窗口大小不是经常调整的。

还折腾过StretchBlt缩放图片,设置HALFTONE或者STRETCH_HALFTONE模式,缩小图片都有失真。速度是快,质量就差了。

StretchBlt缩放的代码在BackGroundBmp类中有实现,被我注释掉了。(完整代码中有包含,可以比较2种缩放方式的差别)


在工程里面还添加了一个高精度计时器类,从《Visual C++ 2010入门经典》这书上扒拉下来的,很棒的一个小工具。

在每个消息处理部分添加了计时。


工程项目顺利完工,迫不及待的编译执行。

效果太完美了,图像清晰,缩放不变形,无失真。OnPaint的FPS是一个我没想到过的数字(嘿嘿,保密,编译我的源码去试试看)。


以下完整源码,vs中建立空的win32 Project,把几个文件复制进去就行了。注意修改头文件的路径和图片的文件名(对话框和控件什么的还不会)

[cpp] view plaincopy
  1. //main.cpp  
  2.   
  3. #include <Windows.h>  
  4. #include <tchar.h>  
  5. #include <GdiPlus.h>  
  6. #include <sstream>  
  7. #include "include\hrtimer.h"  
  8. #include "include\backgroundbmp.h"  
  9. #pragma comment(lib, "gdiplus.lib")  
  10. using namespace Gdiplus;  
  11.   
  12. HRTimer timer;  
  13.   
  14. LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);  
  15. void PrintText(HWND hWnd, const std::basic_string<TCHAR>& string, int xPos, int yPos);  
  16.   
  17. int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)  
  18. {  
  19.     WNDCLASSEX WindowClass;  
  20.   
  21.     static LPCTSTR szAppName = TEXT("myApp");  
  22.     HWND hWnd;  
  23.     MSG msg;  
  24.   
  25.     GdiplusStartupInput gdiplusstartinput;  
  26.     ULONG_PTR gdiplustoken;  
  27.   
  28.     GdiplusStartup(&gdiplustoken, &gdiplusstartinput, 0);  
  29.   
  30.     WindowClass.cbSize = sizeof(WNDCLASSEX);  
  31.     WindowClass.style = CS_HREDRAW | CS_VREDRAW;  
  32.     WindowClass.lpfnWndProc = WindowProc;  
  33.     WindowClass.cbClsExtra = 0;  
  34.     WindowClass.cbWndExtra = 0;  
  35.     WindowClass.hInstance = hInstance;  
  36.     WindowClass.hIcon = LoadIcon(0, IDI_APPLICATION);  
  37.     WindowClass.hCursor = LoadCursor(0, IDC_ARROW);  
  38.     WindowClass.hbrBackground =reinterpret_cast<HBRUSH>(COLOR_WINDOW+1);  
  39.     WindowClass.lpszMenuName = 0;  
  40.     WindowClass.lpszClassName = szAppName;  
  41.     WindowClass.hIconSm = 0;  
  42.   
  43.     RegisterClassEx(&WindowClass);  
  44.   
  45.     hWnd = CreateWindow(szAppName,   
  46.                         TEXT("background"),   
  47.                         WS_OVERLAPPEDWINDOW,   
  48.                         CW_USEDEFAULT,  
  49.                         CW_USEDEFAULT,  
  50.                         CW_USEDEFAULT,  
  51.                         CW_USEDEFAULT,  
  52.                         nullptr,  
  53.                         nullptr,   
  54.                         hInstance,   
  55.                         nullptr);  
  56.       
  57.     ShowWindow(hWnd, nCmdShow);  
  58.     UpdateWindow(hWnd);  
  59.     while(GetMessage(&msg, 0, 0, 0) == TRUE)  
  60.     {  
  61.         TranslateMessage(&msg);  
  62.         DispatchMessage(&msg);  
  63.     }  
  64.     GdiplusShutdown(gdiplustoken);  
  65.     return static_cast<int>(msg.wParam);  
  66. }  
  67.   
  68. LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)  
  69. {  
  70.     HDC hdc;  
  71.     PAINTSTRUCT paintst;  
  72.     static int cxclient, cyclient;   //客户区长度和宽度  
  73.     static std::basic_ostringstream<TCHAR> ss1, ss2, ss3;  
  74.     static BackGroundBmp bmp;  
  75.     switch(message)  
  76.     {  
  77.   
  78.     case WM_CREATE:  
  79.         timer.StartTimer();  
  80.         bmp.Create(TEXT("i:\\background.jpg"));  
  81.         ss1.str(TEXT(""));  
  82.         ss1 << "CreateFPS: " << static_cast<int>(1 / timer.StopTimer());  
  83.         return 0;  
  84.   
  85.     case WM_ERASEBKGND:  
  86.         return 1;  
  87.   
  88.     case WM_SIZE:  
  89.         timer.StartTimer();  
  90.         cxclient = LOWORD(lParam);  
  91.         cyclient = HIWORD(lParam);  
  92.         bmp.FixSize(hWnd, cxclient, cyclient);  
  93.         ss2.str(TEXT(""));  
  94.         ss2 << "SizeFPS: " << static_cast<int>(1 / timer.StopTimer());  
  95.         return 0;  
  96.   
  97.     case WM_PAINT:  
  98.         hdc = BeginPaint(hWnd, &paintst);  
  99.         timer.StartTimer();  
  100.         bmp.Paint(hdc, paintst.rcPaint);  
  101.           
  102.         //输出size和fps  
  103.         ss3.str(TEXT(""));  //清空输出流  
  104.         ss3 << TEXT("PaintFPS: ") << static_cast<int>(1 / timer.StopTimer());  
  105.         PrintText(hWnd, ss1.str(), 0, 0);  
  106.         PrintText(hWnd, ss2.str(), 0, 20);  
  107.         PrintText(hWnd, ss3.str(), 0, 40);  
  108.         ss3.str(TEXT(""));  //清空输出流  
  109.         ss3 << TEXT("SIZE: ") << cxclient << TEXT(" ") << cyclient;  
  110.         PrintText(hWnd, ss3.str(), 0, 60);  
  111.         EndPaint(hWnd, &paintst);  
  112.         return 0;  
  113.       
  114.     case WM_DESTROY:  
  115.         PostQuitMessage(0);  
  116.         return 0;  
  117.       
  118.     default:  
  119.         return DefWindowProc(hWnd, message, wParam, lParam);  
  120.     }  
  121. }  
  122.   
  123. void PrintText(HWND hWnd, const std::basic_string<TCHAR>& str, int xPos, int yPos)  
  124. {  
  125.     HDC hdc = GetDC(hWnd);  
  126.     TextOut(hdc, xPos, yPos, str.c_str(), str.size());  
  127.     ReleaseDC(hWnd, hdc);  
  128. }  

[cpp] view plaincopy
  1. //backgroundbmp.h  
  2. //背景图片类  
  3. //只能用于桌面应用程序,不能在控制台用,受到gdi+的一些函数限制  
  4.   
  5. #pragma once  
  6. #include <windows.h>  
  7. #include <tchar.h>  
  8. #include <GdiPlus.h>  
  9.   
  10. using namespace Gdiplus;  
  11.   
  12. class BackGroundBmp  
  13. {  
  14.   
  15. public:  
  16.     BackGroundBmp()  
  17.         : m_pbmp(nullptr), m_membmp(nullptr), m_width(0), m_height(0) {}  
  18.     BackGroundBmp(const WCHAR* filename)  
  19.         : m_pbmp(nullptr), m_membmp(nullptr), m_width(0), m_height(0)  
  20.         {Create(filename);}  
  21.     virtual ~BackGroundBmp();  
  22.       
  23.     bool isOk() const {return m_pbmp != nullptr;}  
  24.     HBITMAP GetBmp() {return m_membmp;}  
  25.     int GetBmpWidth() const {return m_width;}  
  26.     int GetBmpHeight() const {return m_height;}  
  27.   
  28.     bool Create(const WCHAR* filename);  
  29.     bool FixSize(HWND hWnd, int cxWidth, int cxHeight);  
  30.     bool Paint(HDC hdc, const RECT& rect);  
  31.   
  32. private:  
  33.     BackGroundBmp(const BackGroundBmp&);  //not allow  
  34.     BackGroundBmp& operator=(const BackGroundBmp&);  //not allow  
  35.   
  36. private:  
  37.     Bitmap* m_pbmp;  //Gdi+ object  
  38.     HBITMAP m_membmp;  //gdi object  
  39.     int m_width;  
  40.     int m_height;  
  41. };  

[cpp] view plaincopy
  1. //backgroundbmp.cpp  
  2. #include "include\backgroundbmp.h"  
  3.   
  4. BackGroundBmp::~BackGroundBmp()  
  5. {  
  6.     DeleteObject(m_membmp);  
  7. }  
  8.   
  9. bool BackGroundBmp::Create(const WCHAR* filename)  
  10. {  
  11.     if (filename == nullptr)  
  12.         return false;  
  13.     m_pbmp = Bitmap::FromFile(filename);  
  14.     return (m_pbmp != nullptr);  
  15. }  
  16.   
  17. //效果不好,虽然速度快,缩小有失真  
  18. /* 
  19. bool BackGroundBmp::FixSize(HWND hWnd, int cxWidth, int cxHeight) 
  20. { 
  21.     if (!m_pbmp) 
  22.         return false; 
  23.      
  24.     m_width = cxWidth; 
  25.     m_height = cxHeight; 
  26.      
  27.     //取得原图长宽 
  28.     int bmpwidth = m_pbmp->GetWidth(); 
  29.     int bmpheight = m_pbmp->GetHeight(); 
  30.  
  31.     //计算x、y方向长宽比例 
  32.     double xscale = static_cast<double>(m_width) / bmpwidth; 
  33.     double yscale = static_cast<double>(m_height) / bmpheight; 
  34.  
  35.     //计算原图复制后的长宽,经过同比放大或缩小,截去超过的x部分或y部分,保证图片长宽比例不变形 
  36.     double fixscale = xscale > yscale ? xscale : yscale; 
  37.     //直接drawimage在内存中,比上一个版本快很多 
  38.     //删除旧的内存图 
  39.     DeleteObject(m_membmp); 
  40.      
  41.     HDC hdc = GetDC(hWnd); 
  42.      
  43.     //根据客户区大小建立新的内存图和内存DC 
  44.     m_membmp = CreateCompatibleBitmap(hdc, m_width, m_height); 
  45.     HDC destdc = CreateCompatibleDC(hdc); 
  46.     HDC sourcedc = CreateCompatibleDC(hdc); 
  47.     //选择内存DC绘图 
  48.     HBITMAP tempbmp; 
  49.     Status status = m_pbmp->GetHBITMAP(Color(255,255,255), &tempbmp); 
  50.     if (status != Ok) 
  51.         MessageBox(0,L"wrong",0,0); 
  52.     HGDIOBJ destobj = SelectObject(destdc, m_membmp); 
  53.     HGDIOBJ sourceobj = SelectObject(sourcedc, tempbmp); 
  54.  
  55.     //Graphics graphics(memdc); 
  56.      
  57.     //最佳图像质量 
  58.     //graphics.SetInterpolationMode(InterpolationModeHighQualityBicubic); 
  59.     SetStretchBltMode(sourcedc,STRETCH_HALFTONE); 
  60.     SetBrushOrgEx(sourcedc,0, 0, nullptr); 
  61.  
  62.     //直接画到内存DC中 
  63.     //graphics.DrawImage(m_pbmp, rect, 0, 0, bmpwidth, bmpheight, UnitPixel); 
  64.  
  65.     StretchBlt(destdc, 
  66.                bmpwidth * (xscale - fixscale) / 2,  
  67.                bmpheight * (yscale - fixscale) / 2, 
  68.                bmpwidth * fixscale, 
  69.                bmpheight * fixscale, 
  70.                sourcedc, 
  71.                0, 
  72.                0, 
  73.                bmpwidth, 
  74.                bmpheight, 
  75.                SRCCOPY); 
  76.      
  77.     SelectObject(sourcedc, sourceobj); 
  78.     DeleteDC(sourcedc); 
  79.     SelectObject(destdc, destobj); 
  80.     DeleteDC(destdc); 
  81.     ReleaseDC(hWnd, hdc); 
  82. } 
  83. */  
  84.   
  85. //速度慢,效果很好,无失真  
  86. bool BackGroundBmp::FixSize(HWND hWnd, int cxWidth, int cxHeight)  
  87. {  
  88.     //如果图像没有从文件载入,生成1个空的客户区大小的内存图像  
  89.     HDC hdc = GetDC(hWnd);  
  90.     m_width = cxWidth;  
  91.     m_height = cxHeight;  
  92.   
  93.     //删除旧的内存图  
  94.     DeleteObject(m_membmp);   
  95.     //根据客户区大小建立新的内存图和内存DC  
  96.     m_membmp = CreateCompatibleBitmap(hdc, m_width, m_height);  
  97.       
  98.     if (!m_pbmp)  
  99.     {  
  100.         ReleaseDC(hWnd, hdc);  
  101.         return false;  
  102.     }  
  103.       
  104.     //取得原图长宽  
  105.     int bmpwidth = m_pbmp->GetWidth();  
  106.     int bmpheight = m_pbmp->GetHeight();  
  107.   
  108.     //计算x、y方向长宽比例  
  109.     double xscale = static_cast<double>(m_width) / bmpwidth;  
  110.     double yscale = static_cast<double>(m_height) / bmpheight;  
  111.   
  112.     //计算原图复制后的长宽,经过同比放大或缩小,截去超过的x部分或y部分,保证图片长宽比例不变形  
  113.     double fixscale = xscale > yscale ? xscale : yscale;  
  114.     Rect rect(bmpwidth * (xscale - fixscale) / 2,  
  115.               bmpheight * (yscale - fixscale) / 2,  
  116.               bmpwidth * fixscale,  
  117.               bmpheight * fixscale);  
  118.       
  119.     HDC memdc = CreateCompatibleDC(hdc);  
  120.       
  121.     //选择内存DC绘图  
  122.     HGDIOBJ oldmap = SelectObject(memdc, m_membmp);  
  123.     Graphics graphics(memdc);  
  124.       
  125.     //最佳图像质量  
  126.     graphics.SetInterpolationMode(InterpolationModeHighQualityBicubic);  
  127.       
  128.     //直接画到内存DC中  
  129.     graphics.DrawImage(m_pbmp, rect, 0, 0, bmpwidth, bmpheight, UnitPixel);  
  130.       
  131.     SelectObject(memdc, oldmap);  
  132.     DeleteDC(memdc);  
  133.     ReleaseDC(hWnd, hdc);  
  134.     return true;  
  135. }  
  136.   
  137. bool BackGroundBmp::Paint(HDC hdc, const RECT& rect)  
  138. {  
  139.     HDC memdc = CreateCompatibleDC(hdc);  
  140.     HGDIOBJ oldbmp = SelectObject(memdc, m_membmp);  
  141.     BitBlt(hdc,   
  142.            rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top,   
  143.            memdc,   
  144.            rect.left, rect.top,   
  145.            SRCCOPY);  
  146.     SelectObject(memdc, oldbmp);  
  147.     DeleteDC(memdc);  
  148.     return true;  
  149. }  

[cpp] view plaincopy
  1. //hrtimer.h  
  2. //高精度计时器类  
  3. #pragma once  
  4. #include <Windows.h>  
  5.   
  6. class HRTimer  
  7. {  
  8. public:  
  9.     HRTimer(): frequency(1.0 / GetFrequency()) {}  
  10.     double GetFrequency()  
  11.     {  
  12.         LARGE_INTEGER proc_freq;  
  13.         ::QueryPerformanceFrequency(&proc_freq);  
  14.         return static_cast<double>(proc_freq.QuadPart);  
  15.     }  
  16.     void StartTimer()  
  17.     {  
  18.         DWORD_PTR oldmask = ::SetThreadAffinityMask(::GetCurrentThread(), 0);  
  19.         ::QueryPerformanceCounter(&start);  
  20.         ::SetThreadAffinityMask(::GetCurrentThread(), oldmask);  
  21.     }  
  22.     double StopTimer()  
  23.     {  
  24.         DWORD_PTR oldmask = ::SetThreadAffinityMask(::GetCurrentThread(), 0);  
  25.         ::QueryPerformanceCounter(&stop);  
  26.         ::SetThreadAffinityMask(::GetCurrentThread(), oldmask);  
  27.         return ((stop.QuadPart - start.QuadPart) * frequency);  
  28.     }  
  29. private:  
  30.     LARGE_INTEGER start;  
  31.     LARGE_INTEGER stop;  
  32.     double frequency;  
  33. };  
原文地址:http://blog.csdn.net/ananluowei/article/details/12081401