Add mixed and minSize animation options for WebP output

This commit is contained in:
Lovell Fuller 2022-07-12 14:51:03 +01:00
parent 1b84ccbbe9
commit d247c02762
8 changed files with 49 additions and 1 deletions

View File

@ -291,6 +291,8 @@ Use these WebP options for output image.
* `options.effort` **[number][12]** CPU effort, between 0 (fastest) and 6 (slowest) (optional, default `4`)
* `options.loop` **[number][12]** number of animation iterations, use 0 for infinite animation (optional, default `0`)
* `options.delay` **([number][12] | [Array][13]<[number][12]>)?** delay(s) between animation frames (in milliseconds)
* `options.minSize` **[boolean][10]** prevent use of animation key frames to minimise file size (slow) (optional, default `false`)
* `options.mixed` **[boolean][10]** allow mixture of lossy and lossless animation frames (slow) (optional, default `false`)
* `options.force` **[boolean][10]** force WebP output, otherwise attempt to use input format (optional, default `true`)
### Examples

View File

@ -8,6 +8,8 @@ Requires libvips v8.13.0
* Drop support for Node.js 12, now requires Node.js >= 14.15.0.
* Add WebP `minSize` and `mixed` options for greater control over animation frames.
* Use combined bounding box of alpha and non-alpha channels for `trim` operation.
[#2166](https://github.com/lovell/sharp/issues/2166)

File diff suppressed because one or more lines are too long

View File

@ -251,6 +251,8 @@ const Sharp = function (input, options) {
webpNearLossless: false,
webpSmartSubsample: false,
webpEffort: 4,
webpMinSize: false,
webpMixed: false,
gifBitdepth: 8,
gifEffort: 7,
gifDither: 1,

View File

@ -469,6 +469,8 @@ function png (options) {
* @param {number} [options.effort=4] - CPU effort, between 0 (fastest) and 6 (slowest)
* @param {number} [options.loop=0] - number of animation iterations, use 0 for infinite animation
* @param {number|number[]} [options.delay] - delay(s) between animation frames (in milliseconds)
* @param {boolean} [options.minSize=false] - prevent use of animation key frames to minimise file size (slow)
* @param {boolean} [options.mixed=false] - allow mixture of lossy and lossless animation frames (slow)
* @param {boolean} [options.force=true] - force WebP output, otherwise attempt to use input format
* @returns {Sharp}
* @throws {Error} Invalid options
@ -506,6 +508,12 @@ function webp (options) {
throw is.invalidParameterError('effort', 'integer between 0 and 6', effort);
}
}
if (is.defined(options.minSize)) {
this._setBooleanOption('webpMinSize', options.minSize);
}
if (is.defined(options.mixed)) {
this._setBooleanOption('webpMixed', options.mixed);
}
}
trySetAnimationOptions(options, this.options);
return this._updateFormatOut('webp', options);

View File

@ -872,6 +872,8 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("near_lossless", baton->webpNearLossless)
->set("smart_subsample", baton->webpSmartSubsample)
->set("effort", baton->webpEffort)
->set("min_size", baton->webpMinSize)
->set("mixed", baton->webpMixed)
->set("alpha_q", baton->webpAlphaQuality)));
baton->bufferOut = static_cast<char*>(area->data);
baton->bufferOutLength = area->length;
@ -1039,6 +1041,8 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("near_lossless", baton->webpNearLossless)
->set("smart_subsample", baton->webpSmartSubsample)
->set("effort", baton->webpEffort)
->set("min_size", baton->webpMinSize)
->set("mixed", baton->webpMixed)
->set("alpha_q", baton->webpAlphaQuality));
baton->formatOut = "webp";
} else if (baton->formatOut == "gif" || (mightMatchInput && isGif) ||
@ -1109,6 +1113,8 @@ class PipelineWorker : public Napi::AsyncWorker {
{"lossless", baton->webpLossless ? "TRUE" : "FALSE"},
{"near_lossless", baton->webpNearLossless ? "TRUE" : "FALSE"},
{"smart_subsample", baton->webpSmartSubsample ? "TRUE" : "FALSE"},
{"min_size", baton->webpMinSize ? "TRUE" : "FALSE"},
{"mixed", baton->webpMixed ? "TRUE" : "FALSE"},
{"effort", std::to_string(baton->webpEffort)}
};
suffix = AssembleSuffixString(".webp", options);
@ -1526,6 +1532,8 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
baton->webpNearLossless = sharp::AttrAsBool(options, "webpNearLossless");
baton->webpSmartSubsample = sharp::AttrAsBool(options, "webpSmartSubsample");
baton->webpEffort = sharp::AttrAsUint32(options, "webpEffort");
baton->webpMinSize = sharp::AttrAsBool(options, "webpMinSize");
baton->webpMixed = sharp::AttrAsBool(options, "webpMixed");
baton->gifBitdepth = sharp::AttrAsUint32(options, "gifBitdepth");
baton->gifEffort = sharp::AttrAsUint32(options, "gifEffort");
baton->gifDither = sharp::AttrAsDouble(options, "gifDither");

View File

@ -157,6 +157,8 @@ struct PipelineBaton {
bool webpLossless;
bool webpSmartSubsample;
int webpEffort;
bool webpMinSize;
bool webpMixed;
int gifBitdepth;
int gifEffort;
double gifDither;
@ -302,6 +304,8 @@ struct PipelineBaton {
webpLossless(false),
webpSmartSubsample(false),
webpEffort(4),
webpMinSize(false),
webpMixed(false),
tiffQuality(80),
tiffCompression(VIPS_FOREIGN_TIFF_COMPRESSION_JPEG),
tiffPredictor(VIPS_FOREIGN_TIFF_PREDICTOR_HORIZONTAL),

View File

@ -139,6 +139,28 @@ describe('WebP', function () {
assert.strictEqual(effort, 0);
});
it('valid minSize', () => {
assert.doesNotThrow(() => sharp().webp({ minSize: true }));
});
it('invalid minSize throws', () => {
assert.throws(
() => sharp().webp({ minSize: 1 }),
/Expected boolean for webpMinSize but received 1 of type number/
);
});
it('valid mixed', () => {
assert.doesNotThrow(() => sharp().webp({ mixed: true }));
});
it('invalid mixed throws', () => {
assert.throws(
() => sharp().webp({ mixed: 'fail' }),
/Expected boolean for webpMixed but received fail of type string/
);
});
it('invalid loop throws', () => {
assert.throws(() => {
sharp().webp({ loop: -1 });