Add quality and compressionLevel options for output image. #24

This commit is contained in:
Lovell Fuller 2014-05-10 19:45:12 +01:00
parent 8322b442e0
commit f8338e7c4f
4 changed files with 264 additions and 211 deletions

View File

@ -94,11 +94,11 @@ sharp('input.jpg').resize(null, 200).progressive().toBuffer(function(err, buffer
``` ```
```javascript ```javascript
sharp('input.png').resize(300).sharpen().webp(function(err, buffer) { sharp('input.png').resize(300).sharpen().quality(90).webp(function(err, buffer) {
if (err) { if (err) {
throw err; throw err;
} }
// buffer contains sharpened WebP image data (converted from PNG), 300 pixels wide // buffer contains 300 pixels wide, sharpened, 90% quality WebP image data
}); });
``` ```
@ -159,6 +159,18 @@ Perform a mild sharpen of the resultant image. This typically reduces performanc
Use progressive (interlace) scan for JPEG and PNG output. This typically reduces compression performance by 30% but results in an image that can be rendered sooner when decompressed. Use progressive (interlace) scan for JPEG and PNG output. This typically reduces compression performance by 30% but results in an image that can be rendered sooner when decompressed.
### quality(quality)
The output quality to use for lossy JPEG, WebP and TIFF output formats. The default quality is `80`.
`quality` is a Number between 1 and 100.
### compressionLevel(compressionLevel)
An advanced setting for the _zlib_ compression level of the lossless PNG output format. The default level is `6`.
`compressionLevel` is a Number between -1 and 9.
### sequentialRead() ### sequentialRead()
An advanced setting that switches the libvips access method to `VIPS_ACCESS_SEQUENTIAL`. This will reduce memory usage and can improve performance on some systems. An advanced setting that switches the libvips access method to `VIPS_ACCESS_SEQUENTIAL`. This will reduce memory usage and can improve performance on some systems.

278
index.js
View File

@ -1,128 +1,150 @@
/*jslint node: true */ /*jslint node: true */
'use strict'; 'use strict';
var sharp = require('./build/Release/sharp'); var sharp = require('./build/Release/sharp');
var Sharp = function(input) { var Sharp = function(input) {
if (!(this instanceof Sharp)) { if (!(this instanceof Sharp)) {
return new Sharp(input); return new Sharp(input);
} }
this.options = { this.options = {
width: -1, width: -1,
height: -1, height: -1,
canvas: 'c', canvas: 'c',
sharpen: false, sharpen: false,
progressive: false, progressive: false,
sequentialRead: false, sequentialRead: false,
output: '__jpeg' quality: 80,
}; compressionLevel: 6,
if (typeof input === 'string') { output: '__jpeg'
this.options.inFile = input; };
} else if (typeof input ==='object' && input instanceof Buffer) { if (typeof input === 'string') {
this.options.inBuffer = input; this.options.inFile = input;
} else { } else if (typeof input ==='object' && input instanceof Buffer) {
throw 'Unsupported input ' + typeof input; this.options.inBuffer = input;
} } else {
return this; throw 'Unsupported input ' + typeof input;
}; }
module.exports = Sharp; return this;
};
Sharp.prototype.crop = function() { module.exports = Sharp;
this.options.canvas = 'c';
return this; Sharp.prototype.crop = function() {
}; this.options.canvas = 'c';
return this;
Sharp.prototype.embedWhite = function() { };
this.options.canvas = 'w';
return this; Sharp.prototype.embedWhite = function() {
}; this.options.canvas = 'w';
return this;
Sharp.prototype.embedBlack = function() { };
this.options.canvas = 'b';
return this; Sharp.prototype.embedBlack = function() {
}; this.options.canvas = 'b';
return this;
Sharp.prototype.sharpen = function(sharpen) { };
this.options.sharpen = (typeof sharpen === 'boolean') ? sharpen : true;
return this; Sharp.prototype.sharpen = function(sharpen) {
}; this.options.sharpen = (typeof sharpen === 'boolean') ? sharpen : true;
return this;
Sharp.prototype.progressive = function(progressive) { };
this.options.progressive = (typeof progressive === 'boolean') ? progressive : true;
return this; Sharp.prototype.progressive = function(progressive) {
}; this.options.progressive = (typeof progressive === 'boolean') ? progressive : true;
return this;
Sharp.prototype.sequentialRead = function(sequentialRead) { };
this.options.sequentialRead = (typeof sequentialRead === 'boolean') ? sequentialRead : true;
return this; Sharp.prototype.sequentialRead = function(sequentialRead) {
}; this.options.sequentialRead = (typeof sequentialRead === 'boolean') ? sequentialRead : true;
return this;
Sharp.prototype.resize = function(width, height) { };
if (!width) {
this.options.width = -1; Sharp.prototype.quality = function(quality) {
} else { if (!Number.isNaN(quality) && quality >= 1 && quality <= 100) {
if (!Number.isNaN(width)) { this.options.quality = quality;
this.options.width = width; } else {
} else { throw 'Invalid quality (1 to 100) ' + quality;
throw 'Invalid width ' + width; }
} return this;
} };
if (!height) {
this.options.height = -1; Sharp.prototype.compressionLevel = function(compressionLevel) {
} else { if (!Number.isNaN(compressionLevel) && compressionLevel >= -1 && compressionLevel <= 9) {
if (!Number.isNaN(height)) { this.options.compressionLevel = compressionLevel;
this.options.height = height; } else {
} else { throw 'Invalid compressionLevel (-1 to 9) ' + compressionLevel;
throw 'Invalid height ' + height; }
} return this;
} };
return this;
}; Sharp.prototype.resize = function(width, height) {
if (!width) {
Sharp.prototype.write = function(output, callback) { this.options.width = -1;
if (!output || output.length === 0) { } else {
throw 'Invalid output'; if (!Number.isNaN(width)) {
} else { this.options.width = width;
this._sharp(output, callback); } else {
} throw 'Invalid width ' + width;
return this; }
}; }
if (!height) {
Sharp.prototype.toBuffer = function(callback) { this.options.height = -1;
return this._sharp('__input', callback); } else {
}; if (!Number.isNaN(height)) {
this.options.height = height;
Sharp.prototype.jpeg = function(callback) { } else {
return this._sharp('__jpeg', callback); throw 'Invalid height ' + height;
}; }
}
Sharp.prototype.png = function(callback) { return this;
return this._sharp('__png', callback); };
};
Sharp.prototype.write = function(output, callback) {
Sharp.prototype.webp = function(callback) { if (!output || output.length === 0) {
return this._sharp('__webp', callback); throw 'Invalid output';
}; } else {
this._sharp(output, callback);
Sharp.prototype._sharp = function(output, callback) { }
sharp.resize( return this;
this.options.inFile, };
this.options.inBuffer,
output, Sharp.prototype.toBuffer = function(callback) {
this.options.width, return this._sharp('__input', callback);
this.options.height, };
this.options.canvas,
this.options.sharpen, Sharp.prototype.jpeg = function(callback) {
this.options.progressive, return this._sharp('__jpeg', callback);
this.options.sequentialRead, };
callback
); Sharp.prototype.png = function(callback) {
return this; return this._sharp('__png', callback);
}; };
module.exports.cache = function(limit) { Sharp.prototype.webp = function(callback) {
if (Number.isNaN(limit)) { return this._sharp('__webp', callback);
limit = null; };
}
return sharp.cache(limit); Sharp.prototype._sharp = function(output, callback) {
}; sharp.resize(
this.options.inFile,
this.options.inBuffer,
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,
callback
);
return this;
};
module.exports.cache = function(limit) {
if (Number.isNaN(limit)) {
limit = null;
}
return sharp.cache(limit);
};

View File

@ -24,6 +24,8 @@ struct resize_baton {
bool sharpen; bool sharpen;
bool progessive; bool progessive;
VipsAccess access_method; VipsAccess access_method;
int quality;
int compressionLevel;
std::string err; std::string err;
resize_baton(): buffer_in_len(0), buffer_out_len(0) {} resize_baton(): buffer_in_len(0), buffer_out_len(0) {}
@ -259,37 +261,37 @@ class ResizeWorker : public NanAsyncWorker {
// Output // Output
if (baton->file_out == "__jpeg" || (baton->file_out == "__input" && inputImageType == JPEG)) { if (baton->file_out == "__jpeg" || (baton->file_out == "__input" && inputImageType == JPEG)) {
// Write JPEG to buffer // Write JPEG to buffer
if (vips_jpegsave_buffer(sharpened, &baton->buffer_out, &baton->buffer_out_len, "strip", TRUE, "Q", 80, "optimize_coding", TRUE, "interlace", baton->progessive, NULL)) { if (vips_jpegsave_buffer(sharpened, &baton->buffer_out, &baton->buffer_out_len, "strip", TRUE, "Q", baton->quality, "optimize_coding", TRUE, "interlace", baton->progessive, NULL)) {
return resize_error(baton, sharpened); return resize_error(baton, sharpened);
} }
} else if (baton->file_out == "__png" || (baton->file_out == "__input" && inputImageType == PNG)) { } else if (baton->file_out == "__png" || (baton->file_out == "__input" && inputImageType == PNG)) {
// Write PNG to buffer // Write PNG to buffer
if (vips_pngsave_buffer(sharpened, &baton->buffer_out, &baton->buffer_out_len, "strip", TRUE, "compression", 6, "interlace", baton->progessive, NULL)) { if (vips_pngsave_buffer(sharpened, &baton->buffer_out, &baton->buffer_out_len, "strip", TRUE, "compression", baton->compressionLevel, "interlace", baton->progessive, NULL)) {
return resize_error(baton, sharpened); return resize_error(baton, sharpened);
} }
} else if (baton->file_out == "__webp" || (baton->file_out == "__input" && inputImageType == WEBP)) { } else if (baton->file_out == "__webp" || (baton->file_out == "__input" && inputImageType == WEBP)) {
// Write WEBP to buffer // Write WEBP to buffer
if (vips_webpsave_buffer(sharpened, &baton->buffer_out, &baton->buffer_out_len, "strip", TRUE, "Q", 80, NULL)) { if (vips_webpsave_buffer(sharpened, &baton->buffer_out, &baton->buffer_out_len, "strip", TRUE, "Q", baton->quality, NULL)) {
return resize_error(baton, sharpened); return resize_error(baton, sharpened);
} }
} else if (is_jpeg(baton->file_out)) { } else if (is_jpeg(baton->file_out)) {
// Write JPEG to file // Write JPEG to file
if (vips_jpegsave(sharpened, baton->file_out.c_str(), "strip", TRUE, "Q", 80, "optimize_coding", TRUE, "interlace", baton->progessive, NULL)) { if (vips_jpegsave(sharpened, baton->file_out.c_str(), "strip", TRUE, "Q", baton->quality, "optimize_coding", TRUE, "interlace", baton->progessive, NULL)) {
return resize_error(baton, sharpened); return resize_error(baton, sharpened);
} }
} else if (is_png(baton->file_out)) { } else if (is_png(baton->file_out)) {
// Write PNG to file // Write PNG to file
if (vips_pngsave(sharpened, baton->file_out.c_str(), "strip", TRUE, "compression", 6, "interlace", baton->progessive, NULL)) { if (vips_pngsave(sharpened, baton->file_out.c_str(), "strip", TRUE, "compression", baton->compressionLevel, "interlace", baton->progessive, NULL)) {
return resize_error(baton, sharpened); return resize_error(baton, sharpened);
} }
} else if (is_webp(baton->file_out)) { } else if (is_webp(baton->file_out)) {
// Write WEBP to file // Write WEBP to file
if (vips_webpsave(sharpened, baton->file_out.c_str(), "strip", TRUE, "Q", 80, NULL)) { if (vips_webpsave(sharpened, baton->file_out.c_str(), "strip", TRUE, "Q", baton->quality, NULL)) {
return resize_error(baton, sharpened); return resize_error(baton, sharpened);
} }
} else if (is_tiff(baton->file_out)) { } else if (is_tiff(baton->file_out)) {
// Write TIFF to file // Write TIFF to file
if (vips_tiffsave(sharpened, baton->file_out.c_str(), "strip", TRUE, "compression", VIPS_FOREIGN_TIFF_COMPRESSION_JPEG, "Q", 80, NULL)) { if (vips_tiffsave(sharpened, baton->file_out.c_str(), "strip", TRUE, "compression", VIPS_FOREIGN_TIFF_COMPRESSION_JPEG, "Q", baton->quality, NULL)) {
return resize_error(baton, sharpened); return resize_error(baton, sharpened);
} }
} else { } else {
@ -345,8 +347,10 @@ NAN_METHOD(resize) {
baton->sharpen = args[6]->BooleanValue(); baton->sharpen = args[6]->BooleanValue();
baton->progessive = args[7]->BooleanValue(); baton->progessive = args[7]->BooleanValue();
baton->access_method = args[8]->BooleanValue() ? VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_RANDOM; baton->access_method = args[8]->BooleanValue() ? VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_RANDOM;
baton->quality = args[9]->Int32Value();
baton->compressionLevel = args[10]->Int32Value();
NanCallback *callback = new NanCallback(args[9].As<v8::Function>()); NanCallback *callback = new NanCallback(args[11].As<v8::Function>());
NanAsyncQueueWorker(new ResizeWorker(callback, baton)); NanAsyncQueueWorker(new ResizeWorker(callback, baton));
NanReturnUndefined(); NanReturnUndefined();

View File

@ -1,73 +1,88 @@
var sharp = require("../index"); var sharp = require("../index");
var path = require("path"); var path = require("path");
var imagemagick = require("imagemagick"); var imagemagick = require("imagemagick");
var assert = require("assert"); var assert = require("assert");
var async = require("async"); var async = require("async");
var fixturesPath = path.join(__dirname, "fixtures"); var fixturesPath = path.join(__dirname, "fixtures");
var inputJpg = path.join(fixturesPath, "2569067123_aca715a2ee_o.jpg"); // http://www.flickr.com/photos/grizdave/2569067123/ var inputJpg = path.join(fixturesPath, "2569067123_aca715a2ee_o.jpg"); // http://www.flickr.com/photos/grizdave/2569067123/
var outputJpg = path.join(fixturesPath, "output.jpg"); var outputJpg = path.join(fixturesPath, "output.jpg");
async.series([ async.series([
// Resize with exact crop // Resize with exact crop
function(done) { function(done) {
sharp(inputJpg).resize(320, 240).write(outputJpg, function(err) { sharp(inputJpg).resize(320, 240).write(outputJpg, function(err) {
if (err) throw err; if (err) throw err;
imagemagick.identify(outputJpg, function(err, features) { imagemagick.identify(outputJpg, function(err, features) {
if (err) throw err; if (err) throw err;
assert.strictEqual(320, features.width); assert.strictEqual(320, features.width);
assert.strictEqual(240, features.height); assert.strictEqual(240, features.height);
done(); done();
}); });
}); });
}, },
// Resize to fixed width // Resize to fixed width
function(done) { function(done) {
sharp(inputJpg).resize(320).write(outputJpg, function(err) { sharp(inputJpg).resize(320).write(outputJpg, function(err) {
if (err) throw err; if (err) throw err;
imagemagick.identify(outputJpg, function(err, features) { imagemagick.identify(outputJpg, function(err, features) {
if (err) throw err; if (err) throw err;
assert.strictEqual(320, features.width); assert.strictEqual(320, features.width);
assert.strictEqual(261, features.height); assert.strictEqual(261, features.height);
done(); done();
}); });
}); });
}, },
// Resize to fixed height // Resize to fixed height
function(done) { function(done) {
sharp(inputJpg).resize(null, 320).write(outputJpg, function(err) { sharp(inputJpg).resize(null, 320).write(outputJpg, function(err) {
if (err) throw err; if (err) throw err;
imagemagick.identify(outputJpg, function(err, features) { imagemagick.identify(outputJpg, function(err, features) {
if (err) throw err; if (err) throw err;
assert.strictEqual(391, features.width); assert.strictEqual(391, features.width);
assert.strictEqual(320, features.height); assert.strictEqual(320, features.height);
done(); done();
}); });
}); });
}, },
// Identity transform // Identity transform
function(done) { function(done) {
sharp(inputJpg).write(outputJpg, function(err) { sharp(inputJpg).write(outputJpg, function(err) {
if (err) throw err; if (err) throw err;
imagemagick.identify(outputJpg, function(err, features) { imagemagick.identify(outputJpg, function(err, features) {
if (err) throw err; if (err) throw err;
assert.strictEqual(2725, features.width); assert.strictEqual(2725, features.width);
assert.strictEqual(2225, features.height); assert.strictEqual(2225, features.height);
done(); done();
}); });
}); });
}, },
// Upscale // Upscale
function(done) { function(done) {
sharp(inputJpg).resize(3000).write(outputJpg, function(err) { sharp(inputJpg).resize(3000).write(outputJpg, function(err) {
if (err) throw err; if (err) throw err;
imagemagick.identify(outputJpg, function(err, features) { imagemagick.identify(outputJpg, function(err, features) {
if (err) throw err; if (err) throw err;
assert.strictEqual(3000, features.width); assert.strictEqual(3000, features.width);
assert.strictEqual(2449, features.height); assert.strictEqual(2449, features.height);
done(); done();
}); });
}); });
} },
]); // Quality
function(done) {
sharp(inputJpg).resize(320, 240).quality(70).jpeg(function(err, buffer70) {
if (err) throw err;
sharp(inputJpg).resize(320, 240).jpeg(function(err, buffer80) {
if (err) throw err;
sharp(inputJpg).resize(320, 240).quality(90).jpeg(function(err, buffer90) {
assert(buffer70.length < buffer80.length);
assert(buffer80.length < buffer90.length);
done();
});
});
});
}
]);