constructor.js 19 KB

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