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.lossless` **[boolean][7]** use lossless compression mode (optional, default `false`)
* `options.nearLossless` **[boolean][7]** use near_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.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.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.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) * `options.delay` **[Array][10]<[number][9]>?** list of delays between animation frames (in milliseconds)
@ -300,7 +300,7 @@ const data = await sharp(input)
```javascript ```javascript
// Optimise the file size of an animated WebP // Optimise the file size of an animated WebP
const outputWebp = await sharp(inputWebp, { animated: true }) const outputWebp = await sharp(inputWebp, { animated: true })
.webp({ reductionEffort: 6 }) .webp({ effort: 6 })
.toBuffer(); .toBuffer();
``` ```

View File

@ -10,6 +10,8 @@ Requires libvips v8.12.0
* Reduce minimum Linux ARM64v8 glibc requirement to 2.17. * 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. * Expose control over CPU effort for palette-based PNG output.
[#2541](https://github.com/lovell/sharp/issues/2541) [#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, webpLossless: false,
webpNearLossless: false, webpNearLossless: false,
webpSmartSubsample: false, webpSmartSubsample: false,
webpReductionEffort: 4, webpEffort: 4,
gifBitdepth: 8, gifBitdepth: 8,
gifEffort: 7, gifEffort: 7,
gifDither: 1, gifDither: 1,

View File

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

View File

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

View File

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

View File

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

View File

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