Compare commits
60 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4aff57b071 | ||
|
|
1df8d82fe0 | ||
|
|
98e90784f4 | ||
|
|
87ea54cc66 | ||
|
|
d5e98bc8ad | ||
|
|
fa69ff773a | ||
|
|
a183bb1cac | ||
|
|
cf62372cab | ||
|
|
56fa9c95a1 | ||
|
|
32a34a8841 | ||
|
|
98797445de | ||
|
|
bd377438b6 | ||
|
|
9dd6510de6 | ||
|
|
93ad9d4a4a | ||
|
|
4c01a099ea | ||
|
|
8e70579e47 | ||
|
|
ee8bfa3980 | ||
|
|
c5dfa49cae | ||
|
|
0822404129 | ||
|
|
144f39cd45 | ||
|
|
87f191fd05 | ||
|
|
37ed436202 | ||
|
|
88e490356d | ||
|
|
7c631c0787 | ||
|
|
f5d3721fe0 | ||
|
|
cc633589d9 | ||
|
|
cc1d4c1a6d | ||
|
|
30ca424942 | ||
|
|
813831acf0 | ||
|
|
a54fe9f77c | ||
|
|
8c6da5548a | ||
|
|
a2aa7d69e7 | ||
|
|
34d5252242 | ||
|
|
f31e4d2869 | ||
|
|
c695c40abc | ||
|
|
fd1ca1dbb2 | ||
|
|
f25dbd5f61 | ||
|
|
541e7104fd | ||
|
|
94945cf6ac | ||
|
|
db76e655f8 | ||
|
|
d43c7b581d | ||
|
|
383b933e26 | ||
|
|
d26ccf6294 | ||
|
|
6f9699f605 | ||
|
|
1e9093d781 | ||
|
|
9dc6492e52 | ||
|
|
d22f7cae6a | ||
|
|
473afaab45 | ||
|
|
dcd68303a4 | ||
|
|
03394556b5 | ||
|
|
1c4f6f75f3 | ||
|
|
f00928dedb | ||
|
|
a48f8fbb61 | ||
|
|
1fa388370e | ||
|
|
95ef6b3f71 | ||
|
|
de11d36d00 | ||
|
|
d77c2adabe | ||
|
|
c89c055ae0 | ||
|
|
dac8117f32 | ||
|
|
937b091bab |
21
.travis.yml
@@ -21,6 +21,12 @@ matrix:
|
||||
after_success:
|
||||
- npm install coveralls
|
||||
- cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js
|
||||
- name: "Linux (glibc) - Node 11"
|
||||
os: linux
|
||||
dist: trusty
|
||||
sudo: false
|
||||
language: node_js
|
||||
node_js: "11"
|
||||
- name: "Linux (musl) - Node 8"
|
||||
os: linux
|
||||
dist: trusty
|
||||
@@ -41,6 +47,16 @@ matrix:
|
||||
- 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: "Linux (musl) - Node 11"
|
||||
os: linux
|
||||
dist: trusty
|
||||
sudo: true
|
||||
language: minimal
|
||||
before_install:
|
||||
- sudo docker run -dit --name sharp --env CI --env PREBUILD_TOKEN --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"
|
||||
os: osx
|
||||
osx_image: xcode9.2
|
||||
@@ -56,3 +72,8 @@ matrix:
|
||||
osx_image: xcode9.2
|
||||
language: node_js
|
||||
node_js: "10"
|
||||
- name: "OS X - Node 11"
|
||||
os: osx
|
||||
osx_image: xcode9.2
|
||||
language: node_js
|
||||
node_js: "11"
|
||||
|
||||
@@ -41,8 +41,8 @@ Any change that modifies the existing public API should be added to the relevant
|
||||
|
||||
| Release | WIP branch |
|
||||
| ------: | :--------- |
|
||||
| v0.21.0 | teeth |
|
||||
| v0.22.0 | uptake |
|
||||
| v0.23.0 | vision |
|
||||
|
||||
Please squash your changes into a single commit using a command like `git rebase -i upstream/<wip-branch>`.
|
||||
|
||||
|
||||
20
README.md
@@ -1,5 +1,7 @@
|
||||
# sharp
|
||||
|
||||
<img src="docs/image/sharp-logo.svg" width="160" height="160" alt="sharp logo" align="right">
|
||||
|
||||
```sh
|
||||
npm install sharp
|
||||
```
|
||||
@@ -22,7 +24,7 @@ As well as image resizing, operations such as
|
||||
rotation, extraction, compositing and gamma correction are available.
|
||||
|
||||
Most modern 64-bit OS X, Windows and Linux systems running
|
||||
Node versions 6, 8 and 10
|
||||
Node versions 6, 8, 10 and 11
|
||||
do not require any additional install or runtime dependencies.
|
||||
|
||||
## Examples
|
||||
@@ -63,15 +65,15 @@ readableStream
|
||||
.pipe(writableStream);
|
||||
```
|
||||
|
||||
[](https://coveralls.io/r/lovell/sharp?branch=master)
|
||||
[](https://coveralls.io/r/lovell/sharp?branch=master)
|
||||
|
||||
### Documentation
|
||||
|
||||
Visit [sharp.pixelplumbing.com](http://sharp.pixelplumbing.com/) for complete
|
||||
[installation instructions](http://sharp.pixelplumbing.com/page/install),
|
||||
[API documentation](http://sharp.pixelplumbing.com/page/api),
|
||||
[benchmark tests](http://sharp.pixelplumbing.com/page/performance) and
|
||||
[changelog](http://sharp.pixelplumbing.com/page/changelog).
|
||||
Visit [sharp.pixelplumbing.com](https://sharp.pixelplumbing.com/) for complete
|
||||
[installation instructions](https://sharp.pixelplumbing.com/page/install),
|
||||
[API documentation](https://sharp.pixelplumbing.com/page/api),
|
||||
[benchmark tests](https://sharp.pixelplumbing.com/page/performance) and
|
||||
[changelog](https://sharp.pixelplumbing.com/page/changelog).
|
||||
|
||||
### Contributing
|
||||
|
||||
@@ -80,12 +82,12 @@ covers reporting bugs, requesting features and submitting code changes.
|
||||
|
||||
### Licensing
|
||||
|
||||
Copyright 2013, 2014, 2015, 2016, 2017, 2018 Lovell Fuller and contributors.
|
||||
Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 Lovell Fuller and contributors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
[http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0.html)
|
||||
[https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
|
||||
@@ -7,6 +7,7 @@ environment:
|
||||
- nodejs_version: "6"
|
||||
- nodejs_version: "8"
|
||||
- nodejs_version: "10"
|
||||
- nodejs_version: "11"
|
||||
install:
|
||||
- ps: Install-Product node $env:nodejs_version x64
|
||||
- npm install -g npm@5
|
||||
|
||||
12
binding.gyp
@@ -183,13 +183,23 @@
|
||||
},
|
||||
'configurations': {
|
||||
'Release': {
|
||||
'cflags_cc': [
|
||||
'-Wno-cast-function-type',
|
||||
'-Wno-deprecated-declarations'
|
||||
],
|
||||
'xcode_settings': {
|
||||
'OTHER_CPLUSPLUSFLAGS': [
|
||||
'-Wno-deprecated-declarations'
|
||||
]
|
||||
},
|
||||
'msvs_settings': {
|
||||
'VCCLCompilerTool': {
|
||||
'ExceptionHandling': 1
|
||||
}
|
||||
},
|
||||
'msvs_disabled_warnings': [
|
||||
4275
|
||||
4275,
|
||||
4996
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -16,6 +16,22 @@ sharp('rgba.png')
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
## ensureAlpha
|
||||
|
||||
Ensure alpha channel, if missing. The added alpha channel will be fully opaque. This is a no-op if the image already has an alpha channel.
|
||||
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
sharp('rgb.jpg')
|
||||
.ensureAlpha()
|
||||
.toFile('rgba.png', function(err, info) {
|
||||
// rgba.png is a 4 channel image with a fully opaque alpha channel
|
||||
});
|
||||
```
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
## extractChannel
|
||||
|
||||
Extract a single channel from a multi-channel image.
|
||||
|
||||
@@ -11,7 +11,7 @@ If the overlay image contains an alpha channel then composition with premultipli
|
||||
|
||||
### Parameters
|
||||
|
||||
- `overlay` **([Buffer][1] \| [String][2])** Buffer containing image data or String containing the path to an image file.
|
||||
- `overlay` **([Buffer][1] \| [String][2])?** Buffer containing image data or String containing the path to an image file.
|
||||
- `options` **[Object][3]?**
|
||||
- `options.gravity` **[String][2]** gravity at which to place the overlay. (optional, default `'centre'`)
|
||||
- `options.top` **[Number][4]?** the pixel offset from the top edge.
|
||||
@@ -35,8 +35,7 @@ If the overlay image contains an alpha channel then composition with premultipli
|
||||
sharp('input.png')
|
||||
.rotate(180)
|
||||
.resize(300)
|
||||
.flatten()
|
||||
.background('#ff6600')
|
||||
.flatten( { background: '#ff6600' } )
|
||||
.overlayWith('overlay.png', { gravity: sharp.gravity.southeast } )
|
||||
.sharpen()
|
||||
.withMetadata()
|
||||
|
||||
@@ -9,9 +9,8 @@
|
||||
a 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 not present.
|
||||
- `options` **[Object][3]?** if present, is an Object with optional attributes.
|
||||
- `options.failOnError` **[Boolean][4]** by default apply a "best effort"
|
||||
to decode images, even if the data is corrupt or invalid. Set this flag to true
|
||||
if you'd rather halt processing and raise an error when loading invalid images. (optional, default `false`)
|
||||
- `options.failOnError` **[Boolean][4]** by default halt processing and raise an error when loading invalid images.
|
||||
Set this flag to `false` if you'd rather apply a "best effort" to decode images, even if the data is corrupt or invalid. (optional, default `true`)
|
||||
- `options.density` **[Number][5]** number representing the DPI for vector images. (optional, default `72`)
|
||||
- `options.page` **[Number][5]** page number to extract for multi-page input (GIF, TIFF) (optional, default `0`)
|
||||
- `options.raw` **[Object][3]?** describes raw pixel input image data. See `raw()` for pixel ordering.
|
||||
|
||||
@@ -34,6 +34,8 @@ A Promises/A+ promise is returned when `callback` is not provided.
|
||||
- `density`: Number of pixels per inch (DPI), if present
|
||||
- `chromaSubsampling`: String containing JPEG chroma subsampling, `4:2:0` or `4:4:4` for RGB, `4:2:0:4` or `4:4:4:4` for CMYK
|
||||
- `isProgressive`: Boolean indicating whether the image is interlaced using a progressive scan
|
||||
- `pages`: Number of pages this TIFF, GIF or PDF image contains.
|
||||
- `pageHeight`: Number of pixels high each page in this PDF image will be.
|
||||
- `hasProfile`: Boolean indicating the presence of an embedded ICC profile
|
||||
- `hasAlpha`: Boolean indicating the presence of an alpha transparency channel
|
||||
- `orientation`: Number value of the EXIF Orientation header, if present
|
||||
|
||||
@@ -118,7 +118,8 @@ Merge alpha transparency channel, if any, with a background.
|
||||
|
||||
### 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**
|
||||
|
||||
@@ -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
|
||||
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
|
||||
|
||||
- `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
|
||||
@@ -250,6 +254,35 @@ Apply the linear formula a \* input + b to the image (levels adjustment)
|
||||
- `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
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
@@ -154,6 +154,11 @@ Indexed PNG input at 1, 2 or 4 bits per pixel is converted to 8 bits per pixel.
|
||||
- `options.progressive` **[Boolean][6]** use progressive (interlace) scan (optional, default `false`)
|
||||
- `options.compressionLevel` **[Number][8]** zlib compression level, 0-9 (optional, default `9`)
|
||||
- `options.adaptiveFiltering` **[Boolean][6]** use adaptive row filtering (optional, default `false`)
|
||||
- `options.palette` **[Boolean][6]** quantise to a palette-based image with alpha transparency support, requires libimagequant (optional, default `false`)
|
||||
- `options.quality` **[Number][8]** use the lowest number of colours needed to achieve given quality, requires libimagequant (optional, default `100`)
|
||||
- `options.colours` **[Number][8]** maximum number of palette entries, requires libimagequant (optional, default `256`)
|
||||
- `options.colors` **[Number][8]** alternative spelling of `options.colours`, requires libimagequant (optional, default `256`)
|
||||
- `options.dither` **[Number][8]** level of Floyd-Steinberg error diffusion, requires libimagequant (optional, default `1.0`)
|
||||
- `options.force` **[Boolean][6]** force PNG output, otherwise attempt to use input format (optional, default `true`)
|
||||
|
||||
### Examples
|
||||
@@ -206,6 +211,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.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.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.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`)
|
||||
|
||||
@@ -30,22 +30,23 @@ Possible interpolation kernels are:
|
||||
|
||||
- `nearest`: Use [nearest neighbour interpolation][4].
|
||||
- `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).
|
||||
|
||||
### Parameters
|
||||
|
||||
- `width` **[Number][7]?** 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.
|
||||
- `options` **[Object][8]?**
|
||||
- `options.width` **[String][9]?** 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.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.position` **[String][9]** 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.kernel` **[String][9]** 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.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`)
|
||||
- `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][8]?** pixels high the resultant image should be. Use `null` or `undefined` to auto-scale the height to match the width.
|
||||
- `options` **[Object][9]?**
|
||||
- `options.width` **[String][10]?** alternative means of specifying `width`. 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][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][10]** position, gravity or strategy to use when `fit` is `cover` or `contain`. (optional, default `'centre'`)
|
||||
- `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][10]** the kernel to use for image reduction. (optional, default `'lanczos3'`)
|
||||
- `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][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
|
||||
|
||||
@@ -113,7 +114,7 @@ sharp(input)
|
||||
});
|
||||
```
|
||||
|
||||
- Throws **[Error][12]** Invalid parameters
|
||||
- Throws **[Error][13]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
@@ -124,12 +125,12 @@ This operation will always occur after resizing and extraction, if any.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `extend` **([Number][7] \| [Object][8])** single pixel count to add to all edges or an Object with per-edge counts
|
||||
- `extend.top` **[Number][7]?**
|
||||
- `extend.left` **[Number][7]?**
|
||||
- `extend.bottom` **[Number][7]?**
|
||||
- `extend.right` **[Number][7]?**
|
||||
- `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` **([Number][8] \| [Object][9])** single pixel count to add to all edges or an Object with per-edge counts
|
||||
- `extend.top` **[Number][8]?**
|
||||
- `extend.left` **[Number][8]?**
|
||||
- `extend.bottom` **[Number][8]?**
|
||||
- `extend.right` **[Number][8]?**
|
||||
- `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
|
||||
|
||||
@@ -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
|
||||
sharp(input)
|
||||
.resize(140)
|
||||
.)
|
||||
.extend({
|
||||
top: 10,
|
||||
bottom: 20,
|
||||
@@ -149,7 +149,7 @@ sharp(input)
|
||||
...
|
||||
```
|
||||
|
||||
- Throws **[Error][12]** Invalid parameters
|
||||
- Throws **[Error][13]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
@@ -163,11 +163,11 @@ Extract a region of the image.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `options` **[Object][8]**
|
||||
- `options.left` **[Number][7]** zero-indexed offset from left edge
|
||||
- `options.top` **[Number][7]** zero-indexed offset from top edge
|
||||
- `options.width` **[Number][7]** dimension of extracted image
|
||||
- `options.height` **[Number][7]** dimension of extracted image
|
||||
- `options` **[Object][9]**
|
||||
- `options.left` **[Number][8]** zero-indexed offset from left edge
|
||||
- `options.top` **[Number][8]** zero-indexed offset from top edge
|
||||
- `options.width` **[Number][8]** dimension of extracted image
|
||||
- `options.height` **[Number][8]** dimension of extracted image
|
||||
|
||||
### Examples
|
||||
|
||||
@@ -189,7 +189,7 @@ sharp(input)
|
||||
});
|
||||
```
|
||||
|
||||
- Throws **[Error][12]** Invalid parameters
|
||||
- Throws **[Error][13]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
@@ -200,10 +200,10 @@ The `info` response Object will contain `trimOffsetLeft` and `trimOffsetTop` pro
|
||||
|
||||
### 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**
|
||||
|
||||
@@ -217,16 +217,18 @@ Returns **Sharp**
|
||||
|
||||
[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
|
||||
|
||||
@@ -4,6 +4,67 @@
|
||||
|
||||
Requires libvips v8.7.0.
|
||||
|
||||
#### v0.21.3 - 19<sup>th</sup> January 2019
|
||||
|
||||
* Input image decoding now fails fast, set `failOnError` to change this behaviour.
|
||||
|
||||
* Failed filesystem-based input now separates missing file and invalid format errors.
|
||||
[#1542](https://github.com/lovell/sharp/issues/1542)
|
||||
|
||||
#### v0.21.2 - 13<sup>th</sup> January 2019
|
||||
|
||||
* Ensure all metadata is removed from PNG output unless `withMetadata` used.
|
||||
|
||||
* Ensure shortest edge is at least one pixel after resizing.
|
||||
[#1003](https://github.com/lovell/sharp/issues/1003)
|
||||
|
||||
* Add `ensureAlpha` operation to add an alpha channel, if missing.
|
||||
[#1153](https://github.com/lovell/sharp/issues/1153)
|
||||
|
||||
* Expose `pages` and `pageHeight` metadata for multi-page input images.
|
||||
[#1205](https://github.com/lovell/sharp/issues/1205)
|
||||
|
||||
* Expose PNG output options requiring libimagequant.
|
||||
[#1484](https://github.com/lovell/sharp/issues/1484)
|
||||
|
||||
* Expose underlying error message for invalid input.
|
||||
[#1505](https://github.com/lovell/sharp/issues/1505)
|
||||
|
||||
* Prevent mutatation of options passed to `jpeg`.
|
||||
[#1516](https://github.com/lovell/sharp/issues/1516)
|
||||
|
||||
* Ensure forced output format applied correctly when output chaining.
|
||||
[#1528](https://github.com/lovell/sharp/issues/1528)
|
||||
|
||||
#### 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
|
||||
|
||||
* Deprecate the following resize-related functions:
|
||||
|
||||
5
docs/image/sharp-logo.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="86 86 550 550">
|
||||
<!-- Copyright 2019 Lovell Fuller. This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) License. -->
|
||||
<path fill="none" stroke="#9c0" stroke-width="80" d="M258.411 285.777l200.176-26.8M244.113 466.413L451.44 438.66M451.441 438.66V238.484M451.441 88.363v171.572l178.725-23.917M270.323 255.602V477.22M272.71 634.17V462.591L93.984 486.515"/>
|
||||
<path fill="none" stroke="#090" stroke-width="80" d="M451.441 610.246V438.66l178.725-23.91M269.688 112.59v171.58L90.964 308.093"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 592 B |
@@ -1,5 +1,7 @@
|
||||
# sharp
|
||||
|
||||
<img src="image/sharp-logo.svg" width="160" height="160" alt="sharp logo" align="right">
|
||||
|
||||
The typical use case for this high speed Node.js module
|
||||
is to convert large images in common formats to
|
||||
smaller, web-friendly JPEG, PNG and WebP images of varying dimensions.
|
||||
@@ -119,17 +121,20 @@ the help and code contributions of the following people:
|
||||
* [Aidan Hoolachan](https://github.com/ajhool)
|
||||
* [Axel Eirola](https://github.com/aeirola)
|
||||
* [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!
|
||||
|
||||
### Licensing
|
||||
|
||||
Copyright 2013, 2014, 2015, 2016, 2017, 2018 Lovell Fuller and contributors.
|
||||
Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 Lovell Fuller and contributors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
[http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0.html)
|
||||
[https://www.apache.org/licenses/LICENSE-2.0](https://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
|
||||
@@ -15,7 +15,7 @@ yarn add sharp
|
||||
### Building from source
|
||||
|
||||
Pre-compiled binaries for sharp are provided for use with
|
||||
Node versions 6, 8 and 10 on
|
||||
Node versions 6, 8, 10 and 11 on
|
||||
64-bit Windows, OS X and Linux platforms.
|
||||
|
||||
Sharp will be built from source at install time when:
|
||||
@@ -72,7 +72,9 @@ libvips is available in the
|
||||
[testing repository](https://pkgs.alpinelinux.org/packages?name=vips-dev):
|
||||
|
||||
```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
|
||||
@@ -95,7 +97,8 @@ that it can be located using `pkg-config --modversion vips-cpp`.
|
||||
[](https://ci.appveyor.com/project/lovell/sharp)
|
||||
|
||||
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.
|
||||
|
||||
@@ -146,16 +149,25 @@ docker pull tailor/docker-libvips
|
||||
|
||||
### AWS Lambda
|
||||
|
||||
A [deployment package](http://docs.aws.amazon.com/lambda/latest/dg/nodejs-create-deployment-pkg.html) for the
|
||||
[Lambda Execution Environment](http://docs.aws.amazon.com/lambda/latest/dg/current-supported-versions.html)
|
||||
can be built using Docker.
|
||||
Set the Lambda runtime to Node.js 8.10.
|
||||
|
||||
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
|
||||
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.
|
||||
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)
|
||||
|
||||
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
|
||||
SHARP_DIST_BASE_URL="https://hostname/path/" npm install sharp
|
||||
|
||||
@@ -16,11 +16,12 @@ const libvips = require('../lib/libvips');
|
||||
const platform = require('../lib/platform');
|
||||
|
||||
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) {
|
||||
npmLog.error('sharp', err.message);
|
||||
npmLog.error('sharp', 'Please see http://sharp.pixelplumbing.com/page/install');
|
||||
npmLog.info('sharp', 'Attempting to build from source via node-gyp but this may fail due to the above error');
|
||||
npmLog.info('sharp', 'Please see https://sharp.pixelplumbing.com/page/install for required dependencies');
|
||||
process.exit(1);
|
||||
};
|
||||
|
||||
@@ -55,8 +56,8 @@ try {
|
||||
if (arch === 'ia32') {
|
||||
throw new Error(`Intel Architecture 32-bit systems require manual installation of libvips >= ${minimumLibvipsVersion}`);
|
||||
}
|
||||
if (platformAndArch === 'freebsd-x64') {
|
||||
throw new Error(`FreeBSD systems require manual installation of libvips >= ${minimumLibvipsVersion}`);
|
||||
if (platformAndArch === 'freebsd-x64' || platformAndArch === 'openbsd-x64' || platformAndArch === 'sunos-x64') {
|
||||
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')) {
|
||||
throw new Error(`Use with glibc version ${detectLibc.version} requires manual installation of libvips >= ${minimumLibvipsVersion}`);
|
||||
@@ -79,7 +80,9 @@ try {
|
||||
if (response.statusCode !== 200) {
|
||||
throw new Error(`Status ${response.statusCode}`);
|
||||
}
|
||||
response.pipe(tmpFile);
|
||||
response
|
||||
.on('error', fail)
|
||||
.pipe(tmpFile);
|
||||
});
|
||||
tmpFile
|
||||
.on('error', fail)
|
||||
|
||||
@@ -29,6 +29,23 @@ function removeAlpha () {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure alpha channel, if missing. The added alpha channel will be fully opaque. This is a no-op if the image already has an alpha channel.
|
||||
*
|
||||
* @example
|
||||
* sharp('rgb.jpg')
|
||||
* .ensureAlpha()
|
||||
* .toFile('rgba.png', function(err, info) {
|
||||
* // rgba.png is a 4 channel image with a fully opaque alpha channel
|
||||
* });
|
||||
*
|
||||
* @returns {Sharp}
|
||||
*/
|
||||
function ensureAlpha () {
|
||||
this.options.ensureAlpha = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a single channel from a multi-channel image.
|
||||
*
|
||||
@@ -117,14 +134,13 @@ function bandbool (boolOp) {
|
||||
* @private
|
||||
*/
|
||||
module.exports = function (Sharp) {
|
||||
// Public instance functions
|
||||
[
|
||||
Object.assign(Sharp.prototype, {
|
||||
// Public instance functions
|
||||
removeAlpha,
|
||||
ensureAlpha,
|
||||
extractChannel,
|
||||
joinChannel,
|
||||
bandbool
|
||||
].forEach(function (f) {
|
||||
Sharp.prototype[f.name] = f;
|
||||
});
|
||||
// Class attributes
|
||||
Sharp.bool = bool;
|
||||
|
||||
@@ -123,7 +123,7 @@ function _setColourOption (key, val) {
|
||||
* @private
|
||||
*/
|
||||
module.exports = function (Sharp) {
|
||||
[
|
||||
Object.assign(Sharp.prototype, {
|
||||
// Public
|
||||
tint,
|
||||
greyscale,
|
||||
@@ -132,8 +132,6 @@ module.exports = function (Sharp) {
|
||||
toColorspace,
|
||||
// Private
|
||||
_setColourOption
|
||||
].forEach(function (f) {
|
||||
Sharp.prototype[f.name] = f;
|
||||
});
|
||||
// Class attributes
|
||||
Sharp.colourspace = colourspace;
|
||||
|
||||
@@ -14,8 +14,7 @@ const is = require('./is');
|
||||
* sharp('input.png')
|
||||
* .rotate(180)
|
||||
* .resize(300)
|
||||
* .flatten()
|
||||
* .background('#ff6600')
|
||||
* .flatten( { background: '#ff6600' } )
|
||||
* .overlayWith('overlay.png', { gravity: sharp.gravity.southeast } )
|
||||
* .sharpen()
|
||||
* .withMetadata()
|
||||
@@ -27,7 +26,7 @@ const is = require('./is');
|
||||
* // sharpened, with metadata, 90% quality WebP image data. Phew!
|
||||
* });
|
||||
*
|
||||
* @param {(Buffer|String)} overlay - Buffer containing image data or String containing the path to an image file.
|
||||
* @param {(Buffer|String)} [overlay] - Buffer containing image data or String containing the path to an image file.
|
||||
* @param {Object} [options]
|
||||
* @param {String} [options.gravity='centre'] - gravity at which to place the overlay.
|
||||
* @param {Number} [options.top] - the pixel offset from the top edge.
|
||||
|
||||
@@ -7,7 +7,7 @@ const events = require('events');
|
||||
const is = require('./is');
|
||||
|
||||
require('./libvips').hasVendoredLibvips();
|
||||
const sharp = require('../build/Release/sharp.node');
|
||||
const sharp = require('bindings')('sharp.node');
|
||||
|
||||
// Use NODE_DEBUG=sharp to enable libvips warnings
|
||||
const debuglog = util.debuglog('sharp');
|
||||
@@ -61,9 +61,8 @@ const debuglog = util.debuglog('sharp');
|
||||
* a 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 not present.
|
||||
* @param {Object} [options] - if present, is an Object with optional attributes.
|
||||
* @param {Boolean} [options.failOnError=false] - by default apply a "best effort"
|
||||
* to decode images, even if the data is corrupt or invalid. Set this flag to true
|
||||
* if you'd rather halt processing and raise an error when loading invalid images.
|
||||
* @param {Boolean} [options.failOnError=true] - by default halt processing and raise an error when loading invalid images.
|
||||
* Set this flag to `false` if you'd rather apply a "best effort" to decode images, even if the data is corrupt or invalid.
|
||||
* @param {Number} [options.density=72] - number representing the DPI for vector images.
|
||||
* @param {Number} [options.page=0] - page number to extract for multi-page input (GIF, TIFF)
|
||||
* @param {Object} [options.raw] - describes raw pixel input image data. See `raw()` for pixel ordering.
|
||||
@@ -136,6 +135,7 @@ const Sharp = function (input, options) {
|
||||
thresholdGrayscale: true,
|
||||
trimThreshold: 0,
|
||||
gamma: 0,
|
||||
gammaOut: 0,
|
||||
greyscale: false,
|
||||
normalise: 0,
|
||||
booleanBufferIn: null,
|
||||
@@ -143,6 +143,7 @@ const Sharp = function (input, options) {
|
||||
joinChannelIn: [],
|
||||
extractChannel: -1,
|
||||
removeAlpha: false,
|
||||
ensureAlpha: false,
|
||||
colourspace: 'srgb',
|
||||
// overlay
|
||||
overlayGravity: 0,
|
||||
@@ -169,6 +170,10 @@ const Sharp = function (input, options) {
|
||||
pngProgressive: false,
|
||||
pngCompressionLevel: 9,
|
||||
pngAdaptiveFiltering: false,
|
||||
pngPalette: false,
|
||||
pngQuality: 100,
|
||||
pngColours: 256,
|
||||
pngDither: 1,
|
||||
webpQuality: 80,
|
||||
webpAlphaQuality: 100,
|
||||
webpLossless: false,
|
||||
@@ -176,7 +181,11 @@ const Sharp = function (input, options) {
|
||||
tiffQuality: 80,
|
||||
tiffCompression: 'jpeg',
|
||||
tiffPredictor: 'horizontal',
|
||||
tiffPyramid: false,
|
||||
tiffSquash: false,
|
||||
tiffTile: false,
|
||||
tiffTileHeight: 256,
|
||||
tiffTileWidth: 256,
|
||||
tiffXres: 1.0,
|
||||
tiffYres: 1.0,
|
||||
tileSize: 256,
|
||||
|
||||
@@ -9,7 +9,7 @@ const sharp = require('../build/Release/sharp.node');
|
||||
* @private
|
||||
*/
|
||||
function _createInputDescriptor (input, inputOptions, containerOptions) {
|
||||
const inputDescriptor = { failOnError: false };
|
||||
const inputDescriptor = { failOnError: true };
|
||||
if (is.string(input)) {
|
||||
// filesystem
|
||||
inputDescriptor.file = input;
|
||||
@@ -186,6 +186,8 @@ function clone () {
|
||||
* - `density`: Number of pixels per inch (DPI), if present
|
||||
* - `chromaSubsampling`: String containing JPEG chroma subsampling, `4:2:0` or `4:4:4` for RGB, `4:2:0:4` or `4:4:4:4` for CMYK
|
||||
* - `isProgressive`: Boolean indicating whether the image is interlaced using a progressive scan
|
||||
* - `pages`: Number of pages this TIFF, GIF or PDF image contains.
|
||||
* - `pageHeight`: Number of pixels high each page in this PDF image will be.
|
||||
* - `hasProfile`: Boolean indicating the presence of an embedded ICC profile
|
||||
* - `hasAlpha`: Boolean indicating the presence of an alpha transparency channel
|
||||
* - `orientation`: Number value of the EXIF Orientation header, if present
|
||||
@@ -362,7 +364,7 @@ function sequentialRead (sequentialRead) {
|
||||
* @private
|
||||
*/
|
||||
module.exports = function (Sharp) {
|
||||
[
|
||||
Object.assign(Sharp.prototype, {
|
||||
// Private
|
||||
_createInputDescriptor,
|
||||
_write,
|
||||
@@ -374,7 +376,5 @@ module.exports = function (Sharp) {
|
||||
stats,
|
||||
limitInputPixels,
|
||||
sequentialRead
|
||||
].forEach(function (f) {
|
||||
Sharp.prototype[f.name] = f;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -172,6 +172,7 @@ function blur (sigma) {
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @returns {Sharp}
|
||||
*/
|
||||
@@ -189,11 +190,15 @@ function flatten (options) {
|
||||
* 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
|
||||
* 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} [gammaOut] value between 1.0 and 3.0. (optional, defaults to same as `gamma`)
|
||||
* @returns {Sharp}
|
||||
* @throws {Error} Invalid parameters
|
||||
*/
|
||||
function gamma (gamma) {
|
||||
function gamma (gamma, gammaOut) {
|
||||
if (!is.defined(gamma)) {
|
||||
// Default gamma correction of 2.2 (sRGB)
|
||||
this.options.gamma = 2.2;
|
||||
@@ -202,6 +207,14 @@ function gamma (gamma) {
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -365,12 +378,49 @@ function linear (a, b) {
|
||||
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.
|
||||
* @private
|
||||
*/
|
||||
module.exports = function (Sharp) {
|
||||
[
|
||||
Object.assign(Sharp.prototype, {
|
||||
rotate,
|
||||
flip,
|
||||
flop,
|
||||
@@ -385,8 +435,7 @@ module.exports = function (Sharp) {
|
||||
convolve,
|
||||
threshold,
|
||||
boolean,
|
||||
linear
|
||||
].forEach(function (f) {
|
||||
Sharp.prototype[f.name] = f;
|
||||
linear,
|
||||
recomb
|
||||
});
|
||||
};
|
||||
|
||||
188
lib/output.js
@@ -32,7 +32,7 @@ const sharp = require('../build/Release/sharp.node');
|
||||
*/
|
||||
function toFile (fileOut, callback) {
|
||||
if (!fileOut || fileOut.length === 0) {
|
||||
const errOutputInvalid = new Error('Invalid output');
|
||||
const errOutputInvalid = new Error('Missing output file path');
|
||||
if (is.fn(callback)) {
|
||||
callback(errOutputInvalid);
|
||||
} else {
|
||||
@@ -175,30 +175,30 @@ function jpeg (options) {
|
||||
throw new Error('Invalid chromaSubsampling (4:2:0, 4:4:4) ' + options.chromaSubsampling);
|
||||
}
|
||||
}
|
||||
options.trellisQuantisation = is.bool(options.trellisQuantization) ? options.trellisQuantization : options.trellisQuantisation;
|
||||
if (is.defined(options.trellisQuantisation)) {
|
||||
this._setBooleanOption('jpegTrellisQuantisation', options.trellisQuantisation);
|
||||
const trellisQuantisation = is.bool(options.trellisQuantization) ? options.trellisQuantization : options.trellisQuantisation;
|
||||
if (is.defined(trellisQuantisation)) {
|
||||
this._setBooleanOption('jpegTrellisQuantisation', trellisQuantisation);
|
||||
}
|
||||
if (is.defined(options.overshootDeringing)) {
|
||||
this._setBooleanOption('jpegOvershootDeringing', options.overshootDeringing);
|
||||
}
|
||||
options.optimiseScans = is.bool(options.optimizeScans) ? options.optimizeScans : options.optimiseScans;
|
||||
if (is.defined(options.optimiseScans)) {
|
||||
this._setBooleanOption('jpegOptimiseScans', options.optimiseScans);
|
||||
if (options.optimiseScans) {
|
||||
const optimiseScans = is.bool(options.optimizeScans) ? options.optimizeScans : options.optimiseScans;
|
||||
if (is.defined(optimiseScans)) {
|
||||
this._setBooleanOption('jpegOptimiseScans', optimiseScans);
|
||||
if (optimiseScans) {
|
||||
this.options.jpegProgressive = true;
|
||||
}
|
||||
}
|
||||
options.optimiseCoding = is.bool(options.optimizeCoding) ? options.optimizeCoding : options.optimiseCoding;
|
||||
if (is.defined(options.optimiseCoding)) {
|
||||
this._setBooleanOption('jpegOptimiseCoding', options.optimiseCoding);
|
||||
const optimiseCoding = is.bool(options.optimizeCoding) ? options.optimizeCoding : options.optimiseCoding;
|
||||
if (is.defined(optimiseCoding)) {
|
||||
this._setBooleanOption('jpegOptimiseCoding', optimiseCoding);
|
||||
}
|
||||
options.quantisationTable = is.number(options.quantizationTable) ? options.quantizationTable : options.quantisationTable;
|
||||
if (is.defined(options.quantisationTable)) {
|
||||
if (is.integer(options.quantisationTable) && is.inRange(options.quantisationTable, 0, 8)) {
|
||||
this.options.jpegQuantisationTable = options.quantisationTable;
|
||||
const quantisationTable = is.number(options.quantizationTable) ? options.quantizationTable : options.quantisationTable;
|
||||
if (is.defined(quantisationTable)) {
|
||||
if (is.integer(quantisationTable) && is.inRange(quantisationTable, 0, 8)) {
|
||||
this.options.jpegQuantisationTable = quantisationTable;
|
||||
} else {
|
||||
throw new Error('Invalid quantisation table (integer, 0-8) ' + options.quantisationTable);
|
||||
throw new Error('Invalid quantisation table (integer, 0-8) ' + quantisationTable);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -221,6 +221,11 @@ function jpeg (options) {
|
||||
* @param {Boolean} [options.progressive=false] - use progressive (interlace) scan
|
||||
* @param {Number} [options.compressionLevel=9] - zlib compression level, 0-9
|
||||
* @param {Boolean} [options.adaptiveFiltering=false] - use adaptive row filtering
|
||||
* @param {Boolean} [options.palette=false] - quantise to a palette-based image with alpha transparency support, requires libimagequant
|
||||
* @param {Number} [options.quality=100] - use the lowest number of colours needed to achieve given quality, requires libimagequant
|
||||
* @param {Number} [options.colours=256] - maximum number of palette entries, requires libimagequant
|
||||
* @param {Number} [options.colors=256] - alternative spelling of `options.colours`, requires libimagequant
|
||||
* @param {Number} [options.dither=1.0] - level of Floyd-Steinberg error diffusion, requires libimagequant
|
||||
* @param {Boolean} [options.force=true] - force PNG output, otherwise attempt to use input format
|
||||
* @returns {Sharp}
|
||||
* @throws {Error} Invalid options
|
||||
@@ -240,6 +245,33 @@ function png (options) {
|
||||
if (is.defined(options.adaptiveFiltering)) {
|
||||
this._setBooleanOption('pngAdaptiveFiltering', options.adaptiveFiltering);
|
||||
}
|
||||
if (is.defined(options.palette)) {
|
||||
this._setBooleanOption('pngPalette', options.palette);
|
||||
if (this.options.pngPalette) {
|
||||
if (is.defined(options.quality)) {
|
||||
if (is.integer(options.quality) && is.inRange(options.quality, 0, 100)) {
|
||||
this.options.pngQuality = options.quality;
|
||||
} else {
|
||||
throw is.invalidParameterError('quality', 'integer between 0 and 100', options.quality);
|
||||
}
|
||||
}
|
||||
const colours = options.colours || options.colors;
|
||||
if (is.defined(colours)) {
|
||||
if (is.integer(colours) && is.inRange(colours, 2, 256)) {
|
||||
this.options.pngColours = colours;
|
||||
} else {
|
||||
throw is.invalidParameterError('colours', 'integer between 2 and 256', colours);
|
||||
}
|
||||
}
|
||||
if (is.defined(options.dither)) {
|
||||
if (is.number(options.dither) && is.inRange(options.dither, 0, 1)) {
|
||||
this.options.pngDither = options.dither;
|
||||
} else {
|
||||
throw is.invalidParameterError('dither', 'number between 0.0 and 1.0', options.dither);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return this._updateFormatOut('png', options);
|
||||
}
|
||||
@@ -304,6 +336,10 @@ function webp (options) {
|
||||
* @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.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.yres=1.0] - vertical resolution in pixels/mm
|
||||
* @param {Boolean} [options.squash=false] - squash 8-bit images down to 1 bit
|
||||
@@ -311,51 +347,83 @@ function webp (options) {
|
||||
* @throws {Error} Invalid options
|
||||
*/
|
||||
function tiff (options) {
|
||||
if (is.object(options) && is.defined(options.quality)) {
|
||||
if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
|
||||
this.options.tiffQuality = options.quality;
|
||||
} else {
|
||||
throw new Error('Invalid quality (integer, 1-100) ' + options.quality);
|
||||
if (is.object(options)) {
|
||||
if (is.defined(options.quality)) {
|
||||
if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
|
||||
this.options.tiffQuality = options.quality;
|
||||
} else {
|
||||
throw new Error('Invalid quality (integer, 1-100) ' + options.quality);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (is.object(options) && is.defined(options.squash)) {
|
||||
if (is.bool(options.squash)) {
|
||||
this.options.tiffSquash = options.squash;
|
||||
} else {
|
||||
throw new Error('Invalid Value for squash ' + options.squash + ' Only Boolean Values allowed for options.squash.');
|
||||
if (is.defined(options.squash)) {
|
||||
if (is.bool(options.squash)) {
|
||||
this.options.tiffSquash = options.squash;
|
||||
} else {
|
||||
throw new Error('Invalid Value for squash ' + options.squash + ' Only Boolean Values allowed for options.squash.');
|
||||
}
|
||||
}
|
||||
}
|
||||
// resolution
|
||||
if (is.object(options) && is.defined(options.xres)) {
|
||||
if (is.number(options.xres)) {
|
||||
this.options.tiffXres = options.xres;
|
||||
} else {
|
||||
throw new Error('Invalid Value for xres ' + options.xres + ' Only numeric values allowed for options.xres');
|
||||
// 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.object(options) && is.defined(options.yres)) {
|
||||
if (is.number(options.yres)) {
|
||||
this.options.tiffYres = options.yres;
|
||||
} else {
|
||||
throw new Error('Invalid Value for yres ' + options.yres + ' Only numeric values allowed for options.yres');
|
||||
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');
|
||||
}
|
||||
}
|
||||
}
|
||||
// compression
|
||||
if (is.defined(options) && is.defined(options.compression)) {
|
||||
if (is.string(options.compression) && is.inArray(options.compression, ['lzw', 'deflate', 'jpeg', 'ccittfax4', 'none'])) {
|
||||
this.options.tiffCompression = options.compression;
|
||||
} else {
|
||||
const message = `Invalid compression option "${options.compression}". Should be one of: lzw, deflate, jpeg, ccittfax4, none`;
|
||||
throw new Error(message);
|
||||
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');
|
||||
}
|
||||
}
|
||||
}
|
||||
// predictor
|
||||
if (is.defined(options) && is.defined(options.predictor)) {
|
||||
if (is.string(options.predictor) && is.inArray(options.predictor, ['none', 'horizontal', 'float'])) {
|
||||
this.options.tiffPredictor = options.predictor;
|
||||
} else {
|
||||
const message = `Invalid predictor option "${options.predictor}". Should be one of: none, horizontal, float`;
|
||||
throw new Error(message);
|
||||
// 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
|
||||
if (is.defined(options.xres)) {
|
||||
if (is.number(options.xres)) {
|
||||
this.options.tiffXres = options.xres;
|
||||
} else {
|
||||
throw new Error('Invalid Value for xres ' + options.xres + ' Only numeric values allowed for options.xres');
|
||||
}
|
||||
}
|
||||
if (is.defined(options.yres)) {
|
||||
if (is.number(options.yres)) {
|
||||
this.options.tiffYres = options.yres;
|
||||
} else {
|
||||
throw new Error('Invalid Value for yres ' + options.yres + ' Only numeric values allowed for options.yres');
|
||||
}
|
||||
}
|
||||
// compression
|
||||
if (is.defined(options.compression)) {
|
||||
if (is.string(options.compression) && is.inArray(options.compression, ['lzw', 'deflate', 'jpeg', 'ccittfax4', 'none'])) {
|
||||
this.options.tiffCompression = options.compression;
|
||||
} else {
|
||||
const message = `Invalid compression option "${options.compression}". Should be one of: lzw, deflate, jpeg, ccittfax4, none`;
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
// predictor
|
||||
if (is.defined(options.predictor)) {
|
||||
if (is.string(options.predictor) && is.inArray(options.predictor, ['none', 'horizontal', 'float'])) {
|
||||
this.options.tiffPredictor = options.predictor;
|
||||
} else {
|
||||
const message = `Invalid predictor option "${options.predictor}". Should be one of: none, horizontal, float`;
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
return this._updateFormatOut('tiff', options);
|
||||
@@ -505,7 +573,9 @@ function tile (tile) {
|
||||
* @returns {Sharp}
|
||||
*/
|
||||
function _updateFormatOut (formatOut, options) {
|
||||
this.options.formatOut = (is.object(options) && options.force === false) ? 'input' : formatOut;
|
||||
if (!(is.object(options) && options.force === false)) {
|
||||
this.options.formatOut = formatOut;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -641,7 +711,7 @@ function _pipeline (callback) {
|
||||
* @private
|
||||
*/
|
||||
module.exports = function (Sharp) {
|
||||
[
|
||||
Object.assign(Sharp.prototype, {
|
||||
// Public
|
||||
toFile,
|
||||
toBuffer,
|
||||
@@ -658,7 +728,5 @@ module.exports = function (Sharp) {
|
||||
_setBooleanOption,
|
||||
_read,
|
||||
_pipeline
|
||||
].forEach(function (f) {
|
||||
Sharp.prototype[f.name] = f;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -55,6 +55,7 @@ const strategy = {
|
||||
const kernel = {
|
||||
nearest: 'nearest',
|
||||
cubic: 'cubic',
|
||||
mitchell: 'mitchell',
|
||||
lanczos2: 'lanczos2',
|
||||
lanczos3: 'lanczos3'
|
||||
};
|
||||
@@ -110,6 +111,7 @@ const mapFitToCanvas = {
|
||||
* Possible interpolation kernels are:
|
||||
* - `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).
|
||||
* - `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`.
|
||||
* - `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
|
||||
* sharp(input)
|
||||
* .resize(140)
|
||||
* .)
|
||||
* .extend({
|
||||
* top: 10,
|
||||
* bottom: 20,
|
||||
@@ -468,13 +469,11 @@ function withoutEnlargement (withoutEnlargement) {
|
||||
* @private
|
||||
*/
|
||||
module.exports = function (Sharp) {
|
||||
[
|
||||
Object.assign(Sharp.prototype, {
|
||||
resize,
|
||||
extend,
|
||||
extract,
|
||||
trim
|
||||
].forEach(function (f) {
|
||||
Sharp.prototype[f.name] = f;
|
||||
});
|
||||
// Class attributes
|
||||
Sharp.gravity = gravity;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
site_name: sharp
|
||||
site_url: http://sharp.pixelplumbing.com/
|
||||
site_url: https://sharp.pixelplumbing.com/
|
||||
repo_url: https://github.com/lovell/sharp
|
||||
site_description: High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP and TIFF images
|
||||
copyright: <a href="https://pixelplumbing.com/">pixelplumbing.com</a>
|
||||
|
||||
38
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "sharp",
|
||||
"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.3",
|
||||
"author": "Lovell Fuller <npm@lovell.info>",
|
||||
"homepage": "https://github.com/lovell/sharp",
|
||||
"contributors": [
|
||||
@@ -55,13 +55,19 @@
|
||||
"Alun Davies <alun.owain.davies@googlemail.com>",
|
||||
"Aidan Hoolachan <ajhoolachan21@gmail.com>",
|
||||
"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": {
|
||||
"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.*",
|
||||
"test": "semistandard && cc && nyc --reporter=lcov --branches=99 mocha --slow=5000 --timeout=60000 ./test/unit/*.js && prebuild-ci",
|
||||
"coverage": "./test/coverage/report.sh",
|
||||
"test": "semistandard && cc && npm run test-unit && npm run test-licensing && prebuild-ci",
|
||||
"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",
|
||||
"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,31 +93,33 @@
|
||||
"vips"
|
||||
],
|
||||
"dependencies": {
|
||||
"color": "^3.0.0",
|
||||
"bindings": "^1.3.1",
|
||||
"color": "^3.1.0",
|
||||
"detect-libc": "^1.0.3",
|
||||
"nan": "^2.11.1",
|
||||
"fs-copy-file-sync": "^1.1.1",
|
||||
"nan": "^2.12.1",
|
||||
"npmlog": "^4.1.2",
|
||||
"prebuild-install": "^5.2.0",
|
||||
"semver": "^5.5.1",
|
||||
"prebuild-install": "^5.2.2",
|
||||
"semver": "^5.6.0",
|
||||
"simple-get": "^3.0.3",
|
||||
"tar": "^4.4.6",
|
||||
"tar": "^4.4.8",
|
||||
"tunnel-agent": "^0.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"async": "^2.6.1",
|
||||
"cc": "^1.0.2",
|
||||
"decompress-zip": "^0.3.1",
|
||||
"documentation": "^8.1.2",
|
||||
"decompress-zip": "^0.3.2",
|
||||
"documentation": "^9.1.1",
|
||||
"exif-reader": "^1.0.2",
|
||||
"icc": "^1.0.0",
|
||||
"license-checker": "^25.0.1",
|
||||
"mocha": "^5.2.0",
|
||||
"mock-fs": "^4.7.0",
|
||||
"nyc": "^13.1.0",
|
||||
"prebuild": "^8.1.0",
|
||||
"prebuild-ci": "^2.2.3",
|
||||
"rimraf": "^2.6.2",
|
||||
"semistandard": "^12.0.1"
|
||||
"prebuild": "^8.1.2",
|
||||
"prebuild-ci": "^2.3.0",
|
||||
"rimraf": "^2.6.3",
|
||||
"semistandard": "^13.0.1"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"config": {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2013, 2014, 2015, 2016, 2017 Lovell Fuller and contributors.
|
||||
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 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.
|
||||
@@ -137,6 +137,7 @@ namespace sharp {
|
||||
case ImageType::VIPS: id = "v"; break;
|
||||
case ImageType::RAW: id = "raw"; break;
|
||||
case ImageType::UNKNOWN: id = "unknown"; break;
|
||||
case ImageType::MISSING: id = "missing"; break;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
@@ -203,6 +204,10 @@ namespace sharp {
|
||||
} else if (EndsWith(loader, "Magick") || EndsWith(loader, "MagickFile")) {
|
||||
imageType = ImageType::MAGICK;
|
||||
}
|
||||
} else {
|
||||
if (EndsWith(vips::VError().what(), " not found\n")) {
|
||||
imageType = ImageType::MISSING;
|
||||
}
|
||||
}
|
||||
return imageType;
|
||||
}
|
||||
@@ -245,8 +250,8 @@ namespace sharp {
|
||||
if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
|
||||
SetDensity(image, descriptor->density);
|
||||
}
|
||||
} catch (...) {
|
||||
throw vips::VError("Input buffer has corrupt header");
|
||||
} catch (vips::VError const &err) {
|
||||
throw vips::VError(std::string("Input buffer has corrupt header: ") + err.what());
|
||||
}
|
||||
} else {
|
||||
throw vips::VError("Input buffer contains unsupported image format");
|
||||
@@ -269,6 +274,9 @@ namespace sharp {
|
||||
} else {
|
||||
// From filesystem
|
||||
imageType = DetermineImageType(descriptor->file.data());
|
||||
if (imageType == ImageType::MISSING) {
|
||||
throw vips::VError("Input file is missing");
|
||||
}
|
||||
if (imageType != ImageType::UNKNOWN) {
|
||||
try {
|
||||
vips::VOption *option = VImage::option()
|
||||
@@ -287,11 +295,11 @@ namespace sharp {
|
||||
if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
|
||||
SetDensity(image, descriptor->density);
|
||||
}
|
||||
} catch (...) {
|
||||
throw vips::VError("Input file has corrupt header");
|
||||
} catch (vips::VError const &err) {
|
||||
throw vips::VError(std::string("Input file has corrupt header: ") + err.what());
|
||||
}
|
||||
} else {
|
||||
throw vips::VError("Input file is missing or of an unsupported image format");
|
||||
throw vips::VError("Input file contains unsupported image format");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
11
src/common.h
@@ -1,4 +1,4 @@
|
||||
// Copyright 2013, 2014, 2015, 2016, 2017 Lovell Fuller and contributors.
|
||||
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 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.
|
||||
@@ -25,8 +25,8 @@
|
||||
|
||||
// Verify platform and compiler compatibility
|
||||
|
||||
#if (VIPS_MAJOR_VERSION < 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 6))
|
||||
#error libvips version 8.6.1+ is required - see sharp.pixelplumbing.com/page/install
|
||||
#if (VIPS_MAJOR_VERSION < 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 7))
|
||||
#error libvips version 8.7.0+ is required - see sharp.pixelplumbing.com/page/install
|
||||
#endif
|
||||
|
||||
#if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6)))
|
||||
@@ -61,7 +61,7 @@ namespace sharp {
|
||||
|
||||
InputDescriptor():
|
||||
buffer(nullptr),
|
||||
failOnError(FALSE),
|
||||
failOnError(TRUE),
|
||||
bufferLength(0),
|
||||
density(72.0),
|
||||
rawChannels(0),
|
||||
@@ -106,7 +106,8 @@ namespace sharp {
|
||||
FITS,
|
||||
VIPS,
|
||||
RAW,
|
||||
UNKNOWN
|
||||
UNKNOWN,
|
||||
MISSING
|
||||
};
|
||||
|
||||
// How many tasks are in the queue?
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2013, 2014, 2015, 2016, 2017 Lovell Fuller and contributors.
|
||||
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 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.
|
||||
@@ -68,6 +68,15 @@ class MetadataWorker : public Nan::AsyncWorker {
|
||||
if (image.get_typeof("interlaced") == G_TYPE_INT) {
|
||||
baton->isProgressive = image.get_int("interlaced") == 1;
|
||||
}
|
||||
if (image.get_typeof("palette-bit-depth") == G_TYPE_INT) {
|
||||
baton->paletteBitDepth = image.get_int("palette-bit-depth");
|
||||
}
|
||||
if (image.get_typeof(VIPS_META_N_PAGES) == G_TYPE_INT) {
|
||||
baton->pages = image.get_int(VIPS_META_N_PAGES);
|
||||
}
|
||||
if (image.get_typeof(VIPS_META_PAGE_HEIGHT) == G_TYPE_INT) {
|
||||
baton->pageHeight = image.get_int(VIPS_META_PAGE_HEIGHT);
|
||||
}
|
||||
baton->hasProfile = sharp::HasProfile(image);
|
||||
// Derived attributes
|
||||
baton->hasAlpha = sharp::HasAlpha(image);
|
||||
@@ -140,6 +149,15 @@ class MetadataWorker : public Nan::AsyncWorker {
|
||||
New<v8::String>(baton->chromaSubsampling).ToLocalChecked());
|
||||
}
|
||||
Set(info, New("isProgressive").ToLocalChecked(), New<v8::Boolean>(baton->isProgressive));
|
||||
if (baton->paletteBitDepth > 0) {
|
||||
Set(info, New("paletteBitDepth").ToLocalChecked(), New<v8::Uint32>(baton->paletteBitDepth));
|
||||
}
|
||||
if (baton->pages > 0) {
|
||||
Set(info, New("pages").ToLocalChecked(), New<v8::Uint32>(baton->pages));
|
||||
}
|
||||
if (baton->pageHeight > 0) {
|
||||
Set(info, New("pageHeight").ToLocalChecked(), New<v8::Uint32>(baton->pageHeight));
|
||||
}
|
||||
Set(info, New("hasProfile").ToLocalChecked(), New<v8::Boolean>(baton->hasProfile));
|
||||
Set(info, New("hasAlpha").ToLocalChecked(), New<v8::Boolean>(baton->hasAlpha));
|
||||
if (baton->orientation > 0) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2013, 2014, 2015, 2016, 2017 Lovell Fuller and contributors.
|
||||
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 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.
|
||||
@@ -33,6 +33,9 @@ struct MetadataBaton {
|
||||
int density;
|
||||
std::string chromaSubsampling;
|
||||
bool isProgressive;
|
||||
int paletteBitDepth;
|
||||
int pages;
|
||||
int pageHeight;
|
||||
bool hasProfile;
|
||||
bool hasAlpha;
|
||||
int orientation;
|
||||
@@ -53,6 +56,9 @@ struct MetadataBaton {
|
||||
channels(0),
|
||||
density(0),
|
||||
isProgressive(false),
|
||||
paletteBitDepth(0),
|
||||
pages(0),
|
||||
pageHeight(0),
|
||||
hasProfile(false),
|
||||
hasAlpha(false),
|
||||
orientation(0),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2013, 2014, 2015, 2016, 2017 Lovell Fuller and contributors.
|
||||
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 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.
|
||||
@@ -38,6 +38,18 @@ namespace sharp {
|
||||
return image;
|
||||
}
|
||||
|
||||
/*
|
||||
Ensures alpha channel, if missing.
|
||||
*/
|
||||
VImage EnsureAlpha(VImage image) {
|
||||
if (!HasAlpha(image)) {
|
||||
std::vector<double> alpha;
|
||||
alpha.push_back(sharp::MaximumImageAlpha(image.interpretation()));
|
||||
image = image.bandjoin_const(alpha);
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
/*
|
||||
Composite overlayImage over image at given position
|
||||
Assumes alpha channels are already premultiplied and will be unpremultiplied after
|
||||
@@ -278,6 +290,25 @@ namespace sharp {
|
||||
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.
|
||||
*/
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2013, 2014, 2015, 2016, 2017 Lovell Fuller and contributors.
|
||||
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 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.
|
||||
@@ -30,6 +30,11 @@ namespace sharp {
|
||||
*/
|
||||
VImage RemoveAlpha(VImage image);
|
||||
|
||||
/*
|
||||
Ensures alpha channel, if missing.
|
||||
*/
|
||||
VImage EnsureAlpha(VImage image);
|
||||
|
||||
/*
|
||||
Alpha composite src over dst with given gravity.
|
||||
Assumes alpha channels are already premultiplied and will be unpremultiplied after.
|
||||
@@ -107,6 +112,12 @@ namespace sharp {
|
||||
*/
|
||||
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
|
||||
|
||||
#endif // SRC_OPERATIONS_H_
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2013, 2014, 2015, 2016, 2017 Lovell Fuller and contributors.
|
||||
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 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.
|
||||
@@ -297,7 +297,7 @@ class PipelineWorker : public Nan::AsyncWorker {
|
||||
}
|
||||
|
||||
// Ensure we're using a device-independent colour space
|
||||
if (sharp::HasProfile(image)) {
|
||||
if (sharp::HasProfile(image) && image.interpretation() != VIPS_INTERPRETATION_LABS) {
|
||||
// Convert to sRGB using embedded profile
|
||||
try {
|
||||
image = image.icc_transform(
|
||||
@@ -381,10 +381,19 @@ class PipelineWorker : public Nan::AsyncWorker {
|
||||
vips_enum_from_nick(nullptr, VIPS_TYPE_KERNEL, baton->kernel.data()));
|
||||
if (
|
||||
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");
|
||||
}
|
||||
// Ensure shortest edge is at least 1 pixel
|
||||
if (image.width() / xfactor < 0.5) {
|
||||
xfactor = 2 * image.width();
|
||||
baton->width = 1;
|
||||
}
|
||||
if (image.height() / yfactor < 0.5) {
|
||||
yfactor = 2 * image.height();
|
||||
baton->height = 1;
|
||||
}
|
||||
image = image.resize(1.0 / xfactor, VImage::option()
|
||||
->set("vscale", 1.0 / yfactor)
|
||||
->set("kernel", kernel));
|
||||
@@ -525,6 +534,11 @@ class PipelineWorker : public Nan::AsyncWorker {
|
||||
baton->convKernel);
|
||||
}
|
||||
|
||||
// Recomb
|
||||
if (baton->recombMatrix != NULL) {
|
||||
image = sharp::Recomb(image, baton->recombMatrix);
|
||||
}
|
||||
|
||||
// Sharpen
|
||||
if (shouldSharpen) {
|
||||
image = sharp::Sharpen(image, baton->sharpenSigma, baton->sharpenFlat, baton->sharpenJagged);
|
||||
@@ -612,8 +626,8 @@ class PipelineWorker : public Nan::AsyncWorker {
|
||||
baton->premultiplied = shouldPremultiplyAlpha;
|
||||
|
||||
// Gamma decoding (brighten)
|
||||
if (baton->gamma >= 1 && baton->gamma <= 3) {
|
||||
image = sharp::Gamma(image, baton->gamma);
|
||||
if (baton->gammaOut >= 1 && baton->gammaOut <= 3) {
|
||||
image = sharp::Gamma(image, baton->gammaOut);
|
||||
}
|
||||
|
||||
// Linear adjustment (a * in + b)
|
||||
@@ -663,6 +677,11 @@ class PipelineWorker : public Nan::AsyncWorker {
|
||||
image = sharp::RemoveAlpha(image);
|
||||
}
|
||||
|
||||
// Ensure alpha channel, if missing
|
||||
if (baton->ensureAlpha) {
|
||||
image = sharp::EnsureAlpha(image);
|
||||
}
|
||||
|
||||
// Convert image to sRGB, if not already
|
||||
if (sharp::Is16Bit(image.interpretation())) {
|
||||
image = image.cast(VIPS_FORMAT_USHORT);
|
||||
@@ -716,14 +735,15 @@ class PipelineWorker : public Nan::AsyncWorker {
|
||||
(inputImageType == ImageType::PNG || inputImageType == ImageType::GIF || inputImageType == ImageType::SVG))) {
|
||||
// Write PNG to buffer
|
||||
sharp::AssertImageTypeDimensions(image, ImageType::PNG);
|
||||
// Strip profile
|
||||
if (!baton->withMetadata) {
|
||||
vips_image_remove(image.get_image(), VIPS_META_ICC_NAME);
|
||||
}
|
||||
VipsArea *area = VIPS_AREA(image.pngsave_buffer(VImage::option()
|
||||
->set("strip", !baton->withMetadata)
|
||||
->set("interlace", baton->pngProgressive)
|
||||
->set("compression", baton->pngCompressionLevel)
|
||||
->set("filter", baton->pngAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_ALL : VIPS_FOREIGN_PNG_FILTER_NONE)));
|
||||
->set("filter", baton->pngAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_ALL : VIPS_FOREIGN_PNG_FILTER_NONE)
|
||||
->set("palette", baton->pngPalette)
|
||||
->set("Q", baton->pngQuality)
|
||||
->set("colours", baton->pngColours)
|
||||
->set("dither", baton->pngDither)));
|
||||
baton->bufferOut = static_cast<char*>(area->data);
|
||||
baton->bufferOutLength = area->length;
|
||||
area->free_fn = nullptr;
|
||||
@@ -758,6 +778,10 @@ class PipelineWorker : public Nan::AsyncWorker {
|
||||
->set("squash", baton->tiffSquash)
|
||||
->set("compression", baton->tiffCompression)
|
||||
->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("yres", baton->tiffYres)));
|
||||
baton->bufferOut = static_cast<char*>(area->data);
|
||||
@@ -771,6 +795,7 @@ class PipelineWorker : public Nan::AsyncWorker {
|
||||
if (baton->greyscale || image.interpretation() == VIPS_INTERPRETATION_B_W) {
|
||||
// Extract first band for greyscale image
|
||||
image = image[0];
|
||||
baton->channels = 1;
|
||||
}
|
||||
if (image.format() != VIPS_FORMAT_UCHAR) {
|
||||
// Cast pixels to uint8 (unsigned char)
|
||||
@@ -824,14 +849,15 @@ class PipelineWorker : public Nan::AsyncWorker {
|
||||
(inputImageType == ImageType::PNG || inputImageType == ImageType::GIF || inputImageType == ImageType::SVG))) {
|
||||
// Write PNG to file
|
||||
sharp::AssertImageTypeDimensions(image, ImageType::PNG);
|
||||
// Strip profile
|
||||
if (!baton->withMetadata) {
|
||||
vips_image_remove(image.get_image(), VIPS_META_ICC_NAME);
|
||||
}
|
||||
image.pngsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
||||
->set("strip", !baton->withMetadata)
|
||||
->set("interlace", baton->pngProgressive)
|
||||
->set("compression", baton->pngCompressionLevel)
|
||||
->set("filter", baton->pngAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_ALL : VIPS_FOREIGN_PNG_FILTER_NONE));
|
||||
->set("filter", baton->pngAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_ALL : VIPS_FOREIGN_PNG_FILTER_NONE)
|
||||
->set("palette", baton->pngPalette)
|
||||
->set("Q", baton->pngQuality)
|
||||
->set("colours", baton->pngColours)
|
||||
->set("dither", baton->pngDither));
|
||||
baton->formatOut = "png";
|
||||
} else if (baton->formatOut == "webp" || (mightMatchInput && isWebp) ||
|
||||
(willMatchInput && inputImageType == ImageType::WEBP)) {
|
||||
@@ -856,6 +882,10 @@ class PipelineWorker : public Nan::AsyncWorker {
|
||||
->set("squash", baton->tiffSquash)
|
||||
->set("compression", baton->tiffCompression)
|
||||
->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("yres", baton->tiffYres));
|
||||
baton->formatOut = "tiff";
|
||||
@@ -1193,6 +1223,7 @@ NAN_METHOD(pipeline) {
|
||||
baton->thresholdGrayscale = AttrTo<bool>(options, "thresholdGrayscale");
|
||||
baton->trimThreshold = AttrTo<double>(options, "trimThreshold");
|
||||
baton->gamma = AttrTo<double>(options, "gamma");
|
||||
baton->gammaOut = AttrTo<double>(options, "gammaOut");
|
||||
baton->linearA = AttrTo<double>(options, "linearA");
|
||||
baton->linearB = AttrTo<double>(options, "linearB");
|
||||
baton->greyscale = AttrTo<bool>(options, "greyscale");
|
||||
@@ -1212,6 +1243,7 @@ NAN_METHOD(pipeline) {
|
||||
baton->extractChannel = AttrTo<int32_t>(options, "extractChannel");
|
||||
|
||||
baton->removeAlpha = AttrTo<bool>(options, "removeAlpha");
|
||||
baton->ensureAlpha = AttrTo<bool>(options, "ensureAlpha");
|
||||
if (HasAttr(options, "boolean")) {
|
||||
baton->boolean = CreateInputDescriptor(AttrAs<v8::Object>(options, "boolean"), buffersToPersist);
|
||||
baton->booleanOp = sharp::GetBooleanOperation(AttrAsStr(options, "booleanOp"));
|
||||
@@ -1232,6 +1264,13 @@ NAN_METHOD(pipeline) {
|
||||
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"));
|
||||
if (baton->colourspace == VIPS_INTERPRETATION_ERROR) {
|
||||
baton->colourspace = VIPS_INTERPRETATION_sRGB;
|
||||
@@ -1253,12 +1292,20 @@ NAN_METHOD(pipeline) {
|
||||
baton->pngProgressive = AttrTo<bool>(options, "pngProgressive");
|
||||
baton->pngCompressionLevel = AttrTo<uint32_t>(options, "pngCompressionLevel");
|
||||
baton->pngAdaptiveFiltering = AttrTo<bool>(options, "pngAdaptiveFiltering");
|
||||
baton->pngPalette = AttrTo<bool>(options, "pngPalette");
|
||||
baton->pngQuality = AttrTo<uint32_t>(options, "pngQuality");
|
||||
baton->pngColours = AttrTo<uint32_t>(options, "pngColours");
|
||||
baton->pngDither = AttrTo<double>(options, "pngDither");
|
||||
baton->webpQuality = AttrTo<uint32_t>(options, "webpQuality");
|
||||
baton->webpAlphaQuality = AttrTo<uint32_t>(options, "webpAlphaQuality");
|
||||
baton->webpLossless = AttrTo<bool>(options, "webpLossless");
|
||||
baton->webpNearLossless = AttrTo<bool>(options, "webpNearLossless");
|
||||
baton->tiffQuality = AttrTo<uint32_t>(options, "tiffQuality");
|
||||
baton->tiffPyramid = AttrTo<bool>(options, "tiffPyramid");
|
||||
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->tiffYres = AttrTo<double>(options, "tiffYres");
|
||||
// tiff compression options
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2013, 2014, 2015, 2016, 2017 Lovell Fuller and contributors.
|
||||
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 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.
|
||||
@@ -87,6 +87,7 @@ struct PipelineBaton {
|
||||
double linearA;
|
||||
double linearB;
|
||||
double gamma;
|
||||
double gammaOut;
|
||||
bool greyscale;
|
||||
bool normalise;
|
||||
bool useExifOrientation;
|
||||
@@ -114,6 +115,10 @@ struct PipelineBaton {
|
||||
bool pngProgressive;
|
||||
int pngCompressionLevel;
|
||||
bool pngAdaptiveFiltering;
|
||||
bool pngPalette;
|
||||
int pngQuality;
|
||||
int pngColours;
|
||||
double pngDither;
|
||||
int webpQuality;
|
||||
int webpAlphaQuality;
|
||||
bool webpNearLossless;
|
||||
@@ -121,7 +126,11 @@ struct PipelineBaton {
|
||||
int tiffQuality;
|
||||
VipsForeignTiffCompression tiffCompression;
|
||||
VipsForeignTiffPredictor tiffPredictor;
|
||||
bool tiffPyramid;
|
||||
bool tiffSquash;
|
||||
bool tiffTile;
|
||||
int tiffTileHeight;
|
||||
int tiffTileWidth;
|
||||
double tiffXres;
|
||||
double tiffYres;
|
||||
std::string err;
|
||||
@@ -137,6 +146,7 @@ struct PipelineBaton {
|
||||
VipsOperationBoolean bandBoolOp;
|
||||
int extractChannel;
|
||||
bool removeAlpha;
|
||||
bool ensureAlpha;
|
||||
VipsInterpretation colourspace;
|
||||
int tileSize;
|
||||
int tileOverlap;
|
||||
@@ -145,6 +155,7 @@ struct PipelineBaton {
|
||||
std::string tileFormat;
|
||||
int tileAngle;
|
||||
VipsForeignDzDepth tileDepth;
|
||||
std::unique_ptr<double[]> recombMatrix;
|
||||
|
||||
PipelineBaton():
|
||||
input(nullptr),
|
||||
@@ -209,11 +220,19 @@ struct PipelineBaton {
|
||||
pngProgressive(false),
|
||||
pngCompressionLevel(9),
|
||||
pngAdaptiveFiltering(false),
|
||||
pngPalette(false),
|
||||
pngQuality(100),
|
||||
pngColours(256),
|
||||
pngDither(1.0),
|
||||
webpQuality(80),
|
||||
tiffQuality(80),
|
||||
tiffCompression(VIPS_FOREIGN_TIFF_COMPRESSION_JPEG),
|
||||
tiffPredictor(VIPS_FOREIGN_TIFF_PREDICTOR_HORIZONTAL),
|
||||
tiffPyramid(false),
|
||||
tiffSquash(false),
|
||||
tiffTile(false),
|
||||
tiffTileHeight(256),
|
||||
tiffTileWidth(256),
|
||||
tiffXres(1.0),
|
||||
tiffYres(1.0),
|
||||
withMetadata(false),
|
||||
@@ -227,6 +246,7 @@ struct PipelineBaton {
|
||||
bandBoolOp(VIPS_OPERATION_BOOLEAN_LAST),
|
||||
extractChannel(-1),
|
||||
removeAlpha(false),
|
||||
ensureAlpha(false),
|
||||
colourspace(VIPS_INTERPRETATION_LAST),
|
||||
tileSize(256),
|
||||
tileOverlap(0),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2013, 2014, 2015, 2016, 2017 Lovell Fuller and contributors.
|
||||
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 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.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2013, 2014, 2015, 2016, 2017 Lovell Fuller and contributors.
|
||||
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 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.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2013, 2014, 2015, 2016, 2017 Lovell Fuller and contributors.
|
||||
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 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.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2013, 2014, 2015, 2016, 2017 Lovell Fuller and contributors.
|
||||
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 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.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2013, 2014, 2015, 2016, 2017 Lovell Fuller and contributors.
|
||||
// Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019 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.
|
||||
|
||||
BIN
test/fixtures/expected/Landscape_1-recomb-saturation.jpg
vendored
Normal file
|
After Width: | Height: | Size: 82 KiB |
BIN
test/fixtures/expected/Landscape_1-recomb-sepia.jpg
vendored
Normal file
|
After Width: | Height: | Size: 77 KiB |
BIN
test/fixtures/expected/Landscape_1-recomb-sepia2.jpg
vendored
Normal file
|
After Width: | Height: | Size: 85 KiB |
BIN
test/fixtures/expected/alpha-recomb-sepia.png
vendored
Normal file
|
After Width: | Height: | Size: 209 KiB |
BIN
test/fixtures/expected/gamma-in-2.2-out-3.0.jpg
vendored
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
1
test/fixtures/index.js
vendored
@@ -70,6 +70,7 @@ module.exports = {
|
||||
inputJpgCenteredImage: getPath('centered_image.jpeg'),
|
||||
inputJpgRandom: getPath('random.jpg'), // convert -size 200x200 xc: +noise Random random.jpg
|
||||
inputJpgThRandom: getPath('thRandom.jpg'), // convert random.jpg -channel G -threshold 5% -separate +channel -negate thRandom.jpg
|
||||
inputJpgLossless: getPath('testimgl.jpg'), // Lossless JPEG from ftp://ftp.fu-berlin.de/unix/X11/graphics/ImageMagick/delegates/ljpeg-6b.tar.gz
|
||||
|
||||
inputPng: getPath('50020484-00001.png'), // http://c.searspartsdirect.com/lis_png/PLDM/50020484-00001.png
|
||||
inputPngWithTransparency: getPath('blackbug.png'), // public domain
|
||||
|
||||
BIN
test/fixtures/testimgl.jpg
vendored
Normal file
|
After Width: | Height: | Size: 38 KiB |
@@ -147,6 +147,47 @@
|
||||
...
|
||||
fun:WebPDecode
|
||||
}
|
||||
{
|
||||
cond_libwebp_generic
|
||||
Memcheck:Cond
|
||||
obj:/usr/lib/x86_64-linux-gnu/libwebp.so.6.0.2
|
||||
}
|
||||
|
||||
# tiff
|
||||
{
|
||||
param_tiff_write_encoded_tile
|
||||
Memcheck:Param
|
||||
write(buf)
|
||||
fun:write
|
||||
...
|
||||
fun:TIFFWriteEncodedTile
|
||||
}
|
||||
|
||||
# gsf
|
||||
{
|
||||
param_gsf_output_write
|
||||
Memcheck:Param
|
||||
write(buf)
|
||||
fun:write
|
||||
...
|
||||
fun:gsf_output_write
|
||||
}
|
||||
{
|
||||
value_gsf_output_write_crc32_little
|
||||
Memcheck:Value8
|
||||
fun:crc32_little
|
||||
...
|
||||
fun:gsf_output_write
|
||||
}
|
||||
|
||||
# fontconfig
|
||||
{
|
||||
leak_fontconfig_FcConfigSubstituteWithPat
|
||||
Memcheck:Leak
|
||||
match-leak-kinds: definite,indirect
|
||||
...
|
||||
fun:FcConfigSubstituteWithPat
|
||||
}
|
||||
|
||||
# libvips
|
||||
{
|
||||
@@ -197,6 +238,11 @@
|
||||
...
|
||||
fun:vips_region_prepare_to
|
||||
}
|
||||
{
|
||||
cond_libvips_vips_stats_scan
|
||||
Memcheck:Cond
|
||||
fun:vips_stats_scan
|
||||
}
|
||||
{
|
||||
value_libvips_vips_region_fill
|
||||
Memcheck:Value8
|
||||
@@ -204,6 +250,17 @@
|
||||
fun:vips_region_fill
|
||||
fun:vips_region_prepare
|
||||
}
|
||||
{
|
||||
value_libvips_vips_hist_find_uchar_scan
|
||||
Memcheck:Value8
|
||||
fun:vips_hist_find_uchar_scan
|
||||
}
|
||||
{
|
||||
value_libvips_write_webp_image
|
||||
Memcheck:Value8
|
||||
...
|
||||
fun:write_webp_image
|
||||
}
|
||||
{
|
||||
leak_libvips_init
|
||||
Memcheck:Leak
|
||||
@@ -377,6 +434,56 @@
|
||||
...
|
||||
fun:_ZN4node12NodePlatformC1EiPN2v817TracingControllerE
|
||||
}
|
||||
{
|
||||
param_nodejs_delayed_task_scheduler
|
||||
Memcheck:Param
|
||||
epoll_ctl(event)
|
||||
fun:epoll_ctl
|
||||
fun:uv__io_poll
|
||||
fun:uv_run
|
||||
fun:_ZZN4node20BackgroundTaskRunner20DelayedTaskScheduler5StartEvENUlPvE_4_FUNES2_
|
||||
}
|
||||
{
|
||||
param_nodejs_isolate_data
|
||||
Memcheck:Param
|
||||
epoll_ctl(event)
|
||||
fun:epoll_ctl
|
||||
fun:uv__io_poll
|
||||
fun:uv_run
|
||||
fun:_ZN4node5StartEPN2v87IsolateEPNS_11IsolateDataERKSt6vectorISsSaISsEES9_
|
||||
}
|
||||
{
|
||||
param_nodejs_try_init_and_run_loop
|
||||
Memcheck:Param
|
||||
epoll_ctl(event)
|
||||
fun:epoll_ctl
|
||||
fun:uv__io_poll
|
||||
fun:uv_run
|
||||
fun:_ZN4node17SyncProcessRunner23TryInitializeAndRunLoopEN2v85LocalINS1_5ValueEEE
|
||||
}
|
||||
{
|
||||
param_nodejs_run_exit_handlers
|
||||
Memcheck:Param
|
||||
epoll_ctl(event)
|
||||
fun:epoll_ctl
|
||||
fun:uv__io_poll
|
||||
fun:uv_run
|
||||
fun:_ZN4node7tracing5AgentD1Ev
|
||||
fun:_ZN4node5._215D1Ev
|
||||
fun:__run_exit_handlers
|
||||
}
|
||||
{
|
||||
leak_nodejs_crypto_entropy_source
|
||||
Memcheck:Leak
|
||||
...
|
||||
fun:_ZN4node6crypto13EntropySourceEPhm
|
||||
}
|
||||
{
|
||||
leak_nodejs_debug_options
|
||||
Memcheck:Leak
|
||||
...
|
||||
fun:_ZN4node9inspector5Agent5StartERKSsSt10shared_ptrINS_12DebugOptionsEEb
|
||||
}
|
||||
{
|
||||
leak_nan_FunctionCallbackInfo
|
||||
Memcheck:Leak
|
||||
|
||||
@@ -22,7 +22,7 @@ const median = function (values) {
|
||||
// List of files
|
||||
fs.readdirSync(userDataDir).forEach(function (file) {
|
||||
// Contents of file
|
||||
const lines = fs.readFileSync(path.join(userDataDir, file), {encoding: 'utf-8'}).split(/\r\n/);
|
||||
const lines = fs.readFileSync(path.join(userDataDir, file), { encoding: 'utf-8' }).split(/\r\n/);
|
||||
// First line = number of entries
|
||||
const entries = parseInt(lines[0], 10);
|
||||
// Verify number of entries
|
||||
|
||||
@@ -115,6 +115,7 @@ describe('Alpha transparency', function () {
|
||||
fixtures.inputWebP
|
||||
].map(function (input) {
|
||||
return sharp(input)
|
||||
.resize(10)
|
||||
.removeAlpha()
|
||||
.toBuffer({ resolveWithObject: true })
|
||||
.then(function (result) {
|
||||
@@ -122,4 +123,24 @@ describe('Alpha transparency', function () {
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
it('Ensures alpha from fixtures without transparency, ignores those with', function () {
|
||||
return Promise.all([
|
||||
fixtures.inputPngWithTransparency,
|
||||
fixtures.inputPngWithTransparency16bit,
|
||||
fixtures.inputWebPWithTransparency,
|
||||
fixtures.inputJpg,
|
||||
fixtures.inputPng,
|
||||
fixtures.inputWebP
|
||||
].map(function (input) {
|
||||
return sharp(input)
|
||||
.resize(10)
|
||||
.ensureAlpha()
|
||||
.png()
|
||||
.toBuffer({ resolveWithObject: true })
|
||||
.then(function (result) {
|
||||
assert.strictEqual(4, result.info.channels);
|
||||
});
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@ describe('Deprecated background', function () {
|
||||
it('Flatten to RGB orange', function (done) {
|
||||
sharp(fixtures.inputPngWithTransparency)
|
||||
.flatten()
|
||||
.background({r: 255, g: 102, b: 0})
|
||||
.background({ r: 255, g: 102, b: 0 })
|
||||
.resize(400, 300)
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
@@ -35,7 +35,7 @@ describe('Deprecated background', function () {
|
||||
const output = fixtures.path('output.flatten-rgb16-orange.jpg');
|
||||
sharp(fixtures.inputPngWithTransparency16bit)
|
||||
.flatten()
|
||||
.background({r: 255, g: 102, b: 0})
|
||||
.background({ r: 255, g: 102, b: 0 })
|
||||
.toFile(output, function (err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, info.size > 0);
|
||||
@@ -61,7 +61,7 @@ describe('Deprecated background', function () {
|
||||
it('extend all sides equally with RGB', function (done) {
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(120)
|
||||
.background({r: 255, g: 0, b: 0})
|
||||
.background({ r: 255, g: 0, b: 0 })
|
||||
.extend(10)
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
|
||||
@@ -38,7 +38,7 @@ describe('Deprecated embed', function () {
|
||||
it('JPEG within WebP, to include alpha channel', function (done) {
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240)
|
||||
.background({r: 0, g: 0, b: 0, alpha: 0})
|
||||
.background({ r: 0, g: 0, b: 0, alpha: 0 })
|
||||
.embed()
|
||||
.webp()
|
||||
.toBuffer(function (err, data, info) {
|
||||
@@ -86,7 +86,7 @@ describe('Deprecated embed', function () {
|
||||
sharp(fixtures.inputPngWithTransparency16bit)
|
||||
.resize(32, 16)
|
||||
.embed()
|
||||
.background({r: 0, g: 0, b: 0, alpha: 0})
|
||||
.background({ r: 0, g: 0, b: 0, alpha: 0 })
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
@@ -102,7 +102,7 @@ describe('Deprecated embed', function () {
|
||||
sharp(fixtures.inputPngWithGreyAlpha)
|
||||
.resize(32, 16)
|
||||
.embed()
|
||||
.background({r: 0, g: 0, b: 0, alpha: 0})
|
||||
.background({ r: 0, g: 0, b: 0, alpha: 0 })
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
@@ -153,7 +153,7 @@ describe('Deprecated embed', function () {
|
||||
it('Embed gravity horizontal northwest', function (done) {
|
||||
sharp(fixtures.inputPngEmbed)
|
||||
.resize(200, 100)
|
||||
.background({r: 0, g: 0, b: 0, alpha: 0})
|
||||
.background({ r: 0, g: 0, b: 0, alpha: 0 })
|
||||
.embed(sharp.gravity.northwest)
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
@@ -169,7 +169,7 @@ describe('Deprecated embed', function () {
|
||||
it('Embed gravity horizontal north', function (done) {
|
||||
sharp(fixtures.inputPngEmbed)
|
||||
.resize(200, 100)
|
||||
.background({r: 0, g: 0, b: 0, alpha: 0})
|
||||
.background({ r: 0, g: 0, b: 0, alpha: 0 })
|
||||
.embed(sharp.gravity.north)
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
@@ -185,7 +185,7 @@ describe('Deprecated embed', function () {
|
||||
it('Embed gravity horizontal northeast', function (done) {
|
||||
sharp(fixtures.inputPngEmbed)
|
||||
.resize(200, 100)
|
||||
.background({r: 0, g: 0, b: 0, alpha: 0})
|
||||
.background({ r: 0, g: 0, b: 0, alpha: 0 })
|
||||
.embed(sharp.gravity.northeast)
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
@@ -201,7 +201,7 @@ describe('Deprecated embed', function () {
|
||||
it('Embed gravity horizontal east', function (done) {
|
||||
sharp(fixtures.inputPngEmbed)
|
||||
.resize(200, 100)
|
||||
.background({r: 0, g: 0, b: 0, alpha: 0})
|
||||
.background({ r: 0, g: 0, b: 0, alpha: 0 })
|
||||
.embed(sharp.gravity.east)
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
@@ -217,7 +217,7 @@ describe('Deprecated embed', function () {
|
||||
it('Embed gravity horizontal southeast', function (done) {
|
||||
sharp(fixtures.inputPngEmbed)
|
||||
.resize(200, 100)
|
||||
.background({r: 0, g: 0, b: 0, alpha: 0})
|
||||
.background({ r: 0, g: 0, b: 0, alpha: 0 })
|
||||
.embed(sharp.gravity.southeast)
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
@@ -233,7 +233,7 @@ describe('Deprecated embed', function () {
|
||||
it('Embed gravity horizontal south', function (done) {
|
||||
sharp(fixtures.inputPngEmbed)
|
||||
.resize(200, 100)
|
||||
.background({r: 0, g: 0, b: 0, alpha: 0})
|
||||
.background({ r: 0, g: 0, b: 0, alpha: 0 })
|
||||
.embed(sharp.gravity.south)
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
@@ -249,7 +249,7 @@ describe('Deprecated embed', function () {
|
||||
it('Embed gravity horizontal southwest', function (done) {
|
||||
sharp(fixtures.inputPngEmbed)
|
||||
.resize(200, 100)
|
||||
.background({r: 0, g: 0, b: 0, alpha: 0})
|
||||
.background({ r: 0, g: 0, b: 0, alpha: 0 })
|
||||
.embed(sharp.gravity.southwest)
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
@@ -265,7 +265,7 @@ describe('Deprecated embed', function () {
|
||||
it('Embed gravity horizontal west', function (done) {
|
||||
sharp(fixtures.inputPngEmbed)
|
||||
.resize(200, 100)
|
||||
.background({r: 0, g: 0, b: 0, alpha: 0})
|
||||
.background({ r: 0, g: 0, b: 0, alpha: 0 })
|
||||
.embed(sharp.gravity.west)
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
@@ -281,7 +281,7 @@ describe('Deprecated embed', function () {
|
||||
it('Embed gravity horizontal center', function (done) {
|
||||
sharp(fixtures.inputPngEmbed)
|
||||
.resize(200, 100)
|
||||
.background({r: 0, g: 0, b: 0, alpha: 0})
|
||||
.background({ r: 0, g: 0, b: 0, alpha: 0 })
|
||||
.embed(sharp.gravity.center)
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
@@ -297,7 +297,7 @@ describe('Deprecated embed', function () {
|
||||
it('Embed gravity vertical northwest', function (done) {
|
||||
sharp(fixtures.inputPngEmbed)
|
||||
.resize(200, 200)
|
||||
.background({r: 0, g: 0, b: 0, alpha: 0})
|
||||
.background({ r: 0, g: 0, b: 0, alpha: 0 })
|
||||
.embed(sharp.gravity.northwest)
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
@@ -313,7 +313,7 @@ describe('Deprecated embed', function () {
|
||||
it('Embed gravity vertical north', function (done) {
|
||||
sharp(fixtures.inputPngEmbed)
|
||||
.resize(200, 200)
|
||||
.background({r: 0, g: 0, b: 0, alpha: 0})
|
||||
.background({ r: 0, g: 0, b: 0, alpha: 0 })
|
||||
.embed(sharp.gravity.north)
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
@@ -329,7 +329,7 @@ describe('Deprecated embed', function () {
|
||||
it('Embed gravity vertical northeast', function (done) {
|
||||
sharp(fixtures.inputPngEmbed)
|
||||
.resize(200, 200)
|
||||
.background({r: 0, g: 0, b: 0, alpha: 0})
|
||||
.background({ r: 0, g: 0, b: 0, alpha: 0 })
|
||||
.embed(sharp.gravity.northeast)
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
@@ -345,7 +345,7 @@ describe('Deprecated embed', function () {
|
||||
it('Embed gravity vertical east', function (done) {
|
||||
sharp(fixtures.inputPngEmbed)
|
||||
.resize(200, 200)
|
||||
.background({r: 0, g: 0, b: 0, alpha: 0})
|
||||
.background({ r: 0, g: 0, b: 0, alpha: 0 })
|
||||
.embed(sharp.gravity.east)
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
@@ -361,7 +361,7 @@ describe('Deprecated embed', function () {
|
||||
it('Embed gravity vertical southeast', function (done) {
|
||||
sharp(fixtures.inputPngEmbed)
|
||||
.resize(200, 200)
|
||||
.background({r: 0, g: 0, b: 0, alpha: 0})
|
||||
.background({ r: 0, g: 0, b: 0, alpha: 0 })
|
||||
.embed(sharp.gravity.southeast)
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
@@ -377,7 +377,7 @@ describe('Deprecated embed', function () {
|
||||
it('Embed gravity vertical south', function (done) {
|
||||
sharp(fixtures.inputPngEmbed)
|
||||
.resize(200, 200)
|
||||
.background({r: 0, g: 0, b: 0, alpha: 0})
|
||||
.background({ r: 0, g: 0, b: 0, alpha: 0 })
|
||||
.embed(sharp.gravity.south)
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
@@ -393,7 +393,7 @@ describe('Deprecated embed', function () {
|
||||
it('Embed gravity vertical southwest', function (done) {
|
||||
sharp(fixtures.inputPngEmbed)
|
||||
.resize(200, 200)
|
||||
.background({r: 0, g: 0, b: 0, alpha: 0})
|
||||
.background({ r: 0, g: 0, b: 0, alpha: 0 })
|
||||
.embed(sharp.gravity.southwest)
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
@@ -409,7 +409,7 @@ describe('Deprecated embed', function () {
|
||||
it('Embed gravity vertical west', function (done) {
|
||||
sharp(fixtures.inputPngEmbed)
|
||||
.resize(200, 200)
|
||||
.background({r: 0, g: 0, b: 0, alpha: 0})
|
||||
.background({ r: 0, g: 0, b: 0, alpha: 0 })
|
||||
.embed(sharp.gravity.west)
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
@@ -425,7 +425,7 @@ describe('Deprecated embed', function () {
|
||||
it('Embed gravity vertical center', function (done) {
|
||||
sharp(fixtures.inputPngEmbed)
|
||||
.resize(200, 200)
|
||||
.background({r: 0, g: 0, b: 0, alpha: 0})
|
||||
.background({ r: 0, g: 0, b: 0, alpha: 0 })
|
||||
.embed(sharp.gravity.center)
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
|
||||
@@ -54,7 +54,7 @@ describe('Extend', function () {
|
||||
});
|
||||
it('partial object fails', function () {
|
||||
assert.throws(function () {
|
||||
sharp().extend({top: 1});
|
||||
sharp().extend({ top: 1 });
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -6,10 +6,9 @@ const sharp = require('../../');
|
||||
const fixtures = require('../fixtures');
|
||||
|
||||
describe('failOnError', function () {
|
||||
it('handles truncated JPEG by default', function (done) {
|
||||
sharp(fixtures.inputJpgTruncated)
|
||||
it('handles truncated JPEG', function (done) {
|
||||
sharp(fixtures.inputJpgTruncated, { failOnError: false })
|
||||
.resize(320, 240)
|
||||
// .toFile(fixtures.expected('truncated.jpg'), done);
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
@@ -19,10 +18,9 @@ describe('failOnError', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('handles truncated PNG by default', function (done) {
|
||||
sharp(fixtures.inputPngTruncated)
|
||||
it('handles truncated PNG', function (done) {
|
||||
sharp(fixtures.inputPngTruncated, { failOnError: false })
|
||||
.resize(320, 240)
|
||||
// .toFile(fixtures.expected('truncated.png'), done);
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('png', info.format);
|
||||
@@ -46,26 +44,26 @@ describe('failOnError', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('returns errors to callback for truncated JPEG when failOnError is set', function (done) {
|
||||
sharp(fixtures.inputJpgTruncated, { failOnError: true }).toBuffer(function (err, data, info) {
|
||||
it('returns errors to callback for truncated JPEG', function (done) {
|
||||
sharp(fixtures.inputJpgTruncated).toBuffer(function (err, data, info) {
|
||||
assert.ok(err.message.includes('VipsJpeg: Premature end of JPEG file'), err);
|
||||
assert.equal(data, null);
|
||||
assert.equal(info, null);
|
||||
assert.strictEqual(data, null);
|
||||
assert.strictEqual(info, null);
|
||||
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) {
|
||||
it('returns errors to callback for truncated PNG', function (done) {
|
||||
sharp(fixtures.inputPngTruncated).toBuffer(function (err, data, info) {
|
||||
assert.ok(err.message.includes('vipspng: libpng read error'), err);
|
||||
assert.equal(data, null);
|
||||
assert.equal(info, null);
|
||||
assert.strictEqual(data, null);
|
||||
assert.strictEqual(info, null);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects promises for truncated JPEG when failOnError is set', function (done) {
|
||||
sharp(fixtures.inputJpgTruncated, { failOnError: true })
|
||||
it('rejects promises for truncated JPEG', function (done) {
|
||||
sharp(fixtures.inputJpgTruncated)
|
||||
.toBuffer()
|
||||
.then(() => {
|
||||
throw new Error('Expected rejection');
|
||||
|
||||
@@ -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) {
|
||||
sharp(fixtures.inputPngOverlayLayer1)
|
||||
.resize(320)
|
||||
@@ -57,9 +70,15 @@ describe('Gamma correction', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('invalid value', function () {
|
||||
it('invalid first parameter value', function () {
|
||||
assert.throws(function () {
|
||||
sharp(fixtures.inputJpgWithGammaHoliness).gamma(4);
|
||||
});
|
||||
});
|
||||
|
||||
it('invalid second parameter value', function () {
|
||||
assert.throws(function () {
|
||||
sharp(fixtures.inputJpgWithGammaHoliness).gamma(2.2, 4);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
1007
test/unit/io.js
@@ -138,7 +138,7 @@ describe('Image channel insertion', function () {
|
||||
|
||||
it('Invalid raw buffer description', function () {
|
||||
assert.throws(function () {
|
||||
sharp().joinChannel(fs.readFileSync(fixtures.inputPng), {raw: {}});
|
||||
sharp().joinChannel(fs.readFileSync(fixtures.inputPng), { raw: {} });
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
262
test/unit/jpeg.js
Normal file
@@ -0,0 +1,262 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('assert');
|
||||
|
||||
const sharp = require('../../');
|
||||
const fixtures = require('../fixtures');
|
||||
|
||||
describe('JPEG', function () {
|
||||
it('JPEG quality', function (done) {
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240)
|
||||
.jpeg({ quality: 70 })
|
||||
.toBuffer(function (err, buffer70) {
|
||||
if (err) throw err;
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240)
|
||||
.toBuffer(function (err, buffer80) {
|
||||
if (err) throw err;
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240)
|
||||
.jpeg({ quality: 90 })
|
||||
.toBuffer(function (err, buffer90) {
|
||||
if (err) throw err;
|
||||
assert(buffer70.length < buffer80.length);
|
||||
assert(buffer80.length < buffer90.length);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Invalid JPEG quality', function () {
|
||||
[-1, 88.2, 'test'].forEach(function (quality) {
|
||||
it(quality.toString(), function () {
|
||||
assert.throws(function () {
|
||||
sharp().jpeg({ quality: quality });
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Invalid JPEG quantisation table', function () {
|
||||
[-1, 88.2, 'test'].forEach(function (table) {
|
||||
it(table.toString(), function () {
|
||||
assert.throws(function () {
|
||||
sharp().jpeg({ quantisationTable: table });
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Progressive JPEG image', function (done) {
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240)
|
||||
.jpeg({ progressive: false })
|
||||
.toBuffer(function (err, nonProgressiveData, nonProgressiveInfo) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, nonProgressiveData.length > 0);
|
||||
assert.strictEqual(nonProgressiveData.length, nonProgressiveInfo.size);
|
||||
assert.strictEqual('jpeg', nonProgressiveInfo.format);
|
||||
assert.strictEqual(320, nonProgressiveInfo.width);
|
||||
assert.strictEqual(240, nonProgressiveInfo.height);
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240)
|
||||
.jpeg({ progressive: true })
|
||||
.toBuffer(function (err, progressiveData, progressiveInfo) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, progressiveData.length > 0);
|
||||
assert.strictEqual(progressiveData.length, progressiveInfo.size);
|
||||
assert.strictEqual(false, progressiveData.length === nonProgressiveData.length);
|
||||
assert.strictEqual('jpeg', progressiveInfo.format);
|
||||
assert.strictEqual(320, progressiveInfo.width);
|
||||
assert.strictEqual(240, progressiveInfo.height);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Without chroma subsampling generates larger file', function (done) {
|
||||
// First generate with chroma subsampling (default)
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240)
|
||||
.jpeg({ chromaSubsampling: '4:2:0' })
|
||||
.toBuffer(function (err, withChromaSubsamplingData, withChromaSubsamplingInfo) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, withChromaSubsamplingData.length > 0);
|
||||
assert.strictEqual(withChromaSubsamplingData.length, withChromaSubsamplingInfo.size);
|
||||
assert.strictEqual('jpeg', withChromaSubsamplingInfo.format);
|
||||
assert.strictEqual(320, withChromaSubsamplingInfo.width);
|
||||
assert.strictEqual(240, withChromaSubsamplingInfo.height);
|
||||
// Then generate without
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240)
|
||||
.jpeg({ chromaSubsampling: '4:4:4' })
|
||||
.toBuffer(function (err, withoutChromaSubsamplingData, withoutChromaSubsamplingInfo) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, withoutChromaSubsamplingData.length > 0);
|
||||
assert.strictEqual(withoutChromaSubsamplingData.length, withoutChromaSubsamplingInfo.size);
|
||||
assert.strictEqual('jpeg', withoutChromaSubsamplingInfo.format);
|
||||
assert.strictEqual(320, withoutChromaSubsamplingInfo.width);
|
||||
assert.strictEqual(240, withoutChromaSubsamplingInfo.height);
|
||||
assert.strictEqual(true, withChromaSubsamplingData.length < withoutChromaSubsamplingData.length);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Invalid JPEG chromaSubsampling value throws error', function () {
|
||||
assert.throws(function () {
|
||||
sharp().jpeg({ chromaSubsampling: '4:2:2' });
|
||||
});
|
||||
});
|
||||
|
||||
it('Trellis quantisation', function (done) {
|
||||
// First generate without
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240)
|
||||
.jpeg({ trellisQuantisation: false })
|
||||
.toBuffer(function (err, withoutData, withoutInfo) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, withoutData.length > 0);
|
||||
assert.strictEqual(withoutData.length, withoutInfo.size);
|
||||
assert.strictEqual('jpeg', withoutInfo.format);
|
||||
assert.strictEqual(320, withoutInfo.width);
|
||||
assert.strictEqual(240, withoutInfo.height);
|
||||
// Then generate with
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240)
|
||||
.jpeg({ trellisQuantization: true })
|
||||
.toBuffer(function (err, withData, withInfo) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, withData.length > 0);
|
||||
assert.strictEqual(withData.length, withInfo.size);
|
||||
assert.strictEqual('jpeg', withInfo.format);
|
||||
assert.strictEqual(320, withInfo.width);
|
||||
assert.strictEqual(240, withInfo.height);
|
||||
// Verify image is same (as mozjpeg may not be present) size or less
|
||||
assert.strictEqual(true, withData.length <= withoutData.length);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Overshoot deringing', function (done) {
|
||||
// First generate without
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240)
|
||||
.jpeg({ overshootDeringing: false })
|
||||
.toBuffer(function (err, withoutData, withoutInfo) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, withoutData.length > 0);
|
||||
assert.strictEqual(withoutData.length, withoutInfo.size);
|
||||
assert.strictEqual('jpeg', withoutInfo.format);
|
||||
assert.strictEqual(320, withoutInfo.width);
|
||||
assert.strictEqual(240, withoutInfo.height);
|
||||
// Then generate with
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240)
|
||||
.jpeg({ overshootDeringing: true })
|
||||
.toBuffer(function (err, withData, withInfo) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, withData.length > 0);
|
||||
assert.strictEqual(withData.length, withInfo.size);
|
||||
assert.strictEqual('jpeg', withInfo.format);
|
||||
assert.strictEqual(320, withInfo.width);
|
||||
assert.strictEqual(240, withInfo.height);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Optimise scans generates different output length', function (done) {
|
||||
// First generate without
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240)
|
||||
.jpeg({ optimiseScans: false })
|
||||
.toBuffer(function (err, withoutData, withoutInfo) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, withoutData.length > 0);
|
||||
assert.strictEqual(withoutData.length, withoutInfo.size);
|
||||
assert.strictEqual('jpeg', withoutInfo.format);
|
||||
assert.strictEqual(320, withoutInfo.width);
|
||||
assert.strictEqual(240, withoutInfo.height);
|
||||
// Then generate with
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240)
|
||||
.jpeg({ optimizeScans: true })
|
||||
.toBuffer(function (err, withData, withInfo) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, withData.length > 0);
|
||||
assert.strictEqual(withData.length, withInfo.size);
|
||||
assert.strictEqual('jpeg', withInfo.format);
|
||||
assert.strictEqual(320, withInfo.width);
|
||||
assert.strictEqual(240, withInfo.height);
|
||||
// Verify image is of a different size (progressive output even without mozjpeg)
|
||||
assert.notStrictEqual(withData.length, withoutData.length);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Optimise coding generates smaller output length', function (done) {
|
||||
// First generate with optimize coding enabled (default)
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240)
|
||||
.jpeg()
|
||||
.toBuffer(function (err, withOptimiseCoding, withInfo) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, withOptimiseCoding.length > 0);
|
||||
assert.strictEqual(withOptimiseCoding.length, withInfo.size);
|
||||
assert.strictEqual('jpeg', withInfo.format);
|
||||
assert.strictEqual(320, withInfo.width);
|
||||
assert.strictEqual(240, withInfo.height);
|
||||
// Then generate with coding disabled
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240)
|
||||
.jpeg({ optimizeCoding: false })
|
||||
.toBuffer(function (err, withoutOptimiseCoding, withoutInfo) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, withoutOptimiseCoding.length > 0);
|
||||
assert.strictEqual(withoutOptimiseCoding.length, withoutInfo.size);
|
||||
assert.strictEqual('jpeg', withoutInfo.format);
|
||||
assert.strictEqual(320, withoutInfo.width);
|
||||
assert.strictEqual(240, withoutInfo.height);
|
||||
// Verify optimised image is of a smaller size
|
||||
assert.strictEqual(true, withOptimiseCoding.length < withoutOptimiseCoding.length);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Specifying quantisation table provides different JPEG', function (done) {
|
||||
// First generate with default quantisation table
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240)
|
||||
.jpeg({ optimiseCoding: false })
|
||||
.toBuffer(function (err, withDefaultQuantisationTable, withInfo) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, withDefaultQuantisationTable.length > 0);
|
||||
assert.strictEqual(withDefaultQuantisationTable.length, withInfo.size);
|
||||
assert.strictEqual('jpeg', withInfo.format);
|
||||
assert.strictEqual(320, withInfo.width);
|
||||
assert.strictEqual(240, withInfo.height);
|
||||
// Then generate with different quantisation table
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240)
|
||||
.jpeg({ optimiseCoding: false, quantisationTable: 3 })
|
||||
.toBuffer(function (err, withQuantTable3, withoutInfo) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, withQuantTable3.length > 0);
|
||||
assert.strictEqual(withQuantTable3.length, withoutInfo.size);
|
||||
assert.strictEqual('jpeg', withoutInfo.format);
|
||||
assert.strictEqual(320, withoutInfo.width);
|
||||
assert.strictEqual(240, withoutInfo.height);
|
||||
|
||||
// Verify image is same (as mozjpeg may not be present) size or less
|
||||
assert.strictEqual(true, withQuantTable3.length <= withDefaultQuantisationTable.length);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -103,6 +103,29 @@ describe('Image metadata', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('Multipage TIFF', function (done) {
|
||||
sharp(fixtures.inputTiffMultipage).metadata(function (err, metadata) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('tiff', metadata.format);
|
||||
assert.strictEqual('undefined', typeof metadata.size);
|
||||
assert.strictEqual(2464, metadata.width);
|
||||
assert.strictEqual(3248, metadata.height);
|
||||
assert.strictEqual('b-w', metadata.space);
|
||||
assert.strictEqual(1, metadata.channels);
|
||||
assert.strictEqual('uchar', metadata.depth);
|
||||
assert.strictEqual(300, metadata.density);
|
||||
assert.strictEqual('undefined', typeof metadata.chromaSubsampling);
|
||||
assert.strictEqual(false, metadata.isProgressive);
|
||||
assert.strictEqual(2, metadata.pages);
|
||||
assert.strictEqual(false, metadata.hasProfile);
|
||||
assert.strictEqual(false, metadata.hasAlpha);
|
||||
assert.strictEqual(1, metadata.orientation);
|
||||
assert.strictEqual('undefined', typeof metadata.exif);
|
||||
assert.strictEqual('undefined', typeof metadata.icc);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('PNG', function (done) {
|
||||
sharp(fixtures.inputPng).metadata(function (err, metadata) {
|
||||
if (err) throw err;
|
||||
@@ -434,6 +457,7 @@ describe('Image metadata', function () {
|
||||
sharp(fixtures.inputJpgWithCorruptHeader)
|
||||
.metadata(function (err) {
|
||||
assert.strictEqual(true, !!err);
|
||||
assert.strictEqual(true, /Input file has corrupt header: VipsJpeg: Premature end of JPEG file/.test(err.message));
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -442,6 +466,16 @@ describe('Image metadata', function () {
|
||||
sharp(fs.readFileSync(fixtures.inputJpgWithCorruptHeader))
|
||||
.metadata(function (err) {
|
||||
assert.strictEqual(true, !!err);
|
||||
assert.strictEqual(true, /Input buffer has corrupt header: VipsJpeg: Premature end of JPEG file/.test(err.message));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Unsupported lossless JPEG passes underlying error message', function (done) {
|
||||
sharp(fixtures.inputJpgLossless)
|
||||
.metadata(function (err) {
|
||||
assert.strictEqual(true, !!err);
|
||||
assert.strictEqual(true, /Input file has corrupt header: VipsJpeg: Unsupported JPEG process: SOF type 0xc3/.test(err.message));
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -449,12 +483,12 @@ describe('Image metadata', function () {
|
||||
describe('Invalid withMetadata parameters', function () {
|
||||
it('String orientation', function () {
|
||||
assert.throws(function () {
|
||||
sharp().withMetadata({orientation: 'zoinks'});
|
||||
sharp().withMetadata({ orientation: 'zoinks' });
|
||||
});
|
||||
});
|
||||
it('Negative orientation', function () {
|
||||
assert.throws(function () {
|
||||
sharp().withMetadata({orientation: -1});
|
||||
sharp().withMetadata({ orientation: -1 });
|
||||
});
|
||||
});
|
||||
it('Zero orientation', function () {
|
||||
@@ -464,7 +498,7 @@ describe('Image metadata', function () {
|
||||
});
|
||||
it('Too large orientation', function () {
|
||||
assert.throws(function () {
|
||||
sharp().withMetadata({orientation: 9});
|
||||
sharp().withMetadata({ orientation: 9 });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
171
test/unit/png.js
Normal file
@@ -0,0 +1,171 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const assert = require('assert');
|
||||
|
||||
const sharp = require('../../');
|
||||
const fixtures = require('../fixtures');
|
||||
|
||||
describe('PNG', function () {
|
||||
it('compression level is valid', function () {
|
||||
assert.doesNotThrow(function () {
|
||||
sharp().png({ compressionLevel: 0 });
|
||||
});
|
||||
});
|
||||
|
||||
it('compression level is invalid', function () {
|
||||
assert.throws(function () {
|
||||
sharp().png({ compressionLevel: -1 });
|
||||
});
|
||||
});
|
||||
|
||||
it('default compressionLevel generates smaller file than compressionLevel=6', function (done) {
|
||||
// First generate with default compressionLevel
|
||||
sharp(fixtures.inputPng)
|
||||
.resize(320, 240)
|
||||
.png()
|
||||
.toBuffer(function (err, defaultData, defaultInfo) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, defaultData.length > 0);
|
||||
assert.strictEqual('png', defaultInfo.format);
|
||||
// Then generate with compressionLevel=6
|
||||
sharp(fixtures.inputPng)
|
||||
.resize(320, 240)
|
||||
.png({ compressionLevel: 6 })
|
||||
.toBuffer(function (err, largerData, largerInfo) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, largerData.length > 0);
|
||||
assert.strictEqual('png', largerInfo.format);
|
||||
assert.strictEqual(true, defaultData.length < largerData.length);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('without adaptiveFiltering generates smaller file', function (done) {
|
||||
// First generate with adaptive filtering
|
||||
sharp(fixtures.inputPng)
|
||||
.resize(320, 240)
|
||||
.png({ adaptiveFiltering: true })
|
||||
.toBuffer(function (err, adaptiveData, adaptiveInfo) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, adaptiveData.length > 0);
|
||||
assert.strictEqual(adaptiveData.length, adaptiveInfo.size);
|
||||
assert.strictEqual('png', adaptiveInfo.format);
|
||||
assert.strictEqual(320, adaptiveInfo.width);
|
||||
assert.strictEqual(240, adaptiveInfo.height);
|
||||
// Then generate without
|
||||
sharp(fixtures.inputPng)
|
||||
.resize(320, 240)
|
||||
.png({ adaptiveFiltering: false })
|
||||
.toBuffer(function (err, withoutAdaptiveData, withoutAdaptiveInfo) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, withoutAdaptiveData.length > 0);
|
||||
assert.strictEqual(withoutAdaptiveData.length, withoutAdaptiveInfo.size);
|
||||
assert.strictEqual('png', withoutAdaptiveInfo.format);
|
||||
assert.strictEqual(320, withoutAdaptiveInfo.width);
|
||||
assert.strictEqual(240, withoutAdaptiveInfo.height);
|
||||
assert.strictEqual(true, withoutAdaptiveData.length < adaptiveData.length);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Invalid PNG adaptiveFiltering value throws error', function () {
|
||||
assert.throws(function () {
|
||||
sharp().png({ adaptiveFiltering: 1 });
|
||||
});
|
||||
});
|
||||
|
||||
it('Progressive PNG image', function (done) {
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240)
|
||||
.png({ progressive: false })
|
||||
.toBuffer(function (err, nonProgressiveData, nonProgressiveInfo) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, nonProgressiveData.length > 0);
|
||||
assert.strictEqual(nonProgressiveData.length, nonProgressiveInfo.size);
|
||||
assert.strictEqual('png', nonProgressiveInfo.format);
|
||||
assert.strictEqual(320, nonProgressiveInfo.width);
|
||||
assert.strictEqual(240, nonProgressiveInfo.height);
|
||||
sharp(nonProgressiveData)
|
||||
.png({ progressive: true })
|
||||
.toBuffer(function (err, progressiveData, progressiveInfo) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, progressiveData.length > 0);
|
||||
assert.strictEqual(progressiveData.length, progressiveInfo.size);
|
||||
assert.strictEqual(true, progressiveData.length > nonProgressiveData.length);
|
||||
assert.strictEqual('png', progressiveInfo.format);
|
||||
assert.strictEqual(320, progressiveInfo.width);
|
||||
assert.strictEqual(240, progressiveInfo.height);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Valid PNG libimagequant palette value does not throw error', function () {
|
||||
assert.doesNotThrow(function () {
|
||||
sharp().png({ palette: false });
|
||||
});
|
||||
});
|
||||
|
||||
it('Invalid PNG libimagequant palette value throws error', function () {
|
||||
assert.throws(function () {
|
||||
sharp().png({ palette: 'fail' });
|
||||
});
|
||||
});
|
||||
|
||||
it('Valid PNG libimagequant quality value produces image of same size or smaller', function () {
|
||||
const inputPngBuffer = fs.readFileSync(fixtures.inputPng);
|
||||
return Promise.all([
|
||||
sharp(inputPngBuffer).resize(10).png({ palette: true, quality: 80 }).toBuffer(),
|
||||
sharp(inputPngBuffer).resize(10).png({ palette: true, quality: 100 }).toBuffer()
|
||||
]).then(function (data) {
|
||||
assert.strictEqual(true, data[0].length <= data[1].length);
|
||||
});
|
||||
});
|
||||
|
||||
it('Invalid PNG libimagequant quality value throws error', function () {
|
||||
assert.throws(function () {
|
||||
sharp().png({ palette: true, quality: 101 });
|
||||
});
|
||||
});
|
||||
|
||||
it('Valid PNG libimagequant colours value produces image of same size or smaller', function () {
|
||||
const inputPngBuffer = fs.readFileSync(fixtures.inputPng);
|
||||
return Promise.all([
|
||||
sharp(inputPngBuffer).resize(10).png({ palette: true, colours: 100 }).toBuffer(),
|
||||
sharp(inputPngBuffer).resize(10).png({ palette: true, colours: 200 }).toBuffer()
|
||||
]).then(function (data) {
|
||||
assert.strictEqual(true, data[0].length <= data[1].length);
|
||||
});
|
||||
});
|
||||
|
||||
it('Invalid PNG libimagequant colours value throws error', function () {
|
||||
assert.throws(function () {
|
||||
sharp().png({ palette: true, colours: -1 });
|
||||
});
|
||||
});
|
||||
|
||||
it('Invalid PNG libimagequant colors value throws error', function () {
|
||||
assert.throws(function () {
|
||||
sharp().png({ palette: true, colors: 0.1 });
|
||||
});
|
||||
});
|
||||
|
||||
it('Valid PNG libimagequant dither value produces image of same size or smaller', function () {
|
||||
const inputPngBuffer = fs.readFileSync(fixtures.inputPng);
|
||||
return Promise.all([
|
||||
sharp(inputPngBuffer).resize(10).png({ palette: true, dither: 0.1 }).toBuffer(),
|
||||
sharp(inputPngBuffer).resize(10).png({ palette: true, dither: 0.9 }).toBuffer()
|
||||
]).then(function (data) {
|
||||
assert.strictEqual(true, data[0].length <= data[1].length);
|
||||
});
|
||||
});
|
||||
|
||||
it('Invalid PNG libimagequant dither value throws error', function () {
|
||||
assert.throws(function () {
|
||||
sharp().png({ palette: true, dither: 'fail' });
|
||||
});
|
||||
});
|
||||
});
|
||||
145
test/unit/raw.js
Normal file
@@ -0,0 +1,145 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('assert');
|
||||
|
||||
const sharp = require('../../');
|
||||
const fixtures = require('../fixtures');
|
||||
|
||||
describe('Raw pixel data', function () {
|
||||
describe('Raw pixel input', function () {
|
||||
it('Missing options', function () {
|
||||
assert.throws(function () {
|
||||
sharp({ raw: {} });
|
||||
});
|
||||
});
|
||||
|
||||
it('Incomplete options', function () {
|
||||
assert.throws(function () {
|
||||
sharp({ raw: { width: 1, height: 1 } });
|
||||
});
|
||||
});
|
||||
|
||||
it('Invalid channels', function () {
|
||||
assert.throws(function () {
|
||||
sharp({ raw: { width: 1, height: 1, channels: 5 } });
|
||||
});
|
||||
});
|
||||
|
||||
it('Invalid height', function () {
|
||||
assert.throws(function () {
|
||||
sharp({ raw: { width: 1, height: 0, channels: 4 } });
|
||||
});
|
||||
});
|
||||
|
||||
it('Invalid width', function () {
|
||||
assert.throws(function () {
|
||||
sharp({ raw: { width: 'zoinks', height: 1, channels: 4 } });
|
||||
});
|
||||
});
|
||||
|
||||
it('RGB', function (done) {
|
||||
// Convert to raw pixel data
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(256)
|
||||
.raw()
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(256, info.width);
|
||||
assert.strictEqual(209, info.height);
|
||||
assert.strictEqual(3, info.channels);
|
||||
// Convert back to JPEG
|
||||
sharp(data, {
|
||||
raw: {
|
||||
width: info.width,
|
||||
height: info.height,
|
||||
channels: info.channels
|
||||
} })
|
||||
.jpeg()
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(256, info.width);
|
||||
assert.strictEqual(209, info.height);
|
||||
assert.strictEqual(3, info.channels);
|
||||
fixtures.assertSimilar(fixtures.inputJpg, data, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('RGBA', function (done) {
|
||||
// Convert to raw pixel data
|
||||
sharp(fixtures.inputPngOverlayLayer1)
|
||||
.resize(256)
|
||||
.raw()
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(256, info.width);
|
||||
assert.strictEqual(192, info.height);
|
||||
assert.strictEqual(4, info.channels);
|
||||
// Convert back to PNG
|
||||
sharp(data, {
|
||||
raw: {
|
||||
width: info.width,
|
||||
height: info.height,
|
||||
channels: info.channels
|
||||
} })
|
||||
.png()
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(256, info.width);
|
||||
assert.strictEqual(192, info.height);
|
||||
assert.strictEqual(4, info.channels);
|
||||
fixtures.assertSimilar(fixtures.inputPngOverlayLayer1, data, { threshold: 7 }, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Ouput raw, uncompressed image data', function () {
|
||||
it('1 channel greyscale image', function (done) {
|
||||
sharp(fixtures.inputJpg)
|
||||
.greyscale()
|
||||
.resize(32, 24)
|
||||
.raw()
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(32 * 24 * 1, info.size);
|
||||
assert.strictEqual(data.length, info.size);
|
||||
assert.strictEqual('raw', info.format);
|
||||
assert.strictEqual(32, info.width);
|
||||
assert.strictEqual(24, info.height);
|
||||
assert.strictEqual(1, info.channels);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('3 channel colour image without transparency', function (done) {
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(32, 24)
|
||||
.toFormat('raw')
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(32 * 24 * 3, info.size);
|
||||
assert.strictEqual(data.length, info.size);
|
||||
assert.strictEqual('raw', info.format);
|
||||
assert.strictEqual(32, info.width);
|
||||
assert.strictEqual(24, info.height);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('4 channel colour image with transparency', function (done) {
|
||||
sharp(fixtures.inputPngWithTransparency)
|
||||
.resize(32, 24)
|
||||
.toFormat(sharp.format.raw)
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(32 * 24 * 4, info.size);
|
||||
assert.strictEqual(data.length, info.size);
|
||||
assert.strictEqual('raw', info.format);
|
||||
assert.strictEqual(32, info.width);
|
||||
assert.strictEqual(24, info.height);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
135
test/unit/recomb.js
Normal 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]]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -116,7 +116,7 @@ describe('Resize fit=contain', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it.skip('TIFF in LAB colourspace onto RGBA background', function (done) {
|
||||
it('TIFF in LAB colourspace onto RGBA background', function (done) {
|
||||
sharp(fixtures.inputTiffCielab)
|
||||
.resize(64, 128, {
|
||||
fit: 'contain',
|
||||
|
||||
@@ -497,6 +497,7 @@ describe('Resize dimensions', function () {
|
||||
[
|
||||
sharp.kernel.nearest,
|
||||
sharp.kernel.cubic,
|
||||
sharp.kernel.mitchell,
|
||||
sharp.kernel.lanczos2,
|
||||
sharp.kernel.lanczos3
|
||||
].forEach(function (kernel) {
|
||||
@@ -524,6 +525,40 @@ describe('Resize dimensions', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('Ensure shortest edge (height) is at least 1 pixel', function () {
|
||||
return sharp({
|
||||
create: {
|
||||
width: 10,
|
||||
height: 2,
|
||||
channels: 3,
|
||||
background: 'red'
|
||||
}
|
||||
})
|
||||
.resize(2)
|
||||
.toBuffer({ resolveWithObject: true })
|
||||
.then(function (output) {
|
||||
assert.strictEqual(2, output.info.width);
|
||||
assert.strictEqual(1, output.info.height);
|
||||
});
|
||||
});
|
||||
|
||||
it('Ensure shortest edge (width) is at least 1 pixel', function () {
|
||||
return sharp({
|
||||
create: {
|
||||
width: 2,
|
||||
height: 10,
|
||||
channels: 3,
|
||||
background: 'red'
|
||||
}
|
||||
})
|
||||
.resize(null, 2)
|
||||
.toBuffer({ resolveWithObject: true })
|
||||
.then(function (output) {
|
||||
assert.strictEqual(1, output.info.width);
|
||||
assert.strictEqual(2, output.info.height);
|
||||
});
|
||||
});
|
||||
|
||||
it('unknown kernel throws', function () {
|
||||
assert.throws(function () {
|
||||
sharp().resize(null, null, { kernel: 'unknown' });
|
||||
|
||||
@@ -25,7 +25,7 @@ describe('Rotation', function () {
|
||||
|
||||
it('Rotate by 30 degrees with semi-transparent background', function (done) {
|
||||
sharp(fixtures.inputJpg)
|
||||
.rotate(30, {background: { r: 255, g: 0, b: 0, alpha: 0.5 }})
|
||||
.rotate(30, { background: { r: 255, g: 0, b: 0, alpha: 0.5 } })
|
||||
.resize(320)
|
||||
.png()
|
||||
.toBuffer(function (err, data, info) {
|
||||
@@ -39,7 +39,7 @@ describe('Rotation', function () {
|
||||
|
||||
it('Rotate by 30 degrees with solid background', function (done) {
|
||||
sharp(fixtures.inputJpg)
|
||||
.rotate(30, {background: { r: 255, g: 0, b: 0, alpha: 0.5 }})
|
||||
.rotate(30, { background: { r: 255, g: 0, b: 0, alpha: 0.5 } })
|
||||
.resize(320)
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
@@ -208,7 +208,7 @@ describe('Rotation', function () {
|
||||
sharp(fixtures.inputJpgWithExif)
|
||||
.rotate()
|
||||
.resize(320)
|
||||
.withMetadata({orientation: 3})
|
||||
.withMetadata({ orientation: 3 })
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
|
||||
77
test/unit/svg.js
Normal file
@@ -0,0 +1,77 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('assert');
|
||||
|
||||
const sharp = require('../../');
|
||||
const fixtures = require('../fixtures');
|
||||
|
||||
describe('SVG input', function () {
|
||||
it('Convert SVG to PNG at default 72DPI', function (done) {
|
||||
sharp(fixtures.inputSvg)
|
||||
.resize(1024)
|
||||
.extract({ left: 290, top: 760, width: 40, height: 40 })
|
||||
.toFormat('png')
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('png', info.format);
|
||||
assert.strictEqual(40, info.width);
|
||||
assert.strictEqual(40, info.height);
|
||||
fixtures.assertSimilar(fixtures.expected('svg72.png'), data, function (err) {
|
||||
if (err) throw err;
|
||||
sharp(data).metadata(function (err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(72, info.density);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Convert SVG to PNG at 1200DPI', function (done) {
|
||||
sharp(fixtures.inputSvg, { density: 1200 })
|
||||
.resize(1024)
|
||||
.extract({ left: 290, top: 760, width: 40, height: 40 })
|
||||
.toFormat('png')
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('png', info.format);
|
||||
assert.strictEqual(40, info.width);
|
||||
assert.strictEqual(40, info.height);
|
||||
fixtures.assertSimilar(fixtures.expected('svg1200.png'), data, function (err) {
|
||||
if (err) throw err;
|
||||
sharp(data).metadata(function (err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(1200, info.density);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Convert SVG to PNG at 14.4DPI', function (done) {
|
||||
sharp(fixtures.inputSvg, { density: 14.4 })
|
||||
.toFormat('png')
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('png', info.format);
|
||||
assert.strictEqual(20, info.width);
|
||||
assert.strictEqual(20, info.height);
|
||||
fixtures.assertSimilar(fixtures.expected('svg14.4.png'), data, function (err) {
|
||||
if (err) throw err;
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Convert SVG with embedded images to PNG, respecting dimensions, autoconvert to PNG', function (done) {
|
||||
sharp(fixtures.inputSvgWithEmbeddedImages)
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('png', info.format);
|
||||
assert.strictEqual(480, info.width);
|
||||
assert.strictEqual(360, info.height);
|
||||
assert.strictEqual(4, info.channels);
|
||||
fixtures.assertSimilar(fixtures.expected('svg-embedded.png'), data, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -132,7 +132,7 @@ describe('Threshold', function () {
|
||||
it('color threshold', function (done) {
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240)
|
||||
.threshold(128, {'grayscale': false})
|
||||
.threshold(128, { grayscale: false })
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
|
||||
424
test/unit/tiff.js
Normal file
@@ -0,0 +1,424 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const assert = require('assert');
|
||||
const rimraf = require('rimraf');
|
||||
|
||||
const sharp = require('../../');
|
||||
const fixtures = require('../fixtures');
|
||||
|
||||
describe('TIFF', function () {
|
||||
it('Load TIFF from Buffer', function (done) {
|
||||
const inputTiffBuffer = fs.readFileSync(fixtures.inputTiff);
|
||||
sharp(inputTiffBuffer)
|
||||
.resize(320, 240)
|
||||
.jpeg()
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual(data.length, info.size);
|
||||
assert.strictEqual('jpeg', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Load multi-page TIFF from file', function (done) {
|
||||
sharp(fixtures.inputTiffMultipage) // defaults to page 0
|
||||
.jpeg()
|
||||
.toBuffer(function (err, defaultData, defaultInfo) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, defaultData.length > 0);
|
||||
assert.strictEqual(defaultData.length, defaultInfo.size);
|
||||
assert.strictEqual('jpeg', defaultInfo.format);
|
||||
|
||||
sharp(fixtures.inputTiffMultipage, { page: 1 }) // 50%-scale copy of page 0
|
||||
.jpeg()
|
||||
.toBuffer(function (err, scaledData, scaledInfo) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, scaledData.length > 0);
|
||||
assert.strictEqual(scaledData.length, scaledInfo.size);
|
||||
assert.strictEqual('jpeg', scaledInfo.format);
|
||||
assert.strictEqual(defaultInfo.width, scaledInfo.width * 2);
|
||||
assert.strictEqual(defaultInfo.height, scaledInfo.height * 2);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Load multi-page TIFF from Buffer', function (done) {
|
||||
const inputTiffBuffer = fs.readFileSync(fixtures.inputTiffMultipage);
|
||||
sharp(inputTiffBuffer) // defaults to page 0
|
||||
.jpeg()
|
||||
.toBuffer(function (err, defaultData, defaultInfo) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, defaultData.length > 0);
|
||||
assert.strictEqual(defaultData.length, defaultInfo.size);
|
||||
assert.strictEqual('jpeg', defaultInfo.format);
|
||||
|
||||
sharp(inputTiffBuffer, { page: 1 }) // 50%-scale copy of page 0
|
||||
.jpeg()
|
||||
.toBuffer(function (err, scaledData, scaledInfo) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, scaledData.length > 0);
|
||||
assert.strictEqual(scaledData.length, scaledInfo.size);
|
||||
assert.strictEqual('jpeg', scaledInfo.format);
|
||||
assert.strictEqual(defaultInfo.width, scaledInfo.width * 2);
|
||||
assert.strictEqual(defaultInfo.height, scaledInfo.height * 2);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Save TIFF to Buffer', function (done) {
|
||||
sharp(fixtures.inputTiff)
|
||||
.resize(320, 240)
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual(data.length, info.size);
|
||||
assert.strictEqual('tiff', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Invalid TIFF quality throws error', function () {
|
||||
assert.throws(function () {
|
||||
sharp().tiff({ quality: 101 });
|
||||
});
|
||||
});
|
||||
|
||||
it('Missing TIFF quality does not throw error', function () {
|
||||
assert.doesNotThrow(function () {
|
||||
sharp().tiff();
|
||||
});
|
||||
});
|
||||
|
||||
it('Not squashing TIFF to a bit depth of 1 should not change the file size', function (done) {
|
||||
const startSize = fs.statSync(fixtures.inputTiff8BitDepth).size;
|
||||
sharp(fixtures.inputTiff8BitDepth)
|
||||
.toColourspace('b-w') // can only squash 1 band uchar images
|
||||
.tiff({
|
||||
squash: false,
|
||||
compression: 'none',
|
||||
predictor: 'none'
|
||||
})
|
||||
.toFile(fixtures.outputTiff, (err, info) => {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('tiff', info.format);
|
||||
assert(info.size === startSize);
|
||||
rimraf(fixtures.outputTiff, done);
|
||||
});
|
||||
});
|
||||
|
||||
it('Squashing TIFF to a bit depth of 1 should significantly reduce file size', function (done) {
|
||||
const startSize = fs.statSync(fixtures.inputTiff8BitDepth).size;
|
||||
sharp(fixtures.inputTiff8BitDepth)
|
||||
.toColourspace('b-w') // can only squash 1 band uchar images
|
||||
.tiff({
|
||||
squash: true,
|
||||
compression: 'none',
|
||||
predictor: 'none'
|
||||
})
|
||||
.toFile(fixtures.outputTiff, (err, info) => {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('tiff', info.format);
|
||||
assert(info.size < (startSize / 2));
|
||||
rimraf(fixtures.outputTiff, done);
|
||||
});
|
||||
});
|
||||
|
||||
it('Invalid TIFF squash value throws error', function () {
|
||||
assert.throws(function () {
|
||||
sharp().tiff({ squash: 'true' });
|
||||
});
|
||||
});
|
||||
|
||||
it('TIFF setting xres and yres on file', function (done) {
|
||||
const res = 1000.0; // inputTiff has a dpi of 300 (res*2.54)
|
||||
sharp(fixtures.inputTiff)
|
||||
.tiff({
|
||||
xres: (res),
|
||||
yres: (res)
|
||||
})
|
||||
.toFile(fixtures.outputTiff, (err, info) => {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('tiff', info.format);
|
||||
sharp(fixtures.outputTiff).metadata(function (err, metadata) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(metadata.density, res * 2.54); // convert to dpi
|
||||
rimraf(fixtures.outputTiff, done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('TIFF setting xres and yres on buffer', function (done) {
|
||||
const res = 1000.0; // inputTiff has a dpi of 300 (res*2.54)
|
||||
sharp(fixtures.inputTiff)
|
||||
.tiff({
|
||||
xres: (res),
|
||||
yres: (res)
|
||||
})
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
sharp(data).metadata(function (err, metadata) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(metadata.density, res * 2.54); // convert to dpi
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('TIFF invalid xres value should throw an error', function () {
|
||||
assert.throws(function () {
|
||||
sharp().tiff({ xres: '1000.0' });
|
||||
});
|
||||
});
|
||||
|
||||
it('TIFF invalid yres value should throw an error', function () {
|
||||
assert.throws(function () {
|
||||
sharp().tiff({ yres: '1000.0' });
|
||||
});
|
||||
});
|
||||
|
||||
it('TIFF lzw compression with horizontal predictor shrinks test file', function (done) {
|
||||
const startSize = fs.statSync(fixtures.inputTiffUncompressed).size;
|
||||
sharp(fixtures.inputTiffUncompressed)
|
||||
.tiff({
|
||||
compression: 'lzw',
|
||||
predictor: 'horizontal'
|
||||
})
|
||||
.toFile(fixtures.outputTiff, (err, info) => {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('tiff', info.format);
|
||||
assert(info.size < startSize);
|
||||
rimraf(fixtures.outputTiff, done);
|
||||
});
|
||||
});
|
||||
|
||||
it('TIFF ccittfax4 compression shrinks b-w test file', function (done) {
|
||||
const startSize = fs.statSync(fixtures.inputTiff).size;
|
||||
sharp(fixtures.inputTiff)
|
||||
.toColourspace('b-w')
|
||||
.tiff({
|
||||
squash: true,
|
||||
compression: 'ccittfax4'
|
||||
})
|
||||
.toFile(fixtures.outputTiff, (err, info) => {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('tiff', info.format);
|
||||
assert(info.size < startSize);
|
||||
rimraf(fixtures.outputTiff, done);
|
||||
});
|
||||
});
|
||||
|
||||
it('TIFF deflate compression with horizontal predictor shrinks test file', function (done) {
|
||||
const startSize = fs.statSync(fixtures.inputTiffUncompressed).size;
|
||||
sharp(fixtures.inputTiffUncompressed)
|
||||
.tiff({
|
||||
compression: 'deflate',
|
||||
predictor: 'horizontal'
|
||||
})
|
||||
.toFile(fixtures.outputTiff, (err, info) => {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('tiff', info.format);
|
||||
assert(info.size < startSize);
|
||||
rimraf(fixtures.outputTiff, done);
|
||||
});
|
||||
});
|
||||
|
||||
it('TIFF deflate compression with float predictor shrinks test file', function (done) {
|
||||
const startSize = fs.statSync(fixtures.inputTiffUncompressed).size;
|
||||
sharp(fixtures.inputTiffUncompressed)
|
||||
.tiff({
|
||||
compression: 'deflate',
|
||||
predictor: 'float'
|
||||
})
|
||||
.toFile(fixtures.outputTiff, (err, info) => {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('tiff', info.format);
|
||||
assert(info.size < startSize);
|
||||
rimraf(fixtures.outputTiff, done);
|
||||
});
|
||||
});
|
||||
|
||||
it('TIFF deflate compression without predictor shrinks test file', function (done) {
|
||||
const startSize = fs.statSync(fixtures.inputTiffUncompressed).size;
|
||||
sharp(fixtures.inputTiffUncompressed)
|
||||
.tiff({
|
||||
compression: 'deflate',
|
||||
predictor: 'none'
|
||||
})
|
||||
.toFile(fixtures.outputTiff, (err, info) => {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('tiff', info.format);
|
||||
assert(info.size < startSize);
|
||||
rimraf(fixtures.outputTiff, done);
|
||||
});
|
||||
});
|
||||
|
||||
it('TIFF jpeg compression shrinks test file', function (done) {
|
||||
const startSize = fs.statSync(fixtures.inputTiffUncompressed).size;
|
||||
sharp(fixtures.inputTiffUncompressed)
|
||||
.tiff({
|
||||
compression: 'jpeg'
|
||||
})
|
||||
.toFile(fixtures.outputTiff, (err, info) => {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('tiff', info.format);
|
||||
assert(info.size < startSize);
|
||||
rimraf(fixtures.outputTiff, done);
|
||||
});
|
||||
});
|
||||
|
||||
it('TIFF none compression does not throw error', function () {
|
||||
assert.doesNotThrow(function () {
|
||||
sharp().tiff({ compression: 'none' });
|
||||
});
|
||||
});
|
||||
|
||||
it('TIFF lzw compression does not throw error', function () {
|
||||
assert.doesNotThrow(function () {
|
||||
sharp().tiff({ compression: 'lzw' });
|
||||
});
|
||||
});
|
||||
|
||||
it('TIFF deflate compression does not throw error', function () {
|
||||
assert.doesNotThrow(function () {
|
||||
sharp().tiff({ compression: 'deflate' });
|
||||
});
|
||||
});
|
||||
|
||||
it('TIFF invalid compression option throws', function () {
|
||||
assert.throws(function () {
|
||||
sharp().tiff({ compression: 0 });
|
||||
});
|
||||
});
|
||||
|
||||
it('TIFF invalid compression option throws', function () {
|
||||
assert.throws(function () {
|
||||
sharp().tiff({ compression: 'a' });
|
||||
});
|
||||
});
|
||||
|
||||
it('TIFF invalid predictor option throws', function () {
|
||||
assert.throws(function () {
|
||||
sharp().tiff({ predictor: 'a' });
|
||||
});
|
||||
});
|
||||
|
||||
it('TIFF horizontal predictor does not throw error', function () {
|
||||
assert.doesNotThrow(function () {
|
||||
sharp().tiff({ predictor: 'horizontal' });
|
||||
});
|
||||
});
|
||||
|
||||
it('TIFF float predictor does not throw error', function () {
|
||||
assert.doesNotThrow(function () {
|
||||
sharp().tiff({ predictor: 'float' });
|
||||
});
|
||||
});
|
||||
|
||||
it('TIFF none predictor does not throw error', function () {
|
||||
assert.doesNotThrow(function () {
|
||||
sharp().tiff({ predictor: 'none' });
|
||||
});
|
||||
});
|
||||
|
||||
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('TIFF file input with invalid page fails gracefully', function (done) {
|
||||
sharp(fixtures.inputTiffMultipage, { page: 2 })
|
||||
.toBuffer(function (err) {
|
||||
assert.strictEqual(true, !!err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('TIFF buffer input with invalid page fails gracefully', function (done) {
|
||||
sharp(fs.readFileSync(fixtures.inputTiffMultipage), { page: 2 })
|
||||
.toBuffer(function (err) {
|
||||
assert.strictEqual(true, !!err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
78
test/unit/webp.js
Normal file
@@ -0,0 +1,78 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('assert');
|
||||
|
||||
const sharp = require('../../');
|
||||
const fixtures = require('../fixtures');
|
||||
|
||||
describe('WebP', function () {
|
||||
it('WebP output', function (done) {
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240)
|
||||
.toFormat(sharp.format.webp)
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('webp', info.format);
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Invalid WebP quality throws error', function () {
|
||||
assert.throws(function () {
|
||||
sharp().webp({ quality: 101 });
|
||||
});
|
||||
});
|
||||
|
||||
it('Invalid WebP alpha quality throws error', function () {
|
||||
assert.throws(function () {
|
||||
sharp().webp({ alphaQuality: 101 });
|
||||
});
|
||||
});
|
||||
|
||||
it('should work for webp alpha quality', function (done) {
|
||||
sharp(fixtures.inputPngAlphaPremultiplicationSmall)
|
||||
.webp({ alphaQuality: 80 })
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('webp', info.format);
|
||||
fixtures.assertSimilar(fixtures.expected('webp-alpha-80.webp'), data, done);
|
||||
});
|
||||
});
|
||||
|
||||
it('should work for webp lossless', function (done) {
|
||||
sharp(fixtures.inputPngAlphaPremultiplicationSmall)
|
||||
.webp({ lossless: true })
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('webp', info.format);
|
||||
fixtures.assertSimilar(fixtures.expected('webp-lossless.webp'), data, done);
|
||||
});
|
||||
});
|
||||
|
||||
it('should work for webp near-lossless', function (done) {
|
||||
sharp(fixtures.inputPngAlphaPremultiplicationSmall)
|
||||
.webp({ nearLossless: true, quality: 50 })
|
||||
.toBuffer(function (err50, data50, info50) {
|
||||
if (err50) throw err50;
|
||||
assert.strictEqual(true, data50.length > 0);
|
||||
assert.strictEqual('webp', info50.format);
|
||||
fixtures.assertSimilar(fixtures.expected('webp-near-lossless-50.webp'), data50, done);
|
||||
});
|
||||
});
|
||||
|
||||
it('should use near-lossless when both lossless and nearLossless are specified', function (done) {
|
||||
sharp(fixtures.inputPngAlphaPremultiplicationSmall)
|
||||
.webp({ nearLossless: true, quality: 50, lossless: true })
|
||||
.toBuffer(function (err50, data50, info50) {
|
||||
if (err50) throw err50;
|
||||
assert.strictEqual(true, data50.length > 0);
|
||||
assert.strictEqual('webp', info50.format);
|
||||
fixtures.assertSimilar(fixtures.expected('webp-near-lossless-50.webp'), data50, done);
|
||||
});
|
||||
});
|
||||
});
|
||||