mirror of
https://github.com/lovell/sharp.git
synced 2025-07-09 02:30:12 +02:00
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:
parent
2d1e6f2644
commit
5781a23a4d
2
.gitignore
vendored
2
.gitignore
vendored
@ -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
|
||||
|
19
README.md
19
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])
|
||||
|
46
index.js
46
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
|
||||
|
13
package.json
Normal file → Executable file
13
package.json
Normal file → Executable file
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "sharp",
|
||||
"version": "0.9.3",
|
||||
"version": "0.10.0",
|
||||
"author": "Lovell Fuller <npm@lovell.info>",
|
||||
"contributors": [
|
||||
"Pierre Inglebert <pierre.inglebert@gmail.com>",
|
||||
@ -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": {
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -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?
|
||||
|
@ -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
|
||||
|
@ -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<String>("width"), NanNew<Uint32>(static_cast<uint32_t>(width)));
|
||||
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) {
|
||||
// Copy data to new Buffer
|
||||
argv[1] = NanNewBufferHandle(static_cast<char*>(baton->bufferOut), baton->bufferOutLength);
|
||||
|
113
test/unit/io.js
113
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -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);
|
||||
|
204
test/unit/tile.js
Executable file
204
test/unit/tile.js
Executable 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user