Skip to content

Commit

Permalink
Expose preset option for WebP output #3639
Browse files Browse the repository at this point in the history
  • Loading branch information
lovell committed Jul 12, 2023
1 parent 66f7cef commit 85b26da
Show file tree
Hide file tree
Showing 8 changed files with 43 additions and 1 deletion.
1 change: 1 addition & 0 deletions docs/api-output.md
Expand Up @@ -294,6 +294,7 @@ Use these WebP options for output image.
| [options.lossless] | <code>boolean</code> | <code>false</code> | use lossless compression mode |
| [options.nearLossless] | <code>boolean</code> | <code>false</code> | use near_lossless compression mode |
| [options.smartSubsample] | <code>boolean</code> | <code>false</code> | use high quality chroma subsampling |
| [options.preset] | <code>string</code> | <code>&quot;&#x27;default&#x27;&quot;</code> | named preset for preprocessing/filtering, one of: default, photo, picture, drawing, icon, text |
| [options.effort] | <code>number</code> | <code>4</code> | CPU effort, between 0 (fastest) and 6 (slowest) |
| [options.loop] | <code>number</code> | <code>0</code> | number of animation iterations, use 0 for infinite animation |
| [options.delay] | <code>number</code> \| <code>Array.&lt;number&gt;</code> | | delay(s) between animation frames (in milliseconds) |
Expand Down
3 changes: 3 additions & 0 deletions docs/changelog.md
Expand Up @@ -6,6 +6,9 @@ Requires libvips v8.14.2

### v0.32.3 - TBD

* Expose `preset` option for WebP output.
[#3639](https://github.com/lovell/sharp/issues/3639)

* Ensure decoding remains sequential for all operations (regression in 0.32.2).
[#3725](https://github.com/lovell/sharp/issues/3725)

Expand Down
2 changes: 1 addition & 1 deletion docs/search-index.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions lib/constructor.js
Expand Up @@ -291,6 +291,7 @@ const Sharp = function (input, options) {
webpLossless: false,
webpNearLossless: false,
webpSmartSubsample: false,
webpPreset: 'default',
webpEffort: 4,
webpMinSize: false,
webpMixed: false,
Expand Down
8 changes: 8 additions & 0 deletions lib/output.js
Expand Up @@ -483,6 +483,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 {string} [options.preset='default'] - named preset for preprocessing/filtering, one of: default, photo, picture, drawing, icon, text
* @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)
Expand Down Expand Up @@ -517,6 +518,13 @@ function webp (options) {
if (is.defined(options.smartSubsample)) {
this._setBooleanOption('webpSmartSubsample', options.smartSubsample);
}
if (is.defined(options.preset)) {
if (is.string(options.preset) && is.inArray(options.preset, ['default', 'photo', 'picture', 'drawing', 'icon', 'text'])) {
this.options.webpPreset = options.preset;
} else {
throw is.invalidParameterError('preset', 'one of: default, photo, picture, drawing, icon, text', options.preset);
}
}
if (is.defined(options.effort)) {
if (is.integer(options.effort) && is.inRange(options.effort, 0, 6)) {
this.options.webpEffort = options.effort;
Expand Down
4 changes: 4 additions & 0 deletions src/pipeline.cc
Expand Up @@ -894,6 +894,7 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("lossless", baton->webpLossless)
->set("near_lossless", baton->webpNearLossless)
->set("smart_subsample", baton->webpSmartSubsample)
->set("preset", baton->webpPreset)
->set("effort", baton->webpEffort)
->set("min_size", baton->webpMinSize)
->set("mixed", baton->webpMixed)
Expand Down Expand Up @@ -1099,6 +1100,7 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("lossless", baton->webpLossless)
->set("near_lossless", baton->webpNearLossless)
->set("smart_subsample", baton->webpSmartSubsample)
->set("preset", baton->webpPreset)
->set("effort", baton->webpEffort)
->set("min_size", baton->webpMinSize)
->set("mixed", baton->webpMixed)
Expand Down Expand Up @@ -1377,6 +1379,7 @@ class PipelineWorker : public Napi::AsyncWorker {
{"lossless", baton->webpLossless ? "TRUE" : "FALSE"},
{"near_lossless", baton->webpNearLossless ? "TRUE" : "FALSE"},
{"smart_subsample", baton->webpSmartSubsample ? "TRUE" : "FALSE"},
{"preset", vips_enum_nick(VIPS_TYPE_FOREIGN_WEBP_PRESET, baton->webpPreset)},
{"min_size", baton->webpMinSize ? "TRUE" : "FALSE"},
{"mixed", baton->webpMixed ? "TRUE" : "FALSE"},
{"effort", std::to_string(baton->webpEffort)}
Expand Down Expand Up @@ -1629,6 +1632,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->webpPreset = sharp::AttrAsEnum<VipsForeignWebpPreset>(options, "webpPreset", VIPS_TYPE_FOREIGN_WEBP_PRESET);
baton->webpEffort = sharp::AttrAsUint32(options, "webpEffort");
baton->webpMinSize = sharp::AttrAsBool(options, "webpMinSize");
baton->webpMixed = sharp::AttrAsBool(options, "webpMixed");
Expand Down
2 changes: 2 additions & 0 deletions src/pipeline.h
Expand Up @@ -153,6 +153,7 @@ struct PipelineBaton {
bool webpNearLossless;
bool webpLossless;
bool webpSmartSubsample;
VipsForeignWebpPreset webpPreset;
int webpEffort;
bool webpMinSize;
bool webpMixed;
Expand Down Expand Up @@ -318,6 +319,7 @@ struct PipelineBaton {
webpNearLossless(false),
webpLossless(false),
webpSmartSubsample(false),
webpPreset(VIPS_FOREIGN_WEBP_PRESET_DEFAULT),
webpEffort(4),
webpMinSize(false),
webpMixed(false),
Expand Down
23 changes: 23 additions & 0 deletions test/unit/webp.js
Expand Up @@ -102,6 +102,29 @@ describe('WebP', function () {
});
});

it('should produce a different file size with specific preset', () =>
sharp(fixtures.inputJpg)
.resize(320, 240)
.webp({ preset: 'default' })
.toBuffer()
.then(presetDefault =>
sharp(fixtures.inputJpg)
.resize(320, 240)
.webp({ preset: 'picture' })
.toBuffer()
.then(presetPicture => {
assert.notStrictEqual(presetDefault.length, presetPicture.length);
})
)
);

it('invalid preset throws', () => {
assert.throws(
() => sharp().webp({ preset: 'fail' }),
/Expected one of: default, photo, picture, drawing, icon, text for preset but received fail of type string/
);
});

it('should produce a smaller file size with increased effort', () =>
sharp(fixtures.inputJpg)
.resize(320, 240)
Expand Down

0 comments on commit 85b26da

Please sign in to comment.