Skip to content

Commit 8fcf3f8

Browse files
committed
refactor identify and getters commands
identify getters now only use -verbose when necessary. -verbose is quite slow when run on large (3MB) images. The -format option is a good replacement for most cases. This change causes a slight behavior change since previously all getters used -verbose. The impacted getters are: format() depth() size() color() filesize() These getters now use the -format option which is much faster but may produce different results than previous releases. If you need the old (slow) behavior for these properties, first use the full `identify()` method and read the value afterward. var image = gm(img); image.identify(function (err) { console.log(image.data.format); }) closes #83
1 parent c94b107 commit 8fcf3f8

File tree

8 files changed

+168
-63
lines changed

8 files changed

+168
-63
lines changed

lib/getters.js

Lines changed: 115 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -8,88 +8,129 @@ module.exports = function (gm) {
88

99
var proto = gm.prototype;
1010

11+
/**
12+
* `identify` states
13+
*/
14+
15+
const IDENTIFYING = 1;
16+
const IDENTIFIED = 2;
17+
18+
/**
19+
* Map getter functions to output names.
20+
*
21+
* - format: specifying the -format argument (see man gm)
22+
* - verbose: use -verbose instead of -format (only if necessary b/c its slow)
23+
* - helper: use the conversion helper
24+
*/
25+
1126
var map = {
12-
'size': 'size'
13-
, 'format': 'format'
14-
, 'depth': 'depth'
15-
, 'color': 'color'
16-
, 'orientation': 'Orientation'
17-
, 'res': 'Resolution'
18-
, 'filesize': 'Filesize'
27+
'format': { key: 'format', format: '%m' }
28+
, 'depth': { key: 'depth', format: '%q' }
29+
, 'filesize': { key: 'Filesize', format: '%b' }
30+
, 'size': { key: 'size', format: '%wx%h', helper: 'Geometry' }
31+
, 'color': { key: 'color', format: '%k', helper: 'Colors' }
32+
, 'orientation': { key: 'Orientation', verbose: true }
33+
, 'res': { key: 'Resolution', verbose: true }
1934
}
2035

36+
/**
37+
* Getter functions
38+
*/
39+
2140
Object.keys(map).forEach(function (getter) {
2241
proto[getter] = function (opts, callback) {
2342
if (!callback) callback = opts, opts = {};
43+
if (!callback) return this;
2444

25-
var key = map[getter]
45+
var val = map[getter]
46+
, key = val.key
2647
, self = this;
2748

2849
if (self.data[key]) {
2950
callback.call(self, null, self.data[key]);
3051
return self;
3152
}
3253

33-
self.identify(opts, function (err, stdout, stderr, cmd) {
34-
if (err) {
35-
return callback.call(self, err, stdout, stderr, cmd);
54+
self.bufferStream = !! opts.bufferStream;
55+
56+
self.on(getter, createListener(callback));
57+
58+
if (val.verbose) {
59+
self.identify(opts, function (err, stdout, stderr, cmd) {
60+
self.emit(getter, err, self.data[key], stdout, stderr, cmd);
61+
});
62+
return self;
63+
}
64+
65+
var args = makeArgs(self, val);
66+
self._exec(args, function (err, stdout, stderr, cmd) {
67+
var result = (stdout||'').trim();
68+
69+
if (val.helper in helper) {
70+
helper[val.helper](self.data, result);
71+
} else {
72+
self.data[key] = result;
3673
}
3774

38-
callback.call(self, null, self.data[key]);
75+
self.emit(getter, err, self.data[key], stdout, stderr, cmd);
3976
});
4077

4178
return self;
4279
}
4380
});
4481

82+
/**
83+
* identify command
84+
*
85+
* Overwrites all internal data with the parsed output
86+
* which is more accurate than the fast shortcut
87+
* getters.
88+
*/
89+
4590
proto.identify = function identify (opts, callback) {
4691
if (!callback) callback = opts, opts = {};
4792
if (!callback) return this;
4893

4994
var self = this;
50-
self.bufferStream = !! opts.bufferStream;
5195

52-
if (self._identifying) {
53-
self._queue.push(callback);
96+
if (IDENTIFIED === self._identifyState) {
97+
callback.call(self, null, self.data);
5498
return self;
5599
}
56100

57-
if (Object.keys(self.data).length) {
58-
callback.call(self, null, self.data);
101+
self.on('identify', createListener(callback));
102+
103+
if (IDENTIFYING === self._identifyState) {
59104
return self;
60105
}
61106

62-
self._queue = [callback];
63-
self._identifying = true;
107+
self._identifyState = IDENTIFYING;
108+
self.bufferStream = !! opts.bufferStream;
64109

65-
var args = [
66-
'identify'
67-
, '-ping'
68-
, '-verbose'
69-
, self.sourceStream ? '-' : self.source
70-
];
110+
var args = makeArgs(self, { verbose: true });
71111

72112
self._exec(args, function (err, stdout, stderr, cmd) {
73113
if (err) {
74-
return callback.call(self, err, self.data, stdout, stderr, cmd);
114+
self.emit('identify', err, self.data, stdout, stderr, cmd);
115+
return;
75116
}
76117

77-
// parse response
78-
var err = parse(stdout, self);
118+
err = parse(stdout, self);
79119

80-
var i = self._queue.length;
81-
while (i--) {
82-
self._queue[i].call(self, err, self.data);
120+
if (err) {
121+
self.emit('identify', err, self.data, stdout, stderr, cmd);
122+
return;
83123
}
84124

85-
self._identifying = false;
125+
self.emit('identify', null, self.data);
126+
self._identifyState = IDENTIFIED;
86127
});
87128

88129
return self;
89130
}
90131

91132
/**
92-
* Parse Identify response frames
133+
* Parses `identify` responses.
93134
*
94135
* @param {String} stdout
95136
* @param {Gm} self
@@ -162,7 +203,47 @@ module.exports = function (gm) {
162203
}
163204

164205
/**
165-
* helpers.
206+
* Create an argument array for the identify command.
207+
*
208+
* @param {gm} self
209+
* @param {Object} val
210+
* @return {Array}
211+
*/
212+
213+
function makeArgs (self, val) {
214+
var args = [
215+
'identify'
216+
, '-ping'
217+
];
218+
219+
if (val.format) {
220+
args.push('-format', val.format);
221+
}
222+
223+
if (val.verbose) {
224+
args.push('-verbose');
225+
}
226+
227+
args.push(self.sourceStream ? '-' : self.source);
228+
return args;
229+
}
230+
231+
/**
232+
* Creates a listener function which calls `callback`
233+
* with proper scope and arguments.
234+
*
235+
* @param {Function} callback
236+
* @return {Function}
237+
*/
238+
239+
function createListener (callback) {
240+
return function (err, data, stdout, stderr, cmd) {
241+
return callback.call(this, err, data, stdout, stderr, cmd);
242+
}
243+
}
244+
245+
/**
246+
* identify -verbose helpers
166247
*/
167248

168249
var helper = gm.identifyHelpers = {};

test/getterColor.js

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,9 @@ module.exports = function (_, dir, finish, gm) {
88
gm(dir + '/blue.gif').color(function (err, color) {
99
if (err) return finish(err);
1010

11-
assert.equal(2, color)
11+
assert.equal(1, color)
1212
assert.equal(this.data.color, color)
1313

14-
var blueWorks = this.data.Colors['0'] == '( 0, 0,255)\t blue';
15-
var blackWorks = this.data.Colors['1'] == '( 0, 0, 0)\t black';
16-
17-
if (!blueWorks) {
18-
blueWorks = this.data.Colors['1'] == '( 0, 0,255)\t blue';
19-
blackWorks = this.data.Colors['0'] == '( 0, 0, 0)\t black';
20-
}
21-
22-
assert.ok(blueWorks);
23-
assert.ok(blackWorks);
24-
2514
finish();
2615

2716
});

test/getterDepth.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ module.exports = function (gm, dir, finish) {
88
gm
99
.depth(function getterdepth (err, depth) {
1010
if (err) return finish(err);
11-
assert.equal(8, depth);
12-
assert.equal(8, this.data.depth);
13-
14-
if (gm._options.imageMagick)
15-
assert.equal('8-bit', this.data.Depth);
16-
else
17-
assert.equal('8 bits-per-pixel component', this.data.Depth);
11+
if (this._options.imageMagick) {
12+
assert.equal(16, depth);
13+
assert.equal(16, this.data.depth);
14+
} else {
15+
assert.equal(8, depth);
16+
assert.equal(8, this.data.depth);
17+
}
1818
finish();
1919
});
2020
}

test/getterFilesize.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ module.exports = function (gm, dir, finish) {
1010
if (err) return finish(err);
1111

1212
if (this._options.imageMagick) {
13-
assert.ok(/7.79KB/.test(size));
13+
assert.equal('7792B', size, size);
1414
} else {
1515
assert.equal(size, '7.6K');
1616
}
@@ -27,7 +27,7 @@ module.exports = function (gm, dir, finish) {
2727
if (err) return finish(err);
2828

2929
if (this._options.imageMagick) {
30-
assert.ok(/7.79KB/.test(size));
30+
assert.equal('7792B', size, size);
3131
} else {
3232
assert.equal(size, '7.6K');
3333
}

test/getterFormat.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ module.exports = function (gm, dir, finish) {
99
if (err) return finish(err);
1010

1111
assert.equal(format, 'JPEG');
12-
assert.equal(this.data.Format, 'JPEG (Joint Photographic Experts Group JFIF format)');
12+
assert.equal(gm.data.format, 'JPEG');
1313

1414
finish();
1515
});

test/getterIdentify.js

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ module.exports = function (_, dir, finish, gm) {
2626
assert.equal(d['Rendering intent'], 'Perceptual');
2727
assert.equal(d.Properties['date:create'], '2012-09-15T17:33:20-07:00');
2828
assert.equal(d.Properties['exif:DateTimeDigitized'], '2011:07:01 11:23:16');
29+
assert.equal(d.Format, 'JPEG (Joint Photographic Experts Group JFIF format)');
2930

3031
} else {
3132
assert.equal(d.Orientation, 'TopLeft');
@@ -38,8 +39,43 @@ module.exports = function (_, dir, finish, gm) {
3839
assert.equal(ex['GPS Info'], 558);
3940
assert.equal(ex['GPS Longitude'], '80/1,4970/100,0/1');
4041
assert.equal(ex['GPS Time Stamp'], '15/1,23/1,945/1');
42+
assert.equal(d.Format, 'JPEG (Joint Photographic Experts Group JFIF format)');
43+
assert.equal(d['Geometry'], '430x488');
4144
}
4245

43-
finish();
46+
gif();
4447
});
48+
49+
function gif () {
50+
var test = gm(dir + '/blue.gif');
51+
if (im) test.options({ imageMagick: true });
52+
test.identify(function (err) {
53+
if (err) return finish(err);
54+
55+
if (im) {
56+
assert.equal(1, this.data.color);
57+
58+
var blueWorks = this.data.Colormap['0'] == '( 0, 0,255) #0000FF blue';
59+
var blackWorks = this.data.Colormap['1'] == '( 0, 0, 0) #000000 black';
60+
assert.ok(blueWorks);
61+
assert.ok(blackWorks);
62+
63+
} else {
64+
assert.equal(2, this.data.color);
65+
66+
var blueWorks = this.data.Colors['0'] == '( 0, 0,255)\t blue';
67+
var blackWorks = this.data.Colors['1'] == '( 0, 0, 0)\t black';
68+
69+
if (!blueWorks) {
70+
blueWorks = this.data.Colors['1'] == '( 0, 0,255)\t blue';
71+
blackWorks = this.data.Colors['0'] == '( 0, 0, 0)\t black';
72+
}
73+
74+
assert.ok(blueWorks);
75+
assert.ok(blackWorks);
76+
}
77+
78+
finish();
79+
});
80+
}
4581
}

test/getterRes.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ module.exports = function (gm, dir, finish) {
88
gm
99
.res(function getterres (err, res) {
1010
if (err) return finish(err);
11+
if (gm._options.imageMagick) {
12+
assert.equal('72x72', res);
13+
} else {
14+
assert.equal('72x72 pixels/inch', res);
15+
}
1116
assert.equal(res, this.data.Resolution)
1217
finish();
1318
});

test/getterSize.js

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,6 @@ module.exports = function (gm, dir, finish, GM) {
1212
assert.equal(size.width, 460);
1313
assert.equal(size.height, 155);
1414

15-
if (gm._options.imageMagick) {
16-
assert.equal(this.data.Geometry, '460x155+0+0');
17-
} else {
18-
assert.equal(this.data.Geometry, '460x155');
19-
}
20-
2115
GM(dir + '/identifyParseErr.jpg').size(function (err) {
2216
if (err) return finish(err);
2317
finish();

0 commit comments

Comments
 (0)