利用GDI+基於WIN32實現桌面雪花效果(一)
前言:畢業論文寫完了,閒著沒事幹,研究研究這些一直想做的效果,先從桌面飄雪花開始,下面是過程記錄。最後給出程式碼,供大家參考。
效果圖:(桌面區域性截圖)
一、建立空WIN32工程並初始化
1、建立空WIN32工程(snow)
2、設定
專案-》屬性-》配置屬性-》MFC的使用-》在靜態庫中使用MFC
3、初始化GDI+和MFC庫函式
新建一個Common.h檔案,用來存放一些公用的結構體及程式碼,並在其中初始化GDI+,在其中放下如下程式碼來初始化GDI+
#include <gdiplus.h> #pragma comment(lib,"GdiPlus.lib") using namespace Gdiplus;
在stdafx.h中新增如下程式碼:(新增對MFC類和所有函式庫支援)
#include <math.h> #define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS // 某些 CString 建構函式將是顯式的 // 關閉 MFC 對某些常見但經常可放心忽略的警告訊息的隱藏 #define _AFX_ALL_WARNINGS #include <afxwin.h> // MFC 核心元件和標準組件 #include <afxext.h> // MFC 擴充套件 #include <afxdisp.h> // MFC 自動化類 #ifndef _AFX_NO_OLE_SUPPORT #include <afxdtctl.h> // MFC 對 Internet Explorer 4 公共控制元件的支援 #endif #ifndef _AFX_NO_AFXCMN_SUPPORT #include <afxcmn.h> // MFC 對 Windows 公共控制元件的支援 #endif // _AFX_NO_AFXCMN_SUPPORT
二、新建應用程式入口類(派生自CWinApp)
新建一個類(CSnowApp),該類派生自MFC的CWinApp類,(在標頭檔案右擊-》新增-》類)
標頭檔案程式碼如下:
這裡重寫了CWinApp的兩個函式,InitInstance()和ExitInstance();#pragma once #include "Common.h" // CSnowApp class CSnowApp : public CWinApp { DECLARE_DYNCREATE(CSnowApp) public: CSnowApp(); // 動態建立所使用的受保護的建構函式 virtual ~CSnowApp(); public: virtual BOOL InitInstance(); virtual int ExitInstance(); protected: DECLARE_MESSAGE_MAP() public: ULONG_PTR m_gdiplusToken; BOOL RegisterClass(LPCTSTR lpszClassName); }; extern CSnowApp theApp;
然後自己寫了一個視窗類註冊函式,完整程式碼如下:
BOOL CSnowApp::RegisterClass(LPCTSTR lpszClassName)
{
WNDCLASS wndcls;
memset(&wndcls,0,sizeof(WNDCLASS));
wndcls.style=CS_DBLCLKS|CS_HREDRAW|CS_VREDRAW;
wndcls.lpfnWndProc=::DefWindowProc;
wndcls.hInstance=AfxGetInstanceHandle();
wndcls.hIcon=NULL;
wndcls.hCursor=::LoadCursor(NULL,IDC_ARROW);
wndcls.hbrBackground=(HBRUSH)(COLOR_BTNFACE+1);
wndcls.lpszMenuName=NULL;
wndcls.lpszClassName=lpszClassName;
if(!AfxRegisterClass(&wndcls))
{
TRACE("Class Registration Failed\n");
return FALSE;
}
return TRUE;
}
BOOL CSnowApp::InitInstance()
{
// TODO: 在此執行任意逐執行緒初始化
//初始化GDI+
GdiplusStartupInput gdiplusStartupInput;
GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput, NULL);
//初始化應用程式
CWinApp::InitInstance();
//註冊視窗類
if (!RegisterClass(L"CometAnimationUI"))
{
return FALSE;
}
//建立視窗
CSnowWindow snowWnd;
snowWnd.Create(NULL);
//將snowWnd作為主窗體HWND,傳給m_pMainWnd,供其顯示窗體
m_pMainWnd=&snowWnd;
Run();
return TRUE;
}
int CSnowApp::ExitInstance()
{
// TODO: 在此執行任意逐執行緒清理
GdiplusShutdown(m_gdiplusToken);
return CWinApp::ExitInstance();
}
這裡主要講解InitInstance()的流程,這是應用程式初始化函式,它首先對GDI+進行初始化,然後註冊視窗類,利用CSowWnd類建立視窗,然後將snowWnd作為主窗體HWND,傳給m_pMainWnd,供其顯示窗體。最後利用Run()函式進入訊息迴圈。流程與WIN32窗體的建立過程一樣,只是這裡是經過CWinApp類封裝過的,所以流程顯得不那麼明顯。三、雪花窗體顯示
這裡分析兩個問題:
- 我們並不是將每個雪花建立一個視窗,而是將當前螢幕做為窗體,在它上面畫圖而已。
- 雪花的種類是有限的,即原始圖片,這裡載入了六個,而大家可以看到,整個螢幕的雪花量卻是很大的,所以我們要建一個基類完成原始影象載入、窗體重新整理等功能。而雪花類則建立在此基類的基礎上,完成雪花的下落、移動等功能。
1、定義單個雪花圖片物件
該物件包含每個雪花顯示所需的所有資訊,結構體定義如下:typedef struct tagAnimationImage
{
Gdiplus::Image* pImage;
int X; //圖片的X座標
int Y; //圖片的Y座標
int Width; //圖片顯示高度
int Height; //圖片顯示寬度
int Angle; //旋轉角度
bool firstInit; //是否初步初始化,用於在初次初始化時設定該雪花是往左走還是往右走還是直線下落
int OffsetMode; //圖片的行走方式,向左、向右、向下三種
} AnimationImage, *PAnimationImage,*LPAnimationImage;
2、雪花窗體基類CLayeredWnd(派生自CWnd)
CLayeredWnd標頭檔案如下:
class CLayeredWnd : public CWnd
{
DECLARE_DYNAMIC(CLayeredWnd)
public:
CLayeredWnd();
virtual ~CLayeredWnd();
protected:
DECLARE_MESSAGE_MAP()
public:
int m_nWidth;
int m_nHeight;
CArray<LPAnimationImage,LPAnimationImage> m_ImageArray;//圖片陣列
int m_ImageCount;//圖片數量
public:
// 載入圖片
BOOL LoadImage(LPAnimationImage pImage,LPCTSTR lpName);
// 新增圖片到陣列
LPAnimationImage AddImage(LPCTSTR lpName);
// 釋放圖片陣列
void ReleaseImage();
// 重新繪製視窗
void ReDrawWindow(void);
// 虛擬函式 繪製視窗
virtual void OnDrawWindow(Gdiplus::Graphics* pGraphics);
};
成員變數講解:
m_ImageArray:儲存原始圖片,即載入的六張雪花圖;
m_ImageCount:陣列的長度,即儲存的原始圖片的個數;
成員函式就不講解功能了,每個函式上面都有標註;
具體實現:
載入圖片:LoadImage()
BOOL CLayeredWnd::LoadImage(LPAnimationImage pImage,LPCTSTR lpPath)
{
//初始化空間
if(pImage->pImage)
{
delete pImage->pImage;
pImage->pImage=NULL;
}
ZeroMemory(pImage,sizeof(AnimationImage));
//載入圖片
pImage->pImage = Gdiplus::Image::FromFile(lpPath);
if(!pImage->pImage)
return FALSE;
pImage->Width=pImage->pImage->GetWidth();
pImage->Height=pImage->pImage->GetHeight();
return TRUE;
}
根據給定的地址載入圖片,並設計圖片的高度和寬度資訊。增加圖片:AddImage()
LPAnimationImage CLayeredWnd::AddImage(LPCTSTR lpPath)
{
LPAnimationImage pImage=new AnimationImage;
ZeroMemory(pImage,sizeof(AnimationImage));
LoadImage(pImage,lpPath);
m_ImageArray.Add(pImage);
m_ImageCount=m_ImageArray.GetCount();
return pImage;
}
根據給定的路徑載入圖片,並將其新增到陣列中;重繪視窗:ReDrawWindow
void CLayeredWnd::ReDrawWindow(void)
{
HDC hDC=::GetDC(m_hWnd);
HDC hMemDC=::CreateCompatibleDC(hDC);
BITMAPINFO bitmapinfo;
bitmapinfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bitmapinfo.bmiHeader.biBitCount = 32;
bitmapinfo.bmiHeader.biHeight = m_nHeight;
bitmapinfo.bmiHeader.biWidth = m_nWidth;
bitmapinfo.bmiHeader.biPlanes = 1;
bitmapinfo.bmiHeader.biCompression=BI_RGB;
bitmapinfo.bmiHeader.biXPelsPerMeter=0;
bitmapinfo.bmiHeader.biYPelsPerMeter=0;
bitmapinfo.bmiHeader.biClrUsed=0;
bitmapinfo.bmiHeader.biClrImportant=0;
bitmapinfo.bmiHeader.biSizeImage = bitmapinfo.bmiHeader.biWidth * bitmapinfo.bmiHeader.biHeight * bitmapinfo.bmiHeader.biBitCount / 8;
HBITMAP hBitmap=CreateDIBSection (hMemDC,&bitmapinfo, 0,NULL, 0, 0);
HBITMAP hOldBitmap = (HBITMAP)SelectObject (hMemDC,hBitmap);
Graphics g(hMemDC);
//-------------------------------------------------------------
OnDrawWindow(&g);
//設定透明視窗-------------------------------------------------
CPoint DestPt(0,0);
CSize psize(m_nWidth,m_nHeight);
BLENDFUNCTION blendFunc32bpp;
blendFunc32bpp.AlphaFormat = AC_SRC_ALPHA;
blendFunc32bpp.BlendFlags = 0;
blendFunc32bpp.BlendOp = AC_SRC_OVER;
blendFunc32bpp.SourceConstantAlpha = 255;
::UpdateLayeredWindow(m_hWnd,hDC,NULL,&psize,hMemDC,&DestPt,0,&blendFunc32bpp,ULW_ALPHA);
//釋放資源-------------------------------------------------
::SelectObject (hMemDC,hOldBitmap);
::DeleteObject(hBitmap);
::DeleteDC(hMemDC);
::ReleaseDC(m_hWnd,hDC);
}
這裡注意一點:首先是建立一個空白畫布,然後呼叫虛擬函式OnDrawWindow()來畫圖,由於下面我們將在CLayeredWnd的基礎上派生出CSnowWnd,所以在CSnowWindow我們會重新實現這個繪圖函式,在這個函式中實現雪花的下移和平移功能。
最後在整個畫布完成以後,利用UpdateLayeredWindow更新到窗體上顯示出來。
3、雪花顯示窗體類CSnowWindow
標頭檔案內容如下:
class CSnowWindow :
public CLayeredWnd
{
public:
CSnowWindow(void);
~CSnowWindow(void);
private:
CArray<LPAnimationImage,LPAnimationImage> m_SnowArray;//雪花圖片陣列
int m_SnowCount;//雪花圖片數量
int m_RowCount;//每行圖片數量
int m_AllCount;//可顯示圖片總數
public:
DECLARE_MESSAGE_MAP()
afx_msg void OnTimer(UINT_PTR nIDEvent);
public:
//建立窗體
BOOL Create(HWND hWndParent=NULL);
//初始化初次顯示介面
void InitImage();
//新增原始圖片資源到CLayeredWnd的m_ImageArray中
void AddResImg(LPCTSTR lpName,int nAngle);
//向m_SnowArray新增雪花圖片
void AddSnow(int nCount);
//下移雪花
void DownSnow();
//根據上限和下限,隨機產生一個數字
int GetRndNum(int nMin,int nMax);
public:
//繪製當前介面,重寫CLayeredWnd的此虛擬函式
virtual void OnDrawWindow(Gdiplus::Graphics* pGraphics);
};
由於每個變數和函式都對應有標註,這裡就不再敘述了,下面看具體實現:void CSnowWindow::InitImage()
{
AddResImg(_T("C:\\Snow01.png"),0);
AddResImg(_T("C:\\Snow02.png"),0);
AddResImg(_T("C:\\Snow03.png"),-1);
AddResImg(_T("C:\\Snow04.png"),-1);
AddResImg(_T("C:\\Snow05.png"),0);
AddResImg(_T("C:\\Snow06.png"),0);
AddResImg(_T("C:\\Snow07.png"),0);
AddResImg(_T("C:\\Snow08.png"),0);
AddResImg(_T("C:\\Snow09.png"),0);
AddResImg(_T("C:\\Snow10.png"),0);
AddResImg(_T("C:\\Snow11.png"),0);
AddResImg(_T("C:\\Snow12.png"),0);
m_RowCount=m_nWidth/(m_ImageArray[0]->Width+20);
m_AllCount=m_RowCount*(m_nHeight/(m_ImageArray[0]->Height+20));
AddSnow(m_RowCount);
}
//新增要顯示的雪花圖片到CLayeredWnd的m_ImageArray中
void CSnowWindow::AddResImg(LPCTSTR lpName,int nAngle)
{
LPAnimationImage pImage=AddImage(lpName);
pImage->Angle=nAngle;
}
//新增雪花圖片
void CSnowWindow::AddSnow(int nCount)
{
for(int i=0;i<nCount;i++)
{
//隨機取一張圖片
int nIndex=GetRndNum(0,m_ImageCount-1);
LPAnimationImage pImage=new AnimationImage;
LPAnimationImage pSrcImage=m_ImageArray[nIndex];
CopyMemory(pImage,pSrcImage,sizeof(AnimationImage));
//隨機設定圖片的初始位置
pImage->X=GetRndNum(0,m_nWidth);
pImage->Y=0-GetRndNum(pImage->Height,pImage->Height*2);
//隨機縮放圖片
float f=(float)GetRndNum(50,100);
f=f/(float)100;
pImage->Width=(int)((float)pImage->Width*f);
pImage->Height=(int)((float)pImage->Height*f);
pImage->firstInit=true;//初始化為TRUE,此引數用來判定是否首次初始化OffsetMode引數
m_SnowArray.Add(pImage);
}
m_SnowCount=m_SnowArray.GetCount();
}
講解:1、AddResImg()實現根據圖片路徑將圖片載入到CLayeredWnd的m_ImageArray中
2、AddSnow()實現隨機從m_ImageArray中取一張圖片,並對其初始化,最後將其新增到m_SnowArray中。
3、InitImage()就是實現了初次顯示的雪花圖片的初始化工作,AddSnow(m_RowCount);將每行所具有的雪花個數的圖片,以第一行顯示出來。
在初次頂端雪花初始化完成以後,就要定時將雪花圖片移動(下移和平移)
1、設定定時器
由於是定時重新整理當前介面以使雪花下移,所以必定要設定定時器,並對WM_TIMER訊息進行處理,下面看對WM_TIMER訊息處理的程式碼:
void CSnowWindow::OnTimer(UINT_PTR nIDEvent)
{
// TODO: 在此新增訊息處理程式程式碼和/或呼叫預設值
CLayeredWnd::OnTimer(nIDEvent);
DownSnow();
}
從這裡可以看到,每次產生的定時脈衝所做的事情就是將雪花下移。下面看DownSnow()的程式碼:
//下移雪花圖片
void CSnowWindow::DownSnow()
{
int nTop=25;
for(int i=m_SnowCount-1;i>=0;i--)
{
LPAnimationImage pImage=m_SnowArray[i];
pImage->Y+=5;//下移
if(pImage->Y>m_nHeight)//超出螢幕高度
{
m_SnowArray.RemoveAt(i);//移除這張圖片
delete pImage;
continue;//轉到下一次迴圈
}
//-------------
if(pImage->Y<nTop)
nTop=pImage->Y;//找到當前最後一個雪花離螢幕頂部的距離
//橫向移動
if (pImage->firstInit)
{
pImage->firstInit=false;
pImage->OffsetMode=GetRndNum(1,3);
}
switch (pImage->OffsetMode)
{
case 1:
pImage->X--;
break;
case 2:
pImage->X++;
break;
}
}
m_SnowCount=m_SnowArray.GetCount();
int nCount=m_AllCount-m_SnowCount;
if(nCount>0 && nTop>20)//設定每行的豎向間隔,這裡設定每20個畫素顯示一行雪花
{
if(nCount>m_RowCount)
nCount=m_RowCount;
AddSnow(nCount);
}
ReDrawWindow();
}
講解:1、for迴圈中對每個雪花進行平移和下移。
2、如何產生下一行雪花。
注意這裡有幾句程式碼:
首先定義了一個距離,25個畫素。
int nTop=25;
這句處在FOR迴圈內,由於pImage->Y每次都會遞加5,所以到最後就會出現一種情況,其它雪花縱座標都大於25,只有一個雪花的縱座標小於25,所以這句的目的就是查詢最後一個仍小於nTop的值。由於Y值每次遞加5,所以最後一個Y值肯定落在20-25之間。
if(pImage->Y<nTop)
nTop=pImage->Y;//找到當前最後一個雪花離螢幕頂部的距離
由於當雪花落在螢幕邊界外後,會將雪花從陣列中刪除,而且隨著雪花的下落,螢幕上還要繼續落雪花,新產生的行所具有的要求是:
(1)、數量不能超,而且不能少
這裡使用int nCount=m_AllCount-m_SnowCount;nCount=m_RowCount;來設定每行要顯示的雪花數量。
(2)、每行雪花的行距
上面說了利用nTop來找到最後一個雪花離螢幕的距離(20-25),所以當nTop>20的時候,說明是時候顯示下一行了,這時才可以顯示,不然就擠在一塊了。
m_SnowCount=m_SnowArray.GetCount();
int nCount=m_AllCount-m_SnowCount;
if(nCount>0 && nTop>20)//設定每行的豎向間隔,這裡設定每20個畫素顯示一行雪花
{
if(nCount>m_RowCount)
nCount=m_RowCount;
AddSnow(nCount);
}
3、呼叫CLayeredWnd的ReDrawWindow()
由於在ReDrawWindow中呼叫了虛擬函式OnDrawWindow(),根據虛擬函式性質,會呼叫CSnowWindow重寫的OnDrawWindow函式,看OnDrawWindow的實現:
// 虛擬函式 繪製視窗
void CSnowWindow::OnDrawWindow(Gdiplus::Graphics* pGraphics)
{
for(int i=0;i<m_SnowCount;i++)
{
LPAnimationImage pImage=m_SnowArray[i];
pGraphics->DrawImage(pImage->pImage,pImage->X,pImage->Y,pImage->Width,pImage->Height);
}
}
實現方法就是將每一個雪花圖片重新根據新座標繪製出來,僅而而已。到這裡,本文雪花飄落相關的東東已經介紹完成了,下篇在此基礎上完成雪花在下落過程中實現旋轉的功能。
相關網頁: