搜尋
首頁web前端js教程手把手帶你使用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刪除
node、nvm与npm有什么区别node、nvm与npm有什么区别Jul 04, 2022 pm 04:24 PM

node、nvm与npm的区别:1、nodejs是项目开发时所需要的代码库,nvm是nodejs版本管理工具,npm是nodejs包管理工具;2、nodejs能够使得javascript能够脱离浏览器运行,nvm能够管理nodejs和npm的版本,npm能够管理nodejs的第三方插件。

Vercel是什么?怎么部署Node服务?Vercel是什么?怎么部署Node服务?May 07, 2022 pm 09:34 PM

Vercel是什么?本篇文章带大家了解一下Vercel,并介绍一下在Vercel中部署 Node 服务的方法,希望对大家有所帮助!

node爬取数据实例:聊聊怎么抓取小说章节node爬取数据实例:聊聊怎么抓取小说章节May 02, 2022 am 10:00 AM

node怎么爬取数据?下面本篇文章给大家分享一个node爬虫实例,聊聊利用node抓取小说章节的方法,希望对大家有所帮助!

node导出模块有哪两种方式node导出模块有哪两种方式Apr 22, 2022 pm 02:57 PM

node导出模块的两种方式:1、利用exports,该方法可以通过添加属性的方式导出,并且可以导出多个成员;2、利用“module.exports”,该方法可以直接通过为“module.exports”赋值的方式导出模块,只能导出单个成员。

安装node时会自动安装npm吗安装node时会自动安装npm吗Apr 27, 2022 pm 03:51 PM

安装node时会自动安装npm;npm是nodejs平台默认的包管理工具,新版本的nodejs已经集成了npm,所以npm会随同nodejs一起安装,安装完成后可以利用“npm -v”命令查看是否安装成功。

聊聊V8的内存管理与垃圾回收算法聊聊V8的内存管理与垃圾回收算法Apr 27, 2022 pm 08:44 PM

本篇文章带大家了解一下V8引擎的内存管理与垃圾回收算法,希望对大家有所帮助!

node中是否包含dom和bomnode中是否包含dom和bomJul 06, 2022 am 10:19 AM

node中没有包含dom和bom;bom是指浏览器对象模型,bom是指文档对象模型,而node中采用ecmascript进行编码,并且没有浏览器也没有文档,是JavaScript运行在后端的环境平台,因此node中没有包含dom和bom。

聊聊Node.js path模块中的常用工具函数聊聊Node.js path模块中的常用工具函数Jun 08, 2022 pm 05:37 PM

本篇文章带大家聊聊Node.js中的path模块,介绍一下path的常见使用场景、执行机制,以及常用工具函数,希望对大家有所帮助!

See all articles

熱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.能量晶體解釋及其做什麼(黃色晶體)
2 週前By尊渡假赌尊渡假赌尊渡假赌
倉庫:如何復興隊友
4 週前By尊渡假赌尊渡假赌尊渡假赌
Hello Kitty Island冒險:如何獲得巨型種子
4 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

DVWA

DVWA

Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )專業的PHP整合開發工具

SublimeText3 Mac版

SublimeText3 Mac版

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

MinGW - Minimalist GNU for Windows

MinGW - Minimalist GNU for Windows

這個專案正在遷移到osdn.net/projects/mingw的過程中,你可以繼續在那裡關注我們。 MinGW:GNU編譯器集合(GCC)的本機Windows移植版本,可自由分發的導入函式庫和用於建置本機Windows應用程式的頭檔;包括對MSVC執行時間的擴展,以支援C99功能。 MinGW的所有軟體都可以在64位元Windows平台上運作。

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

強大的PHP整合開發環境