咱們的軟件支持插入gif圖片,而且展現在軟件裏是動態的,例如插入下面這張gif圖。html
在軟件裏顯示的一樣是這樣的動態效果:node
那麼這張動態的圖是怎麼繪製到canvas上面的呢,若是隻是像繪製一張普通圖片用context.drawImage(img,x,y),這樣繪製出來的只是當前顯示到img標籤的一個靜態畫面。git
下面介紹咱們項目中使用的方法:github
安裝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
咱們的思路就是把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 因此就會出現多行的大圖。
插入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 }