@revizly/sharp 0.33.2-revizly13

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,449 @@
1
+ // Copyright 2013 Lovell Fuller and others.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ 'use strict';
5
+
6
+ const util = require('node:util');
7
+ const stream = require('node:stream');
8
+ const is = require('./is');
9
+
10
+ require('./sharp');
11
+
12
+ // Use NODE_DEBUG=sharp to enable libvips warnings
13
+ const debuglog = util.debuglog('sharp');
14
+
15
+ /**
16
+ * Constructor factory to create an instance of `sharp`, to which further methods are chained.
17
+ *
18
+ * JPEG, PNG, WebP, GIF, AVIF or TIFF format image data can be streamed out from this object.
19
+ * When using Stream based output, derived attributes are available from the `info` event.
20
+ *
21
+ * Non-critical problems encountered during processing are emitted as `warning` events.
22
+ *
23
+ * Implements the [stream.Duplex](http://nodejs.org/api/stream.html#stream_class_stream_duplex) class.
24
+ *
25
+ * When loading more than one page/frame of an animated image,
26
+ * these are combined as a vertically-stacked "toilet roll" image
27
+ * where the overall height is the `pageHeight` multiplied by the number of `pages`.
28
+ *
29
+ * @constructs Sharp
30
+ *
31
+ * @emits Sharp#info
32
+ * @emits Sharp#warning
33
+ *
34
+ * @example
35
+ * sharp('input.jpg')
36
+ * .resize(300, 200)
37
+ * .toFile('output.jpg', function(err) {
38
+ * // output.jpg is a 300 pixels wide and 200 pixels high image
39
+ * // containing a scaled and cropped version of input.jpg
40
+ * });
41
+ *
42
+ * @example
43
+ * // Read image data from remote URL,
44
+ * // resize to 300 pixels wide,
45
+ * // emit an 'info' event with calculated dimensions
46
+ * // and finally write image data to writableStream
47
+ * const { body } = fetch('https://...');
48
+ * const readableStream = Readable.fromWeb(body);
49
+ * const transformer = sharp()
50
+ * .resize(300)
51
+ * .on('info', ({ height }) => {
52
+ * console.log(`Image height is ${height}`);
53
+ * });
54
+ * readableStream.pipe(transformer).pipe(writableStream);
55
+ *
56
+ * @example
57
+ * // Create a blank 300x200 PNG image of semi-translucent red pixels
58
+ * sharp({
59
+ * create: {
60
+ * width: 300,
61
+ * height: 200,
62
+ * channels: 4,
63
+ * background: { r: 255, g: 0, b: 0, alpha: 0.5 }
64
+ * }
65
+ * })
66
+ * .png()
67
+ * .toBuffer()
68
+ * .then( ... );
69
+ *
70
+ * @example
71
+ * // Convert an animated GIF to an animated WebP
72
+ * await sharp('in.gif', { animated: true }).toFile('out.webp');
73
+ *
74
+ * @example
75
+ * // Read a raw array of pixels and save it to a png
76
+ * const input = Uint8Array.from([255, 255, 255, 0, 0, 0]); // or Uint8ClampedArray
77
+ * const image = sharp(input, {
78
+ * // because the input does not contain its dimensions or how many channels it has
79
+ * // we need to specify it in the constructor options
80
+ * raw: {
81
+ * width: 2,
82
+ * height: 1,
83
+ * channels: 3
84
+ * }
85
+ * });
86
+ * await image.toFile('my-two-pixels.png');
87
+ *
88
+ * @example
89
+ * // Generate RGB Gaussian noise
90
+ * await sharp({
91
+ * create: {
92
+ * width: 300,
93
+ * height: 200,
94
+ * channels: 3,
95
+ * noise: {
96
+ * type: 'gaussian',
97
+ * mean: 128,
98
+ * sigma: 30
99
+ * }
100
+ * }
101
+ * }).toFile('noise.png');
102
+ *
103
+ * @example
104
+ * // Generate an image from text
105
+ * await sharp({
106
+ * text: {
107
+ * text: 'Hello, world!',
108
+ * width: 400, // max width
109
+ * height: 300 // max height
110
+ * }
111
+ * }).toFile('text_bw.png');
112
+ *
113
+ * @example
114
+ * // Generate an rgba image from text using pango markup and font
115
+ * await sharp({
116
+ * text: {
117
+ * text: '<span foreground="red">Red!</span><span background="cyan">blue</span>',
118
+ * font: 'sans',
119
+ * rgba: true,
120
+ * dpi: 300
121
+ * }
122
+ * }).toFile('text_rgba.png');
123
+ *
124
+ * @param {(Buffer|ArrayBuffer|Uint8Array|Uint8ClampedArray|Int8Array|Uint16Array|Int16Array|Uint32Array|Int32Array|Float32Array|Float64Array|string)} [input] - if present, can be
125
+ * a Buffer / ArrayBuffer / Uint8Array / Uint8ClampedArray containing JPEG, PNG, WebP, AVIF, GIF, SVG or TIFF image data, or
126
+ * a TypedArray containing raw pixel image data, or
127
+ * a String containing the filesystem path to an JPEG, PNG, WebP, AVIF, GIF, SVG or TIFF image file.
128
+ * JPEG, PNG, WebP, AVIF, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present.
129
+ * @param {Object} [options] - if present, is an Object with optional attributes.
130
+ * @param {string} [options.failOn='warning'] - When to abort processing of invalid pixel data, one of (in order of sensitivity, least to most): 'none', 'truncated', 'error', 'warning'. Higher levels imply lower levels. Invalid metadata will always abort.
131
+ * @param {number|boolean} [options.limitInputPixels=268402689] - Do not process input images where the number of pixels
132
+ * (width x height) exceeds this limit. Assumes image dimensions contained in the input metadata can be trusted.
133
+ * An integral Number of pixels, zero or false to remove limit, true to use default limit of 268402689 (0x3FFF x 0x3FFF).
134
+ * @param {boolean} [options.unlimited=false] - Set this to `true` to remove safety features that help prevent memory exhaustion (JPEG, PNG, SVG, HEIF).
135
+ * @param {boolean} [options.sequentialRead=true] - Set this to `false` to use random access rather than sequential read. Some operations will do this automatically.
136
+ * @param {number} [options.density=72] - number representing the DPI for vector images in the range 1 to 100000.
137
+ * @param {number} [options.ignoreIcc=false] - should the embedded ICC profile, if any, be ignored.
138
+ * @param {number} [options.pages=1] - Number of pages to extract for multi-page input (GIF, WebP, TIFF), use -1 for all pages.
139
+ * @param {number} [options.page=0] - Page number to start extracting from for multi-page input (GIF, WebP, TIFF), zero based.
140
+ * @param {number} [options.subifd=-1] - subIFD (Sub Image File Directory) to extract for OME-TIFF, defaults to main image.
141
+ * @param {number} [options.level=0] - level to extract from a multi-level input (OpenSlide), zero based.
142
+ * @param {boolean} [options.animated=false] - Set to `true` to read all frames/pages of an animated image (GIF, WebP, TIFF), equivalent of setting `pages` to `-1`.
143
+ * @param {Object} [options.raw] - describes raw pixel input image data. See `raw()` for pixel ordering.
144
+ * @param {number} [options.raw.width] - integral number of pixels wide.
145
+ * @param {number} [options.raw.height] - integral number of pixels high.
146
+ * @param {number} [options.raw.channels] - integral number of channels, between 1 and 4.
147
+ * @param {boolean} [options.raw.premultiplied] - specifies that the raw input has already been premultiplied, set to `true`
148
+ * to avoid sharp premultiplying the image. (optional, default `false`)
149
+ * @param {Object} [options.create] - describes a new image to be created.
150
+ * @param {number} [options.create.width] - integral number of pixels wide.
151
+ * @param {number} [options.create.height] - integral number of pixels high.
152
+ * @param {number} [options.create.channels] - integral number of channels, either 3 (RGB) or 4 (RGBA).
153
+ * @param {string|Object} [options.create.background] - parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
154
+ * @param {Object} [options.create.noise] - describes a noise to be created.
155
+ * @param {string} [options.create.noise.type] - type of generated noise, currently only `gaussian` is supported.
156
+ * @param {number} [options.create.noise.mean] - mean of pixels in generated noise.
157
+ * @param {number} [options.create.noise.sigma] - standard deviation of pixels in generated noise.
158
+ * @param {Object} [options.text] - describes a new text image to be created.
159
+ * @param {string} [options.text.text] - text to render as a UTF-8 string. It can contain Pango markup, for example `<i>Le</i>Monde`.
160
+ * @param {string} [options.text.font] - font name to render with.
161
+ * @param {string} [options.text.fontfile] - absolute filesystem path to a font file that can be used by `font`.
162
+ * @param {number} [options.text.width=0] - Integral number of pixels to word-wrap at. Lines of text wider than this will be broken at word boundaries.
163
+ * @param {number} [options.text.height=0] - Maximum integral number of pixels high. When defined, `dpi` will be ignored and the text will automatically fit the pixel resolution defined by `width` and `height`. Will be ignored if `width` is not specified or set to 0.
164
+ * @param {string} [options.text.align='left'] - Alignment style for multi-line text (`'left'`, `'centre'`, `'center'`, `'right'`).
165
+ * @param {boolean} [options.text.justify=false] - set this to true to apply justification to the text.
166
+ * @param {number} [options.text.dpi=72] - the resolution (size) at which to render the text. Does not take effect if `height` is specified.
167
+ * @param {boolean} [options.text.rgba=false] - set this to true to enable RGBA output. This is useful for colour emoji rendering, or support for pango markup features like `<span foreground="red">Red!</span>`.
168
+ * @param {number} [options.text.spacing=0] - text line height in points. Will use the font line height if none is specified.
169
+ * @param {string} [options.text.wrap='word'] - word wrapping style when width is provided, one of: 'word', 'char', 'word-char' (prefer word, fallback to char) or 'none'.
170
+ * @returns {Sharp}
171
+ * @throws {Error} Invalid parameters
172
+ */
173
+ const Sharp = function (input, options) {
174
+ if (arguments.length === 1 && !is.defined(input)) {
175
+ throw new Error('Invalid input');
176
+ }
177
+ if (!(this instanceof Sharp)) {
178
+ return new Sharp(input, options);
179
+ }
180
+ stream.Duplex.call(this);
181
+ this.options = {
182
+ // resize options
183
+ topOffsetPre: -1,
184
+ leftOffsetPre: -1,
185
+ widthPre: -1,
186
+ heightPre: -1,
187
+ topOffsetPost: -1,
188
+ leftOffsetPost: -1,
189
+ widthPost: -1,
190
+ heightPost: -1,
191
+ width: -1,
192
+ height: -1,
193
+ canvas: 'crop',
194
+ position: 0,
195
+ resizeBackground: [0, 0, 0, 255],
196
+ useExifOrientation: false,
197
+ angle: 0,
198
+ rotationAngle: 0,
199
+ rotationBackground: [0, 0, 0, 255],
200
+ rotateBeforePreExtract: false,
201
+ flip: false,
202
+ flop: false,
203
+ extendTop: 0,
204
+ extendBottom: 0,
205
+ extendLeft: 0,
206
+ extendRight: 0,
207
+ extendBackground: [0, 0, 0, 255],
208
+ extendWith: 'background',
209
+ withoutEnlargement: false,
210
+ withoutReduction: false,
211
+ affineMatrix: [],
212
+ affineBackground: [0, 0, 0, 255],
213
+ affineIdx: 0,
214
+ affineIdy: 0,
215
+ affineOdx: 0,
216
+ affineOdy: 0,
217
+ affineInterpolator: this.constructor.interpolators.bilinear,
218
+ kernel: 'lanczos3',
219
+ fastShrinkOnLoad: true,
220
+ // operations
221
+ tint: [-1, 0, 0, 0],
222
+ flatten: false,
223
+ flattenBackground: [0, 0, 0],
224
+ unflatten: false,
225
+ negate: false,
226
+ negateAlpha: true,
227
+ medianSize: 0,
228
+ blurSigma: 0,
229
+ sharpenSigma: 0,
230
+ sharpenM1: 1,
231
+ sharpenM2: 2,
232
+ sharpenX1: 2,
233
+ sharpenY2: 10,
234
+ sharpenY3: 20,
235
+ threshold: 0,
236
+ thresholdGrayscale: true,
237
+ trimBackground: [],
238
+ trimThreshold: -1,
239
+ trimLineArt: false,
240
+ gamma: 0,
241
+ gammaOut: 0,
242
+ greyscale: false,
243
+ normalise: false,
244
+ normaliseLower: 1,
245
+ normaliseUpper: 99,
246
+ claheWidth: 0,
247
+ claheHeight: 0,
248
+ claheMaxSlope: 3,
249
+ brightness: 1,
250
+ saturation: 1,
251
+ hue: 0,
252
+ lightness: 0,
253
+ booleanBufferIn: null,
254
+ booleanFileIn: '',
255
+ joinChannelIn: [],
256
+ extractChannel: -1,
257
+ removeAlpha: false,
258
+ ensureAlpha: -1,
259
+ colourspace: 'srgb',
260
+ colourspacePipeline: 'last',
261
+ composite: [],
262
+ // output
263
+ fileOut: '',
264
+ formatOut: 'input',
265
+ streamOut: false,
266
+ keepMetadata: 0,
267
+ withMetadataOrientation: -1,
268
+ withMetadataDensity: 0,
269
+ withIccProfile: '',
270
+ withExif: {},
271
+ withExifMerge: true,
272
+ resolveWithObject: false,
273
+ // output format
274
+ jpegQuality: 80,
275
+ jpegProgressive: false,
276
+ jpegChromaSubsampling: '4:2:0',
277
+ jpegTrellisQuantisation: false,
278
+ jpegOvershootDeringing: false,
279
+ jpegOptimiseScans: false,
280
+ jpegOptimiseCoding: true,
281
+ jpegQuantisationTable: 0,
282
+ pngProgressive: false,
283
+ pngCompressionLevel: 6,
284
+ pngAdaptiveFiltering: false,
285
+ pngPalette: false,
286
+ pngQuality: 100,
287
+ pngEffort: 7,
288
+ pngBitdepth: 8,
289
+ pngDither: 1,
290
+ jp2Quality: 80,
291
+ jp2TileHeight: 512,
292
+ jp2TileWidth: 512,
293
+ jp2Lossless: false,
294
+ jp2ChromaSubsampling: '4:4:4',
295
+ webpQuality: 80,
296
+ webpAlphaQuality: 100,
297
+ webpLossless: false,
298
+ webpNearLossless: false,
299
+ webpSmartSubsample: false,
300
+ webpPreset: 'default',
301
+ webpEffort: 4,
302
+ webpMinSize: false,
303
+ webpMixed: false,
304
+ gifBitdepth: 8,
305
+ gifEffort: 7,
306
+ gifDither: 1,
307
+ gifInterFrameMaxError: 0,
308
+ gifInterPaletteMaxError: 3,
309
+ gifReuse: true,
310
+ gifProgressive: false,
311
+ tiffQuality: 80,
312
+ tiffCompression: 'jpeg',
313
+ tiffPredictor: 'horizontal',
314
+ tiffPyramid: false,
315
+ tiffMiniswhite: false,
316
+ tiffBitdepth: 8,
317
+ tiffTile: false,
318
+ tiffTileHeight: 256,
319
+ tiffTileWidth: 256,
320
+ tiffXres: 1.0,
321
+ tiffYres: 1.0,
322
+ tiffResolutionUnit: 'inch',
323
+ heifQuality: 50,
324
+ heifLossless: false,
325
+ heifCompression: 'av1',
326
+ heifEffort: 4,
327
+ heifChromaSubsampling: '4:4:4',
328
+ jxlDistance: 1,
329
+ jxlDecodingTier: 0,
330
+ jxlEffort: 7,
331
+ jxlLossless: false,
332
+ rawDepth: 'uchar',
333
+ tileSize: 256,
334
+ tileOverlap: 0,
335
+ tileContainer: 'fs',
336
+ tileLayout: 'dz',
337
+ tileFormat: 'last',
338
+ tileDepth: 'last',
339
+ tileAngle: 0,
340
+ tileSkipBlanks: -1,
341
+ tileBackground: [255, 255, 255, 255],
342
+ tileCentre: false,
343
+ tileId: 'https://example.com/iiif',
344
+ tileBasename: '',
345
+ timeoutSeconds: 0,
346
+ linearA: [],
347
+ linearB: [],
348
+ // Function to notify of libvips warnings
349
+ debuglog: warning => {
350
+ this.emit('warning', warning);
351
+ debuglog(warning);
352
+ },
353
+ // Function to notify of queue length changes
354
+ queueListener: function (queueLength) {
355
+ Sharp.queue.emit('change', queueLength);
356
+ }
357
+ };
358
+ this.options.input = this._createInputDescriptor(input, options, { allowStream: true });
359
+ return this;
360
+ };
361
+ Object.setPrototypeOf(Sharp.prototype, stream.Duplex.prototype);
362
+ Object.setPrototypeOf(Sharp, stream.Duplex);
363
+
364
+ /**
365
+ * Take a "snapshot" of the Sharp instance, returning a new instance.
366
+ * Cloned instances inherit the input of their parent instance.
367
+ * This allows multiple output Streams and therefore multiple processing pipelines to share a single input Stream.
368
+ *
369
+ * @example
370
+ * const pipeline = sharp().rotate();
371
+ * pipeline.clone().resize(800, 600).pipe(firstWritableStream);
372
+ * pipeline.clone().extract({ left: 20, top: 20, width: 100, height: 100 }).pipe(secondWritableStream);
373
+ * readableStream.pipe(pipeline);
374
+ * // firstWritableStream receives auto-rotated, resized readableStream
375
+ * // secondWritableStream receives auto-rotated, extracted region of readableStream
376
+ *
377
+ * @example
378
+ * // Create a pipeline that will download an image, resize it and format it to different files
379
+ * // Using Promises to know when the pipeline is complete
380
+ * const fs = require("fs");
381
+ * const got = require("got");
382
+ * const sharpStream = sharp({ failOn: 'none' });
383
+ *
384
+ * const promises = [];
385
+ *
386
+ * promises.push(
387
+ * sharpStream
388
+ * .clone()
389
+ * .jpeg({ quality: 100 })
390
+ * .toFile("originalFile.jpg")
391
+ * );
392
+ *
393
+ * promises.push(
394
+ * sharpStream
395
+ * .clone()
396
+ * .resize({ width: 500 })
397
+ * .jpeg({ quality: 80 })
398
+ * .toFile("optimized-500.jpg")
399
+ * );
400
+ *
401
+ * promises.push(
402
+ * sharpStream
403
+ * .clone()
404
+ * .resize({ width: 500 })
405
+ * .webp({ quality: 80 })
406
+ * .toFile("optimized-500.webp")
407
+ * );
408
+ *
409
+ * // https://github.com/sindresorhus/got/blob/main/documentation/3-streams.md
410
+ * got.stream("https://www.example.com/some-file.jpg").pipe(sharpStream);
411
+ *
412
+ * Promise.all(promises)
413
+ * .then(res => { console.log("Done!", res); })
414
+ * .catch(err => {
415
+ * console.error("Error processing files, let's clean it up", err);
416
+ * try {
417
+ * fs.unlinkSync("originalFile.jpg");
418
+ * fs.unlinkSync("optimized-500.jpg");
419
+ * fs.unlinkSync("optimized-500.webp");
420
+ * } catch (e) {}
421
+ * });
422
+ *
423
+ * @returns {Sharp}
424
+ */
425
+ function clone () {
426
+ // Clone existing options
427
+ const clone = this.constructor.call();
428
+ const { debuglog, queueListener, ...options } = this.options;
429
+ clone.options = structuredClone(options);
430
+ clone.options.debuglog = debuglog;
431
+ clone.options.queueListener = queueListener;
432
+ // Pass 'finish' event to clone for Stream-based input
433
+ if (this._isStreamInput()) {
434
+ this.on('finish', () => {
435
+ // Clone inherits input data
436
+ this._flattenBufferIn();
437
+ clone.options.input.buffer = this.options.input.buffer;
438
+ clone.emit('finish');
439
+ });
440
+ }
441
+ return clone;
442
+ }
443
+ Object.assign(Sharp.prototype, { clone });
444
+
445
+ /**
446
+ * Export constructor.
447
+ * @private
448
+ */
449
+ module.exports = Sharp;