sharp 0.27.0 → 0.28.1

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/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
@@ -16,6 +16,8 @@ const formats = new Map([
16
16
  ['gif', 'gif']
17
17
  ]);
18
18
 
19
+ const errMagickSave = new Error('GIF output requires libvips with support for ImageMagick');
20
+
19
21
  /**
20
22
  * Write output image data to a file.
21
23
  *
@@ -47,25 +49,23 @@ const formats = new Map([
47
49
  * @throws {Error} Invalid parameters
48
50
  */
49
51
  function toFile (fileOut, callback) {
50
- if (!fileOut || fileOut.length === 0) {
51
- const errOutputInvalid = new Error('Missing output file path');
52
+ let err;
53
+ if (!is.string(fileOut)) {
54
+ err = new Error('Missing output file path');
55
+ } else if (this.options.input.file === fileOut) {
56
+ err = new Error('Cannot use same file for input and output');
57
+ } else if (this.options.formatOut === 'input' && fileOut.toLowerCase().endsWith('.gif') && !this.constructor.format.magick.output.file) {
58
+ err = errMagickSave;
59
+ }
60
+ if (err) {
52
61
  if (is.fn(callback)) {
53
- callback(errOutputInvalid);
62
+ callback(err);
54
63
  } else {
55
- return Promise.reject(errOutputInvalid);
64
+ return Promise.reject(err);
56
65
  }
57
66
  } else {
58
- if (this.options.input.file === fileOut) {
59
- const errOutputIsInput = new Error('Cannot use same file for input and output');
60
- if (is.fn(callback)) {
61
- callback(errOutputIsInput);
62
- } else {
63
- return Promise.reject(errOutputIsInput);
64
- }
65
- } else {
66
- this.options.fileOut = fileOut;
67
- return this._pipeline(callback);
68
- }
67
+ this.options.fileOut = fileOut;
68
+ return this._pipeline(callback);
69
69
  }
70
70
  return this;
71
71
  }
@@ -104,6 +104,22 @@ function toFile (fileOut, callback) {
104
104
  * .then(({ data, info }) => { ... })
105
105
  * .catch(err => { ... });
106
106
  *
107
+ * @example
108
+ * const { data, info } = await sharp('my-image.jpg')
109
+ * // output the raw pixels
110
+ * .raw()
111
+ * .toBuffer({ resolveWithObject: true });
112
+ *
113
+ * // create a more type safe way to work with the raw pixel data
114
+ * // this will not copy the data, instead it will change `data`s underlying ArrayBuffer
115
+ * // so `data` and `pixelArray` point to the same memory location
116
+ * const pixelArray = new Uint8ClampedArray(data.buffer);
117
+ *
118
+ * // When you are done changing the pixelArray, sharp takes the `pixelArray` as an input
119
+ * const { width, height, channels } = info;
120
+ * await sharp(pixelArray, { raw: { width, height, channels } })
121
+ * .toFile('my-changed-image.jpg');
122
+ *
107
123
  * @param {Object} [options]
108
124
  * @param {boolean} [options.resolveWithObject] Resolve the Promise with an Object containing `data` and `info` properties instead of resolving only with `data`.
109
125
  * @param {Function} [callback]
@@ -132,9 +148,22 @@ function toBuffer (options, callback) {
132
148
  * .toFile('output-with-metadata.jpg')
133
149
  * .then(info => { ... });
134
150
  *
151
+ * @example
152
+ * // Set "IFD0-Copyright" in output EXIF metadata
153
+ * await sharp(input)
154
+ * .withMetadata({
155
+ * exif: {
156
+ * IFD0: {
157
+ * Copyright: 'Wernham Hogg'
158
+ * }
159
+ * }
160
+ * })
161
+ * .toBuffer();
162
+ *
135
163
  * @param {Object} [options]
136
164
  * @param {number} [options.orientation] value between 1 and 8, used to update the EXIF `Orientation` tag.
137
165
  * @param {string} [options.icc] filesystem path to output ICC profile, defaults to sRGB.
166
+ * @param {Object<Object>} [options.exif={}] Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data.
138
167
  * @returns {Sharp}
139
168
  * @throws {Error} Invalid parameters
140
169
  */
@@ -155,6 +184,25 @@ function withMetadata (options) {
155
184
  throw is.invalidParameterError('icc', 'string filesystem path to ICC profile', options.icc);
156
185
  }
157
186
  }
187
+ if (is.defined(options.exif)) {
188
+ if (is.object(options.exif)) {
189
+ for (const [ifd, entries] of Object.entries(options.exif)) {
190
+ if (is.object(entries)) {
191
+ for (const [k, v] of Object.entries(entries)) {
192
+ if (is.string(v)) {
193
+ this.options.withMetadataStrs[`exif-${ifd.toLowerCase()}-${k}`] = v;
194
+ } else {
195
+ throw is.invalidParameterError(`exif.${ifd}.${k}`, 'string', v);
196
+ }
197
+ }
198
+ } else {
199
+ throw is.invalidParameterError(`exif.${ifd}`, 'object', entries);
200
+ }
201
+ }
202
+ } else {
203
+ throw is.invalidParameterError('exif', 'object', options.exif);
204
+ }
205
+ }
158
206
  }
159
207
  return this;
160
208
  }
@@ -174,7 +222,7 @@ function withMetadata (options) {
174
222
  * @throws {Error} unsupported format or options
175
223
  */
176
224
  function toFormat (format, options) {
177
- const actualFormat = formats.get(is.object(format) && is.string(format.id) ? format.id : format);
225
+ const actualFormat = formats.get((is.object(format) && is.string(format.id) ? format.id : format).toLowerCase());
178
226
  if (!actualFormat) {
179
227
  throw is.invalidParameterError('format', `one of: ${[...formats.keys()].join(', ')}`, format);
180
228
  }
@@ -184,8 +232,6 @@ function toFormat (format, options) {
184
232
  /**
185
233
  * Use these JPEG options for output image.
186
234
  *
187
- * Some of these options require the use of a globally-installed libvips compiled with support for mozjpeg.
188
- *
189
235
  * @example
190
236
  * // Convert any input to very high quality JPEG output
191
237
  * const data = await sharp(input)
@@ -195,18 +241,25 @@ function toFormat (format, options) {
195
241
  * })
196
242
  * .toBuffer();
197
243
  *
244
+ * @example
245
+ * // Use mozjpeg to reduce output JPEG file size (slower)
246
+ * const data = await sharp(input)
247
+ * .jpeg({ mozjpeg: true })
248
+ * .toBuffer();
249
+ *
198
250
  * @param {Object} [options] - output options
199
251
  * @param {number} [options.quality=80] - quality, integer 1-100
200
252
  * @param {boolean} [options.progressive=false] - use progressive (interlace) scan
201
253
  * @param {string} [options.chromaSubsampling='4:2:0'] - set to '4:4:4' to prevent chroma subsampling otherwise defaults to '4:2:0' chroma subsampling
202
254
  * @param {boolean} [options.optimiseCoding=true] - optimise Huffman coding tables
203
255
  * @param {boolean} [options.optimizeCoding=true] - alternative spelling of optimiseCoding
204
- * @param {boolean} [options.trellisQuantisation=false] - apply trellis quantisation, requires libvips compiled with support for mozjpeg
205
- * @param {boolean} [options.overshootDeringing=false] - apply overshoot deringing, requires libvips compiled with support for mozjpeg
206
- * @param {boolean} [options.optimiseScans=false] - optimise progressive scans, forces progressive, requires libvips compiled with support for mozjpeg
207
- * @param {boolean} [options.optimizeScans=false] - alternative spelling of optimiseScans, requires libvips compiled with support for mozjpeg
208
- * @param {number} [options.quantisationTable=0] - quantization table to use, integer 0-8, requires libvips compiled with support for mozjpeg
209
- * @param {number} [options.quantizationTable=0] - alternative spelling of quantisationTable, requires libvips compiled with support for mozjpeg
256
+ * @param {boolean} [options.mozjpeg=false] - use mozjpeg defaults, equivalent to `{ trellisQuantisation: true, overshootDeringing: true, optimiseScans: true, quantisationTable: 3 }`
257
+ * @param {boolean} [options.trellisQuantisation=false] - apply trellis quantisation
258
+ * @param {boolean} [options.overshootDeringing=false] - apply overshoot deringing
259
+ * @param {boolean} [options.optimiseScans=false] - optimise progressive scans, forces progressive
260
+ * @param {boolean} [options.optimizeScans=false] - alternative spelling of optimiseScans
261
+ * @param {number} [options.quantisationTable=0] - quantization table to use, integer 0-8
262
+ * @param {number} [options.quantizationTable=0] - alternative spelling of quantisationTable
210
263
  * @param {boolean} [options.force=true] - force JPEG output, otherwise attempt to use input format
211
264
  * @returns {Sharp}
212
265
  * @throws {Error} Invalid options
@@ -230,6 +283,23 @@ function jpeg (options) {
230
283
  throw is.invalidParameterError('chromaSubsampling', 'one of: 4:2:0, 4:4:4', options.chromaSubsampling);
231
284
  }
232
285
  }
286
+ const optimiseCoding = is.bool(options.optimizeCoding) ? options.optimizeCoding : options.optimiseCoding;
287
+ if (is.defined(optimiseCoding)) {
288
+ this._setBooleanOption('jpegOptimiseCoding', optimiseCoding);
289
+ }
290
+ if (is.defined(options.mozjpeg)) {
291
+ if (is.bool(options.mozjpeg)) {
292
+ if (options.mozjpeg) {
293
+ this.options.jpegTrellisQuantisation = true;
294
+ this.options.jpegOvershootDeringing = true;
295
+ this.options.jpegOptimiseScans = true;
296
+ this.options.jpegProgressive = true;
297
+ this.options.jpegQuantisationTable = 3;
298
+ }
299
+ } else {
300
+ throw is.invalidParameterError('mozjpeg', 'boolean', options.mozjpeg);
301
+ }
302
+ }
233
303
  const trellisQuantisation = is.bool(options.trellisQuantization) ? options.trellisQuantization : options.trellisQuantisation;
234
304
  if (is.defined(trellisQuantisation)) {
235
305
  this._setBooleanOption('jpegTrellisQuantisation', trellisQuantisation);
@@ -244,10 +314,6 @@ function jpeg (options) {
244
314
  this.options.jpegProgressive = true;
245
315
  }
246
316
  }
247
- const optimiseCoding = is.bool(options.optimizeCoding) ? options.optimizeCoding : options.optimiseCoding;
248
- if (is.defined(optimiseCoding)) {
249
- this._setBooleanOption('jpegOptimiseCoding', optimiseCoding);
250
- }
251
317
  const quantisationTable = is.number(options.quantizationTable) ? options.quantizationTable : options.quantisationTable;
252
318
  if (is.defined(quantisationTable)) {
253
319
  if (is.integer(quantisationTable) && is.inRange(quantisationTable, 0, 8)) {
@@ -263,26 +329,31 @@ function jpeg (options) {
263
329
  /**
264
330
  * Use these PNG options for output image.
265
331
  *
266
- * PNG output is always full colour at 8 or 16 bits per pixel.
332
+ * By default, PNG output is full colour at 8 or 16 bits per pixel.
267
333
  * Indexed PNG input at 1, 2 or 4 bits per pixel is converted to 8 bits per pixel.
268
- *
269
- * Some of these options require the use of a globally-installed libvips compiled with support for libimagequant (GPL).
334
+ * Set `palette` to `true` for slower, indexed PNG output.
270
335
  *
271
336
  * @example
272
- * // Convert any input to PNG output
337
+ * // Convert any input to full colour PNG output
273
338
  * const data = await sharp(input)
274
339
  * .png()
275
340
  * .toBuffer();
276
341
  *
342
+ * @example
343
+ * // Convert any input to indexed PNG output (slower)
344
+ * const data = await sharp(input)
345
+ * .png({ palette: true })
346
+ * .toBuffer();
347
+ *
277
348
  * @param {Object} [options]
278
349
  * @param {boolean} [options.progressive=false] - use progressive (interlace) scan
279
- * @param {number} [options.compressionLevel=9] - zlib compression level, 0-9
350
+ * @param {number} [options.compressionLevel=6] - zlib compression level, 0 (fastest, largest) to 9 (slowest, smallest)
280
351
  * @param {boolean} [options.adaptiveFiltering=false] - use adaptive row filtering
281
- * @param {boolean} [options.palette=false] - quantise to a palette-based image with alpha transparency support, requires libvips compiled with support for libimagequant
282
- * @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
283
- * @param {number} [options.colours=256] - maximum number of palette entries, sets `palette` to `true`, requires libvips compiled with support for libimagequant
284
- * @param {number} [options.colors=256] - alternative spelling of `options.colours`, sets `palette` to `true`, requires libvips compiled with support for libimagequant
285
- * @param {number} [options.dither=1.0] - level of Floyd-Steinberg error diffusion, sets `palette` to `true`, requires libvips compiled with support for libimagequant
352
+ * @param {boolean} [options.palette=false] - quantise to a palette-based image with alpha transparency support
353
+ * @param {number} [options.quality=100] - use the lowest number of colours needed to achieve given quality, sets `palette` to `true`
354
+ * @param {number} [options.colours=256] - maximum number of palette entries, sets `palette` to `true`
355
+ * @param {number} [options.colors=256] - alternative spelling of `options.colours`, sets `palette` to `true`
356
+ * @param {number} [options.dither=1.0] - level of Floyd-Steinberg error diffusion, sets `palette` to `true`
286
357
  * @param {boolean} [options.force=true] - force PNG output, otherwise attempt to use input format
287
358
  * @returns {Sharp}
288
359
  * @throws {Error} Invalid options
@@ -344,6 +415,12 @@ function png (options) {
344
415
  * .webp({ lossless: true })
345
416
  * .toBuffer();
346
417
  *
418
+ * @example
419
+ * // Optimise the file size of an animated WebP
420
+ * const outputWebp = await sharp(inputWebp, { animated: true })
421
+ * .webp({ reductionEffort: 6 })
422
+ * .toBuffer();
423
+ *
347
424
  * @param {Object} [options] - output options
348
425
  * @param {number} [options.quality=80] - quality, integer 1-100
349
426
  * @param {number} [options.alphaQuality=100] - quality of alpha layer, integer 0-100
@@ -412,7 +489,7 @@ function webp (options) {
412
489
  /* istanbul ignore next */
413
490
  function gif (options) {
414
491
  if (!this.constructor.format.magick.output.buffer) {
415
- throw new Error('The gif operation requires libvips to have been installed with support for ImageMagick');
492
+ throw errMagickSave;
416
493
  }
417
494
  trySetAnimationOptions(options, this.options);
418
495
  return this._updateFormatOut('gif', options);
@@ -472,15 +549,15 @@ function trySetAnimationOptions (source, target) {
472
549
  * @param {Object} [options] - output options
473
550
  * @param {number} [options.quality=80] - quality, integer 1-100
474
551
  * @param {boolean} [options.force=true] - force TIFF output, otherwise attempt to use input format
475
- * @param {boolean} [options.compression='jpeg'] - compression options: lzw, deflate, jpeg, ccittfax4
476
- * @param {boolean} [options.predictor='horizontal'] - compression predictor options: none, horizontal, float
552
+ * @param {string} [options.compression='jpeg'] - compression options: lzw, deflate, jpeg, ccittfax4
553
+ * @param {string} [options.predictor='horizontal'] - compression predictor options: none, horizontal, float
477
554
  * @param {boolean} [options.pyramid=false] - write an image pyramid
478
555
  * @param {boolean} [options.tile=false] - write a tiled tiff
479
- * @param {boolean} [options.tileWidth=256] - horizontal tile size
480
- * @param {boolean} [options.tileHeight=256] - vertical tile size
556
+ * @param {number} [options.tileWidth=256] - horizontal tile size
557
+ * @param {number} [options.tileHeight=256] - vertical tile size
481
558
  * @param {number} [options.xres=1.0] - horizontal resolution in pixels/mm
482
559
  * @param {number} [options.yres=1.0] - vertical resolution in pixels/mm
483
- * @param {boolean} [options.bitdepth=8] - reduce bitdepth to 1, 2 or 4 bit
560
+ * @param {number} [options.bitdepth=8] - reduce bitdepth to 1, 2 or 4 bit
484
561
  * @returns {Sharp}
485
562
  * @throws {Error} Invalid options
486
563
  */
@@ -568,7 +645,8 @@ function tiff (options) {
568
645
  * @param {Object} [options] - output options
569
646
  * @param {number} [options.quality=50] - quality, integer 1-100
570
647
  * @param {boolean} [options.lossless=false] - use lossless compression
571
- * @param {boolean} [options.speed=5] - CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest)
648
+ * @param {number} [options.speed=5] - CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest)
649
+ * @param {string} [options.chromaSubsampling='4:2:0'] - set to '4:4:4' to prevent chroma subsampling otherwise defaults to '4:2:0' chroma subsampling, requires libvips v8.11.0
572
650
  * @returns {Sharp}
573
651
  * @throws {Error} Invalid options
574
652
  */
@@ -586,9 +664,10 @@ function avif (options) {
586
664
  *
587
665
  * @param {Object} [options] - output options
588
666
  * @param {number} [options.quality=50] - quality, integer 1-100
589
- * @param {boolean} [options.compression='av1'] - compression format: av1, hevc
667
+ * @param {string} [options.compression='av1'] - compression format: av1, hevc
590
668
  * @param {boolean} [options.lossless=false] - use lossless compression
591
- * @param {boolean} [options.speed=5] - CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest)
669
+ * @param {number} [options.speed=5] - CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest)
670
+ * @param {string} [options.chromaSubsampling='4:2:0'] - set to '4:4:4' to prevent chroma subsampling otherwise defaults to '4:2:0' chroma subsampling, requires libvips v8.11.0
592
671
  * @returns {Sharp}
593
672
  * @throws {Error} Invalid options
594
673
  */
@@ -622,6 +701,13 @@ function heif (options) {
622
701
  throw is.invalidParameterError('speed', 'integer between 0 and 8', options.speed);
623
702
  }
624
703
  }
704
+ if (is.defined(options.chromaSubsampling)) {
705
+ if (is.string(options.chromaSubsampling) && is.inArray(options.chromaSubsampling, ['4:2:0', '4:4:4'])) {
706
+ this.options.heifChromaSubsampling = options.chromaSubsampling;
707
+ } else {
708
+ throw is.invalidParameterError('chromaSubsampling', 'one of: 4:2:0, 4:4:4', options.chromaSubsampling);
709
+ }
710
+ }
625
711
  }
626
712
  return this._updateFormatOut('heif', options);
627
713
  }
@@ -681,6 +767,7 @@ function raw () {
681
767
  * @param {string} [options.layout='dz'] filesystem layout, possible values are `dz`, `iiif`, `zoomify` or `google`.
682
768
  * @param {boolean} [options.centre=false] centre image in tile.
683
769
  * @param {boolean} [options.center=false] alternative spelling of centre.
770
+ * @param {string} [options.id='https://example.com/iiif'] when `layout` is `iiif`, sets the `@id` attribute of `info.json`
684
771
  * @returns {Sharp}
685
772
  * @throws {Error} Invalid parameters
686
773
  */
@@ -754,6 +841,14 @@ function tile (options) {
754
841
  if (is.defined(centre)) {
755
842
  this._setBooleanOption('tileCentre', centre);
756
843
  }
844
+ // @id attribute for IIIF layout
845
+ if (is.defined(options.id)) {
846
+ if (is.string(options.id)) {
847
+ this.options.tileId = options.id;
848
+ } else {
849
+ throw is.invalidParameterError('id', 'string', options.id);
850
+ }
851
+ }
757
852
  }
758
853
  // Format
759
854
  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:
@@ -157,14 +168,10 @@ simd(true);
157
168
  * @private
158
169
  */
159
170
  module.exports = function (Sharp) {
160
- [
161
- cache,
162
- concurrency,
163
- counters,
164
- simd
165
- ].forEach(function (f) {
166
- Sharp[f.name] = f;
167
- });
171
+ Sharp.cache = cache;
172
+ Sharp.concurrency = concurrency;
173
+ Sharp.counters = counters;
174
+ Sharp.simd = simd;
168
175
  Sharp.format = format;
169
176
  Sharp.interpolators = interpolators;
170
177
  Sharp.versions = versions;
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.0",
4
+ "version": "0.28.1",
5
5
  "author": "Lovell Fuller <npm@lovell.info>",
6
6
  "homepage": "https://github.com/lovell/sharp",
7
7
  "contributors": [
@@ -72,17 +72,19 @@
72
72
  "Robert O'Rourke <robert@o-rourke.org>",
73
73
  "Guillermo Alfonso Varela Chouciño <guillevch@gmail.com>",
74
74
  "Christian Flintrup <chr@gigahost.dk>",
75
- "Manan Jadhav <manan@motionden.com>"
75
+ "Manan Jadhav <manan@motionden.com>",
76
+ "Leon Radley <leon@radley.se>",
77
+ "alza54 <alza54@thiocod.in>"
76
78
  ],
77
79
  "scripts": {
78
80
  "install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node-gyp rebuild && node install/dll-copy)",
79
81
  "clean": "rm -rf node_modules/ build/ vendor/ .nyc_output/ coverage/ test/fixtures/output.*",
80
82
  "test": "semistandard && cpplint && npm run test-unit && npm run test-licensing",
81
- "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",
82
84
  "test-licensing": "license-checker --production --summary --onlyAllow=\"Apache-2.0;BSD;ISC;MIT\"",
83
85
  "test-coverage": "./test/coverage/report.sh",
84
86
  "test-leak": "./test/leak/leak.sh",
85
- "docs-build": "documentation lint lib && for m in constructor input resize composite operation colour channel output utility; do documentation build --shallow --format=md --markdown-toc=false lib/$m.js >docs/api-$m.md; done && node docs/search-index/build",
87
+ "docs-build": "documentation lint lib && node docs/build && node docs/search-index/build",
86
88
  "docs-serve": "cd docs && npx serve",
87
89
  "docs-publish": "cd docs && npx firebase-tools deploy --project pixelplumbing --only hosting:pixelplumbing-sharp"
88
90
  },
@@ -115,26 +117,24 @@
115
117
  "vips"
116
118
  ],
117
119
  "dependencies": {
118
- "array-flatten": "^3.0.0",
119
120
  "color": "^3.1.3",
120
121
  "detect-libc": "^1.0.3",
121
122
  "node-addon-api": "^3.1.0",
122
- "npmlog": "^4.1.2",
123
- "prebuild-install": "^6.0.0",
124
- "semver": "^7.3.4",
125
- "simple-get": "^4.0.0",
123
+ "prebuild-install": "^6.1.1",
124
+ "semver": "^7.3.5",
125
+ "simple-get": "^3.1.0",
126
126
  "tar-fs": "^2.1.1",
127
127
  "tunnel-agent": "^0.6.0"
128
128
  },
129
129
  "devDependencies": {
130
130
  "async": "^3.2.0",
131
131
  "cc": "^3.0.1",
132
- "decompress-zip": "^0.3.2",
133
- "documentation": "^13.1.0",
132
+ "decompress-zip": "^0.3.3",
133
+ "documentation": "^13.2.0",
134
134
  "exif-reader": "^1.0.3",
135
135
  "icc": "^2.0.0",
136
136
  "license-checker": "^25.0.1",
137
- "mocha": "^8.2.1",
137
+ "mocha": "^8.3.2",
138
138
  "mock-fs": "^4.13.0",
139
139
  "nyc": "^15.1.0",
140
140
  "prebuild": "^10.0.1",
@@ -143,7 +143,7 @@
143
143
  },
144
144
  "license": "Apache-2.0",
145
145
  "config": {
146
- "libvips": "8.10.5",
146
+ "libvips": "8.10.6",
147
147
  "runtime": "napi",
148
148
  "target": 3
149
149
  },