前端图像处理【免费下载链接】piskelA simple web-based tool for Spriting and Pixel art.项目地址https://gitcode.com/gh_mirrors/pi/piskel点击查看免费下载Piskel 不仅是一款浏览器端的像素画/精灵动画编辑器其仓库内还内置了一个名为piskel-cli的命令行工具能够以无界面headless方式复用 Web 应用的完整导出逻辑。读完本文你将掌握piskel-cli的全部命令行参数--scale、--scaledWidth、--scaledHeight、--rows、--columns、--frame、--dataUri、--crop、--dest等的实际行为以及它“驱动 PhantomJS 加载 Piskel 应用 JS 包再调用内部导出控制器”的底层工作原理从而在脚本化工作流中稳定地完成精灵图sprite sheet导出。一、Piskel CLI 是什么piskel-cli是 Piskel 仓库对像素编辑应用的一次“命令行封装”它把 cli/README.md 中声明的核心能力概括为一句话——Wraps the Piskel pixel editing application to enable similar export options via the command line即让命令行获得与网页版设置面板中相近的导出选项。在 package.json 中可以看到它作为 npm bin 入口注册bin: { piskel-root: ./misc/scripts/piskel-root, piskel-cli: ./cli/index.js }也就是说安装 Piskel 包后piskel-cli会作为全局命令可用。仓库另外还提供了一个辅助命令piskel-root其实现非常简单见 misc/scripts/piskel-root// Writes the absolute path to the release build root to stdout var path require(path); process.stdout.write(path.resolve(__dirname, ../../dest/prod) \n);它只负责把生产构建输出目录dest/prod的绝对路径写到标准输出方便其他工具定位构建产物。而真正干活的 CLI 代码全部集中在cli/目录下的三个文件文件角色cli/index.jsNode 侧入口解析命令行参数、校验输入、查找应用 JS 包、启动 PhantomJS 子进程cli/piskel-export.jsPhantomJS 脚本创建空白页面、注入 Piskel 应用 JS、触发页面内导出逻辑cli/export-png.js页面内执行体调用 Piskel 内部控制器完成裁剪/缩放/渲染并回传画布尺寸二、安装与环境准备2.1 两种安装方式cli/README.md 给出两种安装方式方式一直接从上游 tarball 全局安装npm install -g https://github.com/piskelapp/piskel/tarball/master方式二克隆仓库后本地安装并npm linkgit clone https://github.com/piskelapp/piskel.git cd piskel npm install npm link第二种方式的好处是可以直接对源码做调试链接后piskel-cli命令会指向本地仓库中的 cli/index.js。2.2 关键前提必须先有生产构建产物这是使用 CLI 最容易被忽视的一点。cli/index.js 在启动子进程前会先在构建产物目录中寻找应用 JS 包// Get path to Piskels app js bundle let piskelAppJsDir path.resolve(__dirname /../dest/prod/js/); let minJsFiles fs.readdirSync(piskelAppJsDir).filter(filename filename.indexOf(min) -1); let piskelAppJsFileName minJsFiles[0]; let piskelAppJsPath (piskelAppJsFileName) ? path.join(piskelAppJsDir, piskelAppJsFileName) : ; if (!fs.existsSync(piskelAppJsPath)) { console.error(Piskels application JS file not found in: ${piskelAppJsDir}. Run prod build and try again.); return; }逻辑非常明确在dest/prod/js/目录中查找文件名包含min的 JS 文件如果找不到直接报错并提示Run prod build and try again。因此无论是方式一还是方式二运行前都必须先执行一次生产构建package.json 中的build脚本npm run build:sprites vite build node scripts/partials.js确保dest/prod目录及其中打包后的应用 JS 存在。package.json 中main: ./dest/prod/index.html、files: [dest/prod, ...]也印证了dest/prod是正式发布的构建输出根目录。2.3 运行依赖说明从 cli/index.js 可以看到 CLI 通过require(phantomjs)获取 PhantomJS 可执行文件路径const phantomjs require(phantomjs); const binPath phantomjs.path;而 package.json 的运行时dependencies中只声明了minimist用于参数解析。也就是说phantomjs这个 Node 包需要能在运行环境中被解析到否则入口脚本在require阶段就会失败这是当前仓库实现下的一个实际前提使用时请注意运行环境的依赖完整性。三、完整参数速查表cli/README.md 列出了 9 个示例命令结合 cli/index.js 中minimist的默认值配置与 cli/export-png.js 中的参数消费逻辑完整参数如下表参数默认值说明位置参数.piskel 文件路径必填不传或文件不存在时打印Path to a .piskel file is required/No such file: xxx并退出--scale n1整数缩放倍率对应应用内导出缩放zoom--scaledWidth n无按单帧目标宽度反推缩放比例zoom scaledWidth / piskel.getWidth()--scaledHeight n无按单帧目标高度反推缩放比例zoom scaledHeight / piskel.getHeight()优先级低于--scaledWidth--rows n无精灵图行数单行布局常用--rows 1--columns n无精灵图列数单列布局常用--columns 1--frame n无只导出指定帧索引从 0 开始0为第一帧--dataUrifalse额外生成一个包含 PNG>let args minimist(process.argv.slice(2), { default: { crop: false, dataUri: false, debug: false, scale: 1 }, });输出文件的确定逻辑在 cli/index.jsconst dest args.dest || path.basename(src, .piskel);即未传--dest时输出为当前工作目录下与.piskel文件同名的.png例如snow-monster.piskel→snow-monster.png。README 中的全部示例命令以下命令完整继承自 cli/README.md可直接复制使用# 以应用默认设置导出 .piskel 为 png 精灵图 piskel-cli snow-monster.piskel # 导出 5 倍缩放的精灵图 piskel-cli snow-monster.piskel --scale 5 # 按单帧目标宽度缩放 piskel-cli snow-monster.piskel --scaledWidth 435 # 按单帧目标高度缩放 piskel-cli snow-monster.piskel --scaledHeight 435 # 导出为单列精灵图 piskel-cli snow-monster.piskel --columns 1 # 导出为单行精灵图 piskel-cli snow-monster.piskel --rows 1 # 只导出某一帧0 表示第一帧 piskel-cli snow-monster.piskel --frame 3 # 额外导出一个包含>const options { dest: dest, zoom: args.scale, crop: !!args.crop, rows: args.rows, columns: args.columns, frame: args.frame, dataUri: !!args.dataUri, debug: args.debug, piskelAppJsPath: piskelAppJsPath, scaledWidth: args.scaledWidth, scaledHeight: args.scaledHeight }; const childArgs [ path.join(__dirname, piskel-export.js), piskelFile, JSON.stringify(options) ];注意.piskel文件内容JSON 字符串与optionsJSON 字符串都是作为命令行参数传给子脚本的。另外当--debug开启时会在子进程参数前插入远程调试开关cli/index.jsif (args.debug) { childArgs.unshift( --remote-debugger-port9035, --remote-debugger-autorunyes ); }这为排查 PhantomJS 页面内逻辑提供了入口——该参数未出现在 cli/README.md 中但源码支持。4.2 PhantomJS 脚本cli/piskel-export.js注入应用并反序列化cli/piskel-export.js 运行在 PhantomJS 环境内// Create page w/ canvas const page require(webpage).create(); page.content htmlbody/body/html; // Inject Piskel JS page.injectJs(options.piskelAppJsPath); // Run page logic page.evaluate(function (piskelFile, options, onPageEvaluate) { document.body.style.margin 0; // Deserialize piskel file and run exporters page evaluate task pskl.utils.serialization.Deserializer.deserialize(piskelFile, function (piskel) { onPageEvaluate(window, options, piskel); }); }, piskelFile, options, exporter.onPageEvaluate);要点通过page.injectJs把整个 Piskel 应用 JS 包注入空白页面页面中因此存在全局命名空间pskl包含utils.serialization、controller.piskel、tools.transform等文件内容交由应用自带的反序列化器pskl.utils.serialization.Deserializer.deserialize还原为piskel模型对象导出完成后页面通过callPhantom回传数据父函数page.onCallback中调用exporter.onPageExit写文件然后phantom.exit(0)退出。4.3 页面内执行体cli/export-png.js复用应用控制器cli/export-png.js 的onPageEvaluate是全部导出参数的真正消费方// Setup piskelController var piskelController new pskl.controller.piskel.PiskelController(piskel); pskl.app.piskelController piskelController; piskelController.init();它直接实例化了与 Web 版相同的应用主控制器pskl.controller.piskel.PiskelController并挂到pskl.app全局上这是多个内部工具类读取应用状态的约定入口。后续所有行为都建立在这一次“无 DOM 化”的初始化之上。裁剪--crop// Apply crop if enabled if (options.crop) { // Mock selection manager to avoid errors during crop pskl.app.selectionManager {}; // Setup crop tool var crop new pskl.tools.transform.Crop(); // Perform crop crop.applyTransformation(); // Get cropped piskel piskel piskelController.getPiskel(); }这里通过把pskl.app.selectionManagermock 成空对象使 Crop 工具 走“无选区”分支——用TransformUtils.getBoundaries(frames)计算所有帧、所有图层内容的外接矩形并整体裁剪而不是按用户选区裁剪。结合 Crop.js 的applyTool_可以看到它先用moveFramePixels平移各帧像素再用ResizeUtils.resizePiskelorigin: TOP-LEFT重设画布尺寸最后setPiskel替换当前项目。因此--crop的语义是“裁掉四周空白保留实际绘制内容”且作用于全部帧与图层。缩放--scale / --scaledWidth / --scaledHeight// Mock exportController to provide zoom value based on cli args var exportController { getExportZoom: function () { var zoom options.zoom; if (options.scaledWidth) { zoom options.scaledWidth / piskel.getWidth(); } else if (options.scaledHeight) { zoom options.scaledHeight / piskel.getHeight(); } return zoom; } };这是对 Web 版 ExportController 接口getExportZoom()的精确复刻与替换——Web 版中该方法返回“导出面板宽度输入值 / 画布宽度”CLI 则用命令行参数计算。优先级从高到低为--scaledWidthzoom scaledWidth / 画布宽非整数缩放按比例精确命中目标宽度--scaledHeightzoom scaledHeight / 画布高--scale直接使用整数倍率默认 1与 Web 版UserSettings.EXPORT_SCALE的默认值一致见 UserSettings.js。缩放本身由 ImageResizer.resize 完成并且显式传入smoothingEnabled falseif (zoom ! 1) { // Scale rendered frame canvas pskl.utils.ImageResizer.resize(canvas, canvas.width * zoom, canvas.height * zoom, false); }关闭图像平滑意味着浏览器画布会走最近邻插值放大后仍是锐利的像素块这正是像素画放大的期望效果也解释了为什么--scaledWidth 435这类非整数目标值不会把精灵图“抹糊”。行列布局--rows / --columns// Mock getColumns and getRows to use values from cli arguments pngExportController.getColumns_ function () { if (options.columns) return options.columns; if (options.rows) { return Math.ceil(piskelController.getFrameCount() / pngExportController.getRows_()); } else { return pngExportController.getBestFit_(); } }; pngExportController.getRows_ function () { if (options.columns !options.rows) { return Math.ceil(piskelController.getFrameCount() / pngExportController.getColumns_()); } return options.rows; };这段代码直接覆盖了 PngExportController 中原本读取 DOM 输入框的getColumns_/getRows_两个私有方法把布局策略切换为命令行驱动。其决策顺序是传了--columns列数取指定值行数取ceil(帧数 / 列数)--columns 1即整张图一列只传了--rows行数取指定值列数取ceil(帧数 / 行数)--rows 1即整张图一行都不传回退到应用默认的getBestFit_()。回退的“最佳适配”算法来自 PngExportController.getBestFit_var ratio this.piskelController.getWidth() / this.piskelController.getHeight(); var frameCount this.piskelController.getFrameCount(); var bestFit Math.round(Math.sqrt(frameCount / ratio)); return pskl.utils.Math.minmax(bestFit, 1, frameCount);即以sqrt(帧数 / 宽高比)取整作为列数并约束在[1, 帧数]区间内让精灵图整体尽量接近正方形——这就是piskel-cli snow-monster.piskel不带行列参数时“app defaults”的含义。单帧导出--frame与精灵图渲染if (options.frame -1) { // Render a single frame canvas piskelController.renderFrameAt(options.frame, true); ... } else { // Render the sprite sheet canvas pngExportController.createPngSpritesheet_(); }单帧分支--frame 3时调用renderFrameAt(3, true)。该方法定义于 PiskelController内部委托 LayerUtils.flattenFrameAt 把该帧的所有可见图层合成为一张 canvas随后若zoom ! 1再按前述方式缩放。注意帧索引从 0 开始README 原话0 is first frame。精灵图分支createPngSpritesheet_()PngExportController内部用 PiskelRenderer 逐帧渲染并按columns平铺——renderAsCanvas中每帧的落点为posX (i % columns) * 宽、posY floor(i / columns) * 高即从左到右、从上到下依次排布全部帧都绘制在一张大画布上最后整体缩放。4.4 文件落盘onPageExit渲染完成的 canvas 被挂到页面body上后导出逻辑通过callPhantom把画布宽高传回 PhantomJS 层由 cli/export-png.js 的onPageExit完成落盘function onPageExit(page, options, data) { // Set clip for output image if (data.width data.height) { page.clipRect { top: 0, left: 0, width: data.width, height: data.height }; } console.log(\n Generated file(s):); const dest options.dest.replace(.png, ) .png; // Render page to the output image page.render(dest); console.log( dest); if (options.dataUri) { const dataUriPath options.dest .datauri; const dataUri data:image/png;base64, page.renderBase64(PNG); // Write>赞分享前端图像处理【免费下载链接】piskelA simple web-based tool for Spriting and Pixel art.项目地址https://gitcode.com/gh_mirrors/pi/piskel点击查看免费下载相关推荐Piskel 发布实践从 RELEASE.md 解析 piskel / piskel-website 双仓库发布流水线Piskel 发布实践从 RELEASE.md 解析 piskel / piskel website 双仓库发布流水线 Piskel https://link前端图像处理推荐项目Piskel推荐项目Piskel Piskel 是一个在线像素艺术编辑器专为创建像素完美的图像而设计。无论你是游戏开发者、像素艺术家还是只是喜欢复古风格的图像都可以使前端图像处理Piskel用户行为分析Google Analytics事件跟踪实现Piskel用户行为分析Google Analytics事件跟踪实现 项目概述 Piskel是一款基于Web的像素艺术创作工具通过直观的界面和丰富的绘图功能前端图像处理上一篇使用 Rube MCP 自动化 Gladia 任务awesome-codex-skills 中 gladia-automation 技能实战指南下一篇gpui-kit Popup 完全指南触发器测量、锚点定位与延迟渲染的浮动层宿主创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考