mirror of
https://github.com/lovell/sharp.git
synced 2025-07-12 20:10:13 +02:00
Add support for Zoomify and Google tile layouts
Breaks existing tile API
This commit is contained in:
parent
f950294f70
commit
38ddb3b866
19
docs/api.md
19
docs/api.md
@ -510,18 +510,25 @@ This has no effect if the input image does not have an EXIF `Orientation` tag.
|
||||
|
||||
The default behaviour, when `withMetadata` is not used, is to strip all metadata and convert to the device-independent sRGB colour space.
|
||||
|
||||
#### tile([size], [overlap])
|
||||
#### tile(options)
|
||||
|
||||
The size and overlap, in pixels, of square Deep Zoom image pyramid tiles.
|
||||
The size, overlap and directory layout to use when generating square Deep Zoom image pyramid tiles.
|
||||
|
||||
`options` is an Object with one or more of the following attributes:
|
||||
|
||||
* `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.
|
||||
* `layout` is a String, with value `dz`, `zoomify` or `google`. The default value is `dz`.
|
||||
|
||||
```javascript
|
||||
sharp('input.tiff').tile(256).toFile('output.dzi', function(err, info) {
|
||||
// The output.dzi file is the XML format Deep Zoom definition
|
||||
// The output_files directory contains 256x256 pixel tiles grouped by zoom level
|
||||
});
|
||||
sharp('input.tiff')
|
||||
.tile({
|
||||
size: 512
|
||||
})
|
||||
.toFile('output.dzi', function(err, info) {
|
||||
// output.dzi is the Deep Zoom XML definition
|
||||
// output_files contains 512x512 tiles grouped by zoom level
|
||||
});
|
||||
```
|
||||
|
||||
#### withoutChromaSubsampling()
|
||||
|
@ -6,6 +6,10 @@
|
||||
[#128](https://github.com/lovell/sharp/issues/128)
|
||||
[@blowsie](https://github.com/blowsie)
|
||||
|
||||
* Add support for Zoomify and Google tile layouts. Breaks existing tile API.
|
||||
[#223](https://github.com/lovell/sharp/issues/223)
|
||||
[@bdunnette](https://github.com/bdunnette)
|
||||
|
||||
* Improvements to overlayWith: differing sizes/formats, gravity, buffer input.
|
||||
[#239](https://github.com/lovell/sharp/issues/239)
|
||||
[@chrisriley](https://github.com/chrisriley)
|
||||
|
49
index.js
49
index.js
@ -166,6 +166,9 @@ var isInteger = function(val) {
|
||||
var inRange = function(val, min, max) {
|
||||
return val >= min && val <= max;
|
||||
};
|
||||
var contains = function(val, list) {
|
||||
return list.indexOf(val) !== -1;
|
||||
};
|
||||
|
||||
/*
|
||||
Set input-related options
|
||||
@ -629,26 +632,36 @@ Sharp.prototype.withMetadata = function(withMetadata) {
|
||||
};
|
||||
|
||||
/*
|
||||
Tile size and overlap for Deep Zoom output
|
||||
Tile-based deep zoom output options: size, overlap, layout
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
// 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);
|
||||
Sharp.prototype.tile = function(tile) {
|
||||
if (isObject(tile)) {
|
||||
// Size of square tiles, in pixels
|
||||
if (isDefined(tile.size)) {
|
||||
if (isInteger(tile.size) && inRange(tile.size, 1, 8192)) {
|
||||
this.options.tileSize = tile.size;
|
||||
} else {
|
||||
throw new Error('Invalid tile size (1 to 8192) ' + tile.size);
|
||||
}
|
||||
}
|
||||
// Overlap of tiles, in pixels
|
||||
if (isDefined(tile.overlap)) {
|
||||
if (isInteger(tile.overlap) && inRange(tile.overlap, 0, 8192)) {
|
||||
if (tile.overlap > this.options.tileSize) {
|
||||
throw new Error('Tile overlap ' + tile.overlap + ' cannot be larger than tile size ' + this.options.tileSize);
|
||||
}
|
||||
this.options.tileOverlap = tile.overlap;
|
||||
} else {
|
||||
throw new Error('Invalid tile overlap (0 to 8192) ' + tile.overlap);
|
||||
}
|
||||
}
|
||||
// Layout
|
||||
if (isDefined(tile.layout)) {
|
||||
if (isString(tile.layout) && contains(tile.layout, ['dz', 'google', 'zoomify'])) {
|
||||
this.options.tileLayout = tile.layout;
|
||||
} else {
|
||||
throw new Error('Invalid tile layout ' + tile.layout);
|
||||
}
|
||||
this.options.tileOverlap = overlap;
|
||||
} else {
|
||||
throw new Error('Invalid tile overlap (0 to 8192) ' + overlap);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
|
@ -766,6 +766,7 @@ class PipelineWorker : public AsyncWorker {
|
||||
->set("strip", !baton->withMetadata)
|
||||
->set("tile_size", baton->tileSize)
|
||||
->set("overlap", baton->tileOverlap)
|
||||
->set("layout", baton->tileLayout)
|
||||
);
|
||||
baton->formatOut = "dz";
|
||||
} else {
|
||||
@ -1030,8 +1031,18 @@ NAN_METHOD(pipeline) {
|
||||
// Output
|
||||
baton->formatOut = attrAsStr(options, "formatOut");
|
||||
baton->fileOut = attrAsStr(options, "fileOut");
|
||||
// Tile output
|
||||
baton->tileSize = attrAs<int32_t>(options, "tileSize");
|
||||
baton->tileOverlap = attrAs<int32_t>(options, "tileOverlap");
|
||||
std::string tileLayout = attrAsStr(options, "tileLayout");
|
||||
if (tileLayout == "google") {
|
||||
baton->tileLayout = VIPS_FOREIGN_DZ_LAYOUT_GOOGLE;
|
||||
} else if (tileLayout == "zoomify") {
|
||||
baton->tileLayout = VIPS_FOREIGN_DZ_LAYOUT_ZOOMIFY;
|
||||
} else {
|
||||
baton->tileLayout = VIPS_FOREIGN_DZ_LAYOUT_DZ;
|
||||
}
|
||||
|
||||
// Function to notify of queue length changes
|
||||
Callback *queueListener = new Callback(
|
||||
Get(options, New("queueListener").ToLocalChecked()).ToLocalChecked().As<Function>()
|
||||
|
@ -1,6 +1,8 @@
|
||||
#ifndef SRC_PIPELINE_H_
|
||||
#define SRC_PIPELINE_H_
|
||||
|
||||
#include <vips/vips8>
|
||||
|
||||
#include "nan.h"
|
||||
|
||||
NAN_METHOD(pipeline);
|
||||
@ -79,6 +81,7 @@ struct PipelineBaton {
|
||||
int withMetadataOrientation;
|
||||
int tileSize;
|
||||
int tileOverlap;
|
||||
VipsForeignDzLayout tileLayout;
|
||||
|
||||
PipelineBaton():
|
||||
bufferInLength(0),
|
||||
@ -126,7 +129,8 @@ struct PipelineBaton {
|
||||
withMetadata(false),
|
||||
withMetadataOrientation(-1),
|
||||
tileSize(256),
|
||||
tileOverlap(0) {
|
||||
tileOverlap(0),
|
||||
tileLayout(VIPS_FOREIGN_DZ_LAYOUT_DZ) {
|
||||
background[0] = 0.0;
|
||||
background[1] = 0.0;
|
||||
background[2] = 0.0;
|
||||
|
@ -47,156 +47,149 @@ var assertDeepZoomTiles = function(directory, expectedSize, expectedLevels, 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('Valid size values pass', function() {
|
||||
[1, 8192].forEach(function(size) {
|
||||
assert.doesNotThrow(function() {
|
||||
sharp().tile({
|
||||
size: size
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('size - float', function(done) {
|
||||
var isValid = true;
|
||||
try {
|
||||
sharp().tile(1.1);
|
||||
} catch (err) {
|
||||
isValid = false;
|
||||
}
|
||||
assert.strictEqual(false, isValid);
|
||||
done();
|
||||
it('Invalid size values fail', function() {
|
||||
['zoinks', 1.1, -1, 0, 8193].forEach(function(size) {
|
||||
assert.throws(function() {
|
||||
sharp().tile({
|
||||
size: size
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('size - negative', function(done) {
|
||||
var isValid = true;
|
||||
try {
|
||||
sharp().tile(-1);
|
||||
} catch (err) {
|
||||
isValid = false;
|
||||
}
|
||||
assert.strictEqual(false, isValid);
|
||||
done();
|
||||
it('Valid overlap values pass', function() {
|
||||
[0, 8192].forEach(function(overlap) {
|
||||
assert.doesNotThrow(function() {
|
||||
sharp().tile({
|
||||
size: 8192,
|
||||
overlap: overlap
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('size - zero', function(done) {
|
||||
var isValid = true;
|
||||
try {
|
||||
sharp().tile(0);
|
||||
} catch (err) {
|
||||
isValid = false;
|
||||
}
|
||||
assert.strictEqual(false, isValid);
|
||||
done();
|
||||
it('Invalid overlap values fail', function() {
|
||||
['zoinks', 1.1, -1, 8193].forEach(function(overlap) {
|
||||
assert.throws(function() {
|
||||
sharp().tile({
|
||||
overlap: overlap
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('size - too large', function(done) {
|
||||
var isValid = true;
|
||||
try {
|
||||
sharp().tile(8193);
|
||||
} catch (err) {
|
||||
isValid = false;
|
||||
}
|
||||
assert.strictEqual(false, isValid);
|
||||
done();
|
||||
it('Valid layout values pass', function() {
|
||||
['dz', 'google', 'zoomify'].forEach(function(layout) {
|
||||
assert.doesNotThrow(function() {
|
||||
sharp().tile({
|
||||
layout: layout
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('overlap - NaN', function(done) {
|
||||
var isValid = true;
|
||||
try {
|
||||
sharp().tile(null, 'zoinks');
|
||||
} catch (err) {
|
||||
isValid = false;
|
||||
}
|
||||
assert.strictEqual(false, isValid);
|
||||
done();
|
||||
it('Invalid layout values fail', function() {
|
||||
['zoinks', 1].forEach(function(layout) {
|
||||
assert.throws(function() {
|
||||
sharp().tile({
|
||||
layout: layout
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('overlap - float', function(done) {
|
||||
var isValid = true;
|
||||
try {
|
||||
sharp().tile(null, 1.1);
|
||||
} catch (err) {
|
||||
isValid = false;
|
||||
}
|
||||
assert.strictEqual(false, isValid);
|
||||
done();
|
||||
it('Prevent larger overlap than default size', function() {
|
||||
assert.throws(function() {
|
||||
sharp().tile({overlap: 257});
|
||||
});
|
||||
});
|
||||
|
||||
it('overlap - negative', function(done) {
|
||||
var isValid = true;
|
||||
try {
|
||||
sharp().tile(null, -1);
|
||||
} catch (err) {
|
||||
isValid = false;
|
||||
}
|
||||
assert.strictEqual(false, isValid);
|
||||
done();
|
||||
it('Prevent larger overlap than provided size', function() {
|
||||
assert.throws(function() {
|
||||
sharp().tile({size: 512, overlap: 513});
|
||||
});
|
||||
|
||||
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('output.256_files');
|
||||
rimraf(directory, function() {
|
||||
sharp(fixtures.inputJpg).toFile(fixtures.path('output.256.dzi'), function(err, info) {
|
||||
it('Deep Zoom layout', function(done) {
|
||||
var directory = fixtures.path('output.dz_files');
|
||||
rimraf(directory, function() {
|
||||
sharp(fixtures.inputJpg)
|
||||
.toFile(fixtures.path('output.dz.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('output.512_files');
|
||||
rimraf(directory, function() {
|
||||
sharp(fixtures.inputJpg).tile(512, 16).toFile(fixtures.path('output.512.dzi'), function(err, info) {
|
||||
it('Deep Zoom layout with custom size+overlap', function(done) {
|
||||
var directory = fixtures.path('output.dz.512_files');
|
||||
rimraf(directory, function() {
|
||||
sharp(fixtures.inputJpg)
|
||||
.tile({
|
||||
size: 512,
|
||||
overlap: 16
|
||||
})
|
||||
.toFile(fixtures.path('output.dz.512.dzi'), function(err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('dz', info.format);
|
||||
assertDeepZoomTiles(directory, 512 + 2 * 16, 13, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('Zoomify layout', function(done) {
|
||||
var directory = fixtures.path('output.zoomify');
|
||||
rimraf(directory, function() {
|
||||
sharp(fixtures.inputJpg)
|
||||
.tile({
|
||||
layout: 'zoomify'
|
||||
})
|
||||
.toFile(fixtures.path('output.zoomify.dzi'), function(err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('dz', info.format);
|
||||
fs.stat(path.join(directory, 'ImageProperties.xml'), function(err, stat) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, stat.isFile());
|
||||
assert.strictEqual(true, stat.size > 0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Google layout', function(done) {
|
||||
var directory = fixtures.path('output.google');
|
||||
rimraf(directory, function() {
|
||||
sharp(fixtures.inputJpg)
|
||||
.tile({
|
||||
layout: 'google'
|
||||
})
|
||||
.toFile(fixtures.path('output.google.dzi'), function(err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('dz', info.format);
|
||||
fs.stat(path.join(directory, '0', '0', '0.jpg'), function(err, stat) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, stat.isFile());
|
||||
assert.strictEqual(true, stat.size > 0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user