sharp 0.32.0 → 0.32.1

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.
@@ -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
  *
@@ -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.
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
@@ -114,7 +114,7 @@ function flop (flop) {
114
114
  *
115
115
  * You must provide an array of length 4 or a 2x2 affine transformation matrix.
116
116
  * 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`.
117
+ * 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
118
  *
119
119
  * In the case of a 2x2 matrix, the transform is:
120
120
  * - X = `matrix[0, 0]` \* (x + `idx`) + `matrix[0, 1]` \* (y + `idy`) + `odx`
@@ -131,7 +131,7 @@ function flop (flop) {
131
131
  * const pipeline = sharp()
132
132
  * .affine([[1, 0.3], [0.1, 0.7]], {
133
133
  * background: 'white',
134
- * interpolate: sharp.interpolators.nohalo
134
+ * interpolator: sharp.interpolators.nohalo
135
135
  * })
136
136
  * .toBuffer((err, outputBuffer, info) => {
137
137
  * // outputBuffer contains the transformed image
@@ -405,6 +405,32 @@ function flatten (options) {
405
405
  return this;
406
406
  }
407
407
 
408
+ /**
409
+ * Ensure the image has an alpha channel
410
+ * with all white pixel values made fully transparent.
411
+ *
412
+ * Existing alpha channel values for non-white pixels remain unchanged.
413
+ *
414
+ * This feature is experimental and the API may change.
415
+ *
416
+ * @since 0.32.1
417
+ *
418
+ * @example
419
+ * await sharp(rgbInput)
420
+ * .unflatten()
421
+ * .toBuffer();
422
+ *
423
+ * @example
424
+ * await sharp(rgbInput)
425
+ * .threshold(128, { grayscale: false }) // converter bright pixels to white
426
+ * .unflatten()
427
+ * .toBuffer();
428
+ */
429
+ function unflatten () {
430
+ this.options.unflatten = true;
431
+ return this;
432
+ }
433
+
408
434
  /**
409
435
  * Apply a gamma correction by reducing the encoding (darken) pre-resize at a factor of `1/gamma`
410
436
  * then increasing the encoding (brighten) post-resize at a factor of `gamma`.
@@ -875,6 +901,7 @@ module.exports = function (Sharp) {
875
901
  median,
876
902
  blur,
877
903
  flatten,
904
+ unflatten,
878
905
  gamma,
879
906
  negate,
880
907
  normalise,
package/lib/output.js CHANGED
@@ -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
  *
@@ -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.1",
5
5
  "author": "Lovell Fuller <npm@lovell.info>",
6
6
  "homepage": "https://github.com/lovell/sharp",
7
7
  "contributors": [
@@ -134,9 +134,9 @@
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.0",
140
140
  "simple-get": "^4.0.1",
141
141
  "tar-fs": "^2.1.1",
142
142
  "tunnel-agent": "^0.6.0"
@@ -147,7 +147,7 @@
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",
@@ -155,7 +155,7 @@
155
155
  "nyc": "^15.1.0",
156
156
  "prebuild": "^11.0.4",
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) {
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
@@ -322,7 +322,10 @@ class PipelineWorker : public Napi::AsyncWorker {
322
322
  } catch(...) {
323
323
  sharp::VipsWarningCallback(nullptr, G_LOG_LEVEL_WARNING, "Invalid embedded profile", nullptr);
324
324
  }
325
- } else if (image.interpretation() == VIPS_INTERPRETATION_CMYK) {
325
+ } else if (
326
+ image.interpretation() == VIPS_INTERPRETATION_CMYK &&
327
+ baton->colourspaceInput != VIPS_INTERPRETATION_CMYK
328
+ ) {
326
329
  image = image.icc_transform(processingProfile, VImage::option()
327
330
  ->set("input_profile", "cmyk")
328
331
  ->set("intent", VIPS_INTENT_PERCEPTUAL));
@@ -470,6 +473,7 @@ class PipelineWorker : public Napi::AsyncWorker {
470
473
 
471
474
  image = image.smartcrop(baton->width, baton->height, VImage::option()
472
475
  ->set("interesting", baton->position == 16 ? VIPS_INTERESTING_ENTROPY : VIPS_INTERESTING_ATTENTION)
476
+ ->set("premultiplied", shouldPremultiplyAlpha)
473
477
  ->set("attention_x", &attention_x)
474
478
  ->set("attention_y", &attention_y));
475
479
  baton->hasCropOffset = true;
@@ -550,7 +554,9 @@ class PipelineWorker : public Napi::AsyncWorker {
550
554
  if (baton->medianSize > 0) {
551
555
  image = image.median(baton->medianSize);
552
556
  }
557
+
553
558
  // Threshold - must happen before blurring, due to the utility of blurring after thresholding
559
+ // Threshold - must happen before unflatten to enable non-white unflattening
554
560
  if (baton->threshold != 0) {
555
561
  image = sharp::Threshold(image, baton->threshold, baton->thresholdGrayscale);
556
562
  }
@@ -560,6 +566,11 @@ class PipelineWorker : public Napi::AsyncWorker {
560
566
  image = sharp::Blur(image, baton->blurSigma);
561
567
  }
562
568
 
569
+ // Unflatten the image
570
+ if (baton->unflatten) {
571
+ image = sharp::Unflatten(image);
572
+ }
573
+
563
574
  // Convolve
564
575
  if (shouldConv) {
565
576
  image = sharp::Convolve(image,
@@ -1222,8 +1233,8 @@ class PipelineWorker : public Napi::AsyncWorker {
1222
1233
  // Add buffer size to info
1223
1234
  info.Set("size", static_cast<uint32_t>(baton->bufferOutLength));
1224
1235
  // Pass ownership of output data to Buffer instance
1225
- Napi::Buffer<char> data = sharp::NewOrCopyBuffer(env, static_cast<char*>(baton->bufferOut),
1226
- baton->bufferOutLength);
1236
+ Napi::Buffer<char> data = Napi::Buffer<char>::NewOrCopy(env, static_cast<char*>(baton->bufferOut),
1237
+ baton->bufferOutLength, sharp::FreeCallback);
1227
1238
  Callback().MakeCallback(Receiver().Value(), { env.Null(), data, info });
1228
1239
  } else {
1229
1240
  // Add file size to info
@@ -1460,6 +1471,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1460
1471
  // Operators
1461
1472
  baton->flatten = sharp::AttrAsBool(options, "flatten");
1462
1473
  baton->flattenBackground = sharp::AttrAsVectorOfDouble(options, "flattenBackground");
1474
+ baton->unflatten = sharp::AttrAsBool(options, "unflatten");
1463
1475
  baton->negate = sharp::AttrAsBool(options, "negate");
1464
1476
  baton->negateAlpha = sharp::AttrAsBool(options, "negateAlpha");
1465
1477
  baton->blurSigma = sharp::AttrAsDouble(options, "blurSigma");
@@ -1662,6 +1674,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1662
1674
  baton->rotationAngle != 0.0 ||
1663
1675
  baton->tileAngle != 0 ||
1664
1676
  baton->useExifOrientation ||
1677
+ baton->flip ||
1665
1678
  baton->claheWidth != 0 ||
1666
1679
  !baton->affineMatrix.empty()
1667
1680
  ) {
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),