Permalink
Browse files

it's alive

  • Loading branch information...
rsms committed May 9, 2010
0 parents commit 4cbe8cd97dad338a962f050fadbc01449c8fb320
Showing with 321 additions and 0 deletions.
  1. +126 −0 README.md
  2. +170 −0 imagemagick.js
  3. +25 −0 test.js
126 README.md
@@ -0,0 +1,126 @@
+# node-imagemagick
+
+[Imagemagick](http://www.imagemagick.org/) module for [Node](http://nodejs.org/).
+
+## Example
+
+ var im = require('imagemagick');
+ im.readMetadata('kittens.jpg', function(err, metadata){
+ if (err) throw err;
+ sys.puts('Shot at '+metadata.exif.dateTimeOriginal);
+ })
+ // -> Shot at Tue, 06 Feb 2007 21:13:54 GMT
+
+## API
+
+### convert.path
+
+Path to the `convert` program. Defaults to `"convert"`.
+
+### identify.path
+
+Path to the `identify` program. Defaults to `"identify"`.
+
+### identify(path, callback(err, features))
+
+Identify file at `path` and return an object `features`.
+
+Example:
+
+ im.identify('kittens.jpg', function(err, features){
+ if (err) throw err
+ sys.p(features)
+ // { format: 'JPEG', width: 3904, height: 2622, depth: 8 }
+ })
+
+### identify(args, callback(err, output))
+
+Custom identification where `args` is an array of arguments. The result is returned as a raw string to `output`.
+
+Example:
+
+ im.identify(['-format', '%wx%h', 'kittens.jpg'], function(err, output){
+ if (err) throw err
+ sys.puts('dimension: '+output)
+ // dimension: 3904x2622
+ })
+
+### readMetadata(path, callback(err, metadata))
+
+Read metadata (i.e. exif) in `path` and return an object `metadata`. Modelled on top of `identify`.
+
+Example:
+
+ im.readMetadata('kittens.jpg', function(err, metadata){
+ if (err) throw err
+ sys.puts('Shot at '+metadata.exif.dateTimeOriginal)
+ })
+ // -> Shot at Tue, 06 Feb 2007 21:13:54 GMT
+
+
+### convert(args, callback(err, stdout, stderr))
+
+Raw interface to `convert` passing arguments in the array `args`.
+
+Example:
+
+ im.convert(['kittens.jpg', '-resize', '25x120', 'kittens-small.jpg'],
+ function(err, metadata){
+ if (err) throw err
+ sys.puts('stdout: '+sys.inspect(stdout));
+ })
+
+### resize(options, callback(err, stdout, stderr))
+
+Convenience function for resizing an image, modelled on top of `convert`.
+
+The `options` argument have the following default values:
+
+ {
+ srcPath: undefined,
+ dstPath: undefined,
+ quality: 0.8,
+ format: 'jpg',
+ progressive: false,
+ width: 0,
+ height: 0,
+ strip: true,
+ filter: 'Lagrange',
+ sharpening: 0.2,
+ customArgs: []
+ }
+
+srcPath, dstPath and (at least one of) width and height are required. The rest is optional.
+
+Example:
+
+ im.resize({
+ srcPath: 'kittens.jpg',
+ dstPath: 'kittens-small.jpg',
+ width: 256
+ }, function(err, stdout, stderr){
+ if (err) throw err
+ sys.puts('resized kittens.jpg to fit within 256x256px')
+ });
+
+## License (MIT)
+
+Copyright (c) 2010 Rasmus Andersson <http://hunch.se/>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
@@ -0,0 +1,170 @@
+var sys = require('sys'),
+ exec2 = require('child_process').execFile;
+
+exports.identify = function(pathOrArgs, callback) {
+ var isCustom = Array.isArray(pathOrArgs),
+ args = isCustom ? pathOrArgs : [pathOrArgs];
+ exec2(exports.identify.path, args, function(err, stdout, stderr) {
+ var result;
+ if (!err) {
+ if (isCustom) {
+ result = stdout;
+ } else {
+ var v = stdout.split(/ +/),
+ x = v[2].split(/x/);
+ result = {
+ format: v[1],
+ width: parseInt(x[0]),
+ height: parseInt(x[1]),
+ depth: parseInt(v[4]),
+ };
+ }
+ }
+ callback(err, result);
+ });
+}
+exports.identify.path = 'identify';
+
+function ExifDate(value) {
+ // YYYY:MM:DD HH:MM:SS -> Date(YYYY-MM-DD HH:MM:SS +0000)
+ value = value.split(/ /);
+ return new Date(value[0].replace(/:/g, '-')+' '+
+ value[1]+' +0000');
+}
+
+function exifKeyName(k) {
+ return k.replace(exifKeyName.RE, function(x){
+ if (x.length === 1) return x.toLowerCase();
+ else return x.substr(0,x.length-1).toLowerCase()+x.substr(x.length-1);
+ });
+}
+exifKeyName.RE = /^[A-Z]+/;
+
+var exifFieldConverters = {
+ // Numbers
+ bitsPerSample:Number, compression:Number, exifImageLength:Number,
+ exifImageWidth:Number, exifOffset:Number, exposureProgram:Number,
+ flash:Number, imageLength:Number, imageWidth:Number, isoSpeedRatings:Number,
+ jpegInterchangeFormat:Number, jpegInterchangeFormatLength:Number,
+ lightSource:Number, meteringMode:Number, orientation:Number,
+ photometricInterpretation:Number, planarConfiguration:Number,
+ resolutionUnit:Number, rowsPerStrip:Number, samplesPerPixel:Number,
+ sensingMethod:Number, stripByteCounts:Number, subSecTime:Number,
+ subSecTimeDigitized:Number, subSecTimeOriginal:Number, customRendered:Number,
+ exposureMode:Number, focalLengthIn35mmFilm:Number, gainControl:Number,
+ saturation:Number, sharpness:Number, subjectDistanceRange:Number,
+ subSecTime:Number, subSecTimeDigitized:Number, subSecTimeOriginal:Number,
+ whiteBalance:Number, sceneCaptureType:Number,
+
+ // Dates
+ dateTime:ExifDate, dateTimeDigitized:ExifDate, dateTimeOriginal:ExifDate
+};
+
+exports.readMetadata = function(path, callback) {
+ exec2(exports.identify.path, ['-format', '%[EXIF:*]', path], function(err, stdout, stderr) {
+ var meta = {};
+ if (!err) {
+ stdout.split(/\n/).forEach(function(line){
+ var eq_p = line.indexOf('=');
+ if (eq_p === -1) return;
+ var key = line.substr(0, eq_p).replace('/','-'),
+ value = line.substr(eq_p+1).trim(),
+ typekey = 'default';
+ var p = key.indexOf(':');
+ if (p !== -1) {
+ typekey = key.substr(0, p);
+ key = key.substr(p+1);
+ if (typekey === 'exif') {
+ key = exifKeyName(key);
+ var converter = exifFieldConverters[key];
+ if (converter) value = converter(value);
+ }
+ }
+ if (!(typekey in meta)) meta[typekey] = {key:value};
+ else meta[typekey][key] = value;
+ })
+ }
+ callback(err, meta);
+ });
+}
+
+exports.convert = function(args, callback) {
+ exec2(exports.convert.path, args, callback);
+}
+exports.convert.path = 'convert';
+
+exports.resize = function(options, callback) {
+ var args = exports.resizeArgs(options);
+ exports.convert(args, function(err, stdout, stderr) {
+ callback(err, stdout, stderr);
+ });
+}
+
+exports.resizeArgs = function(options) {
+ var opt = {
+ srcPath: undefined,
+ dstPath: undefined,
+ quality: 0.8,
+ format: 'jpg',
+ progressive: false,
+ colorspace: 'sRGB', // if null, no conversion is made
+ width: 0,
+ height: 0,
+ strip: true,
+ filter: 'Lagrange',
+ sharpening: 0.2,
+ customArgs: []
+ }
+
+ // check options
+ if (typeof options !== 'object')
+ throw new Error('first argument must be an object');
+ for (var k in opt) if (k in options) opt[k] = options[k];
+ if (!opt.srcPath || !opt.dstPath)
+ throw new Error('srcPath or dstPath is not set');
+ if (opt.width === 0 && opt.height === 0)
+ throw new Error('both width and height can not be 0 (zero)');
+
+ // build args
+ var args = [opt.srcPath];
+ if (opt.sharpening > 0) {
+ args = args.concat([
+ '-set', 'option:filter:blur', String(1.0-opt.sharpening)]);
+ }
+ if (opt.filter) {
+ args.push('-filter');
+ args.push(opt.filter);
+ }
+ if (opt.strip) {
+ args.push('-strip');
+ }
+ if (opt.width || opt.height) {
+ if (opt.height === 0) opt.height = opt.width;
+ else if (opt.width === 0) opt.width = opt.height;
+ args.push('-resize');
+ args.push(String(opt.width)+'x'+String(opt.height));
+ }
+ opt.format = opt.format.toLowerCase();
+ var isJPEG = (opt.format === 'jpg' || opt.format === 'jpeg');
+ if (isJPEG && opt.progressive) {
+ args.push('-interlace');
+ args.push('plane');
+ }
+ if (isJPEG || opt.format === 'png') {
+ args.push('-quality');
+ args.push(Math.round(opt.quality * 100.0).toString());
+ }
+ else if (opt.format === 'miff' || opt.format === 'mif') {
+ args.push('-quality');
+ args.push(Math.round(opt.quality * 9.0).toString());
+ }
+ if (opt.colorspace) {
+ args.push('-colorspace');
+ args.push(opt.colorspace);
+ }
+ if (Array.isArray(opt.customArgs) && opt.customArgs.length)
+ args = args.concat(opt.customArgs);
+ args.push(opt.dstPath);
+
+ return args;
+}
25 test.js
@@ -0,0 +1,25 @@
+var sys = require('sys'),
+ im = require('./imagemagick');
+
+var path = 'sample-images/jpeg5.jpg';
+
+im.identify(path, function(err, features){
+ if (err) throw err;
+ sys.puts('features: '+sys.inspect(features));
+})
+
+im.readMetadata(path, function(err, metadata){
+ if (err) throw err; sys.puts('metadata: '+sys.inspect(metadata));
+})
+
+im.resize({
+ srcPath: path,
+ dstPath: path+'.resized.jpg',
+ width: 256
+}, function(err, stdout, stderr){
+ if (err) throw err;
+ im.identify(['-format', '%b', path+'.resized.jpg'], function(err, r){
+ if (err) throw err;
+ sys.puts('size: '+r.substr(0,r.length-2)+' Bytes');
+ })
+})

0 comments on commit 4cbe8cd

Please sign in to comment.