mirror of
https://github.com/lovell/sharp.git
synced 2025-07-13 04:20:12 +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.
|
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.
|
* `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.
|
* `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
|
```javascript
|
||||||
sharp('input.tiff').tile(256).toFile('output.dzi', function(err, info) {
|
sharp('input.tiff')
|
||||||
// The output.dzi file is the XML format Deep Zoom definition
|
.tile({
|
||||||
// The output_files directory contains 256x256 pixel tiles grouped by zoom level
|
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()
|
#### withoutChromaSubsampling()
|
||||||
|
@ -6,6 +6,10 @@
|
|||||||
[#128](https://github.com/lovell/sharp/issues/128)
|
[#128](https://github.com/lovell/sharp/issues/128)
|
||||||
[@blowsie](https://github.com/blowsie)
|
[@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.
|
* Improvements to overlayWith: differing sizes/formats, gravity, buffer input.
|
||||||
[#239](https://github.com/lovell/sharp/issues/239)
|
[#239](https://github.com/lovell/sharp/issues/239)
|
||||||
[@chrisriley](https://github.com/chrisriley)
|
[@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) {
|
var inRange = function(val, min, max) {
|
||||||
return val >= min && val <= max;
|
return val >= min && val <= max;
|
||||||
};
|
};
|
||||||
|
var contains = function(val, list) {
|
||||||
|
return list.indexOf(val) !== -1;
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Set input-related options
|
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) {
|
Sharp.prototype.tile = function(tile) {
|
||||||
// Size of square tiles, in pixels
|
if (isObject(tile)) {
|
||||||
if (typeof size !== 'undefined' && size !== null) {
|
// Size of square tiles, in pixels
|
||||||
if (!Number.isNaN(size) && size % 1 === 0 && size >= 1 && size <= 8192) {
|
if (isDefined(tile.size)) {
|
||||||
this.options.tileSize = size;
|
if (isInteger(tile.size) && inRange(tile.size, 1, 8192)) {
|
||||||
} else {
|
this.options.tileSize = tile.size;
|
||||||
throw new Error('Invalid tile size (1 to 8192) ' + size);
|
} else {
|
||||||
}
|
throw new Error('Invalid tile size (1 to 8192) ' + tile.size);
|
||||||
}
|
}
|
||||||
// Overlap of tiles, in pixels
|
}
|
||||||
if (typeof overlap !== 'undefined' && overlap !== null) {
|
// Overlap of tiles, in pixels
|
||||||
if (!Number.isNaN(overlap) && overlap % 1 === 0 && overlap >= 0 && overlap <= 8192) {
|
if (isDefined(tile.overlap)) {
|
||||||
if (overlap > this.options.tileSize) {
|
if (isInteger(tile.overlap) && inRange(tile.overlap, 0, 8192)) {
|
||||||
throw new Error('Tile overlap ' + overlap + ' cannot be larger than tile size ' + this.options.tileSize);
|
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;
|
return this;
|
||||||
|
@ -766,6 +766,7 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
->set("strip", !baton->withMetadata)
|
->set("strip", !baton->withMetadata)
|
||||||
->set("tile_size", baton->tileSize)
|
->set("tile_size", baton->tileSize)
|
||||||
->set("overlap", baton->tileOverlap)
|
->set("overlap", baton->tileOverlap)
|
||||||
|
->set("layout", baton->tileLayout)
|
||||||
);
|
);
|
||||||
baton->formatOut = "dz";
|
baton->formatOut = "dz";
|
||||||
} else {
|
} else {
|
||||||
@ -1030,8 +1031,18 @@ NAN_METHOD(pipeline) {
|
|||||||
// Output
|
// Output
|
||||||
baton->formatOut = attrAsStr(options, "formatOut");
|
baton->formatOut = attrAsStr(options, "formatOut");
|
||||||
baton->fileOut = attrAsStr(options, "fileOut");
|
baton->fileOut = attrAsStr(options, "fileOut");
|
||||||
|
// Tile output
|
||||||
baton->tileSize = attrAs<int32_t>(options, "tileSize");
|
baton->tileSize = attrAs<int32_t>(options, "tileSize");
|
||||||
baton->tileOverlap = attrAs<int32_t>(options, "tileOverlap");
|
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
|
// Function to notify of queue length changes
|
||||||
Callback *queueListener = new Callback(
|
Callback *queueListener = new Callback(
|
||||||
Get(options, New("queueListener").ToLocalChecked()).ToLocalChecked().As<Function>()
|
Get(options, New("queueListener").ToLocalChecked()).ToLocalChecked().As<Function>()
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
#ifndef SRC_PIPELINE_H_
|
#ifndef SRC_PIPELINE_H_
|
||||||
#define SRC_PIPELINE_H_
|
#define SRC_PIPELINE_H_
|
||||||
|
|
||||||
|
#include <vips/vips8>
|
||||||
|
|
||||||
#include "nan.h"
|
#include "nan.h"
|
||||||
|
|
||||||
NAN_METHOD(pipeline);
|
NAN_METHOD(pipeline);
|
||||||
@ -79,6 +81,7 @@ struct PipelineBaton {
|
|||||||
int withMetadataOrientation;
|
int withMetadataOrientation;
|
||||||
int tileSize;
|
int tileSize;
|
||||||
int tileOverlap;
|
int tileOverlap;
|
||||||
|
VipsForeignDzLayout tileLayout;
|
||||||
|
|
||||||
PipelineBaton():
|
PipelineBaton():
|
||||||
bufferInLength(0),
|
bufferInLength(0),
|
||||||
@ -126,7 +129,8 @@ struct PipelineBaton {
|
|||||||
withMetadata(false),
|
withMetadata(false),
|
||||||
withMetadataOrientation(-1),
|
withMetadataOrientation(-1),
|
||||||
tileSize(256),
|
tileSize(256),
|
||||||
tileOverlap(0) {
|
tileOverlap(0),
|
||||||
|
tileLayout(VIPS_FOREIGN_DZ_LAYOUT_DZ) {
|
||||||
background[0] = 0.0;
|
background[0] = 0.0;
|
||||||
background[1] = 0.0;
|
background[1] = 0.0;
|
||||||
background[2] = 0.0;
|
background[2] = 0.0;
|
||||||
|
@ -47,156 +47,149 @@ var assertDeepZoomTiles = function(directory, expectedSize, expectedLevels, done
|
|||||||
|
|
||||||
describe('Tile', function() {
|
describe('Tile', function() {
|
||||||
|
|
||||||
describe('Invalid tile values', function() {
|
it('Valid size values pass', function() {
|
||||||
it('size - NaN', function(done) {
|
[1, 8192].forEach(function(size) {
|
||||||
var isValid = true;
|
assert.doesNotThrow(function() {
|
||||||
try {
|
sharp().tile({
|
||||||
sharp().tile('zoinks');
|
size: size
|
||||||
} catch (err) {
|
});
|
||||||
isValid = false;
|
});
|
||||||
}
|
|
||||||
assert.strictEqual(false, isValid);
|
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('size - float', function(done) {
|
it('Invalid size values fail', function() {
|
||||||
var isValid = true;
|
['zoinks', 1.1, -1, 0, 8193].forEach(function(size) {
|
||||||
try {
|
assert.throws(function() {
|
||||||
sharp().tile(1.1);
|
sharp().tile({
|
||||||
} catch (err) {
|
size: size
|
||||||
isValid = false;
|
});
|
||||||
}
|
});
|
||||||
assert.strictEqual(false, isValid);
|
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('size - negative', function(done) {
|
it('Valid overlap values pass', function() {
|
||||||
var isValid = true;
|
[0, 8192].forEach(function(overlap) {
|
||||||
try {
|
assert.doesNotThrow(function() {
|
||||||
sharp().tile(-1);
|
sharp().tile({
|
||||||
} catch (err) {
|
size: 8192,
|
||||||
isValid = false;
|
overlap: overlap
|
||||||
}
|
});
|
||||||
assert.strictEqual(false, isValid);
|
});
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('size - zero', function(done) {
|
it('Invalid overlap values fail', function() {
|
||||||
var isValid = true;
|
['zoinks', 1.1, -1, 8193].forEach(function(overlap) {
|
||||||
try {
|
assert.throws(function() {
|
||||||
sharp().tile(0);
|
sharp().tile({
|
||||||
} catch (err) {
|
overlap: overlap
|
||||||
isValid = false;
|
});
|
||||||
}
|
});
|
||||||
assert.strictEqual(false, isValid);
|
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('size - too large', function(done) {
|
it('Valid layout values pass', function() {
|
||||||
var isValid = true;
|
['dz', 'google', 'zoomify'].forEach(function(layout) {
|
||||||
try {
|
assert.doesNotThrow(function() {
|
||||||
sharp().tile(8193);
|
sharp().tile({
|
||||||
} catch (err) {
|
layout: layout
|
||||||
isValid = false;
|
});
|
||||||
}
|
});
|
||||||
assert.strictEqual(false, isValid);
|
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('overlap - NaN', function(done) {
|
it('Invalid layout values fail', function() {
|
||||||
var isValid = true;
|
['zoinks', 1].forEach(function(layout) {
|
||||||
try {
|
assert.throws(function() {
|
||||||
sharp().tile(null, 'zoinks');
|
sharp().tile({
|
||||||
} catch (err) {
|
layout: layout
|
||||||
isValid = false;
|
});
|
||||||
}
|
});
|
||||||
assert.strictEqual(false, isValid);
|
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('overlap - float', function(done) {
|
it('Prevent larger overlap than default size', function() {
|
||||||
var isValid = true;
|
assert.throws(function() {
|
||||||
try {
|
sharp().tile({overlap: 257});
|
||||||
sharp().tile(null, 1.1);
|
|
||||||
} catch (err) {
|
|
||||||
isValid = false;
|
|
||||||
}
|
|
||||||
assert.strictEqual(false, isValid);
|
|
||||||
done();
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('overlap - negative', function(done) {
|
it('Prevent larger overlap than provided size', function() {
|
||||||
var isValid = true;
|
assert.throws(function() {
|
||||||
try {
|
sharp().tile({size: 512, overlap: 513});
|
||||||
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) {
|
if (sharp.format.dz.output.file) {
|
||||||
describe('Deep Zoom output', function() {
|
|
||||||
|
|
||||||
it('Tile size - 256px default', function(done) {
|
it('Deep Zoom layout', function(done) {
|
||||||
var directory = fixtures.path('output.256_files');
|
var directory = fixtures.path('output.dz_files');
|
||||||
rimraf(directory, function() {
|
rimraf(directory, function() {
|
||||||
sharp(fixtures.inputJpg).toFile(fixtures.path('output.256.dzi'), function(err, info) {
|
sharp(fixtures.inputJpg)
|
||||||
|
.toFile(fixtures.path('output.dz.dzi'), function(err, info) {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
assert.strictEqual('dz', info.format);
|
assert.strictEqual('dz', info.format);
|
||||||
assertDeepZoomTiles(directory, 256, 13, done);
|
assertDeepZoomTiles(directory, 256, 13, done);
|
||||||
});
|
});
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('Tile size/overlap - 512/16px', function(done) {
|
it('Deep Zoom layout with custom size+overlap', function(done) {
|
||||||
var directory = fixtures.path('output.512_files');
|
var directory = fixtures.path('output.dz.512_files');
|
||||||
rimraf(directory, function() {
|
rimraf(directory, function() {
|
||||||
sharp(fixtures.inputJpg).tile(512, 16).toFile(fixtures.path('output.512.dzi'), function(err, info) {
|
sharp(fixtures.inputJpg)
|
||||||
|
.tile({
|
||||||
|
size: 512,
|
||||||
|
overlap: 16
|
||||||
|
})
|
||||||
|
.toFile(fixtures.path('output.dz.512.dzi'), function(err, info) {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
assert.strictEqual('dz', info.format);
|
assert.strictEqual('dz', info.format);
|
||||||
assertDeepZoomTiles(directory, 512 + 2 * 16, 13, done);
|
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