sharp 0.30.5 → 0.31.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
@@ -16,7 +16,7 @@ Lanczos resampling ensures quality is not sacrificed for speed.
16
16
  As well as image resizing, operations such as
17
17
  rotation, extraction, compositing and gamma correction are available.
18
18
 
19
- Most modern macOS, Windows and Linux systems running Node.js >= 12.13.0
19
+ Most modern macOS, Windows and Linux systems running Node.js >= 14.15.0
20
20
  do not require any additional install or runtime dependencies.
21
21
 
22
22
  ## Documentation
package/binding.gyp CHANGED
@@ -15,10 +15,11 @@
15
15
  '_ALLOW_KEYWORD_MACROS'
16
16
  ],
17
17
  'sources': [
18
- 'src/libvips/cplusplus/VError.cpp',
19
18
  'src/libvips/cplusplus/VConnection.cpp',
19
+ 'src/libvips/cplusplus/VError.cpp',
20
+ 'src/libvips/cplusplus/VImage.cpp',
20
21
  'src/libvips/cplusplus/VInterpolate.cpp',
21
- 'src/libvips/cplusplus/VImage.cpp'
22
+ 'src/libvips/cplusplus/VRegion.cpp'
22
23
  ],
23
24
  'include_dirs': [
24
25
  '<(sharp_vendor_dir)/include',
@@ -69,7 +70,7 @@
69
70
  }, {
70
71
  'target_name': 'sharp-<(platform_and_arch)',
71
72
  'defines': [
72
- 'NAPI_VERSION=5'
73
+ 'NAPI_VERSION=7'
73
74
  ],
74
75
  'dependencies': [
75
76
  '<!(node -p "require(\'node-addon-api\').gyp")',
package/lib/composite.js CHANGED
@@ -43,6 +43,9 @@ const blend = {
43
43
  * The images to composite must be the same size or smaller than the processed image.
44
44
  * If both `top` and `left` options are provided, they take precedence over `gravity`.
45
45
  *
46
+ * Any resize or rotate operations in the same processing pipeline
47
+ * will always be applied to the input image before composition.
48
+ *
46
49
  * The `blend` option can be one of `clear`, `source`, `over`, `in`, `out`, `atop`,
47
50
  * `dest`, `dest-over`, `dest-in`, `dest-out`, `dest-atop`,
48
51
  * `xor`, `add`, `saturate`, `multiply`, `screen`, `overlay`, `darken`, `lighten`,
@@ -93,6 +96,17 @@ const blend = {
93
96
  * @param {Number} [images[].input.create.height]
94
97
  * @param {Number} [images[].input.create.channels] - 3-4
95
98
  * @param {String|Object} [images[].input.create.background] - parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
99
+ * @param {Object} [images[].input.text] - describes a new text image to be created.
100
+ * @param {string} [images[].input.text.text] - text to render as a UTF-8 string. It can contain Pango markup, for example `<i>Le</i>Monde`.
101
+ * @param {string} [images[].input.text.font] - font name to render with.
102
+ * @param {string} [images[].input.text.fontfile] - absolute filesystem path to a font file that can be used by `font`.
103
+ * @param {number} [images[].input.text.width=0] - integral number of pixels to word-wrap at. Lines of text wider than this will be broken at word boundaries.
104
+ * @param {number} [images[].input.text.height=0] - integral number of pixels high. When defined, `dpi` will be ignored and the text will automatically fit the pixel resolution defined by `width` and `height`. Will be ignored if `width` is not specified or set to 0.
105
+ * @param {string} [images[].input.text.align='left'] - text alignment (`'left'`, `'centre'`, `'center'`, `'right'`).
106
+ * @param {boolean} [images[].input.text.justify=false] - set this to true to apply justification to the text.
107
+ * @param {number} [images[].input.text.dpi=72] - the resolution (size) at which to render the text. Does not take effect if `height` is specified.
108
+ * @param {boolean} [images[].input.text.rgba=false] - set this to true to enable RGBA output. This is useful for colour emoji rendering, or support for pango markup features like `<span foreground="red">Red!</span>`.
109
+ * @param {number} [images[].input.text.spacing=0] - text line height in points. Will use the font line height if none is specified.
96
110
  * @param {String} [images[].blend='over'] - how to blend this image with the image below.
97
111
  * @param {String} [images[].gravity='centre'] - gravity at which to place the overlay.
98
112
  * @param {Number} [images[].top] - the pixel offset from the top edge.
@@ -92,6 +92,27 @@ const debuglog = util.debuglog('sharp');
92
92
  * }
93
93
  * }).toFile('noise.png');
94
94
  *
95
+ * @example
96
+ * // Generate an image from text
97
+ * await sharp({
98
+ * text: {
99
+ * text: 'Hello, world!',
100
+ * width: 400, // max width
101
+ * height: 300 // max height
102
+ * }
103
+ * }).toFile('text_bw.png');
104
+ *
105
+ * @example
106
+ * // Generate an rgba image from text using pango markup and font
107
+ * await sharp({
108
+ * text: {
109
+ * text: '<span foreground="red">Red!</span><span background="cyan">blue</span>',
110
+ * font: 'sans',
111
+ * rgba: true,
112
+ * dpi: 300
113
+ * }
114
+ * }).toFile('text_rgba.png');
115
+ *
95
116
  * @param {(Buffer|Uint8Array|Uint8ClampedArray|Int8Array|Uint16Array|Int16Array|Uint32Array|Int32Array|Float32Array|Float64Array|string)} [input] - if present, can be
96
117
  * a Buffer / Uint8Array / Uint8ClampedArray containing JPEG, PNG, WebP, AVIF, GIF, SVG or TIFF image data, or
97
118
  * a TypedArray containing raw pixel image data, or
@@ -102,7 +123,7 @@ const debuglog = util.debuglog('sharp');
102
123
  * @param {number|boolean} [options.limitInputPixels=268402689] - Do not process input images where the number of pixels
103
124
  * (width x height) exceeds this limit. Assumes image dimensions contained in the input metadata can be trusted.
104
125
  * An integral Number of pixels, zero or false to remove limit, true to use default limit of 268402689 (0x3FFF x 0x3FFF).
105
- * @param {boolean} [options.unlimited=false] - Set this to `true` to remove safety features that help prevent memory exhaustion (SVG, PNG).
126
+ * @param {boolean} [options.unlimited=false] - Set this to `true` to remove safety features that help prevent memory exhaustion (JPEG, PNG, SVG, HEIF).
106
127
  * @param {boolean} [options.sequentialRead=false] - Set this to `true` to use sequential rather than random access where possible.
107
128
  * This can reduce memory usage and might improve performance on some systems.
108
129
  * @param {number} [options.density=72] - number representing the DPI for vector images in the range 1 to 100000.
@@ -126,6 +147,17 @@ const debuglog = util.debuglog('sharp');
126
147
  * @param {string} [options.create.noise.type] - type of generated noise, currently only `gaussian` is supported.
127
148
  * @param {number} [options.create.noise.mean] - mean of pixels in generated noise.
128
149
  * @param {number} [options.create.noise.sigma] - standard deviation of pixels in generated noise.
150
+ * @param {Object} [options.text] - describes a new text image to be created.
151
+ * @param {string} [options.text.text] - text to render as a UTF-8 string. It can contain Pango markup, for example `<i>Le</i>Monde`.
152
+ * @param {string} [options.text.font] - font name to render with.
153
+ * @param {string} [options.text.fontfile] - absolute filesystem path to a font file that can be used by `font`.
154
+ * @param {number} [options.text.width=0] - integral number of pixels to word-wrap at. Lines of text wider than this will be broken at word boundaries.
155
+ * @param {number} [options.text.height=0] - integral number of pixels high. When defined, `dpi` will be ignored and the text will automatically fit the pixel resolution defined by `width` and `height`. Will be ignored if `width` is not specified or set to 0.
156
+ * @param {string} [options.text.align='left'] - text alignment (`'left'`, `'centre'`, `'center'`, `'right'`).
157
+ * @param {boolean} [options.text.justify=false] - set this to true to apply justification to the text.
158
+ * @param {number} [options.text.dpi=72] - the resolution (size) at which to render the text. Does not take effect if `height` is specified.
159
+ * @param {boolean} [options.text.rgba=false] - set this to true to enable RGBA output. This is useful for colour emoji rendering, or support for pango markup features like `<span foreground="red">Red!</span>`.
160
+ * @param {number} [options.text.spacing=0] - text line height in points. Will use the font line height if none is specified.
129
161
  * @returns {Sharp}
130
162
  * @throws {Error} Invalid parameters
131
163
  */
@@ -192,6 +224,7 @@ const Sharp = function (input, options) {
192
224
  sharpenY3: 20,
193
225
  threshold: 0,
194
226
  thresholdGrayscale: true,
227
+ trimBackground: [],
195
228
  trimThreshold: 0,
196
229
  gamma: 0,
197
230
  gammaOut: 0,
@@ -251,9 +284,12 @@ const Sharp = function (input, options) {
251
284
  webpNearLossless: false,
252
285
  webpSmartSubsample: false,
253
286
  webpEffort: 4,
287
+ webpMinSize: false,
288
+ webpMixed: false,
254
289
  gifBitdepth: 8,
255
290
  gifEffort: 7,
256
291
  gifDither: 1,
292
+ gifReoptimise: false,
257
293
  tiffQuality: 80,
258
294
  tiffCompression: 'jpeg',
259
295
  tiffPredictor: 'horizontal',
@@ -282,9 +318,10 @@ const Sharp = function (input, options) {
282
318
  tileBackground: [255, 255, 255, 255],
283
319
  tileCentre: false,
284
320
  tileId: 'https://example.com/iiif',
321
+ tileBasename: '',
285
322
  timeoutSeconds: 0,
286
- linearA: 1,
287
- linearB: 0,
323
+ linearA: [],
324
+ linearB: [],
288
325
  // Function to notify of libvips warnings
289
326
  debuglog: warning => {
290
327
  this.emit('warning', warning);
@@ -346,7 +383,7 @@ Object.setPrototypeOf(Sharp, stream.Duplex);
346
383
  * .toFile("optimized-500.webp")
347
384
  * );
348
385
  *
349
- * // https://github.com/sindresorhus/got#gotstreamurl-options
386
+ * // https://github.com/sindresorhus/got/blob/main/documentation/3-streams.md
350
387
  * got.stream("https://www.example.com/some-file.jpg").pipe(sharpStream);
351
388
  *
352
389
  * Promise.all(promises)
package/lib/input.js CHANGED
@@ -4,6 +4,18 @@ const color = require('color');
4
4
  const is = require('./is');
5
5
  const sharp = require('./sharp');
6
6
 
7
+ /**
8
+ * Justication alignment
9
+ * @member
10
+ * @private
11
+ */
12
+ const align = {
13
+ left: 'low',
14
+ center: 'centre',
15
+ centre: 'centre',
16
+ right: 'high'
17
+ };
18
+
7
19
  /**
8
20
  * Extract input options, if any, from an object.
9
21
  * @private
@@ -86,10 +98,10 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
86
98
  inputDescriptor.limitInputPixels = inputOptions.limitInputPixels
87
99
  ? Math.pow(0x3FFF, 2)
88
100
  : 0;
89
- } else if (is.integer(inputOptions.limitInputPixels) && inputOptions.limitInputPixels >= 0) {
101
+ } else if (is.integer(inputOptions.limitInputPixels) && is.inRange(inputOptions.limitInputPixels, 0, Number.MAX_SAFE_INTEGER)) {
90
102
  inputDescriptor.limitInputPixels = inputOptions.limitInputPixels;
91
103
  } else {
92
- throw is.invalidParameterError('limitInputPixels', 'integer >= 0', inputOptions.limitInputPixels);
104
+ throw is.invalidParameterError('limitInputPixels', 'positive integer', inputOptions.limitInputPixels);
93
105
  }
94
106
  }
95
107
  // unlimited
@@ -245,6 +257,81 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
245
257
  throw new Error('Expected valid width, height and channels to create a new input image');
246
258
  }
247
259
  }
260
+ // Create a new image with text
261
+ if (is.defined(inputOptions.text)) {
262
+ if (is.object(inputOptions.text) && is.string(inputOptions.text.text)) {
263
+ inputDescriptor.textValue = inputOptions.text.text;
264
+ if (is.defined(inputOptions.text.height) && is.defined(inputOptions.text.dpi)) {
265
+ throw new Error('Expected only one of dpi or height');
266
+ }
267
+ if (is.defined(inputOptions.text.font)) {
268
+ if (is.string(inputOptions.text.font)) {
269
+ inputDescriptor.textFont = inputOptions.text.font;
270
+ } else {
271
+ throw is.invalidParameterError('text.font', 'string', inputOptions.text.font);
272
+ }
273
+ }
274
+ if (is.defined(inputOptions.text.fontfile)) {
275
+ if (is.string(inputOptions.text.fontfile)) {
276
+ inputDescriptor.textFontfile = inputOptions.text.fontfile;
277
+ } else {
278
+ throw is.invalidParameterError('text.fontfile', 'string', inputOptions.text.fontfile);
279
+ }
280
+ }
281
+ if (is.defined(inputOptions.text.width)) {
282
+ if (is.number(inputOptions.text.width)) {
283
+ inputDescriptor.textWidth = inputOptions.text.width;
284
+ } else {
285
+ throw is.invalidParameterError('text.textWidth', 'number', inputOptions.text.width);
286
+ }
287
+ }
288
+ if (is.defined(inputOptions.text.height)) {
289
+ if (is.number(inputOptions.text.height)) {
290
+ inputDescriptor.textHeight = inputOptions.text.height;
291
+ } else {
292
+ throw is.invalidParameterError('text.height', 'number', inputOptions.text.height);
293
+ }
294
+ }
295
+ if (is.defined(inputOptions.text.align)) {
296
+ if (is.string(inputOptions.text.align) && is.string(this.constructor.align[inputOptions.text.align])) {
297
+ inputDescriptor.textAlign = this.constructor.align[inputOptions.text.align];
298
+ } else {
299
+ throw is.invalidParameterError('text.align', 'valid alignment', inputOptions.text.align);
300
+ }
301
+ }
302
+ if (is.defined(inputOptions.text.justify)) {
303
+ if (is.bool(inputOptions.text.justify)) {
304
+ inputDescriptor.textJustify = inputOptions.text.justify;
305
+ } else {
306
+ throw is.invalidParameterError('text.justify', 'boolean', inputOptions.text.justify);
307
+ }
308
+ }
309
+ if (is.defined(inputOptions.text.dpi)) {
310
+ if (is.number(inputOptions.text.dpi) && is.inRange(inputOptions.text.dpi, 1, 100000)) {
311
+ inputDescriptor.textDpi = inputOptions.text.dpi;
312
+ } else {
313
+ throw is.invalidParameterError('text.dpi', 'number between 1 and 100000', inputOptions.text.dpi);
314
+ }
315
+ }
316
+ if (is.defined(inputOptions.text.rgba)) {
317
+ if (is.bool(inputOptions.text.rgba)) {
318
+ inputDescriptor.textRgba = inputOptions.text.rgba;
319
+ } else {
320
+ throw is.invalidParameterError('text.rgba', 'bool', inputOptions.text.rgba);
321
+ }
322
+ }
323
+ if (is.defined(inputOptions.text.spacing)) {
324
+ if (is.number(inputOptions.text.spacing)) {
325
+ inputDescriptor.textSpacing = inputOptions.text.spacing;
326
+ } else {
327
+ throw is.invalidParameterError('text.spacing', 'number', inputOptions.text.spacing);
328
+ }
329
+ }
330
+ delete inputDescriptor.buffer;
331
+ } else {
332
+ throw new Error('Expected a valid string to create an image with text.');
333
+ }
334
+ }
248
335
  } else if (is.defined(inputOptions)) {
249
336
  throw new Error('Invalid input options ' + inputOptions);
250
337
  }
@@ -303,6 +390,9 @@ function _isStreamInput () {
303
390
  * This is taken from the header of the input image.
304
391
  * It does not include operations, such as resize, to be applied to the output image.
305
392
  *
393
+ * Dimensions in the response will respect the `page` and `pages` properties of the
394
+ * {@link /api-constructor#parameters|constructor parameters}.
395
+ *
306
396
  * A `Promise` is returned when `callback` is not provided.
307
397
  *
308
398
  * - `format`: Name of decoder used to decompress image data e.g. `jpeg`, `png`, `webp`, `gif`, `svg`
@@ -357,7 +447,7 @@ function _isStreamInput () {
357
447
  * const size = getNormalSize(await sharp(input).metadata());
358
448
  *
359
449
  * function getNormalSize({ width, height, orientation }) {
360
- * return orientation || 0 >= 5
450
+ * return (orientation || 0) >= 5
361
451
  * ? { width: height, height: width }
362
452
  * : { width, height };
363
453
  * }
@@ -504,4 +594,6 @@ module.exports = function (Sharp) {
504
594
  metadata,
505
595
  stats
506
596
  });
597
+ // Class attributes
598
+ Sharp.align = align;
507
599
  };
package/lib/libvips.js CHANGED
@@ -68,6 +68,7 @@ const globalLibvipsVersion = function () {
68
68
  const globalLibvipsVersion = spawnSync('pkg-config --modversion vips-cpp', {
69
69
  ...spawnSyncOptions,
70
70
  env: {
71
+ ...env,
71
72
  PKG_CONFIG_PATH: pkgConfigPath()
72
73
  }
73
74
  }).stdout;
package/lib/operation.js CHANGED
@@ -18,8 +18,11 @@ const is = require('./is');
18
18
  *
19
19
  * The use of `rotate` implies the removal of the EXIF `Orientation` tag, if any.
20
20
  *
21
- * Method order is important when both rotating and extracting regions,
22
- * for example `rotate(x).extract(y)` will produce a different result to `extract(y).rotate(x)`.
21
+ * Only one rotation can occur per pipeline.
22
+ * Previous calls to `rotate` in the same pipeline will be ignored.
23
+ *
24
+ * Method order is important when rotating, resizing and/or extracting regions,
25
+ * for example `.rotate(x).extract(y)` will produce a different result to `.extract(y).rotate(x)`.
23
26
  *
24
27
  * @example
25
28
  * const pipeline = sharp()
@@ -32,6 +35,16 @@ const is = require('./is');
32
35
  * });
33
36
  * readableStream.pipe(pipeline);
34
37
  *
38
+ * @example
39
+ * const rotateThenResize = await sharp(input)
40
+ * .rotate(90)
41
+ * .resize({ width: 16, height: 8, fit: 'fill' })
42
+ * .toBuffer();
43
+ * const resizeThenRotate = await sharp(input)
44
+ * .resize({ width: 16, height: 8, fit: 'fill' })
45
+ * .rotate(90)
46
+ * .toBuffer();
47
+ *
35
48
  * @param {number} [angle=auto] angle of rotation.
36
49
  * @param {Object} [options] - if present, is an Object with optional attributes.
37
50
  * @param {string|Object} [options.background="#000000"] parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
@@ -39,6 +52,9 @@ const is = require('./is');
39
52
  * @throws {Error} Invalid parameters
40
53
  */
41
54
  function rotate (angle, options) {
55
+ if (this.options.useExifOrientation || this.options.angle || this.options.rotationAngle) {
56
+ this.options.debuglog('ignoring previous rotate options');
57
+ }
42
58
  if (!is.defined(angle)) {
43
59
  this.options.useExifOrientation = true;
44
60
  } else if (is.integer(angle) && !(angle % 90)) {
@@ -61,7 +77,7 @@ function rotate (angle, options) {
61
77
  }
62
78
 
63
79
  /**
64
- * Flip the image about the vertical Y axis. This always occurs after rotation, if any.
80
+ * Flip the image about the vertical Y axis. This always occurs before rotation, if any.
65
81
  * The use of `flip` implies the removal of the EXIF `Orientation` tag, if any.
66
82
  *
67
83
  * @example
@@ -76,7 +92,7 @@ function flip (flip) {
76
92
  }
77
93
 
78
94
  /**
79
- * Flop the image about the horizontal X axis. This always occurs after rotation, if any.
95
+ * Flop the image about the horizontal X axis. This always occurs before rotation, if any.
80
96
  * The use of `flop` implies the removal of the EXIF `Orientation` tag, if any.
81
97
  *
82
98
  * @example
@@ -205,7 +221,7 @@ function affine (matrix, options) {
205
221
  * const data = await sharp(input)
206
222
  * .sharpen({
207
223
  * sigma: 2,
208
- * m1: 0
224
+ * m1: 0,
209
225
  * m2: 3,
210
226
  * x1: 3,
211
227
  * y2: 15,
@@ -628,26 +644,55 @@ function boolean (operand, operator, options) {
628
644
  }
629
645
 
630
646
  /**
631
- * Apply the linear formula a * input + b to the image (levels adjustment)
632
- * @param {number} [a=1.0] multiplier
633
- * @param {number} [b=0.0] offset
647
+ * Apply the linear formula `a` * input + `b` to the image to adjust image levels.
648
+ *
649
+ * When a single number is provided, it will be used for all image channels.
650
+ * When an array of numbers is provided, the array length must match the number of channels.
651
+ *
652
+ * @example
653
+ * await sharp(input)
654
+ * .linear(0.5, 2)
655
+ * .toBuffer();
656
+ *
657
+ * @example
658
+ * await sharp(rgbInput)
659
+ * .linear(
660
+ * [0.25, 0.5, 0.75],
661
+ * [150, 100, 50]
662
+ * )
663
+ * .toBuffer();
664
+ *
665
+ * @param {(number|number[])} [a=[]] multiplier
666
+ * @param {(number|number[])} [b=[]] offset
634
667
  * @returns {Sharp}
635
668
  * @throws {Error} Invalid parameters
636
669
  */
637
670
  function linear (a, b) {
671
+ if (!is.defined(a) && is.number(b)) {
672
+ a = 1.0;
673
+ } else if (is.number(a) && !is.defined(b)) {
674
+ b = 0.0;
675
+ }
638
676
  if (!is.defined(a)) {
639
- this.options.linearA = 1.0;
677
+ this.options.linearA = [];
640
678
  } else if (is.number(a)) {
679
+ this.options.linearA = [a];
680
+ } else if (Array.isArray(a) && a.length && a.every(is.number)) {
641
681
  this.options.linearA = a;
642
682
  } else {
643
- throw is.invalidParameterError('a', 'numeric', a);
683
+ throw is.invalidParameterError('a', 'number or array of numbers', a);
644
684
  }
645
685
  if (!is.defined(b)) {
646
- this.options.linearB = 0.0;
686
+ this.options.linearB = [];
647
687
  } else if (is.number(b)) {
688
+ this.options.linearB = [b];
689
+ } else if (Array.isArray(b) && b.length && b.every(is.number)) {
648
690
  this.options.linearB = b;
649
691
  } else {
650
- throw is.invalidParameterError('b', 'numeric', b);
692
+ throw is.invalidParameterError('b', 'number or array of numbers', b);
693
+ }
694
+ if (this.options.linearA.length !== this.options.linearB.length) {
695
+ throw new Error('Expected a and b to be arrays of the same length');
651
696
  }
652
697
  return this;
653
698
  }