mirror of
https://github.com/lovell/sharp.git
synced 2025-07-09 18:40:16 +02:00
Add Buffer and Stream support to tile output #2238
This commit is contained in:
parent
3e327a586c
commit
b46ab510da
@ -544,9 +544,12 @@ const data = await sharp('input.png')
|
|||||||
## tile
|
## tile
|
||||||
|
|
||||||
Use tile-based deep zoom (image pyramid) output.
|
Use tile-based deep zoom (image pyramid) output.
|
||||||
|
|
||||||
Set the format and options for tile images via the `toFormat`, `jpeg`, `png` or `webp` functions.
|
Set the format and options for tile images via the `toFormat`, `jpeg`, `png` or `webp` functions.
|
||||||
Use a `.zip` or `.szi` file extension with `toFile` to write to a compressed archive file format.
|
Use a `.zip` or `.szi` file extension with `toFile` to write to a compressed archive file format.
|
||||||
|
|
||||||
|
The container will be set to `zip` when the output is a Buffer or Stream, otherwise it will default to `fs`.
|
||||||
|
|
||||||
### Parameters
|
### Parameters
|
||||||
|
|
||||||
* `options` **[Object][6]?**
|
* `options` **[Object][6]?**
|
||||||
@ -562,6 +565,7 @@ Use a `.zip` or `.szi` file extension with `toFile` to write to a compressed arc
|
|||||||
* `options.centre` **[boolean][10]** centre image in tile. (optional, default `false`)
|
* `options.centre` **[boolean][10]** centre image in tile. (optional, default `false`)
|
||||||
* `options.center` **[boolean][10]** alternative spelling of centre. (optional, default `false`)
|
* `options.center` **[boolean][10]** alternative spelling of centre. (optional, default `false`)
|
||||||
* `options.id` **[string][2]** when `layout` is `iiif`/`iiif3`, sets the `@id`/`id` attribute of `info.json` (optional, default `'https://example.com/iiif'`)
|
* `options.id` **[string][2]** when `layout` is `iiif`/`iiif3`, sets the `@id`/`id` attribute of `info.json` (optional, default `'https://example.com/iiif'`)
|
||||||
|
* `options.basename` **[string][2]?** the name of the directory within the zip file when container is `zip`.
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
@ -577,6 +581,19 @@ sharp('input.tiff')
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const zipFileWithTiles = await sharp(input)
|
||||||
|
.tile({ basename: "tiles" })
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const iiififier = sharp().tile({ layout: "iiif" });
|
||||||
|
readableStream
|
||||||
|
.pipe(iiififier)
|
||||||
|
.pipe(writeableStream);
|
||||||
|
```
|
||||||
|
|
||||||
* Throws **[Error][4]** Invalid parameters
|
* Throws **[Error][4]** Invalid parameters
|
||||||
|
|
||||||
Returns **Sharp**
|
Returns **Sharp**
|
||||||
|
@ -15,6 +15,9 @@ Requires libvips v8.13.0
|
|||||||
* Use combined bounding box of alpha and non-alpha channels for `trim` operation.
|
* Use combined bounding box of alpha and non-alpha channels for `trim` operation.
|
||||||
[#2166](https://github.com/lovell/sharp/issues/2166)
|
[#2166](https://github.com/lovell/sharp/issues/2166)
|
||||||
|
|
||||||
|
* Add Buffer and Stream support to tile-based output.
|
||||||
|
[#2238](https://github.com/lovell/sharp/issues/2238)
|
||||||
|
|
||||||
* Add input `fileSuffix` and output `alias` to `format` information.
|
* Add input `fileSuffix` and output `alias` to `format` information.
|
||||||
[#2642](https://github.com/lovell/sharp/issues/2642)
|
[#2642](https://github.com/lovell/sharp/issues/2642)
|
||||||
|
|
||||||
|
File diff suppressed because one or more lines are too long
@ -285,6 +285,7 @@ const Sharp = function (input, options) {
|
|||||||
tileBackground: [255, 255, 255, 255],
|
tileBackground: [255, 255, 255, 255],
|
||||||
tileCentre: false,
|
tileCentre: false,
|
||||||
tileId: 'https://example.com/iiif',
|
tileId: 'https://example.com/iiif',
|
||||||
|
tileBasename: '',
|
||||||
timeoutSeconds: 0,
|
timeoutSeconds: 0,
|
||||||
linearA: 1,
|
linearA: 1,
|
||||||
linearB: 0,
|
linearB: 0,
|
||||||
|
@ -943,9 +943,12 @@ function raw (options) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Use tile-based deep zoom (image pyramid) output.
|
* Use tile-based deep zoom (image pyramid) output.
|
||||||
|
*
|
||||||
* Set the format and options for tile images via the `toFormat`, `jpeg`, `png` or `webp` functions.
|
* Set the format and options for tile images via the `toFormat`, `jpeg`, `png` or `webp` functions.
|
||||||
* Use a `.zip` or `.szi` file extension with `toFile` to write to a compressed archive file format.
|
* Use a `.zip` or `.szi` file extension with `toFile` to write to a compressed archive file format.
|
||||||
*
|
*
|
||||||
|
* The container will be set to `zip` when the output is a Buffer or Stream, otherwise it will default to `fs`.
|
||||||
|
*
|
||||||
* @example
|
* @example
|
||||||
* sharp('input.tiff')
|
* sharp('input.tiff')
|
||||||
* .png()
|
* .png()
|
||||||
@ -957,6 +960,17 @@ function raw (options) {
|
|||||||
* // output_files contains 512x512 tiles grouped by zoom level
|
* // output_files contains 512x512 tiles grouped by zoom level
|
||||||
* });
|
* });
|
||||||
*
|
*
|
||||||
|
* @example
|
||||||
|
* const zipFileWithTiles = await sharp(input)
|
||||||
|
* .tile({ basename: "tiles" })
|
||||||
|
* .toBuffer();
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* const iiififier = sharp().tile({ layout: "iiif" });
|
||||||
|
* readableStream
|
||||||
|
* .pipe(iiififier)
|
||||||
|
* .pipe(writeableStream);
|
||||||
|
*
|
||||||
* @param {Object} [options]
|
* @param {Object} [options]
|
||||||
* @param {number} [options.size=256] tile size in pixels, a value between 1 and 8192.
|
* @param {number} [options.size=256] tile size in pixels, a value between 1 and 8192.
|
||||||
* @param {number} [options.overlap=0] tile overlap in pixels, a value between 0 and 8192.
|
* @param {number} [options.overlap=0] tile overlap in pixels, a value between 0 and 8192.
|
||||||
@ -969,6 +983,7 @@ function raw (options) {
|
|||||||
* @param {boolean} [options.centre=false] centre image in tile.
|
* @param {boolean} [options.centre=false] centre image in tile.
|
||||||
* @param {boolean} [options.center=false] alternative spelling of centre.
|
* @param {boolean} [options.center=false] alternative spelling of centre.
|
||||||
* @param {string} [options.id='https://example.com/iiif'] when `layout` is `iiif`/`iiif3`, sets the `@id`/`id` attribute of `info.json`
|
* @param {string} [options.id='https://example.com/iiif'] when `layout` is `iiif`/`iiif3`, sets the `@id`/`id` attribute of `info.json`
|
||||||
|
* @param {string} [options.basename] the name of the directory within the zip file when container is `zip`.
|
||||||
* @returns {Sharp}
|
* @returns {Sharp}
|
||||||
* @throws {Error} Invalid parameters
|
* @throws {Error} Invalid parameters
|
||||||
*/
|
*/
|
||||||
@ -1050,6 +1065,14 @@ function tile (options) {
|
|||||||
throw is.invalidParameterError('id', 'string', options.id);
|
throw is.invalidParameterError('id', 'string', options.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Basename for zip container
|
||||||
|
if (is.defined(options.basename)) {
|
||||||
|
if (is.string(options.basename)) {
|
||||||
|
this.options.tileBasename = options.basename;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('basename', 'string', options.basename);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Format
|
// Format
|
||||||
if (is.inArray(this.options.formatOut, ['jpeg', 'png', 'webp'])) {
|
if (is.inArray(this.options.formatOut, ['jpeg', 'png', 'webp'])) {
|
||||||
|
133
src/pipeline.cc
133
src/pipeline.cc
@ -941,6 +941,19 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
area->free_fn = nullptr;
|
area->free_fn = nullptr;
|
||||||
vips_area_unref(area);
|
vips_area_unref(area);
|
||||||
baton->formatOut = "heif";
|
baton->formatOut = "heif";
|
||||||
|
} else if (baton->formatOut == "dz") {
|
||||||
|
// Write DZ to buffer
|
||||||
|
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP;
|
||||||
|
if (!sharp::HasAlpha(image)) {
|
||||||
|
baton->tileBackground.pop_back();
|
||||||
|
}
|
||||||
|
vips::VOption *options = BuildOptionsDZ(baton);
|
||||||
|
VipsArea *area = reinterpret_cast<VipsArea*>(image.dzsave_buffer(options));
|
||||||
|
baton->bufferOut = static_cast<char*>(area->data);
|
||||||
|
baton->bufferOutLength = area->length;
|
||||||
|
area->free_fn = nullptr;
|
||||||
|
vips_area_unref(area);
|
||||||
|
baton->formatOut = "dz";
|
||||||
} else if (baton->formatOut == "raw" ||
|
} else if (baton->formatOut == "raw" ||
|
||||||
(baton->formatOut == "input" && inputImageType == sharp::ImageType::RAW)) {
|
(baton->formatOut == "input" && inputImageType == sharp::ImageType::RAW)) {
|
||||||
// Write raw, uncompressed image data to buffer
|
// Write raw, uncompressed image data to buffer
|
||||||
@ -1096,66 +1109,14 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
->set("lossless", baton->heifLossless));
|
->set("lossless", baton->heifLossless));
|
||||||
baton->formatOut = "heif";
|
baton->formatOut = "heif";
|
||||||
} else if (baton->formatOut == "dz" || isDz || isDzZip) {
|
} else if (baton->formatOut == "dz" || isDz || isDzZip) {
|
||||||
|
// Write DZ to file
|
||||||
if (isDzZip) {
|
if (isDzZip) {
|
||||||
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP;
|
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP;
|
||||||
}
|
}
|
||||||
// Forward format options through suffix
|
|
||||||
std::string suffix;
|
|
||||||
if (baton->tileFormat == "png") {
|
|
||||||
std::vector<std::pair<std::string, std::string>> options {
|
|
||||||
{"interlace", baton->pngProgressive ? "TRUE" : "FALSE"},
|
|
||||||
{"compression", std::to_string(baton->pngCompressionLevel)},
|
|
||||||
{"filter", baton->pngAdaptiveFiltering ? "all" : "none"}
|
|
||||||
};
|
|
||||||
suffix = AssembleSuffixString(".png", options);
|
|
||||||
} else if (baton->tileFormat == "webp") {
|
|
||||||
std::vector<std::pair<std::string, std::string>> options {
|
|
||||||
{"Q", std::to_string(baton->webpQuality)},
|
|
||||||
{"alpha_q", std::to_string(baton->webpAlphaQuality)},
|
|
||||||
{"lossless", baton->webpLossless ? "TRUE" : "FALSE"},
|
|
||||||
{"near_lossless", baton->webpNearLossless ? "TRUE" : "FALSE"},
|
|
||||||
{"smart_subsample", baton->webpSmartSubsample ? "TRUE" : "FALSE"},
|
|
||||||
{"min_size", baton->webpMinSize ? "TRUE" : "FALSE"},
|
|
||||||
{"mixed", baton->webpMixed ? "TRUE" : "FALSE"},
|
|
||||||
{"effort", std::to_string(baton->webpEffort)}
|
|
||||||
};
|
|
||||||
suffix = AssembleSuffixString(".webp", options);
|
|
||||||
} else {
|
|
||||||
std::vector<std::pair<std::string, std::string>> options {
|
|
||||||
{"Q", std::to_string(baton->jpegQuality)},
|
|
||||||
{"interlace", baton->jpegProgressive ? "TRUE" : "FALSE"},
|
|
||||||
{"subsample_mode", baton->jpegChromaSubsampling == "4:4:4" ? "off" : "on"},
|
|
||||||
{"trellis_quant", baton->jpegTrellisQuantisation ? "TRUE" : "FALSE"},
|
|
||||||
{"quant_table", std::to_string(baton->jpegQuantisationTable)},
|
|
||||||
{"overshoot_deringing", baton->jpegOvershootDeringing ? "TRUE": "FALSE"},
|
|
||||||
{"optimize_scans", baton->jpegOptimiseScans ? "TRUE": "FALSE"},
|
|
||||||
{"optimize_coding", baton->jpegOptimiseCoding ? "TRUE": "FALSE"}
|
|
||||||
};
|
|
||||||
std::string extname = baton->tileLayout == VIPS_FOREIGN_DZ_LAYOUT_DZ ? ".jpeg" : ".jpg";
|
|
||||||
suffix = AssembleSuffixString(extname, options);
|
|
||||||
}
|
|
||||||
// Remove alpha channel from tile background if image does not contain an alpha channel
|
|
||||||
if (!sharp::HasAlpha(image)) {
|
if (!sharp::HasAlpha(image)) {
|
||||||
baton->tileBackground.pop_back();
|
baton->tileBackground.pop_back();
|
||||||
}
|
}
|
||||||
// Write DZ to file
|
vips::VOption *options = BuildOptionsDZ(baton);
|
||||||
vips::VOption *options = VImage::option()
|
|
||||||
->set("strip", !baton->withMetadata)
|
|
||||||
->set("tile_size", baton->tileSize)
|
|
||||||
->set("overlap", baton->tileOverlap)
|
|
||||||
->set("container", baton->tileContainer)
|
|
||||||
->set("layout", baton->tileLayout)
|
|
||||||
->set("suffix", const_cast<char*>(suffix.data()))
|
|
||||||
->set("angle", CalculateAngleRotation(baton->tileAngle))
|
|
||||||
->set("background", baton->tileBackground)
|
|
||||||
->set("centre", baton->tileCentre)
|
|
||||||
->set("id", const_cast<char*>(baton->tileId.data()))
|
|
||||||
->set("skip_blanks", baton->tileSkipBlanks);
|
|
||||||
// libvips chooses a default depth based on layout. Instead of replicating that logic here by
|
|
||||||
// not passing anything - libvips will handle choice
|
|
||||||
if (baton->tileDepth < VIPS_FOREIGN_DZ_DEPTH_LAST) {
|
|
||||||
options->set("depth", baton->tileDepth);
|
|
||||||
}
|
|
||||||
image.dzsave(const_cast<char*>(baton->fileOut.data()), options);
|
image.dzsave(const_cast<char*>(baton->fileOut.data()), options);
|
||||||
baton->formatOut = "dz";
|
baton->formatOut = "dz";
|
||||||
} else if (baton->formatOut == "v" || (mightMatchInput && isV) ||
|
} else if (baton->formatOut == "v" || (mightMatchInput && isV) ||
|
||||||
@ -1312,7 +1273,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
Assemble the suffix argument to dzsave, which is the format (by extname)
|
Assemble the suffix argument to dzsave, which is the format (by extname)
|
||||||
alongisde comma-separated arguments to the corresponding `formatsave` vips
|
alongside comma-separated arguments to the corresponding `formatsave` vips
|
||||||
action.
|
action.
|
||||||
*/
|
*/
|
||||||
std::string
|
std::string
|
||||||
@ -1327,6 +1288,67 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
return extname + "[" + argument + "]";
|
return extname + "[" + argument + "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Build VOption for dzsave
|
||||||
|
*/
|
||||||
|
vips::VOption*
|
||||||
|
BuildOptionsDZ(PipelineBaton *baton) {
|
||||||
|
// Forward format options through suffix
|
||||||
|
std::string suffix;
|
||||||
|
if (baton->tileFormat == "png") {
|
||||||
|
std::vector<std::pair<std::string, std::string>> options {
|
||||||
|
{"interlace", baton->pngProgressive ? "TRUE" : "FALSE"},
|
||||||
|
{"compression", std::to_string(baton->pngCompressionLevel)},
|
||||||
|
{"filter", baton->pngAdaptiveFiltering ? "all" : "none"}
|
||||||
|
};
|
||||||
|
suffix = AssembleSuffixString(".png", options);
|
||||||
|
} else if (baton->tileFormat == "webp") {
|
||||||
|
std::vector<std::pair<std::string, std::string>> options {
|
||||||
|
{"Q", std::to_string(baton->webpQuality)},
|
||||||
|
{"alpha_q", std::to_string(baton->webpAlphaQuality)},
|
||||||
|
{"lossless", baton->webpLossless ? "TRUE" : "FALSE"},
|
||||||
|
{"near_lossless", baton->webpNearLossless ? "TRUE" : "FALSE"},
|
||||||
|
{"smart_subsample", baton->webpSmartSubsample ? "TRUE" : "FALSE"},
|
||||||
|
{"min_size", baton->webpMinSize ? "TRUE" : "FALSE"},
|
||||||
|
{"mixed", baton->webpMixed ? "TRUE" : "FALSE"},
|
||||||
|
{"effort", std::to_string(baton->webpEffort)}
|
||||||
|
};
|
||||||
|
suffix = AssembleSuffixString(".webp", options);
|
||||||
|
} else {
|
||||||
|
std::vector<std::pair<std::string, std::string>> options {
|
||||||
|
{"Q", std::to_string(baton->jpegQuality)},
|
||||||
|
{"interlace", baton->jpegProgressive ? "TRUE" : "FALSE"},
|
||||||
|
{"subsample_mode", baton->jpegChromaSubsampling == "4:4:4" ? "off" : "on"},
|
||||||
|
{"trellis_quant", baton->jpegTrellisQuantisation ? "TRUE" : "FALSE"},
|
||||||
|
{"quant_table", std::to_string(baton->jpegQuantisationTable)},
|
||||||
|
{"overshoot_deringing", baton->jpegOvershootDeringing ? "TRUE": "FALSE"},
|
||||||
|
{"optimize_scans", baton->jpegOptimiseScans ? "TRUE": "FALSE"},
|
||||||
|
{"optimize_coding", baton->jpegOptimiseCoding ? "TRUE": "FALSE"}
|
||||||
|
};
|
||||||
|
std::string extname = baton->tileLayout == VIPS_FOREIGN_DZ_LAYOUT_DZ ? ".jpeg" : ".jpg";
|
||||||
|
suffix = AssembleSuffixString(extname, options);
|
||||||
|
}
|
||||||
|
vips::VOption *options = VImage::option()
|
||||||
|
->set("strip", !baton->withMetadata)
|
||||||
|
->set("tile_size", baton->tileSize)
|
||||||
|
->set("overlap", baton->tileOverlap)
|
||||||
|
->set("container", baton->tileContainer)
|
||||||
|
->set("layout", baton->tileLayout)
|
||||||
|
->set("suffix", const_cast<char*>(suffix.data()))
|
||||||
|
->set("angle", CalculateAngleRotation(baton->tileAngle))
|
||||||
|
->set("background", baton->tileBackground)
|
||||||
|
->set("centre", baton->tileCentre)
|
||||||
|
->set("id", const_cast<char*>(baton->tileId.data()))
|
||||||
|
->set("skip_blanks", baton->tileSkipBlanks);
|
||||||
|
if (baton->tileDepth < VIPS_FOREIGN_DZ_DEPTH_LAST) {
|
||||||
|
options->set("depth", baton->tileDepth);
|
||||||
|
}
|
||||||
|
if (!baton->tileBasename.empty()) {
|
||||||
|
options->set("basename", const_cast<char*>(baton->tileBasename.data()));
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Clear all thread-local data.
|
Clear all thread-local data.
|
||||||
*/
|
*/
|
||||||
@ -1600,6 +1622,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|||||||
sharp::AttrAsStr(options, "tileDepth").data()));
|
sharp::AttrAsStr(options, "tileDepth").data()));
|
||||||
baton->tileCentre = sharp::AttrAsBool(options, "tileCentre");
|
baton->tileCentre = sharp::AttrAsBool(options, "tileCentre");
|
||||||
baton->tileId = sharp::AttrAsStr(options, "tileId");
|
baton->tileId = sharp::AttrAsStr(options, "tileId");
|
||||||
|
baton->tileBasename = sharp::AttrAsStr(options, "tileBasename");
|
||||||
|
|
||||||
// Force random access for certain operations
|
// Force random access for certain operations
|
||||||
if (baton->input->access == VIPS_ACCESS_SEQUENTIAL) {
|
if (baton->input->access == VIPS_ACCESS_SEQUENTIAL) {
|
||||||
|
@ -212,6 +212,7 @@ struct PipelineBaton {
|
|||||||
int tileSkipBlanks;
|
int tileSkipBlanks;
|
||||||
VipsForeignDzDepth tileDepth;
|
VipsForeignDzDepth tileDepth;
|
||||||
std::string tileId;
|
std::string tileId;
|
||||||
|
std::string tileBasename;
|
||||||
std::unique_ptr<double[]> recombMatrix;
|
std::unique_ptr<double[]> recombMatrix;
|
||||||
|
|
||||||
PipelineBaton():
|
PipelineBaton():
|
||||||
|
@ -317,6 +317,14 @@ describe('Tile', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Invalid basename parameter value fails', function () {
|
||||||
|
assert.throws(function () {
|
||||||
|
sharp().tile({
|
||||||
|
basename: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
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 () {
|
||||||
@ -952,4 +960,33 @@ describe('Tile', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Write ZIP container to Buffer', function (done) {
|
||||||
|
const container = fixtures.path('output.dz.tiles.zip');
|
||||||
|
const extractTo = fixtures.path('output.dz.tiles');
|
||||||
|
const directory = path.join(extractTo, 'output.dz.tiles_files');
|
||||||
|
rimraf(directory, function () {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.tile({ basename: 'output.dz.tiles' })
|
||||||
|
.toBuffer(function (err, data, 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('number', typeof info.size);
|
||||||
|
fs.writeFileSync(container, data);
|
||||||
|
fs.stat(container, function (err, stat) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, stat.isFile());
|
||||||
|
assert.strictEqual(true, stat.size > 0);
|
||||||
|
extractZip(container, { dir: path.dirname(extractTo) })
|
||||||
|
.then(() => {
|
||||||
|
assertDeepZoomTiles(directory, 256, 13, done);
|
||||||
|
})
|
||||||
|
.catch(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user