constructor.js 16 KB

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