From 3f5e38bb62e63d473a1a2f33d47d3a6b7263aead Mon Sep 17 00:00:00 2001 From: Lovell Fuller Date: Sat, 22 Oct 2016 10:23:09 +0100 Subject: [PATCH] Deprecate output format option functions. Access is now via options of existing output format functions. e.g. use .jpeg({quality: n}) instead of .jpeg().quality(n) --- docs/api.md | 97 +++++------- docs/changelog.md | 7 + index.js | 380 ++++++++++++++++++++++++++++++---------------- src/pipeline.cc | 90 +++++------ src/pipeline.h | 38 +++-- 5 files changed, 361 insertions(+), 251 deletions(-) diff --git a/docs/api.md b/docs/api.md index dc063b5e..d83e4990 100644 --- a/docs/api.md +++ b/docs/api.md @@ -590,18 +590,52 @@ Write image data to a Buffer, the format of which will match the input image by A Promises/A+ promise is returned when `callback` is not provided. -#### jpeg() +#### jpeg([options]) Use JPEG format for the output image. -#### png() +`options`, if present, is an Object with the following optional attributes: + +* `quality` is an integral Number between 1 and 100, default 80. Using quality >90 forces a `chromaSubsampling` value of '4:4:4'. +* `progressive` is a Boolean to control the use of progressive (interlace) scan, default false. +* `chromaSubsampling` is a String with the value '4:2:0' (default) or '4:4:4' to control [chroma subsampling](http://en.wikipedia.org/wiki/Chroma_subsampling). +* `force` is a Boolean, where true (default) will force the use of JPEG output and false will use the input format. + +The following, additional options require libvips to have been compiled with mozjpeg support: + +* `trellisQuantisation` / `trellisQuantization` is a Boolean, default false, to control the use of [trellis quantisation](http://en.wikipedia.org/wiki/Trellis_quantization). +* `overshootDeringing` is a Boolean, default false, to reduce the effects of [ringing](http://en.wikipedia.org/wiki/Ringing_%28signal%29). +* `optimiseScans` / `optimizeScans` is a Boolean, default false, when true calculates which spectrum of DCT coefficients uses the fewest bits for each progressive scan. + +#### png([options]) Use PNG format for the output image. -#### webp() +`options`, if present, is an Object with the following optional attributes: + +* `progressive` is a Boolean to control the use of progressive (interlace) scan, default false. +* `compressionLevel` is an integral Number between 0 and 9, default 6, to set the _zlib_ compression level. +* `adaptiveFiltering` is a Boolean to control [adaptive row filtering](https://en.wikipedia.org/wiki/Portable_Network_Graphics#Filtering), true for adaptive (default), false for none. +* `force` is a Boolean, where true (default) will force the use of PNG output and false will use the input format. + +#### webp([options]) Use WebP format for the output image. +`options`, if present, is an Object with the following optional attributes: + +* `quality` is an integral Number between 1 and 100, default 80. +* `force` is a Boolean, where true (default) will force the use of WebP output and false will use the input format. + +#### tiff([options]) + +Use TIFF format for the output image. + +`options`, if present, is an Object with the following optional attributes: + +* `quality` is an integral Number between 1 and 100, default 80. +* `force` is a Boolean, where true (default) will force the use of TIFF output and false will use the input format. + #### raw() Provide raw, uncompressed uint8 (unsigned char) image data for Buffer and Stream based output. @@ -612,22 +646,12 @@ The number of channels depends on the input image and selected options. * 3 channels for colour images without alpha transparency, with bytes ordered \[red, green, blue, red, green, blue, etc.\]). * 4 channels for colour images with alpha transparency, with bytes ordered \[red, green, blue, alpha, red, green, blue, alpha, etc.\]. -#### toFormat(format) +#### toFormat(format, [options]) Convenience method for the above output format methods, where `format` is either: * an attribute of the `sharp.format` Object e.g. `sharp.format.jpeg`, or -* a String containing `jpeg`, `png`, `webp` or `raw`. - -#### quality(quality) - -The output quality to use for lossy JPEG, WebP and TIFF output formats. The default quality is `80`. - -`quality` is a Number between 1 and 100. - -#### progressive() - -Use progressive (interlace) scan for JPEG and PNG output. This typically reduces compression performance by 30% but results in an image that can be rendered sooner when decompressed. +* a String containing `jpeg`, `png`, `webp`, `tiff` or `raw`. #### withMetadata([metadata]) @@ -666,49 +690,6 @@ sharp('input.tiff') }); ``` -#### withoutChromaSubsampling() - -Disable the use of [chroma subsampling](http://en.wikipedia.org/wiki/Chroma_subsampling) with JPEG output (4:4:4). - -This can improve colour representation at higher quality settings (90+), -but usually increases output file size and typically reduces performance by 25%. - -The default behaviour is to use chroma subsampling (4:2:0). - -#### compressionLevel(compressionLevel) - -An advanced setting for the _zlib_ compression level of the lossless PNG output format. The default level is `6`. - -`compressionLevel` is a Number between 0 and 9. - -#### withoutAdaptiveFiltering() - -An advanced setting to disable adaptive row filtering for the lossless PNG output format. - -#### trellisQuantisation() / trellisQuantization() - -_Requires libvips to have been compiled with mozjpeg support_ - -An advanced setting to apply the use of -[trellis quantisation](http://en.wikipedia.org/wiki/Trellis_quantization) with JPEG output. -Reduces file size and slightly increases relative quality at the cost of increased compression time. - -#### overshootDeringing() - -_Requires libvips to have been compiled with mozjpeg support_ - -An advanced setting to reduce the effects of -[ringing](http://en.wikipedia.org/wiki/Ringing_%28signal%29) in JPEG output, -in particular where black text appears on a white background (or vice versa). - -#### optimiseScans() / optimizeScans() - -_Requires libvips to have been compiled with mozjpeg support_ - -An advanced setting for progressive (interlace) JPEG output. -Calculates which spectrum of DCT coefficients uses the fewest bits. -Usually reduces file size at the cost of increased compression time. - ### Attributes #### format diff --git a/docs/changelog.md b/docs/changelog.md index bad35e34..0c724fba 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -6,6 +6,13 @@ Requires libvips v8.4.2 #### v0.17.0 - TBD +* Deprecate the following output format "option" functions: + quality, progressive, compressionLevel, withoutAdaptiveFiltering, + withoutChromaSubsampling, trellisQuantisation, trellisQuantization, + overshootDeringing, optimiseScans and optimizeScans. + Access to these is now via output format functions, for example `quality(n)` + is now `jpeg({quality: n})` and/or `webp({quality: n})`. + * Ensure support for embedded base64 PNG and JPEG images within an SVG. [#601](https://github.com/lovell/sharp/issues/601) [@dynamite-ready](https://github.com/dynamite-ready) diff --git a/index.js b/index.js index 56c45ae9..3a520614 100644 --- a/index.js +++ b/index.js @@ -87,30 +87,34 @@ var Sharp = function(input, options) { booleanBufferIn: null, booleanFileIn: '', joinChannelIn: [], + extractChannel: -1, + colourspace: 'srgb', // overlay overlayGravity: 0, overlayXOffset : -1, overlayYOffset : -1, overlayTile: false, overlayCutout: false, - // output options - formatOut: 'input', + // output fileOut: '', - progressive: false, - quality: 80, - compressionLevel: 6, - withoutAdaptiveFiltering: false, - withoutChromaSubsampling: false, - trellisQuantisation: false, - overshootDeringing: false, - optimiseScans: false, + formatOut: 'input', streamOut: false, withMetadata: false, withMetadataOrientation: -1, + // output format + jpegQuality: 80, + jpegProgressive: false, + jpegChromaSubsampling: '4:2:0', + jpegTrellisQuantisation: false, + jpegOvershootDeringing: false, + jpegOptimiseScans: false, + pngProgressive: false, + pngCompressionLevel: 6, + pngAdaptiveFiltering: true, + webpQuality: 80, + tiffQuality: 80, tileSize: 256, tileOverlap: 0, - extractChannel: -1, - colourspace: 'srgb', // Function to notify of queue length changes queueListener: function(queueLength) { module.exports.queue.emit('change', queueLength); @@ -146,6 +150,9 @@ var isDefined = function(val) { var isObject = function(val) { return typeof val === 'object'; }; +var isFunction = function(val) { + return typeof val === 'function'; +}; var isBoolean = function(val) { return typeof val === 'boolean'; }; @@ -661,80 +668,63 @@ Sharp.prototype.toColourspace = function(colourspace) { }; Sharp.prototype.toColorspace = Sharp.prototype.toColourspace; -Sharp.prototype.progressive = function(progressive) { - this.options.progressive = isBoolean(progressive) ? progressive : true; - return this; -}; - Sharp.prototype.sequentialRead = function(sequentialRead) { this.options.sequentialRead = isBoolean(sequentialRead) ? sequentialRead : true; return this; }; -Sharp.prototype.quality = function(quality) { - if (isInteger(quality) && inRange(quality, 1, 100)) { - this.options.quality = quality; - } else { - throw new Error('Invalid quality (1 to 100) ' + quality); - } +// Deprecated output options +Sharp.prototype.quality = util.deprecate(function(quality) { + var formatOut = this.options.formatOut; + var options = { quality: quality }; + this.jpeg(options).webp(options).tiff(options); + this.options.formatOut = formatOut; return this; -}; - -/* - zlib compression level for PNG output -*/ -Sharp.prototype.compressionLevel = function(compressionLevel) { - if (isInteger(compressionLevel) && inRange(compressionLevel, 0, 9)) { - this.options.compressionLevel = compressionLevel; - } else { - throw new Error('Invalid compressionLevel (0 to 9) ' + compressionLevel); - } +}, 'quality: use jpeg({ quality: ... }), webp({ quality: ... }) and/or tiff({ quality: ... }) instead'); +Sharp.prototype.progressive = util.deprecate(function(progressive) { + var formatOut = this.options.formatOut; + var options = { progressive: (progressive !== false) }; + this.jpeg(options).png(options); + this.options.formatOut = formatOut; return this; -}; - -/* - Disable the use of adaptive row filtering for PNG output -*/ -Sharp.prototype.withoutAdaptiveFiltering = function(withoutAdaptiveFiltering) { - this.options.withoutAdaptiveFiltering = isBoolean(withoutAdaptiveFiltering) ? withoutAdaptiveFiltering : true; +}, 'progressive: use jpeg({ progressive: ... }) and/or png({ progressive: ... }) instead'); +Sharp.prototype.compressionLevel = util.deprecate(function(compressionLevel) { + var formatOut = this.options.formatOut; + this.png({ compressionLevel: compressionLevel }); + this.options.formatOut = formatOut; return this; -}; - -/* - Disable the use of chroma subsampling for JPEG output -*/ -Sharp.prototype.withoutChromaSubsampling = function(withoutChromaSubsampling) { - this.options.withoutChromaSubsampling = isBoolean(withoutChromaSubsampling) ? withoutChromaSubsampling : true; +}, 'compressionLevel: use png({ compressionLevel: ... }) instead'); +Sharp.prototype.withoutAdaptiveFiltering = util.deprecate(function(withoutAdaptiveFiltering) { + var formatOut = this.options.formatOut; + this.png({ adaptiveFiltering: (withoutAdaptiveFiltering === false) }); + this.options.formatOut = formatOut; return this; -}; - -/* - Apply trellis quantisation to JPEG output - requires mozjpeg 3.0+ -*/ -Sharp.prototype.trellisQuantisation = function(trellisQuantisation) { - this.options.trellisQuantisation = isBoolean(trellisQuantisation) ? trellisQuantisation : true; +}, 'withoutAdaptiveFiltering: use png({ adaptiveFiltering: ... }) instead'); +Sharp.prototype.withoutChromaSubsampling = util.deprecate(function(withoutChromaSubsampling) { + var formatOut = this.options.formatOut; + this.jpeg({ chromaSubsampling: (withoutChromaSubsampling === false) ? '4:2:0' : '4:4:4' }); + this.options.formatOut = formatOut; return this; -}; +}, 'withoutChromaSubsampling: use jpeg({ chromaSubsampling: "4:4:4" }) instead'); +Sharp.prototype.trellisQuantisation = util.deprecate(function(trellisQuantisation) { + var formatOut = this.options.formatOut; + this.jpeg({ trellisQuantisation: (trellisQuantisation !== false) }); + this.options.formatOut = formatOut; + return this; +}, 'trellisQuantisation: use jpeg({ trellisQuantisation: ... }) instead'); Sharp.prototype.trellisQuantization = Sharp.prototype.trellisQuantisation; - -/* - Apply overshoot deringing to JPEG output - requires mozjpeg 3.0+ -*/ -Sharp.prototype.overshootDeringing = function(overshootDeringing) { - this.options.overshootDeringing = isBoolean(overshootDeringing) ? overshootDeringing : true; +Sharp.prototype.overshootDeringing = util.deprecate(function(overshootDeringing) { + var formatOut = this.options.formatOut; + this.jpeg({ overshootDeringing: (overshootDeringing !== false) }); + this.options.formatOut = formatOut; return this; -}; - -/* - Optimise scans in progressive JPEG output - requires mozjpeg 3.0+ -*/ -Sharp.prototype.optimiseScans = function(optimiseScans) { - this.options.optimiseScans = isBoolean(optimiseScans) ? optimiseScans : true; - if (this.options.optimiseScans) { - this.progressive(); - } +}, 'overshootDeringing: use jpeg({ overshootDeringing: ... }) instead'); +Sharp.prototype.optimiseScans = util.deprecate(function(optimiseScans) { + var formatOut = this.options.formatOut; + this.jpeg({ optimiseScans: (optimiseScans !== false) }); + this.options.formatOut = formatOut; return this; -}; +}, 'optimiseScans: use jpeg({ optimiseScans: ... }) instead'); Sharp.prototype.optimizeScans = Sharp.prototype.optimiseScans; /* @@ -924,13 +914,14 @@ Sharp.prototype.limitInputPixels = function(limit) { return this; }; -/* - Write output image data to a file -*/ +/** + * Write output image data to a file + * @throws {Error} if an attempt has been made to force Buffer/Stream output type + */ Sharp.prototype.toFile = function(fileOut, callback) { if (!fileOut || fileOut.length === 0) { var errOutputInvalid = new Error('Invalid output'); - if (typeof callback === 'function') { + if (isFunction(callback)) { callback(errOutputInvalid); } else { return BluebirdPromise.reject(errOutputInvalid); @@ -938,7 +929,7 @@ Sharp.prototype.toFile = function(fileOut, callback) { } else { if (this.options.input.file === fileOut) { var errOutputIsInput = new Error('Cannot use same file for input and output'); - if (typeof callback === 'function') { + if (isFunction(callback)) { callback(errOutputIsInput); } else { return BluebirdPromise.reject(errOutputIsInput); @@ -951,61 +942,188 @@ Sharp.prototype.toFile = function(fileOut, callback) { return this; }; -/* - Write output to a Buffer -*/ +/** + * Write output to a Buffer. + * @param {Function} [callback] + * @returns {Promise} when no callback is provided + */ Sharp.prototype.toBuffer = function(callback) { return this._pipeline(callback); }; -/* - Force JPEG output -*/ -Sharp.prototype.jpeg = function() { - this.options.formatOut = 'jpeg'; +/** + * Update the output format unless options.force is false, + * in which case revert to input format. + * @private + * @param {String} formatOut + * @param {Object} [options] + * @param {Boolean} [options.force=true] - force output format, otherwise attempt to use input format + * @returns {Object} this + */ +Sharp.prototype._updateFormatOut = function(formatOut, options) { + this.options.formatOut = (isObject(options) && options.force === false) ? 'input' : formatOut; return this; }; -/* - Force PNG output -*/ -Sharp.prototype.png = function() { - this.options.formatOut = 'png'; - return this; -}; - -/* - Force WebP output -*/ -Sharp.prototype.webp = function() { - this.options.formatOut = 'webp'; - return this; -}; - -/* - Force raw, uint8 output -*/ -Sharp.prototype.raw = function() { - this.options.formatOut = 'raw'; - return this; -}; - -/* - Force output to a given format - @param format is either the id as a String or an Object with an 'id' attribute -*/ -Sharp.prototype.toFormat = function(formatOut) { - if (isObject(formatOut) && isString(formatOut.id)) { - this.options.formatOut = formatOut.id; - } else if ( - isString(formatOut) && - contains(formatOut, ['jpeg', 'png', 'webp', 'raw', 'tiff', 'dz', 'input']) - ) { - this.options.formatOut = formatOut; +/** + * Update a Boolean attribute of the this.options Object. + * @private + * @param {String} key + * @param {Boolean} val + * @returns {void} + */ +Sharp.prototype._setBooleanOption = function(key, val) { + if (isBoolean(val)) { + this.options[key] = val; } else { - throw new Error('Unsupported output format ' + formatOut); + throw new Error('Invalid ' + key + ' (boolean) ' + val); } - return this; +}; + +/** + * Use these JPEG options for output image. + * @param {Object} [options] - output options + * @param {Number} [options.quality=80] - quality, integer 1-100 + * @param {Boolean} [options.progressive=false] - use progressive (interlace) scan + * @param {String} [options.chromaSubsampling='4:2:0'] - set to '4:4:4' to prevent chroma subsampling when quality <= 90 + * @param {Boolean} [trellisQuantisation=false] - apply trellis quantisation, requires mozjpeg + * @param {Boolean} [overshootDeringing=false] - apply overshoot deringing, requires mozjpeg + * @param {Boolean} [optimiseScans=false] - optimise progressive scans, assumes progressive=true, requires mozjpeg + * @param {Boolean} [options.force=true] - force JPEG output, otherwise attempt to use input format + * @returns {Object} this + * @throws {Error} Invalid options + */ +Sharp.prototype.jpeg = function(options) { + if (isObject(options)) { + if (isDefined(options.quality)) { + if (isInteger(options.quality) && inRange(options.quality, 1, 100)) { + this.options.jpegQuality = options.quality; + } else { + throw new Error('Invalid quality (integer, 1-100) ' + options.quality); + } + } + if (isDefined(options.progressive)) { + this._setBooleanOption('jpegProgressive', options.progressive); + } + if (isDefined(options.chromaSubsampling)) { + if (isString(options.chromaSubsampling) && contains(options.chromaSubsampling, ['4:2:0', '4:4:4'])) { + this.options.jpegChromaSubsampling = options.chromaSubsampling; + } else { + throw new Error('Invalid chromaSubsampling (4:2:0, 4:4:4) ' + options.chromaSubsampling); + } + } + options.trellisQuantisation = options.trellisQuantisation || options.trellisQuantization; + if (isDefined(options.trellisQuantisation)) { + this._setBooleanOption('jpegTrellisQuantisation', options.trellisQuantisation); + } + if (isDefined(options.overshootDeringing)) { + this._setBooleanOption('jpegOvershootDeringing', options.overshootDeringing); + } + options.optimiseScans = options.optimiseScans || options.optimizeScans; + if (isDefined(options.optimiseScans)) { + this._setBooleanOption('jpegOptimiseScans', options.optimiseScans); + if (options.optimiseScans) { + this.options.jpegProgressive = true; + } + } + } + return this._updateFormatOut('jpeg', options); +}; + +/** + * Use these PNG options for output image. + * @param {Object} [options] + * @param {Boolean} [options.progressive=false] - use progressive (interlace) scan + * @param {Number} [options.compressionLevel=6] - zlib compression level + * @param {Boolean} [options.adaptiveFiltering=true] - use adaptive row filtering + * @param {Boolean} [options.force=true] - force PNG output, otherwise attempt to use input format + * @returns {Object} this + * @throws {Error} Invalid options + */ +Sharp.prototype.png = function(options) { + if (isObject(options)) { + if (isDefined(options.progressive)) { + this._setBooleanOption('pngProgressive', options.progressive); + } + if (isDefined(options.compressionLevel)) { + if (isInteger(options.compressionLevel) && inRange(options.compressionLevel, 0, 9)) { + this.options.pngCompressionLevel = options.compressionLevel; + } else { + throw new Error('Invalid compressionLevel (integer, 0-9) ' + options.compressionLevel); + } + } + if (isDefined(options.adaptiveFiltering)) { + this._setBooleanOption('pngAdaptiveFiltering', options.adaptiveFiltering); + } + } + return this._updateFormatOut('png', options); +}; + +/** + * Use these WebP options for output image. + * @param {Object} [options] - output options + * @param {Number} [options.quality=80] - quality, integer 1-100 + * @param {Boolean} [options.force=true] - force WebP output, otherwise attempt to use input format + * @returns {Object} this + * @throws {Error} Invalid options + */ +Sharp.prototype.webp = function(options) { + if (isObject(options)) { + if (isDefined(options.quality)) { + if (isInteger(options.quality) && inRange(options.quality, 1, 100)) { + this.options.webpQuality = options.quality; + } else { + throw new Error('Invalid quality (integer, 1-100) ' + options.quality); + } + } + } + return this._updateFormatOut('webp', options); +}; + +/** + * Use these TIFF options for output image. + * @param {Object} [options] - output options + * @param {Number} [options.quality=80] - quality, integer 1-100 + * @param {Boolean} [options.force=true] - force TIFF output, otherwise attempt to use input format + * @returns {Object} this + * @throws {Error} Invalid options + */ +Sharp.prototype.tiff = function(options) { + if (isObject(options)) { + if (isDefined(options.quality)) { + if (isInteger(options.quality) && inRange(options.quality, 1, 100)) { + this.options.tiffQuality = options.quality; + } else { + throw new Error('Invalid quality (integer, 1-100) ' + options.quality); + } + } + } + return this._updateFormatOut('tiff', options); +}; + +/** + * Force output to be raw, uncompressed uint8 pixel data. + * @returns {Object} this + */ +Sharp.prototype.raw = function() { + return this._updateFormatOut('raw'); +}; + +/** + * Force output to a given format. + * @param {String|Object} format - as a String or an Object with an 'id' attribute + * @param {Object} options - output options + * @returns {Object} this + * @throws {Error} unsupported format or options + */ +Sharp.prototype.toFormat = function(format, options) { + if (isObject(format) && isString(format.id)) { + format = format.id; + } + if (!contains(format, ['jpeg', 'png', 'webp', 'tiff', 'raw'])) { + throw new Error('Unsupported output format ' + format); + } + return this[format](options); }; /* @@ -1164,14 +1282,14 @@ Sharp.prototype.clone = function() { Get and set cache memory, file and item limits */ module.exports.cache = function(options) { - if (typeof options === 'boolean') { + if (isBoolean(options)) { if (options) { // Default cache settings of 50MB, 20 files, 100 items return sharp.cache(50, 20, 100); } else { return sharp.cache(0, 0, 0); } - } else if (typeof options === 'object') { + } else if (isObject(options)) { return sharp.cache(options.memory, options.files, options.items); } else { return sharp.cache(); @@ -1184,10 +1302,7 @@ module.exports.cache(true); Get and set size of thread pool */ module.exports.concurrency = function(concurrency) { - if (typeof concurrency !== 'number' || Number.isNaN(concurrency)) { - concurrency = null; - } - return sharp.concurrency(concurrency); + return sharp.concurrency(isInteger(concurrency) ? concurrency : null); }; /* @@ -1201,10 +1316,7 @@ module.exports.counters = function() { Get and set use of SIMD vector unit instructions */ module.exports.simd = function(simd) { - if (typeof simd !== 'boolean') { - simd = null; - } - return sharp.simd(simd); + return sharp.simd(isBoolean(simd) ? simd : null); }; // Switch off default module.exports.simd(false); diff --git a/src/pipeline.cc b/src/pipeline.cc index 3118ae15..3c3c6b4d 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -709,13 +709,13 @@ class PipelineWorker : public Nan::AsyncWorker { // Write JPEG to buffer VipsArea *area = VIPS_AREA(image.jpegsave_buffer(VImage::option() ->set("strip", !baton->withMetadata) - ->set("Q", baton->quality) + ->set("Q", baton->jpegQuality) + ->set("interlace", baton->jpegProgressive) + ->set("no_subsample", baton->jpegChromaSubsampling == "4:4:4") + ->set("trellis_quant", baton->jpegTrellisQuantisation) + ->set("overshoot_deringing", baton->jpegOvershootDeringing) + ->set("optimize_scans", baton->jpegOptimiseScans) ->set("optimize_coding", TRUE) - ->set("no_subsample", baton->withoutChromaSubsampling) - ->set("trellis_quant", baton->trellisQuantisation) - ->set("overshoot_deringing", baton->overshootDeringing) - ->set("optimize_scans", baton->optimiseScans) - ->set("interlace", baton->progressive) )); baton->bufferOut = static_cast(area->data); baton->bufferOutLength = area->length; @@ -734,10 +734,10 @@ class PipelineWorker : public Nan::AsyncWorker { } // Write PNG to buffer VipsArea *area = VIPS_AREA(image.pngsave_buffer(VImage::option() - ->set("compression", baton->compressionLevel) - ->set("interlace", baton->progressive) - ->set("filter", baton->withoutAdaptiveFiltering ? - VIPS_FOREIGN_PNG_FILTER_NONE : VIPS_FOREIGN_PNG_FILTER_ALL) + ->set("interlace", baton->pngProgressive) + ->set("compression", baton->pngCompressionLevel) + ->set("filter", baton->pngAdaptiveFiltering ? + VIPS_FOREIGN_PNG_FILTER_ALL : VIPS_FOREIGN_PNG_FILTER_NONE ) )); baton->bufferOut = static_cast(area->data); baton->bufferOutLength = area->length; @@ -748,7 +748,7 @@ class PipelineWorker : public Nan::AsyncWorker { // Write WEBP to buffer VipsArea *area = VIPS_AREA(image.webpsave_buffer(VImage::option() ->set("strip", !baton->withMetadata) - ->set("Q", baton->quality) + ->set("Q", baton->webpQuality) )); baton->bufferOut = static_cast(area->data); baton->bufferOutLength = area->length; @@ -784,26 +784,26 @@ class PipelineWorker : public Nan::AsyncWorker { } } else { // File output - bool isJpeg = sharp::IsJpeg(baton->fileOut); - bool isPng = sharp::IsPng(baton->fileOut); - bool isWebp = sharp::IsWebp(baton->fileOut); - bool isTiff = sharp::IsTiff(baton->fileOut); - bool isDz = sharp::IsDz(baton->fileOut); - bool isDzZip = sharp::IsDzZip(baton->fileOut); - bool isV = sharp::IsV(baton->fileOut); - bool matchInput = baton->formatOut == "input" && + bool const isJpeg = sharp::IsJpeg(baton->fileOut); + bool const isPng = sharp::IsPng(baton->fileOut); + bool const isWebp = sharp::IsWebp(baton->fileOut); + bool const isTiff = sharp::IsTiff(baton->fileOut); + bool const isDz = sharp::IsDz(baton->fileOut); + bool const isDzZip = sharp::IsDzZip(baton->fileOut); + bool const isV = sharp::IsV(baton->fileOut); + bool const matchInput = baton->formatOut == "input" && !(isJpeg || isPng || isWebp || isTiff || isDz || isDzZip || isV); if (baton->formatOut == "jpeg" || isJpeg || (matchInput && inputImageType == ImageType::JPEG)) { // Write JPEG to file image.jpegsave(const_cast(baton->fileOut.data()), VImage::option() ->set("strip", !baton->withMetadata) - ->set("Q", baton->quality) + ->set("Q", baton->jpegQuality) + ->set("interlace", baton->jpegProgressive) + ->set("no_subsample", baton->jpegChromaSubsampling == "4:4:4") + ->set("trellis_quant", baton->jpegTrellisQuantisation) + ->set("overshoot_deringing", baton->jpegOvershootDeringing) + ->set("optimize_scans", baton->jpegOptimiseScans) ->set("optimize_coding", TRUE) - ->set("no_subsample", baton->withoutChromaSubsampling) - ->set("trellis_quant", baton->trellisQuantisation) - ->set("overshoot_deringing", baton->overshootDeringing) - ->set("optimize_scans", baton->optimiseScans) - ->set("interlace", baton->progressive) ); baton->formatOut = "jpeg"; baton->channels = std::min(baton->channels, 3); @@ -814,24 +814,24 @@ class PipelineWorker : public Nan::AsyncWorker { } // Write PNG to file image.pngsave(const_cast(baton->fileOut.data()), VImage::option() - ->set("compression", baton->compressionLevel) - ->set("interlace", baton->progressive) - ->set("filter", baton->withoutAdaptiveFiltering ? - VIPS_FOREIGN_PNG_FILTER_NONE : VIPS_FOREIGN_PNG_FILTER_ALL) + ->set("interlace", baton->pngProgressive) + ->set("compression", baton->pngCompressionLevel) + ->set("filter", baton->pngAdaptiveFiltering ? + VIPS_FOREIGN_PNG_FILTER_ALL : VIPS_FOREIGN_PNG_FILTER_NONE ) ); baton->formatOut = "png"; } else if (baton->formatOut == "webp" || isWebp || (matchInput && inputImageType == ImageType::WEBP)) { // Write WEBP to file image.webpsave(const_cast(baton->fileOut.data()), VImage::option() ->set("strip", !baton->withMetadata) - ->set("Q", baton->quality) + ->set("Q", baton->webpQuality) ); baton->formatOut = "webp"; } else if (baton->formatOut == "tiff" || isTiff || (matchInput && inputImageType == ImageType::TIFF)) { // Write TIFF to file image.tiffsave(const_cast(baton->fileOut.data()), VImage::option() ->set("strip", !baton->withMetadata) - ->set("Q", baton->quality) + ->set("Q", baton->tiffQuality) ->set("compression", VIPS_FOREIGN_TIFF_COMPRESSION_JPEG) ); baton->formatOut = "tiff"; @@ -1121,23 +1121,27 @@ NAN_METHOD(pipeline) { baton->convKernel[i] = AttrTo(kdata, i); } } - // Output options - baton->progressive = AttrTo(options, "progressive"); - baton->quality = AttrTo(options, "quality"); - baton->compressionLevel = AttrTo(options, "compressionLevel"); - baton->withoutAdaptiveFiltering = AttrTo(options, "withoutAdaptiveFiltering"); - baton->withoutChromaSubsampling = AttrTo(options, "withoutChromaSubsampling"); - baton->trellisQuantisation = AttrTo(options, "trellisQuantisation"); - baton->overshootDeringing = AttrTo(options, "overshootDeringing"); - baton->optimiseScans = AttrTo(options, "optimiseScans"); - baton->withMetadata = AttrTo(options, "withMetadata"); - baton->withMetadataOrientation = AttrTo(options, "withMetadataOrientation"); baton->colourspace = sharp::GetInterpretation(AttrAsStr(options, "colourspace")); - if(baton->colourspace == VIPS_INTERPRETATION_ERROR) + if (baton->colourspace == VIPS_INTERPRETATION_ERROR) { baton->colourspace = VIPS_INTERPRETATION_sRGB; + } // Output baton->formatOut = AttrAsStr(options, "formatOut"); baton->fileOut = AttrAsStr(options, "fileOut"); + baton->withMetadata = AttrTo(options, "withMetadata"); + baton->withMetadataOrientation = AttrTo(options, "withMetadataOrientation"); + // Format-specific + baton->jpegQuality = AttrTo(options, "jpegQuality"); + baton->jpegProgressive = AttrTo(options, "jpegProgressive"); + baton->jpegChromaSubsampling = AttrAsStr(options, "jpegChromaSubsampling"); + baton->jpegTrellisQuantisation = AttrTo(options, "jpegTrellisQuantisation"); + baton->jpegOvershootDeringing = AttrTo(options, "jpegOvershootDeringing"); + baton->jpegOptimiseScans = AttrTo(options, "jpegOptimiseScans"); + baton->pngProgressive = AttrTo(options, "pngProgressive"); + baton->pngCompressionLevel = AttrTo(options, "pngCompressionLevel"); + baton->pngAdaptiveFiltering = AttrTo(options, "pngAdaptiveFiltering"); + baton->webpQuality = AttrTo(options, "webpQuality"); + baton->tiffQuality = AttrTo(options, "tiffQuality"); // Tile output baton->tileSize = AttrTo(options, "tileSize"); baton->tileOverlap = AttrTo(options, "tileOverlap"); diff --git a/src/pipeline.h b/src/pipeline.h index b647e2bf..f4d0f461 100644 --- a/src/pipeline.h +++ b/src/pipeline.h @@ -71,16 +71,19 @@ struct PipelineBaton { int extendBottom; int extendLeft; int extendRight; - bool progressive; bool withoutEnlargement; VipsAccess accessMethod; - int quality; - int compressionLevel; - bool withoutAdaptiveFiltering; - bool withoutChromaSubsampling; - bool trellisQuantisation; - bool overshootDeringing; - bool optimiseScans; + int jpegQuality; + bool jpegProgressive; + std::string jpegChromaSubsampling; + bool jpegTrellisQuantisation; + bool jpegOvershootDeringing; + bool jpegOptimiseScans; + bool pngProgressive; + int pngCompressionLevel; + bool pngAdaptiveFiltering; + int webpQuality; + int tiffQuality; std::string err; bool withMetadata; int withMetadataOrientation; @@ -135,15 +138,18 @@ struct PipelineBaton { extendBottom(0), extendLeft(0), extendRight(0), - progressive(false), withoutEnlargement(false), - quality(80), - compressionLevel(6), - withoutAdaptiveFiltering(false), - withoutChromaSubsampling(false), - trellisQuantisation(false), - overshootDeringing(false), - optimiseScans(false), + jpegQuality(80), + jpegProgressive(false), + jpegChromaSubsampling("4:2:0"), + jpegTrellisQuantisation(false), + jpegOvershootDeringing(false), + jpegOptimiseScans(false), + pngProgressive(false), + pngCompressionLevel(6), + pngAdaptiveFiltering(true), + webpQuality(80), + tiffQuality(80), withMetadata(false), withMetadataOrientation(-1), convKernelWidth(0),