Add skipBlanks support for tile layout (#1687)

This commit is contained in:
RaboliotTheGrey 2019-07-12 13:02:51 +02:00 committed by Lovell Fuller
parent b737d4601e
commit 6c02949fc1
6 changed files with 112 additions and 2 deletions

View File

@ -311,6 +311,7 @@ Warning: multiple sharp instances concurrently producing tile output can expose
- `tile.overlap` **[Number][8]** tile overlap in pixels, a value between 0 and 8192. (optional, default `0`) - `tile.overlap` **[Number][8]** tile overlap in pixels, a value between 0 and 8192. (optional, default `0`)
- `tile.angle` **[Number][8]** tile angle of rotation, must be a multiple of 90. (optional, default `0`) - `tile.angle` **[Number][8]** tile angle of rotation, must be a multiple of 90. (optional, default `0`)
- `tile.depth` **[String][1]?** how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout. - `tile.depth` **[String][1]?** how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout.
- `tile.skipBlanks` **[Number][8]** threshold to skip tile generation, a value 0 - 255 for 8-bit images or 0 - 65535 for 16-bit images (optional, default `-1`)
- `tile.container` **[String][1]** tile container, with value `fs` (filesystem) or `zip` (compressed file). (optional, default `'fs'`) - `tile.container` **[String][1]** tile container, with value `fs` (filesystem) or `zip` (compressed file). (optional, default `'fs'`)
- `tile.layout` **[String][1]** filesystem layout, possible values are `dz`, `zoomify` or `google`. (optional, default `'dz'`) - `tile.layout` **[String][1]** filesystem layout, possible values are `dz`, `zoomify` or `google`. (optional, default `'dz'`)

View File

@ -539,6 +539,7 @@ function toFormat (format, options) {
* @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 {Number} [tile.angle=0] tile angle of rotation, must be a multiple of 90.
* @param {String} [tile.depth] how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout. * @param {String} [tile.depth] how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout.
* @param {Number} [tile.skipBlanks=-1] threshold to skip tile generation, a value 0 - 255 for 8-bit images or 0 - 65535 for 16-bit images
* @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}
@ -599,6 +600,21 @@ function tile (tile) {
throw new Error("Invalid tile depth '" + tile.depth + "', should be one of 'onepixel', 'onetile' or 'one'"); throw new Error("Invalid tile depth '" + tile.depth + "', should be one of 'onepixel', 'onetile' or 'one'");
} }
} }
// Threshold of skipping blanks,
if (is.defined(tile.skipBlanks)) {
if (is.integer(tile.skipBlanks) && is.inRange(tile.skipBlanks, -1, 65535)) {
this.options.skipBlanks = tile.skipBlanks;
} else {
throw new Error('Invalid skipBlank threshold (-1 to 255/65535) ' + tile.skipBlanks);
}
} else {
if (is.defined(tile.layout) && tile.layout === 'google') {
this.options.skipBlanks = 5;
} else {
this.options.skipBlanks = -1;
}
}
} }
// Format // Format
if (is.inArray(this.options.formatOut, ['jpeg', 'png', 'webp'])) { if (is.inArray(this.options.formatOut, ['jpeg', 'png', 'webp'])) {

View File

@ -59,7 +59,8 @@
"Daiz <taneli.vatanen@gmail.com>", "Daiz <taneli.vatanen@gmail.com>",
"Julian Aubourg <j@ubourg.net>", "Julian Aubourg <j@ubourg.net>",
"Keith Belovay <keith@picthrive.com>", "Keith Belovay <keith@picthrive.com>",
"Michael B. Klein <mbklein@gmail.com>" "Michael B. Klein <mbklein@gmail.com>",
"Jordan Prudhomme <jordan@raboland.fr>"
], ],
"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)",

View File

@ -965,7 +965,8 @@ class PipelineWorker : public Nan::AsyncWorker {
->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)); ->set("angle", CalculateAngleRotation(baton->tileAngle))
->set("skip-blanks", baton->skipBlanks);
// libvips chooses a default depth based on layout. Instead of replicating that logic here by // libvips chooses a default depth based on layout. Instead of replicating that logic here by
// not passing anything - libvips will handle choice // not passing anything - libvips will handle choice
@ -1371,6 +1372,7 @@ NAN_METHOD(pipeline) {
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"); baton->tileAngle = AttrTo<int32_t>(options, "tileAngle");
baton->skipBlanks = AttrTo<int32_t>(options, "skipBlanks");
if (tileContainer == "zip") { if (tileContainer == "zip") {
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP; baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP;
} else { } else {

View File

@ -172,6 +172,7 @@ struct PipelineBaton {
VipsForeignDzLayout tileLayout; VipsForeignDzLayout tileLayout;
std::string tileFormat; std::string tileFormat;
int tileAngle; int tileAngle;
int skipBlanks;
VipsForeignDzDepth tileDepth; VipsForeignDzDepth tileDepth;
std::unique_ptr<double[]> recombMatrix; std::unique_ptr<double[]> recombMatrix;
@ -271,6 +272,7 @@ struct PipelineBaton {
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), tileAngle(0),
skipBlanks(-1),
tileDepth(VIPS_FOREIGN_DZ_DEPTH_LAST) {} tileDepth(VIPS_FOREIGN_DZ_DEPTH_LAST) {}
}; };

View File

@ -246,6 +246,26 @@ describe('Tile', function () {
}); });
}); });
it('Valid skipBlanks threshold values pass', function () {
[-1, 0, 255, 65535].forEach(function (skipBlanksThreshold) {
assert.doesNotThrow(function () {
sharp().tile({
skipBlanks: skipBlanksThreshold
});
});
});
});
it('InvalidskipBlanks threshold values fail', function () {
['zoinks', -2, 65536].forEach(function (skipBlanksThreshold) {
assert.throws(function () {
sharp().tile({
skipBlanks: skipBlanksThreshold
});
});
});
});
it('Deep Zoom layout', function (done) { it('Deep Zoom layout', function (done) {
const directory = fixtures.path('output.dzi_files'); const directory = fixtures.path('output.dzi_files');
rimraf(directory, function () { rimraf(directory, function () {
@ -364,6 +384,25 @@ describe('Tile', function () {
}); });
}); });
it('Deep Zoom layout with skipBlanks', function (done) {
const directory = fixtures.path('output.256_skip_blanks.dzi_files');
rimraf(directory, function () {
sharp(fixtures.inputJpgOverlayLayer2)
.tile({
size: 256,
skipBlanks: 0
})
.toFile(fixtures.path('output.256_skip_blanks.dzi'), function (err, info) {
if (err) throw err;
// assert them 0_0.jpeg doesn't exist because it's a white tile
const whiteTilePath = path.join(directory, '11', '0_0.jpeg');
assert.strictEqual(fs.existsSync(whiteTilePath), false, `Tile shouldn't exist`);
// Verify only one depth generated
assertDeepZoomTiles(directory, 256, 12, done);
});
});
});
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 () {
@ -451,6 +490,30 @@ describe('Tile', function () {
}); });
}); });
it('Zoomify layout with skip blanks', function (done) {
const directory = fixtures.path('output.zoomify.skipBlanks.dzi');
rimraf(directory, function () {
sharp(fixtures.inputJpgOverlayLayer2)
.tile({
size: 256,
layout: 'zoomify',
skipBlanks: 0
})
.toFile(directory, function (err, info) {
if (err) throw err;
// assert them 0_0.jpeg doesn't exist because it's a white tile
const whiteTilePath = path.join(directory, 'TileGroup0', '2-0-0.jpg');
assert.strictEqual(fs.existsSync(whiteTilePath), false, `Tile shouldn't exist`);
assert.strictEqual('dz', info.format);
assert.strictEqual(2048, info.width);
assert.strictEqual(1536, info.height);
assert.strictEqual(3, info.channels);
assert.strictEqual('number', typeof info.size);
assertZoomifyTiles(directory, 256, 4, done);
});
});
});
it('Google layout', function (done) { it('Google layout', function (done) {
const directory = fixtures.path('output.google.dzi'); const directory = fixtures.path('output.google.dzi');
rimraf(directory, function () { rimraf(directory, function () {
@ -652,6 +715,31 @@ describe('Tile', function () {
}); });
}); });
it('Google layout with default skip Blanks', function (done) {
const directory = fixtures.path('output.google_depth_skipBlanks.dzi');
rimraf(directory, function () {
sharp(fixtures.inputPng)
.tile({
layout: 'google',
size: 256
})
.toFile(directory, function (err, info) {
if (err) throw err;
const whiteTilePath = path.join(directory, '4', '8', '0.jpg');
assert.strictEqual(fs.existsSync(whiteTilePath), false, `Tile shouldn't exist`);
assert.strictEqual('dz', info.format);
assert.strictEqual(2809, info.width);
assert.strictEqual(2074, info.height);
assert.strictEqual(3, info.channels);
assert.strictEqual('number', typeof info.size);
assertGoogleTiles(directory, 256, 5, done);
});
});
});
it('Write to ZIP container using file extension', function (done) { it('Write to ZIP container using file extension', function (done) {
const container = fixtures.path('output.dz.container.zip'); const container = fixtures.path('output.dz.container.zip');
const extractTo = fixtures.path('output.dz.container'); const extractTo = fixtures.path('output.dz.container');