constructor.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. 'use strict';
  2. const util = require('util');
  3. const stream = require('stream');
  4. const is = require('./is');
  5. require('./libvips').hasVendoredLibvips();
  6. /* istanbul ignore next */
  7. try {
  8. require('../build/Release/sharp.node');
  9. } catch (err) {
  10. // Bail early if bindings aren't available
  11. const help = ['', 'Something went wrong installing the "sharp" module', '', err.message, ''];
  12. if (/NODE_MODULE_VERSION/.test(err.message)) {
  13. help.push('- Ensure the version of Node.js used at install time matches that used at runtime');
  14. } else if (/invalid ELF header/.test(err.message)) {
  15. help.push(`- Ensure "${process.platform}" is used at install time as well as runtime`);
  16. } else if (/dylib/.test(err.message) && /Incompatible library version/.test(err.message)) {
  17. help.push('- Run "brew update && brew upgrade vips"');
  18. } else if (/Cannot find module/.test(err.message)) {
  19. help.push('- Run "npm rebuild --verbose sharp" and look for errors');
  20. } else {
  21. help.push(
  22. '- Remove the "node_modules/sharp" directory then run',
  23. ' "npm install --ignore-scripts=false --verbose" and look for errors'
  24. );
  25. }
  26. help.push(
  27. '- Consult the installation documentation at https://sharp.pixelplumbing.com/install',
  28. '- Search for this error at https://github.com/lovell/sharp/issues', ''
  29. );
  30. const error = help.join('\n');
  31. throw new Error(error);
  32. }
  33. // Use NODE_DEBUG=sharp to enable libvips warnings
  34. const debuglog = util.debuglog('sharp');
  35. /**
  36. * @constructs sharp
  37. *
  38. * Constructor factory to create an instance of `sharp`, to which further methods are chained.
  39. *
  40. * JPEG, PNG, WebP or TIFF format image data can be streamed out from this object.
  41. * When using Stream based output, derived attributes are available from the `info` event.
  42. *
  43. * Implements the [stream.Duplex](http://nodejs.org/api/stream.html#stream_class_stream_duplex) class.
  44. *
  45. * @example
  46. * sharp('input.jpg')
  47. * .resize(300, 200)
  48. * .toFile('output.jpg', function(err) {
  49. * // output.jpg is a 300 pixels wide and 200 pixels high image
  50. * // containing a scaled and cropped version of input.jpg
  51. * });
  52. *
  53. * @example
  54. * // Read image data from readableStream,
  55. * // resize to 300 pixels wide,
  56. * // emit an 'info' event with calculated dimensions
  57. * // and finally write image data to writableStream
  58. * var transformer = sharp()
  59. * .resize(300)
  60. * .on('info', function(info) {
  61. * console.log('Image height is ' + info.height);
  62. * });
  63. * readableStream.pipe(transformer).pipe(writableStream);
  64. *
  65. * @example
  66. * // Create a blank 300x200 PNG image of semi-transluent red pixels
  67. * sharp({
  68. * create: {
  69. * width: 300,
  70. * height: 200,
  71. * channels: 4,
  72. * background: { r: 255, g: 0, b: 0, alpha: 0.5 }
  73. * }
  74. * })
  75. * .png()
  76. * .toBuffer()
  77. * .then( ... );
  78. *
  79. * @param {(Buffer|String)} [input] - if present, can be
  80. * a Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or
  81. * a String containing the filesystem path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file.
  82. * JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present.
  83. * @param {Object} [options] - if present, is an Object with optional attributes.
  84. * @param {Boolean} [options.failOnError=true] - by default halt processing and raise an error when loading invalid images.
  85. * Set this flag to `false` if you'd rather apply a "best effort" to decode images, even if the data is corrupt or invalid.
  86. * @param {Number|Boolean} [options.limitInputPixels=268402689] - Do not process input images where the number of pixels
  87. * (width x height) exceeds this limit. Assumes image dimensions contained in the input metadata can be trusted.
  88. * An integral Number of pixels, zero or false to remove limit, true to use default limit of 268402689 (0x3FFF x 0x3FFF).
  89. * @param {Boolean} [options.sequentialRead=false] - Set this to `true` to use sequential rather than random access where possible.
  90. * This can reduce memory usage and might improve performance on some systems.
  91. * @param {Number} [options.density=72] - number representing the DPI for vector images.
  92. * @param {Number} [options.pages=1] - number of pages to extract for multi-page input (GIF, TIFF, PDF), use -1 for all pages.
  93. * @param {Number} [options.page=0] - page number to start extracting from for multi-page input (GIF, TIFF, PDF), zero based.
  94. * @param {Object} [options.raw] - describes raw pixel input image data. See `raw()` for pixel ordering.
  95. * @param {Number} [options.raw.width]
  96. * @param {Number} [options.raw.height]
  97. * @param {Number} [options.raw.channels] - 1-4
  98. * @param {Object} [options.create] - describes a new image to be created.
  99. * @param {Number} [options.create.width]
  100. * @param {Number} [options.create.height]
  101. * @param {Number} [options.create.channels] - 3-4
  102. * @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.
  103. * @returns {Sharp}
  104. * @throws {Error} Invalid parameters
  105. */
  106. const Sharp = function (input, options) {
  107. if (arguments.length === 1 && !is.defined(input)) {
  108. throw new Error('Invalid input');
  109. }
  110. if (!(this instanceof Sharp)) {
  111. return new Sharp(input, options);
  112. }
  113. stream.Duplex.call(this);
  114. this.options = {
  115. // resize options
  116. topOffsetPre: -1,
  117. leftOffsetPre: -1,
  118. widthPre: -1,
  119. heightPre: -1,
  120. topOffsetPost: -1,
  121. leftOffsetPost: -1,
  122. widthPost: -1,
  123. heightPost: -1,
  124. width: -1,
  125. height: -1,
  126. canvas: 'crop',
  127. position: 0,
  128. resizeBackground: [0, 0, 0, 255],
  129. useExifOrientation: false,
  130. angle: 0,
  131. rotationAngle: 0,
  132. rotationBackground: [0, 0, 0, 255],
  133. rotateBeforePreExtract: false,
  134. flip: false,
  135. flop: false,
  136. extendTop: 0,
  137. extendBottom: 0,
  138. extendLeft: 0,
  139. extendRight: 0,
  140. extendBackground: [0, 0, 0, 255],
  141. withoutEnlargement: false,
  142. kernel: 'lanczos3',
  143. fastShrinkOnLoad: true,
  144. // operations
  145. tintA: 128,
  146. tintB: 128,
  147. flatten: false,
  148. flattenBackground: [0, 0, 0],
  149. negate: false,
  150. medianSize: 0,
  151. blurSigma: 0,
  152. sharpenSigma: 0,
  153. sharpenFlat: 1,
  154. sharpenJagged: 2,
  155. threshold: 0,
  156. thresholdGrayscale: true,
  157. trimThreshold: 0,
  158. gamma: 0,
  159. gammaOut: 0,
  160. greyscale: false,
  161. normalise: false,
  162. brightness: 1,
  163. saturation: 1,
  164. hue: 0,
  165. booleanBufferIn: null,
  166. booleanFileIn: '',
  167. joinChannelIn: [],
  168. extractChannel: -1,
  169. removeAlpha: false,
  170. ensureAlpha: false,
  171. colourspace: 'srgb',
  172. composite: [],
  173. // output
  174. fileOut: '',
  175. formatOut: 'input',
  176. streamOut: false,
  177. withMetadata: false,
  178. withMetadataOrientation: -1,
  179. resolveWithObject: false,
  180. // output format
  181. jpegQuality: 80,
  182. jpegProgressive: false,
  183. jpegChromaSubsampling: '4:2:0',
  184. jpegTrellisQuantisation: false,
  185. jpegOvershootDeringing: false,
  186. jpegOptimiseScans: false,
  187. jpegOptimiseCoding: true,
  188. jpegQuantisationTable: 0,
  189. pngProgressive: false,
  190. pngCompressionLevel: 9,
  191. pngAdaptiveFiltering: false,
  192. pngPalette: false,
  193. pngQuality: 100,
  194. pngColours: 256,
  195. pngDither: 1,
  196. webpQuality: 80,
  197. webpAlphaQuality: 100,
  198. webpLossless: false,
  199. webpNearLossless: false,
  200. webpSmartSubsample: false,
  201. webpReductionEffort: 4,
  202. tiffQuality: 80,
  203. tiffCompression: 'jpeg',
  204. tiffPredictor: 'horizontal',
  205. tiffPyramid: false,
  206. tiffSquash: false,
  207. tiffTile: false,
  208. tiffTileHeight: 256,
  209. tiffTileWidth: 256,
  210. tiffXres: 1.0,
  211. tiffYres: 1.0,
  212. heifQuality: 80,
  213. heifLossless: false,
  214. heifCompression: 'hevc',
  215. tileSize: 256,
  216. tileOverlap: 0,
  217. tileContainer: 'fs',
  218. tileLayout: 'dz',
  219. tileFormat: 'last',
  220. tileDepth: 'last',
  221. tileAngle: 0,
  222. tileSkipBlanks: -1,
  223. tileBackground: [255, 255, 255, 255],
  224. linearA: 1,
  225. linearB: 0,
  226. // Function to notify of libvips warnings
  227. debuglog: debuglog,
  228. // Function to notify of queue length changes
  229. queueListener: function (queueLength) {
  230. Sharp.queue.emit('change', queueLength);
  231. }
  232. };
  233. this.options.input = this._createInputDescriptor(input, options, { allowStream: true });
  234. return this;
  235. };
  236. util.inherits(Sharp, stream.Duplex);
  237. /**
  238. * Take a "snapshot" of the Sharp instance, returning a new instance.
  239. * Cloned instances inherit the input of their parent instance.
  240. * This allows multiple output Streams and therefore multiple processing pipelines to share a single input Stream.
  241. *
  242. * @example
  243. * const pipeline = sharp().rotate();
  244. * pipeline.clone().resize(800, 600).pipe(firstWritableStream);
  245. * pipeline.clone().extract({ left: 20, top: 20, width: 100, height: 100 }).pipe(secondWritableStream);
  246. * readableStream.pipe(pipeline);
  247. * // firstWritableStream receives auto-rotated, resized readableStream
  248. * // secondWritableStream receives auto-rotated, extracted region of readableStream
  249. *
  250. * @example
  251. * // Create a pipeline that will download an image, resize it and format it to different files
  252. * // Using Promises to know when the pipeline is complete
  253. * const fs = require("fs");
  254. * const got = require("got");
  255. * const sharpStream = sharp({
  256. * failOnError: false
  257. * });
  258. *
  259. * const promises = [];
  260. *
  261. * promises.push(
  262. * sharpStream
  263. * .clone()
  264. * .jpeg({ quality: 100 })
  265. * .toFile("originalFile.jpg")
  266. * );
  267. *
  268. * promises.push(
  269. * sharpStream
  270. * .clone()
  271. * .resize({ width: 500 })
  272. * .jpeg({ quality: 80 })
  273. * .toFile("optimized-500.jpg")
  274. * );
  275. *
  276. * promises.push(
  277. * sharpStream
  278. * .clone()
  279. * .resize({ width: 500 })
  280. * .webp({ quality: 80 })
  281. * .toFile("optimized-500.webp")
  282. * );
  283. *
  284. * // https://github.com/sindresorhus/got#gotstreamurl-options
  285. * got.stream("https://www.example.com/some-file.jpg").pipe(sharpStream);
  286. *
  287. * Promise.all(promises)
  288. * .then(res => { console.log("Done!", res); })
  289. * .catch(err => {
  290. * console.error("Error processing files, let's clean it up", err);
  291. * try {
  292. * fs.unlinkSync("originalFile.jpg");
  293. * fs.unlinkSync("optimized-500.jpg");
  294. * fs.unlinkSync("optimized-500.webp");
  295. * } catch (e) {}
  296. * });
  297. *
  298. * @returns {Sharp}
  299. */
  300. function clone () {
  301. // Clone existing options
  302. const clone = this.constructor.call();
  303. clone.options = Object.assign({}, this.options);
  304. // Pass 'finish' event to clone for Stream-based input
  305. if (this._isStreamInput()) {
  306. this.on('finish', () => {
  307. // Clone inherits input data
  308. this._flattenBufferIn();
  309. clone.options.bufferIn = this.options.bufferIn;
  310. clone.emit('finish');
  311. });
  312. }
  313. return clone;
  314. }
  315. Object.assign(Sharp.prototype, { clone });
  316. /**
  317. * Export constructor.
  318. * @private
  319. */
  320. module.exports = Sharp;