Add support for writing dz format to zip container (#402)

To enable this you can either use the `.zip` or `.szi` file extensions
or use `.tile({container: 'zip'})` with the `.dzi` extension.
This commit is contained in:
Felix Bünemann 2016-04-08 20:58:13 +02:00 committed by Lovell Fuller
parent ef61da3051
commit b224874332
8 changed files with 107 additions and 4 deletions

View File

@ -527,14 +527,17 @@ The default behaviour, when `withMetadata` is not used, is to strip all metadata
#### tile(options) #### tile(options)
The size, overlap and directory layout to use when generating square Deep Zoom image pyramid tiles. The size, overlap, container and directory layout to use when generating square Deep Zoom image pyramid tiles.
`options` is an Object with one or more of the following attributes: `options` is an Object with one or more of the following attributes:
* `size` is an integral Number between 1 and 8192. The default value is 256 pixels. * `size` is an integral Number between 1 and 8192. The default value is 256 pixels.
* `overlap` is an integral Number between 0 and 8192. The default value is 0 pixels. * `overlap` is an integral Number between 0 and 8192. The default value is 0 pixels.
* `container` is a String, with value `fs` or `zip`. The default value is `fs`.
* `layout` is a String, with value `dz`, `zoomify` or `google`. The default value is `dz`. * `layout` is a String, with value `dz`, `zoomify` or `google`. The default value is `dz`.
You can also use the file extension .zip or .szi to write to a ZIP container instead of the filesystem.
```javascript ```javascript
sharp('input.tiff') sharp('input.tiff')
.tile({ .tile({

View File

@ -660,6 +660,14 @@ Sharp.prototype.tile = function(tile) {
throw new Error('Invalid tile overlap (0 to 8192) ' + tile.overlap); throw new Error('Invalid tile overlap (0 to 8192) ' + tile.overlap);
} }
} }
// Container
if (isDefined(tile.container)) {
if (isString(tile.container) && contains(tile.container, ['fs', 'zip'])) {
this.options.tileContainer = tile.container;
} else {
throw new Error('Invalid tile container ' + tile.container);
}
}
// Layout // Layout
if (isDefined(tile.layout)) { if (isDefined(tile.layout)) {
if (isString(tile.layout) && contains(tile.layout, ['dz', 'google', 'zoomify'])) { if (isString(tile.layout) && contains(tile.layout, ['dz', 'google', 'zoomify'])) {

View File

@ -58,6 +58,7 @@
}, },
"devDependencies": { "devDependencies": {
"async": "^1.5.2", "async": "^1.5.2",
"bufferutil": "^1.2.1",
"coveralls": "^2.11.9", "coveralls": "^2.11.9",
"exif-reader": "^1.0.0", "exif-reader": "^1.0.0",
"icc": "^0.0.2", "icc": "^0.0.2",
@ -66,7 +67,7 @@
"mocha-jshint": "^2.3.1", "mocha-jshint": "^2.3.1",
"node-cpplint": "^0.4.0", "node-cpplint": "^0.4.0",
"rimraf": "^2.5.2", "rimraf": "^2.5.2",
"bufferutil": "^1.2.1" "unzip": "^0.1.11"
}, },
"license": "Apache-2.0", "license": "Apache-2.0",
"config": { "config": {

View File

@ -52,6 +52,9 @@ namespace sharp {
bool IsDz(std::string const &str) { bool IsDz(std::string const &str) {
return EndsWith(str, ".dzi") || EndsWith(str, ".DZI"); return EndsWith(str, ".dzi") || EndsWith(str, ".DZI");
} }
bool IsDzZip(std::string const &str) {
return EndsWith(str, ".zip") || EndsWith(str, ".ZIP") || EndsWith(str, ".szi") || EndsWith(str, ".SZI");
}
/* /*
Provide a string identifier for the given image type. Provide a string identifier for the given image type.

View File

@ -35,6 +35,7 @@ namespace sharp {
bool IsWebp(std::string const &str); bool IsWebp(std::string const &str);
bool IsTiff(std::string const &str); bool IsTiff(std::string const &str);
bool IsDz(std::string const &str); bool IsDz(std::string const &str);
bool IsDzZip(std::string const &str);
/* /*
Provide a string identifier for the given image type. Provide a string identifier for the given image type.

View File

@ -65,6 +65,7 @@ using sharp::IsPng;
using sharp::IsWebp; using sharp::IsWebp;
using sharp::IsTiff; using sharp::IsTiff;
using sharp::IsDz; using sharp::IsDz;
using sharp::IsDzZip;
using sharp::FreeCallback; using sharp::FreeCallback;
using sharp::CalculateCrop; using sharp::CalculateCrop;
using sharp::counterProcess; using sharp::counterProcess;
@ -743,7 +744,8 @@ class PipelineWorker : public AsyncWorker {
bool isWebp = IsWebp(baton->fileOut); bool isWebp = IsWebp(baton->fileOut);
bool isTiff = IsTiff(baton->fileOut); bool isTiff = IsTiff(baton->fileOut);
bool isDz = IsDz(baton->fileOut); bool isDz = IsDz(baton->fileOut);
bool matchInput = baton->formatOut == "input" && !(isJpeg || isPng || isWebp || isTiff || isDz); bool isDzZip = IsDzZip(baton->fileOut);
bool matchInput = baton->formatOut == "input" && !(isJpeg || isPng || isWebp || isTiff || isDz || isDzZip);
if (baton->formatOut == "jpeg" || isJpeg || (matchInput && inputImageType == ImageType::JPEG)) { if (baton->formatOut == "jpeg" || isJpeg || (matchInput && inputImageType == ImageType::JPEG)) {
// Write JPEG to file // Write JPEG to file
image.jpegsave(const_cast<char*>(baton->fileOut.data()), VImage::option() image.jpegsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
@ -784,12 +786,16 @@ class PipelineWorker : public AsyncWorker {
); );
baton->formatOut = "tiff"; baton->formatOut = "tiff";
baton->channels = std::min(baton->channels, 3); baton->channels = std::min(baton->channels, 3);
} else if (baton->formatOut == "dz" || IsDz(baton->fileOut)) { } else if (baton->formatOut == "dz" || isDz || isDzZip) {
if (isDzZip) {
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP;
}
// Write DZ to file // Write DZ to file
image.dzsave(const_cast<char*>(baton->fileOut.data()), VImage::option() image.dzsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("tile_size", baton->tileSize) ->set("tile_size", baton->tileSize)
->set("overlap", baton->tileOverlap) ->set("overlap", baton->tileOverlap)
->set("container", baton->tileContainer)
->set("layout", baton->tileLayout) ->set("layout", baton->tileLayout)
); );
baton->formatOut = "dz"; baton->formatOut = "dz";
@ -1058,6 +1064,12 @@ NAN_METHOD(pipeline) {
// Tile output // Tile output
baton->tileSize = attrAs<int32_t>(options, "tileSize"); baton->tileSize = attrAs<int32_t>(options, "tileSize");
baton->tileOverlap = attrAs<int32_t>(options, "tileOverlap"); baton->tileOverlap = attrAs<int32_t>(options, "tileOverlap");
std::string tileContainer = attrAsStr(options, "tileContainer");
if (tileContainer == "zip") {
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP;
} else {
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_FS;
}
std::string tileLayout = attrAsStr(options, "tileLayout"); std::string tileLayout = attrAsStr(options, "tileLayout");
if (tileLayout == "google") { if (tileLayout == "google") {
baton->tileLayout = VIPS_FOREIGN_DZ_LAYOUT_GOOGLE; baton->tileLayout = VIPS_FOREIGN_DZ_LAYOUT_GOOGLE;

View File

@ -81,6 +81,7 @@ struct PipelineBaton {
int withMetadataOrientation; int withMetadataOrientation;
int tileSize; int tileSize;
int tileOverlap; int tileOverlap;
VipsForeignDzContainer tileContainer;
VipsForeignDzLayout tileLayout; VipsForeignDzLayout tileLayout;
PipelineBaton(): PipelineBaton():
@ -130,6 +131,7 @@ struct PipelineBaton {
withMetadataOrientation(-1), withMetadataOrientation(-1),
tileSize(256), tileSize(256),
tileOverlap(0), tileOverlap(0),
tileContainer(VIPS_FOREIGN_DZ_CONTAINER_FS),
tileLayout(VIPS_FOREIGN_DZ_LAYOUT_DZ) { tileLayout(VIPS_FOREIGN_DZ_LAYOUT_DZ) {
background[0] = 0.0; background[0] = 0.0;
background[1] = 0.0; background[1] = 0.0;

View File

@ -6,6 +6,7 @@ var assert = require('assert');
var async = require('async'); var async = require('async');
var rimraf = require('rimraf'); var rimraf = require('rimraf');
var unzip = require('unzip');
var sharp = require('../../index'); var sharp = require('../../index');
var fixtures = require('../fixtures'); var fixtures = require('../fixtures');
@ -88,6 +89,26 @@ describe('Tile', function() {
}); });
}); });
it('Valid container values pass', function() {
['fs', 'zip'].forEach(function(container) {
assert.doesNotThrow(function() {
sharp().tile({
container: container
});
});
});
});
it('Invalid container values fail', function() {
['zoinks', 1].forEach(function(container) {
assert.throws(function() {
sharp().tile({
container: container
});
});
});
});
it('Valid layout values pass', function() { it('Valid layout values pass', function() {
['dz', 'google', 'zoomify'].forEach(function(layout) { ['dz', 'google', 'zoomify'].forEach(function(layout) {
assert.doesNotThrow(function() { assert.doesNotThrow(function() {
@ -190,6 +211,58 @@ describe('Tile', function() {
}); });
}); });
it('Write to ZIP container using file extension', function(done) {
var container = fixtures.path('output.dz.container.zip');
var extractTo = fixtures.path('output.dz.container');
var directory = path.join(extractTo, 'output.dz.container_files');
rimraf(directory, function() {
sharp(fixtures.inputJpg)
.toFile(container, function(err, info) {
if (err) throw err;
assert.strictEqual('dz', info.format);
fs.stat(container, function(err, stat) {
if (err) throw err;
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; })
.on('close', function() {
assertDeepZoomTiles(directory, 256, 13, done);
});
});
});
});
});
it('Write to ZIP container using container tile option', function(done) {
var container = fixtures.path('output.dz.containeropt.zip');
var extractTo = fixtures.path('output.dz.containeropt');
var directory = path.join(extractTo, 'output.dz.containeropt_files');
rimraf(directory, function() {
sharp(fixtures.inputJpg)
.tile({
container: 'zip'
})
.toFile(fixtures.path('output.dz.containeropt.dzi'), function(err, info) {
// Vips overrides .dzi extension to .zip used by container var below
if (err) throw err;
assert.strictEqual('dz', info.format);
fs.stat(container, function(err, stat) {
if (err) throw err;
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; })
.on('close', function() {
assertDeepZoomTiles(directory, 256, 13, done);
});
});
});
});
});
} }
}); });