@revizly/sharp 0.33.2-revizly3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,921 @@
1
+ // Copyright 2013 Lovell Fuller and others.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ 'use strict';
5
+
6
+ const color = require('color');
7
+ const is = require('./is');
8
+
9
+ /**
10
+ * Rotate the output image by either an explicit angle
11
+ * or auto-orient based on the EXIF `Orientation` tag.
12
+ *
13
+ * If an angle is provided, it is converted to a valid positive degree rotation.
14
+ * For example, `-450` will produce a 270 degree rotation.
15
+ *
16
+ * When rotating by an angle other than a multiple of 90,
17
+ * the background colour can be provided with the `background` option.
18
+ *
19
+ * If no angle is provided, it is determined from the EXIF data.
20
+ * Mirroring is supported and may infer the use of a flip operation.
21
+ *
22
+ * The use of `rotate` without an angle will remove the EXIF `Orientation` tag, if any.
23
+ *
24
+ * Only one rotation can occur per pipeline.
25
+ * Previous calls to `rotate` in the same pipeline will be ignored.
26
+ *
27
+ * Multi-page images can only be rotated by 180 degrees.
28
+ *
29
+ * Method order is important when rotating, resizing and/or extracting regions,
30
+ * for example `.rotate(x).extract(y)` will produce a different result to `.extract(y).rotate(x)`.
31
+ *
32
+ * @example
33
+ * const pipeline = sharp()
34
+ * .rotate()
35
+ * .resize(null, 200)
36
+ * .toBuffer(function (err, outputBuffer, info) {
37
+ * // outputBuffer contains 200px high JPEG image data,
38
+ * // auto-rotated using EXIF Orientation tag
39
+ * // info.width and info.height contain the dimensions of the resized image
40
+ * });
41
+ * readableStream.pipe(pipeline);
42
+ *
43
+ * @example
44
+ * const rotateThenResize = await sharp(input)
45
+ * .rotate(90)
46
+ * .resize({ width: 16, height: 8, fit: 'fill' })
47
+ * .toBuffer();
48
+ * const resizeThenRotate = await sharp(input)
49
+ * .resize({ width: 16, height: 8, fit: 'fill' })
50
+ * .rotate(90)
51
+ * .toBuffer();
52
+ *
53
+ * @param {number} [angle=auto] angle of rotation.
54
+ * @param {Object} [options] - if present, is an Object with optional attributes.
55
+ * @param {string|Object} [options.background="#000000"] parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
56
+ * @returns {Sharp}
57
+ * @throws {Error} Invalid parameters
58
+ */
59
+ function rotate (angle, options) {
60
+ if (this.options.useExifOrientation || this.options.angle || this.options.rotationAngle) {
61
+ this.options.debuglog('ignoring previous rotate options');
62
+ }
63
+ if (!is.defined(angle)) {
64
+ this.options.useExifOrientation = true;
65
+ } else if (is.integer(angle) && !(angle % 90)) {
66
+ this.options.angle = angle;
67
+ } else if (is.number(angle)) {
68
+ this.options.rotationAngle = angle;
69
+ if (is.object(options) && options.background) {
70
+ const backgroundColour = color(options.background);
71
+ this.options.rotationBackground = [
72
+ backgroundColour.red(),
73
+ backgroundColour.green(),
74
+ backgroundColour.blue(),
75
+ Math.round(backgroundColour.alpha() * 255)
76
+ ];
77
+ }
78
+ } else {
79
+ throw is.invalidParameterError('angle', 'numeric', angle);
80
+ }
81
+ return this;
82
+ }
83
+
84
+ /**
85
+ * Mirror the image vertically (up-down) about the x-axis.
86
+ * This always occurs before rotation, if any.
87
+ *
88
+ * This operation does not work correctly with multi-page images.
89
+ *
90
+ * @example
91
+ * const output = await sharp(input).flip().toBuffer();
92
+ *
93
+ * @param {Boolean} [flip=true]
94
+ * @returns {Sharp}
95
+ */
96
+ function flip (flip) {
97
+ this.options.flip = is.bool(flip) ? flip : true;
98
+ return this;
99
+ }
100
+
101
+ /**
102
+ * Mirror the image horizontally (left-right) about the y-axis.
103
+ * This always occurs before rotation, if any.
104
+ *
105
+ * @example
106
+ * const output = await sharp(input).flop().toBuffer();
107
+ *
108
+ * @param {Boolean} [flop=true]
109
+ * @returns {Sharp}
110
+ */
111
+ function flop (flop) {
112
+ this.options.flop = is.bool(flop) ? flop : true;
113
+ return this;
114
+ }
115
+
116
+ /**
117
+ * Perform an affine transform on an image. This operation will always occur after resizing, extraction and rotation, if any.
118
+ *
119
+ * You must provide an array of length 4 or a 2x2 affine transformation matrix.
120
+ * By default, new pixels are filled with a black background. You can provide a background color with the `background` option.
121
+ * A particular interpolator may also be specified. Set the `interpolator` option to an attribute of the `sharp.interpolators` Object e.g. `sharp.interpolators.nohalo`.
122
+ *
123
+ * In the case of a 2x2 matrix, the transform is:
124
+ * - X = `matrix[0, 0]` \* (x + `idx`) + `matrix[0, 1]` \* (y + `idy`) + `odx`
125
+ * - Y = `matrix[1, 0]` \* (x + `idx`) + `matrix[1, 1]` \* (y + `idy`) + `ody`
126
+ *
127
+ * where:
128
+ * - x and y are the coordinates in input image.
129
+ * - X and Y are the coordinates in output image.
130
+ * - (0,0) is the upper left corner.
131
+ *
132
+ * @since 0.27.0
133
+ *
134
+ * @example
135
+ * const pipeline = sharp()
136
+ * .affine([[1, 0.3], [0.1, 0.7]], {
137
+ * background: 'white',
138
+ * interpolator: sharp.interpolators.nohalo
139
+ * })
140
+ * .toBuffer((err, outputBuffer, info) => {
141
+ * // outputBuffer contains the transformed image
142
+ * // info.width and info.height contain the new dimensions
143
+ * });
144
+ *
145
+ * inputStream
146
+ * .pipe(pipeline);
147
+ *
148
+ * @param {Array<Array<number>>|Array<number>} matrix - affine transformation matrix
149
+ * @param {Object} [options] - if present, is an Object with optional attributes.
150
+ * @param {String|Object} [options.background="#000000"] - parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
151
+ * @param {Number} [options.idx=0] - input horizontal offset
152
+ * @param {Number} [options.idy=0] - input vertical offset
153
+ * @param {Number} [options.odx=0] - output horizontal offset
154
+ * @param {Number} [options.ody=0] - output vertical offset
155
+ * @param {String} [options.interpolator=sharp.interpolators.bicubic] - interpolator
156
+ * @returns {Sharp}
157
+ * @throws {Error} Invalid parameters
158
+ */
159
+ function affine (matrix, options) {
160
+ const flatMatrix = [].concat(...matrix);
161
+ if (flatMatrix.length === 4 && flatMatrix.every(is.number)) {
162
+ this.options.affineMatrix = flatMatrix;
163
+ } else {
164
+ throw is.invalidParameterError('matrix', '1x4 or 2x2 array', matrix);
165
+ }
166
+
167
+ if (is.defined(options)) {
168
+ if (is.object(options)) {
169
+ this._setBackgroundColourOption('affineBackground', options.background);
170
+ if (is.defined(options.idx)) {
171
+ if (is.number(options.idx)) {
172
+ this.options.affineIdx = options.idx;
173
+ } else {
174
+ throw is.invalidParameterError('options.idx', 'number', options.idx);
175
+ }
176
+ }
177
+ if (is.defined(options.idy)) {
178
+ if (is.number(options.idy)) {
179
+ this.options.affineIdy = options.idy;
180
+ } else {
181
+ throw is.invalidParameterError('options.idy', 'number', options.idy);
182
+ }
183
+ }
184
+ if (is.defined(options.odx)) {
185
+ if (is.number(options.odx)) {
186
+ this.options.affineOdx = options.odx;
187
+ } else {
188
+ throw is.invalidParameterError('options.odx', 'number', options.odx);
189
+ }
190
+ }
191
+ if (is.defined(options.ody)) {
192
+ if (is.number(options.ody)) {
193
+ this.options.affineOdy = options.ody;
194
+ } else {
195
+ throw is.invalidParameterError('options.ody', 'number', options.ody);
196
+ }
197
+ }
198
+ if (is.defined(options.interpolator)) {
199
+ if (is.inArray(options.interpolator, Object.values(this.constructor.interpolators))) {
200
+ this.options.affineInterpolator = options.interpolator;
201
+ } else {
202
+ throw is.invalidParameterError('options.interpolator', 'valid interpolator name', options.interpolator);
203
+ }
204
+ }
205
+ } else {
206
+ throw is.invalidParameterError('options', 'object', options);
207
+ }
208
+ }
209
+
210
+ return this;
211
+ }
212
+
213
+ /**
214
+ * Sharpen the image.
215
+ *
216
+ * When used without parameters, performs a fast, mild sharpen of the output image.
217
+ *
218
+ * When a `sigma` is provided, performs a slower, more accurate sharpen of the L channel in the LAB colour space.
219
+ * Fine-grained control over the level of sharpening in "flat" (m1) and "jagged" (m2) areas is available.
220
+ *
221
+ * See {@link https://www.libvips.org/API/current/libvips-convolution.html#vips-sharpen|libvips sharpen} operation.
222
+ *
223
+ * @example
224
+ * const data = await sharp(input).sharpen().toBuffer();
225
+ *
226
+ * @example
227
+ * const data = await sharp(input).sharpen({ sigma: 2 }).toBuffer();
228
+ *
229
+ * @example
230
+ * const data = await sharp(input)
231
+ * .sharpen({
232
+ * sigma: 2,
233
+ * m1: 0,
234
+ * m2: 3,
235
+ * x1: 3,
236
+ * y2: 15,
237
+ * y3: 15,
238
+ * })
239
+ * .toBuffer();
240
+ *
241
+ * @param {Object|number} [options] - if present, is an Object with attributes
242
+ * @param {number} [options.sigma] - the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`, between 0.000001 and 10
243
+ * @param {number} [options.m1=1.0] - the level of sharpening to apply to "flat" areas, between 0 and 1000000
244
+ * @param {number} [options.m2=2.0] - the level of sharpening to apply to "jagged" areas, between 0 and 1000000
245
+ * @param {number} [options.x1=2.0] - threshold between "flat" and "jagged", between 0 and 1000000
246
+ * @param {number} [options.y2=10.0] - maximum amount of brightening, between 0 and 1000000
247
+ * @param {number} [options.y3=20.0] - maximum amount of darkening, between 0 and 1000000
248
+ * @param {number} [flat] - (deprecated) see `options.m1`.
249
+ * @param {number} [jagged] - (deprecated) see `options.m2`.
250
+ * @returns {Sharp}
251
+ * @throws {Error} Invalid parameters
252
+ */
253
+ function sharpen (options, flat, jagged) {
254
+ if (!is.defined(options)) {
255
+ // No arguments: default to mild sharpen
256
+ this.options.sharpenSigma = -1;
257
+ } else if (is.bool(options)) {
258
+ // Deprecated boolean argument: apply mild sharpen?
259
+ this.options.sharpenSigma = options ? -1 : 0;
260
+ } else if (is.number(options) && is.inRange(options, 0.01, 10000)) {
261
+ // Deprecated numeric argument: specific sigma
262
+ this.options.sharpenSigma = options;
263
+ // Deprecated control over flat areas
264
+ if (is.defined(flat)) {
265
+ if (is.number(flat) && is.inRange(flat, 0, 10000)) {
266
+ this.options.sharpenM1 = flat;
267
+ } else {
268
+ throw is.invalidParameterError('flat', 'number between 0 and 10000', flat);
269
+ }
270
+ }
271
+ // Deprecated control over jagged areas
272
+ if (is.defined(jagged)) {
273
+ if (is.number(jagged) && is.inRange(jagged, 0, 10000)) {
274
+ this.options.sharpenM2 = jagged;
275
+ } else {
276
+ throw is.invalidParameterError('jagged', 'number between 0 and 10000', jagged);
277
+ }
278
+ }
279
+ } else if (is.plainObject(options)) {
280
+ if (is.number(options.sigma) && is.inRange(options.sigma, 0.000001, 10)) {
281
+ this.options.sharpenSigma = options.sigma;
282
+ } else {
283
+ throw is.invalidParameterError('options.sigma', 'number between 0.000001 and 10', options.sigma);
284
+ }
285
+ if (is.defined(options.m1)) {
286
+ if (is.number(options.m1) && is.inRange(options.m1, 0, 1000000)) {
287
+ this.options.sharpenM1 = options.m1;
288
+ } else {
289
+ throw is.invalidParameterError('options.m1', 'number between 0 and 1000000', options.m1);
290
+ }
291
+ }
292
+ if (is.defined(options.m2)) {
293
+ if (is.number(options.m2) && is.inRange(options.m2, 0, 1000000)) {
294
+ this.options.sharpenM2 = options.m2;
295
+ } else {
296
+ throw is.invalidParameterError('options.m2', 'number between 0 and 1000000', options.m2);
297
+ }
298
+ }
299
+ if (is.defined(options.x1)) {
300
+ if (is.number(options.x1) && is.inRange(options.x1, 0, 1000000)) {
301
+ this.options.sharpenX1 = options.x1;
302
+ } else {
303
+ throw is.invalidParameterError('options.x1', 'number between 0 and 1000000', options.x1);
304
+ }
305
+ }
306
+ if (is.defined(options.y2)) {
307
+ if (is.number(options.y2) && is.inRange(options.y2, 0, 1000000)) {
308
+ this.options.sharpenY2 = options.y2;
309
+ } else {
310
+ throw is.invalidParameterError('options.y2', 'number between 0 and 1000000', options.y2);
311
+ }
312
+ }
313
+ if (is.defined(options.y3)) {
314
+ if (is.number(options.y3) && is.inRange(options.y3, 0, 1000000)) {
315
+ this.options.sharpenY3 = options.y3;
316
+ } else {
317
+ throw is.invalidParameterError('options.y3', 'number between 0 and 1000000', options.y3);
318
+ }
319
+ }
320
+ } else {
321
+ throw is.invalidParameterError('sigma', 'number between 0.01 and 10000', options);
322
+ }
323
+ return this;
324
+ }
325
+
326
+ /**
327
+ * Apply median filter.
328
+ * When used without parameters the default window is 3x3.
329
+ *
330
+ * @example
331
+ * const output = await sharp(input).median().toBuffer();
332
+ *
333
+ * @example
334
+ * const output = await sharp(input).median(5).toBuffer();
335
+ *
336
+ * @param {number} [size=3] square mask size: size x size
337
+ * @returns {Sharp}
338
+ * @throws {Error} Invalid parameters
339
+ */
340
+ function median (size) {
341
+ if (!is.defined(size)) {
342
+ // No arguments: default to 3x3
343
+ this.options.medianSize = 3;
344
+ } else if (is.integer(size) && is.inRange(size, 1, 1000)) {
345
+ // Numeric argument: specific sigma
346
+ this.options.medianSize = size;
347
+ } else {
348
+ throw is.invalidParameterError('size', 'integer between 1 and 1000', size);
349
+ }
350
+ return this;
351
+ }
352
+
353
+ /**
354
+ * Blur the image.
355
+ *
356
+ * When used without parameters, performs a fast 3x3 box blur (equivalent to a box linear filter).
357
+ *
358
+ * When a `sigma` is provided, performs a slower, more accurate Gaussian blur.
359
+ *
360
+ * @example
361
+ * const boxBlurred = await sharp(input)
362
+ * .blur()
363
+ * .toBuffer();
364
+ *
365
+ * @example
366
+ * const gaussianBlurred = await sharp(input)
367
+ * .blur(5)
368
+ * .toBuffer();
369
+ *
370
+ * @param {number} [sigma] a value between 0.3 and 1000 representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
371
+ * @returns {Sharp}
372
+ * @throws {Error} Invalid parameters
373
+ */
374
+ function blur (sigma) {
375
+ if (!is.defined(sigma)) {
376
+ // No arguments: default to mild blur
377
+ this.options.blurSigma = -1;
378
+ } else if (is.bool(sigma)) {
379
+ // Boolean argument: apply mild blur?
380
+ this.options.blurSigma = sigma ? -1 : 0;
381
+ } else if (is.number(sigma) && is.inRange(sigma, 0.3, 1000)) {
382
+ // Numeric argument: specific sigma
383
+ this.options.blurSigma = sigma;
384
+ } else {
385
+ throw is.invalidParameterError('sigma', 'number between 0.3 and 1000', sigma);
386
+ }
387
+ return this;
388
+ }
389
+
390
+ /**
391
+ * Merge alpha transparency channel, if any, with a background, then remove the alpha channel.
392
+ *
393
+ * See also {@link /api-channel#removealpha|removeAlpha}.
394
+ *
395
+ * @example
396
+ * await sharp(rgbaInput)
397
+ * .flatten({ background: '#F0A703' })
398
+ * .toBuffer();
399
+ *
400
+ * @param {Object} [options]
401
+ * @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.
402
+ * @returns {Sharp}
403
+ */
404
+ function flatten (options) {
405
+ this.options.flatten = is.bool(options) ? options : true;
406
+ if (is.object(options)) {
407
+ this._setBackgroundColourOption('flattenBackground', options.background);
408
+ }
409
+ return this;
410
+ }
411
+
412
+ /**
413
+ * Ensure the image has an alpha channel
414
+ * with all white pixel values made fully transparent.
415
+ *
416
+ * Existing alpha channel values for non-white pixels remain unchanged.
417
+ *
418
+ * This feature is experimental and the API may change.
419
+ *
420
+ * @since 0.32.1
421
+ *
422
+ * @example
423
+ * await sharp(rgbInput)
424
+ * .unflatten()
425
+ * .toBuffer();
426
+ *
427
+ * @example
428
+ * await sharp(rgbInput)
429
+ * .threshold(128, { grayscale: false }) // converter bright pixels to white
430
+ * .unflatten()
431
+ * .toBuffer();
432
+ */
433
+ function unflatten () {
434
+ this.options.unflatten = true;
435
+ return this;
436
+ }
437
+
438
+ /**
439
+ * Apply a gamma correction by reducing the encoding (darken) pre-resize at a factor of `1/gamma`
440
+ * then increasing the encoding (brighten) post-resize at a factor of `gamma`.
441
+ * This can improve the perceived brightness of a resized image in non-linear colour spaces.
442
+ * JPEG and WebP input images will not take advantage of the shrink-on-load performance optimisation
443
+ * when applying a gamma correction.
444
+ *
445
+ * Supply a second argument to use a different output gamma value, otherwise the first value is used in both cases.
446
+ *
447
+ * @param {number} [gamma=2.2] value between 1.0 and 3.0.
448
+ * @param {number} [gammaOut] value between 1.0 and 3.0. (optional, defaults to same as `gamma`)
449
+ * @returns {Sharp}
450
+ * @throws {Error} Invalid parameters
451
+ */
452
+ function gamma (gamma, gammaOut) {
453
+ if (!is.defined(gamma)) {
454
+ // Default gamma correction of 2.2 (sRGB)
455
+ this.options.gamma = 2.2;
456
+ } else if (is.number(gamma) && is.inRange(gamma, 1, 3)) {
457
+ this.options.gamma = gamma;
458
+ } else {
459
+ throw is.invalidParameterError('gamma', 'number between 1.0 and 3.0', gamma);
460
+ }
461
+ if (!is.defined(gammaOut)) {
462
+ // Default gamma correction for output is same as input
463
+ this.options.gammaOut = this.options.gamma;
464
+ } else if (is.number(gammaOut) && is.inRange(gammaOut, 1, 3)) {
465
+ this.options.gammaOut = gammaOut;
466
+ } else {
467
+ throw is.invalidParameterError('gammaOut', 'number between 1.0 and 3.0', gammaOut);
468
+ }
469
+ return this;
470
+ }
471
+
472
+ /**
473
+ * Produce the "negative" of the image.
474
+ *
475
+ * @example
476
+ * const output = await sharp(input)
477
+ * .negate()
478
+ * .toBuffer();
479
+ *
480
+ * @example
481
+ * const output = await sharp(input)
482
+ * .negate({ alpha: false })
483
+ * .toBuffer();
484
+ *
485
+ * @param {Object} [options]
486
+ * @param {Boolean} [options.alpha=true] Whether or not to negate any alpha channel
487
+ * @returns {Sharp}
488
+ */
489
+ function negate (options) {
490
+ this.options.negate = is.bool(options) ? options : true;
491
+ if (is.plainObject(options) && 'alpha' in options) {
492
+ if (!is.bool(options.alpha)) {
493
+ throw is.invalidParameterError('alpha', 'should be boolean value', options.alpha);
494
+ } else {
495
+ this.options.negateAlpha = options.alpha;
496
+ }
497
+ }
498
+ return this;
499
+ }
500
+
501
+ /**
502
+ * Enhance output image contrast by stretching its luminance to cover a full dynamic range.
503
+ *
504
+ * Uses a histogram-based approach, taking a default range of 1% to 99% to reduce sensitivity to noise at the extremes.
505
+ *
506
+ * Luminance values below the `lower` percentile will be underexposed by clipping to zero.
507
+ * Luminance values above the `upper` percentile will be overexposed by clipping to the max pixel value.
508
+ *
509
+ * @example
510
+ * const output = await sharp(input)
511
+ * .normalise()
512
+ * .toBuffer();
513
+ *
514
+ * @example
515
+ * const output = await sharp(input)
516
+ * .normalise({ lower: 0, upper: 100 })
517
+ * .toBuffer();
518
+ *
519
+ * @param {Object} [options]
520
+ * @param {number} [options.lower=1] - Percentile below which luminance values will be underexposed.
521
+ * @param {number} [options.upper=99] - Percentile above which luminance values will be overexposed.
522
+ * @returns {Sharp}
523
+ */
524
+ function normalise (options) {
525
+ if (is.plainObject(options)) {
526
+ if (is.defined(options.lower)) {
527
+ if (is.number(options.lower) && is.inRange(options.lower, 0, 99)) {
528
+ this.options.normaliseLower = options.lower;
529
+ } else {
530
+ throw is.invalidParameterError('lower', 'number between 0 and 99', options.lower);
531
+ }
532
+ }
533
+ if (is.defined(options.upper)) {
534
+ if (is.number(options.upper) && is.inRange(options.upper, 1, 100)) {
535
+ this.options.normaliseUpper = options.upper;
536
+ } else {
537
+ throw is.invalidParameterError('upper', 'number between 1 and 100', options.upper);
538
+ }
539
+ }
540
+ }
541
+ if (this.options.normaliseLower >= this.options.normaliseUpper) {
542
+ throw is.invalidParameterError('range', 'lower to be less than upper',
543
+ `${this.options.normaliseLower} >= ${this.options.normaliseUpper}`);
544
+ }
545
+ this.options.normalise = true;
546
+ return this;
547
+ }
548
+
549
+ /**
550
+ * Alternative spelling of normalise.
551
+ *
552
+ * @example
553
+ * const output = await sharp(input)
554
+ * .normalize()
555
+ * .toBuffer();
556
+ *
557
+ * @param {Object} [options]
558
+ * @param {number} [options.lower=1] - Percentile below which luminance values will be underexposed.
559
+ * @param {number} [options.upper=99] - Percentile above which luminance values will be overexposed.
560
+ * @returns {Sharp}
561
+ */
562
+ function normalize (options) {
563
+ return this.normalise(options);
564
+ }
565
+
566
+ /**
567
+ * Perform contrast limiting adaptive histogram equalization
568
+ * {@link https://en.wikipedia.org/wiki/Adaptive_histogram_equalization#Contrast_Limited_AHE|CLAHE}.
569
+ *
570
+ * This will, in general, enhance the clarity of the image by bringing out darker details.
571
+ *
572
+ * @since 0.28.3
573
+ *
574
+ * @example
575
+ * const output = await sharp(input)
576
+ * .clahe({
577
+ * width: 3,
578
+ * height: 3,
579
+ * })
580
+ * .toBuffer();
581
+ *
582
+ * @param {Object} options
583
+ * @param {number} options.width - Integral width of the search window, in pixels.
584
+ * @param {number} options.height - Integral height of the search window, in pixels.
585
+ * @param {number} [options.maxSlope=3] - Integral level of brightening, between 0 and 100, where 0 disables contrast limiting.
586
+ * @returns {Sharp}
587
+ * @throws {Error} Invalid parameters
588
+ */
589
+ function clahe (options) {
590
+ if (is.plainObject(options)) {
591
+ if (is.integer(options.width) && options.width > 0) {
592
+ this.options.claheWidth = options.width;
593
+ } else {
594
+ throw is.invalidParameterError('width', 'integer greater than zero', options.width);
595
+ }
596
+ if (is.integer(options.height) && options.height > 0) {
597
+ this.options.claheHeight = options.height;
598
+ } else {
599
+ throw is.invalidParameterError('height', 'integer greater than zero', options.height);
600
+ }
601
+ if (is.defined(options.maxSlope)) {
602
+ if (is.integer(options.maxSlope) && is.inRange(options.maxSlope, 0, 100)) {
603
+ this.options.claheMaxSlope = options.maxSlope;
604
+ } else {
605
+ throw is.invalidParameterError('maxSlope', 'integer between 0 and 100', options.maxSlope);
606
+ }
607
+ }
608
+ } else {
609
+ throw is.invalidParameterError('options', 'plain object', options);
610
+ }
611
+ return this;
612
+ }
613
+
614
+ /**
615
+ * Convolve the image with the specified kernel.
616
+ *
617
+ * @example
618
+ * sharp(input)
619
+ * .convolve({
620
+ * width: 3,
621
+ * height: 3,
622
+ * kernel: [-1, 0, 1, -2, 0, 2, -1, 0, 1]
623
+ * })
624
+ * .raw()
625
+ * .toBuffer(function(err, data, info) {
626
+ * // data contains the raw pixel data representing the convolution
627
+ * // of the input image with the horizontal Sobel operator
628
+ * });
629
+ *
630
+ * @param {Object} kernel
631
+ * @param {number} kernel.width - width of the kernel in pixels.
632
+ * @param {number} kernel.height - height of the kernel in pixels.
633
+ * @param {Array<number>} kernel.kernel - Array of length `width*height` containing the kernel values.
634
+ * @param {number} [kernel.scale=sum] - the scale of the kernel in pixels.
635
+ * @param {number} [kernel.offset=0] - the offset of the kernel in pixels.
636
+ * @returns {Sharp}
637
+ * @throws {Error} Invalid parameters
638
+ */
639
+ function convolve (kernel) {
640
+ if (!is.object(kernel) || !Array.isArray(kernel.kernel) ||
641
+ !is.integer(kernel.width) || !is.integer(kernel.height) ||
642
+ !is.inRange(kernel.width, 3, 1001) || !is.inRange(kernel.height, 3, 1001) ||
643
+ kernel.height * kernel.width !== kernel.kernel.length
644
+ ) {
645
+ // must pass in a kernel
646
+ throw new Error('Invalid convolution kernel');
647
+ }
648
+ // Default scale is sum of kernel values
649
+ if (!is.integer(kernel.scale)) {
650
+ kernel.scale = kernel.kernel.reduce(function (a, b) {
651
+ return a + b;
652
+ }, 0);
653
+ }
654
+ // Clip scale to a minimum value of 1
655
+ if (kernel.scale < 1) {
656
+ kernel.scale = 1;
657
+ }
658
+ if (!is.integer(kernel.offset)) {
659
+ kernel.offset = 0;
660
+ }
661
+ this.options.convKernel = kernel;
662
+ return this;
663
+ }
664
+
665
+ /**
666
+ * Any pixel value greater than or equal to the threshold value will be set to 255, otherwise it will be set to 0.
667
+ * @param {number} [threshold=128] - a value in the range 0-255 representing the level at which the threshold will be applied.
668
+ * @param {Object} [options]
669
+ * @param {Boolean} [options.greyscale=true] - convert to single channel greyscale.
670
+ * @param {Boolean} [options.grayscale=true] - alternative spelling for greyscale.
671
+ * @returns {Sharp}
672
+ * @throws {Error} Invalid parameters
673
+ */
674
+ function threshold (threshold, options) {
675
+ if (!is.defined(threshold)) {
676
+ this.options.threshold = 128;
677
+ } else if (is.bool(threshold)) {
678
+ this.options.threshold = threshold ? 128 : 0;
679
+ } else if (is.integer(threshold) && is.inRange(threshold, 0, 255)) {
680
+ this.options.threshold = threshold;
681
+ } else {
682
+ throw is.invalidParameterError('threshold', 'integer between 0 and 255', threshold);
683
+ }
684
+ if (!is.object(options) || options.greyscale === true || options.grayscale === true) {
685
+ this.options.thresholdGrayscale = true;
686
+ } else {
687
+ this.options.thresholdGrayscale = false;
688
+ }
689
+ return this;
690
+ }
691
+
692
+ /**
693
+ * Perform a bitwise boolean operation with operand image.
694
+ *
695
+ * This operation creates an output image where each pixel is the result of
696
+ * the selected bitwise boolean `operation` between the corresponding pixels of the input images.
697
+ *
698
+ * @param {Buffer|string} operand - Buffer containing image data or string containing the path to an image file.
699
+ * @param {string} operator - one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively.
700
+ * @param {Object} [options]
701
+ * @param {Object} [options.raw] - describes operand when using raw pixel data.
702
+ * @param {number} [options.raw.width]
703
+ * @param {number} [options.raw.height]
704
+ * @param {number} [options.raw.channels]
705
+ * @returns {Sharp}
706
+ * @throws {Error} Invalid parameters
707
+ */
708
+ function boolean (operand, operator, options) {
709
+ this.options.boolean = this._createInputDescriptor(operand, options);
710
+ if (is.string(operator) && is.inArray(operator, ['and', 'or', 'eor'])) {
711
+ this.options.booleanOp = operator;
712
+ } else {
713
+ throw is.invalidParameterError('operator', 'one of: and, or, eor', operator);
714
+ }
715
+ return this;
716
+ }
717
+
718
+ /**
719
+ * Apply the linear formula `a` * input + `b` to the image to adjust image levels.
720
+ *
721
+ * When a single number is provided, it will be used for all image channels.
722
+ * When an array of numbers is provided, the array length must match the number of channels.
723
+ *
724
+ * @example
725
+ * await sharp(input)
726
+ * .linear(0.5, 2)
727
+ * .toBuffer();
728
+ *
729
+ * @example
730
+ * await sharp(rgbInput)
731
+ * .linear(
732
+ * [0.25, 0.5, 0.75],
733
+ * [150, 100, 50]
734
+ * )
735
+ * .toBuffer();
736
+ *
737
+ * @param {(number|number[])} [a=[]] multiplier
738
+ * @param {(number|number[])} [b=[]] offset
739
+ * @returns {Sharp}
740
+ * @throws {Error} Invalid parameters
741
+ */
742
+ function linear (a, b) {
743
+ if (!is.defined(a) && is.number(b)) {
744
+ a = 1.0;
745
+ } else if (is.number(a) && !is.defined(b)) {
746
+ b = 0.0;
747
+ }
748
+ if (!is.defined(a)) {
749
+ this.options.linearA = [];
750
+ } else if (is.number(a)) {
751
+ this.options.linearA = [a];
752
+ } else if (Array.isArray(a) && a.length && a.every(is.number)) {
753
+ this.options.linearA = a;
754
+ } else {
755
+ throw is.invalidParameterError('a', 'number or array of numbers', a);
756
+ }
757
+ if (!is.defined(b)) {
758
+ this.options.linearB = [];
759
+ } else if (is.number(b)) {
760
+ this.options.linearB = [b];
761
+ } else if (Array.isArray(b) && b.length && b.every(is.number)) {
762
+ this.options.linearB = b;
763
+ } else {
764
+ throw is.invalidParameterError('b', 'number or array of numbers', b);
765
+ }
766
+ if (this.options.linearA.length !== this.options.linearB.length) {
767
+ throw new Error('Expected a and b to be arrays of the same length');
768
+ }
769
+ return this;
770
+ }
771
+
772
+ /**
773
+ * Recombine the image with the specified matrix.
774
+ *
775
+ * @since 0.21.1
776
+ *
777
+ * @example
778
+ * sharp(input)
779
+ * .recomb([
780
+ * [0.3588, 0.7044, 0.1368],
781
+ * [0.2990, 0.5870, 0.1140],
782
+ * [0.2392, 0.4696, 0.0912],
783
+ * ])
784
+ * .raw()
785
+ * .toBuffer(function(err, data, info) {
786
+ * // data contains the raw pixel data after applying the matrix
787
+ * // With this example input, a sepia filter has been applied
788
+ * });
789
+ *
790
+ * @param {Array<Array<number>>} inputMatrix - 3x3 Recombination matrix
791
+ * @returns {Sharp}
792
+ * @throws {Error} Invalid parameters
793
+ */
794
+ function recomb (inputMatrix) {
795
+ if (!Array.isArray(inputMatrix) || inputMatrix.length !== 3 ||
796
+ inputMatrix[0].length !== 3 ||
797
+ inputMatrix[1].length !== 3 ||
798
+ inputMatrix[2].length !== 3
799
+ ) {
800
+ // must pass in a kernel
801
+ throw new Error('Invalid recombination matrix');
802
+ }
803
+ this.options.recombMatrix = [
804
+ inputMatrix[0][0], inputMatrix[0][1], inputMatrix[0][2],
805
+ inputMatrix[1][0], inputMatrix[1][1], inputMatrix[1][2],
806
+ inputMatrix[2][0], inputMatrix[2][1], inputMatrix[2][2]
807
+ ].map(Number);
808
+ return this;
809
+ }
810
+
811
+ /**
812
+ * Transforms the image using brightness, saturation, hue rotation, and lightness.
813
+ * Brightness and lightness both operate on luminance, with the difference being that
814
+ * brightness is multiplicative whereas lightness is additive.
815
+ *
816
+ * @since 0.22.1
817
+ *
818
+ * @example
819
+ * // increase brightness by a factor of 2
820
+ * const output = await sharp(input)
821
+ * .modulate({
822
+ * brightness: 2
823
+ * })
824
+ * .toBuffer();
825
+ *
826
+ * @example
827
+ * // hue-rotate by 180 degrees
828
+ * const output = await sharp(input)
829
+ * .modulate({
830
+ * hue: 180
831
+ * })
832
+ * .toBuffer();
833
+ *
834
+ * @example
835
+ * // increase lightness by +50
836
+ * const output = await sharp(input)
837
+ * .modulate({
838
+ * lightness: 50
839
+ * })
840
+ * .toBuffer();
841
+ *
842
+ * @example
843
+ * // decrease brightness and saturation while also hue-rotating by 90 degrees
844
+ * const output = await sharp(input)
845
+ * .modulate({
846
+ * brightness: 0.5,
847
+ * saturation: 0.5,
848
+ * hue: 90,
849
+ * })
850
+ * .toBuffer();
851
+ *
852
+ * @param {Object} [options]
853
+ * @param {number} [options.brightness] Brightness multiplier
854
+ * @param {number} [options.saturation] Saturation multiplier
855
+ * @param {number} [options.hue] Degrees for hue rotation
856
+ * @param {number} [options.lightness] Lightness addend
857
+ * @returns {Sharp}
858
+ */
859
+ function modulate (options) {
860
+ if (!is.plainObject(options)) {
861
+ throw is.invalidParameterError('options', 'plain object', options);
862
+ }
863
+ if ('brightness' in options) {
864
+ if (is.number(options.brightness) && options.brightness >= 0) {
865
+ this.options.brightness = options.brightness;
866
+ } else {
867
+ throw is.invalidParameterError('brightness', 'number above zero', options.brightness);
868
+ }
869
+ }
870
+ if ('saturation' in options) {
871
+ if (is.number(options.saturation) && options.saturation >= 0) {
872
+ this.options.saturation = options.saturation;
873
+ } else {
874
+ throw is.invalidParameterError('saturation', 'number above zero', options.saturation);
875
+ }
876
+ }
877
+ if ('hue' in options) {
878
+ if (is.integer(options.hue)) {
879
+ this.options.hue = options.hue % 360;
880
+ } else {
881
+ throw is.invalidParameterError('hue', 'number', options.hue);
882
+ }
883
+ }
884
+ if ('lightness' in options) {
885
+ if (is.number(options.lightness)) {
886
+ this.options.lightness = options.lightness;
887
+ } else {
888
+ throw is.invalidParameterError('lightness', 'number', options.lightness);
889
+ }
890
+ }
891
+ return this;
892
+ }
893
+
894
+ /**
895
+ * Decorate the Sharp prototype with operation-related functions.
896
+ * @private
897
+ */
898
+ module.exports = function (Sharp) {
899
+ Object.assign(Sharp.prototype, {
900
+ rotate,
901
+ flip,
902
+ flop,
903
+ affine,
904
+ sharpen,
905
+ median,
906
+ blur,
907
+ flatten,
908
+ unflatten,
909
+ gamma,
910
+ negate,
911
+ normalise,
912
+ normalize,
913
+ clahe,
914
+ convolve,
915
+ threshold,
916
+ boolean,
917
+ linear,
918
+ recomb,
919
+ modulate
920
+ });
921
+ };