From 6c02949fc158ce7a86ca9f3ed2cb7885500387c3 Mon Sep 17 00:00:00 2001 From: RaboliotTheGrey Date: Fri, 12 Jul 2019 13:02:51 +0200 Subject: [PATCH] Add skipBlanks support for tile layout (#1687) --- docs/api-output.md | 1 + lib/output.js | 16 +++++++++ package.json | 3 +- src/pipeline.cc | 4 ++- src/pipeline.h | 2 ++ test/unit/tile.js | 88 ++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 112 insertions(+), 2 deletions(-) diff --git a/docs/api-output.md b/docs/api-output.md index e6a5fd58..d4004a48 100644 --- a/docs/api-output.md +++ b/docs/api-output.md @@ -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.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.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.layout` **[String][1]** filesystem layout, possible values are `dz`, `zoomify` or `google`. (optional, default `'dz'`) diff --git a/lib/output.js b/lib/output.js index f49d392f..a7000cd0 100644 --- a/lib/output.js +++ b/lib/output.js @@ -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.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 {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.layout='dz'] filesystem layout, possible values are `dz`, `zoomify` or `google`. * @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'"); } } + + // 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 if (is.inArray(this.options.formatOut, ['jpeg', 'png', 'webp'])) { diff --git a/package.json b/package.json index 7ae62af8..b6cfb399 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,8 @@ "Daiz ", "Julian Aubourg ", "Keith Belovay ", - "Michael B. Klein " + "Michael B. Klein ", + "Jordan Prudhomme " ], "scripts": { "install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node-gyp rebuild && node install/dll-copy)", diff --git a/src/pipeline.cc b/src/pipeline.cc index 0b8d3522..16d753e9 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -965,7 +965,8 @@ class PipelineWorker : public Nan::AsyncWorker { ->set("container", baton->tileContainer) ->set("layout", baton->tileLayout) ->set("suffix", const_cast(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 // not passing anything - libvips will handle choice @@ -1371,6 +1372,7 @@ NAN_METHOD(pipeline) { baton->tileOverlap = AttrTo(options, "tileOverlap"); std::string tileContainer = AttrAsStr(options, "tileContainer"); baton->tileAngle = AttrTo(options, "tileAngle"); + baton->skipBlanks = AttrTo(options, "skipBlanks"); if (tileContainer == "zip") { baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP; } else { diff --git a/src/pipeline.h b/src/pipeline.h index 364efc6c..0d0481d0 100644 --- a/src/pipeline.h +++ b/src/pipeline.h @@ -172,6 +172,7 @@ struct PipelineBaton { VipsForeignDzLayout tileLayout; std::string tileFormat; int tileAngle; + int skipBlanks; VipsForeignDzDepth tileDepth; std::unique_ptr recombMatrix; @@ -271,6 +272,7 @@ struct PipelineBaton { tileContainer(VIPS_FOREIGN_DZ_CONTAINER_FS), tileLayout(VIPS_FOREIGN_DZ_LAYOUT_DZ), tileAngle(0), + skipBlanks(-1), tileDepth(VIPS_FOREIGN_DZ_DEPTH_LAST) {} }; diff --git a/test/unit/tile.js b/test/unit/tile.js index ce8685c6..661c8d43 100644 --- a/test/unit/tile.js +++ b/test/unit/tile.js @@ -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) { const directory = fixtures.path('output.dzi_files'); 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) { const directory = fixtures.path('output.zoomify.dzi'); 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) { const directory = fixtures.path('output.google.dzi'); 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) { const container = fixtures.path('output.dz.container.zip'); const extractTo = fixtures.path('output.dz.container');