phantomjssmith
Version:
PhantomJS engine for spritesmith
153 lines (137 loc) • 5.32 kB
JavaScript
// Load in our dependencies
var assert = require('assert');
var path = require('path');
var spawn = require('child_process').spawn;
var async = require('async');
var Tempfile = require('temporary/lib/file');
var phantomLocation = require('which').sync('phantomjs');
var ContentStream = require('contentstream');
var jpegJs = require('jpeg-js');
var ndarray = require('ndarray');
var savePixels = require('save-pixels');
var through2 = require('through2');
/**
* PhantomJS exporter
* @param {Object} options Options to export with
* @param {Number} [options.quality] Quality of the exported item (jpeg only)
*/
var defaultFormat = 'png';
var supportedFormats = ['jpg', 'jpeg', 'png'];
function exportCanvas(options) {
// Determine the export format
var format = options.format || defaultFormat;
assert(supportedFormats.indexOf(format) !== -1,
'`phantomjssmith` doesn\'t support exporting "' + format + '". Please use "jpeg" or "png"');
// Localize context for later and create common variables
var that = this;
var params, tmp, stdout, stderr, code;
// Generate a stream to return synchronously
var retStream = through2();
// In series
async.waterfall([
function createTmpFile (cb) {
// Convert over all image paths to url paths
var images = that.images;
images.forEach(function getUrlPath (img) {
img = img.img;
img._urlpath = path.relative(__dirname + '/scripts', img._filepath);
});
// Collect our parameters
params = that.params;
params.images = images;
params.options = options;
// Stringify our argument for phantomjs
var arg = JSON.stringify(params);
var encodedArg = encodeURIComponent(arg);
// Write out argument to temporary file -- streams weren't cutting it
tmp = new Tempfile();
tmp.writeFile(encodedArg, 'utf8', cb);
},
function spawnPhantomJS (cb) {
// Create a child process for phantomjs
var phantomjs = spawn(phantomLocation, [path.join(__dirname, 'scripts', 'compose.js'), tmp.path]);
phantomjs.on('error', cb);
// When there is data, save it
// DEV: encodedPixels is an array of rgba values
stdout = '';
phantomjs.stdout.on('data', function handleStdout (buffer) {
stdout += buffer.toString();
});
// When there is an error, concatenate it
stderr = '';
phantomjs.stderr.on('data', function handleData (buffer) {
// Ignore PhantomJS 1.9.2 OSX errors
// https://github.com/Ensighten/grunt-spritesmith/issues/33
var bufferStr = buffer.toString();
var isNot192OSXError = bufferStr.indexOf('WARNING: Method userSpaceScaleFactor') === -1;
var isNotPerformanceNote = bufferStr.indexOf('CoreText performance note:') === -1;
if (isNot192OSXError && isNotPerformanceNote) {
stderr += bufferStr;
}
});
// When we are done, save the code and continue
phantomjs.on('close', function handleClose (_code) {
code = _code;
cb();
});
},
function deleteTmpFile (cb) {
// Destroy the temporary file
tmp.unlink(cb);
},
function handleResult (cb) {
// If we received a non-zero exit code, complain and leave
if (code !== 0) {
var err = new Error('Received non-zero exit code "' + code + '" from PhantomJS. stdout:' + stdout);
return cb(err);
}
// Otherwise, decode the pixel values
// DEV: This used to be thinner and not need padding but Windows was messing up the image
var encodedPixels = stdout;
var decodedPixels;
try {
decodedPixels = JSON.parse(encodedPixels);
} catch (e) {
return cb(new Error('Error while parsing JSON "' + encodedPixels + '".\n' + e.message));
}
// If we are dealing with a `jpeg`, then use `jpeg-js`
// DEV: This is to support `quality` which `save-pixels` doesn't
var resultStream;
if (['jpg', 'jpeg'].indexOf(format) !== -1) {
// Encode our data via `jpeg-js` and callback with its internal buffer
var jpg;
try {
jpg = jpegJs.encode({
data: decodedPixels,
width: params.width,
height: params.height
}, options.quality);
} catch (err) {
return cb(err);
}
resultStream = new ContentStream(jpg.data);
// Otherwise, leverage `save-pixels`
} else {
// Convert the pixels into an ndarray
// Taken from https://github.com/mikolalysenko/get-pixels/blob/2ac98645119244d6e52afcef5fe52cc9300fb27b/dom-pixels.js#L14
var imgNdarray = ndarray(decodedPixels,
[params.height, params.width, 4], [4 * params.width, 4, 1], 0);
// Generate our result stream
resultStream = savePixels(imgNdarray, format);
}
// Pipe the stream into the returned result and handle errors
resultStream.on('error', function forwardError (err) {
retStream.emit('error', err);
});
resultStream.pipe(retStream);
}
], function handleError (err) {
// If there was an error, emit it
if (err) {
retStream.emit('error', err);
}
});
// Return our generated stream
return retStream;
}
module.exports = exportCanvas;