Combine new tile* API methods

Use v7.40.0+ libvips loader methods

Separate Openslide as input vs Deep Zoom as output

Split tile-based tests into new file

Added assertions for generated tile size
This commit is contained in:
Lovell Fuller 2015-03-12 15:39:27 +00:00
parent 2d1e6f2644
commit 5781a23a4d
11 changed files with 314 additions and 215 deletions

2
.gitignore vendored
View File

@ -2,7 +2,7 @@ build
node_modules node_modules
coverage coverage
test/bench/node_modules test/bench/node_modules
test/fixtures/output.* test/fixtures/output*
test/leak/libvips.supp test/leak/libvips.supp
# Mac OS X # Mac OS X

View File

@ -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. 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. 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. 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. 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. 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() #### withoutChromaSubsampling()
Disable the use of [chroma subsampling](http://en.wikipedia.org/wiki/Chroma_subsampling) with JPEG output (4:4:4). 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. 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 ### Output methods
#### toFile(filename, [callback]) #### toFile(filename, [callback])

View File

@ -380,28 +380,28 @@ Sharp.prototype.withMetadata = function(withMetadata) {
}; };
/* /*
dz tile size for DZ output Tile size and overlap for Deep Zoom output
max is 8192 since 7.36.5 (1024 before)
*/ */
Sharp.prototype.tileSize = function(tileSize) { Sharp.prototype.tile = function(size, overlap) {
if (!Number.isNaN(tileSize) && tileSize >= 1 && tileSize <= 8192) { // Size of square tiles, in pixels
this.options.tileSize = tileSize; if (typeof size !== 'undefined' && size !== null) {
if (!Number.isNaN(size) && size % 1 === 0 && size >= 1 && size <= 8192) {
this.options.tileSize = size;
} else { } else {
throw new Error('Invalid tileSize (1 to 8192) ' + tileSize); throw new Error('Invalid tile size (1 to 8192) ' + size);
} }
return this; }
}; // Overlap of tiles, in pixels
if (typeof overlap !== 'undefined' && overlap !== null) {
/* if (!Number.isNaN(overlap) && overlap % 1 === 0 && overlap >=0 && overlap <= 8192) {
dz overlap for DZ output if (overlap > this.options.tileSize) {
*/ throw new Error('Tile overlap ' + overlap + ' cannot be larger than tile size ' + this.options.tileSize);
Sharp.prototype.tileOverlap = function(tileOverlap) { }
if (!Number.isNaN(tileOverlap) && tileOverlap >=0 && tileOverlap <= 8192) { this.options.tileOverlap = overlap;
this.options.tileOverlap = tileOverlap;
} else { } else {
throw new Error('Invalid tileOverlap (0 to 8192) ' + tileOverlap); throw new Error('Invalid tile overlap (0 to 8192) ' + overlap);
}
} }
return this; return this;
}; };
@ -511,14 +511,6 @@ Sharp.prototype.raw = function() {
return this; return this;
}; };
/*
Force DZI output
*/
Sharp.prototype.dz = function() {
this.options.output = '__dzi';
return this;
};
/* /*
Force output to a given format Force output to a given format
@param format is either the id as a String or an Object with an 'id' attribute @param format is either the id as a String or an Object with an 'id' attribute

13
package.json Normal file → Executable file
View File

@ -1,6 +1,6 @@
{ {
"name": "sharp", "name": "sharp",
"version": "0.9.3", "version": "0.10.0",
"author": "Lovell Fuller <npm@lovell.info>", "author": "Lovell Fuller <npm@lovell.info>",
"contributors": [ "contributors": [
"Pierre Inglebert <pierre.inglebert@gmail.com>", "Pierre Inglebert <pierre.inglebert@gmail.com>",
@ -30,6 +30,7 @@
"png", "png",
"webp", "webp",
"tiff", "tiff",
"dzi",
"resize", "resize",
"thumbnail", "thumbnail",
"crop", "crop",
@ -37,17 +38,19 @@
"vips" "vips"
], ],
"dependencies": { "dependencies": {
"bluebird": "^2.9.12", "bluebird": "^2.9.14",
"color": "^0.7.3", "color": "^0.8.0",
"nan": "^1.6.2", "nan": "^1.6.2",
"semver": "^4.3.1" "semver": "^4.3.1"
}, },
"devDependencies": { "devDependencies": {
"mocha": "^2.1.0", "mocha": "^2.1.0",
"mocha-jshint": "^0.0.9", "mocha-jshint": "^1.0.0",
"istanbul": "^0.3.6", "istanbul": "^0.3.6",
"coveralls": "^2.11.2", "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", "license": "Apache 2.0",
"engines": { "engines": {

View File

@ -37,96 +37,61 @@ namespace sharp {
*/ */
ImageType DetermineImageType(void *buffer, size_t const length) { ImageType DetermineImageType(void *buffer, size_t const length) {
ImageType imageType = ImageType::UNKNOWN; ImageType imageType = ImageType::UNKNOWN;
#if (VIPS_MAJOR_VERSION >= 8) char const *load = vips_foreign_find_load_buffer(buffer, length);
if (vips_foreign_is_a_buffer("jpegload_buffer", buffer, length)) { if (load != NULL) {
std::string loader = load;
if (EndsWith(loader, "JpegBuffer")) {
imageType = ImageType::JPEG; imageType = ImageType::JPEG;
} else if (vips_foreign_is_a_buffer("pngload_buffer", buffer, length)) { } else if (EndsWith(loader, "PngBuffer")) {
imageType = ImageType::PNG; imageType = ImageType::PNG;
} else if (vips_foreign_is_a_buffer("webpload_buffer", buffer, length)) { } else if (EndsWith(loader, "WebpBuffer")) {
imageType = ImageType::WEBP; imageType = ImageType::WEBP;
} else if (vips_foreign_is_a_buffer("tiffload_buffer", buffer, length)) { } else if (EndsWith(loader, "TiffBuffer")) {
imageType = ImageType::TIFF; imageType = ImageType::TIFF;
} else if(vips_foreign_is_a_buffer("magickload_buffer", buffer, length)) { } else if (EndsWith(loader, "MagickBuffer")) {
imageType = ImageType::MAGICK;
}
#else
const char* loader = vips_foreign_find_load_buffer(buffer, length);
if (loader != NULL) {
if (!strcmp(loader, "VipsForeignLoadJpegBuffer")) {
imageType = ImageType::JPEG;
} else if (!strcmp(loader, "VipsForeignLoadPngBuffer")) {
imageType = ImageType::PNG;
} else if (!strcmp(loader, "VipsForeignLoadWebpBuffer")) {
imageType = ImageType::WEBP;
} else if (!strcmp(loader, "VipsForeignLoadTiffBuffer")) {
imageType = ImageType::TIFF;
} else if (!strcmp(loader, "VipsForeignLoadMagickBuffer")) {
imageType = ImageType::MAGICK; imageType = ImageType::MAGICK;
} }
} }
#endif
return imageType; return imageType;
} }
/* /*
Initialise and return a VipsImage from a buffer. Supports JPEG, PNG, WebP and TIFF. 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) {
VipsImage *image = NULL; return vips_image_new_from_buffer(buffer, length, NULL, "access", access, 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;
} }
/* /*
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 DetermineImageType(char const *file) {
ImageType imageType = ImageType::UNKNOWN; ImageType imageType = ImageType::UNKNOWN;
if (vips_foreign_is_a("jpegload", file)) { char const *load = vips_foreign_find_load(file);
if (load != NULL) {
std::string loader = load;
if (EndsWith(loader, "JpegFile")) {
imageType = ImageType::JPEG; imageType = ImageType::JPEG;
} else if (vips_foreign_is_a("pngload", file)) { } else if (EndsWith(loader, "Png")) {
imageType = ImageType::PNG; imageType = ImageType::PNG;
} else if (vips_foreign_is_a("webpload", file)) { } else if (EndsWith(loader, "WebpFile")) {
imageType = ImageType::WEBP; imageType = ImageType::WEBP;
} else if (vips_foreign_is_a("tiffload", file)) { } else if (EndsWith(loader, "Openslide")) {
imageType = ImageType::OPENSLIDE;
} else if (EndsWith(loader, "TiffFile")) {
imageType = ImageType::TIFF; imageType = ImageType::TIFF;
} else if(vips_foreign_is_a("magickload", file)) { } else if (EndsWith(loader, "MagickFile")) {
imageType = ImageType::MAGICK; imageType = ImageType::MAGICK;
} }
}
return imageType; return imageType;
} }
/* /*
Initialise and return a VipsImage from a file. 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) {
VipsImage *image = NULL; return vips_image_new_from_file(file, "access", access, 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;
} }
/* /*

View File

@ -10,7 +10,7 @@ namespace sharp {
WEBP, WEBP,
TIFF, TIFF,
MAGICK, MAGICK,
DZ OPENSLIDE
}; };
// How many tasks are in the queue? // 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. 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. 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? Does this image have an embedded profile?

View File

@ -61,7 +61,7 @@ class MetadataWorker : public NanAsyncWorker {
// From buffer // From buffer
imageType = DetermineImageType(baton->bufferIn, baton->bufferInLength); imageType = DetermineImageType(baton->bufferIn, baton->bufferInLength);
if (imageType != ImageType::UNKNOWN) { 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) { if (image == NULL) {
(baton->err).append("Input buffer has corrupt header"); (baton->err).append("Input buffer has corrupt header");
imageType = ImageType::UNKNOWN; imageType = ImageType::UNKNOWN;
@ -73,7 +73,7 @@ class MetadataWorker : public NanAsyncWorker {
// From file // From file
imageType = DetermineImageType(baton->fileIn.c_str()); imageType = DetermineImageType(baton->fileIn.c_str());
if (imageType != ImageType::UNKNOWN) { 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) { if (image == NULL) {
(baton->err).append("Input file has corrupt header"); (baton->err).append("Input file has corrupt header");
imageType = ImageType::UNKNOWN; imageType = ImageType::UNKNOWN;
@ -90,7 +90,7 @@ class MetadataWorker : public NanAsyncWorker {
case ImageType::WEBP: baton->format = "webp"; break; case ImageType::WEBP: baton->format = "webp"; break;
case ImageType::TIFF: baton->format = "tiff"; break; case ImageType::TIFF: baton->format = "tiff"; break;
case ImageType::MAGICK: baton->format = "magick"; 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; case ImageType::UNKNOWN: break;
} }
// VipsImage attributes // VipsImage attributes

View File

@ -172,7 +172,7 @@ class ResizeWorker : public NanAsyncWorker {
// From buffer // From buffer
inputImageType = DetermineImageType(baton->bufferIn, baton->bufferInLength); inputImageType = DetermineImageType(baton->bufferIn, baton->bufferInLength);
if (inputImageType != ImageType::UNKNOWN) { if (inputImageType != ImageType::UNKNOWN) {
image = InitImage(inputImageType, baton->bufferIn, baton->bufferInLength, baton->accessMethod); image = InitImage(baton->bufferIn, baton->bufferInLength, baton->accessMethod);
if (image != NULL) { if (image != NULL) {
// Listen for "postclose" signal to delete input buffer // Listen for "postclose" signal to delete input buffer
g_signal_connect(image, "postclose", G_CALLBACK(DeleteBuffer), baton->bufferIn); g_signal_connect(image, "postclose", G_CALLBACK(DeleteBuffer), baton->bufferIn);
@ -190,7 +190,7 @@ class ResizeWorker : public NanAsyncWorker {
// From file // From file
inputImageType = DetermineImageType(baton->fileIn.c_str()); inputImageType = DetermineImageType(baton->fileIn.c_str());
if (inputImageType != ImageType::UNKNOWN) { if (inputImageType != ImageType::UNKNOWN) {
image = InitImage(inputImageType, baton->fileIn.c_str(), baton->accessMethod); image = InitImage(baton->fileIn.c_str(), baton->accessMethod);
if (image == NULL) { if (image == NULL) {
(baton->err).append("Input file has corrupt header"); (baton->err).append("Input file has corrupt header");
inputImageType = ImageType::UNKNOWN; inputImageType = ImageType::UNKNOWN;
@ -801,7 +801,7 @@ class ResizeWorker : public NanAsyncWorker {
return Error(baton, hook); return Error(baton, hook);
} }
baton->outputFormat = "tiff"; baton->outputFormat = "tiff";
} else if (outputDz || matchInput) { } else if (outputDz) {
// Write DZ to file // Write DZ to file
std::string filename_no_extension = baton->output.substr(0, baton->output.length() - 4); 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, if (vips_dzsave(image, filename_no_extension.c_str(), "strip", !baton->withMetadata,
@ -845,11 +845,6 @@ class ResizeWorker : public NanAsyncWorker {
info->Set(NanNew<String>("width"), NanNew<Uint32>(static_cast<uint32_t>(width))); info->Set(NanNew<String>("width"), NanNew<Uint32>(static_cast<uint32_t>(width)));
info->Set(NanNew<String>("height"), NanNew<Uint32>(static_cast<uint32_t>(height))); info->Set(NanNew<String>("height"), NanNew<Uint32>(static_cast<uint32_t>(height)));
if (baton->outputFormat == "dz" ) {
info->Set(NanNew<String>("tileSize"), NanNew<Uint32>(static_cast<uint32_t>(baton->tileSize)));
info->Set(NanNew<String>("tileOverlap"), NanNew<Uint32>(static_cast<uint32_t>(baton->tileOverlap)));
}
if (baton->bufferOutLength > 0) { if (baton->bufferOutLength > 0) {
// Copy data to new Buffer // Copy data to new Buffer
argv[1] = NanNewBufferHandle(static_cast<char*>(baton->bufferOut), baton->bufferOutLength); argv[1] = NanNewBufferHandle(static_cast<char*>(baton->bufferOut), baton->bufferOutLength);

View File

@ -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) { if (sharp.format.tiff.input.buffer) {
it('Load TIFF from Buffer', function(done) { it('Load TIFF from Buffer', function(done) {
var inputTiffBuffer = fs.readFileSync(fixtures.inputTiff); 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) { if (sharp.format.raw.output.buffer) {
describe('Ouput raw, uncompressed image data', function() { describe('Ouput raw, uncompressed image data', function() {
it('1 channel greyscale image', function(done) { 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();
});
});
});
}
}); });

View File

@ -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) { it('File in, Promise out', function(done) {
sharp(fixtures.inputJpg).metadata().then(function(metadata) { sharp(fixtures.inputJpg).metadata().then(function(metadata) {
assert.strictEqual('jpeg', metadata.format); assert.strictEqual('jpeg', metadata.format);

204
test/unit/tile.js Executable file
View File

@ -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);
});
});
});
});
}
});