@revizly/sharp 0.33.2-revizly3

Sign up to get free protection for your applications and to get access to all the features.
package/lib/index.js ADDED
@@ -0,0 +1,16 @@
1
+ // Copyright 2013 Lovell Fuller and others.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ 'use strict';
5
+
6
+ const Sharp = require('./constructor');
7
+ require('./input')(Sharp);
8
+ require('./resize')(Sharp);
9
+ require('./composite')(Sharp);
10
+ require('./operation')(Sharp);
11
+ require('./colour')(Sharp);
12
+ require('./channel')(Sharp);
13
+ require('./output')(Sharp);
14
+ require('./utility')(Sharp);
15
+
16
+ module.exports = Sharp;
package/lib/input.js ADDED
@@ -0,0 +1,657 @@
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
+ const sharp = require('./sharp');
9
+
10
+ /**
11
+ * Justication alignment
12
+ * @member
13
+ * @private
14
+ */
15
+ const align = {
16
+ left: 'low',
17
+ center: 'centre',
18
+ centre: 'centre',
19
+ right: 'high'
20
+ };
21
+
22
+ /**
23
+ * Extract input options, if any, from an object.
24
+ * @private
25
+ */
26
+ function _inputOptionsFromObject (obj) {
27
+ const { raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd } = obj;
28
+ return [raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd].some(is.defined)
29
+ ? { raw, density, limitInputPixels, ignoreIcc, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd }
30
+ : undefined;
31
+ }
32
+
33
+ /**
34
+ * Create Object containing input and input-related options.
35
+ * @private
36
+ */
37
+ function _createInputDescriptor (input, inputOptions, containerOptions) {
38
+ const inputDescriptor = {
39
+ failOn: 'warning',
40
+ limitInputPixels: Math.pow(0x3FFF, 2),
41
+ ignoreIcc: false,
42
+ unlimited: false,
43
+ sequentialRead: true
44
+ };
45
+ if (is.string(input)) {
46
+ // filesystem
47
+ inputDescriptor.file = input;
48
+ } else if (is.buffer(input)) {
49
+ // Buffer
50
+ if (input.length === 0) {
51
+ throw Error('Input Buffer is empty');
52
+ }
53
+ inputDescriptor.buffer = input;
54
+ } else if (is.arrayBuffer(input)) {
55
+ if (input.byteLength === 0) {
56
+ throw Error('Input bit Array is empty');
57
+ }
58
+ inputDescriptor.buffer = Buffer.from(input, 0, input.byteLength);
59
+ } else if (is.typedArray(input)) {
60
+ if (input.length === 0) {
61
+ throw Error('Input Bit Array is empty');
62
+ }
63
+ inputDescriptor.buffer = Buffer.from(input.buffer, input.byteOffset, input.byteLength);
64
+ } else if (is.plainObject(input) && !is.defined(inputOptions)) {
65
+ // Plain Object descriptor, e.g. create
66
+ inputOptions = input;
67
+ if (_inputOptionsFromObject(inputOptions)) {
68
+ // Stream with options
69
+ inputDescriptor.buffer = [];
70
+ }
71
+ } else if (!is.defined(input) && !is.defined(inputOptions) && is.object(containerOptions) && containerOptions.allowStream) {
72
+ // Stream without options
73
+ inputDescriptor.buffer = [];
74
+ } else {
75
+ throw new Error(`Unsupported input '${input}' of type ${typeof input}${
76
+ is.defined(inputOptions) ? ` when also providing options of type ${typeof inputOptions}` : ''
77
+ }`);
78
+ }
79
+ if (is.object(inputOptions)) {
80
+ // Deprecated: failOnError
81
+ if (is.defined(inputOptions.failOnError)) {
82
+ if (is.bool(inputOptions.failOnError)) {
83
+ inputDescriptor.failOn = inputOptions.failOnError ? 'warning' : 'none';
84
+ } else {
85
+ throw is.invalidParameterError('failOnError', 'boolean', inputOptions.failOnError);
86
+ }
87
+ }
88
+ // failOn
89
+ if (is.defined(inputOptions.failOn)) {
90
+ if (is.string(inputOptions.failOn) && is.inArray(inputOptions.failOn, ['none', 'truncated', 'error', 'warning'])) {
91
+ inputDescriptor.failOn = inputOptions.failOn;
92
+ } else {
93
+ throw is.invalidParameterError('failOn', 'one of: none, truncated, error, warning', inputOptions.failOn);
94
+ }
95
+ }
96
+ // Density
97
+ if (is.defined(inputOptions.density)) {
98
+ if (is.inRange(inputOptions.density, 1, 100000)) {
99
+ inputDescriptor.density = inputOptions.density;
100
+ } else {
101
+ throw is.invalidParameterError('density', 'number between 1 and 100000', inputOptions.density);
102
+ }
103
+ }
104
+ // Ignore embeddded ICC profile
105
+ if (is.defined(inputOptions.ignoreIcc)) {
106
+ if (is.bool(inputOptions.ignoreIcc)) {
107
+ inputDescriptor.ignoreIcc = inputOptions.ignoreIcc;
108
+ } else {
109
+ throw is.invalidParameterError('ignoreIcc', 'boolean', inputOptions.ignoreIcc);
110
+ }
111
+ }
112
+ // limitInputPixels
113
+ if (is.defined(inputOptions.limitInputPixels)) {
114
+ if (is.bool(inputOptions.limitInputPixels)) {
115
+ inputDescriptor.limitInputPixels = inputOptions.limitInputPixels
116
+ ? Math.pow(0x3FFF, 2)
117
+ : 0;
118
+ } else if (is.integer(inputOptions.limitInputPixels) && is.inRange(inputOptions.limitInputPixels, 0, Number.MAX_SAFE_INTEGER)) {
119
+ inputDescriptor.limitInputPixels = inputOptions.limitInputPixels;
120
+ } else {
121
+ throw is.invalidParameterError('limitInputPixels', 'positive integer', inputOptions.limitInputPixels);
122
+ }
123
+ }
124
+ // unlimited
125
+ if (is.defined(inputOptions.unlimited)) {
126
+ if (is.bool(inputOptions.unlimited)) {
127
+ inputDescriptor.unlimited = inputOptions.unlimited;
128
+ } else {
129
+ throw is.invalidParameterError('unlimited', 'boolean', inputOptions.unlimited);
130
+ }
131
+ }
132
+ // sequentialRead
133
+ if (is.defined(inputOptions.sequentialRead)) {
134
+ if (is.bool(inputOptions.sequentialRead)) {
135
+ inputDescriptor.sequentialRead = inputOptions.sequentialRead;
136
+ } else {
137
+ throw is.invalidParameterError('sequentialRead', 'boolean', inputOptions.sequentialRead);
138
+ }
139
+ }
140
+ // Raw pixel input
141
+ if (is.defined(inputOptions.raw)) {
142
+ if (
143
+ is.object(inputOptions.raw) &&
144
+ is.integer(inputOptions.raw.width) && inputOptions.raw.width > 0 &&
145
+ is.integer(inputOptions.raw.height) && inputOptions.raw.height > 0 &&
146
+ is.integer(inputOptions.raw.channels) && is.inRange(inputOptions.raw.channels, 1, 4)
147
+ ) {
148
+ inputDescriptor.rawWidth = inputOptions.raw.width;
149
+ inputDescriptor.rawHeight = inputOptions.raw.height;
150
+ inputDescriptor.rawChannels = inputOptions.raw.channels;
151
+ inputDescriptor.rawPremultiplied = !!inputOptions.raw.premultiplied;
152
+
153
+ switch (input.constructor) {
154
+ case Uint8Array:
155
+ case Uint8ClampedArray:
156
+ inputDescriptor.rawDepth = 'uchar';
157
+ break;
158
+ case Int8Array:
159
+ inputDescriptor.rawDepth = 'char';
160
+ break;
161
+ case Uint16Array:
162
+ inputDescriptor.rawDepth = 'ushort';
163
+ break;
164
+ case Int16Array:
165
+ inputDescriptor.rawDepth = 'short';
166
+ break;
167
+ case Uint32Array:
168
+ inputDescriptor.rawDepth = 'uint';
169
+ break;
170
+ case Int32Array:
171
+ inputDescriptor.rawDepth = 'int';
172
+ break;
173
+ case Float32Array:
174
+ inputDescriptor.rawDepth = 'float';
175
+ break;
176
+ case Float64Array:
177
+ inputDescriptor.rawDepth = 'double';
178
+ break;
179
+ default:
180
+ inputDescriptor.rawDepth = 'uchar';
181
+ break;
182
+ }
183
+ } else {
184
+ throw new Error('Expected width, height and channels for raw pixel input');
185
+ }
186
+ }
187
+ // Multi-page input (GIF, TIFF, PDF)
188
+ if (is.defined(inputOptions.animated)) {
189
+ if (is.bool(inputOptions.animated)) {
190
+ inputDescriptor.pages = inputOptions.animated ? -1 : 1;
191
+ } else {
192
+ throw is.invalidParameterError('animated', 'boolean', inputOptions.animated);
193
+ }
194
+ }
195
+ if (is.defined(inputOptions.pages)) {
196
+ if (is.integer(inputOptions.pages) && is.inRange(inputOptions.pages, -1, 100000)) {
197
+ inputDescriptor.pages = inputOptions.pages;
198
+ } else {
199
+ throw is.invalidParameterError('pages', 'integer between -1 and 100000', inputOptions.pages);
200
+ }
201
+ }
202
+ if (is.defined(inputOptions.page)) {
203
+ if (is.integer(inputOptions.page) && is.inRange(inputOptions.page, 0, 100000)) {
204
+ inputDescriptor.page = inputOptions.page;
205
+ } else {
206
+ throw is.invalidParameterError('page', 'integer between 0 and 100000', inputOptions.page);
207
+ }
208
+ }
209
+ // Multi-level input (OpenSlide)
210
+ if (is.defined(inputOptions.level)) {
211
+ if (is.integer(inputOptions.level) && is.inRange(inputOptions.level, 0, 256)) {
212
+ inputDescriptor.level = inputOptions.level;
213
+ } else {
214
+ throw is.invalidParameterError('level', 'integer between 0 and 256', inputOptions.level);
215
+ }
216
+ }
217
+ // Sub Image File Directory (TIFF)
218
+ if (is.defined(inputOptions.subifd)) {
219
+ if (is.integer(inputOptions.subifd) && is.inRange(inputOptions.subifd, -1, 100000)) {
220
+ inputDescriptor.subifd = inputOptions.subifd;
221
+ } else {
222
+ throw is.invalidParameterError('subifd', 'integer between -1 and 100000', inputOptions.subifd);
223
+ }
224
+ }
225
+ // Create new image
226
+ if (is.defined(inputOptions.create)) {
227
+ if (
228
+ is.object(inputOptions.create) &&
229
+ is.integer(inputOptions.create.width) && inputOptions.create.width > 0 &&
230
+ is.integer(inputOptions.create.height) && inputOptions.create.height > 0 &&
231
+ is.integer(inputOptions.create.channels)
232
+ ) {
233
+ inputDescriptor.createWidth = inputOptions.create.width;
234
+ inputDescriptor.createHeight = inputOptions.create.height;
235
+ inputDescriptor.createChannels = inputOptions.create.channels;
236
+ // Noise
237
+ if (is.defined(inputOptions.create.noise)) {
238
+ if (!is.object(inputOptions.create.noise)) {
239
+ throw new Error('Expected noise to be an object');
240
+ }
241
+ if (!is.inArray(inputOptions.create.noise.type, ['gaussian'])) {
242
+ throw new Error('Only gaussian noise is supported at the moment');
243
+ }
244
+ if (!is.inRange(inputOptions.create.channels, 1, 4)) {
245
+ throw is.invalidParameterError('create.channels', 'number between 1 and 4', inputOptions.create.channels);
246
+ }
247
+ inputDescriptor.createNoiseType = inputOptions.create.noise.type;
248
+ if (is.number(inputOptions.create.noise.mean) && is.inRange(inputOptions.create.noise.mean, 0, 10000)) {
249
+ inputDescriptor.createNoiseMean = inputOptions.create.noise.mean;
250
+ } else {
251
+ throw is.invalidParameterError('create.noise.mean', 'number between 0 and 10000', inputOptions.create.noise.mean);
252
+ }
253
+ if (is.number(inputOptions.create.noise.sigma) && is.inRange(inputOptions.create.noise.sigma, 0, 10000)) {
254
+ inputDescriptor.createNoiseSigma = inputOptions.create.noise.sigma;
255
+ } else {
256
+ throw is.invalidParameterError('create.noise.sigma', 'number between 0 and 10000', inputOptions.create.noise.sigma);
257
+ }
258
+ } else if (is.defined(inputOptions.create.background)) {
259
+ if (!is.inRange(inputOptions.create.channels, 3, 4)) {
260
+ throw is.invalidParameterError('create.channels', 'number between 3 and 4', inputOptions.create.channels);
261
+ }
262
+ const background = color(inputOptions.create.background);
263
+ inputDescriptor.createBackground = [
264
+ background.red(),
265
+ background.green(),
266
+ background.blue(),
267
+ Math.round(background.alpha() * 255)
268
+ ];
269
+ } else {
270
+ throw new Error('Expected valid noise or background to create a new input image');
271
+ }
272
+ delete inputDescriptor.buffer;
273
+ } else {
274
+ throw new Error('Expected valid width, height and channels to create a new input image');
275
+ }
276
+ }
277
+ // Create a new image with text
278
+ if (is.defined(inputOptions.text)) {
279
+ if (is.object(inputOptions.text) && is.string(inputOptions.text.text)) {
280
+ inputDescriptor.textValue = inputOptions.text.text;
281
+ if (is.defined(inputOptions.text.height) && is.defined(inputOptions.text.dpi)) {
282
+ throw new Error('Expected only one of dpi or height');
283
+ }
284
+ if (is.defined(inputOptions.text.font)) {
285
+ if (is.string(inputOptions.text.font)) {
286
+ inputDescriptor.textFont = inputOptions.text.font;
287
+ } else {
288
+ throw is.invalidParameterError('text.font', 'string', inputOptions.text.font);
289
+ }
290
+ }
291
+ if (is.defined(inputOptions.text.fontfile)) {
292
+ if (is.string(inputOptions.text.fontfile)) {
293
+ inputDescriptor.textFontfile = inputOptions.text.fontfile;
294
+ } else {
295
+ throw is.invalidParameterError('text.fontfile', 'string', inputOptions.text.fontfile);
296
+ }
297
+ }
298
+ if (is.defined(inputOptions.text.width)) {
299
+ if (is.number(inputOptions.text.width)) {
300
+ inputDescriptor.textWidth = inputOptions.text.width;
301
+ } else {
302
+ throw is.invalidParameterError('text.textWidth', 'number', inputOptions.text.width);
303
+ }
304
+ }
305
+ if (is.defined(inputOptions.text.height)) {
306
+ if (is.number(inputOptions.text.height)) {
307
+ inputDescriptor.textHeight = inputOptions.text.height;
308
+ } else {
309
+ throw is.invalidParameterError('text.height', 'number', inputOptions.text.height);
310
+ }
311
+ }
312
+ if (is.defined(inputOptions.text.align)) {
313
+ if (is.string(inputOptions.text.align) && is.string(this.constructor.align[inputOptions.text.align])) {
314
+ inputDescriptor.textAlign = this.constructor.align[inputOptions.text.align];
315
+ } else {
316
+ throw is.invalidParameterError('text.align', 'valid alignment', inputOptions.text.align);
317
+ }
318
+ }
319
+ if (is.defined(inputOptions.text.justify)) {
320
+ if (is.bool(inputOptions.text.justify)) {
321
+ inputDescriptor.textJustify = inputOptions.text.justify;
322
+ } else {
323
+ throw is.invalidParameterError('text.justify', 'boolean', inputOptions.text.justify);
324
+ }
325
+ }
326
+ if (is.defined(inputOptions.text.dpi)) {
327
+ if (is.number(inputOptions.text.dpi) && is.inRange(inputOptions.text.dpi, 1, 100000)) {
328
+ inputDescriptor.textDpi = inputOptions.text.dpi;
329
+ } else {
330
+ throw is.invalidParameterError('text.dpi', 'number between 1 and 100000', inputOptions.text.dpi);
331
+ }
332
+ }
333
+ if (is.defined(inputOptions.text.rgba)) {
334
+ if (is.bool(inputOptions.text.rgba)) {
335
+ inputDescriptor.textRgba = inputOptions.text.rgba;
336
+ } else {
337
+ throw is.invalidParameterError('text.rgba', 'bool', inputOptions.text.rgba);
338
+ }
339
+ }
340
+ if (is.defined(inputOptions.text.spacing)) {
341
+ if (is.number(inputOptions.text.spacing)) {
342
+ inputDescriptor.textSpacing = inputOptions.text.spacing;
343
+ } else {
344
+ throw is.invalidParameterError('text.spacing', 'number', inputOptions.text.spacing);
345
+ }
346
+ }
347
+ if (is.defined(inputOptions.text.wrap)) {
348
+ if (is.string(inputOptions.text.wrap) && is.inArray(inputOptions.text.wrap, ['word', 'char', 'wordChar', 'none'])) {
349
+ inputDescriptor.textWrap = inputOptions.text.wrap;
350
+ } else {
351
+ throw is.invalidParameterError('text.wrap', 'one of: word, char, wordChar, none', inputOptions.text.wrap);
352
+ }
353
+ }
354
+ delete inputDescriptor.buffer;
355
+ } else {
356
+ throw new Error('Expected a valid string to create an image with text.');
357
+ }
358
+ }
359
+ } else if (is.defined(inputOptions)) {
360
+ throw new Error('Invalid input options ' + inputOptions);
361
+ }
362
+ return inputDescriptor;
363
+ }
364
+
365
+ /**
366
+ * Handle incoming Buffer chunk on Writable Stream.
367
+ * @private
368
+ * @param {Buffer} chunk
369
+ * @param {string} encoding - unused
370
+ * @param {Function} callback
371
+ */
372
+ function _write (chunk, encoding, callback) {
373
+ /* istanbul ignore else */
374
+ if (Array.isArray(this.options.input.buffer)) {
375
+ /* istanbul ignore else */
376
+ if (is.buffer(chunk)) {
377
+ if (this.options.input.buffer.length === 0) {
378
+ this.on('finish', () => {
379
+ this.streamInFinished = true;
380
+ });
381
+ }
382
+ this.options.input.buffer.push(chunk);
383
+ callback();
384
+ } else {
385
+ callback(new Error('Non-Buffer data on Writable Stream'));
386
+ }
387
+ } else {
388
+ callback(new Error('Unexpected data on Writable Stream'));
389
+ }
390
+ }
391
+
392
+ /**
393
+ * Flattens the array of chunks accumulated in input.buffer.
394
+ * @private
395
+ */
396
+ function _flattenBufferIn () {
397
+ if (this._isStreamInput()) {
398
+ this.options.input.buffer = Buffer.concat(this.options.input.buffer);
399
+ }
400
+ }
401
+
402
+ /**
403
+ * Are we expecting Stream-based input?
404
+ * @private
405
+ * @returns {boolean}
406
+ */
407
+ function _isStreamInput () {
408
+ return Array.isArray(this.options.input.buffer);
409
+ }
410
+
411
+ /**
412
+ * Fast access to (uncached) image metadata without decoding any compressed pixel data.
413
+ *
414
+ * This is read from the header of the input image.
415
+ * It does not take into consideration any operations to be applied to the output image,
416
+ * such as resize or rotate.
417
+ *
418
+ * Dimensions in the response will respect the `page` and `pages` properties of the
419
+ * {@link /api-constructor#parameters|constructor parameters}.
420
+ *
421
+ * A `Promise` is returned when `callback` is not provided.
422
+ *
423
+ * - `format`: Name of decoder used to decompress image data e.g. `jpeg`, `png`, `webp`, `gif`, `svg`
424
+ * - `size`: Total size of image in bytes, for Stream and Buffer input only
425
+ * - `width`: Number of pixels wide (EXIF orientation is not taken into consideration, see example below)
426
+ * - `height`: Number of pixels high (EXIF orientation is not taken into consideration, see example below)
427
+ * - `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://www.libvips.org/API/current/VipsImage.html#VipsInterpretation)
428
+ * - `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK
429
+ * - `depth`: Name of pixel depth format e.g. `uchar`, `char`, `ushort`, `float` [...](https://www.libvips.org/API/current/VipsImage.html#VipsBandFormat)
430
+ * - `density`: Number of pixels per inch (DPI), if present
431
+ * - `chromaSubsampling`: String containing JPEG chroma subsampling, `4:2:0` or `4:4:4` for RGB, `4:2:0:4` or `4:4:4:4` for CMYK
432
+ * - `isProgressive`: Boolean indicating whether the image is interlaced using a progressive scan
433
+ * - `pages`: Number of pages/frames contained within the image, with support for TIFF, HEIF, PDF, animated GIF and animated WebP
434
+ * - `pageHeight`: Number of pixels high each page in a multi-page image will be.
435
+ * - `paletteBitDepth`: Bit depth of palette-based image (GIF, PNG).
436
+ * - `loop`: Number of times to loop an animated image, zero refers to a continuous loop.
437
+ * - `delay`: Delay in ms between each page in an animated image, provided as an array of integers.
438
+ * - `pagePrimary`: Number of the primary page in a HEIF image
439
+ * - `levels`: Details of each level in a multi-level image provided as an array of objects, requires libvips compiled with support for OpenSlide
440
+ * - `subifds`: Number of Sub Image File Directories in an OME-TIFF image
441
+ * - `background`: Default background colour, if present, for PNG (bKGD) and GIF images, either an RGB Object or a single greyscale value
442
+ * - `compression`: The encoder used to compress an HEIF file, `av1` (AVIF) or `hevc` (HEIC)
443
+ * - `resolutionUnit`: The unit of resolution (density), either `inch` or `cm`, if present
444
+ * - `hasProfile`: Boolean indicating the presence of an embedded ICC profile
445
+ * - `hasAlpha`: Boolean indicating the presence of an alpha transparency channel
446
+ * - `orientation`: Number value of the EXIF Orientation header, if present
447
+ * - `exif`: Buffer containing raw EXIF data, if present
448
+ * - `icc`: Buffer containing raw [ICC](https://www.npmjs.com/package/icc) profile data, if present
449
+ * - `iptc`: Buffer containing raw IPTC data, if present
450
+ * - `xmp`: Buffer containing raw XMP data, if present
451
+ * - `tifftagPhotoshop`: Buffer containing raw TIFFTAG_PHOTOSHOP data, if present
452
+ * - `formatMagick`: String containing format for images loaded via *magick
453
+ *
454
+ * @example
455
+ * const metadata = await sharp(input).metadata();
456
+ *
457
+ * @example
458
+ * const image = sharp(inputJpg);
459
+ * image
460
+ * .metadata()
461
+ * .then(function(metadata) {
462
+ * return image
463
+ * .resize(Math.round(metadata.width / 2))
464
+ * .webp()
465
+ * .toBuffer();
466
+ * })
467
+ * .then(function(data) {
468
+ * // data contains a WebP image half the width and height of the original JPEG
469
+ * });
470
+ *
471
+ * @example
472
+ * // Based on EXIF rotation metadata, get the right-side-up width and height:
473
+ *
474
+ * const size = getNormalSize(await sharp(input).metadata());
475
+ *
476
+ * function getNormalSize({ width, height, orientation }) {
477
+ * return (orientation || 0) >= 5
478
+ * ? { width: height, height: width }
479
+ * : { width, height };
480
+ * }
481
+ *
482
+ * @param {Function} [callback] - called with the arguments `(err, metadata)`
483
+ * @returns {Promise<Object>|Sharp}
484
+ */
485
+ function metadata (callback) {
486
+ const stack = Error();
487
+ if (is.fn(callback)) {
488
+ if (this._isStreamInput()) {
489
+ this.on('finish', () => {
490
+ this._flattenBufferIn();
491
+ sharp.metadata(this.options, (err, metadata) => {
492
+ if (err) {
493
+ callback(is.nativeError(err, stack));
494
+ } else {
495
+ callback(null, metadata);
496
+ }
497
+ });
498
+ });
499
+ } else {
500
+ sharp.metadata(this.options, (err, metadata) => {
501
+ if (err) {
502
+ callback(is.nativeError(err, stack));
503
+ } else {
504
+ callback(null, metadata);
505
+ }
506
+ });
507
+ }
508
+ return this;
509
+ } else {
510
+ if (this._isStreamInput()) {
511
+ return new Promise((resolve, reject) => {
512
+ const finished = () => {
513
+ this._flattenBufferIn();
514
+ sharp.metadata(this.options, (err, metadata) => {
515
+ if (err) {
516
+ reject(is.nativeError(err, stack));
517
+ } else {
518
+ resolve(metadata);
519
+ }
520
+ });
521
+ };
522
+ if (this.writableFinished) {
523
+ finished();
524
+ } else {
525
+ this.once('finish', finished);
526
+ }
527
+ });
528
+ } else {
529
+ return new Promise((resolve, reject) => {
530
+ sharp.metadata(this.options, (err, metadata) => {
531
+ if (err) {
532
+ reject(is.nativeError(err, stack));
533
+ } else {
534
+ resolve(metadata);
535
+ }
536
+ });
537
+ });
538
+ }
539
+ }
540
+ }
541
+
542
+ /**
543
+ * Access to pixel-derived image statistics for every channel in the image.
544
+ * A `Promise` is returned when `callback` is not provided.
545
+ *
546
+ * - `channels`: Array of channel statistics for each channel in the image. Each channel statistic contains
547
+ * - `min` (minimum value in the channel)
548
+ * - `max` (maximum value in the channel)
549
+ * - `sum` (sum of all values in a channel)
550
+ * - `squaresSum` (sum of squared values in a channel)
551
+ * - `mean` (mean of the values in a channel)
552
+ * - `stdev` (standard deviation for the values in a channel)
553
+ * - `minX` (x-coordinate of one of the pixel where the minimum lies)
554
+ * - `minY` (y-coordinate of one of the pixel where the minimum lies)
555
+ * - `maxX` (x-coordinate of one of the pixel where the maximum lies)
556
+ * - `maxY` (y-coordinate of one of the pixel where the maximum lies)
557
+ * - `isOpaque`: Is the image fully opaque? Will be `true` if the image has no alpha channel or if every pixel is fully opaque.
558
+ * - `entropy`: Histogram-based estimation of greyscale entropy, discarding alpha channel if any.
559
+ * - `sharpness`: Estimation of greyscale sharpness based on the standard deviation of a Laplacian convolution, discarding alpha channel if any.
560
+ * - `dominant`: Object containing most dominant sRGB colour based on a 4096-bin 3D histogram.
561
+ *
562
+ * **Note**: Statistics are derived from the original input image. Any operations performed on the image must first be
563
+ * written to a buffer in order to run `stats` on the result (see third example).
564
+ *
565
+ * @example
566
+ * const image = sharp(inputJpg);
567
+ * image
568
+ * .stats()
569
+ * .then(function(stats) {
570
+ * // stats contains the channel-wise statistics array and the isOpaque value
571
+ * });
572
+ *
573
+ * @example
574
+ * const { entropy, sharpness, dominant } = await sharp(input).stats();
575
+ * const { r, g, b } = dominant;
576
+ *
577
+ * @example
578
+ * const image = sharp(input);
579
+ * // store intermediate result
580
+ * const part = await image.extract(region).toBuffer();
581
+ * // create new instance to obtain statistics of extracted region
582
+ * const stats = await sharp(part).stats();
583
+ *
584
+ * @param {Function} [callback] - called with the arguments `(err, stats)`
585
+ * @returns {Promise<Object>}
586
+ */
587
+ function stats (callback) {
588
+ const stack = Error();
589
+ if (is.fn(callback)) {
590
+ if (this._isStreamInput()) {
591
+ this.on('finish', () => {
592
+ this._flattenBufferIn();
593
+ sharp.stats(this.options, (err, stats) => {
594
+ if (err) {
595
+ callback(is.nativeError(err, stack));
596
+ } else {
597
+ callback(null, stats);
598
+ }
599
+ });
600
+ });
601
+ } else {
602
+ sharp.stats(this.options, (err, stats) => {
603
+ if (err) {
604
+ callback(is.nativeError(err, stack));
605
+ } else {
606
+ callback(null, stats);
607
+ }
608
+ });
609
+ }
610
+ return this;
611
+ } else {
612
+ if (this._isStreamInput()) {
613
+ return new Promise((resolve, reject) => {
614
+ this.on('finish', function () {
615
+ this._flattenBufferIn();
616
+ sharp.stats(this.options, (err, stats) => {
617
+ if (err) {
618
+ reject(is.nativeError(err, stack));
619
+ } else {
620
+ resolve(stats);
621
+ }
622
+ });
623
+ });
624
+ });
625
+ } else {
626
+ return new Promise((resolve, reject) => {
627
+ sharp.stats(this.options, (err, stats) => {
628
+ if (err) {
629
+ reject(is.nativeError(err, stack));
630
+ } else {
631
+ resolve(stats);
632
+ }
633
+ });
634
+ });
635
+ }
636
+ }
637
+ }
638
+
639
+ /**
640
+ * Decorate the Sharp prototype with input-related functions.
641
+ * @private
642
+ */
643
+ module.exports = function (Sharp) {
644
+ Object.assign(Sharp.prototype, {
645
+ // Private
646
+ _inputOptionsFromObject,
647
+ _createInputDescriptor,
648
+ _write,
649
+ _flattenBufferIn,
650
+ _isStreamInput,
651
+ // Public
652
+ metadata,
653
+ stats
654
+ });
655
+ // Class attributes
656
+ Sharp.align = align;
657
+ };