sharp 0.32.0 → 0.32.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/README.md CHANGED
@@ -98,8 +98,6 @@ readableStream
98
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
- [![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)
102
-
103
101
  ## Licensing
104
102
 
105
103
  Copyright 2013 Lovell Fuller and others.
package/binding.gyp CHANGED
@@ -70,7 +70,9 @@
70
70
  }, {
71
71
  'target_name': 'sharp-<(platform_and_arch)',
72
72
  'defines': [
73
- 'NAPI_VERSION=7'
73
+ 'NAPI_VERSION=7',
74
+ 'NODE_ADDON_API_DISABLE_DEPRECATED',
75
+ 'NODE_API_SWALLOW_UNTHROWABLE_EXCEPTIONS'
74
76
  ],
75
77
  'dependencies': [
76
78
  '<!(node -p "require(\'node-addon-api\').gyp")',
@@ -11,6 +11,7 @@ const zlib = require('zlib');
11
11
  const { createHash } = require('crypto');
12
12
 
13
13
  const detectLibc = require('detect-libc');
14
+ const semverCoerce = require('semver/functions/coerce');
14
15
  const semverLessThan = require('semver/functions/lt');
15
16
  const semverSatisfies = require('semver/functions/satisfies');
16
17
  const simpleGet = require('simple-get');
@@ -77,7 +78,11 @@ const verifyIntegrity = function (platformAndArch) {
77
78
  flush: function (done) {
78
79
  const digest = `sha512-${hash.digest('base64')}`;
79
80
  if (expected !== digest) {
80
- libvips.removeVendoredLibvips();
81
+ try {
82
+ libvips.removeVendoredLibvips();
83
+ } catch (err) {
84
+ libvips.log(err.message);
85
+ }
81
86
  libvips.log(`Integrity expected: ${expected}`);
82
87
  libvips.log(`Integrity received: ${digest}`);
83
88
  done(new Error(`Integrity check failed for ${platformAndArch}`));
@@ -135,17 +140,19 @@ try {
135
140
  throw new Error(`BSD/SunOS systems require manual installation of libvips >= ${minimumLibvipsVersion}`);
136
141
  }
137
142
  // Linux libc version check
138
- const libcFamily = detectLibc.familySync();
139
- const libcVersion = detectLibc.versionSync();
140
- if (libcFamily === detectLibc.GLIBC && libcVersion && minimumGlibcVersionByArch[arch]) {
141
- const libcVersionWithoutPatch = libcVersion.split('.').slice(0, 2).join('.');
142
- if (semverLessThan(`${libcVersionWithoutPatch}.0`, `${minimumGlibcVersionByArch[arch]}.0`)) {
143
- handleError(new Error(`Use with glibc ${libcVersion} requires manual installation of libvips >= ${minimumLibvipsVersion}`));
143
+ const libcVersionRaw = detectLibc.versionSync();
144
+ if (libcVersionRaw) {
145
+ const libcFamily = detectLibc.familySync();
146
+ const libcVersion = semverCoerce(libcVersionRaw).version;
147
+ if (libcFamily === detectLibc.GLIBC && minimumGlibcVersionByArch[arch]) {
148
+ if (semverLessThan(libcVersion, semverCoerce(minimumGlibcVersionByArch[arch]).version)) {
149
+ handleError(new Error(`Use with glibc ${libcVersionRaw} requires manual installation of libvips >= ${minimumLibvipsVersion}`));
150
+ }
144
151
  }
145
- }
146
- if (libcFamily === detectLibc.MUSL && libcVersion) {
147
- if (semverLessThan(libcVersion, '1.1.24')) {
148
- handleError(new Error(`Use with musl ${libcVersion} requires manual installation of libvips >= ${minimumLibvipsVersion}`));
152
+ if (libcFamily === detectLibc.MUSL) {
153
+ if (semverLessThan(libcVersion, '1.1.24')) {
154
+ handleError(new Error(`Use with musl ${libcVersionRaw} requires manual installation of libvips >= ${minimumLibvipsVersion}`));
155
+ }
149
156
  }
150
157
  }
151
158
  // Node.js minimum version check
package/lib/agent.js CHANGED
@@ -30,7 +30,7 @@ module.exports = function (log) {
30
30
  const proxyAuth = proxy.username && proxy.password
31
31
  ? `${decodeURIComponent(proxy.username)}:${decodeURIComponent(proxy.password)}`
32
32
  : null;
33
- log(`Via proxy ${proxy.protocol}://${proxy.hostname}:${proxy.port} ${proxyAuth ? 'with' : 'no'} credentials`);
33
+ log(`Via proxy ${proxy.protocol}//${proxy.hostname}:${proxy.port} ${proxyAuth ? 'with' : 'no'} credentials`);
34
34
  return tunnel({
35
35
  proxy: {
36
36
  port: Number(proxy.port),
package/lib/colour.js CHANGED
@@ -70,7 +70,8 @@ function grayscale (grayscale) {
70
70
  * Set the pipeline colourspace.
71
71
  *
72
72
  * The input image will be converted to the provided colourspace at the start of the pipeline.
73
- * All operations will use this colourspace before converting to the output colourspace, as defined by {@link toColourspace}.
73
+ * All operations will use this colourspace before converting to the output colourspace,
74
+ * as defined by {@link #tocolourspace|toColourspace}.
74
75
  *
75
76
  * This feature is experimental and has not yet been fully-tested with all operations.
76
77
  *
package/lib/composite.js CHANGED
@@ -46,7 +46,7 @@ const blend = {
46
46
  * The images to composite must be the same size or smaller than the processed image.
47
47
  * If both `top` and `left` options are provided, they take precedence over `gravity`.
48
48
  *
49
- * Any resize or rotate operations in the same processing pipeline
49
+ * Any resize, rotate or extract operations in the same processing pipeline
50
50
  * will always be applied to the input image before composition.
51
51
  *
52
52
  * The `blend` option can be one of `clear`, `source`, `over`, `in`, `out`, `atop`,
@@ -154,9 +154,9 @@ const debuglog = util.debuglog('sharp');
154
154
  * @param {string} [options.text.text] - text to render as a UTF-8 string. It can contain Pango markup, for example `<i>Le</i>Monde`.
155
155
  * @param {string} [options.text.font] - font name to render with.
156
156
  * @param {string} [options.text.fontfile] - absolute filesystem path to a font file that can be used by `font`.
157
- * @param {number} [options.text.width=0] - integral number of pixels to word-wrap at. Lines of text wider than this will be broken at word boundaries.
158
- * @param {number} [options.text.height=0] - integral number of pixels high. When defined, `dpi` will be ignored and the text will automatically fit the pixel resolution defined by `width` and `height`. Will be ignored if `width` is not specified or set to 0.
159
- * @param {string} [options.text.align='left'] - text alignment (`'left'`, `'centre'`, `'center'`, `'right'`).
157
+ * @param {number} [options.text.width=0] - Integral number of pixels to word-wrap at. Lines of text wider than this will be broken at word boundaries.
158
+ * @param {number} [options.text.height=0] - Maximum integral number of pixels high. When defined, `dpi` will be ignored and the text will automatically fit the pixel resolution defined by `width` and `height`. Will be ignored if `width` is not specified or set to 0.
159
+ * @param {string} [options.text.align='left'] - Alignment style for multi-line text (`'left'`, `'centre'`, `'center'`, `'right'`).
160
160
  * @param {boolean} [options.text.justify=false] - set this to true to apply justification to the text.
161
161
  * @param {number} [options.text.dpi=72] - the resolution (size) at which to render the text. Does not take effect if `height` is specified.
162
162
  * @param {boolean} [options.text.rgba=false] - set this to true to enable RGBA output. This is useful for colour emoji rendering, or support for pango markup features like `<span foreground="red">Red!</span>`.
@@ -217,6 +217,7 @@ const Sharp = function (input, options) {
217
217
  tintB: 128,
218
218
  flatten: false,
219
219
  flattenBackground: [0, 0, 0],
220
+ unflatten: false,
220
221
  negate: false,
221
222
  negateAlpha: true,
222
223
  medianSize: 0,
package/lib/index.d.ts CHANGED
@@ -356,7 +356,7 @@ declare namespace sharp {
356
356
  * Perform an affine transform on an image. This operation will always occur after resizing, extraction and rotation, if any.
357
357
  * You must provide an array of length 4 or a 2x2 affine transformation matrix.
358
358
  * By default, new pixels are filled with a black background. You can provide a background color with the `background` option.
359
- * A particular interpolator may also be specified. Set the `interpolator` option to an attribute of the `sharp.interpolator` Object e.g. `sharp.interpolator.nohalo`.
359
+ * A particular interpolator may also be specified. Set the `interpolator` option to an attribute of the `sharp.interpolators` Object e.g. `sharp.interpolators.nohalo`.
360
360
  *
361
361
  * In the case of a 2x2 matrix, the transform is:
362
362
  * X = matrix[0, 0] * (x + idx) + matrix[0, 1] * (y + idy) + odx
@@ -427,6 +427,13 @@ declare namespace sharp {
427
427
  */
428
428
  flatten(flatten?: boolean | FlattenOptions): Sharp;
429
429
 
430
+ /**
431
+ * Ensure the image has an alpha channel with all white pixel values made fully transparent.
432
+ * Existing alpha channel values for non-white pixels remain unchanged.
433
+ * @returns A sharp instance that can be used to chain operations
434
+ */
435
+ unflatten(): Sharp;
436
+
430
437
  /**
431
438
  * Apply a gamma correction by reducing the encoding (darken) pre-resize at a factor of 1/gamma then increasing the encoding (brighten) post-resize at a factor of gamma.
432
439
  * This can improve the perceived brightness of a resized image in non-linear colour spaces.
@@ -1343,9 +1350,9 @@ declare namespace sharp {
1343
1350
  grayscale?: boolean | undefined;
1344
1351
  }
1345
1352
 
1346
- interface OverlayOptions {
1353
+ interface OverlayOptions extends SharpOptions {
1347
1354
  /** Buffer containing image data, String containing the path to an image file, or Create object */
1348
- input?: string | Buffer | { create: Create } | { text: CreateText } | undefined;
1355
+ input?: string | Buffer | { create: Create } | { text: CreateText } | { raw: CreateRaw } | undefined;
1349
1356
  /** how to blend this image with the image below. (optional, default `'over'`) */
1350
1357
  blend?: Blend | undefined;
1351
1358
  /** gravity at which to place the overlay. (optional, default 'centre') */
@@ -1356,25 +1363,8 @@ declare namespace sharp {
1356
1363
  left?: number | undefined;
1357
1364
  /** set to true to repeat the overlay image across the entire image with the given gravity. (optional, default false) */
1358
1365
  tile?: boolean | undefined;
1359
- /** number representing the DPI for vector overlay image. (optional, default 72) */
1360
- density?: number | undefined;
1361
- /** describes overlay when using raw pixel data. */
1362
- raw?: Raw | undefined;
1363
1366
  /** Set to true to avoid premultipling the image below. Equivalent to the --premultiplied vips option. */
1364
1367
  premultiplied?: boolean | undefined;
1365
- /** Set to true to read all frames/pages of an animated image. (optional, default false). */
1366
- animated?: boolean | undefined;
1367
- /**
1368
- * When to abort processing of invalid pixel data, one of (in order of sensitivity):
1369
- * 'none' (least), 'truncated', 'error' or 'warning' (most), highers level imply lower levels, invalid metadata will always abort. (optional, default 'warning')
1370
- */
1371
- failOn?: FailOnOptions | undefined;
1372
- /**
1373
- * Do not process input images where the number of pixels (width x height) exceeds this limit.
1374
- * Assumes image dimensions contained in the input metadata can be trusted.
1375
- * An integral Number of pixels, zero or false to remove limit, true to use default limit of 268402689 (0x3FFF x 0x3FFF). (optional, default 268402689)
1376
- */
1377
- limitInputPixels?: number | boolean | undefined;
1378
1368
  }
1379
1369
 
1380
1370
  interface TileOptions {
package/lib/libvips.js CHANGED
@@ -88,8 +88,7 @@ const hasVendoredLibvips = function () {
88
88
 
89
89
  /* istanbul ignore next */
90
90
  const removeVendoredLibvips = function () {
91
- const rm = fs.rmSync ? fs.rmSync : fs.rmdirSync;
92
- rm(vendorPath, { recursive: true, maxRetries: 3, force: true });
91
+ fs.rmSync(vendorPath, { recursive: true, maxRetries: 3, force: true });
93
92
  };
94
93
 
95
94
  /* istanbul ignore next */
package/lib/operation.js CHANGED
@@ -80,9 +80,13 @@ function rotate (angle, options) {
80
80
  }
81
81
 
82
82
  /**
83
- * Flip the image about the vertical Y axis. This always occurs before rotation, if any.
83
+ * Mirror the image vertically (up-down) about the x-axis.
84
+ * This always occurs before rotation, if any.
85
+ *
84
86
  * The use of `flip` implies the removal of the EXIF `Orientation` tag, if any.
85
87
  *
88
+ * This operation does not work correctly with multi-page images.
89
+ *
86
90
  * @example
87
91
  * const output = await sharp(input).flip().toBuffer();
88
92
  *
@@ -95,7 +99,9 @@ function flip (flip) {
95
99
  }
96
100
 
97
101
  /**
98
- * Flop the image about the horizontal X axis. This always occurs before rotation, if any.
102
+ * Mirror the image horizontally (left-right) about the y-axis.
103
+ * This always occurs before rotation, if any.
104
+ *
99
105
  * The use of `flop` implies the removal of the EXIF `Orientation` tag, if any.
100
106
  *
101
107
  * @example
@@ -114,7 +120,7 @@ function flop (flop) {
114
120
  *
115
121
  * You must provide an array of length 4 or a 2x2 affine transformation matrix.
116
122
  * By default, new pixels are filled with a black background. You can provide a background color with the `background` option.
117
- * A particular interpolator may also be specified. Set the `interpolator` option to an attribute of the `sharp.interpolator` Object e.g. `sharp.interpolator.nohalo`.
123
+ * A particular interpolator may also be specified. Set the `interpolator` option to an attribute of the `sharp.interpolators` Object e.g. `sharp.interpolators.nohalo`.
118
124
  *
119
125
  * In the case of a 2x2 matrix, the transform is:
120
126
  * - X = `matrix[0, 0]` \* (x + `idx`) + `matrix[0, 1]` \* (y + `idy`) + `odx`
@@ -131,7 +137,7 @@ function flop (flop) {
131
137
  * const pipeline = sharp()
132
138
  * .affine([[1, 0.3], [0.1, 0.7]], {
133
139
  * background: 'white',
134
- * interpolate: sharp.interpolators.nohalo
140
+ * interpolator: sharp.interpolators.nohalo
135
141
  * })
136
142
  * .toBuffer((err, outputBuffer, info) => {
137
143
  * // outputBuffer contains the transformed image
@@ -405,6 +411,32 @@ function flatten (options) {
405
411
  return this;
406
412
  }
407
413
 
414
+ /**
415
+ * Ensure the image has an alpha channel
416
+ * with all white pixel values made fully transparent.
417
+ *
418
+ * Existing alpha channel values for non-white pixels remain unchanged.
419
+ *
420
+ * This feature is experimental and the API may change.
421
+ *
422
+ * @since 0.32.1
423
+ *
424
+ * @example
425
+ * await sharp(rgbInput)
426
+ * .unflatten()
427
+ * .toBuffer();
428
+ *
429
+ * @example
430
+ * await sharp(rgbInput)
431
+ * .threshold(128, { grayscale: false }) // converter bright pixels to white
432
+ * .unflatten()
433
+ * .toBuffer();
434
+ */
435
+ function unflatten () {
436
+ this.options.unflatten = true;
437
+ return this;
438
+ }
439
+
408
440
  /**
409
441
  * Apply a gamma correction by reducing the encoding (darken) pre-resize at a factor of `1/gamma`
410
442
  * then increasing the encoding (brighten) post-resize at a factor of `gamma`.
@@ -875,6 +907,7 @@ module.exports = function (Sharp) {
875
907
  median,
876
908
  blur,
877
909
  flatten,
910
+ unflatten,
878
911
  gamma,
879
912
  negate,
880
913
  normalise,
package/lib/output.js CHANGED
@@ -29,7 +29,7 @@ const formats = new Map([
29
29
  ['jxl', 'jxl']
30
30
  ]);
31
31
 
32
- const jp2Regex = /\.jp[2x]|j2[kc]$/i;
32
+ const jp2Regex = /\.(jp[2x]|j2[kc])$/i;
33
33
 
34
34
  const errJp2Save = () => new Error('JP2 output requires libvips with support for OpenJPEG');
35
35
 
@@ -43,7 +43,7 @@ const bitdepthFromColourCount = (colours) => 1 << 31 - Math.clz32(Math.ceil(Math
43
43
  * Note that raw pixel data is only supported for buffer output.
44
44
  *
45
45
  * By default all metadata will be removed, which includes EXIF-based orientation.
46
- * See {@link withMetadata} for control over this.
46
+ * See {@link #withmetadata|withMetadata} for control over this.
47
47
  *
48
48
  * The caller is responsible for ensuring directory structures and permissions exist.
49
49
  *
@@ -75,7 +75,7 @@ function toFile (fileOut, callback) {
75
75
  err = new Error('Missing output file path');
76
76
  } else if (is.string(this.options.input.file) && path.resolve(this.options.input.file) === path.resolve(fileOut)) {
77
77
  err = new Error('Cannot use same file for input and output');
78
- } else if (jp2Regex.test(fileOut) && !this.constructor.format.jp2k.output.file) {
78
+ } else if (jp2Regex.test(path.extname(fileOut)) && !this.constructor.format.jp2k.output.file) {
79
79
  err = errJp2Save();
80
80
  }
81
81
  if (err) {
@@ -95,12 +95,12 @@ function toFile (fileOut, callback) {
95
95
  * Write output to a Buffer.
96
96
  * JPEG, PNG, WebP, AVIF, TIFF, GIF and raw pixel data output are supported.
97
97
  *
98
- * Use {@link toFormat} or one of the format-specific functions such as {@link jpeg}, {@link png} etc. to set the output format.
98
+ * Use {@link #toformat|toFormat} or one of the format-specific functions such as {@link jpeg}, {@link png} etc. to set the output format.
99
99
  *
100
100
  * If no explicit format is set, the output format will match the input image, except SVG input which becomes PNG output.
101
101
  *
102
102
  * By default all metadata will be removed, which includes EXIF-based orientation.
103
- * See {@link withMetadata} for control over this.
103
+ * See {@link #withmetadata|withMetadata} for control over this.
104
104
  *
105
105
  * `callback`, if present, gets three arguments `(err, data, info)` where:
106
106
  * - `err` is an error, if any.
@@ -177,12 +177,18 @@ function toBuffer (options, callback) {
177
177
  * .then(info => { ... });
178
178
  *
179
179
  * @example
180
- * // Set "IFD0-Copyright" in output EXIF metadata
180
+ * // Set output EXIF metadata
181
181
  * const data = await sharp(input)
182
182
  * .withMetadata({
183
183
  * exif: {
184
184
  * IFD0: {
185
- * Copyright: 'Wernham Hogg'
185
+ * Copyright: 'The National Gallery'
186
+ * },
187
+ * IFD3: {
188
+ * GPSLatitudeRef: 'N',
189
+ * GPSLatitude: '51/1 30/1 3230/100',
190
+ * GPSLongitudeRef: 'W',
191
+ * GPSLongitude: '0/1 7/1 4366/100'
186
192
  * }
187
193
  * }
188
194
  * })
@@ -626,6 +632,7 @@ function gif (options) {
626
632
  return this._updateFormatOut('gif', options);
627
633
  }
628
634
 
635
+ /* istanbul ignore next */
629
636
  /**
630
637
  * Use these JP2 options for output image.
631
638
  *
@@ -659,7 +666,6 @@ function gif (options) {
659
666
  * @returns {Sharp}
660
667
  * @throws {Error} Invalid options
661
668
  */
662
- /* istanbul ignore next */
663
669
  function jp2 (options) {
664
670
  if (!this.constructor.format.jp2k.output.buffer) {
665
671
  throw errJp2Save();
@@ -740,7 +746,8 @@ function trySetAnimationOptions (source, target) {
740
746
  /**
741
747
  * Use these TIFF options for output image.
742
748
  *
743
- * The `density` can be set in pixels/inch via {@link withMetadata} instead of providing `xres` and `yres` in pixels/mm.
749
+ * The `density` can be set in pixels/inch via {@link #withmetadata|withMetadata}
750
+ * instead of providing `xres` and `yres` in pixels/mm.
744
751
  *
745
752
  * @example
746
753
  * // Convert SVG input to LZW-compressed, 1 bit per pixel TIFF output
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.32.0",
4
+ "version": "0.32.2",
5
5
  "author": "Lovell Fuller <npm@lovell.info>",
6
6
  "homepage": "https://github.com/lovell/sharp",
7
7
  "contributors": [
@@ -134,11 +134,11 @@
134
134
  "dependencies": {
135
135
  "color": "^4.2.3",
136
136
  "detect-libc": "^2.0.1",
137
- "node-addon-api": "^6.0.0",
137
+ "node-addon-api": "^6.1.0",
138
138
  "prebuild-install": "^7.1.1",
139
- "semver": "^7.3.8",
139
+ "semver": "^7.5.4",
140
140
  "simple-get": "^4.0.1",
141
- "tar-fs": "^2.1.1",
141
+ "tar-fs": "^3.0.4",
142
142
  "tunnel-agent": "^0.6.0"
143
143
  },
144
144
  "devDependencies": {
@@ -147,15 +147,15 @@
147
147
  "cc": "^3.0.1",
148
148
  "exif-reader": "^1.2.0",
149
149
  "extract-zip": "^2.0.1",
150
- "icc": "^2.0.0",
150
+ "icc": "^3.0.0",
151
151
  "jsdoc-to-markdown": "^8.0.0",
152
152
  "license-checker": "^25.0.1",
153
153
  "mocha": "^10.2.0",
154
154
  "mock-fs": "^5.2.0",
155
155
  "nyc": "^15.1.0",
156
- "prebuild": "^11.0.4",
156
+ "prebuild": "lovell/prebuild#add-nodejs-20-drop-nodejs-10-and-12",
157
157
  "semistandard": "^16.0.1",
158
- "tsd": "^0.28.0"
158
+ "tsd": "^0.28.1"
159
159
  },
160
160
  "license": "Apache-2.0",
161
161
  "config": {
package/src/common.cc CHANGED
@@ -65,16 +65,6 @@ namespace sharp {
65
65
  }
66
66
  return vector;
67
67
  }
68
- Napi::Buffer<char> NewOrCopyBuffer(Napi::Env env, char* data, size_t len) {
69
- try {
70
- return Napi::Buffer<char>::New(env, data, len, FreeCallback);
71
- } catch (Napi::Error const &err) {
72
- static_cast<void>(err);
73
- }
74
- Napi::Buffer<char> buf = Napi::Buffer<char>::Copy(env, data, len);
75
- FreeCallback(nullptr, data);
76
- return buf;
77
- }
78
68
 
79
69
  // Create an InputDescriptor instance from a Napi::Object describing an input image
80
70
  InputDescriptor* CreateInputDescriptor(Napi::Object input) {
@@ -679,6 +669,10 @@ namespace sharp {
679
669
  if (image.width() > 65535 || height > 65535) {
680
670
  throw vips::VError("Processed image is too large for the GIF format");
681
671
  }
672
+ } else if (imageType == ImageType::HEIF) {
673
+ if (image.width() > 16384 || height > 16384) {
674
+ throw vips::VError("Processed image is too large for the HEIF format");
675
+ }
682
676
  }
683
677
  }
684
678
 
package/src/common.h CHANGED
@@ -126,7 +126,6 @@ namespace sharp {
126
126
  return static_cast<T>(
127
127
  vips_enum_from_nick(nullptr, type, AttrAsStr(obj, attr).data()));
128
128
  }
129
- Napi::Buffer<char> NewOrCopyBuffer(Napi::Env env, char* data, size_t len);
130
129
 
131
130
  // Create an InputDescriptor instance from a Napi::Object describing an input image
132
131
  InputDescriptor* CreateInputDescriptor(Napi::Object input);
package/src/metadata.cc CHANGED
@@ -230,20 +230,21 @@ class MetadataWorker : public Napi::AsyncWorker {
230
230
  info.Set("orientation", baton->orientation);
231
231
  }
232
232
  if (baton->exifLength > 0) {
233
- info.Set("exif", sharp::NewOrCopyBuffer(env, baton->exif, baton->exifLength));
233
+ info.Set("exif", Napi::Buffer<char>::NewOrCopy(env, baton->exif, baton->exifLength, sharp::FreeCallback));
234
234
  }
235
235
  if (baton->iccLength > 0) {
236
- info.Set("icc", sharp::NewOrCopyBuffer(env, baton->icc, baton->iccLength));
236
+ info.Set("icc", Napi::Buffer<char>::NewOrCopy(env, baton->icc, baton->iccLength, sharp::FreeCallback));
237
237
  }
238
238
  if (baton->iptcLength > 0) {
239
- info.Set("iptc", sharp::NewOrCopyBuffer(env, baton->iptc, baton->iptcLength));
239
+ info.Set("iptc", Napi::Buffer<char>::NewOrCopy(env, baton->iptc, baton->iptcLength, sharp::FreeCallback));
240
240
  }
241
241
  if (baton->xmpLength > 0) {
242
- info.Set("xmp", sharp::NewOrCopyBuffer(env, baton->xmp, baton->xmpLength));
242
+ info.Set("xmp", Napi::Buffer<char>::NewOrCopy(env, baton->xmp, baton->xmpLength, sharp::FreeCallback));
243
243
  }
244
244
  if (baton->tifftagPhotoshopLength > 0) {
245
245
  info.Set("tifftagPhotoshop",
246
- sharp::NewOrCopyBuffer(env, baton->tifftagPhotoshop, baton->tifftagPhotoshopLength));
246
+ Napi::Buffer<char>::NewOrCopy(env, baton->tifftagPhotoshop,
247
+ baton->tifftagPhotoshopLength, sharp::FreeCallback));
247
248
  }
248
249
  Callback().MakeCallback(Receiver().Value(), { env.Null(), info });
249
250
  } else {
package/src/operations.cc CHANGED
@@ -186,6 +186,7 @@ namespace sharp {
186
186
 
187
187
  VImage Modulate(VImage image, double const brightness, double const saturation,
188
188
  int const hue, double const lightness) {
189
+ VipsInterpretation colourspaceBeforeModulate = image.interpretation();
189
190
  if (HasAlpha(image)) {
190
191
  // Separate alpha channel
191
192
  VImage alpha = image[image.bands() - 1];
@@ -195,7 +196,7 @@ namespace sharp {
195
196
  { brightness, saturation, 1},
196
197
  { lightness, 0.0, static_cast<double>(hue) }
197
198
  )
198
- .colourspace(VIPS_INTERPRETATION_sRGB)
199
+ .colourspace(colourspaceBeforeModulate)
199
200
  .bandjoin(alpha);
200
201
  } else {
201
202
  return image
@@ -204,7 +205,7 @@ namespace sharp {
204
205
  { brightness, saturation, 1 },
205
206
  { lightness, 0.0, static_cast<double>(hue) }
206
207
  )
207
- .colourspace(VIPS_INTERPRETATION_sRGB);
208
+ .colourspace(colourspaceBeforeModulate);
208
209
  }
209
210
  }
210
211
 
@@ -268,30 +269,20 @@ namespace sharp {
268
269
  if (image.width() < 3 && image.height() < 3) {
269
270
  throw VError("Image to trim must be at least 3x3 pixels");
270
271
  }
271
-
272
- // Scale up 8-bit values to match 16-bit input image
273
- double multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0;
274
- threshold *= multiplier;
275
-
276
- std::vector<double> backgroundAlpha(1);
277
272
  if (background.size() == 0) {
278
273
  // Top-left pixel provides the default background colour if none is given
279
274
  background = image.extract_area(0, 0, 1, 1)(0, 0);
280
- multiplier = 1.0;
281
- }
282
- if (HasAlpha(image) && background.size() == 4) {
283
- // Just discard the alpha because flattening the background colour with
284
- // itself (effectively what find_trim() does) gives the same result
285
- backgroundAlpha[0] = background[3] * multiplier;
275
+ } else if (sharp::Is16Bit(image.interpretation())) {
276
+ for (size_t i = 0; i < background.size(); i++) {
277
+ background[i] *= 256.0;
278
+ }
279
+ threshold *= 256.0;
286
280
  }
287
- if (image.bands() > 2) {
288
- background = {
289
- background[0] * multiplier,
290
- background[1] * multiplier,
291
- background[2] * multiplier
292
- };
281
+ std::vector<double> backgroundAlpha({ background.back() });
282
+ if (HasAlpha(image)) {
283
+ background.pop_back();
293
284
  } else {
294
- background[0] = background[0] * multiplier;
285
+ background.resize(image.bands());
295
286
  }
296
287
  int left, top, width, height;
297
288
  left = image.find_trim(&top, &width, &height, VImage::option()
@@ -332,12 +323,26 @@ namespace sharp {
332
323
  if (a.size() > bands) {
333
324
  throw VError("Band expansion using linear is unsupported");
334
325
  }
326
+ bool const uchar = !Is16Bit(image.interpretation());
335
327
  if (HasAlpha(image) && a.size() != bands && (a.size() == 1 || a.size() == bands - 1 || bands - 1 == 1)) {
336
328
  // Separate alpha channel
337
329
  VImage alpha = image[bands - 1];
338
- return RemoveAlpha(image).linear(a, b, VImage::option()->set("uchar", TRUE)).bandjoin(alpha);
330
+ return RemoveAlpha(image).linear(a, b, VImage::option()->set("uchar", uchar)).bandjoin(alpha);
331
+ } else {
332
+ return image.linear(a, b, VImage::option()->set("uchar", uchar));
333
+ }
334
+ }
335
+
336
+ /*
337
+ * Unflatten
338
+ */
339
+ VImage Unflatten(VImage image) {
340
+ if (HasAlpha(image)) {
341
+ VImage alpha = image[image.bands() - 1];
342
+ VImage noAlpha = RemoveAlpha(image);
343
+ return noAlpha.bandjoin(alpha & (noAlpha.colourspace(VIPS_INTERPRETATION_B_W) < 255));
339
344
  } else {
340
- return image.linear(a, b, VImage::option()->set("uchar", TRUE));
345
+ return image.bandjoin(image.colourspace(VIPS_INTERPRETATION_B_W) < 255);
341
346
  }
342
347
  }
343
348
 
package/src/operations.h CHANGED
@@ -86,6 +86,11 @@ namespace sharp {
86
86
  */
87
87
  VImage Linear(VImage image, std::vector<double> const a, std::vector<double> const b);
88
88
 
89
+ /*
90
+ * Unflatten
91
+ */
92
+ VImage Unflatten(VImage image);
93
+
89
94
  /*
90
95
  * Recomb with a Matrix of the given bands/channel size.
91
96
  * Eg. RGB will be a 3x3 matrix.
package/src/pipeline.cc CHANGED
@@ -79,6 +79,9 @@ class PipelineWorker : public Napi::AsyncWorker {
79
79
  // Rotate and flip image according to Exif orientation
80
80
  std::tie(autoRotation, autoFlip, autoFlop) = CalculateExifRotationAndFlip(sharp::ExifOrientation(image));
81
81
  image = sharp::RemoveExifOrientation(image);
82
+ if (baton->input->access == VIPS_ACCESS_SEQUENTIAL && (autoRotation != VIPS_ANGLE_D0 || autoFlip)) {
83
+ image = image.copy_memory();
84
+ }
82
85
  } else {
83
86
  rotation = CalculateAngleRotation(baton->angle);
84
87
  }
@@ -116,7 +119,7 @@ class PipelineWorker : public Napi::AsyncWorker {
116
119
  MultiPageUnsupported(nPages, "Rotate");
117
120
  std::vector<double> background;
118
121
  std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground, FALSE);
119
- image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background));
122
+ image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background)).copy_memory();
120
123
  }
121
124
  }
122
125
 
@@ -322,7 +325,10 @@ class PipelineWorker : public Napi::AsyncWorker {
322
325
  } catch(...) {
323
326
  sharp::VipsWarningCallback(nullptr, G_LOG_LEVEL_WARNING, "Invalid embedded profile", nullptr);
324
327
  }
325
- } else if (image.interpretation() == VIPS_INTERPRETATION_CMYK) {
328
+ } else if (
329
+ image.interpretation() == VIPS_INTERPRETATION_CMYK &&
330
+ baton->colourspaceInput != VIPS_INTERPRETATION_CMYK
331
+ ) {
326
332
  image = image.icc_transform(processingProfile, VImage::option()
327
333
  ->set("input_profile", "cmyk")
328
334
  ->set("intent", VIPS_INTENT_PERCEPTUAL));
@@ -377,11 +383,11 @@ class PipelineWorker : public Napi::AsyncWorker {
377
383
  if (autoRotation != VIPS_ANGLE_D0) {
378
384
  image = image.rot(autoRotation);
379
385
  }
380
- // Flip (mirror about Y axis)
386
+ // Mirror vertically (up-down) about the x-axis
381
387
  if (baton->flip || autoFlip) {
382
388
  image = image.flip(VIPS_DIRECTION_VERTICAL);
383
389
  }
384
- // Flop (mirror about X axis)
390
+ // Mirror horizontally (left-right) about the y-axis
385
391
  if (baton->flop || autoFlop) {
386
392
  image = image.flip(VIPS_DIRECTION_HORIZONTAL);
387
393
  }
@@ -396,6 +402,7 @@ class PipelineWorker : public Napi::AsyncWorker {
396
402
  sharp::ImageType joinImageType = sharp::ImageType::UNKNOWN;
397
403
 
398
404
  for (unsigned int i = 0; i < baton->joinChannelIn.size(); i++) {
405
+ baton->joinChannelIn[i]->access = baton->input->access;
399
406
  std::tie(joinImage, joinImageType) = sharp::OpenInput(baton->joinChannelIn[i]);
400
407
  joinImage = sharp::EnsureColourspace(joinImage, baton->colourspaceInput);
401
408
  image = image.bandjoin(joinImage);
@@ -470,6 +477,9 @@ class PipelineWorker : public Napi::AsyncWorker {
470
477
 
471
478
  image = image.smartcrop(baton->width, baton->height, VImage::option()
472
479
  ->set("interesting", baton->position == 16 ? VIPS_INTERESTING_ENTROPY : VIPS_INTERESTING_ATTENTION)
480
+ #if (VIPS_MAJOR_VERSION >= 8 && VIPS_MINOR_VERSION >= 15)
481
+ ->set("premultiplied", shouldPremultiplyAlpha)
482
+ #endif
473
483
  ->set("attention_x", &attention_x)
474
484
  ->set("attention_y", &attention_y));
475
485
  baton->hasCropOffset = true;
@@ -550,7 +560,9 @@ class PipelineWorker : public Napi::AsyncWorker {
550
560
  if (baton->medianSize > 0) {
551
561
  image = image.median(baton->medianSize);
552
562
  }
563
+
553
564
  // Threshold - must happen before blurring, due to the utility of blurring after thresholding
565
+ // Threshold - must happen before unflatten to enable non-white unflattening
554
566
  if (baton->threshold != 0) {
555
567
  image = sharp::Threshold(image, baton->threshold, baton->thresholdGrayscale);
556
568
  }
@@ -560,6 +572,11 @@ class PipelineWorker : public Napi::AsyncWorker {
560
572
  image = sharp::Blur(image, baton->blurSigma);
561
573
  }
562
574
 
575
+ // Unflatten the image
576
+ if (baton->unflatten) {
577
+ image = sharp::Unflatten(image);
578
+ }
579
+
563
580
  // Convolve
564
581
  if (shouldConv) {
565
582
  image = sharp::Convolve(image,
@@ -597,6 +614,7 @@ class PipelineWorker : public Napi::AsyncWorker {
597
614
  for (Composite *composite : baton->composite) {
598
615
  VImage compositeImage;
599
616
  sharp::ImageType compositeImageType = sharp::ImageType::UNKNOWN;
617
+ composite->input->access = baton->input->access;
600
618
  std::tie(compositeImage, compositeImageType) = sharp::OpenInput(composite->input);
601
619
  compositeImage = sharp::EnsureColourspace(compositeImage, baton->colourspaceInput);
602
620
  // Verify within current dimensions
@@ -695,6 +713,7 @@ class PipelineWorker : public Napi::AsyncWorker {
695
713
  if (baton->boolean != nullptr) {
696
714
  VImage booleanImage;
697
715
  sharp::ImageType booleanImageType = sharp::ImageType::UNKNOWN;
716
+ baton->boolean->access = baton->input->access;
698
717
  std::tie(booleanImage, booleanImageType) = sharp::OpenInput(baton->boolean);
699
718
  booleanImage = sharp::EnsureColourspace(booleanImage, baton->colourspaceInput);
700
719
  image = sharp::Boolean(image, booleanImage, baton->booleanOp);
@@ -922,6 +941,7 @@ class PipelineWorker : public Napi::AsyncWorker {
922
941
  } else if (baton->formatOut == "heif" ||
923
942
  (baton->formatOut == "input" && inputImageType == sharp::ImageType::HEIF)) {
924
943
  // Write HEIF to buffer
944
+ sharp::AssertImageTypeDimensions(image, sharp::ImageType::HEIF);
925
945
  image = sharp::RemoveAnimationProperties(image).cast(VIPS_FORMAT_UCHAR);
926
946
  VipsArea *area = reinterpret_cast<VipsArea*>(image.heifsave_buffer(VImage::option()
927
947
  ->set("strip", !baton->withMetadata)
@@ -1111,6 +1131,7 @@ class PipelineWorker : public Napi::AsyncWorker {
1111
1131
  } else if (baton->formatOut == "heif" || (mightMatchInput && isHeif) ||
1112
1132
  (willMatchInput && inputImageType == sharp::ImageType::HEIF)) {
1113
1133
  // Write HEIF to file
1134
+ sharp::AssertImageTypeDimensions(image, sharp::ImageType::HEIF);
1114
1135
  image = sharp::RemoveAnimationProperties(image).cast(VIPS_FORMAT_UCHAR);
1115
1136
  image.heifsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
1116
1137
  ->set("strip", !baton->withMetadata)
@@ -1222,8 +1243,8 @@ class PipelineWorker : public Napi::AsyncWorker {
1222
1243
  // Add buffer size to info
1223
1244
  info.Set("size", static_cast<uint32_t>(baton->bufferOutLength));
1224
1245
  // Pass ownership of output data to Buffer instance
1225
- Napi::Buffer<char> data = sharp::NewOrCopyBuffer(env, static_cast<char*>(baton->bufferOut),
1226
- baton->bufferOutLength);
1246
+ Napi::Buffer<char> data = Napi::Buffer<char>::NewOrCopy(env, static_cast<char*>(baton->bufferOut),
1247
+ baton->bufferOutLength, sharp::FreeCallback);
1227
1248
  Callback().MakeCallback(Receiver().Value(), { env.Null(), data, info });
1228
1249
  } else {
1229
1250
  // Add file size to info
@@ -1460,6 +1481,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1460
1481
  // Operators
1461
1482
  baton->flatten = sharp::AttrAsBool(options, "flatten");
1462
1483
  baton->flattenBackground = sharp::AttrAsVectorOfDouble(options, "flattenBackground");
1484
+ baton->unflatten = sharp::AttrAsBool(options, "unflatten");
1463
1485
  baton->negate = sharp::AttrAsBool(options, "negate");
1464
1486
  baton->negateAlpha = sharp::AttrAsBool(options, "negateAlpha");
1465
1487
  baton->blurSigma = sharp::AttrAsDouble(options, "blurSigma");
@@ -1661,7 +1683,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1661
1683
  baton->angle != 0 ||
1662
1684
  baton->rotationAngle != 0.0 ||
1663
1685
  baton->tileAngle != 0 ||
1664
- baton->useExifOrientation ||
1686
+ baton->flip ||
1665
1687
  baton->claheWidth != 0 ||
1666
1688
  !baton->affineMatrix.empty()
1667
1689
  ) {
package/src/pipeline.h CHANGED
@@ -73,6 +73,7 @@ struct PipelineBaton {
73
73
  double tintB;
74
74
  bool flatten;
75
75
  std::vector<double> flattenBackground;
76
+ bool unflatten;
76
77
  bool negate;
77
78
  bool negateAlpha;
78
79
  double blurSigma;
@@ -239,6 +240,7 @@ struct PipelineBaton {
239
240
  tintB(128.0),
240
241
  flatten(false),
241
242
  flattenBackground{ 0.0, 0.0, 0.0 },
243
+ unflatten(false),
242
244
  negate(false),
243
245
  negateAlpha(true),
244
246
  blurSigma(0.0),