mirror of
https://github.com/lovell/sharp.git
synced 2025-07-09 10:30:15 +02:00
Expose libjpeg extension param features
Trellis quantisation, overshoot deringing and scan optimisation
This commit is contained in:
parent
be39297f3b
commit
f6fd45cc90
26
README.md
26
README.md
@ -37,7 +37,7 @@ This module is powered by the blazingly fast [libvips](https://github.com/jcupit
|
||||
|
||||
* Node.js v0.10+ or io.js
|
||||
* [libvips](https://github.com/jcupitt/libvips) v7.40.0+ (7.42.0+ recommended)
|
||||
* C++11 compatible compiler such as gcc 4.6+ or clang 3.0+
|
||||
* C++11 compatible compiler such as gcc 4.6+, clang 3.0+ or MSVC 2013
|
||||
|
||||
To install the most suitable version of libvips on the following Operating Systems:
|
||||
|
||||
@ -555,6 +555,30 @@ but usually increases output file size and typically reduces performance by 25%.
|
||||
|
||||
The default behaviour is to use chroma subsampling (4:2:0).
|
||||
|
||||
#### trellisQuantisation() / trellisQuantization()
|
||||
|
||||
_Requires libvips 8.0.0+ compiled against mozjpeg 3.0+_
|
||||
|
||||
An advanced setting to apply the use of
|
||||
[trellis quantisation](http://en.wikipedia.org/wiki/Trellis_quantization) with JPEG output.
|
||||
Reduces file size and slightly increases relative quality at the cost of increased compression time.
|
||||
|
||||
#### overshootDeringing()
|
||||
|
||||
_Requires libvips 8.0.0+ compiled against mozjpeg 3.0+_
|
||||
|
||||
An advanced setting to reduce the effects of
|
||||
[ringing](http://en.wikipedia.org/wiki/Ringing_%28signal%29) in JPEG output,
|
||||
in particular where black text appears on a white background (or vice versa).
|
||||
|
||||
#### optimiseScans() / optimizeScans()
|
||||
|
||||
_Requires libvips 8.0.0+ compiled against mozjpeg 3.0+_
|
||||
|
||||
An advanced setting for progressive (interlace) JPEG output.
|
||||
Calculates which spectrum of DCT coefficients uses the fewest bits.
|
||||
Usually reduces file size at the cost of increased compression time.
|
||||
|
||||
#### compressionLevel(compressionLevel)
|
||||
|
||||
An advanced setting for the _zlib_ compression level of the lossless PNG output format. The default level is `6`.
|
||||
|
47
index.js
47
index.js
@ -67,6 +67,9 @@ var Sharp = function(input) {
|
||||
compressionLevel: 6,
|
||||
withoutAdaptiveFiltering: false,
|
||||
withoutChromaSubsampling: false,
|
||||
trellisQuantisation: false,
|
||||
overshootDeringing: false,
|
||||
optimiseScans: false,
|
||||
streamOut: false,
|
||||
withMetadata: false,
|
||||
tileSize: 256,
|
||||
@ -403,6 +406,50 @@ Sharp.prototype.withoutChromaSubsampling = function(withoutChromaSubsampling) {
|
||||
return this;
|
||||
};
|
||||
|
||||
/*
|
||||
Apply trellis quantisation to JPEG output - requires libvips 8.0.0+ compiled against mozjpeg 3.0+
|
||||
*/
|
||||
Sharp.prototype.trellisQuantisation = function(trellisQuantisation) {
|
||||
if (semver.gte(libvipsVersion, '8.0.0')) {
|
||||
this.options.trellisQuantisation = (typeof trellisQuantisation === 'boolean') ? trellisQuantisation : true;
|
||||
} else {
|
||||
console.error('trellisQuantisation requires libvips 8.0.0+');
|
||||
}
|
||||
return this;
|
||||
};
|
||||
Sharp.prototype.trellisQuantization = Sharp.prototype.trellisQuantisation;
|
||||
|
||||
/*
|
||||
Apply overshoot deringing to JPEG output - requires libvips 8.0.0+ compiled against mozjpeg 3.0+
|
||||
*/
|
||||
Sharp.prototype.overshootDeringing = function(overshootDeringing) {
|
||||
if (semver.gte(libvipsVersion, '8.0.0')) {
|
||||
this.options.overshootDeringing = (typeof overshootDeringing === 'boolean') ? overshootDeringing : true;
|
||||
} else {
|
||||
console.error('overshootDeringing requires libvips 8.0.0+');
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/*
|
||||
Optimise scans in progressive JPEG output - requires libvips 8.0.0+ compiled against mozjpeg 3.0+
|
||||
*/
|
||||
Sharp.prototype.optimiseScans = function(optimiseScans) {
|
||||
if (semver.gte(libvipsVersion, '8.0.0')) {
|
||||
this.options.optimiseScans = (typeof optimiseScans === 'boolean') ? optimiseScans : true;
|
||||
if (this.options.optimiseScans) {
|
||||
this.progressive();
|
||||
}
|
||||
} else {
|
||||
console.error('optimiseScans requires libvips 8.0.0+');
|
||||
}
|
||||
return this;
|
||||
};
|
||||
Sharp.prototype.optimizeScans = Sharp.prototype.optimiseScans;
|
||||
|
||||
/*
|
||||
Include all metadata (EXIF, XMP, IPTC) from the input image in the output image
|
||||
*/
|
||||
Sharp.prototype.withMetadata = function(withMetadata) {
|
||||
this.options.withMetadata = (typeof withMetadata === 'boolean') ? withMetadata : true;
|
||||
return this;
|
||||
|
@ -95,6 +95,9 @@ struct ResizeBaton {
|
||||
int compressionLevel;
|
||||
bool withoutAdaptiveFiltering;
|
||||
bool withoutChromaSubsampling;
|
||||
bool trellisQuantisation;
|
||||
bool overshootDeringing;
|
||||
bool optimiseScans;
|
||||
std::string err;
|
||||
bool withMetadata;
|
||||
int tileSize;
|
||||
@ -126,6 +129,9 @@ struct ResizeBaton {
|
||||
compressionLevel(6),
|
||||
withoutAdaptiveFiltering(false),
|
||||
withoutChromaSubsampling(false),
|
||||
trellisQuantisation(false),
|
||||
overshootDeringing(false),
|
||||
optimiseScans(false),
|
||||
withMetadata(false),
|
||||
tileSize(256),
|
||||
tileOverlap(0) {
|
||||
@ -813,6 +819,11 @@ class ResizeWorker : public NanAsyncWorker {
|
||||
// Write JPEG to buffer
|
||||
if (vips_jpegsave_buffer(image, &baton->bufferOut, &baton->bufferOutLength, "strip", !baton->withMetadata,
|
||||
"Q", baton->quality, "optimize_coding", TRUE, "no_subsample", baton->withoutChromaSubsampling,
|
||||
#if (VIPS_MAJOR_VERSION >= 8)
|
||||
"trellis_quant", baton->trellisQuantisation,
|
||||
"overshoot_deringing", baton->overshootDeringing,
|
||||
"optimize_scans", baton->optimiseScans,
|
||||
#endif
|
||||
"interlace", baton->progressive, NULL)) {
|
||||
return Error();
|
||||
}
|
||||
@ -881,6 +892,11 @@ class ResizeWorker : public NanAsyncWorker {
|
||||
// Write JPEG to file
|
||||
if (vips_jpegsave(image, baton->output.c_str(), "strip", !baton->withMetadata,
|
||||
"Q", baton->quality, "optimize_coding", TRUE, "no_subsample", baton->withoutChromaSubsampling,
|
||||
#if (VIPS_MAJOR_VERSION >= 8)
|
||||
"trellis_quant", baton->trellisQuantisation,
|
||||
"overshoot_deringing", baton->overshootDeringing,
|
||||
"optimize_scans", baton->optimiseScans,
|
||||
#endif
|
||||
"interlace", baton->progressive, NULL)) {
|
||||
return Error();
|
||||
}
|
||||
@ -1175,6 +1191,9 @@ NAN_METHOD(resize) {
|
||||
baton->compressionLevel = options->Get(NanNew<String>("compressionLevel"))->Int32Value();
|
||||
baton->withoutAdaptiveFiltering = options->Get(NanNew<String>("withoutAdaptiveFiltering"))->BooleanValue();
|
||||
baton->withoutChromaSubsampling = options->Get(NanNew<String>("withoutChromaSubsampling"))->BooleanValue();
|
||||
baton->trellisQuantisation = options->Get(NanNew<String>("trellisQuantisation"))->BooleanValue();
|
||||
baton->overshootDeringing = options->Get(NanNew<String>("overshootDeringing"))->BooleanValue();
|
||||
baton->optimiseScans = options->Get(NanNew<String>("optimiseScans"))->BooleanValue();
|
||||
baton->withMetadata = options->Get(NanNew<String>("withMetadata"))->BooleanValue();
|
||||
// Output filename or __format for Buffer
|
||||
baton->output = *String::Utf8Value(options->Get(NanNew<String>("output"))->ToString());
|
||||
|
138
test/unit/io.js
138
test/unit/io.js
@ -317,19 +317,21 @@ describe('Input/output', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('WebP output', function(done) {
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240)
|
||||
.toFormat(sharp.format.webp)
|
||||
.toBuffer(function(err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('webp', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
done();
|
||||
});
|
||||
});
|
||||
if (sharp.format.webp.output.buffer) {
|
||||
it('WebP output', function(done) {
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240)
|
||||
.toFormat(sharp.format.webp)
|
||||
.toBuffer(function(err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('webp', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
done();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
it('Invalid output format', function(done) {
|
||||
var isValid = false;
|
||||
@ -394,17 +396,19 @@ describe('Input/output', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('WebP', function(done) {
|
||||
sharp(fixtures.inputWebP).resize(320, 80).toFile(fixtures.outputZoinks, function(err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, info.size > 0);
|
||||
assert.strictEqual('webp', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(80, info.height);
|
||||
fs.unlinkSync(fixtures.outputZoinks);
|
||||
done();
|
||||
if (sharp.format.webp.input.file) {
|
||||
it('WebP', function(done) {
|
||||
sharp(fixtures.inputWebP).resize(320, 80).toFile(fixtures.outputZoinks, function(err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, info.size > 0);
|
||||
assert.strictEqual('webp', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(80, info.height);
|
||||
fs.unlinkSync(fixtures.outputZoinks);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
it('TIFF', function(done) {
|
||||
sharp(fixtures.inputTiff).resize(320, 80).toFile(fixtures.outputZoinks, function(err, info) {
|
||||
@ -511,6 +515,94 @@ describe('Input/output', function() {
|
||||
});
|
||||
});
|
||||
|
||||
if (semver.gte(sharp.libvipsVersion(), '8.0.0')) {
|
||||
it('Trellis quantisation [libvips ' + sharp.libvipsVersion() + '>=8.0.0]', function(done) {
|
||||
// First generate without
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240)
|
||||
.trellisQuantisation(false)
|
||||
.toBuffer(function(err, withoutData, withoutInfo) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, withoutData.length > 0);
|
||||
assert.strictEqual(withoutData.length, withoutInfo.size);
|
||||
assert.strictEqual('jpeg', withoutInfo.format);
|
||||
assert.strictEqual(320, withoutInfo.width);
|
||||
assert.strictEqual(240, withoutInfo.height);
|
||||
// Then generate with
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240)
|
||||
.trellisQuantization()
|
||||
.toBuffer(function(err, withData, withInfo) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, withData.length > 0);
|
||||
assert.strictEqual(withData.length, withInfo.size);
|
||||
assert.strictEqual('jpeg', withInfo.format);
|
||||
assert.strictEqual(320, withInfo.width);
|
||||
assert.strictEqual(240, withInfo.height);
|
||||
// Verify image is same (as mozjpeg may not be present) size or less
|
||||
assert.strictEqual(true, withData.length <= withoutData.length);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
it('Overshoot deringing [libvips ' + sharp.libvipsVersion() + '>=8.0.0]', function(done) {
|
||||
// First generate without
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240)
|
||||
.overshootDeringing(false)
|
||||
.toBuffer(function(err, withoutData, withoutInfo) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, withoutData.length > 0);
|
||||
assert.strictEqual(withoutData.length, withoutInfo.size);
|
||||
assert.strictEqual('jpeg', withoutInfo.format);
|
||||
assert.strictEqual(320, withoutInfo.width);
|
||||
assert.strictEqual(240, withoutInfo.height);
|
||||
// Then generate with
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240)
|
||||
.overshootDeringing()
|
||||
.toBuffer(function(err, withData, withInfo) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, withData.length > 0);
|
||||
assert.strictEqual(withData.length, withInfo.size);
|
||||
assert.strictEqual('jpeg', withInfo.format);
|
||||
assert.strictEqual(320, withInfo.width);
|
||||
assert.strictEqual(240, withInfo.height);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
it('Optimise scans [libvips ' + sharp.libvipsVersion() + '>=8.0.0]', function(done) {
|
||||
// First generate without
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240)
|
||||
.optimiseScans(false)
|
||||
.toBuffer(function(err, withoutData, withoutInfo) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, withoutData.length > 0);
|
||||
assert.strictEqual(withoutData.length, withoutInfo.size);
|
||||
assert.strictEqual('jpeg', withoutInfo.format);
|
||||
assert.strictEqual(320, withoutInfo.width);
|
||||
assert.strictEqual(240, withoutInfo.height);
|
||||
// Then generate with
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240)
|
||||
.optimizeScans()
|
||||
.toBuffer(function(err, withData, withInfo) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, withData.length > 0);
|
||||
assert.strictEqual(withData.length, withInfo.size);
|
||||
assert.strictEqual('jpeg', withInfo.format);
|
||||
assert.strictEqual(320, withInfo.width);
|
||||
assert.strictEqual(240, withInfo.height);
|
||||
// Verify image is of a different size (progressive output even without mozjpeg)
|
||||
assert.strictEqual(true, withData.length != withoutData.length);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (sharp.format.magick.input.file) {
|
||||
it('Convert SVG, if supported, to PNG', function(done) {
|
||||
sharp(fixtures.inputSvg)
|
||||
|
Loading…
x
Reference in New Issue
Block a user