Compare commits
61 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
18
.gitignore
vendored
@@ -1,19 +1,9 @@
|
||||
lib-cov
|
||||
*.seed
|
||||
*.log
|
||||
*.csv
|
||||
*.dat
|
||||
*.out
|
||||
*.pid
|
||||
*.gz
|
||||
|
||||
pids
|
||||
logs
|
||||
results
|
||||
build
|
||||
node_modules
|
||||
tests/fixtures/output.*
|
||||
tests/libvips.supp
|
||||
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
|
||||
8
.jshintrc
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"strict": true,
|
||||
"node": true,
|
||||
"globals": {
|
||||
"describe": true,
|
||||
"it": true
|
||||
}
|
||||
}
|
||||
20
.npmignore
@@ -1,18 +1,8 @@
|
||||
lib-cov
|
||||
*.seed
|
||||
*.log
|
||||
*.csv
|
||||
*.dat
|
||||
*.out
|
||||
*.pid
|
||||
*.gz
|
||||
|
||||
pids
|
||||
logs
|
||||
results
|
||||
build
|
||||
node_modules
|
||||
|
||||
coverage
|
||||
.jshintignore
|
||||
.jshintrc
|
||||
.gitignore
|
||||
tests
|
||||
.travis.yml
|
||||
test
|
||||
.travis.yml
|
||||
|
||||
15
.travis.yml
@@ -3,15 +3,6 @@ node_js:
|
||||
- "0.10"
|
||||
- "0.11"
|
||||
before_install:
|
||||
- sudo add-apt-repository ppa:lyrasis/precise-backports -y
|
||||
- sudo apt-get update -qq
|
||||
- sudo apt-get install -qq automake gobject-introspection gtk-doc-tools libglib2.0-dev libjpeg-turbo8-dev libpng12-dev libwebp-dev libtiff4-dev libexif-dev libxml2-dev swig graphicsmagick libmagick++-dev
|
||||
- git clone https://github.com/jcupitt/libvips.git
|
||||
- cd libvips
|
||||
- git checkout 7.38
|
||||
- ./bootstrap.sh
|
||||
- ./configure --enable-debug=no --enable-cxx=yes --without-orc --without-python --without-fftw
|
||||
- make
|
||||
- sudo make install
|
||||
- sudo ldconfig
|
||||
- cd $TRAVIS_BUILD_DIR
|
||||
- curl -s https://raw.githubusercontent.com/lovell/sharp/master/preinstall.sh | sudo bash -
|
||||
after_success:
|
||||
- cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js
|
||||
|
||||
188
README.md
@@ -10,13 +10,15 @@
|
||||
|
||||
The typical use case for this high speed Node.js module is to convert large images of many formats to smaller, web-friendly JPEG, PNG and WebP images of varying dimensions.
|
||||
|
||||
The performance of JPEG resizing is typically 8x faster than ImageMagick and GraphicsMagick, based mainly on the number of CPU cores available.
|
||||
This module supports reading and writing JPEG, PNG and WebP images to and from Streams, Buffer objects and the filesystem.
|
||||
It also supports reading images of many other types from the filesystem via libmagick++ or libgraphicsmagick++ if present.
|
||||
Colour spaces, embedded ICC profiles and alpha transparency channels are all handled correctly.
|
||||
|
||||
Memory usage is kept to a minimum, no child processes are spawned, everything remains non-blocking thanks to _libuv_ and Promises/A+ are supported.
|
||||
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.
|
||||
|
||||
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.
|
||||
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/).
|
||||
|
||||
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/).
|
||||
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.
|
||||
|
||||
@@ -29,11 +31,35 @@ This module is powered by the blazingly fast [libvips](https://github.com/jcupit
|
||||
### Prerequisites
|
||||
|
||||
* 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
|
||||
|
||||
@@ -45,54 +71,19 @@ The _gettext_ dependency of _libvips_ [can lead](https://github.com/lovell/sharp
|
||||
|
||||
brew link gettext --force
|
||||
|
||||
### Install libvips on Linux
|
||||
|
||||
#### Ubuntu 14.04 LTS
|
||||
|
||||
sudo apt-get install libvips-dev
|
||||
|
||||
#### Ubuntu 12.04 LTS
|
||||
|
||||
sudo add-apt-repository -y ppa:lyrasis/precise-backports
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y automake build-essential git gobject-introspection gtk-doc-tools libglib2.0-dev libjpeg-turbo8-dev libpng12-dev libwebp-dev libtiff4-dev libexif-dev libxml2-dev swig libmagickwand-dev
|
||||
git clone https://github.com/jcupitt/libvips.git
|
||||
cd libvips
|
||||
git checkout 7.40
|
||||
./bootstrap.sh
|
||||
./configure --enable-debug=no --enable-cxx=yes --without-python --without-orc --without-fftw
|
||||
make
|
||||
sudo make install
|
||||
sudo ldconfig
|
||||
|
||||
#### Debian Jessie
|
||||
|
||||
apt-get install libvips-dev
|
||||
|
||||
#### Centos 6
|
||||
|
||||
sudo yum groupinstall -y "Development Tools"
|
||||
sudo yum install -y gtk-doc libxml2-devel libjpeg-turbo-devel libpng-devel libtiff-devel libexif-devel ImageMagick-devel
|
||||
sudo yum install -y http://li.nux.ro/download/nux/dextop/el6/x86_64/nux-dextop-release-0-2.el6.nux.noarch.rpm
|
||||
sudo yum install -y --enablerepo=nux-dextop gobject-introspection-devel
|
||||
sudo yum install -y http://rpms.famillecollet.com/enterprise/remi-release-6.rpm
|
||||
sudo yum install -y --enablerepo=remi libwebp-devel
|
||||
git clone https://github.com/jcupitt/libvips.git
|
||||
cd libvips
|
||||
git checkout 7.40
|
||||
./bootstrap.sh
|
||||
./configure --prefix=/usr --enable-docs=no --enable-debug=no --enable-cxx=yes --without-orc --without-python --without-fftw
|
||||
make
|
||||
sudo make install
|
||||
sudo ldconfig
|
||||
|
||||
### Install libvips on Heroku
|
||||
### Heroku
|
||||
|
||||
[Alessandro Tagliapietra](https://github.com/alex88) maintains an [Heroku buildpack for libvips](https://github.com/alex88/heroku-buildpack-vips) and its dependencies.
|
||||
|
||||
### Using with gulp.js
|
||||
### Docker
|
||||
|
||||
[Mohammad Prabowo](https://github.com/rizalp) maintains a [gulp.js plugin](https://github.com/rizalp/gulp-sharp).
|
||||
[Marc Bachmann](https://github.com/marcbachmann) maintains a [Dockerfile for libvips](https://github.com/marcbachmann/dockerfile-libvips).
|
||||
|
||||
docker pull marcbachmann/libvips
|
||||
|
||||
### gulp.js
|
||||
|
||||
[Eugeny Vlasenko](https://github.com/mahnunchik) maintains [gulp-responsive](https://www.npmjs.org/package/gulp-responsive) and [Mohammad Prabowo](https://github.com/rizalp) maintains [gulp-sharp](https://www.npmjs.org/package/gulp-sharp).
|
||||
|
||||
## Usage examples
|
||||
|
||||
@@ -232,12 +223,12 @@ sharp(inputBuffer)
|
||||
|
||||
Constructor to which further methods are chained. `input`, if present, can be one of:
|
||||
|
||||
* Buffer containing JPEG, PNG or WebP image data, or
|
||||
* Buffer containing JPEG, PNG, WebP or TIFF image data, or
|
||||
* String containing the filename of an image, with most major formats supported.
|
||||
|
||||
The object returned implements the [stream.Duplex](http://nodejs.org/api/stream.html#stream_class_stream_duplex) class.
|
||||
|
||||
JPEG, PNG or WebP format image data can be streamed into the object when `input` is not provided.
|
||||
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.
|
||||
|
||||
@@ -250,8 +241,9 @@ Fast access to image metadata without decoding any compressed image data.
|
||||
* `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#L502)
|
||||
* `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `scrgb`, `cmyk`, `lab`, `xyz`, `b-w` [...](https://github.com/jcupitt/libvips/blob/master/libvips/iofuncs/enumtypes.c#L522)
|
||||
* `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK
|
||||
* `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
|
||||
|
||||
@@ -321,7 +313,15 @@ Rotate the output image by either an explicit angle or auto-orient based on the
|
||||
|
||||
`angle`, if present, is a Number with a value of `0`, `90`, `180` or `270`.
|
||||
|
||||
Use this method without `angle` to determine the angle from EXIF data. Mirroring is currently unsupported.
|
||||
Use this method without `angle` to determine the angle from EXIF data. Mirroring is supported and may infer the use of a `flip` operation.
|
||||
|
||||
#### 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()
|
||||
|
||||
@@ -329,9 +329,23 @@ Do not enlarge the output image if the input image width *or* height are already
|
||||
|
||||
This is equivalent to GraphicsMagick's `>` geometry option: "change the dimensions of the image only if its width or height exceeds the geometry specification".
|
||||
|
||||
#### sharpen()
|
||||
#### blur([sigma])
|
||||
|
||||
Perform a mild sharpen of the output image. This typically reduces performance by 10%.
|
||||
When used without parameters, performs a fast, mild blur of the output image. This typically reduces performance by 10%.
|
||||
|
||||
When a `sigma` is provided, performs a slower, more accurate Gaussian blur. This typically reduces performance by 25%.
|
||||
|
||||
* `sigma`, if present, is a Number between 0.3 and 1000 representing the approximate blur radius in pixels.
|
||||
|
||||
#### sharpen([radius], [flat], [jagged])
|
||||
|
||||
When used without parameters, performs a fast, mild sharpen of the output image. This typically reduces performance by 10%.
|
||||
|
||||
When a `radius` is provided, performs a slower, more accurate sharpen of the L channel in the LAB colour space. Separate control over the level of sharpening in "flat" and "jagged" areas is available. This typically reduces performance by 50%.
|
||||
|
||||
* `radius`, if present, is an integral Number representing the sharpen mask radius in pixels.
|
||||
* `flat`, if present, is a Number representing the level of sharpening to apply to "flat" areas, defaulting to a value of 1.0.
|
||||
* `jagged`, if present, is a Number representing the level of sharpening to apply to "jagged" areas, defaulting to a value of 2.0.
|
||||
|
||||
#### interpolateWith(interpolator)
|
||||
|
||||
@@ -390,7 +404,9 @@ Use progressive (interlace) scan for JPEG and PNG output. This typically reduces
|
||||
|
||||
#### withMetadata()
|
||||
|
||||
Include all metadata (ICC, EXIF, XMP) from the input image in the output image. The default behaviour is to strip all metadata.
|
||||
Include all metadata (EXIF, XMP, IPTC) from the input image in the output image. This will also convert to and add the latest web-friendly v2 sRGB ICC profile.
|
||||
|
||||
The default behaviour is to strip all metadata and convert to the device-independent sRGB colour space.
|
||||
|
||||
#### compressionLevel(compressionLevel)
|
||||
|
||||
@@ -398,6 +414,12 @@ An advanced setting for the _zlib_ compression level of the lossless PNG output
|
||||
|
||||
`compressionLevel` is a Number between 0 and 9.
|
||||
|
||||
#### withoutAdaptiveFiltering()
|
||||
|
||||
_Requires libvips 7.42.0+_
|
||||
|
||||
An advanced setting to disable adaptive row filtering for the lossless PNG output format.
|
||||
|
||||
### Output methods
|
||||
|
||||
#### toFile(filename, [callback])
|
||||
@@ -407,7 +429,7 @@ An advanced setting for the _zlib_ compression level of the lossless PNG output
|
||||
`callback`, if present, is called with two arguments `(err, info)` where:
|
||||
|
||||
* `err` contains an error message, if any.
|
||||
* `info` contains the output image `format`, `width` and `height`.
|
||||
* `info` contains the output image `format`, `size` (bytes), `width` and `height`.
|
||||
|
||||
A Promises/A+ promise is returned when `callback` is not provided.
|
||||
|
||||
@@ -419,7 +441,7 @@ Write image data to a Buffer, the format of which will match the input image by
|
||||
|
||||
* `err` is an error message, if any.
|
||||
* `buffer` is the output image data.
|
||||
* `info` contains the output image `format`, `width` and `height`.
|
||||
* `info` contains the output image `format`, `size` (bytes), `width` and `height`.
|
||||
|
||||
A Promises/A+ promise is returned when `callback` is not provided.
|
||||
|
||||
@@ -442,7 +464,7 @@ 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 image processing. The default value is the number of CPU cores. A value of `0` will reset to this default.
|
||||
`threads`, if provided, is the Number of threads _libvips'_ should create for processing each image. The default value is the number of CPU cores. A value of `0` will reset to this default.
|
||||
|
||||
This method always returns the current concurrency.
|
||||
|
||||
@@ -452,6 +474,8 @@ sharp.concurrency(2); // 2
|
||||
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.
|
||||
@@ -465,21 +489,52 @@ var counters = sharp.counters(); // { queue: 2, process: 4 }
|
||||
|
||||
## Testing
|
||||
|
||||
### Ubuntu 12.04
|
||||
### Functional tests
|
||||
|
||||
#### Coverage
|
||||
|
||||
[](https://coveralls.io/r/lovell/sharp?branch=master)
|
||||
|
||||
#### Ubuntu 12.04
|
||||
|
||||
[](https://travis-ci.org/lovell/sharp)
|
||||
|
||||
### Centos 6.5
|
||||
#### Centos 6.5
|
||||
|
||||
[](https://snap-ci.com/lovell/sharp/branch/master)
|
||||
|
||||
### It worked on my machine
|
||||
#### It worked on my machine
|
||||
|
||||
```
|
||||
npm test
|
||||
```
|
||||
|
||||
Running the comparative performance tests requires _ImageMagick_ and _GraphicsMagick_.
|
||||
### Memory leak tests
|
||||
|
||||
```
|
||||
cd sharp/test/leak
|
||||
./leak.sh
|
||||
```
|
||||
|
||||
Requires _valgrind_:
|
||||
|
||||
```
|
||||
brew install valgrind
|
||||
```
|
||||
|
||||
```
|
||||
sudo apt-get install -qq valgrind
|
||||
```
|
||||
|
||||
### Benchmark tests
|
||||
|
||||
```
|
||||
cd sharp/test/bench
|
||||
npm install
|
||||
npm test
|
||||
```
|
||||
|
||||
Requires both _ImageMagick_ and _GraphicsMagick_:
|
||||
|
||||
```
|
||||
brew install imagemagick
|
||||
@@ -507,7 +562,7 @@ sudo yum install -y --enablerepo=epel GraphicsMagick
|
||||
|
||||
### The contenders
|
||||
|
||||
* [imagemagick-native](https://github.com/mash/node-imagemagick-native) v1.2.2 - 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/yourdeveloper/node-imagemagick) v0.1.3 - Supports filesystem only and "has been unmaintained for a long time".
|
||||
* [gm](https://github.com/aheckmann/gm) v1.16.0 - Fully featured wrapper around GraphicsMagick.
|
||||
* sharp v0.6.2 - Caching within libvips disabled to ensure a fair comparison.
|
||||
@@ -551,6 +606,7 @@ This module would never have been possible without the help and code contributio
|
||||
* [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!
|
||||
|
||||
|
||||
25
binding.gyp
@@ -1,7 +1,13 @@
|
||||
{
|
||||
'targets': [{
|
||||
'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'
|
||||
},
|
||||
@@ -12,11 +18,20 @@
|
||||
'<!(PKG_CONFIG_PATH="<(PKG_CONFIG_PATH)" pkg-config --cflags vips glib-2.0)',
|
||||
'<!(node -e "require(\'nan\')")'
|
||||
],
|
||||
'cflags': ['-fexceptions', '-Wall', '-O3'],
|
||||
'cflags_cc': ['-std=c++0x', '-fexceptions', '-Wall', '-O3'],
|
||||
'cflags_cc': [
|
||||
'-std=c++0x',
|
||||
'-fexceptions',
|
||||
'-Wall',
|
||||
'-O3'
|
||||
],
|
||||
'xcode_settings': {
|
||||
'OTHER_CFLAGS': ['-std=c++11', '-stdlib=libc++', '-fexceptions', '-Wall', '-O3'],
|
||||
'OTHER_CPLUSPLUSFLAGS': ['-std=c++11', '-stdlib=libc++', '-fexceptions', '-Wall', '-O3'],
|
||||
'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
185
index.js
@@ -1,13 +1,15 @@
|
||||
/*jslint node: true */
|
||||
'use strict';
|
||||
|
||||
var path = require('path');
|
||||
var util = require('util');
|
||||
var stream = require('stream');
|
||||
|
||||
var semver = require('semver');
|
||||
var color = require('color');
|
||||
var Promise = require('bluebird');
|
||||
var BluebirdPromise = require('bluebird');
|
||||
|
||||
var sharp = require('./build/Release/sharp');
|
||||
var libvipsVersion = sharp.libvipsVersion();
|
||||
|
||||
var Sharp = function(input) {
|
||||
if (!(this instanceof Sharp)) {
|
||||
@@ -16,8 +18,11 @@ var Sharp = function(input) {
|
||||
stream.Duplex.call(this);
|
||||
this.options = {
|
||||
// input options
|
||||
bufferIn: null,
|
||||
streamIn: false,
|
||||
sequentialRead: false,
|
||||
// ICC profiles
|
||||
iccProfilePath: path.join(__dirname, 'icc') + path.sep,
|
||||
// resize options
|
||||
topOffsetPre: -1,
|
||||
leftOffsetPre: -1,
|
||||
@@ -32,12 +37,17 @@ var Sharp = function(input) {
|
||||
canvas: 'c',
|
||||
gravity: 0,
|
||||
angle: 0,
|
||||
flip: false,
|
||||
flop: false,
|
||||
withoutEnlargement: false,
|
||||
interpolator: 'bilinear',
|
||||
// operations
|
||||
background: [0, 0, 0, 255],
|
||||
flatten: false,
|
||||
sharpen: false,
|
||||
blurSigma: 0,
|
||||
sharpenRadius: 0,
|
||||
sharpenFlat: 1,
|
||||
sharpenJagged: 2,
|
||||
gamma: 0,
|
||||
greyscale: false,
|
||||
// output options
|
||||
@@ -45,6 +55,7 @@ var Sharp = function(input) {
|
||||
progressive: false,
|
||||
quality: 80,
|
||||
compressionLevel: 6,
|
||||
withoutAdaptiveFiltering: false,
|
||||
streamOut: false,
|
||||
withMetadata: false
|
||||
};
|
||||
@@ -53,10 +64,21 @@ var Sharp = function(input) {
|
||||
this.options.fileIn = input;
|
||||
} else if (typeof input === 'object' && input instanceof Buffer) {
|
||||
// input=buffer
|
||||
if (input.length > 0) {
|
||||
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;
|
||||
} else {
|
||||
throw new Error('Buffer is empty');
|
||||
throw new Error('Buffer contains an unsupported image format. JPEG, PNG, WebP and TIFF are currently supported.');
|
||||
}
|
||||
} else {
|
||||
// input=stream
|
||||
@@ -73,17 +95,17 @@ util.inherits(Sharp, stream.Duplex);
|
||||
Sharp.prototype._write = function(chunk, encoding, callback) {
|
||||
/*jslint unused: false */
|
||||
if (this.options.streamIn) {
|
||||
if (typeof chunk === 'object' || chunk instanceof Buffer) {
|
||||
if (typeof this.options.bufferIn === 'undefined') {
|
||||
// Create new Buffer
|
||||
this.options.bufferIn = new Buffer(chunk.length);
|
||||
chunk.copy(this.options.bufferIn);
|
||||
} else {
|
||||
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 {
|
||||
@@ -99,13 +121,10 @@ module.exports.gravity = {'center': 0, 'centre': 0, 'north': 1, 'east': 2, 'sout
|
||||
|
||||
Sharp.prototype.crop = function(gravity) {
|
||||
this.options.canvas = 'c';
|
||||
if (typeof gravity !== 'undefined') {
|
||||
// Is this a supported gravity?
|
||||
if (!Number.isNaN(gravity) && gravity >= 0 && gravity <= 4) {
|
||||
this.options.gravity = gravity;
|
||||
} else {
|
||||
throw new Error('Unsupported crop gravity ' + gravity);
|
||||
}
|
||||
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;
|
||||
};
|
||||
@@ -120,16 +139,6 @@ Sharp.prototype.extract = function(topOffset, leftOffset, width, height) {
|
||||
return this;
|
||||
};
|
||||
|
||||
/*
|
||||
Deprecated embed* methods, to be removed in v0.8.0
|
||||
*/
|
||||
Sharp.prototype.embedWhite = util.deprecate(function() {
|
||||
return this.background('white').embed();
|
||||
}, "embedWhite() is deprecated, use background('white').embed() instead");
|
||||
Sharp.prototype.embedBlack = util.deprecate(function() {
|
||||
return this.background('black').embed();
|
||||
}, "embedBlack() is deprecated, use background('black').embed() instead");
|
||||
|
||||
/*
|
||||
Set the background colour for embed and flatten operations.
|
||||
Delegates to the 'Color' module, which can throw an Error
|
||||
@@ -172,6 +181,22 @@ Sharp.prototype.rotate = function(angle) {
|
||||
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
|
||||
This is equivalent to GraphicsMagick's ">" geometry option:
|
||||
@@ -182,8 +207,64 @@ Sharp.prototype.withoutEnlargement = function(withoutEnlargement) {
|
||||
return this;
|
||||
};
|
||||
|
||||
Sharp.prototype.sharpen = function(sharpen) {
|
||||
this.options.sharpen = (typeof sharpen === 'boolean') ? sharpen : true;
|
||||
/*
|
||||
Blur the output image.
|
||||
Call without a sigma to use a fast, mild blur.
|
||||
Call with a sigma to use a slower, more accurate Gaussian blur.
|
||||
*/
|
||||
Sharp.prototype.blur = function(sigma) {
|
||||
if (typeof sigma === 'undefined') {
|
||||
// No arguments: default to mild blur
|
||||
this.options.blurSigma = -1;
|
||||
} else if (typeof sigma === 'boolean') {
|
||||
// Boolean argument: apply mild blur?
|
||||
this.options.blurSigma = sigma ? -1 : 0;
|
||||
} else if (typeof sigma === 'number' && !Number.isNaN(sigma) && sigma >= 0.3 && sigma <= 1000) {
|
||||
// Numeric argument: specific sigma
|
||||
this.options.blurSigma = sigma;
|
||||
} else {
|
||||
throw new Error('Invalid blur sigma (0.3 to 1000.0) ' + sigma);
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/*
|
||||
Sharpen the output image.
|
||||
Call without a radius to use a fast, mild sharpen.
|
||||
Call with a radius to use a slow, accurate sharpen using the L of LAB colour space.
|
||||
radius - size of mask in pixels, must be integer
|
||||
flat - level of "flat" area sharpen, default 1
|
||||
jagged - level of "jagged" area sharpen, default 2
|
||||
*/
|
||||
Sharp.prototype.sharpen = function(radius, flat, jagged) {
|
||||
if (typeof radius === 'undefined') {
|
||||
// No arguments: default to mild sharpen
|
||||
this.options.sharpenRadius = -1;
|
||||
} else if (typeof radius === 'boolean') {
|
||||
// Boolean argument: apply mild sharpen?
|
||||
this.options.sharpenRadius = radius ? -1 : 0;
|
||||
} else if (typeof radius === 'number' && !Number.isNaN(radius) && (radius % 1 === 0) && radius >= 1) {
|
||||
// Numeric argument: specific radius
|
||||
this.options.sharpenRadius = radius;
|
||||
// Control over flat areas
|
||||
if (typeof flat !== 'undefined' && flat !== null) {
|
||||
if (typeof flat === 'number' && !Number.isNaN(flat) && flat >= 0) {
|
||||
this.options.sharpenFlat = flat;
|
||||
} else {
|
||||
throw new Error('Invalid sharpen level for flat areas ' + flat + ' (expected >= 0)');
|
||||
}
|
||||
}
|
||||
// Control over jagged areas
|
||||
if (typeof jagged !== 'undefined' && jagged !== null) {
|
||||
if (typeof jagged === 'number' && !Number.isNaN(jagged) && jagged >= 0) {
|
||||
this.options.sharpenJagged = jagged;
|
||||
} else {
|
||||
throw new Error('Invalid sharpen level for jagged areas ' + jagged + ' (expected >= 0)');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Error('Invalid sharpen radius ' + radius + ' (expected integer >= 1)');
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
@@ -247,6 +328,9 @@ Sharp.prototype.quality = function(quality) {
|
||||
return this;
|
||||
};
|
||||
|
||||
/*
|
||||
zlib compression level for PNG output
|
||||
*/
|
||||
Sharp.prototype.compressionLevel = function(compressionLevel) {
|
||||
if (!Number.isNaN(compressionLevel) && compressionLevel >= 0 && compressionLevel <= 9) {
|
||||
this.options.compressionLevel = compressionLevel;
|
||||
@@ -256,6 +340,18 @@ Sharp.prototype.compressionLevel = function(compressionLevel) {
|
||||
return this;
|
||||
};
|
||||
|
||||
/*
|
||||
Disable the use of adaptive row filtering for PNG output - requires libvips 7.41.0+
|
||||
*/
|
||||
Sharp.prototype.withoutAdaptiveFiltering = function(withoutAdaptiveFiltering) {
|
||||
if (semver.gte(libvipsVersion, '7.41.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;
|
||||
@@ -265,7 +361,7 @@ Sharp.prototype.resize = function(width, height) {
|
||||
if (!width) {
|
||||
this.options.width = -1;
|
||||
} else {
|
||||
if (!Number.isNaN(width)) {
|
||||
if (typeof width === 'number' && !Number.isNaN(width)) {
|
||||
this.options.width = width;
|
||||
} else {
|
||||
throw new Error('Invalid width ' + width);
|
||||
@@ -274,7 +370,7 @@ Sharp.prototype.resize = function(width, height) {
|
||||
if (!height) {
|
||||
this.options.height = -1;
|
||||
} else {
|
||||
if (!Number.isNaN(height)) {
|
||||
if (typeof height === 'number' && !Number.isNaN(height)) {
|
||||
this.options.height = height;
|
||||
} else {
|
||||
throw new Error('Invalid height ' + height);
|
||||
@@ -292,7 +388,7 @@ Sharp.prototype.toFile = function(output, callback) {
|
||||
if (typeof callback === 'function') {
|
||||
callback(errOutputInvalid);
|
||||
} else {
|
||||
return Promise.reject(errOutputInvalid);
|
||||
return BluebirdPromise.reject(errOutputInvalid);
|
||||
}
|
||||
} else {
|
||||
if (this.options.fileIn === output) {
|
||||
@@ -300,7 +396,7 @@ Sharp.prototype.toFile = function(output, callback) {
|
||||
if (typeof callback === 'function') {
|
||||
callback(errOutputIsInput);
|
||||
} else {
|
||||
return Promise.reject(errOutputIsInput);
|
||||
return BluebirdPromise.reject(errOutputIsInput);
|
||||
}
|
||||
} else {
|
||||
this.options.output = output;
|
||||
@@ -387,7 +483,7 @@ Sharp.prototype._sharp = function(callback) {
|
||||
// output=promise
|
||||
if (this.options.streamIn) {
|
||||
// output=promise, input=stream
|
||||
return new Promise(function(resolve, reject) {
|
||||
return new BluebirdPromise(function(resolve, reject) {
|
||||
that.on('finish', function() {
|
||||
sharp.resize(that.options, function(err, data) {
|
||||
if (err) {
|
||||
@@ -400,7 +496,7 @@ Sharp.prototype._sharp = function(callback) {
|
||||
});
|
||||
} else {
|
||||
// output=promise, input=file/buffer
|
||||
return new Promise(function(resolve, reject) {
|
||||
return new BluebirdPromise(function(resolve, reject) {
|
||||
sharp.resize(that.options, function(err, data) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
@@ -430,7 +526,7 @@ Sharp.prototype.metadata = function(callback) {
|
||||
return this;
|
||||
} else {
|
||||
if (this.options.streamIn) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
return new BluebirdPromise(function(resolve, reject) {
|
||||
that.on('finish', function() {
|
||||
sharp.metadata(that.options, function(err, data) {
|
||||
if (err) {
|
||||
@@ -442,7 +538,7 @@ Sharp.prototype.metadata = function(callback) {
|
||||
});
|
||||
});
|
||||
} else {
|
||||
return new Promise(function(resolve, reject) {
|
||||
return new BluebirdPromise(function(resolve, reject) {
|
||||
sharp.metadata(that.options, function(err, data) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
@@ -459,10 +555,10 @@ Sharp.prototype.metadata = function(callback) {
|
||||
Get and set cache memory and item limits
|
||||
*/
|
||||
module.exports.cache = function(memory, items) {
|
||||
if (Number.isNaN(memory)) {
|
||||
if (typeof memory !== 'number' || Number.isNaN(memory)) {
|
||||
memory = null;
|
||||
}
|
||||
if (Number.isNaN(items)) {
|
||||
if (typeof items !== 'number' || Number.isNaN(items)) {
|
||||
items = null;
|
||||
}
|
||||
return sharp.cache(memory, items);
|
||||
@@ -472,7 +568,7 @@ module.exports.cache = function(memory, items) {
|
||||
Get and set size of thread pool
|
||||
*/
|
||||
module.exports.concurrency = function(concurrency) {
|
||||
if (Number.isNaN(concurrency)) {
|
||||
if (typeof concurrency !== 'number' || Number.isNaN(concurrency)) {
|
||||
concurrency = null;
|
||||
}
|
||||
return sharp.concurrency(concurrency);
|
||||
@@ -484,3 +580,10 @@ module.exports.concurrency = function(concurrency) {
|
||||
module.exports.counters = function() {
|
||||
return sharp.counters();
|
||||
};
|
||||
|
||||
/*
|
||||
Get the version of the libvips library
|
||||
*/
|
||||
module.exports.libvipsVersion = function() {
|
||||
return libvipsVersion;
|
||||
};
|
||||
|
||||
34
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "sharp",
|
||||
"version": "0.7.0",
|
||||
"version": "0.8.4",
|
||||
"author": "Lovell Fuller <npm@lovell.info>",
|
||||
"contributors": [
|
||||
"Pierre Inglebert <pierre.inglebert@gmail.com>",
|
||||
@@ -10,11 +10,12 @@
|
||||
"Daniel Gasienica <daniel@gasienica.ch>",
|
||||
"Julian Walker <julian@fiftythree.com>",
|
||||
"Amit Pitaru <pitaru.amit@gmail.com>",
|
||||
"Brandon Aaron <hello.brandon@aaron.sh>"
|
||||
"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": {
|
||||
"test": "node tests/unit && node tests/perf"
|
||||
"test": "node ./node_modules/istanbul/lib/cli.js cover ./node_modules/mocha/bin/_mocha -- --slow=5000 --timeout=10000 ./test/unit/*.js"
|
||||
},
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
@@ -26,30 +27,23 @@
|
||||
"png",
|
||||
"webp",
|
||||
"tiff",
|
||||
"gif",
|
||||
"resize",
|
||||
"thumbnail",
|
||||
"sharpen",
|
||||
"crop",
|
||||
"extract",
|
||||
"embed",
|
||||
"libvips",
|
||||
"vips",
|
||||
"fast",
|
||||
"buffer",
|
||||
"stream"
|
||||
"vips"
|
||||
],
|
||||
"dependencies": {
|
||||
"bluebird": "^2.3.5",
|
||||
"color": "^0.7.1",
|
||||
"nan": "^1.3.0"
|
||||
"bluebird": "^2.6.4",
|
||||
"color": "^0.7.3",
|
||||
"nan": "^1.5.1",
|
||||
"semver": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"imagemagick": "^0.1.3",
|
||||
"imagemagick-native": "^1.4.0",
|
||||
"gm": "^1.16.0",
|
||||
"async": "^0.9.0",
|
||||
"benchmark": "^1.0.0"
|
||||
"mocha": "^2.0.1",
|
||||
"mocha-jshint": "^0.0.9",
|
||||
"istanbul": "^0.3.5",
|
||||
"coveralls": "^2.11.2"
|
||||
},
|
||||
"license": "Apache 2.0",
|
||||
"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=0
|
||||
|
||||
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 libmagickwand-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 libmagickwand-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
|
||||
71
src/common.h
Executable file
@@ -0,0 +1,71 @@
|
||||
#ifndef SHARP_COMMON_H
|
||||
#define SHARP_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
|
||||
|
||||
#endif
|
||||
148
src/metadata.cc
Executable file
@@ -0,0 +1,148 @@
|
||||
#include <node.h>
|
||||
#include <vips/vips.h>
|
||||
|
||||
#include "nan.h"
|
||||
|
||||
#include "common.h"
|
||||
#include "metadata.h"
|
||||
|
||||
using namespace v8;
|
||||
using namespace sharp;
|
||||
|
||||
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);
|
||||
} 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);
|
||||
} else {
|
||||
(baton->err).append("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 SHARP_METADATA_H
|
||||
#define SHARP_METADATA_H
|
||||
|
||||
#include "nan.h"
|
||||
|
||||
NAN_METHOD(metadata);
|
||||
|
||||
#endif
|
||||
911
src/resize.cc
Executable file
@@ -0,0 +1,911 @@
|
||||
#include <tuple>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <node.h>
|
||||
#include <node_buffer.h>
|
||||
#include <vips/vips.h>
|
||||
|
||||
#include "nan.h"
|
||||
|
||||
#include "common.h"
|
||||
#include "resize.h"
|
||||
|
||||
using namespace v8;
|
||||
using namespace sharp;
|
||||
|
||||
enum class Canvas {
|
||||
CROP,
|
||||
MAX,
|
||||
EMBED
|
||||
};
|
||||
|
||||
enum class Angle {
|
||||
D0,
|
||||
D90,
|
||||
D180,
|
||||
D270,
|
||||
DLAST
|
||||
};
|
||||
|
||||
struct ResizeBaton {
|
||||
std::string fileIn;
|
||||
void* bufferIn;
|
||||
size_t bufferInLength;
|
||||
std::string iccProfilePath;
|
||||
std::string output;
|
||||
std::string outputFormat;
|
||||
void* bufferOut;
|
||||
size_t bufferOutLength;
|
||||
int topOffsetPre;
|
||||
int leftOffsetPre;
|
||||
int widthPre;
|
||||
int heightPre;
|
||||
int topOffsetPost;
|
||||
int leftOffsetPost;
|
||||
int widthPost;
|
||||
int heightPost;
|
||||
int width;
|
||||
int height;
|
||||
Canvas canvas;
|
||||
int gravity;
|
||||
std::string interpolator;
|
||||
double background[4];
|
||||
bool flatten;
|
||||
double blurSigma;
|
||||
int sharpenRadius;
|
||||
double sharpenFlat;
|
||||
double sharpenJagged;
|
||||
double gamma;
|
||||
bool greyscale;
|
||||
int angle;
|
||||
bool flip;
|
||||
bool flop;
|
||||
bool progressive;
|
||||
bool withoutEnlargement;
|
||||
VipsAccess accessMethod;
|
||||
int quality;
|
||||
int compressionLevel;
|
||||
bool withoutAdaptiveFiltering;
|
||||
std::string err;
|
||||
bool withMetadata;
|
||||
|
||||
ResizeBaton():
|
||||
bufferInLength(0),
|
||||
outputFormat(""),
|
||||
bufferOutLength(0),
|
||||
topOffsetPre(-1),
|
||||
topOffsetPost(-1),
|
||||
canvas(Canvas::CROP),
|
||||
gravity(0),
|
||||
flatten(false),
|
||||
blurSigma(0.0),
|
||||
sharpenRadius(0),
|
||||
sharpenFlat(1.0),
|
||||
sharpenJagged(2.0),
|
||||
gamma(0.0),
|
||||
greyscale(false),
|
||||
angle(0),
|
||||
flip(false),
|
||||
flop(false),
|
||||
progressive(false),
|
||||
withoutEnlargement(false),
|
||||
quality(80),
|
||||
compressionLevel(6),
|
||||
withoutAdaptiveFiltering(false),
|
||||
withMetadata(false) {
|
||||
background[0] = 0.0;
|
||||
background[1] = 0.0;
|
||||
background[2] = 0.0;
|
||||
background[3] = 255.0;
|
||||
}
|
||||
};
|
||||
|
||||
class ResizeWorker : public NanAsyncWorker {
|
||||
|
||||
public:
|
||||
ResizeWorker(NanCallback *callback, ResizeBaton *baton) : NanAsyncWorker(callback), baton(baton) {}
|
||||
~ResizeWorker() {}
|
||||
|
||||
/*
|
||||
libuv worker
|
||||
*/
|
||||
void Execute() {
|
||||
// Decrement queued task counter
|
||||
g_atomic_int_dec_and_test(&counterQueue);
|
||||
// Increment processing task counter
|
||||
g_atomic_int_inc(&counterProcess);
|
||||
|
||||
// Latest v2 sRGB ICC profile
|
||||
std::string srgbProfile = baton->iccProfilePath + "sRGB_IEC61966-2-1_black_scaled.icc";
|
||||
|
||||
// Hang image references from this hook object
|
||||
VipsObject *hook = reinterpret_cast<VipsObject*>(vips_image_new());
|
||||
|
||||
// Input
|
||||
ImageType inputImageType = ImageType::UNKNOWN;
|
||||
VipsImage *image;
|
||||
if (baton->bufferInLength > 1) {
|
||||
// From buffer
|
||||
inputImageType = DetermineImageType(baton->bufferIn, baton->bufferInLength);
|
||||
if (inputImageType != ImageType::UNKNOWN) {
|
||||
image = InitImage(inputImageType, baton->bufferIn, baton->bufferInLength, baton->accessMethod);
|
||||
} else {
|
||||
(baton->err).append("Input buffer contains unsupported image format");
|
||||
}
|
||||
} else {
|
||||
// From file
|
||||
inputImageType = DetermineImageType(baton->fileIn.c_str());
|
||||
if (inputImageType != ImageType::UNKNOWN) {
|
||||
image = InitImage(inputImageType, baton->fileIn.c_str(), baton->accessMethod);
|
||||
} else {
|
||||
(baton->err).append("File is of an unsupported image format");
|
||||
}
|
||||
}
|
||||
if (inputImageType == ImageType::UNKNOWN) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
vips_object_local(hook, image);
|
||||
|
||||
// Pre extraction
|
||||
if (baton->topOffsetPre != -1) {
|
||||
VipsImage *extractedPre;
|
||||
if (vips_extract_area(image, &extractedPre, baton->leftOffsetPre, baton->topOffsetPre, baton->widthPre, baton->heightPre, NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
vips_object_local(hook, extractedPre);
|
||||
image = extractedPre;
|
||||
}
|
||||
|
||||
// Get input image width and height
|
||||
int inputWidth = image->Xsize;
|
||||
int inputHeight = image->Ysize;
|
||||
|
||||
// Calculate angle of rotation, to be carried out later
|
||||
Angle rotation;
|
||||
bool flip;
|
||||
std::tie(rotation, flip) = CalculateRotationAndFlip(baton->angle, image);
|
||||
if (rotation == Angle::D90 || rotation == Angle::D270) {
|
||||
// Swap input output width and height when rotating by 90 or 270 degrees
|
||||
int swap = inputWidth;
|
||||
inputWidth = inputHeight;
|
||||
inputHeight = swap;
|
||||
}
|
||||
if (flip && !baton->flip) {
|
||||
// Add flip operation due to EXIF mirroring
|
||||
baton->flip = TRUE;
|
||||
}
|
||||
|
||||
// Get window size of interpolator, used for determining shrink vs affine
|
||||
int interpolatorWindowSize = InterpolatorWindowSize(baton->interpolator.c_str());
|
||||
|
||||
// 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->canvas == Canvas::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->canvas == Canvas::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;
|
||||
}
|
||||
|
||||
// Calculate integral box shrink
|
||||
int shrink = 1;
|
||||
if (factor >= 2 && interpolatorWindowSize > 3) {
|
||||
// Shrink less, affine more with interpolators that use at least 4x4 pixel window, e.g. bicubic
|
||||
shrink = floor(factor * 3.0 / interpolatorWindowSize);
|
||||
} else {
|
||||
shrink = floor(factor);
|
||||
}
|
||||
if (shrink < 1) {
|
||||
shrink = 1;
|
||||
}
|
||||
|
||||
// Calculate residual float affine transformation
|
||||
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->withoutEnlargement) {
|
||||
if (inputWidth < baton->width || inputHeight < baton->height) {
|
||||
factor = 1;
|
||||
shrink = 1;
|
||||
residual = 0;
|
||||
baton->width = inputWidth;
|
||||
baton->height = inputHeight;
|
||||
}
|
||||
}
|
||||
|
||||
// Try to use libjpeg shrink-on-load, but not when applying gamma correction or pre-resize extract
|
||||
int shrink_on_load = 1;
|
||||
if (inputImageType == ImageType::JPEG && shrink >= 2 && baton->gamma == 0 && baton->topOffsetPre == -1) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
if (shrink_on_load > 1) {
|
||||
// Recalculate integral shrink and double residual
|
||||
factor = std::max(factor, 1.0);
|
||||
if (factor >= 2 && interpolatorWindowSize > 3) {
|
||||
shrink = floor(factor * 3.0 / interpolatorWindowSize);
|
||||
} else {
|
||||
shrink = floor(factor);
|
||||
}
|
||||
residual = static_cast<double>(shrink) / factor;
|
||||
// Reload input using shrink-on-load
|
||||
VipsImage *shrunkOnLoad;
|
||||
if (baton->bufferInLength > 1) {
|
||||
if (vips_jpegload_buffer(baton->bufferIn, baton->bufferInLength, &shrunkOnLoad, "shrink", shrink_on_load, NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
} else {
|
||||
if (vips_jpegload((baton->fileIn).c_str(), &shrunkOnLoad, "shrink", shrink_on_load, NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
}
|
||||
vips_object_local(hook, shrunkOnLoad);
|
||||
image = shrunkOnLoad;
|
||||
}
|
||||
|
||||
// Ensure we're using a device-independent colour space
|
||||
if (HasProfile(image)) {
|
||||
// Convert to sRGB using embedded profile
|
||||
VipsImage *transformed;
|
||||
if (!vips_icc_transform(image, &transformed, srgbProfile.c_str(), "embedded", TRUE, NULL)) {
|
||||
// Embedded profile can fail, so only update references on success
|
||||
vips_object_local(hook, transformed);
|
||||
image = transformed;
|
||||
}
|
||||
} else if (image->Type == VIPS_INTERPRETATION_CMYK) {
|
||||
// Convert to sRGB using default "USWebCoatedSWOP" CMYK profile
|
||||
std::string cmykProfile = baton->iccProfilePath + "USWebCoatedSWOP.icc";
|
||||
VipsImage *transformed;
|
||||
if (vips_icc_transform(image, &transformed, srgbProfile.c_str(), "input_profile", cmykProfile.c_str(), NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
vips_object_local(hook, transformed);
|
||||
image = transformed;
|
||||
}
|
||||
|
||||
// Flatten image to remove alpha channel
|
||||
if (baton->flatten && HasAlpha(image)) {
|
||||
// Background colour
|
||||
VipsArrayDouble *background = vips_array_double_newv(
|
||||
3, // Ignore alpha channel as we're about to remove it
|
||||
baton->background[0],
|
||||
baton->background[1],
|
||||
baton->background[2]
|
||||
);
|
||||
VipsImage *flattened;
|
||||
if (vips_flatten(image, &flattened, "background", background, NULL)) {
|
||||
vips_area_unref(reinterpret_cast<VipsArea*>(background));
|
||||
return Error(baton, hook);
|
||||
};
|
||||
vips_area_unref(reinterpret_cast<VipsArea*>(background));
|
||||
vips_object_local(hook, flattened);
|
||||
image = flattened;
|
||||
}
|
||||
|
||||
// Gamma encoding (darken)
|
||||
if (baton->gamma >= 1 && baton->gamma <= 3) {
|
||||
VipsImage *gammaEncoded;
|
||||
if (vips_gamma(image, &gammaEncoded, "exponent", 1.0 / baton->gamma, NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
vips_object_local(hook, gammaEncoded);
|
||||
image = gammaEncoded;
|
||||
}
|
||||
|
||||
// Convert to greyscale (linear, therefore after gamma encoding, if any)
|
||||
if (baton->greyscale) {
|
||||
VipsImage *greyscale;
|
||||
if (vips_colourspace(image, &greyscale, VIPS_INTERPRETATION_B_W, NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
vips_object_local(hook, greyscale);
|
||||
image = greyscale;
|
||||
}
|
||||
|
||||
if (shrink > 1) {
|
||||
VipsImage *shrunk;
|
||||
// Use vips_shrink with the integral reduction
|
||||
if (vips_shrink(image, &shrunk, shrink, shrink, NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
vips_object_local(hook, shrunk);
|
||||
image = shrunk;
|
||||
// Recalculate residual float based on dimensions of required vs shrunk images
|
||||
double shrunkWidth = shrunk->Xsize;
|
||||
double shrunkHeight = shrunk->Ysize;
|
||||
if (rotation == Angle::D90 || rotation == Angle::D270) {
|
||||
// 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->canvas == Canvas::EMBED) {
|
||||
residual = std::min(residualx, residualy);
|
||||
} else {
|
||||
residual = std::max(residualx, residualy);
|
||||
}
|
||||
}
|
||||
|
||||
// Use vips_affine with the remaining float part
|
||||
if (residual != 0.0) {
|
||||
// Apply Gaussian blur before large affine reductions
|
||||
if (residual < 1.0) {
|
||||
// Calculate standard deviation
|
||||
double sigma = ((1.0 / residual) - 0.4) / 3.0;
|
||||
if (sigma >= 0.3) {
|
||||
// Create Gaussian function for standard deviation
|
||||
VipsImage *gaussian;
|
||||
if (vips_gaussmat(&gaussian, sigma, 0.2, "separable", TRUE, "integer", TRUE, NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
vips_object_local(hook, gaussian);
|
||||
// Apply Gaussian function
|
||||
VipsImage *blurred;
|
||||
if (vips_convsep(image, &blurred, gaussian, "precision", VIPS_PRECISION_INTEGER, NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
vips_object_local(hook, blurred);
|
||||
image = blurred;
|
||||
}
|
||||
}
|
||||
// Create interpolator - "bilinear" (default), "bicubic" or "nohalo"
|
||||
VipsInterpolate *interpolator = vips_interpolate_new(baton->interpolator.c_str());
|
||||
vips_object_local(hook, interpolator);
|
||||
// Perform affine transformation
|
||||
VipsImage *affined;
|
||||
if (vips_affine(image, &affined, residual, 0.0, 0.0, residual, "interpolate", interpolator, NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
vips_object_local(hook, affined);
|
||||
image = affined;
|
||||
}
|
||||
|
||||
// Rotate
|
||||
if (rotation != Angle::D0) {
|
||||
VipsImage *rotated;
|
||||
if (vips_rot(image, &rotated, static_cast<VipsAngle>(rotation), NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
vips_object_local(hook, rotated);
|
||||
image = rotated;
|
||||
}
|
||||
|
||||
// Flip (mirror about Y axis)
|
||||
if (baton->flip) {
|
||||
VipsImage *flipped;
|
||||
if (vips_flip(image, &flipped, VIPS_DIRECTION_VERTICAL, NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
vips_object_local(hook, flipped);
|
||||
image = flipped;
|
||||
}
|
||||
|
||||
// Flop (mirror about X axis)
|
||||
if (baton->flop) {
|
||||
VipsImage *flopped;
|
||||
if (vips_flip(image, &flopped, VIPS_DIRECTION_HORIZONTAL, NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
vips_object_local(hook, flopped);
|
||||
image = flopped;
|
||||
}
|
||||
|
||||
// Crop/embed
|
||||
if (image->Xsize != baton->width || image->Ysize != baton->height) {
|
||||
if (baton->canvas == Canvas::EMBED) {
|
||||
// Match background colour space, namely sRGB
|
||||
if (image->Type != VIPS_INTERPRETATION_sRGB) {
|
||||
// Convert to sRGB colour space
|
||||
VipsImage *colourspaced;
|
||||
if (vips_colourspace(image, &colourspaced, VIPS_INTERPRETATION_sRGB, NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
vips_object_local(hook, colourspaced);
|
||||
image = colourspaced;
|
||||
}
|
||||
// Add non-transparent alpha channel, if required
|
||||
if (baton->background[3] < 255.0 && !HasAlpha(image)) {
|
||||
// Create single-channel transparency
|
||||
VipsImage *black;
|
||||
if (vips_black(&black, image->Xsize, image->Ysize, "bands", 1, NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
vips_object_local(hook, black);
|
||||
// Invert to become non-transparent
|
||||
VipsImage *alpha;
|
||||
if (vips_invert(black, &alpha, NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
vips_object_local(hook, alpha);
|
||||
// Append alpha channel to existing image
|
||||
VipsImage *joined;
|
||||
if (vips_bandjoin2(image, alpha, &joined, NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
vips_object_local(hook, joined);
|
||||
image = joined;
|
||||
}
|
||||
// Create background
|
||||
VipsArrayDouble *background;
|
||||
if (baton->background[3] < 255.0) {
|
||||
background = vips_array_double_newv(
|
||||
4, baton->background[0], baton->background[1], baton->background[2], baton->background[3]
|
||||
);
|
||||
} else {
|
||||
background = vips_array_double_newv(
|
||||
3, baton->background[0], baton->background[1], baton->background[2]
|
||||
);
|
||||
}
|
||||
// Embed
|
||||
int left = (baton->width - image->Xsize) / 2;
|
||||
int top = (baton->height - image->Ysize) / 2;
|
||||
VipsImage *embedded;
|
||||
if (vips_embed(image, &embedded, left, top, baton->width, baton->height,
|
||||
"extend", VIPS_EXTEND_BACKGROUND, "background", background, NULL
|
||||
)) {
|
||||
vips_area_unref(reinterpret_cast<VipsArea*>(background));
|
||||
return Error(baton, hook);
|
||||
}
|
||||
vips_area_unref(reinterpret_cast<VipsArea*>(background));
|
||||
vips_object_local(hook, embedded);
|
||||
image = embedded;
|
||||
} else {
|
||||
// Crop/max
|
||||
int left;
|
||||
int top;
|
||||
std::tie(left, top) = CalculateCrop(image->Xsize, image->Ysize, baton->width, baton->height, baton->gravity);
|
||||
int width = std::min(image->Xsize, baton->width);
|
||||
int height = std::min(image->Ysize, baton->height);
|
||||
VipsImage *extracted;
|
||||
if (vips_extract_area(image, &extracted, left, top, width, height, NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
vips_object_local(hook, extracted);
|
||||
image = extracted;
|
||||
}
|
||||
}
|
||||
|
||||
// Post extraction
|
||||
if (baton->topOffsetPost != -1) {
|
||||
VipsImage *extractedPost;
|
||||
if (vips_extract_area(image, &extractedPost, baton->leftOffsetPost, baton->topOffsetPost, baton->widthPost, baton->heightPost, NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
vips_object_local(hook, extractedPost);
|
||||
image = extractedPost;
|
||||
}
|
||||
|
||||
// Blur
|
||||
if (baton->blurSigma != 0.0) {
|
||||
VipsImage *blurred;
|
||||
if (baton->blurSigma < 0.0) {
|
||||
// Fast, mild blur - averages neighbouring pixels
|
||||
VipsImage *blur = vips_image_new_matrixv(3, 3,
|
||||
1.0, 1.0, 1.0,
|
||||
1.0, 1.0, 1.0,
|
||||
1.0, 1.0, 1.0);
|
||||
vips_image_set_double(blur, "scale", 9);
|
||||
vips_object_local(hook, blur);
|
||||
if (vips_conv(image, &blurred, blur, NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
} else {
|
||||
// Slower, accurate Gaussian blur
|
||||
// Create Gaussian function for standard deviation
|
||||
VipsImage *gaussian;
|
||||
if (vips_gaussmat(&gaussian, baton->blurSigma, 0.2, "separable", TRUE, "integer", TRUE, NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
vips_object_local(hook, gaussian);
|
||||
// Apply Gaussian function
|
||||
if (vips_convsep(image, &blurred, gaussian, "precision", VIPS_PRECISION_INTEGER, NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
}
|
||||
vips_object_local(hook, blurred);
|
||||
image = blurred;
|
||||
}
|
||||
|
||||
// Sharpen
|
||||
if (baton->sharpenRadius != 0) {
|
||||
VipsImage *sharpened;
|
||||
if (baton->sharpenRadius == -1) {
|
||||
// Fast, mild 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);
|
||||
vips_object_local(hook, sharpen);
|
||||
if (vips_conv(image, &sharpened, sharpen, NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
} else {
|
||||
// Slow, accurate sharpen in LAB colour space, with control over flat vs jagged areas
|
||||
if (vips_sharpen(image, &sharpened, "radius", baton->sharpenRadius, "m1", baton->sharpenFlat, "m2", baton->sharpenJagged, NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
}
|
||||
vips_object_local(hook, sharpened);
|
||||
image = sharpened;
|
||||
}
|
||||
|
||||
// Gamma decoding (brighten)
|
||||
if (baton->gamma >= 1 && baton->gamma <= 3) {
|
||||
VipsImage *gammaDecoded;
|
||||
if (vips_gamma(image, &gammaDecoded, "exponent", baton->gamma, NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
vips_object_local(hook, gammaDecoded);
|
||||
image = gammaDecoded;
|
||||
}
|
||||
|
||||
// Convert image to sRGB, if not already
|
||||
if (image->Type != VIPS_INTERPRETATION_sRGB) {
|
||||
// Switch intrepretation to sRGB
|
||||
VipsImage *rgb;
|
||||
if (vips_colourspace(image, &rgb, VIPS_INTERPRETATION_sRGB, NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
vips_object_local(hook, rgb);
|
||||
image = rgb;
|
||||
// Tranform colours from embedded profile to sRGB profile
|
||||
if (baton->withMetadata && HasProfile(image)) {
|
||||
VipsImage *profiled;
|
||||
if (vips_icc_transform(image, &profiled, srgbProfile.c_str(), "embedded", TRUE, NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
vips_object_local(hook, profiled);
|
||||
image = profiled;
|
||||
}
|
||||
}
|
||||
|
||||
#if !(VIPS_MAJOR_VERSION >= 7 && VIPS_MINOR_VERSION >= 40 && VIPS_MINOR_VERSION >= 5)
|
||||
// Generate image tile cache when interlace output is required - no longer required as of libvips 7.40.5+
|
||||
if (baton->progressive) {
|
||||
VipsImage *cached;
|
||||
if (vips_tilecache(image, &cached, "threaded", TRUE, "persistent", TRUE, "max_tiles", -1, NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
vips_object_local(hook, cached);
|
||||
image = cached;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Output
|
||||
if (baton->output == "__jpeg" || (baton->output == "__input" && inputImageType == ImageType::JPEG)) {
|
||||
// Write JPEG to buffer
|
||||
if (vips_jpegsave_buffer(image, &baton->bufferOut, &baton->bufferOutLength, "strip", !baton->withMetadata,
|
||||
"Q", baton->quality, "optimize_coding", TRUE, "interlace", baton->progressive, NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
baton->outputFormat = "jpeg";
|
||||
} else if (baton->output == "__png" || (baton->output == "__input" && inputImageType == ImageType::PNG)) {
|
||||
#if (VIPS_MAJOR_VERSION >= 7 && VIPS_MINOR_VERSION >= 42)
|
||||
// Select PNG row filter
|
||||
int filter = baton->withoutAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_NONE : VIPS_FOREIGN_PNG_FILTER_ALL;
|
||||
// Write PNG to buffer
|
||||
if (vips_pngsave_buffer(image, &baton->bufferOut, &baton->bufferOutLength, "strip", !baton->withMetadata,
|
||||
"compression", baton->compressionLevel, "interlace", baton->progressive, "filter", filter, NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
#else
|
||||
// Write PNG to buffer
|
||||
if (vips_pngsave_buffer(image, &baton->bufferOut, &baton->bufferOutLength, "strip", !baton->withMetadata,
|
||||
"compression", baton->compressionLevel, "interlace", baton->progressive, NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
#endif
|
||||
baton->outputFormat = "png";
|
||||
} else if (baton->output == "__webp" || (baton->output == "__input" && inputImageType == ImageType::WEBP)) {
|
||||
// Write WEBP to buffer
|
||||
if (vips_webpsave_buffer(image, &baton->bufferOut, &baton->bufferOutLength, "strip", !baton->withMetadata,
|
||||
"Q", baton->quality, NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
baton->outputFormat = "webp";
|
||||
} else {
|
||||
bool outputJpeg = IsJpeg(baton->output);
|
||||
bool outputPng = IsPng(baton->output);
|
||||
bool outputWebp = IsWebp(baton->output);
|
||||
bool outputTiff = IsTiff(baton->output);
|
||||
bool matchInput = !(outputJpeg || outputPng || outputWebp || outputTiff);
|
||||
if (outputJpeg || (matchInput && inputImageType == ImageType::JPEG)) {
|
||||
// Write JPEG to file
|
||||
if (vips_jpegsave(image, baton->output.c_str(), "strip", !baton->withMetadata,
|
||||
"Q", baton->quality, "optimize_coding", TRUE, "interlace", baton->progressive, NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
baton->outputFormat = "jpeg";
|
||||
} else if (outputPng || (matchInput && inputImageType == ImageType::PNG)) {
|
||||
#if (VIPS_MAJOR_VERSION >= 7 && VIPS_MINOR_VERSION >= 41)
|
||||
// Select PNG row filter
|
||||
int filter = baton->withoutAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_NONE : VIPS_FOREIGN_PNG_FILTER_ALL;
|
||||
// Write PNG to file
|
||||
if (vips_pngsave(image, baton->output.c_str(), "strip", !baton->withMetadata,
|
||||
"compression", baton->compressionLevel, "interlace", baton->progressive, "filter", filter, NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
#else
|
||||
// Write PNG to file
|
||||
if (vips_pngsave(image, baton->output.c_str(), "strip", !baton->withMetadata,
|
||||
"compression", baton->compressionLevel, "interlace", baton->progressive, NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
#endif
|
||||
baton->outputFormat = "png";
|
||||
} else if (outputWebp || (matchInput && inputImageType == ImageType::WEBP)) {
|
||||
// Write WEBP to file
|
||||
if (vips_webpsave(image, baton->output.c_str(), "strip", !baton->withMetadata,
|
||||
"Q", baton->quality, NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
baton->outputFormat = "webp";
|
||||
} else if (outputTiff || (matchInput && inputImageType == ImageType::TIFF)) {
|
||||
// Write TIFF to file
|
||||
if (vips_tiffsave(image, baton->output.c_str(), "strip", !baton->withMetadata,
|
||||
"compression", VIPS_FOREIGN_TIFF_COMPRESSION_JPEG, "Q", baton->quality, NULL)) {
|
||||
return Error(baton, hook);
|
||||
}
|
||||
baton->outputFormat = "tiff";
|
||||
} else {
|
||||
(baton->err).append("Unsupported output " + baton->output);
|
||||
return Error(baton, hook);
|
||||
}
|
||||
}
|
||||
// Clean up any dangling image references
|
||||
g_object_unref(hook);
|
||||
// Clean up libvips' per-request data and threads
|
||||
vips_error_clear();
|
||||
vips_thread_shutdown();
|
||||
}
|
||||
|
||||
void HandleOKCallback () {
|
||||
NanScope();
|
||||
|
||||
// Free input Buffer
|
||||
if (baton->bufferInLength > 0) {
|
||||
g_free(baton->bufferIn);
|
||||
}
|
||||
|
||||
Handle<Value> argv[3] = { NanNull(), NanNull(), NanNull() };
|
||||
if (!baton->err.empty()) {
|
||||
// Error
|
||||
argv[0] = Exception::Error(NanNew<String>(baton->err.data(), baton->err.size()));
|
||||
} else {
|
||||
int width = baton->width;
|
||||
int height = baton->height;
|
||||
if (baton->topOffsetPre != -1 && (baton->width == -1 || baton->height == -1)) {
|
||||
width = baton->widthPre;
|
||||
height = baton->heightPre;
|
||||
}
|
||||
if (baton->topOffsetPost != -1) {
|
||||
width = baton->widthPost;
|
||||
height = baton->heightPost;
|
||||
}
|
||||
// Info Object
|
||||
Local<Object> info = NanNew<Object>();
|
||||
info->Set(NanNew<String>("format"), NanNew<String>(baton->outputFormat));
|
||||
info->Set(NanNew<String>("width"), NanNew<Integer>(width));
|
||||
info->Set(NanNew<String>("height"), NanNew<Integer>(height));
|
||||
|
||||
if (baton->bufferOutLength > 0) {
|
||||
// Copy data to new Buffer
|
||||
argv[1] = NanNewBufferHandle(static_cast<char*>(baton->bufferOut), baton->bufferOutLength);
|
||||
g_free(baton->bufferOut);
|
||||
// Add buffer size to info
|
||||
info->Set(NanNew<String>("size"), NanNew<Uint32>(static_cast<uint32_t>(baton->bufferOutLength)));
|
||||
argv[2] = info;
|
||||
} else {
|
||||
// Add file size to info
|
||||
struct stat st;
|
||||
g_stat(baton->output.c_str(), &st);
|
||||
info->Set(NanNew<String>("size"), NanNew<Uint32>(static_cast<uint32_t>(st.st_size)));
|
||||
argv[1] = info;
|
||||
}
|
||||
}
|
||||
delete baton;
|
||||
|
||||
// Decrement processing task counter
|
||||
g_atomic_int_dec_and_test(&counterProcess);
|
||||
|
||||
// Return to JavaScript
|
||||
callback->Call(3, argv);
|
||||
}
|
||||
|
||||
private:
|
||||
ResizeBaton* baton;
|
||||
|
||||
/*
|
||||
Calculate the angle of rotation and need-to-flip for the output image.
|
||||
In order of priority:
|
||||
1. Use explicitly requested angle (supports 90, 180, 270)
|
||||
2. Use input image EXIF Orientation header - supports mirroring
|
||||
3. Otherwise default to zero, i.e. no rotation
|
||||
*/
|
||||
std::tuple<Angle, bool>
|
||||
CalculateRotationAndFlip(int const angle, VipsImage const *input) {
|
||||
Angle rotate = Angle::D0;
|
||||
bool flip = FALSE;
|
||||
if (angle == -1) {
|
||||
switch(ExifOrientation(input)) {
|
||||
case 6: rotate = Angle::D90; break;
|
||||
case 3: rotate = Angle::D180; break;
|
||||
case 8: rotate = Angle::D270; break;
|
||||
case 2: flip = TRUE; break; // flip 1
|
||||
case 7: flip = TRUE; rotate = Angle::D90; break; // flip 6
|
||||
case 4: flip = TRUE; rotate = Angle::D180; break; // flip 3
|
||||
case 5: flip = TRUE; rotate = Angle::D270; break; // flip 8
|
||||
}
|
||||
} else {
|
||||
if (angle == 90) {
|
||||
rotate = Angle::D90;
|
||||
} else if (angle == 180) {
|
||||
rotate = Angle::D180;
|
||||
} else if (angle == 270) {
|
||||
rotate = Angle::D270;
|
||||
}
|
||||
}
|
||||
return std::make_tuple(rotate, flip);
|
||||
}
|
||||
|
||||
/*
|
||||
Calculate the (left, top) coordinates of the output image
|
||||
within the input image, applying the given gravity.
|
||||
*/
|
||||
std::tuple<int, int>
|
||||
CalculateCrop(int const inWidth, int const inHeight, int const outWidth, int const outHeight, int const gravity) {
|
||||
int left = 0;
|
||||
int top = 0;
|
||||
switch (gravity) {
|
||||
case 1: // North
|
||||
left = (inWidth - outWidth + 1) / 2;
|
||||
break;
|
||||
case 2: // East
|
||||
left = inWidth - outWidth;
|
||||
top = (inHeight - outHeight + 1) / 2;
|
||||
break;
|
||||
case 3: // South
|
||||
left = (inWidth - outWidth + 1) / 2;
|
||||
top = inHeight - outHeight;
|
||||
break;
|
||||
case 4: // West
|
||||
top = (inHeight - outHeight + 1) / 2;
|
||||
break;
|
||||
default: // Centre
|
||||
left = (inWidth - outWidth + 1) / 2;
|
||||
top = (inHeight - outHeight + 1) / 2;
|
||||
}
|
||||
return std::make_tuple(left, top);
|
||||
}
|
||||
|
||||
/*
|
||||
Copy then clear the error message.
|
||||
Unref all transitional images on the hook.
|
||||
Clear all thread-local data.
|
||||
*/
|
||||
void Error(ResizeBaton *baton, VipsObject *hook) {
|
||||
// Get libvips' error message
|
||||
(baton->err).append(vips_error_buffer());
|
||||
// Clean up any dangling image references
|
||||
g_object_unref(hook);
|
||||
// Clean up libvips' per-request data and threads
|
||||
vips_error_clear();
|
||||
vips_thread_shutdown();
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
resize(options, output, callback)
|
||||
*/
|
||||
NAN_METHOD(resize) {
|
||||
NanScope();
|
||||
|
||||
// V8 objects are converted to non-V8 types held in the baton struct
|
||||
ResizeBaton *baton = new ResizeBaton;
|
||||
Local<Object> options = args[0]->ToObject();
|
||||
|
||||
// Input filename
|
||||
baton->fileIn = *String::Utf8Value(options->Get(NanNew<String>("fileIn"))->ToString());
|
||||
baton->accessMethod = options->Get(NanNew<String>("sequentialRead"))->BooleanValue() ? VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_RANDOM;
|
||||
// Input Buffer object
|
||||
if (options->Get(NanNew<String>("bufferIn"))->IsObject()) {
|
||||
Local<Object> buffer = options->Get(NanNew<String>("bufferIn"))->ToObject();
|
||||
// Take a copy of the input Buffer to avoid problems with V8 heap compaction
|
||||
baton->bufferInLength = node::Buffer::Length(buffer);
|
||||
baton->bufferIn = g_malloc(baton->bufferInLength);
|
||||
memcpy(baton->bufferIn, node::Buffer::Data(buffer), baton->bufferInLength);
|
||||
options->Set(NanNew<String>("bufferIn"), NanNull());
|
||||
}
|
||||
// ICC profile to use when input CMYK image has no embedded profile
|
||||
baton->iccProfilePath = *String::Utf8Value(options->Get(NanNew<String>("iccProfilePath"))->ToString());
|
||||
// Extract image options
|
||||
baton->topOffsetPre = options->Get(NanNew<String>("topOffsetPre"))->Int32Value();
|
||||
baton->leftOffsetPre = options->Get(NanNew<String>("leftOffsetPre"))->Int32Value();
|
||||
baton->widthPre = options->Get(NanNew<String>("widthPre"))->Int32Value();
|
||||
baton->heightPre = options->Get(NanNew<String>("heightPre"))->Int32Value();
|
||||
baton->topOffsetPost = options->Get(NanNew<String>("topOffsetPost"))->Int32Value();
|
||||
baton->leftOffsetPost = options->Get(NanNew<String>("leftOffsetPost"))->Int32Value();
|
||||
baton->widthPost = options->Get(NanNew<String>("widthPost"))->Int32Value();
|
||||
baton->heightPost = options->Get(NanNew<String>("heightPost"))->Int32Value();
|
||||
// Output image dimensions
|
||||
baton->width = options->Get(NanNew<String>("width"))->Int32Value();
|
||||
baton->height = options->Get(NanNew<String>("height"))->Int32Value();
|
||||
// Canvas option
|
||||
Local<String> canvas = options->Get(NanNew<String>("canvas"))->ToString();
|
||||
if (canvas->Equals(NanNew<String>("c"))) {
|
||||
baton->canvas = Canvas::CROP;
|
||||
} else if (canvas->Equals(NanNew<String>("m"))) {
|
||||
baton->canvas = Canvas::MAX;
|
||||
} else if (canvas->Equals(NanNew<String>("e"))) {
|
||||
baton->canvas = Canvas::EMBED;
|
||||
}
|
||||
// Background colour
|
||||
Local<Array> background = Local<Array>::Cast(options->Get(NanNew<String>("background")));
|
||||
for (int i = 0; i < 4; i++) {
|
||||
baton->background[i] = background->Get(i)->NumberValue();
|
||||
}
|
||||
// Resize options
|
||||
baton->withoutEnlargement = options->Get(NanNew<String>("withoutEnlargement"))->BooleanValue();
|
||||
baton->gravity = options->Get(NanNew<String>("gravity"))->Int32Value();
|
||||
baton->interpolator = *String::Utf8Value(options->Get(NanNew<String>("interpolator"))->ToString());
|
||||
// Operators
|
||||
baton->flatten = options->Get(NanNew<String>("flatten"))->BooleanValue();
|
||||
baton->blurSigma = options->Get(NanNew<String>("blurSigma"))->NumberValue();
|
||||
baton->sharpenRadius = options->Get(NanNew<String>("sharpenRadius"))->Int32Value();
|
||||
baton->sharpenFlat = options->Get(NanNew<String>("sharpenFlat"))->NumberValue();
|
||||
baton->sharpenJagged = options->Get(NanNew<String>("sharpenJagged"))->NumberValue();
|
||||
baton->gamma = options->Get(NanNew<String>("gamma"))->NumberValue();
|
||||
baton->greyscale = options->Get(NanNew<String>("greyscale"))->BooleanValue();
|
||||
baton->angle = options->Get(NanNew<String>("angle"))->Int32Value();
|
||||
baton->flip = options->Get(NanNew<String>("flip"))->BooleanValue();
|
||||
baton->flop = options->Get(NanNew<String>("flop"))->BooleanValue();
|
||||
// Output options
|
||||
baton->progressive = options->Get(NanNew<String>("progressive"))->BooleanValue();
|
||||
baton->quality = options->Get(NanNew<String>("quality"))->Int32Value();
|
||||
baton->compressionLevel = options->Get(NanNew<String>("compressionLevel"))->Int32Value();
|
||||
baton->withoutAdaptiveFiltering = options->Get(NanNew<String>("withoutAdaptiveFiltering"))->BooleanValue();
|
||||
baton->withMetadata = options->Get(NanNew<String>("withMetadata"))->BooleanValue();
|
||||
// Output filename or __format for Buffer
|
||||
baton->output = *String::Utf8Value(options->Get(NanNew<String>("output"))->ToString());
|
||||
|
||||
// Join queue for worker thread
|
||||
NanCallback *callback = new NanCallback(args[1].As<v8::Function>());
|
||||
NanAsyncQueueWorker(new ResizeWorker(callback, baton));
|
||||
|
||||
// Increment queued task counter
|
||||
g_atomic_int_inc(&counterQueue);
|
||||
|
||||
NanReturnUndefined();
|
||||
}
|
||||
8
src/resize.h
Executable file
@@ -0,0 +1,8 @@
|
||||
#ifndef SHARP_RESIZE_H
|
||||
#define SHARP_RESIZE_H
|
||||
|
||||
#include "nan.h"
|
||||
|
||||
NAN_METHOD(resize);
|
||||
|
||||
#endif
|
||||
1036
src/sharp.cc
75
src/utilities.cc
Executable file
@@ -0,0 +1,75 @@
|
||||
#include <node.h>
|
||||
#include <vips/vips.h>
|
||||
|
||||
#include "nan.h"
|
||||
|
||||
#include "common.h"
|
||||
#include "utilities.h"
|
||||
|
||||
using namespace v8;
|
||||
using namespace sharp;
|
||||
|
||||
/*
|
||||
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, 9, "%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 SHARP_UTILITIES_H
|
||||
#define SHARP_UTILITIES_H
|
||||
|
||||
#include "nan.h"
|
||||
|
||||
NAN_METHOD(cache);
|
||||
NAN_METHOD(concurrency);
|
||||
NAN_METHOD(counters);
|
||||
NAN_METHOD(libvipsVersion);
|
||||
|
||||
#endif
|
||||
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.1.0",
|
||||
"benchmark": "^1.0.0"
|
||||
},
|
||||
"license": "Apache 2.0",
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,11 @@
|
||||
/*jslint node: true */
|
||||
'use strict';
|
||||
|
||||
var sharp = require('../index');
|
||||
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 sharp = require('../../index');
|
||||
var fixtures = require('../fixtures');
|
||||
|
||||
var width = 720;
|
||||
var height = 480;
|
||||
|
||||
@@ -21,7 +20,7 @@ async.mapSeries([1, 1, 2, 4, 8, 16, 32, 64, 128], function(parallelism, next) {
|
||||
async.times(parallelism,
|
||||
function(id, callback) {
|
||||
/*jslint unused: false */
|
||||
sharp(inputJpg).resize(width, height).toBuffer(function(err, buffer) {
|
||||
sharp(fixtures.inputJpg).resize(width, height).toBuffer(function(err, buffer) {
|
||||
buffer = null;
|
||||
callback(err, new Date().getTime() - start);
|
||||
});
|
||||
@@ -1,50 +1,42 @@
|
||||
/*jslint node: true */
|
||||
'use strict';
|
||||
|
||||
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 semver = require('semver');
|
||||
|
||||
var fixturesPath = path.join(__dirname, 'fixtures');
|
||||
var imagemagick = require('imagemagick');
|
||||
var imagemagickNative = require('imagemagick-native');
|
||||
var gm = require('gm');
|
||||
var sharp = require('../../index');
|
||||
|
||||
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 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(inputJpg);
|
||||
var inputJpgBuffer = fs.readFileSync(fixtures.inputJpg);
|
||||
(new Benchmark.Suite('jpeg')).add('imagemagick-file-file', {
|
||||
defer: true,
|
||||
fn: function(deferred) {
|
||||
imagemagick.resize({
|
||||
srcPath: inputJpg,
|
||||
dstPath: outputJpg,
|
||||
srcPath: fixtures.inputJpg,
|
||||
dstPath: fixtures.outputJpg,
|
||||
quality: 0.8,
|
||||
width: width,
|
||||
height: height
|
||||
height: height,
|
||||
format: 'jpg',
|
||||
filter: magickFilter
|
||||
}, function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
@@ -61,60 +53,83 @@ async.series({
|
||||
quality: 80,
|
||||
width: width,
|
||||
height: height,
|
||||
format: 'JPEG'
|
||||
format: 'JPEG',
|
||||
filter: magickFilter
|
||||
}, function (err, buffer) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
assert.notStrictEqual(null, buffer);
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
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();
|
||||
}
|
||||
});
|
||||
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).quality(80).toBuffer(function (err, buffer) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
assert.notStrictEqual(null, buffer);
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
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(inputJpg).resize(width, height).quality(80).write(outputJpg, function (err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
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(inputJpg).resize(width, height).quality(80).toBuffer(function (err, buffer) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
assert.notStrictEqual(null, buffer);
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
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(outputJpg, function(err) {
|
||||
sharp(inputJpgBuffer).resize(width, height).toFile(fixtures.outputJpg, function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
@@ -137,7 +152,7 @@ async.series({
|
||||
}).add('sharp-file-file', {
|
||||
defer: true,
|
||||
fn: function(deferred) {
|
||||
sharp(inputJpg).resize(width, height).toFile(outputJpg, function(err) {
|
||||
sharp(fixtures.inputJpg).resize(width, height).toFile(fixtures.outputJpg, function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
@@ -148,8 +163,8 @@ async.series({
|
||||
}).add('sharp-stream-stream', {
|
||||
defer: true,
|
||||
fn: function(deferred) {
|
||||
var readable = fs.createReadStream(inputJpg);
|
||||
var writable = fs.createWriteStream(outputJpg);
|
||||
var readable = fs.createReadStream(fixtures.inputJpg);
|
||||
var writable = fs.createWriteStream(fixtures.outputJpg);
|
||||
writable.on('finish', function() {
|
||||
deferred.resolve();
|
||||
});
|
||||
@@ -159,7 +174,7 @@ async.series({
|
||||
}).add('sharp-file-buffer', {
|
||||
defer: true,
|
||||
fn: function(deferred) {
|
||||
sharp(inputJpg).resize(width, height).toBuffer(function(err, buffer) {
|
||||
sharp(fixtures.inputJpg).resize(width, height).toBuffer(function(err, buffer) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
@@ -168,18 +183,18 @@ async.series({
|
||||
}
|
||||
});
|
||||
}
|
||||
}).add('sharp-file-buffer-promise', {
|
||||
}).add('sharp-promise', {
|
||||
defer: true,
|
||||
fn: function(deferred) {
|
||||
sharp(inputJpg).resize(width, height).toBuffer().then(function(buffer) {
|
||||
sharp(inputJpgBuffer).resize(width, height).toBuffer().then(function(buffer) {
|
||||
assert.notStrictEqual(null, buffer);
|
||||
deferred.resolve();
|
||||
});
|
||||
}
|
||||
}).add('sharp-file-buffer-sharpen', {
|
||||
}).add('sharp-sharpen-mild', {
|
||||
defer: true,
|
||||
fn: function(deferred) {
|
||||
sharp(inputJpg).resize(width, height).sharpen().toBuffer(function(err, buffer) {
|
||||
sharp(inputJpgBuffer).resize(width, height).sharpen().toBuffer(function(err, buffer) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
@@ -188,10 +203,10 @@ async.series({
|
||||
}
|
||||
});
|
||||
}
|
||||
}).add('sharp-file-buffer-nearest-neighbour', {
|
||||
}).add('sharp-sharpen-radius', {
|
||||
defer: true,
|
||||
fn: function(deferred) {
|
||||
sharp(inputJpg).resize(width, height).interpolateWith(sharp.interpolator.nearest).toBuffer(function(err, buffer) {
|
||||
sharp(inputJpgBuffer).resize(width, height).sharpen(3, 1, 3).toBuffer(function(err, buffer) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
@@ -200,10 +215,10 @@ async.series({
|
||||
}
|
||||
});
|
||||
}
|
||||
}).add('sharp-file-buffer-bicubic', {
|
||||
}).add('sharp-blur-mild', {
|
||||
defer: true,
|
||||
fn: function(deferred) {
|
||||
sharp(inputJpg).resize(width, height).interpolateWith(sharp.interpolator.bicubic).toBuffer(function(err, buffer) {
|
||||
sharp(inputJpgBuffer).resize(width, height).blur().toBuffer(function(err, buffer) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
@@ -212,10 +227,10 @@ async.series({
|
||||
}
|
||||
});
|
||||
}
|
||||
}).add('sharp-file-buffer-nohalo', {
|
||||
}).add('sharp-blur-radius', {
|
||||
defer: true,
|
||||
fn: function(deferred) {
|
||||
sharp(inputJpg).resize(width, height).interpolateWith(sharp.interpolator.nohalo).toBuffer(function(err, buffer) {
|
||||
sharp(inputJpgBuffer).resize(width, height).blur(3).toBuffer(function(err, buffer) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
@@ -224,10 +239,10 @@ async.series({
|
||||
}
|
||||
});
|
||||
}
|
||||
}).add('sharp-file-buffer-locallyBoundedBicubic', {
|
||||
}).add('sharp-nearest-neighbour', {
|
||||
defer: true,
|
||||
fn: function(deferred) {
|
||||
sharp(inputJpg).resize(width, height).interpolateWith(sharp.interpolator.locallyBoundedBicubic).toBuffer(function(err, buffer) {
|
||||
sharp(inputJpgBuffer).resize(width, height).interpolateWith(sharp.interpolator.nearest).toBuffer(function(err, buffer) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
@@ -236,10 +251,10 @@ async.series({
|
||||
}
|
||||
});
|
||||
}
|
||||
}).add('sharp-file-buffer-vertexSplitQuadraticBasisSpline', {
|
||||
}).add('sharp-bicubic', {
|
||||
defer: true,
|
||||
fn: function(deferred) {
|
||||
sharp(inputJpg).resize(width, height).interpolateWith(sharp.interpolator.vertexSplitQuadraticBasisSpline).toBuffer(function(err, buffer) {
|
||||
sharp(inputJpgBuffer).resize(width, height).interpolateWith(sharp.interpolator.bicubic).toBuffer(function(err, buffer) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
@@ -248,10 +263,10 @@ async.series({
|
||||
}
|
||||
});
|
||||
}
|
||||
}).add('sharp-file-buffer-gamma', {
|
||||
}).add('sharp-nohalo', {
|
||||
defer: true,
|
||||
fn: function(deferred) {
|
||||
sharp(inputJpg).resize(width, height).gamma().toBuffer(function(err, buffer) {
|
||||
sharp(inputJpgBuffer).resize(width, height).interpolateWith(sharp.interpolator.nohalo).toBuffer(function(err, buffer) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
@@ -260,10 +275,10 @@ async.series({
|
||||
}
|
||||
});
|
||||
}
|
||||
}).add('sharp-file-buffer-greyscale', {
|
||||
}).add('sharp-locallyBoundedBicubic', {
|
||||
defer: true,
|
||||
fn: function(deferred) {
|
||||
sharp(inputJpg).resize(width, height).greyscale().toBuffer(function(err, buffer) {
|
||||
sharp(inputJpgBuffer).resize(width, height).interpolateWith(sharp.interpolator.locallyBoundedBicubic).toBuffer(function(err, buffer) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
@@ -272,10 +287,10 @@ async.series({
|
||||
}
|
||||
});
|
||||
}
|
||||
}).add('sharp-file-buffer-greyscale-gamma', {
|
||||
}).add('sharp-vertexSplitQuadraticBasisSpline', {
|
||||
defer: true,
|
||||
fn: function(deferred) {
|
||||
sharp(inputJpg).resize(width, height).gamma().greyscale().toBuffer(function(err, buffer) {
|
||||
sharp(inputJpgBuffer).resize(width, height).interpolateWith(sharp.interpolator.vertexSplitQuadraticBasisSpline).toBuffer(function(err, buffer) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
@@ -284,10 +299,10 @@ async.series({
|
||||
}
|
||||
});
|
||||
}
|
||||
}).add('sharp-file-buffer-progressive', {
|
||||
}).add('sharp-gamma', {
|
||||
defer: true,
|
||||
fn: function(deferred) {
|
||||
sharp(inputJpg).resize(width, height).progressive().toBuffer(function(err, buffer) {
|
||||
sharp(inputJpgBuffer).resize(width, height).gamma().toBuffer(function(err, buffer) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
@@ -296,10 +311,10 @@ async.series({
|
||||
}
|
||||
});
|
||||
}
|
||||
}).add('sharp-file-buffer-rotate', {
|
||||
}).add('sharp-greyscale', {
|
||||
defer: true,
|
||||
fn: function(deferred) {
|
||||
sharp(inputJpg).rotate(90).resize(width, height).toBuffer(function(err, buffer) {
|
||||
sharp(inputJpgBuffer).resize(width, height).greyscale().toBuffer(function(err, buffer) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
@@ -308,10 +323,46 @@ async.series({
|
||||
}
|
||||
});
|
||||
}
|
||||
}).add('sharp-file-buffer-sequentialRead', {
|
||||
}).add('sharp-greyscale-gamma', {
|
||||
defer: true,
|
||||
fn: function(deferred) {
|
||||
sharp(inputJpg).resize(width, height).sequentialRead().toBuffer(function(err, buffer) {
|
||||
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 {
|
||||
@@ -327,15 +378,18 @@ async.series({
|
||||
}).run();
|
||||
},
|
||||
png: function(callback) {
|
||||
var inputPngBuffer = fs.readFileSync(inputPng);
|
||||
(new Benchmark.Suite('png')).add('imagemagick-file-file', {
|
||||
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: inputPng,
|
||||
dstPath: outputPng,
|
||||
srcPath: fixtures.inputPng,
|
||||
dstPath: fixtures.outputPng,
|
||||
width: width,
|
||||
height: height
|
||||
height: height,
|
||||
format: 'jpg',
|
||||
filter: magickFilter
|
||||
}, function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
@@ -351,37 +405,44 @@ async.series({
|
||||
srcData: inputPngBuffer,
|
||||
width: width,
|
||||
height: height,
|
||||
format: 'PNG'
|
||||
format: 'PNG',
|
||||
filter: magickFilter
|
||||
});
|
||||
deferred.resolve();
|
||||
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();
|
||||
}
|
||||
});
|
||||
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(inputPng).resize(width, height).quality(80).toBuffer(function (err, buffer) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
assert.notStrictEqual(null, buffer);
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
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(outputPng, function(err) {
|
||||
sharp(inputPngBuffer).resize(width, height).toFile(fixtures.outputPng, function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
@@ -404,7 +465,7 @@ async.series({
|
||||
}).add('sharp-file-file', {
|
||||
defer: true,
|
||||
fn: function(deferred) {
|
||||
sharp(inputPng).resize(width, height).toFile(outputPng, function(err) {
|
||||
sharp(fixtures.inputPng).resize(width, height).toFile(fixtures.outputPng, function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
@@ -415,7 +476,7 @@ async.series({
|
||||
}).add('sharp-file-buffer', {
|
||||
defer: true,
|
||||
fn: function(deferred) {
|
||||
sharp(inputPng).resize(width, height).toBuffer(function(err, buffer) {
|
||||
sharp(fixtures.inputPng).resize(width, height).toBuffer(function(err, buffer) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
@@ -424,10 +485,10 @@ async.series({
|
||||
}
|
||||
});
|
||||
}
|
||||
}).add('sharp-file-buffer-sharpen', {
|
||||
}).add('sharp-progressive', {
|
||||
defer: true,
|
||||
fn: function(deferred) {
|
||||
sharp(inputPng).resize(width, height).sharpen().toBuffer(function(err, buffer) {
|
||||
sharp(inputPngBuffer).resize(width, height).progressive().toBuffer(function(err, buffer) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
@@ -436,30 +497,34 @@ async.series({
|
||||
}
|
||||
});
|
||||
}
|
||||
}).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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}).on('cycle', function(event) {
|
||||
});
|
||||
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(inputWebp);
|
||||
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(outputWebp, function(err) {
|
||||
sharp(inputWebPBuffer).resize(width, height).toFile(fixtures.outputWebP, function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
@@ -470,7 +535,7 @@ async.series({
|
||||
}).add('sharp-buffer-buffer', {
|
||||
defer: true,
|
||||
fn: function(deferred) {
|
||||
sharp(inputWebpBuffer).resize(width, height).toBuffer(function(err, buffer) {
|
||||
sharp(inputWebPBuffer).resize(width, height).toBuffer(function(err, buffer) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
@@ -482,7 +547,7 @@ async.series({
|
||||
}).add('sharp-file-file', {
|
||||
defer: true,
|
||||
fn: function(deferred) {
|
||||
sharp(inputWebp).resize(width, height).toFile(outputWebp, function(err) {
|
||||
sharp(fixtures.inputWebP).resize(width, height).toFile(fixtures.outputWebP, function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
@@ -493,19 +558,7 @@ async.series({
|
||||
}).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) {
|
||||
sharp(fixtures.inputWebp).resize(width, height).toBuffer(function(err, buffer) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
@@ -519,75 +572,6 @@ async.series({
|
||||
}).on('complete', function() {
|
||||
callback(null, this.filter('fastest').pluck('name'));
|
||||
}).run();
|
||||
},
|
||||
tiff: function(callback) {
|
||||
(new Benchmark.Suite('tiff')).add('sharp-file-file', {
|
||||
defer: true,
|
||||
fn: function(deferred) {
|
||||
sharp(inputTiff).resize(width, height).toFile(outputTiff, function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
}).add('sharp-file-file-sharpen', {
|
||||
defer: true,
|
||||
fn: function(deferred) {
|
||||
sharp(inputTiff).resize(width, height).sharpen().toFile(outputTiff, function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
}).on('cycle', function(event) {
|
||||
console.log('tiff ' + String(event.target));
|
||||
}).on('complete', function() {
|
||||
callback(null, this.filter('fastest').pluck('name'));
|
||||
}).run();
|
||||
},
|
||||
gif: function(callback) {
|
||||
(new Benchmark.Suite('gif')).add('sharp-file-file', {
|
||||
defer: true,
|
||||
fn: function(deferred) {
|
||||
sharp(inputGif).resize(width, height).toFile(outputTiff, function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
}).add('sharp-file-file-sharpen', {
|
||||
defer: true,
|
||||
fn: function(deferred) {
|
||||
sharp(inputGif).resize(width, height).sharpen().toFile(outputTiff, function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
}).add('sharp-file-file-sequentialRead', {
|
||||
defer: true,
|
||||
fn: function(deferred) {
|
||||
sharp(inputGif).sequentialRead().resize(width, height).toFile(outputTiff, function(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
} else {
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
}).on('cycle', function(event) {
|
||||
console.log('gif ' + String(event.target));
|
||||
}).on('complete', function() {
|
||||
callback(null, this.filter('fastest').pluck('name'));
|
||||
}).run();
|
||||
}
|
||||
}, function(err, results) {
|
||||
assert(!err, err);
|
||||
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.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 |
|
Before Width: | Height: | Size: 714 KiB 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 |
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB |
BIN
test/fixtures/free-gearhead-pack.psd
vendored
Normal file
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 83 KiB |
34
test/fixtures/index.js
vendored
Executable file
@@ -0,0 +1,34 @@
|
||||
'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'),
|
||||
|
||||
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)
|
||||
|
||||
};
|
||||
@@ -3,5 +3,6 @@ if ! type valgrind >/dev/null; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
curl -O https://raw.githubusercontent.com/jcupitt/libvips/master/libvips.supp
|
||||
G_SLICE=always-malloc G_DEBUG=gc-friendly valgrind --suppressions=libvips.supp --suppressions=sharp.supp --leak-check=full --show-leak-kinds=definite,indirect,possible --num-callers=20 node unit.js
|
||||
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
|
||||
0
tests/sharp.supp → test/leak/sharp.supp
Normal file → Executable file
64
test/unit/alpha.js
Executable file
@@ -0,0 +1,64 @@
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
|
||||
var sharp = require('../../index');
|
||||
var fixtures = require('../fixtures');
|
||||
|
||||
sharp.cache(0);
|
||||
|
||||
describe('Alpha transparency', function() {
|
||||
|
||||
it('Flatten to black', function(done) {
|
||||
sharp(fixtures.inputPngWithTransparency)
|
||||
.flatten()
|
||||
.resize(400, 300)
|
||||
.toFile(fixtures.path('output.flatten-black.jpg'), done);
|
||||
});
|
||||
|
||||
it('Flatten to RGB orange', function(done) {
|
||||
sharp(fixtures.inputPngWithTransparency)
|
||||
.flatten()
|
||||
.background({r: 255, g: 102, b: 0})
|
||||
.resize(400, 300)
|
||||
.toFile(fixtures.path('output.flatten-rgb-orange.jpg'), done);
|
||||
});
|
||||
|
||||
it('Flatten to CSS/hex orange', function(done) {
|
||||
sharp(fixtures.inputPngWithTransparency)
|
||||
.flatten()
|
||||
.background('#ff6600')
|
||||
.resize(400, 300)
|
||||
.toFile(fixtures.path('output.flatten-hex-orange.jpg'), done);
|
||||
});
|
||||
|
||||
it('Do not flatten', function(done) {
|
||||
sharp(fixtures.inputPngWithTransparency)
|
||||
.flatten(false)
|
||||
.toBuffer(function(err, data) {
|
||||
if (err) throw err;
|
||||
sharp(data).metadata(function(err, metadata) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('png', metadata.format);
|
||||
assert.strictEqual(4, metadata.channels);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Ignored for JPEG', function(done) {
|
||||
sharp(fixtures.inputJpg)
|
||||
.background('#ff0000')
|
||||
.flatten()
|
||||
.toBuffer(function(err, data) {
|
||||
if (err) throw err;
|
||||
sharp(data).metadata(function(err, metadata) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('jpeg', metadata.format);
|
||||
assert.strictEqual(3, metadata.channels);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
100
test/unit/blur.js
Executable file
@@ -0,0 +1,100 @@
|
||||
'use strict';
|
||||
|
||||
var assert = require('assert');
|
||||
|
||||
var sharp = require('../../index');
|
||||
var fixtures = require('../fixtures');
|
||||
|
||||
sharp.cache(0);
|
||||
|
||||
describe('Blur', function() {
|
||||
|
||||
it('specific radius 1', function(done) {
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240)
|
||||
.blur(1)
|
||||
.toFile(fixtures.path('output.blur-1.jpg'), function(err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('specific radius 10', function(done) {
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240)
|
||||
.blur(10)
|
||||
.toFile(fixtures.path('output.blur-10.jpg'), function(err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('specific radius 0.3', function(done) {
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240)
|
||||
.blur(0.3)
|
||||
.toFile(fixtures.path('output.blur-0.3.jpg'), function(err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('mild blur', function(done) {
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240)
|
||||
.blur()
|
||||
.toFile(fixtures.path('output.blur-mild.jpg'), function(err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('invalid radius', function(done) {
|
||||
var isValid = true;
|
||||
try {
|
||||
sharp(fixtures.inputJpg).blur(0.1);
|
||||
} catch (err) {
|
||||
isValid = false;
|
||||
}
|
||||
assert.strictEqual(false, isValid);
|
||||
done();
|
||||
});
|
||||
|
||||
it('blurred image is smaller than non-blurred', function(done) {
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240)
|
||||
.blur(false)
|
||||
.toBuffer(function(err, notBlurred, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, notBlurred.length > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240)
|
||||
.blur(true)
|
||||
.toBuffer(function(err, blurred, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, blurred.length > 0);
|
||||
assert.strictEqual(true, blurred.length < notBlurred.length);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
95
test/unit/extract.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('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();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
466
test/unit/io.js
Executable file
@@ -0,0 +1,466 @@
|
||||
'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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
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.41.0')) {
|
||||
it('withoutAdaptiveFiltering generates smaller file [libvips ' + sharp.libvipsVersion() + '>=7.41.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 to PNG', function(done) {
|
||||
sharp(fixtures.inputSvg)
|
||||
.resize(100, 100)
|
||||
.png()
|
||||
.toFile(fixtures.path('output.svg.png'), function(err, info) {
|
||||
if (err) throw err;
|
||||
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();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
1
test/unit/jshint.js
Executable file
@@ -0,0 +1 @@
|
||||
require('mocha-jshint')();
|
||||
219
test/unit/metadata.js
Executable file
@@ -0,0 +1,219 @@
|
||||
'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(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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
186
test/unit/resize.js
Executable file
@@ -0,0 +1,186 @@
|
||||
'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', function(done) {
|
||||
var isValid = true;
|
||||
try {
|
||||
sharp(fixtures.inputJpg).resize('spoons', 240);
|
||||
} catch (err) {
|
||||
isValid = false;
|
||||
}
|
||||
assert.strictEqual(false, isValid);
|
||||
done();
|
||||
});
|
||||
|
||||
it('Invalid height', function(done) {
|
||||
var isValid = true;
|
||||
try {
|
||||
sharp(fixtures.inputJpg).resize(320, 'spoons');
|
||||
} 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,69 +0,0 @@
|
||||
/*jslint node: true */
|
||||
'use strict';
|
||||
|
||||
var sharp = require('../index');
|
||||
var path = require('path');
|
||||
var imagemagick = require('imagemagick');
|
||||
var gm = require('gm');
|
||||
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();
|
||||
934
tests/unit.js
@@ -1,934 +0,0 @@
|
||||
/*jslint node: true */
|
||||
/*jslint es5: true */
|
||||
'use strict';
|
||||
|
||||
var sharp = require('../index');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var assert = require('assert');
|
||||
var async = require('async');
|
||||
|
||||
var fixturesPath = path.join(__dirname, 'fixtures');
|
||||
|
||||
var inputJpg = path.join(fixturesPath, '2569067123_aca715a2ee_o.jpg'); // http://www.flickr.com/photos/grizdave/2569067123/
|
||||
var inputJpgWithExif = path.join(fixturesPath, 'Landscape_8.jpg'); // https://github.com/recurser/exif-orientation-examples/blob/master/Landscape_8.jpg
|
||||
var inputJpgWithGammaHoliness = path.join(fixturesPath, 'gamma_dalai_lama_gray.jpg'); // http://www.4p8.com/eric.brasseur/gamma.html
|
||||
var inputJpgWithCmykProfile = path.join(fixturesPath, 'Channel_digital_image_CMYK_color.jpg'); // http://en.wikipedia.org/wiki/File:Channel_digital_image_CMYK_color.jpg
|
||||
|
||||
var inputPng = path.join(fixturesPath, '50020484-00001.png'); // http://c.searspartsdirect.com/lis_png/PLDM/50020484-00001.png
|
||||
var inputPngWithTransparency = path.join(fixturesPath, 'blackbug.png'); // public domain
|
||||
|
||||
var inputWebP = path.join(fixturesPath, '4.webp'); // http://www.gstatic.com/webp/gallery/4.webp
|
||||
var inputTiff = path.join(fixturesPath, 'G31D.TIF'); // http://www.fileformat.info/format/tiff/sample/e6c9a6e5253348f4aef6d17b534360ab/index.htm
|
||||
var inputGif = path.join(fixturesPath, 'Crash_test.gif'); // http://upload.wikimedia.org/wikipedia/commons/e/e3/Crash_test.gif
|
||||
|
||||
var outputJpg = path.join(fixturesPath, 'output.jpg');
|
||||
var outputZoinks = path.join(fixturesPath, 'output.zoinks'); // an 'unknown' file extension
|
||||
|
||||
// Ensure cache limits can be set
|
||||
sharp.cache(0); // Disable
|
||||
sharp.cache(50, 500); // 50MB, 500 items
|
||||
|
||||
// Ensure concurrency can be set
|
||||
var defaultConcurrency = sharp.concurrency();
|
||||
sharp.concurrency(16);
|
||||
assert.strictEqual(16, sharp.concurrency());
|
||||
sharp.concurrency(0);
|
||||
assert.strictEqual(defaultConcurrency, sharp.concurrency());
|
||||
|
||||
async.series([
|
||||
// Resize with exact crop
|
||||
function(done) {
|
||||
sharp(inputJpg).resize(320, 240).toBuffer(function(err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Resize to fixed width
|
||||
function(done) {
|
||||
sharp(inputJpg).resize(320).toBuffer(function(err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(261, info.height);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Resize to fixed height
|
||||
function(done) {
|
||||
sharp(inputJpg).resize(null, 320).toBuffer(function(err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(391, info.width);
|
||||
assert.strictEqual(320, info.height);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Identity transform
|
||||
function(done) {
|
||||
sharp(inputJpg).toBuffer(function(err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(2725, info.width);
|
||||
assert.strictEqual(2225, info.height);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Upscale
|
||||
function(done) {
|
||||
sharp(inputJpg).resize(3000).toBuffer(function(err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(3000, info.width);
|
||||
assert.strictEqual(2449, info.height);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Embed - JPEG within PNG, no alpha channel
|
||||
function(done) {
|
||||
sharp(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();
|
||||
});
|
||||
});
|
||||
},
|
||||
// Embed - JPEG within WebP, to include alpha channel
|
||||
function(done) {
|
||||
sharp(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();
|
||||
});
|
||||
});
|
||||
},
|
||||
// Quality
|
||||
function(done) {
|
||||
sharp(inputJpg).resize(320, 240).quality(70).toBuffer(function(err, buffer70) {
|
||||
if (err) throw err;
|
||||
sharp(inputJpg).resize(320, 240).toBuffer(function(err, buffer80) {
|
||||
if (err) throw err;
|
||||
sharp(inputJpg).resize(320, 240).quality(90).toBuffer(function(err, buffer90) {
|
||||
if (err) throw err;
|
||||
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).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();
|
||||
});
|
||||
},
|
||||
function(done) {
|
||||
sharp(inputTiff).resize(240, 320).jpeg().toBuffer(function(err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(240, info.width);
|
||||
assert.strictEqual(320, info.height);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Resize to max width or height considering ratio (landscape)
|
||||
function(done) {
|
||||
sharp(inputJpg).resize(320, 320).max().toBuffer(function(err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(261, info.height);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Resize to max width or height considering ratio (portrait)
|
||||
function(done) {
|
||||
sharp(inputTiff).resize(320, 320).max().jpeg().toBuffer(function(err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(243, info.width);
|
||||
assert.strictEqual(320, info.height);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Attempt to resize to max but only provide one dimension, so should default to crop
|
||||
function(done) {
|
||||
sharp(inputJpg).resize(320).max().toBuffer(function(err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(261, info.height);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Attempt to output to input, should fail
|
||||
function(done) {
|
||||
sharp(inputJpg).toFile(inputJpg, function(err) {
|
||||
assert(!!err);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Rotate by 90 degrees, respecting output input size
|
||||
function(done) {
|
||||
sharp(inputJpg).rotate(90).resize(320, 240).toBuffer(function(err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Input image has Orientation EXIF tag but do not rotate output
|
||||
function(done) {
|
||||
sharp(inputJpgWithExif).resize(320).toBuffer(function(err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(426, info.height);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Input image has Orientation EXIF tag value of 8 (270 degrees), auto-rotate
|
||||
function(done) {
|
||||
sharp(inputJpgWithExif).rotate().resize(320).toBuffer(function(err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Attempt to auto-rotate using image that has no EXIF
|
||||
function(done) {
|
||||
sharp(inputJpg).rotate().resize(320).toBuffer(function(err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(261, info.height);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Rotate to an invalid angle, should fail
|
||||
function(done) {
|
||||
var fail = false;
|
||||
try {
|
||||
sharp(inputJpg).rotate(1);
|
||||
fail = true;
|
||||
} catch (e) {}
|
||||
assert(!fail);
|
||||
done();
|
||||
},
|
||||
// Do not enlarge the output if the input width is already less than the output width
|
||||
function(done) {
|
||||
sharp(inputJpg).resize(2800).withoutEnlargement().toBuffer(function(err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(2725, info.width);
|
||||
assert.strictEqual(2225, info.height);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Do not enlarge the output if the input height is already less than the output height
|
||||
function(done) {
|
||||
sharp(inputJpg).resize(null, 2300).withoutEnlargement().toBuffer(function(err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(2725, info.width);
|
||||
assert.strictEqual(2225, info.height);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Promises/A+
|
||||
function(done) {
|
||||
sharp(inputJpg).resize(320, 240).toBuffer().then(function(data) {
|
||||
sharp(data).toBuffer(function(err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
done();
|
||||
});
|
||||
}).catch(function(err) {
|
||||
throw err;
|
||||
});
|
||||
},
|
||||
// Empty Buffer, should fail
|
||||
function(done) {
|
||||
var fail = false;
|
||||
try {
|
||||
sharp(new Buffer(0));
|
||||
fail = true;
|
||||
} catch (e) {}
|
||||
assert(!fail);
|
||||
done();
|
||||
},
|
||||
// Check colour space conversion occurs from TIFF to WebP (this used to segfault)
|
||||
function(done) {
|
||||
sharp(inputTiff).webp().toBuffer(function(err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('webp', info.format);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Check colour space conversion from CMYK to sRGB
|
||||
function(done) {
|
||||
sharp(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();
|
||||
});
|
||||
},
|
||||
// Check colour space conversion from CMYK to sRGB works with background colour (yellow=fail)
|
||||
function(done) {
|
||||
sharp(inputJpgWithCmykProfile).resize(320, 240).background('white').embed().toFile(path.join(fixturesPath, '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();
|
||||
});
|
||||
},
|
||||
// Interpolation: nearest neighbour
|
||||
function(done) {
|
||||
sharp(inputJpg).resize(320, 240).interpolateWith(sharp.interpolator.nearest).toBuffer(function(err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Interpolation: bilinear
|
||||
function(done) {
|
||||
sharp(inputJpg).resize(320, 240).interpolateWith(sharp.interpolator.bilinear).toBuffer(function(err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Interpolation: bicubic
|
||||
function(done) {
|
||||
sharp(inputJpg).resize(320, 240).interpolateWith(sharp.interpolator.bicubic).toBuffer(function(err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Interpolation: nohalo
|
||||
function(done) {
|
||||
sharp(inputJpg).resize(320, 240).interpolateWith(sharp.interpolator.nohalo).toBuffer(function(err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Interpolation: locally bounded bicubic (LBB)
|
||||
function(done) {
|
||||
sharp(inputJpg).resize(320, 240).interpolateWith(sharp.interpolator.locallyBoundedBicubic).toBuffer(function(err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Interpolation: vertex split quadratic basis spline (VSQBS)
|
||||
function(done) {
|
||||
sharp(inputJpg).resize(320, 240).interpolateWith(sharp.interpolator.vertexSplitQuadraticBasisSpline).toBuffer(function(err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// File-Stream
|
||||
function(done) {
|
||||
var writable = fs.createWriteStream(outputJpg);
|
||||
writable.on('finish', function() {
|
||||
sharp(outputJpg).toBuffer(function(err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
fs.unlinkSync(outputJpg);
|
||||
done();
|
||||
});
|
||||
});
|
||||
sharp(inputJpg).resize(320, 240).pipe(writable);
|
||||
},
|
||||
// Buffer-Stream
|
||||
function(done) {
|
||||
var inputJpgBuffer = fs.readFileSync(inputJpg);
|
||||
var writable = fs.createWriteStream(outputJpg);
|
||||
writable.on('finish', function() {
|
||||
sharp(outputJpg).toBuffer(function(err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
fs.unlinkSync(outputJpg);
|
||||
done();
|
||||
});
|
||||
});
|
||||
sharp(inputJpgBuffer).resize(320, 240).pipe(writable);
|
||||
},
|
||||
// Stream-File
|
||||
function(done) {
|
||||
var readable = fs.createReadStream(inputJpg);
|
||||
var pipeline = sharp().resize(320, 240).toFile(outputJpg, function(err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
fs.unlinkSync(outputJpg);
|
||||
done();
|
||||
});
|
||||
readable.pipe(pipeline);
|
||||
},
|
||||
// Stream-Buffer
|
||||
function(done) {
|
||||
var readable = fs.createReadStream(inputJpg);
|
||||
var pipeline = sharp().resize(320, 240).toBuffer(function(err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
done();
|
||||
});
|
||||
readable.pipe(pipeline);
|
||||
},
|
||||
// Stream-Stream
|
||||
function(done) {
|
||||
var readable = fs.createReadStream(inputJpg);
|
||||
var writable = fs.createWriteStream(outputJpg);
|
||||
writable.on('finish', function() {
|
||||
sharp(outputJpg).toBuffer(function(err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
fs.unlinkSync(outputJpg);
|
||||
done();
|
||||
});
|
||||
});
|
||||
var pipeline = sharp().resize(320, 240);
|
||||
readable.pipe(pipeline).pipe(writable);
|
||||
},
|
||||
// Stream-Stream error handling
|
||||
function(done) {
|
||||
var pipeline = sharp().resize(320, 240);
|
||||
var anErrorWasEmitted = false;
|
||||
pipeline.on('error', function(err) {
|
||||
anErrorWasEmitted = !!err;
|
||||
}).on('end', function() {
|
||||
assert(anErrorWasEmitted);
|
||||
fs.unlinkSync(outputJpg);
|
||||
done();
|
||||
});
|
||||
var readableButNotAnImage = fs.createReadStream(__filename);
|
||||
var writable = fs.createWriteStream(outputJpg);
|
||||
readableButNotAnImage.pipe(pipeline).pipe(writable);
|
||||
},
|
||||
// File-Stream error handling
|
||||
function(done) {
|
||||
var readableButNotAnImage = sharp(__filename).resize(320, 240);
|
||||
var anErrorWasEmitted = false;
|
||||
readableButNotAnImage.on('error', function(err) {
|
||||
anErrorWasEmitted = !!err;
|
||||
}).on('end', function() {
|
||||
assert(anErrorWasEmitted);
|
||||
fs.unlinkSync(outputJpg);
|
||||
done();
|
||||
});
|
||||
var writable = fs.createWriteStream(outputJpg);
|
||||
readableButNotAnImage.pipe(writable);
|
||||
},
|
||||
// Crop, gravity=north
|
||||
function(done) {
|
||||
sharp(inputJpg).resize(320, 80).crop(sharp.gravity.north).toFile(path.join(fixturesPath, 'output.gravity-north.jpg'), function(err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(80, info.height);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Crop, gravity=east
|
||||
function(done) {
|
||||
sharp(inputJpg).resize(80, 320).crop(sharp.gravity.east).toFile(path.join(fixturesPath, 'output.gravity-east.jpg'), function(err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(80, info.width);
|
||||
assert.strictEqual(320, info.height);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Crop, gravity=south
|
||||
function(done) {
|
||||
sharp(inputJpg).resize(320, 80).crop(sharp.gravity.south).toFile(path.join(fixturesPath, 'output.gravity-south.jpg'), function(err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(80, info.height);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Crop, gravity=west
|
||||
function(done) {
|
||||
sharp(inputJpg).resize(80, 320).crop(sharp.gravity.west).toFile(path.join(fixturesPath, 'output.gravity-west.jpg'), function(err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(80, info.width);
|
||||
assert.strictEqual(320, info.height);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Crop, gravity=center
|
||||
function(done) {
|
||||
sharp(inputJpg).resize(320, 80).crop(sharp.gravity.center).toFile(path.join(fixturesPath, 'output.gravity-center.jpg'), function(err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(80, info.height);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Crop, gravity=centre
|
||||
function(done) {
|
||||
sharp(inputJpg).resize(80, 320).crop(sharp.gravity.centre).toFile(path.join(fixturesPath, 'output.gravity-centre.jpg'), function(err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(80, info.width);
|
||||
assert.strictEqual(320, info.height);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Extract jpg
|
||||
function(done) {
|
||||
sharp(inputJpg).extract(2,2,20,20).toFile(path.join(fixturesPath, 'output.extract.jpg'), function(err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(20, info.width);
|
||||
assert.strictEqual(20, info.height);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Extract png
|
||||
function(done) {
|
||||
sharp(inputPng).extract(300,200,400,200).toFile(path.join(fixturesPath, 'output.extract.png'), function(err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(400, info.width);
|
||||
assert.strictEqual(200, info.height);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Extract webp
|
||||
function(done) {
|
||||
sharp(inputWebP).extract(50, 100, 125, 200).toFile(path.join(fixturesPath, 'output.extract.webp'), function(err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(125, info.width);
|
||||
assert.strictEqual(200, info.height);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Extract tiff
|
||||
function(done) {
|
||||
sharp(inputTiff).extract(63, 34, 341, 529).toFile(path.join(fixturesPath, 'output.extract.tiff'), function(err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(341, info.width);
|
||||
assert.strictEqual(529, info.height);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Extract before resize
|
||||
function(done) {
|
||||
sharp(inputJpg).extract(10, 10, 10, 500, 500).resize(100, 100).toFile(path.join(fixturesPath, 'output.extract.resize.jpg'), function(err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(100, info.width);
|
||||
assert.strictEqual(100, info.height);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Extract after resize and crop
|
||||
function(done) {
|
||||
sharp(inputJpg).resize(500, 500).crop(sharp.gravity.north).extract(10, 10, 100, 100).toFile(path.join(fixturesPath, 'output.resize.crop.extract.jpg'), function(err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(100, info.width);
|
||||
assert.strictEqual(100, info.height);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Extract before and after resize and crop
|
||||
function(done) {
|
||||
sharp(inputJpg).extract(0, 0, 700, 700).resize(500, 500).crop(sharp.gravity.north).extract(10, 10, 100, 100).toFile(path.join(fixturesPath, 'output.extract.resize.crop.extract.jpg'), function(err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(100, info.width);
|
||||
assert.strictEqual(100, info.height);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Keeps Metadata after a resize
|
||||
function(done) {
|
||||
sharp(inputJpgWithExif).resize(320, 240).withMetadata().toBuffer(function(err, buffer) {
|
||||
if (err) throw err;
|
||||
sharp(buffer).metadata(function(err, metadata) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(8, metadata.orientation);
|
||||
done();
|
||||
});
|
||||
});
|
||||
},
|
||||
// Keeps Metadata after a resize
|
||||
function(done) {
|
||||
sharp(inputJpgWithExif).resize(320, 240).withMetadata(false).toBuffer(function(err, buffer) {
|
||||
if (err) throw err;
|
||||
sharp(buffer).metadata(function(err, metadata) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('undefined', typeof metadata.orientation);
|
||||
done();
|
||||
});
|
||||
});
|
||||
},
|
||||
// Output filename without extension should mirror input format
|
||||
function(done) {
|
||||
sharp(inputJpg).resize(320, 80).toFile(outputZoinks, function(err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(80, info.height);
|
||||
fs.unlinkSync(outputZoinks);
|
||||
done();
|
||||
});
|
||||
},
|
||||
function(done) {
|
||||
sharp(inputPng).resize(320, 80).toFile(outputZoinks, function(err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('png', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(80, info.height);
|
||||
fs.unlinkSync(outputZoinks);
|
||||
done();
|
||||
});
|
||||
},
|
||||
function(done) {
|
||||
sharp(inputPngWithTransparency).resize(320, 80).toFile(outputZoinks, function(err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('png', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(80, info.height);
|
||||
done();
|
||||
});
|
||||
},
|
||||
function(done) {
|
||||
sharp(inputWebP).resize(320, 80).toFile(outputZoinks, function(err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('webp', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(80, info.height);
|
||||
fs.unlinkSync(outputZoinks);
|
||||
done();
|
||||
});
|
||||
},
|
||||
function(done) {
|
||||
sharp(inputTiff).resize(320, 80).toFile(outputZoinks, function(err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('tiff', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(80, info.height);
|
||||
fs.unlinkSync(outputZoinks);
|
||||
done();
|
||||
});
|
||||
},
|
||||
function(done) {
|
||||
sharp(inputGif).resize(320, 80).toFile(outputZoinks, function(err) {
|
||||
assert(!!err);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Metadata - JPEG
|
||||
function(done) {
|
||||
sharp(inputJpg).metadata(function(err, metadata) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('jpeg', metadata.format);
|
||||
assert.strictEqual(2725, metadata.width);
|
||||
assert.strictEqual(2225, metadata.height);
|
||||
assert.strictEqual('srgb', metadata.space);
|
||||
assert.strictEqual(3, metadata.channels);
|
||||
assert.strictEqual('undefined', typeof metadata.orientation);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Metadata - JPEG with EXIF
|
||||
function(done) {
|
||||
sharp(inputJpgWithExif).metadata(function(err, metadata) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('jpeg', metadata.format);
|
||||
assert.strictEqual(450, metadata.width);
|
||||
assert.strictEqual(600, metadata.height);
|
||||
assert.strictEqual('srgb', metadata.space);
|
||||
assert.strictEqual(3, metadata.channels);
|
||||
assert.strictEqual(false, metadata.hasAlpha);
|
||||
assert.strictEqual(8, metadata.orientation);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Metadata - TIFF
|
||||
function(done) {
|
||||
sharp(inputTiff).metadata(function(err, metadata) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('tiff', metadata.format);
|
||||
assert.strictEqual(2464, metadata.width);
|
||||
assert.strictEqual(3248, metadata.height);
|
||||
assert.strictEqual('b-w', metadata.space);
|
||||
assert.strictEqual(1, metadata.channels);
|
||||
assert.strictEqual(false, metadata.hasAlpha);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Metadata - PNG
|
||||
function(done) {
|
||||
sharp(inputPng).metadata(function(err, metadata) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('png', metadata.format);
|
||||
assert.strictEqual(2809, metadata.width);
|
||||
assert.strictEqual(2074, metadata.height);
|
||||
assert.strictEqual('b-w', metadata.space);
|
||||
assert.strictEqual(1, metadata.channels);
|
||||
assert.strictEqual(false, metadata.hasAlpha);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Metadata - Transparent PNG
|
||||
function(done) {
|
||||
sharp(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(true, metadata.hasAlpha);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Metadata - WebP
|
||||
function(done) {
|
||||
sharp(inputWebP).metadata(function(err, metadata) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('webp', metadata.format);
|
||||
assert.strictEqual(1024, metadata.width);
|
||||
assert.strictEqual(772, metadata.height);
|
||||
assert.strictEqual('srgb', metadata.space);
|
||||
assert.strictEqual(3, metadata.channels);
|
||||
assert.strictEqual(false, metadata.hasAlpha);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Metadata - GIF (via libmagick)
|
||||
function(done) {
|
||||
sharp(inputGif).metadata(function(err, metadata) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('magick', metadata.format);
|
||||
assert.strictEqual(800, metadata.width);
|
||||
assert.strictEqual(533, metadata.height);
|
||||
assert.strictEqual(3, metadata.channels);
|
||||
assert.strictEqual(false, metadata.hasAlpha);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Metadata - Promise
|
||||
function(done) {
|
||||
sharp(inputJpg).metadata().then(function(metadata) {
|
||||
assert.strictEqual('jpeg', metadata.format);
|
||||
assert.strictEqual(2725, metadata.width);
|
||||
assert.strictEqual(2225, metadata.height);
|
||||
assert.strictEqual('srgb', metadata.space);
|
||||
assert.strictEqual(3, metadata.channels);
|
||||
assert.strictEqual(false, metadata.hasAlpha);
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Metadata - Stream
|
||||
function(done) {
|
||||
var readable = fs.createReadStream(inputJpg);
|
||||
var pipeline = sharp().metadata(function(err, metadata) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('jpeg', metadata.format);
|
||||
assert.strictEqual(2725, metadata.width);
|
||||
assert.strictEqual(2225, metadata.height);
|
||||
assert.strictEqual('srgb', metadata.space);
|
||||
assert.strictEqual(3, metadata.channels);
|
||||
assert.strictEqual(false, metadata.hasAlpha);
|
||||
done();
|
||||
});
|
||||
readable.pipe(pipeline);
|
||||
},
|
||||
// Get metadata then resize to half width
|
||||
function(done) {
|
||||
var image = sharp(inputJpg);
|
||||
image.metadata(function(err, metadata) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('jpeg', metadata.format);
|
||||
assert.strictEqual(2725, metadata.width);
|
||||
assert.strictEqual(2225, metadata.height);
|
||||
assert.strictEqual('srgb', metadata.space);
|
||||
assert.strictEqual(3, metadata.channels);
|
||||
assert.strictEqual(false, metadata.hasAlpha);
|
||||
image.resize(metadata.width / 2).toBuffer(function(err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual(1362, info.width);
|
||||
assert.strictEqual(1112, info.height);
|
||||
done();
|
||||
});
|
||||
});
|
||||
},
|
||||
// Gamma correction
|
||||
function(done) {
|
||||
sharp(inputJpgWithGammaHoliness).resize(129, 111).toFile(path.join(fixturesPath, 'output.gamma-0.0.jpg'), function(err) {
|
||||
if (err) throw err;
|
||||
done();
|
||||
});
|
||||
},
|
||||
function(done) {
|
||||
sharp(inputJpgWithGammaHoliness).resize(129, 111).gamma().toFile(path.join(fixturesPath, 'output.gamma-2.2.jpg'), function(err) {
|
||||
if (err) throw err;
|
||||
done();
|
||||
});
|
||||
},
|
||||
function(done) {
|
||||
sharp(inputJpgWithGammaHoliness).resize(129, 111).gamma(3).toFile(path.join(fixturesPath, 'output.gamma-3.0.jpg'), function(err) {
|
||||
if (err) throw err;
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Greyscale conversion
|
||||
function(done) {
|
||||
sharp(inputJpg).resize(320, 240).greyscale().toFile(path.join(fixturesPath, 'output.greyscale-gamma-0.0.jpg'), function(err) {
|
||||
if (err) throw err;
|
||||
done();
|
||||
});
|
||||
},
|
||||
function(done) {
|
||||
sharp(inputJpg).resize(320, 240).gamma().greyscale().toFile(path.join(fixturesPath, 'output.greyscale-gamma-2.2.jpg'), function(err) {
|
||||
if (err) throw err;
|
||||
done();
|
||||
});
|
||||
},
|
||||
// Flattening
|
||||
function(done) {
|
||||
sharp(inputPngWithTransparency).flatten().resize(400, 300).toFile(path.join(fixturesPath, 'output.flatten-black.jpg'), function(err) {
|
||||
if (err) throw err;
|
||||
done();
|
||||
});
|
||||
},
|
||||
function(done) {
|
||||
sharp(inputPngWithTransparency).flatten().background({r: 255, g: 102, b: 0}).resize(400, 300).toFile(path.join(fixturesPath, 'output.flatten-rgb-orange.jpg'), function(err) {
|
||||
if (err) throw err;
|
||||
done();
|
||||
});
|
||||
},
|
||||
function(done) {
|
||||
sharp(inputPngWithTransparency).flatten().background('#ff6600').resize(400, 300).toFile(path.join(fixturesPath, 'output.flatten-hex-orange.jpg'), function(err) {
|
||||
if (err) throw err;
|
||||
done();
|
||||
});
|
||||
},
|
||||
function(done) {
|
||||
sharp(inputJpg).background('#ff0000').flatten().resize(500, 400).toFile(path.join(fixturesPath, 'output.flatten-input-jpg.jpg'), function(err) {
|
||||
if (err) throw err;
|
||||
done();
|
||||
});
|
||||
},
|
||||
// PNG compression level - valid
|
||||
function(done) {
|
||||
var isValid = false;
|
||||
try {
|
||||
sharp().compressionLevel(0);
|
||||
isValid = true;
|
||||
} catch (e) {}
|
||||
assert(isValid);
|
||||
done();
|
||||
},
|
||||
// PNG compression level - invalid
|
||||
function(done) {
|
||||
var isValid = false;
|
||||
try {
|
||||
sharp().compressionLevel(-1);
|
||||
isValid = true;
|
||||
} catch (e) {}
|
||||
assert(!isValid);
|
||||
done();
|
||||
},
|
||||
// Verify internal counters
|
||||
function(done) {
|
||||
var counters = sharp.counters();
|
||||
assert.strictEqual(0, counters.queue);
|
||||
assert.strictEqual(0, counters.process);
|
||||
done();
|
||||
},
|
||||
// Empty cache
|
||||
function(done) {
|
||||
sharp.cache(0);
|
||||
done();
|
||||
}
|
||||
]);
|
||||