实现QQ表情功能(1)

                                                 作者:小鹏

 

0、前言
相信大家对QQ的表情不陌生。像这样:





QQ的文本框控件支持各种图片格式的显示和插入。看到QQ这个有趣功能,我也想实现下。不过,实现起来却不是太容易,在我写代码的过程中,我也在网上查找了很久的资料,不过总算让我成功实现了。
要实现这个功能,关键的技术是要实现一个控件,这个控件能够播放GIF。
下面开始详细介绍我的写代码的过程。

1、观察QQ的实现方式
先从QQ是怎么实现的讲起。
QQ的这个控件叫ImageOle.dll.在QQ的安装目录下可以找到这个dll.
用regsvr32这个命令可以注册这个dll.

注册成功后可以用Ole View这个功能查看注册的相关信息。这个工具是VC自带的。

名字叫GifAnimator Class的就是QQ的那个控件。
选择GifAnimator,再右键,选择View Type Information.
可以看到ImageOle控件的类型库的内容。

这些内容就是QQ那控件的接口的方法。
我的目标就是实现一个同样的控件,支持其中一个方法,LoadFromFile.

2、创建一个ATL工程

下面就是我的具体实现的过程。
第一步,新建一个ATL工程。输入名称GifOle

下一步,保留默认选项,Server Type的类型是Dynamic Link Library(DLL).点击Finish.

3、添加一个控件

右键,选择New ATL Object.

在Category中选择Controls,在Objects中选择Full Control。

选择next.

在Short Name中输入CifCtl.
Miscellaneous中,把View Status中Opaque的选择去掉。Opaque是不透明的意思。QQ表情是在透明背景下显示的,所以要把那个选项去掉。

点击确定。
4、加入GDI+类库

下面实现播放Gif的功能。我用的是Gdi+。
在VC6要使用Gdi+的话,得下个库。
GDI+ for VC6.0 SDK 下载
地址:
http://www.codeguru.com/code/legacy/gdi/GDIPlus.zip
下载回来后,把里面的头文件和GdiPlus.lib复制到gdiplus的文件夹,再把这个文件夹复制到GifOle工程目录下。
在stdafx.h文件中加入以下代码
#define UNICODE
#ifndef ULONG_PTR
#define ULONG_PTR unsigned long*
#endif
#include "gdiplus//GdiPlus.h"
using namespace Gdiplus;
#pragma comment(lib,
"gdiplus//gdiplus.lib")
效果如下图:


5、加入ImageEx类

另外,我还有用到一个ImageEx类来显示gif.

这个类的头文件是:
// ImageEx.h: interface for the ImageEx class.
//
//////////////////////////////////////////////////////////////////////

#if
!defined(AFX_IMAGEEX_H__D639CC73_8200_42E0_9386_617AD4B0A2E9__INCLUDED_)
#define
AFX_IMAGEEX_H__D639CC73_8200_42E0_9386_617AD4B0A2E9__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

class ImageEx : public Image
{
public:
ImageEx(const
WCHAR* filename, BOOL useEmbeddedColorManagement = FALSE);
~ImageEx();
public:
BOOL
IsAnimatedGif();    //判断是否是动态GIF文件
   long
GetFrameTime();     //获取当前帧应该显示的时间长度
void
ActiveNextFrame();  //激活下一帧为应该显示的帧
protected:
UINT  m_nFrameCount;     //帧数
UINT  m_nFramePosition;  //当前帧的序号
   PropertyItem*
m_pPropertyItem;   //属性项,仅用来测试是否是动态图片
};

#endif // !defined(AFX_IMAGEEX_H__D639CC73_8200_42E0_9386_617AD4B0A2E9__INCLUDED_)
把以上内容复制到ImageEx.h文件中,入到工程目录下。
实现文件的内容是:
// ImageEx.cpp: implementation of the ImageEx class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "ImageEx.h"

ImageEx::ImageEx(const WCHAR* FileName, BOOL
useEmbeddedColorManagement)
:Image(FileName, useEmbeddedColorManagement)
{
m_nFramePosition =
0;
m_nFrameCount = 0;
m_pPropertyItem =
NULL;

UINT count = 0;
count =
GetFrameDimensionsCount();      //获得维数
GUID* pDimensionIDs
= new GUID[count];  //分配维ID数组

GetFrameDimensionsList(pDimensionIDs,
count);//获得各维的ID
m_nFrameCount =
GetFrameCount(&pDimensionIDs[0]);//获得第一维的帧数

   //获得属性项
int nSize =
GetPropertyItemSize(PropertyTagFrameDelay);
m_pPropertyItem =
(PropertyItem*) malloc(nSize);
GetPropertyItem(PropertyTagFrameDelay,
nSize, m_pPropertyItem);  
delete pDimensionIDs;    
}

ImageEx::~ImageEx()
{
free(m_pPropertyItem);
m_pPropertyItem =
NULL;
}

BOOL ImageEx::IsAnimatedGif()
{
return m_nFrameCount
> 1;
}

long ImageEx::GetFrameTime()
{
long lPause =
((long*) m_pPropertyItem->value)[m_nFramePosition] * 10;
return lPause;
}

void ImageEx::ActiveNextFrame()
{
if (IsAnimatedGif()
== FALSE)
    return;

GUID  pageGuid = FrameDimensionTime;
SelectActiveFrame(&pageGuid,
m_nFramePosition++);

if
(m_nFramePosition == m_nFrameCount)
    m_nFramePosition
= 0;
}
把以上内容复制到ImageEx.cpp文件中,复制到项目工程目录下。
再把这两个文件添加到工程。再在GifCtl.h的文件的开头加下以下语句:
#include "ImageEx.h"
效果如下图:


6、加入定时器类

另外,为了让gif表情动起来,还需要一个定时器类来定时刷新,实现动的效果。
// CTimer
template <class Derived, class T, const IID* piid>
class CTimer
{
public:

CTimer()
{
    m_bTimerOn =
FALSE;
}

HRESULT
TimerOn(DWORD dwTimerInterval)
{
    Derived*
pDerived = ((Derived*)this);
    
    m_dwTimerInterval
= dwTimerInterval;
    if (m_bTimerOn)
// already on, just change interval
       return S_OK;
    
    m_bTimerOn =
TRUE;
    m_dwTimerInterval
= dwTimerInterval;
    m_pStream =
NULL;
    
    HRESULT hRes;
    
    hRes =
CoMarshalInterThreadInterfaceInStream(*piid, (T*)pDerived, &m_pStream);
    
    // Create
thread and pass the thread proc the this ptr
    m_hThread =
CreateThread(NULL, 0, &_Apartment, (void*)this, 0, &m_dwThreadID);
    
    return S_OK;
}

void TimerOff()
{
    if (m_bTimerOn)
    {
       m_bTimerOn =
FALSE;
       AtlWaitWithMessageLoop(m_hThread);
    }
}


// Implementation
private:
static DWORD WINAPI
_Apartment(void* pv)
{
    CTimer<Derived,
T, piid>* pThis = (CTimer<Derived, T, piid>*) pv;
    pThis->Apartment();
    return 0;
}

DWORD Apartment()
{
    CoInitialize(NULL);
    HRESULT hRes;
    
    m_spT.Release();
    
    if (m_pStream)
       hRes =
CoGetInterfaceAndReleaseStream(m_pStream, *piid, (void**)&m_spT);
    
    while(m_bTimerOn)
    {
       Sleep(m_dwTimerInterval);
       if
(!m_bTimerOn)
           break;
      
       m_spT->_OnTimer();
    }
    m_spT.Release();
    
    CoUninitialize();
    return 0;
}

// Attributes
public:
DWORD
m_dwTimerInterval;

// Implementation
private:
HANDLE m_hThread;
DWORD m_dwThreadID;
LPSTREAM m_pStream;
CComPtr<T>
m_spT;
BOOL m_bTimerOn;
};
把以上代码同样复制到#include "ImageEx.h"语句的下面
效果如下图:




再把这个定时器加入到CGifCtl控件中
在CGifCtl类开头加入以下代码
public CTimer<CGifCtl, IGifCtl, &IID_IGifCtl>,
如图:

使用GDI+库显示gif动态图片,该类接口如下: 可以看出,该ImageEx完全继承了基类的接口函数。 说明: 如果打开非多帧图片,该类几乎完全等价于基类,比如你可以把该类的对象代入Graphics类系列的成员函数中; 如果打开的是多帧的图片,你只要打开图片后不调用InitAnimation函数(它会创建线程),则上述做法依然可以; 但如果调用InitAnimation函数后(单帧图像没关系,因为不会创建线程),则不可以了, 所有的基类继承过来的接口成员函数和配合gdi+库其他类的函数调用都是不可以的,因为没有作线程同步, 你只能调用下面位数不多的几个public成员函数,调用Destroy成员函数后,则就可以了,因为它会关闭线程。 其实你会发现下面的public成员函数操作的成员变量都是新增的成员变量,没涉及到线程同步问题。 class ImageEx : public Image { public: //以长度为nSize的内存pBuff中的内容构造图像 ImageEx(const void* pBuff, size_t nSize, BOOL useEmbeddedColorManagement = FALSE); //以类型为sResourceType,名称为sResource的资源构造图像 ImageEx(LPCTSTR sResourceType, LPCTSTR sResource, BOOL useEmbeddedColorManagement = FALSE); //以文件构造图像 ImageEx(LPCTSTR filename, BOOL useEmbeddedColorManagement = FALSE); //调用Destroy成员函数 ~ImageEx(); public: //如果已经构造的对象是动画,则创建动画线程,并返回true, //如果为静态图像或已经创建过动画线程,则也返回false // 图像将绘制在m_hWnd客户区的rect区域,会拉伸,支持镜像 bool InitAnimation(HWND hWnd, RECT rect); //判断是否为动画 bool IsAnimatedGIF() { return m_nFrameCount > 1; } //设置动画暂停与否 void SetPause(bool bPause); //判断动画是否处于暂停状态 bool IsPaused() { return m_bPause; } //关闭动画,事实上基类Image中还有的两个成员变量没有关闭,因为析构函数会调用基类析构函数进行关闭的 void Destroy(); //另外的非public的东西省略.. }; 用法: MFC对话框程序在下面添加: BOOL CTestDlgDlg::OnInitDialog() { CDialog::OnInitDialog(); //其它的初始化代码 // GDI+ //m_imageImageEx指针类型成员变量,"GIF"为资源类型,"HEARTS"为资源名称 m_image = new ImageEx( _T("GIF"), _T("HEARTS") ); RECT rc; GetClientRect(&rc); m_image->InitAnimation(this->m_hWnd, rc);//创建gif播放线程 return TRUE; // return TRUE unless you set the focus to a control } CTestDlgDlg::~CTestDlgDlg() { // GDI+ delete m_image; } 其中的m_image = new ImageEx( _T("GIF"), _T("HEARTS") );你可以换成ImageEx类的另外两个构造函数
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值