【可以显示gif图片的控件】
闲暇之余,自己做个东西,需要显示gif图片,于是乎,有了今天的这个作品,首发于【编程论坛】。花费一天时间打造,时间仓促,难免有不足之处,欢迎真正懂得人拍砖!
先说一下起初的一些想法吧,让图片动态显示,其实就是把图片中的一帧一帧取出来,然后根据每帧的延迟时间,觉得显示下一帧图像,这样看起来就是动态的。
这个过程难免要考虑:1)线程(网上有个GifPicture使用的是线程,代码量比较多,感兴趣的朋友可以去找一下)、2)定时器
在线程中显示图片,这个好像不太方便,而且为一个小小的控件专门开一个线程,未免有点大材小用了,同时,如果我要显示10张gif图片,岂不是开10个线程,线程之间来回的切换,这个效率可想而知了。当然了,有些人就是愿意开辟线程,我也没招!
对于我个人而言,我喜欢使用定时器来完成这件事,它的不好处就是精度不够(除非你开多媒体定时器),同时定时器消息的优先级比较低,可能会出现画面偶尔的不流畅,但是容易控制,所以我采纳了定时器。
在图片操作方面,GDI+比GDI好的不是一点半点,简单易学,因此我采用了Image这个类来加载显示gif图片(具体的细节,代码中有注释,要想了解更多的gif知识或Image知识,请百度)
不足之处:在调用Image::SelectActiveFrame()函数时,会报告 0x4aeee2e0 处最可能的异常: 0x80000001: 尚未实现的问题,根别人回答应该是Image不是线程安全的,但是起码我实验的时候,程序未出现异常(使用我代码的朋友,如果发现什么问题,请告知我,先谢过您的支持!),对于这块,我打算有时间尝试另一种方法(但是别的方法是否可行,尚未得知),好了,废话不多说,直接贴代码(因为代码量不多,所以直接贴出来)
测试程序:
程序代码:
#ifndef CGIF_STATIC_H #define CGIF_STATIC_H #include <GdiPlus.h> using namespace Gdiplus; #pragma comment(lib,"GdiPlus.lib") // 定时器ID #define IDT_DISPLAY_GIF 5000 class CGifStatic : public CStatic { DECLARE_DYNAMIC(CGifStatic) public: CGifStatic(); virtual ~CGifStatic(); DECLARE_MESSAGE_MAP() protected: /**自定义函数**/ // 显示gif图片 void DisplayGifImage(); // 绘制背景色 void DrawBackground(); protected: /**重载函数**/ virtual void PreSubclassWindow(); virtual BOOL PreTranslateMessage(MSG *pMsg); afx_msg BOOL OnEraseBkgnd(CDC *pDC); afx_msg void OnPaint(); afx_msg void OnTimer(UINT_PTR nIDEvent); public: /**对外方法**/ // 加载gif图片 BOOL LoadGifImage(LPCTSTR lpszFileName); // 开始显示gif图片 BOOL StartDisplay(); // 暂停/显示 void PauseDisplay(BOOL bPause); // 停止显示gif图片 void StopDisplay(); private: //内存DC 对象 CDC m_MemDC; // CBitmap 对象 CBitmap m_MemBmp; // 控件的大小 int m_cxWnd; int m_cyWnd; // 当前图像帧的延迟时间 LONG m_lCurDelayTime; // 标记是否删除WM_TIMER消息 BOOL m_bDelTimerMsg; // 要显示的gif图片 Image *m_pGifImage; // gif图片的帧的个数 UINT m_nNumOfFrame; // 图像帧的序号 UINT m_nIndexOfFrame; // 保存gif图片属性 PropertyItem *m_pPropertyItem; ULONG_PTR gdiplusToken; }; #endif //CGIF_STATIC_H
程序代码:
#include "CGifStatic.h" IMPLEMENT_DYNAMIC(CGifStatic, CStatic) CGifStatic::CGifStatic() { m_bDelTimerMsg = FALSE; m_pGifImage = NULL; GdiplusStartupInput gdiplusStartupInput; GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL); } CGifStatic::~CGifStatic() { if(NULL != m_pPropertyItem) { delete []m_pPropertyItem; m_pPropertyItem = NULL; } GdiplusShutdown(gdiplusToken); } BEGIN_MESSAGE_MAP(CGifStatic, CStatic) ON_WM_TIMER() ON_WM_ERASEBKGND() ON_WM_PAINT() END_MESSAGE_MAP() BOOL CGifStatic::LoadGifImage(LPCTSTR lpszFileName) { // 加载gif图片之前,应确保调用StopDisplay()终止当前显示的gif图片 if(NULL != m_pGifImage) { return FALSE; } m_pGifImage = new Image(lpszFileName); if(m_pGifImage->GetLastStatus() == Ok) { UINT nNumOfDimensions; // 获取gif图像的“维度”个数,一个维度中包含有不定数目的图像帧,对于gif图像而言,其维度为1 nNumOfDimensions = m_pGifImage->GetFrameDimensionsCount(); if(nNumOfDimensions) { GUID *pDimensionsIDs = new GUID[nNumOfDimensions]; // 获取gif图像“维度”所读应的GUID列表 m_pGifImage->GetFrameDimensionsList(pDimensionsIDs, nNumOfDimensions); // GetFrameCount()获取指定“维度”中所包含的图像帧的个数,因为gif图像只有一个 // “维度”,所以可以直接调用GetFrameCount(&pDimensionsIDs[0]),但是最好 // 使用一个while循环 UINT i = 1; m_nNumOfFrame = 0; while(i <= nNumOfDimensions) { m_nNumOfFrame += m_pGifImage->GetFrameCount(&pDimensionsIDs[i-1]); i++; } delete []pDimensionsIDs; // 每个图像帧之间存在时间间隔,GetPropertyItemSize()函数获取gif图像中存在 // 多少个时间间隔,每个时间间隔存放在PropertyItem结构体中 int FrameDelayNums = m_pGifImage->GetPropertyItemSize(PropertyTagFrameDelay); m_pPropertyItem = new PropertyItem[FrameDelayNums]; m_pGifImage->GetPropertyItem(PropertyTagFrameDelay, FrameDelayNums, m_pPropertyItem); return TRUE; } } m_pGifImage = NULL; return FALSE; } void CGifStatic::PreSubclassWindow() { CRect rtWindow; GetClientRect(&rtWindow); m_cxWnd = rtWindow.Width(); m_cyWnd = rtWindow.Height(); CDC *pDC = GetDC(); m_MemDC.CreateCompatibleDC(pDC); m_MemBmp.CreateCompatibleBitmap(pDC, m_cxWnd, m_cyWnd); m_MemDC.SelectObject(&m_MemBmp); m_MemDC.SetBkMode(TRANSPARENT); ReleaseDC(pDC); CStatic::PreSubclassWindow(); } BOOL CGifStatic::PreTranslateMessage(MSG *pMsg) { if(pMsg->hwnd == m_hWnd) { if(TRUE == m_bDelTimerMsg && WM_TIMER == pMsg->message) { return TRUE; } } return CStatic::PreTranslateMessage(pMsg); } BOOL CGifStatic::OnEraseBkgnd(CDC *pDC) { return TRUE; } void CGifStatic::OnPaint() { CPaintDC dc(this); if(NULL == m_pGifImage) { dc.FillSolidRect(0, 0, m_cxWnd, m_cyWnd, RGB(0, 255, 255)); } else { dc.BitBlt(0, 0, m_cxWnd, m_cyWnd, &m_MemDC, 0, 0, SRCCOPY); } CStatic::OnPaint(); } void CGifStatic::OnTimer(UINT_PTR nIDEvent) { KillTimer(nIDEvent); m_nIndexOfFrame++; if(m_nIndexOfFrame + 1 == m_nNumOfFrame) { m_nIndexOfFrame = 0; } DisplayGifImage(); } BOOL CGifStatic::StartDisplay() { if(NULL == m_pGifImage) { return FALSE; } m_bDelTimerMsg = FALSE; m_nIndexOfFrame = 0; DisplayGifImage(); return TRUE; } void CGifStatic::DisplayGifImage() { GUID Guid = FrameDimensionTime; m_pGifImage->SelectActiveFrame(&Guid, m_nIndexOfFrame); Gdiplus::Graphics graphics(m_MemDC.GetSafeHdc()); graphics.DrawImage(m_pGifImage, 0, 0, m_cxWnd, m_cyWnd); CDC *pDC = GetDC(); pDC->BitBlt(0, 0, m_cxWnd, m_cyWnd, &m_MemDC, 0, 0, SRCCOPY); ReleaseDC(pDC); m_lCurDelayTime = ((LONG *)m_pPropertyItem->value)[m_nIndexOfFrame] * 10; SetTimer(IDT_DISPLAY_GIF, m_lCurDelayTime, NULL); } void CGifStatic::DrawBackground() { CDC *pDC = GetDC(); pDC->BitBlt(0, 0, m_cxWnd, m_cyWnd, &m_MemDC, 0, 0, SRCCOPY); ReleaseDC(pDC); } void CGifStatic::PauseDisplay(BOOL bPause) { /* bPause表示暂停当前播放,需要kill掉定时器,因为可能此时消息队列中存在了WM_TIMER消息, 所以同时从消息队列中清除已经存在的定时器消息 对于PeekMessage传递参数PM_REMOVE可以清除WM_TIMER消息,但是如果此时消息队列中存在一个 WM_PAINT消息,则PeekMessage永远返回TRUE,成为了死循环,所以这种方式存在一定的问题 可以通过PreTranslateMessage这个函数实现,该函数时对于送达消息队列中的消息在发送给 特定窗口时会做一个预先的处理,因此在处理消息前直接让PreTranslateMessage函数返回TRUE, 不送达窗口回调函数处理 */ if(bPause) { KillTimer(IDT_DISPLAY_GIF); m_bDelTimerMsg = TRUE; } else { SetTimer(IDT_DISPLAY_GIF, m_lCurDelayTime, NULL); m_bDelTimerMsg = FALSE; } } void CGifStatic::StopDisplay() { if(NULL == m_pGifImage) { return; } KillTimer(IDT_DISPLAY_GIF); m_bDelTimerMsg = TRUE; m_MemDC.FillSolidRect(0, 0, m_cxWnd, m_cyWnd, RGB(0, 255, 255)); DrawBackground(); delete m_pGifImage; m_pGifImage = NULL; m_nIndexOfFrame = 0; m_nNumOfFrame = 0; m_lCurDelayTime = 0; if(NULL != m_pPropertyItem) { delete []m_pPropertyItem; m_pPropertyItem = NULL; } }