sharp 0.30.0 → 0.30.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # sharp
2
2
 
3
- <img src="https://cdn.jsdelivr.net/gh/lovell/sharp@master/docs/image/sharp-logo.svg" width="160" height="160" alt="sharp logo" align="right">
3
+ <img src="https://cdn.jsdelivr.net/gh/lovell/sharp@main/docs/image/sharp-logo.svg" width="160" height="160" alt="sharp logo" align="right">
4
4
 
5
5
  The typical use case for this high speed Node.js module
6
6
  is to convert large images in common formats to
@@ -95,10 +95,10 @@ readableStream
95
95
 
96
96
  ## Contributing
97
97
 
98
- A [guide for contributors](https://github.com/lovell/sharp/blob/master/.github/CONTRIBUTING.md)
98
+ A [guide for contributors](https://github.com/lovell/sharp/blob/main/.github/CONTRIBUTING.md)
99
99
  covers reporting bugs, requesting features and submitting code changes.
100
100
 
101
- [![Test Coverage](https://coveralls.io/repos/lovell/sharp/badge.svg?branch=master)](https://coveralls.io/r/lovell/sharp?branch=master)
101
+ [![Test Coverage](https://coveralls.io/repos/lovell/sharp/badge.svg?branch=main)](https://coveralls.io/r/lovell/sharp?branch=main)
102
102
  [![Node-API v5](https://img.shields.io/badge/Node--API-v5-green.svg)](https://nodejs.org/dist/latest/docs/api/n-api.html#n_api_n_api_version_matrix)
103
103
 
104
104
  ## Licensing
package/binding.gyp CHANGED
@@ -39,7 +39,6 @@
39
39
  'VCCLCompilerTool': {
40
40
  'ExceptionHandling': 1,
41
41
  'Optimization': 1,
42
- 'RuntimeLibrary': '2', # /MD
43
42
  'WholeProgramOptimization': 'true'
44
43
  },
45
44
  'VCLibrarianTool': {
@@ -206,7 +205,6 @@
206
205
  'VCCLCompilerTool': {
207
206
  'ExceptionHandling': 1,
208
207
  'Optimization': 1,
209
- 'RuntimeLibrary': '2', # /MD
210
208
  'WholeProgramOptimization': 'true'
211
209
  },
212
210
  'VCLibrarianTool': {
package/lib/colour.js CHANGED
@@ -19,6 +19,11 @@ const colourspace = {
19
19
  * Tint the image using the provided chroma while preserving the image luminance.
20
20
  * An alpha channel may be present and will be unchanged by the operation.
21
21
  *
22
+ * @example
23
+ * const output = await sharp(input)
24
+ * .tint({ r: 255, g: 240, b: 16 })
25
+ * .toBuffer();
26
+ *
22
27
  * @param {string|Object} rgb - parsed by the [color](https://www.npmjs.org/package/color) module to extract chroma values.
23
28
  * @returns {Sharp}
24
29
  * @throws {Error} Invalid parameter
@@ -37,6 +42,10 @@ function tint (rgb) {
37
42
  * This may be overridden by other sharp operations such as `toColourspace('b-w')`,
38
43
  * which will produce an output image containing one color channel.
39
44
  * An alpha channel may be present, and will be unchanged by the operation.
45
+ *
46
+ * @example
47
+ * const output = await sharp(input).greyscale().toBuffer();
48
+ *
40
49
  * @param {Boolean} [greyscale=true]
41
50
  * @returns {Sharp}
42
51
  */
package/lib/composite.js CHANGED
@@ -56,6 +56,21 @@ const blend = {
56
56
  * @since 0.22.0
57
57
  *
58
58
  * @example
59
+ * await sharp(background)
60
+ * .composite([
61
+ * { input: layer1, gravity: 'northwest' },
62
+ * { input: layer2, gravity: 'southeast' },
63
+ * ])
64
+ * .toFile('combined.png');
65
+ *
66
+ * @example
67
+ * const output = await sharp('input.gif', { animated: true })
68
+ * .composite([
69
+ * { input: 'overlay.png', tile: true, blend: 'saturate' }
70
+ * ])
71
+ * .toBuffer();
72
+ *
73
+ * @example
59
74
  * sharp('input.png')
60
75
  * .rotate(180)
61
76
  * .resize(300)
@@ -89,6 +104,7 @@ const blend = {
89
104
  * @param {Number} [images[].raw.width]
90
105
  * @param {Number} [images[].raw.height]
91
106
  * @param {Number} [images[].raw.channels]
107
+ * @param {boolean} [images[].animated=false] - Set to `true` to read all frames/pages of an animated image.
92
108
  * @param {boolean} [images[].failOnError=true] - @see {@link /api-constructor#parameters|constructor parameters}
93
109
  * @param {number|boolean} [images[].limitInputPixels=268402689] - @see {@link /api-constructor#parameters|constructor parameters}
94
110
  * @returns {Sharp}
@@ -162,7 +178,6 @@ function composite (images) {
162
178
  throw is.invalidParameterError('premultiplied', 'boolean', image.premultiplied);
163
179
  }
164
180
  }
165
-
166
181
  return composite;
167
182
  });
168
183
  return this;
@@ -186,8 +186,11 @@ const Sharp = function (input, options) {
186
186
  medianSize: 0,
187
187
  blurSigma: 0,
188
188
  sharpenSigma: 0,
189
- sharpenFlat: 1,
190
- sharpenJagged: 2,
189
+ sharpenM1: 1,
190
+ sharpenM2: 2,
191
+ sharpenX1: 2,
192
+ sharpenY2: 10,
193
+ sharpenY3: 20,
191
194
  threshold: 0,
192
195
  thresholdGrayscale: true,
193
196
  trimThreshold: 0,
package/lib/input.js CHANGED
@@ -299,8 +299,8 @@ function _isStreamInput () {
299
299
  *
300
300
  * - `format`: Name of decoder used to decompress image data e.g. `jpeg`, `png`, `webp`, `gif`, `svg`
301
301
  * - `size`: Total size of image in bytes, for Stream and Buffer input only
302
- * - `width`: Number of pixels wide (EXIF orientation is not taken into consideration)
303
- * - `height`: Number of pixels high (EXIF orientation is not taken into consideration)
302
+ * - `width`: Number of pixels wide (EXIF orientation is not taken into consideration, see example below)
303
+ * - `height`: Number of pixels high (EXIF orientation is not taken into consideration, see example below)
304
304
  * - `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://libvips.github.io/libvips/API/current/VipsImage.html#VipsInterpretation)
305
305
  * - `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK
306
306
  * - `depth`: Name of pixel depth format e.g. `uchar`, `char`, `ushort`, `float` [...](https://libvips.github.io/libvips/API/current/VipsImage.html#VipsBandFormat)
@@ -343,6 +343,17 @@ function _isStreamInput () {
343
343
  * // data contains a WebP image half the width and height of the original JPEG
344
344
  * });
345
345
  *
346
+ * @example
347
+ * // Based on EXIF rotation metadata, get the right-side-up width and height:
348
+ *
349
+ * const size = getNormalSize(await sharp(input).metadata());
350
+ *
351
+ * function getNormalSize({ width, height, orientation }) {
352
+ * return orientation || 0 >= 5
353
+ * ? { width: height, height: width }
354
+ * : { width, height };
355
+ * }
356
+ *
346
357
  * @param {Function} [callback] - called with the arguments `(err, metadata)`
347
358
  * @returns {Promise<Object>|Sharp}
348
359
  */
@@ -401,9 +412,9 @@ function metadata (callback) {
401
412
  * - `maxX` (x-coordinate of one of the pixel where the maximum lies)
402
413
  * - `maxY` (y-coordinate of one of the pixel where the maximum lies)
403
414
  * - `isOpaque`: Is the image fully opaque? Will be `true` if the image has no alpha channel or if every pixel is fully opaque.
404
- * - `entropy`: Histogram-based estimation of greyscale entropy, discarding alpha channel if any (experimental)
405
- * - `sharpness`: Estimation of greyscale sharpness based on the standard deviation of a Laplacian convolution, discarding alpha channel if any (experimental)
406
- * - `dominant`: Object containing most dominant sRGB colour based on a 4096-bin 3D histogram (experimental)
415
+ * - `entropy`: Histogram-based estimation of greyscale entropy, discarding alpha channel if any.
416
+ * - `sharpness`: Estimation of greyscale sharpness based on the standard deviation of a Laplacian convolution, discarding alpha channel if any.
417
+ * - `dominant`: Object containing most dominant sRGB colour based on a 4096-bin 3D histogram.
407
418
  *
408
419
  * **Note**: Statistics are derived from the original input image. Any operations performed on the image must first be
409
420
  * written to a buffer in order to run `stats` on the result (see third example).
package/lib/libvips.js CHANGED
@@ -86,9 +86,14 @@ const removeVendoredLibvips = function () {
86
86
  const pkgConfigPath = function () {
87
87
  if (process.platform !== 'win32') {
88
88
  const brewPkgConfigPath = spawnSync('which brew >/dev/null 2>&1 && eval $(brew --env) && echo $PKG_CONFIG_LIBDIR', spawnSyncOptions).stdout || '';
89
- return [brewPkgConfigPath.trim(), env.PKG_CONFIG_PATH, '/usr/local/lib/pkgconfig', '/usr/lib/pkgconfig']
90
- .filter(function (p) { return !!p; })
91
- .join(':');
89
+ return [
90
+ brewPkgConfigPath.trim(),
91
+ env.PKG_CONFIG_PATH,
92
+ '/usr/local/lib/pkgconfig',
93
+ '/usr/lib/pkgconfig',
94
+ '/usr/local/libdata/pkgconfig',
95
+ '/usr/libdata/pkgconfig'
96
+ ].filter(Boolean).join(':');
92
97
  } else {
93
98
  return '';
94
99
  }
package/lib/operation.js CHANGED
@@ -63,6 +63,10 @@ function rotate (angle, options) {
63
63
  /**
64
64
  * Flip the image about the vertical Y axis. This always occurs after rotation, if any.
65
65
  * The use of `flip` implies the removal of the EXIF `Orientation` tag, if any.
66
+ *
67
+ * @example
68
+ * const output = await sharp(input).flip().toBuffer();
69
+ *
66
70
  * @param {Boolean} [flip=true]
67
71
  * @returns {Sharp}
68
72
  */
@@ -74,6 +78,10 @@ function flip (flip) {
74
78
  /**
75
79
  * Flop the image about the horizontal X axis. This always occurs after rotation, if any.
76
80
  * The use of `flop` implies the removal of the EXIF `Orientation` tag, if any.
81
+ *
82
+ * @example
83
+ * const output = await sharp(input).flop().toBuffer();
84
+ *
77
85
  * @param {Boolean} [flop=true]
78
86
  * @returns {Sharp}
79
87
  */
@@ -185,40 +193,107 @@ function affine (matrix, options) {
185
193
  * When a `sigma` is provided, performs a slower, more accurate sharpen of the L channel in the LAB colour space.
186
194
  * Separate control over the level of sharpening in "flat" and "jagged" areas is available.
187
195
  *
188
- * @param {number} [sigma] - the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
189
- * @param {number} [flat=1.0] - the level of sharpening to apply to "flat" areas.
190
- * @param {number} [jagged=2.0] - the level of sharpening to apply to "jagged" areas.
196
+ * See {@link https://www.libvips.org/API/current/libvips-convolution.html#vips-sharpen|libvips sharpen} operation.
197
+ *
198
+ * @example
199
+ * const data = await sharp(input).sharpen().toBuffer();
200
+ *
201
+ * @example
202
+ * const data = await sharp(input).sharpen({ sigma: 2 }).toBuffer();
203
+ *
204
+ * @example
205
+ * const data = await sharp(input)
206
+ * .sharpen({
207
+ * sigma: 2,
208
+ * m1: 0
209
+ * m2: 3,
210
+ * x1: 3,
211
+ * y2: 15,
212
+ * y3: 15,
213
+ * })
214
+ * .toBuffer();
215
+ *
216
+ * @param {Object|number} [options] - if present, is an Object with attributes or (deprecated) a number for `options.sigma`.
217
+ * @param {number} [options.sigma] - the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
218
+ * @param {number} [options.m1=1.0] - the level of sharpening to apply to "flat" areas.
219
+ * @param {number} [options.m2=2.0] - the level of sharpening to apply to "jagged" areas.
220
+ * @param {number} [options.x1=2.0] - threshold between "flat" and "jagged"
221
+ * @param {number} [options.y2=10.0] - maximum amount of brightening.
222
+ * @param {number} [options.y3=20.0] - maximum amount of darkening.
223
+ * @param {number} [flat] - (deprecated) see `options.m1`.
224
+ * @param {number} [jagged] - (deprecated) see `options.m2`.
191
225
  * @returns {Sharp}
192
226
  * @throws {Error} Invalid parameters
193
227
  */
194
- function sharpen (sigma, flat, jagged) {
195
- if (!is.defined(sigma)) {
228
+ function sharpen (options, flat, jagged) {
229
+ if (!is.defined(options)) {
196
230
  // No arguments: default to mild sharpen
197
231
  this.options.sharpenSigma = -1;
198
- } else if (is.bool(sigma)) {
199
- // Boolean argument: apply mild sharpen?
200
- this.options.sharpenSigma = sigma ? -1 : 0;
201
- } else if (is.number(sigma) && is.inRange(sigma, 0.01, 10000)) {
202
- // Numeric argument: specific sigma
203
- this.options.sharpenSigma = sigma;
204
- // Control over flat areas
232
+ } else if (is.bool(options)) {
233
+ // Deprecated boolean argument: apply mild sharpen?
234
+ this.options.sharpenSigma = options ? -1 : 0;
235
+ } else if (is.number(options) && is.inRange(options, 0.01, 10000)) {
236
+ // Deprecated numeric argument: specific sigma
237
+ this.options.sharpenSigma = options;
238
+ // Deprecated control over flat areas
205
239
  if (is.defined(flat)) {
206
240
  if (is.number(flat) && is.inRange(flat, 0, 10000)) {
207
- this.options.sharpenFlat = flat;
241
+ this.options.sharpenM1 = flat;
208
242
  } else {
209
243
  throw is.invalidParameterError('flat', 'number between 0 and 10000', flat);
210
244
  }
211
245
  }
212
- // Control over jagged areas
246
+ // Deprecated control over jagged areas
213
247
  if (is.defined(jagged)) {
214
248
  if (is.number(jagged) && is.inRange(jagged, 0, 10000)) {
215
- this.options.sharpenJagged = jagged;
249
+ this.options.sharpenM2 = jagged;
216
250
  } else {
217
251
  throw is.invalidParameterError('jagged', 'number between 0 and 10000', jagged);
218
252
  }
219
253
  }
254
+ } else if (is.plainObject(options)) {
255
+ if (is.number(options.sigma) && is.inRange(options.sigma, 0.01, 10000)) {
256
+ this.options.sharpenSigma = options.sigma;
257
+ } else {
258
+ throw is.invalidParameterError('options.sigma', 'number between 0.01 and 10000', options.sigma);
259
+ }
260
+ if (is.defined(options.m1)) {
261
+ if (is.number(options.m1) && is.inRange(options.m1, 0, 10000)) {
262
+ this.options.sharpenM1 = options.m1;
263
+ } else {
264
+ throw is.invalidParameterError('options.m1', 'number between 0 and 10000', options.m1);
265
+ }
266
+ }
267
+ if (is.defined(options.m2)) {
268
+ if (is.number(options.m2) && is.inRange(options.m2, 0, 10000)) {
269
+ this.options.sharpenM2 = options.m2;
270
+ } else {
271
+ throw is.invalidParameterError('options.m2', 'number between 0 and 10000', options.m2);
272
+ }
273
+ }
274
+ if (is.defined(options.x1)) {
275
+ if (is.number(options.x1) && is.inRange(options.x1, 0, 10000)) {
276
+ this.options.sharpenX1 = options.x1;
277
+ } else {
278
+ throw is.invalidParameterError('options.x1', 'number between 0 and 10000', options.x1);
279
+ }
280
+ }
281
+ if (is.defined(options.y2)) {
282
+ if (is.number(options.y2) && is.inRange(options.y2, 0, 10000)) {
283
+ this.options.sharpenY2 = options.y2;
284
+ } else {
285
+ throw is.invalidParameterError('options.y2', 'number between 0 and 10000', options.y2);
286
+ }
287
+ }
288
+ if (is.defined(options.y3)) {
289
+ if (is.number(options.y3) && is.inRange(options.y3, 0, 10000)) {
290
+ this.options.sharpenY3 = options.y3;
291
+ } else {
292
+ throw is.invalidParameterError('options.y3', 'number between 0 and 10000', options.y3);
293
+ }
294
+ }
220
295
  } else {
221
- throw is.invalidParameterError('sigma', 'number between 0.01 and 10000', sigma);
296
+ throw is.invalidParameterError('sigma', 'number between 0.01 and 10000', options);
222
297
  }
223
298
  return this;
224
299
  }
@@ -226,6 +301,13 @@ function sharpen (sigma, flat, jagged) {
226
301
  /**
227
302
  * Apply median filter.
228
303
  * When used without parameters the default window is 3x3.
304
+ *
305
+ * @example
306
+ * const output = await sharp(input).median().toBuffer();
307
+ *
308
+ * @example
309
+ * const output = await sharp(input).median(5).toBuffer();
310
+ *
229
311
  * @param {number} [size=3] square mask size: size x size
230
312
  * @returns {Sharp}
231
313
  * @throws {Error} Invalid parameters
@@ -356,6 +438,10 @@ function negate (options) {
356
438
 
357
439
  /**
358
440
  * Enhance output image contrast by stretching its luminance to cover the full dynamic range.
441
+ *
442
+ * @example
443
+ * const output = await sharp(input).normalise().toBuffer();
444
+ *
359
445
  * @param {Boolean} [normalise=true]
360
446
  * @returns {Sharp}
361
447
  */
@@ -366,6 +452,10 @@ function normalise (normalise) {
366
452
 
367
453
  /**
368
454
  * Alternative spelling of normalise.
455
+ *
456
+ * @example
457
+ * const output = await sharp(input).normalize().toBuffer();
458
+ *
369
459
  * @param {Boolean} [normalize=true]
370
460
  * @returns {Sharp}
371
461
  */
@@ -381,6 +471,14 @@ function normalize (normalize) {
381
471
  *
382
472
  * @since 0.28.3
383
473
  *
474
+ * @example
475
+ * const output = await sharp(input)
476
+ * .clahe({
477
+ * width: 3,
478
+ * height: 3,
479
+ * })
480
+ * .toBuffer();
481
+ *
384
482
  * @param {Object} options
385
483
  * @param {number} options.width - integer width of the region in pixels.
386
484
  * @param {number} options.height - integer height of the region in pixels.
@@ -590,28 +688,38 @@ function recomb (inputMatrix) {
590
688
  * @since 0.22.1
591
689
  *
592
690
  * @example
593
- * sharp(input)
691
+ * // increase brightness by a factor of 2
692
+ * const output = await sharp(input)
594
693
  * .modulate({
595
- * brightness: 2 // increase brightness by a factor of 2
596
- * });
694
+ * brightness: 2
695
+ * })
696
+ * .toBuffer();
597
697
  *
598
- * sharp(input)
698
+ * @example
699
+ * // hue-rotate by 180 degrees
700
+ * const output = await sharp(input)
599
701
  * .modulate({
600
- * hue: 180 // hue-rotate by 180 degrees
601
- * });
702
+ * hue: 180
703
+ * })
704
+ * .toBuffer();
602
705
  *
603
- * sharp(input)
706
+ * @example
707
+ * // increase lightness by +50
708
+ * const output = await sharp(input)
604
709
  * .modulate({
605
- * lightness: 50 // increase lightness by +50
606
- * });
710
+ * lightness: 50
711
+ * })
712
+ * .toBuffer();
607
713
  *
714
+ * @example
608
715
  * // decreate brightness and saturation while also hue-rotating by 90 degrees
609
- * sharp(input)
716
+ * const output = await sharp(input)
610
717
  * .modulate({
611
718
  * brightness: 0.5,
612
719
  * saturation: 0.5,
613
- * hue: 90
614
- * });
720
+ * hue: 90,
721
+ * })
722
+ * .toBuffer();
615
723
  *
616
724
  * @param {Object} [options]
617
725
  * @param {number} [options.brightness] Brightness multiplier
package/lib/output.js CHANGED
@@ -139,6 +139,7 @@ function toBuffer (options, callback) {
139
139
  } else if (this.options.resolveWithObject) {
140
140
  this.options.resolveWithObject = false;
141
141
  }
142
+ this.options.fileOut = '';
142
143
  return this._pipeline(is.fn(options) ? options : callback);
143
144
  }
144
145
 
package/lib/sharp.js CHANGED
@@ -21,7 +21,7 @@ try {
21
21
  '- Consult the installation documentation: https://sharp.pixelplumbing.com/install'
22
22
  );
23
23
  // Check loaded
24
- if (process.platform === 'win32') {
24
+ if (process.platform === 'win32' || /symbol/.test(err.message)) {
25
25
  const loadedModule = Object.keys(require.cache).find((i) => /[\\/]build[\\/]Release[\\/]sharp(.*)\.node$/.test(i));
26
26
  if (loadedModule) {
27
27
  const [, loadedPackage] = loadedModule.match(/node_modules[\\/]([^\\/]+)[\\/]/);
package/lib/utility.js CHANGED
@@ -30,11 +30,11 @@ const interpolators = {
30
30
  bilinear: 'bilinear',
31
31
  /** [Bicubic interpolation](http://en.wikipedia.org/wiki/Bicubic_interpolation) (the default). */
32
32
  bicubic: 'bicubic',
33
- /** [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. */
33
+ /** [LBB interpolation](https://github.com/libvips/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. */
34
34
  locallyBoundedBicubic: 'lbb',
35
35
  /** [Nohalo interpolation](http://eprints.soton.ac.uk/268086/). Prevents acutance but typically reduces performance by a factor of 3. */
36
36
  nohalo: 'nohalo',
37
- /** [VSQBS interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/vsqbs.cpp#L48). Prevents "staircasing" when enlarging. */
37
+ /** [VSQBS interpolation](https://github.com/libvips/libvips/blob/master/libvips/resample/vsqbs.cpp#L48). Prevents "staircasing" when enlarging. */
38
38
  vertexSplitQuadraticBasisSpline: 'vsqbs'
39
39
  };
40
40
 
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, GIF, AVIF and TIFF images",
4
- "version": "0.30.0",
4
+ "version": "0.30.3",
5
5
  "author": "Lovell Fuller <npm@lovell.info>",
6
6
  "homepage": "https://github.com/lovell/sharp",
7
7
  "contributors": [
@@ -126,8 +126,8 @@
126
126
  "vips"
127
127
  ],
128
128
  "dependencies": {
129
- "color": "^4.2.0",
130
- "detect-libc": "^2.0.0",
129
+ "color": "^4.2.1",
130
+ "detect-libc": "^2.0.1",
131
131
  "node-addon-api": "^4.3.0",
132
132
  "prebuild-install": "^7.0.1",
133
133
  "semver": "^7.3.5",
@@ -143,10 +143,10 @@
143
143
  "exif-reader": "^1.0.3",
144
144
  "icc": "^2.0.0",
145
145
  "license-checker": "^25.0.1",
146
- "mocha": "^9.2.0",
146
+ "mocha": "^9.2.2",
147
147
  "mock-fs": "^5.1.2",
148
148
  "nyc": "^15.1.0",
149
- "prebuild": "^11.0.2",
149
+ "prebuild": "^11.0.3",
150
150
  "rimraf": "^3.0.2",
151
151
  "semistandard": "^16.0.1"
152
152
  },
package/src/common.cc CHANGED
@@ -398,6 +398,10 @@ namespace sharp {
398
398
  // From filesystem
399
399
  imageType = DetermineImageType(descriptor->file.data());
400
400
  if (imageType == ImageType::MISSING) {
401
+ if (descriptor->file.find("<svg") != std::string::npos) {
402
+ throw vips::VError("Input file is missing, did you mean "
403
+ "sharp(Buffer.from('" + descriptor->file.substr(0, 8) + "...')?");
404
+ }
401
405
  throw vips::VError("Input file is missing");
402
406
  }
403
407
  if (imageType != ImageType::UNKNOWN) {
@@ -885,7 +889,7 @@ namespace sharp {
885
889
  }
886
890
 
887
891
  std::pair<double, double> ResolveShrink(int width, int height, int targetWidth, int targetHeight,
888
- Canvas canvas, bool swap, bool withoutEnlargement) {
892
+ Canvas canvas, bool swap, bool withoutEnlargement, bool withoutReduction) {
889
893
  if (swap) {
890
894
  // Swap input width and height when requested.
891
895
  std::swap(width, height);
@@ -940,9 +944,14 @@ namespace sharp {
940
944
  }
941
945
  }
942
946
 
943
- // We should not enlarge (oversample) the output image,
944
- // if withoutEnlargement is specified.
945
- if (withoutEnlargement) {
947
+ // We should not reduce or enlarge the output image, if
948
+ // withoutReduction or withoutEnlargement is specified.
949
+ if (withoutReduction) {
950
+ // Equivalent of VIPS_SIZE_UP
951
+ hshrink = std::min(1.0, hshrink);
952
+ vshrink = std::min(1.0, vshrink);
953
+ } else if (withoutEnlargement) {
954
+ // Equivalent of VIPS_SIZE_DOWN
946
955
  hshrink = std::max(1.0, hshrink);
947
956
  vshrink = std::max(1.0, vshrink);
948
957
  }
package/src/common.h CHANGED
@@ -345,7 +345,7 @@ namespace sharp {
345
345
  the required thumbnail width/height and canvas mode.
346
346
  */
347
347
  std::pair<double, double> ResolveShrink(int width, int height, int targetWidth, int targetHeight,
348
- Canvas canvas, bool swap, bool withoutEnlargement);
348
+ Canvas canvas, bool swap, bool withoutEnlargement, bool withoutReduction);
349
349
 
350
350
  } // namespace sharp
351
351
 
package/src/operations.cc CHANGED
@@ -209,7 +209,8 @@ namespace sharp {
209
209
  /*
210
210
  * Sharpen flat and jagged areas. Use sigma of -1.0 for fast sharpen.
211
211
  */
212
- VImage Sharpen(VImage image, double const sigma, double const flat, double const jagged) {
212
+ VImage Sharpen(VImage image, double const sigma, double const m1, double const m2,
213
+ double const x1, double const y2, double const y3) {
213
214
  if (sigma == -1.0) {
214
215
  // Fast, mild sharpen
215
216
  VImage sharpen = VImage::new_matrixv(3, 3,
@@ -224,8 +225,14 @@ namespace sharp {
224
225
  if (colourspaceBeforeSharpen == VIPS_INTERPRETATION_RGB) {
225
226
  colourspaceBeforeSharpen = VIPS_INTERPRETATION_sRGB;
226
227
  }
227
- return image.sharpen(
228
- VImage::option()->set("sigma", sigma)->set("m1", flat)->set("m2", jagged))
228
+ return image
229
+ .sharpen(VImage::option()
230
+ ->set("sigma", sigma)
231
+ ->set("m1", m1)
232
+ ->set("m2", m2)
233
+ ->set("x1", x1)
234
+ ->set("y2", y2)
235
+ ->set("y3", y3))
229
236
  .colourspace(colourspaceBeforeSharpen);
230
237
  }
231
238
  }
package/src/operations.h CHANGED
@@ -64,7 +64,8 @@ namespace sharp {
64
64
  /*
65
65
  * Sharpen flat and jagged areas. Use sigma of -1.0 for fast sharpen.
66
66
  */
67
- VImage Sharpen(VImage image, double const sigma, double const flat, double const jagged);
67
+ VImage Sharpen(VImage image, double const sigma, double const m1, double const m2,
68
+ double const x1, double const y2, double const y3);
68
69
 
69
70
  /*
70
71
  Threshold an image
package/src/pipeline.cc CHANGED
@@ -138,17 +138,6 @@ class PipelineWorker : public Napi::AsyncWorker {
138
138
  pageHeight = inputHeight;
139
139
  }
140
140
 
141
- // If withoutReduction is specified,
142
- // Override target width and height if less than respective value from input file
143
- if (baton->withoutReduction) {
144
- if (baton->width < inputWidth) {
145
- baton->width = inputWidth;
146
- }
147
- if (baton->height < inputHeight) {
148
- baton->height = inputHeight;
149
- }
150
- }
151
-
152
141
  // Scaling calculations
153
142
  double hshrink;
154
143
  double vshrink;
@@ -161,7 +150,7 @@ class PipelineWorker : public Napi::AsyncWorker {
161
150
  // Shrink to pageHeight, so we work for multi-page images
162
151
  std::tie(hshrink, vshrink) = sharp::ResolveShrink(
163
152
  inputWidth, pageHeight, targetResizeWidth, targetResizeHeight,
164
- baton->canvas, swap, baton->withoutEnlargement);
153
+ baton->canvas, swap, baton->withoutEnlargement, baton->withoutReduction);
165
154
 
166
155
  // The jpeg preload shrink.
167
156
  int jpegShrinkOnLoad = 1;
@@ -184,7 +173,7 @@ class PipelineWorker : public Napi::AsyncWorker {
184
173
 
185
174
  if (inputImageType == sharp::ImageType::JPEG) {
186
175
  // Leave at least a factor of two for the final resize step, when fastShrinkOnLoad: false
187
- // for more consistent results and avoid occasional small image shifting
176
+ // for more consistent results and to avoid extra sharpness to the image
188
177
  int factor = baton->fastShrinkOnLoad ? 1 : 2;
189
178
  if (shrink >= 8 * factor) {
190
179
  jpegShrinkOnLoad = 8;
@@ -193,6 +182,10 @@ class PipelineWorker : public Napi::AsyncWorker {
193
182
  } else if (shrink >= 2 * factor) {
194
183
  jpegShrinkOnLoad = 2;
195
184
  }
185
+ // Lower shrink-on-load for known libjpeg rounding errors
186
+ if (jpegShrinkOnLoad > 1 && static_cast<int>(shrink) == jpegShrinkOnLoad) {
187
+ jpegShrinkOnLoad /= 2;
188
+ }
196
189
  } else if (inputImageType == sharp::ImageType::WEBP ||
197
190
  inputImageType == sharp::ImageType::SVG ||
198
191
  inputImageType == sharp::ImageType::PDF) {
@@ -270,35 +263,28 @@ class PipelineWorker : public Napi::AsyncWorker {
270
263
  }
271
264
 
272
265
  // Any pre-shrinking may already have been done
273
- int thumbWidth = image.width();
274
- int thumbHeight = image.height();
266
+ inputWidth = image.width();
267
+ inputHeight = image.height();
275
268
 
276
269
  // After pre-shrink, but before the main shrink stage
277
270
  // Reuse the initial pageHeight if we didn't pre-shrink
278
- int preshrunkPageHeight = shouldPreShrink ? sharp::GetPageHeight(image) : pageHeight;
279
-
280
- if (baton->fastShrinkOnLoad && jpegShrinkOnLoad > 1) {
281
- // JPEG shrink-on-load rounds the output dimensions down, which
282
- // may cause incorrect dimensions when fastShrinkOnLoad is enabled
283
- // Just recalculate vshrink / hshrink on the main image instead of
284
- // the pre-shrunk image when this is the case
285
- hshrink = static_cast<double>(thumbWidth) / (static_cast<double>(inputWidth) / hshrink);
286
- vshrink = static_cast<double>(preshrunkPageHeight) / (static_cast<double>(pageHeight) / vshrink);
287
- } else {
288
- // Shrink to preshrunkPageHeight, so we work for multi-page images
289
- std::tie(hshrink, vshrink) = sharp::ResolveShrink(
290
- thumbWidth, preshrunkPageHeight, targetResizeWidth, targetResizeHeight,
291
- baton->canvas, swap, baton->withoutEnlargement);
271
+ if (shouldPreShrink) {
272
+ pageHeight = sharp::GetPageHeight(image);
292
273
  }
293
274
 
294
- int targetHeight = static_cast<int>(std::rint(static_cast<double>(preshrunkPageHeight) / vshrink));
275
+ // Shrink to pageHeight, so we work for multi-page images
276
+ std::tie(hshrink, vshrink) = sharp::ResolveShrink(
277
+ inputWidth, pageHeight, targetResizeWidth, targetResizeHeight,
278
+ baton->canvas, swap, baton->withoutEnlargement, baton->withoutReduction);
279
+
280
+ int targetHeight = static_cast<int>(std::rint(static_cast<double>(pageHeight) / vshrink));
295
281
  int targetPageHeight = targetHeight;
296
282
 
297
283
  // In toilet-roll mode, we must adjust vshrink so that we exactly hit
298
- // preshrunkPageHeight or we'll have pixels straddling pixel boundaries
299
- if (thumbHeight > preshrunkPageHeight) {
284
+ // pageHeight or we'll have pixels straddling pixel boundaries
285
+ if (inputHeight > pageHeight) {
300
286
  targetHeight *= nPages;
301
- vshrink = static_cast<double>(thumbHeight) / targetHeight;
287
+ vshrink = static_cast<double>(inputHeight) / targetHeight;
302
288
  }
303
289
 
304
290
  // Ensure we're using a device-independent colour space
@@ -306,7 +292,8 @@ class PipelineWorker : public Napi::AsyncWorker {
306
292
  if (
307
293
  sharp::HasProfile(image) &&
308
294
  image.interpretation() != VIPS_INTERPRETATION_LABS &&
309
- image.interpretation() != VIPS_INTERPRETATION_GREY16
295
+ image.interpretation() != VIPS_INTERPRETATION_GREY16 &&
296
+ image.interpretation() != VIPS_INTERPRETATION_B_W
310
297
  ) {
311
298
  // Convert to sRGB/P3 using embedded profile
312
299
  try {
@@ -367,7 +354,7 @@ class PipelineWorker : public Napi::AsyncWorker {
367
354
  }
368
355
 
369
356
  bool const shouldPremultiplyAlpha = sharp::HasAlpha(image) &&
370
- (shouldResize || shouldBlur || shouldConv || shouldSharpen || shouldComposite);
357
+ (shouldResize || shouldBlur || shouldConv || shouldSharpen);
371
358
 
372
359
  // Premultiply image alpha channel before all transformations to avoid
373
360
  // dark fringing around bright pixels
@@ -532,12 +519,14 @@ class PipelineWorker : public Napi::AsyncWorker {
532
519
  MultiPageUnsupported(nPages, "Affine");
533
520
  std::vector<double> background;
534
521
  std::tie(image, background) = sharp::ApplyAlpha(image, baton->affineBackground, shouldPremultiplyAlpha);
522
+ vips::VInterpolate interp = vips::VInterpolate::new_from_name(
523
+ const_cast<char*>(baton->affineInterpolator.data()));
535
524
  image = image.affine(baton->affineMatrix, VImage::option()->set("background", background)
536
525
  ->set("idx", baton->affineIdx)
537
526
  ->set("idy", baton->affineIdy)
538
527
  ->set("odx", baton->affineOdx)
539
528
  ->set("ody", baton->affineOdy)
540
- ->set("interpolate", baton->affineInterpolator));
529
+ ->set("interpolate", interp));
541
530
  }
542
531
 
543
532
  // Extend edges
@@ -588,11 +577,14 @@ class PipelineWorker : public Napi::AsyncWorker {
588
577
 
589
578
  // Sharpen
590
579
  if (shouldSharpen) {
591
- image = sharp::Sharpen(image, baton->sharpenSigma, baton->sharpenFlat, baton->sharpenJagged);
580
+ image = sharp::Sharpen(image, baton->sharpenSigma, baton->sharpenM1, baton->sharpenM2,
581
+ baton->sharpenX1, baton->sharpenY2, baton->sharpenY3);
592
582
  }
593
583
 
594
584
  // Composite
595
585
  if (shouldComposite) {
586
+ std::vector<VImage> images = { image };
587
+ std::vector<int> modes, xs, ys;
596
588
  for (Composite *composite : baton->composite) {
597
589
  VImage compositeImage;
598
590
  sharp::ImageType compositeImageType = sharp::ImageType::UNKNOWN;
@@ -638,12 +630,12 @@ class PipelineWorker : public Napi::AsyncWorker {
638
630
  // gravity was used for extract_area, set it back to its default value of 0
639
631
  composite->gravity = 0;
640
632
  }
641
- // Ensure image to composite is sRGB with premultiplied alpha
633
+ // Ensure image to composite is sRGB with unpremultiplied alpha
642
634
  compositeImage = compositeImage.colourspace(VIPS_INTERPRETATION_sRGB);
643
635
  if (!sharp::HasAlpha(compositeImage)) {
644
636
  compositeImage = sharp::EnsureAlpha(compositeImage, 1);
645
637
  }
646
- if (!composite->premultiplied) compositeImage = compositeImage.premultiply();
638
+ if (composite->premultiplied) compositeImage = compositeImage.unpremultiply();
647
639
  // Calculate position
648
640
  int left;
649
641
  int top;
@@ -661,12 +653,12 @@ class PipelineWorker : public Napi::AsyncWorker {
661
653
  std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),
662
654
  compositeImage.width(), compositeImage.height(), composite->gravity);
663
655
  }
664
- // Composite
665
- image = image.composite2(compositeImage, composite->mode, VImage::option()
666
- ->set("premultiplied", TRUE)
667
- ->set("x", left)
668
- ->set("y", top));
656
+ images.push_back(compositeImage);
657
+ modes.push_back(composite->mode);
658
+ xs.push_back(left);
659
+ ys.push_back(top);
669
660
  }
661
+ image = image.composite(images, modes, VImage::option()->set("x", xs)->set("y", ys));
670
662
  }
671
663
 
672
664
  // Reverse premultiplication after all transformations:
@@ -1409,8 +1401,11 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1409
1401
  baton->lightness = sharp::AttrAsDouble(options, "lightness");
1410
1402
  baton->medianSize = sharp::AttrAsUint32(options, "medianSize");
1411
1403
  baton->sharpenSigma = sharp::AttrAsDouble(options, "sharpenSigma");
1412
- baton->sharpenFlat = sharp::AttrAsDouble(options, "sharpenFlat");
1413
- baton->sharpenJagged = sharp::AttrAsDouble(options, "sharpenJagged");
1404
+ baton->sharpenM1 = sharp::AttrAsDouble(options, "sharpenM1");
1405
+ baton->sharpenM2 = sharp::AttrAsDouble(options, "sharpenM2");
1406
+ baton->sharpenX1 = sharp::AttrAsDouble(options, "sharpenX1");
1407
+ baton->sharpenY2 = sharp::AttrAsDouble(options, "sharpenY2");
1408
+ baton->sharpenY3 = sharp::AttrAsDouble(options, "sharpenY3");
1414
1409
  baton->threshold = sharp::AttrAsInt32(options, "threshold");
1415
1410
  baton->thresholdGrayscale = sharp::AttrAsBool(options, "thresholdGrayscale");
1416
1411
  baton->trimThreshold = sharp::AttrAsDouble(options, "trimThreshold");
@@ -1442,7 +1437,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1442
1437
  baton->affineIdy = sharp::AttrAsDouble(options, "affineIdy");
1443
1438
  baton->affineOdx = sharp::AttrAsDouble(options, "affineOdx");
1444
1439
  baton->affineOdy = sharp::AttrAsDouble(options, "affineOdy");
1445
- baton->affineInterpolator = vips::VInterpolate::new_from_name(sharp::AttrAsStr(options, "affineInterpolator").data());
1440
+ baton->affineInterpolator = sharp::AttrAsStr(options, "affineInterpolator");
1446
1441
  baton->removeAlpha = sharp::AttrAsBool(options, "removeAlpha");
1447
1442
  baton->ensureAlpha = sharp::AttrAsDouble(options, "ensureAlpha");
1448
1443
  if (options.Has("boolean")) {
package/src/pipeline.h CHANGED
@@ -90,8 +90,11 @@ struct PipelineBaton {
90
90
  double lightness;
91
91
  int medianSize;
92
92
  double sharpenSigma;
93
- double sharpenFlat;
94
- double sharpenJagged;
93
+ double sharpenM1;
94
+ double sharpenM2;
95
+ double sharpenX1;
96
+ double sharpenY2;
97
+ double sharpenY3;
95
98
  int threshold;
96
99
  bool thresholdGrayscale;
97
100
  double trimThreshold;
@@ -126,7 +129,7 @@ struct PipelineBaton {
126
129
  double affineIdy;
127
130
  double affineOdx;
128
131
  double affineOdy;
129
- vips::VInterpolate affineInterpolator;
132
+ std::string affineInterpolator;
130
133
  int jpegQuality;
131
134
  bool jpegProgressive;
132
135
  std::string jpegChromaSubsampling;
@@ -234,8 +237,11 @@ struct PipelineBaton {
234
237
  lightness(0),
235
238
  medianSize(0),
236
239
  sharpenSigma(0.0),
237
- sharpenFlat(1.0),
238
- sharpenJagged(2.0),
240
+ sharpenM1(1.0),
241
+ sharpenM2(2.0),
242
+ sharpenX1(2.0),
243
+ sharpenY2(10.0),
244
+ sharpenY3(20.0),
239
245
  threshold(0),
240
246
  thresholdGrayscale(true),
241
247
  trimThreshold(0.0),
@@ -268,7 +274,7 @@ struct PipelineBaton {
268
274
  affineIdy(0),
269
275
  affineOdx(0),
270
276
  affineOdy(0),
271
- affineInterpolator(vips::VInterpolate::new_from_name("bicubic")),
277
+ affineInterpolator("bicubic"),
272
278
  jpegQuality(80),
273
279
  jpegProgressive(false),
274
280
  jpegChromaSubsampling("4:2:0"),
package/src/sharp.cc CHANGED
@@ -13,7 +13,6 @@
13
13
  // limitations under the License.
14
14
 
15
15
  #include <napi.h>
16
- #include <cstdlib>
17
16
  #include <vips/vips8>
18
17
 
19
18
  #include "common.h"
@@ -22,14 +21,6 @@
22
21
  #include "utilities.h"
23
22
  #include "stats.h"
24
23
 
25
- #if defined(_MSC_VER) && _MSC_VER >= 1400 // MSVC 2005/8
26
- static void empty_invalid_parameter_handler(const wchar_t* expression,
27
- const wchar_t* function, const wchar_t* file, unsigned int line,
28
- uintptr_t reserved) {
29
- // No-op.
30
- }
31
- #endif
32
-
33
24
  static void* sharp_vips_init(void*) {
34
25
  g_setenv("VIPS_MIN_STACK_SIZE", "2m", FALSE);
35
26
  vips_init("sharp");
@@ -43,13 +34,6 @@ Napi::Object init(Napi::Env env, Napi::Object exports) {
43
34
  g_log_set_handler("VIPS", static_cast<GLogLevelFlags>(G_LOG_LEVEL_WARNING),
44
35
  static_cast<GLogFunc>(sharp::VipsWarningCallback), nullptr);
45
36
 
46
- // Tell the CRT to not exit the application when an invalid parameter is
47
- // passed. The main issue is that invalid FDs will trigger this behaviour.
48
- // See: https://github.com/libvips/libvips/pull/2571.
49
- #if defined(_MSC_VER) && _MSC_VER >= 1400 // MSVC 2005/8
50
- _set_invalid_parameter_handler(empty_invalid_parameter_handler);
51
- #endif
52
-
53
37
  // Methods available to JavaScript
54
38
  exports.Set("metadata", Napi::Function::New(env, metadata));
55
39
  exports.Set("pipeline", Napi::Function::New(env, pipeline));