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 +19 -18
- package/binding.gyp +15 -14
- package/install/can-compile.js +11 -0
- package/install/dll-copy.js +10 -10
- package/install/libvips.js +128 -47
- package/lib/agent.js +1 -1
- package/lib/channel.js +38 -14
- package/lib/colour.js +51 -1
- package/lib/composite.js +19 -2
- package/lib/constructor.js +47 -46
- package/lib/input.js +112 -18
- package/lib/is.js +19 -5
- package/lib/libvips.js +64 -29
- package/lib/operation.js +239 -35
- package/lib/output.js +398 -116
- package/lib/platform.js +5 -3
- package/lib/resize.js +61 -15
- package/lib/sharp.js +32 -0
- package/lib/utility.js +58 -19
- package/package.json +46 -24
- package/src/common.cc +202 -52
- package/src/common.h +59 -10
- package/src/libvips/cplusplus/VConnection.cpp +0 -26
- package/src/libvips/cplusplus/VImage.cpp +88 -32
- package/src/libvips/cplusplus/VInterpolate.cpp +0 -13
- package/src/libvips/cplusplus/vips-operators.cpp +213 -1
- package/src/metadata.cc +26 -0
- package/src/metadata.h +4 -0
- package/src/operations.cc +141 -7
- package/src/operations.h +32 -3
- package/src/pipeline.cc +454 -306
- package/src/pipeline.h +64 -29
- package/src/sharp.cc +1 -0
- package/src/utilities.cc +17 -1
- package/src/utilities.h +1 -0
package/lib/libvips.js
CHANGED
|
@@ -4,24 +4,29 @@ 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
|
|
7
|
+
const semverCoerce = require('semver/functions/coerce');
|
|
8
|
+
const semverGreaterThanOrEqualTo = require('semver/functions/gte');
|
|
9
|
+
|
|
8
10
|
const platform = require('./platform');
|
|
11
|
+
const { config } = require('../package.json');
|
|
9
12
|
|
|
10
13
|
const env = process.env;
|
|
11
14
|
const minimumLibvipsVersionLabelled = env.npm_package_config_libvips || /* istanbul ignore next */
|
|
12
|
-
|
|
13
|
-
const minimumLibvipsVersion =
|
|
15
|
+
config.libvips;
|
|
16
|
+
const minimumLibvipsVersion = semverCoerce(minimumLibvipsVersionLabelled).version;
|
|
14
17
|
|
|
15
18
|
const spawnSyncOptions = {
|
|
16
19
|
encoding: 'utf8',
|
|
17
20
|
shell: true
|
|
18
21
|
};
|
|
19
22
|
|
|
23
|
+
const vendorPath = path.join(__dirname, '..', 'vendor', minimumLibvipsVersion, platform());
|
|
24
|
+
|
|
20
25
|
const mkdirSync = function (dirPath) {
|
|
21
26
|
try {
|
|
22
|
-
fs.mkdirSync(dirPath);
|
|
27
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
23
28
|
} catch (err) {
|
|
24
|
-
/* istanbul ignore
|
|
29
|
+
/* istanbul ignore next */
|
|
25
30
|
if (err.code !== 'EEXIST') {
|
|
26
31
|
throw err;
|
|
27
32
|
}
|
|
@@ -37,9 +42,35 @@ const cachePath = function () {
|
|
|
37
42
|
return libvipsCachePath;
|
|
38
43
|
};
|
|
39
44
|
|
|
45
|
+
const integrity = function (platformAndArch) {
|
|
46
|
+
return env[`npm_package_config_integrity_${platformAndArch.replace('-', '_')}`] || config.integrity[platformAndArch];
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const log = function (item) {
|
|
50
|
+
if (item instanceof Error) {
|
|
51
|
+
console.error(`sharp: Installation error: ${item.message}`);
|
|
52
|
+
} else {
|
|
53
|
+
console.log(`sharp: ${item}`);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const isRosetta = function () {
|
|
58
|
+
/* istanbul ignore next */
|
|
59
|
+
if (process.platform === 'darwin' && process.arch === 'x64') {
|
|
60
|
+
const translated = spawnSync('sysctl sysctl.proc_translated', spawnSyncOptions).stdout;
|
|
61
|
+
return (translated || '').trim() === 'sysctl.proc_translated: 1';
|
|
62
|
+
}
|
|
63
|
+
return false;
|
|
64
|
+
};
|
|
65
|
+
|
|
40
66
|
const globalLibvipsVersion = function () {
|
|
41
67
|
if (process.platform !== 'win32') {
|
|
42
|
-
const globalLibvipsVersion = spawnSync(
|
|
68
|
+
const globalLibvipsVersion = spawnSync('pkg-config --modversion vips-cpp', {
|
|
69
|
+
...spawnSyncOptions,
|
|
70
|
+
env: {
|
|
71
|
+
PKG_CONFIG_PATH: pkgConfigPath()
|
|
72
|
+
}
|
|
73
|
+
}).stdout;
|
|
43
74
|
/* istanbul ignore next */
|
|
44
75
|
return (globalLibvipsVersion || '').trim();
|
|
45
76
|
} else {
|
|
@@ -48,31 +79,29 @@ const globalLibvipsVersion = function () {
|
|
|
48
79
|
};
|
|
49
80
|
|
|
50
81
|
const hasVendoredLibvips = function () {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if (vendorPlatformId) {
|
|
59
|
-
/* istanbul ignore else */
|
|
60
|
-
if (currentPlatformId === vendorPlatformId) {
|
|
61
|
-
return true;
|
|
62
|
-
} else {
|
|
63
|
-
throw new Error(`'${vendorPlatformId}' binaries cannot be used on the '${currentPlatformId}' platform. Please remove the 'node_modules/sharp' directory and run 'npm install' on the '${currentPlatformId}' platform.`);
|
|
64
|
-
}
|
|
65
|
-
} else {
|
|
66
|
-
return false;
|
|
67
|
-
}
|
|
82
|
+
return fs.existsSync(vendorPath);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/* istanbul ignore next */
|
|
86
|
+
const removeVendoredLibvips = function () {
|
|
87
|
+
const rm = fs.rmSync ? fs.rmSync : fs.rmdirSync;
|
|
88
|
+
rm(vendorPath, { recursive: true, maxRetries: 3, force: true });
|
|
68
89
|
};
|
|
69
90
|
|
|
70
91
|
const pkgConfigPath = function () {
|
|
71
92
|
if (process.platform !== 'win32') {
|
|
72
|
-
const brewPkgConfigPath = spawnSync(
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
93
|
+
const brewPkgConfigPath = spawnSync(
|
|
94
|
+
'which brew >/dev/null 2>&1 && brew environment --plain | grep PKG_CONFIG_LIBDIR | cut -d" " -f2',
|
|
95
|
+
spawnSyncOptions
|
|
96
|
+
).stdout || '';
|
|
97
|
+
return [
|
|
98
|
+
brewPkgConfigPath.trim(),
|
|
99
|
+
env.PKG_CONFIG_PATH,
|
|
100
|
+
'/usr/local/lib/pkgconfig',
|
|
101
|
+
'/usr/lib/pkgconfig',
|
|
102
|
+
'/usr/local/libdata/pkgconfig',
|
|
103
|
+
'/usr/libdata/pkgconfig'
|
|
104
|
+
].filter(Boolean).join(':');
|
|
76
105
|
} else {
|
|
77
106
|
return '';
|
|
78
107
|
}
|
|
@@ -82,18 +111,24 @@ const useGlobalLibvips = function () {
|
|
|
82
111
|
if (Boolean(env.SHARP_IGNORE_GLOBAL_LIBVIPS) === true) {
|
|
83
112
|
return false;
|
|
84
113
|
}
|
|
85
|
-
|
|
114
|
+
/* istanbul ignore next */
|
|
115
|
+
if (isRosetta()) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
86
118
|
const globalVipsVersion = globalLibvipsVersion();
|
|
87
119
|
return !!globalVipsVersion && /* istanbul ignore next */
|
|
88
|
-
|
|
120
|
+
semverGreaterThanOrEqualTo(globalVipsVersion, minimumLibvipsVersion);
|
|
89
121
|
};
|
|
90
122
|
|
|
91
123
|
module.exports = {
|
|
92
124
|
minimumLibvipsVersion,
|
|
93
125
|
minimumLibvipsVersionLabelled,
|
|
94
126
|
cachePath,
|
|
127
|
+
integrity,
|
|
128
|
+
log,
|
|
95
129
|
globalLibvipsVersion,
|
|
96
130
|
hasVendoredLibvips,
|
|
131
|
+
removeVendoredLibvips,
|
|
97
132
|
pkgConfigPath,
|
|
98
133
|
useGlobalLibvips,
|
|
99
134
|
mkdirSync
|
package/lib/operation.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { flatten: flattenArray } = require('array-flatten');
|
|
4
3
|
const color = require('color');
|
|
5
4
|
const is = require('./is');
|
|
6
5
|
|
|
@@ -64,6 +63,10 @@ function rotate (angle, options) {
|
|
|
64
63
|
/**
|
|
65
64
|
* Flip the image about the vertical Y axis. This always occurs after rotation, if any.
|
|
66
65
|
* The use of `flip` implies the removal of the EXIF `Orientation` tag, if any.
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* const output = await sharp(input).flip().toBuffer();
|
|
69
|
+
*
|
|
67
70
|
* @param {Boolean} [flip=true]
|
|
68
71
|
* @returns {Sharp}
|
|
69
72
|
*/
|
|
@@ -75,6 +78,10 @@ function flip (flip) {
|
|
|
75
78
|
/**
|
|
76
79
|
* Flop the image about the horizontal X axis. This always occurs after rotation, if any.
|
|
77
80
|
* The use of `flop` implies the removal of the EXIF `Orientation` tag, if any.
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* const output = await sharp(input).flop().toBuffer();
|
|
84
|
+
*
|
|
78
85
|
* @param {Boolean} [flop=true]
|
|
79
86
|
* @returns {Sharp}
|
|
80
87
|
*/
|
|
@@ -127,7 +134,7 @@ function flop (flop) {
|
|
|
127
134
|
* @throws {Error} Invalid parameters
|
|
128
135
|
*/
|
|
129
136
|
function affine (matrix, options) {
|
|
130
|
-
const flatMatrix =
|
|
137
|
+
const flatMatrix = [].concat(...matrix);
|
|
131
138
|
if (flatMatrix.length === 4 && flatMatrix.every(is.number)) {
|
|
132
139
|
this.options.affineMatrix = flatMatrix;
|
|
133
140
|
} else {
|
|
@@ -186,40 +193,107 @@ function affine (matrix, options) {
|
|
|
186
193
|
* When a `sigma` is provided, performs a slower, more accurate sharpen of the L channel in the LAB colour space.
|
|
187
194
|
* Separate control over the level of sharpening in "flat" and "jagged" areas is available.
|
|
188
195
|
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
* @
|
|
196
|
+
* See {@link https://www.libvips.org/API/current/libvips-convolution.html#vips-sharpen|libvips sharpen} operation.
|
|
197
|
+
*
|
|
198
|
+
* @example
|
|
199
|
+
* const data = await sharp(input).sharpen().toBuffer();
|
|
200
|
+
*
|
|
201
|
+
* @example
|
|
202
|
+
* const data = await sharp(input).sharpen({ sigma: 2 }).toBuffer();
|
|
203
|
+
*
|
|
204
|
+
* @example
|
|
205
|
+
* const data = await sharp(input)
|
|
206
|
+
* .sharpen({
|
|
207
|
+
* sigma: 2,
|
|
208
|
+
* m1: 0
|
|
209
|
+
* m2: 3,
|
|
210
|
+
* x1: 3,
|
|
211
|
+
* y2: 15,
|
|
212
|
+
* y3: 15,
|
|
213
|
+
* })
|
|
214
|
+
* .toBuffer();
|
|
215
|
+
*
|
|
216
|
+
* @param {Object|number} [options] - if present, is an Object with attributes or (deprecated) a number for `options.sigma`.
|
|
217
|
+
* @param {number} [options.sigma] - the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
|
|
218
|
+
* @param {number} [options.m1=1.0] - the level of sharpening to apply to "flat" areas.
|
|
219
|
+
* @param {number} [options.m2=2.0] - the level of sharpening to apply to "jagged" areas.
|
|
220
|
+
* @param {number} [options.x1=2.0] - threshold between "flat" and "jagged"
|
|
221
|
+
* @param {number} [options.y2=10.0] - maximum amount of brightening.
|
|
222
|
+
* @param {number} [options.y3=20.0] - maximum amount of darkening.
|
|
223
|
+
* @param {number} [flat] - (deprecated) see `options.m1`.
|
|
224
|
+
* @param {number} [jagged] - (deprecated) see `options.m2`.
|
|
192
225
|
* @returns {Sharp}
|
|
193
226
|
* @throws {Error} Invalid parameters
|
|
194
227
|
*/
|
|
195
|
-
function sharpen (
|
|
196
|
-
if (!is.defined(
|
|
228
|
+
function sharpen (options, flat, jagged) {
|
|
229
|
+
if (!is.defined(options)) {
|
|
197
230
|
// No arguments: default to mild sharpen
|
|
198
231
|
this.options.sharpenSigma = -1;
|
|
199
|
-
} else if (is.bool(
|
|
200
|
-
//
|
|
201
|
-
this.options.sharpenSigma =
|
|
202
|
-
} else if (is.number(
|
|
203
|
-
//
|
|
204
|
-
this.options.sharpenSigma =
|
|
205
|
-
//
|
|
232
|
+
} else if (is.bool(options)) {
|
|
233
|
+
// Deprecated boolean argument: apply mild sharpen?
|
|
234
|
+
this.options.sharpenSigma = options ? -1 : 0;
|
|
235
|
+
} else if (is.number(options) && is.inRange(options, 0.01, 10000)) {
|
|
236
|
+
// Deprecated numeric argument: specific sigma
|
|
237
|
+
this.options.sharpenSigma = options;
|
|
238
|
+
// Deprecated control over flat areas
|
|
206
239
|
if (is.defined(flat)) {
|
|
207
240
|
if (is.number(flat) && is.inRange(flat, 0, 10000)) {
|
|
208
|
-
this.options.
|
|
241
|
+
this.options.sharpenM1 = flat;
|
|
209
242
|
} else {
|
|
210
243
|
throw is.invalidParameterError('flat', 'number between 0 and 10000', flat);
|
|
211
244
|
}
|
|
212
245
|
}
|
|
213
|
-
//
|
|
246
|
+
// Deprecated control over jagged areas
|
|
214
247
|
if (is.defined(jagged)) {
|
|
215
248
|
if (is.number(jagged) && is.inRange(jagged, 0, 10000)) {
|
|
216
|
-
this.options.
|
|
249
|
+
this.options.sharpenM2 = jagged;
|
|
217
250
|
} else {
|
|
218
251
|
throw is.invalidParameterError('jagged', 'number between 0 and 10000', jagged);
|
|
219
252
|
}
|
|
220
253
|
}
|
|
254
|
+
} else if (is.plainObject(options)) {
|
|
255
|
+
if (is.number(options.sigma) && is.inRange(options.sigma, 0.01, 10000)) {
|
|
256
|
+
this.options.sharpenSigma = options.sigma;
|
|
257
|
+
} else {
|
|
258
|
+
throw is.invalidParameterError('options.sigma', 'number between 0.01 and 10000', options.sigma);
|
|
259
|
+
}
|
|
260
|
+
if (is.defined(options.m1)) {
|
|
261
|
+
if (is.number(options.m1) && is.inRange(options.m1, 0, 10000)) {
|
|
262
|
+
this.options.sharpenM1 = options.m1;
|
|
263
|
+
} else {
|
|
264
|
+
throw is.invalidParameterError('options.m1', 'number between 0 and 10000', options.m1);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (is.defined(options.m2)) {
|
|
268
|
+
if (is.number(options.m2) && is.inRange(options.m2, 0, 10000)) {
|
|
269
|
+
this.options.sharpenM2 = options.m2;
|
|
270
|
+
} else {
|
|
271
|
+
throw is.invalidParameterError('options.m2', 'number between 0 and 10000', options.m2);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (is.defined(options.x1)) {
|
|
275
|
+
if (is.number(options.x1) && is.inRange(options.x1, 0, 10000)) {
|
|
276
|
+
this.options.sharpenX1 = options.x1;
|
|
277
|
+
} else {
|
|
278
|
+
throw is.invalidParameterError('options.x1', 'number between 0 and 10000', options.x1);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if (is.defined(options.y2)) {
|
|
282
|
+
if (is.number(options.y2) && is.inRange(options.y2, 0, 10000)) {
|
|
283
|
+
this.options.sharpenY2 = options.y2;
|
|
284
|
+
} else {
|
|
285
|
+
throw is.invalidParameterError('options.y2', 'number between 0 and 10000', options.y2);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
if (is.defined(options.y3)) {
|
|
289
|
+
if (is.number(options.y3) && is.inRange(options.y3, 0, 10000)) {
|
|
290
|
+
this.options.sharpenY3 = options.y3;
|
|
291
|
+
} else {
|
|
292
|
+
throw is.invalidParameterError('options.y3', 'number between 0 and 10000', options.y3);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
221
295
|
} else {
|
|
222
|
-
throw is.invalidParameterError('sigma', 'number between 0.01 and 10000',
|
|
296
|
+
throw is.invalidParameterError('sigma', 'number between 0.01 and 10000', options);
|
|
223
297
|
}
|
|
224
298
|
return this;
|
|
225
299
|
}
|
|
@@ -227,6 +301,13 @@ function sharpen (sigma, flat, jagged) {
|
|
|
227
301
|
/**
|
|
228
302
|
* Apply median filter.
|
|
229
303
|
* When used without parameters the default window is 3x3.
|
|
304
|
+
*
|
|
305
|
+
* @example
|
|
306
|
+
* const output = await sharp(input).median().toBuffer();
|
|
307
|
+
*
|
|
308
|
+
* @example
|
|
309
|
+
* const output = await sharp(input).median(5).toBuffer();
|
|
310
|
+
*
|
|
230
311
|
* @param {number} [size=3] square mask size: size x size
|
|
231
312
|
* @returns {Sharp}
|
|
232
313
|
* @throws {Error} Invalid parameters
|
|
@@ -246,8 +327,21 @@ function median (size) {
|
|
|
246
327
|
|
|
247
328
|
/**
|
|
248
329
|
* Blur the image.
|
|
249
|
-
*
|
|
330
|
+
*
|
|
331
|
+
* When used without parameters, performs a fast 3x3 box blur (equivalent to a box linear filter).
|
|
332
|
+
*
|
|
250
333
|
* When a `sigma` is provided, performs a slower, more accurate Gaussian blur.
|
|
334
|
+
*
|
|
335
|
+
* @example
|
|
336
|
+
* const boxBlurred = await sharp(input)
|
|
337
|
+
* .blur()
|
|
338
|
+
* .toBuffer();
|
|
339
|
+
*
|
|
340
|
+
* @example
|
|
341
|
+
* const gaussianBlurred = await sharp(input)
|
|
342
|
+
* .blur(5)
|
|
343
|
+
* .toBuffer();
|
|
344
|
+
*
|
|
251
345
|
* @param {number} [sigma] a value between 0.3 and 1000 representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
|
|
252
346
|
* @returns {Sharp}
|
|
253
347
|
* @throws {Error} Invalid parameters
|
|
@@ -269,7 +363,15 @@ function blur (sigma) {
|
|
|
269
363
|
}
|
|
270
364
|
|
|
271
365
|
/**
|
|
272
|
-
* Merge alpha transparency channel, if any, with a background.
|
|
366
|
+
* Merge alpha transparency channel, if any, with a background, then remove the alpha channel.
|
|
367
|
+
*
|
|
368
|
+
* See also {@link /api-channel#removealpha|removeAlpha}.
|
|
369
|
+
*
|
|
370
|
+
* @example
|
|
371
|
+
* await sharp(rgbaInput)
|
|
372
|
+
* .flatten({ background: '#F0A703' })
|
|
373
|
+
* .toBuffer();
|
|
374
|
+
*
|
|
273
375
|
* @param {Object} [options]
|
|
274
376
|
* @param {string|Object} [options.background={r: 0, g: 0, b: 0}] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black.
|
|
275
377
|
* @returns {Sharp}
|
|
@@ -318,16 +420,39 @@ function gamma (gamma, gammaOut) {
|
|
|
318
420
|
|
|
319
421
|
/**
|
|
320
422
|
* Produce the "negative" of the image.
|
|
321
|
-
*
|
|
423
|
+
*
|
|
424
|
+
* @example
|
|
425
|
+
* const output = await sharp(input)
|
|
426
|
+
* .negate()
|
|
427
|
+
* .toBuffer();
|
|
428
|
+
*
|
|
429
|
+
* @example
|
|
430
|
+
* const output = await sharp(input)
|
|
431
|
+
* .negate({ alpha: false })
|
|
432
|
+
* .toBuffer();
|
|
433
|
+
*
|
|
434
|
+
* @param {Object} [options]
|
|
435
|
+
* @param {Boolean} [options.alpha=true] Whether or not to negate any alpha channel
|
|
322
436
|
* @returns {Sharp}
|
|
323
437
|
*/
|
|
324
|
-
function negate (
|
|
325
|
-
this.options.negate = is.bool(
|
|
438
|
+
function negate (options) {
|
|
439
|
+
this.options.negate = is.bool(options) ? options : true;
|
|
440
|
+
if (is.plainObject(options) && 'alpha' in options) {
|
|
441
|
+
if (!is.bool(options.alpha)) {
|
|
442
|
+
throw is.invalidParameterError('alpha', 'should be boolean value', options.alpha);
|
|
443
|
+
} else {
|
|
444
|
+
this.options.negateAlpha = options.alpha;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
326
447
|
return this;
|
|
327
448
|
}
|
|
328
449
|
|
|
329
450
|
/**
|
|
330
451
|
* Enhance output image contrast by stretching its luminance to cover the full dynamic range.
|
|
452
|
+
*
|
|
453
|
+
* @example
|
|
454
|
+
* const output = await sharp(input).normalise().toBuffer();
|
|
455
|
+
*
|
|
331
456
|
* @param {Boolean} [normalise=true]
|
|
332
457
|
* @returns {Sharp}
|
|
333
458
|
*/
|
|
@@ -338,6 +463,10 @@ function normalise (normalise) {
|
|
|
338
463
|
|
|
339
464
|
/**
|
|
340
465
|
* Alternative spelling of normalise.
|
|
466
|
+
*
|
|
467
|
+
* @example
|
|
468
|
+
* const output = await sharp(input).normalize().toBuffer();
|
|
469
|
+
*
|
|
341
470
|
* @param {Boolean} [normalize=true]
|
|
342
471
|
* @returns {Sharp}
|
|
343
472
|
*/
|
|
@@ -345,6 +474,55 @@ function normalize (normalize) {
|
|
|
345
474
|
return this.normalise(normalize);
|
|
346
475
|
}
|
|
347
476
|
|
|
477
|
+
/**
|
|
478
|
+
* Perform contrast limiting adaptive histogram equalization
|
|
479
|
+
* {@link https://en.wikipedia.org/wiki/Adaptive_histogram_equalization#Contrast_Limited_AHE|CLAHE}.
|
|
480
|
+
*
|
|
481
|
+
* This will, in general, enhance the clarity of the image by bringing out darker details.
|
|
482
|
+
*
|
|
483
|
+
* @since 0.28.3
|
|
484
|
+
*
|
|
485
|
+
* @example
|
|
486
|
+
* const output = await sharp(input)
|
|
487
|
+
* .clahe({
|
|
488
|
+
* width: 3,
|
|
489
|
+
* height: 3,
|
|
490
|
+
* })
|
|
491
|
+
* .toBuffer();
|
|
492
|
+
*
|
|
493
|
+
* @param {Object} options
|
|
494
|
+
* @param {number} options.width - integer width of the region in pixels.
|
|
495
|
+
* @param {number} options.height - integer height of the region in pixels.
|
|
496
|
+
* @param {number} [options.maxSlope=3] - maximum value for the slope of the
|
|
497
|
+
* cumulative histogram. A value of 0 disables contrast limiting. Valid values
|
|
498
|
+
* are integers in the range 0-100 (inclusive)
|
|
499
|
+
* @returns {Sharp}
|
|
500
|
+
* @throws {Error} Invalid parameters
|
|
501
|
+
*/
|
|
502
|
+
function clahe (options) {
|
|
503
|
+
if (!is.plainObject(options)) {
|
|
504
|
+
throw is.invalidParameterError('options', 'plain object', options);
|
|
505
|
+
}
|
|
506
|
+
if (!('width' in options) || !is.integer(options.width) || options.width <= 0) {
|
|
507
|
+
throw is.invalidParameterError('width', 'integer above zero', options.width);
|
|
508
|
+
} else {
|
|
509
|
+
this.options.claheWidth = options.width;
|
|
510
|
+
}
|
|
511
|
+
if (!('height' in options) || !is.integer(options.height) || options.height <= 0) {
|
|
512
|
+
throw is.invalidParameterError('height', 'integer above zero', options.height);
|
|
513
|
+
} else {
|
|
514
|
+
this.options.claheHeight = options.height;
|
|
515
|
+
}
|
|
516
|
+
if (!is.defined(options.maxSlope)) {
|
|
517
|
+
this.options.claheMaxSlope = 3;
|
|
518
|
+
} else if (!is.integer(options.maxSlope) || options.maxSlope < 0 || options.maxSlope > 100) {
|
|
519
|
+
throw is.invalidParameterError('maxSlope', 'integer 0-100', options.maxSlope);
|
|
520
|
+
} else {
|
|
521
|
+
this.options.claheMaxSlope = options.maxSlope;
|
|
522
|
+
}
|
|
523
|
+
return this;
|
|
524
|
+
}
|
|
525
|
+
|
|
348
526
|
/**
|
|
349
527
|
* Convolve the image with the specified kernel.
|
|
350
528
|
*
|
|
@@ -363,7 +541,7 @@ function normalize (normalize) {
|
|
|
363
541
|
*
|
|
364
542
|
* @param {Object} kernel
|
|
365
543
|
* @param {number} kernel.width - width of the kernel in pixels.
|
|
366
|
-
* @param {number} kernel.height -
|
|
544
|
+
* @param {number} kernel.height - height of the kernel in pixels.
|
|
367
545
|
* @param {Array<number>} kernel.kernel - Array of length `width*height` containing the kernel values.
|
|
368
546
|
* @param {number} [kernel.scale=sum] - the scale of the kernel in pixels.
|
|
369
547
|
* @param {number} [kernel.offset=0] - the offset of the kernel in pixels.
|
|
@@ -397,7 +575,7 @@ function convolve (kernel) {
|
|
|
397
575
|
}
|
|
398
576
|
|
|
399
577
|
/**
|
|
400
|
-
* Any pixel value
|
|
578
|
+
* Any pixel value greater than or equal to the threshold value will be set to 255, otherwise it will be set to 0.
|
|
401
579
|
* @param {number} [threshold=128] - a value in the range 0-255 representing the level at which the threshold will be applied.
|
|
402
580
|
* @param {Object} [options]
|
|
403
581
|
* @param {Boolean} [options.greyscale=true] - convert to single channel greyscale.
|
|
@@ -514,33 +692,51 @@ function recomb (inputMatrix) {
|
|
|
514
692
|
}
|
|
515
693
|
|
|
516
694
|
/**
|
|
517
|
-
* Transforms the image using brightness, saturation
|
|
695
|
+
* Transforms the image using brightness, saturation, hue rotation, and lightness.
|
|
696
|
+
* Brightness and lightness both operate on luminance, with the difference being that
|
|
697
|
+
* brightness is multiplicative whereas lightness is additive.
|
|
518
698
|
*
|
|
519
699
|
* @since 0.22.1
|
|
520
700
|
*
|
|
521
701
|
* @example
|
|
522
|
-
*
|
|
702
|
+
* // increase brightness by a factor of 2
|
|
703
|
+
* const output = await sharp(input)
|
|
523
704
|
* .modulate({
|
|
524
|
-
* brightness: 2
|
|
525
|
-
* })
|
|
705
|
+
* brightness: 2
|
|
706
|
+
* })
|
|
707
|
+
* .toBuffer();
|
|
526
708
|
*
|
|
527
|
-
*
|
|
709
|
+
* @example
|
|
710
|
+
* // hue-rotate by 180 degrees
|
|
711
|
+
* const output = await sharp(input)
|
|
528
712
|
* .modulate({
|
|
529
|
-
* hue: 180
|
|
530
|
-
* })
|
|
713
|
+
* hue: 180
|
|
714
|
+
* })
|
|
715
|
+
* .toBuffer();
|
|
531
716
|
*
|
|
717
|
+
* @example
|
|
718
|
+
* // increase lightness by +50
|
|
719
|
+
* const output = await sharp(input)
|
|
720
|
+
* .modulate({
|
|
721
|
+
* lightness: 50
|
|
722
|
+
* })
|
|
723
|
+
* .toBuffer();
|
|
724
|
+
*
|
|
725
|
+
* @example
|
|
532
726
|
* // decreate brightness and saturation while also hue-rotating by 90 degrees
|
|
533
|
-
* sharp(input)
|
|
727
|
+
* const output = await sharp(input)
|
|
534
728
|
* .modulate({
|
|
535
729
|
* brightness: 0.5,
|
|
536
730
|
* saturation: 0.5,
|
|
537
|
-
* hue: 90
|
|
538
|
-
* })
|
|
731
|
+
* hue: 90,
|
|
732
|
+
* })
|
|
733
|
+
* .toBuffer();
|
|
539
734
|
*
|
|
540
735
|
* @param {Object} [options]
|
|
541
736
|
* @param {number} [options.brightness] Brightness multiplier
|
|
542
737
|
* @param {number} [options.saturation] Saturation multiplier
|
|
543
738
|
* @param {number} [options.hue] Degrees for hue rotation
|
|
739
|
+
* @param {number} [options.lightness] Lightness addend
|
|
544
740
|
* @returns {Sharp}
|
|
545
741
|
*/
|
|
546
742
|
function modulate (options) {
|
|
@@ -568,6 +764,13 @@ function modulate (options) {
|
|
|
568
764
|
throw is.invalidParameterError('hue', 'number', options.hue);
|
|
569
765
|
}
|
|
570
766
|
}
|
|
767
|
+
if ('lightness' in options) {
|
|
768
|
+
if (is.number(options.lightness)) {
|
|
769
|
+
this.options.lightness = options.lightness;
|
|
770
|
+
} else {
|
|
771
|
+
throw is.invalidParameterError('lightness', 'number', options.lightness);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
571
774
|
return this;
|
|
572
775
|
}
|
|
573
776
|
|
|
@@ -589,6 +792,7 @@ module.exports = function (Sharp) {
|
|
|
589
792
|
negate,
|
|
590
793
|
normalise,
|
|
591
794
|
normalize,
|
|
795
|
+
clahe,
|
|
592
796
|
convolve,
|
|
593
797
|
threshold,
|
|
594
798
|
boolean,
|