Compare commits

..

54 Commits

Author SHA1 Message Date
Lovell Fuller
2d500554c1 Release v0.15.1 2016-07-12 16:06:38 +01:00
Lovell Fuller
c42fb97419 Refactor pipeline to use common 16-bit detection methods 2016-07-11 23:03:45 +01:00
Lovell Fuller
d1d6155fd1 Increase unit test coverage for recently added operations
Switch param validation to use internal functions
2016-07-11 22:23:15 +01:00
Lovell Fuller
ff8c42e894 Changelog/doc/test updates for various new operations
Dependency version bumps
2016-07-11 15:52:30 +01:00
Lovell Fuller
e10aeb29eb Add missing include introduced by fee3d88 2016-07-11 14:05:26 +01:00
Lovell Fuller
fee3d882c7 Prevent GC of Buffer object vector via accumulate/lambda.
Removes need for naming Buffers and separate container struct.
2016-07-11 13:07:32 +01:00
Matt Hirsch
d17e8d3450 Add boolean feature for bitwise image operations (#501) 2016-07-11 09:51:43 +01:00
Lovell Fuller
99f960bf56 Docs: Clarify trimming of existing cache entries #487 2016-07-11 09:42:19 +01:00
Matt Hirsch
83d8847f57 Add extractChannel operation to extract a channel from an image (#497) 2016-07-09 16:48:30 +01:00
Matt Hirsch
f672f86b53 Add ability to read and write native vips .v files (#500) 2016-07-09 16:21:16 +01:00
Kleis Auke Wolthuizen
b69627891d Add trim operation to remove "boring" edges (#491 #492) 2016-07-08 22:19:10 +01:00
Matt Hirsch
673d8278b5 Add a unit test for extract area out of bounds (#498) 2016-07-08 20:52:33 +01:00
Lovell Fuller
8dd554b935 Use "previous" AppVeyor env as temp workaround for nodejs/node-gyp#952 2016-07-08 13:15:24 +01:00
Matt Hirsch
65b7f7d7d5 Add bandbool feature for channel-wise boolean operations (#496) 2016-07-07 21:03:49 +01:00
Matt Hirsch
a982cfdb20 Update docs to better reflect output of RAW data (#499) 2016-07-07 20:50:59 +01:00
Lovell Fuller
7689fbe54d Ensure --sharp-cxx11 flag is passed through #456 #494
node-gyp replaces dashes with underscores
2016-07-06 10:40:53 +01:00
Lovell Fuller
c9d32e22d3 Docs, changelog for top/left overlayWith offset #473 2016-07-05 11:17:41 +01:00
Rahul Nanwani
278273b5c3 Add top/left offset support to overlayWith operation (#473) 2016-07-05 10:12:02 +01:00
Lovell Fuller
a5d85b8a54 Changelog plus tidy of code/docs for convolve operation 2016-07-04 22:13:47 +01:00
Lovell Fuller
4c172d25f6 Allow images with alpha channel to use LAB sharpen #490 2016-07-04 21:33:44 +01:00
Matt Hirsch
b70a7d9a3b Add convolve operation for kernel-based convolution (#479) 2016-07-04 20:48:00 +01:00
Lovell Fuller
ba5a8b44ed Changelog, credit and doc tidy for improved threshold operation 2016-07-04 10:41:13 +01:00
Lovell Fuller
91e1ed1314 Ensure ICC profiles are licenced for distribution #486 2016-07-04 10:13:16 +01:00
Matt Hirsch
85f20c6e1b Add greyscale option to threshold operation (#480) 2016-07-03 19:32:07 +01:00
Lovell Fuller
4b98dbb454 Docs: reduce ambiguity of tile-based ZIP file output #474 2016-06-26 13:58:49 +01:00
Lovell Fuller
c3ad4fbdaa Document new cutout option of overlayWith feature #435 2016-06-26 13:53:20 +01:00
Kleis Auke Wolthuizen
2e9cd83ed2 Add support for clipping/cutting out (#435) (#448)
USAGE: overlayWith('overlayimage.png', { cutout: true } )
2016-06-25 16:48:01 +01:00
Lovell Fuller
f1ead06645 Update test expectations/thresholds previously failing on OS X.
Remove Node v5 from CI builds.
2016-06-14 22:25:27 +01:00
Teoh Han Hui
d486eaad03 Document breaking change in extract API in v0.14.0 (#465)
Deprecated style of calling extract was removed in 2034efc
2016-06-14 10:46:19 +01:00
Lovell Fuller
7d261a147d Ensure scaling factors are calculated independently #452
Fixes bug introduced in v0.15.0 where, if the shrink operation
rounded up along one dimension, it could then also round up the
reduce operation on the same axis, creating a small stretch effect.
2016-06-13 23:03:45 +01:00
Lovell Fuller
61038888c4 Document --sharp-cxx11 install flag #456 2016-06-11 08:28:27 +01:00
Jérémy Lal
39040fb9a0 Allow node-gyp configure --sharp-cxx11=1 (#456)
Can also be set in .npmrc.
Closes: #442
2016-06-08 17:42:46 +01:00
Lovell Fuller
4f3262c328 List licence details for all library dependencies 2016-05-27 23:17:06 +01:00
Lovell Fuller
69126a7c5f Add docs and credits for #439 and #443 2016-05-27 21:33:07 +01:00
lemnisk8
62554b766f Add support for repeated/tiled overlay image (#443)
USAGE: overlayWith('overlayimage.png', { tile: true, gravity: northwest} )
When using the tile option, the gravity option is applied to the extracted part of the tiled overlay image.
2016-05-26 16:42:17 +01:00
frulo
e699e36270 Add alpha channel, if required, before extend operation (#439) 2016-05-26 09:46:14 +01:00
Lovell Fuller
331926dc3c Concat Stream-based input in single operation for ~+3% perf and less GC #429 2016-05-24 21:04:18 +01:00
Lovell Fuller
8a3b660bbc Release v0.15.0 2016-05-21 15:33:56 +01:00
Lovell Fuller
933989c87d Update benchmark results ahead of v0.15.0, ~20% improvement 2016-05-21 10:21:42 +01:00
Lovell Fuller
e3cbcb98c0 Add hints about compiling with _GLIBCXX_USE_CXX11_ABI #432
Increase deprecatedness of preinstall script
Additional valgrind suppressions for libwebp
2016-05-21 09:03:09 +01:00
Lovell Fuller
32a2787254 Thank you to all the new contributors 2016-05-18 20:40:31 +01:00
Lovell Fuller
fccfc27de0 Update ARM packaging to libvips v8.3.1 2016-05-18 19:58:30 +01:00
Lovell Fuller
cdb2894bd9 Use libvips' new lanczos3 kernel as default for image reduce
Deprecate interpolateWith method, now provided as an option
2016-05-18 19:57:22 +01:00
Lovell Fuller
051d022fc2 Upgrade to libvips v8.3.1
Remove packaging tests and therefore support for Centos 6
2016-05-08 22:15:08 +01:00
Lovell Fuller
7388d97502 Allow keyword macros for glib support on MSVC 2016-05-08 13:30:42 +01:00
Lovell Fuller
1bece3a792 Add 2 channel (grey+alpha) GIF test case #375 2016-05-07 20:04:17 +01:00
Lovell Fuller
1de0038516 Upgrade to libvips 8.3.x
Add support for libvips' new native loaders, including GIF and SVG
Pre-built binaries now include giflib and librsvg, exclude *magick
2016-05-07 20:04:17 +01:00
Lovell Fuller
b7a098fb28 Break existing sharpen API to accept sigma and improve precision 2016-05-07 20:04:17 +01:00
Lovell Fuller
ee21d2991c Use shrink-on-load for WebP input 2016-05-07 20:04:17 +01:00
Lovell Fuller
f8eab49962 Add Node v6 to CI builds 2016-05-07 19:50:15 +01:00
Lovell Fuller
c9b3847a69 Docs: basic security considerations for installation #424 2016-05-07 19:48:06 +01:00
Felix Bünemann
dce3840537 Update Lambda instructions for Node.js 4.3 (#419)
Amazon introduced Node.js 4.3 support for Lambda, which is now the
recommended runtime instead of the old Node.js 0.10. This commit revises
the Lambda docs to build Node.js 4.3 compatible binaries using the
latest stable Node.js 4.x packages from Nodesource.
2016-04-27 19:39:30 +01:00
Lovell Fuller
b6030c161b Update expected test fixtures for libvips 8.3 2016-04-23 20:07:55 +01:00
Lovell Fuller
c920180cb3 Remove (un)premultiply ops when not resizing/compositing #413 2016-04-23 19:50:00 +01:00
126 changed files with 3285 additions and 1183 deletions

View File

@@ -2,7 +2,7 @@
"strict": true,
"node": true,
"maxparams": 4,
"maxcomplexity": 13,
"maxcomplexity": 14,
"globals": {
"beforeEach": true,
"afterEach": true,

View File

@@ -3,7 +3,7 @@ node_js:
- "0.10"
- "0.12"
- "4"
- "5"
- "6"
os:
- linux
- osx

View File

@@ -41,7 +41,6 @@ Any change that modifies the existing public API should be added to the relevant
| Release | WIP branch |
| ------: | :--------- |
| v0.15.0 | outfit |
| v0.16.0 | pencil |
| v0.17.0 | quill |

View File

@@ -1,14 +1,14 @@
# sharp
The typical use case for this high speed Node.js module
is to convert large images of many formats to
is to convert large images in common formats to
smaller, web-friendly JPEG, PNG and WebP images of varying dimensions.
Resizing an image is typically 4x faster than using the
Resizing an image is typically 4x-5x 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.
Lanczos resampling ensures quality is not sacrificed for speed.
As well as image resizing, operations such as
rotation, extraction, compositing and gamma correction are available.

View File

@@ -1,4 +1,4 @@
os: Visual Studio 2015
os: Previous Visual Studio 2015
version: "{build}"
build: off
platform: x64
@@ -7,7 +7,7 @@ environment:
matrix:
- nodejs_version: "0.12"
- nodejs_version: "4"
- nodejs_version: "5"
- nodejs_version: "6"
install:
- ps: Install-Product node $env:nodejs_version x64
- npm install

View File

@@ -9,7 +9,8 @@
'download_vips': '<!(node -e "require(\'./binding\').download_vips()")'
},
'defines': [
'VIPS_CPLUSPLUS_EXPORTS'
'VIPS_CPLUSPLUS_EXPORTS',
'_ALLOW_KEYWORD_MACROS'
],
'sources': [
'src/libvips/cplusplus/VError.cpp',
@@ -50,6 +51,7 @@
],
# Nested variables "pattern" borrowed from http://src.chromium.org/viewvc/chrome/trunk/src/build/common.gypi
'variables': {
'sharp_cxx11%': '0',
'variables': {
'variables': {
'conditions': [
@@ -91,7 +93,8 @@
'src/utilities.cc'
],
'defines': [
'_GLIBCXX_USE_CXX11_ABI=0'
'_GLIBCXX_USE_CXX11_ABI=<(sharp_cxx11)',
'_ALLOW_KEYWORD_MACROS'
],
'include_dirs': [
'<!(node -e "require(\'nan\')")'
@@ -132,23 +135,33 @@
'<(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/libGraphicsMagick.so',
'<(module_root_dir)/lib/libGraphicsMagickWand.so',
'<(module_root_dir)/lib/libcairo.so',
'<(module_root_dir)/lib/libcroco-0.6.so',
'<(module_root_dir)/lib/libexif.so',
'<(module_root_dir)/lib/libffi.so',
'<(module_root_dir)/lib/libfontconfig.so',
'<(module_root_dir)/lib/libfreetype.so',
'<(module_root_dir)/lib/libgdk_pixbuf-2.0.so',
'<(module_root_dir)/lib/libgif.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/libgthread-2.0.so',
'<(module_root_dir)/lib/libharfbuzz.so',
'<(module_root_dir)/lib/libjpeg.so',
'<(module_root_dir)/lib/liblcms2.so',
'<(module_root_dir)/lib/liborc-0.4.so',
'<(module_root_dir)/lib/libpango-1.0.so',
'<(module_root_dir)/lib/libpangocairo-1.0.so',
'<(module_root_dir)/lib/libpangoft2-1.0.so',
'<(module_root_dir)/lib/libpixman-1.so',
'<(module_root_dir)/lib/libpng.so',
'<(module_root_dir)/lib/libpng16.so',
'<(module_root_dir)/lib/librsvg-2.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',
'<(module_root_dir)/lib/libz.so',
# Ensure runtime linking is relative to sharp.node
'-Wl,-rpath=\'$${ORIGIN}/../../lib\''
]
@@ -199,12 +212,11 @@
'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/libcroco-0.6-3.dll',
'<(module_root_dir)/lib/libexif-12.dll',
'<(module_root_dir)/lib/libexpat-1.dll',
'<(module_root_dir)/lib/libffi-6.dll',
@@ -213,6 +225,7 @@
'<(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/libgif-4.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',
@@ -222,18 +235,18 @@
'<(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/libopenjp2.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/librsvg-2-2.dll',
'<(module_root_dir)/lib/libssp-0.dll',
'<(module_root_dir)/lib/libstdc++-6.dll',
'<(module_root_dir)/lib/libtiff-5.dll',
'<(module_root_dir)/lib/libvips-42.dll',
'<(module_root_dir)/lib/libwebp-6.dll',
'<(module_root_dir)/lib/libxml2-2.dll',
'<(module_root_dir)/lib/zlib1.dll'
]

View File

@@ -13,7 +13,7 @@ Constructor to which further methods are chained.
`input`, if present, can be one of:
* Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or
* String containing the path to an image file, with most major formats supported.
* String containing the path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file.
JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data
can be streamed into the object when `input` is `null` or `undefined`.
@@ -57,7 +57,7 @@ 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`, `openslide`, `ppm`, `fits`)
* `format`: Name of decoder used to decompress image data e.g. `jpeg`, `png`, `webp`, `gif`, `svg`
* `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)
@@ -118,7 +118,7 @@ Do not process input images where the number of pixels (width * height) exceeds
### Resizing
#### resize([width], [height])
#### resize([width], [height], [options])
Scale output to `width` x `height`. By default, the resized image is cropped to the exact size specified.
@@ -126,6 +126,42 @@ Scale output to `width` x `height`. By default, the resized image is cropped to
`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.
`options` is an optional Object. If present, it can contain one or more of:
* `options.kernel`, the kernel to use for image reduction, defaulting to `lanczos3`.
* `options.interpolator`, the interpolator to use for image enlargement, defaulting to `bicubic`.
Possible kernels are:
* `cubic`: Use a [Catmull-Rom spline](https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline).
* `lanczos2`: Use a [Lanczos kernel](https://en.wikipedia.org/wiki/Lanczos_resampling#Lanczos_kernel) with `a=2`.
* `lanczos3`: Use a Lanczos kernel with `a=3` (the default).
Possible interpolators are:
* `nearest`: Use [nearest neighbour interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation).
* `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.
```javascript
sharp(inputBuffer)
.resize(200, 300, {
kernel: sharp.kernel.lanczos2,
interpolator: 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 lanczos2/nohalo scaled version, embedded on a white canvas,
// of the image data in inputBuffer
});
```
#### crop([option])
Crop the resized image to the exact size specified, the default behaviour.
@@ -232,37 +268,6 @@ if its width or height exceeds the geometry specification*".
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 })
@@ -293,6 +298,12 @@ sharp(input)
});
```
#### trim([tolerance])
Trim "boring" pixels from all edges that contain values within a percentage similarity of the top-left pixel.
* `tolerance`, if present, is an integral Number between 1 and 99 representing the percentage similarity, defaulting to 10.
#### background(rgba)
Set the background for the `embed`, `flatten` and `extend` operations.
@@ -374,23 +385,49 @@ When used without parameters, performs a fast, mild blur of the output image. Th
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.
* `sigma`, if present, is a Number between 0.3 and 1000 representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
#### sharpen([radius], [flat], [jagged])
#### convolve(kernel)
Convolve the image with the specified `kernel`, an Object with the following attributes:
* `width` is an integral Number representing the width of the kernel in pixels.
* `height` is an integral Number representing the width of the kernel in pixels.
* `kernel` is an Array of length `width*height` containing the kernel values.
* `scale`, if present, is a Number representing the scale of the kernel in pixels, defaulting to the sum of the kernel's values.
* `offset`, if present, is a Number representing the offset of the kernel in pixels, defaulting to 0.
```javascript
sharp(input)
.convolve({
width: 3,
height: 3,
kernel: [-1, 0, 1, -2, 0, 2, -1, 0, 1]
})
.raw()
.toBuffer(function(err, data, info) {
// data contains the raw pixel data representing the convolution
// of the input image with the horizontal Sobel operator
});
```
#### sharpen([sigma], [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%.
When a `sigma` 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.
* `sigma`, if present, is a Number representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
* `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])
#### threshold([threshold], [options])
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.
Any pixel value greather than or equal to the threshold value will be set to 255, otherwise it will be set to 0.
By default, the image will be converted to single channel greyscale before thresholding.
* `threshold`, if present, is a Number, representing the level above which pixels will be forced to white.
* `threshold`, if present, is a Number between 0 and 255, representing the level at which the threshold will be applied. The default threshold is 128.
* `options`, if present, is an Object containing a Boolean `greyscale` (or `grayscale`). When `false` each channel will have the threshold applied independently.
#### gamma([gamma])
@@ -426,6 +463,12 @@ Overlay (composite) a image containing an alpha channel over the processed (resi
`options`, if present, is an Object with the following optional attributes:
* `gravity` is a String or an attribute of the `sharp.gravity` Object e.g. `sharp.gravity.north` at which to place the overlay, defaulting to `center`/`centre`.
* `top` is an integral Number representing the pixel offset from the top edge.
* `left` is an integral Number representing the pixel offset from the left edge.
* `tile` is a Boolean, defaulting to `false`. When set to `true` repeats the overlay image across the entire image with the given `gravity`.
* `cutout` is a Boolean, defaulting to `false`. When set to `true` applies only the alpha channel of the overlay image to the image to be overlaid, giving the appearance of one image being cut out of another.
If both `top` and `left` are provided, they take precedence over `gravity`.
```javascript
sharp('input.png')
@@ -446,13 +489,61 @@ sharp('input.png')
});
```
#### extractChannel(channel)
Extract a single channel from a multi-channel image.
* `channel` is a zero-indexed integral Number representing the band number to extract. `red`, `green` or `blue` are also accepted as an alternative to `0`, `1` or `2` respectively.
```javascript
sharp(input)
.extractChannel('green')
.toFile('input_green.jpg', function(err, info) {
// info.channels === 1
// input_green.jpg contains the green channel of the input image
});
```
#### bandbool(operation)
Perform a bitwise boolean operation on all input image channels (bands) to produce a single channel output image.
`operation` is a string containing the name of the bitwise operator to be appled to image channels, which can be one of:
* `and` performs a bitwise and operation, like the c-operator `&`.
* `or` performs a bitwise or operation, like the c-operator `|`.
* `eor` performs a bitwise exclusive or operation, like the c-operator `^`.
```javascript
sharp('input.png')
.bandbool(sharp.bool.and)
.toFile('output.png')
```
In the above example if `input.png` is a 3 channel RGB image, `output.png` will be a 1 channel grayscale image where each pixel `P = R & G & B`.
For example, if `I(1,1) = [247, 170, 14] = [0b11110111, 0b10101010, 0b00001111]` then `O(1,1) = 0b11110111 & 0b10101010 & 0b00001111 = 0b00000010 = 2`.
#### boolean(image, operation)
Perform a bitwise boolean operation with `image`, where `image` is one of the following:
* Buffer containing PNG, WebP, GIF or SVG image data, or
* String containing the path to an image file
This operation creates an output image where each pixel is the result of the selected bitwise boolean `operation` between the corresponding pixels of the input images.
The boolean operation can be one of the following:
* `and` performs a bitwise and operation, like the c-operator `&`.
* `or` performs a bitwise or operation, like the c-operator `|`.
* `eor` performs a bitwise exclusive or operation, like the c-operator `^`.
### Output
#### toFile(path, [callback])
`path` is a String containing the path to write the image data to.
If an explicit output format is not selected, it will be inferred from the extension, with JPEG, PNG, WebP, TIFF and DZI supported.
If an explicit output format is not selected, it will be inferred from the extension, with JPEG, PNG, WebP, TIFF, DZI, and VIPS V format supported. Note that RAW format is only supported for buffer output.
`callback`, if present, is called with two arguments `(err, info)` where:
@@ -463,7 +554,7 @@ 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.
Write image data to a Buffer, the format of which will match the input image by default. JPEG, PNG, WebP, and RAW are supported.
`callback`, if present, gets three arguments `(err, buffer, info)` where:
@@ -536,7 +627,7 @@ The size, overlap, container and directory layout to use when generating square
* `container` is a String, with value `fs` or `zip`. The default value is `fs`.
* `layout` is a String, with value `dz`, `zoomify` or `google`. The default value is `dz`.
You can also use the file extension .zip or .szi to write to a ZIP container instead of the filesystem.
You can also use the file extension `.zip` or `.szi` to write to a compressed archive file format.
```javascript
sharp('input.tiff')
@@ -615,9 +706,6 @@ for example:
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 } } }
@@ -641,22 +729,7 @@ sharp.queue.on('change', function(queueLength) {
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' }
console.log(sharp.versions);
```
### Utilities
@@ -671,6 +744,8 @@ If `options` is provided, sets the limits of _libvips'_ operation cache.
`options` can also be a boolean, where `true` enables the default cache settings and `false` disables all caching.
Existing entries in the cache will be trimmed after any change in limits.
This method always returns cache statistics, useful for determining how much working memory is required for a particular task.
```javascript

View File

@@ -1,5 +1,93 @@
# Changelog
### v0.15 - "*outfit*"
Requires libvips v8.3.1
#### v0.15.1 - 12<sup>th</sup> July 2016
* Concat Stream-based input in single operation for ~+3% perf and less GC.
[#429](https://github.com/lovell/sharp/issues/429)
[@papandreou](https://github.com/papandreou)
* Add alpha channel, if required, before extend operation.
[#439](https://github.com/lovell/sharp/pull/439)
[@frulo](https://github.com/frulo)
* Allow overlay image to be repeated across entire image via tile option.
[#443](https://github.com/lovell/sharp/pull/443)
[@lemnisk8](https://github.com/lemnisk8)
* Add cutout option to overlayWith feature, applies only the alpha channel of the overlay image.
[#448](https://github.com/lovell/sharp/pull/448)
[@kleisauke](https://github.com/kleisauke)
* Ensure scaling factors are calculated independently to prevent rounding errors.
[#452](https://github.com/lovell/sharp/issues/452)
[@puzrin](https://github.com/puzrin)
* Add --sharp-cxx11 flag to compile with gcc's new C++11 ABI.
[#456](https://github.com/lovell/sharp/pull/456)
[@kapouer](https://github.com/kapouer)
* Add top/left offset support to overlayWith operation.
[#473](https://github.com/lovell/sharp/pull/473)
[@rnanwani](https://github.com/rnanwani)
* Add convolve operation for kernel-based convolution.
[#479](https://github.com/lovell/sharp/pull/479)
[@mhirsch](https://github.com/mhirsch)
* Add greyscale option to threshold operation for colourspace conversion control.
[#480](https://github.com/lovell/sharp/pull/480)
[@mhirsch](https://github.com/mhirsch)
* Ensure ICC profiles are licenced for distribution.
[#486](https://github.com/lovell/sharp/issues/486)
[@kapouer](https://github.com/kapouer)
* Allow images with an alpha channel to work with LAB-colourspace based sharpen.
[#490](https://github.com/lovell/sharp/issues/490)
[@jwagner](https://github.com/jwagner)
* Add trim operation to remove "boring" edges.
[#492](https://github.com/lovell/sharp/pull/492)
[@kleisauke](https://github.com/kleisauke)
* Add bandbool feature for channel-wise boolean operations.
[#496](https://github.com/lovell/sharp/pull/496)
[@mhirsch](https://github.com/mhirsch)
* Add extractChannel operation to extract a channel from an image.
[#497](https://github.com/lovell/sharp/pull/497)
[@mhirsch](https://github.com/mhirsch)
* Add ability to read and write native libvips .v files.
[#500](https://github.com/lovell/sharp/pull/500)
[@mhirsch](https://github.com/mhirsch)
* Add boolean feature for bitwise image operations.
[#501](https://github.com/lovell/sharp/pull/501)
[@mhirsch](https://github.com/mhirsch)
#### v0.15.0 - 21<sup>st</sup> May 2016
* Use libvips' new Lanczos 3 kernel as default for image reduction.
Deprecate interpolateWith method, now provided as a resize option.
[#310](https://github.com/lovell/sharp/issues/310)
[@jcupitt](https://github.com/jcupitt)
* Take advantage of libvips v8.3 features.
Add support for libvips' new GIF and SVG loaders.
Pre-built binaries now include giflib and librsvg, exclude *magick.
Use shrink-on-load for WebP input.
Break existing sharpen API to accept sigma and improve precision.
[#369](https://github.com/lovell/sharp/issues/369)
* Remove unnecessary (un)premultiply operations when not resizing/compositing.
[#413](https://github.com/lovell/sharp/issues/413)
[@jardakotesovec](https://github.com/jardakotesovec)
### v0.14 - "*needle*"
Requires libvips v8.2.3
@@ -62,6 +150,9 @@ Requires libvips v8.2.3
[#387](https://github.com/lovell/sharp/issues/387)
[@kleisauke](https://github.com/kleisauke)
* Remove deprecated style of calling extract API. Breaks calls using positional arguments.
[#276](https://github.com/lovell/sharp/issues/276)
### v0.13 - "*mind*"
Requires libvips v8.2.2

View File

@@ -1,14 +1,14 @@
# sharp
The typical use case for this high speed Node.js module
is to convert large images of many formats to
is to convert large images in common formats to
smaller, web-friendly JPEG, PNG and WebP images of varying dimensions.
Resizing an image is typically 4x faster than using the
Resizing an image is typically 4x-5x 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.
Lanczos resampling ensures quality is not sacrificed for speed.
As well as image resizing, operations such as
rotation, extraction, compositing and gamma correction are available.
@@ -23,8 +23,7 @@ to install the libvips dependency.
### Formats
This module supports reading JPEG, PNG, WebP, TIFF, OpenSlide,
GIF and most other libmagick-supported formats.
This module supports reading JPEG, PNG, WebP, TIFF, GIF and SVG images.
Output images can be in JPEG, PNG and WebP formats as well as uncompressed raw pixel data.
@@ -89,12 +88,21 @@ the help and code contributions of the following people:
* [Alaric Holloway](https://github.com/skedastik)
* [Bernhard K. Weisshuhn](https://github.com/bkw)
* [David A. Carley](https://github.com/dacarley)
* [John Tobin](https://github.com/jtobinisaniceguy)
* [Kenton Gray](https://github.com/kentongray)
* [Felix Bünemann](https://github.com/felixbuenemann)
* [Samy Al Zahrani](https://github.com/salzhrani)
* [Chintan Thakkar](https://github.com/lemnisk8)
* [F. Orlando Galashan](https://github.com/frulo)
* [Kleis Auke Wolthuizen](https://github.com/kleisauke)
* [Matt Hirsch](https://github.com/mhirsch)
* [Rahul Nanwani](https://github.com/rnanwani)
Thank you!
### Licence
Copyright 2013, 2014, 2015 Lovell Fuller and contributors.
Copyright 2013, 2014, 2015, 2016 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.

View File

@@ -6,7 +6,7 @@ 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
* C++11 compatible compiler such as gcc 4.8+, clang 3.0+ or MSVC 2013+
* [node-gyp](https://github.com/TooTallNate/node-gyp#installation)
### Linux
@@ -15,36 +15,46 @@ npm install 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/lib` during `npm install`.
This involves an automated HTTPS download of approximately 6MB.
This involves an automated HTTPS download of approximately 6.7MB.
Most recent Linux-based operating systems with glibc running on x64 and ARMv6+ CPUs should "just work", e.g.:
* Debian 7, 8
* Ubuntu 12.04, 14.04, 14.10, 15.04, 15.10
* Ubuntu 12.04, 14.04, 15.10, 16.04
* Centos 7
* Fedora 21, 22, 23
* Fedora 22, 23
* openSUSE 13.2
* Archlinux 2015.06.01
* Raspbian Jessie
* Amazon Linux 2015.03, 2015.09
To use your own version of libvips instead of the provided binaries, make sure it is
at least the version listed under `config.libvips` in the `package.json` file that it
can be located using `pkg-config`. If you are using non-stadard paths (anything other
than `/usr` or `/usr/local`), you might need to set `PKG_CONFIG_PATH` during `npm install`
and `LD_LIBRARY_PATH` at runtime.
at least the version listed under `config.libvips` in the `package.json` file,
that it can be located using `pkg-config --modversion vips-cpp`.
You can print the detected vips version using: `pkg-config --modversion vips-cpp`
There are [changes in the C++11 ABI](https://gcc.gnu.org/onlinedocs/libstdc++/manual/using_dual_abi.html)
when using v5.1+ of the `g++` compiler.
If you have installed `libvips-dev` via package manager on an OS such as Debian testing/unstable,
you can pass the required value of the `_GLIBCXX_USE_CXX11_ABI` macro using the `--sharp-cxx11` flag.
```sh
npm install --sharp-cxx11=1
```
If you are using non-stadard paths (anything other than `/usr` or `/usr/local`),
you might need to set `PKG_CONFIG_PATH` during `npm install`
and `LD_LIBRARY_PATH` at runtime.
This allows the use of newer versions of libvips with older versions of sharp.
For older Linux-based operating systems and 32-bit Intel CPUs,
For 32-bit Intel CPUs and older Linux-based operating systems such as Centos 6,
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
# WARNING: This script is deprecated. You probably don't need to run it. Please read above.
curl -s https://raw.githubusercontent.com/lovell/sharp/master/preinstall.sh | sudo bash -
```
@@ -52,12 +62,6 @@ For Linux-based operating systems such as Alpine that use musl libc,
the smaller stack size means libvips' cache should be disabled
via `sharp.cache(false)` to avoid a stack overflow.
Beware of Linux OS upgrades that introduce v5.1+ of the `g++` compiler due to
[changes](https://gcc.gnu.org/onlinedocs/libstdc++/manual/using_dual_abi.html)
in the C++11 ABI.
This module assumes the previous behaviour, which can be enforced by setting the
`_GLIBCXX_USE_CXX11_ABI=0` environment variable at libvips' compile time.
### Mac OS
[![OS X 10.9.5 Build Status](https://travis-ci.org/lovell/sharp.png?branch=master)](https://travis-ci.org/lovell/sharp)
@@ -69,10 +73,10 @@ This can be achieved via homebrew:
brew install homebrew/science/vips
```
For GIF input and WebP output suppport use:
For WebP suppport use:
```sh
brew install homebrew/science/vips --with-imagemagick --with-webp
brew install homebrew/science/vips --with-webp
```
A missing or incorrectly configured _Xcode Command Line Tools_ installation
@@ -93,7 +97,6 @@ libvips and its dependencies are fetched and stored within `node_modules\sharp`
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.
### FreeBSD
@@ -134,12 +137,11 @@ do this on a system similar to the [Lambda Execution Environment](http://docs.aw
small t2.micro instance using the AMI ID listed in the previous link, ssh into it as ec2-user
and follow the instructions below.
Install depencies:
Install dependencies:
```sh
sudo yum-config-manager --enable epel
sudo yum install -y nodejs gcc-c++
curl -s https://www.npmjs.com/install.sh | sudo sh
curl -s https://rpm.nodesource.com/setup_4.x | sudo bash -
sudo yum install -y gcc-c++ nodejs
```
Copy your code and package.json to the instance using `scp` and create a deployment package:
@@ -150,7 +152,7 @@ npm install
zip -ur9 ../sharp-lambda-example.zip index.js node_modules
```
You can now download your deployment ZIP using `scp` and upload it to Lambda.
You can now download your deployment ZIP using `scp` and upload it to Lambda. Be sure to set your Lambda runtime to Node.js 4.3.
**Performance Tip:** To get the best performance on Lambda choose the largest memory available because this also gives you the most cpu time (a 1536 MB function is 12x faster than a 128 MB function).
@@ -158,3 +160,82 @@ You can now download your deployment ZIP using `scp` and upload it to Lambda.
* [gulp-responsive](https://www.npmjs.com/package/gulp-responsive)
* [grunt-sharp](https://www.npmjs.com/package/grunt-sharp)
### Security
Many users of this module process untrusted, user-supplied images,
but there are aspects of security to consider when doing so.
It is possible to compile libvips with support for various third-party image loaders.
Each of these libraries has undergone differing levels of security testing.
Whilst tools such as [American Fuzzy Lop](http://lcamtuf.coredump.cx/afl/)
and [Valgrind](http://valgrind.org/) have been used to test
the most popular web-based formats, as well as libvips itself,
you are advised to perform your own testing and sandboxing.
ImageMagick in particular has a relatively large attack surface,
which can be partially mitigated with a
[policy.xml](http://www.imagemagick.org/script/resources.php)
configuration file to prevent the use of coders known to be vulnerable.
```xml
<policymap>
<policy domain="coder" rights="none" pattern="EPHEMERAL" />
<policy domain="coder" rights="none" pattern="URL" />
<policy domain="coder" rights="none" pattern="HTTPS" />
<policy domain="coder" rights="none" pattern="MVG" />
<policy domain="coder" rights="none" pattern="MSL" />
<policy domain="coder" rights="none" pattern="TEXT" />
<policy domain="coder" rights="none" pattern="SHOW" />
<policy domain="coder" rights="none" pattern="WIN" />
<policy domain="coder" rights="none" pattern="PLT" />
</policymap>
```
Set the `MAGICK_CONFIGURE_PATH` environment variable
to the directory containing the `policy.xml` file.
### Licences
If a global installation of libvips that meets the
minimum version requirement cannot be found,
this module will download a pre-compiled bundle of libvips
and its dependencies on Linux and Windows machines.
Should you need to manually download and inspect these files,
you can do so via https://dl.bintray.com/lovell/sharp/
This module is licensed under the terms of the
[Apache 2.0 Licence](https://github.com/lovell/sharp/blob/master/LICENSE).
The libraries downloaded and used by this module
are done so under the terms of the following licences,
all of which are compatible with the Apache 2.0 Licence.
Use of libraries under the terms of the LGPLv3 is via the
"any later version" clause of the LGPLv2 or LGPLv2.1.
| Library | Used under the terms of |
|---------------|----------------------------------------------------------------------------------------------------------|
| cairo | Mozilla Public License 2.0 |
| fontconfig | [fontconfig Licence](https://cgit.freedesktop.org/fontconfig/tree/COPYING) (BSD-like) |
| freetype | [freetype Licence](http://git.savannah.gnu.org/cgit/freetype/freetype2.git/tree/docs/FTL.TXT) (BSD-like) |
| giflib | MIT Licence |
| glib | LGPLv3 |
| harfbuzz | MIT Licence |
| lcms | MIT Licence |
| libcroco | LGPLv3 |
| libexif | LGPLv3 |
| libffi | MIT Licence |
| libgsf | LGPLv3 |
| libjpeg-turbo | [zlib License, IJG License](https://github.com/libjpeg-turbo/libjpeg-turbo/blob/master/LICENSE.md) |
| libpng | [libpng License](http://www.libpng.org/pub/png/src/libpng-LICENSE.txt) |
| librsvg | LGPLv3 |
| libtiff | [libtiff License](http://www.libtiff.org/misc.html) (BSD-like) |
| libvips | LGPLv3 |
| libwebp | New BSD License |
| libxml2 | MIT Licence |
| pango | LGPLv3 |
| pixman | MIT Licence |
| zlib | [zlib Licence](https://github.com/madler/zlib/blob/master/zlib.h) |

View File

@@ -2,42 +2,45 @@
### 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.5.0
* AWS EC2 [c4.xlarge](http://aws.amazon.com/ec2/instance-types/#c4) (4x E5-2666 v3 @ 2.90GHz)
* Amazon Linux AMI 2016.03.1 (HVM), SSD Volume Type
* Node.js v6.2.0
### The contenders
* [jimp](https://www.npmjs.com/package/jimp) v0.2.20 - 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) @47c7329 - Wrapper around libmagick++, supports Buffers only.
* [jimp](https://www.npmjs.com/package/jimp) v0.2.24 - Image processing in pure JavaScript. Bilinear interpolation only.
* [lwip](https://www.npmjs.com/package/lwip) v0.0.9 - Wrapper around CImg, compiles dependencies from source.
* [imagemagick-native](https://www.npmjs.com/package/imagemagick-native) v1.9.2 - 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.13.0 / libvips v8.2.2 - Caching within libvips disabled to ensure a fair comparison.
* [gm](https://www.npmjs.com/package/gm) v1.22.0 - Fully featured wrapper around GraphicsMagick's `gm` command line utility.
* sharp v0.15.0 / libvips v8.3.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.
Decompress a 2725x2225 JPEG image,
resize to 720x480 using Lanczos 3 resampling (where available),
then compress to JPEG.
### Results
| Module | Input | Output | Ops/sec | Speed-up |
| :----------------- | :----- | :----- | ------: | -------: |
| jimp (bilinear) | file | file | 1.04 | 1.0 |
| jimp (bilinear) | buffer | buffer | 1.07 | 1.0 |
| lwip | file | file | 1.13 | 1.1 |
| lwip | buffer | buffer | 1.13 | 1.1 |
| imagemagick-native | buffer | buffer | 1.65 | 1.6 |
| imagemagick | file | file | 5.02 | 4.8 |
| gm | buffer | buffer | 5.36 | 5.2 |
| gm | file | file | 5.39 | 5.2 |
| sharp | stream | stream | 22.00 | 21.2 |
| sharp | file | file | 22.87 | 22.0 |
| sharp | file | buffer | 23.03 | 22.1 |
| sharp | buffer | file | 23.10 | 22.2 |
| sharp | buffer | buffer | 23.21 | 22.3 |
| jimp (bilinear) | file | file | 0.94 | 1.0 |
| jimp (bilinear) | buffer | buffer | 0.98 | 1.0 |
| lwip | file | file | 1.14 | 1.2 |
| lwip | buffer | buffer | 1.14 | 1.2 |
| imagemagick-native | buffer | buffer | 1.66 | 1.8 |
| imagemagick | file | file | 5.08 | 5.4 |
| gm | buffer | buffer | 5.43 | 5.7 |
| gm | file | file | 5.46 | 5.8 |
| sharp | stream | stream | 26.52 | 28.2 |
| sharp | file | file | 28.16 | 30.0 |
| sharp | file | buffer | 28.27 | 30.1 |
| sharp | buffer | file | 28.42 | 30.2 |
| sharp | buffer | buffer | 28.42 | 30.2 |
Greater performance can be expected with caching enabled (default) and using 8+ core machines.
Greater libvips performance can be expected with caching enabled (default)
and using 8+ core machines, especially those with larger L1/L2 CPU caches.
The I/O limits of the relevant (de)compression library will generally determine maximum throughput.

Binary file not shown.

BIN
icc/cmyk.icm Normal file

Binary file not shown.

BIN
icc/sRGB.icc Normal file

Binary file not shown.

388
index.js
View File

@@ -42,7 +42,7 @@ var Sharp = function(input, options) {
stream.Duplex.call(this);
this.options = {
// input options
bufferIn: null,
bufferIn: [],
streamIn: false,
sequentialRead: false,
limitInputPixels: maximum.pixels,
@@ -74,23 +74,34 @@ var Sharp = function(input, options) {
extendLeft: 0,
extendRight: 0,
withoutEnlargement: false,
kernel: 'lanczos3',
interpolator: 'bicubic',
// operations
background: [0, 0, 0, 255],
flatten: false,
negate: false,
blurSigma: 0,
sharpenRadius: 0,
sharpenSigma: 0,
sharpenFlat: 1,
sharpenJagged: 2,
threshold: 0,
thresholdGrayscale: true,
trimTolerance: 0,
gamma: 0,
greyscale: false,
normalize: 0,
bandBoolOp: null,
booleanOp: null,
booleanBufferIn: null,
booleanFileIn: '',
// overlay
overlayFileIn: '',
overlayBufferIn: null,
overlayGravity: 0,
overlayXOffset : -1,
overlayYOffset : -1,
overlayTile: false,
overlayCutout: false,
// output options
formatOut: 'input',
fileOut: '',
@@ -107,6 +118,7 @@ var Sharp = function(input, options) {
withMetadataOrientation: -1,
tileSize: 256,
tileOverlap: 0,
extractChannel: -1,
// Function to notify of queue length changes
queueListener: function(queueLength) {
module.exports.queue.emit('change', queueLength);
@@ -154,14 +166,20 @@ var isDefined = function(val) {
var isObject = function(val) {
return typeof val === 'object';
};
var isBoolean = function(val) {
return typeof val === 'boolean';
};
var isBuffer = function(val) {
return typeof val === 'object' && val instanceof Buffer;
};
var isString = function(val) {
return typeof val === 'string' && val.length > 0;
};
var isNumber = function(val) {
return typeof val === 'number' && !Number.isNaN(val);
};
var isInteger = function(val) {
return typeof val === 'number' && !Number.isNaN(val) && val % 1 === 0;
return isNumber(val) && val % 1 === 0;
};
var inRange = function(val, min, max) {
return val >= min && val <= max;
@@ -210,18 +228,8 @@ Sharp.prototype._inputOptions = function(options) {
Sharp.prototype._write = function(chunk, encoding, callback) {
/*jslint unused: false */
if (this.options.streamIn) {
if (typeof chunk === 'object' && chunk instanceof Buffer) {
if (this.options.bufferIn instanceof Buffer) {
// Append to existing Buffer
this.options.bufferIn = Buffer.concat(
[this.options.bufferIn, chunk],
this.options.bufferIn.length + chunk.length
);
} else {
// Create new Buffer
this.options.bufferIn = new Buffer(chunk.length);
chunk.copy(this.options.bufferIn);
}
if (isBuffer(chunk)) {
this.options.bufferIn.push(chunk);
callback();
} else {
callback(new Error('Non-Buffer data on Writable Stream'));
@@ -231,6 +239,15 @@ Sharp.prototype._write = function(chunk, encoding, callback) {
}
};
/*
Flattens the array of chunks in bufferIn
*/
Sharp.prototype._flattenBufferIn = function() {
if (Array.isArray(this.options.bufferIn)) {
this.options.bufferIn = Buffer.concat(this.options.bufferIn);
}
};
// Weighting to apply to image crop
module.exports.gravity = {
center: 0,
@@ -290,6 +307,21 @@ Sharp.prototype.extract = function(options) {
return this;
};
Sharp.prototype.extractChannel = function(channel) {
if (channel === 'red')
channel = 0;
else if (channel === 'green')
channel = 1;
else if (channel === 'blue')
channel = 2;
if(isInteger(channel) && inRange(channel,0,4)) {
this.options.extractChannel = channel;
} else {
throw new Error('Cannot extract invalid channel ' + channel);
}
return this;
};
/*
Set the background colour for embed and flatten operations.
Delegates to the 'Color' module, which can throw an Error
@@ -327,12 +359,31 @@ Sharp.prototype.ignoreAspectRatio = function() {
};
Sharp.prototype.flatten = function(flatten) {
this.options.flatten = (typeof flatten === 'boolean') ? flatten : true;
this.options.flatten = isBoolean(flatten) ? flatten : true;
return this;
};
Sharp.prototype.negate = function(negate) {
this.options.negate = (typeof negate === 'boolean') ? negate : true;
this.options.negate = isBoolean(negate) ? negate : true;
return this;
};
/*
Bitwise boolean operations between images
*/
Sharp.prototype.boolean = function(operand, operator) {
if (isString(operand)) {
this.options.booleanFileIn = operand;
} else if (isBuffer(operand)) {
this.options.booleanBufferIn = operand;
} else {
throw new Error('Unsupported boolean operand ' + typeof operand);
}
if (isString(operator) && contains(operator, ['and', 'or', 'eor'])) {
this.options.booleanOp = operator;
} else {
throw new Error('Invalid boolean operation ' + operator);
}
return this;
};
@@ -348,12 +399,39 @@ Sharp.prototype.overlayWith = function(overlay, options) {
throw new Error('Unsupported overlay ' + typeof overlay);
}
if (isObject(options)) {
if (isInteger(options.gravity) && inRange(options.gravity, 0, 8)) {
this.options.overlayGravity = options.gravity;
} else if (isString(options.gravity) && isInteger(module.exports.gravity[options.gravity])) {
this.options.overlayGravity = module.exports.gravity[options.gravity];
} else if (isDefined(options.gravity)) {
throw new Error('Unsupported overlay gravity ' + options.gravity);
if (isDefined(options.tile)) {
if (isBoolean(options.tile)) {
this.options.overlayTile = options.tile;
} else {
throw new Error('Invalid overlay tile ' + options.tile);
}
}
if (isDefined(options.cutout)) {
if (isBoolean(options.cutout)) {
this.options.overlayCutout = options.cutout;
} else {
throw new Error('Invalid overlay cutout ' + options.cutout);
}
}
if (isDefined(options.left) || isDefined(options.top)) {
if (
isInteger(options.left) && inRange(options.left, 0, maximum.width) &&
isInteger(options.top) && inRange(options.top, 0, maximum.height)
) {
this.options.overlayXOffset = options.left;
this.options.overlayYOffset = options.top;
} else {
throw new Error('Invalid overlay left ' + options.left + ' and/or top ' + options.top);
}
}
if (isDefined(options.gravity)) {
if(isInteger(options.gravity) && inRange(options.gravity, 0, 8)) {
this.options.overlayGravity = options.gravity;
} else if (isString(options.gravity) && isInteger(module.exports.gravity[options.gravity])) {
this.options.overlayGravity = module.exports.gravity[options.gravity];
} else {
throw new Error('Unsupported overlay gravity ' + options.gravity);
}
}
}
return this;
@@ -364,9 +442,9 @@ Sharp.prototype.overlayWith = function(overlay, options) {
Auto-rotation based on the EXIF Orientation tag is represented by an angle of -1
*/
Sharp.prototype.rotate = function(angle) {
if (typeof angle === 'undefined') {
if (!isDefined(angle)) {
this.options.angle = -1;
} else if (!Number.isNaN(angle) && [0, 90, 180, 270].indexOf(angle) !== -1) {
} else if (isInteger(angle) && contains(angle, [0, 90, 180, 270])) {
this.options.angle = angle;
} else {
throw new Error('Unsupported angle (0, 90, 180, 270) ' + angle);
@@ -378,7 +456,7 @@ Sharp.prototype.rotate = function(angle) {
Flip the image vertically, about the Y axis
*/
Sharp.prototype.flip = function(flip) {
this.options.flip = (typeof flip === 'boolean') ? flip : true;
this.options.flip = isBoolean(flip) ? flip : true;
return this;
};
@@ -386,7 +464,7 @@ Sharp.prototype.flip = function(flip) {
Flop the image horizontally, about the X axis
*/
Sharp.prototype.flop = function(flop) {
this.options.flop = (typeof flop === 'boolean') ? flop : true;
this.options.flop = isBoolean(flop) ? flop : true;
return this;
};
@@ -396,7 +474,7 @@ Sharp.prototype.flop = function(flop) {
"change the dimensions of the image only if its width or height exceeds the geometry specification"
*/
Sharp.prototype.withoutEnlargement = function(withoutEnlargement) {
this.options.withoutEnlargement = (typeof withoutEnlargement === 'boolean') ? withoutEnlargement : true;
this.options.withoutEnlargement = isBoolean(withoutEnlargement) ? withoutEnlargement : true;
return this;
};
@@ -406,97 +484,117 @@ Sharp.prototype.withoutEnlargement = function(withoutEnlargement) {
Call with a sigma to use a slower, more accurate Gaussian blur.
*/
Sharp.prototype.blur = function(sigma) {
if (typeof sigma === 'undefined') {
if (!isDefined(sigma)) {
// No arguments: default to mild blur
this.options.blurSigma = -1;
} else if (typeof sigma === 'boolean') {
} else if (isBoolean(sigma)) {
// Boolean argument: apply mild blur?
this.options.blurSigma = sigma ? -1 : 0;
} else if (typeof sigma === 'number' && !Number.isNaN(sigma) && sigma >= 0.3 && sigma <= 1000) {
} else if (isNumber(sigma) && inRange(sigma, 0.3, 1000)) {
// Numeric argument: specific sigma
this.options.blurSigma = sigma;
} else {
throw new Error('Invalid blur sigma (0.3 to 1000.0) ' + sigma);
throw new Error('Invalid blur sigma (0.3 - 1000.0) ' + sigma);
}
return this;
};
/*
Convolve the image with a kernel.
*/
Sharp.prototype.convolve = function(kernel) {
if (!isDefined(kernel) || !isDefined(kernel.kernel) ||
!isDefined(kernel.width) || !isDefined(kernel.height) ||
!inRange(kernel.width,3,1001) || !inRange(kernel.height,3,1001) ||
kernel.height * kernel.width != kernel.kernel.length
) {
// must pass in a kernel
throw new Error('Invalid convolution kernel');
}
if(!isDefined(kernel.scale)) {
var sum = 0;
kernel.kernel.forEach(function(e) {
sum += e;
});
kernel.scale = sum;
}
if(!isDefined(kernel.offset)) {
kernel.offset = 0;
}
this.options.convKernel = kernel;
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
sigma - sigma of mask
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') {
Sharp.prototype.sharpen = function(sigma, flat, jagged) {
if (!isDefined(sigma)) {
// No arguments: default to mild sharpen
this.options.sharpenRadius = -1;
} else if (typeof radius === 'boolean') {
this.options.sharpenSigma = -1;
} else if (isBoolean(sigma)) {
// 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;
this.options.sharpenSigma = sigma ? -1 : 0;
} else if (isNumber(sigma) && inRange(sigma, 0.01, 10000)) {
// Numeric argument: specific sigma
this.options.sharpenSigma = sigma;
// Control over flat areas
if (typeof flat !== 'undefined' && flat !== null) {
if (typeof flat === 'number' && !Number.isNaN(flat) && flat >= 0) {
if (isDefined(flat)) {
if (isNumber(flat) && inRange(flat, 0, 10000)) {
this.options.sharpenFlat = flat;
} else {
throw new Error('Invalid sharpen level for flat areas ' + flat + ' (expected >= 0)');
throw new Error('Invalid sharpen level for flat areas (0 - 10000) ' + flat);
}
}
// Control over jagged areas
if (typeof jagged !== 'undefined' && jagged !== null) {
if (typeof jagged === 'number' && !Number.isNaN(jagged) && jagged >= 0) {
if (isDefined(jagged)) {
if (isNumber(jagged) && inRange(jagged, 0, 10000)) {
this.options.sharpenJagged = jagged;
} else {
throw new Error('Invalid sharpen level for jagged areas ' + jagged + ' (expected >= 0)');
throw new Error('Invalid sharpen level for jagged areas (0 - 10000) ' + jagged);
}
}
} else {
throw new Error('Invalid sharpen radius ' + radius + ' (expected integer >= 1)');
throw new Error('Invalid sharpen sigma (0.01 - 10000) ' + sigma);
}
return this;
};
Sharp.prototype.threshold = function(threshold) {
if (typeof threshold === 'undefined') {
Sharp.prototype.threshold = function(threshold, options) {
if (!isDefined(threshold)) {
this.options.threshold = 128;
} else if (typeof threshold === 'boolean') {
} else if (isBoolean(threshold)) {
this.options.threshold = threshold ? 128 : 0;
} else if (typeof threshold === 'number' && !Number.isNaN(threshold) && (threshold % 1 === 0) && threshold >= 0 && threshold <= 255) {
} else if (isInteger(threshold) && inRange(threshold, 0, 255)) {
this.options.threshold = threshold;
} else {
throw new Error('Invalid threshold (0 to 255) ' + threshold);
}
if (!isObject(options) || options.greyscale === true || options.grayscale === true) {
this.options.thresholdGrayscale = true;
} else {
this.options.thresholdGrayscale = false;
}
return this;
};
/*
Set the interpolator to use for the affine transformation
*/
module.exports.interpolator = {
nearest: 'nearest',
bilinear: 'bilinear',
bicubic: 'bicubic',
nohalo: 'nohalo',
locallyBoundedBicubic: 'lbb',
vertexSplitQuadraticBasisSpline: 'vsqbs'
};
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;
Automatically remove "boring" image edges.
tolerance - if present, is a percentaged tolerance level between 0 and 100 to trim away similar color values
Defaulting to 10 when no tolerance is given.
*/
Sharp.prototype.trim = function(tolerance) {
if (!isDefined(tolerance)) {
this.options.trimTolerance = 10;
} else if (isInteger(tolerance) && inRange(tolerance, 1, 99)) {
this.options.trimTolerance = tolerance;
} else {
throw new Error('Invalid interpolator ' + interpolator);
throw new Error('Invalid trim tolerance (1 to 99) ' + tolerance);
}
return this;
};
@@ -506,10 +604,10 @@ Sharp.prototype.interpolateWith = function(interpolator) {
Improves brightness of resized image in non-linear colour spaces.
*/
Sharp.prototype.gamma = function(gamma) {
if (typeof gamma === 'undefined') {
if (!isDefined(gamma)) {
// Default gamma correction of 2.2 (sRGB)
this.options.gamma = 2.2;
} else if (!Number.isNaN(gamma) && gamma >= 1 && gamma <= 3) {
} else if (isNumber(gamma) && inRange(gamma, 1, 3)) {
this.options.gamma = gamma;
} else {
throw new Error('Invalid gamma correction (1.0 to 3.0) ' + gamma);
@@ -521,32 +619,44 @@ Sharp.prototype.gamma = function(gamma) {
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;
this.options.normalize = isBoolean(normalize) ? normalize : true;
return this;
};
Sharp.prototype.normalise = Sharp.prototype.normalize;
/*
Perform boolean/bitwise operation on image color channels - results in one channel image
*/
Sharp.prototype.bandbool = function(boolOp) {
if (isString(boolOp) && contains(boolOp, ['and', 'or', 'eor'])) {
this.options.bandBoolOp = boolOp;
} else {
throw new Error('Invalid bandbool operation ' + boolOp);
}
return this;
};
/*
Convert to greyscale
*/
Sharp.prototype.greyscale = function(greyscale) {
this.options.greyscale = (typeof greyscale === 'boolean') ? greyscale : true;
this.options.greyscale = isBoolean(greyscale) ? greyscale : true;
return this;
};
Sharp.prototype.grayscale = Sharp.prototype.greyscale;
Sharp.prototype.progressive = function(progressive) {
this.options.progressive = (typeof progressive === 'boolean') ? progressive : true;
this.options.progressive = isBoolean(progressive) ? progressive : true;
return this;
};
Sharp.prototype.sequentialRead = function(sequentialRead) {
this.options.sequentialRead = (typeof sequentialRead === 'boolean') ? sequentialRead : true;
this.options.sequentialRead = isBoolean(sequentialRead) ? sequentialRead : true;
return this;
};
Sharp.prototype.quality = function(quality) {
if (!Number.isNaN(quality) && quality >= 1 && quality <= 100 && quality % 1 === 0) {
if (isInteger(quality) && inRange(quality, 1, 100)) {
this.options.quality = quality;
} else {
throw new Error('Invalid quality (1 to 100) ' + quality);
@@ -558,7 +668,7 @@ Sharp.prototype.quality = function(quality) {
zlib compression level for PNG output
*/
Sharp.prototype.compressionLevel = function(compressionLevel) {
if (!Number.isNaN(compressionLevel) && compressionLevel >= 0 && compressionLevel <= 9) {
if (isInteger(compressionLevel) && inRange(compressionLevel, 0, 9)) {
this.options.compressionLevel = compressionLevel;
} else {
throw new Error('Invalid compressionLevel (0 to 9) ' + compressionLevel);
@@ -570,7 +680,7 @@ Sharp.prototype.compressionLevel = function(compressionLevel) {
Disable the use of adaptive row filtering for PNG output
*/
Sharp.prototype.withoutAdaptiveFiltering = function(withoutAdaptiveFiltering) {
this.options.withoutAdaptiveFiltering = (typeof withoutAdaptiveFiltering === 'boolean') ? withoutAdaptiveFiltering : true;
this.options.withoutAdaptiveFiltering = isBoolean(withoutAdaptiveFiltering) ? withoutAdaptiveFiltering : true;
return this;
};
@@ -578,7 +688,7 @@ Sharp.prototype.withoutAdaptiveFiltering = function(withoutAdaptiveFiltering) {
Disable the use of chroma subsampling for JPEG output
*/
Sharp.prototype.withoutChromaSubsampling = function(withoutChromaSubsampling) {
this.options.withoutChromaSubsampling = (typeof withoutChromaSubsampling === 'boolean') ? withoutChromaSubsampling : true;
this.options.withoutChromaSubsampling = isBoolean(withoutChromaSubsampling) ? withoutChromaSubsampling : true;
return this;
};
@@ -586,7 +696,7 @@ Sharp.prototype.withoutChromaSubsampling = function(withoutChromaSubsampling) {
Apply trellis quantisation to JPEG output - requires mozjpeg 3.0+
*/
Sharp.prototype.trellisQuantisation = function(trellisQuantisation) {
this.options.trellisQuantisation = (typeof trellisQuantisation === 'boolean') ? trellisQuantisation : true;
this.options.trellisQuantisation = isBoolean(trellisQuantisation) ? trellisQuantisation : true;
return this;
};
Sharp.prototype.trellisQuantization = Sharp.prototype.trellisQuantisation;
@@ -595,7 +705,7 @@ 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;
this.options.overshootDeringing = isBoolean(overshootDeringing) ? overshootDeringing : true;
return this;
};
@@ -603,7 +713,7 @@ Sharp.prototype.overshootDeringing = function(overshootDeringing) {
Optimise scans in progressive JPEG output - requires mozjpeg 3.0+
*/
Sharp.prototype.optimiseScans = function(optimiseScans) {
this.options.optimiseScans = (typeof optimiseScans === 'boolean') ? optimiseScans : true;
this.options.optimiseScans = isBoolean(optimiseScans) ? optimiseScans : true;
if (this.options.optimiseScans) {
this.progressive();
}
@@ -617,16 +727,10 @@ Sharp.prototype.optimizeScans = Sharp.prototype.optimiseScans;
orientation: numeric value for EXIF Orientation tag
*/
Sharp.prototype.withMetadata = function(withMetadata) {
this.options.withMetadata = (typeof withMetadata === 'boolean') ? withMetadata : true;
if (typeof withMetadata === 'object') {
if ('orientation' in withMetadata) {
if (
typeof withMetadata.orientation === 'number' &&
!Number.isNaN(withMetadata.orientation) &&
withMetadata.orientation % 1 === 0 &&
withMetadata.orientation >= 1 &&
withMetadata.orientation <= 8
) {
this.options.withMetadata = isBoolean(withMetadata) ? withMetadata : true;
if (isObject(withMetadata)) {
if (isDefined(withMetadata.orientation)) {
if (isInteger(withMetadata.orientation) && inRange(withMetadata.orientation, 1, 8)) {
this.options.withMetadataOrientation = withMetadata.orientation;
} else {
throw new Error('Invalid orientation (1 to 8) ' + withMetadata.orientation);
@@ -706,27 +810,81 @@ Sharp.prototype.extend = function(extend) {
return this;
};
Sharp.prototype.resize = function(width, height) {
if (!width) {
this.options.width = -1;
} else {
if (typeof width === 'number' && !Number.isNaN(width) && width % 1 === 0 && width > 0 && width <= maximum.width) {
// Kernels for reduction
module.exports.kernel = {
cubic: 'cubic',
lanczos2: 'lanczos2',
lanczos3: 'lanczos3'
};
// Interpolators for enlargement
module.exports.interpolator = {
nearest: 'nearest',
bilinear: 'bilinear',
bicubic: 'bicubic',
nohalo: 'nohalo',
lbb: 'lbb',
locallyBoundedBicubic: 'lbb',
vsqbs: 'vsqbs',
vertexSplitQuadraticBasisSpline: 'vsqbs'
};
// Boolean operations for bandbool
module.exports.bool = {
and: 'and',
or: 'or',
eor: 'eor'
};
/*
Resize image to width x height pixels
options.kernel is the kernel to use for reductions, default 'lanczos3'
options.interpolator is the interpolator to use for enlargements, default 'bicubic'
*/
Sharp.prototype.resize = function(width, height, options) {
if (isDefined(width)) {
if (isInteger(width) && inRange(width, 1, maximum.width)) {
this.options.width = width;
} else {
throw new Error('Invalid width (1 to ' + maximum.width + ') ' + width);
}
}
if (!height) {
this.options.height = -1;
} else {
if (typeof height === 'number' && !Number.isNaN(height) && height % 1 === 0 && height > 0 && height <= maximum.height) {
this.options.width = -1;
}
if (isDefined(height)) {
if (isInteger(height) && inRange(height, 1, maximum.height)) {
this.options.height = height;
} else {
throw new Error('Invalid height (1 to ' + maximum.height + ') ' + height);
}
} else {
this.options.height = -1;
}
if (isObject(options)) {
// Kernel
if (isDefined(options.kernel)) {
if (isString(module.exports.kernel[options.kernel])) {
this.options.kernel = module.exports.kernel[options.kernel];
} else {
throw new Error('Invalid kernel ' + options.kernel);
}
}
// Interpolator
if (isDefined(options.interpolator)) {
if (isString(module.exports.interpolator[options.interpolator])) {
this.options.interpolator = module.exports.interpolator[options.interpolator];
} else {
throw new Error('Invalid interpolator ' + options.interpolator);
}
}
}
return this;
};
Sharp.prototype.interpolateWith = util.deprecate(function(interpolator) {
return this.resize(
this.options.width > 0 ? this.options.width : null,
this.options.height > 0 ? this.options.height : null,
{ interpolator: interpolator }
);
}, 'interpolateWith: Please use resize(w, h, { interpolator: ... }) instead');
/*
Limit the total number of pixels for input images
@@ -819,12 +977,11 @@ Sharp.prototype.raw = function() {
@param format is either the id as a String or an Object with an 'id' attribute
*/
Sharp.prototype.toFormat = function(formatOut) {
if (isObject(formatOut) && isDefined(formatOut.id)) {
formatOut = formatOut.id;
}
if (
isDefined(formatOut) &&
['jpeg', 'png', 'webp', 'raw', 'tiff', 'dz', 'input'].indexOf(formatOut) !== -1
if (isObject(formatOut) && isString(formatOut.id)) {
this.options.formatOut = formatOut.id;
} else if (
isString(formatOut) &&
contains(formatOut, ['jpeg', 'png', 'webp', 'raw', 'tiff', 'dz', 'input'])
) {
this.options.formatOut = formatOut;
} else {
@@ -854,6 +1011,7 @@ Sharp.prototype._pipeline = function(callback) {
if (this.options.streamIn) {
// output=file/buffer, input=stream
this.on('finish', function() {
that._flattenBufferIn();
sharp.pipeline(that.options, callback);
});
} else {
@@ -866,6 +1024,7 @@ Sharp.prototype._pipeline = function(callback) {
if (this.options.streamIn) {
// output=stream, input=stream
this.on('finish', function() {
that._flattenBufferIn();
sharp.pipeline(that.options, function(err, data, info) {
if (err) {
that.emit('error', err);
@@ -895,6 +1054,7 @@ Sharp.prototype._pipeline = function(callback) {
// output=promise, input=stream
return new BluebirdPromise(function(resolve, reject) {
that.on('finish', function() {
that._flattenBufferIn();
sharp.pipeline(that.options, function(err, data) {
if (err) {
reject(err);
@@ -928,6 +1088,7 @@ Sharp.prototype.metadata = function(callback) {
if (typeof callback === 'function') {
if (this.options.streamIn) {
this.on('finish', function() {
that._flattenBufferIn();
sharp.metadata(that.options, callback);
});
} else {
@@ -938,6 +1099,7 @@ Sharp.prototype.metadata = function(callback) {
if (this.options.streamIn) {
return new BluebirdPromise(function(resolve, reject) {
that.on('finish', function() {
that._flattenBufferIn();
sharp.metadata(that.options, function(err, metadata) {
if (err) {
reject(err);
@@ -966,13 +1128,15 @@ Sharp.prototype.metadata = function(callback) {
Cloned instances share the same input.
*/
Sharp.prototype.clone = function() {
var that = this;
// Clone existing options
var clone = new Sharp();
util._extend(clone.options, this.options);
// Pass 'finish' event to clone for Stream-based input
this.on('finish', function() {
// Clone inherits input data
clone.options.bufferIn = this.options.bufferIn;
that._flattenBufferIn();
clone.options.bufferIn = that.options.bufferIn;
clone.emit('finish');
});
return clone;

View File

@@ -1,7 +1,7 @@
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.
site_description: High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP and TIFF images
copyright: <a href="https://dimens.io/">dimens.io</a>
google_analytics: ['UA-13034748-12', 'sharp.dimens.io']
theme: readthedocs

View File

@@ -1,6 +1,6 @@
{
"name": "sharp",
"version": "0.14.1",
"version": "0.15.1",
"author": "Lovell Fuller <npm@lovell.info>",
"contributors": [
"Pierre Inglebert <pierre.inglebert@gmail.com>",
@@ -22,9 +22,13 @@
"John Tobin <john@limelightmobileinc.com>",
"Kenton Gray <kentongray@gmail.com>",
"Felix Bünemann <Felix.Buenemann@gmail.com>",
"Samy Al Zahrani <samyalzahrany@gmail.com>"
"Samy Al Zahrani <samyalzahrany@gmail.com>",
"Chintan Thakkar <lemnisk8@gmail.com>",
"F. Orlando Galashan <frulo@gmx.de>",
"Kleis Auke Wolthuizen <info@kleisauke.nl>",
"Matt Hirsch <mhirsch@media.mit.edu>"
],
"description": "High performance Node.js module to resize JPEG, PNG, WebP and TIFF images using the libvips library",
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP and TIFF images",
"scripts": {
"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",
@@ -43,7 +47,10 @@
"png",
"webp",
"tiff",
"gif",
"svg",
"dzi",
"image",
"resize",
"thumbnail",
"crop",
@@ -51,11 +58,11 @@
"vips"
],
"dependencies": {
"bluebird": "^3.3.5",
"color": "^0.11.1",
"nan": "^2.2.1",
"semver": "^5.1.0",
"request": "^2.71.0",
"bluebird": "^3.4.1",
"color": "^0.11.3",
"nan": "^2.4.0",
"semver": "^5.2.0",
"request": "^2.73.0",
"tar": "^2.2.1"
},
"devDependencies": {
@@ -64,16 +71,16 @@
"coveralls": "^2.11.9",
"exif-reader": "^1.0.0",
"icc": "^0.0.2",
"istanbul": "^0.4.3",
"mocha": "^2.4.5",
"istanbul": "^0.4.4",
"mocha": "^2.5.3",
"mocha-jshint": "^2.3.1",
"node-cpplint": "^0.4.0",
"rimraf": "^2.5.2",
"rimraf": "^2.5.3",
"unzip": "^0.1.11"
},
"license": "Apache-2.0",
"config": {
"libvips": "8.2.3"
"libvips": "8.3.1"
},
"engines": {
"node": ">=0.10"

View File

@@ -19,18 +19,30 @@ export CXXFLAGS="-O3"
# Dependency version numbers
VERSION_ZLIB=1.2.8
VERSION_FFI=3.2.1
VERSION_GLIB=2.47.6
VERSION_GLIB=2.48.0
VERSION_XML2=2.9.3
VERSION_GSF=1.14.34
VERSION_GSF=1.14.36
VERSION_EXIF=0.6.21
VERSION_LCMS2=2.7
VERSION_GM=1.3.23
VERSION_JPEG=1.4.2
VERSION_JPEG=1.4.90
VERSION_PNG16=1.6.21
VERSION_WEBP=0.5.0
VERSION_TIFF=4.0.6
VERSION_ORC=0.4.25
VERSION_VIPS=8.2.3
VERSION_GDKPIXBUF=2.34.0
VERSION_FREETYPE=2.6.3
VERSION_FONTCONFIG=2.11.95
VERSION_HARFBUZZ=1.2.6
VERSION_PIXMAN=0.34.0
VERSION_CAIRO=1.14.6
VERSION_PANGO=1.40.1
VERSION_CROCO=0.6.11
VERSION_SVG=2.40.15
VERSION_GIF=5.1.4
VERSION_VIPS=8.3.1
# Least out-of-sync Sourceforge mirror
SOURCEFORGE_MIRROR=netix
mkdir ${DEPS}/zlib
curl -Ls http://zlib.net/zlib-${VERSION_ZLIB}.tar.xz | tar xJC ${DEPS}/zlib --strip-components=1
@@ -44,7 +56,7 @@ cd ${DEPS}/ffi
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --disable-builddir && make install-strip
mkdir ${DEPS}/glib
curl -Ls https://download.gnome.org/sources/glib/2.47/glib-${VERSION_GLIB}.tar.xz | tar xJC ${DEPS}/glib --strip-components=1
curl -Ls https://download.gnome.org/sources/glib/2.48/glib-${VERSION_GLIB}.tar.xz | tar xJC ${DEPS}/glib --strip-components=1
cd ${DEPS}/glib
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --with-pcre=internal && make install-strip
@@ -59,27 +71,22 @@ cd ${DEPS}/gsf
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
mkdir ${DEPS}/exif
curl -Ls http://heanet.dl.sourceforge.net/project/libexif/libexif/${VERSION_EXIF}/libexif-${VERSION_EXIF}.tar.bz2 | tar xjC ${DEPS}/exif --strip-components=1
curl -Ls http://${SOURCEFORGE_MIRROR}.dl.sourceforge.net/project/libexif/libexif/${VERSION_EXIF}/libexif-${VERSION_EXIF}.tar.bz2 | tar xjC ${DEPS}/exif --strip-components=1
cd ${DEPS}/exif
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
mkdir ${DEPS}/lcms2
curl -Ls http://heanet.dl.sourceforge.net/project/lcms/lcms/${VERSION_LCMS2}/lcms2-${VERSION_LCMS2}.tar.gz | tar xzC ${DEPS}/lcms2 --strip-components=1
curl -Ls http://${SOURCEFORGE_MIRROR}.dl.sourceforge.net/project/lcms/lcms/${VERSION_LCMS2}/lcms2-${VERSION_LCMS2}.tar.gz | tar xzC ${DEPS}/lcms2 --strip-components=1
cd ${DEPS}/lcms2
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
mkdir ${DEPS}/gm
curl -Ls http://heanet.dl.sourceforge.net/project/graphicsmagick/graphicsmagick/${VERSION_GM}/GraphicsMagick-${VERSION_GM}.tar.xz | tar xJC ${DEPS}/gm --strip-components=1
cd ${DEPS}/gm
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --without-magick-plus-plus && make install-strip
mkdir ${DEPS}/jpeg
curl -Ls http://heanet.dl.sourceforge.net/project/libjpeg-turbo/${VERSION_JPEG}/libjpeg-turbo-${VERSION_JPEG}.tar.gz | tar xzC ${DEPS}/jpeg --strip-components=1
curl -Ls https://github.com/libjpeg-turbo/libjpeg-turbo/archive/${VERSION_JPEG}.tar.gz | tar xzC ${DEPS}/jpeg --strip-components=1
cd ${DEPS}/jpeg
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --with-jpeg8 --without-turbojpeg && make install-strip
autoreconf -fiv && ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --with-jpeg8 --without-turbojpeg && make install-strip
mkdir ${DEPS}/png16
curl -Ls http://heanet.dl.sourceforge.net/project/libpng/libpng16/${VERSION_PNG16}/libpng-${VERSION_PNG16}.tar.xz | tar xJC ${DEPS}/png16 --strip-components=1
curl -Ls http://${SOURCEFORGE_MIRROR}.dl.sourceforge.net/project/libpng/libpng16/${VERSION_PNG16}/libpng-${VERSION_PNG16}.tar.xz | tar xJC ${DEPS}/png16 --strip-components=1
cd ${DEPS}/png16
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
@@ -99,11 +106,69 @@ curl -Ls http://gstreamer.freedesktop.org/data/src/orc/orc-${VERSION_ORC}.tar.xz
cd ${DEPS}/orc
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
mkdir ${DEPS}/gdkpixbuf
curl -Ls https://download.gnome.org/sources/gdk-pixbuf/2.34/gdk-pixbuf-${VERSION_GDKPIXBUF}.tar.xz | tar xJC ${DEPS}/gdkpixbuf --strip-components=1
cd ${DEPS}/gdkpixbuf
LD_LIBRARY_PATH=${TARGET}/lib ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \
--disable-introspection --disable-modules --without-libpng --without-libjpeg --without-libtiff --without-gdiplus --with-included-loaders= \
&& make install-strip
mkdir ${DEPS}/freetype
curl -Ls http://download.savannah.gnu.org/releases/freetype/freetype-${VERSION_FREETYPE}.tar.gz | tar xzC ${DEPS}/freetype --strip-components=1
cd ${DEPS}/freetype
./configure --prefix=${TARGET} --enable-shared --disable-static && make install
mkdir ${DEPS}/fontconfig
curl -Ls https://www.freedesktop.org/software/fontconfig/release/fontconfig-${VERSION_FONTCONFIG}.tar.bz2 | tar xjC ${DEPS}/fontconfig --strip-components=1
cd ${DEPS}/fontconfig
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --enable-libxml2 && make install-strip
mkdir ${DEPS}/harfbuzz
curl -Ls https://www.freedesktop.org/software/harfbuzz/release/harfbuzz-${VERSION_HARFBUZZ}.tar.bz2 | tar xjC ${DEPS}/harfbuzz --strip-components=1
cd ${DEPS}/harfbuzz
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
mkdir ${DEPS}/pixman
curl -Ls http://cairographics.org/releases/pixman-${VERSION_PIXMAN}.tar.gz | tar xzC ${DEPS}/pixman --strip-components=1
cd ${DEPS}/pixman
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --disable-libpng --disable-arm-iwmmxt && make install-strip
mkdir ${DEPS}/cairo
curl -Ls http://cairographics.org/releases/cairo-${VERSION_CAIRO}.tar.xz | tar xJC ${DEPS}/cairo --strip-components=1
cd ${DEPS}/cairo
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \
--disable-xlib --disable-xcb --disable-quartz --disable-win32 --disable-egl --disable-glx --disable-wgl \
--disable-script --disable-ps --disable-gobject --disable-trace --disable-interpreter \
&& make install-strip
mkdir ${DEPS}/pango
curl -Ls https://download.gnome.org/sources/pango/1.40/pango-${VERSION_PANGO}.tar.xz | tar xJC ${DEPS}/pango --strip-components=1
cd ${DEPS}/pango
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
mkdir ${DEPS}/croco
curl -Ls https://download.gnome.org/sources/libcroco/0.6/libcroco-${VERSION_CROCO}.tar.xz | tar xJC ${DEPS}/croco --strip-components=1
cd ${DEPS}/croco
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
mkdir ${DEPS}/svg
curl -Ls https://download.gnome.org/sources/librsvg/2.40/librsvg-${VERSION_SVG}.tar.xz | tar xJC ${DEPS}/svg --strip-components=1
cd ${DEPS}/svg
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \
--disable-introspection --disable-tools \
&& make install-strip
mkdir ${DEPS}/gif
curl -Ls http://${SOURCEFORGE_MIRROR}.dl.sourceforge.net/project/giflib/giflib-${VERSION_GIF}.tar.gz | tar xzC ${DEPS}/gif --strip-components=1
cd ${DEPS}/gif
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
mkdir ${DEPS}/vips
curl -Ls http://www.vips.ecs.soton.ac.uk/supported/8.2/vips-${VERSION_VIPS}.tar.gz | tar xzC ${DEPS}/vips --strip-components=1
curl -Ls http://www.vips.ecs.soton.ac.uk/supported/8.3/vips-${VERSION_VIPS}.tar.gz | tar xzC ${DEPS}/vips --strip-components=1
cd ${DEPS}/vips
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \
--disable-debug --disable-introspection --without-python --without-fftw --with-magickpackage=GraphicsMagick \
--disable-debug --disable-introspection --without-python --without-fftw \
--without-magick --without-pangoft2 --without-ppm --without-analyze --without-radiance \
--with-zip-includes=${TARGET}/include --with-zip-libraries=${TARGET}/lib \
--with-jpeg-includes=${TARGET}/include --with-jpeg-libraries=${TARGET}/lib \
&& make install-strip
@@ -117,20 +182,29 @@ rm -rf pkgconfig .libs *.la libvipsCC*
# Create JSON file of version numbers
cd ${TARGET}
echo "{\n\
\"cairo\": \"${VERSION_CAIRO}\",\n\
\"croco\": \"${VERSION_CROCO}\",\n\
\"exif\": \"${VERSION_EXIF}\",\n\
\"ffi\": \"${VERSION_FFI}\",\n\
\"fontconfig\": \"${VERSION_FONTCONFIG}\",\n\
\"freetype\": \"${VERSION_FREETYPE}\",\n\
\"gdkpixbuf\": \"${VERSION_GDKPIXBUF}\",\n\
\"gif\": \"${VERSION_GIF}\",\n\
\"glib\": \"${VERSION_GLIB}\",\n\
\"gsf\": \"${VERSION_GSF}\",\n\
\"harfbuzz\": \"${VERSION_HARFBUZZ}\",\n\
\"jpeg\": \"${VERSION_JPEG}\",\n\
\"lcms\": \"${VERSION_LCMS2}\",\n\
\"gm\": \"${VERSION_GM}\",\n\
\"orc\": \"${VERSION_ORC}\",\n\
\"pango\": \"${VERSION_PANGO}\",\n\
\"pixman\": \"${VERSION_PIXMAN}\",\n\
\"png\": \"${VERSION_PNG16}\",\n\
\"svg\": \"${VERSION_SVG}\",\n\
\"tiff\": \"${VERSION_TIFF}\",\n\
\"vips\": \"${VERSION_VIPS}\"\n\
\"vips\": \"${VERSION_VIPS}\",\n\
\"webp\": \"${VERSION_WEBP}\",\n\
\"xml\": \"${VERSION_XML2}\",\n\
\"zlib\": \"${VERSION_ZLIB}\",\n\
\"zlib\": \"${VERSION_ZLIB}\"\n\
}" >lib/versions.json
# Create .tar.gz

View File

@@ -1,6 +1,6 @@
#!/bin/sh
VERSION_VIPS=8.2.3
VERSION_VIPS=8.3.1
# Is docker available?
@@ -9,7 +9,7 @@ if ! type docker >/dev/null; then
exit 1
fi
# TODO: docker v1.9.0 will allow build-time args - https://github.com/docker/docker/pull/15182
# TODO: docker v1.9.0 allows build-time args - https://github.com/docker/docker/pull/15182
# Windows

View File

@@ -10,28 +10,40 @@ ENV DEPS=/deps \
RUN mkdir ${DEPS} && mkdir ${TARGET}
# Common build paths and flags
ENV PKG_CONFIG_PATH=${PKG_CONFIG_PATH}:${TARGET}/lib/pkgconfig \
PATH=${PATH}:${TARGET}/bin \
CPPFLAGS=-I${TARGET}/include \
LDFLAGS=-L${TARGET}/lib \
ENV PKG_CONFIG_PATH="${PKG_CONFIG_PATH}:${TARGET}/lib/pkgconfig" \
PATH="${PATH}:${TARGET}/bin" \
CPPFLAGS="-I${TARGET}/include" \
LDFLAGS="-L${TARGET}/lib" \
CFLAGS="-O3" \
CXXFLAGS="-O3"
# Dependency version numbers
ENV VERSION_ZLIB=1.2.8 \
VERSION_FFI=3.2.1 \
VERSION_GLIB=2.47.6 \
VERSION_GLIB=2.48.0 \
VERSION_XML2=2.9.3 \
VERSION_GSF=1.14.34 \
VERSION_GSF=1.14.36 \
VERSION_EXIF=0.6.21 \
VERSION_LCMS2=2.7 \
VERSION_GM=1.3.23 \
VERSION_JPEG=1.4.2 \
VERSION_JPEG=1.4.90 \
VERSION_PNG16=1.6.21 \
VERSION_WEBP=0.5.0 \
VERSION_TIFF=4.0.6 \
VERSION_ORC=0.4.25 \
VERSION_VIPS=8.2.3
VERSION_GDKPIXBUF=2.34.0 \
VERSION_FREETYPE=2.6.3 \
VERSION_FONTCONFIG=2.11.95 \
VERSION_HARFBUZZ=1.2.6 \
VERSION_PIXMAN=0.34.0 \
VERSION_CAIRO=1.14.6 \
VERSION_PANGO=1.40.1 \
VERSION_CROCO=0.6.11 \
VERSION_SVG=2.40.15 \
VERSION_GIF=5.1.4 \
VERSION_VIPS=8.3.1
# Least out-of-sync Sourceforge mirror
ENV SOURCEFORGE_MIRROR=netix
RUN mkdir ${DEPS}/zlib
RUN curl -Ls http://zlib.net/zlib-${VERSION_ZLIB}.tar.xz | tar xJC ${DEPS}/zlib --strip-components=1
@@ -45,14 +57,18 @@ 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 https://download.gnome.org/sources/glib/2.47/glib-${VERSION_GLIB}.tar.xz | tar xJC ${DEPS}/glib --strip-components=1
RUN curl -Ls https://download.gnome.org/sources/glib/2.48/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 --with-pcre=internal && make install-strip
RUN CFLAGS="${CFLAGS} -Wl,--default-symver" CXXFLAGS="${CXXFLAGS} -Wl,--default-symver" \
./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --with-pcre=internal && 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 ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \
--without-python --without-debug --without-docbook --without-ftp --without-html --without-legacy \
--without-pattern --without-push --without-regexps --without-schemas --without-schematron --with-zlib=${TARGET} \
&& make install-strip
RUN mkdir ${DEPS}/gsf
RUN curl -Ls https://download.gnome.org/sources/libgsf/1.14/libgsf-${VERSION_GSF}.tar.xz | tar xJC ${DEPS}/gsf --strip-components=1
@@ -60,27 +76,22 @@ 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://heanet.dl.sourceforge.net/project/libexif/libexif/${VERSION_EXIF}/libexif-${VERSION_EXIF}.tar.bz2 | tar xjC ${DEPS}/exif --strip-components=1
RUN curl -Ls http://${SOURCEFORGE_MIRROR}.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}/lcms2
RUN curl -Ls http://heanet.dl.sourceforge.net/project/lcms/lcms/${VERSION_LCMS2}/lcms2-${VERSION_LCMS2}.tar.gz | tar xzC ${DEPS}/lcms2 --strip-components=1
RUN curl -Ls http://${SOURCEFORGE_MIRROR}.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}/gm
RUN curl -Ls http://heanet.dl.sourceforge.net/project/graphicsmagick/graphicsmagick/${VERSION_GM}/GraphicsMagick-${VERSION_GM}.tar.xz | tar xJC ${DEPS}/gm --strip-components=1
WORKDIR ${DEPS}/gm
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --without-magick-plus-plus && make install-strip
RUN mkdir ${DEPS}/jpeg
RUN curl -Ls http://heanet.dl.sourceforge.net/project/libjpeg-turbo/${VERSION_JPEG}/libjpeg-turbo-${VERSION_JPEG}.tar.gz | tar xzC ${DEPS}/jpeg --strip-components=1
RUN curl -Ls https://github.com/libjpeg-turbo/libjpeg-turbo/archive/${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 autoreconf -fiv && ./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://heanet.dl.sourceforge.net/project/libpng/libpng16/${VERSION_PNG16}/libpng-${VERSION_PNG16}.tar.xz | tar xJC ${DEPS}/png16 --strip-components=1
RUN curl -Ls http://${SOURCEFORGE_MIRROR}.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
@@ -99,12 +110,74 @@ 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 rm ${TARGET}/lib/liborc-test-*
RUN mkdir ${DEPS}/gdkpixbuf
RUN curl -Ls https://download.gnome.org/sources/gdk-pixbuf/2.34/gdk-pixbuf-${VERSION_GDKPIXBUF}.tar.xz | tar xJC ${DEPS}/gdkpixbuf --strip-components=1
WORKDIR ${DEPS}/gdkpixbuf
RUN LD_LIBRARY_PATH=${TARGET}/lib ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \
--disable-introspection --disable-modules --without-libpng --without-libjpeg --without-libtiff --without-gdiplus --with-included-loaders= \
&& make install-strip
RUN mkdir ${DEPS}/freetype
RUN curl -Ls http://download.savannah.gnu.org/releases/freetype/freetype-${VERSION_FREETYPE}.tar.gz | tar xzC ${DEPS}/freetype --strip-components=1
WORKDIR ${DEPS}/freetype
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static && make install
RUN mkdir ${DEPS}/fontconfig
RUN curl -Ls https://www.freedesktop.org/software/fontconfig/release/fontconfig-${VERSION_FONTCONFIG}.tar.bz2 | tar xjC ${DEPS}/fontconfig --strip-components=1
WORKDIR ${DEPS}/fontconfig
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --enable-libxml2 && make install-strip
RUN mkdir ${DEPS}/harfbuzz
RUN curl -Ls https://www.freedesktop.org/software/harfbuzz/release/harfbuzz-${VERSION_HARFBUZZ}.tar.bz2 | tar xjC ${DEPS}/harfbuzz --strip-components=1
WORKDIR ${DEPS}/harfbuzz
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
RUN mkdir ${DEPS}/pixman
RUN curl -Ls http://cairographics.org/releases/pixman-${VERSION_PIXMAN}.tar.gz | tar xzC ${DEPS}/pixman --strip-components=1
WORKDIR ${DEPS}/pixman
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking --disable-libpng && make install-strip
RUN mkdir ${DEPS}/cairo
RUN curl -Ls http://cairographics.org/releases/cairo-${VERSION_CAIRO}.tar.xz | tar xJC ${DEPS}/cairo --strip-components=1
WORKDIR ${DEPS}/cairo
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \
--disable-xlib --disable-xcb --disable-quartz --disable-win32 --disable-egl --disable-glx --disable-wgl \
--disable-script --disable-ps --disable-gobject --disable-trace --disable-interpreter \
&& make install-strip
RUN mkdir ${DEPS}/pango
RUN curl -Ls https://download.gnome.org/sources/pango/1.40/pango-${VERSION_PANGO}.tar.xz | tar xJC ${DEPS}/pango --strip-components=1
WORKDIR ${DEPS}/pango
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
RUN mkdir ${DEPS}/croco
RUN curl -Ls https://download.gnome.org/sources/libcroco/0.6/libcroco-${VERSION_CROCO}.tar.xz | tar xJC ${DEPS}/croco --strip-components=1
WORKDIR ${DEPS}/croco
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking && make install-strip
RUN mkdir ${DEPS}/svg
RUN curl -Ls https://download.gnome.org/sources/librsvg/2.40/librsvg-${VERSION_SVG}.tar.xz | tar xJC ${DEPS}/svg --strip-components=1
WORKDIR ${DEPS}/svg
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \
--disable-introspection --disable-tools \
&& make install-strip
RUN mkdir ${DEPS}/gif
RUN curl -Ls http://${SOURCEFORGE_MIRROR}.dl.sourceforge.net/project/giflib/giflib-${VERSION_GIF}.tar.gz | tar xzC ${DEPS}/gif --strip-components=1
WORKDIR ${DEPS}/gif
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.2/vips-${VERSION_VIPS}.tar.gz | tar xzC ${DEPS}/vips --strip-components=1
RUN curl -Ls http://www.vips.ecs.soton.ac.uk/supported/8.3/vips-${VERSION_VIPS}.tar.gz | tar xzC ${DEPS}/vips --strip-components=1
#RUN apt-get install -y swig gobject-introspection gettext glib2.0-dev
#RUN curl -Ls https://github.com/jcupitt/libvips/archive/master.tar.gz | tar xzC ${DEPS}/vips --strip-components=1
WORKDIR ${DEPS}/vips
#RUN ./bootstrap.sh
RUN ./configure --prefix=${TARGET} --enable-shared --disable-static --disable-dependency-tracking \
--disable-debug --disable-introspection --without-python --without-fftw --with-magickpackage=GraphicsMagick \
--disable-debug --disable-introspection --without-python --without-fftw \
--without-magick --without-pangoft2 --without-ppm --without-analyze --without-radiance \
--with-zip-includes=${TARGET}/include --with-zip-libraries=${TARGET}/lib \
--with-jpeg-includes=${TARGET}/include --with-jpeg-libraries=${TARGET}/lib \
&& make install-strip
@@ -118,20 +191,29 @@ RUN rm -rf pkgconfig .libs *.la libvipsCC*
# Create JSON file of version numbers
WORKDIR ${TARGET}
RUN echo "{\n\
\"cairo\": \"${VERSION_CAIRO}\",\n\
\"croco\": \"${VERSION_CROCO}\",\n\
\"exif\": \"${VERSION_EXIF}\",\n\
\"ffi\": \"${VERSION_FFI}\",\n\
\"fontconfig\": \"${VERSION_FONTCONFIG}\",\n\
\"freetype\": \"${VERSION_FREETYPE}\",\n\
\"gdkpixbuf\": \"${VERSION_GDKPIXBUF}\",\n\
\"gif\": \"${VERSION_GIF}\",\n\
\"glib\": \"${VERSION_GLIB}\",\n\
\"gsf\": \"${VERSION_GSF}\",\n\
\"harfbuzz\": \"${VERSION_HARFBUZZ}\",\n\
\"jpeg\": \"${VERSION_JPEG}\",\n\
\"lcms\": \"${VERSION_LCMS2}\",\n\
\"gm\": \"${VERSION_GM}\",\n\
\"orc\": \"${VERSION_ORC}\",\n\
\"pango\": \"${VERSION_PANGO}\",\n\
\"pixman\": \"${VERSION_PIXMAN}\",\n\
\"png\": \"${VERSION_PNG16}\",\n\
\"svg\": \"${VERSION_SVG}\",\n\
\"tiff\": \"${VERSION_TIFF}\",\n\
\"vips\": \"${VERSION_VIPS}\"\n\
\"vips\": \"${VERSION_VIPS}\",\n\
\"webp\": \"${VERSION_WEBP}\",\n\
\"xml\": \"${VERSION_XML2}\",\n\
\"zlib\": \"${VERSION_ZLIB}\",\n\
\"zlib\": \"${VERSION_ZLIB}\"\n\
}" >lib/versions.json
# Create .tar.gz

View File

@@ -6,34 +6,33 @@ if ! type docker >/dev/null; then
exit 1
fi
version_node=4.4.2
test="npm run clean; npm install --unsafe-perm; npm test"
# Debian 7, 8
# Ubuntu 12.04, 14.04
for dist in wheezy jessie precise trusty; do
# Ubuntu 14.04
for dist in wheezy jessie trusty; do
echo "Testing $dist..."
if docker run -i -t --rm -v $PWD:/v -e "NODE_ENV=development" nodesource/$dist:0.12 >packaging/$dist.log 2>&1 sh -c "cd /v; ./packaging/test/debian.sh; $test";
if docker run -i -t --rm -v $PWD:/v -e "NODE_ENV=development" nodesource/$dist:$version_node >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 -e "NODE_ENV=development" 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 -e "NODE_ENV=development" 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
echo "Testing centos7..."
if docker run -i -t --rm -v $PWD:/v -e "NODE_ENV=development" nodesource/centos7:$version_node >packaging/$dist.log 2>&1 sh -c "cd /v; $test";
then echo "$dist OK"
else echo "$dist fail" && cat packaging/$dist.log
fi
# Fedora 22
echo "Testing fedora22..."
if docker run -i -t --rm -v $PWD:/v -e "NODE_ENV=development" nodesource/fedora22:$version_node >packaging/$dist.log 2>&1 sh -c "cd /v; $test";
then echo "$dist OK"
else echo "$dist fail" && cat packaging/$dist.log
fi
# openSUSE 13.2
echo "Testing opensuse..."

View File

@@ -1,8 +0,0 @@
#!/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++

View File

@@ -1,19 +1,19 @@
FROM ubuntu:precise
FROM debian:wheezy
MAINTAINER Lovell Fuller <npm@lovell.info>
RUN apt-get update && apt-get install -y curl zip
ENV VERSION_VIPS=8.2.3
ENV VERSION_VIPS=8.3.1
# Fetch and unzip
RUN mkdir /vips
WORKDIR /vips
RUN curl -O http://www.vips.ecs.soton.ac.uk/supported/8.2/win32/vips-dev-w64-8.2.zip
RUN unzip vips-dev-w64-8.2.zip
RUN curl -L -O https://github.com/lovell/build-win64/releases/download/v${VERSION_VIPS}/vips-dev-w64-web-${VERSION_VIPS}.zip
RUN unzip vips-dev-w64-web-${VERSION_VIPS}.zip
# Clean and zip
WORKDIR /vips/vips-dev-8.2
RUN rm bin/libvipsCC-42.dll bin/libvips-cpp-42.dll bin/libgsf-win32-1-114.dll bin/libstdc++-6.dll
WORKDIR /vips/vips-dev-8.3
RUN rm bin/libvipsCC-42.dll bin/libvips-cpp-42.dll bin/libgsf-win32-1-114.dll
RUN cp bin/*.dll lib/
RUN cp -r lib64/* lib/

View File

@@ -1,28 +1,19 @@
#!/bin/sh
# This script is no longer required on most
# 64-bit Linux systems when using sharp v0.12.0+
# Use of this script is deprecated
# See http://sharp.dimens.io/page/install#linux
echo
echo "WARNING: This script is no longer required on most 64-bit Linux systems when using sharp v0.12.0+"
echo
echo "See http://sharp.dimens.io/page/install#linux"
echo
echo "If you really, really need this script, it will attempt"
echo "to globally install libvips if not already available."
echo
# If you really need this script, it will attempt to
# globally install libvips if not already available.
# Supports:
# * Debian Linux
# * Debian 7, 8
# * Ubuntu 12.04, 14.04, 14.10, 15.04, 15.10
# * Mint 13, 17
# * Elementary 0.3
# * Red Hat Linux
# * RHEL/Centos/Scientific 6, 7
# * Fedora 21, 22, 23
# * Amazon Linux 2015.03, 2015.09
# * OpenSuse 13
vips_version_minimum=8.2.3
vips_version_latest_major_minor=8.2
vips_version_latest_patch=3
vips_version_minimum=8.3.1
vips_version_latest_major_minor=8.3
vips_version_latest_patch=1
openslide_version_minimum=3.4.0
openslide_version_latest_major_minor=3.4
@@ -33,7 +24,7 @@ install_libvips_from_source() {
curl -O http://www.vips.ecs.soton.ac.uk/supported/$vips_version_latest_major_minor/vips-$vips_version_latest_major_minor.$vips_version_latest_patch.tar.gz
tar zvxf vips-$vips_version_latest_major_minor.$vips_version_latest_patch.tar.gz
cd vips-$vips_version_latest_major_minor.$vips_version_latest_patch
./configure --disable-debug --disable-docs --disable-static --disable-introspection --disable-dependency-tracking --enable-cxx=yes --without-python --without-orc --without-fftw $1
CXXFLAGS="-D_GLIBCXX_USE_CXX11_ABI=0" ./configure --disable-debug --disable-docs --disable-static --disable-introspection --disable-dependency-tracking --enable-cxx=yes --without-python --without-orc --without-fftw $1
make
make install
cd ..
@@ -139,7 +130,7 @@ if [ $enable_openslide -eq 1 ] && [ -z $vips_with_openslide ] && [ $openslide_ex
DISTRO=$(lsb_release -c -s)
echo "Detected Debian Linux '$DISTRO'"
case "$DISTRO" in
jessie|vivid|wily)
jessie|vivid|wily|xenial)
# Debian 8, Ubuntu 15
echo "Installing libopenslide via apt-get"
apt-get install -y libopenslide-dev

View File

@@ -55,6 +55,9 @@ namespace sharp {
bool IsDzZip(std::string const &str) {
return EndsWith(str, ".zip") || EndsWith(str, ".ZIP") || EndsWith(str, ".szi") || EndsWith(str, ".SZI");
}
bool IsV(std::string const &str) {
return EndsWith(str, ".v") || EndsWith(str, ".V") || EndsWith(str, ".vips") || EndsWith(str, ".VIPS");
}
/*
Provide a string identifier for the given image type.
@@ -66,10 +69,14 @@ namespace sharp {
case ImageType::PNG: id = "png"; break;
case ImageType::WEBP: id = "webp"; break;
case ImageType::TIFF: id = "tiff"; break;
case ImageType::GIF: id = "gif"; break;
case ImageType::SVG: id = "svg"; break;
case ImageType::PDF: id = "pdf"; break;
case ImageType::MAGICK: id = "magick"; break;
case ImageType::OPENSLIDE: id = "openslide"; break;
case ImageType::PPM: id = "ppm"; break;
case ImageType::FITS: id = "fits"; break;
case ImageType::VIPS: id = "v"; break;
case ImageType::RAW: id = "raw"; break;
case ImageType::UNKNOWN: id = "unknown"; break;
}
@@ -92,6 +99,12 @@ namespace sharp {
imageType = ImageType::WEBP;
} else if (EndsWith(loader, "TiffBuffer")) {
imageType = ImageType::TIFF;
} else if (EndsWith(loader, "GifBuffer")) {
imageType = ImageType::GIF;
} else if (EndsWith(loader, "SvgBuffer")) {
imageType = ImageType::SVG;
} else if (EndsWith(loader, "PdfBuffer")) {
imageType = ImageType::PDF;
} else if (EndsWith(loader, "MagickBuffer")) {
imageType = ImageType::MAGICK;
}
@@ -117,10 +130,18 @@ namespace sharp {
imageType = ImageType::OPENSLIDE;
} else if (EndsWith(loader, "TiffFile")) {
imageType = ImageType::TIFF;
} else if (EndsWith(loader, "GifFile")) {
imageType = ImageType::GIF;
} else if (EndsWith(loader, "SvgFile")) {
imageType = ImageType::SVG;
} else if (EndsWith(loader, "PdfFile")) {
imageType = ImageType::PDF;
} else if (EndsWith(loader, "Ppm")) {
imageType = ImageType::PPM;
} else if (EndsWith(loader, "Fits")) {
imageType = ImageType::FITS;
} else if (EndsWith(loader, "Vips")) {
imageType = ImageType::VIPS;
} else if (EndsWith(loader, "Magick") || EndsWith(loader, "MagickFile")) {
imageType = ImageType::MAGICK;
}
@@ -262,4 +283,63 @@ namespace sharp {
return std::make_tuple(left, top);
}
/*
Calculate the (left, top) coordinates of the output image
within the input image, applying the given x and y offsets.
*/
std::tuple<int, int> CalculateCrop(int const inWidth, int const inHeight,
int const outWidth, int const outHeight, int const x, int const y) {
// default values
int left = 0;
int top = 0;
// assign only if valid
if(x >= 0 && x < (inWidth - outWidth)) {
left = x;
} else if(x >= (inWidth - outWidth)) {
left = inWidth - outWidth;
}
if(y >= 0 && y < (inHeight - outHeight)) {
top = y;
} else if(x >= (inHeight - outHeight)) {
top = inHeight - outHeight;
}
// the resulting left and top could have been outside the image after calculation from bottom/right edges
if(left < 0) {
left = 0;
}
if(top < 0) {
top = 0;
}
return std::make_tuple(left, top);
}
/*
Are pixel values in this image 16-bit integer?
*/
bool Is16Bit(VipsInterpretation const interpretation) {
return interpretation == VIPS_INTERPRETATION_RGB16 || interpretation == VIPS_INTERPRETATION_GREY16;
}
/*
Return the image alpha maximum. Useful for combining alpha bands. scRGB
images are 0 - 1 for image data, but the alpha is 0 - 255.
*/
double MaximumImageAlpha(VipsInterpretation const interpretation) {
return Is16Bit(interpretation) ? 65535.0 : 255.0;
}
/*
Get boolean operation type from string
*/
VipsOperationBoolean GetBooleanOperation(std::string const opStr) {
return static_cast<VipsOperationBoolean>(
vips_enum_from_nick(nullptr, VIPS_TYPE_OPERATION_BOOLEAN, opStr.data())
);
}
} // namespace sharp

View File

@@ -11,16 +11,20 @@ using vips::VImage;
namespace sharp {
enum class ImageType {
UNKNOWN,
JPEG,
PNG,
WEBP,
TIFF,
GIF,
SVG,
PDF,
MAGICK,
OPENSLIDE,
PPM,
FITS,
RAW
VIPS,
RAW,
UNKNOWN
};
// How many tasks are in the queue?
@@ -36,6 +40,7 @@ namespace sharp {
bool IsTiff(std::string const &str);
bool IsDz(std::string const &str);
bool IsDzZip(std::string const &str);
bool IsV(std::string const &str);
/*
Provide a string identifier for the given image type.
@@ -105,6 +110,29 @@ namespace sharp {
std::tuple<int, int> CalculateCrop(int const inWidth, int const inHeight,
int const outWidth, int const outHeight, int const gravity);
/*
Calculate the (left, top) coordinates of the output image
within the input image, applying the given x and y offsets of the output image.
*/
std::tuple<int, int> CalculateCrop(int const inWidth, int const inHeight,
int const outWidth, int const outHeight, int const x, int const y);
/*
Are pixel values in this image 16-bit integer?
*/
bool Is16Bit(VipsInterpretation const interpretation);
/*
Return the image alpha maximum. Useful for combining alpha bands. scRGB
images are 0 - 1 for image data, but the alpha is 0 - 255.
*/
double MaximumImageAlpha(VipsInterpretation const interpretation);
/*
Get boolean operation type from string
*/
VipsOperationBoolean GetBooleanOperation(std::string const opStr);
} // namespace sharp
#endif // SRC_COMMON_H_

View File

@@ -711,4 +711,499 @@ VImage::maxpos( VOption *options )
return( std::complex<double>( x, y ) );
}
// Operator overloads
VImage
VImage::operator[]( int index )
{
return( this->extract_band( index ) );
}
std::vector<double>
VImage::operator()( int x, int y )
{
return( this->getpoint( x, y ) );
}
VImage
operator+( VImage a, VImage b )
{
return( a.add( b ) );
}
VImage
operator+( double a, VImage b )
{
return( b.linear( 1.0, a ) );
}
VImage
operator+( VImage a, double b )
{
return( a.linear( 1.0, b ) );
}
VImage
operator+( std::vector<double> a, VImage b )
{
return( b.linear( 1.0, a ) );
}
VImage
operator+( VImage a, std::vector<double> b )
{
return( a.linear( 1.0, b ) );
}
VImage
operator-( VImage a, VImage b )
{
return( a.subtract( b ) );
}
VImage operator-( double a, VImage b )
{
return( b.linear( -1.0, a ) );
}
VImage
operator-( VImage a, double b )
{
return( a.linear( 1.0, -b ) );
}
VImage
operator-( std::vector<double> a, VImage b )
{
return( b.linear( -1.0, a ) );
}
VImage
operator-( VImage a, std::vector<double> b )
{
return( a.linear( 1.0, vips::negate( b ) ) );
}
VImage
operator-( VImage a )
{
return( a * -1 );
}
VImage
operator*( VImage a, VImage b )
{
return( a.multiply( b ) );
}
VImage
operator*( double a, VImage b )
{
return( b.linear( a, 0.0 ) );
}
VImage
operator*( VImage a, double b )
{
return( a.linear( b, 0.0 ) );
}
VImage
operator*( std::vector<double> a, VImage b )
{
return( b.linear( a, 0.0 ) );
}
VImage
operator*( VImage a, std::vector<double> b )
{
return( a.linear( b, 0.0 ) );
}
VImage
operator/( VImage a, VImage b )
{
return( a.divide( b ) );
}
VImage
operator/( double a, VImage b )
{
return( b.pow( -1.0 ).linear( a, 0.0 ) );
}
VImage
operator/( VImage a, double b )
{
return( a.linear( 1.0 / b, 0.0 ) );
}
VImage
operator/( std::vector<double> a, VImage b )
{
return( b.pow( -1.0 ).linear( a, 0.0 ) );
}
VImage
operator/( VImage a, std::vector<double> b )
{
return( a.linear( vips::invert( b ), 0.0 ) );
}
VImage
operator%( VImage a, VImage b )
{
return( a.remainder( b ) );
}
VImage
operator%( VImage a, double b )
{
return( a.remainder_const( to_vector( b ) ) );
}
VImage
operator%( VImage a, std::vector<double> b )
{
return( a.remainder_const( b ) );
}
VImage
operator<( VImage a, VImage b )
{
return( a.relational( b, VIPS_OPERATION_RELATIONAL_LESS ) );
}
VImage
operator<( double a, VImage b )
{
return( b.relational_const( to_vector( a ),
VIPS_OPERATION_RELATIONAL_MORE ) );
}
VImage
operator<( VImage a, double b )
{
return( a.relational_const( to_vector( b ),
VIPS_OPERATION_RELATIONAL_LESS ) );
}
VImage
operator<( std::vector<double> a, VImage b )
{
return( b.relational_const( a,
VIPS_OPERATION_RELATIONAL_MORE ) );
}
VImage
operator<( VImage a, std::vector<double> b )
{
return( a.relational_const( b,
VIPS_OPERATION_RELATIONAL_LESS ) );
}
VImage
operator<=( VImage a, VImage b )
{
return( a.relational( b, VIPS_OPERATION_RELATIONAL_LESSEQ ) );
}
VImage
operator<=( double a, VImage b )
{
return( b.relational_const( to_vector( a ),
VIPS_OPERATION_RELATIONAL_MOREEQ ) );
}
VImage
operator<=( VImage a, double b )
{
return( a.relational_const( to_vector( b ),
VIPS_OPERATION_RELATIONAL_LESSEQ ) );
}
VImage
operator<=( std::vector<double> a, VImage b )
{
return( b.relational_const( a,
VIPS_OPERATION_RELATIONAL_MOREEQ ) );
}
VImage
operator<=( VImage a, std::vector<double> b )
{
return( a.relational_const( b,
VIPS_OPERATION_RELATIONAL_LESSEQ ) );
}
VImage
operator>( VImage a, VImage b )
{
return( a.relational( b, VIPS_OPERATION_RELATIONAL_MORE ) );
}
VImage
operator>( double a, VImage b )
{
return( b.relational_const( to_vector( a ),
VIPS_OPERATION_RELATIONAL_LESS ) );
}
VImage
operator>( VImage a, double b )
{
return( a.relational_const( to_vector( b ),
VIPS_OPERATION_RELATIONAL_MORE ) );
}
VImage
operator>( std::vector<double> a, VImage b )
{
return( b.relational_const( a,
VIPS_OPERATION_RELATIONAL_LESS ) );
}
VImage
operator>( VImage a, std::vector<double> b )
{
return( a.relational_const( b,
VIPS_OPERATION_RELATIONAL_MORE ) );
}
VImage
operator>=( VImage a, VImage b )
{
return( a.relational( b, VIPS_OPERATION_RELATIONAL_MOREEQ ) );
}
VImage
operator>=( double a, VImage b )
{
return( b.relational_const( to_vector( a ),
VIPS_OPERATION_RELATIONAL_LESSEQ ) );
}
VImage
operator>=( VImage a, double b )
{
return( a.relational_const( to_vector( b ),
VIPS_OPERATION_RELATIONAL_MOREEQ ) );
}
VImage
operator>=( std::vector<double> a, VImage b )
{
return( b.relational_const( a,
VIPS_OPERATION_RELATIONAL_LESSEQ ) );
}
VImage
operator>=( VImage a, std::vector<double> b )
{
return( a.relational_const( b,
VIPS_OPERATION_RELATIONAL_MOREEQ ) );
}
VImage
operator==( VImage a, VImage b )
{
return( a.relational( b, VIPS_OPERATION_RELATIONAL_EQUAL ) );
}
VImage
operator==( double a, VImage b )
{
return( b.relational_const( to_vector( a ),
VIPS_OPERATION_RELATIONAL_EQUAL ) );
}
VImage
operator==( VImage a, double b )
{
return( a.relational_const( to_vector( b ),
VIPS_OPERATION_RELATIONAL_EQUAL ) );
}
VImage
operator==( std::vector<double> a, VImage b )
{
return( b.relational_const( a,
VIPS_OPERATION_RELATIONAL_EQUAL ) );
}
VImage
operator==( VImage a, std::vector<double> b )
{
return( a.relational_const( b,
VIPS_OPERATION_RELATIONAL_EQUAL ) );
}
VImage
operator!=( VImage a, VImage b )
{
return( a.relational( b, VIPS_OPERATION_RELATIONAL_NOTEQ ) );
}
VImage
operator!=( double a, VImage b )
{
return( b.relational_const( to_vector( a ),
VIPS_OPERATION_RELATIONAL_NOTEQ ) );
}
VImage
operator!=( VImage a, double b )
{
return( a.relational_const( to_vector( b ),
VIPS_OPERATION_RELATIONAL_NOTEQ ) );
}
VImage
operator!=( std::vector<double> a, VImage b )
{
return( b.relational_const( a,
VIPS_OPERATION_RELATIONAL_NOTEQ ) );
}
VImage
operator!=( VImage a, std::vector<double> b )
{
return( a.relational_const( b,
VIPS_OPERATION_RELATIONAL_NOTEQ ) );
}
VImage
operator&( VImage a, VImage b )
{
return( a.boolean( b, VIPS_OPERATION_BOOLEAN_AND ) );
}
VImage
operator&( double a, VImage b )
{
return( b.boolean_const( to_vector( a ),
VIPS_OPERATION_BOOLEAN_AND ) );
}
VImage
operator&( VImage a, double b )
{
return( a.boolean_const( to_vector( b ),
VIPS_OPERATION_BOOLEAN_AND ) );
}
VImage
operator&( std::vector<double> a, VImage b )
{
return( b.boolean_const( a, VIPS_OPERATION_BOOLEAN_AND ) );
}
VImage
operator&( VImage a, std::vector<double> b )
{
return( a.boolean_const( b, VIPS_OPERATION_BOOLEAN_AND ) );
}
VImage
operator|( VImage a, VImage b )
{
return( a.boolean( b, VIPS_OPERATION_BOOLEAN_OR ) );
}
VImage
operator|( double a, VImage b )
{
return( b.boolean_const( to_vector( a ),
VIPS_OPERATION_BOOLEAN_OR ) );
}
VImage
operator|( VImage a, double b )
{
return( a.boolean_const( to_vector( b ),
VIPS_OPERATION_BOOLEAN_OR ) );
}
VImage
operator|( std::vector<double> a, VImage b )
{
return( b.boolean_const( a, VIPS_OPERATION_BOOLEAN_OR ) );
}
VImage
operator|( VImage a, std::vector<double> b )
{
return( a.boolean_const( b, VIPS_OPERATION_BOOLEAN_OR ) );
}
VImage
operator^( VImage a, VImage b )
{
return( a.boolean( b, VIPS_OPERATION_BOOLEAN_EOR ) );
}
VImage
operator^( double a, VImage b )
{
return( b.boolean_const( to_vector( a ),
VIPS_OPERATION_BOOLEAN_EOR ) );
}
VImage
operator^( VImage a, double b )
{
return( a.boolean_const( to_vector( b ),
VIPS_OPERATION_BOOLEAN_EOR ) );
}
VImage
operator^( std::vector<double> a, VImage b )
{
return( b.boolean_const( a, VIPS_OPERATION_BOOLEAN_EOR ) );
}
VImage
operator^( VImage a, std::vector<double> b )
{
return( a.boolean_const( b, VIPS_OPERATION_BOOLEAN_EOR ) );
}
VImage
operator<<( VImage a, VImage b )
{
return( a.boolean( b, VIPS_OPERATION_BOOLEAN_LSHIFT ) );
}
VImage
operator<<( VImage a, double b )
{
return( a.boolean_const( to_vector( b ),
VIPS_OPERATION_BOOLEAN_LSHIFT ) );
}
VImage
operator<<( VImage a, std::vector<double> b )
{
return( a.boolean_const( b, VIPS_OPERATION_BOOLEAN_LSHIFT ) );
}
VImage
operator>>( VImage a, VImage b )
{
return( a.boolean( b, VIPS_OPERATION_BOOLEAN_RSHIFT ) );
}
VImage
operator>>( VImage a, double b )
{
return( a.boolean_const( to_vector( b ),
VIPS_OPERATION_BOOLEAN_RSHIFT ) );
}
VImage
operator>>( VImage a, std::vector<double> b )
{
return( a.boolean_const( b, VIPS_OPERATION_BOOLEAN_RSHIFT ) );
}
VIPS_NAMESPACE_END

View File

@@ -1,5 +1,5 @@
// bodies for vips operations
// Sat Jan 9 15:05:58 GMT 2016
// Fri Feb 12 20:03:53 GMT 2016
// this file is generated automatically, do not edit!
void VImage::system( char * cmd_format , VOption *options )
@@ -1408,6 +1408,78 @@ VImage VImage::vipsload( char * filename , VOption *options )
return( out );
}
VImage VImage::pdfload( char * filename , VOption *options )
{
VImage out;
call( "pdfload" ,
(options ? options : VImage::option()) ->
set( "filename", filename ) ->
set( "out", &out ) );
return( out );
}
VImage VImage::pdfload_buffer( VipsBlob * buffer , VOption *options )
{
VImage out;
call( "pdfload_buffer" ,
(options ? options : VImage::option()) ->
set( "buffer", buffer ) ->
set( "out", &out ) );
return( out );
}
VImage VImage::svgload( char * filename , VOption *options )
{
VImage out;
call( "svgload" ,
(options ? options : VImage::option()) ->
set( "filename", filename ) ->
set( "out", &out ) );
return( out );
}
VImage VImage::svgload_buffer( VipsBlob * buffer , VOption *options )
{
VImage out;
call( "svgload_buffer" ,
(options ? options : VImage::option()) ->
set( "buffer", buffer ) ->
set( "out", &out ) );
return( out );
}
VImage VImage::gifload( char * filename , VOption *options )
{
VImage out;
call( "gifload" ,
(options ? options : VImage::option()) ->
set( "filename", filename ) ->
set( "out", &out ) );
return( out );
}
VImage VImage::gifload_buffer( VipsBlob * buffer , VOption *options )
{
VImage out;
call( "gifload_buffer" ,
(options ? options : VImage::option()) ->
set( "buffer", buffer ) ->
set( "out", &out ) );
return( out );
}
VImage VImage::pngload( char * filename , VOption *options )
{
VImage out;
@@ -1783,11 +1855,37 @@ VImage VImage::shrinkv( int yshrink , VOption *options )
return( out );
}
VImage VImage::shrink2( double xshrink , double yshrink , VOption *options )
VImage VImage::reduceh( double xshrink , VOption *options )
{
VImage out;
call( "shrink2" ,
call( "reduceh" ,
(options ? options : VImage::option()) ->
set( "in", *this ) ->
set( "out", &out ) ->
set( "xshrink", xshrink ) );
return( out );
}
VImage VImage::reducev( double yshrink , VOption *options )
{
VImage out;
call( "reducev" ,
(options ? options : VImage::option()) ->
set( "in", *this ) ->
set( "out", &out ) ->
set( "yshrink", yshrink ) );
return( out );
}
VImage VImage::reduce( double xshrink , double yshrink , VOption *options )
{
VImage out;
call( "reduce" ,
(options ? options : VImage::option()) ->
set( "in", *this ) ->
set( "out", &out ) ->

View File

@@ -1,5 +1,6 @@
#include <algorithm>
#include <tuple>
#include <memory>
#include <vips/vips8>
#include "common.h"
@@ -15,6 +16,49 @@ namespace sharp {
Assumes alpha channels are already premultiplied and will be unpremultiplied after.
*/
VImage Composite(VImage src, VImage dst, const int gravity) {
if(IsInputValidForComposition(src, dst)) {
// Enlarge overlay src, if required
if (src.width() < dst.width() || src.height() < dst.height()) {
// Calculate the (left, top) coordinates of the output image within the input image, applying the given gravity.
int left;
int top;
std::tie(left, top) = CalculateCrop(dst.width(), dst.height(), src.width(), src.height(), gravity);
// Embed onto transparent background
std::vector<double> background { 0.0, 0.0, 0.0, 0.0 };
src = src.embed(left, top, dst.width(), dst.height(), VImage::option()
->set("extend", VIPS_EXTEND_BACKGROUND)
->set("background", background)
);
}
return CompositeImage(src, dst);
}
// If the input was not valid for composition the return the input image itself
return dst;
}
VImage Composite(VImage src, VImage dst, const int x, const int y) {
if(IsInputValidForComposition(src, dst)) {
// Enlarge overlay src, if required
if (src.width() < dst.width() || src.height() < dst.height()) {
// Calculate the (left, top) coordinates of the output image within the input image, applying the given gravity.
int left;
int top;
std::tie(left, top) = CalculateCrop(dst.width(), dst.height(), src.width(), src.height(), x, y);
// Embed onto transparent background
std::vector<double> background { 0.0, 0.0, 0.0, 0.0 };
src = src.embed(left, top, dst.width(), dst.height(), VImage::option()
->set("extend", VIPS_EXTEND_BACKGROUND)
->set("background", background)
);
}
return CompositeImage(src, dst);
}
// If the input was not valid for composition the return the input image itself
return dst;
}
bool IsInputValidForComposition(VImage src, VImage dst) {
using sharp::CalculateCrop;
using sharp::HasAlpha;
@@ -28,20 +72,10 @@ namespace sharp {
throw VError("Overlay image must have same dimensions or smaller");
}
// Enlarge overlay src, if required
if (src.width() < dst.width() || src.height() < dst.height()) {
// Calculate the (left, top) coordinates of the output image within the input image, applying the given gravity.
int left;
int top;
std::tie(left, top) = CalculateCrop(dst.width(), dst.height(), src.width(), src.height(), gravity);
// Embed onto transparent background
std::vector<double> background { 0.0, 0.0, 0.0, 0.0 };
src = src.embed(left, top, dst.width(), dst.height(), VImage::option()
->set("extend", VIPS_EXTEND_BACKGROUND)
->set("background", background)
);
}
return true;
}
VImage CompositeImage(VImage src, VImage dst) {
// Split src into non-alpha and alpha channels
VImage srcWithoutAlpha = src.extract_band(0, VImage::option()->set("n", src.bands() - 1));
VImage srcAlpha = src[src.bands() - 1] * (1.0 / 255.0);
@@ -81,6 +115,65 @@ namespace sharp {
return outRGBPremultiplied.bandjoin(outAlphaNormalized * 255.0);
}
/*
Cutout src over dst with given gravity.
*/
VImage Cutout(VImage mask, VImage dst, const int gravity) {
using sharp::CalculateCrop;
using sharp::HasAlpha;
using sharp::MaximumImageAlpha;
bool maskHasAlpha = HasAlpha(mask);
if (!maskHasAlpha && mask.bands() > 1) {
throw VError("Overlay image must have an alpha channel or one band");
}
if (!HasAlpha(dst)) {
throw VError("Image to be overlaid must have an alpha channel");
}
if (mask.width() > dst.width() || mask.height() > dst.height()) {
throw VError("Overlay image must have same dimensions or smaller");
}
// Enlarge overlay mask, if required
if (mask.width() < dst.width() || mask.height() < dst.height()) {
// Calculate the (left, top) coordinates of the output image within the input image, applying the given gravity.
int left;
int top;
std::tie(left, top) = CalculateCrop(dst.width(), dst.height(), mask.width(), mask.height(), gravity);
// Embed onto transparent background
std::vector<double> background { 0.0, 0.0, 0.0, 0.0 };
mask = mask.embed(left, top, dst.width(), dst.height(), VImage::option()
->set("extend", VIPS_EXTEND_BACKGROUND)
->set("background", background)
);
}
// we use the mask alpha if it has alpha
if(maskHasAlpha) {
mask = mask.extract_band(mask.bands() - 1, VImage::option()->set("n", 1));;
}
// Split dst into an optional alpha
VImage dstAlpha = dst.extract_band(dst.bands() - 1, VImage::option()->set("n", 1));
// we use the dst non-alpha
dst = dst.extract_band(0, VImage::option()->set("n", dst.bands() - 1));
// the range of the mask and the image need to match .. one could be
// 16-bit, one 8-bit
double const dstMax = MaximumImageAlpha(dst.interpretation());
double const maskMax = MaximumImageAlpha(mask.interpretation());
// combine the new mask and the existing alpha ... there are
// many ways of doing this, mult is the simplest
mask = dstMax * ((mask / maskMax) * (dstAlpha / dstMax));
// append the mask to the image data ... the mask might be float now,
// we must cast the format down to match the image data
return dst.bandjoin(mask.cast(dst.format()));
}
/*
* Stretch luminance to cover full dynamic range.
*/
@@ -135,10 +228,10 @@ namespace sharp {
}
/*
* Gaussian blur (use sigma <0 for fast blur)
* Gaussian blur. Use sigma of -1.0 for fast blur.
*/
VImage Blur(VImage image, double const sigma) {
if (sigma < 0.0) {
if (sigma == -1.0) {
// Fast, mild blur - averages neighbouring pixels
VImage blur = VImage::new_matrixv(3, 3,
1.0, 1.0, 1.0,
@@ -153,10 +246,30 @@ namespace sharp {
}
/*
* Sharpen flat and jagged areas. Use radius of -1 for fast sharpen.
* Convolution with a kernel.
*/
VImage Sharpen(VImage image, int const radius, double const flat, double const jagged) {
if (radius == -1) {
VImage Convolve(VImage image, int const width, int const height,
double const scale, double const offset,
std::unique_ptr<double[]> const &kernel_v
) {
VImage kernel = VImage::new_from_memory(
kernel_v.get(),
width * height * sizeof(double),
width,
height,
1,
VIPS_FORMAT_DOUBLE);
kernel.set("scale", scale);
kernel.set("offset", offset);
return image.conv(kernel);
}
/*
* Sharpen flat and jagged areas. Use sigma of -1.0 for fast sharpen.
*/
VImage Sharpen(VImage image, double const sigma, double const flat, double const jagged) {
if (sigma == -1.0) {
// Fast, mild sharpen
VImage sharpen = VImage::new_matrixv(3, 3,
-1.0, -1.0, -1.0,
@@ -166,9 +279,13 @@ namespace sharp {
return image.conv(sharpen);
} else {
// Slow, accurate sharpen in LAB colour space, with control over flat vs jagged areas
VipsInterpretation colourspaceBeforeSharpen = image.interpretation();
if (colourspaceBeforeSharpen == VIPS_INTERPRETATION_RGB) {
colourspaceBeforeSharpen = VIPS_INTERPRETATION_sRGB;
}
return image.sharpen(
VImage::option()->set("radius", radius)->set("m1", flat)->set("m2", jagged)
);
VImage::option()->set("sigma", sigma)->set("m1", flat)->set("m2", jagged)
).colourspace(colourspaceBeforeSharpen);
}
}
@@ -250,4 +367,95 @@ namespace sharp {
return image.hist_find().hist_entropy();
}
/*
Insert a tile cache to prevent over-computation of any previous operations in the pipeline
*/
VImage TileCache(VImage image, double const factor) {
int tile_width;
int tile_height;
int scanline_count;
vips_get_tile_size(image.get_image(), &tile_width, &tile_height, &scanline_count);
double const need_lines = 1.2 * scanline_count / factor;
return image.tilecache(VImage::option()
->set("tile_width", image.width())
->set("tile_height", 10)
->set("max_tiles", static_cast<int>(round(1.0 + need_lines / 10.0)))
->set("access", VIPS_ACCESS_SEQUENTIAL)
->set("threaded", TRUE)
);
}
VImage Threshold(VImage image, double const threshold, bool const thresholdGrayscale) {
if(!thresholdGrayscale) {
return image >= threshold;
}
return image.colourspace(VIPS_INTERPRETATION_B_W) >= threshold;
}
/*
Perform boolean/bitwise operation on image color channels - results in one channel image
*/
VImage Bandbool(VImage image, VipsOperationBoolean const boolean) {
return image.bandbool(boolean);
}
/*
Perform bitwise boolean operation between images
*/
VImage Boolean(VImage image, VImage imageR, VipsOperationBoolean const boolean) {
return image.boolean(imageR, boolean);
}
VImage Trim(VImage image, int const tolerance) {
using sharp::MaximumImageAlpha;
// An equivalent of ImageMagick's -trim in C++ ... automatically remove
// "boring" image edges.
// We use .project to sum the rows and columns of a 0/255 mask image, the first
// non-zero row or column is the object edge. We make the mask image with an
// amount-different-from-background image plus a threshold.
// find the value of the pixel at (0, 0) ... we will search for all pixels
// significantly different from this
std::vector<double> background = image(0, 0);
double const max = MaximumImageAlpha(image.interpretation());
// we need to smooth the image, subtract the background from every pixel, take
// the absolute value of the difference, then threshold
VImage mask = (image.median(3) - background).abs() > (max * tolerance / 100);
// sum mask rows and columns, then search for the first non-zero sum in each
// direction
VImage rows;
VImage columns = mask.project(&rows);
VImage profileLeftV;
VImage profileLeftH = columns.profile(&profileLeftV);
VImage profileRightV;
VImage profileRightH = columns.fliphor().profile(&profileRightV);
VImage profileTopV;
VImage profileTopH = rows.profile(&profileTopV);
VImage profileBottomV;
VImage profileBottomH = rows.flipver().profile(&profileBottomV);
int left = static_cast<int>(floor(profileLeftV.min()));
int right = columns.width() - static_cast<int>(floor(profileRightV.min()));
int top = static_cast<int>(floor(profileTopH.min()));
int bottom = rows.height() - static_cast<int>(floor(profileBottomH.min()));
int width = right - left;
int height = bottom - top;
if(width <= 0 || height <= 0) {
throw VError("Unexpected error while trimming. Try to lower the tolerance");
}
// and now crop the original image
return image.extract_area(left, top, width, height);
}
} // namespace sharp

View File

@@ -2,6 +2,7 @@
#define SRC_OPERATIONS_H_
#include <tuple>
#include <memory>
#include <vips/vips8>
using vips::VImage;
@@ -14,6 +15,27 @@ namespace sharp {
*/
VImage Composite(VImage src, VImage dst, const int gravity);
/*
Alpha composite src over dst with given x and y offsets.
Assumes alpha channels are already premultiplied and will be unpremultiplied after.
*/
VImage Composite(VImage src, VImage dst, const int x, const int y);
/*
Check if the src and dst Images for composition operation are valid
*/
bool IsInputValidForComposition(VImage src, VImage dst);
/*
Given a valid src and dst, returns the composite of the two images
*/
VImage CompositeImage(VImage src, VImage dst);
/*
Cutout src over dst with given gravity.
*/
VImage Cutout(VImage src, VImage dst, const int gravity);
/*
* Stretch luminance to cover full dynamic range.
*/
@@ -25,14 +47,20 @@ namespace sharp {
VImage Gamma(VImage image, double const exponent);
/*
* Gaussian blur. Use sigma of -1 for fast blur.
* Gaussian blur. Use sigma of -1.0 for fast blur.
*/
VImage Blur(VImage image, double const sigma);
/*
* Sharpen flat and jagged areas. Use radius of -1 for fast sharpen.
* Convolution with a kernel.
*/
VImage Sharpen(VImage image, int const radius, double const flat, double const jagged);
VImage Convolve(VImage image, int const width, int const height,
double const scale, double const offset, std::unique_ptr<double[]> const &kernel_v);
/*
* Sharpen flat and jagged areas. Use sigma of -1.0 for fast sharpen.
*/
VImage Sharpen(VImage image, double const sigma, double const flat, double const jagged);
/*
Calculate crop area based on image entropy
@@ -44,6 +72,31 @@ namespace sharp {
*/
double Entropy(VImage image);
/*
Insert a tile cache to prevent over-computation of any previous operations in the pipeline
*/
VImage TileCache(VImage image, double const factor);
/*
Threshold an image
*/
VImage Threshold(VImage image, double const threshold, bool const thresholdColor);
/*
Perform boolean/bitwise operation on image color channels - results in one channel image
*/
VImage Bandbool(VImage image, VipsOperationBoolean const boolean);
/*
Perform bitwise boolean operation between images
*/
VImage Boolean(VImage image, VImage imageR, VipsOperationBoolean const boolean);
/*
Trim an image
*/
VImage Trim(VImage image, int const tolerance);
} // namespace sharp
#endif // SRC_OPERATIONS_H_

View File

@@ -2,6 +2,8 @@
#include <cmath>
#include <tuple>
#include <utility>
#include <memory>
#include <numeric>
#include <vips/vips8>
@@ -45,11 +47,18 @@ using vips::VOption;
using vips::VError;
using sharp::Composite;
using sharp::Cutout;
using sharp::Normalize;
using sharp::Gamma;
using sharp::Blur;
using sharp::Convolve;
using sharp::Sharpen;
using sharp::EntropyCrop;
using sharp::TileCache;
using sharp::Threshold;
using sharp::Bandbool;
using sharp::Boolean;
using sharp::Trim;
using sharp::ImageType;
using sharp::ImageTypeId;
@@ -66,23 +75,30 @@ using sharp::IsWebp;
using sharp::IsTiff;
using sharp::IsDz;
using sharp::IsDzZip;
using sharp::IsV;
using sharp::FreeCallback;
using sharp::CalculateCrop;
using sharp::Is16Bit;
using sharp::MaximumImageAlpha;
using sharp::GetBooleanOperation;
using sharp::counterProcess;
using sharp::counterQueue;
class PipelineWorker : public AsyncWorker {
public:
PipelineWorker(Callback *callback, PipelineBaton *baton, Callback *queueListener,
const Local<Object> &bufferIn, const Local<Object> &overlayBufferIn) :
AsyncWorker(callback), baton(baton), queueListener(queueListener) {
if (baton->bufferInLength > 0) {
SaveToPersistent("bufferIn", bufferIn);
PipelineWorker(
Callback *callback, PipelineBaton *baton, Callback *queueListener,
std::vector<Local<Object>> const buffersToPersist
) : AsyncWorker(callback), baton(baton), queueListener(queueListener), buffersToPersist(buffersToPersist) {
// Protect Buffer objects from GC, keyed on index
std::accumulate(buffersToPersist.begin(), buffersToPersist.end(), 0,
[this](uint32_t index, Local<Object> const buffer) -> uint32_t {
SaveToPersistent(index, buffer);
return index + 1;
}
if (baton->overlayBufferInLength > 0) {
SaveToPersistent("overlayBufferIn", overlayBufferIn);
}
}
);
}
~PipelineWorker() {}
/*
@@ -94,8 +110,8 @@ class PipelineWorker : public AsyncWorker {
// Increment processing task counter
g_atomic_int_inc(&counterProcess);
// Latest v2 sRGB ICC profile
std::string srgbProfile = baton->iccProfilePath + "sRGB_IEC61966-2-1_black_scaled.icc";
// Default sRGB ICC profile from https://packages.debian.org/sid/all/icc-profiles-free/filelist
std::string srgbProfile = baton->iccProfilePath + "sRGB.icc";
// Input
ImageType inputImageType = ImageType::UNKNOWN;
@@ -123,11 +139,16 @@ class PipelineWorker : public AsyncWorker {
if (inputImageType != ImageType::UNKNOWN) {
try {
VOption *option = VImage::option()->set("access", baton->accessMethod);
if (inputImageType == ImageType::SVG || inputImageType == ImageType::PDF) {
option->set("dpi", static_cast<double>(baton->density));
}
if (inputImageType == ImageType::MAGICK) {
option->set("density", std::to_string(baton->density).data());
}
image = VImage::new_from_buffer(baton->bufferIn, baton->bufferInLength, nullptr, option);
if (inputImageType == ImageType::MAGICK) {
if (inputImageType == ImageType::SVG ||
inputImageType == ImageType::PDF ||
inputImageType == ImageType::MAGICK) {
SetDensity(image, baton->density);
}
} catch (...) {
@@ -144,11 +165,16 @@ class PipelineWorker : public AsyncWorker {
if (inputImageType != ImageType::UNKNOWN) {
try {
VOption *option = VImage::option()->set("access", baton->accessMethod);
if (inputImageType == ImageType::SVG || inputImageType == ImageType::PDF) {
option->set("dpi", static_cast<double>(baton->density));
}
if (inputImageType == ImageType::MAGICK) {
option->set("density", std::to_string(baton->density).data());
}
image = VImage::new_from_file(baton->fileIn.data(), option);
if (inputImageType == ImageType::MAGICK) {
if (inputImageType == ImageType::SVG ||
inputImageType == ImageType::PDF ||
inputImageType == ImageType::MAGICK) {
SetDensity(image, baton->density);
}
} catch (...) {
@@ -191,6 +217,11 @@ class PipelineWorker : public AsyncWorker {
RemoveExifOrientation(image);
}
// Trim
if(baton->trimTolerance != 0) {
image = Trim(image, baton->trimTolerance);
}
// Pre extraction
if (baton->topOffsetPre != -1) {
image = image.extract_area(baton->leftOffsetPre, baton->topOffsetPre, baton->widthPre, baton->heightPre);
@@ -205,41 +236,49 @@ class PipelineWorker : public AsyncWorker {
std::swap(inputWidth, inputHeight);
}
// Get window size of interpolator, used for determining shrink vs affine
VInterpolate interpolator = VInterpolate::new_from_name(baton->interpolator.data());
int interpolatorWindowSize = vips_interpolate_get_window_size(interpolator.get_interpolate());
// Scaling calculations
double xfactor = 1.0;
double yfactor = 1.0;
int targetResizeWidth = baton->width;
int targetResizeHeight = baton->height;
if (baton->width > 0 && baton->height > 0) {
// Fixed width and height
xfactor = static_cast<double>(inputWidth) / static_cast<double>(baton->width);
yfactor = static_cast<double>(inputHeight) / static_cast<double>(baton->height);
switch (baton->canvas) {
case Canvas::CROP:
xfactor = std::min(xfactor, yfactor);
yfactor = xfactor;
if (xfactor < yfactor) {
targetResizeHeight = static_cast<int>(round(static_cast<double>(inputHeight) / xfactor));
yfactor = xfactor;
} else {
targetResizeWidth = static_cast<int>(round(static_cast<double>(inputWidth) / yfactor));
xfactor = yfactor;
}
break;
case Canvas::EMBED:
xfactor = std::max(xfactor, yfactor);
yfactor = xfactor;
if (xfactor > yfactor) {
targetResizeHeight = static_cast<int>(round(static_cast<double>(inputHeight) / xfactor));
yfactor = xfactor;
} else {
targetResizeWidth = static_cast<int>(round(static_cast<double>(inputWidth) / yfactor));
xfactor = yfactor;
}
break;
case Canvas::MAX:
if (xfactor > yfactor) {
baton->height = static_cast<int>(round(static_cast<double>(inputHeight) / xfactor));
targetResizeHeight = baton->height = static_cast<int>(round(static_cast<double>(inputHeight) / xfactor));
yfactor = xfactor;
} else {
baton->width = static_cast<int>(round(static_cast<double>(inputWidth) / yfactor));
targetResizeWidth = baton->width = static_cast<int>(round(static_cast<double>(inputWidth) / yfactor));
xfactor = yfactor;
}
break;
case Canvas::MIN:
if (xfactor < yfactor) {
baton->height = static_cast<int>(round(static_cast<double>(inputHeight) / xfactor));
targetResizeHeight = baton->height = static_cast<int>(round(static_cast<double>(inputHeight) / xfactor));
yfactor = xfactor;
} else {
baton->width = static_cast<int>(round(static_cast<double>(inputWidth) / yfactor));
targetResizeWidth = baton->width = static_cast<int>(round(static_cast<double>(inputWidth) / yfactor));
xfactor = yfactor;
}
break;
@@ -254,21 +293,21 @@ class PipelineWorker : public AsyncWorker {
// Fixed width
xfactor = static_cast<double>(inputWidth) / static_cast<double>(baton->width);
if (baton->canvas == Canvas::IGNORE_ASPECT) {
baton->height = inputHeight;
targetResizeHeight = baton->height = inputHeight;
} else {
// Auto height
yfactor = xfactor;
baton->height = static_cast<int>(round(static_cast<double>(inputHeight) / yfactor));
targetResizeHeight = baton->height = static_cast<int>(round(static_cast<double>(inputHeight) / yfactor));
}
} else if (baton->height > 0) {
// Fixed height
yfactor = static_cast<double>(inputHeight) / static_cast<double>(baton->height);
if (baton->canvas == Canvas::IGNORE_ASPECT) {
baton->width = inputWidth;
targetResizeWidth = baton->width = inputWidth;
} else {
// Auto width
xfactor = yfactor;
baton->width = static_cast<int>(round(static_cast<double>(inputWidth) / xfactor));
targetResizeWidth = baton->width = static_cast<int>(round(static_cast<double>(inputWidth) / xfactor));
}
} else {
// Identity transform
@@ -277,23 +316,23 @@ class PipelineWorker : public AsyncWorker {
}
// Calculate integral box shrink
int xshrink = CalculateShrink(xfactor, interpolatorWindowSize);
int yshrink = CalculateShrink(yfactor, interpolatorWindowSize);
int xshrink = std::max(1, static_cast<int>(floor(xfactor)));
int yshrink = std::max(1, static_cast<int>(floor(yfactor)));
// Calculate residual float affine transformation
double xresidual = CalculateResidual(xshrink, xfactor);
double yresidual = CalculateResidual(yshrink, yfactor);
double xresidual = static_cast<double>(xshrink) / xfactor;
double yresidual = static_cast<double>(yshrink) / yfactor;
// 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) {
xfactor = 1;
yfactor = 1;
xfactor = 1.0;
yfactor = 1.0;
xshrink = 1;
yshrink = 1;
xresidual = 0;
yresidual = 0;
xresidual = 1.0;
yresidual = 1.0;
baton->width = inputWidth;
baton->height = inputHeight;
}
@@ -303,7 +342,8 @@ class PipelineWorker : public AsyncWorker {
// but not when applying gamma correction or pre-resize extract
int shrink_on_load = 1;
if (
xshrink == yshrink && inputImageType == ImageType::JPEG && xshrink >= 2 &&
xshrink == yshrink && xshrink >= 2 &&
(inputImageType == ImageType::JPEG || inputImageType == ImageType::WEBP) &&
baton->gamma == 0 && baton->topOffsetPre == -1
) {
if (xshrink >= 8) {
@@ -324,20 +364,30 @@ class PipelineWorker : public AsyncWorker {
// Recalculate integral shrink and double residual
xfactor = std::max(xfactor, 1.0);
yfactor = std::max(yfactor, 1.0);
xshrink = CalculateShrink(xfactor, interpolatorWindowSize);
yshrink = CalculateShrink(yfactor, interpolatorWindowSize);
xresidual = CalculateResidual(xshrink, xfactor);
yresidual = CalculateResidual(yshrink, yfactor);
xshrink = std::max(1, static_cast<int>(floor(xfactor)));
yshrink = std::max(1, static_cast<int>(floor(yfactor)));
xresidual = static_cast<double>(xshrink) / xfactor;
yresidual = static_cast<double>(yshrink) / yfactor;
// Reload input using shrink-on-load
VOption *option = VImage::option()->set("shrink", shrink_on_load);
if (baton->bufferInLength > 1) {
VipsBlob *blob = vips_blob_new(nullptr, baton->bufferIn, baton->bufferInLength);
image = VImage::jpegload_buffer(blob, VImage::option()->set("shrink", shrink_on_load));
if (inputImageType == ImageType::JPEG) {
// Reload JPEG buffer
image = VImage::jpegload_buffer(blob, option);
} else {
// Reload WebP buffer
image = VImage::webpload_buffer(blob, option);
}
vips_area_unref(reinterpret_cast<VipsArea*>(blob));
} else {
image = VImage::jpegload(
const_cast<char*>((baton->fileIn).data()),
VImage::option()->set("shrink", shrink_on_load)
);
if (inputImageType == ImageType::JPEG) {
// Reload JPEG file
image = VImage::jpegload(const_cast<char*>((baton->fileIn).data()), option);
} else {
// Reload WebP file
image = VImage::webpload(const_cast<char*>((baton->fileIn).data()), option);
}
}
}
@@ -353,8 +403,8 @@ class PipelineWorker : public AsyncWorker {
// Ignore failure of embedded profile
}
} else if (image.interpretation() == VIPS_INTERPRETATION_CMYK) {
// Convert to sRGB using default "USWebCoatedSWOP" CMYK profile
std::string cmykProfile = baton->iccProfilePath + "USWebCoatedSWOP.icc";
// Convert to sRGB using default CMYK profile from http://www.argyllcms.com/cmyk.icm
std::string cmykProfile = baton->iccProfilePath + "cmyk.icm";
image = image.icc_transform(const_cast<char*>(srgbProfile.data()), VImage::option()
->set("input_profile", cmykProfile.data())
->set("intent", VIPS_INTENT_PERCEPTUAL)
@@ -362,13 +412,12 @@ class PipelineWorker : public AsyncWorker {
}
// Calculate maximum alpha value based on input image pixel depth
bool is16Bit = (image.format() == VIPS_FORMAT_USHORT);
double maxAlpha = is16Bit ? 65535.0 : 255.0;
double const maxAlpha = MaximumImageAlpha(image.interpretation());
// Flatten image to remove alpha channel
if (baton->flatten && HasAlpha(image)) {
// Scale up 8-bit values to match 16-bit input image
double multiplier = (image.interpretation() == VIPS_INTERPRETATION_RGB16) ? 256.0 : 1.0;
double const multiplier = Is16Bit(image.interpretation()) ? 256.0 : 1.0;
// Background colour
std::vector<double> background {
baton->background[0] * multiplier,
@@ -397,7 +446,12 @@ class PipelineWorker : public AsyncWorker {
}
if (xshrink > 1 || yshrink > 1) {
image = image.shrink(xshrink, yshrink);
if (yshrink > 1) {
image = image.shrinkv(yshrink);
}
if (xshrink > 1) {
image = image.shrinkh(xshrink);
}
// Recalculate residual float based on dimensions of required vs shrunk images
int shrunkWidth = image.width();
int shrunkHeight = image.height();
@@ -406,37 +460,32 @@ class PipelineWorker : public AsyncWorker {
// Swap input output width and height when rotating by 90 or 270 degrees
std::swap(shrunkWidth, shrunkHeight);
}
xresidual = static_cast<double>(baton->width) / static_cast<double>(shrunkWidth);
yresidual = static_cast<double>(baton->height) / static_cast<double>(shrunkHeight);
if (baton->canvas == Canvas::EMBED) {
xresidual = std::min(xresidual, yresidual);
yresidual = xresidual;
} else if (baton->canvas == Canvas::IGNORE_ASPECT) {
if (!baton->rotateBeforePreExtract &&
(rotation == VIPS_ANGLE_D90 || rotation == VIPS_ANGLE_D270)) {
std::swap(xresidual, yresidual);
}
} else {
xresidual = std::max(xresidual, yresidual);
yresidual = xresidual;
xresidual = static_cast<double>(targetResizeWidth) / static_cast<double>(shrunkWidth);
yresidual = static_cast<double>(targetResizeHeight) / static_cast<double>(shrunkHeight);
if (
!baton->rotateBeforePreExtract &&
(rotation == VIPS_ANGLE_D90 || rotation == VIPS_ANGLE_D270)
) {
std::swap(xresidual, yresidual);
}
}
// Ensure image has an alpha channel when there is an overlay
bool hasOverlay = baton->overlayBufferInLength > 0 || !baton->overlayFileIn.empty();
if (hasOverlay && !HasAlpha(image)) {
double multiplier = (image.interpretation() == VIPS_INTERPRETATION_RGB16) ? 256.0 : 1.0;
double const multiplier = Is16Bit(image.interpretation()) ? 256.0 : 1.0;
image = image.bandjoin(
VImage::new_matrix(image.width(), image.height()).new_from_image(255 * multiplier)
);
}
bool shouldAffineTransform = xresidual != 0.0 || yresidual != 0.0;
bool shouldAffineTransform = xresidual != 1.0 || yresidual != 1.0;
bool shouldBlur = baton->blurSigma != 0.0;
bool shouldSharpen = baton->sharpenRadius != 0;
bool shouldThreshold = baton->threshold != 0;
bool shouldConv = baton->convKernelWidth * baton->convKernelHeight > 0;
bool shouldSharpen = baton->sharpenSigma != 0.0;
bool shouldCutout = baton->overlayCutout;
bool shouldPremultiplyAlpha = HasAlpha(image) &&
(shouldAffineTransform || shouldBlur || shouldSharpen || hasOverlay);
(shouldAffineTransform || shouldBlur || shouldConv || shouldSharpen || (hasOverlay && !shouldCutout));
// Premultiply image alpha channel before all transformations to avoid
// dark fringing around bright pixels
@@ -445,31 +494,41 @@ class PipelineWorker : public AsyncWorker {
image = image.premultiply(VImage::option()->set("max_alpha", maxAlpha));
}
// Use affine transformation with the remaining float part
// Use affine increase or kernel reduce with the remaining float part
if (shouldAffineTransform) {
// Use average of x and y residuals to compute sigma for Gaussian blur
double residual = (xresidual + yresidual) / 2.0;
// Apply Gaussian blur before large affine reductions
if (residual < 1.0) {
// Calculate standard deviation
double sigma = ((1.0 / residual) - 0.4) / 3.0;
if (sigma >= 0.3) {
// Sequential input requires a small linecache before use of convolution
if (baton->accessMethod == VIPS_ACCESS_SEQUENTIAL) {
image = image.linecache(VImage::option()
->set("access", VIPS_ACCESS_SEQUENTIAL)
->set("tile_height", 1)
->set("threaded", TRUE)
);
}
// Apply Gaussian blur
image = image.gaussblur(sigma);
// Insert tile cache to prevent over-computation of previous operations
if (baton->accessMethod == VIPS_ACCESS_SEQUENTIAL) {
image = TileCache(image, yresidual);
}
// Perform kernel-based reduction
if (yresidual < 1.0 || xresidual < 1.0) {
VipsKernel kernel = static_cast<VipsKernel>(
vips_enum_from_nick(nullptr, VIPS_TYPE_KERNEL, baton->kernel.data())
);
if (kernel != VIPS_KERNEL_CUBIC && kernel != VIPS_KERNEL_LANCZOS2 && kernel != VIPS_KERNEL_LANCZOS3) {
throw VError("Unknown kernel");
}
if (yresidual < 1.0) {
image = image.reducev(1.0 / yresidual, VImage::option()->set("kernel", kernel));
}
if (xresidual < 1.0) {
image = image.reduceh(1.0 / xresidual, VImage::option()->set("kernel", kernel));
}
}
// Perform affine enlargement
if (yresidual > 1.0 || xresidual > 1.0) {
VInterpolate interpolator = VInterpolate::new_from_name(baton->interpolator.data());
if (yresidual > 1.0) {
image = image.affine({1.0, 0.0, 0.0, yresidual}, VImage::option()
->set("interpolate", interpolator)
);
}
if (xresidual > 1.0) {
image = image.affine({xresidual, 0.0, 0.0, 1.0}, VImage::option()
->set("interpolate", interpolator)
);
}
}
// Perform affine transformation
image = image.affine({xresidual, 0.0, 0.0, yresidual}, VImage::option()
->set("interpolate", interpolator)
);
}
// Rotate
@@ -494,7 +553,7 @@ class PipelineWorker : public AsyncWorker {
if (image.width() != baton->width || image.height() != baton->height) {
if (baton->canvas == Canvas::EMBED) {
// Scale up 8-bit values to match 16-bit input image
double multiplier = (image.interpretation() == VIPS_INTERPRETATION_RGB16) ? 256.0 : 1.0;
double const multiplier = Is16Bit(image.interpretation()) ? 256.0 : 1.0;
// Create background colour
std::vector<double> background;
if (image.bands() > 2) {
@@ -557,7 +616,7 @@ class PipelineWorker : public AsyncWorker {
// Extend edges
if (baton->extendTop > 0 || baton->extendBottom > 0 || baton->extendLeft > 0 || baton->extendRight > 0) {
// Scale up 8-bit values to match 16-bit input image
const double multiplier = (image.interpretation() == VIPS_INTERPRETATION_RGB16) ? 256.0 : 1.0;
double const multiplier = Is16Bit(image.interpretation()) ? 256.0 : 1.0;
// Create background colour
std::vector<double> background {
baton->background[0] * multiplier,
@@ -565,19 +624,26 @@ class PipelineWorker : public AsyncWorker {
baton->background[2] * multiplier
};
// Add alpha channel to background colour
if (HasAlpha(image)) {
if (baton->background[3] < 255.0 || HasAlpha(image)) {
background.push_back(baton->background[3] * multiplier);
}
// Add non-transparent alpha channel, if required
if (baton->background[3] < 255.0 && !HasAlpha(image)) {
image = image.bandjoin(
VImage::new_matrix(image.width(), image.height()).new_from_image(255 * multiplier)
);
}
// Embed
baton->width = image.width() + baton->extendLeft + baton->extendRight;
baton->height = image.height() + baton->extendTop + baton->extendBottom;
image = image.embed(baton->extendLeft, baton->extendTop, baton->width, baton->height,
VImage::option()->set("extend", VIPS_EXTEND_BACKGROUND)->set("background", background));
}
// Threshold - must happen before blurring, due to the utility of blurring after thresholding
if (shouldThreshold) {
image = image.colourspace(VIPS_INTERPRETATION_B_W) >= baton->threshold;
if (baton->threshold != 0) {
image = Threshold(image, baton->threshold, baton->thresholdGrayscale);
}
// Blur
@@ -585,9 +651,18 @@ class PipelineWorker : public AsyncWorker {
image = Blur(image, baton->blurSigma);
}
// Convolve
if (shouldConv) {
image = Convolve(image,
baton->convKernelWidth, baton->convKernelHeight,
baton->convKernelScale, baton->convKernelOffset,
baton->convKernel
);
}
// Sharpen
if (shouldSharpen) {
image = Sharpen(image, baton->sharpenRadius, baton->sharpenFlat, baton->sharpenJagged);
image = Sharpen(image, baton->sharpenSigma, baton->sharpenFlat, baton->sharpenJagged);
}
// Composite with overlay, if present
@@ -624,17 +699,65 @@ class PipelineWorker : public AsyncWorker {
if (overlayImageType == ImageType::UNKNOWN) {
return Error();
}
// Ensure overlay is premultiplied sRGB
overlayImage = overlayImage.colourspace(VIPS_INTERPRETATION_sRGB).premultiply();
// Composite images with given gravity
image = Composite(overlayImage, image, baton->overlayGravity);
// Check if overlay is tiled
if (baton->overlayTile) {
int overlayImageWidth = overlayImage.width();
int overlayImageHeight = overlayImage.height();
int across = 0;
int down = 0;
// use gravity in ovelay
if(overlayImageWidth <= baton->width) {
across = static_cast<int>(ceil(static_cast<double>(image.width()) / overlayImageWidth));
}
if(overlayImageHeight <= baton->height) {
down = static_cast<int>(ceil(static_cast<double>(image.height()) / overlayImageHeight));
}
if(across != 0 || down != 0) {
int left;
int top;
overlayImage = overlayImage.replicate(across, down);
if(baton->overlayXOffset >= 0 && baton->overlayYOffset >= 0) {
// the overlayX/YOffsets will now be used to CalculateCrop for extract_area
std::tie(left, top) = CalculateCrop(
overlayImage.width(), overlayImage.height(), image.width(), image.height(),
baton->overlayXOffset, baton->overlayYOffset
);
} else {
// the overlayGravity will now be used to CalculateCrop for extract_area
std::tie(left, top) = CalculateCrop(
overlayImage.width(), overlayImage.height(), image.width(), image.height(), baton->overlayGravity
);
}
overlayImage = overlayImage.extract_area(
left, top, image.width(), image.height()
);
}
// the overlayGravity was used for extract_area, therefore set it back to its default value of 0
baton->overlayGravity = 0;
}
if(shouldCutout) {
// 'cut out' the image, premultiplication is not required
image = Cutout(overlayImage, image, baton->overlayGravity);
} else {
// Ensure overlay is premultiplied sRGB
overlayImage = overlayImage.colourspace(VIPS_INTERPRETATION_sRGB).premultiply();
if(baton->overlayXOffset >= 0 && baton->overlayYOffset >= 0) {
// Composite images with given offsets
image = Composite(overlayImage, image, baton->overlayXOffset, baton->overlayYOffset);
} else {
// Composite images with given gravity
image = Composite(overlayImage, image, baton->overlayGravity);
}
}
}
// Reverse premultiplication after all transformations:
if (shouldPremultiplyAlpha) {
image = image.unpremultiply(VImage::option()->set("max_alpha", maxAlpha));
// Cast pixel values to integer
if (is16Bit) {
if (Is16Bit(image.interpretation())) {
image = image.cast(VIPS_FORMAT_USHORT);
} else {
image = image.cast(VIPS_FORMAT_UCHAR);
@@ -652,7 +775,7 @@ class PipelineWorker : public AsyncWorker {
}
// Convert image to sRGB, if not already
if (image.interpretation() == VIPS_INTERPRETATION_RGB16) {
if (Is16Bit(image.interpretation())) {
image = image.cast(VIPS_FORMAT_USHORT);
}
if (image.interpretation() != VIPS_INTERPRETATION_sRGB) {
@@ -665,6 +788,58 @@ class PipelineWorker : public AsyncWorker {
}
}
// Apply bitwise boolean operation between images
if (baton->booleanOp != VIPS_OPERATION_BOOLEAN_LAST &&
(baton->booleanBufferInLength > 0 || !baton->booleanFileIn.empty())) {
VImage booleanImage;
ImageType booleanImageType = ImageType::UNKNOWN;
if (baton->booleanBufferInLength > 0) {
// Buffer input for boolean operation
booleanImageType = DetermineImageType(baton->booleanBufferIn, baton->booleanBufferInLength);
if (booleanImageType != ImageType::UNKNOWN) {
try {
booleanImage = VImage::new_from_buffer(baton->booleanBufferIn, baton->booleanBufferInLength,
nullptr, VImage::option()->set("access", baton->accessMethod));
} catch (...) {
(baton->err).append("Boolean operation buffer has corrupt header");
booleanImageType = ImageType::UNKNOWN;
}
} else {
(baton->err).append("Boolean operation buffer contains unsupported image format");
}
} else if (!baton->booleanFileIn.empty()) {
// File input for boolean operation
booleanImageType = DetermineImageType(baton->booleanFileIn.data());
if (booleanImageType != ImageType::UNKNOWN) {
try {
booleanImage = VImage::new_from_file(baton->booleanFileIn.data(),
VImage::option()->set("access", baton->accessMethod));
} catch (...) {
(baton->err).append("Boolean operation file has corrupt header");
}
}
}
if (booleanImageType == ImageType::UNKNOWN) {
return Error();
}
// Apply the boolean operation
image = Boolean(image, booleanImage, baton->booleanOp);
}
// Apply per-channel Bandbool bitwise operations after all other operations
if (baton->bandBoolOp >= VIPS_OPERATION_BOOLEAN_AND && baton->bandBoolOp < VIPS_OPERATION_BOOLEAN_LAST) {
image = Bandbool(image, baton->bandBoolOp);
}
// Extract an image channel (aka vips band)
if(baton->extractChannel > -1) {
if(baton->extractChannel >= image.bands()) {
(baton->err).append("Cannot extract channel from image. Too few channels in image.");
return Error();
}
image = image.extract_band(baton->extractChannel);
}
// Override EXIF Orientation tag
if (baton->withMetadata && baton->withMetadataOrientation != -1) {
SetExifOrientation(image, baton->withMetadataOrientation);
@@ -755,7 +930,9 @@ class PipelineWorker : public AsyncWorker {
bool isTiff = IsTiff(baton->fileOut);
bool isDz = IsDz(baton->fileOut);
bool isDzZip = IsDzZip(baton->fileOut);
bool matchInput = baton->formatOut == "input" && !(isJpeg || isPng || isWebp || isTiff || isDz || isDzZip);
bool isV = IsV(baton->fileOut);
bool matchInput = baton->formatOut == "input" &&
!(isJpeg || isPng || isWebp || isTiff || isDz || isDzZip || isV);
if (baton->formatOut == "jpeg" || isJpeg || (matchInput && inputImageType == ImageType::JPEG)) {
// Write JPEG to file
image.jpegsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
@@ -809,6 +986,12 @@ class PipelineWorker : public AsyncWorker {
->set("layout", baton->tileLayout)
);
baton->formatOut = "dz";
} else if (baton->formatOut == "v" || isV || (matchInput && inputImageType == ImageType::VIPS)) {
// Write V to file
image.vipssave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata)
);
baton->formatOut = "v";
} else {
// Unsupported output format
(baton->err).append("Unsupported output format " + baton->fileOut);
@@ -866,12 +1049,12 @@ class PipelineWorker : public AsyncWorker {
}
// Dispose of Persistent wrapper around input Buffers so they can be garbage collected
if (baton->bufferInLength > 0) {
GetFromPersistent("bufferIn");
}
if (baton->overlayBufferInLength > 0) {
GetFromPersistent("overlayBufferIn");
}
std::accumulate(buffersToPersist.begin(), buffersToPersist.end(), 0,
[this](uint32_t index, Local<Object> const buffer) -> uint32_t {
GetFromPersistent(index);
return index + 1;
}
);
delete baton;
// Decrement processing task counter
@@ -887,6 +1070,7 @@ class PipelineWorker : public AsyncWorker {
private:
PipelineBaton *baton;
Callback *queueListener;
std::vector<Local<Object>> buffersToPersist;
/*
Calculate the angle of rotation and need-to-flip for the output image.
@@ -922,30 +1106,6 @@ class PipelineWorker : public AsyncWorker {
return std::make_tuple(rotate, flip, flop);
}
/*
Calculate integral shrink given factor and interpolator window size
*/
int CalculateShrink(double factor, int interpolatorWindowSize) {
int shrink = 1;
if (factor >= 2.0 && trunc(factor) != factor && interpolatorWindowSize > 3) {
// Shrink less, affine more with interpolators that use at least 4x4 pixel window, e.g. bicubic
shrink = static_cast<int>(floor(factor * 3.0 / interpolatorWindowSize));
} else {
shrink = static_cast<int>(floor(factor));
}
if (shrink < 1) {
shrink = 1;
}
return shrink;
}
/*
Calculate residual given shrink and factor
*/
double CalculateResidual(int shrink, double factor) {
return static_cast<double>(shrink) / factor;
}
/*
Clear all thread-local data.
*/
@@ -974,6 +1134,9 @@ NAN_METHOD(pipeline) {
PipelineBaton *baton = new PipelineBaton;
Local<Object> options = info[0].As<Object>();
// Input Buffers must not undergo GC compaction during processing
std::vector<Local<Object>> buffersToPersist;
// Input filename
baton->fileIn = attrAsStr(options, "fileIn");
baton->accessMethod = attrAs<bool>(options, "sequentialRead") ?
@@ -984,6 +1147,7 @@ NAN_METHOD(pipeline) {
bufferIn = Get(options, New("bufferIn").ToLocalChecked()).ToLocalChecked().As<Object>();
baton->bufferInLength = node::Buffer::Length(bufferIn);
baton->bufferIn = node::Buffer::Data(bufferIn);
buffersToPersist.push_back(bufferIn);
}
// ICC profile to use when input CMYK image has no embedded profile
baton->iccProfilePath = attrAsStr(options, "iccProfilePath");
@@ -1032,20 +1196,40 @@ NAN_METHOD(pipeline) {
overlayBufferIn = Get(options, New("overlayBufferIn").ToLocalChecked()).ToLocalChecked().As<Object>();
baton->overlayBufferInLength = node::Buffer::Length(overlayBufferIn);
baton->overlayBufferIn = node::Buffer::Data(overlayBufferIn);
buffersToPersist.push_back(overlayBufferIn);
}
baton->overlayGravity = attrAs<int32_t>(options, "overlayGravity");
baton->overlayXOffset = attrAs<int32_t>(options, "overlayXOffset");
baton->overlayYOffset = attrAs<int32_t>(options, "overlayYOffset");
baton->overlayTile = attrAs<bool>(options, "overlayTile");
baton->overlayCutout = attrAs<bool>(options, "overlayCutout");
// Boolean options
baton->booleanFileIn = attrAsStr(options, "booleanFileIn");
Local<Object> booleanBufferIn;
if (node::Buffer::HasInstance(Get(options, New("booleanBufferIn").ToLocalChecked()).ToLocalChecked())) {
booleanBufferIn = Get(options, New("booleanBufferIn").ToLocalChecked()).ToLocalChecked().As<Object>();
baton->booleanBufferInLength = node::Buffer::Length(booleanBufferIn);
baton->booleanBufferIn = node::Buffer::Data(booleanBufferIn);
buffersToPersist.push_back(booleanBufferIn);
}
// Resize options
baton->withoutEnlargement = attrAs<bool>(options, "withoutEnlargement");
baton->crop = attrAs<int32_t>(options, "crop");
baton->kernel = attrAsStr(options, "kernel");
baton->interpolator = attrAsStr(options, "interpolator");
// Operators
baton->flatten = attrAs<bool>(options, "flatten");
baton->negate = attrAs<bool>(options, "negate");
baton->blurSigma = attrAs<double>(options, "blurSigma");
baton->sharpenRadius = attrAs<int32_t>(options, "sharpenRadius");
baton->sharpenSigma = attrAs<double>(options, "sharpenSigma");
baton->sharpenFlat = attrAs<double>(options, "sharpenFlat");
baton->sharpenJagged = attrAs<double>(options, "sharpenJagged");
baton->threshold = attrAs<int32_t>(options, "threshold");
baton->thresholdGrayscale = attrAs<bool>(options, "thresholdGrayscale");
baton->trimTolerance = attrAs<int32_t>(options, "trimTolerance");
if(baton->accessMethod == VIPS_ACCESS_SEQUENTIAL && baton->trimTolerance != 0) {
baton->accessMethod = VIPS_ACCESS_RANDOM;
}
baton->gamma = attrAs<double>(options, "gamma");
baton->greyscale = attrAs<bool>(options, "greyscale");
baton->normalize = attrAs<bool>(options, "normalize");
@@ -1057,6 +1241,7 @@ NAN_METHOD(pipeline) {
baton->extendBottom = attrAs<int32_t>(options, "extendBottom");
baton->extendLeft = attrAs<int32_t>(options, "extendLeft");
baton->extendRight = attrAs<int32_t>(options, "extendRight");
baton->extractChannel = attrAs<int32_t>(options, "extractChannel");
// Output options
baton->progressive = attrAs<bool>(options, "progressive");
baton->quality = attrAs<int32_t>(options, "quality");
@@ -1088,6 +1273,26 @@ NAN_METHOD(pipeline) {
} else {
baton->tileLayout = VIPS_FOREIGN_DZ_LAYOUT_DZ;
}
// Convolution Kernel
if(Has(options, New("convKernel").ToLocalChecked()).FromJust()) {
Local<Object> kernel = Get(options, New("convKernel").ToLocalChecked()).ToLocalChecked().As<Object>();
baton->convKernelWidth = attrAs<uint32_t>(kernel, "width");
baton->convKernelHeight = attrAs<uint32_t>(kernel, "height");
baton->convKernelScale = attrAs<double>(kernel, "scale");
baton->convKernelOffset = attrAs<double>(kernel, "offset");
size_t const kernelSize = static_cast<size_t>(baton->convKernelWidth * baton->convKernelHeight);
baton->convKernel = std::unique_ptr<double[]>(new double[kernelSize]);
Local<Array> kdata = Get(kernel, New("kernel").ToLocalChecked()).ToLocalChecked().As<Array>();
for(unsigned int i = 0; i < kernelSize; i++) {
baton->convKernel[i] = To<double>(Get(kdata, i).ToLocalChecked()).FromJust();
}
}
// Bandbool operation
baton->bandBoolOp = GetBooleanOperation(attrAsStr(options, "bandBoolOp"));
// Boolean operation
baton->booleanOp = GetBooleanOperation(attrAsStr(options, "booleanOp"));
// Function to notify of queue length changes
Callback *queueListener = new Callback(
@@ -1096,7 +1301,7 @@ NAN_METHOD(pipeline) {
// Join queue for worker thread
Callback *callback = new Callback(info[1].As<Function>());
AsyncQueueWorker(new PipelineWorker(callback, baton, queueListener, bufferIn, overlayBufferIn));
AsyncQueueWorker(new PipelineWorker(callback, baton, queueListener, buffersToPersist));
// Increment queued task counter
g_atomic_int_inc(&counterQueue);

View File

@@ -1,6 +1,8 @@
#ifndef SRC_PIPELINE_H_
#define SRC_PIPELINE_H_
#include <memory>
#include <vips/vips8>
#include "nan.h"
@@ -33,6 +35,13 @@ struct PipelineBaton {
char *overlayBufferIn;
size_t overlayBufferInLength;
int overlayGravity;
int overlayXOffset;
int overlayYOffset;
bool overlayTile;
bool overlayCutout;
std::string booleanFileIn;
char *booleanBufferIn;
size_t booleanBufferInLength;
int topOffsetPre;
int leftOffsetPre;
int widthPre;
@@ -46,15 +55,18 @@ struct PipelineBaton {
int channels;
Canvas canvas;
int crop;
std::string kernel;
std::string interpolator;
double background[4];
bool flatten;
bool negate;
double blurSigma;
int sharpenRadius;
double sharpenSigma;
double sharpenFlat;
double sharpenJagged;
int threshold;
bool thresholdGrayscale;
int trimTolerance;
double gamma;
bool greyscale;
bool normalize;
@@ -79,6 +91,14 @@ struct PipelineBaton {
std::string err;
bool withMetadata;
int withMetadataOrientation;
std::unique_ptr<double[]> convKernel;
int convKernelWidth;
int convKernelHeight;
double convKernelScale;
double convKernelOffset;
VipsOperationBoolean bandBoolOp;
VipsOperationBoolean booleanOp;
int extractChannel;
int tileSize;
int tileOverlap;
VipsForeignDzContainer tileContainer;
@@ -96,6 +116,11 @@ struct PipelineBaton {
bufferOutLength(0),
overlayBufferInLength(0),
overlayGravity(0),
overlayXOffset(-1),
overlayYOffset(-1),
overlayTile(false),
overlayCutout(false),
booleanBufferInLength(0),
topOffsetPre(-1),
topOffsetPost(-1),
channels(0),
@@ -104,10 +129,12 @@ struct PipelineBaton {
flatten(false),
negate(false),
blurSigma(0.0),
sharpenRadius(0),
sharpenSigma(0.0),
sharpenFlat(1.0),
sharpenJagged(2.0),
threshold(0),
thresholdGrayscale(true),
trimTolerance(0),
gamma(0.0),
greyscale(false),
normalize(false),
@@ -129,6 +156,13 @@ struct PipelineBaton {
optimiseScans(false),
withMetadata(false),
withMetadataOrientation(-1),
convKernelWidth(0),
convKernelHeight(0),
convKernelScale(0.0),
convKernelOffset(0.0),
bandBoolOp(VIPS_OPERATION_BOOLEAN_LAST),
booleanOp(VIPS_OPERATION_BOOLEAN_LAST),
extractChannel(-1),
tileSize(256),
tileOverlap(0),
tileContainer(VIPS_FOREIGN_DZ_CONTAINER_FS),

View File

@@ -138,7 +138,9 @@ NAN_METHOD(format) {
// 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"}) {
for (std::string f : {
"jpeg", "png", "webp", "tiff", "magick", "openslide", "dz", "ppm", "fits", "gif", "svg", "pdf", "v"
}) {
// Input
Local<Boolean> hasInputFile =
New<Boolean>(vips_type_find("VipsOperation", (f + "load").c_str()));

View File

@@ -10,11 +10,11 @@
"devDependencies": {
"async": "^1.5.2",
"benchmark": "^2.1.0",
"gm": "^1.21.0",
"gm": "^1.22.0",
"imagemagick": "^0.1.3",
"imagemagick-native": "^1.9.2",
"jimp": "^0.2.20",
"lwip": "^0.0.8",
"jimp": "^0.2.24",
"lwip": "^0.0.9",
"semver": "^5.1.0"
},
"license": "Apache-2.0",

View File

@@ -30,18 +30,15 @@ var fixtures = require('../fixtures');
var width = 720;
var height = 480;
var magickFilterBilinear = 'Triangle';
var magickFilterBicubic = 'Lanczos';
// Disable libvips cache to ensure tests are as fair as they can be
sharp.cache(false);
// Enable use of SIMD
sharp.simd(true);
async.series({
'jpeg-linear': function(callback) {
'jpeg': function(callback) {
var inputJpgBuffer = fs.readFileSync(fixtures.inputJpg);
var jpegSuite = new Benchmark.Suite('jpeg-linear');
var jpegSuite = new Benchmark.Suite('jpeg');
// jimp
jpegSuite.add('jimp-buffer-buffer', {
defer: true,
@@ -93,7 +90,7 @@ async.series({
if (err) {
throw err;
}
image.resize(width, height, 'linear', function (err, image) {
image.resize(width, height, 'lanczos', function (err, image) {
if (err) {
throw err;
}
@@ -113,7 +110,7 @@ async.series({
if (err) {
throw err;
}
image.resize(width, height, 'linear', function (err, image) {
image.resize(width, height, 'lanczos', function (err, image) {
if (err) {
throw err;
}
@@ -140,7 +137,7 @@ async.series({
width: width,
height: height,
format: 'jpg',
filter: magickFilterBilinear
filter: 'Lanczos'
}, function(err) {
if (err) {
throw err;
@@ -161,7 +158,7 @@ async.series({
width: width,
height: height,
format: 'JPEG',
filter: magickFilterBilinear
filter: 'Lanczos'
}, function (err, buffer) {
if (err) {
throw err;
@@ -179,7 +176,7 @@ async.series({
fn: function(deferred) {
gm(inputJpgBuffer)
.resize(width, height)
.filter(magickFilterBilinear)
.filter('Lanczos')
.quality(80)
.write(fixtures.outputJpg, function (err) {
if (err) {
@@ -194,7 +191,7 @@ async.series({
fn: function(deferred) {
gm(inputJpgBuffer)
.resize(width, height)
.filter(magickFilterBilinear)
.filter('Lanczos')
.quality(80)
.toBuffer(function (err, buffer) {
if (err) {
@@ -210,7 +207,7 @@ async.series({
fn: function(deferred) {
gm(fixtures.inputJpg)
.resize(width, height)
.filter(magickFilterBilinear)
.filter('Lanczos')
.quality(80)
.write(fixtures.outputJpg, function (err) {
if (err) {
@@ -225,7 +222,7 @@ async.series({
fn: function(deferred) {
gm(fixtures.inputJpg)
.resize(width, height)
.filter(magickFilterBilinear)
.filter('Lanczos')
.quality(80)
.toBuffer(function (err, buffer) {
if (err) {
@@ -243,7 +240,6 @@ async.series({
fn: function(deferred) {
sharp(inputJpgBuffer)
.resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.toFile(fixtures.outputJpg, function(err) {
if (err) {
throw err;
@@ -257,7 +253,6 @@ async.series({
fn: function(deferred) {
sharp(inputJpgBuffer)
.resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.toBuffer(function(err, buffer) {
if (err) {
throw err;
@@ -272,7 +267,6 @@ async.series({
fn: function(deferred) {
sharp(fixtures.inputJpg)
.resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.toFile(fixtures.outputJpg, function(err) {
if (err) {
throw err;
@@ -290,8 +284,7 @@ async.series({
deferred.resolve();
});
var pipeline = sharp()
.resize(width, height)
.interpolateWith(sharp.interpolator.bilinear);
.resize(width, height);
readable.pipe(pipeline).pipe(writable);
}
}).add('sharp-file-buffer', {
@@ -299,7 +292,6 @@ async.series({
fn: function(deferred) {
sharp(fixtures.inputJpg)
.resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.toBuffer(function(err, buffer) {
if (err) {
throw err;
@@ -314,19 +306,27 @@ async.series({
fn: function(deferred) {
sharp(inputJpgBuffer)
.resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.toBuffer()
.then(function(buffer) {
assert.notStrictEqual(null, buffer);
deferred.resolve();
});
}
}).add('sharp-sharpen-mild', {
}).on('cycle', function(event) {
console.log('jpeg ' + String(event.target));
}).on('complete', function() {
callback(null, this.filter('fastest').map('name'));
}).run();
},
// Effect of applying operations
operations: function(callback) {
var inputJpgBuffer = fs.readFileSync(fixtures.inputJpg);
var operationsSuite = new Benchmark.Suite('operations');
operationsSuite.add('sharp-sharpen-mild', {
defer: true,
fn: function(deferred) {
sharp(inputJpgBuffer)
.resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.sharpen()
.toBuffer(function(err, buffer) {
if (err) {
@@ -342,7 +342,6 @@ async.series({
fn: function(deferred) {
sharp(inputJpgBuffer)
.resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.sharpen(3, 1, 3)
.toBuffer(function(err, buffer) {
if (err) {
@@ -358,7 +357,6 @@ async.series({
fn: function(deferred) {
sharp(inputJpgBuffer)
.resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.blur()
.toBuffer(function(err, buffer) {
if (err) {
@@ -374,7 +372,6 @@ async.series({
fn: function(deferred) {
sharp(inputJpgBuffer)
.resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.blur(3)
.toBuffer(function(err, buffer) {
if (err) {
@@ -390,7 +387,6 @@ async.series({
fn: function(deferred) {
sharp(inputJpgBuffer)
.resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.gamma()
.toBuffer(function(err, buffer) {
if (err) {
@@ -406,7 +402,6 @@ async.series({
fn: function(deferred) {
sharp(inputJpgBuffer)
.resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.normalise()
.toBuffer(function(err, buffer) {
if (err) {
@@ -422,7 +417,6 @@ async.series({
fn: function(deferred) {
sharp(inputJpgBuffer)
.resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.greyscale()
.toBuffer(function(err, buffer) {
if (err) {
@@ -438,7 +432,6 @@ async.series({
fn: function(deferred) {
sharp(inputJpgBuffer)
.resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.gamma()
.greyscale()
.toBuffer(function(err, buffer) {
@@ -455,7 +448,6 @@ async.series({
fn: function(deferred) {
sharp(inputJpgBuffer)
.resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.progressive()
.toBuffer(function(err, buffer) {
if (err) {
@@ -471,7 +463,6 @@ async.series({
fn: function(deferred) {
sharp(inputJpgBuffer)
.resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.withoutChromaSubsampling()
.toBuffer(function(err, buffer) {
if (err) {
@@ -487,7 +478,6 @@ async.series({
fn: function(deferred) {
sharp(inputJpgBuffer)
.rotate(90)
.interpolateWith(sharp.interpolator.bilinear)
.resize(width, height)
.toBuffer(function(err, buffer) {
if (err) {
@@ -503,8 +493,6 @@ async.series({
fn: function(deferred) {
sharp.simd(false);
sharp(inputJpgBuffer)
.rotate(90)
.interpolateWith(sharp.interpolator.bilinear)
.resize(width, height)
.toBuffer(function(err, buffer) {
sharp.simd(true);
@@ -520,9 +508,8 @@ async.series({
defer: true,
fn: function(deferred) {
sharp(inputJpgBuffer)
.resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.sequentialRead()
.resize(width, height)
.toBuffer(function(err, buffer) {
if (err) {
throw err;
@@ -533,189 +520,19 @@ async.series({
});
}
}).on('cycle', function(event) {
console.log('jpeg-linear ' + String(event.target));
console.log('operations ' + String(event.target));
}).on('complete', function() {
callback(null, this.filter('fastest').map('name'));
}).run();
},
'jpeg-cubic': function(callback) {
// Comparitive speed of kernels
kernels: function(callback) {
var inputJpgBuffer = fs.readFileSync(fixtures.inputJpg);
var jpegSuite = new Benchmark.Suite('jpeg-cubic');
// lwip
if (typeof lwip !== 'undefined') {
jpegSuite.add('lwip-file-file', {
defer: true,
fn: function(deferred) {
lwip.open(fixtures.inputJpg, function (err, image) {
if (err) {
throw err;
}
image.resize(width, height, 'lanczos', function (err, image) {
if (err) {
throw err;
}
image.writeFile(fixtures.outputJpg, {quality: 80}, function (err) {
if (err) {
throw err;
}
deferred.resolve();
});
});
});
}
}).add('lwip-buffer-buffer', {
defer: true,
fn: function(deferred) {
lwip.open(inputJpgBuffer, 'jpg', function (err, image) {
if (err) {
throw err;
}
image.resize(width, height, 'lanczos', function (err, image) {
if (err) {
throw err;
}
image.toBuffer('jpg', {quality: 80}, function (err, buffer) {
if (err) {
throw err;
}
assert.notStrictEqual(null, buffer);
deferred.resolve();
});
});
});
}
});
}
// imagemagick
jpegSuite.add('imagemagick-file-file', {
defer: true,
fn: function(deferred) {
imagemagick.resize({
srcPath: fixtures.inputJpg,
dstPath: fixtures.outputJpg,
quality: 0.8,
width: width,
height: height,
format: 'jpg',
filter: magickFilterBicubic
}, function(err) {
if (err) {
throw err;
} else {
deferred.resolve();
}
});
}
});
// imagemagick-native
if (typeof imagemagickNative !== 'undefined') {
jpegSuite.add('imagemagick-native-buffer-buffer', {
defer: true,
fn: function(deferred) {
imagemagickNative.convert({
srcData: inputJpgBuffer,
quality: 80,
width: width,
height: height,
format: 'JPEG',
filter: magickFilterBicubic
}, function (err, buffer) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
});
}
// gm
jpegSuite.add('gm-buffer-file', {
defer: true,
fn: function(deferred) {
gm(inputJpgBuffer)
.resize(width, height)
.filter(magickFilterBicubic)
.quality(80)
.write(fixtures.outputJpg, function (err) {
if (err) {
throw err;
} else {
deferred.resolve();
}
});
}
}).add('gm-buffer-buffer', {
defer: true,
fn: function(deferred) {
gm(inputJpgBuffer)
.resize(width, height)
.filter(magickFilterBicubic)
.quality(80)
.toBuffer(function (err, buffer) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
}).add('gm-file-file', {
defer: true,
fn: function(deferred) {
gm(fixtures.inputJpg)
.resize(width, height)
.filter(magickFilterBicubic)
.quality(80)
.write(fixtures.outputJpg, function (err) {
if (err) {
throw err;
} else {
deferred.resolve();
}
});
}
}).add('gm-file-buffer', {
defer: true,
fn: function(deferred) {
gm(fixtures.inputJpg)
.resize(width, height)
.filter(magickFilterBicubic)
.quality(80)
.toBuffer(function (err, buffer) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
});
// sharp
jpegSuite.add('sharp-buffer-file', {
(new Benchmark.Suite('kernels')).add('sharp-cubic', {
defer: true,
fn: function(deferred) {
sharp(inputJpgBuffer)
.resize(width, height)
.interpolateWith(sharp.interpolator.bicubic)
.toFile(fixtures.outputJpg, function(err) {
if (err) {
throw err;
} else {
deferred.resolve();
}
});
}
}).add('sharp-buffer-buffer', {
defer: true,
fn: function(deferred) {
sharp(inputJpgBuffer)
.resize(width, height)
.interpolateWith(sharp.interpolator.bicubic)
.resize(width, height, { kernel: 'cubic' })
.toBuffer(function(err, buffer) {
if (err) {
throw err;
@@ -725,39 +542,11 @@ async.series({
}
});
}
}).add('sharp-file-file', {
}).add('sharp-lanczos2', {
defer: true,
fn: function(deferred) {
sharp(fixtures.inputJpg)
.resize(width, height)
.interpolateWith(sharp.interpolator.bicubic)
.toFile(fixtures.outputJpg, function(err) {
if (err) {
throw err;
} else {
deferred.resolve();
}
});
}
}).add('sharp-stream-stream', {
defer: true,
fn: function(deferred) {
var readable = fs.createReadStream(fixtures.inputJpg);
var writable = fs.createWriteStream(fixtures.outputJpg);
writable.on('finish', function() {
deferred.resolve();
});
var pipeline = sharp()
.resize(width, height)
.interpolateWith(sharp.interpolator.bicubic);
readable.pipe(pipeline).pipe(writable);
}
}).add('sharp-file-buffer', {
defer: true,
fn: function(deferred) {
sharp(fixtures.inputJpg)
.resize(width, height)
.interpolateWith(sharp.interpolator.bicubic)
sharp(inputJpgBuffer)
.resize(width, height, { kernel: 'lanczos2' })
.toBuffer(function(err, buffer) {
if (err) {
throw err;
@@ -767,109 +556,11 @@ async.series({
}
});
}
}).add('sharp-promise', {
}).add('sharp-lanczos3', {
defer: true,
fn: function(deferred) {
sharp(inputJpgBuffer)
.resize(width, height)
.interpolateWith(sharp.interpolator.bicubic)
.toBuffer()
.then(function(buffer) {
assert.notStrictEqual(null, buffer);
deferred.resolve();
});
}
}).on('cycle', function(event) {
console.log('jpeg-cubic ' + String(event.target));
}).on('complete', function() {
callback(null, this.filter('fastest').map('name'));
}).run();
},
// Comparitive speed of pixel interpolators
interpolators: function(callback) {
var inputJpgBuffer = fs.readFileSync(fixtures.inputJpg);
(new Benchmark.Suite('interpolators')).add('sharp-nearest-neighbour', {
defer: true,
fn: function(deferred) {
sharp(inputJpgBuffer)
.resize(width, height)
.interpolateWith(sharp.interpolator.nearest)
.toBuffer(function(err, buffer) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
}).add('sharp-bilinear', {
defer: true,
fn: function(deferred) {
sharp(inputJpgBuffer)
.resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.toBuffer(function(err, buffer) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
}).add('sharp-vertexSplitQuadraticBasisSpline', {
defer: true,
fn: function(deferred) {
sharp(inputJpgBuffer)
.resize(width, height)
.interpolateWith(sharp.interpolator.vertexSplitQuadraticBasisSpline)
.toBuffer(function(err, buffer) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
}).add('sharp-bicubic', {
defer: true,
fn: function(deferred) {
sharp(inputJpgBuffer)
.resize(width, height)
.interpolateWith(sharp.interpolator.bicubic)
.toBuffer(function(err, buffer) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
}).add('sharp-locallyBoundedBicubic', {
defer: true,
fn: function(deferred) {
sharp(inputJpgBuffer)
.resize(width, height)
.interpolateWith(sharp.interpolator.locallyBoundedBicubic)
.toBuffer(function(err, buffer) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
}).add('sharp-nohalo', {
defer: true,
fn: function(deferred) {
sharp(inputJpgBuffer)
.resize(width, height)
.interpolateWith(sharp.interpolator.nohalo)
.resize(width, height, { kernel: 'lanczos3' })
.toBuffer(function(err, buffer) {
if (err) {
throw err;
@@ -880,12 +571,12 @@ async.series({
});
}
}).on('cycle', function(event) {
console.log('interpolators ' + String(event.target));
console.log('kernels ' + String(event.target));
}).on('complete', function() {
callback(null, this.filter('fastest').map('name'));
}).run();
},
// PNG
png: function(callback) {
var inputPngBuffer = fs.readFileSync(fixtures.inputPng);
var pngSuite = new Benchmark.Suite('png');
@@ -938,7 +629,7 @@ async.series({
if (err) {
throw err;
}
image.resize(width, height, 'linear', function (err, image) {
image.resize(width, height, 'lanczos', function (err, image) {
if (err) {
throw err;
}
@@ -963,7 +654,7 @@ async.series({
dstPath: fixtures.outputPng,
width: width,
height: height,
filter: magickFilterBilinear
filter: 'Lanczos'
}, function(err) {
if (err) {
throw err;
@@ -983,7 +674,7 @@ async.series({
width: width,
height: height,
format: 'PNG',
filter: magickFilterBilinear
filter: 'Lanczos'
});
deferred.resolve();
}
@@ -995,7 +686,7 @@ async.series({
fn: function(deferred) {
gm(fixtures.inputPng)
.resize(width, height)
.filter(magickFilterBilinear)
.filter('Lanczos')
.write(fixtures.outputPng, function (err) {
if (err) {
throw err;
@@ -1009,7 +700,7 @@ async.series({
fn: function(deferred) {
gm(fixtures.inputPng)
.resize(width, height)
.filter(magickFilterBilinear)
.filter('Lanczos')
.toBuffer(function (err, buffer) {
if (err) {
throw err;
@@ -1026,7 +717,6 @@ async.series({
fn: function(deferred) {
sharp(inputPngBuffer)
.resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.toFile(fixtures.outputPng, function(err) {
if (err) {
throw err;
@@ -1040,7 +730,6 @@ async.series({
fn: function(deferred) {
sharp(inputPngBuffer)
.resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.toBuffer(function(err, buffer) {
if (err) {
throw err;
@@ -1055,7 +744,6 @@ async.series({
fn: function(deferred) {
sharp(fixtures.inputPng)
.resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.toFile(fixtures.outputPng, function(err) {
if (err) {
throw err;
@@ -1069,7 +757,6 @@ async.series({
fn: function(deferred) {
sharp(fixtures.inputPng)
.resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.toBuffer(function(err, buffer) {
if (err) {
throw err;
@@ -1084,7 +771,6 @@ async.series({
fn: function(deferred) {
sharp(inputPngBuffer)
.resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.progressive()
.toBuffer(function(err, buffer) {
if (err) {
@@ -1100,7 +786,6 @@ async.series({
fn: function(deferred) {
sharp(inputPngBuffer)
.resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.withoutAdaptiveFiltering()
.toBuffer(function(err, buffer) {
if (err) {
@@ -1118,7 +803,7 @@ async.series({
callback(null, this.filter('fastest').map('name'));
}).run();
},
// WebP
webp: function(callback) {
var inputWebPBuffer = fs.readFileSync(fixtures.inputWebP);
(new Benchmark.Suite('webp')).add('sharp-buffer-file', {
@@ -1126,7 +811,6 @@ async.series({
fn: function(deferred) {
sharp(inputWebPBuffer)
.resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.toFile(fixtures.outputWebP, function(err) {
if (err) {
throw err;
@@ -1140,7 +824,6 @@ async.series({
fn: function(deferred) {
sharp(inputWebPBuffer)
.resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.toBuffer(function(err, buffer) {
if (err) {
throw err;
@@ -1155,7 +838,6 @@ async.series({
fn: function(deferred) {
sharp(fixtures.inputWebP)
.resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.toFile(fixtures.outputWebP, function(err) {
if (err) {
throw err;
@@ -1169,7 +851,6 @@ async.series({
fn: function(deferred) {
sharp(fixtures.inputWebp)
.resize(width, height)
.interpolateWith(sharp.interpolator.bilinear)
.toBuffer(function(err, buffer) {
if (err) {
throw err;

View File

@@ -8,14 +8,12 @@ var Benchmark = require('benchmark');
var sharp = require('../../index');
var fixtures = require('../fixtures');
sharp.cache(false);
sharp.simd(true);
var min = 320;
var max = 960;
// Nearest equivalent to bilinear
var magickFilter = 'Triangle';
var randomDimension = function() {
return Math.ceil(Math.random() * (max - min) + min);
};
@@ -30,7 +28,7 @@ new Benchmark.Suite('random').add('imagemagick', {
width: randomDimension(),
height: randomDimension(),
format: 'jpg',
filter: magickFilter
filter: 'Lanczos'
}, function(err) {
if (err) {
throw err;
@@ -44,7 +42,7 @@ new Benchmark.Suite('random').add('imagemagick', {
fn: function(deferred) {
gm(fixtures.inputJpg)
.resize(randomDimension(), randomDimension())
.filter(magickFilter)
.filter('Lanczos')
.quality(80)
.toBuffer(function (err, buffer) {
if (err) {
@@ -58,19 +56,20 @@ new Benchmark.Suite('random').add('imagemagick', {
}).add('sharp', {
defer: true,
fn: function(deferred) {
sharp(fixtures.inputJpg).resize(randomDimension(), randomDimension()).toBuffer(function(err, buffer) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
sharp(fixtures.inputJpg)
.resize(randomDimension(), randomDimension())
.toBuffer(function(err, buffer) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
}).on('cycle', function(event) {
console.log(String(event.target));
}).on('complete', function() {
var winner = this.filter('fastest').map('name');
assert.strictEqual('sharp', String(winner), 'sharp was slower than ' + winner);
console.dir(sharp.cache());
}).run();

BIN
test/fixtures/bandbool.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
test/fixtures/booleanTest.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 644 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
test/fixtures/expected/conv-1.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 807 B

BIN
test/fixtures/expected/conv-2.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 806 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 755 B

After

Width:  |  Height:  |  Size: 988 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 827 B

After

Width:  |  Height:  |  Size: 789 B

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 348 B

After

Width:  |  Height:  |  Size: 329 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 840 B

After

Width:  |  Height:  |  Size: 839 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 795 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 795 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 692 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 745 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 633 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 725 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 823 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 745 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 743 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 745 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 699 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 842 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
test/fixtures/expected/sharpen-rgba.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 574 B

After

Width:  |  Height:  |  Size: 716 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 938 B

After

Width:  |  Height:  |  Size: 823 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

BIN
test/fixtures/grey-plus-alpha.gif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 B

View File

@@ -18,7 +18,6 @@ var fingerprint = function(image, callback) {
.normalise()
.resize(9, 8)
.ignoreAspectRatio()
.interpolateWith(sharp.interpolator.vertexSplitQuadraticBasisSpline)
.raw()
.toBuffer(function(err, data) {
if (err) {
@@ -80,21 +79,30 @@ module.exports = {
inputPngOverlayLayer2LowAlpha: getPath('alpha-layer-2-ink-low-alpha.png'),
inputPngAlphaPremultiplicationSmall: getPath('alpha-premultiply-1024x768-paper.png'),
inputPngAlphaPremultiplicationLarge: getPath('alpha-premultiply-2048x1536-paper.png'),
inputPngBooleanNoAlpha: getPath('bandbool.png'),
inputWebP: getPath('4.webp'), // http://www.gstatic.com/webp/gallery/4.webp
inputWebPWithTransparency: getPath('5_webp_a.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp
inputTiff: getPath('G31D.TIF'), // http://www.fileformat.info/format/tiff/sample/e6c9a6e5253348f4aef6d17b534360ab/index.htm
inputGif: getPath('Crash_test.gif'), // http://upload.wikimedia.org/wikipedia/commons/e/e3/Crash_test.gif
inputGifGreyPlusAlpha: getPath('grey-plus-alpha.gif'), // http://i.imgur.com/gZ5jlmE.gif
inputSvg: getPath('check.svg'), // http://dev.w3.org/SVG/tools/svgweb/samples/svg-files/check.svg
inputPsd: getPath('free-gearhead-pack.psd'), // https://dribbble.com/shots/1624241-Free-Gearhead-Vector-Pack
inputSvs: getPath('CMU-1-Small-Region.svs'), // http://openslide.cs.cmu.edu/download/openslide-testdata/Aperio/CMU-1-Small-Region.svs
inputJPGBig: getPath('flowers.jpeg'),
inputPngStripesV: getPath('stripesV.png'),
inputPngStripesH: getPath('stripesH.png'),
inputJpgBooleanTest: getPath('booleanTest.jpg'),
inputV: getPath('vfile.v'),
outputJpg: getPath('output.jpg'),
outputPng: getPath('output.png'),
outputWebP: getPath('output.webp'),
outputV: getPath('output.v'),
outputZoinks: getPath('output.zoinks'), // an 'unknown' file extension
// Path for tests requiring human inspection

BIN
test/fixtures/stripesH.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 502 B

BIN
test/fixtures/stripesV.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 624 B

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