Add withoutEnlargement option #36

This commit is contained in:
Lovell Fuller 2014-05-29 19:19:59 +01:00
parent 906311d403
commit 9a05684302
4 changed files with 93 additions and 30 deletions

View File

@ -175,6 +175,12 @@ Rotate the output image by either an explicit angle or auto-orient based on the
Use this method without `angle` to determine the angle from EXIF data. Use this method without `angle` to determine the angle from EXIF data.
### withoutEnlargement()
Do not enlarge the output image if the input image width *or* height are already less than the required dimensions.
This is equivalent to GraphicsMagick's `>` geometry option: "change the dimensions of the image only if its width or height exceeds the geometry specification".
### sharpen() ### sharpen()
Perform a mild sharpen of the resultant image. This typically reduces performance by 30%. Perform a mild sharpen of the resultant image. This typically reduces performance by 30%.

View File

@ -12,6 +12,7 @@ var Sharp = function(input) {
height: -1, height: -1,
canvas: 'c', canvas: 'c',
angle: 0, angle: 0,
withoutEnlargement: false,
sharpen: false, sharpen: false,
progressive: false, progressive: false,
sequentialRead: false, sequentialRead: false,
@ -20,9 +21,9 @@ var Sharp = function(input) {
output: '__jpeg' output: '__jpeg'
}; };
if (typeof input === 'string') { if (typeof input === 'string') {
this.options.inFile = input; this.options.fileIn = input;
} else if (typeof input ==='object' && input instanceof Buffer) { } else if (typeof input ==='object' && input instanceof Buffer) {
this.options.inBuffer = input; this.options.bufferIn = input;
} else { } else {
throw 'Unsupported input ' + typeof input; throw 'Unsupported input ' + typeof input;
} }
@ -65,6 +66,16 @@ Sharp.prototype.rotate = function(angle) {
return this; return this;
}; };
/*
Do not enlarge the output if the input width *or* height are already less than the required dimensions
This is equivalent to GraphicsMagick's ">" geometry option:
"change the dimensions of the image only if its width or height exceeds the geometry specification"
*/
Sharp.prototype.withoutEnlargement = function(withoutEnlargement) {
this.options.withoutEnlargement = (typeof withoutEnlargement === 'boolean') ? withoutEnlargement : true;
return this;
};
Sharp.prototype.sharpen = function(sharpen) { Sharp.prototype.sharpen = function(sharpen) {
this.options.sharpen = (typeof sharpen === 'boolean') ? sharpen : true; this.options.sharpen = (typeof sharpen === 'boolean') ? sharpen : true;
return this; return this;
@ -127,7 +138,7 @@ Sharp.prototype.toFile = function(output, callback) {
if (!output || output.length === 0) { if (!output || output.length === 0) {
callback('Invalid output'); callback('Invalid output');
} else { } else {
if (this.options.inFile === output) { if (this.options.fileIn === output) {
callback('Cannot use same file for input and output'); callback('Cannot use same file for input and output');
} else { } else {
this._sharp(output, callback); this._sharp(output, callback);
@ -157,18 +168,8 @@ Sharp.prototype.webp = function(callback) {
Sharp.prototype._sharp = function(output, callback) { Sharp.prototype._sharp = function(output, callback) {
sharp.resize( sharp.resize(
this.options.inFile, this.options,
this.options.inBuffer,
output, output,
this.options.width,
this.options.height,
this.options.canvas,
this.options.sharpen,
this.options.progressive,
this.options.sequentialRead,
this.options.quality,
this.options.compressionLevel,
this.options.angle,
callback callback
); );
return this; return this;

View File

@ -24,13 +24,21 @@ struct resize_baton {
VipsExtend extend; VipsExtend extend;
bool sharpen; bool sharpen;
bool progressive; bool progressive;
bool without_enlargement;
VipsAccess access_method; VipsAccess access_method;
int quality; int quality;
int compressionLevel; int compressionLevel;
int angle; int angle;
std::string err; std::string err;
resize_baton(): buffer_in_len(0), buffer_out_len(0), crop(false), max(false), sharpen(false), progressive(false) {} resize_baton():
buffer_in_len(0),
buffer_out_len(0),
crop(false),
max(false),
sharpen(false),
progressive(false),
without_enlargement(false) {}
}; };
typedef enum { typedef enum {
@ -213,6 +221,17 @@ class ResizeWorker : public NanAsyncWorker {
} }
double residual = static_cast<double>(shrink) / factor; double residual = static_cast<double>(shrink) / factor;
// Do not enlarge the output if the input width *or* height are already less than the required dimensions
if (baton->without_enlargement) {
if (inputWidth < baton->width || inputHeight < baton->height) {
factor = 1;
shrink = 1;
residual = 0;
baton->width = inputWidth;
baton->height = inputHeight;
}
}
// Try to use libjpeg shrink-on-load // Try to use libjpeg shrink-on-load
int shrink_on_load = 1; int shrink_on_load = 1;
if (inputImageType == JPEG) { if (inputImageType == JPEG) {
@ -403,20 +422,29 @@ class ResizeWorker : public NanAsyncWorker {
resize_baton* baton; resize_baton* baton;
}; };
/*
resize(options, output, callback)
*/
NAN_METHOD(resize) { NAN_METHOD(resize) {
NanScope(); NanScope();
// V8 objects are converted to non-V8 types held in the baton struct
resize_baton *baton = new resize_baton; resize_baton *baton = new resize_baton;
baton->file_in = *String::Utf8Value(args[0]->ToString()); Local<Object> options = args[0]->ToObject();
if (args[1]->IsObject()) {
Local<Object> buffer = args[1]->ToObject(); // Input filename
baton->file_in = *String::Utf8Value(options->Get(NanNew<String>("fileIn"))->ToString());
// Input Buffer object
if (options->Get(NanNew<String>("bufferIn"))->IsObject()) {
Local<Object> buffer = options->Get(NanNew<String>("bufferIn"))->ToObject();
baton->buffer_in_len = Buffer::Length(buffer); baton->buffer_in_len = Buffer::Length(buffer);
baton->buffer_in = Buffer::Data(buffer); baton->buffer_in = Buffer::Data(buffer);
} }
baton->file_out = *String::Utf8Value(args[2]->ToString()); // Output image dimensions
baton->width = args[3]->Int32Value(); baton->width = options->Get(NanNew<String>("width"))->Int32Value();
baton->height = args[4]->Int32Value(); baton->height = options->Get(NanNew<String>("height"))->Int32Value();
Local<String> canvas = args[5]->ToString(); // Canvas options
Local<String> canvas = options->Get(NanNew<String>("canvas"))->ToString();
if (canvas->Equals(NanNew<String>("c"))) { if (canvas->Equals(NanNew<String>("c"))) {
baton->crop = true; baton->crop = true;
} else if (canvas->Equals(NanNew<String>("w"))) { } else if (canvas->Equals(NanNew<String>("w"))) {
@ -426,15 +454,19 @@ NAN_METHOD(resize) {
} else if (canvas->Equals(NanNew<String>("m"))) { } else if (canvas->Equals(NanNew<String>("m"))) {
baton->max = true; baton->max = true;
} }
baton->sharpen = args[6]->BooleanValue(); // Other options
baton->progressive = args[7]->BooleanValue(); baton->sharpen = options->Get(NanNew<String>("sharpen"))->BooleanValue();
baton->access_method = args[8]->BooleanValue() ? VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_RANDOM; baton->progressive = options->Get(NanNew<String>("progressive"))->BooleanValue();
baton->quality = args[9]->Int32Value(); baton->without_enlargement = options->Get(NanNew<String>("withoutEnlargement"))->BooleanValue();
baton->compressionLevel = args[10]->Int32Value(); baton->access_method = options->Get(NanNew<String>("sequentialRead"))->BooleanValue() ? VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_RANDOM;
baton->angle = args[11]->Int32Value(); baton->quality = options->Get(NanNew<String>("quality"))->Int32Value();
baton->compressionLevel = options->Get(NanNew<String>("compressionLevel"))->Int32Value();
NanCallback *callback = new NanCallback(args[12].As<v8::Function>()); baton->angle = options->Get(NanNew<String>("angle"))->Int32Value();
// Output filename or __format for Buffer
baton->file_out = *String::Utf8Value(args[1]->ToString());
// Join queue for worker thread
NanCallback *callback = new NanCallback(args[2].As<v8::Function>());
NanAsyncQueueWorker(new ResizeWorker(callback, baton)); NanAsyncQueueWorker(new ResizeWorker(callback, baton));
NanReturnUndefined(); NanReturnUndefined();
} }

View File

@ -212,6 +212,30 @@ async.series([
} catch (e) {} } catch (e) {}
assert(!isValid); assert(!isValid);
done(); done();
},
// Do not enlarge the output if the input width is already less than the output width
function(done) {
sharp(inputJpg).resize(2800).withoutEnlargement().write(outputJpg, function(err) {
if (err) throw err;
imagemagick.identify(outputJpg, function(err, features) {
if (err) throw err;
assert.strictEqual(2725, features.width);
assert.strictEqual(2225, features.height);
done();
});
});
},
// Do not enlarge the output if the input height is already less than the output height
function(done) {
sharp(inputJpg).resize(null, 2300).withoutEnlargement().write(outputJpg, function(err) {
if (err) throw err;
imagemagick.identify(outputJpg, function(err, features) {
if (err) throw err;
assert.strictEqual(2725, features.width);
assert.strictEqual(2225, features.height);
done();
});
});
} }
]); ]);