Compare commits

...

95 Commits

Author SHA1 Message Date
Lovell Fuller
75d72cfded Version bumps ahead of v0.10.1 2015-06-01 09:50:49 +01:00
Lovell Fuller
21b0d8c7f7 Version bumps ahead of v0.10.1
Use SPDX format in licence field
2015-05-31 19:35:24 +01:00
Lovell Fuller
fa8f06f07d Include C standard library for 'atoi' #228
Xcode 6.3 appears to no longer do this
2015-05-31 19:05:18 +01:00
Lovell Fuller
e07a105b7c Test availability of __has_feature macro 2015-05-11 11:54:53 +01:00
Lovell Fuller
4f72dcbf54 Verify platform/compiler compatibility #178 #214 2015-05-11 10:46:47 +01:00
Lovell Fuller
b77877c83d Add Amazon Linux 2015.03
Update to libvips version 8.0.2
2015-05-07 15:50:14 +01:00
Lovell Fuller
8fd3520257 Correct use of Windows CI env variable 2015-05-05 11:15:32 +01:00
Lovell Fuller
f15e64039c Windows CI does not yet have io.js v2 2015-05-05 10:46:23 +01:00
Lovell Fuller
3ffe2ba17f Windows CI matrix and version bumps 2015-05-05 10:15:20 +01:00
Lovell Fuller
33782d3c83 Embed alpha image on non-transparent background #204 2015-04-29 20:14:45 +01:00
Lovell Fuller
783826aa26 Reverse Openslide if/else for Debian 8 #203 2015-04-27 18:44:53 +01:00
Lovell Fuller
c2ef16eac2 Don't publish AppVeyor config to npm 2015-04-27 18:44:11 +01:00
Lovell Fuller
e999fb6e30 Dependency version bumps ahead of v0.10.0 2015-04-23 14:29:14 +01:00
Lovell Fuller
d1fc0591a5 Silence Windows compiler warnings #19 2015-04-21 14:39:37 +01:00
Lovell Fuller
fb1c9cf3d3 Less strict assert for unordered events 2015-04-21 13:57:24 +01:00
Lovell Fuller
21ba1dfc26 Wrap flapping event test in nextTick 2015-04-21 13:22:29 +01:00
Lovell Fuller
dacd62428e Fix Windows CI binding config 2015-04-21 12:37:45 +01:00
Lovell Fuller
1e52c2dbe6 Windows compatibility #19
Hide WebP format and normalise option

Separate test runners for node and iojs
2015-04-21 12:13:19 +01:00
Lovell Fuller
8926ebc56c Add Appveyor config for Windows CI
Silence 'possible loss of data' warning
2015-04-20 19:00:22 +01:00
Lovell Fuller
9da87ce868 Fix typo in conditional introduced in 8ac33aa 2015-04-20 17:50:47 +01:00
Lovell Fuller
46cc45c186 Fail fast for unknown interpolator 2015-04-20 11:22:21 +01:00
Lovell Fuller
54f2243386 Merge pull request #198 from bkw/unknownInterpolator
Runtime guard against unknown interpolator class, avoids segfault.
2015-04-20 10:17:44 +01:00
Bernhard K. Weisshuhn
8ac33aad69 avoid segfault with unknown interpolator 2015-04-20 00:24:03 +02:00
Lovell Fuller
6fc62d39c9 Update thank you list
Add perf test for normalise
2015-04-19 21:09:19 +01:00
Lovell Fuller
a0655806de No need to remove dzi file extension
libvips handles this - ensures filenames containing . work
2015-04-19 21:08:15 +01:00
Lovell Fuller
3614d14f83 A few small fixes to the test scripts 2015-04-19 16:15:40 +01:00
Lovell Fuller
f6fd45cc90 Expose libjpeg extension param features
Trellis quantisation, overshoot deringing and scan optimisation
2015-04-19 16:15:40 +01:00
Lovell Fuller
be39297f3b Merge pull request #194 from bkw/normalize
Add normalize() to use full luminance range.
2015-04-19 16:05:20 +01:00
Bernhard K. Weisshuhn
dce36e0074 Add normalize() for simple histogram stretching
Available as normalize() or normalise().
Normalization takes place in LAB place and thus should not change any
colors.

Existing alpha channels are preserved untouched by normalization.
2015-04-18 12:55:04 +02:00
Lovell Fuller
ba034a8164 Add docs for new ignoreAspectRatio option 2015-04-16 18:28:30 +01:00
Lovell Fuller
3dfc7bea3a Merge pull request #192 from skedastik/judgement
Add support for ignoreAspectRatio option when resizing
2015-04-16 15:53:43 +01:00
Alaric Holloway
f72435c750 Support resize without preserving aspect ratio #118 2015-04-16 06:50:47 -07:00
Lovell Fuller
3810f642d3 Add small cache before convolution for seq access 2015-04-14 21:31:20 +01:00
Lovell Fuller
ae968142ee Soften limitInputPixels upper limit #146
Default limit of 14-bit dimensions remains
2015-04-12 14:23:36 +01:00
Lovell Fuller
ccb7887cb9 Use libjpeg-dev metapackage for Debian variants #190 2015-04-09 11:56:36 +01:00
Lovell Fuller
f1ad1216ca Add support for Windows #19
Requires VIPS_HOME environment variable
2015-04-05 22:42:14 +01:00
Lovell Fuller
ce6813329b Improve code portability ahead of Windows support 2015-04-05 21:57:53 +01:00
Lovell Fuller
7ad7193b1e Add test for progressive JPEG output 2015-03-30 11:36:14 +01:00
Lovell Fuller
bd96a49de6 Add usage example of Stream error handling #182 2015-03-24 10:35:05 +00:00
Lovell Fuller
81c710eaa3 Add EventEmitter for queue length changes
Remove unnecessary params from Error handler
2015-03-20 15:44:18 +00:00
Lovell Fuller
711f0fefb6 Disable unused static library and introspection components 2015-03-20 14:57:13 +00:00
Lovell Fuller
33ca86e4f2 Add example of Deep Zoom output
Clean up preinstall and prereqs docs
2015-03-12 17:33:06 +00:00
Lovell Fuller
9b5229f2dd Support old and new Magick loader class names
See jcupitt/libvips@99b4bcb
2015-03-12 17:12:06 +00:00
Lovell Fuller
5781a23a4d Combine new tile* API methods
Use v7.40.0+ libvips loader methods

Separate Openslide as input vs Deep Zoom as output

Split tile-based tests into new file

Added assertions for generated tile size
2015-03-12 15:39:27 +00:00
Victor Mateevitsi
2d1e6f2644 Added Deep Zoom support.
Added OpenSuse 13.1 and 13.2 support in preinstall.sh script.
Added OpenSlide support in preinstall script.
Added unit tests for Deep Zoom and OpenSlide.
2015-03-10 14:00:27 +00:00
Lovell Fuller
5240eeb518 Merge pull request #166 from mcuelenaere/no-custom-image-header-checking
Let libvips check whether we received a valid image or not
2015-03-01 15:41:37 +00:00
Maurus Cuelenaere
125ee836fe Let libvips check whether we received a valid image or not
This removes the custom image fingerprinting code and uses the libvips
is_a_buffer() infrastructure instead.
2015-03-01 11:53:17 +01:00
Lovell Fuller
3ca2f009f4 Remove confusing CSS equivs introduced in 77bbbb9 2015-02-27 15:04:11 +00:00
Lovell Fuller
a900c28f7c Version bumps 2015-02-27 14:46:03 +00:00
Lovell Fuller
77bbbb9715 Improve min/max docs, thanks @LinusU
Add requirement for C++11 compiler

Init scaling factor to silence compiler warning
2015-02-27 13:49:16 +00:00
Lovell Fuller
88753a6333 Merge pull request #175 from LinusU/feature-min
feature: min
2015-02-27 13:25:13 +00:00
Linus Unnebäck
bcd82f4893 feature: min 2015-02-27 13:50:52 +01:00
Lovell Fuller
749dc61f85 JSON and the agro-noughts 2015-02-26 19:49:54 +00:00
Lovell Fuller
c7ccf6801d Expose runtime format availability
Aids addition of new format/method combos

Dogfood this in the test code
2015-02-26 19:41:33 +00:00
Lovell Fuller
1565522ecc Add libgsf dependency ahead of Deep Zoom support 2015-02-26 19:36:42 +00:00
Lovell Fuller
a44df2f533 Merge pull request #173 from LinusU/patch-1
Install libgsf-1-dev on Debian 8
2015-02-26 14:00:51 +00:00
Linus Unnebäck
317510746f Install libgsf-1-dev on Debian 8
This was needed on my docker container running Debian Jessie. This is the error without it:

```text
Package libgsf-1 was not found in the pkg-config search path.
Perhaps you should add the directory containing `libgsf-1.pc'
to the PKG_CONFIG_PATH environment variable
Package 'libgsf-1', required by 'vips', not found
gyp: Call to 'PKG_CONFIG_PATH=":$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig:/usr/lib/pkgconfig" pkg-config --libs vips' returned exit status 1. while trying to load binding.gyp
```
2015-02-26 13:44:00 +01:00
Lovell Fuller
ef54e327b7 Document GIF input via Buffer and Stream
Ensure @mcuelenaere is credited
2015-02-16 13:51:47 +00:00
Lovell Fuller
d8d0158774 Version bump of libvips to 7.42.3
Rename version vars to major, minor and patch
2015-02-16 13:49:22 +00:00
Lovell Fuller
4d75f27a25 Merge branch 'mcuelenaere-imagemagick-buffer' 2015-02-16 13:27:51 +00:00
Lovell Fuller
f89e9d726d Add support for libvips v8.0.0 2015-02-16 13:27:22 +00:00
Lovell Fuller
5194b37460 Merge branch 'imagemagick-buffer' of https://github.com/mcuelenaere/sharp into mcuelenaere-imagemagick-buffer 2015-02-16 12:02:27 +00:00
Lovell Fuller
55ea432711 Add assumeyes flag for Fedora #167 2015-02-16 11:18:42 +00:00
Maurus Cuelenaere
ab7408c96f Add support for loading images through ImageMagick as a buffer 2015-02-16 10:12:59 +01:00
Lovell Fuller
1f7e80e581 Add chroma subsampling options for JPEG output 2015-02-13 09:41:42 +00:00
Lovell Fuller
0e91ca90d6 Remove lingering NanAdjustExternalMemory
Should have been removed in fe34548b
2015-02-12 12:15:56 +00:00
Lovell Fuller
8f41fed9c2 Add toFormat convenience method #137 2015-02-12 11:37:56 +00:00
Lovell Fuller
96dd40cee1 Add Node.js 0.12 stable to CI build
Replaces 0.11 unstable
2015-02-09 17:09:37 +00:00
Lovell Fuller
62767d072b Version bumps 2015-02-09 09:52:27 +00:00
Lovell Fuller
33880ce19e Merge pull request #161 from ide/nan
Change nan dependency back to ^1.6.2
2015-02-06 21:32:27 +00:00
James Ide
988176846d Change nan dependency back to ^1.6.2
The issue with nan on io.js was fixed in https://github.com/rvagg/nan/pull/273. `npm install` on io.js 1.1.0 works now.
2015-02-06 12:30:15 -08:00
Lovell Fuller
657d436a0f Merge pull request #160 from jo/patch-1
Adjust comment in interpolation example
2015-02-06 14:28:59 +00:00
Johannes Jörg Schmidt
e5549e3063 Adjust comment in interpolation example 2015-02-06 15:17:10 +01:00
Lovell Fuller
0b2fb967b8 Add iojs to CI test matrix
Specific version of nan required
2015-02-06 09:36:45 +00:00
Lovell Fuller
f57478c1aa Update bench to latest imagemagick-native
Use 'Triangle' filter as bilinear equiv.
2015-02-02 16:16:45 +00:00
Lovell Fuller
e5a5e2ca7e Tighten 'extract' parameter validation #158 2015-01-29 22:46:04 +00:00
Lovell Fuller
797d503a99 Merge pull request #156 from jonathanong/patch-1
⬇️ nan@^1.5.1
2015-01-28 22:04:52 +00:00
Jonathan Ong
512a281986 ⬇️ nan@^1.5.1
1.6 breaks iojs. this lowers the minimum nan version so that developers can still do `nan@~1.5.1`.
2015-01-27 15:11:40 -08:00
Lovell Fuller
37c5ca7166 Skip SVG test when format is unavailable
It's possible to compile *magick without SVG support
2015-01-25 11:34:16 +00:00
Lovell Fuller
cda700ef73 Update libmagick references to the C library 2015-01-23 11:00:39 +00:00
Lovell Fuller
d32901da8d Dependency version bumps 2015-01-23 10:50:50 +00:00
Lovell Fuller
83ebe12061 Remove atexit handler as libvips defines this
New grunt-sharp build tool

Version bump for latest libvips
2015-01-22 14:17:20 +00:00
Lovell Fuller
fe34548bad Remove optional AdjustAmountOfExternalAllocatedMemory #151
Isolate not available when deleting the buffer
2015-01-21 20:13:43 +00:00
Lovell Fuller
855945bef2 Delete input buffer on postclose #151
Notify V8 GC of memory (de)allocation
2015-01-21 10:34:03 +00:00
Lovell Fuller
8421e3aa5f Add limitInputPixels option to reject input #115 2015-01-20 14:18:05 +00:00
Lovell Fuller
c93f79daa7 Guard against InitImage failure #150
Protects against truncated image headers
2015-01-20 10:38:44 +00:00
Lovell Fuller
35c53f78c8 Ensure bench/random test uses int dimensions 2015-01-16 22:31:46 +00:00
Lovell Fuller
c158d51f8b Explicit cast to uint32 required for nan 1.5.x
See rvagg/nan#229
2015-01-16 22:31:46 +00:00
Lovell Fuller
8e9a8dfede Remove cpplint namespace-related warnings 2015-01-16 22:31:46 +00:00
Lovell Fuller
67dc694cfb Link to new contributor guide
Remove now-duplicate content
2015-01-16 22:31:46 +00:00
Lovell Fuller
74704a132c Add a contribution guide to help those who help 2015-01-16 22:31:46 +00:00
Lovell Fuller
b86674f91f Add cpplint to test suite 2015-01-16 22:31:46 +00:00
Lovell Fuller
5dab3c8482 Allow rotate before pre-resize extraction #145 2015-01-16 22:30:57 +00:00
Lovell Fuller
a190ae6b08 Add raw, uncompressed image data output #136 2015-01-16 22:28:24 +00:00
Lovell Fuller
464fb1726d Keep output dimensions within WebP 14-bit range 2015-01-16 22:28:24 +00:00
41 changed files with 2729 additions and 496 deletions

2
.gitignore vendored
View File

@@ -2,7 +2,7 @@ build
node_modules node_modules
coverage coverage
test/bench/node_modules test/bench/node_modules
test/fixtures/output.* test/fixtures/output*
test/leak/libvips.supp test/leak/libvips.supp
# Mac OS X # Mac OS X

View File

@@ -1,6 +1,8 @@
{ {
"strict": true, "strict": true,
"node": true, "node": true,
"maxparams": 4,
"maxcomplexity": 13,
"globals": { "globals": {
"describe": true, "describe": true,
"it": true "it": true

View File

@@ -6,3 +6,4 @@ coverage
.gitignore .gitignore
test test
.travis.yml .travis.yml
appveyor.yml

View File

@@ -1,7 +1,9 @@
language: node_js language: node_js
node_js: node_js:
- "0.10" - "0.10"
- "0.11" - "0.12"
- "iojs-v1"
- "iojs-v2"
before_install: before_install:
- curl -s https://raw.githubusercontent.com/lovell/sharp/master/preinstall.sh | sudo bash - - curl -s https://raw.githubusercontent.com/lovell/sharp/master/preinstall.sh | sudo bash -
after_success: after_success:

84
CONTRIBUTING.md Executable file
View File

@@ -0,0 +1,84 @@
# Contributing to sharp
Hello, thank you for your interest in helping!
## Submit a new bug report
Please create a [new issue](https://github.com/lovell/sharp/issues/new) containing the steps to reproduce the problem.
New bugs are assigned a `triage` label whilst under investigation.
If you're having problems with `npm install sharp`, please include the output of the following commands, perhaps as a [gist](https://gist.github.com/):
```sh
vips -v
pkg-config --print-provides vips
npm install --verbose sharp
```
## Submit a new feature request
If a [similar request](https://github.com/lovell/sharp/labels/enhancement) exists, it's probably fastest to add a comment to it about your requirement.
Implementation is usually straightforward if _libvips_ [already supports](http://www.vips.ecs.soton.ac.uk/supported/current/doc/html/libvips/ch03.html) the feature you need.
## Submit a Pull Request to fix a bug
Thank you! To prevent the problem occurring again, please add unit tests that would have failed.
Please select the `master` branch as the destination for your Pull Request so your fix can be included in the next minor release.
Please squash your changes into a single commit using a command like `git rebase -i upstream/master`.
## Submit a Pull Request with a new feature
Please add JavaScript [unit tests](https://github.com/lovell/sharp/tree/master/test/unit) to cover your new feature. A test coverage report for the JavaScript code is generated in the `coverage/lcov-report` directory.
You deserve to add your details to the [list of contributors](https://github.com/lovell/sharp/blob/master/package.json#L5).
Any change that modifies the existing public API should be added to the relevant work-in-progress branch for inclusion in the next major release.
| Release | WIP branch |
| ------: | :--------- |
| v0.9.0 | intake |
| v0.10.0 | judgement |
| v0.11.0 | knife |
Please squash your changes into a single commit using a command like `git rebase -i upstream/<wip-branch>`.
### Add a new public method
The API tries to be as fluent as possible. Image processing concepts follow the naming conventions from _libvips_ and, to a lesser extent, _ImageMagick_.
Most methods have optional parameters and assume sensible defaults. Methods with mandatory parameters often have names like `doSomethingWith(X)`.
Please ensure backwards compatibility where possible. Methods to modify previously default behaviour often have names like `withoutOptionY()` or `withExtraZ()`.
Feel free to create a [new issue](https://github.com/lovell/sharp/issues/new) to gather feedback on a potential API change.
### Remove an existing public method
A method to be removed should be deprecated in the next major version then removed in the following major version.
By way of example, the [bilinearInterpolation method](https://github.com/lovell/sharp/blob/v0.6.0/index.js#L155) present in v0.5.0 was deprecated in v0.6.0 and removed in v0.7.0.
## Run the tests
### Functional tests and static code analysis
```sh
npm test
```
### Memory leak tests
Requires _valgrind_.
```sh
cd sharp/test/leak
./leak.sh
```
## Finally
Please feel free to ask any questions via a [new issue](https://github.com/lovell/sharp/issues/new) or contact me by [e-mail](https://github.com/lovell/sharp/blob/master/package.json#L4).

244
README.md
View File

@@ -3,6 +3,7 @@
* [Installation](https://github.com/lovell/sharp#installation) * [Installation](https://github.com/lovell/sharp#installation)
* [Usage examples](https://github.com/lovell/sharp#usage-examples) * [Usage examples](https://github.com/lovell/sharp#usage-examples)
* [API](https://github.com/lovell/sharp#api) * [API](https://github.com/lovell/sharp#api)
* [Contributing](https://github.com/lovell/sharp#contributing)
* [Testing](https://github.com/lovell/sharp#testing) * [Testing](https://github.com/lovell/sharp#testing)
* [Performance](https://github.com/lovell/sharp#performance) * [Performance](https://github.com/lovell/sharp#performance)
* [Thanks](https://github.com/lovell/sharp#thanks) * [Thanks](https://github.com/lovell/sharp#thanks)
@@ -11,7 +12,11 @@
The typical use case for this high speed Node.js module is to convert large images of many formats to smaller, web-friendly JPEG, PNG and WebP images of varying dimensions. The typical use case for this high speed Node.js module is to convert large images of many formats to smaller, web-friendly JPEG, PNG and WebP images of varying dimensions.
This module supports reading and writing JPEG, PNG and WebP images to and from Streams, Buffer objects and the filesystem. This module supports reading and writing JPEG, PNG and WebP images to and from Streams, Buffer objects and the filesystem.
It also supports reading images of many other types from the filesystem via libmagick++ or libgraphicsmagick++ if present. 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. 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. 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.
@@ -30,8 +35,9 @@ This module is powered by the blazingly fast [libvips](https://github.com/jcupit
### Prerequisites ### Prerequisites
* Node.js v0.10+ * Node.js v0.10+ or io.js
* [libvips](https://github.com/jcupitt/libvips) v7.40.0+ (7.42.0+ recommended) * [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: To install the most suitable version of libvips on the following Operating Systems:
@@ -46,6 +52,8 @@ To install the most suitable version of libvips on the following Operating Syste
* RHEL/Centos/Scientific 6, 7 * RHEL/Centos/Scientific 6, 7
* Fedora 21, 22 * Fedora 21, 22
* Amazon Linux 2014.09 * Amazon Linux 2014.09
* OpenSuse Linux
* OpenSuse 13.1, 13.2
run the following as a user with `sudo` access: run the following as a user with `sudo` access:
@@ -57,6 +65,10 @@ or run the following as `root`:
The [preinstall.sh](https://github.com/lovell/sharp/blob/master/preinstall.sh) script requires `curl` and `pkg-config`. 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 ### Mac OS tips
Manual install via homebrew: Manual install via homebrew:
@@ -71,6 +83,18 @@ The _gettext_ dependency of _libvips_ [can lead](https://github.com/lovell/sharp
brew link gettext --force 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 ### Heroku
[Alessandro Tagliapietra](https://github.com/alex88) maintains an [Heroku buildpack for libvips](https://github.com/alex88/heroku-buildpack-vips) and its dependencies. [Alessandro Tagliapietra](https://github.com/alex88) maintains an [Heroku buildpack for libvips](https://github.com/alex88/heroku-buildpack-vips) and its dependencies.
@@ -81,9 +105,11 @@ The _gettext_ dependency of _libvips_ [can lead](https://github.com/lovell/sharp
docker pull marcbachmann/libvips docker pull marcbachmann/libvips
### gulp.js ### Build tools
[Eugeny Vlasenko](https://github.com/mahnunchik) maintains [gulp-responsive](https://www.npmjs.org/package/gulp-responsive) and [Mohammad Prabowo](https://github.com/rizalp) maintains [gulp-sharp](https://www.npmjs.org/package/gulp-sharp). * [gulp-responsive](https://www.npmjs.com/package/gulp-responsive)
* [gulp-sharp](https://www.npmjs.com/package/gulp-sharp)
* [grunt-sharp](https://www.npmjs.com/package/grunt-sharp)
## Usage examples ## Usage examples
@@ -102,15 +128,20 @@ sharp('input.jpg').resize(300, 200).toFile('output.jpg', function(err) {
``` ```
```javascript ```javascript
var transformer = sharp().resize(300, 200).crop(sharp.gravity.north); var transformer = sharp()
readableStream.pipe(transformer).pipe(writableStream); .resize(300, 200)
.crop(sharp.gravity.north)
.on('error', function(err) {
console.log(err);
});
// Read image data from readableStream, resize and write image data to writableStream // Read image data from readableStream, resize and write image data to writableStream
readableStream.pipe(transformer).pipe(writableStream);
``` ```
```javascript ```javascript
var image = sharp(inputJpg); var image = sharp(inputJpg);
image.metadata(function(err, metadata) { image.metadata(function(err, metadata) {
image.resize(metadata.width / 2).webp().toBuffer(function(err, outputBuffer, info) { image.resize(Math.floor(metadata.width / 2)).webp().toBuffer(function(err, outputBuffer, info) {
// outputBuffer contains a WebP image half the width and height of the original JPEG // outputBuffer contains a WebP image half the width and height of the original JPEG
}); });
}); });
@@ -184,7 +215,7 @@ sharp(inputBuffer)
.toFile('output.tiff') .toFile('output.tiff')
.then(function() { .then(function() {
// output.tiff is a 200 pixels wide and 300 pixels high image // output.tiff is a 200 pixels wide and 300 pixels high image
// containing a bicubic scaled version, embedded on a white canvas, // containing a nohalo scaled version, embedded on a white canvas,
// of the image data in inputBuffer // of the image data in inputBuffer
}); });
``` ```
@@ -194,7 +225,7 @@ sharp('input.gif')
.resize(200, 300) .resize(200, 300)
.background({r: 0, g: 0, b: 0, a: 0}) .background({r: 0, g: 0, b: 0, a: 0})
.embed() .embed()
.webp() .toFormat(sharp.format.webp)
.toBuffer(function(err, outputBuffer) { .toBuffer(function(err, outputBuffer) {
if (err) { if (err) {
throw err; throw err;
@@ -208,37 +239,93 @@ sharp('input.gif')
sharp(inputBuffer) sharp(inputBuffer)
.resize(200, 200) .resize(200, 200)
.max() .max()
.jpeg() .toFormat('jpeg')
.toBuffer().then(function(outputBuffer) { .toBuffer().then(function(outputBuffer) {
// outputBuffer contains JPEG image data no wider than 200 pixels and no higher // outputBuffer contains JPEG image data no wider than 200 pixels and no higher
// than 200 pixels regardless of the inputBuffer image dimensions // 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 ## API
### Attributes
#### format
An Object containing nested boolean values
representing the available input and output formats/methods,
for example:
```javascript
{ jpeg: { id: 'jpeg',
input: { file: true, buffer: true, stream: true },
output: { file: true, buffer: true, stream: true } },
png: { id: 'png',
input: { file: true, buffer: true, stream: true },
output: { file: true, buffer: true, stream: true } },
webp: { id: 'webp',
input: { file: true, buffer: true, stream: true },
output: { file: true, buffer: true, stream: true } },
tiff: { id: 'tiff',
input: { file: true, buffer: true, stream: true },
output: { file: true, buffer: false, stream: false } },
magick: { id: 'magick',
input: { file: true, buffer: true, stream: true },
output: { file: false, buffer: false, stream: false } },
raw: { id: 'raw',
input: { file: false, buffer: false, stream: false },
output: { file: false, buffer: true, stream: true } } }
```
#### queue
An EventEmitter that emits a `change` event when a task is either:
* queued, waiting for _libuv_ to provide a worker thread
* complete
```javascript
sharp.queue.on('change', function(queueLength) {
console.log('Queue contains ' + queueLength + ' task(s)');
});
```
### Input methods ### Input methods
#### sharp([input]) #### sharp([input])
Constructor to which further methods are chained. `input`, if present, can be one of: Constructor to which further methods are chained. `input`, if present, can be one of:
* Buffer containing JPEG, PNG, WebP or TIFF image data, or * Buffer containing JPEG, PNG, WebP, GIF* or TIFF image data, or
* String containing the filename of an image, with most major formats supported. * String containing the filename of an image, with most major formats supported.
The object returned implements the [stream.Duplex](http://nodejs.org/api/stream.html#stream_class_stream_duplex) class. The object returned implements the [stream.Duplex](http://nodejs.org/api/stream.html#stream_class_stream_duplex) class.
JPEG, PNG, WebP or TIFF format image data can be streamed into the object when `input` is not provided. JPEG, PNG, WebP, GIF* or TIFF format image data can be streamed into the object when `input` is not provided.
JPEG, PNG or WebP format image data can be streamed out from this object. JPEG, PNG or WebP format image data can be streamed out from this object.
\* libvips 8.0.0+ is required for Buffer/Stream input of GIF and other `magick` formats.
#### metadata([callback]) #### metadata([callback])
Fast access to image metadata without decoding any compressed image data. Fast access to image metadata without decoding any compressed image data.
`callback`, if present, gets the arguments `(err, metadata)` where `metadata` has the attributes: `callback`, if present, gets the arguments `(err, metadata)` where `metadata` has the attributes:
* `format`: Name of decoder to be used to decompress image data e.g. `jpeg`, `png`, `webp` (for file-based input additionally `tiff` and `magick`) * `format`: Name of decoder to be used to decompress image data e.g. `jpeg`, `png`, `webp` (for file-based input additionally `tiff`, `magick` and `openslide`)
* `width`: Number of pixels wide * `width`: Number of pixels wide
* `height`: Number of pixels high * `height`: Number of pixels high
* `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `scrgb`, `cmyk`, `lab`, `xyz`, `b-w` [...](https://github.com/jcupitt/libvips/blob/master/libvips/iofuncs/enumtypes.c#L522) * `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)
@@ -253,15 +340,21 @@ A Promises/A+ promise is returned when `callback` is not provided.
An advanced setting that switches the libvips access method to `VIPS_ACCESS_SEQUENTIAL`. This will reduce memory usage and can improve performance on some systems. An advanced setting that switches the libvips access method to `VIPS_ACCESS_SEQUENTIAL`. This will reduce memory usage and can improve performance on some systems.
#### limitInputPixels(pixels)
Do not process input images where the number of pixels (width * height) exceeds this limit.
`pixels` is the integral Number of pixels, with a value between 1 and the default 268402689 (0x3FFF * 0x3FFF).
### Image transformation options ### Image transformation options
#### resize(width, [height]) #### resize(width, [height])
Scale output to `width` x `height`. By default, the resized image is cropped to the exact size specified. Scale output to `width` x `height`. By default, the resized image is cropped to the exact size specified.
`width` is the Number of pixels wide the resultant image should be. Use `null` or `undefined` to auto-scale the width to match the height. `width` is the integral Number of pixels wide the resultant image should be, between 1 and 16383 (0x3FFF). Use `null` or `undefined` to auto-scale the width to match the height.
`height` is the Number of pixels high the resultant image should be. Use `null` or `undefined` to auto-scale the height to match the width. `height` is the integral Number of pixels high the resultant image should be, between 1 and 16383. Use `null` or `undefined` to auto-scale the height to match the width.
#### extract(top, left, width, height) #### extract(top, left, width, height)
@@ -283,10 +376,26 @@ Possible values are `north`, `east`, `south`, `west`, `center` and `centre`. The
#### max() #### max()
Preserving aspect ratio, resize the image to the maximum `width` or `height` specified. Preserving aspect ratio,
resize the image to be as large as possible
while ensuring its dimensions are less than or equal to
the `width` and `height` specified.
Both `width` and `height` must be provided via `resize` otherwise the behaviour will default to `crop`. Both `width` and `height` must be provided via `resize` otherwise the behaviour will default to `crop`.
#### 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) #### background(rgba)
Set the background for the `embed` and `flatten` operations. Set the background for the `embed` and `flatten` operations.
@@ -315,6 +424,8 @@ Rotate the output image by either an explicit angle or auto-orient based on the
Use this method without `angle` to determine the angle from EXIF data. Mirroring is supported and may infer the use of a `flip` operation. 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()
Flip the image about the vertical Y axis. This always occurs after rotation, if any. Flip the image about the vertical Y axis. This always occurs after rotation, if any.
@@ -378,6 +489,10 @@ This is a linear operation. If the input image is in a non-linear colour space s
The output image will still be web-friendly sRGB and contain three (identical) channels. The output image will still be web-friendly sRGB and contain three (identical) channels.
#### normalize() / normalise()
Enhance output image contrast by stretching its luminance to cover the full dynamic range. This typically reduces performance by 30%.
### Output options ### Output options
#### jpeg() #### jpeg()
@@ -392,6 +507,25 @@ Use PNG format for the output image.
Use WebP format for the output image. Use WebP format for the output image.
#### raw()
_Requires libvips 7.42.0+_
Provide raw, uncompressed uint8 (unsigned char) image data for Buffer and Stream based output.
The number of channels depends on the input image and selected options.
* 1 channel for images converted to `greyscale()`, with each byte representing one pixel.
* 3 channels for colour images without alpha transparency, with bytes ordered \[red, green, blue, red, green, blue, etc.\]).
* 4 channels for colour images with alpha transparency, with bytes ordered \[red, green, blue, alpha, red, green, blue, alpha, etc.\].
#### toFormat(format)
Convenience method for the above output format methods, where `format` is either:
* an attribute of the `sharp.format` Object e.g. `sharp.format.jpeg`, or
* a String containing `jpeg`, `png`, `webp` or `raw`.
#### quality(quality) #### quality(quality)
The output quality to use for lossy JPEG, WebP and TIFF output formats. The default quality is `80`. The output quality to use for lossy JPEG, WebP and TIFF output formats. The default quality is `80`.
@@ -408,6 +542,46 @@ Include all metadata (EXIF, XMP, IPTC) from the input image in the output image.
The default behaviour is to strip all metadata and convert to the device-independent sRGB colour space. The default behaviour is to strip all metadata and convert to the device-independent sRGB colour space.
#### tile([size], [overlap])
The size and overlap, in pixels, of square Deep Zoom image pyramid tiles.
* `size` is an integral Number between 1 and 8192. The default value is 256 pixels.
* `overlap` is an integral Number between 0 and 8192. The default value is 0 pixels.
#### withoutChromaSubsampling()
Disable the use of [chroma subsampling](http://en.wikipedia.org/wiki/Chroma_subsampling) with JPEG output (4:4:4).
This can improve colour representation at higher quality settings (90+),
but usually increases output file size and typically reduces performance by 25%.
The default behaviour is to use chroma subsampling (4:2:0).
#### trellisQuantisation() / trellisQuantization()
_Requires libvips 8.0.0+ compiled against mozjpeg 3.0+_
An advanced setting to apply the use of
[trellis quantisation](http://en.wikipedia.org/wiki/Trellis_quantization) with JPEG output.
Reduces file size and slightly increases relative quality at the cost of increased compression time.
#### overshootDeringing()
_Requires libvips 8.0.0+ compiled against mozjpeg 3.0+_
An advanced setting to reduce the effects of
[ringing](http://en.wikipedia.org/wiki/Ringing_%28signal%29) in JPEG output,
in particular where black text appears on a white background (or vice versa).
#### optimiseScans() / optimizeScans()
_Requires libvips 8.0.0+ compiled against mozjpeg 3.0+_
An advanced setting for progressive (interlace) JPEG output.
Calculates which spectrum of DCT coefficients uses the fewest bits.
Usually reduces file size at the cost of increased compression time.
#### compressionLevel(compressionLevel) #### compressionLevel(compressionLevel)
An advanced setting for the _zlib_ compression level of the lossless PNG output format. The default level is `6`. An advanced setting for the _zlib_ compression level of the lossless PNG output format. The default level is `6`.
@@ -424,7 +598,7 @@ An advanced setting to disable adaptive row filtering for the lossless PNG outpu
#### toFile(filename, [callback]) #### toFile(filename, [callback])
`filename` is a String containing the filename to write the image data to. The format is inferred from the extension, with JPEG, PNG, WebP and TIFF supported. `filename` is a String containing the filename to write the image data to. The format is inferred from the extension, with JPEG, PNG, WebP, TIFF and DZI supported.
`callback`, if present, is called with two arguments `(err, info)` where: `callback`, if present, is called with two arguments `(err, info)` where:
@@ -487,6 +661,10 @@ Provides access to internal task counters.
var counters = sharp.counters(); // { queue: 2, process: 4 } var counters = sharp.counters(); // { queue: 2, process: 4 }
``` ```
## Contributing
A [guide for contributors](https://github.com/lovell/sharp/blob/master/CONTRIBUTING.md) covers reporting bugs, requesting features and submitting code changes.
## Testing ## Testing
### Functional tests ### Functional tests
@@ -503,28 +681,9 @@ var counters = sharp.counters(); // { queue: 2, process: 4 }
[![Centos 6.5 Build Status](https://snap-ci.com/lovell/sharp/branch/master/build_image)](https://snap-ci.com/lovell/sharp/branch/master) [![Centos 6.5 Build Status](https://snap-ci.com/lovell/sharp/branch/master/build_image)](https://snap-ci.com/lovell/sharp/branch/master)
#### It worked on my machine #### Windows Server 2012
``` [![Windows Server 2012 Build Status](https://ci.appveyor.com/api/projects/status/pgtul704nkhhg6sg)](https://ci.appveyor.com/project/lovell/sharp)
npm test
```
### Memory leak tests
```
cd sharp/test/leak
./leak.sh
```
Requires _valgrind_:
```
brew install valgrind
```
```
sudo apt-get install -qq valgrind
```
### Benchmark tests ### Benchmark tests
@@ -542,7 +701,7 @@ brew install graphicsmagick
``` ```
``` ```
sudo apt-get install -qq imagemagick graphicsmagick libmagick++-dev sudo apt-get install -qq imagemagick graphicsmagick libmagickcore-dev
``` ```
``` ```
@@ -607,12 +766,17 @@ This module would never have been possible without the help and code contributio
* [Amit Pitaru](https://github.com/apitaru) * [Amit Pitaru](https://github.com/apitaru)
* [Brandon Aaron](https://github.com/brandonaaron) * [Brandon Aaron](https://github.com/brandonaaron)
* [Andreas Lind](https://github.com/papandreou) * [Andreas Lind](https://github.com/papandreou)
* [Maurus Cuelenaere](https://github.com/mcuelenaere)
* [Linus Unneb<65>ck](https://github.com/LinusU)
* [Victor Mateevitsi](https://github.com/mvictoras)
* [Alaric Holloway](https://github.com/skedastik)
* [Bernhard K. Weisshuhn](https://github.com/bkw)
Thank you! Thank you!
## Licence ## Licence
Copyright 2013, 2014 Lovell Fuller and contributors. Copyright 2013, 2014, 2015 Lovell Fuller and contributors.
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

25
appveyor.yml Normal file
View File

@@ -0,0 +1,25 @@
os: Visual Studio 2014 CTP4
platform: Win32
configuration: Release
environment:
VIPS_VERSION_MAJOR_MINOR: 8.0
VIPS_VERSION_PATCH: 2
VIPS_WARNING: 0
matrix:
- nodejs_version: "0.10"
nodejs_exec: "node"
- nodejs_version: "0.12"
nodejs_exec: "node"
- nodejs_version: "1.0"
nodejs_exec: "iojs"
install:
- ps: $env:VIPS_VERSION = "$env:VIPS_VERSION_MAJOR_MINOR.$env:VIPS_VERSION_PATCH"
- ps: Write-Output "Fetching http://www.vips.ecs.soton.ac.uk/supported/$env:VIPS_VERSION_MAJOR_MINOR/win32/vips-dev-$env:VIPS_VERSION.zip"
- ps: Start-FileDownload http://www.vips.ecs.soton.ac.uk/supported/$env:VIPS_VERSION_MAJOR_MINOR/win32/vips-dev-$env:VIPS_VERSION.zip -FileName c:\vips-dev-$env:VIPS_VERSION.zip
- ps: Invoke-Expression "& 7z -y x c:\vips-dev-$env:VIPS_VERSION.zip -oc:\ | FIND /V `"ing `""
- ps: $env:VIPS_HOME = "c:\vips-dev-$env:VIPS_VERSION"
- ps: $env:PATH = "$env:VIPS_HOME\bin;$env:PATH"
- ps: Install-Product node $env:nodejs_version x86
- npm install --arch=ia32 --msvs_version=2013
test_script:
- npm run-script test-win32-%nodejs_exec%

View File

@@ -8,15 +8,50 @@
'src/resize.cc', 'src/resize.cc',
'src/sharp.cc' 'src/sharp.cc'
], ],
'variables': { 'conditions': [
'PKG_CONFIG_PATH': '<!(which brew >/dev/null 2>&1 && eval $(brew --env) && echo $PKG_CONFIG_LIBDIR || true):$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig:/usr/lib/pkgconfig' ['OS=="win"', {
}, 'library_dirs': [
'libraries': [ '$(VIPS_HOME)/lib'
'<!(PKG_CONFIG_PATH="<(PKG_CONFIG_PATH)" pkg-config --libs vips)' ],
], 'libraries': [
'include_dirs': [ 'libvips.dll.a',
'<!(PKG_CONFIG_PATH="<(PKG_CONFIG_PATH)" pkg-config --cflags vips glib-2.0)', 'glib-2.0.lib',
'<!(node -e "require(\'nan\')")' 'gobject-2.0.lib',
'gthread-2.0.lib',
'gmodule-2.0.lib',
'liblcms2.dll.a',
'libxml2.lib',
'intl.lib',
'libjpeg.dll.a',
'libexif.dll.a',
'libpng.lib',
'libtiff.dll.a',
'libMagickWand-6.Q16.dll.a',
'libMagickCore-6.Q16.dll.a',
'pango-1.0.lib',
'pangoft2-1.0.lib',
'libgsf-1.dll.a',
'libopenslide.dll.a',
'libfftw3.dll.a'
],
'include_dirs': [
'$(VIPS_HOME)/include',
'$(VIPS_HOME)/include/glib-2.0',
'$(VIPS_HOME)/lib/glib-2.0/include',
'<!(node -e "require(\'nan\')")'
]
}, {
'variables': {
'PKG_CONFIG_PATH': '<!(which brew >/dev/null 2>&1 && eval $(brew --env) && echo $PKG_CONFIG_LIBDIR || true):$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig:/usr/lib/pkgconfig'
},
'libraries': [
'<!(PKG_CONFIG_PATH="<(PKG_CONFIG_PATH)" pkg-config --libs vips)'
],
'include_dirs': [
'<!(PKG_CONFIG_PATH="<(PKG_CONFIG_PATH)" pkg-config --cflags vips glib-2.0)',
'<!(node -e "require(\'nan\')")'
]
}]
], ],
'cflags_cc': [ 'cflags_cc': [
'-std=c++0x', '-std=c++0x',
@@ -33,6 +68,11 @@
'-O3' '-O3'
], ],
'MACOSX_DEPLOYMENT_TARGET': '10.7' 'MACOSX_DEPLOYMENT_TARGET': '10.7'
},
'msvs_settings': {
'VCCLCompilerTool': {
'ExceptionHandling': 1 # /EHsc
}
} }
}] }]
} }

252
index.js
View File

@@ -3,6 +3,7 @@
var path = require('path'); var path = require('path');
var util = require('util'); var util = require('util');
var stream = require('stream'); var stream = require('stream');
var events = require('events');
var semver = require('semver'); var semver = require('semver');
var color = require('color'); var color = require('color');
@@ -11,6 +12,12 @@ var BluebirdPromise = require('bluebird');
var sharp = require('./build/Release/sharp'); var sharp = require('./build/Release/sharp');
var libvipsVersion = sharp.libvipsVersion(); var libvipsVersion = sharp.libvipsVersion();
var maximum = {
width: 0x3FFF,
height: 0x3FFF,
pixels: Math.pow(0x3FFF, 2)
};
var Sharp = function(input) { var Sharp = function(input) {
if (!(this instanceof Sharp)) { if (!(this instanceof Sharp)) {
return new Sharp(input); return new Sharp(input);
@@ -21,6 +28,7 @@ var Sharp = function(input) {
bufferIn: null, bufferIn: null,
streamIn: false, streamIn: false,
sequentialRead: false, sequentialRead: false,
limitInputPixels: maximum.pixels,
// ICC profiles // ICC profiles
iccProfilePath: path.join(__dirname, 'icc') + path.sep, iccProfilePath: path.join(__dirname, 'icc') + path.sep,
// resize options // resize options
@@ -34,9 +42,10 @@ var Sharp = function(input) {
heightPost: -1, heightPost: -1,
width: -1, width: -1,
height: -1, height: -1,
canvas: 'c', canvas: 'crop',
gravity: 0, gravity: 0,
angle: 0, angle: 0,
rotateBeforePreExtract: false,
flip: false, flip: false,
flop: false, flop: false,
withoutEnlargement: false, withoutEnlargement: false,
@@ -50,36 +59,32 @@ var Sharp = function(input) {
sharpenJagged: 2, sharpenJagged: 2,
gamma: 0, gamma: 0,
greyscale: false, greyscale: false,
normalize: 0,
// output options // output options
output: '__input', output: '__input',
progressive: false, progressive: false,
quality: 80, quality: 80,
compressionLevel: 6, compressionLevel: 6,
withoutAdaptiveFiltering: false, withoutAdaptiveFiltering: false,
withoutChromaSubsampling: false,
trellisQuantisation: false,
overshootDeringing: false,
optimiseScans: false,
streamOut: false, streamOut: false,
withMetadata: false withMetadata: false,
tileSize: 256,
tileOverlap: 0,
// Function to notify of queue length changes
queueListener: function(queueLength) {
module.exports.queue.emit('change', queueLength);
}
}; };
if (typeof input === 'string') { if (typeof input === 'string') {
// input=file // input=file
this.options.fileIn = input; this.options.fileIn = input;
} else if (typeof input === 'object' && input instanceof Buffer) { } else if (typeof input === 'object' && input instanceof Buffer) {
// input=buffer // input=buffer
if ( this.options.bufferIn = input;
(input.length > 3) &&
// JPEG
(input[0] === 0xFF && input[1] === 0xD8) ||
// PNG
(input[0] === 0x89 && input[1] === 0x50) ||
// WebP
(input[0] === 0x52 && input[1] === 0x49) ||
// TIFF
(input[0] === 0x4D && input[1] === 0x4D && input[2] === 0x00 && (input[3] === 0x2A || input[3] === 0x2B)) ||
(input[0] === 0x49 && input[1] === 0x49 && (input[2] === 0x2A || input[2] === 0x2B) && input[3] === 0x00)
) {
this.options.bufferIn = input;
} else {
throw new Error('Buffer contains an unsupported image format. JPEG, PNG, WebP and TIFF are currently supported.');
}
} else { } else {
// input=stream // input=stream
this.options.streamIn = true; this.options.streamIn = true;
@@ -89,6 +94,16 @@ var Sharp = function(input) {
module.exports = Sharp; module.exports = Sharp;
util.inherits(Sharp, stream.Duplex); util.inherits(Sharp, stream.Duplex);
/*
EventEmitter singleton emits queue length 'change' events
*/
module.exports.queue = new events.EventEmitter();
/*
Supported image formats
*/
module.exports.format = sharp.format();
/* /*
Handle incoming chunk on Writable Stream Handle incoming chunk on Writable Stream
*/ */
@@ -120,7 +135,7 @@ Sharp.prototype._write = function(chunk, encoding, callback) {
module.exports.gravity = {'center': 0, 'centre': 0, 'north': 1, 'east': 2, 'south': 3, 'west': 4}; module.exports.gravity = {'center': 0, 'centre': 0, 'north': 1, 'east': 2, 'south': 3, 'west': 4};
Sharp.prototype.crop = function(gravity) { Sharp.prototype.crop = function(gravity) {
this.options.canvas = 'c'; this.options.canvas = 'crop';
if (typeof gravity === 'number' && !Number.isNaN(gravity) && gravity >= 0 && gravity <= 4) { if (typeof gravity === 'number' && !Number.isNaN(gravity) && gravity >= 0 && gravity <= 4) {
this.options.gravity = gravity; this.options.gravity = gravity;
} else { } else {
@@ -134,8 +149,16 @@ Sharp.prototype.extract = function(topOffset, leftOffset, width, height) {
var suffix = this.options.width === -1 && this.options.height === -1 ? 'Pre' : 'Post'; var suffix = this.options.width === -1 && this.options.height === -1 ? 'Pre' : 'Post';
var values = arguments; var values = arguments;
['topOffset', 'leftOffset', 'width', 'height'].forEach(function(name, index) { ['topOffset', 'leftOffset', 'width', 'height'].forEach(function(name, index) {
this.options[name + suffix] = values[index]; if (typeof values[index] === 'number' && !Number.isNaN(values[index]) && (values[index] % 1 === 0) && values[index] >= 0) {
this.options[name + suffix] = values[index];
} else {
throw new Error('Non-integer value for ' + name + ' of ' + values[index]);
}
}.bind(this)); }.bind(this));
// Ensure existing rotation occurs before pre-resize extraction
if (suffix === 'Pre' && this.options.angle !== 0) {
this.options.rotateBeforePreExtract = true;
}
return this; return this;
}; };
@@ -152,12 +175,26 @@ Sharp.prototype.background = function(rgba) {
}; };
Sharp.prototype.embed = function() { Sharp.prototype.embed = function() {
this.options.canvas = 'e'; this.options.canvas = 'embed';
return this; return this;
}; };
Sharp.prototype.max = function() { Sharp.prototype.max = function() {
this.options.canvas = 'm'; this.options.canvas = 'max';
return this;
};
Sharp.prototype.min = function() {
this.options.canvas = 'min';
return this;
};
/*
Ignoring the aspect ratio of the input, stretch the image to
the exact width and/or height provided via the resize method.
*/
Sharp.prototype.ignoreAspectRatio = function() {
this.options.canvas = 'ignore_aspect';
return this; return this;
}; };
@@ -280,7 +317,18 @@ module.exports.interpolator = {
vertexSplitQuadraticBasisSpline: 'vsqbs' vertexSplitQuadraticBasisSpline: 'vsqbs'
}; };
Sharp.prototype.interpolateWith = function(interpolator) { Sharp.prototype.interpolateWith = function(interpolator) {
this.options.interpolator = interpolator; var isValid = false;
for (var key in module.exports.interpolator) {
if (module.exports.interpolator[key] === interpolator) {
isValid = true;
break;
}
}
if (isValid) {
this.options.interpolator = interpolator;
} else {
throw new Error('Invalid interpolator ' + interpolator);
}
return this; return this;
}; };
@@ -300,6 +348,19 @@ Sharp.prototype.gamma = function(gamma) {
return this; return this;
}; };
/*
Enhance output image contrast by stretching its luminance to cover the full dynamic range
*/
Sharp.prototype.normalize = function(normalize) {
if (process.platform !== 'win32') {
this.options.normalize = (typeof normalize === 'boolean') ? normalize : true;
} else {
console.error('normalize unavailable on win32 platform');
}
return this;
};
Sharp.prototype.normalise = Sharp.prototype.normalize;
/* /*
Convert to greyscale Convert to greyscale
*/ */
@@ -341,10 +402,10 @@ Sharp.prototype.compressionLevel = function(compressionLevel) {
}; };
/* /*
Disable the use of adaptive row filtering for PNG output - requires libvips 7.41.0+ Disable the use of adaptive row filtering for PNG output - requires libvips 7.42.0+
*/ */
Sharp.prototype.withoutAdaptiveFiltering = function(withoutAdaptiveFiltering) { Sharp.prototype.withoutAdaptiveFiltering = function(withoutAdaptiveFiltering) {
if (semver.gte(libvipsVersion, '7.41.0')) { if (semver.gte(libvipsVersion, '7.42.0')) {
this.options.withoutAdaptiveFiltering = (typeof withoutAdaptiveFiltering === 'boolean') ? withoutAdaptiveFiltering : true; this.options.withoutAdaptiveFiltering = (typeof withoutAdaptiveFiltering === 'boolean') ? withoutAdaptiveFiltering : true;
} else { } else {
console.error('withoutAdaptiveFiltering requires libvips 7.41.0+'); console.error('withoutAdaptiveFiltering requires libvips 7.41.0+');
@@ -352,33 +413,124 @@ Sharp.prototype.withoutAdaptiveFiltering = function(withoutAdaptiveFiltering) {
return this; return this;
}; };
/*
Disable the use of chroma subsampling for JPEG output
*/
Sharp.prototype.withoutChromaSubsampling = function(withoutChromaSubsampling) {
this.options.withoutChromaSubsampling = (typeof withoutChromaSubsampling === 'boolean') ? withoutChromaSubsampling : true;
return this;
};
/*
Apply trellis quantisation to JPEG output - requires libvips 8.0.0+ compiled against mozjpeg 3.0+
*/
Sharp.prototype.trellisQuantisation = function(trellisQuantisation) {
if (semver.gte(libvipsVersion, '8.0.0')) {
this.options.trellisQuantisation = (typeof trellisQuantisation === 'boolean') ? trellisQuantisation : true;
} else {
console.error('trellisQuantisation requires libvips 8.0.0+');
}
return this;
};
Sharp.prototype.trellisQuantization = Sharp.prototype.trellisQuantisation;
/*
Apply overshoot deringing to JPEG output - requires libvips 8.0.0+ compiled against mozjpeg 3.0+
*/
Sharp.prototype.overshootDeringing = function(overshootDeringing) {
if (semver.gte(libvipsVersion, '8.0.0')) {
this.options.overshootDeringing = (typeof overshootDeringing === 'boolean') ? overshootDeringing : true;
} else {
console.error('overshootDeringing requires libvips 8.0.0+');
}
return this;
};
/*
Optimise scans in progressive JPEG output - requires libvips 8.0.0+ compiled against mozjpeg 3.0+
*/
Sharp.prototype.optimiseScans = function(optimiseScans) {
if (semver.gte(libvipsVersion, '8.0.0')) {
this.options.optimiseScans = (typeof optimiseScans === 'boolean') ? optimiseScans : true;
if (this.options.optimiseScans) {
this.progressive();
}
} else {
console.error('optimiseScans requires libvips 8.0.0+');
}
return this;
};
Sharp.prototype.optimizeScans = Sharp.prototype.optimiseScans;
/*
Include all metadata (EXIF, XMP, IPTC) from the input image in the output image
*/
Sharp.prototype.withMetadata = function(withMetadata) { Sharp.prototype.withMetadata = function(withMetadata) {
this.options.withMetadata = (typeof withMetadata === 'boolean') ? withMetadata : true; this.options.withMetadata = (typeof withMetadata === 'boolean') ? withMetadata : true;
return this; return this;
}; };
/*
Tile size and overlap for Deep Zoom output
*/
Sharp.prototype.tile = function(size, overlap) {
// Size of square tiles, in pixels
if (typeof size !== 'undefined' && size !== null) {
if (!Number.isNaN(size) && size % 1 === 0 && size >= 1 && size <= 8192) {
this.options.tileSize = size;
} else {
throw new Error('Invalid tile size (1 to 8192) ' + size);
}
}
// Overlap of tiles, in pixels
if (typeof overlap !== 'undefined' && overlap !== null) {
if (!Number.isNaN(overlap) && overlap % 1 === 0 && overlap >=0 && overlap <= 8192) {
if (overlap > this.options.tileSize) {
throw new Error('Tile overlap ' + overlap + ' cannot be larger than tile size ' + this.options.tileSize);
}
this.options.tileOverlap = overlap;
} else {
throw new Error('Invalid tile overlap (0 to 8192) ' + overlap);
}
}
return this;
};
Sharp.prototype.resize = function(width, height) { Sharp.prototype.resize = function(width, height) {
if (!width) { if (!width) {
this.options.width = -1; this.options.width = -1;
} else { } else {
if (typeof width === 'number' && !Number.isNaN(width)) { if (typeof width === 'number' && !Number.isNaN(width) && width % 1 === 0 && width > 0 && width <= maximum.width) {
this.options.width = width; this.options.width = width;
} else { } else {
throw new Error('Invalid width ' + width); throw new Error('Invalid width (1 to ' + maximum.width + ') ' + width);
} }
} }
if (!height) { if (!height) {
this.options.height = -1; this.options.height = -1;
} else { } else {
if (typeof height === 'number' && !Number.isNaN(height)) { if (typeof height === 'number' && !Number.isNaN(height) && height % 1 === 0 && height > 0 && height <= maximum.height) {
this.options.height = height; this.options.height = height;
} else { } else {
throw new Error('Invalid height ' + height); throw new Error('Invalid height (1 to ' + maximum.height + ') ' + height);
} }
} }
return this; return this;
}; };
/*
Limit the total number of pixels for input images
Assumes the image dimensions contained in the file header can be trusted
*/
Sharp.prototype.limitInputPixels = function(limit) {
if (typeof limit === 'number' && !Number.isNaN(limit) && limit % 1 === 0 && limit > 0) {
this.options.limitInputPixels = limit;
} else {
throw new Error('Invalid pixel limit (1 to ' + maximum.pixels + ') ' + limit);
}
return this;
};
/* /*
Write output image data to a file Write output image data to a file
*/ */
@@ -406,25 +558,67 @@ Sharp.prototype.toFile = function(output, callback) {
return this; return this;
}; };
/*
Write output to a Buffer
*/
Sharp.prototype.toBuffer = function(callback) { Sharp.prototype.toBuffer = function(callback) {
return this._sharp(callback); return this._sharp(callback);
}; };
/*
Force JPEG output
*/
Sharp.prototype.jpeg = function() { Sharp.prototype.jpeg = function() {
this.options.output = '__jpeg'; this.options.output = '__jpeg';
return this; return this;
}; };
/*
Force PNG output
*/
Sharp.prototype.png = function() { Sharp.prototype.png = function() {
this.options.output = '__png'; this.options.output = '__png';
return this; return this;
}; };
/*
Force WebP output
*/
Sharp.prototype.webp = function() { Sharp.prototype.webp = function() {
this.options.output = '__webp'; this.options.output = '__webp';
return this; return this;
}; };
/*
Force raw, uint8 output
*/
Sharp.prototype.raw = function() {
var supportsRawOutput = module.exports.format.raw.output;
if (supportsRawOutput.file || supportsRawOutput.buffer || supportsRawOutput.stream) {
this.options.output = '__raw';
} else {
console.error('Raw output requires libvips 7.42.0+');
}
return this;
};
/*
Force output to a given format
@param format is either the id as a String or an Object with an 'id' attribute
*/
Sharp.prototype.toFormat = function(format) {
var id = format;
if (typeof format === 'object') {
id = format.id;
}
if (typeof id === 'string' && typeof module.exports.format[id] === 'object' && typeof this[id] === 'function') {
this[id]();
} else {
throw new Error('Unsupported format ' + format);
}
return this;
};
/* /*
Used by a Writable Stream to notify that it is ready for data Used by a Writable Stream to notify that it is ready for data
*/ */

View File

@@ -1,6 +1,6 @@
{ {
"name": "sharp", "name": "sharp",
"version": "0.8.4", "version": "0.10.1",
"author": "Lovell Fuller <npm@lovell.info>", "author": "Lovell Fuller <npm@lovell.info>",
"contributors": [ "contributors": [
"Pierre Inglebert <pierre.inglebert@gmail.com>", "Pierre Inglebert <pierre.inglebert@gmail.com>",
@@ -11,11 +11,18 @@
"Julian Walker <julian@fiftythree.com>", "Julian Walker <julian@fiftythree.com>",
"Amit Pitaru <pitaru.amit@gmail.com>", "Amit Pitaru <pitaru.amit@gmail.com>",
"Brandon Aaron <hello.brandon@aaron.sh>", "Brandon Aaron <hello.brandon@aaron.sh>",
"Andreas Lind <andreas@one.com>" "Andreas Lind <andreas@one.com>",
"Maurus Cuelenaere <mcuelenaere@gmail.com>",
"Linus Unnebäck <linus@folkdatorn.se>",
"Victor Mateevitsi <mvictoras@gmail.com>",
"Alaric Holloway <alaric.holloway@gmail.com>",
"Bernhard K. Weisshuhn <bkw@codingforce.com>"
], ],
"description": "High performance Node.js module to resize JPEG, PNG, WebP and TIFF images using the libvips library", "description": "High performance Node.js module to resize JPEG, PNG, WebP and TIFF images using the libvips library",
"scripts": { "scripts": {
"test": "node ./node_modules/istanbul/lib/cli.js cover ./node_modules/mocha/bin/_mocha -- --slow=5000 --timeout=10000 ./test/unit/*.js" "test": "VIPS_WARNING=0 node ./node_modules/istanbul/lib/cli.js cover ./node_modules/mocha/bin/_mocha -- --slow=5000 --timeout=20000 ./test/unit/*.js",
"test-win32-node": "node ./node_modules/mocha/bin/mocha --slow=5000 --timeout=20000 ./test/unit/*.js",
"test-win32-iojs": "iojs ./node_modules/mocha/bin/mocha --slow=5000 --timeout=20000 ./test/unit/*.js"
}, },
"main": "index.js", "main": "index.js",
"repository": { "repository": {
@@ -27,6 +34,7 @@
"png", "png",
"webp", "webp",
"tiff", "tiff",
"dzi",
"resize", "resize",
"thumbnail", "thumbnail",
"crop", "crop",
@@ -34,18 +42,21 @@
"vips" "vips"
], ],
"dependencies": { "dependencies": {
"bluebird": "^2.6.4", "bluebird": "^2.9.27",
"color": "^0.7.3", "color": "^0.8.0",
"nan": "^1.5.1", "nan": "^1.8.4",
"semver": "^4.1.0" "semver": "^4.3.6"
}, },
"devDependencies": { "devDependencies": {
"mocha": "^2.0.1", "async": "^1.1.0",
"mocha-jshint": "^0.0.9", "coveralls": "^2.11.2",
"istanbul": "^0.3.5", "istanbul": "^0.3.14",
"coveralls": "^2.11.2" "mocha": "^2.2.5",
"mocha-jshint": "^2.2.3",
"node-cpplint": "^0.4.0",
"rimraf": "^2.3.4"
}, },
"license": "Apache 2.0", "license": "Apache-2.0",
"engines": { "engines": {
"node": ">=0.10" "node": ">=0.10"
} }

View File

@@ -13,50 +13,104 @@
# * Amazon Linux 2014.09 # * Amazon Linux 2014.09
vips_version_minimum=7.40.0 vips_version_minimum=7.40.0
vips_version_latest_major=7.42 vips_version_latest_major_minor=8.0
vips_version_latest_minor=0 vips_version_latest_patch=2
openslide_version_minimum=3.4.0
openslide_version_latest_major_minor=3.4
openslide_version_latest_patch=1
install_libvips_from_source() { install_libvips_from_source() {
echo "Compiling libvips $vips_version_latest_major.$vips_version_latest_minor from source" echo "Compiling libvips $vips_version_latest_major_minor.$vips_version_latest_patch from source"
curl -O http://www.vips.ecs.soton.ac.uk/supported/$vips_version_latest_major/vips-$vips_version_latest_major.$vips_version_latest_minor.tar.gz 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.$vips_version_latest_minor.tar.gz tar zvxf vips-$vips_version_latest_major_minor.$vips_version_latest_patch.tar.gz
cd vips-$vips_version_latest_major.$vips_version_latest_minor cd vips-$vips_version_latest_major_minor.$vips_version_latest_patch
./configure --enable-debug=no --enable-docs=no --enable-cxx=yes --without-python --without-orc --without-fftw --without-gsf $1 ./configure --disable-debug --disable-docs --disable-static --disable-introspection --enable-cxx=yes --without-python --without-orc --without-fftw $1
make make
make install make install
cd .. cd ..
rm -rf vips-$vips_version_latest_major.$vips_version_latest_minor rm -rf vips-$vips_version_latest_major_minor.$vips_version_latest_patch
rm vips-$vips_version_latest_major.$vips_version_latest_minor.tar.gz rm vips-$vips_version_latest_major_minor.$vips_version_latest_patch.tar.gz
ldconfig ldconfig
echo "Installed libvips $vips_version_latest_major.$vips_version_latest_minor" echo "Installed libvips $vips_version_latest_major_minor.$vips_version_latest_patch"
}
install_libopenslide_from_source() {
echo "Compiling openslide $openslide_version_latest_major_minor.$openslide_version_latest_patch from source"
curl -O -L https://github.com/openslide/openslide/releases/download/v$openslide_version_latest_major_minor.$openslide_version_latest_patch/openslide-$openslide_version_latest_major_minor.$openslide_version_latest_patch.tar.gz
tar xzvf openslide-$openslide_version_latest_major_minor.$openslide_version_latest_patch.tar.gz
cd openslide-$openslide_version_latest_major_minor.$openslide_version_latest_patch
PKG_CONFIG_PATH=$pkg_config_path ./configure $1
make
make install
cd ..
rm -rf openslide-$openslide_version_latest_major_minor.$openslide_version_latest_patch
rm openslide-$openslide_version_latest_major_minor.$openslide_version_latest_patch.tar.gz
ldconfig
echo "Installed libopenslide $openslide_version_latest_major_minor.$openslide_version_latest_patch"
} }
sorry() { sorry() {
echo "Sorry, I don't yet know how to install libvips on $1" echo "Sorry, I don't yet know how to install lib$1 on $2"
exit 1 exit 1
} }
# Is libvips already installed, and is it at least the minimum required version?
if ! type pkg-config >/dev/null; then
sorry "a system without pkg-config"
fi
pkg_config_path_homebrew=`which brew >/dev/null 2>&1 && eval $(brew --env) && echo $PKG_CONFIG_LIBDIR || true` pkg_config_path_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_homebrew:$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig:/usr/lib/pkgconfig"
PKG_CONFIG_PATH=$pkg_config_path pkg-config --exists vips check_if_library_exists() {
if [ $? -eq 0 ]; then PKG_CONFIG_PATH=$pkg_config_path pkg-config --exists $1
vips_version_found=$(PKG_CONFIG_PATH=$pkg_config_path pkg-config --modversion vips)
pkg-config --atleast-version=$vips_version_minimum vips
if [ $? -eq 0 ]; then if [ $? -eq 0 ]; then
# Found suitable version of libvips version_found=$(PKG_CONFIG_PATH=$pkg_config_path pkg-config --modversion $1)
echo "Found libvips $vips_version_found" PKG_CONFIG_PATH=$pkg_config_path pkg-config --atleast-version=$2 $1
exit 0 if [ $? -eq 0 ]; then
# Found suitable version of libvips
echo "Found lib$1 $version_found"
return 1
fi
echo "Found lib$1 $version_found but require $2"
else
echo "Could not find lib$1 using a PKG_CONFIG_PATH of '$pkg_config_path'"
fi fi
echo "Found libvips $vips_version_found but require $vips_version_minimum" return 0
else }
echo "Could not find libvips using a PKG_CONFIG_PATH of '$pkg_config_path'"
enable_openslide=0
# Is libvips already installed, and is it at least the minimum required version?
if [ $# -eq 1 ]; then
if [ "$1" = "--with-openslide" ]; then
echo "Installing vips with openslide support"
enable_openslide=1
else
echo "Sorry, $1 is not supported. Did you mean --with-openslide?"
exit 1
fi
fi
if ! type pkg-config >/dev/null; then
sorry "vips" "a system without pkg-config"
fi
openslide_exists=0
if [ $enable_openslide -eq 1 ]; then
check_if_library_exists "openslide" "$openslide_version_minimum"
openslide_exists=$?
fi
check_if_library_exists "vips" "$vips_version_minimum"
vips_exists=$?
if [ $vips_exists -eq 1 ] && [ $enable_openslide -eq 1 ]; then
if [ $openslide_exists -eq 1 ]; then
# Check if vips compiled with openslide support
vips_with_openslide=`vips list classes | grep -i opensli`
if [ -z $vips_with_openslide ]; then
echo "Vips compiled without openslide support."
else
exit 0
fi
fi
elif [ $vips_exists -eq 1 ] && [ $enable_openslide -eq 0 ]; then
exit 0
fi fi
# Verify root/sudo access # Verify root/sudo access
@@ -65,6 +119,108 @@ if [ "$(id -u)" -ne "0" ]; then
exit 1 exit 1
fi fi
# OS-specific installations of libopenslide follows
# Either openslide does not exist, or vips is installed without openslide support
if [ $enable_openslide -eq 1 ] && [ -z $vips_with_openslide ] && [ $openslide_exists -eq 0 ]; then
case $(uname -s) in
*[Dd]arwin*)
# Mac OS
echo "Detected Mac OS"
if type "brew" > /dev/null; then
echo "Installing libopenslide via homebrew"
brew install openslide
elif type "port" > /dev/null; then
echo "Installing libopenslide via MacPorts"
port install openslide
else
sorry "openslide" "Mac OS without homebrew or MacPorts"
fi
;;
*)
if [ -f /etc/debian_version ]; then
# Debian Linux
DISTRO=$(lsb_release -c -s)
echo "Detected Debian Linux '$DISTRO'"
case "$DISTRO" in
jessie|vivid)
# Debian 8, Ubuntu 15
echo "Installing libopenslide via apt-get"
apt-get install -y libopenslide-dev
;;
trusty|utopic|qiana|rebecca)
# Ubuntu 14, Mint 17
echo "Installing libopenslide dependencies via apt-get"
apt-get install -y automake build-essential curl zlib1g-dev libopenjpeg-dev libpng12-dev libjpeg-dev libtiff5-dev libgdk-pixbuf2.0-dev libxml2-dev libsqlite3-dev libcairo2-dev libglib2.0-dev sqlite3 libsqlite3-dev
install_libopenslide_from_source
;;
precise|wheezy|maya)
# Debian 7, Ubuntu 12.04, Mint 13
echo "Installing libopenslide dependencies via apt-get"
apt-get install -y automake build-essential curl zlib1g-dev libopenjpeg-dev libpng12-dev libjpeg-dev libtiff5-dev libgdk-pixbuf2.0-dev libxml2-dev libsqlite3-dev libcairo2-dev libglib2.0-dev sqlite3 libsqlite3-dev
install_libopenslide_from_source
;;
*)
# Unsupported Debian-based OS
sorry "openslide" "Debian-based $DISTRO"
;;
esac
elif [ -f /etc/redhat-release ]; then
# Red Hat Linux
RELEASE=$(cat /etc/redhat-release)
echo "Detected Red Hat Linux '$RELEASE'"
case $RELEASE in
"Red Hat Enterprise Linux release 7."*|"CentOS Linux release 7."*|"Scientific Linux release 7."*)
# RHEL/CentOS 7
echo "Installing libopenslide dependencies via yum"
yum groupinstall -y "Development Tools"
yum install -y tar curl libpng-devel libjpeg-devel libxml2-devel zlib-devel openjpeg-devel libtiff-devel gdk-pixbuf2-devel sqlite-devel cairo-devel glib2-devel
install_libopenslide_from_source "--prefix=/usr"
;;
"Red Hat Enterprise Linux release 6."*|"CentOS release 6."*|"Scientific Linux release 6."*)
# RHEL/CentOS 6
echo "Installing libopenslide dependencies via yum"
yum groupinstall -y "Development Tools"
yum install -y tar curl libpng-devel libjpeg-devel libxml2-devel zlib-devel openjpeg-devel libtiff-devel gdk-pixbuf2-devel sqlite-devel cairo-devel glib2-devel
install_libopenslide_from_source "--prefix=/usr"
;;
"Fedora release 21 "*|"Fedora release 22 "*)
# Fedora 21, 22
echo "Installing libopenslide via yum"
yum install -y openslide-devel
;;
*)
# Unsupported RHEL-based OS
sorry "openslide" "$RELEASE"
;;
esac
elif [ -f /etc/os-release ]; then
RELEASE=$(cat /etc/os-release | grep VERSION)
echo "Detected OpenSuse Linux '$RELEASE'"
case $RELEASE in
*"13.2"*)
echo "Installing libopenslide via zypper"
zypper --gpg-auto-import-keys install -y libopenslide-devel
;;
esac
elif [ -f /etc/SuSE-brand ]; then
RELEASE=$(cat /etc/SuSE-brand | grep VERSION)
echo "Detected OpenSuse Linux '$RELEASE'"
case $RELEASE in
*"13.1")
echo "Installing libopenslide dependencies via zypper"
zypper --gpg-auto-import-keys install -y --type pattern devel_basis
zypper --gpg-auto-import-keys install -y tar curl libpng16-devel libjpeg-turbo libjpeg8-devel libxml2-devel zlib-devel openjpeg-devel libtiff-devel libgdk_pixbuf-2_0-0 sqlite3-devel cairo-devel glib2-devel
install_libopenslide_from_source
;;
esac
else
# Unsupported OS
sorry "openslide" "$(uname -a)"
fi
;;
esac
fi
# OS-specific installations of libvips follows # OS-specific installations of libvips follows
case $(uname -s) in case $(uname -s) in
@@ -73,12 +229,16 @@ case $(uname -s) in
echo "Detected Mac OS" echo "Detected Mac OS"
if type "brew" > /dev/null; then if type "brew" > /dev/null; then
echo "Installing libvips via homebrew" echo "Installing libvips via homebrew"
brew install homebrew/science/vips --with-webp --with-graphicsmagick if [ $enable_openslide -eq 1 ]; then
brew install homebrew/science/vips --with-webp --with-graphicsmagick --with-openslide
else
brew install homebrew/science/vips --with-webp --with-graphicsmagick
fi
elif type "port" > /dev/null; then elif type "port" > /dev/null; then
echo "Installing libvips via MacPorts" echo "Installing libvips via MacPorts"
port install vips port install vips
else else
sorry "Mac OS without homebrew or MacPorts" sorry "vips" "Mac OS without homebrew or MacPorts"
fi fi
;; ;;
*) *)
@@ -89,13 +249,18 @@ case $(uname -s) in
case "$DISTRO" in case "$DISTRO" in
jessie|vivid) jessie|vivid)
# Debian 8, Ubuntu 15 # Debian 8, Ubuntu 15
echo "Installing libvips via apt-get" if [ $enable_openslide -eq 1 ]; then
apt-get install -y libvips-dev 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) trusty|utopic|qiana|rebecca)
# Ubuntu 14, Mint 17 # Ubuntu 14, Mint 17
echo "Installing libvips dependencies via apt-get" echo "Installing libvips dependencies via apt-get"
apt-get install -y automake build-essential gobject-introspection gtk-doc-tools libglib2.0-dev libjpeg-turbo8-dev libpng12-dev libwebp-dev libtiff5-dev libexif-dev liblcms2-dev libxml2-dev swig libmagickwand-dev curl 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 install_libvips_from_source
;; ;;
precise|wheezy|maya) precise|wheezy|maya)
@@ -103,12 +268,12 @@ case $(uname -s) in
echo "Installing libvips dependencies via apt-get" echo "Installing libvips dependencies via apt-get"
add-apt-repository -y ppa:lyrasis/precise-backports add-apt-repository -y ppa:lyrasis/precise-backports
apt-get update apt-get update
apt-get install -y automake build-essential gobject-introspection gtk-doc-tools libglib2.0-dev libjpeg-turbo8-dev libpng12-dev libwebp-dev libtiff4-dev libexif-dev liblcms2-dev libxml2-dev swig libmagickwand-dev curl 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 install_libvips_from_source
;; ;;
*) *)
# Unsupported Debian-based OS # Unsupported Debian-based OS
sorry "Debian-based $DISTRO" sorry "vips" "Debian-based $DISTRO"
;; ;;
esac esac
elif [ -f /etc/redhat-release ]; then elif [ -f /etc/redhat-release ]; then
@@ -120,14 +285,14 @@ case $(uname -s) in
# RHEL/CentOS 7 # RHEL/CentOS 7
echo "Installing libvips dependencies via yum" echo "Installing libvips dependencies via yum"
yum groupinstall -y "Development Tools" yum groupinstall -y "Development Tools"
yum install -y gtk-doc libxml2-devel libjpeg-turbo-devel libpng-devel libtiff-devel libexif-devel lcms-devel ImageMagick-devel gobject-introspection-devel libwebp-devel curl 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" install_libvips_from_source "--prefix=/usr"
;; ;;
"Red Hat Enterprise Linux release 6."*|"CentOS release 6."*|"Scientific Linux release 6."*) "Red Hat Enterprise Linux release 6."*|"CentOS release 6."*|"Scientific Linux release 6."*)
# RHEL/CentOS 6 # RHEL/CentOS 6
echo "Installing libvips dependencies via yum" echo "Installing libvips dependencies via yum"
yum groupinstall -y "Development Tools" yum groupinstall -y "Development Tools"
yum install -y gtk-doc libxml2-devel libjpeg-turbo-devel libpng-devel libtiff-devel libexif-devel lcms-devel ImageMagick-devel curl yum install -y 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 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 --enablerepo=nux-dextop gobject-introspection-devel
yum install -y http://rpms.famillecollet.com/enterprise/remi-release-6.rpm yum install -y http://rpms.famillecollet.com/enterprise/remi-release-6.rpm
@@ -136,30 +301,64 @@ case $(uname -s) in
;; ;;
"Fedora release 21 "*|"Fedora release 22 "*) "Fedora release 21 "*|"Fedora release 22 "*)
# Fedora 21, 22 # Fedora 21, 22
echo "Installing libvips via yum" if [ $enable_openslide -eq 1 ]; then
yum install vips-devel 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 # Unsupported RHEL-based OS
sorry "$RELEASE" sorry "vips" "$RELEASE"
;; ;;
esac esac
elif [ -f /etc/system-release ]; then elif [ -f /etc/system-release ]; then
# Probably Amazon Linux # Probably Amazon Linux
RELEASE=$(cat /etc/system-release) RELEASE=$(cat /etc/system-release)
case $RELEASE in case $RELEASE in
"Amazon Linux AMI release 2014.09") "Amazon Linux AMI release 2014.09"|"Amazon Linux AMI release 2015.03")
# Amazon Linux # Amazon Linux
echo "Detected '$RELEASE'" echo "Detected '$RELEASE'"
echo "Installing libvips dependencies via yum" echo "Installing libvips dependencies via yum"
yum groupinstall -y "Development Tools" yum groupinstall -y "Development Tools"
yum install -y gtk-doc libxml2-devel libjpeg-turbo-devel libpng-devel libtiff-devel libexif-devel lcms-devel ImageMagick-devel gobject-introspection-devel libwebp-devel curl 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" 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 esac
else else
# Unsupported OS # Unsupported OS
sorry "$(uname -a)" sorry "vips" "$(uname -a)"
fi fi
;; ;;
esac esac

View File

@@ -1,9 +1,26 @@
#include <cstdlib>
#include <string> #include <string>
#include <string.h> #include <string.h>
#include <vips/vips.h> #include <vips/vips.h>
#include "common.h" #include "common.h"
// Verify platform and compiler compatibility
#ifdef _WIN64
#error Windows 64-bit is currently unsupported - see https://github.com/lovell/sharp#windows
#endif
#if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6)))
#error GCC version 4.6+ is required for C++11 features - see https://github.com/lovell/sharp#prerequisites
#endif
#if (defined(__clang__) && defined(__has_feature))
#if (!__has_feature(cxx_range_for))
#error clang version 3.0+ is required for C++11 features - see https://github.com/lovell/sharp#prerequisites
#endif
#endif
namespace sharp { namespace sharp {
// How many tasks are in the queue? // How many tasks are in the queue?
@@ -28,19 +45,8 @@ namespace sharp {
bool IsTiff(std::string const &str) { bool IsTiff(std::string const &str) {
return EndsWith(str, ".tif") || EndsWith(str, ".tiff") || EndsWith(str, ".TIF") || EndsWith(str, ".TIFF"); return EndsWith(str, ".tif") || EndsWith(str, ".tiff") || EndsWith(str, ".TIF") || EndsWith(str, ".TIFF");
} }
bool IsDz(std::string const &str) {
// Buffer content checkers return EndsWith(str, ".dzi") || EndsWith(str, ".DZI");
unsigned char const MARKER_JPEG[] = {0xff, 0xd8};
unsigned char const MARKER_PNG[] = {0x89, 0x50};
unsigned char const MARKER_WEBP[] = {0x52, 0x49};
static bool buffer_is_tiff(char *buffer, size_t len) {
return (
len >= 4 && (
(buffer[0] == 'M' && buffer[1] == 'M' && buffer[2] == '\0' && (buffer[3] == '*' || buffer[3] == '+')) ||
(buffer[0] == 'I' && buffer[1] == 'I' && (buffer[2] == '*' || buffer[2] == '+') && buffer[3] == '\0')
)
);
} }
/* /*
@@ -48,15 +54,19 @@ namespace sharp {
*/ */
ImageType DetermineImageType(void *buffer, size_t const length) { ImageType DetermineImageType(void *buffer, size_t const length) {
ImageType imageType = ImageType::UNKNOWN; ImageType imageType = ImageType::UNKNOWN;
if (length >= 4) { char const *load = vips_foreign_find_load_buffer(buffer, length);
if (memcmp(MARKER_JPEG, buffer, 2) == 0) { if (load != NULL) {
std::string loader = load;
if (EndsWith(loader, "JpegBuffer")) {
imageType = ImageType::JPEG; imageType = ImageType::JPEG;
} else if (memcmp(MARKER_PNG, buffer, 2) == 0) { } else if (EndsWith(loader, "PngBuffer")) {
imageType = ImageType::PNG; imageType = ImageType::PNG;
} else if (memcmp(MARKER_WEBP, buffer, 2) == 0) { } else if (EndsWith(loader, "WebpBuffer")) {
imageType = ImageType::WEBP; imageType = ImageType::WEBP;
} else if (buffer_is_tiff(static_cast<char*>(buffer), length)) { } else if (EndsWith(loader, "TiffBuffer")) {
imageType = ImageType::TIFF; imageType = ImageType::TIFF;
} else if (EndsWith(loader, "MagickBuffer")) {
imageType = ImageType::MAGICK;
} }
} }
return imageType; return imageType;
@@ -65,35 +75,31 @@ namespace sharp {
/* /*
Initialise and return a VipsImage from a buffer. Supports JPEG, PNG, WebP and TIFF. Initialise and return a VipsImage from a buffer. Supports JPEG, PNG, WebP and TIFF.
*/ */
VipsImage* InitImage(ImageType imageType, void *buffer, size_t const length, VipsAccess const access) { VipsImage* InitImage(void *buffer, size_t const length, VipsAccess const access) {
VipsImage *image = NULL; return vips_image_new_from_buffer(buffer, length, NULL, "access", access, NULL);
if (imageType == ImageType::JPEG) {
vips_jpegload_buffer(buffer, length, &image, "access", access, NULL);
} else if (imageType == ImageType::PNG) {
vips_pngload_buffer(buffer, length, &image, "access", access, NULL);
} else if (imageType == ImageType::WEBP) {
vips_webpload_buffer(buffer, length, &image, "access", access, NULL);
} else if (imageType == ImageType::TIFF) {
vips_tiffload_buffer(buffer, length, &image, "access", access, NULL);
}
return image;
} }
/* /*
Inpect the first 2-4 bytes of a file to determine image format Determine image format, reads the first few bytes of the file
*/ */
ImageType DetermineImageType(char const *file) { ImageType DetermineImageType(char const *file) {
ImageType imageType = ImageType::UNKNOWN; ImageType imageType = ImageType::UNKNOWN;
if (vips_foreign_is_a("jpegload", file)) { char const *load = vips_foreign_find_load(file);
imageType = ImageType::JPEG; if (load != NULL) {
} else if (vips_foreign_is_a("pngload", file)) { std::string loader = load;
imageType = ImageType::PNG; if (EndsWith(loader, "JpegFile")) {
} else if (vips_foreign_is_a("webpload", file)) { imageType = ImageType::JPEG;
imageType = ImageType::WEBP; } else if (EndsWith(loader, "Png")) {
} else if (vips_foreign_is_a("tiffload", file)) { imageType = ImageType::PNG;
imageType = ImageType::TIFF; } else if (EndsWith(loader, "WebpFile")) {
} else if(vips_foreign_is_a("magickload", file)) { imageType = ImageType::WEBP;
imageType = ImageType::MAGICK; } else if (EndsWith(loader, "Openslide")) {
imageType = ImageType::OPENSLIDE;
} else if (EndsWith(loader, "TiffFile")) {
imageType = ImageType::TIFF;
} else if (EndsWith(loader, "Magick") || EndsWith(loader, "MagickFile")) {
imageType = ImageType::MAGICK;
}
} }
return imageType; return imageType;
} }
@@ -101,20 +107,8 @@ namespace sharp {
/* /*
Initialise and return a VipsImage from a file. Initialise and return a VipsImage from a file.
*/ */
VipsImage* InitImage(ImageType imageType, char const *file, VipsAccess const access) { VipsImage* InitImage(char const *file, VipsAccess const access) {
VipsImage *image = NULL; return vips_image_new_from_file(file, "access", access, NULL);
if (imageType == ImageType::JPEG) {
vips_jpegload(file, &image, "access", access, NULL);
} else if (imageType == ImageType::PNG) {
vips_pngload(file, &image, "access", access, NULL);
} else if (imageType == ImageType::WEBP) {
vips_webpload(file, &image, "access", access, NULL);
} else if (imageType == ImageType::TIFF) {
vips_tiffload(file, &image, "access", access, NULL);
} else if (imageType == ImageType::MAGICK) {
vips_magickload(file, &image, "access", access, NULL);
}
return image;
} }
/* /*
@@ -157,9 +151,12 @@ namespace sharp {
*/ */
int InterpolatorWindowSize(char const *name) { int InterpolatorWindowSize(char const *name) {
VipsInterpolate *interpolator = vips_interpolate_new(name); VipsInterpolate *interpolator = vips_interpolate_new(name);
if (interpolator == NULL) {
return -1;
}
int window_size = vips_interpolate_get_window_size(interpolator); int window_size = vips_interpolate_get_window_size(interpolator);
g_object_unref(interpolator); g_object_unref(interpolator);
return window_size; return window_size;
} }
} // namespace } // namespace sharp

View File

@@ -1,5 +1,5 @@
#ifndef SHARP_COMMON_H #ifndef SRC_COMMON_H_
#define SHARP_COMMON_H #define SRC_COMMON_H_
namespace sharp { namespace sharp {
@@ -9,7 +9,8 @@ namespace sharp {
PNG, PNG,
WEBP, WEBP,
TIFF, TIFF,
MAGICK MAGICK,
OPENSLIDE
}; };
// How many tasks are in the queue? // How many tasks are in the queue?
@@ -23,6 +24,7 @@ namespace sharp {
bool IsPng(std::string const &str); bool IsPng(std::string const &str);
bool IsWebp(std::string const &str); bool IsWebp(std::string const &str);
bool IsTiff(std::string const &str); bool IsTiff(std::string const &str);
bool IsDz(std::string const &str);
/* /*
Determine image format of a buffer. Determine image format of a buffer.
@@ -37,12 +39,12 @@ namespace sharp {
/* /*
Initialise and return a VipsImage from a buffer. Supports JPEG, PNG, WebP and TIFF. Initialise and return a VipsImage from a buffer. Supports JPEG, PNG, WebP and TIFF.
*/ */
VipsImage* InitImage(ImageType imageType, void *buffer, size_t const length, VipsAccess const access); VipsImage* InitImage(void *buffer, size_t const length, VipsAccess const access);
/* /*
Initialise and return a VipsImage from a file. Initialise and return a VipsImage from a file.
*/ */
VipsImage* InitImage(ImageType imageType, char const *file, VipsAccess const access); VipsImage* InitImage(char const *file, VipsAccess const access);
/* /*
Does this image have an embedded profile? Does this image have an embedded profile?
@@ -66,6 +68,6 @@ namespace sharp {
*/ */
int InterpolatorWindowSize(char const *name); int InterpolatorWindowSize(char const *name);
} // namespace } // namespace sharp
#endif #endif // SRC_COMMON_H_

View File

@@ -6,8 +6,23 @@
#include "common.h" #include "common.h"
#include "metadata.h" #include "metadata.h"
using namespace v8; using v8::Handle;
using namespace sharp; using v8::Local;
using v8::Value;
using v8::Object;
using v8::Number;
using v8::String;
using v8::Boolean;
using v8::Function;
using v8::Exception;
using sharp::ImageType;
using sharp::DetermineImageType;
using sharp::InitImage;
using sharp::HasProfile;
using sharp::HasAlpha;
using sharp::ExifOrientation;
using sharp::counterQueue;
struct MetadataBaton { struct MetadataBaton {
// Input // Input
@@ -46,7 +61,11 @@ class MetadataWorker : public NanAsyncWorker {
// From buffer // From buffer
imageType = DetermineImageType(baton->bufferIn, baton->bufferInLength); imageType = DetermineImageType(baton->bufferIn, baton->bufferInLength);
if (imageType != ImageType::UNKNOWN) { if (imageType != ImageType::UNKNOWN) {
image = InitImage(imageType, baton->bufferIn, baton->bufferInLength, VIPS_ACCESS_RANDOM); image = InitImage(baton->bufferIn, baton->bufferInLength, VIPS_ACCESS_RANDOM);
if (image == NULL) {
(baton->err).append("Input buffer has corrupt header");
imageType = ImageType::UNKNOWN;
}
} else { } else {
(baton->err).append("Input buffer contains unsupported image format"); (baton->err).append("Input buffer contains unsupported image format");
} }
@@ -54,9 +73,13 @@ class MetadataWorker : public NanAsyncWorker {
// From file // From file
imageType = DetermineImageType(baton->fileIn.c_str()); imageType = DetermineImageType(baton->fileIn.c_str());
if (imageType != ImageType::UNKNOWN) { if (imageType != ImageType::UNKNOWN) {
image = InitImage(imageType, baton->fileIn.c_str(), VIPS_ACCESS_RANDOM); image = InitImage(baton->fileIn.c_str(), VIPS_ACCESS_RANDOM);
if (image == NULL) {
(baton->err).append("Input file has corrupt header");
imageType = ImageType::UNKNOWN;
}
} else { } else {
(baton->err).append("File is of an unsupported image format"); (baton->err).append("Input file is of an unsupported image format");
} }
} }
if (image != NULL && imageType != ImageType::UNKNOWN) { if (image != NULL && imageType != ImageType::UNKNOWN) {
@@ -67,6 +90,7 @@ class MetadataWorker : public NanAsyncWorker {
case ImageType::WEBP: baton->format = "webp"; break; case ImageType::WEBP: baton->format = "webp"; break;
case ImageType::TIFF: baton->format = "tiff"; break; case ImageType::TIFF: baton->format = "tiff"; break;
case ImageType::MAGICK: baton->format = "magick"; break; case ImageType::MAGICK: baton->format = "magick"; break;
case ImageType::OPENSLIDE: baton->format = "openslide"; break;
case ImageType::UNKNOWN: break; case ImageType::UNKNOWN: break;
} }
// VipsImage attributes // VipsImage attributes

View File

@@ -1,8 +1,8 @@
#ifndef SHARP_METADATA_H #ifndef SRC_METADATA_H_
#define SHARP_METADATA_H #define SRC_METADATA_H_
#include "nan.h" #include "nan.h"
NAN_METHOD(metadata); NAN_METHOD(metadata);
#endif #endif // SRC_METADATA_H_

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -8,25 +8,14 @@
#include "resize.h" #include "resize.h"
#include "utilities.h" #include "utilities.h"
using namespace v8; extern "C" void init(v8::Handle<v8::Object> target) {
static void at_exit(void* arg) {
NanScope();
vips_shutdown();
}
extern "C" void init(Handle<Object> target) {
NanScope(); NanScope();
vips_init("sharp"); vips_init("sharp");
node::AtExit(at_exit);
// Set libvips operation cache limits // Set libvips operation cache limits
vips_cache_set_max_mem(100 * 1048576); // 100 MB vips_cache_set_max_mem(100 * 1024 * 1024); // 100 MB
vips_cache_set_max(500); // 500 operations vips_cache_set_max(500); // 500 operations
// Notify the V8 garbage collector of max cache size
NanAdjustExternalMemory(vips_cache_get_max_mem());
// Methods available to JavaScript // Methods available to JavaScript
NODE_SET_METHOD(target, "metadata", metadata); NODE_SET_METHOD(target, "metadata", metadata);
NODE_SET_METHOD(target, "resize", resize); NODE_SET_METHOD(target, "resize", resize);
@@ -34,6 +23,7 @@ extern "C" void init(Handle<Object> target) {
NODE_SET_METHOD(target, "concurrency", concurrency); NODE_SET_METHOD(target, "concurrency", concurrency);
NODE_SET_METHOD(target, "counters", counters); NODE_SET_METHOD(target, "counters", counters);
NODE_SET_METHOD(target, "libvipsVersion", libvipsVersion); NODE_SET_METHOD(target, "libvipsVersion", libvipsVersion);
NODE_SET_METHOD(target, "format", format);
} }
NODE_MODULE(sharp, init) NODE_MODULE(sharp, init)

View File

@@ -6,8 +6,14 @@
#include "common.h" #include "common.h"
#include "utilities.h" #include "utilities.h"
using namespace v8; using v8::Local;
using namespace sharp; using v8::Object;
using v8::Number;
using v8::String;
using v8::Boolean;
using sharp::counterQueue;
using sharp::counterProcess;
/* /*
Get and set cache memory and item limits Get and set cache memory and item limits
@@ -70,6 +76,69 @@ NAN_METHOD(counters) {
NAN_METHOD(libvipsVersion) { NAN_METHOD(libvipsVersion) {
NanScope(); NanScope();
char version[9]; char version[9];
snprintf(version, 9, "%d.%d.%d", vips_version(0), vips_version(1), vips_version(2)); g_snprintf(version, sizeof(version), "%d.%d.%d", vips_version(0), vips_version(1), vips_version(2));
NanReturnValue(NanNew<String>(version)); NanReturnValue(NanNew<String>(version));
} }
/*
Get available input/output file/buffer/stream formats
*/
NAN_METHOD(format) {
NanScope();
// Attribute names
Local<String> attrId = NanNew<String>("id");
Local<String> attrInput = NanNew<String>("input");
Local<String> attrOutput = NanNew<String>("output");
Local<String> attrFile = NanNew<String>("file");
Local<String> attrBuffer = NanNew<String>("buffer");
Local<String> attrStream = NanNew<String>("stream");
// Which load/save operations are available for each compressed format?
Local<Object> format = NanNew<Object>();
for (std::string f : {"jpeg", "png", "webp", "tiff", "magick", "openslide", "dz"}) {
// Input
Local<Object> input = NanNew<Object>();
input->Set(attrFile, NanNew<Boolean>(
vips_type_find("VipsOperation", (f + "load").c_str())));
input->Set(attrBuffer, NanNew<Boolean>(
vips_type_find("VipsOperation", (f + "load_buffer").c_str())));
input->Set(attrStream, input->Get(attrBuffer));
// Output
Local<Object> output = NanNew<Object>();
output->Set(attrFile, NanNew<Boolean>(
vips_type_find("VipsOperation", (f + "save").c_str())));
output->Set(attrBuffer, NanNew<Boolean>(
vips_type_find("VipsOperation", (f + "save_buffer").c_str())));
output->Set(attrStream, output->Get(attrBuffer));
// Other attributes
Local<Object> container = NanNew<Object>();
Local<String> formatId = NanNew<String>(f);
container->Set(attrId, formatId);
container->Set(attrInput, input);
container->Set(attrOutput, output);
// Add to set of formats
format->Set(formatId, container);
}
// Raw, uncompressed data
Local<Object> raw = NanNew<Object>();
raw->Set(attrId, NanNew<String>("raw"));
format->Set(NanNew<String>("raw"), raw);
// No support for raw input yet, so always false
Local<Boolean> unsupported = NanNew<Boolean>(false);
Local<Object> rawInput = NanNew<Object>();
rawInput->Set(attrFile, unsupported);
rawInput->Set(attrBuffer, unsupported);
rawInput->Set(attrStream, unsupported);
raw->Set(attrInput, rawInput);
// Raw output via Buffer/Stream is available in libvips >= 7.42.0
Local<Boolean> supportsRawOutput = NanNew<Boolean>(vips_version(0) >= 8 || (vips_version(0) == 7 && vips_version(1) >= 42));
Local<Object> rawOutput = NanNew<Object>();
rawOutput->Set(attrFile, unsupported);
rawOutput->Set(attrBuffer, supportsRawOutput);
rawOutput->Set(attrStream, supportsRawOutput);
raw->Set(attrOutput, rawOutput);
NanReturnValue(format);
}

View File

@@ -1,5 +1,5 @@
#ifndef SHARP_UTILITIES_H #ifndef SRC_UTILITIES_H_
#define SHARP_UTILITIES_H #define SRC_UTILITIES_H_
#include "nan.h" #include "nan.h"
@@ -7,5 +7,6 @@ NAN_METHOD(cache);
NAN_METHOD(concurrency); NAN_METHOD(concurrency);
NAN_METHOD(counters); NAN_METHOD(counters);
NAN_METHOD(libvipsVersion); NAN_METHOD(libvipsVersion);
NAN_METHOD(format);
#endif #endif // SRC_UTILITIES_H_

View File

@@ -9,10 +9,10 @@
}, },
"devDependencies": { "devDependencies": {
"imagemagick": "^0.1.3", "imagemagick": "^0.1.3",
"imagemagick-native": "^1.6.0", "imagemagick-native": "mash/node-imagemagick-native",
"gm": "^1.17.0", "gm": "^1.17.0",
"async": "^0.9.0", "async": "^0.9.0",
"semver": "^4.1.0", "semver": "^4.3.0",
"benchmark": "^1.0.0" "benchmark": "^1.0.0"
}, },
"license": "Apache 2.0", "license": "Apache 2.0",

View File

@@ -311,6 +311,18 @@ async.series({
} }
}); });
} }
}).add('sharp-normalise', {
defer: true,
fn: function(deferred) {
sharp(inputJpgBuffer).resize(width, height).normalise().toBuffer(function(err, buffer) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
}).add('sharp-greyscale', { }).add('sharp-greyscale', {
defer: true, defer: true,
fn: function(deferred) { fn: function(deferred) {
@@ -347,6 +359,18 @@ async.series({
} }
}); });
} }
}).add('sharp-without-chroma-subsampling', {
defer: true,
fn: function(deferred) {
sharp(inputJpgBuffer).resize(width, height).withoutChromaSubsampling().toBuffer(function(err, buffer) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
}).add('sharp-rotate', { }).add('sharp-rotate', {
defer: true, defer: true,
fn: function(deferred) { fn: function(deferred) {

View File

@@ -11,8 +11,11 @@ var fixtures = require('../fixtures');
var min = 320; var min = 320;
var max = 960; var max = 960;
// Nearest equivalent to bilinear
var magickFilter = 'Triangle';
var randomDimension = function() { var randomDimension = function() {
return Math.random() * (max - min) + min; return Math.ceil(Math.random() * (max - min) + min);
}; };
new Benchmark.Suite('random').add('imagemagick', { new Benchmark.Suite('random').add('imagemagick', {
@@ -23,7 +26,9 @@ new Benchmark.Suite('random').add('imagemagick', {
dstPath: fixtures.outputJpg, dstPath: fixtures.outputJpg,
quality: 0.8, quality: 0.8,
width: randomDimension(), width: randomDimension(),
height: randomDimension() height: randomDimension(),
format: 'jpg',
filter: magickFilter
}, function(err) { }, function(err) {
if (err) { if (err) {
throw err; throw err;
@@ -35,14 +40,18 @@ new Benchmark.Suite('random').add('imagemagick', {
}).add('gm', { }).add('gm', {
defer: true, defer: true,
fn: function(deferred) { fn: function(deferred) {
gm(fixtures.inputJpg).resize(randomDimension(), randomDimension()).quality(80).toBuffer(function (err, buffer) { gm(fixtures.inputJpg)
if (err) { .resize(randomDimension(), randomDimension())
throw err; .filter(magickFilter)
} else { .quality(80)
assert.notStrictEqual(null, buffer); .toBuffer(function (err, buffer) {
deferred.resolve(); if (err) {
} throw err;
}); } else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
} }
}).add('sharp', { }).add('sharp', {
defer: true, defer: true,

BIN
test/fixtures/2x2_fdcce6.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 B

BIN
test/fixtures/CMU-1-Small-Region.svs vendored Normal file

Binary file not shown.

BIN
test/fixtures/corrupt-header.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
test/fixtures/grey-8bit-alpha.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -14,9 +14,13 @@ module.exports = {
inputJpgWithGammaHoliness: getPath('gamma_dalai_lama_gray.jpg'), // http://www.4p8.com/eric.brasseur/gamma.html inputJpgWithGammaHoliness: getPath('gamma_dalai_lama_gray.jpg'), // http://www.4p8.com/eric.brasseur/gamma.html
inputJpgWithCmykProfile: getPath('Channel_digital_image_CMYK_color.jpg'), // http://en.wikipedia.org/wiki/File:Channel_digital_image_CMYK_color.jpg inputJpgWithCmykProfile: getPath('Channel_digital_image_CMYK_color.jpg'), // http://en.wikipedia.org/wiki/File:Channel_digital_image_CMYK_color.jpg
inputJpgWithCmykNoProfile: getPath('Channel_digital_image_CMYK_color_no_profile.jpg'), inputJpgWithCmykNoProfile: getPath('Channel_digital_image_CMYK_color_no_profile.jpg'),
inputJpgWithCorruptHeader: getPath('corrupt-header.jpg'),
inputJpgWithLowContrast: getPath('low-contrast.jpg'), // http://www.flickr.com/photos/grizdave/2569067123/
inputPng: getPath('50020484-00001.png'), // http://c.searspartsdirect.com/lis_png/PLDM/50020484-00001.png inputPng: getPath('50020484-00001.png'), // http://c.searspartsdirect.com/lis_png/PLDM/50020484-00001.png
inputPngWithTransparency: getPath('blackbug.png'), // public domain inputPngWithTransparency: getPath('blackbug.png'), // public domain
inputPngWithGreyAlpha: getPath('grey-8bit-alpha.png'),
inputPngWithOneColor: getPath('2x2_fdcce6.png'),
inputWebP: getPath('4.webp'), // http://www.gstatic.com/webp/gallery/4.webp inputWebP: getPath('4.webp'), // http://www.gstatic.com/webp/gallery/4.webp
inputTiff: getPath('G31D.TIF'), // http://www.fileformat.info/format/tiff/sample/e6c9a6e5253348f4aef6d17b534360ab/index.htm inputTiff: getPath('G31D.TIF'), // http://www.fileformat.info/format/tiff/sample/e6c9a6e5253348f4aef6d17b534360ab/index.htm
@@ -24,6 +28,8 @@ module.exports = {
inputSvg: getPath('Wikimedia-logo.svg'), // http://commons.wikimedia.org/wiki/File:Wikimedia-logo.svg inputSvg: getPath('Wikimedia-logo.svg'), // http://commons.wikimedia.org/wiki/File:Wikimedia-logo.svg
inputPsd: getPath('free-gearhead-pack.psd'), // https://dribbble.com/shots/1624241-Free-Gearhead-Vector-Pack inputPsd: getPath('free-gearhead-pack.psd'), // https://dribbble.com/shots/1624241-Free-Gearhead-Vector-Pack
inputSvs: getPath('CMU-1-Small-Region.svs'), // http://openslide.cs.cmu.edu/download/openslide-testdata/Aperio/CMU-1-Small-Region.svs
outputJpg: getPath('output.jpg'), outputJpg: getPath('output.jpg'),
outputPng: getPath('output.png'), outputPng: getPath('output.png'),
outputWebP: getPath('output.webp'), outputWebP: getPath('output.webp'),

BIN
test/fixtures/low-contrast.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -3,6 +3,6 @@ if ! type valgrind >/dev/null; then
exit 1 exit 1
fi fi
curl -O https://raw.githubusercontent.com/jcupitt/libvips/master/libvips.supp test/leak/libvips.supp curl -O https://raw.githubusercontent.com/jcupitt/libvips/master/libvips.supp
cd ../../ cd ../../
G_SLICE=always-malloc G_DEBUG=gc-friendly valgrind --suppressions=test/leak/libvips.supp --suppressions=test/leak/sharp.supp --leak-check=full --show-leak-kinds=definite,indirect,possible --num-callers=20 --trace-children=yes npm test G_SLICE=always-malloc G_DEBUG=gc-friendly valgrind --suppressions=test/leak/libvips.supp --suppressions=test/leak/sharp.supp --leak-check=full --show-leak-kinds=definite,indirect,possible --num-callers=20 --trace-children=yes npm test

View File

@@ -31,16 +31,18 @@ describe('Colour space conversion', function() {
.toFile(fixtures.path('output.greyscale-not.jpg'), done); .toFile(fixtures.path('output.greyscale-not.jpg'), done);
}); });
it('From 1-bit TIFF to sRGB WebP [slow]', function(done) { if (sharp.format.webp.output.buffer) {
sharp(fixtures.inputTiff) it('From 1-bit TIFF to sRGB WebP [slow]', function(done) {
.webp() sharp(fixtures.inputTiff)
.toBuffer(function(err, data, info) { .webp()
if (err) throw err; .toBuffer(function(err, data, info) {
assert.strictEqual(true, data.length > 0); if (err) throw err;
assert.strictEqual('webp', info.format); assert.strictEqual(true, data.length > 0);
done(); assert.strictEqual('webp', info.format);
}); done();
}); });
});
}
it('From CMYK to sRGB', function(done) { it('From CMYK to sRGB', function(done) {
sharp(fixtures.inputJpgWithCmykProfile) sharp(fixtures.inputJpgWithCmykProfile)

49
test/unit/cpplint.js Executable file
View File

@@ -0,0 +1,49 @@
'use strict';
var fs = require('fs');
var path = require('path');
var assert = require('assert');
var cpplint = require('node-cpplint/lib/');
describe('cpplint', function() {
// Ignore cpplint failures, possibly newline-related, on Windows
if (process.platform !== 'win32') {
// List C++ source files
fs.readdirSync(path.join(__dirname, '..', '..', 'src')).forEach(function (source) {
var file = path.join('src', source);
it(file, function(done) {
// Lint each source file
cpplint({
files: [file],
linelength: 140,
filters: {
legal: {
copyright: false
},
build: {
include: false,
include_order: false
},
whitespace: {
blank_line: false,
comments: false,
parens: false
}
}
}, function(err, report) {
if (err) {
throw err;
}
var expected = {};
expected[file] = [];
assert.deepEqual(expected, report);
done();
});
});
});
}
});

View File

@@ -28,18 +28,38 @@ describe('Embed', function() {
}); });
}); });
it('JPEG within WebP, to include alpha channel', function(done) { if (sharp.format.webp.output.buffer) {
sharp(fixtures.inputJpg) it('JPEG within WebP, to include alpha channel', function(done) {
.resize(320, 240) sharp(fixtures.inputJpg)
.background({r: 0, g: 0, b: 0, a: 0}) .resize(320, 240)
.background({r: 0, g: 0, b: 0, a: 0})
.embed()
.webp()
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual('webp', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
sharp(data).metadata(function(err, metadata) {
if (err) throw err;
assert.strictEqual(4, metadata.channels);
done();
});
});
});
}
it('PNG with alpha channel', function(done) {
sharp(fixtures.inputPngWithTransparency)
.resize(50, 50)
.embed() .embed()
.webp()
.toBuffer(function(err, data, info) { .toBuffer(function(err, data, info) {
if (err) throw err; if (err) throw err;
assert.strictEqual(true, data.length > 0); assert.strictEqual(true, data.length > 0);
assert.strictEqual('webp', info.format); assert.strictEqual('png', info.format);
assert.strictEqual(320, info.width); assert.strictEqual(50, info.width);
assert.strictEqual(240, info.height); assert.strictEqual(50, info.height);
sharp(data).metadata(function(err, metadata) { sharp(data).metadata(function(err, metadata) {
if (err) throw err; if (err) throw err;
assert.strictEqual(4, metadata.channels); assert.strictEqual(4, metadata.channels);

View File

@@ -31,16 +31,18 @@ describe('Partial image extraction', function() {
}); });
}); });
it('WebP', function(done) { if (sharp.format.webp.output.file) {
sharp(fixtures.inputWebP) it('WebP', function(done) {
.extract(50, 100, 125, 200) sharp(fixtures.inputWebP)
.toFile(fixtures.path('output.extract.webp'), function(err, info) { .extract(50, 100, 125, 200)
if (err) throw err; .toFile(fixtures.path('output.extract.webp'), function(err, info) {
assert.strictEqual(125, info.width); if (err) throw err;
assert.strictEqual(200, info.height); assert.strictEqual(125, info.width);
done(); assert.strictEqual(200, info.height);
}); done();
}); });
});
}
it('TIFF', function(done) { it('TIFF', function(done) {
sharp(fixtures.inputTiff) sharp(fixtures.inputTiff)
@@ -92,4 +94,86 @@ describe('Partial image extraction', function() {
}); });
}); });
it('Extract then rotate', function(done) {
sharp(fixtures.inputJpg)
.extract(10, 10, 100, 100)
.rotate(90)
.toFile(fixtures.path('output.extract.extract-then-rotate.jpg'), function(err, info) {
if (err) throw err;
assert.strictEqual(100, info.width);
assert.strictEqual(100, info.height);
done();
});
});
it('Rotate then extract', function(done) {
sharp(fixtures.inputJpg)
.rotate(90)
.extract(10, 10, 100, 100)
.toFile(fixtures.path('output.extract.rotate-then-extract.jpg'), function(err, info) {
if (err) throw err;
assert.strictEqual(100, info.width);
assert.strictEqual(100, info.height);
done();
});
});
describe('Invalid parameters', function() {
it('Undefined', function(done) {
var isValid = true;
try {
sharp(fixtures.inputJpg).extract();
} catch (err) {
isValid = false;
}
assert.strictEqual(false, isValid);
done();
});
it('String top', function(done) {
var isValid = true;
try {
sharp(fixtures.inputJpg).extract('spoons', 10, 10, 10);
} catch (err) {
isValid = false;
}
assert.strictEqual(false, isValid);
done();
});
it('Non-integral left', function(done) {
var isValid = true;
try {
sharp(fixtures.inputJpg).extract(10, 10.2, 10, 10);
} catch (err) {
isValid = false;
}
assert.strictEqual(false, isValid);
done();
});
it('Negative width - negative', function(done) {
var isValid = true;
try {
sharp(fixtures.inputJpg).extract(10, 10, -10, 10);
} catch (err) {
isValid = false;
}
assert.strictEqual(false, isValid);
done();
});
it('Null height', function(done) {
var isValid = true;
try {
sharp(fixtures.inputJpg).extract(10, 10, 10, null);
} catch (err) {
isValid = false;
}
assert.strictEqual(false, isValid);
done();
});
});
}); });

View File

@@ -93,4 +93,14 @@ describe('Interpolation', function() {
}); });
}); });
it('unknown interpolator throws', function(done) {
var isValid = false;
try {
sharp().interpolateWith('nonexistant');
isValid = true;
} catch (e) {}
assert(!isValid);
done();
});
}); });

View File

@@ -135,10 +135,11 @@ describe('Input/output', function() {
readableButNotAnImage.pipe(writable); readableButNotAnImage.pipe(writable);
}); });
it('Sequential read', function(done) { it('Sequential read, force JPEG', function(done) {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.sequentialRead() .sequentialRead()
.resize(320, 240) .resize(320, 240)
.toFormat(sharp.format.jpeg)
.toBuffer(function(err, data, info) { .toBuffer(function(err, data, info) {
if (err) throw err; if (err) throw err;
assert.strictEqual(true, data.length > 0); assert.strictEqual(true, data.length > 0);
@@ -150,10 +151,11 @@ describe('Input/output', function() {
}); });
}); });
it('Not sequential read', function(done) { it('Not sequential read, force JPEG', function(done) {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.sequentialRead(false) .sequentialRead(false)
.resize(320, 240) .resize(320, 240)
.toFormat('jpeg')
.toBuffer(function(err, data, info) { .toBuffer(function(err, data, info) {
if (err) throw err; if (err) throw err;
assert.strictEqual(true, data.length > 0); assert.strictEqual(true, data.length > 0);
@@ -200,27 +202,23 @@ describe('Input/output', function() {
}); });
it('Fail when input is empty Buffer', function(done) { it('Fail when input is empty Buffer', function(done) {
var failed = true; sharp(new Buffer(0)).toBuffer().then(function () {
try { assert(false);
sharp(new Buffer(0)); done();
failed = false; }).catch(function (err) {
} catch (err) {
assert(err instanceof Error); assert(err instanceof Error);
} done();
assert(failed); });
done();
}); });
it('Fail when input is invalid Buffer', function(done) { it('Fail when input is invalid Buffer', function(done) {
var failed = true; sharp(new Buffer([0x1, 0x2, 0x3, 0x4])).toBuffer().then(function () {
try { assert(false);
sharp(new Buffer([0x1, 0x2, 0x3, 0x4])); done();
failed = false; }).catch(function (err) {
} catch (err) {
assert(err instanceof Error); assert(err instanceof Error);
} done();
assert(failed); });
done();
}); });
it('Promises/A+', function(done) { it('Promises/A+', function(done) {
@@ -265,7 +263,34 @@ describe('Input/output', function() {
done(); done();
}); });
it('Progressive image', function(done) { it('Progressive JPEG image', function(done) {
sharp(fixtures.inputJpg)
.resize(320, 240)
.progressive(false)
.toBuffer(function(err, nonProgressiveData, nonProgressiveInfo) {
if (err) throw err;
assert.strictEqual(true, nonProgressiveData.length > 0);
assert.strictEqual(nonProgressiveData.length, nonProgressiveInfo.size);
assert.strictEqual('jpeg', nonProgressiveInfo.format);
assert.strictEqual(320, nonProgressiveInfo.width);
assert.strictEqual(240, nonProgressiveInfo.height);
sharp(fixtures.inputJpg)
.resize(320, 240)
.progressive()
.toBuffer(function(err, progressiveData, progressiveInfo) {
if (err) throw err;
assert.strictEqual(true, progressiveData.length > 0);
assert.strictEqual(progressiveData.length, progressiveInfo.size);
assert.strictEqual(false, progressiveData.length === nonProgressiveData.length);
assert.strictEqual('jpeg', progressiveInfo.format);
assert.strictEqual(320, progressiveInfo.width);
assert.strictEqual(240, progressiveInfo.height);
done();
});
});
});
it('Progressive PNG image', function(done) {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(320, 240) .resize(320, 240)
.png() .png()
@@ -292,6 +317,48 @@ describe('Input/output', function() {
}); });
}); });
if (sharp.format.webp.output.buffer) {
it('WebP output', function(done) {
sharp(fixtures.inputJpg)
.resize(320, 240)
.toFormat(sharp.format.webp)
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual('webp', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
done();
});
});
}
it('Invalid output format', function(done) {
var isValid = false;
try {
sharp().toFormat('zoinks');
isValid = true;
} catch (e) {}
assert(!isValid);
done();
});
it('File input with corrupt header fails gracefully', function(done) {
sharp(fixtures.inputJpgWithCorruptHeader)
.toBuffer(function(err) {
assert.strictEqual(true, !!err);
done();
});
});
it('Buffer input with corrupt header fails gracefully', function(done) {
sharp(fs.readFileSync(fixtures.inputJpgWithCorruptHeader))
.toBuffer(function(err) {
assert.strictEqual(true, !!err);
done();
});
});
describe('Output filename without extension uses input format', function() { describe('Output filename without extension uses input format', function() {
it('JPEG', function(done) { it('JPEG', function(done) {
@@ -329,17 +396,19 @@ describe('Input/output', function() {
}); });
}); });
it('WebP', function(done) { if (sharp.format.webp.input.file) {
sharp(fixtures.inputWebP).resize(320, 80).toFile(fixtures.outputZoinks, function(err, info) { it('WebP', function(done) {
if (err) throw err; sharp(fixtures.inputWebP).resize(320, 80).toFile(fixtures.outputZoinks, function(err, info) {
assert.strictEqual(true, info.size > 0); if (err) throw err;
assert.strictEqual('webp', info.format); assert.strictEqual(true, info.size > 0);
assert.strictEqual(320, info.width); assert.strictEqual('webp', info.format);
assert.strictEqual(80, info.height); assert.strictEqual(320, info.width);
fs.unlinkSync(fixtures.outputZoinks); assert.strictEqual(80, info.height);
done(); fs.unlinkSync(fixtures.outputZoinks);
done();
});
}); });
}); }
it('TIFF', function(done) { it('TIFF', function(done) {
sharp(fixtures.inputTiff).resize(320, 80).toFile(fixtures.outputZoinks, function(err, info) { sharp(fixtures.inputTiff).resize(320, 80).toFile(fixtures.outputZoinks, function(err, info) {
@@ -384,8 +453,8 @@ describe('Input/output', function() {
done(); done();
}); });
if (semver.gte(sharp.libvipsVersion(), '7.41.0')) { if (semver.gte(sharp.libvipsVersion(), '7.42.0')) {
it('withoutAdaptiveFiltering generates smaller file [libvips ' + sharp.libvipsVersion() + '>=7.41.0]', function(done) { it('withoutAdaptiveFiltering generates smaller file [libvips ' + sharp.libvipsVersion() + '>=7.42.0]', function(done) {
// First generate with adaptive filtering // First generate with adaptive filtering
sharp(fixtures.inputPng) sharp(fixtures.inputPng)
.resize(320, 240) .resize(320, 240)
@@ -417,36 +486,160 @@ describe('Input/output', function() {
}); });
it('Convert SVG to PNG', function(done) { it('Without chroma subsampling generates larger file', function(done) {
sharp(fixtures.inputSvg) // First generate with chroma subsampling (default)
.resize(100, 100) sharp(fixtures.inputJpg)
.png()
.toFile(fixtures.path('output.svg.png'), function(err, info) {
if (err) throw err;
assert.strictEqual(true, info.size > 0);
assert.strictEqual('png', info.format);
assert.strictEqual(100, info.width);
assert.strictEqual(100, info.height);
done();
});
});
it('Convert PSD to PNG', function(done) {
sharp(fixtures.inputPsd)
.resize(320, 240) .resize(320, 240)
.png() .withoutChromaSubsampling(false)
.toFile(fixtures.path('output.psd.png'), function(err, info) { .toBuffer(function(err, withChromaSubsamplingData, withChromaSubsamplingInfo) {
if (err) throw err; if (err) throw err;
assert.strictEqual(true, info.size > 0); assert.strictEqual(true, withChromaSubsamplingData.length > 0);
assert.strictEqual('png', info.format); assert.strictEqual(withChromaSubsamplingData.length, withChromaSubsamplingInfo.size);
assert.strictEqual(320, info.width); assert.strictEqual('jpeg', withChromaSubsamplingInfo.format);
assert.strictEqual(240, info.height); assert.strictEqual(320, withChromaSubsamplingInfo.width);
done(); assert.strictEqual(240, withChromaSubsamplingInfo.height);
// Then generate without
sharp(fixtures.inputJpg)
.resize(320, 240)
.withoutChromaSubsampling()
.toBuffer(function(err, withoutChromaSubsamplingData, withoutChromaSubsamplingInfo) {
if (err) throw err;
assert.strictEqual(true, withoutChromaSubsamplingData.length > 0);
assert.strictEqual(withoutChromaSubsamplingData.length, withoutChromaSubsamplingInfo.size);
assert.strictEqual('jpeg', withoutChromaSubsamplingInfo.format);
assert.strictEqual(320, withoutChromaSubsamplingInfo.width);
assert.strictEqual(240, withoutChromaSubsamplingInfo.height);
assert.strictEqual(true, withChromaSubsamplingData.length < withoutChromaSubsamplingData.length);
done();
});
}); });
}); });
if (semver.gte(sharp.libvipsVersion(), '7.40.0')) { if (semver.gte(sharp.libvipsVersion(), '8.0.0')) {
it('Load TIFF from Buffer [libvips ' + sharp.libvipsVersion() + '>=7.40.0]', function(done) { it('Trellis quantisation [libvips ' + sharp.libvipsVersion() + '>=8.0.0]', function(done) {
// First generate without
sharp(fixtures.inputJpg)
.resize(320, 240)
.trellisQuantisation(false)
.toBuffer(function(err, withoutData, withoutInfo) {
if (err) throw err;
assert.strictEqual(true, withoutData.length > 0);
assert.strictEqual(withoutData.length, withoutInfo.size);
assert.strictEqual('jpeg', withoutInfo.format);
assert.strictEqual(320, withoutInfo.width);
assert.strictEqual(240, withoutInfo.height);
// Then generate with
sharp(fixtures.inputJpg)
.resize(320, 240)
.trellisQuantization()
.toBuffer(function(err, withData, withInfo) {
if (err) throw err;
assert.strictEqual(true, withData.length > 0);
assert.strictEqual(withData.length, withInfo.size);
assert.strictEqual('jpeg', withInfo.format);
assert.strictEqual(320, withInfo.width);
assert.strictEqual(240, withInfo.height);
// Verify image is same (as mozjpeg may not be present) size or less
assert.strictEqual(true, withData.length <= withoutData.length);
done();
});
});
});
it('Overshoot deringing [libvips ' + sharp.libvipsVersion() + '>=8.0.0]', function(done) {
// First generate without
sharp(fixtures.inputJpg)
.resize(320, 240)
.overshootDeringing(false)
.toBuffer(function(err, withoutData, withoutInfo) {
if (err) throw err;
assert.strictEqual(true, withoutData.length > 0);
assert.strictEqual(withoutData.length, withoutInfo.size);
assert.strictEqual('jpeg', withoutInfo.format);
assert.strictEqual(320, withoutInfo.width);
assert.strictEqual(240, withoutInfo.height);
// Then generate with
sharp(fixtures.inputJpg)
.resize(320, 240)
.overshootDeringing()
.toBuffer(function(err, withData, withInfo) {
if (err) throw err;
assert.strictEqual(true, withData.length > 0);
assert.strictEqual(withData.length, withInfo.size);
assert.strictEqual('jpeg', withInfo.format);
assert.strictEqual(320, withInfo.width);
assert.strictEqual(240, withInfo.height);
done();
});
});
});
it('Optimise scans [libvips ' + sharp.libvipsVersion() + '>=8.0.0]', function(done) {
// First generate without
sharp(fixtures.inputJpg)
.resize(320, 240)
.optimiseScans(false)
.toBuffer(function(err, withoutData, withoutInfo) {
if (err) throw err;
assert.strictEqual(true, withoutData.length > 0);
assert.strictEqual(withoutData.length, withoutInfo.size);
assert.strictEqual('jpeg', withoutInfo.format);
assert.strictEqual(320, withoutInfo.width);
assert.strictEqual(240, withoutInfo.height);
// Then generate with
sharp(fixtures.inputJpg)
.resize(320, 240)
.optimizeScans()
.toBuffer(function(err, withData, withInfo) {
if (err) throw err;
assert.strictEqual(true, withData.length > 0);
assert.strictEqual(withData.length, withInfo.size);
assert.strictEqual('jpeg', withInfo.format);
assert.strictEqual(320, withInfo.width);
assert.strictEqual(240, withInfo.height);
// Verify image is of a different size (progressive output even without mozjpeg)
assert.strictEqual(true, withData.length != withoutData.length);
done();
});
});
});
}
if (sharp.format.magick.input.file) {
it('Convert SVG, if supported, to PNG', function(done) {
sharp(fixtures.inputSvg)
.resize(100, 100)
.toFormat('png')
.toFile(fixtures.path('output.svg.png'), function(err, info) {
if (err) {
assert.strictEqual(0, err.message.indexOf('Input file is of an unsupported image format'));
} else {
assert.strictEqual(true, info.size > 0);
assert.strictEqual('png', info.format);
assert.strictEqual(100, info.width);
assert.strictEqual(100, info.height);
}
done();
});
});
}
if (sharp.format.magick.input.file) {
it('Convert PSD to PNG', function(done) {
sharp(fixtures.inputPsd)
.resize(320, 240)
.toFormat(sharp.format.png)
.toFile(fixtures.path('output.psd.png'), function(err, info) {
if (err) throw err;
assert.strictEqual(true, info.size > 0);
assert.strictEqual('png', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
done();
});
});
}
if (sharp.format.tiff.input.buffer) {
it('Load TIFF from Buffer', function(done) {
var inputTiffBuffer = fs.readFileSync(fixtures.inputTiff); var inputTiffBuffer = fs.readFileSync(fixtures.inputTiff);
sharp(inputTiffBuffer) sharp(inputTiffBuffer)
.resize(320, 240) .resize(320, 240)
@@ -463,4 +656,160 @@ describe('Input/output', function() {
}); });
} }
if (sharp.format.magick.input.buffer) {
it('Load GIF from Buffer', function(done) {
var inputGifBuffer = fs.readFileSync(fixtures.inputGif);
sharp(inputGifBuffer)
.resize(320, 240)
.jpeg()
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual(data.length, info.size);
assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
done();
});
});
}
if (sharp.format.openslide.input.file) {
it('Load Aperio SVS file via Openslide', function(done) {
sharp(fixtures.inputSvs)
.resize(320, 240)
.jpeg()
.toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
done();
});
});
}
if (sharp.format.raw.output.buffer) {
describe('Ouput raw, uncompressed image data', function() {
it('1 channel greyscale image', function(done) {
sharp(fixtures.inputJpg)
.greyscale()
.resize(32, 24)
.raw()
.toBuffer(function(err, data, info) {
assert.strictEqual(32 * 24 * 1, info.size);
assert.strictEqual(data.length, info.size);
assert.strictEqual('raw', info.format);
assert.strictEqual(32, info.width);
assert.strictEqual(24, info.height);
done();
});
});
it('3 channel colour image without transparency', function(done) {
sharp(fixtures.inputJpg)
.resize(32, 24)
.toFormat('raw')
.toBuffer(function(err, data, info) {
assert.strictEqual(32 * 24 * 3, info.size);
assert.strictEqual(data.length, info.size);
assert.strictEqual('raw', info.format);
assert.strictEqual(32, info.width);
assert.strictEqual(24, info.height);
done();
});
});
it('4 channel colour image with transparency', function(done) {
sharp(fixtures.inputPngWithTransparency)
.resize(32, 24)
.toFormat(sharp.format.raw)
.toBuffer(function(err, data, info) {
assert.strictEqual(32 * 24 * 4, info.size);
assert.strictEqual(data.length, info.size);
assert.strictEqual('raw', info.format);
assert.strictEqual(32, info.width);
assert.strictEqual(24, info.height);
done();
});
});
});
}
describe('Limit pixel count of input image', function() {
it('Invalid fails - negative', function(done) {
var isValid = false;
try {
sharp().limitInputPixels(-1);
isValid = true;
} catch (e) {}
assert(!isValid);
done();
});
it('Invalid fails - float', function(done) {
var isValid = false;
try {
sharp().limitInputPixels(12.3);
isValid = true;
} catch (e) {}
assert(!isValid);
done();
});
it('Invalid fails - string', function(done) {
var isValid = false;
try {
sharp().limitInputPixels('fail');
isValid = true;
} catch (e) {}
assert(!isValid);
done();
});
it('Same size as input works', function(done) {
sharp(fixtures.inputJpg).metadata(function(err, metadata) {
if (err) throw err;
sharp(fixtures.inputJpg)
.limitInputPixels(metadata.width * metadata.height)
.toBuffer(function(err) {
assert.strictEqual(true, !err);
done();
});
});
});
it('Smaller than input fails', function(done) {
sharp(fixtures.inputJpg).metadata(function(err, metadata) {
if (err) throw err;
sharp(fixtures.inputJpg)
.limitInputPixels(metadata.width * metadata.height - 1)
.toBuffer(function(err) {
assert.strictEqual(true, !!err);
done();
});
});
});
});
it('Queue length change events', function(done) {
var eventCounter = 0;
var queueListener = function(queueLength) {
assert.strictEqual(true, queueLength === 0 || queueLength === 1);
eventCounter++;
};
sharp.queue.on('change', queueListener);
sharp(fixtures.inputJpg)
.resize(320, 240)
.toBuffer(function(err) {
process.nextTick(function() {
sharp.queue.removeListener('change', queueListener);
if (err) throw err;
assert.strictEqual(2, eventCounter);
done();
});
});
});
}); });

View File

@@ -82,19 +82,21 @@ describe('Image metadata', function() {
}); });
}); });
it('WebP', function(done) { if (sharp.format.webp.input.file) {
sharp(fixtures.inputWebP).metadata(function(err, metadata) { it('WebP', function(done) {
if (err) throw err; sharp(fixtures.inputWebP).metadata(function(err, metadata) {
assert.strictEqual('webp', metadata.format); if (err) throw err;
assert.strictEqual(1024, metadata.width); assert.strictEqual('webp', metadata.format);
assert.strictEqual(772, metadata.height); assert.strictEqual(1024, metadata.width);
assert.strictEqual('srgb', metadata.space); assert.strictEqual(772, metadata.height);
assert.strictEqual(3, metadata.channels); assert.strictEqual('srgb', metadata.space);
assert.strictEqual(false, metadata.hasProfile); assert.strictEqual(3, metadata.channels);
assert.strictEqual(false, metadata.hasAlpha); assert.strictEqual(false, metadata.hasProfile);
done(); assert.strictEqual(false, metadata.hasAlpha);
done();
});
}); });
}); }
it('GIF via libmagick', function(done) { it('GIF via libmagick', function(done) {
sharp(fixtures.inputGif).metadata(function(err, metadata) { sharp(fixtures.inputGif).metadata(function(err, metadata) {
@@ -109,6 +111,22 @@ describe('Image metadata', function() {
}); });
}); });
if (sharp.format.openslide.input.file) {
it('Aperio SVS via openslide', function(done) {
sharp(fixtures.inputSvs).metadata(function(err, metadata) {
if (err) throw err;
assert.strictEqual('openslide', metadata.format);
assert.strictEqual(2220, metadata.width);
assert.strictEqual(2967, metadata.height);
assert.strictEqual(4, metadata.channels);
assert.strictEqual('rgb', metadata.space);
assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(true, metadata.hasAlpha);
done();
});
});
}
it('File in, Promise out', function(done) { it('File in, Promise out', function(done) {
sharp(fixtures.inputJpg).metadata().then(function(metadata) { sharp(fixtures.inputJpg).metadata().then(function(metadata) {
assert.strictEqual('jpeg', metadata.format); assert.strictEqual('jpeg', metadata.format);
@@ -176,7 +194,7 @@ describe('Image metadata', function() {
assert.strictEqual(3, metadata.channels); assert.strictEqual(3, metadata.channels);
assert.strictEqual(false, metadata.hasProfile); assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha); assert.strictEqual(false, metadata.hasAlpha);
image.resize(metadata.width / 2).toBuffer(function(err, data, info) { image.resize(Math.floor(metadata.width / 2)).toBuffer(function(err, data, info) {
if (err) throw err; if (err) throw err;
assert.strictEqual(true, data.length > 0); assert.strictEqual(true, data.length > 0);
assert.strictEqual(1362, info.width); assert.strictEqual(1362, info.width);
@@ -216,4 +234,20 @@ describe('Image metadata', function() {
}); });
}); });
it('File input with corrupt header fails gracefully', function(done) {
sharp(fixtures.inputJpgWithCorruptHeader)
.metadata(function(err) {
assert.strictEqual(true, !!err);
done();
});
});
it('Buffer input with corrupt header fails gracefully', function(done) {
sharp(fs.readFileSync(fixtures.inputJpgWithCorruptHeader))
.metadata(function(err) {
assert.strictEqual(true, !!err);
done();
});
});
}); });

129
test/unit/normalize.js Executable file
View File

@@ -0,0 +1,129 @@
'use strict';
var assert = require('assert');
var sharp = require('../../index');
var fixtures = require('../fixtures');
sharp.cache(0);
describe('Normalization', function () {
it('uses the same prototype for both spellings', function () {
assert.strictEqual(sharp.prototype.normalize, sharp.prototype.normalise);
});
// Normalize is currently unavailable on Windows
if (process.platform !== 'win32') {
it('spreads rgb image values between 0 and 255', function(done) {
sharp(fixtures.inputJpgWithLowContrast)
.normalize()
.raw()
.toBuffer(function (err, data, info) {
if (err) throw err;
var min = 255, max = 0, i;
for (i = 0; i < data.length; i += 3) {
min = Math.min(min, data[i], data[i + 1], data[i + 2]);
max = Math.max(max, data[i], data[i + 1], data[i + 2]);
}
assert.strictEqual(0, min);
assert.strictEqual(255, max);
return done();
});
});
it('spreads grayscaled image values between 0 and 255', function(done) {
sharp(fixtures.inputJpgWithLowContrast)
.gamma()
.greyscale()
.normalize(true)
.raw()
.toBuffer(function (err, data, info) {
if (err) throw err;
var min = 255, max = 0, i;
for (i = 0; i < data.length; i++) {
min = Math.min(min, data[i]);
max = Math.max(max, data[i]);
}
assert.strictEqual(0, min);
assert.strictEqual(255, max);
return done();
});
});
it('stretches greyscale images with alpha channel', function (done) {
sharp(fixtures.inputPngWithGreyAlpha)
.normalize()
.raw()
.toBuffer(function (err, data, info) {
var min = 255, max = 0, i;
for (i = 0; i < data.length; i++) {
min = Math.min(min, data[i]);
max = Math.max(max, data[i]);
}
assert.strictEqual(0, min);
assert.strictEqual(255, max);
return done();
});
});
it('keeps an existing alpha channel', function (done) {
sharp(fixtures.inputPngWithTransparency)
.normalize()
.toBuffer(function (err, data, info) {
sharp(data)
.metadata()
.then(function (metadata) {
assert.strictEqual(4, metadata.channels);
assert.strictEqual(true, metadata.hasAlpha);
assert.strictEqual('srgb', metadata.space);
})
.finally(done);
});
});
it('keeps the alpha channel of greyscale images intact', function (done) {
sharp(fixtures.inputPngWithGreyAlpha)
.normalize()
.toBuffer(function (err, data, info) {
sharp(data)
.metadata()
.then(function (metadata) {
assert.strictEqual(true, metadata.hasAlpha);
assert.strictEqual(4, metadata.channels);
assert.strictEqual('srgb', metadata.space);
})
.finally(done);
});
});
it('returns a black image for images with only one color', function (done) {
sharp(fixtures.inputPngWithOneColor)
.normalize()
.toBuffer()
.bind({})
.then(function (imageData) {
this.imageData = imageData;
return sharp(imageData)
.metadata();
})
.then(function (metadata) {
assert.strictEqual(false, metadata.hasAlpha);
assert.strictEqual(3, metadata.channels);
assert.strictEqual('srgb', metadata.space);
})
.then(function () {
return sharp(this.imageData)
.raw()
.toBuffer();
})
.then(function (rawData) {
var blackBuffer = new Buffer([0,0,0, 0,0,0, 0,0,0, 0,0,0]);
assert.strictEqual(blackBuffer.toString(), rawData.toString());
})
.finally(done);
});
}
});

View File

@@ -64,7 +64,7 @@ describe('Resize dimensions', function() {
}); });
}); });
it('Invalid width', function(done) { it('Invalid width - NaN', function(done) {
var isValid = true; var isValid = true;
try { try {
sharp(fixtures.inputJpg).resize('spoons', 240); sharp(fixtures.inputJpg).resize('spoons', 240);
@@ -75,7 +75,7 @@ describe('Resize dimensions', function() {
done(); done();
}); });
it('Invalid height', function(done) { it('Invalid height - NaN', function(done) {
var isValid = true; var isValid = true;
try { try {
sharp(fixtures.inputJpg).resize(320, 'spoons'); sharp(fixtures.inputJpg).resize(320, 'spoons');
@@ -86,6 +86,50 @@ describe('Resize dimensions', function() {
done(); done();
}); });
it('Invalid width - float', function(done) {
var isValid = true;
try {
sharp(fixtures.inputJpg).resize(1.5, 240);
} catch (err) {
isValid = false;
}
assert.strictEqual(false, isValid);
done();
});
it('Invalid height - float', function(done) {
var isValid = true;
try {
sharp(fixtures.inputJpg).resize(320, 1.5);
} catch (err) {
isValid = false;
}
assert.strictEqual(false, isValid);
done();
});
it('Invalid width - too large', function(done) {
var isValid = true;
try {
sharp(fixtures.inputJpg).resize(0x4000, 240);
} catch (err) {
isValid = false;
}
assert.strictEqual(false, isValid);
done();
});
it('Invalid height - too large', function(done) {
var isValid = true;
try {
sharp(fixtures.inputJpg).resize(320, 0x4000);
} catch (err) {
isValid = false;
}
assert.strictEqual(false, isValid);
done();
});
it('TIFF embed known to cause rounding errors', function(done) { it('TIFF embed known to cause rounding errors', function(done) {
sharp(fixtures.inputTiff).resize(240, 320).embed().jpeg().toBuffer(function(err, data, info) { sharp(fixtures.inputTiff).resize(240, 320).embed().jpeg().toBuffer(function(err, data, info) {
if (err) throw err; if (err) throw err;
@@ -141,6 +185,39 @@ describe('Resize dimensions', function() {
}); });
}); });
it('Min width or height considering ratio (landscape)', function(done) {
sharp(fixtures.inputJpg).resize(320, 320).min().toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual('jpeg', info.format);
assert.strictEqual(392, info.width);
assert.strictEqual(320, info.height);
done();
});
});
it('Min width or height considering ratio (portrait)', function(done) {
sharp(fixtures.inputTiff).resize(320, 320).min().jpeg().toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(422, info.height);
done();
});
});
it('Provide only one dimension with min, should default to crop', function(done) {
sharp(fixtures.inputJpg).resize(320).min().toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(261, info.height);
done();
});
});
it('Do not enlarge when input width is already less than output width', function(done) { it('Do not enlarge when input width is already less than output width', function(done) {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(2800) .resize(2800)
@@ -182,5 +259,104 @@ describe('Resize dimensions', function() {
done(); done();
}); });
}); });
it('Downscale width and height, ignoring aspect ratio', function(done) {
sharp(fixtures.inputJpg).resize(320, 320).ignoreAspectRatio().toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(320, info.height);
done();
});
});
it('Downscale width, ignoring aspect ratio', function(done) {
sharp(fixtures.inputJpg).resize(320).ignoreAspectRatio().toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(2225, info.height);
done();
});
});
it('Downscale height, ignoring aspect ratio', function(done) {
sharp(fixtures.inputJpg).resize(null, 320).ignoreAspectRatio().toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual('jpeg', info.format);
assert.strictEqual(2725, info.width);
assert.strictEqual(320, info.height);
done();
});
});
it('Upscale width and height, ignoring aspect ratio', function(done) {
sharp(fixtures.inputJpg).resize(3000, 3000).ignoreAspectRatio().toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual('jpeg', info.format);
assert.strictEqual(3000, info.width);
assert.strictEqual(3000, info.height);
done();
});
});
it('Upscale width, ignoring aspect ratio', function(done) {
sharp(fixtures.inputJpg).resize(3000).ignoreAspectRatio().toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual('jpeg', info.format);
assert.strictEqual(3000, info.width);
assert.strictEqual(2225, info.height);
done();
});
});
it('Upscale height, ignoring aspect ratio', function(done) {
sharp(fixtures.inputJpg).resize(null, 3000).ignoreAspectRatio().toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual('jpeg', info.format);
assert.strictEqual(2725, info.width);
assert.strictEqual(3000, info.height);
done();
});
});
it('Downscale width, upscale height, ignoring aspect ratio', function(done) {
sharp(fixtures.inputJpg).resize(320, 3000).ignoreAspectRatio().toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(3000, info.height);
done();
});
});
it('Upscale width, downscale height, ignoring aspect ratio', function(done) {
sharp(fixtures.inputJpg).resize(3000, 320).ignoreAspectRatio().toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual('jpeg', info.format);
assert.strictEqual(3000, info.width);
assert.strictEqual(320, info.height);
done();
});
});
it('Identity transform, ignoring aspect ratio', function(done) {
sharp(fixtures.inputJpg).ignoreAspectRatio().toBuffer(function(err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual('jpeg', info.format);
assert.strictEqual(2725, info.width);
assert.strictEqual(2225, info.height);
done();
});
});
}); });

204
test/unit/tile.js Executable file
View File

@@ -0,0 +1,204 @@
'use strict';
var fs = require('fs');
var path = require('path');
var assert = require('assert');
var async = require('async');
var rimraf = require('rimraf');
var sharp = require('../../index');
var fixtures = require('../fixtures');
sharp.cache(0);
// Verifies all tiles in a given dz output directory are <= size
var assertDeepZoomTiles = function(directory, expectedSize, expectedLevels, done) {
// Get levels
var levels = fs.readdirSync(directory);
assert.strictEqual(expectedLevels, levels.length);
// Get tiles
var tiles = [];
levels.forEach(function(level) {
// Verify level directory name
assert.strictEqual(true, /^[0-9]+$/.test(level));
fs.readdirSync(path.join(directory, level)).forEach(function(tile) {
// Verify tile file name
assert.strictEqual(true, /^[0-9]+_[0-9]+\.jpeg$/.test(tile));
tiles.push(path.join(directory, level, tile));
});
});
// Verify each tile is <= expectedSize
async.eachSeries(tiles, function(tile, done) {
sharp(tile).metadata(function(err, metadata) {
if (err) {
done(err);
} else {
assert.strictEqual('jpeg', metadata.format);
assert.strictEqual('srgb', metadata.space);
assert.strictEqual(3, metadata.channels);
assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha);
assert.strictEqual(true, metadata.width <= expectedSize);
assert.strictEqual(true, metadata.height <= expectedSize);
done();
}
});
}, done);
};
describe('Tile', function() {
describe('Invalid tile values', function() {
it('size - NaN', function(done) {
var isValid = true;
try {
sharp().tile('zoinks');
} catch (err) {
isValid = false;
}
assert.strictEqual(false, isValid);
done();
});
it('size - float', function(done) {
var isValid = true;
try {
sharp().tile(1.1);
} catch (err) {
isValid = false;
}
assert.strictEqual(false, isValid);
done();
});
it('size - negative', function(done) {
var isValid = true;
try {
sharp().tile(-1);
} catch (err) {
isValid = false;
}
assert.strictEqual(false, isValid);
done();
});
it('size - zero', function(done) {
var isValid = true;
try {
sharp().tile(0);
} catch (err) {
isValid = false;
}
assert.strictEqual(false, isValid);
done();
});
it('size - too large', function(done) {
var isValid = true;
try {
sharp().tile(8193);
} catch (err) {
isValid = false;
}
assert.strictEqual(false, isValid);
done();
});
it('overlap - NaN', function(done) {
var isValid = true;
try {
sharp().tile(null, 'zoinks');
} catch (err) {
isValid = false;
}
assert.strictEqual(false, isValid);
done();
});
it('overlap - float', function(done) {
var isValid = true;
try {
sharp().tile(null, 1.1);
} catch (err) {
isValid = false;
}
assert.strictEqual(false, isValid);
done();
});
it('overlap - negative', function(done) {
var isValid = true;
try {
sharp().tile(null, -1);
} catch (err) {
isValid = false;
}
assert.strictEqual(false, isValid);
done();
});
it('overlap - too large', function(done) {
var isValid = true;
try {
sharp().tile(null, 8193);
} catch (err) {
isValid = false;
}
assert.strictEqual(false, isValid);
done();
});
it('overlap - larger than default size', function(done) {
var isValid = true;
try {
sharp().tile(null, 257);
} catch (err) {
isValid = false;
}
assert.strictEqual(false, isValid);
done();
});
it('overlap - larger than provided size', function(done) {
var isValid = true;
try {
sharp().tile(512, 513);
} catch (err) {
isValid = false;
}
assert.strictEqual(false, isValid);
done();
});
});
if (sharp.format.dz.output.file) {
describe('Deep Zoom output', function() {
it('Tile size - 256px default', function(done) {
var directory = fixtures.path('output.256_files');
rimraf(directory, function() {
sharp(fixtures.inputJpg).toFile(fixtures.path('output.256.dzi'), function(err, info) {
if (err) throw err;
assert.strictEqual('dz', info.format);
assertDeepZoomTiles(directory, 256, 13, done);
});
});
});
it('Tile size/overlap - 512/16px', function(done) {
var directory = fixtures.path('output.512_files');
rimraf(directory, function() {
sharp(fixtures.inputJpg).tile(512, 16).toFile(fixtures.path('output.512.dzi'), function(err, info) {
if (err) throw err;
assert.strictEqual('dz', info.format);
assertDeepZoomTiles(directory, 512 + 2 * 16, 13, done);
});
});
});
});
}
});

View File

@@ -50,4 +50,25 @@ describe('Utilities', function() {
}); });
}); });
describe('Format', function() {
it('Contains expected attributes', function() {
assert.strictEqual('object', typeof sharp.format);
Object.keys(sharp.format).forEach(function(format) {
assert.strictEqual(true, 'id' in sharp.format[format]);
assert.strictEqual(format, sharp.format[format].id);
['input', 'output'].forEach(function(direction) {
assert.strictEqual(true, direction in sharp.format[format]);
assert.strictEqual('object', typeof sharp.format[format][direction]);
assert.strictEqual(3, Object.keys(sharp.format[format][direction]).length);
assert.strictEqual(true, 'file' in sharp.format[format][direction]);
assert.strictEqual(true, 'buffer' in sharp.format[format][direction]);
assert.strictEqual(true, 'stream' in sharp.format[format][direction]);
assert.strictEqual('boolean', typeof sharp.format[format][direction].file);
assert.strictEqual('boolean', typeof sharp.format[format][direction].buffer);
assert.strictEqual('boolean', typeof sharp.format[format][direction].stream);
});
});
});
});
}); });