Expose angle option in tile feature (#1121)

This commit is contained in:
Andrea Bianco 2018-02-18 20:00:08 +01:00 committed by Lovell Fuller
parent 1a4e68096f
commit f86ae79fdb
4 changed files with 99 additions and 11 deletions

View File

@ -319,6 +319,7 @@ function toFormat (format, options) {
* @param {Object} [tile] * @param {Object} [tile]
* @param {Number} [tile.size=256] tile size in pixels, a value between 1 and 8192. * @param {Number} [tile.size=256] tile size in pixels, a value between 1 and 8192.
* @param {Number} [tile.overlap=0] tile overlap in pixels, a value between 0 and 8192. * @param {Number} [tile.overlap=0] tile overlap in pixels, a value between 0 and 8192.
* @param {Number} [tile.angle=0] tile, angle of rotation, must be a multiple of 90..
* @param {String} [tile.container='fs'] tile container, with value `fs` (filesystem) or `zip` (compressed file). * @param {String} [tile.container='fs'] tile container, with value `fs` (filesystem) or `zip` (compressed file).
* @param {String} [tile.layout='dz'] filesystem layout, possible values are `dz`, `zoomify` or `google`. * @param {String} [tile.layout='dz'] filesystem layout, possible values are `dz`, `zoomify` or `google`.
* @returns {Sharp} * @returns {Sharp}
@ -361,6 +362,15 @@ function tile (tile) {
throw new Error('Invalid tile layout ' + tile.layout); throw new Error('Invalid tile layout ' + tile.layout);
} }
} }
// Angle of rotation,
if (is.defined(tile.angle)) {
if (is.integer(tile.angle) && !(tile.angle % 90)) {
this.options.tileAngle = tile.angle;
} else {
throw new Error('Unsupported angle: angle must be a positive/negative multiple of 90 ' + tile.angle);
}
}
} }
// Format // Format
if (is.inArray(this.options.formatOut, ['jpeg', 'png', 'webp'])) { if (is.inArray(this.options.formatOut, ['jpeg', 'png', 'webp'])) {
@ -368,6 +378,7 @@ function tile (tile) {
} else if (this.options.formatOut !== 'input') { } else if (this.options.formatOut !== 'input') {
throw new Error('Invalid tile format ' + this.options.formatOut); throw new Error('Invalid tile format ' + this.options.formatOut);
} }
return this._updateFormatOut('dz'); return this._updateFormatOut('dz');
} }

View File

@ -917,7 +917,8 @@ class PipelineWorker : public Nan::AsyncWorker {
->set("overlap", baton->tileOverlap) ->set("overlap", baton->tileOverlap)
->set("container", baton->tileContainer) ->set("container", baton->tileContainer)
->set("layout", baton->tileLayout) ->set("layout", baton->tileLayout)
->set("suffix", const_cast<char*>(suffix.data()))); ->set("suffix", const_cast<char*>(suffix.data()))
->set("angle", CalculateAngleRotation(baton->tileAngle)));
baton->formatOut = "dz"; baton->formatOut = "dz";
} else if (baton->formatOut == "v" || (mightMatchInput && isV) || } else if (baton->formatOut == "v" || (mightMatchInput && isV) ||
(willMatchInput && inputImageType == ImageType::VIPS)) { (willMatchInput && inputImageType == ImageType::VIPS)) {
@ -1263,6 +1264,7 @@ NAN_METHOD(pipeline) {
baton->tileSize = AttrTo<uint32_t>(options, "tileSize"); baton->tileSize = AttrTo<uint32_t>(options, "tileSize");
baton->tileOverlap = AttrTo<uint32_t>(options, "tileOverlap"); baton->tileOverlap = AttrTo<uint32_t>(options, "tileOverlap");
std::string tileContainer = AttrAsStr(options, "tileContainer"); std::string tileContainer = AttrAsStr(options, "tileContainer");
baton->tileAngle = AttrTo<int32_t>(options, "tileAngle");
if (tileContainer == "zip") { if (tileContainer == "zip") {
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP; baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP;
} else { } else {

View File

@ -132,6 +132,7 @@ struct PipelineBaton {
VipsForeignDzContainer tileContainer; VipsForeignDzContainer tileContainer;
VipsForeignDzLayout tileLayout; VipsForeignDzLayout tileLayout;
std::string tileFormat; std::string tileFormat;
int tileAngle;
PipelineBaton(): PipelineBaton():
input(nullptr), input(nullptr),
@ -206,7 +207,8 @@ struct PipelineBaton {
tileSize(256), tileSize(256),
tileOverlap(0), tileOverlap(0),
tileContainer(VIPS_FOREIGN_DZ_CONTAINER_FS), tileContainer(VIPS_FOREIGN_DZ_CONTAINER_FS),
tileLayout(VIPS_FOREIGN_DZ_LAYOUT_DZ) { tileLayout(VIPS_FOREIGN_DZ_LAYOUT_DZ),
tileAngle(0){
background[0] = 0.0; background[0] = 0.0;
background[1] = 0.0; background[1] = 0.0;
background[2] = 0.0; background[2] = 0.0;

View File

@ -146,13 +146,38 @@ describe('Tile', function () {
it('Prevent larger overlap than default size', function () { it('Prevent larger overlap than default size', function () {
assert.throws(function () { assert.throws(function () {
sharp().tile({overlap: 257}); sharp().tile({
overlap: 257
});
}); });
}); });
it('Prevent larger overlap than provided size', function () { it('Prevent larger overlap than provided size', function () {
assert.throws(function () { assert.throws(function () {
sharp().tile({size: 512, overlap: 513}); sharp().tile({
size: 512,
overlap: 513
});
});
});
it('Valid rotation angle values pass', function () {
[90, 270, -90].forEach(function (angle) {
assert.doesNotThrow(function () {
sharp().tile({
angle: angle
});
});
});
});
it('Invalid rotation angle values fail', function () {
['zoinks', 1.1, -1, 27].forEach(function (angle) {
assert.throws(function () {
sharp().tile({
angle: angle
});
});
}); });
}); });
@ -192,6 +217,40 @@ describe('Tile', function () {
}); });
}); });
it('Deep Zoom layout with custom size+angle', function (done) {
const directory = fixtures.path('output.512_90.dzi_files');
rimraf(directory, function () {
sharp(fixtures.inputJpg)
.tile({
size: 512,
angle: 90
})
.toFile(fixtures.path('output.512_90.dzi'), function (err, info) {
if (err) throw err;
assert.strictEqual('dz', info.format);
assert.strictEqual(2725, info.width);
assert.strictEqual(2225, info.height);
assert.strictEqual(3, info.channels);
assert.strictEqual('undefined', typeof info.size);
assertDeepZoomTiles(directory, 512, 13, done);
// Verifies tiles in 10th level are rotated
let tile = path.join(directory, '10', '0_1.jpeg');
// verify that the width and height correspond to the rotated image
// expected are w=512 and h=170 for the 0_1.jpeg.
// if a 0 angle is supplied to the .tile function
// the expected values are w=170 and h=512 for the 1_0.jpeg
sharp(tile).metadata(function (err, metadata) {
if (err) {
throw err;
} else {
assert.strictEqual(true, metadata.width === 512);
assert.strictEqual(true, metadata.height === 170);
}
});
});
});
});
it('Zoomify layout', function (done) { it('Zoomify layout', function (done) {
const directory = fixtures.path('output.zoomify.dzi'); const directory = fixtures.path('output.zoomify.dzi');
rimraf(directory, function () { rimraf(directory, function () {
@ -244,7 +303,9 @@ describe('Tile', function () {
const directory = fixtures.path('output.jpg.google.dzi'); const directory = fixtures.path('output.jpg.google.dzi');
rimraf(directory, function () { rimraf(directory, function () {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.jpeg({ quality: 1 }) .jpeg({
quality: 1
})
.tile({ .tile({
layout: 'google' layout: 'google'
}) })
@ -279,7 +340,9 @@ describe('Tile', function () {
const directory = fixtures.path('output.png.google.dzi'); const directory = fixtures.path('output.png.google.dzi');
rimraf(directory, function () { rimraf(directory, function () {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.png({ compressionLevel: 1 }) .png({
compressionLevel: 1
})
.tile({ .tile({
layout: 'google' layout: 'google'
}) })
@ -314,7 +377,9 @@ describe('Tile', function () {
const directory = fixtures.path('output.webp.google.dzi'); const directory = fixtures.path('output.webp.google.dzi');
rimraf(directory, function () { rimraf(directory, function () {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.webp({ quality: 1 }) .webp({
quality: 1
})
.tile({ .tile({
layout: 'google' layout: 'google'
}) })
@ -363,8 +428,12 @@ describe('Tile', function () {
assert.strictEqual(true, stat.isFile()); assert.strictEqual(true, stat.isFile());
assert.strictEqual(true, stat.size > 0); assert.strictEqual(true, stat.size > 0);
fs.createReadStream(container) fs.createReadStream(container)
.pipe(unzip.Extract({path: path.dirname(extractTo)})) .pipe(unzip.Extract({
.on('error', function (err) { throw err; }) path: path.dirname(extractTo)
}))
.on('error', function (err) {
throw err;
})
.on('close', function () { .on('close', function () {
assertDeepZoomTiles(directory, 256, 13, done); assertDeepZoomTiles(directory, 256, 13, done);
}); });
@ -395,8 +464,12 @@ describe('Tile', function () {
assert.strictEqual(true, stat.isFile()); assert.strictEqual(true, stat.isFile());
assert.strictEqual(true, stat.size > 0); assert.strictEqual(true, stat.size > 0);
fs.createReadStream(container) fs.createReadStream(container)
.pipe(unzip.Extract({path: path.dirname(extractTo)})) .pipe(unzip.Extract({
.on('error', function (err) { throw err; }) path: path.dirname(extractTo)
}))
.on('error', function (err) {
throw err;
})
.on('close', function () { .on('close', function () {
assertDeepZoomTiles(directory, 256, 13, done); assertDeepZoomTiles(directory, 256, 13, done);
}); });