Expose libwebp smartSubsample and reductionEffort #1545

This commit is contained in:
Lovell Fuller 2019-07-14 22:52:38 +01:00
parent 119d16cad3
commit 36e8a3da88
7 changed files with 88 additions and 3 deletions

View File

@ -185,6 +185,8 @@ Use these WebP options for output image.
- `options.alphaQuality` **[Number][8]** quality of alpha layer, integer 0-100 (optional, default `100`)
- `options.lossless` **[Boolean][6]** use lossless compression mode (optional, default `false`)
- `options.nearLossless` **[Boolean][6]** use near_lossless compression mode (optional, default `false`)
- `options.smartSubsample` **[Boolean][6]** use high quality chroma subsampling (optional, default `false`)
- `options.reductionEffort` **[Number][8]** level of CPU effort to reduce file size, integer 0-6 (optional, default `4`)
- `options.force` **[Boolean][6]** force WebP output, otherwise attempt to use input format (optional, default `true`)
### Examples

View File

@ -11,6 +11,9 @@ Requires libvips v8.8.0.
* Add experimental support for HEIF images. Requires libvips compiled with libheif.
[#1105](https://github.com/lovell/sharp/issues/1105)
* Expose libwebp `smartSubsample` and `reductionEffort` options.
[#1545](https://github.com/lovell/sharp/issues/1545)
* Add experimental support for Worker Threads.
[#1558](https://github.com/lovell/sharp/issues/1558)

View File

@ -9,6 +9,7 @@ const is = require('./is');
require('./libvips').hasVendoredLibvips();
let sharp;
/* istanbul ignore next */
try {
sharp = require('../build/Release/sharp.node');
} catch (err) {
@ -197,6 +198,8 @@ const Sharp = function (input, options) {
webpAlphaQuality: 100,
webpLossless: false,
webpNearLossless: false,
webpSmartSubsample: false,
webpReductionEffort: 4,
tiffQuality: 80,
tiffCompression: 'jpeg',
tiffPredictor: 'horizontal',

View File

@ -290,6 +290,8 @@ function png (options) {
* @param {Number} [options.alphaQuality=100] - quality of alpha layer, integer 0-100
* @param {Boolean} [options.lossless=false] - use lossless compression mode
* @param {Boolean} [options.nearLossless=false] - use near_lossless compression mode
* @param {Boolean} [options.smartSubsample=false] - use high quality chroma subsampling
* @param {Number} [options.reductionEffort=4] - level of CPU effort to reduce file size, integer 0-6
* @param {Boolean} [options.force=true] - force WebP output, otherwise attempt to use input format
* @returns {Sharp}
* @throws {Error} Invalid options
@ -299,14 +301,14 @@ function webp (options) {
if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
this.options.webpQuality = options.quality;
} else {
throw new Error('Invalid quality (integer, 1-100) ' + options.quality);
throw is.invalidParameterError('quality', 'integer between 1 and 100', options.quality);
}
}
if (is.object(options) && is.defined(options.alphaQuality)) {
if (is.integer(options.alphaQuality) && is.inRange(options.alphaQuality, 0, 100)) {
this.options.webpAlphaQuality = options.alphaQuality;
} else {
throw new Error('Invalid webp alpha quality (integer, 0-100) ' + options.alphaQuality);
throw is.invalidParameterError('alphaQuality', 'integer between 0 and 100', options.alphaQuality);
}
}
if (is.object(options) && is.defined(options.lossless)) {
@ -315,6 +317,16 @@ function webp (options) {
if (is.object(options) && is.defined(options.nearLossless)) {
this._setBooleanOption('webpNearLossless', options.nearLossless);
}
if (is.object(options) && is.defined(options.smartSubsample)) {
this._setBooleanOption('webpSmartSubsample', options.smartSubsample);
}
if (is.object(options) && is.defined(options.reductionEffort)) {
if (is.integer(options.reductionEffort) && is.inRange(options.reductionEffort, 0, 6)) {
this.options.webpReductionEffort = options.reductionEffort;
} else {
throw is.invalidParameterError('reductionEffort', 'integer between 0 and 6', options.reductionEffort);
}
}
return this._updateFormatOut('webp', options);
}

View File

@ -760,6 +760,8 @@ class PipelineWorker : public Nan::AsyncWorker {
->set("Q", baton->webpQuality)
->set("lossless", baton->webpLossless)
->set("near_lossless", baton->webpNearLossless)
->set("smart_subsample", baton->webpSmartSubsample)
->set("reduction_effort", baton->webpReductionEffort)
->set("alpha_q", baton->webpAlphaQuality)));
baton->bufferOut = static_cast<char*>(area->data);
baton->bufferOutLength = area->length;
@ -884,6 +886,8 @@ class PipelineWorker : public Nan::AsyncWorker {
->set("Q", baton->webpQuality)
->set("lossless", baton->webpLossless)
->set("near_lossless", baton->webpNearLossless)
->set("smart_subsample", baton->webpSmartSubsample)
->set("reduction_effort", baton->webpReductionEffort)
->set("alpha_q", baton->webpAlphaQuality));
baton->formatOut = "webp";
} else if (baton->formatOut == "tiff" || (mightMatchInput && isTiff) ||
@ -938,7 +942,9 @@ class PipelineWorker : public Nan::AsyncWorker {
{"Q", std::to_string(baton->webpQuality)},
{"alpha_q", std::to_string(baton->webpAlphaQuality)},
{"lossless", baton->webpLossless ? "TRUE" : "FALSE"},
{"near_lossless", baton->webpNearLossless ? "TRUE" : "FALSE"}
{"near_lossless", baton->webpNearLossless ? "TRUE" : "FALSE"},
{"smart_subsample", baton->webpSmartSubsample ? "TRUE" : "FALSE"},
{"reduction_effort", std::to_string(baton->webpReductionEffort)}
};
suffix = AssembleSuffixString(".webp", options);
} else {
@ -1345,6 +1351,8 @@ NAN_METHOD(pipeline) {
baton->webpAlphaQuality = AttrTo<uint32_t>(options, "webpAlphaQuality");
baton->webpLossless = AttrTo<bool>(options, "webpLossless");
baton->webpNearLossless = AttrTo<bool>(options, "webpNearLossless");
baton->webpSmartSubsample = AttrTo<bool>(options, "webpSmartSubsample");
baton->webpReductionEffort = AttrTo<uint32_t>(options, "webpReductionEffort");
baton->tiffQuality = AttrTo<uint32_t>(options, "tiffQuality");
baton->tiffPyramid = AttrTo<bool>(options, "tiffPyramid");
baton->tiffSquash = AttrTo<bool>(options, "tiffSquash");

View File

@ -138,6 +138,8 @@ struct PipelineBaton {
int webpAlphaQuality;
bool webpNearLossless;
bool webpLossless;
bool webpSmartSubsample;
int webpReductionEffort;
int tiffQuality;
VipsForeignTiffCompression tiffCompression;
VipsForeignTiffPredictor tiffPredictor;
@ -241,6 +243,11 @@ struct PipelineBaton {
pngColours(256),
pngDither(1.0),
webpQuality(80),
webpAlphaQuality(100),
webpNearLossless(false),
webpLossless(false),
webpSmartSubsample(false),
webpReductionEffort(4),
tiffQuality(80),
tiffCompression(VIPS_FOREIGN_TIFF_COMPRESSION_JPEG),
tiffPredictor(VIPS_FOREIGN_TIFF_PREDICTOR_HORIZONTAL),

View File

@ -75,4 +75,54 @@ describe('WebP', function () {
fixtures.assertSimilar(fixtures.expected('webp-near-lossless-50.webp'), data50, done);
});
});
it('should produce a larger file size using smartSubsample', () =>
sharp(fixtures.inputJpg)
.resize(320, 240)
.webp({ smartSubsample: false })
.toBuffer()
.then(withoutSmartSubsample => {
sharp(fixtures.inputJpg)
.resize(320, 240)
.webp({ smartSubsample: true })
.toBuffer()
.then(withSmartSubsample => {
assert.strictEqual(true, withSmartSubsample.length > withoutSmartSubsample.length);
});
})
);
it('invalid smartSubsample throws', () => {
assert.throws(() => {
sharp().webp({ smartSubsample: 1 });
});
});
it('should produce a smaller file size with increased reductionEffort', () =>
sharp(fixtures.inputJpg)
.resize(320, 240)
.webp()
.toBuffer()
.then(reductionEffort4 => {
sharp(fixtures.inputJpg)
.resize(320, 240)
.webp({ reductionEffort: 6 })
.toBuffer()
.then(reductionEffort6 => {
assert.strictEqual(true, reductionEffort4.length > reductionEffort6.length);
});
})
);
it('invalid reductionEffort throws', () => {
assert.throws(() => {
sharp().webp({ reductionEffort: true });
});
});
it('out of range reductionEffort throws', () => {
assert.throws(() => {
sharp().webp({ reductionEffort: -1 });
});
});
});