sharp 0.28.3 → 0.29.3

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,7 +1,8 @@
1
1
  'use strict';
2
2
 
3
+ const path = require('path');
3
4
  const is = require('./is');
4
- const sharp = require('../build/Release/sharp.node');
5
+ const sharp = require('./sharp');
5
6
 
6
7
  const formats = new Map([
7
8
  ['heic', 'heif'],
@@ -12,11 +13,17 @@ 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
25
  const errMagickSave = new Error('GIF output requires libvips with support for ImageMagick');
26
+ const errJp2Save = new Error('JP2 output requires libvips with support for OpenJPEG');
20
27
 
21
28
  /**
22
29
  * Write output image data to a file.
@@ -28,6 +35,8 @@ const errMagickSave = new Error('GIF output requires libvips with support for Im
28
35
  * By default all metadata will be removed, which includes EXIF-based orientation.
29
36
  * See {@link withMetadata} for control over this.
30
37
  *
38
+ * The caller is responsible for ensuring directory structures and permissions exist.
39
+ *
31
40
  * A `Promise` is returned when `callback` is not provided.
32
41
  *
33
42
  * @example
@@ -52,7 +61,7 @@ function toFile (fileOut, callback) {
52
61
  let err;
53
62
  if (!is.string(fileOut)) {
54
63
  err = new Error('Missing output file path');
55
- } else if (this.options.input.file === fileOut) {
64
+ } else if (is.string(this.options.input.file) && path.resolve(this.options.input.file) === path.resolve(fileOut)) {
56
65
  err = new Error('Cannot use same file for input and output');
57
66
  } else if (this.options.formatOut === 'input' && fileOut.toLowerCase().endsWith('.gif') && !this.constructor.format.magick.output.file) {
58
67
  err = errMagickSave;
@@ -403,7 +412,7 @@ function png (options) {
403
412
  const colours = options.colours || options.colors;
404
413
  if (is.defined(colours)) {
405
414
  if (is.integer(colours) && is.inRange(colours, 2, 256)) {
406
- this.options.pngColours = colours;
415
+ this.options.pngBitdepth = 1 << 31 - Math.clz32(Math.ceil(Math.log2(colours)));
407
416
  } else {
408
417
  throw is.invalidParameterError('colours', 'integer between 2 and 256', colours);
409
418
  }
@@ -509,6 +518,84 @@ function gif (options) {
509
518
  return this._updateFormatOut('gif', options);
510
519
  }
511
520
 
521
+ /**
522
+ * Use these JP2 options for output image.
523
+ *
524
+ * Requires libvips compiled with support for OpenJPEG.
525
+ * The prebuilt binaries do not include this - see
526
+ * {@link https://sharp.pixelplumbing.com/install#custom-libvips installing a custom libvips}.
527
+ *
528
+ * @example
529
+ * // Convert any input to lossless JP2 output
530
+ * const data = await sharp(input)
531
+ * .jp2({ lossless: true })
532
+ * .toBuffer();
533
+ *
534
+ * @example
535
+ * // Convert any input to very high quality JP2 output
536
+ * const data = await sharp(input)
537
+ * .jp2({
538
+ * quality: 100,
539
+ * chromaSubsampling: '4:4:4'
540
+ * })
541
+ * .toBuffer();
542
+ *
543
+ * @since 0.29.1
544
+ *
545
+ * @param {Object} [options] - output options
546
+ * @param {number} [options.quality=80] - quality, integer 1-100
547
+ * @param {boolean} [options.lossless=false] - use lossless compression mode
548
+ * @param {number} [options.tileWidth=512] - horizontal tile size
549
+ * @param {number} [options.tileHeight=512] - vertical tile size
550
+ * @param {string} [options.chromaSubsampling='4:4:4'] - set to '4:2:0' to use chroma subsampling
551
+ * @returns {Sharp}
552
+ * @throws {Error} Invalid options
553
+ */
554
+ /* istanbul ignore next */
555
+ function jp2 (options) {
556
+ if (!this.constructor.format.jp2k.output.buffer) {
557
+ throw errJp2Save;
558
+ }
559
+ if (is.object(options)) {
560
+ if (is.defined(options.quality)) {
561
+ if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
562
+ this.options.jp2Quality = options.quality;
563
+ } else {
564
+ throw is.invalidParameterError('quality', 'integer between 1 and 100', options.quality);
565
+ }
566
+ }
567
+ if (is.defined(options.lossless)) {
568
+ if (is.bool(options.lossless)) {
569
+ this.options.jp2Lossless = options.lossless;
570
+ } else {
571
+ throw is.invalidParameterError('lossless', 'boolean', options.lossless);
572
+ }
573
+ }
574
+ if (is.defined(options.tileWidth)) {
575
+ if (is.integer(options.tileWidth) && is.inRange(options.tileWidth, 1, 32768)) {
576
+ this.options.jp2TileWidth = options.tileWidth;
577
+ } else {
578
+ throw is.invalidParameterError('tileWidth', 'integer between 1 and 32768', options.tileWidth);
579
+ }
580
+ }
581
+ if (is.defined(options.tileHeight)) {
582
+ if (is.integer(options.tileHeight) && is.inRange(options.tileHeight, 1, 32768)) {
583
+ this.options.jp2TileHeight = options.tileHeight;
584
+ } else {
585
+ throw is.invalidParameterError('tileHeight', 'integer between 1 and 32768', options.tileHeight);
586
+ }
587
+ }
588
+ if (is.defined(options.chromaSubsampling)) {
589
+ if (is.string(options.chromaSubsampling) && is.inArray(options.chromaSubsampling, ['4:2:0', '4:4:4'])) {
590
+ this.options.heifChromaSubsampling = options.chromaSubsampling;
591
+ } else {
592
+ throw is.invalidParameterError('chromaSubsampling', 'one of: 4:2:0, 4:4:4', options.chromaSubsampling);
593
+ }
594
+ }
595
+ }
596
+ return this._updateFormatOut('jp2', options);
597
+ }
598
+
512
599
  /**
513
600
  * Set animation options if available.
514
601
  * @private
@@ -550,6 +637,8 @@ function trySetAnimationOptions (source, target) {
550
637
  /**
551
638
  * Use these TIFF options for output image.
552
639
  *
640
+ * The `density` can be set in pixels/inch via {@link withMetadata} instead of providing `xres` and `yres` in pixels/mm.
641
+ *
553
642
  * @example
554
643
  * // Convert SVG input to LZW-compressed, 1 bit per pixel TIFF output
555
644
  * sharp('input.svg')
@@ -654,13 +743,15 @@ function tiff (options) {
654
743
  * Whilst it is possible to create AVIF images smaller than 16x16 pixels,
655
744
  * most web browsers do not display these properly.
656
745
  *
746
+ * AVIF image sequences are not supported.
747
+ *
657
748
  * @since 0.27.0
658
749
  *
659
750
  * @param {Object} [options] - output options
660
751
  * @param {number} [options.quality=50] - quality, integer 1-100
661
752
  * @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)
663
- * @param {string} [options.chromaSubsampling='4:2:0'] - set to '4:4:4' to prevent chroma subsampling otherwise defaults to '4:2:0' chroma subsampling, requires libvips v8.11.0
753
+ * @param {number} [options.speed=5] - CPU effort vs file size, 0 (slowest/smallest) to 9 (fastest/largest)
754
+ * @param {string} [options.chromaSubsampling='4:4:4'] - set to '4:2:0' to use chroma subsampling
664
755
  * @returns {Sharp}
665
756
  * @throws {Error} Invalid options
666
757
  */
@@ -680,8 +771,8 @@ function avif (options) {
680
771
  * @param {number} [options.quality=50] - quality, integer 1-100
681
772
  * @param {string} [options.compression='av1'] - compression format: av1, hevc
682
773
  * @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)
684
- * @param {string} [options.chromaSubsampling='4:2:0'] - set to '4:4:4' to prevent chroma subsampling otherwise defaults to '4:2:0' chroma subsampling, requires libvips v8.11.0
774
+ * @param {number} [options.speed=5] - CPU effort vs file size, 0 (slowest/smallest) to 9 (fastest/largest)
775
+ * @param {string} [options.chromaSubsampling='4:4:4'] - set to '4:2:0' to use chroma subsampling
685
776
  * @returns {Sharp}
686
777
  * @throws {Error} Invalid options
687
778
  */
@@ -709,10 +800,10 @@ function heif (options) {
709
800
  }
710
801
  }
711
802
  if (is.defined(options.speed)) {
712
- if (is.integer(options.speed) && is.inRange(options.speed, 0, 8)) {
803
+ if (is.integer(options.speed) && is.inRange(options.speed, 0, 9)) {
713
804
  this.options.heifSpeed = options.speed;
714
805
  } else {
715
- throw is.invalidParameterError('speed', 'integer between 0 and 8', options.speed);
806
+ throw is.invalidParameterError('speed', 'integer between 0 and 9', options.speed);
716
807
  }
717
808
  }
718
809
  if (is.defined(options.chromaSubsampling)) {
@@ -727,28 +818,41 @@ function heif (options) {
727
818
  }
728
819
 
729
820
  /**
730
- * Force output to be raw, uncompressed, 8-bit unsigned integer (unit8) pixel data.
821
+ * Force output to be raw, uncompressed pixel data.
731
822
  * Pixel ordering is left-to-right, top-to-bottom, without padding.
732
823
  * Channel ordering will be RGB or RGBA for non-greyscale colourspaces.
733
824
  *
734
825
  * @example
735
- * // Extract raw RGB pixel data from JPEG input
826
+ * // Extract raw, unsigned 8-bit RGB pixel data from JPEG input
736
827
  * const { data, info } = await sharp('input.jpg')
737
828
  * .raw()
738
829
  * .toBuffer({ resolveWithObject: true });
739
830
  *
740
831
  * @example
741
- * // Extract alpha channel as raw pixel data from PNG input
832
+ * // Extract alpha channel as raw, unsigned 16-bit pixel data from PNG input
742
833
  * const data = await sharp('input.png')
743
834
  * .ensureAlpha()
744
835
  * .extractChannel(3)
745
836
  * .toColourspace('b-w')
746
- * .raw()
837
+ * .raw({ depth: 'ushort' })
747
838
  * .toBuffer();
748
839
  *
749
- * @returns {Sharp}
840
+ * @param {Object} [options] - output options
841
+ * @param {string} [options.depth='uchar'] - bit depth, one of: char, uchar (default), short, ushort, int, uint, float, complex, double, dpcomplex
842
+ * @throws {Error} Invalid options
750
843
  */
751
- function raw () {
844
+ function raw (options) {
845
+ if (is.object(options)) {
846
+ if (is.defined(options.depth)) {
847
+ if (is.string(options.depth) && is.inArray(options.depth,
848
+ ['char', 'uchar', 'short', 'ushort', 'int', 'uint', 'float', 'complex', 'double', 'dpcomplex']
849
+ )) {
850
+ this.options.rawDepth = options.depth;
851
+ } else {
852
+ throw is.invalidParameterError('depth', 'one of: char, uchar, short, ushort, int, uint, float, complex, double, dpcomplex', options.depth);
853
+ }
854
+ }
855
+ }
752
856
  return this._updateFormatOut('raw');
753
857
  }
754
858
 
@@ -873,6 +977,31 @@ function tile (options) {
873
977
  return this._updateFormatOut('dz');
874
978
  }
875
979
 
980
+ /**
981
+ * Set a timeout for processing, in seconds.
982
+ * Use a value of zero to continue processing indefinitely, the default behaviour.
983
+ *
984
+ * The clock starts when libvips opens an input image for processing.
985
+ * Time spent waiting for a libuv thread to become available is not included.
986
+ *
987
+ * @since 0.29.2
988
+ *
989
+ * @param {Object} options
990
+ * @param {number} options.seconds - Number of seconds after which processing will be stopped
991
+ * @returns {Sharp}
992
+ */
993
+ function timeout (options) {
994
+ if (!is.plainObject(options)) {
995
+ throw is.invalidParameterError('options', 'object', options);
996
+ }
997
+ if (is.integer(options.seconds) && is.inRange(options.seconds, 0, 3600)) {
998
+ this.options.timeoutSeconds = options.seconds;
999
+ } else {
1000
+ throw is.invalidParameterError('seconds', 'integer between 0 and 3600', options.seconds);
1001
+ }
1002
+ return this;
1003
+ }
1004
+
876
1005
  /**
877
1006
  * Update the output format unless options.force is false,
878
1007
  * in which case revert to input format.
@@ -1018,6 +1147,7 @@ module.exports = function (Sharp) {
1018
1147
  withMetadata,
1019
1148
  toFormat,
1020
1149
  jpeg,
1150
+ jp2,
1021
1151
  png,
1022
1152
  webp,
1023
1153
  tiff,
@@ -1026,6 +1156,7 @@ module.exports = function (Sharp) {
1026
1156
  gif,
1027
1157
  raw,
1028
1158
  tile,
1159
+ timeout,
1029
1160
  // Private
1030
1161
  _updateFormatOut,
1031
1162
  _setBooleanOption,
package/lib/sharp.js ADDED
@@ -0,0 +1,31 @@
1
+ 'use strict';
2
+
3
+ const platformAndArch = require('./platform')();
4
+
5
+ /* istanbul ignore next */
6
+ try {
7
+ module.exports = require(`../build/Release/sharp-${platformAndArch}.node`);
8
+ } catch (err) {
9
+ // Bail early if bindings aren't available
10
+ const help = ['', 'Something went wrong installing the "sharp" module', '', err.message, '', 'Possible solutions:'];
11
+ if (/dylib/.test(err.message) && /Incompatible library version/.test(err.message)) {
12
+ help.push('- Update Homebrew: "brew update && brew upgrade vips"');
13
+ } else {
14
+ help.push(
15
+ '- 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
+ );
18
+ }
19
+ help.push(
20
+ '- Consult the installation documentation: https://sharp.pixelplumbing.com/install'
21
+ );
22
+ // Check loaded
23
+ if (process.platform === 'win32') {
24
+ const loadedModule = Object.keys(require.cache).find((i) => /[\\/]build[\\/]Release[\\/]sharp(.*)\.node$/.test(i));
25
+ if (loadedModule) {
26
+ const [, loadedPackage] = loadedModule.match(/node_modules[\\/]([^\\/]+)[\\/]/);
27
+ help.push(`- Ensure the version of sharp aligns with the ${loadedPackage} package: "npm ls sharp"`);
28
+ }
29
+ }
30
+ throw new Error(help.join('\n'));
31
+ }
package/lib/utility.js CHANGED
@@ -4,7 +4,8 @@ const events = require('events');
4
4
  const detectLibc = require('detect-libc');
5
5
 
6
6
  const is = require('./is');
7
- const sharp = require('../build/Release/sharp.node');
7
+ const platformAndArch = require('./platform')();
8
+ const sharp = require('./sharp');
8
9
 
9
10
  /**
10
11
  * An Object containing nested boolean values representing the available input and output formats/methods.
@@ -45,7 +46,7 @@ let versions = {
45
46
  vips: sharp.libvipsVersion()
46
47
  };
47
48
  try {
48
- versions = require(`../vendor/${versions.vips}/versions.json`);
49
+ versions = require(`../vendor/${versions.vips}/${platformAndArch}/versions.json`);
49
50
  } catch (err) {}
50
51
 
51
52
  /**
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sharp",
3
3
  "description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, AVIF and TIFF images",
4
- "version": "0.28.3",
4
+ "version": "0.29.3",
5
5
  "author": "Lovell Fuller <npm@lovell.info>",
6
6
  "homepage": "https://github.com/lovell/sharp",
7
7
  "contributors": [
@@ -77,7 +77,9 @@
77
77
  "alza54 <alza54@thiocod.in>",
78
78
  "Jacob Smith <jacob@frende.me>",
79
79
  "Michael Nutt <michael@nutt.im>",
80
- "Brad Parham <baparham@gmail.com>"
80
+ "Brad Parham <baparham@gmail.com>",
81
+ "Taneli Vatanen <taneli.vatanen@gmail.com>",
82
+ "Joris Dugué <zaruike10@gmail.com>"
81
83
  ],
82
84
  "scripts": {
83
85
  "install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node install/can-compile && node-gyp rebuild && node install/dll-copy)",
@@ -111,6 +113,7 @@
111
113
  "tiff",
112
114
  "gif",
113
115
  "svg",
116
+ "jp2",
114
117
  "dzi",
115
118
  "image",
116
119
  "resize",
@@ -121,45 +124,45 @@
121
124
  "vips"
122
125
  ],
123
126
  "dependencies": {
124
- "color": "^3.1.3",
127
+ "color": "^4.0.1",
125
128
  "detect-libc": "^1.0.3",
126
- "node-addon-api": "^3.2.0",
127
- "prebuild-install": "^6.1.2",
129
+ "node-addon-api": "^4.2.0",
130
+ "prebuild-install": "^7.0.0",
128
131
  "semver": "^7.3.5",
129
- "simple-get": "^3.1.0",
132
+ "simple-get": "^4.0.0",
130
133
  "tar-fs": "^2.1.1",
131
134
  "tunnel-agent": "^0.6.0"
132
135
  },
133
136
  "devDependencies": {
134
- "async": "^3.2.0",
137
+ "async": "^3.2.2",
135
138
  "cc": "^3.0.1",
136
139
  "decompress-zip": "^0.3.3",
137
140
  "documentation": "^13.2.5",
138
141
  "exif-reader": "^1.0.3",
139
142
  "icc": "^2.0.0",
140
143
  "license-checker": "^25.0.1",
141
- "mocha": "^8.4.0",
142
- "mock-fs": "^4.14.0",
144
+ "mocha": "^9.1.3",
145
+ "mock-fs": "^5.1.2",
143
146
  "nyc": "^15.1.0",
144
- "prebuild": "^10.0.1",
147
+ "prebuild": "^11.0.0",
145
148
  "rimraf": "^3.0.2",
146
- "semistandard": "^16.0.0"
149
+ "semistandard": "^16.0.1"
147
150
  },
148
151
  "license": "Apache-2.0",
149
152
  "config": {
150
- "libvips": "8.10.6",
153
+ "libvips": "8.11.3",
151
154
  "runtime": "napi",
152
- "target": 3
155
+ "target": 5
153
156
  },
154
157
  "engines": {
155
- "node": ">=10"
158
+ "node": ">=12.13.0"
156
159
  },
157
160
  "funding": {
158
161
  "url": "https://opencollective.com/libvips"
159
162
  },
160
163
  "binary": {
161
164
  "napi_versions": [
162
- 3
165
+ 5
163
166
  ]
164
167
  },
165
168
  "semistandard": {
package/src/common.cc CHANGED
@@ -92,6 +92,9 @@ namespace sharp {
92
92
  }
93
93
  // Raw pixel input
94
94
  if (HasAttr(input, "rawChannels")) {
95
+ descriptor->rawDepth = static_cast<VipsBandFormat>(
96
+ vips_enum_from_nick(nullptr, VIPS_TYPE_BAND_FORMAT,
97
+ AttrAsStr(input, "rawDepth").data()));
95
98
  descriptor->rawChannels = AttrAsUint32(input, "rawChannels");
96
99
  descriptor->rawWidth = AttrAsUint32(input, "rawWidth");
97
100
  descriptor->rawHeight = AttrAsUint32(input, "rawHeight");
@@ -154,6 +157,10 @@ namespace sharp {
154
157
  bool IsGif(std::string const &str) {
155
158
  return EndsWith(str, ".gif") || EndsWith(str, ".GIF");
156
159
  }
160
+ bool IsJp2(std::string const &str) {
161
+ return EndsWith(str, ".jp2") || EndsWith(str, ".jpx") || EndsWith(str, ".j2k") || EndsWith(str, ".j2c")
162
+ || EndsWith(str, ".JP2") || EndsWith(str, ".JPX") || EndsWith(str, ".J2K") || EndsWith(str, ".J2C");
163
+ }
157
164
  bool IsTiff(std::string const &str) {
158
165
  return EndsWith(str, ".tif") || EndsWith(str, ".tiff") || EndsWith(str, ".TIF") || EndsWith(str, ".TIFF");
159
166
  }
@@ -187,6 +194,7 @@ namespace sharp {
187
194
  case ImageType::WEBP: id = "webp"; break;
188
195
  case ImageType::TIFF: id = "tiff"; break;
189
196
  case ImageType::GIF: id = "gif"; break;
197
+ case ImageType::JP2: id = "jp2"; break;
190
198
  case ImageType::SVG: id = "svg"; break;
191
199
  case ImageType::HEIF: id = "heif"; break;
192
200
  case ImageType::PDF: id = "pdf"; break;
@@ -223,6 +231,8 @@ namespace sharp {
223
231
  { "VipsForeignLoadGifBuffer", ImageType::GIF },
224
232
  { "VipsForeignLoadNsgifFile", ImageType::GIF },
225
233
  { "VipsForeignLoadNsgifBuffer", ImageType::GIF },
234
+ { "VipsForeignLoadJp2kBuffer", ImageType::JP2 },
235
+ { "VipsForeignLoadJp2kFile", ImageType::JP2 },
226
236
  { "VipsForeignLoadSvgFile", ImageType::SVG },
227
237
  { "VipsForeignLoadSvgBuffer", ImageType::SVG },
228
238
  { "VipsForeignLoadHeifFile", ImageType::HEIF },
@@ -231,6 +241,8 @@ namespace sharp {
231
241
  { "VipsForeignLoadPdfBuffer", ImageType::PDF },
232
242
  { "VipsForeignLoadMagickFile", ImageType::MAGICK },
233
243
  { "VipsForeignLoadMagickBuffer", ImageType::MAGICK },
244
+ { "VipsForeignLoadMagick7File", ImageType::MAGICK },
245
+ { "VipsForeignLoadMagick7Buffer", ImageType::MAGICK },
234
246
  { "VipsForeignLoadOpenslide", ImageType::OPENSLIDE },
235
247
  { "VipsForeignLoadPpmFile", ImageType::PPM },
236
248
  { "VipsForeignLoadFits", ImageType::FITS },
@@ -282,6 +294,7 @@ namespace sharp {
282
294
  imageType == ImageType::WEBP ||
283
295
  imageType == ImageType::MAGICK ||
284
296
  imageType == ImageType::GIF ||
297
+ imageType == ImageType::JP2 ||
285
298
  imageType == ImageType::TIFF ||
286
299
  imageType == ImageType::HEIF ||
287
300
  imageType == ImageType::PDF;
@@ -297,7 +310,7 @@ namespace sharp {
297
310
  if (descriptor->rawChannels > 0) {
298
311
  // Raw, uncompressed pixel data
299
312
  image = VImage::new_from_memory(descriptor->buffer, descriptor->bufferLength,
300
- descriptor->rawWidth, descriptor->rawHeight, descriptor->rawChannels, VIPS_FORMAT_UCHAR);
313
+ descriptor->rawWidth, descriptor->rawHeight, descriptor->rawChannels, descriptor->rawDepth);
301
314
  if (descriptor->rawChannels < 3) {
302
315
  image.get_image()->Type = VIPS_INTERPRETATION_B_W;
303
316
  } else {
@@ -505,6 +518,17 @@ namespace sharp {
505
518
  return copy;
506
519
  }
507
520
 
521
+ /*
522
+ Remove animation properties from image.
523
+ */
524
+ VImage RemoveAnimationProperties(VImage image) {
525
+ VImage copy = image.copy();
526
+ copy.remove(VIPS_META_PAGE_HEIGHT);
527
+ copy.remove("delay");
528
+ copy.remove("loop");
529
+ return copy;
530
+ }
531
+
508
532
  /*
509
533
  Does this image have a non-default density?
510
534
  */
@@ -586,6 +610,33 @@ namespace sharp {
586
610
  return warning;
587
611
  }
588
612
 
613
+ /*
614
+ Attach an event listener for progress updates, used to detect timeout
615
+ */
616
+ void SetTimeout(VImage image, int const seconds) {
617
+ if (seconds > 0) {
618
+ VipsImage *im = image.get_image();
619
+ if (im->progress_signal == NULL) {
620
+ int *timeout = VIPS_NEW(im, int);
621
+ *timeout = seconds;
622
+ g_signal_connect(im, "eval", G_CALLBACK(VipsProgressCallBack), timeout);
623
+ vips_image_set_progress(im, TRUE);
624
+ }
625
+ }
626
+ }
627
+
628
+ /*
629
+ Event listener for progress updates, used to detect timeout
630
+ */
631
+ void VipsProgressCallBack(VipsImage *im, VipsProgress *progress, int *timeout) {
632
+ // printf("VipsProgressCallBack progress=%d run=%d timeout=%d\n", progress->percent, progress->run, *timeout);
633
+ if (*timeout > 0 && progress->run >= *timeout) {
634
+ vips_image_set_kill(im, TRUE);
635
+ vips_error("timeout", "%d%% complete", progress->percent);
636
+ *timeout = 0;
637
+ }
638
+ }
639
+
589
640
  /*
590
641
  Calculate the (left, top) coordinates of the output image
591
642
  within the input image, applying the given gravity during an embed.
@@ -754,23 +805,27 @@ namespace sharp {
754
805
  /*
755
806
  Convert RGBA value to another colourspace
756
807
  */
757
- std::vector<double> GetRgbaAsColourspace(std::vector<double> const rgba, VipsInterpretation const interpretation) {
808
+ std::vector<double> GetRgbaAsColourspace(std::vector<double> const rgba,
809
+ VipsInterpretation const interpretation, bool premultiply) {
758
810
  int const bands = static_cast<int>(rgba.size());
759
- if (bands < 3 || interpretation == VIPS_INTERPRETATION_sRGB || interpretation == VIPS_INTERPRETATION_RGB) {
811
+ if (bands < 3) {
760
812
  return rgba;
761
- } else {
762
- VImage pixel = VImage::new_matrix(1, 1);
763
- pixel.set("bands", bands);
764
- pixel = pixel.new_from_image(rgba);
765
- pixel = pixel.colourspace(interpretation, VImage::option()->set("source_space", VIPS_INTERPRETATION_sRGB));
766
- return pixel(0, 0);
767
813
  }
814
+ VImage pixel = VImage::new_matrix(1, 1);
815
+ pixel.set("bands", bands);
816
+ pixel = pixel
817
+ .new_from_image(rgba)
818
+ .colourspace(interpretation, VImage::option()->set("source_space", VIPS_INTERPRETATION_sRGB));
819
+ if (premultiply) {
820
+ pixel = pixel.premultiply();
821
+ }
822
+ return pixel(0, 0);
768
823
  }
769
824
 
770
825
  /*
771
826
  Apply the alpha channel to a given colour
772
827
  */
773
- std::tuple<VImage, std::vector<double>> ApplyAlpha(VImage image, std::vector<double> colour) {
828
+ std::tuple<VImage, std::vector<double>> ApplyAlpha(VImage image, std::vector<double> colour, bool premultiply) {
774
829
  // Scale up 8-bit values to match 16-bit input image
775
830
  double const multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0;
776
831
  // Create alphaColour colour
@@ -794,7 +849,7 @@ namespace sharp {
794
849
  alphaColour.push_back(colour[3] * multiplier);
795
850
  }
796
851
  // Ensure alphaColour colour uses correct colourspace
797
- alphaColour = sharp::GetRgbaAsColourspace(alphaColour, image.interpretation());
852
+ alphaColour = sharp::GetRgbaAsColourspace(alphaColour, image.interpretation(), premultiply);
798
853
  // Add non-transparent alpha channel, if required
799
854
  if (colour[3] < 255.0 && !HasAlpha(image)) {
800
855
  image = image.bandjoin(
@@ -824,4 +879,5 @@ namespace sharp {
824
879
  }
825
880
  return image;
826
881
  }
882
+
827
883
  } // namespace sharp
package/src/common.h CHANGED
@@ -25,9 +25,9 @@
25
25
  // Verify platform and compiler compatibility
26
26
 
27
27
  #if (VIPS_MAJOR_VERSION < 8) || \
28
- (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 10) || \
29
- (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 10 && VIPS_MICRO_VERSION < 6)
30
- #error "libvips version 8.10.6+ is required - please see https://sharp.pixelplumbing.com/install"
28
+ (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 11) || \
29
+ (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 11 && VIPS_MICRO_VERSION < 3)
30
+ #error "libvips version 8.11.3+ is required - please see https://sharp.pixelplumbing.com/install"
31
31
  #endif
32
32
 
33
33
  #if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6)))
@@ -54,6 +54,7 @@ namespace sharp {
54
54
  size_t bufferLength;
55
55
  bool isBuffer;
56
56
  double density;
57
+ VipsBandFormat rawDepth;
57
58
  int rawChannels;
58
59
  int rawWidth;
59
60
  int rawHeight;
@@ -78,6 +79,7 @@ namespace sharp {
78
79
  bufferLength(0),
79
80
  isBuffer(FALSE),
80
81
  density(72.0),
82
+ rawDepth(VIPS_FORMAT_UCHAR),
81
83
  rawChannels(0),
82
84
  rawWidth(0),
83
85
  rawHeight(0),
@@ -114,6 +116,7 @@ namespace sharp {
114
116
  JPEG,
115
117
  PNG,
116
118
  WEBP,
119
+ JP2,
117
120
  TIFF,
118
121
  GIF,
119
122
  SVG,
@@ -140,6 +143,7 @@ namespace sharp {
140
143
  bool IsJpeg(std::string const &str);
141
144
  bool IsPng(std::string const &str);
142
145
  bool IsWebp(std::string const &str);
146
+ bool IsJp2(std::string const &str);
143
147
  bool IsGif(std::string const &str);
144
148
  bool IsTiff(std::string const &str);
145
149
  bool IsHeic(std::string const &str);
@@ -206,6 +210,11 @@ namespace sharp {
206
210
  */
207
211
  VImage SetAnimationProperties(VImage image, int pageHeight, std::vector<int> delay, int loop);
208
212
 
213
+ /*
214
+ Remove animation properties from image.
215
+ */
216
+ VImage RemoveAnimationProperties(VImage image);
217
+
209
218
  /*
210
219
  Does this image have a non-default density?
211
220
  */
@@ -241,6 +250,16 @@ namespace sharp {
241
250
  */
242
251
  std::string VipsWarningPop();
243
252
 
253
+ /*
254
+ Attach an event listener for progress updates, used to detect timeout
255
+ */
256
+ void SetTimeout(VImage image, int const timeoutSeconds);
257
+
258
+ /*
259
+ Event listener for progress updates, used to detect timeout
260
+ */
261
+ void VipsProgressCallBack(VipsImage *image, VipsProgress *progress, int *timeoutSeconds);
262
+
244
263
  /*
245
264
  Calculate the (left, top) coordinates of the output image
246
265
  within the input image, applying the given gravity during an embed.
@@ -286,12 +305,13 @@ namespace sharp {
286
305
  /*
287
306
  Convert RGBA value to another colourspace
288
307
  */
289
- std::vector<double> GetRgbaAsColourspace(std::vector<double> const rgba, VipsInterpretation const interpretation);
308
+ std::vector<double> GetRgbaAsColourspace(std::vector<double> const rgba,
309
+ VipsInterpretation const interpretation, bool premultiply);
290
310
 
291
311
  /*
292
312
  Apply the alpha channel to a given colour
293
313
  */
294
- std::tuple<VImage, std::vector<double>> ApplyAlpha(VImage image, std::vector<double> colour);
314
+ std::tuple<VImage, std::vector<double>> ApplyAlpha(VImage image, std::vector<double> colour, bool premultiply);
295
315
 
296
316
  /*
297
317
  Removes alpha channel, if any.