sharp 0.27.2 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -19,6 +19,14 @@ rotation, extraction, compositing and gamma correction are available.
19
19
  Most modern macOS, Windows and Linux systems running Node.js v10+
20
20
  do not require any additional install or runtime dependencies.
21
21
 
22
+ ## Documentation
23
+
24
+ Visit [sharp.pixelplumbing.com](https://sharp.pixelplumbing.com/) for complete
25
+ [installation instructions](https://sharp.pixelplumbing.com/install),
26
+ [API documentation](https://sharp.pixelplumbing.com/api-constructor),
27
+ [benchmark tests](https://sharp.pixelplumbing.com/performance) and
28
+ [changelog](https://sharp.pixelplumbing.com/changelog).
29
+
22
30
  ## Examples
23
31
 
24
32
  ```sh
@@ -43,6 +51,7 @@ sharp(inputBuffer)
43
51
  sharp('input.jpg')
44
52
  .rotate()
45
53
  .resize(200)
54
+ .jpeg({ mozjpeg: true })
46
55
  .toBuffer()
47
56
  .then( data => { ... })
48
57
  .catch( err => { ... });
@@ -84,23 +93,15 @@ readableStream
84
93
  .pipe(writableStream);
85
94
  ```
86
95
 
87
- [![Test Coverage](https://coveralls.io/repos/lovell/sharp/badge.svg?branch=master)](https://coveralls.io/r/lovell/sharp?branch=master)
88
- [![N-API v3](https://img.shields.io/badge/N--API-v3-green.svg)](https://nodejs.org/dist/latest/docs/api/n-api.html#n_api_n_api_version_matrix)
89
-
90
- ### Documentation
91
-
92
- Visit [sharp.pixelplumbing.com](https://sharp.pixelplumbing.com/) for complete
93
- [installation instructions](https://sharp.pixelplumbing.com/install),
94
- [API documentation](https://sharp.pixelplumbing.com/api-constructor),
95
- [benchmark tests](https://sharp.pixelplumbing.com/performance) and
96
- [changelog](https://sharp.pixelplumbing.com/changelog).
97
-
98
- ### Contributing
96
+ ## Contributing
99
97
 
100
98
  A [guide for contributors](https://github.com/lovell/sharp/blob/master/.github/CONTRIBUTING.md)
101
99
  covers reporting bugs, requesting features and submitting code changes.
102
100
 
103
- ### Licensing
101
+ [![Test Coverage](https://coveralls.io/repos/lovell/sharp/badge.svg?branch=master)](https://coveralls.io/r/lovell/sharp?branch=master)
102
+ [![N-API v3](https://img.shields.io/badge/N--API-v3-green.svg)](https://nodejs.org/dist/latest/docs/api/n-api.html#n_api_n_api_version_matrix)
103
+
104
+ ## Licensing
104
105
 
105
106
  Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Lovell Fuller and contributors.
106
107
 
package/binding.gyp CHANGED
@@ -140,8 +140,7 @@
140
140
  'link_settings': {
141
141
  'library_dirs': ['<(sharp_vendor_dir)/lib'],
142
142
  'libraries': [
143
- 'libvips-cpp.42.dylib',
144
- 'libvips.42.dylib'
143
+ 'libvips-cpp.42.dylib'
145
144
  ]
146
145
  },
147
146
  'xcode_settings': {
@@ -153,13 +152,12 @@
153
152
  }],
154
153
  ['OS == "linux"', {
155
154
  'defines': [
156
- '_GLIBCXX_USE_CXX11_ABI=0'
155
+ '_GLIBCXX_USE_CXX11_ABI=1'
157
156
  ],
158
157
  'link_settings': {
159
158
  'library_dirs': ['<(sharp_vendor_dir)/lib'],
160
159
  'libraries': [
161
- '-l:libvips-cpp.so.42',
162
- '-l:libvips.so.42'
160
+ '-l:libvips-cpp.so.42'
163
161
  ],
164
162
  'ldflags': [
165
163
  # Ensure runtime linking is relative to sharp.node
@@ -4,7 +4,6 @@ const fs = require('fs');
4
4
  const path = require('path');
5
5
 
6
6
  const libvips = require('../lib/libvips');
7
- const npmLog = require('npmlog');
8
7
 
9
8
  const minimumLibvipsVersion = libvips.minimumLibvipsVersion;
10
9
 
@@ -12,13 +11,13 @@ const platform = process.env.npm_config_platform || process.platform;
12
11
  if (platform === 'win32') {
13
12
  const buildDir = path.join(__dirname, '..', 'build');
14
13
  const buildReleaseDir = path.join(buildDir, 'Release');
15
- npmLog.info('sharp', `Creating ${buildReleaseDir}`);
14
+ libvips.log(`Creating ${buildReleaseDir}`);
16
15
  try {
17
16
  libvips.mkdirSync(buildDir);
18
17
  libvips.mkdirSync(buildReleaseDir);
19
18
  } catch (err) {}
20
19
  const vendorLibDir = path.join(__dirname, '..', 'vendor', minimumLibvipsVersion, 'lib');
21
- npmLog.info('sharp', `Copying DLLs from ${vendorLibDir} to ${buildReleaseDir}`);
20
+ libvips.log(`Copying DLLs from ${vendorLibDir} to ${buildReleaseDir}`);
22
21
  try {
23
22
  fs
24
23
  .readdirSync(vendorLibDir)
@@ -32,6 +31,7 @@ if (platform === 'win32') {
32
31
  );
33
32
  });
34
33
  } catch (err) {
35
- npmLog.error('sharp', err.message);
34
+ libvips.log(err);
35
+ process.exit(1);
36
36
  }
37
37
  }
@@ -7,7 +7,6 @@ const stream = require('stream');
7
7
  const zlib = require('zlib');
8
8
 
9
9
  const detectLibc = require('detect-libc');
10
- const npmLog = require('npmlog');
11
10
  const semver = require('semver');
12
11
  const simpleGet = require('simple-get');
13
12
  const tarFs = require('tar-fs');
@@ -22,34 +21,50 @@ const minimumGlibcVersionByArch = {
22
21
  x64: '2.17'
23
22
  };
24
23
 
24
+ const hasSharpPrebuild = [
25
+ 'darwin-x64',
26
+ 'linux-arm64',
27
+ 'linux-x64',
28
+ 'linuxmusl-x64',
29
+ 'linuxmusl-arm64',
30
+ 'win32-ia32',
31
+ 'win32-x64'
32
+ ];
33
+
25
34
  const { minimumLibvipsVersion, minimumLibvipsVersionLabelled } = libvips;
26
35
  const distHost = process.env.npm_config_sharp_libvips_binary_host || 'https://github.com/lovell/sharp-libvips/releases/download';
27
36
  const distBaseUrl = process.env.npm_config_sharp_dist_base_url || process.env.SHARP_DIST_BASE_URL || `${distHost}/v${minimumLibvipsVersionLabelled}/`;
28
37
  const supportsBrotli = ('BrotliDecompress' in zlib);
29
38
 
30
39
  const fail = function (err) {
31
- npmLog.error('sharp', err.message);
40
+ libvips.log(err);
32
41
  if (err.code === 'EACCES') {
33
- npmLog.info('sharp', 'Are you trying to install as a root or sudo user? Try again with the --unsafe-perm flag');
42
+ libvips.log('Are you trying to install as a root or sudo user? Try again with the --unsafe-perm flag');
34
43
  }
35
- npmLog.info('sharp', 'Attempting to build from source via node-gyp but this may fail due to the above error');
36
- npmLog.info('sharp', 'Please see https://sharp.pixelplumbing.com/install for required dependencies');
44
+ libvips.log('Attempting to build from source via node-gyp but this may fail due to the above error');
45
+ libvips.log('Please see https://sharp.pixelplumbing.com/install for required dependencies');
37
46
  process.exit(1);
38
47
  };
39
48
 
40
- const extractTarball = function (tarPath) {
49
+ const extractTarball = function (tarPath, platformAndArch) {
41
50
  const vendorPath = path.join(__dirname, '..', 'vendor');
42
51
  libvips.mkdirSync(vendorPath);
43
52
  const versionedVendorPath = path.join(vendorPath, minimumLibvipsVersion);
44
53
  libvips.mkdirSync(versionedVendorPath);
54
+
55
+ const ignoreVendorInclude = hasSharpPrebuild.includes(platformAndArch) && !process.env.npm_config_build_from_source;
56
+ const ignore = function (name) {
57
+ return ignoreVendorInclude && name.includes('include/');
58
+ };
59
+
45
60
  stream.pipeline(
46
61
  fs.createReadStream(tarPath),
47
62
  supportsBrotli ? new zlib.BrotliDecompress() : new zlib.Gunzip(),
48
- tarFs.extract(versionedVendorPath),
63
+ tarFs.extract(versionedVendorPath, { ignore }),
49
64
  function (err) {
50
65
  if (err) {
51
66
  if (/unexpected end of file/.test(err.message)) {
52
- npmLog.error('sharp', `Please delete ${tarPath} as it is not a valid tarball`);
67
+ fail(new Error(`Please delete ${tarPath} as it is not a valid tarball`));
53
68
  }
54
69
  fail(err);
55
70
  }
@@ -62,11 +77,11 @@ try {
62
77
 
63
78
  if (useGlobalLibvips) {
64
79
  const globalLibvipsVersion = libvips.globalLibvipsVersion();
65
- npmLog.info('sharp', `Detected globally-installed libvips v${globalLibvipsVersion}`);
66
- npmLog.info('sharp', 'Building from source via node-gyp');
80
+ libvips.log(`Detected globally-installed libvips v${globalLibvipsVersion}`);
81
+ libvips.log('Building from source via node-gyp');
67
82
  process.exit(1);
68
83
  } else if (libvips.hasVendoredLibvips()) {
69
- npmLog.info('sharp', `Using existing vendored libvips v${minimumLibvipsVersion}`);
84
+ libvips.log(`Using existing vendored libvips v${minimumLibvipsVersion}`);
70
85
  } else {
71
86
  // Is this arch/platform supported?
72
87
  const arch = process.env.npm_config_arch || process.arch;
@@ -86,7 +101,7 @@ try {
86
101
  }
87
102
  }
88
103
  if (detectLibc.family === detectLibc.MUSL && detectLibc.version) {
89
- if (!semver.satisfies(detectLibc.version, '>=1.1.24 <1.2.0')) {
104
+ if (semver.lt(detectLibc.version, '1.1.24')) {
90
105
  throw new Error(`Use with musl ${detectLibc.version} requires manual installation of libvips >= ${minimumLibvipsVersion}`);
91
106
  }
92
107
  }
@@ -102,13 +117,11 @@ try {
102
117
  const tarFilename = ['libvips', minimumLibvipsVersion, platformAndArch].join('-') + '.tar.' + extension;
103
118
  const tarPathCache = path.join(libvips.cachePath(), tarFilename);
104
119
  if (fs.existsSync(tarPathCache)) {
105
- npmLog.info('sharp', `Using cached ${tarPathCache}`);
106
- extractTarball(tarPathCache);
120
+ libvips.log(`Using cached ${tarPathCache}`);
121
+ extractTarball(tarPathCache, platformAndArch);
107
122
  } else {
108
- const tarPathTemp = path.join(os.tmpdir(), `${process.pid}-${tarFilename}`);
109
- const tmpFile = fs.createWriteStream(tarPathTemp);
110
123
  const url = distBaseUrl + tarFilename;
111
- npmLog.info('sharp', `Downloading ${url}`);
124
+ libvips.log(`Downloading ${url}`);
112
125
  simpleGet({ url: url, agent: agent() }, function (err, response) {
113
126
  if (err) {
114
127
  fail(err);
@@ -117,24 +130,37 @@ try {
117
130
  } else if (response.statusCode !== 200) {
118
131
  fail(new Error(`Status ${response.statusCode} ${response.statusMessage}`));
119
132
  } else {
133
+ const tarPathTemp = path.join(os.tmpdir(), `${process.pid}-${tarFilename}`);
134
+ const tmpFileStream = fs.createWriteStream(tarPathTemp);
120
135
  response
121
- .on('error', fail)
122
- .pipe(tmpFile);
136
+ .on('error', function (err) {
137
+ tmpFileStream.destroy(err);
138
+ })
139
+ .on('close', function () {
140
+ if (!response.complete) {
141
+ tmpFileStream.destroy(new Error('Download incomplete (connection was terminated)'));
142
+ }
143
+ })
144
+ .pipe(tmpFileStream);
145
+ tmpFileStream
146
+ .on('error', function (err) {
147
+ // Clean up temporary file
148
+ fs.unlinkSync(tarPathTemp);
149
+ fail(err);
150
+ })
151
+ .on('close', function () {
152
+ try {
153
+ // Attempt to rename
154
+ fs.renameSync(tarPathTemp, tarPathCache);
155
+ } catch (err) {
156
+ // Fall back to copy and unlink
157
+ fs.copyFileSync(tarPathTemp, tarPathCache);
158
+ fs.unlinkSync(tarPathTemp);
159
+ }
160
+ extractTarball(tarPathCache);
161
+ });
123
162
  }
124
163
  });
125
- tmpFile
126
- .on('error', fail)
127
- .on('close', function () {
128
- try {
129
- // Attempt to rename
130
- fs.renameSync(tarPathTemp, tarPathCache);
131
- } catch (err) {
132
- // Fall back to copy and unlink
133
- fs.copyFileSync(tarPathTemp, tarPathCache);
134
- fs.unlinkSync(tarPathTemp);
135
- }
136
- extractTarball(tarPathCache);
137
- });
138
164
  }
139
165
  }
140
166
  } catch (err) {
@@ -18,12 +18,10 @@ try {
18
18
  help.push(`- Ensure "${process.platform}" is used at install time as well as runtime`);
19
19
  } else if (/dylib/.test(err.message) && /Incompatible library version/.test(err.message)) {
20
20
  help.push('- Run "brew update && brew upgrade vips"');
21
- } else if (/Cannot find module/.test(err.message)) {
22
- help.push('- Run "npm rebuild --verbose sharp" and look for errors');
23
21
  } else {
24
22
  help.push(
25
23
  '- Remove the "node_modules/sharp" directory then run',
26
- ' "npm install --ignore-scripts=false --verbose" and look for errors'
24
+ ' "npm install --ignore-scripts=false --verbose sharp" and look for errors'
27
25
  );
28
26
  }
29
27
  help.push(
@@ -244,7 +242,7 @@ const Sharp = function (input, options) {
244
242
  jpegOptimiseCoding: true,
245
243
  jpegQuantisationTable: 0,
246
244
  pngProgressive: false,
247
- pngCompressionLevel: 9,
245
+ pngCompressionLevel: 6,
248
246
  pngAdaptiveFiltering: false,
249
247
  pngPalette: false,
250
248
  pngQuality: 100,
@@ -281,6 +279,7 @@ const Sharp = function (input, options) {
281
279
  tileSkipBlanks: -1,
282
280
  tileBackground: [255, 255, 255, 255],
283
281
  tileCentre: false,
282
+ tileId: 'https://example.com/iiif',
284
283
  linearA: 1,
285
284
  linearB: 0,
286
285
  // Function to notify of libvips warnings
package/lib/libvips.js CHANGED
@@ -37,6 +37,14 @@ const cachePath = function () {
37
37
  return libvipsCachePath;
38
38
  };
39
39
 
40
+ const log = function (item) {
41
+ if (item instanceof Error) {
42
+ console.error(`sharp: ${item.message}`);
43
+ } else {
44
+ console.log(`sharp: ${item}`);
45
+ }
46
+ };
47
+
40
48
  const isRosetta = function () {
41
49
  /* istanbul ignore next */
42
50
  if (process.platform === 'darwin' && process.arch === 'x64') {
@@ -104,6 +112,7 @@ module.exports = {
104
112
  minimumLibvipsVersion,
105
113
  minimumLibvipsVersionLabelled,
106
114
  cachePath,
115
+ log,
107
116
  globalLibvipsVersion,
108
117
  hasVendoredLibvips,
109
118
  pkgConfigPath,
package/lib/operation.js CHANGED
@@ -1,6 +1,5 @@
1
1
  'use strict';
2
2
 
3
- const { flatten: flattenArray } = require('array-flatten');
4
3
  const color = require('color');
5
4
  const is = require('./is');
6
5
 
@@ -127,7 +126,7 @@ function flop (flop) {
127
126
  * @throws {Error} Invalid parameters
128
127
  */
129
128
  function affine (matrix, options) {
130
- const flatMatrix = flattenArray(matrix);
129
+ const flatMatrix = [].concat(...matrix);
131
130
  if (flatMatrix.length === 4 && flatMatrix.every(is.number)) {
132
131
  this.options.affineMatrix = flatMatrix;
133
132
  } else {
@@ -269,7 +268,13 @@ function blur (sigma) {
269
268
  }
270
269
 
271
270
  /**
272
- * Merge alpha transparency channel, if any, with a background.
271
+ * Merge alpha transparency channel, if any, with a background, then remove the alpha channel.
272
+ *
273
+ * @example
274
+ * await sharp(rgbaInput)
275
+ * .flatten('#F0A703')
276
+ * .toBuffer();
277
+ *
273
278
  * @param {Object} [options]
274
279
  * @param {string|Object} [options.background={r: 0, g: 0, b: 0}] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black.
275
280
  * @returns {Sharp}
@@ -397,7 +402,7 @@ function convolve (kernel) {
397
402
  }
398
403
 
399
404
  /**
400
- * Any pixel value greather than or equal to the threshold value will be set to 255, otherwise it will be set to 0.
405
+ * Any pixel value greater than or equal to the threshold value will be set to 255, otherwise it will be set to 0.
401
406
  * @param {number} [threshold=128] - a value in the range 0-255 representing the level at which the threshold will be applied.
402
407
  * @param {Object} [options]
403
408
  * @param {Boolean} [options.greyscale=true] - convert to single channel greyscale.
package/lib/output.js CHANGED
@@ -105,10 +105,10 @@ function toFile (fileOut, callback) {
105
105
  * .catch(err => { ... });
106
106
  *
107
107
  * @example
108
- * const data = await sharp('my-image.jpg')
108
+ * const { data, info } = await sharp('my-image.jpg')
109
109
  * // output the raw pixels
110
110
  * .raw()
111
- * .toBuffer();
111
+ * .toBuffer({ resolveWithObject: true });
112
112
  *
113
113
  * // create a more type safe way to work with the raw pixel data
114
114
  * // this will not copy the data, instead it will change `data`s underlying ArrayBuffer
@@ -116,7 +116,9 @@ function toFile (fileOut, callback) {
116
116
  * const pixelArray = new Uint8ClampedArray(data.buffer);
117
117
  *
118
118
  * // When you are done changing the pixelArray, sharp takes the `pixelArray` as an input
119
- * await sharp(pixelArray).toFile('my-changed-image.jpg');
119
+ * const { width, height, channels } = info;
120
+ * await sharp(pixelArray, { raw: { width, height, channels } })
121
+ * .toFile('my-changed-image.jpg');
120
122
  *
121
123
  * @param {Object} [options]
122
124
  * @param {boolean} [options.resolveWithObject] Resolve the Promise with an Object containing `data` and `info` properties instead of resolving only with `data`.
@@ -198,8 +200,6 @@ function toFormat (format, options) {
198
200
  /**
199
201
  * Use these JPEG options for output image.
200
202
  *
201
- * Some of these options require the use of a globally-installed libvips compiled with support for mozjpeg.
202
- *
203
203
  * @example
204
204
  * // Convert any input to very high quality JPEG output
205
205
  * const data = await sharp(input)
@@ -209,18 +209,25 @@ function toFormat (format, options) {
209
209
  * })
210
210
  * .toBuffer();
211
211
  *
212
+ * @example
213
+ * // Use mozjpeg to reduce output JPEG file size (slower)
214
+ * const data = await sharp(input)
215
+ * .jpeg({ mozjpeg: true })
216
+ * .toBuffer();
217
+ *
212
218
  * @param {Object} [options] - output options
213
219
  * @param {number} [options.quality=80] - quality, integer 1-100
214
220
  * @param {boolean} [options.progressive=false] - use progressive (interlace) scan
215
221
  * @param {string} [options.chromaSubsampling='4:2:0'] - set to '4:4:4' to prevent chroma subsampling otherwise defaults to '4:2:0' chroma subsampling
216
222
  * @param {boolean} [options.optimiseCoding=true] - optimise Huffman coding tables
217
223
  * @param {boolean} [options.optimizeCoding=true] - alternative spelling of optimiseCoding
218
- * @param {boolean} [options.trellisQuantisation=false] - apply trellis quantisation, requires libvips compiled with support for mozjpeg
219
- * @param {boolean} [options.overshootDeringing=false] - apply overshoot deringing, requires libvips compiled with support for mozjpeg
220
- * @param {boolean} [options.optimiseScans=false] - optimise progressive scans, forces progressive, requires libvips compiled with support for mozjpeg
221
- * @param {boolean} [options.optimizeScans=false] - alternative spelling of optimiseScans, requires libvips compiled with support for mozjpeg
222
- * @param {number} [options.quantisationTable=0] - quantization table to use, integer 0-8, requires libvips compiled with support for mozjpeg
223
- * @param {number} [options.quantizationTable=0] - alternative spelling of quantisationTable, requires libvips compiled with support for mozjpeg
224
+ * @param {boolean} [options.mozjpeg=false] - use mozjpeg defaults, equivalent to `{ trellisQuantisation: true, overshootDeringing: true, optimiseScans: true, quantisationTable: 3 }`
225
+ * @param {boolean} [options.trellisQuantisation=false] - apply trellis quantisation
226
+ * @param {boolean} [options.overshootDeringing=false] - apply overshoot deringing
227
+ * @param {boolean} [options.optimiseScans=false] - optimise progressive scans, forces progressive
228
+ * @param {boolean} [options.optimizeScans=false] - alternative spelling of optimiseScans
229
+ * @param {number} [options.quantisationTable=0] - quantization table to use, integer 0-8
230
+ * @param {number} [options.quantizationTable=0] - alternative spelling of quantisationTable
224
231
  * @param {boolean} [options.force=true] - force JPEG output, otherwise attempt to use input format
225
232
  * @returns {Sharp}
226
233
  * @throws {Error} Invalid options
@@ -244,6 +251,23 @@ function jpeg (options) {
244
251
  throw is.invalidParameterError('chromaSubsampling', 'one of: 4:2:0, 4:4:4', options.chromaSubsampling);
245
252
  }
246
253
  }
254
+ const optimiseCoding = is.bool(options.optimizeCoding) ? options.optimizeCoding : options.optimiseCoding;
255
+ if (is.defined(optimiseCoding)) {
256
+ this._setBooleanOption('jpegOptimiseCoding', optimiseCoding);
257
+ }
258
+ if (is.defined(options.mozjpeg)) {
259
+ if (is.bool(options.mozjpeg)) {
260
+ if (options.mozjpeg) {
261
+ this.options.jpegTrellisQuantisation = true;
262
+ this.options.jpegOvershootDeringing = true;
263
+ this.options.jpegOptimiseScans = true;
264
+ this.options.jpegProgressive = true;
265
+ this.options.jpegQuantisationTable = 3;
266
+ }
267
+ } else {
268
+ throw is.invalidParameterError('mozjpeg', 'boolean', options.mozjpeg);
269
+ }
270
+ }
247
271
  const trellisQuantisation = is.bool(options.trellisQuantization) ? options.trellisQuantization : options.trellisQuantisation;
248
272
  if (is.defined(trellisQuantisation)) {
249
273
  this._setBooleanOption('jpegTrellisQuantisation', trellisQuantisation);
@@ -258,10 +282,6 @@ function jpeg (options) {
258
282
  this.options.jpegProgressive = true;
259
283
  }
260
284
  }
261
- const optimiseCoding = is.bool(options.optimizeCoding) ? options.optimizeCoding : options.optimiseCoding;
262
- if (is.defined(optimiseCoding)) {
263
- this._setBooleanOption('jpegOptimiseCoding', optimiseCoding);
264
- }
265
285
  const quantisationTable = is.number(options.quantizationTable) ? options.quantizationTable : options.quantisationTable;
266
286
  if (is.defined(quantisationTable)) {
267
287
  if (is.integer(quantisationTable) && is.inRange(quantisationTable, 0, 8)) {
@@ -277,26 +297,31 @@ function jpeg (options) {
277
297
  /**
278
298
  * Use these PNG options for output image.
279
299
  *
280
- * PNG output is always full colour at 8 or 16 bits per pixel.
300
+ * By default, PNG output is full colour at 8 or 16 bits per pixel.
281
301
  * Indexed PNG input at 1, 2 or 4 bits per pixel is converted to 8 bits per pixel.
282
- *
283
- * Some of these options require the use of a globally-installed libvips compiled with support for libimagequant (GPL).
302
+ * Set `palette` to `true` for slower, indexed PNG output.
284
303
  *
285
304
  * @example
286
- * // Convert any input to PNG output
305
+ * // Convert any input to full colour PNG output
287
306
  * const data = await sharp(input)
288
307
  * .png()
289
308
  * .toBuffer();
290
309
  *
310
+ * @example
311
+ * // Convert any input to indexed PNG output (slower)
312
+ * const data = await sharp(input)
313
+ * .png({ palette: true })
314
+ * .toBuffer();
315
+ *
291
316
  * @param {Object} [options]
292
317
  * @param {boolean} [options.progressive=false] - use progressive (interlace) scan
293
- * @param {number} [options.compressionLevel=9] - zlib compression level, 0-9
318
+ * @param {number} [options.compressionLevel=6] - zlib compression level, 0 (fastest, largest) to 9 (slowest, smallest)
294
319
  * @param {boolean} [options.adaptiveFiltering=false] - use adaptive row filtering
295
- * @param {boolean} [options.palette=false] - quantise to a palette-based image with alpha transparency support, requires libvips compiled with support for libimagequant
296
- * @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
297
- * @param {number} [options.colours=256] - maximum number of palette entries, sets `palette` to `true`, requires libvips compiled with support for libimagequant
298
- * @param {number} [options.colors=256] - alternative spelling of `options.colours`, sets `palette` to `true`, requires libvips compiled with support for libimagequant
299
- * @param {number} [options.dither=1.0] - level of Floyd-Steinberg error diffusion, sets `palette` to `true`, requires libvips compiled with support for libimagequant
320
+ * @param {boolean} [options.palette=false] - quantise to a palette-based image with alpha transparency support
321
+ * @param {number} [options.quality=100] - use the lowest number of colours needed to achieve given quality, sets `palette` to `true`
322
+ * @param {number} [options.colours=256] - maximum number of palette entries, sets `palette` to `true`
323
+ * @param {number} [options.colors=256] - alternative spelling of `options.colours`, sets `palette` to `true`
324
+ * @param {number} [options.dither=1.0] - level of Floyd-Steinberg error diffusion, sets `palette` to `true`
300
325
  * @param {boolean} [options.force=true] - force PNG output, otherwise attempt to use input format
301
326
  * @returns {Sharp}
302
327
  * @throws {Error} Invalid options
@@ -704,6 +729,7 @@ function raw () {
704
729
  * @param {string} [options.layout='dz'] filesystem layout, possible values are `dz`, `iiif`, `zoomify` or `google`.
705
730
  * @param {boolean} [options.centre=false] centre image in tile.
706
731
  * @param {boolean} [options.center=false] alternative spelling of centre.
732
+ * @param {string} [options.id='https://example.com/iiif'] when `layout` is `iiif`, sets the `@id` attribute of `info.json`
707
733
  * @returns {Sharp}
708
734
  * @throws {Error} Invalid parameters
709
735
  */
@@ -777,6 +803,14 @@ function tile (options) {
777
803
  if (is.defined(centre)) {
778
804
  this._setBooleanOption('tileCentre', centre);
779
805
  }
806
+ // @id attribute for IIIF layout
807
+ if (is.defined(options.id)) {
808
+ if (is.string(options.id)) {
809
+ this.options.tileId = options.id;
810
+ } else {
811
+ throw is.invalidParameterError('id', 'string', options.id);
812
+ }
813
+ }
780
814
  }
781
815
  // Format
782
816
  if (is.inArray(this.options.formatOut, ['jpeg', 'png', 'webp'])) {
package/lib/resize.js CHANGED
@@ -302,11 +302,20 @@ function resize (width, height, options) {
302
302
  * })
303
303
  * ...
304
304
  *
305
+ * @example
306
+ * // Add a row of 10 red pixels to the bottom
307
+ * sharp(input)
308
+ * .extend({
309
+ * bottom: 10,
310
+ * background: 'red'
311
+ * })
312
+ * ...
313
+ *
305
314
  * @param {(number|Object)} extend - single pixel count to add to all edges or an Object with per-edge counts
306
- * @param {number} [extend.top]
307
- * @param {number} [extend.left]
308
- * @param {number} [extend.bottom]
309
- * @param {number} [extend.right]
315
+ * @param {number} [extend.top=0]
316
+ * @param {number} [extend.left=0]
317
+ * @param {number} [extend.bottom=0]
318
+ * @param {number} [extend.right=0]
310
319
  * @param {String|Object} [extend.background={r: 0, g: 0, b: 0, alpha: 1}] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black without transparency.
311
320
  * @returns {Sharp}
312
321
  * @throws {Error} Invalid parameters
@@ -317,17 +326,35 @@ function extend (extend) {
317
326
  this.options.extendBottom = extend;
318
327
  this.options.extendLeft = extend;
319
328
  this.options.extendRight = extend;
320
- } else if (
321
- is.object(extend) &&
322
- is.integer(extend.top) && extend.top >= 0 &&
323
- is.integer(extend.bottom) && extend.bottom >= 0 &&
324
- is.integer(extend.left) && extend.left >= 0 &&
325
- is.integer(extend.right) && extend.right >= 0
326
- ) {
327
- this.options.extendTop = extend.top;
328
- this.options.extendBottom = extend.bottom;
329
- this.options.extendLeft = extend.left;
330
- this.options.extendRight = extend.right;
329
+ } else if (is.object(extend)) {
330
+ if (is.defined(extend.top)) {
331
+ if (is.integer(extend.top) && extend.top >= 0) {
332
+ this.options.extendTop = extend.top;
333
+ } else {
334
+ throw is.invalidParameterError('top', 'positive integer', extend.top);
335
+ }
336
+ }
337
+ if (is.defined(extend.bottom)) {
338
+ if (is.integer(extend.bottom) && extend.bottom >= 0) {
339
+ this.options.extendBottom = extend.bottom;
340
+ } else {
341
+ throw is.invalidParameterError('bottom', 'positive integer', extend.bottom);
342
+ }
343
+ }
344
+ if (is.defined(extend.left)) {
345
+ if (is.integer(extend.left) && extend.left >= 0) {
346
+ this.options.extendLeft = extend.left;
347
+ } else {
348
+ throw is.invalidParameterError('left', 'positive integer', extend.left);
349
+ }
350
+ }
351
+ if (is.defined(extend.right)) {
352
+ if (is.integer(extend.right) && extend.right >= 0) {
353
+ this.options.extendRight = extend.right;
354
+ } else {
355
+ throw is.invalidParameterError('right', 'positive integer', extend.right);
356
+ }
357
+ }
331
358
  this._setBackgroundColourOption('extendBackground', extend.background);
332
359
  } else {
333
360
  throw is.invalidParameterError('extend', 'integer or object', extend);
package/lib/utility.js CHANGED
@@ -1,6 +1,8 @@
1
1
  'use strict';
2
2
 
3
3
  const events = require('events');
4
+ const detectLibc = require('detect-libc');
5
+
4
6
  const is = require('./is');
5
7
  const sharp = require('../build/Release/sharp.node');
6
8
 
@@ -84,8 +86,12 @@ cache(true);
84
86
  /**
85
87
  * Gets or, when a concurrency is provided, sets
86
88
  * the number of threads _libvips'_ should create to process each image.
87
- * The default value is the number of CPU cores.
88
- * A value of `0` will reset to this default.
89
+ *
90
+ * The default value is the number of CPU cores,
91
+ * except when using glibc-based Linux without jemalloc,
92
+ * where the default is `1` to help reduce memory fragmentation.
93
+ *
94
+ * A value of `0` will reset this to the number of CPU cores.
89
95
  *
90
96
  * The maximum number of images that can be processed in parallel
91
97
  * is limited by libuv's `UV_THREADPOOL_SIZE` environment variable.
@@ -103,6 +109,11 @@ cache(true);
103
109
  function concurrency (concurrency) {
104
110
  return sharp.concurrency(is.integer(concurrency) ? concurrency : null);
105
111
  }
112
+ /* istanbul ignore next */
113
+ if (detectLibc.family === detectLibc.GLIBC && !sharp._isUsingJemalloc()) {
114
+ // Reduce default concurrency to 1 when using glibc memory allocator
115
+ sharp.concurrency(1);
116
+ }
106
117
 
107
118
  /**
108
119
  * An EventEmitter that emits a `change` event when a task is either:
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sharp",
3
3
  "description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, AVIF and TIFF images",
4
- "version": "0.27.2",
4
+ "version": "0.28.0",
5
5
  "author": "Lovell Fuller <npm@lovell.info>",
6
6
  "homepage": "https://github.com/lovell/sharp",
7
7
  "contributors": [
@@ -80,7 +80,7 @@
80
80
  "install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node-gyp rebuild && node install/dll-copy)",
81
81
  "clean": "rm -rf node_modules/ build/ vendor/ .nyc_output/ coverage/ test/fixtures/output.*",
82
82
  "test": "semistandard && cpplint && npm run test-unit && npm run test-licensing",
83
- "test-unit": "nyc --reporter=lcov --branches=99 mocha --slow=5000 --timeout=60000 ./test/unit/*.js",
83
+ "test-unit": "nyc --reporter=lcov --branches=99 mocha --slow=1000 --timeout=60000 ./test/unit/*.js",
84
84
  "test-licensing": "license-checker --production --summary --onlyAllow=\"Apache-2.0;BSD;ISC;MIT\"",
85
85
  "test-coverage": "./test/coverage/report.sh",
86
86
  "test-leak": "./test/leak/leak.sh",
@@ -117,14 +117,12 @@
117
117
  "vips"
118
118
  ],
119
119
  "dependencies": {
120
- "array-flatten": "^3.0.0",
121
120
  "color": "^3.1.3",
122
121
  "detect-libc": "^1.0.3",
123
122
  "node-addon-api": "^3.1.0",
124
- "npmlog": "^4.1.2",
125
123
  "prebuild-install": "^6.0.1",
126
- "semver": "^7.3.4",
127
- "simple-get": "^4.0.0",
124
+ "semver": "^7.3.5",
125
+ "simple-get": "^3.1.0",
128
126
  "tar-fs": "^2.1.1",
129
127
  "tunnel-agent": "^0.6.0"
130
128
  },
@@ -132,11 +130,11 @@
132
130
  "async": "^3.2.0",
133
131
  "cc": "^3.0.1",
134
132
  "decompress-zip": "^0.3.3",
135
- "documentation": "^13.1.1",
133
+ "documentation": "^13.2.0",
136
134
  "exif-reader": "^1.0.3",
137
135
  "icc": "^2.0.0",
138
136
  "license-checker": "^25.0.1",
139
- "mocha": "^8.3.0",
137
+ "mocha": "^8.3.2",
140
138
  "mock-fs": "^4.13.0",
141
139
  "nyc": "^15.1.0",
142
140
  "prebuild": "^10.0.1",
@@ -145,7 +143,7 @@
145
143
  },
146
144
  "license": "Apache-2.0",
147
145
  "config": {
148
- "libvips": "8.10.5",
146
+ "libvips": "8.10.6",
149
147
  "runtime": "napi",
150
148
  "target": 3
151
149
  },
package/src/common.cc CHANGED
@@ -213,6 +213,8 @@ namespace sharp {
213
213
  { "VipsForeignLoadTiffBuffer", ImageType::TIFF },
214
214
  { "VipsForeignLoadGifFile", ImageType::GIF },
215
215
  { "VipsForeignLoadGifBuffer", ImageType::GIF },
216
+ { "VipsForeignLoadNsgifFile", ImageType::GIF },
217
+ { "VipsForeignLoadNsgifBuffer", ImageType::GIF },
216
218
  { "VipsForeignLoadSvgFile", ImageType::SVG },
217
219
  { "VipsForeignLoadSvgBuffer", ImageType::SVG },
218
220
  { "VipsForeignLoadHeifFile", ImageType::HEIF },
package/src/common.h CHANGED
@@ -26,8 +26,8 @@
26
26
 
27
27
  #if (VIPS_MAJOR_VERSION < 8) || \
28
28
  (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 10) || \
29
- (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 10 && VIPS_MICRO_VERSION < 5)
30
- #error "libvips version 8.10.5+ is required - please see https://sharp.pixelplumbing.com/install"
29
+ (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 10 && VIPS_MICRO_VERSION < 6)
30
+ #error "libvips version 8.10.6+ is required - please see https://sharp.pixelplumbing.com/install"
31
31
  #endif
32
32
 
33
33
  #if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6)))
package/src/pipeline.cc CHANGED
@@ -562,9 +562,17 @@ class PipelineWorker : public Napi::AsyncWorker {
562
562
  // Use gravity in overlay
563
563
  if (compositeImage.width() <= baton->width) {
564
564
  across = static_cast<int>(ceil(static_cast<double>(image.width()) / compositeImage.width()));
565
+ // Ensure odd number of tiles across when gravity is centre, north or south
566
+ if (composite->gravity == 0 || composite->gravity == 1 || composite->gravity == 3) {
567
+ across |= 1;
568
+ }
565
569
  }
566
570
  if (compositeImage.height() <= baton->height) {
567
571
  down = static_cast<int>(ceil(static_cast<double>(image.height()) / compositeImage.height()));
572
+ // Ensure odd number of tiles down when gravity is centre, east or west
573
+ if (composite->gravity == 0 || composite->gravity == 2 || composite->gravity == 4) {
574
+ down |= 1;
575
+ }
568
576
  }
569
577
  if (across != 0 || down != 0) {
570
578
  int left;
@@ -594,8 +602,13 @@ class PipelineWorker : public Napi::AsyncWorker {
594
602
  int top;
595
603
  if (composite->hasOffset) {
596
604
  // Composite image at given offsets
597
- std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),
598
- compositeImage.width(), compositeImage.height(), composite->left, composite->top);
605
+ if (composite->tile) {
606
+ std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),
607
+ compositeImage.width(), compositeImage.height(), composite->left, composite->top);
608
+ } else {
609
+ left = composite->left;
610
+ top = composite->top;
611
+ }
599
612
  } else {
600
613
  // Composite image with given gravity
601
614
  std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),
@@ -1038,6 +1051,7 @@ class PipelineWorker : public Napi::AsyncWorker {
1038
1051
  ->set("angle", CalculateAngleRotation(baton->tileAngle))
1039
1052
  ->set("background", baton->tileBackground)
1040
1053
  ->set("centre", baton->tileCentre)
1054
+ ->set("id", const_cast<char*>(baton->tileId.data()))
1041
1055
  ->set("skip_blanks", baton->tileSkipBlanks);
1042
1056
  // libvips chooses a default depth based on layout. Instead of replicating that logic here by
1043
1057
  // not passing anything - libvips will handle choice
@@ -1438,6 +1452,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1438
1452
  vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_DZ_DEPTH,
1439
1453
  sharp::AttrAsStr(options, "tileDepth").data()));
1440
1454
  baton->tileCentre = sharp::AttrAsBool(options, "tileCentre");
1455
+ baton->tileId = sharp::AttrAsStr(options, "tileId");
1441
1456
 
1442
1457
  // Force random access for certain operations
1443
1458
  if (baton->input->access == VIPS_ACCESS_SEQUENTIAL) {
package/src/pipeline.h CHANGED
@@ -192,6 +192,7 @@ struct PipelineBaton {
192
192
  std::vector<double> tileBackground;
193
193
  int tileSkipBlanks;
194
194
  VipsForeignDzDepth tileDepth;
195
+ std::string tileId;
195
196
  std::unique_ptr<double[]> recombMatrix;
196
197
 
197
198
  PipelineBaton():
@@ -258,7 +259,7 @@ struct PipelineBaton {
258
259
  jpegOptimiseScans(false),
259
260
  jpegOptimiseCoding(true),
260
261
  pngProgressive(false),
261
- pngCompressionLevel(9),
262
+ pngCompressionLevel(6),
262
263
  pngAdaptiveFiltering(false),
263
264
  pngPalette(false),
264
265
  pngQuality(100),
package/src/sharp.cc CHANGED
@@ -44,6 +44,7 @@ Napi::Object init(Napi::Env env, Napi::Object exports) {
44
44
  exports.Set("libvipsVersion", Napi::Function::New(env, libvipsVersion));
45
45
  exports.Set("format", Napi::Function::New(env, format));
46
46
  exports.Set("_maxColourDistance", Napi::Function::New(env, _maxColourDistance));
47
+ exports.Set("_isUsingJemalloc", Napi::Function::New(env, _isUsingJemalloc));
47
48
  exports.Set("stats", Napi::Function::New(env, stats));
48
49
  return exports;
49
50
  }
package/src/utilities.cc CHANGED
@@ -225,3 +225,19 @@ Napi::Value _maxColourDistance(const Napi::CallbackInfo& info) {
225
225
 
226
226
  return Napi::Number::New(env, maxColourDistance);
227
227
  }
228
+
229
+ #if defined(__GNUC__)
230
+ // mallctl will be resolved by the runtime linker when jemalloc is being used
231
+ extern "C" {
232
+ int mallctl(const char *name, void *oldp, size_t *oldlenp, void *newp, size_t newlen) __attribute__((weak));
233
+ }
234
+ Napi::Value _isUsingJemalloc(const Napi::CallbackInfo& info) {
235
+ Napi::Env env = info.Env();
236
+ return Napi::Boolean::New(env, mallctl != nullptr);
237
+ }
238
+ #else
239
+ Napi::Value _isUsingJemalloc(const Napi::CallbackInfo& info) {
240
+ Napi::Env env = info.Env();
241
+ return Napi::Boolean::New(env, false);
242
+ }
243
+ #endif
package/src/utilities.h CHANGED
@@ -24,5 +24,6 @@ Napi::Value simd(const Napi::CallbackInfo& info);
24
24
  Napi::Value libvipsVersion(const Napi::CallbackInfo& info);
25
25
  Napi::Value format(const Napi::CallbackInfo& info);
26
26
  Napi::Value _maxColourDistance(const Napi::CallbackInfo& info);
27
+ Napi::Value _isUsingJemalloc(const Napi::CallbackInfo& info);
27
28
 
28
29
  #endif // SRC_UTILITIES_H_