sharp 0.27.1 → 0.28.2

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
@@ -16,6 +16,8 @@ const formats = new Map([
16
16
  ['gif', 'gif']
17
17
  ]);
18
18
 
19
+ const errMagickSave = new Error('GIF output requires libvips with support for ImageMagick');
20
+
19
21
  /**
20
22
  * Write output image data to a file.
21
23
  *
@@ -47,25 +49,23 @@ const formats = new Map([
47
49
  * @throws {Error} Invalid parameters
48
50
  */
49
51
  function toFile (fileOut, callback) {
50
- if (!fileOut || fileOut.length === 0) {
51
- const errOutputInvalid = new Error('Missing output file path');
52
+ let err;
53
+ if (!is.string(fileOut)) {
54
+ err = new Error('Missing output file path');
55
+ } else if (this.options.input.file === fileOut) {
56
+ 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
+ }
60
+ if (err) {
52
61
  if (is.fn(callback)) {
53
- callback(errOutputInvalid);
62
+ callback(err);
54
63
  } else {
55
- return Promise.reject(errOutputInvalid);
64
+ return Promise.reject(err);
56
65
  }
57
66
  } else {
58
- if (this.options.input.file === fileOut) {
59
- const errOutputIsInput = new Error('Cannot use same file for input and output');
60
- if (is.fn(callback)) {
61
- callback(errOutputIsInput);
62
- } else {
63
- return Promise.reject(errOutputIsInput);
64
- }
65
- } else {
66
- this.options.fileOut = fileOut;
67
- return this._pipeline(callback);
68
- }
67
+ this.options.fileOut = fileOut;
68
+ return this._pipeline(callback);
69
69
  }
70
70
  return this;
71
71
  }
@@ -105,10 +105,10 @@ function toFile (fileOut, callback) {
105
105
  * .catch(err => { ... });
106
106
  *
107
107
  * @example
108
- * const data = await sharp('my-image.jpg')
108
+ * const { data, info } = await sharp('my-image.jpg')
109
109
  * // output the raw pixels
110
110
  * .raw()
111
- * .toBuffer();
111
+ * .toBuffer({ resolveWithObject: true });
112
112
  *
113
113
  * // create a more type safe way to work with the raw pixel data
114
114
  * // this will not copy the data, instead it will change `data`s underlying ArrayBuffer
@@ -116,7 +116,9 @@ function toFile (fileOut, callback) {
116
116
  * const pixelArray = new Uint8ClampedArray(data.buffer);
117
117
  *
118
118
  * // When you are done changing the pixelArray, sharp takes the `pixelArray` as an input
119
- * await sharp(pixelArray).toFile('my-changed-image.jpg');
119
+ * const { width, height, channels } = info;
120
+ * await sharp(pixelArray, { raw: { width, height, channels } })
121
+ * .toFile('my-changed-image.jpg');
120
122
  *
121
123
  * @param {Object} [options]
122
124
  * @param {boolean} [options.resolveWithObject] Resolve the Promise with an Object containing `data` and `info` properties instead of resolving only with `data`.
@@ -146,9 +148,29 @@ function toBuffer (options, callback) {
146
148
  * .toFile('output-with-metadata.jpg')
147
149
  * .then(info => { ... });
148
150
  *
151
+ * @example
152
+ * // Set "IFD0-Copyright" in output EXIF metadata
153
+ * const data = await sharp(input)
154
+ * .withMetadata({
155
+ * exif: {
156
+ * IFD0: {
157
+ * Copyright: 'Wernham Hogg'
158
+ * }
159
+ * }
160
+ * })
161
+ * .toBuffer();
162
+ *
163
+ * * @example
164
+ * // Set output metadata to 96 DPI
165
+ * const data = await sharp(input)
166
+ * .withMetadata({ density: 96 })
167
+ * .toBuffer();
168
+ *
149
169
  * @param {Object} [options]
150
170
  * @param {number} [options.orientation] value between 1 and 8, used to update the EXIF `Orientation` tag.
151
171
  * @param {string} [options.icc] filesystem path to output ICC profile, defaults to sRGB.
172
+ * @param {Object<Object>} [options.exif={}] Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data.
173
+ * @param {number} [options.density] Number of pixels per inch (DPI).
152
174
  * @returns {Sharp}
153
175
  * @throws {Error} Invalid parameters
154
176
  */
@@ -162,6 +184,13 @@ function withMetadata (options) {
162
184
  throw is.invalidParameterError('orientation', 'integer between 1 and 8', options.orientation);
163
185
  }
164
186
  }
187
+ if (is.defined(options.density)) {
188
+ if (is.number(options.density) && options.density > 0) {
189
+ this.options.withMetadataDensity = options.density;
190
+ } else {
191
+ throw is.invalidParameterError('density', 'positive number', options.density);
192
+ }
193
+ }
165
194
  if (is.defined(options.icc)) {
166
195
  if (is.string(options.icc)) {
167
196
  this.options.withMetadataIcc = options.icc;
@@ -169,6 +198,25 @@ function withMetadata (options) {
169
198
  throw is.invalidParameterError('icc', 'string filesystem path to ICC profile', options.icc);
170
199
  }
171
200
  }
201
+ if (is.defined(options.exif)) {
202
+ if (is.object(options.exif)) {
203
+ for (const [ifd, entries] of Object.entries(options.exif)) {
204
+ if (is.object(entries)) {
205
+ for (const [k, v] of Object.entries(entries)) {
206
+ if (is.string(v)) {
207
+ this.options.withMetadataStrs[`exif-${ifd.toLowerCase()}-${k}`] = v;
208
+ } else {
209
+ throw is.invalidParameterError(`exif.${ifd}.${k}`, 'string', v);
210
+ }
211
+ }
212
+ } else {
213
+ throw is.invalidParameterError(`exif.${ifd}`, 'object', entries);
214
+ }
215
+ }
216
+ } else {
217
+ throw is.invalidParameterError('exif', 'object', options.exif);
218
+ }
219
+ }
172
220
  }
173
221
  return this;
174
222
  }
@@ -188,7 +236,7 @@ function withMetadata (options) {
188
236
  * @throws {Error} unsupported format or options
189
237
  */
190
238
  function toFormat (format, options) {
191
- const actualFormat = formats.get(is.object(format) && is.string(format.id) ? format.id : format);
239
+ const actualFormat = formats.get((is.object(format) && is.string(format.id) ? format.id : format).toLowerCase());
192
240
  if (!actualFormat) {
193
241
  throw is.invalidParameterError('format', `one of: ${[...formats.keys()].join(', ')}`, format);
194
242
  }
@@ -198,8 +246,6 @@ function toFormat (format, options) {
198
246
  /**
199
247
  * Use these JPEG options for output image.
200
248
  *
201
- * Some of these options require the use of a globally-installed libvips compiled with support for mozjpeg.
202
- *
203
249
  * @example
204
250
  * // Convert any input to very high quality JPEG output
205
251
  * const data = await sharp(input)
@@ -209,18 +255,25 @@ function toFormat (format, options) {
209
255
  * })
210
256
  * .toBuffer();
211
257
  *
258
+ * @example
259
+ * // Use mozjpeg to reduce output JPEG file size (slower)
260
+ * const data = await sharp(input)
261
+ * .jpeg({ mozjpeg: true })
262
+ * .toBuffer();
263
+ *
212
264
  * @param {Object} [options] - output options
213
265
  * @param {number} [options.quality=80] - quality, integer 1-100
214
266
  * @param {boolean} [options.progressive=false] - use progressive (interlace) scan
215
267
  * @param {string} [options.chromaSubsampling='4:2:0'] - set to '4:4:4' to prevent chroma subsampling otherwise defaults to '4:2:0' chroma subsampling
216
268
  * @param {boolean} [options.optimiseCoding=true] - optimise Huffman coding tables
217
269
  * @param {boolean} [options.optimizeCoding=true] - alternative spelling of optimiseCoding
218
- * @param {boolean} [options.trellisQuantisation=false] - apply trellis quantisation, requires libvips compiled with support for mozjpeg
219
- * @param {boolean} [options.overshootDeringing=false] - apply overshoot deringing, requires libvips compiled with support for mozjpeg
220
- * @param {boolean} [options.optimiseScans=false] - optimise progressive scans, forces progressive, requires libvips compiled with support for mozjpeg
221
- * @param {boolean} [options.optimizeScans=false] - alternative spelling of optimiseScans, requires libvips compiled with support for mozjpeg
222
- * @param {number} [options.quantisationTable=0] - quantization table to use, integer 0-8, requires libvips compiled with support for mozjpeg
223
- * @param {number} [options.quantizationTable=0] - alternative spelling of quantisationTable, requires libvips compiled with support for mozjpeg
270
+ * @param {boolean} [options.mozjpeg=false] - use mozjpeg defaults, equivalent to `{ trellisQuantisation: true, overshootDeringing: true, optimiseScans: true, quantisationTable: 3 }`
271
+ * @param {boolean} [options.trellisQuantisation=false] - apply trellis quantisation
272
+ * @param {boolean} [options.overshootDeringing=false] - apply overshoot deringing
273
+ * @param {boolean} [options.optimiseScans=false] - optimise progressive scans, forces progressive
274
+ * @param {boolean} [options.optimizeScans=false] - alternative spelling of optimiseScans
275
+ * @param {number} [options.quantisationTable=0] - quantization table to use, integer 0-8
276
+ * @param {number} [options.quantizationTable=0] - alternative spelling of quantisationTable
224
277
  * @param {boolean} [options.force=true] - force JPEG output, otherwise attempt to use input format
225
278
  * @returns {Sharp}
226
279
  * @throws {Error} Invalid options
@@ -244,6 +297,23 @@ function jpeg (options) {
244
297
  throw is.invalidParameterError('chromaSubsampling', 'one of: 4:2:0, 4:4:4', options.chromaSubsampling);
245
298
  }
246
299
  }
300
+ const optimiseCoding = is.bool(options.optimizeCoding) ? options.optimizeCoding : options.optimiseCoding;
301
+ if (is.defined(optimiseCoding)) {
302
+ this._setBooleanOption('jpegOptimiseCoding', optimiseCoding);
303
+ }
304
+ if (is.defined(options.mozjpeg)) {
305
+ if (is.bool(options.mozjpeg)) {
306
+ if (options.mozjpeg) {
307
+ this.options.jpegTrellisQuantisation = true;
308
+ this.options.jpegOvershootDeringing = true;
309
+ this.options.jpegOptimiseScans = true;
310
+ this.options.jpegProgressive = true;
311
+ this.options.jpegQuantisationTable = 3;
312
+ }
313
+ } else {
314
+ throw is.invalidParameterError('mozjpeg', 'boolean', options.mozjpeg);
315
+ }
316
+ }
247
317
  const trellisQuantisation = is.bool(options.trellisQuantization) ? options.trellisQuantization : options.trellisQuantisation;
248
318
  if (is.defined(trellisQuantisation)) {
249
319
  this._setBooleanOption('jpegTrellisQuantisation', trellisQuantisation);
@@ -258,10 +328,6 @@ function jpeg (options) {
258
328
  this.options.jpegProgressive = true;
259
329
  }
260
330
  }
261
- const optimiseCoding = is.bool(options.optimizeCoding) ? options.optimizeCoding : options.optimiseCoding;
262
- if (is.defined(optimiseCoding)) {
263
- this._setBooleanOption('jpegOptimiseCoding', optimiseCoding);
264
- }
265
331
  const quantisationTable = is.number(options.quantizationTable) ? options.quantizationTable : options.quantisationTable;
266
332
  if (is.defined(quantisationTable)) {
267
333
  if (is.integer(quantisationTable) && is.inRange(quantisationTable, 0, 8)) {
@@ -277,26 +343,31 @@ function jpeg (options) {
277
343
  /**
278
344
  * Use these PNG options for output image.
279
345
  *
280
- * PNG output is always full colour at 8 or 16 bits per pixel.
346
+ * By default, PNG output is full colour at 8 or 16 bits per pixel.
281
347
  * Indexed PNG input at 1, 2 or 4 bits per pixel is converted to 8 bits per pixel.
282
- *
283
- * Some of these options require the use of a globally-installed libvips compiled with support for libimagequant (GPL).
348
+ * Set `palette` to `true` for slower, indexed PNG output.
284
349
  *
285
350
  * @example
286
- * // Convert any input to PNG output
351
+ * // Convert any input to full colour PNG output
287
352
  * const data = await sharp(input)
288
353
  * .png()
289
354
  * .toBuffer();
290
355
  *
356
+ * @example
357
+ * // Convert any input to indexed PNG output (slower)
358
+ * const data = await sharp(input)
359
+ * .png({ palette: true })
360
+ * .toBuffer();
361
+ *
291
362
  * @param {Object} [options]
292
363
  * @param {boolean} [options.progressive=false] - use progressive (interlace) scan
293
- * @param {number} [options.compressionLevel=9] - zlib compression level, 0-9
364
+ * @param {number} [options.compressionLevel=6] - zlib compression level, 0 (fastest, largest) to 9 (slowest, smallest)
294
365
  * @param {boolean} [options.adaptiveFiltering=false] - use adaptive row filtering
295
- * @param {boolean} [options.palette=false] - quantise to a palette-based image with alpha transparency support, requires libvips compiled with support for libimagequant
296
- * @param {number} [options.quality=100] - use the lowest number of colours needed to achieve given quality, sets `palette` to `true`, requires libvips compiled with support for libimagequant
297
- * @param {number} [options.colours=256] - maximum number of palette entries, sets `palette` to `true`, requires libvips compiled with support for libimagequant
298
- * @param {number} [options.colors=256] - alternative spelling of `options.colours`, sets `palette` to `true`, requires libvips compiled with support for libimagequant
299
- * @param {number} [options.dither=1.0] - level of Floyd-Steinberg error diffusion, sets `palette` to `true`, requires libvips compiled with support for libimagequant
366
+ * @param {boolean} [options.palette=false] - quantise to a palette-based image with alpha transparency support
367
+ * @param {number} [options.quality=100] - use the lowest number of colours needed to achieve given quality, sets `palette` to `true`
368
+ * @param {number} [options.colours=256] - maximum number of palette entries, sets `palette` to `true`
369
+ * @param {number} [options.colors=256] - alternative spelling of `options.colours`, sets `palette` to `true`
370
+ * @param {number} [options.dither=1.0] - level of Floyd-Steinberg error diffusion, sets `palette` to `true`
300
371
  * @param {boolean} [options.force=true] - force PNG output, otherwise attempt to use input format
301
372
  * @returns {Sharp}
302
373
  * @throws {Error} Invalid options
@@ -358,6 +429,12 @@ function png (options) {
358
429
  * .webp({ lossless: true })
359
430
  * .toBuffer();
360
431
  *
432
+ * @example
433
+ * // Optimise the file size of an animated WebP
434
+ * const outputWebp = await sharp(inputWebp, { animated: true })
435
+ * .webp({ reductionEffort: 6 })
436
+ * .toBuffer();
437
+ *
361
438
  * @param {Object} [options] - output options
362
439
  * @param {number} [options.quality=80] - quality, integer 1-100
363
440
  * @param {number} [options.alphaQuality=100] - quality of alpha layer, integer 0-100
@@ -426,7 +503,7 @@ function webp (options) {
426
503
  /* istanbul ignore next */
427
504
  function gif (options) {
428
505
  if (!this.constructor.format.magick.output.buffer) {
429
- throw new Error('The gif operation requires libvips to have been installed with support for ImageMagick');
506
+ throw errMagickSave;
430
507
  }
431
508
  trySetAnimationOptions(options, this.options);
432
509
  return this._updateFormatOut('gif', options);
@@ -582,7 +659,7 @@ function tiff (options) {
582
659
  * @param {Object} [options] - output options
583
660
  * @param {number} [options.quality=50] - quality, integer 1-100
584
661
  * @param {boolean} [options.lossless=false] - use lossless compression
585
- * @param {boolean} [options.speed=5] - CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest)
662
+ * @param {number} [options.speed=5] - CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest)
586
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
587
664
  * @returns {Sharp}
588
665
  * @throws {Error} Invalid options
@@ -704,6 +781,7 @@ function raw () {
704
781
  * @param {string} [options.layout='dz'] filesystem layout, possible values are `dz`, `iiif`, `zoomify` or `google`.
705
782
  * @param {boolean} [options.centre=false] centre image in tile.
706
783
  * @param {boolean} [options.center=false] alternative spelling of centre.
784
+ * @param {string} [options.id='https://example.com/iiif'] when `layout` is `iiif`, sets the `@id` attribute of `info.json`
707
785
  * @returns {Sharp}
708
786
  * @throws {Error} Invalid parameters
709
787
  */
@@ -777,6 +855,14 @@ function tile (options) {
777
855
  if (is.defined(centre)) {
778
856
  this._setBooleanOption('tileCentre', centre);
779
857
  }
858
+ // @id attribute for IIIF layout
859
+ if (is.defined(options.id)) {
860
+ if (is.string(options.id)) {
861
+ this.options.tileId = options.id;
862
+ } else {
863
+ throw is.invalidParameterError('id', 'string', options.id);
864
+ }
865
+ }
780
866
  }
781
867
  // Format
782
868
  if (is.inArray(this.options.formatOut, ['jpeg', 'png', 'webp'])) {
package/lib/resize.js CHANGED
@@ -302,11 +302,20 @@ function resize (width, height, options) {
302
302
  * })
303
303
  * ...
304
304
  *
305
+ * @example
306
+ * // Add a row of 10 red pixels to the bottom
307
+ * sharp(input)
308
+ * .extend({
309
+ * bottom: 10,
310
+ * background: 'red'
311
+ * })
312
+ * ...
313
+ *
305
314
  * @param {(number|Object)} extend - single pixel count to add to all edges or an Object with per-edge counts
306
- * @param {number} [extend.top]
307
- * @param {number} [extend.left]
308
- * @param {number} [extend.bottom]
309
- * @param {number} [extend.right]
315
+ * @param {number} [extend.top=0]
316
+ * @param {number} [extend.left=0]
317
+ * @param {number} [extend.bottom=0]
318
+ * @param {number} [extend.right=0]
310
319
  * @param {String|Object} [extend.background={r: 0, g: 0, b: 0, alpha: 1}] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black without transparency.
311
320
  * @returns {Sharp}
312
321
  * @throws {Error} Invalid parameters
@@ -317,17 +326,35 @@ function extend (extend) {
317
326
  this.options.extendBottom = extend;
318
327
  this.options.extendLeft = extend;
319
328
  this.options.extendRight = extend;
320
- } else if (
321
- is.object(extend) &&
322
- is.integer(extend.top) && extend.top >= 0 &&
323
- is.integer(extend.bottom) && extend.bottom >= 0 &&
324
- is.integer(extend.left) && extend.left >= 0 &&
325
- is.integer(extend.right) && extend.right >= 0
326
- ) {
327
- this.options.extendTop = extend.top;
328
- this.options.extendBottom = extend.bottom;
329
- this.options.extendLeft = extend.left;
330
- this.options.extendRight = extend.right;
329
+ } else if (is.object(extend)) {
330
+ if (is.defined(extend.top)) {
331
+ if (is.integer(extend.top) && extend.top >= 0) {
332
+ this.options.extendTop = extend.top;
333
+ } else {
334
+ throw is.invalidParameterError('top', 'positive integer', extend.top);
335
+ }
336
+ }
337
+ if (is.defined(extend.bottom)) {
338
+ if (is.integer(extend.bottom) && extend.bottom >= 0) {
339
+ this.options.extendBottom = extend.bottom;
340
+ } else {
341
+ throw is.invalidParameterError('bottom', 'positive integer', extend.bottom);
342
+ }
343
+ }
344
+ if (is.defined(extend.left)) {
345
+ if (is.integer(extend.left) && extend.left >= 0) {
346
+ this.options.extendLeft = extend.left;
347
+ } else {
348
+ throw is.invalidParameterError('left', 'positive integer', extend.left);
349
+ }
350
+ }
351
+ if (is.defined(extend.right)) {
352
+ if (is.integer(extend.right) && extend.right >= 0) {
353
+ this.options.extendRight = extend.right;
354
+ } else {
355
+ throw is.invalidParameterError('right', 'positive integer', extend.right);
356
+ }
357
+ }
331
358
  this._setBackgroundColourOption('extendBackground', extend.background);
332
359
  } else {
333
360
  throw is.invalidParameterError('extend', 'integer or object', extend);
package/lib/utility.js CHANGED
@@ -1,6 +1,8 @@
1
1
  'use strict';
2
2
 
3
3
  const events = require('events');
4
+ const detectLibc = require('detect-libc');
5
+
4
6
  const is = require('./is');
5
7
  const sharp = require('../build/Release/sharp.node');
6
8
 
@@ -84,8 +86,12 @@ cache(true);
84
86
  /**
85
87
  * Gets or, when a concurrency is provided, sets
86
88
  * the number of threads _libvips'_ should create to process each image.
87
- * The default value is the number of CPU cores.
88
- * A value of `0` will reset to this default.
89
+ *
90
+ * The default value is the number of CPU cores,
91
+ * except when using glibc-based Linux without jemalloc,
92
+ * where the default is `1` to help reduce memory fragmentation.
93
+ *
94
+ * A value of `0` will reset this to the number of CPU cores.
89
95
  *
90
96
  * The maximum number of images that can be processed in parallel
91
97
  * is limited by libuv's `UV_THREADPOOL_SIZE` environment variable.
@@ -103,6 +109,11 @@ cache(true);
103
109
  function concurrency (concurrency) {
104
110
  return sharp.concurrency(is.integer(concurrency) ? concurrency : null);
105
111
  }
112
+ /* istanbul ignore next */
113
+ if (detectLibc.family === detectLibc.GLIBC && !sharp._isUsingJemalloc()) {
114
+ // Reduce default concurrency to 1 when using glibc memory allocator
115
+ sharp.concurrency(1);
116
+ }
106
117
 
107
118
  /**
108
119
  * An EventEmitter that emits a `change` event when a task is either:
@@ -157,14 +168,10 @@ simd(true);
157
168
  * @private
158
169
  */
159
170
  module.exports = function (Sharp) {
160
- [
161
- cache,
162
- concurrency,
163
- counters,
164
- simd
165
- ].forEach(function (f) {
166
- Sharp[f.name] = f;
167
- });
171
+ Sharp.cache = cache;
172
+ Sharp.concurrency = concurrency;
173
+ Sharp.counters = counters;
174
+ Sharp.simd = simd;
168
175
  Sharp.format = format;
169
176
  Sharp.interpolators = interpolators;
170
177
  Sharp.versions = versions;
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.27.1",
4
+ "version": "0.28.2",
5
5
  "author": "Lovell Fuller <npm@lovell.info>",
6
6
  "homepage": "https://github.com/lovell/sharp",
7
7
  "contributors": [
@@ -74,13 +74,16 @@
74
74
  "Christian Flintrup <chr@gigahost.dk>",
75
75
  "Manan Jadhav <manan@motionden.com>",
76
76
  "Leon Radley <leon@radley.se>",
77
- "alza54 <alza54@thiocod.in>"
77
+ "alza54 <alza54@thiocod.in>",
78
+ "Jacob Smith <jacob@frende.me>",
79
+ "Michael Nutt <michael@nutt.im>"
78
80
  ],
79
81
  "scripts": {
80
82
  "install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node-gyp rebuild && node install/dll-copy)",
81
83
  "clean": "rm -rf node_modules/ build/ vendor/ .nyc_output/ coverage/ test/fixtures/output.*",
82
- "test": "semistandard && cpplint && npm run test-unit && npm run test-licensing",
83
- "test-unit": "nyc --reporter=lcov --branches=99 mocha --parallel --slow=5000 --timeout=60000 ./test/unit/*.js",
84
+ "test": "npm run test-lint && npm run test-unit && npm run test-licensing",
85
+ "test-lint": "semistandard && cpplint",
86
+ "test-unit": "nyc --reporter=lcov --branches=99 mocha --slow=1000 --timeout=60000 ./test/unit/*.js",
84
87
  "test-licensing": "license-checker --production --summary --onlyAllow=\"Apache-2.0;BSD;ISC;MIT\"",
85
88
  "test-coverage": "./test/coverage/report.sh",
86
89
  "test-leak": "./test/leak/leak.sh",
@@ -117,14 +120,12 @@
117
120
  "vips"
118
121
  ],
119
122
  "dependencies": {
120
- "array-flatten": "^3.0.0",
121
123
  "color": "^3.1.3",
122
124
  "detect-libc": "^1.0.3",
123
125
  "node-addon-api": "^3.1.0",
124
- "npmlog": "^4.1.2",
125
- "prebuild-install": "^6.0.0",
126
- "semver": "^7.3.4",
127
- "simple-get": "^4.0.0",
126
+ "prebuild-install": "^6.1.2",
127
+ "semver": "^7.3.5",
128
+ "simple-get": "^3.1.0",
128
129
  "tar-fs": "^2.1.1",
129
130
  "tunnel-agent": "^0.6.0"
130
131
  },
@@ -132,12 +133,12 @@
132
133
  "async": "^3.2.0",
133
134
  "cc": "^3.0.1",
134
135
  "decompress-zip": "^0.3.3",
135
- "documentation": "^13.1.1",
136
+ "documentation": "^13.2.5",
136
137
  "exif-reader": "^1.0.3",
137
138
  "icc": "^2.0.0",
138
139
  "license-checker": "^25.0.1",
139
- "mocha": "^8.2.1",
140
- "mock-fs": "^4.13.0",
140
+ "mocha": "^8.4.0",
141
+ "mock-fs": "^4.14.0",
141
142
  "nyc": "^15.1.0",
142
143
  "prebuild": "^10.0.1",
143
144
  "rimraf": "^3.0.2",
@@ -145,7 +146,7 @@
145
146
  },
146
147
  "license": "Apache-2.0",
147
148
  "config": {
148
- "libvips": "8.10.5",
149
+ "libvips": "8.10.6",
149
150
  "runtime": "napi",
150
151
  "target": 3
151
152
  },
package/src/common.cc CHANGED
@@ -36,6 +36,9 @@ namespace sharp {
36
36
  std::string AttrAsStr(Napi::Object obj, std::string attr) {
37
37
  return obj.Get(attr).As<Napi::String>();
38
38
  }
39
+ std::string AttrAsStr(Napi::Object obj, unsigned int const attr) {
40
+ return obj.Get(attr).As<Napi::String>();
41
+ }
39
42
  uint32_t AttrAsUint32(Napi::Object obj, std::string attr) {
40
43
  return obj.Get(attr).As<Napi::Number>().Uint32Value();
41
44
  }
@@ -92,6 +95,7 @@ namespace sharp {
92
95
  descriptor->rawChannels = AttrAsUint32(input, "rawChannels");
93
96
  descriptor->rawWidth = AttrAsUint32(input, "rawWidth");
94
97
  descriptor->rawHeight = AttrAsUint32(input, "rawHeight");
98
+ descriptor->rawPremultiplied = AttrAsBool(input, "rawPremultiplied");
95
99
  }
96
100
  // Multi-page input (GIF, TIFF, PDF)
97
101
  if (HasAttr(input, "pages")) {
@@ -104,6 +108,10 @@ namespace sharp {
104
108
  if (HasAttr(input, "level")) {
105
109
  descriptor->level = AttrAsUint32(input, "level");
106
110
  }
111
+ // subIFD (OME-TIFF)
112
+ if (HasAttr(input, "subifd")) {
113
+ descriptor->subifd = AttrAsInt32(input, "subifd");
114
+ }
107
115
  // Create new image
108
116
  if (HasAttr(input, "createChannels")) {
109
117
  descriptor->createChannels = AttrAsUint32(input, "createChannels");
@@ -213,6 +221,8 @@ namespace sharp {
213
221
  { "VipsForeignLoadTiffBuffer", ImageType::TIFF },
214
222
  { "VipsForeignLoadGifFile", ImageType::GIF },
215
223
  { "VipsForeignLoadGifBuffer", ImageType::GIF },
224
+ { "VipsForeignLoadNsgifFile", ImageType::GIF },
225
+ { "VipsForeignLoadNsgifBuffer", ImageType::GIF },
216
226
  { "VipsForeignLoadSvgFile", ImageType::SVG },
217
227
  { "VipsForeignLoadSvgBuffer", ImageType::SVG },
218
228
  { "VipsForeignLoadHeifFile", ImageType::HEIF },
@@ -226,6 +236,7 @@ namespace sharp {
226
236
  { "VipsForeignLoadFits", ImageType::FITS },
227
237
  { "VipsForeignLoadOpenexr", ImageType::EXR },
228
238
  { "VipsForeignLoadVips", ImageType::VIPS },
239
+ { "VipsForeignLoadVipsFile", ImageType::VIPS },
229
240
  { "VipsForeignLoadRaw", ImageType::RAW }
230
241
  };
231
242
 
@@ -292,6 +303,9 @@ namespace sharp {
292
303
  } else {
293
304
  image.get_image()->Type = VIPS_INTERPRETATION_sRGB;
294
305
  }
306
+ if (descriptor->rawPremultiplied) {
307
+ image = image.unpremultiply();
308
+ }
295
309
  imageType = ImageType::RAW;
296
310
  } else {
297
311
  // Compressed data
@@ -317,6 +331,9 @@ namespace sharp {
317
331
  if (imageType == ImageType::OPENSLIDE) {
318
332
  option->set("level", descriptor->level);
319
333
  }
334
+ if (imageType == ImageType::TIFF) {
335
+ option->set("subifd", descriptor->subifd);
336
+ }
320
337
  image = VImage::new_from_buffer(descriptor->buffer, descriptor->bufferLength, nullptr, option);
321
338
  if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
322
339
  image = SetDensity(image, descriptor->density);
@@ -389,6 +406,9 @@ namespace sharp {
389
406
  if (imageType == ImageType::OPENSLIDE) {
390
407
  option->set("level", descriptor->level);
391
408
  }
409
+ if (imageType == ImageType::TIFF) {
410
+ option->set("subifd", descriptor->subifd);
411
+ }
392
412
  image = VImage::new_from_file(descriptor->file.data(), option);
393
413
  if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
394
414
  image = SetDensity(image, descriptor->density);
@@ -421,12 +441,7 @@ namespace sharp {
421
441
  Uses colour space interpretation with number of channels to guess this.
422
442
  */
423
443
  bool HasAlpha(VImage image) {
424
- int const bands = image.bands();
425
- VipsInterpretation const interpretation = image.interpretation();
426
- return (
427
- (bands == 2 && interpretation == VIPS_INTERPRETATION_B_W) ||
428
- (bands == 4 && interpretation != VIPS_INTERPRETATION_CMYK) ||
429
- (bands == 5 && interpretation == VIPS_INTERPRETATION_CMYK));
444
+ return image.has_alpha();
430
445
  }
431
446
 
432
447
  /*
@@ -510,9 +525,8 @@ namespace sharp {
510
525
  VImage SetDensity(VImage image, const double density) {
511
526
  const double pixelsPerMm = density / 25.4;
512
527
  VImage copy = image.copy();
513
- copy.set("Xres", pixelsPerMm);
514
- copy.set("Yres", pixelsPerMm);
515
- copy.set(VIPS_META_RESOLUTION_UNIT, "in");
528
+ copy.get_image()->Xres = pixelsPerMm;
529
+ copy.get_image()->Yres = pixelsPerMm;
516
530
  return copy;
517
531
  }
518
532
 
@@ -802,10 +816,10 @@ namespace sharp {
802
816
  /*
803
817
  Ensures alpha channel, if missing.
804
818
  */
805
- VImage EnsureAlpha(VImage image) {
819
+ VImage EnsureAlpha(VImage image, double const value) {
806
820
  if (!HasAlpha(image)) {
807
821
  std::vector<double> alpha;
808
- alpha.push_back(sharp::MaximumImageAlpha(image.interpretation()));
822
+ alpha.push_back(value * sharp::MaximumImageAlpha(image.interpretation()));
809
823
  image = image.bandjoin_const(alpha);
810
824
  }
811
825
  return image;