Compare commits
59 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc3311cbad | ||
|
|
7b03eb89d7 | ||
|
|
15a519ebd9 | ||
|
|
3f8e9f6487 | ||
|
|
39688371a8 | ||
|
|
e32faac17a | ||
|
|
6c96bd0d37 | ||
|
|
6b5f2028b7 | ||
|
|
276ba5228b | ||
|
|
ad7735a0a6 | ||
|
|
88edad3fae | ||
|
|
308d1971d8 | ||
|
|
6622045172 | ||
|
|
f68ba8ea57 | ||
|
|
2e427bb28a | ||
|
|
efc7504961 | ||
|
|
8118613fa0 | ||
|
|
eb6a221cee | ||
|
|
acdfe02502 | ||
|
|
2e106f8e2e | ||
|
|
10496881f1 | ||
|
|
e275f6f5dd | ||
|
|
d635c297a2 | ||
|
|
817c0a2a5a | ||
|
|
92fd34c627 | ||
|
|
43086cf134 | ||
|
|
e607bac31c | ||
|
|
f8338e7c4f | ||
|
|
8322b442e0 | ||
|
|
afc51df4d8 | ||
|
|
e3a70c1075 | ||
|
|
e3ee2b2976 | ||
|
|
cb285a6fb3 | ||
|
|
aed3ca63b3 | ||
|
|
cbcf5e0dcc | ||
|
|
59f5c2d31b | ||
|
|
d1b47ef419 | ||
|
|
0954ca6adf | ||
|
|
c9d7f43bd9 | ||
|
|
481741315d | ||
|
|
cae1dbdb89 | ||
|
|
200d5a9312 | ||
|
|
e9ca25cb45 | ||
|
|
33f24d41e7 | ||
|
|
45d5f12a63 | ||
|
|
8785ca4331 | ||
|
|
1ecdf97bdb | ||
|
|
3703ee41aa | ||
|
|
c8f023d8ba | ||
|
|
fe773733cd | ||
|
|
19bec9346e | ||
|
|
9bd335079f | ||
|
|
b6dc179551 | ||
|
|
e96fd8b9de | ||
|
|
6a2816e917 | ||
|
|
06d88de5a2 | ||
|
|
a292e1fe8e | ||
|
|
08bb35e7af | ||
|
|
0e89a5bbf2 |
4
.gitignore
vendored
@@ -12,6 +12,4 @@ logs
|
|||||||
results
|
results
|
||||||
build
|
build
|
||||||
node_modules
|
node_modules
|
||||||
tests/output.*
|
tests/fixtures/output.*
|
||||||
|
|
||||||
npm-debug.log
|
|
||||||
|
|||||||
18
.npmignore
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
lib-cov
|
||||||
|
*.seed
|
||||||
|
*.log
|
||||||
|
*.csv
|
||||||
|
*.dat
|
||||||
|
*.out
|
||||||
|
*.pid
|
||||||
|
*.gz
|
||||||
|
|
||||||
|
pids
|
||||||
|
logs
|
||||||
|
results
|
||||||
|
build
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
.gitignore
|
||||||
|
tests
|
||||||
|
.travis.yml
|
||||||
17
.travis.yml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
language: node_js
|
||||||
|
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 libfftw3-dev libglib2.0-dev libjpeg-turbo8-dev libpng12-dev libwebp-dev libtiff4-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=no --without-orc --without-python
|
||||||
|
- make
|
||||||
|
- sudo make install
|
||||||
|
- sudo ldconfig
|
||||||
|
- cd $TRAVIS_BUILD_DIR
|
||||||
205
README.md
@@ -1,45 +1,72 @@
|
|||||||
# sharp
|
# sharp
|
||||||
|
|
||||||
_adj_
|
* [Installation](https://github.com/lovell/sharp#installation)
|
||||||
|
* [Usage examples](https://github.com/lovell/sharp#usage-examples)
|
||||||
|
* [API](https://github.com/lovell/sharp#api)
|
||||||
|
* [Testing](https://github.com/lovell/sharp#testing)
|
||||||
|
* [Performance](https://github.com/lovell/sharp#performance)
|
||||||
|
* [Licence](https://github.com/lovell/sharp#licence)
|
||||||
|
|
||||||
1. clearly defined; distinct: a sharp photographic image.
|
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.
|
||||||
2. quick, brisk, or spirited.
|
|
||||||
3. shrewd or astute: a sharp bargainer.
|
|
||||||
4. (Informal.) very stylish: a sharp dresser; a sharp jacket.
|
|
||||||
|
|
||||||
The typical use case for this high speed Node.js module is to convert large JPEG, PNG, WebP and TIFF images to smaller images of varying dimensions.
|
The performance of JPEG resizing is typically 8x faster than ImageMagick and GraphicsMagick, based mainly on the number of CPU cores available. Everything remains non-blocking thanks to _libuv_.
|
||||||
|
|
||||||
The performance of JPEG resizing is typically 15x-25x faster than ImageMagick and GraphicsMagick, based mainly on the number of CPU cores available.
|
This module supports reading and writing images of JPEG, PNG and WebP to and from both Buffer objects and the filesystem. It also supports reading images of many other types from the filesystem via libmagick++ or libgraphicsmagick++ if present.
|
||||||
|
|
||||||
This module supports reading and writing images to and from both the filesystem and Buffer objects (TIFF is limited to filesystem only). Everything remains non-blocking thanks to _libuv_.
|
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/).
|
||||||
|
|
||||||
Anyone who has used the Node.js bindings for [GraphicsMagick](https://github.com/aheckmann/gm) will find the API similarly expressive.
|
Anyone who has used the Node.js bindings for [GraphicsMagick](https://github.com/aheckmann/gm) will find the API similarly fluent.
|
||||||
|
|
||||||
This module is powered by the blazingly fast [libvips](https://github.com/jcupitt/libvips) image processing library, originally created in 1989 at Birkbeck College and currently maintained by John Cupitt.
|
This module is powered by the blazingly fast [libvips](https://github.com/jcupitt/libvips) image processing library, originally created in 1989 at Birkbeck College and currently maintained by John Cupitt.
|
||||||
|
|
||||||
## Prerequisites
|
## Installation
|
||||||
|
|
||||||
* Node.js v0.8+
|
npm install sharp
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
* Node.js v0.10+
|
||||||
* [libvips](https://github.com/jcupitt/libvips) v7.38.5+
|
* [libvips](https://github.com/jcupitt/libvips) v7.38.5+
|
||||||
|
|
||||||
|
_libvips_ will take advantage of [liborc](http://code.entropywave.com/orc/) if present, however versions of _liborc_ prior to 0.4.19 suffer memory leaks.
|
||||||
|
|
||||||
### Install libvips on Mac OS
|
### Install libvips on Mac OS
|
||||||
|
|
||||||
brew install homebrew/science/vips
|
brew install homebrew/science/vips --with-webp --with-graphicsmagick
|
||||||
|
|
||||||
### Install libvips on Ubuntu/Debian Linux
|
The _gettext_ dependency of _libvips_ [can lead](https://github.com/lovell/sharp/issues/9) to a `library not found for -lintl` error. If so, please try:
|
||||||
|
|
||||||
sudo apt-get install automake build-essential git gobject-introspection gtk-doc-tools libfftw3-dev libglib2.0-dev libjpeg-turbo8-dev libpng12-dev libwebp-dev libtiff5-dev liborc-0.4-dev libxml2-dev swig
|
brew link gettext --force
|
||||||
|
|
||||||
|
### Install libvips on Ubuntu Linux
|
||||||
|
|
||||||
|
#### Ubuntu 14.x
|
||||||
|
|
||||||
|
sudo apt-get install libvips-dev
|
||||||
|
|
||||||
|
#### Ubuntu 13.x
|
||||||
|
|
||||||
|
Compiling from source is recommended:
|
||||||
|
|
||||||
|
sudo apt-get install automake build-essential git gobject-introspection gtk-doc-tools libfftw3-dev libglib2.0-dev libjpeg-turbo8-dev libpng12-dev libwebp-dev libtiff5-dev libxml2-dev swig
|
||||||
git clone https://github.com/jcupitt/libvips.git
|
git clone https://github.com/jcupitt/libvips.git
|
||||||
cd libvips
|
cd libvips
|
||||||
|
git checkout 7.38
|
||||||
./bootstrap.sh
|
./bootstrap.sh
|
||||||
./configure --enable-debug=no
|
./configure --enable-debug=no --enable-cxx=no --without-python --without-orc
|
||||||
make
|
make
|
||||||
sudo make install
|
sudo make install
|
||||||
sudo ldconfig
|
sudo ldconfig
|
||||||
|
|
||||||
## Install
|
#### Ubuntu 12.x
|
||||||
|
|
||||||
npm install sharp
|
Requires `libtiff4-dev` instead of `libtiff5-dev` and has [a bug](https://bugs.launchpad.net/ubuntu/+source/libwebp/+bug/1108731) in the libwebp package. Work around these problems by running these command first:
|
||||||
|
|
||||||
|
sudo add-apt-repository ppa:lyrasis/precise-backports
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install libtiff4-dev
|
||||||
|
|
||||||
|
Then follow Ubuntu 13.x instructions.
|
||||||
|
|
||||||
## Usage examples
|
## Usage examples
|
||||||
|
|
||||||
@@ -58,25 +85,25 @@ sharp('input.jpg').resize(300, 200).write('output.jpg', function(err) {
|
|||||||
```
|
```
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
sharp('input.jpg').resize(null, 200).progressive().toBuffer(function(err, buffer) {
|
sharp('input.jpg').resize(null, 200).progressive().toBuffer(function(err, outputBuffer) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
// buffer contains progressive JPEG image data, 200 pixels high
|
// outputBuffer contains progressive JPEG image data, 200 pixels high
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
sharp('input.png').resize(300).sharpen().webp(function(err, buffer) {
|
sharp('input.png').resize(300).sharpen().quality(90).webp(function(err, outputBuffer) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
// buffer contains sharpened WebP image data (converted from PNG), 300 pixels wide
|
// outputBuffer contains 300 pixels wide, sharpened, 90% quality WebP image data
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
sharp(buffer).resize(200, 300).embedWhite().write('output.tiff', function(err) {
|
sharp(inputBuffer).resize(200, 300).embedWhite().write('output.tiff', function(err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
@@ -86,12 +113,22 @@ sharp(buffer).resize(200, 300).embedWhite().write('output.tiff', function(err) {
|
|||||||
```
|
```
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
sharp('input.jpg').resize(200, 300).embedBlack().webp(function(err, buffer) {
|
sharp('input.gif').resize(200, 300).embedBlack().webp(function(err, outputBuffer) {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
// buffer contains WebP image data of a 200 pixels wide and 300 pixels high image
|
// outputBuffer contains WebP image data of a 200 pixels wide and 300 pixels high
|
||||||
// containing a scaled version, embedded on a black canvas, of input.png
|
// containing a scaled version, embedded on a black canvas, of input.gif
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
sharp(inputBuffer).resize(200, 200).max().jpeg(function(err, outputBuffer) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
// outputBuffer contains JPEG image data no wider than 200 pixels and no higher
|
||||||
|
// than 200 pixels regardless of the inputBuffer image dimensions
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -99,9 +136,10 @@ sharp('input.jpg').resize(200, 300).embedBlack().webp(function(err, buffer) {
|
|||||||
|
|
||||||
### sharp(input)
|
### sharp(input)
|
||||||
|
|
||||||
Constructor to which further methods are chained.
|
Constructor to which further methods are chained. `input` can be one of:
|
||||||
|
|
||||||
`input` can either be a filename String or a Buffer.
|
* Buffer containing JPEG, PNG or WebP image data, or
|
||||||
|
* String containing the filename of an image, with most major formats supported.
|
||||||
|
|
||||||
### resize(width, [height])
|
### resize(width, [height])
|
||||||
|
|
||||||
@@ -115,6 +153,12 @@ Scale to `width` x `height`. By default, the resized image is cropped to the exa
|
|||||||
|
|
||||||
Crop the resized image to the exact size specified, the default behaviour.
|
Crop the resized image to the exact size specified, the default behaviour.
|
||||||
|
|
||||||
|
### max()
|
||||||
|
|
||||||
|
Preserving aspect ratio, resize the image to the maximum width or height specified.
|
||||||
|
|
||||||
|
Both `width` and `height` must be provided via `resize` otherwise the behaviour will default to `crop`.
|
||||||
|
|
||||||
### embedWhite()
|
### embedWhite()
|
||||||
|
|
||||||
Embed the resized image on a white background of the exact size specified.
|
Embed the resized image on a white background of the exact size specified.
|
||||||
@@ -129,7 +173,19 @@ Perform a mild sharpen of the resultant image. This typically reduces performanc
|
|||||||
|
|
||||||
### progressive()
|
### progressive()
|
||||||
|
|
||||||
Use progressive (interlace) scan for the output. This typically reduces performance by 30%.
|
Use progressive (interlace) scan for JPEG and PNG output. This typically reduces compression performance by 30% but results in an image that can be rendered sooner when decompressed.
|
||||||
|
|
||||||
|
### quality(quality)
|
||||||
|
|
||||||
|
The output quality to use for lossy JPEG, WebP and TIFF output formats. The default quality is `80`.
|
||||||
|
|
||||||
|
`quality` is a Number between 1 and 100.
|
||||||
|
|
||||||
|
### compressionLevel(compressionLevel)
|
||||||
|
|
||||||
|
An advanced setting for the _zlib_ compression level of the lossless PNG output format. The default level is `6`.
|
||||||
|
|
||||||
|
`compressionLevel` is a Number between -1 and 9.
|
||||||
|
|
||||||
### sequentialRead()
|
### sequentialRead()
|
||||||
|
|
||||||
@@ -161,7 +217,7 @@ Write WebP image data to a Buffer.
|
|||||||
|
|
||||||
### toBuffer(callback)
|
### toBuffer(callback)
|
||||||
|
|
||||||
Write image data to a Buffer, the format of which will match the input image.
|
Write image data to a Buffer, the format of which will match the input image. JPEG, PNG and WebP are supported.
|
||||||
|
|
||||||
`callback` gets two arguments `(err, buffer)` where `err` is an error message, if any, and `buffer` is the resultant image data.
|
`callback` gets two arguments `(err, buffer)` where `err` is an error message, if any, and `buffer` is the resultant image data.
|
||||||
|
|
||||||
@@ -181,74 +237,59 @@ sharp.cache(50); // { current: 49, high: 115, limit: 50 }
|
|||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
|
[](https://travis-ci.org/lovell/sharp)
|
||||||
|
|
||||||
npm test
|
npm test
|
||||||
|
|
||||||
|
Running the tests requires both ImageMagick and GraphicsMagick plus one of either libmagick++-dev or libgraphicsmagick++.
|
||||||
|
|
||||||
|
brew install imagemagick
|
||||||
|
brew install graphicsmagick
|
||||||
|
|
||||||
|
sudo apt-get install imagemagick graphicsmagick libmagick++-dev
|
||||||
|
|
||||||
## Performance
|
## Performance
|
||||||
|
|
||||||
Test environment:
|
### Test environment
|
||||||
|
|
||||||
* AMD Athlon 4 core 3.3GHz 512KB L2 CPU 1333 DDR3
|
* Intel Xeon [L5520](http://ark.intel.com/products/40201/Intel-Xeon-Processor-L5520-8M-Cache-2_26-GHz-5_86-GTs-Intel-QPI) 2.27GHz 8MB cache
|
||||||
* libvips 7.38
|
* Ubuntu 13.10
|
||||||
* libjpeg-turbo8 1.3.0
|
* libvips 7.38.5
|
||||||
* libpng 1.6.6
|
|
||||||
* zlib1g 1.2.7
|
|
||||||
* libwebp 0.3.0
|
|
||||||
* libtiff 4.0.2
|
|
||||||
|
|
||||||
`-file-buffer` indicates read from file and write to buffer, `-buffer-file` indicates read from buffer and write to file etc.
|
### The contenders
|
||||||
|
|
||||||
`-sharpen`, `-progressive` etc. demonstrate the negative effect of options on performance.
|
* [imagemagick-native](https://github.com/mash/node-imagemagick-native) - Supports Buffers only and blocks main V8 thread whilst processing.
|
||||||
|
* [imagemagick](https://github.com/rsms/node-imagemagick) - Supports filesystem only and "has been unmaintained for a long time".
|
||||||
|
* [gm](https://github.com/aheckmann/gm) - Fully featured wrapper around GraphicsMagick.
|
||||||
|
* sharp - Caching within libvips disabled to ensure a fair comparison.
|
||||||
|
|
||||||
### JPEG
|
### The task
|
||||||
|
|
||||||
* imagemagick x 5.53 ops/sec ±0.62% (31 runs sampled)
|
Decompress a 2725x2225 JPEG image, resize and crop to 720x480, then compress to JPEG.
|
||||||
* gm-file-file x 4.10 ops/sec ±0.41% (25 runs sampled)
|
|
||||||
* gm-file-buffer x 4.10 ops/sec ±0.36% (25 runs sampled)
|
|
||||||
* epeg-file-file x 23.82 ops/sec ±0.18% (60 runs sampled)
|
|
||||||
* epeg-file-buffer x 23.98 ops/sec ±0.16% (61 runs sampled)
|
|
||||||
|
|
||||||
* sharp-buffer-file x 20.76 ops/sec ±0.55% (54 runs sampled)
|
### Results
|
||||||
* sharp-buffer-buffer x 20.90 ops/sec ±0.26% (54 runs sampled)
|
|
||||||
* sharp-file-file x 91.78 ops/sec ±0.38% (88 runs sampled)
|
|
||||||
* sharp-file-buffer x __93.05 ops/sec__ ±0.61% (76 runs sampled)
|
|
||||||
|
|
||||||
* sharp-file-buffer-sharpen x 63.09 ops/sec ±5.58% (63 runs sampled)
|
| Module | Input | Output | Ops/sec | Speed-up |
|
||||||
* sharp-file-buffer-progressive x 61.68 ops/sec ±0.53% (76 runs sampled)
|
| :-------------------- | :----- | :----- | ------: | -------: |
|
||||||
* sharp-file-buffer-sequentialRead x 60.66 ops/sec ±0.38% (75 runs sampled)
|
| imagemagick-native | buffer | buffer | 0.97 | 1 |
|
||||||
|
| imagemagick | file | file | 2.49 | 2.6 |
|
||||||
|
| gm | buffer | file | 3.72 | 3.8 |
|
||||||
|
| gm | buffer | buffer | 3.80 | 3.9 |
|
||||||
|
| gm | file | file | 3.67 | 3.8 |
|
||||||
|
| gm | file | buffer | 3.67 | 3.8 |
|
||||||
|
| sharp | buffer | file | 13.62 | 14.0 |
|
||||||
|
| sharp | buffer | buffer | 12.43 | 12.8 |
|
||||||
|
| sharp | file | file | 13.02 | 13.4 |
|
||||||
|
| sharp | file | buffer | 11.15 | 11.5 |
|
||||||
|
| sharp +sharpen | file | buffer | 10.26 | 10.6 |
|
||||||
|
| sharp +progressive | file | buffer | 9.44 | 9.7 |
|
||||||
|
| sharp +sequentialRead | file | buffer | 11.94 | 12.3 |
|
||||||
|
|
||||||
### PNG
|
You can expect much greater performance with caching enabled (default) and using 16+ core machines.
|
||||||
|
|
||||||
* imagemagick x 4.27 ops/sec ±0.21% (25 runs sampled)
|
|
||||||
* gm-file-file x 8.33 ops/sec ±0.19% (44 runs sampled)
|
|
||||||
* gm-file-buffer x 7.45 ops/sec ±0.16% (40 runs sampled)
|
|
||||||
|
|
||||||
* sharp-buffer-file x 4.94 ops/sec ±118.46% (26 runs sampled)
|
|
||||||
* sharp-buffer-buffer x 12.59 ops/sec ±0.55% (64 runs sampled)
|
|
||||||
* sharp-file-file x 44.06 ops/sec ±6.86% (75 runs sampled)
|
|
||||||
* sharp-file-buffer x __46.29 ops/sec__ ±0.38% (76 runs sampled)
|
|
||||||
|
|
||||||
* sharp-file-buffer-sharpen x 38.86 ops/sec ±0.22% (65 runs sampled)
|
|
||||||
* sharp-file-buffer-progressive x 46.35 ops/sec ±0.20% (76 runs sampled)
|
|
||||||
* sharp-file-buffer-sequentialRead x 29.02 ops/sec ±0.62% (72 runs sampled)
|
|
||||||
|
|
||||||
### WebP
|
|
||||||
|
|
||||||
* sharp-buffer-file x 3.30 ops/sec ±117.14% (19 runs sampled)
|
|
||||||
* sharp-buffer-buffer x 7.66 ops/sec ±5.83% (43 runs sampled)
|
|
||||||
* sharp-file-file x 9.88 ops/sec ±0.98% (52 runs sampled)
|
|
||||||
* sharp-file-buffer x 9.95 ops/sec ±0.25% (52 runs sampled)
|
|
||||||
* sharp-file-buffer-sharpen x 9.05 ops/sec ±0.36% (48 runs sampled)
|
|
||||||
* sharp-file-buffer-sequentialRead x 9.87 ops/sec ±0.98% (52 runs sampled)
|
|
||||||
|
|
||||||
### TIFF
|
|
||||||
|
|
||||||
* sharp-file-file x 68.24 ops/sec ±5.93% (85 runs sampled)
|
|
||||||
* sharp-file-file-sharpen x 50.76 ops/sec ±0.52% (82 runs sampled)
|
|
||||||
* sharp-file-file-sequentialRead x 36.37 ops/sec ±0.90% (87 runs sampled)
|
|
||||||
|
|
||||||
## Licence
|
## Licence
|
||||||
|
|
||||||
Copyright 2013, 2014 Lovell Fuller
|
Copyright 2013, 2014 Lovell Fuller and Pierre Inglebert
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|||||||
@@ -3,15 +3,16 @@
|
|||||||
'target_name': 'sharp',
|
'target_name': 'sharp',
|
||||||
'sources': ['src/sharp.cc'],
|
'sources': ['src/sharp.cc'],
|
||||||
'libraries': [
|
'libraries': [
|
||||||
'<!@(PKG_CONFIG_PATH="/usr/local/lib/pkgconfig" pkg-config --libs vips)',
|
'<!@(PKG_CONFIG_PATH="/usr/local/Library/ENV/pkgconfig/10.8:/usr/local/lib/pkgconfig:/usr/lib/pkgconfig" pkg-config --libs vips)'
|
||||||
'<!@(PKG_CONFIG_PATH="/usr/lib/pkgconfig" pkg-config --libs vips)'
|
|
||||||
],
|
],
|
||||||
'include_dirs': [
|
'include_dirs': [
|
||||||
'/usr/local/include/glib-2.0',
|
'/usr/local/include/glib-2.0',
|
||||||
'/usr/local/lib/glib-2.0/include',
|
'/usr/local/lib/glib-2.0/include',
|
||||||
'/usr/include/glib-2.0',
|
'/usr/include/glib-2.0',
|
||||||
'/usr/lib/glib-2.0/include',
|
'/usr/lib/glib-2.0/include',
|
||||||
'/usr/lib/x86_64-linux-gnu/glib-2.0/include'
|
'/usr/lib/x86_64-linux-gnu/glib-2.0/include',
|
||||||
|
'/usr/lib/i386-linux-gnu/glib-2.0/include',
|
||||||
|
'<!(node -e "require(\'nan\')")'
|
||||||
],
|
],
|
||||||
'cflags': ['-fexceptions', '-pedantic', '-Wall', '-O3'],
|
'cflags': ['-fexceptions', '-pedantic', '-Wall', '-O3'],
|
||||||
'cflags_cc': ['-fexceptions', '-pedantic', '-Wall', '-O3']
|
'cflags_cc': ['-fexceptions', '-pedantic', '-Wall', '-O3']
|
||||||
|
|||||||
34
index.js
@@ -14,6 +14,8 @@ var Sharp = function(input) {
|
|||||||
sharpen: false,
|
sharpen: false,
|
||||||
progressive: false,
|
progressive: false,
|
||||||
sequentialRead: false,
|
sequentialRead: false,
|
||||||
|
quality: 80,
|
||||||
|
compressionLevel: 6,
|
||||||
output: '__jpeg'
|
output: '__jpeg'
|
||||||
};
|
};
|
||||||
if (typeof input === 'string') {
|
if (typeof input === 'string') {
|
||||||
@@ -42,6 +44,12 @@ Sharp.prototype.embedBlack = function() {
|
|||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Sharp.prototype.max = function() {
|
||||||
|
this.options.canvas = 'm';
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
Sharp.prototype.sharpen = function(sharpen) {
|
Sharp.prototype.sharpen = function(sharpen) {
|
||||||
this.options.sharpen = (typeof sharpen === 'boolean') ? sharpen : true;
|
this.options.sharpen = (typeof sharpen === 'boolean') ? sharpen : true;
|
||||||
return this;
|
return this;
|
||||||
@@ -57,6 +65,24 @@ Sharp.prototype.sequentialRead = function(sequentialRead) {
|
|||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Sharp.prototype.quality = function(quality) {
|
||||||
|
if (!Number.isNaN(quality) && quality >= 1 && quality <= 100) {
|
||||||
|
this.options.quality = quality;
|
||||||
|
} else {
|
||||||
|
throw 'Invalid quality (1 to 100) ' + quality;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
Sharp.prototype.compressionLevel = function(compressionLevel) {
|
||||||
|
if (!Number.isNaN(compressionLevel) && compressionLevel >= -1 && compressionLevel <= 9) {
|
||||||
|
this.options.compressionLevel = compressionLevel;
|
||||||
|
} else {
|
||||||
|
throw 'Invalid compressionLevel (-1 to 9) ' + compressionLevel;
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
Sharp.prototype.resize = function(width, height) {
|
Sharp.prototype.resize = function(width, height) {
|
||||||
if (!width) {
|
if (!width) {
|
||||||
this.options.width = -1;
|
this.options.width = -1;
|
||||||
@@ -81,10 +107,14 @@ Sharp.prototype.resize = function(width, height) {
|
|||||||
|
|
||||||
Sharp.prototype.write = function(output, callback) {
|
Sharp.prototype.write = function(output, callback) {
|
||||||
if (!output || output.length === 0) {
|
if (!output || output.length === 0) {
|
||||||
throw 'Invalid output';
|
callback('Invalid output');
|
||||||
|
} else {
|
||||||
|
if (this.options.inFile === output) {
|
||||||
|
callback('Cannot use same file for input and output');
|
||||||
} else {
|
} else {
|
||||||
this._sharp(output, callback);
|
this._sharp(output, callback);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -115,6 +145,8 @@ Sharp.prototype._sharp = function(output, callback) {
|
|||||||
this.options.sharpen,
|
this.options.sharpen,
|
||||||
this.options.progressive,
|
this.options.progressive,
|
||||||
this.options.sequentialRead,
|
this.options.sequentialRead,
|
||||||
|
this.options.quality,
|
||||||
|
this.options.compressionLevel,
|
||||||
callback
|
callback
|
||||||
);
|
);
|
||||||
return this;
|
return this;
|
||||||
|
|||||||
25
package.json
@@ -1,8 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "sharp",
|
"name": "sharp",
|
||||||
"version": "0.2.0",
|
"version": "0.4.2",
|
||||||
"author": "Lovell Fuller",
|
"author": "Lovell Fuller <npm@lovell.info>",
|
||||||
"description": "High performance module to resize JPEG, PNG, WebP and TIFF images using the libvips image processing library",
|
"contributors": [
|
||||||
|
"Pierre Inglebert <pierre.inglebert@gmail.com>"
|
||||||
|
],
|
||||||
|
"description": "High performance Node.js module to resize JPEG, PNG and WebP images using the libvips library",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "node tests/unit && node tests/perf"
|
"test": "node tests/unit && node tests/perf"
|
||||||
},
|
},
|
||||||
@@ -16,6 +19,7 @@
|
|||||||
"png",
|
"png",
|
||||||
"webp",
|
"webp",
|
||||||
"tiff",
|
"tiff",
|
||||||
|
"gif",
|
||||||
"resize",
|
"resize",
|
||||||
"thumbnail",
|
"thumbnail",
|
||||||
"sharpen",
|
"sharpen",
|
||||||
@@ -26,15 +30,18 @@
|
|||||||
"fast",
|
"fast",
|
||||||
"buffer"
|
"buffer"
|
||||||
],
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"nan": "^1.1.0"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"imagemagick": "*",
|
"imagemagick": "^0.1.3",
|
||||||
"gm": "*",
|
"imagemagick-native": "^1.0.0",
|
||||||
"epeg": "*",
|
"gm": "^1.16.0",
|
||||||
"async": "*",
|
"async": "^0.9.0",
|
||||||
"benchmark": "*"
|
"benchmark": "^1.0.0"
|
||||||
},
|
},
|
||||||
"license": "Apache 2.0",
|
"license": "Apache 2.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.8"
|
"node": ">=0.10"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
185
src/sharp.cc
@@ -5,6 +5,8 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <vips/vips.h>
|
#include <vips/vips.h>
|
||||||
|
|
||||||
|
#include "nan.h"
|
||||||
|
|
||||||
using namespace v8;
|
using namespace v8;
|
||||||
using namespace node;
|
using namespace node;
|
||||||
|
|
||||||
@@ -18,21 +20,24 @@ struct resize_baton {
|
|||||||
int width;
|
int width;
|
||||||
int height;
|
int height;
|
||||||
bool crop;
|
bool crop;
|
||||||
|
bool max;
|
||||||
VipsExtend extend;
|
VipsExtend extend;
|
||||||
bool sharpen;
|
bool sharpen;
|
||||||
bool progessive;
|
bool progressive;
|
||||||
VipsAccess access_method;
|
VipsAccess access_method;
|
||||||
|
int quality;
|
||||||
|
int compressionLevel;
|
||||||
std::string err;
|
std::string err;
|
||||||
Persistent<Function> callback;
|
|
||||||
|
|
||||||
resize_baton(): buffer_in_len(0), buffer_out_len(0) {}
|
resize_baton(): buffer_in_len(0), buffer_out_len(0), crop(false), max(false), sharpen(false), progressive(false) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
JPEG,
|
JPEG,
|
||||||
PNG,
|
PNG,
|
||||||
WEBP,
|
WEBP,
|
||||||
TIFF
|
TIFF,
|
||||||
|
MAGICK
|
||||||
} ImageType;
|
} ImageType;
|
||||||
|
|
||||||
unsigned char MARKER_JPEG[] = {0xff, 0xd8};
|
unsigned char MARKER_JPEG[] = {0xff, 0xd8};
|
||||||
@@ -67,9 +72,13 @@ void resize_error(resize_baton *baton, VipsImage *unref) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
void resize_async(uv_work_t *work) {
|
class ResizeWorker : public NanAsyncWorker {
|
||||||
resize_baton* baton = static_cast<resize_baton*>(work->data);
|
public:
|
||||||
|
ResizeWorker(NanCallback *callback, resize_baton *baton)
|
||||||
|
: NanAsyncWorker(callback), baton(baton) {}
|
||||||
|
~ResizeWorker() {}
|
||||||
|
|
||||||
|
void Execute () {
|
||||||
// Input
|
// Input
|
||||||
ImageType inputImageType = JPEG;
|
ImageType inputImageType = JPEG;
|
||||||
VipsImage *in = vips_image_new();
|
VipsImage *in = vips_image_new();
|
||||||
@@ -93,25 +102,30 @@ void resize_async(uv_work_t *work) {
|
|||||||
(baton->err).append("Unsupported input buffer");
|
(baton->err).append("Unsupported input buffer");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else if (is_jpeg(baton->file_in)) {
|
} else if (vips_foreign_is_a("jpegload", baton->file_in.c_str())) {
|
||||||
if (vips_jpegload((baton->file_in).c_str(), &in, "access", baton->access_method, NULL)) {
|
if (vips_jpegload((baton->file_in).c_str(), &in, "access", baton->access_method, NULL)) {
|
||||||
return resize_error(baton, in);
|
return resize_error(baton, in);
|
||||||
}
|
}
|
||||||
} else if (is_png(baton->file_in)) {
|
} else if (vips_foreign_is_a("pngload", baton->file_in.c_str())) {
|
||||||
inputImageType = PNG;
|
inputImageType = PNG;
|
||||||
if (vips_pngload((baton->file_in).c_str(), &in, "access", baton->access_method, NULL)) {
|
if (vips_pngload((baton->file_in).c_str(), &in, "access", baton->access_method, NULL)) {
|
||||||
return resize_error(baton, in);
|
return resize_error(baton, in);
|
||||||
}
|
}
|
||||||
} else if (is_webp(baton->file_in)) {
|
} else if (vips_foreign_is_a("webpload", baton->file_in.c_str())) {
|
||||||
inputImageType = WEBP;
|
inputImageType = WEBP;
|
||||||
if (vips_webpload((baton->file_in).c_str(), &in, "access", baton->access_method, NULL)) {
|
if (vips_webpload((baton->file_in).c_str(), &in, "access", baton->access_method, NULL)) {
|
||||||
return resize_error(baton, in);
|
return resize_error(baton, in);
|
||||||
}
|
}
|
||||||
} else if (is_tiff(baton->file_in)) {
|
} else if (vips_foreign_is_a("tiffload", baton->file_in.c_str())) {
|
||||||
inputImageType = TIFF;
|
inputImageType = TIFF;
|
||||||
if (vips_tiffload((baton->file_in).c_str(), &in, "access", baton->access_method, NULL)) {
|
if (vips_tiffload((baton->file_in).c_str(), &in, "access", baton->access_method, NULL)) {
|
||||||
return resize_error(baton, in);
|
return resize_error(baton, in);
|
||||||
}
|
}
|
||||||
|
} else if(vips_foreign_is_a("magickload", (baton->file_in).c_str())) {
|
||||||
|
inputImageType = MAGICK;
|
||||||
|
if (vips_magickload((baton->file_in).c_str(), &in, "access", baton->access_method, NULL)) {
|
||||||
|
return resize_error(baton, in);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
resize_error(baton, in);
|
resize_error(baton, in);
|
||||||
(baton->err).append("Unsupported input file " + baton->file_in);
|
(baton->err).append("Unsupported input file " + baton->file_in);
|
||||||
@@ -122,17 +136,25 @@ void resize_async(uv_work_t *work) {
|
|||||||
double factor;
|
double factor;
|
||||||
if (baton->width > 0 && baton->height > 0) {
|
if (baton->width > 0 && baton->height > 0) {
|
||||||
// Fixed width and height
|
// Fixed width and height
|
||||||
double xfactor = (double)(in->Xsize) / (double)(baton->width);
|
double xfactor = static_cast<double>(in->Xsize) / static_cast<double>(baton->width);
|
||||||
double yfactor = (double)(in->Ysize) / (double)(baton->height);
|
double yfactor = static_cast<double>(in->Ysize) / static_cast<double>(baton->height);
|
||||||
factor = baton->crop ? std::min(xfactor, yfactor) : std::max(xfactor, yfactor);
|
factor = baton->crop ? std::min(xfactor, yfactor) : std::max(xfactor, yfactor);
|
||||||
|
// if max is set, we need to compute the real size of the thumb image
|
||||||
|
if (baton->max) {
|
||||||
|
if (xfactor > yfactor) {
|
||||||
|
baton->height = round(static_cast<double>(in->Ysize) / xfactor);
|
||||||
|
} else {
|
||||||
|
baton->width = round(static_cast<double>(in->Xsize) / yfactor);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if (baton->width > 0) {
|
} else if (baton->width > 0) {
|
||||||
// Fixed width, auto height
|
// Fixed width, auto height
|
||||||
factor = (double)(in->Xsize) / (double)(baton->width);
|
factor = static_cast<double>(in->Xsize) / static_cast<double>(baton->width);
|
||||||
baton->height = floor((double)(in->Ysize) / factor);
|
baton->height = floor(static_cast<double>(in->Ysize) / factor);
|
||||||
} else if (baton->height > 0) {
|
} else if (baton->height > 0) {
|
||||||
// Fixed height, auto width
|
// Fixed height, auto width
|
||||||
factor = (double)(in->Ysize) / (double)(baton->height);
|
factor = static_cast<double>(in->Ysize) / static_cast<double>(baton->height);
|
||||||
baton->width = floor((double)(in->Xsize) / factor);
|
baton->width = floor(static_cast<double>(in->Xsize) / factor);
|
||||||
} else {
|
} else {
|
||||||
// Identity transform
|
// Identity transform
|
||||||
factor = 1;
|
factor = 1;
|
||||||
@@ -143,7 +165,7 @@ void resize_async(uv_work_t *work) {
|
|||||||
if (shrink < 1) {
|
if (shrink < 1) {
|
||||||
shrink = 1;
|
shrink = 1;
|
||||||
}
|
}
|
||||||
double residual = shrink / (double)factor;
|
double residual = static_cast<double>(shrink) / factor;
|
||||||
|
|
||||||
// Try to use libjpeg shrink-on-load
|
// Try to use libjpeg shrink-on-load
|
||||||
int shrink_on_load = 1;
|
int shrink_on_load = 1;
|
||||||
@@ -186,6 +208,14 @@ void resize_async(uv_work_t *work) {
|
|||||||
if (vips_shrink(shrunk_on_load, &shrunk, shrink, shrink, NULL)) {
|
if (vips_shrink(shrunk_on_load, &shrunk, shrink, shrink, NULL)) {
|
||||||
return resize_error(baton, shrunk_on_load);
|
return resize_error(baton, shrunk_on_load);
|
||||||
}
|
}
|
||||||
|
// Recalculate residual float based on dimensions of required vs shrunk images
|
||||||
|
double residualx = static_cast<double>(baton->width) / static_cast<double>(shrunk->Xsize);
|
||||||
|
double residualy = static_cast<double>(baton->height) / static_cast<double>(shrunk->Ysize);
|
||||||
|
if (baton->crop || baton->max) {
|
||||||
|
residual = std::max(residualx, residualy);
|
||||||
|
} else {
|
||||||
|
residual = std::min(residualx, residualy);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
vips_copy(shrunk_on_load, &shrunk, NULL);
|
vips_copy(shrunk_on_load, &shrunk, NULL);
|
||||||
}
|
}
|
||||||
@@ -205,8 +235,8 @@ void resize_async(uv_work_t *work) {
|
|||||||
// Crop/embed
|
// Crop/embed
|
||||||
VipsImage *canvased = vips_image_new();
|
VipsImage *canvased = vips_image_new();
|
||||||
if (affined->Xsize != baton->width || affined->Ysize != baton->height) {
|
if (affined->Xsize != baton->width || affined->Ysize != baton->height) {
|
||||||
if (baton->crop) {
|
if (baton->crop || baton->max) {
|
||||||
// Crop
|
// Crop/max
|
||||||
int width = std::min(affined->Xsize, baton->width);
|
int width = std::min(affined->Xsize, baton->width);
|
||||||
int height = std::min(affined->Ysize, baton->height);
|
int height = std::min(affined->Ysize, baton->height);
|
||||||
int left = (affined->Xsize - width + 1) / 2;
|
int left = (affined->Xsize - width + 1) / 2;
|
||||||
@@ -230,14 +260,16 @@ void resize_async(uv_work_t *work) {
|
|||||||
// Mild sharpen
|
// Mild sharpen
|
||||||
VipsImage *sharpened = vips_image_new();
|
VipsImage *sharpened = vips_image_new();
|
||||||
if (baton->sharpen) {
|
if (baton->sharpen) {
|
||||||
INTMASK* sharpen = im_create_imaskv("sharpen", 3, 3,
|
VipsImage *sharpen = vips_image_new_matrixv(3, 3,
|
||||||
-1, -1, -1,
|
-1.0, -1.0, -1.0,
|
||||||
-1, 32, -1,
|
-1.0, 32.0, -1.0,
|
||||||
-1, -1, -1);
|
-1.0, -1.0, -1.0);
|
||||||
sharpen->scale = 24;
|
vips_image_set_double(sharpen, "scale", 24);
|
||||||
if (im_conv(canvased, sharpened, sharpen)) {
|
if (vips_conv(canvased, &sharpened, sharpen, NULL)) {
|
||||||
|
g_object_unref(sharpen);
|
||||||
return resize_error(baton, canvased);
|
return resize_error(baton, canvased);
|
||||||
}
|
}
|
||||||
|
g_object_unref(sharpen);
|
||||||
} else {
|
} else {
|
||||||
vips_copy(canvased, &sharpened, NULL);
|
vips_copy(canvased, &sharpened, NULL);
|
||||||
}
|
}
|
||||||
@@ -246,37 +278,37 @@ void resize_async(uv_work_t *work) {
|
|||||||
// Output
|
// Output
|
||||||
if (baton->file_out == "__jpeg" || (baton->file_out == "__input" && inputImageType == JPEG)) {
|
if (baton->file_out == "__jpeg" || (baton->file_out == "__input" && inputImageType == JPEG)) {
|
||||||
// Write JPEG to buffer
|
// Write JPEG to buffer
|
||||||
if (vips_jpegsave_buffer(sharpened, &baton->buffer_out, &baton->buffer_out_len, "strip", TRUE, "Q", 80, "optimize_coding", TRUE, "interlace", baton->progessive, NULL)) {
|
if (vips_jpegsave_buffer(sharpened, &baton->buffer_out, &baton->buffer_out_len, "strip", TRUE, "Q", baton->quality, "optimize_coding", TRUE, "interlace", baton->progressive, NULL)) {
|
||||||
return resize_error(baton, sharpened);
|
return resize_error(baton, sharpened);
|
||||||
}
|
}
|
||||||
} else if (baton->file_out == "__png" || (baton->file_out == "__input" && inputImageType == PNG)) {
|
} else if (baton->file_out == "__png" || (baton->file_out == "__input" && inputImageType == PNG)) {
|
||||||
// Write PNG to buffer
|
// Write PNG to buffer
|
||||||
if (vips_pngsave_buffer(sharpened, &baton->buffer_out, &baton->buffer_out_len, "strip", TRUE, "compression", 6, "interlace", baton->progessive, NULL)) {
|
if (vips_pngsave_buffer(sharpened, &baton->buffer_out, &baton->buffer_out_len, "strip", TRUE, "compression", baton->compressionLevel, "interlace", baton->progressive, NULL)) {
|
||||||
return resize_error(baton, sharpened);
|
return resize_error(baton, sharpened);
|
||||||
}
|
}
|
||||||
} else if (baton->file_out == "__webp" || (baton->file_out == "__input" && inputImageType == WEBP)) {
|
} else if (baton->file_out == "__webp" || (baton->file_out == "__input" && inputImageType == WEBP)) {
|
||||||
// Write WEBP to buffer
|
// Write WEBP to buffer
|
||||||
if (vips_webpsave_buffer(sharpened, &baton->buffer_out, &baton->buffer_out_len, "strip", TRUE, "Q", 80, NULL)) {
|
if (vips_webpsave_buffer(sharpened, &baton->buffer_out, &baton->buffer_out_len, "strip", TRUE, "Q", baton->quality, NULL)) {
|
||||||
return resize_error(baton, sharpened);
|
return resize_error(baton, sharpened);
|
||||||
}
|
}
|
||||||
} else if (is_jpeg(baton->file_out)) {
|
} else if (is_jpeg(baton->file_out)) {
|
||||||
// Write JPEG to file
|
// Write JPEG to file
|
||||||
if (vips_jpegsave(sharpened, baton->file_out.c_str(), "strip", TRUE, "Q", 80, "optimize_coding", TRUE, "interlace", baton->progessive, NULL)) {
|
if (vips_jpegsave(sharpened, baton->file_out.c_str(), "strip", TRUE, "Q", baton->quality, "optimize_coding", TRUE, "interlace", baton->progressive, NULL)) {
|
||||||
return resize_error(baton, sharpened);
|
return resize_error(baton, sharpened);
|
||||||
}
|
}
|
||||||
} else if (is_png(baton->file_out)) {
|
} else if (is_png(baton->file_out)) {
|
||||||
// Write PNG to file
|
// Write PNG to file
|
||||||
if (vips_pngsave(sharpened, baton->file_out.c_str(), "strip", TRUE, "compression", 6, "interlace", baton->progessive, NULL)) {
|
if (vips_pngsave(sharpened, baton->file_out.c_str(), "strip", TRUE, "compression", baton->compressionLevel, "interlace", baton->progressive, NULL)) {
|
||||||
return resize_error(baton, sharpened);
|
return resize_error(baton, sharpened);
|
||||||
}
|
}
|
||||||
} else if (is_webp(baton->file_out)) {
|
} else if (is_webp(baton->file_out)) {
|
||||||
// Write WEBP to file
|
// Write WEBP to file
|
||||||
if (vips_webpsave(sharpened, baton->file_out.c_str(), "strip", TRUE, "Q", 80, NULL)) {
|
if (vips_webpsave(sharpened, baton->file_out.c_str(), "strip", TRUE, "Q", baton->quality, NULL)) {
|
||||||
return resize_error(baton, sharpened);
|
return resize_error(baton, sharpened);
|
||||||
}
|
}
|
||||||
} else if (is_tiff(baton->file_out)) {
|
} else if (is_tiff(baton->file_out)) {
|
||||||
// Write TIFF to file
|
// Write TIFF to file
|
||||||
if (vips_tiffsave(sharpened, baton->file_out.c_str(), "strip", TRUE, "compression", VIPS_FOREIGN_TIFF_COMPRESSION_JPEG, "Q", 80, NULL)) {
|
if (vips_tiffsave(sharpened, baton->file_out.c_str(), "strip", TRUE, "compression", VIPS_FOREIGN_TIFF_COMPRESSION_JPEG, "Q", baton->quality, NULL)) {
|
||||||
return resize_error(baton, sharpened);
|
return resize_error(baton, sharpened);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -284,36 +316,30 @@ void resize_async(uv_work_t *work) {
|
|||||||
}
|
}
|
||||||
g_object_unref(sharpened);
|
g_object_unref(sharpened);
|
||||||
vips_thread_shutdown();
|
vips_thread_shutdown();
|
||||||
}
|
|
||||||
|
|
||||||
void resize_async_after(uv_work_t *work, int status) {
|
|
||||||
HandleScope scope;
|
|
||||||
|
|
||||||
resize_baton *baton = static_cast<resize_baton*>(work->data);
|
|
||||||
|
|
||||||
Handle<Value> argv[2] = { Null(), Null() };
|
|
||||||
if (!baton->err.empty()) {
|
|
||||||
// Error
|
|
||||||
argv[0] = scope.Close(String::New(baton->err.data(), baton->err.size()));
|
|
||||||
} else if (baton->buffer_out_len > 0) {
|
|
||||||
// Buffer
|
|
||||||
Buffer *slowBuffer = Buffer::New(baton->buffer_out_len);
|
|
||||||
memcpy(Buffer::Data(slowBuffer), baton->buffer_out, baton->buffer_out_len);
|
|
||||||
Local<Object> globalObj = Context::GetCurrent()->Global();
|
|
||||||
Local<Function> bufferConstructor = Local<Function>::Cast(globalObj->Get(String::New("Buffer")));
|
|
||||||
Handle<Value> constructorArgs[3] = { slowBuffer->handle_, v8::Integer::New(baton->buffer_out_len), v8::Integer::New(0) };
|
|
||||||
argv[1] = scope.Close(bufferConstructor->NewInstance(3, constructorArgs));
|
|
||||||
g_free(baton->buffer_out);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
baton->callback->Call(Context::GetCurrent()->Global(), 2, argv);
|
void HandleOKCallback () {
|
||||||
baton->callback.Dispose();
|
NanScope();
|
||||||
delete baton;
|
|
||||||
delete work;
|
|
||||||
}
|
|
||||||
|
|
||||||
Handle<Value> resize(const Arguments& args) {
|
Handle<Value> argv[2] = { NanNull(), NanNull() };
|
||||||
HandleScope scope;
|
if (!baton->err.empty()) {
|
||||||
|
// Error
|
||||||
|
argv[0] = NanNew<String>(baton->err.data(), baton->err.size());
|
||||||
|
} else if (baton->buffer_out_len > 0) {
|
||||||
|
// Buffer
|
||||||
|
argv[1] = NanNewBufferHandle((char *)baton->buffer_out, baton->buffer_out_len);
|
||||||
|
g_free(baton->buffer_out);
|
||||||
|
}
|
||||||
|
delete baton;
|
||||||
|
callback->Call(2, argv);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
resize_baton* baton;
|
||||||
|
};
|
||||||
|
|
||||||
|
NAN_METHOD(resize) {
|
||||||
|
NanScope();
|
||||||
|
|
||||||
resize_baton *baton = new resize_baton;
|
resize_baton *baton = new resize_baton;
|
||||||
baton->file_in = *String::Utf8Value(args[0]->ToString());
|
baton->file_in = *String::Utf8Value(args[0]->ToString());
|
||||||
@@ -326,28 +352,29 @@ Handle<Value> resize(const Arguments& args) {
|
|||||||
baton->width = args[3]->Int32Value();
|
baton->width = args[3]->Int32Value();
|
||||||
baton->height = args[4]->Int32Value();
|
baton->height = args[4]->Int32Value();
|
||||||
Local<String> canvas = args[5]->ToString();
|
Local<String> canvas = args[5]->ToString();
|
||||||
if (canvas->Equals(String::NewSymbol("c"))) {
|
if (canvas->Equals(NanNew<String>("c"))) {
|
||||||
baton->crop = true;
|
baton->crop = true;
|
||||||
} else if (canvas->Equals(String::NewSymbol("w"))) {
|
} else if (canvas->Equals(NanNew<String>("w"))) {
|
||||||
baton->crop = false;
|
|
||||||
baton->extend = VIPS_EXTEND_WHITE;
|
baton->extend = VIPS_EXTEND_WHITE;
|
||||||
} else if (canvas->Equals(String::NewSymbol("b"))) {
|
} else if (canvas->Equals(NanNew<String>("b"))) {
|
||||||
baton->crop = false;
|
|
||||||
baton->extend = VIPS_EXTEND_BLACK;
|
baton->extend = VIPS_EXTEND_BLACK;
|
||||||
|
} else if (canvas->Equals(NanNew<String>("m"))) {
|
||||||
|
baton->max = true;
|
||||||
}
|
}
|
||||||
baton->sharpen = args[6]->BooleanValue();
|
baton->sharpen = args[6]->BooleanValue();
|
||||||
baton->progessive = args[7]->BooleanValue();
|
baton->progressive = args[7]->BooleanValue();
|
||||||
baton->access_method = args[8]->BooleanValue() ? VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_RANDOM;
|
baton->access_method = args[8]->BooleanValue() ? VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_RANDOM;
|
||||||
baton->callback = Persistent<Function>::New(Local<Function>::Cast(args[9]));
|
baton->quality = args[9]->Int32Value();
|
||||||
|
baton->compressionLevel = args[10]->Int32Value();
|
||||||
|
|
||||||
uv_work_t *work = new uv_work_t;
|
NanCallback *callback = new NanCallback(args[11].As<v8::Function>());
|
||||||
work->data = baton;
|
|
||||||
uv_queue_work(uv_default_loop(), work, resize_async, (uv_after_work_cb)resize_async_after);
|
NanAsyncQueueWorker(new ResizeWorker(callback, baton));
|
||||||
return scope.Close(Undefined());
|
NanReturnUndefined();
|
||||||
}
|
}
|
||||||
|
|
||||||
Handle<Value> cache(const Arguments& args) {
|
NAN_METHOD(cache) {
|
||||||
HandleScope scope;
|
NanScope();
|
||||||
|
|
||||||
// Set cache limit
|
// Set cache limit
|
||||||
if (args[0]->IsInt32()) {
|
if (args[0]->IsInt32()) {
|
||||||
@@ -355,20 +382,20 @@ Handle<Value> cache(const Arguments& args) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get cache statistics
|
// Get cache statistics
|
||||||
Local<Object> cache = Object::New();
|
Local<Object> cache = NanNew<Object>();
|
||||||
cache->Set(String::NewSymbol("current"), Number::New(vips_tracked_get_mem() / 1048576));
|
cache->Set(NanNew<String>("current"), NanNew<Number>(vips_tracked_get_mem() / 1048576));
|
||||||
cache->Set(String::NewSymbol("high"), Number::New(vips_tracked_get_mem_highwater() / 1048576));
|
cache->Set(NanNew<String>("high"), NanNew<Number>(vips_tracked_get_mem_highwater() / 1048576));
|
||||||
cache->Set(String::NewSymbol("limit"), Number::New(vips_cache_get_max_mem() / 1048576));
|
cache->Set(NanNew<String>("limit"), NanNew<Number>(vips_cache_get_max_mem() / 1048576));
|
||||||
return scope.Close(cache);
|
NanReturnValue(cache);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void at_exit(void* arg) {
|
static void at_exit(void* arg) {
|
||||||
HandleScope scope;
|
NanScope();
|
||||||
vips_shutdown();
|
vips_shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" void init(Handle<Object> target) {
|
extern "C" void init(Handle<Object> target) {
|
||||||
HandleScope scope;
|
NanScope();
|
||||||
vips_init("");
|
vips_init("");
|
||||||
AtExit(at_exit);
|
AtExit(at_exit);
|
||||||
NODE_SET_METHOD(target, "resize", resize);
|
NODE_SET_METHOD(target, "resize", resize);
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 813 KiB After Width: | Height: | Size: 813 KiB |
0
tests/4.webp → tests/fixtures/4.webp
vendored
|
Before Width: | Height: | Size: 173 KiB After Width: | Height: | Size: 173 KiB |
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 84 KiB |
BIN
tests/fixtures/Crash_test.gif
vendored
Normal file
|
After Width: | Height: | Size: 278 KiB |
@@ -1,9 +1,10 @@
|
|||||||
var sharp = require("../index");
|
var sharp = require("../index");
|
||||||
var fs = require("fs");
|
var fs = require("fs");
|
||||||
|
var path = require("path");
|
||||||
var assert = require("assert");
|
var assert = require("assert");
|
||||||
var async = require("async");
|
var async = require("async");
|
||||||
|
|
||||||
var inputJpg = __dirname + "/2569067123_aca715a2ee_o.jpg"; // http://www.flickr.com/photos/grizdave/2569067123/
|
var inputJpg = path.join(__dirname, "fixtures/2569067123_aca715a2ee_o.jpg"); // http://www.flickr.com/photos/grizdave/2569067123/
|
||||||
var width = 720;
|
var width = 720;
|
||||||
var height = 480;
|
var height = 480;
|
||||||
|
|
||||||
|
|||||||
135
tests/perf.js
@@ -1,31 +1,39 @@
|
|||||||
var sharp = require("../index");
|
var sharp = require("../index");
|
||||||
var fs = require("fs");
|
var fs = require("fs");
|
||||||
|
var path = require("path");
|
||||||
var imagemagick = require("imagemagick");
|
var imagemagick = require("imagemagick");
|
||||||
|
var imagemagickNative = require("imagemagick-native");
|
||||||
var gm = require("gm");
|
var gm = require("gm");
|
||||||
var epeg = require("epeg");
|
|
||||||
var async = require("async");
|
var async = require("async");
|
||||||
var assert = require("assert");
|
var assert = require("assert");
|
||||||
var Benchmark = require("benchmark");
|
var Benchmark = require("benchmark");
|
||||||
|
|
||||||
var inputJpg = __dirname + "/2569067123_aca715a2ee_o.jpg"; // http://www.flickr.com/photos/grizdave/2569067123/
|
var fixturesPath = path.join(__dirname, "fixtures");
|
||||||
var outputJpg = __dirname + "/output.jpg";
|
|
||||||
|
|
||||||
var inputPng = __dirname + "/50020484-00001.png"; // http://c.searspartsdirect.com/lis_png/PLDM/50020484-00001.png
|
var inputJpg = path.join(fixturesPath, "2569067123_aca715a2ee_o.jpg"); // http://www.flickr.com/photos/grizdave/2569067123/
|
||||||
var outputPng = __dirname + "/output.png";
|
var outputJpg = path.join(fixturesPath, "output.jpg");
|
||||||
|
|
||||||
var inputWebp = __dirname + "/4.webp"; // http://www.gstatic.com/webp/gallery/4.webp
|
var inputPng = path.join(fixturesPath, "50020484-00001.png"); // http://c.searspartsdirect.com/lis_png/PLDM/50020484-00001.png
|
||||||
var outputWebp = __dirname + "/output.webp";
|
var outputPng = path.join(fixturesPath, "output.png");
|
||||||
|
|
||||||
var inputTiff = __dirname + "/G31D.TIF"; // http://www.fileformat.info/format/tiff/sample/e6c9a6e5253348f4aef6d17b534360ab/index.htm
|
var inputWebp = path.join(fixturesPath, "4.webp"); // http://www.gstatic.com/webp/gallery/4.webp
|
||||||
var outputTiff = __dirname + "/output.tiff";
|
var outputWebp = path.join(fixturesPath, "output.webp");
|
||||||
|
|
||||||
|
var inputTiff = path.join(fixturesPath, "G31D.TIF"); // http://www.fileformat.info/format/tiff/sample/e6c9a6e5253348f4aef6d17b534360ab/index.htm
|
||||||
|
var outputTiff = path.join(fixturesPath, "output.tiff");
|
||||||
|
|
||||||
|
var inputGif = path.join(fixturesPath, "Crash_test.gif"); // http://upload.wikimedia.org/wikipedia/commons/e/e3/Crash_test.gif
|
||||||
|
|
||||||
var width = 720;
|
var width = 720;
|
||||||
var height = 480;
|
var height = 480;
|
||||||
|
|
||||||
|
// Disable libvips cache to ensure tests are as fair as they can be
|
||||||
|
sharp.cache(0);
|
||||||
|
|
||||||
async.series({
|
async.series({
|
||||||
jpeg: function(callback) {
|
jpeg: function(callback) {
|
||||||
var inputJpgBuffer = fs.readFileSync(inputJpg);
|
var inputJpgBuffer = fs.readFileSync(inputJpg);
|
||||||
(new Benchmark.Suite("jpeg")).add("imagemagick", {
|
(new Benchmark.Suite("jpeg")).add("imagemagick-file-file", {
|
||||||
defer: true,
|
defer: true,
|
||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
imagemagick.resize({
|
imagemagick.resize({
|
||||||
@@ -42,6 +50,41 @@ async.series({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}).add("imagemagick-native-buffer-buffer", {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
imagemagickNative.convert({
|
||||||
|
srcData: inputJpgBuffer,
|
||||||
|
quality: 80,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
format: 'JPEG'
|
||||||
|
});
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
}).add("gm-buffer-file", {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
gm(inputJpgBuffer).resize(width, height).quality(80).write(outputJpg, function (err) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).add("gm-buffer-buffer", {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
gm(inputJpgBuffer).resize(width, height).quality(80).toBuffer(function (err, buffer) {
|
||||||
|
if (err) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
assert.notStrictEqual(null, buffer);
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}).add("gm-file-file", {
|
}).add("gm-file-file", {
|
||||||
defer: true,
|
defer: true,
|
||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
@@ -65,21 +108,6 @@ async.series({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).add("epeg-file-file", {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
var image = new epeg.Image({path: inputJpg});
|
|
||||||
image.downsize(width, height, 80).saveTo(outputJpg);
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
}).add("epeg-file-buffer", {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
var image = new epeg.Image({path: inputJpg});
|
|
||||||
var buffer = image.downsize(width, height, 80).process();
|
|
||||||
assert.notStrictEqual(null, buffer);
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
}).add("sharp-buffer-file", {
|
}).add("sharp-buffer-file", {
|
||||||
defer: true,
|
defer: true,
|
||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
@@ -170,7 +198,7 @@ async.series({
|
|||||||
},
|
},
|
||||||
png: function(callback) {
|
png: function(callback) {
|
||||||
var inputPngBuffer = fs.readFileSync(inputPng);
|
var inputPngBuffer = fs.readFileSync(inputPng);
|
||||||
(new Benchmark.Suite("png")).add("imagemagick", {
|
(new Benchmark.Suite("png")).add("imagemagick-file-file", {
|
||||||
defer: true,
|
defer: true,
|
||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
imagemagick.resize({
|
imagemagick.resize({
|
||||||
@@ -186,6 +214,17 @@ async.series({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}).add("imagemagick-native-buffer-buffer", {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
imagemagickNative.convert({
|
||||||
|
srcData: inputPngBuffer,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
format: 'PNG'
|
||||||
|
});
|
||||||
|
deferred.resolve();
|
||||||
|
}
|
||||||
}).add("gm-file-file", {
|
}).add("gm-file-file", {
|
||||||
defer: true,
|
defer: true,
|
||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
@@ -414,11 +453,53 @@ async.series({
|
|||||||
}).on("complete", function() {
|
}).on("complete", function() {
|
||||||
callback(null, this.filter("fastest").pluck("name"));
|
callback(null, this.filter("fastest").pluck("name"));
|
||||||
}).run();
|
}).run();
|
||||||
|
},
|
||||||
|
gif: function(callback) {
|
||||||
|
(new Benchmark.Suite("gif")).add("sharp-file-file", {
|
||||||
|
defer: true,
|
||||||
|
fn: function(deferred) {
|
||||||
|
sharp(inputGif).resize(width, height).write(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().write(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).write(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) {
|
}, function(err, results) {
|
||||||
assert(!err, err);
|
assert(!err, err);
|
||||||
Object.keys(results).forEach(function(format) {
|
Object.keys(results).forEach(function(format) {
|
||||||
assert.strictEqual("sharp", results[format].toString().substr(0, 5), "sharp was slower than " + results[format] + " for " + format);
|
if (results[format].toString().substr(0, 5) !== "sharp") {
|
||||||
|
console.log("sharp was slower than " + results[format] + " for " + format);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
console.dir(sharp.cache());
|
console.dir(sharp.cache());
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
var sharp = require("../index");
|
var sharp = require("../index");
|
||||||
var fs = require("fs");
|
var fs = require("fs");
|
||||||
|
var path = require("path");
|
||||||
var imagemagick = require("imagemagick");
|
var imagemagick = require("imagemagick");
|
||||||
var gm = require("gm");
|
var gm = require("gm");
|
||||||
var epeg = require("epeg");
|
|
||||||
var async = require("async");
|
var async = require("async");
|
||||||
var assert = require("assert");
|
var assert = require("assert");
|
||||||
var Benchmark = require("benchmark");
|
var Benchmark = require("benchmark");
|
||||||
|
|
||||||
var inputJpg = __dirname + "/2569067123_aca715a2ee_o.jpg"; // http://www.flickr.com/photos/grizdave/2569067123/
|
var fixturesPath = path.join(__dirname, "fixtures");
|
||||||
var outputJpg = __dirname + "/output.jpg";
|
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 min = 320;
|
||||||
var max = 960;
|
var max = 960;
|
||||||
@@ -46,14 +47,6 @@ new Benchmark.Suite("random").add("imagemagick", {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}).add("epeg", {
|
|
||||||
defer: true,
|
|
||||||
fn: function(deferred) {
|
|
||||||
var image = new epeg.Image({path: inputJpg});
|
|
||||||
var buffer = image.downsize(randomDimension(), randomDimension(), 80).process();
|
|
||||||
assert.notStrictEqual(null, buffer);
|
|
||||||
deferred.resolve();
|
|
||||||
}
|
|
||||||
}).add("sharp", {
|
}).add("sharp", {
|
||||||
defer: true,
|
defer: true,
|
||||||
fn: function(deferred) {
|
fn: function(deferred) {
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
var sharp = require("../index");
|
var sharp = require("../index");
|
||||||
|
var path = require("path");
|
||||||
var imagemagick = require("imagemagick");
|
var imagemagick = require("imagemagick");
|
||||||
var assert = require("assert");
|
var assert = require("assert");
|
||||||
var async = require("async");
|
var async = require("async");
|
||||||
|
|
||||||
var inputJpg = __dirname + "/2569067123_aca715a2ee_o.jpg"; // http://www.flickr.com/photos/grizdave/2569067123/
|
var fixturesPath = path.join(__dirname, "fixtures");
|
||||||
var outputJpg = __dirname + "/output.jpg";
|
|
||||||
|
var inputJpg = path.join(fixturesPath, "2569067123_aca715a2ee_o.jpg"); // http://www.flickr.com/photos/grizdave/2569067123/
|
||||||
|
var outputJpg = path.join(fixturesPath, "output.jpg");
|
||||||
|
|
||||||
|
var inputTiff = path.join(fixturesPath, "G31D.TIF"); // http://www.fileformat.info/format/tiff/sample/e6c9a6e5253348f4aef6d17b534360ab/index.htm
|
||||||
|
var outputTiff = path.join(fixturesPath, "output.tiff");
|
||||||
|
|
||||||
async.series([
|
async.series([
|
||||||
// Resize with exact crop
|
// Resize with exact crop
|
||||||
@@ -66,5 +72,85 @@ async.series([
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
// Quality
|
||||||
|
function(done) {
|
||||||
|
sharp(inputJpg).resize(320, 240).quality(70).jpeg(function(err, buffer70) {
|
||||||
|
if (err) throw err;
|
||||||
|
sharp(inputJpg).resize(320, 240).jpeg(function(err, buffer80) {
|
||||||
|
if (err) throw err;
|
||||||
|
sharp(inputJpg).resize(320, 240).quality(90).jpeg(function(err, buffer90) {
|
||||||
|
assert(buffer70.length < buffer80.length);
|
||||||
|
assert(buffer80.length < buffer90.length);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// TIFF with dimensions known to cause rounding errors
|
||||||
|
function(done) {
|
||||||
|
sharp(inputTiff).resize(240, 320).embedBlack().write(outputJpg, function(err) {
|
||||||
|
if (err) throw err;
|
||||||
|
imagemagick.identify(outputJpg, function(err, features) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(240, features.width);
|
||||||
|
assert.strictEqual(320, features.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function(done) {
|
||||||
|
sharp(inputTiff).resize(240, 320).write(outputJpg, function(err) {
|
||||||
|
if (err) throw err;
|
||||||
|
imagemagick.identify(outputJpg, function(err, features) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(240, features.width);
|
||||||
|
assert.strictEqual(320, features.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// Resize to max width or height considering ratio (landscape)
|
||||||
|
function(done) {
|
||||||
|
sharp(inputJpg).resize(320, 320).max().write(outputJpg, function(err) {
|
||||||
|
if (err) throw err;
|
||||||
|
imagemagick.identify(outputJpg, function(err, features) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(320, features.width);
|
||||||
|
assert.strictEqual(261, features.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// Resize to max width or height considering ratio (portrait)
|
||||||
|
function(done) {
|
||||||
|
sharp(inputTiff).resize(320, 320).max().write(outputJpg, function(err) {
|
||||||
|
if (err) throw err;
|
||||||
|
imagemagick.identify(outputJpg, function(err, features) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(243, features.width);
|
||||||
|
assert.strictEqual(320, features.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// Attempt to resize to max but only provide one dimension, so should default to crop
|
||||||
|
function(done) {
|
||||||
|
sharp(inputJpg).resize(320).max().write(outputJpg, function(err) {
|
||||||
|
if (err) throw err;
|
||||||
|
imagemagick.identify(outputJpg, function(err, features) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(320, features.width);
|
||||||
|
assert.strictEqual(261, features.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// Attempt to output to input, should fail
|
||||||
|
function(done) {
|
||||||
|
sharp(inputJpg).write(inputJpg, function(err) {
|
||||||
|
assert(!!err);
|
||||||
|
done();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
|||||||