Skip to content

Commit

Permalink
Expose angle option in tile feature (#1121)
Browse files Browse the repository at this point in the history
  • Loading branch information
BiancoA authored and lovell committed Feb 18, 2018
1 parent 1a4e680 commit f86ae79
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 11 deletions.
11 changes: 11 additions & 0 deletions lib/output.js
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ function toFormat (format, options) {
* @param {Object} [tile]
* @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.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.layout='dz'] filesystem layout, possible values are `dz`, `zoomify` or `google`.
* @returns {Sharp}
Expand Down Expand Up @@ -361,13 +362,23 @@ function tile (tile) {
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
if (is.inArray(this.options.formatOut, ['jpeg', 'png', 'webp'])) {
this.options.tileFormat = this.options.formatOut;
} else if (this.options.formatOut !== 'input') {
throw new Error('Invalid tile format ' + this.options.formatOut);
}

return this._updateFormatOut('dz');
}

Expand Down
4 changes: 3 additions & 1 deletion src/pipeline.cc
Original file line number Diff line number Diff line change
Expand Up @@ -917,7 +917,8 @@ class PipelineWorker : public Nan::AsyncWorker {
->set("overlap", baton->tileOverlap)
->set("container", baton->tileContainer)
->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";
} else if (baton->formatOut == "v" || (mightMatchInput && isV) ||
(willMatchInput && inputImageType == ImageType::VIPS)) {
Expand Down Expand Up @@ -1263,6 +1264,7 @@ NAN_METHOD(pipeline) {
baton->tileSize = AttrTo<uint32_t>(options, "tileSize");
baton->tileOverlap = AttrTo<uint32_t>(options, "tileOverlap");
std::string tileContainer = AttrAsStr(options, "tileContainer");
baton->tileAngle = AttrTo<int32_t>(options, "tileAngle");
if (tileContainer == "zip") {
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP;
} else {
Expand Down
4 changes: 3 additions & 1 deletion src/pipeline.h
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ struct PipelineBaton {
VipsForeignDzContainer tileContainer;
VipsForeignDzLayout tileLayout;
std::string tileFormat;
int tileAngle;

PipelineBaton():
input(nullptr),
Expand Down Expand Up @@ -206,7 +207,8 @@ struct PipelineBaton {
tileSize(256),
tileOverlap(0),
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[1] = 0.0;
background[2] = 0.0;
Expand Down
91 changes: 82 additions & 9 deletions test/unit/tile.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,13 +146,38 @@ describe('Tile', function () {

it('Prevent larger overlap than default size', function () {
assert.throws(function () {
sharp().tile({overlap: 257});
sharp().tile({
overlap: 257
});
});
});

it('Prevent larger overlap than provided size', 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
});
});
});
});

Expand Down Expand Up @@ -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) {
const directory = fixtures.path('output.zoomify.dzi');
rimraf(directory, function () {
Expand Down Expand Up @@ -244,7 +303,9 @@ describe('Tile', function () {
const directory = fixtures.path('output.jpg.google.dzi');
rimraf(directory, function () {
sharp(fixtures.inputJpg)
.jpeg({ quality: 1 })
.jpeg({
quality: 1
})
.tile({
layout: 'google'
})
Expand Down Expand Up @@ -279,7 +340,9 @@ describe('Tile', function () {
const directory = fixtures.path('output.png.google.dzi');
rimraf(directory, function () {
sharp(fixtures.inputJpg)
.png({ compressionLevel: 1 })
.png({
compressionLevel: 1
})
.tile({
layout: 'google'
})
Expand Down Expand Up @@ -314,7 +377,9 @@ describe('Tile', function () {
const directory = fixtures.path('output.webp.google.dzi');
rimraf(directory, function () {
sharp(fixtures.inputJpg)
.webp({ quality: 1 })
.webp({
quality: 1
})
.tile({
layout: 'google'
})
Expand Down Expand Up @@ -363,8 +428,12 @@ describe('Tile', function () {
assert.strictEqual(true, stat.isFile());
assert.strictEqual(true, stat.size > 0);
fs.createReadStream(container)
.pipe(unzip.Extract({path: path.dirname(extractTo)}))
.on('error', function (err) { throw err; })
.pipe(unzip.Extract({
path: path.dirname(extractTo)
}))
.on('error', function (err) {
throw err;
})
.on('close', function () {
assertDeepZoomTiles(directory, 256, 13, done);
});
Expand Down Expand Up @@ -395,8 +464,12 @@ describe('Tile', function () {
assert.strictEqual(true, stat.isFile());
assert.strictEqual(true, stat.size > 0);
fs.createReadStream(container)
.pipe(unzip.Extract({path: path.dirname(extractTo)}))
.on('error', function (err) { throw err; })
.pipe(unzip.Extract({
path: path.dirname(extractTo)
}))
.on('error', function (err) {
throw err;
})
.on('close', function () {
assertDeepZoomTiles(directory, 256, 13, done);
});
Expand Down

0 comments on commit f86ae79

Please sign in to comment.