這篇文章主要為大家展示了“如何使用node開發(fā)一款圖集打包工具”,內(nèi)容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領(lǐng)大家一起研究并學(xué)習(xí)一下“如何使用node開發(fā)一款圖集打包工具”這篇文章吧。
創(chuàng)新互聯(lián)是一家專注于成都做網(wǎng)站、網(wǎng)站建設(shè)與策劃設(shè)計,商水網(wǎng)站建設(shè)哪家好?創(chuàng)新互聯(lián)做網(wǎng)站,專注于網(wǎng)站建設(shè)十載,網(wǎng)設(shè)計領(lǐng)域的專業(yè)建站公司;建站業(yè)務(wù)涵蓋:商水等地區(qū)。商水做網(wǎng)站價格咨詢:18980820575
比如把下面的幾張圖片合成一張.
這一張圖集就是我用本文介紹的工具打包合成的.
合成的圖片品質(zhì)依然十分高呢.
我們在web開發(fā)中, 每次在瀏覽器展示一張圖片都需要請求一次服務(wù)器資源.
舉個例子, 3次請求每次4k, 和一次請求12k還是有本質(zhì)區(qū)別的, 然后更多的時候一次請求并不是3 * 4k.
使用圖集能讓我們優(yōu)化資源加載, 提高網(wǎng)站的性能.
在游戲開發(fā)中, 圖集的使用至關(guān)重要, 不管是一般幀動畫還是svga等動畫解決方案, 都不會每張圖片去請求資源.
更多的時候, 我們都是打包成圖集, 而圖集打包工具texturepacker
更是大行其道.
其次, 游戲場景過多, 我們一般都需要分步加載資源, 有的時候一個動畫模型, 涉及的圖片少則十來張, 多則近百張.
圖集的使用不可或缺.
下面我們就來看下如何編寫一款圖集打包工具.
開發(fā)一個圖集打包工具腳本需要什么技能.
node.js編程能力
二維矩形裝箱算法
然后我們思考如何去打包一張圖集.
我們需要找到需要打包的文件夾, 可能有多個或者嵌套文件夾.
圖集是多張散圖拼合而成.
圖集的大小需要可配置
盡可能的壓縮圖集空間, 使每張圖緊密貼合
每個文件夾打包成一個圖集, 需要考慮圖片過多的情況
可能需要生成圖集所需要的json文件, 記錄圖片位置信息
我這里是這樣設(shè)計.
首先我們需要一個打包對象實例MySpritePackTool
, 同時支持寫入配置參數(shù)options
.
/** 圖集打包對象 */ const MySpritePackTool = function (opt) { this.options = { //一個文件夾圖片過多或者過長 遞歸最大次數(shù) maxCount: opt.maxCount || 2, //需要打包圖集的文件路徑 assetsPath: opt.assetsPath, //輸出文件路徑 outPutPath: opt.outPutPath, //一張圖集打包最大size maxSize: { width: 2048, height: 2048 } } };
然后我們需要輸出這個對象, 可以被其他項目所引用.
module.exports = MySpritePackTool;
我們的輸入?yún)?shù)盡可能的少, 這樣就需要我們程序去遍歷文件夾.
例如, 我們有如下的目錄樹:
|--assets |--index |--img-3.png |--img-4.png |--login |--img-5.png |--img-1.png |--img-2.png
我們需要每個文件夾下打包出一張圖集.
思考: 需要什么樣的數(shù)據(jù)結(jié)構(gòu)?
首先便于js解析, 我們約定一個對象,
每一層, 需要一個圖片信息容器assets
;
一個所包含的圖片標識keys
;
一個文件夾名字, 也方便我們后面為圖集命名name
;
然后每層文件夾前套相同對象;
結(jié)構(gòu)如下:
{ assets: [ { id: 'assets/img-1.png', width: 190, height: 187 }, ... ], name: 'assets', keys: 'img-1.png,img-2.png,', index: { assets: [ { id: 'assets/index/img-3.png', width: 190, height: 187 }, ... ], name: 'index', keys: 'img-3.png,img-4.png,' }, login: { assets: [ { id: 'assets/login/img-5.png', width: 190, height: 187 } ], name: 'index', keys: 'img-5.png,' }, }
不難發(fā)現(xiàn), 我們已經(jīng)可以得到需要打包的所有文件和文件夾.
那么用程序如何實現(xiàn)呢?
主要用到nodejs的fs模塊來遞歸操作文件夾, 并輸出所需要的節(jié)點樹.
注意在書寫的時候需要判斷是圖片還是文件夾.
MySpritePackTool.prototype.findAllFiles = function (obj, rootPath) { let nodeFiles = []; if (fs.existsSync(rootPath)) { //獲取所有文件名 nodeFiles = fs.readdirSync(rootPath); //組裝對象 let nameArr = rootPath.split('/'); obj["assets"] = []; obj["name"] = nameArr[nameArr.length - 1]; obj["keys"] = ""; nodeFiles.forEach(item => { //判斷不是圖片路徑 if (!/(.png)|(.jpe?g)$/.test(item)) { let newPath = path.join(rootPath, item); //判斷存在文件 同時是文件夾系統(tǒng) if (fs.existsSync(newPath) && fs.statSync(newPath).isDirectory()) { // console.log("獲得新的地址", newPath); obj[item] = {}; this.findAllFiles(obj[item], newPath); } else { console.log(`文件路徑: ${newPath}不存在!`); } } else { console.log(`圖片路徑: ${item}`); obj["keys"] += item + ","; let params = {}; params["id"] = path.resolve(rootPath, `./${item}`); //獲得圖片寬高 params["width"] = images(path.resolve(rootPath, `./${item}`)).width(); params["height"] = images(path.resolve(rootPath, `./${item}`)).height(); obj["assets"].push(params); } }) } else { console.log(`文件路徑: ${rootPath}不存在!`); } }
這樣子我們就可以得到我們所需要的節(jié)點樹了.
我們對文件夾的操作已經(jīng)完成, 這時候我們就需要思考.
如何把這些零散的圖片打包到一張圖片上.
散圖有兩個信息, 一個width
和一個height
, 其實就是一個矩形.
我們現(xiàn)在所要做的就是把這些不同面積的矩形放到一個具有最大長寬的大矩形中.
跳開圖片, 從矩形放置入手
二維矩形裝箱算法有不少, 我這里選用一種比較簡單的.
首先得到一個具有最大長寬的矩形盒子.
我們先放入一個矩形A, 這樣子, 剩余區(qū)域就有兩塊: 矩形A的右邊和矩形A的下邊.
然后我們繼續(xù)放入矩形B, 可以先右再下, 然后基于矩形B又有兩塊空白空間.
依次類推, 我們就可以將合適的矩形全部放入.
舉個例子
把左邊的散裝矩形放入右邊的矩形框中, 可以得到:
可以看到, 我們節(jié)省了很多空間, 矩形排列緊湊.
如果用代碼實現(xiàn), 是怎么樣的呢?
/** * 確定寬高 w h * 空白區(qū)域先放一個, 剩下的尋找右邊和下邊 * 是否有滿足右邊的, 有則 放入 無則 繼續(xù)遍歷 * 是否有滿足下邊的, 有則 放入 無則 繼續(xù)遍歷 */ const Packer = function (w, h) { this.root = { x: 0, y: 0, width: w, height: h }; // /** 匹配所有的方格 */ Packer.prototype.fit = function (blocks) { let node; for (let i = 0; i < blocks.length; i++) { let block = blocks[i]; node = this.findNode(this.root, block.width, block.height); if (node) { let fit = this.findEmptyNode(node, block.width, block.height); block.x = fit.x; block.y = fit.y; block.fit = fit; } } } /** 找到可以放入的節(jié)點 */ Packer.prototype.findNode = function (node, w, h) { if (node.used) { return this.findNode(node.rightArea, w, h) || this.findNode(node.downArea, w, h); } else if (node.width >= w && node.height >= h) { return node; } else { return null; } } /** 找到空位 */ Packer.prototype.findEmptyNode = function (node, w, h) { //已經(jīng)使用過的 刪除 node.used = true; //右邊空間 node.rightArea = { x: node.x + w, y: node.y, width: node.width - w, height: h }; //下方空位 node.downArea = { x: node.x, y: node.y + h, width: node.width, height: node.height - h } return node; } }
使用遞歸, 代碼量很少, 但是功能強大.
但是有一個問題, 如果超出定長定寬, 或者一個矩形裝不完, 我們的算法是不會放入到大矩形中的.
這樣子就有點不滿足我們的圖集打包思路了.
所以我們還需要將這個算法改進一下;
加入兩個變量, 一個記錄使用的總的區(qū)域, 一個記錄未被裝入的矩形.
//記錄使用的總的區(qū)域 this.usedArea = { width: 0, height: 0 }; //記錄未被裝入的矩形 this.levelBlocks = [];
詳細代碼可以查看源碼中的packing
.
當然, 這里只是最簡單的一種二維裝箱算法
還有一種加強版的裝箱算法, 我放在源碼里了, 這里就不贅述了, 原理基本一致
現(xiàn)在, 我們已經(jīng)可以將矩形合適的裝箱了, 那怎么使用去處理成圖集呢?
定義一個dealImgsPacking
方法, 繼續(xù)去處理我們的節(jié)點樹.
這里用到了我們的配置項maxCount
, 就是為了一張圖集裝不完, 多打出幾張圖集的作用.
然后我們打包出來的圖集命名使用文件夾 + 當前是第幾張的形式.
`${obj['name'] + (count ? "-" + count : '')}`
具體方法如下:
MySpritePackTool.prototype.dealImgsPacking = function (obj) { let count = 0; if (obj.hasOwnProperty("assets")) { let newBlocks = obj["assets"]; obj["assets"] = []; while (newBlocks.length > 0 && count < this.options.maxCount) { let packer1 = new Packer(this.options.maxSize.width, this.options.maxSize.height); packer1.fit(newBlocks); let sheets1 = { maxArea: packer1.usedArea, atlas: newBlocks, fileName: `${obj['name'] + (count ? "-" + count : '')}` }; newBlocks = packer1.levelBlocks; obj["assets"].push(sheets1); count++; } } for (let item in obj) { if (obj[item].hasOwnProperty("assets")) { this.dealImgsPacking(obj[item]); } } }
通過這個方法我們改造了之前的節(jié)點樹;
將之前節(jié)點樹中的assest
變?yōu)榱艘粋€數(shù)組, 每個數(shù)組元素代表一張圖集信息.
結(jié)構(gòu)如下:
assets: [ { maxArea: { width: 180,height: 340 }, atlas: [ { id: 'assets/index/img-3.png', width: 190, height: 187, x: 0, y: 0 } ], fileName: 'assets' }, ... ]
我們可以清晰的得到, 打包之后的圖集, 最大寬高是maxArea
, 每張圖寬高位置信息是atlas
,以及圖集名稱fileName
.
接下來, 就是最后一步了, 繪制新的圖片, 并輸出圖片文件.
注意
我們在使用打包算法的時候, 可以先進行一下基于圖片大小的排序
這樣以來打包出來的圖集會留白更小
這里圖集的繪制和輸出均是使用了node-images
的API;
遍歷之前得到的節(jié)點樹, 首先繪制一張maxArea
大小的空白圖像.
images(item["maxArea"].width, item["maxArea"].height)
然后遍歷一張圖集所需要的圖片信息, 將每一張圖片繪制到空白圖像上.
//繪制空白圖像 let newSprites = images(item["maxArea"].width, item["maxArea"].height); //繪制圖集 imgObj.forEach(it => { newSprites.draw(images(it["id"]), it["x"], it["y"]); });
然后繪制完一張圖集輸出一張.
newSprites.save(`${this.options.outPutPath}/${item['fileName']}.png`);
最后對節(jié)點樹遞歸調(diào)用, 繪制出所有的圖集.
具體代碼如下:
MySpritePackTool.prototype.drawImages = function (obj) { let count = 0; if (obj.hasOwnProperty("assets")) { //打包出一個或者多個圖集 let imgsInfo = obj["assets"]; imgsInfo.forEach(item => { if (item.hasOwnProperty("atlas")) { let imgObj = item["atlas"]; // console.log("8888",imgObj) //繪制一張透明圖像 let newSprites = images(item["maxArea"].width, item["maxArea"].height); imgObj.forEach(it => { newSprites.draw(images(it["id"]), it["x"], it["y"]); }); newSprites.save(`${this.options.outPutPath}/${item['fileName']}.png`); count++; } }) } for (let item in obj) { if (obj[item].hasOwnProperty("assets")) { this.drawImages(obj[item]); } } }
這樣子, 我們就大功告成了,
運行測試一下, 可以得到如下的圖集:
效果還不錯.
安裝
npm i sprites-pack-tool
使用
const MySpritePackTool = require("sprites-pack-tool"); const path = require("path"); /** 打包最多遞歸次數(shù) */ const MAX_COUNT = 2; //需要合成的圖集的路徑 const assetsPath = path.resolve(__dirname, "./assets"); /** 圖集打包工具配置*/ const mySpritePackTool = new MySpritePackTool({ //一個文件夾圖片過多或者過長 遞歸最大次數(shù) maxCount: MAX_COUNT, //需要打包圖集的文件路徑 assetsPath: assetsPath, //輸出文件路徑 outPutPath: path.resolve(__dirname, "./res"), //一張圖集打包最大size maxSize: { width: 2048,height: 2048} }); /** 圖集打包 */ mySpritePackTool.Pack2Sprite();
以上是“如何使用node開發(fā)一款圖集打包工具”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學(xué)習(xí)更多知識,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道!
網(wǎng)站名稱:如何使用node開發(fā)一款圖集打包工具
新聞來源:http://jinyejixie.com/article46/jopieg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站設(shè)計、關(guān)鍵詞優(yōu)化、建站公司、域名注冊、小程序開發(fā)、網(wǎng)站維護
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)