Compare commits

...

78 Commits

Author SHA1 Message Date
Lovell Fuller
9f59a2aebf Version bumps and changelog for v0.11.4 2015-11-05 21:36:44 +00:00
Lovell Fuller
26fb75bf3f Merge pull request #291 from brandonaaron/corner-gravity
Add corner gravity support
2015-10-27 21:17:53 +00:00
Brandon Aaron
25e5f27785 add corner gavity support 2015-10-27 15:10:10 -04:00
Lovell Fuller
ef62daccf9 Upgrade CI and preinstall script to libvips 8.1.1 2015-10-16 23:43:55 +01:00
Lovell Fuller
9067c0a000 Merge pull request #288 from brandonaaron/exif_flop_2_and_4
EXIF Orientation tags 2 and 4 were flipping instead of flopping
2015-10-16 21:45:16 +01:00
Brandon Aaron
79470d2e07 EXIF Orientation tags 2 and 4 were flipping instead of flopping 2015-10-16 15:41:40 -04:00
Lovell Fuller
3cefa6f2bf Merge pull request #287 from vlapo/master
Add --runtime_link=static option to GYP binding for use with AWS Lambda
2015-10-14 19:22:09 +01:00
vlapo
75d954a6bc Add --runtime_link=static 2015-10-14 11:29:29 +02:00
Lovell Fuller
1b7e3746cc Merge pull request #286 from rayshan/patch-1
Replace `filename` with `path` to make consistent with Node's `fs` module
2015-10-13 21:03:32 +01:00
Ray Shan
29252d9dbb Clarify fileName argument by changing to path
I noticed we can do something like `.toFile("tmp/temp.jpg")`
2015-10-13 12:21:41 -07:00
Lovell Fuller
23e14861be Update perf test results to include jimp
Remove imagemagick-native until Node v4 support
2015-10-10 18:41:01 +01:00
Lovell Fuller
97960b5f91 Merge branch 'master' of https://github.com/lovell/sharp 2015-10-10 13:44:42 +01:00
Lovell Fuller
18c4ad9adf Version bumps of perf test candidates 2015-10-10 13:44:02 +01:00
Lovell Fuller
b240c53633 Merge pull request #282 from jabbrass/patch-1
Fix typo (docs/api)
2015-10-08 07:27:35 +01:00
J. Andrew Brassington
660f3d58be Fix typo (docs/api)
Line 330: "betweem" => "between"
2015-10-07 19:01:35 -07:00
Lovell Fuller
b6d75cda8e Revert Windows/Appveyor CI to libvips 8.0.2 2015-10-07 21:28:58 +01:00
Lovell Fuller
e07356c11c Update list of preinstall OS support
Upgrade to libvips 8.1.0
2015-10-07 20:49:47 +01:00
Lovell Fuller
82e215a42e Merge pull request #280 from roryrjb/master
(preinstall) add wily to 'supported' distros
2015-10-03 10:20:51 +01:00
Rory Bradford
cc1c36d891 (preinstall) add wily to 'supported' distros 2015-10-03 09:45:46 +01:00
Lovell Fuller
a1a2d7de5c Centos 7 provides LittleCMS via lcms2-devel #278 2015-09-30 17:35:35 +01:00
Lovell Fuller
6dce2deb82 Add jimp to perf tests #275 2015-09-27 09:38:47 +01:00
Lovell Fuller
cdad84edc6 Merge pull request #266 from roryrjb/master
(preinstall) add freya to 'supported' distros
2015-09-11 17:32:03 +01:00
Rory Bradford
de842a67d8 (preinstall) add freya to 'supported' distros 2015-09-11 17:10:25 +01:00
Lovell Fuller
918bbe88c6 Switch Travis to Ubuntu 14.04/GCE
Remove v0.10 and custom msvs flag from Appveyor
2015-09-09 22:18:22 +01:00
Lovell Fuller
7d3891989c Add Node.js v4 to CI builds 2015-09-08 20:50:33 +01:00
Lovell Fuller
168fe7c8d9 v0.11.3 2015-09-08 19:16:07 +01:00
Lovell Fuller
29ab8408fb Version bumps and changelog for v0.11.3 2015-09-08 15:13:28 +01:00
Lovell Fuller
692e2d4df4 Automate expected vs actual for blur/sharpen tests 2015-09-07 14:17:35 +01:00
Lovell Fuller
85d86dbede Merge pull request #263 from chrisriley/nan2-doubles
Convert blur and sharpen parameters to double instead of int.
This was broken during the nan v2 upgrade by commit b7e0a6f.
2015-09-06 19:29:06 +01:00
Chris Riley
ce2d7b8efc Update pipeline.cc
In the upgrade to nan v2 that was part of v0.11.2 the baton values for blurSigma, sharpenFlat, and sharpenJagged were being cast to int32_t causing blur(sigma) and sharpen(radius, flat, jagged) to fail with "parameters out of range". This patch casts these baton values to doubles.
2015-09-06 12:29:07 -04:00
Lovell Fuller
be00d72d82 Upgrade nan to fix clang linking problem #259 2015-08-28 10:48:20 +01:00
Lovell Fuller
5b376364f5 Add test case for require-time libc++ segfault 2015-08-27 23:59:48 +01:00
Lovell Fuller
409d15c624 Support EXIF Orientation removal in libvips v8.1.0
See #189 and jcupitt/libvips@fbe321e
2015-08-24 21:48:38 +01:00
Lovell Fuller
ce22388c3b Add io.js v3 to Appveyor CI test matrix 2015-08-24 19:31:11 +01:00
Lovell Fuller
30143cf509 Version bumps and changelog updates for v0.11.2 2015-08-24 19:03:05 +01:00
Lovell Fuller
78f31d2b0d Auto-exclude benchmark contenders that fail compilation 2015-08-24 18:55:59 +01:00
Lovell Fuller
4e67a5025a Use malloc for Nan::NewBuffer owned data
GC results in free() rather than delete[]

Add plenty of new valgrind suppressions
2015-08-24 16:14:49 +01:00
Lovell Fuller
b7e0a6f277 Upgrade to nan v2 #246
Provides support for io.js v3 and Node v4
2015-08-23 21:36:48 +01:00
Lovell Fuller
045680fba5 Document use of crop gravity as a String 2015-08-19 16:28:28 +01:00
Lovell Fuller
692347cc6b Merge pull request #255 from papandreou/feature/cropString
crop: Permit specifying the gravity as a string
2015-08-19 15:13:29 +01:00
Andreas Lind
faa515d969 crop: Permit specifying the gravity as a string.
Will be looked up in require('sharp').gravity.
2015-08-19 14:49:02 +02:00
Lovell Fuller
c4a278ec9c Update changelog for v0.11.1
Version bumps for benchmark test
2015-08-12 09:49:50 +01:00
Lovell Fuller
658a541f49 Add test case for enlarge+embed combo #243 2015-08-12 09:26:38 +01:00
Lovell Fuller
01435977de Blur and sharpen ops always convolve
Partial revert of 36ac882
2015-08-12 07:25:14 +01:00
Lovell Fuller
36ac8828f2 Gamma correction and premultiply do not mix
Skip premultiply for fast blur/sharpen

Automate gamma correction tests
2015-08-11 21:51:22 +01:00
Lovell Fuller
9c83d98bbb Update Appveyor CI to MSVC 2015 2015-07-31 12:11:18 +01:00
Lovell Fuller
dee9ca3ec2 Merge pull request #244 from TheThing/patch-1
Silence MSVC warning:
"C4530: C++ exception handler used,
but unwind semantics are not enabled."
2015-07-26 15:58:54 +01:00
Jonatan Nilsson
d375327d20 binding.gyp: Fix warnings during compiling
Fixes the following warnings while compiling:

```
D:\Forritun\Microsoft Visual Studio 14.0\VC\include\xlocale(341): warning C4530: C++ exception handler used, but unwind semantics are not enabled. Specify /EHsc (compiling source file ..\src\common.cc) [D:\sharp\build\sharp.vcxproj]
D:\Forritun\Microsoft Visual Studio 14.0\VC\include\xlocale(341): warning C4530: C++ exception handler used, but unwind semantics are not enabled. Specify /EHsc (compiling source file ..\src\operations.cc) [D:\sharp\build\sharp.vcxproj]
...etc...
```

Solution taken from here: https://github.com/TooTallNate/node-gyp/issues/26#issuecomment-7296389
2015-07-25 07:44:06 +00:00
Lovell Fuller
09244192e9 Add permalinks back to mkdocs
Erroneously removed in de333eb
2015-07-16 09:34:42 +01:00
Lovell Fuller
de333eb02d Add PPA details for Ubuntu LTS
Remove Mac OS from preinstall script - please use homebrew
2015-07-16 09:28:54 +01:00
Lovell Fuller
8b50f15a44 Version bumps ahead of v0.11.0 2015-07-15 10:55:06 +01:00
Lovell Fuller
f853fa3e23 Suppress known magick and glib leaks 2015-07-15 10:47:09 +01:00
Lovell Fuller
d26f6b3b89 Big documentation clean-up
Add structure via mkdocs (replaces ever-growing README)

Inline usage examples with the method they demonstrate

Add changelog
2015-07-15 10:17:55 +01:00
Lovell Fuller
022a2b1ade Ensure test coverage of overlayWith error paths 2015-07-15 09:37:25 +01:00
Lovell Fuller
4f1ac5717e Clarify removal of EXIF Orientation tag #189 2015-07-13 22:28:59 +01:00
Lovell Fuller
d303703dc5 Allow override of EXIF Orientation tag #189
Clear Orientation when rotate/flip/flop are used
2015-07-13 20:00:33 +01:00
Lovell Fuller
642e5687b6 Guard against unexpected overlay band format #97 2015-07-11 22:56:18 +01:00
Lovell Fuller
2ec845b083 Create normalize/blur/sharpen operations
Reduces pipeline by ~100 LOC
2015-07-11 21:44:39 +01:00
Lovell Fuller
c40cd1aa50 Switch Appveyor CI to x64 and iojs 2.x
Still install/build for 32-bit node/iojs exe
2015-07-11 19:56:23 +01:00
Lovell Fuller
804162c69a Increase AppVeyor CI timeout 2015-07-11 08:58:15 +01:00
Lovell Fuller
08b2a647d0 Update benchmark results for 0.11.0
Adds CImg-based 'lwip' module

Use AWS EC2 c4 instance type
2015-07-10 23:26:25 +01:00
Lovell Fuller
3a058c0c27 Add 'lwip' module to benchmark tests
Dependencies bump
2015-07-10 22:14:56 +01:00
Lovell Fuller
b8885c1faa Auto-width and height calcs now round instead of floor
I think this will better match people's expectations
2015-07-03 15:21:28 +01:00
Lovell Fuller
321e0f2bfe Add 'icc' raw profile data to metadata #129 2015-06-29 21:27:23 +01:00
Lovell Fuller
cff8b45420 Add OS X build status image
Almost-automated(TM)
2015-06-29 14:24:59 +01:00
Lovell Fuller
6ac47c1ef8 Add raw EXIF data to metadata response
Copy metadata input buffer to match pipeline

Prevents possible metadata segfault under load
2015-06-28 23:35:40 +01:00
Lovell Fuller
86490bedfb Add 'clone' method to snapshot an instance
Cloned instances share a common input
Allows multiple output Streams to use a single input Stream
2015-06-28 14:21:02 +01:00
Lovell Fuller
1091be374e Alpha compositing: support grey+alpha src and non-alpha dst 2015-06-02 14:51:08 +01:00
Lovell Fuller
36be0453dd Refactor internal 'resize' to more apt 'pipeline'
Refactor 'composite' C to C++ 'operations'
2015-06-01 16:33:26 +01:00
Lovell Fuller
e2c53b59ce Tighten constructor and quality param checks #221 2015-06-01 14:48:57 +01:00
Lovell Fuller
f19b6c48ca Skip normalise operation for images with one colour
It didn't play nicely with premultiplication
2015-06-01 14:21:02 +01:00
Lovell Fuller
d2a2654ace Replace use of 'finally' in failing tests 2015-06-01 14:21:02 +01:00
Lovell Fuller
8832ae0bf9 Add private maxColourDistance for functional tests
Switch MSE-based tests to use it

Remove experimental MSE-based compare API
2015-06-01 14:21:02 +01:00
Daniel Gasienica
ef8db1eebf Premultiply alpha channel to avoid dark artifacts during tranformation
Add `Sharp.compare(file1, file2, callback)` function for comparing images
using mean squared error (MSE). This is useful for unit tests.

See:
- https://github.com/jcupitt/libvips/issues/291
- http://entropymine.com/imageworsener/resizealpha/
2015-06-01 14:21:02 +01:00
Lovell Fuller
c792a047b1 Ensure libvips version requirement
Should improve debugging, e.g. #222
2015-06-01 14:21:01 +01:00
Daniel Gasienica
64f7f1d662 Add experimental overlayWith API
Composites an overlay image with alpha channel into the input image (which
must have alpha channel) using ‘over’ alpha compositing blend mode. This API
requires both images to have the same dimensions.

References:
- http://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending
- https://github.com/jcupitt/ruby-vips/issues/28#issuecomment-9014826

See #97.
2015-06-01 14:21:01 +01:00
Lovell Fuller
c886eaa6b0 Version bumps for v0.11.0 WIP 2015-06-01 14:21:01 +01:00
Lovell Fuller
b50fb53f27 Use image fingerprints in functional tests #122 2015-06-01 14:21:01 +01:00
142 changed files with 3682 additions and 1742 deletions

4
.gitignore vendored
View File

@@ -4,6 +4,6 @@ coverage
test/bench/node_modules
test/fixtures/output*
test/leak/libvips.supp
# Mac OS X
lib
include
.DS_Store

View File

@@ -4,6 +4,7 @@
"maxparams": 4,
"maxcomplexity": 13,
"globals": {
"before": true,
"describe": true,
"it": true
}

View File

@@ -7,3 +7,6 @@ coverage
test
.travis.yml
appveyor.yml
mkdocs.yml
lib
include

View File

@@ -2,9 +2,18 @@ language: node_js
node_js:
- "0.10"
- "0.12"
- "iojs-v1"
- "iojs-v2"
- "iojs-v3"
- "4"
sudo: 9000
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-4.8
before_install:
- sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-4.8 90
- 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

18
CONTRIBUTING.md Executable file → Normal file
View File

@@ -30,9 +30,16 @@ Please select the `master` branch as the destination for your Pull Request so yo
Please squash your changes into a single commit using a command like `git rebase -i upstream/master`.
To test C++ changes, you can compile the module using `npm install` and then run the tests using `npm test`.
## Submit a Pull Request with a new feature
Please add JavaScript [unit tests](https://github.com/lovell/sharp/tree/master/test/unit) to cover your new feature. A test coverage report for the JavaScript code is generated in the `coverage/lcov-report` directory.
Please add JavaScript [unit tests](https://github.com/lovell/sharp/tree/master/test/unit) to cover your new feature.
A test coverage report for the JavaScript code is generated in the `coverage/lcov-report` directory.
Where possible, the functional tests use gradient-based perceptual hashes
based on [dHash](http://www.hackerfactor.com/blog/index.php?/archives/529-Kind-of-Like-That.html)
to compare expected vs actual images.
You deserve to add your details to the [list of contributors](https://github.com/lovell/sharp/blob/master/package.json#L5).
@@ -40,9 +47,9 @@ Any change that modifies the existing public API should be added to the relevant
| Release | WIP branch |
| ------: | :--------- |
| v0.9.0 | intake |
| v0.10.0 | judgement |
| v0.11.0 | knife |
| v0.12.0 | look |
| v0.13.0 | mind |
Please squash your changes into a single commit using a command like `git rebase -i upstream/<wip-branch>`.
@@ -72,11 +79,10 @@ npm test
### Memory leak tests
Requires _valgrind_.
Requires [Valgrind](http://valgrind.org/).
```sh
cd sharp/test/leak
./leak.sh
npm run test-leak
```
## Finally

782
README.md Executable file → Normal file
View File

@@ -1,786 +1,30 @@
# sharp
* [Installation](https://github.com/lovell/sharp#installation)
* [Usage examples](https://github.com/lovell/sharp#usage-examples)
* [API](https://github.com/lovell/sharp#api)
* [Contributing](https://github.com/lovell/sharp#contributing)
* [Testing](https://github.com/lovell/sharp#testing)
* [Performance](https://github.com/lovell/sharp#performance)
* [Thanks](https://github.com/lovell/sharp#thanks)
* [Licence](https://github.com/lovell/sharp#licence)
The typical use case for this high speed Node.js module is to convert large images of many formats to smaller, web-friendly JPEG, PNG and WebP images of varying dimensions.
This module supports reading and writing JPEG, PNG and WebP images to and from Streams, Buffer objects and the filesystem.
It also supports reading images of many other formats from the filesystem via libmagick, libgraphicsmagick or [OpenSlide](http://openslide.org/) if present.
Deep Zoom image pyramids can be generated, suitable for use with "slippy map" tile viewers like
[OpenSeadragon](https://github.com/openseadragon/openseadragon) and [Leaflet](https://github.com/turban/Leaflet.Zoomify).
Colour spaces, embedded ICC profiles and alpha transparency channels are all handled correctly.
Only small regions of uncompressed image data are held in memory and processed at a time, taking full advantage of multiple CPU cores and L1/L2/L3 cache. Resizing an image is typically 4x faster than using the quickest ImageMagick and GraphicsMagick settings.
Huffman tables are optimised when generating JPEG output images without having to use separate command line tools like [jpegoptim](https://github.com/tjko/jpegoptim) and [jpegtran](http://jpegclub.org/jpegtran/). PNG filtering can be disabled, which for diagrams and line art often produces the same result as [pngcrush](http://pmt.sourceforge.net/pngcrush/).
Everything remains non-blocking thanks to _libuv_, no child processes are spawned and Promises/A+ are supported.
Anyone who has used the Node.js bindings for [GraphicsMagick](https://github.com/aheckmann/gm) will find the API similarly fluent.
This module is powered by the blazingly fast [libvips](https://github.com/jcupitt/libvips) image processing library, originally created in 1989 at Birkbeck College and currently maintained by [John Cupitt](https://github.com/jcupitt).
## Installation
npm install sharp
### Prerequisites
* Node.js v0.10+ or io.js
* [libvips](https://github.com/jcupitt/libvips) v7.40.0+ (7.42.0+ recommended)
* C++11 compatible compiler such as gcc 4.6+, clang 3.0+ or MSVC 2013
To install the most suitable version of libvips on the following Operating Systems:
* Mac OS
* Homebrew
* MacPorts
* Debian Linux
* Debian 7, 8
* Ubuntu 12.04, 14.04, 14.10, 15.04
* Mint 13, 17
* Red Hat Linux
* RHEL/Centos/Scientific 6, 7
* Fedora 21, 22
* Amazon Linux 2014.09
* OpenSuse Linux
* OpenSuse 13.1, 13.2
run the following as a user with `sudo` access:
curl -s https://raw.githubusercontent.com/lovell/sharp/master/preinstall.sh | sudo bash -
or run the following as `root`:
curl -s https://raw.githubusercontent.com/lovell/sharp/master/preinstall.sh | bash -
The [preinstall.sh](https://github.com/lovell/sharp/blob/master/preinstall.sh) script requires `curl` and `pkg-config`.
Add `--with-openslide` to enable OpenSlide support:
curl -s https://raw.githubusercontent.com/lovell/sharp/master/preinstall.sh | sudo bash -s -- --with-openslide
### Mac OS tips
Manual install via homebrew:
brew install homebrew/science/vips --with-webp --with-graphicsmagick
A missing or incorrectly configured _Xcode Command Line Tools_ installation [can lead](https://github.com/lovell/sharp/issues/80) to a `library not found for -ljpeg` error. If so, please try:
xcode-select --install
The _gettext_ dependency of _libvips_ [can lead](https://github.com/lovell/sharp/issues/9) to a `library not found for -lintl` error. If so, please try:
brew link gettext --force
### Windows
Requires x86 32-bit Node.js or io.js (use `iojs.exe` rather than `node.exe`).
The WebP format is currently unsupported.
1. Ensure the [node-gyp prerequisites](https://github.com/TooTallNate/node-gyp#installation) are met.
2. [Download](http://www.vips.ecs.soton.ac.uk/supported/current/win32/) and unzip `vips-dev.x.y.z.zip`.
3. Set the `VIPS_HOME` environment variable to the full path of the `vips-dev-x.y.z` directory.
4. Add `vips-dev-x.y.z\bin` to `PATH`.
Versions of MSVC more recent than 2013 may require the use of `npm install --arch=ia32 --msvs_version=2013`.
### Heroku
[Alessandro Tagliapietra](https://github.com/alex88) maintains an [Heroku buildpack for libvips](https://github.com/alex88/heroku-buildpack-vips) and its dependencies.
### Docker
[Marc Bachmann](https://github.com/marcbachmann) maintains a [Dockerfile for libvips](https://github.com/marcbachmann/dockerfile-libvips).
docker pull marcbachmann/libvips
### Build tools
* [gulp-responsive](https://www.npmjs.com/package/gulp-responsive)
* [gulp-sharp](https://www.npmjs.com/package/gulp-sharp)
* [grunt-sharp](https://www.npmjs.com/package/grunt-sharp)
## Usage examples
```javascript
var sharp = require('sharp');
```
```javascript
sharp('input.jpg').resize(300, 200).toFile('output.jpg', function(err) {
if (err) {
throw err;
}
// output.jpg is a 300 pixels wide and 200 pixels high image
// containing a scaled and cropped version of input.jpg
});
```
```javascript
var transformer = sharp()
.resize(300, 200)
.crop(sharp.gravity.north)
.on('error', function(err) {
console.log(err);
});
// Read image data from readableStream, resize and write image data to writableStream
readableStream.pipe(transformer).pipe(writableStream);
```
```javascript
var image = sharp(inputJpg);
image.metadata(function(err, metadata) {
image.resize(Math.floor(metadata.width / 2)).webp().toBuffer(function(err, outputBuffer, info) {
// outputBuffer contains a WebP image half the width and height of the original JPEG
});
});
```
```javascript
var pipeline = sharp()
.rotate()
.resize(null, 200)
.progressive()
.toBuffer(function(err, outputBuffer, info) {
if (err) {
throw err;
}
// outputBuffer contains 200px high progressive JPEG image data,
// auto-rotated using EXIF Orientation tag
// info.width and info.height contain the dimensions of the resized image
});
readableStream.pipe(pipeline);
```
```javascript
sharp('input.png')
.rotate(180)
.resize(300)
.flatten()
.background('#ff6600')
.sharpen()
.withMetadata()
.quality(90)
.webp()
.toBuffer()
.then(function(outputBuffer) {
// outputBuffer contains upside down, 300px wide, alpha channel flattened
// onto orange background, sharpened, with metadata, 90% quality WebP image
// data
});
```
```javascript
http.createServer(function(request, response) {
response.writeHead(200, {'Content-Type': 'image/webp'});
sharp('input.jpg').rotate().resize(200).webp().pipe(response);
}).listen(8000);
// Create HTTP server that always returns auto-rotated 'input.jpg',
// resized to 200 pixels wide, in WebP format
```
```javascript
sharp(input)
.extract(top, left, width, height)
.toFile(output);
// Extract a region of the input image, saving in the same format.
```
```javascript
sharp(input)
.extract(topOffsetPre, leftOffsetPre, widthPre, heightPre)
.resize(width, height)
.extract(topOffsetPost, leftOffsetPost, widthPost, heightPost)
.toFile(output);
// Extract a region, resize, then extract from the resized image
```
```javascript
sharp(inputBuffer)
.resize(200, 300)
.interpolateWith(sharp.interpolator.nohalo)
.background('white')
.embed()
.toFile('output.tiff')
.then(function() {
// output.tiff is a 200 pixels wide and 300 pixels high image
// containing a nohalo scaled version, embedded on a white canvas,
// of the image data in inputBuffer
});
```
```javascript
sharp('input.gif')
.resize(200, 300)
.background({r: 0, g: 0, b: 0, a: 0})
.embed()
.toFormat(sharp.format.webp)
.toBuffer(function(err, outputBuffer) {
if (err) {
throw err;
}
// outputBuffer contains WebP image data of a 200 pixels wide and 300 pixels high
// containing a scaled version, embedded on a transparent canvas, of input.gif
});
```
```javascript
sharp(inputBuffer)
.resize(200, 200)
.max()
.toFormat('jpeg')
.toBuffer().then(function(outputBuffer) {
// outputBuffer contains JPEG image data no wider than 200 pixels and no higher
// than 200 pixels regardless of the inputBuffer image dimensions
});
```
```javascript
sharp('input.tiff').tile(256).toFile('output.dzi', function(err, info) {
// The output.dzi file is the XML format Deep Zoom definition
// The output_files directory contains 256x256 pixel tiles grouped by zoom level
});
```
```javascript
// Runtime discovery of available formats
console.dir(sharp.format);
```
## API
### Attributes
#### format
An Object containing nested boolean values
representing the available input and output formats/methods,
for example:
```javascript
{ jpeg: { id: 'jpeg',
input: { file: true, buffer: true, stream: true },
output: { file: true, buffer: true, stream: true } },
png: { id: 'png',
input: { file: true, buffer: true, stream: true },
output: { file: true, buffer: true, stream: true } },
webp: { id: 'webp',
input: { file: true, buffer: true, stream: true },
output: { file: true, buffer: true, stream: true } },
tiff: { id: 'tiff',
input: { file: true, buffer: true, stream: true },
output: { file: true, buffer: false, stream: false } },
magick: { id: 'magick',
input: { file: true, buffer: true, stream: true },
output: { file: false, buffer: false, stream: false } },
raw: { id: 'raw',
input: { file: false, buffer: false, stream: false },
output: { file: false, buffer: true, stream: true } } }
```
#### queue
An EventEmitter that emits a `change` event when a task is either:
* queued, waiting for _libuv_ to provide a worker thread
* complete
```javascript
sharp.queue.on('change', function(queueLength) {
console.log('Queue contains ' + queueLength + ' task(s)');
});
```
### Input methods
#### sharp([input])
Constructor to which further methods are chained. `input`, if present, can be one of:
* Buffer containing JPEG, PNG, WebP, GIF* 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, WebP, GIF* or TIFF format image data can be streamed into the object when `input` is not provided.
JPEG, PNG or WebP format image data can be streamed out from this object.
\* libvips 8.0.0+ is required for Buffer/Stream input of GIF and other `magick` formats.
#### metadata([callback])
Fast access to image metadata without decoding any compressed image data.
`callback`, if present, gets the arguments `(err, metadata)` where `metadata` has the attributes:
* `format`: Name of decoder to be used to decompress image data e.g. `jpeg`, `png`, `webp` (for file-based input additionally `tiff`, `magick` and `openslide`)
* `width`: Number of pixels wide
* `height`: Number of pixels high
* `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `scrgb`, `cmyk`, `lab`, `xyz`, `b-w` [...](https://github.com/jcupitt/libvips/blob/master/libvips/iofuncs/enumtypes.c#L522)
* `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK
* `hasProfile`: Boolean indicating the presence of an embedded ICC profile
* `hasAlpha`: Boolean indicating the presence of an alpha transparency channel
* `orientation`: Number value of the EXIF Orientation header, if present
A Promises/A+ promise is returned when `callback` is not provided.
#### sequentialRead()
An advanced setting that switches the libvips access method to `VIPS_ACCESS_SEQUENTIAL`. This will reduce memory usage and can improve performance on some systems.
#### limitInputPixels(pixels)
Do not process input images where the number of pixels (width * height) exceeds this limit.
`pixels` is the integral Number of pixels, with a value between 1 and the default 268402689 (0x3FFF * 0x3FFF).
### Image transformation options
#### resize(width, [height])
Scale output to `width` x `height`. By default, the resized image is cropped to the exact size specified.
`width` is the integral Number of pixels wide the resultant image should be, between 1 and 16383 (0x3FFF). Use `null` or `undefined` to auto-scale the width to match the height.
`height` is the integral Number of pixels high the resultant image should be, between 1 and 16383. Use `null` or `undefined` to auto-scale the height to match the width.
#### extract(top, left, width, height)
Extract a region of the image. Can be used with or without a `resize` operation.
`top` and `left` are the offset, in pixels, from the top-left corner.
`width` and `height` are the dimensions of the extracted image.
Use `extract` before `resize` for pre-resize extraction. Use `extract` after `resize` for post-resize extraction. Use `extract` before and after for both.
#### crop([gravity])
Crop the resized image to the exact size specified, the default behaviour.
`gravity`, if present, is an attribute of the `sharp.gravity` Object e.g. `sharp.gravity.north`.
Possible values are `north`, `east`, `south`, `west`, `center` and `centre`. The default gravity is `center`/`centre`.
#### max()
Preserving aspect ratio,
resize the image to be as large as possible
while ensuring its dimensions are less than or equal to
the `width` and `height` specified.
Both `width` and `height` must be provided via `resize` otherwise the behaviour will default to `crop`.
#### min()
Preserving aspect ratio,
resize the image to be as small as possible
while ensuring its dimensions are greater than or equal to
the `width` and `height` specified.
Both `width` and `height` must be provided via `resize` otherwise the behaviour will default to `crop`.
#### ignoreAspectRatio()
Ignoring the aspect ratio of the input, stretch the image to the exact `width` and/or `height` provided via `resize`.
#### background(rgba)
Set the background for the `embed` and `flatten` operations.
`rgba` is parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
The alpha value is a float between `0` (transparent) and `1` (opaque).
The default background is `{r: 0, g: 0, b: 0, a: 1}`, black without transparency.
#### embed()
Preserving aspect ratio, resize the image to the maximum `width` or `height` specified then embed on a background of the exact `width` and `height` specified.
If the background contains an alpha value then WebP and PNG format output images will contain an alpha channel, even when the input image does not.
#### flatten()
Merge alpha transparency channel, if any, with `background`.
#### rotate([angle])
Rotate the output image by either an explicit angle or auto-orient based on the EXIF `Orientation` tag.
`angle`, if present, is a Number with a value of `0`, `90`, `180` or `270`.
Use this method without `angle` to determine the angle from EXIF data. Mirroring is supported and may infer the use of a `flip` operation.
Method order is important when both rotating and extracting regions, for example `rotate(x).extract(y)` will produce a different result to `extract(y).rotate(x)`.
#### flip()
Flip the image about the vertical Y axis. This always occurs after rotation, if any.
#### flop()
Flop the image about the horizontal X axis. This always occurs after rotation, if any.
#### withoutEnlargement()
Do not enlarge the output image if the input image width *or* height are already less than the required dimensions.
This is equivalent to GraphicsMagick's `>` geometry option: "change the dimensions of the image only if its width or height exceeds the geometry specification".
#### blur([sigma])
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)
Use the given interpolator for image resizing, where `interpolator` is an attribute of the `sharp.interpolator` Object e.g. `sharp.interpolator.bicubic`.
Possible interpolators, in order of performance, are:
* `nearest`: Use [nearest neighbour interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation), suitable for image enlargement only.
* `bilinear`: Use [bilinear interpolation](http://en.wikipedia.org/wiki/Bilinear_interpolation), the default and fastest image reduction interpolation.
* `bicubic`: Use [bicubic interpolation](http://en.wikipedia.org/wiki/Bicubic_interpolation), which typically reduces performance by 5%.
* `vertexSplitQuadraticBasisSpline`: Use [VSQBS interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/vsqbs.cpp#L48), which prevents "staircasing" and typically reduces performance by 5%.
* `locallyBoundedBicubic`: Use [LBB interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/lbb.cpp#L100), which prevents some "[acutance](http://en.wikipedia.org/wiki/Acutance)" and typically reduces performance by a factor of 2.
* `nohalo`: Use [Nohalo interpolation](http://eprints.soton.ac.uk/268086/), which prevents acutance and typically reduces performance by a factor of 3.
#### gamma([gamma])
Apply a gamma correction by reducing the encoding (darken) pre-resize at a factor of `1/gamma` then increasing the encoding (brighten) post-resize at a factor of `gamma`.
`gamma`, if present, is a Number betweem 1 and 3. The default value is `2.2`, a suitable approximation for sRGB images.
This can improve the perceived brightness of a resized image in non-linear colour spaces.
JPEG input images will not take advantage of the shrink-on-load performance optimisation when applying a gamma correction.
#### grayscale() / greyscale()
Convert to 8-bit greyscale; 256 shades of grey.
This is a linear operation. If the input image is in a non-linear colour space such as sRGB, use `gamma()` with `greyscale()` for the best results.
The output image will still be web-friendly sRGB and contain three (identical) channels.
#### normalize() / normalise()
Enhance output image contrast by stretching its luminance to cover the full dynamic range. This typically reduces performance by 30%.
### Output options
#### jpeg()
Use JPEG format for the output image.
#### png()
Use PNG format for the output image.
#### webp()
Use WebP format for the output image.
#### raw()
_Requires libvips 7.42.0+_
Provide raw, uncompressed uint8 (unsigned char) image data for Buffer and Stream based output.
The number of channels depends on the input image and selected options.
* 1 channel for images converted to `greyscale()`, with each byte representing one pixel.
* 3 channels for colour images without alpha transparency, with bytes ordered \[red, green, blue, red, green, blue, etc.\]).
* 4 channels for colour images with alpha transparency, with bytes ordered \[red, green, blue, alpha, red, green, blue, alpha, etc.\].
#### toFormat(format)
Convenience method for the above output format methods, where `format` is either:
* an attribute of the `sharp.format` Object e.g. `sharp.format.jpeg`, or
* a String containing `jpeg`, `png`, `webp` or `raw`.
#### quality(quality)
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.
#### progressive()
Use progressive (interlace) scan for JPEG and PNG output. This typically reduces compression performance by 30% but results in an image that can be rendered sooner when decompressed.
#### withMetadata()
Include all metadata (EXIF, XMP, IPTC) from the input image in the output image. This will also convert to and add the latest web-friendly v2 sRGB ICC profile.
The default behaviour is to strip all metadata and convert to the device-independent sRGB colour space.
#### tile([size], [overlap])
The size and overlap, in pixels, of square Deep Zoom image pyramid tiles.
* `size` is an integral Number between 1 and 8192. The default value is 256 pixels.
* `overlap` is an integral Number between 0 and 8192. The default value is 0 pixels.
#### withoutChromaSubsampling()
Disable the use of [chroma subsampling](http://en.wikipedia.org/wiki/Chroma_subsampling) with JPEG output (4:4:4).
This can improve colour representation at higher quality settings (90+),
but usually increases output file size and typically reduces performance by 25%.
The default behaviour is to use chroma subsampling (4:2:0).
#### trellisQuantisation() / trellisQuantization()
_Requires libvips 8.0.0+ compiled against mozjpeg 3.0+_
An advanced setting to apply the use of
[trellis quantisation](http://en.wikipedia.org/wiki/Trellis_quantization) with JPEG output.
Reduces file size and slightly increases relative quality at the cost of increased compression time.
#### overshootDeringing()
_Requires libvips 8.0.0+ compiled against mozjpeg 3.0+_
An advanced setting to reduce the effects of
[ringing](http://en.wikipedia.org/wiki/Ringing_%28signal%29) in JPEG output,
in particular where black text appears on a white background (or vice versa).
#### optimiseScans() / optimizeScans()
_Requires libvips 8.0.0+ compiled against mozjpeg 3.0+_
An advanced setting for progressive (interlace) JPEG output.
Calculates which spectrum of DCT coefficients uses the fewest bits.
Usually reduces file size at the cost of increased compression time.
#### compressionLevel(compressionLevel)
An advanced setting for the _zlib_ compression level of the lossless PNG output format. The default level is `6`.
`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])
`filename` is a String containing the filename to write the image data to. The format is inferred from the extension, with JPEG, PNG, WebP, TIFF and DZI supported.
`callback`, if present, is called with two arguments `(err, info)` where:
* `err` contains an error message, if any.
* `info` contains the output image `format`, `size` (bytes), `width` and `height`.
A Promises/A+ promise is returned when `callback` is not provided.
#### toBuffer([callback])
Write image data to a Buffer, the format of which will match the input image by default. JPEG, PNG and WebP are supported.
`callback`, if present, gets three arguments `(err, buffer, info)` where:
* `err` is an error message, if any.
* `buffer` is the output image data.
* `info` contains the output image `format`, `size` (bytes), `width` and `height`.
A Promises/A+ promise is returned when `callback` is not provided.
### Utility methods
#### sharp.cache([memory], [items])
If `memory` or `items` are provided, set the limits of _libvips'_ operation cache.
* `memory` is the maximum memory in MB to use for this cache, with a default value of 100
* `items` is the maximum number of operations to cache, with a default value of 500
This method always returns cache statistics, useful for determining how much working memory is required for a particular task.
```javascript
var stats = sharp.cache(); // { current: 75, high: 99, memory: 100, items: 500 }
sharp.cache(200); // { current: 75, high: 99, memory: 200, items: 500 }
sharp.cache(50, 200); // { current: 49, high: 99, memory: 50, items: 200}
```
#### sharp.concurrency([threads])
`threads`, if provided, is the Number of threads _libvips'_ should create for processing each image. The default value is the number of CPU cores. A value of `0` will reset to this default.
This method always returns the current concurrency.
```javascript
var threads = sharp.concurrency(); // 4
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.
* `queue` is the number of tasks this module has queued waiting for _libuv_ to provide a worker thread from its pool.
* `process` is the number of resize tasks currently being processed.
```javascript
var counters = sharp.counters(); // { queue: 2, process: 4 }
```
## Contributing
A [guide for contributors](https://github.com/lovell/sharp/blob/master/CONTRIBUTING.md) covers reporting bugs, requesting features and submitting code changes.
## Testing
### Functional tests
#### Coverage
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.
[![Test Coverage](https://coveralls.io/repos/lovell/sharp/badge.png?branch=master)](https://coveralls.io/r/lovell/sharp?branch=master)
#### Ubuntu 12.04
### Documentation
[![Ubuntu 12.04 Build Status](https://travis-ci.org/lovell/sharp.png?branch=master)](https://travis-ci.org/lovell/sharp)
Visit [sharp.dimens.io](http://sharp.dimens.io/) for
complete installation instructions, API documentation,
benchmark tests and a changelog.
#### Centos 6.5
### Contributing
[![Centos 6.5 Build Status](https://snap-ci.com/lovell/sharp/branch/master/build_image)](https://snap-ci.com/lovell/sharp/branch/master)
A [guide for contributors](https://github.com/lovell/sharp/blob/master/CONTRIBUTING.md)
covers reporting bugs, requesting features and submitting code changes.
#### Windows Server 2012
[![Windows Server 2012 Build Status](https://ci.appveyor.com/api/projects/status/pgtul704nkhhg6sg)](https://ci.appveyor.com/project/lovell/sharp)
### Benchmark tests
```
cd sharp/test/bench
npm install
npm test
```
Requires both _ImageMagick_ and _GraphicsMagick_:
```
brew install imagemagick
brew install graphicsmagick
```
```
sudo apt-get install -qq imagemagick graphicsmagick libmagickcore-dev
```
```
sudo yum install ImageMagick
sudo yum install -y http://download.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm
sudo yum install -y --enablerepo=epel GraphicsMagick
```
## Performance
### Test environment
* AWS EC2 [c3.xlarge](http://aws.amazon.com/ec2/instance-types/#Compute_Optimized)
* Ubuntu 14.04
* libvips 7.40.8
* liborc 0.4.22
### The contenders
* [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.
### The task
Decompress a 2725x2225 JPEG image, resize and crop to 720x480, then compress to JPEG.
### Results
| Module | Input | Output | Ops/sec | Speed-up |
| :-------------------- | :----- | :----- | ------: | -------: |
| imagemagick-native | buffer | buffer | 1.58 | 1 |
| imagemagick | file | file | 6.23 | 3.9 |
| gm | buffer | file | 5.32 | 3.4 |
| gm | buffer | buffer | 5.32 | 3.4 |
| gm | file | file | 5.36 | 3.4 |
| gm | file | buffer | 5.36 | 3.4 |
| sharp | buffer | file | 22.05 | 14.0 |
| sharp | buffer | buffer | 22.14 | 14.0 |
| sharp | file | file | 21.79 | 13.8 |
| sharp | file | buffer | 21.90 | 13.9 |
| sharp | stream | stream | 20.87 | 13.2 |
| sharp +promise | file | buffer | 21.89 | 13.9 |
| sharp +sharpen | file | buffer | 19.69 | 12.5 |
| sharp +progressive | file | buffer | 16.93 | 10.7 |
| sharp +sequentialRead | file | buffer | 21.60 | 13.7 |
You can expect greater performance with caching enabled (default) and using 8+ core machines.
## Thanks
This module would never have been possible without the help and code contributions of the following people:
* [John Cupitt](https://github.com/jcupitt)
* [Pierre Inglebert](https://github.com/pierreinglebert)
* [Jonathan Ong](https://github.com/jonathanong)
* [Chanon Sajjamanochai](https://github.com/chanon)
* [Juliano Julio](https://github.com/julianojulio)
* [Daniel Gasienica](https://github.com/gasi)
* [Julian Walker](https://github.com/julianwa)
* [Amit Pitaru](https://github.com/apitaru)
* [Brandon Aaron](https://github.com/brandonaaron)
* [Andreas Lind](https://github.com/papandreou)
* [Maurus Cuelenaere](https://github.com/mcuelenaere)
* [Linus Unneb<65>ck](https://github.com/LinusU)
* [Victor Mateevitsi](https://github.com/mvictoras)
* [Alaric Holloway](https://github.com/skedastik)
* [Bernhard K. Weisshuhn](https://github.com/bkw)
Thank you!
## Licence
### Licence
Copyright 2013, 2014, 2015 Lovell Fuller and contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0.html)
You may obtain a copy of the License at
[http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0.html)
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,

View File

@@ -1,17 +1,20 @@
os: Visual Studio 2014 CTP4
platform: Win32
configuration: Release
os: Visual Studio 2015
version: "{build}"
build: off
platform: x64
environment:
VIPS_VERSION_MAJOR_MINOR: 8.0
VIPS_VERSION_PATCH: 2
VIPS_VERSION_MAJOR_MINOR: 8.1
VIPS_VERSION_PATCH: 1
VIPS_WARNING: 0
matrix:
- nodejs_version: "0.10"
nodejs_exec: "node"
- nodejs_version: "0.12"
nodejs_exec: "node"
- nodejs_version: "1.0"
- nodejs_version: "2"
nodejs_exec: "iojs"
- nodejs_version: "3"
nodejs_exec: "iojs"
- nodejs_version: "4"
nodejs_exec: "node"
install:
- ps: $env:VIPS_VERSION = "$env:VIPS_VERSION_MAJOR_MINOR.$env:VIPS_VERSION_PATCH"
- ps: Write-Output "Fetching http://www.vips.ecs.soton.ac.uk/supported/$env:VIPS_VERSION_MAJOR_MINOR/win32/vips-dev-$env:VIPS_VERSION.zip"
@@ -20,6 +23,6 @@ install:
- ps: $env:VIPS_HOME = "c:\vips-dev-$env:VIPS_VERSION"
- ps: $env:PATH = "$env:VIPS_HOME\bin;$env:PATH"
- ps: Install-Product node $env:nodejs_version x86
- npm install --arch=ia32 --msvs_version=2013
- npm install --arch=ia32
test_script:
- npm run-script test-win32-%nodejs_exec%

View File

@@ -1,12 +1,16 @@
{
'targets': [{
'target_name': 'sharp',
'variables': {
'runtime_link%':'shared',
},
'sources': [
'src/common.cc',
'src/utilities.cc',
'src/metadata.cc',
'src/resize.cc',
'src/sharp.cc'
'src/operations.cc',
'src/pipeline.cc',
'src/sharp.cc',
'src/utilities.cc'
],
'conditions': [
['OS=="win"', {
@@ -50,6 +54,11 @@
'include_dirs': [
'<!(PKG_CONFIG_PATH="<(PKG_CONFIG_PATH)" pkg-config --cflags vips glib-2.0)',
'<!(node -e "require(\'nan\')")'
],
'conditions': [
['runtime_link == "static"', {
'libraries': ['<!(PKG_CONFIG_PATH="<(PKG_CONFIG_PATH)" pkg-config --libs vips --static)']
}]
]
}]
],
@@ -73,6 +82,15 @@
'VCCLCompilerTool': {
'ExceptionHandling': 1 # /EHsc
}
}
},
'configurations': {
'Release': {
'msvs_settings': {
'VCCLCompilerTool': {
'ExceptionHandling': 1,
}
}
}
},
}]
}

598
docs/api.md Normal file
View File

@@ -0,0 +1,598 @@
# API
```javascript
var sharp = require('sharp');
```
### Input
#### sharp([input])
Constructor to which further methods are chained. `input`, if present, can be one of:
* Buffer containing JPEG, PNG, WebP, GIF* or TIFF image data, or
* String containing the path to an image file, 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, WebP, GIF* or TIFF format image data
can be streamed into the object when `input` is not provided.
JPEG, PNG or WebP format image data can be streamed out from this object.
\* libvips 8.0.0+ is required for Buffer/Stream input of GIF and other `magick` formats.
```javascript
sharp('input.jpg')
.resize(300, 200)
.toFile('output.jpg', function(err) {
// output.jpg is a 300 pixels wide and 200 pixels high image
// containing a scaled and cropped version of input.jpg
});
```
#### metadata([callback])
Fast access to image metadata without decoding any compressed image data.
`callback`, if present, gets the arguments `(err, metadata)` where `metadata` has the attributes:
* `format`: Name of decoder to be used to decompress image data e.g. `jpeg`, `png`, `webp` (for file-based input additionally `tiff`, `magick` and `openslide`)
* `width`: Number of pixels wide
* `height`: Number of pixels high
* `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `scrgb`, `cmyk`, `lab`, `xyz`, `b-w` [...](https://github.com/jcupitt/libvips/blob/master/libvips/iofuncs/enumtypes.c#L522)
* `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK
* `hasProfile`: Boolean indicating the presence of an embedded ICC profile
* `hasAlpha`: Boolean indicating the presence of an alpha transparency channel
* `orientation`: Number value of the EXIF Orientation header, if present
* `exif`: Buffer containing raw EXIF data, if present
* `icc`: Buffer containing raw [ICC](https://www.npmjs.com/package/icc) profile data, if present
A Promises/A+ promise is returned when `callback` is not provided.
```javascript
var image = sharp(inputJpg);
image
.metadata()
.then(function(metadata) {
return image
.resize(Math.round(metadata.width / 2))
.webp()
.toBuffer();
})
.then(function(data) {
// data contains a WebP image half the width and height of the original JPEG
});
```
#### clone()
Takes a "snapshot" of the instance, returning a new instance.
Cloned instances inherit the input of their parent instance.
This allows multiple output Streams
and therefore multiple processing pipelines
to share a single input Stream.
```javascript
var pipeline = sharp().rotate();
pipeline.clone().resize(800, 600).pipe(firstWritableStream);
pipeline.clone().extract(20, 20, 100, 100).pipe(secondWritableStream);
readableStream.pipe(pipeline);
// firstWritableStream receives auto-rotated, resized readableStream
// secondWritableStream receives auto-rotated, extracted region of readableStream
```
#### sequentialRead()
An advanced setting that switches the libvips access method to `VIPS_ACCESS_SEQUENTIAL`.
This will reduce memory usage and can improve performance on some systems.
#### limitInputPixels(pixels)
Do not process input images where the number of pixels (width * height) exceeds this limit.
`pixels` is the integral Number of pixels, with a value between 1 and the default 268402689 (0x3FFF * 0x3FFF).
### Resizing
#### resize([width], [height])
Scale output to `width` x `height`. By default, the resized image is cropped to the exact size specified.
`width` is the integral Number of pixels wide the resultant image should be, between 1 and 16383 (0x3FFF). Use `null` or `undefined` to auto-scale the width to match the height.
`height` is the integral Number of pixels high the resultant image should be, between 1 and 16383. Use `null` or `undefined` to auto-scale the height to match the width.
#### crop([gravity])
Crop the resized image to the exact size specified, the default behaviour.
`gravity`, if present, is a String or an attribute of the `sharp.gravity` Object e.g. `sharp.gravity.north`.
Possible values are `north`, `northeast`, `east`, `southeast`, `south`, `southwest`, `west`, `northwest`, `center` and `centre`.
The default gravity is `center`/`centre`.
```javascript
var transformer = sharp()
.resize(300, 200)
.crop(sharp.gravity.north)
.on('error', function(err) {
console.log(err);
});
// Read image data from readableStream, resize and write image data to writableStream
readableStream.pipe(transformer).pipe(writableStream);
```
#### embed()
Preserving aspect ratio, resize the image to the
maximum `width` or `height` specified
then embed on a background of the exact
`width` and `height` specified.
If the background contains an alpha value
then WebP and PNG format output images will
contain an alpha channel,
even when the input image does not.
```javascript
sharp('input.gif')
.resize(200, 300)
.background({r: 0, g: 0, b: 0, a: 0})
.embed()
.toFormat(sharp.format.webp)
.toBuffer(function(err, outputBuffer) {
if (err) {
throw err;
}
// outputBuffer contains WebP image data of a 200 pixels wide and 300 pixels high
// containing a scaled version, embedded on a transparent canvas, of input.gif
});
```
#### max()
Preserving aspect ratio,
resize the image to be as large as possible
while ensuring its dimensions are less than or equal to
the `width` and `height` specified.
Both `width` and `height` must be provided via
`resize` otherwise the behaviour will default to `crop`.
```javascript
sharp(inputBuffer)
.resize(200, 200)
.max()
.toFormat('jpeg')
.toBuffer()
.then(function(outputBuffer) {
// outputBuffer contains JPEG image data no wider than 200 pixels and no higher
// than 200 pixels regardless of the inputBuffer image dimensions
});
```
#### min()
Preserving aspect ratio,
resize the image to be as small as possible
while ensuring its dimensions are greater than or equal to
the `width` and `height` specified.
Both `width` and `height` must be provided via `resize` otherwise the behaviour will default to `crop`.
#### withoutEnlargement()
Do not enlarge the output image
if the input image width *or* height
are already less than the required dimensions.
This is equivalent to GraphicsMagick's `>` geometry option:
"*change the dimensions of the image only
if its width or height exceeds the geometry specification*".
#### ignoreAspectRatio()
Ignoring the aspect ratio of the input, stretch the image to the exact `width` and/or `height` provided via `resize`.
#### interpolateWith(interpolator)
Use the given interpolator for image resizing, where `interpolator` is an attribute of the `sharp.interpolator` Object e.g. `sharp.interpolator.bicubic`.
Possible interpolators, in order of performance, are:
* `nearest`: Use [nearest neighbour interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation), suitable for image enlargement only.
* `bilinear`: Use [bilinear interpolation](http://en.wikipedia.org/wiki/Bilinear_interpolation), the default and fastest image reduction interpolation.
* `bicubic`: Use [bicubic interpolation](http://en.wikipedia.org/wiki/Bicubic_interpolation), which typically reduces performance by 5%.
* `vertexSplitQuadraticBasisSpline`: Use [VSQBS interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/vsqbs.cpp#L48), which prevents "staircasing" and typically reduces performance by 5%.
* `locallyBoundedBicubic`: Use [LBB interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/lbb.cpp#L100), which prevents some "[acutance](http://en.wikipedia.org/wiki/Acutance)" and typically reduces performance by a factor of 2.
* `nohalo`: Use [Nohalo interpolation](http://eprints.soton.ac.uk/268086/), which prevents acutance and typically reduces performance by a factor of 3.
```javascript
sharp(inputBuffer)
.resize(200, 300)
.interpolateWith(sharp.interpolator.nohalo)
.background('white')
.embed()
.toFile('output.tiff')
.then(function() {
// output.tiff is a 200 pixels wide and 300 pixels high image
// containing a nohalo scaled version, embedded on a white canvas,
// of the image data in inputBuffer
});
```
### Operations
#### extract(top, left, width, height)
Extract a region of the image. Can be used with or without a `resize` operation.
`top` and `left` are the offset, in pixels, from the top-left corner.
`width` and `height` are the dimensions of the extracted image.
Use `extract` before `resize` for pre-resize extraction. Use `extract` after `resize` for post-resize extraction. Use `extract` before and after for both.
```javascript
sharp(input)
.extract(top, left, width, height)
.toFile(output, function(err) {
// Extract a region of the input image, saving in the same format.
});
```
```javascript
sharp(input)
.extract(topOffsetPre, leftOffsetPre, widthPre, heightPre)
.resize(width, height)
.extract(topOffsetPost, leftOffsetPost, widthPost, heightPost)
.toFile(output, function(err) {
// Extract a region, resize, then extract from the resized image
});
```
#### background(rgba)
Set the background for the `embed` and `flatten` operations.
`rgba` is parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
The alpha value is a float between `0` (transparent) and `1` (opaque).
The default background is `{r: 0, g: 0, b: 0, a: 1}`, black without transparency.
#### flatten()
Merge alpha transparency channel, if any, with `background`.
#### rotate([angle])
Rotate the output image by either an explicit angle or auto-orient based on the EXIF `Orientation` tag.
`angle`, if present, is a Number with a value of `0`, `90`, `180` or `270`.
Use this method without `angle` to determine the angle from EXIF data. Mirroring is supported and may infer the use of a `flip` operation.
Method order is important when both rotating and extracting regions, for example `rotate(x).extract(y)` will produce a different result to `extract(y).rotate(x)`.
The use of `rotate` implies the removal of the EXIF `Orientation` tag, if any.
```javascript
var pipeline = sharp()
.rotate()
.resize(null, 200)
.progressive()
.toBuffer(function(err, outputBuffer, info) {
if (err) {
throw err;
}
// outputBuffer contains 200px high progressive JPEG image data,
// auto-rotated using EXIF Orientation tag
// info.width and info.height contain the dimensions of the resized image
});
readableStream.pipe(pipeline);
```
#### flip()
Flip the image about the vertical Y axis. This always occurs after rotation, if any.
The use of `flip` implies the removal of the EXIF `Orientation` tag, if any.
#### flop()
Flop the image about the horizontal X axis. This always occurs after rotation, if any.
The use of `flop` implies the removal of the EXIF `Orientation` tag, if any.
#### blur([sigma])
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.
#### gamma([gamma])
Apply a gamma correction by reducing the encoding (darken) pre-resize at a factor of `1/gamma` then increasing the encoding (brighten) post-resize at a factor of `gamma`.
`gamma`, if present, is a Number between 1 and 3. The default value is `2.2`, a suitable approximation for sRGB images.
This can improve the perceived brightness of a resized image in non-linear colour spaces.
JPEG input images will not take advantage of the shrink-on-load performance optimisation when applying a gamma correction.
#### grayscale() / greyscale()
Convert to 8-bit greyscale; 256 shades of grey.
This is a linear operation. If the input image is in a non-linear colour space such as sRGB, use `gamma()` with `greyscale()` for the best results.
The output image will still be web-friendly sRGB and contain three (identical) channels.
#### normalize() / normalise()
Enhance output image contrast by stretching its luminance to cover the full dynamic range. This typically reduces performance by 30%.
#### overlayWith(path)
_Experimental_
Alpha composite image at `path` over the processed (resized, extracted) image. The dimensions of the two images must match.
* `path` is a String containing the path to an image file with an alpha channel.
```javascript
sharp('input.png')
.rotate(180)
.resize(300)
.flatten()
.background('#ff6600')
.overlayWith('overlay.png')
.sharpen()
.withMetadata()
.quality(90)
.webp()
.toBuffer()
.then(function(outputBuffer) {
// outputBuffer contains upside down, 300px wide, alpha channel flattened
// onto orange background, composited with overlay.png, sharpened,
// with metadata, 90% quality WebP image data. Phew!
});
```
### Output
#### toFile(path, [callback])
`path` is a String containing the path to write the image data to. The format is inferred from the extension, with JPEG, PNG, WebP, TIFF and DZI supported.
`callback`, if present, is called with two arguments `(err, info)` where:
* `err` contains an error message, if any.
* `info` contains the output image `format`, `size` (bytes), `width` and `height`.
A Promises/A+ promise is returned when `callback` is not provided.
#### toBuffer([callback])
Write image data to a Buffer, the format of which will match the input image by default. JPEG, PNG and WebP are supported.
`callback`, if present, gets three arguments `(err, buffer, info)` where:
* `err` is an error message, if any.
* `buffer` is the output image data.
* `info` contains the output image `format`, `size` (bytes), `width` and `height`.
A Promises/A+ promise is returned when `callback` is not provided.
#### jpeg()
Use JPEG format for the output image.
#### png()
Use PNG format for the output image.
#### webp()
Use WebP format for the output image.
#### raw()
_Requires libvips 7.42.0+_
Provide raw, uncompressed uint8 (unsigned char) image data for Buffer and Stream based output.
The number of channels depends on the input image and selected options.
* 1 channel for images converted to `greyscale()`, with each byte representing one pixel.
* 3 channels for colour images without alpha transparency, with bytes ordered \[red, green, blue, red, green, blue, etc.\]).
* 4 channels for colour images with alpha transparency, with bytes ordered \[red, green, blue, alpha, red, green, blue, alpha, etc.\].
#### toFormat(format)
Convenience method for the above output format methods, where `format` is either:
* an attribute of the `sharp.format` Object e.g. `sharp.format.jpeg`, or
* a String containing `jpeg`, `png`, `webp` or `raw`.
#### quality(quality)
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.
#### progressive()
Use progressive (interlace) scan for JPEG and PNG output. This typically reduces compression performance by 30% but results in an image that can be rendered sooner when decompressed.
#### withMetadata([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 optional `metadata` parameter, if present, is an Object with the attributes to update.
New attributes cannot be inserted, only existing attributes updated.
* `orientation` is an integral Number between 0 and 7, used to update the value of the EXIF `Orientation` tag.
This has no effect if the input image does not have an EXIF `Orientation` tag.
The default behaviour, when `withMetadata` is not used, is to strip all metadata and convert to the device-independent sRGB colour space.
#### tile([size], [overlap])
The size and overlap, in pixels, of square Deep Zoom image pyramid tiles.
* `size` is an integral Number between 1 and 8192. The default value is 256 pixels.
* `overlap` is an integral Number between 0 and 8192. The default value is 0 pixels.
```javascript
sharp('input.tiff').tile(256).toFile('output.dzi', function(err, info) {
// The output.dzi file is the XML format Deep Zoom definition
// The output_files directory contains 256x256 pixel tiles grouped by zoom level
});
```
#### withoutChromaSubsampling()
Disable the use of [chroma subsampling](http://en.wikipedia.org/wiki/Chroma_subsampling) with JPEG output (4:4:4).
This can improve colour representation at higher quality settings (90+),
but usually increases output file size and typically reduces performance by 25%.
The default behaviour is to use chroma subsampling (4:2:0).
#### 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 0 and 9.
#### withoutAdaptiveFiltering()
_Requires libvips 7.42.0+_
An advanced setting to disable adaptive row filtering for the lossless PNG output format.
#### trellisQuantisation() / trellisQuantization()
_Requires libvips 8.0.0+ compiled against mozjpeg 3.0+_
An advanced setting to apply the use of
[trellis quantisation](http://en.wikipedia.org/wiki/Trellis_quantization) with JPEG output.
Reduces file size and slightly increases relative quality at the cost of increased compression time.
#### overshootDeringing()
_Requires libvips 8.0.0+ compiled against mozjpeg 3.0+_
An advanced setting to reduce the effects of
[ringing](http://en.wikipedia.org/wiki/Ringing_%28signal%29) in JPEG output,
in particular where black text appears on a white background (or vice versa).
#### optimiseScans() / optimizeScans()
_Requires libvips 8.0.0+ compiled against mozjpeg 3.0+_
An advanced setting for progressive (interlace) JPEG output.
Calculates which spectrum of DCT coefficients uses the fewest bits.
Usually reduces file size at the cost of increased compression time.
### Attributes
#### format
An Object containing nested boolean values
representing the available input and output formats/methods,
for example:
```javascript
> console.dir(sharp.format);
{ jpeg: { id: 'jpeg',
input: { file: true, buffer: true, stream: true },
output: { file: true, buffer: true, stream: true } },
png: { id: 'png',
input: { file: true, buffer: true, stream: true },
output: { file: true, buffer: true, stream: true } },
webp: { id: 'webp',
input: { file: true, buffer: true, stream: true },
output: { file: true, buffer: true, stream: true } },
tiff: { id: 'tiff',
input: { file: true, buffer: true, stream: true },
output: { file: true, buffer: false, stream: false } },
magick: { id: 'magick',
input: { file: true, buffer: true, stream: true },
output: { file: false, buffer: false, stream: false } },
raw: { id: 'raw',
input: { file: false, buffer: false, stream: false },
output: { file: false, buffer: true, stream: true } } }
```
#### queue
An EventEmitter that emits a `change` event when a task is either:
* queued, waiting for _libuv_ to provide a worker thread
* complete
```javascript
sharp.queue.on('change', function(queueLength) {
console.log('Queue contains ' + queueLength + ' task(s)');
});
```
### Utilities
#### sharp.cache([memory], [items])
If `memory` or `items` are provided, set the limits of _libvips'_ operation cache.
* `memory` is the maximum memory in MB to use for this cache, with a default value of 100
* `items` is the maximum number of operations to cache, with a default value of 500
This method always returns cache statistics, useful for determining how much working memory is required for a particular task.
```javascript
var stats = sharp.cache(); // { current: 75, high: 99, memory: 100, items: 500 }
sharp.cache(200); // { current: 75, high: 99, memory: 200, items: 500 }
sharp.cache(50, 200); // { current: 49, high: 99, memory: 50, items: 200}
```
#### sharp.concurrency([threads])
`threads`, if provided, is the Number of threads _libvips'_ should create for processing each image. The default value is the number of CPU cores. A value of `0` will reset to this default.
This method always returns the current concurrency.
```javascript
var threads = sharp.concurrency(); // 4
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.
* `queue` is the number of tasks this module has queued waiting for _libuv_ to provide a worker thread from its pool.
* `process` is the number of resize tasks currently being processed.
```javascript
var counters = sharp.counters(); // { queue: 2, process: 4 }
```

101
docs/changelog.md Normal file
View File

@@ -0,0 +1,101 @@
# Changelog
### v0.11 - "*knife*"
#### v0.11.4 - 5<sup>th</sup> November 2015
* Add corners, e.g. `northeast`, to existing `gravity` option.
[#291](https://github.com/lovell/sharp/pull/291)
[@brandonaaron](https://github.com/brandonaaron)
* Ensure correct auto-rotation for EXIF Orientation values 2 and 4.
[#288](https://github.com/lovell/sharp/pull/288)
[@brandonaaron](https://github.com/brandonaaron)
* Make static linking possible via `--runtime_link` install option.
[#287](https://github.com/lovell/sharp/pull/287)
[@vlapo](https://github.com/vlapo)
#### v0.11.3 - 8<sup>th</sup> September 2015
* Intrepret blurSigma, sharpenFlat, and sharpenJagged as double precision.
[#263](https://github.com/lovell/sharp/pull/263)
[@chrisriley](https://github.com/chrisriley)
#### v0.11.2 - 28<sup>th</sup> August 2015
* Allow crop gravity to be provided as a String.
[#255](https://github.com/lovell/sharp/pull/255)
[@papandreou](https://github.com/papandreou)
* Add support for io.js v3 and Node v4.
[#246](https://github.com/lovell/sharp/issues/246)
#### v0.11.1 - 12<sup>th</sup> August 2015
* Silence MSVC warning: "C4530: C++ exception handler used, but unwind semantics are not enabled".
[#244](https://github.com/lovell/sharp/pull/244)
[@TheThing](https://github.com/TheThing)
* Suppress gamma correction for input image with alpha transparency.
[#249](https://github.com/lovell/sharp/issues/249)
[@compeak](https://github.com/compeak)
#### v0.11.0 - 15<sup>th</sup> July 2015
* Allow alpha transparency compositing via new `overlayWith` method.
[#97](https://github.com/lovell/sharp/issues/97)
[@gasi](https://github.com/gasi)
* Expose raw ICC profile data as a Buffer when using `metadata`.
[#129](https://github.com/lovell/sharp/issues/129)
[@homerjam](https://github.com/homerjam)
* Allow image header updates via a parameter passed to existing `withMetadata` method.
Provide initial support for EXIF `Orientation` tag,
which if present is now removed when using `rotate`, `flip` or `flop`.
[#189](https://github.com/lovell/sharp/issues/189)
[@h2non](https://github.com/h2non)
* Tighten constructor parameter checks.
[#221](https://github.com/lovell/sharp/issues/221)
[@mikemorris](https://github.com/mikemorris)
* Allow one input Stream to be shared with two or more output Streams via new `clone` method.
[#235](https://github.com/lovell/sharp/issues/235)
[@jaubourg](https://github.com/jaubourg)
* Use `round` instead of `floor` when auto-scaling dimensions to avoid floating-point rounding errors.
[#238](https://github.com/lovell/sharp/issues/238)
[@richardadjogah](https://github.com/richardadjogah)
### v0.10 - "*judgment*"
#### v0.10.1 - 1<sup>st</sup> June 2015
* Allow embed of image with alpha transparency onto non-transparent background.
[#204](https://github.com/lovell/sharp/issues/204)
[@mikemliu](https://github.com/mikemliu)
* Include C standard library for `atoi` as Xcode 6.3 appears to no longer do this.
[#228](https://github.com/lovell/sharp/issues/228)
[@doggan](https://github.com/doggan)
#### v0.10.0 - 23<sup>rd</sup> April 2015
* Add support for Windows (x86).
[#19](https://github.com/lovell/sharp/issues/19)
[@DullReferenceException](https://github.com/DullReferenceException)
[@itsananderson](https://github.com/itsananderson)
* Add support for Openslide input and DeepZoom output.
[#146](https://github.com/lovell/sharp/issues/146)
[@mvictoras](https://github.com/mvictoras)
* Allow arbitrary aspect ratios when resizing images via new `ignoreAspectRatio` method.
[#192](https://github.com/lovell/sharp/issues/192)
[@skedastik](https://github.com/skedastik)
* Enhance output image contrast by stretching its luminance to cover the full dynamic range via new `normalize` method.
[#194](https://github.com/lovell/sharp/issues/194)
[@bkw](https://github.com/bkw)
[@codingforce](https://github.com/codingforce)

103
docs/index.md Normal file
View File

@@ -0,0 +1,103 @@
# sharp
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.
Resizing an image is typically 4x faster than using
the quickest ImageMagick and GraphicsMagick settings.
[![Test Coverage](https://coveralls.io/repos/lovell/sharp/badge.png?branch=master)](https://coveralls.io/r/lovell/sharp?branch=master)
### Formats
This module supports reading JPEG, PNG, WebP, TIFF, OpenSlide,
GIF and other libmagick-supported formats.
Output images can be in JPEG, PNG and WebP formats as well as uncompressed raw pixel data.
Streams, Buffer objects and the filesystem can be used for input and output.
A single input Stream can be split into multiple processing pipelines and output Streams.
Deep Zoom image pyramids can be generated,
suitable for use with "slippy map" tile viewers like
[OpenSeadragon](https://github.com/openseadragon/openseadragon)
and [Leaflet](https://github.com/turban/Leaflet.Zoomify).
### Features
As well as image resizing, operations such as
rotation, extraction, compositing and gamma correction are available.
Colour spaces, embedded ICC profiles and alpha transparency channels
are all handled correctly.
### Fast
This module is powered by the blazingly fast
[libvips](https://github.com/jcupitt/libvips) image processing library,
originally created in 1989 at Birkbeck College
and currently maintained by
[John Cupitt](https://github.com/jcupitt).
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.
Everything remains non-blocking thanks to _libuv_,
no child processes are spawned and Promises/A+ are supported.
### Optimal
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/).
### Contributing
A [guide for contributors](https://github.com/lovell/sharp/blob/master/CONTRIBUTING.md)
covers reporting bugs, requesting features and submitting code changes.
### Credits
This module would never have been possible without
the help and code contributions of the following people:
* [John Cupitt](https://github.com/jcupitt)
* [Pierre Inglebert](https://github.com/pierreinglebert)
* [Jonathan Ong](https://github.com/jonathanong)
* [Chanon Sajjamanochai](https://github.com/chanon)
* [Juliano Julio](https://github.com/julianojulio)
* [Daniel Gasienica](https://github.com/gasi)
* [Julian Walker](https://github.com/julianwa)
* [Amit Pitaru](https://github.com/apitaru)
* [Brandon Aaron](https://github.com/brandonaaron)
* [Andreas Lind](https://github.com/papandreou)
* [Maurus Cuelenaere](https://github.com/mcuelenaere)
* [Linus Unnebäck](https://github.com/LinusU)
* [Victor Mateevitsi](https://github.com/mvictoras)
* [Alaric Holloway](https://github.com/skedastik)
* [Bernhard K. Weisshuhn](https://github.com/bkw)
Thank you!
### Licence
Copyright 2013, 2014, 2015 Lovell Fuller and contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
[http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0.html)
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

124
docs/install.md Normal file
View File

@@ -0,0 +1,124 @@
# Installation
```sh
npm install sharp
```
### Prerequisites
* Node.js v0.10+ or io.js
* [libvips](https://github.com/jcupitt/libvips) v7.40.0+ (8.1.1+ recommended)
* C++11 compatible compiler such as gcc 4.6+, clang 3.0+ or MSVC 2013 (Node v4+ requires gcc 4.8+)
### Linux
[![Ubuntu 12.04 Build Status](https://travis-ci.org/lovell/sharp.png?branch=master)](https://travis-ci.org/lovell/sharp)
[![Centos 6.5 Build Status](https://snap-ci.com/lovell/sharp/branch/master/build_image)](https://snap-ci.com/lovell/sharp/branch/master)
For a system-wide installation of the most suitable version of
libvips and its dependencies on the following Operating Systems:
* Debian 7, 8
* Ubuntu 12.04, 14.04, 14.10, 15.04, 15.10
* Mint 13, 17
* Elementary 0.3
* RHEL/Centos/Scientific 6, 7
* Fedora 21, 22
* Amazon Linux 2015.03, 2015.09
* OpenSuse 13
run the following as a user with `sudo` access:
```sh
curl -s https://raw.githubusercontent.com/lovell/sharp/master/preinstall.sh | sudo bash -
```
or run the following as `root`:
```sh
curl -s https://raw.githubusercontent.com/lovell/sharp/master/preinstall.sh | bash -
```
The [preinstall.sh](https://github.com/lovell/sharp/blob/master/preinstall.sh) script requires `curl` and `pkg-config`.
Add `--with-openslide` to enable OpenSlide support:
```sh
curl -s https://raw.githubusercontent.com/lovell/sharp/master/preinstall.sh | sudo bash -s -- --with-openslide
```
#### Ubuntu LTS
libvips v7.40.6 is available via a PPA.
##### 12.04
```sh
sudo add-apt-repository -y ppa:lovell/precise-backport-vips
sudo apt-get update
sudo apt-get install -y libvips-dev libgsf-1-dev
```
##### 14.04
```sh
sudo add-apt-repository -y ppa:lovell/trusty-backport-vips
sudo apt-get update
sudo apt-get install -y libvips-dev libgsf-1-dev
```
### Mac OS
[![OS X 10.9.5 Build Status](https://travis-ci.org/lovell/sharp-osx-ci.png?branch=master)](https://travis-ci.org/lovell/sharp-osx-ci)
Install libvips via homebrew:
```sh
brew install homebrew/science/vips --with-webp --with-graphicsmagick
```
A missing or incorrectly configured _Xcode Command Line Tools_ installation
[can lead](https://github.com/lovell/sharp/issues/80) to a
`library not found for -ljpeg` error.
If so, please try: `xcode-select --install`.
The _gettext_ dependency of _libvips_
[can lead](https://github.com/lovell/sharp/issues/9)
to a `library not found for -lintl` error.
If so, please try `brew link gettext --force`.
### Windows
[![Windows Server 2012 Build Status](https://ci.appveyor.com/api/projects/status/pgtul704nkhhg6sg)](https://ci.appveyor.com/project/lovell/sharp)
Requires x86 32-bit Node.js or io.js (use `iojs.exe` rather than `node.exe`).
The WebP format is currently unsupported.
1. Ensure the [node-gyp prerequisites](https://github.com/TooTallNate/node-gyp#installation) are met.
2. [Download](http://www.vips.ecs.soton.ac.uk/supported/current/win32/) and unzip `vips-dev.x.y.z.zip`.
3. Set the `VIPS_HOME` environment variable to the full path of the `vips-dev-x.y.z` directory.
4. Add `vips-dev-x.y.z\bin` to `PATH`.
Versions of MSVC more recent than 2013 may require the use of `npm install --arch=ia32 --msvs_version=2013`.
### Heroku
[Alessandro Tagliapietra](https://github.com/alex88) maintains an
[Heroku buildpack for libvips](https://github.com/alex88/heroku-buildpack-vips)
and its dependencies.
### Docker
[Marc Bachmann](https://github.com/marcbachmann) maintains a
[Dockerfile for libvips](https://github.com/marcbachmann/dockerfile-libvips).
```sh
docker pull marcbachmann/libvips
```
### Build tools
* [gulp-responsive](https://www.npmjs.com/package/gulp-responsive)
* [gulp-sharp](https://www.npmjs.com/package/gulp-sharp)
* [grunt-sharp](https://www.npmjs.com/package/grunt-sharp)

65
docs/performance.md Normal file
View File

@@ -0,0 +1,65 @@
# Performance
### Test environment
* AWS EC2 [c4.xlarge](http://aws.amazon.com/ec2/instance-types/#c4)
* Amazon Linux AMI 2015.09
* Node.js v4.1.2
### The contenders
* [jimp](https://www.npmjs.com/package/jimp) v0.2.8 - Image processing in pure JavaScript.
* [lwip](https://www.npmjs.com/package/lwip) v0.0.8 - Wrapper around CImg, compiles dependencies from source.
* ~~[imagemagick-native](https://www.npmjs.com/package/imagemagick-native)~~ - Wrapper around libmagick++, supports Buffers only. Does not currently work with Node.js v4.
* [imagemagick](https://www.npmjs.com/package/imagemagick) v0.1.3 - Supports filesystem only and "*has been unmaintained for a long time*".
* [gm](https://www.npmjs.com/package/gm) v1.20.0 - Fully featured wrapper around GraphicsMagick's `convert` command line utility.
* sharp v0.11.3 / libvips v8.1.0 - Caching within libvips disabled to ensure a fair comparison.
### The task
Decompress a 2725x2225 JPEG image, resize to 720x480 using bilinear interpolation, then compress to JPEG.
### Results
| Module | Input | Output | Ops/sec | Speed-up |
| :---------- | :----- | :----- | ------: | -------: |
| jimp | file | file | 0.94 | 1.0 |
| jimp | buffer | buffer | 1.12 | 1.2 |
| lwip | file | file | 1.12 | 1.2 |
| lwip | buffer | buffer | 1.13 | 1.2 |
| gm | buffer | buffer | 5.50 | 5.9 |
| gm | file | file | 5.55 | 5.9 |
| imagemagick | file | file | 8.66 | 9.2 |
| sharp | stream | stream | 16.33 | 17.4 |
| sharp | file | file | 16.88 | 18.0 |
| sharp | file | buffer | 16.90 | 18.1 |
| sharp | buffer | file | 16.99 | 18.1 |
| sharp | buffer | buffer | 17.06 | 18.1 |
Greater performance can be expected with caching enabled (default) and using 8+ core machines.
The I/O limits of the relevant (de)compression library will generally determine maximum throughput.
### Benchmark test prerequisites
Requires both _ImageMagick_ and _GraphicsMagick_:
```sh
brew install imagemagick
brew install graphicsmagick
```
```sh
sudo apt-get install imagemagick graphicsmagick libmagick++-dev
```
### Running the benchmark test
```sh
git clone https://github.com/lovell/sharp.git
cd sharp
npm install
cd test/bench
npm install
npm test
```

View File

@@ -60,6 +60,8 @@ var Sharp = function(input) {
gamma: 0,
greyscale: false,
normalize: 0,
// overlay
overlayPath: '',
// output options
output: '__input',
progressive: false,
@@ -72,6 +74,7 @@ var Sharp = function(input) {
optimiseScans: false,
streamOut: false,
withMetadata: false,
withMetadataOrientation: -1,
tileSize: 256,
tileOverlap: 0,
// Function to notify of queue length changes
@@ -85,9 +88,11 @@ var Sharp = function(input) {
} else if (typeof input === 'object' && input instanceof Buffer) {
// input=buffer
this.options.bufferIn = input;
} else {
} else if (typeof input === 'undefined') {
// input=stream
this.options.streamIn = true;
} else {
throw new Error('Unsupported input ' + typeof input);
}
return this;
};
@@ -132,12 +137,14 @@ Sharp.prototype._write = function(chunk, encoding, callback) {
};
// Crop this part of the resized image (Center/Centre, North, East, South, West)
module.exports.gravity = {'center': 0, 'centre': 0, 'north': 1, 'east': 2, 'south': 3, 'west': 4};
module.exports.gravity = {'center': 0, 'centre': 0, 'north': 1, 'east': 2, 'south': 3, 'west': 4, 'northeast': 5, 'southeast': 6, 'southwest': 7, 'northwest': 8};
Sharp.prototype.crop = function(gravity) {
this.options.canvas = 'crop';
if (typeof gravity === 'number' && !Number.isNaN(gravity) && gravity >= 0 && gravity <= 4) {
if (typeof gravity === 'number' && !Number.isNaN(gravity) && gravity >= 0 && gravity <= 8) {
this.options.gravity = gravity;
} else if (typeof gravity === 'string' && typeof module.exports.gravity[gravity] === 'number') {
this.options.gravity = module.exports.gravity[gravity];
} else {
throw new Error('Unsupported crop gravity ' + gravity);
}
@@ -203,6 +210,17 @@ Sharp.prototype.flatten = function(flatten) {
return this;
};
Sharp.prototype.overlayWith = function(overlayPath) {
if (typeof overlayPath !== 'string') {
throw new Error('The overlay path must be a string');
}
if (overlayPath === '') {
throw new Error('The overlay path cannot be empty');
}
this.options.overlayPath = overlayPath;
return this;
};
/*
Rotate output image by 0, 90, 180 or 270 degrees
Auto-rotation based on the EXIF Orientation tag is represented by an angle of -1
@@ -381,7 +399,7 @@ Sharp.prototype.sequentialRead = function(sequentialRead) {
};
Sharp.prototype.quality = function(quality) {
if (!Number.isNaN(quality) && quality >= 1 && quality <= 100) {
if (!Number.isNaN(quality) && quality >= 1 && quality <= 100 && quality % 1 === 0) {
this.options.quality = quality;
} else {
throw new Error('Invalid quality (1 to 100) ' + quality);
@@ -464,9 +482,26 @@ Sharp.prototype.optimizeScans = Sharp.prototype.optimiseScans;
/*
Include all metadata (EXIF, XMP, IPTC) from the input image in the output image
Optionally provide an Object with attributes to update:
orientation: numeric value for EXIF Orientation tag
*/
Sharp.prototype.withMetadata = function(withMetadata) {
this.options.withMetadata = (typeof withMetadata === 'boolean') ? withMetadata : true;
if (typeof withMetadata === 'object') {
if ('orientation' in withMetadata) {
if (
typeof withMetadata.orientation === 'number' &&
!Number.isNaN(withMetadata.orientation) &&
withMetadata.orientation % 1 === 0 &&
withMetadata.orientation >=0 &&
withMetadata.orientation <= 7
) {
this.options.withMetadataOrientation = withMetadata.orientation;
} else {
throw new Error('Invalid orientation (0 to 7) ' + withMetadata.orientation);
}
}
}
return this;
};
@@ -552,7 +587,7 @@ Sharp.prototype.toFile = function(output, callback) {
}
} else {
this.options.output = output;
return this._sharp(callback);
return this._pipeline(callback);
}
}
return this;
@@ -562,7 +597,7 @@ Sharp.prototype.toFile = function(output, callback) {
Write output to a Buffer
*/
Sharp.prototype.toBuffer = function(callback) {
return this._sharp(callback);
return this._pipeline(callback);
};
/*
@@ -625,7 +660,7 @@ Sharp.prototype.toFormat = function(format) {
Sharp.prototype._read = function() {
if (!this.options.streamOut) {
this.options.streamOut = true;
this._sharp();
this._pipeline();
}
};
@@ -633,18 +668,18 @@ Sharp.prototype._read = function() {
Invoke the C++ image processing pipeline
Supports callback, stream and promise variants
*/
Sharp.prototype._sharp = function(callback) {
Sharp.prototype._pipeline = function(callback) {
var that = this;
if (typeof callback === 'function') {
// output=file/buffer
if (this.options.streamIn) {
// output=file/buffer, input=stream
this.on('finish', function() {
sharp.resize(that.options, callback);
sharp.pipeline(that.options, callback);
});
} else {
// output=file/buffer, input=file/buffer
sharp.resize(this.options, callback);
sharp.pipeline(this.options, callback);
}
return this;
} else if (this.options.streamOut) {
@@ -652,9 +687,9 @@ Sharp.prototype._sharp = function(callback) {
if (this.options.streamIn) {
// output=stream, input=stream
this.on('finish', function() {
sharp.resize(that.options, function(err, data) {
sharp.pipeline(that.options, function(err, data) {
if (err) {
that.emit('error', new Error(err));
that.emit('error', err);
} else {
that.push(data);
}
@@ -663,9 +698,9 @@ Sharp.prototype._sharp = function(callback) {
});
} else {
// output=stream, input=file/buffer
sharp.resize(this.options, function(err, data) {
sharp.pipeline(this.options, function(err, data) {
if (err) {
that.emit('error', new Error(err));
that.emit('error', err);
} else {
that.push(data);
}
@@ -679,7 +714,7 @@ Sharp.prototype._sharp = function(callback) {
// output=promise, input=stream
return new BluebirdPromise(function(resolve, reject) {
that.on('finish', function() {
sharp.resize(that.options, function(err, data) {
sharp.pipeline(that.options, function(err, data) {
if (err) {
reject(err);
} else {
@@ -691,7 +726,7 @@ Sharp.prototype._sharp = function(callback) {
} else {
// output=promise, input=file/buffer
return new BluebirdPromise(function(resolve, reject) {
sharp.resize(that.options, function(err, data) {
sharp.pipeline(that.options, function(err, data) {
if (err) {
reject(err);
} else {
@@ -722,22 +757,22 @@ Sharp.prototype.metadata = function(callback) {
if (this.options.streamIn) {
return new BluebirdPromise(function(resolve, reject) {
that.on('finish', function() {
sharp.metadata(that.options, function(err, data) {
sharp.metadata(that.options, function(err, metadata) {
if (err) {
reject(err);
} else {
resolve(data);
resolve(metadata);
}
});
});
});
} else {
return new BluebirdPromise(function(resolve, reject) {
sharp.metadata(that.options, function(err, data) {
sharp.metadata(that.options, function(err, metadata) {
if (err) {
reject(err);
} else {
resolve(data);
resolve(metadata);
}
});
});
@@ -745,6 +780,24 @@ Sharp.prototype.metadata = function(callback) {
}
};
/*
Clone new instance using existing options.
Cloned instances share the same input.
*/
Sharp.prototype.clone = function() {
// Clone existing options
var clone = new Sharp();
util._extend(clone.options, this.options);
clone.streamIn = false;
// Pass 'finish' event to clone for Stream-based input
this.on('finish', function() {
// Clone inherits input data
clone.options.bufferIn = this.options.bufferIn;
clone.emit('finish');
});
return clone;
};
/*
Get and set cache memory and item limits
*/

17
mkdocs.yml Normal file
View File

@@ -0,0 +1,17 @@
site_name: sharp
site_url: http://sharp.dimens.io/
repo_url: https://github.com/lovell/sharp
site_description: The fastest Node.js module for resizing JPEG, PNG, WebP and TIFF images. Uses the libvips library.
copyright: <a href="https://dimens.io/">dimens.io</a>
google_analytics: ['UA-13034748-12', 'sharp.dimens.io']
theme: readthedocs
markdown_extensions:
- toc:
permalink: True
dev_addr: 0.0.0.0:10101
pages:
- Home: index.md
- Installation: install.md
- API: api.md
- Performance: performance.md
- Changelog: changelog.md

35
package.json Executable file → Normal file
View File

@@ -1,6 +1,6 @@
{
"name": "sharp",
"version": "0.10.1",
"version": "0.11.4",
"author": "Lovell Fuller <npm@lovell.info>",
"contributors": [
"Pierre Inglebert <pierre.inglebert@gmail.com>",
@@ -16,13 +16,17 @@
"Linus Unnebäck <linus@folkdatorn.se>",
"Victor Mateevitsi <mvictoras@gmail.com>",
"Alaric Holloway <alaric.holloway@gmail.com>",
"Bernhard K. Weisshuhn <bkw@codingforce.com>"
"Bernhard K. Weisshuhn <bkw@codingforce.com>",
"Chris Riley <criley@primedia.com>"
],
"description": "High performance Node.js module to resize JPEG, PNG, WebP and TIFF images using the libvips library",
"scripts": {
"clean": "rm -rf test/fixtures/output.*",
"test": "VIPS_WARNING=0 node ./node_modules/istanbul/lib/cli.js cover ./node_modules/mocha/bin/_mocha -- --slow=5000 --timeout=20000 ./test/unit/*.js",
"test-win32-node": "node ./node_modules/mocha/bin/mocha --slow=5000 --timeout=20000 ./test/unit/*.js",
"test-win32-iojs": "iojs ./node_modules/mocha/bin/mocha --slow=5000 --timeout=20000 ./test/unit/*.js"
"test-clean": "npm run clean && npm install && npm test",
"test-leak": "./test/leak/leak.sh",
"test-win32-node": "node ./node_modules/mocha/bin/mocha --slow=5000 --timeout=30000 ./test/unit/*.js",
"test-win32-iojs": "iojs ./node_modules/mocha/bin/mocha --slow=5000 --timeout=30000 ./test/unit/*.js"
},
"main": "index.js",
"repository": {
@@ -42,19 +46,22 @@
"vips"
],
"dependencies": {
"bluebird": "^2.9.27",
"color": "^0.8.0",
"nan": "^1.8.4",
"semver": "^4.3.6"
"bluebird": "^3.0.5",
"color": "^0.10.1",
"nan": "^2.1.0",
"semver": "^5.0.3"
},
"devDependencies": {
"async": "^1.1.0",
"coveralls": "^2.11.2",
"istanbul": "^0.3.14",
"mocha": "^2.2.5",
"mocha-jshint": "^2.2.3",
"async": "^1.5.0",
"coveralls": "^2.11.4",
"exif-reader": "1.0.0",
"icc": "^0.0.2",
"istanbul": "^0.4.0",
"mocha": "^2.3.3",
"mocha-jshint": "^2.2.5",
"node-cpplint": "^0.4.0",
"rimraf": "^2.3.4"
"rimraf": "^2.4.3",
"bufferutil": "^1.2.1"
},
"license": "Apache-2.0",
"engines": {

View File

@@ -2,19 +2,20 @@
# 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
# * Ubuntu 12.04, 14.04, 14.10, 15.04, 15.10
# * Mint 13, 17
# * Elementary 0.3
# * Red Hat Linux
# * RHEL/Centos/Scientific 6, 7
# * Fedora 21, 22
# * Amazon Linux 2014.09
# * Amazon Linux 2015.03, 2015.09
# * OpenSuse 13
vips_version_minimum=7.40.0
vips_version_latest_major_minor=8.0
vips_version_latest_patch=2
vips_version_latest_major_minor=8.1
vips_version_latest_patch=1
openslide_version_minimum=3.4.0
openslide_version_latest_major_minor=3.4
@@ -25,7 +26,7 @@ install_libvips_from_source() {
curl -O http://www.vips.ecs.soton.ac.uk/supported/$vips_version_latest_major_minor/vips-$vips_version_latest_major_minor.$vips_version_latest_patch.tar.gz
tar zvxf vips-$vips_version_latest_major_minor.$vips_version_latest_patch.tar.gz
cd vips-$vips_version_latest_major_minor.$vips_version_latest_patch
./configure --disable-debug --disable-docs --disable-static --disable-introspection --enable-cxx=yes --without-python --without-orc --without-fftw $1
./configure --disable-debug --disable-docs --disable-static --disable-introspection --disable-dependency-tracking --enable-cxx=yes --without-python --without-orc --without-fftw $1
make
make install
cd ..
@@ -55,8 +56,7 @@ sorry() {
exit 1
}
pkg_config_path_homebrew=`which brew >/dev/null 2>&1 && eval $(brew --env) && echo $PKG_CONFIG_LIBDIR || true`
pkg_config_path="$pkg_config_path_homebrew:$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig:/usr/lib/pkgconfig"
pkg_config_path="$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig:/usr/lib/pkgconfig"
check_if_library_exists() {
PKG_CONFIG_PATH=$pkg_config_path pkg-config --exists $1
@@ -122,243 +122,205 @@ fi
# OS-specific installations of libopenslide follows
# Either openslide does not exist, or vips is installed without openslide support
if [ $enable_openslide -eq 1 ] && [ -z $vips_with_openslide ] && [ $openslide_exists -eq 0 ]; then
case $(uname -s) in
*[Dd]arwin*)
# Mac OS
echo "Detected Mac OS"
if type "brew" > /dev/null; then
echo "Installing libopenslide via homebrew"
brew install openslide
elif type "port" > /dev/null; then
echo "Installing libopenslide via MacPorts"
port install openslide
else
sorry "openslide" "Mac OS without homebrew or MacPorts"
fi
if [ -f /etc/debian_version ]; then
# Debian Linux
DISTRO=$(lsb_release -c -s)
echo "Detected Debian Linux '$DISTRO'"
case "$DISTRO" in
jessie|vivid|wily)
# Debian 8, Ubuntu 15
echo "Installing libopenslide via apt-get"
apt-get install -y libopenslide-dev
;;
trusty|utopic|qiana|rebecca|rafaela|freya)
# Ubuntu 14, Mint 17
echo "Installing libopenslide dependencies via apt-get"
apt-get install -y automake build-essential curl zlib1g-dev libopenjpeg-dev libpng12-dev libjpeg-dev libtiff5-dev libgdk-pixbuf2.0-dev libxml2-dev libsqlite3-dev libcairo2-dev libglib2.0-dev sqlite3 libsqlite3-dev
install_libopenslide_from_source
;;
precise|wheezy|maya)
# Debian 7, Ubuntu 12.04, Mint 13
echo "Installing libopenslide dependencies via apt-get"
apt-get install -y automake build-essential curl zlib1g-dev libopenjpeg-dev libpng12-dev libjpeg-dev libtiff5-dev libgdk-pixbuf2.0-dev libxml2-dev libsqlite3-dev libcairo2-dev libglib2.0-dev sqlite3 libsqlite3-dev
install_libopenslide_from_source
;;
*)
# Unsupported Debian-based OS
sorry "openslide" "Debian-based $DISTRO"
;;
esac
elif [ -f /etc/redhat-release ]; then
# Red Hat Linux
RELEASE=$(cat /etc/redhat-release)
echo "Detected Red Hat Linux '$RELEASE'"
case $RELEASE in
"Red Hat Enterprise Linux release 7."*|"CentOS Linux release 7."*|"Scientific Linux release 7."*)
# RHEL/CentOS 7
echo "Installing libopenslide dependencies via yum"
yum groupinstall -y "Development Tools"
yum install -y tar curl libpng-devel libjpeg-devel libxml2-devel zlib-devel openjpeg-devel libtiff-devel gdk-pixbuf2-devel sqlite-devel cairo-devel glib2-devel
install_libopenslide_from_source "--prefix=/usr"
;;
"Red Hat Enterprise Linux release 6."*|"CentOS release 6."*|"Scientific Linux release 6."*)
# RHEL/CentOS 6
echo "Installing libopenslide dependencies via yum"
yum groupinstall -y "Development Tools"
yum install -y tar curl libpng-devel libjpeg-devel libxml2-devel zlib-devel openjpeg-devel libtiff-devel gdk-pixbuf2-devel sqlite-devel cairo-devel glib2-devel
install_libopenslide_from_source "--prefix=/usr"
;;
"Fedora release 21 "*|"Fedora release 22 "*)
# Fedora 21, 22
echo "Installing libopenslide via yum"
yum install -y openslide-devel
;;
*)
# Unsupported RHEL-based OS
sorry "openslide" "$RELEASE"
;;
esac
elif [ -f /etc/os-release ]; then
RELEASE=$(cat /etc/os-release | grep VERSION)
echo "Detected OpenSuse Linux '$RELEASE'"
case $RELEASE in
*"13.2"*)
echo "Installing libopenslide via zypper"
zypper --gpg-auto-import-keys install -y libopenslide-devel
;;
*)
if [ -f /etc/debian_version ]; then
# Debian Linux
DISTRO=$(lsb_release -c -s)
echo "Detected Debian Linux '$DISTRO'"
case "$DISTRO" in
jessie|vivid)
# Debian 8, Ubuntu 15
echo "Installing libopenslide via apt-get"
apt-get install -y libopenslide-dev
;;
trusty|utopic|qiana|rebecca)
# Ubuntu 14, Mint 17
echo "Installing libopenslide dependencies via apt-get"
apt-get install -y automake build-essential curl zlib1g-dev libopenjpeg-dev libpng12-dev libjpeg-dev libtiff5-dev libgdk-pixbuf2.0-dev libxml2-dev libsqlite3-dev libcairo2-dev libglib2.0-dev sqlite3 libsqlite3-dev
install_libopenslide_from_source
;;
precise|wheezy|maya)
# Debian 7, Ubuntu 12.04, Mint 13
echo "Installing libopenslide dependencies via apt-get"
apt-get install -y automake build-essential curl zlib1g-dev libopenjpeg-dev libpng12-dev libjpeg-dev libtiff5-dev libgdk-pixbuf2.0-dev libxml2-dev libsqlite3-dev libcairo2-dev libglib2.0-dev sqlite3 libsqlite3-dev
install_libopenslide_from_source
;;
*)
# Unsupported Debian-based OS
sorry "openslide" "Debian-based $DISTRO"
;;
esac
elif [ -f /etc/redhat-release ]; then
# Red Hat Linux
RELEASE=$(cat /etc/redhat-release)
echo "Detected Red Hat Linux '$RELEASE'"
case $RELEASE in
"Red Hat Enterprise Linux release 7."*|"CentOS Linux release 7."*|"Scientific Linux release 7."*)
# RHEL/CentOS 7
echo "Installing libopenslide dependencies via yum"
yum groupinstall -y "Development Tools"
yum install -y tar curl libpng-devel libjpeg-devel libxml2-devel zlib-devel openjpeg-devel libtiff-devel gdk-pixbuf2-devel sqlite-devel cairo-devel glib2-devel
install_libopenslide_from_source "--prefix=/usr"
;;
"Red Hat Enterprise Linux release 6."*|"CentOS release 6."*|"Scientific Linux release 6."*)
# RHEL/CentOS 6
echo "Installing libopenslide dependencies via yum"
yum groupinstall -y "Development Tools"
yum install -y tar curl libpng-devel libjpeg-devel libxml2-devel zlib-devel openjpeg-devel libtiff-devel gdk-pixbuf2-devel sqlite-devel cairo-devel glib2-devel
install_libopenslide_from_source "--prefix=/usr"
;;
"Fedora release 21 "*|"Fedora release 22 "*)
# Fedora 21, 22
echo "Installing libopenslide via yum"
yum install -y openslide-devel
;;
*)
# Unsupported RHEL-based OS
sorry "openslide" "$RELEASE"
;;
esac
elif [ -f /etc/os-release ]; then
RELEASE=$(cat /etc/os-release | grep VERSION)
echo "Detected OpenSuse Linux '$RELEASE'"
case $RELEASE in
*"13.2"*)
echo "Installing libopenslide via zypper"
zypper --gpg-auto-import-keys install -y libopenslide-devel
;;
esac
elif [ -f /etc/SuSE-brand ]; then
RELEASE=$(cat /etc/SuSE-brand | grep VERSION)
echo "Detected OpenSuse Linux '$RELEASE'"
case $RELEASE in
*"13.1")
echo "Installing libopenslide dependencies via zypper"
zypper --gpg-auto-import-keys install -y --type pattern devel_basis
zypper --gpg-auto-import-keys install -y tar curl libpng16-devel libjpeg-turbo libjpeg8-devel libxml2-devel zlib-devel openjpeg-devel libtiff-devel libgdk_pixbuf-2_0-0 sqlite3-devel cairo-devel glib2-devel
install_libopenslide_from_source
;;
esac
else
# Unsupported OS
sorry "openslide" "$(uname -a)"
fi
esac
elif [ -f /etc/SuSE-brand ]; then
RELEASE=$(cat /etc/SuSE-brand | grep VERSION)
echo "Detected OpenSuse Linux '$RELEASE'"
case $RELEASE in
*"13.1")
echo "Installing libopenslide dependencies via zypper"
zypper --gpg-auto-import-keys install -y --type pattern devel_basis
zypper --gpg-auto-import-keys install -y tar curl libpng16-devel libjpeg-turbo libjpeg8-devel libxml2-devel zlib-devel openjpeg-devel libtiff-devel libgdk_pixbuf-2_0-0 sqlite3-devel cairo-devel glib2-devel
install_libopenslide_from_source
;;
esac
esac
else
# Unsupported OS
sorry "openslide" "$(uname -a)"
fi
fi
# OS-specific installations of libvips follows
case $(uname -s) in
*[Dd]arwin*)
# Mac OS
echo "Detected Mac OS"
if type "brew" > /dev/null; then
echo "Installing libvips via homebrew"
if [ -f /etc/debian_version ]; then
# Debian Linux
DISTRO=$(lsb_release -c -s)
echo "Detected Debian Linux '$DISTRO'"
case "$DISTRO" in
jessie|vivid|wily)
# Debian 8, Ubuntu 15
if [ $enable_openslide -eq 1 ]; then
brew install homebrew/science/vips --with-webp --with-graphicsmagick --with-openslide
echo "Recompiling vips with openslide support"
install_libvips_from_source
else
brew install homebrew/science/vips --with-webp --with-graphicsmagick
echo "Installing libvips via apt-get"
apt-get install -y libvips-dev libgsf-1-dev
fi
elif type "port" > /dev/null; then
echo "Installing libvips via MacPorts"
port install vips
else
sorry "vips" "Mac OS without homebrew or MacPorts"
fi
;;
trusty|utopic|qiana|rebecca|rafaela|freya)
# Ubuntu 14, Mint 17
echo "Installing libvips dependencies via apt-get"
apt-get install -y automake build-essential gobject-introspection gtk-doc-tools libglib2.0-dev libjpeg-dev libpng12-dev libwebp-dev libtiff5-dev libexif-dev libgsf-1-dev liblcms2-dev libxml2-dev swig libmagickcore-dev curl
install_libvips_from_source
;;
precise|wheezy|maya)
# Debian 7, Ubuntu 12.04, Mint 13
echo "Installing libvips dependencies via apt-get"
add-apt-repository -y ppa:lyrasis/precise-backports
apt-get update
apt-get install -y automake build-essential gobject-introspection gtk-doc-tools libglib2.0-dev libjpeg-dev libpng12-dev libwebp-dev libtiff4-dev libexif-dev libgsf-1-dev liblcms2-dev libxml2-dev swig libmagickcore-dev curl
install_libvips_from_source
;;
*)
# Unsupported Debian-based OS
sorry "vips" "Debian-based $DISTRO"
;;
esac
elif [ -f /etc/redhat-release ]; then
# Red Hat Linux
RELEASE=$(cat /etc/redhat-release)
echo "Detected Red Hat Linux '$RELEASE'"
case $RELEASE in
"Red Hat Enterprise Linux release 7."*|"CentOS Linux release 7."*|"Scientific Linux release 7."*)
# RHEL/CentOS 7
echo "Installing libvips dependencies via yum"
yum groupinstall -y "Development Tools"
yum install -y gtk-doc libxml2-devel libjpeg-turbo-devel libpng-devel libtiff-devel libexif-devel libgsf-devel lcms2-devel ImageMagick-devel gobject-introspection-devel libwebp-devel curl
install_libvips_from_source "--prefix=/usr"
;;
"Red Hat Enterprise Linux release 6."*|"CentOS release 6."*|"Scientific Linux release 6."*)
# RHEL/CentOS 6
echo "Installing libvips dependencies via yum"
yum groupinstall -y "Development Tools"
yum install -y gtk-doc libxml2-devel libjpeg-turbo-devel libpng-devel libtiff-devel libexif-devel libgsf-devel lcms-devel ImageMagick-devel curl
yum install -y http://li.nux.ro/download/nux/dextop/el6/x86_64/nux-dextop-release-0-2.el6.nux.noarch.rpm
yum install -y --enablerepo=nux-dextop gobject-introspection-devel
yum install -y http://rpms.famillecollet.com/enterprise/remi-release-6.rpm
yum install -y --enablerepo=remi libwebp-devel
install_libvips_from_source "--prefix=/usr"
;;
"Fedora release 21 "*|"Fedora release 22 "*)
# Fedora 21, 22
if [ $enable_openslide -eq 1 ]; then
echo "Installing libvips dependencies via yum"
yum groupinstall -y "Development Tools"
yum install -y gcc-c++ gtk-doc libxml2-devel libjpeg-turbo-devel libpng-devel libtiff-devel libexif-devel lcms-devel ImageMagick-devel gobject-introspection-devel libwebp-devel curl
echo "Compiling vips with openslide support"
install_libvips_from_source "--prefix=/usr"
else
echo "Installing libvips via yum"
yum install -y vips-devel
fi
;;
*)
# Unsupported RHEL-based OS
sorry "vips" "$RELEASE"
;;
esac
elif [ -f /etc/system-release ]; then
# Probably Amazon Linux
RELEASE=$(cat /etc/system-release)
case $RELEASE in
"Amazon Linux AMI release 2015.03"|"Amazon Linux AMI release 2015.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 libgsf-devel lcms2-devel ImageMagick-devel gobject-introspection-devel libwebp-devel curl
install_libvips_from_source "--prefix=/usr"
;;
*)
# Unsupported Amazon Linux version
sorry "vips" "$RELEASE"
;;
esac
elif [ -f /etc/os-release ]; then
RELEASE=$(cat /etc/os-release | grep VERSION)
echo "Detected OpenSuse Linux '$RELEASE'"
case $RELEASE in
*"13.2"*)
echo "Installing libvips dependencies via zypper"
zypper --gpg-auto-import-keys install -y --type pattern devel_basis
zypper --gpg-auto-import-keys install -y tar curl gtk-doc libxml2-devel libjpeg-turbo libjpeg8-devel libpng16-devel libtiff-devel libexif-devel liblcms2-devel ImageMagick-devel gobject-introspection-devel libwebp-devel
install_libvips_from_source
;;
*)
if [ -f /etc/debian_version ]; then
# Debian Linux
DISTRO=$(lsb_release -c -s)
echo "Detected Debian Linux '$DISTRO'"
case "$DISTRO" in
jessie|vivid)
# Debian 8, Ubuntu 15
if [ $enable_openslide -eq 1 ]; then
echo "Recompiling vips with openslide support"
install_libvips_from_source
else
echo "Installing libvips via apt-get"
apt-get install -y libvips-dev libgsf-1-dev
fi
;;
trusty|utopic|qiana|rebecca)
# Ubuntu 14, Mint 17
echo "Installing libvips dependencies via apt-get"
apt-get install -y automake build-essential gobject-introspection gtk-doc-tools libglib2.0-dev libjpeg-dev libpng12-dev libwebp-dev libtiff5-dev libexif-dev libgsf-1-dev liblcms2-dev libxml2-dev swig libmagickcore-dev curl
install_libvips_from_source
;;
precise|wheezy|maya)
# Debian 7, Ubuntu 12.04, Mint 13
echo "Installing libvips dependencies via apt-get"
add-apt-repository -y ppa:lyrasis/precise-backports
apt-get update
apt-get install -y automake build-essential gobject-introspection gtk-doc-tools libglib2.0-dev libjpeg-dev libpng12-dev libwebp-dev libtiff4-dev libexif-dev libgsf-1-dev liblcms2-dev libxml2-dev swig libmagickcore-dev curl
install_libvips_from_source
;;
*)
# Unsupported Debian-based OS
sorry "vips" "Debian-based $DISTRO"
;;
esac
elif [ -f /etc/redhat-release ]; then
# Red Hat Linux
RELEASE=$(cat /etc/redhat-release)
echo "Detected Red Hat Linux '$RELEASE'"
case $RELEASE in
"Red Hat Enterprise Linux release 7."*|"CentOS Linux release 7."*|"Scientific Linux release 7."*)
# RHEL/CentOS 7
echo "Installing libvips dependencies via yum"
yum groupinstall -y "Development Tools"
yum install -y gtk-doc libxml2-devel libjpeg-turbo-devel libpng-devel libtiff-devel libexif-devel libgsf-devel lcms-devel ImageMagick-devel gobject-introspection-devel libwebp-devel curl
install_libvips_from_source "--prefix=/usr"
;;
"Red Hat Enterprise Linux release 6."*|"CentOS release 6."*|"Scientific Linux release 6."*)
# RHEL/CentOS 6
echo "Installing libvips dependencies via yum"
yum groupinstall -y "Development Tools"
yum install -y gtk-doc libxml2-devel libjpeg-turbo-devel libpng-devel libtiff-devel libexif-devel libgsf-devel lcms-devel ImageMagick-devel curl
yum install -y http://li.nux.ro/download/nux/dextop/el6/x86_64/nux-dextop-release-0-2.el6.nux.noarch.rpm
yum install -y --enablerepo=nux-dextop gobject-introspection-devel
yum install -y http://rpms.famillecollet.com/enterprise/remi-release-6.rpm
yum install -y --enablerepo=remi libwebp-devel
install_libvips_from_source "--prefix=/usr"
;;
"Fedora release 21 "*|"Fedora release 22 "*)
# Fedora 21, 22
if [ $enable_openslide -eq 1 ]; then
echo "Installing libvips dependencies via yum"
yum groupinstall -y "Development Tools"
yum install -y gcc-c++ gtk-doc libxml2-devel libjpeg-turbo-devel libpng-devel libtiff-devel libexif-devel lcms-devel ImageMagick-devel gobject-introspection-devel libwebp-devel curl
echo "Compiling vips with openslide support"
install_libvips_from_source "--prefix=/usr"
else
echo "Installing libvips via yum"
yum install -y vips-devel
fi
;;
*)
# Unsupported RHEL-based OS
sorry "vips" "$RELEASE"
;;
esac
elif [ -f /etc/system-release ]; then
# Probably Amazon Linux
RELEASE=$(cat /etc/system-release)
case $RELEASE in
"Amazon Linux AMI release 2014.09"|"Amazon Linux AMI release 2015.03")
# Amazon Linux
echo "Detected '$RELEASE'"
echo "Installing libvips dependencies via yum"
yum groupinstall -y "Development Tools"
yum install -y gtk-doc libxml2-devel libjpeg-turbo-devel libpng-devel libtiff-devel libexif-devel libgsf-devel lcms-devel ImageMagick-devel gobject-introspection-devel libwebp-devel curl
install_libvips_from_source "--prefix=/usr"
;;
*)
# Unsupported Amazon Linux version
sorry "vips" "$RELEASE"
;;
esac
elif [ -f /etc/os-release ]; then
RELEASE=$(cat /etc/os-release | grep VERSION)
echo "Detected OpenSuse Linux '$RELEASE'"
case $RELEASE in
*"13.2"*)
echo "Installing libvips dependencies via zypper"
zypper --gpg-auto-import-keys install -y --type pattern devel_basis
zypper --gpg-auto-import-keys install -y tar curl gtk-doc libxml2-devel libjpeg-turbo libjpeg8-devel libpng16-devel libtiff-devel libexif-devel liblcms2-devel ImageMagick-devel gobject-introspection-devel libwebp-devel
install_libvips_from_source
;;
esac
elif [ -f /etc/SuSE-brand ]; then
RELEASE=$(cat /etc/SuSE-brand | grep VERSION)
echo "Detected OpenSuse Linux '$RELEASE'"
case $RELEASE in
*"13.1")
echo "Installing libvips dependencies via zypper"
zypper --gpg-auto-import-keys install -y --type pattern devel_basis
zypper --gpg-auto-import-keys install -y tar curl gtk-doc libxml2-devel libjpeg-turbo libjpeg8-devel libpng16-devel libtiff-devel libexif-devel liblcms2-devel ImageMagick-devel gobject-introspection-devel libwebp-devel
install_libvips_from_source
;;
esac
else
# Unsupported OS
sorry "vips" "$(uname -a)"
fi
esac
elif [ -f /etc/SuSE-brand ]; then
RELEASE=$(cat /etc/SuSE-brand | grep VERSION)
echo "Detected OpenSuse Linux '$RELEASE'"
case $RELEASE in
*"13.1")
echo "Installing libvips dependencies via zypper"
zypper --gpg-auto-import-keys install -y --type pattern devel_basis
zypper --gpg-auto-import-keys install -y tar curl gtk-doc libxml2-devel libjpeg-turbo libjpeg8-devel libpng16-devel libtiff-devel libexif-devel liblcms2-devel ImageMagick-devel gobject-introspection-devel libwebp-devel
install_libvips_from_source
;;
esac
esac
else
# Unsupported OS
sorry "vips" "$(uname -a)"
fi

View File

@@ -7,20 +7,26 @@
// Verify platform and compiler compatibility
#if (VIPS_MAJOR_VERSION < 7 || (VIPS_MAJOR_VERSION == 7 && VIPS_MINOR_VERSION < 40))
#error libvips version 7.40.0+ required - see http://sharp.dimens.io/page/install
#endif
#ifdef _WIN64
#error Windows 64-bit is currently unsupported - see https://github.com/lovell/sharp#windows
#error Windows 64-bit is currently unsupported - see http://sharp.dimens.io/page/install#windows
#endif
#if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6)))
#error GCC version 4.6+ is required for C++11 features - see https://github.com/lovell/sharp#prerequisites
#error GCC version 4.6+ is required for C++11 features - see http://sharp.dimens.io/page/install#prerequisites
#endif
#if (defined(__clang__) && defined(__has_feature))
#if (!__has_feature(cxx_range_for))
#error clang version 3.0+ is required for C++11 features - see https://github.com/lovell/sharp#prerequisites
#error clang version 3.0+ is required for C++11 features - see http://sharp.dimens.io/page/install#prerequisites
#endif
#endif
#define EXIF_IFD0_ORIENTATION "exif-ifd0-Orientation"
namespace sharp {
// How many tasks are in the queue?
@@ -137,14 +143,30 @@ namespace sharp {
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)
vips_image_get_typeof(image, EXIF_IFD0_ORIENTATION) != 0 &&
!vips_image_get_string(image, EXIF_IFD0_ORIENTATION, &exif)
) {
orientation = atoi(&exif[0]);
}
return orientation;
}
/*
Set EXIF Orientation of image.
*/
void SetExifOrientation(VipsImage *image, int const orientation) {
char exif[3];
g_snprintf(exif, sizeof(exif), "%d", orientation);
vips_image_set_string(image, EXIF_IFD0_ORIENTATION, exif);
}
/*
Remove EXIF Orientation from image.
*/
void RemoveExifOrientation(VipsImage *image) {
SetExifOrientation(image, 0);
}
/*
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.

View File

@@ -1,6 +1,8 @@
#ifndef SRC_COMMON_H_
#define SRC_COMMON_H_
#include <string>
namespace sharp {
enum class ImageType {
@@ -62,6 +64,16 @@ namespace sharp {
*/
int ExifOrientation(VipsImage const *image);
/*
Set EXIF Orientation of image.
*/
void SetExifOrientation(VipsImage *image, int const orientation);
/*
Remove EXIF Orientation from image.
*/
void RemoveExifOrientation(VipsImage *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.

View File

@@ -16,6 +16,19 @@ using v8::Boolean;
using v8::Function;
using v8::Exception;
using Nan::AsyncQueueWorker;
using Nan::AsyncWorker;
using Nan::Callback;
using Nan::HandleScope;
using Nan::Utf8String;
using Nan::Has;
using Nan::Get;
using Nan::Set;
using Nan::New;
using Nan::NewBuffer;
using Nan::Null;
using Nan::Error;
using sharp::ImageType;
using sharp::DetermineImageType;
using sharp::InitImage;
@@ -27,7 +40,7 @@ using sharp::counterQueue;
struct MetadataBaton {
// Input
std::string fileIn;
void* bufferIn;
char *bufferIn;
size_t bufferInLength;
// Output
std::string format;
@@ -38,17 +51,33 @@ struct MetadataBaton {
bool hasProfile;
bool hasAlpha;
int orientation;
char *exif;
size_t exifLength;
char *icc;
size_t iccLength;
std::string err;
MetadataBaton():
bufferInLength(0),
orientation(0) {}
orientation(0),
exifLength(0),
iccLength(0) {}
};
class MetadataWorker : public NanAsyncWorker {
/*
Delete input char[] buffer and notify V8 of memory deallocation
Used as the callback function for the "postclose" signal
*/
static void DeleteBuffer(VipsObject *object, char *buffer) {
if (buffer != NULL) {
delete[] buffer;
}
}
class MetadataWorker : public AsyncWorker {
public:
MetadataWorker(NanCallback *callback, MetadataBaton *baton) : NanAsyncWorker(callback), baton(baton) {}
MetadataWorker(Callback *callback, MetadataBaton *baton) : AsyncWorker(callback), baton(baton) {}
~MetadataWorker() {}
void Execute() {
@@ -57,17 +86,22 @@ class MetadataWorker : public NanAsyncWorker {
ImageType imageType = ImageType::UNKNOWN;
VipsImage *image = NULL;
if (baton->bufferInLength > 1) {
if (baton->bufferInLength > 0) {
// From buffer
imageType = DetermineImageType(baton->bufferIn, baton->bufferInLength);
if (imageType != ImageType::UNKNOWN) {
image = InitImage(baton->bufferIn, baton->bufferInLength, VIPS_ACCESS_RANDOM);
if (image == NULL) {
if (image != NULL) {
// Listen for "postclose" signal to delete input buffer
g_signal_connect(image, "postclose", G_CALLBACK(DeleteBuffer), baton->bufferIn);
} else {
(baton->err).append("Input buffer has corrupt header");
imageType = ImageType::UNKNOWN;
DeleteBuffer(NULL, baton->bufferIn);
}
} else {
(baton->err).append("Input buffer contains unsupported image format");
DeleteBuffer(NULL, baton->bufferIn);
}
} else {
// From file
@@ -102,6 +136,26 @@ class MetadataWorker : public NanAsyncWorker {
// Derived attributes
baton->hasAlpha = HasAlpha(image);
baton->orientation = ExifOrientation(image);
// EXIF
if (vips_image_get_typeof(image, VIPS_META_EXIF_NAME) == VIPS_TYPE_BLOB) {
void* exif;
size_t exifLength;
if (!vips_image_get_blob(image, VIPS_META_EXIF_NAME, &exif, &exifLength)) {
baton->exifLength = exifLength;
baton->exif = static_cast<char*>(malloc(exifLength));
memcpy(baton->exif, exif, exifLength);
}
}
// ICC profile
if (vips_image_get_typeof(image, VIPS_META_ICC_NAME) == VIPS_TYPE_BLOB) {
void* icc;
size_t iccLength;
if (!vips_image_get_blob(image, VIPS_META_ICC_NAME, &icc, &iccLength)) {
baton->iccLength = iccLength;
baton->icc = static_cast<char*>(malloc(iccLength));
memcpy(baton->icc, icc, iccLength);
}
}
// Drop image reference
g_object_unref(image);
}
@@ -111,24 +165,30 @@ class MetadataWorker : public NanAsyncWorker {
}
void HandleOKCallback () {
NanScope();
HandleScope();
Handle<Value> argv[2] = { NanNull(), NanNull() };
Local<Value> argv[2] = { Null(), Null() };
if (!baton->err.empty()) {
// Error
argv[0] = Exception::Error(NanNew<String>(baton->err.data(), baton->err.size()));
argv[0] = Error(baton->err.c_str());
} 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));
Local<Object> info = New<Object>();
Set(info, New("format").ToLocalChecked(), New<String>(baton->format).ToLocalChecked());
Set(info, New("width").ToLocalChecked(), New<Number>(baton->width));
Set(info, New("height").ToLocalChecked(), New<Number>(baton->height));
Set(info, New("space").ToLocalChecked(), New<String>(baton->space).ToLocalChecked());
Set(info, New("channels").ToLocalChecked(), New<Number>(baton->channels));
Set(info, New("hasProfile").ToLocalChecked(), New<Boolean>(baton->hasProfile));
Set(info, New("hasAlpha").ToLocalChecked(), New<Boolean>(baton->hasAlpha));
if (baton->orientation > 0) {
info->Set(NanNew<String>("orientation"), NanNew<Number>(baton->orientation));
Set(info, New("orientation").ToLocalChecked(), New<Number>(baton->orientation));
}
if (baton->exifLength > 0) {
Set(info, New("exif").ToLocalChecked(), NewBuffer(baton->exif, baton->exifLength).ToLocalChecked());
}
if (baton->iccLength > 0) {
Set(info, New("icc").ToLocalChecked(), NewBuffer(baton->icc, baton->iccLength).ToLocalChecked());
}
argv[1] = info;
}
@@ -146,27 +206,27 @@ class MetadataWorker : public NanAsyncWorker {
metadata(options, callback)
*/
NAN_METHOD(metadata) {
NanScope();
HandleScope();
// V8 objects are converted to non-V8 types held in the baton struct
MetadataBaton *baton = new MetadataBaton;
Local<Object> options = args[0]->ToObject();
Local<Object> options = info[0].As<Object>();
// Input filename
baton->fileIn = *String::Utf8Value(options->Get(NanNew<String>("fileIn"))->ToString());
baton->fileIn = *Utf8String(Get(options, New("fileIn").ToLocalChecked()).ToLocalChecked());
// Input Buffer object
if (options->Get(NanNew<String>("bufferIn"))->IsObject()) {
Local<Object> buffer = options->Get(NanNew<String>("bufferIn"))->ToObject();
if (node::Buffer::HasInstance(Get(options, New("bufferIn").ToLocalChecked()).ToLocalChecked())) {
Local<Object> buffer = Get(options, New("bufferIn").ToLocalChecked()).ToLocalChecked().As<Object>();
// Take a copy of the input Buffer to avoid problems with V8 heap compaction
baton->bufferInLength = node::Buffer::Length(buffer);
baton->bufferIn = node::Buffer::Data(buffer);
baton->bufferIn = new char[baton->bufferInLength];
memcpy(baton->bufferIn, node::Buffer::Data(buffer), baton->bufferInLength);
}
// Join queue for worker thread
NanCallback *callback = new NanCallback(args[1].As<v8::Function>());
NanAsyncQueueWorker(new MetadataWorker(callback, baton));
Callback *callback = new Callback(info[1].As<Function>());
AsyncQueueWorker(new MetadataWorker(callback, baton));
// Increment queued task counter
g_atomic_int_inc(&counterQueue);
NanReturnUndefined();
}

333
src/operations.cc Executable file
View File

@@ -0,0 +1,333 @@
#include <vips/vips.h>
#include "common.h"
#include "operations.h"
namespace sharp {
/*
Alpha composite src over dst
Assumes alpha channels are already premultiplied and will be unpremultiplied after
*/
int Composite(VipsObject *context, VipsImage *src, VipsImage *dst, VipsImage **out) {
using sharp::HasAlpha;
// Split src into non-alpha and alpha
VipsImage *srcWithoutAlpha;
if (vips_extract_band(src, &srcWithoutAlpha, 0, "n", src->Bands - 1, NULL))
return -1;
vips_object_local(context, srcWithoutAlpha);
VipsImage *srcAlpha;
if (vips_extract_band(src, &srcAlpha, src->Bands - 1, "n", 1, NULL))
return -1;
vips_object_local(context, srcAlpha);
// Split dst into non-alpha and alpha channels
VipsImage *dstWithoutAlpha;
VipsImage *dstAlpha;
if (HasAlpha(dst)) {
// Non-alpha: extract all-but-last channel
if (vips_extract_band(dst, &dstWithoutAlpha, 0, "n", dst->Bands - 1, NULL)) {
return -1;
}
vips_object_local(context, dstWithoutAlpha);
// Alpha: Extract last channel
if (vips_extract_band(dst, &dstAlpha, dst->Bands - 1, "n", 1, NULL)) {
return -1;
}
vips_object_local(context, dstAlpha);
} else {
// Non-alpha: Copy reference
dstWithoutAlpha = dst;
// Alpha: Use blank, opaque (0xFF) image
VipsImage *black;
if (vips_black(&black, dst->Xsize, dst->Ysize, NULL)) {
return -1;
}
vips_object_local(context, black);
if (vips_invert(black, &dstAlpha, NULL)) {
return -1;
}
vips_object_local(context, dstAlpha);
}
// Compute normalized input alpha channels:
VipsImage *srcAlphaNormalized;
if (vips_linear1(srcAlpha, &srcAlphaNormalized, 1.0 / 255.0, 0.0, NULL))
return -1;
vips_object_local(context, srcAlphaNormalized);
VipsImage *dstAlphaNormalized;
if (vips_linear1(dstAlpha, &dstAlphaNormalized, 1.0 / 255.0, 0.0, NULL))
return -1;
vips_object_local(context, dstAlphaNormalized);
//
// Compute normalized output alpha channel:
//
// References:
// - http://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending
// - https://github.com/jcupitt/ruby-vips/issues/28#issuecomment-9014826
//
// out_a = src_a + dst_a * (1 - src_a)
// ^^^^^^^^^^^
// t0
// ^^^^^^^^^^^^^^^^^^^
// t1
VipsImage *t0;
if (vips_linear1(srcAlphaNormalized, &t0, -1.0, 1.0, NULL))
return -1;
vips_object_local(context, t0);
VipsImage *t1;
if (vips_multiply(dstAlphaNormalized, t0, &t1, NULL))
return -1;
vips_object_local(context, t1);
VipsImage *outAlphaNormalized;
if (vips_add(srcAlphaNormalized, t1, &outAlphaNormalized, NULL))
return -1;
vips_object_local(context, outAlphaNormalized);
//
// Compute output RGB channels:
//
// Wikipedia:
// out_rgb = (src_rgb * src_a + dst_rgb * dst_a * (1 - src_a)) / out_a
// ^^^^^^^^^^^
// t0
//
// Omit division by `out_a` since `Compose` is supposed to output a
// premultiplied RGBA image as reversal of premultiplication is handled
// externally.
//
VipsImage *t2;
if (vips_multiply(dstWithoutAlpha, t0, &t2, NULL))
return -1;
vips_object_local(context, t2);
VipsImage *outRGBPremultiplied;
if (vips_add(srcWithoutAlpha, t2, &outRGBPremultiplied, NULL))
return -1;
vips_object_local(context, outRGBPremultiplied);
// Denormalize output alpha channel:
VipsImage *outAlpha;
if (vips_linear1(outAlphaNormalized, &outAlpha, 255.0, 0.0, NULL))
return -1;
vips_object_local(context, outAlpha);
// Combine RGB and alpha channel into output image:
return vips_bandjoin2(outRGBPremultiplied, outAlpha, out, NULL);
}
/*
* Premultiply alpha channel of `image`.
*/
int Premultiply(VipsObject *context, VipsImage *image, VipsImage **out) {
#if (VIPS_MAJOR_VERSION >= 9 || (VIPS_MAJOR_VERSION >= 8 && VIPS_MINOR_VERSION >= 1))
return vips_premultiply(image, out, NULL);
#else
VipsImage *imageRGB;
if (vips_extract_band(image, &imageRGB, 0, "n", image->Bands - 1, NULL))
return -1;
vips_object_local(context, imageRGB);
VipsImage *imageAlpha;
if (vips_extract_band(image, &imageAlpha, image->Bands - 1, "n", 1, NULL))
return -1;
vips_object_local(context, imageAlpha);
VipsImage *imageAlphaNormalized;
if (vips_linear1(imageAlpha, &imageAlphaNormalized, 1.0 / 255.0, 0.0, NULL))
return -1;
vips_object_local(context, imageAlphaNormalized);
VipsImage *imageRGBPremultiplied;
if (vips_multiply(imageRGB, imageAlphaNormalized, &imageRGBPremultiplied, NULL))
return -1;
vips_object_local(context, imageRGBPremultiplied);
return vips_bandjoin2(imageRGBPremultiplied, imageAlpha, out, NULL);
#endif
}
/*
* Unpremultiply alpha channel of `image`.
*/
int Unpremultiply(VipsObject *context, VipsImage *image, VipsImage **out) {
#if (VIPS_MAJOR_VERSION >= 9 || (VIPS_MAJOR_VERSION >= 8 && VIPS_MINOR_VERSION >= 1))
return vips_unpremultiply(image, out, NULL);
#else
VipsImage *imageRGBPremultipliedTransformed;
if (vips_extract_band(image, &imageRGBPremultipliedTransformed, 0, "n", image->Bands - 1, NULL))
return -1;
vips_object_local(context, imageRGBPremultipliedTransformed);
VipsImage *imageAlphaTransformed;
if (vips_extract_band(image, &imageAlphaTransformed, image->Bands - 1, "n", 1, NULL))
return -1;
vips_object_local(context, imageAlphaTransformed);
VipsImage *imageAlphaNormalizedTransformed;
if (vips_linear1(imageAlphaTransformed, &imageAlphaNormalizedTransformed, 1.0 / 255.0, 0.0, NULL))
return -1;
vips_object_local(context, imageAlphaNormalizedTransformed);
VipsImage *imageRGBUnpremultipliedTransformed;
if (vips_divide(imageRGBPremultipliedTransformed, imageAlphaNormalizedTransformed, &imageRGBUnpremultipliedTransformed, NULL))
return -1;
vips_object_local(context, imageRGBUnpremultipliedTransformed);
return vips_bandjoin2(imageRGBUnpremultipliedTransformed, imageAlphaTransformed, out, NULL);
#endif
}
/*
* Stretch luminance to cover full dynamic range.
*/
int Normalize(VipsObject *context, VipsImage *image, VipsImage **out) {
#ifndef _WIN32
// Get original colourspace
VipsInterpretation typeBeforeNormalize = image->Type;
if (typeBeforeNormalize == VIPS_INTERPRETATION_RGB) {
typeBeforeNormalize = VIPS_INTERPRETATION_sRGB;
}
// Convert to LAB colourspace
VipsImage *lab;
if (vips_colourspace(image, &lab, VIPS_INTERPRETATION_LAB, NULL)) {
return -1;
}
vips_object_local(context, lab);
// Extract luminance
VipsImage *luminance;
if (vips_extract_band(lab, &luminance, 0, "n", 1, NULL)) {
return -1;
}
vips_object_local(context, luminance);
// Extract chroma
VipsImage *chroma;
if (vips_extract_band(lab, &chroma, 1, "n", 2, NULL)) {
return -1;
}
vips_object_local(context, chroma);
// Find luminance range
VipsImage *stats;
if (vips_stats(luminance, &stats, NULL)) {
return -1;
}
vips_object_local(context, stats);
double min = *VIPS_MATRIX(stats, 0, 0);
double max = *VIPS_MATRIX(stats, 1, 0);
if (min != max) {
double f = 100.0 / (max - min);
double a = -(min * f);
// Scale luminance
VipsImage *luminance100;
if (vips_linear1(luminance, &luminance100, f, a, NULL)) {
return -1;
}
vips_object_local(context, luminance100);
// Join scaled luminance to chroma
VipsImage *normalizedLab;
if (vips_bandjoin2(luminance100, chroma, &normalizedLab, NULL)) {
return -1;
}
vips_object_local(context, normalizedLab);
// Convert to original colourspace
VipsImage *normalized;
if (vips_colourspace(normalizedLab, &normalized, typeBeforeNormalize, NULL)) {
return -1;
}
vips_object_local(context, normalized);
// Attach original alpha channel, if any
if (HasAlpha(image)) {
// Extract original alpha channel
VipsImage *alpha;
if (vips_extract_band(image, &alpha, image->Bands - 1, "n", 1, NULL)) {
return -1;
}
vips_object_local(context, alpha);
// Join alpha channel to normalised image
VipsImage *normalizedAlpha;
if (vips_bandjoin2(normalized, alpha, &normalizedAlpha, NULL)) {
return -1;
}
vips_object_local(context, normalizedAlpha);
*out = normalizedAlpha;
} else {
*out = normalized;
}
} else {
// Cannot normalise zero-range image
*out = image;
}
#else
// The normalize operation is currently unsupported on Windows
// See https://github.com/lovell/sharp/issues/152
*out = image;
#endif
return 0;
}
/*
* Gaussian blur (use sigma <0 for fast blur)
*/
int Blur(VipsObject *context, VipsImage *image, VipsImage **out, double sigma) {
VipsImage *blurred;
if (sigma < 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(context, blur);
if (vips_conv(image, &blurred, blur, NULL)) {
return -1;
}
} else {
// Slower, accurate Gaussian blur
// Create Gaussian function for standard deviation
VipsImage *gaussian;
if (vips_gaussmat(&gaussian, sigma, 0.2, "separable", TRUE, "integer", TRUE, NULL)) {
return -1;
}
vips_object_local(context, gaussian);
// Apply Gaussian function
if (vips_convsep(image, &blurred, gaussian, "precision", VIPS_PRECISION_INTEGER, NULL)) {
return -1;
}
}
vips_object_local(context, blurred);
*out = blurred;
return 0;
}
/*
* Sharpen flat and jagged areas. Use radius of -1 for fast sharpen.
*/
int Sharpen(VipsObject *context, VipsImage *image, VipsImage **out, int radius, double flat, double jagged) {
VipsImage *sharpened;
if (radius == -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(context, sharpen);
if (vips_conv(image, &sharpened, sharpen, NULL)) {
return -1;
}
} else {
// Slow, accurate sharpen in LAB colour space, with control over flat vs jagged areas
if (vips_sharpen(image, &sharpened, "radius", radius, "m1", flat, "m2", jagged, NULL)) {
return -1;
}
}
vips_object_local(context, sharpened);
*out = sharpened;
return 0;
}
} // namespace sharp

39
src/operations.h Executable file
View File

@@ -0,0 +1,39 @@
#ifndef SRC_OPERATIONS_H_
#define SRC_OPERATIONS_H_
namespace sharp {
/*
Composite images `src` and `dst` with premultiplied alpha channel and output
image with premultiplied alpha.
*/
int Composite(VipsObject *context, VipsImage *src, VipsImage *dst, VipsImage **out);
/*
* Premultiply alpha channel of `image`.
*/
int Premultiply(VipsObject *context, VipsImage *image, VipsImage **out);
/*
* Unpremultiply alpha channel of `image`.
*/
int Unpremultiply(VipsObject *context, VipsImage *image, VipsImage **out);
/*
* Stretch luminance to cover full dynamic range.
*/
int Normalize(VipsObject *context, VipsImage *image, VipsImage **out);
/*
* Gaussian blur. Use sigma of -1 for fast blur.
*/
int Blur(VipsObject *context, VipsImage *image, VipsImage **out, double sigma);
/*
* Sharpen flat and jagged areas. Use radius of -1 for fast sharpen.
*/
int Sharpen(VipsObject *context, VipsImage *image, VipsImage **out, int radius, double flat, double jagged);
} // namespace sharp
#endif // SRC_OPERATIONS_H_

View File

@@ -8,7 +8,8 @@
#include "nan.h"
#include "common.h"
#include "resize.h"
#include "operations.h"
#include "pipeline.h"
using v8::Handle;
using v8::Local;
@@ -21,6 +22,27 @@ using v8::Array;
using v8::Function;
using v8::Exception;
using Nan::AsyncQueueWorker;
using Nan::AsyncWorker;
using Nan::Callback;
using Nan::HandleScope;
using Nan::Utf8String;
using Nan::Has;
using Nan::Get;
using Nan::Set;
using Nan::To;
using Nan::New;
using Nan::CopyBuffer;
using Nan::Null;
using Nan::Equals;
using sharp::Composite;
using sharp::Premultiply;
using sharp::Unpremultiply;
using sharp::Normalize;
using sharp::Blur;
using sharp::Sharpen;
using sharp::ImageType;
using sharp::DetermineImageType;
using sharp::InitImage;
@@ -28,6 +50,8 @@ using sharp::InterpolatorWindowSize;
using sharp::HasProfile;
using sharp::HasAlpha;
using sharp::ExifOrientation;
using sharp::SetExifOrientation;
using sharp::RemoveExifOrientation;
using sharp::IsJpeg;
using sharp::IsPng;
using sharp::IsWebp;
@@ -52,7 +76,7 @@ enum class Angle {
DLAST
};
struct ResizeBaton {
struct PipelineBaton {
std::string fileIn;
char *bufferIn;
size_t bufferInLength;
@@ -81,6 +105,7 @@ struct ResizeBaton {
int sharpenRadius;
double sharpenFlat;
double sharpenJagged;
std::string overlayPath;
double gamma;
bool greyscale;
bool normalize;
@@ -100,10 +125,11 @@ struct ResizeBaton {
bool optimiseScans;
std::string err;
bool withMetadata;
int withMetadataOrientation;
int tileSize;
int tileOverlap;
ResizeBaton():
PipelineBaton():
bufferInLength(0),
limitInputPixels(0),
outputFormat(""),
@@ -133,6 +159,7 @@ struct ResizeBaton {
overshootDeringing(false),
optimiseScans(false),
withMetadata(false),
withMetadataOrientation(-1),
tileSize(256),
tileOverlap(0) {
background[0] = 0.0;
@@ -152,12 +179,12 @@ static void DeleteBuffer(VipsObject *object, char *buffer) {
}
}
class ResizeWorker : public NanAsyncWorker {
class PipelineWorker : public AsyncWorker {
public:
ResizeWorker(NanCallback *callback, ResizeBaton *baton, NanCallback *queueListener) :
NanAsyncWorker(callback), baton(baton), queueListener(queueListener) {}
~ResizeWorker() {}
PipelineWorker(Callback *callback, PipelineBaton *baton, Callback *queueListener) :
AsyncWorker(callback), baton(baton), queueListener(queueListener) {}
~PipelineWorker() {}
/*
libuv worker
@@ -178,7 +205,7 @@ class ResizeWorker : public NanAsyncWorker {
// Input
ImageType inputImageType = ImageType::UNKNOWN;
VipsImage *image = NULL;
if (baton->bufferInLength > 1) {
if (baton->bufferInLength > 0) {
// From buffer
inputImageType = DetermineImageType(baton->bufferIn, baton->bufferInLength);
if (inputImageType != ImageType::UNKNOWN) {
@@ -223,11 +250,16 @@ class ResizeWorker : public NanAsyncWorker {
// Calculate angle of rotation
Angle rotation;
bool flip;
std::tie(rotation, flip) = CalculateRotationAndFlip(baton->angle, image);
bool flop;
std::tie(rotation, flip, flop) = CalculateRotationAndFlip(baton->angle, image);
if (flip && !baton->flip) {
// Add flip operation due to EXIF mirroring
baton->flip = TRUE;
}
if (flop && !baton->flop) {
// Add flip operation due to EXIF mirroring
baton->flop = TRUE;
}
// Rotate pre-extract
if (baton->rotateBeforePreExtract && rotation != Angle::D0) {
@@ -237,6 +269,7 @@ class ResizeWorker : public NanAsyncWorker {
}
vips_object_local(hook, rotated);
image = rotated;
RemoveExifOrientation(image);
}
// Pre extraction
@@ -311,7 +344,7 @@ class ResizeWorker : public NanAsyncWorker {
} else {
// Auto height
yfactor = xfactor;
baton->height = static_cast<int>(floor(static_cast<double>(inputHeight) / yfactor));
baton->height = static_cast<int>(round(static_cast<double>(inputHeight) / yfactor));
}
} else if (baton->height > 0) {
// Fixed height
@@ -321,7 +354,7 @@ class ResizeWorker : public NanAsyncWorker {
} else {
// Auto width
xfactor = yfactor;
baton->width = static_cast<int>(floor(static_cast<double>(inputWidth) / xfactor));
baton->width = static_cast<int>(round(static_cast<double>(inputWidth) / xfactor));
}
} else {
// Identity transform
@@ -431,7 +464,7 @@ class ResizeWorker : public NanAsyncWorker {
}
// Gamma encoding (darken)
if (baton->gamma >= 1 && baton->gamma <= 3) {
if (baton->gamma >= 1 && baton->gamma <= 3 && !HasAlpha(image)) {
VipsImage *gammaEncoded;
if (vips_gamma(image, &gammaEncoded, "exponent", 1.0 / baton->gamma, NULL)) {
return Error();
@@ -478,8 +511,27 @@ class ResizeWorker : public NanAsyncWorker {
}
}
bool shouldAffineTransform = xresidual != 0.0 || yresidual != 0.0;
bool shouldBlur = baton->blurSigma != 0.0;
bool shouldSharpen = baton->sharpenRadius != 0;
bool hasOverlay = !baton->overlayPath.empty();
bool shouldPremultiplyAlpha = HasAlpha(image) && (shouldAffineTransform || shouldBlur || shouldSharpen || hasOverlay);
// Premultiply image alpha channel before all transformations to avoid
// dark fringing around bright pixels
// See: http://entropymine.com/imageworsener/resizealpha/
if (shouldPremultiplyAlpha) {
VipsImage *imagePremultiplied;
if (Premultiply(hook, image, &imagePremultiplied)) {
(baton->err).append("Failed to premultiply alpha channel.");
return Error();
}
vips_object_local(hook, imagePremultiplied);
image = imagePremultiplied;
}
// Use vips_affine with the remaining float part
if (xresidual != 0.0 || yresidual != 0.0) {
if (shouldAffineTransform) {
// Use average of x and y residuals to compute sigma for Gaussian blur
double residual = (xresidual + yresidual) / 2.0;
// Apply Gaussian blur before large affine reductions
@@ -534,6 +586,7 @@ class ResizeWorker : public NanAsyncWorker {
}
vips_object_local(hook, rotated);
image = rotated;
RemoveExifOrientation(image);
}
// Flip (mirror about Y axis)
@@ -544,6 +597,7 @@ class ResizeWorker : public NanAsyncWorker {
}
vips_object_local(hook, flipped);
image = flipped;
RemoveExifOrientation(image);
}
// Flop (mirror about X axis)
@@ -554,6 +608,7 @@ class ResizeWorker : public NanAsyncWorker {
}
vips_object_local(hook, flopped);
image = flopped;
RemoveExifOrientation(image);
}
// Crop/embed
@@ -644,62 +699,97 @@ class ResizeWorker : public NanAsyncWorker {
}
// Blur
if (baton->blurSigma != 0.0) {
if (shouldBlur) {
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();
}
} 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();
}
vips_object_local(hook, gaussian);
// Apply Gaussian function
if (vips_convsep(image, &blurred, gaussian, "precision", VIPS_PRECISION_INTEGER, NULL)) {
return Error();
}
if (Blur(hook, image, &blurred, baton->blurSigma)) {
return Error();
}
vips_object_local(hook, blurred);
image = blurred;
}
// Sharpen
if (baton->sharpenRadius != 0) {
if (shouldSharpen) {
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();
}
} 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();
}
if (Sharpen(hook, image, &sharpened, baton->sharpenRadius, baton->sharpenFlat, baton->sharpenJagged)) {
return Error();
}
vips_object_local(hook, sharpened);
image = sharpened;
}
// Composite with overlay, if present
if (hasOverlay) {
VipsImage *overlayImage = NULL;
ImageType overlayImageType = ImageType::UNKNOWN;
overlayImageType = DetermineImageType(baton->overlayPath.c_str());
if (overlayImageType != ImageType::UNKNOWN) {
overlayImage = InitImage(baton->overlayPath.c_str(), baton->accessMethod);
if (overlayImage == NULL) {
(baton->err).append("Overlay image has corrupt header");
return Error();
} else {
vips_object_local(hook, overlayImage);
}
} else {
(baton->err).append("Overlay image is of an unsupported image format");
return Error();
}
if (image->BandFmt != VIPS_FORMAT_UCHAR && image->BandFmt != VIPS_FORMAT_FLOAT) {
(baton->err).append("Expected image band format to be uchar or float: ");
(baton->err).append(vips_enum_nick(VIPS_TYPE_BAND_FORMAT, image->BandFmt));
return Error();
}
if (overlayImage->BandFmt != VIPS_FORMAT_UCHAR && overlayImage->BandFmt != VIPS_FORMAT_FLOAT) {
(baton->err).append("Expected overlay image band format to be uchar or float: ");
(baton->err).append(vips_enum_nick(VIPS_TYPE_BAND_FORMAT, overlayImage->BandFmt));
return Error();
}
if (!HasAlpha(overlayImage)) {
(baton->err).append("Overlay image must have an alpha channel");
return Error();
}
if (overlayImage->Xsize != image->Xsize && overlayImage->Ysize != image->Ysize) {
(baton->err).append("Overlay image must have same dimensions as resized image");
return Error();
}
// Ensure overlay is sRGB
VipsImage *overlayImageRGB;
if (vips_colourspace(overlayImage, &overlayImageRGB, VIPS_INTERPRETATION_sRGB, NULL)) {
return Error();
}
vips_object_local(hook, overlayImageRGB);
// Premultiply overlay
VipsImage *overlayImagePremultiplied;
if (Premultiply(hook, overlayImageRGB, &overlayImagePremultiplied)) {
(baton->err).append("Failed to premultiply alpha channel of overlay image.");
return Error();
}
vips_object_local(hook, overlayImagePremultiplied);
VipsImage *composited;
if (Composite(hook, overlayImagePremultiplied, image, &composited)) {
(baton->err).append("Failed to composite images");
return Error();
}
vips_object_local(hook, composited);
image = composited;
}
// Reverse premultiplication after all transformations:
if (shouldPremultiplyAlpha) {
VipsImage *imageUnpremultiplied;
if (Unpremultiply(hook, image, &imageUnpremultiplied)) {
(baton->err).append("Failed to unpremultiply alpha channel.");
return Error();
}
vips_object_local(hook, imageUnpremultiplied);
image = imageUnpremultiplied;
}
// Gamma decoding (brighten)
if (baton->gamma >= 1 && baton->gamma <= 3) {
if (baton->gamma >= 1 && baton->gamma <= 3 && !HasAlpha(image)) {
VipsImage *gammaDecoded;
if (vips_gamma(image, &gammaDecoded, "exponent", baton->gamma, NULL)) {
return Error();
@@ -708,98 +798,25 @@ class ResizeWorker : public NanAsyncWorker {
image = gammaDecoded;
}
#ifndef _WIN32
// Apply normalization
// Apply normalization - stretch luminance to cover full dynamic range
if (baton->normalize) {
VipsInterpretation typeBeforeNormalize = image->Type;
if (typeBeforeNormalize == VIPS_INTERPRETATION_RGB) {
typeBeforeNormalize = VIPS_INTERPRETATION_sRGB;
}
// normalize the luminance band in LAB space:
VipsImage *lab;
if (vips_colourspace(image, &lab, VIPS_INTERPRETATION_LAB, NULL)) {
return Error();
}
vips_object_local(hook, lab);
VipsImage *luminance;
if (vips_extract_band(lab, &luminance, 0, "n", 1, NULL)) {
return Error();
}
vips_object_local(hook, luminance);
VipsImage *chroma;
if (vips_extract_band(lab, &chroma, 1, "n", 2, NULL)) {
return Error();
}
vips_object_local(hook, chroma);
VipsImage *stats;
if (vips_stats(luminance, &stats, NULL)) {
return Error();
}
vips_object_local(hook, stats);
double min = *VIPS_MATRIX(stats, 0, 0);
double max = *VIPS_MATRIX(stats, 1, 0);
VipsImage *normalized;
if (min == max) {
// Range of zero: create black image
if (vips_black(&normalized, image->Xsize, image->Ysize, "bands", 1, NULL )) {
return Error();
}
vips_object_local(hook, normalized);
} else {
double f = 100.0 / (max - min);
double a = -(min * f);
VipsImage *luminance100;
if (vips_linear1(luminance, &luminance100, f, a, NULL)) {
return Error();
}
vips_object_local(hook, luminance100);
VipsImage *normalizedLab;
if (vips_bandjoin2(luminance100, chroma, &normalizedLab, NULL)) {
return Error();
}
vips_object_local(hook, normalizedLab);
if (vips_colourspace(normalizedLab, &normalized, typeBeforeNormalize, NULL)) {
return Error();
}
vips_object_local(hook, normalized);
}
if (HasAlpha(image)) {
VipsImage *alpha;
if (vips_extract_band(image, &alpha, image->Bands - 1, "n", 1, NULL)) {
return Error();
}
vips_object_local(hook, alpha);
VipsImage *normalizedAlpha;
if (vips_bandjoin2(normalized, alpha, &normalizedAlpha, NULL)) {
return Error();
}
vips_object_local(hook, normalizedAlpha);
image = normalizedAlpha;
} else {
image = normalized;
if (Normalize(hook, image, &normalized)) {
return Error();
}
image = normalized;
}
#endif
// Convert image to sRGB, if not already
if (image->Type != VIPS_INTERPRETATION_sRGB) {
// Switch intrepretation to sRGB
// Switch interpretation to sRGB
VipsImage *rgb;
if (vips_colourspace(image, &rgb, VIPS_INTERPRETATION_sRGB, NULL)) {
return Error();
}
vips_object_local(hook, rgb);
image = rgb;
// Tranform colours from embedded profile to sRGB profile
// Transform 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)) {
@@ -810,6 +827,11 @@ class ResizeWorker : public NanAsyncWorker {
}
}
// Override EXIF Orientation tag
if (baton->withMetadata && baton->withMetadataOrientation != -1) {
SetExifOrientation(image, baton->withMetadataOrientation);
}
#if !(VIPS_MAJOR_VERSION >= 8 || (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) {
@@ -960,12 +982,12 @@ class ResizeWorker : public NanAsyncWorker {
}
void HandleOKCallback () {
NanScope();
HandleScope();
Handle<Value> argv[3] = { NanNull(), NanNull(), NanNull() };
Local<Value> argv[3] = { Null(), Null(), Null() };
if (!baton->err.empty()) {
// Error
argv[0] = Exception::Error(NanNew<String>(baton->err.data(), baton->err.size()));
argv[0] = Nan::Error(baton->err.c_str());
} else {
int width = baton->width;
int height = baton->height;
@@ -978,32 +1000,33 @@ class ResizeWorker : public NanAsyncWorker {
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<Uint32>(static_cast<uint32_t>(width)));
info->Set(NanNew<String>("height"), NanNew<Uint32>(static_cast<uint32_t>(height)));
Local<Object> info = New<Object>();
Set(info, New("format").ToLocalChecked(), New<String>(baton->outputFormat).ToLocalChecked());
Set(info, New("width").ToLocalChecked(), New<Uint32>(static_cast<uint32_t>(width)));
Set(info, New("height").ToLocalChecked(), New<Uint32>(static_cast<uint32_t>(height)));
if (baton->bufferOutLength > 0) {
// Copy data to new Buffer
argv[1] = NanNewBufferHandle(static_cast<char*>(baton->bufferOut), baton->bufferOutLength);
argv[1] = CopyBuffer(static_cast<char*>(baton->bufferOut), baton->bufferOutLength).ToLocalChecked();
// bufferOut was allocated via g_malloc
g_free(baton->bufferOut);
// Add buffer size to info
info->Set(NanNew<String>("size"), NanNew<Uint32>(static_cast<uint32_t>(baton->bufferOutLength)));
Set(info, New("size").ToLocalChecked(), New<Uint32>(static_cast<uint32_t>(baton->bufferOutLength)));
argv[2] = info;
} else {
// Add file size to info
GStatBuf st;
g_stat(baton->output.c_str(), &st);
info->Set(NanNew<String>("size"), NanNew<Uint32>(static_cast<uint32_t>(st.st_size)));
Set(info, New("size").ToLocalChecked(), New<Uint32>(static_cast<uint32_t>(st.st_size)));
argv[1] = info;
}
}
delete baton;
// to here
// Decrement processing task counter
g_atomic_int_dec_and_test(&counterProcess);
Handle<Value> queueLength[1] = { NanNew<Uint32>(counterQueue) };
Local<Value> queueLength[1] = { New<Uint32>(counterQueue) };
queueListener->Call(1, queueLength);
delete queueListener;
@@ -1012,8 +1035,8 @@ class ResizeWorker : public NanAsyncWorker {
}
private:
ResizeBaton *baton;
NanCallback *queueListener;
PipelineBaton *baton;
Callback *queueListener;
VipsObject *hook;
/*
@@ -1023,18 +1046,19 @@ class ResizeWorker : public NanAsyncWorker {
2. Use input image EXIF Orientation header - supports mirroring
3. Otherwise default to zero, i.e. no rotation
*/
std::tuple<Angle, bool>
std::tuple<Angle, bool, bool>
CalculateRotationAndFlip(int const angle, VipsImage const *input) {
Angle rotate = Angle::D0;
bool flip = FALSE;
bool flop = 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 2: flop = TRUE; break; // flop 1
case 7: flip = TRUE; rotate = Angle::D90; break; // flip 6
case 4: flip = TRUE; rotate = Angle::D180; break; // flip 3
case 4: flop = TRUE; rotate = Angle::D180; break; // flop 3
case 5: flip = TRUE; rotate = Angle::D270; break; // flip 8
}
} else {
@@ -1046,7 +1070,7 @@ class ResizeWorker : public NanAsyncWorker {
rotate = Angle::D270;
}
}
return std::make_tuple(rotate, flip);
return std::make_tuple(rotate, flip, flop);
}
/*
@@ -1072,6 +1096,16 @@ class ResizeWorker : public NanAsyncWorker {
case 4: // West
top = (inHeight - outHeight + 1) / 2;
break;
case 5: // Northeast
left = inWidth - outWidth;
break;
case 6: // Southeast
left = inWidth - outWidth;
top = inHeight - outHeight;
case 7: // Southwest
top = inHeight - outHeight;
case 8: // Northwest
break;
default: // Centre
left = (inWidth - outWidth + 1) / 2;
top = (inHeight - outHeight + 1) / 2;
@@ -1120,103 +1154,106 @@ class ResizeWorker : public NanAsyncWorker {
};
/*
resize(options, output, callback)
pipeline(options, output, callback)
*/
NAN_METHOD(resize) {
NanScope();
NAN_METHOD(pipeline) {
HandleScope();
// V8 objects are converted to non-V8 types held in the baton struct
ResizeBaton *baton = new ResizeBaton;
Local<Object> options = args[0]->ToObject();
PipelineBaton *baton = new PipelineBaton;
Local<Object> options = info[0].As<Object>();
// 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;
baton->fileIn = *Utf8String(Get(options, New("fileIn").ToLocalChecked()).ToLocalChecked());
baton->accessMethod =
To<bool>(Get(options, New("sequentialRead").ToLocalChecked()).ToLocalChecked()).FromJust() ?
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();
if (node::Buffer::HasInstance(Get(options, New("bufferIn").ToLocalChecked()).ToLocalChecked())) {
Local<Object> buffer = Get(options, New("bufferIn").ToLocalChecked()).ToLocalChecked().As<Object>();
// Take a copy of the input Buffer to avoid problems with V8 heap compaction
baton->bufferInLength = node::Buffer::Length(buffer);
baton->bufferIn = new char[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());
baton->iccProfilePath = *Utf8String(Get(options, New("iccProfilePath").ToLocalChecked()).ToLocalChecked());
// Limit input images to a given number of pixels, where pixels = width * height
baton->limitInputPixels = options->Get(NanNew<String>("limitInputPixels"))->Int32Value();
baton->limitInputPixels = To<int32_t>(Get(options, New("limitInputPixels").ToLocalChecked()).ToLocalChecked()).FromJust();
// 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();
baton->topOffsetPre = To<int32_t>(Get(options, New("topOffsetPre").ToLocalChecked()).ToLocalChecked()).FromJust();
baton->leftOffsetPre = To<int32_t>(Get(options, New("leftOffsetPre").ToLocalChecked()).ToLocalChecked()).FromJust();
baton->widthPre = To<int32_t>(Get(options, New("widthPre").ToLocalChecked()).ToLocalChecked()).FromJust();
baton->heightPre = To<int32_t>(Get(options, New("heightPre").ToLocalChecked()).ToLocalChecked()).FromJust();
baton->topOffsetPost = To<int32_t>(Get(options, New("topOffsetPost").ToLocalChecked()).ToLocalChecked()).FromJust();
baton->leftOffsetPost = To<int32_t>(Get(options, New("leftOffsetPost").ToLocalChecked()).ToLocalChecked()).FromJust();
baton->widthPost = To<int32_t>(Get(options, New("widthPost").ToLocalChecked()).ToLocalChecked()).FromJust();
baton->heightPost = To<int32_t>(Get(options, New("heightPost").ToLocalChecked()).ToLocalChecked()).FromJust();
// Output image dimensions
baton->width = options->Get(NanNew<String>("width"))->Int32Value();
baton->height = options->Get(NanNew<String>("height"))->Int32Value();
baton->width = To<int32_t>(Get(options, New("width").ToLocalChecked()).ToLocalChecked()).FromJust();
baton->height = To<int32_t>(Get(options, New("height").ToLocalChecked()).ToLocalChecked()).FromJust();
// Canvas option
Local<String> canvas = options->Get(NanNew<String>("canvas"))->ToString();
if (canvas->Equals(NanNew<String>("crop"))) {
Local<String> canvas = To<String>(Get(options, New("canvas").ToLocalChecked()).ToLocalChecked()).ToLocalChecked();
if (Equals(canvas, New("crop").ToLocalChecked()).FromJust()) {
baton->canvas = Canvas::CROP;
} else if (canvas->Equals(NanNew<String>("embed"))) {
} else if (Equals(canvas, New("embed").ToLocalChecked()).FromJust()) {
baton->canvas = Canvas::EMBED;
} else if (canvas->Equals(NanNew<String>("max"))) {
} else if (Equals(canvas, New("max").ToLocalChecked()).FromJust()) {
baton->canvas = Canvas::MAX;
} else if (canvas->Equals(NanNew<String>("min"))) {
} else if (Equals(canvas, New("min").ToLocalChecked()).FromJust()) {
baton->canvas = Canvas::MIN;
} else if (canvas->Equals(NanNew<String>("ignore_aspect"))) {
} else if (Equals(canvas, New("ignore_aspect").ToLocalChecked()).FromJust()) {
baton->canvas = Canvas::IGNORE_ASPECT;
}
// Background colour
Local<Array> background = Local<Array>::Cast(options->Get(NanNew<String>("background")));
Local<Object> background = Get(options, New("background").ToLocalChecked()).ToLocalChecked().As<Object>();
for (int i = 0; i < 4; i++) {
baton->background[i] = background->Get(i)->NumberValue();
baton->background[i] = To<int32_t>(Get(background, i).ToLocalChecked()).FromJust();
}
// Overlay options
baton->overlayPath = *Utf8String(Get(options, New("overlayPath").ToLocalChecked()).ToLocalChecked());
// 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());
baton->withoutEnlargement = To<bool>(Get(options, New("withoutEnlargement").ToLocalChecked()).ToLocalChecked()).FromJust();
baton->gravity = To<int32_t>(Get(options, New("gravity").ToLocalChecked()).ToLocalChecked()).FromJust();
baton->interpolator = *Utf8String(Get(options, New("interpolator").ToLocalChecked()).ToLocalChecked());
// 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->normalize = options->Get(NanNew<String>("normalize"))->BooleanValue();
baton->angle = options->Get(NanNew<String>("angle"))->Int32Value();
baton->rotateBeforePreExtract = options->Get(NanNew<String>("rotateBeforePreExtract"))->BooleanValue();
baton->flip = options->Get(NanNew<String>("flip"))->BooleanValue();
baton->flop = options->Get(NanNew<String>("flop"))->BooleanValue();
baton->flatten = To<bool>(Get(options, New("flatten").ToLocalChecked()).ToLocalChecked()).FromJust();
baton->blurSigma = To<double>(Get(options, New("blurSigma").ToLocalChecked()).ToLocalChecked()).FromJust();
baton->sharpenRadius = To<int32_t>(Get(options, New("sharpenRadius").ToLocalChecked()).ToLocalChecked()).FromJust();
baton->sharpenFlat = To<double>(Get(options, New("sharpenFlat").ToLocalChecked()).ToLocalChecked()).FromJust();
baton->sharpenJagged = To<double>(Get(options, New("sharpenJagged").ToLocalChecked()).ToLocalChecked()).FromJust();
baton->gamma = To<int32_t>(Get(options, New("gamma").ToLocalChecked()).ToLocalChecked()).FromJust();
baton->greyscale = To<bool>(Get(options, New("greyscale").ToLocalChecked()).ToLocalChecked()).FromJust();
baton->normalize = To<bool>(Get(options, New("normalize").ToLocalChecked()).ToLocalChecked()).FromJust();
baton->angle = To<int32_t>(Get(options, New("angle").ToLocalChecked()).ToLocalChecked()).FromJust();
baton->rotateBeforePreExtract = To<bool>(Get(options, New("rotateBeforePreExtract").ToLocalChecked()).ToLocalChecked()).FromJust();
baton->flip = To<bool>(Get(options, New("flip").ToLocalChecked()).ToLocalChecked()).FromJust();
baton->flop = To<bool>(Get(options, New("flop").ToLocalChecked()).ToLocalChecked()).FromJust();
// 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->withoutChromaSubsampling = options->Get(NanNew<String>("withoutChromaSubsampling"))->BooleanValue();
baton->trellisQuantisation = options->Get(NanNew<String>("trellisQuantisation"))->BooleanValue();
baton->overshootDeringing = options->Get(NanNew<String>("overshootDeringing"))->BooleanValue();
baton->optimiseScans = options->Get(NanNew<String>("optimiseScans"))->BooleanValue();
baton->withMetadata = options->Get(NanNew<String>("withMetadata"))->BooleanValue();
baton->progressive = To<bool>(Get(options, New("progressive").ToLocalChecked()).ToLocalChecked()).FromJust();
baton->quality = To<int32_t>(Get(options, New("quality").ToLocalChecked()).ToLocalChecked()).FromJust();
baton->compressionLevel = To<int32_t>(Get(options, New("compressionLevel").ToLocalChecked()).ToLocalChecked()).FromJust();
baton->withoutAdaptiveFiltering = To<bool>(Get(options, New("withoutAdaptiveFiltering").ToLocalChecked()).ToLocalChecked()).FromJust();
baton->withoutChromaSubsampling = To<bool>(Get(options, New("withoutChromaSubsampling").ToLocalChecked()).ToLocalChecked()).FromJust();
baton->trellisQuantisation = To<bool>(Get(options, New("trellisQuantisation").ToLocalChecked()).ToLocalChecked()).FromJust();
baton->overshootDeringing = To<bool>(Get(options, New("overshootDeringing").ToLocalChecked()).ToLocalChecked()).FromJust();
baton->optimiseScans = To<bool>(Get(options, New("optimiseScans").ToLocalChecked()).ToLocalChecked()).FromJust();
baton->withMetadata = To<bool>(Get(options, New("withMetadata").ToLocalChecked()).ToLocalChecked()).FromJust();
baton->withMetadataOrientation = To<int32_t>(Get(options, New("withMetadataOrientation").ToLocalChecked()).ToLocalChecked()).FromJust();
// Output filename or __format for Buffer
baton->output = *String::Utf8Value(options->Get(NanNew<String>("output"))->ToString());
baton->tileSize = options->Get(NanNew<String>("tileSize"))->Int32Value();
baton->tileOverlap = options->Get(NanNew<String>("tileOverlap"))->Int32Value();
baton->output = *Utf8String(Get(options, New("output").ToLocalChecked()).ToLocalChecked());
baton->tileSize = To<int32_t>(Get(options, New("tileSize").ToLocalChecked()).ToLocalChecked()).FromJust();
baton->tileOverlap = To<int32_t>(Get(options, New("tileOverlap").ToLocalChecked()).ToLocalChecked()).FromJust();
// Function to notify of queue length changes
NanCallback *queueListener = new NanCallback(Handle<Function>::Cast(options->Get(NanNew<String>("queueListener"))));
Callback *queueListener = new Callback(Get(options, New("queueListener").ToLocalChecked()).ToLocalChecked().As<Function>());
// Join queue for worker thread
NanCallback *callback = new NanCallback(args[1].As<Function>());
NanAsyncQueueWorker(new ResizeWorker(callback, baton, queueListener));
Callback *callback = new Callback(info[1].As<Function>());
AsyncQueueWorker(new PipelineWorker(callback, baton, queueListener));
// Increment queued task counter
g_atomic_int_inc(&counterQueue);
Handle<Value> queueLength[1] = { NanNew<Uint32>(counterQueue) };
Local<Value> queueLength[1] = { New<Uint32>(counterQueue) };
queueListener->Call(1, queueLength);
NanReturnUndefined();
}

8
src/pipeline.h Executable file
View File

@@ -0,0 +1,8 @@
#ifndef SRC_PIPELINE_H_
#define SRC_PIPELINE_H_
#include "nan.h"
NAN_METHOD(pipeline);
#endif // SRC_PIPELINE_H_

View File

@@ -1,8 +0,0 @@
#ifndef SRC_RESIZE_H_
#define SRC_RESIZE_H_
#include "nan.h"
NAN_METHOD(resize);
#endif // SRC_RESIZE_H_

View File

@@ -5,11 +5,10 @@
#include "common.h"
#include "metadata.h"
#include "resize.h"
#include "pipeline.h"
#include "utilities.h"
extern "C" void init(v8::Handle<v8::Object> target) {
NanScope();
NAN_MODULE_INIT(init) {
vips_init("sharp");
// Set libvips operation cache limits
@@ -17,13 +16,22 @@ extern "C" void init(v8::Handle<v8::Object> target) {
vips_cache_set_max(500); // 500 operations
// Methods available to JavaScript
NODE_SET_METHOD(target, "metadata", metadata);
NODE_SET_METHOD(target, "resize", resize);
NODE_SET_METHOD(target, "cache", cache);
NODE_SET_METHOD(target, "concurrency", concurrency);
NODE_SET_METHOD(target, "counters", counters);
NODE_SET_METHOD(target, "libvipsVersion", libvipsVersion);
NODE_SET_METHOD(target, "format", format);
Nan::Set(target, Nan::New("metadata").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(metadata)).ToLocalChecked());
Nan::Set(target, Nan::New("pipeline").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(pipeline)).ToLocalChecked());
Nan::Set(target, Nan::New("cache").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(cache)).ToLocalChecked());
Nan::Set(target, Nan::New("concurrency").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(concurrency)).ToLocalChecked());
Nan::Set(target, Nan::New("counters").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(counters)).ToLocalChecked());
Nan::Set(target, Nan::New("libvipsVersion").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(libvipsVersion)).ToLocalChecked());
Nan::Set(target, Nan::New("format").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(format)).ToLocalChecked());
Nan::Set(target, Nan::New("_maxColourDistance").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(_maxColourDistance)).ToLocalChecked());
}
NODE_MODULE(sharp, init)

View File

@@ -4,6 +4,7 @@
#include "nan.h"
#include "common.h"
#include "operations.h"
#include "utilities.h"
using v8::Local;
@@ -12,133 +13,246 @@ using v8::Number;
using v8::String;
using v8::Boolean;
using sharp::counterQueue;
using sharp::counterProcess;
using Nan::HandleScope;
using Nan::New;
using Nan::Set;
using Nan::ThrowError;
using Nan::To;
using Nan::Utf8String;
/*
Get and set cache memory and item limits
*/
NAN_METHOD(cache) {
NanScope();
HandleScope();
// 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);
if (info[0]->IsInt32()) {
vips_cache_set_max_mem(To<int32_t>(info[0]).FromJust() * 1048576);
}
// Set cache items limit
if (args[1]->IsInt32()) {
vips_cache_set_max(args[1]->Int32Value());
if (info[1]->IsInt32()) {
vips_cache_set_max(To<int32_t>(info[1]).FromJust());
}
// 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);
Local<Object> cache = New<Object>();
Set(cache, New("current").ToLocalChecked(), New<Number>(vips_tracked_get_mem() / 1048576));
Set(cache, New("high").ToLocalChecked(), New<Number>(vips_tracked_get_mem_highwater() / 1048576));
Set(cache, New("memory").ToLocalChecked(), New<Number>(vips_cache_get_max_mem() / 1048576));
Set(cache, New("items").ToLocalChecked(), New<Number>(vips_cache_get_max()));
info.GetReturnValue().Set(cache);
}
/*
Get and set size of thread pool
*/
NAN_METHOD(concurrency) {
NanScope();
HandleScope();
// Set concurrency
if (args[0]->IsInt32()) {
vips_concurrency_set(args[0]->Int32Value());
if (info[0]->IsInt32()) {
vips_concurrency_set(To<int32_t>(info[0]).FromJust());
}
// Get concurrency
NanReturnValue(NanNew<Number>(vips_concurrency_get()));
info.GetReturnValue().Set(New<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);
using sharp::counterProcess;
using sharp::counterQueue;
HandleScope();
Local<Object> counters = New<Object>();
Set(counters, New("queue").ToLocalChecked(), New<Number>(counterQueue));
Set(counters, New("process").ToLocalChecked(), New<Number>(counterProcess));
info.GetReturnValue().Set(counters);
}
/*
Get libvips version
*/
NAN_METHOD(libvipsVersion) {
NanScope();
HandleScope();
char version[9];
g_snprintf(version, sizeof(version), "%d.%d.%d", vips_version(0), vips_version(1), vips_version(2));
NanReturnValue(NanNew<String>(version));
info.GetReturnValue().Set(New(version).ToLocalChecked());
}
/*
Get available input/output file/buffer/stream formats
*/
NAN_METHOD(format) {
NanScope();
HandleScope();
// Attribute names
Local<String> attrId = NanNew<String>("id");
Local<String> attrInput = NanNew<String>("input");
Local<String> attrOutput = NanNew<String>("output");
Local<String> attrFile = NanNew<String>("file");
Local<String> attrBuffer = NanNew<String>("buffer");
Local<String> attrStream = NanNew<String>("stream");
Local<String> attrId = New("id").ToLocalChecked();
Local<String> attrInput = New("input").ToLocalChecked();
Local<String> attrOutput = New("output").ToLocalChecked();
Local<String> attrFile = New("file").ToLocalChecked();
Local<String> attrBuffer = New("buffer").ToLocalChecked();
Local<String> attrStream = New("stream").ToLocalChecked();
// Which load/save operations are available for each compressed format?
Local<Object> format = NanNew<Object>();
Local<Object> format = New<Object>();
for (std::string f : {"jpeg", "png", "webp", "tiff", "magick", "openslide", "dz"}) {
// Input
Local<Object> input = NanNew<Object>();
input->Set(attrFile, NanNew<Boolean>(
vips_type_find("VipsOperation", (f + "load").c_str())));
input->Set(attrBuffer, NanNew<Boolean>(
vips_type_find("VipsOperation", (f + "load_buffer").c_str())));
input->Set(attrStream, input->Get(attrBuffer));
Local<Boolean> hasInputFile =
New<Boolean>(vips_type_find("VipsOperation", (f + "load").c_str()));
Local<Boolean> hasInputBuffer =
New<Boolean>(vips_type_find("VipsOperation", (f + "load_buffer").c_str()));
Local<Object> input = New<Object>();
Set(input, attrFile, hasInputFile);
Set(input, attrBuffer, hasInputBuffer);
Set(input, attrStream, hasInputBuffer);
// Output
Local<Object> output = NanNew<Object>();
output->Set(attrFile, NanNew<Boolean>(
vips_type_find("VipsOperation", (f + "save").c_str())));
output->Set(attrBuffer, NanNew<Boolean>(
vips_type_find("VipsOperation", (f + "save_buffer").c_str())));
output->Set(attrStream, output->Get(attrBuffer));
Local<Boolean> hasOutputFile =
New<Boolean>(vips_type_find("VipsOperation", (f + "save").c_str()));
Local<Boolean> hasOutputBuffer =
New<Boolean>(vips_type_find("VipsOperation", (f + "save_buffer").c_str()));
Local<Object> output = New<Object>();
Set(output, attrFile, hasOutputFile);
Set(output, attrBuffer, hasOutputBuffer);
Set(output, attrStream, hasOutputBuffer);
// Other attributes
Local<Object> container = NanNew<Object>();
Local<String> formatId = NanNew<String>(f);
container->Set(attrId, formatId);
container->Set(attrInput, input);
container->Set(attrOutput, output);
Local<Object> container = New<Object>();
Local<String> formatId = New(f).ToLocalChecked();
Set(container, attrId, formatId);
Set(container, attrInput, input);
Set(container, attrOutput, output);
// Add to set of formats
format->Set(formatId, container);
Set(format, formatId, container);
}
// Raw, uncompressed data
Local<Object> raw = NanNew<Object>();
raw->Set(attrId, NanNew<String>("raw"));
format->Set(NanNew<String>("raw"), raw);
Local<Object> raw = New<Object>();
Local<String> rawId = New("raw").ToLocalChecked();
Set(raw, attrId, rawId);
Set(format, rawId, raw);
// No support for raw input yet, so always false
Local<Boolean> unsupported = NanNew<Boolean>(false);
Local<Object> rawInput = NanNew<Object>();
rawInput->Set(attrFile, unsupported);
rawInput->Set(attrBuffer, unsupported);
rawInput->Set(attrStream, unsupported);
raw->Set(attrInput, rawInput);
Local<Boolean> unsupported = New<Boolean>(false);
Local<Object> rawInput = New<Object>();
Set(rawInput, attrFile, unsupported);
Set(rawInput, attrBuffer, unsupported);
Set(rawInput, attrStream, unsupported);
Set(raw, attrInput, rawInput);
// Raw output via Buffer/Stream is available in libvips >= 7.42.0
Local<Boolean> supportsRawOutput = NanNew<Boolean>(vips_version(0) >= 8 || (vips_version(0) == 7 && vips_version(1) >= 42));
Local<Object> rawOutput = NanNew<Object>();
rawOutput->Set(attrFile, unsupported);
rawOutput->Set(attrBuffer, supportsRawOutput);
rawOutput->Set(attrStream, supportsRawOutput);
raw->Set(attrOutput, rawOutput);
Local<Boolean> hasOutputBufferRaw = New<Boolean>(vips_version(0) >= 8 || (vips_version(0) == 7 && vips_version(1) >= 42));
Local<Object> rawOutput = New<Object>();
Set(rawOutput, attrFile, unsupported);
Set(rawOutput, attrBuffer, hasOutputBufferRaw);
Set(rawOutput, attrStream, hasOutputBufferRaw);
Set(raw, attrOutput, rawOutput);
NanReturnValue(format);
info.GetReturnValue().Set(format);
}
/*
Synchronous, internal-only method used by some of the functional tests.
Calculates the maximum colour distance using the DE2000 algorithm
between two images of the same dimensions and number of channels.
*/
NAN_METHOD(_maxColourDistance) {
using sharp::Premultiply;
using sharp::DetermineImageType;
using sharp::ImageType;
using sharp::InitImage;
using sharp::HasAlpha;
HandleScope();
// Create "hook" VipsObject to hang image references from
VipsObject *hook = reinterpret_cast<VipsObject*>(vips_image_new());
// Open input files
VipsImage *image1 = NULL;
ImageType imageType1 = DetermineImageType(*Utf8String(info[0]));
if (imageType1 != ImageType::UNKNOWN) {
image1 = InitImage(*Utf8String(info[0]), VIPS_ACCESS_SEQUENTIAL);
if (image1 == NULL) {
g_object_unref(hook);
return ThrowError("Input file 1 has corrupt header");
} else {
vips_object_local(hook, image1);
}
} else {
g_object_unref(hook);
return ThrowError("Input file 1 is of an unsupported image format");
}
VipsImage *image2 = NULL;
ImageType imageType2 = DetermineImageType(*Utf8String(info[1]));
if (imageType2 != ImageType::UNKNOWN) {
image2 = InitImage(*Utf8String(info[1]), VIPS_ACCESS_SEQUENTIAL);
if (image2 == NULL) {
g_object_unref(hook);
return ThrowError("Input file 2 has corrupt header");
} else {
vips_object_local(hook, image2);
}
} else {
g_object_unref(hook);
return ThrowError("Input file 2 is of an unsupported image format");
}
// Ensure same number of channels
if (image1->Bands != image2->Bands) {
g_object_unref(hook);
return ThrowError("mismatchedBands");
}
// Ensure same dimensions
if (image1->Xsize != image2->Xsize || image1->Ysize != image2->Ysize) {
g_object_unref(hook);
return ThrowError("mismatchedDimensions");
}
// Premultiply and remove alpha
if (HasAlpha(image1)) {
VipsImage *imagePremultiplied1;
if (Premultiply(hook, image1, &imagePremultiplied1)) {
g_object_unref(hook);
return ThrowError(vips_error_buffer());
}
vips_object_local(hook, imagePremultiplied1);
VipsImage *imagePremultipliedNoAlpha1;
if (vips_extract_band(image1, &imagePremultipliedNoAlpha1, 1, "n", image1->Bands - 1, NULL)) {
g_object_unref(hook);
return ThrowError(vips_error_buffer());
}
vips_object_local(hook, imagePremultipliedNoAlpha1);
image1 = imagePremultipliedNoAlpha1;
}
if (HasAlpha(image2)) {
VipsImage *imagePremultiplied2;
if (Premultiply(hook, image2, &imagePremultiplied2)) {
g_object_unref(hook);
return ThrowError(vips_error_buffer());
}
vips_object_local(hook, imagePremultiplied2);
VipsImage *imagePremultipliedNoAlpha2;
if (vips_extract_band(image2, &imagePremultipliedNoAlpha2, 1, "n", image2->Bands - 1, NULL)) {
g_object_unref(hook);
return ThrowError(vips_error_buffer());
}
vips_object_local(hook, imagePremultipliedNoAlpha2);
image2 = imagePremultipliedNoAlpha2;
}
// Calculate colour distance
VipsImage *difference;
if (vips_dE00(image1, image2, &difference, NULL)) {
g_object_unref(hook);
return ThrowError(vips_error_buffer());
}
vips_object_local(hook, difference);
// Extract maximum distance
double maxColourDistance;
if (vips_max(difference, &maxColourDistance, NULL)) {
g_object_unref(hook);
return ThrowError(vips_error_buffer());
}
g_object_unref(hook);
info.GetReturnValue().Set(New<Number>(maxColourDistance));
}

View File

@@ -8,5 +8,6 @@ NAN_METHOD(concurrency);
NAN_METHOD(counters);
NAN_METHOD(libvipsVersion);
NAN_METHOD(format);
NAN_METHOD(_maxColourDistance);
#endif // SRC_UTILITIES_H_

14
test/bench/package.json Executable file → Normal file
View File

@@ -8,14 +8,16 @@
"test": "node perf && node random && node parallel"
},
"devDependencies": {
"async": "^1.4.2",
"benchmark": "^1.0.0",
"gm": "^1.20.0",
"imagemagick": "^0.1.3",
"imagemagick-native": "mash/node-imagemagick-native",
"gm": "^1.17.0",
"async": "^0.9.0",
"semver": "^4.3.0",
"benchmark": "^1.0.0"
"imagemagick-native": "^1.8.0",
"jimp": "^0.2.8",
"lwip": "^0.0.8",
"semver": "^5.0.3"
},
"license": "Apache 2.0",
"license": "Apache-2.0",
"engines": {
"node": ">=0.10"
}

View File

@@ -7,10 +7,23 @@ var assert = require('assert');
var Benchmark = require('benchmark');
var semver = require('semver');
var imagemagick = require('imagemagick');
var imagemagickNative = require('imagemagick-native');
// Contenders
var gm = require('gm');
var imagemagick = require('imagemagick');
var jimp = require('jimp');
var sharp = require('../../index');
var imagemagickNative;
try {
imagemagickNative = require('imagemagick-native');
} catch (err) {
console.log('Excluding imagemagick-native');
}
var lwip;
try {
lwip = require('lwip');
} catch (err) {
console.log('Excluding lwip');
}
var fixtures = require('../fixtures');
@@ -26,7 +39,96 @@ sharp.cache(0);
async.series({
jpeg: function(callback) {
var inputJpgBuffer = fs.readFileSync(fixtures.inputJpg);
(new Benchmark.Suite('jpeg')).add('imagemagick-file-file', {
var jpegSuite = new Benchmark.Suite('jpeg');
// jimp
jpegSuite.add('jimp-buffer-buffer', {
defer: true,
fn: function(deferred) {
new jimp(inputJpgBuffer, function(err) {
if (err) {
throw err;
} else {
this
.resize(width, height)
.quality(80)
.getBuffer(jimp.MIME_JPEG, function (err) {
if (err) {
throw err;
} else {
deferred.resolve();
}
});
}
});
}
}).add('jimp-file-file', {
defer: true,
fn: function(deferred) {
new jimp(fixtures.inputJpg, function(err) {
if (err) {
throw err;
} else {
this
.resize(width, height)
.quality(80)
.write(fixtures.outputJpg, function (err) {
if (err) {
throw err;
} else {
deferred.resolve();
}
});
}
});
}
});
// lwip
if (typeof lwip !== 'undefined') {
jpegSuite.add('lwip-file-file', {
defer: true,
fn: function(deferred) {
lwip.open(fixtures.inputJpg, function (err, image) {
if (err) {
throw err;
}
image.resize(width, height, 'linear', function (err, image) {
if (err) {
throw err;
}
image.writeFile(fixtures.outputJpg, {quality: 80}, function (err) {
if (err) {
throw err;
}
deferred.resolve();
});
});
});
}
}).add('lwip-buffer-buffer', {
defer: true,
fn: function(deferred) {
lwip.open(inputJpgBuffer, 'jpg', function (err, image) {
if (err) {
throw err;
}
image.resize(width, height, 'linear', function (err, image) {
if (err) {
throw err;
}
image.toBuffer('jpg', {quality: 80}, function (err, buffer) {
if (err) {
throw err;
}
assert.notStrictEqual(null, buffer);
deferred.resolve();
});
});
});
}
});
}
// imagemagick
jpegSuite.add('imagemagick-file-file', {
defer: true,
fn: function(deferred) {
imagemagick.resize({
@@ -45,26 +147,32 @@ async.series({
}
});
}
}).add('imagemagick-native-buffer-buffer', {
defer: true,
fn: function(deferred) {
imagemagickNative.convert({
srcData: inputJpgBuffer,
quality: 80,
width: width,
height: height,
format: 'JPEG',
filter: magickFilter
}, function (err, buffer) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
}).add('gm-buffer-file', {
});
// imagemagick-native
if (typeof imagemagickNative !== 'undefined') {
jpegSuite.add('imagemagick-native-buffer-buffer', {
defer: true,
fn: function(deferred) {
imagemagickNative.convert({
srcData: inputJpgBuffer,
quality: 80,
width: width,
height: height,
format: 'JPEG',
filter: magickFilter
}, function (err, buffer) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
});
}
// gm
jpegSuite.add('gm-buffer-file', {
defer: true,
fn: function(deferred) {
gm(inputJpgBuffer)
@@ -126,7 +234,9 @@ async.series({
}
});
}
}).add('sharp-buffer-file', {
});
// sharp
jpegSuite.add('sharp-buffer-file', {
defer: true,
fn: function(deferred) {
sharp(inputJpgBuffer).resize(width, height).toFile(fixtures.outputJpg, function(err) {
@@ -404,6 +514,72 @@ async.series({
png: function(callback) {
var inputPngBuffer = fs.readFileSync(fixtures.inputPng);
var pngSuite = new Benchmark.Suite('png');
// jimp
pngSuite.add('jimp-buffer-buffer', {
defer: true,
fn: function(deferred) {
new jimp(inputPngBuffer, function(err) {
if (err) {
throw err;
} else {
this
.resize(width, height)
.getBuffer(jimp.MIME_PNG, function (err) {
if (err) {
throw err;
} else {
deferred.resolve();
}
});
}
});
}
}).add('jimp-file-file', {
defer: true,
fn: function(deferred) {
new jimp(fixtures.inputPng, function(err) {
if (err) {
throw err;
} else {
this
.resize(width, height)
.write(fixtures.outputPng, function (err) {
if (err) {
throw err;
} else {
deferred.resolve();
}
});
}
});
}
});
// lwip
if (typeof lwip !== 'undefined') {
pngSuite.add('lwip-buffer-buffer', {
defer: true,
fn: function(deferred) {
lwip.open(inputPngBuffer, 'png', function (err, image) {
if (err) {
throw err;
}
image.resize(width, height, 'linear', function (err, image) {
if (err) {
throw err;
}
image.toBuffer('png', function (err, buffer) {
if (err) {
throw err;
}
assert.notStrictEqual(null, buffer);
deferred.resolve();
});
});
});
}
});
}
// imagemagick
pngSuite.add('imagemagick-file-file', {
defer: true,
fn: function(deferred) {
@@ -412,7 +588,6 @@ async.series({
dstPath: fixtures.outputPng,
width: width,
height: height,
format: 'jpg',
filter: magickFilter
}, function(err) {
if (err) {
@@ -422,19 +597,25 @@ async.series({
}
});
}
}).add('imagemagick-native-buffer-buffer', {
defer: true,
fn: function(deferred) {
imagemagickNative.convert({
srcData: inputPngBuffer,
width: width,
height: height,
format: 'PNG',
filter: magickFilter
});
deferred.resolve();
}
}).add('gm-file-file', {
});
// imagemagick-native
if (typeof imagemagickNative !== 'undefined') {
pngSuite.add('imagemagick-native-buffer-buffer', {
defer: true,
fn: function(deferred) {
imagemagickNative.convert({
srcData: inputPngBuffer,
width: width,
height: height,
format: 'PNG',
filter: magickFilter
});
deferred.resolve();
}
});
}
// gm
pngSuite.add('gm-file-file', {
defer: true,
fn: function(deferred) {
gm(fixtures.inputPng)
@@ -463,7 +644,9 @@ async.series({
}
});
}
}).add('sharp-buffer-file', {
});
// sharp
pngSuite.add('sharp-buffer-file', {
defer: true,
fn: function(deferred) {
sharp(inputPngBuffer).resize(width, height).toFile(fixtures.outputPng, function(err) {

BIN
test/fixtures/5_webp_a.webp vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

BIN
test/fixtures/Landscape_1.jpg vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

BIN
test/fixtures/Landscape_2.jpg vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

BIN
test/fixtures/Landscape_3.jpg vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

BIN
test/fixtures/Landscape_4.jpg vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

0
test/fixtures/Landscape_5.jpg vendored Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 134 KiB

After

Width:  |  Height:  |  Size: 134 KiB

BIN
test/fixtures/Landscape_6.jpg vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

BIN
test/fixtures/Landscape_7.jpg vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

0
test/fixtures/Landscape_8.jpg vendored Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 138 KiB

After

Width:  |  Height:  |  Size: 138 KiB

BIN
test/fixtures/Portrait_1.jpg vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

BIN
test/fixtures/Portrait_2.jpg vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

BIN
test/fixtures/Portrait_3.jpg vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

BIN
test/fixtures/Portrait_4.jpg vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

BIN
test/fixtures/Portrait_5.jpg vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

BIN
test/fixtures/Portrait_6.jpg vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

BIN
test/fixtures/Portrait_7.jpg vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

BIN
test/fixtures/Portrait_8.jpg vendored Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

BIN
test/fixtures/alpha-layer-1-fill.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

BIN
test/fixtures/alpha-layer-2-ink.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 640 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 922 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

BIN
test/fixtures/expected/blur-0.3.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
test/fixtures/expected/blur-1.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
test/fixtures/expected/blur-10.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

BIN
test/fixtures/expected/blur-mild.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
test/fixtures/expected/embed-enlarge.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 813 B

BIN
test/fixtures/expected/exif-5.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
test/fixtures/expected/exif-8.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 506 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
test/fixtures/expected/extract.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 B

BIN
test/fixtures/expected/extract.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

BIN
test/fixtures/expected/extract.tiff vendored Normal file

Binary file not shown.

BIN
test/fixtures/expected/extract.webp vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

BIN
test/fixtures/expected/flatten-black.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 996 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Some files were not shown because too many files have changed in this diff Show More