Add support for repeated/tiled overlay image (#443)

USAGE: overlayWith('overlayimage.png', { tile: true, gravity: northwest} )
When using the tile option, the gravity option is applied to the extracted part of the tiled overlay image.
This commit is contained in:
lemnisk8 2016-05-26 21:12:17 +05:30 committed by Lovell Fuller
parent e699e36270
commit 62554b766f
16 changed files with 103 additions and 0 deletions

View File

@ -92,6 +92,7 @@ var Sharp = function(input, options) {
overlayFileIn: '', overlayFileIn: '',
overlayBufferIn: null, overlayBufferIn: null,
overlayGravity: 0, overlayGravity: 0,
overlayTile: false,
// output options // output options
formatOut: 'input', formatOut: 'input',
fileOut: '', fileOut: '',
@ -354,6 +355,15 @@ 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') {
this.options.overlayTile = false;
}
else if (isBoolean(options.tile)) {
this.options.overlayTile = options.tile;
} else {
throw new Error(' Invalid Value for tile ' + options.tile + 'Only Boolean Values allowed for overlay.tile.');
}
if (isInteger(options.gravity) && inRange(options.gravity, 0, 8)) { if (isInteger(options.gravity) && inRange(options.gravity, 0, 8)) {
this.options.overlayGravity = options.gravity; this.options.overlayGravity = options.gravity;
} else if (isString(options.gravity) && isInteger(module.exports.gravity[options.gravity])) { } else if (isString(options.gravity) && isInteger(module.exports.gravity[options.gravity])) {

View File

@ -664,6 +664,35 @@ class PipelineWorker : public AsyncWorker {
if (overlayImageType == ImageType::UNKNOWN) { if (overlayImageType == ImageType::UNKNOWN) {
return Error(); return Error();
} }
// Check if overlay is tiled
if (baton->overlayTile) {
int overlayImageWidth = overlayImage.width();
int overlayImageHeight = overlayImage.height();
int across = 0;
int down = 0;
// use gravity in ovelay
if(overlayImageWidth <= baton->width) {
across = static_cast<int>(ceil(static_cast<double>(baton->width) / overlayImageWidth));
}
if(overlayImageHeight <= baton->height) {
down = static_cast<int>(ceil(static_cast<double>(baton->height) / overlayImageHeight));
}
if(across != 0 || down != 0) {
int left;
int top;
overlayImage = overlayImage.replicate(across, down);
// the overlayGravity will now be used to CalculateCrop for extract_area
std::tie(left, top) = CalculateCrop(
overlayImage.width(), overlayImage.height(), baton->width, baton->height, baton->overlayGravity
);
overlayImage = overlayImage.extract_area(
left, top, baton->width, baton->height
);
}
// the overlayGravity was used for extract_area, therefore set it back to its default value of 0
baton->overlayGravity = 0;
}
// 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 // Composite images with given gravity
@ -1050,6 +1079,7 @@ 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->overlayTile = attrAs<bool>(options, "overlayTile");
// Resize options // Resize options
baton->withoutEnlargement = attrAs<bool>(options, "withoutEnlargement"); baton->withoutEnlargement = attrAs<bool>(options, "withoutEnlargement");
baton->crop = attrAs<int32_t>(options, "crop"); baton->crop = attrAs<int32_t>(options, "crop");

View File

@ -33,6 +33,7 @@ struct PipelineBaton {
char *overlayBufferIn; char *overlayBufferIn;
size_t overlayBufferInLength; size_t overlayBufferInLength;
int overlayGravity; int overlayGravity;
bool overlayTile;
int topOffsetPre; int topOffsetPre;
int leftOffsetPre; int leftOffsetPre;
int widthPre; int widthPre;
@ -97,6 +98,7 @@ struct PipelineBaton {
bufferOutLength(0), bufferOutLength(0),
overlayBufferInLength(0), overlayBufferInLength(0),
overlayGravity(0), overlayGravity(0),
overlayTile(false),
topOffsetPre(-1), topOffsetPre(-1),
topOffsetPost(-1), topOffsetPost(-1),
channels(0), channels(0),

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@ -242,4 +242,65 @@ describe('Overlays', function() {
}); });
}); });
describe('Overlay with tile enabled and gravity', function() {
Object.keys(sharp.gravity).forEach(function(gravity) {
it(gravity, function(done) {
var expected = fixtures.expected('overlay-tile-gravity-' + gravity + '.jpg');
sharp(fixtures.inputJpg)
.resize(80)
.overlayWith(fixtures.inputPngWithTransparency16bit, {
tile: true,
gravity: gravity
})
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual('jpeg', info.format);
assert.strictEqual(80, info.width);
assert.strictEqual(65, info.height);
assert.strictEqual(3, info.channels);
fixtures.assertSimilar(expected, data, done);
});
});
});
});
it('With tile enabled and image rotated 90 degrees', function(done) {
var expected = fixtures.expected('overlay-tile-rotated90.jpg');
sharp(fixtures.inputJpg)
.rotate(90)
.resize(80)
.overlayWith(fixtures.inputPngWithTransparency16bit, {
tile: true
})
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual('jpeg', info.format);
assert.strictEqual(80, info.width);
assert.strictEqual(98, info.height);
assert.strictEqual(3, info.channels);
fixtures.assertSimilar(expected, data, done);
});
});
it('With tile enabled and image rotated 90 degrees and gravity northwest', function(done) {
var expected = fixtures.expected('overlay-tile-rotated90-gravity-northwest.jpg');
sharp(fixtures.inputJpg)
.rotate(90)
.resize(80)
.overlayWith(fixtures.inputPngWithTransparency16bit, {
tile: true,
gravity: 'northwest'
})
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual('jpeg', info.format);
assert.strictEqual(80, info.width);
assert.strictEqual(98, info.height);
assert.strictEqual(3, info.channels);
fixtures.assertSimilar(expected, data, done);
});
});
}); });