Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add skipBlanks support for dz and zoomify layout #1687

Merged
merged 1 commit into from Jul 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/api-output.md
Expand Up @@ -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'`)

Expand Down
16 changes: 16 additions & 0 deletions lib/output.js
Expand Up @@ -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}
Expand Down Expand Up @@ -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'])) {
Expand Down
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -59,7 +59,8 @@
"Daiz <taneli.vatanen@gmail.com>",
"Julian Aubourg <j@ubourg.net>",
"Keith Belovay <keith@picthrive.com>",
"Michael B. Klein <mbklein@gmail.com>"
"Michael B. Klein <mbklein@gmail.com>",
"Jordan Prudhomme <jordan@raboland.fr>"
],
"scripts": {
"install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node-gyp rebuild && node install/dll-copy)",
Expand Down
4 changes: 3 additions & 1 deletion src/pipeline.cc
Expand Up @@ -965,7 +965,8 @@ class PipelineWorker : public Nan::AsyncWorker {
->set("container", baton->tileContainer)
->set("layout", baton->tileLayout)
->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
// not passing anything - libvips will handle choice
Expand Down Expand Up @@ -1371,6 +1372,7 @@ NAN_METHOD(pipeline) {
baton->tileOverlap = AttrTo<uint32_t>(options, "tileOverlap");
std::string tileContainer = AttrAsStr(options, "tileContainer");
baton->tileAngle = AttrTo<int32_t>(options, "tileAngle");
baton->skipBlanks = AttrTo<int32_t>(options, "skipBlanks");
if (tileContainer == "zip") {
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP;
} else {
Expand Down
2 changes: 2 additions & 0 deletions src/pipeline.h
Expand Up @@ -172,6 +172,7 @@ struct PipelineBaton {
VipsForeignDzLayout tileLayout;
std::string tileFormat;
int tileAngle;
int skipBlanks;
VipsForeignDzDepth tileDepth;
std::unique_ptr<double[]> recombMatrix;

Expand Down Expand Up @@ -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) {}
};

Expand Down
88 changes: 88 additions & 0 deletions test/unit/tile.js
Expand Up @@ -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 () {
Expand Down Expand Up @@ -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 () {
Expand Down Expand Up @@ -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 () {
Expand Down Expand Up @@ -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');
Expand Down