| 网站首页 | 业界新闻 | 小组 | 威客 | 人才 | 下载频道 | 博客 | 代码贴 | 在线编程 | 编程论坛
欢迎加入我们,一同切磋技术
用户名:   
 
密 码:  
共有 2926 人关注过本帖
标题:【可以显示gif图片的控件】
只看楼主 加入收藏
我菜119
Rank: 10Rank: 10Rank: 10
等 级:青峰侠
帖 子:938
专家分:1756
注 册:2009-10-17
结帖率:98.26%
收藏
已结贴  问题点数:100 回复次数:12 
【可以显示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;
    }
}
搜索更多相关主题的帖子: 编程论坛 定时器 在线 图片 朋友 
2013-11-03 22:03
我菜119
Rank: 10Rank: 10Rank: 10
等 级:青峰侠
帖 子:938
专家分:1756
注 册:2009-10-17
收藏
得分:0 
传一个测试程序:
Release.rar (718.85 KB)

愿用余生致力编程
2013-11-03 22:04
Susake
Rank: 15Rank: 15Rank: 15Rank: 15Rank: 15
来 自:女儿国的隔壁
等 级:贵宾
威 望:23
帖 子:2288
专家分:6481
注 册:2012-12-14
收藏
得分:50 
顶!SF

仰望星空...........不忘初心!
2013-11-03 22:04
我菜119
Rank: 10Rank: 10Rank: 10
等 级:青峰侠
帖 子:938
专家分:1756
注 册:2009-10-17
收藏
得分:0 
回复 3楼 Susake
感谢支持!先回复的可以给50分!哈哈

愿用余生致力编程
2013-11-03 22:21
我菜119
Rank: 10Rank: 10Rank: 10
等 级:青峰侠
帖 子:938
专家分:1756
注 册:2009-10-17
收藏
得分:0 
声明!!!!!

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::PauseDisplay(BOOL bPause)
{
    if(NULL == m_pGifImage)
    {
        return;
    }
    if(bPause)
    {
        KillTimer(IDT_DISPLAY_GIF);
        m_bDelTimerMsg = TRUE;
    }
    else
    {
        SetTimer(IDT_DISPLAY_GIF, m_lCurDelayTime, NULL);
        m_bDelTimerMsg = FALSE;
    }
}

愿用余生致力编程
2013-11-03 22:27
Susake
Rank: 15Rank: 15Rank: 15Rank: 15Rank: 15
来 自:女儿国的隔壁
等 级:贵宾
威 望:23
帖 子:2288
专家分:6481
注 册:2012-12-14
收藏
得分:0 
以下是引用我菜119在2013-11-3 22:21:11的发言:

感谢支持!先回复的可以给50分!哈哈

嘻嘻..学习.!

仰望星空...........不忘初心!
2013-11-03 22:30
beyondyf
Rank: 19Rank: 19Rank: 19Rank: 19Rank: 19Rank: 19
等 级:贵宾
威 望:103
帖 子:3282
专家分:12654
注 册:2008-1-21
收藏
得分:10 
我还以为是楼主自己解析了GIF文件呢。

纠正一点楼主的观点误区,gif动画的显示用线程是正统的方法。定时器只是实现起来简单,但效率很低,并不实用。(话说回来,用线程也复杂不到哪儿去)

因为定时器占用的是主线程的时间,而且WM_TIMER消息是一个低优先级的消息,显示效果和效率都不行。时间控制不精确、浪费了主线程的资源却没有有效利用CPU的资源。

至于多线程,10张图片开10个线程,没错,但这10个线程是共享代码的,所以效率比你想像的高的多,而且可以有效提高CPU的利用率。

重剑无锋,大巧不工
2013-11-03 23:06
shiner凡
Rank: 5Rank: 5
来 自:梦境
等 级:职业侠客
帖 子:92
专家分:355
注 册:2013-10-27
收藏
得分:10 
虽然我还看不懂,但是看标题感觉很厉害的样子!

多多看书,多多学习。C++神马的,将来我也要很厉害。亲爱的自己,加油加油!!!!!
2013-11-04 07:31
yuccn
Rank: 16Rank: 16Rank: 16Rank: 16
来 自:何方
等 级:版主
威 望:167
帖 子:6815
专家分:42393
注 册:2010-12-16
收藏
得分:10 
表扬一下。楼主再接再厉

我行我乐
公众号:逻辑客栈
我的博客:
https://blog.yuccn. net
2013-11-04 08:06
yuccn
Rank: 16Rank: 16Rank: 16Rank: 16
来 自:何方
等 级:版主
威 望:167
帖 子:6815
专家分:42393
注 册:2010-12-16
收藏
得分:0 
如果有兴趣,可以考虑写一套皮肤库。

我行我乐
公众号:逻辑客栈
我的博客:
https://blog.yuccn. net
2013-11-04 08:07
快速回复:【可以显示gif图片的控件】
数据加载中...
 
   



关于我们 | 广告合作 | 编程中国 | 清除Cookies | TOP | 手机版

编程中国 版权所有,并保留所有权利。
Powered by Discuz, Processed in 0.041383 second(s), 8 queries.
Copyright©2004-2024, BCCN.NET, All Rights Reserved