diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e377c706..36a55084 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -62,6 +62,17 @@ A method to be removed should be deprecated in the next major version then remov By way of example, the [bilinearInterpolation method](https://github.com/lovell/sharp/blob/v0.6.0/index.js#L155) present in v0.5.0 was deprecated in v0.6.0 and removed in v0.7.0. +## Documentation + +The public API is documented with [JSDoc](http://usejsdoc.org/) annotated comments. +These can be converted to Markdown by running: + +```sh +npm run docs +``` + +Please include documentation updates in any Pull Request that modifies the public API. + ## Run the tests ### Functional tests and static code analysis diff --git a/docs/api-channel.md b/docs/api-channel.md new file mode 100644 index 00000000..37c39980 --- /dev/null +++ b/docs/api-channel.md @@ -0,0 +1,72 @@ + + +# extractChannel + +Extract a single channel from a multi-channel image. + +**Parameters** + +- `channel` **([Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number) \| [String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String))** zero-indexed band number to extract, or `red`, `green` or `blue` as alternative to `0`, `1` or `2` respectively. + +**Examples** + +```javascript +sharp(input) + .extractChannel('green') + .toFile('input_green.jpg', function(err, info) { + // info.channels === 1 + // input_green.jpg contains the green channel of the input image + }); +``` + +- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid channel + +Returns **Sharp** + +# joinChannel + +Join one or more channels to the image. +The meaning of the added channels depends on the output colourspace, set with `toColourspace()`. +By default the output image will be web-friendly sRGB, with additional channels interpreted as alpha channels. +Channel ordering follows vips convention: + +- sRGB: 0: Red, 1: Green, 2: Blue, 3: Alpha. +- CMYK: 0: Magenta, 1: Cyan, 2: Yellow, 3: Black, 4: Alpha. + +Buffers may be any of the image formats supported by sharp: JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data. +For raw pixel input, the `options` object should contain a `raw` attribute, which follows the format of the attribute of the same name in the `sharp()` constructor. + +**Parameters** + +- `images` **([Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) \| [String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) \| [Buffer](https://nodejs.org/api/buffer.html))** one or more images (file paths, Buffers). +- `Object` image options, see `sharp()` constructor. +- `options` + + +- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters + +Returns **Sharp** + +# bandbool + +Perform a bitwise boolean operation on all input image channels (bands) to produce a single channel output image. + +**Parameters** + +- `boolOp` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively. + +**Examples** + +```javascript +sharp('3-channel-rgb-input.png') + .bandbool(sharp.bool.and) + .toFile('1-channel-output.png', function (err, info) { + // The output will be a single channel image where each pixel `P = R & G & B`. + // If `I(1,1) = [247, 170, 14] = [0b11110111, 0b10101010, 0b00001111]` + // then `O(1,1) = 0b11110111 & 0b10101010 & 0b00001111 = 0b00000010 = 2`. + }); +``` + +- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters + +Returns **Sharp** diff --git a/docs/api-colour.md b/docs/api-colour.md new file mode 100644 index 00000000..02948179 --- /dev/null +++ b/docs/api-colour.md @@ -0,0 +1,71 @@ + + +# background + +Set the background for the `embed`, `flatten` and `extend` operations. +The default background is `{r: 0, g: 0, b: 0, a: 1}`, black without transparency. + +Delegates to the _color_ module, which can throw an Error +but is liberal in what it accepts, clipping values to sensible min/max. +The alpha value is a float between `0` (transparent) and `1` (opaque). + +**Parameters** + +- `rgba` **([String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) \| [Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object))** parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha. + + +- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameter + +Returns **Sharp** + +# greyscale + +Convert to 8-bit greyscale; 256 shades of grey. +This is a linear operation. If the input image is in a non-linear colour space such as sRGB, use `gamma()` with `greyscale()` for the best results. +By default the output image will be web-friendly sRGB and contain three (identical) color channels. +This may be overridden by other sharp operations such as `toColourspace('b-w')`, +which will produce an output image containing one color channel. +An alpha channel may be present, and will be unchanged by the operation. + +**Parameters** + +- `greyscale` **\[[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)]** (optional, default `true`) + +Returns **Sharp** + +# grayscale + +Alternative spelling of `greyscale`. + +**Parameters** + +- `grayscale` **\[[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)]** (optional, default `true`) + +Returns **Sharp** + +# toColourspace + +Set the output colourspace. +By default output image will be web-friendly sRGB, with additional channels interpreted as alpha channels. + +**Parameters** + +- `colourspace` **\[[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)]** output colourspace e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://github.com/jcupitt/libvips/blob/master/libvips/iofuncs/enumtypes.c#L568) + + +- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters + +Returns **Sharp** + +# toColorspace + +Alternative spelling of `toColourspace`. + +**Parameters** + +- `colorspace` **\[[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)]** output colorspace. + + +- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters + +Returns **Sharp** diff --git a/docs/api-composite.md b/docs/api-composite.md new file mode 100644 index 00000000..e4481273 --- /dev/null +++ b/docs/api-composite.md @@ -0,0 +1,47 @@ + + +# overlayWith + +Overlay (composite) an image over the processed (resized, extracted etc.) image. + +The overlay image must be the same size or smaller than the processed image. +If both `top` and `left` options are provided, they take precedence over `gravity`. + +**Parameters** + +- `overlay` **([Buffer](https://nodejs.org/api/buffer.html) \| [String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String))** Buffer containing image data or String containing the path to an image file. +- `options` **\[[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)]** + - `options.gravity` **\[[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)]** gravity at which to place the overlay. (optional, default `'centre'`) + - `options.top` **\[[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)]** the pixel offset from the top edge. + - `options.left` **\[[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)]** the pixel offset from the left edge. + - `options.tile` **\[[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)]** set to true to repeat the overlay image across the entire image with the given `gravity`. (optional, default `false`) + - `options.cutout` **\[[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)]** set to true to apply only the alpha channel of the overlay image to the input image, giving the appearance of one image being cut out of another. (optional, default `false`) + - `options.raw` **\[[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)]** describes overlay when using raw pixel data. + - `options.raw.width` **\[[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)]** + - `options.raw.height` **\[[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)]** + - `options.raw.channels` **\[[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)]** + +**Examples** + +```javascript +sharp('input.png') + .rotate(180) + .resize(300) + .flatten() + .background('#ff6600') + .overlayWith('overlay.png', { gravity: sharp.gravity.southeast } ) + .sharpen() + .withMetadata() + .quality(90) + .webp() + .toBuffer() + .then(function(outputBuffer) { + // outputBuffer contains upside down, 300px wide, alpha channel flattened + // onto orange background, composited with overlay.png with SE gravity, + // sharpened, with metadata, 90% quality WebP image data. Phew! + }); +``` + +- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters + +Returns **Sharp** diff --git a/docs/api-constructor.md b/docs/api-constructor.md new file mode 100644 index 00000000..42f25e72 --- /dev/null +++ b/docs/api-constructor.md @@ -0,0 +1,81 @@ + + +# + +**Parameters** + +- `input` **\[([Buffer](https://nodejs.org/api/buffer.html) \| [String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String))]** if present, can be + a Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or + a String containing the 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 null or undefined. +- `options` **\[[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)]** if present, is an Object with optional attributes. + - `options.density` **\[[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)]** integral number representing the DPI for vector images. (optional, default `72`) + - `options.raw` **\[[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)]** describes raw pixel image data. See `raw()` for pixel ordering. + - `options.raw.width` **\[[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)]** + - `options.raw.height` **\[[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)]** + - `options.raw.channels` **\[[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)]** + +**Examples** + +```javascript +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 + }); +``` + +```javascript +// 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); +``` + +- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters + +Returns **Sharp** + +# queue + +An EventEmitter that emits a `change` event when a task is either: + +- queued, waiting for _libuv_ to provide a worker thread +- complete + +**Examples** + +```javascript +sharp.queue.on('change', function(queueLength) { + console.log('Queue contains ' + queueLength + ' task(s)'); +}); +``` + +# format + +An Object containing nested boolean values representing the available input and output formats/methods. + +**Examples** + +```javascript +console.log(sharp.format()); +``` + +Returns **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** + +# versions + +An Object containing the version numbers of libvips and its dependencies. + +**Examples** + +```javascript +console.log(sharp.versions); +``` diff --git a/docs/api-input.md b/docs/api-input.md new file mode 100644 index 00000000..9f065dbf --- /dev/null +++ b/docs/api-input.md @@ -0,0 +1,86 @@ + + +# clone + +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. + +**Examples** + +```javascript +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 +``` + +Returns **Sharp** + +# metadata + +Fast access to image metadata without decoding any compressed image data. +A Promises/A+ promise is returned when `callback` is not provided. + +- `format`: Name of decoder used to decompress image data e.g. `jpeg`, `png`, `webp`, `gif`, `svg` +- `width`: Number of pixels wide +- `height`: Number of pixels high +- `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://github.com/jcupitt/libvips/blob/master/libvips/iofuncs/enumtypes.c#L568) +- `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK +- `density`: Number of pixels per inch (DPI), if present +- `hasProfile`: Boolean indicating the presence of an embedded ICC profile +- `hasAlpha`: Boolean indicating the presence of an alpha transparency channel +- `orientation`: Number value of the EXIF Orientation header, if present +- `exif`: Buffer containing raw EXIF data, if present +- `icc`: Buffer containing raw [ICC](https://www.npmjs.com/package/icc) profile data, if present + +**Parameters** + +- `callback` **\[[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)]** called with the arguments `(err, metadata)` + +**Examples** + +```javascript +const image = sharp(inputJpg); +image + .metadata() + .then(function(metadata) { + return image + .resize(Math.round(metadata.width / 2)) + .webp() + .toBuffer(); + }) + .then(function(data) { + // data contains a WebP image half the width and height of the original JPEG + }); +``` + +Returns **([Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) | Sharp)** + +# limitInputPixels + +Do not process input images where the number of pixels (width _ height) exceeds this limit. +Assumes image dimensions contained in the input metadata can be trusted. +The default limit is 268402689 (0x3FFF _ 0x3FFF) pixels. + +**Parameters** + +- `limit` **([Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number) \| [Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean))** an integral Number of pixels, zero or false to remove limit, true to use default limit. + + +- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid limit + +Returns **Sharp** + +# sequentialRead + +An advanced setting that switches the libvips access method to `VIPS_ACCESS_SEQUENTIAL`. +This will reduce memory usage and can improve performance on some systems. + +**Parameters** + +- `sequentialRead` **\[[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)]** (optional, default `true`) + +Returns **Sharp** diff --git a/docs/api-operation.md b/docs/api-operation.md new file mode 100644 index 00000000..aff9f37a --- /dev/null +++ b/docs/api-operation.md @@ -0,0 +1,301 @@ + + +# rotate + +Rotate the output image by either an explicit angle +or auto-orient based on the EXIF `Orientation` tag. + +Use this method without angle to determine the angle from EXIF data. +Mirroring is supported and may infer the use of a flip operation. + +The use of `rotate` implies the removal of the EXIF `Orientation` tag, if any. + +Method order is important when both rotating and extracting regions, +for example `rotate(x).extract(y)` will produce a different result to `extract(y).rotate(x)`. + +**Parameters** + +- `angle` **\[[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)]** 0, 90, 180 or 270. (optional, default `auto`) + +**Examples** + +```javascript +const pipeline = sharp() + .rotate() + .resize(null, 200) + .toBuffer(function (err, outputBuffer, info) { + // outputBuffer contains 200px high JPEG image data, + // auto-rotated using EXIF Orientation tag + // info.width and info.height contain the dimensions of the resized image + }); +readableStream.pipe(pipeline); +``` + +- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters + +Returns **Sharp** + +# extract + +Extract a region of the image. + +- Use `extract` before `resize` for pre-resize extraction. +- Use `extract` after `resize` for post-resize extraction. +- Use `extract` before and after for both. + +**Parameters** + +- `options` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** + - `options.left` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** zero-indexed offset from left edge + - `options.top` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** zero-indexed offset from top edge + - `options.width` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** dimension of extracted image + - `options.height` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** dimension of extracted image + +**Examples** + +```javascript +sharp(input) + .extract({ left: left, top: top, width: width, height: height }) + .toFile(output, function(err) { + // Extract a region of the input image, saving in the same format. + }); +``` + +```javascript +sharp(input) + .extract({ left: leftOffsetPre, top: topOffsetPre, width: widthPre, height: heightPre }) + .resize(width, height) + .extract({ left: leftOffsetPost, top: topOffsetPost, width: widthPost, height: heightPost }) + .toFile(output, function(err) { + // Extract a region, resize, then extract from the resized image + }); +``` + +- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters + +Returns **Sharp** + +# flip + +Flip the image about the vertical Y axis. This always occurs after rotation, if any. +The use of `flip` implies the removal of the EXIF `Orientation` tag, if any. + +**Parameters** + +- `flip` **\[[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)]** (optional, default `true`) + +Returns **Sharp** + +# flop + +Flop the image about the horizontal X axis. This always occurs after rotation, if any. +The use of `flop` implies the removal of the EXIF `Orientation` tag, if any. + +**Parameters** + +- `flop` **\[[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)]** (optional, default `true`) + +Returns **Sharp** + +# sharpen + +Sharpen the image. +When used without parameters, performs a fast, mild sharpen of the output image. +When a `sigma` is provided, performs a slower, more accurate sharpen of the L channel in the LAB colour space. +Separate control over the level of sharpening in "flat" and "jagged" areas is available. + +**Parameters** + +- `sigma` **\[[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)]** the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`. +- `flat` **\[[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)]** the level of sharpening to apply to "flat" areas. (optional, default `1.0`) +- `jagged` **\[[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)]** the level of sharpening to apply to "jagged" areas. (optional, default `2.0`) + + +- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters + +Returns **Sharp** + +# blur + +Blur the image. +When used without parameters, performs a fast, mild blur of the output image. +When a `sigma` is provided, performs a slower, more accurate Gaussian blur. + +**Parameters** + +- `sigma` **\[[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)]** a value between 0.3 and 1000 representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`. + + +- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters + +Returns **Sharp** + +# extend + +Extends/pads the edges of the image with the colour provided to the `background` method. +This operation will always occur after resizing and extraction, if any. + +**Parameters** + +- `extend` **([Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number) \| [Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object))** single pixel count to add to all edges or an Object with per-edge counts + - `extend.top` **\[[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)]** + - `extend.left` **\[[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)]** + - `extend.bottom` **\[[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)]** + - `extend.right` **\[[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)]** + +**Examples** + +```javascript +// Resize to 140 pixels wide, then add 10 transparent pixels +// to the top, left and right edges and 20 to the bottom edge +sharp(input) + .resize(140) + .background({r: 0, g: 0, b: 0, a: 0}) + .extend({top: 10, bottom: 20, left: 10, right: 10}) + ... +``` + +- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters + +Returns **Sharp** + +# flatten + +Merge alpha transparency channel, if any, with `background`. + +**Parameters** + +- `flatten` **\[[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)]** (optional, default `true`) + +Returns **Sharp** + +# trim + +Trim "boring" pixels from all edges that contain values within a percentage similarity of the top-left pixel. + +**Parameters** + +- `tolerance` **\[[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)]** value between 1 and 99 representing the percentage similarity. (optional, default `10`) + + +- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters + +Returns **Sharp** + +# gamma + +Apply a gamma correction by reducing the encoding (darken) pre-resize at a factor of `1/gamma` +then increasing the encoding (brighten) post-resize at a factor of `gamma`. +This can improve the perceived brightness of a resized image in non-linear colour spaces. +JPEG and WebP input images will not take advantage of the shrink-on-load performance optimisation +when applying a gamma correction. + +**Parameters** + +- `gamma` **\[[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)]** value between 1.0 and 3.0. (optional, default `2.2`) + + +- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters + +Returns **Sharp** + +# negate + +Produce the "negative" of the image. + +**Parameters** + +- `negate` **\[[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)]** (optional, default `true`) + +Returns **Sharp** + +# normalise + +Enhance output image contrast by stretching its luminance to cover the full dynamic range. + +**Parameters** + +- `normalise` **\[[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)]** (optional, default `true`) + +Returns **Sharp** + +# normalize + +Alternative spelling of normalise. + +**Parameters** + +- `normalize` **\[[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)]** (optional, default `true`) + +Returns **Sharp** + +# convolve + +Convolve the image with the specified kernel. + +**Parameters** + +- `kernel` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** + - `kernel.width` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** width of the kernel in pixels. + - `kernel.height` **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** width of the kernel in pixels. + - `kernel.kernel` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)** Array of length `width*height` containing the kernel values. + - `kernel.scale` **\[[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)]** the scale of the kernel in pixels. (optional, default `sum`) + - `kernel.offset` **\[[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)]** the offset of the kernel in pixels. (optional, default `0`) + +**Examples** + +```javascript +sharp(input) + .convolve({ + width: 3, + height: 3, + kernel: [-1, 0, 1, -2, 0, 2, -1, 0, 1] + }) + .raw() + .toBuffer(function(err, data, info) { + // data contains the raw pixel data representing the convolution + // of the input image with the horizontal Sobel operator + }); +``` + +- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters + +Returns **Sharp** + +# threshold + +Any pixel value greather than or equal to the threshold value will be set to 255, otherwise it will be set to 0. + +**Parameters** + +- `threshold` **\[[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)]** a value in the range 0-255 representing the level at which the threshold will be applied. (optional, default `128`) +- `options` **\[[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)]** + - `options.greyscale` **\[[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)]** convert to single channel greyscale. (optional, default `true`) + - `options.grayscale` **\[[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)]** alternative spelling for greyscale. (optional, default `true`) + + +- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters + +Returns **Sharp** + +# boolean + +Perform a bitwise boolean operation with operand image. + +This operation creates an output image where each pixel is the result of +the selected bitwise boolean `operation` between the corresponding pixels of the input images. + +**Parameters** + +- `operand` **([Buffer](https://nodejs.org/api/buffer.html) \| [String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String))** Buffer containing image data or String containing the path to an image file. +- `operator` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively. +- `options` **\[[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)]** + - `options.raw` **\[[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)]** describes operand when using raw pixel data. + - `options.raw.width` **\[[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)]** + - `options.raw.height` **\[[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)]** + - `options.raw.channels` **\[[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)]** + + +- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters + +Returns **Sharp** diff --git a/docs/api-output.md b/docs/api-output.md new file mode 100644 index 00000000..a29d5119 --- /dev/null +++ b/docs/api-output.md @@ -0,0 +1,172 @@ + + +# toFile + +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. + +A Promises/A+ promise is returned when `callback` is not provided. + +**Parameters** + +- `fileOut` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** the path to write the image data to. +- `callback` **\[[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)]** called on completion with two arguments `(err, info)`. + `info` contains the output image `format`, `size` (bytes), `width`, `height` and `channels`. + + +- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters + +Returns **[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)** when no callback is provided + +# toBuffer + +Write output to a Buffer. +By default, the format will match the input image. JPEG, PNG, WebP, and RAW are supported. +`callback`, if present, gets three arguments `(err, buffer, info)` where: + +- `err` is an error message, if any. +- `buffer` is the output image data. +- `info` contains the output image `format`, `size` (bytes), `width`, `height` and `channels`. + A Promises/A+ promise is returned when `callback` is not provided. + +**Parameters** + +- `callback` **\[[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)]** + +Returns **[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)** when no callback is provided + +# withMetadata + +Include all metadata (EXIF, XMP, IPTC) from the input image in the output image. +The default behaviour, when `withMetadata` is not used, is to strip all metadata and convert to the device-independent sRGB colour space. +This will also convert to and add a web-friendly sRGB ICC profile. + +**Parameters** + +- `withMetadata` **\[[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)]** + - `withMetadata.orientation` **\[[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)]** value between 1 and 8, used to update the EXIF `Orientation` tag. + + +- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters + +Returns **Sharp** + +# jpeg + +Use these JPEG options for output image. + +**Parameters** + +- `options` **\[[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)]** output options + - `options.quality` **\[[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)]** quality, integer 1-100 (optional, default `80`) + - `options.progressive` **\[[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)]** use progressive (interlace) scan (optional, default `false`) + - `options.chromaSubsampling` **\[[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)]** set to '4:4:4' to prevent chroma subsampling when quality <= 90 (optional, default `'4:2:0'`) + - `options.force` **\[[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)]** force JPEG output, otherwise attempt to use input format (optional, default `true`) +- `trellisQuantisation` **\[[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)]** apply trellis quantisation, requires mozjpeg (optional, default `false`) +- `overshootDeringing` **\[[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)]** apply overshoot deringing, requires mozjpeg (optional, default `false`) +- `optimiseScans` **\[[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)]** optimise progressive scans, forces progressive, requires mozjpeg (optional, default `false`) + + +- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid options + +Returns **Sharp** + +# png + +Use these PNG options for output image. + +**Parameters** + +- `options` **\[[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)]** + - `options.progressive` **\[[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)]** use progressive (interlace) scan (optional, default `false`) + - `options.compressionLevel` **\[[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)]** zlib compression level (optional, default `6`) + - `options.adaptiveFiltering` **\[[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)]** use adaptive row filtering (optional, default `true`) + - `options.force` **\[[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)]** force PNG output, otherwise attempt to use input format (optional, default `true`) + + +- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid options + +Returns **Sharp** + +# webp + +Use these WebP options for output image. + +**Parameters** + +- `options` **\[[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)]** output options + - `options.quality` **\[[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)]** quality, integer 1-100 (optional, default `80`) + - `options.force` **\[[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)]** force WebP output, otherwise attempt to use input format (optional, default `true`) + + +- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid options + +Returns **Sharp** + +# tiff + +Use these TIFF options for output image. + +**Parameters** + +- `options` **\[[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)]** output options + - `options.quality` **\[[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)]** quality, integer 1-100 (optional, default `80`) + - `options.force` **\[[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)]** force TIFF output, otherwise attempt to use input format (optional, default `true`) + + +- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid options + +Returns **Sharp** + +# raw + +Force output to be raw, uncompressed uint8 pixel data. + +Returns **Sharp** + +# toFormat + +Force output to a given format. + +**Parameters** + +- `format` **([String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) \| [Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object))** as a String or an Object with an 'id' attribute +- `options` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** output options + + +- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** unsupported format or options + +Returns **Sharp** + +# tile + +Use tile-based deep zoom (image pyramid) output. +You can also use a `.zip` or `.szi` file extension with `toFile` to write to a compressed archive file format. + +**Parameters** + +- `tile` **\[[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)]** + - `tile.size` **\[[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)]** tile size in pixels, a value between 1 and 8192. (optional, default `256`) + - `tile.overlap` **\[[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)]** tile overlap in pixels, a value between 0 and 8192. (optional, default `0`) + - `tile.container` **\[[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)]** tile container, with value `fs` (filesystem) or `zip` (compressed file). (optional, default `'fs'`) + - `tile.layout` **\[[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)]** filesystem layout, possible values are `dz`, `zoomify` or `google`. (optional, default `'dz'`) + +**Examples** + +```javascript +sharp('input.tiff') + .tile({ + size: 512 + }) + .toFile('output.dzi', function(err, info) { + // output.dzi is the Deep Zoom XML definition + // output_files contains 512x512 tiles grouped by zoom level + }); +``` + +- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters + +Returns **Sharp** diff --git a/docs/api-resize.md b/docs/api-resize.md new file mode 100644 index 00000000..f69e45e1 --- /dev/null +++ b/docs/api-resize.md @@ -0,0 +1,164 @@ + + +# resize + +Resize image to `width` x `height`. +By default, the resized image is centre cropped to the exact size specified. + +Possible reduction kernels are: + +- `cubic`: Use a [Catmull-Rom spline](https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline). +- `lanczos2`: Use a [Lanczos kernel](https://en.wikipedia.org/wiki/Lanczos_resampling#Lanczos_kernel) with `a=2`. +- `lanczos3`: Use a Lanczos kernel with `a=3` (the default). + +Possible enlargement interpolators are: + +- `nearest`: Use [nearest neighbour interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation). +- `bilinear`: Use [bilinear interpolation](http://en.wikipedia.org/wiki/Bilinear_interpolation), faster than bicubic but with less smooth results. +- `vertexSplitQuadraticBasisSpline`: Use the smoother [VSQBS interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/vsqbs.cpp#L48) to prevent "staircasing" when enlarging. +- `bicubic`: Use [bicubic interpolation](http://en.wikipedia.org/wiki/Bicubic_interpolation) (the default). +- `locallyBoundedBicubic`: Use [LBB interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/lbb.cpp#L100), which prevents some "[acutance](http://en.wikipedia.org/wiki/Acutance)" but typically reduces performance by a factor of 2. +- `nohalo`: Use [Nohalo interpolation](http://eprints.soton.ac.uk/268086/), which prevents acutance but typically reduces performance by a factor of 3. + +**Parameters** + +- `width` **\[[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)]** pixels wide the resultant image should be, between 1 and 16383 (0x3FFF). Use `null` or `undefined` to auto-scale the width to match the height. +- `height` **\[[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)]** pixels high the resultant image should be, between 1 and 16383. Use `null` or `undefined` to auto-scale the height to match the width. +- `options` **\[[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)]** + - `options.kernel` **\[[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)]** the kernel to use for image reduction. (optional, default `'lanczos3'`) + - `options.interpolator` **\[[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)]** the interpolator to use for image enlargement. (optional, default `'bicubic'`) + +**Examples** + +```javascript +sharp(inputBuffer) + .resize(200, 300, { + kernel: sharp.kernel.lanczos2, + interpolator: sharp.interpolator.nohalo + }) + .background('white') + .embed() + .toFile('output.tiff') + .then(function() { + // output.tiff is a 200 pixels wide and 300 pixels high image + // containing a lanczos2/nohalo scaled version, embedded on a white canvas, + // of the image data in inputBuffer + }); +``` + +- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters + +Returns **Sharp** + +# crop + +Crop the resized image to the exact size specified, the default behaviour. + +Possible attributes of the optional `sharp.gravity` are `north`, `northeast`, `east`, `southeast`, `south`, +`southwest`, `west`, `northwest`, `center` and `centre`. + +The experimental strategy-based approach resizes so one dimension is at its target length +then repeatedly ranks edge regions, discarding the edge with the lowest score based on the selected strategy. + +- `entropy`: focus on the region with the highest [Shannon entropy](https://en.wikipedia.org/wiki/Entropy_%28information_theory%29). +- `attention`: focus on the region with the highest luminance frequency, colour saturation and presence of skin tones. + +**Parameters** + +- `crop` **\[[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)]** A member of `sharp.gravity` to crop to an edge/corner or `sharp.strategy` to crop dynamically. (optional, default `'centre'`) + +**Examples** + +```javascript +const transformer = sharp() + .resize(200, 200) + .crop(sharp.strategy.entropy) + .on('error', function(err) { + console.log(err); + }); +// Read image data from readableStream +// Write 200px square auto-cropped image data to writableStream +readableStream.pipe(transformer).pipe(writableStream); +``` + +- Throws **[Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error)** Invalid parameters + +Returns **Sharp** + +# embed + +Preserving aspect ratio, resize the image to the maximum `width` or `height` specified +then embed on a background of the exact `width` and `height` specified. + +If the background contains an alpha value then WebP and PNG format output images will +contain an alpha channel, even when the input image does not. + +**Examples** + +```javascript +sharp('input.gif') + .resize(200, 300) + .background({r: 0, g: 0, b: 0, a: 0}) + .embed() + .toFormat(sharp.format.webp) + .toBuffer(function(err, outputBuffer) { + if (err) { + throw err; + } + // outputBuffer contains WebP image data of a 200 pixels wide and 300 pixels high + // containing a scaled version, embedded on a transparent canvas, of input.gif + }); +``` + +Returns **Sharp** + +# max + +Preserving aspect ratio, resize the image to be as large as possible +while ensuring its dimensions are less than or equal to the `width` and `height` specified. + +Both `width` and `height` must be provided via `resize` otherwise the behaviour will default to `crop`. + +**Examples** + +```javascript +sharp(inputBuffer) + .resize(200, 200) + .max() + .toFormat('jpeg') + .toBuffer() + .then(function(outputBuffer) { + // outputBuffer contains JPEG image data no wider than 200 pixels and no higher + // than 200 pixels regardless of the inputBuffer image dimensions + }); +``` + +Returns **Sharp** + +# min + +Preserving aspect ratio, resize the image to be as small as possible +while ensuring its dimensions are greater than or equal to the `width` and `height` specified. + +Both `width` and `height` must be provided via `resize` otherwise the behaviour will default to `crop`. + +Returns **Sharp** + +# ignoreAspectRatio + +Ignoring the aspect ratio of the input, stretch the image to +the exact `width` and/or `height` provided via `resize`. + +Returns **Sharp** + +# withoutEnlargement + +Do not enlarge the output image if the input image width _or_ height are already less than the required dimensions. +This is equivalent to GraphicsMagick's `>` geometry option: +"_change the dimensions of the image only if its width or height exceeds the geometry specification_". + +**Parameters** + +- `withoutEnlargement` **\[[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)]** (optional, default `true`) + +Returns **Sharp** diff --git a/docs/api-utility.md b/docs/api-utility.md new file mode 100644 index 00000000..710d1fec --- /dev/null +++ b/docs/api-utility.md @@ -0,0 +1,100 @@ + + +# cache + +Gets, or when options are provided sets, the limits of _libvips'_ operation cache. +Existing entries in the cache will be trimmed after any change in limits. +This method always returns cache statistics, +useful for determining how much working memory is required for a particular task. + +**Parameters** + +- `Object` **([Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) \| [Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean))** with the following attributes, or Boolean where true uses default cache settings and false removes all caching. +- `options` + - `options.memory` **\[[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)]** is the maximum memory in MB to use for this cache (optional, default `50`) + - `options.files` **\[[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)]** is the maximum number of files to hold open (optional, default `20`) + - `options.items` **\[[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)]** is the maximum number of operations to cache (optional, default `100`) + +**Examples** + +```javascript +const stats = sharp.cache(); +``` + +```javascript +sharp.cache( { items: 200 } ); +sharp.cache( { files: 0 } ); +sharp.cache(false); +``` + +Returns **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** + +# concurrency + +Gets, or when a concurrency is provided sets, +the number of threads _libvips'_ should create to process each image. +The default value is the number of CPU cores. +A value of `0` will reset to this default. + +The maximum number of images that can be processed in parallel +is limited by libuv's `UV_THREADPOOL_SIZE` environment variable. + +This method always returns the current concurrency. + +**Parameters** + +- `concurrency` **\[[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)]** + +**Examples** + +```javascript +const threads = sharp.concurrency(); // 4 +sharp.concurrency(2); // 2 +sharp.concurrency(0); // 4 +``` + +Returns **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** concurrency + +# counters + +Provides access to internal task counters. + +- queue is the number of tasks this module has queued waiting for _libuv_ to provide a worker thread from its pool. +- process is the number of resize tasks currently being processed. + +**Examples** + +```javascript +const counters = sharp.counters(); // { queue: 2, process: 4 } +``` + +Returns **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** + +# simd + +Get and set use of SIMD vector unit instructions. +Requires libvips to have been compiled with liborc support. + +Improves the performance of `resize`, `blur` and `sharpen` operations +by taking advantage of the SIMD vector unit of the CPU, e.g. Intel SSE and ARM NEON. + +This feature is currently off by default but future versions may reverse this. +Versions of liborc prior to 0.4.25 are known to segfault under heavy load. + +**Parameters** + +- `simd` **\[[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)]** (optional, default `false`) + +**Examples** + +```javascript +const simd = sharp.simd(); +// simd is `true` if SIMD is currently enabled +``` + +```javascript +const simd = sharp.simd(true); +// attempts to enable the use of SIMD, returning true if available +``` + +Returns **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** diff --git a/docs/api.md b/docs/api.md deleted file mode 100644 index d83e4990..00000000 --- a/docs/api.md +++ /dev/null @@ -1,819 +0,0 @@ -# API - -```javascript -var sharp = require('sharp'); -``` - -### Input - -#### sharp([input], [options]) - -Constructor to which further methods are chained. - -`input`, if present, can be one of: - -* Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or -* String containing the 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 `input` is `null` or `undefined`. - -`options`, if present, is an Object with the following optional attributes: - -* `density` an integral number representing the DPI for vector images, defaulting to 72. -* `raw` an Object containing `width`, `height` and `channels` when providing uncompressed data. See `raw()` for pixel ordering. - -The object returned by the constructor implements the -[stream.Duplex](http://nodejs.org/api/stream.html#stream_class_stream_duplex) class. - -JPEG, PNG or WebP format image data can be streamed out from this object. -When using Stream based output, derived attributes are available from the `info` event. - -```javascript -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 - }); -``` - -```javascript -// 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); -``` - -#### metadata([callback]) - -Fast access to image metadata without decoding any compressed image data. - -`callback`, if present, gets the arguments `(err, metadata)` where `metadata` has the attributes: - -* `format`: Name of decoder used to decompress image data e.g. `jpeg`, `png`, `webp`, `gif`, `svg` -* `width`: Number of pixels wide -* `height`: Number of pixels high -* `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `scrgb`, `cmyk`, `lab`, `xyz`, `b-w` [...](https://github.com/jcupitt/libvips/blob/master/libvips/iofuncs/enumtypes.c#L568) -* `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK -* `density`: Number of pixels per inch (DPI), if present -* `hasProfile`: Boolean indicating the presence of an embedded ICC profile -* `hasAlpha`: Boolean indicating the presence of an alpha transparency channel -* `orientation`: Number value of the EXIF Orientation header, if present -* `exif`: Buffer containing raw EXIF data, if present -* `icc`: Buffer containing raw [ICC](https://www.npmjs.com/package/icc) profile data, if present - -A Promises/A+ promise is returned when `callback` is not provided. - -```javascript -var image = sharp(inputJpg); -image - .metadata() - .then(function(metadata) { - return image - .resize(Math.round(metadata.width / 2)) - .webp() - .toBuffer(); - }) - .then(function(data) { - // data contains a WebP image half the width and height of the original JPEG - }); -``` - -#### clone() - -Takes a "snapshot" of the 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. - -```javascript -var 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 -``` - -#### sequentialRead() - -An advanced setting that switches the libvips access method to `VIPS_ACCESS_SEQUENTIAL`. -This will reduce memory usage and can improve performance on some systems. - -#### limitInputPixels(pixels) - -Do not process input images where the number of pixels (width * height) exceeds this limit. - -`pixels` is either an integral Number of pixels, with a value between 1 and the default 268402689 (0x3FFF * 0x3FFF) or - a boolean. `false` will disable checking while `true` will revert to the default limit. - -### Resizing - -#### resize([width], [height], [options]) - -Scale output to `width` x `height`. By default, the resized image is cropped to the exact size specified. - -`width` is the integral Number of pixels wide the resultant image should be, between 1 and 16383 (0x3FFF). Use `null` or `undefined` to auto-scale the width to match the height. - -`height` is the integral Number of pixels high the resultant image should be, between 1 and 16383. Use `null` or `undefined` to auto-scale the height to match the width. - -`options` is an optional Object. If present, it can contain one or more of: - -* `options.kernel`, the kernel to use for image reduction, defaulting to `lanczos3`. -* `options.interpolator`, the interpolator to use for image enlargement, defaulting to `bicubic`. - -Possible kernels are: - -* `cubic`: Use a [Catmull-Rom spline](https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline). -* `lanczos2`: Use a [Lanczos kernel](https://en.wikipedia.org/wiki/Lanczos_resampling#Lanczos_kernel) with `a=2`. -* `lanczos3`: Use a Lanczos kernel with `a=3` (the default). - -Possible interpolators are: - -* `nearest`: Use [nearest neighbour interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation). -* `bilinear`: Use [bilinear interpolation](http://en.wikipedia.org/wiki/Bilinear_interpolation), faster than bicubic but with less smooth results. -* `vertexSplitQuadraticBasisSpline`: Use the smoother [VSQBS interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/vsqbs.cpp#L48) to prevent "staircasing" when enlarging. -* `bicubic`: Use [bicubic interpolation](http://en.wikipedia.org/wiki/Bicubic_interpolation) (the default). -* `locallyBoundedBicubic`: Use [LBB interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/lbb.cpp#L100), which prevents some "[acutance](http://en.wikipedia.org/wiki/Acutance)" but typically reduces performance by a factor of 2. -* `nohalo`: Use [Nohalo interpolation](http://eprints.soton.ac.uk/268086/), which prevents acutance but typically reduces performance by a factor of 3. - -```javascript -sharp(inputBuffer) - .resize(200, 300, { - kernel: sharp.kernel.lanczos2, - interpolator: sharp.interpolator.nohalo - }) - .background('white') - .embed() - .toFile('output.tiff') - .then(function() { - // output.tiff is a 200 pixels wide and 300 pixels high image - // containing a lanczos2/nohalo scaled version, embedded on a white canvas, - // of the image data in inputBuffer - }); -``` - -#### crop([option]) - -Crop the resized image to the exact size specified, the default behaviour. - -`option`, if present, is an attribute of: - -* `sharp.gravity` e.g. `sharp.gravity.north`, to crop to an edge or corner, or -* `sharp.strategy` e.g. `sharp.strategy.entropy`, to crop dynamically. - -Possible attributes of `sharp.gravity` are -`north`, `northeast`, `east`, `southeast`, `south`, -`southwest`, `west`, `northwest`, `center` and `centre`. - -The experimental strategy-based approach resizes so one dimension is at its target length -then repeatedly ranks edge regions, discarding the edge with the lowest score based on the selected strategy. - -* `entropy`: focus on the region with the highest [Shannon entropy](https://en.wikipedia.org/wiki/Entropy_%28information_theory%29). -* `attention`: focus on the region with the highest luminance frequency, colour saturation and presence of skin tones. - -The default crop option is a `center`/`centre` gravity. - -```javascript -var transformer = sharp() - .resize(200, 200) - .crop(sharp.strategy.entropy) - .on('error', function(err) { - console.log(err); - }); -// Read image data from readableStream -// Write 200px square auto-cropped image data to writableStream -readableStream.pipe(transformer).pipe(writableStream); -``` - -#### embed() - -Preserving aspect ratio, resize the image to the -maximum `width` or `height` specified -then embed on a background of the exact -`width` and `height` specified. - -If the background contains an alpha value -then WebP and PNG format output images will -contain an alpha channel, -even when the input image does not. - -```javascript -sharp('input.gif') - .resize(200, 300) - .background({r: 0, g: 0, b: 0, a: 0}) - .embed() - .toFormat(sharp.format.webp) - .toBuffer(function(err, outputBuffer) { - if (err) { - throw err; - } - // outputBuffer contains WebP image data of a 200 pixels wide and 300 pixels high - // containing a scaled version, embedded on a transparent canvas, of input.gif - }); -``` - -#### max() - -Preserving aspect ratio, -resize the image to be as large as possible -while ensuring its dimensions are less than or equal to -the `width` and `height` specified. - -Both `width` and `height` must be provided via -`resize` otherwise the behaviour will default to `crop`. - -```javascript -sharp(inputBuffer) - .resize(200, 200) - .max() - .toFormat('jpeg') - .toBuffer() - .then(function(outputBuffer) { - // outputBuffer contains JPEG image data no wider than 200 pixels and no higher - // than 200 pixels regardless of the inputBuffer image dimensions - }); -``` - -#### min() - -Preserving aspect ratio, -resize the image to be as small as possible -while ensuring its dimensions are greater than or equal to -the `width` and `height` specified. - -Both `width` and `height` must be provided via `resize` otherwise the behaviour will default to `crop`. - -#### withoutEnlargement() - -Do not enlarge the output image -if the input image width *or* height -are already less than the required dimensions. - -This is equivalent to GraphicsMagick's `>` geometry option: -"*change the dimensions of the image only -if its width or height exceeds the geometry specification*". - -#### ignoreAspectRatio() - -Ignoring the aspect ratio of the input, stretch the image to the exact `width` and/or `height` provided via `resize`. - -### Operations - -#### extract({ left: left, top: top, width: width, height: height }) - -Extract a region of the image. Can be used with or without a `resize` operation. - -`left` and `top` are the offset, in pixels, from the top-left corner. - -`width` and `height` are the dimensions of the extracted image. - -Use `extract` before `resize` for pre-resize extraction. Use `extract` after `resize` for post-resize extraction. Use `extract` before and after for both. - -```javascript -sharp(input) - .extract({ left: left, top: top, width: width, height: height }) - .toFile(output, function(err) { - // Extract a region of the input image, saving in the same format. - }); -``` - -```javascript -sharp(input) - .extract({ left: leftOffsetPre, top: topOffsetPre, width: widthPre, height: heightPre }) - .resize(width, height) - .extract({ left: leftOffsetPost, top: topOffsetPost, width: widthPost, height: heightPost }) - .toFile(output, function(err) { - // Extract a region, resize, then extract from the resized image - }); -``` - -#### trim([tolerance]) - -Trim "boring" pixels from all edges that contain values within a percentage similarity of the top-left pixel. - -* `tolerance`, if present, is an integral Number between 1 and 99 representing the percentage similarity, defaulting to 10. - -#### background(rgba) - -Set the background for the `embed`, `flatten` and `extend` operations. - -`rgba` is parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha. - -The alpha value is a float between `0` (transparent) and `1` (opaque). - -The default background is `{r: 0, g: 0, b: 0, a: 1}`, black without transparency. - -#### flatten() - -Merge alpha transparency channel, if any, with `background`. - -#### extend(extension) - -Extends/pads the edges of the image with `background`, where `extension` is one of: - -* a Number representing the pixel count to add to each edge, or -* an Object containing `top`, `left`, `bottom` and `right` attributes, each a Number of pixels to add to that edge. - -This operation will always occur after resizing and extraction, if any. - -```javascript -// Resize to 140 pixels wide, then add 10 transparent pixels -// to the top, left and right edges and 20 to the bottom edge -sharp(input) - .resize(140) - .background({r: 0, g: 0, b: 0, a: 0}) - .extend({top: 10, bottom: 20, left: 10, right: 10}) - ... -``` - -#### negate() - -Produces the "negative" of the image. White => Black, Black => White, Blue => Yellow, etc. - -#### rotate([angle]) - -Rotate the output image by either an explicit angle or auto-orient based on the EXIF `Orientation` tag. - -`angle`, if present, is a Number with a value of `0`, `90`, `180` or `270`. - -Use this method without `angle` to determine the angle from EXIF data. Mirroring is supported and may infer the use of a `flip` operation. - -Method order is important when both rotating and extracting regions, for example `rotate(x).extract(y)` will produce a different result to `extract(y).rotate(x)`. - -The use of `rotate` implies the removal of the EXIF `Orientation` tag, if any. - -```javascript -var pipeline = sharp() - .rotate() - .resize(null, 200) - .progressive() - .toBuffer(function(err, outputBuffer, info) { - if (err) { - throw err; - } - // outputBuffer contains 200px high progressive JPEG image data, - // auto-rotated using EXIF Orientation tag - // info.width and info.height contain the dimensions of the resized image - }); -readableStream.pipe(pipeline); -``` - -#### flip() - -Flip the image about the vertical Y axis. This always occurs after rotation, if any. -The use of `flip` implies the removal of the EXIF `Orientation` tag, if any. - -#### flop() - -Flop the image about the horizontal X axis. This always occurs after rotation, if any. -The use of `flop` implies the removal of the EXIF `Orientation` tag, if any. - -#### blur([sigma]) - -When used without parameters, performs a fast, mild blur of the output image. This typically reduces performance by 10%. - -When a `sigma` is provided, performs a slower, more accurate Gaussian blur. This typically reduces performance by 25%. - -* `sigma`, if present, is a Number between 0.3 and 1000 representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`. - -#### convolve(kernel) - -Convolve the image with the specified `kernel`, an Object with the following attributes: - -* `width` is an integral Number representing the width of the kernel in pixels. -* `height` is an integral Number representing the width of the kernel in pixels. -* `kernel` is an Array of length `width*height` containing the kernel values. -* `scale`, if present, is a Number representing the scale of the kernel in pixels, defaulting to the sum of the kernel's values. -* `offset`, if present, is a Number representing the offset of the kernel in pixels, defaulting to 0. - -```javascript -sharp(input) - .convolve({ - width: 3, - height: 3, - kernel: [-1, 0, 1, -2, 0, 2, -1, 0, 1] - }) - .raw() - .toBuffer(function(err, data, info) { - // data contains the raw pixel data representing the convolution - // of the input image with the horizontal Sobel operator - }); -``` - -#### sharpen([sigma], [flat], [jagged]) - -When used without parameters, performs a fast, mild sharpen of the output image. This typically reduces performance by 10%. - -When a `sigma` is provided, performs a slower, more accurate sharpen of the L channel in the LAB colour space. Separate control over the level of sharpening in "flat" and "jagged" areas is available. This typically reduces performance by 50%. - -* `sigma`, if present, is a Number representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`. -* `flat`, if present, is a Number representing the level of sharpening to apply to "flat" areas, defaulting to a value of 1.0. -* `jagged`, if present, is a Number representing the level of sharpening to apply to "jagged" areas, defaulting to a value of 2.0. - -#### threshold([threshold], [options]) - -Any pixel value greather than or equal to the threshold value will be set to 255, otherwise it will be set to 0. -By default, the image will be converted to single channel greyscale before thresholding. - -* `threshold`, if present, is a Number between 0 and 255, representing the level at which the threshold will be applied. The default threshold is 128. -* `options`, if present, is an Object containing a Boolean `greyscale` (or `grayscale`). When `false` each channel will have the threshold applied independently. - -#### gamma([gamma]) - -Apply a gamma correction by reducing the encoding (darken) pre-resize at a factor of `1/gamma` then increasing the encoding (brighten) post-resize at a factor of `gamma`. - -`gamma`, if present, is a Number between 1 and 3. The default value is `2.2`, a suitable approximation for sRGB images. - -This can improve the perceived brightness of a resized image in non-linear colour spaces. - -JPEG input images will not take advantage of the shrink-on-load performance optimisation when applying a gamma correction. - -#### grayscale() / greyscale() - -Convert to 8-bit greyscale; 256 shades of grey. - -This is a linear operation. If the input image is in a non-linear colour space such as sRGB, use `gamma()` with `greyscale()` for the best results. - -By default the output image will be web-friendly sRGB and contain three (identical) color channels. This may be overridden by other sharp operations such as `toColourspace('b-w')`, which will produce an output image containing one color channel. An alpha channel may be present, and will be unchanged by the operation. - -#### normalize() / normalise() - -Enhance output image contrast by stretching its luminance to cover the full dynamic range. This typically reduces performance by 30%. - -#### overlayWith(image, [options]) - -Overlay (composite) a image over the processed (resized, extracted etc.) image. - -`image` is one of the following, and must be the same size or smaller than the processed image: - -* Buffer containing image data, or -* String containing the path to an image file - -`options`, if present, is an Object with the following optional attributes: - -* `gravity` is a String or an attribute of the `sharp.gravity` Object e.g. `sharp.gravity.north` at which to place the overlay, defaulting to `center`/`centre`. -* `top` is an integral Number representing the pixel offset from the top edge. -* `left` is an integral Number representing the pixel offset from the left edge. -* `tile` is a Boolean, defaulting to `false`. When set to `true` repeats the overlay image across the entire image with the given `gravity`. -* `cutout` is a Boolean, defaulting to `false`. When set to `true` applies only the alpha channel of the overlay image to the image to be overlaid, giving the appearance of one image being cut out of another. -* `raw` an Object containing `width`, `height` and `channels` when providing uncompressed data. - -If both `top` and `left` are provided, they take precedence over `gravity`. - -```javascript -sharp('input.png') - .rotate(180) - .resize(300) - .flatten() - .background('#ff6600') - .overlayWith('overlay.png', { gravity: sharp.gravity.southeast } ) - .sharpen() - .withMetadata() - .quality(90) - .webp() - .toBuffer() - .then(function(outputBuffer) { - // outputBuffer contains upside down, 300px wide, alpha channel flattened - // onto orange background, composited with overlay.png with SE gravity, - // sharpened, with metadata, 90% quality WebP image data. Phew! - }); -``` - -#### toColourspace(colourspace) / toColorspace(colorspace) - -Set the output colourspace. By default output image will be web-friendly sRGB, with additional channels interpreted as alpha channels. - -`colourspace` is a string or `sharp.colourspace` enum that identifies an output colourspace. String arguments comprise vips colour space interpretation names e.g. `srgb`, `rgb`, `scrgb`, `cmyk`, `lab`, `xyz`, `b-w` [...](https://github.com/jcupitt/libvips/blob/master/libvips/iofuncs/enumtypes.c#L568) - -#### extractChannel(channel) - -Extract a single channel from a multi-channel image. - -`channel` is a zero-indexed integral Number representing the band number to extract. `red`, `green` or `blue` are also accepted as an alternative to `0`, `1` or `2` respectively. - -```javascript -sharp(input) - .extractChannel('green') - .toFile('input_green.jpg', function(err, info) { - // info.channels === 1 - // input_green.jpg contains the green channel of the input image - }); -``` - -#### joinChannel(channels, [options]) - -Join a data channel to the image. The meaning of the added channels depends on the output colourspace, set with `toColourspace()`. By default the output image will be web-friendly sRGB, with additional channels interpreted as alpha channels. - -`channels` is one of -* a single file path -* an array of file paths -* a single buffer -* an array of buffers - -Note that channel ordering follows vips convention: -* sRGB: 0: Red, 1: Green, 2: Blue, 3: Alpha -* CMYK: 0: Magenta, 1: Cyan, 2: Yellow, 3: Black, 4: Alpha - -Buffers may be any of the image formats supported by sharp: JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data. In the case of a RAW buffer, the `options` object should contain a `raw` attribute, which follows the format of the attribute of the same name in the `sharp()` constructor. See `sharp()` for details. See `raw()` for pixel ordering. - -#### bandbool(operation) - -Perform a bitwise boolean operation on all input image channels (bands) to produce a single channel output image. - -`operation` is a string containing the name of the bitwise operator to be appled to image channels, which can be one of: - -* `and` performs a bitwise and operation, like the c-operator `&`. -* `or` performs a bitwise or operation, like the c-operator `|`. -* `eor` performs a bitwise exclusive or operation, like the c-operator `^`. - -```javascript -sharp('input.png') - .bandbool(sharp.bool.and) - .toFile('output.png') -``` - -In the above example if `input.png` is a 3 channel RGB image, `output.png` will be a 1 channel grayscale image where each pixel `P = R & G & B`. -For example, if `I(1,1) = [247, 170, 14] = [0b11110111, 0b10101010, 0b00001111]` then `O(1,1) = 0b11110111 & 0b10101010 & 0b00001111 = 0b00000010 = 2`. - -#### boolean(image, operation, [options]) - -Perform a bitwise boolean operation with `image`, where `image` is one of the following: - -* Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or -* String containing the path to an image file - -This operation creates an output image where each pixel is the result of the selected bitwise boolean `operation` between the corresponding pixels of the input images. -The boolean operation can be one of the following: - -* `and` performs a bitwise and operation, like the c-operator `&`. -* `or` performs a bitwise or operation, like the c-operator `|`. -* `eor` performs a bitwise exclusive or operation, like the c-operator `^`. - -`options`, if present, is an Object with the following optional attributes: - -* `raw` an Object containing `width`, `height` and `channels` when providing uncompressed data. - -### Output - -#### toFile(path, [callback]) - -`path` is a String containing the path to write the image data to. - -If an explicit output format is not selected, it will be inferred from the extension, with JPEG, PNG, WebP, TIFF, DZI, and VIPS V format supported. Note that RAW format is only supported for buffer output. - -`callback`, if present, is called with two arguments `(err, info)` where: - -* `err` contains an error message, if any. -* `info` contains the output image `format`, `size` (bytes), `width`, `height` and `channels`. - -A Promises/A+ promise is returned when `callback` is not provided. - -#### toBuffer([callback]) - -Write image data to a Buffer, the format of which will match the input image by default. JPEG, PNG, WebP, and RAW are supported. - -`callback`, if present, gets three arguments `(err, buffer, info)` where: - -* `err` is an error message, if any. -* `buffer` is the output image data. -* `info` contains the output image `format`, `size` (bytes), `width`, `height` and `channels`. - -A Promises/A+ promise is returned when `callback` is not provided. - -#### jpeg([options]) - -Use JPEG format for the output image. - -`options`, if present, is an Object with the following optional attributes: - -* `quality` is an integral Number between 1 and 100, default 80. Using quality >90 forces a `chromaSubsampling` value of '4:4:4'. -* `progressive` is a Boolean to control the use of progressive (interlace) scan, default false. -* `chromaSubsampling` is a String with the value '4:2:0' (default) or '4:4:4' to control [chroma subsampling](http://en.wikipedia.org/wiki/Chroma_subsampling). -* `force` is a Boolean, where true (default) will force the use of JPEG output and false will use the input format. - -The following, additional options require libvips to have been compiled with mozjpeg support: - -* `trellisQuantisation` / `trellisQuantization` is a Boolean, default false, to control the use of [trellis quantisation](http://en.wikipedia.org/wiki/Trellis_quantization). -* `overshootDeringing` is a Boolean, default false, to reduce the effects of [ringing](http://en.wikipedia.org/wiki/Ringing_%28signal%29). -* `optimiseScans` / `optimizeScans` is a Boolean, default false, when true calculates which spectrum of DCT coefficients uses the fewest bits for each progressive scan. - -#### png([options]) - -Use PNG format for the output image. - -`options`, if present, is an Object with the following optional attributes: - -* `progressive` is a Boolean to control the use of progressive (interlace) scan, default false. -* `compressionLevel` is an integral Number between 0 and 9, default 6, to set the _zlib_ compression level. -* `adaptiveFiltering` is a Boolean to control [adaptive row filtering](https://en.wikipedia.org/wiki/Portable_Network_Graphics#Filtering), true for adaptive (default), false for none. -* `force` is a Boolean, where true (default) will force the use of PNG output and false will use the input format. - -#### webp([options]) - -Use WebP format for the output image. - -`options`, if present, is an Object with the following optional attributes: - -* `quality` is an integral Number between 1 and 100, default 80. -* `force` is a Boolean, where true (default) will force the use of WebP output and false will use the input format. - -#### tiff([options]) - -Use TIFF format for the output image. - -`options`, if present, is an Object with the following optional attributes: - -* `quality` is an integral Number between 1 and 100, default 80. -* `force` is a Boolean, where true (default) will force the use of TIFF output and false will use the input format. - -#### raw() - -Provide raw, uncompressed uint8 (unsigned char) image data for Buffer and Stream based output. - -The number of channels depends on the input image and selected options. - -* 1 channel for images converted to `greyscale()`, with each byte representing one pixel. -* 3 channels for colour images without alpha transparency, with bytes ordered \[red, green, blue, red, green, blue, etc.\]). -* 4 channels for colour images with alpha transparency, with bytes ordered \[red, green, blue, alpha, red, green, blue, alpha, etc.\]. - -#### toFormat(format, [options]) - -Convenience method for the above output format methods, where `format` is either: - -* an attribute of the `sharp.format` Object e.g. `sharp.format.jpeg`, or -* a String containing `jpeg`, `png`, `webp`, `tiff` or `raw`. - -#### withMetadata([metadata]) - -Include all metadata (EXIF, XMP, IPTC) from the input image in the output image. -This will also convert to and add the latest web-friendly v2 sRGB ICC profile. - -The optional `metadata` parameter, if present, is an Object with the attributes to update. -New attributes cannot be inserted, only existing attributes updated. - -* `orientation` is an integral Number between 1 and 8, used to update the value of the EXIF `Orientation` tag. -This has no effect if the input image does not have an EXIF `Orientation` tag. - -The default behaviour, when `withMetadata` is not used, is to strip all metadata and convert to the device-independent sRGB colour space. - -#### tile(options) - -The size, overlap, container and directory layout to use when generating square Deep Zoom image pyramid tiles. - -`options` is an Object with one or more of the following attributes: - -* `size` is an integral Number between 1 and 8192. The default value is 256 pixels. -* `overlap` is an integral Number between 0 and 8192. The default value is 0 pixels. -* `container` is a String, with value `fs` or `zip`. The default value is `fs`. -* `layout` is a String, with value `dz`, `zoomify` or `google`. The default value is `dz`. - -You can also use the file extension `.zip` or `.szi` to write to a compressed archive file format. - -```javascript -sharp('input.tiff') - .tile({ - size: 512 - }) - .toFile('output.dzi', function(err, info) { - // output.dzi is the Deep Zoom XML definition - // output_files contains 512x512 tiles grouped by zoom level - }); -``` - -### Attributes - -#### format - -An Object containing nested boolean values -representing the available input and output formats/methods, -for example: - -```javascript -> console.dir(sharp.format); - -{ jpeg: { id: 'jpeg', - input: { file: true, buffer: true, stream: true }, - output: { file: true, buffer: true, stream: true } }, - png: { id: 'png', - input: { file: true, buffer: true, stream: true }, - output: { file: true, buffer: true, stream: true } }, - webp: { id: 'webp', - input: { file: true, buffer: true, stream: true }, - output: { file: true, buffer: true, stream: true } }, - tiff: { id: 'tiff', - input: { file: true, buffer: true, stream: true }, - output: { file: true, buffer: false, stream: false } }, - raw: { id: 'raw', - input: { file: false, buffer: false, stream: false }, - output: { file: false, buffer: true, stream: true } } } -``` - -#### queue - -An EventEmitter that emits a `change` event when a task is either: - -* queued, waiting for _libuv_ to provide a worker thread -* complete - -```javascript -sharp.queue.on('change', function(queueLength) { - console.log('Queue contains ' + queueLength + ' task(s)'); -}); -``` - -#### versions - -An Object containing the version numbers of libvips and, on Linux, its dependencies. - -```javascript -console.log(sharp.versions); -``` - -### Utilities - -#### sharp.cache([options]) - -If `options` is provided, sets the limits of _libvips'_ operation cache. - -* `options.memory` is the maximum memory in MB to use for this cache, with a default value of 50 -* `options.files` is the maximum number of files to hold open, with a default value of 20 -* `options.items` is the maximum number of operations to cache, with a default value of 100 - -`options` can also be a boolean, where `true` enables the default cache settings and `false` disables all caching. - -Existing entries in the cache will be trimmed after any change in limits. - -This method always returns cache statistics, useful for determining how much working memory is required for a particular task. - -```javascript -var stats = sharp.cache(); -``` - -```javascript -sharp.cache( { items: 200 } ); -sharp.cache( { files: 0 } ); -sharp.cache(false); -``` - -#### sharp.concurrency([threads]) - -`threads`, if provided, is the Number of threads _libvips'_ should create for processing each image. The default value is the number of CPU cores. A value of `0` will reset to this default. - -This method always returns the current concurrency. - -```javascript -var threads = sharp.concurrency(); // 4 -sharp.concurrency(2); // 2 -sharp.concurrency(0); // 4 -``` - -The maximum number of images that can be processed in parallel is limited by libuv's `UV_THREADPOOL_SIZE` environment variable. - -#### sharp.counters() - -Provides access to internal task counters. - -* `queue` is the number of tasks this module has queued waiting for _libuv_ to provide a worker thread from its pool. -* `process` is the number of resize tasks currently being processed. - -```javascript -var counters = sharp.counters(); // { queue: 2, process: 4 } -``` - -#### sharp.simd([enable]) - -_Requires libvips to have been compiled with liborc support_ - -Improves the performance of `resize`, `blur` and `sharpen` operations -by taking advantage of the SIMD vector unit of the CPU, e.g. Intel SSE and ARM NEON. - -* `enable`, if present, is a boolean where `true` enables and `false` disables the use of SIMD. - -This method always returns the current state. - -This feature is currently disabled by default -but future versions may enable it by default. - -When enabled, versions of liborc prior to 0.4.24 -and versions of libvips prior to 8.2.0 -have been known to crash under heavy load. - -```javascript -var simd = sharp.simd(); -// simd is `true` if SIMD is currently enabled -``` - -```javascript -var simd = sharp.simd(true); -// attempts to enable the use of SIMD, returning true if available -``` diff --git a/docs/install.md b/docs/install.md index 7b7947fc..71cebd2b 100644 --- a/docs/install.md +++ b/docs/install.md @@ -4,8 +4,13 @@ npm install sharp ``` +```sh +yarn add sharp +``` + ### Prerequisites +* Node v4+ * C++11 compatible compiler such as gcc 4.8+, clang 3.0+ or MSVC 2013+ * [node-gyp](https://github.com/TooTallNate/node-gyp#installation) and its dependencies diff --git a/index.js b/index.js deleted file mode 100644 index 2c0c27e4..00000000 --- a/index.js +++ /dev/null @@ -1,1909 +0,0 @@ -'use strict'; - -const path = require('path'); -const util = require('util'); -const stream = require('stream'); -const events = require('events'); - -const semver = require('semver'); -const color = require('color'); - -const sharp = require('./build/Release/sharp.node'); - -// Versioning -let versions = { - vips: sharp.libvipsVersion() -}; -(function () { - // Does libvips meet minimum requirement? - const libvipsVersionMin = require('./package.json').config.libvips; - if (semver.lt(versions.vips, libvipsVersionMin)) { - throw new Error('Found libvips ' + versions.vips + ' but require at least ' + libvipsVersionMin); - } - // Include versions of dependencies, if present - try { - versions = require('./lib/versions.json'); - } catch (err) {} -})(); - -// Limits -const maximum = { - width: 0x3FFF, - height: 0x3FFF, - pixels: Math.pow(0x3FFF, 2) -}; - -/** - * Constructor factory to which further methods are chained. - * - * JPEG, PNG or WebP format image data can be streamed out from this object. - * When using Stream based output, derived attributes are available from the `info` event. - * - * @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); - * - * @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 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 null or undefined. - * - * @param {Object} [options] - if present, is an Object with optional attributes. - * @param {Number} [options.density=72] - integral number representing the DPI for vector images. - * @param {Object} [options.raw] - describes raw pixel image data. See `raw()` for pixel ordering. - * @param {Number} [options.raw.width] - * @param {Number} [options.raw.height] - * @param {Number} [options.raw.channels] - * @returns {Sharp} - Implements the [stream.Duplex](http://nodejs.org/api/stream.html#stream_class_stream_duplex) class. - * @throws {Error} Invalid parameters - */ -const Sharp = function (input, options) { - if (!(this instanceof Sharp)) { - return new Sharp(input, options); - } - stream.Duplex.call(this); - this.options = { - // input options - sequentialRead: false, - limitInputPixels: maximum.pixels, - // ICC profiles - iccProfilePath: path.join(__dirname, 'icc') + path.sep, - // resize options - topOffsetPre: -1, - leftOffsetPre: -1, - widthPre: -1, - heightPre: -1, - topOffsetPost: -1, - leftOffsetPost: -1, - widthPost: -1, - heightPost: -1, - width: -1, - height: -1, - canvas: 'crop', - crop: 0, - angle: 0, - rotateBeforePreExtract: false, - flip: false, - flop: false, - extendTop: 0, - extendBottom: 0, - extendLeft: 0, - extendRight: 0, - withoutEnlargement: false, - kernel: 'lanczos3', - interpolator: 'bicubic', - // operations - background: [0, 0, 0, 255], - flatten: false, - negate: false, - blurSigma: 0, - sharpenSigma: 0, - sharpenFlat: 1, - sharpenJagged: 2, - threshold: 0, - thresholdGrayscale: true, - trimTolerance: 0, - gamma: 0, - greyscale: false, - normalize: 0, - booleanBufferIn: null, - booleanFileIn: '', - joinChannelIn: [], - extractChannel: -1, - colourspace: 'srgb', - // overlay - overlayGravity: 0, - overlayXOffset: -1, - overlayYOffset: -1, - overlayTile: false, - overlayCutout: false, - // output - fileOut: '', - formatOut: 'input', - streamOut: false, - withMetadata: false, - withMetadataOrientation: -1, - // output format - jpegQuality: 80, - jpegProgressive: false, - jpegChromaSubsampling: '4:2:0', - jpegTrellisQuantisation: false, - jpegOvershootDeringing: false, - jpegOptimiseScans: false, - pngProgressive: false, - pngCompressionLevel: 6, - pngAdaptiveFiltering: true, - webpQuality: 80, - tiffQuality: 80, - tileSize: 256, - tileOverlap: 0, - // Function to notify of queue length changes - queueListener: function (queueLength) { - module.exports.queue.emit('change', queueLength); - } - }; - this.options.input = this._createInputDescriptor(input, options, { allowStream: true }); - return this; -}; -module.exports = Sharp; -util.inherits(Sharp, stream.Duplex); - -/** - * An EventEmitter that emits a `change` event when a task is either: - * - queued, waiting for _libuv_ to provide a worker thread - * - complete - * @member - * @example - * sharp.queue.on('change', function(queueLength) { - * console.log('Queue contains ' + queueLength + ' task(s)'); - * }); - */ -module.exports.queue = new events.EventEmitter(); - -/** - * An Object containing nested boolean values representing the available input and output formats/methods. - * @example - * console.log(sharp.format()); - * @returns {Object} - */ -module.exports.format = sharp.format(); - -/** - * An Object containing the version numbers of libvips and its dependencies. - * @member - * @example - * console.log(sharp.versions); - */ -module.exports.versions = versions; - -// Validation helpers -const isDefined = function (val) { - return typeof val !== 'undefined' && val !== null; -}; -const isObject = function (val) { - return typeof val === 'object'; -}; -const isFunction = function (val) { - return typeof val === 'function'; -}; -const isBoolean = function (val) { - return typeof val === 'boolean'; -}; -const isBuffer = function (val) { - return typeof val === 'object' && val instanceof Buffer; -}; -const isString = function (val) { - return typeof val === 'string' && val.length > 0; -}; -const isNumber = function (val) { - return typeof val === 'number' && !Number.isNaN(val); -}; -const isInteger = function (val) { - return isNumber(val) && val % 1 === 0; -}; -const inRange = function (val, min, max) { - return val >= min && val <= max; -}; -const contains = function (val, list) { - return list.indexOf(val) !== -1; -}; - -// Create Object containing input and input-related options -Sharp.prototype._createInputDescriptor = function (input, inputOptions, containerOptions) { - const inputDescriptor = {}; - if (isString(input)) { - // filesystem - inputDescriptor.file = input; - } else if (isBuffer(input)) { - // Buffer - inputDescriptor.buffer = input; - } else if (!isDefined(input) && isObject(containerOptions) && containerOptions.allowStream) { - // Stream - inputDescriptor.buffer = []; - } else { - throw new Error('Unsupported input ' + typeof input); - } - if (isObject(inputOptions)) { - // Density - if (isDefined(inputOptions.density)) { - if (isInteger(inputOptions.density) && inRange(inputOptions.density, 1, 2400)) { - inputDescriptor.density = inputOptions.density; - } else { - throw new Error('Invalid density (1 to 2400) ' + inputOptions.density); - } - } - // Raw pixel input - if (isDefined(inputOptions.raw)) { - if ( - isObject(inputOptions.raw) && - isInteger(inputOptions.raw.width) && inRange(inputOptions.raw.width, 1, maximum.width) && - isInteger(inputOptions.raw.height) && inRange(inputOptions.raw.height, 1, maximum.height) && - isInteger(inputOptions.raw.channels) && inRange(inputOptions.raw.channels, 1, 4) - ) { - inputDescriptor.rawWidth = inputOptions.raw.width; - inputDescriptor.rawHeight = inputOptions.raw.height; - inputDescriptor.rawChannels = inputOptions.raw.channels; - } else { - throw new Error('Expected width, height and channels for raw pixel input'); - } - } - } else if (isDefined(inputOptions)) { - throw new Error('Invalid input options ' + inputOptions); - } - return inputDescriptor; -}; - -/** - * Handle incoming Buffer chunk on Writable Stream. - * @param {Buffer} chunk - * @param {String} encoding - unused - * @param {Function} callback - */ -Sharp.prototype._write = function (chunk, encoding, callback) { - if (Array.isArray(this.options.input.buffer)) { - if (isBuffer(chunk)) { - this.options.input.buffer.push(chunk); - callback(); - } else { - callback(new Error('Non-Buffer data on Writable Stream')); - } - } else { - callback(new Error('Unexpected data on Writable Stream')); - } -}; - -// Flattens the array of chunks accumulated in input.buffer -Sharp.prototype._flattenBufferIn = function () { - if (this._isStreamInput()) { - this.options.input.buffer = Buffer.concat(this.options.input.buffer); - } -}; -Sharp.prototype._isStreamInput = function () { - return Array.isArray(this.options.input.buffer); -}; - -/** - * Weighting to apply to image crop. - * @member - */ -module.exports.gravity = { - center: 0, - centre: 0, - north: 1, - east: 2, - south: 3, - west: 4, - northeast: 5, - southeast: 6, - southwest: 7, - northwest: 8 -}; - -/** - * Strategies for automagic crop behaviour. - * @member - */ -module.exports.strategy = { - entropy: 16, - attention: 17 -}; - -/** - * Crop the resized image to the exact size specified, the default behaviour. - * - * Possible attributes of the optional `sharp.gravity` are `north`, `northeast`, `east`, `southeast`, `south`, - * `southwest`, `west`, `northwest`, `center` and `centre`. - * - * The experimental strategy-based approach resizes so one dimension is at its target length - * then repeatedly ranks edge regions, discarding the edge with the lowest score based on the selected strategy. - * - `entropy`: focus on the region with the highest [Shannon entropy](https://en.wikipedia.org/wiki/Entropy_%28information_theory%29). - * - `attention`: focus on the region with the highest luminance frequency, colour saturation and presence of skin tones. - * - * @example - * const transformer = sharp() - * .resize(200, 200) - * .crop(sharp.strategy.entropy) - * .on('error', function(err) { - * console.log(err); - * }); - * // Read image data from readableStream - * // Write 200px square auto-cropped image data to writableStream - * readableStream.pipe(transformer).pipe(writableStream); - * - * @param {String} [crop='centre'] - A member of `sharp.gravity` to crop to an edge/corner or `sharp.strategy` to crop dynamically. - * @returns {Sharp} - * @throws {Error} Invalid parameters - */ -Sharp.prototype.crop = function (crop) { - this.options.canvas = 'crop'; - if (!isDefined(crop)) { - // Default - this.options.crop = module.exports.gravity.center; - } else if (isInteger(crop) && inRange(crop, 0, 8)) { - // Gravity (numeric) - this.options.crop = crop; - } else if (isString(crop) && isInteger(module.exports.gravity[crop])) { - // Gravity (string) - this.options.crop = module.exports.gravity[crop]; - } else if (isInteger(crop) && crop >= module.exports.strategy.entropy) { - // Strategy - this.options.crop = crop; - } else { - throw new Error('Unsupported crop ' + crop); - } - return this; -}; - -/** - * Extract a region of the image. - * - * - Use `extract` before `resize` for pre-resize extraction. - * - Use `extract` after `resize` for post-resize extraction. - * - Use `extract` before and after for both. - * - * @example - * sharp(input) - * .extract({ left: left, top: top, width: width, height: height }) - * .toFile(output, function(err) { - * // Extract a region of the input image, saving in the same format. - * }); - * @example - * sharp(input) - * .extract({ left: leftOffsetPre, top: topOffsetPre, width: widthPre, height: heightPre }) - * .resize(width, height) - * .extract({ left: leftOffsetPost, top: topOffsetPost, width: widthPost, height: heightPost }) - * .toFile(output, function(err) { - * // Extract a region, resize, then extract from the resized image - * }); - * - * @param {Object} options - * @param {Number} options.left - zero-indexed offset from left edge - * @param {Number} options.top - zero-indexed offset from top edge - * @param {Number} options.width - dimension of extracted image - * @param {Number} options.height - dimension of extracted image - * @returns {Sharp} - * @throws {Error} Invalid parameters - */ -Sharp.prototype.extract = function (options) { - const suffix = this.options.width === -1 && this.options.height === -1 ? 'Pre' : 'Post'; - ['left', 'top', 'width', 'height'].forEach(function (name) { - const value = options[name]; - if (isInteger(value) && value >= 0) { - this.options[name + (name === 'left' || name === 'top' ? 'Offset' : '') + suffix] = value; - } else { - throw new Error('Non-integer value for ' + name + ' of ' + value); - } - }, this); - // Ensure existing rotation occurs before pre-resize extraction - if (suffix === 'Pre' && this.options.angle !== 0) { - this.options.rotateBeforePreExtract = true; - } - return this; -}; - -/** - * Extract a single channel from a multi-channel image. - * - * @example - * sharp(input) - * .extractChannel('green') - * .toFile('input_green.jpg', function(err, info) { - * // info.channels === 1 - * // input_green.jpg contains the green channel of the input image - * }); - * - * @param {Number|String} channel - zero-indexed band number to extract, or `red`, `green` or `blue` as alternative to `0`, `1` or `2` respectively. - * @returns {Sharp} - * @throws {Error} Invalid channel - */ -Sharp.prototype.extractChannel = function (channel) { - if (channel === 'red') { - channel = 0; - } else if (channel === 'green') { - channel = 1; - } else if (channel === 'blue') { - channel = 2; - } - if (isInteger(channel) && inRange(channel, 0, 4)) { - this.options.extractChannel = channel; - } else { - throw new Error('Cannot extract invalid channel ' + channel); - } - return this; -}; - -/** - * Set the background for the `embed`, `flatten` and `extend` operations. - * The default background is `{r: 0, g: 0, b: 0, a: 1}`, black without transparency. - * - * Delegates to the _color_ module, which can throw an Error - * but is liberal in what it accepts, clipping values to sensible min/max. - * The alpha value is a float between `0` (transparent) and `1` (opaque). - * - * @param {String|Object} rgba - 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 parameter - */ -Sharp.prototype.background = function (rgba) { - const colour = color(rgba); - this.options.background = colour.rgbArray(); - this.options.background.push(colour.alpha() * 255); - return this; -}; - -/** - * Preserving aspect ratio, resize the image to the maximum `width` or `height` specified - * then embed on a background of the exact `width` and `height` specified. - * - * If the background contains an alpha value then WebP and PNG format output images will - * contain an alpha channel, even when the input image does not. - * - * @example - * sharp('input.gif') - * .resize(200, 300) - * .background({r: 0, g: 0, b: 0, a: 0}) - * .embed() - * .toFormat(sharp.format.webp) - * .toBuffer(function(err, outputBuffer) { - * if (err) { - * throw err; - * } - * // outputBuffer contains WebP image data of a 200 pixels wide and 300 pixels high - * // containing a scaled version, embedded on a transparent canvas, of input.gif - * }); - * - * @returns {Sharp} - */ -Sharp.prototype.embed = function () { - this.options.canvas = 'embed'; - return this; -}; - -/** - * Preserving aspect ratio, resize the image to be as large as possible - * while ensuring its dimensions are less than or equal to the `width` and `height` specified. - * - * Both `width` and `height` must be provided via `resize` otherwise the behaviour will default to `crop`. - * - * @example - * sharp(inputBuffer) - * .resize(200, 200) - * .max() - * .toFormat('jpeg') - * .toBuffer() - * .then(function(outputBuffer) { - * // outputBuffer contains JPEG image data no wider than 200 pixels and no higher - * // than 200 pixels regardless of the inputBuffer image dimensions - * }); - * - * @returns {Sharp} - */ -Sharp.prototype.max = function () { - this.options.canvas = 'max'; - return this; -}; - -/** - * Preserving aspect ratio, resize the image to be as small as possible - * while ensuring its dimensions are greater than or equal to the `width` and `height` specified. - * - * Both `width` and `height` must be provided via `resize` otherwise the behaviour will default to `crop`. - * - * @returns {Sharp} - */ -Sharp.prototype.min = function () { - this.options.canvas = 'min'; - return this; -}; - -/** - * Ignoring the aspect ratio of the input, stretch the image to - * the exact `width` and/or `height` provided via `resize`. - * @returns {Sharp} - */ -Sharp.prototype.ignoreAspectRatio = function () { - this.options.canvas = 'ignore_aspect'; - return this; -}; - -/** - * Merge alpha transparency channel, if any, with `background`. - * @param {Boolean} [flatten=true] - * @returns {Sharp} - */ -Sharp.prototype.flatten = function (flatten) { - this.options.flatten = isBoolean(flatten) ? flatten : true; - return this; -}; - -/** - * Produce the "negative" of the image. - * White => Black, Black => White, Blue => Yellow, etc. - * @param {Boolean} [negate=true] - * @returns {Sharp} - */ -Sharp.prototype.negate = function (negate) { - this.options.negate = isBoolean(negate) ? negate : true; - return this; -}; - -/** - * Perform a bitwise boolean operation with operand image. - * - * This operation creates an output image where each pixel is the result of - * the selected bitwise boolean `operation` between the corresponding pixels of the input images. - * - * @param {Buffer|String} operand - Buffer containing image data or String containing the path to an image file. - * @param {String} operator - one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively. - * @param {Object} [options] - * @param {Object} [options.raw] - describes operand when using raw pixel data. - * @param {Number} [options.raw.width] - * @param {Number} [options.raw.height] - * @param {Number} [options.raw.channels] - * @returns {Sharp} - * @throws {Error} Invalid parameters - */ -Sharp.prototype.boolean = function (operand, operator, options) { - this.options.boolean = this._createInputDescriptor(operand, options); - if (isString(operator) && contains(operator, ['and', 'or', 'eor'])) { - this.options.booleanOp = operator; - } else { - throw new Error('Invalid boolean operator ' + operator); - } - return this; -}; - -/** - * Overlay (composite) an image over the processed (resized, extracted etc.) image. - * - * The overlay image must be the same size or smaller than the processed image. - * If both `top` and `left` options are provided, they take precedence over `gravity`. - * - * @example - * sharp('input.png') - * .rotate(180) - * .resize(300) - * .flatten() - * .background('#ff6600') - * .overlayWith('overlay.png', { gravity: sharp.gravity.southeast } ) - * .sharpen() - * .withMetadata() - * .quality(90) - * .webp() - * .toBuffer() - * .then(function(outputBuffer) { - * // outputBuffer contains upside down, 300px wide, alpha channel flattened - * // onto orange background, composited with overlay.png with SE gravity, - * // sharpened, with metadata, 90% quality WebP image data. Phew! - * }); - * - * @param {Buffer|String} overlay - Buffer containing image data or String containing the path to an image file. - * @param {Object} [options] - * @param {String} [options.gravity='centre'] - gravity at which to place the overlay. - * @param {Number} [options.top] - the pixel offset from the top edge. - * @param {Number} [options.left] - the pixel offset from the left edge. - * @param {Boolean} [options.tile=false] - set to true to repeat the overlay image across the entire image with the given `gravity`. - * @param {Boolean} [options.cutout=false] - set to true to apply only the alpha channel of the overlay image to the input image, giving the appearance of one image being cut out of another. - * @param {Object} [options.raw] - describes overlay when using raw pixel data. - * @param {Number} [options.raw.width] - * @param {Number} [options.raw.height] - * @param {Number} [options.raw.channels] - * @returns {Sharp} - * @throws {Error} Invalid parameters - */ -Sharp.prototype.overlayWith = function (overlay, options) { - this.options.overlay = this._createInputDescriptor(overlay, options, { - allowStream: false - }); - if (isObject(options)) { - if (isDefined(options.tile)) { - if (isBoolean(options.tile)) { - this.options.overlayTile = options.tile; - } else { - throw new Error('Invalid overlay tile ' + options.tile); - } - } - if (isDefined(options.cutout)) { - if (isBoolean(options.cutout)) { - this.options.overlayCutout = options.cutout; - } else { - throw new Error('Invalid overlay cutout ' + options.cutout); - } - } - if (isDefined(options.left) || isDefined(options.top)) { - if ( - isInteger(options.left) && inRange(options.left, 0, maximum.width) && - isInteger(options.top) && inRange(options.top, 0, maximum.height) - ) { - this.options.overlayXOffset = options.left; - this.options.overlayYOffset = options.top; - } else { - throw new Error('Invalid overlay left ' + options.left + ' and/or top ' + options.top); - } - } - if (isDefined(options.gravity)) { - if (isInteger(options.gravity) && inRange(options.gravity, 0, 8)) { - this.options.overlayGravity = options.gravity; - } else if (isString(options.gravity) && isInteger(module.exports.gravity[options.gravity])) { - this.options.overlayGravity = module.exports.gravity[options.gravity]; - } else { - throw new Error('Unsupported overlay gravity ' + options.gravity); - } - } - } - return this; -}; - -/** - * Join one or more channels to the image. - * The meaning of the added channels depends on the output colourspace, set with `toColourspace()`. - * By default the output image will be web-friendly sRGB, with additional channels interpreted as alpha channels. - * Channel ordering follows vips convention: - * - sRGB: 0: Red, 1: Green, 2: Blue, 3: Alpha. - * - CMYK: 0: Magenta, 1: Cyan, 2: Yellow, 3: Black, 4: Alpha. - * - * Buffers may be any of the image formats supported by sharp: JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data. - * For raw pixel input, the `options` object should contain a `raw` attribute, which follows the format of the attribute of the same name in the `sharp()` constructor. - * - * @param {Array|String|Buffer} images - one or more images (file paths, Buffers). - * @param {Object} - image options, see `sharp()` constructor. - * @returns {Sharp} - * @throws {Error} Invalid parameters - */ -Sharp.prototype.joinChannel = function (images, options) { - if (Array.isArray(images)) { - images.forEach(function (image) { - this.options.joinChannelIn.push(this._createInputDescriptor(image, options)); - }, this); - } else { - this.options.joinChannelIn.push(this._createInputDescriptor(images, options)); - } - return this; -}; - -/** - * Rotate the output image by either an explicit angle - * or auto-orient based on the EXIF `Orientation` tag. - * - * Use this method without angle to determine the angle from EXIF data. - * Mirroring is supported and may infer the use of a flip operation. - * - * The use of `rotate` implies the removal of the EXIF `Orientation` tag, if any. - * - * Method order is important when both rotating and extracting regions, - * for example `rotate(x).extract(y)` will produce a different result to `extract(y).rotate(x)`. - * - * @example - * const pipeline = sharp() - * .rotate() - * .resize(null, 200) - * .toBuffer(function (err, outputBuffer, info) { - * // outputBuffer contains 200px high JPEG image data, - * // auto-rotated using EXIF Orientation tag - * // info.width and info.height contain the dimensions of the resized image - * }); - * readableStream.pipe(pipeline); - * - * @param {Number} [angle=auto] 0, 90, 180 or 270. - * @returns {Sharp} - * @throws {Error} Invalid parameters - */ -Sharp.prototype.rotate = function (angle) { - if (!isDefined(angle)) { - this.options.angle = -1; - } else if (isInteger(angle) && contains(angle, [0, 90, 180, 270])) { - this.options.angle = angle; - } else { - throw new Error('Unsupported angle (0, 90, 180, 270) ' + angle); - } - return this; -}; - -/** - * Flip the image about the vertical Y axis. This always occurs after rotation, if any. - * The use of `flip` implies the removal of the EXIF `Orientation` tag, if any. - * @param {Boolean} [flip=true] - * @returns {Sharp} - */ -Sharp.prototype.flip = function (flip) { - this.options.flip = isBoolean(flip) ? flip : true; - return this; -}; - -/** - * Flop the image about the horizontal X axis. This always occurs after rotation, if any. - * The use of `flop` implies the removal of the EXIF `Orientation` tag, if any. - * @param {Boolean} [flop=true] - * @returns {Sharp} - */ -Sharp.prototype.flop = function (flop) { - this.options.flop = isBoolean(flop) ? flop : true; - return this; -}; - -/** - * Do not enlarge the output image if the input image width *or* height are already less than the required dimensions. - * This is equivalent to GraphicsMagick's `>` geometry option: - * "*change the dimensions of the image only if its width or height exceeds the geometry specification*". - * @param {Boolean} [withoutEnlargement=true] - * @returns {Sharp} -*/ -Sharp.prototype.withoutEnlargement = function (withoutEnlargement) { - this.options.withoutEnlargement = isBoolean(withoutEnlargement) ? withoutEnlargement : true; - return this; -}; - -/** - * Blur the image. - * When used without parameters, performs a fast, mild blur of the output image. - * When a `sigma` is provided, performs a slower, more accurate Gaussian blur. - * @param {Number} [sigma] a value between 0.3 and 1000 representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`. - * @returns {Sharp} - * @throws {Error} Invalid parameters - */ -Sharp.prototype.blur = function (sigma) { - if (!isDefined(sigma)) { - // No arguments: default to mild blur - this.options.blurSigma = -1; - } else if (isBoolean(sigma)) { - // Boolean argument: apply mild blur? - this.options.blurSigma = sigma ? -1 : 0; - } else if (isNumber(sigma) && inRange(sigma, 0.3, 1000)) { - // Numeric argument: specific sigma - this.options.blurSigma = sigma; - } else { - throw new Error('Invalid blur sigma (0.3 - 1000.0) ' + sigma); - } - return this; -}; - -/** - * Convolve the image with the specified kernel. - * - * @example - * sharp(input) - * .convolve({ - * width: 3, - * height: 3, - * kernel: [-1, 0, 1, -2, 0, 2, -1, 0, 1] - * }) - * .raw() - * .toBuffer(function(err, data, info) { - * // data contains the raw pixel data representing the convolution - * // of the input image with the horizontal Sobel operator - * }); - * - * @param {Object} kernel - * @param {Number} kernel.width - width of the kernel in pixels. - * @param {Number} kernel.height - width of the kernel in pixels. - * @param {Array} kernel.kernel - Array of length `width*height` containing the kernel values. - * @param {Number} [kernel.scale=sum] - the scale of the kernel in pixels. - * @param {Number} [kernel.offset=0] - the offset of the kernel in pixels. - * @returns {Sharp} - * @throws {Error} Invalid parameters - */ -Sharp.prototype.convolve = function (kernel) { - if (!isObject(kernel) || !Array.isArray(kernel.kernel) || - !isInteger(kernel.width) || !isInteger(kernel.height) || - !inRange(kernel.width, 3, 1001) || !inRange(kernel.height, 3, 1001) || - kernel.height * kernel.width !== kernel.kernel.length - ) { - // must pass in a kernel - throw new Error('Invalid convolution kernel'); - } - // Default scale is sum of kernel values - if (!isInteger(kernel.scale)) { - kernel.scale = kernel.kernel.reduce(function (a, b) { - return a + b; - }, 0); - } - // Clip scale to a minimum value of 1 - if (kernel.scale < 1) { - kernel.scale = 1; - } - if (!isInteger(kernel.offset)) { - kernel.offset = 0; - } - this.options.convKernel = kernel; - return this; -}; - -/** - * Sharpen the image. - * When used without parameters, performs a fast, mild sharpen of the output image. - * When a `sigma` is provided, performs a slower, more accurate sharpen of the L channel in the LAB colour space. - * Separate control over the level of sharpening in "flat" and "jagged" areas is available. - * - * @param {Number} [sigma] - the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`. - * @param {Number} [flat=1.0] - the level of sharpening to apply to "flat" areas. - * @param {Number} [jagged=2.0] - the level of sharpening to apply to "jagged" areas. - * @returns {Sharp} - * @throws {Error} Invalid parameters - */ -Sharp.prototype.sharpen = function (sigma, flat, jagged) { - if (!isDefined(sigma)) { - // No arguments: default to mild sharpen - this.options.sharpenSigma = -1; - } else if (isBoolean(sigma)) { - // Boolean argument: apply mild sharpen? - this.options.sharpenSigma = sigma ? -1 : 0; - } else if (isNumber(sigma) && inRange(sigma, 0.01, 10000)) { - // Numeric argument: specific sigma - this.options.sharpenSigma = sigma; - // Control over flat areas - if (isDefined(flat)) { - if (isNumber(flat) && inRange(flat, 0, 10000)) { - this.options.sharpenFlat = flat; - } else { - throw new Error('Invalid sharpen level for flat areas (0 - 10000) ' + flat); - } - } - // Control over jagged areas - if (isDefined(jagged)) { - if (isNumber(jagged) && inRange(jagged, 0, 10000)) { - this.options.sharpenJagged = jagged; - } else { - throw new Error('Invalid sharpen level for jagged areas (0 - 10000) ' + jagged); - } - } - } else { - throw new Error('Invalid sharpen sigma (0.01 - 10000) ' + sigma); - } - return this; -}; - -/** - * Any pixel value greather than or equal to the threshold value will be set to 255, otherwise it will be set to 0. - * @param {Number} [threshold=128] - a value in the range 0-255 representing the level at which the threshold will be applied. - * @param {Object} [options] - * @param {Boolean} [options.greyscale=true] - convert to single channel greyscale. - * @param {Boolean} [options.grayscale=true] - alternative spelling for greyscale. - * @returns {Sharp} - * @throws {Error} Invalid parameters - */ -Sharp.prototype.threshold = function (threshold, options) { - if (!isDefined(threshold)) { - this.options.threshold = 128; - } else if (isBoolean(threshold)) { - this.options.threshold = threshold ? 128 : 0; - } else if (isInteger(threshold) && inRange(threshold, 0, 255)) { - this.options.threshold = threshold; - } else { - throw new Error('Invalid threshold (0 to 255) ' + threshold); - } - if (!isObject(options) || options.greyscale === true || options.grayscale === true) { - this.options.thresholdGrayscale = true; - } else { - this.options.thresholdGrayscale = false; - } - return this; -}; - -/** - * Trim "boring" pixels from all edges that contain values within a percentage similarity of the top-left pixel. - * @param {Number} [tolerance=10] value between 1 and 99 representing the percentage similarity. - * @returns {Sharp} - * @throws {Error} Invalid parameters - */ -Sharp.prototype.trim = function (tolerance) { - if (!isDefined(tolerance)) { - this.options.trimTolerance = 10; - } else if (isInteger(tolerance) && inRange(tolerance, 1, 99)) { - this.options.trimTolerance = tolerance; - } else { - throw new Error('Invalid trim tolerance (1 to 99) ' + tolerance); - } - return this; -}; - -/** - * Apply a gamma correction by reducing the encoding (darken) pre-resize at a factor of `1/gamma` - * then increasing the encoding (brighten) post-resize at a factor of `gamma`. - * This can improve the perceived brightness of a resized image in non-linear colour spaces. - * JPEG and WebP input images will not take advantage of the shrink-on-load performance optimisation - * when applying a gamma correction. - * @param {Number} [gamma=2.2] value between 1.0 and 3.0. - * @returns {Sharp} - * @throws {Error} Invalid parameters - */ -Sharp.prototype.gamma = function (gamma) { - if (!isDefined(gamma)) { - // Default gamma correction of 2.2 (sRGB) - this.options.gamma = 2.2; - } else if (isNumber(gamma) && inRange(gamma, 1, 3)) { - this.options.gamma = gamma; - } else { - throw new Error('Invalid gamma correction (1.0 to 3.0) ' + gamma); - } - return this; -}; - -/** - * Enhance output image contrast by stretching its luminance to cover the full dynamic range. - * @param {Boolean} [normalize=true] - * @returns {Sharp} - */ -Sharp.prototype.normalize = function (normalize) { - this.options.normalize = isBoolean(normalize) ? normalize : true; - return this; -}; -/** - * Alternative spelling of normalize. - * @param {Boolean} [normalise=true] - * @returns {Sharp} - */ -Sharp.prototype.normalise = Sharp.prototype.normalize; - -/** - * Perform a bitwise boolean operation on all input image channels (bands) to produce a single channel output image. - * - * @example - * sharp('3-channel-rgb-input.png') - * .bandbool(sharp.bool.and) - * .toFile('1-channel-output.png', function (err, info) { - * // The output will be a single channel image where each pixel `P = R & G & B`. - * // If `I(1,1) = [247, 170, 14] = [0b11110111, 0b10101010, 0b00001111]` - * // then `O(1,1) = 0b11110111 & 0b10101010 & 0b00001111 = 0b00000010 = 2`. - * }); - * - * @param {String} boolOp - one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively. - * @returns {Sharp} - * @throws {Error} Invalid parameters - */ -Sharp.prototype.bandbool = function (boolOp) { - if (isString(boolOp) && contains(boolOp, ['and', 'or', 'eor'])) { - this.options.bandBoolOp = boolOp; - } else { - throw new Error('Invalid bandbool operation ' + boolOp); - } - return this; -}; - -/** - * Convert to 8-bit greyscale; 256 shades of grey. - * This is a linear operation. If the input image is in a non-linear colour space such as sRGB, use `gamma()` with `greyscale()` for the best results. - * By default the output image will be web-friendly sRGB and contain three (identical) color channels. - * This may be overridden by other sharp operations such as `toColourspace('b-w')`, - * which will produce an output image containing one color channel. - * An alpha channel may be present, and will be unchanged by the operation. - * @param {Boolean} [greyscale=true] - * @returns {Sharp} - */ -Sharp.prototype.greyscale = function (greyscale) { - this.options.greyscale = isBoolean(greyscale) ? greyscale : true; - return this; -}; -/** - * Alternative spelling of `greyscale`. - * @param {Boolean} [grayscale=true] - * @returns {Sharp} - */ -Sharp.prototype.grayscale = Sharp.prototype.greyscale; - -/** - * Set the output colourspace. - * By default output image will be web-friendly sRGB, with additional channels interpreted as alpha channels. - * @param {String} [colourspace] - output colourspace e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://github.com/jcupitt/libvips/blob/master/libvips/iofuncs/enumtypes.c#L568) - * @returns {Sharp} - * @throws {Error} Invalid parameters - */ -Sharp.prototype.toColourspace = function (colourspace) { - if (!isString(colourspace)) { - throw new Error('Invalid output colourspace ' + colourspace); - } - this.options.colourspace = colourspace; - return this; -}; -/** - * Alternative spelling of `toColourspace`. - * @param {String} [colorspace] - output colorspace. - * @returns {Sharp} - * @throws {Error} Invalid parameters - */ -Sharp.prototype.toColorspace = Sharp.prototype.toColourspace; - -/** - * An advanced setting that switches the libvips access method to `VIPS_ACCESS_SEQUENTIAL`. - * This will reduce memory usage and can improve performance on some systems. - * @param {Boolean} [sequentialRead=true] - * @returns {Sharp} - */ -Sharp.prototype.sequentialRead = function (sequentialRead) { - this.options.sequentialRead = isBoolean(sequentialRead) ? sequentialRead : true; - return this; -}; - -// Deprecated output options -Sharp.prototype.quality = util.deprecate(function (quality) { - const formatOut = this.options.formatOut; - const options = { quality: quality }; - this.jpeg(options).webp(options).tiff(options); - this.options.formatOut = formatOut; - return this; -}, 'quality: use jpeg({ quality: ... }), webp({ quality: ... }) and/or tiff({ quality: ... }) instead'); -Sharp.prototype.progressive = util.deprecate(function (progressive) { - const formatOut = this.options.formatOut; - const options = { progressive: (progressive !== false) }; - this.jpeg(options).png(options); - this.options.formatOut = formatOut; - return this; -}, 'progressive: use jpeg({ progressive: ... }) and/or png({ progressive: ... }) instead'); -Sharp.prototype.compressionLevel = util.deprecate(function (compressionLevel) { - const formatOut = this.options.formatOut; - this.png({ compressionLevel: compressionLevel }); - this.options.formatOut = formatOut; - return this; -}, 'compressionLevel: use png({ compressionLevel: ... }) instead'); -Sharp.prototype.withoutAdaptiveFiltering = util.deprecate(function (withoutAdaptiveFiltering) { - const formatOut = this.options.formatOut; - this.png({ adaptiveFiltering: (withoutAdaptiveFiltering === false) }); - this.options.formatOut = formatOut; - return this; -}, 'withoutAdaptiveFiltering: use png({ adaptiveFiltering: ... }) instead'); -Sharp.prototype.withoutChromaSubsampling = util.deprecate(function (withoutChromaSubsampling) { - const formatOut = this.options.formatOut; - this.jpeg({ chromaSubsampling: (withoutChromaSubsampling === false) ? '4:2:0' : '4:4:4' }); - this.options.formatOut = formatOut; - return this; -}, 'withoutChromaSubsampling: use jpeg({ chromaSubsampling: "4:4:4" }) instead'); -Sharp.prototype.trellisQuantisation = util.deprecate(function (trellisQuantisation) { - const formatOut = this.options.formatOut; - this.jpeg({ trellisQuantisation: (trellisQuantisation !== false) }); - this.options.formatOut = formatOut; - return this; -}, 'trellisQuantisation: use jpeg({ trellisQuantisation: ... }) instead'); -Sharp.prototype.trellisQuantization = Sharp.prototype.trellisQuantisation; -Sharp.prototype.overshootDeringing = util.deprecate(function (overshootDeringing) { - const formatOut = this.options.formatOut; - this.jpeg({ overshootDeringing: (overshootDeringing !== false) }); - this.options.formatOut = formatOut; - return this; -}, 'overshootDeringing: use jpeg({ overshootDeringing: ... }) instead'); -Sharp.prototype.optimiseScans = util.deprecate(function (optimiseScans) { - const formatOut = this.options.formatOut; - this.jpeg({ optimiseScans: (optimiseScans !== false) }); - this.options.formatOut = formatOut; - return this; -}, 'optimiseScans: use jpeg({ optimiseScans: ... }) instead'); -Sharp.prototype.optimizeScans = Sharp.prototype.optimiseScans; - -/** - * Include all metadata (EXIF, XMP, IPTC) from the input image in the output image. - * The default behaviour, when `withMetadata` is not used, is to strip all metadata and convert to the device-independent sRGB colour space. - * This will also convert to and add a web-friendly sRGB ICC profile. - * @param {Object} [withMetadata] - * @param {Number} [withMetadata.orientation] value between 1 and 8, used to update the EXIF `Orientation` tag. - * @returns {Sharp} - * @throws {Error} Invalid parameters - */ -Sharp.prototype.withMetadata = function (withMetadata) { - this.options.withMetadata = isBoolean(withMetadata) ? withMetadata : true; - if (isObject(withMetadata)) { - if (isDefined(withMetadata.orientation)) { - if (isInteger(withMetadata.orientation) && inRange(withMetadata.orientation, 1, 8)) { - this.options.withMetadataOrientation = withMetadata.orientation; - } else { - throw new Error('Invalid orientation (1 to 8) ' + withMetadata.orientation); - } - } - } - return this; -}; - -/** - * Use tile-based deep zoom (image pyramid) output. - * You can also use a `.zip` or `.szi` file extension with `toFile` to write to a compressed archive file format. - * - * @example - * sharp('input.tiff') - * .tile({ - * size: 512 - * }) - * .toFile('output.dzi', function(err, info) { - * // output.dzi is the Deep Zoom XML definition - * // output_files contains 512x512 tiles grouped by zoom level - * }); - * - * @param {Object} [tile] - * @param {Number} [tile.size=256] tile size in pixels, a value between 1 and 8192. - * @param {Number} [tile.overlap=0] tile overlap in pixels, a value between 0 and 8192. - * @param {String} [tile.container='fs'] tile container, with value `fs` (filesystem) or `zip` (compressed file). - * @param {String} [tile.layout='dz'] filesystem layout, possible values are `dz`, `zoomify` or `google`. - * @returns {Sharp} - * @throws {Error} Invalid parameters - */ -Sharp.prototype.tile = function (tile) { - if (isObject(tile)) { - // Size of square tiles, in pixels - if (isDefined(tile.size)) { - if (isInteger(tile.size) && inRange(tile.size, 1, 8192)) { - this.options.tileSize = tile.size; - } else { - throw new Error('Invalid tile size (1 to 8192) ' + tile.size); - } - } - // Overlap of tiles, in pixels - if (isDefined(tile.overlap)) { - if (isInteger(tile.overlap) && inRange(tile.overlap, 0, 8192)) { - if (tile.overlap > this.options.tileSize) { - throw new Error('Tile overlap ' + tile.overlap + ' cannot be larger than tile size ' + this.options.tileSize); - } - this.options.tileOverlap = tile.overlap; - } else { - throw new Error('Invalid tile overlap (0 to 8192) ' + tile.overlap); - } - } - // Container - if (isDefined(tile.container)) { - if (isString(tile.container) && contains(tile.container, ['fs', 'zip'])) { - this.options.tileContainer = tile.container; - } else { - throw new Error('Invalid tile container ' + tile.container); - } - } - // Layout - if (isDefined(tile.layout)) { - if (isString(tile.layout) && contains(tile.layout, ['dz', 'google', 'zoomify'])) { - this.options.tileLayout = tile.layout; - } else { - throw new Error('Invalid tile layout ' + tile.layout); - } - } - } - return this; -}; - -/** - * Extends/pads the edges of the image with the colour provided to the `background` method. - * This operation will always occur after resizing and extraction, if any. - * - * @example - * // Resize to 140 pixels wide, then add 10 transparent pixels - * // to the top, left and right edges and 20 to the bottom edge - * sharp(input) - * .resize(140) - * .background({r: 0, g: 0, b: 0, a: 0}) - * .extend({top: 10, bottom: 20, left: 10, right: 10}) - * ... - * - * @param {Number|Object} extend - single pixel count to add to all edges or an Object with per-edge counts - * @param {Number} [extend.top] - * @param {Number} [extend.left] - * @param {Number} [extend.bottom] - * @param {Number} [extend.right] - * @returns {Sharp} - * @throws {Error} Invalid parameters -*/ -Sharp.prototype.extend = function (extend) { - if (isInteger(extend) && extend > 0) { - this.options.extendTop = extend; - this.options.extendBottom = extend; - this.options.extendLeft = extend; - this.options.extendRight = extend; - } else if ( - isObject(extend) && - isInteger(extend.top) && extend.top >= 0 && - isInteger(extend.bottom) && extend.bottom >= 0 && - isInteger(extend.left) && extend.left >= 0 && - isInteger(extend.right) && extend.right >= 0 - ) { - this.options.extendTop = extend.top; - this.options.extendBottom = extend.bottom; - this.options.extendLeft = extend.left; - this.options.extendRight = extend.right; - } else { - throw new Error('Invalid edge extension ' + extend); - } - return this; -}; - -/** - * Reduction kernels. - * @member - */ -module.exports.kernel = { - cubic: 'cubic', - lanczos2: 'lanczos2', - lanczos3: 'lanczos3' -}; -/** - * Enlargement interpolators. - * @member - */ -module.exports.interpolator = { - nearest: 'nearest', - bilinear: 'bilinear', - bicubic: 'bicubic', - nohalo: 'nohalo', - lbb: 'lbb', - locallyBoundedBicubic: 'lbb', - vsqbs: 'vsqbs', - vertexSplitQuadraticBasisSpline: 'vsqbs' -}; -/** - * Boolean operations for bandbool. - * @member - */ -module.exports.bool = { - and: 'and', - or: 'or', - eor: 'eor' -}; -/** - * Colourspaces. - * @member - */ -module.exports.colourspace = { - multiband: 'multiband', - 'b-w': 'b-w', - bw: 'b-w', - cmyk: 'cmyk', - srgb: 'srgb' -}; -/** - * Alternative spelling of colourspace. - * @member - */ -module.exports.colorspace = module.exports.colourspace; - -/** - * Resize image to `width` x `height`. - * By default, the resized image is centre cropped to the exact size specified. - * - * Possible reduction kernels are: - * - `cubic`: Use a [Catmull-Rom spline](https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline). - * - `lanczos2`: Use a [Lanczos kernel](https://en.wikipedia.org/wiki/Lanczos_resampling#Lanczos_kernel) with `a=2`. - * - `lanczos3`: Use a Lanczos kernel with `a=3` (the default). - * - * Possible enlargement interpolators are: - * - `nearest`: Use [nearest neighbour interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation). - * - `bilinear`: Use [bilinear interpolation](http://en.wikipedia.org/wiki/Bilinear_interpolation), faster than bicubic but with less smooth results. - * - `vertexSplitQuadraticBasisSpline`: Use the smoother [VSQBS interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/vsqbs.cpp#L48) to prevent "staircasing" when enlarging. - * - `bicubic`: Use [bicubic interpolation](http://en.wikipedia.org/wiki/Bicubic_interpolation) (the default). - * - `locallyBoundedBicubic`: Use [LBB interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/lbb.cpp#L100), which prevents some "[acutance](http://en.wikipedia.org/wiki/Acutance)" but typically reduces performance by a factor of 2. - * - `nohalo`: Use [Nohalo interpolation](http://eprints.soton.ac.uk/268086/), which prevents acutance but typically reduces performance by a factor of 3. - * - * @example - * sharp(inputBuffer) - * .resize(200, 300, { - * kernel: sharp.kernel.lanczos2, - * interpolator: sharp.interpolator.nohalo - * }) - * .background('white') - * .embed() - * .toFile('output.tiff') - * .then(function() { - * // output.tiff is a 200 pixels wide and 300 pixels high image - * // containing a lanczos2/nohalo scaled version, embedded on a white canvas, - * // of the image data in inputBuffer - * }); - * - * @param {Number} [width] - pixels wide the resultant image should be, between 1 and 16383 (0x3FFF). Use `null` or `undefined` to auto-scale the width to match the height. - * @param {Number} [height] - pixels high the resultant image should be, between 1 and 16383. Use `null` or `undefined` to auto-scale the height to match the width. - * @param {Object} [options] - * @param {String} [options.kernel='lanczos3'] - the kernel to use for image reduction. - * @param {String} [options.interpolator='bicubic'] - the interpolator to use for image enlargement. - * @returns {Sharp} - * @throws {Error} Invalid parameters - */ -Sharp.prototype.resize = function (width, height, options) { - if (isDefined(width)) { - if (isInteger(width) && inRange(width, 1, maximum.width)) { - this.options.width = width; - } else { - throw new Error('Invalid width (1 to ' + maximum.width + ') ' + width); - } - } else { - this.options.width = -1; - } - if (isDefined(height)) { - if (isInteger(height) && inRange(height, 1, maximum.height)) { - this.options.height = height; - } else { - throw new Error('Invalid height (1 to ' + maximum.height + ') ' + height); - } - } else { - this.options.height = -1; - } - if (isObject(options)) { - // Kernel - if (isDefined(options.kernel)) { - if (isString(module.exports.kernel[options.kernel])) { - this.options.kernel = module.exports.kernel[options.kernel]; - } else { - throw new Error('Invalid kernel ' + options.kernel); - } - } - // Interpolator - if (isDefined(options.interpolator)) { - if (isString(module.exports.interpolator[options.interpolator])) { - this.options.interpolator = module.exports.interpolator[options.interpolator]; - } else { - throw new Error('Invalid interpolator ' + options.interpolator); - } - } - } - return this; -}; - -/** - * Do not process input images where the number of pixels (width * height) exceeds this limit. - * Assumes image dimensions contained in the input metadata can be trusted. - * The default limit is 268402689 (0x3FFF * 0x3FFF) pixels. - * @param {Number|Boolean} limit - an integral Number of pixels, zero or false to remove limit, true to use default limit. - * @returns {Sharp} - * @throws {Error} Invalid limit -*/ -Sharp.prototype.limitInputPixels = function (limit) { - // if we pass in false we represent the integer as 0 to disable - if (limit === false) { - limit = 0; - } else if (limit === true) { - limit = maximum.pixels; - } - if (isInteger(limit) && limit >= 0) { - this.options.limitInputPixels = limit; - } else { - throw new Error('Invalid pixel limit (0 to ' + maximum.pixels + ') ' + limit); - } - return this; -}; - -/** - * 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. - * - * A Promises/A+ promise is returned when `callback` is not provided. - * - * @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` and `channels`. - * @returns {Promise} - when no callback is provided - * @throws {Error} Invalid parameters - */ -Sharp.prototype.toFile = function (fileOut, callback) { - if (!fileOut || fileOut.length === 0) { - const errOutputInvalid = new Error('Invalid output'); - if (isFunction(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 (isFunction(callback)) { - callback(errOutputIsInput); - } else { - return Promise.reject(errOutputIsInput); - } - } else { - this.options.fileOut = fileOut; - return this._pipeline(callback); - } - } - return this; -}; - -/** - * Write output to a Buffer. - * By default, the format will match the input image. JPEG, PNG, WebP, and RAW are supported. - * `callback`, if present, gets three arguments `(err, buffer, info)` where: - * - `err` is an error message, if any. - * - `buffer` is the output image data. - * - `info` contains the output image `format`, `size` (bytes), `width`, `height` and `channels`. - * A Promises/A+ promise is returned when `callback` is not provided. - * - * @param {Function} [callback] - * @returns {Promise|Sharp} - */ -Sharp.prototype.toBuffer = function (callback) { - return this._pipeline(callback); -}; - -/** - * 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} - */ -Sharp.prototype._updateFormatOut = function (formatOut, options) { - this.options.formatOut = (isObject(options) && options.force === false) ? 'input' : formatOut; - return this; -}; - -/** - * Update a Boolean attribute of the this.options Object. - * @private - * @param {String} key - * @param {Boolean} val - * @throws {Error} Invalid key - */ -Sharp.prototype._setBooleanOption = function (key, val) { - if (isBoolean(val)) { - this.options[key] = val; - } else { - throw new Error('Invalid ' + key + ' (boolean) ' + val); - } -}; - -/** - * Use these JPEG options for output image. - * @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'] - set to '4:4:4' to prevent chroma subsampling when quality <= 90 - * @param {Boolean} [trellisQuantisation=false] - apply trellis quantisation, requires mozjpeg - * @param {Boolean} [overshootDeringing=false] - apply overshoot deringing, requires mozjpeg - * @param {Boolean} [optimiseScans=false] - optimise progressive scans, assumes progressive=true, requires mozjpeg - * @param {Boolean} [options.force=true] - force JPEG output, otherwise attempt to use input format - * @returns {Sharp} - * @throws {Error} Invalid options - */ -Sharp.prototype.jpeg = function (options) { - if (isObject(options)) { - if (isDefined(options.quality)) { - if (isInteger(options.quality) && inRange(options.quality, 1, 100)) { - this.options.jpegQuality = options.quality; - } else { - throw new Error('Invalid quality (integer, 1-100) ' + options.quality); - } - } - if (isDefined(options.progressive)) { - this._setBooleanOption('jpegProgressive', options.progressive); - } - if (isDefined(options.chromaSubsampling)) { - if (isString(options.chromaSubsampling) && contains(options.chromaSubsampling, ['4:2:0', '4:4:4'])) { - this.options.jpegChromaSubsampling = options.chromaSubsampling; - } else { - throw new Error('Invalid chromaSubsampling (4:2:0, 4:4:4) ' + options.chromaSubsampling); - } - } - options.trellisQuantisation = options.trellisQuantisation || options.trellisQuantization; - if (isDefined(options.trellisQuantisation)) { - this._setBooleanOption('jpegTrellisQuantisation', options.trellisQuantisation); - } - if (isDefined(options.overshootDeringing)) { - this._setBooleanOption('jpegOvershootDeringing', options.overshootDeringing); - } - options.optimiseScans = options.optimiseScans || options.optimizeScans; - if (isDefined(options.optimiseScans)) { - this._setBooleanOption('jpegOptimiseScans', options.optimiseScans); - if (options.optimiseScans) { - this.options.jpegProgressive = true; - } - } - } - return this._updateFormatOut('jpeg', options); -}; - -/** - * Use these PNG options for output image. - * @param {Object} [options] - * @param {Boolean} [options.progressive=false] - use progressive (interlace) scan - * @param {Number} [options.compressionLevel=6] - zlib compression level - * @param {Boolean} [options.adaptiveFiltering=true] - use adaptive row filtering - * @param {Boolean} [options.force=true] - force PNG output, otherwise attempt to use input format - * @returns {Sharp} - * @throws {Error} Invalid options - */ -Sharp.prototype.png = function (options) { - if (isObject(options)) { - if (isDefined(options.progressive)) { - this._setBooleanOption('pngProgressive', options.progressive); - } - if (isDefined(options.compressionLevel)) { - if (isInteger(options.compressionLevel) && inRange(options.compressionLevel, 0, 9)) { - this.options.pngCompressionLevel = options.compressionLevel; - } else { - throw new Error('Invalid compressionLevel (integer, 0-9) ' + options.compressionLevel); - } - } - if (isDefined(options.adaptiveFiltering)) { - this._setBooleanOption('pngAdaptiveFiltering', options.adaptiveFiltering); - } - } - return this._updateFormatOut('png', options); -}; - -/** - * Use these WebP options for output image. - * @param {Object} [options] - output options - * @param {Number} [options.quality=80] - quality, integer 1-100 - * @param {Boolean} [options.force=true] - force WebP output, otherwise attempt to use input format - * @returns {Sharp} - * @throws {Error} Invalid options - */ -Sharp.prototype.webp = function (options) { - if (isObject(options)) { - if (isDefined(options.quality)) { - if (isInteger(options.quality) && inRange(options.quality, 1, 100)) { - this.options.webpQuality = options.quality; - } else { - throw new Error('Invalid quality (integer, 1-100) ' + options.quality); - } - } - } - return this._updateFormatOut('webp', options); -}; - -/** - * Use these TIFF options for output image. - * @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 - * @returns {Sharp} - * @throws {Error} Invalid options - */ -Sharp.prototype.tiff = function (options) { - if (isObject(options)) { - if (isDefined(options.quality)) { - if (isInteger(options.quality) && inRange(options.quality, 1, 100)) { - this.options.tiffQuality = options.quality; - } else { - throw new Error('Invalid quality (integer, 1-100) ' + options.quality); - } - } - } - return this._updateFormatOut('tiff', options); -}; - -/** - * Force output to be raw, uncompressed uint8 pixel data. - * @returns {Sharp} - */ -Sharp.prototype.raw = function () { - return this._updateFormatOut('raw'); -}; - -/** - * Force output to a given format. - * @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 - */ -Sharp.prototype.toFormat = function (format, options) { - if (isObject(format) && isString(format.id)) { - format = format.id; - } - if (!contains(format, ['jpeg', 'png', 'webp', 'tiff', 'raw'])) { - throw new Error('Unsupported output format ' + format); - } - return this[format](options); -}; - -/** - * Called by a WriteableStream to notify us it is ready for data. - * @private - */ -Sharp.prototype._read = function () { - if (!this.options.streamOut) { - this.options.streamOut = true; - this._pipeline(); - } -}; - -/** - * Invoke the C++ image processing pipeline - * Supports callback, stream and promise variants - * @private - */ -Sharp.prototype._pipeline = function (callback) { - const that = this; - if (typeof callback === 'function') { - // output=file/buffer - if (this._isStreamInput()) { - // output=file/buffer, input=stream - this.on('finish', function () { - that._flattenBufferIn(); - sharp.pipeline(that.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.on('finish', function () { - that._flattenBufferIn(); - sharp.pipeline(that.options, function (err, data, info) { - if (err) { - that.emit('error', err); - } else { - that.emit('info', info); - that.push(data); - } - that.push(null); - }); - }); - } else { - // output=stream, input=file/buffer - sharp.pipeline(this.options, function (err, data, info) { - if (err) { - that.emit('error', err); - } else { - that.emit('info', info); - that.push(data); - } - that.push(null); - }); - } - return this; - } else { - // output=promise - if (this._isStreamInput()) { - // output=promise, input=stream - return new Promise(function (resolve, reject) { - that.on('finish', function () { - that._flattenBufferIn(); - sharp.pipeline(that.options, function (err, data) { - if (err) { - reject(err); - } else { - resolve(data); - } - }); - }); - }); - } else { - // output=promise, input=file/buffer - return new Promise(function (resolve, reject) { - sharp.pipeline(that.options, function (err, data) { - if (err) { - reject(err); - } else { - resolve(data); - } - }); - }); - } - } -}; - -/** - * Fast access to image metadata without decoding any compressed image data. - * A Promises/A+ promise is returned when `callback` is not provided. - * - * - `format`: Name of decoder used to decompress image data e.g. `jpeg`, `png`, `webp`, `gif`, `svg` - * - `width`: Number of pixels wide - * - `height`: Number of pixels high - * - `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://github.com/jcupitt/libvips/blob/master/libvips/iofuncs/enumtypes.c#L568) - * - `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK - * - `density`: Number of pixels per inch (DPI), if present - * - `hasProfile`: Boolean indicating the presence of an embedded ICC profile - * - `hasAlpha`: Boolean indicating the presence of an alpha transparency channel - * - `orientation`: Number value of the EXIF Orientation header, if present - * - `exif`: Buffer containing raw EXIF data, if present - * - `icc`: Buffer containing raw [ICC](https://www.npmjs.com/package/icc) profile data, if present - * - * @example - * const image = sharp(inputJpg); - * image - * .metadata() - * .then(function(metadata) { - * return image - * .resize(Math.round(metadata.width / 2)) - * .webp() - * .toBuffer(); - * }) - * .then(function(data) { - * // data contains a WebP image half the width and height of the original JPEG - * }); - * - * @param {Function} [callback] - called with the arguments `(err, metadata)` - * @returns {Promise|Sharp} - */ -Sharp.prototype.metadata = function (callback) { - const that = this; - if (typeof callback === 'function') { - if (this._isStreamInput()) { - this.on('finish', function () { - that._flattenBufferIn(); - sharp.metadata(that.options, callback); - }); - } else { - sharp.metadata(this.options, callback); - } - return this; - } else { - if (this._isStreamInput()) { - return new Promise(function (resolve, reject) { - that.on('finish', function () { - that._flattenBufferIn(); - sharp.metadata(that.options, function (err, metadata) { - if (err) { - reject(err); - } else { - resolve(metadata); - } - }); - }); - }); - } else { - return new Promise(function (resolve, reject) { - sharp.metadata(that.options, function (err, metadata) { - if (err) { - reject(err); - } else { - resolve(metadata); - } - }); - }); - } - } -}; - -/** - * 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 - * - * @returns {Sharp} - */ -Sharp.prototype.clone = function () { - const that = this; - // Clone existing options - const clone = new Sharp(); - util._extend(clone.options, this.options); - // Pass 'finish' event to clone for Stream-based input - this.on('finish', function () { - // Clone inherits input data - that._flattenBufferIn(); - clone.options.bufferIn = that.options.bufferIn; - clone.emit('finish'); - }); - return clone; -}; - -/** - * Gets, or when options are provided sets, the limits of _libvips'_ operation cache. - * Existing entries in the cache will be trimmed after any change in limits. - * This method always returns cache statistics, - * useful for determining how much working memory is required for a particular task. - * - * @example - * const stats = sharp.cache(); - * @example - * sharp.cache( { items: 200 } ); - * sharp.cache( { files: 0 } ); - * sharp.cache(false); - * - * @param {Object|Boolean} Object with the following attributes, or Boolean where true uses default cache settings and false removes all caching. - * @param {Number} [options.memory=50] is the maximum memory in MB to use for this cache - * @param {Number} [options.files=20] is the maximum number of files to hold open - * @param {Number} [options.items=100] is the maximum number of operations to cache - * @returns {Object} - */ -module.exports.cache = function (options) { - if (isBoolean(options)) { - if (options) { - // Default cache settings of 50MB, 20 files, 100 items - return sharp.cache(50, 20, 100); - } else { - return sharp.cache(0, 0, 0); - } - } else if (isObject(options)) { - return sharp.cache(options.memory, options.files, options.items); - } else { - return sharp.cache(); - } -}; -// Ensure default cache settings are set -module.exports.cache(true); - -/** - * Gets, or when a concurrency is provided sets, - * the number of threads _libvips'_ should create to process each image. - * The default value is the number of CPU cores. - * A value of `0` will reset to this default. - * - * The maximum number of images that can be processed in parallel - * is limited by libuv's `UV_THREADPOOL_SIZE` environment variable. - * - * This method always returns the current concurrency. - * - * @example - * const threads = sharp.concurrency(); // 4 - * sharp.concurrency(2); // 2 - * sharp.concurrency(0); // 4 - * - * @param {Number} [concurrency] - * @returns {Number} concurrency - */ -module.exports.concurrency = function (concurrency) { - return sharp.concurrency(isInteger(concurrency) ? concurrency : null); -}; - -/** - * Provides access to internal task counters. - * - queue is the number of tasks this module has queued waiting for _libuv_ to provide a worker thread from its pool. - * - process is the number of resize tasks currently being processed. - * - * @example - * const counters = sharp.counters(); // { queue: 2, process: 4 } - * - * @returns {Object} - */ -module.exports.counters = function () { - return sharp.counters(); -}; - -/** - * Get and set use of SIMD vector unit instructions. - * Requires libvips to have been compiled with liborc support. - * - * Improves the performance of `resize`, `blur` and `sharpen` operations - * by taking advantage of the SIMD vector unit of the CPU, e.g. Intel SSE and ARM NEON. - * - * This feature is currently off by default but future versions may reverse this. - * Versions of liborc prior to 0.4.25 are prone to segfault under heavy load. - * - * @example - * const simd = sharp.simd(); - * // simd is `true` if SIMD is currently enabled - * @example - * const simd = sharp.simd(true); - * // attempts to enable the use of SIMD, returning true if available - * - * @param {Boolean} [simd=false] - * @returns {Boolean} - */ -module.exports.simd = function (simd) { - return sharp.simd(isBoolean(simd) ? simd : null); -}; -// Switch off default -module.exports.simd(false); diff --git a/lib/channel.js b/lib/channel.js new file mode 100644 index 00000000..21c11b71 --- /dev/null +++ b/lib/channel.js @@ -0,0 +1,113 @@ +'use strict'; + +const is = require('./is'); + +/** + * Boolean operations for bandbool. + * @private + */ +const bool = { + and: 'and', + or: 'or', + eor: 'eor' +}; + +/** + * Extract a single channel from a multi-channel image. + * + * @example + * sharp(input) + * .extractChannel('green') + * .toFile('input_green.jpg', function(err, info) { + * // info.channels === 1 + * // input_green.jpg contains the green channel of the input image + * }); + * + * @param {Number|String} channel - zero-indexed band number to extract, or `red`, `green` or `blue` as alternative to `0`, `1` or `2` respectively. + * @returns {Sharp} + * @throws {Error} Invalid channel + */ +const extractChannel = function extractChannel (channel) { + if (channel === 'red') { + channel = 0; + } else if (channel === 'green') { + channel = 1; + } else if (channel === 'blue') { + channel = 2; + } + if (is.integer(channel) && is.inRange(channel, 0, 4)) { + this.options.extractChannel = channel; + } else { + throw new Error('Cannot extract invalid channel ' + channel); + } + return this; +}; + +/** + * Join one or more channels to the image. + * The meaning of the added channels depends on the output colourspace, set with `toColourspace()`. + * By default the output image will be web-friendly sRGB, with additional channels interpreted as alpha channels. + * Channel ordering follows vips convention: + * - sRGB: 0: Red, 1: Green, 2: Blue, 3: Alpha. + * - CMYK: 0: Magenta, 1: Cyan, 2: Yellow, 3: Black, 4: Alpha. + * + * Buffers may be any of the image formats supported by sharp: JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data. + * For raw pixel input, the `options` object should contain a `raw` attribute, which follows the format of the attribute of the same name in the `sharp()` constructor. + * + * @param {Array|String|Buffer} images - one or more images (file paths, Buffers). + * @param {Object} options - image options, see `sharp()` constructor. + * @returns {Sharp} + * @throws {Error} Invalid parameters + */ +const joinChannel = function joinChannel (images, options) { + if (Array.isArray(images)) { + images.forEach(function (image) { + this.options.joinChannelIn.push(this._createInputDescriptor(image, options)); + }, this); + } else { + this.options.joinChannelIn.push(this._createInputDescriptor(images, options)); + } + return this; +}; + +/** + * Perform a bitwise boolean operation on all input image channels (bands) to produce a single channel output image. + * + * @example + * sharp('3-channel-rgb-input.png') + * .bandbool(sharp.bool.and) + * .toFile('1-channel-output.png', function (err, info) { + * // The output will be a single channel image where each pixel `P = R & G & B`. + * // If `I(1,1) = [247, 170, 14] = [0b11110111, 0b10101010, 0b00001111]` + * // then `O(1,1) = 0b11110111 & 0b10101010 & 0b00001111 = 0b00000010 = 2`. + * }); + * + * @param {String} boolOp - one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively. + * @returns {Sharp} + * @throws {Error} Invalid parameters + */ +const bandbool = function bandbool (boolOp) { + if (is.string(boolOp) && is.inArray(boolOp, ['and', 'or', 'eor'])) { + this.options.bandBoolOp = boolOp; + } else { + throw new Error('Invalid bandbool operation ' + boolOp); + } + return this; +}; + +/** + * Decorate the Sharp prototype with channel-related functions. + * @private + */ +module.exports = function (Sharp) { + // Public instance functions + [ + extractChannel, + joinChannel, + bandbool + ].forEach(function (f) { + Sharp.prototype[f.name] = f; + }); + // Class attributes + Sharp.bool = bool; +}; diff --git a/lib/colour.js b/lib/colour.js new file mode 100644 index 00000000..9110f6f1 --- /dev/null +++ b/lib/colour.js @@ -0,0 +1,104 @@ +'use strict'; + +const color = require('color'); +const is = require('./is'); + +/** + * Colourspaces. + * @private + */ +const colourspace = { + multiband: 'multiband', + 'b-w': 'b-w', + bw: 'b-w', + cmyk: 'cmyk', + srgb: 'srgb' +}; + +/** + * Set the background for the `embed`, `flatten` and `extend` operations. + * The default background is `{r: 0, g: 0, b: 0, a: 1}`, black without transparency. + * + * Delegates to the _color_ module, which can throw an Error + * but is liberal in what it accepts, clipping values to sensible min/max. + * The alpha value is a float between `0` (transparent) and `1` (opaque). + * + * @param {String|Object} rgba - 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 parameter + */ +const background = function background (rgba) { + const colour = color(rgba); + this.options.background = colour.rgbArray(); + this.options.background.push(colour.alpha() * 255); + return this; +}; + +/** + * Convert to 8-bit greyscale; 256 shades of grey. + * This is a linear operation. If the input image is in a non-linear colour space such as sRGB, use `gamma()` with `greyscale()` for the best results. + * By default the output image will be web-friendly sRGB and contain three (identical) color channels. + * This may be overridden by other sharp operations such as `toColourspace('b-w')`, + * which will produce an output image containing one color channel. + * An alpha channel may be present, and will be unchanged by the operation. + * @param {Boolean} [greyscale=true] + * @returns {Sharp} + */ +const greyscale = function greyscale (greyscale) { + this.options.greyscale = is.bool(greyscale) ? greyscale : true; + return this; +}; + +/** + * Alternative spelling of `greyscale`. + * @param {Boolean} [grayscale=true] + * @returns {Sharp} + */ +const grayscale = function grayscale (grayscale) { + return this.greyscale(grayscale); +}; + +/** + * Set the output colourspace. + * By default output image will be web-friendly sRGB, with additional channels interpreted as alpha channels. + * @param {String} [colourspace] - output colourspace e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://github.com/jcupitt/libvips/blob/master/libvips/iofuncs/enumtypes.c#L568) + * @returns {Sharp} + * @throws {Error} Invalid parameters + */ +const toColourspace = function toColourspace (colourspace) { + if (!is.string(colourspace)) { + throw new Error('Invalid output colourspace ' + colourspace); + } + this.options.colourspace = colourspace; + return this; +}; + +/** + * Alternative spelling of `toColourspace`. + * @param {String} [colorspace] - output colorspace. + * @returns {Sharp} + * @throws {Error} Invalid parameters + */ +const toColorspace = function toColorspace (colorspace) { + return this.toColourspace(colorspace); +}; + +/** + * Decorate the Sharp prototype with colour-related functions. + * @private + */ +module.exports = function (Sharp) { + // Public instance functions + [ + background, + greyscale, + grayscale, + toColourspace, + toColorspace + ].forEach(function (f) { + Sharp.prototype[f.name] = f; + }); + // Class attributes + Sharp.colourspace = colourspace; + Sharp.colorspace = colourspace; +}; diff --git a/lib/composite.js b/lib/composite.js new file mode 100644 index 00000000..0d26b920 --- /dev/null +++ b/lib/composite.js @@ -0,0 +1,92 @@ +'use strict'; + +const is = require('./is'); + +/** + * Overlay (composite) an image over the processed (resized, extracted etc.) image. + * + * The overlay image must be the same size or smaller than the processed image. + * If both `top` and `left` options are provided, they take precedence over `gravity`. + * + * @example + * sharp('input.png') + * .rotate(180) + * .resize(300) + * .flatten() + * .background('#ff6600') + * .overlayWith('overlay.png', { gravity: sharp.gravity.southeast } ) + * .sharpen() + * .withMetadata() + * .quality(90) + * .webp() + * .toBuffer() + * .then(function(outputBuffer) { + * // outputBuffer contains upside down, 300px wide, alpha channel flattened + * // onto orange background, composited with overlay.png with SE gravity, + * // sharpened, with metadata, 90% quality WebP image data. Phew! + * }); + * + * @param {(Buffer|String)} overlay - Buffer containing image data or String containing the path to an image file. + * @param {Object} [options] + * @param {String} [options.gravity='centre'] - gravity at which to place the overlay. + * @param {Number} [options.top] - the pixel offset from the top edge. + * @param {Number} [options.left] - the pixel offset from the left edge. + * @param {Boolean} [options.tile=false] - set to true to repeat the overlay image across the entire image with the given `gravity`. + * @param {Boolean} [options.cutout=false] - set to true to apply only the alpha channel of the overlay image to the input image, giving the appearance of one image being cut out of another. + * @param {Object} [options.raw] - describes overlay when using raw pixel data. + * @param {Number} [options.raw.width] + * @param {Number} [options.raw.height] + * @param {Number} [options.raw.channels] + * @returns {Sharp} + * @throws {Error} Invalid parameters + */ +const overlayWith = function overlayWith (overlay, options) { + this.options.overlay = this._createInputDescriptor(overlay, options, { + allowStream: false + }); + if (is.object(options)) { + if (is.defined(options.tile)) { + if (is.bool(options.tile)) { + this.options.overlayTile = options.tile; + } else { + throw new Error('Invalid overlay tile ' + options.tile); + } + } + if (is.defined(options.cutout)) { + if (is.bool(options.cutout)) { + this.options.overlayCutout = options.cutout; + } else { + throw new Error('Invalid overlay cutout ' + options.cutout); + } + } + if (is.defined(options.left) || is.defined(options.top)) { + if ( + is.integer(options.left) && is.inRange(options.left, 0, this.constructor.maximum.width) && + is.integer(options.top) && is.inRange(options.top, 0, this.constructor.maximum.height) + ) { + this.options.overlayXOffset = options.left; + this.options.overlayYOffset = options.top; + } else { + throw new Error('Invalid overlay left ' + options.left + ' and/or top ' + options.top); + } + } + if (is.defined(options.gravity)) { + if (is.integer(options.gravity) && is.inRange(options.gravity, 0, 8)) { + this.options.overlayGravity = options.gravity; + } else if (is.string(options.gravity) && is.integer(this.constructor.gravity[options.gravity])) { + this.options.overlayGravity = this.constructor.gravity[options.gravity]; + } else { + throw new Error('Unsupported overlay gravity ' + options.gravity); + } + } + } + return this; +}; + +/** + * Decorate the Sharp prototype with composite-related functions. + * @private + */ +module.exports = function (Sharp) { + Sharp.prototype.overlayWith = overlayWith; +}; diff --git a/lib/constructor.js b/lib/constructor.js new file mode 100644 index 00000000..be9394ad --- /dev/null +++ b/lib/constructor.js @@ -0,0 +1,204 @@ +'use strict'; + +const path = require('path'); +const util = require('util'); +const stream = require('stream'); +const events = require('events'); +const semver = require('semver'); +const sharp = require('../build/Release/sharp.node'); + +// Versioning +let versions = { + vips: sharp.libvipsVersion() +}; +(function () { + // Does libvips meet minimum requirement? + const libvipsVersionMin = require('../package.json').config.libvips; + if (semver.lt(versions.vips, libvipsVersionMin)) { + throw new Error('Found libvips ' + versions.vips + ' but require at least ' + libvipsVersionMin); + } + // Include versions of dependencies, if present + try { + versions = require('../vendor/lib/versions.json'); + } catch (err) {} +})(); + +/** + * @name sharp + * + * Constructor factory to create an instance of `sharp`, to which further methods are chained. + * + * JPEG, PNG or WebP format image data can be streamed out from this object. + * When using Stream based output, derived attributes are available from the `info` event. + * + * Implements the [stream.Duplex](http://nodejs.org/api/stream.html#stream_class_stream_duplex) class. + * + * @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); + * + * @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 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 null or undefined. + * @param {Object} [options] - if present, is an Object with optional attributes. + * @param {Number} [options.density=72] - integral number representing the DPI for vector images. + * @param {Object} [options.raw] - describes raw pixel image data. See `raw()` for pixel ordering. + * @param {Number} [options.raw.width] + * @param {Number} [options.raw.height] + * @param {Number} [options.raw.channels] + * @returns {Sharp} + * @throws {Error} Invalid parameters + */ +const Sharp = function (input, options) { + if (!(this instanceof Sharp)) { + return new Sharp(input, options); + } + stream.Duplex.call(this); + this.options = { + // input options + sequentialRead: false, + limitInputPixels: maximum.pixels, + // ICC profiles + iccProfilePath: path.join(__dirname, 'icc') + path.sep, + // resize options + topOffsetPre: -1, + leftOffsetPre: -1, + widthPre: -1, + heightPre: -1, + topOffsetPost: -1, + leftOffsetPost: -1, + widthPost: -1, + heightPost: -1, + width: -1, + height: -1, + canvas: 'crop', + crop: 0, + angle: 0, + rotateBeforePreExtract: false, + flip: false, + flop: false, + extendTop: 0, + extendBottom: 0, + extendLeft: 0, + extendRight: 0, + withoutEnlargement: false, + kernel: 'lanczos3', + interpolator: 'bicubic', + // operations + background: [0, 0, 0, 255], + flatten: false, + negate: false, + blurSigma: 0, + sharpenSigma: 0, + sharpenFlat: 1, + sharpenJagged: 2, + threshold: 0, + thresholdGrayscale: true, + trimTolerance: 0, + gamma: 0, + greyscale: false, + normalise: 0, + booleanBufferIn: null, + booleanFileIn: '', + joinChannelIn: [], + extractChannel: -1, + colourspace: 'srgb', + // overlay + overlayGravity: 0, + overlayXOffset: -1, + overlayYOffset: -1, + overlayTile: false, + overlayCutout: false, + // output + fileOut: '', + formatOut: 'input', + streamOut: false, + withMetadata: false, + withMetadataOrientation: -1, + // output format + jpegQuality: 80, + jpegProgressive: false, + jpegChromaSubsampling: '4:2:0', + jpegTrellisQuantisation: false, + jpegOvershootDeringing: false, + jpegOptimiseScans: false, + pngProgressive: false, + pngCompressionLevel: 6, + pngAdaptiveFiltering: true, + webpQuality: 80, + tiffQuality: 80, + tileSize: 256, + tileOverlap: 0, + // Function to notify of queue length changes + queueListener: function (queueLength) { + queue.emit('change', queueLength); + } + }; + this.options.input = this._createInputDescriptor(input, options, { allowStream: true }); + return this; +}; +util.inherits(Sharp, stream.Duplex); + +/** + * Pixel limits. + * @member + * @private + */ +const maximum = { + width: 0x3FFF, + height: 0x3FFF, + pixels: Math.pow(0x3FFF, 2) +}; +Sharp.maximum = maximum; + +/** + * An EventEmitter that emits a `change` event when a task is either: + * - queued, waiting for _libuv_ to provide a worker thread + * - complete + * @member + * @example + * sharp.queue.on('change', function(queueLength) { + * console.log('Queue contains ' + queueLength + ' task(s)'); + * }); + */ +const queue = new events.EventEmitter(); +Sharp.queue = queue; + +/** + * An Object containing nested boolean values representing the available input and output formats/methods. + * @example + * console.log(sharp.format()); + * @returns {Object} + */ +Sharp.format = sharp.format(); + +/** + * An Object containing the version numbers of libvips and its dependencies. + * @member + * @example + * console.log(sharp.versions); + */ +Sharp.versions = versions; + +/** + * Export constructor. + * @private + */ +module.exports = Sharp; diff --git a/icc/cmyk.icm b/lib/icc/cmyk.icm similarity index 100% rename from icc/cmyk.icm rename to lib/icc/cmyk.icm diff --git a/icc/sRGB.icc b/lib/icc/sRGB.icc similarity index 100% rename from icc/sRGB.icc rename to lib/icc/sRGB.icc diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 00000000..0976cbb6 --- /dev/null +++ b/lib/index.js @@ -0,0 +1,17 @@ +'use strict'; + +const Sharp = require('./constructor'); +[ + 'input', + 'resize', + 'composite', + 'operation', + 'colour', + 'channel', + 'output', + 'utility' +].forEach(function (decorator) { + require('./' + decorator)(Sharp); +}); + +module.exports = Sharp; diff --git a/lib/input.js b/lib/input.js new file mode 100644 index 00000000..f92cf03e --- /dev/null +++ b/lib/input.js @@ -0,0 +1,250 @@ +'use strict'; + +const util = require('util'); +const is = require('./is'); +const sharp = require('../build/Release/sharp.node'); + +/** + * Create Object containing input and input-related options. + * @private + */ +const _createInputDescriptor = function _createInputDescriptor (input, inputOptions, containerOptions) { + const inputDescriptor = {}; + if (is.string(input)) { + // filesystem + inputDescriptor.file = input; + } else if (is.buffer(input)) { + // Buffer + inputDescriptor.buffer = input; + } else if (!is.defined(input) && is.object(containerOptions) && containerOptions.allowStream) { + // Stream + inputDescriptor.buffer = []; + } else { + throw new Error('Unsupported input ' + typeof input); + } + if (is.object(inputOptions)) { + // Density + if (is.defined(inputOptions.density)) { + if (is.integer(inputOptions.density) && is.inRange(inputOptions.density, 1, 2400)) { + inputDescriptor.density = inputOptions.density; + } else { + throw new Error('Invalid density (1 to 2400) ' + inputOptions.density); + } + } + // Raw pixel input + if (is.defined(inputOptions.raw)) { + if ( + is.object(inputOptions.raw) && + is.integer(inputOptions.raw.width) && is.inRange(inputOptions.raw.width, 1, this.constructor.maximum.width) && + is.integer(inputOptions.raw.height) && is.inRange(inputOptions.raw.height, 1, this.constructor.maximum.height) && + is.integer(inputOptions.raw.channels) && is.inRange(inputOptions.raw.channels, 1, 4) + ) { + inputDescriptor.rawWidth = inputOptions.raw.width; + inputDescriptor.rawHeight = inputOptions.raw.height; + inputDescriptor.rawChannels = inputOptions.raw.channels; + } else { + throw new Error('Expected width, height and channels for raw pixel input'); + } + } + } else if (is.defined(inputOptions)) { + throw new Error('Invalid input options ' + inputOptions); + } + return inputDescriptor; +}; + +/** + * Handle incoming Buffer chunk on Writable Stream. + * @private + * @param {Buffer} chunk + * @param {String} encoding - unused + * @param {Function} callback + */ +const _write = function _write (chunk, encoding, callback) { + if (Array.isArray(this.options.input.buffer)) { + if (is.buffer(chunk)) { + this.options.input.buffer.push(chunk); + callback(); + } else { + callback(new Error('Non-Buffer data on Writable Stream')); + } + } else { + callback(new Error('Unexpected data on Writable Stream')); + } +}; + +/** + * Flattens the array of chunks accumulated in input.buffer. + * @private + */ +const _flattenBufferIn = function _flattenBufferIn () { + if (this._isStreamInput()) { + this.options.input.buffer = Buffer.concat(this.options.input.buffer); + } +}; + +/** + * Are we expecting Stream-based input? + * @private + * @returns {Boolean} + */ +const _isStreamInput = function _isStreamInput () { + return Array.isArray(this.options.input.buffer); +}; + +/** + * 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 + * + * @returns {Sharp} + */ +const clone = function clone () { + const that = this; + // Clone existing options + const clone = this.constructor.call(); + util._extend(clone.options, this.options); + // Pass 'finish' event to clone for Stream-based input + this.on('finish', function () { + // Clone inherits input data + that._flattenBufferIn(); + clone.options.bufferIn = that.options.bufferIn; + clone.emit('finish'); + }); + return clone; +}; + +/** + * Fast access to image metadata without decoding any compressed image data. + * A Promises/A+ promise is returned when `callback` is not provided. + * + * - `format`: Name of decoder used to decompress image data e.g. `jpeg`, `png`, `webp`, `gif`, `svg` + * - `width`: Number of pixels wide + * - `height`: Number of pixels high + * - `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://github.com/jcupitt/libvips/blob/master/libvips/iofuncs/enumtypes.c#L568) + * - `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK + * - `density`: Number of pixels per inch (DPI), if present + * - `hasProfile`: Boolean indicating the presence of an embedded ICC profile + * - `hasAlpha`: Boolean indicating the presence of an alpha transparency channel + * - `orientation`: Number value of the EXIF Orientation header, if present + * - `exif`: Buffer containing raw EXIF data, if present + * - `icc`: Buffer containing raw [ICC](https://www.npmjs.com/package/icc) profile data, if present + * + * @example + * const image = sharp(inputJpg); + * image + * .metadata() + * .then(function(metadata) { + * return image + * .resize(Math.round(metadata.width / 2)) + * .webp() + * .toBuffer(); + * }) + * .then(function(data) { + * // data contains a WebP image half the width and height of the original JPEG + * }); + * + * @param {Function} [callback] - called with the arguments `(err, metadata)` + * @returns {Promise|Sharp} + */ +const metadata = function metadata (callback) { + const that = this; + if (is.fn(callback)) { + if (this._isStreamInput()) { + this.on('finish', function () { + that._flattenBufferIn(); + sharp.metadata(that.options, callback); + }); + } else { + sharp.metadata(this.options, callback); + } + return this; + } else { + if (this._isStreamInput()) { + return new Promise(function (resolve, reject) { + that.on('finish', function () { + that._flattenBufferIn(); + sharp.metadata(that.options, function (err, metadata) { + if (err) { + reject(err); + } else { + resolve(metadata); + } + }); + }); + }); + } else { + return new Promise(function (resolve, reject) { + sharp.metadata(that.options, function (err, metadata) { + if (err) { + reject(err); + } else { + resolve(metadata); + } + }); + }); + } + } +}; + +/** + * Do not process input images where the number of pixels (width * height) exceeds this limit. + * Assumes image dimensions contained in the input metadata can be trusted. + * The default limit is 268402689 (0x3FFF * 0x3FFF) pixels. + * @param {(Number|Boolean)} limit - an integral Number of pixels, zero or false to remove limit, true to use default limit. + * @returns {Sharp} + * @throws {Error} Invalid limit +*/ +const limitInputPixels = function limitInputPixels (limit) { + // if we pass in false we represent the integer as 0 to disable + if (limit === false) { + limit = 0; + } else if (limit === true) { + limit = this.constructor.maximum.pixels; + } + if (is.integer(limit) && limit >= 0) { + this.options.limitInputPixels = limit; + } else { + throw new Error('Invalid pixel limit (0 to ' + this.constructor.maximum.pixels + ') ' + limit); + } + return this; +}; + +/** + * An advanced setting that switches the libvips access method to `VIPS_ACCESS_SEQUENTIAL`. + * This will reduce memory usage and can improve performance on some systems. + * @param {Boolean} [sequentialRead=true] + * @returns {Sharp} + */ +const sequentialRead = function sequentialRead (sequentialRead) { + this.options.sequentialRead = is.bool(sequentialRead) ? sequentialRead : true; + return this; +}; + +/** + * Decorate the Sharp prototype with input-related functions. + * @private + */ +module.exports = function (Sharp) { + [ + // Private + _createInputDescriptor, + _write, + _flattenBufferIn, + _isStreamInput, + // Public + clone, + metadata, + limitInputPixels, + sequentialRead + ].forEach(function (f) { + Sharp.prototype[f.name] = f; + }); +}; diff --git a/lib/is.js b/lib/is.js new file mode 100644 index 00000000..ffa6cd91 --- /dev/null +++ b/lib/is.js @@ -0,0 +1,94 @@ +'use strict'; + +/** + * Is this value defined and not null? + * @private + */ +const defined = function (val) { + return typeof val !== 'undefined' && val !== null; +}; + +/** + * Is this value an object? + * @private + */ +const object = function (val) { + return typeof val === 'object'; +}; + +/** + * Is this value a function? + * @private + */ +const fn = function (val) { + return typeof val === 'function'; +}; + +/** + * Is this value a boolean? + * @private + */ +const bool = function (val) { + return typeof val === 'boolean'; +}; + +/** + * Is this value a Buffer object? + * @private + */ +const buffer = function (val) { + return object(val) && val instanceof Buffer; +}; + +/** + * Is this value a non-empty string? + * @private + */ +const string = function (val) { + return typeof val === 'string' && val.length > 0; +}; + +/** + * Is this value a real number? + * @private + */ +const number = function (val) { + return typeof val === 'number' && !Number.isNaN(val); +}; + +/** + * Is this value an integer? + * @private + */ +const integer = function (val) { + return number(val) && val % 1 === 0; +}; + +/** + * Is this value within an inclusive given range? + * @private + */ +const inRange = function (val, min, max) { + return val >= min && val <= max; +}; + +/** + * Is this value within the elements of an array? + * @private + */ +const inArray = function (val, list) { + return list.indexOf(val) !== -1; +}; + +module.exports = { + defined: defined, + object: object, + fn: fn, + bool: bool, + buffer: buffer, + string: string, + number: number, + integer: integer, + inRange: inRange, + inArray: inArray +}; diff --git a/lib/operation.js b/lib/operation.js new file mode 100644 index 00000000..e516691f --- /dev/null +++ b/lib/operation.js @@ -0,0 +1,431 @@ +'use strict'; + +const is = require('./is'); + +/** + * Rotate the output image by either an explicit angle + * or auto-orient based on the EXIF `Orientation` tag. + * + * Use this method without angle to determine the angle from EXIF data. + * Mirroring is supported and may infer the use of a flip operation. + * + * The use of `rotate` implies the removal of the EXIF `Orientation` tag, if any. + * + * Method order is important when both rotating and extracting regions, + * for example `rotate(x).extract(y)` will produce a different result to `extract(y).rotate(x)`. + * + * @example + * const pipeline = sharp() + * .rotate() + * .resize(null, 200) + * .toBuffer(function (err, outputBuffer, info) { + * // outputBuffer contains 200px high JPEG image data, + * // auto-rotated using EXIF Orientation tag + * // info.width and info.height contain the dimensions of the resized image + * }); + * readableStream.pipe(pipeline); + * + * @param {Number} [angle=auto] 0, 90, 180 or 270. + * @returns {Sharp} + * @throws {Error} Invalid parameters + */ +const rotate = function rotate (angle) { + if (!is.defined(angle)) { + this.options.angle = -1; + } else if (is.integer(angle) && is.inArray(angle, [0, 90, 180, 270])) { + this.options.angle = angle; + } else { + throw new Error('Unsupported angle (0, 90, 180, 270) ' + angle); + } + return this; +}; + +/** + * Extract a region of the image. + * + * - Use `extract` before `resize` for pre-resize extraction. + * - Use `extract` after `resize` for post-resize extraction. + * - Use `extract` before and after for both. + * + * @example + * sharp(input) + * .extract({ left: left, top: top, width: width, height: height }) + * .toFile(output, function(err) { + * // Extract a region of the input image, saving in the same format. + * }); + * @example + * sharp(input) + * .extract({ left: leftOffsetPre, top: topOffsetPre, width: widthPre, height: heightPre }) + * .resize(width, height) + * .extract({ left: leftOffsetPost, top: topOffsetPost, width: widthPost, height: heightPost }) + * .toFile(output, function(err) { + * // Extract a region, resize, then extract from the resized image + * }); + * + * @param {Object} options + * @param {Number} options.left - zero-indexed offset from left edge + * @param {Number} options.top - zero-indexed offset from top edge + * @param {Number} options.width - dimension of extracted image + * @param {Number} options.height - dimension of extracted image + * @returns {Sharp} + * @throws {Error} Invalid parameters + */ +const extract = function extract (options) { + const suffix = this.options.width === -1 && this.options.height === -1 ? 'Pre' : 'Post'; + ['left', 'top', 'width', 'height'].forEach(function (name) { + const value = options[name]; + if (is.integer(value) && value >= 0) { + this.options[name + (name === 'left' || name === 'top' ? 'Offset' : '') + suffix] = value; + } else { + throw new Error('Non-integer value for ' + name + ' of ' + value); + } + }, this); + // Ensure existing rotation occurs before pre-resize extraction + if (suffix === 'Pre' && this.options.angle !== 0) { + this.options.rotateBeforePreExtract = true; + } + return this; +}; + +/** + * Flip the image about the vertical Y axis. This always occurs after rotation, if any. + * The use of `flip` implies the removal of the EXIF `Orientation` tag, if any. + * @param {Boolean} [flip=true] + * @returns {Sharp} + */ +const flip = function flip (flip) { + this.options.flip = is.bool(flip) ? flip : true; + return this; +}; + +/** + * Flop the image about the horizontal X axis. This always occurs after rotation, if any. + * The use of `flop` implies the removal of the EXIF `Orientation` tag, if any. + * @param {Boolean} [flop=true] + * @returns {Sharp} + */ +const flop = function flop (flop) { + this.options.flop = is.bool(flop) ? flop : true; + return this; +}; + +/** + * Sharpen the image. + * When used without parameters, performs a fast, mild sharpen of the output image. + * When a `sigma` is provided, performs a slower, more accurate sharpen of the L channel in the LAB colour space. + * Separate control over the level of sharpening in "flat" and "jagged" areas is available. + * + * @param {Number} [sigma] - the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`. + * @param {Number} [flat=1.0] - the level of sharpening to apply to "flat" areas. + * @param {Number} [jagged=2.0] - the level of sharpening to apply to "jagged" areas. + * @returns {Sharp} + * @throws {Error} Invalid parameters + */ +const sharpen = function sharpen (sigma, flat, jagged) { + if (!is.defined(sigma)) { + // No arguments: default to mild sharpen + this.options.sharpenSigma = -1; + } else if (is.bool(sigma)) { + // Boolean argument: apply mild sharpen? + this.options.sharpenSigma = sigma ? -1 : 0; + } else if (is.number(sigma) && is.inRange(sigma, 0.01, 10000)) { + // Numeric argument: specific sigma + this.options.sharpenSigma = sigma; + // Control over flat areas + if (is.defined(flat)) { + if (is.number(flat) && is.inRange(flat, 0, 10000)) { + this.options.sharpenFlat = flat; + } else { + throw new Error('Invalid sharpen level for flat areas (0.0 - 10000.0) ' + flat); + } + } + // Control over jagged areas + if (is.defined(jagged)) { + if (is.number(jagged) && is.inRange(jagged, 0, 10000)) { + this.options.sharpenJagged = jagged; + } else { + throw new Error('Invalid sharpen level for jagged areas (0.0 - 10000.0) ' + jagged); + } + } + } else { + throw new Error('Invalid sharpen sigma (0.01 - 10000) ' + sigma); + } + return this; +}; + +/** + * Blur the image. + * When used without parameters, performs a fast, mild blur of the output image. + * When a `sigma` is provided, performs a slower, more accurate Gaussian blur. + * @param {Number} [sigma] a value between 0.3 and 1000 representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`. + * @returns {Sharp} + * @throws {Error} Invalid parameters + */ +const blur = function blur (sigma) { + if (!is.defined(sigma)) { + // No arguments: default to mild blur + this.options.blurSigma = -1; + } else if (is.bool(sigma)) { + // Boolean argument: apply mild blur? + this.options.blurSigma = sigma ? -1 : 0; + } else if (is.number(sigma) && is.inRange(sigma, 0.3, 1000)) { + // Numeric argument: specific sigma + this.options.blurSigma = sigma; + } else { + throw new Error('Invalid blur sigma (0.3 - 1000.0) ' + sigma); + } + return this; +}; + +/** + * Extends/pads the edges of the image with the colour provided to the `background` method. + * This operation will always occur after resizing and extraction, if any. + * + * @example + * // Resize to 140 pixels wide, then add 10 transparent pixels + * // to the top, left and right edges and 20 to the bottom edge + * sharp(input) + * .resize(140) + * .background({r: 0, g: 0, b: 0, a: 0}) + * .extend({top: 10, bottom: 20, left: 10, right: 10}) + * ... + * + * @param {(Number|Object)} extend - single pixel count to add to all edges or an Object with per-edge counts + * @param {Number} [extend.top] + * @param {Number} [extend.left] + * @param {Number} [extend.bottom] + * @param {Number} [extend.right] + * @returns {Sharp} + * @throws {Error} Invalid parameters +*/ +const extend = function extend (extend) { + if (is.integer(extend) && extend > 0) { + this.options.extendTop = extend; + this.options.extendBottom = extend; + this.options.extendLeft = extend; + this.options.extendRight = extend; + } else if ( + is.object(extend) && + is.integer(extend.top) && extend.top >= 0 && + is.integer(extend.bottom) && extend.bottom >= 0 && + is.integer(extend.left) && extend.left >= 0 && + is.integer(extend.right) && extend.right >= 0 + ) { + this.options.extendTop = extend.top; + this.options.extendBottom = extend.bottom; + this.options.extendLeft = extend.left; + this.options.extendRight = extend.right; + } else { + throw new Error('Invalid edge extension ' + extend); + } + return this; +}; + +/** + * Merge alpha transparency channel, if any, with `background`. + * @param {Boolean} [flatten=true] + * @returns {Sharp} + */ +const flatten = function flatten (flatten) { + this.options.flatten = is.bool(flatten) ? flatten : true; + return this; +}; + +/** + * Trim "boring" pixels from all edges that contain values within a percentage similarity of the top-left pixel. + * @param {Number} [tolerance=10] value between 1 and 99 representing the percentage similarity. + * @returns {Sharp} + * @throws {Error} Invalid parameters + */ +const trim = function trim (tolerance) { + if (!is.defined(tolerance)) { + this.options.trimTolerance = 10; + } else if (is.integer(tolerance) && is.inRange(tolerance, 1, 99)) { + this.options.trimTolerance = tolerance; + } else { + throw new Error('Invalid trim tolerance (1 to 99) ' + tolerance); + } + return this; +}; + +/** + * Apply a gamma correction by reducing the encoding (darken) pre-resize at a factor of `1/gamma` + * then increasing the encoding (brighten) post-resize at a factor of `gamma`. + * This can improve the perceived brightness of a resized image in non-linear colour spaces. + * JPEG and WebP input images will not take advantage of the shrink-on-load performance optimisation + * when applying a gamma correction. + * @param {Number} [gamma=2.2] value between 1.0 and 3.0. + * @returns {Sharp} + * @throws {Error} Invalid parameters + */ +const gamma = function gamma (gamma) { + if (!is.defined(gamma)) { + // Default gamma correction of 2.2 (sRGB) + this.options.gamma = 2.2; + } else if (is.number(gamma) && is.inRange(gamma, 1, 3)) { + this.options.gamma = gamma; + } else { + throw new Error('Invalid gamma correction (1.0 to 3.0) ' + gamma); + } + return this; +}; + +/** + * Produce the "negative" of the image. + * @param {Boolean} [negate=true] + * @returns {Sharp} + */ +const negate = function negate (negate) { + this.options.negate = is.bool(negate) ? negate : true; + return this; +}; + +/** + * Enhance output image contrast by stretching its luminance to cover the full dynamic range. + * @param {Boolean} [normalise=true] + * @returns {Sharp} + */ +const normalise = function normalise (normalise) { + this.options.normalise = is.bool(normalise) ? normalise : true; + return this; +}; + +/** + * Alternative spelling of normalise. + * @param {Boolean} [normalize=true] + * @returns {Sharp} + */ +const normalize = function normalize (normalize) { + return this.normalise(normalize); +}; + +/** + * Convolve the image with the specified kernel. + * + * @example + * sharp(input) + * .convolve({ + * width: 3, + * height: 3, + * kernel: [-1, 0, 1, -2, 0, 2, -1, 0, 1] + * }) + * .raw() + * .toBuffer(function(err, data, info) { + * // data contains the raw pixel data representing the convolution + * // of the input image with the horizontal Sobel operator + * }); + * + * @param {Object} kernel + * @param {Number} kernel.width - width of the kernel in pixels. + * @param {Number} kernel.height - width of the kernel in pixels. + * @param {Array} kernel.kernel - Array of length `width*height` containing the kernel values. + * @param {Number} [kernel.scale=sum] - the scale of the kernel in pixels. + * @param {Number} [kernel.offset=0] - the offset of the kernel in pixels. + * @returns {Sharp} + * @throws {Error} Invalid parameters + */ +const convolve = function convolve (kernel) { + if (!is.object(kernel) || !Array.isArray(kernel.kernel) || + !is.integer(kernel.width) || !is.integer(kernel.height) || + !is.inRange(kernel.width, 3, 1001) || !is.inRange(kernel.height, 3, 1001) || + kernel.height * kernel.width !== kernel.kernel.length + ) { + // must pass in a kernel + throw new Error('Invalid convolution kernel'); + } + // Default scale is sum of kernel values + if (!is.integer(kernel.scale)) { + kernel.scale = kernel.kernel.reduce(function (a, b) { + return a + b; + }, 0); + } + // Clip scale to a minimum value of 1 + if (kernel.scale < 1) { + kernel.scale = 1; + } + if (!is.integer(kernel.offset)) { + kernel.offset = 0; + } + this.options.convKernel = kernel; + return this; +}; + +/** + * Any pixel value greather than or equal to the threshold value will be set to 255, otherwise it will be set to 0. + * @param {Number} [threshold=128] - a value in the range 0-255 representing the level at which the threshold will be applied. + * @param {Object} [options] + * @param {Boolean} [options.greyscale=true] - convert to single channel greyscale. + * @param {Boolean} [options.grayscale=true] - alternative spelling for greyscale. + * @returns {Sharp} + * @throws {Error} Invalid parameters + */ +const threshold = function threshold (threshold, options) { + if (!is.defined(threshold)) { + this.options.threshold = 128; + } else if (is.bool(threshold)) { + this.options.threshold = threshold ? 128 : 0; + } else if (is.integer(threshold) && is.inRange(threshold, 0, 255)) { + this.options.threshold = threshold; + } else { + throw new Error('Invalid threshold (0 to 255) ' + threshold); + } + if (!is.object(options) || options.greyscale === true || options.grayscale === true) { + this.options.thresholdGrayscale = true; + } else { + this.options.thresholdGrayscale = false; + } + return this; +}; + +/** + * Perform a bitwise boolean operation with operand image. + * + * This operation creates an output image where each pixel is the result of + * the selected bitwise boolean `operation` between the corresponding pixels of the input images. + * + * @param {Buffer|String} operand - Buffer containing image data or String containing the path to an image file. + * @param {String} operator - one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively. + * @param {Object} [options] + * @param {Object} [options.raw] - describes operand when using raw pixel data. + * @param {Number} [options.raw.width] + * @param {Number} [options.raw.height] + * @param {Number} [options.raw.channels] + * @returns {Sharp} + * @throws {Error} Invalid parameters + */ +const boolean = function boolean (operand, operator, options) { + this.options.boolean = this._createInputDescriptor(operand, options); + if (is.string(operator) && is.inArray(operator, ['and', 'or', 'eor'])) { + this.options.booleanOp = operator; + } else { + throw new Error('Invalid boolean operator ' + operator); + } + return this; +}; + +/** + * Decorate the Sharp prototype with operation-related functions. + * @private + */ +module.exports = function (Sharp) { + [ + rotate, + extract, + flip, + flop, + sharpen, + blur, + extend, + flatten, + trim, + gamma, + negate, + normalise, + normalize, + convolve, + threshold, + boolean + ].forEach(function (f) { + Sharp.prototype[f.name] = f; + }); +}; diff --git a/lib/output.js b/lib/output.js new file mode 100644 index 00000000..a10feb96 --- /dev/null +++ b/lib/output.js @@ -0,0 +1,502 @@ +'use strict'; + +const util = require('util'); +const is = require('./is'); +const sharp = require('../build/Release/sharp.node'); + +/** + * 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. + * + * A Promises/A+ promise is returned when `callback` is not provided. + * + * @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` and `channels`. + * @returns {Promise} - when no callback is provided + * @throws {Error} Invalid parameters + */ +const toFile = function toFile (fileOut, callback) { + if (!fileOut || fileOut.length === 0) { + const errOutputInvalid = new Error('Invalid output'); + 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. + * By default, the format will match the input image. JPEG, PNG, WebP, and RAW are supported. + * `callback`, if present, gets three arguments `(err, buffer, info)` where: + * - `err` is an error message, if any. + * - `buffer` is the output image data. + * - `info` contains the output image `format`, `size` (bytes), `width`, `height` and `channels`. + * A Promises/A+ promise is returned when `callback` is not provided. + * + * @param {Function} [callback] + * @returns {Promise} - when no callback is provided + */ +const toBuffer = function toBuffer (callback) { + return this._pipeline(callback); +}; + +/** + * Include all metadata (EXIF, XMP, IPTC) from the input image in the output image. + * The default behaviour, when `withMetadata` is not used, is to strip all metadata and convert to the device-independent sRGB colour space. + * This will also convert to and add a web-friendly sRGB ICC profile. + * @param {Object} [withMetadata] + * @param {Number} [withMetadata.orientation] value between 1 and 8, used to update the EXIF `Orientation` tag. + * @returns {Sharp} + * @throws {Error} Invalid parameters + */ +const withMetadata = function withMetadata (withMetadata) { + this.options.withMetadata = is.bool(withMetadata) ? withMetadata : true; + if (is.object(withMetadata)) { + if (is.defined(withMetadata.orientation)) { + if (is.integer(withMetadata.orientation) && is.inRange(withMetadata.orientation, 1, 8)) { + this.options.withMetadataOrientation = withMetadata.orientation; + } else { + throw new Error('Invalid orientation (1 to 8) ' + withMetadata.orientation); + } + } + } + return this; +}; + +/** + * Use these JPEG options for output image. + * @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'] - set to '4:4:4' to prevent chroma subsampling when quality <= 90 + * @param {Boolean} [trellisQuantisation=false] - apply trellis quantisation, requires mozjpeg + * @param {Boolean} [overshootDeringing=false] - apply overshoot deringing, requires mozjpeg + * @param {Boolean} [optimiseScans=false] - optimise progressive scans, forces progressive, requires mozjpeg + * @param {Boolean} [options.force=true] - force JPEG output, otherwise attempt to use input format + * @returns {Sharp} + * @throws {Error} Invalid options + */ +const jpeg = 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 new Error('Invalid quality (integer, 1-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 new Error('Invalid chromaSubsampling (4:2:0, 4:4:4) ' + options.chromaSubsampling); + } + } + options.trellisQuantisation = options.trellisQuantisation || options.trellisQuantization; + if (is.defined(options.trellisQuantisation)) { + this._setBooleanOption('jpegTrellisQuantisation', options.trellisQuantisation); + } + if (is.defined(options.overshootDeringing)) { + this._setBooleanOption('jpegOvershootDeringing', options.overshootDeringing); + } + options.optimiseScans = options.optimiseScans || options.optimizeScans; + if (is.defined(options.optimiseScans)) { + this._setBooleanOption('jpegOptimiseScans', options.optimiseScans); + if (options.optimiseScans) { + this.options.jpegProgressive = true; + } + } + } + return this._updateFormatOut('jpeg', options); +}; + +/** + * Use these PNG options for output image. + * @param {Object} [options] + * @param {Boolean} [options.progressive=false] - use progressive (interlace) scan + * @param {Number} [options.compressionLevel=6] - zlib compression level + * @param {Boolean} [options.adaptiveFiltering=true] - use adaptive row filtering + * @param {Boolean} [options.force=true] - force PNG output, otherwise attempt to use input format + * @returns {Sharp} + * @throws {Error} Invalid options + */ +const png = 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 new Error('Invalid compressionLevel (integer, 0-9) ' + options.compressionLevel); + } + } + if (is.defined(options.adaptiveFiltering)) { + this._setBooleanOption('pngAdaptiveFiltering', options.adaptiveFiltering); + } + } + return this._updateFormatOut('png', options); +}; + +/** + * Use these WebP options for output image. + * @param {Object} [options] - output options + * @param {Number} [options.quality=80] - quality, integer 1-100 + * @param {Boolean} [options.force=true] - force WebP output, otherwise attempt to use input format + * @returns {Sharp} + * @throws {Error} Invalid options + */ +const webp = function webp (options) { + if (is.object(options)) { + if (is.defined(options.quality)) { + if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) { + this.options.webpQuality = options.quality; + } else { + throw new Error('Invalid quality (integer, 1-100) ' + options.quality); + } + } + } + return this._updateFormatOut('webp', options); +}; + +/** + * Use these TIFF options for output image. + * @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 + * @returns {Sharp} + * @throws {Error} Invalid options + */ +const tiff = 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 new Error('Invalid quality (integer, 1-100) ' + options.quality); + } + } + } + return this._updateFormatOut('tiff', options); +}; + +/** + * Force output to be raw, uncompressed uint8 pixel data. + * @returns {Sharp} + */ +const raw = function raw () { + return this._updateFormatOut('raw'); +}; + +/** + * Force output to a given format. + * @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 + */ +const toFormat = function toFormat (format, options) { + if (is.object(format) && is.string(format.id)) { + format = format.id; + } + if (!is.inArray(format, ['jpeg', 'png', 'webp', 'tiff', 'raw'])) { + throw new Error('Unsupported output format ' + format); + } + return this[format](options); +}; + +/** + * Use tile-based deep zoom (image pyramid) output. + * You can also use a `.zip` or `.szi` file extension with `toFile` to write to a compressed archive file format. + * + * @example + * sharp('input.tiff') + * .tile({ + * size: 512 + * }) + * .toFile('output.dzi', function(err, info) { + * // output.dzi is the Deep Zoom XML definition + * // output_files contains 512x512 tiles grouped by zoom level + * }); + * + * @param {Object} [tile] + * @param {Number} [tile.size=256] tile size in pixels, a value between 1 and 8192. + * @param {Number} [tile.overlap=0] tile overlap in pixels, a value between 0 and 8192. + * @param {String} [tile.container='fs'] tile container, with value `fs` (filesystem) or `zip` (compressed file). + * @param {String} [tile.layout='dz'] filesystem layout, possible values are `dz`, `zoomify` or `google`. + * @returns {Sharp} + * @throws {Error} Invalid parameters + */ +const tile = function tile (tile) { + if (is.object(tile)) { + // Size of square tiles, in pixels + if (is.defined(tile.size)) { + if (is.integer(tile.size) && is.inRange(tile.size, 1, 8192)) { + this.options.tileSize = tile.size; + } else { + throw new Error('Invalid tile size (1 to 8192) ' + tile.size); + } + } + // Overlap of tiles, in pixels + if (is.defined(tile.overlap)) { + if (is.integer(tile.overlap) && is.inRange(tile.overlap, 0, 8192)) { + if (tile.overlap > this.options.tileSize) { + throw new Error('Tile overlap ' + tile.overlap + ' cannot be larger than tile size ' + this.options.tileSize); + } + this.options.tileOverlap = tile.overlap; + } else { + throw new Error('Invalid tile overlap (0 to 8192) ' + tile.overlap); + } + } + // Container + if (is.defined(tile.container)) { + if (is.string(tile.container) && is.inArray(tile.container, ['fs', 'zip'])) { + this.options.tileContainer = tile.container; + } else { + throw new Error('Invalid tile container ' + tile.container); + } + } + // Layout + if (is.defined(tile.layout)) { + if (is.string(tile.layout) && is.inArray(tile.layout, ['dz', 'google', 'zoomify'])) { + this.options.tileLayout = tile.layout; + } else { + throw new Error('Invalid tile layout ' + tile.layout); + } + } + } + return this; +}; + +/** + * 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} + */ +const _updateFormatOut = function _updateFormatOut (formatOut, options) { + this.options.formatOut = (is.object(options) && options.force === false) ? 'input' : formatOut; + return this; +}; + +/** + * Update a Boolean attribute of the this.options Object. + * @private + * @param {String} key + * @param {Boolean} val + * @throws {Error} Invalid key + */ +const _setBooleanOption = function _setBooleanOption (key, val) { + if (is.bool(val)) { + this.options[key] = val; + } else { + throw new Error('Invalid ' + key + ' (boolean) ' + val); + } +}; + +/** + * Called by a WriteableStream to notify us it is ready for data. + * @private + */ +const _read = function _read () { + if (!this.options.streamOut) { + this.options.streamOut = true; + this._pipeline(); + } +}; + +/** + * Invoke the C++ image processing pipeline + * Supports callback, stream and promise variants + * @private + */ +const _pipeline = function _pipeline (callback) { + const that = this; + if (typeof callback === 'function') { + // output=file/buffer + if (this._isStreamInput()) { + // output=file/buffer, input=stream + this.on('finish', function () { + that._flattenBufferIn(); + sharp.pipeline(that.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.on('finish', function () { + that._flattenBufferIn(); + sharp.pipeline(that.options, function (err, data, info) { + if (err) { + that.emit('error', err); + } else { + that.emit('info', info); + that.push(data); + } + that.push(null); + }); + }); + } else { + // output=stream, input=file/buffer + sharp.pipeline(this.options, function (err, data, info) { + if (err) { + that.emit('error', err); + } else { + that.emit('info', info); + that.push(data); + } + that.push(null); + }); + } + return this; + } else { + // output=promise + if (this._isStreamInput()) { + // output=promise, input=stream + return new Promise(function (resolve, reject) { + that.on('finish', function () { + that._flattenBufferIn(); + sharp.pipeline(that.options, function (err, data) { + if (err) { + reject(err); + } else { + resolve(data); + } + }); + }); + }); + } else { + // output=promise, input=file/buffer + return new Promise(function (resolve, reject) { + sharp.pipeline(that.options, function (err, data) { + if (err) { + reject(err); + } else { + resolve(data); + } + }); + }); + } + } +}; + +// Deprecated output options +const quality = util.deprecate(function (quality) { + const formatOut = this.options.formatOut; + const options = { quality: quality }; + this.jpeg(options).webp(options).tiff(options); + this.options.formatOut = formatOut; + return this; +}, 'quality: use jpeg({ quality: ... }), webp({ quality: ... }) and/or tiff({ quality: ... }) instead'); +const progressive = util.deprecate(function (progressive) { + const formatOut = this.options.formatOut; + const options = { progressive: (progressive !== false) }; + this.jpeg(options).png(options); + this.options.formatOut = formatOut; + return this; +}, 'progressive: use jpeg({ progressive: ... }) and/or png({ progressive: ... }) instead'); +const compressionLevel = util.deprecate(function (compressionLevel) { + const formatOut = this.options.formatOut; + this.png({ compressionLevel: compressionLevel }); + this.options.formatOut = formatOut; + return this; +}, 'compressionLevel: use png({ compressionLevel: ... }) instead'); +const withoutAdaptiveFiltering = util.deprecate(function (withoutAdaptiveFiltering) { + const formatOut = this.options.formatOut; + this.png({ adaptiveFiltering: (withoutAdaptiveFiltering === false) }); + this.options.formatOut = formatOut; + return this; +}, 'withoutAdaptiveFiltering: use png({ adaptiveFiltering: ... }) instead'); +const withoutChromaSubsampling = util.deprecate(function (withoutChromaSubsampling) { + const formatOut = this.options.formatOut; + this.jpeg({ chromaSubsampling: (withoutChromaSubsampling === false) ? '4:2:0' : '4:4:4' }); + this.options.formatOut = formatOut; + return this; +}, 'withoutChromaSubsampling: use jpeg({ chromaSubsampling: "4:4:4" }) instead'); +const trellisQuantisation = util.deprecate(function (trellisQuantisation) { + const formatOut = this.options.formatOut; + this.jpeg({ trellisQuantisation: (trellisQuantisation !== false) }); + this.options.formatOut = formatOut; + return this; +}, 'trellisQuantisation: use jpeg({ trellisQuantisation: ... }) instead'); +const overshootDeringing = util.deprecate(function (overshootDeringing) { + const formatOut = this.options.formatOut; + this.jpeg({ overshootDeringing: (overshootDeringing !== false) }); + this.options.formatOut = formatOut; + return this; +}, 'overshootDeringing: use jpeg({ overshootDeringing: ... }) instead'); +const optimiseScans = util.deprecate(function (optimiseScans) { + const formatOut = this.options.formatOut; + this.jpeg({ optimiseScans: (optimiseScans !== false) }); + this.options.formatOut = formatOut; + return this; +}, 'optimiseScans: use jpeg({ optimiseScans: ... }) instead'); + +/** + * Decorate the Sharp prototype with output-related functions. + * @private + */ +module.exports = function (Sharp) { + [ + // Public + toFile, + toBuffer, + withMetadata, + jpeg, + png, + webp, + tiff, + raw, + toFormat, + tile, + // Private + _updateFormatOut, + _setBooleanOption, + _read, + _pipeline + ].forEach(function (f) { + Sharp.prototype[f.name] = f; + }); + // Deprecated + Sharp.prototype.quality = quality; + Sharp.prototype.progressive = progressive; + Sharp.prototype.compressionLevel = compressionLevel; + Sharp.prototype.withoutAdaptiveFiltering = withoutAdaptiveFiltering; + Sharp.prototype.withoutChromaSubsampling = withoutChromaSubsampling; + Sharp.prototype.trellisQuantisation = trellisQuantisation; + Sharp.prototype.trellisQuantization = trellisQuantisation; + Sharp.prototype.overshootDeringing = overshootDeringing; + Sharp.prototype.optimiseScans = optimiseScans; + Sharp.prototype.optimizeScans = optimiseScans; +}; diff --git a/lib/resize.js b/lib/resize.js new file mode 100644 index 00000000..d049bbff --- /dev/null +++ b/lib/resize.js @@ -0,0 +1,294 @@ +'use strict'; + +const is = require('./is'); + +/** + * Weighting to apply to image crop. + * @member + * @private + */ +const gravity = { + center: 0, + centre: 0, + north: 1, + east: 2, + south: 3, + west: 4, + northeast: 5, + southeast: 6, + southwest: 7, + northwest: 8 +}; + +/** + * Strategies for automagic crop behaviour. + * @member + * @private + */ +const strategy = { + entropy: 16, + attention: 17 +}; + +/** + * Reduction kernels. + * @member + * @private + */ +const kernel = { + cubic: 'cubic', + lanczos2: 'lanczos2', + lanczos3: 'lanczos3' +}; + +/** + * Enlargement interpolators. + * @member + * @private + */ +const interpolator = { + nearest: 'nearest', + bilinear: 'bilinear', + bicubic: 'bicubic', + nohalo: 'nohalo', + lbb: 'lbb', + locallyBoundedBicubic: 'lbb', + vsqbs: 'vsqbs', + vertexSplitQuadraticBasisSpline: 'vsqbs' +}; + +/** + * Resize image to `width` x `height`. + * By default, the resized image is centre cropped to the exact size specified. + * + * Possible reduction kernels are: + * - `cubic`: Use a [Catmull-Rom spline](https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline). + * - `lanczos2`: Use a [Lanczos kernel](https://en.wikipedia.org/wiki/Lanczos_resampling#Lanczos_kernel) with `a=2`. + * - `lanczos3`: Use a Lanczos kernel with `a=3` (the default). + * + * Possible enlargement interpolators are: + * - `nearest`: Use [nearest neighbour interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation). + * - `bilinear`: Use [bilinear interpolation](http://en.wikipedia.org/wiki/Bilinear_interpolation), faster than bicubic but with less smooth results. + * - `vertexSplitQuadraticBasisSpline`: Use the smoother [VSQBS interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/vsqbs.cpp#L48) to prevent "staircasing" when enlarging. + * - `bicubic`: Use [bicubic interpolation](http://en.wikipedia.org/wiki/Bicubic_interpolation) (the default). + * - `locallyBoundedBicubic`: Use [LBB interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/lbb.cpp#L100), which prevents some "[acutance](http://en.wikipedia.org/wiki/Acutance)" but typically reduces performance by a factor of 2. + * - `nohalo`: Use [Nohalo interpolation](http://eprints.soton.ac.uk/268086/), which prevents acutance but typically reduces performance by a factor of 3. + * + * @example + * sharp(inputBuffer) + * .resize(200, 300, { + * kernel: sharp.kernel.lanczos2, + * interpolator: sharp.interpolator.nohalo + * }) + * .background('white') + * .embed() + * .toFile('output.tiff') + * .then(function() { + * // output.tiff is a 200 pixels wide and 300 pixels high image + * // containing a lanczos2/nohalo scaled version, embedded on a white canvas, + * // of the image data in inputBuffer + * }); + * + * @param {Number} [width] - pixels wide the resultant image should be, between 1 and 16383 (0x3FFF). Use `null` or `undefined` to auto-scale the width to match the height. + * @param {Number} [height] - pixels high the resultant image should be, between 1 and 16383. Use `null` or `undefined` to auto-scale the height to match the width. + * @param {Object} [options] + * @param {String} [options.kernel='lanczos3'] - the kernel to use for image reduction. + * @param {String} [options.interpolator='bicubic'] - the interpolator to use for image enlargement. + * @returns {Sharp} + * @throws {Error} Invalid parameters + */ +const resize = function resize (width, height, options) { + if (is.defined(width)) { + if (is.integer(width) && is.inRange(width, 1, this.constructor.maximum.width)) { + this.options.width = width; + } else { + throw new Error('Invalid width (1 to ' + this.constructor.maximum.width + ') ' + width); + } + } else { + this.options.width = -1; + } + if (is.defined(height)) { + if (is.integer(height) && is.inRange(height, 1, this.constructor.maximum.height)) { + this.options.height = height; + } else { + throw new Error('Invalid height (1 to ' + this.constructor.maximum.height + ') ' + height); + } + } else { + this.options.height = -1; + } + if (is.object(options)) { + // Kernel + if (is.defined(options.kernel)) { + if (is.string(kernel[options.kernel])) { + this.options.kernel = kernel[options.kernel]; + } else { + throw new Error('Invalid kernel ' + options.kernel); + } + } + // Interpolator + if (is.defined(options.interpolator)) { + if (is.string(interpolator[options.interpolator])) { + this.options.interpolator = interpolator[options.interpolator]; + } else { + throw new Error('Invalid interpolator ' + options.interpolator); + } + } + } + return this; +}; + +/** + * Crop the resized image to the exact size specified, the default behaviour. + * + * Possible attributes of the optional `sharp.gravity` are `north`, `northeast`, `east`, `southeast`, `south`, + * `southwest`, `west`, `northwest`, `center` and `centre`. + * + * The experimental strategy-based approach resizes so one dimension is at its target length + * then repeatedly ranks edge regions, discarding the edge with the lowest score based on the selected strategy. + * - `entropy`: focus on the region with the highest [Shannon entropy](https://en.wikipedia.org/wiki/Entropy_%28information_theory%29). + * - `attention`: focus on the region with the highest luminance frequency, colour saturation and presence of skin tones. + * + * @example + * const transformer = sharp() + * .resize(200, 200) + * .crop(sharp.strategy.entropy) + * .on('error', function(err) { + * console.log(err); + * }); + * // Read image data from readableStream + * // Write 200px square auto-cropped image data to writableStream + * readableStream.pipe(transformer).pipe(writableStream); + * + * @param {String} [crop='centre'] - A member of `sharp.gravity` to crop to an edge/corner or `sharp.strategy` to crop dynamically. + * @returns {Sharp} + * @throws {Error} Invalid parameters + */ +const crop = function crop (crop) { + this.options.canvas = 'crop'; + if (!is.defined(crop)) { + // Default + this.options.crop = gravity.center; + } else if (is.integer(crop) && is.inRange(crop, 0, 8)) { + // Gravity (numeric) + this.options.crop = crop; + } else if (is.string(crop) && is.integer(gravity[crop])) { + // Gravity (string) + this.options.crop = gravity[crop]; + } else if (is.integer(crop) && crop >= strategy.entropy) { + // Strategy + this.options.crop = crop; + } else { + throw new Error('Unsupported crop ' + crop); + } + return this; +}; + +/** + * Preserving aspect ratio, resize the image to the maximum `width` or `height` specified + * then embed on a background of the exact `width` and `height` specified. + * + * If the background contains an alpha value then WebP and PNG format output images will + * contain an alpha channel, even when the input image does not. + * + * @example + * sharp('input.gif') + * .resize(200, 300) + * .background({r: 0, g: 0, b: 0, a: 0}) + * .embed() + * .toFormat(sharp.format.webp) + * .toBuffer(function(err, outputBuffer) { + * if (err) { + * throw err; + * } + * // outputBuffer contains WebP image data of a 200 pixels wide and 300 pixels high + * // containing a scaled version, embedded on a transparent canvas, of input.gif + * }); + * + * @returns {Sharp} + */ +const embed = function embed () { + this.options.canvas = 'embed'; + return this; +}; + +/** + * Preserving aspect ratio, resize the image to be as large as possible + * while ensuring its dimensions are less than or equal to the `width` and `height` specified. + * + * Both `width` and `height` must be provided via `resize` otherwise the behaviour will default to `crop`. + * + * @example + * sharp(inputBuffer) + * .resize(200, 200) + * .max() + * .toFormat('jpeg') + * .toBuffer() + * .then(function(outputBuffer) { + * // outputBuffer contains JPEG image data no wider than 200 pixels and no higher + * // than 200 pixels regardless of the inputBuffer image dimensions + * }); + * + * @returns {Sharp} + */ +const max = function max () { + this.options.canvas = 'max'; + return this; +}; + +/** + * Preserving aspect ratio, resize the image to be as small as possible + * while ensuring its dimensions are greater than or equal to the `width` and `height` specified. + * + * Both `width` and `height` must be provided via `resize` otherwise the behaviour will default to `crop`. + * + * @returns {Sharp} + */ +const min = function min () { + this.options.canvas = 'min'; + return this; +}; + +/** + * Ignoring the aspect ratio of the input, stretch the image to + * the exact `width` and/or `height` provided via `resize`. + * @returns {Sharp} + */ +const ignoreAspectRatio = function ignoreAspectRatio () { + this.options.canvas = 'ignore_aspect'; + return this; +}; + +/** + * Do not enlarge the output image if the input image width *or* height are already less than the required dimensions. + * This is equivalent to GraphicsMagick's `>` geometry option: + * "*change the dimensions of the image only if its width or height exceeds the geometry specification*". + * @param {Boolean} [withoutEnlargement=true] + * @returns {Sharp} +*/ +const withoutEnlargement = function withoutEnlargement (withoutEnlargement) { + this.options.withoutEnlargement = is.bool(withoutEnlargement) ? withoutEnlargement : true; + return this; +}; + +/** + * Decorate the Sharp prototype with resize-related functions. + * @private + */ +module.exports = function (Sharp) { + [ + resize, + crop, + embed, + max, + min, + ignoreAspectRatio, + withoutEnlargement + ].forEach(function (f) { + Sharp.prototype[f.name] = f; + }); + // Class attributes + Sharp.gravity = gravity; + Sharp.strategy = strategy; + Sharp.kernel = kernel; + Sharp.interpolator = interpolator; +}; diff --git a/lib/utility.js b/lib/utility.js new file mode 100644 index 00000000..f47d5c52 --- /dev/null +++ b/lib/utility.js @@ -0,0 +1,116 @@ +'use strict'; + +const is = require('./is'); +const sharp = require('../build/Release/sharp.node'); + +/** + * Gets, or when options are provided sets, the limits of _libvips'_ operation cache. + * Existing entries in the cache will be trimmed after any change in limits. + * This method always returns cache statistics, + * useful for determining how much working memory is required for a particular task. + * + * @example + * const stats = sharp.cache(); + * @example + * sharp.cache( { items: 200 } ); + * sharp.cache( { files: 0 } ); + * sharp.cache(false); + * + * @param {Object|Boolean} options - Object with the following attributes, or Boolean where true uses default cache settings and false removes all caching. + * @param {Number} [options.memory=50] - is the maximum memory in MB to use for this cache + * @param {Number} [options.files=20] - is the maximum number of files to hold open + * @param {Number} [options.items=100] - is the maximum number of operations to cache + * @returns {Object} + */ +const cache = function cache (options) { + if (is.bool(options)) { + if (options) { + // Default cache settings of 50MB, 20 files, 100 items + return sharp.cache(50, 20, 100); + } else { + return sharp.cache(0, 0, 0); + } + } else if (is.object(options)) { + return sharp.cache(options.memory, options.files, options.items); + } else { + return sharp.cache(); + } +}; +cache(true); + +/** + * Gets, or when a concurrency is provided sets, + * the number of threads _libvips'_ should create to process each image. + * The default value is the number of CPU cores. + * A value of `0` will reset to this default. + * + * The maximum number of images that can be processed in parallel + * is limited by libuv's `UV_THREADPOOL_SIZE` environment variable. + * + * This method always returns the current concurrency. + * + * @example + * const threads = sharp.concurrency(); // 4 + * sharp.concurrency(2); // 2 + * sharp.concurrency(0); // 4 + * + * @param {Number} [concurrency] + * @returns {Number} concurrency + */ +const concurrency = function concurrency (concurrency) { + return sharp.concurrency(is.integer(concurrency) ? concurrency : null); +}; + +/** + * Provides access to internal task counters. + * - queue is the number of tasks this module has queued waiting for _libuv_ to provide a worker thread from its pool. + * - process is the number of resize tasks currently being processed. + * + * @example + * const counters = sharp.counters(); // { queue: 2, process: 4 } + * + * @returns {Object} + */ +const counters = function counters () { + return sharp.counters(); +}; + +/** + * Get and set use of SIMD vector unit instructions. + * Requires libvips to have been compiled with liborc support. + * + * Improves the performance of `resize`, `blur` and `sharpen` operations + * by taking advantage of the SIMD vector unit of the CPU, e.g. Intel SSE and ARM NEON. + * + * This feature is currently off by default but future versions may reverse this. + * Versions of liborc prior to 0.4.25 are known to segfault under heavy load. + * + * @example + * const simd = sharp.simd(); + * // simd is `true` if SIMD is currently enabled + * @example + * const simd = sharp.simd(true); + * // attempts to enable the use of SIMD, returning true if available + * + * @param {Boolean} [simd=false] + * @returns {Boolean} + */ +const simd = function simd (simd) { + return sharp.simd(is.bool(simd) ? simd : null); +}; +simd(false); + +/** + * Decorate the Sharp class with utility-related functions. + * @private + */ +module.exports = function (Sharp) { + [ + cache, + concurrency, + counters, + simd + ].forEach(function (f) { + Sharp[f.name] = f; + }); +}; diff --git a/mkdocs.yml b/mkdocs.yml index ee9462dc..1ab43a3d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -12,6 +12,15 @@ dev_addr: 0.0.0.0:10101 pages: - Home: index.md - Installation: install.md - - API: api.md + - API: + - Constructor: api-constructor.md + - Input: api-input.md + - Output: api-output.md + - "Resizing images": api-resize.md + - "Compositing images": api-composite.md + - "Image operations": api-operation.md + - "Colour manipulation": api-colour.md + - "Channel manipulation": api-channel.md + - Utilities: api-utility.md - Performance: performance.md - Changelog: changelog.md diff --git a/package.json b/package.json index 8c0cc054..fc932d0c 100755 --- a/package.json +++ b/package.json @@ -34,9 +34,10 @@ "clean": "rm -rf node_modules/ build/ vendor/ coverage/ test/fixtures/output.*", "test": "semistandard && cross-env VIPS_WARNING=0 nyc --reporter=lcov --branches=96 mocha --slow=5000 --timeout=60000 ./test/unit/*.js", "test-leak": "./test/leak/leak.sh", - "test-packaging": "./packaging/test-linux-x64.sh" + "test-packaging": "./packaging/test-linux-x64.sh", + "docs": "for m in constructor input resize composite operation colour channel output utility; do documentation build --shallow --format=md lib/$m.js >docs/api-$m.md; done" }, - "main": "index.js", + "main": "lib/", "repository": { "type": "git", "url": "git://github.com/lovell/sharp" @@ -59,7 +60,7 @@ "dependencies": { "caw": "^2.0.0", "color": "^0.11.3", - "got": "^6.5.0", + "got": "^6.6.0", "nan": "^2.4.0", "semver": "^5.3.0", "tar": "^2.2.1" @@ -68,6 +69,7 @@ "async": "^2.1.2", "bufferutil": "^1.2.1", "cross-env": "^3.1.3", + "documentation": "^4.0.0-beta11", "exif-reader": "^1.0.1", "icc": "^0.0.2", "mocha": "^3.1.2", diff --git a/src/operations.cc b/src/operations.cc index 17280ab5..20adfd40 100644 --- a/src/operations.cc +++ b/src/operations.cc @@ -37,7 +37,6 @@ namespace sharp { return dst; } - VImage Composite(VImage src, VImage dst, const int x, const int y) { if(IsInputValidForComposition(src, dst)) { // Enlarge overlay src, if required @@ -178,7 +177,7 @@ namespace sharp { /* * Stretch luminance to cover full dynamic range. */ - VImage Normalize(VImage image) { + VImage Normalise(VImage image) { // Get original colourspace VipsInterpretation typeBeforeNormalize = image.interpretation(); if (typeBeforeNormalize == VIPS_INTERPRETATION_RGB) { diff --git a/src/operations.h b/src/operations.h index e8b4c585..93687cbb 100644 --- a/src/operations.h +++ b/src/operations.h @@ -41,7 +41,7 @@ namespace sharp { /* * Stretch luminance to cover full dynamic range. */ - VImage Normalize(VImage image); + VImage Normalise(VImage image); /* * Gamma encoding/decoding diff --git a/src/pipeline.cc b/src/pipeline.cc index 3c3c6b4d..bf571452 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -649,9 +649,9 @@ class PipelineWorker : public Nan::AsyncWorker { image = sharp::Gamma(image, baton->gamma); } - // Apply normalization - stretch luminance to cover full dynamic range - if (baton->normalize) { - image = sharp::Normalize(image); + // Apply normalisation - stretch luminance to cover full dynamic range + if (baton->normalise) { + image = sharp::Normalise(image); } // Apply bitwise boolean operation between images @@ -1091,7 +1091,7 @@ NAN_METHOD(pipeline) { } baton->gamma = AttrTo(options, "gamma"); baton->greyscale = AttrTo(options, "greyscale"); - baton->normalize = AttrTo(options, "normalize"); + baton->normalise = AttrTo(options, "normalise"); baton->angle = AttrTo(options, "angle"); baton->rotateBeforePreExtract = AttrTo(options, "rotateBeforePreExtract"); baton->flip = AttrTo(options, "flip"); diff --git a/src/pipeline.h b/src/pipeline.h index f4d0f461..64e471cb 100644 --- a/src/pipeline.h +++ b/src/pipeline.h @@ -62,7 +62,7 @@ struct PipelineBaton { int trimTolerance; double gamma; bool greyscale; - bool normalize; + bool normalise; int angle; bool rotateBeforePreExtract; bool flip; @@ -130,7 +130,7 @@ struct PipelineBaton { trimTolerance(0), gamma(0.0), greyscale(false), - normalize(false), + normalise(false), angle(0), flip(false), flop(false), diff --git a/test/fixtures/index.js b/test/fixtures/index.js index 651f1571..0dfb9933 100644 --- a/test/fixtures/index.js +++ b/test/fixtures/index.js @@ -1,7 +1,7 @@ 'use strict'; const path = require('path'); -const sharp = require('../../index'); +const sharp = require('../../'); const maxColourDistance = require('../../build/Release/sharp')._maxColourDistance; // Helpers diff --git a/test/unit/alpha.js b/test/unit/alpha.js index 5514846e..750f51ed 100644 --- a/test/unit/alpha.js +++ b/test/unit/alpha.js @@ -2,7 +2,7 @@ const assert = require('assert'); const fixtures = require('../fixtures'); -const sharp = require('../../index'); +const sharp = require('../../'); describe('Alpha transparency', function () { it('Flatten to black', function (done) { diff --git a/test/unit/bandbool.js b/test/unit/bandbool.js index a5481312..e6ea9d66 100644 --- a/test/unit/bandbool.js +++ b/test/unit/bandbool.js @@ -2,7 +2,7 @@ const assert = require('assert'); const fixtures = require('../fixtures'); -const sharp = require('../../index'); +const sharp = require('../../'); describe('Bandbool per-channel boolean operations', function () { [ diff --git a/test/unit/blur.js b/test/unit/blur.js index 76ffd345..72b85303 100644 --- a/test/unit/blur.js +++ b/test/unit/blur.js @@ -2,7 +2,7 @@ const assert = require('assert'); -const sharp = require('../../index'); +const sharp = require('../../'); const fixtures = require('../fixtures'); describe('Blur', function () { diff --git a/test/unit/boolean.js b/test/unit/boolean.js index 0dd82fd1..98a4570e 100644 --- a/test/unit/boolean.js +++ b/test/unit/boolean.js @@ -4,7 +4,7 @@ const fs = require('fs'); const assert = require('assert'); const fixtures = require('../fixtures'); -const sharp = require('../../index'); +const sharp = require('../../'); describe('Boolean operation between two images', function () { const inputJpgBooleanTestBuffer = fs.readFileSync(fixtures.inputJpgBooleanTest); diff --git a/test/unit/cache.js b/test/unit/cache.js index 30b2c084..093b91c0 100644 --- a/test/unit/cache.js +++ b/test/unit/cache.js @@ -1,6 +1,6 @@ 'use strict'; -const sharp = require('../../index'); +const sharp = require('../../'); // Define SHARP_TEST_WITHOUT_CACHE environment variable to prevent use of libvips' cache diff --git a/test/unit/clone.js b/test/unit/clone.js index 445b5030..7e404328 100644 --- a/test/unit/clone.js +++ b/test/unit/clone.js @@ -3,7 +3,7 @@ const fs = require('fs'); const assert = require('assert'); -const sharp = require('../../index'); +const sharp = require('../../'); const fixtures = require('../fixtures'); describe('Clone', function () { diff --git a/test/unit/colourspace.js b/test/unit/colourspace.js index ac96174b..d4862ccf 100644 --- a/test/unit/colourspace.js +++ b/test/unit/colourspace.js @@ -2,7 +2,7 @@ const assert = require('assert'); -const sharp = require('../../index'); +const sharp = require('../../'); const fixtures = require('../fixtures'); describe('Colour space conversion', function () { diff --git a/test/unit/convolve.js b/test/unit/convolve.js index 2a9b4676..37f0c5da 100644 --- a/test/unit/convolve.js +++ b/test/unit/convolve.js @@ -2,7 +2,7 @@ const assert = require('assert'); -const sharp = require('../../index'); +const sharp = require('../../'); const fixtures = require('../fixtures'); describe('Convolve', function () { diff --git a/test/unit/crop.js b/test/unit/crop.js index b4ffc408..d84c3a06 100644 --- a/test/unit/crop.js +++ b/test/unit/crop.js @@ -2,7 +2,7 @@ const assert = require('assert'); -const sharp = require('../../index'); +const sharp = require('../../'); const fixtures = require('../fixtures'); describe('Crop', function () { diff --git a/test/unit/embed.js b/test/unit/embed.js index 68df784c..ed139bca 100644 --- a/test/unit/embed.js +++ b/test/unit/embed.js @@ -2,7 +2,7 @@ const assert = require('assert'); -const sharp = require('../../index'); +const sharp = require('../../'); const fixtures = require('../fixtures'); describe('Embed', function () { diff --git a/test/unit/extend.js b/test/unit/extend.js index ef53838c..13686c49 100644 --- a/test/unit/extend.js +++ b/test/unit/extend.js @@ -2,7 +2,7 @@ const assert = require('assert'); -const sharp = require('../../index'); +const sharp = require('../../'); const fixtures = require('../fixtures'); describe('Extend', function () { diff --git a/test/unit/extract.js b/test/unit/extract.js index fb79375d..e339a9d2 100644 --- a/test/unit/extract.js +++ b/test/unit/extract.js @@ -2,7 +2,7 @@ const assert = require('assert'); -const sharp = require('../../index'); +const sharp = require('../../'); const fixtures = require('../fixtures'); describe('Partial image extraction', function () { diff --git a/test/unit/extractChannel.js b/test/unit/extractChannel.js index 59fc7001..a12fa708 100644 --- a/test/unit/extractChannel.js +++ b/test/unit/extractChannel.js @@ -2,7 +2,7 @@ const assert = require('assert'); -const sharp = require('../../index'); +const sharp = require('../../'); const fixtures = require('../fixtures'); describe('Image channel extraction', function () { diff --git a/test/unit/gamma.js b/test/unit/gamma.js index d2f09164..d049bd92 100644 --- a/test/unit/gamma.js +++ b/test/unit/gamma.js @@ -2,7 +2,7 @@ const assert = require('assert'); -const sharp = require('../../index'); +const sharp = require('../../'); const fixtures = require('../fixtures'); describe('Gamma correction', function () { diff --git a/test/unit/interpolation.js b/test/unit/interpolation.js index c61ce8ad..2db8aa43 100644 --- a/test/unit/interpolation.js +++ b/test/unit/interpolation.js @@ -2,7 +2,7 @@ const assert = require('assert'); -const sharp = require('../../index'); +const sharp = require('../../'); const fixtures = require('../fixtures'); describe('Interpolators and kernels', function () { diff --git a/test/unit/io.js b/test/unit/io.js index c380494b..21095c86 100644 --- a/test/unit/io.js +++ b/test/unit/io.js @@ -3,7 +3,7 @@ const fs = require('fs'); const assert = require('assert'); -const sharp = require('../../index'); +const sharp = require('../../'); const fixtures = require('../fixtures'); describe('Input/output', function () { diff --git a/test/unit/joinChannel.js b/test/unit/joinChannel.js index 4ddcc19e..aa09dc26 100644 --- a/test/unit/joinChannel.js +++ b/test/unit/joinChannel.js @@ -3,7 +3,7 @@ const assert = require('assert'); const fs = require('fs'); -const sharp = require('../../index'); +const sharp = require('../../'); const fixtures = require('../fixtures'); describe('Image channel insertion', function () { diff --git a/test/unit/metadata.js b/test/unit/metadata.js index c397ee1d..70438db4 100644 --- a/test/unit/metadata.js +++ b/test/unit/metadata.js @@ -5,7 +5,7 @@ const assert = require('assert'); const exifReader = require('exif-reader'); const icc = require('icc'); -const sharp = require('../../index'); +const sharp = require('../../'); const fixtures = require('../fixtures'); describe('Image metadata', function () { diff --git a/test/unit/negate.js b/test/unit/negate.js index 3a273eaf..aff4fd90 100644 --- a/test/unit/negate.js +++ b/test/unit/negate.js @@ -2,7 +2,7 @@ const assert = require('assert'); -const sharp = require('../../index'); +const sharp = require('../../'); const fixtures = require('../fixtures'); describe('Negate', function () { diff --git a/test/unit/normalize.js b/test/unit/normalize.js index 1957fc17..fcf41631 100644 --- a/test/unit/normalize.js +++ b/test/unit/normalize.js @@ -2,7 +2,7 @@ const assert = require('assert'); -const sharp = require('../../index'); +const sharp = require('../../'); const fixtures = require('../fixtures'); const assertNormalized = function (data) { @@ -17,13 +17,9 @@ const assertNormalized = function (data) { }; describe('Normalization', function () { - it('uses the same prototype for both spellings', function () { - assert.strictEqual(sharp.prototype.normalize, sharp.prototype.normalise); - }); - it('spreads rgb image values between 0 and 255', function (done) { sharp(fixtures.inputJpgWithLowContrast) - .normalize() + .normalise() .raw() .toBuffer(function (err, data, info) { if (err) throw err; @@ -47,7 +43,7 @@ describe('Normalization', function () { it('stretches greyscale images with alpha channel', function (done) { sharp(fixtures.inputPngWithGreyAlpha) - .normalize() + .normalise() .raw() .toBuffer(function (err, data, info) { if (err) throw err; @@ -73,7 +69,7 @@ describe('Normalization', function () { it('keeps the alpha channel of greyscale images intact', function (done) { sharp(fixtures.inputPngWithGreyAlpha) - .normalize() + .normalise() .toBuffer(function (err, data) { if (err) throw err; sharp(data).metadata(function (err, metadata) { @@ -99,7 +95,7 @@ describe('Normalization', function () { it('works with 16-bit RGBA images', function (done) { sharp(fixtures.inputPngWithTransparency16bit) - .normalize() + .normalise() .raw() .toBuffer(function (err, data, info) { if (err) throw err; diff --git a/test/unit/overlay.js b/test/unit/overlay.js index 9d1a802e..9e33c3d7 100644 --- a/test/unit/overlay.js +++ b/test/unit/overlay.js @@ -4,7 +4,7 @@ const fs = require('fs'); const assert = require('assert'); const fixtures = require('../fixtures'); -const sharp = require('../../index'); +const sharp = require('../../'); // Helpers const getPaths = function (baseName, extension) { diff --git a/test/unit/require.js b/test/unit/require.js index e3465ac7..3756b6c5 100644 --- a/test/unit/require.js +++ b/test/unit/require.js @@ -10,6 +10,6 @@ describe('Require-time checks', function () { */ it('Require alongside C++ module that does not use libc++', function () { const bufferutil = require('bufferutil'); - const sharp = require('../../index'); + const sharp = require('../../'); }); }); diff --git a/test/unit/resize.js b/test/unit/resize.js index a3411ee3..38704703 100644 --- a/test/unit/resize.js +++ b/test/unit/resize.js @@ -2,7 +2,7 @@ const assert = require('assert'); -const sharp = require('../../index'); +const sharp = require('../../'); const fixtures = require('../fixtures'); describe('Resize dimensions', function () { diff --git a/test/unit/rotate.js b/test/unit/rotate.js index 33daa74a..13502940 100644 --- a/test/unit/rotate.js +++ b/test/unit/rotate.js @@ -2,7 +2,7 @@ const assert = require('assert'); -const sharp = require('../../index'); +const sharp = require('../../'); const fixtures = require('../fixtures'); describe('Rotation', function () { diff --git a/test/unit/sharpen.js b/test/unit/sharpen.js index 1122479f..ab3b134e 100644 --- a/test/unit/sharpen.js +++ b/test/unit/sharpen.js @@ -2,7 +2,7 @@ const assert = require('assert'); -const sharp = require('../../index'); +const sharp = require('../../'); const fixtures = require('../fixtures'); describe('Sharpen', function () { diff --git a/test/unit/threshold.js b/test/unit/threshold.js index b449c8c5..5dad411a 100644 --- a/test/unit/threshold.js +++ b/test/unit/threshold.js @@ -2,7 +2,7 @@ const assert = require('assert'); -const sharp = require('../../index'); +const sharp = require('../../'); const fixtures = require('../fixtures'); describe('Threshold', function () { diff --git a/test/unit/tile.js b/test/unit/tile.js index 798a0921..f913008a 100644 --- a/test/unit/tile.js +++ b/test/unit/tile.js @@ -8,7 +8,7 @@ const eachLimit = require('async/eachLimit'); const rimraf = require('rimraf'); const unzip = require('unzip'); -const sharp = require('../../index'); +const sharp = require('../../'); const fixtures = require('../fixtures'); // Verifies all tiles in a given dz output directory are <= size diff --git a/test/unit/trim.js b/test/unit/trim.js index 2e09e05a..d18247f8 100644 --- a/test/unit/trim.js +++ b/test/unit/trim.js @@ -2,7 +2,7 @@ const assert = require('assert'); -const sharp = require('../../index'); +const sharp = require('../../'); const fixtures = require('../fixtures'); describe('Trim borders', function () { diff --git a/test/unit/util.js b/test/unit/util.js index d842a54f..3d99a9c1 100644 --- a/test/unit/util.js +++ b/test/unit/util.js @@ -1,7 +1,7 @@ 'use strict'; const assert = require('assert'); -const sharp = require('../../index'); +const sharp = require('../../'); const defaultConcurrency = sharp.concurrency();