C++ GDI+ 多张图片合并生成GIF动画格式图片

在写这篇文章前,首先吐槽一下:今天因工作需要,研究在C++中将多张图片合并生成Gif动画格式的方法。在网上看了很多这类的文章,没一个靠谱的,唯一靠谱的是使用C#实现的GIf编解码的方法(NGif http://www.codeproject.com/Articles/11505/NGif-Animated-GIF-Encoder-for-NET),它是使用流的方法进行编解码,满足要求,但将其转码成C++很麻烦,所以索性自己编写了一套在C++中将多张图片合并成Gif格式的方法,现在呈现给大家,以弥补C++模块的空缺。

【1】Gif格式简介

GIF(GraphicsInterchange Format)的原义是“图像互换格式”,是CompuServe公司在 1987年开发的图像文件格式。GIF文件的数据,是一种基于LZW算法的连续色调的无损压缩格式。其压缩率一般在50%左右,它不属于任何应用程序。目前几乎所有相关软件都支持它,公共领域有大量的软件在使用GIF图像文件。GIF图像文件的数据是经过压缩的,而且是采用了可变长度等压缩算法。GIF格式的另一个特点是其在一个GIF文件中可以存多幅彩色图像,如果把存于一个文件中的多幅图像数据逐幅读出并显示到屏幕上,就可构成一种最简单的动画。

GIF格式自1987年由CompuServe公司引入后,因其体积小而成像相对清晰,特别适合于初期慢速的互联网,而从此大受欢迎。它采用无损压缩技术,只要图像不多于256色,则可既减少文件的大小,又保持成像的质量。(当然,现在也存在一些hack技术,在一定的条件下克服256色的限制,具体参见真彩色)然而,256色的限制大大局限了GIF文件的应用范围,如彩色相机等。(当然采用无损压缩技术的彩色相机照片亦不适合通过网络传输。)另一方面,在高彩图片上有着不俗表现的JPG格式却在简单的折线上效果难以差强人意。因此GIF格式普遍适用于图表,按钮等等只需少量颜色的图像(如黑白照片)

【2】图像合并成GIf的方法

使用 GDI+库即可完成多张图片合并生成Gif动画图片的功能,主要涉及:
SaveAdd 方法,在原有的图像上添加一张新的图像:
statue = m_pImage->SaveAdd(m_pBitMapVec[ix], &encoderParams);
通过 EncoderParameters参数设置图像编码的信息,如第一张图像需要设置为 EncoderValueMultiFrame,其余的图像需要设置为 EncoderValueFrameDimensionTime
GUID						gifGUID;
	Gdiplus::EncoderParameters	encoderParams;  
	GetEncoderClsid(L"image/gif", &gifGUID);
	
	encoderParams.Count = 1;
	encoderParams.Parameter[0].Guid			= Gdiplus::EncoderSaveFlag;
	encoderParams.Parameter[0].NumberOfValues = 1;
	encoderParams.Parameter[0].Type			=  Gdiplus::EncoderParameterValueTypeLong;//第一帧需要设置为MultiFrame

	long firstValue						= Gdiplus::EncoderValueMultiFrame;
	encoderParams.Parameter[0].Value	=  &firstValue;	
	
	m_pImage->Save(m_pStrSavePath->c_str(), &gifGUID, &encoderParams);

	//3.0 保存剩余的图像
	size_t size = m_pBitMapVec.size();
	firstValue						= Gdiplus::EncoderValueFrameDimensionTime;
	encoderParams.Parameter[0].Value=  &firstValue;	
	for (size_t ix = 0; ix <size; ix++)
	{
		statue = m_pImage->SaveAdd(m_pBitMapVec[ix], &encoderParams);
	}
 

通过SetPropertyItem函数设置Gif动画图像的循环的次数(PropertyTagLoopCount)及每个图像持续的时间(PropertyTagFrameDelay):

	Gdiplus::PropertyItem propertyItem;

	//1.0 设置动画循环的次数	
	short sValue		= m_repeatNum; //0 -- 不限次数
	propertyItem.id		= PropertyTagLoopCount; 
	propertyItem.length = 2; //字节数
	propertyItem.type	= PropertyTagTypeShort;
	propertyItem.value	= &sValue; 
	m_pImage->SetPropertyItem(&propertyItem);

	//2.0 设置每副图像延迟的时间,从第一副开始
	long lImageNum = 1 + m_pBitMapVec.size();
	long *plValue = new long[lImageNum];
	for (int ix=0; ix<lImageNum; ix++)
	{
		plValue[ix] = m_delayTime; //可以设置不一样
	}
	propertyItem.id		= PropertyTagFrameDelay; 
	propertyItem.length = 4 * lImageNum;//字节数
	propertyItem.type	= PropertyTagTypeLong;
	propertyItem.value	= plValue; //不限次数
	m_pImage->SetPropertyItem(&propertyItem);

	delete []plValue;
	plValue = NULL;
 

【3】完整代码如下

类声明:
//================================================================================
/// @brief 动态Gif图像的编码
///
/// 通过动态添加多幅图像将其合并成Gif动画图像
//================================================================================
class CGifEncoder
{
public:
	CGifEncoder();
	~CGifEncoder();

public:	
	//=================================StartEncoder()=================================
	/// @brief 开始gif编码
	///
	/// @param [in] saveFilePath gif图像保存的全路径
	///
	/// @return 成功返回true
	//================================================================================
	bool StartEncoder(wstring &saveFilePath); 	
	//===================================AddFrame()===================================
	/// @brief 添加图像
	///
	/// @param [in] im  Image对象
	///
	/// @return 成功返回true
	//================================================================================
	bool AddFrame(Gdiplus::Image *pImage);
	//===================================AddFrame()===================================
	/// @brief 添加图像
	///
	/// @param [in] framePath 图像的全路径
	///
	/// @return  成功返回true
	//================================================================================
	bool AddFrame(wstring &framePath);
	//================================FinishEncoder()===============================
	/// @brief 结束gif的编码
	///
	/// @return  成功返回true
	//================================================================================
	bool FinishEncoder();
	//=================================SetDelayTime()=================================
	/// @brief 设置两幅图像切换的时间间隔
	///
	/// @param [in] ms 时间,以毫秒为单位
	///
	/// @return 无
	//================================================================================
	void SetDelayTime(int ms);
	//=================================SetRepeatNum()=================================
	/// @brief 设置gif动画播放的次数
	///
	/// @param [in] num 次数,0表示无限次
	///
	/// @return 无
	//================================================================================
	void SetRepeatNum(int num);	
	//=================================SetFrameRate()=================================
	/// @brief 设置图像的帧率
	///
	/// @param [in] fps 帧率,每秒播放图像的数目
	///
	/// @return 无
	//================================================================================
	void SetFrameRate(float fps);
	//=================================SetFrameSize()=================================
	/// @brief 设置图像的尺寸
	///
	/// @param [in] width  图像的宽度
	/// @param [in] height 图像的高度
	///
	/// @return 无
	//================================================================================
	void SetFrameSize(int width, int height);

private:
	void SetImagePropertyItem();
	bool GetEncoderClsid(const WCHAR* format, CLSID* pClsid);	
	
private:
	int					m_width; 
	int					m_height;
	int					m_repeatNum; 
	int					m_delayTime; 
	bool				m_started;
	bool				m_haveFrame;

	wstring				*m_pStrSavePath;
	Gdiplus::Bitmap		*m_pImage;
	vector<Gdiplus::Bitmap *> m_pBitMapVec;
};

类实现:
CGifEncoder::CGifEncoder()
{
	m_started	= false;
	m_width		= 320;
	m_height	= 240;
	m_delayTime = 100;
	m_repeatNum = 0;
	m_haveFrame = false;
	m_pStrSavePath = NULL;
	m_pImage	   = NULL;
}

CGifEncoder::~CGifEncoder()
{
	if (NULL != m_pStrSavePath)
	{
		delete m_pStrSavePath;
	}
	
	if (NULL != m_pImage)
	{
		delete m_pImage;
	}	

	size_t size = m_pBitMapVec.size();
	for (size_t ix=0; ix<size; ix++)
	{
		delete m_pBitMapVec[ix];
		m_pBitMapVec[ix] = NULL;
	}
}

bool CGifEncoder::StartEncoder( wstring &saveFilePath )
{
	bool flag = true;

	if ( NULL != m_pStrSavePath)
	{
		delete m_pStrSavePath;
		m_pStrSavePath = NULL;
	}

	m_pStrSavePath = new wstring(saveFilePath);
	m_started = true;

	return(flag);
}

bool CGifEncoder::AddFrame( Gdiplus::Image *pImage )
{
	if (!m_started || NULL == pImage)
	{
		return(false);
	}

	bool flag = true;
	if (!m_haveFrame)
	{
		m_pImage = new Gdiplus::Bitmap(m_width, m_height);
		Gdiplus::Graphics g(m_pImage);
		g.DrawImage(pImage, 0, 0, m_width, m_height);

		m_haveFrame = true;
		return(true);
	}

	Gdiplus::Bitmap *pBitMap = new Gdiplus::Bitmap(m_width, m_height);
	Gdiplus::Graphics g(pBitMap);
	g.DrawImage(pImage, 0, 0, m_width, m_height);
	m_pBitMapVec.push_back(pBitMap);
	
	return(flag);
}

bool CGifEncoder::AddFrame( wstring &framePath )
{
	if (!m_started)
	{
		return(false);
	}

	bool flag = true;
	Gdiplus::Status statue;
	if (!m_haveFrame)
	{
		m_pImage = new Gdiplus::Bitmap(m_width, m_height);
		Gdiplus::Graphics g(m_pImage);

		Gdiplus::Bitmap bitmap(framePath.c_str());
		g.DrawImage(&bitmap, 0, 0, m_width, m_height);

		m_haveFrame = true;
		return(true);
	}	

	Gdiplus::Bitmap		*pBitMap = new Gdiplus::Bitmap(m_width, m_height);
	Gdiplus::Graphics	g(pBitMap);

	Gdiplus::Bitmap bitmap(framePath.c_str());
	statue = g.DrawImage(&bitmap, 0, 0, m_width, m_height);

	m_pBitMapVec.push_back(pBitMap);

	return(flag);
}

bool CGifEncoder::FinishEncoder()
{
	if (!m_started || !m_haveFrame)
	{
		return(false);
	}

	bool	flag = true;
	Gdiplus::Status statue;

	//1.0 设置图像的属性
	SetImagePropertyItem();

	//2.0 保存第一副图像
	GUID						gifGUID;
	Gdiplus::EncoderParameters	encoderParams;  
	GetEncoderClsid(L"image/gif", &gifGUID);
	
	encoderParams.Count = 1;
	encoderParams.Parameter[0].Guid			= Gdiplus::EncoderSaveFlag;
	encoderParams.Parameter[0].NumberOfValues = 1;
	encoderParams.Parameter[0].Type			=  Gdiplus::EncoderParameterValueTypeLong;//第一帧需要设置为MultiFrame

	long firstValue						= Gdiplus::EncoderValueMultiFrame;
	encoderParams.Parameter[0].Value	=  &firstValue;	
	
	m_pImage->Save(m_pStrSavePath->c_str(), &gifGUID, &encoderParams);

	//3.0 保存剩余的图像
	size_t size = m_pBitMapVec.size();
	firstValue						= Gdiplus::EncoderValueFrameDimensionTime;
	encoderParams.Parameter[0].Value=  &firstValue;	
	for (size_t ix = 0; ix <size; ix++)
	{
		statue = m_pImage->SaveAdd(m_pBitMapVec[ix], &encoderParams);
	}

	m_started = false;
	m_haveFrame = false;
	return(flag);
}

void CGifEncoder::SetDelayTime( int ms )
{
	if (ms > 0)
	{
		m_delayTime = ms / 10.0f;
	}	
}

void CGifEncoder::SetRepeatNum( int num )
{
	if (num >= 0) 
	{
		m_repeatNum = num;
	}
}

void CGifEncoder::SetFrameRate( float fps )
{
	if (fps > 0) 
	{
		m_delayTime = 100.0f / fps;
	}
}

void CGifEncoder::SetFrameSize( int width, int height )
{
	if (!m_haveFrame) 
	{
		m_width  = width;
		m_height = height;

		if (m_width < 1) 
			m_width = 320;

		if (m_height < 1) 
			height = 240;
	}	
}

void CGifEncoder::SetImagePropertyItem()
{
	if (!m_started || NULL == m_pImage)
	{
		return;
	}

	Gdiplus::PropertyItem propertyItem;

	//1.0 设置动画循环的次数	
	short sValue		= m_repeatNum; //0 -- 不限次数
	propertyItem.id		= PropertyTagLoopCount; 
	propertyItem.length = 2; //字节数
	propertyItem.type	= PropertyTagTypeShort;
	propertyItem.value	= &sValue; 
	m_pImage->SetPropertyItem(&propertyItem);

	//2.0 设置每副图像延迟的时间,从第一副开始
	long lImageNum = 1 + m_pBitMapVec.size();
	long *plValue = new long[lImageNum];
	for (int ix=0; ix<lImageNum; ix++)
	{
		plValue[ix] = m_delayTime; //可以设置不一样
	}
	propertyItem.id		= PropertyTagFrameDelay; 
	propertyItem.length = 4 * lImageNum;//字节数
	propertyItem.type	= PropertyTagTypeLong;
	propertyItem.value	= plValue; //不限次数
	m_pImage->SetPropertyItem(&propertyItem);

	delete []plValue;
	plValue = NULL;
}

bool CGifEncoder::GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
{
	UINT num = 0, size = 0;

	Gdiplus::GetImageEncodersSize(&num, &size);
	if(size == 0)
		return false;  // Failure

	Gdiplus::ImageCodecInfo* pImageCodecInfo = (Gdiplus::ImageCodecInfo*)(malloc(size));

	Gdiplus::GetImageEncoders(num, size, pImageCodecInfo);
	bool found = false;
	for (UINT ix = 0; !found && ix < num; ++ix) 
	{
		if (_wcsicmp(pImageCodecInfo[ix].MimeType, format) == 0) 
		{
			*pClsid = pImageCodecInfo[ix].Clsid;
			found = true;
			break;
		}
	}

	free(pImageCodecInfo);
	return found;
}
 

【4】测试代码

	CGifEncoder gifEncoder;
	gifEncoder.SetFrameSize(200, 200);
	gifEncoder.SetDelayTime(500);
	gifEncoder.StartEncoder(wstring(L"C:\\1.gif"));
	gifEncoder.AddFrame(wstring(L"C:\\1.png"));
	gifEncoder.AddFrame(wstring(L"C:\\2.jpg"));
	gifEncoder.AddFrame(wstring(L"C:\\3.jpg"));
	gifEncoder.AddFrame(wstring(L"C:\\4.jpg"));
	gifEncoder.FinishEncoder();

 

评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

郎涯技术

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值