Add piskel-cli for exporting from .piskel files via command line

This commit is contained in:
Jordan Watkins 2018-10-17 18:33:55 -07:00 committed by Julian Descottes
parent 5ebf83badf
commit b54efbde21
5 changed files with 337 additions and 1 deletions

64
cli/README.md Normal file
View file

@ -0,0 +1,64 @@
# Piskel CLI
Wraps the Piskel pixel editing application to enable similar export options via the command line.
## Installation
Option 1: Globally install Piskel
```
npm install -g https://github.com/piskelapp/piskel/tarball/master
```
Option 2: Clone and install Piskel normally and then run npm link inside the installation root
## Usage
**Export provided .piskel file as a png sprite sheet using app defaults**
```
piskel-cli snow-monster.piskel
```
**Export scaled sprite sheet**
```
piskel-cli snow-monster.piskel --scale 5
```
**Export scaled to specific (single frame) width value**
```
piskel-cli snow-monster.piskel --scaledWidth 435
```
**Export scaled to specific (single frame) height value**
```
piskel-cli snow-monster.piskel --scaledHeight 435
```
**Export sprite sheet as a single column**
```
piskel-cli snow-monster.piskel --columns 1
```
**Export sprite sheet as a single row**
```
piskel-cli snow-monster.piskel --rows 1
```
**Export a single frame (0 is first frame)**
```
piskel-cli snow-monster.piskel --frame 3
```
**Export a second file containing the data-uri for the exported png**
```
piskel-cli snow-monster.piskel --dataUri
```
**Export cropped**
```
piskel-cli snow-monster.piskel --crop
```
**Custom output path and/or filename**
```
piskel-cli snow-monster.piskel --dest ./output-folder/snah-monstah.png
```

130
cli/export-png.js Normal file
View file

@ -0,0 +1,130 @@
const fs = require('fs');
function onPageEvaluate(window, options, piskel) {
console.log("\nPiskel name: " + piskel.descriptor.name);
// Setup piskelController
var piskelController = new pskl.controller.piskel.PiskelController(piskel);
pskl.app.piskelController = piskelController;
piskelController.init();
// 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();
}
// Mock exportController to provide zoom value based on cli args
// and to avoid errors and/or unnecessary bootstrapping
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;
}
};
// Setup pngExportController
var pngExportController = new pskl.controller.settings.exportimage.PngExportController(piskelController, exportController);
// 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;
};
// Render to output canvas
var canvas;
if (options.frame > -1) {
// Render a single frame
canvas = piskelController.renderFrameAt(options.frame, true);
var zoom = exportController.getExportZoom();
if (zoom != 1) {
// Scale rendered frame
canvas = pskl.utils.ImageResizer.resize(canvas, canvas.width * zoom, canvas.height * zoom, false);
}
} else {
// Render the sprite sheet
canvas = pngExportController.createPngSpritesheet_();
}
// Add output canvas to DOM
window.document.body.appendChild(canvas);
// Prepare return data
const returnData = {
width: canvas.width,
height: canvas.height
};
// Wait a tick for things to wrap up
setTimeout(function () {
// Exit and pass data to parent process
window.callPhantom(returnData);
}, 0);
}
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 + '.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 data-uri to file
fs.write(dataUriPath, dataUri, 'w');
console.log(" " + dataUriPath);
}
}
module.exports = {
onPageEvaluate: onPageEvaluate,
onPageExit: onPageExit
};

93
cli/index.js Normal file
View file

@ -0,0 +1,93 @@
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const minimist = require('minimist');
const childProcess = require('child_process');
const phantomjs = require('phantomjs');
const binPath = phantomjs.path;
// Parse command args
let args = minimist(process.argv.slice(2), {
default: {
crop: false,
dataUri: false,
debug: false,
scale: 1
},
});
if (args.debug) console.log(args);
// Ensure a path for the src file was passed
if (!args._ || (args._ && !args._.length)) {
console.error('Path to a .piskel file is required');
return;
}
const src = args._[0];
// Ensure the src file exists
if (!fs.existsSync(src)) {
console.error('No such file: ' + src);
return;
}
// Read src piskel file
const piskelFile = fs.readFileSync(src, 'utf-8');
const dest = args.dest || path.basename(src, '.piskel');
console.log('Piskel CLI is exporting...');
// Get path to Piskel's 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(`Piskel's application JS file not found in: ${piskelAppJsDir}. Run prod build and try again.`);
return;
}
// Prepare args to pass to phantom script
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)
];
if (args.debug) {
childArgs.unshift(
'--remote-debugger-port=9035',
'--remote-debugger-autorun=yes'
);
}
// Run phantom script
childProcess.execFile(binPath, childArgs, function (err, stdout, stderr) {
// Print any output the from child process
if (err) console.log(err);
if (stderr) console.log(stderr);
if (stdout) console.log(stdout);
console.log('Export complete');
});

45
cli/piskel-export.js Normal file
View file

@ -0,0 +1,45 @@
// PhantomJS system
const system = require('system');
// Exporter
const exporter = require('./export-png');
// Get passed args
const args = system.args;
// Parse input piskel file and options
const piskelFile = JSON.parse(args[1]);
const options = JSON.parse(args[2]);
// Create page w/ canvas
const page = require('webpage').create();
page.content = '<html><body></body></html>';
// Inject Piskel JS
page.injectJs(options.piskelAppJsPath);
// Listen for page console logs
page.onConsoleMessage = function (msg) {
console.log(msg);
};
// Run page logic
page.evaluate(function (piskelFile, options, onPageEvaluate) {
// Zero out default body margin
document.body.style.margin = 0;
// Deserialize piskel file and run exporter's page evaluate task
pskl.utils.serialization.Deserializer.deserialize(piskelFile, function (piskel) {
onPageEvaluate(window, options, piskel);
});
}, piskelFile, options, exporter.onPageEvaluate);
// Wait for page to trigger exit
page.onCallback = function (data) {
// Run exporter page exit task
exporter.onPageExit(page, options, data);
// Exit
phantom.exit(0);
};

View file

@ -17,7 +17,8 @@
"misc/scripts/piskel-root"
],
"bin": {
"piskel-root": "./misc/scripts/piskel-root"
"piskel-root": "./misc/scripts/piskel-root",
"piskel-cli": "./cli/index.js"
},
"main": "./dest/prod/index.html",
"scripts": {
@ -63,5 +64,8 @@
"toolbar": false,
"width": 1000,
"height": 700
},
"dependencies": {
"minimist": "^1.2.0"
}
}