軟件項目技術點(9)——如何將gif動態圖拆分繪製

AxeSlide軟件項目梳理   canvas繪圖系列知識點整理

背景介紹

咱們的軟件支持插入gif圖片,而且展現在軟件裏是動態的,例如插入下面這張gif圖。html

在軟件裏顯示的一樣是這樣的動態效果:node

那麼這張動態的圖是怎麼繪製到canvas上面的呢,若是隻是像繪製一張普通圖片用context.drawImage(img,x,y),這樣繪製出來的只是當前顯示到img標籤的一個靜態畫面。git

下面介紹咱們項目中使用的方法:github

1. 解析gif文件信息

安裝Node.js的gify-parse模塊,該模塊用於解析gif文件信息的API。npm

具體使用和介紹參見: https://www.npmjs.com/package/gify-parsecanvas

咱們讀取上面那張gif圖到buffer而後用該模塊解析出的結果以下圖:ide

注意:解析出來的結果有點小問題,寬高的值是顛倒的函數

利用上面圖1中的gifInfo信息,咱們用animated=true斷定這張圖確實是gif圖,它是由24張圖組成,每張圖的寬高爲384*288post

利用上面圖2中的delay這個屬性值,它表示兩張圖變換的間隔時間,在接下來的第3步繪製大圖到canvas中會用到這個屬性。ui

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         }
相關文章
相關標籤/搜索