mirror of
https://github.com/lovell/sharp.git
synced 2025-07-09 18:40:16 +02:00
Expose libwebp smartSubsample and reductionEffort #1545
This commit is contained in:
parent
119d16cad3
commit
36e8a3da88
@ -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.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.lossless` **[Boolean][6]** use lossless compression mode (optional, default `false`)
|
||||||
- `options.nearLossless` **[Boolean][6]** use near_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`)
|
- `options.force` **[Boolean][6]** force WebP output, otherwise attempt to use input format (optional, default `true`)
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
@ -11,6 +11,9 @@ Requires libvips v8.8.0.
|
|||||||
* Add experimental support for HEIF images. Requires libvips compiled with libheif.
|
* Add experimental support for HEIF images. Requires libvips compiled with libheif.
|
||||||
[#1105](https://github.com/lovell/sharp/issues/1105)
|
[#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.
|
* Add experimental support for Worker Threads.
|
||||||
[#1558](https://github.com/lovell/sharp/issues/1558)
|
[#1558](https://github.com/lovell/sharp/issues/1558)
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ const is = require('./is');
|
|||||||
require('./libvips').hasVendoredLibvips();
|
require('./libvips').hasVendoredLibvips();
|
||||||
|
|
||||||
let sharp;
|
let sharp;
|
||||||
|
/* istanbul ignore next */
|
||||||
try {
|
try {
|
||||||
sharp = require('../build/Release/sharp.node');
|
sharp = require('../build/Release/sharp.node');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -197,6 +198,8 @@ const Sharp = function (input, options) {
|
|||||||
webpAlphaQuality: 100,
|
webpAlphaQuality: 100,
|
||||||
webpLossless: false,
|
webpLossless: false,
|
||||||
webpNearLossless: false,
|
webpNearLossless: false,
|
||||||
|
webpSmartSubsample: false,
|
||||||
|
webpReductionEffort: 4,
|
||||||
tiffQuality: 80,
|
tiffQuality: 80,
|
||||||
tiffCompression: 'jpeg',
|
tiffCompression: 'jpeg',
|
||||||
tiffPredictor: 'horizontal',
|
tiffPredictor: 'horizontal',
|
||||||
|
@ -290,6 +290,8 @@ function png (options) {
|
|||||||
* @param {Number} [options.alphaQuality=100] - quality of alpha layer, integer 0-100
|
* @param {Number} [options.alphaQuality=100] - quality of alpha layer, integer 0-100
|
||||||
* @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 {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
|
* @param {Boolean} [options.force=true] - force WebP output, otherwise attempt to use input format
|
||||||
* @returns {Sharp}
|
* @returns {Sharp}
|
||||||
* @throws {Error} Invalid options
|
* @throws {Error} Invalid options
|
||||||
@ -299,14 +301,14 @@ function webp (options) {
|
|||||||
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 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.object(options) && 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 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)) {
|
if (is.object(options) && is.defined(options.lossless)) {
|
||||||
@ -315,6 +317,16 @@ function webp (options) {
|
|||||||
if (is.object(options) && is.defined(options.nearLossless)) {
|
if (is.object(options) && is.defined(options.nearLossless)) {
|
||||||
this._setBooleanOption('webpNearLossless', 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);
|
return this._updateFormatOut('webp', options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -760,6 +760,8 @@ class PipelineWorker : public Nan::AsyncWorker {
|
|||||||
->set("Q", baton->webpQuality)
|
->set("Q", baton->webpQuality)
|
||||||
->set("lossless", baton->webpLossless)
|
->set("lossless", baton->webpLossless)
|
||||||
->set("near_lossless", baton->webpNearLossless)
|
->set("near_lossless", baton->webpNearLossless)
|
||||||
|
->set("smart_subsample", baton->webpSmartSubsample)
|
||||||
|
->set("reduction_effort", baton->webpReductionEffort)
|
||||||
->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;
|
||||||
@ -884,6 +886,8 @@ class PipelineWorker : public Nan::AsyncWorker {
|
|||||||
->set("Q", baton->webpQuality)
|
->set("Q", baton->webpQuality)
|
||||||
->set("lossless", baton->webpLossless)
|
->set("lossless", baton->webpLossless)
|
||||||
->set("near_lossless", baton->webpNearLossless)
|
->set("near_lossless", baton->webpNearLossless)
|
||||||
|
->set("smart_subsample", baton->webpSmartSubsample)
|
||||||
|
->set("reduction_effort", baton->webpReductionEffort)
|
||||||
->set("alpha_q", baton->webpAlphaQuality));
|
->set("alpha_q", baton->webpAlphaQuality));
|
||||||
baton->formatOut = "webp";
|
baton->formatOut = "webp";
|
||||||
} else if (baton->formatOut == "tiff" || (mightMatchInput && isTiff) ||
|
} else if (baton->formatOut == "tiff" || (mightMatchInput && isTiff) ||
|
||||||
@ -938,7 +942,9 @@ class PipelineWorker : public Nan::AsyncWorker {
|
|||||||
{"Q", std::to_string(baton->webpQuality)},
|
{"Q", std::to_string(baton->webpQuality)},
|
||||||
{"alpha_q", std::to_string(baton->webpAlphaQuality)},
|
{"alpha_q", std::to_string(baton->webpAlphaQuality)},
|
||||||
{"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"},
|
||||||
|
{"reduction_effort", std::to_string(baton->webpReductionEffort)}
|
||||||
};
|
};
|
||||||
suffix = AssembleSuffixString(".webp", options);
|
suffix = AssembleSuffixString(".webp", options);
|
||||||
} else {
|
} else {
|
||||||
@ -1345,6 +1351,8 @@ NAN_METHOD(pipeline) {
|
|||||||
baton->webpAlphaQuality = AttrTo<uint32_t>(options, "webpAlphaQuality");
|
baton->webpAlphaQuality = AttrTo<uint32_t>(options, "webpAlphaQuality");
|
||||||
baton->webpLossless = AttrTo<bool>(options, "webpLossless");
|
baton->webpLossless = AttrTo<bool>(options, "webpLossless");
|
||||||
baton->webpNearLossless = AttrTo<bool>(options, "webpNearLossless");
|
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->tiffQuality = AttrTo<uint32_t>(options, "tiffQuality");
|
||||||
baton->tiffPyramid = AttrTo<bool>(options, "tiffPyramid");
|
baton->tiffPyramid = AttrTo<bool>(options, "tiffPyramid");
|
||||||
baton->tiffSquash = AttrTo<bool>(options, "tiffSquash");
|
baton->tiffSquash = AttrTo<bool>(options, "tiffSquash");
|
||||||
|
@ -138,6 +138,8 @@ struct PipelineBaton {
|
|||||||
int webpAlphaQuality;
|
int webpAlphaQuality;
|
||||||
bool webpNearLossless;
|
bool webpNearLossless;
|
||||||
bool webpLossless;
|
bool webpLossless;
|
||||||
|
bool webpSmartSubsample;
|
||||||
|
int webpReductionEffort;
|
||||||
int tiffQuality;
|
int tiffQuality;
|
||||||
VipsForeignTiffCompression tiffCompression;
|
VipsForeignTiffCompression tiffCompression;
|
||||||
VipsForeignTiffPredictor tiffPredictor;
|
VipsForeignTiffPredictor tiffPredictor;
|
||||||
@ -241,6 +243,11 @@ struct PipelineBaton {
|
|||||||
pngColours(256),
|
pngColours(256),
|
||||||
pngDither(1.0),
|
pngDither(1.0),
|
||||||
webpQuality(80),
|
webpQuality(80),
|
||||||
|
webpAlphaQuality(100),
|
||||||
|
webpNearLossless(false),
|
||||||
|
webpLossless(false),
|
||||||
|
webpSmartSubsample(false),
|
||||||
|
webpReductionEffort(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),
|
||||||
|
@ -75,4 +75,54 @@ describe('WebP', function () {
|
|||||||
fixtures.assertSimilar(fixtures.expected('webp-near-lossless-50.webp'), data50, done);
|
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 });
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user