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. 软件项目技术点(1)——Tween算法及缓动效果

    AxeSlide软件项目梳理   canvas绘图系列知识点整理 Tween算法及缓动效果 软件里在切换步序时需要有过渡动画效果,从当前位置的画面缓动到目标位置的画面.动画效果可重新查看文章系列第一篇 ...

  2. 软件项目技术点(1)——d3.interpolateZoom-在两个点之间平滑地缩放平移

    AxeSlide软件项目梳理   canvas绘图系列知识点整理 软件参考d3的知识点 我们在软件中主要用到d3.js的核心函数d3.interpolateZoom - 在两个点之间平滑地缩放平移.请 ...

  3. 软件项目技术点(5)——在canvas上绘制动态网格线

    AxeSlide软件项目梳理   canvas绘图系列知识点整理 grid类的实现 当鼠标在画布上缩放时,网格能跟着我的鼠标滚动而相应的有放大缩小的效果. 下面是具体实现的代码,draw函数里计算出大 ...

  4. 软件项目技术点(6)——结合鼠标操作绘制动态canvas画布

    AxeSlide软件项目梳理   canvas绘图系列知识点整理 我们创建一个类封装了所有鼠标需要处理的事件. export class MouseEventInfo { el: HTMLElemen ...

  5. 软件项目技术点(7)——在canvas上绘制自定义图形

    AxeSlide软件项目梳理   canvas绘图系列知识点整理 图形种类 目前我们软件可以绘制出来的形状有如下这几种,作为开发者我们一直想支持用户可以拖拽的类似word里面图形库,但目前还没有找到比 ...

  6. 软件项目技术点(2)——Canvas之平移translate、旋转rotate、缩放scale

    AxeSlide软件项目梳理   canvas绘图系列知识点整理 画布操作介绍 画布绘图的环境通过translate(),scale(),rotate(), setTransform()和transf ...

  7. 软件项目技术点(2)——Canvas之坐标系转换

    AxeSlide软件项目梳理   canvas绘图系列知识点整理 默认坐标系与当前坐标系 canvas中的坐标是从左上角开始的,x轴沿着水平方向(按像素)向右延伸,y轴沿垂直方向向下延伸.左上角坐标为 ...

  8. 软件项目技术点(2)——Canvas之获取Canvas当前坐标系矩阵

    AxeSlide软件项目梳理   canvas绘图系列知识点整理 前言 在我的另一篇博文 Canvas坐标系转换 中,我们知道了所有的平移缩放旋转操作都会影响到画布坐标系.那在我们对画布进行了一系列操 ...

  9. 软件项目技术点(8)—— canvas调用drawImage绘制图片

    AxeSlide软件项目梳理   canvas绘图系列知识点整理 html5中标签canvas,函数drawImage(): 使用drawImage()方法绘制图像.绘图环境提供了该方法的三个不同版本 ...

随机推荐

  1. php-elasticsearch bulk批量插入数据

    1.单条插入 <?php include '../vendor/Elasticsearch/autoload.php'; $a['hosts'] = array( #需要用户名时 http:// ...

  2. 通过Maven简单搭建SSM框架

    创建Maven就不用多说了,下面直接看Pom.xml里面的依赖吧 <properties> <!-- spring版本号 --> <spring.version>5 ...

  3. css 命名规划

    命名规范 前言中略微描述了 CSS 怎么使用:下面介绍一下 CSS 的一些代码规范: CSS 命名一般采用小写英文单词或组合命名,单词与单词间以"-"分割:英文单词不缩写,除非一看 ...

  4. 清除bean中所有非基本数据类型的属性值

    利用beanutils清除javabean中所有非基本数据类型的属性值: import com.google.gson.Gson; import lombok.Data; import org.apa ...

  5. git 修改配置

    git config 查看配置信息 config 配置项帮助信息 $ git config config 配置有system级别 global(用户级别) 和local(当前仓库)三个设置项 从sys ...

  6. Quartz .net 一直运行失败

    使用了新的: Quartz.NET 2.6.2 https://github.com/quartznet/quartznet/releases IScheduler scheduler = StdSc ...

  7. java的值传递

    java中都是值传递 如果参数是引用类型的,实际上是把引用地址复制了一份传递 例如 User user = new User(); user存放在栈中,里面存放着内存地址,new User()会在堆中 ...

  8. Git修改文件

    如果我们修改了本地的某个文件但是没有提交,这时我们用 $ git status可以看到提示,例如我在readme2.txt里面新加了一行,然后查看状态: git status命令可以让我们时刻掌握仓库 ...

  9. 《数据密集型应用系统设计》读书笔记-ch1可靠、可扩展与可维护的应用系统

    我们以Twitter为例,使用其2012年11月发布的数据.Twitter的两个典型业务操作是: - 发布tweet消息: 用户可以快速推送新消息到所有的关注者,平均大约4.6k request/se ...

  10. zookeeper 选举机制 和 eruake

    zookeeper简介: 在分布式环境中,多个服务之间协调一致.有提供分布式锁.服务配置.实现分布式领域CAP(consistency一致性,Availiablity高可用,patition tolr ...