C++ GDI+ 多张图片合并生成GIF动画格式图片
2014-08-28 20:17
931 查看
在写这篇文章前,首先吐槽一下:今天因工作需要,研究在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();
相关文章推荐
- 使用JMagick生成GIF动画图片
- asp无组件生成验证码 GIF图片格式
- java生成验证码图片,包括动态gif图片格式
- PHP使用JPG生成GIF动画图片,基于php_imagick_st-Q8.dll
- asp无组件生成验证码 GIF图片格式
- GDI+生成动画式的Gif图片示例代码
- iOS GIF 格式动画 图片显示
- MATLAB中利用多幅图片生成GIF动画
- vc6.0使用gdi+在内存中绘图并将其保存为bmp,jpg,gif,png等格式的图片
- 一个将图片生成gif动画的最简单程序
- byte字节流如何生成gif格式图片 java
- 【代码】PHP 生成GIF动画实现动态图片验证码
- C++ budilder 使用ImageOle.dll 实现richedit显示任意格式图片和连接 gif
- iOS开发之旋转动画及GIF格式图片的播放
- ASP生成验证码-BMP图片原理,GIF格式,无组件生成验证码
- iOS支持Gif格式图片动画
- vc6.0使用gdi+在内存中绘图并将其保存为bmp,jpg,gif,png等格式的图片
- iOS GIF 格式动画 图片显示
- 将 image 图片数组生成 gif 格式图片 - iOS
- qt生成的exe可执行程序打包到其他电脑上执行时jpg、gif图片不能正常显示,但png等其他格式可以显示