Standardise WebP effort option name, deprecate reductionEffort

This commit is contained in:
Lovell Fuller 2021-11-24 18:54:30 +00:00
parent 72025051c5
commit 2b1f5cbe07
9 changed files with 68 additions and 59 deletions

View File

@ -282,7 +282,7 @@ Use these WebP options for output image.
* `options.lossless` **[boolean][7]** use lossless compression mode (optional, default `false`)
* `options.nearLossless` **[boolean][7]** use near_lossless compression mode (optional, default `false`)
* `options.smartSubsample` **[boolean][7]** use high quality chroma subsampling (optional, default `false`)
* `options.reductionEffort` **[number][9]** level of CPU effort to reduce file size, integer 0-6 (optional, default `4`)
* `options.effort` **[number][9]** level of CPU effort to reduce file size, integer 0-6 (optional, default `4`)
* `options.pageHeight` **[number][9]?** page height for animated output
* `options.loop` **[number][9]** number of animation iterations, use 0 for infinite animation (optional, default `0`)
* `options.delay` **[Array][10]<[number][9]>?** list of delays between animation frames (in milliseconds)
@ -300,7 +300,7 @@ const data = await sharp(input)
```javascript
// Optimise the file size of an animated WebP
const outputWebp = await sharp(inputWebp, { animated: true })
.webp({ reductionEffort: 6 })
.webp({ effort: 6 })
.toBuffer();
```

View File

@ -10,6 +10,8 @@ Requires libvips v8.12.0
* Reduce minimum Linux ARM64v8 glibc requirement to 2.17.
* Standardise WebP `effort` option name, deprecate `reductionEffort`.
* Expose control over CPU effort for palette-based PNG output.
[#2541](https://github.com/lovell/sharp/issues/2541)

File diff suppressed because one or more lines are too long

View File

@ -247,7 +247,7 @@ const Sharp = function (input, options) {
webpLossless: false,
webpNearLossless: false,
webpSmartSubsample: false,
webpReductionEffort: 4,
webpEffort: 4,
gifBitdepth: 8,
gifEffort: 7,
gifDither: 1,

View File

@ -448,7 +448,7 @@ function png (options) {
* @example
* // Optimise the file size of an animated WebP
* const outputWebp = await sharp(inputWebp, { animated: true })
* .webp({ reductionEffort: 6 })
* .webp({ effort: 6 })
* .toBuffer();
*
* @param {Object} [options] - output options
@ -457,7 +457,7 @@ function png (options) {
* @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 {number} [options.effort=4] - level of CPU effort to reduce file size, integer 0-6
* @param {number} [options.pageHeight] - page height for animated output
* @param {number} [options.loop=0] - number of animation iterations, use 0 for infinite animation
* @param {number[]} [options.delay] - list of delays between animation frames (in milliseconds)
@ -466,37 +466,39 @@ function png (options) {
* @throws {Error} Invalid options
*/
function webp (options) {
if (is.object(options) && is.defined(options.quality)) {
if (is.object(options)) {
if (is.defined(options.quality)) {
if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
this.options.webpQuality = options.quality;
} else {
throw is.invalidParameterError('quality', 'integer between 1 and 100', options.quality);
}
}
if (is.object(options) && is.defined(options.alphaQuality)) {
if (is.defined(options.alphaQuality)) {
if (is.integer(options.alphaQuality) && is.inRange(options.alphaQuality, 0, 100)) {
this.options.webpAlphaQuality = options.alphaQuality;
} else {
throw is.invalidParameterError('alphaQuality', 'integer between 0 and 100', options.alphaQuality);
}
}
if (is.object(options) && is.defined(options.lossless)) {
if (is.defined(options.lossless)) {
this._setBooleanOption('webpLossless', options.lossless);
}
if (is.object(options) && is.defined(options.nearLossless)) {
if (is.defined(options.nearLossless)) {
this._setBooleanOption('webpNearLossless', options.nearLossless);
}
if (is.object(options) && is.defined(options.smartSubsample)) {
if (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;
const effort = options.effort || options.reductionEffort;
if (is.defined(effort)) {
if (is.integer(effort) && is.inRange(effort, 0, 6)) {
this.options.webpEffort = effort;
} else {
throw is.invalidParameterError('reductionEffort', 'integer between 0 and 6', options.reductionEffort);
throw is.invalidParameterError('effort', 'integer between 0 and 6', effort);
}
}
}
trySetAnimationOptions(options, this.options);
return this._updateFormatOut('webp', options);
}

View File

@ -842,7 +842,7 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("lossless", baton->webpLossless)
->set("near_lossless", baton->webpNearLossless)
->set("smart_subsample", baton->webpSmartSubsample)
->set("reduction_effort", baton->webpReductionEffort)
->set("effort", baton->webpEffort)
->set("alpha_q", baton->webpAlphaQuality)));
baton->bufferOut = static_cast<char*>(area->data);
baton->bufferOutLength = area->length;
@ -1008,7 +1008,7 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("lossless", baton->webpLossless)
->set("near_lossless", baton->webpNearLossless)
->set("smart_subsample", baton->webpSmartSubsample)
->set("reduction_effort", baton->webpReductionEffort)
->set("effort", baton->webpEffort)
->set("alpha_q", baton->webpAlphaQuality));
baton->formatOut = "webp";
} else if (baton->formatOut == "gif" || (mightMatchInput && isGif) ||
@ -1078,7 +1078,7 @@ class PipelineWorker : public Napi::AsyncWorker {
{"lossless", baton->webpLossless ? "TRUE" : "FALSE"},
{"near_lossless", baton->webpNearLossless ? "TRUE" : "FALSE"},
{"smart_subsample", baton->webpSmartSubsample ? "TRUE" : "FALSE"},
{"reduction_effort", std::to_string(baton->webpReductionEffort)}
{"effort", std::to_string(baton->webpEffort)}
};
suffix = AssembleSuffixString(".webp", options);
} else {
@ -1485,7 +1485,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
baton->webpLossless = sharp::AttrAsBool(options, "webpLossless");
baton->webpNearLossless = sharp::AttrAsBool(options, "webpNearLossless");
baton->webpSmartSubsample = sharp::AttrAsBool(options, "webpSmartSubsample");
baton->webpReductionEffort = sharp::AttrAsUint32(options, "webpReductionEffort");
baton->webpEffort = sharp::AttrAsUint32(options, "webpEffort");
baton->gifBitdepth = sharp::AttrAsUint32(options, "gifBitdepth");
baton->gifEffort = sharp::AttrAsUint32(options, "gifEffort");
baton->gifDither = sharp::AttrAsDouble(options, "gifDither");

View File

@ -160,7 +160,7 @@ struct PipelineBaton {
bool webpNearLossless;
bool webpLossless;
bool webpSmartSubsample;
int webpReductionEffort;
int webpEffort;
int gifBitdepth;
int gifEffort;
double gifDither;
@ -301,7 +301,7 @@ struct PipelineBaton {
webpNearLossless(false),
webpLossless(false),
webpSmartSubsample(false),
webpReductionEffort(4),
webpEffort(4),
tiffQuality(80),
tiffCompression(VIPS_FOREIGN_TIFF_COMPRESSION_JPEG),
tiffPredictor(VIPS_FOREIGN_TIFF_PREDICTOR_HORIZONTAL),

View File

@ -667,7 +667,7 @@ describe('Tile', function () {
sharp(fixtures.inputJpg)
.webp({
quality: 1,
reductionEffort: 0
effort: 0
})
.tile({
layout: 'google'

View File

@ -35,7 +35,7 @@ describe('WebP', function () {
it('should work for webp alpha quality', function (done) {
sharp(fixtures.inputPngAlphaPremultiplicationSmall)
.webp({ alphaQuality: 80, reductionEffort: 0 })
.webp({ alphaQuality: 80, effort: 0 })
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
@ -46,7 +46,7 @@ describe('WebP', function () {
it('should work for webp lossless', function (done) {
sharp(fixtures.inputPngAlphaPremultiplicationSmall)
.webp({ lossless: true, reductionEffort: 0 })
.webp({ lossless: true, effort: 0 })
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
@ -57,7 +57,7 @@ describe('WebP', function () {
it('should work for webp near-lossless', function (done) {
sharp(fixtures.inputPngAlphaPremultiplicationSmall)
.webp({ nearLossless: true, quality: 50, reductionEffort: 0 })
.webp({ nearLossless: true, quality: 50, effort: 0 })
.toBuffer(function (err50, data50, info50) {
if (err50) throw err50;
assert.strictEqual(true, data50.length > 0);
@ -68,7 +68,7 @@ describe('WebP', function () {
it('should use near-lossless when both lossless and nearLossless are specified', function (done) {
sharp(fixtures.inputPngAlphaPremultiplicationSmall)
.webp({ nearLossless: true, quality: 50, lossless: true, reductionEffort: 0 })
.webp({ nearLossless: true, quality: 50, lossless: true, effort: 0 })
.toBuffer(function (err50, data50, info50) {
if (err50) throw err50;
assert.strictEqual(true, data50.length > 0);
@ -99,31 +99,37 @@ describe('WebP', function () {
});
});
it('should produce a smaller file size with increased reductionEffort', () =>
it('should produce a smaller file size with increased effort', () =>
sharp(fixtures.inputJpg)
.resize(320, 240)
.webp()
.toBuffer()
.then(reductionEffort4 =>
.then(effort4 =>
sharp(fixtures.inputJpg)
.resize(320, 240)
.webp({ reductionEffort: 6 })
.webp({ effort: 6 })
.toBuffer()
.then(reductionEffort6 => {
assert.strictEqual(true, reductionEffort4.length > reductionEffort6.length);
.then(effort6 => {
assert.strictEqual(true, effort4.length > effort6.length);
})
)
);
it('invalid reductionEffort throws', () => {
it('invalid effort throws', () => {
assert.throws(() => {
sharp().webp({ effort: true });
});
});
it('invalid reductionEffort (deprecated) throws', () => {
assert.throws(() => {
sharp().webp({ reductionEffort: true });
});
});
it('out of range reductionEffort throws', () => {
it('out of range effort throws', () => {
assert.throws(() => {
sharp().webp({ reductionEffort: -1 });
sharp().webp({ effort: -1 });
});
});
@ -189,7 +195,7 @@ describe('WebP', function () {
it('should work with streams when only animated is set', function (done) {
fs.createReadStream(fixtures.inputWebPAnimated)
.pipe(sharp({ animated: true }))
.webp({ lossless: true, reductionEffort: 0 })
.webp({ lossless: true, effort: 0 })
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
@ -201,7 +207,7 @@ describe('WebP', function () {
it('should work with streams when only pages is set', function (done) {
fs.createReadStream(fixtures.inputWebPAnimated)
.pipe(sharp({ pages: -1 }))
.webp({ lossless: true, reductionEffort: 0 })
.webp({ lossless: true, effort: 0 })
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
@ -213,12 +219,11 @@ describe('WebP', function () {
it('should remove animation properties when loading single page', async () => {
const data = await sharp(fixtures.inputGifAnimatedLoop3)
.resize({ height: 570 })
.webp({ reductionEffort: 0 })
.webp({ effort: 0 })
.toBuffer();
const metadata = await sharp(data).metadata();
const { size, ...metadata } = await sharp(data).metadata();
assert.deepStrictEqual(metadata, {
format: 'webp',
size: 2580,
width: 740,
height: 570,
space: 'srgb',