Compare commits

..

28 Commits

Author SHA1 Message Date
Lovell Fuller
34d5252242 Release v0.21.1 2018-12-07 19:23:54 +00:00
Lovell Fuller
f31e4d2869 Changelog, credit and doc refresh for #1483 2018-12-06 21:58:14 +00:00
Michael B. Klein
c695c40abc Expose libvips pyramid/tile options for TIFF output (#1483) 2018-12-06 22:33:46 +01:00
Lovell Fuller
fd1ca1dbb2 Ensure the tests for #1477 pass on OS X 2018-12-04 23:58:02 +01:00
Lovell Fuller
f25dbd5f61 Ensure the tests for #1477 pass on OS X 2018-12-04 23:45:08 +01:00
Keith
541e7104fd Expose libvips recombination matrix operation #1477 2018-12-04 23:06:34 +01:00
Lovell Fuller
94945cf6ac Changelog and credit for #1475 2018-12-04 07:49:41 +00:00
Lovell Fuller
db76e655f8 Ensure licensing checker works on Windows 2018-11-29 10:15:13 +00:00
Lovell Fuller
d43c7b581d Add licensing checker for production dependencies 2018-11-29 09:53:00 +00:00
Julian Aubourg
383b933e26 Build prototype with Object.assign to allow minification (#1475) 2018-11-26 19:40:06 +01:00
Lovell Fuller
d26ccf6294 Docs: update Alpine repository URLs 2018-11-21 22:42:58 +00:00
Lovell Fuller
6f9699f605 Ensure correct channel info for raw, greyscale output #1425 2018-11-19 20:00:30 +00:00
Quinn Pan
1e9093d781 Docs: correct code example in extend operation 2018-11-19 20:35:16 +01:00
Lovell Fuller
9dc6492e52 Docs: correct code example in extend operation 2018-11-19 19:34:16 +00:00
Lovell Fuller
d22f7cae6a Silence cast-function-type warnings from GCC 8+ 2018-11-19 18:47:35 +00:00
Lovell Fuller
473afaab45 Install: detect missing libvips on OpenBSD and SunOS
See https://github.com/lovell/sharp-libvips/issues/12
2018-11-19 18:46:05 +00:00
Lovell Fuller
dcd68303a4 Docs: add installation details for Lambda without Docker 2018-11-16 10:28:34 +00:00
Lovell Fuller
03394556b5 Update semistandard linter to latest 2018-11-11 18:05:40 +00:00
Lovell Fuller
1c4f6f75f3 Add Node 11 to CI, experimental only, no prebuild
Hide deprecation warnings - see nodejs/nan#811
2018-11-11 17:55:35 +00:00
Lovell Fuller
f00928dedb Doc refresh for #1438 #1439 2018-11-11 17:40:19 +00:00
Daiz
a48f8fbb61 Allow separate parameters for gamma encoding and decoding (#1439) 2018-11-11 10:15:38 +01:00
Daiz
1fa388370e Add support for the "mitchell" kernel for image reductions (#1438) 2018-10-28 15:11:27 +00:00
Christoph Tavan
95ef6b3f71 Docs: update Alpine libvips installation instructions (#1429)
With version 8.7.0 of the vips-dev Alpine package the documented installation instructions no longer work and result in:

ERROR: unsatisfiable constraints:
  pc:fftw3 (missing):
    required by: vips-dev-8.7.0-r0[pc:fftw3] vips-dev-8.7.0-r0[pc:fftw3] vips-dev-8.7.0-r0[pc:fftw3]

The fix was proposed in https://bugs.alpinelinux.org/issues/9561#note-2
2018-10-24 14:31:32 +01:00
Lovell Fuller
de11d36d00 Minor version bumps 2018-10-22 19:38:06 +01:00
Lovell Fuller
d77c2adabe Changelog and docs for #1422 2018-10-22 19:17:42 +01:00
SethWen
c89c055ae0 Install: add support for npm_config_sharp_dist_base_url (#1422) 2018-10-22 18:54:47 +01:00
Lovell Fuller
dac8117f32 Docs: ensure options are included for flatten op 2018-10-08 19:56:30 +01:00
Waylon Walker
937b091bab Docs: clear _libvips cache on Windows (#1403) 2018-10-07 16:49:05 +01:00
40 changed files with 693 additions and 182 deletions

View File

@@ -21,6 +21,14 @@ matrix:
after_success: after_success:
- npm install coveralls - npm install coveralls
- cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js
- name: "Linux (glibc) - Node 11 (Experimental)"
os: linux
dist: trusty
sudo: false
language: node_js
node_js: "11"
before_install:
- unset PREBUILD_TOKEN
- name: "Linux (musl) - Node 8" - name: "Linux (musl) - Node 8"
os: linux os: linux
dist: trusty dist: trusty
@@ -41,6 +49,16 @@ matrix:
- sudo docker exec sharp apk add build-base git python2 --update-cache - sudo docker exec sharp apk add build-base git python2 --update-cache
install: sudo docker exec sharp sh -c "npm install --unsafe-perm" install: sudo docker exec sharp sh -c "npm install --unsafe-perm"
script: sudo docker exec sharp sh -c "npm test" script: sudo docker exec sharp sh -c "npm test"
- name: "Linux (musl) - Node 11 (Experimental)"
os: linux
dist: trusty
sudo: true
language: minimal
before_install:
- sudo docker run -dit --name sharp --env CI --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp node:11-alpine
- sudo docker exec sharp apk add build-base git python2 --update-cache
install: sudo docker exec sharp sh -c "npm install --unsafe-perm"
script: sudo docker exec sharp sh -c "npm test"
- name: "OS X - Node 6" - name: "OS X - Node 6"
os: osx os: osx
osx_image: xcode9.2 osx_image: xcode9.2
@@ -56,3 +74,10 @@ matrix:
osx_image: xcode9.2 osx_image: xcode9.2
language: node_js language: node_js
node_js: "10" node_js: "10"
- name: "OS X - Node 11 (Experimental)"
os: osx
osx_image: xcode9.2
language: node_js
node_js: "11"
before_install:
- unset PREBUILD_TOKEN

View File

@@ -7,6 +7,8 @@ environment:
- nodejs_version: "6" - nodejs_version: "6"
- nodejs_version: "8" - nodejs_version: "8"
- nodejs_version: "10" - nodejs_version: "10"
- nodejs_version: "11"
PREBUILD_TOKEN: ""
install: install:
- ps: Install-Product node $env:nodejs_version x64 - ps: Install-Product node $env:nodejs_version x64
- npm install -g npm@5 - npm install -g npm@5

View File

@@ -183,13 +183,23 @@
}, },
'configurations': { 'configurations': {
'Release': { 'Release': {
'cflags_cc': [
'-Wno-cast-function-type',
'-Wno-deprecated-declarations'
],
'xcode_settings': {
'OTHER_CPLUSPLUSFLAGS': [
'-Wno-deprecated-declarations'
]
},
'msvs_settings': { 'msvs_settings': {
'VCCLCompilerTool': { 'VCCLCompilerTool': {
'ExceptionHandling': 1 'ExceptionHandling': 1
} }
}, },
'msvs_disabled_warnings': [ 'msvs_disabled_warnings': [
4275 4275,
4996
] ]
} }
}, },

View File

@@ -118,7 +118,8 @@ Merge alpha transparency channel, if any, with a background.
### Parameters ### Parameters
- `options` - `options` **[Object][2]?**
- `options.background` **([String][3] \| [Object][2])** background colour, parsed by the [color][4] module, defaults to black. (optional, default `{r:0,g:0,b:0}`)
Returns **Sharp** Returns **Sharp**
@@ -130,9 +131,12 @@ This can improve the perceived brightness of a resized image in non-linear colou
JPEG and WebP input images will not take advantage of the shrink-on-load performance optimisation JPEG and WebP input images will not take advantage of the shrink-on-load performance optimisation
when applying a gamma correction. when applying a gamma correction.
Supply a second argument to use a different output gamma value, otherwise the first value is used in both cases.
### Parameters ### Parameters
- `gamma` **[Number][1]** value between 1.0 and 3.0. (optional, default `2.2`) - `gamma` **[Number][1]** value between 1.0 and 3.0. (optional, default `2.2`)
- `gammaOut` **[Number][1]?** value between 1.0 and 3.0. (optional, defaults to same as `gamma`)
- Throws **[Error][5]** Invalid parameters - Throws **[Error][5]** Invalid parameters
@@ -250,6 +254,35 @@ Apply the linear formula a \* input + b to the image (levels adjustment)
- `b` **[Number][1]** offset (optional, default `0.0`) - `b` **[Number][1]** offset (optional, default `0.0`)
- Throws **[Error][5]** Invalid parameters
Returns **Sharp**
## recomb
Recomb the image with the specified matrix.
### Parameters
- `inputMatrix`
- `3x3` **[Array][7]<[Array][7]<[Number][1]>>** Recombination matrix
### Examples
```javascript
sharp(input)
.recomb([
[0.3588, 0.7044, 0.1368],
[0.2990, 0.5870, 0.1140],
[0.2392, 0.4696, 0.0912],
])
.raw()
.toBuffer(function(err, data, info) {
// data contains the raw pixel data after applying the recomb
// With this example input, a sepia filter has been applied
});
```
- Throws **[Error][5]** Invalid parameters - Throws **[Error][5]** Invalid parameters
Returns **Sharp** Returns **Sharp**

View File

@@ -206,6 +206,10 @@ Use these TIFF options for output image.
- `options.force` **[Boolean][6]** force TIFF output, otherwise attempt to use input format (optional, default `true`) - `options.force` **[Boolean][6]** force TIFF output, otherwise attempt to use input format (optional, default `true`)
- `options.compression` **[Boolean][6]** compression options: lzw, deflate, jpeg, ccittfax4 (optional, default `'jpeg'`) - `options.compression` **[Boolean][6]** compression options: lzw, deflate, jpeg, ccittfax4 (optional, default `'jpeg'`)
- `options.predictor` **[Boolean][6]** compression predictor options: none, horizontal, float (optional, default `'horizontal'`) - `options.predictor` **[Boolean][6]** compression predictor options: none, horizontal, float (optional, default `'horizontal'`)
- `options.pyramid` **[Boolean][6]** write an image pyramid (optional, default `false`)
- `options.tile` **[Boolean][6]** write a tiled tiff (optional, default `false`)
- `options.tileWidth` **[Boolean][6]** horizontal tile size (optional, default `256`)
- `options.tileHeight` **[Boolean][6]** vertical tile size (optional, default `256`)
- `options.xres` **[Number][8]** horizontal resolution in pixels/mm (optional, default `1.0`) - `options.xres` **[Number][8]** horizontal resolution in pixels/mm (optional, default `1.0`)
- `options.yres` **[Number][8]** vertical resolution in pixels/mm (optional, default `1.0`) - `options.yres` **[Number][8]** vertical resolution in pixels/mm (optional, default `1.0`)
- `options.squash` **[Boolean][6]** squash 8-bit images down to 1 bit (optional, default `false`) - `options.squash` **[Boolean][6]** squash 8-bit images down to 1 bit (optional, default `false`)

View File

@@ -30,22 +30,23 @@ Possible interpolation kernels are:
- `nearest`: Use [nearest neighbour interpolation][4]. - `nearest`: Use [nearest neighbour interpolation][4].
- `cubic`: Use a [Catmull-Rom spline][5]. - `cubic`: Use a [Catmull-Rom spline][5].
- `lanczos2`: Use a [Lanczos kernel][6] with `a=2`. - `mitchell`: Use a [Mitchell-Netravali spline][6].
- `lanczos2`: Use a [Lanczos kernel][7] with `a=2`.
- `lanczos3`: Use a Lanczos kernel with `a=3` (the default). - `lanczos3`: Use a Lanczos kernel with `a=3` (the default).
### Parameters ### Parameters
- `width` **[Number][7]?** pixels wide the resultant image should be. Use `null` or `undefined` to auto-scale the width to match the height. - `width` **[Number][8]?** pixels wide the resultant image should be. Use `null` or `undefined` to auto-scale the width to match the height.
- `height` **[Number][7]?** pixels high the resultant image should be. Use `null` or `undefined` to auto-scale the height to match the width. - `height` **[Number][8]?** pixels high the resultant image should be. Use `null` or `undefined` to auto-scale the height to match the width.
- `options` **[Object][8]?** - `options` **[Object][9]?**
- `options.width` **[String][9]?** alternative means of specifying `width`. If both are present this take priority. - `options.width` **[String][10]?** alternative means of specifying `width`. If both are present this take priority.
- `options.height` **[String][9]?** alternative means of specifying `height`. If both are present this take priority. - `options.height` **[String][10]?** alternative means of specifying `height`. If both are present this take priority.
- `options.fit` **[String][9]** how the image should be resized to fit both provided dimensions, one of `cover`, `contain`, `fill`, `inside` or `outside`. (optional, default `'cover'`) - `options.fit` **[String][10]** how the image should be resized to fit both provided dimensions, one of `cover`, `contain`, `fill`, `inside` or `outside`. (optional, default `'cover'`)
- `options.position` **[String][9]** position, gravity or strategy to use when `fit` is `cover` or `contain`. (optional, default `'centre'`) - `options.position` **[String][10]** position, gravity or strategy to use when `fit` is `cover` or `contain`. (optional, default `'centre'`)
- `options.background` **([String][9] \| [Object][8])** background colour when using a `fit` of `contain`, parsed by the [color][10] module, defaults to black without transparency. (optional, default `{r:0,g:0,b:0,alpha:1}`) - `options.background` **([String][10] \| [Object][9])** background colour when using a `fit` of `contain`, parsed by the [color][11] module, defaults to black without transparency. (optional, default `{r:0,g:0,b:0,alpha:1}`)
- `options.kernel` **[String][9]** the kernel to use for image reduction. (optional, default `'lanczos3'`) - `options.kernel` **[String][10]** the kernel to use for image reduction. (optional, default `'lanczos3'`)
- `options.withoutEnlargement` **[Boolean][11]** do not enlarge if the width _or_ height are already less than the specified dimensions, equivalent to GraphicsMagick's `>` geometry option. (optional, default `false`) - `options.withoutEnlargement` **[Boolean][12]** do not enlarge if the width _or_ height are already less than the specified dimensions, equivalent to GraphicsMagick's `>` geometry option. (optional, default `false`)
- `options.fastShrinkOnLoad` **[Boolean][11]** take greater advantage of the JPEG and WebP shrink-on-load feature, which can lead to a slight moiré pattern on some images. (optional, default `true`) - `options.fastShrinkOnLoad` **[Boolean][12]** take greater advantage of the JPEG and WebP shrink-on-load feature, which can lead to a slight moiré pattern on some images. (optional, default `true`)
### Examples ### Examples
@@ -113,7 +114,7 @@ sharp(input)
}); });
``` ```
- Throws **[Error][12]** Invalid parameters - Throws **[Error][13]** Invalid parameters
Returns **Sharp** Returns **Sharp**
@@ -124,12 +125,12 @@ This operation will always occur after resizing and extraction, if any.
### Parameters ### Parameters
- `extend` **([Number][7] \| [Object][8])** single pixel count to add to all edges or an Object with per-edge counts - `extend` **([Number][8] \| [Object][9])** single pixel count to add to all edges or an Object with per-edge counts
- `extend.top` **[Number][7]?** - `extend.top` **[Number][8]?**
- `extend.left` **[Number][7]?** - `extend.left` **[Number][8]?**
- `extend.bottom` **[Number][7]?** - `extend.bottom` **[Number][8]?**
- `extend.right` **[Number][7]?** - `extend.right` **[Number][8]?**
- `extend.background` **([String][9] \| [Object][8])** background colour, parsed by the [color][10] module, defaults to black without transparency. (optional, default `{r:0,g:0,b:0,alpha:1}`) - `extend.background` **([String][10] \| [Object][9])** background colour, parsed by the [color][11] module, defaults to black without transparency. (optional, default `{r:0,g:0,b:0,alpha:1}`)
### Examples ### Examples
@@ -138,7 +139,6 @@ This operation will always occur after resizing and extraction, if any.
// to the top, left and right edges and 20 to the bottom edge // to the top, left and right edges and 20 to the bottom edge
sharp(input) sharp(input)
.resize(140) .resize(140)
.)
.extend({ .extend({
top: 10, top: 10,
bottom: 20, bottom: 20,
@@ -149,7 +149,7 @@ sharp(input)
... ...
``` ```
- Throws **[Error][12]** Invalid parameters - Throws **[Error][13]** Invalid parameters
Returns **Sharp** Returns **Sharp**
@@ -163,11 +163,11 @@ Extract a region of the image.
### Parameters ### Parameters
- `options` **[Object][8]** - `options` **[Object][9]**
- `options.left` **[Number][7]** zero-indexed offset from left edge - `options.left` **[Number][8]** zero-indexed offset from left edge
- `options.top` **[Number][7]** zero-indexed offset from top edge - `options.top` **[Number][8]** zero-indexed offset from top edge
- `options.width` **[Number][7]** dimension of extracted image - `options.width` **[Number][8]** dimension of extracted image
- `options.height` **[Number][7]** dimension of extracted image - `options.height` **[Number][8]** dimension of extracted image
### Examples ### Examples
@@ -189,7 +189,7 @@ sharp(input)
}); });
``` ```
- Throws **[Error][12]** Invalid parameters - Throws **[Error][13]** Invalid parameters
Returns **Sharp** Returns **Sharp**
@@ -200,10 +200,10 @@ The `info` response Object will contain `trimOffsetLeft` and `trimOffsetTop` pro
### Parameters ### Parameters
- `threshold` **[Number][7]** the allowed difference from the top-left pixel, a number greater than zero. (optional, default `10`) - `threshold` **[Number][8]** the allowed difference from the top-left pixel, a number greater than zero. (optional, default `10`)
- Throws **[Error][12]** Invalid parameters - Throws **[Error][13]** Invalid parameters
Returns **Sharp** Returns **Sharp**
@@ -217,16 +217,18 @@ Returns **Sharp**
[5]: https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline [5]: https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline
[6]: https://en.wikipedia.org/wiki/Lanczos_resampling#Lanczos_kernel [6]: https://www.cs.utexas.edu/~fussell/courses/cs384g-fall2013/lectures/mitchell/Mitchell.pdf
[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number [7]: https://en.wikipedia.org/wiki/Lanczos_resampling#Lanczos_kernel
[8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object [8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
[9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String [9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[10]: https://www.npmjs.org/package/color [10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[11]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean [11]: https://www.npmjs.org/package/color
[12]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error [12]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[13]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error

View File

@@ -4,6 +4,35 @@
Requires libvips v8.7.0. Requires libvips v8.7.0.
#### v0.21.1 - 7<sup>th</sup> December 2018
* Install: support `sharp_dist_base_url` npm config, like existing `SHARP_DIST_BASE_URL`.
[#1422](https://github.com/lovell/sharp/pull/1422)
[@SethWen](https://github.com/SethWen)
* Ensure `channel` metadata is correct for raw, greyscale output.
[#1425](https://github.com/lovell/sharp/issues/1425)
* Add support for the "mitchell" kernel for image reductions.
[#1438](https://github.com/lovell/sharp/pull/1438)
[@Daiz](https://github.com/Daiz)
* Allow separate parameters for gamma encoding and decoding.
[#1439](https://github.com/lovell/sharp/pull/1439)
[@Daiz](https://github.com/Daiz)
* Build prototype with `Object.assign` to allow minification.
[#1475](https://github.com/lovell/sharp/pull/1475)
[@jaubourg](https://github.com/jaubourg)
* Expose libvips' recombination matrix operation.
[#1477](https://github.com/lovell/sharp/pull/1477)
[@fromkeith](https://github.com/fromkeith)
* Expose libvips' pyramid/tile options for TIFF output.
[#1483](https://github.com/lovell/sharp/pull/1483)
[@mbklein](https://github.com/mbklein)
#### v0.21.0 - 4<sup>th</sup> October 2018 #### v0.21.0 - 4<sup>th</sup> October 2018
* Deprecate the following resize-related functions: * Deprecate the following resize-related functions:

View File

@@ -119,6 +119,9 @@ the help and code contributions of the following people:
* [Aidan Hoolachan](https://github.com/ajhool) * [Aidan Hoolachan](https://github.com/ajhool)
* [Axel Eirola](https://github.com/aeirola) * [Axel Eirola](https://github.com/aeirola)
* [Freezy](https://github.com/freezy) * [Freezy](https://github.com/freezy)
* [Julian Aubourg](https://github.com/jaubourg)
* [Keith Belovay](https://github.com/fromkeith)
* [Michael B. Klein](https://github.com/mbklein)
Thank you! Thank you!

View File

@@ -72,7 +72,9 @@ libvips is available in the
[testing repository](https://pkgs.alpinelinux.org/packages?name=vips-dev): [testing repository](https://pkgs.alpinelinux.org/packages?name=vips-dev):
```sh ```sh
apk add vips-dev fftw-dev build-base --update-cache --repository https://dl-3.alpinelinux.org/alpine/edge/testing/ apk add vips-dev fftw-dev build-base --update-cache \
--repository https://alpine.global.ssl.fastly.net/alpine/edge/testing/ \
--repository https://alpine.global.ssl.fastly.net/alpine/edge/main
``` ```
The smaller stack size of musl libc means The smaller stack size of musl libc means
@@ -95,7 +97,8 @@ that it can be located using `pkg-config --modversion vips-cpp`.
[![Windows x64 Build Status](https://ci.appveyor.com/api/projects/status/pgtul704nkhhg6sg)](https://ci.appveyor.com/project/lovell/sharp) [![Windows x64 Build Status](https://ci.appveyor.com/api/projects/status/pgtul704nkhhg6sg)](https://ci.appveyor.com/project/lovell/sharp)
libvips and its dependencies are fetched and stored within `node_modules\sharp\vendor` during `npm install`. libvips and its dependencies are fetched and stored within `node_modules\sharp\vendor` during `npm install`.
This involves an automated HTTPS download of approximately 13MB. This involves an automated HTTPS download of approximately 13MB. If you are having issues during
installation consider removing the directory ```C:\Users\[user]\AppData\Roaming\npm-cache\_libvips```.
Only 64-bit (x64) `node.exe` is supported. Only 64-bit (x64) `node.exe` is supported.
@@ -146,16 +149,25 @@ docker pull tailor/docker-libvips
### AWS Lambda ### AWS Lambda
A [deployment package](http://docs.aws.amazon.com/lambda/latest/dg/nodejs-create-deployment-pkg.html) for the Set the Lambda runtime to Node.js 8.10.
[Lambda Execution Environment](http://docs.aws.amazon.com/lambda/latest/dg/current-supported-versions.html)
can be built using Docker. The binaries in the `node_modules` directory of the
[deployment package](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-create-deployment-pkg.html)
must be for the Linux x64 platform/architecture.
On non-Linux machines such as OS X and Windows run the following:
```sh ```sh
rm -rf node_modules/sharp rm -rf node_modules/sharp
docker run -v "$PWD":/var/task lambci/lambda:build-nodejs8.10 npm install npm install --arch=x64 --platform=linux --target=8.10.0 sharp
``` ```
Set the Lambda runtime to Node.js 8.10. Alternatively a Docker container closely matching the Lambda runtime can be used:
```sh
rm -rf node_modules/sharp
docker run -v "$PWD":/var/task lambci/lambda:build-nodejs8.10 npm install sharp
```
To get the best performance select the largest memory available. To get the best performance select the largest memory available.
A 1536 MB function provides ~12x more CPU time than a 128 MB function. A 1536 MB function provides ~12x more CPU time than a 128 MB function.
@@ -217,7 +229,14 @@ you can do so via
[https://github.com/lovell/sharp-libvips/releases](https://github.com/lovell/sharp-libvips/releases) [https://github.com/lovell/sharp-libvips/releases](https://github.com/lovell/sharp-libvips/releases)
Should you wish to install these from your own location, Should you wish to install these from your own location,
set the `SHARP_DIST_BASE_URL` environment variable, e.g. set the `sharp_dist_base_url` npm config option, e.g.
```sh
npm config set sharp_dist_base_url "https://hostname/path/"
npm install sharp
```
or set the `SHARP_DIST_BASE_URL` environment variable, e.g.
```sh ```sh
SHARP_DIST_BASE_URL="https://hostname/path/" npm install sharp SHARP_DIST_BASE_URL="https://hostname/path/" npm install sharp

View File

@@ -16,7 +16,7 @@ const libvips = require('../lib/libvips');
const platform = require('../lib/platform'); const platform = require('../lib/platform');
const minimumLibvipsVersion = libvips.minimumLibvipsVersion; const minimumLibvipsVersion = libvips.minimumLibvipsVersion;
const distBaseUrl = process.env.SHARP_DIST_BASE_URL || `https://github.com/lovell/sharp-libvips/releases/download/v${minimumLibvipsVersion}/`; const distBaseUrl = process.env.npm_config_sharp_dist_base_url || process.env.SHARP_DIST_BASE_URL || `https://github.com/lovell/sharp-libvips/releases/download/v${minimumLibvipsVersion}/`;
const fail = function (err) { const fail = function (err) {
npmLog.error('sharp', err.message); npmLog.error('sharp', err.message);
@@ -55,8 +55,8 @@ try {
if (arch === 'ia32') { if (arch === 'ia32') {
throw new Error(`Intel Architecture 32-bit systems require manual installation of libvips >= ${minimumLibvipsVersion}`); throw new Error(`Intel Architecture 32-bit systems require manual installation of libvips >= ${minimumLibvipsVersion}`);
} }
if (platformAndArch === 'freebsd-x64') { if (platformAndArch === 'freebsd-x64' || platformAndArch === 'openbsd-x64' || platformAndArch === 'sunos-x64') {
throw new Error(`FreeBSD systems require manual installation of libvips >= ${minimumLibvipsVersion}`); throw new Error(`BSD/SunOS systems require manual installation of libvips >= ${minimumLibvipsVersion}`);
} }
if (detectLibc.family === detectLibc.GLIBC && detectLibc.version && semver.lt(`${detectLibc.version}.0`, '2.13.0')) { if (detectLibc.family === detectLibc.GLIBC && detectLibc.version && semver.lt(`${detectLibc.version}.0`, '2.13.0')) {
throw new Error(`Use with glibc version ${detectLibc.version} requires manual installation of libvips >= ${minimumLibvipsVersion}`); throw new Error(`Use with glibc version ${detectLibc.version} requires manual installation of libvips >= ${minimumLibvipsVersion}`);

View File

@@ -117,14 +117,12 @@ function bandbool (boolOp) {
* @private * @private
*/ */
module.exports = function (Sharp) { module.exports = function (Sharp) {
Object.assign(Sharp.prototype, {
// Public instance functions // Public instance functions
[
removeAlpha, removeAlpha,
extractChannel, extractChannel,
joinChannel, joinChannel,
bandbool bandbool
].forEach(function (f) {
Sharp.prototype[f.name] = f;
}); });
// Class attributes // Class attributes
Sharp.bool = bool; Sharp.bool = bool;

View File

@@ -123,7 +123,7 @@ function _setColourOption (key, val) {
* @private * @private
*/ */
module.exports = function (Sharp) { module.exports = function (Sharp) {
[ Object.assign(Sharp.prototype, {
// Public // Public
tint, tint,
greyscale, greyscale,
@@ -132,8 +132,6 @@ module.exports = function (Sharp) {
toColorspace, toColorspace,
// Private // Private
_setColourOption _setColourOption
].forEach(function (f) {
Sharp.prototype[f.name] = f;
}); });
// Class attributes // Class attributes
Sharp.colourspace = colourspace; Sharp.colourspace = colourspace;

View File

@@ -7,7 +7,7 @@ const events = require('events');
const is = require('./is'); const is = require('./is');
require('./libvips').hasVendoredLibvips(); require('./libvips').hasVendoredLibvips();
const sharp = require('../build/Release/sharp.node'); const sharp = require('bindings')('sharp.node');
// Use NODE_DEBUG=sharp to enable libvips warnings // Use NODE_DEBUG=sharp to enable libvips warnings
const debuglog = util.debuglog('sharp'); const debuglog = util.debuglog('sharp');
@@ -136,6 +136,7 @@ const Sharp = function (input, options) {
thresholdGrayscale: true, thresholdGrayscale: true,
trimThreshold: 0, trimThreshold: 0,
gamma: 0, gamma: 0,
gammaOut: 0,
greyscale: false, greyscale: false,
normalise: 0, normalise: 0,
booleanBufferIn: null, booleanBufferIn: null,
@@ -176,7 +177,11 @@ const Sharp = function (input, options) {
tiffQuality: 80, tiffQuality: 80,
tiffCompression: 'jpeg', tiffCompression: 'jpeg',
tiffPredictor: 'horizontal', tiffPredictor: 'horizontal',
tiffPyramid: false,
tiffSquash: false, tiffSquash: false,
tiffTile: false,
tiffTileHeight: 256,
tiffTileWidth: 256,
tiffXres: 1.0, tiffXres: 1.0,
tiffYres: 1.0, tiffYres: 1.0,
tileSize: 256, tileSize: 256,

View File

@@ -362,7 +362,7 @@ function sequentialRead (sequentialRead) {
* @private * @private
*/ */
module.exports = function (Sharp) { module.exports = function (Sharp) {
[ Object.assign(Sharp.prototype, {
// Private // Private
_createInputDescriptor, _createInputDescriptor,
_write, _write,
@@ -374,7 +374,5 @@ module.exports = function (Sharp) {
stats, stats,
limitInputPixels, limitInputPixels,
sequentialRead sequentialRead
].forEach(function (f) {
Sharp.prototype[f.name] = f;
}); });
}; };

View File

@@ -172,6 +172,7 @@ function blur (sigma) {
/** /**
* Merge alpha transparency channel, if any, with a background. * Merge alpha transparency channel, if any, with a background.
* @param {Object} [options]
* @param {String|Object} [options.background={r: 0, g: 0, b: 0}] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black. * @param {String|Object} [options.background={r: 0, g: 0, b: 0}] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black.
* @returns {Sharp} * @returns {Sharp}
*/ */
@@ -189,11 +190,15 @@ function flatten (options) {
* This can improve the perceived brightness of a resized image in non-linear colour spaces. * This can improve the perceived brightness of a resized image in non-linear colour spaces.
* JPEG and WebP input images will not take advantage of the shrink-on-load performance optimisation * JPEG and WebP input images will not take advantage of the shrink-on-load performance optimisation
* when applying a gamma correction. * when applying a gamma correction.
*
* Supply a second argument to use a different output gamma value, otherwise the first value is used in both cases.
*
* @param {Number} [gamma=2.2] value between 1.0 and 3.0. * @param {Number} [gamma=2.2] value between 1.0 and 3.0.
* @param {Number} [gammaOut] value between 1.0 and 3.0. (optional, defaults to same as `gamma`)
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
function gamma (gamma) { function gamma (gamma, gammaOut) {
if (!is.defined(gamma)) { if (!is.defined(gamma)) {
// Default gamma correction of 2.2 (sRGB) // Default gamma correction of 2.2 (sRGB)
this.options.gamma = 2.2; this.options.gamma = 2.2;
@@ -202,6 +207,14 @@ function gamma (gamma) {
} else { } else {
throw new Error('Invalid gamma correction (1.0 to 3.0) ' + gamma); throw new Error('Invalid gamma correction (1.0 to 3.0) ' + gamma);
} }
if (!is.defined(gammaOut)) {
// Default gamma correction for output is same as input
this.options.gammaOut = this.options.gamma;
} else if (is.number(gammaOut) && is.inRange(gammaOut, 1, 3)) {
this.options.gammaOut = gammaOut;
} else {
throw new Error('Invalid output gamma correction (1.0 to 3.0) ' + gammaOut);
}
return this; return this;
} }
@@ -365,12 +378,49 @@ function linear (a, b) {
return this; return this;
} }
/**
* Recomb the image with the specified matrix.
*
* @example
* sharp(input)
* .recomb([
* [0.3588, 0.7044, 0.1368],
* [0.2990, 0.5870, 0.1140],
* [0.2392, 0.4696, 0.0912],
* ])
* .raw()
* .toBuffer(function(err, data, info) {
* // data contains the raw pixel data after applying the recomb
* // With this example input, a sepia filter has been applied
* });
*
* @param {Array<Array<Number>>} 3x3 Recombination matrix
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
function recomb (inputMatrix) {
if (!Array.isArray(inputMatrix) || inputMatrix.length !== 3 ||
inputMatrix[0].length !== 3 ||
inputMatrix[1].length !== 3 ||
inputMatrix[2].length !== 3
) {
// must pass in a kernel
throw new Error('Invalid Recomb Matrix');
}
this.options.recombMatrix = [
inputMatrix[0][0], inputMatrix[0][1], inputMatrix[0][2],
inputMatrix[1][0], inputMatrix[1][1], inputMatrix[1][2],
inputMatrix[2][0], inputMatrix[2][1], inputMatrix[2][2]
].map(Number);
return this;
}
/** /**
* Decorate the Sharp prototype with operation-related functions. * Decorate the Sharp prototype with operation-related functions.
* @private * @private
*/ */
module.exports = function (Sharp) { module.exports = function (Sharp) {
[ Object.assign(Sharp.prototype, {
rotate, rotate,
flip, flip,
flop, flop,
@@ -385,8 +435,7 @@ module.exports = function (Sharp) {
convolve, convolve,
threshold, threshold,
boolean, boolean,
linear linear,
].forEach(function (f) { recomb
Sharp.prototype[f.name] = f;
}); });
}; };

View File

@@ -304,6 +304,10 @@ function webp (options) {
* @param {Boolean} [options.force=true] - force TIFF output, otherwise attempt to use input format * @param {Boolean} [options.force=true] - force TIFF output, otherwise attempt to use input format
* @param {Boolean} [options.compression='jpeg'] - compression options: lzw, deflate, jpeg, ccittfax4 * @param {Boolean} [options.compression='jpeg'] - compression options: lzw, deflate, jpeg, ccittfax4
* @param {Boolean} [options.predictor='horizontal'] - compression predictor options: none, horizontal, float * @param {Boolean} [options.predictor='horizontal'] - compression predictor options: none, horizontal, float
* @param {Boolean} [options.pyramid=false] - write an image pyramid
* @param {Boolean} [options.tile=false] - write a tiled tiff
* @param {Boolean} [options.tileWidth=256] - horizontal tile size
* @param {Boolean} [options.tileHeight=256] - vertical tile size
* @param {Number} [options.xres=1.0] - horizontal resolution in pixels/mm * @param {Number} [options.xres=1.0] - horizontal resolution in pixels/mm
* @param {Number} [options.yres=1.0] - vertical resolution in pixels/mm * @param {Number} [options.yres=1.0] - vertical resolution in pixels/mm
* @param {Boolean} [options.squash=false] - squash 8-bit images down to 1 bit * @param {Boolean} [options.squash=false] - squash 8-bit images down to 1 bit
@@ -311,29 +315,60 @@ function webp (options) {
* @throws {Error} Invalid options * @throws {Error} Invalid options
*/ */
function tiff (options) { function tiff (options) {
if (is.object(options) && is.defined(options.quality)) { if (is.object(options)) {
if (is.defined(options.quality)) {
if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) { if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
this.options.tiffQuality = options.quality; this.options.tiffQuality = options.quality;
} else { } else {
throw new Error('Invalid quality (integer, 1-100) ' + options.quality); throw new Error('Invalid quality (integer, 1-100) ' + options.quality);
} }
} }
if (is.object(options) && is.defined(options.squash)) { if (is.defined(options.squash)) {
if (is.bool(options.squash)) { if (is.bool(options.squash)) {
this.options.tiffSquash = options.squash; this.options.tiffSquash = options.squash;
} else { } else {
throw new Error('Invalid Value for squash ' + options.squash + ' Only Boolean Values allowed for options.squash.'); throw new Error('Invalid Value for squash ' + options.squash + ' Only Boolean Values allowed for options.squash.');
} }
} }
// tiling
if (is.defined(options.tile)) {
if (is.bool(options.tile)) {
this.options.tiffTile = options.tile;
} else {
throw new Error('Invalid Value for tile ' + options.tile + ' Only Boolean values allowed for options.tile');
}
}
if (is.defined(options.tileWidth)) {
if (is.number(options.tileWidth) && options.tileWidth > 0) {
this.options.tiffTileWidth = options.tileWidth;
} else {
throw new Error('Invalid Value for tileWidth ' + options.tileWidth + ' Only positive numeric values allowed for options.tileWidth');
}
}
if (is.defined(options.tileHeight)) {
if (is.number(options.tileHeight) && options.tileHeight > 0) {
this.options.tiffTileHeight = options.tileHeight;
} else {
throw new Error('Invalid Value for tileHeight ' + options.tileHeight + ' Only positive numeric values allowed for options.tileHeight');
}
}
// pyramid
if (is.defined(options.pyramid)) {
if (is.bool(options.pyramid)) {
this.options.tiffPyramid = options.pyramid;
} else {
throw new Error('Invalid Value for pyramid ' + options.pyramid + ' Only Boolean values allowed for options.pyramid');
}
}
// resolution // resolution
if (is.object(options) && is.defined(options.xres)) { if (is.defined(options.xres)) {
if (is.number(options.xres)) { if (is.number(options.xres)) {
this.options.tiffXres = options.xres; this.options.tiffXres = options.xres;
} else { } else {
throw new Error('Invalid Value for xres ' + options.xres + ' Only numeric values allowed for options.xres'); throw new Error('Invalid Value for xres ' + options.xres + ' Only numeric values allowed for options.xres');
} }
} }
if (is.object(options) && is.defined(options.yres)) { if (is.defined(options.yres)) {
if (is.number(options.yres)) { if (is.number(options.yres)) {
this.options.tiffYres = options.yres; this.options.tiffYres = options.yres;
} else { } else {
@@ -341,7 +376,7 @@ function tiff (options) {
} }
} }
// compression // compression
if (is.defined(options) && is.defined(options.compression)) { if (is.defined(options.compression)) {
if (is.string(options.compression) && is.inArray(options.compression, ['lzw', 'deflate', 'jpeg', 'ccittfax4', 'none'])) { if (is.string(options.compression) && is.inArray(options.compression, ['lzw', 'deflate', 'jpeg', 'ccittfax4', 'none'])) {
this.options.tiffCompression = options.compression; this.options.tiffCompression = options.compression;
} else { } else {
@@ -350,7 +385,7 @@ function tiff (options) {
} }
} }
// predictor // predictor
if (is.defined(options) && is.defined(options.predictor)) { if (is.defined(options.predictor)) {
if (is.string(options.predictor) && is.inArray(options.predictor, ['none', 'horizontal', 'float'])) { if (is.string(options.predictor) && is.inArray(options.predictor, ['none', 'horizontal', 'float'])) {
this.options.tiffPredictor = options.predictor; this.options.tiffPredictor = options.predictor;
} else { } else {
@@ -358,6 +393,7 @@ function tiff (options) {
throw new Error(message); throw new Error(message);
} }
} }
}
return this._updateFormatOut('tiff', options); return this._updateFormatOut('tiff', options);
} }
@@ -641,7 +677,7 @@ function _pipeline (callback) {
* @private * @private
*/ */
module.exports = function (Sharp) { module.exports = function (Sharp) {
[ Object.assign(Sharp.prototype, {
// Public // Public
toFile, toFile,
toBuffer, toBuffer,
@@ -658,7 +694,5 @@ module.exports = function (Sharp) {
_setBooleanOption, _setBooleanOption,
_read, _read,
_pipeline _pipeline
].forEach(function (f) {
Sharp.prototype[f.name] = f;
}); });
}; };

View File

@@ -55,6 +55,7 @@ const strategy = {
const kernel = { const kernel = {
nearest: 'nearest', nearest: 'nearest',
cubic: 'cubic', cubic: 'cubic',
mitchell: 'mitchell',
lanczos2: 'lanczos2', lanczos2: 'lanczos2',
lanczos3: 'lanczos3' lanczos3: 'lanczos3'
}; };
@@ -110,6 +111,7 @@ const mapFitToCanvas = {
* Possible interpolation kernels are: * Possible interpolation kernels are:
* - `nearest`: Use [nearest neighbour interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation). * - `nearest`: Use [nearest neighbour interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation).
* - `cubic`: Use a [Catmull-Rom spline](https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline). * - `cubic`: Use a [Catmull-Rom spline](https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline).
* - `mitchell`: Use a [Mitchell-Netravali spline](https://www.cs.utexas.edu/~fussell/courses/cs384g-fall2013/lectures/mitchell/Mitchell.pdf).
* - `lanczos2`: Use a [Lanczos kernel](https://en.wikipedia.org/wiki/Lanczos_resampling#Lanczos_kernel) with `a=2`. * - `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). * - `lanczos3`: Use a Lanczos kernel with `a=3` (the default).
* *
@@ -269,7 +271,6 @@ function resize (width, height, options) {
* // to the top, left and right edges and 20 to the bottom edge * // to the top, left and right edges and 20 to the bottom edge
* sharp(input) * sharp(input)
* .resize(140) * .resize(140)
* .)
* .extend({ * .extend({
* top: 10, * top: 10,
* bottom: 20, * bottom: 20,
@@ -468,13 +469,11 @@ function withoutEnlargement (withoutEnlargement) {
* @private * @private
*/ */
module.exports = function (Sharp) { module.exports = function (Sharp) {
[ Object.assign(Sharp.prototype, {
resize, resize,
extend, extend,
extract, extract,
trim trim
].forEach(function (f) {
Sharp.prototype[f.name] = f;
}); });
// Class attributes // Class attributes
Sharp.gravity = gravity; Sharp.gravity = gravity;

View File

@@ -1,7 +1,7 @@
{ {
"name": "sharp", "name": "sharp",
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP and TIFF images", "description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP and TIFF images",
"version": "0.21.0", "version": "0.21.1",
"author": "Lovell Fuller <npm@lovell.info>", "author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://github.com/lovell/sharp", "homepage": "https://github.com/lovell/sharp",
"contributors": [ "contributors": [
@@ -55,13 +55,19 @@
"Alun Davies <alun.owain.davies@googlemail.com>", "Alun Davies <alun.owain.davies@googlemail.com>",
"Aidan Hoolachan <ajhoolachan21@gmail.com>", "Aidan Hoolachan <ajhoolachan21@gmail.com>",
"Axel Eirola <axel.eirola@iki.fi>", "Axel Eirola <axel.eirola@iki.fi>",
"Freezy <freezy@xbmc.org>" "Freezy <freezy@xbmc.org>",
"Daiz <taneli.vatanen@gmail.com>",
"Julian Aubourg <j@ubourg.net>",
"Keith Belovay <keith@picthrive.com>",
"Michael B. Klein <mbklein@gmail.com>"
], ],
"scripts": { "scripts": {
"install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node-gyp rebuild && node install/dll-copy)", "install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node-gyp rebuild && node install/dll-copy)",
"clean": "rm -rf node_modules/ build/ vendor/ .nyc_output/ coverage/ test/fixtures/output.*", "clean": "rm -rf node_modules/ build/ vendor/ .nyc_output/ coverage/ test/fixtures/output.*",
"test": "semistandard && cc && nyc --reporter=lcov --branches=99 mocha --slow=5000 --timeout=60000 ./test/unit/*.js && prebuild-ci", "test": "semistandard && cc && npm run test-unit && npm run test-licensing && prebuild-ci",
"coverage": "./test/coverage/report.sh", "test-unit": "nyc --reporter=lcov --branches=99 mocha --slow=5000 --timeout=60000 ./test/unit/*.js",
"test-licensing": "license-checker --production --summary --onlyAllow=\"Apache-2.0;BSD;ISC;MIT\"",
"test-coverage": "./test/coverage/report.sh",
"test-leak": "./test/leak/leak.sh", "test-leak": "./test/leak/leak.sh",
"docs": "for m in constructor input resize composite operation colour channel output utility; do documentation build --shallow --format=md --markdown-toc=false lib/$m.js >docs/api-$m.md; done" "docs": "for m in constructor input resize composite operation colour channel output utility; do documentation build --shallow --format=md --markdown-toc=false lib/$m.js >docs/api-$m.md; done"
}, },
@@ -87,15 +93,16 @@
"vips" "vips"
], ],
"dependencies": { "dependencies": {
"color": "^3.0.0", "bindings": "^1.3.1",
"color": "^3.1.0",
"detect-libc": "^1.0.3", "detect-libc": "^1.0.3",
"nan": "^2.11.1",
"fs-copy-file-sync": "^1.1.1", "fs-copy-file-sync": "^1.1.1",
"nan": "^2.11.1",
"npmlog": "^4.1.2", "npmlog": "^4.1.2",
"prebuild-install": "^5.2.0", "prebuild-install": "^5.2.2",
"semver": "^5.5.1", "semver": "^5.6.0",
"simple-get": "^3.0.3", "simple-get": "^3.0.3",
"tar": "^4.4.6", "tar": "^4.4.8",
"tunnel-agent": "^0.6.0" "tunnel-agent": "^0.6.0"
}, },
"devDependencies": { "devDependencies": {
@@ -105,13 +112,14 @@
"documentation": "^8.1.2", "documentation": "^8.1.2",
"exif-reader": "^1.0.2", "exif-reader": "^1.0.2",
"icc": "^1.0.0", "icc": "^1.0.0",
"license-checker": "^24.0.1",
"mocha": "^5.2.0", "mocha": "^5.2.0",
"mock-fs": "^4.7.0", "mock-fs": "^4.7.0",
"nyc": "^13.1.0", "nyc": "^13.1.0",
"prebuild": "^8.1.0", "prebuild": "^8.1.2",
"prebuild-ci": "^2.2.3", "prebuild-ci": "^2.3.0",
"rimraf": "^2.6.2", "rimraf": "^2.6.2",
"semistandard": "^12.0.1" "semistandard": "^13.0.1"
}, },
"license": "Apache-2.0", "license": "Apache-2.0",
"config": { "config": {

View File

@@ -278,6 +278,25 @@ namespace sharp {
return image.conv(kernel); return image.conv(kernel);
} }
/*
* Recomb with a Matrix of the given bands/channel size.
* Eg. RGB will be a 3x3 matrix.
*/
VImage Recomb(VImage image, std::unique_ptr<double[]> const &matrix) {
double *m = matrix.get();
return image
.colourspace(VIPS_INTERPRETATION_sRGB)
.recomb(image.bands() == 3
? VImage::new_from_memory(
m, 9 * sizeof(double), 3, 3, 1, VIPS_FORMAT_DOUBLE
)
: VImage::new_matrixv(4, 4,
m[0], m[1], m[2], 0.0,
m[3], m[4], m[5], 0.0,
m[6], m[7], m[8], 0.0,
0.0, 0.0, 0.0, 1.0));
}
/* /*
* Sharpen flat and jagged areas. Use sigma of -1.0 for fast sharpen. * Sharpen flat and jagged areas. Use sigma of -1.0 for fast sharpen.
*/ */

View File

@@ -107,6 +107,12 @@ namespace sharp {
*/ */
VImage Linear(VImage image, double const a, double const b); VImage Linear(VImage image, double const a, double const b);
/*
* Recomb with a Matrix of the given bands/channel size.
* Eg. RGB will be a 3x3 matrix.
*/
VImage Recomb(VImage image, std::unique_ptr<double[]> const &matrix);
} // namespace sharp } // namespace sharp
#endif // SRC_OPERATIONS_H_ #endif // SRC_OPERATIONS_H_

View File

@@ -381,7 +381,7 @@ class PipelineWorker : public Nan::AsyncWorker {
vips_enum_from_nick(nullptr, VIPS_TYPE_KERNEL, baton->kernel.data())); vips_enum_from_nick(nullptr, VIPS_TYPE_KERNEL, baton->kernel.data()));
if ( if (
kernel != VIPS_KERNEL_NEAREST && kernel != VIPS_KERNEL_CUBIC && kernel != VIPS_KERNEL_LANCZOS2 && kernel != VIPS_KERNEL_NEAREST && kernel != VIPS_KERNEL_CUBIC && kernel != VIPS_KERNEL_LANCZOS2 &&
kernel != VIPS_KERNEL_LANCZOS3 kernel != VIPS_KERNEL_LANCZOS3 && kernel != VIPS_KERNEL_MITCHELL
) { ) {
throw vips::VError("Unknown kernel"); throw vips::VError("Unknown kernel");
} }
@@ -525,6 +525,11 @@ class PipelineWorker : public Nan::AsyncWorker {
baton->convKernel); baton->convKernel);
} }
// Recomb
if (baton->recombMatrix != NULL) {
image = sharp::Recomb(image, baton->recombMatrix);
}
// Sharpen // Sharpen
if (shouldSharpen) { if (shouldSharpen) {
image = sharp::Sharpen(image, baton->sharpenSigma, baton->sharpenFlat, baton->sharpenJagged); image = sharp::Sharpen(image, baton->sharpenSigma, baton->sharpenFlat, baton->sharpenJagged);
@@ -612,8 +617,8 @@ class PipelineWorker : public Nan::AsyncWorker {
baton->premultiplied = shouldPremultiplyAlpha; baton->premultiplied = shouldPremultiplyAlpha;
// Gamma decoding (brighten) // Gamma decoding (brighten)
if (baton->gamma >= 1 && baton->gamma <= 3) { if (baton->gammaOut >= 1 && baton->gammaOut <= 3) {
image = sharp::Gamma(image, baton->gamma); image = sharp::Gamma(image, baton->gammaOut);
} }
// Linear adjustment (a * in + b) // Linear adjustment (a * in + b)
@@ -758,6 +763,10 @@ class PipelineWorker : public Nan::AsyncWorker {
->set("squash", baton->tiffSquash) ->set("squash", baton->tiffSquash)
->set("compression", baton->tiffCompression) ->set("compression", baton->tiffCompression)
->set("predictor", baton->tiffPredictor) ->set("predictor", baton->tiffPredictor)
->set("pyramid", baton->tiffPyramid)
->set("tile", baton->tiffTile)
->set("tile_height", baton->tiffTileHeight)
->set("tile_width", baton->tiffTileWidth)
->set("xres", baton->tiffXres) ->set("xres", baton->tiffXres)
->set("yres", baton->tiffYres))); ->set("yres", baton->tiffYres)));
baton->bufferOut = static_cast<char*>(area->data); baton->bufferOut = static_cast<char*>(area->data);
@@ -771,6 +780,7 @@ class PipelineWorker : public Nan::AsyncWorker {
if (baton->greyscale || image.interpretation() == VIPS_INTERPRETATION_B_W) { if (baton->greyscale || image.interpretation() == VIPS_INTERPRETATION_B_W) {
// Extract first band for greyscale image // Extract first band for greyscale image
image = image[0]; image = image[0];
baton->channels = 1;
} }
if (image.format() != VIPS_FORMAT_UCHAR) { if (image.format() != VIPS_FORMAT_UCHAR) {
// Cast pixels to uint8 (unsigned char) // Cast pixels to uint8 (unsigned char)
@@ -856,6 +866,10 @@ class PipelineWorker : public Nan::AsyncWorker {
->set("squash", baton->tiffSquash) ->set("squash", baton->tiffSquash)
->set("compression", baton->tiffCompression) ->set("compression", baton->tiffCompression)
->set("predictor", baton->tiffPredictor) ->set("predictor", baton->tiffPredictor)
->set("pyramid", baton->tiffPyramid)
->set("tile", baton->tiffTile)
->set("tile_height", baton->tiffTileHeight)
->set("tile_width", baton->tiffTileWidth)
->set("xres", baton->tiffXres) ->set("xres", baton->tiffXres)
->set("yres", baton->tiffYres)); ->set("yres", baton->tiffYres));
baton->formatOut = "tiff"; baton->formatOut = "tiff";
@@ -1193,6 +1207,7 @@ NAN_METHOD(pipeline) {
baton->thresholdGrayscale = AttrTo<bool>(options, "thresholdGrayscale"); baton->thresholdGrayscale = AttrTo<bool>(options, "thresholdGrayscale");
baton->trimThreshold = AttrTo<double>(options, "trimThreshold"); baton->trimThreshold = AttrTo<double>(options, "trimThreshold");
baton->gamma = AttrTo<double>(options, "gamma"); baton->gamma = AttrTo<double>(options, "gamma");
baton->gammaOut = AttrTo<double>(options, "gammaOut");
baton->linearA = AttrTo<double>(options, "linearA"); baton->linearA = AttrTo<double>(options, "linearA");
baton->linearB = AttrTo<double>(options, "linearB"); baton->linearB = AttrTo<double>(options, "linearB");
baton->greyscale = AttrTo<bool>(options, "greyscale"); baton->greyscale = AttrTo<bool>(options, "greyscale");
@@ -1232,6 +1247,13 @@ NAN_METHOD(pipeline) {
baton->convKernel[i] = AttrTo<double>(kdata, i); baton->convKernel[i] = AttrTo<double>(kdata, i);
} }
} }
if (HasAttr(options, "recombMatrix")) {
baton->recombMatrix = std::unique_ptr<double[]>(new double[9]);
v8::Local<v8::Array> recombMatrix = AttrAs<v8::Array>(options, "recombMatrix");
for (unsigned int i = 0; i < 9; i++) {
baton->recombMatrix[i] = AttrTo<double>(recombMatrix, i);
}
}
baton->colourspace = sharp::GetInterpretation(AttrAsStr(options, "colourspace")); baton->colourspace = sharp::GetInterpretation(AttrAsStr(options, "colourspace"));
if (baton->colourspace == VIPS_INTERPRETATION_ERROR) { if (baton->colourspace == VIPS_INTERPRETATION_ERROR) {
baton->colourspace = VIPS_INTERPRETATION_sRGB; baton->colourspace = VIPS_INTERPRETATION_sRGB;
@@ -1258,7 +1280,11 @@ NAN_METHOD(pipeline) {
baton->webpLossless = AttrTo<bool>(options, "webpLossless"); baton->webpLossless = AttrTo<bool>(options, "webpLossless");
baton->webpNearLossless = AttrTo<bool>(options, "webpNearLossless"); baton->webpNearLossless = AttrTo<bool>(options, "webpNearLossless");
baton->tiffQuality = AttrTo<uint32_t>(options, "tiffQuality"); baton->tiffQuality = AttrTo<uint32_t>(options, "tiffQuality");
baton->tiffPyramid = AttrTo<bool>(options, "tiffPyramid");
baton->tiffSquash = AttrTo<bool>(options, "tiffSquash"); baton->tiffSquash = AttrTo<bool>(options, "tiffSquash");
baton->tiffTile = AttrTo<bool>(options, "tiffTile");
baton->tiffTileWidth = AttrTo<uint32_t>(options, "tiffTileWidth");
baton->tiffTileHeight = AttrTo<uint32_t>(options, "tiffTileHeight");
baton->tiffXres = AttrTo<double>(options, "tiffXres"); baton->tiffXres = AttrTo<double>(options, "tiffXres");
baton->tiffYres = AttrTo<double>(options, "tiffYres"); baton->tiffYres = AttrTo<double>(options, "tiffYres");
// tiff compression options // tiff compression options

View File

@@ -87,6 +87,7 @@ struct PipelineBaton {
double linearA; double linearA;
double linearB; double linearB;
double gamma; double gamma;
double gammaOut;
bool greyscale; bool greyscale;
bool normalise; bool normalise;
bool useExifOrientation; bool useExifOrientation;
@@ -121,7 +122,11 @@ struct PipelineBaton {
int tiffQuality; int tiffQuality;
VipsForeignTiffCompression tiffCompression; VipsForeignTiffCompression tiffCompression;
VipsForeignTiffPredictor tiffPredictor; VipsForeignTiffPredictor tiffPredictor;
bool tiffPyramid;
bool tiffSquash; bool tiffSquash;
bool tiffTile;
int tiffTileHeight;
int tiffTileWidth;
double tiffXres; double tiffXres;
double tiffYres; double tiffYres;
std::string err; std::string err;
@@ -145,6 +150,7 @@ struct PipelineBaton {
std::string tileFormat; std::string tileFormat;
int tileAngle; int tileAngle;
VipsForeignDzDepth tileDepth; VipsForeignDzDepth tileDepth;
std::unique_ptr<double[]> recombMatrix;
PipelineBaton(): PipelineBaton():
input(nullptr), input(nullptr),
@@ -213,7 +219,11 @@ struct PipelineBaton {
tiffQuality(80), tiffQuality(80),
tiffCompression(VIPS_FOREIGN_TIFF_COMPRESSION_JPEG), tiffCompression(VIPS_FOREIGN_TIFF_COMPRESSION_JPEG),
tiffPredictor(VIPS_FOREIGN_TIFF_PREDICTOR_HORIZONTAL), tiffPredictor(VIPS_FOREIGN_TIFF_PREDICTOR_HORIZONTAL),
tiffPyramid(false),
tiffSquash(false), tiffSquash(false),
tiffTile(false),
tiffTileHeight(256),
tiffTileWidth(256),
tiffXres(1.0), tiffXres(1.0),
tiffYres(1.0), tiffYres(1.0),
withMetadata(false), withMetadata(false),

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -49,8 +49,8 @@ describe('failOnError', function () {
it('returns errors to callback for truncated JPEG when failOnError is set', function (done) { it('returns errors to callback for truncated JPEG when failOnError is set', function (done) {
sharp(fixtures.inputJpgTruncated, { failOnError: true }).toBuffer(function (err, data, info) { sharp(fixtures.inputJpgTruncated, { failOnError: true }).toBuffer(function (err, data, info) {
assert.ok(err.message.includes('VipsJpeg: Premature end of JPEG file'), err); assert.ok(err.message.includes('VipsJpeg: Premature end of JPEG file'), err);
assert.equal(data, null); assert.strictEqual(data, null);
assert.equal(info, null); assert.strictEqual(info, null);
done(); done();
}); });
}); });
@@ -58,8 +58,8 @@ describe('failOnError', function () {
it('returns errors to callback for truncated PNG when failOnError is set', function (done) { it('returns errors to callback for truncated PNG when failOnError is set', function (done) {
sharp(fixtures.inputPngTruncated, { failOnError: true }).toBuffer(function (err, data, info) { sharp(fixtures.inputPngTruncated, { failOnError: true }).toBuffer(function (err, data, info) {
assert.ok(err.message.includes('vipspng: libpng read error'), err); assert.ok(err.message.includes('vipspng: libpng read error'), err);
assert.equal(data, null); assert.strictEqual(data, null);
assert.equal(info, null); assert.strictEqual(info, null);
done(); done();
}); });
}); });

View File

@@ -44,6 +44,19 @@ describe('Gamma correction', function () {
}); });
}); });
it('input value of 2.2, output value of 3.0', function (done) {
sharp(fixtures.inputJpgWithGammaHoliness)
.resize(129, 111)
.gamma(2.2, 3.0)
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual('jpeg', info.format);
assert.strictEqual(129, info.width);
assert.strictEqual(111, info.height);
fixtures.assertSimilar(fixtures.expected('gamma-in-2.2-out-3.0.jpg'), data, { threshold: 6 }, done);
});
});
it('alpha transparency', function (done) { it('alpha transparency', function (done) {
sharp(fixtures.inputPngOverlayLayer1) sharp(fixtures.inputPngOverlayLayer1)
.resize(320) .resize(320)
@@ -57,9 +70,15 @@ describe('Gamma correction', function () {
}); });
}); });
it('invalid value', function () { it('invalid first parameter value', function () {
assert.throws(function () { assert.throws(function () {
sharp(fixtures.inputJpgWithGammaHoliness).gamma(4); sharp(fixtures.inputJpgWithGammaHoliness).gamma(4);
}); });
}); });
it('invalid second parameter value', function () {
assert.throws(function () {
sharp(fixtures.inputJpgWithGammaHoliness).gamma(2.2, 4);
});
});
}); });

View File

@@ -817,7 +817,7 @@ describe('Input/output', function () {
assert.strictEqual(320, withInfo.width); assert.strictEqual(320, withInfo.width);
assert.strictEqual(240, withInfo.height); assert.strictEqual(240, withInfo.height);
// Verify image is of a different size (progressive output even without mozjpeg) // Verify image is of a different size (progressive output even without mozjpeg)
assert.notEqual(withData.length, withoutData.length); assert.notStrictEqual(withData.length, withoutData.length);
done(); done();
}); });
}); });
@@ -1285,6 +1285,84 @@ describe('Input/output', function () {
}); });
}); });
it('TIFF tiled pyramid image without compression enlarges test file', function (done) {
const startSize = fs.statSync(fixtures.inputTiffUncompressed).size;
sharp(fixtures.inputTiffUncompressed)
.tiff({
compression: 'none',
pyramid: true,
tile: true,
tileHeight: 256,
tileWidth: 256
})
.toFile(fixtures.outputTiff, (err, info) => {
if (err) throw err;
assert.strictEqual('tiff', info.format);
assert(info.size > startSize);
rimraf(fixtures.outputTiff, done);
});
});
it('TIFF pyramid true value does not throw error', function () {
assert.doesNotThrow(function () {
sharp().tiff({ pyramid: true });
});
});
it('Invalid TIFF pyramid value throws error', function () {
assert.throws(function () {
sharp().tiff({ pyramid: 'true' });
});
});
it('Invalid TIFF tile value throws error', function () {
assert.throws(function () {
sharp().tiff({ tile: 'true' });
});
});
it('TIFF tile true value does not throw error', function () {
assert.doesNotThrow(function () {
sharp().tiff({ tile: true });
});
});
it('Valid TIFF tileHeight value does not throw error', function () {
assert.doesNotThrow(function () {
sharp().tiff({ tileHeight: 512 });
});
});
it('Valid TIFF tileWidth value does not throw error', function () {
assert.doesNotThrow(function () {
sharp().tiff({ tileWidth: 512 });
});
});
it('Invalid TIFF tileHeight value throws error', function () {
assert.throws(function () {
sharp().tiff({ tileHeight: '256' });
});
});
it('Invalid TIFF tileWidth value throws error', function () {
assert.throws(function () {
sharp().tiff({ tileWidth: '256' });
});
});
it('Invalid TIFF tileHeight value throws error', function () {
assert.throws(function () {
sharp().tiff({ tileHeight: 0 });
});
});
it('Invalid TIFF tileWidth value throws error', function () {
assert.throws(function () {
sharp().tiff({ tileWidth: 0 });
});
});
it('Input and output formats match when not forcing', function (done) { it('Input and output formats match when not forcing', function (done) {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(320, 240) .resize(320, 240)
@@ -1388,6 +1466,7 @@ describe('Input/output', function () {
assert.strictEqual('raw', info.format); assert.strictEqual('raw', info.format);
assert.strictEqual(32, info.width); assert.strictEqual(32, info.width);
assert.strictEqual(24, info.height); assert.strictEqual(24, info.height);
assert.strictEqual(1, info.channels);
done(); done();
}); });
}); });

135
test/unit/recomb.js Normal file
View File

@@ -0,0 +1,135 @@
'use strict';
const assert = require('assert');
const sharp = require('../../');
const fixtures = require('../fixtures');
describe('Recomb', function () {
it('applies a sepia filter using recomb', function (done) {
const output = fixtures.path('output.recomb-sepia.jpg');
sharp(fixtures.inputJpgWithLandscapeExif1)
.recomb([
[0.3588, 0.7044, 0.1368],
[0.299, 0.587, 0.114],
[0.2392, 0.4696, 0.0912]
])
.toFile(output, function (err, info) {
if (err) throw err;
assert.strictEqual('jpeg', info.format);
assert.strictEqual(600, info.width);
assert.strictEqual(450, info.height);
fixtures.assertMaxColourDistance(
output,
fixtures.expected('Landscape_1-recomb-sepia.jpg'),
17
);
done();
});
});
it('applies a sepia filter using recomb to an PNG with Alpha', function (done) {
const output = fixtures.path('output.recomb-sepia.png');
sharp(fixtures.inputPngAlphaPremultiplicationSmall)
.recomb([
[0.3588, 0.7044, 0.1368],
[0.299, 0.587, 0.114],
[0.2392, 0.4696, 0.0912]
])
.toFile(output, function (err, info) {
if (err) throw err;
assert.strictEqual('png', info.format);
assert.strictEqual(1024, info.width);
assert.strictEqual(768, info.height);
fixtures.assertMaxColourDistance(
output,
fixtures.expected('alpha-recomb-sepia.png'),
17
);
done();
});
});
it('applies a different sepia filter using recomb', function (done) {
const output = fixtures.path('output.recomb-sepia2.jpg');
sharp(fixtures.inputJpgWithLandscapeExif1)
.recomb([
[0.393, 0.769, 0.189],
[0.349, 0.686, 0.168],
[0.272, 0.534, 0.131]
])
.toFile(output, function (err, info) {
if (err) throw err;
assert.strictEqual('jpeg', info.format);
assert.strictEqual(600, info.width);
assert.strictEqual(450, info.height);
fixtures.assertMaxColourDistance(
output,
fixtures.expected('Landscape_1-recomb-sepia2.jpg'),
17
);
done();
});
});
it('increases the saturation of the image', function (done) {
const saturationLevel = 1;
const output = fixtures.path('output.recomb-saturation.jpg');
sharp(fixtures.inputJpgWithLandscapeExif1)
.recomb([
[
saturationLevel + 1 - 0.2989,
-0.587 * saturationLevel,
-0.114 * saturationLevel
],
[
-0.2989 * saturationLevel,
saturationLevel + 1 - 0.587,
-0.114 * saturationLevel
],
[
-0.2989 * saturationLevel,
-0.587 * saturationLevel,
saturationLevel + 1 - 0.114
]
])
.toFile(output, function (err, info) {
if (err) throw err;
assert.strictEqual('jpeg', info.format);
assert.strictEqual(600, info.width);
assert.strictEqual(450, info.height);
fixtures.assertMaxColourDistance(
output,
fixtures.expected('Landscape_1-recomb-saturation.jpg'),
37
);
done();
});
});
describe('invalid matrix specification', function () {
it('missing', function () {
assert.throws(function () {
sharp(fixtures.inputJpg).recomb();
});
});
it('incorrect flat data', function () {
assert.throws(function () {
sharp(fixtures.inputJpg).recomb([1, 2, 3, 4, 5, 6, 7, 8, 9]);
});
});
it('incorrect sub size', function () {
assert.throws(function () {
sharp(fixtures.inputJpg).recomb([
[1, 2, 3, 4],
[5, 6, 7, 8],
[1, 2, 9, 6]
]);
});
});
it('incorrect top size', function () {
assert.throws(function () {
sharp(fixtures.inputJpg).recomb([[1, 2, 3, 4], [5, 6, 7, 8]]);
});
});
});
});

View File

@@ -497,6 +497,7 @@ describe('Resize dimensions', function () {
[ [
sharp.kernel.nearest, sharp.kernel.nearest,
sharp.kernel.cubic, sharp.kernel.cubic,
sharp.kernel.mitchell,
sharp.kernel.lanczos2, sharp.kernel.lanczos2,
sharp.kernel.lanczos3 sharp.kernel.lanczos3
].forEach(function (kernel) { ].forEach(function (kernel) {

View File

@@ -132,7 +132,7 @@ describe('Threshold', function () {
it('color threshold', function (done) { it('color threshold', function (done) {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(320, 240) .resize(320, 240)
.threshold(128, {'grayscale': false}) .threshold(128, { grayscale: false })
.toBuffer(function (err, data, info) { .toBuffer(function (err, data, info) {
if (err) throw err; if (err) throw err;
assert.strictEqual('jpeg', info.format); assert.strictEqual('jpeg', info.format);