软件项目技术点(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的宽高:

 1 var tempCavas = <HTMLCanvasElement>document.createElement("canvas");
 2 //canvas元素的宽在大约40000的时候,将无法进行绘图
 3 //设置30000为最大值
 4 var shouldWidth = gifInfo.width * gifInfo.images.length;
 5 if (shouldWidth > 30000) {
 6     tempCavas.width = Math.floor(30000 / gifInfo.width) * gifInfo.width;
 7     tempCavas.height = Math.ceil(gifInfo.images.length / Math.floor(30000 / gifInfo.width))*gifInfo.height;
 8 }else {
 9     tempCavas.width = shouldWidth;
10     tempCavas.height = gifInfo.height;
11 }

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);
 1 require('gif')(path,(error, files: Array<string>) => {
 2     if (error) {
 3         Logger.setErrLog(LogCode.image, "文件:File,方法:node_modules-gif,异常信息:" + error);
 4         callBack(null);
 5     }
 6     files.forEach((file, index) => {
 7         var targetDir = FileSytem.imageTempDir + id + index + ".jpg";
 8         
 9         FileSytem.copySync(file, targetDir);
10 
11         try
12         {
13             var tempImage = <HTMLImageElement>document.createElement("img");
14             var tempImageSrc = targetDir;
15             tempImage.id = index.toString();
16             tempImage.src = tempImageSrc;
17             tempImage.onload = (ev: Event) => {
18                 try
19                 {  //计算该张图片绘制到canvas上的位置
20                     var atWidth = gifInfo.width * Number(tempImage.id);
21                     var startx = atWidth % tempCavas.width;
22                     var starty = (atWidth / tempCavas.width | 0) * gifInfo.height;
23 
24                     cxt.drawImage(tempImage, 0, 0, gifInfo.width, gifInfo.height, startx, starty, gifInfo.width, gifInfo.height);
25                     FileSytem.remove(tempImageSrc, null);
26                     ev.target = null;
27                     loadCounter++;
28                     if (gifInfo.images.length == loadCounter) {
29                         var dataBuffer = new Buffer(tempCavas.toDataURL("image/png").replace(/^data:image\/\w+;base64,/, ""), 'base64');
30                         var dataPath = FileSytem.imageDir + id + ".png";
31                         FileSytem.fileSaveSync(dataPath, dataBuffer);
32                         callBack(dataPath, tempCavas.width);
33                         tempCavas.width = 0;
34                         tempCavas.height = 0;
35                     }
36                 }
37                 catch (e) {
38                     Logger.setErrLog(LogCode.image, "文件:File,方法:gifToPng_1,异常信息:" + e);
39                     callBack(null);
40                 }
41             }
42         }
43         catch (e) {
44             Logger.setErrLog(LogCode.image, "文件:File,方法:gifToPng_2,异常信息:" + e);
45             callBack(null);
46         }
47     });
48 });

最后我们拼成的一张大图如下,如果帧数多或者较宽,因为我们设置了最宽30000px 所以就会出现多行的大图。 

 3.将大图绘制到canvas

插入gif到生成大图的过程已经写清楚了,那么怎么利用这张大图来绘制到canvas形成一张动态的效果图呢?

之前我们提到过插入的任何元素都继承自commonElement类,Image是继承commonElement,我们针对gif图插入的功能专门有一个类GifImage,而它继承自Image。这个类里面有个最主要的函数:将大图中的每一部分一张张的循环绘制,具体代码如下:

 1         private drawGif() {
 2             if (this.element && this.context) {
 3                 var lastFrame = this.gifInfo.images[this.currentFrame % this.gifInfo.images.length]
 4                 var nowTime = this.tempNowTime || Date.now();
 5                 if (nowTime- this.lastDrawTime >= lastFrame.delay) {//控制绘制的速度
 6                     this.currentFrame++;
 7                     this.lastDrawTime = nowTime;
 8                 }
 9                 var frameNum = this.currentFrame % this.gifInfo.images.length;//计算是该绘制第几张图
10 
11                 this.context.save();
12                 this.rotate();
13                 //计算截取大图某一部分绘制到画布的其实坐标
14                 var atWidth = this.gifInfo.width * frameNum;
15                 var startx = atWidth % this.totalWidth;
16                 var starty = (atWidth / this.totalWidth | 0) * this.gifInfo.height;
17                 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);
18                 this.context.restore();
19             }
20         }

 

posted @ 2017-01-13 18:11  方帅  阅读(3440)  评论(0编辑  收藏  举报