软件项目技术点(9)——如何将gif动态图拆分绘制
AxeSlide软件项目梳理 canvas绘图系列知识点整理
背景介绍
我们的软件支持插入gif图片,并且展示在软件里是动态的,例如插入下面这张gif图。

在软件里显示的同样是这样的动态效果:

那么这张动态的图是怎么绘制到canvas上面的呢,如果只是像绘制一张普通图片用context.drawImage(img,x,y),这样绘制出来的只是当前显示到img标签的一个静态画面。
下面介绍我们项目中使用的方法:
1. 解析gif文件信息
安装Node.js的gify-parse模块,该模块用于解析gif文件信息的API。
具体使用和介绍参见: https://www.npmjs.com/package/gify-parse
我们读取上面那张gif图到buffer然后用该模块解析出的结果如下图:


注意:解析出来的结果有点小问题,宽高的值是颠倒的
利用上面图1中的gifInfo信息,我们用animated=true判定这张图确实是gif图,它是由24张图组成,每张图的宽高为384*288
利用上面图2中的delay这个属性值,它表示两张图变换的间隔时间,在接下来的第3步绘制大图到canvas中会用到这个属性。
2. 拼大图
我们的思路就是把gif中包含的24张图拼成一张大图片,拼大图我们利用canvas,将24张图挨个绘制到临时的一个canvas上面,最后将canvas保存成本地png文件。
下面的代码用来计算我们的画布tempCanvas的宽高:
var tempCavas = <HTMLCanvasElement>document.createElement("canvas");
//canvas元素的宽在大约40000的时候,将无法进行绘图
//设置30000为最大值
var shouldWidth = gifInfo.width * gifInfo.images.length;
if (shouldWidth > 30000) {
tempCavas.width = Math.floor(30000 / gifInfo.width) * gifInfo.width;
tempCavas.height = Math.ceil(gifInfo.images.length / Math.floor(30000 / gifInfo.width))*gifInfo.height;
}else {
tempCavas.width = shouldWidth;
tempCavas.height = gifInfo.height;
}
gify-parse模块只解析出来了宽高等一部分有用信息那么,不能得到每张具体的图片。
我们需要引入gif模块,https://github.com/liufangfang/gif从这里下载即可,该模块很简单只有一个函数function(gifSrcPath, callBack) {},传入gif图片文件路径和一个回调函数,回调函数接收错误信息和每个帧存储到本地的图片路径callBack(null, pathList)。
下面就看我们的回调函数如何利用这个文件列表files:
1)基本思路就是通过createElement("img")创建IMG标签
2)img.onload之后将图片绘制到canvas.context上,当然绘制的位置是需要根据当前图片是gif图中第几帧位置去计算的
3)绘制完最后一张后,将canvas转换成图片信息保存到本地
cxt.drawImage(tempImage, 0, 0, gifInfo.width, gifInfo.height, startx, starty, gifInfo.width, gifInfo.height);
require('gif')(path,(error, files: Array<string>) => {
if (error) {
Logger.setErrLog(LogCode.image, "文件:File,方法:node_modules-gif,异常信息:" + error);
callBack(null);
}
files.forEach((file, index) => {
var targetDir = FileSytem.imageTempDir + id + index + ".jpg";
FileSytem.copySync(file, targetDir);
try
{
var tempImage = <HTMLImageElement>document.createElement("img");
var tempImageSrc = targetDir;
tempImage.id = index.toString();
tempImage.src = tempImageSrc;
tempImage.onload = (ev: Event) => {
try
{ //计算该张图片绘制到canvas上的位置
var atWidth = gifInfo.width * Number(tempImage.id);
var startx = atWidth % tempCavas.width;
var starty = (atWidth / tempCavas.width | 0) * gifInfo.height;
cxt.drawImage(tempImage, 0, 0, gifInfo.width, gifInfo.height, startx, starty, gifInfo.width, gifInfo.height);
FileSytem.remove(tempImageSrc, null);
ev.target = null;
loadCounter++;
if (gifInfo.images.length == loadCounter) {
var dataBuffer = new Buffer(tempCavas.toDataURL("image/png").replace(/^data:image\/\w+;base64,/, ""), 'base64');
var dataPath = FileSytem.imageDir + id + ".png";
FileSytem.fileSaveSync(dataPath, dataBuffer);
callBack(dataPath, tempCavas.width);
tempCavas.width = 0;
tempCavas.height = 0;
}
}
catch (e) {
Logger.setErrLog(LogCode.image, "文件:File,方法:gifToPng_1,异常信息:" + e);
callBack(null);
}
}
}
catch (e) {
Logger.setErrLog(LogCode.image, "文件:File,方法:gifToPng_2,异常信息:" + e);
callBack(null);
}
});
});
最后我们拼成的一张大图如下,如果帧数多或者较宽,因为我们设置了最宽30000px 所以就会出现多行的大图。

3.将大图绘制到canvas
插入gif到生成大图的过程已经写清楚了,那么怎么利用这张大图来绘制到canvas形成一张动态的效果图呢?
之前我们提到过插入的任何元素都继承自commonElement类,Image是继承commonElement,我们针对gif图插入的功能专门有一个类GifImage,而它继承自Image。这个类里面有个最主要的函数:将大图中的每一部分一张张的循环绘制,具体代码如下:
private drawGif() {
if (this.element && this.context) {
var lastFrame = this.gifInfo.images[this.currentFrame % this.gifInfo.images.length]
var nowTime = this.tempNowTime || Date.now();
if (nowTime- this.lastDrawTime >= lastFrame.delay) {//控制绘制的速度
this.currentFrame++;
this.lastDrawTime = nowTime;
}
var frameNum = this.currentFrame % this.gifInfo.images.length;//计算是该绘制第几张图
this.context.save();
this.rotate();
//计算截取大图某一部分绘制到画布的其实坐标
var atWidth = this.gifInfo.width * frameNum;
var startx = atWidth % this.totalWidth;
var starty = (atWidth / this.totalWidth | 0) * this.gifInfo.height;
this.context.drawImage(this.element, startx, starty, this.gifInfo.width, this.gifInfo.height, this.config.translate.x, this.config.translate.y, this.config.width, this.config.height);
this.context.restore();
}
}
软件项目技术点(9)——如何将gif动态图拆分绘制的更多相关文章
- 软件项目技术点(1)——Tween算法及缓动效果
AxeSlide软件项目梳理 canvas绘图系列知识点整理 Tween算法及缓动效果 软件里在切换步序时需要有过渡动画效果,从当前位置的画面缓动到目标位置的画面.动画效果可重新查看文章系列第一篇 ...
- 软件项目技术点(1)——d3.interpolateZoom-在两个点之间平滑地缩放平移
AxeSlide软件项目梳理 canvas绘图系列知识点整理 软件参考d3的知识点 我们在软件中主要用到d3.js的核心函数d3.interpolateZoom - 在两个点之间平滑地缩放平移.请 ...
- 软件项目技术点(5)——在canvas上绘制动态网格线
AxeSlide软件项目梳理 canvas绘图系列知识点整理 grid类的实现 当鼠标在画布上缩放时,网格能跟着我的鼠标滚动而相应的有放大缩小的效果. 下面是具体实现的代码,draw函数里计算出大 ...
- 软件项目技术点(6)——结合鼠标操作绘制动态canvas画布
AxeSlide软件项目梳理 canvas绘图系列知识点整理 我们创建一个类封装了所有鼠标需要处理的事件. export class MouseEventInfo { el: HTMLElemen ...
- 软件项目技术点(7)——在canvas上绘制自定义图形
AxeSlide软件项目梳理 canvas绘图系列知识点整理 图形种类 目前我们软件可以绘制出来的形状有如下这几种,作为开发者我们一直想支持用户可以拖拽的类似word里面图形库,但目前还没有找到比 ...
- 软件项目技术点(2)——Canvas之平移translate、旋转rotate、缩放scale
AxeSlide软件项目梳理 canvas绘图系列知识点整理 画布操作介绍 画布绘图的环境通过translate(),scale(),rotate(), setTransform()和transf ...
- 软件项目技术点(2)——Canvas之坐标系转换
AxeSlide软件项目梳理 canvas绘图系列知识点整理 默认坐标系与当前坐标系 canvas中的坐标是从左上角开始的,x轴沿着水平方向(按像素)向右延伸,y轴沿垂直方向向下延伸.左上角坐标为 ...
- 软件项目技术点(2)——Canvas之获取Canvas当前坐标系矩阵
AxeSlide软件项目梳理 canvas绘图系列知识点整理 前言 在我的另一篇博文 Canvas坐标系转换 中,我们知道了所有的平移缩放旋转操作都会影响到画布坐标系.那在我们对画布进行了一系列操 ...
- 软件项目技术点(8)—— canvas调用drawImage绘制图片
AxeSlide软件项目梳理 canvas绘图系列知识点整理 html5中标签canvas,函数drawImage(): 使用drawImage()方法绘制图像.绘图环境提供了该方法的三个不同版本 ...
随机推荐
- [web]Servlet中的Listener和Filter
建议先看看 ——> Servlet工作原理 一.Listener 在Tomcat服务中,Listener的设计是基于观察者模式的,目前在Servlet中提供6中两类事件的观察者接口,它们分别是: ...
- 2019 CCPC-Wannafly Winter Camp Day2(Div2, onsite)
solve 4/11 A Erase Numbers II Code:KK Thinking :KK 用ans表示当前最优答案,maxx表示遍历到的最大数字,一开始ans肯定等于a[ 1 ]+a[ 2 ...
- Javascript 定时器的使用陷阱 (setInterval)
setTimeout(function(){ // 其他代码 setTimeout(arguments.callee, interval); }, interval); setInterval会产生回 ...
- JobScheduler布置后台任务以及实现进程保活?
1.简介 在Android 5.0 提供了一套新的 JobScheduler API,它允许您定义要在以后的某个时间或在指定的条件下(例如,当设备在充电时)异步运行的作业来优化电池寿命. https: ...
- 使用vmtools来设置windows和linux的共享文件夹
目的:通过vmtools来实现windows和linux的共享文件夹 步骤: 1.前提条件是vmtools已经安装 2.在windows任意磁盘新建一个共享文件夹 3.进入虚拟机->设置-> ...
- thinkphp5+nginx的linux环境搭建
安装环境&工具安装php安装nginx运行服务器安装thinkphp安装Composer安装thinkphp配置nginx.conf配置php-fpm运行thinkphp注意事项 php7已经 ...
- 与native交互时会出现的问题
1.jsbridge: 可以用jsbridge与native交互,这属于第三方库,前端后端都需要加jsbridge 2.可以直接调用原生的方法,ios: window.webkit.message ...
- java实现HTTP请求的三种方式
目前JAVA实现HTTP请求的方法用的最多的有两种:一种是通过HTTPClient这种第三方的开源框架去实现.HTTPClient对HTTP的封装性比较不错,通过它基本上能够满足我们大部分的需求,Ht ...
- [Android]Caused by: java.lang.IllegalArgumentException: Service not registered.md
Caused by: java.lang.IllegalArgumentException: Service not registered: org.diql.aidldemo.MainActivit ...
- php的stristr()函数,查找字符
1.用法,要传2个参数 stristr(string,search):查找并返还匹配后,剩下的部分字符串 查找过程不区分大小写,要区分大小写用 strstr(string,search)少一个字母i ...