Add top/left offset support to overlayWith operation (#473)
70
index.js
@ -93,6 +93,8 @@ var Sharp = function(input, options) {
|
|||||||
overlayFileIn: '',
|
overlayFileIn: '',
|
||||||
overlayBufferIn: null,
|
overlayBufferIn: null,
|
||||||
overlayGravity: 0,
|
overlayGravity: 0,
|
||||||
|
overlayXOffset : -1,
|
||||||
|
overlayYOffset : -1,
|
||||||
overlayTile: false,
|
overlayTile: false,
|
||||||
overlayCutout: false,
|
overlayCutout: false,
|
||||||
// output options
|
// output options
|
||||||
@ -357,35 +359,61 @@ Sharp.prototype.overlayWith = function(overlay, options) {
|
|||||||
throw new Error('Unsupported overlay ' + typeof overlay);
|
throw new Error('Unsupported overlay ' + typeof overlay);
|
||||||
}
|
}
|
||||||
if (isObject(options)) {
|
if (isObject(options)) {
|
||||||
if (typeof options.tile === 'undefined') {
|
if(isDefined(options.tile)) {
|
||||||
this.options.overlayTile = false;
|
setTileOption(options.tile, this.options);
|
||||||
}
|
}
|
||||||
else if (isBoolean(options.tile)) {
|
if(isDefined(options.cutout)) {
|
||||||
this.options.overlayTile = options.tile;
|
setCutoutOption(options.cutout, this.options);
|
||||||
} else {
|
|
||||||
throw new Error('Invalid Value for tile ' + options.tile + ' Only Boolean Values allowed for overlay.tile.');
|
|
||||||
}
|
}
|
||||||
|
if(isDefined(options.left) || isDefined(options.top)) {
|
||||||
if (typeof options.cutout === 'undefined') {
|
setOffsetOption(options.top, options.left, this.options);
|
||||||
this.options.overlayCutout = false;
|
|
||||||
}
|
}
|
||||||
else if (isBoolean(options.cutout)) {
|
if (isDefined(options.gravity)) {
|
||||||
this.options.overlayCutout = options.cutout;
|
setGravityOption(options.gravity, this.options);
|
||||||
} else {
|
|
||||||
throw new Error('Invalid Value for cutout ' + options.cutout + ' Only Boolean Values allowed for overlay.cutout.');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isInteger(options.gravity) && inRange(options.gravity, 0, 8)) {
|
|
||||||
this.options.overlayGravity = options.gravity;
|
|
||||||
} else if (isString(options.gravity) && isInteger(module.exports.gravity[options.gravity])) {
|
|
||||||
this.options.overlayGravity = module.exports.gravity[options.gravity];
|
|
||||||
} else if (isDefined(options.gravity)) {
|
|
||||||
throw new Error('Unsupported overlay gravity ' + options.gravity);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Supporting functions for overlayWith
|
||||||
|
*/
|
||||||
|
function setTileOption(tile, options) {
|
||||||
|
if(isBoolean(tile)) {
|
||||||
|
options.overlayTile = tile;
|
||||||
|
} else {
|
||||||
|
throw new Error('Invalid Value for tile ' + tile + ' Only Boolean Values allowed for overlay.tile.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setCutoutOption(cutout, options) {
|
||||||
|
if(isBoolean(cutout)) {
|
||||||
|
options.overlayCutout = cutout;
|
||||||
|
} else {
|
||||||
|
throw new Error('Invalid Value for cutout ' + cutout + ' Only Boolean Values allowed for overlay.cutout.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setOffsetOption(top, left, options) {
|
||||||
|
if(isInteger(left) && left >= 0 && isInteger(top) && top >= 0) {
|
||||||
|
options.overlayXOffset = left;
|
||||||
|
options.overlayYOffset = top;
|
||||||
|
} else {
|
||||||
|
throw new Error('Unsupported top and/or left offset values');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setGravityOption(gravity, options) {
|
||||||
|
if(isInteger(gravity) && inRange(gravity, 0, 8)) {
|
||||||
|
options.overlayGravity = gravity;
|
||||||
|
} else if (isString(gravity) && isInteger(module.exports.gravity[gravity])) {
|
||||||
|
options.overlayGravity = module.exports.gravity[gravity];
|
||||||
|
} else {
|
||||||
|
throw new Error('Unsupported overlay gravity ' + gravity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Rotate output image by 0, 90, 180 or 270 degrees
|
Rotate output image by 0, 90, 180 or 270 degrees
|
||||||
Auto-rotation based on the EXIF Orientation tag is represented by an angle of -1
|
Auto-rotation based on the EXIF Orientation tag is represented by an angle of -1
|
||||||
|
@ -277,6 +277,40 @@ namespace sharp {
|
|||||||
return std::make_tuple(left, top);
|
return std::make_tuple(left, top);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Calculate the (left, top) coordinates of the output image
|
||||||
|
within the input image, applying the given x and y offsets.
|
||||||
|
*/
|
||||||
|
std::tuple<int, int> CalculateCrop(int const inWidth, int const inHeight,
|
||||||
|
int const outWidth, int const outHeight, int const x, int const y) {
|
||||||
|
|
||||||
|
// default values
|
||||||
|
int left = 0;
|
||||||
|
int top = 0;
|
||||||
|
|
||||||
|
// assign only if valid
|
||||||
|
if(x >= 0 && x < (inWidth - outWidth)) {
|
||||||
|
left = x;
|
||||||
|
} else if(x >= (inWidth - outWidth)) {
|
||||||
|
left = inWidth - outWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(y >= 0 && y < (inHeight - outHeight)) {
|
||||||
|
top = y;
|
||||||
|
} else if(x >= (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 the image alpha maximum. Useful for combining alpha bands. scRGB
|
Return the image alpha maximum. Useful for combining alpha bands. scRGB
|
||||||
images are 0 - 1 for image data, but the alpha is 0 - 255.
|
images are 0 - 1 for image data, but the alpha is 0 - 255.
|
||||||
|
@ -108,8 +108,16 @@ namespace sharp {
|
|||||||
std::tuple<int, int> CalculateCrop(int const inWidth, int const inHeight,
|
std::tuple<int, int> CalculateCrop(int const inWidth, int const inHeight,
|
||||||
int const outWidth, int const outHeight, int const gravity);
|
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 x and y offsets of the output image.
|
||||||
|
*/
|
||||||
|
std::tuple<int, int> CalculateCrop(int const inWidth, int const inHeight,
|
||||||
|
int const outWidth, int const outHeight, int const x, int const y);
|
||||||
|
|
||||||
int MaximumImageAlpha(VipsInterpretation interpretation);
|
int MaximumImageAlpha(VipsInterpretation interpretation);
|
||||||
|
|
||||||
|
|
||||||
} // namespace sharp
|
} // namespace sharp
|
||||||
|
|
||||||
#endif // SRC_COMMON_H_
|
#endif // SRC_COMMON_H_
|
||||||
|
@ -16,6 +16,49 @@ namespace sharp {
|
|||||||
Assumes alpha channels are already premultiplied and will be unpremultiplied after.
|
Assumes alpha channels are already premultiplied and will be unpremultiplied after.
|
||||||
*/
|
*/
|
||||||
VImage Composite(VImage src, VImage dst, const int gravity) {
|
VImage Composite(VImage src, VImage dst, const int gravity) {
|
||||||
|
if(IsInputValidForComposition(src, dst)) {
|
||||||
|
// Enlarge overlay src, if required
|
||||||
|
if (src.width() < dst.width() || src.height() < dst.height()) {
|
||||||
|
// Calculate the (left, top) coordinates of the output image within the input image, applying the given gravity.
|
||||||
|
int left;
|
||||||
|
int top;
|
||||||
|
std::tie(left, top) = CalculateCrop(dst.width(), dst.height(), src.width(), src.height(), gravity);
|
||||||
|
// Embed onto transparent background
|
||||||
|
std::vector<double> background { 0.0, 0.0, 0.0, 0.0 };
|
||||||
|
src = src.embed(left, top, dst.width(), dst.height(), VImage::option()
|
||||||
|
->set("extend", VIPS_EXTEND_BACKGROUND)
|
||||||
|
->set("background", background)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return CompositeImage(src, dst);
|
||||||
|
}
|
||||||
|
// If the input was not valid for composition the return the input image itself
|
||||||
|
return dst;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
VImage Composite(VImage src, VImage dst, const int x, const int y) {
|
||||||
|
if(IsInputValidForComposition(src, dst)) {
|
||||||
|
// Enlarge overlay src, if required
|
||||||
|
if (src.width() < dst.width() || src.height() < dst.height()) {
|
||||||
|
// Calculate the (left, top) coordinates of the output image within the input image, applying the given gravity.
|
||||||
|
int left;
|
||||||
|
int top;
|
||||||
|
std::tie(left, top) = CalculateCrop(dst.width(), dst.height(), src.width(), src.height(), x, y);
|
||||||
|
// Embed onto transparent background
|
||||||
|
std::vector<double> background { 0.0, 0.0, 0.0, 0.0 };
|
||||||
|
src = src.embed(left, top, dst.width(), dst.height(), VImage::option()
|
||||||
|
->set("extend", VIPS_EXTEND_BACKGROUND)
|
||||||
|
->set("background", background)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return CompositeImage(src, dst);
|
||||||
|
}
|
||||||
|
// If the input was not valid for composition the return the input image itself
|
||||||
|
return dst;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsInputValidForComposition(VImage src, VImage dst) {
|
||||||
using sharp::CalculateCrop;
|
using sharp::CalculateCrop;
|
||||||
using sharp::HasAlpha;
|
using sharp::HasAlpha;
|
||||||
|
|
||||||
@ -29,20 +72,10 @@ namespace sharp {
|
|||||||
throw VError("Overlay image must have same dimensions or smaller");
|
throw VError("Overlay image must have same dimensions or smaller");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enlarge overlay src, if required
|
return true;
|
||||||
if (src.width() < dst.width() || src.height() < dst.height()) {
|
}
|
||||||
// Calculate the (left, top) coordinates of the output image within the input image, applying the given gravity.
|
|
||||||
int left;
|
|
||||||
int top;
|
|
||||||
std::tie(left, top) = CalculateCrop(dst.width(), dst.height(), src.width(), src.height(), gravity);
|
|
||||||
// Embed onto transparent background
|
|
||||||
std::vector<double> background { 0.0, 0.0, 0.0, 0.0 };
|
|
||||||
src = src.embed(left, top, dst.width(), dst.height(), VImage::option()
|
|
||||||
->set("extend", VIPS_EXTEND_BACKGROUND)
|
|
||||||
->set("background", background)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
VImage CompositeImage(VImage src, VImage dst) {
|
||||||
// Split src into non-alpha and alpha channels
|
// Split src into non-alpha and alpha channels
|
||||||
VImage srcWithoutAlpha = src.extract_band(0, VImage::option()->set("n", src.bands() - 1));
|
VImage srcWithoutAlpha = src.extract_band(0, VImage::option()->set("n", src.bands() - 1));
|
||||||
VImage srcAlpha = src[src.bands() - 1] * (1.0 / 255.0);
|
VImage srcAlpha = src[src.bands() - 1] * (1.0 / 255.0);
|
||||||
|
@ -16,8 +16,24 @@ namespace sharp {
|
|||||||
VImage Composite(VImage src, VImage dst, const int gravity);
|
VImage Composite(VImage src, VImage dst, const int gravity);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Cutout src over dst with given gravity.
|
Alpha composite src over dst with given x and y offsets.
|
||||||
|
Assumes alpha channels are already premultiplied and will be unpremultiplied after.
|
||||||
*/
|
*/
|
||||||
|
VImage Composite(VImage src, VImage dst, const int x, const int y);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Check if the src and dst Images for composition operation are valid
|
||||||
|
*/
|
||||||
|
bool IsInputValidForComposition(VImage src, VImage dst);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Given a valid src and dst, returns the composite of the two images
|
||||||
|
*/
|
||||||
|
VImage CompositeImage(VImage src, VImage dst);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Cutout src over dst with given gravity.
|
||||||
|
*/
|
||||||
VImage Cutout(VImage src, VImage dst, const int gravity);
|
VImage Cutout(VImage src, VImage dst, const int gravity);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -703,10 +703,19 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
int left;
|
int left;
|
||||||
int top;
|
int top;
|
||||||
overlayImage = overlayImage.replicate(across, down);
|
overlayImage = overlayImage.replicate(across, down);
|
||||||
// the overlayGravity will now be used to CalculateCrop for extract_area
|
|
||||||
std::tie(left, top) = CalculateCrop(
|
if(baton->overlayXOffset >= 0 && baton->overlayYOffset >= 0) {
|
||||||
overlayImage.width(), overlayImage.height(), image.width(), image.height(), baton->overlayGravity
|
// the overlayX/YOffsets will now be used to CalculateCrop for extract_area
|
||||||
);
|
std::tie(left, top) = CalculateCrop(
|
||||||
|
overlayImage.width(), overlayImage.height(), image.width(), image.height(),
|
||||||
|
baton->overlayXOffset, baton->overlayYOffset
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// the overlayGravity will now be used to CalculateCrop for extract_area
|
||||||
|
std::tie(left, top) = CalculateCrop(
|
||||||
|
overlayImage.width(), overlayImage.height(), image.width(), image.height(), baton->overlayGravity
|
||||||
|
);
|
||||||
|
}
|
||||||
overlayImage = overlayImage.extract_area(
|
overlayImage = overlayImage.extract_area(
|
||||||
left, top, image.width(), image.height()
|
left, top, image.width(), image.height()
|
||||||
);
|
);
|
||||||
@ -720,8 +729,13 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
} else {
|
} else {
|
||||||
// Ensure overlay is premultiplied sRGB
|
// Ensure overlay is premultiplied sRGB
|
||||||
overlayImage = overlayImage.colourspace(VIPS_INTERPRETATION_sRGB).premultiply();
|
overlayImage = overlayImage.colourspace(VIPS_INTERPRETATION_sRGB).premultiply();
|
||||||
// Composite images with given gravity
|
if(baton->overlayXOffset >= 0 && baton->overlayYOffset >= 0) {
|
||||||
image = Composite(overlayImage, image, baton->overlayGravity);
|
// Composite images with given offsets
|
||||||
|
image = Composite(overlayImage, image, baton->overlayXOffset, baton->overlayYOffset);
|
||||||
|
} else {
|
||||||
|
// Composite images with given gravity
|
||||||
|
image = Composite(overlayImage, image, baton->overlayGravity);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1105,6 +1119,8 @@ NAN_METHOD(pipeline) {
|
|||||||
baton->overlayBufferIn = node::Buffer::Data(overlayBufferIn);
|
baton->overlayBufferIn = node::Buffer::Data(overlayBufferIn);
|
||||||
}
|
}
|
||||||
baton->overlayGravity = attrAs<int32_t>(options, "overlayGravity");
|
baton->overlayGravity = attrAs<int32_t>(options, "overlayGravity");
|
||||||
|
baton->overlayXOffset = attrAs<int32_t>(options, "overlayXOffset");
|
||||||
|
baton->overlayYOffset = attrAs<int32_t>(options, "overlayYOffset");
|
||||||
baton->overlayTile = attrAs<bool>(options, "overlayTile");
|
baton->overlayTile = attrAs<bool>(options, "overlayTile");
|
||||||
baton->overlayCutout = attrAs<bool>(options, "overlayCutout");
|
baton->overlayCutout = attrAs<bool>(options, "overlayCutout");
|
||||||
// Resize options
|
// Resize options
|
||||||
|
@ -35,6 +35,8 @@ struct PipelineBaton {
|
|||||||
char *overlayBufferIn;
|
char *overlayBufferIn;
|
||||||
size_t overlayBufferInLength;
|
size_t overlayBufferInLength;
|
||||||
int overlayGravity;
|
int overlayGravity;
|
||||||
|
int overlayXOffset;
|
||||||
|
int overlayYOffset;
|
||||||
bool overlayTile;
|
bool overlayTile;
|
||||||
bool overlayCutout;
|
bool overlayCutout;
|
||||||
int topOffsetPre;
|
int topOffsetPre;
|
||||||
@ -107,6 +109,8 @@ struct PipelineBaton {
|
|||||||
bufferOutLength(0),
|
bufferOutLength(0),
|
||||||
overlayBufferInLength(0),
|
overlayBufferInLength(0),
|
||||||
overlayGravity(0),
|
overlayGravity(0),
|
||||||
|
overlayXOffset(-1),
|
||||||
|
overlayYOffset(-1),
|
||||||
overlayTile(false),
|
overlayTile(false),
|
||||||
overlayCutout(false),
|
overlayCutout(false),
|
||||||
topOffsetPre(-1),
|
topOffsetPre(-1),
|
||||||
|
BIN
test/fixtures/expected/overlay-offset-0.jpg
vendored
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
test/fixtures/expected/overlay-offset-with-gravity-tile.jpg
vendored
Normal file
After Width: | Height: | Size: 64 KiB |
BIN
test/fixtures/expected/overlay-offset-with-gravity.jpg
vendored
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
test/fixtures/expected/overlay-offset-with-tile.jpg
vendored
Normal file
After Width: | Height: | Size: 64 KiB |
BIN
test/fixtures/expected/overlay-valid-offsets-10-10.jpg
vendored
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
test/fixtures/expected/overlay-valid-offsets-100-300.jpg
vendored
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
test/fixtures/expected/overlay-very-large-offset.jpg
vendored
Normal file
After Width: | Height: | Size: 24 KiB |
@ -264,6 +264,164 @@ describe('Overlays', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("Overlay with top-left offsets", function() {
|
||||||
|
it('Overlay with 10px top & 10px left offsets', function(done) {
|
||||||
|
var expected = fixtures.expected('overlay-valid-offsets-10-10.jpg');
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(400)
|
||||||
|
.overlayWith(fixtures.inputPngWithTransparency16bit, {
|
||||||
|
top: 10,
|
||||||
|
left: 10
|
||||||
|
})
|
||||||
|
.toBuffer(function(err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(3, info.channels);
|
||||||
|
fixtures.assertSimilar(expected, data, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Overlay with 100px top & 300px left offsets', function(done) {
|
||||||
|
var expected = fixtures.expected('overlay-valid-offsets-100-300.jpg');
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(400)
|
||||||
|
.overlayWith(fixtures.inputPngWithTransparency16bit, {
|
||||||
|
top: 100,
|
||||||
|
left: 300
|
||||||
|
})
|
||||||
|
.toBuffer(function(err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(3, info.channels);
|
||||||
|
fixtures.assertSimilar(expected, data, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Overlay with only top offset', function() {
|
||||||
|
assert.throws(function() {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(400)
|
||||||
|
.overlayWith(fixtures.inputPngWithTransparency16bit, {
|
||||||
|
top: 1000
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Overlay with only left offset', function() {
|
||||||
|
assert.throws(function() {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(400)
|
||||||
|
.overlayWith(fixtures.inputPngWithTransparency16bit, {
|
||||||
|
left: 1000
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Overlay with negative offsets', function() {
|
||||||
|
assert.throws(function() {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(400)
|
||||||
|
.overlayWith(fixtures.inputPngWithTransparency16bit, {
|
||||||
|
top: -1000,
|
||||||
|
left: -1000
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Overlay with 0 offset', function(done) {
|
||||||
|
var expected = fixtures.expected('overlay-offset-0.jpg');
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(400)
|
||||||
|
.overlayWith(fixtures.inputPngWithTransparency16bit, {
|
||||||
|
top: 0,
|
||||||
|
left: 0
|
||||||
|
})
|
||||||
|
.toBuffer(function(err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(3, info.channels);
|
||||||
|
fixtures.assertSimilar(expected, data, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Overlay with offset and gravity', function(done) {
|
||||||
|
var expected = fixtures.expected('overlay-offset-with-gravity.jpg');
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(400)
|
||||||
|
.overlayWith(fixtures.inputPngWithTransparency16bit, {
|
||||||
|
left: 10,
|
||||||
|
top: 10,
|
||||||
|
gravity : 4
|
||||||
|
|
||||||
|
})
|
||||||
|
.toBuffer(function(err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(3, info.channels);
|
||||||
|
fixtures.assertSimilar(expected, data, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Overlay with offset and gravity and tile', function(done) {
|
||||||
|
var expected = fixtures.expected('overlay-offset-with-gravity-tile.jpg');
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(400)
|
||||||
|
.overlayWith(fixtures.inputPngWithTransparency16bit, {
|
||||||
|
left: 10,
|
||||||
|
top: 10,
|
||||||
|
gravity : 4,
|
||||||
|
tile: true
|
||||||
|
})
|
||||||
|
.toBuffer(function(err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(3, info.channels);
|
||||||
|
fixtures.assertSimilar(expected, data, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Overlay with offset and tile', function(done) {
|
||||||
|
var expected = fixtures.expected('overlay-offset-with-tile.jpg');
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(400)
|
||||||
|
.overlayWith(fixtures.inputPngWithTransparency16bit, {
|
||||||
|
left: 10,
|
||||||
|
top: 10,
|
||||||
|
tile: true
|
||||||
|
})
|
||||||
|
.toBuffer(function(err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(3, info.channels);
|
||||||
|
fixtures.assertSimilar(expected, data, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('Overlay with very large offset', function(done) {
|
||||||
|
var expected = fixtures.expected('overlay-very-large-offset.jpg');
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(400)
|
||||||
|
.overlayWith(fixtures.inputPngWithTransparency16bit, {
|
||||||
|
left: 1000000,
|
||||||
|
top: 100000
|
||||||
|
})
|
||||||
|
.toBuffer(function(err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(3, info.channels);
|
||||||
|
fixtures.assertSimilar(expected, data, done);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('With tile enabled and image rotated 90 degrees', function(done) {
|
it('With tile enabled and image rotated 90 degrees', function(done) {
|
||||||
var expected = fixtures.expected('overlay-tile-rotated90.jpg');
|
var expected = fixtures.expected('overlay-tile-rotated90.jpg');
|
||||||
sharp(fixtures.inputJpg)
|
sharp(fixtures.inputJpg)
|
||||||
|