diff --git a/docs/api-output.md b/docs/api-output.md index 8db6da48..b5112440 100644 --- a/docs/api-output.md +++ b/docs/api-output.md @@ -115,6 +115,9 @@ Use these WebP options for output image. - `options` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** output options - `options.quality` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** quality, integer 1-100 (optional, default `80`) + - `options.alphaQuality` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)?** quality of alpha layer, integer 0-100 (optional, default `100`) + - `options.lossless` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** use lossless compression mode (optional, default `false`) + - `options.nearLossless` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** use near_lossless compression mode (optional, default `false`) - `options.force` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** force WebP output, otherwise attempt to use input format (optional, default `true`) diff --git a/lib/constructor.js b/lib/constructor.js index b530f195..920b5436 100644 --- a/lib/constructor.js +++ b/lib/constructor.js @@ -145,6 +145,9 @@ const Sharp = function (input, options) { pngCompressionLevel: 6, pngAdaptiveFiltering: true, webpQuality: 80, + webpAlphaQuality: 100, + webpLossless: false, + webpNearLossless: false, tiffQuality: 80, tileSize: 256, tileOverlap: 0, diff --git a/lib/output.js b/lib/output.js index b164af75..29c17011 100644 --- a/lib/output.js +++ b/lib/output.js @@ -168,6 +168,9 @@ const png = function png (options) { * Use these WebP options for output image. * @param {Object} [options] - output options * @param {Number} [options.quality=80] - quality, integer 1-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.nearLossless=false] - use near_lossless compression mode * @param {Boolean} [options.force=true] - force WebP output, otherwise attempt to use input format * @returns {Sharp} * @throws {Error} Invalid options @@ -180,6 +183,19 @@ const webp = function webp (options) { throw new Error('Invalid quality (integer, 1-100) ' + options.quality); } } + if (is.object(options) && is.defined(options.alphaQuality)) { + if (is.integer(options.alphaQuality) && is.inRange(options.alphaQuality, 1, 100)) { + this.options.webpAlphaQuality = options.alphaQuality; + } else { + throw new Error('Invalid webp alpha quality (integer, 1-100) ' + options.alphaQuality); + } + } + if (is.object(options) && is.defined(options.lossless)) { + this._setBooleanOption('webpLossless', options.lossless); + } + if (is.object(options) && is.defined(options.nearLossless)) { + this._setBooleanOption('webpNearLossless', options.nearLossless); + } return this._updateFormatOut('webp', options); }; diff --git a/src/pipeline.cc b/src/pipeline.cc index 3066e28d..263a5c2c 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -767,6 +767,9 @@ class PipelineWorker : public Nan::AsyncWorker { VipsArea *area = VIPS_AREA(image.webpsave_buffer(VImage::option() ->set("strip", !baton->withMetadata) ->set("Q", baton->webpQuality) + ->set("lossless", baton->webpLossless) + ->set("near_lossless", baton->webpNearLossless) + ->set("alpha_q", baton->webpAlphaQuality) )); baton->bufferOut = static_cast(area->data); baton->bufferOutLength = area->length; @@ -844,6 +847,9 @@ class PipelineWorker : public Nan::AsyncWorker { image.webpsave(const_cast(baton->fileOut.data()), VImage::option() ->set("strip", !baton->withMetadata) ->set("Q", baton->webpQuality) + ->set("lossless", baton->webpLossless) + ->set("near_lossless", baton->webpNearLossless) + ->set("alpha_q", baton->webpAlphaQuality) ); baton->formatOut = "webp"; } else if (baton->formatOut == "tiff" || isTiff || (matchInput && inputImageType == ImageType::TIFF)) { @@ -870,7 +876,10 @@ class PipelineWorker : public Nan::AsyncWorker { suffix = AssembleSuffixString(".png", options); } else if (baton->tileFormat == "webp") { std::vector> options { - {"Q", std::to_string(baton->webpQuality)} + {"Q", std::to_string(baton->webpQuality)}, + {"alpha_q", std::to_string(baton->webpAlphaQuality)}, + {"lossless", baton->webpLossless ? "TRUE" : "FALSE"}, + {"near_lossless", baton->webpNearLossless ? "TRUE" : "FALSE"} }; suffix = AssembleSuffixString(".webp", options); } else { @@ -1209,6 +1218,9 @@ NAN_METHOD(pipeline) { baton->pngCompressionLevel = AttrTo(options, "pngCompressionLevel"); baton->pngAdaptiveFiltering = AttrTo(options, "pngAdaptiveFiltering"); baton->webpQuality = AttrTo(options, "webpQuality"); + baton->webpAlphaQuality = AttrTo(options, "webpAlphaQuality"); + baton->webpLossless = AttrTo(options, "webpLossless"); + baton->webpNearLossless = AttrTo(options, "webpNearLossless"); baton->tiffQuality = AttrTo(options, "tiffQuality"); // Tile output baton->tileSize = AttrTo(options, "tileSize"); diff --git a/src/pipeline.h b/src/pipeline.h index 66589c45..803cfce3 100644 --- a/src/pipeline.h +++ b/src/pipeline.h @@ -84,6 +84,9 @@ struct PipelineBaton { int pngCompressionLevel; bool pngAdaptiveFiltering; int webpQuality; + int webpAlphaQuality; + bool webpNearLossless; + bool webpLossless; int tiffQuality; std::string err; bool withMetadata; diff --git a/test/fixtures/expected/webp-alpha-80.webp b/test/fixtures/expected/webp-alpha-80.webp new file mode 100644 index 00000000..739d8c00 Binary files /dev/null and b/test/fixtures/expected/webp-alpha-80.webp differ diff --git a/test/fixtures/expected/webp-lossless.webp b/test/fixtures/expected/webp-lossless.webp new file mode 100644 index 00000000..c008ba76 Binary files /dev/null and b/test/fixtures/expected/webp-lossless.webp differ diff --git a/test/fixtures/expected/webp-near-lossless-50.webp b/test/fixtures/expected/webp-near-lossless-50.webp new file mode 100644 index 00000000..20bce456 Binary files /dev/null and b/test/fixtures/expected/webp-near-lossless-50.webp differ diff --git a/test/unit/io.js b/test/unit/io.js index c46b872f..6a31210d 100644 --- a/test/unit/io.js +++ b/test/unit/io.js @@ -372,6 +372,50 @@ describe('Input/output', function () { done(); }); }); + + it('should work for webp alpha quality', function (done) { + sharp(fixtures.inputPngAlphaPremultiplicationSmall) + .webp({alphaQuality: 80}) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('webp', info.format); + fixtures.assertSimilar(fixtures.expected('webp-alpha-80.webp'), data, done); + }); + }); + + it('should work for webp lossless', function (done) { + sharp(fixtures.inputPngAlphaPremultiplicationSmall) + .webp({lossless: true}) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('webp', info.format); + fixtures.assertSimilar(fixtures.expected('webp-lossless.webp'), data, done); + }); + }); + + it('should work for webp near-lossless', function (done) { + sharp(fixtures.inputPngAlphaPremultiplicationSmall) + .webp({nearLossless: true, quality: 50}) + .toBuffer(function (err50, data50, info50) { + if (err50) throw err50; + assert.strictEqual(true, data50.length > 0); + assert.strictEqual('webp', info50.format); + fixtures.assertSimilar(fixtures.expected('webp-near-lossless-50.webp'), data50, done); + }); + }); + + it('should use near-lossless when both lossless and nearLossless are specified', function (done) { + sharp(fixtures.inputPngAlphaPremultiplicationSmall) + .webp({nearLossless: true, quality: 50, lossless: true}) + .toBuffer(function (err50, data50, info50) { + if (err50) throw err50; + assert.strictEqual(true, data50.length > 0); + assert.strictEqual('webp', info50.format); + fixtures.assertSimilar(fixtures.expected('webp-near-lossless-50.webp'), data50, done); + }); + }); } it('Invalid output format', function (done) { @@ -735,6 +779,12 @@ describe('Input/output', function () { }); }); + it('Invalid WebP alpha quality throws error', function () { + assert.throws(function () { + sharp().webp({ alphaQuality: 101 }); + }); + }); + it('Invalid TIFF quality throws error', function () { assert.throws(function () { sharp().tiff({ quality: 101 });