Expose mozjpeg quant_table flag (#1285)

This commit is contained in:
Espen Hovlandsdal 2018-07-10 16:56:05 +02:00 committed by Lovell Fuller
parent 5cb35485f1
commit 7bbc5176a1
6 changed files with 62 additions and 0 deletions

View File

@ -116,6 +116,8 @@ Use these JPEG options for output image.
- `options.progressive` **[Boolean][6]** use progressive (interlace) scan (optional, default `false`) - `options.progressive` **[Boolean][6]** use progressive (interlace) scan (optional, default `false`)
- `options.chromaSubsampling` **[String][1]** set to '4:4:4' to prevent chroma subsampling when quality <= 90 (optional, default `'4:2:0'`) - `options.chromaSubsampling` **[String][1]** set to '4:4:4' to prevent chroma subsampling when quality <= 90 (optional, default `'4:2:0'`)
- `options.trellisQuantisation` **[Boolean][6]** apply trellis quantisation, requires mozjpeg (optional, default `false`) - `options.trellisQuantisation` **[Boolean][6]** apply trellis quantisation, requires mozjpeg (optional, default `false`)
- `options.quantisationTable` **[Number][8]** [quantisation table][9] to use, integer 0-8 (optional, default `0`)
- `options.quantizationTable` **[Number][8]** alternative spelling of quantisationTable (optional, default `0`)
- `options.overshootDeringing` **[Boolean][6]** apply overshoot deringing, requires mozjpeg (optional, default `false`) - `options.overshootDeringing` **[Boolean][6]** apply overshoot deringing, requires mozjpeg (optional, default `false`)
- `options.optimiseScans` **[Boolean][6]** optimise progressive scans, forces progressive, requires mozjpeg (optional, default `false`) - `options.optimiseScans` **[Boolean][6]** optimise progressive scans, forces progressive, requires mozjpeg (optional, default `false`)
- `options.optimizeScans` **[Boolean][6]** alternative spelling of optimiseScans (optional, default `false`) - `options.optimizeScans` **[Boolean][6]** alternative spelling of optimiseScans (optional, default `false`)
@ -312,3 +314,5 @@ Returns **Sharp**
[7]: https://nodejs.org/api/buffer.html [7]: https://nodejs.org/api/buffer.html
[8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number [8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
[9]: https://jcupitt.github.io/libvips/API/current/VipsForeignSave.html#vips-jpegsave

View File

@ -193,6 +193,7 @@ const Sharp = function (input, options) {
jpegOvershootDeringing: false, jpegOvershootDeringing: false,
jpegOptimiseScans: false, jpegOptimiseScans: false,
jpegOptimiseCoding: true, jpegOptimiseCoding: true,
jpegQuantisationTable: 0,
pngProgressive: false, pngProgressive: false,
pngCompressionLevel: 9, pngCompressionLevel: 9,
pngAdaptiveFiltering: false, pngAdaptiveFiltering: false,

View File

@ -150,6 +150,8 @@ function withMetadata (withMetadata) {
* @param {Boolean} [options.optimizeScans=false] - alternative spelling of optimiseScans * @param {Boolean} [options.optimizeScans=false] - alternative spelling of optimiseScans
* @param {Boolean} [options.optimiseCoding=true] - optimise Huffman coding tables * @param {Boolean} [options.optimiseCoding=true] - optimise Huffman coding tables
* @param {Boolean} [options.optimizeCoding=true] - alternative spelling of optimiseCoding * @param {Boolean} [options.optimizeCoding=true] - alternative spelling of optimiseCoding
* @param {Number} [options.quantisationTable=0] - quantization table to use, integer 0-8, requires mozjpeg
* @param {Number} [options.quantizationTable=0] - alternative spelling of quantisationTable
* @param {Boolean} [options.force=true] - force JPEG output, otherwise attempt to use input format * @param {Boolean} [options.force=true] - force JPEG output, otherwise attempt to use input format
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid options * @throws {Error} Invalid options
@ -191,6 +193,14 @@ function jpeg (options) {
if (is.defined(options.optimiseCoding)) { if (is.defined(options.optimiseCoding)) {
this._setBooleanOption('jpegOptimiseCoding', options.optimiseCoding); this._setBooleanOption('jpegOptimiseCoding', options.optimiseCoding);
} }
options.quantisationTable = is.number(options.quantizationTable) ? options.quantizationTable : options.quantisationTable;
if (is.defined(options.quantisationTable)) {
if (is.integer(options.quantisationTable) && is.inRange(options.quantisationTable, 0, 8)) {
this.options.jpegQuantisationTable = options.quantisationTable;
} else {
throw new Error('Invalid quantisation table (integer, 0-8) ' + options.quantisationTable);
}
}
} }
return this._updateFormatOut('jpeg', options); return this._updateFormatOut('jpeg', options);
} }

View File

@ -733,6 +733,7 @@ class PipelineWorker : public Nan::AsyncWorker {
->set("interlace", baton->jpegProgressive) ->set("interlace", baton->jpegProgressive)
->set("no_subsample", baton->jpegChromaSubsampling == "4:4:4") ->set("no_subsample", baton->jpegChromaSubsampling == "4:4:4")
->set("trellis_quant", baton->jpegTrellisQuantisation) ->set("trellis_quant", baton->jpegTrellisQuantisation)
->set("quant_table", baton->jpegQuantisationTable)
->set("overshoot_deringing", baton->jpegOvershootDeringing) ->set("overshoot_deringing", baton->jpegOvershootDeringing)
->set("optimize_scans", baton->jpegOptimiseScans) ->set("optimize_scans", baton->jpegOptimiseScans)
->set("optimize_coding", baton->jpegOptimiseCoding))); ->set("optimize_coding", baton->jpegOptimiseCoding)));
@ -848,6 +849,7 @@ class PipelineWorker : public Nan::AsyncWorker {
->set("interlace", baton->jpegProgressive) ->set("interlace", baton->jpegProgressive)
->set("no_subsample", baton->jpegChromaSubsampling == "4:4:4") ->set("no_subsample", baton->jpegChromaSubsampling == "4:4:4")
->set("trellis_quant", baton->jpegTrellisQuantisation) ->set("trellis_quant", baton->jpegTrellisQuantisation)
->set("quant_table", baton->jpegQuantisationTable)
->set("overshoot_deringing", baton->jpegOvershootDeringing) ->set("overshoot_deringing", baton->jpegOvershootDeringing)
->set("optimize_scans", baton->jpegOptimiseScans) ->set("optimize_scans", baton->jpegOptimiseScans)
->set("optimize_coding", baton->jpegOptimiseCoding)); ->set("optimize_coding", baton->jpegOptimiseCoding));
@ -927,6 +929,7 @@ class PipelineWorker : public Nan::AsyncWorker {
{"interlace", baton->jpegProgressive ? "TRUE" : "FALSE"}, {"interlace", baton->jpegProgressive ? "TRUE" : "FALSE"},
{"no_subsample", baton->jpegChromaSubsampling == "4:4:4" ? "TRUE": "FALSE"}, {"no_subsample", baton->jpegChromaSubsampling == "4:4:4" ? "TRUE": "FALSE"},
{"trellis_quant", baton->jpegTrellisQuantisation ? "TRUE" : "FALSE"}, {"trellis_quant", baton->jpegTrellisQuantisation ? "TRUE" : "FALSE"},
{"quant_table", std::to_string(baton->jpegQuantisationTable)},
{"overshoot_deringing", baton->jpegOvershootDeringing ? "TRUE": "FALSE"}, {"overshoot_deringing", baton->jpegOvershootDeringing ? "TRUE": "FALSE"},
{"optimize_scans", baton->jpegOptimiseScans ? "TRUE": "FALSE"}, {"optimize_scans", baton->jpegOptimiseScans ? "TRUE": "FALSE"},
{"optimize_coding", baton->jpegOptimiseCoding ? "TRUE": "FALSE"} {"optimize_coding", baton->jpegOptimiseCoding ? "TRUE": "FALSE"}
@ -1266,6 +1269,7 @@ NAN_METHOD(pipeline) {
baton->jpegProgressive = AttrTo<bool>(options, "jpegProgressive"); baton->jpegProgressive = AttrTo<bool>(options, "jpegProgressive");
baton->jpegChromaSubsampling = AttrAsStr(options, "jpegChromaSubsampling"); baton->jpegChromaSubsampling = AttrAsStr(options, "jpegChromaSubsampling");
baton->jpegTrellisQuantisation = AttrTo<bool>(options, "jpegTrellisQuantisation"); baton->jpegTrellisQuantisation = AttrTo<bool>(options, "jpegTrellisQuantisation");
baton->jpegQuantisationTable = AttrTo<uint32_t>(options, "jpegQuantisationTable");
baton->jpegOvershootDeringing = AttrTo<bool>(options, "jpegOvershootDeringing"); baton->jpegOvershootDeringing = AttrTo<bool>(options, "jpegOvershootDeringing");
baton->jpegOptimiseScans = AttrTo<bool>(options, "jpegOptimiseScans"); baton->jpegOptimiseScans = AttrTo<bool>(options, "jpegOptimiseScans");
baton->jpegOptimiseCoding = AttrTo<bool>(options, "jpegOptimiseCoding"); baton->jpegOptimiseCoding = AttrTo<bool>(options, "jpegOptimiseCoding");

View File

@ -102,6 +102,7 @@ struct PipelineBaton {
bool jpegProgressive; bool jpegProgressive;
std::string jpegChromaSubsampling; std::string jpegChromaSubsampling;
bool jpegTrellisQuantisation; bool jpegTrellisQuantisation;
int jpegQuantisationTable;
bool jpegOvershootDeringing; bool jpegOvershootDeringing;
bool jpegOptimiseScans; bool jpegOptimiseScans;
bool jpegOptimiseCoding; bool jpegOptimiseCoding;
@ -188,6 +189,7 @@ struct PipelineBaton {
jpegProgressive(false), jpegProgressive(false),
jpegChromaSubsampling("4:2:0"), jpegChromaSubsampling("4:2:0"),
jpegTrellisQuantisation(false), jpegTrellisQuantisation(false),
jpegQuantisationTable(0),
jpegOvershootDeringing(false), jpegOvershootDeringing(false),
jpegOptimiseScans(false), jpegOptimiseScans(false),
jpegOptimiseCoding(true), jpegOptimiseCoding(true),

View File

@ -389,6 +389,16 @@ describe('Input/output', function () {
}); });
}); });
describe('Invalid JPEG quantisation table', function () {
[-1, 88.2, 'test'].forEach(function (table) {
it(table.toString(), function () {
assert.throws(function () {
sharp().jpeg({ quantisationTable: table });
});
});
});
});
it('Progressive JPEG image', function (done) { it('Progressive JPEG image', function (done) {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(320, 240) .resize(320, 240)
@ -856,6 +866,37 @@ describe('Input/output', function () {
}); });
}); });
it('Specifying quantisation table provides different JPEG', function (done) {
// First generate with default quantisation table
sharp(fixtures.inputJpg)
.resize(320, 240)
.jpeg({ optimiseCoding: false })
.toBuffer(function (err, withDefaultQuantisationTable, withInfo) {
if (err) throw err;
assert.strictEqual(true, withDefaultQuantisationTable.length > 0);
assert.strictEqual(withDefaultQuantisationTable.length, withInfo.size);
assert.strictEqual('jpeg', withInfo.format);
assert.strictEqual(320, withInfo.width);
assert.strictEqual(240, withInfo.height);
// Then generate with different quantisation table
sharp(fixtures.inputJpg)
.resize(320, 240)
.jpeg({ optimiseCoding: false, quantisationTable: 3 })
.toBuffer(function (err, withQuantTable3, withoutInfo) {
if (err) throw err;
assert.strictEqual(true, withQuantTable3.length > 0);
assert.strictEqual(withQuantTable3.length, withoutInfo.size);
assert.strictEqual('jpeg', withoutInfo.format);
assert.strictEqual(320, withoutInfo.width);
assert.strictEqual(240, withoutInfo.height);
// Verify image is same (as mozjpeg may not be present) size or less
assert.strictEqual(true, withQuantTable3.length <= withDefaultQuantisationTable.length);
done();
});
});
});
it('Convert SVG to PNG at default 72DPI', function (done) { it('Convert SVG to PNG at default 72DPI', function (done) {
sharp(fixtures.inputSvg) sharp(fixtures.inputSvg)
.resize(1024) .resize(1024)