sharp 0.27.1 → 0.30.5

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,10 +1,10 @@
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
7
- smaller, web-friendly JPEG, PNG, WebP and AVIF images of varying dimensions.
7
+ smaller, web-friendly JPEG, PNG, WebP, GIF and AVIF images of varying dimensions.
8
8
 
9
9
  Resizing an image is typically 4x-5x faster than using the
10
10
  quickest ImageMagick and GraphicsMagick settings
@@ -16,9 +16,17 @@ Lanczos resampling ensures quality is not sacrificed for speed.
16
16
  As well as image resizing, operations such as
17
17
  rotation, extraction, compositing and gamma correction are available.
18
18
 
19
- Most modern macOS, Windows and Linux systems running Node.js v10+
19
+ Most modern macOS, Windows and Linux systems running Node.js >= 12.13.0
20
20
  do not require any additional install or runtime dependencies.
21
21
 
22
+ ## Documentation
23
+
24
+ Visit [sharp.pixelplumbing.com](https://sharp.pixelplumbing.com/) for complete
25
+ [installation instructions](https://sharp.pixelplumbing.com/install),
26
+ [API documentation](https://sharp.pixelplumbing.com/api-constructor),
27
+ [benchmark tests](https://sharp.pixelplumbing.com/performance) and
28
+ [changelog](https://sharp.pixelplumbing.com/changelog).
29
+
22
30
  ## Examples
23
31
 
24
32
  ```sh
@@ -43,6 +51,7 @@ sharp(inputBuffer)
43
51
  sharp('input.jpg')
44
52
  .rotate()
45
53
  .resize(200)
54
+ .jpeg({ mozjpeg: true })
46
55
  .toBuffer()
47
56
  .then( data => { ... })
48
57
  .catch( err => { ... });
@@ -84,25 +93,17 @@ readableStream
84
93
  .pipe(writableStream);
85
94
  ```
86
95
 
87
- [![Test Coverage](https://coveralls.io/repos/lovell/sharp/badge.svg?branch=master)](https://coveralls.io/r/lovell/sharp?branch=master)
88
- [![N-API v3](https://img.shields.io/badge/N--API-v3-green.svg)](https://nodejs.org/dist/latest/docs/api/n-api.html#n_api_n_api_version_matrix)
89
-
90
- ### Documentation
96
+ ## Contributing
91
97
 
92
- Visit [sharp.pixelplumbing.com](https://sharp.pixelplumbing.com/) for complete
93
- [installation instructions](https://sharp.pixelplumbing.com/install),
94
- [API documentation](https://sharp.pixelplumbing.com/api-constructor),
95
- [benchmark tests](https://sharp.pixelplumbing.com/performance) and
96
- [changelog](https://sharp.pixelplumbing.com/changelog).
97
-
98
- ### Contributing
99
-
100
- 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)
101
99
  covers reporting bugs, requesting features and submitting code changes.
102
100
 
103
- ### Licensing
101
+ [![Test Coverage](https://coveralls.io/repos/lovell/sharp/badge.svg?branch=main)](https://coveralls.io/r/lovell/sharp?branch=main)
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
+
104
+ ## Licensing
104
105
 
105
- Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Lovell Fuller and contributors.
106
+ Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Lovell Fuller and contributors.
106
107
 
107
108
  Licensed under the Apache License, Version 2.0 (the "License");
108
109
  you may not use this file except in compliance with the License.
package/binding.gyp CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  'variables': {
3
3
  'vips_version': '<!(node -p "require(\'./lib/libvips\').minimumLibvipsVersion")',
4
- 'sharp_vendor_dir': '<(module_root_dir)/vendor/<(vips_version)'
4
+ 'platform_and_arch': '<!(node -p "require(\'./lib/platform\')()")',
5
+ 'sharp_vendor_dir': './vendor/<(vips_version)/<(platform_and_arch)'
5
6
  },
6
7
  'targets': [{
7
8
  'target_name': 'libvips-cpp',
@@ -37,6 +38,7 @@
37
38
  'msvs_settings': {
38
39
  'VCCLCompilerTool': {
39
40
  'ExceptionHandling': 1,
41
+ 'Optimization': 1,
40
42
  'WholeProgramOptimization': 'true'
41
43
  },
42
44
  'VCLibrarianTool': {
@@ -65,9 +67,9 @@
65
67
  }]
66
68
  ]
67
69
  }, {
68
- 'target_name': 'sharp',
70
+ 'target_name': 'sharp-<(platform_and_arch)',
69
71
  'defines': [
70
- 'NAPI_VERSION=3'
72
+ 'NAPI_VERSION=5'
71
73
  ],
72
74
  'dependencies': [
73
75
  '<!(node -p "require(\'node-addon-api\').gyp")',
@@ -138,32 +140,30 @@
138
140
  }],
139
141
  ['OS == "mac"', {
140
142
  'link_settings': {
141
- 'library_dirs': ['<(sharp_vendor_dir)/lib'],
143
+ 'library_dirs': ['../<(sharp_vendor_dir)/lib'],
142
144
  'libraries': [
143
- 'libvips-cpp.42.dylib',
144
- 'libvips.42.dylib'
145
+ 'libvips-cpp.42.dylib'
145
146
  ]
146
147
  },
147
148
  'xcode_settings': {
148
149
  'OTHER_LDFLAGS': [
149
150
  # Ensure runtime linking is relative to sharp.node
150
- '-Wl,-rpath,\'@loader_path/../../vendor/<(vips_version)/lib\''
151
+ '-Wl,-rpath,\'@loader_path/../../<(sharp_vendor_dir)/lib\''
151
152
  ]
152
153
  }
153
154
  }],
154
155
  ['OS == "linux"', {
155
156
  'defines': [
156
- '_GLIBCXX_USE_CXX11_ABI=0'
157
+ '_GLIBCXX_USE_CXX11_ABI=1'
157
158
  ],
158
159
  'link_settings': {
159
- 'library_dirs': ['<(sharp_vendor_dir)/lib'],
160
+ 'library_dirs': ['../<(sharp_vendor_dir)/lib'],
160
161
  'libraries': [
161
- '-l:libvips-cpp.so.42',
162
- '-l:libvips.so.42'
162
+ '-l:libvips-cpp.so.42'
163
163
  ],
164
164
  'ldflags': [
165
165
  # Ensure runtime linking is relative to sharp.node
166
- '-Wl,-s -Wl,--disable-new-dtags -Wl,-rpath=\'$$ORIGIN/../../vendor/<(vips_version)/lib\''
166
+ '-Wl,-s -Wl,--disable-new-dtags -Wl,-rpath=\'$$ORIGIN/../../<(sharp_vendor_dir)/lib\''
167
167
  ]
168
168
  }
169
169
  }]
@@ -174,7 +174,7 @@
174
174
  '-std=c++0x',
175
175
  '-fexceptions',
176
176
  '-Wall',
177
- '-O3'
177
+ '-Os'
178
178
  ],
179
179
  'xcode_settings': {
180
180
  'CLANG_CXX_LANGUAGE_STANDARD': 'c++11',
@@ -184,7 +184,7 @@
184
184
  'OTHER_CPLUSPLUSFLAGS': [
185
185
  '-fexceptions',
186
186
  '-Wall',
187
- '-O3'
187
+ '-Oz'
188
188
  ]
189
189
  },
190
190
  'configurations': {
@@ -204,6 +204,7 @@
204
204
  'msvs_settings': {
205
205
  'VCCLCompilerTool': {
206
206
  'ExceptionHandling': 1,
207
+ 'Optimization': 1,
207
208
  'WholeProgramOptimization': 'true'
208
209
  },
209
210
  'VCLibrarianTool': {
@@ -0,0 +1,11 @@
1
+ 'use strict';
2
+
3
+ const libvips = require('../lib/libvips');
4
+
5
+ try {
6
+ if (!(libvips.useGlobalLibvips() || libvips.hasVendoredLibvips())) {
7
+ process.exitCode = 1;
8
+ }
9
+ } catch (err) {
10
+ process.exitCode = 1;
11
+ }
@@ -4,21 +4,20 @@ const fs = require('fs');
4
4
  const path = require('path');
5
5
 
6
6
  const libvips = require('../lib/libvips');
7
- const npmLog = require('npmlog');
7
+ const platform = require('../lib/platform');
8
8
 
9
9
  const minimumLibvipsVersion = libvips.minimumLibvipsVersion;
10
10
 
11
- const platform = process.env.npm_config_platform || process.platform;
12
- if (platform === 'win32') {
13
- const buildDir = path.join(__dirname, '..', 'build');
14
- const buildReleaseDir = path.join(buildDir, 'Release');
15
- npmLog.info('sharp', `Creating ${buildReleaseDir}`);
11
+ const platformAndArch = platform();
12
+
13
+ if (platformAndArch.startsWith('win32')) {
14
+ const buildReleaseDir = path.join(__dirname, '..', 'build', 'Release');
15
+ libvips.log(`Creating ${buildReleaseDir}`);
16
16
  try {
17
- libvips.mkdirSync(buildDir);
18
17
  libvips.mkdirSync(buildReleaseDir);
19
18
  } catch (err) {}
20
- const vendorLibDir = path.join(__dirname, '..', 'vendor', minimumLibvipsVersion, 'lib');
21
- npmLog.info('sharp', `Copying DLLs from ${vendorLibDir} to ${buildReleaseDir}`);
19
+ const vendorLibDir = path.join(__dirname, '..', 'vendor', minimumLibvipsVersion, platformAndArch, 'lib');
20
+ libvips.log(`Copying DLLs from ${vendorLibDir} to ${buildReleaseDir}`);
22
21
  try {
23
22
  fs
24
23
  .readdirSync(vendorLibDir)
@@ -32,6 +31,7 @@ if (platform === 'win32') {
32
31
  );
33
32
  });
34
33
  } catch (err) {
35
- npmLog.error('sharp', err.message);
34
+ libvips.log(err);
35
+ process.exit(1);
36
36
  }
37
37
  }
@@ -5,10 +5,11 @@ const os = require('os');
5
5
  const path = require('path');
6
6
  const stream = require('stream');
7
7
  const zlib = require('zlib');
8
+ const { createHash } = require('crypto');
8
9
 
9
10
  const detectLibc = require('detect-libc');
10
- const npmLog = require('npmlog');
11
- const semver = require('semver');
11
+ const semverLessThan = require('semver/functions/lt');
12
+ const semverSatisfies = require('semver/functions/satisfies');
12
13
  const simpleGet = require('simple-get');
13
14
  const tarFs = require('tar-fs');
14
15
 
@@ -18,38 +19,91 @@ const platform = require('../lib/platform');
18
19
 
19
20
  const minimumGlibcVersionByArch = {
20
21
  arm: '2.28',
21
- arm64: '2.29',
22
+ arm64: '2.17',
22
23
  x64: '2.17'
23
24
  };
24
25
 
26
+ const hasSharpPrebuild = [
27
+ 'darwin-x64',
28
+ 'darwin-arm64',
29
+ 'linux-arm64',
30
+ 'linux-x64',
31
+ 'linuxmusl-x64',
32
+ 'linuxmusl-arm64',
33
+ 'win32-ia32',
34
+ 'win32-x64'
35
+ ];
36
+
25
37
  const { minimumLibvipsVersion, minimumLibvipsVersionLabelled } = libvips;
38
+ const localLibvipsDir = process.env.npm_config_sharp_libvips_local_prebuilds || '';
26
39
  const distHost = process.env.npm_config_sharp_libvips_binary_host || 'https://github.com/lovell/sharp-libvips/releases/download';
27
40
  const distBaseUrl = process.env.npm_config_sharp_dist_base_url || process.env.SHARP_DIST_BASE_URL || `${distHost}/v${minimumLibvipsVersionLabelled}/`;
28
- const supportsBrotli = ('BrotliDecompress' in zlib);
41
+ const installationForced = !!(process.env.npm_config_sharp_install_force || process.env.SHARP_INSTALL_FORCE);
29
42
 
30
43
  const fail = function (err) {
31
- npmLog.error('sharp', err.message);
44
+ libvips.log(err);
32
45
  if (err.code === 'EACCES') {
33
- npmLog.info('sharp', 'Are you trying to install as a root or sudo user? Try again with the --unsafe-perm flag');
46
+ libvips.log('Are you trying to install as a root or sudo user?');
47
+ libvips.log('- For npm <= v6, try again with the "--unsafe-perm" flag');
48
+ libvips.log('- For npm >= v8, the user must own the directory "npm install" is run in');
34
49
  }
35
- npmLog.info('sharp', 'Attempting to build from source via node-gyp but this may fail due to the above error');
36
- npmLog.info('sharp', 'Please see https://sharp.pixelplumbing.com/install for required dependencies');
50
+ libvips.log('Please see https://sharp.pixelplumbing.com/install for required dependencies');
37
51
  process.exit(1);
38
52
  };
39
53
 
40
- const extractTarball = function (tarPath) {
41
- const vendorPath = path.join(__dirname, '..', 'vendor');
42
- libvips.mkdirSync(vendorPath);
43
- const versionedVendorPath = path.join(vendorPath, minimumLibvipsVersion);
54
+ const handleError = function (err) {
55
+ if (installationForced) {
56
+ libvips.log(`Installation warning: ${err.message}`);
57
+ } else {
58
+ throw err;
59
+ }
60
+ };
61
+
62
+ const verifyIntegrity = function (platformAndArch) {
63
+ const expected = libvips.integrity(platformAndArch);
64
+ if (installationForced || !expected) {
65
+ libvips.log(`Integrity check skipped for ${platformAndArch}`);
66
+ return new stream.PassThrough();
67
+ }
68
+ const hash = createHash('sha512');
69
+ return new stream.Transform({
70
+ transform: function (chunk, _encoding, done) {
71
+ hash.update(chunk);
72
+ done(null, chunk);
73
+ },
74
+ flush: function (done) {
75
+ const digest = `sha512-${hash.digest('base64')}`;
76
+ if (expected !== digest) {
77
+ libvips.removeVendoredLibvips();
78
+ libvips.log(`Integrity expected: ${expected}`);
79
+ libvips.log(`Integrity received: ${digest}`);
80
+ done(new Error(`Integrity check failed for ${platformAndArch}`));
81
+ } else {
82
+ libvips.log(`Integrity check passed for ${platformAndArch}`);
83
+ done();
84
+ }
85
+ }
86
+ });
87
+ };
88
+
89
+ const extractTarball = function (tarPath, platformAndArch) {
90
+ const versionedVendorPath = path.join(__dirname, '..', 'vendor', minimumLibvipsVersion, platformAndArch);
44
91
  libvips.mkdirSync(versionedVendorPath);
92
+
93
+ const ignoreVendorInclude = hasSharpPrebuild.includes(platformAndArch) && !process.env.npm_config_build_from_source;
94
+ const ignore = function (name) {
95
+ return ignoreVendorInclude && name.includes('include/');
96
+ };
97
+
45
98
  stream.pipeline(
46
99
  fs.createReadStream(tarPath),
47
- supportsBrotli ? new zlib.BrotliDecompress() : new zlib.Gunzip(),
48
- tarFs.extract(versionedVendorPath),
100
+ verifyIntegrity(platformAndArch),
101
+ new zlib.BrotliDecompress(),
102
+ tarFs.extract(versionedVendorPath, { ignore }),
49
103
  function (err) {
50
104
  if (err) {
51
105
  if (/unexpected end of file/.test(err.message)) {
52
- npmLog.error('sharp', `Please delete ${tarPath} as it is not a valid tarball`);
106
+ fail(new Error(`Please delete ${tarPath} as it is not a valid tarball`));
53
107
  }
54
108
  fail(err);
55
109
  }
@@ -62,11 +116,11 @@ try {
62
116
 
63
117
  if (useGlobalLibvips) {
64
118
  const globalLibvipsVersion = libvips.globalLibvipsVersion();
65
- npmLog.info('sharp', `Detected globally-installed libvips v${globalLibvipsVersion}`);
66
- npmLog.info('sharp', 'Building from source via node-gyp');
119
+ libvips.log(`Detected globally-installed libvips v${globalLibvipsVersion}`);
120
+ libvips.log('Building from source via node-gyp');
67
121
  process.exit(1);
68
122
  } else if (libvips.hasVendoredLibvips()) {
69
- npmLog.info('sharp', `Using existing vendored libvips v${minimumLibvipsVersion}`);
123
+ libvips.log(`Using existing vendored libvips v${minimumLibvipsVersion}`);
70
124
  } else {
71
125
  // Is this arch/platform supported?
72
126
  const arch = process.env.npm_config_arch || process.arch;
@@ -74,33 +128,45 @@ try {
74
128
  if (arch === 'ia32' && !platformAndArch.startsWith('win32')) {
75
129
  throw new Error(`Intel Architecture 32-bit systems require manual installation of libvips >= ${minimumLibvipsVersion}`);
76
130
  }
131
+ if (platformAndArch === 'darwin-arm64') {
132
+ throw new Error("Please run 'brew install vips' to install libvips on Apple M1 (ARM64) systems");
133
+ }
77
134
  if (platformAndArch === 'freebsd-x64' || platformAndArch === 'openbsd-x64' || platformAndArch === 'sunos-x64') {
78
135
  throw new Error(`BSD/SunOS systems require manual installation of libvips >= ${minimumLibvipsVersion}`);
79
136
  }
80
- if (detectLibc.family === detectLibc.GLIBC && detectLibc.version) {
81
- if (semver.lt(`${detectLibc.version}.0`, `${minimumGlibcVersionByArch[arch]}.0`)) {
82
- throw new Error(`Use with glibc ${detectLibc.version} requires manual installation of libvips >= ${minimumLibvipsVersion}`);
137
+ // Linux libc version check
138
+ const libcFamily = detectLibc.familySync();
139
+ const libcVersion = detectLibc.versionSync();
140
+ if (libcFamily === detectLibc.GLIBC && libcVersion && minimumGlibcVersionByArch[arch]) {
141
+ if (semverLessThan(`${libcVersion}.0`, `${minimumGlibcVersionByArch[arch]}.0`)) {
142
+ handleError(new Error(`Use with glibc ${libcVersion} requires manual installation of libvips >= ${minimumLibvipsVersion}`));
83
143
  }
84
144
  }
85
-
145
+ if (libcFamily === detectLibc.MUSL && libcVersion) {
146
+ if (semverLessThan(libcVersion, '1.1.24')) {
147
+ handleError(new Error(`Use with musl ${libcVersion} requires manual installation of libvips >= ${minimumLibvipsVersion}`));
148
+ }
149
+ }
150
+ // Node.js minimum version check
86
151
  const supportedNodeVersion = process.env.npm_package_engines_node || require('../package.json').engines.node;
87
- if (!semver.satisfies(process.versions.node, supportedNodeVersion)) {
88
- throw new Error(`Expected Node.js version ${supportedNodeVersion} but found ${process.versions.node}`);
152
+ if (!semverSatisfies(process.versions.node, supportedNodeVersion)) {
153
+ handleError(new Error(`Expected Node.js version ${supportedNodeVersion} but found ${process.versions.node}`));
89
154
  }
90
155
 
91
- const extension = supportsBrotli ? 'br' : 'gz';
92
-
93
156
  // Download to per-process temporary file
94
- const tarFilename = ['libvips', minimumLibvipsVersion, platformAndArch].join('-') + '.tar.' + extension;
157
+ const tarFilename = ['libvips', minimumLibvipsVersionLabelled, platformAndArch].join('-') + '.tar.br';
95
158
  const tarPathCache = path.join(libvips.cachePath(), tarFilename);
96
159
  if (fs.existsSync(tarPathCache)) {
97
- npmLog.info('sharp', `Using cached ${tarPathCache}`);
98
- extractTarball(tarPathCache);
160
+ libvips.log(`Using cached ${tarPathCache}`);
161
+ extractTarball(tarPathCache, platformAndArch);
162
+ } else if (localLibvipsDir) {
163
+ // If localLibvipsDir is given try to use binaries from local directory
164
+ const tarPathLocal = path.join(path.resolve(localLibvipsDir), `v${minimumLibvipsVersionLabelled}`, tarFilename);
165
+ libvips.log(`Using local libvips from ${tarPathLocal}`);
166
+ extractTarball(tarPathLocal, platformAndArch);
99
167
  } else {
100
- const tarPathTemp = path.join(os.tmpdir(), `${process.pid}-${tarFilename}`);
101
- const tmpFile = fs.createWriteStream(tarPathTemp);
102
168
  const url = distBaseUrl + tarFilename;
103
- npmLog.info('sharp', `Downloading ${url}`);
169
+ libvips.log(`Downloading ${url}`);
104
170
  simpleGet({ url: url, agent: agent() }, function (err, response) {
105
171
  if (err) {
106
172
  fail(err);
@@ -109,24 +175,39 @@ try {
109
175
  } else if (response.statusCode !== 200) {
110
176
  fail(new Error(`Status ${response.statusCode} ${response.statusMessage}`));
111
177
  } else {
178
+ const tarPathTemp = path.join(os.tmpdir(), `${process.pid}-${tarFilename}`);
179
+ const tmpFileStream = fs.createWriteStream(tarPathTemp);
112
180
  response
113
- .on('error', fail)
114
- .pipe(tmpFile);
181
+ .on('error', function (err) {
182
+ tmpFileStream.destroy(err);
183
+ })
184
+ .on('close', function () {
185
+ if (!response.complete) {
186
+ tmpFileStream.destroy(new Error('Download incomplete (connection was terminated)'));
187
+ }
188
+ })
189
+ .pipe(tmpFileStream);
190
+ tmpFileStream
191
+ .on('error', function (err) {
192
+ // Clean up temporary file
193
+ try {
194
+ fs.unlinkSync(tarPathTemp);
195
+ } catch (e) {}
196
+ fail(err);
197
+ })
198
+ .on('close', function () {
199
+ try {
200
+ // Attempt to rename
201
+ fs.renameSync(tarPathTemp, tarPathCache);
202
+ } catch (err) {
203
+ // Fall back to copy and unlink
204
+ fs.copyFileSync(tarPathTemp, tarPathCache);
205
+ fs.unlinkSync(tarPathTemp);
206
+ }
207
+ extractTarball(tarPathCache, platformAndArch);
208
+ });
115
209
  }
116
210
  });
117
- tmpFile
118
- .on('error', fail)
119
- .on('close', function () {
120
- try {
121
- // Attempt to rename
122
- fs.renameSync(tarPathTemp, tarPathCache);
123
- } catch (err) {
124
- // Fall back to copy and unlink
125
- fs.copyFileSync(tarPathTemp, tarPathCache);
126
- fs.unlinkSync(tarPathTemp);
127
- }
128
- extractTarball(tarPathCache);
129
- });
130
211
  }
131
212
  }
132
213
  } catch (err) {
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: {
package/lib/channel.js CHANGED
@@ -15,6 +15,8 @@ const bool = {
15
15
  /**
16
16
  * Remove alpha channel, if any. This is a no-op if the image does not have an alpha channel.
17
17
  *
18
+ * See also {@link /api-operation#flatten|flatten}.
19
+ *
18
20
  * @example
19
21
  * sharp('rgba.png')
20
22
  * .removeAlpha()
@@ -30,21 +32,39 @@ function removeAlpha () {
30
32
  }
31
33
 
32
34
  /**
33
- * Ensure alpha channel, if missing. The added alpha channel will be fully opaque. This is a no-op if the image already has an alpha channel.
35
+ * Ensure the output image has an alpha transparency channel.
36
+ * If missing, the added alpha channel will have the specified
37
+ * transparency level, defaulting to fully-opaque (1).
38
+ * This is a no-op if the image already has an alpha channel.
34
39
  *
35
40
  * @since 0.21.2
36
41
  *
37
42
  * @example
38
- * sharp('rgb.jpg')
43
+ * // rgba.png will be a 4 channel image with a fully-opaque alpha channel
44
+ * await sharp('rgb.jpg')
39
45
  * .ensureAlpha()
40
- * .toFile('rgba.png', function(err, info) {
41
- * // rgba.png is a 4 channel image with a fully opaque alpha channel
42
- * });
46
+ * .toFile('rgba.png')
43
47
  *
48
+ * @example
49
+ * // rgba is a 4 channel image with a fully-transparent alpha channel
50
+ * const rgba = await sharp(rgb)
51
+ * .ensureAlpha(0)
52
+ * .toBuffer();
53
+ *
54
+ * @param {number} [alpha=1] - alpha transparency level (0=fully-transparent, 1=fully-opaque)
44
55
  * @returns {Sharp}
56
+ * @throws {Error} Invalid alpha transparency level
45
57
  */
46
- function ensureAlpha () {
47
- this.options.ensureAlpha = true;
58
+ function ensureAlpha (alpha) {
59
+ if (is.defined(alpha)) {
60
+ if (is.number(alpha) && is.inRange(alpha, 0, 1)) {
61
+ this.options.ensureAlpha = alpha;
62
+ } else {
63
+ throw is.invalidParameterError('alpha', 'number between 0 and 1', alpha);
64
+ }
65
+ } else {
66
+ this.options.ensureAlpha = 1;
67
+ }
48
68
  return this;
49
69
  }
50
70
 
@@ -52,13 +72,17 @@ function ensureAlpha () {
52
72
  * Extract a single channel from a multi-channel image.
53
73
  *
54
74
  * @example
55
- * sharp(input)
75
+ * // green.jpg is a greyscale image containing the green channel of the input
76
+ * await sharp(input)
56
77
  * .extractChannel('green')
57
- * .toColourspace('b-w')
58
- * .toFile('green.jpg', function(err, info) {
59
- * // info.channels === 1
60
- * // green.jpg is a greyscale image containing the green channel of the input
61
- * });
78
+ * .toFile('green.jpg');
79
+ *
80
+ * @example
81
+ * // red1 is the red value of the first pixel, red2 the second pixel etc.
82
+ * const [red1, red2, ...] = await sharp(input)
83
+ * .extractChannel(0)
84
+ * .raw()
85
+ * .toBuffer();
62
86
  *
63
87
  * @param {number|string} channel - zero-indexed channel/band number to extract, or `red`, `green`, `blue` or `alpha`.
64
88
  * @returns {Sharp}
@@ -74,7 +98,7 @@ function extractChannel (channel) {
74
98
  } else {
75
99
  throw is.invalidParameterError('channel', 'integer or one of: red, green, blue, alpha', channel);
76
100
  }
77
- return this;
101
+ return this.toColourspace('b-w');
78
102
  }
79
103
 
80
104
  /**
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
  */
@@ -54,6 +63,45 @@ function grayscale (grayscale) {
54
63
  return this.greyscale(grayscale);
55
64
  }
56
65
 
66
+ /**
67
+ * Set the pipeline colourspace.
68
+ *
69
+ * The input image will be converted to the provided colourspace at the start of the pipeline.
70
+ * All operations will use this colourspace before converting to the output colourspace, as defined by {@link toColourspace}.
71
+ *
72
+ * This feature is experimental and has not yet been fully-tested with all operations.
73
+ *
74
+ * @since 0.29.0
75
+ *
76
+ * @example
77
+ * // Run pipeline in 16 bits per channel RGB while converting final result to 8 bits per channel sRGB.
78
+ * await sharp(input)
79
+ * .pipelineColourspace('rgb16')
80
+ * .toColourspace('srgb')
81
+ * .toFile('16bpc-pipeline-to-8bpc-output.png')
82
+ *
83
+ * @param {string} [colourspace] - pipeline colourspace e.g. `rgb16`, `scrgb`, `lab`, `grey16` [...](https://github.com/libvips/libvips/blob/41cff4e9d0838498487a00623462204eb10ee5b8/libvips/iofuncs/enumtypes.c#L774)
84
+ * @returns {Sharp}
85
+ * @throws {Error} Invalid parameters
86
+ */
87
+ function pipelineColourspace (colourspace) {
88
+ if (!is.string(colourspace)) {
89
+ throw is.invalidParameterError('colourspace', 'string', colourspace);
90
+ }
91
+ this.options.colourspaceInput = colourspace;
92
+ return this;
93
+ }
94
+
95
+ /**
96
+ * Alternative spelling of `pipelineColourspace`.
97
+ * @param {string} [colorspace] - pipeline colorspace.
98
+ * @returns {Sharp}
99
+ * @throws {Error} Invalid parameters
100
+ */
101
+ function pipelineColorspace (colorspace) {
102
+ return this.pipelineColourspace(colorspace);
103
+ }
104
+
57
105
  /**
58
106
  * Set the output colourspace.
59
107
  * By default output image will be web-friendly sRGB, with additional channels interpreted as alpha channels.
@@ -64,7 +112,7 @@ function grayscale (grayscale) {
64
112
  * .toColourspace('rgb16')
65
113
  * .toFile('16-bpp.png')
66
114
  *
67
- * @param {string} [colourspace] - output colourspace e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://github.com/libvips/libvips/blob/master/libvips/iofuncs/enumtypes.c#L568)
115
+ * @param {string} [colourspace] - output colourspace e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://github.com/libvips/libvips/blob/3c0bfdf74ce1dc37a6429bed47fa76f16e2cd70a/libvips/iofuncs/enumtypes.c#L777-L794)
68
116
  * @returns {Sharp}
69
117
  * @throws {Error} Invalid parameters
70
118
  */
@@ -119,6 +167,8 @@ module.exports = function (Sharp) {
119
167
  tint,
120
168
  greyscale,
121
169
  grayscale,
170
+ pipelineColourspace,
171
+ pipelineColorspace,
122
172
  toColourspace,
123
173
  toColorspace,
124
174
  // Private