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

来源:互联网 发布:mysql 存储过程 循环 编辑:程序博客网 时间:2024/05/20 14:26

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

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

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

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

void OnPaint(HWND hWnd){Bitmap bmp(TEXT("i:\\background.jpg"));  //用图片文件创建Bitmap对象HDC hdc(GetDC(hWnd));  //取得设备环境句柄RECT rect;GetClientRect(hWnd, &rect);   //获取窗口坐标UINT clientWidth = rect.right - rect.left;  //计算宽度UINT clientHeight = rect.bottom - rect.top; //计算高度Graphics gs(hdc); //创建Graphics对象gs.DrawImage(&bmp, 0, 0, clientWidth, clientHeight); //画图ReleaseDC(hWnd, hdc); //释放设备环境句柄}

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

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

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类,用于创建背景图片,调整图片尺寸,画到窗口中。

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

成员变量很简单,4个

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

        bool Create(const WCHAR* filename);        bool FixSize(HWND hWnd, int cxWidth, int cxHeight);        bool Paint(HDC hdc, const RECT& rect);

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

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

double xscale = static_cast<double>(m_width) / bmpwidth;double yscale = static_cast<double>(m_height) / bmpheight;
bmpwidth、bmpheight是原图像的尺寸

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

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

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

double fixscale = xscale > yscale ? xscale : yscale;Rect rect(bmpwidth * (xscale - fixscale) / 2,  bmpheight * (yscale - fixscale) / 2,  bmpwidth * fixscale,  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,把几个文件复制进去就行了。注意修改头文件的路径和图片的文件名(对话框和控件什么的还不会)

//main.cpp#include <Windows.h>#include <tchar.h>#include <GdiPlus.h>#include <sstream>#include "include\hrtimer.h"#include "include\backgroundbmp.h"#pragma comment(lib, "gdiplus.lib")using namespace Gdiplus;HRTimer timer;LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);void PrintText(HWND hWnd, const std::basic_string<TCHAR>& string, int xPos, int yPos);int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow){WNDCLASSEX WindowClass;static LPCTSTR szAppName = TEXT("myApp");HWND hWnd;MSG msg;GdiplusStartupInput gdiplusstartinput;ULONG_PTR gdiplustoken;GdiplusStartup(&gdiplustoken, &gdiplusstartinput, 0);WindowClass.cbSize = sizeof(WNDCLASSEX);WindowClass.style = CS_HREDRAW | CS_VREDRAW;WindowClass.lpfnWndProc = WindowProc;WindowClass.cbClsExtra = 0;WindowClass.cbWndExtra = 0;WindowClass.hInstance = hInstance;WindowClass.hIcon = LoadIcon(0, IDI_APPLICATION);WindowClass.hCursor = LoadCursor(0, IDC_ARROW);WindowClass.hbrBackground =reinterpret_cast<HBRUSH>(COLOR_WINDOW+1);WindowClass.lpszMenuName = 0;WindowClass.lpszClassName = szAppName;WindowClass.hIconSm = 0;RegisterClassEx(&WindowClass);hWnd = CreateWindow(szAppName, TEXT("background"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,nullptr,nullptr, hInstance, nullptr);ShowWindow(hWnd, nCmdShow);UpdateWindow(hWnd);while(GetMessage(&msg, 0, 0, 0) == TRUE){TranslateMessage(&msg);DispatchMessage(&msg);}GdiplusShutdown(gdiplustoken);return static_cast<int>(msg.wParam);}LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam){HDC hdc;PAINTSTRUCT paintst;static int cxclient, cyclient;   //客户区长度和宽度static std::basic_ostringstream<TCHAR> ss1, ss2, ss3;static BackGroundBmp bmp;switch(message){case WM_CREATE:timer.StartTimer();bmp.Create(TEXT("i:\\background.jpg"));ss1.str(TEXT(""));ss1 << "CreateFPS: " << static_cast<int>(1 / timer.StopTimer());return 0;case WM_ERASEBKGND:return 1;case WM_SIZE:timer.StartTimer();cxclient = LOWORD(lParam);cyclient = HIWORD(lParam);bmp.FixSize(hWnd, cxclient, cyclient);ss2.str(TEXT(""));ss2 << "SizeFPS: " << static_cast<int>(1 / timer.StopTimer());return 0;case WM_PAINT:hdc = BeginPaint(hWnd, &paintst);timer.StartTimer();bmp.Paint(hdc, paintst.rcPaint);//输出size和fpsss3.str(TEXT(""));  //清空输出流ss3 << TEXT("PaintFPS: ") << static_cast<int>(1 / timer.StopTimer());PrintText(hWnd, ss1.str(), 0, 0);PrintText(hWnd, ss2.str(), 0, 20);PrintText(hWnd, ss3.str(), 0, 40);ss3.str(TEXT(""));  //清空输出流ss3 << TEXT("SIZE: ") << cxclient << TEXT(" ") << cyclient;PrintText(hWnd, ss3.str(), 0, 60);EndPaint(hWnd, &paintst);return 0;case WM_DESTROY:PostQuitMessage(0);return 0;default:return DefWindowProc(hWnd, message, wParam, lParam);}}void PrintText(HWND hWnd, const std::basic_string<TCHAR>& str, int xPos, int yPos){HDC hdc = GetDC(hWnd);TextOut(hdc, xPos, yPos, str.c_str(), str.size());ReleaseDC(hWnd, hdc);}

//backgroundbmp.h//背景图片类//只能用于桌面应用程序,不能在控制台用,受到gdi+的一些函数限制#pragma once#include <windows.h>#include <tchar.h>#include <GdiPlus.h>using namespace Gdiplus;class BackGroundBmp{public:BackGroundBmp(): m_pbmp(nullptr), m_membmp(nullptr), m_width(0), m_height(0) {}BackGroundBmp(const WCHAR* filename): m_pbmp(nullptr), m_membmp(nullptr), m_width(0), m_height(0){Create(filename);}virtual ~BackGroundBmp();bool isOk() const {return m_pbmp != nullptr;}HBITMAP GetBmp() {return m_membmp;}int GetBmpWidth() const {return m_width;}int GetBmpHeight() const {return m_height;}bool Create(const WCHAR* filename);bool FixSize(HWND hWnd, int cxWidth, int cxHeight);bool Paint(HDC hdc, const RECT& rect);private:BackGroundBmp(const BackGroundBmp&);  //not allowBackGroundBmp& operator=(const BackGroundBmp&);  //not allowprivate:Bitmap* m_pbmp;  //Gdi+ objectHBITMAP m_membmp;  //gdi objectint m_width;int m_height;};

//backgroundbmp.cpp#include "include\backgroundbmp.h"BackGroundBmp::~BackGroundBmp(){DeleteObject(m_membmp);}bool BackGroundBmp::Create(const WCHAR* filename){if (filename == nullptr)return false;m_pbmp = Bitmap::FromFile(filename);return (m_pbmp != nullptr);}//效果不好,虽然速度快,缩小有失真/*bool BackGroundBmp::FixSize(HWND hWnd, int cxWidth, int cxHeight){if (!m_pbmp)return false;m_width = cxWidth;m_height = cxHeight;//取得原图长宽int bmpwidth = m_pbmp->GetWidth();int bmpheight = m_pbmp->GetHeight();//计算x、y方向长宽比例double xscale = static_cast<double>(m_width) / bmpwidth;double yscale = static_cast<double>(m_height) / bmpheight;//计算原图复制后的长宽,经过同比放大或缩小,截去超过的x部分或y部分,保证图片长宽比例不变形double fixscale = xscale > yscale ? xscale : yscale;//直接drawimage在内存中,比上一个版本快很多//删除旧的内存图DeleteObject(m_membmp);HDC hdc = GetDC(hWnd);//根据客户区大小建立新的内存图和内存DCm_membmp = CreateCompatibleBitmap(hdc, m_width, m_height);HDC destdc = CreateCompatibleDC(hdc);HDC sourcedc = CreateCompatibleDC(hdc);//选择内存DC绘图HBITMAP tempbmp;Status status = m_pbmp->GetHBITMAP(Color(255,255,255), &tempbmp);if (status != Ok)MessageBox(0,L"wrong",0,0);HGDIOBJ destobj = SelectObject(destdc, m_membmp);HGDIOBJ sourceobj = SelectObject(sourcedc, tempbmp);//Graphics graphics(memdc);//最佳图像质量//graphics.SetInterpolationMode(InterpolationModeHighQualityBicubic);SetStretchBltMode(sourcedc,STRETCH_HALFTONE);SetBrushOrgEx(sourcedc,0, 0, nullptr);//直接画到内存DC中//graphics.DrawImage(m_pbmp, rect, 0, 0, bmpwidth, bmpheight, UnitPixel);    StretchBlt(destdc,   bmpwidth * (xscale - fixscale) / 2,    bmpheight * (yscale - fixscale) / 2,   bmpwidth * fixscale,   bmpheight * fixscale,   sourcedc,   0,   0,   bmpwidth,   bmpheight,   SRCCOPY);SelectObject(sourcedc, sourceobj);DeleteDC(sourcedc);SelectObject(destdc, destobj);DeleteDC(destdc);ReleaseDC(hWnd, hdc);}*///速度慢,效果很好,无失真bool BackGroundBmp::FixSize(HWND hWnd, int cxWidth, int cxHeight){//如果图像没有从文件载入,生成1个空的客户区大小的内存图像HDC hdc = GetDC(hWnd);m_width = cxWidth;m_height = cxHeight;//删除旧的内存图DeleteObject(m_membmp);//根据客户区大小建立新的内存图和内存DCm_membmp = CreateCompatibleBitmap(hdc, m_width, m_height);if (!m_pbmp){ReleaseDC(hWnd, hdc);return false;}//取得原图长宽int bmpwidth = m_pbmp->GetWidth();int bmpheight = m_pbmp->GetHeight();//计算x、y方向长宽比例double xscale = static_cast<double>(m_width) / bmpwidth;double yscale = static_cast<double>(m_height) / bmpheight;//计算原图复制后的长宽,经过同比放大或缩小,截去超过的x部分或y部分,保证图片长宽比例不变形double fixscale = xscale > yscale ? xscale : yscale;Rect rect(bmpwidth * (xscale - fixscale) / 2,  bmpheight * (yscale - fixscale) / 2,  bmpwidth * fixscale,  bmpheight * fixscale);HDC memdc = CreateCompatibleDC(hdc);//选择内存DC绘图HGDIOBJ oldmap = SelectObject(memdc, m_membmp);Graphics graphics(memdc);//最佳图像质量graphics.SetInterpolationMode(InterpolationModeHighQualityBicubic);//直接画到内存DC中graphics.DrawImage(m_pbmp, rect, 0, 0, bmpwidth, bmpheight, UnitPixel);SelectObject(memdc, oldmap);DeleteDC(memdc);ReleaseDC(hWnd, hdc);return true;}bool BackGroundBmp::Paint(HDC hdc, const RECT& rect){HDC memdc = CreateCompatibleDC(hdc);HGDIOBJ oldbmp = SelectObject(memdc, m_membmp);BitBlt(hdc,    rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top,    memdc,    rect.left, rect.top,    SRCCOPY);SelectObject(memdc, oldbmp);DeleteDC(memdc);return true;}

//hrtimer.h//高精度计时器类#pragma once#include <Windows.h>class HRTimer{public:HRTimer(): frequency(1.0 / GetFrequency()) {}double GetFrequency(){LARGE_INTEGER proc_freq;::QueryPerformanceFrequency(&proc_freq);return static_cast<double>(proc_freq.QuadPart);}void StartTimer(){DWORD_PTR oldmask = ::SetThreadAffinityMask(::GetCurrentThread(), 0);::QueryPerformanceCounter(&start);::SetThreadAffinityMask(::GetCurrentThread(), oldmask);}double StopTimer(){DWORD_PTR oldmask = ::SetThreadAffinityMask(::GetCurrentThread(), 0);::QueryPerformanceCounter(&stop);::SetThreadAffinityMask(::GetCurrentThread(), oldmask);return ((stop.QuadPart - start.QuadPart) * frequency);}private:LARGE_INTEGER start;LARGE_INTEGER stop;double frequency;};


原创粉丝点击