From 70a3d4fb5eccc7c2763937758f0bc0144389eb69 Mon Sep 17 00:00:00 2001 From: Lovell Fuller Date: Thu, 5 Jan 2017 22:16:59 +0000 Subject: [PATCH] Improve error messages for invalid resize parameters Dependency version bumps and doc refresh --- docs/api-channel.md | 12 +++++++--- docs/api-colour.md | 18 +++++++++++---- docs/api-composite.md | 6 ++++- docs/api-constructor.md | 15 ++++++++---- docs/api-input.md | 15 ++++++++---- docs/api-operation.md | 51 ++++++++++++++++++++++++++++------------- docs/api-output.md | 33 ++++++++++++++++++-------- docs/api-resize.md | 24 +++++++++++++------ docs/api-utility.md | 15 ++++++++---- docs/changelog.md | 9 ++++++++ lib/is.js | 18 ++++++++++++++- lib/resize.js | 10 ++++---- package.json | 12 +++++----- test/unit/crop.js | 8 +++---- test/unit/resize.js | 12 +++++----- 15 files changed, 182 insertions(+), 76 deletions(-) diff --git a/docs/api-channel.md b/docs/api-channel.md index 079245af..e295f12a 100644 --- a/docs/api-channel.md +++ b/docs/api-channel.md @@ -1,6 +1,12 @@ -# extractChannel +### Table of Contents + +- [extractChannel](#extractchannel) +- [joinChannel](#joinchannel) +- [bandbool](#bandbool) + +## extractChannel Extract a single channel from a multi-channel image. @@ -23,7 +29,7 @@ sharp(input) Returns **Sharp** -# joinChannel +## joinChannel Join one or more channels to the image. The meaning of the added channels depends on the output colourspace, set with `toColourspace()`. @@ -46,7 +52,7 @@ For raw pixel input, the `options` object should contain a `raw` attribute, whic Returns **Sharp** -# bandbool +## bandbool Perform a bitwise boolean operation on all input image channels (bands) to produce a single channel output image. diff --git a/docs/api-colour.md b/docs/api-colour.md index 2104fc4f..92664423 100644 --- a/docs/api-colour.md +++ b/docs/api-colour.md @@ -1,6 +1,14 @@ -# background +### Table of Contents + +- [background](#background) +- [greyscale](#greyscale) +- [grayscale](#grayscale) +- [toColourspace](#tocolourspace) +- [toColorspace](#tocolorspace) + +## background Set the background for the `embed`, `flatten` and `extend` operations. The default background is `{r: 0, g: 0, b: 0, alpha: 1}`, black without transparency. @@ -18,7 +26,7 @@ The alpha value is a float between `0` (transparent) and `1` (opaque). Returns **Sharp** -# greyscale +## 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. @@ -33,7 +41,7 @@ An alpha channel may be present, and will be unchanged by the operation. Returns **Sharp** -# grayscale +## grayscale Alternative spelling of `greyscale`. @@ -43,7 +51,7 @@ Alternative spelling of `greyscale`. Returns **Sharp** -# toColourspace +## toColourspace Set the output colourspace. By default output image will be web-friendly sRGB, with additional channels interpreted as alpha channels. @@ -57,7 +65,7 @@ By default output image will be web-friendly sRGB, with additional channels inte Returns **Sharp** -# toColorspace +## toColorspace Alternative spelling of `toColourspace`. diff --git a/docs/api-composite.md b/docs/api-composite.md index 68eee27a..0fb45526 100644 --- a/docs/api-composite.md +++ b/docs/api-composite.md @@ -1,6 +1,10 @@ -# overlayWith +### Table of Contents + +- [overlayWith](#overlaywith) + +## overlayWith Overlay (composite) an image over the processed (resized, extracted etc.) image. diff --git a/docs/api-constructor.md b/docs/api-constructor.md index 9c6371fb..09fac196 100644 --- a/docs/api-constructor.md +++ b/docs/api-constructor.md @@ -1,6 +1,13 @@ -# Sharp +### Table of Contents + +- [Sharp](#sharp) + - [format](#format) + - [versions](#versions) +- [queue](#queue) + +## Sharp **Parameters** @@ -43,7 +50,7 @@ readableStream.pipe(transformer).pipe(writableStream); Returns **[Sharp](#sharp)** -## format +### format An Object containing nested boolean values representing the available input and output formats/methods. @@ -55,7 +62,7 @@ console.log(sharp.format()); Returns **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** -## versions +### versions An Object containing the version numbers of libvips and its dependencies. @@ -65,7 +72,7 @@ An Object containing the version numbers of libvips and its dependencies. console.log(sharp.versions); ``` -# queue +## queue An EventEmitter that emits a `change` event when a task is either: diff --git a/docs/api-input.md b/docs/api-input.md index 7a9918f4..9b78b45e 100644 --- a/docs/api-input.md +++ b/docs/api-input.md @@ -1,6 +1,13 @@ -# clone +### Table of Contents + +- [clone](#clone) +- [metadata](#metadata) +- [limitInputPixels](#limitinputpixels) +- [sequentialRead](#sequentialread) + +## clone Take a "snapshot" of the Sharp instance, returning a new instance. Cloned instances inherit the input of their parent instance. @@ -19,7 +26,7 @@ readableStream.pipe(pipeline); Returns **Sharp** -# metadata +## metadata Fast access to image metadata without decoding any compressed image data. A Promises/A+ promise is returned when `callback` is not provided. @@ -59,7 +66,7 @@ image Returns **([Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)<[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)> | Sharp)** -# limitInputPixels +## 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. @@ -74,7 +81,7 @@ The default limit is 268402689 (0x3FFF _ 0x3FFF) pixels. Returns **Sharp** -# sequentialRead +## 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. diff --git a/docs/api-operation.md b/docs/api-operation.md index 3ef71bef..c11cef3f 100644 --- a/docs/api-operation.md +++ b/docs/api-operation.md @@ -1,6 +1,25 @@ -# rotate +### Table of Contents + +- [rotate](#rotate) +- [extract](#extract) +- [flip](#flip) +- [flop](#flop) +- [sharpen](#sharpen) +- [blur](#blur) +- [extend](#extend) +- [flatten](#flatten) +- [trim](#trim) +- [gamma](#gamma) +- [negate](#negate) +- [normalise](#normalise) +- [normalize](#normalize) +- [convolve](#convolve) +- [threshold](#threshold) +- [boolean](#boolean) + +## rotate Rotate the output image by either an explicit angle or auto-orient based on the EXIF `Orientation` tag. @@ -35,7 +54,7 @@ readableStream.pipe(pipeline); Returns **Sharp** -# extract +## extract Extract a region of the image. @@ -75,7 +94,7 @@ sharp(input) Returns **Sharp** -# flip +## 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. @@ -86,7 +105,7 @@ The use of `flip` implies the removal of the EXIF `Orientation` tag, if any. Returns **Sharp** -# flop +## 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. @@ -97,7 +116,7 @@ The use of `flop` implies the removal of the EXIF `Orientation` tag, if any. Returns **Sharp** -# sharpen +## sharpen Sharpen the image. When used without parameters, performs a fast, mild sharpen of the output image. @@ -115,7 +134,7 @@ Separate control over the level of sharpening in "flat" and "jagged" areas is av Returns **Sharp** -# blur +## blur Blur the image. When used without parameters, performs a fast, mild blur of the output image. @@ -130,7 +149,7 @@ When a `sigma` is provided, performs a slower, more accurate Gaussian blur. Returns **Sharp** -# extend +## 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. @@ -159,7 +178,7 @@ sharp(input) Returns **Sharp** -# flatten +## flatten Merge alpha transparency channel, if any, with `background`. @@ -169,7 +188,7 @@ Merge alpha transparency channel, if any, with `background`. Returns **Sharp** -# trim +## trim Trim "boring" pixels from all edges that contain values within a percentage similarity of the top-left pixel. @@ -182,7 +201,7 @@ Trim "boring" pixels from all edges that contain values within a percentage simi Returns **Sharp** -# 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`. @@ -199,7 +218,7 @@ when applying a gamma correction. Returns **Sharp** -# negate +## negate Produce the "negative" of the image. @@ -209,7 +228,7 @@ Produce the "negative" of the image. Returns **Sharp** -# normalise +## normalise Enhance output image contrast by stretching its luminance to cover the full dynamic range. @@ -219,7 +238,7 @@ Enhance output image contrast by stretching its luminance to cover the full dyna Returns **Sharp** -# normalize +## normalize Alternative spelling of normalise. @@ -229,7 +248,7 @@ Alternative spelling of normalise. Returns **Sharp** -# convolve +## convolve Convolve the image with the specified kernel. @@ -262,7 +281,7 @@ sharp(input) Returns **Sharp** -# threshold +## threshold Any pixel value greather than or equal to the threshold value will be set to 255, otherwise it will be set to 0. @@ -278,7 +297,7 @@ Any pixel value greather than or equal to the threshold value will be set to 255 Returns **Sharp** -# boolean +## boolean Perform a bitwise boolean operation with operand image. diff --git a/docs/api-output.md b/docs/api-output.md index dd065b30..8db6da48 100644 --- a/docs/api-output.md +++ b/docs/api-output.md @@ -1,6 +1,19 @@ -# toFile +### Table of Contents + +- [toFile](#tofile) +- [toBuffer](#tobuffer) +- [withMetadata](#withmetadata) +- [jpeg](#jpeg) +- [png](#png) +- [webp](#webp) +- [tiff](#tiff) +- [raw](#raw) +- [toFormat](#toformat) +- [tile](#tile) + +## toFile Write output image data to a file. @@ -21,7 +34,7 @@ A Promises/A+ promise is returned when `callback` is not provided. Returns **[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)<[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)>** when no callback is provided -# toBuffer +## toBuffer Write output to a Buffer. JPEG, PNG, WebP, and RAW output are supported. @@ -40,7 +53,7 @@ By default, the format will match the input image, except GIF and SVG input whic Returns **[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)<[Buffer](https://nodejs.org/api/buffer.html)>** when no callback is provided -# withMetadata +## 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. @@ -56,7 +69,7 @@ This will also convert to and add a web-friendly sRGB ICC profile. Returns **Sharp** -# jpeg +## jpeg Use these JPEG options for output image. @@ -77,7 +90,7 @@ Use these JPEG options for output image. Returns **Sharp** -# png +## png Use these PNG options for output image. @@ -94,7 +107,7 @@ Use these PNG options for output image. Returns **Sharp** -# webp +## webp Use these WebP options for output image. @@ -109,7 +122,7 @@ Use these WebP options for output image. Returns **Sharp** -# tiff +## tiff Use these TIFF options for output image. @@ -124,13 +137,13 @@ Use these TIFF options for output image. Returns **Sharp** -# raw +## raw Force output to be raw, uncompressed uint8 pixel data. Returns **Sharp** -# toFormat +## toFormat Force output to a given format. @@ -144,7 +157,7 @@ Force output to a given format. Returns **Sharp** -# tile +## tile Use tile-based deep zoom (image pyramid) output. Set the format and options for tile images via the `toFormat`, `jpeg`, `png` or `webp` functions. diff --git a/docs/api-resize.md b/docs/api-resize.md index b6382402..d5886cdc 100644 --- a/docs/api-resize.md +++ b/docs/api-resize.md @@ -1,6 +1,16 @@ -# resize +### Table of Contents + +- [resize](#resize) +- [crop](#crop) +- [embed](#embed) +- [max](#max) +- [min](#min) +- [ignoreAspectRatio](#ignoreaspectratio) +- [withoutEnlargement](#withoutenlargement) + +## resize Resize image to `width` x `height`. By default, the resized image is centre cropped to the exact size specified. @@ -52,7 +62,7 @@ sharp(inputBuffer) Returns **Sharp** -# crop +## crop Crop the resized image to the exact size specified, the default behaviour. @@ -87,7 +97,7 @@ readableStream.pipe(transformer).pipe(writableStream); Returns **Sharp** -# embed +## 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. @@ -114,7 +124,7 @@ sharp('input.gif') Returns **Sharp** -# max +## 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. @@ -137,7 +147,7 @@ sharp(inputBuffer) Returns **Sharp** -# min +## 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. @@ -146,14 +156,14 @@ Both `width` and `height` must be provided via `resize` otherwise the behaviour Returns **Sharp** -# ignoreAspectRatio +## ignoreAspectRatio Ignoring the aspect ratio of the input, stretch the image to the exact `width` and/or `height` provided via `resize`. Returns **Sharp** -# withoutEnlargement +## 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: diff --git a/docs/api-utility.md b/docs/api-utility.md index 1871aee4..3b914a54 100644 --- a/docs/api-utility.md +++ b/docs/api-utility.md @@ -1,6 +1,13 @@ -# cache +### Table of Contents + +- [cache](#cache) +- [concurrency](#concurrency) +- [counters](#counters) +- [simd](#simd) + +## 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. @@ -28,7 +35,7 @@ sharp.cache(false); Returns **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** -# concurrency +## concurrency Gets, or when a concurrency is provided sets, the number of threads _libvips'_ should create to process each image. @@ -54,7 +61,7 @@ sharp.concurrency(0); // 4 Returns **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** concurrency -# counters +## counters Provides access to internal task counters. @@ -69,7 +76,7 @@ const counters = sharp.counters(); // { queue: 2, process: 4 } Returns **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** -# simd +## simd Get and set use of SIMD vector unit instructions. Requires libvips to have been compiled with liborc support. diff --git a/docs/changelog.md b/docs/changelog.md index 7d81a7b0..d11681ed 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -4,6 +4,15 @@ Requires libvips v8.4.2. +#### v0.17.1 - TBD + +* Improve error messages for invalid parameters. + [@spikeon](https://github.com/spikeon) + [#644](https://github.com/lovell/sharp/pull/644) + +* Simplify expression for finding vips-cpp libdir. + [#656](https://github.com/lovell/sharp/pull/656) + #### v0.17.0 - 11th December 2016 * Drop support for versions of Node prior to v4. diff --git a/lib/is.js b/lib/is.js index ffa6cd91..d9fb06f7 100644 --- a/lib/is.js +++ b/lib/is.js @@ -80,6 +80,21 @@ const inArray = function (val, list) { return list.indexOf(val) !== -1; }; +/** + * Create an Error with a message relating to an invalid parameter. + * + * @param {String} name - parameter name. + * @param {String} expected - description of the type/value/range expected. + * @param {*} actual - the value received. + * @returns {Error} Containing the formatted message. + * @private + */ +const invalidParameterError = function (name, expected, actual) { + return new Error( + `Expected ${expected} for ${name} but received ${actual} of type ${typeof actual}` + ); +}; + module.exports = { defined: defined, object: object, @@ -90,5 +105,6 @@ module.exports = { number: number, integer: integer, inRange: inRange, - inArray: inArray + inArray: inArray, + invalidParameterError: invalidParameterError }; diff --git a/lib/resize.js b/lib/resize.js index 4f8318e2..db60b11b 100644 --- a/lib/resize.js +++ b/lib/resize.js @@ -104,7 +104,7 @@ const resize = function resize (width, height, options) { 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); + throw is.invalidParameterError('width', `integer between 1 and ${this.constructor.maximum.width}`, width); } } else { this.options.width = -1; @@ -113,7 +113,7 @@ const resize = function resize (width, height, options) { 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); + throw is.invalidParameterError('height', `integer between 1 and ${this.constructor.maximum.height}`, height); } } else { this.options.height = -1; @@ -124,7 +124,7 @@ const resize = function resize (width, height, options) { if (is.string(kernel[options.kernel])) { this.options.kernel = kernel[options.kernel]; } else { - throw new Error('Invalid kernel ' + options.kernel); + throw is.invalidParameterError('kernel', 'valid kernel name', options.kernel); } } // Interpolator @@ -132,7 +132,7 @@ const resize = function resize (width, height, options) { if (is.string(interpolator[options.interpolator])) { this.options.interpolator = interpolator[options.interpolator]; } else { - throw new Error('Invalid interpolator ' + options.interpolator); + throw is.invalidParameterError('interpolator', 'valid interpolator name', options.interpolator); } } // Centre sampling @@ -185,7 +185,7 @@ const crop = function crop (crop) { // Strategy this.options.crop = crop; } else { - throw new Error('Unsupported crop ' + crop); + throw is.invalidParameterError('crop', 'valid crop id/name/strategy', crop); } return this; }; diff --git a/package.json b/package.json index 2f7d53ad..7ace4ceb 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "sharp", "description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP and TIFF images", - "version": "0.17.0", + "version": "0.17.1", "author": "Lovell Fuller ", "contributors": [ "Pierre Inglebert ", @@ -60,17 +60,17 @@ ], "dependencies": { "caw": "^2.0.0", - "color": "^1.0.2", - "got": "^6.6.3", - "nan": "^2.4.0", + "color": "^1.0.3", + "got": "^6.7.1", + "nan": "^2.5.0", "semver": "^5.3.0", "tar": "^2.2.1" }, "devDependencies": { "async": "^2.1.4", "bufferutil": "^1.3.0", - "cross-env": "^3.1.3", - "documentation": "^4.0.0-beta16", + "cross-env": "^3.1.4", + "documentation": "^4.0.0-beta.18", "exif-reader": "^1.0.1", "icc": "^0.0.2", "mocha": "^3.2.0", diff --git a/test/unit/crop.js b/test/unit/crop.js index d84c3a06..cb6333e1 100644 --- a/test/unit/crop.js +++ b/test/unit/crop.js @@ -141,16 +141,16 @@ describe('Crop', function () { it('Invalid values fail', function () { assert.throws(function () { sharp().crop(9); - }); + }, /Expected valid crop id\/name\/strategy for crop but received 9 of type number/); assert.throws(function () { sharp().crop(1.1); - }); + }, /Expected valid crop id\/name\/strategy for crop but received 1.1 of type number/); assert.throws(function () { sharp().crop(-1); - }); + }, /Expected valid crop id\/name\/strategy for crop but received -1 of type number/); assert.throws(function () { sharp().crop('zoinks'); - }); + }, /Expected valid crop id\/name\/strategy for crop but received zoinks of type string/); }); it('Uses default value when none specified', function () { diff --git a/test/unit/resize.js b/test/unit/resize.js index 5342d62c..1a79a1a7 100644 --- a/test/unit/resize.js +++ b/test/unit/resize.js @@ -66,37 +66,37 @@ describe('Resize dimensions', function () { it('Invalid width - NaN', function () { assert.throws(function () { sharp().resize('spoons', 240); - }); + }, /Expected integer between 1 and 16383 for width but received spoons of type string/); }); it('Invalid height - NaN', function () { assert.throws(function () { sharp().resize(320, 'spoons'); - }); + }, /Expected integer between 1 and 16383 for height but received spoons of type string/); }); it('Invalid width - float', function () { assert.throws(function () { sharp().resize(1.5, 240); - }); + }, /Expected integer between 1 and 16383 for width but received 1.5 of type number/); }); it('Invalid height - float', function () { assert.throws(function () { sharp().resize(320, 1.5); - }); + }, /Expected integer between 1 and 16383 for height but received 1.5 of type number/); }); it('Invalid width - too large', function () { assert.throws(function () { sharp().resize(0x4000, 240); - }); + }, /Expected integer between 1 and 16383 for width but received 16384 of type number/); }); it('Invalid height - too large', function () { assert.throws(function () { sharp().resize(320, 0x4000); - }); + }, /Expected integer between 1 and 16383 for height but received 16384 of type number/); }); it('WebP shrink-on-load rounds to zero, ensure recalculation is correct', function (done) {