add support for streams (in and out), minor updates for node v0.4 - v0.6

* Allow for first arg of gm() to be input stream, filename or width
  * Use 'spawn' instead of 'exec', arguments no longer need escaping
  * Retain original write() API for complete backwards compatibility
  * Add stream() method, returning stdout and stderr ReadableStreams
  * NPM now requires Node v0.4.2
  * Upgrade gleak to 0.2.2
master
Evan Owen 13 years ago committed by Aaron Heckmann
parent 4590af660b
commit 6562ce9240
  1. 17
      index.js
  2. 7
      lib/args.js
  3. 133
      lib/command.js
  4. 8
      lib/drawing.js
  5. 8
      lib/getters.js
  6. 4
      package.json
  7. 2
      test/index.js
  8. 12
      test/streamIn.js
  9. 16
      test/streamInOut.js
  10. 15
      test/streamOut.js

@ -5,13 +5,13 @@
* Module dependencies.
*/
var escape = require('./lib/utils').escape;
var Stream = require('stream').Stream;
/**
* Constructor.
*
* @param {String|Number} path - path to img source or width of img to create
* @param {Number} [height] - optional height of img to create
* @param {String|Number} path - path to img source or ReadableStream or width of img to create
* @param {Number} [height] - optional filename of ReadableStream or height of img to create
* @param {String} [color] - optional hex background color of created img
*/
@ -22,11 +22,10 @@ function gm (source, height, color) {
return new gm(source, height, color);
}
this.data = {};
this._in = [];
this._out = [];
if (height) {
if(source instanceof Stream) {
this.sourceStream = source;
source = height || 'unknown.jpg';
} else if (height) {
// new images
width = source;
source = "";
@ -37,8 +36,6 @@ function gm (source, height, color) {
this.in("xc:"+ color);
}
} else {
source = escape(source);
}
this.source = source;

@ -12,7 +12,7 @@ module.exports = function (proto) {
options = options || "";
// avoid error "geometry does not contain image (unable to crop image)" - gh-17
if (!(this.inputIs('jpg') && ~this._out.indexOf('"-crop"'))) {
if (!(this.inputIs('jpg') && ~this._out.indexOf('-crop'))) {
this.in("-size", w +"x"+ h + options);
}
@ -26,8 +26,7 @@ module.exports = function (proto) {
// http://www.graphicsmagick.org/GraphicsMagick.html#details-profile
proto.noProfile = function noProfile () {
// profile has a lame particularity so we don't escape
this._out.push('+profile "*"');
this.out('+profile', '"*"');
return this;
}
@ -55,7 +54,7 @@ module.exports = function (proto) {
proto.crop = function crop (w, h, x, y) {
if (this.inputIs('jpg')) {
// avoid error "geometry does not contain image (unable to crop image)" - gh-17
var index = this._in.indexOf('"-size"');
var index = this._in.indexOf('-size');
if (~index) {
this._in.splice(index, 2);
}

@ -6,7 +6,7 @@
*/
var exec = require('child_process').exec;
var escape = require('./utils').escape;
var spawn = require('child_process').spawn;
/**
* Extend proto
@ -21,7 +21,7 @@ module.exports = function (proto) {
var i = 0;
for (; i < len; ++i) {
a.push(escape(arguments[i]));
a.push(arguments[i]);
}
this[prop] = this[prop].concat(a);
@ -29,13 +29,24 @@ module.exports = function (proto) {
}
}
proto._in = [];
proto.in = args('_in');
proto._out = [];
proto.out = args('_out');
/**
* Execute the command and write the image to the specified file name.
*
* @param {String} name
* @param {Function} callback
* @return {Object} gm
*/
proto.write = function write (name, callback) {
if (!callback) callback = name, name = null;
if ("function" !== typeof callback) {
if (typeof callback !== "function") {
throw new TypeError("gm().write() expects a callback function")
}
@ -43,32 +54,98 @@ module.exports = function (proto) {
throw new TypeError("gm().write() expects a filename when writing new files")
}
if (name) {
this.outname = escape(name);
}
this.outname = name;
return this._exec(this.cmd(), callback);
return this._spawn("gm", this.args(), this.sourceStream, true, callback);
}
proto._exec = function _exec (cmd, callback) {
/**
* Execute the command and return stdin and stderr ReadableStreams providing the image data.
*
* @param {Function} callback
* @return {Object} gm
*/
proto.stream = function stream (callback) {
if (typeof callback !== "function") {
throw new TypeError("gm().stream() expects a callback function")
}
var self = this;
exec(cmd, function (err, stdout, stderr) {
callback.call(self, err, stdout, stderr, cmd);
});
return this._spawn("gm", this.args(), this.sourceStream, false, callback);
}
/**
* Execute the command, returning stdout and stderr buffers.
*
* @param {String} bin
* @param {Array} args
* @param {Function} callback
* @return {Object} gm
*/
proto._exec = function _exec (bin, args, callback) {
return this._spawn(bin, args, null, true, callback);
}
/**
* Execute the command with stdin, returning stdout and stderr streams or buffers.
*
* @param {String} bin
* @param {Array} args
* @param {ReadableStream} stream
* @param {Boolean} shouldBuffer
* @param {Function} callback
* @return {Object} gm
*/
proto._spawn = function _spawn (bin, args, stream, shouldBuffer, callback) {
var proc = spawn(bin, args);
var cmd = bin + " " + args.join(' ');
if(stream) {
stream.pipe(proc.stdin);
}
if(shouldBuffer) {
var stdout = ''
, stderr = '';
proc.stdout.addListener('data', function(data) {
stdout += data;
});
proc.stderr.addListener('data', function(data) {
stderr += data;
});
proc.addListener('exit', function(code, signal) {
var err = null;
if(code !== 0 || signal !== null) {
var e = new Error('Command failed: ' + stderr);
e.code = code;
e.signal = signal;
};
callback.call(this, err, stdout, stderr, cmd);
});
} else {
callback.call(this, null, proc.stdout, proc.stderr, cmd);
}
return self;
return this;
}
proto.cmd = function cmd () {
return "gm convert "
+ this._in.join(" ")
+ " "
+ this.source
+ " "
+ this._out.join(" ")
+ " "
+ this.outname || this.source;
proto.args = function args () {
return [].concat(
'convert'
, this._in
, this.sourceStream ? "-" : this.source
, this._out
, this.outname || "-"
);
}
/**
@ -76,12 +153,12 @@ module.exports = function (proto) {
*/
var types = {
'jpg': /[\.jpg|\.jpeg]"$/i
, 'png' : /\.png"$/i
, 'gif' : /\.gif"$/i
, 'tiff': /[\.tiff|\.tif]"$/i
, 'bmp' : /[\.bmp|\.dib]"$/i
, 'webp': /\.webp"$/i
'jpg': /[\.jpg|\.jpeg]$/i
, 'png' : /\.png$/i
, 'gif' : /\.gif$/i
, 'tiff': /[\.tiff|\.tif]$/i
, 'bmp' : /[\.bmp|\.dib]$/i
, 'webp': /\.webp$/i
};
types.jpeg = types.jpg;
@ -103,7 +180,7 @@ module.exports = function (proto) {
var rgx = types[type];
if (!rgx) {
if ('.' !== type[0]) type = '.' + type;
rgx = new RegExp('\\' + type + '"$', 'i');
rgx = new RegExp('\\' + type + '$', 'i');
}
return rgx.test(this.source);

@ -1,6 +1,12 @@
// gm - Copyright Aaron Heckmann <aaron.heckmann+github@gmail.com> (MIT Licensed)
/**
* Module dependencies.
*/
var escape = require('./utils').escape;
/**
* Extend proto.
*/
@ -119,7 +125,7 @@ module.exports = function (proto) {
// http://www.graphicsmagick.org/GraphicsMagick.html#details-draw
proto.drawText = function drawText (x0, y0, text, gravity) {
var gravity = String(gravity || "").toLowerCase()
, arg = ["text " + x0 + "," + y0 + ' "' + text + '"'];
, arg = ["text " + x0 + "," + y0 + " " + escape(text)];
if (~this._gravities.indexOf(gravity)) {
arg.unshift("gravity", gravity);

@ -8,7 +8,9 @@
module.exports = function (proto) {
;['size', 'format', 'depth', 'color', 'res', 'filesize'].forEach(function (getter) {
proto.data = {}
proto[getter] = function (callback) {
var self = this;
@ -47,9 +49,9 @@ proto.identify = function identify (callback) {
self._iq = [callback];
self._identifying = true;
var cmd = "gm identify -ping -verbose " + self.source;
var args = ['identify', '-ping', '-verbose', self.source];
self._exec(cmd, function (err, stdout, stderr) {
self._exec("gm", args, function (err, stdout, stderr) {
if (err) {
return callback.call(self, err, stdout, stderr, cmd);
}

@ -3,7 +3,7 @@
, "version": "0.5.0"
, "author": "Aaron Heckmann <aaron.heckmann+github@gmail.com>"
, "keywords": ["nodejs", "graphics magick", "graphics", "magick", "image"]
, "engines": { "node": ">= 0.1.96" }
, "engines": { "node": ">= 0.4.2" }
, "bugs": "http://github.com/aheckmann/gm/issues"
, "licenses": [{ "type": "MIT", "url": "http://www.opensource.org/licenses/mit-license.php"}]
, "main": "./index"
@ -17,6 +17,6 @@
}
, "devDependencies": {
"should": "0.2.1"
, "gleak": "0.0.1"
, "gleak": "0.2.2"
}
}

@ -4,7 +4,7 @@
var dir = __dirname + '/../examples/imgs';
var gm = require('../');
var assert = require('assert');
var gleak = require('gleak');
var gleak = require('gleak')();
var fs = require('fs');
var files = fs.readdirSync(__dirname).filter(filter);

@ -0,0 +1,12 @@
// gm - Copyright Aaron Heckmann <aaron.heckmann+github@gmail.com> (MIT Licensed)
var fs = require('fs');
module.exports = function (_, dir, finish, gm) {
gm(fs.createReadStream(dir + '/original.jpg'), "original.jpg")
.write(dir + '/streamIn.png', function streamIn (err){
finish(err);
});
}

@ -0,0 +1,16 @@
// gm - Copyright Aaron Heckmann <aaron.heckmann+github@gmail.com> (MIT Licensed)
var fs = require('fs');
module.exports = function (_, dir, finish, gm) {
gm(fs.createReadStream(dir + '/original.jpg'), "original.jpg")
.stream(function streamOut (err, stdout, stderr){
stdout.pipe(fs.createReadStream(dir + '/streamInOut.jpg'));
stdout.addListener('close', function() {
finish(err);
});
});
}

@ -0,0 +1,15 @@
// gm - Copyright Aaron Heckmann <aaron.heckmann+github@gmail.com> (MIT Licensed)
var fs = require('fs');
module.exports = function (gm, dir, finish) {
gm
.stream(function streamOut (err, stdout, stderr){
stdout.pipe(fs.createWriteStream(dir + '/streamOut.jpg'));
stdout.addListener('close', function() {
finish(err);
});
});
}
Loading…
Cancel
Save