sharp 0.25.3 → 0.25.4

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/binding.gyp CHANGED
@@ -29,10 +29,22 @@
29
29
  'Release': {
30
30
  'msvs_settings': {
31
31
  'VCCLCompilerTool': {
32
- 'ExceptionHandling': 1
32
+ 'ExceptionHandling': 1,
33
+ 'WholeProgramOptimization': 'true'
34
+ },
35
+ 'VCLibrarianTool': {
36
+ 'AdditionalOptions': [
37
+ '/LTCG:INCREMENTAL'
38
+ ]
33
39
  },
34
40
  'VCLinkerTool': {
35
- 'ImageHasSafeExceptionHandlers': 'false'
41
+ 'ImageHasSafeExceptionHandlers': 'false',
42
+ 'OptimizeReferences': 2,
43
+ 'EnableCOMDATFolding': 2,
44
+ 'LinkIncremental': 1,
45
+ 'AdditionalOptions': [
46
+ '/LTCG:INCREMENTAL'
47
+ ]
36
48
  }
37
49
  },
38
50
  'msvs_disabled_warnings': [
@@ -121,7 +133,7 @@
121
133
  '../vendor/lib/libglib-2.0.0.dylib',
122
134
  '../vendor/lib/libgobject-2.0.0.dylib',
123
135
  # Ensure runtime linking is relative to sharp.node
124
- '-rpath \'@loader_path/../../vendor/lib\''
136
+ '-Wl,-s -rpath \'@loader_path/../../vendor/lib\''
125
137
  ]
126
138
  }],
127
139
  ['OS == "linux"', {
@@ -164,7 +176,7 @@
164
176
  '../vendor/lib/libxml2.so',
165
177
  '../vendor/lib/libz.so',
166
178
  # Ensure runtime linking is relative to sharp.node
167
- '-Wl,--disable-new-dtags -Wl,-rpath=\'$${ORIGIN}/../../vendor/lib\''
179
+ '-Wl,-s -Wl,--disable-new-dtags -Wl,-rpath=\'$${ORIGIN}/../../vendor/lib\''
168
180
  ]
169
181
  }]
170
182
  ]
@@ -204,10 +216,22 @@
204
216
  ['OS == "win"', {
205
217
  'msvs_settings': {
206
218
  'VCCLCompilerTool': {
207
- 'ExceptionHandling': 1
219
+ 'ExceptionHandling': 1,
220
+ 'WholeProgramOptimization': 'true'
221
+ },
222
+ 'VCLibrarianTool': {
223
+ 'AdditionalOptions': [
224
+ '/LTCG:INCREMENTAL'
225
+ ]
208
226
  },
209
227
  'VCLinkerTool': {
210
- 'ImageHasSafeExceptionHandlers': 'false'
228
+ 'ImageHasSafeExceptionHandlers': 'false',
229
+ 'OptimizeReferences': 2,
230
+ 'EnableCOMDATFolding': 2,
231
+ 'LinkIncremental': 1,
232
+ 'AdditionalOptions': [
233
+ '/LTCG:INCREMENTAL'
234
+ ]
211
235
  }
212
236
  },
213
237
  'msvs_disabled_warnings': [
@@ -21,7 +21,8 @@ const minimumGlibcVersionByArch = {
21
21
  };
22
22
 
23
23
  const { minimumLibvipsVersion, minimumLibvipsVersionLabelled } = libvips;
24
- const distBaseUrl = process.env.npm_config_sharp_dist_base_url || process.env.SHARP_DIST_BASE_URL || `https://github.com/lovell/sharp-libvips/releases/download/v${minimumLibvipsVersionLabelled}/`;
24
+ const distHost = process.env.npm_config_sharp_libvips_binary_host || 'https://github.com/lovell/sharp-libvips/releases/download';
25
+ const distBaseUrl = process.env.npm_config_sharp_dist_base_url || process.env.SHARP_DIST_BASE_URL || `${distHost}/v${minimumLibvipsVersionLabelled}/`;
25
26
 
26
27
  const fail = function (err) {
27
28
  npmLog.error('sharp', err.message);
package/lib/channel.js CHANGED
@@ -60,22 +60,19 @@ function ensureAlpha () {
60
60
  * // green.jpg is a greyscale image containing the green channel of the input
61
61
  * });
62
62
  *
63
- * @param {number|string} channel - zero-indexed band number to extract, or `red`, `green` or `blue` as alternative to `0`, `1` or `2` respectively.
63
+ * @param {number|string} channel - zero-indexed channel/band number to extract, or `red`, `green`, `blue` or `alpha`.
64
64
  * @returns {Sharp}
65
65
  * @throws {Error} Invalid channel
66
66
  */
67
67
  function extractChannel (channel) {
68
- if (channel === 'red') {
69
- channel = 0;
70
- } else if (channel === 'green') {
71
- channel = 1;
72
- } else if (channel === 'blue') {
73
- channel = 2;
68
+ const channelMap = { red: 0, green: 1, blue: 2, alpha: 3 };
69
+ if (Object.keys(channelMap).includes(channel)) {
70
+ channel = channelMap[channel];
74
71
  }
75
72
  if (is.integer(channel) && is.inRange(channel, 0, 4)) {
76
73
  this.options.extractChannel = channel;
77
74
  } else {
78
- throw is.invalidParameterError('channel', 'integer or one of: red, green, blue', channel);
75
+ throw is.invalidParameterError('channel', 'integer or one of: red, green, blue, alpha', channel);
79
76
  }
80
77
  return this;
81
78
  }
@@ -38,15 +38,20 @@ try {
38
38
  const debuglog = util.debuglog('sharp');
39
39
 
40
40
  /**
41
- * @constructs sharp
42
- *
43
41
  * Constructor factory to create an instance of `sharp`, to which further methods are chained.
44
42
  *
45
43
  * JPEG, PNG, WebP or TIFF format image data can be streamed out from this object.
46
44
  * When using Stream based output, derived attributes are available from the `info` event.
47
45
  *
46
+ * Non-critical problems encountered during processing are emitted as `warning` events.
47
+ *
48
48
  * Implements the [stream.Duplex](http://nodejs.org/api/stream.html#stream_class_stream_duplex) class.
49
49
  *
50
+ * @constructs Sharp
51
+ *
52
+ * @emits Sharp#info
53
+ * @emits Sharp#warning
54
+ *
50
55
  * @example
51
56
  * sharp('input.jpg')
52
57
  * .resize(300, 200)
@@ -81,30 +86,31 @@ const debuglog = util.debuglog('sharp');
81
86
  * .toBuffer()
82
87
  * .then( ... );
83
88
  *
84
- * @param {(Buffer|String)} [input] - if present, can be
89
+ * @param {(Buffer|string)} [input] - if present, can be
85
90
  * a Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or
86
91
  * a String containing the filesystem path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file.
87
92
  * JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present.
88
93
  * @param {Object} [options] - if present, is an Object with optional attributes.
89
- * @param {Boolean} [options.failOnError=true] - by default halt processing and raise an error when loading invalid images.
94
+ * @param {boolean} [options.failOnError=true] - by default halt processing and raise an error when loading invalid images.
90
95
  * Set this flag to `false` if you'd rather apply a "best effort" to decode images, even if the data is corrupt or invalid.
91
- * @param {Number|Boolean} [options.limitInputPixels=268402689] - Do not process input images where the number of pixels
96
+ * @param {number|boolean} [options.limitInputPixels=268402689] - Do not process input images where the number of pixels
92
97
  * (width x height) exceeds this limit. Assumes image dimensions contained in the input metadata can be trusted.
93
98
  * An integral Number of pixels, zero or false to remove limit, true to use default limit of 268402689 (0x3FFF x 0x3FFF).
94
- * @param {Boolean} [options.sequentialRead=false] - Set this to `true` to use sequential rather than random access where possible.
99
+ * @param {boolean} [options.sequentialRead=false] - Set this to `true` to use sequential rather than random access where possible.
95
100
  * This can reduce memory usage and might improve performance on some systems.
96
- * @param {Number} [options.density=72] - number representing the DPI for vector images.
97
- * @param {Number} [options.pages=1] - number of pages to extract for multi-page input (GIF, TIFF, PDF), use -1 for all pages.
98
- * @param {Number} [options.page=0] - page number to start extracting from for multi-page input (GIF, TIFF, PDF), zero based.
101
+ * @param {number} [options.density=72] - number representing the DPI for vector images.
102
+ * @param {number} [options.pages=1] - number of pages to extract for multi-page input (GIF, TIFF, PDF), use -1 for all pages.
103
+ * @param {number} [options.page=0] - page number to start extracting from for multi-page input (GIF, TIFF, PDF), zero based.
104
+ * @param {number} [options.level=0] - level to extract from a multi-level input (OpenSlide), zero based.
99
105
  * @param {Object} [options.raw] - describes raw pixel input image data. See `raw()` for pixel ordering.
100
- * @param {Number} [options.raw.width]
101
- * @param {Number} [options.raw.height]
102
- * @param {Number} [options.raw.channels] - 1-4
106
+ * @param {number} [options.raw.width]
107
+ * @param {number} [options.raw.height]
108
+ * @param {number} [options.raw.channels] - 1-4
103
109
  * @param {Object} [options.create] - describes a new image to be created.
104
- * @param {Number} [options.create.width]
105
- * @param {Number} [options.create.height]
106
- * @param {Number} [options.create.channels] - 3-4
107
- * @param {String|Object} [options.create.background] - parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
110
+ * @param {number} [options.create.width]
111
+ * @param {number} [options.create.height]
112
+ * @param {number} [options.create.channels] - 3-4
113
+ * @param {string|Object} [options.create.background] - parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
108
114
  * @returns {Sharp}
109
115
  * @throws {Error} Invalid parameters
110
116
  */
@@ -229,7 +235,10 @@ const Sharp = function (input, options) {
229
235
  linearA: 1,
230
236
  linearB: 0,
231
237
  // Function to notify of libvips warnings
232
- debuglog: debuglog,
238
+ debuglog: warning => {
239
+ this.emit('warning', warning);
240
+ debuglog(warning);
241
+ },
233
242
  // Function to notify of queue length changes
234
243
  queueListener: function (queueLength) {
235
244
  Sharp.queue.emit('change', queueLength);
package/lib/input.js CHANGED
@@ -113,6 +113,14 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
113
113
  throw is.invalidParameterError('page', 'integer between 0 and 100000', inputOptions.page);
114
114
  }
115
115
  }
116
+ // Multi-level input (OpenSlide)
117
+ if (is.defined(inputOptions.level)) {
118
+ if (is.integer(inputOptions.level) && is.inRange(inputOptions.level, 0, 256)) {
119
+ inputDescriptor.level = inputOptions.level;
120
+ } else {
121
+ throw is.invalidParameterError('level', 'integer between 0 and 256', inputOptions.level);
122
+ }
123
+ }
116
124
  // Create new image
117
125
  if (is.defined(inputOptions.create)) {
118
126
  if (
@@ -208,6 +216,7 @@ function _isStreamInput () {
208
216
  * - `loop`: Number of times to loop an animated image, zero refers to a continuous loop.
209
217
  * - `delay`: Delay in ms between each page in an animated image, provided as an array of integers.
210
218
  * - `pagePrimary`: Number of the primary page in a HEIF image
219
+ * - `levels`: Details of each level in a multi-level image provided as an array of objects, requires libvips compiled with support for OpenSlide
211
220
  * - `hasProfile`: Boolean indicating the presence of an embedded ICC profile
212
221
  * - `hasAlpha`: Boolean indicating the presence of an alpha transparency channel
213
222
  * - `orientation`: Number value of the EXIF Orientation header, if present
@@ -290,6 +299,7 @@ function metadata (callback) {
290
299
  * - `maxY` (y-coordinate of one of the pixel where the maximum lies)
291
300
  * - `isOpaque`: Is the image fully opaque? Will be `true` if the image has no alpha channel or if every pixel is fully opaque.
292
301
  * - `entropy`: Histogram-based estimation of greyscale entropy, discarding alpha channel if any (experimental)
302
+ * - `sharpness`: Estimation of greyscale sharpness based on the standard deviation of a Laplacian convolution, discarding alpha channel if any (experimental)
293
303
  *
294
304
  * @example
295
305
  * const image = sharp(inputJpg);
@@ -299,6 +309,9 @@ function metadata (callback) {
299
309
  * // stats contains the channel-wise statistics array and the isOpaque value
300
310
  * });
301
311
  *
312
+ * @example
313
+ * const { entropy, sharpness } = await sharp(input).stats();
314
+ *
302
315
  * @param {Function} [callback] - called with the arguments `(err, stats)`
303
316
  * @returns {Promise<Object>}
304
317
  */
package/lib/is.js CHANGED
@@ -21,7 +21,7 @@ const object = function (val) {
21
21
  * @private
22
22
  */
23
23
  const plainObject = function (val) {
24
- return object(val) && Object.prototype.toString.call(val) === '[object Object]';
24
+ return Object.prototype.toString.call(val) === '[object Object]';
25
25
  };
26
26
 
27
27
  /**
@@ -45,7 +45,7 @@ const bool = function (val) {
45
45
  * @private
46
46
  */
47
47
  const buffer = function (val) {
48
- return object(val) && val instanceof Buffer;
48
+ return val instanceof Buffer;
49
49
  };
50
50
 
51
51
  /**
@@ -69,7 +69,7 @@ const number = function (val) {
69
69
  * @private
70
70
  */
71
71
  const integer = function (val) {
72
- return number(val) && val % 1 === 0;
72
+ return Number.isInteger(val);
73
73
  };
74
74
 
75
75
  /**
@@ -85,7 +85,7 @@ const inRange = function (val, min, max) {
85
85
  * @private
86
86
  */
87
87
  const inArray = function (val, list) {
88
- return list.indexOf(val) !== -1;
88
+ return list.includes(val);
89
89
  };
90
90
 
91
91
  /**
package/lib/libvips.js CHANGED
@@ -65,7 +65,7 @@ const hasVendoredLibvips = function () {
65
65
  if (currentPlatformId === vendorPlatformId) {
66
66
  return true;
67
67
  } else {
68
- throw new Error(`'${vendorPlatformId}' binaries cannot be used on the '${currentPlatformId}' platform. Please remove the 'node_modules/sharp/vendor' directory and run 'npm install'.`);
68
+ throw new Error(`'${vendorPlatformId}' binaries cannot be used on the '${currentPlatformId}' platform. Please remove the 'node_modules/sharp' directory and run 'npm install' on the '${currentPlatformId}' platform.`);
69
69
  }
70
70
  } else {
71
71
  return false;
package/lib/output.js CHANGED
@@ -173,6 +173,8 @@ function toFormat (format, options) {
173
173
  /**
174
174
  * Use these JPEG options for output image.
175
175
  *
176
+ * Some of these options require the use of a globally-installed libvips compiled with support for mozjpeg.
177
+ *
176
178
  * @example
177
179
  * // Convert any input to very high quality JPEG output
178
180
  * const data = await sharp(input)
@@ -186,14 +188,14 @@ function toFormat (format, options) {
186
188
  * @param {number} [options.quality=80] - quality, integer 1-100
187
189
  * @param {boolean} [options.progressive=false] - use progressive (interlace) scan
188
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
191
+ * @param {boolean} [options.optimiseCoding=true] - optimise Huffman coding tables
192
+ * @param {boolean} [options.optimizeCoding=true] - alternative spelling of optimiseCoding
189
193
  * @param {boolean} [options.trellisQuantisation=false] - apply trellis quantisation, requires libvips compiled with support for mozjpeg
190
194
  * @param {boolean} [options.overshootDeringing=false] - apply overshoot deringing, requires libvips compiled with support for mozjpeg
191
195
  * @param {boolean} [options.optimiseScans=false] - optimise progressive scans, forces progressive, requires libvips compiled with support for mozjpeg
192
- * @param {boolean} [options.optimizeScans=false] - alternative spelling of optimiseScans
193
- * @param {boolean} [options.optimiseCoding=true] - optimise Huffman coding tables
194
- * @param {boolean} [options.optimizeCoding=true] - alternative spelling of optimiseCoding
196
+ * @param {boolean} [options.optimizeScans=false] - alternative spelling of optimiseScans, requires libvips compiled with support for mozjpeg
195
197
  * @param {number} [options.quantisationTable=0] - quantization table to use, integer 0-8, requires libvips compiled with support for mozjpeg
196
- * @param {number} [options.quantizationTable=0] - alternative spelling of quantisationTable
198
+ * @param {number} [options.quantizationTable=0] - alternative spelling of quantisationTable, requires libvips compiled with support for mozjpeg
197
199
  * @param {boolean} [options.force=true] - force JPEG output, otherwise attempt to use input format
198
200
  * @returns {Sharp}
199
201
  * @throws {Error} Invalid options
@@ -253,6 +255,8 @@ function jpeg (options) {
253
255
  * PNG output is always full colour at 8 or 16 bits per pixel.
254
256
  * Indexed PNG input at 1, 2 or 4 bits per pixel is converted to 8 bits per pixel.
255
257
  *
258
+ * Some of these options require the use of a globally-installed libvips compiled with support for libimagequant (GPL).
259
+ *
256
260
  * @example
257
261
  * // Convert any input to PNG output
258
262
  * const data = await sharp(input)
@@ -264,10 +268,10 @@ function jpeg (options) {
264
268
  * @param {number} [options.compressionLevel=9] - zlib compression level, 0-9
265
269
  * @param {boolean} [options.adaptiveFiltering=false] - use adaptive row filtering
266
270
  * @param {boolean} [options.palette=false] - quantise to a palette-based image with alpha transparency support, requires libvips compiled with support for libimagequant
267
- * @param {number} [options.quality=100] - use the lowest number of colours needed to achieve given quality, requires libvips compiled with support for libimagequant
268
- * @param {number} [options.colours=256] - maximum number of palette entries, requires libvips compiled with support for libimagequant
269
- * @param {number} [options.colors=256] - alternative spelling of `options.colours`, requires libvips compiled with support for libimagequant
270
- * @param {number} [options.dither=1.0] - level of Floyd-Steinberg error diffusion, requires libvips compiled with support for libimagequant
271
+ * @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
272
+ * @param {number} [options.colours=256] - maximum number of palette entries, sets `palette` to `true`, requires libvips compiled with support for libimagequant
273
+ * @param {number} [options.colors=256] - alternative spelling of `options.colours`, sets `palette` to `true`, requires libvips compiled with support for libimagequant
274
+ * @param {number} [options.dither=1.0] - level of Floyd-Steinberg error diffusion, sets `palette` to `true`, requires libvips compiled with support for libimagequant
271
275
  * @param {boolean} [options.force=true] - force PNG output, otherwise attempt to use input format
272
276
  * @returns {Sharp}
273
277
  * @throws {Error} Invalid options
@@ -289,28 +293,30 @@ function png (options) {
289
293
  }
290
294
  if (is.defined(options.palette)) {
291
295
  this._setBooleanOption('pngPalette', options.palette);
292
- if (this.options.pngPalette) {
293
- if (is.defined(options.quality)) {
294
- if (is.integer(options.quality) && is.inRange(options.quality, 0, 100)) {
295
- this.options.pngQuality = options.quality;
296
- } else {
297
- throw is.invalidParameterError('quality', 'integer between 0 and 100', options.quality);
298
- }
296
+ } else if (is.defined(options.quality) || is.defined(options.colours || options.colors) || is.defined(options.dither)) {
297
+ this._setBooleanOption('pngPalette', true);
298
+ }
299
+ if (this.options.pngPalette) {
300
+ if (is.defined(options.quality)) {
301
+ if (is.integer(options.quality) && is.inRange(options.quality, 0, 100)) {
302
+ this.options.pngQuality = options.quality;
303
+ } else {
304
+ throw is.invalidParameterError('quality', 'integer between 0 and 100', options.quality);
299
305
  }
300
- const colours = options.colours || options.colors;
301
- if (is.defined(colours)) {
302
- if (is.integer(colours) && is.inRange(colours, 2, 256)) {
303
- this.options.pngColours = colours;
304
- } else {
305
- throw is.invalidParameterError('colours', 'integer between 2 and 256', colours);
306
- }
306
+ }
307
+ const colours = options.colours || options.colors;
308
+ if (is.defined(colours)) {
309
+ if (is.integer(colours) && is.inRange(colours, 2, 256)) {
310
+ this.options.pngColours = colours;
311
+ } else {
312
+ throw is.invalidParameterError('colours', 'integer between 2 and 256', colours);
307
313
  }
308
- if (is.defined(options.dither)) {
309
- if (is.number(options.dither) && is.inRange(options.dither, 0, 1)) {
310
- this.options.pngDither = options.dither;
311
- } else {
312
- throw is.invalidParameterError('dither', 'number between 0.0 and 1.0', options.dither);
313
- }
314
+ }
315
+ if (is.defined(options.dither)) {
316
+ if (is.number(options.dither) && is.inRange(options.dither, 0, 1)) {
317
+ this.options.pngDither = options.dither;
318
+ } else {
319
+ throw is.invalidParameterError('dither', 'number between 0.0 and 1.0', options.dither);
314
320
  }
315
321
  }
316
322
  }
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.3",
4
+ "version": "0.25.4",
5
5
  "author": "Lovell Fuller <npm@lovell.info>",
6
6
  "homepage": "https://github.com/lovell/sharp",
7
7
  "contributors": [
@@ -66,7 +66,8 @@
66
66
  "Paul Neave <paul.neave@gmail.com>",
67
67
  "Brendan Kennedy <brenwken@gmail.com>",
68
68
  "Brychan Bennett-Odlum <git@brychan.io>",
69
- "Edward Silverton <e.silverton@gmail.com>"
69
+ "Edward Silverton <e.silverton@gmail.com>",
70
+ "Roman Malieiev <aromaleev@gmail.com>"
70
71
  ],
71
72
  "scripts": {
72
73
  "install": "(node install/libvips && node install/dll-copy && prebuild-install --runtime=napi) || (node-gyp rebuild && node install/dll-copy)",
@@ -112,7 +113,7 @@
112
113
  "detect-libc": "^1.0.3",
113
114
  "node-addon-api": "^3.0.0",
114
115
  "npmlog": "^4.1.2",
115
- "prebuild-install": "^5.3.3",
116
+ "prebuild-install": "^5.3.4",
116
117
  "semver": "^7.3.2",
117
118
  "simple-get": "^4.0.0",
118
119
  "tar": "^6.0.2",
@@ -122,13 +123,13 @@
122
123
  "async": "^3.2.0",
123
124
  "cc": "^2.0.1",
124
125
  "decompress-zip": "^0.3.2",
125
- "documentation": "^13.0.0",
126
+ "documentation": "^13.0.1",
126
127
  "exif-reader": "^1.0.3",
127
128
  "icc": "^1.0.0",
128
129
  "license-checker": "^25.0.1",
129
- "mocha": "^7.1.2",
130
+ "mocha": "^8.0.1",
130
131
  "mock-fs": "^4.12.0",
131
- "nyc": "^15.0.1",
132
+ "nyc": "^15.1.0",
132
133
  "prebuild": "^10.0.0",
133
134
  "prebuild-ci": "^3.1.0",
134
135
  "rimraf": "^3.0.2",
package/src/common.cc CHANGED
@@ -88,6 +88,10 @@ namespace sharp {
88
88
  if (HasAttr(input, "page")) {
89
89
  descriptor->page = AttrAsUint32(input, "page");
90
90
  }
91
+ // Multi-level input (OpenSlide)
92
+ if (HasAttr(input, "level")) {
93
+ descriptor->level = AttrAsUint32(input, "level");
94
+ }
91
95
  // Create new image
92
96
  if (HasAttr(input, "createChannels")) {
93
97
  descriptor->createChannels = AttrAsUint32(input, "createChannels");
@@ -292,6 +296,9 @@ namespace sharp {
292
296
  option->set("n", descriptor->pages);
293
297
  option->set("page", descriptor->page);
294
298
  }
299
+ if (imageType == ImageType::OPENSLIDE) {
300
+ option->set("level", descriptor->level);
301
+ }
295
302
  image = VImage::new_from_buffer(descriptor->buffer, descriptor->bufferLength, nullptr, option);
296
303
  if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
297
304
  image = SetDensity(image, descriptor->density);
@@ -341,6 +348,9 @@ namespace sharp {
341
348
  option->set("n", descriptor->pages);
342
349
  option->set("page", descriptor->page);
343
350
  }
351
+ if (imageType == ImageType::OPENSLIDE) {
352
+ option->set("level", descriptor->level);
353
+ }
344
354
  image = VImage::new_from_file(descriptor->file.data(), option);
345
355
  if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
346
356
  image = SetDensity(image, descriptor->density);
package/src/common.h CHANGED
@@ -57,6 +57,7 @@ namespace sharp {
57
57
  int rawHeight;
58
58
  int pages;
59
59
  int page;
60
+ int level;
60
61
  int createChannels;
61
62
  int createWidth;
62
63
  int createHeight;
@@ -75,6 +76,7 @@ namespace sharp {
75
76
  rawHeight(0),
76
77
  pages(1),
77
78
  page(0),
79
+ level(0),
78
80
  createChannels(0),
79
81
  createWidth(0),
80
82
  createHeight(0),
package/src/metadata.cc CHANGED
@@ -74,6 +74,15 @@ class MetadataWorker : public Napi::AsyncWorker {
74
74
  if (image.get_typeof("heif-primary") == G_TYPE_INT) {
75
75
  baton->pagePrimary = image.get_int("heif-primary");
76
76
  }
77
+ if (image.get_typeof("openslide.level-count") == VIPS_TYPE_REF_STRING) {
78
+ int const levels = std::stoi(image.get_string("openslide.level-count"));
79
+ for (int l = 0; l < levels; l++) {
80
+ std::string prefix = "openslide.level[" + std::to_string(l) + "].";
81
+ int const width = std::stoi(image.get_string((prefix + "width").data()));
82
+ int const height = std::stoi(image.get_string((prefix + "height").data()));
83
+ baton->levels.push_back(std::pair<int, int>(width, height));
84
+ }
85
+ }
77
86
  baton->hasProfile = sharp::HasProfile(image);
78
87
  // Derived attributes
79
88
  baton->hasAlpha = sharp::HasAlpha(image);
@@ -177,6 +186,17 @@ class MetadataWorker : public Napi::AsyncWorker {
177
186
  if (baton->pagePrimary > -1) {
178
187
  info.Set("pagePrimary", baton->pagePrimary);
179
188
  }
189
+ if (!baton->levels.empty()) {
190
+ int i = 0;
191
+ Napi::Array levels = Napi::Array::New(env, static_cast<size_t>(baton->levels.size()));
192
+ for (std::pair<int, int> const l : baton->levels) {
193
+ Napi::Object level = Napi::Object::New(env);
194
+ level.Set("width", l.first);
195
+ level.Set("height", l.second);
196
+ levels.Set(i++, level);
197
+ }
198
+ info.Set("levels", levels);
199
+ }
180
200
  info.Set("hasProfile", baton->hasProfile);
181
201
  info.Set("hasAlpha", baton->hasAlpha);
182
202
  if (baton->orientation > 0) {
package/src/metadata.h CHANGED
@@ -39,6 +39,7 @@ struct MetadataBaton {
39
39
  int loop;
40
40
  std::vector<int> delay;
41
41
  int pagePrimary;
42
+ std::vector<std::pair<int, int>> levels;
42
43
  bool hasProfile;
43
44
  bool hasAlpha;
44
45
  int orientation;
package/src/pipeline.cc CHANGED
@@ -645,8 +645,12 @@ class PipelineWorker : public Napi::AsyncWorker {
645
645
  // Extract an image channel (aka vips band)
646
646
  if (baton->extractChannel > -1) {
647
647
  if (baton->extractChannel >= image.bands()) {
648
- (baton->err).append("Cannot extract channel from image. Too few channels in image.");
649
- return Error();
648
+ if (baton->extractChannel == 3 && sharp::HasAlpha(image)) {
649
+ baton->extractChannel = image.bands() - 1;
650
+ } else {
651
+ (baton->err).append("Cannot extract channel from image. Too few channels in image.");
652
+ return Error();
653
+ }
650
654
  }
651
655
  VipsInterpretation const interpretation = sharp::Is16Bit(image.interpretation())
652
656
  ? VIPS_INTERPRETATION_GREY16
package/src/stats.cc CHANGED
@@ -75,8 +75,17 @@ class StatsWorker : public Napi::AsyncWorker {
75
75
  baton->isOpaque = false;
76
76
  }
77
77
  }
78
+ // Convert to greyscale
79
+ vips::VImage greyscale = image.colourspace(VIPS_INTERPRETATION_B_W)[0];
78
80
  // Estimate entropy via histogram of greyscale value frequency
79
- baton->entropy = std::abs(image.colourspace(VIPS_INTERPRETATION_B_W)[0].hist_find().hist_entropy());
81
+ baton->entropy = std::abs(greyscale.hist_find().hist_entropy());
82
+ // Estimate sharpness via standard deviation of greyscale laplacian
83
+ VImage laplacian = VImage::new_matrixv(3, 3,
84
+ 0.0, 1.0, 0.0,
85
+ 1.0, -4.0, 1.0,
86
+ 0.0, 1.0, 0.0);
87
+ laplacian.set("scale", 9.0);
88
+ baton->sharpness = greyscale.conv(laplacian).deviate();
80
89
  } catch (vips::VError const &err) {
81
90
  (baton->err).append(err.what());
82
91
  }
@@ -123,6 +132,7 @@ class StatsWorker : public Napi::AsyncWorker {
123
132
  info.Set("channels", channels);
124
133
  info.Set("isOpaque", baton->isOpaque);
125
134
  info.Set("entropy", baton->entropy);
135
+ info.Set("sharpness", baton->sharpness);
126
136
  Callback().MakeCallback(Receiver().Value(), { env.Null(), info });
127
137
  } else {
128
138
  Callback().MakeCallback(Receiver().Value(), { Napi::Error::New(env, baton->err).Value() });
package/src/stats.h CHANGED
@@ -47,13 +47,15 @@ struct StatsBaton {
47
47
  std::vector<ChannelStats> channelStats;
48
48
  bool isOpaque;
49
49
  double entropy;
50
+ double sharpness;
50
51
 
51
52
  std::string err;
52
53
 
53
54
  StatsBaton():
54
55
  input(nullptr),
55
56
  isOpaque(true),
56
- entropy(0.0)
57
+ entropy(0.0),
58
+ sharpness(0.0)
57
59
  {}
58
60
  };
59
61