sharp 0.30.1 → 0.30.4

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/binding.gyp CHANGED
@@ -39,7 +39,6 @@
39
39
  'VCCLCompilerTool': {
40
40
  'ExceptionHandling': 1,
41
41
  'Optimization': 1,
42
- 'RuntimeLibrary': '2', # /MD
43
42
  'WholeProgramOptimization': 'true'
44
43
  },
45
44
  'VCLibrarianTool': {
@@ -206,7 +205,6 @@
206
205
  'VCCLCompilerTool': {
207
206
  'ExceptionHandling': 1,
208
207
  'Optimization': 1,
209
- 'RuntimeLibrary': '2', # /MD
210
208
  'WholeProgramOptimization': 'true'
211
209
  },
212
210
  'VCLibrarianTool': {
package/lib/colour.js CHANGED
@@ -19,6 +19,11 @@ const colourspace = {
19
19
  * Tint the image using the provided chroma while preserving the image luminance.
20
20
  * An alpha channel may be present and will be unchanged by the operation.
21
21
  *
22
+ * @example
23
+ * const output = await sharp(input)
24
+ * .tint({ r: 255, g: 240, b: 16 })
25
+ * .toBuffer();
26
+ *
22
27
  * @param {string|Object} rgb - parsed by the [color](https://www.npmjs.org/package/color) module to extract chroma values.
23
28
  * @returns {Sharp}
24
29
  * @throws {Error} Invalid parameter
@@ -37,6 +42,10 @@ function tint (rgb) {
37
42
  * This may be overridden by other sharp operations such as `toColourspace('b-w')`,
38
43
  * which will produce an output image containing one color channel.
39
44
  * An alpha channel may be present, and will be unchanged by the operation.
45
+ *
46
+ * @example
47
+ * const output = await sharp(input).greyscale().toBuffer();
48
+ *
40
49
  * @param {Boolean} [greyscale=true]
41
50
  * @returns {Sharp}
42
51
  */
package/lib/composite.js CHANGED
@@ -56,6 +56,21 @@ const blend = {
56
56
  * @since 0.22.0
57
57
  *
58
58
  * @example
59
+ * await sharp(background)
60
+ * .composite([
61
+ * { input: layer1, gravity: 'northwest' },
62
+ * { input: layer2, gravity: 'southeast' },
63
+ * ])
64
+ * .toFile('combined.png');
65
+ *
66
+ * @example
67
+ * const output = await sharp('input.gif', { animated: true })
68
+ * .composite([
69
+ * { input: 'overlay.png', tile: true, blend: 'saturate' }
70
+ * ])
71
+ * .toBuffer();
72
+ *
73
+ * @example
59
74
  * sharp('input.png')
60
75
  * .rotate(180)
61
76
  * .resize(300)
@@ -89,7 +104,8 @@ const blend = {
89
104
  * @param {Number} [images[].raw.width]
90
105
  * @param {Number} [images[].raw.height]
91
106
  * @param {Number} [images[].raw.channels]
92
- * @param {boolean} [images[].failOnError=true] - @see {@link /api-constructor#parameters|constructor parameters}
107
+ * @param {boolean} [images[].animated=false] - Set to `true` to read all frames/pages of an animated image.
108
+ * @param {string} [images[].failOn='warning'] - @see {@link /api-constructor#parameters|constructor parameters}
93
109
  * @param {number|boolean} [images[].limitInputPixels=268402689] - @see {@link /api-constructor#parameters|constructor parameters}
94
110
  * @returns {Sharp}
95
111
  * @throws {Error} Invalid parameters
@@ -162,7 +178,6 @@ function composite (images) {
162
178
  throw is.invalidParameterError('premultiplied', 'boolean', image.premultiplied);
163
179
  }
164
180
  }
165
-
166
181
  return composite;
167
182
  });
168
183
  return this;
@@ -98,8 +98,7 @@ const debuglog = util.debuglog('sharp');
98
98
  * a String containing the filesystem path to an JPEG, PNG, WebP, AVIF, GIF, SVG or TIFF image file.
99
99
  * JPEG, PNG, WebP, AVIF, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present.
100
100
  * @param {Object} [options] - if present, is an Object with optional attributes.
101
- * @param {boolean} [options.failOnError=true] - by default halt processing and raise an error when loading invalid images.
102
- * Set this flag to `false` if you'd rather apply a "best effort" to decode images, even if the data is corrupt or invalid.
101
+ * @param {string} [options.failOn='warning'] - level of sensitivity to invalid images, one of (in order of sensitivity): 'none' (least), 'truncated', 'error' or 'warning' (most), highers level imply lower levels.
103
102
  * @param {number|boolean} [options.limitInputPixels=268402689] - Do not process input images where the number of pixels
104
103
  * (width x height) exceeds this limit. Assumes image dimensions contained in the input metadata can be trusted.
105
104
  * An integral Number of pixels, zero or false to remove limit, true to use default limit of 268402689 (0x3FFF x 0x3FFF).
@@ -186,8 +185,11 @@ const Sharp = function (input, options) {
186
185
  medianSize: 0,
187
186
  blurSigma: 0,
188
187
  sharpenSigma: 0,
189
- sharpenFlat: 1,
190
- sharpenJagged: 2,
188
+ sharpenM1: 1,
189
+ sharpenM2: 2,
190
+ sharpenX1: 2,
191
+ sharpenY2: 10,
192
+ sharpenY3: 20,
191
193
  threshold: 0,
192
194
  thresholdGrayscale: true,
193
195
  trimThreshold: 0,
@@ -317,9 +319,7 @@ Object.setPrototypeOf(Sharp, stream.Duplex);
317
319
  * // Using Promises to know when the pipeline is complete
318
320
  * const fs = require("fs");
319
321
  * const got = require("got");
320
- * const sharpStream = sharp({
321
- * failOnError: false
322
- * });
322
+ * const sharpStream = sharp({ failOn: 'none' });
323
323
  *
324
324
  * const promises = [];
325
325
  *
package/lib/input.js CHANGED
@@ -9,9 +9,9 @@ const sharp = require('./sharp');
9
9
  * @private
10
10
  */
11
11
  function _inputOptionsFromObject (obj) {
12
- const { raw, density, limitInputPixels, unlimited, sequentialRead, failOnError, animated, page, pages, subifd } = obj;
13
- return [raw, density, limitInputPixels, unlimited, sequentialRead, failOnError, animated, page, pages, subifd].some(is.defined)
14
- ? { raw, density, limitInputPixels, unlimited, sequentialRead, failOnError, animated, page, pages, subifd }
12
+ const { raw, density, limitInputPixels, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd } = obj;
13
+ return [raw, density, limitInputPixels, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd].some(is.defined)
14
+ ? { raw, density, limitInputPixels, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd }
15
15
  : undefined;
16
16
  }
17
17
 
@@ -21,7 +21,7 @@ function _inputOptionsFromObject (obj) {
21
21
  */
22
22
  function _createInputDescriptor (input, inputOptions, containerOptions) {
23
23
  const inputDescriptor = {
24
- failOnError: true,
24
+ failOn: 'warning',
25
25
  limitInputPixels: Math.pow(0x3FFF, 2),
26
26
  unlimited: false,
27
27
  sequentialRead: false
@@ -39,7 +39,7 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
39
39
  if (input.length === 0) {
40
40
  throw Error('Input Bit Array is empty');
41
41
  }
42
- inputDescriptor.buffer = Buffer.from(input.buffer);
42
+ inputDescriptor.buffer = Buffer.from(input.buffer, input.byteOffset, input.byteLength);
43
43
  } else if (is.plainObject(input) && !is.defined(inputOptions)) {
44
44
  // Plain Object descriptor, e.g. create
45
45
  inputOptions = input;
@@ -56,14 +56,22 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
56
56
  }`);
57
57
  }
58
58
  if (is.object(inputOptions)) {
59
- // Fail on error
59
+ // Deprecated: failOnError
60
60
  if (is.defined(inputOptions.failOnError)) {
61
61
  if (is.bool(inputOptions.failOnError)) {
62
- inputDescriptor.failOnError = inputOptions.failOnError;
62
+ inputDescriptor.failOn = inputOptions.failOnError ? 'warning' : 'none';
63
63
  } else {
64
64
  throw is.invalidParameterError('failOnError', 'boolean', inputOptions.failOnError);
65
65
  }
66
66
  }
67
+ // failOn
68
+ if (is.defined(inputOptions.failOn)) {
69
+ if (is.string(inputOptions.failOn) && is.inArray(inputOptions.failOn, ['none', 'truncated', 'error', 'warning'])) {
70
+ inputDescriptor.failOn = inputOptions.failOn;
71
+ } else {
72
+ throw is.invalidParameterError('failOn', 'one of: none, truncated, error, warning', inputOptions.failOn);
73
+ }
74
+ }
67
75
  // Density
68
76
  if (is.defined(inputOptions.density)) {
69
77
  if (is.inRange(inputOptions.density, 1, 100000)) {
@@ -299,8 +307,8 @@ function _isStreamInput () {
299
307
  *
300
308
  * - `format`: Name of decoder used to decompress image data e.g. `jpeg`, `png`, `webp`, `gif`, `svg`
301
309
  * - `size`: Total size of image in bytes, for Stream and Buffer input only
302
- * - `width`: Number of pixels wide (EXIF orientation is not taken into consideration)
303
- * - `height`: Number of pixels high (EXIF orientation is not taken into consideration)
310
+ * - `width`: Number of pixels wide (EXIF orientation is not taken into consideration, see example below)
311
+ * - `height`: Number of pixels high (EXIF orientation is not taken into consideration, see example below)
304
312
  * - `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://libvips.github.io/libvips/API/current/VipsImage.html#VipsInterpretation)
305
313
  * - `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK
306
314
  * - `depth`: Name of pixel depth format e.g. `uchar`, `char`, `ushort`, `float` [...](https://libvips.github.io/libvips/API/current/VipsImage.html#VipsBandFormat)
@@ -343,6 +351,17 @@ function _isStreamInput () {
343
351
  * // data contains a WebP image half the width and height of the original JPEG
344
352
  * });
345
353
  *
354
+ * @example
355
+ * // Based on EXIF rotation metadata, get the right-side-up width and height:
356
+ *
357
+ * const size = getNormalSize(await sharp(input).metadata());
358
+ *
359
+ * function getNormalSize({ width, height, orientation }) {
360
+ * return orientation || 0 >= 5
361
+ * ? { width: height, height: width }
362
+ * : { width, height };
363
+ * }
364
+ *
346
365
  * @param {Function} [callback] - called with the arguments `(err, metadata)`
347
366
  * @returns {Promise<Object>|Sharp}
348
367
  */
@@ -401,9 +420,9 @@ function metadata (callback) {
401
420
  * - `maxX` (x-coordinate of one of the pixel where the maximum lies)
402
421
  * - `maxY` (y-coordinate of one of the pixel where the maximum lies)
403
422
  * - `isOpaque`: Is the image fully opaque? Will be `true` if the image has no alpha channel or if every pixel is fully opaque.
404
- * - `entropy`: Histogram-based estimation of greyscale entropy, discarding alpha channel if any (experimental)
405
- * - `sharpness`: Estimation of greyscale sharpness based on the standard deviation of a Laplacian convolution, discarding alpha channel if any (experimental)
406
- * - `dominant`: Object containing most dominant sRGB colour based on a 4096-bin 3D histogram (experimental)
423
+ * - `entropy`: Histogram-based estimation of greyscale entropy, discarding alpha channel if any.
424
+ * - `sharpness`: Estimation of greyscale sharpness based on the standard deviation of a Laplacian convolution, discarding alpha channel if any.
425
+ * - `dominant`: Object containing most dominant sRGB colour based on a 4096-bin 3D histogram.
407
426
  *
408
427
  * **Note**: Statistics are derived from the original input image. Any operations performed on the image must first be
409
428
  * written to a buffer in order to run `stats` on the result (see third example).
package/lib/libvips.js CHANGED
@@ -86,9 +86,14 @@ const removeVendoredLibvips = function () {
86
86
  const pkgConfigPath = function () {
87
87
  if (process.platform !== 'win32') {
88
88
  const brewPkgConfigPath = spawnSync('which brew >/dev/null 2>&1 && eval $(brew --env) && echo $PKG_CONFIG_LIBDIR', spawnSyncOptions).stdout || '';
89
- return [brewPkgConfigPath.trim(), env.PKG_CONFIG_PATH, '/usr/local/lib/pkgconfig', '/usr/lib/pkgconfig']
90
- .filter(function (p) { return !!p; })
91
- .join(':');
89
+ return [
90
+ brewPkgConfigPath.trim(),
91
+ env.PKG_CONFIG_PATH,
92
+ '/usr/local/lib/pkgconfig',
93
+ '/usr/lib/pkgconfig',
94
+ '/usr/local/libdata/pkgconfig',
95
+ '/usr/libdata/pkgconfig'
96
+ ].filter(Boolean).join(':');
92
97
  } else {
93
98
  return '';
94
99
  }
package/lib/operation.js CHANGED
@@ -63,6 +63,10 @@ function rotate (angle, options) {
63
63
  /**
64
64
  * Flip the image about the vertical Y axis. This always occurs after rotation, if any.
65
65
  * The use of `flip` implies the removal of the EXIF `Orientation` tag, if any.
66
+ *
67
+ * @example
68
+ * const output = await sharp(input).flip().toBuffer();
69
+ *
66
70
  * @param {Boolean} [flip=true]
67
71
  * @returns {Sharp}
68
72
  */
@@ -74,6 +78,10 @@ function flip (flip) {
74
78
  /**
75
79
  * Flop the image about the horizontal X axis. This always occurs after rotation, if any.
76
80
  * The use of `flop` implies the removal of the EXIF `Orientation` tag, if any.
81
+ *
82
+ * @example
83
+ * const output = await sharp(input).flop().toBuffer();
84
+ *
77
85
  * @param {Boolean} [flop=true]
78
86
  * @returns {Sharp}
79
87
  */
@@ -185,40 +193,107 @@ function affine (matrix, options) {
185
193
  * When a `sigma` is provided, performs a slower, more accurate sharpen of the L channel in the LAB colour space.
186
194
  * Separate control over the level of sharpening in "flat" and "jagged" areas is available.
187
195
  *
188
- * @param {number} [sigma] - the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
189
- * @param {number} [flat=1.0] - the level of sharpening to apply to "flat" areas.
190
- * @param {number} [jagged=2.0] - the level of sharpening to apply to "jagged" areas.
196
+ * See {@link https://www.libvips.org/API/current/libvips-convolution.html#vips-sharpen|libvips sharpen} operation.
197
+ *
198
+ * @example
199
+ * const data = await sharp(input).sharpen().toBuffer();
200
+ *
201
+ * @example
202
+ * const data = await sharp(input).sharpen({ sigma: 2 }).toBuffer();
203
+ *
204
+ * @example
205
+ * const data = await sharp(input)
206
+ * .sharpen({
207
+ * sigma: 2,
208
+ * m1: 0
209
+ * m2: 3,
210
+ * x1: 3,
211
+ * y2: 15,
212
+ * y3: 15,
213
+ * })
214
+ * .toBuffer();
215
+ *
216
+ * @param {Object|number} [options] - if present, is an Object with attributes or (deprecated) a number for `options.sigma`.
217
+ * @param {number} [options.sigma] - the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
218
+ * @param {number} [options.m1=1.0] - the level of sharpening to apply to "flat" areas.
219
+ * @param {number} [options.m2=2.0] - the level of sharpening to apply to "jagged" areas.
220
+ * @param {number} [options.x1=2.0] - threshold between "flat" and "jagged"
221
+ * @param {number} [options.y2=10.0] - maximum amount of brightening.
222
+ * @param {number} [options.y3=20.0] - maximum amount of darkening.
223
+ * @param {number} [flat] - (deprecated) see `options.m1`.
224
+ * @param {number} [jagged] - (deprecated) see `options.m2`.
191
225
  * @returns {Sharp}
192
226
  * @throws {Error} Invalid parameters
193
227
  */
194
- function sharpen (sigma, flat, jagged) {
195
- if (!is.defined(sigma)) {
228
+ function sharpen (options, flat, jagged) {
229
+ if (!is.defined(options)) {
196
230
  // No arguments: default to mild sharpen
197
231
  this.options.sharpenSigma = -1;
198
- } else if (is.bool(sigma)) {
199
- // Boolean argument: apply mild sharpen?
200
- this.options.sharpenSigma = sigma ? -1 : 0;
201
- } else if (is.number(sigma) && is.inRange(sigma, 0.01, 10000)) {
202
- // Numeric argument: specific sigma
203
- this.options.sharpenSigma = sigma;
204
- // Control over flat areas
232
+ } else if (is.bool(options)) {
233
+ // Deprecated boolean argument: apply mild sharpen?
234
+ this.options.sharpenSigma = options ? -1 : 0;
235
+ } else if (is.number(options) && is.inRange(options, 0.01, 10000)) {
236
+ // Deprecated numeric argument: specific sigma
237
+ this.options.sharpenSigma = options;
238
+ // Deprecated control over flat areas
205
239
  if (is.defined(flat)) {
206
240
  if (is.number(flat) && is.inRange(flat, 0, 10000)) {
207
- this.options.sharpenFlat = flat;
241
+ this.options.sharpenM1 = flat;
208
242
  } else {
209
243
  throw is.invalidParameterError('flat', 'number between 0 and 10000', flat);
210
244
  }
211
245
  }
212
- // Control over jagged areas
246
+ // Deprecated control over jagged areas
213
247
  if (is.defined(jagged)) {
214
248
  if (is.number(jagged) && is.inRange(jagged, 0, 10000)) {
215
- this.options.sharpenJagged = jagged;
249
+ this.options.sharpenM2 = jagged;
216
250
  } else {
217
251
  throw is.invalidParameterError('jagged', 'number between 0 and 10000', jagged);
218
252
  }
219
253
  }
254
+ } else if (is.plainObject(options)) {
255
+ if (is.number(options.sigma) && is.inRange(options.sigma, 0.01, 10000)) {
256
+ this.options.sharpenSigma = options.sigma;
257
+ } else {
258
+ throw is.invalidParameterError('options.sigma', 'number between 0.01 and 10000', options.sigma);
259
+ }
260
+ if (is.defined(options.m1)) {
261
+ if (is.number(options.m1) && is.inRange(options.m1, 0, 10000)) {
262
+ this.options.sharpenM1 = options.m1;
263
+ } else {
264
+ throw is.invalidParameterError('options.m1', 'number between 0 and 10000', options.m1);
265
+ }
266
+ }
267
+ if (is.defined(options.m2)) {
268
+ if (is.number(options.m2) && is.inRange(options.m2, 0, 10000)) {
269
+ this.options.sharpenM2 = options.m2;
270
+ } else {
271
+ throw is.invalidParameterError('options.m2', 'number between 0 and 10000', options.m2);
272
+ }
273
+ }
274
+ if (is.defined(options.x1)) {
275
+ if (is.number(options.x1) && is.inRange(options.x1, 0, 10000)) {
276
+ this.options.sharpenX1 = options.x1;
277
+ } else {
278
+ throw is.invalidParameterError('options.x1', 'number between 0 and 10000', options.x1);
279
+ }
280
+ }
281
+ if (is.defined(options.y2)) {
282
+ if (is.number(options.y2) && is.inRange(options.y2, 0, 10000)) {
283
+ this.options.sharpenY2 = options.y2;
284
+ } else {
285
+ throw is.invalidParameterError('options.y2', 'number between 0 and 10000', options.y2);
286
+ }
287
+ }
288
+ if (is.defined(options.y3)) {
289
+ if (is.number(options.y3) && is.inRange(options.y3, 0, 10000)) {
290
+ this.options.sharpenY3 = options.y3;
291
+ } else {
292
+ throw is.invalidParameterError('options.y3', 'number between 0 and 10000', options.y3);
293
+ }
294
+ }
220
295
  } else {
221
- throw is.invalidParameterError('sigma', 'number between 0.01 and 10000', sigma);
296
+ throw is.invalidParameterError('sigma', 'number between 0.01 and 10000', options);
222
297
  }
223
298
  return this;
224
299
  }
@@ -226,6 +301,13 @@ function sharpen (sigma, flat, jagged) {
226
301
  /**
227
302
  * Apply median filter.
228
303
  * When used without parameters the default window is 3x3.
304
+ *
305
+ * @example
306
+ * const output = await sharp(input).median().toBuffer();
307
+ *
308
+ * @example
309
+ * const output = await sharp(input).median(5).toBuffer();
310
+ *
229
311
  * @param {number} [size=3] square mask size: size x size
230
312
  * @returns {Sharp}
231
313
  * @throws {Error} Invalid parameters
@@ -338,6 +420,17 @@ function gamma (gamma, gammaOut) {
338
420
 
339
421
  /**
340
422
  * Produce the "negative" of the image.
423
+ *
424
+ * @example
425
+ * const output = await sharp(input)
426
+ * .negate()
427
+ * .toBuffer();
428
+ *
429
+ * @example
430
+ * const output = await sharp(input)
431
+ * .negate({ alpha: false })
432
+ * .toBuffer();
433
+ *
341
434
  * @param {Object} [options]
342
435
  * @param {Boolean} [options.alpha=true] Whether or not to negate any alpha channel
343
436
  * @returns {Sharp}
@@ -356,6 +449,10 @@ function negate (options) {
356
449
 
357
450
  /**
358
451
  * Enhance output image contrast by stretching its luminance to cover the full dynamic range.
452
+ *
453
+ * @example
454
+ * const output = await sharp(input).normalise().toBuffer();
455
+ *
359
456
  * @param {Boolean} [normalise=true]
360
457
  * @returns {Sharp}
361
458
  */
@@ -366,6 +463,10 @@ function normalise (normalise) {
366
463
 
367
464
  /**
368
465
  * Alternative spelling of normalise.
466
+ *
467
+ * @example
468
+ * const output = await sharp(input).normalize().toBuffer();
469
+ *
369
470
  * @param {Boolean} [normalize=true]
370
471
  * @returns {Sharp}
371
472
  */
@@ -381,6 +482,14 @@ function normalize (normalize) {
381
482
  *
382
483
  * @since 0.28.3
383
484
  *
485
+ * @example
486
+ * const output = await sharp(input)
487
+ * .clahe({
488
+ * width: 3,
489
+ * height: 3,
490
+ * })
491
+ * .toBuffer();
492
+ *
384
493
  * @param {Object} options
385
494
  * @param {number} options.width - integer width of the region in pixels.
386
495
  * @param {number} options.height - integer height of the region in pixels.
@@ -590,28 +699,38 @@ function recomb (inputMatrix) {
590
699
  * @since 0.22.1
591
700
  *
592
701
  * @example
593
- * sharp(input)
702
+ * // increase brightness by a factor of 2
703
+ * const output = await sharp(input)
594
704
  * .modulate({
595
- * brightness: 2 // increase brightness by a factor of 2
596
- * });
705
+ * brightness: 2
706
+ * })
707
+ * .toBuffer();
597
708
  *
598
- * sharp(input)
709
+ * @example
710
+ * // hue-rotate by 180 degrees
711
+ * const output = await sharp(input)
599
712
  * .modulate({
600
- * hue: 180 // hue-rotate by 180 degrees
601
- * });
713
+ * hue: 180
714
+ * })
715
+ * .toBuffer();
602
716
  *
603
- * sharp(input)
717
+ * @example
718
+ * // increase lightness by +50
719
+ * const output = await sharp(input)
604
720
  * .modulate({
605
- * lightness: 50 // increase lightness by +50
606
- * });
721
+ * lightness: 50
722
+ * })
723
+ * .toBuffer();
607
724
  *
725
+ * @example
608
726
  * // decreate brightness and saturation while also hue-rotating by 90 degrees
609
- * sharp(input)
727
+ * const output = await sharp(input)
610
728
  * .modulate({
611
729
  * brightness: 0.5,
612
730
  * saturation: 0.5,
613
- * hue: 90
614
- * });
731
+ * hue: 90,
732
+ * })
733
+ * .toBuffer();
615
734
  *
616
735
  * @param {Object} [options]
617
736
  * @param {number} [options.brightness] Brightness multiplier
package/lib/output.js CHANGED
@@ -151,6 +151,8 @@ function toBuffer (options, callback) {
151
151
  * The default behaviour, when `withMetadata` is not used, is to convert to the device-independent
152
152
  * sRGB colour space and strip all metadata, including the removal of any ICC profile.
153
153
  *
154
+ * EXIF metadata is unsupported for TIFF output.
155
+ *
154
156
  * @example
155
157
  * sharp('input.jpg')
156
158
  * .withMetadata()
package/lib/sharp.js CHANGED
@@ -21,7 +21,7 @@ try {
21
21
  '- Consult the installation documentation: https://sharp.pixelplumbing.com/install'
22
22
  );
23
23
  // Check loaded
24
- if (process.platform === 'win32') {
24
+ if (process.platform === 'win32' || /symbol/.test(err.message)) {
25
25
  const loadedModule = Object.keys(require.cache).find((i) => /[\\/]build[\\/]Release[\\/]sharp(.*)\.node$/.test(i));
26
26
  if (loadedModule) {
27
27
  const [, loadedPackage] = loadedModule.match(/node_modules[\\/]([^\\/]+)[\\/]/);
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, GIF, AVIF and TIFF images",
4
- "version": "0.30.1",
4
+ "version": "0.30.4",
5
5
  "author": "Lovell Fuller <npm@lovell.info>",
6
6
  "homepage": "https://github.com/lovell/sharp",
7
7
  "contributors": [
@@ -81,7 +81,8 @@
81
81
  "Taneli Vatanen <taneli.vatanen@gmail.com>",
82
82
  "Joris Dugué <zaruike10@gmail.com>",
83
83
  "Chris Banks <christopher.bradley.banks@gmail.com>",
84
- "Ompal Singh <ompal.hitm09@gmail.com>"
84
+ "Ompal Singh <ompal.hitm09@gmail.com>",
85
+ "Brodan <christopher.hranj@gmail.com"
85
86
  ],
86
87
  "scripts": {
87
88
  "install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node install/can-compile && node-gyp rebuild && node install/dll-copy)",
@@ -126,11 +127,11 @@
126
127
  "vips"
127
128
  ],
128
129
  "dependencies": {
129
- "color": "^4.2.0",
130
- "detect-libc": "^2.0.0",
130
+ "color": "^4.2.3",
131
+ "detect-libc": "^2.0.1",
131
132
  "node-addon-api": "^4.3.0",
132
133
  "prebuild-install": "^7.0.1",
133
- "semver": "^7.3.5",
134
+ "semver": "^7.3.7",
134
135
  "simple-get": "^4.0.1",
135
136
  "tar-fs": "^2.1.1",
136
137
  "tunnel-agent": "^0.6.0"
@@ -143,7 +144,7 @@
143
144
  "exif-reader": "^1.0.3",
144
145
  "icc": "^2.0.0",
145
146
  "license-checker": "^25.0.1",
146
- "mocha": "^9.2.0",
147
+ "mocha": "^9.2.2",
147
148
  "mock-fs": "^5.1.2",
148
149
  "nyc": "^15.1.0",
149
150
  "prebuild": "^11.0.3",
package/src/common.cc CHANGED
@@ -85,7 +85,9 @@ namespace sharp {
85
85
  descriptor->buffer = buffer.Data();
86
86
  descriptor->isBuffer = TRUE;
87
87
  }
88
- descriptor->failOnError = AttrAsBool(input, "failOnError");
88
+ descriptor->failOn = static_cast<VipsFailOn>(
89
+ vips_enum_from_nick(nullptr, VIPS_TYPE_FAIL_ON,
90
+ AttrAsStr(input, "failOn").data()));
89
91
  // Density for vector-based input
90
92
  if (HasAttr(input, "density")) {
91
93
  descriptor->density = AttrAsDouble(input, "density");
@@ -329,7 +331,7 @@ namespace sharp {
329
331
  try {
330
332
  vips::VOption *option = VImage::option()
331
333
  ->set("access", descriptor->access)
332
- ->set("fail", descriptor->failOnError);
334
+ ->set("fail_on", descriptor->failOn);
333
335
  if (descriptor->unlimited && (imageType == ImageType::SVG || imageType == ImageType::PNG)) {
334
336
  option->set("unlimited", TRUE);
335
337
  }
@@ -375,12 +377,6 @@ namespace sharp {
375
377
  VImage::option()->set("mean", descriptor->createNoiseMean)->set("sigma", descriptor->createNoiseSigma)));
376
378
  }
377
379
  image = image.bandjoin(bands);
378
- image = image.cast(VipsBandFormat::VIPS_FORMAT_UCHAR);
379
- if (channels < 3) {
380
- image = image.colourspace(VIPS_INTERPRETATION_B_W);
381
- } else {
382
- image = image.colourspace(VIPS_INTERPRETATION_sRGB);
383
- }
384
380
  } else {
385
381
  std::vector<double> background = {
386
382
  descriptor->createBackground[0],
@@ -392,19 +388,24 @@ namespace sharp {
392
388
  }
393
389
  image = VImage::new_matrix(descriptor->createWidth, descriptor->createHeight).new_from_image(background);
394
390
  }
395
- image.get_image()->Type = VIPS_INTERPRETATION_sRGB;
391
+ image.get_image()->Type = image.guess_interpretation();
392
+ image = image.cast(VIPS_FORMAT_UCHAR);
396
393
  imageType = ImageType::RAW;
397
394
  } else {
398
395
  // From filesystem
399
396
  imageType = DetermineImageType(descriptor->file.data());
400
397
  if (imageType == ImageType::MISSING) {
401
- throw vips::VError("Input file is missing");
398
+ if (descriptor->file.find("<svg") != std::string::npos) {
399
+ throw vips::VError("Input file is missing, did you mean "
400
+ "sharp(Buffer.from('" + descriptor->file.substr(0, 8) + "...')?");
401
+ }
402
+ throw vips::VError("Input file is missing: " + descriptor->file);
402
403
  }
403
404
  if (imageType != ImageType::UNKNOWN) {
404
405
  try {
405
406
  vips::VOption *option = VImage::option()
406
407
  ->set("access", descriptor->access)
407
- ->set("fail", descriptor->failOnError);
408
+ ->set("fail_on", descriptor->failOn);
408
409
  if (descriptor->unlimited && (imageType == ImageType::SVG || imageType == ImageType::PNG)) {
409
410
  option->set("unlimited", TRUE);
410
411
  }
package/src/common.h CHANGED
@@ -48,7 +48,7 @@ namespace sharp {
48
48
  std::string name;
49
49
  std::string file;
50
50
  char *buffer;
51
- bool failOnError;
51
+ VipsFailOn failOn;
52
52
  int limitInputPixels;
53
53
  bool unlimited;
54
54
  VipsAccess access;
@@ -74,7 +74,7 @@ namespace sharp {
74
74
 
75
75
  InputDescriptor():
76
76
  buffer(nullptr),
77
- failOnError(TRUE),
77
+ failOn(VIPS_FAIL_ON_WARNING),
78
78
  limitInputPixels(0x3FFF * 0x3FFF),
79
79
  unlimited(FALSE),
80
80
  access(VIPS_ACCESS_RANDOM),
package/src/operations.cc CHANGED
@@ -209,7 +209,8 @@ namespace sharp {
209
209
  /*
210
210
  * Sharpen flat and jagged areas. Use sigma of -1.0 for fast sharpen.
211
211
  */
212
- VImage Sharpen(VImage image, double const sigma, double const flat, double const jagged) {
212
+ VImage Sharpen(VImage image, double const sigma, double const m1, double const m2,
213
+ double const x1, double const y2, double const y3) {
213
214
  if (sigma == -1.0) {
214
215
  // Fast, mild sharpen
215
216
  VImage sharpen = VImage::new_matrixv(3, 3,
@@ -224,8 +225,14 @@ namespace sharp {
224
225
  if (colourspaceBeforeSharpen == VIPS_INTERPRETATION_RGB) {
225
226
  colourspaceBeforeSharpen = VIPS_INTERPRETATION_sRGB;
226
227
  }
227
- return image.sharpen(
228
- VImage::option()->set("sigma", sigma)->set("m1", flat)->set("m2", jagged))
228
+ return image
229
+ .sharpen(VImage::option()
230
+ ->set("sigma", sigma)
231
+ ->set("m1", m1)
232
+ ->set("m2", m2)
233
+ ->set("x1", x1)
234
+ ->set("y2", y2)
235
+ ->set("y3", y3))
229
236
  .colourspace(colourspaceBeforeSharpen);
230
237
  }
231
238
  }
package/src/operations.h CHANGED
@@ -64,7 +64,8 @@ namespace sharp {
64
64
  /*
65
65
  * Sharpen flat and jagged areas. Use sigma of -1.0 for fast sharpen.
66
66
  */
67
- VImage Sharpen(VImage image, double const sigma, double const flat, double const jagged);
67
+ VImage Sharpen(VImage image, double const sigma, double const m1, double const m2,
68
+ double const x1, double const y2, double const y3);
68
69
 
69
70
  /*
70
71
  Threshold an image
package/src/pipeline.cc CHANGED
@@ -200,7 +200,7 @@ class PipelineWorker : public Napi::AsyncWorker {
200
200
  vips::VOption *option = VImage::option()
201
201
  ->set("access", baton->input->access)
202
202
  ->set("shrink", jpegShrinkOnLoad)
203
- ->set("fail", baton->input->failOnError);
203
+ ->set("fail_on", baton->input->failOn);
204
204
  if (baton->input->buffer != nullptr) {
205
205
  // Reload JPEG buffer
206
206
  VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength);
@@ -214,7 +214,7 @@ class PipelineWorker : public Napi::AsyncWorker {
214
214
  vips::VOption *option = VImage::option()
215
215
  ->set("access", baton->input->access)
216
216
  ->set("scale", scale)
217
- ->set("fail", baton->input->failOnError);
217
+ ->set("fail_on", baton->input->failOn);
218
218
  if (inputImageType == sharp::ImageType::WEBP) {
219
219
  option->set("n", baton->input->pages);
220
220
  option->set("page", baton->input->page);
@@ -241,8 +241,10 @@ class PipelineWorker : public Napi::AsyncWorker {
241
241
  // Reload SVG file
242
242
  image = VImage::svgload(const_cast<char*>(baton->input->file.data()), option);
243
243
  }
244
-
245
244
  sharp::SetDensity(image, baton->input->density);
245
+ if (image.width() > 32767 || image.height() > 32767) {
246
+ throw vips::VError("Input SVG image will exceed 32767x32767 pixel limit when scaled");
247
+ }
246
248
  } else if (inputImageType == sharp::ImageType::PDF) {
247
249
  option->set("n", baton->input->pages);
248
250
  option->set("page", baton->input->page);
@@ -260,6 +262,10 @@ class PipelineWorker : public Napi::AsyncWorker {
260
262
 
261
263
  sharp::SetDensity(image, baton->input->density);
262
264
  }
265
+ } else {
266
+ if (inputImageType == sharp::ImageType::SVG && (image.width() > 32767 || image.height() > 32767)) {
267
+ throw vips::VError("Input SVG image exceeds 32767x32767 pixel limit");
268
+ }
263
269
  }
264
270
 
265
271
  // Any pre-shrinking may already have been done
@@ -292,7 +298,8 @@ class PipelineWorker : public Napi::AsyncWorker {
292
298
  if (
293
299
  sharp::HasProfile(image) &&
294
300
  image.interpretation() != VIPS_INTERPRETATION_LABS &&
295
- image.interpretation() != VIPS_INTERPRETATION_GREY16
301
+ image.interpretation() != VIPS_INTERPRETATION_GREY16 &&
302
+ image.interpretation() != VIPS_INTERPRETATION_B_W
296
303
  ) {
297
304
  // Convert to sRGB/P3 using embedded profile
298
305
  try {
@@ -353,7 +360,7 @@ class PipelineWorker : public Napi::AsyncWorker {
353
360
  }
354
361
 
355
362
  bool const shouldPremultiplyAlpha = sharp::HasAlpha(image) &&
356
- (shouldResize || shouldBlur || shouldConv || shouldSharpen || shouldComposite);
363
+ (shouldResize || shouldBlur || shouldConv || shouldSharpen);
357
364
 
358
365
  // Premultiply image alpha channel before all transformations to avoid
359
366
  // dark fringing around bright pixels
@@ -576,11 +583,14 @@ class PipelineWorker : public Napi::AsyncWorker {
576
583
 
577
584
  // Sharpen
578
585
  if (shouldSharpen) {
579
- image = sharp::Sharpen(image, baton->sharpenSigma, baton->sharpenFlat, baton->sharpenJagged);
586
+ image = sharp::Sharpen(image, baton->sharpenSigma, baton->sharpenM1, baton->sharpenM2,
587
+ baton->sharpenX1, baton->sharpenY2, baton->sharpenY3);
580
588
  }
581
589
 
582
590
  // Composite
583
591
  if (shouldComposite) {
592
+ std::vector<VImage> images = { image };
593
+ std::vector<int> modes, xs, ys;
584
594
  for (Composite *composite : baton->composite) {
585
595
  VImage compositeImage;
586
596
  sharp::ImageType compositeImageType = sharp::ImageType::UNKNOWN;
@@ -626,12 +636,12 @@ class PipelineWorker : public Napi::AsyncWorker {
626
636
  // gravity was used for extract_area, set it back to its default value of 0
627
637
  composite->gravity = 0;
628
638
  }
629
- // Ensure image to composite is sRGB with premultiplied alpha
639
+ // Ensure image to composite is sRGB with unpremultiplied alpha
630
640
  compositeImage = compositeImage.colourspace(VIPS_INTERPRETATION_sRGB);
631
641
  if (!sharp::HasAlpha(compositeImage)) {
632
642
  compositeImage = sharp::EnsureAlpha(compositeImage, 1);
633
643
  }
634
- if (!composite->premultiplied) compositeImage = compositeImage.premultiply();
644
+ if (composite->premultiplied) compositeImage = compositeImage.unpremultiply();
635
645
  // Calculate position
636
646
  int left;
637
647
  int top;
@@ -649,12 +659,12 @@ class PipelineWorker : public Napi::AsyncWorker {
649
659
  std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),
650
660
  compositeImage.width(), compositeImage.height(), composite->gravity);
651
661
  }
652
- // Composite
653
- image = image.composite2(compositeImage, composite->mode, VImage::option()
654
- ->set("premultiplied", TRUE)
655
- ->set("x", left)
656
- ->set("y", top));
662
+ images.push_back(compositeImage);
663
+ modes.push_back(composite->mode);
664
+ xs.push_back(left);
665
+ ys.push_back(top);
657
666
  }
667
+ image = image.composite(images, modes, VImage::option()->set("x", xs)->set("y", ys));
658
668
  }
659
669
 
660
670
  // Reverse premultiplication after all transformations:
@@ -1397,8 +1407,11 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1397
1407
  baton->lightness = sharp::AttrAsDouble(options, "lightness");
1398
1408
  baton->medianSize = sharp::AttrAsUint32(options, "medianSize");
1399
1409
  baton->sharpenSigma = sharp::AttrAsDouble(options, "sharpenSigma");
1400
- baton->sharpenFlat = sharp::AttrAsDouble(options, "sharpenFlat");
1401
- baton->sharpenJagged = sharp::AttrAsDouble(options, "sharpenJagged");
1410
+ baton->sharpenM1 = sharp::AttrAsDouble(options, "sharpenM1");
1411
+ baton->sharpenM2 = sharp::AttrAsDouble(options, "sharpenM2");
1412
+ baton->sharpenX1 = sharp::AttrAsDouble(options, "sharpenX1");
1413
+ baton->sharpenY2 = sharp::AttrAsDouble(options, "sharpenY2");
1414
+ baton->sharpenY3 = sharp::AttrAsDouble(options, "sharpenY3");
1402
1415
  baton->threshold = sharp::AttrAsInt32(options, "threshold");
1403
1416
  baton->thresholdGrayscale = sharp::AttrAsBool(options, "thresholdGrayscale");
1404
1417
  baton->trimThreshold = sharp::AttrAsDouble(options, "trimThreshold");
package/src/pipeline.h CHANGED
@@ -90,8 +90,11 @@ struct PipelineBaton {
90
90
  double lightness;
91
91
  int medianSize;
92
92
  double sharpenSigma;
93
- double sharpenFlat;
94
- double sharpenJagged;
93
+ double sharpenM1;
94
+ double sharpenM2;
95
+ double sharpenX1;
96
+ double sharpenY2;
97
+ double sharpenY3;
95
98
  int threshold;
96
99
  bool thresholdGrayscale;
97
100
  double trimThreshold;
@@ -234,8 +237,11 @@ struct PipelineBaton {
234
237
  lightness(0),
235
238
  medianSize(0),
236
239
  sharpenSigma(0.0),
237
- sharpenFlat(1.0),
238
- sharpenJagged(2.0),
240
+ sharpenM1(1.0),
241
+ sharpenM2(2.0),
242
+ sharpenX1(2.0),
243
+ sharpenY2(10.0),
244
+ sharpenY3(20.0),
239
245
  threshold(0),
240
246
  thresholdGrayscale(true),
241
247
  trimThreshold(0.0),
package/src/sharp.cc CHANGED
@@ -13,7 +13,6 @@
13
13
  // limitations under the License.
14
14
 
15
15
  #include <napi.h>
16
- #include <cstdlib>
17
16
  #include <vips/vips8>
18
17
 
19
18
  #include "common.h"
@@ -22,14 +21,6 @@
22
21
  #include "utilities.h"
23
22
  #include "stats.h"
24
23
 
25
- #if defined(_MSC_VER) && _MSC_VER >= 1400 // MSVC 2005/8
26
- static void empty_invalid_parameter_handler(const wchar_t* expression,
27
- const wchar_t* function, const wchar_t* file, unsigned int line,
28
- uintptr_t reserved) {
29
- // No-op.
30
- }
31
- #endif
32
-
33
24
  static void* sharp_vips_init(void*) {
34
25
  g_setenv("VIPS_MIN_STACK_SIZE", "2m", FALSE);
35
26
  vips_init("sharp");
@@ -43,13 +34,6 @@ Napi::Object init(Napi::Env env, Napi::Object exports) {
43
34
  g_log_set_handler("VIPS", static_cast<GLogLevelFlags>(G_LOG_LEVEL_WARNING),
44
35
  static_cast<GLogFunc>(sharp::VipsWarningCallback), nullptr);
45
36
 
46
- // Tell the CRT to not exit the application when an invalid parameter is
47
- // passed. The main issue is that invalid FDs will trigger this behaviour.
48
- // See: https://github.com/libvips/libvips/pull/2571.
49
- #if defined(_MSC_VER) && _MSC_VER >= 1400 // MSVC 2005/8
50
- _set_invalid_parameter_handler(empty_invalid_parameter_handler);
51
- #endif
52
-
53
37
  // Methods available to JavaScript
54
38
  exports.Set("metadata", Napi::Function::New(env, metadata));
55
39
  exports.Set("pipeline", Napi::Function::New(env, pipeline));