news 2026/10/2 8:15:25

battery 项目图标编译器实战:将电池百分比 SVG 批量渲染为 Electron 托盘 PNG

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
battery 项目图标编译器实战:将电池百分比 SVG 批量渲染为 Electron 托盘 PNG
  • 桌面应用
  • CLI

【免费下载链接】battery

CLI/GUI for managing the battery charging status for Apple silicon (M1, M2, M3) Macs

项目地址:https://gitcode.com/GitHub_Trending/ba/battery
点击查看免费下载

本指南以仓库 assets 目录下的“Logo compiler”工具为主体,讲解其存在原因(Electron 托盘不支持直接使用 SVG 图片)、一行命令的运行方式(npm start)、以及配置入口 assets/modules/compile-images.js 中百分比步长、DPI 倍率、active/inactive 透明度等核心参数。读完本文,你将掌握该图标编译器从 SVG 模板生成、批量栅格化 PNG 到被 app/modules/theme.js 消费的完整链路,并能在修改“电量百分比显示粒度”这类需求时正确地重新生成全部图标。

背景:为什么 Electron 托盘图标需要一个“图标编译器”

原文档开宗明义:“Because Electron does not support SCG images”(原文此处为笔误,实指 SVG)。Electron 的nativeImageAPI 并不支持直接加载 SVG,而本项目(一款用于 Apple silicon Mac 电池充电限制的托盘应用)需要在菜单栏托盘上按电量百分比动态显示不同的电池图标,因此必须先把矢量图源批量渲染成 PNG。

为了让托盘图标能覆盖“从 0% 到 100%”的每一个展示档位,同时兼顾 macOS 菜单栏在不同屏幕 DPI 下的清晰度,仓库在 assets 目录里维护了一个独立的小工具项目(asset-compiler):

  • 它读取“百分比”作为输入,调用 SVG 模板函数生成图标;
  • 通过 @resvg/resvg-js(Rust 实现的 SVG 渲染库)将 SVG 栅格化为 PNG;
  • 一次性输出数百张命名规范的图标文件,供 Electron 应用直接引用。

这套流程在 app/README.md 的开发者说明中也被印证:图标由脚本自动生成,应用侧无需再处理任何 SVG。

快速上手:安装依赖与运行

工具本身的工程信息定义在 assets/package.json 中,运行命令极其简单:

cd assets npm install # 安装 @resvg/resvg-js 等依赖 npm start # 等价于 node index.js

npm start对应 assets/package.json 中的"start": "node index.js",入口文件 assets/index.js 只有寥寥几行——引入 assets/modules/compile-images.js 并调用其导出的render_and_write_template_files():

const render_and_write_template_files = require( './modules/compile-images' ) render_and_write_template_files()

整个渲染过程是同步等待完成的,命令执行完毕后,所有 PNG 会写入 app/assets 目录(源码中export_directory = ${ __dirname }/../../app/assets)。原文档特别强调:这个工具只在“图标百分比支持范围发生变化”时才需要重新运行,日常开发无需反复执行。

核心配置解析:百分比、倍率与命名规范

配置的真正“大脑”是 assets/modules/compile-images.js,它集中了全部渲染参数:

配置项源码位置取值说明
百分比步长compile-images.js5从 0 到 100 每 5% 生成一档,共 21 个百分比
DPI 倍率集合compile-images.js['1.25','1.33','1.4','1.5','1.8','2','2.5','3','4','5']对应 ElectronnativeImage支持的高分辨率表示(High-resolution image),注释中说明了依据
图标状态compile-images.jsactive/inactive每种百分比都同时生成激活态与未激活态
透明度compile-images.js 与 L59active 用bake_logo(percentage, 1),inactive 用bake_logo(percentage, .5)与 app/README.md 中“100% vs 50% for active inactive”的说明一致
输出目录compile-images.js../../app/assets相对assets/modules,即仓库根下的 app/assets

渲染组合逻辑为:每个百分比生成 22 张图 = 1 张基础图(1x,无倍率后缀)+ 10 张@Nx倍率图,且 active/inactive 各一套。21 个百分比 × 22 张 =共 462 张 PNG,全部通过Promise.all并发栅格化写入磁盘(见 compile-images.js)。

文件名遵循统一模板(见 compile-images.js):

battery-{active|inactive}-{percentage}-Template{@Nx}.png

例如:

  • battery-active-80-Template.png(1x 基础图,18×18)
  • battery-active-80-Template@2x.png(2 倍图,36×36)
  • battery-inactive-50-Template@1.5x.png

文件名的Template后缀并非装饰:macOS 会把名称带Template的图片当作模板图像处理,自动适配菜单栏的深色/浅色主题,这正是本项目图标能随系统外观切换的原因之一。

栅格化核心函数render_and_write_png使用 Resvg 的fitTo.zoom模式按倍率放大输出:

const resvg = new Resvg( svg_string, { fitTo: { mode: 'zoom', value: Number( size ) } } ) const png_data = resvg.render() const png_buffer = png_data.asPng() return fs.writeFile( `${ export_directory }/${ filename }.png`, png_buffer )

因此基础图 18×18 的@2x版本即为 36×36、@5x即为 90×90,这与 app/assets 中实际存在的各倍率图片尺寸完全吻合。

SVG 模板的生成原理

SVG 模板由 assets/modules/logo.js 的bake_logo(percentage, opacity)函数生成,它是整个图标系统的“画布”。核心几何逻辑如下:

const max_height = 12 const min_y_position = 3.5 const percentage_as_decimal = percentage / 100 const current_height = max_height * percentage_as_decimal const current_y_position = min_y_position + ( max_height - current_height )
  • 画布为viewBox="0 0 18 18",即 18×18 的逻辑坐标系;
  • 电池内部的电量填充矩形(<rect id="content">)宽度固定为 7.25、圆角半径 1.5;
  • 填充高度current_height随百分比线性变化:0% 时高度为 0(只有空电池轮廓),100% 时高度为 12(充满);
  • 填充矩形纵坐标current_y_position随高度反向移动:电量越低越靠下,电量越高越靠上,从而让电量条始终“从底部开始生长”。

<svg>根节点上施加的opacity参数则直接控制了 active(1.0)与 inactive(0.5)两种状态的视觉差异,这也是 app/README.md 所述“图标内部电量填充部分在激活/未激活时透明度不同”的实现来源。电池外壳则由一段预置的path路径绘制,配合两重matrix变换缩放到 18×18 画布内。

生成的图标如何被应用消费

编译产物最终被 Electron 应用侧读取。关键代码在 app/modules/theme.js 的get_logo_template(percent, active):

const percentage_increment_to_render = 5 const display_percentage = Math.floor( percent / percentage_increment_to_render ) * percentage_increment_to_render const image_path = path.join( asset_path, `/battery-${ active ? 'active' : 'inactive' }-${ display_percentage }-Template.png` ) return nativeImage.createFromPath( image_path )

这里有两个值得注意的联动点:

  1. 百分比取整对齐:display_percentage会把任意实时电量(如 83%)向下取整到 5 的倍数(80%)。由于编译器恰好以 5 为步长生成图标,取整后总能命中已存在的文件;反过来,若想提高展示精度(例如改为 1% 一档),就必须同步修改compile-images.js中的percentage_increment_to_render并重新运行编译器。
  2. 资源路径切换:asset_path = app.isPackaged ? resourcesPath : './assets'——开发态直接读 app/assets,打包后则读取资源目录。

消费端 app/modules/interface.js 在生成托盘菜单时调用tray.setImage( get_logo_template( percentage, limiter_on ) )(见 interface.js),并在启用/停用充电限制时分别用active/inactive强制刷新图标(见 interface.js 的enable_limiter/disable_limiter)。整套流程形成了“实时电量 → 取整到 5 的倍数 → 查表选 PNG → 设置托盘图像”的完整数据链路。

打包与资源路径

打包配置位于 app/package.json 的build.extraFiles:

"extraFiles": { "from": "./assets", "to": "resources", "filter": [ "**/*" ] }

编译生成的 app/assets 下全部 PNG 会被原样打入应用包内的resources目录,运行时通过resourcesPath读取——这解释了为什么这 462 张图片必须作为静态资源随包发布,而不是在运行时动态生成。

何时需要重新运行编译器

原文档给出的原则非常明确:“This only needs to be done if icon percentage support changes.”结合源码可以归纳出需要重新执行npm start的典型场景:

  • 修改了百分比步长(如把 5% 一档改为 1% 一档),需要同时改 compile-images.js 与 theme.js 中的percentage_increment_to_render;
  • 调整了图标倍率集合(增删 DPI 档位,见 compile-images.js);
  • 修改了 logo.js 中的几何参数(填充条尺寸、坐标、透明度策略等),需要重新栅格化全部档位;
  • 调整了图标命名规则(前缀、后缀),此时还需同步更新 theme.js 中的文件名拼接逻辑。

日常使用、只改应用业务逻辑(如菜单文案、刷新频率)时,均无需触碰编译流程。

注意事项小结

  • 原文档中配置文件名写作comile-images.js,为笔误,实际文件为 assets/modules/compile-images.js。
  • app/README.md 提到图标可通过在app/assets下运行node index.js生成;而编译器工程实际位于仓库根的 assets 目录,产物输出到 app/assets,运行入口以npm start为准。
  • 渲染依赖仅一个运行时包@resvg/resvg-js(^2.4.1),整个工具非常轻量;npm test目前仅输出占位提示(见 assets/package.json),功能验证可直接检查输出目录中的 PNG 数量与尺寸。
  • 桌面应用
  • CLI

【免费下载链接】battery

CLI/GUI for managing the battery charging status for Apple silicon (M1, M2, M3) Macs

项目地址:https://gitcode.com/GitHub_Trending/ba/battery
点击查看免费下载
上一篇:抖音无水印下载终极指南:douyin-downloader批量下载神器完整教程
下一篇:解析 Symfony Console 的 RST 选项描述:以 `input_option_2.rst` 为例读懂 `VALUE_OPTIONAL` 与描述器输出规范

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 8:14:59

编译原理高频错题解析:FIRST/FOLLOW集、NFA确定化与LL(1)分析表避坑指南

简介&#xff1a;本资源是南京邮电大学《编译原理》课程配套的习题解答汇编&#xff0c;面向计算机科学与技术、软件工程等专业本科生及考研复习者&#xff0c;聚焦编译系统核心概念的理解与解题训练。内容覆盖翻译程序分类&#xff08;编译、汇编、解释&#xff09;、编译程序…

作者头像 李华
网站建设 2026/10/2 8:12:13

C/C++内存管理+模板初阶

目录 一. C/C内存分布 二. C语言中动态内存管理方式 三. C内存管理方式 3.1 new/delete 操作内置类型 3.2 new/delete 操作自定义类型 四. operator new与operator delete函数 五. new和delete的实现原理 5.1 内置类型 5.2 自定义类型 六. 定位new表达式(placement-n…

作者头像 李华
网站建设 2026/10/2 8:09:49

Agent循环的隐形代价:Strands Harness SDK如何把生产级封装成一行代码

做 AI 应用这些年&#xff0c;我越来越确认一个反直觉的结论&#xff1a;Agent 项目里最贵的东西不是模型 token&#xff0c;而是那套没多少人爱写、又不得不写的 Agent 循环。过去大半年&#xff0c;我前后手写过五个循环&#xff0c;每一个都从“无非是个 while 加几次模型调…

作者头像 李华