Compare commits
158 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
515b4656e6 | ||
|
|
34c96ff925 | ||
|
|
b8a04cc4ef | ||
|
|
ddc3f6e9c6 | ||
|
|
3699e61c20 | ||
|
|
2820218609 | ||
|
|
eb3e739f7b | ||
|
|
87f6e83988 | ||
|
|
5728efd32b | ||
|
|
bac367b005 | ||
|
|
0d89131f66 | ||
|
|
8380be4be3 | ||
|
|
d0f51363bf | ||
|
|
15160d3b61 | ||
|
|
c5efb77bad | ||
|
|
b877751b2d | ||
|
|
40db482fd8 | ||
|
|
98554e919c | ||
|
|
017bf1e905 | ||
|
|
6498fc3a9e | ||
|
|
8ba71c94f4 | ||
|
|
f2f3eb76e1 | ||
|
|
5fe945fca8 | ||
|
|
8ef0851a49 | ||
|
|
e45956db6c | ||
|
|
7cc9f7e2e0 | ||
|
|
df3903532d | ||
|
|
46456c9a2a | ||
|
|
e98f2fc013 | ||
|
|
7df7a505ee | ||
|
|
d40bdcc6ac | ||
|
|
1cce56b024 | ||
|
|
2126f9afc1 | ||
|
|
41420eedcf | ||
|
|
1b6ab19b6d | ||
|
|
fbe5c18762 | ||
|
|
8acb0ed5d0 | ||
|
|
430e04d894 | ||
|
|
012edb4379 | ||
|
|
11ead360a9 | ||
|
|
84a059d7e3 | ||
|
|
b1b070ae5c | ||
|
|
4eb910fec9 | ||
|
|
f0a9d82bf7 | ||
|
|
6d20a1ca81 | ||
|
|
eca2787213 | ||
|
|
8d146accf3 | ||
|
|
b635d015cd | ||
|
|
261a90c8a2 | ||
|
|
4ae22b3425 | ||
|
|
c9aa9c7723 | ||
|
|
5e0b5969da | ||
|
|
ae6d5e69b1 |
20
.gitignore
vendored
@@ -1,15 +1,9 @@
|
|||||||
lib-cov
|
|
||||||
*.seed
|
|
||||||
*.log
|
|
||||||
*.csv
|
|
||||||
*.dat
|
|
||||||
*.out
|
|
||||||
*.pid
|
|
||||||
*.gz
|
|
||||||
|
|
||||||
pids
|
|
||||||
logs
|
|
||||||
results
|
|
||||||
build
|
build
|
||||||
node_modules
|
node_modules
|
||||||
tests/fixtures/output.*
|
coverage
|
||||||
|
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": 11,
|
||||||
|
"globals": {
|
||||||
|
"describe": true,
|
||||||
|
"it": true
|
||||||
|
}
|
||||||
|
}
|
||||||
20
.npmignore
@@ -1,18 +1,8 @@
|
|||||||
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
|
||||||
|
|||||||
15
.travis.yml
@@ -3,15 +3,6 @@ node_js:
|
|||||||
- "0.10"
|
- "0.10"
|
||||||
- "0.11"
|
- "0.11"
|
||||||
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 libfftw3-dev 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=no --without-orc --without-python
|
|
||||||
- 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).
|
||||||
592
README.md
@@ -3,21 +3,27 @@
|
|||||||
* [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)
|
||||||
* [Licence](https://github.com/lovell/sharp#licence)
|
* [Licence](https://github.com/lovell/sharp#licence)
|
||||||
|
|
||||||
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. Everything remains non-blocking thanks to _libuv_ and Promises/A+ are supported.
|
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.
|
||||||
|
|
||||||
This module supports reading and writing images of JPEG, PNG and WebP to and from both Buffer objects and the filesystem. It also supports reading images of many other types from the filesystem via libmagick++ or libgraphicsmagick++ if present.
|
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.
|
||||||
|
|
||||||
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/).
|
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.
|
||||||
|
|
||||||
This module is powered by the blazingly fast [libvips](https://github.com/jcupitt/libvips) image processing library, originally created in 1989 at Birkbeck College and currently maintained by John Cupitt.
|
This module is powered by the blazingly fast [libvips](https://github.com/jcupitt/libvips) image processing library, originally created in 1989 at Birkbeck College and currently maintained by [John Cupitt](https://github.com/jcupitt).
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@@ -26,47 +32,61 @@ This module is powered by the blazingly fast [libvips](https://github.com/jcupit
|
|||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
* Node.js v0.10+
|
* Node.js v0.10+
|
||||||
* [libvips](https://github.com/jcupitt/libvips) v7.38.5+
|
* [libvips](https://github.com/jcupitt/libvips) v7.40.0+ (7.42.0+ recommended)
|
||||||
|
|
||||||
_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
|
||||||
|
|
||||||
|
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`.
|
||||||
|
|
||||||
|
### 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
|
||||||
|
|
||||||
|
A missing or incorrectly configured _Xcode Command Line Tools_ installation [can lead](https://github.com/lovell/sharp/issues/80) to a `library not found for -ljpeg` error. If so, please try:
|
||||||
|
|
||||||
|
xcode-select --install
|
||||||
|
|
||||||
The _gettext_ dependency of _libvips_ [can lead](https://github.com/lovell/sharp/issues/9) to a `library not found for -lintl` error. If so, please try:
|
The _gettext_ dependency of _libvips_ [can lead](https://github.com/lovell/sharp/issues/9) to a `library not found for -lintl` error. If so, please try:
|
||||||
|
|
||||||
brew link gettext --force
|
brew link gettext --force
|
||||||
|
|
||||||
### Install libvips on Ubuntu Linux
|
### Heroku
|
||||||
|
|
||||||
#### Ubuntu 14.x
|
[Alessandro Tagliapietra](https://github.com/alex88) maintains an [Heroku buildpack for libvips](https://github.com/alex88/heroku-buildpack-vips) and its dependencies.
|
||||||
|
|
||||||
sudo apt-get install libvips-dev
|
### Docker
|
||||||
|
|
||||||
#### Ubuntu 13.x
|
[Marc Bachmann](https://github.com/marcbachmann) maintains a [Dockerfile for libvips](https://github.com/marcbachmann/dockerfile-libvips).
|
||||||
|
|
||||||
Compiling from source is recommended:
|
docker pull marcbachmann/libvips
|
||||||
|
|
||||||
sudo apt-get install automake build-essential git gobject-introspection gtk-doc-tools libfftw3-dev libglib2.0-dev libjpeg-turbo8-dev libpng12-dev libwebp-dev libtiff5-dev libexif-dev libxml2-dev swig
|
### Build tools
|
||||||
git clone https://github.com/jcupitt/libvips.git
|
|
||||||
cd libvips
|
|
||||||
git checkout 7.38
|
|
||||||
./bootstrap.sh
|
|
||||||
./configure --enable-debug=no --enable-cxx=no --without-python --without-orc
|
|
||||||
make
|
|
||||||
sudo make install
|
|
||||||
sudo ldconfig
|
|
||||||
|
|
||||||
#### Ubuntu 12.x
|
* [gulp-responsive](https://www.npmjs.com/package/gulp-responsive)
|
||||||
|
* [gulp-sharp](https://www.npmjs.com/package/gulp-sharp)
|
||||||
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:
|
* [grunt-sharp](https://www.npmjs.com/package/grunt-sharp)
|
||||||
|
|
||||||
sudo add-apt-repository ppa:lyrasis/precise-backports
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install libtiff4-dev
|
|
||||||
|
|
||||||
Then follow Ubuntu 13.x instructions.
|
|
||||||
|
|
||||||
## Usage examples
|
## Usage examples
|
||||||
|
|
||||||
@@ -85,198 +105,471 @@ sharp('input.jpg').resize(300, 200).toFile('output.jpg', function(err) {
|
|||||||
```
|
```
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
sharp('input.jpg').rotate().resize(null, 200).progressive().toBuffer(function(err, outputBuffer) {
|
var transformer = sharp().resize(300, 200).crop(sharp.gravity.north);
|
||||||
if (err) {
|
readableStream.pipe(transformer).pipe(writableStream);
|
||||||
throw err;
|
// Read image data from readableStream, resize and write image data to writableStream
|
||||||
}
|
```
|
||||||
// outputBuffer contains 200px high progressive JPEG image data, auto-rotated using EXIF Orientation tag
|
|
||||||
|
```javascript
|
||||||
|
var image = sharp(inputJpg);
|
||||||
|
image.metadata(function(err, metadata) {
|
||||||
|
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
|
||||||
|
});
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
sharp('input.png').rotate(180).resize(300).sharpen().quality(90).webp().then(function(outputBuffer) {
|
var pipeline = sharp()
|
||||||
// outputBuffer contains 300px wide, upside down, sharpened, 90% quality WebP image data
|
.rotate()
|
||||||
});
|
.resize(null, 200)
|
||||||
|
.progressive()
|
||||||
|
.toBuffer(function(err, outputBuffer, info) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
// outputBuffer contains 200px high progressive JPEG image data,
|
||||||
|
// auto-rotated using EXIF Orientation tag
|
||||||
|
// info.width and info.height contain the dimensions of the resized image
|
||||||
|
});
|
||||||
|
readableStream.pipe(pipeline);
|
||||||
```
|
```
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
sharp(inputBuffer).resize(200, 300).embedWhite().toFile('output.tiff').then(function() {
|
sharp('input.png')
|
||||||
// output.tiff is a 200 pixels wide and 300 pixels high image containing a scaled
|
.rotate(180)
|
||||||
// version, embedded on a white canvas, of the image data in buffer
|
.resize(300)
|
||||||
});
|
.flatten()
|
||||||
|
.background('#ff6600')
|
||||||
|
.sharpen()
|
||||||
|
.withMetadata()
|
||||||
|
.quality(90)
|
||||||
|
.webp()
|
||||||
|
.toBuffer()
|
||||||
|
.then(function(outputBuffer) {
|
||||||
|
// outputBuffer contains upside down, 300px wide, alpha channel flattened
|
||||||
|
// onto orange background, sharpened, with metadata, 90% quality WebP image
|
||||||
|
// data
|
||||||
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
sharp('input.gif').resize(200, 300).embedBlack().webp(function(err, outputBuffer) {
|
http.createServer(function(request, response) {
|
||||||
if (err) {
|
response.writeHead(200, {'Content-Type': 'image/webp'});
|
||||||
throw err;
|
sharp('input.jpg').rotate().resize(200).webp().pipe(response);
|
||||||
}
|
}).listen(8000);
|
||||||
// outputBuffer contains WebP image data of a 200 pixels wide and 300 pixels high
|
// Create HTTP server that always returns auto-rotated 'input.jpg',
|
||||||
// containing a scaled version, embedded on a black canvas, of input.gif
|
// resized to 200 pixels wide, in WebP format
|
||||||
});
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
sharp(inputBuffer).resize(200, 200).max().jpeg().then(function(outputBuffer) {
|
sharp(input)
|
||||||
// outputBuffer contains JPEG image data no wider than 200 pixels and no higher
|
.extract(top, left, width, height)
|
||||||
// than 200 pixels regardless of the inputBuffer image dimensions
|
.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
|
||||||
|
sharp(inputBuffer)
|
||||||
|
.resize(200, 300)
|
||||||
|
.interpolateWith(sharp.interpolator.nohalo)
|
||||||
|
.background('white')
|
||||||
|
.embed()
|
||||||
|
.toFile('output.tiff')
|
||||||
|
.then(function() {
|
||||||
|
// output.tiff is a 200 pixels wide and 300 pixels high image
|
||||||
|
// containing a bicubic scaled version, embedded on a white canvas,
|
||||||
|
// of the image data in inputBuffer
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
sharp('input.gif')
|
||||||
|
.resize(200, 300)
|
||||||
|
.background({r: 0, g: 0, b: 0, a: 0})
|
||||||
|
.embed()
|
||||||
|
.webp()
|
||||||
|
.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()
|
||||||
|
.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
|
||||||
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
## API
|
## API
|
||||||
|
|
||||||
### sharp(input)
|
### Input methods
|
||||||
|
|
||||||
Constructor to which further methods are chained. `input` can be one of:
|
#### sharp([input])
|
||||||
|
|
||||||
* Buffer containing JPEG, PNG or WebP image data, or
|
Constructor to which further methods are chained. `input`, if present, can be one of:
|
||||||
|
|
||||||
|
* Buffer containing JPEG, PNG, WebP 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.
|
||||||
|
|
||||||
### resize(width, [height])
|
The object returned implements the [stream.Duplex](http://nodejs.org/api/stream.html#stream_class_stream_duplex) class.
|
||||||
|
|
||||||
|
JPEG, PNG, WebP 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.
|
||||||
|
|
||||||
|
#### metadata([callback])
|
||||||
|
|
||||||
|
Fast access to image metadata without decoding any compressed image data.
|
||||||
|
|
||||||
|
`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`)
|
||||||
|
* `width`: Number of pixels wide
|
||||||
|
* `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#L522)
|
||||||
|
* `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
|
||||||
|
|
||||||
|
A Promises/A+ promise is returned when `callback` is not provided.
|
||||||
|
|
||||||
|
#### sequentialRead()
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
#### 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.
|
||||||
|
|
||||||
### crop()
|
#### 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 the resized image to the exact size specified, the default behaviour.
|
Crop the resized image to the exact size specified, the default behaviour.
|
||||||
|
|
||||||
### max()
|
`gravity`, if present, is an attribute of the `sharp.gravity` Object e.g. `sharp.gravity.north`.
|
||||||
|
|
||||||
Preserving aspect ratio, resize the image to the maximum width or height specified.
|
Possible values are `north`, `east`, `south`, `west`, `center` and `centre`. The default gravity is `center`/`centre`.
|
||||||
|
|
||||||
|
#### max()
|
||||||
|
|
||||||
|
Preserving aspect ratio, resize the image to the maximum `width` or `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()
|
#### background(rgba)
|
||||||
|
|
||||||
Embed the resized image on a white background of the exact size specified.
|
Set the background for the `embed` and `flatten` operations.
|
||||||
|
|
||||||
### embedBlack()
|
`rgba` is parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
|
||||||
|
|
||||||
Embed the resized image on a black background of the exact size specified.
|
The alpha value is a float between `0` (transparent) and `1` (opaque).
|
||||||
|
|
||||||
### rotate([angle])
|
The default background is `{r: 0, g: 0, b: 0, a: 1}`, black without transparency.
|
||||||
|
|
||||||
Rotate the output image by either an explicit angle or auto-orient based on the EXIF `Orientation` tag. Mirroring is not supported.
|
#### 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 the output image by either an explicit angle or auto-orient based on the EXIF `Orientation` tag.
|
||||||
|
|
||||||
`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.
|
Use this method without `angle` to determine the angle from EXIF data. Mirroring is supported and may infer the use of a `flip` operation.
|
||||||
|
|
||||||
### withoutEnlargement()
|
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()
|
||||||
|
|
||||||
Do not enlarge the output image if the input image width *or* height are already less than the required dimensions.
|
Do not enlarge the output image if the input image width *or* height are already less than the required dimensions.
|
||||||
|
|
||||||
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%.
|
||||||
|
|
||||||
### progressive()
|
When a `sigma` is provided, performs a slower, more accurate Gaussian blur. This typically reduces performance by 25%.
|
||||||
|
|
||||||
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.
|
* `sigma`, if present, is a Number between 0.3 and 1000 representing the approximate blur radius in pixels.
|
||||||
|
|
||||||
### quality(quality)
|
#### 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)
|
||||||
|
|
||||||
|
Use the given interpolator for image resizing, where `interpolator` is an attribute of the `sharp.interpolator` Object e.g. `sharp.interpolator.bicubic`.
|
||||||
|
|
||||||
|
Possible interpolators, in order of performance, are:
|
||||||
|
|
||||||
|
* `nearest`: Use [nearest neighbour interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation), suitable for image enlargement only.
|
||||||
|
* `bilinear`: Use [bilinear interpolation](http://en.wikipedia.org/wiki/Bilinear_interpolation), the default and fastest image reduction interpolation.
|
||||||
|
* `bicubic`: Use [bicubic interpolation](http://en.wikipedia.org/wiki/Bicubic_interpolation), which typically reduces performance by 5%.
|
||||||
|
* `vertexSplitQuadraticBasisSpline`: Use [VSQBS interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/vsqbs.cpp#L48), which prevents "staircasing" and typically reduces performance by 5%.
|
||||||
|
* `locallyBoundedBicubic`: Use [LBB interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/lbb.cpp#L100), which prevents some "[acutance](http://en.wikipedia.org/wiki/Acutance)" and typically reduces performance by a factor of 2.
|
||||||
|
* `nohalo`: Use [Nohalo interpolation](http://eprints.soton.ac.uk/268086/), which prevents acutance and typically reduces performance by a factor of 3.
|
||||||
|
|
||||||
|
#### gamma([gamma])
|
||||||
|
|
||||||
|
Apply a gamma correction by reducing the encoding (darken) pre-resize at a factor of `1/gamma` then increasing the encoding (brighten) post-resize at a factor of `gamma`.
|
||||||
|
|
||||||
|
`gamma`, if present, is a Number betweem 1 and 3. The default value is `2.2`, a suitable approximation for sRGB images.
|
||||||
|
|
||||||
|
This can improve the perceived brightness of a resized image in non-linear colour spaces.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
### Output options
|
||||||
|
|
||||||
|
#### jpeg()
|
||||||
|
|
||||||
|
Use JPEG format for the output image.
|
||||||
|
|
||||||
|
#### png()
|
||||||
|
|
||||||
|
Use PNG format for the output image.
|
||||||
|
|
||||||
|
#### webp()
|
||||||
|
|
||||||
|
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.\].
|
||||||
|
|
||||||
|
#### 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`.
|
||||||
|
|
||||||
`quality` is a Number between 1 and 100.
|
`quality` is a Number between 1 and 100.
|
||||||
|
|
||||||
### compressionLevel(compressionLevel)
|
#### progressive()
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
#### 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.
|
||||||
|
|
||||||
### sequentialRead()
|
#### withoutAdaptiveFiltering()
|
||||||
|
|
||||||
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.
|
_Requires libvips 7.42.0+_
|
||||||
|
|
||||||
### toFile(filename, [callback])
|
An advanced setting to disable adaptive row filtering for the lossless PNG output format.
|
||||||
|
|
||||||
|
### Output methods
|
||||||
|
|
||||||
|
#### 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 and TIFF supported.
|
||||||
|
|
||||||
`callback`, if present, is called with a single argument `(err)` containing an error message, if any.
|
`callback`, if present, is called with two arguments `(err, info)` where:
|
||||||
|
|
||||||
|
* `err` contains an error message, if any.
|
||||||
|
* `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.
|
||||||
|
|
||||||
### toBuffer([callback])
|
#### toBuffer([callback])
|
||||||
|
|
||||||
Write image data to a Buffer, the format of which will match the input image. JPEG, PNG and WebP are supported.
|
Write image data to a Buffer, the format of which will match the input image by default. JPEG, PNG and WebP are supported.
|
||||||
|
|
||||||
`callback`, if present, gets two arguments `(err, buffer)` where `err` is an error message, if any, and `buffer` is the resultant image data.
|
`callback`, if present, gets three arguments `(err, buffer, info)` where:
|
||||||
|
|
||||||
|
* `err` is an error message, if any.
|
||||||
|
* `buffer` is the output image data.
|
||||||
|
* `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.
|
||||||
|
|
||||||
### jpeg([callback])
|
### Utility methods
|
||||||
|
|
||||||
Write JPEG image data to a Buffer.
|
#### sharp.cache([memory], [items])
|
||||||
|
|
||||||
`callback`, if present, gets two arguments `(err, buffer)` where `err` is an error message, if any, and `buffer` is the resultant JPEG image data.
|
If `memory` or `items` are provided, set the limits of _libvips'_ operation cache.
|
||||||
|
|
||||||
A Promises/A+ promise is returned when `callback` is not provided.
|
* `memory` is the maximum memory in MB to use for this cache, with a default value of 100
|
||||||
|
* `items` is the maximum number of operations to cache, with a default value of 500
|
||||||
### png(callback)
|
|
||||||
|
|
||||||
Write PNG image data to a Buffer.
|
|
||||||
|
|
||||||
`callback`, if present, gets two arguments `(err, buffer)` where `err` is an error message, if any, and `buffer` is the resultant PNG image data.
|
|
||||||
|
|
||||||
A Promises/A+ promise is returned when `callback` is not provided.
|
|
||||||
|
|
||||||
### webp([callback])
|
|
||||||
|
|
||||||
Write WebP image data to a Buffer.
|
|
||||||
|
|
||||||
`callback`, if present, gets two arguments `(err, buffer)` where `err` is an error message, if any, and `buffer` is the resultant WebP image data.
|
|
||||||
|
|
||||||
A Promises/A+ promise is returned when `callback` is not provided.
|
|
||||||
|
|
||||||
### sharp.cache([limit])
|
|
||||||
|
|
||||||
If `limit` is provided, set the (soft) limit of _libvips_ working/cache memory to this value in MB. The default value is 100.
|
|
||||||
|
|
||||||
This method always returns cache statistics, useful for determining how much working memory is required for a particular task.
|
This method always returns cache statistics, useful for determining how much working memory is required for a particular task.
|
||||||
|
|
||||||
Warnings such as _Application transferred too many scanlines_ are a good indicator you've set this value too low.
|
```javascript
|
||||||
|
var stats = sharp.cache(); // { current: 75, high: 99, memory: 100, items: 500 }
|
||||||
|
sharp.cache(200); // { current: 75, high: 99, memory: 200, items: 500 }
|
||||||
|
sharp.cache(50, 200); // { current: 49, high: 99, memory: 50, items: 200}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### sharp.concurrency([threads])
|
||||||
|
|
||||||
|
`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.
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
var stats = sharp.cache(); // { current: 98, high: 115, limit: 100 }
|
var threads = sharp.concurrency(); // 4
|
||||||
sharp.cache(200); // { current: 98, high: 115, limit: 200 }
|
sharp.concurrency(2); // 2
|
||||||
sharp.cache(50); // { current: 49, high: 115, limit: 50 }
|
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()
|
||||||
|
|
||||||
|
Provides access to internal task counters.
|
||||||
|
|
||||||
|
* `queue` is the number of tasks this module has queued waiting for _libuv_ to provide a worker thread from its pool.
|
||||||
|
* `process` is the number of resize tasks currently being processed.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
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
|
||||||
|
|
||||||
[](https://travis-ci.org/lovell/sharp)
|
### Functional tests
|
||||||
|
|
||||||
npm test
|
#### Coverage
|
||||||
|
|
||||||
Running the tests requires both ImageMagick and GraphicsMagick plus one of either libmagick++-dev or libgraphicsmagick++.
|
[](https://coveralls.io/r/lovell/sharp?branch=master)
|
||||||
|
|
||||||
brew install imagemagick
|
#### Ubuntu 12.04
|
||||||
brew install graphicsmagick
|
|
||||||
|
|
||||||
sudo apt-get install imagemagick graphicsmagick libmagick++-dev
|
[](https://travis-ci.org/lovell/sharp)
|
||||||
|
|
||||||
|
#### Centos 6.5
|
||||||
|
|
||||||
|
[](https://snap-ci.com/lovell/sharp/branch/master)
|
||||||
|
|
||||||
|
### Benchmark tests
|
||||||
|
|
||||||
|
```
|
||||||
|
cd sharp/test/bench
|
||||||
|
npm install
|
||||||
|
npm test
|
||||||
|
```
|
||||||
|
|
||||||
|
Requires both _ImageMagick_ and _GraphicsMagick_:
|
||||||
|
|
||||||
|
```
|
||||||
|
brew install imagemagick
|
||||||
|
brew install graphicsmagick
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo apt-get install -qq imagemagick graphicsmagick libmagickcore-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo yum install ImageMagick
|
||||||
|
sudo yum install -y http://download.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm
|
||||||
|
sudo yum install -y --enablerepo=epel GraphicsMagick
|
||||||
|
```
|
||||||
|
|
||||||
## Performance
|
## Performance
|
||||||
|
|
||||||
### 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
|
||||||
|
|
||||||
@@ -286,25 +579,44 @@ 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
|
||||||
|
|
||||||
|
This module would never have been possible without the help and code contributions of the following people:
|
||||||
|
|
||||||
|
* [John Cupitt](https://github.com/jcupitt)
|
||||||
|
* [Pierre Inglebert](https://github.com/pierreinglebert)
|
||||||
|
* [Jonathan Ong](https://github.com/jonathanong)
|
||||||
|
* [Chanon Sajjamanochai](https://github.com/chanon)
|
||||||
|
* [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)
|
||||||
|
|
||||||
|
Thank you!
|
||||||
|
|
||||||
## Licence
|
## Licence
|
||||||
|
|
||||||
Copyright 2013, 2014 Lovell Fuller and Pierre Inglebert
|
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.
|
||||||
|
|||||||
38
binding.gyp
@@ -1,20 +1,38 @@
|
|||||||
{
|
{
|
||||||
'targets': [{
|
'targets': [{
|
||||||
'target_name': 'sharp',
|
'target_name': 'sharp',
|
||||||
'sources': ['src/sharp.cc'],
|
'sources': [
|
||||||
|
'src/common.cc',
|
||||||
|
'src/utilities.cc',
|
||||||
|
'src/metadata.cc',
|
||||||
|
'src/resize.cc',
|
||||||
|
'src/sharp.cc'
|
||||||
|
],
|
||||||
|
'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': [
|
'libraries': [
|
||||||
'<!@(PKG_CONFIG_PATH="/usr/local/Library/ENV/pkgconfig/10.8:/usr/local/lib/pkgconfig:/usr/lib/pkgconfig" pkg-config --libs vips)'
|
'<!(PKG_CONFIG_PATH="<(PKG_CONFIG_PATH)" pkg-config --libs vips)'
|
||||||
],
|
],
|
||||||
'include_dirs': [
|
'include_dirs': [
|
||||||
'/usr/local/include/glib-2.0',
|
'<!(PKG_CONFIG_PATH="<(PKG_CONFIG_PATH)" pkg-config --cflags vips glib-2.0)',
|
||||||
'/usr/local/lib/glib-2.0/include',
|
|
||||||
'/usr/include/glib-2.0',
|
|
||||||
'/usr/lib/glib-2.0/include',
|
|
||||||
'/usr/lib/x86_64-linux-gnu/glib-2.0/include',
|
|
||||||
'/usr/lib/i386-linux-gnu/glib-2.0/include',
|
|
||||||
'<!(node -e "require(\'nan\')")'
|
'<!(node -e "require(\'nan\')")'
|
||||||
],
|
],
|
||||||
'cflags': ['-fexceptions', '-pedantic', '-Wall', '-O3'],
|
'cflags_cc': [
|
||||||
'cflags_cc': ['-fexceptions', '-pedantic', '-Wall', '-O3']
|
'-std=c++0x',
|
||||||
|
'-fexceptions',
|
||||||
|
'-Wall',
|
||||||
|
'-O3'
|
||||||
|
],
|
||||||
|
'xcode_settings': {
|
||||||
|
'OTHER_CPLUSPLUSFLAGS': [
|
||||||
|
'-std=c++11',
|
||||||
|
'-stdlib=libc++',
|
||||||
|
'-fexceptions',
|
||||||
|
'-Wall',
|
||||||
|
'-O3'
|
||||||
|
],
|
||||||
|
'MACOSX_DEPLOYMENT_TARGET': '10.7'
|
||||||
|
}
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
icc/USWebCoatedSWOP.icc
Normal file
BIN
icc/sRGB_IEC61966-2-1_black_scaled.icc
Normal file
538
index.js
@@ -1,53 +1,174 @@
|
|||||||
/*jslint node: true */
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var Promise = require('bluebird');
|
var path = require('path');
|
||||||
|
var util = require('util');
|
||||||
|
var stream = require('stream');
|
||||||
|
|
||||||
|
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)) {
|
||||||
return new Sharp(input);
|
return new Sharp(input);
|
||||||
}
|
}
|
||||||
|
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: 'c',
|
||||||
|
gravity: 0,
|
||||||
angle: 0,
|
angle: 0,
|
||||||
|
rotateBeforePreExtract: false,
|
||||||
|
flip: false,
|
||||||
|
flop: false,
|
||||||
withoutEnlargement: false,
|
withoutEnlargement: false,
|
||||||
sharpen: false,
|
interpolator: 'bilinear',
|
||||||
|
// operations
|
||||||
|
background: [0, 0, 0, 255],
|
||||||
|
flatten: false,
|
||||||
|
blurSigma: 0,
|
||||||
|
sharpenRadius: 0,
|
||||||
|
sharpenFlat: 1,
|
||||||
|
sharpenJagged: 2,
|
||||||
|
gamma: 0,
|
||||||
|
greyscale: false,
|
||||||
|
// output options
|
||||||
|
output: '__input',
|
||||||
progressive: false,
|
progressive: false,
|
||||||
sequentialRead: false,
|
quality: 80,
|
||||||
quality: 80,
|
compressionLevel: 6,
|
||||||
compressionLevel: 6,
|
withoutAdaptiveFiltering: false,
|
||||||
output: '__jpeg'
|
streamOut: false,
|
||||||
|
withMetadata: false
|
||||||
};
|
};
|
||||||
if (typeof input === 'string') {
|
if (typeof input === 'string') {
|
||||||
|
// 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) {
|
||||||
if (input.length > 0) {
|
// input=buffer
|
||||||
|
if (
|
||||||
|
(input.length > 3) &&
|
||||||
|
// JPEG
|
||||||
|
(input[0] === 0xFF && input[1] === 0xD8) ||
|
||||||
|
// PNG
|
||||||
|
(input[0] === 0x89 && input[1] === 0x50) ||
|
||||||
|
// WebP
|
||||||
|
(input[0] === 0x52 && input[1] === 0x49) ||
|
||||||
|
// TIFF
|
||||||
|
(input[0] === 0x4D && input[1] === 0x4D && input[2] === 0x00 && (input[3] === 0x2A || input[3] === 0x2B)) ||
|
||||||
|
(input[0] === 0x49 && input[1] === 0x49 && (input[2] === 0x2A || input[2] === 0x2B) && input[3] === 0x00)
|
||||||
|
) {
|
||||||
this.options.bufferIn = input;
|
this.options.bufferIn = input;
|
||||||
} else {
|
} else {
|
||||||
throw 'Buffer is empty';
|
throw new Error('Buffer contains an unsupported image format. JPEG, PNG, WebP and TIFF are currently supported.');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw 'Unsupported input ' + typeof input;
|
// input=stream
|
||||||
|
this.options.streamIn = true;
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
module.exports = Sharp;
|
module.exports = Sharp;
|
||||||
|
util.inherits(Sharp, stream.Duplex);
|
||||||
|
|
||||||
Sharp.prototype.crop = function() {
|
/*
|
||||||
|
Handle incoming chunk on Writable Stream
|
||||||
|
*/
|
||||||
|
Sharp.prototype._write = function(chunk, encoding, callback) {
|
||||||
|
/*jslint unused: false */
|
||||||
|
if (this.options.streamIn) {
|
||||||
|
if (typeof chunk === 'object' && chunk instanceof Buffer) {
|
||||||
|
if (this.options.bufferIn instanceof Buffer) {
|
||||||
|
// Append to existing Buffer
|
||||||
|
this.options.bufferIn = Buffer.concat(
|
||||||
|
[this.options.bufferIn, chunk],
|
||||||
|
this.options.bufferIn.length + chunk.length
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Create new Buffer
|
||||||
|
this.options.bufferIn = new Buffer(chunk.length);
|
||||||
|
chunk.copy(this.options.bufferIn);
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
} else {
|
||||||
|
callback(new Error('Non-Buffer data on Writable Stream'));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
callback(new Error('Unexpected data on Writable Stream'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Crop this part of the resized image (Center/Centre, North, East, South, West)
|
||||||
|
module.exports.gravity = {'center': 0, 'centre': 0, 'north': 1, 'east': 2, 'south': 3, 'west': 4};
|
||||||
|
|
||||||
|
Sharp.prototype.crop = function(gravity) {
|
||||||
this.options.canvas = 'c';
|
this.options.canvas = 'c';
|
||||||
|
if (typeof gravity === 'number' && !Number.isNaN(gravity) && gravity >= 0 && gravity <= 4) {
|
||||||
|
this.options.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 = 'e';
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -56,6 +177,11 @@ Sharp.prototype.max = function() {
|
|||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Sharp.prototype.flatten = function(flatten) {
|
||||||
|
this.options.flatten = (typeof flatten === 'boolean') ? flatten : true;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Rotate output image by 0, 90, 180 or 270 degrees
|
Rotate output image by 0, 90, 180 or 270 degrees
|
||||||
Auto-rotation based on the EXIF Orientation tag is represented by an angle of -1
|
Auto-rotation based on the EXIF Orientation tag is represented by an angle of -1
|
||||||
@@ -66,11 +192,27 @@ Sharp.prototype.rotate = function(angle) {
|
|||||||
} else if (!Number.isNaN(angle) && [0, 90, 180, 270].indexOf(angle) !== -1) {
|
} else if (!Number.isNaN(angle) && [0, 90, 180, 270].indexOf(angle) !== -1) {
|
||||||
this.options.angle = angle;
|
this.options.angle = angle;
|
||||||
} else {
|
} else {
|
||||||
throw 'Unsupport angle (0, 90, 180, 270) ' + angle;
|
throw new Error('Unsupported angle (0, 90, 180, 270) ' + 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:
|
||||||
@@ -81,11 +223,108 @@ 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;
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Set the interpolator to use for the affine transformation
|
||||||
|
*/
|
||||||
|
module.exports.interpolator = {
|
||||||
|
nearest: 'nearest',
|
||||||
|
bilinear: 'bilinear',
|
||||||
|
bicubic: 'bicubic',
|
||||||
|
nohalo: 'nohalo',
|
||||||
|
locallyBoundedBicubic: 'lbb',
|
||||||
|
vertexSplitQuadraticBasisSpline: 'vsqbs'
|
||||||
|
};
|
||||||
|
Sharp.prototype.interpolateWith = function(interpolator) {
|
||||||
|
this.options.interpolator = interpolator;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Darken image pre-resize (1/gamma) and brighten post-resize (gamma).
|
||||||
|
Improves brightness of resized image in non-linear colour spaces.
|
||||||
|
*/
|
||||||
|
Sharp.prototype.gamma = function(gamma) {
|
||||||
|
if (typeof gamma === 'undefined') {
|
||||||
|
// Default gamma correction of 2.2 (sRGB)
|
||||||
|
this.options.gamma = 2.2;
|
||||||
|
} else if (!Number.isNaN(gamma) && gamma >= 1 && gamma <= 3) {
|
||||||
|
this.options.gamma = gamma;
|
||||||
|
} else {
|
||||||
|
throw new Error('Invalid gamma correction (1.0 to 3.0) ' + gamma);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
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;
|
||||||
@@ -100,104 +339,289 @@ Sharp.prototype.quality = function(quality) {
|
|||||||
if (!Number.isNaN(quality) && quality >= 1 && quality <= 100) {
|
if (!Number.isNaN(quality) && quality >= 1 && quality <= 100) {
|
||||||
this.options.quality = quality;
|
this.options.quality = quality;
|
||||||
} else {
|
} else {
|
||||||
throw 'Invalid quality (1 to 100) ' + quality;
|
throw new Error('Invalid quality (1 to 100) ' + 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 '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;
|
||||||
|
};
|
||||||
|
|
||||||
|
Sharp.prototype.withMetadata = function(withMetadata) {
|
||||||
|
this.options.withMetadata = (typeof withMetadata === 'boolean') ? withMetadata : true;
|
||||||
|
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 '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 '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 && limit <= maximum.pixels) {
|
||||||
|
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
|
||||||
*/
|
*/
|
||||||
Sharp.prototype.toFile = function(output, callback) {
|
Sharp.prototype.toFile = function(output, callback) {
|
||||||
if (!output || output.length === 0) {
|
if (!output || output.length === 0) {
|
||||||
callback('Invalid output');
|
var errOutputInvalid = new Error('Invalid output');
|
||||||
|
if (typeof callback === 'function') {
|
||||||
|
callback(errOutputInvalid);
|
||||||
|
} else {
|
||||||
|
return BluebirdPromise.reject(errOutputInvalid);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (this.options.fileIn === output) {
|
if (this.options.fileIn === output) {
|
||||||
callback('Cannot use same file for input and output');
|
var errOutputIsInput = new Error('Cannot use same file for input and output');
|
||||||
|
if (typeof callback === 'function') {
|
||||||
|
callback(errOutputIsInput);
|
||||||
|
} else {
|
||||||
|
return BluebirdPromise.reject(errOutputIsInput);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return this._sharp(output, callback);
|
this.options.output = output;
|
||||||
|
return this._sharp(callback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Deprecated to make way for future stream support - remove in v0.6.0
|
|
||||||
Sharp.prototype.write = Sharp.prototype.toFile;
|
|
||||||
|
|
||||||
Sharp.prototype.toBuffer = function(callback) {
|
Sharp.prototype.toBuffer = function(callback) {
|
||||||
return this._sharp('__input', callback);
|
return this._sharp(callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
Sharp.prototype.jpeg = function(callback) {
|
Sharp.prototype.jpeg = function() {
|
||||||
return this._sharp('__jpeg', callback);
|
this.options.output = '__jpeg';
|
||||||
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
Sharp.prototype.png = function(callback) {
|
Sharp.prototype.png = function() {
|
||||||
return this._sharp('__png', callback);
|
this.options.output = '__png';
|
||||||
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
Sharp.prototype.webp = function(callback) {
|
Sharp.prototype.webp = function() {
|
||||||
return this._sharp('__webp', callback);
|
this.options.output = '__webp';
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
Sharp.prototype.raw = function() {
|
||||||
|
if (semver.gte(libvipsVersion, '7.42.0')) {
|
||||||
|
this.options.output = '__raw';
|
||||||
|
} else {
|
||||||
|
console.error('Raw output requires libvips 7.42.0+');
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Used by a Writable Stream to notify that it is ready for data
|
||||||
|
*/
|
||||||
|
Sharp.prototype._read = function() {
|
||||||
|
if (!this.options.streamOut) {
|
||||||
|
this.options.streamOut = true;
|
||||||
|
this._sharp();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Invoke the C++ image processing pipeline
|
Invoke the C++ image processing pipeline
|
||||||
Supports callback and promise variants
|
Supports callback, stream and promise variants
|
||||||
*/
|
*/
|
||||||
Sharp.prototype._sharp = function(output, callback) {
|
Sharp.prototype._sharp = function(callback) {
|
||||||
|
var that = this;
|
||||||
if (typeof callback === 'function') {
|
if (typeof callback === 'function') {
|
||||||
// I like callbacks
|
// output=file/buffer
|
||||||
sharp.resize(this.options, output, callback);
|
if (this.options.streamIn) {
|
||||||
|
// output=file/buffer, input=stream
|
||||||
|
this.on('finish', function() {
|
||||||
|
sharp.resize(that.options, callback);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// output=file/buffer, input=file/buffer
|
||||||
|
sharp.resize(this.options, callback);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
} else if (this.options.streamOut) {
|
||||||
|
// output=stream
|
||||||
|
if (this.options.streamIn) {
|
||||||
|
// output=stream, input=stream
|
||||||
|
this.on('finish', function() {
|
||||||
|
sharp.resize(that.options, function(err, data) {
|
||||||
|
if (err) {
|
||||||
|
that.emit('error', new Error(err));
|
||||||
|
} else {
|
||||||
|
that.push(data);
|
||||||
|
}
|
||||||
|
that.push(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// output=stream, input=file/buffer
|
||||||
|
sharp.resize(this.options, function(err, data) {
|
||||||
|
if (err) {
|
||||||
|
that.emit('error', new Error(err));
|
||||||
|
} else {
|
||||||
|
that.push(data);
|
||||||
|
}
|
||||||
|
that.push(null);
|
||||||
|
});
|
||||||
|
}
|
||||||
return this;
|
return this;
|
||||||
} else {
|
} else {
|
||||||
// I like promises
|
// output=promise
|
||||||
var options = this.options;
|
if (this.options.streamIn) {
|
||||||
return new Promise(function(resolve, reject) {
|
// output=promise, input=stream
|
||||||
sharp.resize(options, output, function(err, data) {
|
return new BluebirdPromise(function(resolve, reject) {
|
||||||
if (err) {
|
that.on('finish', function() {
|
||||||
reject(err);
|
sharp.resize(that.options, function(err, data) {
|
||||||
} else {
|
if (err) {
|
||||||
resolve(data);
|
reject(err);
|
||||||
}
|
} else {
|
||||||
|
resolve(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
} else {
|
||||||
|
// output=promise, input=file/buffer
|
||||||
|
return new BluebirdPromise(function(resolve, reject) {
|
||||||
|
sharp.resize(that.options, function(err, data) {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports.cache = function(limit) {
|
/*
|
||||||
if (Number.isNaN(limit)) {
|
Reads the image header and returns metadata
|
||||||
limit = null;
|
Supports callback, stream and promise variants
|
||||||
|
*/
|
||||||
|
Sharp.prototype.metadata = function(callback) {
|
||||||
|
var that = this;
|
||||||
|
if (typeof callback === 'function') {
|
||||||
|
if (this.options.streamIn) {
|
||||||
|
this.on('finish', function() {
|
||||||
|
sharp.metadata(that.options, callback);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
sharp.metadata(this.options, callback);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
} else {
|
||||||
|
if (this.options.streamIn) {
|
||||||
|
return new BluebirdPromise(function(resolve, reject) {
|
||||||
|
that.on('finish', function() {
|
||||||
|
sharp.metadata(that.options, function(err, data) {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return new BluebirdPromise(function(resolve, reject) {
|
||||||
|
sharp.metadata(that.options, function(err, data) {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return sharp.cache(limit);
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Get and set cache memory and item limits
|
||||||
|
*/
|
||||||
|
module.exports.cache = function(memory, items) {
|
||||||
|
if (typeof memory !== 'number' || Number.isNaN(memory)) {
|
||||||
|
memory = null;
|
||||||
|
}
|
||||||
|
if (typeof items !== 'number' || Number.isNaN(items)) {
|
||||||
|
items = null;
|
||||||
|
}
|
||||||
|
return sharp.cache(memory, items);
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Get and set size of thread pool
|
||||||
|
*/
|
||||||
|
module.exports.concurrency = function(concurrency) {
|
||||||
|
if (typeof concurrency !== 'number' || Number.isNaN(concurrency)) {
|
||||||
|
concurrency = null;
|
||||||
|
}
|
||||||
|
return sharp.concurrency(concurrency);
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Get internal counters
|
||||||
|
*/
|
||||||
|
module.exports.counters = function() {
|
||||||
|
return sharp.counters();
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Get the version of the libvips library
|
||||||
|
*/
|
||||||
|
module.exports.libvipsVersion = function() {
|
||||||
|
return libvipsVersion;
|
||||||
};
|
};
|
||||||
|
|||||||
39
package.json
@@ -1,13 +1,21 @@
|
|||||||
{
|
{
|
||||||
"name": "sharp",
|
"name": "sharp",
|
||||||
"version": "0.5.0",
|
"version": "0.9.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>",
|
||||||
|
"Chanon Sajjamanochai <chanon.s@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>"
|
||||||
],
|
],
|
||||||
"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=10000 ./test/unit/*.js"
|
||||||
},
|
},
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -19,27 +27,24 @@
|
|||||||
"png",
|
"png",
|
||||||
"webp",
|
"webp",
|
||||||
"tiff",
|
"tiff",
|
||||||
"gif",
|
|
||||||
"resize",
|
"resize",
|
||||||
"thumbnail",
|
"thumbnail",
|
||||||
"sharpen",
|
|
||||||
"crop",
|
"crop",
|
||||||
"embed",
|
|
||||||
"libvips",
|
"libvips",
|
||||||
"vips",
|
"vips"
|
||||||
"fast",
|
|
||||||
"buffer"
|
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"nan": "^1.1.2",
|
"bluebird": "^2.9.3",
|
||||||
"bluebird": "^1.2.4"
|
"color": "^0.7.3",
|
||||||
|
"nan": "^1.5.1",
|
||||||
|
"semver": "^4.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"imagemagick": "^0.1.3",
|
"mocha": "^2.1.0",
|
||||||
"imagemagick-native": "^1.0.0",
|
"mocha-jshint": "^0.0.9",
|
||||||
"gm": "^1.16.0",
|
"istanbul": "^0.3.5",
|
||||||
"async": "^0.9.0",
|
"coveralls": "^2.11.2",
|
||||||
"benchmark": "^1.0.0"
|
"node-cpplint": "^0.4.0"
|
||||||
},
|
},
|
||||||
"license": "Apache 2.0",
|
"license": "Apache 2.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|||||||
165
preinstall.sh
Executable file
@@ -0,0 +1,165 @@
|
|||||||
|
#!/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=7.42
|
||||||
|
vips_version_latest_minor=1
|
||||||
|
|
||||||
|
install_libvips_from_source() {
|
||||||
|
echo "Compiling libvips $vips_version_latest_major.$vips_version_latest_minor from source"
|
||||||
|
curl -O http://www.vips.ecs.soton.ac.uk/supported/$vips_version_latest_major/vips-$vips_version_latest_major.$vips_version_latest_minor.tar.gz
|
||||||
|
tar zvxf vips-$vips_version_latest_major.$vips_version_latest_minor.tar.gz
|
||||||
|
cd vips-$vips_version_latest_major.$vips_version_latest_minor
|
||||||
|
./configure --enable-debug=no --enable-docs=no --enable-cxx=yes --without-python --without-orc --without-fftw --without-gsf $1
|
||||||
|
make
|
||||||
|
make install
|
||||||
|
cd ..
|
||||||
|
rm -rf vips-$vips_version_latest_major.$vips_version_latest_minor
|
||||||
|
rm vips-$vips_version_latest_major.$vips_version_latest_minor.tar.gz
|
||||||
|
ldconfig
|
||||||
|
echo "Installed libvips $vips_version_latest_major.$vips_version_latest_minor"
|
||||||
|
}
|
||||||
|
|
||||||
|
sorry() {
|
||||||
|
echo "Sorry, I don't yet know how to install libvips on $1"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Is libvips already installed, and is it at least the minimum required version?
|
||||||
|
|
||||||
|
if ! type pkg-config >/dev/null; then
|
||||||
|
sorry "a system without pkg-config"
|
||||||
|
fi
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
PKG_CONFIG_PATH=$pkg_config_path pkg-config --exists vips
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
vips_version_found=$(PKG_CONFIG_PATH=$pkg_config_path pkg-config --modversion vips)
|
||||||
|
pkg-config --atleast-version=$vips_version_minimum vips
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
# Found suitable version of libvips
|
||||||
|
echo "Found libvips $vips_version_found"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "Found libvips $vips_version_found but require $vips_version_minimum"
|
||||||
|
else
|
||||||
|
echo "Could not find libvips using a PKG_CONFIG_PATH of '$pkg_config_path'"
|
||||||
|
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 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"
|
||||||
|
brew install homebrew/science/vips --with-webp --with-graphicsmagick
|
||||||
|
elif type "port" > /dev/null; then
|
||||||
|
echo "Installing libvips via MacPorts"
|
||||||
|
port install vips
|
||||||
|
else
|
||||||
|
sorry "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 libvips via apt-get"
|
||||||
|
apt-get install -y libvips-dev
|
||||||
|
;;
|
||||||
|
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-turbo8-dev libpng12-dev libwebp-dev libtiff5-dev libexif-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-turbo8-dev libpng12-dev libwebp-dev libtiff4-dev libexif-dev liblcms2-dev libxml2-dev swig libmagickcore-dev curl
|
||||||
|
install_libvips_from_source
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# Unsupported Debian-based OS
|
||||||
|
sorry "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 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 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
|
||||||
|
echo "Installing libvips via yum"
|
||||||
|
yum install vips-devel
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# Unsupported RHEL-based OS
|
||||||
|
sorry "$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
|
||||||
|
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 lcms-devel ImageMagick-devel gobject-introspection-devel libwebp-devel curl
|
||||||
|
install_libvips_from_source "--prefix=/usr"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
else
|
||||||
|
# Unsupported OS
|
||||||
|
sorry "$(uname -a)"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
165
src/common.cc
Executable file
@@ -0,0 +1,165 @@
|
|||||||
|
#include <string>
|
||||||
|
#include <string.h>
|
||||||
|
#include <vips/vips.h>
|
||||||
|
|
||||||
|
#include "common.h"
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buffer content checkers
|
||||||
|
unsigned char const MARKER_JPEG[] = {0xff, 0xd8};
|
||||||
|
unsigned char const MARKER_PNG[] = {0x89, 0x50};
|
||||||
|
unsigned char const MARKER_WEBP[] = {0x52, 0x49};
|
||||||
|
|
||||||
|
static bool buffer_is_tiff(char *buffer, size_t len) {
|
||||||
|
return (
|
||||||
|
len >= 4 && (
|
||||||
|
(buffer[0] == 'M' && buffer[1] == 'M' && buffer[2] == '\0' && (buffer[3] == '*' || buffer[3] == '+')) ||
|
||||||
|
(buffer[0] == 'I' && buffer[1] == 'I' && (buffer[2] == '*' || buffer[2] == '+') && buffer[3] == '\0')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Determine image format of a buffer.
|
||||||
|
*/
|
||||||
|
ImageType DetermineImageType(void *buffer, size_t const length) {
|
||||||
|
ImageType imageType = ImageType::UNKNOWN;
|
||||||
|
if (length >= 4) {
|
||||||
|
if (memcmp(MARKER_JPEG, buffer, 2) == 0) {
|
||||||
|
imageType = ImageType::JPEG;
|
||||||
|
} else if (memcmp(MARKER_PNG, buffer, 2) == 0) {
|
||||||
|
imageType = ImageType::PNG;
|
||||||
|
} else if (memcmp(MARKER_WEBP, buffer, 2) == 0) {
|
||||||
|
imageType = ImageType::WEBP;
|
||||||
|
} else if (buffer_is_tiff(static_cast<char*>(buffer), length)) {
|
||||||
|
imageType = ImageType::TIFF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return imageType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Initialise and return a VipsImage from a buffer. Supports JPEG, PNG, WebP and TIFF.
|
||||||
|
*/
|
||||||
|
VipsImage* InitImage(ImageType imageType, void *buffer, size_t const length, VipsAccess const access) {
|
||||||
|
VipsImage *image = NULL;
|
||||||
|
if (imageType == ImageType::JPEG) {
|
||||||
|
vips_jpegload_buffer(buffer, length, &image, "access", access, NULL);
|
||||||
|
} else if (imageType == ImageType::PNG) {
|
||||||
|
vips_pngload_buffer(buffer, length, &image, "access", access, NULL);
|
||||||
|
} else if (imageType == ImageType::WEBP) {
|
||||||
|
vips_webpload_buffer(buffer, length, &image, "access", access, NULL);
|
||||||
|
} else if (imageType == ImageType::TIFF) {
|
||||||
|
vips_tiffload_buffer(buffer, length, &image, "access", access, NULL);
|
||||||
|
}
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Inpect the first 2-4 bytes of a file to determine image format
|
||||||
|
*/
|
||||||
|
ImageType DetermineImageType(char const *file) {
|
||||||
|
ImageType imageType = ImageType::UNKNOWN;
|
||||||
|
if (vips_foreign_is_a("jpegload", file)) {
|
||||||
|
imageType = ImageType::JPEG;
|
||||||
|
} else if (vips_foreign_is_a("pngload", file)) {
|
||||||
|
imageType = ImageType::PNG;
|
||||||
|
} else if (vips_foreign_is_a("webpload", file)) {
|
||||||
|
imageType = ImageType::WEBP;
|
||||||
|
} else if (vips_foreign_is_a("tiffload", file)) {
|
||||||
|
imageType = ImageType::TIFF;
|
||||||
|
} else if(vips_foreign_is_a("magickload", file)) {
|
||||||
|
imageType = ImageType::MAGICK;
|
||||||
|
}
|
||||||
|
return imageType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Initialise and return a VipsImage from a file.
|
||||||
|
*/
|
||||||
|
VipsImage* InitImage(ImageType imageType, char const *file, VipsAccess const access) {
|
||||||
|
VipsImage *image = NULL;
|
||||||
|
if (imageType == ImageType::JPEG) {
|
||||||
|
vips_jpegload(file, &image, "access", access, NULL);
|
||||||
|
} else if (imageType == ImageType::PNG) {
|
||||||
|
vips_pngload(file, &image, "access", access, NULL);
|
||||||
|
} else if (imageType == ImageType::WEBP) {
|
||||||
|
vips_webpload(file, &image, "access", access, NULL);
|
||||||
|
} else if (imageType == ImageType::TIFF) {
|
||||||
|
vips_tiffload(file, &image, "access", access, NULL);
|
||||||
|
} else if (imageType == ImageType::MAGICK) {
|
||||||
|
vips_magickload(file, &image, "access", access, NULL);
|
||||||
|
}
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
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);
|
||||||
|
int window_size = vips_interpolate_get_window_size(interpolator);
|
||||||
|
g_object_unref(interpolator);
|
||||||
|
return window_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sharp
|
||||||
71
src/common.h
Executable file
@@ -0,0 +1,71 @@
|
|||||||
|
#ifndef SRC_COMMON_H_
|
||||||
|
#define SRC_COMMON_H_
|
||||||
|
|
||||||
|
namespace sharp {
|
||||||
|
|
||||||
|
enum class ImageType {
|
||||||
|
UNKNOWN,
|
||||||
|
JPEG,
|
||||||
|
PNG,
|
||||||
|
WEBP,
|
||||||
|
TIFF,
|
||||||
|
MAGICK
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
/*
|
||||||
|
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(ImageType imageType, void *buffer, size_t const length, VipsAccess const access);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Initialise and return a VipsImage from a file.
|
||||||
|
*/
|
||||||
|
VipsImage* InitImage(ImageType imageType, char const *file, VipsAccess const access);
|
||||||
|
|
||||||
|
/*
|
||||||
|
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_
|
||||||
171
src/metadata.cc
Executable file
@@ -0,0 +1,171 @@
|
|||||||
|
#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(imageType, 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(imageType, 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::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_
|
||||||
1007
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_
|
||||||
504
src/sharp.cc
@@ -1,503 +1,31 @@
|
|||||||
#include <node.h>
|
#include <node.h>
|
||||||
#include <node_buffer.h>
|
|
||||||
#include <math.h>
|
|
||||||
#include <string>
|
|
||||||
#include <string.h>
|
|
||||||
#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 file_out;
|
|
||||||
void* buffer_out;
|
|
||||||
size_t buffer_out_len;
|
|
||||||
int width;
|
|
||||||
int height;
|
|
||||||
bool crop;
|
|
||||||
bool max;
|
|
||||||
VipsExtend extend;
|
|
||||||
bool sharpen;
|
|
||||||
bool progressive;
|
|
||||||
bool without_enlargement;
|
|
||||||
VipsAccess access_method;
|
|
||||||
int quality;
|
|
||||||
int compressionLevel;
|
|
||||||
int angle;
|
|
||||||
std::string err;
|
|
||||||
|
|
||||||
resize_baton():
|
|
||||||
buffer_in_len(0),
|
|
||||||
buffer_out_len(0),
|
|
||||||
crop(false),
|
|
||||||
max(false),
|
|
||||||
sharpen(false),
|
|
||||||
progressive(false),
|
|
||||||
without_enlargement(false) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
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};
|
|
||||||
|
|
||||||
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 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
class ResizeWorker : public NanAsyncWorker {
|
|
||||||
public:
|
|
||||||
ResizeWorker(NanCallback *callback, resize_baton *baton)
|
|
||||||
: NanAsyncWorker(callback), baton(baton) {}
|
|
||||||
~ResizeWorker() {}
|
|
||||||
|
|
||||||
void Execute () {
|
|
||||||
// Input
|
|
||||||
ImageType inputImageType = JPEG;
|
|
||||||
VipsImage *in = vips_image_new();
|
|
||||||
if (baton->buffer_in_len > 1) {
|
|
||||||
if (memcmp(MARKER_JPEG, baton->buffer_in, 2) == 0) {
|
|
||||||
if (vips_jpegload_buffer(baton->buffer_in, baton->buffer_in_len, &in, "access", baton->access_method, NULL)) {
|
|
||||||
return resize_error(baton, in);
|
|
||||||
}
|
|
||||||
} else if(memcmp(MARKER_PNG, baton->buffer_in, 2) == 0) {
|
|
||||||
inputImageType = PNG;
|
|
||||||
if (vips_pngload_buffer(baton->buffer_in, baton->buffer_in_len, &in, "access", baton->access_method, NULL)) {
|
|
||||||
return resize_error(baton, in);
|
|
||||||
}
|
|
||||||
} else if(memcmp(MARKER_WEBP, baton->buffer_in, 2) == 0) {
|
|
||||||
inputImageType = WEBP;
|
|
||||||
if (vips_webpload_buffer(baton->buffer_in, baton->buffer_in_len, &in, "access", baton->access_method, NULL)) {
|
|
||||||
return resize_error(baton, in);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
resize_error(baton, in);
|
|
||||||
(baton->err).append("Unsupported input buffer");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else if (vips_foreign_is_a("jpegload", baton->file_in.c_str())) {
|
|
||||||
if (vips_jpegload((baton->file_in).c_str(), &in, "access", baton->access_method, NULL)) {
|
|
||||||
return resize_error(baton, in);
|
|
||||||
}
|
|
||||||
} else if (vips_foreign_is_a("pngload", baton->file_in.c_str())) {
|
|
||||||
inputImageType = PNG;
|
|
||||||
if (vips_pngload((baton->file_in).c_str(), &in, "access", baton->access_method, NULL)) {
|
|
||||||
return resize_error(baton, in);
|
|
||||||
}
|
|
||||||
} else if (vips_foreign_is_a("webpload", baton->file_in.c_str())) {
|
|
||||||
inputImageType = WEBP;
|
|
||||||
if (vips_webpload((baton->file_in).c_str(), &in, "access", baton->access_method, NULL)) {
|
|
||||||
return resize_error(baton, in);
|
|
||||||
}
|
|
||||||
} else if (vips_foreign_is_a("tiffload", baton->file_in.c_str())) {
|
|
||||||
inputImageType = TIFF;
|
|
||||||
if (vips_tiffload((baton->file_in).c_str(), &in, "access", baton->access_method, NULL)) {
|
|
||||||
return resize_error(baton, in);
|
|
||||||
}
|
|
||||||
} else if(vips_foreign_is_a("magickload", (baton->file_in).c_str())) {
|
|
||||||
inputImageType = MAGICK;
|
|
||||||
if (vips_magickload((baton->file_in).c_str(), &in, "access", baton->access_method, NULL)) {
|
|
||||||
return resize_error(baton, in);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
resize_error(baton, in);
|
|
||||||
(baton->err).append("Unsupported input file " + baton->file_in);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 = 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
|
|
||||||
int shrink_on_load = 1;
|
|
||||||
if (inputImageType == JPEG) {
|
|
||||||
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);
|
|
||||||
|
|
||||||
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 using bilinear interpolation
|
|
||||||
VipsImage *affined = vips_image_new();
|
|
||||||
if (residual != 0) {
|
|
||||||
if (vips_affine(shrunk, &affined, residual, 0, 0, residual, "interpolate", vips_interpolate_bilinear_static(), NULL)) {
|
|
||||||
return resize_error(baton, shrunk);
|
|
||||||
}
|
|
||||||
} 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 width = std::min(rotated->Xsize, baton->width);
|
|
||||||
int height = std::min(rotated->Ysize, baton->height);
|
|
||||||
int left = (rotated->Xsize - width + 1) / 2;
|
|
||||||
int top = (rotated->Ysize - height + 1) / 2;
|
|
||||||
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);
|
|
||||||
|
|
||||||
// Output
|
|
||||||
if (baton->file_out == "__jpeg" || (baton->file_out == "__input" && inputImageType == JPEG)) {
|
|
||||||
// Write JPEG to buffer
|
|
||||||
if (vips_jpegsave_buffer(sharpened, &baton->buffer_out, &baton->buffer_out_len, "strip", TRUE, "Q", baton->quality, "optimize_coding", TRUE, "interlace", baton->progressive, NULL)) {
|
|
||||||
return resize_error(baton, sharpened);
|
|
||||||
}
|
|
||||||
} else if (baton->file_out == "__png" || (baton->file_out == "__input" && inputImageType == PNG)) {
|
|
||||||
// Write PNG to buffer
|
|
||||||
if (vips_pngsave_buffer(sharpened, &baton->buffer_out, &baton->buffer_out_len, "strip", TRUE, "compression", baton->compressionLevel, "interlace", baton->progressive, NULL)) {
|
|
||||||
return resize_error(baton, sharpened);
|
|
||||||
}
|
|
||||||
} else if (baton->file_out == "__webp" || (baton->file_out == "__input" && inputImageType == WEBP)) {
|
|
||||||
// Write WEBP to buffer
|
|
||||||
if (vips_webpsave_buffer(sharpened, &baton->buffer_out, &baton->buffer_out_len, "strip", TRUE, "Q", baton->quality, NULL)) {
|
|
||||||
return resize_error(baton, sharpened);
|
|
||||||
}
|
|
||||||
} else if (is_jpeg(baton->file_out)) {
|
|
||||||
// Write JPEG to file
|
|
||||||
if (vips_jpegsave(sharpened, baton->file_out.c_str(), "strip", TRUE, "Q", baton->quality, "optimize_coding", TRUE, "interlace", baton->progressive, NULL)) {
|
|
||||||
return resize_error(baton, sharpened);
|
|
||||||
}
|
|
||||||
} else if (is_png(baton->file_out)) {
|
|
||||||
// Write PNG to file
|
|
||||||
if (vips_pngsave(sharpened, baton->file_out.c_str(), "strip", TRUE, "compression", baton->compressionLevel, "interlace", baton->progressive, NULL)) {
|
|
||||||
return resize_error(baton, sharpened);
|
|
||||||
}
|
|
||||||
} else if (is_webp(baton->file_out)) {
|
|
||||||
// Write WEBP to file
|
|
||||||
if (vips_webpsave(sharpened, baton->file_out.c_str(), "strip", TRUE, "Q", baton->quality, NULL)) {
|
|
||||||
return resize_error(baton, sharpened);
|
|
||||||
}
|
|
||||||
} else if (is_tiff(baton->file_out)) {
|
|
||||||
// Write TIFF to file
|
|
||||||
if (vips_tiffsave(sharpened, baton->file_out.c_str(), "strip", TRUE, "compression", VIPS_FOREIGN_TIFF_COMPRESSION_JPEG, "Q", baton->quality, NULL)) {
|
|
||||||
return resize_error(baton, sharpened);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
(baton->err).append("Unsupported output " + baton->file_out);
|
|
||||||
}
|
|
||||||
g_object_unref(sharpened);
|
|
||||||
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 if (baton->buffer_out_len > 0) {
|
|
||||||
// Buffer
|
|
||||||
argv[1] = NanNewBufferHandle((char *)baton->buffer_out, baton->buffer_out_len);
|
|
||||||
g_free(baton->buffer_out);
|
|
||||||
}
|
|
||||||
delete baton;
|
|
||||||
callback->Call(2, argv);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
resize_baton* baton;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
resize(options, output, callback)
|
|
||||||
*/
|
|
||||||
NAN_METHOD(resize) {
|
|
||||||
NanScope();
|
NanScope();
|
||||||
|
vips_init("sharp");
|
||||||
|
|
||||||
// V8 objects are converted to non-V8 types held in the baton struct
|
// Set libvips operation cache limits
|
||||||
resize_baton *baton = new resize_baton;
|
vips_cache_set_max_mem(100 * 1048576); // 100 MB
|
||||||
Local<Object> options = args[0]->ToObject();
|
vips_cache_set_max(500); // 500 operations
|
||||||
|
|
||||||
// Input filename
|
// Notify the V8 garbage collector of max cache size
|
||||||
baton->file_in = *String::Utf8Value(options->Get(NanNew<String>("fileIn"))->ToString());
|
NanAdjustExternalMemory(vips_cache_get_max_mem());
|
||||||
// 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->sharpen = options->Get(NanNew<String>("sharpen"))->BooleanValue();
|
|
||||||
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();
|
|
||||||
// Output filename or __format for Buffer
|
|
||||||
baton->file_out = *String::Utf8Value(args[1]->ToString());
|
|
||||||
|
|
||||||
// Join queue for worker thread
|
// Methods available to JavaScript
|
||||||
NanCallback *callback = new NanCallback(args[2].As<v8::Function>());
|
NODE_SET_METHOD(target, "metadata", metadata);
|
||||||
NanAsyncQueueWorker(new ResizeWorker(callback, baton));
|
|
||||||
NanReturnUndefined();
|
|
||||||
}
|
|
||||||
|
|
||||||
NAN_METHOD(cache) {
|
|
||||||
NanScope();
|
|
||||||
|
|
||||||
// Set cache limit
|
|
||||||
if (args[0]->IsInt32()) {
|
|
||||||
vips_cache_set_max_mem(args[0]->Int32Value() * 1048576);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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>("limit"), NanNew<Number>(vips_cache_get_max_mem() / 1048576));
|
|
||||||
NanReturnValue(cache);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void at_exit(void* arg) {
|
|
||||||
NanScope();
|
|
||||||
vips_shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" void init(Handle<Object> target) {
|
|
||||||
NanScope();
|
|
||||||
vips_init("");
|
|
||||||
AtExit(at_exit);
|
|
||||||
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, "counters", counters);
|
||||||
|
NODE_SET_METHOD(target, "libvipsVersion", libvipsVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
NODE_MODULE(sharp, init)
|
NODE_MODULE(sharp, init)
|
||||||
|
|||||||
80
src/utilities.cc
Executable file
@@ -0,0 +1,80 @@
|
|||||||
|
#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 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];
|
||||||
|
snprintf(version, sizeof(version), "%d.%d.%d", vips_version(0), vips_version(1), vips_version(2));
|
||||||
|
NanReturnValue(NanNew<String>(version));
|
||||||
|
}
|
||||||
11
src/utilities.h
Executable file
@@ -0,0 +1,11 @@
|
|||||||
|
#ifndef SRC_UTILITIES_H_
|
||||||
|
#define SRC_UTILITIES_H_
|
||||||
|
|
||||||
|
#include "nan.h"
|
||||||
|
|
||||||
|
NAN_METHOD(cache);
|
||||||
|
NAN_METHOD(concurrency);
|
||||||
|
NAN_METHOD(counters);
|
||||||
|
NAN_METHOD(libvipsVersion);
|
||||||
|
|
||||||
|
#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": "^1.6.0",
|
||||||
|
"gm": "^1.17.0",
|
||||||
|
"async": "^0.9.0",
|
||||||
|
"semver": "^4.2.0",
|
||||||
|
"benchmark": "^1.0.0"
|
||||||
|
},
|
||||||
|
"license": "Apache 2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10"
|
||||||
|
}
|
||||||
|
}
|
||||||
41
test/bench/parallel.js
Executable file
@@ -0,0 +1,41 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var assert = require('assert');
|
||||||
|
var async = require('async');
|
||||||
|
|
||||||
|
var sharp = require('../../index');
|
||||||
|
var fixtures = require('../fixtures');
|
||||||
|
|
||||||
|
var width = 720;
|
||||||
|
var height = 480;
|
||||||
|
|
||||||
|
sharp.concurrency(1);
|
||||||
|
|
||||||
|
var timer = setInterval(function() {
|
||||||
|
console.dir(sharp.counters());
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
async.mapSeries([1, 1, 2, 4, 8, 16, 32, 64, 128], function(parallelism, next) {
|
||||||
|
var start = new Date().getTime();
|
||||||
|
async.times(parallelism,
|
||||||
|
function(id, callback) {
|
||||||
|
/*jslint unused: false */
|
||||||
|
sharp(fixtures.inputJpg).resize(width, height).toBuffer(function(err, buffer) {
|
||||||
|
buffer = null;
|
||||||
|
callback(err, new Date().getTime() - start);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function(err, ids) {
|
||||||
|
assert(!err);
|
||||||
|
assert(ids.length === parallelism);
|
||||||
|
var mean = ids.reduce(function(a, b) {
|
||||||
|
return a + b;
|
||||||
|
}) / ids.length;
|
||||||
|
console.log(parallelism + ' parallel calls: fastest=' + ids[0] + 'ms slowest=' + ids[ids.length - 1] + 'ms mean=' + mean + 'ms');
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}, function() {
|
||||||
|
clearInterval(timer);
|
||||||
|
console.dir(sharp.counters());
|
||||||
|
});
|
||||||
584
test/bench/perf.js
Executable file
@@ -0,0 +1,584 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var fs = require('fs');
|
||||||
|
|
||||||
|
var async = require('async');
|
||||||
|
var assert = require('assert');
|
||||||
|
var Benchmark = require('benchmark');
|
||||||
|
var semver = require('semver');
|
||||||
|
|
||||||
|
var imagemagick = require('imagemagick');
|
||||||
|
var imagemagickNative = require('imagemagick-native');
|
||||||
|
var gm = require('gm');
|
||||||
|
var sharp = require('../../index');
|
||||||
|
|
||||||
|
var fixtures = require('../fixtures');
|
||||||
|
|
||||||
|
var width = 720;
|
||||||
|
var height = 480;
|
||||||
|
|
||||||
|
// Approximately equivalent to fast bilinear
|
||||||
|
var magickFilter = 'Triangle';
|
||||||
|
|
||||||
|
// Disable libvips cache to ensure tests are as fair as they can be
|
||||||
|
sharp.cache(0);
|
||||||
|
|
||||||
|
async.series({
|
||||||
|
jpeg: function(callback) {
|
||||||
|
var inputJpgBuffer = fs.readFileSync(fixtures.inputJpg);
|
||||||
|
(new Benchmark.Suite('jpeg')).add('imagemagick-file-file', {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
imagemagick.resize({
|
||||||
|
srcPath: fixtures.inputJpg,
|
||||||
|
dstPath: fixtures.outputJpg,
|
||||||
|
quality: 0.8,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
format: 'jpg',
|
||||||
|
filter: magickFilter
|
||||||
|
}, function(err) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).add('imagemagick-native-buffer-buffer', {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
imagemagickNative.convert({
|
||||||
|
srcData: inputJpgBuffer,
|
||||||
|
quality: 80,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
format: 'JPEG',
|
||||||
|
filter: magickFilter
|
||||||
|
}, function (err, buffer) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
assert.notStrictEqual(null, buffer);
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).add('gm-buffer-file', {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
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) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).add('sharp-buffer-buffer', {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
sharp(inputJpgBuffer).resize(width, height).toBuffer(function(err, buffer) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
assert.notStrictEqual(null, buffer);
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).add('sharp-file-file', {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
sharp(fixtures.inputJpg).resize(width, height).toFile(fixtures.outputJpg, function(err) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).add('sharp-stream-stream', {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
var readable = fs.createReadStream(fixtures.inputJpg);
|
||||||
|
var writable = fs.createWriteStream(fixtures.outputJpg);
|
||||||
|
writable.on('finish', function() {
|
||||||
|
deferred.resolve();
|
||||||
|
});
|
||||||
|
var pipeline = sharp().resize(width, height);
|
||||||
|
readable.pipe(pipeline).pipe(writable);
|
||||||
|
}
|
||||||
|
}).add('sharp-file-buffer', {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
sharp(fixtures.inputJpg).resize(width, height).toBuffer(function(err, buffer) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
assert.notStrictEqual(null, buffer);
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).add('sharp-promise', {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
sharp(inputJpgBuffer).resize(width, height).toBuffer().then(function(buffer) {
|
||||||
|
assert.notStrictEqual(null, buffer);
|
||||||
|
deferred.resolve();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).add('sharp-sharpen-mild', {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
sharp(inputJpgBuffer).resize(width, height).sharpen().toBuffer(function(err, buffer) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
assert.notStrictEqual(null, buffer);
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).add('sharp-sharpen-radius', {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
sharp(inputJpgBuffer).resize(width, height).sharpen(3, 1, 3).toBuffer(function(err, buffer) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
assert.notStrictEqual(null, buffer);
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).add('sharp-blur-mild', {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
sharp(inputJpgBuffer).resize(width, height).blur().toBuffer(function(err, buffer) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
assert.notStrictEqual(null, buffer);
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).add('sharp-blur-radius', {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
sharp(inputJpgBuffer).resize(width, height).blur(3).toBuffer(function(err, buffer) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
assert.notStrictEqual(null, buffer);
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).add('sharp-nearest-neighbour', {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
sharp(inputJpgBuffer).resize(width, height).interpolateWith(sharp.interpolator.nearest).toBuffer(function(err, buffer) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
assert.notStrictEqual(null, buffer);
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).add('sharp-bicubic', {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
sharp(inputJpgBuffer).resize(width, height).interpolateWith(sharp.interpolator.bicubic).toBuffer(function(err, buffer) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
assert.notStrictEqual(null, buffer);
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).add('sharp-nohalo', {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
sharp(inputJpgBuffer).resize(width, height).interpolateWith(sharp.interpolator.nohalo).toBuffer(function(err, buffer) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
assert.notStrictEqual(null, buffer);
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).add('sharp-locallyBoundedBicubic', {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
sharp(inputJpgBuffer).resize(width, height).interpolateWith(sharp.interpolator.locallyBoundedBicubic).toBuffer(function(err, buffer) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
assert.notStrictEqual(null, buffer);
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).add('sharp-vertexSplitQuadraticBasisSpline', {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
sharp(inputJpgBuffer).resize(width, height).interpolateWith(sharp.interpolator.vertexSplitQuadraticBasisSpline).toBuffer(function(err, buffer) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
assert.notStrictEqual(null, buffer);
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).add('sharp-gamma', {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
sharp(inputJpgBuffer).resize(width, height).gamma().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-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();
|
||||||
|
},
|
||||||
|
png: function(callback) {
|
||||||
|
var inputPngBuffer = fs.readFileSync(fixtures.inputPng);
|
||||||
|
var pngSuite = new Benchmark.Suite('png');
|
||||||
|
pngSuite.add('imagemagick-file-file', {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
imagemagick.resize({
|
||||||
|
srcPath: fixtures.inputPng,
|
||||||
|
dstPath: fixtures.outputPng,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
format: 'jpg',
|
||||||
|
filter: magickFilter
|
||||||
|
}, function(err) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).add('imagemagick-native-buffer-buffer', {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
imagemagickNative.convert({
|
||||||
|
srcData: inputPngBuffer,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
format: 'PNG',
|
||||||
|
filter: magickFilter
|
||||||
|
});
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
}).add('gm-file-file', {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
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) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).add('sharp-buffer-buffer', {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
sharp(inputPngBuffer).resize(width, height).toBuffer(function(err, buffer) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
assert.notStrictEqual(null, buffer);
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).add('sharp-file-file', {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
sharp(fixtures.inputPng).resize(width, height).toFile(fixtures.outputPng, function(err) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).add('sharp-file-buffer', {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
sharp(fixtures.inputPng).resize(width, height).toBuffer(function(err, buffer) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
assert.notStrictEqual(null, buffer);
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).add('sharp-progressive', {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
sharp(inputPngBuffer).resize(width, height).progressive().toBuffer(function(err, buffer) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
assert.notStrictEqual(null, buffer);
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (semver.gte(sharp.libvipsVersion(), '7.41.0')) {
|
||||||
|
pngSuite.add('sharp-withoutAdaptiveFiltering', {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
sharp(inputPngBuffer).resize(width, height).withoutAdaptiveFiltering().toBuffer(function(err, buffer) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
assert.notStrictEqual(null, buffer);
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
pngSuite.on('cycle', function(event) {
|
||||||
|
console.log(' png ' + String(event.target));
|
||||||
|
}).on('complete', function() {
|
||||||
|
callback(null, this.filter('fastest').pluck('name'));
|
||||||
|
}).run();
|
||||||
|
},
|
||||||
|
webp: function(callback) {
|
||||||
|
var inputWebPBuffer = fs.readFileSync(fixtures.inputWebP);
|
||||||
|
(new Benchmark.Suite('webp')).add('sharp-buffer-file', {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
sharp(inputWebPBuffer).resize(width, height).toFile(fixtures.outputWebP, function(err) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).add('sharp-buffer-buffer', {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
sharp(inputWebPBuffer).resize(width, height).toBuffer(function(err, buffer) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
assert.notStrictEqual(null, buffer);
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).add('sharp-file-file', {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
sharp(fixtures.inputWebP).resize(width, height).toFile(fixtures.outputWebP, function(err) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).add('sharp-file-buffer', {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
sharp(fixtures.inputWebp).resize(width, height).toBuffer(function(err, buffer) {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}, function(err, results) {
|
||||||
|
assert(!err, err);
|
||||||
|
Object.keys(results).forEach(function(format) {
|
||||||
|
if (results[format].toString().substr(0, 5) !== 'sharp') {
|
||||||
|
console.log('sharp was slower than ' + results[format] + ' for ' + format);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.dir(sharp.cache());
|
||||||
|
});
|
||||||
65
test/bench/random.js
Executable file
@@ -0,0 +1,65 @@
|
|||||||
|
'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;
|
||||||
|
|
||||||
|
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()
|
||||||
|
}, function(err) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).add('gm', {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
gm(fixtures.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(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 |
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/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
BIN
test/fixtures/gamma_dalai_lama_gray.jpg
vendored
Normal file
|
After Width: | Height: | Size: 83 KiB |
35
test/fixtures/index.js
vendored
Executable file
@@ -0,0 +1,35 @@
|
|||||||
|
'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'),
|
||||||
|
|
||||||
|
inputPng: getPath('50020484-00001.png'), // http://c.searspartsdirect.com/lis_png/PLDM/50020484-00001.png
|
||||||
|
inputPngWithTransparency: getPath('blackbug.png'), // public domain
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
};
|
||||||
8
test/leak/leak.sh
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
if ! type valgrind >/dev/null; then
|
||||||
|
echo "Please install valgrind before running memory leak tests"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
curl -O https://raw.githubusercontent.com/jcupitt/libvips/master/libvips.supp test/leak/libvips.supp
|
||||||
|
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
|
||||||
111
test/leak/sharp.supp
Executable file
@@ -0,0 +1,111 @@
|
|||||||
|
# libjpeg warnings
|
||||||
|
{
|
||||||
|
cond_jpeg_read_scanlines
|
||||||
|
Memcheck:Cond
|
||||||
|
...
|
||||||
|
fun:jpeg_read_scanlines
|
||||||
|
}
|
||||||
|
{
|
||||||
|
value_jpeg_read_scanlines
|
||||||
|
Memcheck:Value8
|
||||||
|
...
|
||||||
|
fun:jpeg_read_scanlines
|
||||||
|
}
|
||||||
|
{
|
||||||
|
cond_jpeg_write_scanlines
|
||||||
|
Memcheck:Cond
|
||||||
|
...
|
||||||
|
fun:jpeg_write_scanlines
|
||||||
|
}
|
||||||
|
{
|
||||||
|
cond_jpeg_finish_compress
|
||||||
|
Memcheck:Cond
|
||||||
|
...
|
||||||
|
fun:jpeg_finish_compress
|
||||||
|
}
|
||||||
|
{
|
||||||
|
value_jpeg_finish_compress
|
||||||
|
Memcheck:Value8
|
||||||
|
...
|
||||||
|
fun:jpeg_finish_compress
|
||||||
|
}
|
||||||
|
|
||||||
|
# libvips interpolator warnings
|
||||||
|
{
|
||||||
|
cond_libvips_interpolate_lbb
|
||||||
|
Memcheck:Cond
|
||||||
|
...
|
||||||
|
fun:_ZL32vips_interpolate_lbb_interpolateP16_VipsInterpolatePvP11_VipsRegiondd
|
||||||
|
fun:vips_affine_gen
|
||||||
|
}
|
||||||
|
|
||||||
|
# libuv warnings
|
||||||
|
{
|
||||||
|
free_libuv
|
||||||
|
Memcheck:Free
|
||||||
|
...
|
||||||
|
fun:uv__work_done
|
||||||
|
}
|
||||||
|
|
||||||
|
# nodejs warnings
|
||||||
|
{
|
||||||
|
param_nodejs_write_buffer
|
||||||
|
Memcheck:Param
|
||||||
|
write(buf)
|
||||||
|
...
|
||||||
|
obj:/usr/bin/nodejs
|
||||||
|
}
|
||||||
|
{
|
||||||
|
leak_nodejs_ImmutableAsciiSource_CreateFromLiteral
|
||||||
|
Memcheck:Leak
|
||||||
|
match-leak-kinds: definite
|
||||||
|
...
|
||||||
|
fun:_ZN4node20ImmutableAsciiSource17CreateFromLiteralEPKcm
|
||||||
|
}
|
||||||
|
{
|
||||||
|
leak_nodejs_Buffer_New
|
||||||
|
Memcheck:Leak
|
||||||
|
match-leak-kinds: definite
|
||||||
|
...
|
||||||
|
fun:_ZN4node6Buffer3NewERKN2v89ArgumentsE
|
||||||
|
}
|
||||||
|
{
|
||||||
|
leak_nodejs_Buffer_Replace
|
||||||
|
Memcheck:Leak
|
||||||
|
match-leak-kinds: indirect,possible
|
||||||
|
...
|
||||||
|
fun:_ZN4node6Buffer7ReplaceEPcmPFvS1_PvES2_
|
||||||
|
}
|
||||||
|
{
|
||||||
|
leak_nodejs_SignalWrap_New
|
||||||
|
Memcheck:Leak
|
||||||
|
match-leak-kinds: possible
|
||||||
|
...
|
||||||
|
fun:_ZN4node10SignalWrap3NewERKN2v89ArgumentsE
|
||||||
|
}
|
||||||
|
{
|
||||||
|
leak_nodejs_TTYWrap_New
|
||||||
|
Memcheck:Leak
|
||||||
|
match-leak-kinds: possible
|
||||||
|
...
|
||||||
|
fun:_ZN4node7TTYWrap3NewERKN2v89ArgumentsE
|
||||||
|
}
|
||||||
|
{
|
||||||
|
leak_nodejs_ares_init_options
|
||||||
|
Memcheck:Leak
|
||||||
|
match-leak-kinds: reachable
|
||||||
|
fun:malloc
|
||||||
|
fun:strdup
|
||||||
|
...
|
||||||
|
fun:ares_init_options
|
||||||
|
}
|
||||||
|
|
||||||
|
# vips__init warnings
|
||||||
|
{
|
||||||
|
leak_libvips_init
|
||||||
|
Memcheck:Leak
|
||||||
|
match-leak-kinds: reachable
|
||||||
|
fun:malloc
|
||||||
|
...
|
||||||
|
fun:vips__init
|
||||||
|
}
|
||||||
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
83
test/unit/colourspace.js
Executable file
@@ -0,0 +1,83 @@
|
|||||||
|
'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);
|
||||||
|
});
|
||||||
|
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
46
test/unit/cpplint.js
Executable file
@@ -0,0 +1,46 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var fs = require('fs');
|
||||||
|
var path = require('path');
|
||||||
|
var assert = require('assert');
|
||||||
|
|
||||||
|
var cpplint = require('node-cpplint/lib/');
|
||||||
|
|
||||||
|
describe('cpplint', function() {
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
51
test/unit/embed.js
Executable file
@@ -0,0 +1,51 @@
|
|||||||
|
'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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
177
test/unit/extract.js
Executable file
@@ -0,0 +1,177 @@
|
|||||||
|
'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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
96
test/unit/interpolation.js
Executable file
@@ -0,0 +1,96 @@
|
|||||||
|
'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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
598
test/unit/io.js
Executable file
@@ -0,0 +1,598 @@
|
|||||||
|
'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', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.sequentialRead()
|
||||||
|
.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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Not sequential read', function(done) {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.sequentialRead(false)
|
||||||
|
.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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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) {
|
||||||
|
var failed = true;
|
||||||
|
try {
|
||||||
|
sharp(new Buffer(0));
|
||||||
|
failed = false;
|
||||||
|
} catch (err) {
|
||||||
|
assert(err instanceof Error);
|
||||||
|
}
|
||||||
|
assert(failed);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Fail when input is invalid Buffer', function(done) {
|
||||||
|
var failed = true;
|
||||||
|
try {
|
||||||
|
sharp(new Buffer([0x1, 0x2, 0x3, 0x4]));
|
||||||
|
failed = false;
|
||||||
|
} catch (err) {
|
||||||
|
assert(err instanceof Error);
|
||||||
|
}
|
||||||
|
assert(failed);
|
||||||
|
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 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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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('Convert SVG, if supported, to PNG', function(done) {
|
||||||
|
sharp(fixtures.inputSvg)
|
||||||
|
.resize(100, 100)
|
||||||
|
.png()
|
||||||
|
.toFile(fixtures.path('output.svg.png'), function(err, info) {
|
||||||
|
if (err) {
|
||||||
|
assert.strictEqual('Input file is of an unsupported image format', err.message);
|
||||||
|
} else {
|
||||||
|
assert.strictEqual(true, info.size > 0);
|
||||||
|
assert.strictEqual('png', info.format);
|
||||||
|
assert.strictEqual(100, info.width);
|
||||||
|
assert.strictEqual(100, info.height);
|
||||||
|
}
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Convert PSD to PNG', function(done) {
|
||||||
|
sharp(fixtures.inputPsd)
|
||||||
|
.resize(320, 240)
|
||||||
|
.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 (semver.gte(sharp.libvipsVersion(), '7.40.0')) {
|
||||||
|
it('Load TIFF from Buffer [libvips ' + sharp.libvipsVersion() + '>=7.40.0]', 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 (semver.gte(sharp.libvipsVersion(), '7.42.0')) {
|
||||||
|
describe('Ouput raw, uncompressed image data [libvips ' + sharp.libvipsVersion() + '>=7.42.0]', 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)
|
||||||
|
.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)
|
||||||
|
.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 - huge', function(done) {
|
||||||
|
var isValid = false;
|
||||||
|
try {
|
||||||
|
sharp().limitInputPixels(Math.pow(0x3FFF, 2) + 1);
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
1
test/unit/jshint.js
Executable file
@@ -0,0 +1 @@
|
|||||||
|
require('mocha-jshint')();
|
||||||
235
test/unit/metadata.js
Executable file
@@ -0,0 +1,235 @@
|
|||||||
|
'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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
230
test/unit/resize.js
Executable file
@@ -0,0 +1,230 @@
|
|||||||
|
'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('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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
53
test/unit/util.js
Executable file
@@ -0,0 +1,53 @@
|
|||||||
|
'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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
BIN
tests/fixtures/2569067123_aca715a2ee_o.jpg
vendored
|
Before Width: | Height: | Size: 813 KiB |
@@ -1,32 +0,0 @@
|
|||||||
var sharp = require("../index");
|
|
||||||
var fs = require("fs");
|
|
||||||
var path = require("path");
|
|
||||||
var assert = require("assert");
|
|
||||||
var async = require("async");
|
|
||||||
|
|
||||||
var inputJpg = path.join(__dirname, "fixtures/2569067123_aca715a2ee_o.jpg"); // http://www.flickr.com/photos/grizdave/2569067123/
|
|
||||||
var width = 720;
|
|
||||||
var height = 480;
|
|
||||||
|
|
||||||
async.mapSeries([1, 1, 2, 4, 8, 16, 32, 64, 128], function(parallelism, next) {
|
|
||||||
var start = new Date().getTime();
|
|
||||||
async.times(parallelism,
|
|
||||||
function(id, callback) {
|
|
||||||
sharp(inputJpg).resize(width, height).toBuffer(function(err, buffer) {
|
|
||||||
buffer = null;
|
|
||||||
callback(err, new Date().getTime() - start);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
function(err, ids) {
|
|
||||||
assert(!err);
|
|
||||||
assert(ids.length === parallelism);
|
|
||||||
var mean = ids.reduce(function(a, b) {
|
|
||||||
return a + b;
|
|
||||||
}) / ids.length;
|
|
||||||
console.log(parallelism + " parallel calls: fastest=" + ids[0] + "ms slowest=" + ids[ids.length - 1] + "ms mean=" + mean + "ms");
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}, function() {
|
|
||||||
console.dir(sharp.cache());
|
|
||||||
});
|
|
||||||
517
tests/perf.js
@@ -1,517 +0,0 @@
|
|||||||
var sharp = require("../index");
|
|
||||||
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 inputJpg = path.join(fixturesPath, "2569067123_aca715a2ee_o.jpg"); // http://www.flickr.com/photos/grizdave/2569067123/
|
|
||||||
var outputJpg = path.join(fixturesPath, "output.jpg");
|
|
||||||
|
|
||||||
var inputPng = path.join(fixturesPath, "50020484-00001.png"); // http://c.searspartsdirect.com/lis_png/PLDM/50020484-00001.png
|
|
||||||
var outputPng = path.join(fixturesPath, "output.png");
|
|
||||||
|
|
||||||
var inputWebp = path.join(fixturesPath, "4.webp"); // http://www.gstatic.com/webp/gallery/4.webp
|
|
||||||
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 height = 480;
|
|
||||||
|
|
||||||
// Disable libvips cache to ensure tests are as fair as they can be
|
|
||||||
sharp.cache(0);
|
|
||||||
|
|
||||||
async.series({
|
|
||||||
jpeg: function(callback) {
|
|
||||||
var inputJpgBuffer = fs.readFileSync(inputJpg);
|
|
||||||
(new Benchmark.Suite("jpeg")).add("imagemagick-file-file", {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
imagemagick.resize({
|
|
||||||
srcPath: inputJpg,
|
|
||||||
dstPath: outputJpg,
|
|
||||||
quality: 0.8,
|
|
||||||
width: width,
|
|
||||||
height: height
|
|
||||||
}, function(err) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
} else {
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).add("imagemagick-native-buffer-buffer", {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
imagemagickNative.convert({
|
|
||||||
srcData: inputJpgBuffer,
|
|
||||||
quality: 80,
|
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
format: 'JPEG'
|
|
||||||
});
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
}).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) {
|
|
||||||
throw err;
|
|
||||||
} else {
|
|
||||||
assert.notStrictEqual(null, buffer);
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).add("gm-file-file", {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
gm(inputJpg).resize(width, height).quality(80).write(outputJpg, function (err) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
} else {
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).add("gm-file-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,
|
|
||||||
fn: function(deferred) {
|
|
||||||
sharp(inputJpgBuffer).resize(width, height).toBuffer(function(err, buffer) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
} else {
|
|
||||||
assert.notStrictEqual(null, buffer);
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).add("sharp-file-file", {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
sharp(inputJpg).resize(width, height).toFile(outputJpg, function(err) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
} else {
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).add("sharp-file-buffer", {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
sharp(inputJpg).resize(width, height).toBuffer(function(err, buffer) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
} else {
|
|
||||||
assert.notStrictEqual(null, buffer);
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).add("sharp-file-buffer-sharpen", {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
sharp(inputJpg).resize(width, height).sharpen().toBuffer(function(err, buffer) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
} else {
|
|
||||||
assert.notStrictEqual(null, buffer);
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).add("sharp-file-buffer-progressive", {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
sharp(inputJpg).resize(width, height).progressive().toBuffer(function(err, buffer) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
} else {
|
|
||||||
assert.notStrictEqual(null, buffer);
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).add("sharp-file-buffer-rotate", {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
sharp(inputJpg).rotate(90).resize(width, height).toBuffer(function(err, buffer) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
} else {
|
|
||||||
assert.notStrictEqual(null, buffer);
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).add("sharp-file-buffer-sequentialRead", {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
sharp(inputJpg).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();
|
|
||||||
},
|
|
||||||
png: function(callback) {
|
|
||||||
var inputPngBuffer = fs.readFileSync(inputPng);
|
|
||||||
(new Benchmark.Suite("png")).add("imagemagick-file-file", {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
imagemagick.resize({
|
|
||||||
srcPath: inputPng,
|
|
||||||
dstPath: outputPng,
|
|
||||||
width: width,
|
|
||||||
height: height
|
|
||||||
}, function(err) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
} else {
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).add("imagemagick-native-buffer-buffer", {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
imagemagickNative.convert({
|
|
||||||
srcData: inputPngBuffer,
|
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
format: 'PNG'
|
|
||||||
});
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
}).add("gm-file-file", {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
gm(inputPng).resize(width, height).write(outputPng, function (err) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
} else {
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).add("gm-file-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,
|
|
||||||
fn: function(deferred) {
|
|
||||||
sharp(inputPngBuffer).resize(width, height).toBuffer(function(err, buffer) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
} else {
|
|
||||||
assert.notStrictEqual(null, buffer);
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).add("sharp-file-file", {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
sharp(inputPng).resize(width, height).toFile(outputPng, function(err) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
} else {
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).add("sharp-file-buffer", {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
sharp(inputPng).resize(width, height).toBuffer(function(err, buffer) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
} else {
|
|
||||||
assert.notStrictEqual(null, buffer);
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).add("sharp-file-buffer-sharpen", {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
sharp(inputPng).resize(width, height).sharpen().toBuffer(function(err, buffer) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
} else {
|
|
||||||
assert.notStrictEqual(null, buffer);
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).add("sharp-file-buffer-progressive", {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
sharp(inputPng).resize(width, height).progressive().toBuffer(function(err, buffer) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
} else {
|
|
||||||
assert.notStrictEqual(null, buffer);
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).add("sharp-file-buffer-sequentialRead", {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
sharp(inputPng).sequentialRead().resize(width, height).toBuffer(function(err, buffer) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
} 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"));
|
|
||||||
}).run();
|
|
||||||
},
|
|
||||||
webp: function(callback) {
|
|
||||||
var inputWebpBuffer = fs.readFileSync(inputWebp);
|
|
||||||
(new Benchmark.Suite("webp")).add("sharp-buffer-file", {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
sharp(inputWebpBuffer).resize(width, height).toFile(outputWebp, function(err) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
} else {
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).add("sharp-buffer-buffer", {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
sharp(inputWebpBuffer).resize(width, height).toBuffer(function(err, buffer) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
} else {
|
|
||||||
assert.notStrictEqual(null, buffer);
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).add("sharp-file-file", {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
sharp(inputWebp).resize(width, height).toFile(outputWebp, function(err) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
} else {
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).add("sharp-file-buffer", {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
sharp(inputWebp).resize(width, height).toBuffer(function(err, buffer) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
} else {
|
|
||||||
assert.notStrictEqual(null, buffer);
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).add("sharp-file-buffer-sharpen", {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
sharp(inputWebp).resize(width, height).sharpen().toBuffer(function(err, buffer) {
|
|
||||||
if (err) {
|
|
||||||
throw err;
|
|
||||||
} else {
|
|
||||||
assert.notStrictEqual(null, buffer);
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).add("sharp-file-buffer-sequentialRead", {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
sharp(inputWebp).sequentialRead().resize(width, height).toBuffer(function(err, buffer) {
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}).add("sharp-file-file-sequentialRead", {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
sharp(inputTiff).sequentialRead().resize(width, height).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();
|
|
||||||
}
|
|
||||||
}, function(err, results) {
|
|
||||||
assert(!err, err);
|
|
||||||
Object.keys(results).forEach(function(format) {
|
|
||||||
if (results[format].toString().substr(0, 5) !== "sharp") {
|
|
||||||
console.log("sharp was slower than " + results[format] + " for " + format);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
console.dir(sharp.cache());
|
|
||||||
});
|
|
||||||
@@ -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();
|
|
||||||
266
tests/unit.js
@@ -1,266 +0,0 @@
|
|||||||
/*jslint node: true */
|
|
||||||
/*jslint es5: true */
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var sharp = require("../index");
|
|
||||||
var path = require("path");
|
|
||||||
var imagemagick = require("imagemagick");
|
|
||||||
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
|
|
||||||
|
|
||||||
async.series([
|
|
||||||
// Resize with exact crop
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpg).resize(320, 240).toFile(outputJpg, function(err) {
|
|
||||||
if (err) throw err;
|
|
||||||
imagemagick.identify(outputJpg, function(err, features) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(320, features.width);
|
|
||||||
assert.strictEqual(240, features.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Resize to fixed width
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpg).resize(320).toFile(outputJpg, function(err) {
|
|
||||||
if (err) throw err;
|
|
||||||
imagemagick.identify(outputJpg, function(err, features) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(320, features.width);
|
|
||||||
assert.strictEqual(261, features.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Resize to fixed height
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpg).resize(null, 320).toFile(outputJpg, function(err) {
|
|
||||||
if (err) throw err;
|
|
||||||
imagemagick.identify(outputJpg, function(err, features) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(391, features.width);
|
|
||||||
assert.strictEqual(320, features.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Identity transform
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpg).toFile(outputJpg, function(err) {
|
|
||||||
if (err) throw err;
|
|
||||||
imagemagick.identify(outputJpg, function(err, features) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(2725, features.width);
|
|
||||||
assert.strictEqual(2225, features.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Upscale
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpg).resize(3000).toFile(outputJpg, function(err) {
|
|
||||||
if (err) throw err;
|
|
||||||
imagemagick.identify(outputJpg, function(err, features) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(3000, features.width);
|
|
||||||
assert.strictEqual(2449, features.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Quality
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpg).resize(320, 240).quality(70).jpeg(function(err, buffer70) {
|
|
||||||
if (err) throw err;
|
|
||||||
sharp(inputJpg).resize(320, 240).jpeg(function(err, buffer80) {
|
|
||||||
if (err) throw err;
|
|
||||||
sharp(inputJpg).resize(320, 240).quality(90).jpeg(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().toFile(outputJpg, function(err) {
|
|
||||||
if (err) throw err;
|
|
||||||
imagemagick.identify(outputJpg, function(err, features) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(240, features.width);
|
|
||||||
assert.strictEqual(320, features.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
function(done) {
|
|
||||||
sharp(inputTiff).resize(240, 320).toFile(outputJpg, function(err) {
|
|
||||||
if (err) throw err;
|
|
||||||
imagemagick.identify(outputJpg, function(err, features) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(240, features.width);
|
|
||||||
assert.strictEqual(320, features.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Resize to max width or height considering ratio (landscape)
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpg).resize(320, 320).max().toFile(outputJpg, function(err) {
|
|
||||||
if (err) throw err;
|
|
||||||
imagemagick.identify(outputJpg, function(err, features) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(320, features.width);
|
|
||||||
assert.strictEqual(261, features.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Resize to max width or height considering ratio (portrait)
|
|
||||||
function(done) {
|
|
||||||
sharp(inputTiff).resize(320, 320).max().toFile(outputJpg, function(err) {
|
|
||||||
if (err) throw err;
|
|
||||||
imagemagick.identify(outputJpg, function(err, features) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(243, features.width);
|
|
||||||
assert.strictEqual(320, features.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Attempt to resize to max but only provide one dimension, so should default to crop
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpg).resize(320).max().toFile(outputJpg, function(err) {
|
|
||||||
if (err) throw err;
|
|
||||||
imagemagick.identify(outputJpg, function(err, features) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(320, features.width);
|
|
||||||
assert.strictEqual(261, features.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).toFile(outputJpg, function(err) {
|
|
||||||
if (err) throw err;
|
|
||||||
imagemagick.identify(outputJpg, function(err, features) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(320, features.width);
|
|
||||||
assert.strictEqual(240, features.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Input image has Orientation EXIF tag but do not rotate output
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpgWithExif).resize(320).toFile(outputJpg, function(err) {
|
|
||||||
if (err) throw err;
|
|
||||||
imagemagick.identify(outputJpg, function(err, features) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(320, features.width);
|
|
||||||
assert.strictEqual(426, features.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Input image has Orientation EXIF tag value of 8 (270 degrees), auto-rotate
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpgWithExif).rotate().resize(320).toFile(outputJpg, function(err) {
|
|
||||||
if (err) throw err;
|
|
||||||
imagemagick.identify(outputJpg, function(err, features) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(320, features.width);
|
|
||||||
assert.strictEqual(240, features.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Attempt to auto-rotate using image that has no EXIF
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpg).rotate().resize(320).toFile(outputJpg, function(err) {
|
|
||||||
if (err) throw err;
|
|
||||||
imagemagick.identify(outputJpg, function(err, features) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(320, features.width);
|
|
||||||
assert.strictEqual(261, features.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().write(outputJpg, function(err) {
|
|
||||||
if (err) throw err;
|
|
||||||
imagemagick.identify(outputJpg, function(err, features) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(2725, features.width);
|
|
||||||
assert.strictEqual(2225, features.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().write(outputJpg, function(err) {
|
|
||||||
if (err) throw err;
|
|
||||||
imagemagick.identify(outputJpg, function(err, features) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(2725, features.width);
|
|
||||||
assert.strictEqual(2225, features.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// Promises/A+
|
|
||||||
function(done) {
|
|
||||||
sharp(inputJpg).resize(320, 240).toFile(outputJpg).then(function() {
|
|
||||||
imagemagick.identify(outputJpg, function(err, features) {
|
|
||||||
assert.strictEqual(320, features.width);
|
|
||||||
assert.strictEqual(240, features.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();
|
|
||||||
}
|
|
||||||
]);
|
|
||||||