• 软件项目技术点(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         }
  • 相关阅读:
    三角形的个数
    Nightmare(搜索)
    Prime Ring Problem(搜索)
    Safecracker(搜索)
    丑数
    八皇后问题(回溯法)
    Dijkstra
    floyd详解
    继续畅通工程(kruskal prim)
    畅通工程
  • 原文地址:https://www.cnblogs.com/fangsmile/p/6273714.html
Copyright © 2020-2023  润新知