diff --git a/.gitignore b/.gitignore index f0e8ed9c..75b47ca5 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ build node_modules coverage test/bench/node_modules -test/fixtures/output.* +test/fixtures/output* test/leak/libvips.supp # Mac OS X diff --git a/README.md b/README.md index c9ff0211..aaa78888 100755 --- a/README.md +++ b/README.md @@ -12,7 +12,11 @@ The typical use case for this high speed Node.js module is to convert large images of many formats to smaller, web-friendly JPEG, PNG and WebP images of varying dimensions. This module supports reading and writing JPEG, PNG and WebP images to and from Streams, Buffer objects and the filesystem. -It also supports reading images of many other types from the filesystem via libmagick, libgraphicsmagick or [OpenSlide](http://openslide.org/) if present and writing Deep Zoom images. +It also supports reading images of many other formats from the filesystem via libmagick, libgraphicsmagick or [OpenSlide](http://openslide.org/) if present. + +Deep Zoom image pyramids can be generated, suitable for use with "slippy map" tile viewers like +[OpenSeadragon](https://github.com/openseadragon/openseadragon) and [Leaflet](https://github.com/turban/Leaflet.Zoomify). + Colour spaces, embedded ICC profiles and alpha transparency channels are all handled correctly. Only small regions of uncompressed image data are held in memory and processed at a time, taking full advantage of multiple CPU cores and L1/L2/L3 cache. Resizing an image is typically 4x faster than using the quickest ImageMagick and GraphicsMagick settings. @@ -498,6 +502,13 @@ Include all metadata (EXIF, XMP, IPTC) from the input image in the output image. The default behaviour is to strip all metadata and convert to the device-independent sRGB colour space. +#### tile([size], [overlap]) + +The size and overlap, in pixels, of square Deep Zoom image pyramid tiles. + +* `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. + #### withoutChromaSubsampling() Disable the use of [chroma subsampling](http://en.wikipedia.org/wiki/Chroma_subsampling) with JPEG output (4:4:4). @@ -519,12 +530,6 @@ _Requires libvips 7.42.0+_ An advanced setting to disable adaptive row filtering for the lossless PNG output format. -#### tileSize(tileSize) -Setting the tile_size when DZI output format is selected. Default is 256. - -#### tileOverlap(tileOverlap) -Setting the overlap when DZI output format is selected. Default is 0. - ### Output methods #### toFile(filename, [callback]) diff --git a/index.js b/index.js index d83b00f6..631d8c1f 100755 --- a/index.js +++ b/index.js @@ -380,28 +380,28 @@ Sharp.prototype.withMetadata = function(withMetadata) { }; /* - dz tile size for DZ output - max is 8192 since 7.36.5 (1024 before) + Tile size and overlap for Deep Zoom output */ -Sharp.prototype.tileSize = function(tileSize) { - if (!Number.isNaN(tileSize) && tileSize >= 1 && tileSize <= 8192) { - this.options.tileSize = tileSize; - } else { - throw new Error('Invalid tileSize (1 to 8192) ' + tileSize); +Sharp.prototype.tile = function(size, overlap) { + // Size of square tiles, in pixels + if (typeof size !== 'undefined' && size !== null) { + if (!Number.isNaN(size) && size % 1 === 0 && size >= 1 && size <= 8192) { + this.options.tileSize = size; + } else { + throw new Error('Invalid tile size (1 to 8192) ' + size); + } } - return this; -}; - -/* - dz overlap for DZ output -*/ -Sharp.prototype.tileOverlap = function(tileOverlap) { - if (!Number.isNaN(tileOverlap) && tileOverlap >=0 && tileOverlap <= 8192) { - this.options.tileOverlap = tileOverlap; - } else { - throw new Error('Invalid tileOverlap (0 to 8192) ' + tileOverlap); + // Overlap of tiles, in pixels + if (typeof overlap !== 'undefined' && overlap !== null) { + if (!Number.isNaN(overlap) && overlap % 1 === 0 && overlap >=0 && overlap <= 8192) { + if (overlap > this.options.tileSize) { + throw new Error('Tile overlap ' + overlap + ' cannot be larger than tile size ' + this.options.tileSize); + } + this.options.tileOverlap = overlap; + } else { + throw new Error('Invalid tile overlap (0 to 8192) ' + overlap); + } } - return this; }; @@ -511,14 +511,6 @@ Sharp.prototype.raw = function() { return this; }; -/* - Force DZI output -*/ -Sharp.prototype.dz = function() { - this.options.output = '__dzi'; - return this; -}; - /* Force output to a given format @param format is either the id as a String or an Object with an 'id' attribute diff --git a/package.json b/package.json old mode 100644 new mode 100755 index dc4cbb71..2caa02c2 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sharp", - "version": "0.9.3", + "version": "0.10.0", "author": "Lovell Fuller ", "contributors": [ "Pierre Inglebert ", @@ -30,6 +30,7 @@ "png", "webp", "tiff", + "dzi", "resize", "thumbnail", "crop", @@ -37,17 +38,19 @@ "vips" ], "dependencies": { - "bluebird": "^2.9.12", - "color": "^0.7.3", + "bluebird": "^2.9.14", + "color": "^0.8.0", "nan": "^1.6.2", "semver": "^4.3.1" }, "devDependencies": { "mocha": "^2.1.0", - "mocha-jshint": "^0.0.9", + "mocha-jshint": "^1.0.0", "istanbul": "^0.3.6", "coveralls": "^2.11.2", - "node-cpplint": "^0.4.0" + "node-cpplint": "^0.4.0", + "rimraf": "^2.3.2", + "async": "^0.9.0" }, "license": "Apache 2.0", "engines": { diff --git a/src/common.cc b/src/common.cc index 0f6c345c..e48e8b06 100755 --- a/src/common.cc +++ b/src/common.cc @@ -37,96 +37,61 @@ namespace sharp { */ ImageType DetermineImageType(void *buffer, size_t const length) { ImageType imageType = ImageType::UNKNOWN; -#if (VIPS_MAJOR_VERSION >= 8) - if (vips_foreign_is_a_buffer("jpegload_buffer", buffer, length)) { - imageType = ImageType::JPEG; - } else if (vips_foreign_is_a_buffer("pngload_buffer", buffer, length)) { - imageType = ImageType::PNG; - } else if (vips_foreign_is_a_buffer("webpload_buffer", buffer, length)) { - imageType = ImageType::WEBP; - } else if (vips_foreign_is_a_buffer("tiffload_buffer", buffer, length)) { - imageType = ImageType::TIFF; - } else if(vips_foreign_is_a_buffer("magickload_buffer", buffer, length)) { - imageType = ImageType::MAGICK; - } -#else - const char* loader = vips_foreign_find_load_buffer(buffer, length); - - if (loader != NULL) { - if (!strcmp(loader, "VipsForeignLoadJpegBuffer")) { + char const *load = vips_foreign_find_load_buffer(buffer, length); + if (load != NULL) { + std::string loader = load; + if (EndsWith(loader, "JpegBuffer")) { imageType = ImageType::JPEG; - } else if (!strcmp(loader, "VipsForeignLoadPngBuffer")) { + } else if (EndsWith(loader, "PngBuffer")) { imageType = ImageType::PNG; - } else if (!strcmp(loader, "VipsForeignLoadWebpBuffer")) { + } else if (EndsWith(loader, "WebpBuffer")) { imageType = ImageType::WEBP; - } else if (!strcmp(loader, "VipsForeignLoadTiffBuffer")) { + } else if (EndsWith(loader, "TiffBuffer")) { imageType = ImageType::TIFF; - } else if (!strcmp(loader, "VipsForeignLoadMagickBuffer")) { + } else if (EndsWith(loader, "MagickBuffer")) { imageType = ImageType::MAGICK; } } -#endif return imageType; } /* Initialise and return a VipsImage from a buffer. Supports JPEG, PNG, WebP and TIFF. */ - VipsImage* InitImage(ImageType imageType, void *buffer, size_t const length, VipsAccess const access) { - VipsImage *image = NULL; - if (imageType == ImageType::JPEG) { - vips_jpegload_buffer(buffer, length, &image, "access", access, NULL); - } else if (imageType == ImageType::PNG) { - vips_pngload_buffer(buffer, length, &image, "access", access, NULL); - } else if (imageType == ImageType::WEBP) { - vips_webpload_buffer(buffer, length, &image, "access", access, NULL); - } else if (imageType == ImageType::TIFF) { - vips_tiffload_buffer(buffer, length, &image, "access", access, NULL); -#if (VIPS_MAJOR_VERSION >= 8) - } else if (imageType == ImageType::MAGICK) { - vips_magickload_buffer(buffer, length, &image, "access", access, NULL); -#endif - } - return image; + VipsImage* InitImage(void *buffer, size_t const length, VipsAccess const access) { + return vips_image_new_from_buffer(buffer, length, NULL, "access", access, NULL); } /* - Inpect the first 2-4 bytes of a file to determine image format + Determine image format, reads the first few bytes of the file */ ImageType DetermineImageType(char const *file) { ImageType imageType = ImageType::UNKNOWN; - if (vips_foreign_is_a("jpegload", file)) { - imageType = ImageType::JPEG; - } else if (vips_foreign_is_a("pngload", file)) { - imageType = ImageType::PNG; - } else if (vips_foreign_is_a("webpload", file)) { - imageType = ImageType::WEBP; - } else if (vips_foreign_is_a("tiffload", file)) { - imageType = ImageType::TIFF; - } else if(vips_foreign_is_a("magickload", file)) { - imageType = ImageType::MAGICK; + char const *load = vips_foreign_find_load(file); + if (load != NULL) { + std::string loader = load; + if (EndsWith(loader, "JpegFile")) { + imageType = ImageType::JPEG; + } else if (EndsWith(loader, "Png")) { + imageType = ImageType::PNG; + } else if (EndsWith(loader, "WebpFile")) { + imageType = ImageType::WEBP; + } else if (EndsWith(loader, "Openslide")) { + imageType = ImageType::OPENSLIDE; + } else if (EndsWith(loader, "TiffFile")) { + imageType = ImageType::TIFF; + } else if (EndsWith(loader, "MagickFile")) { + imageType = ImageType::MAGICK; + } } - return imageType; } /* Initialise and return a VipsImage from a file. */ - VipsImage* InitImage(ImageType imageType, char const *file, VipsAccess const access) { - VipsImage *image = NULL; - if (imageType == ImageType::JPEG) { - vips_jpegload(file, &image, "access", access, NULL); - } else if (imageType == ImageType::PNG) { - vips_pngload(file, &image, "access", access, NULL); - } else if (imageType == ImageType::WEBP) { - vips_webpload(file, &image, "access", access, NULL); - } else if (imageType == ImageType::TIFF) { - vips_tiffload(file, &image, "access", access, NULL); - } else if (imageType == ImageType::MAGICK) { - vips_magickload(file, &image, "access", access, NULL); - } - return image; + VipsImage* InitImage(char const *file, VipsAccess const access) { + return vips_image_new_from_file(file, "access", access, NULL); } /* diff --git a/src/common.h b/src/common.h index 0712ef15..f27e36df 100755 --- a/src/common.h +++ b/src/common.h @@ -10,7 +10,7 @@ namespace sharp { WEBP, TIFF, MAGICK, - DZ + OPENSLIDE }; // How many tasks are in the queue? @@ -39,12 +39,12 @@ namespace sharp { /* Initialise and return a VipsImage from a buffer. Supports JPEG, PNG, WebP and TIFF. */ - VipsImage* InitImage(ImageType imageType, void *buffer, size_t const length, VipsAccess const access); + VipsImage* InitImage(void *buffer, size_t const length, VipsAccess const access); /* Initialise and return a VipsImage from a file. */ - VipsImage* InitImage(ImageType imageType, char const *file, VipsAccess const access); + VipsImage* InitImage(char const *file, VipsAccess const access); /* Does this image have an embedded profile? diff --git a/src/metadata.cc b/src/metadata.cc index 20473058..a8ceab42 100755 --- a/src/metadata.cc +++ b/src/metadata.cc @@ -61,7 +61,7 @@ class MetadataWorker : public NanAsyncWorker { // From buffer imageType = DetermineImageType(baton->bufferIn, baton->bufferInLength); if (imageType != ImageType::UNKNOWN) { - image = InitImage(imageType, baton->bufferIn, baton->bufferInLength, VIPS_ACCESS_RANDOM); + image = InitImage(baton->bufferIn, baton->bufferInLength, VIPS_ACCESS_RANDOM); if (image == NULL) { (baton->err).append("Input buffer has corrupt header"); imageType = ImageType::UNKNOWN; @@ -73,7 +73,7 @@ class MetadataWorker : public NanAsyncWorker { // From file imageType = DetermineImageType(baton->fileIn.c_str()); if (imageType != ImageType::UNKNOWN) { - image = InitImage(imageType, baton->fileIn.c_str(), VIPS_ACCESS_RANDOM); + image = InitImage(baton->fileIn.c_str(), VIPS_ACCESS_RANDOM); if (image == NULL) { (baton->err).append("Input file has corrupt header"); imageType = ImageType::UNKNOWN; @@ -90,7 +90,7 @@ class MetadataWorker : public NanAsyncWorker { case ImageType::WEBP: baton->format = "webp"; break; case ImageType::TIFF: baton->format = "tiff"; break; case ImageType::MAGICK: baton->format = "magick"; break; - case ImageType::DZ: baton->format = "dzi"; break; + case ImageType::OPENSLIDE: baton->format = "openslide"; break; case ImageType::UNKNOWN: break; } // VipsImage attributes diff --git a/src/resize.cc b/src/resize.cc index b37eeaca..ffcd3e85 100755 --- a/src/resize.cc +++ b/src/resize.cc @@ -172,7 +172,7 @@ class ResizeWorker : public NanAsyncWorker { // From buffer inputImageType = DetermineImageType(baton->bufferIn, baton->bufferInLength); if (inputImageType != ImageType::UNKNOWN) { - image = InitImage(inputImageType, baton->bufferIn, baton->bufferInLength, baton->accessMethod); + image = InitImage(baton->bufferIn, baton->bufferInLength, baton->accessMethod); if (image != NULL) { // Listen for "postclose" signal to delete input buffer g_signal_connect(image, "postclose", G_CALLBACK(DeleteBuffer), baton->bufferIn); @@ -190,7 +190,7 @@ class ResizeWorker : public NanAsyncWorker { // From file inputImageType = DetermineImageType(baton->fileIn.c_str()); if (inputImageType != ImageType::UNKNOWN) { - image = InitImage(inputImageType, baton->fileIn.c_str(), baton->accessMethod); + image = InitImage(baton->fileIn.c_str(), baton->accessMethod); if (image == NULL) { (baton->err).append("Input file has corrupt header"); inputImageType = ImageType::UNKNOWN; @@ -801,7 +801,7 @@ class ResizeWorker : public NanAsyncWorker { return Error(baton, hook); } baton->outputFormat = "tiff"; - } else if (outputDz || matchInput) { + } else if (outputDz) { // Write DZ to file std::string filename_no_extension = baton->output.substr(0, baton->output.length() - 4); if (vips_dzsave(image, filename_no_extension.c_str(), "strip", !baton->withMetadata, @@ -845,11 +845,6 @@ class ResizeWorker : public NanAsyncWorker { info->Set(NanNew("width"), NanNew(static_cast(width))); info->Set(NanNew("height"), NanNew(static_cast(height))); - if (baton->outputFormat == "dz" ) { - info->Set(NanNew("tileSize"), NanNew(static_cast(baton->tileSize))); - info->Set(NanNew("tileOverlap"), NanNew(static_cast(baton->tileOverlap))); - } - if (baton->bufferOutLength > 0) { // Copy data to new Buffer argv[1] = NanNewBufferHandle(static_cast(baton->bufferOut), baton->bufferOutLength); diff --git a/test/unit/io.js b/test/unit/io.js index 4b490161..fd1cb20a 100755 --- a/test/unit/io.js +++ b/test/unit/io.js @@ -519,42 +519,6 @@ describe('Input/output', function() { }); } - if (sharp.format.dz.output.file) { - it('Convert JPEG to DZ', function(done) { - sharp(fixtures.inputJpg) - .resize(320, 240) - .toFormat(sharp.format.dz) - .toFile(fixtures.path('output.jpg.dzi'), function(err, info) { - if (err) throw err; - assert.strictEqual(true, info.size > 0); - assert.strictEqual('dz', info.format); - assert.strictEqual(320, info.width); - assert.strictEqual(240, info.height); - assert.strictEqual(256, info.tileSize); - assert.strictEqual(0, info.tileOverlap); - done(); - }); - }); - - it('Convert JPEG to DZ (test tileSize and tileOverlap)', function(done) { - sharp(fixtures.inputJpg) - .resize(320, 240) - .toFormat(sharp.format.dz) - .tileSize(512) - .tileOverlap(10) - .toFile(fixtures.path('output.tileTest.jpg.dzi'), function(err, info) { - if (err) throw err; - assert.strictEqual(true, info.size > 0); - assert.strictEqual('dz', info.format); - assert.strictEqual(320, info.width); - assert.strictEqual(240, info.height); - assert.strictEqual(512, info.tileSize); - assert.strictEqual(10, info.tileOverlap); - done(); - }); - }); -} - if (sharp.format.tiff.input.buffer) { it('Load TIFF from Buffer', function(done) { var inputTiffBuffer = fs.readFileSync(fixtures.inputTiff); @@ -591,6 +555,22 @@ describe('Input/output', function() { }); } + if (sharp.format.openslide.input.file) { + it('Load Aperio SVS file via Openslide', function(done) { + sharp(fixtures.inputSvs) + .resize(320, 240) + .jpeg() + .toBuffer(function(err, data, info) { + if (err) throw err; + assert.strictEqual(true, data.length > 0); + assert.strictEqual('jpeg', info.format); + assert.strictEqual(320, info.width); + assert.strictEqual(240, info.height); + done(); + }); + }); + } + if (sharp.format.raw.output.buffer) { describe('Ouput raw, uncompressed image data', function() { it('1 channel greyscale image', function(done) { @@ -704,65 +684,4 @@ describe('Input/output', function() { }); - if(sharp.format.openslide.input.file) { - describe('Openslide output', function() { - it('Aperio - convert SVS to PNG', function(done) { - sharp(fixtures.inputSvs) - .toFile(fixtures.path('output.svs.png'), function(err, info) { - if (err) throw err; - assert.strictEqual(true, info.size > 0); - assert.strictEqual('png', info.format); - assert.strictEqual(2220, info.width); - assert.strictEqual(2967, info.height); - done(); - }); - }); - it('Aperio - convert SVS to JPEG', function(done) { - sharp(fixtures.inputSvs) - .toFile(fixtures.path('output.svs.jpeg'), function(err, info) { - if (err) throw err; - assert.strictEqual(true, info.size > 0); - assert.strictEqual('jpeg', info.format); - assert.strictEqual(2220, info.width); - assert.strictEqual(2967, info.height); - done(); - }); - }); - it('Aperio - convert SVS to TIFF', function(done) { - sharp(fixtures.inputSvs) - .toFile(fixtures.path('output.svs.tiff'), function(err, info) { - if (err) throw err; - assert.strictEqual(true, info.size > 0); - assert.strictEqual('tiff', info.format); - assert.strictEqual(2220, info.width); - assert.strictEqual(2967, info.height); - done(); - }); - }); - it('Aperio - convert SVS to WEBP', function(done) { - sharp(fixtures.inputSvs) - .toFile(fixtures.path('output.svs.webp'), function(err, info) { - if (err) throw err; - assert.strictEqual(true, info.size > 0); - assert.strictEqual('webp', info.format); - assert.strictEqual(2220, info.width); - assert.strictEqual(2967, info.height); - done(); - }); - }); - it('Aperio - convert SVS to DZI', function(done) { - sharp(fixtures.inputSvs) - .toFile(fixtures.path('output.aperio.svs.dzi'), function(err, info) { - if (err) throw err; - assert.strictEqual(true, info.size > 0); - assert.strictEqual('dz', info.format); - assert.strictEqual(2220, info.width); - assert.strictEqual(2967, info.height); - assert.strictEqual(256, info.tileSize); - assert.strictEqual(0, info.tileOverlap); - done(); - }); - }); - }); - } }); diff --git a/test/unit/metadata.js b/test/unit/metadata.js index 272e9162..6ea7e11d 100755 --- a/test/unit/metadata.js +++ b/test/unit/metadata.js @@ -109,6 +109,22 @@ describe('Image metadata', function() { }); }); + if (sharp.format.openslide.input.file) { + it('Aperio SVS via openslide', function(done) { + sharp(fixtures.inputSvs).metadata(function(err, metadata) { + if (err) throw err; + assert.strictEqual('openslide', metadata.format); + assert.strictEqual(2220, metadata.width); + assert.strictEqual(2967, metadata.height); + assert.strictEqual(4, metadata.channels); + assert.strictEqual('rgb', metadata.space); + assert.strictEqual(false, metadata.hasProfile); + assert.strictEqual(true, metadata.hasAlpha); + done(); + }); + }); + } + it('File in, Promise out', function(done) { sharp(fixtures.inputJpg).metadata().then(function(metadata) { assert.strictEqual('jpeg', metadata.format); diff --git a/test/unit/tile.js b/test/unit/tile.js new file mode 100755 index 00000000..f109ba7a --- /dev/null +++ b/test/unit/tile.js @@ -0,0 +1,204 @@ +'use strict'; + +var fs = require('fs'); +var path = require('path'); +var assert = require('assert'); + +var async = require('async'); +var rimraf = require('rimraf'); + +var sharp = require('../../index'); +var fixtures = require('../fixtures'); + +sharp.cache(0); + +// Verifies all tiles in a given dz output directory are <= size +var assertDeepZoomTiles = function(directory, expectedSize, expectedLevels, done) { + // Get levels + var levels = fs.readdirSync(directory); + assert.strictEqual(expectedLevels, levels.length); + // Get tiles + var tiles = []; + levels.forEach(function(level) { + // Verify level directory name + assert.strictEqual(true, /^[0-9]+$/.test(level)); + fs.readdirSync(path.join(directory, level)).forEach(function(tile) { + // Verify tile file name + assert.strictEqual(true, /^[0-9]+_[0-9]+\.jpeg$/.test(tile)); + tiles.push(path.join(directory, level, tile)); + }); + }); + // Verify each tile is <= expectedSize + async.eachSeries(tiles, function(tile, done) { + sharp(tile).metadata(function(err, metadata) { + if (err) { + done(err); + } else { + assert.strictEqual('jpeg', metadata.format); + assert.strictEqual('srgb', metadata.space); + assert.strictEqual(3, metadata.channels); + assert.strictEqual(false, metadata.hasProfile); + assert.strictEqual(false, metadata.hasAlpha); + assert.strictEqual(true, metadata.width <= expectedSize); + assert.strictEqual(true, metadata.height <= expectedSize); + done(); + } + }); + }, done); +}; + +describe('Tile', function() { + + describe('Invalid tile values', function() { + it('size - NaN', function(done) { + var isValid = true; + try { + sharp().tile('zoinks'); + } catch (err) { + isValid = false; + } + assert.strictEqual(false, isValid); + done(); + }); + + it('size - float', function(done) { + var isValid = true; + try { + sharp().tile(1.1); + } catch (err) { + isValid = false; + } + assert.strictEqual(false, isValid); + done(); + }); + + it('size - negative', function(done) { + var isValid = true; + try { + sharp().tile(-1); + } catch (err) { + isValid = false; + } + assert.strictEqual(false, isValid); + done(); + }); + + it('size - zero', function(done) { + var isValid = true; + try { + sharp().tile(0); + } catch (err) { + isValid = false; + } + assert.strictEqual(false, isValid); + done(); + }); + + it('size - too large', function(done) { + var isValid = true; + try { + sharp().tile(8193); + } catch (err) { + isValid = false; + } + assert.strictEqual(false, isValid); + done(); + }); + + it('overlap - NaN', function(done) { + var isValid = true; + try { + sharp().tile(null, 'zoinks'); + } catch (err) { + isValid = false; + } + assert.strictEqual(false, isValid); + done(); + }); + + it('overlap - float', function(done) { + var isValid = true; + try { + sharp().tile(null, 1.1); + } catch (err) { + isValid = false; + } + assert.strictEqual(false, isValid); + done(); + }); + + it('overlap - negative', function(done) { + var isValid = true; + try { + sharp().tile(null, -1); + } catch (err) { + isValid = false; + } + assert.strictEqual(false, isValid); + done(); + }); + + it('overlap - too large', function(done) { + var isValid = true; + try { + sharp().tile(null, 8193); + } catch (err) { + isValid = false; + } + assert.strictEqual(false, isValid); + done(); + }); + + it('overlap - larger than default size', function(done) { + var isValid = true; + try { + sharp().tile(null, 257); + } catch (err) { + isValid = false; + } + assert.strictEqual(false, isValid); + done(); + }); + + it('overlap - larger than provided size', function(done) { + var isValid = true; + try { + sharp().tile(512, 513); + } catch (err) { + isValid = false; + } + assert.strictEqual(false, isValid); + done(); + }); + + }); + + if (sharp.format.dz.output.file) { + describe('Deep Zoom output', function() { + + it('Tile size - 256px default', function(done) { + var directory = fixtures.path('output256_files'); + rimraf(directory, function() { + sharp(fixtures.inputJpg).toFile(fixtures.path('output256.dzi'), function(err, info) { + if (err) throw err; + assert.strictEqual('dz', info.format); + assertDeepZoomTiles(directory, 256, 13, done); + }); + }); + }); + + it('Tile size/overlap - 512/16px', function(done) { + var directory = fixtures.path('output512_files'); + rimraf(directory, function() { + sharp(fixtures.inputJpg).tile(512, 16).toFile(fixtures.path('output512.dzi'), function(err, info) { + if (err) throw err; + assert.strictEqual('dz', info.format); + assertDeepZoomTiles(directory, 512 + 2 * 16, 13, done); + }); + }); + }); + + }); + } + +});