diff --git a/index.js b/index.js index ede6c40ad..1eaedfdf8 100755 --- a/index.js +++ b/index.js @@ -19,6 +19,14 @@ var Sharp = function(input) { streamIn: false, sequentialRead: false, // resize options + topOffsetPre: -1, + leftOffsetPre: -1, + widthPre: -1, + heightPre: -1, + topOffsetPost: -1, + leftOffsetPost: -1, + widthPost: -1, + heightPost: -1, width: -1, height: -1, canvas: 'c', @@ -102,6 +110,15 @@ Sharp.prototype.crop = function(gravity) { return this; }; +Sharp.prototype.extract = function(topOffset, leftOffset, width, height) { + var suffix = this.options.width === -1 && this.options.height === -1 ? 'Pre' : 'Post'; + var values = arguments; + ['topOffset', 'leftOffset', 'width', 'height'].forEach(function(name, index) { + this.options[name + suffix] = values[index]; + }.bind(this)); + return this; +}; + /* Deprecated embed* methods, to be removed in v0.8.0 */ @@ -239,8 +256,8 @@ Sharp.prototype.compressionLevel = function(compressionLevel) { }; Sharp.prototype.withMetadata = function(withMetadata) { - this.options.withMetadata = (typeof withMetadata === 'boolean') ? withMetadata : true; - return this; + this.options.withMetadata = (typeof withMetadata === 'boolean') ? withMetadata : true; + return this; }; Sharp.prototype.resize = function(width, height) { diff --git a/package.json b/package.json index 414326f3a..6f15a77d3 100755 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ "Juliano Julio ", "Daniel Gasienica ", "Julian Walker ", - "Amit Pitaru " + "Amit Pitaru ", + "Brandon Aaron " ], "description": "High performance Node.js module to resize JPEG, PNG and WebP images using the libvips library", "scripts": { @@ -30,6 +31,7 @@ "thumbnail", "sharpen", "crop", + "extract", "embed", "libvips", "vips", diff --git a/src/sharp.cc b/src/sharp.cc index 536205327..acecef4ba 100755 --- a/src/sharp.cc +++ b/src/sharp.cc @@ -25,6 +25,14 @@ struct resize_baton { std::string output_format; void* buffer_out; size_t buffer_out_len; + int topOffsetPre; + int leftOffsetPre; + int widthPre; + int heightPre; + int topOffsetPost; + int leftOffsetPost; + int widthPost; + int heightPost; int width; int height; Canvas canvas; @@ -48,6 +56,8 @@ struct resize_baton { buffer_in_len(0), output_format(""), buffer_out_len(0), + topOffsetPre(-1), + topOffsetPost(-1), canvas(CROP), gravity(0), background{0.0, 0.0, 0.0, 255.0}, @@ -417,6 +427,17 @@ class ResizeWorker : public NanAsyncWorker { return resize_error(baton, hook); } + // Pre extraction + if (baton->topOffsetPre != -1) { + VipsImage *extractedPre = vips_image_new(); + vips_object_local(hook, extractedPre); + if (vips_extract_area(image, &extractedPre, baton->leftOffsetPre, baton->topOffsetPre, baton->widthPre, baton->heightPre, NULL)) { + return resize_error(baton, hook); + } + g_object_unref(image); + image = extractedPre; + } + // Get input image width and height int inputWidth = image->Xsize; int inputHeight = image->Ysize; @@ -691,6 +712,17 @@ class ResizeWorker : public NanAsyncWorker { image = canvased; } + // Post extraction + if (baton->topOffsetPost != -1) { + VipsImage *extractedPost = vips_image_new(); + vips_object_local(hook, extractedPost); + if (vips_extract_area(image, &extractedPost, baton->leftOffsetPost, baton->topOffsetPost, baton->widthPost, baton->heightPost, NULL)) { + return resize_error(baton, hook); + } + g_object_unref(image); + image = extractedPost; + } + // Mild sharpen if (baton->sharpen) { VipsImage *sharpened = vips_image_new(); @@ -818,11 +850,21 @@ class ResizeWorker : public NanAsyncWorker { // Error argv[0] = NanNew(baton->err.data(), baton->err.size()); } else { + int width = baton->width; + int height = baton->height; + if (baton->topOffsetPre != -1 && (baton->width == -1 || baton->height == -1)) { + width = baton->widthPre; + height = baton->heightPre; + } + if (baton->topOffsetPost != -1) { + width = baton->widthPost; + height = baton->heightPost; + } // Info Object Local info = NanNew(); info->Set(NanNew("format"), NanNew(baton->output_format)); - info->Set(NanNew("width"), NanNew(baton->width)); - info->Set(NanNew("height"), NanNew(baton->height)); + info->Set(NanNew("width"), NanNew(width)); + info->Set(NanNew("height"), NanNew(height)); if (baton->buffer_out_len > 0) { // Buffer @@ -847,6 +889,7 @@ class ResizeWorker : public NanAsyncWorker { resize_baton* baton; }; + /* resize(options, output, callback) */ @@ -866,6 +909,15 @@ NAN_METHOD(resize) { baton->buffer_in_len = Buffer::Length(buffer); baton->buffer_in = Buffer::Data(buffer); } + // Extract image options + baton->topOffsetPre = options->Get(NanNew("topOffsetPre"))->Int32Value(); + baton->leftOffsetPre = options->Get(NanNew("leftOffsetPre"))->Int32Value(); + baton->widthPre = options->Get(NanNew("widthPre"))->Int32Value(); + baton->heightPre = options->Get(NanNew("heightPre"))->Int32Value(); + baton->topOffsetPost = options->Get(NanNew("topOffsetPost"))->Int32Value(); + baton->leftOffsetPost = options->Get(NanNew("leftOffsetPost"))->Int32Value(); + baton->widthPost = options->Get(NanNew("widthPost"))->Int32Value(); + baton->heightPost = options->Get(NanNew("heightPost"))->Int32Value(); // Output image dimensions baton->width = options->Get(NanNew("width"))->Int32Value(); baton->height = options->Get(NanNew("height"))->Int32Value(); diff --git a/tests/unit.js b/tests/unit.js index 04732a006..8c71ccf1a 100755 --- a/tests/unit.js +++ b/tests/unit.js @@ -563,6 +563,69 @@ async.series([ done(); }); }, + // Extract jpg + function(done) { + sharp(inputJpg).extract(2,2,20,20).toFile(path.join(fixturesPath, 'output.extract.jpg'), function(err, info) { + if (err) throw err; + assert.strictEqual(20, info.width); + assert.strictEqual(20, info.height); + done(); + }); + }, + // Extract png + function(done) { + sharp(inputPng).extract(300,200,400,200).toFile(path.join(fixturesPath, 'output.extract.png'), function(err, info) { + if (err) throw err; + assert.strictEqual(400, info.width); + assert.strictEqual(200, info.height); + done(); + }); + }, + // Extract webp + function(done) { + sharp(inputWebP).extract(50, 100, 125, 200).toFile(path.join(fixturesPath, 'output.extract.webp'), function(err, info) { + if (err) throw err; + assert.strictEqual(125, info.width); + assert.strictEqual(200, info.height); + done(); + }); + }, + // Extract tiff + function(done) { + sharp(inputTiff).extract(63, 34, 341, 529).toFile(path.join(fixturesPath, 'output.extract.tiff'), function(err, info) { + if (err) throw err; + assert.strictEqual(341, info.width); + assert.strictEqual(529, info.height); + done(); + }); + }, + // Extract before resize + function(done) { + sharp(inputJpg).extract(10, 10, 10, 500, 500).resize(100, 100).toFile(path.join(fixturesPath, 'output.extract.resize.jpg'), function(err, info) { + if (err) throw err; + assert.strictEqual(100, info.width); + assert.strictEqual(100, info.height); + done(); + }); + }, + // Extract after resize and crop + function(done) { + sharp(inputJpg).resize(500, 500).crop(sharp.gravity.north).extract(10, 10, 100, 100).toFile(path.join(fixturesPath, 'output.resize.crop.extract.jpg'), function(err, info) { + if (err) throw err; + assert.strictEqual(100, info.width); + assert.strictEqual(100, info.height); + done(); + }); + }, + // Extract before and after resize and crop + function(done) { + sharp(inputJpg).extract(0, 0, 700, 700).resize(500, 500).crop(sharp.gravity.north).extract(10, 10, 100, 100).toFile(path.join(fixturesPath, 'output.extract.resize.crop.extract.jpg'), function(err, info) { + if (err) throw err; + assert.strictEqual(100, info.width); + assert.strictEqual(100, info.height); + done(); + }); + }, // Keeps Metadata after a resize function(done) { sharp(inputJpgWithExif).resize(320, 240).withMetadata().toBuffer(function(err, buffer) {