sharp 0.28.1 → 0.28.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,7 +7,8 @@ const stream = require('stream');
7
7
  const zlib = require('zlib');
8
8
 
9
9
  const detectLibc = require('detect-libc');
10
- const semver = require('semver');
10
+ const semverLessThan = require('semver/functions/lt');
11
+ const semverSatisfies = require('semver/functions/satisfies');
11
12
  const simpleGet = require('simple-get');
12
13
  const tarFs = require('tar-fs');
13
14
 
@@ -35,6 +36,7 @@ const { minimumLibvipsVersion, minimumLibvipsVersionLabelled } = libvips;
35
36
  const distHost = process.env.npm_config_sharp_libvips_binary_host || 'https://github.com/lovell/sharp-libvips/releases/download';
36
37
  const distBaseUrl = process.env.npm_config_sharp_dist_base_url || process.env.SHARP_DIST_BASE_URL || `${distHost}/v${minimumLibvipsVersionLabelled}/`;
37
38
  const supportsBrotli = ('BrotliDecompress' in zlib);
39
+ const installationForced = !!(process.env.npm_config_sharp_install_force || process.env.SHARP_INSTALL_FORCE);
38
40
 
39
41
  const fail = function (err) {
40
42
  libvips.log(err);
@@ -46,6 +48,14 @@ const fail = function (err) {
46
48
  process.exit(1);
47
49
  };
48
50
 
51
+ const handleError = function (err) {
52
+ if (installationForced) {
53
+ libvips.log(`Installation warning: ${err.message}`);
54
+ } else {
55
+ throw err;
56
+ }
57
+ };
58
+
49
59
  const extractTarball = function (tarPath, platformAndArch) {
50
60
  const vendorPath = path.join(__dirname, '..', 'vendor');
51
61
  libvips.mkdirSync(vendorPath);
@@ -95,20 +105,21 @@ try {
95
105
  if (platformAndArch === 'freebsd-x64' || platformAndArch === 'openbsd-x64' || platformAndArch === 'sunos-x64') {
96
106
  throw new Error(`BSD/SunOS systems require manual installation of libvips >= ${minimumLibvipsVersion}`);
97
107
  }
98
- if (detectLibc.family === detectLibc.GLIBC && detectLibc.version) {
99
- if (semver.lt(`${detectLibc.version}.0`, `${minimumGlibcVersionByArch[arch]}.0`)) {
100
- throw new Error(`Use with glibc ${detectLibc.version} requires manual installation of libvips >= ${minimumLibvipsVersion}`);
108
+ // Linux libc version check
109
+ if (detectLibc.family === detectLibc.GLIBC && detectLibc.version && minimumGlibcVersionByArch[arch]) {
110
+ if (semverLessThan(`${detectLibc.version}.0`, `${minimumGlibcVersionByArch[arch]}.0`)) {
111
+ handleError(new Error(`Use with glibc ${detectLibc.version} requires manual installation of libvips >= ${minimumLibvipsVersion}`));
101
112
  }
102
113
  }
103
114
  if (detectLibc.family === detectLibc.MUSL && detectLibc.version) {
104
- if (semver.lt(detectLibc.version, '1.1.24')) {
105
- throw new Error(`Use with musl ${detectLibc.version} requires manual installation of libvips >= ${minimumLibvipsVersion}`);
115
+ if (semverLessThan(detectLibc.version, '1.1.24')) {
116
+ handleError(new Error(`Use with musl ${detectLibc.version} requires manual installation of libvips >= ${minimumLibvipsVersion}`));
106
117
  }
107
118
  }
108
-
119
+ // Node.js minimum version check
109
120
  const supportedNodeVersion = process.env.npm_package_engines_node || require('../package.json').engines.node;
110
- if (!semver.satisfies(process.versions.node, supportedNodeVersion)) {
111
- throw new Error(`Expected Node.js version ${supportedNodeVersion} but found ${process.versions.node}`);
121
+ if (!semverSatisfies(process.versions.node, supportedNodeVersion)) {
122
+ handleError(new Error(`Expected Node.js version ${supportedNodeVersion} but found ${process.versions.node}`));
112
123
  }
113
124
 
114
125
  const extension = supportsBrotli ? 'br' : 'gz';
package/lib/agent.js CHANGED
@@ -25,7 +25,7 @@ module.exports = function () {
25
25
  ? tunnelAgent.httpsOverHttps
26
26
  : tunnelAgent.httpsOverHttp;
27
27
  const proxyAuth = proxy.username && proxy.password
28
- ? `${proxy.username}:${proxy.password}`
28
+ ? `${decodeURIComponent(proxy.username)}:${decodeURIComponent(proxy.password)}`
29
29
  : null;
30
30
  return tunnel({
31
31
  proxy: {
@@ -139,6 +139,8 @@ const debuglog = util.debuglog('sharp');
139
139
  * @param {number} [options.raw.width] - integral number of pixels wide.
140
140
  * @param {number} [options.raw.height] - integral number of pixels high.
141
141
  * @param {number} [options.raw.channels] - integral number of channels, between 1 and 4.
142
+ * @param {boolean} [options.raw.premultiplied] - specifies that the raw input has already been premultiplied, set to `true`
143
+ * to avoid sharp premultiplying the image. (optional, default `false`)
142
144
  * @param {Object} [options.create] - describes a new image to be created.
143
145
  * @param {number} [options.create.width] - integral number of pixels wide.
144
146
  * @param {number} [options.create.height] - integral number of pixels high.
@@ -231,6 +233,7 @@ const Sharp = function (input, options) {
231
233
  streamOut: false,
232
234
  withMetadata: false,
233
235
  withMetadataOrientation: -1,
236
+ withMetadataDensity: 0,
234
237
  withMetadataIcc: '',
235
238
  withMetadataStrs: {},
236
239
  resolveWithObject: false,
package/lib/input.js CHANGED
@@ -30,9 +30,15 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
30
30
  inputDescriptor.file = input;
31
31
  } else if (is.buffer(input)) {
32
32
  // Buffer
33
+ if (input.length === 0) {
34
+ throw Error('Input Buffer is empty');
35
+ }
33
36
  inputDescriptor.buffer = input;
34
37
  } else if (is.uint8Array(input)) {
35
38
  // Uint8Array or Uint8ClampedArray
39
+ if (input.length === 0) {
40
+ throw Error('Input Bit Array is empty');
41
+ }
36
42
  inputDescriptor.buffer = Buffer.from(input.buffer);
37
43
  } else if (is.plainObject(input) && !is.defined(inputOptions)) {
38
44
  // Plain Object descriptor, e.g. create
@@ -97,6 +103,7 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
97
103
  inputDescriptor.rawWidth = inputOptions.raw.width;
98
104
  inputDescriptor.rawHeight = inputOptions.raw.height;
99
105
  inputDescriptor.rawChannels = inputOptions.raw.channels;
106
+ inputDescriptor.rawPremultiplied = !!inputOptions.raw.premultiplied;
100
107
  } else {
101
108
  throw new Error('Expected width, height and channels for raw pixel input');
102
109
  }
package/lib/libvips.js CHANGED
@@ -4,13 +4,15 @@ const fs = require('fs');
4
4
  const os = require('os');
5
5
  const path = require('path');
6
6
  const spawnSync = require('child_process').spawnSync;
7
- const semver = require('semver');
7
+ const semverCoerce = require('semver/functions/coerce');
8
+ const semverGreaterThanOrEqualTo = require('semver/functions/gte');
9
+
8
10
  const platform = require('./platform');
9
11
 
10
12
  const env = process.env;
11
13
  const minimumLibvipsVersionLabelled = env.npm_package_config_libvips || /* istanbul ignore next */
12
14
  require('../package.json').config.libvips;
13
- const minimumLibvipsVersion = semver.coerce(minimumLibvipsVersionLabelled).version;
15
+ const minimumLibvipsVersion = semverCoerce(minimumLibvipsVersionLabelled).version;
14
16
 
15
17
  const spawnSyncOptions = {
16
18
  encoding: 'utf8',
@@ -105,7 +107,7 @@ const useGlobalLibvips = function () {
105
107
  }
106
108
  const globalVipsVersion = globalLibvipsVersion();
107
109
  return !!globalVipsVersion && /* istanbul ignore next */
108
- semver.gte(globalVipsVersion, minimumLibvipsVersion);
110
+ semverGreaterThanOrEqualTo(globalVipsVersion, minimumLibvipsVersion);
109
111
  };
110
112
 
111
113
  module.exports = {
package/lib/operation.js CHANGED
@@ -272,7 +272,7 @@ function blur (sigma) {
272
272
  *
273
273
  * @example
274
274
  * await sharp(rgbaInput)
275
- * .flatten('#F0A703')
275
+ * .flatten({background: '#F0A703' })
276
276
  * .toBuffer();
277
277
  *
278
278
  * @param {Object} [options]
package/lib/output.js CHANGED
@@ -150,7 +150,7 @@ function toBuffer (options, callback) {
150
150
  *
151
151
  * @example
152
152
  * // Set "IFD0-Copyright" in output EXIF metadata
153
- * await sharp(input)
153
+ * const data = await sharp(input)
154
154
  * .withMetadata({
155
155
  * exif: {
156
156
  * IFD0: {
@@ -160,10 +160,17 @@ function toBuffer (options, callback) {
160
160
  * })
161
161
  * .toBuffer();
162
162
  *
163
+ * * @example
164
+ * // Set output metadata to 96 DPI
165
+ * const data = await sharp(input)
166
+ * .withMetadata({ density: 96 })
167
+ * .toBuffer();
168
+ *
163
169
  * @param {Object} [options]
164
170
  * @param {number} [options.orientation] value between 1 and 8, used to update the EXIF `Orientation` tag.
165
171
  * @param {string} [options.icc] filesystem path to output ICC profile, defaults to sRGB.
166
172
  * @param {Object<Object>} [options.exif={}] Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data.
173
+ * @param {number} [options.density] Number of pixels per inch (DPI).
167
174
  * @returns {Sharp}
168
175
  * @throws {Error} Invalid parameters
169
176
  */
@@ -177,6 +184,13 @@ function withMetadata (options) {
177
184
  throw is.invalidParameterError('orientation', 'integer between 1 and 8', options.orientation);
178
185
  }
179
186
  }
187
+ if (is.defined(options.density)) {
188
+ if (is.number(options.density) && options.density > 0) {
189
+ this.options.withMetadataDensity = options.density;
190
+ } else {
191
+ throw is.invalidParameterError('density', 'positive number', options.density);
192
+ }
193
+ }
180
194
  if (is.defined(options.icc)) {
181
195
  if (is.string(options.icc)) {
182
196
  this.options.withMetadataIcc = options.icc;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sharp",
3
3
  "description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, AVIF and TIFF images",
4
- "version": "0.28.1",
4
+ "version": "0.28.2",
5
5
  "author": "Lovell Fuller <npm@lovell.info>",
6
6
  "homepage": "https://github.com/lovell/sharp",
7
7
  "contributors": [
@@ -74,12 +74,15 @@
74
74
  "Christian Flintrup <chr@gigahost.dk>",
75
75
  "Manan Jadhav <manan@motionden.com>",
76
76
  "Leon Radley <leon@radley.se>",
77
- "alza54 <alza54@thiocod.in>"
77
+ "alza54 <alza54@thiocod.in>",
78
+ "Jacob Smith <jacob@frende.me>",
79
+ "Michael Nutt <michael@nutt.im>"
78
80
  ],
79
81
  "scripts": {
80
82
  "install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node-gyp rebuild && node install/dll-copy)",
81
83
  "clean": "rm -rf node_modules/ build/ vendor/ .nyc_output/ coverage/ test/fixtures/output.*",
82
- "test": "semistandard && cpplint && npm run test-unit && npm run test-licensing",
84
+ "test": "npm run test-lint && npm run test-unit && npm run test-licensing",
85
+ "test-lint": "semistandard && cpplint",
83
86
  "test-unit": "nyc --reporter=lcov --branches=99 mocha --slow=1000 --timeout=60000 ./test/unit/*.js",
84
87
  "test-licensing": "license-checker --production --summary --onlyAllow=\"Apache-2.0;BSD;ISC;MIT\"",
85
88
  "test-coverage": "./test/coverage/report.sh",
@@ -120,7 +123,7 @@
120
123
  "color": "^3.1.3",
121
124
  "detect-libc": "^1.0.3",
122
125
  "node-addon-api": "^3.1.0",
123
- "prebuild-install": "^6.1.1",
126
+ "prebuild-install": "^6.1.2",
124
127
  "semver": "^7.3.5",
125
128
  "simple-get": "^3.1.0",
126
129
  "tar-fs": "^2.1.1",
@@ -130,12 +133,12 @@
130
133
  "async": "^3.2.0",
131
134
  "cc": "^3.0.1",
132
135
  "decompress-zip": "^0.3.3",
133
- "documentation": "^13.2.0",
136
+ "documentation": "^13.2.5",
134
137
  "exif-reader": "^1.0.3",
135
138
  "icc": "^2.0.0",
136
139
  "license-checker": "^25.0.1",
137
- "mocha": "^8.3.2",
138
- "mock-fs": "^4.13.0",
140
+ "mocha": "^8.4.0",
141
+ "mock-fs": "^4.14.0",
139
142
  "nyc": "^15.1.0",
140
143
  "prebuild": "^10.0.1",
141
144
  "rimraf": "^3.0.2",
package/src/common.cc CHANGED
@@ -95,6 +95,7 @@ namespace sharp {
95
95
  descriptor->rawChannels = AttrAsUint32(input, "rawChannels");
96
96
  descriptor->rawWidth = AttrAsUint32(input, "rawWidth");
97
97
  descriptor->rawHeight = AttrAsUint32(input, "rawHeight");
98
+ descriptor->rawPremultiplied = AttrAsBool(input, "rawPremultiplied");
98
99
  }
99
100
  // Multi-page input (GIF, TIFF, PDF)
100
101
  if (HasAttr(input, "pages")) {
@@ -235,6 +236,7 @@ namespace sharp {
235
236
  { "VipsForeignLoadFits", ImageType::FITS },
236
237
  { "VipsForeignLoadOpenexr", ImageType::EXR },
237
238
  { "VipsForeignLoadVips", ImageType::VIPS },
239
+ { "VipsForeignLoadVipsFile", ImageType::VIPS },
238
240
  { "VipsForeignLoadRaw", ImageType::RAW }
239
241
  };
240
242
 
@@ -301,6 +303,9 @@ namespace sharp {
301
303
  } else {
302
304
  image.get_image()->Type = VIPS_INTERPRETATION_sRGB;
303
305
  }
306
+ if (descriptor->rawPremultiplied) {
307
+ image = image.unpremultiply();
308
+ }
304
309
  imageType = ImageType::RAW;
305
310
  } else {
306
311
  // Compressed data
@@ -520,9 +525,8 @@ namespace sharp {
520
525
  VImage SetDensity(VImage image, const double density) {
521
526
  const double pixelsPerMm = density / 25.4;
522
527
  VImage copy = image.copy();
523
- copy.set("Xres", pixelsPerMm);
524
- copy.set("Yres", pixelsPerMm);
525
- copy.set(VIPS_META_RESOLUTION_UNIT, "in");
528
+ copy.get_image()->Xres = pixelsPerMm;
529
+ copy.get_image()->Yres = pixelsPerMm;
526
530
  return copy;
527
531
  }
528
532
 
package/src/common.h CHANGED
@@ -57,6 +57,7 @@ namespace sharp {
57
57
  int rawChannels;
58
58
  int rawWidth;
59
59
  int rawHeight;
60
+ bool rawPremultiplied;
60
61
  int pages;
61
62
  int page;
62
63
  int level;
@@ -80,6 +81,7 @@ namespace sharp {
80
81
  rawChannels(0),
81
82
  rawWidth(0),
82
83
  rawHeight(0),
84
+ rawPremultiplied(false),
83
85
  pages(1),
84
86
  page(0),
85
87
  level(0),
package/src/pipeline.cc CHANGED
@@ -226,7 +226,8 @@ class PipelineWorker : public Napi::AsyncWorker {
226
226
  if (
227
227
  xshrink == yshrink && xshrink >= 2 * shrink_on_load_factor &&
228
228
  (inputImageType == sharp::ImageType::JPEG || inputImageType == sharp::ImageType::WEBP) &&
229
- baton->gamma == 0 && baton->topOffsetPre == -1 && baton->trimThreshold == 0.0
229
+ baton->gamma == 0 && baton->topOffsetPre == -1 && baton->trimThreshold == 0.0 &&
230
+ image.width() > 3 && image.height() > 3
230
231
  ) {
231
232
  if (xshrink >= 8 * shrink_on_load_factor) {
232
233
  xfactor = xfactor / 8;
@@ -721,6 +722,10 @@ class PipelineWorker : public Napi::AsyncWorker {
721
722
  if (baton->withMetadata && baton->withMetadataOrientation != -1) {
722
723
  image = sharp::SetExifOrientation(image, baton->withMetadataOrientation);
723
724
  }
725
+ // Override pixel density
726
+ if (baton->withMetadataDensity > 0) {
727
+ image = sharp::SetDensity(image, baton->withMetadataDensity);
728
+ }
724
729
  // Metadata key/value pairs, e.g. EXIF
725
730
  if (!baton->withMetadataStrs.empty()) {
726
731
  image = image.copy();
@@ -1384,6 +1389,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1384
1389
  baton->fileOut = sharp::AttrAsStr(options, "fileOut");
1385
1390
  baton->withMetadata = sharp::AttrAsBool(options, "withMetadata");
1386
1391
  baton->withMetadataOrientation = sharp::AttrAsUint32(options, "withMetadataOrientation");
1392
+ baton->withMetadataDensity = sharp::AttrAsDouble(options, "withMetadataDensity");
1387
1393
  baton->withMetadataIcc = sharp::AttrAsStr(options, "withMetadataIcc");
1388
1394
  Napi::Object mdStrs = options.Get("withMetadataStrs").As<Napi::Object>();
1389
1395
  Napi::Array mdStrKeys = mdStrs.GetPropertyNames();
package/src/pipeline.h CHANGED
@@ -168,6 +168,7 @@ struct PipelineBaton {
168
168
  std::string err;
169
169
  bool withMetadata;
170
170
  int withMetadataOrientation;
171
+ double withMetadataDensity;
171
172
  std::string withMetadataIcc;
172
173
  std::unordered_map<std::string, std::string> withMetadataStrs;
173
174
  std::unique_ptr<double[]> convKernel;
@@ -290,6 +291,7 @@ struct PipelineBaton {
290
291
  heifLossless(false),
291
292
  withMetadata(false),
292
293
  withMetadataOrientation(-1),
294
+ withMetadataDensity(0.0),
293
295
  convKernelWidth(0),
294
296
  convKernelHeight(0),
295
297
  convKernelScale(0.0),