'use strict'; const util = require('util'); const stream = require('stream'); const is = require('./is'); require('./libvips').hasVendoredLibvips(); /* istanbul ignore next */ try { require('../build/Release/sharp.node'); } catch (err) { // Bail early if bindings aren't available const help = ['', 'Something went wrong installing the "sharp" module', '', err.message, '']; if (/NODE_MODULE_VERSION/.test(err.message)) { help.push('- Ensure the version of Node.js used at install time matches that used at runtime'); } else if (/invalid ELF header/.test(err.message)) { help.push(`- Ensure "${process.platform}" is used at install time as well as runtime`); } else if (/dylib/.test(err.message) && /Incompatible library version/.test(err.message)) { help.push('- Run "brew update && brew upgrade vips"'); } else if (/Cannot find module/.test(err.message)) { help.push('- Run "npm rebuild --verbose sharp" and look for errors'); } else { help.push( '- Remove the "node_modules/sharp" directory then run', ' "npm install --ignore-scripts=false --verbose" and look for errors' ); } help.push( '- Consult the installation documentation at https://sharp.pixelplumbing.com/install', '- Search for this error at https://github.com/lovell/sharp/issues', '' ); const error = help.join('\n'); throw new Error(error); } // Use NODE_DEBUG=sharp to enable libvips warnings const debuglog = util.debuglog('sharp'); /** * Constructor factory to create an instance of `sharp`, to which further methods are chained. * * JPEG, PNG, WebP or TIFF format image data can be streamed out from this object. * When using Stream based output, derived attributes are available from the `info` event. * * Non-critical problems encountered during processing are emitted as `warning` events. * * Implements the [stream.Duplex](http://nodejs.org/api/stream.html#stream_class_stream_duplex) class. * * @constructs Sharp * * @emits Sharp#info * @emits Sharp#warning * * @example * sharp('input.jpg') * .resize(300, 200) * .toFile('output.jpg', function(err) { * // output.jpg is a 300 pixels wide and 200 pixels high image * // containing a scaled and cropped version of input.jpg * }); * * @example * // Read image data from readableStream, * // resize to 300 pixels wide, * // emit an 'info' event with calculated dimensions * // and finally write image data to writableStream * var transformer = sharp() * .resize(300) * .on('info', function(info) { * console.log('Image height is ' + info.height); * }); * readableStream.pipe(transformer).pipe(writableStream); * * @example * // Create a blank 300x200 PNG image of semi-transluent red pixels * sharp({ * create: { * width: 300, * height: 200, * channels: 4, * background: { r: 255, g: 0, b: 0, alpha: 0.5 } * } * }) * .png() * .toBuffer() * .then( ... ); * * @example * // Convert an animated GIF to an animated WebP * await sharp('in.gif', { animated: true }).toFile('out.webp'); * * @param {(Buffer|string)} [input] - if present, can be * a Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or * a String containing the filesystem path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file. * JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present. * @param {Object} [options] - if present, is an Object with optional attributes. * @param {boolean} [options.failOnError=true] - by default halt processing and raise an error when loading invalid images. * Set this flag to `false` if you'd rather apply a "best effort" to decode images, even if the data is corrupt or invalid. * @param {number|boolean} [options.limitInputPixels=268402689] - Do not process input images where the number of pixels * (width x height) exceeds this limit. Assumes image dimensions contained in the input metadata can be trusted. * An integral Number of pixels, zero or false to remove limit, true to use default limit of 268402689 (0x3FFF x 0x3FFF). * @param {boolean} [options.sequentialRead=false] - Set this to `true` to use sequential rather than random access where possible. * This can reduce memory usage and might improve performance on some systems. * @param {number} [options.density=72] - number representing the DPI for vector images in the range 1 to 100000. * @param {number} [options.pages=1] - number of pages to extract for multi-page input (GIF, TIFF, PDF), use -1 for all pages. * @param {number} [options.page=0] - page number to start extracting from for multi-page input (GIF, TIFF, PDF), zero based. * @param {number} [options.level=0] - level to extract from a multi-level input (OpenSlide), zero based. * @param {boolean} [options.animated=false] - Set to `true` to read all frames/pages of an animated image (equivalent of setting `pages` to `-1`). * @param {Object} [options.raw] - describes raw pixel input image data. See `raw()` for pixel ordering. * @param {number} [options.raw.width] * @param {number} [options.raw.height] * @param {number} [options.raw.channels] - 1-4 * @param {Object} [options.create] - describes a new image to be created. * @param {number} [options.create.width] * @param {number} [options.create.height] * @param {number} [options.create.channels] - 3-4 * @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. * @returns {Sharp} * @throws {Error} Invalid parameters */ const Sharp = function (input, options) { if (arguments.length === 1 && !is.defined(input)) { throw new Error('Invalid input'); } if (!(this instanceof Sharp)) { return new Sharp(input, options); } stream.Duplex.call(this); this.options = { // resize options topOffsetPre: -1, leftOffsetPre: -1, widthPre: -1, heightPre: -1, topOffsetPost: -1, leftOffsetPost: -1, widthPost: -1, heightPost: -1, width: -1, height: -1, canvas: 'crop', position: 0, resizeBackground: [0, 0, 0, 255], useExifOrientation: false, angle: 0, rotationAngle: 0, rotationBackground: [0, 0, 0, 255], rotateBeforePreExtract: false, flip: false, flop: false, extendTop: 0, extendBottom: 0, extendLeft: 0, extendRight: 0, extendBackground: [0, 0, 0, 255], withoutEnlargement: false, kernel: 'lanczos3', fastShrinkOnLoad: true, // operations tintA: 128, tintB: 128, flatten: false, flattenBackground: [0, 0, 0], negate: false, medianSize: 0, blurSigma: 0, sharpenSigma: 0, sharpenFlat: 1, sharpenJagged: 2, threshold: 0, thresholdGrayscale: true, trimThreshold: 0, gamma: 0, gammaOut: 0, greyscale: false, normalise: false, brightness: 1, saturation: 1, hue: 0, booleanBufferIn: null, booleanFileIn: '', joinChannelIn: [], extractChannel: -1, removeAlpha: false, ensureAlpha: false, colourspace: 'srgb', composite: [], // output fileOut: '', formatOut: 'input', streamOut: false, withMetadata: false, withMetadataOrientation: -1, withMetadataIcc: '', resolveWithObject: false, // output format jpegQuality: 80, jpegProgressive: false, jpegChromaSubsampling: '4:2:0', jpegTrellisQuantisation: false, jpegOvershootDeringing: false, jpegOptimiseScans: false, jpegOptimiseCoding: true, jpegQuantisationTable: 0, pngProgressive: false, pngCompressionLevel: 9, pngAdaptiveFiltering: false, pngPalette: false, pngQuality: 100, pngColours: 256, pngDither: 1, webpQuality: 80, webpAlphaQuality: 100, webpLossless: false, webpNearLossless: false, webpSmartSubsample: false, webpReductionEffort: 4, tiffQuality: 80, tiffCompression: 'jpeg', tiffPredictor: 'horizontal', tiffPyramid: false, tiffBitdepth: 8, tiffTile: false, tiffTileHeight: 256, tiffTileWidth: 256, tiffXres: 1.0, tiffYres: 1.0, heifQuality: 80, heifLossless: false, heifCompression: 'hevc', tileSize: 256, tileOverlap: 0, tileContainer: 'fs', tileLayout: 'dz', tileFormat: 'last', tileDepth: 'last', tileAngle: 0, tileSkipBlanks: -1, tileBackground: [255, 255, 255, 255], tileCentre: false, linearA: 1, linearB: 0, // Function to notify of libvips warnings debuglog: warning => { this.emit('warning', warning); debuglog(warning); }, // Function to notify of queue length changes queueListener: function (queueLength) { Sharp.queue.emit('change', queueLength); } }; this.options.input = this._createInputDescriptor(input, options, { allowStream: true }); return this; }; util.inherits(Sharp, stream.Duplex); /** * Take a "snapshot" of the Sharp instance, returning a new instance. * Cloned instances inherit the input of their parent instance. * This allows multiple output Streams and therefore multiple processing pipelines to share a single input Stream. * * @example * const pipeline = sharp().rotate(); * pipeline.clone().resize(800, 600).pipe(firstWritableStream); * pipeline.clone().extract({ left: 20, top: 20, width: 100, height: 100 }).pipe(secondWritableStream); * readableStream.pipe(pipeline); * // firstWritableStream receives auto-rotated, resized readableStream * // secondWritableStream receives auto-rotated, extracted region of readableStream * * @example * // Create a pipeline that will download an image, resize it and format it to different files * // Using Promises to know when the pipeline is complete * const fs = require("fs"); * const got = require("got"); * const sharpStream = sharp({ * failOnError: false * }); * * const promises = []; * * promises.push( * sharpStream * .clone() * .jpeg({ quality: 100 }) * .toFile("originalFile.jpg") * ); * * promises.push( * sharpStream * .clone() * .resize({ width: 500 }) * .jpeg({ quality: 80 }) * .toFile("optimized-500.jpg") * ); * * promises.push( * sharpStream * .clone() * .resize({ width: 500 }) * .webp({ quality: 80 }) * .toFile("optimized-500.webp") * ); * * // https://github.com/sindresorhus/got#gotstreamurl-options * got.stream("https://www.example.com/some-file.jpg").pipe(sharpStream); * * Promise.all(promises) * .then(res => { console.log("Done!", res); }) * .catch(err => { * console.error("Error processing files, let's clean it up", err); * try { * fs.unlinkSync("originalFile.jpg"); * fs.unlinkSync("optimized-500.jpg"); * fs.unlinkSync("optimized-500.webp"); * } catch (e) {} * }); * * @returns {Sharp} */ function clone () { // Clone existing options const clone = this.constructor.call(); clone.options = Object.assign({}, this.options); // Pass 'finish' event to clone for Stream-based input if (this._isStreamInput()) { this.on('finish', () => { // Clone inherits input data this._flattenBufferIn(); clone.options.bufferIn = this.options.bufferIn; clone.emit('finish'); }); } return clone; } Object.assign(Sharp.prototype, { clone }); /** * Export constructor. * @private */ module.exports = Sharp;