diff --git a/docs/changelog.md b/docs/changelog.md index bac3a8aa1..51b504729 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -6,6 +6,15 @@ Requires libvips v8.7.0. #### v0.21.0 - TBD +* Deprecate the following resize-related functions: + `crop`, `embed`, `ignoreAspectRatio`, `max`, `min` and `withoutEnlargement`. + Access to these is now via options passed to the `resize` function. + For example: + `embed('north')` is now `resize(width, height, { fit: 'contain', position: 'north' })`, + `crop('attention')` is now `resize(width, height, { fit: 'cover', position: 'attention' })`, + `max().withoutEnlargement()` is now `resize(width, height, { fit: 'inside', withoutEnlargement: true })`. + [#1135](https://github.com/lovell/sharp/issues/1135) + * Drop Node 4 support. [#1212](https://github.com/lovell/sharp/issues/1212) diff --git a/lib/constructor.js b/lib/constructor.js index 5feca120c..6f07ea1bf 100644 --- a/lib/constructor.js +++ b/lib/constructor.js @@ -104,8 +104,7 @@ const Sharp = function (input, options) { width: -1, height: -1, canvas: 'crop', - crop: 0, - embed: 0, + position: 0, useExifOrientation: false, angle: 0, rotationAngle: 0, diff --git a/lib/resize.js b/lib/resize.js index 1a4b3271a..b9e568b48 100644 --- a/lib/resize.js +++ b/lib/resize.js @@ -1,9 +1,10 @@ 'use strict'; +const deprecate = require('util').deprecate; const is = require('./is'); /** - * Weighting to apply to image crop. + * Weighting to apply when using contain/cover fit. * @member * @private */ @@ -21,7 +22,23 @@ const gravity = { }; /** - * Strategies for automagic crop behaviour. + * Position to apply when using contain/cover fit. + * @member + * @private + */ +const position = { + top: 1, + right: 2, + bottom: 3, + left: 4, + 'right top': 5, + 'right bottom': 6, + 'left bottom': 7, + 'left top': 8 +}; + +/** + * Strategies for automagic cover behaviour. * @member * @private */ @@ -43,40 +60,135 @@ const kernel = { }; /** - * Resize image to `width` x `height`. - * By default, the resized image is centre cropped to the exact size specified. + * Methods by which an image can be resized to fit the provided dimensions. + * @member + * @private + */ +const fit = { + contain: 'contain', + cover: 'cover', + fill: 'fill', + inside: 'inside', + outside: 'outside' +}; + +/** + * Map external fit property to internal canvas property. + * @member + * @private + */ +const mapFitToCanvas = { + contain: 'embed', + cover: 'crop', + fill: 'ignore_aspect', + inside: 'max', + outside: 'min' +}; + +/** + * Resize image to `width`, `height` or `width x height`. + * + * When both a `width` and `height` are provided, the possible methods by which the image should **fit** these are: + * - `cover`: Crop to cover both provided dimensions (the default). + * - `contain`: Embed within both provided dimensions. + * - `fill`: Ignore the aspect ratio of the input and stretch to both provided dimensions. + * - `inside`: Preserving aspect ratio, resize the image to be as large as possible while ensuring its dimensions are less than or equal to both those specified. + * - `outside`: Preserving aspect ratio, resize the image to be as small as possible while ensuring its dimensions are greater than or equal to both those specified. + * Some of these values are based on the [object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) CSS property. * - * Possible kernels are: + * When using a `fit` of `cover` or `contain`, the default **position** is `centre`. Other options are: + * - `sharp.position`: `top`, `right top`, `right`, `right bottom`, `bottom`, `left bottom`, `left`, `left top`. + * - `sharp.gravity`: `north`, `northeast`, `east`, `southeast`, `south`, `southwest`, `west`, `northwest`, `center` or `centre`. + * - `sharp.strategy`: `cover` only, dynamically crop using either the `entropy` or `attention` strategy. + * Some of these values are based on the [object-position](https://developer.mozilla.org/en-US/docs/Web/CSS/object-position) CSS property. + * + * 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. + * + * Possible interpolation kernels are: * - `nearest`: Use [nearest neighbour interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation). * - `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). * * @example + * sharp(input) + * .resize({ width: 100 }) + * .toBuffer() + * .then(data => { + * // 100 pixels wide, auto-scaled height + * }); + * + * @example + * sharp(input) + * .resize({ height: 100 }) + * .toBuffer() + * .then(data => { + * // 100 pixels high, auto-scaled width + * }); + * + * @example * sharp(inputBuffer) * .resize(200, 300, { - * kernel: sharp.kernel.nearest + * kernel: sharp.kernel.nearest, + * fit: 'contain', + * position: 'right top' * }) * .background('white') - * .embed() * .toFile('output.tiff') - * .then(function() { + * .then(() => { * // output.tiff is a 200 pixels wide and 300 pixels high image - * // containing a nearest-neighbour scaled version, embedded on a white canvas, - * // of the image data in inputBuffer + * // containing a nearest-neighbour scaled version + * // embedded in the north-east corner of a white canvas + * }); + * + * @example + * const transformer = sharp() + * .resize({ + * width: 200, + * height: 200, + * fit: sharp.fit.cover, + * position: sharp.strategy.entropy + * }); + * // Read image data from readableStream + * // Write 200px square auto-cropped image data to writableStream + * readableStream + * .pipe(transformer) + * .pipe(writableStream); + * + * @example + * sharp(inputBuffer) + * .resize(200, 200, { + * fit: sharp.fit.inside, + * withoutEnlargement: true + * }) + * .toFormat('jpeg') + * .toBuffer() + * .then(function(outputBuffer) { + * // outputBuffer contains JPEG image data + * // no wider and no higher than 200 pixels + * // and no larger than the input image * }); * * @param {Number} [width] - pixels wide the resultant image should be. Use `null` or `undefined` to auto-scale the width to match the height. * @param {Number} [height] - pixels high the resultant image should be. Use `null` or `undefined` to auto-scale the height to match the width. - * @param {Object} [options] + * @param {String} [options.width] - alternative means of specifying `width`. If both are present this take priority. + * @param {String} [options.height] - alternative means of specifying `height`. If both are present this take priority. + * @param {String} [options.fit='cover'] - how the image should be resized to fit both provided dimensions, one of `cover`, `contain`, `fill`, `inside` or `outside`. + * @param {String} [options.position='centre'] - position, gravity or strategy to use when `fit` is `cover` or `contain`. * @param {String} [options.kernel='lanczos3'] - the kernel to use for image reduction. + * @param {Boolean} [options.withoutEnlargement=false] - do not enlarge if the width *or* height are already less than the specified dimensions, equivalent to GraphicsMagick's `>` geometry option. * @param {Boolean} [options.fastShrinkOnLoad=true] - take greater advantage of the JPEG and WebP shrink-on-load feature, which can lead to a slight moiré pattern on some images. * @returns {Sharp} * @throws {Error} Invalid parameters */ function resize (width, height, options) { if (is.defined(width)) { - if (is.integer(width) && width > 0) { + if (is.object(width) && !is.defined(options)) { + options = width; + } else if (is.integer(width) && width > 0) { this.options.width = width; } else { throw is.invalidParameterError('width', 'positive integer', width); @@ -94,6 +206,34 @@ function resize (width, height, options) { this.options.height = -1; } if (is.object(options)) { + // Width + if (is.integer(options.width) && options.width > 0) { + this.options.width = options.width; + } + // Height + if (is.integer(options.height) && options.height > 0) { + this.options.height = options.height; + } + // Fit + if (is.defined(options.fit)) { + const canvas = mapFitToCanvas[options.fit]; + if (is.string(canvas)) { + this.options.canvas = canvas; + } else { + throw is.invalidParameterError('fit', 'valid fit', options.fit); + } + } + // Position + if (is.defined(options.position)) { + const pos = is.integer(options.position) + ? options.position + : strategy[options.position] || position[options.position] || gravity[options.position]; + if (is.integer(pos) && (is.inRange(pos, 0, 8) || is.inRange(pos, 16, 17))) { + this.options.position = pos; + } else { + throw is.invalidParameterError('position', 'valid position/gravity/strategy', options.position); + } + } // Kernel if (is.defined(options.kernel)) { if (is.string(kernel[options.kernel])) { @@ -102,6 +242,10 @@ function resize (width, height, options) { throw is.invalidParameterError('kernel', 'valid kernel name', options.kernel); } } + // Without enlargement + if (is.defined(options.withoutEnlargement)) { + this._setBooleanOption('withoutEnlargement', options.withoutEnlargement); + } // Shrink on load if (is.defined(options.fastShrinkOnLoad)) { this._setBooleanOption('fastShrinkOnLoad', options.fastShrinkOnLoad); @@ -110,161 +254,6 @@ function resize (width, height, options) { 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 - */ -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 if (is.string(crop) && is.integer(strategy[crop])) { - // Strategy (string) - this.options.crop = strategy[crop]; - } else { - throw is.invalidParameterError('crop', 'valid crop id/name/strategy', 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, alpha: 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 - * }); - * @param {String} [embed='centre'] - A member of `sharp.gravity` to embed to an edge/corner. - * @returns {Sharp} - * @throws {Error} Invalid parameters - */ -function embed (embed) { - this.options.canvas = 'embed'; - - if (!is.defined(embed)) { - // Default - this.options.embed = gravity.center; - } else if (is.integer(embed) && is.inRange(embed, 0, 8)) { - // Gravity (numeric) - this.options.embed = embed; - } else if (is.string(embed) && is.integer(gravity[embed])) { - // Gravity (string) - this.options.embed = gravity[embed]; - } else { - throw is.invalidParameterError('embed', 'valid embed id/name', 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} - */ -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} - */ -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} - */ -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*". - * Use with `max()` to preserve the image's aspect ratio. - * - * The default behaviour *before* function call is `false`, meaning the image will be enlarged. - * - * @param {Boolean} [withoutEnlargement=true] - * @returns {Sharp} -*/ -function withoutEnlargement (withoutEnlargement) { - this.options.withoutEnlargement = is.bool(withoutEnlargement) ? withoutEnlargement : true; - 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. @@ -373,6 +362,92 @@ function trim (tolerance) { return this; } +// Deprecated functions + +/** + * @deprecated + * @private + */ +function crop (crop) { + this.options.canvas = 'crop'; + if (!is.defined(crop)) { + // Default + this.options.position = gravity.center; + } else if (is.integer(crop) && is.inRange(crop, 0, 8)) { + // Gravity (numeric) + this.options.position = crop; + } else if (is.string(crop) && is.integer(gravity[crop])) { + // Gravity (string) + this.options.position = gravity[crop]; + } else if (is.integer(crop) && crop >= strategy.entropy) { + // Strategy + this.options.position = crop; + } else if (is.string(crop) && is.integer(strategy[crop])) { + // Strategy (string) + this.options.position = strategy[crop]; + } else { + throw is.invalidParameterError('crop', 'valid crop id/name/strategy', crop); + } + return this; +} + +/** + * @deprecated + * @private + */ +function embed (embed) { + this.options.canvas = 'embed'; + if (!is.defined(embed)) { + // Default + this.options.position = gravity.center; + } else if (is.integer(embed) && is.inRange(embed, 0, 8)) { + // Gravity (numeric) + this.options.position = embed; + } else if (is.string(embed) && is.integer(gravity[embed])) { + // Gravity (string) + this.options.position = gravity[embed]; + } else { + throw is.invalidParameterError('embed', 'valid embed id/name', embed); + } + return this; +} + +/** + * @deprecated + * @private + */ +function max () { + this.options.canvas = 'max'; + return this; +} + +/** + * @deprecated + * @private + */ +function min () { + this.options.canvas = 'min'; + return this; +} + +/** + * @deprecated + * @private + */ +function ignoreAspectRatio () { + this.options.canvas = 'ignore_aspect'; + return this; +} + +/** + * @deprecated + * @private +*/ +function withoutEnlargement (withoutEnlargement) { + this.options.withoutEnlargement = is.bool(withoutEnlargement) ? withoutEnlargement : true; + return this; +} + /** * Decorate the Sharp prototype with resize-related functions. * @private @@ -380,12 +455,6 @@ function trim (tolerance) { module.exports = function (Sharp) { [ resize, - crop, - embed, - max, - min, - ignoreAspectRatio, - withoutEnlargement, extend, extract, trim @@ -396,4 +465,13 @@ module.exports = function (Sharp) { Sharp.gravity = gravity; Sharp.strategy = strategy; Sharp.kernel = kernel; + Sharp.fit = fit; + Sharp.position = position; + // Deprecated functions, to be removed in v0.22.0 + Sharp.prototype.crop = deprecate(crop, 'crop(position) is deprecated, use resize({ fit: "cover", position }) instead'); + Sharp.prototype.embed = deprecate(embed, 'embed(position) is deprecated, use resize({ fit: "contain", position }) instead'); + Sharp.prototype.max = deprecate(max, 'max() is deprecated, use resize({ fit: "inside" }) instead'); + Sharp.prototype.min = deprecate(min, 'min() is deprecated, use resize({ fit: "outside" }) instead'); + Sharp.prototype.ignoreAspectRatio = deprecate(ignoreAspectRatio, 'ignoreAspectRatio() is deprecated, use resize({ fit: "fill" }) instead'); + Sharp.prototype.withoutEnlargement = deprecate(withoutEnlargement, 'withoutEnlargement() is deprecated, use resize({ withoutEnlargement: true }) instead'); }; diff --git a/src/pipeline.cc b/src/pipeline.cc index 366adf8c4..f9dc18887 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -436,7 +436,7 @@ class PipelineWorker : public Nan::AsyncWorker { int width = std::max(image.width(), baton->width); int height = std::max(image.height(), baton->height); std::tie(left, top) = sharp::CalculateEmbedPosition( - image.width(), image.height(), baton->width, baton->height, baton->embed); + image.width(), image.height(), baton->width, baton->height, baton->position); image = image.embed(left, top, width, height, VImage::option() ->set("extend", VIPS_EXTEND_BACKGROUND) @@ -447,12 +447,12 @@ class PipelineWorker : public Nan::AsyncWorker { (image.width() > baton->width || image.height() > baton->height) ) { // Crop/max/min - if (baton->crop < 9) { + if (baton->position < 9) { // Gravity-based crop int left; int top; std::tie(left, top) = sharp::CalculateCrop( - image.width(), image.height(), baton->width, baton->height, baton->crop); + image.width(), image.height(), baton->width, baton->height, baton->position); int width = std::min(image.width(), baton->width); int height = std::min(image.height(), baton->height); image = image.extract_area(left, top, width, height); @@ -468,7 +468,7 @@ class PipelineWorker : public Nan::AsyncWorker { ->set("access", baton->accessMethod) ->set("threaded", TRUE)); image = image.smartcrop(baton->width, baton->height, VImage::option() - ->set("interesting", baton->crop == 16 ? VIPS_INTERESTING_ENTROPY : VIPS_INTERESTING_ATTENTION)); + ->set("interesting", baton->position == 16 ? VIPS_INTERESTING_ENTROPY : VIPS_INTERESTING_ATTENTION)); baton->hasCropOffset = true; baton->cropOffsetLeft = static_cast(image.xoffset()); baton->cropOffsetTop = static_cast(image.yoffset()); @@ -1159,8 +1159,7 @@ NAN_METHOD(pipeline) { } // Resize options baton->withoutEnlargement = AttrTo(options, "withoutEnlargement"); - baton->crop = AttrTo(options, "crop"); - baton->embed = AttrTo(options, "embed"); + baton->position = AttrTo(options, "position"); baton->kernel = AttrAsStr(options, "kernel"); baton->fastShrinkOnLoad = AttrTo(options, "fastShrinkOnLoad"); // Join Channel Options @@ -1299,7 +1298,7 @@ NAN_METHOD(pipeline) { // Force random access for certain operations if (baton->accessMethod == VIPS_ACCESS_SEQUENTIAL && ( baton->trimTolerance != 0 || baton->normalise || - baton->crop == 16 || baton->crop == 17)) { + baton->position == 16 || baton->position == 17)) { baton->accessMethod = VIPS_ACCESS_RANDOM; } diff --git a/src/pipeline.h b/src/pipeline.h index 357b4c497..475db8d5d 100644 --- a/src/pipeline.h +++ b/src/pipeline.h @@ -61,8 +61,7 @@ struct PipelineBaton { int height; int channels; Canvas canvas; - int crop; - int embed; + int position; bool hasCropOffset; int cropOffsetLeft; int cropOffsetTop; @@ -157,8 +156,7 @@ struct PipelineBaton { topOffsetPost(-1), channels(0), canvas(Canvas::CROP), - crop(0), - embed(0), + position(0), hasCropOffset(false), cropOffsetLeft(0), cropOffsetTop(0), diff --git a/test/fixtures/index.js b/test/fixtures/index.js index 2504af750..28e9a0b2e 100644 --- a/test/fixtures/index.js +++ b/test/fixtures/index.js @@ -15,8 +15,7 @@ const fingerprint = function (image, callback) { sharp(image) .greyscale() .normalise() - .resize(9, 8) - .ignoreAspectRatio() + .resize(9, 8, { fit: sharp.fit.fill }) .raw() .toBuffer(function (err, data) { if (err) { diff --git a/test/unit/colourspace.js b/test/unit/colourspace.js index d3d041b4b..0ef88331d 100644 --- a/test/unit/colourspace.js +++ b/test/unit/colourspace.js @@ -69,9 +69,8 @@ describe('Colour space conversion', function () { it('From CMYK to sRGB with white background, not yellow', function (done) { sharp(fixtures.inputJpgWithCmykProfile) - .resize(320, 240) + .resize(320, 240, { fit: sharp.fit.contain }) .background('white') - .embed() .toBuffer(function (err, data, info) { if (err) throw err; assert.strictEqual('jpeg', info.format); diff --git a/test/unit/crop.js b/test/unit/deprecated-crop.js similarity index 99% rename from test/unit/crop.js rename to test/unit/deprecated-crop.js index cfa80afa4..c477a02f7 100644 --- a/test/unit/crop.js +++ b/test/unit/deprecated-crop.js @@ -5,7 +5,7 @@ const assert = require('assert'); const sharp = require('../../'); const fixtures = require('../fixtures'); -describe('Crop', function () { +describe('Deprecated crop', function () { [ { name: 'North', diff --git a/test/unit/embed.js b/test/unit/deprecated-embed.js similarity index 96% rename from test/unit/embed.js rename to test/unit/deprecated-embed.js index e625db377..1937aa3a2 100644 --- a/test/unit/embed.js +++ b/test/unit/deprecated-embed.js @@ -5,7 +5,7 @@ const assert = require('assert'); const sharp = require('../../'); const fixtures = require('../fixtures'); -describe('Embed', function () { +describe('Deprecated embed', function () { it('Allows specifying the gravity as a string', function (done) { sharp(fixtures.inputJpg) .resize(320, 240) @@ -114,23 +114,6 @@ describe('Embed', function () { }); }); - it.skip('embed TIFF in LAB colourspace onto RGBA background', function (done) { - sharp(fixtures.inputTiffCielab) - .resize(64, 128) - .embed() - .background({r: 255, g: 102, b: 0, alpha: 0.5}) - .png() - .toBuffer(function (err, data, info) { - if (err) throw err; - assert.strictEqual(true, data.length > 0); - assert.strictEqual('png', info.format); - assert.strictEqual(64, info.width); - assert.strictEqual(128, info.height); - assert.strictEqual(4, info.channels); - fixtures.assertSimilar(fixtures.expected('embed-lab-into-rgba.png'), data, done); - }); - }); - it('Enlarge and embed', function (done) { sharp(fixtures.inputPngWithOneColor) .embed() diff --git a/test/unit/deprecated-resize.js b/test/unit/deprecated-resize.js new file mode 100644 index 000000000..432c33920 --- /dev/null +++ b/test/unit/deprecated-resize.js @@ -0,0 +1,261 @@ +'use strict'; + +const assert = require('assert'); + +const sharp = require('../../'); +const fixtures = require('../fixtures'); + +describe('Deprecated resize-related functions', function () { + it('Max width or height considering ratio (portrait)', function (done) { + sharp(fixtures.inputTiff) + .resize(320, 320) + .max() + .jpeg() + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('jpeg', info.format); + assert.strictEqual(243, info.width); + assert.strictEqual(320, info.height); + done(); + }); + }); + + it('Min width or height considering ratio (portrait)', function (done) { + sharp(fixtures.inputTiff) + .resize(320, 320) + .min() + .jpeg() + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('jpeg', info.format); + assert.strictEqual(320, info.width); + assert.strictEqual(422, info.height); + done(); + }); + }); + + it('Max width or height considering ratio (landscape)', function (done) { + sharp(fixtures.inputJpg) + .resize(320, 320) + .max() + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('jpeg', info.format); + assert.strictEqual(320, info.width); + assert.strictEqual(261, info.height); + done(); + }); + }); + + it('Provide only one dimension with max, should default to crop', function (done) { + sharp(fixtures.inputJpg) + .resize(320) + .max() + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('jpeg', info.format); + assert.strictEqual(320, info.width); + assert.strictEqual(261, info.height); + done(); + }); + }); + + it('Min width or height considering ratio (landscape)', function (done) { + sharp(fixtures.inputJpg) + .resize(320, 320) + .min() + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('jpeg', info.format); + assert.strictEqual(392, info.width); + assert.strictEqual(320, info.height); + done(); + }); + }); + + it('Provide only one dimension with min, should default to crop', function (done) { + sharp(fixtures.inputJpg) + .resize(320) + .min() + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('jpeg', info.format); + assert.strictEqual(320, info.width); + assert.strictEqual(261, info.height); + done(); + }); + }); + + it('Do not enlarge when input width is already less than output width', function (done) { + sharp(fixtures.inputJpg) + .resize(2800) + .withoutEnlargement() + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('jpeg', info.format); + assert.strictEqual(2725, info.width); + assert.strictEqual(2225, info.height); + done(); + }); + }); + + it('Do not enlarge when input height is already less than output height', function (done) { + sharp(fixtures.inputJpg) + .resize(null, 2300) + .withoutEnlargement() + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('jpeg', info.format); + assert.strictEqual(2725, info.width); + assert.strictEqual(2225, info.height); + done(); + }); + }); + + it('Do enlarge when input width is less than output width', function (done) { + sharp(fixtures.inputJpg) + .resize(2800) + .withoutEnlargement(false) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('jpeg', info.format); + assert.strictEqual(2800, info.width); + assert.strictEqual(2286, info.height); + done(); + }); + }); + + it('Downscale width and height, ignoring aspect ratio', function (done) { + sharp(fixtures.inputJpg) + .resize(320, 320) + .ignoreAspectRatio() + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('jpeg', info.format); + assert.strictEqual(320, info.width); + assert.strictEqual(320, info.height); + done(); + }); + }); + + it('Downscale width, ignoring aspect ratio', function (done) { + sharp(fixtures.inputJpg) + .resize(320) + .ignoreAspectRatio() + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('jpeg', info.format); + assert.strictEqual(320, info.width); + assert.strictEqual(2225, info.height); + done(); + }); + }); + + it('Downscale height, ignoring aspect ratio', function (done) { + sharp(fixtures.inputJpg) + .resize(null, 320) + .ignoreAspectRatio() + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('jpeg', info.format); + assert.strictEqual(2725, info.width); + assert.strictEqual(320, info.height); + done(); + }); + }); + + it('Upscale width and height, ignoring aspect ratio', function (done) { + sharp(fixtures.inputJpg) + .resize(3000, 3000) + .ignoreAspectRatio() + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('jpeg', info.format); + assert.strictEqual(3000, info.width); + assert.strictEqual(3000, info.height); + done(); + }); + }); + + it('Upscale width, ignoring aspect ratio', function (done) { + sharp(fixtures.inputJpg) + .resize(3000) + .ignoreAspectRatio() + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('jpeg', info.format); + assert.strictEqual(3000, info.width); + assert.strictEqual(2225, info.height); + done(); + }); + }); + + it('Upscale height, ignoring aspect ratio', function (done) { + sharp(fixtures.inputJpg) + .resize(null, 3000) + .ignoreAspectRatio() + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('jpeg', info.format); + assert.strictEqual(2725, info.width); + assert.strictEqual(3000, info.height); + done(); + }); + }); + + it('Downscale width, upscale height, ignoring aspect ratio', function (done) { + sharp(fixtures.inputJpg) + .resize(320, 3000) + .ignoreAspectRatio() + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('jpeg', info.format); + assert.strictEqual(320, info.width); + assert.strictEqual(3000, info.height); + done(); + }); + }); + + it('Upscale width, downscale height, ignoring aspect ratio', function (done) { + sharp(fixtures.inputJpg) + .resize(3000, 320) + .ignoreAspectRatio() + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('jpeg', info.format); + assert.strictEqual(3000, info.width); + assert.strictEqual(320, info.height); + done(); + }); + }); + + it('Identity transform, ignoring aspect ratio', function (done) { + sharp(fixtures.inputJpg) + .ignoreAspectRatio() + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('jpeg', info.format); + assert.strictEqual(2725, info.width); + assert.strictEqual(2225, info.height); + done(); + }); + }); +}); diff --git a/test/unit/extract.js b/test/unit/extract.js index 9b9629f01..107bbbaf4 100644 --- a/test/unit/extract.js +++ b/test/unit/extract.js @@ -69,8 +69,9 @@ describe('Partial image extraction', function () { it('After resize and crop', function (done) { sharp(fixtures.inputJpg) - .resize(500, 500) - .crop(sharp.gravity.north) + .resize(500, 500, { + position: sharp.gravity.north + }) .extract({ left: 10, top: 10, width: 100, height: 100 }) .toBuffer(function (err, data, info) { if (err) throw err; @@ -83,8 +84,9 @@ describe('Partial image extraction', function () { it('Before and after resize and crop', function (done) { sharp(fixtures.inputJpg) .extract({ left: 0, top: 0, width: 700, height: 700 }) - .resize(500, 500) - .crop(sharp.gravity.north) + .resize(500, 500, { + position: sharp.gravity.north + }) .extract({ left: 10, top: 10, width: 100, height: 100 }) .toBuffer(function (err, data, info) { if (err) throw err; diff --git a/test/unit/resize-contain.js b/test/unit/resize-contain.js new file mode 100644 index 000000000..423b472a4 --- /dev/null +++ b/test/unit/resize-contain.js @@ -0,0 +1,762 @@ +'use strict'; + +const assert = require('assert'); + +const sharp = require('../../'); +const fixtures = require('../fixtures'); + +describe('Resize fit=contain', function () { + it('Allows specifying the position as a string', function (done) { + sharp(fixtures.inputJpg) + .resize(320, 240, { + fit: 'contain', + position: 'center' + }) + .png() + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(320, info.width); + assert.strictEqual(240, info.height); + fixtures.assertSimilar(fixtures.expected('embed-3-into-3.png'), data, done); + }); + }); + + it('JPEG within PNG, no alpha channel', function (done) { + sharp(fixtures.inputJpg) + .resize(320, 240, { fit: 'contain' }) + .png() + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('png', info.format); + assert.strictEqual(320, info.width); + assert.strictEqual(240, info.height); + assert.strictEqual(3, info.channels); + fixtures.assertSimilar(fixtures.expected('embed-3-into-3.png'), data, done); + }); + }); + + it('JPEG within WebP, to include alpha channel', function (done) { + sharp(fixtures.inputJpg) + .resize(320, 240, { fit: 'contain' }) + .background({r: 0, g: 0, b: 0, alpha: 0}) + .webp() + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('webp', info.format); + assert.strictEqual(320, info.width); + assert.strictEqual(240, info.height); + assert.strictEqual(4, info.channels); + fixtures.assertSimilar(fixtures.expected('embed-3-into-4.webp'), data, done); + }); + }); + + it('PNG with alpha channel', function (done) { + sharp(fixtures.inputPngWithTransparency) + .resize(50, 50, { fit: 'contain' }) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('png', info.format); + assert.strictEqual(50, info.width); + assert.strictEqual(50, info.height); + assert.strictEqual(4, info.channels); + fixtures.assertSimilar(fixtures.expected('embed-4-into-4.png'), data, done); + }); + }); + + it('16-bit PNG with alpha channel', function (done) { + sharp(fixtures.inputPngWithTransparency16bit) + .resize(32, 16, { fit: 'contain' }) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('png', info.format); + assert.strictEqual(32, info.width); + assert.strictEqual(16, info.height); + assert.strictEqual(4, info.channels); + fixtures.assertSimilar(fixtures.expected('embed-16bit.png'), data, done); + }); + }); + + it('16-bit PNG with alpha channel onto RGBA', function (done) { + sharp(fixtures.inputPngWithTransparency16bit) + .resize(32, 16, { fit: 'contain' }) + .background({r: 0, g: 0, b: 0, alpha: 0}) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('png', info.format); + assert.strictEqual(32, info.width); + assert.strictEqual(16, info.height); + assert.strictEqual(4, info.channels); + fixtures.assertSimilar(fixtures.expected('embed-16bit-rgba.png'), data, done); + }); + }); + + it('PNG with 2 channels', function (done) { + sharp(fixtures.inputPngWithGreyAlpha) + .resize(32, 16, { fit: 'contain' }) + .background({r: 0, g: 0, b: 0, alpha: 0}) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('png', info.format); + assert.strictEqual(32, info.width); + assert.strictEqual(16, info.height); + assert.strictEqual(4, info.channels); + fixtures.assertSimilar(fixtures.expected('embed-2channel.png'), data, done); + }); + }); + + it.skip('TIFF in LAB colourspace onto RGBA background', function (done) { + sharp(fixtures.inputTiffCielab) + .resize(64, 128, { fit: 'contain' }) + .background({r: 255, g: 102, b: 0, alpha: 0.5}) + .png() + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('png', info.format); + assert.strictEqual(64, info.width); + assert.strictEqual(128, info.height); + assert.strictEqual(4, info.channels); + fixtures.assertSimilar(fixtures.expected('embed-lab-into-rgba.png'), data, done); + }); + }); + + it('Enlarge', function (done) { + sharp(fixtures.inputPngWithOneColor) + .resize(320, 240, { fit: 'contain' }) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('png', info.format); + assert.strictEqual(320, info.width); + assert.strictEqual(240, info.height); + assert.strictEqual(3, info.channels); + fixtures.assertSimilar(fixtures.expected('embed-enlarge.png'), data, done); + }); + }); + + it('Invalid position values should fail', function () { + [-1, 8.1, 9, 1000000, false, 'vallejo'].forEach(function (position) { + assert.throws(function () { + sharp().resize(null, null, { fit: 'contain', position }); + }); + }); + }); + + it('Position horizontal top', function (done) { + sharp(fixtures.inputPngEmbed) + .resize(200, 100, { + fit: sharp.fit.contain, + position: 'top' + }) + .background({r: 0, g: 0, b: 0, alpha: 0}) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('png', info.format); + assert.strictEqual(200, info.width); + assert.strictEqual(100, info.height); + assert.strictEqual(4, info.channels); + fixtures.assertSimilar(fixtures.expected('./embedgravitybird/a2-n.png'), data, done); + }); + }); + + it('Position horizontal right top', function (done) { + sharp(fixtures.inputPngEmbed) + .resize(200, 100, { + fit: sharp.fit.contain, + position: 'right top' + }) + .background({r: 0, g: 0, b: 0, alpha: 0}) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('png', info.format); + assert.strictEqual(200, info.width); + assert.strictEqual(100, info.height); + assert.strictEqual(4, info.channels); + fixtures.assertSimilar(fixtures.expected('./embedgravitybird/a3-ne.png'), data, done); + }); + }); + + it('Position horizontal right', function (done) { + sharp(fixtures.inputPngEmbed) + .resize(200, 100, { + fit: sharp.fit.contain, + position: 'right' + }) + .background({r: 0, g: 0, b: 0, alpha: 0}) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('png', info.format); + assert.strictEqual(200, info.width); + assert.strictEqual(100, info.height); + assert.strictEqual(4, info.channels); + fixtures.assertSimilar(fixtures.expected('./embedgravitybird/a4-e.png'), data, done); + }); + }); + + it('Position horizontal right bottom', function (done) { + sharp(fixtures.inputPngEmbed) + .resize(200, 100, { + fit: sharp.fit.contain, + position: 'right bottom' + }) + .background({r: 0, g: 0, b: 0, alpha: 0}) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('png', info.format); + assert.strictEqual(200, info.width); + assert.strictEqual(100, info.height); + assert.strictEqual(4, info.channels); + fixtures.assertSimilar(fixtures.expected('./embedgravitybird/a5-se.png'), data, done); + }); + }); + + it('Position horizontal bottom', function (done) { + sharp(fixtures.inputPngEmbed) + .resize(200, 100, { + fit: sharp.fit.contain, + position: 'bottom' + }) + .background({r: 0, g: 0, b: 0, alpha: 0}) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('png', info.format); + assert.strictEqual(200, info.width); + assert.strictEqual(100, info.height); + assert.strictEqual(4, info.channels); + fixtures.assertSimilar(fixtures.expected('./embedgravitybird/a6-s.png'), data, done); + }); + }); + + it('Position horizontal left bottom', function (done) { + sharp(fixtures.inputPngEmbed) + .resize(200, 100, { + fit: sharp.fit.contain, + position: 'left bottom' + }) + .background({r: 0, g: 0, b: 0, alpha: 0}) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('png', info.format); + assert.strictEqual(200, info.width); + assert.strictEqual(100, info.height); + assert.strictEqual(4, info.channels); + fixtures.assertSimilar(fixtures.expected('./embedgravitybird/a7-sw.png'), data, done); + }); + }); + + it('Position horizontal left', function (done) { + sharp(fixtures.inputPngEmbed) + .resize(200, 100, { + fit: sharp.fit.contain, + position: 'left' + }) + .background({r: 0, g: 0, b: 0, alpha: 0}) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('png', info.format); + assert.strictEqual(200, info.width); + assert.strictEqual(100, info.height); + assert.strictEqual(4, info.channels); + fixtures.assertSimilar(fixtures.expected('./embedgravitybird/a8-w.png'), data, done); + }); + }); + + it('Position horizontal left top', function (done) { + sharp(fixtures.inputPngEmbed) + .resize(200, 100, { + fit: sharp.fit.contain, + position: 'left top' + }) + .background({r: 0, g: 0, b: 0, alpha: 0}) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('png', info.format); + assert.strictEqual(200, info.width); + assert.strictEqual(100, info.height); + assert.strictEqual(4, info.channels); + fixtures.assertSimilar(fixtures.expected('./embedgravitybird/a1-nw.png'), data, done); + }); + }); + + it('Position horizontal north', function (done) { + sharp(fixtures.inputPngEmbed) + .resize(200, 100, { + fit: sharp.fit.contain, + position: sharp.gravity.north + }) + .background({r: 0, g: 0, b: 0, alpha: 0}) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('png', info.format); + assert.strictEqual(200, info.width); + assert.strictEqual(100, info.height); + assert.strictEqual(4, info.channels); + fixtures.assertSimilar(fixtures.expected('./embedgravitybird/a2-n.png'), data, done); + }); + }); + + it('Position horizontal northeast', function (done) { + sharp(fixtures.inputPngEmbed) + .resize(200, 100, { + fit: sharp.fit.contain, + position: sharp.gravity.northeast + }) + .background({r: 0, g: 0, b: 0, alpha: 0}) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('png', info.format); + assert.strictEqual(200, info.width); + assert.strictEqual(100, info.height); + assert.strictEqual(4, info.channels); + fixtures.assertSimilar(fixtures.expected('./embedgravitybird/a3-ne.png'), data, done); + }); + }); + + it('Position horizontal east', function (done) { + sharp(fixtures.inputPngEmbed) + .resize(200, 100, { + fit: sharp.fit.contain, + position: sharp.gravity.east + }) + .background({r: 0, g: 0, b: 0, alpha: 0}) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('png', info.format); + assert.strictEqual(200, info.width); + assert.strictEqual(100, info.height); + assert.strictEqual(4, info.channels); + fixtures.assertSimilar(fixtures.expected('./embedgravitybird/a4-e.png'), data, done); + }); + }); + + it('Position horizontal southeast', function (done) { + sharp(fixtures.inputPngEmbed) + .resize(200, 100, { + fit: sharp.fit.contain, + position: sharp.gravity.southeast + }) + .background({r: 0, g: 0, b: 0, alpha: 0}) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('png', info.format); + assert.strictEqual(200, info.width); + assert.strictEqual(100, info.height); + assert.strictEqual(4, info.channels); + fixtures.assertSimilar(fixtures.expected('./embedgravitybird/a5-se.png'), data, done); + }); + }); + + it('Position horizontal south', function (done) { + sharp(fixtures.inputPngEmbed) + .resize(200, 100, { + fit: sharp.fit.contain, + position: sharp.gravity.south + }) + .background({r: 0, g: 0, b: 0, alpha: 0}) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('png', info.format); + assert.strictEqual(200, info.width); + assert.strictEqual(100, info.height); + assert.strictEqual(4, info.channels); + fixtures.assertSimilar(fixtures.expected('./embedgravitybird/a6-s.png'), data, done); + }); + }); + + it('Position horizontal southwest', function (done) { + sharp(fixtures.inputPngEmbed) + .resize(200, 100, { + fit: sharp.fit.contain, + position: sharp.gravity.southwest + }) + .background({r: 0, g: 0, b: 0, alpha: 0}) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('png', info.format); + assert.strictEqual(200, info.width); + assert.strictEqual(100, info.height); + assert.strictEqual(4, info.channels); + fixtures.assertSimilar(fixtures.expected('./embedgravitybird/a7-sw.png'), data, done); + }); + }); + + it('Position horizontal west', function (done) { + sharp(fixtures.inputPngEmbed) + .resize(200, 100, { + fit: sharp.fit.contain, + position: sharp.gravity.west + }) + .background({r: 0, g: 0, b: 0, alpha: 0}) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('png', info.format); + assert.strictEqual(200, info.width); + assert.strictEqual(100, info.height); + assert.strictEqual(4, info.channels); + fixtures.assertSimilar(fixtures.expected('./embedgravitybird/a8-w.png'), data, done); + }); + }); + + it('Position horizontal northwest', function (done) { + sharp(fixtures.inputPngEmbed) + .resize(200, 100, { + fit: sharp.fit.contain, + position: sharp.gravity.northwest + }) + .background({r: 0, g: 0, b: 0, alpha: 0}) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('png', info.format); + assert.strictEqual(200, info.width); + assert.strictEqual(100, info.height); + assert.strictEqual(4, info.channels); + fixtures.assertSimilar(fixtures.expected('./embedgravitybird/a1-nw.png'), data, done); + }); + }); + + it('Position horizontal center', function (done) { + sharp(fixtures.inputPngEmbed) + .resize(200, 100, { + fit: sharp.fit.contain, + position: sharp.gravity.center + }) + .background({r: 0, g: 0, b: 0, alpha: 0}) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('png', info.format); + assert.strictEqual(200, info.width); + assert.strictEqual(100, info.height); + assert.strictEqual(4, info.channels); + fixtures.assertSimilar(fixtures.expected('./embedgravitybird/a9-c.png'), data, done); + }); + }); + + it('Position vertical top', function (done) { + sharp(fixtures.inputPngEmbed) + .resize(200, 200, { + fit: sharp.fit.contain, + position: 'top' + }) + .background({r: 0, g: 0, b: 0, alpha: 0}) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('png', info.format); + assert.strictEqual(200, info.width); + assert.strictEqual(200, info.height); + assert.strictEqual(4, info.channels); + fixtures.assertSimilar(fixtures.expected('./embedgravitybird/2-n.png'), data, done); + }); + }); + + it('Position vertical right top', function (done) { + sharp(fixtures.inputPngEmbed) + .resize(200, 200, { + fit: sharp.fit.contain, + position: 'right top' + }) + .background({r: 0, g: 0, b: 0, alpha: 0}) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('png', info.format); + assert.strictEqual(200, info.width); + assert.strictEqual(200, info.height); + assert.strictEqual(4, info.channels); + fixtures.assertSimilar(fixtures.expected('./embedgravitybird/3-ne.png'), data, done); + }); + }); + + it('Position vertical right', function (done) { + sharp(fixtures.inputPngEmbed) + .resize(200, 200, { + fit: sharp.fit.contain, + position: 'right' + }) + .background({r: 0, g: 0, b: 0, alpha: 0}) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('png', info.format); + assert.strictEqual(200, info.width); + assert.strictEqual(200, info.height); + assert.strictEqual(4, info.channels); + fixtures.assertSimilar(fixtures.expected('./embedgravitybird/4-e.png'), data, done); + }); + }); + + it('Position vertical right bottom', function (done) { + sharp(fixtures.inputPngEmbed) + .resize(200, 200, { + fit: sharp.fit.contain, + position: 'right bottom' + }) + .background({r: 0, g: 0, b: 0, alpha: 0}) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('png', info.format); + assert.strictEqual(200, info.width); + assert.strictEqual(200, info.height); + assert.strictEqual(4, info.channels); + fixtures.assertSimilar(fixtures.expected('./embedgravitybird/5-se.png'), data, done); + }); + }); + + it('Position vertical bottom', function (done) { + sharp(fixtures.inputPngEmbed) + .resize(200, 200, { + fit: sharp.fit.contain, + position: 'bottom' + }) + .background({r: 0, g: 0, b: 0, alpha: 0}) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('png', info.format); + assert.strictEqual(200, info.width); + assert.strictEqual(200, info.height); + assert.strictEqual(4, info.channels); + fixtures.assertSimilar(fixtures.expected('./embedgravitybird/6-s.png'), data, done); + }); + }); + + it('Position vertical left bottom', function (done) { + sharp(fixtures.inputPngEmbed) + .resize(200, 200, { + fit: sharp.fit.contain, + position: 'left bottom' + }) + .background({r: 0, g: 0, b: 0, alpha: 0}) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('png', info.format); + assert.strictEqual(200, info.width); + assert.strictEqual(200, info.height); + assert.strictEqual(4, info.channels); + fixtures.assertSimilar(fixtures.expected('./embedgravitybird/7-sw.png'), data, done); + }); + }); + + it('Position vertical left', function (done) { + sharp(fixtures.inputPngEmbed) + .resize(200, 200, { + fit: sharp.fit.contain, + position: 'left' + }) + .background({r: 0, g: 0, b: 0, alpha: 0}) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('png', info.format); + assert.strictEqual(200, info.width); + assert.strictEqual(200, info.height); + assert.strictEqual(4, info.channels); + fixtures.assertSimilar(fixtures.expected('./embedgravitybird/8-w.png'), data, done); + }); + }); + + it('Position vertical left top', function (done) { + sharp(fixtures.inputPngEmbed) + .resize(200, 200, { + fit: sharp.fit.contain, + position: 'left top' + }) + .background({r: 0, g: 0, b: 0, alpha: 0}) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('png', info.format); + assert.strictEqual(200, info.width); + assert.strictEqual(200, info.height); + assert.strictEqual(4, info.channels); + fixtures.assertSimilar(fixtures.expected('./embedgravitybird/1-nw.png'), data, done); + }); + }); + + it('Position vertical north', function (done) { + sharp(fixtures.inputPngEmbed) + .resize(200, 200, { + fit: sharp.fit.contain, + position: sharp.gravity.north + }) + .background({r: 0, g: 0, b: 0, alpha: 0}) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('png', info.format); + assert.strictEqual(200, info.width); + assert.strictEqual(200, info.height); + assert.strictEqual(4, info.channels); + fixtures.assertSimilar(fixtures.expected('./embedgravitybird/2-n.png'), data, done); + }); + }); + + it('Position vertical northeast', function (done) { + sharp(fixtures.inputPngEmbed) + .resize(200, 200, { + fit: sharp.fit.contain, + position: sharp.gravity.northeast + }) + .background({r: 0, g: 0, b: 0, alpha: 0}) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('png', info.format); + assert.strictEqual(200, info.width); + assert.strictEqual(200, info.height); + assert.strictEqual(4, info.channels); + fixtures.assertSimilar(fixtures.expected('./embedgravitybird/3-ne.png'), data, done); + }); + }); + + it('Position vertical east', function (done) { + sharp(fixtures.inputPngEmbed) + .resize(200, 200, { + fit: sharp.fit.contain, + position: sharp.gravity.east + }) + .background({r: 0, g: 0, b: 0, alpha: 0}) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('png', info.format); + assert.strictEqual(200, info.width); + assert.strictEqual(200, info.height); + assert.strictEqual(4, info.channels); + fixtures.assertSimilar(fixtures.expected('./embedgravitybird/4-e.png'), data, done); + }); + }); + + it('Position vertical southeast', function (done) { + sharp(fixtures.inputPngEmbed) + .resize(200, 200, { + fit: sharp.fit.contain, + position: sharp.gravity.southeast + }) + .background({r: 0, g: 0, b: 0, alpha: 0}) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('png', info.format); + assert.strictEqual(200, info.width); + assert.strictEqual(200, info.height); + assert.strictEqual(4, info.channels); + fixtures.assertSimilar(fixtures.expected('./embedgravitybird/5-se.png'), data, done); + }); + }); + + it('Position vertical south', function (done) { + sharp(fixtures.inputPngEmbed) + .resize(200, 200, { + fit: sharp.fit.contain, + position: sharp.gravity.south + }) + .background({r: 0, g: 0, b: 0, alpha: 0}) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('png', info.format); + assert.strictEqual(200, info.width); + assert.strictEqual(200, info.height); + assert.strictEqual(4, info.channels); + fixtures.assertSimilar(fixtures.expected('./embedgravitybird/6-s.png'), data, done); + }); + }); + + it('Position vertical southwest', function (done) { + sharp(fixtures.inputPngEmbed) + .resize(200, 200, { + fit: sharp.fit.contain, + position: sharp.gravity.southwest + }) + .background({r: 0, g: 0, b: 0, alpha: 0}) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('png', info.format); + assert.strictEqual(200, info.width); + assert.strictEqual(200, info.height); + assert.strictEqual(4, info.channels); + fixtures.assertSimilar(fixtures.expected('./embedgravitybird/7-sw.png'), data, done); + }); + }); + + it('Position vertical west', function (done) { + sharp(fixtures.inputPngEmbed) + .resize(200, 200, { + fit: sharp.fit.contain, + position: sharp.gravity.west + }) + .background({r: 0, g: 0, b: 0, alpha: 0}) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('png', info.format); + assert.strictEqual(200, info.width); + assert.strictEqual(200, info.height); + assert.strictEqual(4, info.channels); + fixtures.assertSimilar(fixtures.expected('./embedgravitybird/8-w.png'), data, done); + }); + }); + + it('Position vertical northwest', function (done) { + sharp(fixtures.inputPngEmbed) + .resize(200, 200, { + fit: sharp.fit.contain, + position: sharp.gravity.northwest + }) + .background({r: 0, g: 0, b: 0, alpha: 0}) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('png', info.format); + assert.strictEqual(200, info.width); + assert.strictEqual(200, info.height); + assert.strictEqual(4, info.channels); + fixtures.assertSimilar(fixtures.expected('./embedgravitybird/1-nw.png'), data, done); + }); + }); + + it('Position vertical center', function (done) { + sharp(fixtures.inputPngEmbed) + .resize(200, 200, { + fit: sharp.fit.contain, + position: sharp.gravity.center + }) + .background({r: 0, g: 0, b: 0, alpha: 0}) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('png', info.format); + assert.strictEqual(200, info.width); + assert.strictEqual(200, info.height); + assert.strictEqual(4, info.channels); + fixtures.assertSimilar(fixtures.expected('./embedgravitybird/9-c.png'), data, done); + }); + }); +}); diff --git a/test/unit/resize-cover.js b/test/unit/resize-cover.js new file mode 100644 index 000000000..321271fe3 --- /dev/null +++ b/test/unit/resize-cover.js @@ -0,0 +1,383 @@ +'use strict'; + +const assert = require('assert'); + +const sharp = require('../../'); +const fixtures = require('../fixtures'); + +describe('Resize fit=cover', function () { + [ + // Position + { + name: 'Position: top', + width: 320, + height: 80, + gravity: sharp.position.top, + fixture: 'gravity-north.jpg' + }, + { + name: 'Position: right', + width: 80, + height: 320, + gravity: sharp.position.right, + fixture: 'gravity-east.jpg' + }, + { + name: 'Position: bottom', + width: 320, + height: 80, + gravity: sharp.position.bottom, + fixture: 'gravity-south.jpg' + }, + { + name: 'Position: left', + width: 80, + height: 320, + gravity: sharp.position.left, + fixture: 'gravity-west.jpg' + }, + { + name: 'Position: right top (top)', + width: 320, + height: 80, + gravity: sharp.position['right top'], + fixture: 'gravity-north.jpg' + }, + { + name: 'Position: right top (right)', + width: 80, + height: 320, + gravity: sharp.position['right top'], + fixture: 'gravity-east.jpg' + }, + { + name: 'Position: right bottom (bottom)', + width: 320, + height: 80, + gravity: sharp.position['right bottom'], + fixture: 'gravity-south.jpg' + }, + { + name: 'Position: right bottom (right)', + width: 80, + height: 320, + gravity: sharp.position['right bottom'], + fixture: 'gravity-east.jpg' + }, + { + name: 'Position: left bottom (bottom)', + width: 320, + height: 80, + gravity: sharp.position['left bottom'], + fixture: 'gravity-south.jpg' + }, + { + name: 'Position: left bottom (left)', + width: 80, + height: 320, + gravity: sharp.position['left bottom'], + fixture: 'gravity-west.jpg' + }, + { + name: 'Position: left top (top)', + width: 320, + height: 80, + gravity: sharp.position['left top'], + fixture: 'gravity-north.jpg' + }, + { + name: 'Position: left top (left)', + width: 80, + height: 320, + gravity: sharp.position['left top'], + fixture: 'gravity-west.jpg' + }, + // Gravity + { + name: 'Gravity: north', + width: 320, + height: 80, + gravity: sharp.gravity.north, + fixture: 'gravity-north.jpg' + }, + { + name: 'Gravity: east', + width: 80, + height: 320, + gravity: sharp.gravity.east, + fixture: 'gravity-east.jpg' + }, + { + name: 'Gravity: south', + width: 320, + height: 80, + gravity: sharp.gravity.south, + fixture: 'gravity-south.jpg' + }, + { + name: 'Gravity: west', + width: 80, + height: 320, + gravity: sharp.gravity.west, + fixture: 'gravity-west.jpg' + }, + { + name: 'Gravity: center', + width: 320, + height: 80, + gravity: sharp.gravity.center, + fixture: 'gravity-center.jpg' + }, + { + name: 'Gravity: centre', + width: 80, + height: 320, + gravity: sharp.gravity.centre, + fixture: 'gravity-centre.jpg' + }, + { + name: 'Default (centre)', + width: 80, + height: 320, + gravity: undefined, + fixture: 'gravity-centre.jpg' + }, + { + name: 'Gravity: northeast (north)', + width: 320, + height: 80, + gravity: sharp.gravity.northeast, + fixture: 'gravity-north.jpg' + }, + { + name: 'Gravity: northeast (east)', + width: 80, + height: 320, + gravity: sharp.gravity.northeast, + fixture: 'gravity-east.jpg' + }, + { + name: 'Gravity: southeast (south)', + width: 320, + height: 80, + gravity: sharp.gravity.southeast, + fixture: 'gravity-south.jpg' + }, + { + name: 'Gravity: southeast (east)', + width: 80, + height: 320, + gravity: sharp.gravity.southeast, + fixture: 'gravity-east.jpg' + }, + { + name: 'Gravity: southwest (south)', + width: 320, + height: 80, + gravity: sharp.gravity.southwest, + fixture: 'gravity-south.jpg' + }, + { + name: 'Gravity: southwest (west)', + width: 80, + height: 320, + gravity: sharp.gravity.southwest, + fixture: 'gravity-west.jpg' + }, + { + name: 'Gravity: northwest (north)', + width: 320, + height: 80, + gravity: sharp.gravity.northwest, + fixture: 'gravity-north.jpg' + }, + { + name: 'Gravity: northwest (west)', + width: 80, + height: 320, + gravity: sharp.gravity.northwest, + fixture: 'gravity-west.jpg' + } + ].forEach(function (settings) { + it(settings.name, function (done) { + sharp(fixtures.inputJpg) + .resize(settings.width, settings.height, { + fit: sharp.fit.cover, + position: settings.gravity + }) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(settings.width, info.width); + assert.strictEqual(settings.height, info.height); + fixtures.assertSimilar(fixtures.expected(settings.fixture), data, done); + }); + }); + }); + + it('Allows specifying the gravity as a string', function (done) { + sharp(fixtures.inputJpg) + .resize(80, 320, { + fit: sharp.fit.cover, + position: 'east' + }) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(80, info.width); + assert.strictEqual(320, info.height); + fixtures.assertSimilar(fixtures.expected('gravity-east.jpg'), data, done); + }); + }); + + it('Invalid position values fail', function () { + assert.throws(function () { + sharp().resize(null, null, { fit: 'cover', position: 9 }); + }, /Expected valid position\/gravity\/strategy for position but received 9 of type number/); + assert.throws(function () { + sharp().resize(null, null, { fit: 'cover', position: 1.1 }); + }, /Expected valid position\/gravity\/strategy for position but received 1.1 of type number/); + assert.throws(function () { + sharp().resize(null, null, { fit: 'cover', position: -1 }); + }, /Expected valid position\/gravity\/strategy for position but received -1 of type number/); + assert.throws(function () { + sharp().resize(null, null, { fit: 'cover', position: 'zoinks' }).crop(); + }, /Expected valid position\/gravity\/strategy for position but received zoinks of type string/); + }); + + it('Uses default value when none specified', function () { + assert.doesNotThrow(function () { + sharp().resize(null, null, { fit: 'cover' }); + }); + }); + + it('Skip crop when post-resize dimensions are at target', function () { + return sharp(fixtures.inputJpg) + .resize(1600, 1200) + .toBuffer() + .then(function (input) { + return sharp(input) + .resize(1110, null, { + fit: sharp.fit.cover, + position: sharp.strategy.attention + }) + .toBuffer({ resolveWithObject: true }) + .then(function (result) { + assert.strictEqual(1110, result.info.width); + assert.strictEqual(832, result.info.height); + assert.strictEqual(undefined, result.info.cropOffsetLeft); + assert.strictEqual(undefined, result.info.cropOffsetTop); + }); + }); + }); + + describe('Entropy-based strategy', function () { + it('JPEG', function (done) { + sharp(fixtures.inputJpg) + .resize(80, 320, { + fit: 'cover', + position: sharp.strategy.entropy + }) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual('jpeg', info.format); + assert.strictEqual(3, info.channels); + assert.strictEqual(80, info.width); + assert.strictEqual(320, info.height); + assert.strictEqual(-117, info.cropOffsetLeft); + assert.strictEqual(0, info.cropOffsetTop); + fixtures.assertSimilar(fixtures.expected('crop-strategy-entropy.jpg'), data, done); + }); + }); + + it('PNG', function (done) { + sharp(fixtures.inputPngWithTransparency) + .resize(320, 80, { + fit: 'cover', + position: sharp.strategy.entropy + }) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual('png', info.format); + assert.strictEqual(4, info.channels); + assert.strictEqual(320, info.width); + assert.strictEqual(80, info.height); + assert.strictEqual(0, info.cropOffsetLeft); + assert.strictEqual(-80, info.cropOffsetTop); + fixtures.assertSimilar(fixtures.expected('crop-strategy.png'), data, done); + }); + }); + + it('supports the strategy passed as a string', function (done) { + sharp(fixtures.inputPngWithTransparency) + .resize(320, 80, { + fit: 'cover', + position: 'entropy' + }) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual('png', info.format); + assert.strictEqual(4, info.channels); + assert.strictEqual(320, info.width); + assert.strictEqual(80, info.height); + assert.strictEqual(0, info.cropOffsetLeft); + assert.strictEqual(-80, info.cropOffsetTop); + fixtures.assertSimilar(fixtures.expected('crop-strategy.png'), data, done); + }); + }); + }); + + describe('Attention strategy', function () { + it('JPEG', function (done) { + sharp(fixtures.inputJpg) + .resize(80, 320, { + fit: 'cover', + position: sharp.strategy.attention + }) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual('jpeg', info.format); + assert.strictEqual(3, info.channels); + assert.strictEqual(80, info.width); + assert.strictEqual(320, info.height); + assert.strictEqual(-143, info.cropOffsetLeft); + assert.strictEqual(0, info.cropOffsetTop); + fixtures.assertSimilar(fixtures.expected('crop-strategy-attention.jpg'), data, done); + }); + }); + + it('PNG', function (done) { + sharp(fixtures.inputPngWithTransparency) + .resize(320, 80, { + fit: 'cover', + position: sharp.strategy.attention + }) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual('png', info.format); + assert.strictEqual(4, info.channels); + assert.strictEqual(320, info.width); + assert.strictEqual(80, info.height); + assert.strictEqual(0, info.cropOffsetLeft); + assert.strictEqual(0, info.cropOffsetTop); + fixtures.assertSimilar(fixtures.expected('crop-strategy.png'), data, done); + }); + }); + + it('supports the strategy passed as a string', function (done) { + sharp(fixtures.inputPngWithTransparency) + .resize(320, 80, { + fit: 'cover', + position: 'attention' + }) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual('png', info.format); + assert.strictEqual(4, info.channels); + assert.strictEqual(320, info.width); + assert.strictEqual(80, info.height); + assert.strictEqual(0, info.cropOffsetLeft); + assert.strictEqual(0, info.cropOffsetTop); + fixtures.assertSimilar(fixtures.expected('crop-strategy.png'), data, done); + }); + }); + }); +}); diff --git a/test/unit/resize.js b/test/unit/resize.js index 1bb2004b8..62309c875 100644 --- a/test/unit/resize.js +++ b/test/unit/resize.js @@ -151,8 +151,7 @@ describe('Resize dimensions', function () { it('TIFF embed known to cause rounding errors', function (done) { sharp(fixtures.inputTiff) - .resize(240, 320) - .embed() + .resize(240, 320, { fit: sharp.fit.contain }) .jpeg() .toBuffer(function (err, data, info) { if (err) throw err; @@ -178,10 +177,9 @@ describe('Resize dimensions', function () { }); }); - it('Max width or height considering ratio (portrait)', function (done) { + it('fit=inside, portrait', function (done) { sharp(fixtures.inputTiff) - .resize(320, 320) - .max() + .resize(320, 320, { fit: sharp.fit.inside }) .jpeg() .toBuffer(function (err, data, info) { if (err) throw err; @@ -193,10 +191,9 @@ describe('Resize dimensions', function () { }); }); - it('Min width or height considering ratio (portrait)', function (done) { + it('fit=outside, portrait', function (done) { sharp(fixtures.inputTiff) - .resize(320, 320) - .min() + .resize(320, 320, { fit: sharp.fit.outside }) .jpeg() .toBuffer(function (err, data, info) { if (err) throw err; @@ -208,10 +205,9 @@ describe('Resize dimensions', function () { }); }); - it('Max width or height considering ratio (landscape)', function (done) { + it('fit=inside, landscape', function (done) { sharp(fixtures.inputJpg) - .resize(320, 320) - .max() + .resize(320, 320, { fit: sharp.fit.inside }) .toBuffer(function (err, data, info) { if (err) throw err; assert.strictEqual(true, data.length > 0); @@ -222,38 +218,41 @@ describe('Resize dimensions', function () { }); }); - it('Provide only one dimension with max, should default to crop', function (done) { + it('fit=outside, landscape', function (done) { sharp(fixtures.inputJpg) - .resize(320) - .max() + .resize(320, 320, { fit: sharp.fit.outside }) .toBuffer(function (err, data, info) { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('jpeg', info.format); - assert.strictEqual(320, info.width); - assert.strictEqual(261, info.height); + assert.strictEqual(392, info.width); + assert.strictEqual(320, info.height); done(); }); }); - it('Min width or height considering ratio (landscape)', function (done) { + it('fit=inside, provide only one dimension', function (done) { sharp(fixtures.inputJpg) - .resize(320, 320) - .min() + .resize({ + width: 320, + fit: sharp.fit.inside + }) .toBuffer(function (err, data, info) { if (err) throw err; assert.strictEqual(true, data.length > 0); assert.strictEqual('jpeg', info.format); - assert.strictEqual(392, info.width); - assert.strictEqual(320, info.height); + assert.strictEqual(320, info.width); + assert.strictEqual(261, info.height); done(); }); }); - it('Provide only one dimension with min, should default to crop', function (done) { + it('fit=outside, provide only one dimension', function (done) { sharp(fixtures.inputJpg) - .resize(320) - .min() + .resize({ + width: 320, + fit: sharp.fit.outside + }) .toBuffer(function (err, data, info) { if (err) throw err; assert.strictEqual(true, data.length > 0); @@ -266,8 +265,10 @@ describe('Resize dimensions', function () { it('Do not enlarge when input width is already less than output width', function (done) { sharp(fixtures.inputJpg) - .resize(2800) - .withoutEnlargement() + .resize({ + width: 2800, + withoutEnlargement: true + }) .toBuffer(function (err, data, info) { if (err) throw err; assert.strictEqual(true, data.length > 0); @@ -280,8 +281,10 @@ describe('Resize dimensions', function () { it('Do not enlarge when input height is already less than output height', function (done) { sharp(fixtures.inputJpg) - .resize(null, 2300) - .withoutEnlargement() + .resize({ + height: 2300, + withoutEnlargement: true + }) .toBuffer(function (err, data, info) { if (err) throw err; assert.strictEqual(true, data.length > 0); @@ -294,8 +297,10 @@ describe('Resize dimensions', function () { it('Do enlarge when input width is less than output width', function (done) { sharp(fixtures.inputJpg) - .resize(2800) - .withoutEnlargement(false) + .resize({ + width: 2800, + withoutEnlargement: false + }) .toBuffer(function (err, data, info) { if (err) throw err; assert.strictEqual(true, data.length > 0); @@ -306,103 +311,127 @@ describe('Resize dimensions', function () { }); }); - it('Downscale width and height, ignoring aspect ratio', function (done) { - sharp(fixtures.inputJpg).resize(320, 320).ignoreAspectRatio().toBuffer(function (err, data, info) { - if (err) throw err; - assert.strictEqual(true, data.length > 0); - assert.strictEqual('jpeg', info.format); - assert.strictEqual(320, info.width); - assert.strictEqual(320, info.height); - done(); - }); + it('fit=fill, downscale width and height', function (done) { + sharp(fixtures.inputJpg) + .resize(320, 320, { fit: 'fill' }) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('jpeg', info.format); + assert.strictEqual(320, info.width); + assert.strictEqual(320, info.height); + done(); + }); }); - it('Downscale width, ignoring aspect ratio', function (done) { - sharp(fixtures.inputJpg).resize(320).ignoreAspectRatio().toBuffer(function (err, data, info) { - if (err) throw err; - assert.strictEqual(true, data.length > 0); - assert.strictEqual('jpeg', info.format); - assert.strictEqual(320, info.width); - assert.strictEqual(2225, info.height); - done(); - }); + it('fit=fill, downscale width', function (done) { + sharp(fixtures.inputJpg) + .resize({ + width: 320, + fit: 'fill' + }) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('jpeg', info.format); + assert.strictEqual(320, info.width); + assert.strictEqual(2225, info.height); + done(); + }); }); - it('Downscale height, ignoring aspect ratio', function (done) { - sharp(fixtures.inputJpg).resize(null, 320).ignoreAspectRatio().toBuffer(function (err, data, info) { - if (err) throw err; - assert.strictEqual(true, data.length > 0); - assert.strictEqual('jpeg', info.format); - assert.strictEqual(2725, info.width); - assert.strictEqual(320, info.height); - done(); - }); + it('fit=fill, downscale height', function (done) { + sharp(fixtures.inputJpg) + .resize({ + height: 320, + fit: 'fill' + }) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('jpeg', info.format); + assert.strictEqual(2725, info.width); + assert.strictEqual(320, info.height); + done(); + }); }); - it('Upscale width and height, ignoring aspect ratio', function (done) { - sharp(fixtures.inputJpg).resize(3000, 3000).ignoreAspectRatio().toBuffer(function (err, data, info) { - if (err) throw err; - assert.strictEqual(true, data.length > 0); - assert.strictEqual('jpeg', info.format); - assert.strictEqual(3000, info.width); - assert.strictEqual(3000, info.height); - done(); - }); + it('fit=fill, upscale width and height', function (done) { + sharp(fixtures.inputJpg) + .resize(3000, 3000, { fit: 'fill' }) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('jpeg', info.format); + assert.strictEqual(3000, info.width); + assert.strictEqual(3000, info.height); + done(); + }); }); - it('Upscale width, ignoring aspect ratio', function (done) { - sharp(fixtures.inputJpg).resize(3000).ignoreAspectRatio().toBuffer(function (err, data, info) { - if (err) throw err; - assert.strictEqual(true, data.length > 0); - assert.strictEqual('jpeg', info.format); - assert.strictEqual(3000, info.width); - assert.strictEqual(2225, info.height); - done(); - }); + it('fit=fill, upscale width', function (done) { + sharp(fixtures.inputJpg) + .resize(3000, null, { fit: 'fill' }) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('jpeg', info.format); + assert.strictEqual(3000, info.width); + assert.strictEqual(2225, info.height); + done(); + }); }); - it('Upscale height, ignoring aspect ratio', function (done) { - sharp(fixtures.inputJpg).resize(null, 3000).ignoreAspectRatio().toBuffer(function (err, data, info) { - if (err) throw err; - assert.strictEqual(true, data.length > 0); - assert.strictEqual('jpeg', info.format); - assert.strictEqual(2725, info.width); - assert.strictEqual(3000, info.height); - done(); - }); + it('fit=fill, upscale height', function (done) { + sharp(fixtures.inputJpg) + .resize(null, 3000, { fit: 'fill' }) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('jpeg', info.format); + assert.strictEqual(2725, info.width); + assert.strictEqual(3000, info.height); + done(); + }); }); - it('Downscale width, upscale height, ignoring aspect ratio', function (done) { - sharp(fixtures.inputJpg).resize(320, 3000).ignoreAspectRatio().toBuffer(function (err, data, info) { - if (err) throw err; - assert.strictEqual(true, data.length > 0); - assert.strictEqual('jpeg', info.format); - assert.strictEqual(320, info.width); - assert.strictEqual(3000, info.height); - done(); - }); + it('fit=fill, downscale width, upscale height', function (done) { + sharp(fixtures.inputJpg) + .resize(320, 3000, { fit: 'fill' }) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('jpeg', info.format); + assert.strictEqual(320, info.width); + assert.strictEqual(3000, info.height); + done(); + }); }); - it('Upscale width, downscale height, ignoring aspect ratio', function (done) { - sharp(fixtures.inputJpg).resize(3000, 320).ignoreAspectRatio().toBuffer(function (err, data, info) { - if (err) throw err; - assert.strictEqual(true, data.length > 0); - assert.strictEqual('jpeg', info.format); - assert.strictEqual(3000, info.width); - assert.strictEqual(320, info.height); - done(); - }); + it('fit=fill, upscale width, downscale height', function (done) { + sharp(fixtures.inputJpg) + .resize(3000, 320, { fit: 'fill' }) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('jpeg', info.format); + assert.strictEqual(3000, info.width); + assert.strictEqual(320, info.height); + done(); + }); }); - it('Identity transform, ignoring aspect ratio', function (done) { - sharp(fixtures.inputJpg).ignoreAspectRatio().toBuffer(function (err, data, info) { - if (err) throw err; - assert.strictEqual(true, data.length > 0); - assert.strictEqual('jpeg', info.format); - assert.strictEqual(2725, info.width); - assert.strictEqual(2225, info.height); - done(); - }); + it('fit=fill, identity transform', function (done) { + sharp(fixtures.inputJpg) + .resize(null, null, { fit: 'fill' }) + .toBuffer(function (err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('jpeg', info.format); + assert.strictEqual(2725, info.width); + assert.strictEqual(2225, info.height); + done(); + }); }); it('Dimensions that result in differing even shrinks on each axis', function (done) { @@ -500,4 +529,16 @@ describe('Resize dimensions', function () { sharp().resize(null, null, { kernel: 'unknown' }); }); }); + + it('unknown fit throws', function () { + assert.throws(function () { + sharp().resize(null, null, { fit: 'unknown' }); + }); + }); + + it('unknown position throws', function () { + assert.throws(function () { + sharp().resize(null, null, { position: 'unknown' }); + }); + }); }); diff --git a/test/unit/rotate.js b/test/unit/rotate.js index 43b128737..2cc5554d8 100644 --- a/test/unit/rotate.js +++ b/test/unit/rotate.js @@ -107,8 +107,7 @@ describe('Rotation', function () { it('Rotate by 270 degrees, square output ignoring aspect ratio', function (done) { sharp(fixtures.inputJpg) - .resize(240, 240) - .ignoreAspectRatio() + .resize(240, 240, { fit: sharp.fit.fill }) .rotate(270) .toBuffer(function (err, data, info) { if (err) throw err; @@ -125,8 +124,7 @@ describe('Rotation', function () { it('Rotate by 315 degrees, square output ignoring aspect ratio', function (done) { sharp(fixtures.inputJpg) - .resize(240, 240) - .ignoreAspectRatio() + .resize(240, 240, { fit: sharp.fit.fill }) .rotate(315) .toBuffer(function (err, data, info) { if (err) throw err; @@ -143,8 +141,7 @@ describe('Rotation', function () { it('Rotate by 270 degrees, rectangular output ignoring aspect ratio', function (done) { sharp(fixtures.inputJpg) - .resize(320, 240) - .ignoreAspectRatio() + .resize(320, 240, { fit: sharp.fit.fill }) .rotate(270) .toBuffer(function (err, data, info) { if (err) throw err; @@ -161,8 +158,7 @@ describe('Rotation', function () { it('Rotate by 30 degrees, rectangular output ignoring aspect ratio', function (done) { sharp(fixtures.inputJpg) - .resize(320, 240) - .ignoreAspectRatio() + .resize(320, 240, { fit: sharp.fit.fill }) .rotate(30) .toBuffer(function (err, data, info) { if (err) throw err;