sharp 0.25.4 → 0.26.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
@@ -11,7 +11,8 @@ const formats = new Map([
11
11
  ['png', 'png'],
12
12
  ['raw', 'raw'],
13
13
  ['tiff', 'tiff'],
14
- ['webp', 'webp']
14
+ ['webp', 'webp'],
15
+ ['gif', 'gif']
15
16
  ]);
16
17
 
17
18
  /**
@@ -118,7 +119,8 @@ function toBuffer (options, callback) {
118
119
 
119
120
  /**
120
121
  * Include all metadata (EXIF, XMP, IPTC) from the input image in the output image.
121
- * This will also convert to and add a web-friendly sRGB ICC profile.
122
+ * This will also convert to and add a web-friendly sRGB ICC profile unless a custom
123
+ * output profile is provided.
122
124
  *
123
125
  * The default behaviour, when `withMetadata` is not used, is to convert to the device-independent
124
126
  * sRGB colour space and strip all metadata, including the removal of any ICC profile.
@@ -131,6 +133,7 @@ function toBuffer (options, callback) {
131
133
  *
132
134
  * @param {Object} [options]
133
135
  * @param {number} [options.orientation] value between 1 and 8, used to update the EXIF `Orientation` tag.
136
+ * @param {string} [options.icc] filesystem path to output ICC profile, defaults to sRGB.
134
137
  * @returns {Sharp}
135
138
  * @throws {Error} Invalid parameters
136
139
  */
@@ -144,6 +147,13 @@ function withMetadata (options) {
144
147
  throw is.invalidParameterError('orientation', 'integer between 1 and 8', options.orientation);
145
148
  }
146
149
  }
150
+ if (is.defined(options.icc)) {
151
+ if (is.string(options.icc)) {
152
+ this.options.withMetadataIcc = options.icc;
153
+ } else {
154
+ throw is.invalidParameterError('icc', 'string filesystem path to ICC profile', options.icc);
155
+ }
156
+ }
147
157
  }
148
158
  return this;
149
159
  }
@@ -187,7 +197,7 @@ function toFormat (format, options) {
187
197
  * @param {Object} [options] - output options
188
198
  * @param {number} [options.quality=80] - quality, integer 1-100
189
199
  * @param {boolean} [options.progressive=false] - use progressive (interlace) scan
190
- * @param {string} [options.chromaSubsampling='4:2:0'] - for quality < 90, set to '4:4:4' to prevent chroma subsampling otherwise defaults to '4:2:0' (use chroma subsampling); for quality >= 90 chroma is never subsampled
200
+ * @param {string} [options.chromaSubsampling='4:2:0'] - set to '4:4:4' to prevent chroma subsampling otherwise defaults to '4:2:0' chroma subsampling
191
201
  * @param {boolean} [options.optimiseCoding=true] - optimise Huffman coding tables
192
202
  * @param {boolean} [options.optimizeCoding=true] - alternative spelling of optimiseCoding
193
203
  * @param {boolean} [options.trellisQuantisation=false] - apply trellis quantisation, requires libvips compiled with support for mozjpeg
@@ -340,6 +350,9 @@ function png (options) {
340
350
  * @param {boolean} [options.nearLossless=false] - use near_lossless compression mode
341
351
  * @param {boolean} [options.smartSubsample=false] - use high quality chroma subsampling
342
352
  * @param {number} [options.reductionEffort=4] - level of CPU effort to reduce file size, integer 0-6
353
+ * @param {number} [options.pageHeight] - page height for animated output
354
+ * @param {number} [options.loop=0] - number of animation iterations, use 0 for infinite animation
355
+ * @param {number[]} [options.delay] - list of delays between animation frames (in milliseconds)
343
356
  * @param {boolean} [options.force=true] - force WebP output, otherwise attempt to use input format
344
357
  * @returns {Sharp}
345
358
  * @throws {Error} Invalid options
@@ -375,9 +388,73 @@ function webp (options) {
375
388
  throw is.invalidParameterError('reductionEffort', 'integer between 0 and 6', options.reductionEffort);
376
389
  }
377
390
  }
391
+
392
+ trySetAnimationOptions(options, this.options);
378
393
  return this._updateFormatOut('webp', options);
379
394
  }
380
395
 
396
+ /**
397
+ * Use these GIF options for output image.
398
+ *
399
+ * Requires libvips compiled with support for ImageMagick or GraphicsMagick.
400
+ * The prebuilt binaries do not include this - see
401
+ * {@link https://sharp.pixelplumbing.com/install#custom-libvips installing a custom libvips}.
402
+ *
403
+ * @param {Object} [options] - output options
404
+ * @param {number} [options.pageHeight] - page height for animated output
405
+ * @param {number} [options.loop=0] - number of animation iterations, use 0 for infinite animation
406
+ * @param {number[]} [options.delay] - list of delays between animation frames (in milliseconds)
407
+ * @param {boolean} [options.force=true] - force GIF output, otherwise attempt to use input format
408
+ * @returns {Sharp}
409
+ * @throws {Error} Invalid options
410
+ */
411
+ /* istanbul ignore next */
412
+ function gif (options) {
413
+ if (!this.constructor.format.magick.output.buffer) {
414
+ throw new Error('The gif operation requires libvips to have been installed with support for ImageMagick');
415
+ }
416
+ trySetAnimationOptions(options, this.options);
417
+ return this._updateFormatOut('gif', options);
418
+ }
419
+
420
+ /**
421
+ * Set animation options if available.
422
+ * @private
423
+ *
424
+ * @param {Object} [source] - output options
425
+ * @param {number} [source.pageHeight] - page height for animated output
426
+ * @param {number} [source.loop=0] - number of animation iterations, use 0 for infinite animation
427
+ * @param {number[]} [source.delay] - list of delays between animation frames (in milliseconds)
428
+ * @param {Object} [target] - target object for valid options
429
+ * @throws {Error} Invalid options
430
+ */
431
+ function trySetAnimationOptions (source, target) {
432
+ if (is.object(source) && is.defined(source.pageHeight)) {
433
+ if (is.integer(source.pageHeight) && source.pageHeight > 0) {
434
+ target.pageHeight = source.pageHeight;
435
+ } else {
436
+ throw is.invalidParameterError('pageHeight', 'integer larger than 0', source.pageHeight);
437
+ }
438
+ }
439
+ if (is.object(source) && is.defined(source.loop)) {
440
+ if (is.integer(source.loop) && is.inRange(source.loop, 0, 65535)) {
441
+ target.loop = source.loop;
442
+ } else {
443
+ throw is.invalidParameterError('loop', 'integer between 0 and 65535', source.loop);
444
+ }
445
+ }
446
+ if (is.object(source) && is.defined(source.delay)) {
447
+ if (
448
+ Array.isArray(source.delay) &&
449
+ source.delay.every(is.integer) &&
450
+ source.delay.every(v => is.inRange(v, 0, 65535))) {
451
+ target.delay = source.delay;
452
+ } else {
453
+ throw is.invalidParameterError('delay', 'array of integers between 0 and 65535', source.delay);
454
+ }
455
+ }
456
+ }
457
+
381
458
  /**
382
459
  * Use these TIFF options for output image.
383
460
  *
@@ -386,7 +463,7 @@ function webp (options) {
386
463
  * sharp('input.svg')
387
464
  * .tiff({
388
465
  * compression: 'lzw',
389
- * squash: true
466
+ * bitdepth: 1
390
467
  * })
391
468
  * .toFile('1-bpp-output.tiff')
392
469
  * .then(info => { ... });
@@ -402,7 +479,7 @@ function webp (options) {
402
479
  * @param {boolean} [options.tileHeight=256] - vertical tile size
403
480
  * @param {number} [options.xres=1.0] - horizontal resolution in pixels/mm
404
481
  * @param {number} [options.yres=1.0] - vertical resolution in pixels/mm
405
- * @param {boolean} [options.squash=false] - squash 8-bit images down to 1 bit
482
+ * @param {boolean} [options.bitdepth=8] - reduce bitdepth to 1, 2 or 4 bit
406
483
  * @returns {Sharp}
407
484
  * @throws {Error} Invalid options
408
485
  */
@@ -415,8 +492,12 @@ function tiff (options) {
415
492
  throw is.invalidParameterError('quality', 'integer between 1 and 100', options.quality);
416
493
  }
417
494
  }
418
- if (is.defined(options.squash)) {
419
- this._setBooleanOption('tiffSquash', options.squash);
495
+ if (is.defined(options.bitdepth)) {
496
+ if (is.integer(options.bitdepth) && is.inArray(options.bitdepth, [1, 2, 4, 8])) {
497
+ this.options.tiffBitdepth = options.bitdepth;
498
+ } else {
499
+ throw is.invalidParameterError('bitdepth', '1, 2, 4 or 8', options.bitdepth);
500
+ }
420
501
  }
421
502
  // tiling
422
503
  if (is.defined(options.tile)) {
@@ -577,6 +658,8 @@ function raw () {
577
658
  * @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
578
659
  * @param {string} [options.container='fs'] tile container, with value `fs` (filesystem) or `zip` (compressed file).
579
660
  * @param {string} [options.layout='dz'] filesystem layout, possible values are `dz`, `iiif`, `zoomify` or `google`.
661
+ * @param {boolean} [options.centre=false] centre image in tile.
662
+ * @param {boolean} [options.center=false] alternative spelling of centre.
580
663
  * @returns {Sharp}
581
664
  * @throws {Error} Invalid parameters
582
665
  */
@@ -645,6 +728,11 @@ function tile (options) {
645
728
  } else if (is.defined(options.layout) && options.layout === 'google') {
646
729
  this.options.tileSkipBlanks = 5;
647
730
  }
731
+ // Center image in tile
732
+ const centre = is.bool(options.center) ? options.center : options.centre;
733
+ if (is.defined(centre)) {
734
+ this._setBooleanOption('tileCentre', centre);
735
+ }
648
736
  }
649
737
  // Format
650
738
  if (is.inArray(this.options.formatOut, ['jpeg', 'png', 'webp'])) {
@@ -804,6 +892,7 @@ module.exports = function (Sharp) {
804
892
  webp,
805
893
  tiff,
806
894
  heif,
895
+ gif,
807
896
  raw,
808
897
  tile,
809
898
  // Private
package/lib/platform.js CHANGED
@@ -13,7 +13,8 @@ module.exports = function () {
13
13
  const platformId = [`${platform}${libc}`];
14
14
 
15
15
  if (arch === 'arm') {
16
- platformId.push(`armv${env.npm_config_arm_version || process.config.variables.arm_version || '6'}`);
16
+ const fallback = process.versions.electron ? '7' : '6';
17
+ platformId.push(`armv${env.npm_config_arm_version || process.config.variables.arm_version || fallback}`);
17
18
  } else if (arch === 'arm64') {
18
19
  platformId.push(`arm64v${env.npm_config_arm_version || '8'}`);
19
20
  } else {
package/lib/resize.js CHANGED
@@ -386,7 +386,8 @@ function extract (options) {
386
386
  * Trim "boring" pixels from all edges that contain values similar to the top-left pixel.
387
387
  * Images consisting entirely of a single colour will calculate "boring" using the alpha channel, if any.
388
388
  *
389
- * The `info` response Object will contain `trimOffsetLeft` and `trimOffsetTop` properties.
389
+ * The `info` response Object, obtained from callback of `.toFile()` or `.toBuffer()`,
390
+ * will contain `trimOffsetLeft` and `trimOffsetTop` properties.
390
391
  *
391
392
  * @param {number} [threshold=10] the allowed difference from the top-left pixel, a number greater than zero.
392
393
  * @returns {Sharp}
package/lib/utility.js CHANGED
@@ -13,6 +13,26 @@ const sharp = require('../build/Release/sharp.node');
13
13
  */
14
14
  const format = sharp.format();
15
15
 
16
+ /**
17
+ * An Object containing the available interpolators and their proper values
18
+ * @readonly
19
+ * @enum {string}
20
+ */
21
+ const interpolators = {
22
+ /** [Nearest neighbour interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation). Suitable for image enlargement only. */
23
+ nearest: 'nearest',
24
+ /** [Bilinear interpolation](http://en.wikipedia.org/wiki/Bilinear_interpolation). Faster than bicubic but with less smooth results. */
25
+ bilinear: 'bilinear',
26
+ /** [Bicubic interpolation](http://en.wikipedia.org/wiki/Bicubic_interpolation) (the default). */
27
+ bicubic: 'bicubic',
28
+ /** [LBB interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/lbb.cpp#L100). Prevents some "[acutance](http://en.wikipedia.org/wiki/Acutance)" but typically reduces performance by a factor of 2. */
29
+ locallyBoundedBicubic: 'lbb',
30
+ /** [Nohalo interpolation](http://eprints.soton.ac.uk/268086/). Prevents acutance but typically reduces performance by a factor of 3. */
31
+ nohalo: 'nohalo',
32
+ /** [VSQBS interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/vsqbs.cpp#L48). Prevents "staircasing" when enlarging. */
33
+ vertexSplitQuadraticBasisSpline: 'vsqbs'
34
+ };
35
+
16
36
  /**
17
37
  * An Object containing the version numbers of libvips and its dependencies.
18
38
  * @member
@@ -23,7 +43,7 @@ let versions = {
23
43
  vips: sharp.libvipsVersion()
24
44
  };
25
45
  try {
26
- versions = require('../vendor/versions.json');
46
+ versions = require(`../vendor/${versions.vips}/versions.json`);
27
47
  } catch (err) {}
28
48
 
29
49
  /**
@@ -146,6 +166,7 @@ module.exports = function (Sharp) {
146
166
  Sharp[f.name] = f;
147
167
  });
148
168
  Sharp.format = format;
169
+ Sharp.interpolators = interpolators;
149
170
  Sharp.versions = versions;
150
171
  Sharp.queue = queue;
151
172
  };
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 and TIFF images",
4
- "version": "0.25.4",
4
+ "version": "0.26.3",
5
5
  "author": "Lovell Fuller <npm@lovell.info>",
6
6
  "homepage": "https://github.com/lovell/sharp",
7
7
  "contributors": [
@@ -67,12 +67,15 @@
67
67
  "Brendan Kennedy <brenwken@gmail.com>",
68
68
  "Brychan Bennett-Odlum <git@brychan.io>",
69
69
  "Edward Silverton <e.silverton@gmail.com>",
70
- "Roman Malieiev <aromaleev@gmail.com>"
70
+ "Roman Malieiev <aromaleev@gmail.com>",
71
+ "Tomas Szabo <tomas.szabo@deftomat.com>",
72
+ "Robert O'Rourke <robert@o-rourke.org>",
73
+ "Guillermo Alfonso Varela Chouciño <guillevch@gmail.com>"
71
74
  ],
72
75
  "scripts": {
73
- "install": "(node install/libvips && node install/dll-copy && prebuild-install --runtime=napi) || (node-gyp rebuild && node install/dll-copy)",
76
+ "install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node-gyp rebuild && node install/dll-copy)",
74
77
  "clean": "rm -rf node_modules/ build/ vendor/ .nyc_output/ coverage/ test/fixtures/output.*",
75
- "test": "semistandard && cpplint && npm run test-unit && npm run test-licensing && prebuild-ci",
78
+ "test": "semistandard && cpplint && npm run test-unit && npm run test-licensing && node install/prebuild-ci",
76
79
  "test-unit": "nyc --reporter=lcov --branches=99 mocha --slow=5000 --timeout=60000 ./test/unit/*.js",
77
80
  "test-licensing": "license-checker --production --summary --onlyAllow=\"Apache-2.0;BSD;ISC;MIT\"",
78
81
  "test-coverage": "./test/coverage/report.sh",
@@ -85,6 +88,7 @@
85
88
  "files": [
86
89
  "binding.gyp",
87
90
  "install/**",
91
+ "!install/prebuild-ci.js",
88
92
  "lib/**",
89
93
  "src/**"
90
94
  ],
@@ -109,35 +113,37 @@
109
113
  "vips"
110
114
  ],
111
115
  "dependencies": {
112
- "color": "^3.1.2",
116
+ "array-flatten": "^3.0.0",
117
+ "color": "^3.1.3",
113
118
  "detect-libc": "^1.0.3",
114
- "node-addon-api": "^3.0.0",
119
+ "node-addon-api": "^3.0.2",
115
120
  "npmlog": "^4.1.2",
116
- "prebuild-install": "^5.3.4",
121
+ "prebuild-install": "^6.0.0",
117
122
  "semver": "^7.3.2",
118
123
  "simple-get": "^4.0.0",
119
- "tar": "^6.0.2",
124
+ "tar-fs": "^2.1.1",
120
125
  "tunnel-agent": "^0.6.0"
121
126
  },
122
127
  "devDependencies": {
123
128
  "async": "^3.2.0",
124
129
  "cc": "^2.0.1",
125
130
  "decompress-zip": "^0.3.2",
126
- "documentation": "^13.0.1",
131
+ "documentation": "^13.1.0",
127
132
  "exif-reader": "^1.0.3",
128
- "icc": "^1.0.0",
133
+ "icc": "^2.0.0",
129
134
  "license-checker": "^25.0.1",
130
- "mocha": "^8.0.1",
131
- "mock-fs": "^4.12.0",
135
+ "mocha": "^8.2.1",
136
+ "mock-fs": "^4.13.0",
132
137
  "nyc": "^15.1.0",
133
- "prebuild": "^10.0.0",
134
- "prebuild-ci": "^3.1.0",
138
+ "prebuild": "^10.0.1",
135
139
  "rimraf": "^3.0.2",
136
- "semistandard": "^14.2.0"
140
+ "semistandard": "^16.0.0"
137
141
  },
138
142
  "license": "Apache-2.0",
139
143
  "config": {
140
- "libvips": "8.9.1"
144
+ "libvips": "8.10.0",
145
+ "runtime": "napi",
146
+ "target": 3
141
147
  },
142
148
  "engines": {
143
149
  "node": ">=10"
package/src/common.cc CHANGED
@@ -17,6 +17,7 @@
17
17
  #include <string.h>
18
18
  #include <vector>
19
19
  #include <queue>
20
+ #include <map>
20
21
  #include <mutex> // NOLINT(build/c++11)
21
22
 
22
23
  #include <napi.h>
@@ -41,6 +42,9 @@ namespace sharp {
41
42
  int32_t AttrAsInt32(Napi::Object obj, std::string attr) {
42
43
  return obj.Get(attr).As<Napi::Number>().Int32Value();
43
44
  }
45
+ int32_t AttrAsInt32(Napi::Object obj, unsigned int const attr) {
46
+ return obj.Get(attr).As<Napi::Number>().Int32Value();
47
+ }
44
48
  double AttrAsDouble(Napi::Object obj, std::string attr) {
45
49
  return obj.Get(attr).As<Napi::Number>().DoubleValue();
46
50
  }
@@ -50,13 +54,21 @@ namespace sharp {
50
54
  bool AttrAsBool(Napi::Object obj, std::string attr) {
51
55
  return obj.Get(attr).As<Napi::Boolean>().Value();
52
56
  }
53
- std::vector<double> AttrAsRgba(Napi::Object obj, std::string attr) {
54
- Napi::Array background = obj.Get(attr).As<Napi::Array>();
55
- std::vector<double> rgba(background.Length());
56
- for (unsigned int i = 0; i < background.Length(); i++) {
57
- rgba[i] = AttrAsDouble(background, i);
57
+ std::vector<double> AttrAsVectorOfDouble(Napi::Object obj, std::string attr) {
58
+ Napi::Array napiArray = obj.Get(attr).As<Napi::Array>();
59
+ std::vector<double> vectorOfDouble(napiArray.Length());
60
+ for (unsigned int i = 0; i < napiArray.Length(); i++) {
61
+ vectorOfDouble[i] = AttrAsDouble(napiArray, i);
58
62
  }
59
- return rgba;
63
+ return vectorOfDouble;
64
+ }
65
+ std::vector<int32_t> AttrAsInt32Vector(Napi::Object obj, std::string attr) {
66
+ Napi::Array array = obj.Get(attr).As<Napi::Array>();
67
+ std::vector<int32_t> vector(array.Length());
68
+ for (unsigned int i = 0; i < array.Length(); i++) {
69
+ vector[i] = AttrAsInt32(array, i);
70
+ }
71
+ return vector;
60
72
  }
61
73
 
62
74
  // Create an InputDescriptor instance from a Napi::Object describing an input image
@@ -97,7 +109,7 @@ namespace sharp {
97
109
  descriptor->createChannels = AttrAsUint32(input, "createChannels");
98
110
  descriptor->createWidth = AttrAsUint32(input, "createWidth");
99
111
  descriptor->createHeight = AttrAsUint32(input, "createHeight");
100
- descriptor->createBackground = AttrAsRgba(input, "createBackground");
112
+ descriptor->createBackground = AttrAsVectorOfDouble(input, "createBackground");
101
113
  }
102
114
  // Limit input images to a given number of pixels, where pixels = width * height
103
115
  descriptor->limitInputPixels = AttrAsUint32(input, "limitInputPixels");
@@ -125,6 +137,9 @@ namespace sharp {
125
137
  bool IsWebp(std::string const &str) {
126
138
  return EndsWith(str, ".webp") || EndsWith(str, ".WEBP");
127
139
  }
140
+ bool IsGif(std::string const &str) {
141
+ return EndsWith(str, ".gif") || EndsWith(str, ".GIF");
142
+ }
128
143
  bool IsTiff(std::string const &str) {
129
144
  return EndsWith(str, ".tif") || EndsWith(str, ".tiff") || EndsWith(str, ".TIF") || EndsWith(str, ".TIFF");
130
145
  }
@@ -165,6 +180,7 @@ namespace sharp {
165
180
  case ImageType::OPENSLIDE: id = "openslide"; break;
166
181
  case ImageType::PPM: id = "ppm"; break;
167
182
  case ImageType::FITS: id = "fits"; break;
183
+ case ImageType::EXR: id = "exr"; break;
168
184
  case ImageType::VIPS: id = "vips"; break;
169
185
  case ImageType::RAW: id = "raw"; break;
170
186
  case ImageType::UNKNOWN: id = "unknown"; break;
@@ -173,32 +189,43 @@ namespace sharp {
173
189
  return id;
174
190
  }
175
191
 
192
+ std::map<std::string, ImageType> loaderToType = {
193
+ { "jpegload", ImageType::JPEG },
194
+ { "jpegload_buffer", ImageType::JPEG },
195
+ { "pngload", ImageType::PNG },
196
+ { "pngload_buffer", ImageType::PNG },
197
+ { "webpload", ImageType::WEBP },
198
+ { "webpload_buffer", ImageType::WEBP },
199
+ { "tiffload", ImageType::TIFF },
200
+ { "tiffload_buffer", ImageType::TIFF },
201
+ { "gifload", ImageType::GIF },
202
+ { "gifload_buffer", ImageType::GIF },
203
+ { "svgload", ImageType::SVG },
204
+ { "svgload_buffer", ImageType::SVG },
205
+ { "heifload", ImageType::HEIF },
206
+ { "heifload_buffer", ImageType::HEIF },
207
+ { "pdfload", ImageType::PDF },
208
+ { "pdfload_buffer", ImageType::PDF },
209
+ { "magickload", ImageType::MAGICK },
210
+ { "magickload_buffer", ImageType::MAGICK },
211
+ { "openslideload", ImageType::OPENSLIDE },
212
+ { "ppmload", ImageType::PPM },
213
+ { "fitsload", ImageType::FITS },
214
+ { "openexrload", ImageType::EXR },
215
+ { "vipsload", ImageType::VIPS },
216
+ { "rawload", ImageType::RAW }
217
+ };
218
+
176
219
  /*
177
220
  Determine image format of a buffer.
178
221
  */
179
222
  ImageType DetermineImageType(void *buffer, size_t const length) {
180
223
  ImageType imageType = ImageType::UNKNOWN;
181
224
  char const *load = vips_foreign_find_load_buffer(buffer, length);
182
- if (load != NULL) {
183
- std::string const loader = load;
184
- if (EndsWith(loader, "JpegBuffer")) {
185
- imageType = ImageType::JPEG;
186
- } else if (EndsWith(loader, "PngBuffer")) {
187
- imageType = ImageType::PNG;
188
- } else if (EndsWith(loader, "WebpBuffer")) {
189
- imageType = ImageType::WEBP;
190
- } else if (EndsWith(loader, "TiffBuffer")) {
191
- imageType = ImageType::TIFF;
192
- } else if (EndsWith(loader, "GifBuffer")) {
193
- imageType = ImageType::GIF;
194
- } else if (EndsWith(loader, "SvgBuffer")) {
195
- imageType = ImageType::SVG;
196
- } else if (EndsWith(loader, "HeifBuffer")) {
197
- imageType = ImageType::HEIF;
198
- } else if (EndsWith(loader, "PdfBuffer")) {
199
- imageType = ImageType::PDF;
200
- } else if (EndsWith(loader, "MagickBuffer")) {
201
- imageType = ImageType::MAGICK;
225
+ if (load != nullptr) {
226
+ auto it = loaderToType.find(vips_nickname_find(g_type_from_name(load)));
227
+ if (it != loaderToType.end()) {
228
+ imageType = it->second;
202
229
  }
203
230
  }
204
231
  return imageType;
@@ -211,36 +238,12 @@ namespace sharp {
211
238
  ImageType imageType = ImageType::UNKNOWN;
212
239
  char const *load = vips_foreign_find_load(file);
213
240
  if (load != nullptr) {
214
- std::string const loader = load;
215
- if (EndsWith(loader, "JpegFile")) {
216
- imageType = ImageType::JPEG;
217
- } else if (EndsWith(loader, "PngFile")) {
218
- imageType = ImageType::PNG;
219
- } else if (EndsWith(loader, "WebpFile")) {
220
- imageType = ImageType::WEBP;
221
- } else if (EndsWith(loader, "Openslide")) {
222
- imageType = ImageType::OPENSLIDE;
223
- } else if (EndsWith(loader, "TiffFile")) {
224
- imageType = ImageType::TIFF;
225
- } else if (EndsWith(loader, "GifFile")) {
226
- imageType = ImageType::GIF;
227
- } else if (EndsWith(loader, "SvgFile")) {
228
- imageType = ImageType::SVG;
229
- } else if (EndsWith(loader, "HeifFile")) {
230
- imageType = ImageType::HEIF;
231
- } else if (EndsWith(loader, "PdfFile")) {
232
- imageType = ImageType::PDF;
233
- } else if (EndsWith(loader, "Ppm")) {
234
- imageType = ImageType::PPM;
235
- } else if (EndsWith(loader, "Fits")) {
236
- imageType = ImageType::FITS;
237
- } else if (EndsWith(loader, "Vips")) {
238
- imageType = ImageType::VIPS;
239
- } else if (EndsWith(loader, "Magick") || EndsWith(loader, "MagickFile")) {
240
- imageType = ImageType::MAGICK;
241
+ auto it = loaderToType.find(vips_nickname_find(g_type_from_name(load)));
242
+ if (it != loaderToType.end()) {
243
+ imageType = it->second;
241
244
  }
242
245
  } else {
243
- if (EndsWith(vips::VError().what(), " not found\n")) {
246
+ if (EndsWith(vips::VError().what(), " does not exist\n")) {
244
247
  imageType = ImageType::MISSING;
245
248
  }
246
249
  }
@@ -252,6 +255,8 @@ namespace sharp {
252
255
  */
253
256
  bool ImageTypeSupportsPage(ImageType imageType) {
254
257
  return
258
+ imageType == ImageType::WEBP ||
259
+ imageType == ImageType::MAGICK ||
255
260
  imageType == ImageType::GIF ||
256
261
  imageType == ImageType::TIFF ||
257
262
  imageType == ImageType::HEIF ||
@@ -420,6 +425,38 @@ namespace sharp {
420
425
  return copy;
421
426
  }
422
427
 
428
+ /*
429
+ Set animation properties if necessary.
430
+ Non-provided properties will be loaded from image.
431
+ */
432
+ VImage SetAnimationProperties(VImage image, int pageHeight, std::vector<int> delay, int loop) {
433
+ bool hasDelay = delay.size() != 1 || delay.front() != -1;
434
+
435
+ if (pageHeight == 0 && image.get_typeof(VIPS_META_PAGE_HEIGHT) == G_TYPE_INT) {
436
+ pageHeight = image.get_int(VIPS_META_PAGE_HEIGHT);
437
+ }
438
+
439
+ if (!hasDelay && image.get_typeof("delay") == VIPS_TYPE_ARRAY_INT) {
440
+ delay = image.get_array_int("delay");
441
+ hasDelay = true;
442
+ }
443
+
444
+ if (loop == -1 && image.get_typeof("loop") == G_TYPE_INT) {
445
+ loop = image.get_int("loop");
446
+ }
447
+
448
+ if (pageHeight == 0) return image;
449
+
450
+ // It is necessary to create the copy as otherwise, pageHeight will be ignored!
451
+ VImage copy = image.copy();
452
+
453
+ copy.set(VIPS_META_PAGE_HEIGHT, pageHeight);
454
+ if (hasDelay) copy.set("delay", delay);
455
+ if (loop != -1) copy.set("loop", loop);
456
+
457
+ return copy;
458
+ }
459
+
423
460
  /*
424
461
  Does this image have a non-default density?
425
462
  */
@@ -450,14 +487,21 @@ namespace sharp {
450
487
  Check the proposed format supports the current dimensions.
451
488
  */
452
489
  void AssertImageTypeDimensions(VImage image, ImageType const imageType) {
490
+ const int height = image.get_typeof(VIPS_META_PAGE_HEIGHT) == G_TYPE_INT
491
+ ? image.get_int(VIPS_META_PAGE_HEIGHT)
492
+ : image.height();
453
493
  if (imageType == ImageType::JPEG) {
454
- if (image.width() > 65535 || image.height() > 65535) {
494
+ if (image.width() > 65535 || height > 65535) {
455
495
  throw vips::VError("Processed image is too large for the JPEG format");
456
496
  }
457
497
  } else if (imageType == ImageType::WEBP) {
458
- if (image.width() > 16383 || image.height() > 16383) {
498
+ if (image.width() > 16383 || height > 16383) {
459
499
  throw vips::VError("Processed image is too large for the WebP format");
460
500
  }
501
+ } else if (imageType == ImageType::GIF) {
502
+ if (image.width() > 65535 || height > 65535) {
503
+ throw vips::VError("Processed image is too large for the GIF format");
504
+ }
461
505
  }
462
506
  }
463
507
 
@@ -720,4 +764,25 @@ namespace sharp {
720
764
  return std::make_tuple(image, alphaColour);
721
765
  }
722
766
 
767
+ /*
768
+ Removes alpha channel, if any.
769
+ */
770
+ VImage RemoveAlpha(VImage image) {
771
+ if (HasAlpha(image)) {
772
+ image = image.extract_band(0, VImage::option()->set("n", image.bands() - 1));
773
+ }
774
+ return image;
775
+ }
776
+
777
+ /*
778
+ Ensures alpha channel, if missing.
779
+ */
780
+ VImage EnsureAlpha(VImage image) {
781
+ if (!HasAlpha(image)) {
782
+ std::vector<double> alpha;
783
+ alpha.push_back(sharp::MaximumImageAlpha(image.interpretation()));
784
+ image = image.bandjoin_const(alpha);
785
+ }
786
+ return image;
787
+ }
723
788
  } // namespace sharp