123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809 |
- 'use strict';
- const is = require('./is');
- const sharp = require('../build/Release/sharp.node');
- const formats = new Map([
- ['heic', 'heif'],
- ['heif', 'heif'],
- ['jpeg', 'jpeg'],
- ['jpg', 'jpeg'],
- ['png', 'png'],
- ['raw', 'raw'],
- ['tiff', 'tiff'],
- ['webp', 'webp']
- ]);
- /**
- * Write output image data to a file.
- *
- * If an explicit output format is not selected, it will be inferred from the extension,
- * with JPEG, PNG, WebP, TIFF, DZI, and libvips' V format supported.
- * Note that raw pixel data is only supported for buffer output.
- *
- * By default all metadata will be removed, which includes EXIF-based orientation.
- * See {@link withMetadata} for control over this.
- *
- * A `Promise` is returned when `callback` is not provided.
- *
- * @example
- * sharp(input)
- * .toFile('output.png', (err, info) => { ... });
- *
- * @example
- * sharp(input)
- * .toFile('output.png')
- * .then(info => { ... })
- * .catch(err => { ... });
- *
- * @param {string} fileOut - the path to write the image data to.
- * @param {Function} [callback] - called on completion with two arguments `(err, info)`.
- * `info` contains the output image `format`, `size` (bytes), `width`, `height`,
- * `channels` and `premultiplied` (indicating if premultiplication was used).
- * When using a crop strategy also contains `cropOffsetLeft` and `cropOffsetTop`.
- * @returns {Promise<Object>} - when no callback is provided
- * @throws {Error} Invalid parameters
- */
- function toFile (fileOut, callback) {
- if (!fileOut || fileOut.length === 0) {
- const errOutputInvalid = new Error('Missing output file path');
- if (is.fn(callback)) {
- callback(errOutputInvalid);
- } else {
- return Promise.reject(errOutputInvalid);
- }
- } else {
- if (this.options.input.file === fileOut) {
- const errOutputIsInput = new Error('Cannot use same file for input and output');
- if (is.fn(callback)) {
- callback(errOutputIsInput);
- } else {
- return Promise.reject(errOutputIsInput);
- }
- } else {
- this.options.fileOut = fileOut;
- return this._pipeline(callback);
- }
- }
- return this;
- }
- /**
- * Write output to a Buffer.
- * JPEG, PNG, WebP, TIFF and RAW output are supported.
- *
- * If no explicit format is set, the output format will match the input image, except GIF and SVG input which become PNG output.
- *
- * By default all metadata will be removed, which includes EXIF-based orientation.
- * See {@link withMetadata} for control over this.
- *
- * `callback`, if present, gets three arguments `(err, data, info)` where:
- * - `err` is an error, if any.
- * - `data` is the output image data.
- * - `info` contains the output image `format`, `size` (bytes), `width`, `height`,
- * `channels` and `premultiplied` (indicating if premultiplication was used).
- * When using a crop strategy also contains `cropOffsetLeft` and `cropOffsetTop`.
- *
- * A `Promise` is returned when `callback` is not provided.
- *
- * @example
- * sharp(input)
- * .toBuffer((err, data, info) => { ... });
- *
- * @example
- * sharp(input)
- * .toBuffer()
- * .then(data => { ... })
- * .catch(err => { ... });
- *
- * @example
- * sharp(input)
- * .toBuffer({ resolveWithObject: true })
- * .then(({ data, info }) => { ... })
- * .catch(err => { ... });
- *
- * @param {Object} [options]
- * @param {boolean} [options.resolveWithObject] Resolve the Promise with an Object containing `data` and `info` properties instead of resolving only with `data`.
- * @param {Function} [callback]
- * @returns {Promise<Buffer>} - when no callback is provided
- */
- function toBuffer (options, callback) {
- if (is.object(options)) {
- this._setBooleanOption('resolveWithObject', options.resolveWithObject);
- } else if (this.options.resolveWithObject) {
- this.options.resolveWithObject = false;
- }
- return this._pipeline(is.fn(options) ? options : callback);
- }
- /**
- * Include all metadata (EXIF, XMP, IPTC) from the input image in the output image.
- * This will also convert to and add a web-friendly sRGB ICC profile.
- *
- * The default behaviour, when `withMetadata` is not used, is to convert to the device-independent
- * sRGB colour space and strip all metadata, including the removal of any ICC profile.
- *
- * @example
- * sharp('input.jpg')
- * .withMetadata()
- * .toFile('output-with-metadata.jpg')
- * .then(info => { ... });
- *
- * @param {Object} [options]
- * @param {number} [options.orientation] value between 1 and 8, used to update the EXIF `Orientation` tag.
- * @returns {Sharp}
- * @throws {Error} Invalid parameters
- */
- function withMetadata (options) {
- this.options.withMetadata = is.bool(options) ? options : true;
- if (is.object(options)) {
- if (is.defined(options.orientation)) {
- if (is.integer(options.orientation) && is.inRange(options.orientation, 1, 8)) {
- this.options.withMetadataOrientation = options.orientation;
- } else {
- throw is.invalidParameterError('orientation', 'integer between 1 and 8', options.orientation);
- }
- }
- }
- return this;
- }
- /**
- * Force output to a given format.
- *
- * @example
- * // Convert any input to PNG output
- * const data = await sharp(input)
- * .toFormat('png')
- * .toBuffer();
- *
- * @param {(string|Object)} format - as a string or an Object with an 'id' attribute
- * @param {Object} options - output options
- * @returns {Sharp}
- * @throws {Error} unsupported format or options
- */
- function toFormat (format, options) {
- const actualFormat = formats.get(is.object(format) && is.string(format.id) ? format.id : format);
- if (!actualFormat) {
- throw is.invalidParameterError('format', `one of: ${[...formats.keys()].join(', ')}`, format);
- }
- return this[actualFormat](options);
- }
- /**
- * Use these JPEG options for output image.
- *
- * @example
- * // Convert any input to very high quality JPEG output
- * const data = await sharp(input)
- * .jpeg({
- * quality: 100,
- * chromaSubsampling: '4:4:4'
- * })
- * .toBuffer();
- *
- * @param {Object} [options] - output options
- * @param {number} [options.quality=80] - quality, integer 1-100
- * @param {boolean} [options.progressive=false] - use progressive (interlace) scan
- * @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
- * @param {boolean} [options.trellisQuantisation=false] - apply trellis quantisation, requires libvips compiled with support for mozjpeg
- * @param {boolean} [options.overshootDeringing=false] - apply overshoot deringing, requires libvips compiled with support for mozjpeg
- * @param {boolean} [options.optimiseScans=false] - optimise progressive scans, forces progressive, requires libvips compiled with support for mozjpeg
- * @param {boolean} [options.optimizeScans=false] - alternative spelling of optimiseScans
- * @param {boolean} [options.optimiseCoding=true] - optimise Huffman coding tables
- * @param {boolean} [options.optimizeCoding=true] - alternative spelling of optimiseCoding
- * @param {number} [options.quantisationTable=0] - quantization table to use, integer 0-8, requires libvips compiled with support for mozjpeg
- * @param {number} [options.quantizationTable=0] - alternative spelling of quantisationTable
- * @param {boolean} [options.force=true] - force JPEG output, otherwise attempt to use input format
- * @returns {Sharp}
- * @throws {Error} Invalid options
- */
- function jpeg (options) {
- if (is.object(options)) {
- if (is.defined(options.quality)) {
- if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
- this.options.jpegQuality = options.quality;
- } else {
- throw is.invalidParameterError('quality', 'integer between 1 and 100', options.quality);
- }
- }
- if (is.defined(options.progressive)) {
- this._setBooleanOption('jpegProgressive', options.progressive);
- }
- if (is.defined(options.chromaSubsampling)) {
- if (is.string(options.chromaSubsampling) && is.inArray(options.chromaSubsampling, ['4:2:0', '4:4:4'])) {
- this.options.jpegChromaSubsampling = options.chromaSubsampling;
- } else {
- throw is.invalidParameterError('chromaSubsampling', 'one of: 4:2:0, 4:4:4', options.chromaSubsampling);
- }
- }
- const trellisQuantisation = is.bool(options.trellisQuantization) ? options.trellisQuantization : options.trellisQuantisation;
- if (is.defined(trellisQuantisation)) {
- this._setBooleanOption('jpegTrellisQuantisation', trellisQuantisation);
- }
- if (is.defined(options.overshootDeringing)) {
- this._setBooleanOption('jpegOvershootDeringing', options.overshootDeringing);
- }
- const optimiseScans = is.bool(options.optimizeScans) ? options.optimizeScans : options.optimiseScans;
- if (is.defined(optimiseScans)) {
- this._setBooleanOption('jpegOptimiseScans', optimiseScans);
- if (optimiseScans) {
- this.options.jpegProgressive = true;
- }
- }
- const optimiseCoding = is.bool(options.optimizeCoding) ? options.optimizeCoding : options.optimiseCoding;
- if (is.defined(optimiseCoding)) {
- this._setBooleanOption('jpegOptimiseCoding', optimiseCoding);
- }
- const quantisationTable = is.number(options.quantizationTable) ? options.quantizationTable : options.quantisationTable;
- if (is.defined(quantisationTable)) {
- if (is.integer(quantisationTable) && is.inRange(quantisationTable, 0, 8)) {
- this.options.jpegQuantisationTable = quantisationTable;
- } else {
- throw is.invalidParameterError('quantisationTable', 'integer between 0 and 8', quantisationTable);
- }
- }
- }
- return this._updateFormatOut('jpeg', options);
- }
- /**
- * Use these PNG options for output image.
- *
- * PNG output is always full colour at 8 or 16 bits per pixel.
- * Indexed PNG input at 1, 2 or 4 bits per pixel is converted to 8 bits per pixel.
- *
- * @example
- * // Convert any input to PNG output
- * const data = await sharp(input)
- * .png()
- * .toBuffer();
- *
- * @param {Object} [options]
- * @param {boolean} [options.progressive=false] - use progressive (interlace) scan
- * @param {number} [options.compressionLevel=9] - zlib compression level, 0-9
- * @param {boolean} [options.adaptiveFiltering=false] - use adaptive row filtering
- * @param {boolean} [options.palette=false] - quantise to a palette-based image with alpha transparency support, requires libvips compiled with support for libimagequant
- * @param {number} [options.quality=100] - use the lowest number of colours needed to achieve given quality, requires libvips compiled with support for libimagequant
- * @param {number} [options.colours=256] - maximum number of palette entries, requires libvips compiled with support for libimagequant
- * @param {number} [options.colors=256] - alternative spelling of `options.colours`, requires libvips compiled with support for libimagequant
- * @param {number} [options.dither=1.0] - level of Floyd-Steinberg error diffusion, requires libvips compiled with support for libimagequant
- * @param {boolean} [options.force=true] - force PNG output, otherwise attempt to use input format
- * @returns {Sharp}
- * @throws {Error} Invalid options
- */
- function png (options) {
- if (is.object(options)) {
- if (is.defined(options.progressive)) {
- this._setBooleanOption('pngProgressive', options.progressive);
- }
- if (is.defined(options.compressionLevel)) {
- if (is.integer(options.compressionLevel) && is.inRange(options.compressionLevel, 0, 9)) {
- this.options.pngCompressionLevel = options.compressionLevel;
- } else {
- throw is.invalidParameterError('compressionLevel', 'integer between 0 and 9', options.compressionLevel);
- }
- }
- if (is.defined(options.adaptiveFiltering)) {
- this._setBooleanOption('pngAdaptiveFiltering', options.adaptiveFiltering);
- }
- if (is.defined(options.palette)) {
- this._setBooleanOption('pngPalette', options.palette);
- if (this.options.pngPalette) {
- if (is.defined(options.quality)) {
- if (is.integer(options.quality) && is.inRange(options.quality, 0, 100)) {
- this.options.pngQuality = options.quality;
- } else {
- throw is.invalidParameterError('quality', 'integer between 0 and 100', options.quality);
- }
- }
- const colours = options.colours || options.colors;
- if (is.defined(colours)) {
- if (is.integer(colours) && is.inRange(colours, 2, 256)) {
- this.options.pngColours = colours;
- } else {
- throw is.invalidParameterError('colours', 'integer between 2 and 256', colours);
- }
- }
- if (is.defined(options.dither)) {
- if (is.number(options.dither) && is.inRange(options.dither, 0, 1)) {
- this.options.pngDither = options.dither;
- } else {
- throw is.invalidParameterError('dither', 'number between 0.0 and 1.0', options.dither);
- }
- }
- }
- }
- }
- return this._updateFormatOut('png', options);
- }
- /**
- * Use these WebP options for output image.
- *
- * @example
- * // Convert any input to lossless WebP output
- * const data = await sharp(input)
- * .webp({ lossless: true })
- * .toBuffer();
- *
- * @param {Object} [options] - output options
- * @param {number} [options.quality=80] - quality, integer 1-100
- * @param {number} [options.alphaQuality=100] - quality of alpha layer, integer 0-100
- * @param {boolean} [options.lossless=false] - use lossless compression mode
- * @param {boolean} [options.nearLossless=false] - use near_lossless compression mode
- * @param {boolean} [options.smartSubsample=false] - use high quality chroma subsampling
- * @param {number} [options.reductionEffort=4] - level of CPU effort to reduce file size, integer 0-6
- * @param {boolean} [options.force=true] - force WebP output, otherwise attempt to use input format
- * @returns {Sharp}
- * @throws {Error} Invalid options
- */
- function webp (options) {
- if (is.object(options) && is.defined(options.quality)) {
- if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
- this.options.webpQuality = options.quality;
- } else {
- throw is.invalidParameterError('quality', 'integer between 1 and 100', options.quality);
- }
- }
- if (is.object(options) && is.defined(options.alphaQuality)) {
- if (is.integer(options.alphaQuality) && is.inRange(options.alphaQuality, 0, 100)) {
- this.options.webpAlphaQuality = options.alphaQuality;
- } else {
- throw is.invalidParameterError('alphaQuality', 'integer between 0 and 100', options.alphaQuality);
- }
- }
- if (is.object(options) && is.defined(options.lossless)) {
- this._setBooleanOption('webpLossless', options.lossless);
- }
- if (is.object(options) && is.defined(options.nearLossless)) {
- this._setBooleanOption('webpNearLossless', options.nearLossless);
- }
- if (is.object(options) && is.defined(options.smartSubsample)) {
- this._setBooleanOption('webpSmartSubsample', options.smartSubsample);
- }
- if (is.object(options) && is.defined(options.reductionEffort)) {
- if (is.integer(options.reductionEffort) && is.inRange(options.reductionEffort, 0, 6)) {
- this.options.webpReductionEffort = options.reductionEffort;
- } else {
- throw is.invalidParameterError('reductionEffort', 'integer between 0 and 6', options.reductionEffort);
- }
- }
- return this._updateFormatOut('webp', options);
- }
- /**
- * Use these TIFF options for output image.
- *
- * @example
- * // Convert SVG input to LZW-compressed, 1 bit per pixel TIFF output
- * sharp('input.svg')
- * .tiff({
- * compression: 'lzw',
- * squash: true
- * })
- * .toFile('1-bpp-output.tiff')
- * .then(info => { ... });
- *
- * @param {Object} [options] - output options
- * @param {number} [options.quality=80] - quality, integer 1-100
- * @param {boolean} [options.force=true] - force TIFF output, otherwise attempt to use input format
- * @param {boolean} [options.compression='jpeg'] - compression options: lzw, deflate, jpeg, ccittfax4
- * @param {boolean} [options.predictor='horizontal'] - compression predictor options: none, horizontal, float
- * @param {boolean} [options.pyramid=false] - write an image pyramid
- * @param {boolean} [options.tile=false] - write a tiled tiff
- * @param {boolean} [options.tileWidth=256] - horizontal tile size
- * @param {boolean} [options.tileHeight=256] - vertical tile size
- * @param {number} [options.xres=1.0] - horizontal resolution in pixels/mm
- * @param {number} [options.yres=1.0] - vertical resolution in pixels/mm
- * @param {boolean} [options.squash=false] - squash 8-bit images down to 1 bit
- * @returns {Sharp}
- * @throws {Error} Invalid options
- */
- function tiff (options) {
- if (is.object(options)) {
- if (is.defined(options.quality)) {
- if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
- this.options.tiffQuality = options.quality;
- } else {
- throw is.invalidParameterError('quality', 'integer between 1 and 100', options.quality);
- }
- }
- if (is.defined(options.squash)) {
- this._setBooleanOption('tiffSquash', options.squash);
- }
- // tiling
- if (is.defined(options.tile)) {
- this._setBooleanOption('tiffTile', options.tile);
- }
- if (is.defined(options.tileWidth)) {
- if (is.integer(options.tileWidth) && options.tileWidth > 0) {
- this.options.tiffTileWidth = options.tileWidth;
- } else {
- throw is.invalidParameterError('tileWidth', 'integer greater than zero', options.tileWidth);
- }
- }
- if (is.defined(options.tileHeight)) {
- if (is.integer(options.tileHeight) && options.tileHeight > 0) {
- this.options.tiffTileHeight = options.tileHeight;
- } else {
- throw is.invalidParameterError('tileHeight', 'integer greater than zero', options.tileHeight);
- }
- }
- // pyramid
- if (is.defined(options.pyramid)) {
- this._setBooleanOption('tiffPyramid', options.pyramid);
- }
- // resolution
- if (is.defined(options.xres)) {
- if (is.number(options.xres) && options.xres > 0) {
- this.options.tiffXres = options.xres;
- } else {
- throw is.invalidParameterError('xres', 'number greater than zero', options.xres);
- }
- }
- if (is.defined(options.yres)) {
- if (is.number(options.yres) && options.yres > 0) {
- this.options.tiffYres = options.yres;
- } else {
- throw is.invalidParameterError('yres', 'number greater than zero', options.yres);
- }
- }
- // compression
- if (is.defined(options.compression)) {
- if (is.string(options.compression) && is.inArray(options.compression, ['lzw', 'deflate', 'jpeg', 'ccittfax4', 'none'])) {
- this.options.tiffCompression = options.compression;
- } else {
- throw is.invalidParameterError('compression', 'one of: lzw, deflate, jpeg, ccittfax4, none', options.compression);
- }
- }
- // predictor
- if (is.defined(options.predictor)) {
- if (is.string(options.predictor) && is.inArray(options.predictor, ['none', 'horizontal', 'float'])) {
- this.options.tiffPredictor = options.predictor;
- } else {
- throw is.invalidParameterError('predictor', 'one of: none, horizontal, float', options.predictor);
- }
- }
- }
- return this._updateFormatOut('tiff', options);
- }
- /**
- * Use these HEIF options for output image.
- *
- * Support for HEIF (HEIC/AVIF) is experimental.
- * Do not use this in production systems.
- *
- * Requires a custom, globally-installed libvips compiled with support for libheif.
- *
- * Most versions of libheif support only the patent-encumbered HEVC compression format.
- *
- * @since 0.23.0
- *
- * @param {Object} [options] - output options
- * @param {number} [options.quality=80] - quality, integer 1-100
- * @param {boolean} [options.compression='hevc'] - compression format: hevc, avc, jpeg, av1
- * @param {boolean} [options.lossless=false] - use lossless compression
- * @returns {Sharp}
- * @throws {Error} Invalid options
- */
- function heif (options) {
- if (!this.constructor.format.heif.output.buffer) {
- throw new Error('The heif operation requires libvips to have been installed with support for libheif');
- }
- if (is.object(options)) {
- if (is.defined(options.quality)) {
- if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
- this.options.heifQuality = options.quality;
- } else {
- throw is.invalidParameterError('quality', 'integer between 1 and 100', options.quality);
- }
- }
- if (is.defined(options.lossless)) {
- if (is.bool(options.lossless)) {
- this.options.heifLossless = options.lossless;
- } else {
- throw is.invalidParameterError('lossless', 'boolean', options.lossless);
- }
- }
- if (is.defined(options.compression)) {
- if (is.string(options.compression) && is.inArray(options.compression, ['hevc', 'avc', 'jpeg', 'av1'])) {
- this.options.heifCompression = options.compression;
- } else {
- throw is.invalidParameterError('compression', 'one of: hevc, avc, jpeg, av1', options.compression);
- }
- }
- }
- return this._updateFormatOut('heif', options);
- }
- /**
- * Force output to be raw, uncompressed, 8-bit unsigned integer (unit8) pixel data.
- * Pixel ordering is left-to-right, top-to-bottom, without padding.
- * Channel ordering will be RGB or RGBA for non-greyscale colourspaces.
- *
- * @example
- * // Extract raw RGB pixel data from JPEG input
- * const { data, info } = await sharp('input.jpg')
- * .raw()
- * .toBuffer({ resolveWithObject: true });
- *
- * @example
- * // Extract alpha channel as raw pixel data from PNG input
- * const data = await sharp('input.png')
- * .ensureAlpha()
- * .extractChannel(3)
- * .toColourspace('b-w')
- * .raw()
- * .toBuffer();
- *
- * @returns {Sharp}
- */
- function raw () {
- return this._updateFormatOut('raw');
- }
- /**
- * Use tile-based deep zoom (image pyramid) output.
- * Set the format and options for tile images via the `toFormat`, `jpeg`, `png` or `webp` functions.
- * Use a `.zip` or `.szi` file extension with `toFile` to write to a compressed archive file format.
- *
- * Warning: multiple sharp instances concurrently producing tile output can expose a possible race condition in some versions of libgsf.
- *
- * @example
- * sharp('input.tiff')
- * .png()
- * .tile({
- * size: 512
- * })
- * .toFile('output.dz', function(err, info) {
- * // output.dzi is the Deep Zoom XML definition
- * // output_files contains 512x512 tiles grouped by zoom level
- * });
- *
- * @param {Object} [options]
- * @param {number} [options.size=256] tile size in pixels, a value between 1 and 8192.
- * @param {number} [options.overlap=0] tile overlap in pixels, a value between 0 and 8192.
- * @param {number} [options.angle=0] tile angle of rotation, must be a multiple of 90.
- * @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.
- * @param {string} [options.depth] how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout.
- * @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
- * @param {string} [options.container='fs'] tile container, with value `fs` (filesystem) or `zip` (compressed file).
- * @param {string} [options.layout='dz'] filesystem layout, possible values are `dz`, `iiif`, `zoomify` or `google`.
- * @returns {Sharp}
- * @throws {Error} Invalid parameters
- */
- function tile (options) {
- if (is.object(options)) {
- // Size of square tiles, in pixels
- if (is.defined(options.size)) {
- if (is.integer(options.size) && is.inRange(options.size, 1, 8192)) {
- this.options.tileSize = options.size;
- } else {
- throw is.invalidParameterError('size', 'integer between 1 and 8192', options.size);
- }
- }
- // Overlap of tiles, in pixels
- if (is.defined(options.overlap)) {
- if (is.integer(options.overlap) && is.inRange(options.overlap, 0, 8192)) {
- if (options.overlap > this.options.tileSize) {
- throw is.invalidParameterError('overlap', `<= size (${this.options.tileSize})`, options.overlap);
- }
- this.options.tileOverlap = options.overlap;
- } else {
- throw is.invalidParameterError('overlap', 'integer between 0 and 8192', options.overlap);
- }
- }
- // Container
- if (is.defined(options.container)) {
- if (is.string(options.container) && is.inArray(options.container, ['fs', 'zip'])) {
- this.options.tileContainer = options.container;
- } else {
- throw is.invalidParameterError('container', 'one of: fs, zip', options.container);
- }
- }
- // Layout
- if (is.defined(options.layout)) {
- if (is.string(options.layout) && is.inArray(options.layout, ['dz', 'google', 'iiif', 'zoomify'])) {
- this.options.tileLayout = options.layout;
- } else {
- throw is.invalidParameterError('layout', 'one of: dz, google, iiif, zoomify', options.layout);
- }
- }
- // Angle of rotation,
- if (is.defined(options.angle)) {
- if (is.integer(options.angle) && !(options.angle % 90)) {
- this.options.tileAngle = options.angle;
- } else {
- throw is.invalidParameterError('angle', 'positive/negative multiple of 90', options.angle);
- }
- }
- // Background colour
- this._setBackgroundColourOption('tileBackground', options.background);
- // Depth of tiles
- if (is.defined(options.depth)) {
- if (is.string(options.depth) && is.inArray(options.depth, ['onepixel', 'onetile', 'one'])) {
- this.options.tileDepth = options.depth;
- } else {
- throw is.invalidParameterError('depth', 'one of: onepixel, onetile, one', options.depth);
- }
- }
- // Threshold to skip blank tiles
- if (is.defined(options.skipBlanks)) {
- if (is.integer(options.skipBlanks) && is.inRange(options.skipBlanks, -1, 65535)) {
- this.options.tileSkipBlanks = options.skipBlanks;
- } else {
- throw is.invalidParameterError('skipBlanks', 'integer between -1 and 255/65535', options.skipBlanks);
- }
- } else if (is.defined(options.layout) && options.layout === 'google') {
- this.options.tileSkipBlanks = 5;
- }
- }
- // Format
- if (is.inArray(this.options.formatOut, ['jpeg', 'png', 'webp'])) {
- this.options.tileFormat = this.options.formatOut;
- } else if (this.options.formatOut !== 'input') {
- throw is.invalidParameterError('format', 'one of: jpeg, png, webp', this.options.formatOut);
- }
- return this._updateFormatOut('dz');
- }
- /**
- * Update the output format unless options.force is false,
- * in which case revert to input format.
- * @private
- * @param {string} formatOut
- * @param {Object} [options]
- * @param {boolean} [options.force=true] - force output format, otherwise attempt to use input format
- * @returns {Sharp}
- */
- function _updateFormatOut (formatOut, options) {
- if (!(is.object(options) && options.force === false)) {
- this.options.formatOut = formatOut;
- }
- return this;
- }
- /**
- * Update a boolean attribute of the this.options Object.
- * @private
- * @param {string} key
- * @param {boolean} val
- * @throws {Error} Invalid key
- */
- function _setBooleanOption (key, val) {
- if (is.bool(val)) {
- this.options[key] = val;
- } else {
- throw is.invalidParameterError(key, 'boolean', val);
- }
- }
- /**
- * Called by a WriteableStream to notify us it is ready for data.
- * @private
- */
- function _read () {
- /* istanbul ignore else */
- if (!this.options.streamOut) {
- this.options.streamOut = true;
- this._pipeline();
- }
- }
- /**
- * Invoke the C++ image processing pipeline
- * Supports callback, stream and promise variants
- * @private
- */
- function _pipeline (callback) {
- if (typeof callback === 'function') {
- // output=file/buffer
- if (this._isStreamInput()) {
- // output=file/buffer, input=stream
- this.on('finish', () => {
- this._flattenBufferIn();
- sharp.pipeline(this.options, callback);
- });
- } else {
- // output=file/buffer, input=file/buffer
- sharp.pipeline(this.options, callback);
- }
- return this;
- } else if (this.options.streamOut) {
- // output=stream
- if (this._isStreamInput()) {
- // output=stream, input=stream
- this.once('finish', () => {
- this._flattenBufferIn();
- sharp.pipeline(this.options, (err, data, info) => {
- if (err) {
- this.emit('error', err);
- } else {
- this.emit('info', info);
- this.push(data);
- }
- this.push(null);
- });
- });
- if (this.streamInFinished) {
- this.emit('finish');
- }
- } else {
- // output=stream, input=file/buffer
- sharp.pipeline(this.options, (err, data, info) => {
- if (err) {
- this.emit('error', err);
- } else {
- this.emit('info', info);
- this.push(data);
- }
- this.push(null);
- });
- }
- return this;
- } else {
- // output=promise
- if (this._isStreamInput()) {
- // output=promise, input=stream
- return new Promise((resolve, reject) => {
- this.once('finish', () => {
- this._flattenBufferIn();
- sharp.pipeline(this.options, (err, data, info) => {
- if (err) {
- reject(err);
- } else {
- if (this.options.resolveWithObject) {
- resolve({ data, info });
- } else {
- resolve(data);
- }
- }
- });
- });
- });
- } else {
- // output=promise, input=file/buffer
- return new Promise((resolve, reject) => {
- sharp.pipeline(this.options, (err, data, info) => {
- if (err) {
- reject(err);
- } else {
- if (this.options.resolveWithObject) {
- resolve({ data: data, info: info });
- } else {
- resolve(data);
- }
- }
- });
- });
- }
- }
- }
- /**
- * Decorate the Sharp prototype with output-related functions.
- * @private
- */
- module.exports = function (Sharp) {
- Object.assign(Sharp.prototype, {
- // Public
- toFile,
- toBuffer,
- withMetadata,
- toFormat,
- jpeg,
- png,
- webp,
- tiff,
- heif,
- raw,
- tile,
- // Private
- _updateFormatOut,
- _setBooleanOption,
- _read,
- _pipeline
- });
- };
|