mirror of
https://github.com/lovell/sharp.git
synced 2026-02-05 14:16:17 +01:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
531a0402f7 | ||
|
|
cb10f9a9c8 | ||
|
|
c808139b02 | ||
|
|
e0d58266be | ||
|
|
1b7c5816fc | ||
|
|
b224874332 | ||
|
|
ef61da3051 | ||
|
|
f214269aa1 | ||
|
|
6bc2ea8dc7 | ||
|
|
71fb839e2b | ||
|
|
8c9c070caf |
@@ -13,3 +13,4 @@ mkdocs.yml
|
|||||||
lib
|
lib
|
||||||
include
|
include
|
||||||
packaging
|
packaging
|
||||||
|
preinstall.sh
|
||||||
|
|||||||
@@ -167,6 +167,7 @@
|
|||||||
'CLANG_CXX_LIBRARY': 'libc++',
|
'CLANG_CXX_LIBRARY': 'libc++',
|
||||||
'MACOSX_DEPLOYMENT_TARGET': '10.7',
|
'MACOSX_DEPLOYMENT_TARGET': '10.7',
|
||||||
'GCC_ENABLE_CPP_EXCEPTIONS': 'YES',
|
'GCC_ENABLE_CPP_EXCEPTIONS': 'YES',
|
||||||
|
'GCC_ENABLE_CPP_RTTI': 'YES',
|
||||||
'OTHER_CPLUSPLUSFLAGS': [
|
'OTHER_CPLUSPLUSFLAGS': [
|
||||||
'-fexceptions',
|
'-fexceptions',
|
||||||
'-Wall',
|
'-Wall',
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ var tmp = require('os').tmpdir();
|
|||||||
|
|
||||||
var distBaseUrl = 'https://dl.bintray.com/lovell/sharp/';
|
var distBaseUrl = 'https://dl.bintray.com/lovell/sharp/';
|
||||||
|
|
||||||
|
// Use NPM-provided environment variable where available, falling back to require-based method for Electron
|
||||||
|
var minimumLibvipsVersion = process.env.npm_package_config_libvips || require('./package.json').config.libvips;
|
||||||
|
|
||||||
var vipsHeaderPath = path.join(__dirname, 'include', 'vips', 'vips.h');
|
var vipsHeaderPath = path.join(__dirname, 'include', 'vips', 'vips.h');
|
||||||
|
|
||||||
// -- Helpers
|
// -- Helpers
|
||||||
@@ -75,7 +78,7 @@ module.exports.download_vips = function() {
|
|||||||
}
|
}
|
||||||
// Arch/platform-specific .tar.gz
|
// Arch/platform-specific .tar.gz
|
||||||
var platform = (process.arch === 'arm') ? 'arm' : process.platform.substr(0, 3);
|
var platform = (process.arch === 'arm') ? 'arm' : process.platform.substr(0, 3);
|
||||||
var tarFilename = ['libvips', process.env.npm_package_config_libvips, platform].join('-') + '.tar.gz';
|
var tarFilename = ['libvips', minimumLibvipsVersion, platform].join('-') + '.tar.gz';
|
||||||
var tarPath = path.join(__dirname, 'packaging', tarFilename);
|
var tarPath = path.join(__dirname, 'packaging', tarFilename);
|
||||||
if (isFile(tarPath)) {
|
if (isFile(tarPath)) {
|
||||||
unpack(tarPath);
|
unpack(tarPath);
|
||||||
@@ -114,13 +117,13 @@ module.exports.use_global_vips = function() {
|
|||||||
if (globalVipsVersion) {
|
if (globalVipsVersion) {
|
||||||
useGlobalVips = semver.gte(
|
useGlobalVips = semver.gte(
|
||||||
globalVipsVersion,
|
globalVipsVersion,
|
||||||
process.env.npm_package_config_libvips
|
minimumLibvipsVersion
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (process.platform === 'darwin' && !useGlobalVips) {
|
if (process.platform === 'darwin' && !useGlobalVips) {
|
||||||
if (globalVipsVersion) {
|
if (globalVipsVersion) {
|
||||||
error(
|
error(
|
||||||
'Found libvips ' + globalVipsVersion + ' but require ' + process.env.npm_package_config_libvips +
|
'Found libvips ' + globalVipsVersion + ' but require ' + minimumLibvipsVersion +
|
||||||
'\nPlease upgrade libvips by running: brew update && brew upgrade'
|
'\nPlease upgrade libvips by running: brew update && brew upgrade'
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -113,7 +113,8 @@ This will reduce memory usage and can improve performance on some systems.
|
|||||||
|
|
||||||
Do not process input images where the number of pixels (width * height) exceeds this limit.
|
Do not process input images where the number of pixels (width * height) exceeds this limit.
|
||||||
|
|
||||||
`pixels` is the integral Number of pixels, with a value between 1 and the default 268402689 (0x3FFF * 0x3FFF).
|
`pixels` is either an integral Number of pixels, with a value between 1 and the default 268402689 (0x3FFF * 0x3FFF) or
|
||||||
|
a boolean. `false` will disable checking while `true` will revert to the default limit.
|
||||||
|
|
||||||
### Resizing
|
### Resizing
|
||||||
|
|
||||||
@@ -526,14 +527,17 @@ The default behaviour, when `withMetadata` is not used, is to strip all metadata
|
|||||||
|
|
||||||
#### tile(options)
|
#### tile(options)
|
||||||
|
|
||||||
The size, overlap and directory layout to use when generating square Deep Zoom image pyramid tiles.
|
The size, overlap, container 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:
|
`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.
|
||||||
|
* `container` is a String, with value `fs` or `zip`. The default value is `fs`.
|
||||||
* `layout` is a String, with value `dz`, `zoomify` or `google`. The default value is `dz`.
|
* `layout` is a String, with value `dz`, `zoomify` or `google`. The default value is `dz`.
|
||||||
|
|
||||||
|
You can also use the file extension .zip or .szi to write to a ZIP container instead of the filesystem.
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
sharp('input.tiff')
|
sharp('input.tiff')
|
||||||
.tile({
|
.tile({
|
||||||
|
|||||||
@@ -4,6 +4,30 @@
|
|||||||
|
|
||||||
Requires libvips v8.2.3
|
Requires libvips v8.2.3
|
||||||
|
|
||||||
|
#### v0.14.1 - 16<sup>th</sup> April 2016
|
||||||
|
|
||||||
|
* Allow removal of limitation on input pixel count via limitInputPixels. Use with care.
|
||||||
|
[#250](https://github.com/lovell/sharp/issues/250)
|
||||||
|
[#316](https://github.com/lovell/sharp/pull/316)
|
||||||
|
[@anandthakker](https://github.com/anandthakker)
|
||||||
|
[@kentongray](https://github.com/kentongray)
|
||||||
|
|
||||||
|
* Use final output image for metadata passed to callback.
|
||||||
|
[#399](https://github.com/lovell/sharp/pull/399)
|
||||||
|
[@salzhrani](https://github.com/salzhrani)
|
||||||
|
|
||||||
|
* Add support for writing tiled images to a zip container.
|
||||||
|
[#402](https://github.com/lovell/sharp/pull/402)
|
||||||
|
[@felixbuenemann](https://github.com/felixbuenemann)
|
||||||
|
|
||||||
|
* Allow use of embed with 1 and 2 channel images.
|
||||||
|
[#411](https://github.com/lovell/sharp/issues/411)
|
||||||
|
[@janaz](https://github.com/janaz)
|
||||||
|
|
||||||
|
* Improve Electron compatibility by allowing node-gyp rebuilds without npm.
|
||||||
|
[#412](https://github.com/lovell/sharp/issues/412)
|
||||||
|
[@nouh](https://github.com/nouh)
|
||||||
|
|
||||||
#### v0.14.0 - 2<sup>nd</sup> April 2016
|
#### v0.14.0 - 2<sup>nd</sup> April 2016
|
||||||
|
|
||||||
* Add ability to extend (pad) the edges of an image.
|
* Add ability to extend (pad) the edges of an image.
|
||||||
|
|||||||
@@ -52,9 +52,15 @@ For Linux-based operating systems such as Alpine that use musl libc,
|
|||||||
the smaller stack size means libvips' cache should be disabled
|
the smaller stack size means libvips' cache should be disabled
|
||||||
via `sharp.cache(false)` to avoid a stack overflow.
|
via `sharp.cache(false)` to avoid a stack overflow.
|
||||||
|
|
||||||
|
Beware of Linux OS upgrades that introduce v5.1+ of the `g++` compiler due to
|
||||||
|
[changes](https://gcc.gnu.org/onlinedocs/libstdc++/manual/using_dual_abi.html)
|
||||||
|
in the C++11 ABI.
|
||||||
|
This module assumes the previous behaviour, which can be enforced by setting the
|
||||||
|
`_GLIBCXX_USE_CXX11_ABI=0` environment variable at libvips' compile time.
|
||||||
|
|
||||||
### Mac OS
|
### Mac OS
|
||||||
|
|
||||||
[](https://travis-ci.org/lovell/sharp-osx-ci)
|
[](https://travis-ci.org/lovell/sharp)
|
||||||
|
|
||||||
libvips must be installed before `npm install` is run.
|
libvips must be installed before `npm install` is run.
|
||||||
This can be achieved via homebrew:
|
This can be achieved via homebrew:
|
||||||
|
|||||||
19
index.js
19
index.js
@@ -660,6 +660,14 @@ Sharp.prototype.tile = function(tile) {
|
|||||||
throw new Error('Invalid tile overlap (0 to 8192) ' + tile.overlap);
|
throw new Error('Invalid tile overlap (0 to 8192) ' + tile.overlap);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Container
|
||||||
|
if (isDefined(tile.container)) {
|
||||||
|
if (isString(tile.container) && contains(tile.container, ['fs', 'zip'])) {
|
||||||
|
this.options.tileContainer = tile.container;
|
||||||
|
} else {
|
||||||
|
throw new Error('Invalid tile container ' + tile.container);
|
||||||
|
}
|
||||||
|
}
|
||||||
// Layout
|
// Layout
|
||||||
if (isDefined(tile.layout)) {
|
if (isDefined(tile.layout)) {
|
||||||
if (isString(tile.layout) && contains(tile.layout, ['dz', 'google', 'zoomify'])) {
|
if (isString(tile.layout) && contains(tile.layout, ['dz', 'google', 'zoomify'])) {
|
||||||
@@ -722,10 +730,17 @@ Sharp.prototype.resize = function(width, height) {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
Limit the total number of pixels for input images
|
Limit the total number of pixels for input images
|
||||||
Assumes the image dimensions contained in the file header can be trusted
|
Assumes the image dimensions contained in the file header can be trusted.
|
||||||
|
Alternatively can use boolean to disable or reset to default (maximum pixels)
|
||||||
*/
|
*/
|
||||||
Sharp.prototype.limitInputPixels = function(limit) {
|
Sharp.prototype.limitInputPixels = function(limit) {
|
||||||
if (typeof limit === 'number' && !Number.isNaN(limit) && limit % 1 === 0 && limit > 0) {
|
//if we pass in false we represent the integer as 0 to disable
|
||||||
|
if(limit === false) {
|
||||||
|
limit = 0;
|
||||||
|
} else if(limit === true) {
|
||||||
|
limit = maximum.pixels;
|
||||||
|
}
|
||||||
|
if (typeof limit === 'number' && !Number.isNaN(limit) && limit % 1 === 0 && limit >= 0) {
|
||||||
this.options.limitInputPixels = limit;
|
this.options.limitInputPixels = limit;
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Invalid pixel limit (1 to ' + maximum.pixels + ') ' + limit);
|
throw new Error('Invalid pixel limit (1 to ' + maximum.pixels + ') ' + limit);
|
||||||
|
|||||||
18
package.json
18
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "sharp",
|
"name": "sharp",
|
||||||
"version": "0.14.0",
|
"version": "0.14.1",
|
||||||
"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>",
|
||||||
@@ -19,13 +19,16 @@
|
|||||||
"Bernhard K. Weisshuhn <bkw@codingforce.com>",
|
"Bernhard K. Weisshuhn <bkw@codingforce.com>",
|
||||||
"Chris Riley <criley@primedia.com>",
|
"Chris Riley <criley@primedia.com>",
|
||||||
"David Carley <dacarley@gmail.com>",
|
"David Carley <dacarley@gmail.com>",
|
||||||
"John Tobin <john@limelightmobileinc.com>"
|
"John Tobin <john@limelightmobileinc.com>",
|
||||||
|
"Kenton Gray <kentongray@gmail.com>",
|
||||||
|
"Felix Bünemann <Felix.Buenemann@gmail.com>",
|
||||||
|
"Samy Al Zahrani <samyalzahrany@gmail.com>"
|
||||||
],
|
],
|
||||||
"description": "High performance Node.js module to resize JPEG, PNG, WebP and TIFF images using the libvips library",
|
"description": "High performance Node.js module to resize JPEG, PNG, WebP and TIFF images using the libvips library",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"clean": "rm -rf node_modules/ build/ include/ lib/ coverage/ test/fixtures/output.*",
|
"clean": "rm -rf node_modules/ build/ include/ lib/ coverage/ test/fixtures/output.*",
|
||||||
"test": "VIPS_WARNING=0 node ./node_modules/istanbul/lib/cli.js cover ./node_modules/mocha/bin/_mocha -- --slow=5000 --timeout=30000 ./test/unit/*.js",
|
"test": "VIPS_WARNING=0 node ./node_modules/istanbul/lib/cli.js cover ./node_modules/mocha/bin/_mocha -- --slow=5000 --timeout=30000 ./test/unit/*.js",
|
||||||
"test-win": "node ./node_modules/mocha/bin/mocha --slow=5000 --timeout=30000 ./test/unit/*.js",
|
"test-win": "node ./node_modules/mocha/bin/mocha --slow=5000 --timeout=60000 ./test/unit/*.js",
|
||||||
"test-leak": "./test/leak/leak.sh",
|
"test-leak": "./test/leak/leak.sh",
|
||||||
"test-packaging": "./packaging/test.sh",
|
"test-packaging": "./packaging/test.sh",
|
||||||
"test-clean": "rm -rf coverage/ test/fixtures/output.* && npm install && npm test"
|
"test-clean": "rm -rf coverage/ test/fixtures/output.* && npm install && npm test"
|
||||||
@@ -48,24 +51,25 @@
|
|||||||
"vips"
|
"vips"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bluebird": "^3.3.4",
|
"bluebird": "^3.3.5",
|
||||||
"color": "^0.11.1",
|
"color": "^0.11.1",
|
||||||
"nan": "^2.2.1",
|
"nan": "^2.2.1",
|
||||||
"semver": "^5.1.0",
|
"semver": "^5.1.0",
|
||||||
"request": "^2.69.0",
|
"request": "^2.71.0",
|
||||||
"tar": "^2.2.1"
|
"tar": "^2.2.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"async": "^1.5.2",
|
"async": "^1.5.2",
|
||||||
|
"bufferutil": "^1.2.1",
|
||||||
"coveralls": "^2.11.9",
|
"coveralls": "^2.11.9",
|
||||||
"exif-reader": "^1.0.0",
|
"exif-reader": "^1.0.0",
|
||||||
"icc": "^0.0.2",
|
"icc": "^0.0.2",
|
||||||
"istanbul": "^0.4.2",
|
"istanbul": "^0.4.3",
|
||||||
"mocha": "^2.4.5",
|
"mocha": "^2.4.5",
|
||||||
"mocha-jshint": "^2.3.1",
|
"mocha-jshint": "^2.3.1",
|
||||||
"node-cpplint": "^0.4.0",
|
"node-cpplint": "^0.4.0",
|
||||||
"rimraf": "^2.5.2",
|
"rimraf": "^2.5.2",
|
||||||
"bufferutil": "^1.2.1"
|
"unzip": "^0.1.11"
|
||||||
},
|
},
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"config": {
|
"config": {
|
||||||
|
|||||||
@@ -52,6 +52,9 @@ namespace sharp {
|
|||||||
bool IsDz(std::string const &str) {
|
bool IsDz(std::string const &str) {
|
||||||
return EndsWith(str, ".dzi") || EndsWith(str, ".DZI");
|
return EndsWith(str, ".dzi") || EndsWith(str, ".DZI");
|
||||||
}
|
}
|
||||||
|
bool IsDzZip(std::string const &str) {
|
||||||
|
return EndsWith(str, ".zip") || EndsWith(str, ".ZIP") || EndsWith(str, ".szi") || EndsWith(str, ".SZI");
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Provide a string identifier for the given image type.
|
Provide a string identifier for the given image type.
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ namespace sharp {
|
|||||||
bool IsWebp(std::string const &str);
|
bool IsWebp(std::string const &str);
|
||||||
bool IsTiff(std::string const &str);
|
bool IsTiff(std::string const &str);
|
||||||
bool IsDz(std::string const &str);
|
bool IsDz(std::string const &str);
|
||||||
|
bool IsDzZip(std::string const &str);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Provide a string identifier for the given image type.
|
Provide a string identifier for the given image type.
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ using sharp::IsPng;
|
|||||||
using sharp::IsWebp;
|
using sharp::IsWebp;
|
||||||
using sharp::IsTiff;
|
using sharp::IsTiff;
|
||||||
using sharp::IsDz;
|
using sharp::IsDz;
|
||||||
|
using sharp::IsDzZip;
|
||||||
using sharp::FreeCallback;
|
using sharp::FreeCallback;
|
||||||
using sharp::CalculateCrop;
|
using sharp::CalculateCrop;
|
||||||
using sharp::counterProcess;
|
using sharp::counterProcess;
|
||||||
@@ -103,6 +104,7 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
// From buffer
|
// From buffer
|
||||||
if (baton->rawWidth > 0 && baton->rawHeight > 0 && baton->rawChannels > 0) {
|
if (baton->rawWidth > 0 && baton->rawHeight > 0 && baton->rawChannels > 0) {
|
||||||
// Raw, uncompressed pixel data
|
// Raw, uncompressed pixel data
|
||||||
|
try {
|
||||||
image = VImage::new_from_memory(baton->bufferIn, baton->bufferInLength,
|
image = VImage::new_from_memory(baton->bufferIn, baton->bufferInLength,
|
||||||
baton->rawWidth, baton->rawHeight, baton->rawChannels, VIPS_FORMAT_UCHAR);
|
baton->rawWidth, baton->rawHeight, baton->rawChannels, VIPS_FORMAT_UCHAR);
|
||||||
if (baton->rawChannels < 3) {
|
if (baton->rawChannels < 3) {
|
||||||
@@ -111,6 +113,10 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
image.get_image()->Type = VIPS_INTERPRETATION_sRGB;
|
image.get_image()->Type = VIPS_INTERPRETATION_sRGB;
|
||||||
}
|
}
|
||||||
inputImageType = ImageType::RAW;
|
inputImageType = ImageType::RAW;
|
||||||
|
} catch(VError const &err) {
|
||||||
|
(baton->err).append(err.what());
|
||||||
|
inputImageType = ImageType::UNKNOWN;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Compressed data
|
// Compressed data
|
||||||
inputImageType = DetermineImageType(baton->bufferIn, baton->bufferInLength);
|
inputImageType = DetermineImageType(baton->bufferIn, baton->bufferInLength);
|
||||||
@@ -158,7 +164,8 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Limit input images to a given number of pixels, where pixels = width * height
|
// Limit input images to a given number of pixels, where pixels = width * height
|
||||||
if (image.width() * image.height() > baton->limitInputPixels) {
|
// Ignore if 0
|
||||||
|
if (baton->limitInputPixels > 0 && image.width() * image.height() > baton->limitInputPixels) {
|
||||||
(baton->err).append("Input image exceeds pixel limit");
|
(baton->err).append("Input image exceeds pixel limit");
|
||||||
return Error();
|
return Error();
|
||||||
}
|
}
|
||||||
@@ -489,11 +496,21 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
// Scale up 8-bit values to match 16-bit input image
|
// Scale up 8-bit values to match 16-bit input image
|
||||||
double multiplier = (image.interpretation() == VIPS_INTERPRETATION_RGB16) ? 256.0 : 1.0;
|
double multiplier = (image.interpretation() == VIPS_INTERPRETATION_RGB16) ? 256.0 : 1.0;
|
||||||
// Create background colour
|
// Create background colour
|
||||||
std::vector<double> background {
|
std::vector<double> background;
|
||||||
baton->background[0] * multiplier,
|
if (image.bands() > 2) {
|
||||||
baton->background[1] * multiplier,
|
background = {
|
||||||
baton->background[2] * multiplier
|
multiplier * baton->background[0],
|
||||||
|
multiplier * baton->background[1],
|
||||||
|
multiplier * baton->background[2]
|
||||||
};
|
};
|
||||||
|
} else {
|
||||||
|
// Convert sRGB to greyscale
|
||||||
|
background = { multiplier * (
|
||||||
|
0.2126 * baton->background[0] +
|
||||||
|
0.7152 * baton->background[1] +
|
||||||
|
0.0722 * baton->background[2]
|
||||||
|
)};
|
||||||
|
}
|
||||||
// Add alpha channel to background colour
|
// Add alpha channel to background colour
|
||||||
if (baton->background[3] < 255.0 || HasAlpha(image)) {
|
if (baton->background[3] < 255.0 || HasAlpha(image)) {
|
||||||
background.push_back(baton->background[3] * multiplier);
|
background.push_back(baton->background[3] * multiplier);
|
||||||
@@ -655,7 +672,8 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
|
|
||||||
// Number of channels used in output image
|
// Number of channels used in output image
|
||||||
baton->channels = image.bands();
|
baton->channels = image.bands();
|
||||||
|
baton->width = image.width();
|
||||||
|
baton->height = image.height();
|
||||||
// Output
|
// Output
|
||||||
if (baton->fileOut == "") {
|
if (baton->fileOut == "") {
|
||||||
// Buffer output
|
// Buffer output
|
||||||
@@ -736,7 +754,8 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
bool isWebp = IsWebp(baton->fileOut);
|
bool isWebp = IsWebp(baton->fileOut);
|
||||||
bool isTiff = IsTiff(baton->fileOut);
|
bool isTiff = IsTiff(baton->fileOut);
|
||||||
bool isDz = IsDz(baton->fileOut);
|
bool isDz = IsDz(baton->fileOut);
|
||||||
bool matchInput = baton->formatOut == "input" && !(isJpeg || isPng || isWebp || isTiff || isDz);
|
bool isDzZip = IsDzZip(baton->fileOut);
|
||||||
|
bool matchInput = baton->formatOut == "input" && !(isJpeg || isPng || isWebp || isTiff || isDz || isDzZip);
|
||||||
if (baton->formatOut == "jpeg" || isJpeg || (matchInput && inputImageType == ImageType::JPEG)) {
|
if (baton->formatOut == "jpeg" || isJpeg || (matchInput && inputImageType == ImageType::JPEG)) {
|
||||||
// Write JPEG to file
|
// Write JPEG to file
|
||||||
image.jpegsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
image.jpegsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
||||||
@@ -777,12 +796,16 @@ class PipelineWorker : public AsyncWorker {
|
|||||||
);
|
);
|
||||||
baton->formatOut = "tiff";
|
baton->formatOut = "tiff";
|
||||||
baton->channels = std::min(baton->channels, 3);
|
baton->channels = std::min(baton->channels, 3);
|
||||||
} else if (baton->formatOut == "dz" || IsDz(baton->fileOut)) {
|
} else if (baton->formatOut == "dz" || isDz || isDzZip) {
|
||||||
|
if (isDzZip) {
|
||||||
|
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP;
|
||||||
|
}
|
||||||
// Write DZ to file
|
// Write DZ to file
|
||||||
image.dzsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
image.dzsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
||||||
->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("container", baton->tileContainer)
|
||||||
->set("layout", baton->tileLayout)
|
->set("layout", baton->tileLayout)
|
||||||
);
|
);
|
||||||
baton->formatOut = "dz";
|
baton->formatOut = "dz";
|
||||||
@@ -1051,6 +1074,12 @@ NAN_METHOD(pipeline) {
|
|||||||
// Tile output
|
// 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 tileContainer = attrAsStr(options, "tileContainer");
|
||||||
|
if (tileContainer == "zip") {
|
||||||
|
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP;
|
||||||
|
} else {
|
||||||
|
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_FS;
|
||||||
|
}
|
||||||
std::string tileLayout = attrAsStr(options, "tileLayout");
|
std::string tileLayout = attrAsStr(options, "tileLayout");
|
||||||
if (tileLayout == "google") {
|
if (tileLayout == "google") {
|
||||||
baton->tileLayout = VIPS_FOREIGN_DZ_LAYOUT_GOOGLE;
|
baton->tileLayout = VIPS_FOREIGN_DZ_LAYOUT_GOOGLE;
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ struct PipelineBaton {
|
|||||||
int withMetadataOrientation;
|
int withMetadataOrientation;
|
||||||
int tileSize;
|
int tileSize;
|
||||||
int tileOverlap;
|
int tileOverlap;
|
||||||
|
VipsForeignDzContainer tileContainer;
|
||||||
VipsForeignDzLayout tileLayout;
|
VipsForeignDzLayout tileLayout;
|
||||||
|
|
||||||
PipelineBaton():
|
PipelineBaton():
|
||||||
@@ -130,6 +131,7 @@ struct PipelineBaton {
|
|||||||
withMetadataOrientation(-1),
|
withMetadataOrientation(-1),
|
||||||
tileSize(256),
|
tileSize(256),
|
||||||
tileOverlap(0),
|
tileOverlap(0),
|
||||||
|
tileContainer(VIPS_FOREIGN_DZ_CONTAINER_FS),
|
||||||
tileLayout(VIPS_FOREIGN_DZ_LAYOUT_DZ) {
|
tileLayout(VIPS_FOREIGN_DZ_LAYOUT_DZ) {
|
||||||
background[0] = 0.0;
|
background[0] = 0.0;
|
||||||
background[1] = 0.0;
|
background[1] = 0.0;
|
||||||
|
|||||||
BIN
test/fixtures/expected/embed-2channel.png
vendored
Normal file
BIN
test/fixtures/expected/embed-2channel.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 703 B |
BIN
test/fixtures/flowers.jpeg
vendored
Normal file
BIN
test/fixtures/flowers.jpeg
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 83 KiB |
BIN
test/fixtures/giant-image.jpg
vendored
Normal file
BIN
test/fixtures/giant-image.jpg
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.0 MiB |
3
test/fixtures/index.js
vendored
3
test/fixtures/index.js
vendored
@@ -66,6 +66,7 @@ module.exports = {
|
|||||||
inputJpgWithCmykNoProfile: getPath('Channel_digital_image_CMYK_color_no_profile.jpg'),
|
inputJpgWithCmykNoProfile: getPath('Channel_digital_image_CMYK_color_no_profile.jpg'),
|
||||||
inputJpgWithCorruptHeader: getPath('corrupt-header.jpg'),
|
inputJpgWithCorruptHeader: getPath('corrupt-header.jpg'),
|
||||||
inputJpgWithLowContrast: getPath('low-contrast.jpg'), // http://www.flickr.com/photos/grizdave/2569067123/
|
inputJpgWithLowContrast: getPath('low-contrast.jpg'), // http://www.flickr.com/photos/grizdave/2569067123/
|
||||||
|
inputJpgLarge: getPath('giant-image.jpg'),
|
||||||
|
|
||||||
inputPng: getPath('50020484-00001.png'), // http://c.searspartsdirect.com/lis_png/PLDM/50020484-00001.png
|
inputPng: getPath('50020484-00001.png'), // http://c.searspartsdirect.com/lis_png/PLDM/50020484-00001.png
|
||||||
inputPngWithTransparency: getPath('blackbug.png'), // public domain
|
inputPngWithTransparency: getPath('blackbug.png'), // public domain
|
||||||
@@ -89,6 +90,8 @@ module.exports = {
|
|||||||
|
|
||||||
inputSvs: getPath('CMU-1-Small-Region.svs'), // http://openslide.cs.cmu.edu/download/openslide-testdata/Aperio/CMU-1-Small-Region.svs
|
inputSvs: getPath('CMU-1-Small-Region.svs'), // http://openslide.cs.cmu.edu/download/openslide-testdata/Aperio/CMU-1-Small-Region.svs
|
||||||
|
|
||||||
|
inputJPGBig: getPath('flowers.jpeg'),
|
||||||
|
|
||||||
outputJpg: getPath('output.jpg'),
|
outputJpg: getPath('output.jpg'),
|
||||||
outputPng: getPath('output.png'),
|
outputPng: getPath('output.png'),
|
||||||
outputWebP: getPath('output.webp'),
|
outputWebP: getPath('output.webp'),
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ describe('Alpha transparency', function() {
|
|||||||
assert.strictEqual(true, info.size > 0);
|
assert.strictEqual(true, info.size > 0);
|
||||||
assert.strictEqual(32, info.width);
|
assert.strictEqual(32, info.width);
|
||||||
assert.strictEqual(32, info.height);
|
assert.strictEqual(32, info.height);
|
||||||
fixtures.assertSimilar(fixtures.expected('flatten-rgb16-orange.jpg'), data, done);
|
fixtures.assertSimilar(fixtures.expected('flatten-rgb16-orange.jpg'), data, { threshold: 6 }, done);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -88,6 +88,22 @@ describe('Embed', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('PNG with 2 channels', function(done) {
|
||||||
|
sharp(fixtures.inputPngWithGreyAlpha)
|
||||||
|
.resize(32, 16)
|
||||||
|
.embed()
|
||||||
|
.background({r: 0, g: 0, b: 0, a: 0})
|
||||||
|
.toBuffer(function(err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, data.length > 0);
|
||||||
|
assert.strictEqual('png', info.format);
|
||||||
|
assert.strictEqual(32, info.width);
|
||||||
|
assert.strictEqual(16, info.height);
|
||||||
|
assert.strictEqual(4, info.channels);
|
||||||
|
fixtures.assertSimilar(fixtures.expected('embed-2channel.png'), data, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('Enlarge and embed', function(done) {
|
it('Enlarge and embed', function(done) {
|
||||||
sharp(fixtures.inputPngWithOneColor)
|
sharp(fixtures.inputPngWithOneColor)
|
||||||
.embed()
|
.embed()
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ describe('Gamma correction', function() {
|
|||||||
assert.strictEqual('jpeg', info.format);
|
assert.strictEqual('jpeg', info.format);
|
||||||
assert.strictEqual(129, info.width);
|
assert.strictEqual(129, info.width);
|
||||||
assert.strictEqual(111, info.height);
|
assert.strictEqual(111, info.height);
|
||||||
fixtures.assertSimilar(fixtures.expected('gamma-3.0.jpg'), data, done);
|
fixtures.assertSimilar(fixtures.expected('gamma-3.0.jpg'), data, { threshold: 6 }, done);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ describe('Gamma correction', function() {
|
|||||||
.toBuffer(function(err, data, info) {
|
.toBuffer(function(err, data, info) {
|
||||||
assert.strictEqual('png', info.format);
|
assert.strictEqual('png', info.format);
|
||||||
assert.strictEqual(320, info.width);
|
assert.strictEqual(320, info.width);
|
||||||
fixtures.assertSimilar(fixtures.expected('gamma-alpha.jpg'), data, done);
|
fixtures.assertSimilar(fixtures.expected('gamma-alpha.jpg'), data, { threshold: 11 }, done);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -876,6 +876,25 @@ describe('Input/output', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Disabling limit works', function(done) {
|
||||||
|
sharp(fixtures.inputJpgLarge)
|
||||||
|
.limitInputPixels(false)
|
||||||
|
.resize(2)
|
||||||
|
.toBuffer(function(err) {
|
||||||
|
assert.strictEqual(true, !err);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Enabling default limit works and fails with a large image', function(done) {
|
||||||
|
sharp(fixtures.inputJpgLarge)
|
||||||
|
.limitInputPixels(true)
|
||||||
|
.toBuffer(function(err) {
|
||||||
|
assert.strictEqual(true, !!err);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('Smaller than input fails', function(done) {
|
it('Smaller than input fails', function(done) {
|
||||||
sharp(fixtures.inputJpg).metadata(function(err, metadata) {
|
sharp(fixtures.inputJpg).metadata(function(err, metadata) {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
@@ -1012,4 +1031,32 @@ describe('Input/output', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Info event data', function(done) {
|
||||||
|
var readable = fs.createReadStream(fixtures.inputJPGBig);
|
||||||
|
var inPipeline = sharp()
|
||||||
|
.resize(840)
|
||||||
|
.raw()
|
||||||
|
.on('info', function(info) {
|
||||||
|
assert.strictEqual(840, info.width);
|
||||||
|
assert.strictEqual(472, info.height);
|
||||||
|
assert.strictEqual(3, info.channels);
|
||||||
|
});
|
||||||
|
var badPipeline = sharp(null, {raw: {width: 840, height: 473, channels: 3}})
|
||||||
|
.toFormat('jpeg')
|
||||||
|
.toBuffer(function(err, data, info) {
|
||||||
|
assert.strictEqual(err.message.indexOf('memory area too small') > 0, true);
|
||||||
|
readable = fs.createReadStream(fixtures.inputJPGBig);
|
||||||
|
var goodPipeline = sharp(null, {raw: {width: 840, height: 472, channels: 3}})
|
||||||
|
.toFormat('jpeg')
|
||||||
|
.toBuffer(function(err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
inPipeline = sharp()
|
||||||
|
.resize(840)
|
||||||
|
.raw();
|
||||||
|
readable.pipe(inPipeline).pipe(goodPipeline);
|
||||||
|
});
|
||||||
|
readable.pipe(inPipeline).pipe(badPipeline);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ var assert = require('assert');
|
|||||||
|
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
var rimraf = require('rimraf');
|
var rimraf = require('rimraf');
|
||||||
|
var unzip = require('unzip');
|
||||||
|
|
||||||
var sharp = require('../../index');
|
var sharp = require('../../index');
|
||||||
var fixtures = require('../fixtures');
|
var fixtures = require('../fixtures');
|
||||||
@@ -88,6 +89,26 @@ describe('Tile', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Valid container values pass', function() {
|
||||||
|
['fs', 'zip'].forEach(function(container) {
|
||||||
|
assert.doesNotThrow(function() {
|
||||||
|
sharp().tile({
|
||||||
|
container: container
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Invalid container values fail', function() {
|
||||||
|
['zoinks', 1].forEach(function(container) {
|
||||||
|
assert.throws(function() {
|
||||||
|
sharp().tile({
|
||||||
|
container: container
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('Valid layout values pass', function() {
|
it('Valid layout values pass', function() {
|
||||||
['dz', 'google', 'zoomify'].forEach(function(layout) {
|
['dz', 'google', 'zoomify'].forEach(function(layout) {
|
||||||
assert.doesNotThrow(function() {
|
assert.doesNotThrow(function() {
|
||||||
@@ -190,6 +211,58 @@ describe('Tile', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Write to ZIP container using file extension', function(done) {
|
||||||
|
var container = fixtures.path('output.dz.container.zip');
|
||||||
|
var extractTo = fixtures.path('output.dz.container');
|
||||||
|
var directory = path.join(extractTo, 'output.dz.container_files');
|
||||||
|
rimraf(directory, function() {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.toFile(container, function(err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('dz', info.format);
|
||||||
|
fs.stat(container, function(err, stat) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, stat.isFile());
|
||||||
|
assert.strictEqual(true, stat.size > 0);
|
||||||
|
fs.createReadStream(container)
|
||||||
|
.pipe(unzip.Extract({path: path.dirname(extractTo)}))
|
||||||
|
.on('error', function(err) { throw err; })
|
||||||
|
.on('close', function() {
|
||||||
|
assertDeepZoomTiles(directory, 256, 13, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Write to ZIP container using container tile option', function(done) {
|
||||||
|
var container = fixtures.path('output.dz.containeropt.zip');
|
||||||
|
var extractTo = fixtures.path('output.dz.containeropt');
|
||||||
|
var directory = path.join(extractTo, 'output.dz.containeropt_files');
|
||||||
|
rimraf(directory, function() {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.tile({
|
||||||
|
container: 'zip'
|
||||||
|
})
|
||||||
|
.toFile(fixtures.path('output.dz.containeropt.dzi'), function(err, info) {
|
||||||
|
// Vips overrides .dzi extension to .zip used by container var below
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('dz', info.format);
|
||||||
|
fs.stat(container, function(err, stat) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, stat.isFile());
|
||||||
|
assert.strictEqual(true, stat.size > 0);
|
||||||
|
fs.createReadStream(container)
|
||||||
|
.pipe(unzip.Extract({path: path.dirname(extractTo)}))
|
||||||
|
.on('error', function(err) { throw err; })
|
||||||
|
.on('close', function() {
|
||||||
|
assertDeepZoomTiles(directory, 256, 13, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user