c++项目,制作GIF动态图

项目背景

在很多时候,人们所说的一句话在不同的的环境和状态下回有不同的含义,但是,我们有时候并不能表达出的信息并不是自己想要的结果,尤其是在我们在虚拟网络上聊天时,仅仅依靠文字我们并不能很好地看出对方的情况,而随着互联网的不断发展与进步,有时候一个简简单单的表情也许能比很长一段文字能更好地表现出来对方想让我们知道的信息,这样就更加方便彼此之间的交流。在我们上网聊天时,“一言不合就斗图”的情况屡见不鲜,我们经常在QQ或微信聊天中发现这种现象,“斗图”就是彼此之间通过发送一些搞怪图片来表达出自己想要表达的意思,久而久之,“斗图”成为网友们在网上聊天时最常见的一种娱乐方式。

项目简介

此次项目,是合理使用第三方库Duilib和ffmpeg工具来将一些图片或者从视频中截取的一小段视频来生成我们平时常见的动态图

项目可行性分析

本次项目可以从三方面来进行可行性分析
1.经济可行性分析:制作成本十分低
2.使用操作可行性分析:使用起来很简单也分方便
3.技术可行性分析:使用了一些工具和第三方库,制作起来并不是很麻烦

第三方库以及项目工具介绍

本次项目的设计过程中,设计到了第三方库Duilib的使用以及ffmpeg工具的使用
Duilib库是基于Win32系统的一套UI库,是一款强大的界面开发工具,可以将用户界面和处理逻辑彻底分离,极大地提高用户界面的开发效率,本次项目通过使用Duilib库来生成UI布局界面,使用Duilib库的好处在于:
1.Duilib界面库解决了使用传统MFC界面库开发软件不美观、界面细节处理不好、使用硬编码、开发效率低下、生成程序体积大等问题
2.Duilib界面库完全基于GDI在窗口上自绘,无其他依赖,未使用特殊或危险的系统调用
3.Duilib界面库可广泛用于互联网客户端、工具软件客户端、管理系统客户端、多媒体客户端(如KTV、触摸屏)、车载电脑系统、gps系统和手机客户端软件等。

ffmpeg是特别强大的专门用于处理音视频的开源库,既可以使用它的API对音视频进行处理,也可以使用它提供的工具,如 ffmpeg, ffplay, ffprobe,来编辑你的音视频文件;
本次项目主要利用ffmpeg.exe工具来完成,ffmpeg是使用命令行的格式,响应Cmd来实现相关的操作,因此我们在实现Gif的制作,主要就是通过Cmd控制台来使用ffmpeg工具,向该工具发送对应的命令来实现各部分操作。

项目总体设计流程图

本项目共有两种生成Gif的方式:图片生成和视频生成,下图为便是实现本次项目的流程:
在这里插入图片描述
在这里插入图片描述

项目详细设计

本次项目用到了Duilib库,Duilib库是基于Win32系统的一套UI库,因此在项目开始之前我们先来了解一下Win32相关知识。

Win32 是指 Microsoft Windows 操作系统的 32 位环境,与 Win64 都为 Windows 常见环境。
下面我们来建立一个win32程序

1、首先建立一个Win32工程,如图:
在这里插入图片描述

建立工程,出现下图:
在这里插入图片描述

点击下一步,出现下图:
在这里插入图片描述

单击空项目,然后完成,这样一个完整的Win32项目创建完成。
2、实现代码:

#include <Windows.h>
#include <tchar.h>
//消息回调函数
POINT start;//起点
POINT end;//终点
int state = 0;//刚开始的状态,来控制画什么图形
//用户自己定义:函数的格式必须按照系统的固定格式来定义,
LRESULT CALLBACK WinProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
   
	switch (message)
	{
   
	case WM_MOUSEMOVE://鼠标移动,要获取起点,在移动期间,触发重绘
		end.x = LOWORD(lParam);//改变终点
		end.y = HIWORD(lParam);
		InvalidateRect(hWnd, NULL, true);//触发重绘
		break;
	case WM_LBUTTONDOWN://按鼠标就要触发重绘
		//MessageBox(NULL, _T("LBTNDOWN"), _T("Test"), IDOK);
	{
   
							start.x = LOWORD(lParam);
							start.y = HIWORD(lParam);
							return 0;
	}
	case WM_LBUTTONUP://放开鼠标
	{
   
						  end.x = LOWORD(lParam);
						  end.y = HIWORD(lParam);
						  HDC hdc = GetDC(hWnd);
						  switch (state)
						  {
   
						  case 1://画直线
							  MoveToEx(hdc, start.x, start.y, NULL);//将光标移到起点的位置
							  LineTo(hdc, end.x, end.y);//画到终点的位置
							  break;
						  case 2://画矩形
							  Rectangle(hdc, start.x, start.y, end.x, end.y);//画矩形
							  break;
						  case 3://画椭圆
							  Ellipse(hdc, start.x, start.y, end.x, end.y);
							  break;
						  }
						  ReleaseDC(hWnd, hdc);//用完了要进行释放
						  start = end;//每次画完后将起点和终点放在同一位置
						  return 0;
	}
	case WM_PAINT://重绘
	{
   
					  HDC hdc = GetDC(hWnd);
					  switch (state)
					  {
   
					  case 1://画直线
						  MoveToEx(hdc, start.x, start.y, NULL);//将光标移到起点的位置
						  LineTo(hdc, end.x, end.y);//画到终点的位置
						  break;
					  case 2://画矩形
						  Rectangle(hdc, start.x, start.y, end.x, end.y);//画矩形
						  break;
					  case 3://画椭圆
						  Ellipse(hdc, start.x, start.y, end.x, end.y);
						  break;
					  }
					  ReleaseDC(hWnd, hdc);//用完了要进行释放
					  return 0;
	}
	case WM_CLOSE:
		DestroyWindow(hWnd);
		return 0;
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
}

int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int
	nCmdShow)//hInstance相当于这个应用程序
{
   
	//MessageBox(NULL,_T("hell Win32"),_T("Win32"),IDCANCEL);//IDCANCEL/IDOK/等改变按钮
	//Step1:注册一个窗口类
	HWND hwnd; //窗口的句柄
	WNDCLASSEX wc; //窗口类结构
	wc.cbSize = sizeof(WNDCLASSEX);//当前结构体总共占了多少个字节
	wc.style = CS_VREDRAW | CS_HREDRAW;//窗口刷新机制
	wc.lpszMenuName = 0;//是否有菜单
	wc.lpszClassName = _T("Win32");//标记这个窗口
	wc.lpfnWndProc = WinProc; //消息回调函数,用户必须提供
	wc.hInstance = hInstance;
	wc.hIcon = NULL;
	wc.hCursor = NULL;
	wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
	wc.cbWndExtra = 0;
	wc.cbClsExtra = 0;
	wc.hIconSm = NULL;
	RegisterClassEx(&wc); //注册窗口
	//Step2:创建一个窗口
	hwnd = CreateWindow(
		_T("Win32"), //窗口的类名,也就是上面我们自定义的窗口类的名字
		_T("我的第一个Win32程序"), //窗口的标题
		WS_OVERLAPPEDWINDOW, //窗口style
		500, //窗口位置x坐标
		300, //窗口位置y坐标
		800, //窗口宽度
		600, //窗口高度
		NULL, //父窗口句柄
		NULL,//菜单句柄,没有时设置为NULL
		hInstance, //实例句柄
		NULL //创建数据
		);
	if (!hwnd)
	{
   
		return FALSE;
	}
	ShowWindow(hwnd, SW_SHOW); //显示窗口
	UpdateWindow(hwnd); //刷新
	//Step3:消息循环
	MSG msg;
	while (GetMessage(&msg, NULL, 0, 0))
	{
   
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return 0;
}

详细设计
1、进行环境配置:vs2013将Duilib库包含进来
(1)Duilib环境搭建:
先对Duilib库编译(Duilib库自行下载),将生成的静态库lib和生成的dll文件包含到最外层的Debug目录下,与.exe文件一层,将库文件Duilib放在与.cpp文件一层的目录下,如图:
在这里插入图片描述

在这里插入图片描述

然后对环境进行配置,如图点开项目属性,在C/C++栏中的附加包含目录栏中选择Duilib所在的目录:
在这里插入图片描述

然后在链接器栏的附加库目录选择lib文件所在的目录,如图:
在这里插入图片描述

然后在代码中加入:

#include "UIlib.h"
using namespace DuiLib;
#pragma comment(lib, "DuiLib_ud.lib")

之后我们就可以使用Duilib库来进行窗口的创建响应等操作了;
(2)Duilib库的使用
1> 定义类CDuiFramWnd继承 CWindowWnd类,在该类中实现:
virtual LPCTSTR GetWindowClassName():返回窗口类的名字(CWindowWnd:Duilib自己封装的关于窗口类的相关操作:create(…):注册窗口和创建窗口,显示窗口,更新窗口;ShowModal():消息循环)
virtual LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam):子类如果需要处理系统消息(Windows自己维护的消息)时,需要进行重写uMsg:获取到的消息ID—>区分捕获到的是什么类型的消息
2> 另外CDuiFramWnd继承自INotifyUI类(duilib自己定义的类—>抽象类),按钮创建成功后,添加按钮控件消息响应到duilib的消息循环中;重写INotifyUI类的Notify纯虚函数,在该函数中用户捕获其想要处理的消息,进行自己想要的操作即可。

class INotifyUI
{
   
	virtual void Notify(TNotifyUI& msg)=0;
};

如果需要拦截duilib自己维护的消息时,只需要在子类中重写Notify即可;

typedef struct tagTNotifyUI 
{
   
	CDuiString sType;//消息的类型---"click"鼠标点击"windowinit"
	CDuiString sVirtualWnd;
	CControlUI* pSender;//消息是由哪个空间触发的
	DWORD dwTimestamp;
	POINT ptMouse;
	WPARAM wParam;
	LPARAM lParam;
} TNotifyUI;

3> Duilib其实并没有区分标题栏和客户区,它的实现方法是屏蔽了系统自带的标题栏,用客户区来模拟标题栏,所以想怎么画就怎么画,非常方便。 在HandleMessage函数里屏蔽以下三个消息即可 WM_NCACTIVATE、WM_NCCALCSIZE、WM_NCPAINT;

实现代码:

#include "UIlib.h"
using namespace DuiLib;
#pragma comment(lib, "DuiLib_ud.lib")
class CDuiFramWnd : public CWindowWnd,public INotifyUI
{
   
public:
	// CWindowWnd类的纯虚函数,在该函数中必须返回用户所定义窗口的类名称,注册窗口时需要用到
	virtual LPCTSTR GetWindowClassName() const
	{
   
		return _T("DuiFramWnd");
	}
	virtual LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)//处理系统消息
	{
   
		if (WM_CREATE == uMsg)
		{
   
			初始化绘画管理器
			//m_PaintManager.Init(m_hWnd);//m_hWnd:基类中的句柄

			在窗口创建期间创建一个按钮
			//CControlUI* pBTN = new CButtonUI;//定义按钮
			//pBTN->SetText(_T("hello"));//设置窗口按钮文本
			//pBTN->SetBkColor(0xFF00FF00);//设置窗口按钮颜色

			//m_PaintManager.AttachDialog(pBTN);//将按钮关联到绘制管理器上

			//m_PaintManager.AddNotifier(this);//将按钮增加到消息循环中
			m_PaintManager.Init(m_hWnd);
			CDialogBuilder builder;
			// duilib.xml需要放到exe目录下
			CControlUI* pRoot = builder.Create(_T("111.xml"), (UINT)0, NULL, &m_PaintManager);//要设置路径
			m_PaintManager.AttachDialog(pRoot);
			m_PaintManager.AddNotifier(this);
			return 0;
		}
		else if (uMsg == WM_NCACTIVATE)
		{
   
			if (!::IsIconic(m_hWnd))
			{
   
				return (wParam == 0) ? TRUE : FALSE;
			}
		}
		else if (uMsg == WM_NCCALCSIZE)
		{
   
			return 0;
		}
		else if (uMsg == WM_NCPAINT)
		{
   
			return 0;
		}
		//拦截会话相关消息
		LRESULT lRse = 0;
		if (m_PaintManager.MessageHandler(uMsg, wParam, lParam, lRse))
		{
   
			return lRse;
		}
		//其他消息
		return __super::HandleMessage(uMsg, wParam, lParam);
	}
	virtual void Notify(TNotifyUI& msg)
	{
   
		//响应按钮单击消息
		if (msg.sType == _T("click"))//注意这里不能交换常量和sType
		{
   
			MessageBox(m_hWnd, _T("按钮单击"), _T("Test"), IDOK);
		}
	}
private:
	CPaintManagerUI m_PaintManager;
};

int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int
	nCmdShow)
{
   
	//对应111.xml文件,因为要找到路径
	CPaintManagerUI::SetInstance(hInstance);
	// 设置资源的默认路径(此处设置为和exe在同一目录)
	CPaintManagerUI::SetResourcePath(CPaintManagerUI::GetInstancePath());
	CDuiFramWnd framWnd;
	// Cashier即在窗口右上角显式的名字
	// UI_WNDSTYLE_FRAME: duilib封装的宏,代表窗口可视,具有标题栏,最大化最小化,关闭功能等
	// WS_EX_WINDOWEDGE: Win32的窗口风格,带有边框
	framWnd.Create(NULL, _T("DulibTest"), UI_WNDSTYLE_FRAME, WS_EX_WINDOWEDGE);
	//显示窗口,激活消息循环
	framWnd.ShowModal();
	return 0;
}

具体项目实现
1、我这里实现的是能够使用这个窗口来响应对照片或者视频进行gif动态图的制作,因此这时需要用到ffmpeg工具(它提供了音视频的编码,解码,转码,封装,解封装,流,滤镜,播放等功能),此时就需要用到ffmpeg相关命令,我们先将所需要的图片和外挂字幕视频资源以及ffmpeg工具放在Win32工程最外层的Debug目录下,我们可以在本机的cmd控制台上执行相关命令先来完成gif动态图的制作,相关命令如下:
使用图片生成gif动态图

ffmpeg -r 3 -i .\Picture %d.jpg output.gif -y

其中-r后跟上数字表示每一次显示多少帧,-i表示输入,.\Picture %d.jpg表示图片资源路径,其中%d表示通配以数字开头的照片,output.gif表示生成的gif名字,-y表示如果路径下存在与输出文件相同的文件名,则覆盖
使用视频生成gif动态图

1、从原视频中截取所需要的片段:ffmpeg -i input.mkv -vcodec copy -acodec copy -ss 00:40:07 -to 00:40:28 11.mkv -y

-vcodec copy -acodec copy表示将视频和音频流拷贝过来,-ss … -to …表示截取视频的长度,11.mkv表示生成的片段;

2、从视频中提取字幕:ffmpeg -i 11.mkv input.srt -y

input.srt表示生成的字幕;

3、编辑字幕

4、从截取的片段中只抽离视频(不要字幕和音频),即提取视频裸流:ffmpeg -i 11.mkv -vcodec copy -an -sn 22.mkv -y

其中-an表示不要音频,-sn表示不要字幕,22.mkv是生成的视频裸流;

5、将修改后的srt字幕内嵌到视频裸流中,即烧录成一个整天:ffmpeg -i 22.mkv -vf subtitles = input.srt 33.mkv -y

6、生成动态图:ffmpeg -i 33.mkv -vf scale = iw/2:ih/2 -f gif

2、使用Duilib中的UI界面布局器进行窗口设计,我们使用UI布局器来对各个控件(按钮、编辑框、下拉框等)进行绘制,此时就会生成一个xml文件,但是由于UI界面布局器自身存在缺陷,例如:
bug—>在使用时记得及时备份和对于控件支持的不是非常全面;
因此我们在使用时必须及时保存,对于不支持的控件我们可以查询资料对xml文件进行编辑补充
如图:
在这里插入图片描述

选择图片我们就不需要截取编辑框和视频加载路径的控件,诸如截取、提取SRT、写入SRT、提取视频、烧录的按钮都不用实现,因为图片只需要一组ffmpeg命令直接生成Gif就可以,在点击生成Gif按钮后,就可以生成Gif了;选择视频我们就需要一步步来进行实现,点击按钮即可;
3、制作Gif各个部分代码的实现
(1)功能设置,类CDuiFramWnd继承WindowImplBase这个类,因为Duilib已经对常用的操作进行了很好的封装,正常使用时不需要按照之前的方式实现,只需要让用户实现的窗口类继承自Duilib封装的:WindowImplBase 类即可,该类是一个duilib的基础框架类,封装了常用操作,以方便大家使用。 它是以XML作为界面描述的,所以用它的时候,我们必须将界面描述写到XML里。即通过xml文件描述窗口—窗口创建
重写这3个纯虚函数:
virtual CDuiString GetSkinFolder();
virtual CDuiString GetSkinFile();
virtual LPCTSTR GetWindowClassName(void)const;

class CDuiFramWnd : public WindowImplBase
{
   
protected:
	virtual CDuiString GetSkinFolder()
	{
   
		return _T("");//这里不用给xml文件的路径,因为在主函数中已经给过了
	}
	virtual CDuiString GetSkinFile()
	{
   
		return _T("gifMake.xml");//一定要与目录放的xml文件文件名称相同

	}
	virtual LPCTSTR GetWindowClassName(void)const
	{
   
		return _T("GIFMakeWnd");
	}

然后进行消息处理:
1、Windows系统消息—Ha

  • 2
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值