sharp 0.31.2 → 0.32.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/lib/index.js CHANGED
@@ -1,3 +1,6 @@
1
+ // Copyright 2013 Lovell Fuller and others.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
1
4
  'use strict';
2
5
 
3
6
  const Sharp = require('./constructor');
package/lib/input.js CHANGED
@@ -1,3 +1,6 @@
1
+ // Copyright 2013 Lovell Fuller and others.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
1
4
  'use strict';
2
5
 
3
6
  const color = require('color');
@@ -21,9 +24,9 @@ const align = {
21
24
  * @private
22
25
  */
23
26
  function _inputOptionsFromObject (obj) {
24
- const { raw, density, limitInputPixels, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd } = obj;
25
- return [raw, density, limitInputPixels, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd].some(is.defined)
26
- ? { raw, density, limitInputPixels, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd }
27
+ const { raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd } = obj;
28
+ return [raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd].some(is.defined)
29
+ ? { raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd }
27
30
  : undefined;
28
31
  }
29
32
 
@@ -35,8 +38,9 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
35
38
  const inputDescriptor = {
36
39
  failOn: 'warning',
37
40
  limitInputPixels: Math.pow(0x3FFF, 2),
41
+ ignoreIcc: false,
38
42
  unlimited: false,
39
- sequentialRead: false
43
+ sequentialRead: true
40
44
  };
41
45
  if (is.string(input)) {
42
46
  // filesystem
@@ -47,6 +51,11 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
47
51
  throw Error('Input Buffer is empty');
48
52
  }
49
53
  inputDescriptor.buffer = input;
54
+ } else if (is.arrayBuffer(input)) {
55
+ if (input.byteLength === 0) {
56
+ throw Error('Input bit Array is empty');
57
+ }
58
+ inputDescriptor.buffer = Buffer.from(input, 0, input.byteLength);
50
59
  } else if (is.typedArray(input)) {
51
60
  if (input.length === 0) {
52
61
  throw Error('Input Bit Array is empty');
@@ -92,6 +101,14 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
92
101
  throw is.invalidParameterError('density', 'number between 1 and 100000', inputOptions.density);
93
102
  }
94
103
  }
104
+ // Ignore embeddded ICC profile
105
+ if (is.defined(inputOptions.ignoreIcc)) {
106
+ if (is.bool(inputOptions.ignoreIcc)) {
107
+ inputDescriptor.ignoreIcc = inputOptions.ignoreIcc;
108
+ } else {
109
+ throw is.invalidParameterError('ignoreIcc', 'boolean', inputOptions.ignoreIcc);
110
+ }
111
+ }
95
112
  // limitInputPixels
96
113
  if (is.defined(inputOptions.limitInputPixels)) {
97
114
  if (is.bool(inputOptions.limitInputPixels)) {
@@ -327,6 +344,13 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
327
344
  throw is.invalidParameterError('text.spacing', 'number', inputOptions.text.spacing);
328
345
  }
329
346
  }
347
+ if (is.defined(inputOptions.text.wrap)) {
348
+ if (is.string(inputOptions.text.wrap) && is.inArray(inputOptions.text.wrap, ['word', 'char', 'wordChar', 'none'])) {
349
+ inputDescriptor.textWrap = inputOptions.text.wrap;
350
+ } else {
351
+ throw is.invalidParameterError('text.wrap', 'one of: word, char, wordChar, none', inputOptions.text.wrap);
352
+ }
353
+ }
330
354
  delete inputDescriptor.buffer;
331
355
  } else {
332
356
  throw new Error('Expected a valid string to create an image with text.');
@@ -387,8 +411,9 @@ function _isStreamInput () {
387
411
  /**
388
412
  * Fast access to (uncached) image metadata without decoding any compressed pixel data.
389
413
  *
390
- * This is taken from the header of the input image.
391
- * It does not include operations, such as resize, to be applied to the output image.
414
+ * This is read from the header of the input image.
415
+ * It does not take into consideration any operations to be applied to the output image,
416
+ * such as resize or rotate.
392
417
  *
393
418
  * Dimensions in the response will respect the `page` and `pages` properties of the
394
419
  * {@link /api-constructor#parameters|constructor parameters}.
@@ -423,6 +448,7 @@ function _isStreamInput () {
423
448
  * - `iptc`: Buffer containing raw IPTC data, if present
424
449
  * - `xmp`: Buffer containing raw XMP data, if present
425
450
  * - `tifftagPhotoshop`: Buffer containing raw TIFFTAG_PHOTOSHOP data, if present
451
+ * - `formatMagick`: String containing format for images loaded via *magick
426
452
  *
427
453
  * @example
428
454
  * const metadata = await sharp(input).metadata();
@@ -469,7 +495,7 @@ function metadata (callback) {
469
495
  } else {
470
496
  if (this._isStreamInput()) {
471
497
  return new Promise((resolve, reject) => {
472
- this.on('finish', () => {
498
+ const finished = () => {
473
499
  this._flattenBufferIn();
474
500
  sharp.metadata(this.options, (err, metadata) => {
475
501
  if (err) {
@@ -478,7 +504,12 @@ function metadata (callback) {
478
504
  resolve(metadata);
479
505
  }
480
506
  });
481
- });
507
+ };
508
+ if (this.writableFinished) {
509
+ finished();
510
+ } else {
511
+ this.once('finish', finished);
512
+ }
482
513
  });
483
514
  } else {
484
515
  return new Promise((resolve, reject) => {
package/lib/is.js CHANGED
@@ -1,3 +1,6 @@
1
+ // Copyright 2013 Lovell Fuller and others.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
1
4
  'use strict';
2
5
 
3
6
  /**
@@ -71,6 +74,14 @@ const typedArray = function (val) {
71
74
  return false;
72
75
  };
73
76
 
77
+ /**
78
+ * Is this value an ArrayBuffer object?
79
+ * @private
80
+ */
81
+ const arrayBuffer = function (val) {
82
+ return val instanceof ArrayBuffer;
83
+ };
84
+
74
85
  /**
75
86
  * Is this value a non-empty string?
76
87
  * @private
@@ -134,6 +145,7 @@ module.exports = {
134
145
  bool: bool,
135
146
  buffer: buffer,
136
147
  typedArray: typedArray,
148
+ arrayBuffer: arrayBuffer,
137
149
  string: string,
138
150
  number: number,
139
151
  integer: integer,
package/lib/libvips.js CHANGED
@@ -1,3 +1,6 @@
1
+ // Copyright 2013 Lovell Fuller and others.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
1
4
  'use strict';
2
5
 
3
6
  const fs = require('fs');
@@ -115,6 +118,7 @@ const useGlobalLibvips = function () {
115
118
  }
116
119
  /* istanbul ignore next */
117
120
  if (isRosetta()) {
121
+ log('Detected Rosetta, skipping search for globally-installed libvips');
118
122
  return false;
119
123
  }
120
124
  const globalVipsVersion = globalLibvipsVersion();
package/lib/operation.js CHANGED
@@ -1,3 +1,6 @@
1
+ // Copyright 2013 Lovell Fuller and others.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
1
4
  'use strict';
2
5
 
3
6
  const color = require('color');
@@ -205,9 +208,11 @@ function affine (matrix, options) {
205
208
 
206
209
  /**
207
210
  * Sharpen the image.
211
+ *
208
212
  * When used without parameters, performs a fast, mild sharpen of the output image.
213
+ *
209
214
  * When a `sigma` is provided, performs a slower, more accurate sharpen of the L channel in the LAB colour space.
210
- * Separate control over the level of sharpening in "flat" and "jagged" areas is available.
215
+ * Fine-grained control over the level of sharpening in "flat" (m1) and "jagged" (m2) areas is available.
211
216
  *
212
217
  * See {@link https://www.libvips.org/API/current/libvips-convolution.html#vips-sharpen|libvips sharpen} operation.
213
218
  *
@@ -229,13 +234,13 @@ function affine (matrix, options) {
229
234
  * })
230
235
  * .toBuffer();
231
236
  *
232
- * @param {Object|number} [options] - if present, is an Object with attributes or (deprecated) a number for `options.sigma`.
233
- * @param {number} [options.sigma] - the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
234
- * @param {number} [options.m1=1.0] - the level of sharpening to apply to "flat" areas.
235
- * @param {number} [options.m2=2.0] - the level of sharpening to apply to "jagged" areas.
236
- * @param {number} [options.x1=2.0] - threshold between "flat" and "jagged"
237
- * @param {number} [options.y2=10.0] - maximum amount of brightening.
238
- * @param {number} [options.y3=20.0] - maximum amount of darkening.
237
+ * @param {Object|number} [options] - if present, is an Object with attributes
238
+ * @param {number} [options.sigma] - the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`, between 0.000001 and 10
239
+ * @param {number} [options.m1=1.0] - the level of sharpening to apply to "flat" areas, between 0 and 1000000
240
+ * @param {number} [options.m2=2.0] - the level of sharpening to apply to "jagged" areas, between 0 and 1000000
241
+ * @param {number} [options.x1=2.0] - threshold between "flat" and "jagged", between 0 and 1000000
242
+ * @param {number} [options.y2=10.0] - maximum amount of brightening, between 0 and 1000000
243
+ * @param {number} [options.y3=20.0] - maximum amount of darkening, between 0 and 1000000
239
244
  * @param {number} [flat] - (deprecated) see `options.m1`.
240
245
  * @param {number} [jagged] - (deprecated) see `options.m2`.
241
246
  * @returns {Sharp}
@@ -268,44 +273,44 @@ function sharpen (options, flat, jagged) {
268
273
  }
269
274
  }
270
275
  } else if (is.plainObject(options)) {
271
- if (is.number(options.sigma) && is.inRange(options.sigma, 0.01, 10000)) {
276
+ if (is.number(options.sigma) && is.inRange(options.sigma, 0.000001, 10)) {
272
277
  this.options.sharpenSigma = options.sigma;
273
278
  } else {
274
- throw is.invalidParameterError('options.sigma', 'number between 0.01 and 10000', options.sigma);
279
+ throw is.invalidParameterError('options.sigma', 'number between 0.000001 and 10', options.sigma);
275
280
  }
276
281
  if (is.defined(options.m1)) {
277
- if (is.number(options.m1) && is.inRange(options.m1, 0, 10000)) {
282
+ if (is.number(options.m1) && is.inRange(options.m1, 0, 1000000)) {
278
283
  this.options.sharpenM1 = options.m1;
279
284
  } else {
280
- throw is.invalidParameterError('options.m1', 'number between 0 and 10000', options.m1);
285
+ throw is.invalidParameterError('options.m1', 'number between 0 and 1000000', options.m1);
281
286
  }
282
287
  }
283
288
  if (is.defined(options.m2)) {
284
- if (is.number(options.m2) && is.inRange(options.m2, 0, 10000)) {
289
+ if (is.number(options.m2) && is.inRange(options.m2, 0, 1000000)) {
285
290
  this.options.sharpenM2 = options.m2;
286
291
  } else {
287
- throw is.invalidParameterError('options.m2', 'number between 0 and 10000', options.m2);
292
+ throw is.invalidParameterError('options.m2', 'number between 0 and 1000000', options.m2);
288
293
  }
289
294
  }
290
295
  if (is.defined(options.x1)) {
291
- if (is.number(options.x1) && is.inRange(options.x1, 0, 10000)) {
296
+ if (is.number(options.x1) && is.inRange(options.x1, 0, 1000000)) {
292
297
  this.options.sharpenX1 = options.x1;
293
298
  } else {
294
- throw is.invalidParameterError('options.x1', 'number between 0 and 10000', options.x1);
299
+ throw is.invalidParameterError('options.x1', 'number between 0 and 1000000', options.x1);
295
300
  }
296
301
  }
297
302
  if (is.defined(options.y2)) {
298
- if (is.number(options.y2) && is.inRange(options.y2, 0, 10000)) {
303
+ if (is.number(options.y2) && is.inRange(options.y2, 0, 1000000)) {
299
304
  this.options.sharpenY2 = options.y2;
300
305
  } else {
301
- throw is.invalidParameterError('options.y2', 'number between 0 and 10000', options.y2);
306
+ throw is.invalidParameterError('options.y2', 'number between 0 and 1000000', options.y2);
302
307
  }
303
308
  }
304
309
  if (is.defined(options.y3)) {
305
- if (is.number(options.y3) && is.inRange(options.y3, 0, 10000)) {
310
+ if (is.number(options.y3) && is.inRange(options.y3, 0, 1000000)) {
306
311
  this.options.sharpenY3 = options.y3;
307
312
  } else {
308
- throw is.invalidParameterError('options.y3', 'number between 0 and 10000', options.y3);
313
+ throw is.invalidParameterError('options.y3', 'number between 0 and 1000000', options.y3);
309
314
  }
310
315
  }
311
316
  } else {
@@ -464,16 +469,50 @@ function negate (options) {
464
469
  }
465
470
 
466
471
  /**
467
- * Enhance output image contrast by stretching its luminance to cover the full dynamic range.
472
+ * Enhance output image contrast by stretching its luminance to cover a full dynamic range.
473
+ *
474
+ * Uses a histogram-based approach, taking a default range of 1% to 99% to reduce sensitivity to noise at the extremes.
475
+ *
476
+ * Luminance values below the `lower` percentile will be underexposed by clipping to zero.
477
+ * Luminance values above the `upper` percentile will be overexposed by clipping to the max pixel value.
468
478
  *
469
479
  * @example
470
- * const output = await sharp(input).normalise().toBuffer();
480
+ * const output = await sharp(input)
481
+ * .normalise()
482
+ * .toBuffer();
471
483
  *
472
- * @param {Boolean} [normalise=true]
484
+ * @example
485
+ * const output = await sharp(input)
486
+ * .normalise({ lower: 0, upper: 100 })
487
+ * .toBuffer();
488
+ *
489
+ * @param {Object} [options]
490
+ * @param {number} [options.lower=1] - Percentile below which luminance values will be underexposed.
491
+ * @param {number} [options.upper=99] - Percentile above which luminance values will be overexposed.
473
492
  * @returns {Sharp}
474
493
  */
475
- function normalise (normalise) {
476
- this.options.normalise = is.bool(normalise) ? normalise : true;
494
+ function normalise (options) {
495
+ if (is.plainObject(options)) {
496
+ if (is.defined(options.lower)) {
497
+ if (is.number(options.lower) && is.inRange(options.lower, 0, 99)) {
498
+ this.options.normaliseLower = options.lower;
499
+ } else {
500
+ throw is.invalidParameterError('lower', 'number between 0 and 99', options.lower);
501
+ }
502
+ }
503
+ if (is.defined(options.upper)) {
504
+ if (is.number(options.upper) && is.inRange(options.upper, 1, 100)) {
505
+ this.options.normaliseUpper = options.upper;
506
+ } else {
507
+ throw is.invalidParameterError('upper', 'number between 1 and 100', options.upper);
508
+ }
509
+ }
510
+ }
511
+ if (this.options.normaliseLower >= this.options.normaliseUpper) {
512
+ throw is.invalidParameterError('range', 'lower to be less than upper',
513
+ `${this.options.normaliseLower} >= ${this.options.normaliseUpper}`);
514
+ }
515
+ this.options.normalise = true;
477
516
  return this;
478
517
  }
479
518
 
@@ -481,13 +520,17 @@ function normalise (normalise) {
481
520
  * Alternative spelling of normalise.
482
521
  *
483
522
  * @example
484
- * const output = await sharp(input).normalize().toBuffer();
523
+ * const output = await sharp(input)
524
+ * .normalize()
525
+ * .toBuffer();
485
526
  *
486
- * @param {Boolean} [normalize=true]
527
+ * @param {Object} [options]
528
+ * @param {number} [options.lower=1] - Percentile below which luminance values will be underexposed.
529
+ * @param {number} [options.upper=99] - Percentile above which luminance values will be overexposed.
487
530
  * @returns {Sharp}
488
531
  */
489
- function normalize (normalize) {
490
- return this.normalise(normalize);
532
+ function normalize (options) {
533
+ return this.normalise(options);
491
534
  }
492
535
 
493
536
  /**
@@ -507,34 +550,33 @@ function normalize (normalize) {
507
550
  * .toBuffer();
508
551
  *
509
552
  * @param {Object} options
510
- * @param {number} options.width - integer width of the region in pixels.
511
- * @param {number} options.height - integer height of the region in pixels.
512
- * @param {number} [options.maxSlope=3] - maximum value for the slope of the
513
- * cumulative histogram. A value of 0 disables contrast limiting. Valid values
514
- * are integers in the range 0-100 (inclusive)
553
+ * @param {number} options.width - Integral width of the search window, in pixels.
554
+ * @param {number} options.height - Integral height of the search window, in pixels.
555
+ * @param {number} [options.maxSlope=3] - Integral level of brightening, between 0 and 100, where 0 disables contrast limiting.
515
556
  * @returns {Sharp}
516
557
  * @throws {Error} Invalid parameters
517
558
  */
518
559
  function clahe (options) {
519
- if (!is.plainObject(options)) {
520
- throw is.invalidParameterError('options', 'plain object', options);
521
- }
522
- if (!('width' in options) || !is.integer(options.width) || options.width <= 0) {
523
- throw is.invalidParameterError('width', 'integer above zero', options.width);
524
- } else {
525
- this.options.claheWidth = options.width;
526
- }
527
- if (!('height' in options) || !is.integer(options.height) || options.height <= 0) {
528
- throw is.invalidParameterError('height', 'integer above zero', options.height);
529
- } else {
530
- this.options.claheHeight = options.height;
531
- }
532
- if (!is.defined(options.maxSlope)) {
533
- this.options.claheMaxSlope = 3;
534
- } else if (!is.integer(options.maxSlope) || options.maxSlope < 0 || options.maxSlope > 100) {
535
- throw is.invalidParameterError('maxSlope', 'integer 0-100', options.maxSlope);
560
+ if (is.plainObject(options)) {
561
+ if (is.integer(options.width) && options.width > 0) {
562
+ this.options.claheWidth = options.width;
563
+ } else {
564
+ throw is.invalidParameterError('width', 'integer greater than zero', options.width);
565
+ }
566
+ if (is.integer(options.height) && options.height > 0) {
567
+ this.options.claheHeight = options.height;
568
+ } else {
569
+ throw is.invalidParameterError('height', 'integer greater than zero', options.height);
570
+ }
571
+ if (is.defined(options.maxSlope)) {
572
+ if (is.integer(options.maxSlope) && is.inRange(options.maxSlope, 0, 100)) {
573
+ this.options.claheMaxSlope = options.maxSlope;
574
+ } else {
575
+ throw is.invalidParameterError('maxSlope', 'integer between 0 and 100', options.maxSlope);
576
+ }
577
+ }
536
578
  } else {
537
- this.options.claheMaxSlope = options.maxSlope;
579
+ throw is.invalidParameterError('options', 'plain object', options);
538
580
  }
539
581
  return this;
540
582
  }
package/lib/output.js CHANGED
@@ -1,3 +1,6 @@
1
+ // Copyright 2013 Lovell Fuller and others.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
1
4
  'use strict';
2
5
 
3
6
  const path = require('path');
@@ -22,10 +25,13 @@ const formats = new Map([
22
25
  ['jp2', 'jp2'],
23
26
  ['jpx', 'jp2'],
24
27
  ['j2k', 'jp2'],
25
- ['j2c', 'jp2']
28
+ ['j2c', 'jp2'],
29
+ ['jxl', 'jxl']
26
30
  ]);
27
31
 
28
- const errJp2Save = new Error('JP2 output requires libvips with support for OpenJPEG');
32
+ const jp2Regex = /\.jp[2x]|j2[kc]$/i;
33
+
34
+ const errJp2Save = () => new Error('JP2 output requires libvips with support for OpenJPEG');
29
35
 
30
36
  const bitdepthFromColourCount = (colours) => 1 << 31 - Math.clz32(Math.ceil(Math.log2(colours)));
31
37
 
@@ -58,6 +64,7 @@ const bitdepthFromColourCount = (colours) => 1 << 31 - Math.clz32(Math.ceil(Math
58
64
  * `info` contains the output image `format`, `size` (bytes), `width`, `height`,
59
65
  * `channels` and `premultiplied` (indicating if premultiplication was used).
60
66
  * When using a crop strategy also contains `cropOffsetLeft` and `cropOffsetTop`.
67
+ * When using the attention crop strategy also contains `attentionX` and `attentionY`, the focal point of the cropped region.
61
68
  * May also contain `textAutofitDpi` (dpi the font was rendered at) if image was created from text.
62
69
  * @returns {Promise<Object>} - when no callback is provided
63
70
  * @throws {Error} Invalid parameters
@@ -68,6 +75,8 @@ function toFile (fileOut, callback) {
68
75
  err = new Error('Missing output file path');
69
76
  } else if (is.string(this.options.input.file) && path.resolve(this.options.input.file) === path.resolve(fileOut)) {
70
77
  err = new Error('Cannot use same file for input and output');
78
+ } else if (jp2Regex.test(fileOut) && !this.constructor.format.jp2k.output.file) {
79
+ err = errJp2Save();
71
80
  }
72
81
  if (err) {
73
82
  if (is.fn(callback)) {
@@ -187,7 +196,7 @@ function toBuffer (options, callback) {
187
196
  *
188
197
  * @param {Object} [options]
189
198
  * @param {number} [options.orientation] value between 1 and 8, used to update the EXIF `Orientation` tag.
190
- * @param {string} [options.icc] filesystem path to output ICC profile, defaults to sRGB.
199
+ * @param {string} [options.icc='srgb'] Filesystem path to output ICC profile, relative to `process.cwd()`, defaults to built-in sRGB.
191
200
  * @param {Object<Object>} [options.exif={}] Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data.
192
201
  * @param {number} [options.density] Number of pixels per inch (DPI).
193
202
  * @returns {Sharp}
@@ -547,13 +556,21 @@ function webp (options) {
547
556
  * .gif({ dither: 0 })
548
557
  * .toBuffer();
549
558
  *
559
+ * @example
560
+ * // Lossy file size reduction of animated GIF
561
+ * await sharp('in.gif', { animated: true })
562
+ * .gif({ interFrameMaxError: 8 })
563
+ * .toFile('optim.gif');
564
+ *
550
565
  * @param {Object} [options] - output options
551
- * @param {boolean} [options.reoptimise=false] - always generate new palettes (slow), re-use existing by default
552
- * @param {boolean} [options.reoptimize=false] - alternative spelling of `options.reoptimise`
566
+ * @param {boolean} [options.reuse=true] - re-use existing palette, otherwise generate new (slow)
567
+ * @param {boolean} [options.progressive=false] - use progressive (interlace) scan
553
568
  * @param {number} [options.colours=256] - maximum number of palette entries, including transparency, between 2 and 256
554
569
  * @param {number} [options.colors=256] - alternative spelling of `options.colours`
555
570
  * @param {number} [options.effort=7] - CPU effort, between 1 (fastest) and 10 (slowest)
556
571
  * @param {number} [options.dither=1.0] - level of Floyd-Steinberg error diffusion, between 0 (least) and 1 (most)
572
+ * @param {number} [options.interFrameMaxError=0] - maximum inter-frame error for transparency, between 0 (lossless) and 32
573
+ * @param {number} [options.interPaletteMaxError=3] - maximum inter-palette error for palette reuse, between 0 and 256
557
574
  * @param {number} [options.loop=0] - number of animation iterations, use 0 for infinite animation
558
575
  * @param {number|number[]} [options.delay] - delay(s) between animation frames (in milliseconds)
559
576
  * @param {boolean} [options.force=true] - force GIF output, otherwise attempt to use input format
@@ -562,10 +579,11 @@ function webp (options) {
562
579
  */
563
580
  function gif (options) {
564
581
  if (is.object(options)) {
565
- if (is.defined(options.reoptimise)) {
566
- this._setBooleanOption('gifReoptimise', options.reoptimise);
567
- } else if (is.defined(options.reoptimize)) {
568
- this._setBooleanOption('gifReoptimise', options.reoptimize);
582
+ if (is.defined(options.reuse)) {
583
+ this._setBooleanOption('gifReuse', options.reuse);
584
+ }
585
+ if (is.defined(options.progressive)) {
586
+ this._setBooleanOption('gifProgressive', options.progressive);
569
587
  }
570
588
  const colours = options.colours || options.colors;
571
589
  if (is.defined(colours)) {
@@ -589,6 +607,20 @@ function gif (options) {
589
607
  throw is.invalidParameterError('dither', 'number between 0.0 and 1.0', options.dither);
590
608
  }
591
609
  }
610
+ if (is.defined(options.interFrameMaxError)) {
611
+ if (is.number(options.interFrameMaxError) && is.inRange(options.interFrameMaxError, 0, 32)) {
612
+ this.options.gifInterFrameMaxError = options.interFrameMaxError;
613
+ } else {
614
+ throw is.invalidParameterError('interFrameMaxError', 'number between 0.0 and 32.0', options.interFrameMaxError);
615
+ }
616
+ }
617
+ if (is.defined(options.interPaletteMaxError)) {
618
+ if (is.number(options.interPaletteMaxError) && is.inRange(options.interPaletteMaxError, 0, 256)) {
619
+ this.options.gifInterPaletteMaxError = options.interPaletteMaxError;
620
+ } else {
621
+ throw is.invalidParameterError('interPaletteMaxError', 'number between 0.0 and 256.0', options.interPaletteMaxError);
622
+ }
623
+ }
592
624
  }
593
625
  trySetAnimationOptions(options, this.options);
594
626
  return this._updateFormatOut('gif', options);
@@ -630,7 +662,7 @@ function gif (options) {
630
662
  /* istanbul ignore next */
631
663
  function jp2 (options) {
632
664
  if (!this.constructor.format.jp2k.output.buffer) {
633
- throw errJp2Save;
665
+ throw errJp2Save();
634
666
  }
635
667
  if (is.object(options)) {
636
668
  if (is.defined(options.quality)) {
@@ -663,7 +695,7 @@ function jp2 (options) {
663
695
  }
664
696
  if (is.defined(options.chromaSubsampling)) {
665
697
  if (is.string(options.chromaSubsampling) && is.inArray(options.chromaSubsampling, ['4:2:0', '4:4:4'])) {
666
- this.options.heifChromaSubsampling = options.chromaSubsampling;
698
+ this.options.jp2ChromaSubsampling = options.chromaSubsampling;
667
699
  } else {
668
700
  throw is.invalidParameterError('chromaSubsampling', 'one of: 4:2:0, 4:4:4', options.chromaSubsampling);
669
701
  }
@@ -912,6 +944,71 @@ function heif (options) {
912
944
  return this._updateFormatOut('heif', options);
913
945
  }
914
946
 
947
+ /**
948
+ * Use these JPEG-XL (JXL) options for output image.
949
+ *
950
+ * This feature is experimental, please do not use in production systems.
951
+ *
952
+ * Requires libvips compiled with support for libjxl.
953
+ * The prebuilt binaries do not include this - see
954
+ * {@link https://sharp.pixelplumbing.com/install#custom-libvips installing a custom libvips}.
955
+ *
956
+ * Image metadata (EXIF, XMP) is unsupported.
957
+ *
958
+ * @since 0.31.3
959
+ *
960
+ * @param {Object} [options] - output options
961
+ * @param {number} [options.distance=1.0] - maximum encoding error, between 0 (highest quality) and 15 (lowest quality)
962
+ * @param {number} [options.quality] - calculate `distance` based on JPEG-like quality, between 1 and 100, overrides distance if specified
963
+ * @param {number} [options.decodingTier=0] - target decode speed tier, between 0 (highest quality) and 4 (lowest quality)
964
+ * @param {boolean} [options.lossless=false] - use lossless compression
965
+ * @param {number} [options.effort=7] - CPU effort, between 3 (fastest) and 9 (slowest)
966
+ * @returns {Sharp}
967
+ * @throws {Error} Invalid options
968
+ */
969
+ function jxl (options) {
970
+ if (is.object(options)) {
971
+ if (is.defined(options.quality)) {
972
+ if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
973
+ // https://github.com/libjxl/libjxl/blob/0aeea7f180bafd6893c1db8072dcb67d2aa5b03d/tools/cjxl_main.cc#L640-L644
974
+ this.options.jxlDistance = options.quality >= 30
975
+ ? 0.1 + (100 - options.quality) * 0.09
976
+ : 53 / 3000 * options.quality * options.quality - 23 / 20 * options.quality + 25;
977
+ } else {
978
+ throw is.invalidParameterError('quality', 'integer between 1 and 100', options.quality);
979
+ }
980
+ } else if (is.defined(options.distance)) {
981
+ if (is.number(options.distance) && is.inRange(options.distance, 0, 15)) {
982
+ this.options.jxlDistance = options.distance;
983
+ } else {
984
+ throw is.invalidParameterError('distance', 'number between 0.0 and 15.0', options.distance);
985
+ }
986
+ }
987
+ if (is.defined(options.decodingTier)) {
988
+ if (is.integer(options.decodingTier) && is.inRange(options.decodingTier, 0, 4)) {
989
+ this.options.jxlDecodingTier = options.decodingTier;
990
+ } else {
991
+ throw is.invalidParameterError('decodingTier', 'integer between 0 and 4', options.decodingTier);
992
+ }
993
+ }
994
+ if (is.defined(options.lossless)) {
995
+ if (is.bool(options.lossless)) {
996
+ this.options.jxlLossless = options.lossless;
997
+ } else {
998
+ throw is.invalidParameterError('lossless', 'boolean', options.lossless);
999
+ }
1000
+ }
1001
+ if (is.defined(options.effort)) {
1002
+ if (is.integer(options.effort) && is.inRange(options.effort, 3, 9)) {
1003
+ this.options.jxlEffort = options.effort;
1004
+ } else {
1005
+ throw is.invalidParameterError('effort', 'integer between 3 and 9', options.effort);
1006
+ }
1007
+ }
1008
+ }
1009
+ return this._updateFormatOut('jxl', options);
1010
+ }
1011
+
915
1012
  /**
916
1013
  * Force output to be raw, uncompressed pixel data.
917
1014
  * Pixel ordering is left-to-right, top-to-bottom, without padding.
@@ -959,6 +1056,10 @@ function raw (options) {
959
1056
  *
960
1057
  * The container will be set to `zip` when the output is a Buffer or Stream, otherwise it will default to `fs`.
961
1058
  *
1059
+ * Requires libvips compiled with support for libgsf.
1060
+ * The prebuilt binaries do not include this - see
1061
+ * {@link https://sharp.pixelplumbing.com/install#custom-libvips installing a custom libvips}.
1062
+ *
962
1063
  * @example
963
1064
  * sharp('input.tiff')
964
1065
  * .png()
@@ -1282,6 +1383,7 @@ module.exports = function (Sharp) {
1282
1383
  tiff,
1283
1384
  avif,
1284
1385
  heif,
1386
+ jxl,
1285
1387
  gif,
1286
1388
  raw,
1287
1389
  tile,
package/lib/platform.js CHANGED
@@ -1,3 +1,6 @@
1
+ // Copyright 2013 Lovell Fuller and others.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
1
4
  'use strict';
2
5
 
3
6
  const detectLibc = require('detect-libc');