diff --git a/docs/api-output.md b/docs/api-output.md index db8229f1..c2a5344a 100644 --- a/docs/api-output.md +++ b/docs/api-output.md @@ -157,8 +157,6 @@ Returns **Sharp** Use these JPEG options for output image. -Some of these options require the use of a globally-installed libvips compiled with support for mozjpeg. - ### Parameters - `options` **[Object][6]?** output options @@ -167,12 +165,13 @@ Some of these options require the use of a globally-installed libvips compiled w - `options.chromaSubsampling` **[string][2]** set to '4:4:4' to prevent chroma subsampling otherwise defaults to '4:2:0' chroma subsampling (optional, default `'4:2:0'`) - `options.optimiseCoding` **[boolean][7]** optimise Huffman coding tables (optional, default `true`) - `options.optimizeCoding` **[boolean][7]** alternative spelling of optimiseCoding (optional, default `true`) - - `options.trellisQuantisation` **[boolean][7]** apply trellis quantisation, requires libvips compiled with support for mozjpeg (optional, default `false`) - - `options.overshootDeringing` **[boolean][7]** apply overshoot deringing, requires libvips compiled with support for mozjpeg (optional, default `false`) - - `options.optimiseScans` **[boolean][7]** optimise progressive scans, forces progressive, requires libvips compiled with support for mozjpeg (optional, default `false`) - - `options.optimizeScans` **[boolean][7]** alternative spelling of optimiseScans, requires libvips compiled with support for mozjpeg (optional, default `false`) - - `options.quantisationTable` **[number][9]** quantization table to use, integer 0-8, requires libvips compiled with support for mozjpeg (optional, default `0`) - - `options.quantizationTable` **[number][9]** alternative spelling of quantisationTable, requires libvips compiled with support for mozjpeg (optional, default `0`) + - `options.mozjpeg` **[boolean][7]** use mozjpeg defaults, equivalent to `{ trellisQuantisation: true, overshootDeringing: true, optimiseScans: true, quantisationTable: 3 }` (optional, default `false`) + - `options.trellisQuantisation` **[boolean][7]** apply trellis quantisation (optional, default `false`) + - `options.overshootDeringing` **[boolean][7]** apply overshoot deringing (optional, default `false`) + - `options.optimiseScans` **[boolean][7]** optimise progressive scans, forces progressive (optional, default `false`) + - `options.optimizeScans` **[boolean][7]** alternative spelling of optimiseScans (optional, default `false`) + - `options.quantisationTable` **[number][9]** quantization table to use, integer 0-8 (optional, default `0`) + - `options.quantizationTable` **[number][9]** alternative spelling of quantisationTable (optional, default `0`) - `options.force` **[boolean][7]** force JPEG output, otherwise attempt to use input format (optional, default `true`) ### Examples @@ -187,6 +186,13 @@ const data = await sharp(input) .toBuffer(); ``` +```javascript +// Use mozjpeg to reduce output JPEG file size (slower) +const data = await sharp(input) + .jpeg({ mozjpeg: true }) + .toBuffer(); +``` + - Throws **[Error][4]** Invalid options Returns **Sharp** @@ -195,33 +201,39 @@ Returns **Sharp** Use these PNG options for output image. -PNG output is always full colour at 8 or 16 bits per pixel. +By default, PNG output is full colour at 8 or 16 bits per pixel. Indexed PNG input at 1, 2 or 4 bits per pixel is converted to 8 bits per pixel. - -Some of these options require the use of a globally-installed libvips compiled with support for libimagequant (GPL). +Set `palette` to `true` for slower, indexed PNG output. ### Parameters - `options` **[Object][6]?** - `options.progressive` **[boolean][7]** use progressive (interlace) scan (optional, default `false`) - - `options.compressionLevel` **[number][9]** zlib compression level, 0-9 (optional, default `9`) + - `options.compressionLevel` **[number][9]** zlib compression level, 0 (fastest, largest) to 9 (slowest, smallest) (optional, default `6`) - `options.adaptiveFiltering` **[boolean][7]** use adaptive row filtering (optional, default `false`) - - `options.palette` **[boolean][7]** quantise to a palette-based image with alpha transparency support, requires libvips compiled with support for libimagequant (optional, default `false`) - - `options.quality` **[number][9]** use the lowest number of colours needed to achieve given quality, sets `palette` to `true`, requires libvips compiled with support for libimagequant (optional, default `100`) - - `options.colours` **[number][9]** maximum number of palette entries, sets `palette` to `true`, requires libvips compiled with support for libimagequant (optional, default `256`) - - `options.colors` **[number][9]** alternative spelling of `options.colours`, sets `palette` to `true`, requires libvips compiled with support for libimagequant (optional, default `256`) - - `options.dither` **[number][9]** level of Floyd-Steinberg error diffusion, sets `palette` to `true`, requires libvips compiled with support for libimagequant (optional, default `1.0`) + - `options.palette` **[boolean][7]** quantise to a palette-based image with alpha transparency support (optional, default `false`) + - `options.quality` **[number][9]** use the lowest number of colours needed to achieve given quality, sets `palette` to `true` (optional, default `100`) + - `options.colours` **[number][9]** maximum number of palette entries, sets `palette` to `true` (optional, default `256`) + - `options.colors` **[number][9]** alternative spelling of `options.colours`, sets `palette` to `true` (optional, default `256`) + - `options.dither` **[number][9]** level of Floyd-Steinberg error diffusion, sets `palette` to `true` (optional, default `1.0`) - `options.force` **[boolean][7]** force PNG output, otherwise attempt to use input format (optional, default `true`) ### Examples ```javascript -// Convert any input to PNG output +// Convert any input to full colour PNG output const data = await sharp(input) .png() .toBuffer(); ``` +```javascript +// Convert any input to indexed PNG output (slower) +const data = await sharp(input) + .png({ palette: true }) + .toBuffer(); +``` + - Throws **[Error][4]** Invalid options Returns **Sharp** diff --git a/docs/changelog.md b/docs/changelog.md index a213fb99..4ce8b643 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,5 +1,19 @@ # Changelog +## v0.28 - *bijou* + +Requires libvips v8.10.6 + +### v0.28.0 - TBD + +* Prebuilt binaries now include mozjpeg and libimagequant (BSD 2-Clause). + +* Prebuilt binaries limit AVIF support to the most common 8-bit depth. + +* Add `mozjpeg` option to `jpeg` method, sets mozjpeg defaults. + +* Reduce the default PNG `compressionLevel` to the more commonly used 6. + ## v0.27 - *avif* Requires libvips v8.10.5 diff --git a/docs/install.md b/docs/install.md index ec6e7f63..6145672f 100644 --- a/docs/install.md +++ b/docs/install.md @@ -20,10 +20,11 @@ Node.js v10+ on the most common platforms: * macOS x64 (>= 10.13) * Linux x64 (glibc >= 2.17, musl >=1.1.24 <1.2.0) * Linux ARM64 (glibc >= 2.29) +* Linux ARM64 (musl >= 1.1.24) * Windows x64 * Windows x86 -A ~9MB tarball containing libvips and its most commonly used dependencies +An ~8MB tarball containing libvips and its most commonly used dependencies is downloaded via HTTPS and stored within `node_modules/sharp/vendor` during `npm install`. This provides support for the @@ -31,16 +32,16 @@ JPEG, PNG, WebP, AVIF, TIFF, GIF (input) and SVG (input) image formats. The following platforms have prebuilt libvips but not sharp: +* macOS ARM64 * Linux ARMv6 * Linux ARMv7 (glibc >= 2.28) * Windows ARM64 The following platforms require compilation of both libvips and sharp from source: -* macOS ARM64 * Linux x86 * Linux x64 (glibc <= 2.16, includes RHEL/CentOS 6) -* Linux ARM64 (glibc <= 2.28, musl) +* Linux ARM64 (glibc <= 2.28) * Linux PowerPC * FreeBSD * OpenBSD diff --git a/lib/constructor.js b/lib/constructor.js index 817fc27e..54f2c878 100644 --- a/lib/constructor.js +++ b/lib/constructor.js @@ -244,7 +244,7 @@ const Sharp = function (input, options) { jpegOptimiseCoding: true, jpegQuantisationTable: 0, pngProgressive: false, - pngCompressionLevel: 9, + pngCompressionLevel: 6, pngAdaptiveFiltering: false, pngPalette: false, pngQuality: 100, diff --git a/lib/output.js b/lib/output.js index 6d309105..56de2da4 100644 --- a/lib/output.js +++ b/lib/output.js @@ -198,8 +198,6 @@ function toFormat (format, options) { /** * Use these JPEG options for output image. * - * Some of these options require the use of a globally-installed libvips compiled with support for mozjpeg. - * * @example * // Convert any input to very high quality JPEG output * const data = await sharp(input) @@ -209,18 +207,25 @@ function toFormat (format, options) { * }) * .toBuffer(); * + * @example + * // Use mozjpeg to reduce output JPEG file size (slower) + * const data = await sharp(input) + * .jpeg({ mozjpeg: true }) + * .toBuffer(); + * * @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 otherwise defaults to '4:2:0' chroma subsampling * @param {boolean} [options.optimiseCoding=true] - optimise Huffman coding tables * @param {boolean} [options.optimizeCoding=true] - alternative spelling of optimiseCoding - * @param {boolean} [options.trellisQuantisation=false] - apply trellis quantisation, requires libvips compiled with support for mozjpeg - * @param {boolean} [options.overshootDeringing=false] - apply overshoot deringing, requires libvips compiled with support for mozjpeg - * @param {boolean} [options.optimiseScans=false] - optimise progressive scans, forces progressive, requires libvips compiled with support for mozjpeg - * @param {boolean} [options.optimizeScans=false] - alternative spelling of optimiseScans, requires libvips compiled with support for mozjpeg - * @param {number} [options.quantisationTable=0] - quantization table to use, integer 0-8, requires libvips compiled with support for mozjpeg - * @param {number} [options.quantizationTable=0] - alternative spelling of quantisationTable, requires libvips compiled with support for mozjpeg + * @param {boolean} [options.mozjpeg=false] - use mozjpeg defaults, equivalent to `{ trellisQuantisation: true, overshootDeringing: true, optimiseScans: true, quantisationTable: 3 }` + * @param {boolean} [options.trellisQuantisation=false] - apply trellis quantisation + * @param {boolean} [options.overshootDeringing=false] - apply overshoot deringing + * @param {boolean} [options.optimiseScans=false] - optimise progressive scans, forces progressive + * @param {boolean} [options.optimizeScans=false] - alternative spelling of optimiseScans + * @param {number} [options.quantisationTable=0] - quantization table to use, integer 0-8 + * @param {number} [options.quantizationTable=0] - alternative spelling of quantisationTable * @param {boolean} [options.force=true] - force JPEG output, otherwise attempt to use input format * @returns {Sharp} * @throws {Error} Invalid options @@ -244,6 +249,23 @@ function jpeg (options) { throw is.invalidParameterError('chromaSubsampling', 'one of: 4:2:0, 4:4:4', options.chromaSubsampling); } } + const optimiseCoding = is.bool(options.optimizeCoding) ? options.optimizeCoding : options.optimiseCoding; + if (is.defined(optimiseCoding)) { + this._setBooleanOption('jpegOptimiseCoding', optimiseCoding); + } + if (is.defined(options.mozjpeg)) { + if (is.bool(options.mozjpeg)) { + if (options.mozjpeg) { + this.options.jpegTrellisQuantisation = true; + this.options.jpegOvershootDeringing = true; + this.options.jpegOptimiseScans = true; + this.options.jpegProgressive = true; + this.options.jpegQuantisationTable = 3; + } + } else { + throw is.invalidParameterError('mozjpeg', 'boolean', options.mozjpeg); + } + } const trellisQuantisation = is.bool(options.trellisQuantization) ? options.trellisQuantization : options.trellisQuantisation; if (is.defined(trellisQuantisation)) { this._setBooleanOption('jpegTrellisQuantisation', trellisQuantisation); @@ -258,10 +280,6 @@ function jpeg (options) { this.options.jpegProgressive = true; } } - const optimiseCoding = is.bool(options.optimizeCoding) ? options.optimizeCoding : options.optimiseCoding; - if (is.defined(optimiseCoding)) { - this._setBooleanOption('jpegOptimiseCoding', optimiseCoding); - } const quantisationTable = is.number(options.quantizationTable) ? options.quantizationTable : options.quantisationTable; if (is.defined(quantisationTable)) { if (is.integer(quantisationTable) && is.inRange(quantisationTable, 0, 8)) { @@ -277,26 +295,31 @@ function jpeg (options) { /** * Use these PNG options for output image. * - * PNG output is always full colour at 8 or 16 bits per pixel. + * By default, PNG output is full colour at 8 or 16 bits per pixel. * Indexed PNG input at 1, 2 or 4 bits per pixel is converted to 8 bits per pixel. - * - * Some of these options require the use of a globally-installed libvips compiled with support for libimagequant (GPL). + * Set `palette` to `true` for slower, indexed PNG output. * * @example - * // Convert any input to PNG output + * // Convert any input to full colour PNG output * const data = await sharp(input) * .png() * .toBuffer(); * + * @example + * // Convert any input to indexed PNG output (slower) + * const data = await sharp(input) + * .png({ palette: true }) + * .toBuffer(); + * * @param {Object} [options] * @param {boolean} [options.progressive=false] - use progressive (interlace) scan - * @param {number} [options.compressionLevel=9] - zlib compression level, 0-9 + * @param {number} [options.compressionLevel=6] - zlib compression level, 0 (fastest, largest) to 9 (slowest, smallest) * @param {boolean} [options.adaptiveFiltering=false] - use adaptive row filtering - * @param {boolean} [options.palette=false] - quantise to a palette-based image with alpha transparency support, requires libvips compiled with support for libimagequant - * @param {number} [options.quality=100] - use the lowest number of colours needed to achieve given quality, sets `palette` to `true`, requires libvips compiled with support for libimagequant - * @param {number} [options.colours=256] - maximum number of palette entries, sets `palette` to `true`, requires libvips compiled with support for libimagequant - * @param {number} [options.colors=256] - alternative spelling of `options.colours`, sets `palette` to `true`, requires libvips compiled with support for libimagequant - * @param {number} [options.dither=1.0] - level of Floyd-Steinberg error diffusion, sets `palette` to `true`, requires libvips compiled with support for libimagequant + * @param {boolean} [options.palette=false] - quantise to a palette-based image with alpha transparency support + * @param {number} [options.quality=100] - use the lowest number of colours needed to achieve given quality, sets `palette` to `true` + * @param {number} [options.colours=256] - maximum number of palette entries, sets `palette` to `true` + * @param {number} [options.colors=256] - alternative spelling of `options.colours`, sets `palette` to `true` + * @param {number} [options.dither=1.0] - level of Floyd-Steinberg error diffusion, sets `palette` to `true` * @param {boolean} [options.force=true] - force PNG output, otherwise attempt to use input format * @returns {Sharp} * @throws {Error} Invalid options diff --git a/package.json b/package.json index 65f9ba81..6665e762 100644 --- a/package.json +++ b/package.json @@ -143,7 +143,7 @@ }, "license": "Apache-2.0", "config": { - "libvips": "8.10.5", + "libvips": "8.10.6-alpha1", "runtime": "napi", "target": 3 }, diff --git a/src/common.h b/src/common.h index b8a0d929..74eadded 100644 --- a/src/common.h +++ b/src/common.h @@ -26,8 +26,8 @@ #if (VIPS_MAJOR_VERSION < 8) || \ (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 10) || \ - (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 10 && VIPS_MICRO_VERSION < 5) -#error "libvips version 8.10.5+ is required - please see https://sharp.pixelplumbing.com/install" + (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 10 && VIPS_MICRO_VERSION < 6) +#error "libvips version 8.10.6+ is required - please see https://sharp.pixelplumbing.com/install" #endif #if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6))) diff --git a/src/pipeline.h b/src/pipeline.h index ae6eb73f..37fb5e8a 100644 --- a/src/pipeline.h +++ b/src/pipeline.h @@ -258,7 +258,7 @@ struct PipelineBaton { jpegOptimiseScans(false), jpegOptimiseCoding(true), pngProgressive(false), - pngCompressionLevel(9), + pngCompressionLevel(6), pngAdaptiveFiltering(false), pngPalette(false), pngQuality(100), diff --git a/test/fixtures/cosmos_frame12924_yuv420_10bpc_bt2020_pq_q50.avif b/test/fixtures/cosmos_frame12924_yuv420_10bpc_bt2020_pq_q50.avif deleted file mode 100644 index 487d8013..00000000 Binary files a/test/fixtures/cosmos_frame12924_yuv420_10bpc_bt2020_pq_q50.avif and /dev/null differ diff --git a/test/fixtures/index.js b/test/fixtures/index.js index 7b8f6e45..6d3ff5c0 100644 --- a/test/fixtures/index.js +++ b/test/fixtures/index.js @@ -109,7 +109,7 @@ module.exports = { inputSvg: getPath('check.svg'), // http://dev.w3.org/SVG/tools/svgweb/samples/svg-files/check.svg inputSvgSmallViewBox: getPath('circle.svg'), inputSvgWithEmbeddedImages: getPath('struct-image-04-t.svg'), // https://dev.w3.org/SVG/profiles/1.2T/test/svg/struct-image-04-t.svg - inputAvif: getPath('cosmos_frame12924_yuv420_10bpc_bt2020_pq_q50.avif'), // CC by-nc-nd https://github.com/AOMediaCodec/av1-avif/tree/master/testFiles/Netflix + inputAvif: getPath('sdr_cosmos12920_cicp1-13-6_yuv444_full_qp10.avif'), // CC by-nc-nd https://github.com/AOMediaCodec/av1-avif/tree/master/testFiles/Netflix inputJPGBig: getPath('flowers.jpeg'), diff --git a/test/fixtures/sdr_cosmos12920_cicp1-13-6_yuv444_full_qp10.avif b/test/fixtures/sdr_cosmos12920_cicp1-13-6_yuv444_full_qp10.avif new file mode 100644 index 00000000..85d30b15 Binary files /dev/null and b/test/fixtures/sdr_cosmos12920_cicp1-13-6_yuv444_full_qp10.avif differ diff --git a/test/unit/jpeg.js b/test/unit/jpeg.js index 148bd64c..f368843c 100644 --- a/test/unit/jpeg.js +++ b/test/unit/jpeg.js @@ -290,4 +290,24 @@ describe('JPEG', function () { }); }); }); + + it('Can use mozjpeg defaults', async () => { + const withoutData = await sharp(fixtures.inputJpg) + .resize(32, 24) + .jpeg({ mozjpeg: false }) + .toBuffer(); + const withoutMeta = await sharp(withoutData).metadata(); + assert.strictEqual(false, withoutMeta.isProgressive); + + const withData = await sharp(fixtures.inputJpg) + .resize(32, 24) + .jpeg({ mozjpeg: true }) + .toBuffer(); + const withMeta = await sharp(withData).metadata(); + assert.strictEqual(true, withMeta.isProgressive); + }); + + it('Invalid mozjpeg value throws error', () => { + assert.throws(() => sharp().jpeg({ mozjpeg: 'fail' })); + }); }); diff --git a/test/unit/png.js b/test/unit/png.js index 810cb3d4..99694133 100644 --- a/test/unit/png.js +++ b/test/unit/png.js @@ -19,7 +19,7 @@ describe('PNG', function () { }); }); - it('default compressionLevel generates smaller file than compressionLevel=6', function (done) { + it('default compressionLevel generates smaller file than compressionLevel=0', function (done) { // First generate with default compressionLevel sharp(fixtures.inputPng) .resize(320, 240) @@ -31,7 +31,7 @@ describe('PNG', function () { // Then generate with compressionLevel=6 sharp(fixtures.inputPng) .resize(320, 240) - .png({ compressionLevel: 6 }) + .png({ compressionLevel: 0 }) .toBuffer(function (err, largerData, largerInfo) { if (err) throw err; assert.strictEqual(true, largerData.length > 0); diff --git a/test/unit/tiff.js b/test/unit/tiff.js index cd496ce2..8905e2e3 100644 --- a/test/unit/tiff.js +++ b/test/unit/tiff.js @@ -279,7 +279,7 @@ describe('TIFF', function () { }); }); - it('TIFF deflate compression of integral input with float predictor increases file size', function (done) { + it('TIFF deflate compression with float predictor shrinks test file', function (done) { const startSize = fs.statSync(fixtures.inputTiffUncompressed).size; sharp(fixtures.inputTiffUncompressed) .tiff({ @@ -289,7 +289,7 @@ describe('TIFF', function () { .toFile(outputTiff, (err, info) => { if (err) throw err; assert.strictEqual('tiff', info.format); - assert(info.size > startSize); + assert(startSize > info.size); rimraf(outputTiff, done); }); });