diff --git a/lib/constructor.js b/lib/constructor.js index 7efa8605..6ca17db6 100644 --- a/lib/constructor.js +++ b/lib/constructor.js @@ -192,6 +192,7 @@ const Sharp = function (input, options) { jpegTrellisQuantisation: false, jpegOvershootDeringing: false, jpegOptimiseScans: false, + jpegOptimiseCoding: true, pngProgressive: false, pngCompressionLevel: 9, pngAdaptiveFiltering: false, diff --git a/lib/output.js b/lib/output.js index db2df2b2..ec49559b 100644 --- a/lib/output.js +++ b/lib/output.js @@ -148,6 +148,8 @@ function withMetadata (withMetadata) { * @param {Boolean} [options.overshootDeringing=false] - apply overshoot deringing, requires mozjpeg * @param {Boolean} [options.optimiseScans=false] - optimise progressive scans, forces progressive, requires mozjpeg * @param {Boolean} [options.optimizeScans=false] - alternative spelling of optimiseScans + * @param {Boolean} [options.optimiseCoding=true] - optimise Huffman coding tables + * @param {Boolean} [options.optimizeCoding=true] - alternative spelling of optimiseCoding * @param {Boolean} [options.force=true] - force JPEG output, otherwise attempt to use input format * @returns {Sharp} * @throws {Error} Invalid options @@ -185,6 +187,10 @@ function jpeg (options) { this.options.jpegProgressive = true; } } + options.optimiseCoding = is.bool(options.optimizeCoding) ? options.optimizeCoding : options.optimiseCoding; + if (is.defined(options.optimiseCoding)) { + this._setBooleanOption('jpegOptimiseCoding', options.optimiseCoding); + } } return this._updateFormatOut('jpeg', options); } diff --git a/src/pipeline.cc b/src/pipeline.cc index 13b4501c..8b5cbdf2 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -735,7 +735,7 @@ class PipelineWorker : public Nan::AsyncWorker { ->set("trellis_quant", baton->jpegTrellisQuantisation) ->set("overshoot_deringing", baton->jpegOvershootDeringing) ->set("optimize_scans", baton->jpegOptimiseScans) - ->set("optimize_coding", TRUE))); + ->set("optimize_coding", baton->jpegOptimiseCoding))); baton->bufferOut = static_cast(area->data); baton->bufferOutLength = area->length; area->free_fn = nullptr; @@ -850,7 +850,7 @@ class PipelineWorker : public Nan::AsyncWorker { ->set("trellis_quant", baton->jpegTrellisQuantisation) ->set("overshoot_deringing", baton->jpegOvershootDeringing) ->set("optimize_scans", baton->jpegOptimiseScans) - ->set("optimize_coding", TRUE)); + ->set("optimize_coding", baton->jpegOptimiseCoding)); baton->formatOut = "jpeg"; baton->channels = std::min(baton->channels, 3); } else if (baton->formatOut == "png" || (mightMatchInput && isPng) || (willMatchInput && @@ -929,7 +929,7 @@ class PipelineWorker : public Nan::AsyncWorker { {"trellis_quant", baton->jpegTrellisQuantisation ? "TRUE" : "FALSE"}, {"overshoot_deringing", baton->jpegOvershootDeringing ? "TRUE": "FALSE"}, {"optimize_scans", baton->jpegOptimiseScans ? "TRUE": "FALSE"}, - {"optimize_coding", "TRUE"} + {"optimize_coding", baton->jpegOptimiseCoding ? "TRUE": "FALSE"} }; suffix = AssembleSuffixString(extname, options); } @@ -1268,6 +1268,7 @@ NAN_METHOD(pipeline) { baton->jpegTrellisQuantisation = AttrTo(options, "jpegTrellisQuantisation"); baton->jpegOvershootDeringing = AttrTo(options, "jpegOvershootDeringing"); baton->jpegOptimiseScans = AttrTo(options, "jpegOptimiseScans"); + baton->jpegOptimiseCoding = AttrTo(options, "jpegOptimiseCoding"); baton->pngProgressive = AttrTo(options, "pngProgressive"); baton->pngCompressionLevel = AttrTo(options, "pngCompressionLevel"); baton->pngAdaptiveFiltering = AttrTo(options, "pngAdaptiveFiltering"); diff --git a/src/pipeline.h b/src/pipeline.h index 74e12706..0e8de558 100644 --- a/src/pipeline.h +++ b/src/pipeline.h @@ -104,6 +104,7 @@ struct PipelineBaton { bool jpegTrellisQuantisation; bool jpegOvershootDeringing; bool jpegOptimiseScans; + bool jpegOptimiseCoding; bool pngProgressive; int pngCompressionLevel; bool pngAdaptiveFiltering; @@ -189,6 +190,7 @@ struct PipelineBaton { jpegTrellisQuantisation(false), jpegOvershootDeringing(false), jpegOptimiseScans(false), + jpegOptimiseCoding(true), pngProgressive(false), pngCompressionLevel(9), pngAdaptiveFiltering(false), diff --git a/test/unit/io.js b/test/unit/io.js index 59209214..e31f4af1 100644 --- a/test/unit/io.js +++ b/test/unit/io.js @@ -826,6 +826,36 @@ describe('Input/output', function () { }); }); + it('Optimise coding generates smaller output length', function (done) { + // First generate with optimize coding enabled (default) + sharp(fixtures.inputJpg) + .resize(320, 240) + .jpeg() + .toBuffer(function (err, withOptimiseCoding, withInfo) { + if (err) throw err; + assert.strictEqual(true, withOptimiseCoding.length > 0); + assert.strictEqual(withOptimiseCoding.length, withInfo.size); + assert.strictEqual('jpeg', withInfo.format); + assert.strictEqual(320, withInfo.width); + assert.strictEqual(240, withInfo.height); + // Then generate with coding disabled + sharp(fixtures.inputJpg) + .resize(320, 240) + .jpeg({ optimizeCoding: false }) + .toBuffer(function (err, withoutOptimiseCoding, withoutInfo) { + if (err) throw err; + assert.strictEqual(true, withoutOptimiseCoding.length > 0); + assert.strictEqual(withoutOptimiseCoding.length, withoutInfo.size); + assert.strictEqual('jpeg', withoutInfo.format); + assert.strictEqual(320, withoutInfo.width); + assert.strictEqual(240, withoutInfo.height); + // Verify optimised image is of a smaller size + assert.strictEqual(true, withOptimiseCoding.length < withoutOptimiseCoding.length); + done(); + }); + }); + }); + it('Convert SVG to PNG at default 72DPI', function (done) { sharp(fixtures.inputSvg) .resize(1024)