output.js 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809
  1. 'use strict';
  2. const is = require('./is');
  3. const sharp = require('../build/Release/sharp.node');
  4. const formats = new Map([
  5. ['heic', 'heif'],
  6. ['heif', 'heif'],
  7. ['jpeg', 'jpeg'],
  8. ['jpg', 'jpeg'],
  9. ['png', 'png'],
  10. ['raw', 'raw'],
  11. ['tiff', 'tiff'],
  12. ['webp', 'webp']
  13. ]);
  14. /**
  15. * Write output image data to a file.
  16. *
  17. * If an explicit output format is not selected, it will be inferred from the extension,
  18. * with JPEG, PNG, WebP, TIFF, DZI, and libvips' V format supported.
  19. * Note that raw pixel data is only supported for buffer output.
  20. *
  21. * By default all metadata will be removed, which includes EXIF-based orientation.
  22. * See {@link withMetadata} for control over this.
  23. *
  24. * A `Promise` is returned when `callback` is not provided.
  25. *
  26. * @example
  27. * sharp(input)
  28. * .toFile('output.png', (err, info) => { ... });
  29. *
  30. * @example
  31. * sharp(input)
  32. * .toFile('output.png')
  33. * .then(info => { ... })
  34. * .catch(err => { ... });
  35. *
  36. * @param {string} fileOut - the path to write the image data to.
  37. * @param {Function} [callback] - called on completion with two arguments `(err, info)`.
  38. * `info` contains the output image `format`, `size` (bytes), `width`, `height`,
  39. * `channels` and `premultiplied` (indicating if premultiplication was used).
  40. * When using a crop strategy also contains `cropOffsetLeft` and `cropOffsetTop`.
  41. * @returns {Promise<Object>} - when no callback is provided
  42. * @throws {Error} Invalid parameters
  43. */
  44. function toFile (fileOut, callback) {
  45. if (!fileOut || fileOut.length === 0) {
  46. const errOutputInvalid = new Error('Missing output file path');
  47. if (is.fn(callback)) {
  48. callback(errOutputInvalid);
  49. } else {
  50. return Promise.reject(errOutputInvalid);
  51. }
  52. } else {
  53. if (this.options.input.file === fileOut) {
  54. const errOutputIsInput = new Error('Cannot use same file for input and output');
  55. if (is.fn(callback)) {
  56. callback(errOutputIsInput);
  57. } else {
  58. return Promise.reject(errOutputIsInput);
  59. }
  60. } else {
  61. this.options.fileOut = fileOut;
  62. return this._pipeline(callback);
  63. }
  64. }
  65. return this;
  66. }
  67. /**
  68. * Write output to a Buffer.
  69. * JPEG, PNG, WebP, TIFF and RAW output are supported.
  70. *
  71. * If no explicit format is set, the output format will match the input image, except GIF and SVG input which become PNG output.
  72. *
  73. * By default all metadata will be removed, which includes EXIF-based orientation.
  74. * See {@link withMetadata} for control over this.
  75. *
  76. * `callback`, if present, gets three arguments `(err, data, info)` where:
  77. * - `err` is an error, if any.
  78. * - `data` is the output image data.
  79. * - `info` contains the output image `format`, `size` (bytes), `width`, `height`,
  80. * `channels` and `premultiplied` (indicating if premultiplication was used).
  81. * When using a crop strategy also contains `cropOffsetLeft` and `cropOffsetTop`.
  82. *
  83. * A `Promise` is returned when `callback` is not provided.
  84. *
  85. * @example
  86. * sharp(input)
  87. * .toBuffer((err, data, info) => { ... });
  88. *
  89. * @example
  90. * sharp(input)
  91. * .toBuffer()
  92. * .then(data => { ... })
  93. * .catch(err => { ... });
  94. *
  95. * @example
  96. * sharp(input)
  97. * .toBuffer({ resolveWithObject: true })
  98. * .then(({ data, info }) => { ... })
  99. * .catch(err => { ... });
  100. *
  101. * @param {Object} [options]
  102. * @param {boolean} [options.resolveWithObject] Resolve the Promise with an Object containing `data` and `info` properties instead of resolving only with `data`.
  103. * @param {Function} [callback]
  104. * @returns {Promise<Buffer>} - when no callback is provided
  105. */
  106. function toBuffer (options, callback) {
  107. if (is.object(options)) {
  108. this._setBooleanOption('resolveWithObject', options.resolveWithObject);
  109. } else if (this.options.resolveWithObject) {
  110. this.options.resolveWithObject = false;
  111. }
  112. return this._pipeline(is.fn(options) ? options : callback);
  113. }
  114. /**
  115. * Include all metadata (EXIF, XMP, IPTC) from the input image in the output image.
  116. * This will also convert to and add a web-friendly sRGB ICC profile.
  117. *
  118. * The default behaviour, when `withMetadata` is not used, is to convert to the device-independent
  119. * sRGB colour space and strip all metadata, including the removal of any ICC profile.
  120. *
  121. * @example
  122. * sharp('input.jpg')
  123. * .withMetadata()
  124. * .toFile('output-with-metadata.jpg')
  125. * .then(info => { ... });
  126. *
  127. * @param {Object} [options]
  128. * @param {number} [options.orientation] value between 1 and 8, used to update the EXIF `Orientation` tag.
  129. * @returns {Sharp}
  130. * @throws {Error} Invalid parameters
  131. */
  132. function withMetadata (options) {
  133. this.options.withMetadata = is.bool(options) ? options : true;
  134. if (is.object(options)) {
  135. if (is.defined(options.orientation)) {
  136. if (is.integer(options.orientation) && is.inRange(options.orientation, 1, 8)) {
  137. this.options.withMetadataOrientation = options.orientation;
  138. } else {
  139. throw is.invalidParameterError('orientation', 'integer between 1 and 8', options.orientation);
  140. }
  141. }
  142. }
  143. return this;
  144. }
  145. /**
  146. * Force output to a given format.
  147. *
  148. * @example
  149. * // Convert any input to PNG output
  150. * const data = await sharp(input)
  151. * .toFormat('png')
  152. * .toBuffer();
  153. *
  154. * @param {(string|Object)} format - as a string or an Object with an 'id' attribute
  155. * @param {Object} options - output options
  156. * @returns {Sharp}
  157. * @throws {Error} unsupported format or options
  158. */
  159. function toFormat (format, options) {
  160. const actualFormat = formats.get(is.object(format) && is.string(format.id) ? format.id : format);
  161. if (!actualFormat) {
  162. throw is.invalidParameterError('format', `one of: ${[...formats.keys()].join(', ')}`, format);
  163. }
  164. return this[actualFormat](options);
  165. }
  166. /**
  167. * Use these JPEG options for output image.
  168. *
  169. * @example
  170. * // Convert any input to very high quality JPEG output
  171. * const data = await sharp(input)
  172. * .jpeg({
  173. * quality: 100,
  174. * chromaSubsampling: '4:4:4'
  175. * })
  176. * .toBuffer();
  177. *
  178. * @param {Object} [options] - output options
  179. * @param {number} [options.quality=80] - quality, integer 1-100
  180. * @param {boolean} [options.progressive=false] - use progressive (interlace) scan
  181. * @param {string} [options.chromaSubsampling='4:2:0'] - for quality < 90, set to '4:4:4' to prevent chroma subsampling otherwise defaults to '4:2:0' (use chroma subsampling); for quality >= 90 chroma is never subsampled
  182. * @param {boolean} [options.trellisQuantisation=false] - apply trellis quantisation, requires libvips compiled with support for mozjpeg
  183. * @param {boolean} [options.overshootDeringing=false] - apply overshoot deringing, requires libvips compiled with support for mozjpeg
  184. * @param {boolean} [options.optimiseScans=false] - optimise progressive scans, forces progressive, requires libvips compiled with support for mozjpeg
  185. * @param {boolean} [options.optimizeScans=false] - alternative spelling of optimiseScans
  186. * @param {boolean} [options.optimiseCoding=true] - optimise Huffman coding tables
  187. * @param {boolean} [options.optimizeCoding=true] - alternative spelling of optimiseCoding
  188. * @param {number} [options.quantisationTable=0] - quantization table to use, integer 0-8, requires libvips compiled with support for mozjpeg
  189. * @param {number} [options.quantizationTable=0] - alternative spelling of quantisationTable
  190. * @param {boolean} [options.force=true] - force JPEG output, otherwise attempt to use input format
  191. * @returns {Sharp}
  192. * @throws {Error} Invalid options
  193. */
  194. function jpeg (options) {
  195. if (is.object(options)) {
  196. if (is.defined(options.quality)) {
  197. if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
  198. this.options.jpegQuality = options.quality;
  199. } else {
  200. throw is.invalidParameterError('quality', 'integer between 1 and 100', options.quality);
  201. }
  202. }
  203. if (is.defined(options.progressive)) {
  204. this._setBooleanOption('jpegProgressive', options.progressive);
  205. }
  206. if (is.defined(options.chromaSubsampling)) {
  207. if (is.string(options.chromaSubsampling) && is.inArray(options.chromaSubsampling, ['4:2:0', '4:4:4'])) {
  208. this.options.jpegChromaSubsampling = options.chromaSubsampling;
  209. } else {
  210. throw is.invalidParameterError('chromaSubsampling', 'one of: 4:2:0, 4:4:4', options.chromaSubsampling);
  211. }
  212. }
  213. const trellisQuantisation = is.bool(options.trellisQuantization) ? options.trellisQuantization : options.trellisQuantisation;
  214. if (is.defined(trellisQuantisation)) {
  215. this._setBooleanOption('jpegTrellisQuantisation', trellisQuantisation);
  216. }
  217. if (is.defined(options.overshootDeringing)) {
  218. this._setBooleanOption('jpegOvershootDeringing', options.overshootDeringing);
  219. }
  220. const optimiseScans = is.bool(options.optimizeScans) ? options.optimizeScans : options.optimiseScans;
  221. if (is.defined(optimiseScans)) {
  222. this._setBooleanOption('jpegOptimiseScans', optimiseScans);
  223. if (optimiseScans) {
  224. this.options.jpegProgressive = true;
  225. }
  226. }
  227. const optimiseCoding = is.bool(options.optimizeCoding) ? options.optimizeCoding : options.optimiseCoding;
  228. if (is.defined(optimiseCoding)) {
  229. this._setBooleanOption('jpegOptimiseCoding', optimiseCoding);
  230. }
  231. const quantisationTable = is.number(options.quantizationTable) ? options.quantizationTable : options.quantisationTable;
  232. if (is.defined(quantisationTable)) {
  233. if (is.integer(quantisationTable) && is.inRange(quantisationTable, 0, 8)) {
  234. this.options.jpegQuantisationTable = quantisationTable;
  235. } else {
  236. throw is.invalidParameterError('quantisationTable', 'integer between 0 and 8', quantisationTable);
  237. }
  238. }
  239. }
  240. return this._updateFormatOut('jpeg', options);
  241. }
  242. /**
  243. * Use these PNG options for output image.
  244. *
  245. * PNG output is always full colour at 8 or 16 bits per pixel.
  246. * Indexed PNG input at 1, 2 or 4 bits per pixel is converted to 8 bits per pixel.
  247. *
  248. * @example
  249. * // Convert any input to PNG output
  250. * const data = await sharp(input)
  251. * .png()
  252. * .toBuffer();
  253. *
  254. * @param {Object} [options]
  255. * @param {boolean} [options.progressive=false] - use progressive (interlace) scan
  256. * @param {number} [options.compressionLevel=9] - zlib compression level, 0-9
  257. * @param {boolean} [options.adaptiveFiltering=false] - use adaptive row filtering
  258. * @param {boolean} [options.palette=false] - quantise to a palette-based image with alpha transparency support, requires libvips compiled with support for libimagequant
  259. * @param {number} [options.quality=100] - use the lowest number of colours needed to achieve given quality, requires libvips compiled with support for libimagequant
  260. * @param {number} [options.colours=256] - maximum number of palette entries, requires libvips compiled with support for libimagequant
  261. * @param {number} [options.colors=256] - alternative spelling of `options.colours`, requires libvips compiled with support for libimagequant
  262. * @param {number} [options.dither=1.0] - level of Floyd-Steinberg error diffusion, requires libvips compiled with support for libimagequant
  263. * @param {boolean} [options.force=true] - force PNG output, otherwise attempt to use input format
  264. * @returns {Sharp}
  265. * @throws {Error} Invalid options
  266. */
  267. function png (options) {
  268. if (is.object(options)) {
  269. if (is.defined(options.progressive)) {
  270. this._setBooleanOption('pngProgressive', options.progressive);
  271. }
  272. if (is.defined(options.compressionLevel)) {
  273. if (is.integer(options.compressionLevel) && is.inRange(options.compressionLevel, 0, 9)) {
  274. this.options.pngCompressionLevel = options.compressionLevel;
  275. } else {
  276. throw is.invalidParameterError('compressionLevel', 'integer between 0 and 9', options.compressionLevel);
  277. }
  278. }
  279. if (is.defined(options.adaptiveFiltering)) {
  280. this._setBooleanOption('pngAdaptiveFiltering', options.adaptiveFiltering);
  281. }
  282. if (is.defined(options.palette)) {
  283. this._setBooleanOption('pngPalette', options.palette);
  284. if (this.options.pngPalette) {
  285. if (is.defined(options.quality)) {
  286. if (is.integer(options.quality) && is.inRange(options.quality, 0, 100)) {
  287. this.options.pngQuality = options.quality;
  288. } else {
  289. throw is.invalidParameterError('quality', 'integer between 0 and 100', options.quality);
  290. }
  291. }
  292. const colours = options.colours || options.colors;
  293. if (is.defined(colours)) {
  294. if (is.integer(colours) && is.inRange(colours, 2, 256)) {
  295. this.options.pngColours = colours;
  296. } else {
  297. throw is.invalidParameterError('colours', 'integer between 2 and 256', colours);
  298. }
  299. }
  300. if (is.defined(options.dither)) {
  301. if (is.number(options.dither) && is.inRange(options.dither, 0, 1)) {
  302. this.options.pngDither = options.dither;
  303. } else {
  304. throw is.invalidParameterError('dither', 'number between 0.0 and 1.0', options.dither);
  305. }
  306. }
  307. }
  308. }
  309. }
  310. return this._updateFormatOut('png', options);
  311. }
  312. /**
  313. * Use these WebP options for output image.
  314. *
  315. * @example
  316. * // Convert any input to lossless WebP output
  317. * const data = await sharp(input)
  318. * .webp({ lossless: true })
  319. * .toBuffer();
  320. *
  321. * @param {Object} [options] - output options
  322. * @param {number} [options.quality=80] - quality, integer 1-100
  323. * @param {number} [options.alphaQuality=100] - quality of alpha layer, integer 0-100
  324. * @param {boolean} [options.lossless=false] - use lossless compression mode
  325. * @param {boolean} [options.nearLossless=false] - use near_lossless compression mode
  326. * @param {boolean} [options.smartSubsample=false] - use high quality chroma subsampling
  327. * @param {number} [options.reductionEffort=4] - level of CPU effort to reduce file size, integer 0-6
  328. * @param {boolean} [options.force=true] - force WebP output, otherwise attempt to use input format
  329. * @returns {Sharp}
  330. * @throws {Error} Invalid options
  331. */
  332. function webp (options) {
  333. if (is.object(options) && is.defined(options.quality)) {
  334. if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
  335. this.options.webpQuality = options.quality;
  336. } else {
  337. throw is.invalidParameterError('quality', 'integer between 1 and 100', options.quality);
  338. }
  339. }
  340. if (is.object(options) && is.defined(options.alphaQuality)) {
  341. if (is.integer(options.alphaQuality) && is.inRange(options.alphaQuality, 0, 100)) {
  342. this.options.webpAlphaQuality = options.alphaQuality;
  343. } else {
  344. throw is.invalidParameterError('alphaQuality', 'integer between 0 and 100', options.alphaQuality);
  345. }
  346. }
  347. if (is.object(options) && is.defined(options.lossless)) {
  348. this._setBooleanOption('webpLossless', options.lossless);
  349. }
  350. if (is.object(options) && is.defined(options.nearLossless)) {
  351. this._setBooleanOption('webpNearLossless', options.nearLossless);
  352. }
  353. if (is.object(options) && is.defined(options.smartSubsample)) {
  354. this._setBooleanOption('webpSmartSubsample', options.smartSubsample);
  355. }
  356. if (is.object(options) && is.defined(options.reductionEffort)) {
  357. if (is.integer(options.reductionEffort) && is.inRange(options.reductionEffort, 0, 6)) {
  358. this.options.webpReductionEffort = options.reductionEffort;
  359. } else {
  360. throw is.invalidParameterError('reductionEffort', 'integer between 0 and 6', options.reductionEffort);
  361. }
  362. }
  363. return this._updateFormatOut('webp', options);
  364. }
  365. /**
  366. * Use these TIFF options for output image.
  367. *
  368. * @example
  369. * // Convert SVG input to LZW-compressed, 1 bit per pixel TIFF output
  370. * sharp('input.svg')
  371. * .tiff({
  372. * compression: 'lzw',
  373. * squash: true
  374. * })
  375. * .toFile('1-bpp-output.tiff')
  376. * .then(info => { ... });
  377. *
  378. * @param {Object} [options] - output options
  379. * @param {number} [options.quality=80] - quality, integer 1-100
  380. * @param {boolean} [options.force=true] - force TIFF output, otherwise attempt to use input format
  381. * @param {boolean} [options.compression='jpeg'] - compression options: lzw, deflate, jpeg, ccittfax4
  382. * @param {boolean} [options.predictor='horizontal'] - compression predictor options: none, horizontal, float
  383. * @param {boolean} [options.pyramid=false] - write an image pyramid
  384. * @param {boolean} [options.tile=false] - write a tiled tiff
  385. * @param {boolean} [options.tileWidth=256] - horizontal tile size
  386. * @param {boolean} [options.tileHeight=256] - vertical tile size
  387. * @param {number} [options.xres=1.0] - horizontal resolution in pixels/mm
  388. * @param {number} [options.yres=1.0] - vertical resolution in pixels/mm
  389. * @param {boolean} [options.squash=false] - squash 8-bit images down to 1 bit
  390. * @returns {Sharp}
  391. * @throws {Error} Invalid options
  392. */
  393. function tiff (options) {
  394. if (is.object(options)) {
  395. if (is.defined(options.quality)) {
  396. if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
  397. this.options.tiffQuality = options.quality;
  398. } else {
  399. throw is.invalidParameterError('quality', 'integer between 1 and 100', options.quality);
  400. }
  401. }
  402. if (is.defined(options.squash)) {
  403. this._setBooleanOption('tiffSquash', options.squash);
  404. }
  405. // tiling
  406. if (is.defined(options.tile)) {
  407. this._setBooleanOption('tiffTile', options.tile);
  408. }
  409. if (is.defined(options.tileWidth)) {
  410. if (is.integer(options.tileWidth) && options.tileWidth > 0) {
  411. this.options.tiffTileWidth = options.tileWidth;
  412. } else {
  413. throw is.invalidParameterError('tileWidth', 'integer greater than zero', options.tileWidth);
  414. }
  415. }
  416. if (is.defined(options.tileHeight)) {
  417. if (is.integer(options.tileHeight) && options.tileHeight > 0) {
  418. this.options.tiffTileHeight = options.tileHeight;
  419. } else {
  420. throw is.invalidParameterError('tileHeight', 'integer greater than zero', options.tileHeight);
  421. }
  422. }
  423. // pyramid
  424. if (is.defined(options.pyramid)) {
  425. this._setBooleanOption('tiffPyramid', options.pyramid);
  426. }
  427. // resolution
  428. if (is.defined(options.xres)) {
  429. if (is.number(options.xres) && options.xres > 0) {
  430. this.options.tiffXres = options.xres;
  431. } else {
  432. throw is.invalidParameterError('xres', 'number greater than zero', options.xres);
  433. }
  434. }
  435. if (is.defined(options.yres)) {
  436. if (is.number(options.yres) && options.yres > 0) {
  437. this.options.tiffYres = options.yres;
  438. } else {
  439. throw is.invalidParameterError('yres', 'number greater than zero', options.yres);
  440. }
  441. }
  442. // compression
  443. if (is.defined(options.compression)) {
  444. if (is.string(options.compression) && is.inArray(options.compression, ['lzw', 'deflate', 'jpeg', 'ccittfax4', 'none'])) {
  445. this.options.tiffCompression = options.compression;
  446. } else {
  447. throw is.invalidParameterError('compression', 'one of: lzw, deflate, jpeg, ccittfax4, none', options.compression);
  448. }
  449. }
  450. // predictor
  451. if (is.defined(options.predictor)) {
  452. if (is.string(options.predictor) && is.inArray(options.predictor, ['none', 'horizontal', 'float'])) {
  453. this.options.tiffPredictor = options.predictor;
  454. } else {
  455. throw is.invalidParameterError('predictor', 'one of: none, horizontal, float', options.predictor);
  456. }
  457. }
  458. }
  459. return this._updateFormatOut('tiff', options);
  460. }
  461. /**
  462. * Use these HEIF options for output image.
  463. *
  464. * Support for HEIF (HEIC/AVIF) is experimental.
  465. * Do not use this in production systems.
  466. *
  467. * Requires a custom, globally-installed libvips compiled with support for libheif.
  468. *
  469. * Most versions of libheif support only the patent-encumbered HEVC compression format.
  470. *
  471. * @since 0.23.0
  472. *
  473. * @param {Object} [options] - output options
  474. * @param {number} [options.quality=80] - quality, integer 1-100
  475. * @param {boolean} [options.compression='hevc'] - compression format: hevc, avc, jpeg, av1
  476. * @param {boolean} [options.lossless=false] - use lossless compression
  477. * @returns {Sharp}
  478. * @throws {Error} Invalid options
  479. */
  480. function heif (options) {
  481. if (!this.constructor.format.heif.output.buffer) {
  482. throw new Error('The heif operation requires libvips to have been installed with support for libheif');
  483. }
  484. if (is.object(options)) {
  485. if (is.defined(options.quality)) {
  486. if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
  487. this.options.heifQuality = options.quality;
  488. } else {
  489. throw is.invalidParameterError('quality', 'integer between 1 and 100', options.quality);
  490. }
  491. }
  492. if (is.defined(options.lossless)) {
  493. if (is.bool(options.lossless)) {
  494. this.options.heifLossless = options.lossless;
  495. } else {
  496. throw is.invalidParameterError('lossless', 'boolean', options.lossless);
  497. }
  498. }
  499. if (is.defined(options.compression)) {
  500. if (is.string(options.compression) && is.inArray(options.compression, ['hevc', 'avc', 'jpeg', 'av1'])) {
  501. this.options.heifCompression = options.compression;
  502. } else {
  503. throw is.invalidParameterError('compression', 'one of: hevc, avc, jpeg, av1', options.compression);
  504. }
  505. }
  506. }
  507. return this._updateFormatOut('heif', options);
  508. }
  509. /**
  510. * Force output to be raw, uncompressed, 8-bit unsigned integer (unit8) pixel data.
  511. * Pixel ordering is left-to-right, top-to-bottom, without padding.
  512. * Channel ordering will be RGB or RGBA for non-greyscale colourspaces.
  513. *
  514. * @example
  515. * // Extract raw RGB pixel data from JPEG input
  516. * const { data, info } = await sharp('input.jpg')
  517. * .raw()
  518. * .toBuffer({ resolveWithObject: true });
  519. *
  520. * @example
  521. * // Extract alpha channel as raw pixel data from PNG input
  522. * const data = await sharp('input.png')
  523. * .ensureAlpha()
  524. * .extractChannel(3)
  525. * .toColourspace('b-w')
  526. * .raw()
  527. * .toBuffer();
  528. *
  529. * @returns {Sharp}
  530. */
  531. function raw () {
  532. return this._updateFormatOut('raw');
  533. }
  534. /**
  535. * Use tile-based deep zoom (image pyramid) output.
  536. * Set the format and options for tile images via the `toFormat`, `jpeg`, `png` or `webp` functions.
  537. * Use a `.zip` or `.szi` file extension with `toFile` to write to a compressed archive file format.
  538. *
  539. * Warning: multiple sharp instances concurrently producing tile output can expose a possible race condition in some versions of libgsf.
  540. *
  541. * @example
  542. * sharp('input.tiff')
  543. * .png()
  544. * .tile({
  545. * size: 512
  546. * })
  547. * .toFile('output.dz', function(err, info) {
  548. * // output.dzi is the Deep Zoom XML definition
  549. * // output_files contains 512x512 tiles grouped by zoom level
  550. * });
  551. *
  552. * @param {Object} [options]
  553. * @param {number} [options.size=256] tile size in pixels, a value between 1 and 8192.
  554. * @param {number} [options.overlap=0] tile overlap in pixels, a value between 0 and 8192.
  555. * @param {number} [options.angle=0] tile angle of rotation, must be a multiple of 90.
  556. * @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.
  557. * @param {string} [options.depth] how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout.
  558. * @param {number} [options.skipBlanks=-1] threshold to skip tile generation, a value 0 - 255 for 8-bit images or 0 - 65535 for 16-bit images
  559. * @param {string} [options.container='fs'] tile container, with value `fs` (filesystem) or `zip` (compressed file).
  560. * @param {string} [options.layout='dz'] filesystem layout, possible values are `dz`, `iiif`, `zoomify` or `google`.
  561. * @returns {Sharp}
  562. * @throws {Error} Invalid parameters
  563. */
  564. function tile (options) {
  565. if (is.object(options)) {
  566. // Size of square tiles, in pixels
  567. if (is.defined(options.size)) {
  568. if (is.integer(options.size) && is.inRange(options.size, 1, 8192)) {
  569. this.options.tileSize = options.size;
  570. } else {
  571. throw is.invalidParameterError('size', 'integer between 1 and 8192', options.size);
  572. }
  573. }
  574. // Overlap of tiles, in pixels
  575. if (is.defined(options.overlap)) {
  576. if (is.integer(options.overlap) && is.inRange(options.overlap, 0, 8192)) {
  577. if (options.overlap > this.options.tileSize) {
  578. throw is.invalidParameterError('overlap', `<= size (${this.options.tileSize})`, options.overlap);
  579. }
  580. this.options.tileOverlap = options.overlap;
  581. } else {
  582. throw is.invalidParameterError('overlap', 'integer between 0 and 8192', options.overlap);
  583. }
  584. }
  585. // Container
  586. if (is.defined(options.container)) {
  587. if (is.string(options.container) && is.inArray(options.container, ['fs', 'zip'])) {
  588. this.options.tileContainer = options.container;
  589. } else {
  590. throw is.invalidParameterError('container', 'one of: fs, zip', options.container);
  591. }
  592. }
  593. // Layout
  594. if (is.defined(options.layout)) {
  595. if (is.string(options.layout) && is.inArray(options.layout, ['dz', 'google', 'iiif', 'zoomify'])) {
  596. this.options.tileLayout = options.layout;
  597. } else {
  598. throw is.invalidParameterError('layout', 'one of: dz, google, iiif, zoomify', options.layout);
  599. }
  600. }
  601. // Angle of rotation,
  602. if (is.defined(options.angle)) {
  603. if (is.integer(options.angle) && !(options.angle % 90)) {
  604. this.options.tileAngle = options.angle;
  605. } else {
  606. throw is.invalidParameterError('angle', 'positive/negative multiple of 90', options.angle);
  607. }
  608. }
  609. // Background colour
  610. this._setBackgroundColourOption('tileBackground', options.background);
  611. // Depth of tiles
  612. if (is.defined(options.depth)) {
  613. if (is.string(options.depth) && is.inArray(options.depth, ['onepixel', 'onetile', 'one'])) {
  614. this.options.tileDepth = options.depth;
  615. } else {
  616. throw is.invalidParameterError('depth', 'one of: onepixel, onetile, one', options.depth);
  617. }
  618. }
  619. // Threshold to skip blank tiles
  620. if (is.defined(options.skipBlanks)) {
  621. if (is.integer(options.skipBlanks) && is.inRange(options.skipBlanks, -1, 65535)) {
  622. this.options.tileSkipBlanks = options.skipBlanks;
  623. } else {
  624. throw is.invalidParameterError('skipBlanks', 'integer between -1 and 255/65535', options.skipBlanks);
  625. }
  626. } else if (is.defined(options.layout) && options.layout === 'google') {
  627. this.options.tileSkipBlanks = 5;
  628. }
  629. }
  630. // Format
  631. if (is.inArray(this.options.formatOut, ['jpeg', 'png', 'webp'])) {
  632. this.options.tileFormat = this.options.formatOut;
  633. } else if (this.options.formatOut !== 'input') {
  634. throw is.invalidParameterError('format', 'one of: jpeg, png, webp', this.options.formatOut);
  635. }
  636. return this._updateFormatOut('dz');
  637. }
  638. /**
  639. * Update the output format unless options.force is false,
  640. * in which case revert to input format.
  641. * @private
  642. * @param {string} formatOut
  643. * @param {Object} [options]
  644. * @param {boolean} [options.force=true] - force output format, otherwise attempt to use input format
  645. * @returns {Sharp}
  646. */
  647. function _updateFormatOut (formatOut, options) {
  648. if (!(is.object(options) && options.force === false)) {
  649. this.options.formatOut = formatOut;
  650. }
  651. return this;
  652. }
  653. /**
  654. * Update a boolean attribute of the this.options Object.
  655. * @private
  656. * @param {string} key
  657. * @param {boolean} val
  658. * @throws {Error} Invalid key
  659. */
  660. function _setBooleanOption (key, val) {
  661. if (is.bool(val)) {
  662. this.options[key] = val;
  663. } else {
  664. throw is.invalidParameterError(key, 'boolean', val);
  665. }
  666. }
  667. /**
  668. * Called by a WriteableStream to notify us it is ready for data.
  669. * @private
  670. */
  671. function _read () {
  672. /* istanbul ignore else */
  673. if (!this.options.streamOut) {
  674. this.options.streamOut = true;
  675. this._pipeline();
  676. }
  677. }
  678. /**
  679. * Invoke the C++ image processing pipeline
  680. * Supports callback, stream and promise variants
  681. * @private
  682. */
  683. function _pipeline (callback) {
  684. if (typeof callback === 'function') {
  685. // output=file/buffer
  686. if (this._isStreamInput()) {
  687. // output=file/buffer, input=stream
  688. this.on('finish', () => {
  689. this._flattenBufferIn();
  690. sharp.pipeline(this.options, callback);
  691. });
  692. } else {
  693. // output=file/buffer, input=file/buffer
  694. sharp.pipeline(this.options, callback);
  695. }
  696. return this;
  697. } else if (this.options.streamOut) {
  698. // output=stream
  699. if (this._isStreamInput()) {
  700. // output=stream, input=stream
  701. this.once('finish', () => {
  702. this._flattenBufferIn();
  703. sharp.pipeline(this.options, (err, data, info) => {
  704. if (err) {
  705. this.emit('error', err);
  706. } else {
  707. this.emit('info', info);
  708. this.push(data);
  709. }
  710. this.push(null);
  711. });
  712. });
  713. if (this.streamInFinished) {
  714. this.emit('finish');
  715. }
  716. } else {
  717. // output=stream, input=file/buffer
  718. sharp.pipeline(this.options, (err, data, info) => {
  719. if (err) {
  720. this.emit('error', err);
  721. } else {
  722. this.emit('info', info);
  723. this.push(data);
  724. }
  725. this.push(null);
  726. });
  727. }
  728. return this;
  729. } else {
  730. // output=promise
  731. if (this._isStreamInput()) {
  732. // output=promise, input=stream
  733. return new Promise((resolve, reject) => {
  734. this.once('finish', () => {
  735. this._flattenBufferIn();
  736. sharp.pipeline(this.options, (err, data, info) => {
  737. if (err) {
  738. reject(err);
  739. } else {
  740. if (this.options.resolveWithObject) {
  741. resolve({ data, info });
  742. } else {
  743. resolve(data);
  744. }
  745. }
  746. });
  747. });
  748. });
  749. } else {
  750. // output=promise, input=file/buffer
  751. return new Promise((resolve, reject) => {
  752. sharp.pipeline(this.options, (err, data, info) => {
  753. if (err) {
  754. reject(err);
  755. } else {
  756. if (this.options.resolveWithObject) {
  757. resolve({ data: data, info: info });
  758. } else {
  759. resolve(data);
  760. }
  761. }
  762. });
  763. });
  764. }
  765. }
  766. }
  767. /**
  768. * Decorate the Sharp prototype with output-related functions.
  769. * @private
  770. */
  771. module.exports = function (Sharp) {
  772. Object.assign(Sharp.prototype, {
  773. // Public
  774. toFile,
  775. toBuffer,
  776. withMetadata,
  777. toFormat,
  778. jpeg,
  779. png,
  780. webp,
  781. tiff,
  782. heif,
  783. raw,
  784. tile,
  785. // Private
  786. _updateFormatOut,
  787. _setBooleanOption,
  788. _read,
  789. _pipeline
  790. });
  791. };