sharp 0.29.0 → 0.30.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/output.js CHANGED
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ const path = require('path');
3
4
  const is = require('./is');
4
5
  const sharp = require('./sharp');
5
6
 
@@ -12,22 +13,31 @@ const formats = new Map([
12
13
  ['png', 'png'],
13
14
  ['raw', 'raw'],
14
15
  ['tiff', 'tiff'],
16
+ ['tif', 'tiff'],
15
17
  ['webp', 'webp'],
16
- ['gif', 'gif']
18
+ ['gif', 'gif'],
19
+ ['jp2', 'jp2'],
20
+ ['jpx', 'jp2'],
21
+ ['j2k', 'jp2'],
22
+ ['j2c', 'jp2']
17
23
  ]);
18
24
 
19
- const errMagickSave = new Error('GIF output requires libvips with support for ImageMagick');
25
+ const errJp2Save = new Error('JP2 output requires libvips with support for OpenJPEG');
26
+
27
+ const bitdepthFromColourCount = (colours) => 1 << 31 - Math.clz32(Math.ceil(Math.log2(colours)));
20
28
 
21
29
  /**
22
30
  * Write output image data to a file.
23
31
  *
24
32
  * If an explicit output format is not selected, it will be inferred from the extension,
25
- * with JPEG, PNG, WebP, AVIF, TIFF, DZI, and libvips' V format supported.
33
+ * with JPEG, PNG, WebP, AVIF, TIFF, GIF, DZI, and libvips' V format supported.
26
34
  * Note that raw pixel data is only supported for buffer output.
27
35
  *
28
36
  * By default all metadata will be removed, which includes EXIF-based orientation.
29
37
  * See {@link withMetadata} for control over this.
30
38
  *
39
+ * The caller is responsible for ensuring directory structures and permissions exist.
40
+ *
31
41
  * A `Promise` is returned when `callback` is not provided.
32
42
  *
33
43
  * @example
@@ -52,10 +62,8 @@ function toFile (fileOut, callback) {
52
62
  let err;
53
63
  if (!is.string(fileOut)) {
54
64
  err = new Error('Missing output file path');
55
- } else if (this.options.input.file === fileOut) {
65
+ } else if (is.string(this.options.input.file) && path.resolve(this.options.input.file) === path.resolve(fileOut)) {
56
66
  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
67
  }
60
68
  if (err) {
61
69
  if (is.fn(callback)) {
@@ -72,9 +80,9 @@ function toFile (fileOut, callback) {
72
80
 
73
81
  /**
74
82
  * Write output to a Buffer.
75
- * JPEG, PNG, WebP, AVIF, TIFF and raw pixel data output are supported.
83
+ * JPEG, PNG, WebP, AVIF, TIFF, GIF and raw pixel data output are supported.
76
84
  *
77
- * If no explicit format is set, the output format will match the input image, except GIF and SVG input which become PNG output.
85
+ * If no explicit format is set, the output format will match the input image, except SVG input which becomes PNG output.
78
86
  *
79
87
  * By default all metadata will be removed, which includes EXIF-based orientation.
80
88
  * See {@link withMetadata} for control over this.
@@ -160,7 +168,7 @@ function toBuffer (options, callback) {
160
168
  * })
161
169
  * .toBuffer();
162
170
  *
163
- * * @example
171
+ * @example
164
172
  * // Set output metadata to 96 DPI
165
173
  * const data = await sharp(input)
166
174
  * .withMetadata({ density: 96 })
@@ -365,6 +373,7 @@ function jpeg (options) {
365
373
  * @param {boolean} [options.adaptiveFiltering=false] - use adaptive row filtering
366
374
  * @param {boolean} [options.palette=false] - quantise to a palette-based image with alpha transparency support
367
375
  * @param {number} [options.quality=100] - use the lowest number of colours needed to achieve given quality, sets `palette` to `true`
376
+ * @param {number} [options.effort=7] - CPU effort, between 1 (fastest) and 10 (slowest), sets `palette` to `true`
368
377
  * @param {number} [options.colours=256] - maximum number of palette entries, sets `palette` to `true`
369
378
  * @param {number} [options.colors=256] - alternative spelling of `options.colours`, sets `palette` to `true`
370
379
  * @param {number} [options.dither=1.0] - level of Floyd-Steinberg error diffusion, sets `palette` to `true`
@@ -389,7 +398,7 @@ function png (options) {
389
398
  }
390
399
  if (is.defined(options.palette)) {
391
400
  this._setBooleanOption('pngPalette', options.palette);
392
- } else if (is.defined(options.quality) || is.defined(options.colours || options.colors) || is.defined(options.dither)) {
401
+ } else if ([options.quality, options.effort, options.colours, options.colors, options.dither].some(is.defined)) {
393
402
  this._setBooleanOption('pngPalette', true);
394
403
  }
395
404
  if (this.options.pngPalette) {
@@ -400,10 +409,17 @@ function png (options) {
400
409
  throw is.invalidParameterError('quality', 'integer between 0 and 100', options.quality);
401
410
  }
402
411
  }
412
+ if (is.defined(options.effort)) {
413
+ if (is.integer(options.effort) && is.inRange(options.effort, 1, 10)) {
414
+ this.options.pngEffort = options.effort;
415
+ } else {
416
+ throw is.invalidParameterError('effort', 'integer between 1 and 10', options.effort);
417
+ }
418
+ }
403
419
  const colours = options.colours || options.colors;
404
420
  if (is.defined(colours)) {
405
421
  if (is.integer(colours) && is.inRange(colours, 2, 256)) {
406
- this.options.pngColours = colours;
422
+ this.options.pngBitdepth = bitdepthFromColourCount(colours);
407
423
  } else {
408
424
  throw is.invalidParameterError('colours', 'integer between 2 and 256', colours);
409
425
  }
@@ -432,7 +448,7 @@ function png (options) {
432
448
  * @example
433
449
  * // Optimise the file size of an animated WebP
434
450
  * const outputWebp = await sharp(inputWebp, { animated: true })
435
- * .webp({ reductionEffort: 6 })
451
+ * .webp({ effort: 6 })
436
452
  * .toBuffer();
437
453
  *
438
454
  * @param {Object} [options] - output options
@@ -441,93 +457,205 @@ function png (options) {
441
457
  * @param {boolean} [options.lossless=false] - use lossless compression mode
442
458
  * @param {boolean} [options.nearLossless=false] - use near_lossless compression mode
443
459
  * @param {boolean} [options.smartSubsample=false] - use high quality chroma subsampling
444
- * @param {number} [options.reductionEffort=4] - level of CPU effort to reduce file size, integer 0-6
445
- * @param {number} [options.pageHeight] - page height for animated output
460
+ * @param {number} [options.effort=4] - CPU effort, between 0 (fastest) and 6 (slowest)
446
461
  * @param {number} [options.loop=0] - number of animation iterations, use 0 for infinite animation
447
- * @param {number[]} [options.delay] - list of delays between animation frames (in milliseconds)
462
+ * @param {number|number[]} [options.delay] - delay(s) between animation frames (in milliseconds)
448
463
  * @param {boolean} [options.force=true] - force WebP output, otherwise attempt to use input format
449
464
  * @returns {Sharp}
450
465
  * @throws {Error} Invalid options
451
466
  */
452
467
  function webp (options) {
453
- if (is.object(options) && is.defined(options.quality)) {
454
- if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
455
- this.options.webpQuality = options.quality;
456
- } else {
457
- throw is.invalidParameterError('quality', 'integer between 1 and 100', options.quality);
468
+ if (is.object(options)) {
469
+ if (is.defined(options.quality)) {
470
+ if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
471
+ this.options.webpQuality = options.quality;
472
+ } else {
473
+ throw is.invalidParameterError('quality', 'integer between 1 and 100', options.quality);
474
+ }
458
475
  }
459
- }
460
- if (is.object(options) && is.defined(options.alphaQuality)) {
461
- if (is.integer(options.alphaQuality) && is.inRange(options.alphaQuality, 0, 100)) {
462
- this.options.webpAlphaQuality = options.alphaQuality;
463
- } else {
464
- throw is.invalidParameterError('alphaQuality', 'integer between 0 and 100', options.alphaQuality);
476
+ if (is.defined(options.alphaQuality)) {
477
+ if (is.integer(options.alphaQuality) && is.inRange(options.alphaQuality, 0, 100)) {
478
+ this.options.webpAlphaQuality = options.alphaQuality;
479
+ } else {
480
+ throw is.invalidParameterError('alphaQuality', 'integer between 0 and 100', options.alphaQuality);
481
+ }
465
482
  }
466
- }
467
- if (is.object(options) && is.defined(options.lossless)) {
468
- this._setBooleanOption('webpLossless', options.lossless);
469
- }
470
- if (is.object(options) && is.defined(options.nearLossless)) {
471
- this._setBooleanOption('webpNearLossless', options.nearLossless);
472
- }
473
- if (is.object(options) && is.defined(options.smartSubsample)) {
474
- this._setBooleanOption('webpSmartSubsample', options.smartSubsample);
475
- }
476
- if (is.object(options) && is.defined(options.reductionEffort)) {
477
- if (is.integer(options.reductionEffort) && is.inRange(options.reductionEffort, 0, 6)) {
478
- this.options.webpReductionEffort = options.reductionEffort;
479
- } else {
480
- throw is.invalidParameterError('reductionEffort', 'integer between 0 and 6', options.reductionEffort);
483
+ if (is.defined(options.lossless)) {
484
+ this._setBooleanOption('webpLossless', options.lossless);
485
+ }
486
+ if (is.defined(options.nearLossless)) {
487
+ this._setBooleanOption('webpNearLossless', options.nearLossless);
488
+ }
489
+ if (is.defined(options.smartSubsample)) {
490
+ this._setBooleanOption('webpSmartSubsample', options.smartSubsample);
491
+ }
492
+ const effort = options.effort || options.reductionEffort;
493
+ if (is.defined(effort)) {
494
+ if (is.integer(effort) && is.inRange(effort, 0, 6)) {
495
+ this.options.webpEffort = effort;
496
+ } else {
497
+ throw is.invalidParameterError('effort', 'integer between 0 and 6', effort);
498
+ }
481
499
  }
482
500
  }
483
-
484
501
  trySetAnimationOptions(options, this.options);
485
502
  return this._updateFormatOut('webp', options);
486
503
  }
487
504
 
488
505
  /**
489
- * Use these GIF options for output image.
506
+ * Use these GIF options for the output image.
490
507
  *
491
- * Requires libvips compiled with support for ImageMagick or GraphicsMagick.
492
- * The prebuilt binaries do not include this - see
493
- * {@link https://sharp.pixelplumbing.com/install#custom-libvips installing a custom libvips}.
508
+ * The first entry in the palette is reserved for transparency.
509
+ *
510
+ * @since 0.30.0
511
+ *
512
+ * @example
513
+ * // Convert PNG to GIF
514
+ * await sharp(pngBuffer)
515
+ * .gif()
516
+ * .toBuffer());
517
+ *
518
+ * @example
519
+ * // Convert animated WebP to animated GIF
520
+ * await sharp('animated.webp', { animated: true })
521
+ * .toFile('animated.gif');
522
+ *
523
+ * @example
524
+ * // Create a 128x128, cropped, non-dithered, animated thumbnail of an animated GIF
525
+ * const out = await sharp('in.gif', { animated: true })
526
+ * .resize({ width: 128, height: 128 })
527
+ * .gif({ dither: 0 })
528
+ * .toBuffer();
494
529
  *
495
530
  * @param {Object} [options] - output options
496
- * @param {number} [options.pageHeight] - page height for animated output
531
+ * @param {number} [options.colours=256] - maximum number of palette entries, including transparency, between 2 and 256
532
+ * @param {number} [options.colors=256] - alternative spelling of `options.colours`
533
+ * @param {number} [options.effort=7] - CPU effort, between 1 (fastest) and 10 (slowest)
534
+ * @param {number} [options.dither=1.0] - level of Floyd-Steinberg error diffusion, between 0 (least) and 1 (most)
497
535
  * @param {number} [options.loop=0] - number of animation iterations, use 0 for infinite animation
498
- * @param {number[]} [options.delay] - list of delays between animation frames (in milliseconds)
536
+ * @param {number|number[]} [options.delay] - delay(s) between animation frames (in milliseconds)
499
537
  * @param {boolean} [options.force=true] - force GIF output, otherwise attempt to use input format
500
538
  * @returns {Sharp}
501
539
  * @throws {Error} Invalid options
502
540
  */
503
- /* istanbul ignore next */
504
541
  function gif (options) {
505
- if (!this.constructor.format.magick.output.buffer) {
506
- throw errMagickSave;
542
+ if (is.object(options)) {
543
+ const colours = options.colours || options.colors;
544
+ if (is.defined(colours)) {
545
+ if (is.integer(colours) && is.inRange(colours, 2, 256)) {
546
+ this.options.gifBitdepth = bitdepthFromColourCount(colours);
547
+ } else {
548
+ throw is.invalidParameterError('colours', 'integer between 2 and 256', colours);
549
+ }
550
+ }
551
+ if (is.defined(options.effort)) {
552
+ if (is.number(options.effort) && is.inRange(options.effort, 1, 10)) {
553
+ this.options.gifEffort = options.effort;
554
+ } else {
555
+ throw is.invalidParameterError('effort', 'integer between 1 and 10', options.effort);
556
+ }
557
+ }
558
+ if (is.defined(options.dither)) {
559
+ if (is.number(options.dither) && is.inRange(options.dither, 0, 1)) {
560
+ this.options.gifDither = options.dither;
561
+ } else {
562
+ throw is.invalidParameterError('dither', 'number between 0.0 and 1.0', options.dither);
563
+ }
564
+ }
507
565
  }
508
566
  trySetAnimationOptions(options, this.options);
509
567
  return this._updateFormatOut('gif', options);
510
568
  }
511
569
 
570
+ /**
571
+ * Use these JP2 options for output image.
572
+ *
573
+ * Requires libvips compiled with support for OpenJPEG.
574
+ * The prebuilt binaries do not include this - see
575
+ * {@link https://sharp.pixelplumbing.com/install#custom-libvips installing a custom libvips}.
576
+ *
577
+ * @example
578
+ * // Convert any input to lossless JP2 output
579
+ * const data = await sharp(input)
580
+ * .jp2({ lossless: true })
581
+ * .toBuffer();
582
+ *
583
+ * @example
584
+ * // Convert any input to very high quality JP2 output
585
+ * const data = await sharp(input)
586
+ * .jp2({
587
+ * quality: 100,
588
+ * chromaSubsampling: '4:4:4'
589
+ * })
590
+ * .toBuffer();
591
+ *
592
+ * @since 0.29.1
593
+ *
594
+ * @param {Object} [options] - output options
595
+ * @param {number} [options.quality=80] - quality, integer 1-100
596
+ * @param {boolean} [options.lossless=false] - use lossless compression mode
597
+ * @param {number} [options.tileWidth=512] - horizontal tile size
598
+ * @param {number} [options.tileHeight=512] - vertical tile size
599
+ * @param {string} [options.chromaSubsampling='4:4:4'] - set to '4:2:0' to use chroma subsampling
600
+ * @returns {Sharp}
601
+ * @throws {Error} Invalid options
602
+ */
603
+ /* istanbul ignore next */
604
+ function jp2 (options) {
605
+ if (!this.constructor.format.jp2k.output.buffer) {
606
+ throw errJp2Save;
607
+ }
608
+ if (is.object(options)) {
609
+ if (is.defined(options.quality)) {
610
+ if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
611
+ this.options.jp2Quality = options.quality;
612
+ } else {
613
+ throw is.invalidParameterError('quality', 'integer between 1 and 100', options.quality);
614
+ }
615
+ }
616
+ if (is.defined(options.lossless)) {
617
+ if (is.bool(options.lossless)) {
618
+ this.options.jp2Lossless = options.lossless;
619
+ } else {
620
+ throw is.invalidParameterError('lossless', 'boolean', options.lossless);
621
+ }
622
+ }
623
+ if (is.defined(options.tileWidth)) {
624
+ if (is.integer(options.tileWidth) && is.inRange(options.tileWidth, 1, 32768)) {
625
+ this.options.jp2TileWidth = options.tileWidth;
626
+ } else {
627
+ throw is.invalidParameterError('tileWidth', 'integer between 1 and 32768', options.tileWidth);
628
+ }
629
+ }
630
+ if (is.defined(options.tileHeight)) {
631
+ if (is.integer(options.tileHeight) && is.inRange(options.tileHeight, 1, 32768)) {
632
+ this.options.jp2TileHeight = options.tileHeight;
633
+ } else {
634
+ throw is.invalidParameterError('tileHeight', 'integer between 1 and 32768', options.tileHeight);
635
+ }
636
+ }
637
+ if (is.defined(options.chromaSubsampling)) {
638
+ if (is.string(options.chromaSubsampling) && is.inArray(options.chromaSubsampling, ['4:2:0', '4:4:4'])) {
639
+ this.options.heifChromaSubsampling = options.chromaSubsampling;
640
+ } else {
641
+ throw is.invalidParameterError('chromaSubsampling', 'one of: 4:2:0, 4:4:4', options.chromaSubsampling);
642
+ }
643
+ }
644
+ }
645
+ return this._updateFormatOut('jp2', options);
646
+ }
647
+
512
648
  /**
513
649
  * Set animation options if available.
514
650
  * @private
515
651
  *
516
652
  * @param {Object} [source] - output options
517
- * @param {number} [source.pageHeight] - page height for animated output
518
653
  * @param {number} [source.loop=0] - number of animation iterations, use 0 for infinite animation
519
654
  * @param {number[]} [source.delay] - list of delays between animation frames (in milliseconds)
520
655
  * @param {Object} [target] - target object for valid options
521
656
  * @throws {Error} Invalid options
522
657
  */
523
658
  function trySetAnimationOptions (source, target) {
524
- if (is.object(source) && is.defined(source.pageHeight)) {
525
- if (is.integer(source.pageHeight) && source.pageHeight > 0) {
526
- target.pageHeight = source.pageHeight;
527
- } else {
528
- throw is.invalidParameterError('pageHeight', 'integer larger than 0', source.pageHeight);
529
- }
530
- }
531
659
  if (is.object(source) && is.defined(source.loop)) {
532
660
  if (is.integer(source.loop) && is.inRange(source.loop, 0, 65535)) {
533
661
  target.loop = source.loop;
@@ -536,13 +664,16 @@ function trySetAnimationOptions (source, target) {
536
664
  }
537
665
  }
538
666
  if (is.object(source) && is.defined(source.delay)) {
539
- if (
667
+ // We allow singular values as well
668
+ if (is.integer(source.delay) && is.inRange(source.delay, 0, 65535)) {
669
+ target.delay = [source.delay];
670
+ } else if (
540
671
  Array.isArray(source.delay) &&
541
672
  source.delay.every(is.integer) &&
542
673
  source.delay.every(v => is.inRange(v, 0, 65535))) {
543
674
  target.delay = source.delay;
544
675
  } else {
545
- throw is.invalidParameterError('delay', 'array of integers between 0 and 65535', source.delay);
676
+ throw is.invalidParameterError('delay', 'integer or an array of integers between 0 and 65535', source.delay);
546
677
  }
547
678
  }
548
679
  }
@@ -550,6 +681,8 @@ function trySetAnimationOptions (source, target) {
550
681
  /**
551
682
  * Use these TIFF options for output image.
552
683
  *
684
+ * The `density` can be set in pixels/inch via {@link withMetadata} instead of providing `xres` and `yres` in pixels/mm.
685
+ *
553
686
  * @example
554
687
  * // Convert SVG input to LZW-compressed, 1 bit per pixel TIFF output
555
688
  * sharp('input.svg')
@@ -571,6 +704,7 @@ function trySetAnimationOptions (source, target) {
571
704
  * @param {number} [options.tileHeight=256] - vertical tile size
572
705
  * @param {number} [options.xres=1.0] - horizontal resolution in pixels/mm
573
706
  * @param {number} [options.yres=1.0] - vertical resolution in pixels/mm
707
+ * @param {string} [options.resolutionUnit='inch'] - resolution unit options: inch, cm
574
708
  * @param {number} [options.bitdepth=8] - reduce bitdepth to 1, 2 or 4 bit
575
709
  * @returns {Sharp}
576
710
  * @throws {Error} Invalid options
@@ -644,6 +778,14 @@ function tiff (options) {
644
778
  throw is.invalidParameterError('predictor', 'one of: none, horizontal, float', options.predictor);
645
779
  }
646
780
  }
781
+ // resolutionUnit
782
+ if (is.defined(options.resolutionUnit)) {
783
+ if (is.string(options.resolutionUnit) && is.inArray(options.resolutionUnit, ['inch', 'cm'])) {
784
+ this.options.tiffResolutionUnit = options.resolutionUnit;
785
+ } else {
786
+ throw is.invalidParameterError('resolutionUnit', 'one of: inch, cm', options.resolutionUnit);
787
+ }
788
+ }
647
789
  }
648
790
  return this._updateFormatOut('tiff', options);
649
791
  }
@@ -654,12 +796,14 @@ function tiff (options) {
654
796
  * Whilst it is possible to create AVIF images smaller than 16x16 pixels,
655
797
  * most web browsers do not display these properly.
656
798
  *
799
+ * AVIF image sequences are not supported.
800
+ *
657
801
  * @since 0.27.0
658
802
  *
659
803
  * @param {Object} [options] - output options
660
804
  * @param {number} [options.quality=50] - quality, integer 1-100
661
805
  * @param {boolean} [options.lossless=false] - use lossless compression
662
- * @param {number} [options.speed=5] - CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest)
806
+ * @param {number} [options.effort=4] - CPU effort, between 0 (fastest) and 9 (slowest)
663
807
  * @param {string} [options.chromaSubsampling='4:4:4'] - set to '4:2:0' to use chroma subsampling
664
808
  * @returns {Sharp}
665
809
  * @throws {Error} Invalid options
@@ -680,7 +824,7 @@ function avif (options) {
680
824
  * @param {number} [options.quality=50] - quality, integer 1-100
681
825
  * @param {string} [options.compression='av1'] - compression format: av1, hevc
682
826
  * @param {boolean} [options.lossless=false] - use lossless compression
683
- * @param {number} [options.speed=5] - CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest)
827
+ * @param {number} [options.effort=4] - CPU effort, between 0 (fastest) and 9 (slowest)
684
828
  * @param {string} [options.chromaSubsampling='4:4:4'] - set to '4:2:0' to use chroma subsampling
685
829
  * @returns {Sharp}
686
830
  * @throws {Error} Invalid options
@@ -708,11 +852,17 @@ function heif (options) {
708
852
  throw is.invalidParameterError('compression', 'one of: av1, hevc', options.compression);
709
853
  }
710
854
  }
711
- if (is.defined(options.speed)) {
712
- if (is.integer(options.speed) && is.inRange(options.speed, 0, 8)) {
713
- this.options.heifSpeed = options.speed;
855
+ if (is.defined(options.effort)) {
856
+ if (is.integer(options.effort) && is.inRange(options.effort, 0, 9)) {
857
+ this.options.heifEffort = options.effort;
858
+ } else {
859
+ throw is.invalidParameterError('effort', 'integer between 0 and 9', options.effort);
860
+ }
861
+ } else if (is.defined(options.speed)) {
862
+ if (is.integer(options.speed) && is.inRange(options.speed, 0, 9)) {
863
+ this.options.heifEffort = 9 - options.speed;
714
864
  } else {
715
- throw is.invalidParameterError('speed', 'integer between 0 and 8', options.speed);
865
+ throw is.invalidParameterError('speed', 'integer between 0 and 9', options.speed);
716
866
  }
717
867
  }
718
868
  if (is.defined(options.chromaSubsampling)) {
@@ -770,8 +920,6 @@ function raw (options) {
770
920
  * Set the format and options for tile images via the `toFormat`, `jpeg`, `png` or `webp` functions.
771
921
  * Use a `.zip` or `.szi` file extension with `toFile` to write to a compressed archive file format.
772
922
  *
773
- * Warning: multiple sharp instances concurrently producing tile output can expose a possible race condition in some versions of libgsf.
774
- *
775
923
  * @example
776
924
  * sharp('input.tiff')
777
925
  * .png()
@@ -791,10 +939,10 @@ function raw (options) {
791
939
  * @param {string} [options.depth] how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout.
792
940
  * @param {number} [options.skipBlanks=-1] threshold to skip tile generation, a value 0 - 255 for 8-bit images or 0 - 65535 for 16-bit images
793
941
  * @param {string} [options.container='fs'] tile container, with value `fs` (filesystem) or `zip` (compressed file).
794
- * @param {string} [options.layout='dz'] filesystem layout, possible values are `dz`, `iiif`, `zoomify` or `google`.
942
+ * @param {string} [options.layout='dz'] filesystem layout, possible values are `dz`, `iiif`, `iiif3`, `zoomify` or `google`.
795
943
  * @param {boolean} [options.centre=false] centre image in tile.
796
944
  * @param {boolean} [options.center=false] alternative spelling of centre.
797
- * @param {string} [options.id='https://example.com/iiif'] when `layout` is `iiif`, sets the `@id` attribute of `info.json`
945
+ * @param {string} [options.id='https://example.com/iiif'] when `layout` is `iiif`/`iiif3`, sets the `@id`/`id` attribute of `info.json`
798
946
  * @returns {Sharp}
799
947
  * @throws {Error} Invalid parameters
800
948
  */
@@ -829,10 +977,10 @@ function tile (options) {
829
977
  }
830
978
  // Layout
831
979
  if (is.defined(options.layout)) {
832
- if (is.string(options.layout) && is.inArray(options.layout, ['dz', 'google', 'iiif', 'zoomify'])) {
980
+ if (is.string(options.layout) && is.inArray(options.layout, ['dz', 'google', 'iiif', 'iiif3', 'zoomify'])) {
833
981
  this.options.tileLayout = options.layout;
834
982
  } else {
835
- throw is.invalidParameterError('layout', 'one of: dz, google, iiif, zoomify', options.layout);
983
+ throw is.invalidParameterError('layout', 'one of: dz, google, iiif, iiif3, zoomify', options.layout);
836
984
  }
837
985
  }
838
986
  // Angle of rotation,
@@ -886,6 +1034,31 @@ function tile (options) {
886
1034
  return this._updateFormatOut('dz');
887
1035
  }
888
1036
 
1037
+ /**
1038
+ * Set a timeout for processing, in seconds.
1039
+ * Use a value of zero to continue processing indefinitely, the default behaviour.
1040
+ *
1041
+ * The clock starts when libvips opens an input image for processing.
1042
+ * Time spent waiting for a libuv thread to become available is not included.
1043
+ *
1044
+ * @since 0.29.2
1045
+ *
1046
+ * @param {Object} options
1047
+ * @param {number} options.seconds - Number of seconds after which processing will be stopped
1048
+ * @returns {Sharp}
1049
+ */
1050
+ function timeout (options) {
1051
+ if (!is.plainObject(options)) {
1052
+ throw is.invalidParameterError('options', 'object', options);
1053
+ }
1054
+ if (is.integer(options.seconds) && is.inRange(options.seconds, 0, 3600)) {
1055
+ this.options.timeoutSeconds = options.seconds;
1056
+ } else {
1057
+ throw is.invalidParameterError('seconds', 'integer between 0 and 3600', options.seconds);
1058
+ }
1059
+ return this;
1060
+ }
1061
+
889
1062
  /**
890
1063
  * Update the output format unless options.force is false,
891
1064
  * in which case revert to input format.
@@ -962,6 +1135,7 @@ function _pipeline (callback) {
962
1135
  this.push(data);
963
1136
  }
964
1137
  this.push(null);
1138
+ this.emit('close');
965
1139
  });
966
1140
  });
967
1141
  if (this.streamInFinished) {
@@ -977,6 +1151,7 @@ function _pipeline (callback) {
977
1151
  this.push(data);
978
1152
  }
979
1153
  this.push(null);
1154
+ this.emit('close');
980
1155
  });
981
1156
  }
982
1157
  return this;
@@ -1031,6 +1206,7 @@ module.exports = function (Sharp) {
1031
1206
  withMetadata,
1032
1207
  toFormat,
1033
1208
  jpeg,
1209
+ jp2,
1034
1210
  png,
1035
1211
  webp,
1036
1212
  tiff,
@@ -1039,6 +1215,7 @@ module.exports = function (Sharp) {
1039
1215
  gif,
1040
1216
  raw,
1041
1217
  tile,
1218
+ timeout,
1042
1219
  // Private
1043
1220
  _updateFormatOut,
1044
1221
  _setBooleanOption,
package/lib/platform.js CHANGED
@@ -8,7 +8,7 @@ module.exports = function () {
8
8
  const arch = env.npm_config_arch || process.arch;
9
9
  const platform = env.npm_config_platform || process.platform;
10
10
  /* istanbul ignore next */
11
- const libc = (platform === 'linux' && detectLibc.isNonGlibcLinux) ? detectLibc.family : '';
11
+ const libc = (platform === 'linux' && detectLibc.isNonGlibcLinuxSync()) ? detectLibc.familySync() : '';
12
12
 
13
13
  const platformId = [`${platform}${libc}`];
14
14
 
package/lib/resize.js CHANGED
@@ -183,6 +183,20 @@ function isRotationExpected (options) {
183
183
  * });
184
184
  *
185
185
  * @example
186
+ * sharp(input)
187
+ * .resize(200, 200, {
188
+ * fit: sharp.fit.outside,
189
+ * withoutReduction: true
190
+ * })
191
+ * .toFormat('jpeg')
192
+ * .toBuffer()
193
+ * .then(function(outputBuffer) {
194
+ * // outputBuffer contains JPEG image data
195
+ * // of at least 200 pixels wide and 200 pixels high while maintaining aspect ratio
196
+ * // and no smaller than the input image
197
+ * });
198
+ *
199
+ * @example
186
200
  * const scaleByHalf = await sharp(input)
187
201
  * .metadata()
188
202
  * .then(({ width }) => sharp(input)
@@ -200,6 +214,7 @@ function isRotationExpected (options) {
200
214
  * @param {String|Object} [options.background={r: 0, g: 0, b: 0, alpha: 1}] - background colour when using a `fit` of `contain`, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black without transparency.
201
215
  * @param {String} [options.kernel='lanczos3'] - the kernel to use for image reduction.
202
216
  * @param {Boolean} [options.withoutEnlargement=false] - do not enlarge if the width *or* height are already less than the specified dimensions, equivalent to GraphicsMagick's `>` geometry option.
217
+ * @param {Boolean} [options.withoutReduction=false] - do not reduce if the width *or* height are already greater than the specified dimensions, equivalent to GraphicsMagick's `<` geometry option.
203
218
  * @param {Boolean} [options.fastShrinkOnLoad=true] - take greater advantage of the JPEG and WebP shrink-on-load feature, which can lead to a slight moiré pattern on some images.
204
219
  * @returns {Sharp}
205
220
  * @throws {Error} Invalid parameters
@@ -276,6 +291,10 @@ function resize (width, height, options) {
276
291
  if (is.defined(options.withoutEnlargement)) {
277
292
  this._setBooleanOption('withoutEnlargement', options.withoutEnlargement);
278
293
  }
294
+ // Without reduction
295
+ if (is.defined(options.withoutReduction)) {
296
+ this._setBooleanOption('withoutReduction', options.withoutReduction);
297
+ }
279
298
  // Shrink on load
280
299
  if (is.defined(options.fastShrinkOnLoad)) {
281
300
  this._setBooleanOption('fastShrinkOnLoad', options.fastShrinkOnLoad);
package/lib/sharp.js CHANGED
@@ -11,14 +11,22 @@ try {
11
11
  if (/dylib/.test(err.message) && /Incompatible library version/.test(err.message)) {
12
12
  help.push('- Update Homebrew: "brew update && brew upgrade vips"');
13
13
  } else {
14
+ const [platform, arch] = platformAndArch.split('-');
14
15
  help.push(
15
16
  '- Install with the --verbose flag and look for errors: "npm install --ignore-scripts=false --verbose sharp"',
16
- `- Install for the current runtime: "npm install --platform=${process.platform} --arch=${process.arch} sharp"`
17
+ `- Install for the current ${platformAndArch} runtime: "npm install --platform=${platform} --arch=${arch} sharp"`
17
18
  );
18
19
  }
19
20
  help.push(
20
21
  '- Consult the installation documentation: https://sharp.pixelplumbing.com/install'
21
22
  );
22
- console.error(help.join('\n'));
23
- process.exit(1);
23
+ // Check loaded
24
+ if (process.platform === 'win32') {
25
+ const loadedModule = Object.keys(require.cache).find((i) => /[\\/]build[\\/]Release[\\/]sharp(.*)\.node$/.test(i));
26
+ if (loadedModule) {
27
+ const [, loadedPackage] = loadedModule.match(/node_modules[\\/]([^\\/]+)[\\/]/);
28
+ help.push(`- Ensure the version of sharp aligns with the ${loadedPackage} package: "npm ls sharp"`);
29
+ }
30
+ }
31
+ throw new Error(help.join('\n'));
24
32
  }