sharp/index.js
Lovell Fuller 3f5e38bb62 Deprecate output format option functions.
Access is now via options of existing output format functions.

e.g. use .jpeg({quality: n}) instead of .jpeg().quality(n)
2016-11-02 09:25:20 +00:00

1323 lines
40 KiB
JavaScript

'use strict';
var path = require('path');
var util = require('util');
var stream = require('stream');
var events = require('events');
var semver = require('semver');
var color = require('color');
var BluebirdPromise = require('bluebird');
var sharp = require('./build/Release/sharp.node');
// Versioning
var versions = {
vips: sharp.libvipsVersion()
};
(function() {
// Does libvips meet minimum requirement?
var 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
var maximum = {
width: 0x3FFF,
height: 0x3FFF,
pixels: Math.pow(0x3FFF, 2)
};
// Constructor-factory
var 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);
/*
EventEmitter singleton emits queue length 'change' events
*/
module.exports.queue = new events.EventEmitter();
/*
Supported image formats
*/
module.exports.format = sharp.format();
/*
Version numbers of libvips and its dependencies
*/
module.exports.versions = versions;
/*
Validation helpers
*/
var isDefined = function(val) {
return typeof val !== 'undefined' && val !== null;
};
var isObject = function(val) {
return typeof val === 'object';
};
var isFunction = function(val) {
return typeof val === 'function';
};
var isBoolean = function(val) {
return typeof val === 'boolean';
};
var isBuffer = function(val) {
return typeof val === 'object' && val instanceof Buffer;
};
var isString = function(val) {
return typeof val === 'string' && val.length > 0;
};
var isNumber = function(val) {
return typeof val === 'number' && !Number.isNaN(val);
};
var isInteger = function(val) {
return isNumber(val) && val % 1 === 0;
};
var inRange = function(val, min, max) {
return val >= min && val <= max;
};
var contains = function(val, list) {
return list.indexOf(val) !== -1;
};
/*
Create Object containing input and input-related options
*/
Sharp.prototype._createInputDescriptor = function(input, inputOptions, containerOptions) {
var 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 chunk on Writable Stream
*/
Sharp.prototype._write = function(chunk, encoding, callback) {
/*jslint unused: false */
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
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 behaviour
module.exports.strategy = {
entropy: 16,
attention: 17
};
/*
What part of the image should be retained when cropping?
*/
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;
};
Sharp.prototype.extract = function(options) {
var suffix = this.options.width === -1 && this.options.height === -1 ? 'Pre' : 'Post';
['left', 'top', 'width', 'height'].forEach(function (name) {
var 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;
};
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 colour for embed and flatten operations.
Delegates to the 'Color' module, which can throw an Error
but is liberal in what it accepts, clamping values to sensible min/max.
*/
Sharp.prototype.background = function(rgba) {
var colour = color(rgba);
this.options.background = colour.rgbArray();
this.options.background.push(colour.alpha() * 255);
return this;
};
Sharp.prototype.embed = function() {
this.options.canvas = 'embed';
return this;
};
Sharp.prototype.max = function() {
this.options.canvas = 'max';
return this;
};
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 the resize method.
*/
Sharp.prototype.ignoreAspectRatio = function() {
this.options.canvas = 'ignore_aspect';
return this;
};
Sharp.prototype.flatten = function(flatten) {
this.options.flatten = isBoolean(flatten) ? flatten : true;
return this;
};
Sharp.prototype.negate = function(negate) {
this.options.negate = isBoolean(negate) ? negate : true;
return this;
};
/*
Bitwise boolean operations between images
*/
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 operation ' + operator);
}
return this;
};
/*
Overlay with another image, using an optional gravity
*/
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;
};
/*
Add another color channel to the image
*/
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 output image by 0, 90, 180 or 270 degrees
Auto-rotation based on the EXIF Orientation tag is represented by an angle of -1
*/
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 vertically, about the Y axis
*/
Sharp.prototype.flip = function(flip) {
this.options.flip = isBoolean(flip) ? flip : true;
return this;
};
/*
Flop the image horizontally, about the X axis
*/
Sharp.prototype.flop = function(flop) {
this.options.flop = isBoolean(flop) ? flop : true;
return this;
};
/*
Do not enlarge the output if the input 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"
*/
Sharp.prototype.withoutEnlargement = function(withoutEnlargement) {
this.options.withoutEnlargement = isBoolean(withoutEnlargement) ? withoutEnlargement : true;
return this;
};
/*
Blur the output image.
Call without a sigma to use a fast, mild blur.
Call with a sigma to use a slower, more accurate Gaussian blur.
*/
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 a kernel.
*/
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);
}
// Clamp 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 output image.
Call without a radius to use a fast, mild sharpen.
Call with a radius to use a slow, accurate sharpen using the L of LAB colour space.
sigma - sigma of mask
flat - level of "flat" area sharpen, default 1
jagged - level of "jagged" area sharpen, default 2
*/
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;
};
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;
};
/*
Automatically remove "boring" image edges.
tolerance - if present, is a percentaged tolerance level between 0 and 100 to trim away similar color values
Defaulting to 10 when no tolerance is given.
*/
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;
};
/*
Darken image pre-resize (1/gamma) and brighten post-resize (gamma).
Improves brightness of resized image in non-linear colour spaces.
*/
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
*/
Sharp.prototype.normalize = function(normalize) {
this.options.normalize = isBoolean(normalize) ? normalize : true;
return this;
};
Sharp.prototype.normalise = Sharp.prototype.normalize;
/*
Perform boolean/bitwise operation on image color channels - results in one channel image
*/
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 greyscale
*/
Sharp.prototype.greyscale = function(greyscale) {
this.options.greyscale = isBoolean(greyscale) ? greyscale : true;
return this;
};
Sharp.prototype.grayscale = Sharp.prototype.greyscale;
/*
Set output colourspace
*/
Sharp.prototype.toColourspace = function(colourspace) {
if (!isString(colourspace) ) {
throw new Error('Invalid output colourspace ' + colourspace);
}
this.options.colourspace = colourspace;
return this;
};
Sharp.prototype.toColorspace = Sharp.prototype.toColourspace;
Sharp.prototype.sequentialRead = function(sequentialRead) {
this.options.sequentialRead = isBoolean(sequentialRead) ? sequentialRead : true;
return this;
};
// Deprecated output options
Sharp.prototype.quality = util.deprecate(function(quality) {
var formatOut = this.options.formatOut;
var 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) {
var formatOut = this.options.formatOut;
var 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) {
var 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) {
var 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) {
var 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) {
var 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) {
var 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) {
var 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
Optionally provide an Object with attributes to update:
orientation: numeric value for EXIF Orientation tag
*/
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;
};
/*
Tile-based deep zoom output options: size, overlap, layout
*/
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;
};
/*
Extend edges
*/
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;
};
// Kernels for reduction
module.exports.kernel = {
cubic: 'cubic',
lanczos2: 'lanczos2',
lanczos3: 'lanczos3'
};
// Interpolators for enlargement
module.exports.interpolator = {
nearest: 'nearest',
bilinear: 'bilinear',
bicubic: 'bicubic',
nohalo: 'nohalo',
lbb: 'lbb',
locallyBoundedBicubic: 'lbb',
vsqbs: 'vsqbs',
vertexSplitQuadraticBasisSpline: 'vsqbs'
};
// Boolean operations for bandbool
module.exports.bool = {
and: 'and',
or: 'or',
eor: 'eor'
};
// Colourspaces
module.exports.colourspace = {
multiband: 'multiband',
'b-w': 'b-w',
bw: 'b-w',
cmyk: 'cmyk',
srgb: 'srgb'
};
module.exports.colorspace = module.exports.colourspace;
/*
Resize image to width x height pixels
options.kernel is the kernel to use for reductions, default 'lanczos3'
options.interpolator is the interpolator to use for enlargements, default 'bicubic'
*/
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;
};
/*
Limit the total number of pixels for input images
Assumes the image dimensions contained in the file header can be trusted.
Alternatively can use boolean to disable or reset to default (maximum pixels)
*/
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 (typeof limit === 'number' && !Number.isNaN(limit) && limit % 1 === 0 && limit >= 0) {
this.options.limitInputPixels = limit;
} else {
throw new Error('Invalid pixel limit (1 to ' + maximum.pixels + ') ' + limit);
}
return this;
};
/**
* Write output image data to a file
* @throws {Error} if an attempt has been made to force Buffer/Stream output type
*/
Sharp.prototype.toFile = function(fileOut, callback) {
if (!fileOut || fileOut.length === 0) {
var errOutputInvalid = new Error('Invalid output');
if (isFunction(callback)) {
callback(errOutputInvalid);
} else {
return BluebirdPromise.reject(errOutputInvalid);
}
} else {
if (this.options.input.file === fileOut) {
var errOutputIsInput = new Error('Cannot use same file for input and output');
if (isFunction(callback)) {
callback(errOutputIsInput);
} else {
return BluebirdPromise.reject(errOutputIsInput);
}
} else {
this.options.fileOut = fileOut;
return this._pipeline(callback);
}
}
return this;
};
/**
* Write output to a Buffer.
* @param {Function} [callback]
* @returns {Promise} when no callback is provided
*/
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 {Object} this
*/
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
* @returns {void}
*/
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 {Object} this
* @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 {Object} this
* @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 {Object} this
* @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 {Object} this
* @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 {Object} this
*/
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 {Object} this
* @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);
};
/*
Used by a Writable Stream to notify that it is ready for data
*/
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
*/
Sharp.prototype._pipeline = function(callback) {
var 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 BluebirdPromise(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 BluebirdPromise(function(resolve, reject) {
sharp.pipeline(that.options, function(err, data) {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
}
}
};
/*
Reads the image header and returns metadata
Supports callback, stream and promise variants
*/
Sharp.prototype.metadata = function(callback) {
var 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 BluebirdPromise(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 BluebirdPromise(function(resolve, reject) {
sharp.metadata(that.options, function(err, metadata) {
if (err) {
reject(err);
} else {
resolve(metadata);
}
});
});
}
}
};
/*
Clone new instance using existing options.
Cloned instances share the same input.
*/
Sharp.prototype.clone = function() {
var that = this;
// Clone existing options
var 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;
};
/**
Get and set cache memory, file and item limits
*/
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);
/*
Get and set size of thread pool
*/
module.exports.concurrency = function(concurrency) {
return sharp.concurrency(isInteger(concurrency) ? concurrency : null);
};
/*
Get internal counters
*/
module.exports.counters = function() {
return sharp.counters();
};
/*
Get and set use of SIMD vector unit instructions
*/
module.exports.simd = function(simd) {
return sharp.simd(isBoolean(simd) ? simd : null);
};
// Switch off default
module.exports.simd(false);