Compare commits

...

255 Commits

Author SHA1 Message Date
Lovell Fuller
fd5b4a131f v0.12.1 2015-12-12 10:21:36 +00:00
Lovell Fuller
32c4b9eff1 Allow SIMD vector unit to be toggled on/off #172
Currently defaults to off but future versions may default to on
2015-12-12 09:11:50 +00:00
Lovell Fuller
95cf35efc5 Add changelog for v0.12.1 2015-12-06 20:40:05 +00:00
Lovell Fuller
58e6368525 Ensure embedded ICC profiles output with perceptual intent #321 2015-12-06 20:24:17 +00:00
Lovell Fuller
16e0d54b15 Use the NPM-configured HTTPS proxy, if present 2015-12-03 21:30:47 +00:00
Lovell Fuller
be381e4440 Add release date for v0.12.0 2015-11-23 14:06:49 +00:00
Lovell Fuller
9982182926 Update perf test results ahead of v0.12.0 2015-11-23 13:09:06 +00:00
Lovell Fuller
607d157b76 Benchmark test updates ahead of v0.12.0 2015-11-23 10:53:52 +00:00
Lovell Fuller
e21277ceba Version bump of libpng Windows dependency 2015-11-22 21:11:29 +00:00
Lovell Fuller
8012733a52 Expose libvips+deps versions attribute
Add versions.json for Linux packaging

Bump vips-dev Windows version for latest libpng
2015-11-22 20:58:38 +00:00
Lovell Fuller
01a1377972 Include cmath header for clang #301 2015-11-22 09:39:42 +00:00
Lovell Fuller
37e4b9b5ba Update changelog ahead of v0.12.0
Highlight features in readme+docs

Add link to Docker-based Linux CI build status
2015-11-22 09:17:51 +00:00
Lovell Fuller
8a3098604c Remove experimental code that could prevent anti-alias filter 2015-11-21 23:37:44 +00:00
Lovell Fuller
5febce7a59 Remove executable bit from test/* file permissions 2015-11-21 23:05:48 +00:00
Lovell Fuller
3dbedf1fb6 Match g_malloc/g_free for make benefit glorious nation of Windows
Remove C++ standard lib from Windows packaging
2015-11-21 22:51:32 +00:00
Lovell Fuller
0ae619dfc5 Remove stray win32 library that was causing segfaults
Explicit int cast to prevent 'loss of precision' warnings

Remove unnecessary semver checking from I/O tests
2015-11-21 22:18:39 +00:00
Lovell Fuller
05dd191e17 Ensure 16-bit+alpha input images work with vips_premultiply #301
Improves SVG support as *magick serves these as 16-bit

Add automated tests for SVG and 16-bit+alpha PNG inputs
2015-11-21 20:21:34 +00:00
Lovell Fuller
c9ecc7a517 Document Debian 7 support
Add liborc to fix openSUSE support

Switch recommended *magick to ImageMagick for OS X

Increase test timeouts (for Circle CI)
2015-11-19 22:47:34 +00:00
Lovell Fuller
434a433a09 Make output of packaging tests easier to understand
Slight simplification of Linux packaging script
2015-11-19 21:36:16 +00:00
Lovell Fuller
3de54d897c Merge pull request #309 from papandreou/feature/extractWithOptionsObject
Add an options object to the extract operation, deprecate existing behaviour.
2015-11-18 11:48:11 +00:00
Andreas Lind
530c2a9fcf Stop using the legacy .extract(top,left,width,height) in the documentation. 2015-11-18 12:06:10 +01:00
Andreas Lind
60b8b92630 Add support for .extract({left:...,top:...,width:...,height:...}). 2015-11-18 12:06:10 +01:00
Lovell Fuller
5842da22d8 Merge pull request #306 from dacarley/negate
Add negate operation to invert all pixel values.
2015-11-17 20:45:09 +00:00
Lovell Fuller
9850e3dae0 Merge pull request #303 from dacarley/threshold
Add threshold operation.
Converts to greyscale then splits into black/white based on given value.
2015-11-17 20:34:40 +00:00
David Carley
3af62446fc Implements greyscale thresholding 2015-11-17 12:15:34 -06:00
Lovell Fuller
1f71dade67 Merge pull request #307 from papandreou/wheezy
Linux Dockerfile: Use Debian Wheezy as the base image
2015-11-17 17:29:35 +00:00
Lovell Fuller
8be664b66f Merge pull request #305 from papandreou/fix/imagemagickUrl
Dockerfile: Update ImageMagick tarball url (they took -5 down and up -6)
2015-11-17 17:24:30 +00:00
Andreas Lind
c0be4f1307 Dial back the required libc version to 2.13 2015-11-17 17:37:06 +01:00
Andreas Lind
d9c754f5c1 Linux Dockerfile: Use a Debian Wheezy image instead of Ubuntu Precise. 2015-11-17 17:37:06 +01:00
David Carley
33a175eafb Implements negation. 2015-11-17 10:18:59 -06:00
Andreas Lind
7c990b3ab3 Dockerfile: Update ImageMagick tarball url (they took -5 down and up put -6). 2015-11-17 17:16:19 +01:00
Lovell Fuller
5dfeaa9fd1 Ensure gaussian blur is applied before lbb interpolator #289 2015-11-16 08:26:35 +00:00
Lovell Fuller
84fd1caa46 Switch default interpolator to bicubic #289
Only use gaussian blur for non-linear interpolators

Improves performance of bilinear by ~15%

Add liborc to the packaged build to improve bicubic perf

Add examples of the various interpolation methods

Add bilinear vs bicubic to perf tests
2015-11-15 22:04:31 +00:00
Lovell Fuller
2678d761ba Use FreeCallback to support mixed Windows runtime libs #152 2015-11-14 13:58:05 +00:00
Lovell Fuller
ede2ee9ce3 Use Persistent wrapper to prevent GC of input Buffer #152
Avoids memcpy of input and output Buffer objects

Improves Buffer and Stream performance by ~3%
2015-11-14 11:24:15 +00:00
Lovell Fuller
20f468991f Start to use libvips 8.1.0+ features #152
Use native (un)premultiply

Support normalise on Windows
2015-11-12 22:14:53 +00:00
Lovell Fuller
58d9e0fef7 Pre-extract rotate should not swap width/height #296 2015-11-11 22:34:30 +00:00
Lovell Fuller
d7278f022b Ensure latest npm on openSUSE test 2015-11-11 22:25:17 +00:00
Lovell Fuller
7383596f8c Allow tty-less docker-inside-docker usage 2015-11-11 21:37:04 +00:00
Lovell Fuller
f6831ab46b Increase packaging test logging 2015-11-10 23:30:08 +00:00
Lovell Fuller
7cf0f95ed1 Fix Circle CI config
CI providers should really provide config linters :)
2015-11-10 21:53:26 +00:00
Lovell Fuller
bf6b894480 Improve dependency-less documentation
Start to comment ever-growing GYP config

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

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

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

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

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

Inline usage examples with the method they demonstrate

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

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

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

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

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

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

See #97.
2015-06-01 14:21:01 +01:00
Lovell Fuller
c886eaa6b0 Version bumps for v0.11.0 WIP 2015-06-01 14:21:01 +01:00
Lovell Fuller
b50fb53f27 Use image fingerprints in functional tests #122 2015-06-01 14:21:01 +01:00
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
Lovell Fuller
065ce6454b Explicit cast of size_t to 32 bit integer
Ensures compilation with nan 1.5.0+
2015-01-15 15:16:01 +00:00
Lovell Fuller
850c2ecdd6 Version bumps 2014-12-15 14:02:30 +00:00
Lovell Fuller
926c5603aa Improve documentation on concurrency/parallelism 2014-12-15 14:00:23 +00:00
Lovell Fuller
d3225fa193 Add 'size' attribute to callback's info Object #138 2014-12-15 13:54:19 +00:00
Lovell Fuller
f026a835fd Move unref of input Buffer to C++ #138 2014-12-14 10:31:25 +00:00
Lovell Fuller
47241db789 Let V8 garbage collect the Buffer earlier #138 2014-12-13 08:48:24 +00:00
Lovell Fuller
34a9970bd9 Remove useless re-definition of image #139 2014-12-12 22:04:55 +00:00
Lovell Fuller
57203f841a Copy input Buffer to avoid V8 heap compaction #138 2014-12-12 22:02:42 +00:00
Lovell Fuller
bd20bd1881 Version bumps 2014-12-11 13:32:52 +00:00
Lovell Fuller
60f1fda7ee Change interpretation to sRGB before transformation #133 2014-12-11 13:32:36 +00:00
Lovell Fuller
ea1013f6ec Add support for latest Amazon Linux 2014-12-08 10:52:59 +00:00
Lovell Fuller
247b607afd Add SVG and PSD fixtures and tests 2014-12-05 21:35:18 +00:00
Lovell Fuller
a56102a209 Ensure ICC transform of withMetadata output #133 2014-12-04 11:28:09 +00:00
Lovell Fuller
940b6f505f Add test for Promise rejection path 2014-12-04 10:48:45 +00:00
Lovell Fuller
e1b5574c4a Handle broken, embedded ICC profile #131 2014-12-03 10:23:35 +00:00
Lovell Fuller
f4cc6a2db4 Correct location of Dockerfile 2014-11-26 10:50:47 +00:00
Lovell Fuller
0acf865654 Faster ICC profile transform via lcms #125 2014-11-25 22:52:24 +00:00
Lovell Fuller
8460e50ee0 Remove spurious keywords 2014-11-25 19:16:01 +00:00
Lovell Fuller
f57a0e3b00 Ensure embedded profile, if any, is always used
Perform sRGB conversion at end of pipe only

withMetadata exports profile, should not convert

Convert one fixture to sRGB to help test

Discovered while investigating #125
2014-11-25 18:54:49 +00:00
Lovell Fuller
02b6016390 Add link to Dockerfile for libvips
Thanks @marcbachmann
2014-11-25 10:33:43 +00:00
Lovell Fuller
4e01d63195 Add hasProfile attribute to metadata response
At the very least will be useful investigating #125
2014-11-24 17:24:29 +00:00
Lovell Fuller
94b47508c0 imagemagick-native now supports async and filter 2014-11-24 15:13:47 +00:00
Lovell Fuller
328cda82c5 Updates for 7.42 stable release of libvips 2014-11-24 12:19:44 +00:00
Lovell Fuller
118b17aa2f Apply less blur before affine reduction #121 2014-11-24 11:52:48 +00:00
Lovell Fuller
b7c7fc22f3 Ensure correct Gaussian blur before affine #121
Use double sigma instead of int radius for blur
2014-11-20 13:59:39 +00:00
Lovell Fuller
177a4f574c Minimum version of libvips now 7.40.0 #74 2014-11-17 12:08:05 +00:00
Lovell Fuller
e22d093002 Ubuntu 14 now compiles 7.40.x from source
as packaged version is the outdated 7.38.x

Added support for Ubuntu 15 and Mint 17.1
2014-11-12 20:30:13 +00:00
Lovell Fuller
e7f6d49bc1 Additional blur radii tests #108 2014-11-12 20:11:28 +00:00
Lovell Fuller
b886db4b0d Add bounds checks on blur/sharpen parameters #108 2014-11-12 20:06:28 +00:00
Lovell Fuller
ee513ac7a7 Less C, more C++ e.g. namespace, enum class
Improve image reference handling
2014-11-11 18:28:23 +00:00
Lovell Fuller
e465306d97 Disable libvips cache for unit tests
Aids memory leak detection
2014-11-11 18:09:48 +00:00
Lovell Fuller
32d9bc204a Add 'fast' blur and Gaussian blur feature #108 2014-11-10 22:38:13 +00:00
Lovell Fuller
df5cf402e3 Ensure leak check tests child processes 2014-11-10 22:35:22 +00:00
Lovell Fuller
86681100b7 Control level of sharpening via radius/flat/jagged #108 2014-11-10 16:20:04 +00:00
Lovell Fuller
47927ef47d Shrink less, affine more, maintain performance #75
Affects interpolators with 4x4+ window size

e.g. Bicubic, LBB, Nohalo

Introduces blur before large affine

to improve large PNG reductions
2014-11-08 12:08:27 +00:00
Lovell Fuller
7537adf399 Add features from libvips 7.40+
Load TIFF from Buffer/Stream

Interlaced PNG output no longer needs tilecache

Option to disable PNG adaptive row filtering
2014-11-08 12:08:27 +00:00
197 changed files with 8796 additions and 2386 deletions

12
.editorconfig Normal file
View File

@@ -0,0 +1,12 @@
# http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false

8
.gitignore vendored
View File

@@ -2,8 +2,10 @@ 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
lib
# Mac OS X include
packaging/libvips*
packaging/*.log
.DS_Store .DS_Store

View File

@@ -1,7 +1,10 @@
{ {
"strict": true, "strict": true,
"node": true, "node": true,
"maxparams": 4,
"maxcomplexity": 13,
"globals": { "globals": {
"before": true,
"describe": true, "describe": true,
"it": true "it": true
} }

View File

@@ -1,8 +1,15 @@
build build
node_modules node_modules
coverage coverage
.editorconfig
.jshintignore .jshintignore
.jshintrc .jshintrc
.gitignore .gitignore
test test
.travis.yml .travis.yml
appveyor.yml
circle.yml
mkdocs.yml
lib
include
packaging

View File

@@ -1,8 +1,17 @@
language: node_js language: node_js
node_js: node_js:
- "0.10" - "0.10"
- "0.11" - "0.12"
before_install: - "4"
- curl -s https://raw.githubusercontent.com/lovell/sharp/master/preinstall.sh | sudo bash - - "5"
sudo: false
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-4.8
env:
CXX=g++-4.8
after_success: after_success:
- cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js

92
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,92 @@
# 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.
If you're having installation problems, please include the output of running `npm install --verbose sharp`.
New bugs are assigned a `triage` label whilst under investigation.
## 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`.
To test C++ changes, you can compile the module using `npm install` and then run the tests using `npm test`.
## Submit a Pull Request with a new feature
Please add JavaScript [unit tests](https://github.com/lovell/sharp/tree/master/test/unit) to cover your new feature.
A test coverage report for the JavaScript code is generated in the `coverage/lcov-report` directory.
Where possible, the functional tests use gradient-based perceptual hashes
based on [dHash](http://www.hackerfactor.com/blog/index.php?/archives/529-Kind-of-Like-That.html)
to compare expected vs actual images.
You deserve to add your details to the [list of contributors](https://github.com/lovell/sharp/blob/master/package.json#L5).
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.12.0 | look |
| v0.13.0 | mind |
Please squash your changes into a single commit using a command like `git rebase -i upstream/<wip-branch>`.
### 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](http://valgrind.org/).
```sh
npm run test-leak
```
### Packaging tests
Tests the installation on a number of Linux-based operating systems.
Requires docker.
```sh
npm run test-packaging
```
## 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).

593
README.md Executable file → Normal file
View File

@@ -1,588 +1,47 @@
# sharp # sharp
* [Installation](https://github.com/lovell/sharp#installation) The typical use case for this high speed Node.js module
* [Usage examples](https://github.com/lovell/sharp#usage-examples) is to convert large images of many formats to
* [API](https://github.com/lovell/sharp#api) smaller, web-friendly JPEG, PNG and WebP images of varying dimensions.
* [Testing](https://github.com/lovell/sharp#testing)
* [Performance](https://github.com/lovell/sharp#performance)
* [Thanks](https://github.com/lovell/sharp#thanks)
* [Licence](https://github.com/lovell/sharp#licence)
The typical use case for this high speed Node.js module is to convert large images of many formats to smaller, web-friendly JPEG, PNG and WebP images of varying dimensions. Resizing an image is typically 4x faster than using the
quickest ImageMagick and GraphicsMagick settings.
The performance of JPEG resizing is typically 8x faster than ImageMagick and GraphicsMagick, based mainly on the number of CPU cores available. Colour spaces, embedded ICC profiles and alpha transparency channels are all handled correctly.
Bicubic interpolation with Lanczos anti-alias filtering ensures quality is not sacrificed for speed.
Memory usage is kept to a minimum, no child processes are spawned, everything remains non-blocking thanks to _libuv_ and Promises/A+ are supported. As well as image resizing, operations such as
rotation, extraction, compositing and gamma correction are available.
This module supports reading and writing JPEG, PNG and WebP images to and from Streams, Buffer objects and the filesystem. It also supports reading images of many other types from the filesystem via libmagick++ or libgraphicsmagick++ if present. 64-bit Windows and recent Linux systems do not require
the installation of any external runtime dependencies.
When generating JPEG output all metadata is removed and Huffman tables optimised without having to use separate command line tools like [jpegoptim](https://github.com/tjko/jpegoptim) and [jpegtran](http://jpegclub.org/jpegtran/). Use with OS X is as simple as running `brew install homebrew/science/vips`
to install the libvips dependency.
Anyone who has used the Node.js bindings for [GraphicsMagick](https://github.com/aheckmann/gm) will find the API similarly fluent.
This module is powered by the blazingly fast [libvips](https://github.com/jcupitt/libvips) image processing library, originally created in 1989 at Birkbeck College and currently maintained by [John Cupitt](https://github.com/jcupitt).
## Installation
npm install sharp
### Prerequisites
* Node.js v0.10+
* [libvips](https://github.com/jcupitt/libvips) v7.38.5+
To install the latest version of libvips on the following Operating Systems:
* Mac OS
* Homebrew
* MacPorts
* Debian Linux
* Debian 7, 8
* Ubuntu 12.04, 14.04, 14.10
* Mint 13, 17
* Red Hat Linux
* RHEL/Centos/Scientific 6, 7
* Fedora 21, 22
run the following as a user with `sudo` access:
curl -s https://raw.githubusercontent.com/lovell/sharp/master/preinstall.sh | sudo bash -
or run the following as `root`:
curl -s https://raw.githubusercontent.com/lovell/sharp/master/preinstall.sh | bash -
The [preinstall.sh](https://github.com/lovell/sharp/blob/master/preinstall.sh) script requires `curl` and `pkg-config`.
### Mac OS tips
Manual install via homebrew:
brew install homebrew/science/vips --with-webp --with-graphicsmagick
A missing or incorrectly configured _Xcode Command Line Tools_ installation [can lead](https://github.com/lovell/sharp/issues/80) to a `library not found for -ljpeg` error. If so, please try:
xcode-select --install
The _gettext_ dependency of _libvips_ [can lead](https://github.com/lovell/sharp/issues/9) to a `library not found for -lintl` error. If so, please try:
brew link gettext --force
### Install libvips on Heroku
[Alessandro Tagliapietra](https://github.com/alex88) maintains an [Heroku buildpack for libvips](https://github.com/alex88/heroku-buildpack-vips) and its dependencies.
### Using with gulp.js
[Eugeny Vlasenko](https://github.com/mahnunchik) maintains [gulp-responsive](https://www.npmjs.org/package/gulp-responsive) and [Mohammad Prabowo](https://github.com/rizalp) maintains [gulp-sharp](https://www.npmjs.org/package/gulp-sharp).
## Usage examples
```javascript
var sharp = require('sharp');
```
```javascript
sharp('input.jpg').resize(300, 200).toFile('output.jpg', function(err) {
if (err) {
throw err;
}
// output.jpg is a 300 pixels wide and 200 pixels high image
// containing a scaled and cropped version of input.jpg
});
```
```javascript
var transformer = sharp().resize(300, 200).crop(sharp.gravity.north);
readableStream.pipe(transformer).pipe(writableStream);
// Read image data from readableStream, resize and write image data to writableStream
```
```javascript
var image = sharp(inputJpg);
image.metadata(function(err, metadata) {
image.resize(metadata.width / 2).webp().toBuffer(function(err, outputBuffer, info) {
// outputBuffer contains a WebP image half the width and height of the original JPEG
});
});
```
```javascript
var pipeline = sharp()
.rotate()
.resize(null, 200)
.progressive()
.toBuffer(function(err, outputBuffer, info) {
if (err) {
throw err;
}
// outputBuffer contains 200px high progressive JPEG image data,
// auto-rotated using EXIF Orientation tag
// info.width and info.height contain the dimensions of the resized image
});
readableStream.pipe(pipeline);
```
```javascript
sharp('input.png')
.rotate(180)
.resize(300)
.flatten()
.background('#ff6600')
.sharpen()
.withMetadata()
.quality(90)
.webp()
.toBuffer()
.then(function(outputBuffer) {
// outputBuffer contains upside down, 300px wide, alpha channel flattened
// onto orange background, sharpened, with metadata, 90% quality WebP image
// data
});
```
```javascript
http.createServer(function(request, response) {
response.writeHead(200, {'Content-Type': 'image/webp'});
sharp('input.jpg').rotate().resize(200).webp().pipe(response);
}).listen(8000);
// Create HTTP server that always returns auto-rotated 'input.jpg',
// resized to 200 pixels wide, in WebP format
```
```javascript
sharp(input)
.extract(top, left, width, height)
.toFile(output);
// Extract a region of the input image, saving in the same format.
```
```javascript
sharp(input)
.extract(topOffsetPre, leftOffsetPre, widthPre, heightPre)
.resize(width, height)
.extract(topOffsetPost, leftOffsetPost, widthPost, heightPost)
.toFile(output);
// Extract a region, resize, then extract from the resized image
```
```javascript
sharp(inputBuffer)
.resize(200, 300)
.interpolateWith(sharp.interpolator.nohalo)
.background('white')
.embed()
.toFile('output.tiff')
.then(function() {
// output.tiff is a 200 pixels wide and 300 pixels high image
// containing a bicubic scaled version, embedded on a white canvas,
// of the image data in inputBuffer
});
```
```javascript
sharp('input.gif')
.resize(200, 300)
.background({r: 0, g: 0, b: 0, a: 0})
.embed()
.webp()
.toBuffer(function(err, outputBuffer) {
if (err) {
throw err;
}
// outputBuffer contains WebP image data of a 200 pixels wide and 300 pixels high
// containing a scaled version, embedded on a transparent canvas, of input.gif
});
```
```javascript
sharp(inputBuffer)
.resize(200, 200)
.max()
.jpeg()
.toBuffer().then(function(outputBuffer) {
// outputBuffer contains JPEG image data no wider than 200 pixels and no higher
// than 200 pixels regardless of the inputBuffer image dimensions
});
```
## API
### Input methods
#### sharp([input])
Constructor to which further methods are chained. `input`, if present, can be one of:
* Buffer containing JPEG, PNG or WebP image data, or
* String containing the filename of an image, with most major formats supported.
The object returned implements the [stream.Duplex](http://nodejs.org/api/stream.html#stream_class_stream_duplex) class.
JPEG, PNG or WebP format image data can be streamed into the object when `input` is not provided.
JPEG, PNG or WebP format image data can be streamed out from this object.
#### metadata([callback])
Fast access to image metadata without decoding any compressed image data.
`callback`, if present, gets the arguments `(err, metadata)` where `metadata` has the attributes:
* `format`: Name of decoder to be used to decompress image data e.g. `jpeg`, `png`, `webp` (for file-based input additionally `tiff` and `magick`)
* `width`: Number of pixels wide
* `height`: Number of pixels high
* `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `scrgb`, `cmyk`, `lab`, `xyz`, `b-w` [...](https://github.com/jcupitt/libvips/blob/master/libvips/iofuncs/enumtypes.c#L502)
* `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK
* `hasAlpha`: Boolean indicating the presence of an alpha transparency channel
* `orientation`: Number value of the EXIF Orientation header, if present
A Promises/A+ promise is returned when `callback` is not provided.
#### sequentialRead()
An advanced setting that switches the libvips access method to `VIPS_ACCESS_SEQUENTIAL`. This will reduce memory usage and can improve performance on some systems.
### Image transformation options
#### resize(width, [height])
Scale output to `width` x `height`. By default, the resized image is cropped to the exact size specified.
`width` is the Number of pixels wide the resultant image should be. 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.
#### extract(top, left, width, height)
Extract a region of the image. Can be used with or without a `resize` operation.
`top` and `left` are the offset, in pixels, from the top-left corner.
`width` and `height` are the dimensions of the extracted image.
Use `extract` before `resize` for pre-resize extraction. Use `extract` after `resize` for post-resize extraction. Use `extract` before and after for both.
#### crop([gravity])
Crop the resized image to the exact size specified, the default behaviour.
`gravity`, if present, is an attribute of the `sharp.gravity` Object e.g. `sharp.gravity.north`.
Possible values are `north`, `east`, `south`, `west`, `center` and `centre`. The default gravity is `center`/`centre`.
#### max()
Preserving aspect ratio, resize the image to the maximum `width` or `height` specified.
Both `width` and `height` must be provided via `resize` otherwise the behaviour will default to `crop`.
#### background(rgba)
Set the background for the `embed` and `flatten` operations.
`rgba` is parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
The alpha value is a float between `0` (transparent) and `1` (opaque).
The default background is `{r: 0, g: 0, b: 0, a: 1}`, black without transparency.
#### embed()
Preserving aspect ratio, resize the image to the maximum `width` or `height` specified then embed on a background of the exact `width` and `height` specified.
If the background contains an alpha value then WebP and PNG format output images will contain an alpha channel, even when the input image does not.
#### flatten()
Merge alpha transparency channel, if any, with `background`.
#### rotate([angle])
Rotate the output image by either an explicit angle or auto-orient based on the EXIF `Orientation` tag.
`angle`, if present, is a Number with a value of `0`, `90`, `180` or `270`.
Use this method without `angle` to determine the angle from EXIF data. Mirroring is supported and may infer the use of a `flip` operation.
#### flip()
Flip the image about the vertical Y axis. This always occurs after rotation, if any.
#### flop()
Flop the image about the horizontal X axis. This always occurs after rotation, if any.
#### withoutEnlargement()
Do not enlarge the output image if the input image width *or* height are already less than the required dimensions.
This is equivalent to GraphicsMagick's `>` geometry option: "change the dimensions of the image only if its width or height exceeds the geometry specification".
#### sharpen()
Perform a mild sharpen of the output image. This typically reduces performance by 10%.
#### interpolateWith(interpolator)
Use the given interpolator for image resizing, where `interpolator` is an attribute of the `sharp.interpolator` Object e.g. `sharp.interpolator.bicubic`.
Possible interpolators, in order of performance, are:
* `nearest`: Use [nearest neighbour interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation), suitable for image enlargement only.
* `bilinear`: Use [bilinear interpolation](http://en.wikipedia.org/wiki/Bilinear_interpolation), the default and fastest image reduction interpolation.
* `bicubic`: Use [bicubic interpolation](http://en.wikipedia.org/wiki/Bicubic_interpolation), which typically reduces performance by 5%.
* `vertexSplitQuadraticBasisSpline`: Use [VSQBS interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/vsqbs.cpp#L48), which prevents "staircasing" and typically reduces performance by 5%.
* `locallyBoundedBicubic`: Use [LBB interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/lbb.cpp#L100), which prevents some "[acutance](http://en.wikipedia.org/wiki/Acutance)" and typically reduces performance by a factor of 2.
* `nohalo`: Use [Nohalo interpolation](http://eprints.soton.ac.uk/268086/), which prevents acutance and typically reduces performance by a factor of 3.
#### gamma([gamma])
Apply a gamma correction by reducing the encoding (darken) pre-resize at a factor of `1/gamma` then increasing the encoding (brighten) post-resize at a factor of `gamma`.
`gamma`, if present, is a Number betweem 1 and 3. The default value is `2.2`, a suitable approximation for sRGB images.
This can improve the perceived brightness of a resized image in non-linear colour spaces.
JPEG input images will not take advantage of the shrink-on-load performance optimisation when applying a gamma correction.
#### grayscale() / greyscale()
Convert to 8-bit greyscale; 256 shades of grey.
This is a linear operation. If the input image is in a non-linear colour space such as sRGB, use `gamma()` with `greyscale()` for the best results.
The output image will still be web-friendly sRGB and contain three (identical) channels.
### Output options
#### jpeg()
Use JPEG format for the output image.
#### png()
Use PNG format for the output image.
#### webp()
Use WebP format for the output image.
#### quality(quality)
The output quality to use for lossy JPEG, WebP and TIFF output formats. The default quality is `80`.
`quality` is a Number between 1 and 100.
#### progressive()
Use progressive (interlace) scan for JPEG and PNG output. This typically reduces compression performance by 30% but results in an image that can be rendered sooner when decompressed.
#### withMetadata()
Include all metadata (ICC, EXIF, XMP) from the input image in the output image. The default behaviour is to strip all metadata.
#### compressionLevel(compressionLevel)
An advanced setting for the _zlib_ compression level of the lossless PNG output format. The default level is `6`.
`compressionLevel` is a Number between 0 and 9.
### Output methods
#### toFile(filename, [callback])
`filename` is a String containing the filename to write the image data to. The format is inferred from the extension, with JPEG, PNG, WebP and TIFF supported.
`callback`, if present, is called with two arguments `(err, info)` where:
* `err` contains an error message, if any.
* `info` contains the output image `format`, `width` and `height`.
A Promises/A+ promise is returned when `callback` is not provided.
#### toBuffer([callback])
Write image data to a Buffer, the format of which will match the input image by default. JPEG, PNG and WebP are supported.
`callback`, if present, gets three arguments `(err, buffer, info)` where:
* `err` is an error message, if any.
* `buffer` is the output image data.
* `info` contains the output image `format`, `width` and `height`.
A Promises/A+ promise is returned when `callback` is not provided.
### Utility methods
#### sharp.cache([memory], [items])
If `memory` or `items` are provided, set the limits of _libvips'_ operation cache.
* `memory` is the maximum memory in MB to use for this cache, with a default value of 100
* `items` is the maximum number of operations to cache, with a default value of 500
This method always returns cache statistics, useful for determining how much working memory is required for a particular task.
```javascript
var stats = sharp.cache(); // { current: 75, high: 99, memory: 100, items: 500 }
sharp.cache(200); // { current: 75, high: 99, memory: 200, items: 500 }
sharp.cache(50, 200); // { current: 49, high: 99, memory: 50, items: 200}
```
#### sharp.concurrency([threads])
`threads`, if provided, is the Number of threads _libvips'_ should create for image processing. The default value is the number of CPU cores. A value of `0` will reset to this default.
This method always returns the current concurrency.
```javascript
var threads = sharp.concurrency(); // 4
sharp.concurrency(2); // 2
sharp.concurrency(0); // 4
```
#### sharp.counters()
Provides access to internal task counters.
* `queue` is the number of tasks this module has queued waiting for _libuv_ to provide a worker thread from its pool.
* `process` is the number of resize tasks currently being processed.
```javascript
var counters = sharp.counters(); // { queue: 2, process: 4 }
```
## Testing
### Functional tests
#### Coverage
[![Test Coverage](https://coveralls.io/repos/lovell/sharp/badge.png?branch=master)](https://coveralls.io/r/lovell/sharp?branch=master) [![Test Coverage](https://coveralls.io/repos/lovell/sharp/badge.png?branch=master)](https://coveralls.io/r/lovell/sharp?branch=master)
#### Ubuntu 12.04 ### Documentation
[![Ubuntu 12.04 Build Status](https://travis-ci.org/lovell/sharp.png?branch=master)](https://travis-ci.org/lovell/sharp) Visit [sharp.dimens.io](http://sharp.dimens.io/) for complete
[installation instructions](http://sharp.dimens.io/page/install),
[API documentation](http://sharp.dimens.io/page/api),
[benchmark tests](http://sharp.dimens.io/page/performance) and
[changelog](http://sharp.dimens.io/page/changelog).
#### Centos 6.5 ### Contributing
[![Centos 6.5 Build Status](https://snap-ci.com/lovell/sharp/branch/master/build_image)](https://snap-ci.com/lovell/sharp/branch/master) A [guide for contributors](https://github.com/lovell/sharp/blob/master/CONTRIBUTING.md)
covers reporting bugs, requesting features and submitting code changes.
#### It worked on my machine ### Licence
``` Copyright 2013, 2014, 2015 Lovell Fuller and contributors.
npm test
```
### Memory leak tests
```
cd sharp/test/leak
./leak.sh
```
Requires _valgrind_:
```
brew install valgrind
```
```
sudo apt-get install -qq valgrind
```
### Benchmark tests
```
cd sharp/test/bench
npm install
npm test
```
Requires both _ImageMagick_ and _GraphicsMagick_:
```
brew install imagemagick
brew install graphicsmagick
```
```
sudo apt-get install -qq imagemagick graphicsmagick libmagick++-dev
```
```
sudo yum install ImageMagick
sudo yum install -y http://download.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm
sudo yum install -y --enablerepo=epel GraphicsMagick
```
## Performance
### Test environment
* AWS EC2 [c3.xlarge](http://aws.amazon.com/ec2/instance-types/#Compute_Optimized)
* Ubuntu 14.04
* libvips 7.40.8
* liborc 0.4.22
### The contenders
* [imagemagick-native](https://github.com/mash/node-imagemagick-native) v1.2.2 - Supports Buffers only and blocks main V8 thread whilst processing.
* [imagemagick](https://github.com/yourdeveloper/node-imagemagick) v0.1.3 - Supports filesystem only and "has been unmaintained for a long time".
* [gm](https://github.com/aheckmann/gm) v1.16.0 - Fully featured wrapper around GraphicsMagick.
* sharp v0.6.2 - Caching within libvips disabled to ensure a fair comparison.
### The task
Decompress a 2725x2225 JPEG image, resize and crop to 720x480, then compress to JPEG.
### Results
| Module | Input | Output | Ops/sec | Speed-up |
| :-------------------- | :----- | :----- | ------: | -------: |
| imagemagick-native | buffer | buffer | 1.58 | 1 |
| imagemagick | file | file | 6.23 | 3.9 |
| gm | buffer | file | 5.32 | 3.4 |
| gm | buffer | buffer | 5.32 | 3.4 |
| gm | file | file | 5.36 | 3.4 |
| gm | file | buffer | 5.36 | 3.4 |
| sharp | buffer | file | 22.05 | 14.0 |
| sharp | buffer | buffer | 22.14 | 14.0 |
| sharp | file | file | 21.79 | 13.8 |
| sharp | file | buffer | 21.90 | 13.9 |
| sharp | stream | stream | 20.87 | 13.2 |
| sharp +promise | file | buffer | 21.89 | 13.9 |
| sharp +sharpen | file | buffer | 19.69 | 12.5 |
| sharp +progressive | file | buffer | 16.93 | 10.7 |
| sharp +sequentialRead | file | buffer | 21.60 | 13.7 |
You can expect greater performance with caching enabled (default) and using 8+ core machines.
## Thanks
This module would never have been possible without the help and code contributions of the following people:
* [John Cupitt](https://github.com/jcupitt)
* [Pierre Inglebert](https://github.com/pierreinglebert)
* [Jonathan Ong](https://github.com/jonathanong)
* [Chanon Sajjamanochai](https://github.com/chanon)
* [Juliano Julio](https://github.com/julianojulio)
* [Daniel Gasienica](https://github.com/gasi)
* [Julian Walker](https://github.com/julianwa)
* [Amit Pitaru](https://github.com/apitaru)
* [Brandon Aaron](https://github.com/brandonaaron)
* [Andreas Lind](https://github.com/papandreou)
Thank you!
## Licence
Copyright 2013, 2014 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.
You may obtain a copy of the License at [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0.html) You may obtain a copy of the License at
[http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0.html)
Unless required by applicable law or agreed to in writing, software Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, distributed under the License is distributed on an "AS IS" BASIS,

15
appveyor.yml Normal file
View File

@@ -0,0 +1,15 @@
os: Visual Studio 2015
version: "{build}"
build: off
platform: x64
environment:
VIPS_WARNING: 0
matrix:
- nodejs_version: "0.12"
- nodejs_version: "4"
- nodejs_version: "5"
install:
- ps: Install-Product node $env:nodejs_version x64
- npm install
test_script:
- npm run-script test-win

181
binding.gyp Executable file → Normal file
View File

@@ -1,23 +1,113 @@
{ {
'targets': [{ 'targets': [{
'target_name': 'sharp', 'target_name': 'sharp',
# Nested variables "pattern" borrowed from http://src.chromium.org/viewvc/chrome/trunk/src/build/common.gypi
'variables': {
'variables': {
'variables': {
'conditions': [
['OS != "win"', {
# Build the PKG_CONFIG_PATH environment variable with all possible combinations
'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'
}, {
'pkg_config_path': ''
}]
],
},
'conditions': [
['OS != "win"', {
# Which version, if any, of libvips is available globally via pkg-config?
'global_vips_version': '<!(PKG_CONFIG_PATH="<(pkg_config_path)" pkg-config --modversion vips 2>/dev/null || true)'
}, {
'global_vips_version': ''
}]
],
'pkg_config_path%': '<(pkg_config_path)'
},
'pkg_config_path%': '<(pkg_config_path)',
'runtime_link%': 'shared',
'conditions': [
['OS != "win"', {
# Does the globally available version of libvips, if any, meet the minimum version requirement?
'use_global_vips': '<!(GLOBAL_VIPS_VERSION="<(global_vips_version)" node -e "require(\'./binding\').use_global_vips()")'
}, {
'use_global_vips': ''
}]
]
},
'sources': [ 'sources': [
'src/common.cc', 'src/common.cc',
'src/utilities.cc',
'src/metadata.cc', 'src/metadata.cc',
'src/resize.cc', 'src/operations.cc',
'src/sharp.cc' 'src/pipeline.cc',
], 'src/sharp.cc',
'variables': { 'src/utilities.cc'
'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': [ 'include_dirs': [
'<!(PKG_CONFIG_PATH="<(PKG_CONFIG_PATH)" pkg-config --cflags vips glib-2.0)',
'<!(node -e "require(\'nan\')")' '<!(node -e "require(\'nan\')")'
], ],
'conditions': [
['use_global_vips == "true"', {
# Use pkg-config for include and lib
'include_dirs': ['<!(PKG_CONFIG_PATH="<(pkg_config_path)" pkg-config --cflags vips glib-2.0)'],
'conditions': [
['runtime_link == "static"', {
'libraries': ['<!(PKG_CONFIG_PATH="<(pkg_config_path)" pkg-config --libs vips --static)']
}, {
'libraries': ['<!(PKG_CONFIG_PATH="<(pkg_config_path)" pkg-config --libs vips)']
}]
]
}, {
# Attempt to download pre-built libvips and install locally within node_modules
'include_dirs': [
'<(module_root_dir)/include',
'<(module_root_dir)/include/glib-2.0',
'<(module_root_dir)/lib/glib-2.0/include'
],
'conditions': [
['OS == "win"', {
'variables': {
'download_vips': '<!(node -e "require(\'./binding\').download_vips()")'
},
'libraries': [
'<(module_root_dir)/lib/libvips.lib',
'<(module_root_dir)/lib/libglib-2.0.lib',
'<(module_root_dir)/lib/libgobject-2.0.lib'
]
}],
['OS == "linux"', {
'variables': {
'download_vips': '<!(LDD_VERSION="<!(ldd --version 2>&1 || true)" node -e "require(\'./binding\').download_vips()")'
},
'libraries': [
'<(module_root_dir)/lib/libvips.so',
'<(module_root_dir)/lib/libglib-2.0.so',
'<(module_root_dir)/lib/libgobject-2.0.so',
# Dependencies of dependencies, included for openSUSE support
'<(module_root_dir)/lib/libMagickCore-6.Q16.so',
'<(module_root_dir)/lib/libMagickWand-6.Q16.so',
'<(module_root_dir)/lib/libexif.so',
'<(module_root_dir)/lib/libgio-2.0.so',
'<(module_root_dir)/lib/libgmodule-2.0.so',
'<(module_root_dir)/lib/libgsf-1.so',
'<(module_root_dir)/lib/libjpeg.so',
'<(module_root_dir)/lib/libpng.so',
'<(module_root_dir)/lib/libtiff.so',
'<(module_root_dir)/lib/libwebp.so',
'<(module_root_dir)/lib/libz.so',
'<(module_root_dir)/lib/libffi.so',
'<(module_root_dir)/lib/libgthread-2.0.so',
'<(module_root_dir)/lib/liblcms2.so',
'<(module_root_dir)/lib/libpng16.so',
'<(module_root_dir)/lib/libxml2.so',
'<(module_root_dir)/lib/liborc-0.4.so',
# Ensure runtime linking is relative to sharp.node
'-Wl,-rpath=\'$${ORIGIN}/../../lib\''
]
}]
]
}]
],
'cflags_cc': [ 'cflags_cc': [
'-std=c++0x', '-std=c++0x',
'-fexceptions', '-fexceptions',
@@ -25,14 +115,77 @@
'-O3' '-O3'
], ],
'xcode_settings': { 'xcode_settings': {
'CLANG_CXX_LANGUAGE_STANDARD': 'c++11',
'CLANG_CXX_LIBRARY': 'libc++',
'MACOSX_DEPLOYMENT_TARGET': '10.7',
'OTHER_CPLUSPLUSFLAGS': [ 'OTHER_CPLUSPLUSFLAGS': [
'-std=c++11',
'-stdlib=libc++',
'-fexceptions', '-fexceptions',
'-Wall', '-Wall',
'-O3' '-O3'
], ]
'MACOSX_DEPLOYMENT_TARGET': '10.7' },
'configurations': {
'Release': {
'msvs_settings': {
'VCCLCompilerTool': {
'ExceptionHandling': 1
} }
}
}
},
}, {
'target_name': 'win_copy_dlls',
'type': 'none',
'dependencies': [
'sharp'
],
'conditions': [
['OS == "win"', {
# Windows lacks support for rpath
'copies': [{
'destination': '<(module_root_dir)/build/Release',
'files': [
'<(module_root_dir)/lib/GNU.Gettext.dll',
'<(module_root_dir)/lib/libMagickCore-6.Q16-2.dll',
'<(module_root_dir)/lib/libMagickWand-6.Q16-2.dll',
'<(module_root_dir)/lib/libasprintf-0.dll',
'<(module_root_dir)/lib/libcairo-2.dll',
'<(module_root_dir)/lib/libcairo-gobject-2.dll',
'<(module_root_dir)/lib/libcairo-script-interpreter-2.dll',
'<(module_root_dir)/lib/libexif-12.dll',
'<(module_root_dir)/lib/libexpat-1.dll',
'<(module_root_dir)/lib/libffi-6.dll',
'<(module_root_dir)/lib/libfftw3-3.dll',
'<(module_root_dir)/lib/libfontconfig-1.dll',
'<(module_root_dir)/lib/libfreetype-6.dll',
'<(module_root_dir)/lib/libgcc_s_seh-1.dll',
'<(module_root_dir)/lib/libgdk_pixbuf-2.0-0.dll',
'<(module_root_dir)/lib/libgio-2.0-0.dll',
'<(module_root_dir)/lib/libglib-2.0-0.dll',
'<(module_root_dir)/lib/libgmodule-2.0-0.dll',
'<(module_root_dir)/lib/libgobject-2.0-0.dll',
'<(module_root_dir)/lib/libgsf-1-114.dll',
'<(module_root_dir)/lib/libgthread-2.0-0.dll',
'<(module_root_dir)/lib/libintl-8.dll',
'<(module_root_dir)/lib/libjpeg-62.dll',
'<(module_root_dir)/lib/liblcms2-2.dll',
'<(module_root_dir)/lib/libopenjpeg-1.dll',
'<(module_root_dir)/lib/libopenslide-0.dll',
'<(module_root_dir)/lib/libpango-1.0-0.dll',
'<(module_root_dir)/lib/libpangocairo-1.0-0.dll',
'<(module_root_dir)/lib/libpangowin32-1.0-0.dll',
'<(module_root_dir)/lib/libpixman-1-0.dll',
'<(module_root_dir)/lib/libpng16-16.dll',
'<(module_root_dir)/lib/libquadmath-0.dll',
'<(module_root_dir)/lib/libsqlite3-0.dll',
'<(module_root_dir)/lib/libssp-0.dll',
'<(module_root_dir)/lib/libtiff-5.dll',
'<(module_root_dir)/lib/libvips-42.dll',
'<(module_root_dir)/lib/libxml2-2.dll',
'<(module_root_dir)/lib/zlib1.dll'
]
}]
}]
]
}] }]
} }

118
binding.js Normal file
View File

@@ -0,0 +1,118 @@
'use strict';
var fs = require('fs');
var path = require('path');
var zlib = require('zlib');
var semver = require('semver');
var request = require('request');
var tar = require('tar');
var tmp = require('os').tmpdir();
var distBaseUrl = 'https://dl.bintray.com/lovell/sharp/';
var vipsHeaderPath = path.join(__dirname, 'include', 'vips', 'vips.h');
// -- Helpers
// Does this file exist?
var isFile = function(file) {
var exists = false;
try {
exists = fs.statSync(file).isFile();
} catch (err) {}
return exists;
};
var unpack = function(tarPath) {
var extractor = tar.Extract({
path: __dirname
});
extractor.on('error', error);
extractor.on('end', function() {
if (!isFile(vipsHeaderPath)) {
error('Could not unpack ' + tarPath);
}
});
fs.createReadStream(tarPath).on('error', error)
.pipe(zlib.Unzip())
.pipe(extractor);
};
// Error
var error = function(msg) {
if (msg instanceof Error) {
msg = msg.message;
}
process.stderr.write('ERROR: ' + msg + '\n');
process.exit(1);
};
// -- Binary downloaders
module.exports.download_vips = function() {
// Has vips been installed locally?
if (!isFile(vipsHeaderPath)) {
// Ensure 64-bit
if (process.arch !== 'x64') {
error('ARM and 32-bit systems require manual installation - please see http://sharp.dimens.io/en/stable/install/');
}
// Ensure libc >= 2.15
var lddVersion = process.env.LDD_VERSION;
if (lddVersion) {
var libcVersion = lddVersion ? lddVersion.split(/\n/)[0].split(' ').slice(-1)[0].trim() : '';
if (libcVersion && semver.lt(libcVersion + '.0', '2.13.0')) {
error('libc version ' + libcVersion + ' requires manual installation - please see http://sharp.dimens.io/en/stable/install/');
}
}
// Platform-specific .tar.gz
var tarFilename = ['libvips', process.env.npm_package_config_libvips, process.platform.substr(0, 3)].join('-') + '.tar.gz';
var tarPath = path.join(__dirname, 'packaging', tarFilename);
if (isFile(tarPath)) {
unpack(tarPath);
} else {
// Download
tarPath = path.join(tmp, tarFilename);
var tmpFile = fs.createWriteStream(tarPath).on('finish', function() {
unpack(tarPath);
});
var options = {
url: distBaseUrl + tarFilename
};
if (process.env.npm_config_https_proxy) {
// Use the NPM-configured HTTPS proxy
options.proxy = process.env.npm_config_https_proxy;
}
request(options).on('response', function(response) {
if (response.statusCode !== 200) {
error(distBaseUrl + tarFilename + ' status code ' + response.statusCode);
}
}).on('error', function(err) {
error('Download from ' + distBaseUrl + tarFilename + ' failed: ' + err.message);
}).pipe(tmpFile);
}
}
};
module.exports.use_global_vips = function() {
var useGlobalVips = false;
var globalVipsVersion = process.env.GLOBAL_VIPS_VERSION;
if (globalVipsVersion) {
useGlobalVips = semver.gte(
globalVipsVersion,
process.env.npm_package_config_libvips
);
}
if (process.platform === 'darwin' && !useGlobalVips) {
if (globalVipsVersion) {
error(
'Found libvips ' + globalVipsVersion + ' but require ' + process.env.npm_package_config_libvips +
'\nPlease upgrade libvips by running: brew update && brew upgrade'
);
} else {
error('Please install libvips by running: brew install homebrew/science/vips --with-webp --with-graphicsmagick');
}
}
process.stdout.write(useGlobalVips ? 'true' : 'false');
};

6
circle.yml Normal file
View File

@@ -0,0 +1,6 @@
machine:
services:
- docker
test:
override:
- ./packaging/test.sh

659
docs/api.md Normal file
View File

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

152
docs/changelog.md Normal file
View File

@@ -0,0 +1,152 @@
# Changelog
### v0.12 - "*look*"
#### v0.12.1 - 12<sup>th</sup> December 2015
* Allow use of SIMD vector instructions (via liborc) to be toggled on/off.
[#172](https://github.com/lovell/sharp/issues/172)
[@bkw](https://github.com/bkw)
[@puzrin](https://github.com/puzrin)
* Ensure embedded ICC profiles output with perceptual intent.
[#321](https://github.com/lovell/sharp/issues/321)
[@vlapo](https://github.com/vlapo)
* Use the NPM-configured HTTPS proxy, if any, for binary downloads.
#### v0.12.0 - 23<sup>rd</sup> November 2015
* Bundle pre-compiled libvips and its dependencies for 64-bit Linux and Windows.
[#42](https://github.com/lovell/sharp/issues/42)
* Take advantage of libvips v8.1.0+ features.
[#152](https://github.com/lovell/sharp/issues/152)
* Add support for 64-bit Windows. Drop support for 32-bit Windows.
[#224](https://github.com/lovell/sharp/issues/224)
[@sabrehagen](https://github.com/sabrehagen)
* Switch default interpolator to bicubic.
[#289](https://github.com/lovell/sharp/issues/289)
[@mahnunchik](https://github.com/mahnunchik)
* Pre-extract rotatation should not swap width/height.
[#296](https://github.com/lovell/sharp/issues/296)
[@asilvas](https://github.com/asilvas)
* Ensure 16-bit+alpha input images are (un)premultiplied correctly.
[#301](https://github.com/lovell/sharp/issues/301)
[@izaakschroeder](https://github.com/izaakschroeder)
* Add `threshold` operation.
[#303](https://github.com/lovell/sharp/pull/303)
[@dacarley](https://github.com/dacarley)
* Add `negate` operation.
[#306](https://github.com/lovell/sharp/pull/306)
[@dacarley](https://github.com/dacarley)
* Support `options` Object with existing `extract` operation.
[#309](https://github.com/lovell/sharp/pull/309)
[@papandreou](https://github.com/papandreou)
### v0.11 - "*knife*"
#### v0.11.4 - 5<sup>th</sup> November 2015
* Add corners, e.g. `northeast`, to existing `gravity` option.
[#291](https://github.com/lovell/sharp/pull/291)
[@brandonaaron](https://github.com/brandonaaron)
* Ensure correct auto-rotation for EXIF Orientation values 2 and 4.
[#288](https://github.com/lovell/sharp/pull/288)
[@brandonaaron](https://github.com/brandonaaron)
* Make static linking possible via `--runtime_link` install option.
[#287](https://github.com/lovell/sharp/pull/287)
[@vlapo](https://github.com/vlapo)
#### v0.11.3 - 8<sup>th</sup> September 2015
* Intrepret blurSigma, sharpenFlat, and sharpenJagged as double precision.
[#263](https://github.com/lovell/sharp/pull/263)
[@chrisriley](https://github.com/chrisriley)
#### v0.11.2 - 28<sup>th</sup> August 2015
* Allow crop gravity to be provided as a String.
[#255](https://github.com/lovell/sharp/pull/255)
[@papandreou](https://github.com/papandreou)
* Add support for io.js v3 and Node v4.
[#246](https://github.com/lovell/sharp/issues/246)
#### v0.11.1 - 12<sup>th</sup> August 2015
* Silence MSVC warning: "C4530: C++ exception handler used, but unwind semantics are not enabled".
[#244](https://github.com/lovell/sharp/pull/244)
[@TheThing](https://github.com/TheThing)
* Suppress gamma correction for input image with alpha transparency.
[#249](https://github.com/lovell/sharp/issues/249)
[@compeak](https://github.com/compeak)
#### v0.11.0 - 15<sup>th</sup> July 2015
* Allow alpha transparency compositing via new `overlayWith` method.
[#97](https://github.com/lovell/sharp/issues/97)
[@gasi](https://github.com/gasi)
* Expose raw ICC profile data as a Buffer when using `metadata`.
[#129](https://github.com/lovell/sharp/issues/129)
[@homerjam](https://github.com/homerjam)
* Allow image header updates via a parameter passed to existing `withMetadata` method.
Provide initial support for EXIF `Orientation` tag,
which if present is now removed when using `rotate`, `flip` or `flop`.
[#189](https://github.com/lovell/sharp/issues/189)
[@h2non](https://github.com/h2non)
* Tighten constructor parameter checks.
[#221](https://github.com/lovell/sharp/issues/221)
[@mikemorris](https://github.com/mikemorris)
* Allow one input Stream to be shared with two or more output Streams via new `clone` method.
[#235](https://github.com/lovell/sharp/issues/235)
[@jaubourg](https://github.com/jaubourg)
* Use `round` instead of `floor` when auto-scaling dimensions to avoid floating-point rounding errors.
[#238](https://github.com/lovell/sharp/issues/238)
[@richardadjogah](https://github.com/richardadjogah)
### v0.10 - "*judgment*"
#### v0.10.1 - 1<sup>st</sup> June 2015
* Allow embed of image with alpha transparency onto non-transparent background.
[#204](https://github.com/lovell/sharp/issues/204)
[@mikemliu](https://github.com/mikemliu)
* Include C standard library for `atoi` as Xcode 6.3 appears to no longer do this.
[#228](https://github.com/lovell/sharp/issues/228)
[@doggan](https://github.com/doggan)
#### v0.10.0 - 23<sup>rd</sup> April 2015
* Add support for Windows (x86).
[#19](https://github.com/lovell/sharp/issues/19)
[@DullReferenceException](https://github.com/DullReferenceException)
[@itsananderson](https://github.com/itsananderson)
* Add support for Openslide input and DeepZoom output.
[#146](https://github.com/lovell/sharp/issues/146)
[@mvictoras](https://github.com/mvictoras)
* Allow arbitrary aspect ratios when resizing images via new `ignoreAspectRatio` method.
[#192](https://github.com/lovell/sharp/issues/192)
[@skedastik](https://github.com/skedastik)
* Enhance output image contrast by stretching its luminance to cover the full dynamic range via new `normalize` method.
[#194](https://github.com/lovell/sharp/issues/194)
[@bkw](https://github.com/bkw)
[@codingforce](https://github.com/codingforce)

108
docs/index.md Normal file
View File

@@ -0,0 +1,108 @@
# sharp
The typical use case for this high speed Node.js module
is to convert large images of many formats to
smaller, web-friendly JPEG, PNG and WebP images of varying dimensions.
Resizing an image is typically 4x faster than using
the quickest ImageMagick and GraphicsMagick settings.
Colour spaces, embedded ICC profiles and alpha transparency channels are all handled correctly.
Bicubic interpolation with Lanczos anti-alias filtering ensures quality is not sacrificed for speed.
As well as image resizing, operations such as
rotation, extraction, compositing and gamma correction are available.
64-bit Windows and recent Linux systems do not require
the installation of any external runtime dependencies.
Use with OS X is as simple as running `brew install homebrew/science/vips`
to install the libvips dependency.
[![Test Coverage](https://coveralls.io/repos/lovell/sharp/badge.png?branch=master)](https://coveralls.io/r/lovell/sharp?branch=master)
### Formats
This module supports reading JPEG, PNG, WebP, TIFF, OpenSlide,
GIF and other libmagick-supported formats.
Output images can be in JPEG, PNG and WebP formats as well as uncompressed raw pixel data.
Streams, Buffer objects and the filesystem can be used for input and output.
A single input Stream can be split into multiple processing pipelines and output Streams.
Deep Zoom image pyramids can be generated,
suitable for use with "slippy map" tile viewers like
[OpenSeadragon](https://github.com/openseadragon/openseadragon)
and [Leaflet](https://github.com/turban/Leaflet.Zoomify).
### Fast
This module is powered by the blazingly fast
[libvips](https://github.com/jcupitt/libvips) image processing library,
originally created in 1989 at Birkbeck College
and currently maintained by
[John Cupitt](https://github.com/jcupitt).
Only small regions of uncompressed image data
are held in memory and processed at a time,
taking full advantage of multiple CPU cores and L1/L2/L3 cache.
Everything remains non-blocking thanks to _libuv_,
no child processes are spawned and Promises/A+ are supported.
### Optimal
Huffman tables are optimised when generating JPEG output images
without having to use separate command line tools like
[jpegoptim](https://github.com/tjko/jpegoptim) and
[jpegtran](http://jpegclub.org/jpegtran/).
PNG filtering can be disabled,
which for diagrams and line art often produces the same result
as [pngcrush](http://pmt.sourceforge.net/pngcrush/).
### Contributing
A [guide for contributors](https://github.com/lovell/sharp/blob/master/CONTRIBUTING.md)
covers reporting bugs, requesting features and submitting code changes.
### Credits
This module would never have been possible without
the help and code contributions of the following people:
* [John Cupitt](https://github.com/jcupitt)
* [Pierre Inglebert](https://github.com/pierreinglebert)
* [Jonathan Ong](https://github.com/jonathanong)
* [Chanon Sajjamanochai](https://github.com/chanon)
* [Juliano Julio](https://github.com/julianojulio)
* [Daniel Gasienica](https://github.com/gasi)
* [Julian Walker](https://github.com/julianwa)
* [Amit Pitaru](https://github.com/apitaru)
* [Brandon Aaron](https://github.com/brandonaaron)
* [Andreas Lind](https://github.com/papandreou)
* [Maurus Cuelenaere](https://github.com/mcuelenaere)
* [Linus Unnebäck](https://github.com/LinusU)
* [Victor Mateevitsi](https://github.com/mvictoras)
* [Alaric Holloway](https://github.com/skedastik)
* [Bernhard K. Weisshuhn](https://github.com/bkw)
* [David A. Carley](https://github.com/dacarley)
Thank you!
### Licence
Copyright 2013, 2014, 2015 Lovell Fuller and contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
[http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0.html)
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

106
docs/install.md Normal file
View File

@@ -0,0 +1,106 @@
# Installation
```sh
npm install sharp
```
### Prerequisites
* C++11 compatible compiler such as gcc 4.6+ (Node v4+ requires gcc 4.8+), clang 3.0+ or MSVC 2013
* [node-gyp](https://github.com/TooTallNate/node-gyp#installation)
### Linux
[![Ubuntu 14.04 Build Status](https://travis-ci.org/lovell/sharp.png?branch=master)](https://travis-ci.org/lovell/sharp)
[![Linux Build Status](https://circleci.com/gh/lovell/sharp.svg?style=svg&circle-token=6cb6d1d287a51af83722b19ed8885377fbc85e5c)](https://circleci.com/gh/lovell/sharp)
libvips and its dependencies are fetched and stored within `node_modules/sharp` during `npm install`.
This involves an automated HTTPS download of approximately 6MB.
Most recent 64-bit Linux-based operating systems should "just work", e.g.:
* Debian 7, 8
* Ubuntu 12.04, 14.04, 14.10, 15.04, 15.10
* Centos 7
* Fedora 20, 21
* openSUSE 13.2
* Archlinux 2015.06.01
Preference will be given to an existing globally-installed (via `pkg-config`)
version of libvips that meets the minimum version requirement.
This allows the use of newer versions of libvips with older versions of sharp.
For older and 32-bit Linux-based operating systems,
a system-wide installation of the most suitable version of
libvips and its dependencies can be achieved by running
the following command as a user with `sudo` access
(requires `curl` and `pkg-config`):
```sh
curl -s https://raw.githubusercontent.com/lovell/sharp/master/preinstall.sh | sudo bash -
```
### Mac OS
[![OS X 10.9.5 Build Status](https://travis-ci.org/lovell/sharp-osx-ci.png?branch=master)](https://travis-ci.org/lovell/sharp-osx-ci)
libvips must be installed before `npm install` is run.
This can be achieved via homebrew:
```sh
brew install homebrew/science/vips
```
For GIF input and WebP output suppport use:
```sh
brew install homebrew/science/vips --with-imagemagick --with-webp
```
A missing or incorrectly configured _Xcode Command Line Tools_ installation
[can lead](https://github.com/lovell/sharp/issues/80) to a
`library not found for -ljpeg` error.
If so, please try: `xcode-select --install`.
The _gettext_ dependency of _libvips_
[can lead](https://github.com/lovell/sharp/issues/9)
to a `library not found for -lintl` error.
If so, please try `brew link gettext --force`.
### Windows
[![Windows x64 Build Status](https://ci.appveyor.com/api/projects/status/pgtul704nkhhg6sg)](https://ci.appveyor.com/project/lovell/sharp)
libvips and its dependencies are fetched and stored within `node_modules\sharp` during `npm install`.
This involves an automated HTTPS download of approximately 9MB.
Only 64-bit (x64) `node.exe` is supported.
The WebP format is currently unavailable on Windows.
### Heroku
[Alessandro Tagliapietra](https://github.com/alex88) maintains an
[Heroku buildpack for libvips](https://github.com/alex88/heroku-buildpack-vips)
and its dependencies.
### Docker
[Marc Bachmann](https://github.com/marcbachmann) maintains an
[Ubuntu-based Dockerfile for libvips](https://github.com/marcbachmann/dockerfile-libvips).
```sh
docker pull marcbachmann/libvips
```
[Will Jordan](https://github.com/wjordan) maintains an
[Alpine-based Dockerfile for libvips](https://github.com/wjordan/dockerfile-libvips).
```sh
docker pull wjordan/libvips
```
### Build tools
* [gulp-responsive](https://www.npmjs.com/package/gulp-responsive)
* [gulp-sharp](https://www.npmjs.com/package/gulp-sharp)
* [grunt-sharp](https://www.npmjs.com/package/grunt-sharp)

70
docs/performance.md Normal file
View File

@@ -0,0 +1,70 @@
# Performance
### Test environment
* AWS EC2 [c4.xlarge](http://aws.amazon.com/ec2/instance-types/#c4) (4x E5-2666 v3 @2.90GHz)
* Amazon Linux 2015.09.1
* Node.js v5.1.0
### The contenders
* [jimp](https://www.npmjs.com/package/jimp) v0.2.19 - Image processing in pure JavaScript. Bilinear interpolation only.
* [lwip](https://www.npmjs.com/package/lwip) v0.0.8 - Wrapper around CImg, compiles dependencies from source.
* [imagemagick-native](https://www.npmjs.com/package/imagemagick-native) @5ab570e - Wrapper around libmagick++, supports Buffers only.
* [imagemagick](https://www.npmjs.com/package/imagemagick) v0.1.3 - Supports filesystem only and "*has been unmaintained for a long time*".
* [gm](https://www.npmjs.com/package/gm) v1.21.0 - Fully featured wrapper around GraphicsMagick's `gm` command line utility.
* sharp v0.12.0 / libvips v8.1.1 - Caching within libvips disabled to ensure a fair comparison.
### The task
Decompress a 2725x2225 JPEG image, resize to 720x480 using bicubic interpolation (where available), then compress to JPEG.
### Results
| Module | Input | Output | Ops/sec | Speed-up |
| :----------------- | :----- | :----- | ------: | -------: |
| jimp | file | file | 0.99 | 1.0 |
| jimp | buffer | buffer | 1.05 | 1.1 |
| lwip | file | file | 1.13 | 1.1 |
| lwip | buffer | buffer | 1.13 | 1.1 |
| imagemagick-native | buffer | buffer | 1.67 | 1.7 |
| imagemagick | file | file | 5.19 | 5.2 |
| gm | buffer | buffer | 5.56 | 5.6 |
| gm | file | file | 5.59 | 5.6 |
| sharp | stream | stream | 21.91 | 22.1 |
| sharp | file | file | 22.79 | 23.0 |
| sharp | file | buffer | 22.91 | 23.1 |
| sharp | buffer | file | 23.03 | 23.3 |
| sharp | buffer | buffer | 23.15 | 23.4 |
Greater performance can be expected with caching enabled (default) and using 8+ core machines.
The I/O limits of the relevant (de)compression library will generally determine maximum throughput.
### Benchmark test prerequisites
Requires both _ImageMagick_ and _GraphicsMagick_:
```sh
brew install imagemagick
brew install graphicsmagick
```
```sh
sudo apt-get install imagemagick libmagick++-dev graphicsmagick
```
```sh
sudo yum install ImageMagick-devel ImageMagick-c++-devel GraphicsMagick
```
### Running the benchmark test
```sh
git clone https://github.com/lovell/sharp.git
cd sharp
npm install
cd test/bench
npm install
npm test
```

Binary file not shown.

499
index.js Executable file → Normal file
View File

@@ -3,12 +3,38 @@
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 color = require('color'); var color = require('color');
var BluebirdPromise = require('bluebird'); var BluebirdPromise = require('bluebird');
var sharp = require('./build/Release/sharp'); var sharp = require('./build/Release/sharp');
// Versioning
var versions = {
vips: sharp.libvipsVersion()
};
(function() {
// Does libvips meet minimum requirement?
var libvipsVersionMin = require('./package.json').config.libvips;
if (semver.lt(versions.vips, libvipsVersionMin)) {
throw new Error('Found libvips ' + versions.vips + ' but require at least ' + libvipsVersionMin);
}
// Include versions of dependencies, if present
try {
versions = require('./lib/versions.json');
} catch (err) {}
})();
// Limits
var maximum = {
width: 0x3FFF,
height: 0x3FFF,
pixels: Math.pow(0x3FFF, 2)
};
// Constructor-factory
var Sharp = function(input) { var Sharp = function(input) {
if (!(this instanceof Sharp)) { if (!(this instanceof Sharp)) {
return new Sharp(input); return new Sharp(input);
@@ -16,10 +42,12 @@ var Sharp = function(input) {
stream.Duplex.call(this); stream.Duplex.call(this);
this.options = { this.options = {
// input options // input options
bufferIn: null,
streamIn: false, streamIn: false,
sequentialRead: false, sequentialRead: false,
// ICC profile to use when input CMYK image has no embedded profile limitInputPixels: maximum.pixels,
iccProfileCmyk: path.join(__dirname, 'icc', 'USWebCoatedSWOP.icc'), // ICC profiles
iccProfilePath: path.join(__dirname, 'icc') + path.sep,
// resize options // resize options
topOffsetPre: -1, topOffsetPre: -1,
leftOffsetPre: -1, leftOffsetPre: -1,
@@ -31,51 +59,80 @@ 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,
interpolator: 'bilinear', interpolator: 'bicubic',
// operations // operations
background: [0, 0, 0, 255], background: [0, 0, 0, 255],
flatten: false, flatten: false,
sharpen: false, negate: false,
blurSigma: 0,
sharpenRadius: 0,
sharpenFlat: 1,
sharpenJagged: 2,
threshold: 0,
gamma: 0, gamma: 0,
greyscale: false, greyscale: false,
normalize: 0,
// overlay
overlayPath: '',
// output options // output options
output: '__input', output: '__input',
progressive: false, progressive: false,
quality: 80, quality: 80,
compressionLevel: 6, compressionLevel: 6,
withoutAdaptiveFiltering: false,
withoutChromaSubsampling: false,
trellisQuantisation: false,
overshootDeringing: false,
optimiseScans: false,
streamOut: false, streamOut: false,
withMetadata: false withMetadata: false,
withMetadataOrientation: -1,
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 (
(input.length > 1) &&
(input[0] === 0xff && input[1] === 0xd8) || // JPEG
(input[0] === 0x89 && input[1] === 0x50) || // PNG
(input[0] === 0x52 && input[1] === 0x49) // WebP
) {
this.options.bufferIn = input; this.options.bufferIn = input;
} else { } else if (typeof input === 'undefined') {
throw new Error('Buffer contains an unsupported image format. JPEG, PNG and WebP are currently supported.');
}
} else {
// input=stream // input=stream
this.options.streamIn = true; this.options.streamIn = true;
} else {
throw new Error('Unsupported input ' + typeof input);
} }
return this; return this;
}; };
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();
/*
Version numbers of libvips and its dependencies
*/
module.exports.versions = versions;
/* /*
Handle incoming chunk on Writable Stream Handle incoming chunk on Writable Stream
*/ */
@@ -83,16 +140,16 @@ Sharp.prototype._write = function(chunk, encoding, callback) {
/*jslint unused: false */ /*jslint unused: false */
if (this.options.streamIn) { if (this.options.streamIn) {
if (typeof chunk === 'object' && chunk instanceof Buffer) { if (typeof chunk === 'object' && chunk instanceof Buffer) {
if (typeof this.options.bufferIn === 'undefined') { if (this.options.bufferIn instanceof Buffer) {
// Create new Buffer
this.options.bufferIn = new Buffer(chunk.length);
chunk.copy(this.options.bufferIn);
} else {
// Append to existing Buffer // Append to existing Buffer
this.options.bufferIn = Buffer.concat( this.options.bufferIn = Buffer.concat(
[this.options.bufferIn, chunk], [this.options.bufferIn, chunk],
this.options.bufferIn.length + chunk.length this.options.bufferIn.length + chunk.length
); );
} else {
// Create new Buffer
this.options.bufferIn = new Buffer(chunk.length);
chunk.copy(this.options.bufferIn);
} }
callback(); callback();
} else { } else {
@@ -104,38 +161,57 @@ Sharp.prototype._write = function(chunk, encoding, callback) {
}; };
// Crop this part of the resized image (Center/Centre, North, East, South, West) // Crop this part of the resized image (Center/Centre, North, East, South, West)
module.exports.gravity = {'center': 0, 'centre': 0, 'north': 1, 'east': 2, 'south': 3, 'west': 4}; module.exports.gravity = {
'center': 0,
'centre': 0,
'north': 1,
'east': 2,
'south': 3,
'west': 4,
'northeast': 5,
'southeast': 6,
'southwest': 7,
'northwest': 8
};
Sharp.prototype.crop = function(gravity) { 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 <= 8) {
this.options.gravity = gravity; this.options.gravity = gravity;
} else if (typeof gravity === 'string' && typeof module.exports.gravity[gravity] === 'number') {
this.options.gravity = module.exports.gravity[gravity];
} else { } else {
throw new Error('Unsupported crop gravity ' + gravity); throw new Error('Unsupported crop gravity ' + gravity);
} }
return this; return this;
}; };
Sharp.prototype.extract = function(topOffset, leftOffset, width, height) { Sharp.prototype.extract = function(options) {
/*jslint unused: false */ if (!options || typeof options !== 'object') {
// Legacy extract(top,left,width,height) syntax
options = {
left: arguments[1],
top: arguments[0],
width: arguments[2],
height: arguments[3]
};
}
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; ['left', 'top', 'width', 'height'].forEach(function (name) {
['topOffset', 'leftOffset', 'width', 'height'].forEach(function(name, index) { var value = options[name];
this.options[name + suffix] = values[index]; if (typeof value === 'number' && !Number.isNaN(value) && value % 1 === 0 && value >= 0) {
}.bind(this)); this.options[name + (name === 'left' || name === 'top' ? 'Offset' : '') + suffix] = value;
} else {
throw new Error('Non-integer value for ' + name + ' of ' + value);
}
}, this);
// Ensure existing rotation occurs before pre-resize extraction
if (suffix === 'Pre' && this.options.angle !== 0) {
this.options.rotateBeforePreExtract = true;
}
return this; return this;
}; };
/*
Deprecated embed* methods, to be removed in v0.8.0
*/
Sharp.prototype.embedWhite = util.deprecate(function() {
return this.background('white').embed();
}, "embedWhite() is deprecated, use background('white').embed() instead");
Sharp.prototype.embedBlack = util.deprecate(function() {
return this.background('black').embed();
}, "embedBlack() is deprecated, use background('black').embed() instead");
/* /*
Set the background colour for embed and flatten operations. Set the background colour for embed and flatten operations.
Delegates to the 'Color' module, which can throw an Error Delegates to the 'Color' module, which can throw an Error
@@ -149,12 +225,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;
}; };
@@ -163,6 +253,22 @@ Sharp.prototype.flatten = function(flatten) {
return this; return this;
}; };
Sharp.prototype.negate = function(negate) {
this.options.negate = (typeof negate === 'boolean') ? negate : true;
return this;
};
Sharp.prototype.overlayWith = function(overlayPath) {
if (typeof overlayPath !== 'string') {
throw new Error('The overlay path must be a string');
}
if (overlayPath === '') {
throw new Error('The overlay path cannot be empty');
}
this.options.overlayPath = overlayPath;
return this;
};
/* /*
Rotate output image by 0, 90, 180 or 270 degrees Rotate output image by 0, 90, 180 or 270 degrees
Auto-rotation based on the EXIF Orientation tag is represented by an angle of -1 Auto-rotation based on the EXIF Orientation tag is represented by an angle of -1
@@ -204,8 +310,77 @@ Sharp.prototype.withoutEnlargement = function(withoutEnlargement) {
return this; return this;
}; };
Sharp.prototype.sharpen = function(sharpen) { /*
this.options.sharpen = (typeof sharpen === 'boolean') ? sharpen : true; Blur the output image.
Call without a sigma to use a fast, mild blur.
Call with a sigma to use a slower, more accurate Gaussian blur.
*/
Sharp.prototype.blur = function(sigma) {
if (typeof sigma === 'undefined') {
// No arguments: default to mild blur
this.options.blurSigma = -1;
} else if (typeof sigma === 'boolean') {
// Boolean argument: apply mild blur?
this.options.blurSigma = sigma ? -1 : 0;
} else if (typeof sigma === 'number' && !Number.isNaN(sigma) && sigma >= 0.3 && sigma <= 1000) {
// Numeric argument: specific sigma
this.options.blurSigma = sigma;
} else {
throw new Error('Invalid blur sigma (0.3 to 1000.0) ' + sigma);
}
return this;
};
/*
Sharpen the output image.
Call without a radius to use a fast, mild sharpen.
Call with a radius to use a slow, accurate sharpen using the L of LAB colour space.
radius - size of mask in pixels, must be integer
flat - level of "flat" area sharpen, default 1
jagged - level of "jagged" area sharpen, default 2
*/
Sharp.prototype.sharpen = function(radius, flat, jagged) {
if (typeof radius === 'undefined') {
// No arguments: default to mild sharpen
this.options.sharpenRadius = -1;
} else if (typeof radius === 'boolean') {
// Boolean argument: apply mild sharpen?
this.options.sharpenRadius = radius ? -1 : 0;
} else if (typeof radius === 'number' && !Number.isNaN(radius) && (radius % 1 === 0) && radius >= 1) {
// Numeric argument: specific radius
this.options.sharpenRadius = radius;
// Control over flat areas
if (typeof flat !== 'undefined' && flat !== null) {
if (typeof flat === 'number' && !Number.isNaN(flat) && flat >= 0) {
this.options.sharpenFlat = flat;
} else {
throw new Error('Invalid sharpen level for flat areas ' + flat + ' (expected >= 0)');
}
}
// Control over jagged areas
if (typeof jagged !== 'undefined' && jagged !== null) {
if (typeof jagged === 'number' && !Number.isNaN(jagged) && jagged >= 0) {
this.options.sharpenJagged = jagged;
} else {
throw new Error('Invalid sharpen level for jagged areas ' + jagged + ' (expected >= 0)');
}
}
} else {
throw new Error('Invalid sharpen radius ' + radius + ' (expected integer >= 1)');
}
return this;
};
Sharp.prototype.threshold = function(threshold) {
if (typeof threshold === 'undefined') {
this.options.threshold = 128;
} else if (typeof threshold === 'boolean') {
this.options.threshold = threshold ? 128 : 0;
} else if (typeof threshold === 'number' && !Number.isNaN(threshold) && (threshold % 1 === 0) && threshold >= 0 && threshold <= 255) {
this.options.threshold = threshold;
} else {
throw new Error('Invalid threshold (0 to 255) ' + threshold);
}
return this; return this;
}; };
@@ -221,7 +396,18 @@ module.exports.interpolator = {
vertexSplitQuadraticBasisSpline: 'vsqbs' vertexSplitQuadraticBasisSpline: 'vsqbs'
}; };
Sharp.prototype.interpolateWith = function(interpolator) { Sharp.prototype.interpolateWith = function(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; this.options.interpolator = interpolator;
} else {
throw new Error('Invalid interpolator ' + interpolator);
}
return this; return this;
}; };
@@ -241,6 +427,15 @@ 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) {
this.options.normalize = (typeof normalize === 'boolean') ? normalize : true;
return this;
};
Sharp.prototype.normalise = Sharp.prototype.normalize;
/* /*
Convert to greyscale Convert to greyscale
*/ */
@@ -261,7 +456,7 @@ Sharp.prototype.sequentialRead = function(sequentialRead) {
}; };
Sharp.prototype.quality = function(quality) { Sharp.prototype.quality = function(quality) {
if (!Number.isNaN(quality) && quality >= 1 && quality <= 100) { if (!Number.isNaN(quality) && quality >= 1 && quality <= 100 && quality % 1 === 0) {
this.options.quality = quality; this.options.quality = quality;
} else { } else {
throw new Error('Invalid quality (1 to 100) ' + quality); throw new Error('Invalid quality (1 to 100) ' + quality);
@@ -269,6 +464,9 @@ Sharp.prototype.quality = function(quality) {
return this; return this;
}; };
/*
zlib compression level for PNG output
*/
Sharp.prototype.compressionLevel = function(compressionLevel) { Sharp.prototype.compressionLevel = function(compressionLevel) {
if (!Number.isNaN(compressionLevel) && compressionLevel >= 0 && compressionLevel <= 9) { if (!Number.isNaN(compressionLevel) && compressionLevel >= 0 && compressionLevel <= 9) {
this.options.compressionLevel = compressionLevel; this.options.compressionLevel = compressionLevel;
@@ -278,8 +476,99 @@ Sharp.prototype.compressionLevel = function(compressionLevel) {
return this; return this;
}; };
/*
Disable the use of adaptive row filtering for PNG output
*/
Sharp.prototype.withoutAdaptiveFiltering = function(withoutAdaptiveFiltering) {
this.options.withoutAdaptiveFiltering = (typeof withoutAdaptiveFiltering === 'boolean') ? withoutAdaptiveFiltering : true;
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 mozjpeg 3.0+
*/
Sharp.prototype.trellisQuantisation = function(trellisQuantisation) {
this.options.trellisQuantisation = (typeof trellisQuantisation === 'boolean') ? trellisQuantisation : true;
return this;
};
Sharp.prototype.trellisQuantization = Sharp.prototype.trellisQuantisation;
/*
Apply overshoot deringing to JPEG output - requires mozjpeg 3.0+
*/
Sharp.prototype.overshootDeringing = function(overshootDeringing) {
this.options.overshootDeringing = (typeof overshootDeringing === 'boolean') ? overshootDeringing : true;
return this;
};
/*
Optimise scans in progressive JPEG output - requires mozjpeg 3.0+
*/
Sharp.prototype.optimiseScans = function(optimiseScans) {
this.options.optimiseScans = (typeof optimiseScans === 'boolean') ? optimiseScans : true;
if (this.options.optimiseScans) {
this.progressive();
}
return this;
};
Sharp.prototype.optimizeScans = Sharp.prototype.optimiseScans;
/*
Include all metadata (EXIF, XMP, IPTC) from the input image in the output image
Optionally provide an Object with attributes to update:
orientation: numeric value for EXIF Orientation tag
*/
Sharp.prototype.withMetadata = function(withMetadata) { Sharp.prototype.withMetadata = function(withMetadata) {
this.options.withMetadata = (typeof withMetadata === 'boolean') ? withMetadata : true; this.options.withMetadata = (typeof withMetadata === 'boolean') ? withMetadata : true;
if (typeof withMetadata === 'object') {
if ('orientation' in withMetadata) {
if (
typeof withMetadata.orientation === 'number' &&
!Number.isNaN(withMetadata.orientation) &&
withMetadata.orientation % 1 === 0 &&
withMetadata.orientation >= 0 &&
withMetadata.orientation <= 7
) {
this.options.withMetadataOrientation = withMetadata.orientation;
} else {
throw new Error('Invalid orientation (0 to 7) ' + withMetadata.orientation);
}
}
}
return this;
};
/*
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; return this;
}; };
@@ -287,24 +576,37 @@ 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
*/ */
@@ -326,38 +628,75 @@ Sharp.prototype.toFile = function(output, callback) {
} }
} else { } else {
this.options.output = output; this.options.output = output;
return this._sharp(callback); return this._pipeline(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._pipeline(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() {
this.options.output = '__raw';
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
*/ */
Sharp.prototype._read = function() { Sharp.prototype._read = function() {
if (!this.options.streamOut) { if (!this.options.streamOut) {
this.options.streamOut = true; this.options.streamOut = true;
this._sharp(); this._pipeline();
} }
}; };
@@ -365,18 +704,18 @@ Sharp.prototype._read = function() {
Invoke the C++ image processing pipeline Invoke the C++ image processing pipeline
Supports callback, stream and promise variants Supports callback, stream and promise variants
*/ */
Sharp.prototype._sharp = function(callback) { Sharp.prototype._pipeline = function(callback) {
var that = this; var that = this;
if (typeof callback === 'function') { if (typeof callback === 'function') {
// output=file/buffer // output=file/buffer
if (this.options.streamIn) { if (this.options.streamIn) {
// output=file/buffer, input=stream // output=file/buffer, input=stream
this.on('finish', function() { this.on('finish', function() {
sharp.resize(that.options, callback); sharp.pipeline(that.options, callback);
}); });
} else { } else {
// output=file/buffer, input=file/buffer // output=file/buffer, input=file/buffer
sharp.resize(this.options, callback); sharp.pipeline(this.options, callback);
} }
return this; return this;
} else if (this.options.streamOut) { } else if (this.options.streamOut) {
@@ -384,9 +723,9 @@ Sharp.prototype._sharp = function(callback) {
if (this.options.streamIn) { if (this.options.streamIn) {
// output=stream, input=stream // output=stream, input=stream
this.on('finish', function() { this.on('finish', function() {
sharp.resize(that.options, function(err, data) { sharp.pipeline(that.options, function(err, data) {
if (err) { if (err) {
that.emit('error', new Error(err)); that.emit('error', err);
} else { } else {
that.push(data); that.push(data);
} }
@@ -395,9 +734,9 @@ Sharp.prototype._sharp = function(callback) {
}); });
} else { } else {
// output=stream, input=file/buffer // output=stream, input=file/buffer
sharp.resize(this.options, function(err, data) { sharp.pipeline(this.options, function(err, data) {
if (err) { if (err) {
that.emit('error', new Error(err)); that.emit('error', err);
} else { } else {
that.push(data); that.push(data);
} }
@@ -411,7 +750,7 @@ Sharp.prototype._sharp = function(callback) {
// output=promise, input=stream // output=promise, input=stream
return new BluebirdPromise(function(resolve, reject) { return new BluebirdPromise(function(resolve, reject) {
that.on('finish', function() { that.on('finish', function() {
sharp.resize(that.options, function(err, data) { sharp.pipeline(that.options, function(err, data) {
if (err) { if (err) {
reject(err); reject(err);
} else { } else {
@@ -423,7 +762,7 @@ Sharp.prototype._sharp = function(callback) {
} else { } else {
// output=promise, input=file/buffer // output=promise, input=file/buffer
return new BluebirdPromise(function(resolve, reject) { return new BluebirdPromise(function(resolve, reject) {
sharp.resize(that.options, function(err, data) { sharp.pipeline(that.options, function(err, data) {
if (err) { if (err) {
reject(err); reject(err);
} else { } else {
@@ -454,22 +793,22 @@ Sharp.prototype.metadata = function(callback) {
if (this.options.streamIn) { if (this.options.streamIn) {
return new BluebirdPromise(function(resolve, reject) { return new BluebirdPromise(function(resolve, reject) {
that.on('finish', function() { that.on('finish', function() {
sharp.metadata(that.options, function(err, data) { sharp.metadata(that.options, function(err, metadata) {
if (err) { if (err) {
reject(err); reject(err);
} else { } else {
resolve(data); resolve(metadata);
} }
}); });
}); });
}); });
} else { } else {
return new BluebirdPromise(function(resolve, reject) { return new BluebirdPromise(function(resolve, reject) {
sharp.metadata(that.options, function(err, data) { sharp.metadata(that.options, function(err, metadata) {
if (err) { if (err) {
reject(err); reject(err);
} else { } else {
resolve(data); resolve(metadata);
} }
}); });
}); });
@@ -477,6 +816,24 @@ Sharp.prototype.metadata = function(callback) {
} }
}; };
/*
Clone new instance using existing options.
Cloned instances share the same input.
*/
Sharp.prototype.clone = function() {
// Clone existing options
var clone = new Sharp();
util._extend(clone.options, this.options);
clone.streamIn = false;
// Pass 'finish' event to clone for Stream-based input
this.on('finish', function() {
// Clone inherits input data
clone.options.bufferIn = this.options.bufferIn;
clone.emit('finish');
});
return clone;
};
/* /*
Get and set cache memory and item limits Get and set cache memory and item limits
*/ */
@@ -506,3 +863,15 @@ module.exports.concurrency = function(concurrency) {
module.exports.counters = function() { module.exports.counters = function() {
return sharp.counters(); return sharp.counters();
}; };
/*
Get and set use of SIMD vector unit instructions
*/
module.exports.simd = function(simd) {
if (typeof simd !== 'boolean') {
simd = null;
}
return sharp.simd(simd);
};
// Switch off default
module.exports.simd(false);

17
mkdocs.yml Normal file
View File

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

58
package.json Executable file → Normal file
View File

@@ -1,6 +1,6 @@
{ {
"name": "sharp", "name": "sharp",
"version": "0.7.2", "version": "0.12.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,23 @@
"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>",
"Chris Riley <criley@primedia.com>",
"David Carley <dacarley@gmail.com>"
], ],
"description": "High performance Node.js module to resize JPEG, PNG and WebP images using the libvips library", "description": "High performance Node.js module to resize JPEG, PNG, WebP and TIFF images using the libvips library",
"scripts": { "scripts": {
"test": "node ./node_modules/istanbul/lib/cli.js cover ./node_modules/mocha/bin/_mocha -- --slow=5000 --timeout=10000 ./test/unit/*.js" "clean": "rm -rf node_modules/ build/ include/ lib/ coverage/ test/fixtures/output.*",
"test": "VIPS_WARNING=0 node ./node_modules/istanbul/lib/cli.js cover ./node_modules/mocha/bin/_mocha -- --slow=5000 --timeout=30000 ./test/unit/*.js",
"test-win": "node ./node_modules/mocha/bin/mocha --slow=5000 --timeout=30000 ./test/unit/*.js",
"test-leak": "./test/leak/leak.sh",
"test-packaging": "./packaging/test.sh",
"test-clean": "rm -rf coverage/ test/fixtures/output.* && npm install && npm test"
}, },
"main": "index.js", "main": "index.js",
"repository": { "repository": {
@@ -27,31 +39,37 @@
"png", "png",
"webp", "webp",
"tiff", "tiff",
"gif", "dzi",
"resize", "resize",
"thumbnail", "thumbnail",
"sharpen",
"crop", "crop",
"extract",
"embed",
"libvips", "libvips",
"vips", "vips"
"fast",
"buffer",
"stream"
], ],
"dependencies": { "dependencies": {
"bluebird": "^2.3.10", "bluebird": "^3.0.5",
"color": "^0.7.1", "color": "^0.10.1",
"nan": "^1.4.0" "nan": "^2.1.0",
"semver": "^5.1.0",
"request": "^2.67.0",
"tar": "^2.2.1"
}, },
"devDependencies": { "devDependencies": {
"mocha": "^2.0.1", "async": "^1.5.0",
"mocha-jshint": "^0.0.9", "coveralls": "^2.11.6",
"istanbul": "^0.3.2", "exif-reader": "^1.0.0",
"coveralls": "^2.11.2" "icc": "^0.0.2",
"istanbul": "^0.4.0",
"mocha": "^2.3.4",
"mocha-jshint": "^2.2.5",
"node-cpplint": "^0.4.0",
"rimraf": "^2.4.4",
"bufferutil": "^1.2.1"
},
"license": "Apache-2.0",
"config": {
"libvips": "8.1.1"
}, },
"license": "Apache 2.0",
"engines": { "engines": {
"node": ">=0.10" "node": ">=0.10"
} }

28
packaging/build.sh Executable file
View File

@@ -0,0 +1,28 @@
#!/bin/sh
# Is docker available?
if ! type docker >/dev/null; then
echo "Please install docker"
exit 1
fi
# TODO: docker v1.9.0 will allow build-time args - https://github.com/docker/docker/pull/15182
# Windows
docker build -t vips-dev-win win
WIN_CONTAINER_ID=$(docker run -d vips-dev-win)
docker cp $WIN_CONTAINER_ID:/libvips-8.1.1-win.tar.gz .
docker rm $WIN_CONTAINER_ID
# Linux
docker build -t vips-dev-lin lin
LIN_CONTAINER_ID=$(docker run -d vips-dev-lin)
docker cp $LIN_CONTAINER_ID:/libvips-8.1.1-lin.tar.gz .
docker rm $LIN_CONTAINER_ID
# Checksums
sha256sum *.tar.gz

137
packaging/lin/Dockerfile Normal file
View File

@@ -0,0 +1,137 @@
FROM debian:wheezy
MAINTAINER Lovell Fuller <npm@lovell.info>
# Build dependencies
RUN apt-get update && apt-get install -y build-essential autoconf libtool nasm gtk-doc-tools texinfo
# Working directories
ENV DEPS /deps
ENV TARGET /target
RUN mkdir ${DEPS} && mkdir ${TARGET}
# Common build paths and flags
ENV PKG_CONFIG_PATH ${PKG_CONFIG_PATH}:${TARGET}/lib/pkgconfig
ENV PATH ${PATH}:${TARGET}/bin
ENV CPPFLAGS -I${TARGET}/include
ENV LDFLAGS -L${TARGET}/lib
# Dependency version numbers
ENV VERSION_ZLIB 1.2.8
ENV VERSION_FFI 3.2.1
ENV VERSION_GLIB 2.46.2
ENV VERSION_XML2 2.9.2
ENV VERSION_GSF 1.14.34
ENV VERSION_EXIF 0.6.21
ENV VERSION_JPEG 1.4.2
ENV VERSION_PNG16 1.6.19
ENV VERSION_LCMS2 2.7
ENV VERSION_WEBP 0.4.4
ENV VERSION_TIFF 4.0.6
ENV VERSION_MAGICK 6.9.2-6
ENV VERSION_ORC 0.4.24
ENV VERSION_VIPS 8.1.1
RUN mkdir ${DEPS}/zlib
RUN curl -Ls http://zlib.net/zlib-${VERSION_ZLIB}.tar.xz | tar xJC ${DEPS}/zlib --strip-components=1
WORKDIR ${DEPS}/zlib
RUN ./configure --prefix=${TARGET} && make install
RUN rm ${TARGET}/lib/libz.a
RUN mkdir ${DEPS}/ffi
RUN curl -Ls ftp://sourceware.org/pub/libffi/libffi-${VERSION_FFI}.tar.gz | tar xzC ${DEPS}/ffi --strip-components=1
WORKDIR ${DEPS}/ffi
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --disable-builddir && make install-strip
RUN mkdir ${DEPS}/glib
RUN curl -Ls http://ftp.gnome.org/pub/gnome/sources/glib/2.46/glib-${VERSION_GLIB}.tar.xz | tar xJC ${DEPS}/glib --strip-components=1
WORKDIR ${DEPS}/glib
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
RUN mkdir ${DEPS}/xml2
RUN curl -Ls http://xmlsoft.org/sources/libxml2-${VERSION_XML2}.tar.gz | tar xzC ${DEPS}/xml2 --strip-components=1
WORKDIR ${DEPS}/xml2
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --without-python --with-zlib=${TARGET} && make install-strip
RUN mkdir ${DEPS}/gsf
RUN curl -Ls http://ftp.gnome.org/pub/GNOME/sources/libgsf/1.14/libgsf-${VERSION_GSF}.tar.xz | tar xJC ${DEPS}/gsf --strip-components=1
WORKDIR ${DEPS}/gsf
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
RUN mkdir ${DEPS}/exif
RUN curl -Ls http://kent.dl.sourceforge.net/project/libexif/libexif/${VERSION_EXIF}/libexif-${VERSION_EXIF}.tar.bz2 | tar xjC ${DEPS}/exif --strip-components=1
WORKDIR ${DEPS}/exif
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
RUN mkdir ${DEPS}/jpeg
RUN curl -Ls http://kent.dl.sourceforge.net/project/libjpeg-turbo/${VERSION_JPEG}/libjpeg-turbo-${VERSION_JPEG}.tar.gz | tar xzC ${DEPS}/jpeg --strip-components=1
WORKDIR ${DEPS}/jpeg
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --with-jpeg8 --without-turbojpeg && make install-strip
RUN mkdir ${DEPS}/png16
RUN curl -Ls http://kent.dl.sourceforge.net/project/libpng/libpng16/${VERSION_PNG16}/libpng-${VERSION_PNG16}.tar.xz | tar xJC ${DEPS}/png16 --strip-components=1
WORKDIR ${DEPS}/png16
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
RUN mkdir ${DEPS}/lcms2
RUN curl -Ls http://kent.dl.sourceforge.net/project/lcms/lcms/${VERSION_LCMS2}/lcms2-${VERSION_LCMS2}.tar.gz | tar xzC ${DEPS}/lcms2 --strip-components=1
WORKDIR ${DEPS}/lcms2
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
RUN mkdir ${DEPS}/webp
RUN curl -Ls http://downloads.webmproject.org/releases/webp/libwebp-${VERSION_WEBP}.tar.gz | tar xzC ${DEPS}/webp --strip-components=1
WORKDIR ${DEPS}/webp
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
RUN mkdir ${DEPS}/tiff
RUN curl -Ls http://download.osgeo.org/libtiff/tiff-${VERSION_TIFF}.tar.gz /deps/tiff.tar.gz | tar xzC ${DEPS}/tiff --strip-components=1
WORKDIR ${DEPS}/tiff
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
RUN rm ${TARGET}/lib/libtiffxx*
RUN mkdir ${DEPS}/magick
RUN curl -Ls http://www.imagemagick.org/download/releases/ImageMagick-${VERSION_MAGICK}.tar.xz | tar xJC ${DEPS}/magick --strip-components=1
WORKDIR ${DEPS}/magick
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --without-magick-plus-plus && make install-strip
RUN mkdir ${DEPS}/orc
RUN curl -Ls http://gstreamer.freedesktop.org/data/src/orc/orc-${VERSION_ORC}.tar.xz | tar xJC ${DEPS}/orc --strip-components=1
WORKDIR ${DEPS}/orc
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
RUN mkdir ${DEPS}/vips
RUN curl -Ls http://www.vips.ecs.soton.ac.uk/supported/8.1/vips-${VERSION_VIPS}.tar.gz | tar xzC ${DEPS}/vips --strip-components=1
WORKDIR ${DEPS}/vips
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \
--disable-debug --disable-introspection --without-python --without-fftw \
--with-zip-includes=${TARGET}/include --with-zip-libraries=${TARGET}/lib \
--with-jpeg-includes=${TARGET}/include --with-jpeg-libraries=${TARGET}/lib \
&& make install-strip
# Remove the C++ bindings
WORKDIR ${TARGET}/include
RUN rm -rf vips/vipsc++.h vips/vipscpp.h vips/V*.h
WORKDIR ${TARGET}/lib
RUN rm -rf pkgconfig .libs *.la libvipsCC* libvips-cpp.*
# Create JSON file of version numbers
WORKDIR ${TARGET}
RUN echo "{\n\
\"zlib\": \"${VERSION_ZLIB}\",\n\
\"ffi\": \"${VERSION_FFI}\",\n\
\"glib\": \"${VERSION_GLIB}\",\n\
\"xml\": \"${VERSION_XML2}\",\n\
\"gsf\": \"${VERSION_GSF}\",\n\
\"exif\": \"${VERSION_EXIF}\",\n\
\"jpeg\": \"${VERSION_JPEG}\",\n\
\"png\": \"${VERSION_PNG16}\",\n\
\"lcms\": \"${VERSION_LCMS2}\",\n\
\"webp\": \"${VERSION_WEBP}\",\n\
\"tiff\": \"${VERSION_TIFF}\",\n\
\"magick\": \"${VERSION_MAGICK}\",\n\
\"orc\": \"${VERSION_ORC}\",\n\
\"vips\": \"${VERSION_VIPS}\"\n\
}" >lib/versions.json
# Create .tar.gz
WORKDIR ${TARGET}
RUN GZIP=-9 tar czf /libvips-${VERSION_VIPS}-lin.tar.gz include lib

50
packaging/test.sh Executable file
View File

@@ -0,0 +1,50 @@
#!/bin/sh
# Verify docker is available
if ! type docker >/dev/null; then
echo "Please install docker"
exit 1
fi
test="npm run clean; NODE_ENV=development npm install --unsafe-perm; npm test"
# Debian 7, 8
# Ubuntu 12.04, 14.04
for dist in wheezy jessie precise trusty; do
echo "Testing $dist..."
if docker run -i -t --rm -v $PWD:/v nodesource/$dist:0.12 >packaging/$dist.log 2>&1 sh -c "cd /v; ./packaging/test/debian.sh; $test";
then echo "$dist OK"
else echo "$dist fail" && cat packaging/$dist.log
fi
done
# Centos 6
echo "Testing centos6..."
if docker run -i -t --rm -v $PWD:/v nodesource/centos6:0.12 >packaging/centos6.log 2>&1 sh -c "cd /v; source ./packaging/test/centos6.sh; ./preinstall.sh; $test";
then echo "centos6 OK"
else echo "centos6 fail" && cat packaging/centos6.log
fi
# Centos 7
# Fedora 20, 21
for dist in centos7 fedora20 fedora21; do
echo "Testing $dist..."
if docker run -i -t --rm -v $PWD:/v nodesource/$dist:0.12 >packaging/$dist.log 2>&1 sh -c "cd /v; $test";
then echo "$dist OK"
else echo "$dist fail" && cat packaging/$dist.log
fi
done
# openSUSE 13.2
echo "Testing opensuse..."
if docker run -i -t --rm -v $PWD:/v opensuse:13.2 >packaging/opensuse.log 2>&1 /bin/sh -c "cd /v; ./packaging/test/opensuse.sh; $test";
then echo "opensuse OK"
else echo "opensuse fail" && cat packaging/opensuse.log
fi
# Archlinux 2015.06.01
echo "Testing archlinux..."
if docker run -i -t --rm -v $PWD:/v base/archlinux:2015.06.01 >packaging/archlinux.log 2>&1 sh -c "cd /v; ./packaging/test/archlinux.sh; $test";
then echo "archlinux OK"
else echo "archlinux fail" && cat packaging/archlinux.log
fi

5
packaging/test/archlinux.sh Executable file
View File

@@ -0,0 +1,5 @@
#!/bin/sh
# Install Node.js on Archlinux
pacman -Sy --noconfirm gcc make python2 nodejs npm | cat
ln -s /usr/bin/python2 /usr/bin/python

8
packaging/test/centos6.sh Executable file
View File

@@ -0,0 +1,8 @@
#!/bin/sh
# Install C++11 compatible version of g++ on Centos 6
curl -o /etc/yum.repos.d/devtools-1.1.repo http://people.centos.org/tru/devtools-1.1/devtools-1.1.repo
yum install -y devtoolset-1.1
export CC=/opt/centos/devtoolset-1.1/root/usr/bin/gcc
export CPP=/opt/centos/devtoolset-1.1/root/usr/bin/cpp
export CXX=/opt/centos/devtoolset-1.1/root/usr/bin/c++

5
packaging/test/debian.sh Executable file
View File

@@ -0,0 +1,5 @@
#!/bin/sh
# Install pkg-config on Debian/Ubuntu
apt-get update
apt-get install -y pkg-config

7
packaging/test/opensuse.sh Executable file
View File

@@ -0,0 +1,7 @@
#!/bin/sh
# Install Node.js on openSUSE 13.2
zypper addrepo http://download.opensuse.org/repositories/devel:languages:nodejs/openSUSE_13.2/devel:languages:nodejs.repo
zypper --gpg-auto-import-keys refresh
zypper --non-interactive install gcc-c++ make nodejs-devel nodejs-npm
npm install -g npm

16
packaging/win/Dockerfile Normal file
View File

@@ -0,0 +1,16 @@
FROM ubuntu:precise
MAINTAINER Lovell Fuller <npm@lovell.info>
RUN apt-get update && apt-get install -y curl zip
# Fetch and unzip
RUN mkdir /vips
WORKDIR /vips
RUN curl -O http://www.vips.ecs.soton.ac.uk/supported/8.1/win32/vips-dev-w64-8.1.1-3.zip
RUN unzip vips-dev-w64-8.1.1-3.zip
# Clean and zip
WORKDIR /vips/vips-dev-8.1.1
RUN rm bin/libvipsCC-42.dll bin/libvips-cpp-42.dll bin/libgsf-win32-1-114.dll bin/libstdc++-6.dll
RUN cp bin/*.dll lib/
RUN GZIP=-9 tar czf /libvips-8.1.1-win.tar.gz include lib/glib-2.0 lib/libvips.lib lib/libglib-2.0.lib lib/libgobject-2.0.lib lib/*.dll

View File

@@ -2,60 +2,115 @@
# Ensures libvips is installed and attempts to install it if not # Ensures libvips is installed and attempts to install it if not
# Currently supports: # Currently supports:
# * Mac OS
# * Debian Linux # * Debian Linux
# * Debian 7, 8 # * Debian 7, 8
# * Ubuntu 12.04, 14.04, 14.10 # * Ubuntu 12.04, 14.04, 14.10, 15.04, 15.10
# * Mint 13, 17 # * Mint 13, 17
# * Elementary 0.3
# * Red Hat Linux # * Red Hat Linux
# * RHEL/Centos/Scientific 6, 7 # * RHEL/Centos/Scientific 6, 7
# * Fedora 21, 22 # * Fedora 21, 22
# * Amazon Linux 2015.03, 2015.09
# * OpenSuse 13
vips_version_minimum=7.38.5 vips_version_minimum=7.40.0
vips_version_latest_major=7.40 vips_version_latest_major_minor=8.1
vips_version_latest_minor=11 vips_version_latest_patch=1
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 $1 ./configure --disable-debug --disable-docs --disable-static --disable-introspection --disable-dependency-tracking --enable-cxx=yes --without-python --without-orc --without-fftw $1
make make
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 $(PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig:/usr/lib/pkgconfig pkg-config --modversion vips)"
}
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? pkg_config_path="$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig:/usr/lib/pkgconfig"
if ! type pkg-config >/dev/null; then check_if_library_exists() {
sorry "a system without pkg-config" PKG_CONFIG_PATH=$pkg_config_path pkg-config --exists $1
fi if [ $? -eq 0 ]; then
version_found=$(PKG_CONFIG_PATH=$pkg_config_path pkg-config --modversion $1)
pkg_config_path_homebrew=`which brew >/dev/null 2>&1 && eval $(brew --env) && echo $PKG_CONFIG_LIBDIR || true` PKG_CONFIG_PATH=$pkg_config_path pkg-config --atleast-version=$2 $1
pkg_config_path="$pkg_config_path_homebrew:$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig:/usr/lib/pkgconfig"
PKG_CONFIG_PATH=$pkg_config_path pkg-config --exists vips
if [ $? -eq 0 ]; then
vips_version_found=$(PKG_CONFIG_PATH=$pkg_config_path pkg-config --modversion vips)
pkg-config --atleast-version=$vips_version_minimum vips
if [ $? -eq 0 ]; then if [ $? -eq 0 ]; then
# Found suitable version of libvips # Found suitable version of libvips
echo "Found libvips $vips_version_found" 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
return 0
}
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 exit 0
fi fi
echo "Found libvips $vips_version_found but require $vips_version_minimum" fi
else elif [ $vips_exists -eq 1 ] && [ $enable_openslide -eq 0 ]; then
echo "Could not find libvips using a PKG_CONFIG_PATH of '$pkg_config_path'" exit 0
fi fi
# Verify root/sudo access # Verify root/sudo access
@@ -64,47 +119,128 @@ if [ "$(id -u)" -ne "0" ]; then
exit 1 exit 1
fi fi
# OS-specific installations of libvips follows # OS-specific installations of libopenslide follows
# Either openslide does not exist, or vips is installed without openslide support
case $(uname -s) in if [ $enable_openslide -eq 1 ] && [ -z $vips_with_openslide ] && [ $openslide_exists -eq 0 ]; then
*[Dd]arwin*)
# Mac OS
echo "Detected Mac OS"
if type "brew" > /dev/null; then
echo "Installing libvips via homebrew"
brew install homebrew/science/vips --with-webp --with-graphicsmagick
elif type "port" > /dev/null; then
echo "Installing libvips via MacPorts"
port install vips
else
sorry "Mac OS without homebrew or MacPorts"
fi
;;
*)
if [ -f /etc/debian_version ]; then if [ -f /etc/debian_version ]; then
# Debian Linux # Debian Linux
DISTRO=$(lsb_release -c -s) DISTRO=$(lsb_release -c -s)
echo "Detected Debian Linux '$DISTRO'" echo "Detected Debian Linux '$DISTRO'"
case "$DISTRO" in case "$DISTRO" in
jessie|trusty|utopic|qiana) jessie|vivid|wily)
# Debian 8, Ubuntu 14, Mint 17 # Debian 8, Ubuntu 15
echo "Installing libvips via apt-get" echo "Installing libopenslide via apt-get"
apt-get install -y libvips-dev apt-get install -y libopenslide-dev
;;
trusty|utopic|qiana|rebecca|rafaela|freya)
# Ubuntu 14, Mint 17
echo "Installing libopenslide dependencies via apt-get"
apt-get install -y automake build-essential curl zlib1g-dev libopenjpeg-dev libpng12-dev libjpeg-dev libtiff5-dev libgdk-pixbuf2.0-dev libxml2-dev libsqlite3-dev libcairo2-dev libglib2.0-dev sqlite3 libsqlite3-dev
install_libopenslide_from_source
;; ;;
precise|wheezy|maya) precise|wheezy|maya)
# Debian 7, Ubuntu 12.04, Mint 13 # Debian 7, Ubuntu 12.04, Mint 13
echo "Installing libvips dependencies via apt-get" echo "Installing libopenslide dependencies via apt-get"
add-apt-repository -y ppa:lyrasis/precise-backports 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
apt-get update install_libopenslide_from_source
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 libxml2-dev swig libmagickwand-dev curl
install_libvips_from_source
;; ;;
*) *)
# Unsupported Debian-based OS # Unsupported Debian-based OS
sorry "Debian-based $DISTRO" sorry "openslide" "Debian-based $DISTRO"
;; ;;
esac esac
elif [ -f /etc/redhat-release ]; then 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
fi
# OS-specific installations of libvips follows
if [ -f /etc/debian_version ]; then
# Debian Linux
DISTRO=$(lsb_release -c -s)
echo "Detected Debian Linux '$DISTRO'"
case "$DISTRO" in
jessie|vivid|wily)
# Debian 8, Ubuntu 15
if [ $enable_openslide -eq 1 ]; then
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|rafaela|freya)
# Ubuntu 14, Mint 17
echo "Installing libvips dependencies via apt-get"
apt-get install -y automake build-essential gobject-introspection gtk-doc-tools libglib2.0-dev libjpeg-dev libpng12-dev libwebp-dev libtiff5-dev libexif-dev libgsf-1-dev liblcms2-dev libxml2-dev swig libmagickcore-dev curl
install_libvips_from_source
;;
precise|wheezy|maya)
# Debian 7, Ubuntu 12.04, Mint 13
echo "Installing libvips dependencies via apt-get"
add-apt-repository -y ppa:lyrasis/precise-backports
apt-get update
apt-get install -y automake build-essential gobject-introspection gtk-doc-tools libglib2.0-dev libjpeg-dev libpng12-dev libwebp-dev libtiff4-dev libexif-dev libgsf-1-dev liblcms2-dev libxml2-dev swig libmagickcore-dev curl
install_libvips_from_source
;;
*)
# Unsupported Debian-based OS
sorry "vips" "Debian-based $DISTRO"
;;
esac
elif [ -f /etc/redhat-release ]; then
# Red Hat Linux # Red Hat Linux
RELEASE=$(cat /etc/redhat-release) RELEASE=$(cat /etc/redhat-release)
echo "Detected Red Hat Linux '$RELEASE'" echo "Detected Red Hat Linux '$RELEASE'"
@@ -113,14 +249,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 ImageMagick-devel gobject-introspection-devel libwebp-devel curl yum install -y tar curl gtk-doc libxml2-devel libjpeg-turbo-devel libpng-devel libtiff-devel libexif-devel libgsf-devel lcms2-devel ImageMagick-devel gobject-introspection-devel libwebp-devel
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 ImageMagick-devel curl yum install -y tar curl gtk-doc libxml2-devel libjpeg-turbo-devel libpng-devel libtiff-devel libexif-devel libgsf-devel lcms-devel ImageMagick-devel
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
@@ -129,17 +265,62 @@ case $(uname -s) in
;; ;;
"Fedora release 21 "*|"Fedora release 22 "*) "Fedora release 21 "*|"Fedora release 22 "*)
# Fedora 21, 22 # Fedora 21, 22
if [ $enable_openslide -eq 1 ]; then
echo "Installing libvips dependencies via yum"
yum groupinstall -y "Development Tools"
yum install -y gcc-c++ gtk-doc libxml2-devel libjpeg-turbo-devel libpng-devel libtiff-devel libexif-devel lcms-devel ImageMagick-devel gobject-introspection-devel libwebp-devel curl
echo "Compiling vips with openslide support"
install_libvips_from_source "--prefix=/usr"
else
echo "Installing libvips via yum" echo "Installing libvips via yum"
yum install vips-devel yum install -y vips-devel
fi
;; ;;
*) *)
# Unsupported RHEL-based OS # Unsupported RHEL-based OS
sorry "$RELEASE" sorry "vips" "$RELEASE"
;; ;;
esac esac
else elif [ -f /etc/system-release ]; then
# Unsupported OS # Probably Amazon Linux
sorry "$(uname -a)" RELEASE=$(cat /etc/system-release)
fi case $RELEASE in
"Amazon Linux AMI release 2015.03"|"Amazon Linux AMI release 2015.09")
# Amazon Linux
echo "Detected '$RELEASE'"
echo "Installing libvips dependencies via yum"
yum groupinstall -y "Development Tools"
yum install -y gtk-doc libxml2-devel libjpeg-turbo-devel libpng-devel libtiff-devel libexif-devel libgsf-devel lcms2-devel ImageMagick-devel gobject-introspection-devel libwebp-devel curl
install_libvips_from_source "--prefix=/usr"
;; ;;
esac *)
# Unsupported Amazon Linux version
sorry "vips" "$RELEASE"
;;
esac
elif [ -f /etc/os-release ]; then
RELEASE=$(cat /etc/os-release | grep VERSION)
echo "Detected OpenSuse Linux '$RELEASE'"
case $RELEASE in
*"13.2"*)
echo "Installing libvips dependencies via zypper"
zypper --gpg-auto-import-keys install -y --type pattern devel_basis
zypper --gpg-auto-import-keys install -y tar curl gtk-doc libxml2-devel libjpeg-turbo libjpeg8-devel libpng16-devel libtiff-devel libexif-devel liblcms2-devel ImageMagick-devel gobject-introspection-devel libwebp-devel
install_libvips_from_source
;;
esac
elif [ -f /etc/SuSE-brand ]; then
RELEASE=$(cat /etc/SuSE-brand | grep VERSION)
echo "Detected OpenSuse Linux '$RELEASE'"
case $RELEASE in
*"13.1")
echo "Installing libvips dependencies via zypper"
zypper --gpg-auto-import-keys install -y --type pattern devel_basis
zypper --gpg-auto-import-keys install -y tar curl gtk-doc libxml2-devel libjpeg-turbo libjpeg8-devel libpng16-devel libtiff-devel libexif-devel liblcms2-devel ImageMagick-devel gobject-introspection-devel libwebp-devel
install_libvips_from_source
;;
esac
else
# Unsupported OS
sorry "vips" "$(uname -a)"
fi

234
src/common.cc Executable file → Normal file
View File

@@ -1,99 +1,189 @@
#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"
// How many tasks are in the queue? // Verify platform and compiler compatibility
volatile int counter_queue = 0;
// How many tasks are being processed? #if (VIPS_MAJOR_VERSION < 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 1 && VIPS_PATCH_VERSION < 1))
volatile int counter_process = 0; #error libvips version 8.1.1+ required - see http://sharp.dimens.io/page/install
#endif
// Filename extension checkers #if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6)))
static bool ends_with(std::string const &str, std::string const &end) { #error GCC version 4.6+ is required for C++11 features - see http://sharp.dimens.io/page/install#prerequisites
#endif
#if (defined(__clang__) && defined(__has_feature))
#if (!__has_feature(cxx_range_for))
#error clang version 3.0+ is required for C++11 features - see http://sharp.dimens.io/page/install#prerequisites
#endif
#endif
#define EXIF_IFD0_ORIENTATION "exif-ifd0-Orientation"
namespace sharp {
// How many tasks are in the queue?
volatile int counterQueue = 0;
// How many tasks are being processed?
volatile int counterProcess = 0;
// Filename extension checkers
static bool EndsWith(std::string const &str, std::string const &end) {
return str.length() >= end.length() && 0 == str.compare(str.length() - end.length(), end.length(), end); return str.length() >= end.length() && 0 == str.compare(str.length() - end.length(), end.length(), end);
}
bool is_jpeg(std::string const &str) {
return ends_with(str, ".jpg") || ends_with(str, ".jpeg") || ends_with(str, ".JPG") || ends_with(str, ".JPEG");
}
bool is_png(std::string const &str) {
return ends_with(str, ".png") || ends_with(str, ".PNG");
}
bool is_webp(std::string const &str) {
return ends_with(str, ".webp") || ends_with(str, ".WEBP");
}
bool is_tiff(std::string const &str) {
return ends_with(str, ".tif") || ends_with(str, ".tiff") || ends_with(str, ".TIF") || ends_with(str, ".TIFF");
}
unsigned char const MARKER_JPEG[] = {0xff, 0xd8};
unsigned char const MARKER_PNG[] = {0x89, 0x50};
unsigned char const MARKER_WEBP[] = {0x52, 0x49};
/*
Initialise a VipsImage from a buffer. Supports JPEG, PNG and WebP.
Returns the ImageType detected, if any.
*/
ImageType
sharp_init_image_from_buffer(VipsImage **image, void *buffer, size_t const length, VipsAccess const access) {
ImageType imageType = UNKNOWN;
if (memcmp(MARKER_JPEG, buffer, 2) == 0) {
if (!vips_jpegload_buffer(buffer, length, image, "access", access, NULL)) {
imageType = JPEG;
} }
} else if(memcmp(MARKER_PNG, buffer, 2) == 0) { bool IsJpeg(std::string const &str) {
if (!vips_pngload_buffer(buffer, length, image, "access", access, NULL)) { return EndsWith(str, ".jpg") || EndsWith(str, ".jpeg") || EndsWith(str, ".JPG") || EndsWith(str, ".JPEG");
imageType = PNG;
} }
} else if(memcmp(MARKER_WEBP, buffer, 2) == 0) { bool IsPng(std::string const &str) {
if (!vips_webpload_buffer(buffer, length, image, "access", access, NULL)) { return EndsWith(str, ".png") || EndsWith(str, ".PNG");
imageType = WEBP; }
bool IsWebp(std::string const &str) {
return EndsWith(str, ".webp") || EndsWith(str, ".WEBP");
}
bool IsTiff(std::string const &str) {
return EndsWith(str, ".tif") || EndsWith(str, ".tiff") || EndsWith(str, ".TIF") || EndsWith(str, ".TIFF");
}
bool IsDz(std::string const &str) {
return EndsWith(str, ".dzi") || EndsWith(str, ".DZI");
}
/*
Determine image format of a buffer.
*/
ImageType DetermineImageType(void *buffer, size_t const length) {
ImageType imageType = ImageType::UNKNOWN;
char const *load = vips_foreign_find_load_buffer(buffer, length);
if (load != NULL) {
std::string loader = load;
if (EndsWith(loader, "JpegBuffer")) {
imageType = ImageType::JPEG;
} else if (EndsWith(loader, "PngBuffer")) {
imageType = ImageType::PNG;
} else if (EndsWith(loader, "WebpBuffer")) {
imageType = ImageType::WEBP;
} else if (EndsWith(loader, "TiffBuffer")) {
imageType = ImageType::TIFF;
} else if (EndsWith(loader, "MagickBuffer")) {
imageType = ImageType::MAGICK;
} }
} }
return imageType; return imageType;
} }
/* /*
Initialise a VipsImage from a file. Initialise and return a VipsImage from a buffer. Supports JPEG, PNG, WebP and TIFF.
Returns the ImageType detected, if any. */
*/ VipsImage* InitImage(void *buffer, size_t const length, VipsAccess const access) {
ImageType return vips_image_new_from_buffer(buffer, length, nullptr, "access", access, nullptr);
sharp_init_image_from_file(VipsImage **image, char const *file, VipsAccess const access) {
ImageType imageType = UNKNOWN;
if (vips_foreign_is_a("jpegload", file)) {
if (!vips_jpegload(file, image, "access", access, NULL)) {
imageType = JPEG;
} }
} else if (vips_foreign_is_a("pngload", file)) {
if (!vips_pngload(file, image, "access", access, NULL)) { /*
imageType = PNG; Determine image format, reads the first few bytes of the file
} */
} else if (vips_foreign_is_a("webpload", file)) { ImageType DetermineImageType(char const *file) {
if (!vips_webpload(file, image, "access", access, NULL)) { ImageType imageType = ImageType::UNKNOWN;
imageType = WEBP; char const *load = vips_foreign_find_load(file);
} if (load != nullptr) {
} else if (vips_foreign_is_a("tiffload", file)) { std::string loader = load;
if (!vips_tiffload(file, image, "access", access, NULL)) { if (EndsWith(loader, "JpegFile")) {
imageType = TIFF; imageType = ImageType::JPEG;
} } else if (EndsWith(loader, "Png")) {
} else if(vips_foreign_is_a("magickload", file)) { imageType = ImageType::PNG;
if (!vips_magickload(file, image, "access", access, NULL)) { } else if (EndsWith(loader, "WebpFile")) {
imageType = MAGICK; imageType = ImageType::WEBP;
} 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;
} }
/* /*
Initialise and return a VipsImage from a file.
*/
VipsImage* InitImage(char const *file, VipsAccess const access) {
return vips_image_new_from_file(file, "access", access, nullptr);
}
/*
Does this image have an embedded profile?
*/
bool HasProfile(VipsImage *image) {
return (vips_image_get_typeof(image, VIPS_META_ICC_NAME) > 0) ? TRUE : FALSE;
}
/*
Does this image have an alpha channel? Does this image have an alpha channel?
Uses colour space interpretation with number of channels to guess this. Uses colour space interpretation with number of channels to guess this.
*/ */
bool bool HasAlpha(VipsImage *image) {
sharp_image_has_alpha(VipsImage *image) {
return ( return (
(image->Bands == 2 && image->Type == VIPS_INTERPRETATION_B_W) || (image->Bands == 2 && image->Type == VIPS_INTERPRETATION_B_W) ||
(image->Bands == 4 && image->Type != VIPS_INTERPRETATION_CMYK) || (image->Bands == 4 && image->Type != VIPS_INTERPRETATION_CMYK) ||
(image->Bands == 5 && image->Type == VIPS_INTERPRETATION_CMYK) (image->Bands == 5 && image->Type == VIPS_INTERPRETATION_CMYK)
); );
} }
/*
Get EXIF Orientation of image, if any.
*/
int ExifOrientation(VipsImage const *image) {
int orientation = 0;
const char *exif;
if (
vips_image_get_typeof(image, EXIF_IFD0_ORIENTATION) != 0 &&
!vips_image_get_string(image, EXIF_IFD0_ORIENTATION, &exif)
) {
orientation = atoi(&exif[0]);
}
return orientation;
}
/*
Set EXIF Orientation of image.
*/
void SetExifOrientation(VipsImage *image, int const orientation) {
char exif[3];
g_snprintf(exif, sizeof(exif), "%d", orientation);
vips_image_set_string(image, EXIF_IFD0_ORIENTATION, exif);
}
/*
Remove EXIF Orientation from image.
*/
void RemoveExifOrientation(VipsImage *image) {
SetExifOrientation(image, 0);
}
/*
Returns the window size for the named interpolator. For example,
a window size of 3 means a 3x3 pixel grid is used for the calculation.
*/
int InterpolatorWindowSize(char const *name) {
VipsInterpolate *interpolator = vips_interpolate_new(name);
if (interpolator == nullptr) {
return -1;
}
int window_size = vips_interpolate_get_window_size(interpolator);
g_object_unref(interpolator);
return window_size;
}
/*
Called when a Buffer undergoes GC, required to support mixed runtime libraries in Windows
*/
void FreeCallback(char* data, void* hint) {
if (data != nullptr) {
g_free(data);
}
}
} // namespace sharp

106
src/common.h Executable file → Normal file
View File

@@ -1,46 +1,90 @@
#ifndef SHARP_COMMON_H #ifndef SRC_COMMON_H_
#define SHARP_COMMON_H #define SRC_COMMON_H_
typedef enum { #include <string>
namespace sharp {
enum class ImageType {
UNKNOWN, UNKNOWN,
JPEG, JPEG,
PNG, PNG,
WEBP, WEBP,
TIFF, TIFF,
MAGICK MAGICK,
} ImageType; OPENSLIDE
};
// Filename extension checkers // How many tasks are in the queue?
bool is_jpeg(std::string const &str); extern volatile int counterQueue;
bool is_png(std::string const &str);
bool is_webp(std::string const &str);
bool is_tiff(std::string const &str);
// How many tasks are in the queue? // How many tasks are being processed?
extern volatile int counter_queue; extern volatile int counterProcess;
// How many tasks are being processed? // Filename extension checkers
extern volatile int counter_process; bool IsJpeg(std::string const &str);
bool IsPng(std::string const &str);
bool IsWebp(std::string const &str);
bool IsTiff(std::string const &str);
bool IsDz(std::string const &str);
/* /*
Initialise a VipsImage from a buffer. Supports JPEG, PNG and WebP. Determine image format of a buffer.
Returns the ImageType detected, if any. */
*/ ImageType DetermineImageType(void *buffer, size_t const length);
ImageType
sharp_init_image_from_buffer(VipsImage **image, void *buffer, size_t const length, VipsAccess const access);
/* /*
Initialise a VipsImage from a file. Determine image format of a file.
Returns the ImageType detected, if any. */
*/ ImageType DetermineImageType(char const *file);
ImageType
sharp_init_image_from_file(VipsImage **image, char const *file, VipsAccess const access);
/* /*
Initialise and return a VipsImage from a buffer. Supports JPEG, PNG, WebP and TIFF.
*/
VipsImage* InitImage(void *buffer, size_t const length, VipsAccess const access);
/*
Initialise and return a VipsImage from a file.
*/
VipsImage* InitImage(char const *file, VipsAccess const access);
/*
Does this image have an embedded profile?
*/
bool HasProfile(VipsImage *image);
/*
Does this image have an alpha channel? Does this image have an alpha channel?
Uses colour space interpretation with number of channels to guess this. Uses colour space interpretation with number of channels to guess this.
*/ */
bool bool HasAlpha(VipsImage *image);
sharp_image_has_alpha(VipsImage *image);
#endif /*
Get EXIF Orientation of image, if any.
*/
int ExifOrientation(VipsImage const *image);
/*
Set EXIF Orientation of image.
*/
void SetExifOrientation(VipsImage *image, int const orientation);
/*
Remove EXIF Orientation from image.
*/
void RemoveExifOrientation(VipsImage *image);
/*
Returns the window size for the named interpolator. For example,
a window size of 3 means a 3x3 pixel grid is used for the calculation.
*/
int InterpolatorWindowSize(char const *name);
/*
Called when a Buffer undergoes GC, required to support mixed runtime libraries in Windows
*/
void FreeCallback(char* data, void* hint);
} // namespace sharp
#endif // SRC_COMMON_H_

191
src/metadata.cc Executable file → Normal file
View File

@@ -6,12 +6,42 @@
#include "common.h" #include "common.h"
#include "metadata.h" #include "metadata.h"
using namespace v8; using v8::Handle;
using v8::Local;
using v8::Value;
using v8::Object;
using v8::Number;
using v8::String;
using v8::Boolean;
using v8::Function;
using v8::Exception;
using Nan::AsyncQueueWorker;
using Nan::AsyncWorker;
using Nan::Callback;
using Nan::HandleScope;
using Nan::Utf8String;
using Nan::Has;
using Nan::Get;
using Nan::Set;
using Nan::New;
using Nan::NewBuffer;
using Nan::Null;
using Nan::Error;
using sharp::ImageType;
using sharp::DetermineImageType;
using sharp::InitImage;
using sharp::HasProfile;
using sharp::HasAlpha;
using sharp::ExifOrientation;
using sharp::FreeCallback;
using sharp::counterQueue;
struct MetadataBaton { struct MetadataBaton {
// Input // Input
std::string fileIn; std::string fileIn;
void* bufferIn; char *bufferIn;
size_t bufferInLength; size_t bufferInLength;
// Output // Output
std::string format; std::string format;
@@ -19,91 +49,151 @@ struct MetadataBaton {
int height; int height;
std::string space; std::string space;
int channels; int channels;
bool hasProfile;
bool hasAlpha; bool hasAlpha;
int orientation; int orientation;
char *exif;
size_t exifLength;
char *icc;
size_t iccLength;
std::string err; std::string err;
MetadataBaton(): MetadataBaton():
bufferInLength(0), bufferInLength(0),
orientation(0) {} orientation(0),
exifLength(0),
iccLength(0) {}
}; };
class MetadataWorker : public NanAsyncWorker { class MetadataWorker : public AsyncWorker {
public: public:
MetadataWorker(NanCallback *callback, MetadataBaton *baton) : NanAsyncWorker(callback), baton(baton) {} MetadataWorker(Callback *callback, MetadataBaton *baton, const Local<Object> &bufferIn) :
AsyncWorker(callback), baton(baton) {
if (baton->bufferInLength > 0) {
SaveToPersistent("bufferIn", bufferIn);
}
}
~MetadataWorker() {} ~MetadataWorker() {}
void Execute() { void Execute() {
// Decrement queued task counter // Decrement queued task counter
g_atomic_int_dec_and_test(&counter_queue); g_atomic_int_dec_and_test(&counterQueue);
ImageType imageType = UNKNOWN; ImageType imageType = ImageType::UNKNOWN;
VipsImage *image; VipsImage *image = nullptr;
if (baton->bufferInLength > 1) { if (baton->bufferInLength > 0) {
// From buffer // From buffer
imageType = sharp_init_image_from_buffer(&image, baton->bufferIn, baton->bufferInLength, VIPS_ACCESS_RANDOM); imageType = DetermineImageType(baton->bufferIn, baton->bufferInLength);
if (imageType == UNKNOWN) { if (imageType != ImageType::UNKNOWN) {
image = InitImage(baton->bufferIn, baton->bufferInLength, VIPS_ACCESS_RANDOM);
if (image == nullptr) {
(baton->err).append("Input buffer has corrupt header");
imageType = ImageType::UNKNOWN;
}
} else {
(baton->err).append("Input buffer contains unsupported image format"); (baton->err).append("Input buffer contains unsupported image format");
} }
} else { } else {
// From file // From file
imageType = sharp_init_image_from_file(&image, baton->fileIn.c_str(), VIPS_ACCESS_RANDOM); imageType = DetermineImageType(baton->fileIn.data());
if (imageType == UNKNOWN) { if (imageType != ImageType::UNKNOWN) {
(baton->err).append("File is of an unsupported image format"); image = InitImage(baton->fileIn.data(), VIPS_ACCESS_RANDOM);
if (image == nullptr) {
(baton->err).append("Input file has corrupt header");
imageType = ImageType::UNKNOWN;
}
} else {
(baton->err).append("Input file is of an unsupported image format");
} }
} }
if (imageType != UNKNOWN) { if (image != nullptr && imageType != ImageType::UNKNOWN) {
// Image type // Image type
switch (imageType) { switch (imageType) {
case JPEG: baton->format = "jpeg"; break; case ImageType::JPEG: baton->format = "jpeg"; break;
case PNG: baton->format = "png"; break; case ImageType::PNG: baton->format = "png"; break;
case WEBP: baton->format = "webp"; break; case ImageType::WEBP: baton->format = "webp"; break;
case TIFF: baton->format = "tiff"; break; case ImageType::TIFF: baton->format = "tiff"; break;
case MAGICK: baton->format = "magick"; break; case ImageType::MAGICK: baton->format = "magick"; break;
case UNKNOWN: default: baton->format = ""; case ImageType::OPENSLIDE: baton->format = "openslide"; break;
case ImageType::UNKNOWN: break;
} }
// VipsImage attributes // VipsImage attributes
baton->width = image->Xsize; baton->width = image->Xsize;
baton->height = image->Ysize; baton->height = image->Ysize;
baton->space = vips_enum_nick(VIPS_TYPE_INTERPRETATION, image->Type); baton->space = vips_enum_nick(VIPS_TYPE_INTERPRETATION, image->Type);
baton->channels = image->Bands; baton->channels = image->Bands;
baton->hasAlpha = sharp_image_has_alpha(image); baton->hasProfile = HasProfile(image);
// EXIF Orientation // Derived attributes
const char *exif; baton->hasAlpha = HasAlpha(image);
if (!vips_image_get_string(image, "exif-ifd0-Orientation", &exif)) { baton->orientation = ExifOrientation(image);
baton->orientation = atoi(&exif[0]); // EXIF
if (vips_image_get_typeof(image, VIPS_META_EXIF_NAME) == VIPS_TYPE_BLOB) {
void* exif;
size_t exifLength;
if (!vips_image_get_blob(image, VIPS_META_EXIF_NAME, &exif, &exifLength)) {
baton->exifLength = exifLength;
baton->exif = static_cast<char*>(g_malloc(exifLength));
memcpy(baton->exif, exif, exifLength);
} }
} }
// Clean up // ICC profile
if (imageType != UNKNOWN) { if (vips_image_get_typeof(image, VIPS_META_ICC_NAME) == VIPS_TYPE_BLOB) {
void* icc;
size_t iccLength;
if (!vips_image_get_blob(image, VIPS_META_ICC_NAME, &icc, &iccLength)) {
baton->iccLength = iccLength;
baton->icc = static_cast<char*>(g_malloc(iccLength));
memcpy(baton->icc, icc, iccLength);
}
}
// Drop image reference
g_object_unref(image); g_object_unref(image);
} }
// Clean up
vips_error_clear(); vips_error_clear();
vips_thread_shutdown(); vips_thread_shutdown();
} }
void HandleOKCallback () { void HandleOKCallback () {
NanScope(); HandleScope();
Handle<Value> argv[2] = { NanNull(), NanNull() }; Local<Value> argv[2] = { Null(), Null() };
if (!baton->err.empty()) { if (!baton->err.empty()) {
// Error // Error
argv[0] = Exception::Error(NanNew<String>(baton->err.data(), baton->err.size())); argv[0] = Error(baton->err.data());
} else { } else {
// Metadata Object // Metadata Object
Local<Object> info = NanNew<Object>(); Local<Object> info = New<Object>();
info->Set(NanNew<String>("format"), NanNew<String>(baton->format)); Set(info, New("format").ToLocalChecked(), New<String>(baton->format).ToLocalChecked());
info->Set(NanNew<String>("width"), NanNew<Number>(baton->width)); Set(info, New("width").ToLocalChecked(), New<Number>(baton->width));
info->Set(NanNew<String>("height"), NanNew<Number>(baton->height)); Set(info, New("height").ToLocalChecked(), New<Number>(baton->height));
info->Set(NanNew<String>("space"), NanNew<String>(baton->space)); Set(info, New("space").ToLocalChecked(), New<String>(baton->space).ToLocalChecked());
info->Set(NanNew<String>("channels"), NanNew<Number>(baton->channels)); Set(info, New("channels").ToLocalChecked(), New<Number>(baton->channels));
info->Set(NanNew<String>("hasAlpha"), NanNew<Boolean>(baton->hasAlpha)); Set(info, New("hasProfile").ToLocalChecked(), New<Boolean>(baton->hasProfile));
Set(info, New("hasAlpha").ToLocalChecked(), New<Boolean>(baton->hasAlpha));
if (baton->orientation > 0) { if (baton->orientation > 0) {
info->Set(NanNew<String>("orientation"), NanNew<Number>(baton->orientation)); Set(info, New("orientation").ToLocalChecked(), New<Number>(baton->orientation));
}
if (baton->exifLength > 0) {
Set(info,
New("exif").ToLocalChecked(),
NewBuffer(baton->exif, baton->exifLength, FreeCallback, nullptr).ToLocalChecked()
);
}
if (baton->iccLength > 0) {
Set(info,
New("icc").ToLocalChecked(),
NewBuffer(baton->icc, baton->iccLength, FreeCallback, nullptr).ToLocalChecked()
);
} }
argv[1] = info; argv[1] = info;
} }
// Dispose of Persistent wrapper around input Buffer so it can be garbage collected
if (baton->bufferInLength > 0) {
GetFromPersistent("bufferIn");
}
delete baton; delete baton;
// Return to JavaScript // Return to JavaScript
@@ -118,27 +208,26 @@ class MetadataWorker : public NanAsyncWorker {
metadata(options, callback) metadata(options, callback)
*/ */
NAN_METHOD(metadata) { NAN_METHOD(metadata) {
NanScope(); HandleScope();
// V8 objects are converted to non-V8 types held in the baton struct // V8 objects are converted to non-V8 types held in the baton struct
MetadataBaton *baton = new MetadataBaton; MetadataBaton *baton = new MetadataBaton;
Local<Object> options = args[0]->ToObject(); Local<Object> options = info[0].As<Object>();
// Input filename // Input filename
baton->fileIn = *String::Utf8Value(options->Get(NanNew<String>("fileIn"))->ToString()); baton->fileIn = *Utf8String(Get(options, New("fileIn").ToLocalChecked()).ToLocalChecked());
// Input Buffer object // Input Buffer object
if (options->Get(NanNew<String>("bufferIn"))->IsObject()) { Local<Object> bufferIn;
Local<Object> buffer = options->Get(NanNew<String>("bufferIn"))->ToObject(); if (node::Buffer::HasInstance(Get(options, New("bufferIn").ToLocalChecked()).ToLocalChecked())) {
baton->bufferInLength = node::Buffer::Length(buffer); bufferIn = Get(options, New("bufferIn").ToLocalChecked()).ToLocalChecked().As<Object>();
baton->bufferIn = node::Buffer::Data(buffer); baton->bufferInLength = node::Buffer::Length(bufferIn);
baton->bufferIn = node::Buffer::Data(bufferIn);
} }
// Join queue for worker thread // Join queue for worker thread
NanCallback *callback = new NanCallback(args[1].As<v8::Function>()); Callback *callback = new Callback(info[1].As<Function>());
NanAsyncQueueWorker(new MetadataWorker(callback, baton)); AsyncQueueWorker(new MetadataWorker(callback, baton, bufferIn));
// Increment queued task counter // Increment queued task counter
g_atomic_int_inc(&counter_queue); g_atomic_int_inc(&counterQueue);
NanReturnUndefined();
} }

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_

282
src/operations.cc Normal file
View File

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

34
src/operations.h Executable file
View File

@@ -0,0 +1,34 @@
#ifndef SRC_OPERATIONS_H_
#define SRC_OPERATIONS_H_
namespace sharp {
/*
Composite images `src` and `dst` with premultiplied alpha channel and output
image with premultiplied alpha.
*/
int Composite(VipsObject *context, VipsImage *src, VipsImage *dst, VipsImage **out);
/*
* Stretch luminance to cover full dynamic range.
*/
int Normalize(VipsObject *context, VipsImage *image, VipsImage **out);
/*
* Gaussian blur. Use sigma of -1 for fast blur.
*/
int Blur(VipsObject *context, VipsImage *image, VipsImage **out, double sigma);
/*
* Sharpen flat and jagged areas. Use radius of -1 for fast sharpen.
*/
int Sharpen(VipsObject *context, VipsImage *image, VipsImage **out, int radius, double flat, double jagged);
/*
* Perform thresholding on an image. If the image is not greyscale, will convert before thresholding.
* Pixels with a greyscale value greater-than-or-equal-to `threshold` will be pure white. All others will be pure black.
*/
int Threshold(VipsObject *context, VipsImage *image, VipsImage **out, int threshold);
} // namespace sharp
#endif // SRC_OPERATIONS_H_

1305
src/pipeline.cc Normal file

File diff suppressed because it is too large Load Diff

8
src/pipeline.h Executable file
View File

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

View File

@@ -1,813 +0,0 @@
#include <tuple>
#include <algorithm>
#include <cmath>
#include <node.h>
#include <node_buffer.h>
#include <vips/vips.h>
#include "nan.h"
#include "common.h"
#include "resize.h"
using namespace v8;
typedef enum {
CROP,
MAX,
EMBED
} Canvas;
typedef enum {
ANGLE_0,
ANGLE_90,
ANGLE_180,
ANGLE_270,
ANGLE_LAST
} Angle;
struct ResizeBaton {
std::string fileIn;
void* bufferIn;
size_t bufferInLength;
std::string iccProfileCmyk;
std::string output;
std::string outputFormat;
void* bufferOut;
size_t bufferOutLength;
int topOffsetPre;
int leftOffsetPre;
int widthPre;
int heightPre;
int topOffsetPost;
int leftOffsetPost;
int widthPost;
int heightPost;
int width;
int height;
Canvas canvas;
int gravity;
std::string interpolator;
double background[4];
bool flatten;
bool sharpen;
double gamma;
bool greyscale;
int angle;
bool flip;
bool flop;
bool progressive;
bool withoutEnlargement;
VipsAccess accessMethod;
int quality;
int compressionLevel;
std::string err;
bool withMetadata;
ResizeBaton():
bufferInLength(0),
outputFormat(""),
bufferOutLength(0),
topOffsetPre(-1),
topOffsetPost(-1),
canvas(CROP),
gravity(0),
flatten(false),
sharpen(false),
gamma(0.0),
greyscale(false),
flip(false),
flop(false),
progressive(false),
withoutEnlargement(false),
withMetadata(false) {
background[0] = 0.0;
background[1] = 0.0;
background[2] = 0.0;
background[3] = 255.0;
}
};
class ResizeWorker : public NanAsyncWorker {
public:
ResizeWorker(NanCallback *callback, ResizeBaton *baton) : NanAsyncWorker(callback), baton(baton) {}
~ResizeWorker() {}
/*
libuv worker
*/
void Execute() {
// Decrement queued task counter
g_atomic_int_dec_and_test(&counter_queue);
// Increment processing task counter
g_atomic_int_inc(&counter_process);
// Hang image references from this hook object
VipsObject *hook = reinterpret_cast<VipsObject*>(vips_image_new());
// Input
ImageType inputImageType = UNKNOWN;
VipsImage *image = vips_image_new();
vips_object_local(hook, image);
if (baton->bufferInLength > 1) {
// From buffer
inputImageType = sharp_init_image_from_buffer(&image, baton->bufferIn, baton->bufferInLength, baton->accessMethod);
if (inputImageType == UNKNOWN) {
(baton->err).append("Input buffer contains unsupported image format");
}
} else {
// From file
inputImageType = sharp_init_image_from_file(&image, baton->fileIn.c_str(), baton->accessMethod);
if (inputImageType == UNKNOWN) {
(baton->err).append("File is of an unsupported image format");
}
}
if (inputImageType == UNKNOWN) {
return Error(baton, hook);
}
// Pre extraction
if (baton->topOffsetPre != -1) {
VipsImage *extractedPre = vips_image_new();
vips_object_local(hook, extractedPre);
if (vips_extract_area(image, &extractedPre, baton->leftOffsetPre, baton->topOffsetPre, baton->widthPre, baton->heightPre, NULL)) {
return Error(baton, hook);
}
g_object_unref(image);
image = extractedPre;
}
// Get input image width and height
int inputWidth = image->Xsize;
int inputHeight = image->Ysize;
// Calculate angle of rotation, to be carried out later
Angle rotation;
bool flip;
std::tie(rotation, flip) = CalculateRotationAndFlip(baton->angle, image);
if (rotation == ANGLE_90 || rotation == ANGLE_270) {
// Swap input output width and height when rotating by 90 or 270 degrees
int swap = inputWidth;
inputWidth = inputHeight;
inputHeight = swap;
}
if (flip && !baton->flip) {
// Add flip operation due to EXIF mirroring
baton->flip = TRUE;
}
// Scaling calculations
double factor;
if (baton->width > 0 && baton->height > 0) {
// Fixed width and height
double xfactor = static_cast<double>(inputWidth) / static_cast<double>(baton->width);
double yfactor = static_cast<double>(inputHeight) / static_cast<double>(baton->height);
factor = (baton->canvas == CROP) ? std::min(xfactor, yfactor) : std::max(xfactor, yfactor);
// if max is set, we need to compute the real size of the thumb image
if (baton->canvas == MAX) {
if (xfactor > yfactor) {
baton->height = round(static_cast<double>(inputHeight) / xfactor);
} else {
baton->width = round(static_cast<double>(inputWidth) / yfactor);
}
}
} else if (baton->width > 0) {
// Fixed width, auto height
factor = static_cast<double>(inputWidth) / static_cast<double>(baton->width);
baton->height = floor(static_cast<double>(inputHeight) / factor);
} else if (baton->height > 0) {
// Fixed height, auto width
factor = static_cast<double>(inputHeight) / static_cast<double>(baton->height);
baton->width = floor(static_cast<double>(inputWidth) / factor);
} else {
// Identity transform
factor = 1;
baton->width = inputWidth;
baton->height = inputHeight;
}
int shrink = floor(factor);
if (shrink < 1) {
shrink = 1;
}
double residual = static_cast<double>(shrink) / factor;
// Do not enlarge the output if the input width *or* height are already less than the required dimensions
if (baton->withoutEnlargement) {
if (inputWidth < baton->width || inputHeight < baton->height) {
factor = 1;
shrink = 1;
residual = 0;
baton->width = inputWidth;
baton->height = inputHeight;
}
}
// Try to use libjpeg shrink-on-load, but not when applying gamma correction or pre-resize extract
int shrink_on_load = 1;
if (inputImageType == JPEG && baton->gamma == 0 && baton->topOffsetPre == -1) {
if (shrink >= 8) {
factor = factor / 8;
shrink_on_load = 8;
} else if (shrink >= 4) {
factor = factor / 4;
shrink_on_load = 4;
} else if (shrink >= 2) {
factor = factor / 2;
shrink_on_load = 2;
}
}
if (shrink_on_load > 1) {
// Recalculate integral shrink and double residual
factor = std::max(factor, 1.0);
shrink = floor(factor);
residual = static_cast<double>(shrink) / factor;
// Reload input using shrink-on-load
g_object_unref(image);
if (baton->bufferInLength > 1) {
if (vips_jpegload_buffer(baton->bufferIn, baton->bufferInLength, &image, "shrink", shrink_on_load, NULL)) {
return Error(baton, hook);
}
} else {
if (vips_jpegload((baton->fileIn).c_str(), &image, "shrink", shrink_on_load, NULL)) {
return Error(baton, hook);
}
}
}
// Handle colour profile, if any, for non sRGB images
if (image->Type != VIPS_INTERPRETATION_sRGB) {
// Get the input colour profile
if (vips_image_get_typeof(image, VIPS_META_ICC_NAME)) {
// Use embedded profile
VipsImage *profile = vips_image_new();
vips_object_local(hook, profile);
if (vips_icc_import(image, &profile, "pcs", VIPS_PCS_XYZ, "embedded", TRUE, NULL)) {
return Error(baton, hook);
}
g_object_unref(image);
image = profile;
} else if (image->Type == VIPS_INTERPRETATION_CMYK) {
// CMYK with no embedded profile
VipsImage *profile = vips_image_new();
vips_object_local(hook, profile);
if (vips_icc_import(image, &profile, "pcs", VIPS_PCS_XYZ, "input_profile", (baton->iccProfileCmyk).c_str(), NULL)) {
return Error(baton, hook);
}
g_object_unref(image);
image = profile;
}
// Attempt to convert to sRGB colour space
VipsImage *colourspaced = vips_image_new();
vips_object_local(hook, colourspaced);
if (vips_colourspace(image, &colourspaced, VIPS_INTERPRETATION_sRGB, NULL)) {
return Error(baton, hook);
}
g_object_unref(image);
image = colourspaced;
}
// Flatten image to remove alpha channel
if (baton->flatten && sharp_image_has_alpha(image)) {
// Background colour
VipsArrayDouble *background = vips_array_double_newv(
3, // Ignore alpha channel as we're about to remove it
baton->background[0],
baton->background[1],
baton->background[2]
);
VipsImage *flattened = vips_image_new();
vips_object_local(hook, flattened);
if (vips_flatten(image, &flattened, "background", background, NULL)) {
vips_area_unref(reinterpret_cast<VipsArea*>(background));
return Error(baton, hook);
};
vips_area_unref(reinterpret_cast<VipsArea*>(background));
g_object_unref(image);
image = flattened;
}
// Gamma encoding (darken)
if (baton->gamma >= 1 && baton->gamma <= 3) {
VipsImage *gammaEncoded = vips_image_new();
vips_object_local(hook, gammaEncoded);
if (vips_gamma(image, &gammaEncoded, "exponent", 1.0 / baton->gamma, NULL)) {
return Error(baton, hook);
}
g_object_unref(image);
image = gammaEncoded;
}
// Convert to greyscale (linear, therefore after gamma encoding, if any)
if (baton->greyscale) {
VipsImage *greyscale = vips_image_new();
vips_object_local(hook, greyscale);
if (vips_colourspace(image, &greyscale, VIPS_INTERPRETATION_B_W, NULL)) {
return Error(baton, hook);
}
g_object_unref(image);
image = greyscale;
}
if (shrink > 1) {
VipsImage *shrunk = vips_image_new();
vips_object_local(hook, shrunk);
// Use vips_shrink with the integral reduction
if (vips_shrink(image, &shrunk, shrink, shrink, NULL)) {
return Error(baton, hook);
}
g_object_unref(image);
image = shrunk;
// Recalculate residual float based on dimensions of required vs shrunk images
double shrunkWidth = shrunk->Xsize;
double shrunkHeight = shrunk->Ysize;
if (rotation == ANGLE_90 || rotation == ANGLE_270) {
// Swap input output width and height when rotating by 90 or 270 degrees
int swap = shrunkWidth;
shrunkWidth = shrunkHeight;
shrunkHeight = swap;
}
double residualx = static_cast<double>(baton->width) / static_cast<double>(shrunkWidth);
double residualy = static_cast<double>(baton->height) / static_cast<double>(shrunkHeight);
if (baton->canvas == EMBED) {
residual = std::min(residualx, residualy);
} else {
residual = std::max(residualx, residualy);
}
}
// Use vips_affine with the remaining float part
if (residual != 0) {
VipsImage *affined = vips_image_new();
vips_object_local(hook, affined);
// Create interpolator - "bilinear" (default), "bicubic" or "nohalo"
VipsInterpolate *interpolator = vips_interpolate_new(baton->interpolator.c_str());
// Perform affine transformation
if (vips_affine(image, &affined, residual, 0, 0, residual, "interpolate", interpolator, NULL)) {
g_object_unref(interpolator);
return Error(baton, hook);
}
g_object_unref(interpolator);
g_object_unref(image);
image = affined;
}
// Rotate
if (rotation != ANGLE_0) {
VipsImage *rotated = vips_image_new();
vips_object_local(hook, rotated);
if (vips_rot(image, &rotated, static_cast<VipsAngle>(rotation), NULL)) {
return Error(baton, hook);
}
g_object_unref(image);
image = rotated;
}
// Flip (mirror about Y axis)
if (baton->flip) {
VipsImage *flipped = vips_image_new();
vips_object_local(hook, flipped);
if (vips_flip(image, &flipped, VIPS_DIRECTION_VERTICAL, NULL)) {
return Error(baton, hook);
}
g_object_unref(image);
image = flipped;
}
// Flop (mirror about X axis)
if (baton->flop) {
VipsImage *flopped = vips_image_new();
vips_object_local(hook, flopped);
if (vips_flip(image, &flopped, VIPS_DIRECTION_HORIZONTAL, NULL)) {
return Error(baton, hook);
}
g_object_unref(image);
image = flopped;
}
// Crop/embed
if (image->Xsize != baton->width || image->Ysize != baton->height) {
if (baton->canvas == EMBED) {
// Match background colour space, namely sRGB
if (image->Type != VIPS_INTERPRETATION_sRGB) {
// Convert to sRGB colour space
VipsImage *colourspaced = vips_image_new();
vips_object_local(hook, colourspaced);
if (vips_colourspace(image, &colourspaced, VIPS_INTERPRETATION_sRGB, NULL)) {
return Error(baton, hook);
}
g_object_unref(image);
image = colourspaced;
}
// Add non-transparent alpha channel, if required
if (baton->background[3] < 255.0 && !sharp_image_has_alpha(image)) {
// Create single-channel transparency
VipsImage *black = vips_image_new();
vips_object_local(hook, black);
if (vips_black(&black, image->Xsize, image->Ysize, "bands", 1, NULL)) {
return Error(baton, hook);
}
// Invert to become non-transparent
VipsImage *alpha = vips_image_new();
vips_object_local(hook, alpha);
if (vips_invert(black, &alpha, NULL)) {
return Error(baton, hook);
}
g_object_unref(black);
// Append alpha channel to existing image
VipsImage *joined = vips_image_new();
vips_object_local(hook, joined);
if (vips_bandjoin2(image, alpha, &joined, NULL)) {
return Error(baton, hook);
}
g_object_unref(alpha);
g_object_unref(image);
image = joined;
}
// Create background
VipsArrayDouble *background;
if (baton->background[3] < 255.0) {
background = vips_array_double_newv(
4, baton->background[0], baton->background[1], baton->background[2], baton->background[3]
);
} else {
background = vips_array_double_newv(
3, baton->background[0], baton->background[1], baton->background[2]
);
}
// Embed
int left = (baton->width - image->Xsize) / 2;
int top = (baton->height - image->Ysize) / 2;
VipsImage *embedded = vips_image_new();
vips_object_local(hook, embedded);
if (vips_embed(image, &embedded, left, top, baton->width, baton->height,
"extend", VIPS_EXTEND_BACKGROUND, "background", background, NULL
)) {
vips_area_unref(reinterpret_cast<VipsArea*>(background));
return Error(baton, hook);
}
vips_area_unref(reinterpret_cast<VipsArea*>(background));
g_object_unref(image);
image = embedded;
} else {
// Crop/max
int left;
int top;
std::tie(left, top) = CalculateCrop(image->Xsize, image->Ysize, baton->width, baton->height, baton->gravity);
int width = std::min(image->Xsize, baton->width);
int height = std::min(image->Ysize, baton->height);
VipsImage *extracted = vips_image_new();
vips_object_local(hook, extracted);
if (vips_extract_area(image, &extracted, left, top, width, height, NULL)) {
return Error(baton, hook);
}
g_object_unref(image);
image = extracted;
}
}
// Post extraction
if (baton->topOffsetPost != -1) {
VipsImage *extractedPost = vips_image_new();
vips_object_local(hook, extractedPost);
if (vips_extract_area(image, &extractedPost, baton->leftOffsetPost, baton->topOffsetPost, baton->widthPost, baton->heightPost, NULL)) {
return Error(baton, hook);
}
g_object_unref(image);
image = extractedPost;
}
// Mild sharpen
if (baton->sharpen) {
VipsImage *sharpened = vips_image_new();
vips_object_local(hook, sharpened);
VipsImage *sharpen = vips_image_new_matrixv(3, 3,
-1.0, -1.0, -1.0,
-1.0, 32.0, -1.0,
-1.0, -1.0, -1.0);
vips_image_set_double(sharpen, "scale", 24);
vips_object_local(hook, sharpen);
if (vips_conv(image, &sharpened, sharpen, NULL)) {
return Error(baton, hook);
}
g_object_unref(image);
image = sharpened;
}
// Gamma decoding (brighten)
if (baton->gamma >= 1 && baton->gamma <= 3) {
VipsImage *gammaDecoded = vips_image_new();
vips_object_local(hook, gammaDecoded);
if (vips_gamma(image, &gammaDecoded, "exponent", baton->gamma, NULL)) {
return Error(baton, hook);
}
g_object_unref(image);
image = gammaDecoded;
}
// Convert to sRGB colour space, if not already
if (image->Type != VIPS_INTERPRETATION_sRGB) {
VipsImage *colourspaced = vips_image_new();
vips_object_local(hook, colourspaced);
if (vips_colourspace(image, &colourspaced, VIPS_INTERPRETATION_sRGB, NULL)) {
return Error(baton, hook);
}
g_object_unref(image);
image = colourspaced;
}
// Generate image tile cache when interlace output is required
if (baton->progressive) {
VipsImage *cached = vips_image_new();
vips_object_local(hook, cached);
if (vips_tilecache(image, &cached, "threaded", TRUE, "persistent", TRUE, "max_tiles", -1, NULL)) {
return Error(baton, hook);
}
g_object_unref(image);
image = cached;
}
// Output
if (baton->output == "__jpeg" || (baton->output == "__input" && inputImageType == JPEG)) {
// Write JPEG to buffer
if (vips_jpegsave_buffer(image, &baton->bufferOut, &baton->bufferOutLength, "strip", !baton->withMetadata,
"Q", baton->quality, "optimize_coding", TRUE, "interlace", baton->progressive, NULL)) {
return Error(baton, hook);
}
baton->outputFormat = "jpeg";
} else if (baton->output == "__png" || (baton->output == "__input" && inputImageType == PNG)) {
// Write PNG to buffer
if (vips_pngsave_buffer(image, &baton->bufferOut, &baton->bufferOutLength, "strip", !baton->withMetadata,
"compression", baton->compressionLevel, "interlace", baton->progressive, NULL)) {
return Error(baton, hook);
}
baton->outputFormat = "png";
} else if (baton->output == "__webp" || (baton->output == "__input" && inputImageType == WEBP)) {
// Write WEBP to buffer
if (vips_webpsave_buffer(image, &baton->bufferOut, &baton->bufferOutLength, "strip", !baton->withMetadata,
"Q", baton->quality, NULL)) {
return Error(baton, hook);
}
baton->outputFormat = "webp";
} else {
bool output_jpeg = is_jpeg(baton->output);
bool output_png = is_png(baton->output);
bool output_webp = is_webp(baton->output);
bool output_tiff = is_tiff(baton->output);
bool match_input = !(output_jpeg || output_png || output_webp || output_tiff);
if (output_jpeg || (match_input && inputImageType == JPEG)) {
// Write JPEG to file
if (vips_jpegsave(image, baton->output.c_str(), "strip", !baton->withMetadata,
"Q", baton->quality, "optimize_coding", TRUE, "interlace", baton->progressive, NULL)) {
return Error(baton, hook);
}
baton->outputFormat = "jpeg";
} else if (output_png || (match_input && inputImageType == PNG)) {
// Write PNG to file
if (vips_pngsave(image, baton->output.c_str(), "strip", !baton->withMetadata,
"compression", baton->compressionLevel, "interlace", baton->progressive, NULL)) {
return Error(baton, hook);
}
baton->outputFormat = "png";
} else if (output_webp || (match_input && inputImageType == WEBP)) {
// Write WEBP to file
if (vips_webpsave(image, baton->output.c_str(), "strip", !baton->withMetadata,
"Q", baton->quality, NULL)) {
return Error(baton, hook);
}
baton->outputFormat = "webp";
} else if (output_tiff || (match_input && inputImageType == TIFF)) {
// Write TIFF to file
if (vips_tiffsave(image, baton->output.c_str(), "strip", !baton->withMetadata,
"compression", VIPS_FOREIGN_TIFF_COMPRESSION_JPEG, "Q", baton->quality, NULL)) {
return Error(baton, hook);
}
baton->outputFormat = "tiff";
} else {
(baton->err).append("Unsupported output " + baton->output);
g_object_unref(image);
return Error(baton, hook);
}
}
// Clean up any dangling image references
g_object_unref(image);
g_object_unref(hook);
// Clean up libvips' per-request data and threads
vips_error_clear();
vips_thread_shutdown();
}
void HandleOKCallback () {
NanScope();
Handle<Value> argv[3] = { NanNull(), NanNull(), NanNull() };
if (!baton->err.empty()) {
// Error
argv[0] = Exception::Error(NanNew<String>(baton->err.data(), baton->err.size()));
} else {
int width = baton->width;
int height = baton->height;
if (baton->topOffsetPre != -1 && (baton->width == -1 || baton->height == -1)) {
width = baton->widthPre;
height = baton->heightPre;
}
if (baton->topOffsetPost != -1) {
width = baton->widthPost;
height = baton->heightPost;
}
// Info Object
Local<Object> info = NanNew<Object>();
info->Set(NanNew<String>("format"), NanNew<String>(baton->outputFormat));
info->Set(NanNew<String>("width"), NanNew<Number>(width));
info->Set(NanNew<String>("height"), NanNew<Number>(height));
if (baton->bufferOutLength > 0) {
// Buffer
argv[1] = NanNewBufferHandle(static_cast<char*>(baton->bufferOut), baton->bufferOutLength);
g_free(baton->bufferOut);
argv[2] = info;
} else {
// File
argv[1] = info;
}
}
delete baton;
// Decrement processing task counter
g_atomic_int_dec_and_test(&counter_process);
// Return to JavaScript
callback->Call(3, argv);
}
private:
ResizeBaton* baton;
/*
Calculate the angle of rotation and need-to-flip for the output image.
In order of priority:
1. Use explicitly requested angle (supports 90, 180, 270)
2. Use input image EXIF Orientation header - supports mirroring
3. Otherwise default to zero, i.e. no rotation
*/
std::tuple<Angle, bool>
CalculateRotationAndFlip(int const angle, VipsImage const *input) {
Angle rotate = ANGLE_0;
bool flip = FALSE;
if (angle == -1) {
const char *exif;
if (
vips_image_get_typeof(input, "exif-ifd0-Orientation") != 0 &&
!vips_image_get_string(input, "exif-ifd0-Orientation", &exif)
) {
if (exif[0] == 0x36) { // "6"
rotate = ANGLE_90;
} else if (exif[0] == 0x33) { // "3"
rotate = ANGLE_180;
} else if (exif[0] == 0x38) { // "8"
rotate = ANGLE_270;
} else if (exif[0] == 0x32) { // "2" (flip 1)
flip = TRUE;
} else if (exif[0] == 0x37) { // "7" (flip 6)
rotate = ANGLE_90;
flip = TRUE;
} else if (exif[0] == 0x34) { // "4" (flip 3)
rotate = ANGLE_180;
flip = TRUE;
} else if (exif[0] == 0x35) { // "5" (flip 8)
rotate = ANGLE_270;
flip = TRUE;
}
}
} else {
if (angle == 90) {
rotate = ANGLE_90;
} else if (angle == 180) {
rotate = ANGLE_180;
} else if (angle == 270) {
rotate = ANGLE_270;
}
}
return std::make_tuple(rotate, flip);
}
/*
Calculate the (left, top) coordinates of the output image
within the input image, applying the given gravity.
*/
std::tuple<int, int>
CalculateCrop(int const inWidth, int const inHeight, int const outWidth, int const outHeight, int const gravity) {
int left = 0;
int top = 0;
switch (gravity) {
case 1: // North
left = (inWidth - outWidth + 1) / 2;
break;
case 2: // East
left = inWidth - outWidth;
top = (inHeight - outHeight + 1) / 2;
break;
case 3: // South
left = (inWidth - outWidth + 1) / 2;
top = inHeight - outHeight;
break;
case 4: // West
top = (inHeight - outHeight + 1) / 2;
break;
default: // Centre
left = (inWidth - outWidth + 1) / 2;
top = (inHeight - outHeight + 1) / 2;
}
return std::make_tuple(left, top);
}
/*
Copy then clear the error message.
Unref all transitional images on the hook.
Clear all thread-local data.
*/
void Error(ResizeBaton *baton, VipsObject *hook) {
(baton->err).append(vips_error_buffer());
vips_error_clear();
g_object_unref(hook);
vips_thread_shutdown();
}
};
/*
resize(options, output, callback)
*/
NAN_METHOD(resize) {
NanScope();
// V8 objects are converted to non-V8 types held in the baton struct
ResizeBaton *baton = new ResizeBaton;
Local<Object> options = args[0]->ToObject();
// Input filename
baton->fileIn = *String::Utf8Value(options->Get(NanNew<String>("fileIn"))->ToString());
baton->accessMethod = options->Get(NanNew<String>("sequentialRead"))->BooleanValue() ? VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_RANDOM;
// Input Buffer object
if (options->Get(NanNew<String>("bufferIn"))->IsObject()) {
Local<Object> buffer = options->Get(NanNew<String>("bufferIn"))->ToObject();
baton->bufferInLength = node::Buffer::Length(buffer);
baton->bufferIn = node::Buffer::Data(buffer);
}
// ICC profile to use when input CMYK image has no embedded profile
baton->iccProfileCmyk = *String::Utf8Value(options->Get(NanNew<String>("iccProfileCmyk"))->ToString());
// Extract image options
baton->topOffsetPre = options->Get(NanNew<String>("topOffsetPre"))->Int32Value();
baton->leftOffsetPre = options->Get(NanNew<String>("leftOffsetPre"))->Int32Value();
baton->widthPre = options->Get(NanNew<String>("widthPre"))->Int32Value();
baton->heightPre = options->Get(NanNew<String>("heightPre"))->Int32Value();
baton->topOffsetPost = options->Get(NanNew<String>("topOffsetPost"))->Int32Value();
baton->leftOffsetPost = options->Get(NanNew<String>("leftOffsetPost"))->Int32Value();
baton->widthPost = options->Get(NanNew<String>("widthPost"))->Int32Value();
baton->heightPost = options->Get(NanNew<String>("heightPost"))->Int32Value();
// Output image dimensions
baton->width = options->Get(NanNew<String>("width"))->Int32Value();
baton->height = options->Get(NanNew<String>("height"))->Int32Value();
// Canvas option
Local<String> canvas = options->Get(NanNew<String>("canvas"))->ToString();
if (canvas->Equals(NanNew<String>("c"))) {
baton->canvas = CROP;
} else if (canvas->Equals(NanNew<String>("m"))) {
baton->canvas = MAX;
} else if (canvas->Equals(NanNew<String>("e"))) {
baton->canvas = EMBED;
}
// Background colour
Local<Array> background = Local<Array>::Cast(options->Get(NanNew<String>("background")));
for (int i = 0; i < 4; i++) {
baton->background[i] = background->Get(i)->NumberValue();
}
// Resize options
baton->withoutEnlargement = options->Get(NanNew<String>("withoutEnlargement"))->BooleanValue();
baton->gravity = options->Get(NanNew<String>("gravity"))->Int32Value();
baton->interpolator = *String::Utf8Value(options->Get(NanNew<String>("interpolator"))->ToString());
// Operators
baton->flatten = options->Get(NanNew<String>("flatten"))->BooleanValue();
baton->sharpen = options->Get(NanNew<String>("sharpen"))->BooleanValue();
baton->gamma = options->Get(NanNew<String>("gamma"))->NumberValue();
baton->greyscale = options->Get(NanNew<String>("greyscale"))->BooleanValue();
baton->angle = options->Get(NanNew<String>("angle"))->Int32Value();
baton->flip = options->Get(NanNew<String>("flip"))->BooleanValue();
baton->flop = options->Get(NanNew<String>("flop"))->BooleanValue();
// Output options
baton->progressive = options->Get(NanNew<String>("progressive"))->BooleanValue();
baton->quality = options->Get(NanNew<String>("quality"))->Int32Value();
baton->compressionLevel = options->Get(NanNew<String>("compressionLevel"))->Int32Value();
baton->withMetadata = options->Get(NanNew<String>("withMetadata"))->BooleanValue();
// Output filename or __format for Buffer
baton->output = *String::Utf8Value(options->Get(NanNew<String>("output"))->ToString());
// Join queue for worker thread
NanCallback *callback = new NanCallback(args[1].As<v8::Function>());
NanAsyncQueueWorker(new ResizeWorker(callback, baton));
// Increment queued task counter
g_atomic_int_inc(&counter_queue);
NanReturnUndefined();
}

View File

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

41
src/sharp.cc Executable file → Normal file
View File

@@ -5,34 +5,35 @@
#include "common.h" #include "common.h"
#include "metadata.h" #include "metadata.h"
#include "resize.h" #include "pipeline.h"
#include "utilities.h" #include "utilities.h"
using namespace v8; NAN_MODULE_INIT(init) {
static void at_exit(void* arg) {
NanScope();
vips_shutdown();
}
extern "C" void init(Handle<Object> target) {
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); Nan::Set(target, Nan::New("metadata").ToLocalChecked(),
NODE_SET_METHOD(target, "resize", resize); Nan::GetFunction(Nan::New<v8::FunctionTemplate>(metadata)).ToLocalChecked());
NODE_SET_METHOD(target, "cache", cache); Nan::Set(target, Nan::New("pipeline").ToLocalChecked(),
NODE_SET_METHOD(target, "concurrency", concurrency); Nan::GetFunction(Nan::New<v8::FunctionTemplate>(pipeline)).ToLocalChecked());
NODE_SET_METHOD(target, "counters", counters); Nan::Set(target, Nan::New("cache").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(cache)).ToLocalChecked());
Nan::Set(target, Nan::New("concurrency").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(concurrency)).ToLocalChecked());
Nan::Set(target, Nan::New("counters").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(counters)).ToLocalChecked());
Nan::Set(target, Nan::New("simd").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(simd)).ToLocalChecked());
Nan::Set(target, Nan::New("libvipsVersion").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(libvipsVersion)).ToLocalChecked());
Nan::Set(target, Nan::New("format").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(format)).ToLocalChecked());
Nan::Set(target, Nan::New("_maxColourDistance").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(_maxColourDistance)).ToLocalChecked());
} }
NODE_MODULE(sharp, init) NODE_MODULE(sharp, init)

268
src/utilities.cc Executable file → Normal file
View File

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

10
src/utilities.h Executable file → Normal file
View File

@@ -1,10 +1,14 @@
#ifndef SHARP_UTILITIES_H #ifndef SRC_UTILITIES_H_
#define SHARP_UTILITIES_H #define SRC_UTILITIES_H_
#include "nan.h" #include "nan.h"
NAN_METHOD(cache); NAN_METHOD(cache);
NAN_METHOD(concurrency); NAN_METHOD(concurrency);
NAN_METHOD(counters); NAN_METHOD(counters);
NAN_METHOD(simd);
NAN_METHOD(libvipsVersion);
NAN_METHOD(format);
NAN_METHOD(_maxColourDistance);
#endif #endif // SRC_UTILITIES_H_

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

@@ -8,13 +8,16 @@
"test": "node perf && node random && node parallel" "test": "node perf && node random && node parallel"
}, },
"devDependencies": { "devDependencies": {
"async": "^1.5.0",
"benchmark": "^1.0.0",
"gm": "^1.21.0",
"imagemagick": "^0.1.3", "imagemagick": "^0.1.3",
"imagemagick-native": "^1.4.0", "imagemagick-native": "elad/node-imagemagick-native",
"gm": "^1.16.0", "jimp": "^0.2.20",
"async": "^0.9.0", "lwip": "^0.0.8",
"benchmark": "^1.0.0" "semver": "^5.1.0"
}, },
"license": "Apache 2.0", "license": "Apache-2.0",
"engines": { "engines": {
"node": ">=0.10" "node": ">=0.10"
} }

5
test/bench/parallel.js Executable file → Normal file
View File

@@ -1,5 +1,7 @@
'use strict'; 'use strict';
process.env.UV_THREADPOOL_SIZE = 64;
var assert = require('assert'); var assert = require('assert');
var async = require('async'); var async = require('async');
@@ -10,12 +12,13 @@ var width = 720;
var height = 480; var height = 480;
sharp.concurrency(1); sharp.concurrency(1);
sharp.simd(true);
var timer = setInterval(function() { var timer = setInterval(function() {
console.dir(sharp.counters()); console.dir(sharp.counters());
}, 100); }, 100);
async.mapSeries([1, 1, 2, 4, 8, 16, 32, 64, 128], function(parallelism, next) { async.mapSeries([1, 1, 2, 4, 8, 16, 32, 64], function(parallelism, next) {
var start = new Date().getTime(); var start = new Date().getTime();
async.times(parallelism, async.times(parallelism,
function(id, callback) { function(id, callback) {

869
test/bench/perf.js Executable file → Normal file

File diff suppressed because it is too large Load Diff

17
test/bench/random.js Executable file → Normal file
View File

@@ -8,11 +8,16 @@ var Benchmark = require('benchmark');
var sharp = require('../../index'); var sharp = require('../../index');
var fixtures = require('../fixtures'); var fixtures = require('../fixtures');
sharp.simd(true);
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 +28,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,7 +42,11 @@ 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)
.resize(randomDimension(), randomDimension())
.filter(magickFilter)
.quality(80)
.toBuffer(function (err, buffer) {
if (err) { if (err) {
throw err; throw err;
} else { } else {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 813 KiB

After

Width:  |  Height:  |  Size: 810 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

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

Binary file not shown.

BIN
test/fixtures/Landscape_1.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

BIN
test/fixtures/Landscape_2.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

BIN
test/fixtures/Landscape_3.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

BIN
test/fixtures/Landscape_4.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

BIN
test/fixtures/Landscape_6.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

BIN
test/fixtures/Landscape_7.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

BIN
test/fixtures/Portrait_1.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

BIN
test/fixtures/Portrait_2.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

BIN
test/fixtures/Portrait_3.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

BIN
test/fixtures/Portrait_4.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

BIN
test/fixtures/Portrait_5.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

BIN
test/fixtures/Portrait_6.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

BIN
test/fixtures/Portrait_7.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

BIN
test/fixtures/Portrait_8.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

17
test/fixtures/Wikimedia-logo.svg vendored Normal file
View File

@@ -0,0 +1,17 @@
<?xml version="1.0" standalone="yes"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1"
id="Wikimedia logo"
viewBox="-599 -599 1198 1198" width="1024" height="1024">
<defs>
<clipPath id="mask">
<path d="M 47.5,-87.5 v 425 h -95 v -425 l -552,-552 v 1250 h 1199 v -1250 z" />
</clipPath>
</defs>
<g clip-path="url(#mask)">
<circle id="green parts" fill="#396" r="336.5"/>
<circle id="blue arc" fill="none" stroke="#069" r="480.25" stroke-width="135.5" />
</g>
<circle fill="#900" cy="-379.5" r="184.5" id="red circle"/>
</svg>

After

Width:  |  Height:  |  Size: 692 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 640 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

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