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
|
* Node.js v0.10+ or io.js
|
||||||
* [libvips](https://github.com/jcupitt/libvips) v7.40.0+ (7.42.0+ recommended)
|
* [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:
|
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).
|
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)
|
#### compressionLevel(compressionLevel)
|
||||||
|
|
||||||
An advanced setting for the _zlib_ compression level of the lossless PNG output format. The default level is `6`.
|
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,
|
compressionLevel: 6,
|
||||||
withoutAdaptiveFiltering: false,
|
withoutAdaptiveFiltering: false,
|
||||||
withoutChromaSubsampling: false,
|
withoutChromaSubsampling: false,
|
||||||
|
trellisQuantisation: false,
|
||||||
|
overshootDeringing: false,
|
||||||
|
optimiseScans: false,
|
||||||
streamOut: false,
|
streamOut: false,
|
||||||
withMetadata: false,
|
withMetadata: false,
|
||||||
tileSize: 256,
|
tileSize: 256,
|
||||||
@ -403,6 +406,50 @@ Sharp.prototype.withoutChromaSubsampling = function(withoutChromaSubsampling) {
|
|||||||
return this;
|
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) {
|
Sharp.prototype.withMetadata = function(withMetadata) {
|
||||||
this.options.withMetadata = (typeof withMetadata === 'boolean') ? withMetadata : true;
|
this.options.withMetadata = (typeof withMetadata === 'boolean') ? withMetadata : true;
|
||||||
return this;
|
return this;
|
||||||
|
@ -95,6 +95,9 @@ struct ResizeBaton {
|
|||||||
int compressionLevel;
|
int compressionLevel;
|
||||||
bool withoutAdaptiveFiltering;
|
bool withoutAdaptiveFiltering;
|
||||||
bool withoutChromaSubsampling;
|
bool withoutChromaSubsampling;
|
||||||
|
bool trellisQuantisation;
|
||||||
|
bool overshootDeringing;
|
||||||
|
bool optimiseScans;
|
||||||
std::string err;
|
std::string err;
|
||||||
bool withMetadata;
|
bool withMetadata;
|
||||||
int tileSize;
|
int tileSize;
|
||||||
@ -126,6 +129,9 @@ struct ResizeBaton {
|
|||||||
compressionLevel(6),
|
compressionLevel(6),
|
||||||
withoutAdaptiveFiltering(false),
|
withoutAdaptiveFiltering(false),
|
||||||
withoutChromaSubsampling(false),
|
withoutChromaSubsampling(false),
|
||||||
|
trellisQuantisation(false),
|
||||||
|
overshootDeringing(false),
|
||||||
|
optimiseScans(false),
|
||||||
withMetadata(false),
|
withMetadata(false),
|
||||||
tileSize(256),
|
tileSize(256),
|
||||||
tileOverlap(0) {
|
tileOverlap(0) {
|
||||||
@ -813,6 +819,11 @@ class ResizeWorker : public NanAsyncWorker {
|
|||||||
// Write JPEG to buffer
|
// Write JPEG to buffer
|
||||||
if (vips_jpegsave_buffer(image, &baton->bufferOut, &baton->bufferOutLength, "strip", !baton->withMetadata,
|
if (vips_jpegsave_buffer(image, &baton->bufferOut, &baton->bufferOutLength, "strip", !baton->withMetadata,
|
||||||
"Q", baton->quality, "optimize_coding", TRUE, "no_subsample", baton->withoutChromaSubsampling,
|
"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)) {
|
"interlace", baton->progressive, NULL)) {
|
||||||
return Error();
|
return Error();
|
||||||
}
|
}
|
||||||
@ -881,6 +892,11 @@ class ResizeWorker : public NanAsyncWorker {
|
|||||||
// Write JPEG to file
|
// Write JPEG to file
|
||||||
if (vips_jpegsave(image, baton->output.c_str(), "strip", !baton->withMetadata,
|
if (vips_jpegsave(image, baton->output.c_str(), "strip", !baton->withMetadata,
|
||||||
"Q", baton->quality, "optimize_coding", TRUE, "no_subsample", baton->withoutChromaSubsampling,
|
"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)) {
|
"interlace", baton->progressive, NULL)) {
|
||||||
return Error();
|
return Error();
|
||||||
}
|
}
|
||||||
@ -1175,6 +1191,9 @@ NAN_METHOD(resize) {
|
|||||||
baton->compressionLevel = options->Get(NanNew<String>("compressionLevel"))->Int32Value();
|
baton->compressionLevel = options->Get(NanNew<String>("compressionLevel"))->Int32Value();
|
||||||
baton->withoutAdaptiveFiltering = options->Get(NanNew<String>("withoutAdaptiveFiltering"))->BooleanValue();
|
baton->withoutAdaptiveFiltering = options->Get(NanNew<String>("withoutAdaptiveFiltering"))->BooleanValue();
|
||||||
baton->withoutChromaSubsampling = options->Get(NanNew<String>("withoutChromaSubsampling"))->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();
|
baton->withMetadata = options->Get(NanNew<String>("withMetadata"))->BooleanValue();
|
||||||
// Output filename or __format for Buffer
|
// Output filename or __format for Buffer
|
||||||
baton->output = *String::Utf8Value(options->Get(NanNew<String>("output"))->ToString());
|
baton->output = *String::Utf8Value(options->Get(NanNew<String>("output"))->ToString());
|
||||||
|
@ -317,6 +317,7 @@ describe('Input/output', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (sharp.format.webp.output.buffer) {
|
||||||
it('WebP output', function(done) {
|
it('WebP output', function(done) {
|
||||||
sharp(fixtures.inputJpg)
|
sharp(fixtures.inputJpg)
|
||||||
.resize(320, 240)
|
.resize(320, 240)
|
||||||
@ -330,6 +331,7 @@ describe('Input/output', function() {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
it('Invalid output format', function(done) {
|
it('Invalid output format', function(done) {
|
||||||
var isValid = false;
|
var isValid = false;
|
||||||
@ -394,6 +396,7 @@ describe('Input/output', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (sharp.format.webp.input.file) {
|
||||||
it('WebP', function(done) {
|
it('WebP', function(done) {
|
||||||
sharp(fixtures.inputWebP).resize(320, 80).toFile(fixtures.outputZoinks, function(err, info) {
|
sharp(fixtures.inputWebP).resize(320, 80).toFile(fixtures.outputZoinks, function(err, info) {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
@ -405,6 +408,7 @@ describe('Input/output', function() {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
it('TIFF', function(done) {
|
it('TIFF', function(done) {
|
||||||
sharp(fixtures.inputTiff).resize(320, 80).toFile(fixtures.outputZoinks, function(err, info) {
|
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) {
|
if (sharp.format.magick.input.file) {
|
||||||
it('Convert SVG, if supported, to PNG', function(done) {
|
it('Convert SVG, if supported, to PNG', function(done) {
|
||||||
sharp(fixtures.inputSvg)
|
sharp(fixtures.inputSvg)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user