Add gravity support to embed feature (#1038)

This commit is contained in:
Kenric D'Souza 2017-12-13 00:59:16 +05:30 committed by Lovell Fuller
parent 1d7a0ea99e
commit 927b77700d
28 changed files with 426 additions and 7 deletions

View File

@ -136,6 +136,7 @@ const Sharp = function (input, options) {
height: -1,
canvas: 'crop',
crop: 0,
embed: 0,
useExifOrientation: false,
angle: 0,
rotateBeforePreExtract: false,

View File

@ -179,11 +179,26 @@ function crop (crop) {
* // 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 () {
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;
}

View File

@ -40,7 +40,8 @@
"Nicolas Coden <nicolas@ncoden.fr>",
"Matt Parrish <matt.r.parrish@gmail.com>",
"Matthew McEachen <matthew+github@mceachen.org>",
"Jarda Kotěšovec <jarda.kotesovec@gmail.com>"
"Jarda Kotěšovec <jarda.kotesovec@gmail.com>",
"Kenric D'Souza <kenric.dsouza@gmail.com>"
],
"scripts": {
"clean": "rm -rf node_modules/ build/ vendor/ coverage/ test/fixtures/output.*",
@ -66,6 +67,7 @@
"resize",
"thumbnail",
"crop",
"embed",
"libvips",
"vips"
],

View File

@ -409,7 +409,62 @@ namespace sharp {
/*
Calculate the (left, top) coordinates of the output image
within the input image, applying the given gravity.
within the input image, applying the given gravity during an embed.
@Azurebyte: We are basically swapping the inWidth and outWidth, inHeight and outHeight from the CalculateCrop function.
*/
std::tuple<int, int> CalculateEmbedPosition(int const inWidth, int const inHeight,
int const outWidth, int const outHeight, int const gravity) {
int left = 0;
int top = 0;
switch (gravity) {
case 1:
// North
left = (outWidth - inWidth) / 2;
break;
case 2:
// East
left = outWidth - inWidth;
top = (outHeight - inHeight) / 2;
break;
case 3:
// South
left = (outWidth - inWidth) / 2;
top = outHeight - inHeight;
break;
case 4:
// West
top = (outHeight - inHeight) / 2;
break;
case 5:
// Northeast
left = outWidth - inWidth;
break;
case 6:
// Southeast
left = outWidth - inWidth;
top = outHeight - inHeight;
break;
case 7:
// Southwest
top = outHeight - inHeight;
break;
case 8:
// Northwest
// Which is the default is 0,0 so we do not assign anything here.
break;
default:
// Centre
left = (outWidth - inWidth) / 2;
top = (outHeight - inHeight) / 2;
}
return std::make_tuple(left, top);
}
/*
Calculate the (left, top) coordinates of the output image
within the input image, applying the given gravity during a crop.
*/
std::tuple<int, int> CalculateCrop(int const inWidth, int const inHeight,
int const outWidth, int const outHeight, int const gravity) {

View File

@ -206,6 +206,13 @@ namespace sharp {
*/
std::string VipsWarningPop();
/*
Calculate the (left, top) coordinates of the output image
within the input image, applying the given gravity during an embed.
*/
std::tuple<int, int> CalculateEmbedPosition(int const inWidth, int const inHeight,
int const outWidth, int const outHeight, int const gravity);
/*
Calculate the (left, top) coordinates of the output image
within the input image, applying the given gravity.

View File

@ -446,12 +446,25 @@ class PipelineWorker : public Nan::AsyncWorker {
image = image.bandjoin(
VImage::new_matrix(image.width(), image.height()).new_from_image(255 * multiplier));
}
// Embed
int left = static_cast<int>(round((baton->width - image.width()) / 2));
int top = static_cast<int>(round((baton->height - image.height()) / 2));
image = image.embed(left, top, baton->width, baton->height, VImage::option()
// Calculate where to position the embeded image if gravity specified, else center.
int left;
int top;
left = static_cast<int>(round((baton->width - image.width()) / 2));
top = static_cast<int>(round((baton->height - image.height()) / 2));
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 = image.embed(left, top, width, height, VImage::option()
->set("extend", VIPS_EXTEND_BACKGROUND)
->set("background", background));
} else if (baton->canvas != Canvas::IGNORE_ASPECT) {
// Crop/max/min
if (baton->crop < 9) {
@ -1141,6 +1154,7 @@ NAN_METHOD(pipeline) {
// Resize options
baton->withoutEnlargement = AttrTo<bool>(options, "withoutEnlargement");
baton->crop = AttrTo<int32_t>(options, "crop");
baton->embed = AttrTo<int32_t>(options, "embed");
baton->kernel = AttrAsStr(options, "kernel");
baton->fastShrinkOnLoad = AttrTo<bool>(options, "fastShrinkOnLoad");
// Join Channel Options

View File

@ -62,6 +62,7 @@ struct PipelineBaton {
int channels;
Canvas canvas;
int crop;
int embed;
bool hasCropOffset;
int cropOffsetLeft;
int cropOffsetTop;
@ -145,6 +146,7 @@ struct PipelineBaton {
channels(0),
canvas(Canvas::CROP),
crop(0),
embed(0),
hasCropOffset(false),
cropOffsetLeft(0),
cropOffsetTop(0),

BIN
test/fixtures/embedgravitybird.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 476 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

View File

@ -85,6 +85,7 @@ module.exports = {
inputPngBooleanNoAlpha: getPath('bandbool.png'),
inputPngTestJoinChannel: getPath('testJoinChannel.png'),
inputPngTruncated: getPath('truncated.png'), // gm convert 2569067123_aca715a2ee_o.jpg -resize 320x240 saw.png ; head -c 10000 saw.png > truncated.png
inputPngEmbed: getPath('embedgravitybird.png'), // Released to sharp under a CC BY 4.0
inputWebP: getPath('4.webp'), // http://www.gstatic.com/webp/gallery/4.webp
inputWebPWithTransparency: getPath('5_webp_a.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp

View File

@ -6,6 +6,19 @@ const sharp = require('../../');
const fixtures = require('../fixtures');
describe('Embed', function () {
it('Allows specifying the gravity as a string', function (done) {
sharp(fixtures.inputJpg)
.resize(320, 240)
.embed('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)
.embed()
@ -132,4 +145,313 @@ describe('Embed', function () {
fixtures.assertSimilar(fixtures.expected('embed-enlarge.png'), data, done);
});
});
it('Embed invalid param values should fail', function () {
assert.throws(function () {
sharp().embed(-1);
});
assert.throws(function () {
sharp().embed(8.1);
});
assert.throws(function () {
sharp().embed(9);
});
assert.throws(function () {
sharp().embed(1000000);
});
assert.throws(function () {
sharp().embed(false);
});
assert.throws(function () {
sharp().embed('vallejo');
});
});
it('Embed gravity horizontal northwest', function (done) {
sharp(fixtures.inputPngEmbed)
.resize(200, 100)
.background({r: 0, g: 0, b: 0, alpha: 0})
.embed(sharp.gravity.northwest)
.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('Embed gravity horizontal north', function (done) {
sharp(fixtures.inputPngEmbed)
.resize(200, 100)
.background({r: 0, g: 0, b: 0, alpha: 0})
.embed(sharp.gravity.north)
.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('Embed gravity horizontal northeast', function (done) {
sharp(fixtures.inputPngEmbed)
.resize(200, 100)
.background({r: 0, g: 0, b: 0, alpha: 0})
.embed(sharp.gravity.northeast)
.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('Embed gravity horizontal east', function (done) {
sharp(fixtures.inputPngEmbed)
.resize(200, 100)
.background({r: 0, g: 0, b: 0, alpha: 0})
.embed(sharp.gravity.east)
.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('Embed gravity horizontal southeast', function (done) {
sharp(fixtures.inputPngEmbed)
.resize(200, 100)
.background({r: 0, g: 0, b: 0, alpha: 0})
.embed(sharp.gravity.southeast)
.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('Embed gravity horizontal south', function (done) {
sharp(fixtures.inputPngEmbed)
.resize(200, 100)
.background({r: 0, g: 0, b: 0, alpha: 0})
.embed(sharp.gravity.south)
.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('Embed gravity horizontal southwest', function (done) {
sharp(fixtures.inputPngEmbed)
.resize(200, 100)
.background({r: 0, g: 0, b: 0, alpha: 0})
.embed(sharp.gravity.southwest)
.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('Embed gravity horizontal west', function (done) {
sharp(fixtures.inputPngEmbed)
.resize(200, 100)
.background({r: 0, g: 0, b: 0, alpha: 0})
.embed(sharp.gravity.west)
.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('Embed gravity horizontal center', function (done) {
sharp(fixtures.inputPngEmbed)
.resize(200, 100)
.background({r: 0, g: 0, b: 0, alpha: 0})
.embed(sharp.gravity.center)
.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('Embed gravity vertical northwest', function (done) {
sharp(fixtures.inputPngEmbed)
.resize(200, 200)
.background({r: 0, g: 0, b: 0, alpha: 0})
.embed(sharp.gravity.northwest)
.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('Embed gravity vertical north', function (done) {
sharp(fixtures.inputPngEmbed)
.resize(200, 200)
.background({r: 0, g: 0, b: 0, alpha: 0})
.embed(sharp.gravity.north)
.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('Embed gravity vertical northeast', function (done) {
sharp(fixtures.inputPngEmbed)
.resize(200, 200)
.background({r: 0, g: 0, b: 0, alpha: 0})
.embed(sharp.gravity.northeast)
.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('Embed gravity vertical east', function (done) {
sharp(fixtures.inputPngEmbed)
.resize(200, 200)
.background({r: 0, g: 0, b: 0, alpha: 0})
.embed(sharp.gravity.east)
.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('Embed gravity vertical southeast', function (done) {
sharp(fixtures.inputPngEmbed)
.resize(200, 200)
.background({r: 0, g: 0, b: 0, alpha: 0})
.embed(sharp.gravity.southeast)
.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('Embed gravity vertical south', function (done) {
sharp(fixtures.inputPngEmbed)
.resize(200, 200)
.background({r: 0, g: 0, b: 0, alpha: 0})
.embed(sharp.gravity.south)
.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('Embed gravity vertical southwest', function (done) {
sharp(fixtures.inputPngEmbed)
.resize(200, 200)
.background({r: 0, g: 0, b: 0, alpha: 0})
.embed(sharp.gravity.southwest)
.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('Embed gravity vertical west', function (done) {
sharp(fixtures.inputPngEmbed)
.resize(200, 200)
.background({r: 0, g: 0, b: 0, alpha: 0})
.embed(sharp.gravity.west)
.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('Embed gravity vertical center', function (done) {
sharp(fixtures.inputPngEmbed)
.resize(200, 200)
.background({r: 0, g: 0, b: 0, alpha: 0})
.embed(sharp.gravity.center)
.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);
});
});
});