sharp 0.35.0-rc.3 → 0.35.0-rc.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.
@@ -0,0 +1,1799 @@
1
+ /*!
2
+ Copyright 2013 Lovell Fuller and others.
3
+ SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ import path from 'node:path';
7
+ import is from './is.mjs';
8
+ import sharp from './sharp.mjs';
9
+
10
+ const formats = new Map([
11
+ ['heic', 'heif'],
12
+ ['heif', 'heif'],
13
+ ['avif', 'avif'],
14
+ ['jpeg', 'jpeg'],
15
+ ['jpg', 'jpeg'],
16
+ ['jpe', 'jpeg'],
17
+ ['tile', 'tile'],
18
+ ['dz', 'tile'],
19
+ ['png', 'png'],
20
+ ['raw', 'raw'],
21
+ ['tiff', 'tiff'],
22
+ ['tif', 'tiff'],
23
+ ['webp', 'webp'],
24
+ ['gif', 'gif'],
25
+ ['jp2', 'jp2'],
26
+ ['jpx', 'jp2'],
27
+ ['j2k', 'jp2'],
28
+ ['j2c', 'jp2'],
29
+ ['jxl', 'jxl']
30
+ ]);
31
+
32
+ const jp2Regex = /\.(jp[2x]|j2[kc])$/i;
33
+
34
+ const errJp2Save = () => new Error('JP2 output requires libvips with support for OpenJPEG');
35
+
36
+ const bitdepthFromColourCount = (colours) => 1 << 31 - Math.clz32(Math.ceil(Math.log2(colours)));
37
+
38
+ /**
39
+ * Write output image data to a file.
40
+ *
41
+ * If an explicit output format is not selected, it will be inferred from the extension,
42
+ * with JPEG, PNG, WebP, AVIF, TIFF, GIF, DZI, and libvips' V format supported.
43
+ * Note that raw pixel data is only supported for buffer output.
44
+ *
45
+ * By default all metadata will be removed, which includes EXIF-based orientation.
46
+ * See {@link #withmetadata withMetadata} for control over this.
47
+ *
48
+ * The caller is responsible for ensuring directory structures and permissions exist.
49
+ *
50
+ * A `Promise` is returned when `callback` is not provided.
51
+ *
52
+ * @example
53
+ * sharp(input)
54
+ * .toFile('output.png', (err, info) => { ... });
55
+ *
56
+ * @example
57
+ * sharp(input)
58
+ * .toFile('output.png')
59
+ * .then(info => { ... })
60
+ * .catch(err => { ... });
61
+ *
62
+ * @param {string} fileOut - the path to write the image data to.
63
+ * @param {Function} [callback] - called on completion with two arguments `(err, info)`.
64
+ * `info` contains the output image `format`, `size` (bytes), `width`, `height`,
65
+ * `channels` and `premultiplied` (indicating if premultiplication was used).
66
+ * When using a crop strategy also contains `cropOffsetLeft` and `cropOffsetTop`.
67
+ * When using the attention crop strategy also contains `attentionX` and `attentionY`, the focal point of the cropped region.
68
+ * Animated output will also contain `pageHeight` and `pages`.
69
+ * May also contain `textAutofitDpi` (dpi the font was rendered at) if image was created from text.
70
+ * @returns {Promise<Object>} - when no callback is provided
71
+ * @throws {Error} Invalid parameters
72
+ */
73
+ function toFile (fileOut, callback) {
74
+ let err;
75
+ if (!is.string(fileOut)) {
76
+ err = new Error('Missing output file path');
77
+ } else if (is.string(this.options.input.file) && path.resolve(this.options.input.file) === path.resolve(fileOut)) {
78
+ err = new Error('Cannot use same file for input and output');
79
+ } else if (jp2Regex.test(path.extname(fileOut)) && !this.constructor.format.jp2.output.file) {
80
+ err = errJp2Save();
81
+ }
82
+ if (err) {
83
+ if (is.fn(callback)) {
84
+ callback(err);
85
+ } else {
86
+ return Promise.reject(err);
87
+ }
88
+ } else {
89
+ this.options.fileOut = fileOut;
90
+ const stack = Error();
91
+ return this._pipeline(callback, stack);
92
+ }
93
+ return this;
94
+ }
95
+
96
+ /**
97
+ * Write output to a Buffer.
98
+ * JPEG, PNG, WebP, AVIF, TIFF, GIF and raw pixel data output are supported.
99
+ *
100
+ * Use {@link #toformat toFormat} or one of the format-specific functions such as {@link #jpeg jpeg}, {@link #png png} etc. to set the output format.
101
+ *
102
+ * If no explicit format is set, the output format will match the input image, except SVG input which becomes PNG output.
103
+ *
104
+ * By default all metadata will be removed, which includes EXIF-based orientation.
105
+ * See {@link #withmetadata withMetadata} for control over this.
106
+ *
107
+ * `callback`, if present, gets three arguments `(err, data, info)` where:
108
+ * - `err` is an error, if any.
109
+ * - `data` is the output image data.
110
+ * - `info` contains the output image `format`, `size` (bytes), `width`, `height`,
111
+ * `channels` and `premultiplied` (indicating if premultiplication was used).
112
+ * When using a crop strategy also contains `cropOffsetLeft` and `cropOffsetTop`.
113
+ * Animated output will also contain `pageHeight` and `pages`.
114
+ * May also contain `textAutofitDpi` (dpi the font was rendered at) if image was created from text.
115
+ *
116
+ * A `Promise` is returned when `callback` is not provided.
117
+ *
118
+ * @example
119
+ * sharp(input)
120
+ * .toBuffer((err, data, info) => { ... });
121
+ *
122
+ * @example
123
+ * sharp(input)
124
+ * .toBuffer()
125
+ * .then(data => { ... })
126
+ * .catch(err => { ... });
127
+ *
128
+ * @example
129
+ * sharp(input)
130
+ * .png()
131
+ * .toBuffer({ resolveWithObject: true })
132
+ * .then(({ data, info }) => { ... })
133
+ * .catch(err => { ... });
134
+ *
135
+ * @example
136
+ * const { data, info } = await sharp('my-image.jpg')
137
+ * // output the raw pixels
138
+ * .raw()
139
+ * .toBuffer({ resolveWithObject: true });
140
+ *
141
+ * // create a more type safe way to work with the raw pixel data
142
+ * // this will not copy the data, instead it will change `data`s underlying ArrayBuffer
143
+ * // so `data` and `pixelArray` point to the same memory location
144
+ * const pixelArray = new Uint8ClampedArray(data.buffer);
145
+ *
146
+ * // When you are done changing the pixelArray, sharp takes the `pixelArray` as an input
147
+ * const { width, height, channels } = info;
148
+ * await sharp(pixelArray, { raw: { width, height, channels } })
149
+ * .toFile('my-changed-image.jpg');
150
+ *
151
+ * @param {Object} [options]
152
+ * @param {boolean} [options.resolveWithObject] Resolve the Promise with an Object containing `data` and `info` properties instead of resolving only with `data`.
153
+ * @param {Function} [callback]
154
+ * @returns {Promise<Buffer>} - when no callback is provided
155
+ */
156
+ function toBuffer (options, callback) {
157
+ if (is.object(options)) {
158
+ this._setBooleanOption('resolveWithObject', options.resolveWithObject);
159
+ } else if (this.options.resolveWithObject) {
160
+ this.options.resolveWithObject = false;
161
+ }
162
+ this.options.fileOut = '';
163
+ const stack = Error();
164
+ return this._pipeline(is.fn(options) ? options : callback, stack);
165
+ }
166
+
167
+ /**
168
+ * Write output to a `Uint8Array` backed by a transferable `ArrayBuffer`.
169
+ * JPEG, PNG, WebP, AVIF, TIFF, GIF and raw pixel data output are supported.
170
+ *
171
+ * Use {@link #toformat toFormat} or one of the format-specific functions such as {@link #jpeg jpeg}, {@link #png png} etc. to set the output format.
172
+ *
173
+ * If no explicit format is set, the output format will match the input image, except SVG input which becomes PNG output.
174
+ *
175
+ * By default all metadata will be removed, which includes EXIF-based orientation.
176
+ * See {@link #keepexif keepExif} and similar methods for control over this.
177
+ *
178
+ * Resolves with an `Object` containing:
179
+ * - `data` is the output image as a `Uint8Array` backed by a transferable `ArrayBuffer`.
180
+ * - `info` contains properties relating to the output image such as `width` and `height`.
181
+ *
182
+ * @since v0.35.0
183
+ *
184
+ * @example
185
+ * const { data, info } = await sharp(input).toUint8Array();
186
+ *
187
+ * @example
188
+ * const { data } = await sharp(input)
189
+ * .avif()
190
+ * .toUint8Array();
191
+ * const base64String = data.toBase64();
192
+ *
193
+ * @returns {Promise<{ data: Uint8Array, info: Object }>}
194
+ */
195
+ function toUint8Array () {
196
+ this.options.resolveWithObject = true;
197
+ this.options.typedArrayOut = true;
198
+ const stack = Error();
199
+ return this._pipeline(null, stack);
200
+ }
201
+
202
+ /**
203
+ * Set output density (DPI) in EXIF metadata.
204
+ *
205
+ * @since 0.35.0
206
+ *
207
+ * @example
208
+ * const data = await sharp(input)
209
+ * .withDensity(96)
210
+ * .toBuffer();
211
+ *
212
+ * @param {number} density Number of pixels per inch (DPI).
213
+ * @returns {Sharp}
214
+ * @throws {Error} Invalid parameters
215
+ */
216
+ function withDensity (density) {
217
+ if (is.number(density) && density > 0) {
218
+ this.options.withMetadataDensity = density;
219
+ } else {
220
+ throw is.invalidParameterError('density', 'positive number', density);
221
+ }
222
+ return this.keepExif();
223
+ }
224
+
225
+ /**
226
+ * Keep all EXIF metadata from the input image in the output image.
227
+ *
228
+ * EXIF metadata is unsupported for TIFF output.
229
+ *
230
+ * @since 0.33.0
231
+ *
232
+ * @example
233
+ * const outputWithExif = await sharp(inputWithExif)
234
+ * .keepExif()
235
+ * .toBuffer();
236
+ *
237
+ * @returns {Sharp}
238
+ */
239
+ function keepExif () {
240
+ this.options.keepMetadata |= 0b00001;
241
+ return this;
242
+ }
243
+
244
+ /**
245
+ * Set EXIF metadata in the output image, ignoring any EXIF in the input image.
246
+ *
247
+ * @since 0.33.0
248
+ *
249
+ * @example
250
+ * const dataWithExif = await sharp(input)
251
+ * .withExif({
252
+ * IFD0: {
253
+ * Copyright: 'The National Gallery'
254
+ * },
255
+ * IFD3: {
256
+ * GPSLatitudeRef: 'N',
257
+ * GPSLatitude: '51/1 30/1 3230/100',
258
+ * GPSLongitudeRef: 'W',
259
+ * GPSLongitude: '0/1 7/1 4366/100'
260
+ * }
261
+ * })
262
+ * .toBuffer();
263
+ *
264
+ * @param {Object<string, Object<string, string>>} exif Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data.
265
+ * @returns {Sharp}
266
+ * @throws {Error} Invalid parameters
267
+ */
268
+ function withExif (exif) {
269
+ if (is.object(exif)) {
270
+ for (const [ifd, entries] of Object.entries(exif)) {
271
+ if (is.object(entries)) {
272
+ for (const [k, v] of Object.entries(entries)) {
273
+ if (is.string(v)) {
274
+ this.options.withExif[`exif-${ifd.toLowerCase()}-${k}`] = v;
275
+ } else {
276
+ throw is.invalidParameterError(`${ifd}.${k}`, 'string', v);
277
+ }
278
+ }
279
+ } else {
280
+ throw is.invalidParameterError(ifd, 'object', entries);
281
+ }
282
+ }
283
+ } else {
284
+ throw is.invalidParameterError('exif', 'object', exif);
285
+ }
286
+ this.options.withExifMerge = false;
287
+ return this.keepExif();
288
+ }
289
+
290
+ /**
291
+ * Update EXIF metadata from the input image in the output image.
292
+ *
293
+ * @since 0.33.0
294
+ *
295
+ * @example
296
+ * const dataWithMergedExif = await sharp(inputWithExif)
297
+ * .withExifMerge({
298
+ * IFD0: {
299
+ * Copyright: 'The National Gallery'
300
+ * }
301
+ * })
302
+ * .toBuffer();
303
+ *
304
+ * @param {Object<string, Object<string, string>>} exif Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data.
305
+ * @returns {Sharp}
306
+ * @throws {Error} Invalid parameters
307
+ */
308
+ function withExifMerge (exif) {
309
+ this.withExif(exif);
310
+ this.options.withExifMerge = true;
311
+ return this;
312
+ }
313
+
314
+ /**
315
+ * Keep ICC profile from the input image in the output image.
316
+ *
317
+ * When input and output colour spaces differ, use with {@link /api-colour/#tocolourspace toColourspace} and optionally {@link /api-colour/#pipelinecolourspace pipelineColourspace}.
318
+ *
319
+ * @since 0.33.0
320
+ *
321
+ * @example
322
+ * const outputWithIccProfile = await sharp(inputWithIccProfile)
323
+ * .keepIccProfile()
324
+ * .toBuffer();
325
+ *
326
+ * @example
327
+ * const cmykOutputWithIccProfile = await sharp(cmykInputWithIccProfile)
328
+ * .pipelineColourspace('cmyk')
329
+ * .toColourspace('cmyk')
330
+ * .keepIccProfile()
331
+ * .toBuffer();
332
+ *
333
+ * @returns {Sharp}
334
+ */
335
+ function keepIccProfile () {
336
+ this.options.keepMetadata |= 0b01000;
337
+ return this;
338
+ }
339
+
340
+ /**
341
+ * Transform using an ICC profile and attach to the output image.
342
+ *
343
+ * This can either be an absolute filesystem path or
344
+ * built-in profile name (`srgb`, `p3`, `cmyk`).
345
+ *
346
+ * @since 0.33.0
347
+ *
348
+ * @example
349
+ * const outputWithP3 = await sharp(input)
350
+ * .withIccProfile('p3')
351
+ * .toBuffer();
352
+ *
353
+ * @param {string} icc - Absolute filesystem path to output ICC profile or built-in profile name (srgb, p3, cmyk).
354
+ * @param {Object} [options]
355
+ * @param {number} [options.attach=true] Should the ICC profile be included in the output image metadata?
356
+ * @returns {Sharp}
357
+ * @throws {Error} Invalid parameters
358
+ */
359
+ function withIccProfile (icc, options) {
360
+ if (is.string(icc)) {
361
+ this.options.withIccProfile = icc;
362
+ } else {
363
+ throw is.invalidParameterError('icc', 'string', icc);
364
+ }
365
+ this.keepIccProfile();
366
+ if (is.object(options)) {
367
+ if (is.defined(options.attach)) {
368
+ if (is.bool(options.attach)) {
369
+ if (!options.attach) {
370
+ this.options.keepMetadata &= ~0b01000;
371
+ }
372
+ } else {
373
+ throw is.invalidParameterError('attach', 'boolean', options.attach);
374
+ }
375
+ }
376
+ }
377
+ return this;
378
+ }
379
+
380
+ /**
381
+ * If the input contains gain map metadata, attempt to process the image and gain map separately,
382
+ * recombining them into a single output image.
383
+ *
384
+ * This approach is faster and should produce better results than {@link #withgainmap withGainMap},
385
+ * however not all operations are supported.
386
+ *
387
+ * Only JPEG input and output are supported.
388
+ * JPEG output options other than `quality` are ignored.
389
+ *
390
+ * This feature is experimental and the API may change.
391
+ *
392
+ * @since 0.35.0
393
+ *
394
+ * @example
395
+ * const outputWithResizedGainMap = await sharp(inputWithGainMap)
396
+ * .keepGainMap()
397
+ * .resize({ width: 64 })
398
+ * .toBuffer();
399
+ *
400
+ * @returns {Sharp}
401
+ */
402
+ function keepGainMap() {
403
+ this.options.keepGainMap = true;
404
+ this.options.withGainMap = false;
405
+ this.options.keepMetadata |= 0b100000;
406
+ return this;
407
+ }
408
+
409
+ /**
410
+ * If the input contains gain map metadata, use it to convert the main image to HDR (High Dynamic Range) before further processing.
411
+ * The input gain map is discarded.
412
+ *
413
+ * If the output is JPEG, generate and attach a new ISO 21496-1 gain map.
414
+ * JPEG output options other than `quality` are ignored.
415
+ *
416
+ * This feature is experimental and the API may change.
417
+ *
418
+ * @since 0.35.0
419
+ *
420
+ * @example
421
+ * const outputWithRegeneratedGainMap = await sharp(inputWithGainMap)
422
+ * .withGainMap()
423
+ * .toBuffer();
424
+ *
425
+ * @returns {Sharp}
426
+ */
427
+ function withGainMap() {
428
+ this.options.withGainMap = true;
429
+ this.options.keepGainMap = false;
430
+ this.options.colourspace = 'scrgb';
431
+ return this;
432
+ }
433
+
434
+ /**
435
+ * Keep XMP metadata from the input image in the output image.
436
+ *
437
+ * @since 0.34.3
438
+ *
439
+ * @example
440
+ * const outputWithXmp = await sharp(inputWithXmp)
441
+ * .keepXmp()
442
+ * .toBuffer();
443
+ *
444
+ * @returns {Sharp}
445
+ */
446
+ function keepXmp () {
447
+ this.options.keepMetadata |= 0b00010;
448
+ return this;
449
+ }
450
+
451
+ /**
452
+ * Set XMP metadata in the output image.
453
+ *
454
+ * Supported by PNG, JPEG, WebP, and TIFF output.
455
+ *
456
+ * @since 0.34.3
457
+ *
458
+ * @example
459
+ * const xmpString = `
460
+ * <?xml version="1.0"?>
461
+ * <x:xmpmeta xmlns:x="adobe:ns:meta/">
462
+ * <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
463
+ * <rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/">
464
+ * <dc:creator><rdf:Seq><rdf:li>John Doe</rdf:li></rdf:Seq></dc:creator>
465
+ * </rdf:Description>
466
+ * </rdf:RDF>
467
+ * </x:xmpmeta>`;
468
+ *
469
+ * const data = await sharp(input)
470
+ * .withXmp(xmpString)
471
+ * .toBuffer();
472
+ *
473
+ * @param {string} xmp String containing XMP metadata to be embedded in the output image.
474
+ * @returns {Sharp}
475
+ * @throws {Error} Invalid parameters
476
+ */
477
+ function withXmp (xmp) {
478
+ if (is.string(xmp) && xmp.length > 0) {
479
+ this.options.withXmp = xmp;
480
+ this.options.keepMetadata |= 0b00010;
481
+ } else {
482
+ throw is.invalidParameterError('xmp', 'non-empty string', xmp);
483
+ }
484
+ return this;
485
+ }
486
+
487
+ /**
488
+ * Keep all metadata (EXIF, ICC, XMP, IPTC) from the input image in the output image.
489
+ *
490
+ * The default behaviour, when `keepMetadata` is not used, is to convert to the device-independent
491
+ * sRGB colour space and strip all metadata, including the removal of any ICC profile.
492
+ *
493
+ * @since 0.33.0
494
+ *
495
+ * @example
496
+ * const outputWithMetadata = await sharp(inputWithMetadata)
497
+ * .keepMetadata()
498
+ * .toBuffer();
499
+ *
500
+ * @returns {Sharp}
501
+ */
502
+ function keepMetadata () {
503
+ this.options.keepMetadata |= 0b11111;
504
+ return this;
505
+ }
506
+
507
+ /**
508
+ * Keep most metadata (EXIF, XMP, IPTC) from the input image in the output image.
509
+ *
510
+ * This will also convert to and add a web-friendly sRGB ICC profile if appropriate.
511
+ *
512
+ * Allows orientation and density to be set or updated.
513
+ *
514
+ * @example
515
+ * const outputSrgbWithMetadata = await sharp(inputRgbWithMetadata)
516
+ * .withMetadata()
517
+ * .toBuffer();
518
+ *
519
+ * @example
520
+ * // Set output metadata to 96 DPI
521
+ * const data = await sharp(input)
522
+ * .withMetadata({ density: 96 })
523
+ * .toBuffer();
524
+ *
525
+ * @param {Object} [options]
526
+ * @param {number} [options.orientation] Used to update the EXIF `Orientation` tag, integer between 1 and 8.
527
+ * @param {number} [options.density] Number of pixels per inch (DPI).
528
+ * @returns {Sharp}
529
+ * @throws {Error} Invalid parameters
530
+ */
531
+ function withMetadata (options) {
532
+ this.keepMetadata();
533
+ this.withIccProfile('srgb');
534
+ if (is.object(options)) {
535
+ if (is.defined(options.orientation)) {
536
+ if (is.integer(options.orientation) && is.inRange(options.orientation, 1, 8)) {
537
+ this.options.withMetadataOrientation = options.orientation;
538
+ } else {
539
+ throw is.invalidParameterError('orientation', 'integer between 1 and 8', options.orientation);
540
+ }
541
+ }
542
+ if (is.defined(options.density)) {
543
+ if (is.number(options.density) && options.density > 0) {
544
+ this.options.withMetadataDensity = options.density;
545
+ } else {
546
+ throw is.invalidParameterError('density', 'positive number', options.density);
547
+ }
548
+ }
549
+ if (is.defined(options.icc)) {
550
+ this.withIccProfile(options.icc);
551
+ }
552
+ if (is.defined(options.exif)) {
553
+ this.withExifMerge(options.exif);
554
+ }
555
+ }
556
+ return this;
557
+ }
558
+
559
+ /**
560
+ * Force output to a given format.
561
+ *
562
+ * @example
563
+ * // Convert any input to PNG output
564
+ * const data = await sharp(input)
565
+ * .toFormat('png')
566
+ * .toBuffer();
567
+ *
568
+ * @param {(string|Object)} format - as a string or an Object with an 'id' attribute
569
+ * @param {Object} options - output options
570
+ * @returns {Sharp}
571
+ * @throws {Error} unsupported format or options
572
+ */
573
+ function toFormat (format, options) {
574
+ const actualFormat = formats.get((is.object(format) && is.string(format.id) ? format.id : format).toLowerCase());
575
+ if (!actualFormat) {
576
+ throw is.invalidParameterError('format', `one of: ${[...formats.keys()].join(', ')}`, format);
577
+ }
578
+ return this[actualFormat](options);
579
+ }
580
+
581
+ /**
582
+ * Use these JPEG options for output image.
583
+ *
584
+ * @example
585
+ * // Convert any input to very high quality JPEG output
586
+ * const data = await sharp(input)
587
+ * .jpeg({
588
+ * quality: 100,
589
+ * chromaSubsampling: '4:4:4'
590
+ * })
591
+ * .toBuffer();
592
+ *
593
+ * @example
594
+ * // Use mozjpeg to reduce output JPEG file size (slower)
595
+ * const data = await sharp(input)
596
+ * .jpeg({ mozjpeg: true })
597
+ * .toBuffer();
598
+ *
599
+ * @param {Object} [options] - output options
600
+ * @param {number} [options.quality=80] - quality, integer 1-100
601
+ * @param {boolean} [options.progressive=false] - use progressive (interlace) scan
602
+ * @param {string} [options.chromaSubsampling='4:2:0'] - set to '4:4:4' to prevent chroma subsampling otherwise defaults to '4:2:0' chroma subsampling
603
+ * @param {boolean} [options.optimiseCoding=true] - optimise Huffman coding tables
604
+ * @param {boolean} [options.optimizeCoding=true] - alternative spelling of optimiseCoding
605
+ * @param {boolean} [options.mozjpeg=false] - use mozjpeg defaults, equivalent to `{ trellisQuantisation: true, overshootDeringing: true, optimiseScans: true, quantisationTable: 3 }`
606
+ * @param {boolean} [options.trellisQuantisation=false] - apply trellis quantisation
607
+ * @param {boolean} [options.overshootDeringing=false] - apply overshoot deringing
608
+ * @param {boolean} [options.optimiseScans=false] - optimise progressive scans, forces progressive
609
+ * @param {boolean} [options.optimizeScans=false] - alternative spelling of optimiseScans
610
+ * @param {number} [options.quantisationTable=0] - quantization table to use, integer 0-8
611
+ * @param {number} [options.quantizationTable=0] - alternative spelling of quantisationTable
612
+ * @param {boolean} [options.force=true] - force JPEG output, otherwise attempt to use input format
613
+ * @returns {Sharp}
614
+ * @throws {Error} Invalid options
615
+ */
616
+ function jpeg (options) {
617
+ if (is.object(options)) {
618
+ if (is.defined(options.quality)) {
619
+ if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
620
+ this.options.jpegQuality = options.quality;
621
+ } else {
622
+ throw is.invalidParameterError('quality', 'integer between 1 and 100', options.quality);
623
+ }
624
+ }
625
+ if (is.defined(options.progressive)) {
626
+ this._setBooleanOption('jpegProgressive', options.progressive);
627
+ }
628
+ if (is.defined(options.chromaSubsampling)) {
629
+ if (is.string(options.chromaSubsampling) && is.inArray(options.chromaSubsampling, ['4:2:0', '4:4:4'])) {
630
+ this.options.jpegChromaSubsampling = options.chromaSubsampling;
631
+ } else {
632
+ throw is.invalidParameterError('chromaSubsampling', 'one of: 4:2:0, 4:4:4', options.chromaSubsampling);
633
+ }
634
+ }
635
+ const optimiseCoding = is.bool(options.optimizeCoding) ? options.optimizeCoding : options.optimiseCoding;
636
+ if (is.defined(optimiseCoding)) {
637
+ this._setBooleanOption('jpegOptimiseCoding', optimiseCoding);
638
+ }
639
+ if (is.defined(options.mozjpeg)) {
640
+ if (is.bool(options.mozjpeg)) {
641
+ if (options.mozjpeg) {
642
+ this.options.jpegTrellisQuantisation = true;
643
+ this.options.jpegOvershootDeringing = true;
644
+ this.options.jpegOptimiseScans = true;
645
+ this.options.jpegProgressive = true;
646
+ this.options.jpegQuantisationTable = 3;
647
+ }
648
+ } else {
649
+ throw is.invalidParameterError('mozjpeg', 'boolean', options.mozjpeg);
650
+ }
651
+ }
652
+ const trellisQuantisation = is.bool(options.trellisQuantization) ? options.trellisQuantization : options.trellisQuantisation;
653
+ if (is.defined(trellisQuantisation)) {
654
+ this._setBooleanOption('jpegTrellisQuantisation', trellisQuantisation);
655
+ }
656
+ if (is.defined(options.overshootDeringing)) {
657
+ this._setBooleanOption('jpegOvershootDeringing', options.overshootDeringing);
658
+ }
659
+ const optimiseScans = is.bool(options.optimizeScans) ? options.optimizeScans : options.optimiseScans;
660
+ if (is.defined(optimiseScans)) {
661
+ this._setBooleanOption('jpegOptimiseScans', optimiseScans);
662
+ if (optimiseScans) {
663
+ this.options.jpegProgressive = true;
664
+ }
665
+ }
666
+ const quantisationTable = is.number(options.quantizationTable) ? options.quantizationTable : options.quantisationTable;
667
+ if (is.defined(quantisationTable)) {
668
+ if (is.integer(quantisationTable) && is.inRange(quantisationTable, 0, 8)) {
669
+ this.options.jpegQuantisationTable = quantisationTable;
670
+ } else {
671
+ throw is.invalidParameterError('quantisationTable', 'integer between 0 and 8', quantisationTable);
672
+ }
673
+ }
674
+ }
675
+ return this._updateFormatOut('jpeg', options);
676
+ }
677
+
678
+ /**
679
+ * Use these PNG options for output image.
680
+ *
681
+ * By default, PNG output is full colour at 8 bits per pixel.
682
+ *
683
+ * Indexed PNG input at 1, 2 or 4 bits per pixel is converted to 8 bits per pixel.
684
+ * Set `palette` to `true` for slower, indexed PNG output.
685
+ *
686
+ * For 16 bits per pixel output, convert to `rgb16` via
687
+ * {@link /api-colour/#tocolourspace toColourspace}.
688
+ *
689
+ * @example
690
+ * // Convert any input to full colour PNG output
691
+ * const data = await sharp(input)
692
+ * .png()
693
+ * .toBuffer();
694
+ *
695
+ * @example
696
+ * // Convert any input to indexed PNG output (slower)
697
+ * const data = await sharp(input)
698
+ * .png({ palette: true })
699
+ * .toBuffer();
700
+ *
701
+ * @example
702
+ * // Output 16 bits per pixel RGB(A)
703
+ * const data = await sharp(input)
704
+ * .toColourspace('rgb16')
705
+ * .png()
706
+ * .toBuffer();
707
+ *
708
+ * @param {Object} [options]
709
+ * @param {boolean} [options.progressive=false] - use progressive (interlace) scan
710
+ * @param {number} [options.compressionLevel=6] - zlib compression level, 0 (fastest, largest) to 9 (slowest, smallest)
711
+ * @param {boolean} [options.adaptiveFiltering=false] - use adaptive row filtering
712
+ * @param {boolean} [options.palette=false] - quantise to a palette-based image with alpha transparency support
713
+ * @param {number} [options.quality=100] - use the lowest number of colours needed to achieve given quality, sets `palette` to `true`
714
+ * @param {number} [options.effort=7] - CPU effort, between 1 (fastest) and 10 (slowest), sets `palette` to `true`
715
+ * @param {number} [options.colours=256] - maximum number of palette entries, sets `palette` to `true`
716
+ * @param {number} [options.colors=256] - alternative spelling of `options.colours`, sets `palette` to `true`
717
+ * @param {number} [options.dither=1.0] - level of Floyd-Steinberg error diffusion, sets `palette` to `true`
718
+ * @param {boolean} [options.force=true] - force PNG output, otherwise attempt to use input format
719
+ * @returns {Sharp}
720
+ * @throws {Error} Invalid options
721
+ */
722
+ function png (options) {
723
+ if (is.object(options)) {
724
+ if (is.defined(options.progressive)) {
725
+ this._setBooleanOption('pngProgressive', options.progressive);
726
+ }
727
+ if (is.defined(options.compressionLevel)) {
728
+ if (is.integer(options.compressionLevel) && is.inRange(options.compressionLevel, 0, 9)) {
729
+ this.options.pngCompressionLevel = options.compressionLevel;
730
+ } else {
731
+ throw is.invalidParameterError('compressionLevel', 'integer between 0 and 9', options.compressionLevel);
732
+ }
733
+ }
734
+ if (is.defined(options.adaptiveFiltering)) {
735
+ this._setBooleanOption('pngAdaptiveFiltering', options.adaptiveFiltering);
736
+ }
737
+ const colours = options.colours || options.colors;
738
+ if (is.defined(colours)) {
739
+ if (is.integer(colours) && is.inRange(colours, 2, 256)) {
740
+ this.options.pngBitdepth = bitdepthFromColourCount(colours);
741
+ } else {
742
+ throw is.invalidParameterError('colours', 'integer between 2 and 256', colours);
743
+ }
744
+ }
745
+ if (is.defined(options.palette)) {
746
+ this._setBooleanOption('pngPalette', options.palette);
747
+ } else if ([options.quality, options.effort, options.colours, options.colors, options.dither].some(is.defined)) {
748
+ this._setBooleanOption('pngPalette', true);
749
+ }
750
+ if (this.options.pngPalette) {
751
+ if (is.defined(options.quality)) {
752
+ if (is.integer(options.quality) && is.inRange(options.quality, 0, 100)) {
753
+ this.options.pngQuality = options.quality;
754
+ } else {
755
+ throw is.invalidParameterError('quality', 'integer between 0 and 100', options.quality);
756
+ }
757
+ }
758
+ if (is.defined(options.effort)) {
759
+ if (is.integer(options.effort) && is.inRange(options.effort, 1, 10)) {
760
+ this.options.pngEffort = options.effort;
761
+ } else {
762
+ throw is.invalidParameterError('effort', 'integer between 1 and 10', options.effort);
763
+ }
764
+ }
765
+ if (is.defined(options.dither)) {
766
+ if (is.number(options.dither) && is.inRange(options.dither, 0, 1)) {
767
+ this.options.pngDither = options.dither;
768
+ } else {
769
+ throw is.invalidParameterError('dither', 'number between 0.0 and 1.0', options.dither);
770
+ }
771
+ }
772
+ }
773
+ }
774
+ return this._updateFormatOut('png', options);
775
+ }
776
+
777
+ /**
778
+ * Use these WebP options for output image.
779
+ *
780
+ * @example
781
+ * // Convert any input to lossless WebP output
782
+ * const data = await sharp(input)
783
+ * .webp({ lossless: true })
784
+ * .toBuffer();
785
+ *
786
+ * @example
787
+ * // Optimise the file size of an animated WebP
788
+ * const outputWebp = await sharp(inputWebp, { animated: true })
789
+ * .webp({ effort: 6 })
790
+ * .toBuffer();
791
+ *
792
+ * @param {Object} [options] - output options
793
+ * @param {number} [options.quality=80] - quality, integer 1-100
794
+ * @param {number} [options.alphaQuality=100] - quality of alpha layer, integer 0-100
795
+ * @param {boolean} [options.lossless=false] - use lossless compression mode
796
+ * @param {boolean} [options.nearLossless=false] - use near_lossless compression mode
797
+ * @param {boolean} [options.smartSubsample=false] - use high quality chroma subsampling
798
+ * @param {boolean} [options.smartDeblock=false] - auto-adjust the deblocking filter, can improve low contrast edges (slow)
799
+ * @param {string} [options.preset='default'] - named preset for preprocessing/filtering, one of: default, photo, picture, drawing, icon, text
800
+ * @param {number} [options.effort=4] - CPU effort, between 0 (fastest) and 6 (slowest)
801
+ * @param {number} [options.loop=0] - number of animation iterations, use 0 for infinite animation
802
+ * @param {number|number[]} [options.delay] - delay(s) between animation frames (in milliseconds)
803
+ * @param {boolean} [options.minSize=false] - prevent use of animation key frames to minimise file size (slow)
804
+ * @param {boolean} [options.mixed=false] - allow mixture of lossy and lossless animation frames (slow)
805
+ * @param {boolean} [options.exact=false] - preserve the colour data in transparent pixels
806
+ * @param {boolean} [options.force=true] - force WebP output, otherwise attempt to use input format
807
+ * @returns {Sharp}
808
+ * @throws {Error} Invalid options
809
+ */
810
+ function webp (options) {
811
+ if (is.object(options)) {
812
+ if (is.defined(options.quality)) {
813
+ if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
814
+ this.options.webpQuality = options.quality;
815
+ } else {
816
+ throw is.invalidParameterError('quality', 'integer between 1 and 100', options.quality);
817
+ }
818
+ }
819
+ if (is.defined(options.alphaQuality)) {
820
+ if (is.integer(options.alphaQuality) && is.inRange(options.alphaQuality, 0, 100)) {
821
+ this.options.webpAlphaQuality = options.alphaQuality;
822
+ } else {
823
+ throw is.invalidParameterError('alphaQuality', 'integer between 0 and 100', options.alphaQuality);
824
+ }
825
+ }
826
+ if (is.defined(options.lossless)) {
827
+ this._setBooleanOption('webpLossless', options.lossless);
828
+ }
829
+ if (is.defined(options.nearLossless)) {
830
+ this._setBooleanOption('webpNearLossless', options.nearLossless);
831
+ }
832
+ if (is.defined(options.smartSubsample)) {
833
+ this._setBooleanOption('webpSmartSubsample', options.smartSubsample);
834
+ }
835
+ if (is.defined(options.smartDeblock)) {
836
+ this._setBooleanOption('webpSmartDeblock', options.smartDeblock);
837
+ }
838
+ if (is.defined(options.preset)) {
839
+ if (is.string(options.preset) && is.inArray(options.preset, ['default', 'photo', 'picture', 'drawing', 'icon', 'text'])) {
840
+ this.options.webpPreset = options.preset;
841
+ } else {
842
+ throw is.invalidParameterError('preset', 'one of: default, photo, picture, drawing, icon, text', options.preset);
843
+ }
844
+ }
845
+ if (is.defined(options.effort)) {
846
+ if (is.integer(options.effort) && is.inRange(options.effort, 0, 6)) {
847
+ this.options.webpEffort = options.effort;
848
+ } else {
849
+ throw is.invalidParameterError('effort', 'integer between 0 and 6', options.effort);
850
+ }
851
+ }
852
+ if (is.defined(options.minSize)) {
853
+ this._setBooleanOption('webpMinSize', options.minSize);
854
+ }
855
+ if (is.defined(options.mixed)) {
856
+ this._setBooleanOption('webpMixed', options.mixed);
857
+ }
858
+ if (is.defined(options.exact)) {
859
+ this._setBooleanOption('webpExact', options.exact);
860
+ }
861
+ }
862
+ trySetAnimationOptions(options, this.options);
863
+ return this._updateFormatOut('webp', options);
864
+ }
865
+
866
+ /**
867
+ * Use these GIF options for the output image.
868
+ *
869
+ * The first entry in the palette is reserved for transparency.
870
+ *
871
+ * The palette of the input image will be re-used if possible.
872
+ *
873
+ * @since 0.30.0
874
+ *
875
+ * @example
876
+ * // Convert PNG to GIF
877
+ * await sharp(pngBuffer)
878
+ * .gif()
879
+ * .toBuffer();
880
+ *
881
+ * @example
882
+ * // Convert animated WebP to animated GIF
883
+ * await sharp('animated.webp', { animated: true })
884
+ * .toFile('animated.gif');
885
+ *
886
+ * @example
887
+ * // Create a 128x128, cropped, non-dithered, animated thumbnail of an animated GIF
888
+ * const out = await sharp('in.gif', { animated: true })
889
+ * .resize({ width: 128, height: 128 })
890
+ * .gif({ dither: 0 })
891
+ * .toBuffer();
892
+ *
893
+ * @example
894
+ * // Lossy file size reduction of animated GIF
895
+ * await sharp('in.gif', { animated: true })
896
+ * .gif({ interFrameMaxError: 8 })
897
+ * .toFile('optim.gif');
898
+ *
899
+ * @param {Object} [options] - output options
900
+ * @param {boolean} [options.reuse=true] - re-use existing palette, otherwise generate new (slow)
901
+ * @param {boolean} [options.progressive=false] - use progressive (interlace) scan
902
+ * @param {number} [options.colours=256] - maximum number of palette entries, including transparency, between 2 and 256
903
+ * @param {number} [options.colors=256] - alternative spelling of `options.colours`
904
+ * @param {number} [options.effort=7] - CPU effort, between 1 (fastest) and 10 (slowest)
905
+ * @param {number} [options.dither=1.0] - level of Floyd-Steinberg error diffusion, between 0 (least) and 1 (most)
906
+ * @param {number} [options.interFrameMaxError=0] - maximum inter-frame error for transparency, between 0 (lossless) and 32
907
+ * @param {number} [options.interPaletteMaxError=3] - maximum inter-palette error for palette reuse, between 0 and 256
908
+ * @param {boolean} [options.keepDuplicateFrames=false] - keep duplicate frames in the output instead of combining them
909
+ * @param {number} [options.loop=0] - number of animation iterations, use 0 for infinite animation
910
+ * @param {number|number[]} [options.delay] - delay(s) between animation frames (in milliseconds)
911
+ * @param {boolean} [options.force=true] - force GIF output, otherwise attempt to use input format
912
+ * @returns {Sharp}
913
+ * @throws {Error} Invalid options
914
+ */
915
+ function gif (options) {
916
+ if (is.object(options)) {
917
+ if (is.defined(options.reuse)) {
918
+ this._setBooleanOption('gifReuse', options.reuse);
919
+ }
920
+ if (is.defined(options.progressive)) {
921
+ this._setBooleanOption('gifProgressive', options.progressive);
922
+ }
923
+ const colours = options.colours || options.colors;
924
+ if (is.defined(colours)) {
925
+ if (is.integer(colours) && is.inRange(colours, 2, 256)) {
926
+ this.options.gifBitdepth = bitdepthFromColourCount(colours);
927
+ } else {
928
+ throw is.invalidParameterError('colours', 'integer between 2 and 256', colours);
929
+ }
930
+ }
931
+ if (is.defined(options.effort)) {
932
+ if (is.number(options.effort) && is.inRange(options.effort, 1, 10)) {
933
+ this.options.gifEffort = options.effort;
934
+ } else {
935
+ throw is.invalidParameterError('effort', 'integer between 1 and 10', options.effort);
936
+ }
937
+ }
938
+ if (is.defined(options.dither)) {
939
+ if (is.number(options.dither) && is.inRange(options.dither, 0, 1)) {
940
+ this.options.gifDither = options.dither;
941
+ } else {
942
+ throw is.invalidParameterError('dither', 'number between 0.0 and 1.0', options.dither);
943
+ }
944
+ }
945
+ if (is.defined(options.interFrameMaxError)) {
946
+ if (is.number(options.interFrameMaxError) && is.inRange(options.interFrameMaxError, 0, 32)) {
947
+ this.options.gifInterFrameMaxError = options.interFrameMaxError;
948
+ } else {
949
+ throw is.invalidParameterError('interFrameMaxError', 'number between 0.0 and 32.0', options.interFrameMaxError);
950
+ }
951
+ }
952
+ if (is.defined(options.interPaletteMaxError)) {
953
+ if (is.number(options.interPaletteMaxError) && is.inRange(options.interPaletteMaxError, 0, 256)) {
954
+ this.options.gifInterPaletteMaxError = options.interPaletteMaxError;
955
+ } else {
956
+ throw is.invalidParameterError('interPaletteMaxError', 'number between 0.0 and 256.0', options.interPaletteMaxError);
957
+ }
958
+ }
959
+ if (is.defined(options.keepDuplicateFrames)) {
960
+ if (is.bool(options.keepDuplicateFrames)) {
961
+ this._setBooleanOption('gifKeepDuplicateFrames', options.keepDuplicateFrames);
962
+ } else {
963
+ throw is.invalidParameterError('keepDuplicateFrames', 'boolean', options.keepDuplicateFrames);
964
+ }
965
+ }
966
+ }
967
+ trySetAnimationOptions(options, this.options);
968
+ return this._updateFormatOut('gif', options);
969
+ }
970
+
971
+ /**
972
+ * Use these JP2 options for output image.
973
+ *
974
+ * Requires libvips compiled with support for OpenJPEG.
975
+ * The prebuilt binaries do not include this - see
976
+ * {@link /install#custom-libvips installing a custom libvips}.
977
+ *
978
+ * @example
979
+ * // Convert any input to lossless JP2 output
980
+ * const data = await sharp(input)
981
+ * .jp2({ lossless: true })
982
+ * .toBuffer();
983
+ *
984
+ * @example
985
+ * // Convert any input to very high quality JP2 output
986
+ * const data = await sharp(input)
987
+ * .jp2({
988
+ * quality: 100,
989
+ * chromaSubsampling: '4:4:4'
990
+ * })
991
+ * .toBuffer();
992
+ *
993
+ * @since 0.29.1
994
+ *
995
+ * @param {Object} [options] - output options
996
+ * @param {number} [options.quality=80] - quality, integer 1-100
997
+ * @param {boolean} [options.lossless=false] - use lossless compression mode
998
+ * @param {number} [options.tileWidth=512] - horizontal tile size
999
+ * @param {number} [options.tileHeight=512] - vertical tile size
1000
+ * @param {string} [options.chromaSubsampling='4:4:4'] - set to '4:2:0' to use chroma subsampling
1001
+ * @returns {Sharp}
1002
+ * @throws {Error} Invalid options
1003
+ */
1004
+ function jp2 (options) {
1005
+ /* node:coverage ignore next 41 */
1006
+ if (!this.constructor.format.jp2.output.buffer) {
1007
+ throw errJp2Save();
1008
+ }
1009
+ if (is.object(options)) {
1010
+ if (is.defined(options.quality)) {
1011
+ if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
1012
+ this.options.jp2Quality = options.quality;
1013
+ } else {
1014
+ throw is.invalidParameterError('quality', 'integer between 1 and 100', options.quality);
1015
+ }
1016
+ }
1017
+ if (is.defined(options.lossless)) {
1018
+ if (is.bool(options.lossless)) {
1019
+ this.options.jp2Lossless = options.lossless;
1020
+ } else {
1021
+ throw is.invalidParameterError('lossless', 'boolean', options.lossless);
1022
+ }
1023
+ }
1024
+ if (is.defined(options.tileWidth)) {
1025
+ if (is.integer(options.tileWidth) && is.inRange(options.tileWidth, 1, 32768)) {
1026
+ this.options.jp2TileWidth = options.tileWidth;
1027
+ } else {
1028
+ throw is.invalidParameterError('tileWidth', 'integer between 1 and 32768', options.tileWidth);
1029
+ }
1030
+ }
1031
+ if (is.defined(options.tileHeight)) {
1032
+ if (is.integer(options.tileHeight) && is.inRange(options.tileHeight, 1, 32768)) {
1033
+ this.options.jp2TileHeight = options.tileHeight;
1034
+ } else {
1035
+ throw is.invalidParameterError('tileHeight', 'integer between 1 and 32768', options.tileHeight);
1036
+ }
1037
+ }
1038
+ if (is.defined(options.chromaSubsampling)) {
1039
+ if (is.string(options.chromaSubsampling) && is.inArray(options.chromaSubsampling, ['4:2:0', '4:4:4'])) {
1040
+ this.options.jp2ChromaSubsampling = options.chromaSubsampling;
1041
+ } else {
1042
+ throw is.invalidParameterError('chromaSubsampling', 'one of: 4:2:0, 4:4:4', options.chromaSubsampling);
1043
+ }
1044
+ }
1045
+ }
1046
+ return this._updateFormatOut('jp2', options);
1047
+ }
1048
+
1049
+ /**
1050
+ * Set animation options if available.
1051
+ * @private
1052
+ *
1053
+ * @param {Object} [source] - output options
1054
+ * @param {number} [source.loop=0] - number of animation iterations, use 0 for infinite animation
1055
+ * @param {number[]} [source.delay] - list of delays between animation frames (in milliseconds)
1056
+ * @param {Object} [target] - target object for valid options
1057
+ * @throws {Error} Invalid options
1058
+ */
1059
+ function trySetAnimationOptions (source, target) {
1060
+ if (is.object(source) && is.defined(source.loop)) {
1061
+ if (is.integer(source.loop) && is.inRange(source.loop, 0, 65535)) {
1062
+ target.loop = source.loop;
1063
+ } else {
1064
+ throw is.invalidParameterError('loop', 'integer between 0 and 65535', source.loop);
1065
+ }
1066
+ }
1067
+ if (is.object(source) && is.defined(source.delay)) {
1068
+ // We allow singular values as well
1069
+ if (is.integer(source.delay) && is.inRange(source.delay, 0, 65535)) {
1070
+ target.delay = [source.delay];
1071
+ } else if (
1072
+ Array.isArray(source.delay) &&
1073
+ source.delay.every(is.integer) &&
1074
+ source.delay.every(v => is.inRange(v, 0, 65535))) {
1075
+ target.delay = source.delay;
1076
+ } else {
1077
+ throw is.invalidParameterError('delay', 'integer or an array of integers between 0 and 65535', source.delay);
1078
+ }
1079
+ }
1080
+ }
1081
+
1082
+ /**
1083
+ * Use these TIFF options for output image.
1084
+ *
1085
+ * The `density` can be set in pixels/inch via {@link #withmetadata withMetadata}
1086
+ * instead of providing `xres` and `yres` in pixels/mm.
1087
+ *
1088
+ * @example
1089
+ * // Convert SVG input to LZW-compressed, 1 bit per pixel TIFF output
1090
+ * sharp('input.svg')
1091
+ * .tiff({
1092
+ * compression: 'lzw',
1093
+ * bitdepth: 1
1094
+ * })
1095
+ * .toFile('1-bpp-output.tiff')
1096
+ * .then(info => { ... });
1097
+ *
1098
+ * @param {Object} [options] - output options
1099
+ * @param {number} [options.quality=80] - quality, integer 1-100
1100
+ * @param {boolean} [options.force=true] - force TIFF output, otherwise attempt to use input format
1101
+ * @param {string} [options.compression='jpeg'] - compression options: none, jpeg, deflate, packbits, ccittfax4, lzw, webp, zstd, jp2k
1102
+ * @param {boolean} [options.bigtiff=false] - use BigTIFF variant (has no effect when compression is none)
1103
+ * @param {string} [options.predictor='horizontal'] - compression predictor options: none, horizontal, float
1104
+ * @param {boolean} [options.pyramid=false] - write an image pyramid
1105
+ * @param {boolean} [options.tile=false] - write a tiled tiff
1106
+ * @param {number} [options.tileWidth=256] - horizontal tile size
1107
+ * @param {number} [options.tileHeight=256] - vertical tile size
1108
+ * @param {number} [options.xres=1.0] - horizontal resolution in pixels/mm
1109
+ * @param {number} [options.yres=1.0] - vertical resolution in pixels/mm
1110
+ * @param {string} [options.resolutionUnit='inch'] - resolution unit options: inch, cm
1111
+ * @param {number} [options.bitdepth=0] - reduce bitdepth to 1, 2 or 4 bit
1112
+ * @param {boolean} [options.miniswhite=false] - write 1-bit images as miniswhite
1113
+ * @returns {Sharp}
1114
+ * @throws {Error} Invalid options
1115
+ */
1116
+ function tiff (options) {
1117
+ if (is.object(options)) {
1118
+ if (is.defined(options.quality)) {
1119
+ if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
1120
+ this.options.tiffQuality = options.quality;
1121
+ } else {
1122
+ throw is.invalidParameterError('quality', 'integer between 1 and 100', options.quality);
1123
+ }
1124
+ }
1125
+ if (is.defined(options.bitdepth)) {
1126
+ if (is.integer(options.bitdepth) && is.inArray(options.bitdepth, [1, 2, 4])) {
1127
+ this.options.tiffBitdepth = options.bitdepth;
1128
+ } else {
1129
+ throw is.invalidParameterError('bitdepth', '1, 2 or 4', options.bitdepth);
1130
+ }
1131
+ }
1132
+ // tiling
1133
+ if (is.defined(options.tile)) {
1134
+ this._setBooleanOption('tiffTile', options.tile);
1135
+ }
1136
+ if (is.defined(options.tileWidth)) {
1137
+ if (is.integer(options.tileWidth) && options.tileWidth > 0) {
1138
+ this.options.tiffTileWidth = options.tileWidth;
1139
+ } else {
1140
+ throw is.invalidParameterError('tileWidth', 'integer greater than zero', options.tileWidth);
1141
+ }
1142
+ }
1143
+ if (is.defined(options.tileHeight)) {
1144
+ if (is.integer(options.tileHeight) && options.tileHeight > 0) {
1145
+ this.options.tiffTileHeight = options.tileHeight;
1146
+ } else {
1147
+ throw is.invalidParameterError('tileHeight', 'integer greater than zero', options.tileHeight);
1148
+ }
1149
+ }
1150
+ // miniswhite
1151
+ if (is.defined(options.miniswhite)) {
1152
+ this._setBooleanOption('tiffMiniswhite', options.miniswhite);
1153
+ }
1154
+ // pyramid
1155
+ if (is.defined(options.pyramid)) {
1156
+ this._setBooleanOption('tiffPyramid', options.pyramid);
1157
+ }
1158
+ // resolution
1159
+ if (is.defined(options.xres)) {
1160
+ if (is.number(options.xres) && options.xres > 0) {
1161
+ this.options.tiffXres = options.xres;
1162
+ } else {
1163
+ throw is.invalidParameterError('xres', 'number greater than zero', options.xres);
1164
+ }
1165
+ }
1166
+ if (is.defined(options.yres)) {
1167
+ if (is.number(options.yres) && options.yres > 0) {
1168
+ this.options.tiffYres = options.yres;
1169
+ } else {
1170
+ throw is.invalidParameterError('yres', 'number greater than zero', options.yres);
1171
+ }
1172
+ }
1173
+ // compression
1174
+ if (is.defined(options.compression)) {
1175
+ if (is.string(options.compression) && is.inArray(options.compression, ['none', 'jpeg', 'deflate', 'packbits', 'ccittfax4', 'lzw', 'webp', 'zstd', 'jp2k'])) {
1176
+ this.options.tiffCompression = options.compression;
1177
+ } else {
1178
+ throw is.invalidParameterError('compression', 'one of: none, jpeg, deflate, packbits, ccittfax4, lzw, webp, zstd, jp2k', options.compression);
1179
+ }
1180
+ }
1181
+ // bigtiff
1182
+ if (is.defined(options.bigtiff)) {
1183
+ this._setBooleanOption('tiffBigtiff', options.bigtiff);
1184
+ }
1185
+ // predictor
1186
+ if (is.defined(options.predictor)) {
1187
+ if (is.string(options.predictor) && is.inArray(options.predictor, ['none', 'horizontal', 'float'])) {
1188
+ this.options.tiffPredictor = options.predictor;
1189
+ } else {
1190
+ throw is.invalidParameterError('predictor', 'one of: none, horizontal, float', options.predictor);
1191
+ }
1192
+ }
1193
+ // resolutionUnit
1194
+ if (is.defined(options.resolutionUnit)) {
1195
+ if (is.string(options.resolutionUnit) && is.inArray(options.resolutionUnit, ['inch', 'cm'])) {
1196
+ this.options.tiffResolutionUnit = options.resolutionUnit;
1197
+ } else {
1198
+ throw is.invalidParameterError('resolutionUnit', 'one of: inch, cm', options.resolutionUnit);
1199
+ }
1200
+ }
1201
+ }
1202
+ return this._updateFormatOut('tiff', options);
1203
+ }
1204
+
1205
+ /**
1206
+ * Use these AVIF options for output image.
1207
+ *
1208
+ * AVIF image sequences are not supported.
1209
+ * Prebuilt binaries support a bitdepth of 8 only.
1210
+ *
1211
+ * When using Windows ARM64, this feature requires a CPU with ARM64v8.4 or later.
1212
+ *
1213
+ * @example
1214
+ * const data = await sharp(input)
1215
+ * .avif({ effort: 2 })
1216
+ * .toBuffer();
1217
+ *
1218
+ * @example
1219
+ * const data = await sharp(input)
1220
+ * .avif({ lossless: true })
1221
+ * .toBuffer();
1222
+ *
1223
+ * @since 0.27.0
1224
+ *
1225
+ * @param {Object} [options] - output options
1226
+ * @param {number} [options.quality=50] - quality, integer 1-100
1227
+ * @param {boolean} [options.lossless=false] - use lossless compression
1228
+ * @param {number} [options.effort=4] - CPU effort, between 0 (fastest) and 9 (slowest)
1229
+ * @param {string} [options.chromaSubsampling='4:4:4'] - set to '4:2:0' to use chroma subsampling
1230
+ * @param {number} [options.bitdepth=8] - set bitdepth to 8, 10 or 12 bit
1231
+ * @param {string} [options.tune='iq'] - tune output for a quality metric, one of 'iq' (default), 'ssim' (default when lossless) or 'psnr'
1232
+ * @returns {Sharp}
1233
+ * @throws {Error} Invalid options
1234
+ */
1235
+ function avif (options) {
1236
+ const tune = is.object(options) && is.defined(options.tune) ? options.tune : 'iq';
1237
+ return this.heif({ ...options, compression: 'av1', tune });
1238
+ }
1239
+
1240
+ /**
1241
+ * Use these HEIF options for output image.
1242
+ *
1243
+ * Support for patent-encumbered HEIC images using `hevc` compression requires the use of a
1244
+ * globally-installed libvips compiled with support for libheif, libde265 and x265.
1245
+ *
1246
+ * @example
1247
+ * const data = await sharp(input)
1248
+ * .heif({ compression: 'hevc' })
1249
+ * .toBuffer();
1250
+ *
1251
+ * @since 0.23.0
1252
+ *
1253
+ * @param {Object} options - output options
1254
+ * @param {string} options.compression - compression format: av1, hevc
1255
+ * @param {number} [options.quality=50] - quality, integer 1-100
1256
+ * @param {boolean} [options.lossless=false] - use lossless compression
1257
+ * @param {number} [options.effort=4] - CPU effort, between 0 (fastest) and 9 (slowest)
1258
+ * @param {string} [options.chromaSubsampling='4:4:4'] - set to '4:2:0' to use chroma subsampling
1259
+ * @param {number} [options.bitdepth=8] - set bitdepth to 8, 10 or 12 bit
1260
+ * @param {string} [options.tune='ssim'] - tune output for a quality metric, one of 'ssim' (default), 'psnr' or 'iq'
1261
+ * @returns {Sharp}
1262
+ * @throws {Error} Invalid options
1263
+ */
1264
+ function heif (options) {
1265
+ if (is.object(options)) {
1266
+ if (is.string(options.compression) && is.inArray(options.compression, ['av1', 'hevc'])) {
1267
+ this.options.heifCompression = options.compression;
1268
+ } else {
1269
+ throw is.invalidParameterError('compression', 'one of: av1, hevc', options.compression);
1270
+ }
1271
+ if (is.defined(options.quality)) {
1272
+ if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
1273
+ this.options.heifQuality = options.quality;
1274
+ } else {
1275
+ throw is.invalidParameterError('quality', 'integer between 1 and 100', options.quality);
1276
+ }
1277
+ }
1278
+ if (is.defined(options.lossless)) {
1279
+ if (is.bool(options.lossless)) {
1280
+ this.options.heifLossless = options.lossless;
1281
+ } else {
1282
+ throw is.invalidParameterError('lossless', 'boolean', options.lossless);
1283
+ }
1284
+ }
1285
+ if (is.defined(options.effort)) {
1286
+ if (is.integer(options.effort) && is.inRange(options.effort, 0, 9)) {
1287
+ this.options.heifEffort = options.effort;
1288
+ } else {
1289
+ throw is.invalidParameterError('effort', 'integer between 0 and 9', options.effort);
1290
+ }
1291
+ }
1292
+ if (is.defined(options.chromaSubsampling)) {
1293
+ if (is.string(options.chromaSubsampling) && is.inArray(options.chromaSubsampling, ['4:2:0', '4:4:4'])) {
1294
+ this.options.heifChromaSubsampling = options.chromaSubsampling;
1295
+ } else {
1296
+ throw is.invalidParameterError('chromaSubsampling', 'one of: 4:2:0, 4:4:4', options.chromaSubsampling);
1297
+ }
1298
+ }
1299
+ if (is.defined(options.bitdepth)) {
1300
+ if (is.integer(options.bitdepth) && is.inArray(options.bitdepth, [8, 10, 12])) {
1301
+ if (options.bitdepth !== 8 && this.constructor.versions.heif) {
1302
+ throw is.invalidParameterError('bitdepth when using prebuilt binaries', 8, options.bitdepth);
1303
+ }
1304
+ this.options.heifBitdepth = options.bitdepth;
1305
+ } else {
1306
+ throw is.invalidParameterError('bitdepth', '8, 10 or 12', options.bitdepth);
1307
+ }
1308
+ }
1309
+ if (is.defined(options.tune)) {
1310
+ if (is.string(options.tune) && is.inArray(options.tune, ['iq', 'ssim', 'psnr'])) {
1311
+ if (this.options.heifLossless && options.tune === 'iq') {
1312
+ this.options.heifTune = 'ssim';
1313
+ } else {
1314
+ this.options.heifTune = options.tune;
1315
+ }
1316
+ } else {
1317
+ throw is.invalidParameterError('tune', 'one of: psnr, ssim, iq', options.tune);
1318
+ }
1319
+ }
1320
+ } else {
1321
+ throw is.invalidParameterError('options', 'Object', options);
1322
+ }
1323
+ return this._updateFormatOut('heif', options);
1324
+ }
1325
+
1326
+ /**
1327
+ * Use these JPEG-XL (JXL) options for output image.
1328
+ *
1329
+ * This feature is experimental, please do not use in production systems.
1330
+ *
1331
+ * Requires libvips compiled with support for libjxl.
1332
+ * The prebuilt binaries do not include this - see
1333
+ * {@link /install/#custom-libvips installing a custom libvips}.
1334
+ *
1335
+ * @since 0.31.3
1336
+ *
1337
+ * @param {Object} [options] - output options
1338
+ * @param {number} [options.distance=1.0] - maximum encoding error, between 0 (highest quality) and 15 (lowest quality)
1339
+ * @param {number} [options.quality] - calculate `distance` based on JPEG-like quality, between 1 and 100, overrides distance if specified
1340
+ * @param {number} [options.decodingTier=0] - target decode speed tier, between 0 (highest quality) and 4 (lowest quality)
1341
+ * @param {boolean} [options.lossless=false] - use lossless compression
1342
+ * @param {number} [options.effort=7] - CPU effort, between 1 (fastest) and 9 (slowest)
1343
+ * @param {number} [options.loop=0] - number of animation iterations, use 0 for infinite animation
1344
+ * @param {number|number[]} [options.delay] - delay(s) between animation frames (in milliseconds)
1345
+ * @returns {Sharp}
1346
+ * @throws {Error} Invalid options
1347
+ */
1348
+ function jxl (options) {
1349
+ if (is.object(options)) {
1350
+ if (is.defined(options.quality)) {
1351
+ if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
1352
+ // https://github.com/libjxl/libjxl/blob/0aeea7f180bafd6893c1db8072dcb67d2aa5b03d/tools/cjxl_main.cc#L640-L644
1353
+ this.options.jxlDistance = options.quality >= 30
1354
+ ? 0.1 + (100 - options.quality) * 0.09
1355
+ : 53 / 3000 * options.quality * options.quality - 23 / 20 * options.quality + 25;
1356
+ } else {
1357
+ throw is.invalidParameterError('quality', 'integer between 1 and 100', options.quality);
1358
+ }
1359
+ } else if (is.defined(options.distance)) {
1360
+ if (is.number(options.distance) && is.inRange(options.distance, 0, 15)) {
1361
+ this.options.jxlDistance = options.distance;
1362
+ } else {
1363
+ throw is.invalidParameterError('distance', 'number between 0.0 and 15.0', options.distance);
1364
+ }
1365
+ }
1366
+ if (is.defined(options.decodingTier)) {
1367
+ if (is.integer(options.decodingTier) && is.inRange(options.decodingTier, 0, 4)) {
1368
+ this.options.jxlDecodingTier = options.decodingTier;
1369
+ } else {
1370
+ throw is.invalidParameterError('decodingTier', 'integer between 0 and 4', options.decodingTier);
1371
+ }
1372
+ }
1373
+ if (is.defined(options.lossless)) {
1374
+ if (is.bool(options.lossless)) {
1375
+ this.options.jxlLossless = options.lossless;
1376
+ } else {
1377
+ throw is.invalidParameterError('lossless', 'boolean', options.lossless);
1378
+ }
1379
+ }
1380
+ if (is.defined(options.effort)) {
1381
+ if (is.integer(options.effort) && is.inRange(options.effort, 1, 9)) {
1382
+ this.options.jxlEffort = options.effort;
1383
+ } else {
1384
+ throw is.invalidParameterError('effort', 'integer between 1 and 9', options.effort);
1385
+ }
1386
+ }
1387
+ }
1388
+ trySetAnimationOptions(options, this.options);
1389
+ return this._updateFormatOut('jxl', options);
1390
+ }
1391
+
1392
+ /**
1393
+ * Force output to be raw, uncompressed pixel data.
1394
+ * Pixel ordering is left-to-right, top-to-bottom, without padding.
1395
+ * Channel ordering will be RGB or RGBA for non-greyscale colourspaces.
1396
+ *
1397
+ * @example
1398
+ * // Extract raw, unsigned 8-bit RGB pixel data from JPEG input
1399
+ * const { data, info } = await sharp('input.jpg')
1400
+ * .raw()
1401
+ * .toBuffer({ resolveWithObject: true });
1402
+ *
1403
+ * @example
1404
+ * // Extract alpha channel as raw, unsigned 16-bit pixel data from PNG input
1405
+ * const data = await sharp('input.png')
1406
+ * .ensureAlpha()
1407
+ * .extractChannel(3)
1408
+ * .toColourspace('b-w')
1409
+ * .raw({ depth: 'ushort' })
1410
+ * .toBuffer();
1411
+ *
1412
+ * @param {Object} [options] - output options
1413
+ * @param {string} [options.depth='uchar'] - bit depth, one of: char, uchar (default), short, ushort, int, uint, float, complex, double, dpcomplex
1414
+ * @returns {Sharp}
1415
+ * @throws {Error} Invalid options
1416
+ */
1417
+ function raw (options) {
1418
+ if (is.object(options)) {
1419
+ if (is.defined(options.depth)) {
1420
+ if (is.string(options.depth) && is.inArray(options.depth,
1421
+ ['char', 'uchar', 'short', 'ushort', 'int', 'uint', 'float', 'complex', 'double', 'dpcomplex']
1422
+ )) {
1423
+ this.options.rawDepth = options.depth;
1424
+ } else {
1425
+ throw is.invalidParameterError('depth', 'one of: char, uchar, short, ushort, int, uint, float, complex, double, dpcomplex', options.depth);
1426
+ }
1427
+ }
1428
+ }
1429
+ return this._updateFormatOut('raw');
1430
+ }
1431
+
1432
+ /**
1433
+ * Use tile-based deep zoom (image pyramid) output.
1434
+ *
1435
+ * Set the format and options for tile images via the `toFormat`, `jpeg`, `png` or `webp` functions.
1436
+ * Use a `.zip` or `.szi` file extension with `toFile` to write to a compressed archive file format.
1437
+ *
1438
+ * The container will be set to `zip` when the output is a Buffer or Stream, otherwise it will default to `fs`.
1439
+ *
1440
+ * @example
1441
+ * sharp('input.tiff')
1442
+ * .png()
1443
+ * .tile({
1444
+ * size: 512
1445
+ * })
1446
+ * .toFile('output.dz', function(err, info) {
1447
+ * // output.dzi is the Deep Zoom XML definition
1448
+ * // output_files contains 512x512 tiles grouped by zoom level
1449
+ * });
1450
+ *
1451
+ * @example
1452
+ * const zipFileWithTiles = await sharp(input)
1453
+ * .tile({ basename: "tiles" })
1454
+ * .toBuffer();
1455
+ *
1456
+ * @example
1457
+ * const iiififier = sharp().tile({ layout: "iiif" });
1458
+ * readableStream
1459
+ * .pipe(iiififier)
1460
+ * .pipe(writeableStream);
1461
+ *
1462
+ * @param {Object} [options]
1463
+ * @param {number} [options.size=256] tile size in pixels, a value between 1 and 8192.
1464
+ * @param {number} [options.overlap=0] tile overlap in pixels, a value between 0 and 8192.
1465
+ * @param {number} [options.angle=0] tile angle of rotation, must be a multiple of 90.
1466
+ * @param {string|Object} [options.background={r: 255, g: 255, b: 255, alpha: 1}] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to white without transparency.
1467
+ * @param {string} [options.depth] how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout.
1468
+ * @param {number} [options.skipBlanks=-1] Threshold to skip tile generation. Range is 0-255 for 8-bit images, 0-65535 for 16-bit images. Default is 5 for `google` layout, -1 (no skip) otherwise.
1469
+ * @param {string} [options.container='fs'] tile container, with value `fs` (filesystem) or `zip` (compressed file).
1470
+ * @param {string} [options.layout='dz'] filesystem layout, possible values are `dz`, `iiif`, `iiif3`, `zoomify` or `google`.
1471
+ * @param {boolean} [options.centre=false] centre image in tile.
1472
+ * @param {boolean} [options.center=false] alternative spelling of centre.
1473
+ * @param {string} [options.id='https://example.com/iiif'] when `layout` is `iiif`/`iiif3`, sets the `@id`/`id` attribute of `info.json`
1474
+ * @param {string} [options.basename] the name of the directory within the zip file when container is `zip`.
1475
+ * @returns {Sharp}
1476
+ * @throws {Error} Invalid parameters
1477
+ */
1478
+ function tile (options) {
1479
+ if (is.object(options)) {
1480
+ // Size of square tiles, in pixels
1481
+ if (is.defined(options.size)) {
1482
+ if (is.integer(options.size) && is.inRange(options.size, 1, 8192)) {
1483
+ this.options.tileSize = options.size;
1484
+ } else {
1485
+ throw is.invalidParameterError('size', 'integer between 1 and 8192', options.size);
1486
+ }
1487
+ }
1488
+ // Overlap of tiles, in pixels
1489
+ if (is.defined(options.overlap)) {
1490
+ if (is.integer(options.overlap) && is.inRange(options.overlap, 0, 8192)) {
1491
+ if (options.overlap > this.options.tileSize) {
1492
+ throw is.invalidParameterError('overlap', `<= size (${this.options.tileSize})`, options.overlap);
1493
+ }
1494
+ this.options.tileOverlap = options.overlap;
1495
+ } else {
1496
+ throw is.invalidParameterError('overlap', 'integer between 0 and 8192', options.overlap);
1497
+ }
1498
+ }
1499
+ // Container
1500
+ if (is.defined(options.container)) {
1501
+ if (is.string(options.container) && is.inArray(options.container, ['fs', 'zip'])) {
1502
+ this.options.tileContainer = options.container;
1503
+ } else {
1504
+ throw is.invalidParameterError('container', 'one of: fs, zip', options.container);
1505
+ }
1506
+ }
1507
+ // Layout
1508
+ if (is.defined(options.layout)) {
1509
+ if (is.string(options.layout) && is.inArray(options.layout, ['dz', 'google', 'iiif', 'iiif3', 'zoomify'])) {
1510
+ this.options.tileLayout = options.layout;
1511
+ } else {
1512
+ throw is.invalidParameterError('layout', 'one of: dz, google, iiif, iiif3, zoomify', options.layout);
1513
+ }
1514
+ }
1515
+ // Angle of rotation,
1516
+ if (is.defined(options.angle)) {
1517
+ if (is.integer(options.angle) && !(options.angle % 90)) {
1518
+ this.options.tileAngle = options.angle;
1519
+ } else {
1520
+ throw is.invalidParameterError('angle', 'positive/negative multiple of 90', options.angle);
1521
+ }
1522
+ }
1523
+ // Background colour
1524
+ this._setBackgroundColourOption('tileBackground', options.background);
1525
+ // Depth of tiles
1526
+ if (is.defined(options.depth)) {
1527
+ if (is.string(options.depth) && is.inArray(options.depth, ['onepixel', 'onetile', 'one'])) {
1528
+ this.options.tileDepth = options.depth;
1529
+ } else {
1530
+ throw is.invalidParameterError('depth', 'one of: onepixel, onetile, one', options.depth);
1531
+ }
1532
+ }
1533
+ // Threshold to skip blank tiles
1534
+ if (is.defined(options.skipBlanks)) {
1535
+ if (is.integer(options.skipBlanks) && is.inRange(options.skipBlanks, -1, 65535)) {
1536
+ this.options.tileSkipBlanks = options.skipBlanks;
1537
+ } else {
1538
+ throw is.invalidParameterError('skipBlanks', 'integer between -1 and 255/65535', options.skipBlanks);
1539
+ }
1540
+ } else if (is.defined(options.layout) && options.layout === 'google') {
1541
+ this.options.tileSkipBlanks = 5;
1542
+ }
1543
+ // Center image in tile
1544
+ const centre = is.bool(options.center) ? options.center : options.centre;
1545
+ if (is.defined(centre)) {
1546
+ this._setBooleanOption('tileCentre', centre);
1547
+ }
1548
+ // @id attribute for IIIF layout
1549
+ if (is.defined(options.id)) {
1550
+ if (is.string(options.id)) {
1551
+ this.options.tileId = options.id;
1552
+ } else {
1553
+ throw is.invalidParameterError('id', 'string', options.id);
1554
+ }
1555
+ }
1556
+ // Basename for zip container
1557
+ if (is.defined(options.basename)) {
1558
+ if (is.string(options.basename)) {
1559
+ this.options.tileBasename = options.basename;
1560
+ } else {
1561
+ throw is.invalidParameterError('basename', 'string', options.basename);
1562
+ }
1563
+ }
1564
+ }
1565
+ // Format
1566
+ if (is.inArray(this.options.formatOut, ['jpeg', 'png', 'webp'])) {
1567
+ this.options.tileFormat = this.options.formatOut;
1568
+ } else if (this.options.formatOut !== 'input') {
1569
+ throw is.invalidParameterError('format', 'one of: jpeg, png, webp', this.options.formatOut);
1570
+ }
1571
+ return this._updateFormatOut('dz');
1572
+ }
1573
+
1574
+ /**
1575
+ * Set a timeout for processing, in seconds.
1576
+ * Use a value of zero to continue processing indefinitely, the default behaviour.
1577
+ *
1578
+ * The clock starts when libvips opens an input image for processing.
1579
+ * Time spent waiting for a libuv thread to become available is not included.
1580
+ *
1581
+ * @example
1582
+ * // Ensure processing takes no longer than 3 seconds
1583
+ * try {
1584
+ * const data = await sharp(input)
1585
+ * .blur(1000)
1586
+ * .timeout({ seconds: 3 })
1587
+ * .toBuffer();
1588
+ * } catch (err) {
1589
+ * if (err.message.includes('timeout')) { ... }
1590
+ * }
1591
+ *
1592
+ * @since 0.29.2
1593
+ *
1594
+ * @param {Object} options
1595
+ * @param {number} options.seconds - Number of seconds after which processing will be stopped
1596
+ * @returns {Sharp}
1597
+ */
1598
+ function timeout (options) {
1599
+ if (!is.plainObject(options)) {
1600
+ throw is.invalidParameterError('options', 'object', options);
1601
+ }
1602
+ if (is.integer(options.seconds) && is.inRange(options.seconds, 0, 3600)) {
1603
+ this.options.timeoutSeconds = options.seconds;
1604
+ } else {
1605
+ throw is.invalidParameterError('seconds', 'integer between 0 and 3600', options.seconds);
1606
+ }
1607
+ return this;
1608
+ }
1609
+
1610
+ /**
1611
+ * Update the output format unless options.force is false,
1612
+ * in which case revert to input format.
1613
+ * @private
1614
+ * @param {string} formatOut
1615
+ * @param {Object} [options]
1616
+ * @param {boolean} [options.force=true] - force output format, otherwise attempt to use input format
1617
+ * @returns {Sharp}
1618
+ */
1619
+ function _updateFormatOut (formatOut, options) {
1620
+ if (!(is.object(options) && options.force === false)) {
1621
+ this.options.formatOut = formatOut;
1622
+ }
1623
+ return this;
1624
+ }
1625
+
1626
+ /**
1627
+ * Update a boolean attribute of the this.options Object.
1628
+ * @private
1629
+ * @param {string} key
1630
+ * @param {boolean} val
1631
+ * @throws {Error} Invalid key
1632
+ */
1633
+ function _setBooleanOption (key, val) {
1634
+ if (is.bool(val)) {
1635
+ this.options[key] = val;
1636
+ } else {
1637
+ throw is.invalidParameterError(key, 'boolean', val);
1638
+ }
1639
+ }
1640
+
1641
+ /**
1642
+ * Called by a WriteableStream to notify us it is ready for data.
1643
+ * @private
1644
+ */
1645
+ function _read () {
1646
+ if (!this.options.streamOut) {
1647
+ this.options.streamOut = true;
1648
+ const stack = Error();
1649
+ this._pipeline(undefined, stack);
1650
+ }
1651
+ }
1652
+
1653
+ /**
1654
+ * Invoke the C++ image processing pipeline
1655
+ * Supports callback, stream and promise variants
1656
+ * @private
1657
+ */
1658
+ function _pipeline (callback, stack) {
1659
+ if (typeof callback === 'function') {
1660
+ // output=file/buffer
1661
+ if (this._isStreamInput()) {
1662
+ // output=file/buffer, input=stream
1663
+ this.on('finish', () => {
1664
+ this._flattenBufferIn();
1665
+ sharp.pipeline(this.options, (err, data, info) => {
1666
+ if (err) {
1667
+ callback(is.nativeError(err, stack));
1668
+ } else {
1669
+ callback(null, data, info);
1670
+ }
1671
+ });
1672
+ });
1673
+ } else {
1674
+ // output=file/buffer, input=file/buffer
1675
+ sharp.pipeline(this.options, (err, data, info) => {
1676
+ if (err) {
1677
+ callback(is.nativeError(err, stack));
1678
+ } else {
1679
+ callback(null, data, info);
1680
+ }
1681
+ });
1682
+ }
1683
+ return this;
1684
+ } else if (this.options.streamOut) {
1685
+ // output=stream
1686
+ if (this._isStreamInput()) {
1687
+ // output=stream, input=stream
1688
+ this.once('finish', () => {
1689
+ this._flattenBufferIn();
1690
+ sharp.pipeline(this.options, (err, data, info) => {
1691
+ if (err) {
1692
+ this.emit('error', is.nativeError(err, stack));
1693
+ } else {
1694
+ this.emit('info', info);
1695
+ this.push(data);
1696
+ }
1697
+ this.push(null);
1698
+ this.on('end', () => this.emit('close'));
1699
+ });
1700
+ });
1701
+ if (this.streamInFinished) {
1702
+ this.emit('finish');
1703
+ }
1704
+ } else {
1705
+ // output=stream, input=file/buffer
1706
+ sharp.pipeline(this.options, (err, data, info) => {
1707
+ if (err) {
1708
+ this.emit('error', is.nativeError(err, stack));
1709
+ } else {
1710
+ this.emit('info', info);
1711
+ this.push(data);
1712
+ }
1713
+ this.push(null);
1714
+ this.on('end', () => this.emit('close'));
1715
+ });
1716
+ }
1717
+ return this;
1718
+ } else {
1719
+ // output=promise
1720
+ if (this._isStreamInput()) {
1721
+ // output=promise, input=stream
1722
+ return new Promise((resolve, reject) => {
1723
+ this.once('finish', () => {
1724
+ this._flattenBufferIn();
1725
+ sharp.pipeline(this.options, (err, data, info) => {
1726
+ if (err) {
1727
+ reject(is.nativeError(err, stack));
1728
+ } else {
1729
+ if (this.options.resolveWithObject) {
1730
+ resolve({ data, info });
1731
+ } else {
1732
+ resolve(data);
1733
+ }
1734
+ }
1735
+ });
1736
+ });
1737
+ });
1738
+ } else {
1739
+ // output=promise, input=file/buffer
1740
+ return new Promise((resolve, reject) => {
1741
+ sharp.pipeline(this.options, (err, data, info) => {
1742
+ if (err) {
1743
+ reject(is.nativeError(err, stack));
1744
+ } else {
1745
+ if (this.options.resolveWithObject) {
1746
+ resolve({ data, info });
1747
+ } else {
1748
+ resolve(data);
1749
+ }
1750
+ }
1751
+ });
1752
+ });
1753
+ }
1754
+ }
1755
+ }
1756
+
1757
+ /**
1758
+ * Decorate the Sharp prototype with output-related functions.
1759
+ * @module Sharp
1760
+ * @private
1761
+ */
1762
+ export default (Sharp) => {
1763
+ Object.assign(Sharp.prototype, {
1764
+ // Public
1765
+ toFile,
1766
+ toBuffer,
1767
+ toUint8Array,
1768
+ withDensity,
1769
+ keepExif,
1770
+ withExif,
1771
+ withExifMerge,
1772
+ keepIccProfile,
1773
+ withIccProfile,
1774
+ keepGainMap,
1775
+ withGainMap,
1776
+ keepXmp,
1777
+ withXmp,
1778
+ keepMetadata,
1779
+ withMetadata,
1780
+ toFormat,
1781
+ jpeg,
1782
+ jp2,
1783
+ png,
1784
+ webp,
1785
+ tiff,
1786
+ avif,
1787
+ heif,
1788
+ jxl,
1789
+ gif,
1790
+ raw,
1791
+ tile,
1792
+ timeout,
1793
+ // Private
1794
+ _updateFormatOut,
1795
+ _setBooleanOption,
1796
+ _read,
1797
+ _pipeline
1798
+ });
1799
+ };