Compare commits
180 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
75d72cfded | ||
|
|
21b0d8c7f7 | ||
|
|
fa8f06f07d | ||
|
|
e07a105b7c | ||
|
|
4f72dcbf54 | ||
|
|
b77877c83d | ||
|
|
8fd3520257 | ||
|
|
f15e64039c | ||
|
|
3ffe2ba17f | ||
|
|
33782d3c83 | ||
|
|
783826aa26 | ||
|
|
c2ef16eac2 | ||
|
|
e999fb6e30 | ||
|
|
d1fc0591a5 | ||
|
|
fb1c9cf3d3 | ||
|
|
21ba1dfc26 | ||
|
|
dacd62428e | ||
|
|
1e52c2dbe6 | ||
|
|
8926ebc56c | ||
|
|
9da87ce868 | ||
|
|
46cc45c186 | ||
|
|
54f2243386 | ||
|
|
8ac33aad69 | ||
|
|
6fc62d39c9 | ||
|
|
a0655806de | ||
|
|
3614d14f83 | ||
|
|
f6fd45cc90 | ||
|
|
be39297f3b | ||
|
|
dce36e0074 | ||
|
|
ba034a8164 | ||
|
|
3dfc7bea3a | ||
|
|
f72435c750 | ||
|
|
3810f642d3 | ||
|
|
ae968142ee | ||
|
|
ccb7887cb9 | ||
|
|
f1ad1216ca | ||
|
|
ce6813329b | ||
|
|
7ad7193b1e | ||
|
|
bd96a49de6 | ||
|
|
81c710eaa3 | ||
|
|
711f0fefb6 | ||
|
|
33ca86e4f2 | ||
|
|
9b5229f2dd | ||
|
|
5781a23a4d | ||
|
|
2d1e6f2644 | ||
|
|
5240eeb518 | ||
|
|
125ee836fe | ||
|
|
3ca2f009f4 | ||
|
|
a900c28f7c | ||
|
|
77bbbb9715 | ||
|
|
88753a6333 | ||
|
|
bcd82f4893 | ||
|
|
749dc61f85 | ||
|
|
c7ccf6801d | ||
|
|
1565522ecc | ||
|
|
a44df2f533 | ||
|
|
317510746f | ||
|
|
ef54e327b7 | ||
|
|
d8d0158774 | ||
|
|
4d75f27a25 | ||
|
|
f89e9d726d | ||
|
|
5194b37460 | ||
|
|
55ea432711 | ||
|
|
ab7408c96f | ||
|
|
1f7e80e581 | ||
|
|
0e91ca90d6 | ||
|
|
8f41fed9c2 | ||
|
|
96dd40cee1 | ||
|
|
62767d072b | ||
|
|
33880ce19e | ||
|
|
988176846d | ||
|
|
657d436a0f | ||
|
|
e5549e3063 | ||
|
|
0b2fb967b8 | ||
|
|
f57478c1aa | ||
|
|
e5a5e2ca7e | ||
|
|
797d503a99 | ||
|
|
512a281986 | ||
|
|
37c5ca7166 | ||
|
|
cda700ef73 | ||
|
|
d32901da8d | ||
|
|
83ebe12061 | ||
|
|
fe34548bad | ||
|
|
855945bef2 | ||
|
|
8421e3aa5f | ||
|
|
c93f79daa7 | ||
|
|
35c53f78c8 | ||
|
|
c158d51f8b | ||
|
|
8e9a8dfede | ||
|
|
67dc694cfb | ||
|
|
74704a132c | ||
|
|
b86674f91f | ||
|
|
5dab3c8482 | ||
|
|
a190ae6b08 | ||
|
|
464fb1726d | ||
|
|
065ce6454b | ||
|
|
850c2ecdd6 | ||
|
|
926c5603aa | ||
|
|
d3225fa193 | ||
|
|
f026a835fd | ||
|
|
47241db789 | ||
|
|
34a9970bd9 | ||
|
|
57203f841a | ||
|
|
bd20bd1881 | ||
|
|
60f1fda7ee | ||
|
|
ea1013f6ec | ||
|
|
247b607afd | ||
|
|
a56102a209 | ||
|
|
940b6f505f | ||
|
|
e1b5574c4a | ||
|
|
f4cc6a2db4 | ||
|
|
0acf865654 | ||
|
|
8460e50ee0 | ||
|
|
f57a0e3b00 | ||
|
|
02b6016390 | ||
|
|
4e01d63195 | ||
|
|
94b47508c0 | ||
|
|
328cda82c5 | ||
|
|
118b17aa2f | ||
|
|
b7c7fc22f3 | ||
|
|
177a4f574c | ||
|
|
e22d093002 | ||
|
|
e7f6d49bc1 | ||
|
|
b886db4b0d | ||
|
|
ee513ac7a7 | ||
|
|
e465306d97 | ||
|
|
32d9bc204a | ||
|
|
df5cf402e3 | ||
|
|
86681100b7 | ||
|
|
47927ef47d | ||
|
|
7537adf399 | ||
|
|
740838b47c | ||
|
|
f7c2a839ad | ||
|
|
62fcfb3dba | ||
|
|
333e8789f4 | ||
|
|
3a9a137f40 | ||
|
|
5c51612982 | ||
|
|
a472adeb74 | ||
|
|
2e61839387 | ||
|
|
51805ef657 | ||
|
|
5856e41a62 | ||
|
|
ffbe6b7d76 | ||
|
|
ed6a966534 | ||
|
|
97fc2a2a3a | ||
|
|
3e1be7a33a | ||
|
|
4a4dd7f987 | ||
|
|
005c628352 | ||
|
|
1bd316de80 | ||
|
|
49b44d8238 | ||
|
|
8bc1981891 | ||
|
|
db6dc6431b | ||
|
|
f214673c3c | ||
|
|
ffe00ee398 | ||
|
|
6cade5bd7f | ||
|
|
a531b5917e | ||
|
|
ca561daedf | ||
|
|
f4cb577cb4 | ||
|
|
91be57cbce | ||
|
|
78596545b0 | ||
|
|
9f6cc33858 | ||
|
|
d82de45b7e | ||
|
|
b7bbf58624 | ||
|
|
945d941c7b | ||
|
|
2605bf966f | ||
|
|
83b72a1ede | ||
|
|
6190ca4307 | ||
|
|
c2fcf7fc4a | ||
|
|
37cb4339e2 | ||
|
|
46f229e308 | ||
|
|
7f8f38f666 | ||
|
|
fb0769a327 | ||
|
|
b84cc3d49e | ||
|
|
0cba506bc4 | ||
|
|
5cdfbba55c | ||
|
|
6145231936 | ||
|
|
513b07ddcf | ||
|
|
150971fa92 | ||
|
|
ac85d88c9c | ||
|
|
1c79d6fb5d | ||
|
|
d41321254a |
21
.gitignore
vendored
@@ -1,16 +1,9 @@
|
|||||||
lib-cov
|
|
||||||
*.seed
|
|
||||||
*.log
|
|
||||||
*.csv
|
|
||||||
*.dat
|
|
||||||
*.out
|
|
||||||
*.pid
|
|
||||||
*.gz
|
|
||||||
|
|
||||||
pids
|
|
||||||
logs
|
|
||||||
results
|
|
||||||
build
|
build
|
||||||
node_modules
|
node_modules
|
||||||
tests/fixtures/output.*
|
coverage
|
||||||
tests/libvips.supp
|
test/bench/node_modules
|
||||||
|
test/fixtures/output*
|
||||||
|
test/leak/libvips.supp
|
||||||
|
|
||||||
|
# Mac OS X
|
||||||
|
.DS_Store
|
||||||
|
|||||||
3
.jshintignore
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
node_modules
|
||||||
|
test/bench/node_modules
|
||||||
|
coverage
|
||||||
10
.jshintrc
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"strict": true,
|
||||||
|
"node": true,
|
||||||
|
"maxparams": 4,
|
||||||
|
"maxcomplexity": 13,
|
||||||
|
"globals": {
|
||||||
|
"describe": true,
|
||||||
|
"it": true
|
||||||
|
}
|
||||||
|
}
|
||||||
19
.npmignore
@@ -1,18 +1,9 @@
|
|||||||
lib-cov
|
|
||||||
*.seed
|
|
||||||
*.log
|
|
||||||
*.csv
|
|
||||||
*.dat
|
|
||||||
*.out
|
|
||||||
*.pid
|
|
||||||
*.gz
|
|
||||||
|
|
||||||
pids
|
|
||||||
logs
|
|
||||||
results
|
|
||||||
build
|
build
|
||||||
node_modules
|
node_modules
|
||||||
|
coverage
|
||||||
|
.jshintignore
|
||||||
|
.jshintrc
|
||||||
.gitignore
|
.gitignore
|
||||||
tests
|
test
|
||||||
.travis.yml
|
.travis.yml
|
||||||
|
appveyor.yml
|
||||||
|
|||||||
19
.travis.yml
@@ -1,17 +1,10 @@
|
|||||||
language: node_js
|
language: node_js
|
||||||
node_js:
|
node_js:
|
||||||
- "0.10"
|
- "0.10"
|
||||||
- "0.11"
|
- "0.12"
|
||||||
|
- "iojs-v1"
|
||||||
|
- "iojs-v2"
|
||||||
before_install:
|
before_install:
|
||||||
- sudo add-apt-repository ppa:lyrasis/precise-backports -y
|
- curl -s https://raw.githubusercontent.com/lovell/sharp/master/preinstall.sh | sudo bash -
|
||||||
- sudo apt-get update -qq
|
after_success:
|
||||||
- sudo apt-get install -qq automake gobject-introspection gtk-doc-tools libglib2.0-dev libjpeg-turbo8-dev libpng12-dev libwebp-dev libtiff4-dev libexif-dev libxml2-dev swig graphicsmagick libmagick++-dev
|
- cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js
|
||||||
- git clone https://github.com/jcupitt/libvips.git
|
|
||||||
- cd libvips
|
|
||||||
- git checkout 7.38
|
|
||||||
- ./bootstrap.sh
|
|
||||||
- ./configure --enable-debug=no --enable-cxx=yes --without-orc --without-python --without-fftw
|
|
||||||
- make
|
|
||||||
- sudo make install
|
|
||||||
- sudo ldconfig
|
|
||||||
- cd $TRAVIS_BUILD_DIR
|
|
||||||
|
|||||||
84
CONTRIBUTING.md
Executable file
@@ -0,0 +1,84 @@
|
|||||||
|
# Contributing to sharp
|
||||||
|
|
||||||
|
Hello, thank you for your interest in helping!
|
||||||
|
|
||||||
|
## Submit a new bug report
|
||||||
|
|
||||||
|
Please create a [new issue](https://github.com/lovell/sharp/issues/new) containing the steps to reproduce the problem.
|
||||||
|
|
||||||
|
New bugs are assigned a `triage` label whilst under investigation.
|
||||||
|
|
||||||
|
If you're having problems with `npm install sharp`, please include the output of the following commands, perhaps as a [gist](https://gist.github.com/):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
vips -v
|
||||||
|
pkg-config --print-provides vips
|
||||||
|
npm install --verbose sharp
|
||||||
|
```
|
||||||
|
|
||||||
|
## Submit a new feature request
|
||||||
|
|
||||||
|
If a [similar request](https://github.com/lovell/sharp/labels/enhancement) exists, it's probably fastest to add a comment to it about your requirement.
|
||||||
|
|
||||||
|
Implementation is usually straightforward if _libvips_ [already supports](http://www.vips.ecs.soton.ac.uk/supported/current/doc/html/libvips/ch03.html) the feature you need.
|
||||||
|
|
||||||
|
## Submit a Pull Request to fix a bug
|
||||||
|
|
||||||
|
Thank you! To prevent the problem occurring again, please add unit tests that would have failed.
|
||||||
|
|
||||||
|
Please select the `master` branch as the destination for your Pull Request so your fix can be included in the next minor release.
|
||||||
|
|
||||||
|
Please squash your changes into a single commit using a command like `git rebase -i upstream/master`.
|
||||||
|
|
||||||
|
## Submit a Pull Request with a new feature
|
||||||
|
|
||||||
|
Please add JavaScript [unit tests](https://github.com/lovell/sharp/tree/master/test/unit) to cover your new feature. A test coverage report for the JavaScript code is generated in the `coverage/lcov-report` directory.
|
||||||
|
|
||||||
|
You deserve to add your details to the [list of contributors](https://github.com/lovell/sharp/blob/master/package.json#L5).
|
||||||
|
|
||||||
|
Any change that modifies the existing public API should be added to the relevant work-in-progress branch for inclusion in the next major release.
|
||||||
|
|
||||||
|
| Release | WIP branch |
|
||||||
|
| ------: | :--------- |
|
||||||
|
| v0.9.0 | intake |
|
||||||
|
| v0.10.0 | judgement |
|
||||||
|
| v0.11.0 | knife |
|
||||||
|
|
||||||
|
Please squash your changes into a single commit using a command like `git rebase -i upstream/<wip-branch>`.
|
||||||
|
|
||||||
|
### Add a new public method
|
||||||
|
|
||||||
|
The API tries to be as fluent as possible. Image processing concepts follow the naming conventions from _libvips_ and, to a lesser extent, _ImageMagick_.
|
||||||
|
|
||||||
|
Most methods have optional parameters and assume sensible defaults. Methods with mandatory parameters often have names like `doSomethingWith(X)`.
|
||||||
|
|
||||||
|
Please ensure backwards compatibility where possible. Methods to modify previously default behaviour often have names like `withoutOptionY()` or `withExtraZ()`.
|
||||||
|
|
||||||
|
Feel free to create a [new issue](https://github.com/lovell/sharp/issues/new) to gather feedback on a potential API change.
|
||||||
|
|
||||||
|
### Remove an existing public method
|
||||||
|
|
||||||
|
A method to be removed should be deprecated in the next major version then removed in the following major version.
|
||||||
|
|
||||||
|
By way of example, the [bilinearInterpolation method](https://github.com/lovell/sharp/blob/v0.6.0/index.js#L155) present in v0.5.0 was deprecated in v0.6.0 and removed in v0.7.0.
|
||||||
|
|
||||||
|
## Run the tests
|
||||||
|
|
||||||
|
### Functional tests and static code analysis
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm test
|
||||||
|
```
|
||||||
|
|
||||||
|
### Memory leak tests
|
||||||
|
|
||||||
|
Requires _valgrind_.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd sharp/test/leak
|
||||||
|
./leak.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Finally
|
||||||
|
|
||||||
|
Please feel free to ask any questions via a [new issue](https://github.com/lovell/sharp/issues/new) or contact me by [e-mail](https://github.com/lovell/sharp/blob/master/package.json#L4).
|
||||||
521
README.md
@@ -3,6 +3,7 @@
|
|||||||
* [Installation](https://github.com/lovell/sharp#installation)
|
* [Installation](https://github.com/lovell/sharp#installation)
|
||||||
* [Usage examples](https://github.com/lovell/sharp#usage-examples)
|
* [Usage examples](https://github.com/lovell/sharp#usage-examples)
|
||||||
* [API](https://github.com/lovell/sharp#api)
|
* [API](https://github.com/lovell/sharp#api)
|
||||||
|
* [Contributing](https://github.com/lovell/sharp#contributing)
|
||||||
* [Testing](https://github.com/lovell/sharp#testing)
|
* [Testing](https://github.com/lovell/sharp#testing)
|
||||||
* [Performance](https://github.com/lovell/sharp#performance)
|
* [Performance](https://github.com/lovell/sharp#performance)
|
||||||
* [Thanks](https://github.com/lovell/sharp#thanks)
|
* [Thanks](https://github.com/lovell/sharp#thanks)
|
||||||
@@ -10,13 +11,19 @@
|
|||||||
|
|
||||||
The typical use case for this high speed Node.js module is to convert large images of many formats to smaller, web-friendly JPEG, PNG and WebP images of varying dimensions.
|
The typical use case for this high speed Node.js module is to convert large images of many formats to smaller, web-friendly JPEG, PNG and WebP images of varying dimensions.
|
||||||
|
|
||||||
The performance of JPEG resizing is typically 8x faster than ImageMagick and GraphicsMagick, based mainly on the number of CPU cores available.
|
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 formats from the filesystem via libmagick, libgraphicsmagick or [OpenSlide](http://openslide.org/) if present.
|
||||||
|
|
||||||
Memory usage is kept to a minimum, no child processes are spawned, everything remains non-blocking thanks to _libuv_ and Promises/A+ are supported.
|
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).
|
||||||
|
|
||||||
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++ or libgraphicsmagick++ if present.
|
Colour spaces, embedded ICC profiles and alpha transparency channels are all handled correctly.
|
||||||
|
|
||||||
When generating JPEG output all metadata is removed and Huffman tables optimised without having to use separate command line tools like [jpegoptim](https://github.com/tjko/jpegoptim) and [jpegtran](http://jpegclub.org/jpegtran/).
|
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.
|
||||||
|
|
||||||
|
Huffman tables are optimised when generating JPEG output images without having to use separate command line tools like [jpegoptim](https://github.com/tjko/jpegoptim) and [jpegtran](http://jpegclub.org/jpegtran/). PNG filtering can be disabled, which for diagrams and line art often produces the same result as [pngcrush](http://pmt.sourceforge.net/pngcrush/).
|
||||||
|
|
||||||
|
Everything remains non-blocking thanks to _libuv_, no child processes are spawned and Promises/A+ are supported.
|
||||||
|
|
||||||
Anyone who has used the Node.js bindings for [GraphicsMagick](https://github.com/aheckmann/gm) will find the API similarly fluent.
|
Anyone who has used the Node.js bindings for [GraphicsMagick](https://github.com/aheckmann/gm) will find the API similarly fluent.
|
||||||
|
|
||||||
@@ -28,12 +35,43 @@ This module is powered by the blazingly fast [libvips](https://github.com/jcupit
|
|||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
* Node.js v0.10+
|
* Node.js v0.10+ or io.js
|
||||||
* [libvips](https://github.com/jcupitt/libvips) v7.38.5+
|
* [libvips](https://github.com/jcupitt/libvips) v7.40.0+ (7.42.0+ recommended)
|
||||||
|
* C++11 compatible compiler such as gcc 4.6+, clang 3.0+ or MSVC 2013
|
||||||
|
|
||||||
_libvips_ can take advantage of [liborc](http://code.entropywave.com/orc/) if present. Warning: versions of _liborc_ prior to 0.4.19 suffer [memory leaks](https://github.com/lovell/sharp/issues/21#issuecomment-42367306) and version 0.4.19 suffers [buffer overflows](https://github.com/lovell/sharp/issues/21#issuecomment-44813498).
|
To install the most suitable version of libvips on the following Operating Systems:
|
||||||
|
|
||||||
### Install libvips on Mac OS
|
* Mac OS
|
||||||
|
* Homebrew
|
||||||
|
* MacPorts
|
||||||
|
* Debian Linux
|
||||||
|
* Debian 7, 8
|
||||||
|
* Ubuntu 12.04, 14.04, 14.10, 15.04
|
||||||
|
* Mint 13, 17
|
||||||
|
* Red Hat Linux
|
||||||
|
* RHEL/Centos/Scientific 6, 7
|
||||||
|
* Fedora 21, 22
|
||||||
|
* Amazon Linux 2014.09
|
||||||
|
* OpenSuse Linux
|
||||||
|
* OpenSuse 13.1, 13.2
|
||||||
|
|
||||||
|
run the following as a user with `sudo` access:
|
||||||
|
|
||||||
|
curl -s https://raw.githubusercontent.com/lovell/sharp/master/preinstall.sh | sudo bash -
|
||||||
|
|
||||||
|
or run the following as `root`:
|
||||||
|
|
||||||
|
curl -s https://raw.githubusercontent.com/lovell/sharp/master/preinstall.sh | bash -
|
||||||
|
|
||||||
|
The [preinstall.sh](https://github.com/lovell/sharp/blob/master/preinstall.sh) script requires `curl` and `pkg-config`.
|
||||||
|
|
||||||
|
Add `--with-openslide` to enable OpenSlide support:
|
||||||
|
|
||||||
|
curl -s https://raw.githubusercontent.com/lovell/sharp/master/preinstall.sh | sudo bash -s -- --with-openslide
|
||||||
|
|
||||||
|
### Mac OS tips
|
||||||
|
|
||||||
|
Manual install via homebrew:
|
||||||
|
|
||||||
brew install homebrew/science/vips --with-webp --with-graphicsmagick
|
brew install homebrew/science/vips --with-webp --with-graphicsmagick
|
||||||
|
|
||||||
@@ -45,59 +83,34 @@ The _gettext_ dependency of _libvips_ [can lead](https://github.com/lovell/sharp
|
|||||||
|
|
||||||
brew link gettext --force
|
brew link gettext --force
|
||||||
|
|
||||||
### Install libvips on Ubuntu Linux
|
### Windows
|
||||||
|
|
||||||
#### Ubuntu 14.x
|
Requires x86 32-bit Node.js or io.js (use `iojs.exe` rather than `node.exe`).
|
||||||
|
The WebP format is currently unsupported.
|
||||||
|
|
||||||
sudo apt-get install libvips-dev
|
1. Ensure the [node-gyp prerequisites](https://github.com/TooTallNate/node-gyp#installation) are met.
|
||||||
|
2. [Download](http://www.vips.ecs.soton.ac.uk/supported/current/win32/) and unzip `vips-dev.x.y.z.zip`.
|
||||||
|
3. Set the `VIPS_HOME` environment variable to the full path of the `vips-dev-x.y.z` directory.
|
||||||
|
4. Add `vips-dev-x.y.z\bin` to `PATH`.
|
||||||
|
|
||||||
#### Ubuntu 13.x
|
Versions of MSVC more recent than 2013 may require the use of `npm install --arch=ia32 --msvs_version=2013`.
|
||||||
|
|
||||||
Compiling from source is recommended:
|
### Heroku
|
||||||
|
|
||||||
sudo apt-get install automake build-essential git gobject-introspection gtk-doc-tools libglib2.0-dev libjpeg-turbo8-dev libpng12-dev libwebp-dev libtiff5-dev libexif-dev libxml2-dev swig libmagickwand-dev
|
|
||||||
git clone https://github.com/jcupitt/libvips.git
|
|
||||||
cd libvips
|
|
||||||
git checkout 7.38
|
|
||||||
./bootstrap.sh
|
|
||||||
./configure --enable-debug=no --enable-cxx=yes --without-python --without-orc --without-fftw
|
|
||||||
make
|
|
||||||
sudo make install
|
|
||||||
sudo ldconfig
|
|
||||||
|
|
||||||
#### Ubuntu 12.x
|
|
||||||
|
|
||||||
Requires `libtiff4-dev` instead of `libtiff5-dev` and has [a bug](https://bugs.launchpad.net/ubuntu/+source/libwebp/+bug/1108731) in the libwebp package. Work around these problems by running these commands first:
|
|
||||||
|
|
||||||
sudo add-apt-repository ppa:lyrasis/precise-backports
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install libtiff4-dev
|
|
||||||
|
|
||||||
Then follow Ubuntu 13.x instructions.
|
|
||||||
|
|
||||||
### Install libvips on Redhat/Centos Linux
|
|
||||||
|
|
||||||
#### Centos 6
|
|
||||||
|
|
||||||
sudo yum groupinstall -y "Development Tools"
|
|
||||||
sudo yum install -y gtk-doc libxml2-devel libjpeg-turbo-devel libpng-devel libtiff-devel libexif-devel ImageMagick-devel
|
|
||||||
sudo yum install -y http://li.nux.ro/download/nux/dextop/el6/x86_64/nux-dextop-release-0-2.el6.nux.noarch.rpm
|
|
||||||
sudo yum install -y --enablerepo=nux-dextop gobject-introspection-devel
|
|
||||||
sudo yum install -y http://rpms.famillecollet.com/enterprise/remi-release-6.rpm
|
|
||||||
sudo yum install -y --enablerepo=remi libwebp-devel
|
|
||||||
git clone https://github.com/jcupitt/libvips.git
|
|
||||||
cd libvips
|
|
||||||
git checkout 7.40
|
|
||||||
./bootstrap.sh
|
|
||||||
./configure --prefix=/usr --enable-docs=no --enable-debug=no --enable-cxx=yes --without-orc --without-python --without-fftw
|
|
||||||
make
|
|
||||||
sudo make install
|
|
||||||
sudo ldconfig
|
|
||||||
|
|
||||||
### Install libvips on Heroku
|
|
||||||
|
|
||||||
[Alessandro Tagliapietra](https://github.com/alex88) maintains an [Heroku buildpack for libvips](https://github.com/alex88/heroku-buildpack-vips) and its dependencies.
|
[Alessandro Tagliapietra](https://github.com/alex88) maintains an [Heroku buildpack for libvips](https://github.com/alex88/heroku-buildpack-vips) and its dependencies.
|
||||||
|
|
||||||
|
### Docker
|
||||||
|
|
||||||
|
[Marc Bachmann](https://github.com/marcbachmann) maintains a [Dockerfile for libvips](https://github.com/marcbachmann/dockerfile-libvips).
|
||||||
|
|
||||||
|
docker pull marcbachmann/libvips
|
||||||
|
|
||||||
|
### Build tools
|
||||||
|
|
||||||
|
* [gulp-responsive](https://www.npmjs.com/package/gulp-responsive)
|
||||||
|
* [gulp-sharp](https://www.npmjs.com/package/gulp-sharp)
|
||||||
|
* [grunt-sharp](https://www.npmjs.com/package/grunt-sharp)
|
||||||
|
|
||||||
## Usage examples
|
## Usage examples
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
@@ -115,15 +128,20 @@ sharp('input.jpg').resize(300, 200).toFile('output.jpg', function(err) {
|
|||||||
```
|
```
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
var transformer = sharp().resize(300, 200).crop(sharp.gravity.north);
|
var transformer = sharp()
|
||||||
readableStream.pipe(transformer).pipe(writableStream);
|
.resize(300, 200)
|
||||||
|
.crop(sharp.gravity.north)
|
||||||
|
.on('error', function(err) {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
// Read image data from readableStream, resize and write image data to writableStream
|
// Read image data from readableStream, resize and write image data to writableStream
|
||||||
|
readableStream.pipe(transformer).pipe(writableStream);
|
||||||
```
|
```
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
var image = sharp(inputJpg);
|
var image = sharp(inputJpg);
|
||||||
image.metadata(function(err, metadata) {
|
image.metadata(function(err, metadata) {
|
||||||
image.resize(metadata.width / 2).webp().toBuffer(function(err, outputBuffer, info) {
|
image.resize(Math.floor(metadata.width / 2)).webp().toBuffer(function(err, outputBuffer, info) {
|
||||||
// outputBuffer contains a WebP image half the width and height of the original JPEG
|
// outputBuffer contains a WebP image half the width and height of the original JPEG
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -149,14 +167,17 @@ readableStream.pipe(pipeline);
|
|||||||
sharp('input.png')
|
sharp('input.png')
|
||||||
.rotate(180)
|
.rotate(180)
|
||||||
.resize(300)
|
.resize(300)
|
||||||
|
.flatten()
|
||||||
|
.background('#ff6600')
|
||||||
.sharpen()
|
.sharpen()
|
||||||
.withMetadata()
|
.withMetadata()
|
||||||
.quality(90)
|
.quality(90)
|
||||||
.webp()
|
.webp()
|
||||||
.toBuffer()
|
.toBuffer()
|
||||||
.then(function(outputBuffer) {
|
.then(function(outputBuffer) {
|
||||||
// outputBuffer contains 300px wide, upside down, sharpened,
|
// outputBuffer contains upside down, 300px wide, alpha channel flattened
|
||||||
// with metadata, 90% quality WebP image data
|
// onto orange background, sharpened, with metadata, 90% quality WebP image
|
||||||
|
// data
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -169,64 +190,148 @@ http.createServer(function(request, response) {
|
|||||||
// resized to 200 pixels wide, in WebP format
|
// resized to 200 pixels wide, in WebP format
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
sharp(input)
|
||||||
|
.extract(top, left, width, height)
|
||||||
|
.toFile(output);
|
||||||
|
// Extract a region of the input image, saving in the same format.
|
||||||
|
```
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
sharp(input)
|
||||||
|
.extract(topOffsetPre, leftOffsetPre, widthPre, heightPre)
|
||||||
|
.resize(width, height)
|
||||||
|
.extract(topOffsetPost, leftOffsetPost, widthPost, heightPost)
|
||||||
|
.toFile(output);
|
||||||
|
// Extract a region, resize, then extract from the resized image
|
||||||
|
```
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
sharp(inputBuffer)
|
sharp(inputBuffer)
|
||||||
.resize(200, 300)
|
.resize(200, 300)
|
||||||
.interpolateWith(sharp.interpolator.nohalo)
|
.interpolateWith(sharp.interpolator.nohalo)
|
||||||
.embedWhite()
|
.background('white')
|
||||||
|
.embed()
|
||||||
.toFile('output.tiff')
|
.toFile('output.tiff')
|
||||||
.then(function() {
|
.then(function() {
|
||||||
// output.tiff is a 200 pixels wide and 300 pixels high image
|
// output.tiff is a 200 pixels wide and 300 pixels high image
|
||||||
// containing a bicubic scaled version, embedded on a white canvas,
|
// containing a nohalo scaled version, embedded on a white canvas,
|
||||||
// of the image data in inputBuffer
|
// of the image data in inputBuffer
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
sharp('input.gif').resize(200, 300).embedBlack().webp().toBuffer(function(err, outputBuffer) {
|
sharp('input.gif')
|
||||||
if (err) {
|
.resize(200, 300)
|
||||||
throw err;
|
.background({r: 0, g: 0, b: 0, a: 0})
|
||||||
}
|
.embed()
|
||||||
// outputBuffer contains WebP image data of a 200 pixels wide and 300 pixels high
|
.toFormat(sharp.format.webp)
|
||||||
// containing a scaled version, embedded on a black canvas, of input.gif
|
.toBuffer(function(err, outputBuffer) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
// outputBuffer contains WebP image data of a 200 pixels wide and 300 pixels high
|
||||||
|
// containing a scaled version, embedded on a transparent canvas, of input.gif
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
sharp(inputBuffer)
|
||||||
|
.resize(200, 200)
|
||||||
|
.max()
|
||||||
|
.toFormat('jpeg')
|
||||||
|
.toBuffer().then(function(outputBuffer) {
|
||||||
|
// outputBuffer contains JPEG image data no wider than 200 pixels and no higher
|
||||||
|
// than 200 pixels regardless of the inputBuffer image dimensions
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
```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
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
sharp(inputBuffer).resize(200, 200).max().jpeg().toBuffer().then(function(outputBuffer) {
|
// Runtime discovery of available formats
|
||||||
// outputBuffer contains JPEG image data no wider than 200 pixels and no higher
|
console.dir(sharp.format);
|
||||||
// than 200 pixels regardless of the inputBuffer image dimensions
|
|
||||||
});
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## API
|
## API
|
||||||
|
|
||||||
|
### Attributes
|
||||||
|
|
||||||
|
#### format
|
||||||
|
|
||||||
|
An Object containing nested boolean values
|
||||||
|
representing the available input and output formats/methods,
|
||||||
|
for example:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{ jpeg: { id: 'jpeg',
|
||||||
|
input: { file: true, buffer: true, stream: true },
|
||||||
|
output: { file: true, buffer: true, stream: true } },
|
||||||
|
png: { id: 'png',
|
||||||
|
input: { file: true, buffer: true, stream: true },
|
||||||
|
output: { file: true, buffer: true, stream: true } },
|
||||||
|
webp: { id: 'webp',
|
||||||
|
input: { file: true, buffer: true, stream: true },
|
||||||
|
output: { file: true, buffer: true, stream: true } },
|
||||||
|
tiff: { id: 'tiff',
|
||||||
|
input: { file: true, buffer: true, stream: true },
|
||||||
|
output: { file: true, buffer: false, stream: false } },
|
||||||
|
magick: { id: 'magick',
|
||||||
|
input: { file: true, buffer: true, stream: true },
|
||||||
|
output: { file: false, buffer: false, stream: false } },
|
||||||
|
raw: { id: 'raw',
|
||||||
|
input: { file: false, buffer: false, stream: false },
|
||||||
|
output: { file: false, buffer: true, stream: true } } }
|
||||||
|
```
|
||||||
|
|
||||||
|
#### queue
|
||||||
|
|
||||||
|
An EventEmitter that emits a `change` event when a task is either:
|
||||||
|
|
||||||
|
* queued, waiting for _libuv_ to provide a worker thread
|
||||||
|
* complete
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
sharp.queue.on('change', function(queueLength) {
|
||||||
|
console.log('Queue contains ' + queueLength + ' task(s)');
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
### Input methods
|
### Input methods
|
||||||
|
|
||||||
#### sharp([input])
|
#### sharp([input])
|
||||||
|
|
||||||
Constructor to which further methods are chained. `input`, if present, can be one of:
|
Constructor to which further methods are chained. `input`, if present, can be one of:
|
||||||
|
|
||||||
* Buffer containing JPEG, PNG or WebP image data, or
|
* Buffer containing JPEG, PNG, WebP, GIF* or TIFF image data, or
|
||||||
* String containing the filename of an image, with most major formats supported.
|
* String containing the filename of an image, with most major formats supported.
|
||||||
|
|
||||||
The object returned implements the [stream.Duplex](http://nodejs.org/api/stream.html#stream_class_stream_duplex) class.
|
The object returned implements the [stream.Duplex](http://nodejs.org/api/stream.html#stream_class_stream_duplex) class.
|
||||||
|
|
||||||
JPEG, PNG or WebP format image data can be streamed into the object when `input` is not provided.
|
JPEG, PNG, WebP, GIF* or TIFF format image data can be streamed into the object when `input` is not provided.
|
||||||
|
|
||||||
JPEG, PNG or WebP format image data can be streamed out from this object.
|
JPEG, PNG or WebP format image data can be streamed out from this object.
|
||||||
|
|
||||||
|
\* libvips 8.0.0+ is required for Buffer/Stream input of GIF and other `magick` formats.
|
||||||
|
|
||||||
#### metadata([callback])
|
#### metadata([callback])
|
||||||
|
|
||||||
Fast access to image metadata without decoding any compressed image data.
|
Fast access to image metadata without decoding any compressed image data.
|
||||||
|
|
||||||
`callback`, if present, gets the arguments `(err, metadata)` where `metadata` has the attributes:
|
`callback`, if present, gets the arguments `(err, metadata)` where `metadata` has the attributes:
|
||||||
|
|
||||||
* `format`: Name of decoder to be used to decompress image data e.g. `jpeg`, `png`, `webp` (for file-based input additionally `tiff` and `magick`)
|
* `format`: Name of decoder to be used to decompress image data e.g. `jpeg`, `png`, `webp` (for file-based input additionally `tiff`, `magick` and `openslide`)
|
||||||
* `width`: Number of pixels wide
|
* `width`: Number of pixels wide
|
||||||
* `height`: Number of pixels high
|
* `height`: Number of pixels high
|
||||||
* `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `scrgb`, `cmyk`, `lab`, `xyz`, `b-w` [...](https://github.com/jcupitt/libvips/blob/master/libvips/iofuncs/enumtypes.c#L502)
|
* `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `scrgb`, `cmyk`, `lab`, `xyz`, `b-w` [...](https://github.com/jcupitt/libvips/blob/master/libvips/iofuncs/enumtypes.c#L522)
|
||||||
* `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK
|
* `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK
|
||||||
|
* `hasProfile`: Boolean indicating the presence of an embedded ICC profile
|
||||||
|
* `hasAlpha`: Boolean indicating the presence of an alpha transparency channel
|
||||||
* `orientation`: Number value of the EXIF Orientation header, if present
|
* `orientation`: Number value of the EXIF Orientation header, if present
|
||||||
|
|
||||||
A Promises/A+ promise is returned when `callback` is not provided.
|
A Promises/A+ promise is returned when `callback` is not provided.
|
||||||
@@ -235,15 +340,31 @@ A Promises/A+ promise is returned when `callback` is not provided.
|
|||||||
|
|
||||||
An advanced setting that switches the libvips access method to `VIPS_ACCESS_SEQUENTIAL`. This will reduce memory usage and can improve performance on some systems.
|
An advanced setting that switches the libvips access method to `VIPS_ACCESS_SEQUENTIAL`. This will reduce memory usage and can improve performance on some systems.
|
||||||
|
|
||||||
|
#### limitInputPixels(pixels)
|
||||||
|
|
||||||
|
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).
|
||||||
|
|
||||||
### Image transformation options
|
### Image transformation options
|
||||||
|
|
||||||
#### resize(width, [height])
|
#### resize(width, [height])
|
||||||
|
|
||||||
Scale output to `width` x `height`. By default, the resized image is cropped to the exact size specified.
|
Scale output to `width` x `height`. By default, the resized image is cropped to the exact size specified.
|
||||||
|
|
||||||
`width` is the Number of pixels wide the resultant image should be. Use `null` or `undefined` to auto-scale the width to match the height.
|
`width` is the integral Number of pixels wide the resultant image should be, between 1 and 16383 (0x3FFF). Use `null` or `undefined` to auto-scale the width to match the height.
|
||||||
|
|
||||||
`height` is the Number of pixels high the resultant image should be. Use `null` or `undefined` to auto-scale the height to match the width.
|
`height` is the integral Number of pixels high the resultant image should be, between 1 and 16383. Use `null` or `undefined` to auto-scale the height to match the width.
|
||||||
|
|
||||||
|
#### extract(top, left, width, height)
|
||||||
|
|
||||||
|
Extract a region of the image. Can be used with or without a `resize` operation.
|
||||||
|
|
||||||
|
`top` and `left` are the offset, in pixels, from the top-left corner.
|
||||||
|
|
||||||
|
`width` and `height` are the dimensions of the extracted image.
|
||||||
|
|
||||||
|
Use `extract` before `resize` for pre-resize extraction. Use `extract` after `resize` for post-resize extraction. Use `extract` before and after for both.
|
||||||
|
|
||||||
#### crop([gravity])
|
#### crop([gravity])
|
||||||
|
|
||||||
@@ -255,17 +376,45 @@ Possible values are `north`, `east`, `south`, `west`, `center` and `centre`. The
|
|||||||
|
|
||||||
#### max()
|
#### max()
|
||||||
|
|
||||||
Preserving aspect ratio, resize the image to the maximum width or height specified.
|
Preserving aspect ratio,
|
||||||
|
resize the image to be as large as possible
|
||||||
|
while ensuring its dimensions are less than or equal to
|
||||||
|
the `width` and `height` specified.
|
||||||
|
|
||||||
Both `width` and `height` must be provided via `resize` otherwise the behaviour will default to `crop`.
|
Both `width` and `height` must be provided via `resize` otherwise the behaviour will default to `crop`.
|
||||||
|
|
||||||
#### embedWhite()
|
#### min()
|
||||||
|
|
||||||
Embed the resized image on a white background of the exact size specified.
|
Preserving aspect ratio,
|
||||||
|
resize the image to be as small as possible
|
||||||
|
while ensuring its dimensions are greater than or equal to
|
||||||
|
the `width` and `height` specified.
|
||||||
|
|
||||||
#### embedBlack()
|
Both `width` and `height` must be provided via `resize` otherwise the behaviour will default to `crop`.
|
||||||
|
|
||||||
Embed the resized image on a black background of the exact size specified.
|
#### ignoreAspectRatio()
|
||||||
|
|
||||||
|
Ignoring the aspect ratio of the input, stretch the image to the exact `width` and/or `height` provided via `resize`.
|
||||||
|
|
||||||
|
#### background(rgba)
|
||||||
|
|
||||||
|
Set the background for the `embed` and `flatten` operations.
|
||||||
|
|
||||||
|
`rgba` is parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
|
||||||
|
|
||||||
|
The alpha value is a float between `0` (transparent) and `1` (opaque).
|
||||||
|
|
||||||
|
The default background is `{r: 0, g: 0, b: 0, a: 1}`, black without transparency.
|
||||||
|
|
||||||
|
#### embed()
|
||||||
|
|
||||||
|
Preserving aspect ratio, resize the image to the maximum `width` or `height` specified then embed on a background of the exact `width` and `height` specified.
|
||||||
|
|
||||||
|
If the background contains an alpha value then WebP and PNG format output images will contain an alpha channel, even when the input image does not.
|
||||||
|
|
||||||
|
#### flatten()
|
||||||
|
|
||||||
|
Merge alpha transparency channel, if any, with `background`.
|
||||||
|
|
||||||
#### rotate([angle])
|
#### rotate([angle])
|
||||||
|
|
||||||
@@ -273,7 +422,17 @@ Rotate the output image by either an explicit angle or auto-orient based on the
|
|||||||
|
|
||||||
`angle`, if present, is a Number with a value of `0`, `90`, `180` or `270`.
|
`angle`, if present, is a Number with a value of `0`, `90`, `180` or `270`.
|
||||||
|
|
||||||
Use this method without `angle` to determine the angle from EXIF data. Mirroring is currently unsupported.
|
Use this method without `angle` to determine the angle from EXIF data. Mirroring is supported and may infer the use of a `flip` operation.
|
||||||
|
|
||||||
|
Method order is important when both rotating and extracting regions, for example `rotate(x).extract(y)` will produce a different result to `extract(y).rotate(x)`.
|
||||||
|
|
||||||
|
#### flip()
|
||||||
|
|
||||||
|
Flip the image about the vertical Y axis. This always occurs after rotation, if any.
|
||||||
|
|
||||||
|
#### flop()
|
||||||
|
|
||||||
|
Flop the image about the horizontal X axis. This always occurs after rotation, if any.
|
||||||
|
|
||||||
#### withoutEnlargement()
|
#### withoutEnlargement()
|
||||||
|
|
||||||
@@ -281,9 +440,23 @@ Do not enlarge the output image if the input image width *or* height are already
|
|||||||
|
|
||||||
This is equivalent to GraphicsMagick's `>` geometry option: "change the dimensions of the image only if its width or height exceeds the geometry specification".
|
This is equivalent to GraphicsMagick's `>` geometry option: "change the dimensions of the image only if its width or height exceeds the geometry specification".
|
||||||
|
|
||||||
#### sharpen()
|
#### blur([sigma])
|
||||||
|
|
||||||
Perform a mild sharpen of the resultant image. This typically reduces performance by 30%.
|
When used without parameters, performs a fast, mild blur of the output image. This typically reduces performance by 10%.
|
||||||
|
|
||||||
|
When a `sigma` is provided, performs a slower, more accurate Gaussian blur. This typically reduces performance by 25%.
|
||||||
|
|
||||||
|
* `sigma`, if present, is a Number between 0.3 and 1000 representing the approximate blur radius in pixels.
|
||||||
|
|
||||||
|
#### sharpen([radius], [flat], [jagged])
|
||||||
|
|
||||||
|
When used without parameters, performs a fast, mild sharpen of the output image. This typically reduces performance by 10%.
|
||||||
|
|
||||||
|
When a `radius` is provided, performs a slower, more accurate sharpen of the L channel in the LAB colour space. Separate control over the level of sharpening in "flat" and "jagged" areas is available. This typically reduces performance by 50%.
|
||||||
|
|
||||||
|
* `radius`, if present, is an integral Number representing the sharpen mask radius in pixels.
|
||||||
|
* `flat`, if present, is a Number representing the level of sharpening to apply to "flat" areas, defaulting to a value of 1.0.
|
||||||
|
* `jagged`, if present, is a Number representing the level of sharpening to apply to "jagged" areas, defaulting to a value of 2.0.
|
||||||
|
|
||||||
#### interpolateWith(interpolator)
|
#### interpolateWith(interpolator)
|
||||||
|
|
||||||
@@ -308,6 +481,18 @@ This can improve the perceived brightness of a resized image in non-linear colou
|
|||||||
|
|
||||||
JPEG input images will not take advantage of the shrink-on-load performance optimisation when applying a gamma correction.
|
JPEG input images will not take advantage of the shrink-on-load performance optimisation when applying a gamma correction.
|
||||||
|
|
||||||
|
#### grayscale() / greyscale()
|
||||||
|
|
||||||
|
Convert to 8-bit greyscale; 256 shades of grey.
|
||||||
|
|
||||||
|
This is a linear operation. If the input image is in a non-linear colour space such as sRGB, use `gamma()` with `greyscale()` for the best results.
|
||||||
|
|
||||||
|
The output image will still be web-friendly sRGB and contain three (identical) channels.
|
||||||
|
|
||||||
|
#### normalize() / normalise()
|
||||||
|
|
||||||
|
Enhance output image contrast by stretching its luminance to cover the full dynamic range. This typically reduces performance by 30%.
|
||||||
|
|
||||||
### Output options
|
### Output options
|
||||||
|
|
||||||
#### jpeg()
|
#### jpeg()
|
||||||
@@ -322,6 +507,25 @@ Use PNG format for the output image.
|
|||||||
|
|
||||||
Use WebP format for the output image.
|
Use WebP format for the output image.
|
||||||
|
|
||||||
|
#### raw()
|
||||||
|
|
||||||
|
_Requires libvips 7.42.0+_
|
||||||
|
|
||||||
|
Provide raw, uncompressed uint8 (unsigned char) image data for Buffer and Stream based output.
|
||||||
|
|
||||||
|
The number of channels depends on the input image and selected options.
|
||||||
|
|
||||||
|
* 1 channel for images converted to `greyscale()`, with each byte representing one pixel.
|
||||||
|
* 3 channels for colour images without alpha transparency, with bytes ordered \[red, green, blue, red, green, blue, etc.\]).
|
||||||
|
* 4 channels for colour images with alpha transparency, with bytes ordered \[red, green, blue, alpha, red, green, blue, alpha, etc.\].
|
||||||
|
|
||||||
|
#### toFormat(format)
|
||||||
|
|
||||||
|
Convenience method for the above output format methods, where `format` is either:
|
||||||
|
|
||||||
|
* an attribute of the `sharp.format` Object e.g. `sharp.format.jpeg`, or
|
||||||
|
* a String containing `jpeg`, `png`, `webp` or `raw`.
|
||||||
|
|
||||||
#### quality(quality)
|
#### quality(quality)
|
||||||
|
|
||||||
The output quality to use for lossy JPEG, WebP and TIFF output formats. The default quality is `80`.
|
The output quality to use for lossy JPEG, WebP and TIFF output formats. The default quality is `80`.
|
||||||
@@ -332,26 +536,74 @@ The output quality to use for lossy JPEG, WebP and TIFF output formats. The defa
|
|||||||
|
|
||||||
Use progressive (interlace) scan for JPEG and PNG output. This typically reduces compression performance by 30% but results in an image that can be rendered sooner when decompressed.
|
Use progressive (interlace) scan for JPEG and PNG output. This typically reduces compression performance by 30% but results in an image that can be rendered sooner when decompressed.
|
||||||
|
|
||||||
#### withMetadata([boolean])
|
#### withMetadata()
|
||||||
|
|
||||||
Include all metadata (ICC, EXIF, XMP) from the input image in the output image. The default behaviour is to strip all metadata.
|
Include all metadata (EXIF, XMP, IPTC) from the input image in the output image. This will also convert to and add the latest web-friendly v2 sRGB ICC profile.
|
||||||
|
|
||||||
|
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).
|
||||||
|
|
||||||
|
This can improve colour representation at higher quality settings (90+),
|
||||||
|
but usually increases output file size and typically reduces performance by 25%.
|
||||||
|
|
||||||
|
The default behaviour is to use chroma subsampling (4:2:0).
|
||||||
|
|
||||||
|
#### trellisQuantisation() / trellisQuantization()
|
||||||
|
|
||||||
|
_Requires libvips 8.0.0+ compiled against mozjpeg 3.0+_
|
||||||
|
|
||||||
|
An advanced setting to apply the use of
|
||||||
|
[trellis quantisation](http://en.wikipedia.org/wiki/Trellis_quantization) with JPEG output.
|
||||||
|
Reduces file size and slightly increases relative quality at the cost of increased compression time.
|
||||||
|
|
||||||
|
#### overshootDeringing()
|
||||||
|
|
||||||
|
_Requires libvips 8.0.0+ compiled against mozjpeg 3.0+_
|
||||||
|
|
||||||
|
An advanced setting to reduce the effects of
|
||||||
|
[ringing](http://en.wikipedia.org/wiki/Ringing_%28signal%29) in JPEG output,
|
||||||
|
in particular where black text appears on a white background (or vice versa).
|
||||||
|
|
||||||
|
#### optimiseScans() / optimizeScans()
|
||||||
|
|
||||||
|
_Requires libvips 8.0.0+ compiled against mozjpeg 3.0+_
|
||||||
|
|
||||||
|
An advanced setting for progressive (interlace) JPEG output.
|
||||||
|
Calculates which spectrum of DCT coefficients uses the fewest bits.
|
||||||
|
Usually reduces file size at the cost of increased compression time.
|
||||||
|
|
||||||
#### compressionLevel(compressionLevel)
|
#### compressionLevel(compressionLevel)
|
||||||
|
|
||||||
An advanced setting for the _zlib_ compression level of the lossless PNG output format. The default level is `6`.
|
An advanced setting for the _zlib_ compression level of the lossless PNG output format. The default level is `6`.
|
||||||
|
|
||||||
`compressionLevel` is a Number between -1 and 9.
|
`compressionLevel` is a Number between 0 and 9.
|
||||||
|
|
||||||
|
#### withoutAdaptiveFiltering()
|
||||||
|
|
||||||
|
_Requires libvips 7.42.0+_
|
||||||
|
|
||||||
|
An advanced setting to disable adaptive row filtering for the lossless PNG output format.
|
||||||
|
|
||||||
### Output methods
|
### Output methods
|
||||||
|
|
||||||
#### toFile(filename, [callback])
|
#### toFile(filename, [callback])
|
||||||
|
|
||||||
`filename` is a String containing the filename to write the image data to. The format is inferred from the extension, with JPEG, PNG, WebP and TIFF supported.
|
`filename` is a String containing the filename to write the image data to. The format is inferred from the extension, with JPEG, PNG, WebP, TIFF and DZI supported.
|
||||||
|
|
||||||
`callback`, if present, is called with two arguments `(err, info)` where:
|
`callback`, if present, is called with two arguments `(err, info)` where:
|
||||||
|
|
||||||
* `err` contains an error message, if any.
|
* `err` contains an error message, if any.
|
||||||
* `info` contains the output image `format`, `width` and `height`.
|
* `info` contains the output image `format`, `size` (bytes), `width` and `height`.
|
||||||
|
|
||||||
A Promises/A+ promise is returned when `callback` is not provided.
|
A Promises/A+ promise is returned when `callback` is not provided.
|
||||||
|
|
||||||
@@ -363,7 +615,7 @@ Write image data to a Buffer, the format of which will match the input image by
|
|||||||
|
|
||||||
* `err` is an error message, if any.
|
* `err` is an error message, if any.
|
||||||
* `buffer` is the output image data.
|
* `buffer` is the output image data.
|
||||||
* `info` contains the output image `format`, `width` and `height`.
|
* `info` contains the output image `format`, `size` (bytes), `width` and `height`.
|
||||||
|
|
||||||
A Promises/A+ promise is returned when `callback` is not provided.
|
A Promises/A+ promise is returned when `callback` is not provided.
|
||||||
|
|
||||||
@@ -386,7 +638,7 @@ sharp.cache(50, 200); // { current: 49, high: 99, memory: 50, items: 200}
|
|||||||
|
|
||||||
#### sharp.concurrency([threads])
|
#### sharp.concurrency([threads])
|
||||||
|
|
||||||
`threads`, if provided, is the Number of threads _libvips'_ should create for image processing. The default value is the number of CPU cores. A value of `0` will reset to this default.
|
`threads`, if provided, is the Number of threads _libvips'_ should create for processing each image. The default value is the number of CPU cores. A value of `0` will reset to this default.
|
||||||
|
|
||||||
This method always returns the current concurrency.
|
This method always returns the current concurrency.
|
||||||
|
|
||||||
@@ -396,6 +648,8 @@ sharp.concurrency(2); // 2
|
|||||||
sharp.concurrency(0); // 4
|
sharp.concurrency(0); // 4
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The maximum number of images that can be processed in parallel is limited by libuv's `UV_THREADPOOL_SIZE` environment variable.
|
||||||
|
|
||||||
#### sharp.counters()
|
#### sharp.counters()
|
||||||
|
|
||||||
Provides access to internal task counters.
|
Provides access to internal task counters.
|
||||||
@@ -407,23 +661,39 @@ Provides access to internal task counters.
|
|||||||
var counters = sharp.counters(); // { queue: 2, process: 4 }
|
var counters = sharp.counters(); // { queue: 2, process: 4 }
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
A [guide for contributors](https://github.com/lovell/sharp/blob/master/CONTRIBUTING.md) covers reporting bugs, requesting features and submitting code changes.
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
### Ubuntu 12.04
|
### Functional tests
|
||||||
|
|
||||||
|
#### Coverage
|
||||||
|
|
||||||
|
[](https://coveralls.io/r/lovell/sharp?branch=master)
|
||||||
|
|
||||||
|
#### Ubuntu 12.04
|
||||||
|
|
||||||
[](https://travis-ci.org/lovell/sharp)
|
[](https://travis-ci.org/lovell/sharp)
|
||||||
|
|
||||||
### Centos 6.5
|
#### Centos 6.5
|
||||||
|
|
||||||
[](https://snap-ci.com/lovell/sharp/branch/master)
|
[](https://snap-ci.com/lovell/sharp/branch/master)
|
||||||
|
|
||||||
### It worked on my machine
|
#### Windows Server 2012
|
||||||
|
|
||||||
|
[](https://ci.appveyor.com/project/lovell/sharp)
|
||||||
|
|
||||||
|
### Benchmark tests
|
||||||
|
|
||||||
```
|
```
|
||||||
|
cd sharp/test/bench
|
||||||
|
npm install
|
||||||
npm test
|
npm test
|
||||||
```
|
```
|
||||||
|
|
||||||
Running the comparative performance tests requires _ImageMagick_ and _GraphicsMagick_.
|
Requires both _ImageMagick_ and _GraphicsMagick_:
|
||||||
|
|
||||||
```
|
```
|
||||||
brew install imagemagick
|
brew install imagemagick
|
||||||
@@ -431,7 +701,7 @@ brew install graphicsmagick
|
|||||||
```
|
```
|
||||||
|
|
||||||
```
|
```
|
||||||
sudo apt-get install -qq imagemagick graphicsmagick
|
sudo apt-get install -qq imagemagick graphicsmagick libmagickcore-dev
|
||||||
```
|
```
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -444,16 +714,17 @@ sudo yum install -y --enablerepo=epel GraphicsMagick
|
|||||||
|
|
||||||
### Test environment
|
### Test environment
|
||||||
|
|
||||||
* Intel Xeon [L5520](http://ark.intel.com/products/40201/Intel-Xeon-Processor-L5520-8M-Cache-2_26-GHz-5_86-GTs-Intel-QPI) 2.27GHz 8MB cache
|
* AWS EC2 [c3.xlarge](http://aws.amazon.com/ec2/instance-types/#Compute_Optimized)
|
||||||
* Ubuntu 13.10
|
* Ubuntu 14.04
|
||||||
* libvips 7.38.5
|
* libvips 7.40.8
|
||||||
|
* liborc 0.4.22
|
||||||
|
|
||||||
### The contenders
|
### The contenders
|
||||||
|
|
||||||
* [imagemagick-native](https://github.com/mash/node-imagemagick-native) - Supports Buffers only and blocks main V8 thread whilst processing.
|
* [imagemagick-native](https://github.com/mash/node-imagemagick-native) v1.2.2 - Supports Buffers only
|
||||||
* [imagemagick](https://github.com/rsms/node-imagemagick) - Supports filesystem only and "has been unmaintained for a long time".
|
* [imagemagick](https://github.com/yourdeveloper/node-imagemagick) v0.1.3 - Supports filesystem only and "has been unmaintained for a long time".
|
||||||
* [gm](https://github.com/aheckmann/gm) - Fully featured wrapper around GraphicsMagick.
|
* [gm](https://github.com/aheckmann/gm) v1.16.0 - Fully featured wrapper around GraphicsMagick.
|
||||||
* sharp - Caching within libvips disabled to ensure a fair comparison.
|
* sharp v0.6.2 - Caching within libvips disabled to ensure a fair comparison.
|
||||||
|
|
||||||
### The task
|
### The task
|
||||||
|
|
||||||
@@ -463,21 +734,23 @@ Decompress a 2725x2225 JPEG image, resize and crop to 720x480, then compress to
|
|||||||
|
|
||||||
| Module | Input | Output | Ops/sec | Speed-up |
|
| Module | Input | Output | Ops/sec | Speed-up |
|
||||||
| :-------------------- | :----- | :----- | ------: | -------: |
|
| :-------------------- | :----- | :----- | ------: | -------: |
|
||||||
| imagemagick-native | buffer | buffer | 0.97 | 1 |
|
| imagemagick-native | buffer | buffer | 1.58 | 1 |
|
||||||
| imagemagick | file | file | 2.49 | 2.6 |
|
| imagemagick | file | file | 6.23 | 3.9 |
|
||||||
| gm | buffer | file | 3.72 | 3.8 |
|
| gm | buffer | file | 5.32 | 3.4 |
|
||||||
| gm | buffer | buffer | 3.80 | 3.9 |
|
| gm | buffer | buffer | 5.32 | 3.4 |
|
||||||
| gm | file | file | 3.67 | 3.8 |
|
| gm | file | file | 5.36 | 3.4 |
|
||||||
| gm | file | buffer | 3.67 | 3.8 |
|
| gm | file | buffer | 5.36 | 3.4 |
|
||||||
| sharp | buffer | file | 13.62 | 14.0 |
|
| sharp | buffer | file | 22.05 | 14.0 |
|
||||||
| sharp | buffer | buffer | 12.43 | 12.8 |
|
| sharp | buffer | buffer | 22.14 | 14.0 |
|
||||||
| sharp | file | file | 13.02 | 13.4 |
|
| sharp | file | file | 21.79 | 13.8 |
|
||||||
| sharp | file | buffer | 11.15 | 11.5 |
|
| sharp | file | buffer | 21.90 | 13.9 |
|
||||||
| sharp +sharpen | file | buffer | 10.26 | 10.6 |
|
| sharp | stream | stream | 20.87 | 13.2 |
|
||||||
| sharp +progressive | file | buffer | 9.44 | 9.7 |
|
| sharp +promise | file | buffer | 21.89 | 13.9 |
|
||||||
| sharp +sequentialRead | file | buffer | 11.94 | 12.3 |
|
| sharp +sharpen | file | buffer | 19.69 | 12.5 |
|
||||||
|
| sharp +progressive | file | buffer | 16.93 | 10.7 |
|
||||||
|
| sharp +sequentialRead | file | buffer | 21.60 | 13.7 |
|
||||||
|
|
||||||
You can expect much greater performance with caching enabled (default) and using 16+ core machines.
|
You can expect greater performance with caching enabled (default) and using 8+ core machines.
|
||||||
|
|
||||||
## Thanks
|
## Thanks
|
||||||
|
|
||||||
@@ -488,12 +761,22 @@ This module would never have been possible without the help and code contributio
|
|||||||
* [Jonathan Ong](https://github.com/jonathanong)
|
* [Jonathan Ong](https://github.com/jonathanong)
|
||||||
* [Chanon Sajjamanochai](https://github.com/chanon)
|
* [Chanon Sajjamanochai](https://github.com/chanon)
|
||||||
* [Juliano Julio](https://github.com/julianojulio)
|
* [Juliano Julio](https://github.com/julianojulio)
|
||||||
|
* [Daniel Gasienica](https://github.com/gasi)
|
||||||
|
* [Julian Walker](https://github.com/julianwa)
|
||||||
|
* [Amit Pitaru](https://github.com/apitaru)
|
||||||
|
* [Brandon Aaron](https://github.com/brandonaaron)
|
||||||
|
* [Andreas Lind](https://github.com/papandreou)
|
||||||
|
* [Maurus Cuelenaere](https://github.com/mcuelenaere)
|
||||||
|
* [Linus Unneb<65>ck](https://github.com/LinusU)
|
||||||
|
* [Victor Mateevitsi](https://github.com/mvictoras)
|
||||||
|
* [Alaric Holloway](https://github.com/skedastik)
|
||||||
|
* [Bernhard K. Weisshuhn](https://github.com/bkw)
|
||||||
|
|
||||||
Thank you!
|
Thank you!
|
||||||
|
|
||||||
## Licence
|
## Licence
|
||||||
|
|
||||||
Copyright 2013, 2014 Lovell Fuller and contributors.
|
Copyright 2013, 2014, 2015 Lovell Fuller and contributors.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|||||||
25
appveyor.yml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
os: Visual Studio 2014 CTP4
|
||||||
|
platform: Win32
|
||||||
|
configuration: Release
|
||||||
|
environment:
|
||||||
|
VIPS_VERSION_MAJOR_MINOR: 8.0
|
||||||
|
VIPS_VERSION_PATCH: 2
|
||||||
|
VIPS_WARNING: 0
|
||||||
|
matrix:
|
||||||
|
- nodejs_version: "0.10"
|
||||||
|
nodejs_exec: "node"
|
||||||
|
- nodejs_version: "0.12"
|
||||||
|
nodejs_exec: "node"
|
||||||
|
- nodejs_version: "1.0"
|
||||||
|
nodejs_exec: "iojs"
|
||||||
|
install:
|
||||||
|
- ps: $env:VIPS_VERSION = "$env:VIPS_VERSION_MAJOR_MINOR.$env:VIPS_VERSION_PATCH"
|
||||||
|
- ps: Write-Output "Fetching http://www.vips.ecs.soton.ac.uk/supported/$env:VIPS_VERSION_MAJOR_MINOR/win32/vips-dev-$env:VIPS_VERSION.zip"
|
||||||
|
- ps: Start-FileDownload http://www.vips.ecs.soton.ac.uk/supported/$env:VIPS_VERSION_MAJOR_MINOR/win32/vips-dev-$env:VIPS_VERSION.zip -FileName c:\vips-dev-$env:VIPS_VERSION.zip
|
||||||
|
- ps: Invoke-Expression "& 7z -y x c:\vips-dev-$env:VIPS_VERSION.zip -oc:\ | FIND /V `"ing `""
|
||||||
|
- ps: $env:VIPS_HOME = "c:\vips-dev-$env:VIPS_VERSION"
|
||||||
|
- ps: $env:PATH = "$env:VIPS_HOME\bin;$env:PATH"
|
||||||
|
- ps: Install-Product node $env:nodejs_version x86
|
||||||
|
- npm install --arch=ia32 --msvs_version=2013
|
||||||
|
test_script:
|
||||||
|
- npm run-script test-win32-%nodejs_exec%
|
||||||
81
binding.gyp
@@ -1,23 +1,78 @@
|
|||||||
{
|
{
|
||||||
'targets': [{
|
'targets': [{
|
||||||
'target_name': 'sharp',
|
'target_name': 'sharp',
|
||||||
'sources': ['src/sharp.cc'],
|
'sources': [
|
||||||
'variables': {
|
'src/common.cc',
|
||||||
'PKG_CONFIG_PATH': '<!(which brew >/dev/null 2>&1 && eval $(brew --env) && echo $PKG_CONFIG_LIBDIR || true):$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig:/usr/lib/pkgconfig'
|
'src/utilities.cc',
|
||||||
},
|
'src/metadata.cc',
|
||||||
'libraries': [
|
'src/resize.cc',
|
||||||
'<!(PKG_CONFIG_PATH="<(PKG_CONFIG_PATH)" pkg-config --libs vips)'
|
'src/sharp.cc'
|
||||||
],
|
],
|
||||||
'include_dirs': [
|
'conditions': [
|
||||||
'<!(PKG_CONFIG_PATH="<(PKG_CONFIG_PATH)" pkg-config --cflags vips glib-2.0)',
|
['OS=="win"', {
|
||||||
'<!(node -e "require(\'nan\')")'
|
'library_dirs': [
|
||||||
|
'$(VIPS_HOME)/lib'
|
||||||
|
],
|
||||||
|
'libraries': [
|
||||||
|
'libvips.dll.a',
|
||||||
|
'glib-2.0.lib',
|
||||||
|
'gobject-2.0.lib',
|
||||||
|
'gthread-2.0.lib',
|
||||||
|
'gmodule-2.0.lib',
|
||||||
|
'liblcms2.dll.a',
|
||||||
|
'libxml2.lib',
|
||||||
|
'intl.lib',
|
||||||
|
'libjpeg.dll.a',
|
||||||
|
'libexif.dll.a',
|
||||||
|
'libpng.lib',
|
||||||
|
'libtiff.dll.a',
|
||||||
|
'libMagickWand-6.Q16.dll.a',
|
||||||
|
'libMagickCore-6.Q16.dll.a',
|
||||||
|
'pango-1.0.lib',
|
||||||
|
'pangoft2-1.0.lib',
|
||||||
|
'libgsf-1.dll.a',
|
||||||
|
'libopenslide.dll.a',
|
||||||
|
'libfftw3.dll.a'
|
||||||
|
],
|
||||||
|
'include_dirs': [
|
||||||
|
'$(VIPS_HOME)/include',
|
||||||
|
'$(VIPS_HOME)/include/glib-2.0',
|
||||||
|
'$(VIPS_HOME)/lib/glib-2.0/include',
|
||||||
|
'<!(node -e "require(\'nan\')")'
|
||||||
|
]
|
||||||
|
}, {
|
||||||
|
'variables': {
|
||||||
|
'PKG_CONFIG_PATH': '<!(which brew >/dev/null 2>&1 && eval $(brew --env) && echo $PKG_CONFIG_LIBDIR || true):$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig:/usr/lib/pkgconfig'
|
||||||
|
},
|
||||||
|
'libraries': [
|
||||||
|
'<!(PKG_CONFIG_PATH="<(PKG_CONFIG_PATH)" pkg-config --libs vips)'
|
||||||
|
],
|
||||||
|
'include_dirs': [
|
||||||
|
'<!(PKG_CONFIG_PATH="<(PKG_CONFIG_PATH)" pkg-config --cflags vips glib-2.0)',
|
||||||
|
'<!(node -e "require(\'nan\')")'
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
],
|
||||||
|
'cflags_cc': [
|
||||||
|
'-std=c++0x',
|
||||||
|
'-fexceptions',
|
||||||
|
'-Wall',
|
||||||
|
'-O3'
|
||||||
],
|
],
|
||||||
'cflags': ['-fexceptions', '-Wall', '-O3'],
|
|
||||||
'cflags_cc': ['-std=c++0x', '-fexceptions', '-Wall', '-O3'],
|
|
||||||
'xcode_settings': {
|
'xcode_settings': {
|
||||||
'OTHER_CFLAGS': ['-std=c++11', '-stdlib=libc++', '-fexceptions', '-Wall', '-O3'],
|
'OTHER_CPLUSPLUSFLAGS': [
|
||||||
'OTHER_CPLUSPLUSFLAGS': ['-std=c++11', '-stdlib=libc++', '-fexceptions', '-Wall', '-O3'],
|
'-std=c++11',
|
||||||
|
'-stdlib=libc++',
|
||||||
|
'-fexceptions',
|
||||||
|
'-Wall',
|
||||||
|
'-O3'
|
||||||
|
],
|
||||||
'MACOSX_DEPLOYMENT_TARGET': '10.7'
|
'MACOSX_DEPLOYMENT_TARGET': '10.7'
|
||||||
|
},
|
||||||
|
'msvs_settings': {
|
||||||
|
'VCCLCompilerTool': {
|
||||||
|
'ExceptionHandling': 1 # /EHsc
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
icc/USWebCoatedSWOP.icc
Normal file
BIN
icc/sRGB_IEC61966-2-1_black_scaled.icc
Normal file
487
index.js
@@ -1,10 +1,22 @@
|
|||||||
/*jslint node: true */
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
var path = require('path');
|
||||||
var util = require('util');
|
var util = require('util');
|
||||||
var stream = require('stream');
|
var stream = require('stream');
|
||||||
var Promise = require('bluebird');
|
var events = require('events');
|
||||||
|
|
||||||
|
var semver = require('semver');
|
||||||
|
var color = require('color');
|
||||||
|
var BluebirdPromise = require('bluebird');
|
||||||
|
|
||||||
var sharp = require('./build/Release/sharp');
|
var sharp = require('./build/Release/sharp');
|
||||||
|
var libvipsVersion = sharp.libvipsVersion();
|
||||||
|
|
||||||
|
var maximum = {
|
||||||
|
width: 0x3FFF,
|
||||||
|
height: 0x3FFF,
|
||||||
|
pixels: Math.pow(0x3FFF, 2)
|
||||||
|
};
|
||||||
|
|
||||||
var Sharp = function(input) {
|
var Sharp = function(input) {
|
||||||
if (!(this instanceof Sharp)) {
|
if (!(this instanceof Sharp)) {
|
||||||
@@ -12,34 +24,67 @@ var Sharp = function(input) {
|
|||||||
}
|
}
|
||||||
stream.Duplex.call(this);
|
stream.Duplex.call(this);
|
||||||
this.options = {
|
this.options = {
|
||||||
|
// input options
|
||||||
|
bufferIn: null,
|
||||||
|
streamIn: false,
|
||||||
|
sequentialRead: false,
|
||||||
|
limitInputPixels: maximum.pixels,
|
||||||
|
// ICC profiles
|
||||||
|
iccProfilePath: path.join(__dirname, 'icc') + path.sep,
|
||||||
|
// resize options
|
||||||
|
topOffsetPre: -1,
|
||||||
|
leftOffsetPre: -1,
|
||||||
|
widthPre: -1,
|
||||||
|
heightPre: -1,
|
||||||
|
topOffsetPost: -1,
|
||||||
|
leftOffsetPost: -1,
|
||||||
|
widthPost: -1,
|
||||||
|
heightPost: -1,
|
||||||
width: -1,
|
width: -1,
|
||||||
height: -1,
|
height: -1,
|
||||||
canvas: 'c',
|
canvas: 'crop',
|
||||||
gravity: 0,
|
gravity: 0,
|
||||||
angle: 0,
|
angle: 0,
|
||||||
|
rotateBeforePreExtract: false,
|
||||||
|
flip: false,
|
||||||
|
flop: false,
|
||||||
withoutEnlargement: false,
|
withoutEnlargement: false,
|
||||||
sharpen: false,
|
|
||||||
interpolator: 'bilinear',
|
interpolator: 'bilinear',
|
||||||
|
// operations
|
||||||
|
background: [0, 0, 0, 255],
|
||||||
|
flatten: false,
|
||||||
|
blurSigma: 0,
|
||||||
|
sharpenRadius: 0,
|
||||||
|
sharpenFlat: 1,
|
||||||
|
sharpenJagged: 2,
|
||||||
gamma: 0,
|
gamma: 0,
|
||||||
|
greyscale: false,
|
||||||
|
normalize: 0,
|
||||||
|
// output options
|
||||||
|
output: '__input',
|
||||||
progressive: false,
|
progressive: false,
|
||||||
sequentialRead: false,
|
|
||||||
quality: 80,
|
quality: 80,
|
||||||
compressionLevel: 6,
|
compressionLevel: 6,
|
||||||
streamIn: false,
|
withoutAdaptiveFiltering: false,
|
||||||
|
withoutChromaSubsampling: false,
|
||||||
|
trellisQuantisation: false,
|
||||||
|
overshootDeringing: false,
|
||||||
|
optimiseScans: false,
|
||||||
streamOut: false,
|
streamOut: false,
|
||||||
withMetadata: false,
|
withMetadata: false,
|
||||||
output: '__input'
|
tileSize: 256,
|
||||||
|
tileOverlap: 0,
|
||||||
|
// Function to notify of queue length changes
|
||||||
|
queueListener: function(queueLength) {
|
||||||
|
module.exports.queue.emit('change', queueLength);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
if (typeof input === 'string') {
|
if (typeof input === 'string') {
|
||||||
// input=file
|
// input=file
|
||||||
this.options.fileIn = input;
|
this.options.fileIn = input;
|
||||||
} else if (typeof input === 'object' && input instanceof Buffer) {
|
} else if (typeof input === 'object' && input instanceof Buffer) {
|
||||||
// input=buffer
|
// input=buffer
|
||||||
if (input.length > 0) {
|
this.options.bufferIn = input;
|
||||||
this.options.bufferIn = input;
|
|
||||||
} else {
|
|
||||||
throw new Error('Buffer is empty');
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// input=stream
|
// input=stream
|
||||||
this.options.streamIn = true;
|
this.options.streamIn = true;
|
||||||
@@ -49,22 +94,33 @@ var Sharp = function(input) {
|
|||||||
module.exports = Sharp;
|
module.exports = Sharp;
|
||||||
util.inherits(Sharp, stream.Duplex);
|
util.inherits(Sharp, stream.Duplex);
|
||||||
|
|
||||||
|
/*
|
||||||
|
EventEmitter singleton emits queue length 'change' events
|
||||||
|
*/
|
||||||
|
module.exports.queue = new events.EventEmitter();
|
||||||
|
|
||||||
|
/*
|
||||||
|
Supported image formats
|
||||||
|
*/
|
||||||
|
module.exports.format = sharp.format();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Handle incoming chunk on Writable Stream
|
Handle incoming chunk on Writable Stream
|
||||||
*/
|
*/
|
||||||
Sharp.prototype._write = function(chunk, encoding, callback) {
|
Sharp.prototype._write = function(chunk, encoding, callback) {
|
||||||
|
/*jslint unused: false */
|
||||||
if (this.options.streamIn) {
|
if (this.options.streamIn) {
|
||||||
if (typeof chunk === 'object' || chunk instanceof Buffer) {
|
if (typeof chunk === 'object' && chunk instanceof Buffer) {
|
||||||
if (typeof this.options.bufferIn === 'undefined') {
|
if (this.options.bufferIn instanceof Buffer) {
|
||||||
// Create new Buffer
|
|
||||||
this.options.bufferIn = new Buffer(chunk.length);
|
|
||||||
chunk.copy(this.options.bufferIn);
|
|
||||||
} else {
|
|
||||||
// Append to existing Buffer
|
// Append to existing Buffer
|
||||||
this.options.bufferIn = Buffer.concat(
|
this.options.bufferIn = Buffer.concat(
|
||||||
[this.options.bufferIn, chunk],
|
[this.options.bufferIn, chunk],
|
||||||
this.options.bufferIn.length + chunk.length
|
this.options.bufferIn.length + chunk.length
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
// Create new Buffer
|
||||||
|
this.options.bufferIn = new Buffer(chunk.length);
|
||||||
|
chunk.copy(this.options.bufferIn);
|
||||||
}
|
}
|
||||||
callback();
|
callback();
|
||||||
} else {
|
} else {
|
||||||
@@ -79,30 +135,71 @@ Sharp.prototype._write = function(chunk, encoding, callback) {
|
|||||||
module.exports.gravity = {'center': 0, 'centre': 0, 'north': 1, 'east': 2, 'south': 3, 'west': 4};
|
module.exports.gravity = {'center': 0, 'centre': 0, 'north': 1, 'east': 2, 'south': 3, 'west': 4};
|
||||||
|
|
||||||
Sharp.prototype.crop = function(gravity) {
|
Sharp.prototype.crop = function(gravity) {
|
||||||
this.options.canvas = 'c';
|
this.options.canvas = 'crop';
|
||||||
if (typeof gravity !== 'undefined') {
|
if (typeof gravity === 'number' && !Number.isNaN(gravity) && gravity >= 0 && gravity <= 4) {
|
||||||
// Is this a supported gravity?
|
this.options.gravity = gravity;
|
||||||
if (!Number.isNaN(gravity) && gravity >= 0 && gravity <= 4) {
|
} else {
|
||||||
this.options.gravity = gravity;
|
throw new Error('Unsupported crop gravity ' + gravity);
|
||||||
} else {
|
|
||||||
throw new Error('Unsupported crop gravity ' + gravity);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
Sharp.prototype.embedWhite = function() {
|
Sharp.prototype.extract = function(topOffset, leftOffset, width, height) {
|
||||||
this.options.canvas = 'w';
|
/*jslint unused: false */
|
||||||
|
var suffix = this.options.width === -1 && this.options.height === -1 ? 'Pre' : 'Post';
|
||||||
|
var values = arguments;
|
||||||
|
['topOffset', 'leftOffset', 'width', 'height'].forEach(function(name, index) {
|
||||||
|
if (typeof values[index] === 'number' && !Number.isNaN(values[index]) && (values[index] % 1 === 0) && values[index] >= 0) {
|
||||||
|
this.options[name + suffix] = values[index];
|
||||||
|
} else {
|
||||||
|
throw new Error('Non-integer value for ' + name + ' of ' + values[index]);
|
||||||
|
}
|
||||||
|
}.bind(this));
|
||||||
|
// Ensure existing rotation occurs before pre-resize extraction
|
||||||
|
if (suffix === 'Pre' && this.options.angle !== 0) {
|
||||||
|
this.options.rotateBeforePreExtract = true;
|
||||||
|
}
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
Sharp.prototype.embedBlack = function() {
|
/*
|
||||||
this.options.canvas = 'b';
|
Set the background colour for embed and flatten operations.
|
||||||
|
Delegates to the 'Color' module, which can throw an Error
|
||||||
|
but is liberal in what it accepts, clamping values to sensible min/max.
|
||||||
|
*/
|
||||||
|
Sharp.prototype.background = function(rgba) {
|
||||||
|
var colour = color(rgba);
|
||||||
|
this.options.background = colour.rgbArray();
|
||||||
|
this.options.background.push(colour.alpha() * 255);
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
Sharp.prototype.embed = function() {
|
||||||
|
this.options.canvas = 'embed';
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
Sharp.prototype.max = function() {
|
Sharp.prototype.max = function() {
|
||||||
this.options.canvas = 'm';
|
this.options.canvas = 'max';
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
Sharp.prototype.min = function() {
|
||||||
|
this.options.canvas = 'min';
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Ignoring the aspect ratio of the input, stretch the image to
|
||||||
|
the exact width and/or height provided via the resize method.
|
||||||
|
*/
|
||||||
|
Sharp.prototype.ignoreAspectRatio = function() {
|
||||||
|
this.options.canvas = 'ignore_aspect';
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
Sharp.prototype.flatten = function(flatten) {
|
||||||
|
this.options.flatten = (typeof flatten === 'boolean') ? flatten : true;
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -121,6 +218,22 @@ Sharp.prototype.rotate = function(angle) {
|
|||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Flip the image vertically, about the Y axis
|
||||||
|
*/
|
||||||
|
Sharp.prototype.flip = function(flip) {
|
||||||
|
this.options.flip = (typeof flip === 'boolean') ? flip : true;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Flop the image horizontally, about the X axis
|
||||||
|
*/
|
||||||
|
Sharp.prototype.flop = function(flop) {
|
||||||
|
this.options.flop = (typeof flop === 'boolean') ? flop : true;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Do not enlarge the output if the input width *or* height are already less than the required dimensions
|
Do not enlarge the output if the input width *or* height are already less than the required dimensions
|
||||||
This is equivalent to GraphicsMagick's ">" geometry option:
|
This is equivalent to GraphicsMagick's ">" geometry option:
|
||||||
@@ -131,8 +244,64 @@ Sharp.prototype.withoutEnlargement = function(withoutEnlargement) {
|
|||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
Sharp.prototype.sharpen = function(sharpen) {
|
/*
|
||||||
this.options.sharpen = (typeof sharpen === 'boolean') ? sharpen : true;
|
Blur the output image.
|
||||||
|
Call without a sigma to use a fast, mild blur.
|
||||||
|
Call with a sigma to use a slower, more accurate Gaussian blur.
|
||||||
|
*/
|
||||||
|
Sharp.prototype.blur = function(sigma) {
|
||||||
|
if (typeof sigma === 'undefined') {
|
||||||
|
// No arguments: default to mild blur
|
||||||
|
this.options.blurSigma = -1;
|
||||||
|
} else if (typeof sigma === 'boolean') {
|
||||||
|
// Boolean argument: apply mild blur?
|
||||||
|
this.options.blurSigma = sigma ? -1 : 0;
|
||||||
|
} else if (typeof sigma === 'number' && !Number.isNaN(sigma) && sigma >= 0.3 && sigma <= 1000) {
|
||||||
|
// Numeric argument: specific sigma
|
||||||
|
this.options.blurSigma = sigma;
|
||||||
|
} else {
|
||||||
|
throw new Error('Invalid blur sigma (0.3 to 1000.0) ' + sigma);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Sharpen the output image.
|
||||||
|
Call without a radius to use a fast, mild sharpen.
|
||||||
|
Call with a radius to use a slow, accurate sharpen using the L of LAB colour space.
|
||||||
|
radius - size of mask in pixels, must be integer
|
||||||
|
flat - level of "flat" area sharpen, default 1
|
||||||
|
jagged - level of "jagged" area sharpen, default 2
|
||||||
|
*/
|
||||||
|
Sharp.prototype.sharpen = function(radius, flat, jagged) {
|
||||||
|
if (typeof radius === 'undefined') {
|
||||||
|
// No arguments: default to mild sharpen
|
||||||
|
this.options.sharpenRadius = -1;
|
||||||
|
} else if (typeof radius === 'boolean') {
|
||||||
|
// Boolean argument: apply mild sharpen?
|
||||||
|
this.options.sharpenRadius = radius ? -1 : 0;
|
||||||
|
} else if (typeof radius === 'number' && !Number.isNaN(radius) && (radius % 1 === 0) && radius >= 1) {
|
||||||
|
// Numeric argument: specific radius
|
||||||
|
this.options.sharpenRadius = radius;
|
||||||
|
// Control over flat areas
|
||||||
|
if (typeof flat !== 'undefined' && flat !== null) {
|
||||||
|
if (typeof flat === 'number' && !Number.isNaN(flat) && flat >= 0) {
|
||||||
|
this.options.sharpenFlat = flat;
|
||||||
|
} else {
|
||||||
|
throw new Error('Invalid sharpen level for flat areas ' + flat + ' (expected >= 0)');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Control over jagged areas
|
||||||
|
if (typeof jagged !== 'undefined' && jagged !== null) {
|
||||||
|
if (typeof jagged === 'number' && !Number.isNaN(jagged) && jagged >= 0) {
|
||||||
|
this.options.sharpenJagged = jagged;
|
||||||
|
} else {
|
||||||
|
throw new Error('Invalid sharpen level for jagged areas ' + jagged + ' (expected >= 0)');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error('Invalid sharpen radius ' + radius + ' (expected integer >= 1)');
|
||||||
|
}
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -148,23 +317,21 @@ module.exports.interpolator = {
|
|||||||
vertexSplitQuadraticBasisSpline: 'vsqbs'
|
vertexSplitQuadraticBasisSpline: 'vsqbs'
|
||||||
};
|
};
|
||||||
Sharp.prototype.interpolateWith = function(interpolator) {
|
Sharp.prototype.interpolateWith = function(interpolator) {
|
||||||
this.options.interpolator = interpolator;
|
var isValid = false;
|
||||||
|
for (var key in module.exports.interpolator) {
|
||||||
|
if (module.exports.interpolator[key] === interpolator) {
|
||||||
|
isValid = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isValid) {
|
||||||
|
this.options.interpolator = interpolator;
|
||||||
|
} else {
|
||||||
|
throw new Error('Invalid interpolator ' + interpolator);
|
||||||
|
}
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
|
||||||
Deprecated interpolation methods, to be removed in v0.7.0
|
|
||||||
*/
|
|
||||||
Sharp.prototype.bilinearInterpolation = util.deprecate(function() {
|
|
||||||
return this.interpolateWith(module.exports.interpolator.bilinear);
|
|
||||||
}, 'bilinearInterpolation() is deprecated, use interpolateWith(sharp.interpolator.bilinear) instead');
|
|
||||||
Sharp.prototype.bicubicInterpolation = util.deprecate(function() {
|
|
||||||
return this.interpolateWith(module.exports.interpolator.bicubic);
|
|
||||||
}, 'bicubicInterpolation() is deprecated, use interpolateWith(sharp.interpolator.bicubic) instead');
|
|
||||||
Sharp.prototype.nohaloInterpolation = util.deprecate(function() {
|
|
||||||
return this.interpolateWith(module.exports.interpolator.nohalo);
|
|
||||||
}, 'nohaloInterpolation() is deprecated, use interpolateWith(sharp.interpolator.nohalo) instead');
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Darken image pre-resize (1/gamma) and brighten post-resize (gamma).
|
Darken image pre-resize (1/gamma) and brighten post-resize (gamma).
|
||||||
Improves brightness of resized image in non-linear colour spaces.
|
Improves brightness of resized image in non-linear colour spaces.
|
||||||
@@ -181,6 +348,28 @@ Sharp.prototype.gamma = function(gamma) {
|
|||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Enhance output image contrast by stretching its luminance to cover the full dynamic range
|
||||||
|
*/
|
||||||
|
Sharp.prototype.normalize = function(normalize) {
|
||||||
|
if (process.platform !== 'win32') {
|
||||||
|
this.options.normalize = (typeof normalize === 'boolean') ? normalize : true;
|
||||||
|
} else {
|
||||||
|
console.error('normalize unavailable on win32 platform');
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
Sharp.prototype.normalise = Sharp.prototype.normalize;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Convert to greyscale
|
||||||
|
*/
|
||||||
|
Sharp.prototype.greyscale = function(greyscale) {
|
||||||
|
this.options.greyscale = (typeof greyscale === 'boolean') ? greyscale : true;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
Sharp.prototype.grayscale = Sharp.prototype.greyscale;
|
||||||
|
|
||||||
Sharp.prototype.progressive = function(progressive) {
|
Sharp.prototype.progressive = function(progressive) {
|
||||||
this.options.progressive = (typeof progressive === 'boolean') ? progressive : true;
|
this.options.progressive = (typeof progressive === 'boolean') ? progressive : true;
|
||||||
return this;
|
return this;
|
||||||
@@ -200,42 +389,148 @@ Sharp.prototype.quality = function(quality) {
|
|||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
zlib compression level for PNG output
|
||||||
|
*/
|
||||||
Sharp.prototype.compressionLevel = function(compressionLevel) {
|
Sharp.prototype.compressionLevel = function(compressionLevel) {
|
||||||
if (!Number.isNaN(compressionLevel) && compressionLevel >= -1 && compressionLevel <= 9) {
|
if (!Number.isNaN(compressionLevel) && compressionLevel >= 0 && compressionLevel <= 9) {
|
||||||
this.options.compressionLevel = compressionLevel;
|
this.options.compressionLevel = compressionLevel;
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Invalid compressionLevel (-1 to 9) ' + compressionLevel);
|
throw new Error('Invalid compressionLevel (0 to 9) ' + compressionLevel);
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Disable the use of adaptive row filtering for PNG output - requires libvips 7.42.0+
|
||||||
|
*/
|
||||||
|
Sharp.prototype.withoutAdaptiveFiltering = function(withoutAdaptiveFiltering) {
|
||||||
|
if (semver.gte(libvipsVersion, '7.42.0')) {
|
||||||
|
this.options.withoutAdaptiveFiltering = (typeof withoutAdaptiveFiltering === 'boolean') ? withoutAdaptiveFiltering : true;
|
||||||
|
} else {
|
||||||
|
console.error('withoutAdaptiveFiltering requires libvips 7.41.0+');
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Disable the use of chroma subsampling for JPEG output
|
||||||
|
*/
|
||||||
|
Sharp.prototype.withoutChromaSubsampling = function(withoutChromaSubsampling) {
|
||||||
|
this.options.withoutChromaSubsampling = (typeof withoutChromaSubsampling === 'boolean') ? withoutChromaSubsampling : true;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Apply trellis quantisation to JPEG output - requires libvips 8.0.0+ compiled against mozjpeg 3.0+
|
||||||
|
*/
|
||||||
|
Sharp.prototype.trellisQuantisation = function(trellisQuantisation) {
|
||||||
|
if (semver.gte(libvipsVersion, '8.0.0')) {
|
||||||
|
this.options.trellisQuantisation = (typeof trellisQuantisation === 'boolean') ? trellisQuantisation : true;
|
||||||
|
} else {
|
||||||
|
console.error('trellisQuantisation requires libvips 8.0.0+');
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
Sharp.prototype.trellisQuantization = Sharp.prototype.trellisQuantisation;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Apply overshoot deringing to JPEG output - requires libvips 8.0.0+ compiled against mozjpeg 3.0+
|
||||||
|
*/
|
||||||
|
Sharp.prototype.overshootDeringing = function(overshootDeringing) {
|
||||||
|
if (semver.gte(libvipsVersion, '8.0.0')) {
|
||||||
|
this.options.overshootDeringing = (typeof overshootDeringing === 'boolean') ? overshootDeringing : true;
|
||||||
|
} else {
|
||||||
|
console.error('overshootDeringing requires libvips 8.0.0+');
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Optimise scans in progressive JPEG output - requires libvips 8.0.0+ compiled against mozjpeg 3.0+
|
||||||
|
*/
|
||||||
|
Sharp.prototype.optimiseScans = function(optimiseScans) {
|
||||||
|
if (semver.gte(libvipsVersion, '8.0.0')) {
|
||||||
|
this.options.optimiseScans = (typeof optimiseScans === 'boolean') ? optimiseScans : true;
|
||||||
|
if (this.options.optimiseScans) {
|
||||||
|
this.progressive();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error('optimiseScans requires libvips 8.0.0+');
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
Sharp.prototype.optimizeScans = Sharp.prototype.optimiseScans;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Include all metadata (EXIF, XMP, IPTC) from the input image in the output image
|
||||||
|
*/
|
||||||
Sharp.prototype.withMetadata = function(withMetadata) {
|
Sharp.prototype.withMetadata = function(withMetadata) {
|
||||||
this.options.withMetadata = (typeof withMetadata === 'boolean') ? withMetadata : true;
|
this.options.withMetadata = (typeof withMetadata === 'boolean') ? withMetadata : true;
|
||||||
return this;
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Tile size and overlap for Deep Zoom output
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
this.options.tileOverlap = overlap;
|
||||||
|
} else {
|
||||||
|
throw new Error('Invalid tile overlap (0 to 8192) ' + overlap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
Sharp.prototype.resize = function(width, height) {
|
Sharp.prototype.resize = function(width, height) {
|
||||||
if (!width) {
|
if (!width) {
|
||||||
this.options.width = -1;
|
this.options.width = -1;
|
||||||
} else {
|
} else {
|
||||||
if (!Number.isNaN(width)) {
|
if (typeof width === 'number' && !Number.isNaN(width) && width % 1 === 0 && width > 0 && width <= maximum.width) {
|
||||||
this.options.width = width;
|
this.options.width = width;
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Invalid width ' + width);
|
throw new Error('Invalid width (1 to ' + maximum.width + ') ' + width);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!height) {
|
if (!height) {
|
||||||
this.options.height = -1;
|
this.options.height = -1;
|
||||||
} else {
|
} else {
|
||||||
if (!Number.isNaN(height)) {
|
if (typeof height === 'number' && !Number.isNaN(height) && height % 1 === 0 && height > 0 && height <= maximum.height) {
|
||||||
this.options.height = height;
|
this.options.height = height;
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Invalid height ' + height);
|
throw new Error('Invalid height (1 to ' + maximum.height + ') ' + height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Limit the total number of pixels for input images
|
||||||
|
Assumes the image dimensions contained in the file header can be trusted
|
||||||
|
*/
|
||||||
|
Sharp.prototype.limitInputPixels = function(limit) {
|
||||||
|
if (typeof limit === 'number' && !Number.isNaN(limit) && limit % 1 === 0 && limit > 0) {
|
||||||
|
this.options.limitInputPixels = limit;
|
||||||
|
} else {
|
||||||
|
throw new Error('Invalid pixel limit (1 to ' + maximum.pixels + ') ' + limit);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Write output image data to a file
|
Write output image data to a file
|
||||||
*/
|
*/
|
||||||
@@ -245,7 +540,7 @@ Sharp.prototype.toFile = function(output, callback) {
|
|||||||
if (typeof callback === 'function') {
|
if (typeof callback === 'function') {
|
||||||
callback(errOutputInvalid);
|
callback(errOutputInvalid);
|
||||||
} else {
|
} else {
|
||||||
return Promise.reject(errOutputInvalid);
|
return BluebirdPromise.reject(errOutputInvalid);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (this.options.fileIn === output) {
|
if (this.options.fileIn === output) {
|
||||||
@@ -253,7 +548,7 @@ Sharp.prototype.toFile = function(output, callback) {
|
|||||||
if (typeof callback === 'function') {
|
if (typeof callback === 'function') {
|
||||||
callback(errOutputIsInput);
|
callback(errOutputIsInput);
|
||||||
} else {
|
} else {
|
||||||
return Promise.reject(errOutputIsInput);
|
return BluebirdPromise.reject(errOutputIsInput);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.options.output = output;
|
this.options.output = output;
|
||||||
@@ -263,36 +558,63 @@ Sharp.prototype.toFile = function(output, callback) {
|
|||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Write output to a Buffer
|
||||||
|
*/
|
||||||
Sharp.prototype.toBuffer = function(callback) {
|
Sharp.prototype.toBuffer = function(callback) {
|
||||||
return this._sharp(callback);
|
return this._sharp(callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Force JPEG output
|
||||||
|
*/
|
||||||
Sharp.prototype.jpeg = function() {
|
Sharp.prototype.jpeg = function() {
|
||||||
this.options.output = '__jpeg';
|
this.options.output = '__jpeg';
|
||||||
if (arguments.length > 0) {
|
|
||||||
console.error('Use of the jpeg() method with a callback is deprecated in 0.6.x and will be removed in 0.7.x');
|
|
||||||
console.error('Please add toFile(), toBuffer() or Stream methods e.g. pipe() for JPEG output');
|
|
||||||
this._sharp(arguments);
|
|
||||||
}
|
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Force PNG output
|
||||||
|
*/
|
||||||
Sharp.prototype.png = function() {
|
Sharp.prototype.png = function() {
|
||||||
this.options.output = '__png';
|
this.options.output = '__png';
|
||||||
if (arguments.length > 0) {
|
return this;
|
||||||
console.error('Use of the png() method with a callback is deprecated in 0.6.x and will be removed in 0.7.x');
|
};
|
||||||
console.error('Please add toFile(), toBuffer() or Stream methods e.g. pipe() for PNG output');
|
|
||||||
this._sharp(arguments);
|
/*
|
||||||
|
Force WebP output
|
||||||
|
*/
|
||||||
|
Sharp.prototype.webp = function() {
|
||||||
|
this.options.output = '__webp';
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Force raw, uint8 output
|
||||||
|
*/
|
||||||
|
Sharp.prototype.raw = function() {
|
||||||
|
var supportsRawOutput = module.exports.format.raw.output;
|
||||||
|
if (supportsRawOutput.file || supportsRawOutput.buffer || supportsRawOutput.stream) {
|
||||||
|
this.options.output = '__raw';
|
||||||
|
} else {
|
||||||
|
console.error('Raw output requires libvips 7.42.0+');
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
Sharp.prototype.webp = function() {
|
/*
|
||||||
this.options.output = '__webp';
|
Force output to a given format
|
||||||
if (arguments.length > 0) {
|
@param format is either the id as a String or an Object with an 'id' attribute
|
||||||
console.error('Use of the webp() method with a callback is deprecated in 0.6.x and will be removed in 0.7.x');
|
*/
|
||||||
console.error('Please add toFile(), toBuffer() or Stream methods e.g. pipe() for WebP output');
|
Sharp.prototype.toFormat = function(format) {
|
||||||
this._sharp(arguments);
|
var id = format;
|
||||||
|
if (typeof format === 'object') {
|
||||||
|
id = format.id;
|
||||||
|
}
|
||||||
|
if (typeof id === 'string' && typeof module.exports.format[id] === 'object' && typeof this[id] === 'function') {
|
||||||
|
this[id]();
|
||||||
|
} else {
|
||||||
|
throw new Error('Unsupported format ' + format);
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
@@ -355,7 +677,7 @@ Sharp.prototype._sharp = function(callback) {
|
|||||||
// output=promise
|
// output=promise
|
||||||
if (this.options.streamIn) {
|
if (this.options.streamIn) {
|
||||||
// output=promise, input=stream
|
// output=promise, input=stream
|
||||||
return new Promise(function(resolve, reject) {
|
return new BluebirdPromise(function(resolve, reject) {
|
||||||
that.on('finish', function() {
|
that.on('finish', function() {
|
||||||
sharp.resize(that.options, function(err, data) {
|
sharp.resize(that.options, function(err, data) {
|
||||||
if (err) {
|
if (err) {
|
||||||
@@ -368,7 +690,7 @@ Sharp.prototype._sharp = function(callback) {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// output=promise, input=file/buffer
|
// output=promise, input=file/buffer
|
||||||
return new Promise(function(resolve, reject) {
|
return new BluebirdPromise(function(resolve, reject) {
|
||||||
sharp.resize(that.options, function(err, data) {
|
sharp.resize(that.options, function(err, data) {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
@@ -398,7 +720,7 @@ Sharp.prototype.metadata = function(callback) {
|
|||||||
return this;
|
return this;
|
||||||
} else {
|
} else {
|
||||||
if (this.options.streamIn) {
|
if (this.options.streamIn) {
|
||||||
return new Promise(function(resolve, reject) {
|
return new BluebirdPromise(function(resolve, reject) {
|
||||||
that.on('finish', function() {
|
that.on('finish', function() {
|
||||||
sharp.metadata(that.options, function(err, data) {
|
sharp.metadata(that.options, function(err, data) {
|
||||||
if (err) {
|
if (err) {
|
||||||
@@ -410,7 +732,7 @@ Sharp.prototype.metadata = function(callback) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return new Promise(function(resolve, reject) {
|
return new BluebirdPromise(function(resolve, reject) {
|
||||||
sharp.metadata(that.options, function(err, data) {
|
sharp.metadata(that.options, function(err, data) {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
@@ -427,10 +749,10 @@ Sharp.prototype.metadata = function(callback) {
|
|||||||
Get and set cache memory and item limits
|
Get and set cache memory and item limits
|
||||||
*/
|
*/
|
||||||
module.exports.cache = function(memory, items) {
|
module.exports.cache = function(memory, items) {
|
||||||
if (Number.isNaN(memory)) {
|
if (typeof memory !== 'number' || Number.isNaN(memory)) {
|
||||||
memory = null;
|
memory = null;
|
||||||
}
|
}
|
||||||
if (Number.isNaN(items)) {
|
if (typeof items !== 'number' || Number.isNaN(items)) {
|
||||||
items = null;
|
items = null;
|
||||||
}
|
}
|
||||||
return sharp.cache(memory, items);
|
return sharp.cache(memory, items);
|
||||||
@@ -440,7 +762,7 @@ module.exports.cache = function(memory, items) {
|
|||||||
Get and set size of thread pool
|
Get and set size of thread pool
|
||||||
*/
|
*/
|
||||||
module.exports.concurrency = function(concurrency) {
|
module.exports.concurrency = function(concurrency) {
|
||||||
if (Number.isNaN(concurrency)) {
|
if (typeof concurrency !== 'number' || Number.isNaN(concurrency)) {
|
||||||
concurrency = null;
|
concurrency = null;
|
||||||
}
|
}
|
||||||
return sharp.concurrency(concurrency);
|
return sharp.concurrency(concurrency);
|
||||||
@@ -452,3 +774,10 @@ module.exports.concurrency = function(concurrency) {
|
|||||||
module.exports.counters = function() {
|
module.exports.counters = function() {
|
||||||
return sharp.counters();
|
return sharp.counters();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Get the version of the libvips library
|
||||||
|
*/
|
||||||
|
module.exports.libvipsVersion = function() {
|
||||||
|
return libvipsVersion;
|
||||||
|
};
|
||||||
|
|||||||
49
package.json
@@ -1,16 +1,28 @@
|
|||||||
{
|
{
|
||||||
"name": "sharp",
|
"name": "sharp",
|
||||||
"version": "0.6.2",
|
"version": "0.10.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>",
|
||||||
"Jonathan Ong <jonathanrichardong@gmail.com>",
|
"Jonathan Ong <jonathanrichardong@gmail.com>",
|
||||||
"Chanon Sajjamanochai <chanon.s@gmail.com>",
|
"Chanon Sajjamanochai <chanon.s@gmail.com>",
|
||||||
"Juliano Julio <julianojulio@gmail.com>"
|
"Juliano Julio <julianojulio@gmail.com>",
|
||||||
|
"Daniel Gasienica <daniel@gasienica.ch>",
|
||||||
|
"Julian Walker <julian@fiftythree.com>",
|
||||||
|
"Amit Pitaru <pitaru.amit@gmail.com>",
|
||||||
|
"Brandon Aaron <hello.brandon@aaron.sh>",
|
||||||
|
"Andreas Lind <andreas@one.com>",
|
||||||
|
"Maurus Cuelenaere <mcuelenaere@gmail.com>",
|
||||||
|
"Linus Unnebäck <linus@folkdatorn.se>",
|
||||||
|
"Victor Mateevitsi <mvictoras@gmail.com>",
|
||||||
|
"Alaric Holloway <alaric.holloway@gmail.com>",
|
||||||
|
"Bernhard K. Weisshuhn <bkw@codingforce.com>"
|
||||||
],
|
],
|
||||||
"description": "High performance Node.js module to resize JPEG, PNG and WebP 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": {
|
||||||
"test": "node tests/unit && node tests/perf"
|
"test": "VIPS_WARNING=0 node ./node_modules/istanbul/lib/cli.js cover ./node_modules/mocha/bin/_mocha -- --slow=5000 --timeout=20000 ./test/unit/*.js",
|
||||||
|
"test-win32-node": "node ./node_modules/mocha/bin/mocha --slow=5000 --timeout=20000 ./test/unit/*.js",
|
||||||
|
"test-win32-iojs": "iojs ./node_modules/mocha/bin/mocha --slow=5000 --timeout=20000 ./test/unit/*.js"
|
||||||
},
|
},
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -22,30 +34,29 @@
|
|||||||
"png",
|
"png",
|
||||||
"webp",
|
"webp",
|
||||||
"tiff",
|
"tiff",
|
||||||
"gif",
|
"dzi",
|
||||||
"resize",
|
"resize",
|
||||||
"thumbnail",
|
"thumbnail",
|
||||||
"sharpen",
|
|
||||||
"crop",
|
"crop",
|
||||||
"embed",
|
|
||||||
"libvips",
|
"libvips",
|
||||||
"vips",
|
"vips"
|
||||||
"fast",
|
|
||||||
"buffer",
|
|
||||||
"stream"
|
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"nan": "^1.3.0",
|
"bluebird": "^2.9.27",
|
||||||
"bluebird": "^2.3.2"
|
"color": "^0.8.0",
|
||||||
|
"nan": "^1.8.4",
|
||||||
|
"semver": "^4.3.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"imagemagick": "^0.1.3",
|
"async": "^1.1.0",
|
||||||
"imagemagick-native": "^1.2.2",
|
"coveralls": "^2.11.2",
|
||||||
"gm": "^1.16.0",
|
"istanbul": "^0.3.14",
|
||||||
"async": "^0.9.0",
|
"mocha": "^2.2.5",
|
||||||
"benchmark": "^1.0.0"
|
"mocha-jshint": "^2.2.3",
|
||||||
|
"node-cpplint": "^0.4.0",
|
||||||
|
"rimraf": "^2.3.4"
|
||||||
},
|
},
|
||||||
"license": "Apache 2.0",
|
"license": "Apache-2.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10"
|
"node": ">=0.10"
|
||||||
}
|
}
|
||||||
|
|||||||
364
preinstall.sh
Executable file
@@ -0,0 +1,364 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Ensures libvips is installed and attempts to install it if not
|
||||||
|
# Currently supports:
|
||||||
|
# * Mac OS
|
||||||
|
# * Debian Linux
|
||||||
|
# * Debian 7, 8
|
||||||
|
# * Ubuntu 12.04, 14.04, 14.10, 15.04
|
||||||
|
# * Mint 13, 17
|
||||||
|
# * Red Hat Linux
|
||||||
|
# * RHEL/Centos/Scientific 6, 7
|
||||||
|
# * Fedora 21, 22
|
||||||
|
# * Amazon Linux 2014.09
|
||||||
|
|
||||||
|
vips_version_minimum=7.40.0
|
||||||
|
vips_version_latest_major_minor=8.0
|
||||||
|
vips_version_latest_patch=2
|
||||||
|
|
||||||
|
openslide_version_minimum=3.4.0
|
||||||
|
openslide_version_latest_major_minor=3.4
|
||||||
|
openslide_version_latest_patch=1
|
||||||
|
|
||||||
|
install_libvips_from_source() {
|
||||||
|
echo "Compiling libvips $vips_version_latest_major_minor.$vips_version_latest_patch from source"
|
||||||
|
curl -O http://www.vips.ecs.soton.ac.uk/supported/$vips_version_latest_major_minor/vips-$vips_version_latest_major_minor.$vips_version_latest_patch.tar.gz
|
||||||
|
tar zvxf vips-$vips_version_latest_major_minor.$vips_version_latest_patch.tar.gz
|
||||||
|
cd vips-$vips_version_latest_major_minor.$vips_version_latest_patch
|
||||||
|
./configure --disable-debug --disable-docs --disable-static --disable-introspection --enable-cxx=yes --without-python --without-orc --without-fftw $1
|
||||||
|
make
|
||||||
|
make install
|
||||||
|
cd ..
|
||||||
|
rm -rf vips-$vips_version_latest_major_minor.$vips_version_latest_patch
|
||||||
|
rm vips-$vips_version_latest_major_minor.$vips_version_latest_patch.tar.gz
|
||||||
|
ldconfig
|
||||||
|
echo "Installed libvips $vips_version_latest_major_minor.$vips_version_latest_patch"
|
||||||
|
}
|
||||||
|
|
||||||
|
install_libopenslide_from_source() {
|
||||||
|
echo "Compiling openslide $openslide_version_latest_major_minor.$openslide_version_latest_patch from source"
|
||||||
|
curl -O -L https://github.com/openslide/openslide/releases/download/v$openslide_version_latest_major_minor.$openslide_version_latest_patch/openslide-$openslide_version_latest_major_minor.$openslide_version_latest_patch.tar.gz
|
||||||
|
tar xzvf openslide-$openslide_version_latest_major_minor.$openslide_version_latest_patch.tar.gz
|
||||||
|
cd openslide-$openslide_version_latest_major_minor.$openslide_version_latest_patch
|
||||||
|
PKG_CONFIG_PATH=$pkg_config_path ./configure $1
|
||||||
|
make
|
||||||
|
make install
|
||||||
|
cd ..
|
||||||
|
rm -rf openslide-$openslide_version_latest_major_minor.$openslide_version_latest_patch
|
||||||
|
rm openslide-$openslide_version_latest_major_minor.$openslide_version_latest_patch.tar.gz
|
||||||
|
ldconfig
|
||||||
|
echo "Installed libopenslide $openslide_version_latest_major_minor.$openslide_version_latest_patch"
|
||||||
|
}
|
||||||
|
|
||||||
|
sorry() {
|
||||||
|
echo "Sorry, I don't yet know how to install lib$1 on $2"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
pkg_config_path_homebrew=`which brew >/dev/null 2>&1 && eval $(brew --env) && echo $PKG_CONFIG_LIBDIR || true`
|
||||||
|
pkg_config_path="$pkg_config_path_homebrew:$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig:/usr/lib/pkgconfig"
|
||||||
|
|
||||||
|
check_if_library_exists() {
|
||||||
|
PKG_CONFIG_PATH=$pkg_config_path pkg-config --exists $1
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
version_found=$(PKG_CONFIG_PATH=$pkg_config_path pkg-config --modversion $1)
|
||||||
|
PKG_CONFIG_PATH=$pkg_config_path pkg-config --atleast-version=$2 $1
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
# Found suitable version of libvips
|
||||||
|
echo "Found lib$1 $version_found"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
echo "Found lib$1 $version_found but require $2"
|
||||||
|
else
|
||||||
|
echo "Could not find lib$1 using a PKG_CONFIG_PATH of '$pkg_config_path'"
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
enable_openslide=0
|
||||||
|
# Is libvips already installed, and is it at least the minimum required version?
|
||||||
|
if [ $# -eq 1 ]; then
|
||||||
|
if [ "$1" = "--with-openslide" ]; then
|
||||||
|
echo "Installing vips with openslide support"
|
||||||
|
enable_openslide=1
|
||||||
|
else
|
||||||
|
echo "Sorry, $1 is not supported. Did you mean --with-openslide?"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! type pkg-config >/dev/null; then
|
||||||
|
sorry "vips" "a system without pkg-config"
|
||||||
|
fi
|
||||||
|
|
||||||
|
openslide_exists=0
|
||||||
|
if [ $enable_openslide -eq 1 ]; then
|
||||||
|
check_if_library_exists "openslide" "$openslide_version_minimum"
|
||||||
|
openslide_exists=$?
|
||||||
|
fi
|
||||||
|
|
||||||
|
check_if_library_exists "vips" "$vips_version_minimum"
|
||||||
|
vips_exists=$?
|
||||||
|
if [ $vips_exists -eq 1 ] && [ $enable_openslide -eq 1 ]; then
|
||||||
|
if [ $openslide_exists -eq 1 ]; then
|
||||||
|
# Check if vips compiled with openslide support
|
||||||
|
vips_with_openslide=`vips list classes | grep -i opensli`
|
||||||
|
if [ -z $vips_with_openslide ]; then
|
||||||
|
echo "Vips compiled without openslide support."
|
||||||
|
else
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
elif [ $vips_exists -eq 1 ] && [ $enable_openslide -eq 0 ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Verify root/sudo access
|
||||||
|
if [ "$(id -u)" -ne "0" ]; then
|
||||||
|
echo "Sorry, I need root/sudo access to continue"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# OS-specific installations of libopenslide follows
|
||||||
|
# Either openslide does not exist, or vips is installed without openslide support
|
||||||
|
if [ $enable_openslide -eq 1 ] && [ -z $vips_with_openslide ] && [ $openslide_exists -eq 0 ]; then
|
||||||
|
case $(uname -s) in
|
||||||
|
*[Dd]arwin*)
|
||||||
|
# Mac OS
|
||||||
|
echo "Detected Mac OS"
|
||||||
|
if type "brew" > /dev/null; then
|
||||||
|
echo "Installing libopenslide via homebrew"
|
||||||
|
brew install openslide
|
||||||
|
elif type "port" > /dev/null; then
|
||||||
|
echo "Installing libopenslide via MacPorts"
|
||||||
|
port install openslide
|
||||||
|
else
|
||||||
|
sorry "openslide" "Mac OS without homebrew or MacPorts"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
if [ -f /etc/debian_version ]; then
|
||||||
|
# Debian Linux
|
||||||
|
DISTRO=$(lsb_release -c -s)
|
||||||
|
echo "Detected Debian Linux '$DISTRO'"
|
||||||
|
case "$DISTRO" in
|
||||||
|
jessie|vivid)
|
||||||
|
# Debian 8, Ubuntu 15
|
||||||
|
echo "Installing libopenslide via apt-get"
|
||||||
|
apt-get install -y libopenslide-dev
|
||||||
|
;;
|
||||||
|
trusty|utopic|qiana|rebecca)
|
||||||
|
# Ubuntu 14, Mint 17
|
||||||
|
echo "Installing libopenslide dependencies via apt-get"
|
||||||
|
apt-get install -y automake build-essential curl zlib1g-dev libopenjpeg-dev libpng12-dev libjpeg-dev libtiff5-dev libgdk-pixbuf2.0-dev libxml2-dev libsqlite3-dev libcairo2-dev libglib2.0-dev sqlite3 libsqlite3-dev
|
||||||
|
install_libopenslide_from_source
|
||||||
|
;;
|
||||||
|
precise|wheezy|maya)
|
||||||
|
# Debian 7, Ubuntu 12.04, Mint 13
|
||||||
|
echo "Installing libopenslide dependencies via apt-get"
|
||||||
|
apt-get install -y automake build-essential curl zlib1g-dev libopenjpeg-dev libpng12-dev libjpeg-dev libtiff5-dev libgdk-pixbuf2.0-dev libxml2-dev libsqlite3-dev libcairo2-dev libglib2.0-dev sqlite3 libsqlite3-dev
|
||||||
|
install_libopenslide_from_source
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# Unsupported Debian-based OS
|
||||||
|
sorry "openslide" "Debian-based $DISTRO"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
elif [ -f /etc/redhat-release ]; then
|
||||||
|
# Red Hat Linux
|
||||||
|
RELEASE=$(cat /etc/redhat-release)
|
||||||
|
echo "Detected Red Hat Linux '$RELEASE'"
|
||||||
|
case $RELEASE in
|
||||||
|
"Red Hat Enterprise Linux release 7."*|"CentOS Linux release 7."*|"Scientific Linux release 7."*)
|
||||||
|
# RHEL/CentOS 7
|
||||||
|
echo "Installing libopenslide dependencies via yum"
|
||||||
|
yum groupinstall -y "Development Tools"
|
||||||
|
yum install -y tar curl libpng-devel libjpeg-devel libxml2-devel zlib-devel openjpeg-devel libtiff-devel gdk-pixbuf2-devel sqlite-devel cairo-devel glib2-devel
|
||||||
|
install_libopenslide_from_source "--prefix=/usr"
|
||||||
|
;;
|
||||||
|
"Red Hat Enterprise Linux release 6."*|"CentOS release 6."*|"Scientific Linux release 6."*)
|
||||||
|
# RHEL/CentOS 6
|
||||||
|
echo "Installing libopenslide dependencies via yum"
|
||||||
|
yum groupinstall -y "Development Tools"
|
||||||
|
yum install -y tar curl libpng-devel libjpeg-devel libxml2-devel zlib-devel openjpeg-devel libtiff-devel gdk-pixbuf2-devel sqlite-devel cairo-devel glib2-devel
|
||||||
|
install_libopenslide_from_source "--prefix=/usr"
|
||||||
|
;;
|
||||||
|
"Fedora release 21 "*|"Fedora release 22 "*)
|
||||||
|
# Fedora 21, 22
|
||||||
|
echo "Installing libopenslide via yum"
|
||||||
|
yum install -y openslide-devel
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# Unsupported RHEL-based OS
|
||||||
|
sorry "openslide" "$RELEASE"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
elif [ -f /etc/os-release ]; then
|
||||||
|
RELEASE=$(cat /etc/os-release | grep VERSION)
|
||||||
|
echo "Detected OpenSuse Linux '$RELEASE'"
|
||||||
|
case $RELEASE in
|
||||||
|
*"13.2"*)
|
||||||
|
echo "Installing libopenslide via zypper"
|
||||||
|
zypper --gpg-auto-import-keys install -y libopenslide-devel
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
elif [ -f /etc/SuSE-brand ]; then
|
||||||
|
RELEASE=$(cat /etc/SuSE-brand | grep VERSION)
|
||||||
|
echo "Detected OpenSuse Linux '$RELEASE'"
|
||||||
|
case $RELEASE in
|
||||||
|
*"13.1")
|
||||||
|
echo "Installing libopenslide dependencies via zypper"
|
||||||
|
zypper --gpg-auto-import-keys install -y --type pattern devel_basis
|
||||||
|
zypper --gpg-auto-import-keys install -y tar curl libpng16-devel libjpeg-turbo libjpeg8-devel libxml2-devel zlib-devel openjpeg-devel libtiff-devel libgdk_pixbuf-2_0-0 sqlite3-devel cairo-devel glib2-devel
|
||||||
|
install_libopenslide_from_source
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
else
|
||||||
|
# Unsupported OS
|
||||||
|
sorry "openslide" "$(uname -a)"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# OS-specific installations of libvips follows
|
||||||
|
|
||||||
|
case $(uname -s) in
|
||||||
|
*[Dd]arwin*)
|
||||||
|
# Mac OS
|
||||||
|
echo "Detected Mac OS"
|
||||||
|
if type "brew" > /dev/null; then
|
||||||
|
echo "Installing libvips via homebrew"
|
||||||
|
if [ $enable_openslide -eq 1 ]; then
|
||||||
|
brew install homebrew/science/vips --with-webp --with-graphicsmagick --with-openslide
|
||||||
|
else
|
||||||
|
brew install homebrew/science/vips --with-webp --with-graphicsmagick
|
||||||
|
fi
|
||||||
|
elif type "port" > /dev/null; then
|
||||||
|
echo "Installing libvips via MacPorts"
|
||||||
|
port install vips
|
||||||
|
else
|
||||||
|
sorry "vips" "Mac OS without homebrew or MacPorts"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
if [ -f /etc/debian_version ]; then
|
||||||
|
# Debian Linux
|
||||||
|
DISTRO=$(lsb_release -c -s)
|
||||||
|
echo "Detected Debian Linux '$DISTRO'"
|
||||||
|
case "$DISTRO" in
|
||||||
|
jessie|vivid)
|
||||||
|
# Debian 8, Ubuntu 15
|
||||||
|
if [ $enable_openslide -eq 1 ]; then
|
||||||
|
echo "Recompiling vips with openslide support"
|
||||||
|
install_libvips_from_source
|
||||||
|
else
|
||||||
|
echo "Installing libvips via apt-get"
|
||||||
|
apt-get install -y libvips-dev libgsf-1-dev
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
trusty|utopic|qiana|rebecca)
|
||||||
|
# Ubuntu 14, Mint 17
|
||||||
|
echo "Installing libvips dependencies via apt-get"
|
||||||
|
apt-get install -y automake build-essential gobject-introspection gtk-doc-tools libglib2.0-dev libjpeg-dev libpng12-dev libwebp-dev libtiff5-dev libexif-dev libgsf-1-dev liblcms2-dev libxml2-dev swig libmagickcore-dev curl
|
||||||
|
install_libvips_from_source
|
||||||
|
;;
|
||||||
|
precise|wheezy|maya)
|
||||||
|
# Debian 7, Ubuntu 12.04, Mint 13
|
||||||
|
echo "Installing libvips dependencies via apt-get"
|
||||||
|
add-apt-repository -y ppa:lyrasis/precise-backports
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y automake build-essential gobject-introspection gtk-doc-tools libglib2.0-dev libjpeg-dev libpng12-dev libwebp-dev libtiff4-dev libexif-dev libgsf-1-dev liblcms2-dev libxml2-dev swig libmagickcore-dev curl
|
||||||
|
install_libvips_from_source
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# Unsupported Debian-based OS
|
||||||
|
sorry "vips" "Debian-based $DISTRO"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
elif [ -f /etc/redhat-release ]; then
|
||||||
|
# Red Hat Linux
|
||||||
|
RELEASE=$(cat /etc/redhat-release)
|
||||||
|
echo "Detected Red Hat Linux '$RELEASE'"
|
||||||
|
case $RELEASE in
|
||||||
|
"Red Hat Enterprise Linux release 7."*|"CentOS Linux release 7."*|"Scientific Linux release 7."*)
|
||||||
|
# RHEL/CentOS 7
|
||||||
|
echo "Installing libvips dependencies via yum"
|
||||||
|
yum groupinstall -y "Development Tools"
|
||||||
|
yum install -y gtk-doc libxml2-devel libjpeg-turbo-devel libpng-devel libtiff-devel libexif-devel libgsf-devel lcms-devel ImageMagick-devel gobject-introspection-devel libwebp-devel curl
|
||||||
|
install_libvips_from_source "--prefix=/usr"
|
||||||
|
;;
|
||||||
|
"Red Hat Enterprise Linux release 6."*|"CentOS release 6."*|"Scientific Linux release 6."*)
|
||||||
|
# RHEL/CentOS 6
|
||||||
|
echo "Installing libvips dependencies via yum"
|
||||||
|
yum groupinstall -y "Development Tools"
|
||||||
|
yum install -y gtk-doc libxml2-devel libjpeg-turbo-devel libpng-devel libtiff-devel libexif-devel libgsf-devel lcms-devel ImageMagick-devel curl
|
||||||
|
yum install -y http://li.nux.ro/download/nux/dextop/el6/x86_64/nux-dextop-release-0-2.el6.nux.noarch.rpm
|
||||||
|
yum install -y --enablerepo=nux-dextop gobject-introspection-devel
|
||||||
|
yum install -y http://rpms.famillecollet.com/enterprise/remi-release-6.rpm
|
||||||
|
yum install -y --enablerepo=remi libwebp-devel
|
||||||
|
install_libvips_from_source "--prefix=/usr"
|
||||||
|
;;
|
||||||
|
"Fedora release 21 "*|"Fedora release 22 "*)
|
||||||
|
# Fedora 21, 22
|
||||||
|
if [ $enable_openslide -eq 1 ]; then
|
||||||
|
echo "Installing libvips dependencies via yum"
|
||||||
|
yum groupinstall -y "Development Tools"
|
||||||
|
yum install -y gcc-c++ gtk-doc libxml2-devel libjpeg-turbo-devel libpng-devel libtiff-devel libexif-devel lcms-devel ImageMagick-devel gobject-introspection-devel libwebp-devel curl
|
||||||
|
echo "Compiling vips with openslide support"
|
||||||
|
install_libvips_from_source "--prefix=/usr"
|
||||||
|
else
|
||||||
|
echo "Installing libvips via yum"
|
||||||
|
yum install -y vips-devel
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# Unsupported RHEL-based OS
|
||||||
|
sorry "vips" "$RELEASE"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
elif [ -f /etc/system-release ]; then
|
||||||
|
# Probably Amazon Linux
|
||||||
|
RELEASE=$(cat /etc/system-release)
|
||||||
|
case $RELEASE in
|
||||||
|
"Amazon Linux AMI release 2014.09"|"Amazon Linux AMI release 2015.03")
|
||||||
|
# Amazon Linux
|
||||||
|
echo "Detected '$RELEASE'"
|
||||||
|
echo "Installing libvips dependencies via yum"
|
||||||
|
yum groupinstall -y "Development Tools"
|
||||||
|
yum install -y gtk-doc libxml2-devel libjpeg-turbo-devel libpng-devel libtiff-devel libexif-devel libgsf-devel lcms-devel ImageMagick-devel gobject-introspection-devel libwebp-devel curl
|
||||||
|
install_libvips_from_source "--prefix=/usr"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# Unsupported Amazon Linux version
|
||||||
|
sorry "vips" "$RELEASE"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
elif [ -f /etc/os-release ]; then
|
||||||
|
RELEASE=$(cat /etc/os-release | grep VERSION)
|
||||||
|
echo "Detected OpenSuse Linux '$RELEASE'"
|
||||||
|
case $RELEASE in
|
||||||
|
*"13.2"*)
|
||||||
|
echo "Installing libvips dependencies via zypper"
|
||||||
|
zypper --gpg-auto-import-keys install -y --type pattern devel_basis
|
||||||
|
zypper --gpg-auto-import-keys install -y tar curl gtk-doc libxml2-devel libjpeg-turbo libjpeg8-devel libpng16-devel libtiff-devel libexif-devel liblcms2-devel ImageMagick-devel gobject-introspection-devel libwebp-devel
|
||||||
|
install_libvips_from_source
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
elif [ -f /etc/SuSE-brand ]; then
|
||||||
|
RELEASE=$(cat /etc/SuSE-brand | grep VERSION)
|
||||||
|
echo "Detected OpenSuse Linux '$RELEASE'"
|
||||||
|
case $RELEASE in
|
||||||
|
*"13.1")
|
||||||
|
echo "Installing libvips dependencies via zypper"
|
||||||
|
zypper --gpg-auto-import-keys install -y --type pattern devel_basis
|
||||||
|
zypper --gpg-auto-import-keys install -y tar curl gtk-doc libxml2-devel libjpeg-turbo libjpeg8-devel libpng16-devel libtiff-devel libexif-devel liblcms2-devel ImageMagick-devel gobject-introspection-devel libwebp-devel
|
||||||
|
install_libvips_from_source
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
else
|
||||||
|
# Unsupported OS
|
||||||
|
sorry "vips" "$(uname -a)"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
162
src/common.cc
Executable file
@@ -0,0 +1,162 @@
|
|||||||
|
#include <cstdlib>
|
||||||
|
#include <string>
|
||||||
|
#include <string.h>
|
||||||
|
#include <vips/vips.h>
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
// Verify platform and compiler compatibility
|
||||||
|
|
||||||
|
#ifdef _WIN64
|
||||||
|
#error Windows 64-bit is currently unsupported - see https://github.com/lovell/sharp#windows
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6)))
|
||||||
|
#error GCC version 4.6+ is required for C++11 features - see https://github.com/lovell/sharp#prerequisites
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if (defined(__clang__) && defined(__has_feature))
|
||||||
|
#if (!__has_feature(cxx_range_for))
|
||||||
|
#error clang version 3.0+ is required for C++11 features - see https://github.com/lovell/sharp#prerequisites
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace sharp {
|
||||||
|
|
||||||
|
// How many tasks are in the queue?
|
||||||
|
volatile int counterQueue = 0;
|
||||||
|
|
||||||
|
// How many tasks are being processed?
|
||||||
|
volatile int counterProcess = 0;
|
||||||
|
|
||||||
|
// Filename extension checkers
|
||||||
|
static bool EndsWith(std::string const &str, std::string const &end) {
|
||||||
|
return str.length() >= end.length() && 0 == str.compare(str.length() - end.length(), end.length(), end);
|
||||||
|
}
|
||||||
|
bool IsJpeg(std::string const &str) {
|
||||||
|
return EndsWith(str, ".jpg") || EndsWith(str, ".jpeg") || EndsWith(str, ".JPG") || EndsWith(str, ".JPEG");
|
||||||
|
}
|
||||||
|
bool IsPng(std::string const &str) {
|
||||||
|
return EndsWith(str, ".png") || EndsWith(str, ".PNG");
|
||||||
|
}
|
||||||
|
bool IsWebp(std::string const &str) {
|
||||||
|
return EndsWith(str, ".webp") || EndsWith(str, ".WEBP");
|
||||||
|
}
|
||||||
|
bool IsTiff(std::string const &str) {
|
||||||
|
return EndsWith(str, ".tif") || EndsWith(str, ".tiff") || EndsWith(str, ".TIF") || EndsWith(str, ".TIFF");
|
||||||
|
}
|
||||||
|
bool IsDz(std::string const &str) {
|
||||||
|
return EndsWith(str, ".dzi") || EndsWith(str, ".DZI");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Determine image format of a buffer.
|
||||||
|
*/
|
||||||
|
ImageType DetermineImageType(void *buffer, size_t const length) {
|
||||||
|
ImageType imageType = ImageType::UNKNOWN;
|
||||||
|
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 (EndsWith(loader, "PngBuffer")) {
|
||||||
|
imageType = ImageType::PNG;
|
||||||
|
} else if (EndsWith(loader, "WebpBuffer")) {
|
||||||
|
imageType = ImageType::WEBP;
|
||||||
|
} else if (EndsWith(loader, "TiffBuffer")) {
|
||||||
|
imageType = ImageType::TIFF;
|
||||||
|
} else if (EndsWith(loader, "MagickBuffer")) {
|
||||||
|
imageType = ImageType::MAGICK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return imageType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Initialise and return a VipsImage from a buffer. Supports JPEG, PNG, WebP and TIFF.
|
||||||
|
*/
|
||||||
|
VipsImage* InitImage(void *buffer, size_t const length, VipsAccess const access) {
|
||||||
|
return vips_image_new_from_buffer(buffer, length, NULL, "access", access, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Determine image format, reads the first few bytes of the file
|
||||||
|
*/
|
||||||
|
ImageType DetermineImageType(char const *file) {
|
||||||
|
ImageType imageType = ImageType::UNKNOWN;
|
||||||
|
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, "Magick") || EndsWith(loader, "MagickFile")) {
|
||||||
|
imageType = ImageType::MAGICK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return imageType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Initialise and return a VipsImage from a file.
|
||||||
|
*/
|
||||||
|
VipsImage* InitImage(char const *file, VipsAccess const access) {
|
||||||
|
return vips_image_new_from_file(file, "access", access, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Does this image have an embedded profile?
|
||||||
|
*/
|
||||||
|
bool HasProfile(VipsImage *image) {
|
||||||
|
return (vips_image_get_typeof(image, VIPS_META_ICC_NAME) > 0) ? TRUE : FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Does this image have an alpha channel?
|
||||||
|
Uses colour space interpretation with number of channels to guess this.
|
||||||
|
*/
|
||||||
|
bool HasAlpha(VipsImage *image) {
|
||||||
|
return (
|
||||||
|
(image->Bands == 2 && image->Type == VIPS_INTERPRETATION_B_W) ||
|
||||||
|
(image->Bands == 4 && image->Type != VIPS_INTERPRETATION_CMYK) ||
|
||||||
|
(image->Bands == 5 && image->Type == VIPS_INTERPRETATION_CMYK)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Get EXIF Orientation of image, if any.
|
||||||
|
*/
|
||||||
|
int ExifOrientation(VipsImage const *image) {
|
||||||
|
int orientation = 0;
|
||||||
|
const char *exif;
|
||||||
|
if (
|
||||||
|
vips_image_get_typeof(image, "exif-ifd0-Orientation") != 0 &&
|
||||||
|
!vips_image_get_string(image, "exif-ifd0-Orientation", &exif)
|
||||||
|
) {
|
||||||
|
orientation = atoi(&exif[0]);
|
||||||
|
}
|
||||||
|
return orientation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Returns the window size for the named interpolator. For example,
|
||||||
|
a window size of 3 means a 3x3 pixel grid is used for the calculation.
|
||||||
|
*/
|
||||||
|
int InterpolatorWindowSize(char const *name) {
|
||||||
|
VipsInterpolate *interpolator = vips_interpolate_new(name);
|
||||||
|
if (interpolator == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
int window_size = vips_interpolate_get_window_size(interpolator);
|
||||||
|
g_object_unref(interpolator);
|
||||||
|
return window_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sharp
|
||||||
73
src/common.h
Executable file
@@ -0,0 +1,73 @@
|
|||||||
|
#ifndef SRC_COMMON_H_
|
||||||
|
#define SRC_COMMON_H_
|
||||||
|
|
||||||
|
namespace sharp {
|
||||||
|
|
||||||
|
enum class ImageType {
|
||||||
|
UNKNOWN,
|
||||||
|
JPEG,
|
||||||
|
PNG,
|
||||||
|
WEBP,
|
||||||
|
TIFF,
|
||||||
|
MAGICK,
|
||||||
|
OPENSLIDE
|
||||||
|
};
|
||||||
|
|
||||||
|
// How many tasks are in the queue?
|
||||||
|
extern volatile int counterQueue;
|
||||||
|
|
||||||
|
// How many tasks are being processed?
|
||||||
|
extern volatile int counterProcess;
|
||||||
|
|
||||||
|
// Filename extension checkers
|
||||||
|
bool IsJpeg(std::string const &str);
|
||||||
|
bool IsPng(std::string const &str);
|
||||||
|
bool IsWebp(std::string const &str);
|
||||||
|
bool IsTiff(std::string const &str);
|
||||||
|
bool IsDz(std::string const &str);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Determine image format of a buffer.
|
||||||
|
*/
|
||||||
|
ImageType DetermineImageType(void *buffer, size_t const length);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Determine image format of a file.
|
||||||
|
*/
|
||||||
|
ImageType DetermineImageType(char const *file);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Initialise and return a VipsImage from a buffer. Supports JPEG, PNG, WebP and TIFF.
|
||||||
|
*/
|
||||||
|
VipsImage* InitImage(void *buffer, size_t const length, VipsAccess const access);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Initialise and return a VipsImage from a file.
|
||||||
|
*/
|
||||||
|
VipsImage* InitImage(char const *file, VipsAccess const access);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Does this image have an embedded profile?
|
||||||
|
*/
|
||||||
|
bool HasProfile(VipsImage *image);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Does this image have an alpha channel?
|
||||||
|
Uses colour space interpretation with number of channels to guess this.
|
||||||
|
*/
|
||||||
|
bool HasAlpha(VipsImage *image);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Get EXIF Orientation of image, if any.
|
||||||
|
*/
|
||||||
|
int ExifOrientation(VipsImage const *image);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Returns the window size for the named interpolator. For example,
|
||||||
|
a window size of 3 means a 3x3 pixel grid is used for the calculation.
|
||||||
|
*/
|
||||||
|
int InterpolatorWindowSize(char const *name);
|
||||||
|
|
||||||
|
} // namespace sharp
|
||||||
|
|
||||||
|
#endif // SRC_COMMON_H_
|
||||||
172
src/metadata.cc
Executable file
@@ -0,0 +1,172 @@
|
|||||||
|
#include <node.h>
|
||||||
|
#include <vips/vips.h>
|
||||||
|
|
||||||
|
#include "nan.h"
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
#include "metadata.h"
|
||||||
|
|
||||||
|
using v8::Handle;
|
||||||
|
using v8::Local;
|
||||||
|
using v8::Value;
|
||||||
|
using v8::Object;
|
||||||
|
using v8::Number;
|
||||||
|
using v8::String;
|
||||||
|
using v8::Boolean;
|
||||||
|
using v8::Function;
|
||||||
|
using v8::Exception;
|
||||||
|
|
||||||
|
using sharp::ImageType;
|
||||||
|
using sharp::DetermineImageType;
|
||||||
|
using sharp::InitImage;
|
||||||
|
using sharp::HasProfile;
|
||||||
|
using sharp::HasAlpha;
|
||||||
|
using sharp::ExifOrientation;
|
||||||
|
using sharp::counterQueue;
|
||||||
|
|
||||||
|
struct MetadataBaton {
|
||||||
|
// Input
|
||||||
|
std::string fileIn;
|
||||||
|
void* bufferIn;
|
||||||
|
size_t bufferInLength;
|
||||||
|
// Output
|
||||||
|
std::string format;
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
std::string space;
|
||||||
|
int channels;
|
||||||
|
bool hasProfile;
|
||||||
|
bool hasAlpha;
|
||||||
|
int orientation;
|
||||||
|
std::string err;
|
||||||
|
|
||||||
|
MetadataBaton():
|
||||||
|
bufferInLength(0),
|
||||||
|
orientation(0) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
class MetadataWorker : public NanAsyncWorker {
|
||||||
|
|
||||||
|
public:
|
||||||
|
MetadataWorker(NanCallback *callback, MetadataBaton *baton) : NanAsyncWorker(callback), baton(baton) {}
|
||||||
|
~MetadataWorker() {}
|
||||||
|
|
||||||
|
void Execute() {
|
||||||
|
// Decrement queued task counter
|
||||||
|
g_atomic_int_dec_and_test(&counterQueue);
|
||||||
|
|
||||||
|
ImageType imageType = ImageType::UNKNOWN;
|
||||||
|
VipsImage *image = NULL;
|
||||||
|
if (baton->bufferInLength > 1) {
|
||||||
|
// From buffer
|
||||||
|
imageType = DetermineImageType(baton->bufferIn, baton->bufferInLength);
|
||||||
|
if (imageType != ImageType::UNKNOWN) {
|
||||||
|
image = InitImage(baton->bufferIn, baton->bufferInLength, VIPS_ACCESS_RANDOM);
|
||||||
|
if (image == NULL) {
|
||||||
|
(baton->err).append("Input buffer has corrupt header");
|
||||||
|
imageType = ImageType::UNKNOWN;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(baton->err).append("Input buffer contains unsupported image format");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// From file
|
||||||
|
imageType = DetermineImageType(baton->fileIn.c_str());
|
||||||
|
if (imageType != ImageType::UNKNOWN) {
|
||||||
|
image = InitImage(baton->fileIn.c_str(), VIPS_ACCESS_RANDOM);
|
||||||
|
if (image == NULL) {
|
||||||
|
(baton->err).append("Input file has corrupt header");
|
||||||
|
imageType = ImageType::UNKNOWN;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(baton->err).append("Input file is of an unsupported image format");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (image != NULL && imageType != ImageType::UNKNOWN) {
|
||||||
|
// Image type
|
||||||
|
switch (imageType) {
|
||||||
|
case ImageType::JPEG: baton->format = "jpeg"; break;
|
||||||
|
case ImageType::PNG: baton->format = "png"; break;
|
||||||
|
case ImageType::WEBP: baton->format = "webp"; break;
|
||||||
|
case ImageType::TIFF: baton->format = "tiff"; break;
|
||||||
|
case ImageType::MAGICK: baton->format = "magick"; break;
|
||||||
|
case ImageType::OPENSLIDE: baton->format = "openslide"; break;
|
||||||
|
case ImageType::UNKNOWN: break;
|
||||||
|
}
|
||||||
|
// VipsImage attributes
|
||||||
|
baton->width = image->Xsize;
|
||||||
|
baton->height = image->Ysize;
|
||||||
|
baton->space = vips_enum_nick(VIPS_TYPE_INTERPRETATION, image->Type);
|
||||||
|
baton->channels = image->Bands;
|
||||||
|
baton->hasProfile = HasProfile(image);
|
||||||
|
// Derived attributes
|
||||||
|
baton->hasAlpha = HasAlpha(image);
|
||||||
|
baton->orientation = ExifOrientation(image);
|
||||||
|
// Drop image reference
|
||||||
|
g_object_unref(image);
|
||||||
|
}
|
||||||
|
// Clean up
|
||||||
|
vips_error_clear();
|
||||||
|
vips_thread_shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HandleOKCallback () {
|
||||||
|
NanScope();
|
||||||
|
|
||||||
|
Handle<Value> argv[2] = { NanNull(), NanNull() };
|
||||||
|
if (!baton->err.empty()) {
|
||||||
|
// Error
|
||||||
|
argv[0] = Exception::Error(NanNew<String>(baton->err.data(), baton->err.size()));
|
||||||
|
} else {
|
||||||
|
// Metadata Object
|
||||||
|
Local<Object> info = NanNew<Object>();
|
||||||
|
info->Set(NanNew<String>("format"), NanNew<String>(baton->format));
|
||||||
|
info->Set(NanNew<String>("width"), NanNew<Number>(baton->width));
|
||||||
|
info->Set(NanNew<String>("height"), NanNew<Number>(baton->height));
|
||||||
|
info->Set(NanNew<String>("space"), NanNew<String>(baton->space));
|
||||||
|
info->Set(NanNew<String>("channels"), NanNew<Number>(baton->channels));
|
||||||
|
info->Set(NanNew<String>("hasProfile"), NanNew<Boolean>(baton->hasProfile));
|
||||||
|
info->Set(NanNew<String>("hasAlpha"), NanNew<Boolean>(baton->hasAlpha));
|
||||||
|
if (baton->orientation > 0) {
|
||||||
|
info->Set(NanNew<String>("orientation"), NanNew<Number>(baton->orientation));
|
||||||
|
}
|
||||||
|
argv[1] = info;
|
||||||
|
}
|
||||||
|
delete baton;
|
||||||
|
|
||||||
|
// Return to JavaScript
|
||||||
|
callback->Call(2, argv);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
MetadataBaton* baton;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
metadata(options, callback)
|
||||||
|
*/
|
||||||
|
NAN_METHOD(metadata) {
|
||||||
|
NanScope();
|
||||||
|
|
||||||
|
// V8 objects are converted to non-V8 types held in the baton struct
|
||||||
|
MetadataBaton *baton = new MetadataBaton;
|
||||||
|
Local<Object> options = args[0]->ToObject();
|
||||||
|
|
||||||
|
// Input filename
|
||||||
|
baton->fileIn = *String::Utf8Value(options->Get(NanNew<String>("fileIn"))->ToString());
|
||||||
|
// Input Buffer object
|
||||||
|
if (options->Get(NanNew<String>("bufferIn"))->IsObject()) {
|
||||||
|
Local<Object> buffer = options->Get(NanNew<String>("bufferIn"))->ToObject();
|
||||||
|
baton->bufferInLength = node::Buffer::Length(buffer);
|
||||||
|
baton->bufferIn = node::Buffer::Data(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Join queue for worker thread
|
||||||
|
NanCallback *callback = new NanCallback(args[1].As<v8::Function>());
|
||||||
|
NanAsyncQueueWorker(new MetadataWorker(callback, baton));
|
||||||
|
|
||||||
|
// Increment queued task counter
|
||||||
|
g_atomic_int_inc(&counterQueue);
|
||||||
|
|
||||||
|
NanReturnUndefined();
|
||||||
|
}
|
||||||
8
src/metadata.h
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
#ifndef SRC_METADATA_H_
|
||||||
|
#define SRC_METADATA_H_
|
||||||
|
|
||||||
|
#include "nan.h"
|
||||||
|
|
||||||
|
NAN_METHOD(metadata);
|
||||||
|
|
||||||
|
#endif // SRC_METADATA_H_
|
||||||
1222
src/resize.cc
Executable file
8
src/resize.h
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
#ifndef SRC_RESIZE_H_
|
||||||
|
#define SRC_RESIZE_H_
|
||||||
|
|
||||||
|
#include "nan.h"
|
||||||
|
|
||||||
|
NAN_METHOD(resize);
|
||||||
|
|
||||||
|
#endif // SRC_RESIZE_H_
|
||||||
840
src/sharp.cc
@@ -1,853 +1,29 @@
|
|||||||
#include <node.h>
|
#include <node.h>
|
||||||
#include <node_buffer.h>
|
|
||||||
#include <math.h>
|
|
||||||
#include <string>
|
|
||||||
#include <string.h>
|
|
||||||
#include <tuple>
|
|
||||||
#include <vips/vips.h>
|
#include <vips/vips.h>
|
||||||
|
|
||||||
#include "nan.h"
|
#include "nan.h"
|
||||||
|
|
||||||
using namespace v8;
|
#include "common.h"
|
||||||
using namespace node;
|
#include "metadata.h"
|
||||||
|
#include "resize.h"
|
||||||
|
#include "utilities.h"
|
||||||
|
|
||||||
struct resize_baton {
|
extern "C" void init(v8::Handle<v8::Object> target) {
|
||||||
std::string file_in;
|
|
||||||
void* buffer_in;
|
|
||||||
size_t buffer_in_len;
|
|
||||||
std::string output;
|
|
||||||
std::string output_format;
|
|
||||||
void* buffer_out;
|
|
||||||
size_t buffer_out_len;
|
|
||||||
int width;
|
|
||||||
int height;
|
|
||||||
bool crop;
|
|
||||||
int gravity;
|
|
||||||
bool max;
|
|
||||||
VipsExtend extend;
|
|
||||||
bool sharpen;
|
|
||||||
std::string interpolator;
|
|
||||||
double gamma;
|
|
||||||
bool progressive;
|
|
||||||
bool without_enlargement;
|
|
||||||
VipsAccess access_method;
|
|
||||||
int quality;
|
|
||||||
int compressionLevel;
|
|
||||||
int angle;
|
|
||||||
std::string err;
|
|
||||||
bool withMetadata;
|
|
||||||
|
|
||||||
resize_baton():
|
|
||||||
buffer_in_len(0),
|
|
||||||
output_format(""),
|
|
||||||
buffer_out_len(0),
|
|
||||||
crop(false),
|
|
||||||
gravity(0),
|
|
||||||
max(false),
|
|
||||||
sharpen(false),
|
|
||||||
gamma(0.0),
|
|
||||||
progressive(false),
|
|
||||||
without_enlargement(false),
|
|
||||||
withMetadata(false) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
UNKNOWN,
|
|
||||||
JPEG,
|
|
||||||
PNG,
|
|
||||||
WEBP,
|
|
||||||
TIFF,
|
|
||||||
MAGICK
|
|
||||||
} ImageType;
|
|
||||||
|
|
||||||
unsigned char const MARKER_JPEG[] = {0xff, 0xd8};
|
|
||||||
unsigned char const MARKER_PNG[] = {0x89, 0x50};
|
|
||||||
unsigned char const MARKER_WEBP[] = {0x52, 0x49};
|
|
||||||
|
|
||||||
// How many tasks are in the queue?
|
|
||||||
volatile int counter_queue = 0;
|
|
||||||
// How many tasks are being processed?
|
|
||||||
volatile int counter_process = 0;
|
|
||||||
|
|
||||||
static bool ends_with(std::string const &str, std::string const &end) {
|
|
||||||
return str.length() >= end.length() && 0 == str.compare(str.length() - end.length(), end.length(), end);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool is_jpeg(std::string const &str) {
|
|
||||||
return ends_with(str, ".jpg") || ends_with(str, ".jpeg") || ends_with(str, ".JPG") || ends_with(str, ".JPEG");
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool is_png(std::string const &str) {
|
|
||||||
return ends_with(str, ".png") || ends_with(str, ".PNG");
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool is_webp(std::string const &str) {
|
|
||||||
return ends_with(str, ".webp") || ends_with(str, ".WEBP");
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool is_tiff(std::string const &str) {
|
|
||||||
return ends_with(str, ".tif") || ends_with(str, ".tiff") || ends_with(str, ".TIF") || ends_with(str, ".TIFF");
|
|
||||||
}
|
|
||||||
|
|
||||||
static void resize_error(resize_baton *baton, VipsImage *unref) {
|
|
||||||
(baton->err).append(vips_error_buffer());
|
|
||||||
vips_error_clear();
|
|
||||||
g_object_unref(unref);
|
|
||||||
vips_thread_shutdown();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Calculate the angle of rotation for the output image.
|
|
||||||
In order of priority:
|
|
||||||
1. Use explicitly requested angle (supports 90, 180, 270)
|
|
||||||
2. Use input image EXIF Orientation header (does not support mirroring)
|
|
||||||
3. Otherwise default to zero, i.e. no rotation
|
|
||||||
*/
|
|
||||||
static VipsAngle
|
|
||||||
sharp_calc_rotation(int const angle, VipsImage const *input) {
|
|
||||||
VipsAngle rotate = VIPS_ANGLE_0;
|
|
||||||
if (angle == -1) {
|
|
||||||
const char *exif;
|
|
||||||
if (!vips_image_get_string(input, "exif-ifd0-Orientation", &exif)) {
|
|
||||||
if (exif[0] == 0x36) { // "6"
|
|
||||||
rotate = VIPS_ANGLE_90;
|
|
||||||
} else if (exif[0] == 0x33) { // "3"
|
|
||||||
rotate = VIPS_ANGLE_180;
|
|
||||||
} else if (exif[0] == 0x38) { // "8"
|
|
||||||
rotate = VIPS_ANGLE_270;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (angle == 90) {
|
|
||||||
rotate = VIPS_ANGLE_90;
|
|
||||||
} else if (angle == 180) {
|
|
||||||
rotate = VIPS_ANGLE_180;
|
|
||||||
} else if (angle == 270) {
|
|
||||||
rotate = VIPS_ANGLE_270;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rotate;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Calculate the (left, top) coordinates of the output image
|
|
||||||
within the input image, applying the given gravity.
|
|
||||||
*/
|
|
||||||
static std::tuple<int, int>
|
|
||||||
sharp_calc_crop(int const inWidth, int const inHeight, int const outWidth, int const outHeight, int const gravity) {
|
|
||||||
int left = 0;
|
|
||||||
int top = 0;
|
|
||||||
switch (gravity) {
|
|
||||||
case 1: // North
|
|
||||||
left = (inWidth - outWidth + 1) / 2;
|
|
||||||
break;
|
|
||||||
case 2: // East
|
|
||||||
left = inWidth - outWidth;
|
|
||||||
top = (inHeight - outHeight + 1) / 2;
|
|
||||||
break;
|
|
||||||
case 3: // South
|
|
||||||
left = (inWidth - outWidth + 1) / 2;
|
|
||||||
top = inHeight - outHeight;
|
|
||||||
break;
|
|
||||||
case 4: // West
|
|
||||||
top = (inHeight - outHeight + 1) / 2;
|
|
||||||
break;
|
|
||||||
default: // Centre
|
|
||||||
left = (inWidth - outWidth + 1) / 2;
|
|
||||||
top = (inHeight - outHeight + 1) / 2;
|
|
||||||
}
|
|
||||||
return std::make_tuple(left, top);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Initialise a VipsImage from a buffer. Supports JPEG, PNG and WebP.
|
|
||||||
Returns the ImageType detected, if any.
|
|
||||||
*/
|
|
||||||
static ImageType
|
|
||||||
sharp_init_image_from_buffer(VipsImage **image, void *buffer, size_t const length, VipsAccess const access) {
|
|
||||||
ImageType imageType = UNKNOWN;
|
|
||||||
if (memcmp(MARKER_JPEG, buffer, 2) == 0) {
|
|
||||||
if (!vips_jpegload_buffer(buffer, length, image, "access", access, NULL)) {
|
|
||||||
imageType = JPEG;
|
|
||||||
}
|
|
||||||
} else if(memcmp(MARKER_PNG, buffer, 2) == 0) {
|
|
||||||
if (!vips_pngload_buffer(buffer, length, image, "access", access, NULL)) {
|
|
||||||
imageType = PNG;
|
|
||||||
}
|
|
||||||
} else if(memcmp(MARKER_WEBP, buffer, 2) == 0) {
|
|
||||||
if (!vips_webpload_buffer(buffer, length, image, "access", access, NULL)) {
|
|
||||||
imageType = WEBP;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return imageType;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Initialise a VipsImage from a file.
|
|
||||||
Returns the ImageType detected, if any.
|
|
||||||
*/
|
|
||||||
static ImageType
|
|
||||||
sharp_init_image_from_file(VipsImage **image, char const *file, VipsAccess const access) {
|
|
||||||
ImageType imageType = UNKNOWN;
|
|
||||||
if (vips_foreign_is_a("jpegload", file)) {
|
|
||||||
if (!vips_jpegload(file, image, "access", access, NULL)) {
|
|
||||||
imageType = JPEG;
|
|
||||||
}
|
|
||||||
} else if (vips_foreign_is_a("pngload", file)) {
|
|
||||||
if (!vips_pngload(file, image, "access", access, NULL)) {
|
|
||||||
imageType = PNG;
|
|
||||||
}
|
|
||||||
} else if (vips_foreign_is_a("webpload", file)) {
|
|
||||||
if (!vips_webpload(file, image, "access", access, NULL)) {
|
|
||||||
imageType = WEBP;
|
|
||||||
}
|
|
||||||
} else if (vips_foreign_is_a("tiffload", file)) {
|
|
||||||
if (!vips_tiffload(file, image, "access", access, NULL)) {
|
|
||||||
imageType = TIFF;
|
|
||||||
}
|
|
||||||
} else if(vips_foreign_is_a("magickload", file)) {
|
|
||||||
if (!vips_magickload(file, image, "access", access, NULL)) {
|
|
||||||
imageType = MAGICK;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return imageType;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Metadata
|
|
||||||
|
|
||||||
struct metadata_baton {
|
|
||||||
// Input
|
|
||||||
std::string file_in;
|
|
||||||
void* buffer_in;
|
|
||||||
size_t buffer_in_len;
|
|
||||||
// Output
|
|
||||||
std::string format;
|
|
||||||
int width;
|
|
||||||
int height;
|
|
||||||
std::string space;
|
|
||||||
int channels;
|
|
||||||
int orientation;
|
|
||||||
std::string err;
|
|
||||||
|
|
||||||
metadata_baton():
|
|
||||||
buffer_in_len(0),
|
|
||||||
orientation(0) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
class MetadataWorker : public NanAsyncWorker {
|
|
||||||
|
|
||||||
public:
|
|
||||||
MetadataWorker(NanCallback *callback, metadata_baton *baton) : NanAsyncWorker(callback), baton(baton) {}
|
|
||||||
~MetadataWorker() {}
|
|
||||||
|
|
||||||
void Execute() {
|
|
||||||
// Decrement queued task counter
|
|
||||||
g_atomic_int_dec_and_test(&counter_queue);
|
|
||||||
|
|
||||||
ImageType imageType = UNKNOWN;
|
|
||||||
VipsImage *image = vips_image_new();
|
|
||||||
if (baton->buffer_in_len > 1) {
|
|
||||||
// From buffer
|
|
||||||
imageType = sharp_init_image_from_buffer(&image, baton->buffer_in, baton->buffer_in_len, VIPS_ACCESS_RANDOM);
|
|
||||||
if (imageType == UNKNOWN) {
|
|
||||||
(baton->err).append("Input buffer contains unsupported image format");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// From file
|
|
||||||
imageType = sharp_init_image_from_file(&image, baton->file_in.c_str(), VIPS_ACCESS_RANDOM);
|
|
||||||
if (imageType == UNKNOWN) {
|
|
||||||
(baton->err).append("File is of an unsupported image format");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (imageType != UNKNOWN) {
|
|
||||||
// Image type
|
|
||||||
switch (imageType) {
|
|
||||||
case JPEG: baton->format = "jpeg"; break;
|
|
||||||
case PNG: baton->format = "png"; break;
|
|
||||||
case WEBP: baton->format = "webp"; break;
|
|
||||||
case TIFF: baton->format = "tiff"; break;
|
|
||||||
case MAGICK: baton->format = "magick"; break;
|
|
||||||
case UNKNOWN: default: baton->format = "";
|
|
||||||
}
|
|
||||||
// VipsImage attributes
|
|
||||||
baton->width = image->Xsize;
|
|
||||||
baton->height = image->Ysize;
|
|
||||||
baton->space = vips_enum_nick(VIPS_TYPE_INTERPRETATION, image->Type);
|
|
||||||
baton->channels = image->Bands;
|
|
||||||
// EXIF Orientation
|
|
||||||
const char *exif;
|
|
||||||
if (!vips_image_get_string(image, "exif-ifd0-Orientation", &exif)) {
|
|
||||||
baton->orientation = atoi(&exif[0]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Clean up
|
|
||||||
g_object_unref(image);
|
|
||||||
vips_error_clear();
|
|
||||||
vips_thread_shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
void HandleOKCallback () {
|
|
||||||
NanScope();
|
|
||||||
|
|
||||||
Handle<Value> argv[2] = { NanNull(), NanNull() };
|
|
||||||
if (!baton->err.empty()) {
|
|
||||||
// Error
|
|
||||||
argv[0] = NanNew<String>(baton->err.data(), baton->err.size());
|
|
||||||
} else {
|
|
||||||
// Metadata Object
|
|
||||||
Local<Object> info = NanNew<Object>();
|
|
||||||
info->Set(NanNew<String>("format"), NanNew<String>(baton->format));
|
|
||||||
info->Set(NanNew<String>("width"), NanNew<Number>(baton->width));
|
|
||||||
info->Set(NanNew<String>("height"), NanNew<Number>(baton->height));
|
|
||||||
info->Set(NanNew<String>("space"), NanNew<String>(baton->space));
|
|
||||||
info->Set(NanNew<String>("channels"), NanNew<Number>(baton->channels));
|
|
||||||
if (baton->orientation > 0) {
|
|
||||||
info->Set(NanNew<String>("orientation"), NanNew<Number>(baton->orientation));
|
|
||||||
}
|
|
||||||
argv[1] = info;
|
|
||||||
}
|
|
||||||
delete baton;
|
|
||||||
|
|
||||||
// Return to JavaScript
|
|
||||||
callback->Call(2, argv);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
metadata_baton* baton;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
metadata(options, callback)
|
|
||||||
*/
|
|
||||||
NAN_METHOD(metadata) {
|
|
||||||
NanScope();
|
|
||||||
|
|
||||||
// V8 objects are converted to non-V8 types held in the baton struct
|
|
||||||
metadata_baton *baton = new metadata_baton;
|
|
||||||
Local<Object> options = args[0]->ToObject();
|
|
||||||
|
|
||||||
// Input filename
|
|
||||||
baton->file_in = *String::Utf8Value(options->Get(NanNew<String>("fileIn"))->ToString());
|
|
||||||
// Input Buffer object
|
|
||||||
if (options->Get(NanNew<String>("bufferIn"))->IsObject()) {
|
|
||||||
Local<Object> buffer = options->Get(NanNew<String>("bufferIn"))->ToObject();
|
|
||||||
baton->buffer_in_len = Buffer::Length(buffer);
|
|
||||||
baton->buffer_in = Buffer::Data(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Join queue for worker thread
|
|
||||||
NanCallback *callback = new NanCallback(args[1].As<v8::Function>());
|
|
||||||
NanAsyncQueueWorker(new MetadataWorker(callback, baton));
|
|
||||||
|
|
||||||
// Increment queued task counter
|
|
||||||
g_atomic_int_inc(&counter_queue);
|
|
||||||
|
|
||||||
NanReturnUndefined();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resize
|
|
||||||
|
|
||||||
class ResizeWorker : public NanAsyncWorker {
|
|
||||||
public:
|
|
||||||
ResizeWorker(NanCallback *callback, resize_baton *baton) : NanAsyncWorker(callback), baton(baton) {}
|
|
||||||
~ResizeWorker() {}
|
|
||||||
|
|
||||||
void Execute() {
|
|
||||||
// Decrement queued task counter
|
|
||||||
g_atomic_int_dec_and_test(&counter_queue);
|
|
||||||
// Increment processing task counter
|
|
||||||
g_atomic_int_inc(&counter_process);
|
|
||||||
|
|
||||||
// Input
|
|
||||||
ImageType inputImageType = UNKNOWN;
|
|
||||||
VipsImage *in = vips_image_new();
|
|
||||||
if (baton->buffer_in_len > 1) {
|
|
||||||
// From buffer
|
|
||||||
inputImageType = sharp_init_image_from_buffer(&in, baton->buffer_in, baton->buffer_in_len, baton->access_method);
|
|
||||||
if (inputImageType == UNKNOWN) {
|
|
||||||
(baton->err).append("Input buffer contains unsupported image format");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// From file
|
|
||||||
inputImageType = sharp_init_image_from_file(&in, baton->file_in.c_str(), baton->access_method);
|
|
||||||
if (inputImageType == UNKNOWN) {
|
|
||||||
(baton->err).append("File is of an unsupported image format");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (inputImageType == UNKNOWN) {
|
|
||||||
return resize_error(baton, in);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get input image width and height
|
|
||||||
int inputWidth = in->Xsize;
|
|
||||||
int inputHeight = in->Ysize;
|
|
||||||
|
|
||||||
// Calculate angle of rotation, to be carried out later
|
|
||||||
VipsAngle rotation = sharp_calc_rotation(baton->angle, in);
|
|
||||||
if (rotation == VIPS_ANGLE_90 || rotation == VIPS_ANGLE_270) {
|
|
||||||
// Swap input output width and height when rotating by 90 or 270 degrees
|
|
||||||
int swap = inputWidth;
|
|
||||||
inputWidth = inputHeight;
|
|
||||||
inputHeight = swap;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scaling calculations
|
|
||||||
double factor;
|
|
||||||
if (baton->width > 0 && baton->height > 0) {
|
|
||||||
// Fixed width and height
|
|
||||||
double xfactor = static_cast<double>(inputWidth) / static_cast<double>(baton->width);
|
|
||||||
double yfactor = static_cast<double>(inputHeight) / static_cast<double>(baton->height);
|
|
||||||
factor = baton->crop ? std::min(xfactor, yfactor) : std::max(xfactor, yfactor);
|
|
||||||
// if max is set, we need to compute the real size of the thumb image
|
|
||||||
if (baton->max) {
|
|
||||||
if (xfactor > yfactor) {
|
|
||||||
baton->height = round(static_cast<double>(inputHeight) / xfactor);
|
|
||||||
} else {
|
|
||||||
baton->width = round(static_cast<double>(inputWidth) / yfactor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (baton->width > 0) {
|
|
||||||
// Fixed width, auto height
|
|
||||||
factor = static_cast<double>(inputWidth) / static_cast<double>(baton->width);
|
|
||||||
baton->height = floor(static_cast<double>(inputHeight) / factor);
|
|
||||||
} else if (baton->height > 0) {
|
|
||||||
// Fixed height, auto width
|
|
||||||
factor = static_cast<double>(inputHeight) / static_cast<double>(baton->height);
|
|
||||||
baton->width = floor(static_cast<double>(inputWidth) / factor);
|
|
||||||
} else {
|
|
||||||
// Identity transform
|
|
||||||
factor = 1;
|
|
||||||
baton->width = inputWidth;
|
|
||||||
baton->height = inputHeight;
|
|
||||||
}
|
|
||||||
int shrink = floor(factor);
|
|
||||||
if (shrink < 1) {
|
|
||||||
shrink = 1;
|
|
||||||
}
|
|
||||||
double residual = static_cast<double>(shrink) / factor;
|
|
||||||
|
|
||||||
// Do not enlarge the output if the input width *or* height are already less than the required dimensions
|
|
||||||
if (baton->without_enlargement) {
|
|
||||||
if (inputWidth < baton->width || inputHeight < baton->height) {
|
|
||||||
factor = 1;
|
|
||||||
shrink = 1;
|
|
||||||
residual = 0;
|
|
||||||
baton->width = inputWidth;
|
|
||||||
baton->height = inputHeight;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to use libjpeg shrink-on-load, but not when applying gamma correction
|
|
||||||
int shrink_on_load = 1;
|
|
||||||
if (inputImageType == JPEG && baton->gamma == 0) {
|
|
||||||
if (shrink >= 8) {
|
|
||||||
factor = factor / 8;
|
|
||||||
shrink_on_load = 8;
|
|
||||||
} else if (shrink >= 4) {
|
|
||||||
factor = factor / 4;
|
|
||||||
shrink_on_load = 4;
|
|
||||||
} else if (shrink >= 2) {
|
|
||||||
factor = factor / 2;
|
|
||||||
shrink_on_load = 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
VipsImage *shrunk_on_load = vips_image_new();
|
|
||||||
if (shrink_on_load > 1) {
|
|
||||||
// Recalculate integral shrink and double residual
|
|
||||||
factor = std::max(factor, 1.0);
|
|
||||||
shrink = floor(factor);
|
|
||||||
residual = static_cast<double>(shrink) / factor;
|
|
||||||
// Reload input using shrink-on-load
|
|
||||||
if (baton->buffer_in_len > 1) {
|
|
||||||
if (vips_jpegload_buffer(baton->buffer_in, baton->buffer_in_len, &shrunk_on_load, "shrink", shrink_on_load, NULL)) {
|
|
||||||
return resize_error(baton, in);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (vips_jpegload((baton->file_in).c_str(), &shrunk_on_load, "shrink", shrink_on_load, NULL)) {
|
|
||||||
return resize_error(baton, in);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
vips_copy(in, &shrunk_on_load, NULL);
|
|
||||||
}
|
|
||||||
g_object_unref(in);
|
|
||||||
|
|
||||||
// Gamma encoding (darken)
|
|
||||||
if (baton->gamma >= 1 && baton->gamma <= 3) {
|
|
||||||
VipsImage *gamma_encoded = vips_image_new();
|
|
||||||
if (vips_gamma(shrunk_on_load, &gamma_encoded, "exponent", 1.0 / baton->gamma, NULL)) {
|
|
||||||
return resize_error(baton, shrunk_on_load);
|
|
||||||
}
|
|
||||||
g_object_unref(shrunk_on_load);
|
|
||||||
shrunk_on_load = gamma_encoded;
|
|
||||||
}
|
|
||||||
|
|
||||||
VipsImage *shrunk = vips_image_new();
|
|
||||||
if (shrink > 1) {
|
|
||||||
// Use vips_shrink with the integral reduction
|
|
||||||
if (vips_shrink(shrunk_on_load, &shrunk, shrink, shrink, NULL)) {
|
|
||||||
return resize_error(baton, shrunk_on_load);
|
|
||||||
}
|
|
||||||
// Recalculate residual float based on dimensions of required vs shrunk images
|
|
||||||
double shrunkWidth = shrunk->Xsize;
|
|
||||||
double shrunkHeight = shrunk->Ysize;
|
|
||||||
if (rotation == VIPS_ANGLE_90 || rotation == VIPS_ANGLE_270) {
|
|
||||||
// Swap input output width and height when rotating by 90 or 270 degrees
|
|
||||||
int swap = shrunkWidth;
|
|
||||||
shrunkWidth = shrunkHeight;
|
|
||||||
shrunkHeight = swap;
|
|
||||||
}
|
|
||||||
double residualx = static_cast<double>(baton->width) / static_cast<double>(shrunkWidth);
|
|
||||||
double residualy = static_cast<double>(baton->height) / static_cast<double>(shrunkHeight);
|
|
||||||
if (baton->crop || baton->max) {
|
|
||||||
residual = std::max(residualx, residualy);
|
|
||||||
} else {
|
|
||||||
residual = std::min(residualx, residualy);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
vips_copy(shrunk_on_load, &shrunk, NULL);
|
|
||||||
}
|
|
||||||
g_object_unref(shrunk_on_load);
|
|
||||||
|
|
||||||
// Use vips_affine with the remaining float part
|
|
||||||
VipsImage *affined = vips_image_new();
|
|
||||||
if (residual != 0) {
|
|
||||||
// Create interpolator - "bilinear" (default), "bicubic" or "nohalo"
|
|
||||||
VipsInterpolate *interpolator = vips_interpolate_new(baton->interpolator.c_str());
|
|
||||||
// Perform affine transformation
|
|
||||||
if (vips_affine(shrunk, &affined, residual, 0, 0, residual, "interpolate", interpolator, NULL)) {
|
|
||||||
g_object_unref(interpolator);
|
|
||||||
return resize_error(baton, shrunk);
|
|
||||||
}
|
|
||||||
g_object_unref(interpolator);
|
|
||||||
} else {
|
|
||||||
vips_copy(shrunk, &affined, NULL);
|
|
||||||
}
|
|
||||||
g_object_unref(shrunk);
|
|
||||||
|
|
||||||
// Rotate
|
|
||||||
VipsImage *rotated = vips_image_new();
|
|
||||||
if (rotation != VIPS_ANGLE_0) {
|
|
||||||
if (vips_rot(affined, &rotated, rotation, NULL)) {
|
|
||||||
return resize_error(baton, affined);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
vips_copy(affined, &rotated, NULL);
|
|
||||||
}
|
|
||||||
g_object_unref(affined);
|
|
||||||
|
|
||||||
// Crop/embed
|
|
||||||
VipsImage *canvased = vips_image_new();
|
|
||||||
if (rotated->Xsize != baton->width || rotated->Ysize != baton->height) {
|
|
||||||
if (baton->crop || baton->max) {
|
|
||||||
// Crop/max
|
|
||||||
int left;
|
|
||||||
int top;
|
|
||||||
std::tie(left, top) = sharp_calc_crop(rotated->Xsize, rotated->Ysize, baton->width, baton->height, baton->gravity);
|
|
||||||
int width = std::min(rotated->Xsize, baton->width);
|
|
||||||
int height = std::min(rotated->Ysize, baton->height);
|
|
||||||
if (vips_extract_area(rotated, &canvased, left, top, width, height, NULL)) {
|
|
||||||
return resize_error(baton, rotated);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Embed
|
|
||||||
int left = (baton->width - rotated->Xsize) / 2;
|
|
||||||
int top = (baton->height - rotated->Ysize) / 2;
|
|
||||||
if (vips_embed(rotated, &canvased, left, top, baton->width, baton->height, "extend", baton->extend, NULL)) {
|
|
||||||
return resize_error(baton, rotated);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
vips_copy(rotated, &canvased, NULL);
|
|
||||||
}
|
|
||||||
g_object_unref(rotated);
|
|
||||||
|
|
||||||
// Mild sharpen
|
|
||||||
VipsImage *sharpened = vips_image_new();
|
|
||||||
if (baton->sharpen) {
|
|
||||||
VipsImage *sharpen = vips_image_new_matrixv(3, 3,
|
|
||||||
-1.0, -1.0, -1.0,
|
|
||||||
-1.0, 32.0, -1.0,
|
|
||||||
-1.0, -1.0, -1.0);
|
|
||||||
vips_image_set_double(sharpen, "scale", 24);
|
|
||||||
if (vips_conv(canvased, &sharpened, sharpen, NULL)) {
|
|
||||||
g_object_unref(sharpen);
|
|
||||||
return resize_error(baton, canvased);
|
|
||||||
}
|
|
||||||
g_object_unref(sharpen);
|
|
||||||
} else {
|
|
||||||
vips_copy(canvased, &sharpened, NULL);
|
|
||||||
}
|
|
||||||
g_object_unref(canvased);
|
|
||||||
|
|
||||||
// Gamma decoding (brighten)
|
|
||||||
if (baton->gamma >= 1 && baton->gamma <= 3) {
|
|
||||||
VipsImage *gamma_decoded = vips_image_new();
|
|
||||||
if (vips_gamma(sharpened, &gamma_decoded, "exponent", baton->gamma, NULL)) {
|
|
||||||
return resize_error(baton, sharpened);
|
|
||||||
}
|
|
||||||
g_object_unref(sharpened);
|
|
||||||
sharpened = gamma_decoded;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Always convert to sRGB colour space
|
|
||||||
VipsImage *colourspaced = vips_image_new();
|
|
||||||
vips_colourspace(sharpened, &colourspaced, VIPS_INTERPRETATION_sRGB, NULL);
|
|
||||||
g_object_unref(sharpened);
|
|
||||||
|
|
||||||
// Generate image tile cache when interlace output is required
|
|
||||||
VipsImage *cached = vips_image_new();
|
|
||||||
if (baton->progressive) {
|
|
||||||
if (vips_tilecache(colourspaced, &cached, "threaded", TRUE, "persistent", TRUE, "max_tiles", -1, NULL)) {
|
|
||||||
return resize_error(baton, colourspaced);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
vips_copy(colourspaced, &cached, NULL);
|
|
||||||
}
|
|
||||||
g_object_unref(colourspaced);
|
|
||||||
|
|
||||||
// Output
|
|
||||||
VipsImage *output = cached;
|
|
||||||
if (baton->output == "__jpeg" || (baton->output == "__input" && inputImageType == JPEG)) {
|
|
||||||
// Write JPEG to buffer
|
|
||||||
if (vips_jpegsave_buffer(output, &baton->buffer_out, &baton->buffer_out_len, "strip", !baton->withMetadata,
|
|
||||||
"Q", baton->quality, "optimize_coding", TRUE, "interlace", baton->progressive, NULL)) {
|
|
||||||
return resize_error(baton, output);
|
|
||||||
}
|
|
||||||
baton->output_format = "jpeg";
|
|
||||||
} else if (baton->output == "__png" || (baton->output == "__input" && inputImageType == PNG)) {
|
|
||||||
// Write PNG to buffer
|
|
||||||
if (vips_pngsave_buffer(output, &baton->buffer_out, &baton->buffer_out_len, "strip", !baton->withMetadata,
|
|
||||||
"compression", baton->compressionLevel, "interlace", baton->progressive, NULL)) {
|
|
||||||
return resize_error(baton, output);
|
|
||||||
}
|
|
||||||
baton->output_format = "png";
|
|
||||||
} else if (baton->output == "__webp" || (baton->output == "__input" && inputImageType == WEBP)) {
|
|
||||||
// Write WEBP to buffer
|
|
||||||
if (vips_webpsave_buffer(output, &baton->buffer_out, &baton->buffer_out_len, "strip", !baton->withMetadata,
|
|
||||||
"Q", baton->quality, NULL)) {
|
|
||||||
return resize_error(baton, output);
|
|
||||||
}
|
|
||||||
baton->output_format = "webp";
|
|
||||||
} else {
|
|
||||||
bool output_jpeg = is_jpeg(baton->output);
|
|
||||||
bool output_png = is_png(baton->output);
|
|
||||||
bool output_webp = is_webp(baton->output);
|
|
||||||
bool output_tiff = is_tiff(baton->output);
|
|
||||||
bool match_input = !(output_jpeg || output_png || output_webp || output_tiff);
|
|
||||||
if (output_jpeg || (match_input && inputImageType == JPEG)) {
|
|
||||||
// Write JPEG to file
|
|
||||||
if (vips_jpegsave(output, baton->output.c_str(), "strip", !baton->withMetadata,
|
|
||||||
"Q", baton->quality, "optimize_coding", TRUE, "interlace", baton->progressive, NULL)) {
|
|
||||||
return resize_error(baton, output);
|
|
||||||
}
|
|
||||||
baton->output_format = "jpeg";
|
|
||||||
} else if (output_png || (match_input && inputImageType == PNG)) {
|
|
||||||
// Write PNG to file
|
|
||||||
if (vips_pngsave(output, baton->output.c_str(), "strip", !baton->withMetadata,
|
|
||||||
"compression", baton->compressionLevel, "interlace", baton->progressive, NULL)) {
|
|
||||||
return resize_error(baton, output);
|
|
||||||
}
|
|
||||||
baton->output_format = "png";
|
|
||||||
} else if (output_webp || (match_input && inputImageType == WEBP)) {
|
|
||||||
// Write WEBP to file
|
|
||||||
if (vips_webpsave(output, baton->output.c_str(), "strip", !baton->withMetadata,
|
|
||||||
"Q", baton->quality, NULL)) {
|
|
||||||
return resize_error(baton, output);
|
|
||||||
}
|
|
||||||
baton->output_format = "webp";
|
|
||||||
} else if (output_tiff || (match_input && inputImageType == TIFF)) {
|
|
||||||
// Write TIFF to file
|
|
||||||
if (vips_tiffsave(output, baton->output.c_str(), "strip", !baton->withMetadata,
|
|
||||||
"compression", VIPS_FOREIGN_TIFF_COMPRESSION_JPEG, "Q", baton->quality, NULL)) {
|
|
||||||
return resize_error(baton, output);
|
|
||||||
}
|
|
||||||
baton->output_format = "tiff";
|
|
||||||
} else {
|
|
||||||
(baton->err).append("Unsupported output " + baton->output);
|
|
||||||
return resize_error(baton, output);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
g_object_unref(output);
|
|
||||||
|
|
||||||
// Clean up libvips' per-request data and threads
|
|
||||||
vips_error_clear();
|
|
||||||
vips_thread_shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
void HandleOKCallback () {
|
|
||||||
NanScope();
|
|
||||||
|
|
||||||
Handle<Value> argv[3] = { NanNull(), NanNull(), NanNull() };
|
|
||||||
if (!baton->err.empty()) {
|
|
||||||
// Error
|
|
||||||
argv[0] = NanNew<String>(baton->err.data(), baton->err.size());
|
|
||||||
} else {
|
|
||||||
// Info Object
|
|
||||||
Local<Object> info = NanNew<Object>();
|
|
||||||
info->Set(NanNew<String>("format"), NanNew<String>(baton->output_format));
|
|
||||||
info->Set(NanNew<String>("width"), NanNew<Number>(baton->width));
|
|
||||||
info->Set(NanNew<String>("height"), NanNew<Number>(baton->height));
|
|
||||||
|
|
||||||
if (baton->buffer_out_len > 0) {
|
|
||||||
// Buffer
|
|
||||||
argv[1] = NanNewBufferHandle(static_cast<char*>(baton->buffer_out), baton->buffer_out_len);
|
|
||||||
g_free(baton->buffer_out);
|
|
||||||
argv[2] = info;
|
|
||||||
} else {
|
|
||||||
// File
|
|
||||||
argv[1] = info;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delete baton;
|
|
||||||
|
|
||||||
// Decrement processing task counter
|
|
||||||
g_atomic_int_dec_and_test(&counter_process);
|
|
||||||
|
|
||||||
// Return to JavaScript
|
|
||||||
callback->Call(3, argv);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
resize_baton* baton;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
resize(options, output, callback)
|
|
||||||
*/
|
|
||||||
NAN_METHOD(resize) {
|
|
||||||
NanScope();
|
|
||||||
|
|
||||||
// V8 objects are converted to non-V8 types held in the baton struct
|
|
||||||
resize_baton *baton = new resize_baton;
|
|
||||||
Local<Object> options = args[0]->ToObject();
|
|
||||||
|
|
||||||
// Input filename
|
|
||||||
baton->file_in = *String::Utf8Value(options->Get(NanNew<String>("fileIn"))->ToString());
|
|
||||||
// Input Buffer object
|
|
||||||
if (options->Get(NanNew<String>("bufferIn"))->IsObject()) {
|
|
||||||
Local<Object> buffer = options->Get(NanNew<String>("bufferIn"))->ToObject();
|
|
||||||
baton->buffer_in_len = Buffer::Length(buffer);
|
|
||||||
baton->buffer_in = Buffer::Data(buffer);
|
|
||||||
}
|
|
||||||
// Output image dimensions
|
|
||||||
baton->width = options->Get(NanNew<String>("width"))->Int32Value();
|
|
||||||
baton->height = options->Get(NanNew<String>("height"))->Int32Value();
|
|
||||||
// Canvas options
|
|
||||||
Local<String> canvas = options->Get(NanNew<String>("canvas"))->ToString();
|
|
||||||
if (canvas->Equals(NanNew<String>("c"))) {
|
|
||||||
baton->crop = true;
|
|
||||||
} else if (canvas->Equals(NanNew<String>("w"))) {
|
|
||||||
baton->extend = VIPS_EXTEND_WHITE;
|
|
||||||
} else if (canvas->Equals(NanNew<String>("b"))) {
|
|
||||||
baton->extend = VIPS_EXTEND_BLACK;
|
|
||||||
} else if (canvas->Equals(NanNew<String>("m"))) {
|
|
||||||
baton->max = true;
|
|
||||||
}
|
|
||||||
// Other options
|
|
||||||
baton->gravity = options->Get(NanNew<String>("gravity"))->Int32Value();
|
|
||||||
baton->sharpen = options->Get(NanNew<String>("sharpen"))->BooleanValue();
|
|
||||||
baton->interpolator = *String::Utf8Value(options->Get(NanNew<String>("interpolator"))->ToString());
|
|
||||||
baton->gamma = options->Get(NanNew<String>("gamma"))->NumberValue();
|
|
||||||
baton->progressive = options->Get(NanNew<String>("progressive"))->BooleanValue();
|
|
||||||
baton->without_enlargement = options->Get(NanNew<String>("withoutEnlargement"))->BooleanValue();
|
|
||||||
baton->access_method = options->Get(NanNew<String>("sequentialRead"))->BooleanValue() ? VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_RANDOM;
|
|
||||||
baton->quality = options->Get(NanNew<String>("quality"))->Int32Value();
|
|
||||||
baton->compressionLevel = options->Get(NanNew<String>("compressionLevel"))->Int32Value();
|
|
||||||
baton->angle = options->Get(NanNew<String>("angle"))->Int32Value();
|
|
||||||
baton->withMetadata = options->Get(NanNew<String>("withMetadata"))->BooleanValue();
|
|
||||||
// Output filename or __format for Buffer
|
|
||||||
baton->output = *String::Utf8Value(options->Get(NanNew<String>("output"))->ToString());
|
|
||||||
|
|
||||||
// Join queue for worker thread
|
|
||||||
NanCallback *callback = new NanCallback(args[1].As<v8::Function>());
|
|
||||||
NanAsyncQueueWorker(new ResizeWorker(callback, baton));
|
|
||||||
|
|
||||||
// Increment queued task counter
|
|
||||||
g_atomic_int_inc(&counter_queue);
|
|
||||||
|
|
||||||
NanReturnUndefined();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Get and set cache memory and item limits
|
|
||||||
*/
|
|
||||||
NAN_METHOD(cache) {
|
|
||||||
NanScope();
|
|
||||||
|
|
||||||
// Set cache memory limit
|
|
||||||
if (args[0]->IsInt32()) {
|
|
||||||
int newMax = args[0]->Int32Value() * 1048576;
|
|
||||||
int oldMax = vips_cache_get_max_mem();
|
|
||||||
vips_cache_set_max_mem(newMax);
|
|
||||||
|
|
||||||
// Notify the V8 garbage collector of delta in max cache size
|
|
||||||
NanAdjustExternalMemory(newMax - oldMax);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set cache items limit
|
|
||||||
if (args[1]->IsInt32()) {
|
|
||||||
vips_cache_set_max(args[1]->Int32Value());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get cache statistics
|
|
||||||
Local<Object> cache = NanNew<Object>();
|
|
||||||
cache->Set(NanNew<String>("current"), NanNew<Number>(vips_tracked_get_mem() / 1048576));
|
|
||||||
cache->Set(NanNew<String>("high"), NanNew<Number>(vips_tracked_get_mem_highwater() / 1048576));
|
|
||||||
cache->Set(NanNew<String>("memory"), NanNew<Number>(vips_cache_get_max_mem() / 1048576));
|
|
||||||
cache->Set(NanNew<String>("items"), NanNew<Number>(vips_cache_get_max()));
|
|
||||||
NanReturnValue(cache);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Get and set size of thread pool
|
|
||||||
*/
|
|
||||||
NAN_METHOD(concurrency) {
|
|
||||||
NanScope();
|
|
||||||
|
|
||||||
// Set concurrency
|
|
||||||
if (args[0]->IsInt32()) {
|
|
||||||
vips_concurrency_set(args[0]->Int32Value());
|
|
||||||
}
|
|
||||||
// Get concurrency
|
|
||||||
NanReturnValue(NanNew<Number>(vips_concurrency_get()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Get internal counters (queued tasks, processing tasks)
|
|
||||||
*/
|
|
||||||
NAN_METHOD(counters) {
|
|
||||||
NanScope();
|
|
||||||
Local<Object> counters = NanNew<Object>();
|
|
||||||
counters->Set(NanNew<String>("queue"), NanNew<Number>(counter_queue));
|
|
||||||
counters->Set(NanNew<String>("process"), NanNew<Number>(counter_process));
|
|
||||||
NanReturnValue(counters);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void at_exit(void* arg) {
|
|
||||||
NanScope();
|
|
||||||
vips_shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" void init(Handle<Object> target) {
|
|
||||||
NanScope();
|
NanScope();
|
||||||
vips_init("sharp");
|
vips_init("sharp");
|
||||||
AtExit(at_exit);
|
|
||||||
|
|
||||||
// Set libvips operation cache limits
|
// Set libvips operation cache limits
|
||||||
vips_cache_set_max_mem(100 * 1048576); // 100 MB
|
vips_cache_set_max_mem(100 * 1024 * 1024); // 100 MB
|
||||||
vips_cache_set_max(500); // 500 operations
|
vips_cache_set_max(500); // 500 operations
|
||||||
|
|
||||||
// Notify the V8 garbage collector of max cache size
|
|
||||||
NanAdjustExternalMemory(vips_cache_get_max_mem());
|
|
||||||
|
|
||||||
// Methods available to JavaScript
|
// Methods available to JavaScript
|
||||||
NODE_SET_METHOD(target, "metadata", metadata);
|
NODE_SET_METHOD(target, "metadata", metadata);
|
||||||
NODE_SET_METHOD(target, "resize", resize);
|
NODE_SET_METHOD(target, "resize", resize);
|
||||||
NODE_SET_METHOD(target, "cache", cache);
|
NODE_SET_METHOD(target, "cache", cache);
|
||||||
NODE_SET_METHOD(target, "concurrency", concurrency);
|
NODE_SET_METHOD(target, "concurrency", concurrency);
|
||||||
NODE_SET_METHOD(target, "counters", counters);
|
NODE_SET_METHOD(target, "counters", counters);
|
||||||
|
NODE_SET_METHOD(target, "libvipsVersion", libvipsVersion);
|
||||||
|
NODE_SET_METHOD(target, "format", format);
|
||||||
}
|
}
|
||||||
|
|
||||||
NODE_MODULE(sharp, init)
|
NODE_MODULE(sharp, init)
|
||||||
|
|||||||
144
src/utilities.cc
Executable file
@@ -0,0 +1,144 @@
|
|||||||
|
#include <node.h>
|
||||||
|
#include <vips/vips.h>
|
||||||
|
|
||||||
|
#include "nan.h"
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
#include "utilities.h"
|
||||||
|
|
||||||
|
using v8::Local;
|
||||||
|
using v8::Object;
|
||||||
|
using v8::Number;
|
||||||
|
using v8::String;
|
||||||
|
using v8::Boolean;
|
||||||
|
|
||||||
|
using sharp::counterQueue;
|
||||||
|
using sharp::counterProcess;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Get and set cache memory and item limits
|
||||||
|
*/
|
||||||
|
NAN_METHOD(cache) {
|
||||||
|
NanScope();
|
||||||
|
|
||||||
|
// Set cache memory limit
|
||||||
|
if (args[0]->IsInt32()) {
|
||||||
|
int newMax = args[0]->Int32Value() * 1048576;
|
||||||
|
int oldMax = vips_cache_get_max_mem();
|
||||||
|
vips_cache_set_max_mem(newMax);
|
||||||
|
|
||||||
|
// Notify the V8 garbage collector of delta in max cache size
|
||||||
|
NanAdjustExternalMemory(newMax - oldMax);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set cache items limit
|
||||||
|
if (args[1]->IsInt32()) {
|
||||||
|
vips_cache_set_max(args[1]->Int32Value());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get cache statistics
|
||||||
|
Local<Object> cache = NanNew<Object>();
|
||||||
|
cache->Set(NanNew<String>("current"), NanNew<Number>(vips_tracked_get_mem() / 1048576));
|
||||||
|
cache->Set(NanNew<String>("high"), NanNew<Number>(vips_tracked_get_mem_highwater() / 1048576));
|
||||||
|
cache->Set(NanNew<String>("memory"), NanNew<Number>(vips_cache_get_max_mem() / 1048576));
|
||||||
|
cache->Set(NanNew<String>("items"), NanNew<Number>(vips_cache_get_max()));
|
||||||
|
NanReturnValue(cache);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Get and set size of thread pool
|
||||||
|
*/
|
||||||
|
NAN_METHOD(concurrency) {
|
||||||
|
NanScope();
|
||||||
|
|
||||||
|
// Set concurrency
|
||||||
|
if (args[0]->IsInt32()) {
|
||||||
|
vips_concurrency_set(args[0]->Int32Value());
|
||||||
|
}
|
||||||
|
// Get concurrency
|
||||||
|
NanReturnValue(NanNew<Number>(vips_concurrency_get()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Get internal counters (queued tasks, processing tasks)
|
||||||
|
*/
|
||||||
|
NAN_METHOD(counters) {
|
||||||
|
NanScope();
|
||||||
|
Local<Object> counters = NanNew<Object>();
|
||||||
|
counters->Set(NanNew<String>("queue"), NanNew<Number>(counterQueue));
|
||||||
|
counters->Set(NanNew<String>("process"), NanNew<Number>(counterProcess));
|
||||||
|
NanReturnValue(counters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Get libvips version
|
||||||
|
*/
|
||||||
|
NAN_METHOD(libvipsVersion) {
|
||||||
|
NanScope();
|
||||||
|
char version[9];
|
||||||
|
g_snprintf(version, sizeof(version), "%d.%d.%d", vips_version(0), vips_version(1), vips_version(2));
|
||||||
|
NanReturnValue(NanNew<String>(version));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Get available input/output file/buffer/stream formats
|
||||||
|
*/
|
||||||
|
NAN_METHOD(format) {
|
||||||
|
NanScope();
|
||||||
|
|
||||||
|
// Attribute names
|
||||||
|
Local<String> attrId = NanNew<String>("id");
|
||||||
|
Local<String> attrInput = NanNew<String>("input");
|
||||||
|
Local<String> attrOutput = NanNew<String>("output");
|
||||||
|
Local<String> attrFile = NanNew<String>("file");
|
||||||
|
Local<String> attrBuffer = NanNew<String>("buffer");
|
||||||
|
Local<String> attrStream = NanNew<String>("stream");
|
||||||
|
|
||||||
|
// Which load/save operations are available for each compressed format?
|
||||||
|
Local<Object> format = NanNew<Object>();
|
||||||
|
for (std::string f : {"jpeg", "png", "webp", "tiff", "magick", "openslide", "dz"}) {
|
||||||
|
// Input
|
||||||
|
Local<Object> input = NanNew<Object>();
|
||||||
|
input->Set(attrFile, NanNew<Boolean>(
|
||||||
|
vips_type_find("VipsOperation", (f + "load").c_str())));
|
||||||
|
input->Set(attrBuffer, NanNew<Boolean>(
|
||||||
|
vips_type_find("VipsOperation", (f + "load_buffer").c_str())));
|
||||||
|
input->Set(attrStream, input->Get(attrBuffer));
|
||||||
|
// Output
|
||||||
|
Local<Object> output = NanNew<Object>();
|
||||||
|
output->Set(attrFile, NanNew<Boolean>(
|
||||||
|
vips_type_find("VipsOperation", (f + "save").c_str())));
|
||||||
|
output->Set(attrBuffer, NanNew<Boolean>(
|
||||||
|
vips_type_find("VipsOperation", (f + "save_buffer").c_str())));
|
||||||
|
output->Set(attrStream, output->Get(attrBuffer));
|
||||||
|
// Other attributes
|
||||||
|
Local<Object> container = NanNew<Object>();
|
||||||
|
Local<String> formatId = NanNew<String>(f);
|
||||||
|
container->Set(attrId, formatId);
|
||||||
|
container->Set(attrInput, input);
|
||||||
|
container->Set(attrOutput, output);
|
||||||
|
// Add to set of formats
|
||||||
|
format->Set(formatId, container);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Raw, uncompressed data
|
||||||
|
Local<Object> raw = NanNew<Object>();
|
||||||
|
raw->Set(attrId, NanNew<String>("raw"));
|
||||||
|
format->Set(NanNew<String>("raw"), raw);
|
||||||
|
// No support for raw input yet, so always false
|
||||||
|
Local<Boolean> unsupported = NanNew<Boolean>(false);
|
||||||
|
Local<Object> rawInput = NanNew<Object>();
|
||||||
|
rawInput->Set(attrFile, unsupported);
|
||||||
|
rawInput->Set(attrBuffer, unsupported);
|
||||||
|
rawInput->Set(attrStream, unsupported);
|
||||||
|
raw->Set(attrInput, rawInput);
|
||||||
|
// Raw output via Buffer/Stream is available in libvips >= 7.42.0
|
||||||
|
Local<Boolean> supportsRawOutput = NanNew<Boolean>(vips_version(0) >= 8 || (vips_version(0) == 7 && vips_version(1) >= 42));
|
||||||
|
Local<Object> rawOutput = NanNew<Object>();
|
||||||
|
rawOutput->Set(attrFile, unsupported);
|
||||||
|
rawOutput->Set(attrBuffer, supportsRawOutput);
|
||||||
|
rawOutput->Set(attrStream, supportsRawOutput);
|
||||||
|
raw->Set(attrOutput, rawOutput);
|
||||||
|
|
||||||
|
NanReturnValue(format);
|
||||||
|
}
|
||||||
12
src/utilities.h
Executable file
@@ -0,0 +1,12 @@
|
|||||||
|
#ifndef SRC_UTILITIES_H_
|
||||||
|
#define SRC_UTILITIES_H_
|
||||||
|
|
||||||
|
#include "nan.h"
|
||||||
|
|
||||||
|
NAN_METHOD(cache);
|
||||||
|
NAN_METHOD(concurrency);
|
||||||
|
NAN_METHOD(counters);
|
||||||
|
NAN_METHOD(libvipsVersion);
|
||||||
|
NAN_METHOD(format);
|
||||||
|
|
||||||
|
#endif // SRC_UTILITIES_H_
|
||||||
22
test/bench/package.json
Executable file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "sharp-benchmark",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"author": "Lovell Fuller <npm@lovell.info>",
|
||||||
|
"description": "Benchmark and performance tests for sharp",
|
||||||
|
"scripts": {
|
||||||
|
"test": "node perf && node random && node parallel"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"imagemagick": "^0.1.3",
|
||||||
|
"imagemagick-native": "mash/node-imagemagick-native",
|
||||||
|
"gm": "^1.17.0",
|
||||||
|
"async": "^0.9.0",
|
||||||
|
"semver": "^4.3.0",
|
||||||
|
"benchmark": "^1.0.0"
|
||||||
|
},
|
||||||
|
"license": "Apache 2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
var sharp = require("../index");
|
'use strict';
|
||||||
var fs = require("fs");
|
|
||||||
var path = require("path");
|
var assert = require('assert');
|
||||||
var assert = require("assert");
|
var async = require('async');
|
||||||
var async = require("async");
|
|
||||||
|
var sharp = require('../../index');
|
||||||
|
var fixtures = require('../fixtures');
|
||||||
|
|
||||||
var inputJpg = path.join(__dirname, "fixtures/2569067123_aca715a2ee_o.jpg"); // http://www.flickr.com/photos/grizdave/2569067123/
|
|
||||||
var width = 720;
|
var width = 720;
|
||||||
var height = 480;
|
var height = 480;
|
||||||
|
|
||||||
@@ -18,7 +19,8 @@ async.mapSeries([1, 1, 2, 4, 8, 16, 32, 64, 128], function(parallelism, next) {
|
|||||||
var start = new Date().getTime();
|
var start = new Date().getTime();
|
||||||
async.times(parallelism,
|
async.times(parallelism,
|
||||||
function(id, callback) {
|
function(id, callback) {
|
||||||
sharp(inputJpg).resize(width, height).toBuffer(function(err, buffer) {
|
/*jslint unused: false */
|
||||||
|
sharp(fixtures.inputJpg).resize(width, height).toBuffer(function(err, buffer) {
|
||||||
buffer = null;
|
buffer = null;
|
||||||
callback(err, new Date().getTime() - start);
|
callback(err, new Date().getTime() - start);
|
||||||
});
|
});
|
||||||
@@ -29,7 +31,7 @@ async.mapSeries([1, 1, 2, 4, 8, 16, 32, 64, 128], function(parallelism, next) {
|
|||||||
var mean = ids.reduce(function(a, b) {
|
var mean = ids.reduce(function(a, b) {
|
||||||
return a + b;
|
return a + b;
|
||||||
}) / ids.length;
|
}) / ids.length;
|
||||||
console.log(parallelism + " parallel calls: fastest=" + ids[0] + "ms slowest=" + ids[ids.length - 1] + "ms mean=" + mean + "ms");
|
console.log(parallelism + ' parallel calls: fastest=' + ids[0] + 'ms slowest=' + ids[ids.length - 1] + 'ms mean=' + mean + 'ms');
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -1,47 +1,42 @@
|
|||||||
var sharp = require("../index");
|
'use strict';
|
||||||
var fs = require("fs");
|
|
||||||
var path = require("path");
|
|
||||||
var imagemagick = require("imagemagick");
|
|
||||||
var imagemagickNative = require("imagemagick-native");
|
|
||||||
var gm = require("gm");
|
|
||||||
var async = require("async");
|
|
||||||
var assert = require("assert");
|
|
||||||
var Benchmark = require("benchmark");
|
|
||||||
|
|
||||||
var fixturesPath = path.join(__dirname, "fixtures");
|
var fs = require('fs');
|
||||||
|
|
||||||
var inputJpg = path.join(fixturesPath, "2569067123_aca715a2ee_o.jpg"); // http://www.flickr.com/photos/grizdave/2569067123/
|
var async = require('async');
|
||||||
var outputJpg = path.join(fixturesPath, "output.jpg");
|
var assert = require('assert');
|
||||||
|
var Benchmark = require('benchmark');
|
||||||
|
var semver = require('semver');
|
||||||
|
|
||||||
var inputPng = path.join(fixturesPath, "50020484-00001.png"); // http://c.searspartsdirect.com/lis_png/PLDM/50020484-00001.png
|
var imagemagick = require('imagemagick');
|
||||||
var outputPng = path.join(fixturesPath, "output.png");
|
var imagemagickNative = require('imagemagick-native');
|
||||||
|
var gm = require('gm');
|
||||||
|
var sharp = require('../../index');
|
||||||
|
|
||||||
var inputWebp = path.join(fixturesPath, "4.webp"); // http://www.gstatic.com/webp/gallery/4.webp
|
var fixtures = require('../fixtures');
|
||||||
var outputWebp = path.join(fixturesPath, "output.webp");
|
|
||||||
|
|
||||||
var inputTiff = path.join(fixturesPath, "G31D.TIF"); // http://www.fileformat.info/format/tiff/sample/e6c9a6e5253348f4aef6d17b534360ab/index.htm
|
|
||||||
var outputTiff = path.join(fixturesPath, "output.tiff");
|
|
||||||
|
|
||||||
var inputGif = path.join(fixturesPath, "Crash_test.gif"); // http://upload.wikimedia.org/wikipedia/commons/e/e3/Crash_test.gif
|
|
||||||
|
|
||||||
var width = 720;
|
var width = 720;
|
||||||
var height = 480;
|
var height = 480;
|
||||||
|
|
||||||
|
// Approximately equivalent to fast bilinear
|
||||||
|
var magickFilter = 'Triangle';
|
||||||
|
|
||||||
// Disable libvips cache to ensure tests are as fair as they can be
|
// Disable libvips cache to ensure tests are as fair as they can be
|
||||||
sharp.cache(0);
|
sharp.cache(0);
|
||||||
|
|
||||||
async.series({
|
async.series({
|
||||||
jpeg: function(callback) {
|
jpeg: function(callback) {
|
||||||
var inputJpgBuffer = fs.readFileSync(inputJpg);
|
var inputJpgBuffer = fs.readFileSync(fixtures.inputJpg);
|
||||||
(new Benchmark.Suite("jpeg")).add("imagemagick-file-file", {
|
(new Benchmark.Suite('jpeg')).add('imagemagick-file-file', {
|
||||||
defer: true,
|
defer: true,
|
||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
imagemagick.resize({
|
imagemagick.resize({
|
||||||
srcPath: inputJpg,
|
srcPath: fixtures.inputJpg,
|
||||||
dstPath: outputJpg,
|
dstPath: fixtures.outputJpg,
|
||||||
quality: 0.8,
|
quality: 0.8,
|
||||||
width: width,
|
width: width,
|
||||||
height: height
|
height: height,
|
||||||
|
format: 'jpg',
|
||||||
|
filter: magickFilter
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
@@ -50,7 +45,7 @@ async.series({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).add("imagemagick-native-buffer-buffer", {
|
}).add('imagemagick-native-buffer-buffer', {
|
||||||
defer: true,
|
defer: true,
|
||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
imagemagickNative.convert({
|
imagemagickNative.convert({
|
||||||
@@ -58,25 +53,9 @@ async.series({
|
|||||||
quality: 80,
|
quality: 80,
|
||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
format: 'JPEG'
|
format: 'JPEG',
|
||||||
});
|
filter: magickFilter
|
||||||
deferred.resolve();
|
}, function (err, buffer) {
|
||||||
}
|
|
||||||
}).add("gm-buffer-file", {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
gm(inputJpgBuffer).resize(width, height).quality(80).write(outputJpg, function (err) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
} else {
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).add("gm-buffer-buffer", {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
gm(inputJpgBuffer).resize(width, height).quality(80).toBuffer(function (err, buffer) {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
} else {
|
} else {
|
||||||
@@ -85,10 +64,72 @@ async.series({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).add("gm-file-file", {
|
}).add('gm-buffer-file', {
|
||||||
defer: true,
|
defer: true,
|
||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
gm(inputJpg).resize(width, height).quality(80).write(outputJpg, function (err) {
|
gm(inputJpgBuffer)
|
||||||
|
.resize(width, height)
|
||||||
|
.filter(magickFilter)
|
||||||
|
.quality(80)
|
||||||
|
.write(fixtures.outputJpg, function (err) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).add('gm-buffer-buffer', {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
gm(inputJpgBuffer)
|
||||||
|
.resize(width, height)
|
||||||
|
.filter(magickFilter)
|
||||||
|
.quality(80)
|
||||||
|
.toBuffer(function (err, buffer) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
assert.notStrictEqual(null, buffer);
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).add('gm-file-file', {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
gm(fixtures.inputJpg)
|
||||||
|
.resize(width, height)
|
||||||
|
.filter(magickFilter)
|
||||||
|
.quality(80)
|
||||||
|
.write(fixtures.outputJpg, function (err) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).add('gm-file-buffer', {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
gm(fixtures.inputJpg)
|
||||||
|
.resize(width, height)
|
||||||
|
.filter(magickFilter)
|
||||||
|
.quality(80)
|
||||||
|
.toBuffer(function (err, buffer) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
assert.notStrictEqual(null, buffer);
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).add('sharp-buffer-file', {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
sharp(inputJpgBuffer).resize(width, height).toFile(fixtures.outputJpg, function(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
} else {
|
} else {
|
||||||
@@ -96,30 +137,7 @@ async.series({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).add("gm-file-buffer", {
|
}).add('sharp-buffer-buffer', {
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
gm(inputJpg).resize(width, height).quality(80).toBuffer(function (err, buffer) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
} else {
|
|
||||||
assert.notStrictEqual(null, buffer);
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).add("sharp-buffer-file", {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
sharp(inputJpgBuffer).resize(width, height).toFile(outputJpg, function(err) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
} else {
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).add("sharp-buffer-buffer", {
|
|
||||||
defer: true,
|
defer: true,
|
||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(inputJpgBuffer).resize(width, height).toBuffer(function(err, buffer) {
|
sharp(inputJpgBuffer).resize(width, height).toBuffer(function(err, buffer) {
|
||||||
@@ -131,10 +149,10 @@ async.series({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).add("sharp-file-file", {
|
}).add('sharp-file-file', {
|
||||||
defer: true,
|
defer: true,
|
||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(inputJpg).resize(width, height).toFile(outputJpg, function(err) {
|
sharp(fixtures.inputJpg).resize(width, height).toFile(fixtures.outputJpg, function(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
} else {
|
} else {
|
||||||
@@ -142,21 +160,21 @@ async.series({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).add("sharp-stream-stream", {
|
}).add('sharp-stream-stream', {
|
||||||
defer: true,
|
defer: true,
|
||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
var readable = fs.createReadStream(inputJpg);
|
var readable = fs.createReadStream(fixtures.inputJpg);
|
||||||
var writable = fs.createWriteStream(outputJpg);
|
var writable = fs.createWriteStream(fixtures.outputJpg);
|
||||||
writable.on('finish', function() {
|
writable.on('finish', function() {
|
||||||
deferred.resolve();
|
deferred.resolve();
|
||||||
});
|
});
|
||||||
var pipeline = sharp().resize(width, height);
|
var pipeline = sharp().resize(width, height);
|
||||||
readable.pipe(pipeline).pipe(writable);
|
readable.pipe(pipeline).pipe(writable);
|
||||||
}
|
}
|
||||||
}).add("sharp-file-buffer", {
|
}).add('sharp-file-buffer', {
|
||||||
defer: true,
|
defer: true,
|
||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(inputJpg).resize(width, height).toBuffer(function(err, buffer) {
|
sharp(fixtures.inputJpg).resize(width, height).toBuffer(function(err, buffer) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
} else {
|
} else {
|
||||||
@@ -165,18 +183,18 @@ async.series({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).add("sharp-file-buffer-promise", {
|
}).add('sharp-promise', {
|
||||||
defer: true,
|
defer: true,
|
||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(inputJpg).resize(width, height).toBuffer().then(function(buffer) {
|
sharp(inputJpgBuffer).resize(width, height).toBuffer().then(function(buffer) {
|
||||||
assert.notStrictEqual(null, buffer);
|
assert.notStrictEqual(null, buffer);
|
||||||
deferred.resolve();
|
deferred.resolve();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).add("sharp-file-buffer-sharpen", {
|
}).add('sharp-sharpen-mild', {
|
||||||
defer: true,
|
defer: true,
|
||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(inputJpg).resize(width, height).sharpen().toBuffer(function(err, buffer) {
|
sharp(inputJpgBuffer).resize(width, height).sharpen().toBuffer(function(err, buffer) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
} else {
|
} else {
|
||||||
@@ -185,10 +203,10 @@ async.series({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).add("sharp-file-buffer-nearest-neighbour", {
|
}).add('sharp-sharpen-radius', {
|
||||||
defer: true,
|
defer: true,
|
||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(inputJpg).resize(width, height).interpolateWith(sharp.interpolator.nearest).toBuffer(function(err, buffer) {
|
sharp(inputJpgBuffer).resize(width, height).sharpen(3, 1, 3).toBuffer(function(err, buffer) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
} else {
|
} else {
|
||||||
@@ -197,10 +215,10 @@ async.series({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).add("sharp-file-buffer-bicubic", {
|
}).add('sharp-blur-mild', {
|
||||||
defer: true,
|
defer: true,
|
||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(inputJpg).resize(width, height).interpolateWith(sharp.interpolator.bicubic).toBuffer(function(err, buffer) {
|
sharp(inputJpgBuffer).resize(width, height).blur().toBuffer(function(err, buffer) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
} else {
|
} else {
|
||||||
@@ -209,10 +227,10 @@ async.series({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).add("sharp-file-buffer-nohalo", {
|
}).add('sharp-blur-radius', {
|
||||||
defer: true,
|
defer: true,
|
||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(inputJpg).resize(width, height).interpolateWith(sharp.interpolator.nohalo).toBuffer(function(err, buffer) {
|
sharp(inputJpgBuffer).resize(width, height).blur(3).toBuffer(function(err, buffer) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
} else {
|
} else {
|
||||||
@@ -221,10 +239,10 @@ async.series({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).add("sharp-file-buffer-locallyBoundedBicubic", {
|
}).add('sharp-nearest-neighbour', {
|
||||||
defer: true,
|
defer: true,
|
||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(inputJpg).resize(width, height).interpolateWith(sharp.interpolator.locallyBoundedBicubic).toBuffer(function(err, buffer) {
|
sharp(inputJpgBuffer).resize(width, height).interpolateWith(sharp.interpolator.nearest).toBuffer(function(err, buffer) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
} else {
|
} else {
|
||||||
@@ -233,10 +251,10 @@ async.series({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).add("sharp-file-buffer-vertexSplitQuadraticBasisSpline", {
|
}).add('sharp-bicubic', {
|
||||||
defer: true,
|
defer: true,
|
||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(inputJpg).resize(width, height).interpolateWith(sharp.interpolator.vertexSplitQuadraticBasisSpline).toBuffer(function(err, buffer) {
|
sharp(inputJpgBuffer).resize(width, height).interpolateWith(sharp.interpolator.bicubic).toBuffer(function(err, buffer) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
} else {
|
} else {
|
||||||
@@ -245,10 +263,10 @@ async.series({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).add("sharp-file-buffer-gamma", {
|
}).add('sharp-nohalo', {
|
||||||
defer: true,
|
defer: true,
|
||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(inputJpg).resize(width, height).gamma().toBuffer(function(err, buffer) {
|
sharp(inputJpgBuffer).resize(width, height).interpolateWith(sharp.interpolator.nohalo).toBuffer(function(err, buffer) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
} else {
|
} else {
|
||||||
@@ -257,10 +275,10 @@ async.series({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).add("sharp-file-buffer-progressive", {
|
}).add('sharp-locallyBoundedBicubic', {
|
||||||
defer: true,
|
defer: true,
|
||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(inputJpg).resize(width, height).progressive().toBuffer(function(err, buffer) {
|
sharp(inputJpgBuffer).resize(width, height).interpolateWith(sharp.interpolator.locallyBoundedBicubic).toBuffer(function(err, buffer) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
} else {
|
} else {
|
||||||
@@ -269,10 +287,10 @@ async.series({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).add("sharp-file-buffer-rotate", {
|
}).add('sharp-vertexSplitQuadraticBasisSpline', {
|
||||||
defer: true,
|
defer: true,
|
||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(inputJpg).rotate(90).resize(width, height).toBuffer(function(err, buffer) {
|
sharp(inputJpgBuffer).resize(width, height).interpolateWith(sharp.interpolator.vertexSplitQuadraticBasisSpline).toBuffer(function(err, buffer) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
} else {
|
} else {
|
||||||
@@ -281,10 +299,10 @@ async.series({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).add("sharp-file-buffer-sequentialRead", {
|
}).add('sharp-gamma', {
|
||||||
defer: true,
|
defer: true,
|
||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(inputJpg).resize(width, height).sequentialRead().toBuffer(function(err, buffer) {
|
sharp(inputJpgBuffer).resize(width, height).gamma().toBuffer(function(err, buffer) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
} else {
|
} else {
|
||||||
@@ -293,22 +311,109 @@ async.series({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).on("cycle", function(event) {
|
}).add('sharp-normalise', {
|
||||||
console.log("jpeg " + String(event.target));
|
defer: true,
|
||||||
}).on("complete", function() {
|
fn: function(deferred) {
|
||||||
callback(null, this.filter("fastest").pluck("name"));
|
sharp(inputJpgBuffer).resize(width, height).normalise().toBuffer(function(err, buffer) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
assert.notStrictEqual(null, buffer);
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).add('sharp-greyscale', {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
sharp(inputJpgBuffer).resize(width, height).greyscale().toBuffer(function(err, buffer) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
assert.notStrictEqual(null, buffer);
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).add('sharp-greyscale-gamma', {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
sharp(inputJpgBuffer).resize(width, height).gamma().greyscale().toBuffer(function(err, buffer) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
assert.notStrictEqual(null, buffer);
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).add('sharp-progressive', {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
sharp(inputJpgBuffer).resize(width, height).progressive().toBuffer(function(err, buffer) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
assert.notStrictEqual(null, buffer);
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).add('sharp-without-chroma-subsampling', {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
sharp(inputJpgBuffer).resize(width, height).withoutChromaSubsampling().toBuffer(function(err, buffer) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
assert.notStrictEqual(null, buffer);
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).add('sharp-rotate', {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
sharp(inputJpgBuffer).rotate(90).resize(width, height).toBuffer(function(err, buffer) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
assert.notStrictEqual(null, buffer);
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).add('sharp-sequentialRead', {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
sharp(inputJpgBuffer).resize(width, height).sequentialRead().toBuffer(function(err, buffer) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
assert.notStrictEqual(null, buffer);
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).on('cycle', function(event) {
|
||||||
|
console.log('jpeg ' + String(event.target));
|
||||||
|
}).on('complete', function() {
|
||||||
|
callback(null, this.filter('fastest').pluck('name'));
|
||||||
}).run();
|
}).run();
|
||||||
},
|
},
|
||||||
png: function(callback) {
|
png: function(callback) {
|
||||||
var inputPngBuffer = fs.readFileSync(inputPng);
|
var inputPngBuffer = fs.readFileSync(fixtures.inputPng);
|
||||||
(new Benchmark.Suite("png")).add("imagemagick-file-file", {
|
var pngSuite = new Benchmark.Suite('png');
|
||||||
|
pngSuite.add('imagemagick-file-file', {
|
||||||
defer: true,
|
defer: true,
|
||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
imagemagick.resize({
|
imagemagick.resize({
|
||||||
srcPath: inputPng,
|
srcPath: fixtures.inputPng,
|
||||||
dstPath: outputPng,
|
dstPath: fixtures.outputPng,
|
||||||
width: width,
|
width: width,
|
||||||
height: height
|
height: height,
|
||||||
|
format: 'jpg',
|
||||||
|
filter: magickFilter
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
@@ -317,21 +422,51 @@ async.series({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).add("imagemagick-native-buffer-buffer", {
|
}).add('imagemagick-native-buffer-buffer', {
|
||||||
defer: true,
|
defer: true,
|
||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
imagemagickNative.convert({
|
imagemagickNative.convert({
|
||||||
srcData: inputPngBuffer,
|
srcData: inputPngBuffer,
|
||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
format: 'PNG'
|
format: 'PNG',
|
||||||
|
filter: magickFilter
|
||||||
});
|
});
|
||||||
deferred.resolve();
|
deferred.resolve();
|
||||||
}
|
}
|
||||||
}).add("gm-file-file", {
|
}).add('gm-file-file', {
|
||||||
defer: true,
|
defer: true,
|
||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
gm(inputPng).resize(width, height).write(outputPng, function (err) {
|
gm(fixtures.inputPng)
|
||||||
|
.resize(width, height)
|
||||||
|
.filter(magickFilter)
|
||||||
|
.write(fixtures.outputPng, function (err) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).add('gm-file-buffer', {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
gm(fixtures.inputPng)
|
||||||
|
.resize(width, height)
|
||||||
|
.filter(magickFilter)
|
||||||
|
.toBuffer(function (err, buffer) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
assert.notStrictEqual(null, buffer);
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).add('sharp-buffer-file', {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
sharp(inputPngBuffer).resize(width, height).toFile(fixtures.outputPng, function(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
} else {
|
} else {
|
||||||
@@ -339,30 +474,7 @@ async.series({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).add("gm-file-buffer", {
|
}).add('sharp-buffer-buffer', {
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
gm(inputPng).resize(width, height).quality(80).toBuffer(function (err, buffer) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
} else {
|
|
||||||
assert.notStrictEqual(null, buffer);
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).add("sharp-buffer-file", {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
sharp(inputPngBuffer).resize(width, height).toFile(outputPng, function(err) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
} else {
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).add("sharp-buffer-buffer", {
|
|
||||||
defer: true,
|
defer: true,
|
||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(inputPngBuffer).resize(width, height).toBuffer(function(err, buffer) {
|
sharp(inputPngBuffer).resize(width, height).toBuffer(function(err, buffer) {
|
||||||
@@ -374,10 +486,10 @@ async.series({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).add("sharp-file-file", {
|
}).add('sharp-file-file', {
|
||||||
defer: true,
|
defer: true,
|
||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(inputPng).resize(width, height).toFile(outputPng, function(err) {
|
sharp(fixtures.inputPng).resize(width, height).toFile(fixtures.outputPng, function(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
} else {
|
} else {
|
||||||
@@ -385,10 +497,10 @@ async.series({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).add("sharp-file-buffer", {
|
}).add('sharp-file-buffer', {
|
||||||
defer: true,
|
defer: true,
|
||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(inputPng).resize(width, height).toBuffer(function(err, buffer) {
|
sharp(fixtures.inputPng).resize(width, height).toBuffer(function(err, buffer) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
} else {
|
} else {
|
||||||
@@ -397,10 +509,10 @@ async.series({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).add("sharp-file-buffer-sharpen", {
|
}).add('sharp-progressive', {
|
||||||
defer: true,
|
defer: true,
|
||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(inputPng).resize(width, height).sharpen().toBuffer(function(err, buffer) {
|
sharp(inputPngBuffer).resize(width, height).progressive().toBuffer(function(err, buffer) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
} else {
|
} else {
|
||||||
@@ -409,30 +521,34 @@ async.series({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).add("sharp-file-buffer-progressive", {
|
});
|
||||||
defer: true,
|
if (semver.gte(sharp.libvipsVersion(), '7.41.0')) {
|
||||||
fn: function(deferred) {
|
pngSuite.add('sharp-withoutAdaptiveFiltering', {
|
||||||
sharp(inputPng).resize(width, height).progressive().toBuffer(function(err, buffer) {
|
defer: true,
|
||||||
if (err) {
|
fn: function(deferred) {
|
||||||
throw err;
|
sharp(inputPngBuffer).resize(width, height).withoutAdaptiveFiltering().toBuffer(function(err, buffer) {
|
||||||
} else {
|
if (err) {
|
||||||
assert.notStrictEqual(null, buffer);
|
throw err;
|
||||||
deferred.resolve();
|
} else {
|
||||||
}
|
assert.notStrictEqual(null, buffer);
|
||||||
});
|
deferred.resolve();
|
||||||
}
|
}
|
||||||
}).on("cycle", function(event) {
|
});
|
||||||
console.log(" png " + String(event.target));
|
}
|
||||||
}).on("complete", function() {
|
});
|
||||||
callback(null, this.filter("fastest").pluck("name"));
|
}
|
||||||
|
pngSuite.on('cycle', function(event) {
|
||||||
|
console.log(' png ' + String(event.target));
|
||||||
|
}).on('complete', function() {
|
||||||
|
callback(null, this.filter('fastest').pluck('name'));
|
||||||
}).run();
|
}).run();
|
||||||
},
|
},
|
||||||
webp: function(callback) {
|
webp: function(callback) {
|
||||||
var inputWebpBuffer = fs.readFileSync(inputWebp);
|
var inputWebPBuffer = fs.readFileSync(fixtures.inputWebP);
|
||||||
(new Benchmark.Suite("webp")).add("sharp-buffer-file", {
|
(new Benchmark.Suite('webp')).add('sharp-buffer-file', {
|
||||||
defer: true,
|
defer: true,
|
||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(inputWebpBuffer).resize(width, height).toFile(outputWebp, function(err) {
|
sharp(inputWebPBuffer).resize(width, height).toFile(fixtures.outputWebP, function(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
} else {
|
} else {
|
||||||
@@ -440,10 +556,10 @@ async.series({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).add("sharp-buffer-buffer", {
|
}).add('sharp-buffer-buffer', {
|
||||||
defer: true,
|
defer: true,
|
||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(inputWebpBuffer).resize(width, height).toBuffer(function(err, buffer) {
|
sharp(inputWebPBuffer).resize(width, height).toBuffer(function(err, buffer) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
} else {
|
} else {
|
||||||
@@ -452,10 +568,10 @@ async.series({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).add("sharp-file-file", {
|
}).add('sharp-file-file', {
|
||||||
defer: true,
|
defer: true,
|
||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(inputWebp).resize(width, height).toFile(outputWebp, function(err) {
|
sharp(fixtures.inputWebP).resize(width, height).toFile(fixtures.outputWebP, function(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
} else {
|
} else {
|
||||||
@@ -463,10 +579,10 @@ async.series({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).add("sharp-file-buffer", {
|
}).add('sharp-file-buffer', {
|
||||||
defer: true,
|
defer: true,
|
||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
sharp(inputWebp).resize(width, height).toBuffer(function(err, buffer) {
|
sharp(fixtures.inputWebp).resize(width, height).toBuffer(function(err, buffer) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
} else {
|
} else {
|
||||||
@@ -475,98 +591,17 @@ async.series({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).add("sharp-file-buffer-sharpen", {
|
}).on('cycle', function(event) {
|
||||||
defer: true,
|
console.log('webp ' + String(event.target));
|
||||||
fn: function(deferred) {
|
}).on('complete', function() {
|
||||||
sharp(inputWebp).resize(width, height).sharpen().toBuffer(function(err, buffer) {
|
callback(null, this.filter('fastest').pluck('name'));
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
} else {
|
|
||||||
assert.notStrictEqual(null, buffer);
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).on("cycle", function(event) {
|
|
||||||
console.log("webp " + String(event.target));
|
|
||||||
}).on("complete", function() {
|
|
||||||
callback(null, this.filter("fastest").pluck("name"));
|
|
||||||
}).run();
|
|
||||||
},
|
|
||||||
tiff: function(callback) {
|
|
||||||
(new Benchmark.Suite("tiff")).add("sharp-file-file", {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
sharp(inputTiff).resize(width, height).toFile(outputTiff, function(err) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
} else {
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).add("sharp-file-file-sharpen", {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
sharp(inputTiff).resize(width, height).sharpen().toFile(outputTiff, function(err) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
} else {
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).on("cycle", function(event) {
|
|
||||||
console.log("tiff " + String(event.target));
|
|
||||||
}).on("complete", function() {
|
|
||||||
callback(null, this.filter("fastest").pluck("name"));
|
|
||||||
}).run();
|
|
||||||
},
|
|
||||||
gif: function(callback) {
|
|
||||||
(new Benchmark.Suite("gif")).add("sharp-file-file", {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
sharp(inputGif).resize(width, height).toFile(outputTiff, function(err) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
} else {
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).add("sharp-file-file-sharpen", {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
sharp(inputGif).resize(width, height).sharpen().toFile(outputTiff, function(err) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
} else {
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).add("sharp-file-file-sequentialRead", {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
sharp(inputGif).sequentialRead().resize(width, height).toFile(outputTiff, function(err) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
} else {
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).on("cycle", function(event) {
|
|
||||||
console.log("gif " + String(event.target));
|
|
||||||
}).on("complete", function() {
|
|
||||||
callback(null, this.filter("fastest").pluck("name"));
|
|
||||||
}).run();
|
}).run();
|
||||||
}
|
}
|
||||||
}, function(err, results) {
|
}, function(err, results) {
|
||||||
assert(!err, err);
|
assert(!err, err);
|
||||||
Object.keys(results).forEach(function(format) {
|
Object.keys(results).forEach(function(format) {
|
||||||
if (results[format].toString().substr(0, 5) !== "sharp") {
|
if (results[format].toString().substr(0, 5) !== 'sharp') {
|
||||||
console.log("sharp was slower than " + results[format] + " for " + format);
|
console.log('sharp was slower than ' + results[format] + ' for ' + format);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
console.dir(sharp.cache());
|
console.dir(sharp.cache());
|
||||||
74
test/bench/random.js
Executable file
@@ -0,0 +1,74 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var imagemagick = require('imagemagick');
|
||||||
|
var gm = require('gm');
|
||||||
|
var assert = require('assert');
|
||||||
|
var Benchmark = require('benchmark');
|
||||||
|
|
||||||
|
var sharp = require('../../index');
|
||||||
|
var fixtures = require('../fixtures');
|
||||||
|
|
||||||
|
var min = 320;
|
||||||
|
var max = 960;
|
||||||
|
|
||||||
|
// Nearest equivalent to bilinear
|
||||||
|
var magickFilter = 'Triangle';
|
||||||
|
|
||||||
|
var randomDimension = function() {
|
||||||
|
return Math.ceil(Math.random() * (max - min) + min);
|
||||||
|
};
|
||||||
|
|
||||||
|
new Benchmark.Suite('random').add('imagemagick', {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
imagemagick.resize({
|
||||||
|
srcPath: fixtures.inputJpg,
|
||||||
|
dstPath: fixtures.outputJpg,
|
||||||
|
quality: 0.8,
|
||||||
|
width: randomDimension(),
|
||||||
|
height: randomDimension(),
|
||||||
|
format: 'jpg',
|
||||||
|
filter: magickFilter
|
||||||
|
}, function(err) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).add('gm', {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
gm(fixtures.inputJpg)
|
||||||
|
.resize(randomDimension(), randomDimension())
|
||||||
|
.filter(magickFilter)
|
||||||
|
.quality(80)
|
||||||
|
.toBuffer(function (err, buffer) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
assert.notStrictEqual(null, buffer);
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).add('sharp', {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
sharp(fixtures.inputJpg).resize(randomDimension(), randomDimension()).toBuffer(function(err, buffer) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
assert.notStrictEqual(null, buffer);
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).on('cycle', function(event) {
|
||||||
|
console.log(String(event.target));
|
||||||
|
}).on('complete', function() {
|
||||||
|
var winner = this.filter('fastest').pluck('name');
|
||||||
|
assert.strictEqual('sharp', String(winner), 'sharp was slower than ' + winner);
|
||||||
|
console.dir(sharp.cache());
|
||||||
|
}).run();
|
||||||
BIN
test/fixtures/2569067123_aca715a2ee_o.jpg
vendored
Normal file
|
After Width: | Height: | Size: 810 KiB |
BIN
test/fixtures/2x2_fdcce6.png
vendored
Normal file
|
After Width: | Height: | Size: 76 B |
0
tests/fixtures/4.webp → test/fixtures/4.webp
vendored
|
Before Width: | Height: | Size: 173 KiB After Width: | Height: | Size: 173 KiB |
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 84 KiB |
BIN
test/fixtures/CMU-1-Small-Region.svs
vendored
Normal file
BIN
test/fixtures/Channel_digital_image_CMYK_color.jpg
vendored
Normal file
|
After Width: | Height: | Size: 714 KiB |
BIN
test/fixtures/Channel_digital_image_CMYK_color_no_profile.jpg
vendored
Normal file
|
After Width: | Height: | Size: 135 KiB |
|
Before Width: | Height: | Size: 278 KiB After Width: | Height: | Size: 278 KiB |
BIN
test/fixtures/Landscape_5.jpg
vendored
Normal file
|
After Width: | Height: | Size: 134 KiB |
|
Before Width: | Height: | Size: 138 KiB After Width: | Height: | Size: 138 KiB |
17
test/fixtures/Wikimedia-logo.svg
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" standalone="yes"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"
|
||||||
|
id="Wikimedia logo"
|
||||||
|
viewBox="-599 -599 1198 1198" width="1024" height="1024">
|
||||||
|
<defs>
|
||||||
|
<clipPath id="mask">
|
||||||
|
<path d="M 47.5,-87.5 v 425 h -95 v -425 l -552,-552 v 1250 h 1199 v -1250 z" />
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
<g clip-path="url(#mask)">
|
||||||
|
<circle id="green parts" fill="#396" r="336.5"/>
|
||||||
|
<circle id="blue arc" fill="none" stroke="#069" r="480.25" stroke-width="135.5" />
|
||||||
|
</g>
|
||||||
|
<circle fill="#900" cy="-379.5" r="184.5" id="red circle"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 692 B |
BIN
test/fixtures/blackbug.png
vendored
Normal file
|
After Width: | Height: | Size: 105 KiB |
BIN
test/fixtures/corrupt-header.jpg
vendored
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
test/fixtures/free-gearhead-pack.psd
vendored
Normal file
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 83 KiB |
BIN
test/fixtures/grey-8bit-alpha.png
vendored
Normal file
|
After Width: | Height: | Size: 23 KiB |
40
test/fixtures/index.js
vendored
Executable file
@@ -0,0 +1,40 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var path = require('path');
|
||||||
|
|
||||||
|
var getPath = function(filename) {
|
||||||
|
return path.join(__dirname, filename);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
|
||||||
|
inputJpg: getPath('2569067123_aca715a2ee_o.jpg'), // http://www.flickr.com/photos/grizdave/2569067123/
|
||||||
|
inputJpgWithExif: getPath('Landscape_8.jpg'), // https://github.com/recurser/exif-orientation-examples/blob/master/Landscape_8.jpg
|
||||||
|
inputJpgWithExifMirroring: getPath('Landscape_5.jpg'), // https://github.com/recurser/exif-orientation-examples/blob/master/Landscape_5.jpg
|
||||||
|
inputJpgWithGammaHoliness: getPath('gamma_dalai_lama_gray.jpg'), // http://www.4p8.com/eric.brasseur/gamma.html
|
||||||
|
inputJpgWithCmykProfile: getPath('Channel_digital_image_CMYK_color.jpg'), // http://en.wikipedia.org/wiki/File:Channel_digital_image_CMYK_color.jpg
|
||||||
|
inputJpgWithCmykNoProfile: getPath('Channel_digital_image_CMYK_color_no_profile.jpg'),
|
||||||
|
inputJpgWithCorruptHeader: getPath('corrupt-header.jpg'),
|
||||||
|
inputJpgWithLowContrast: getPath('low-contrast.jpg'), // http://www.flickr.com/photos/grizdave/2569067123/
|
||||||
|
|
||||||
|
inputPng: getPath('50020484-00001.png'), // http://c.searspartsdirect.com/lis_png/PLDM/50020484-00001.png
|
||||||
|
inputPngWithTransparency: getPath('blackbug.png'), // public domain
|
||||||
|
inputPngWithGreyAlpha: getPath('grey-8bit-alpha.png'),
|
||||||
|
inputPngWithOneColor: getPath('2x2_fdcce6.png'),
|
||||||
|
|
||||||
|
inputWebP: getPath('4.webp'), // http://www.gstatic.com/webp/gallery/4.webp
|
||||||
|
inputTiff: getPath('G31D.TIF'), // http://www.fileformat.info/format/tiff/sample/e6c9a6e5253348f4aef6d17b534360ab/index.htm
|
||||||
|
inputGif: getPath('Crash_test.gif'), // http://upload.wikimedia.org/wikipedia/commons/e/e3/Crash_test.gif
|
||||||
|
inputSvg: getPath('Wikimedia-logo.svg'), // http://commons.wikimedia.org/wiki/File:Wikimedia-logo.svg
|
||||||
|
inputPsd: getPath('free-gearhead-pack.psd'), // https://dribbble.com/shots/1624241-Free-Gearhead-Vector-Pack
|
||||||
|
|
||||||
|
inputSvs: getPath('CMU-1-Small-Region.svs'), // http://openslide.cs.cmu.edu/download/openslide-testdata/Aperio/CMU-1-Small-Region.svs
|
||||||
|
|
||||||
|
outputJpg: getPath('output.jpg'),
|
||||||
|
outputPng: getPath('output.png'),
|
||||||
|
outputWebP: getPath('output.webp'),
|
||||||
|
outputZoinks: getPath('output.zoinks'), // an 'unknown' file extension
|
||||||
|
|
||||||
|
path: getPath // allows tests to write files to fixtures directory (for testing with human eyes)
|
||||||
|
|
||||||
|
};
|
||||||
BIN
test/fixtures/low-contrast.jpg
vendored
Normal file
|
After Width: | Height: | Size: 27 KiB |
@@ -4,4 +4,5 @@ if ! type valgrind >/dev/null; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
curl -O https://raw.githubusercontent.com/jcupitt/libvips/master/libvips.supp
|
curl -O https://raw.githubusercontent.com/jcupitt/libvips/master/libvips.supp
|
||||||
G_SLICE=always-malloc G_DEBUG=gc-friendly valgrind --suppressions=libvips.supp --suppressions=sharp.supp --leak-check=full --show-leak-kinds=definite,indirect,possible node unit.js
|
cd ../../
|
||||||
|
G_SLICE=always-malloc G_DEBUG=gc-friendly valgrind --suppressions=test/leak/libvips.supp --suppressions=test/leak/sharp.supp --leak-check=full --show-leak-kinds=definite,indirect,possible --num-callers=20 --trace-children=yes npm test
|
||||||
0
tests/sharp.supp → test/leak/sharp.supp
Normal file → Executable file
64
test/unit/alpha.js
Executable file
@@ -0,0 +1,64 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var assert = require('assert');
|
||||||
|
|
||||||
|
var sharp = require('../../index');
|
||||||
|
var fixtures = require('../fixtures');
|
||||||
|
|
||||||
|
sharp.cache(0);
|
||||||
|
|
||||||
|
describe('Alpha transparency', function() {
|
||||||
|
|
||||||
|
it('Flatten to black', function(done) {
|
||||||
|
sharp(fixtures.inputPngWithTransparency)
|
||||||
|
.flatten()
|
||||||
|
.resize(400, 300)
|
||||||
|
.toFile(fixtures.path('output.flatten-black.jpg'), done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Flatten to RGB orange', function(done) {
|
||||||
|
sharp(fixtures.inputPngWithTransparency)
|
||||||
|
.flatten()
|
||||||
|
.background({r: 255, g: 102, b: 0})
|
||||||
|
.resize(400, 300)
|
||||||
|
.toFile(fixtures.path('output.flatten-rgb-orange.jpg'), done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Flatten to CSS/hex orange', function(done) {
|
||||||
|
sharp(fixtures.inputPngWithTransparency)
|
||||||
|
.flatten()
|
||||||
|
.background('#ff6600')
|
||||||
|
.resize(400, 300)
|
||||||
|
.toFile(fixtures.path('output.flatten-hex-orange.jpg'), done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Do not flatten', function(done) {
|
||||||
|
sharp(fixtures.inputPngWithTransparency)
|
||||||
|
.flatten(false)
|
||||||
|
.toBuffer(function(err, data) {
|
||||||
|
if (err) throw err;
|
||||||
|
sharp(data).metadata(function(err, metadata) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('png', metadata.format);
|
||||||
|
assert.strictEqual(4, metadata.channels);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Ignored for JPEG', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.background('#ff0000')
|
||||||
|
.flatten()
|
||||||
|
.toBuffer(function(err, data) {
|
||||||
|
if (err) throw err;
|
||||||
|
sharp(data).metadata(function(err, metadata) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('jpeg', metadata.format);
|
||||||
|
assert.strictEqual(3, metadata.channels);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
100
test/unit/blur.js
Executable file
@@ -0,0 +1,100 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var assert = require('assert');
|
||||||
|
|
||||||
|
var sharp = require('../../index');
|
||||||
|
var fixtures = require('../fixtures');
|
||||||
|
|
||||||
|
sharp.cache(0);
|
||||||
|
|
||||||
|
describe('Blur', function() {
|
||||||
|
|
||||||
|
it('specific radius 1', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.blur(1)
|
||||||
|
.toFile(fixtures.path('output.blur-1.jpg'), function(err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(240, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('specific radius 10', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.blur(10)
|
||||||
|
.toFile(fixtures.path('output.blur-10.jpg'), function(err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(240, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('specific radius 0.3', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.blur(0.3)
|
||||||
|
.toFile(fixtures.path('output.blur-0.3.jpg'), function(err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(240, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('mild blur', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.blur()
|
||||||
|
.toFile(fixtures.path('output.blur-mild.jpg'), function(err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(240, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('invalid radius', function(done) {
|
||||||
|
var isValid = true;
|
||||||
|
try {
|
||||||
|
sharp(fixtures.inputJpg).blur(0.1);
|
||||||
|
} catch (err) {
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
assert.strictEqual(false, isValid);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('blurred image is smaller than non-blurred', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.blur(false)
|
||||||
|
.toBuffer(function(err, notBlurred, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, notBlurred.length > 0);
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(240, info.height);
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.blur(true)
|
||||||
|
.toBuffer(function(err, blurred, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, blurred.length > 0);
|
||||||
|
assert.strictEqual(true, blurred.length < notBlurred.length);
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(240, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
85
test/unit/colourspace.js
Executable file
@@ -0,0 +1,85 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var assert = require('assert');
|
||||||
|
|
||||||
|
var sharp = require('../../index');
|
||||||
|
var fixtures = require('../fixtures');
|
||||||
|
|
||||||
|
sharp.cache(0);
|
||||||
|
|
||||||
|
describe('Colour space conversion', function() {
|
||||||
|
|
||||||
|
it('To greyscale', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.greyscale()
|
||||||
|
.toFile(fixtures.path('output.greyscale-gamma-0.0.jpg'), done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('To greyscale with gamma correction', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.gamma()
|
||||||
|
.greyscale()
|
||||||
|
.toFile(fixtures.path('output.greyscale-gamma-2.2.jpg'), done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Not to greyscale', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.greyscale(false)
|
||||||
|
.toFile(fixtures.path('output.greyscale-not.jpg'), done);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (sharp.format.webp.output.buffer) {
|
||||||
|
it('From 1-bit TIFF to sRGB WebP [slow]', function(done) {
|
||||||
|
sharp(fixtures.inputTiff)
|
||||||
|
.webp()
|
||||||
|
.toBuffer(function(err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, data.length > 0);
|
||||||
|
assert.strictEqual('webp', info.format);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
it('From CMYK to sRGB', function(done) {
|
||||||
|
sharp(fixtures.inputJpgWithCmykProfile)
|
||||||
|
.resize(320)
|
||||||
|
.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);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('From CMYK to sRGB with white background, not yellow', function(done) {
|
||||||
|
sharp(fixtures.inputJpgWithCmykProfile)
|
||||||
|
.resize(320, 240)
|
||||||
|
.background('white')
|
||||||
|
.embed()
|
||||||
|
.toFile(fixtures.path('output.cmyk2srgb.jpg'), function(err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(240, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('From profile-less CMYK to sRGB', function(done) {
|
||||||
|
sharp(fixtures.inputJpgWithCmykNoProfile)
|
||||||
|
.resize(320)
|
||||||
|
.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);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
49
test/unit/cpplint.js
Executable file
@@ -0,0 +1,49 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var fs = require('fs');
|
||||||
|
var path = require('path');
|
||||||
|
var assert = require('assert');
|
||||||
|
|
||||||
|
var cpplint = require('node-cpplint/lib/');
|
||||||
|
|
||||||
|
describe('cpplint', function() {
|
||||||
|
|
||||||
|
// Ignore cpplint failures, possibly newline-related, on Windows
|
||||||
|
if (process.platform !== 'win32') {
|
||||||
|
// List C++ source files
|
||||||
|
fs.readdirSync(path.join(__dirname, '..', '..', 'src')).forEach(function (source) {
|
||||||
|
var file = path.join('src', source);
|
||||||
|
it(file, function(done) {
|
||||||
|
// Lint each source file
|
||||||
|
cpplint({
|
||||||
|
files: [file],
|
||||||
|
linelength: 140,
|
||||||
|
filters: {
|
||||||
|
legal: {
|
||||||
|
copyright: false
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
include: false,
|
||||||
|
include_order: false
|
||||||
|
},
|
||||||
|
whitespace: {
|
||||||
|
blank_line: false,
|
||||||
|
comments: false,
|
||||||
|
parens: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, function(err, report) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
var expected = {};
|
||||||
|
expected[file] = [];
|
||||||
|
assert.deepEqual(expected, report);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
95
test/unit/crop.js
Executable file
@@ -0,0 +1,95 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var assert = require('assert');
|
||||||
|
|
||||||
|
var sharp = require('../../index');
|
||||||
|
var fixtures = require('../fixtures');
|
||||||
|
|
||||||
|
sharp.cache(0);
|
||||||
|
|
||||||
|
describe('Crop gravities', function() {
|
||||||
|
|
||||||
|
it('North', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 80)
|
||||||
|
.crop(sharp.gravity.north)
|
||||||
|
.toFile(fixtures.path('output.gravity-north.jpg'), function(err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(80, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('East', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(80, 320)
|
||||||
|
.crop(sharp.gravity.east)
|
||||||
|
.toFile(fixtures.path('output.gravity-east.jpg'), function(err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(80, info.width);
|
||||||
|
assert.strictEqual(320, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('South', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 80)
|
||||||
|
.crop(sharp.gravity.south)
|
||||||
|
.toFile(fixtures.path('output.gravity-south.jpg'), function(err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(80, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('West', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(80, 320)
|
||||||
|
.crop(sharp.gravity.west)
|
||||||
|
.toFile(fixtures.path('output.gravity-west.jpg'), function(err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(80, info.width);
|
||||||
|
assert.strictEqual(320, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Center', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 80)
|
||||||
|
.crop(sharp.gravity.center)
|
||||||
|
.toFile(fixtures.path('output.gravity-center.jpg'), function(err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(80, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Centre', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(80, 320)
|
||||||
|
.crop(sharp.gravity.centre)
|
||||||
|
.toFile(fixtures.path('output.gravity-centre.jpg'), function(err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(80, info.width);
|
||||||
|
assert.strictEqual(320, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Invalid', function(done) {
|
||||||
|
var isValid = true;
|
||||||
|
try {
|
||||||
|
sharp(fixtures.inputJpg).crop(5);
|
||||||
|
} catch (err) {
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
assert.strictEqual(false, isValid);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
71
test/unit/embed.js
Executable file
@@ -0,0 +1,71 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var assert = require('assert');
|
||||||
|
|
||||||
|
var sharp = require('../../index');
|
||||||
|
var fixtures = require('../fixtures');
|
||||||
|
|
||||||
|
sharp.cache(0);
|
||||||
|
|
||||||
|
describe('Embed', function() {
|
||||||
|
|
||||||
|
it('JPEG within PNG, no alpha channel', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.embed()
|
||||||
|
.resize(320, 240)
|
||||||
|
.png()
|
||||||
|
.toBuffer(function(err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, data.length > 0);
|
||||||
|
assert.strictEqual('png', info.format);
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(240, info.height);
|
||||||
|
sharp(data).metadata(function(err, metadata) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(3, metadata.channels);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (sharp.format.webp.output.buffer) {
|
||||||
|
it('JPEG within WebP, to include alpha channel', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.background({r: 0, g: 0, b: 0, a: 0})
|
||||||
|
.embed()
|
||||||
|
.webp()
|
||||||
|
.toBuffer(function(err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, data.length > 0);
|
||||||
|
assert.strictEqual('webp', info.format);
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(240, info.height);
|
||||||
|
sharp(data).metadata(function(err, metadata) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(4, metadata.channels);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
it('PNG with alpha channel', function(done) {
|
||||||
|
sharp(fixtures.inputPngWithTransparency)
|
||||||
|
.resize(50, 50)
|
||||||
|
.embed()
|
||||||
|
.toBuffer(function(err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, data.length > 0);
|
||||||
|
assert.strictEqual('png', info.format);
|
||||||
|
assert.strictEqual(50, info.width);
|
||||||
|
assert.strictEqual(50, info.height);
|
||||||
|
sharp(data).metadata(function(err, metadata) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(4, metadata.channels);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
179
test/unit/extract.js
Executable file
@@ -0,0 +1,179 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var assert = require('assert');
|
||||||
|
|
||||||
|
var sharp = require('../../index');
|
||||||
|
var fixtures = require('../fixtures');
|
||||||
|
|
||||||
|
sharp.cache(0);
|
||||||
|
|
||||||
|
describe('Partial image extraction', function() {
|
||||||
|
|
||||||
|
it('JPEG', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.extract(2, 2, 20, 20)
|
||||||
|
.toFile(fixtures.path('output.extract.jpg'), function(err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(20, info.width);
|
||||||
|
assert.strictEqual(20, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('PNG', function(done) {
|
||||||
|
sharp(fixtures.inputPng)
|
||||||
|
.extract(300, 200, 400, 200)
|
||||||
|
.toFile(fixtures.path('output.extract.png'), function(err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(400, info.width);
|
||||||
|
assert.strictEqual(200, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (sharp.format.webp.output.file) {
|
||||||
|
it('WebP', function(done) {
|
||||||
|
sharp(fixtures.inputWebP)
|
||||||
|
.extract(50, 100, 125, 200)
|
||||||
|
.toFile(fixtures.path('output.extract.webp'), function(err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(125, info.width);
|
||||||
|
assert.strictEqual(200, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
it('TIFF', function(done) {
|
||||||
|
sharp(fixtures.inputTiff)
|
||||||
|
.extract(63, 34, 341, 529)
|
||||||
|
.toFile(fixtures.path('output.extract.tiff'), function(err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(341, info.width);
|
||||||
|
assert.strictEqual(529, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Before resize', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.extract(10, 10, 10, 500, 500)
|
||||||
|
.resize(100, 100)
|
||||||
|
.toFile(fixtures.path('output.extract.resize.jpg'), function(err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(100, info.width);
|
||||||
|
assert.strictEqual(100, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('After resize and crop', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(500, 500)
|
||||||
|
.crop(sharp.gravity.north)
|
||||||
|
.extract(10, 10, 100, 100)
|
||||||
|
.toFile(fixtures.path('output.resize.crop.extract.jpg'), function(err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(100, info.width);
|
||||||
|
assert.strictEqual(100, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Before and after resize and crop', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.extract(0, 0, 700, 700)
|
||||||
|
.resize(500, 500)
|
||||||
|
.crop(sharp.gravity.north)
|
||||||
|
.extract(10, 10, 100, 100)
|
||||||
|
.toFile(fixtures.path('output.extract.resize.crop.extract.jpg'), function(err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(100, info.width);
|
||||||
|
assert.strictEqual(100, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Extract then rotate', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.extract(10, 10, 100, 100)
|
||||||
|
.rotate(90)
|
||||||
|
.toFile(fixtures.path('output.extract.extract-then-rotate.jpg'), function(err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(100, info.width);
|
||||||
|
assert.strictEqual(100, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Rotate then extract', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.rotate(90)
|
||||||
|
.extract(10, 10, 100, 100)
|
||||||
|
.toFile(fixtures.path('output.extract.rotate-then-extract.jpg'), function(err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(100, info.width);
|
||||||
|
assert.strictEqual(100, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Invalid parameters', function() {
|
||||||
|
|
||||||
|
it('Undefined', function(done) {
|
||||||
|
var isValid = true;
|
||||||
|
try {
|
||||||
|
sharp(fixtures.inputJpg).extract();
|
||||||
|
} catch (err) {
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
assert.strictEqual(false, isValid);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('String top', function(done) {
|
||||||
|
var isValid = true;
|
||||||
|
try {
|
||||||
|
sharp(fixtures.inputJpg).extract('spoons', 10, 10, 10);
|
||||||
|
} catch (err) {
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
assert.strictEqual(false, isValid);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Non-integral left', function(done) {
|
||||||
|
var isValid = true;
|
||||||
|
try {
|
||||||
|
sharp(fixtures.inputJpg).extract(10, 10.2, 10, 10);
|
||||||
|
} catch (err) {
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
assert.strictEqual(false, isValid);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Negative width - negative', function(done) {
|
||||||
|
var isValid = true;
|
||||||
|
try {
|
||||||
|
sharp(fixtures.inputJpg).extract(10, 10, -10, 10);
|
||||||
|
} catch (err) {
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
assert.strictEqual(false, isValid);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Null height', function(done) {
|
||||||
|
var isValid = true;
|
||||||
|
try {
|
||||||
|
sharp(fixtures.inputJpg).extract(10, 10, 10, null);
|
||||||
|
} catch (err) {
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
assert.strictEqual(false, isValid);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
43
test/unit/gamma.js
Executable file
@@ -0,0 +1,43 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var assert = require('assert');
|
||||||
|
|
||||||
|
var sharp = require('../../index');
|
||||||
|
var fixtures = require('../fixtures');
|
||||||
|
|
||||||
|
sharp.cache(0);
|
||||||
|
|
||||||
|
describe('Gamma correction', function() {
|
||||||
|
|
||||||
|
it('value of 0.0 (disabled)', function(done) {
|
||||||
|
sharp(fixtures.inputJpgWithGammaHoliness)
|
||||||
|
.resize(129, 111)
|
||||||
|
.toFile(fixtures.path('output.gamma-0.0.jpg'), done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('value of 2.2 (default)', function(done) {
|
||||||
|
sharp(fixtures.inputJpgWithGammaHoliness)
|
||||||
|
.resize(129, 111)
|
||||||
|
.gamma()
|
||||||
|
.toFile(fixtures.path('output.gamma-2.2.jpg'), done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('value of 3.0', function(done) {
|
||||||
|
sharp(fixtures.inputJpgWithGammaHoliness)
|
||||||
|
.resize(129, 111)
|
||||||
|
.gamma(3)
|
||||||
|
.toFile(fixtures.path('output.gamma-3.0.jpg'), done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('invalid value', function(done) {
|
||||||
|
var isValid = true;
|
||||||
|
try {
|
||||||
|
sharp(fixtures.inputJpgWithGammaHoliness).gamma(4);
|
||||||
|
} catch (err) {
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
assert.strictEqual(false, isValid);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
106
test/unit/interpolation.js
Executable file
@@ -0,0 +1,106 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var assert = require('assert');
|
||||||
|
|
||||||
|
var sharp = require('../../index');
|
||||||
|
var fixtures = require('../fixtures');
|
||||||
|
|
||||||
|
sharp.cache(0);
|
||||||
|
|
||||||
|
describe('Interpolation', function() {
|
||||||
|
|
||||||
|
it('nearest neighbour', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.interpolateWith(sharp.interpolator.nearest)
|
||||||
|
.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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('bilinear', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.interpolateWith(sharp.interpolator.bilinear)
|
||||||
|
.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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('bicubic', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.interpolateWith(sharp.interpolator.bicubic)
|
||||||
|
.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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('nohalo', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.interpolateWith(sharp.interpolator.nohalo)
|
||||||
|
.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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('locally bounded bicubic (LBB)', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.interpolateWith(sharp.interpolator.locallyBoundedBicubic)
|
||||||
|
.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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('vertex split quadratic basis spline (VSQBS)', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.interpolateWith(sharp.interpolator.vertexSplitQuadraticBasisSpline)
|
||||||
|
.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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('unknown interpolator throws', function(done) {
|
||||||
|
var isValid = false;
|
||||||
|
try {
|
||||||
|
sharp().interpolateWith('nonexistant');
|
||||||
|
isValid = true;
|
||||||
|
} catch (e) {}
|
||||||
|
assert(!isValid);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
815
test/unit/io.js
Executable file
@@ -0,0 +1,815 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var fs = require('fs');
|
||||||
|
var assert = require('assert');
|
||||||
|
|
||||||
|
var semver = require('semver');
|
||||||
|
|
||||||
|
var sharp = require('../../index');
|
||||||
|
var fixtures = require('../fixtures');
|
||||||
|
|
||||||
|
sharp.cache(0);
|
||||||
|
|
||||||
|
describe('Input/output', function() {
|
||||||
|
|
||||||
|
it('Read from File and write to Stream', function(done) {
|
||||||
|
var writable = fs.createWriteStream(fixtures.outputJpg);
|
||||||
|
writable.on('finish', function() {
|
||||||
|
sharp(fixtures.outputJpg).toBuffer(function(err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, data.length > 0);
|
||||||
|
assert.strictEqual(data.length, info.size);
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(240, info.height);
|
||||||
|
fs.unlinkSync(fixtures.outputJpg);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
sharp(fixtures.inputJpg).resize(320, 240).pipe(writable);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Read from Buffer and write to Stream', function(done) {
|
||||||
|
var inputJpgBuffer = fs.readFileSync(fixtures.inputJpg);
|
||||||
|
var writable = fs.createWriteStream(fixtures.outputJpg);
|
||||||
|
writable.on('finish', function() {
|
||||||
|
sharp(fixtures.outputJpg).toBuffer(function(err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, data.length > 0);
|
||||||
|
assert.strictEqual(data.length, info.size);
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(240, info.height);
|
||||||
|
fs.unlinkSync(fixtures.outputJpg);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
sharp(inputJpgBuffer).resize(320, 240).pipe(writable);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Read from Stream and write to File', function(done) {
|
||||||
|
var readable = fs.createReadStream(fixtures.inputJpg);
|
||||||
|
var pipeline = sharp().resize(320, 240).toFile(fixtures.outputJpg, function(err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, info.size > 0);
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(240, info.height);
|
||||||
|
fs.unlinkSync(fixtures.outputJpg);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
readable.pipe(pipeline);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Read from Stream and write to Buffer', function(done) {
|
||||||
|
var readable = fs.createReadStream(fixtures.inputJpg);
|
||||||
|
var pipeline = sharp().resize(320, 240).toBuffer(function(err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, data.length > 0);
|
||||||
|
assert.strictEqual(data.length, info.size);
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(240, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
readable.pipe(pipeline);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Read from Stream and write to Buffer via Promise', function(done) {
|
||||||
|
var readable = fs.createReadStream(fixtures.inputJpg);
|
||||||
|
var pipeline = sharp().resize(1, 1);
|
||||||
|
pipeline.toBuffer().then(function(data) {
|
||||||
|
assert.strictEqual(true, data.length > 0);
|
||||||
|
done();
|
||||||
|
}).catch(function(err) {
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
readable.pipe(pipeline);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Read from Stream and write to Stream', function(done) {
|
||||||
|
var readable = fs.createReadStream(fixtures.inputJpg);
|
||||||
|
var writable = fs.createWriteStream(fixtures.outputJpg);
|
||||||
|
writable.on('finish', function() {
|
||||||
|
sharp(fixtures.outputJpg).toBuffer(function(err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, data.length > 0);
|
||||||
|
assert.strictEqual(data.length, info.size);
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(240, info.height);
|
||||||
|
fs.unlinkSync(fixtures.outputJpg);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
var pipeline = sharp().resize(320, 240);
|
||||||
|
readable.pipe(pipeline).pipe(writable);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Handle Stream to Stream error ', function(done) {
|
||||||
|
var pipeline = sharp().resize(320, 240);
|
||||||
|
var anErrorWasEmitted = false;
|
||||||
|
pipeline.on('error', function(err) {
|
||||||
|
anErrorWasEmitted = !!err;
|
||||||
|
}).on('end', function() {
|
||||||
|
assert(anErrorWasEmitted);
|
||||||
|
fs.unlinkSync(fixtures.outputJpg);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
var readableButNotAnImage = fs.createReadStream(__filename);
|
||||||
|
var writable = fs.createWriteStream(fixtures.outputJpg);
|
||||||
|
readableButNotAnImage.pipe(pipeline).pipe(writable);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Handle File to Stream error', function(done) {
|
||||||
|
var readableButNotAnImage = sharp(__filename).resize(320, 240);
|
||||||
|
var anErrorWasEmitted = false;
|
||||||
|
readableButNotAnImage.on('error', function(err) {
|
||||||
|
anErrorWasEmitted = !!err;
|
||||||
|
}).on('end', function() {
|
||||||
|
assert(anErrorWasEmitted);
|
||||||
|
fs.unlinkSync(fixtures.outputJpg);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
var writable = fs.createWriteStream(fixtures.outputJpg);
|
||||||
|
readableButNotAnImage.pipe(writable);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Sequential read, force JPEG', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.sequentialRead()
|
||||||
|
.resize(320, 240)
|
||||||
|
.toFormat(sharp.format.jpeg)
|
||||||
|
.toBuffer(function(err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, data.length > 0);
|
||||||
|
assert.strictEqual(data.length, info.size);
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(240, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Not sequential read, force JPEG', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.sequentialRead(false)
|
||||||
|
.resize(320, 240)
|
||||||
|
.toFormat('jpeg')
|
||||||
|
.toBuffer(function(err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, data.length > 0);
|
||||||
|
assert.strictEqual(data.length, info.size);
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(240, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Fail when output File is input File', function(done) {
|
||||||
|
sharp(fixtures.inputJpg).toFile(fixtures.inputJpg, function(err) {
|
||||||
|
assert(!!err);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Fail when output File is input File via Promise', function(done) {
|
||||||
|
sharp(fixtures.inputJpg).toFile(fixtures.inputJpg).then(function(data) {
|
||||||
|
assert(false);
|
||||||
|
done();
|
||||||
|
}).catch(function(err) {
|
||||||
|
assert(!!err);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Fail when output File is empty', function(done) {
|
||||||
|
sharp(fixtures.inputJpg).toFile('', function(err) {
|
||||||
|
assert(!!err);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Fail when output File is empty via Promise', function(done) {
|
||||||
|
sharp(fixtures.inputJpg).toFile('').then(function(data) {
|
||||||
|
assert(false);
|
||||||
|
done();
|
||||||
|
}).catch(function(err) {
|
||||||
|
assert(!!err);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Fail when input is empty Buffer', function(done) {
|
||||||
|
sharp(new Buffer(0)).toBuffer().then(function () {
|
||||||
|
assert(false);
|
||||||
|
done();
|
||||||
|
}).catch(function (err) {
|
||||||
|
assert(err instanceof Error);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Fail when input is invalid Buffer', function(done) {
|
||||||
|
sharp(new Buffer([0x1, 0x2, 0x3, 0x4])).toBuffer().then(function () {
|
||||||
|
assert(false);
|
||||||
|
done();
|
||||||
|
}).catch(function (err) {
|
||||||
|
assert(err instanceof Error);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Promises/A+', function(done) {
|
||||||
|
sharp(fixtures.inputJpg).resize(320, 240).toBuffer().then(function(data) {
|
||||||
|
sharp(data).toBuffer(function(err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, data.length > 0);
|
||||||
|
assert.strictEqual(data.length, info.size);
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(240, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
}).catch(function(err) {
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('JPEG quality', function(done) {
|
||||||
|
sharp(fixtures.inputJpg).resize(320, 240).quality(70).toBuffer(function(err, buffer70) {
|
||||||
|
if (err) throw err;
|
||||||
|
sharp(fixtures.inputJpg).resize(320, 240).toBuffer(function(err, buffer80) {
|
||||||
|
if (err) throw err;
|
||||||
|
sharp(fixtures.inputJpg).resize(320, 240).quality(90).toBuffer(function(err, buffer90) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert(buffer70.length < buffer80.length);
|
||||||
|
assert(buffer80.length < buffer90.length);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Invalid quality', function(done) {
|
||||||
|
var isValid = true;
|
||||||
|
try {
|
||||||
|
sharp(fixtures.inputJpg).quality(-1);
|
||||||
|
} catch (err) {
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
assert.strictEqual(false, isValid);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Progressive JPEG image', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.progressive(false)
|
||||||
|
.toBuffer(function(err, nonProgressiveData, nonProgressiveInfo) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, nonProgressiveData.length > 0);
|
||||||
|
assert.strictEqual(nonProgressiveData.length, nonProgressiveInfo.size);
|
||||||
|
assert.strictEqual('jpeg', nonProgressiveInfo.format);
|
||||||
|
assert.strictEqual(320, nonProgressiveInfo.width);
|
||||||
|
assert.strictEqual(240, nonProgressiveInfo.height);
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.progressive()
|
||||||
|
.toBuffer(function(err, progressiveData, progressiveInfo) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, progressiveData.length > 0);
|
||||||
|
assert.strictEqual(progressiveData.length, progressiveInfo.size);
|
||||||
|
assert.strictEqual(false, progressiveData.length === nonProgressiveData.length);
|
||||||
|
assert.strictEqual('jpeg', progressiveInfo.format);
|
||||||
|
assert.strictEqual(320, progressiveInfo.width);
|
||||||
|
assert.strictEqual(240, progressiveInfo.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Progressive PNG image', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.png()
|
||||||
|
.progressive(false)
|
||||||
|
.toBuffer(function(err, nonProgressiveData, nonProgressiveInfo) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, nonProgressiveData.length > 0);
|
||||||
|
assert.strictEqual(nonProgressiveData.length, nonProgressiveInfo.size);
|
||||||
|
assert.strictEqual('png', nonProgressiveInfo.format);
|
||||||
|
assert.strictEqual(320, nonProgressiveInfo.width);
|
||||||
|
assert.strictEqual(240, nonProgressiveInfo.height);
|
||||||
|
sharp(nonProgressiveData)
|
||||||
|
.progressive()
|
||||||
|
.toBuffer(function(err, progressiveData, progressiveInfo) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, progressiveData.length > 0);
|
||||||
|
assert.strictEqual(progressiveData.length, progressiveInfo.size);
|
||||||
|
assert.strictEqual(true, progressiveData.length > nonProgressiveData.length);
|
||||||
|
assert.strictEqual('png', progressiveInfo.format);
|
||||||
|
assert.strictEqual(320, progressiveInfo.width);
|
||||||
|
assert.strictEqual(240, progressiveInfo.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (sharp.format.webp.output.buffer) {
|
||||||
|
it('WebP output', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.toFormat(sharp.format.webp)
|
||||||
|
.toBuffer(function(err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, data.length > 0);
|
||||||
|
assert.strictEqual('webp', info.format);
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(240, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
it('Invalid output format', function(done) {
|
||||||
|
var isValid = false;
|
||||||
|
try {
|
||||||
|
sharp().toFormat('zoinks');
|
||||||
|
isValid = true;
|
||||||
|
} catch (e) {}
|
||||||
|
assert(!isValid);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('File input with corrupt header fails gracefully', function(done) {
|
||||||
|
sharp(fixtures.inputJpgWithCorruptHeader)
|
||||||
|
.toBuffer(function(err) {
|
||||||
|
assert.strictEqual(true, !!err);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Buffer input with corrupt header fails gracefully', function(done) {
|
||||||
|
sharp(fs.readFileSync(fixtures.inputJpgWithCorruptHeader))
|
||||||
|
.toBuffer(function(err) {
|
||||||
|
assert.strictEqual(true, !!err);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Output filename without extension uses input format', function() {
|
||||||
|
|
||||||
|
it('JPEG', function(done) {
|
||||||
|
sharp(fixtures.inputJpg).resize(320, 80).toFile(fixtures.outputZoinks, function(err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, info.size > 0);
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(80, info.height);
|
||||||
|
fs.unlinkSync(fixtures.outputZoinks);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('PNG', function(done) {
|
||||||
|
sharp(fixtures.inputPng).resize(320, 80).toFile(fixtures.outputZoinks, function(err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, info.size > 0);
|
||||||
|
assert.strictEqual('png', info.format);
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(80, info.height);
|
||||||
|
fs.unlinkSync(fixtures.outputZoinks);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Transparent PNG', function(done) {
|
||||||
|
sharp(fixtures.inputPngWithTransparency).resize(320, 80).toFile(fixtures.outputZoinks, function(err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, info.size > 0);
|
||||||
|
assert.strictEqual('png', info.format);
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(80, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (sharp.format.webp.input.file) {
|
||||||
|
it('WebP', function(done) {
|
||||||
|
sharp(fixtures.inputWebP).resize(320, 80).toFile(fixtures.outputZoinks, function(err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, info.size > 0);
|
||||||
|
assert.strictEqual('webp', info.format);
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(80, info.height);
|
||||||
|
fs.unlinkSync(fixtures.outputZoinks);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
it('TIFF', function(done) {
|
||||||
|
sharp(fixtures.inputTiff).resize(320, 80).toFile(fixtures.outputZoinks, function(err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, info.size > 0);
|
||||||
|
assert.strictEqual('tiff', info.format);
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(80, info.height);
|
||||||
|
fs.unlinkSync(fixtures.outputZoinks);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Fail with GIF', function(done) {
|
||||||
|
sharp(fixtures.inputGif).resize(320, 80).toFile(fixtures.outputZoinks, function(err) {
|
||||||
|
assert(!!err);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('PNG output', function() {
|
||||||
|
|
||||||
|
it('compression level is valid', function(done) {
|
||||||
|
var isValid = false;
|
||||||
|
try {
|
||||||
|
sharp().compressionLevel(0);
|
||||||
|
isValid = true;
|
||||||
|
} catch (e) {}
|
||||||
|
assert(isValid);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('compression level is invalid', function(done) {
|
||||||
|
var isValid = false;
|
||||||
|
try {
|
||||||
|
sharp().compressionLevel(-1);
|
||||||
|
isValid = true;
|
||||||
|
} catch (e) {}
|
||||||
|
assert(!isValid);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (semver.gte(sharp.libvipsVersion(), '7.42.0')) {
|
||||||
|
it('withoutAdaptiveFiltering generates smaller file [libvips ' + sharp.libvipsVersion() + '>=7.42.0]', function(done) {
|
||||||
|
// First generate with adaptive filtering
|
||||||
|
sharp(fixtures.inputPng)
|
||||||
|
.resize(320, 240)
|
||||||
|
.withoutAdaptiveFiltering(false)
|
||||||
|
.toBuffer(function(err, adaptiveData, adaptiveInfo) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, adaptiveData.length > 0);
|
||||||
|
assert.strictEqual(adaptiveData.length, adaptiveInfo.size);
|
||||||
|
assert.strictEqual('png', adaptiveInfo.format);
|
||||||
|
assert.strictEqual(320, adaptiveInfo.width);
|
||||||
|
assert.strictEqual(240, adaptiveInfo.height);
|
||||||
|
// Then generate without
|
||||||
|
sharp(fixtures.inputPng)
|
||||||
|
.resize(320, 240)
|
||||||
|
.withoutAdaptiveFiltering()
|
||||||
|
.toBuffer(function(err, withoutAdaptiveData, withoutAdaptiveInfo) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, withoutAdaptiveData.length > 0);
|
||||||
|
assert.strictEqual(withoutAdaptiveData.length, withoutAdaptiveInfo.size);
|
||||||
|
assert.strictEqual('png', withoutAdaptiveInfo.format);
|
||||||
|
assert.strictEqual(320, withoutAdaptiveInfo.width);
|
||||||
|
assert.strictEqual(240, withoutAdaptiveInfo.height);
|
||||||
|
assert.strictEqual(true, withoutAdaptiveData.length < adaptiveData.length);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Without chroma subsampling generates larger file', function(done) {
|
||||||
|
// First generate with chroma subsampling (default)
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.withoutChromaSubsampling(false)
|
||||||
|
.toBuffer(function(err, withChromaSubsamplingData, withChromaSubsamplingInfo) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, withChromaSubsamplingData.length > 0);
|
||||||
|
assert.strictEqual(withChromaSubsamplingData.length, withChromaSubsamplingInfo.size);
|
||||||
|
assert.strictEqual('jpeg', withChromaSubsamplingInfo.format);
|
||||||
|
assert.strictEqual(320, withChromaSubsamplingInfo.width);
|
||||||
|
assert.strictEqual(240, withChromaSubsamplingInfo.height);
|
||||||
|
// Then generate without
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.withoutChromaSubsampling()
|
||||||
|
.toBuffer(function(err, withoutChromaSubsamplingData, withoutChromaSubsamplingInfo) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, withoutChromaSubsamplingData.length > 0);
|
||||||
|
assert.strictEqual(withoutChromaSubsamplingData.length, withoutChromaSubsamplingInfo.size);
|
||||||
|
assert.strictEqual('jpeg', withoutChromaSubsamplingInfo.format);
|
||||||
|
assert.strictEqual(320, withoutChromaSubsamplingInfo.width);
|
||||||
|
assert.strictEqual(240, withoutChromaSubsamplingInfo.height);
|
||||||
|
assert.strictEqual(true, withChromaSubsamplingData.length < withoutChromaSubsamplingData.length);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (semver.gte(sharp.libvipsVersion(), '8.0.0')) {
|
||||||
|
it('Trellis quantisation [libvips ' + sharp.libvipsVersion() + '>=8.0.0]', function(done) {
|
||||||
|
// First generate without
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.trellisQuantisation(false)
|
||||||
|
.toBuffer(function(err, withoutData, withoutInfo) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, withoutData.length > 0);
|
||||||
|
assert.strictEqual(withoutData.length, withoutInfo.size);
|
||||||
|
assert.strictEqual('jpeg', withoutInfo.format);
|
||||||
|
assert.strictEqual(320, withoutInfo.width);
|
||||||
|
assert.strictEqual(240, withoutInfo.height);
|
||||||
|
// Then generate with
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.trellisQuantization()
|
||||||
|
.toBuffer(function(err, withData, withInfo) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, withData.length > 0);
|
||||||
|
assert.strictEqual(withData.length, withInfo.size);
|
||||||
|
assert.strictEqual('jpeg', withInfo.format);
|
||||||
|
assert.strictEqual(320, withInfo.width);
|
||||||
|
assert.strictEqual(240, withInfo.height);
|
||||||
|
// Verify image is same (as mozjpeg may not be present) size or less
|
||||||
|
assert.strictEqual(true, withData.length <= withoutData.length);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Overshoot deringing [libvips ' + sharp.libvipsVersion() + '>=8.0.0]', function(done) {
|
||||||
|
// First generate without
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.overshootDeringing(false)
|
||||||
|
.toBuffer(function(err, withoutData, withoutInfo) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, withoutData.length > 0);
|
||||||
|
assert.strictEqual(withoutData.length, withoutInfo.size);
|
||||||
|
assert.strictEqual('jpeg', withoutInfo.format);
|
||||||
|
assert.strictEqual(320, withoutInfo.width);
|
||||||
|
assert.strictEqual(240, withoutInfo.height);
|
||||||
|
// Then generate with
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.overshootDeringing()
|
||||||
|
.toBuffer(function(err, withData, withInfo) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, withData.length > 0);
|
||||||
|
assert.strictEqual(withData.length, withInfo.size);
|
||||||
|
assert.strictEqual('jpeg', withInfo.format);
|
||||||
|
assert.strictEqual(320, withInfo.width);
|
||||||
|
assert.strictEqual(240, withInfo.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('Optimise scans [libvips ' + sharp.libvipsVersion() + '>=8.0.0]', function(done) {
|
||||||
|
// First generate without
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.optimiseScans(false)
|
||||||
|
.toBuffer(function(err, withoutData, withoutInfo) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, withoutData.length > 0);
|
||||||
|
assert.strictEqual(withoutData.length, withoutInfo.size);
|
||||||
|
assert.strictEqual('jpeg', withoutInfo.format);
|
||||||
|
assert.strictEqual(320, withoutInfo.width);
|
||||||
|
assert.strictEqual(240, withoutInfo.height);
|
||||||
|
// Then generate with
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.optimizeScans()
|
||||||
|
.toBuffer(function(err, withData, withInfo) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, withData.length > 0);
|
||||||
|
assert.strictEqual(withData.length, withInfo.size);
|
||||||
|
assert.strictEqual('jpeg', withInfo.format);
|
||||||
|
assert.strictEqual(320, withInfo.width);
|
||||||
|
assert.strictEqual(240, withInfo.height);
|
||||||
|
// Verify image is of a different size (progressive output even without mozjpeg)
|
||||||
|
assert.strictEqual(true, withData.length != withoutData.length);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sharp.format.magick.input.file) {
|
||||||
|
it('Convert SVG, if supported, to PNG', function(done) {
|
||||||
|
sharp(fixtures.inputSvg)
|
||||||
|
.resize(100, 100)
|
||||||
|
.toFormat('png')
|
||||||
|
.toFile(fixtures.path('output.svg.png'), function(err, info) {
|
||||||
|
if (err) {
|
||||||
|
assert.strictEqual(0, err.message.indexOf('Input file is of an unsupported image format'));
|
||||||
|
} else {
|
||||||
|
assert.strictEqual(true, info.size > 0);
|
||||||
|
assert.strictEqual('png', info.format);
|
||||||
|
assert.strictEqual(100, info.width);
|
||||||
|
assert.strictEqual(100, info.height);
|
||||||
|
}
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sharp.format.magick.input.file) {
|
||||||
|
it('Convert PSD to PNG', function(done) {
|
||||||
|
sharp(fixtures.inputPsd)
|
||||||
|
.resize(320, 240)
|
||||||
|
.toFormat(sharp.format.png)
|
||||||
|
.toFile(fixtures.path('output.psd.png'), function(err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, info.size > 0);
|
||||||
|
assert.strictEqual('png', info.format);
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(240, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sharp.format.tiff.input.buffer) {
|
||||||
|
it('Load TIFF from Buffer', function(done) {
|
||||||
|
var inputTiffBuffer = fs.readFileSync(fixtures.inputTiff);
|
||||||
|
sharp(inputTiffBuffer)
|
||||||
|
.resize(320, 240)
|
||||||
|
.jpeg()
|
||||||
|
.toBuffer(function(err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, data.length > 0);
|
||||||
|
assert.strictEqual(data.length, info.size);
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(240, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sharp.format.magick.input.buffer) {
|
||||||
|
it('Load GIF from Buffer', function(done) {
|
||||||
|
var inputGifBuffer = fs.readFileSync(fixtures.inputGif);
|
||||||
|
sharp(inputGifBuffer)
|
||||||
|
.resize(320, 240)
|
||||||
|
.jpeg()
|
||||||
|
.toBuffer(function(err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, data.length > 0);
|
||||||
|
assert.strictEqual(data.length, info.size);
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(240, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.greyscale()
|
||||||
|
.resize(32, 24)
|
||||||
|
.raw()
|
||||||
|
.toBuffer(function(err, data, info) {
|
||||||
|
assert.strictEqual(32 * 24 * 1, info.size);
|
||||||
|
assert.strictEqual(data.length, info.size);
|
||||||
|
assert.strictEqual('raw', info.format);
|
||||||
|
assert.strictEqual(32, info.width);
|
||||||
|
assert.strictEqual(24, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('3 channel colour image without transparency', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(32, 24)
|
||||||
|
.toFormat('raw')
|
||||||
|
.toBuffer(function(err, data, info) {
|
||||||
|
assert.strictEqual(32 * 24 * 3, info.size);
|
||||||
|
assert.strictEqual(data.length, info.size);
|
||||||
|
assert.strictEqual('raw', info.format);
|
||||||
|
assert.strictEqual(32, info.width);
|
||||||
|
assert.strictEqual(24, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('4 channel colour image with transparency', function(done) {
|
||||||
|
sharp(fixtures.inputPngWithTransparency)
|
||||||
|
.resize(32, 24)
|
||||||
|
.toFormat(sharp.format.raw)
|
||||||
|
.toBuffer(function(err, data, info) {
|
||||||
|
assert.strictEqual(32 * 24 * 4, info.size);
|
||||||
|
assert.strictEqual(data.length, info.size);
|
||||||
|
assert.strictEqual('raw', info.format);
|
||||||
|
assert.strictEqual(32, info.width);
|
||||||
|
assert.strictEqual(24, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Limit pixel count of input image', function() {
|
||||||
|
|
||||||
|
it('Invalid fails - negative', function(done) {
|
||||||
|
var isValid = false;
|
||||||
|
try {
|
||||||
|
sharp().limitInputPixels(-1);
|
||||||
|
isValid = true;
|
||||||
|
} catch (e) {}
|
||||||
|
assert(!isValid);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Invalid fails - float', function(done) {
|
||||||
|
var isValid = false;
|
||||||
|
try {
|
||||||
|
sharp().limitInputPixels(12.3);
|
||||||
|
isValid = true;
|
||||||
|
} catch (e) {}
|
||||||
|
assert(!isValid);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Invalid fails - string', function(done) {
|
||||||
|
var isValid = false;
|
||||||
|
try {
|
||||||
|
sharp().limitInputPixels('fail');
|
||||||
|
isValid = true;
|
||||||
|
} catch (e) {}
|
||||||
|
assert(!isValid);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Same size as input works', function(done) {
|
||||||
|
sharp(fixtures.inputJpg).metadata(function(err, metadata) {
|
||||||
|
if (err) throw err;
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.limitInputPixels(metadata.width * metadata.height)
|
||||||
|
.toBuffer(function(err) {
|
||||||
|
assert.strictEqual(true, !err);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Smaller than input fails', function(done) {
|
||||||
|
sharp(fixtures.inputJpg).metadata(function(err, metadata) {
|
||||||
|
if (err) throw err;
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.limitInputPixels(metadata.width * metadata.height - 1)
|
||||||
|
.toBuffer(function(err) {
|
||||||
|
assert.strictEqual(true, !!err);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Queue length change events', function(done) {
|
||||||
|
var eventCounter = 0;
|
||||||
|
var queueListener = function(queueLength) {
|
||||||
|
assert.strictEqual(true, queueLength === 0 || queueLength === 1);
|
||||||
|
eventCounter++;
|
||||||
|
};
|
||||||
|
sharp.queue.on('change', queueListener);
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.toBuffer(function(err) {
|
||||||
|
process.nextTick(function() {
|
||||||
|
sharp.queue.removeListener('change', queueListener);
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(2, eventCounter);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
1
test/unit/jshint.js
Executable file
@@ -0,0 +1 @@
|
|||||||
|
require('mocha-jshint')();
|
||||||
253
test/unit/metadata.js
Executable file
@@ -0,0 +1,253 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var fs = require('fs');
|
||||||
|
var assert = require('assert');
|
||||||
|
|
||||||
|
var sharp = require('../../index');
|
||||||
|
var fixtures = require('../fixtures');
|
||||||
|
|
||||||
|
sharp.cache(0);
|
||||||
|
|
||||||
|
describe('Image metadata', function() {
|
||||||
|
|
||||||
|
it('JPEG', function(done) {
|
||||||
|
sharp(fixtures.inputJpg).metadata(function(err, metadata) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('jpeg', metadata.format);
|
||||||
|
assert.strictEqual(2725, metadata.width);
|
||||||
|
assert.strictEqual(2225, metadata.height);
|
||||||
|
assert.strictEqual('srgb', metadata.space);
|
||||||
|
assert.strictEqual(3, metadata.channels);
|
||||||
|
assert.strictEqual(false, metadata.hasProfile);
|
||||||
|
assert.strictEqual(false, metadata.hasAlpha);
|
||||||
|
assert.strictEqual('undefined', typeof metadata.orientation);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('JPEG with EXIF', function(done) {
|
||||||
|
sharp(fixtures.inputJpgWithExif).metadata(function(err, metadata) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('jpeg', metadata.format);
|
||||||
|
assert.strictEqual(450, metadata.width);
|
||||||
|
assert.strictEqual(600, metadata.height);
|
||||||
|
assert.strictEqual('srgb', metadata.space);
|
||||||
|
assert.strictEqual(3, metadata.channels);
|
||||||
|
assert.strictEqual(true, metadata.hasProfile);
|
||||||
|
assert.strictEqual(false, metadata.hasAlpha);
|
||||||
|
assert.strictEqual(8, metadata.orientation);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('TIFF', function(done) {
|
||||||
|
sharp(fixtures.inputTiff).metadata(function(err, metadata) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('tiff', metadata.format);
|
||||||
|
assert.strictEqual(2464, metadata.width);
|
||||||
|
assert.strictEqual(3248, metadata.height);
|
||||||
|
assert.strictEqual('b-w', metadata.space);
|
||||||
|
assert.strictEqual(1, metadata.channels);
|
||||||
|
assert.strictEqual(false, metadata.hasProfile);
|
||||||
|
assert.strictEqual(false, metadata.hasAlpha);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('PNG', function(done) {
|
||||||
|
sharp(fixtures.inputPng).metadata(function(err, metadata) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('png', metadata.format);
|
||||||
|
assert.strictEqual(2809, metadata.width);
|
||||||
|
assert.strictEqual(2074, metadata.height);
|
||||||
|
assert.strictEqual('b-w', metadata.space);
|
||||||
|
assert.strictEqual(1, metadata.channels);
|
||||||
|
assert.strictEqual(false, metadata.hasProfile);
|
||||||
|
assert.strictEqual(false, metadata.hasAlpha);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Transparent PNG', function(done) {
|
||||||
|
sharp(fixtures.inputPngWithTransparency).metadata(function(err, metadata) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('png', metadata.format);
|
||||||
|
assert.strictEqual(2048, metadata.width);
|
||||||
|
assert.strictEqual(1536, metadata.height);
|
||||||
|
assert.strictEqual('srgb', metadata.space);
|
||||||
|
assert.strictEqual(4, metadata.channels);
|
||||||
|
assert.strictEqual(false, metadata.hasProfile);
|
||||||
|
assert.strictEqual(true, metadata.hasAlpha);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (sharp.format.webp.input.file) {
|
||||||
|
it('WebP', function(done) {
|
||||||
|
sharp(fixtures.inputWebP).metadata(function(err, metadata) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('webp', metadata.format);
|
||||||
|
assert.strictEqual(1024, metadata.width);
|
||||||
|
assert.strictEqual(772, metadata.height);
|
||||||
|
assert.strictEqual('srgb', metadata.space);
|
||||||
|
assert.strictEqual(3, metadata.channels);
|
||||||
|
assert.strictEqual(false, metadata.hasProfile);
|
||||||
|
assert.strictEqual(false, metadata.hasAlpha);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
it('GIF via libmagick', function(done) {
|
||||||
|
sharp(fixtures.inputGif).metadata(function(err, metadata) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('magick', metadata.format);
|
||||||
|
assert.strictEqual(800, metadata.width);
|
||||||
|
assert.strictEqual(533, metadata.height);
|
||||||
|
assert.strictEqual(3, metadata.channels);
|
||||||
|
assert.strictEqual(false, metadata.hasProfile);
|
||||||
|
assert.strictEqual(false, metadata.hasAlpha);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
assert.strictEqual(2725, metadata.width);
|
||||||
|
assert.strictEqual(2225, metadata.height);
|
||||||
|
assert.strictEqual('srgb', metadata.space);
|
||||||
|
assert.strictEqual(3, metadata.channels);
|
||||||
|
assert.strictEqual(false, metadata.hasProfile);
|
||||||
|
assert.strictEqual(false, metadata.hasAlpha);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Non-existent file in, Promise out', function(done) {
|
||||||
|
sharp('fail').metadata().then(function(metadata) {
|
||||||
|
throw new Error('Non-existent file');
|
||||||
|
}, function (err) {
|
||||||
|
assert.ok(!!err);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Stream in, Promise out', function(done) {
|
||||||
|
var readable = fs.createReadStream(fixtures.inputJpg);
|
||||||
|
var pipeline = sharp();
|
||||||
|
pipeline.metadata().then(function(metadata) {
|
||||||
|
assert.strictEqual('jpeg', metadata.format);
|
||||||
|
assert.strictEqual(2725, metadata.width);
|
||||||
|
assert.strictEqual(2225, metadata.height);
|
||||||
|
assert.strictEqual('srgb', metadata.space);
|
||||||
|
assert.strictEqual(3, metadata.channels);
|
||||||
|
assert.strictEqual(false, metadata.hasProfile);
|
||||||
|
assert.strictEqual(false, metadata.hasAlpha);
|
||||||
|
done();
|
||||||
|
}).catch(function(err) {
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
readable.pipe(pipeline);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Stream', function(done) {
|
||||||
|
var readable = fs.createReadStream(fixtures.inputJpg);
|
||||||
|
var pipeline = sharp().metadata(function(err, metadata) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('jpeg', metadata.format);
|
||||||
|
assert.strictEqual(2725, metadata.width);
|
||||||
|
assert.strictEqual(2225, metadata.height);
|
||||||
|
assert.strictEqual('srgb', metadata.space);
|
||||||
|
assert.strictEqual(3, metadata.channels);
|
||||||
|
assert.strictEqual(false, metadata.hasProfile);
|
||||||
|
assert.strictEqual(false, metadata.hasAlpha);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
readable.pipe(pipeline);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Resize to half width using metadata', function(done) {
|
||||||
|
var image = sharp(fixtures.inputJpg);
|
||||||
|
image.metadata(function(err, metadata) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('jpeg', metadata.format);
|
||||||
|
assert.strictEqual(2725, metadata.width);
|
||||||
|
assert.strictEqual(2225, metadata.height);
|
||||||
|
assert.strictEqual('srgb', metadata.space);
|
||||||
|
assert.strictEqual(3, metadata.channels);
|
||||||
|
assert.strictEqual(false, metadata.hasProfile);
|
||||||
|
assert.strictEqual(false, metadata.hasAlpha);
|
||||||
|
image.resize(Math.floor(metadata.width / 2)).toBuffer(function(err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, data.length > 0);
|
||||||
|
assert.strictEqual(1362, info.width);
|
||||||
|
assert.strictEqual(1112, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Keep EXIF metadata after a resize', function(done) {
|
||||||
|
sharp(fixtures.inputJpgWithExif)
|
||||||
|
.resize(320, 240)
|
||||||
|
.withMetadata()
|
||||||
|
.toBuffer(function(err, buffer) {
|
||||||
|
if (err) throw err;
|
||||||
|
sharp(buffer).metadata(function(err, metadata) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, metadata.hasProfile);
|
||||||
|
assert.strictEqual(8, metadata.orientation);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Remove EXIF metadata after a resize', function(done) {
|
||||||
|
sharp(fixtures.inputJpgWithExif)
|
||||||
|
.resize(320, 240)
|
||||||
|
.withMetadata(false)
|
||||||
|
.toBuffer(function(err, buffer) {
|
||||||
|
if (err) throw err;
|
||||||
|
sharp(buffer).metadata(function(err, metadata) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(false, metadata.hasProfile);
|
||||||
|
assert.strictEqual('undefined', typeof metadata.orientation);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('File input with corrupt header fails gracefully', function(done) {
|
||||||
|
sharp(fixtures.inputJpgWithCorruptHeader)
|
||||||
|
.metadata(function(err) {
|
||||||
|
assert.strictEqual(true, !!err);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Buffer input with corrupt header fails gracefully', function(done) {
|
||||||
|
sharp(fs.readFileSync(fixtures.inputJpgWithCorruptHeader))
|
||||||
|
.metadata(function(err) {
|
||||||
|
assert.strictEqual(true, !!err);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
129
test/unit/normalize.js
Executable file
@@ -0,0 +1,129 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var assert = require('assert');
|
||||||
|
|
||||||
|
var sharp = require('../../index');
|
||||||
|
var fixtures = require('../fixtures');
|
||||||
|
|
||||||
|
sharp.cache(0);
|
||||||
|
|
||||||
|
describe('Normalization', function () {
|
||||||
|
|
||||||
|
it('uses the same prototype for both spellings', function () {
|
||||||
|
assert.strictEqual(sharp.prototype.normalize, sharp.prototype.normalise);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Normalize is currently unavailable on Windows
|
||||||
|
if (process.platform !== 'win32') {
|
||||||
|
|
||||||
|
it('spreads rgb image values between 0 and 255', function(done) {
|
||||||
|
sharp(fixtures.inputJpgWithLowContrast)
|
||||||
|
.normalize()
|
||||||
|
.raw()
|
||||||
|
.toBuffer(function (err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
var min = 255, max = 0, i;
|
||||||
|
for (i = 0; i < data.length; i += 3) {
|
||||||
|
min = Math.min(min, data[i], data[i + 1], data[i + 2]);
|
||||||
|
max = Math.max(max, data[i], data[i + 1], data[i + 2]);
|
||||||
|
}
|
||||||
|
assert.strictEqual(0, min);
|
||||||
|
assert.strictEqual(255, max);
|
||||||
|
return done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('spreads grayscaled image values between 0 and 255', function(done) {
|
||||||
|
sharp(fixtures.inputJpgWithLowContrast)
|
||||||
|
.gamma()
|
||||||
|
.greyscale()
|
||||||
|
.normalize(true)
|
||||||
|
.raw()
|
||||||
|
.toBuffer(function (err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
var min = 255, max = 0, i;
|
||||||
|
for (i = 0; i < data.length; i++) {
|
||||||
|
min = Math.min(min, data[i]);
|
||||||
|
max = Math.max(max, data[i]);
|
||||||
|
}
|
||||||
|
assert.strictEqual(0, min);
|
||||||
|
assert.strictEqual(255, max);
|
||||||
|
return done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('stretches greyscale images with alpha channel', function (done) {
|
||||||
|
sharp(fixtures.inputPngWithGreyAlpha)
|
||||||
|
.normalize()
|
||||||
|
.raw()
|
||||||
|
.toBuffer(function (err, data, info) {
|
||||||
|
var min = 255, max = 0, i;
|
||||||
|
for (i = 0; i < data.length; i++) {
|
||||||
|
min = Math.min(min, data[i]);
|
||||||
|
max = Math.max(max, data[i]);
|
||||||
|
}
|
||||||
|
assert.strictEqual(0, min);
|
||||||
|
assert.strictEqual(255, max);
|
||||||
|
return done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps an existing alpha channel', function (done) {
|
||||||
|
sharp(fixtures.inputPngWithTransparency)
|
||||||
|
.normalize()
|
||||||
|
.toBuffer(function (err, data, info) {
|
||||||
|
sharp(data)
|
||||||
|
.metadata()
|
||||||
|
.then(function (metadata) {
|
||||||
|
assert.strictEqual(4, metadata.channels);
|
||||||
|
assert.strictEqual(true, metadata.hasAlpha);
|
||||||
|
assert.strictEqual('srgb', metadata.space);
|
||||||
|
})
|
||||||
|
.finally(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps the alpha channel of greyscale images intact', function (done) {
|
||||||
|
sharp(fixtures.inputPngWithGreyAlpha)
|
||||||
|
.normalize()
|
||||||
|
.toBuffer(function (err, data, info) {
|
||||||
|
sharp(data)
|
||||||
|
.metadata()
|
||||||
|
.then(function (metadata) {
|
||||||
|
assert.strictEqual(true, metadata.hasAlpha);
|
||||||
|
assert.strictEqual(4, metadata.channels);
|
||||||
|
assert.strictEqual('srgb', metadata.space);
|
||||||
|
})
|
||||||
|
.finally(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns a black image for images with only one color', function (done) {
|
||||||
|
sharp(fixtures.inputPngWithOneColor)
|
||||||
|
.normalize()
|
||||||
|
.toBuffer()
|
||||||
|
.bind({})
|
||||||
|
.then(function (imageData) {
|
||||||
|
this.imageData = imageData;
|
||||||
|
return sharp(imageData)
|
||||||
|
.metadata();
|
||||||
|
})
|
||||||
|
.then(function (metadata) {
|
||||||
|
assert.strictEqual(false, metadata.hasAlpha);
|
||||||
|
assert.strictEqual(3, metadata.channels);
|
||||||
|
assert.strictEqual('srgb', metadata.space);
|
||||||
|
})
|
||||||
|
.then(function () {
|
||||||
|
return sharp(this.imageData)
|
||||||
|
.raw()
|
||||||
|
.toBuffer();
|
||||||
|
})
|
||||||
|
.then(function (rawData) {
|
||||||
|
var blackBuffer = new Buffer([0,0,0, 0,0,0, 0,0,0, 0,0,0]);
|
||||||
|
assert.strictEqual(blackBuffer.toString(), rawData.toString());
|
||||||
|
})
|
||||||
|
.finally(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
362
test/unit/resize.js
Executable file
@@ -0,0 +1,362 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var assert = require('assert');
|
||||||
|
|
||||||
|
var sharp = require('../../index');
|
||||||
|
var fixtures = require('../fixtures');
|
||||||
|
|
||||||
|
sharp.cache(0);
|
||||||
|
|
||||||
|
describe('Resize dimensions', function() {
|
||||||
|
|
||||||
|
it('Exact crop', function(done) {
|
||||||
|
sharp(fixtures.inputJpg).resize(320, 240).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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Fixed width', function(done) {
|
||||||
|
sharp(fixtures.inputJpg).resize(320).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(261, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Fixed height', function(done) {
|
||||||
|
sharp(fixtures.inputJpg).resize(null, 320).toBuffer(function(err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, data.length > 0);
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(391, info.width);
|
||||||
|
assert.strictEqual(320, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Identity transform', function(done) {
|
||||||
|
sharp(fixtures.inputJpg).toBuffer(function(err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, data.length > 0);
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(2725, info.width);
|
||||||
|
assert.strictEqual(2225, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Upscale', function(done) {
|
||||||
|
sharp(fixtures.inputJpg).resize(3000).toBuffer(function(err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, data.length > 0);
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(3000, info.width);
|
||||||
|
assert.strictEqual(2449, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Invalid width - NaN', function(done) {
|
||||||
|
var isValid = true;
|
||||||
|
try {
|
||||||
|
sharp(fixtures.inputJpg).resize('spoons', 240);
|
||||||
|
} catch (err) {
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
assert.strictEqual(false, isValid);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Invalid height - NaN', function(done) {
|
||||||
|
var isValid = true;
|
||||||
|
try {
|
||||||
|
sharp(fixtures.inputJpg).resize(320, 'spoons');
|
||||||
|
} catch (err) {
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
assert.strictEqual(false, isValid);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Invalid width - float', function(done) {
|
||||||
|
var isValid = true;
|
||||||
|
try {
|
||||||
|
sharp(fixtures.inputJpg).resize(1.5, 240);
|
||||||
|
} catch (err) {
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
assert.strictEqual(false, isValid);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Invalid height - float', function(done) {
|
||||||
|
var isValid = true;
|
||||||
|
try {
|
||||||
|
sharp(fixtures.inputJpg).resize(320, 1.5);
|
||||||
|
} catch (err) {
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
assert.strictEqual(false, isValid);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Invalid width - too large', function(done) {
|
||||||
|
var isValid = true;
|
||||||
|
try {
|
||||||
|
sharp(fixtures.inputJpg).resize(0x4000, 240);
|
||||||
|
} catch (err) {
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
assert.strictEqual(false, isValid);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Invalid height - too large', function(done) {
|
||||||
|
var isValid = true;
|
||||||
|
try {
|
||||||
|
sharp(fixtures.inputJpg).resize(320, 0x4000);
|
||||||
|
} catch (err) {
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
assert.strictEqual(false, isValid);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('TIFF embed known to cause rounding errors', function(done) {
|
||||||
|
sharp(fixtures.inputTiff).resize(240, 320).embed().jpeg().toBuffer(function(err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, data.length > 0);
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(240, info.width);
|
||||||
|
assert.strictEqual(320, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('TIFF known to cause rounding errors', function(done) {
|
||||||
|
sharp(fixtures.inputTiff).resize(240, 320).jpeg().toBuffer(function(err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, data.length > 0);
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(240, info.width);
|
||||||
|
assert.strictEqual(320, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Max width or height considering ratio (landscape)', function(done) {
|
||||||
|
sharp(fixtures.inputJpg).resize(320, 320).max().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(261, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Max width or height considering ratio (portrait)', function(done) {
|
||||||
|
sharp(fixtures.inputTiff).resize(320, 320).max().jpeg().toBuffer(function(err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, data.length > 0);
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(243, info.width);
|
||||||
|
assert.strictEqual(320, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Provide only one dimension with max, should default to crop', function(done) {
|
||||||
|
sharp(fixtures.inputJpg).resize(320).max().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(261, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Min width or height considering ratio (landscape)', function(done) {
|
||||||
|
sharp(fixtures.inputJpg).resize(320, 320).min().toBuffer(function(err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, data.length > 0);
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(392, info.width);
|
||||||
|
assert.strictEqual(320, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Min width or height considering ratio (portrait)', function(done) {
|
||||||
|
sharp(fixtures.inputTiff).resize(320, 320).min().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(422, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Provide only one dimension with min, should default to crop', function(done) {
|
||||||
|
sharp(fixtures.inputJpg).resize(320).min().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(261, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Do not enlarge when input width is already less than output width', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(2800)
|
||||||
|
.withoutEnlargement()
|
||||||
|
.toBuffer(function(err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, data.length > 0);
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(2725, info.width);
|
||||||
|
assert.strictEqual(2225, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Do not enlarge when input height is already less than output height', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(null, 2300)
|
||||||
|
.withoutEnlargement()
|
||||||
|
.toBuffer(function(err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, data.length > 0);
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(2725, info.width);
|
||||||
|
assert.strictEqual(2225, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Do enlarge when input width is less than output width', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(2800)
|
||||||
|
.withoutEnlargement(false)
|
||||||
|
.toBuffer(function(err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, data.length > 0);
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(2800, info.width);
|
||||||
|
assert.strictEqual(2286, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Downscale width and height, ignoring aspect ratio', function(done) {
|
||||||
|
sharp(fixtures.inputJpg).resize(320, 320).ignoreAspectRatio().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(320, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Downscale width, ignoring aspect ratio', function(done) {
|
||||||
|
sharp(fixtures.inputJpg).resize(320).ignoreAspectRatio().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(2225, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Downscale height, ignoring aspect ratio', function(done) {
|
||||||
|
sharp(fixtures.inputJpg).resize(null, 320).ignoreAspectRatio().toBuffer(function(err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, data.length > 0);
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(2725, info.width);
|
||||||
|
assert.strictEqual(320, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Upscale width and height, ignoring aspect ratio', function(done) {
|
||||||
|
sharp(fixtures.inputJpg).resize(3000, 3000).ignoreAspectRatio().toBuffer(function(err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, data.length > 0);
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(3000, info.width);
|
||||||
|
assert.strictEqual(3000, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Upscale width, ignoring aspect ratio', function(done) {
|
||||||
|
sharp(fixtures.inputJpg).resize(3000).ignoreAspectRatio().toBuffer(function(err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, data.length > 0);
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(3000, info.width);
|
||||||
|
assert.strictEqual(2225, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Upscale height, ignoring aspect ratio', function(done) {
|
||||||
|
sharp(fixtures.inputJpg).resize(null, 3000).ignoreAspectRatio().toBuffer(function(err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, data.length > 0);
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(2725, info.width);
|
||||||
|
assert.strictEqual(3000, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Downscale width, upscale height, ignoring aspect ratio', function(done) {
|
||||||
|
sharp(fixtures.inputJpg).resize(320, 3000).ignoreAspectRatio().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(3000, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Upscale width, downscale height, ignoring aspect ratio', function(done) {
|
||||||
|
sharp(fixtures.inputJpg).resize(3000, 320).ignoreAspectRatio().toBuffer(function(err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, data.length > 0);
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(3000, info.width);
|
||||||
|
assert.strictEqual(320, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Identity transform, ignoring aspect ratio', function(done) {
|
||||||
|
sharp(fixtures.inputJpg).ignoreAspectRatio().toBuffer(function(err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, data.length > 0);
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(2725, info.width);
|
||||||
|
assert.strictEqual(2225, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
151
test/unit/rotate.js
Executable file
@@ -0,0 +1,151 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var assert = require('assert');
|
||||||
|
|
||||||
|
var sharp = require('../../index');
|
||||||
|
var fixtures = require('../fixtures');
|
||||||
|
|
||||||
|
sharp.cache(0);
|
||||||
|
|
||||||
|
describe('Rotation', function() {
|
||||||
|
|
||||||
|
it('Rotate by 90 degrees, respecting output input size', function(done) {
|
||||||
|
sharp(fixtures.inputJpg).rotate(90).resize(320, 240).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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Input image has Orientation EXIF tag but do not rotate output', function(done) {
|
||||||
|
sharp(fixtures.inputJpgWithExif)
|
||||||
|
.resize(320)
|
||||||
|
.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(426, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Input image has Orientation EXIF tag value of 8 (270 degrees), auto-rotate', function(done) {
|
||||||
|
sharp(fixtures.inputJpgWithExif)
|
||||||
|
.rotate()
|
||||||
|
.resize(320)
|
||||||
|
.toFile(fixtures.path('output.exif.8.jpg'), function(err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(240, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Input image has Orientation EXIF tag value of 5 (270 degrees + flip), auto-rotate', function(done) {
|
||||||
|
sharp(fixtures.inputJpgWithExifMirroring)
|
||||||
|
.rotate()
|
||||||
|
.resize(320)
|
||||||
|
.toFile(fixtures.path('output.exif.5.jpg'), function(err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(240, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Attempt to auto-rotate using image that has no EXIF', function(done) {
|
||||||
|
sharp(fixtures.inputJpg).rotate().resize(320).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(261, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Attempt to auto-rotate image format without EXIF support', function(done) {
|
||||||
|
sharp(fixtures.inputGif)
|
||||||
|
.rotate()
|
||||||
|
.resize(320)
|
||||||
|
.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(213, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Rotate to an invalid angle, should fail', function(done) {
|
||||||
|
var fail = false;
|
||||||
|
try {
|
||||||
|
sharp(fixtures.inputJpg).rotate(1);
|
||||||
|
fail = true;
|
||||||
|
} catch (e) {}
|
||||||
|
assert(!fail);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Flip - vertical', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320)
|
||||||
|
.flip()
|
||||||
|
.toFile(fixtures.path('output.flip.jpg'), function(err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(261, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Flop - horizontal', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320)
|
||||||
|
.flop()
|
||||||
|
.toFile(fixtures.path('output.flop.jpg'), function(err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(261, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Flip and flop', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320)
|
||||||
|
.flop()
|
||||||
|
.toFile(fixtures.path('output.flip.flop.jpg'), function(err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(261, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Neither flip nor flop', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320)
|
||||||
|
.flip(false)
|
||||||
|
.flop(false)
|
||||||
|
.toFile(fixtures.path('output.flip.flop.nope.jpg'), function(err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(261, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
122
test/unit/sharpen.js
Executable file
@@ -0,0 +1,122 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var assert = require('assert');
|
||||||
|
|
||||||
|
var sharp = require('../../index');
|
||||||
|
var fixtures = require('../fixtures');
|
||||||
|
|
||||||
|
sharp.cache(0);
|
||||||
|
|
||||||
|
describe('Sharpen', function() {
|
||||||
|
|
||||||
|
it('specific radius 10', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.sharpen(10)
|
||||||
|
.toFile(fixtures.path('output.sharpen-10.jpg'), function(err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(240, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('specific radius 3 and levels 0.5, 2.5', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.sharpen(3, 0.5, 2.5)
|
||||||
|
.toFile(fixtures.path('output.sharpen-3-0.5-2.5.jpg'), function(err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(240, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('specific radius 5 and levels 2, 4', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.sharpen(5, 2, 4)
|
||||||
|
.toFile(fixtures.path('output.sharpen-5-2-4.jpg'), function(err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(240, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('mild sharpen', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.sharpen()
|
||||||
|
.toFile(fixtures.path('output.sharpen-mild.jpg'), function(err, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(240, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('invalid radius', function(done) {
|
||||||
|
var isValid = true;
|
||||||
|
try {
|
||||||
|
sharp(fixtures.inputJpg).sharpen(1.5);
|
||||||
|
} catch (err) {
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
assert.strictEqual(false, isValid);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('invalid flat', function(done) {
|
||||||
|
var isValid = true;
|
||||||
|
try {
|
||||||
|
sharp(fixtures.inputJpg).sharpen(1, -1);
|
||||||
|
} catch (err) {
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
assert.strictEqual(false, isValid);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('invalid jagged', function(done) {
|
||||||
|
var isValid = true;
|
||||||
|
try {
|
||||||
|
sharp(fixtures.inputJpg).sharpen(1, 1, -1);
|
||||||
|
} catch (err) {
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
assert.strictEqual(false, isValid);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sharpened image is larger than non-sharpened', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.sharpen(false)
|
||||||
|
.toBuffer(function(err, notSharpened, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, notSharpened.length > 0);
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(240, info.height);
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(320, 240)
|
||||||
|
.sharpen(true)
|
||||||
|
.toBuffer(function(err, sharpened, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, sharpened.length > 0);
|
||||||
|
assert.strictEqual(true, sharpened.length > notSharpened.length);
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(320, info.width);
|
||||||
|
assert.strictEqual(240, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
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('output.256_files');
|
||||||
|
rimraf(directory, function() {
|
||||||
|
sharp(fixtures.inputJpg).toFile(fixtures.path('output.256.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) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('dz', info.format);
|
||||||
|
assertDeepZoomTiles(directory, 512 + 2 * 16, 13, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
74
test/unit/util.js
Executable file
@@ -0,0 +1,74 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var assert = require('assert');
|
||||||
|
var sharp = require('../../index');
|
||||||
|
|
||||||
|
var defaultConcurrency = sharp.concurrency();
|
||||||
|
|
||||||
|
describe('Utilities', function() {
|
||||||
|
|
||||||
|
describe('Cache', function() {
|
||||||
|
it('Can be disabled', function() {
|
||||||
|
var cache = sharp.cache(0, 0);
|
||||||
|
assert.strictEqual(0, cache.memory);
|
||||||
|
assert.strictEqual(0, cache.items);
|
||||||
|
});
|
||||||
|
it('Can be set to a maximum of 50MB and 500 items', function() {
|
||||||
|
var cache = sharp.cache(50, 500);
|
||||||
|
assert.strictEqual(50, cache.memory);
|
||||||
|
assert.strictEqual(500, cache.items);
|
||||||
|
});
|
||||||
|
it('Ignores invalid values', function() {
|
||||||
|
sharp.cache(50, 500);
|
||||||
|
var cache = sharp.cache('spoons');
|
||||||
|
assert.strictEqual(50, cache.memory);
|
||||||
|
assert.strictEqual(500, cache.items);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Concurrency', function() {
|
||||||
|
it('Can be set to use 16 threads', function() {
|
||||||
|
sharp.concurrency(16);
|
||||||
|
assert.strictEqual(16, sharp.concurrency());
|
||||||
|
});
|
||||||
|
it('Can be reset to default', function() {
|
||||||
|
sharp.concurrency(0);
|
||||||
|
assert.strictEqual(defaultConcurrency, sharp.concurrency());
|
||||||
|
});
|
||||||
|
it('Ignores invalid values', function() {
|
||||||
|
sharp.concurrency(0);
|
||||||
|
sharp.concurrency('spoons');
|
||||||
|
assert.strictEqual(defaultConcurrency, sharp.concurrency());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Counters', function() {
|
||||||
|
it('Have zero value at rest', function() {
|
||||||
|
var counters = sharp.counters();
|
||||||
|
assert.strictEqual(0, counters.queue);
|
||||||
|
assert.strictEqual(0, counters.process);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Format', function() {
|
||||||
|
it('Contains expected attributes', function() {
|
||||||
|
assert.strictEqual('object', typeof sharp.format);
|
||||||
|
Object.keys(sharp.format).forEach(function(format) {
|
||||||
|
assert.strictEqual(true, 'id' in sharp.format[format]);
|
||||||
|
assert.strictEqual(format, sharp.format[format].id);
|
||||||
|
['input', 'output'].forEach(function(direction) {
|
||||||
|
assert.strictEqual(true, direction in sharp.format[format]);
|
||||||
|
assert.strictEqual('object', typeof sharp.format[format][direction]);
|
||||||
|
assert.strictEqual(3, Object.keys(sharp.format[format][direction]).length);
|
||||||
|
assert.strictEqual(true, 'file' in sharp.format[format][direction]);
|
||||||
|
assert.strictEqual(true, 'buffer' in sharp.format[format][direction]);
|
||||||
|
assert.strictEqual(true, 'stream' in sharp.format[format][direction]);
|
||||||
|
assert.strictEqual('boolean', typeof sharp.format[format][direction].file);
|
||||||
|
assert.strictEqual('boolean', typeof sharp.format[format][direction].buffer);
|
||||||
|
assert.strictEqual('boolean', typeof sharp.format[format][direction].stream);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
BIN
tests/fixtures/2569067123_aca715a2ee_o.jpg
vendored
|
Before Width: | Height: | Size: 813 KiB |
@@ -1,68 +0,0 @@
|
|||||||
var sharp = require("../index");
|
|
||||||
var fs = require("fs");
|
|
||||||
var path = require("path");
|
|
||||||
var imagemagick = require("imagemagick");
|
|
||||||
var gm = require("gm");
|
|
||||||
var async = require("async");
|
|
||||||
var assert = require("assert");
|
|
||||||
var Benchmark = require("benchmark");
|
|
||||||
|
|
||||||
var fixturesPath = path.join(__dirname, "fixtures");
|
|
||||||
var inputJpg = path.join(fixturesPath, "2569067123_aca715a2ee_o.jpg"); // http://www.flickr.com/photos/grizdave/2569067123/
|
|
||||||
var outputJpg = path.join(fixturesPath, "output.jpg");
|
|
||||||
|
|
||||||
var min = 320;
|
|
||||||
var max = 960;
|
|
||||||
|
|
||||||
var randomDimension = function() {
|
|
||||||
return Math.random() * (max - min) + min;
|
|
||||||
};
|
|
||||||
|
|
||||||
new Benchmark.Suite("random").add("imagemagick", {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
imagemagick.resize({
|
|
||||||
srcPath: inputJpg,
|
|
||||||
dstPath: outputJpg,
|
|
||||||
quality: 0.8,
|
|
||||||
width: randomDimension(),
|
|
||||||
height: randomDimension()
|
|
||||||
}, function(err) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
} else {
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).add("gm", {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
gm(inputJpg).resize(randomDimension(), randomDimension()).quality(80).toBuffer(function (err, buffer) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
} else {
|
|
||||||
assert.notStrictEqual(null, buffer);
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).add("sharp", {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
sharp(inputJpg).resize(randomDimension(), randomDimension()).toBuffer(function(err, buffer) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
} else {
|
|
||||||
assert.notStrictEqual(null, buffer);
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).on("cycle", function(event) {
|
|
||||||
console.log(String(event.target));
|
|
||||||
}).on("complete", function() {
|
|
||||||
var winner = this.filter("fastest").pluck("name");
|
|
||||||
assert.strictEqual("sharp", String(winner), "sharp was slower than " + winner);
|
|
||||||
console.dir(sharp.cache());
|
|
||||||
}).run();
|
|
||||||
708
tests/unit.js
@@ -1,708 +0,0 @@
|
|||||||
/*jslint node: true */
|
|
||||||
/*jslint es5: true */
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var sharp = require("../index");
|
|
||||||
var fs = require("fs");
|
|
||||||
var path = require("path");
|
|
||||||
var assert = require("assert");
|
|
||||||
var async = require("async");
|
|
||||||
|
|
||||||
var fixturesPath = path.join(__dirname, "fixtures");
|
|
||||||
|
|
||||||
var inputJpg = path.join(fixturesPath, "2569067123_aca715a2ee_o.jpg"); // http://www.flickr.com/photos/grizdave/2569067123/
|
|
||||||
var outputJpg = path.join(fixturesPath, "output.jpg");
|
|
||||||
|
|
||||||
var inputTiff = path.join(fixturesPath, "G31D.TIF"); // http://www.fileformat.info/format/tiff/sample/e6c9a6e5253348f4aef6d17b534360ab/index.htm
|
|
||||||
var outputTiff = path.join(fixturesPath, "output.tiff");
|
|
||||||
|
|
||||||
var inputJpgWithExif = path.join(fixturesPath, "Landscape_8.jpg"); // https://github.com/recurser/exif-orientation-examples/blob/master/Landscape_8.jpg
|
|
||||||
|
|
||||||
var inputJpgWithGammaHoliness = path.join(fixturesPath, "gamma_dalai_lama_gray.jpg"); // http://www.4p8.com/eric.brasseur/gamma.html
|
|
||||||
|
|
||||||
var inputPng = path.join(fixturesPath, "50020484-00001.png"); // http://c.searspartsdirect.com/lis_png/PLDM/50020484-00001.png
|
|
||||||
var inputWebP = path.join(fixturesPath, "4.webp"); // http://www.gstatic.com/webp/gallery/4.webp
|
|
||||||
var inputGif = path.join(fixturesPath, "Crash_test.gif"); // http://upload.wikimedia.org/wikipedia/commons/e/e3/Crash_test.gif
|
|
||||||
|
|
||||||
// Ensure cache limits can be set
|
|
||||||
sharp.cache(0); // Disable
|
|
||||||
sharp.cache(50, 500); // 50MB, 500 items
|
|
||||||
|
|
||||||
// Ensure concurrency can be set
|
|
||||||
var defaultConcurrency = sharp.concurrency();
|
|
||||||
sharp.concurrency(16);
|
|
||||||
assert.strictEqual(16, sharp.concurrency());
|
|
||||||
sharp.concurrency(0);
|
|
||||||
assert.strictEqual(defaultConcurrency, sharp.concurrency());
|
|
||||||
|
|
||||||
async.series([
|
|
||||||
// Resize with exact crop
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpg).resize(320, 240).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();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Resize to fixed width
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpg).resize(320).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(261, info.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Resize to fixed height
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpg).resize(null, 320).toBuffer(function(err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('jpeg', info.format);
|
|
||||||
assert.strictEqual(391, info.width);
|
|
||||||
assert.strictEqual(320, info.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Identity transform
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpg).toBuffer(function(err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('jpeg', info.format);
|
|
||||||
assert.strictEqual(2725, info.width);
|
|
||||||
assert.strictEqual(2225, info.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Upscale
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpg).resize(3000).toBuffer(function(err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('jpeg', info.format);
|
|
||||||
assert.strictEqual(3000, info.width);
|
|
||||||
assert.strictEqual(2449, info.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Quality
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpg).resize(320, 240).quality(70).toBuffer(function(err, buffer70) {
|
|
||||||
if (err) throw err;
|
|
||||||
sharp(inputJpg).resize(320, 240).toBuffer(function(err, buffer80) {
|
|
||||||
if (err) throw err;
|
|
||||||
sharp(inputJpg).resize(320, 240).quality(90).toBuffer(function(err, buffer90) {
|
|
||||||
assert(buffer70.length < buffer80.length);
|
|
||||||
assert(buffer80.length < buffer90.length);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// TIFF with dimensions known to cause rounding errors
|
|
||||||
function(done) {
|
|
||||||
sharp(inputTiff).resize(240, 320).embedBlack().jpeg().toBuffer(function(err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('jpeg', info.format);
|
|
||||||
assert.strictEqual(240, info.width);
|
|
||||||
assert.strictEqual(320, info.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
function(done) {
|
|
||||||
sharp(inputTiff).resize(240, 320).jpeg().toBuffer(function(err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('jpeg', info.format);
|
|
||||||
assert.strictEqual(240, info.width);
|
|
||||||
assert.strictEqual(320, info.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Resize to max width or height considering ratio (landscape)
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpg).resize(320, 320).max().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(261, info.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Resize to max width or height considering ratio (portrait)
|
|
||||||
function(done) {
|
|
||||||
sharp(inputTiff).resize(320, 320).max().jpeg().toBuffer(function(err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('jpeg', info.format);
|
|
||||||
assert.strictEqual(243, info.width);
|
|
||||||
assert.strictEqual(320, info.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Attempt to resize to max but only provide one dimension, so should default to crop
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpg).resize(320).max().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(261, info.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Attempt to output to input, should fail
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpg).toFile(inputJpg, function(err) {
|
|
||||||
assert(!!err);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Rotate by 90 degrees, respecting output input size
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpg).rotate(90).resize(320, 240).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();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Input image has Orientation EXIF tag but do not rotate output
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpgWithExif).resize(320).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(426, info.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Input image has Orientation EXIF tag value of 8 (270 degrees), auto-rotate
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpgWithExif).rotate().resize(320).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();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Attempt to auto-rotate using image that has no EXIF
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpg).rotate().resize(320).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(261, info.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Rotate to an invalid angle, should fail
|
|
||||||
function(done) {
|
|
||||||
var fail = false;
|
|
||||||
try {
|
|
||||||
sharp(inputJpg).rotate(1);
|
|
||||||
fail = true;
|
|
||||||
} catch (e) {}
|
|
||||||
assert(!fail);
|
|
||||||
done();
|
|
||||||
},
|
|
||||||
// Do not enlarge the output if the input width is already less than the output width
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpg).resize(2800).withoutEnlargement().toBuffer(function(err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('jpeg', info.format);
|
|
||||||
assert.strictEqual(2725, info.width);
|
|
||||||
assert.strictEqual(2225, info.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Do not enlarge the output if the input height is already less than the output height
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpg).resize(null, 2300).withoutEnlargement().toBuffer(function(err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('jpeg', info.format);
|
|
||||||
assert.strictEqual(2725, info.width);
|
|
||||||
assert.strictEqual(2225, info.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Promises/A+
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpg).resize(320, 240).toBuffer().then(function(data) {
|
|
||||||
sharp(data).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();
|
|
||||||
});
|
|
||||||
}).catch(function(err) {
|
|
||||||
throw err;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Empty Buffer, should fail
|
|
||||||
function(done) {
|
|
||||||
var fail = false;
|
|
||||||
try {
|
|
||||||
sharp(new Buffer(0));
|
|
||||||
fail = true;
|
|
||||||
} catch (e) {}
|
|
||||||
assert(!fail);
|
|
||||||
done();
|
|
||||||
},
|
|
||||||
// Check colour space conversion occurs from TIFF to WebP (this used to segfault)
|
|
||||||
function(done) {
|
|
||||||
sharp(inputTiff).webp().toBuffer(function(err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual('webp', info.format);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Interpolation: nearest neighbour
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpg).resize(320, 240).interpolateWith(sharp.interpolator.nearest).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();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Interpolation: bilinear
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpg).resize(320, 240).interpolateWith(sharp.interpolator.bilinear).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();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Interpolation: bicubic
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpg).resize(320, 240).interpolateWith(sharp.interpolator.bicubic).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();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Interpolation: nohalo
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpg).resize(320, 240).interpolateWith(sharp.interpolator.nohalo).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();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Interpolation: locally bounded bicubic (LBB)
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpg).resize(320, 240).interpolateWith(sharp.interpolator.locallyBoundedBicubic).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();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Interpolation: vertex split quadratic basis spline (VSQBS)
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpg).resize(320, 240).interpolateWith(sharp.interpolator.vertexSplitQuadraticBasisSpline).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();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// File-Stream
|
|
||||||
function(done) {
|
|
||||||
var writable = fs.createWriteStream(outputJpg);
|
|
||||||
writable.on('finish', function() {
|
|
||||||
sharp(outputJpg).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);
|
|
||||||
fs.unlinkSync(outputJpg);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
sharp(inputJpg).resize(320, 240).pipe(writable);
|
|
||||||
},
|
|
||||||
// Buffer-Stream
|
|
||||||
function(done) {
|
|
||||||
var inputJpgBuffer = fs.readFileSync(inputJpg);
|
|
||||||
var writable = fs.createWriteStream(outputJpg);
|
|
||||||
writable.on('finish', function() {
|
|
||||||
sharp(outputJpg).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);
|
|
||||||
fs.unlinkSync(outputJpg);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
sharp(inputJpgBuffer).resize(320, 240).pipe(writable);
|
|
||||||
},
|
|
||||||
// Stream-File
|
|
||||||
function(done) {
|
|
||||||
var readable = fs.createReadStream(inputJpg);
|
|
||||||
var pipeline = sharp().resize(320, 240).toFile(outputJpg, function(err, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual('jpeg', info.format);
|
|
||||||
assert.strictEqual(320, info.width);
|
|
||||||
assert.strictEqual(240, info.height);
|
|
||||||
fs.unlinkSync(outputJpg);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
readable.pipe(pipeline);
|
|
||||||
},
|
|
||||||
// Stream-Buffer
|
|
||||||
function(done) {
|
|
||||||
var readable = fs.createReadStream(inputJpg);
|
|
||||||
var pipeline = sharp().resize(320, 240).toBuffer(function(err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual('jpeg', info.format);
|
|
||||||
assert.strictEqual(320, info.width);
|
|
||||||
assert.strictEqual(240, info.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
readable.pipe(pipeline);
|
|
||||||
},
|
|
||||||
// Stream-Stream
|
|
||||||
function(done) {
|
|
||||||
var readable = fs.createReadStream(inputJpg);
|
|
||||||
var writable = fs.createWriteStream(outputJpg);
|
|
||||||
writable.on('finish', function() {
|
|
||||||
sharp(outputJpg).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);
|
|
||||||
fs.unlinkSync(outputJpg);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
var pipeline = sharp().resize(320, 240);
|
|
||||||
readable.pipe(pipeline).pipe(writable);
|
|
||||||
},
|
|
||||||
// Stream-Stream error handling
|
|
||||||
function(done) {
|
|
||||||
var pipeline = sharp().resize(320, 240);
|
|
||||||
var anErrorWasEmitted = false;
|
|
||||||
pipeline.on('error', function(err) {
|
|
||||||
anErrorWasEmitted = !!err;
|
|
||||||
}).on('end', function() {
|
|
||||||
assert(anErrorWasEmitted);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
var readableButNotAnImage = fs.createReadStream(__filename);
|
|
||||||
var writable = fs.createWriteStream(outputJpg);
|
|
||||||
readableButNotAnImage.pipe(pipeline).pipe(writable);
|
|
||||||
},
|
|
||||||
// File-Stream error handling
|
|
||||||
function(done) {
|
|
||||||
var readableButNotAnImage = sharp(__filename).resize(320, 240);
|
|
||||||
var anErrorWasEmitted = false;
|
|
||||||
readableButNotAnImage.on('error', function(err) {
|
|
||||||
anErrorWasEmitted = !!err;
|
|
||||||
}).on('end', function() {
|
|
||||||
assert(anErrorWasEmitted);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
var writable = fs.createWriteStream(outputJpg);
|
|
||||||
readableButNotAnImage.pipe(writable);
|
|
||||||
},
|
|
||||||
// Crop, gravity=north
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpg).resize(320, 80).crop(sharp.gravity.north).toFile(path.join(fixturesPath, 'output.gravity-north.jpg'), function(err, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(320, info.width);
|
|
||||||
assert.strictEqual(80, info.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Crop, gravity=east
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpg).resize(80, 320).crop(sharp.gravity.east).toFile(path.join(fixturesPath, 'output.gravity-east.jpg'), function(err, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(80, info.width);
|
|
||||||
assert.strictEqual(320, info.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Crop, gravity=south
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpg).resize(320, 80).crop(sharp.gravity.south).toFile(path.join(fixturesPath, 'output.gravity-south.jpg'), function(err, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(320, info.width);
|
|
||||||
assert.strictEqual(80, info.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Crop, gravity=west
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpg).resize(80, 320).crop(sharp.gravity.west).toFile(path.join(fixturesPath, 'output.gravity-west.jpg'), function(err, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(80, info.width);
|
|
||||||
assert.strictEqual(320, info.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Crop, gravity=center
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpg).resize(320, 80).crop(sharp.gravity.center).toFile(path.join(fixturesPath, 'output.gravity-center.jpg'), function(err, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(320, info.width);
|
|
||||||
assert.strictEqual(80, info.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Crop, gravity=centre
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpg).resize(80, 320).crop(sharp.gravity.centre).toFile(path.join(fixturesPath, 'output.gravity-centre.jpg'), function(err, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(80, info.width);
|
|
||||||
assert.strictEqual(320, info.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Keeps Metadata after a resize
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpgWithExif).resize(320, 240).withMetadata().toBuffer(function(err, buffer) {
|
|
||||||
if (err) throw err;
|
|
||||||
sharp(buffer).metadata(function(err, metadata) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(8, metadata.orientation);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Keeps Metadata after a resize
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpgWithExif).resize(320, 240).withMetadata(false).toBuffer(function(err, buffer) {
|
|
||||||
if (err) throw err;
|
|
||||||
sharp(buffer).metadata(function(err, metadata) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual('undefined', typeof metadata.orientation);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Output filename without extension should mirror input format
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpg).resize(320, 80).toFile(path.join(fixturesPath, 'output.zoinks'), function(err, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual('jpeg', info.format);
|
|
||||||
assert.strictEqual(320, info.width);
|
|
||||||
assert.strictEqual(80, info.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
function(done) {
|
|
||||||
sharp(inputPng).resize(320, 80).toFile(path.join(fixturesPath, 'output.zoinks'), function(err, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual('png', info.format);
|
|
||||||
assert.strictEqual(320, info.width);
|
|
||||||
assert.strictEqual(80, info.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
function(done) {
|
|
||||||
sharp(inputWebP).resize(320, 80).toFile(path.join(fixturesPath, 'output.zoinks'), function(err, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual('webp', info.format);
|
|
||||||
assert.strictEqual(320, info.width);
|
|
||||||
assert.strictEqual(80, info.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
function(done) {
|
|
||||||
sharp(inputTiff).resize(320, 80).toFile(path.join(fixturesPath, 'output.zoinks'), function(err, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual('tiff', info.format);
|
|
||||||
assert.strictEqual(320, info.width);
|
|
||||||
assert.strictEqual(80, info.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
function(done) {
|
|
||||||
sharp(inputGif).resize(320, 80).toFile(path.join(fixturesPath, 'output.zoinks'), function(err, info) {
|
|
||||||
assert(!!err);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Metadata - JPEG
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpg).metadata(function(err, metadata) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual('jpeg', metadata.format);
|
|
||||||
assert.strictEqual(2725, metadata.width);
|
|
||||||
assert.strictEqual(2225, metadata.height);
|
|
||||||
assert.strictEqual('srgb', metadata.space);
|
|
||||||
assert.strictEqual(3, metadata.channels);
|
|
||||||
assert.strictEqual('undefined', typeof metadata.orientation);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Metadata - JPEG with EXIF
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpgWithExif).metadata(function(err, metadata) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual('jpeg', metadata.format);
|
|
||||||
assert.strictEqual(450, metadata.width);
|
|
||||||
assert.strictEqual(600, metadata.height);
|
|
||||||
assert.strictEqual('srgb', metadata.space);
|
|
||||||
assert.strictEqual(3, metadata.channels);
|
|
||||||
assert.strictEqual(8, metadata.orientation);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Metadata - TIFF
|
|
||||||
function(done) {
|
|
||||||
sharp(inputTiff).metadata(function(err, metadata) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual('tiff', metadata.format);
|
|
||||||
assert.strictEqual(2464, metadata.width);
|
|
||||||
assert.strictEqual(3248, metadata.height);
|
|
||||||
assert.strictEqual('b-w', metadata.space);
|
|
||||||
assert.strictEqual(1, metadata.channels);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Metadata - PNG
|
|
||||||
function(done) {
|
|
||||||
sharp(inputPng).metadata(function(err, metadata) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual('png', metadata.format);
|
|
||||||
assert.strictEqual(2809, metadata.width);
|
|
||||||
assert.strictEqual(2074, metadata.height);
|
|
||||||
assert.strictEqual('b-w', metadata.space);
|
|
||||||
assert.strictEqual(1, metadata.channels);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Metadata - WebP
|
|
||||||
function(done) {
|
|
||||||
sharp(inputWebP).metadata(function(err, metadata) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual('webp', metadata.format);
|
|
||||||
assert.strictEqual(1024, metadata.width);
|
|
||||||
assert.strictEqual(772, metadata.height);
|
|
||||||
assert.strictEqual('srgb', metadata.space);
|
|
||||||
assert.strictEqual(3, metadata.channels);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Metadata - GIF (via libmagick)
|
|
||||||
function(done) {
|
|
||||||
sharp(inputGif).metadata(function(err, metadata) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual('magick', metadata.format);
|
|
||||||
assert.strictEqual(800, metadata.width);
|
|
||||||
assert.strictEqual(533, metadata.height);
|
|
||||||
assert.strictEqual(3, metadata.channels);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Metadata - Promise
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpg).metadata().then(function(metadata) {
|
|
||||||
assert.strictEqual('jpeg', metadata.format);
|
|
||||||
assert.strictEqual(2725, metadata.width);
|
|
||||||
assert.strictEqual(2225, metadata.height);
|
|
||||||
assert.strictEqual('srgb', metadata.space);
|
|
||||||
assert.strictEqual(3, metadata.channels);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Metadata - Stream
|
|
||||||
function(done) {
|
|
||||||
var readable = fs.createReadStream(inputJpg);
|
|
||||||
var pipeline = sharp().metadata(function(err, metadata) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual('jpeg', metadata.format);
|
|
||||||
assert.strictEqual(2725, metadata.width);
|
|
||||||
assert.strictEqual(2225, metadata.height);
|
|
||||||
assert.strictEqual('srgb', metadata.space);
|
|
||||||
assert.strictEqual(3, metadata.channels);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
readable.pipe(pipeline);
|
|
||||||
},
|
|
||||||
// Get metadata then resize to half width
|
|
||||||
function(done) {
|
|
||||||
var image = sharp(inputJpg);
|
|
||||||
image.metadata(function(err, metadata) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual('jpeg', metadata.format);
|
|
||||||
assert.strictEqual(2725, metadata.width);
|
|
||||||
assert.strictEqual(2225, metadata.height);
|
|
||||||
assert.strictEqual('srgb', metadata.space);
|
|
||||||
assert.strictEqual(3, metadata.channels);
|
|
||||||
image.resize(metadata.width / 2).toBuffer(function(err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual(1362, info.width);
|
|
||||||
assert.strictEqual(1112, info.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Gamma correction
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpgWithGammaHoliness).resize(129, 111).toFile(path.join(fixturesPath, 'output.gamma-0.0.jpg'), function(err) {
|
|
||||||
if (err) throw err;
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpgWithGammaHoliness).resize(129, 111).gamma().toFile(path.join(fixturesPath, 'output.gamma-2.2.jpg'), function(err) {
|
|
||||||
if (err) throw err;
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpgWithGammaHoliness).resize(129, 111).gamma(3).toFile(path.join(fixturesPath, 'output.gamma-3.0.jpg'), function(err) {
|
|
||||||
if (err) throw err;
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Verify internal counters
|
|
||||||
function(done) {
|
|
||||||
var counters = sharp.counters();
|
|
||||||
assert.strictEqual(0, counters.queue);
|
|
||||||
assert.strictEqual(0, counters.process);
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
]);
|
|
||||||