当前位置: 首页 > article >正文

分享一个精灵图生成和拆分的实现

概述

精灵图(Sprite)是一种将多个小图像合并到单个图像文件中的技术,广泛应用于网页开发、游戏开发和UI设计中。在MapboxGL中,跟之配套的还有一个json文件用来记录图标的大小和位置。本文分享基于Node和sharp库实现精灵图的合并与拆分。

实现效果

拆分后的图片

合并后的精灵图

合并后精灵图的json文件
运行时的截图

代码实现

将拆分和合并封装成了一个方法,实现代码如下:

const sharp = require("sharp");
const fs = require("fs").promises;
const path = require("path");// 二叉树节点类
class Node {constructor(x, y, width, height) {this.x = x;this.y = y;this.width = width;this.height = height;this.used = false;this.right = null;this.down = null;}// 查找可以放置图片的节点find(width, height) {// 如果当前节点已被使用,在子节点中查找if (this.used) {return this.right?.find(width, height) || this.down?.find(width, height);}// 检查图片是否适合当前节点if (width <= this.width && height <= this.height) {return this;}return null;}// 分割节点split(width, height) {this.used = true;// 创建右侧节点this.right = new Node(this.x + width, this.y, this.width - width, height);// 创建底部节点this.down = new Node(this.x,this.y + height,this.width,this.height - height);return this;}
}class SpriteManager {constructor() {this.metadata = {sprites: {},width: 0,height: 0,};}/*** 将多个图片合并成一个精灵图* @param {string} inputDir 输入图片目录* @param {string} outputImage 输出精灵图路径* @param {string} outputJson 输出JSON文件路径*/async createSprite(inputDir, outputImage, outputJson) {const start = Date.now();try {// 读取目录下所有图片const files = await fs.readdir(inputDir);const images = files.filter((file) => /\.(png|jpg|jpeg)$/i.test(file));// 并行处理图片元数据const imageMetadata = await Promise.all(images.map(async (file) => {const imagePath = path.join(inputDir, file);const image = sharp(imagePath);const metadata = await image.metadata();const name = file.split(".")[0];// 预处理图片 - 统一转换为PNG格式并缓存const buffer = await image.png().toBuffer();return {name,width: metadata.width,height: metadata.height,buffer,};}));// 按面积从大到小排序imageMetadata.sort((a, b) => b.width * b.height - a.width * a.height);// 计算初始画布大小const totalArea = imageMetadata.reduce((sum, img) => sum + img.width * img.height,0);const estimatedSide = Math.ceil(Math.sqrt(totalArea * 1.1));// 创建根节点let root = new Node(0, 0, estimatedSide, estimatedSide);let maxWidth = 0;let maxHeight = 0;// 使用二叉树算法放置图片for (const img of imageMetadata) {// 查找合适的节点let node = root.find(img.width, img.height);// 如果找不到合适的节点,扩展画布if (!node) {// 创建新的更大的根节点const newRoot = new Node(0, 0, root.width * 1.5, root.height * 1.5);newRoot.used = true;newRoot.down = root;root = newRoot;node = root.find(img.width, img.height);}// 分割节点并记录位置if (node) {const position = node.split(img.width, img.height);this.metadata.sprites[img.name] = {x: position.x,y: position.y,width: img.width,height: img.height,};// 更新最大尺寸maxWidth = Math.max(maxWidth, position.x + img.width);maxHeight = Math.max(maxHeight, position.y + img.height);}}// 更新最终画布尺寸this.metadata.width = maxWidth;this.metadata.height = maxHeight;// 创建并合成图片const composite = sharp({create: {width: this.metadata.width,height: this.metadata.height,channels: 4,background: { r: 0, g: 0, b: 0, alpha: 0 },},});// 一次性合成所有图片const compositeOperations = imageMetadata.map((img) => ({input: img.buffer,left: this.metadata.sprites[img.name].x,top: this.metadata.sprites[img.name].y,}));await composite.composite(compositeOperations).png({ quality: 100 }).toFile(outputImage);// 保存JSON文件await fs.writeFile(outputJson, JSON.stringify(this.metadata.sprites));const end = Date.now();console.log("精灵图创建完成, 耗时" + (end - start) / 1000 + "s");} catch (error) {throw new Error(`创建精灵图失败: ${error.message}`);}}/*** 从精灵图中提取单个图片* @param {string} spriteImage 精灵图路径* @param {string} jsonFile JSON文件路径* @param {string} outputDir 输出目录*/async extractSprites(spriteImage, jsonFile, outputDir) {// 读取JSON文件const metadata = JSON.parse(await fs.readFile(jsonFile, "utf-8"));// 确保输出目录存在await fs.mkdir(outputDir, { recursive: true });// 提取每个图片for (const [filename, info] of Object.entries(metadata)) {const iconPath = path.join(outputDir, filename + ".png");sharp(spriteImage).extract({left: info.x,top: info.y,width: info.width,height: info.height,}) // 裁剪区域.toFile(iconPath).then((_info) => {console.log("Image cropped successfully:", _info);}).catch((error) => {console.log(iconPath, info);console.error("Error processing image:", error);});}}
}module.exports = SpriteManager;

调用代码如下:

// 引用
const SpriteManager = require("./sprite/sprite");const spriteManager = new SpriteManager();// 创建精灵图
spriteManager.createSprite("./sprite/icons", // 输入图片目录"./sprite/sprite.png", // 输出精灵图路径"./sprite/sprite.json" // 输出JSON文件路径
);
// 拆分精灵图
// spriteManager.extractSprites(
//   "./sprite/sprite.png", // 精灵图路径
//   "./sprite/sprite.json", // JSON文件路径
//   "./sprite/icons" // 输出目录
// );

相关文章:

分享一个精灵图生成和拆分的实现

概述 精灵图&#xff08;Sprite&#xff09;是一种将多个小图像合并到单个图像文件中的技术&#xff0c;广泛应用于网页开发、游戏开发和UI设计中。在MapboxGL中&#xff0c;跟之配套的还有一个json文件用来记录图标的大小和位置。本文分享基于Node和sharp库实现精灵图的合并与…...

AI日报 - 2025年3月21日

&#x1f31f; 今日概览&#xff08;60秒速览&#xff09; ▎&#x1f916; AGI突破 | OpenAI成立安全委员会&#xff0c;加速AGI治理框架构建 ▎&#x1f4bc; 商业动向 | 微软发布医疗大模型DAX Copilot 3.0&#xff0c;覆盖全球临床场景 ▎&#x1f4dc; 政策追踪 | 中国发布…...

MongoDB 配合python使用的入门教程

MongoDB 入门教程 1. 安装 MongoDB 首先&#xff0c;你需要在你的机器上安装MongoDB。你可以从 MongoDB官网 下载并安装 Community 版本。安装完成后&#xff0c;启动MongoDB服务。 # 在Linux/Mac上启动MongoDB mongod# 在Windows上&#xff0c;你可以通过Windows服务启动Mo…...

函数:形参和实参

在函数的使用过程中分为实参和形参&#xff0c;实参是主函数实际调用的值而形参则是给实参调用的值&#xff0c;如果函数没被调用则函式不会向内存申请空间&#xff0c;先用一段代码演示 形参&#xff1a; int test(int x ,int y ) {int z 0;z x y;return z; } 为何会叫做…...

【C#知识点详解】ExcelDataReader介绍

今天来给大家介绍一下ExcelDataReader&#xff0c;ExcelDataReader是一个轻量级的可快速读取Excel文件中数据的工具。话不多说直接开始。 ExcelDataReader简介 ExcelDataReader支持.xlsx、.xlsb、.xls、.csv格式文件的读取&#xff0c;版本基本在2007及以上版本&#xff0c;支…...

Vala编程语言教程-控制结构

控制结构 while (a > b) { a--; } 会重复递减a&#xff0c;每次迭代前检查a是否大于b。 do { a--; } while (a > b); 会重复递减a&#xff0c;每次迭代后检查a是否大于b。 for (int a 0; a < 10; a) { stdout.printf("%d\n", a); } 会先将a初始化为0…...

《视觉SLAM十四讲》ch13 设计SLAM系统 相机轨迹实现

前言 相信大家在slam学习中&#xff0c;一定会遇到slam系统的性能评估问题。虽然有EVO这样的开源评估工具&#xff0c;我们也需要自己了解系统生成的trajectory.txt的含义&#xff0c;方便我们更好的理解相机的运行跟踪过程。 项目配置如下&#xff1a; 数据解读&#xff1a; …...

服务的拆分数据的迁移

参考&#xff1a; 数据迁移调研...

在类Unix终端中如何实现快速进入新建目录

&#x1f6aa; 前言 相信喜欢使用终端工作的小伙伴或多或少会被一个小地方给膈应&#xff0c;那就是每次想要新建一个文件夹并且进入之&#xff0c;那么就需要两条指令&#xff1a;mkdir DIR和cd DIR&#xff0c;有些人可能要杠了&#xff0c;我一条指令也能&#xff0c;mkdir…...

01分数规划,二分法,题目练习

一、01分数规划 1.1 01分数规划 01分数规划用来求一个分式的极值。模型如下&#xff1a; 给出 a i a_i ai​ 和 b i b_i bi​&#xff0c;求一组 w i ∈ { 0 , 1 } w_i \in \{ 0, 1 \} wi​∈{0,1}最小化或最大化 ∑ i 1 n a i w i ∑ i 1 n b i w i \frac{\sum_{i1…...

TG电报群管理机器人定制开发的重要性

在Telegram&#xff08;电报&#xff09;用户突破20亿、中文社群规模持续扩张的背景下&#xff0c;定制化群管理机器人的开发已成为社群运营的战略刚需。这种技术工具不仅解决了海量用户管理的效率难题&#xff0c;更通过智能化功能重构了数字社群的治理范式。本文从管理效能、…...

VNA操作使用学习-01 界面说明

以我手里面的liteVNA为例。也可以参考其他的nanoVNA的操作说明。我先了解一下具体的菜单意思。 今天我想做一个天调&#xff0c;居然发现我连一颗基本的50欧姆插件电阻和50欧姆的smt电阻的幅频特性都没有去测试过&#xff0c;那买来这个nva有什么用途呢&#xff0c;束之高阁求…...

mysql-DELETE、DROP 和 TRUNCATE区别

在MySQL中&#xff0c;DELETE、DROP 和 TRUNCATE 是用于不同目的的SQL命令&#xff0c;它们各自执行不同的数据库操作。以下是它们的区别和适用场景&#xff1a; DELETE 用途&#xff1a;用于从表中删除满足特定条件的行。语法示例&#xff1a;DELETE FROM table_name WHERE …...

耘想Docker版Linux NAS的安装说明

耘想LinNAS&#xff08;Linux NAS&#xff09;可以通过Docker部署&#xff0c;支持x86和arm64两种硬件架构。下面讲解LinNAS的部署过程。 1. 安装Docker CentOS系统&#xff1a;yum install docker –y Ubuntu系统&#xff1a;apt install docker.io –y 2. 下载LinNas镜像…...

OpenCV图像拼接(4)图像拼接模块的一个匹配器类cv::detail::BestOf2NearestRangeMatcher

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 cv::detail::BestOf2NearestRangeMatcher 是 OpenCV 库中用于图像拼接模块的一个匹配器类&#xff0c;专门用于寻找两幅图像之间的最佳特征点匹配…...

k8s 配置imagePullSecrets仓库认证

在 Kubernetes (K8s) 中&#xff0c;imagePullSecrets 允许 Pod 访问私有镜像仓库&#xff0c;例如 Docker Hub、Harbor、阿里云镜像仓库、腾讯云 TCR 或自建的 registry.flow.cn。以下是完整的 imagePullSecrets 配置步骤&#xff1a; 步骤 1&#xff1a;创建 imagePullSecret…...

不用 Tomcat?SpringBoot 项目用啥代替?

在SpringBoot框架中&#xff0c;我们使用最多的是Tomcat&#xff0c;这是SpringBoot默认的容器技术&#xff0c;而且是内嵌式的Tomcat。 同时&#xff0c;SpringBoot也支持Undertow容器&#xff0c;我们可以很方便的用Undertow替换Tomcat&#xff0c;而Undertow的性能和内存使…...

Zabbix安装(保姆级教程)

Zabbix 是一款开源的企业级监控解决方案&#xff0c;能够监控网络的多个参数以及服务器、虚拟机、应用程序、服务、数据库、网站和云的健康状况和完整性。它提供了灵活的通知机制&#xff0c;允许用户为几乎任何事件配置基于电子邮件的告警&#xff0c;从而能够快速响应服务器问…...

鸿蒙开发真机调试:无线调试和USB调试

前言 在鸿蒙开发的旅程中&#xff0c;真机调试堪称至关重要的环节&#xff0c;其意义不容小觑。虽说模拟器能够为我们提供初步的测试环境&#xff0c;方便我们在开发过程中快速预览应用的基本效果&#xff0c;但它与真机环境相比&#xff0c;仍存在诸多差异。就好比在模拟器中…...

深度学习中的“刹车”:正则化如何防止模型“超速”

深度学习中的“刹车”&#xff1a;正则化如何防止模型“超速” 大家好&#xff01;今天我们来聊聊深度学习中的一个重要概念——正则化。 什么是过拟合&#xff1f; 想象一下&#xff0c;你正在教一个孩子认字。你给他看很多猫的图片&#xff0c;他都能正确识别。但是&#…...

在C语言基础上学Java【Java】【一】

众所周知&#xff0c;Java是C风格的语言&#xff0c;对于学过C语言的人学Java可以快速适应。 废话不多说&#xff0c;直接边看代码边学。 数据类型&#xff0c;输入和输出 import java.util.Scanner;//为了使用Scanner public class a1 {//a1是类名&#xff0c;就是文件名&am…...

工厂函数详解:概念、目的与作用

一、什么是工厂函数&#xff1f; 工厂函数&#xff08;Factory Function&#xff09;是一种设计模式&#xff0c;其核心是通过一个函数来 创建并返回对象&#xff0c;而不是直接使用 new 或构造函数实例化对象。它封装了对象的创建过程&#xff0c;使代码更灵活、可维护。 二、…...

FastAPI WebSocket 无法获取真实 IP 错误记录

FastAPI WebSocket 无法获取真实 IP 错误记录 问题描述 在使用 FastAPI WebSocket 服务时&#xff0c;发现无法获取设备的真实 Remote IP&#xff0c;所有连接均显示为内网地址 10.x.x.1。以下是完整的排查过程及解决方案。 排查步骤 1. 基础信息检查 • 现象复现&#xff1…...

Android自动化测试终极指南:从单元到性能全覆盖!

在快节奏的移动开发中,应用的稳定性与用户体验直接决定成败。手动测试效率低下,自动化测试成为提升质量的核心手段。本文将手把手带你掌握Android项目中的六大测试工具,涵盖单元测试、UI测试、性能测试、端到端测试等核心场景,助你构建坚如磐石的应用! 1. 单元测试:JUni…...

Python简单爬虫实践案例

学习目标 能够知道Web开发流程 能够掌握FastAPI实现访问多个指定网页 知道通过requests模块爬取图片 知道通过requests模块爬取GDP数据 能够用pyecharts实现饼图 能够知道logging日志的使用 一、基于FastAPI之Web站点开发 1、基于FastAPI搭建Web服务器 # 导入FastAPI模…...

Thunderbolt(雷电接口)详解

一、Thunderbolt的定义与核心特性 Thunderbolt 是由 Intel 和 Apple 联合开发的高速接口标准&#xff0c;结合PCIe和DisplayPort协议&#xff0c;支持 数据传输、视频输出、电源供应及设备级联。其核心特性包括&#xff1a; 超高带宽&#xff1a;Thunderbolt 4支持 40Gbps全双…...

基于springboot的房产销售系统(016)

摘 要 随着科学技术的飞速发展&#xff0c;各行各业都在努力与现代先进技术接轨&#xff0c;通过科技手段提高自身的优势&#xff1b;对于房产销售系统当然也不能排除在外&#xff0c;随着网络技术的不断成熟&#xff0c;带动了房产销售系统&#xff0c;它彻底改变了过去传统的…...

云盘搭建笔记

报错问题&#xff1a; No input file specified. 伪静态 location / {if (!-e $request_filename) { rewrite ^(.*)$ /index.php/$1 last;break;} } location / { if (!-e $request_filename) { rewrite ^(.*)$ /index.php/$1 last; break; } } 设…...

《从深海到卫浴:Relax Max如何用军工科技重塑生活仪式》​

《从深海到卫浴&#xff1a;Relax Max如何用军工科技重塑生活仪式》​ 当瑞士联邦理工学院的一纸专利授权书揭开帷幕&#xff0c;卫浴行业终于意识到&#xff1a;Relax Max的「军工科技民用化」绝非营销噱头。这支由前潜艇工程师和航天材料学家组成的团队&#xff0c;将核潜艇…...

【vulhub/wordpress靶场】------获取webshell

1.进入靶场环境&#xff1a; 输入&#xff1a;cd / vulhub / wordpress / pwnscriptum 修改版本号&#xff1a; vim docker-compose.yml version: 3 保存退出 开启靶场环境&#xff1a; docker - compose up - d 开启成功&#xff0c;docker ps查看端口 靶场环境80…...