mirror of
https://github.com/lovell/sharp.git
synced 2025-07-12 20:10:13 +02:00
Allow for negative top/left offsets in composite overlays
A top or left offset value of -1 will no longer mean that the value is not set, but will now be an actual offset of -1. INT_MIN for left & top will mean that the values are not set. Co-authored-by: Christian Flintrup <chr@gigahost.dk>
This commit is contained in:
parent
182beaa4a1
commit
02676140e8
@ -10,6 +10,10 @@ Requires libvips v8.10.5
|
|||||||
|
|
||||||
* Remove experimental status from `heif` output, defaults are now AVIF-centric.
|
* Remove experimental status from `heif` output, defaults are now AVIF-centric.
|
||||||
|
|
||||||
|
* Allow negative top/left offsets for composite operation.
|
||||||
|
[#2391](https://github.com/lovell/sharp/pull/2391)
|
||||||
|
[@CurosMJ](https://github.com/CurosMJ)
|
||||||
|
|
||||||
* Ensure all platforms use fontconfig for font rendering.
|
* Ensure all platforms use fontconfig for font rendering.
|
||||||
[#2399](https://github.com/lovell/sharp/issues/2399)
|
[#2399](https://github.com/lovell/sharp/issues/2399)
|
||||||
|
|
||||||
|
@ -105,8 +105,9 @@ function composite (images) {
|
|||||||
input: this._createInputDescriptor(image.input, inputOptions, { allowStream: false }),
|
input: this._createInputDescriptor(image.input, inputOptions, { allowStream: false }),
|
||||||
blend: 'over',
|
blend: 'over',
|
||||||
tile: false,
|
tile: false,
|
||||||
left: -1,
|
left: 0,
|
||||||
top: -1,
|
top: 0,
|
||||||
|
hasOffset: false,
|
||||||
gravity: 0,
|
gravity: 0,
|
||||||
premultiplied: false
|
premultiplied: false
|
||||||
};
|
};
|
||||||
@ -125,21 +126,23 @@ function composite (images) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (is.defined(image.left)) {
|
if (is.defined(image.left)) {
|
||||||
if (is.integer(image.left) && image.left >= 0) {
|
if (is.integer(image.left)) {
|
||||||
composite.left = image.left;
|
composite.left = image.left;
|
||||||
} else {
|
} else {
|
||||||
throw is.invalidParameterError('left', 'positive integer', image.left);
|
throw is.invalidParameterError('left', 'integer', image.left);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (is.defined(image.top)) {
|
if (is.defined(image.top)) {
|
||||||
if (is.integer(image.top) && image.top >= 0) {
|
if (is.integer(image.top)) {
|
||||||
composite.top = image.top;
|
composite.top = image.top;
|
||||||
} else {
|
} else {
|
||||||
throw is.invalidParameterError('top', 'positive integer', image.top);
|
throw is.invalidParameterError('top', 'integer', image.top);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (composite.left !== composite.top && Math.min(composite.left, composite.top) === -1) {
|
if (is.defined(image.top) !== is.defined(image.left)) {
|
||||||
throw new Error('Expected both left and top to be set');
|
throw new Error('Expected both left and top to be set');
|
||||||
|
} else {
|
||||||
|
composite.hasOffset = is.integer(image.top) && is.integer(image.left);
|
||||||
}
|
}
|
||||||
if (is.defined(image.gravity)) {
|
if (is.defined(image.gravity)) {
|
||||||
if (is.integer(image.gravity) && is.inRange(image.gravity, 0, 8)) {
|
if (is.integer(image.gravity) && is.inRange(image.gravity, 0, 8)) {
|
||||||
|
@ -70,7 +70,9 @@
|
|||||||
"Roman Malieiev <aromaleev@gmail.com>",
|
"Roman Malieiev <aromaleev@gmail.com>",
|
||||||
"Tomas Szabo <tomas.szabo@deftomat.com>",
|
"Tomas Szabo <tomas.szabo@deftomat.com>",
|
||||||
"Robert O'Rourke <robert@o-rourke.org>",
|
"Robert O'Rourke <robert@o-rourke.org>",
|
||||||
"Guillermo Alfonso Varela Chouciño <guillevch@gmail.com>"
|
"Guillermo Alfonso Varela Chouciño <guillevch@gmail.com>",
|
||||||
|
"Christian Flintrup <chr@gigahost.dk>",
|
||||||
|
"Manan Jadhav <manan@motionden.com>"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node-gyp rebuild && node install/dll-copy)",
|
"install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node-gyp rebuild && node install/dll-copy)",
|
||||||
|
@ -658,26 +658,18 @@ namespace sharp {
|
|||||||
int top = 0;
|
int top = 0;
|
||||||
|
|
||||||
// assign only if valid
|
// assign only if valid
|
||||||
if (x >= 0 && x < (inWidth - outWidth)) {
|
if (x < (inWidth - outWidth)) {
|
||||||
left = x;
|
left = x;
|
||||||
} else if (x >= (inWidth - outWidth)) {
|
} else if (x >= (inWidth - outWidth)) {
|
||||||
left = inWidth - outWidth;
|
left = inWidth - outWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (y >= 0 && y < (inHeight - outHeight)) {
|
if (y < (inHeight - outHeight)) {
|
||||||
top = y;
|
top = y;
|
||||||
} else if (y >= (inHeight - outHeight)) {
|
} else if (y >= (inHeight - outHeight)) {
|
||||||
top = inHeight - outHeight;
|
top = inHeight - outHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
// the resulting left and top could have been outside the image after calculation from bottom/right edges
|
|
||||||
if (left < 0) {
|
|
||||||
left = 0;
|
|
||||||
}
|
|
||||||
if (top < 0) {
|
|
||||||
top = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::make_tuple(left, top);
|
return std::make_tuple(left, top);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -570,7 +570,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
int left;
|
int left;
|
||||||
int top;
|
int top;
|
||||||
compositeImage = compositeImage.replicate(across, down);
|
compositeImage = compositeImage.replicate(across, down);
|
||||||
if (composite->left >= 0 && composite->top >= 0) {
|
if (composite->hasOffset) {
|
||||||
std::tie(left, top) = sharp::CalculateCrop(
|
std::tie(left, top) = sharp::CalculateCrop(
|
||||||
compositeImage.width(), compositeImage.height(), image.width(), image.height(),
|
compositeImage.width(), compositeImage.height(), image.width(), image.height(),
|
||||||
composite->left, composite->top);
|
composite->left, composite->top);
|
||||||
@ -592,7 +592,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
// Calculate position
|
// Calculate position
|
||||||
int left;
|
int left;
|
||||||
int top;
|
int top;
|
||||||
if (composite->left >= 0 && composite->top >= 0) {
|
if (composite->hasOffset) {
|
||||||
// Composite image at given offsets
|
// Composite image at given offsets
|
||||||
std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),
|
std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),
|
||||||
compositeImage.width(), compositeImage.height(), composite->left, composite->top);
|
compositeImage.width(), compositeImage.height(), composite->left, composite->top);
|
||||||
@ -1253,6 +1253,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|||||||
composite->gravity = sharp::AttrAsUint32(compositeObject, "gravity");
|
composite->gravity = sharp::AttrAsUint32(compositeObject, "gravity");
|
||||||
composite->left = sharp::AttrAsInt32(compositeObject, "left");
|
composite->left = sharp::AttrAsInt32(compositeObject, "left");
|
||||||
composite->top = sharp::AttrAsInt32(compositeObject, "top");
|
composite->top = sharp::AttrAsInt32(compositeObject, "top");
|
||||||
|
composite->hasOffset = sharp::AttrAsBool(compositeObject, "hasOffset");
|
||||||
composite->tile = sharp::AttrAsBool(compositeObject, "tile");
|
composite->tile = sharp::AttrAsBool(compositeObject, "tile");
|
||||||
composite->premultiplied = sharp::AttrAsBool(compositeObject, "premultiplied");
|
composite->premultiplied = sharp::AttrAsBool(compositeObject, "premultiplied");
|
||||||
baton->composite.push_back(composite);
|
baton->composite.push_back(composite);
|
||||||
|
@ -40,6 +40,7 @@ struct Composite {
|
|||||||
int gravity;
|
int gravity;
|
||||||
int left;
|
int left;
|
||||||
int top;
|
int top;
|
||||||
|
bool hasOffset;
|
||||||
bool tile;
|
bool tile;
|
||||||
bool premultiplied;
|
bool premultiplied;
|
||||||
|
|
||||||
@ -47,8 +48,9 @@ struct Composite {
|
|||||||
input(nullptr),
|
input(nullptr),
|
||||||
mode(VIPS_BLEND_MODE_OVER),
|
mode(VIPS_BLEND_MODE_OVER),
|
||||||
gravity(0),
|
gravity(0),
|
||||||
left(-1),
|
left(0),
|
||||||
top(-1),
|
top(0),
|
||||||
|
hasOffset(false),
|
||||||
tile(false),
|
tile(false),
|
||||||
premultiplied(false) {}
|
premultiplied(false) {}
|
||||||
};
|
};
|
||||||
|
BIN
test/fixtures/expected/overlay-negative-offset-with-gravity.jpg
vendored
Normal file
BIN
test/fixtures/expected/overlay-negative-offset-with-gravity.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
@ -172,6 +172,24 @@ describe('composite', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('negative offset and gravity', done => {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(400)
|
||||||
|
.composite([{
|
||||||
|
input: fixtures.inputPngWithTransparency16bit,
|
||||||
|
left: -10,
|
||||||
|
top: -10,
|
||||||
|
gravity: 4
|
||||||
|
}])
|
||||||
|
.toBuffer((err, data, info) => {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(3, info.channels);
|
||||||
|
fixtures.assertSimilar(
|
||||||
|
fixtures.expected('overlay-negative-offset-with-gravity.jpg'), data, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('offset, gravity and tile', done => {
|
it('offset, gravity and tile', done => {
|
||||||
sharp(fixtures.inputJpg)
|
sharp(fixtures.inputJpg)
|
||||||
.resize(80)
|
.resize(80)
|
||||||
@ -333,13 +351,25 @@ describe('composite', () => {
|
|||||||
it('invalid left', () => {
|
it('invalid left', () => {
|
||||||
assert.throws(() => {
|
assert.throws(() => {
|
||||||
sharp().composite([{ input: 'test', left: 0.5 }]);
|
sharp().composite([{ input: 'test', left: 0.5 }]);
|
||||||
}, /Expected positive integer for left but received 0.5 of type number/);
|
}, /Expected integer for left but received 0.5 of type number/);
|
||||||
|
assert.throws(() => {
|
||||||
|
sharp().composite([{ input: 'test', left: 'invalid' }]);
|
||||||
|
}, /Expected integer for left but received invalid of type string/);
|
||||||
|
assert.throws(() => {
|
||||||
|
sharp().composite([{ input: 'test', left: 'invalid', top: 10 }]);
|
||||||
|
}, /Expected integer for left but received invalid of type string/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('invalid top', () => {
|
it('invalid top', () => {
|
||||||
assert.throws(() => {
|
assert.throws(() => {
|
||||||
sharp().composite([{ input: 'test', top: -1 }]);
|
sharp().composite([{ input: 'test', top: 0.5 }]);
|
||||||
}, /Expected positive integer for top but received -1 of type number/);
|
}, /Expected integer for top but received 0.5 of type number/);
|
||||||
|
assert.throws(() => {
|
||||||
|
sharp().composite([{ input: 'test', top: 'invalid' }]);
|
||||||
|
}, /Expected integer for top but received invalid of type string/);
|
||||||
|
assert.throws(() => {
|
||||||
|
sharp().composite([{ input: 'test', top: 'invalid', left: 10 }]);
|
||||||
|
}, /Expected integer for top but received invalid of type string/);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('left but no top', () => {
|
it('left but no top', () => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user