目錄
為什麼需要使用圖集
web開發
遊戲開發
工具設計
開始寫腳本
腳本IO
遍歷檔案產生節點樹
图集打包并输出
如何使用
展望
後文
首頁 web前端 js教程 手把手帶你使用node開發一款圖集打包工具

手把手帶你使用node開發一款圖集打包工具

Nov 29, 2021 pm 06:53 PM
node

這篇文章就來手把手教你使用node手寫一款圖集打包工具,有一定的參考價值,希望對大家有所幫助!

手把手帶你使用node開發一款圖集打包工具

偶然發現一款很好用的跨平台圖像編解碼庫node-images.

仔細閱讀其API,就萌生了一個使用其製作精靈圖集的想法.

於是就誕生了這個工具sprites-pack-tool.

你可以在github查看

https://github.com/xdq1553/MySpritesPackTool

你可以使用npm安裝

#https://www.npmjs.com /package/sprites-pack-tool


對於精靈圖集, 我想大家都不陌生.

例如把下面的幾張圖片合成一張.

手把手帶你使用node開發一款圖集打包工具

手把手帶你使用node開發一款圖集打包工具

#這一張圖集就是我用本文介紹的工具打包合成的.

合成的圖片品質依然十分高呢.

為什麼需要使用圖集

web開發

我們在web開發, 每次在瀏覽器展示一張圖片都需要請求一次伺服器資源.

舉例, 3次請求每次4k, 和一次請求12k還是有本質區別的, 然後更多的時候一次請求並不是3 * 4k.

使用圖集能讓我們優化資源加載, 提高網站的性能.

遊戲開發

在遊戲開發中, 圖集的使用至關重要, 不管是一般幀動畫還是svga等動畫解決方案, 都不會每張圖片去請求資源.

更多的時候, 我們都是打包成圖集, 而圖集打包工具texturepacker更是大行其道.

其次, 遊戲場景過多, 我們一般都需要分步加載資源, 有的時候一個動畫模型, 涉及的圖片少則十來張, 多則近百張.

圖集的使用不可或缺.

下面我們就來看如何寫一款圖集打包工具.

工具設計

開發一個圖集打包工具腳本需要什麼技能.

  • node.js程式設計能力

  • 二維矩形裝箱演算法

然後我們思考如何去打包一張圖集.

  • 我們需要找到需要打包的資料夾, 可能有多個或巢狀資料夾.

  • 圖集是多張散圖拼合而成.

  • 圖集的大小需要可配置

  • 盡可能的壓縮圖集空間, 使每張圖緊密貼合

  • 每個資料夾打包成一個圖集, 需要考慮圖片過多的情況

  • 可能需要產生圖集所需的json文件, 記錄圖片位置資訊

開始寫腳本

腳本IO

我這裡是這樣設計.

首先我們需要一個打包物件實例MySpritePackTool, 同時支援寫入配置參數options.

/** 图集打包对象 */
const MySpritePackTool = function (opt) {
    this.options = {
        //一个文件夹图片过多或者过长 递归最大次数
        maxCount: opt.maxCount || 2,
        //需要打包图集的文件路径
        assetsPath: opt.assetsPath,
        //输出文件路径
        outPutPath: opt.outPutPath,
        //一张图集打包最大size
        maxSize: { width: 2048, height: 2048 }
    }
};
登入後複製

然後我們需要輸出這個物件, 可以被其他項目所引用.

module.exports = MySpritePackTool;
登入後複製

遍歷檔案產生節點樹

我們的輸入參數盡可能的少, 這樣就需要我們程式去遍歷資料夾.

例如, 我們有如下的目錄樹:

|--assets
   |--index
      |--img-手把手帶你使用node開發一款圖集打包工具
      |--img-手把手帶你使用node開發一款圖集打包工具
   |--login
      |--img-手把手帶你使用node開發一款圖集打包工具
   |--img-手把手帶你使用node開發一款圖集打包工具
   |--img-手把手帶你使用node開發一款圖集打包工具
登入後複製

我們需要每個資料夾下方打包出一張圖集.

思考: 需要什麼樣的資料結構?

##首先便於js解析, 我們約定一個物件,

每一層, 需要一個圖片資訊容器

assets;

一個所包含的圖片標識

keys;

一個資料夾名字, 也方便我們後面為圖集命名

name;

#然後每層資料夾前套相同物件;

#結構如下:

{
  assets: [
    {
      id: 'assets/img-手把手帶你使用node開發一款圖集打包工具',
      width: 190,
      height: 187
    },
    ...
  ],
  name: 'assets',
  keys: 'img-手把手帶你使用node開發一款圖集打包工具,img-手把手帶你使用node開發一款圖集打包工具,',
  index: {
    assets: [
        {
            id: 'assets/index/img-手把手帶你使用node開發一款圖集打包工具',
            width: 190,
            height: 187
        },
        ...
    ],
    name: 'index',
    keys: 'img-手把手帶你使用node開發一款圖集打包工具,img-手把手帶你使用node開發一款圖集打包工具,'
  },
  login: {
    assets: [
        {
            id: 'assets/login/img-手把手帶你使用node開發一款圖集打包工具',
            width: 190,
            height: 187
        }
    ],
    name: 'index',
    keys: 'img-手把手帶你使用node開發一款圖集打包工具,'
  },
}
登入後複製

不難發現, 我們已經可以得到需要打包的所有檔案和資料夾.

那麼用程式如何實作呢?

主要用到nodejs的

fs模組來遞歸操作資料夾, 並輸出所需的節點樹.

注意在書寫的時候需要判斷是圖片還是資料夾.

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);
                //判断存在文件 同时是文件夹系统
                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}不存在!`);
    }
}
登入後複製

這樣子我們就可以得到我們所需要的節點樹了.

#獲取新的圖集位置資訊

我們對資料夾的操作已經完成, 這時候我們就需要思考.

如何把這些零散的圖片打包到一張圖片上.

散圖有兩個訊息, 一個

width和一個height, 其實就是一個矩形.

我们现在所要做的就是把这些不同面积的矩形放到一个具有最大长宽的大矩形中.


跳开图片, 从矩形放置入手

二维矩形装箱算法有不少, 我这里选用一种比较简单的.

首先得到一个具有最大长宽的矩形盒子.

我们先放入一个矩形A, 这样子, 剩余区域就有两块: 矩形A的右边矩形A的下边.

手把手帶你使用node開發一款圖集打包工具

然后我们继续放入矩形B, 可以先右再下, 然后基于矩形B又有两块空白空间.

手把手帶你使用node開發一款圖集打包工具

依次类推, 我们就可以将合适的矩形全部放入.

举个例子

把左边的散装矩形放入右边的矩形框中, 可以得到:

手把手帶你使用node開發一款圖集打包工具

可以看到, 我们节省了很多空间, 矩形排列紧凑.

如果用代码实现, 是怎么样的呢?

/** 
 * 确定宽高 w h
 * 空白区域先放一个, 剩下的寻找右边和下边
 * 是否有满足右边的, 有则 放入 无则 继续遍历
 * 是否有满足下边的, 有则 放入 无则 继续遍历
 */
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;
            }
        }

    }

    /** 找到可以放入的节点 */
    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) {
        //已经使用过的 删除 
        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;
    }
}
登入後複製

使用递归, 代码量很少, 但是功能强大.

但是有一个问题, 如果超出定长定宽, 或者一个矩形装不完, 我们的算法是不会放入到大矩形中的.

这样子就有点不满足我们的图集打包思路了.

所以我们还需要将这个算法改进一下;

加入两个变量, 一个记录使用的总的区域, 一个记录未被装入的矩形.

//记录使用的总的区域
this.usedArea = { width: 0, height: 0 };
//记录未被装入的矩形
this.levelBlocks = [];
登入後複製

详细代码可以查看源码中的packing.

当然, 这里只是最简单的一种二维装箱算法

还有一种加强版的装箱算法, 我放在源码里了, 这里就不赘述了, 原理基本一致

现在, 我们已经可以将矩形合适的装箱了, 那怎么使用去处理成图集呢?

定义一个dealImgsPacking方法, 继续去处理我们的节点树.

这里用到了我们的配置项maxCount, 就是为了一张图集装不完, 多打出几张图集的作用.

然后我们打包出来的图集命名使用文件夹 + 当前是第几张的形式.

`${obj[&#39;name&#39;] + (count ? "-" + count : &#39;&#39;)}`
登入後複製

具体方法如下:

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[&#39;name&#39;] + (count ? "-" + count : &#39;&#39;)}`
            };
            newBlocks = packer1.levelBlocks;
            obj["assets"].push(sheets1);
            count++;
        }
    }
    for (let item in obj) {
        if (obj[item].hasOwnProperty("assets")) {
            this.dealImgsPacking(obj[item]);
        }
    }
}
登入後複製

通过这个方法我们改造了之前的节点树;

将之前节点树中的assest变为了一个数组, 每个数组元素代表一张图集信息.

结构如下:

  assets: [
    { 
        maxArea: { width: 180,height: 340 }, 
        atlas: [
            {
                id: &#39;assets/index/img-手把手帶你使用node開發一款圖集打包工具&#39;,
                width: 190,
                height: 187,
                x: 0,
                y: 0
            }
        ], 
        fileName: &#39;assets&#39; },
        ...
  ]
登入後複製

我们可以清晰的得到, 打包之后的图集, 最大宽高是maxArea, 每张图宽高位置信息是atlas,以及图集名称fileName.

接下来, 就是最后一步了, 绘制新的图片, 并输出图片文件.

注意
我们在使用打包算法的时候, 可以先进行一下基于图片大小的排序
这样以来打包出来的图集会留白更小

图集打包并输出

这里图集的绘制和输出均是使用了node-images的API;

遍历之前得到的节点树, 首先绘制一张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[&#39;fileName&#39;]}.png`);
登入後複製

最后对节点树递归调用, 绘制出所有的图集.

具体代码如下:

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[&#39;fileName&#39;]}.png`);
                count++;
            }
        })
    }

    for (let item in obj) {
        if (obj[item].hasOwnProperty("assets")) {
            this.drawImages(obj[item]);
        }
    }

}
登入後複製

这样子, 我们就大功告成了,

运行测试一下, 可以得到如下的图集:

手把手帶你使用node開發一款圖集打包工具

手把手帶你使用node開發一款圖集打包工具

效果还不错.

如何使用

安装

npm i sprites-pack-tool
登入後複製

使用

const MySpritePackTool = require("sprites-pack-tool");
const path = require("path");
/** 打包最多递归次数 */
const MAX_COUNT = 2;
//需要合成的图集的路径
const assetsPath = path.resolve(__dirname, "./assets");

/** 图集打包工具配置*/
const mySpritePackTool = new MySpritePackTool({
    //一个文件夹图片过多或者过长 递归最大次数
    maxCount: MAX_COUNT,
    //需要打包图集的文件路径
    assetsPath: assetsPath,
    //输出文件路径
    outPutPath: path.resolve(__dirname, "./res"),
    //一张图集打包最大size
    maxSize: { width: 2048,height: 2048}
});
/** 图集打包 */
mySpritePackTool.Pack2Sprite();
登入後複製

展望

当然, 这个工具只是初版, 后续还会继续优化并增加新的功能.

  • 算法可以继续优化, 现在留白也挺多.

  • 文件夹操作,可以优化. 比如写入图片可以每个文件夹下一张图集.

  • 增加更多配置项, 比如开启图片压缩

  • 增加json文件

  • ...

我大致看了下, 市场上有几款图集打包工具, 要么基于texturePacker, 要么基于imagemagick;

使用这俩应用开放的API也是可以打包图集的, 效果品质可能更好.

但是你还得额外安装一个应用.

同样的, 你也可以使用webpack的一些loader或者plugins. 目的都是打包图集.

本文介紹的工具比較輕量, 但是可堪一用, 同時開箱即可.

後文

有一陣子沒有寫文章了, 這個週末偶然想寫一個這樣的工具, 就付諸實踐了, 結果還不錯.

如果你還有更好的方式, 可以留下你的評論, 感激不盡~.

歡迎大家拍磚指正, 筆者功力尚淺, 如有不當之處請斧正.

#源

你可以在github查看:https://github. com/xdq1553/MySpritesPackTool

你可以使用npm安裝:https://www.npmjs.com/package/sprites-pack-tool

#參考

#node-images:https://github.com/zhangyuanwei/node-images

spritesheet:https://github.com/krzysztof-o/spritesheet.js

#文章粗淺, 望諸位不吝您的評論與按讚~

#原文網址:https://juejin.cn/post/7035809483666227230

作者:起小就些熊

更多node相關知識,請造訪:nodejs 教學! !

以上是手把手帶你使用node開發一款圖集打包工具的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.聊天命令以及如何使用它們
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

nvm 怎麼刪除node nvm 怎麼刪除node Dec 29, 2022 am 10:07 AM

nvm刪除node的方法:1、下載「nvm-setup.zip」並將其安裝在C碟;2、設定環境變量,並透過「nvm -v」指令查看版本號;3、使用「nvm install」指令安裝node;4、透過「nvm uninstall」指令刪除已安裝的node即可。

node專案中如何使用express來處理檔案的上傳 node專案中如何使用express來處理檔案的上傳 Mar 28, 2023 pm 07:28 PM

怎麼處理文件上傳?以下這篇文章為大家介紹一下node專案中如何使用express來處理文件的上傳,希望對大家有幫助!

Node服務怎麼進行Docker鏡像化?極致優化詳解 Node服務怎麼進行Docker鏡像化?極致優化詳解 Oct 19, 2022 pm 07:38 PM

這段時間在開發一個騰訊文檔全品類通用的HTML 動態服務,為了方便各品類接入的生成與部署,也順應上雲的趨勢,考慮使用Docker 的方式來固定服務內容,統一進行製品版本的管理。這篇文章就將我在服務 Docker 化的過程中累積起來的優化經驗分享出來,供大家參考。

深入淺析Node的進程管理工具'pm2” 深入淺析Node的進程管理工具'pm2” Apr 03, 2023 pm 06:02 PM

這篇文章跟大家分享Node的進程管理工具“pm2”,聊聊為什麼需要pm2、安裝和使用pm2的方法,希望對大家有幫助!

Pi Node教學:什麼是Pi節點?如何安裝和設定Pi Node? Pi Node教學:什麼是Pi節點?如何安裝和設定Pi Node? Mar 05, 2025 pm 05:57 PM

PiNetwork節點詳解及安裝指南本文將詳細介紹PiNetwork生態系統中的關鍵角色——Pi節點,並提供安裝和配置的完整步驟。 Pi節點在PiNetwork區塊鏈測試網推出後,成為眾多先鋒積極參與測試的重要環節,為即將到來的主網發布做準備。如果您還不了解PiNetwork,請參考Pi幣是什麼?上市價格多少? Pi用途、挖礦及安全性分析。什麼是PiNetwork? PiNetwork項目始於2019年,擁有其專屬加密貨幣Pi幣。該項目旨在創建一個人人可參與

聊聊用pkg將Node.js專案打包為執行檔的方法 聊聊用pkg將Node.js專案打包為執行檔的方法 Dec 02, 2022 pm 09:06 PM

如何用pkg打包nodejs可執行檔?以下這篇文章跟大家介紹一下使用pkg將Node專案打包為執行檔的方法,希望對大家有幫助!

npm node gyp失敗怎麼辦 npm node gyp失敗怎麼辦 Dec 29, 2022 pm 02:42 PM

npm node gyp失敗是因為“node-gyp.js”跟“Node.js”版本不匹配,其解決辦法:1、透過“npm cache clean -f”清除node快取;2、透過“npm install -g n”安裝n模組;3、透過「n v12.21.0」指令安裝「node v12.21.0」版本即可。

使用Angular和Node進行基於令牌的身份驗證 使用Angular和Node進行基於令牌的身份驗證 Sep 01, 2023 pm 02:01 PM

身份驗證是任何網路應用程式中最重要的部分之一。本教程討論基於令牌的身份驗證系統以及它們與傳統登入系統的差異。在本教程結束時,您將看到一個用Angular和Node.js編寫的完整工作演示。傳統身份驗證系統在繼續基於令牌的身份驗證系統之前,讓我們先來看看傳統的身份驗證系統。使用者在登入表單中提供使用者名稱和密碼,然後點擊登入。發出請求後,透過查詢資料庫在後端驗證使用者。如果請求有效,則使用從資料庫中獲取的使用者資訊建立會話,然後在回應頭中傳回會話訊息,以便將會話ID儲存在瀏覽器中。提供用於存取應用程式中受

See all articles