diff --git a/docs/api.md b/docs/api.md index bbe7d387..83b177dc 100644 --- a/docs/api.md +++ b/docs/api.md @@ -78,7 +78,7 @@ to share a single input Stream. ```javascript var pipeline = sharp().rotate(); pipeline.clone().resize(800, 600).pipe(firstWritableStream); -pipeline.clone().extract(20, 20, 100, 100).pipe(secondWritableStream); +pipeline.clone().extract({ left: 20, top: 20, width: 100, height: 100 }).pipe(secondWritableStream); readableStream.pipe(pipeline); // firstWritableStream receives auto-rotated, resized readableStream // secondWritableStream receives auto-rotated, extracted region of readableStream @@ -230,11 +230,11 @@ sharp(inputBuffer) ### Operations -#### extract(top, left, width, height) +#### extract({ left: left, top: top, width: width, height: height }) Extract a region of the image. Can be used with or without a `resize` operation. -`top` and `left` are the offset, in pixels, from the top-left corner. +`left` and `top` are the offset, in pixels, from the top-left corner. `width` and `height` are the dimensions of the extracted image. @@ -242,7 +242,7 @@ Use `extract` before `resize` for pre-resize extraction. Use `extract` after `re ```javascript sharp(input) - .extract(top, left, width, height) + .extract({ left: left, top: top, width: width, height: height }) .toFile(output, function(err) { // Extract a region of the input image, saving in the same format. }); @@ -250,9 +250,9 @@ sharp(input) ```javascript sharp(input) - .extract(topOffsetPre, leftOffsetPre, widthPre, heightPre) + .extract({ left: leftOffsetPre, top: topOffsetPre, width: widthPre, height: heightPre }) .resize(width, height) - .extract(topOffsetPost, leftOffsetPost, widthPost, heightPost) + .extract({ left: leftOffsetPost, top: topOffsetPost, width: widthPost, height: heightPost }) .toFile(output, function(err) { // Extract a region, resize, then extract from the resized image }); diff --git a/index.js b/index.js index 2fe57c1f..76a6fb18 100644 --- a/index.js +++ b/index.js @@ -169,17 +169,25 @@ Sharp.prototype.crop = function(gravity) { return this; }; -Sharp.prototype.extract = function(topOffset, leftOffset, width, height) { - /*jslint unused: false */ +Sharp.prototype.extract = function(options) { + if (!options || typeof options !== 'object') { + // Legacy extract(top,left,width,height) syntax + options = { + left: arguments[1], + top: arguments[0], + width: arguments[2], + height: arguments[3] + }; + } var suffix = this.options.width === -1 && this.options.height === -1 ? 'Pre' : 'Post'; - var values = arguments; - ['topOffset', 'leftOffset', 'width', 'height'].forEach(function(name, index) { - if (typeof values[index] === 'number' && !Number.isNaN(values[index]) && (values[index] % 1 === 0) && values[index] >= 0) { - this.options[name + suffix] = values[index]; + ['left', 'top', 'width', 'height'].forEach(function (name) { + var value = options[name]; + if (typeof value === 'number' && !Number.isNaN(value) && value % 1 === 0 && value >= 0) { + this.options[name + (name === 'left' || name === 'top' ? 'Offset' : '') + suffix] = value; } else { - throw new Error('Non-integer value for ' + name + ' of ' + values[index]); + throw new Error('Non-integer value for ' + name + ' of ' + value); } - }.bind(this)); + }, this); // Ensure existing rotation occurs before pre-resize extraction if (suffix === 'Pre' && this.options.angle !== 0) { this.options.rotateBeforePreExtract = true; diff --git a/test/unit/extract.js b/test/unit/extract.js index 4497e048..fda4d4a5 100755 --- a/test/unit/extract.js +++ b/test/unit/extract.js @@ -8,10 +8,33 @@ var fixtures = require('../fixtures'); sharp.cache(0); describe('Partial image extraction', function() { + describe('using the legacy extract(top,left,width,height) syntax', function () { + it('JPEG', function(done) { + sharp(fixtures.inputJpg) + .extract(2, 2, 20, 20) + .toBuffer(function(err, data, info) { + if (err) throw err; + assert.strictEqual(20, info.width); + assert.strictEqual(20, info.height); + fixtures.assertSimilar(fixtures.expected('extract.jpg'), data, done); + }); + }); + + it('PNG', function(done) { + sharp(fixtures.inputPng) + .extract(300, 200, 400, 200) + .toBuffer(function(err, data, info) { + if (err) throw err; + assert.strictEqual(400, info.width); + assert.strictEqual(200, info.height); + fixtures.assertSimilar(fixtures.expected('extract.png'), data, done); + }); + }); + }); it('JPEG', function(done) { sharp(fixtures.inputJpg) - .extract(2, 2, 20, 20) + .extract({ left: 2, top: 2, width: 20, height: 20 }) .toBuffer(function(err, data, info) { if (err) throw err; assert.strictEqual(20, info.width); @@ -22,7 +45,7 @@ describe('Partial image extraction', function() { it('PNG', function(done) { sharp(fixtures.inputPng) - .extract(300, 200, 400, 200) + .extract({ left: 200, top: 300, width: 400, height: 200 }) .toBuffer(function(err, data, info) { if (err) throw err; assert.strictEqual(400, info.width); @@ -34,7 +57,7 @@ describe('Partial image extraction', function() { if (sharp.format.webp.output.file) { it('WebP', function(done) { sharp(fixtures.inputWebP) - .extract(50, 100, 125, 200) + .extract({ left: 100, top: 50, width: 125, height: 200 }) .toBuffer(function(err, data, info) { if (err) throw err; assert.strictEqual(125, info.width); @@ -46,7 +69,7 @@ describe('Partial image extraction', function() { it('TIFF', function(done) { sharp(fixtures.inputTiff) - .extract(63, 34, 341, 529) + .extract({ left: 34, top: 63, width: 341, height: 529 }) .jpeg() .toBuffer(function(err, data, info) { if (err) throw err; @@ -58,7 +81,7 @@ describe('Partial image extraction', function() { it('Before resize', function(done) { sharp(fixtures.inputJpg) - .extract(10, 10, 10, 500, 500) + .extract({ left: 10, top: 10, width: 10, height: 500 }) .resize(100, 100) .toBuffer(function(err, data, info) { if (err) throw err; @@ -72,7 +95,7 @@ describe('Partial image extraction', function() { sharp(fixtures.inputJpg) .resize(500, 500) .crop(sharp.gravity.north) - .extract(10, 10, 100, 100) + .extract({ left: 10, top: 10, width: 100, height: 100 }) .toBuffer(function(err, data, info) { if (err) throw err; assert.strictEqual(100, info.width); @@ -83,10 +106,10 @@ describe('Partial image extraction', function() { it('Before and after resize and crop', function(done) { sharp(fixtures.inputJpg) - .extract(0, 0, 700, 700) + .extract({ left: 0, top: 0, width: 700, height: 700 }) .resize(500, 500) .crop(sharp.gravity.north) - .extract(10, 10, 100, 100) + .extract({ left: 10, top: 10, width: 100, height: 100 }) .toBuffer(function(err, data, info) { if (err) throw err; assert.strictEqual(100, info.width); @@ -97,7 +120,7 @@ describe('Partial image extraction', function() { it('Extract then rotate', function(done) { sharp(fixtures.inputPngWithGreyAlpha) - .extract(10, 20, 380, 280) + .extract({ left: 20, top: 10, width: 380, height: 280 }) .rotate(90) .toBuffer(function(err, data, info) { if (err) throw err; @@ -110,7 +133,7 @@ describe('Partial image extraction', function() { it('Rotate then extract', function(done) { sharp(fixtures.inputPngWithGreyAlpha) .rotate(90) - .extract(10, 20, 280, 380) + .extract({ left: 20, top: 10, width: 280, height: 380 }) .toBuffer(function(err, data, info) { if (err) throw err; assert.strictEqual(280, info.width); @@ -120,6 +143,31 @@ describe('Partial image extraction', function() { }); describe('Invalid parameters', function() { + describe('using the legacy extract(top,left,width,height) syntax', function () { + it('String top', function() { + assert.throws(function() { + sharp(fixtures.inputJpg).extract('spoons', 10, 10, 10); + }); + }); + + it('Non-integral left', function() { + assert.throws(function() { + sharp(fixtures.inputJpg).extract(10, 10.2, 10, 10); + }); + }); + + it('Negative width - negative', function() { + assert.throws(function() { + sharp(fixtures.inputJpg).extract(10, 10, -10, 10); + }); + }); + + it('Null height', function() { + assert.throws(function() { + sharp(fixtures.inputJpg).extract(10, 10, 10, null); + }); + }); + }); it('Undefined', function() { assert.throws(function() { @@ -129,27 +177,26 @@ describe('Partial image extraction', function() { it('String top', function() { assert.throws(function() { - sharp(fixtures.inputJpg).extract('spoons', 10, 10, 10); + sharp(fixtures.inputJpg).extract({ left: 10, top: 'spoons', width: 10, height: 10 }); }); }); it('Non-integral left', function() { assert.throws(function() { - sharp(fixtures.inputJpg).extract(10, 10.2, 10, 10); + sharp(fixtures.inputJpg).extract({ left: 10.2, top: 10, width: 10, height: 10 }); }); }); it('Negative width - negative', function() { assert.throws(function() { - sharp(fixtures.inputJpg).extract(10, 10, -10, 10); + sharp(fixtures.inputJpg).extract({ left: 10, top: 10, width: -10, height: 10 }); }); }); it('Null height', function() { assert.throws(function() { - sharp(fixtures.inputJpg).extract(10, 10, 10, null); + sharp(fixtures.inputJpg).extract({ left: 10, top: 10, width: 10, height: null }); }); }); - }); });