Comparing version 0.5.0 to 0.6.0
## v0.6.0 - 12/14/2011 | ||
* added; stream support [kainosnoema] (#22) | ||
## v0.5.0 - 07/07/2011 | ||
@@ -3,0 +7,0 @@ |
23
index.js
@@ -8,3 +8,3 @@ | ||
var escape = require('./lib/utils').escape; | ||
var Stream = require('stream').Stream; | ||
@@ -14,4 +14,4 @@ /** | ||
* | ||
* @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 | ||
@@ -31,3 +31,6 @@ */ | ||
if (height) { | ||
if (source instanceof Stream) { | ||
this.sourceStream = source; | ||
source = height || 'unknown.jpg'; | ||
} else if (height) { | ||
// new images | ||
@@ -42,5 +45,11 @@ width = source; | ||
} | ||
} | ||
} else { | ||
source = escape(source); | ||
// parse out gif frame brackets from filename | ||
// since stream doesn't use source path | ||
// eg. "filename.gif[0]" | ||
var frames; | ||
if (frames = source.match(/(\[.+\])$/)) { | ||
this.sourceFrames = source.substr(frames.index, frames[0].length); | ||
source = source.substr(0, frames.index); | ||
} | ||
@@ -66,3 +75,3 @@ | ||
module.exports = gm; | ||
module.exports.version = "0.5.0"; | ||
module.exports.version = "0.6.0"; | ||
@@ -15,3 +15,3 @@ | ||
// 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); | ||
@@ -30,4 +30,3 @@ } | ||
proto.noProfile = function noProfile () { | ||
// profile has a lame particularity so we don't escape | ||
this._out.push('+profile "*"'); | ||
this.out('+profile', '"*"'); | ||
return this; | ||
@@ -60,3 +59,3 @@ } | ||
// 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) { | ||
@@ -312,6 +311,6 @@ this._in.splice(index, 2); | ||
} | ||
// http://www.graphicsmagick.org/GraphicsMagick.html#details-trim | ||
proto.trim = function trim () { | ||
return this.out("-trim"); | ||
return this.out("-trim"); | ||
} | ||
@@ -318,0 +317,0 @@ }; |
@@ -9,3 +9,4 @@ | ||
var exec = require('child_process').exec; | ||
var escape = require('./utils').escape; | ||
var spawn = require('child_process').spawn; | ||
var utils = require('./utils'); | ||
@@ -25,3 +26,3 @@ /** | ||
for (; i < len; ++i) { | ||
a.push(escape(arguments[i])); | ||
a.push(arguments[i]); | ||
} | ||
@@ -37,2 +38,10 @@ | ||
/** | ||
* 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) { | ||
@@ -46,31 +55,128 @@ if (!callback) callback = name, name = null; | ||
if (!name) { | ||
throw new TypeError("gm().write() expects a filename when writing new files") | ||
return callback(TypeError("gm().write() expects a filename when writing new files")); | ||
} | ||
if (name) { | ||
this.outname = escape(name); | ||
this.outname = name; | ||
return this._spawn("gm", this.args(), true, callback); | ||
} | ||
/** | ||
* Execute the command and return stdin and stderr ReadableStreams providing the image data. | ||
* | ||
* @param {Function} callback | ||
* @return {Object} gm | ||
*/ | ||
proto.stream = function stream (format, callback) { | ||
if (!callback) callback = format, format = null; | ||
if ("function" !== typeof callback) { | ||
throw new TypeError("gm().stream() expects a callback function") | ||
} | ||
return this._exec(this.cmd(), callback); | ||
if (format) { | ||
format = format.split('.').slice(-1)[0].toUpperCase(); | ||
this.outname = format + ":-"; | ||
} | ||
return this._spawn("gm", this.args(), false, callback); | ||
} | ||
proto._exec = function _exec (cmd, callback) { | ||
var self = this; | ||
/** | ||
* Execute the command, buffer input and output, return stdout and stderr buffers. | ||
* | ||
* @param {String} bin | ||
* @param {Array} args | ||
* @param {Function} callback | ||
* @return {Object} gm | ||
*/ | ||
exec(cmd, function (err, stdout, stderr) { | ||
callback.call(self, err, stdout, stderr, cmd); | ||
}); | ||
proto._exec = function _exec (bin, args, callback) { | ||
return this._spawn(bin, args, true, callback); | ||
} | ||
return self; | ||
/** | ||
* 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, bufferOutput, callback) { | ||
var proc = spawn(bin, args) | ||
, cmd = bin + " " + args.map(utils.escape).join(' ') | ||
, self = this | ||
, err; | ||
// pipe in the sourceStream if present | ||
if (self.sourceStream) { | ||
if (!self.sourceStream.readable && !self.bufferStream) { | ||
err = new Error("gm().stream() or gm().write() with a non-readable " + | ||
"stream. Pass \"{bufferStream: true}\" to identify() " + | ||
"or getter (size, format, etc...)"); | ||
return callback.call(this, err); | ||
} | ||
self.sourceStream.pipe(proc.stdin); | ||
// resume any buffered events from a previous identify operation | ||
if (self.buffer) { | ||
self.buffer.resume(); | ||
// if {bufferStream: true} was passed to an identify operation, | ||
// we buffer the input stream events so we can use them again | ||
} else if (self.bufferStream) { | ||
self.buffer = utils.buffer(self.sourceStream); | ||
} | ||
} | ||
// for _exec operations (identify() mostly), we also | ||
// need to buffer the output stream before returning | ||
if (bufferOutput) { | ||
var stdout = '' | ||
, stderr = '' | ||
, onOut | ||
, onErr | ||
, onExit | ||
proc.stdout.addListener('data', onOut = function (data) { | ||
stdout += data; | ||
}); | ||
proc.stderr.addListener('data', onErr = function (data) { | ||
stderr += data; | ||
}); | ||
proc.addListener('exit', onExit = function (code, signal) { | ||
if (code !== 0 || signal !== null) { | ||
err = new Error('Command failed: ' + stderr); | ||
err.code = code; | ||
err.signal = signal; | ||
}; | ||
callback.call(this, err, stdout, stderr, cmd); | ||
stdout = stderr = onOut = onErr = onExit = null; | ||
}); | ||
} else { | ||
callback.call(this, null, proc.stdout, proc.stderr, cmd); | ||
} | ||
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 () { | ||
var source = (this.sourceStream ? "-" : this.source); | ||
if (source && this.sourceFrames) source += this.sourceFrames; | ||
return [].concat( | ||
'convert' | ||
, this._in | ||
, source | ||
, this._out | ||
, this.outname || "-" | ||
).filter(Boolean); // remove falsey | ||
} | ||
@@ -83,8 +189,8 @@ | ||
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 | ||
}; | ||
@@ -111,3 +217,3 @@ | ||
if ('.' !== type[0]) type = '.' + type; | ||
rgx = new RegExp('\\' + type + '"$', 'i'); | ||
rgx = new RegExp('\\' + type + '$', 'i'); | ||
} | ||
@@ -114,0 +220,0 @@ |
@@ -5,2 +5,8 @@ | ||
/** | ||
* Module dependencies. | ||
*/ | ||
var escape = require('./utils').escape; | ||
/** | ||
* Extend proto. | ||
@@ -123,3 +129,3 @@ */ | ||
var gravity = String(gravity || "").toLowerCase() | ||
, arg = ["text " + x0 + "," + y0 + ' "' + text + '"']; | ||
, arg = ["text " + x0 + "," + y0 + " " + escape(text)]; | ||
@@ -126,0 +132,0 @@ if (~this._gravities.indexOf(gravity)) { |
@@ -12,5 +12,6 @@ | ||
proto[getter] = function (callback) { | ||
proto[getter] = function (opts, callback) { | ||
if (!callback) callback = opts, opts = {}; | ||
var self = this; | ||
if (self.data[getter]) { | ||
@@ -21,3 +22,3 @@ callback.call(self, null, self.data[getter]); | ||
self.identify(function (err, stdout, stderr, cmd) { | ||
self.identify(opts, function (err, stdout, stderr, cmd) { | ||
if (err) { | ||
@@ -34,3 +35,5 @@ return callback.call(self, err, stdout, stderr, cmd); | ||
proto.identify = function identify (callback) { | ||
proto.identify = function identify (opts, callback) { | ||
if (!callback) callback = opts, opts = {}; | ||
var self = this; | ||
@@ -40,2 +43,4 @@ | ||
self.bufferStream = !! opts.bufferStream; | ||
if (self._identifying) { | ||
@@ -54,5 +59,5 @@ self._iq.push(callback); | ||
var cmd = "gm identify -ping -verbose " + self.source; | ||
var args = ['identify', '-ping', '-verbose', self.sourceStream ? '-' : self.source]; | ||
self._exec(cmd, function (err, stdout, stderr) { | ||
self._exec("gm", args, function (err, stdout, stderr) { | ||
if (err) { | ||
@@ -59,0 +64,0 @@ return callback.call(self, err, stdout, stderr, cmd); |
@@ -15,1 +15,48 @@ | ||
} | ||
/** | ||
* Buffer `data` and `end` events from the given stream `obj`. | ||
* | ||
* @param {Stream} obj | ||
* @api public | ||
*/ | ||
// __Attribution:__ Taken from node-http-proxy's stream buffer implementation | ||
// https://github.com/nodejitsu/node-http-proxy/blob/9f05e6c567/lib/node-http-proxy.js#L223-256 | ||
// https://github.com/nodejitsu/node-http-proxy/blob/9f05e6c567/LICENSE | ||
exports.buffer = function (obj) { | ||
var events = [] | ||
, onData | ||
, onEnd; | ||
obj.on('data', onData = function (data, encoding) { | ||
events.push(['data', data, encoding]); | ||
}); | ||
obj.on('end', onEnd = function (data, encoding) { | ||
events.push(['end', data, encoding]); | ||
}); | ||
return { | ||
end: function () { | ||
obj.removeListener('data', onData); | ||
obj.removeListener('end', onEnd); | ||
} | ||
, destroy: function () { | ||
this.end(); | ||
this.resume = function () { | ||
console.error("Cannot resume buffer after destroying it."); | ||
}; | ||
onData = onEnd = events = obj = null; | ||
} | ||
, resume: function () { | ||
this.end(); | ||
for (var i = 0, len = events.length; i < len; ++i) { | ||
obj.emit.apply(obj, events[i]); | ||
} | ||
} | ||
}; | ||
}; |
{ "name": "gm" | ||
, "description": "Graphics Magick for node." | ||
, "version": "0.5.0" | ||
, "version": "0.6.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" | ||
@@ -20,3 +20,4 @@ , "licenses": [{ "type": "MIT", "url": "http://www.opensource.org/licenses/mit-license.php"}] | ||
"should": "0.2.1" | ||
, "gleak": "0.2.2" | ||
} | ||
} |
166
README.md
# gm | ||
GraphicsMagick for node | ||
var gm = require('./gm'); | ||
// resize and remove EXIF profile data | ||
gm('/path/to/my/img.jpg') | ||
.resize(240, 240) | ||
.noProfile() | ||
.write('/path/to/resize.png', function (err) { | ||
if (!err) console.log('done'); | ||
}); | ||
## Basic Usage | ||
```` js | ||
var fs = require('fs') | ||
, gm = require('./gm'); | ||
// obtain the size of an image | ||
gm('/path/to/my/img.jpg') | ||
.size(function (err, size) { | ||
if (!err) | ||
console.log(size.width > size.height ? 'wider' : 'taller than you'); | ||
}); | ||
// resize and remove EXIF profile data | ||
gm('/path/to/my/img.jpg') | ||
.resize(240, 240) | ||
.noProfile() | ||
.write('/path/to/resize.png', function (err) { | ||
if (!err) console.log('done'); | ||
}); | ||
// output all available image properties | ||
gm('/path/to/img.png') | ||
.identify(function (err, data) { | ||
if (!err) console.dir(data) | ||
}); | ||
// obtain the size of an image | ||
gm('/path/to/my/img.jpg') | ||
.size(function (err, size) { | ||
if (!err) | ||
console.log(size.width > size.height ? 'wider' : 'taller than you'); | ||
}); | ||
// pull out the first frame of an animated gif and save as png | ||
gm('/path/to/animated.gif[0]') | ||
.write('/path/to/firstframe.png', function (err) { | ||
if (err) console.log('aaw, shucks'); | ||
}); | ||
// output all available image properties | ||
gm('/path/to/img.png') | ||
.identify(function (err, data) { | ||
if (!err) console.dir(data) | ||
}); | ||
// crazytown | ||
gm('/path/to/my/img.jpg') | ||
.flip() | ||
.magnify() | ||
.rotate('green', 45) | ||
.blur(7, 3) | ||
.crop(300, 300, 150, 130) | ||
.edge(3) | ||
.write('/path/to/crazy.jpg', function (err) { | ||
if (!err) console.log('crazytown has arrived'); | ||
}) | ||
// pull out the first frame of an animated gif and save as png | ||
gm('/path/to/animated.gif[0]') | ||
.write('/path/to/firstframe.png', function (err) { | ||
if (err) console.log('aaw, shucks'); | ||
}); | ||
// annotate an image | ||
gm('/path/to/my/img.jpg') | ||
.stroke("#ffffff") | ||
.drawCircle(10, 10, 20, 10) | ||
.font("Helvetica.ttf", 12) | ||
.drawText(30, 20, "GMagick!") | ||
.write("/path/to/drawing.png", function (err) { | ||
if (!err) console.log('done'); | ||
}); | ||
// crazytown | ||
gm('/path/to/my/img.jpg') | ||
.flip() | ||
.magnify() | ||
.rotate('green', 45) | ||
.blur(7, 3) | ||
.crop(300, 300, 150, 130) | ||
.edge(3) | ||
.write('/path/to/crazy.jpg', function (err) { | ||
if (!err) console.log('crazytown has arrived'); | ||
}) | ||
// creating an image | ||
gm(200, 400, "#ddff99f3") | ||
.drawText(10, 50, "from scratch") | ||
.write("/path/to/brandNewImg.jpg", function (err) { | ||
// ... | ||
}); | ||
// annotate an image | ||
gm('/path/to/my/img.jpg') | ||
.stroke("#ffffff") | ||
.drawCircle(10, 10, 20, 10) | ||
.font("Helvetica.ttf", 12) | ||
.drawText(30, 20, "GMagick!") | ||
.write("/path/to/drawing.png", function (err) { | ||
if (!err) console.log('done'); | ||
}); | ||
// creating an image | ||
gm(200, 400, "#ddff99f3") | ||
.drawText(10, 50, "from scratch") | ||
.write("/path/to/brandNewImg.jpg", function (err) { | ||
// ... | ||
}); | ||
```` | ||
## Image paths or Streams | ||
```` js | ||
// can provide either a file path or a ReadableStream | ||
// (from a local file or incoming network request) | ||
var readStream = fs.createReadStream('/path/to/my/img.jpg'); | ||
gm(readStream, 'img.jpg') | ||
.write('/path/to/reformat.png', function (err) { | ||
if (!err) console.log('done'); | ||
}); | ||
// can also stream output to a ReadableStream | ||
// (can be piped to a local file or remote server) | ||
gm('/path/to/my/img.jpg') | ||
.resize('200', '200') | ||
.stream(function (err, stdout, stderr) { | ||
var writeStream = fs.createWriteStream('/path/to/my/resized.jpg'); | ||
stdout.pipe(writeStream); | ||
}); | ||
// pass a format or filename to stream() and | ||
// gm will provide image data in that format | ||
gm('/path/to/my/img.jpg') | ||
.stream('png', function (err, stdout, stderr) { | ||
var writeStream = fs.createWriteStream('/path/to/my/reformated.png'); | ||
stdout.pipe(writeStream); | ||
}); | ||
// combine the two for true streaming image processing | ||
var readStream = fs.createReadStream('/path/to/my/img.jpg'); | ||
gm(readStream, 'img.jpg') | ||
.resize('200', '200') | ||
.stream(function (err, stdout, stderr) { | ||
var writeStream = fs.createWriteStream('/path/to/my/resized.jpg'); | ||
stdout.pipe(writeStream); | ||
}); | ||
// when working with input streams and any 'identify' | ||
// operation (size, format, etc), you must pass "{bufferStream: true}" if | ||
// you also need to convert (write() or stream()) the image afterwards | ||
// NOTE: this temporarily buffers the image stream in Node memory | ||
var readStream = fs.createReadStream('/path/to/my/img.jpg'); | ||
gm(readStream, 'img.jpg') | ||
.size({bufferStream: true}, function(err, size) { | ||
this.resize(size.width / 2, size.height / 2) | ||
this.write('/path/to/resized.jpg', function (err) { | ||
if (!err) console.log('done'); | ||
}); | ||
}); | ||
```` | ||
## Getting started | ||
@@ -83,4 +138,4 @@ First download and install [GraphicsMagick](http://www.graphicsmagick.org/) | ||
- 1) `gm(path)` When you pass a string as the first argument it is interpreted as the path to an image you intend to manipulate. | ||
- 2) `gm(width, height)` When you pass two arguments it tells gm to create a new image on the fly with the provided dimensions. And you can still chain just like you do with pre-existing images too. See [here](http://github.com/aheckmann/gm/blob/master/examples/new.js) for an example. | ||
- 3) `gm(width, height, color)` The same as #2 but you may also specify a background color for the created image. | ||
- 2) `gm(stream, [filename])` You may also pass a ReadableStream as the first argument, with an optional file name for format inference. | ||
- 3) `gm(width, height, [color])` When you pass two integer arguments, gm will create a new image on the fly with the provided dimensions and an optional background color. And you can still chain just like you do with pre-existing images too. See [here](http://github.com/aheckmann/gm/blob/master/examples/new.js) for an example. | ||
@@ -170,5 +225,8 @@ ## Methods | ||
- image output | ||
- **write** - writes the processed image data to the specified filename | ||
- **stream** - provides a ReadableStream with the processed image data | ||
## Node version | ||
Compatible with > v0.1.96 | ||
## Contributors | ||
[https://github.com/aheckmann/gm/contributors](https://github.com/aheckmann/gm/contributors) | ||
@@ -175,0 +233,0 @@ ## Inspiration |
@@ -5,4 +5,19 @@ | ||
var dir = __dirname + '/../examples/imgs'; | ||
var gm = require('../') | ||
var gm = require('../'); | ||
var assert = require('assert'); | ||
var gleak = require('gleak')(); | ||
var fs = require('fs'); | ||
var files = fs.readdirSync(__dirname).filter(filter); | ||
var pending, total = pending = files.length; | ||
function filter (file) { | ||
if (!/\.js$/.test(file)) return false; | ||
if ('index.js' === file) return false; | ||
var filename = __dirname + '/' + file; | ||
if (!fs.statSync(filename).isFile()) return false; | ||
return true; | ||
} | ||
function test () { | ||
@@ -12,17 +27,28 @@ return gm(dir + '/original.jpg'); | ||
var fs = require('fs'); | ||
function finish (filename) { | ||
return function (err) { | ||
if (err) throw new Error(err); | ||
fs.readdirSync(__dirname).forEach(function (file) { | ||
if (!/\.js$/.test(file)) return; | ||
if ('index.js' === file) return; | ||
--pending; | ||
process.stderr.write( | ||
'\u001B[30m' | ||
+ (new Array(total - pending)).join('√') | ||
+ '\u001B[0m' | ||
+ '\u001B[30m' | ||
+ (new Array(pending)).join('░') | ||
+ '\u001B[0m' | ||
+ '\r' | ||
); | ||
var filename = __dirname + '/' + file; | ||
if (pending) return; | ||
if (!fs.statSync(filename).isFile()) return; | ||
var leaks = gleak.detect(); | ||
assert.equal(0, leaks.length, "global leaks detected: " + leaks); | ||
console.error("\n\u001B[32mAll tests passed\u001B[0m") | ||
} | ||
} | ||
require(filename)(test(), dir, finish, gm); | ||
files.forEach(function (file) { | ||
var filename = __dirname + '/' + file; | ||
require(filename)(test(), dir, finish(filename), gm); | ||
}); | ||
function finish (err) { | ||
if (err) throw new Error(err); | ||
} |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Non-existent author
Supply chain riskThe package was published by an npm account that no longer exists.
Found 1 instance in 1 package
119599
145
2105
258
0
2
10
3