Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
18afcf5f90 | ||
|
|
87a422942d | ||
|
|
ac515121e5 | ||
|
|
2bfea0ad76 | ||
|
|
83cdb558f6 | ||
|
|
9ee377963e | ||
|
|
9cc06c887b | ||
|
|
7457b50373 | ||
|
|
6387fb79b1 | ||
|
|
54e5514b9a | ||
|
|
1e4597c284 | ||
|
|
7cafd4386c | ||
|
|
e3549ba28c | ||
|
|
d1bbe62e52 | ||
|
|
36af74a09b | ||
|
|
5afe02be60 | ||
|
|
2262959673 | ||
|
|
ba3f914445 | ||
|
|
770be35c44 | ||
|
|
cc9f2b90fd | ||
|
|
4aff57b071 | ||
|
|
1df8d82fe0 | ||
|
|
98e90784f4 | ||
|
|
87ea54cc66 | ||
|
|
d5e98bc8ad | ||
|
|
fa69ff773a | ||
|
|
a183bb1cac | ||
|
|
cf62372cab |
@@ -41,8 +41,8 @@ Any change that modifies the existing public API should be added to the relevant
|
|||||||
|
|
||||||
| Release | WIP branch |
|
| Release | WIP branch |
|
||||||
| ------: | :--------- |
|
| ------: | :--------- |
|
||||||
| v0.21.0 | teeth |
|
|
||||||
| v0.22.0 | uptake |
|
| 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>`.
|
Please squash your changes into a single commit using a command like `git rebase -i upstream/<wip-branch>`.
|
||||||
|
|
||||||
|
|||||||
39
README.md
@@ -1,15 +1,11 @@
|
|||||||
# sharp
|
# sharp
|
||||||
|
|
||||||
<img src="docs/image/sharp-logo.svg" width="160" height="160" alt="sharp logo" align="right">
|
<img src="https://raw.githubusercontent.com/lovell/sharp/master/docs/image/sharp-logo.svg?sanitize=true" width="160" height="160" alt="sharp logo" align="right">
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
npm install sharp
|
npm install sharp
|
||||||
```
|
```
|
||||||
|
|
||||||
```sh
|
|
||||||
yarn add sharp
|
|
||||||
```
|
|
||||||
|
|
||||||
The typical use case for this high speed Node.js module
|
The typical use case for this high speed Node.js module
|
||||||
is to convert large images in common formats to
|
is to convert large images in common formats to
|
||||||
smaller, web-friendly JPEG, PNG and WebP images of varying dimensions.
|
smaller, web-friendly JPEG, PNG and WebP images of varying dimensions.
|
||||||
@@ -33,22 +29,42 @@ do not require any additional install or runtime dependencies.
|
|||||||
const sharp = require('sharp');
|
const sharp = require('sharp');
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Callback
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
sharp(inputBuffer)
|
sharp(inputBuffer)
|
||||||
.resize(320, 240)
|
.resize(320, 240)
|
||||||
.toFile('output.webp', (err, info) => ... );
|
.toFile('output.webp', (err, info) => { ... });
|
||||||
// A Promises/A+ promise is returned when callback is not provided.
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Promise
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
sharp('input.jpg')
|
sharp('input.jpg')
|
||||||
.rotate()
|
.rotate()
|
||||||
.resize(200)
|
.resize(200)
|
||||||
.toBuffer()
|
.toBuffer()
|
||||||
.then( data => ... )
|
.then( data => { ... })
|
||||||
.catch( err => ... );
|
.catch( err => { ... });
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Async/await
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const semiTransparentRedPng = await sharp({
|
||||||
|
create: {
|
||||||
|
width: 48,
|
||||||
|
height: 48,
|
||||||
|
channels: 4,
|
||||||
|
background: { r: 255, g: 0, b: 0, alpha: 0.5 }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.png()
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Stream
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const roundedCorners = Buffer.from(
|
const roundedCorners = Buffer.from(
|
||||||
'<svg><rect x="0" y="0" width="200" height="200" rx="50" ry="50"/></svg>'
|
'<svg><rect x="0" y="0" width="200" height="200" rx="50" ry="50"/></svg>'
|
||||||
@@ -57,7 +73,10 @@ const roundedCorners = Buffer.from(
|
|||||||
const roundedCornerResizer =
|
const roundedCornerResizer =
|
||||||
sharp()
|
sharp()
|
||||||
.resize(200, 200)
|
.resize(200, 200)
|
||||||
.overlayWith(roundedCorners, { cutout: true })
|
.composite([{
|
||||||
|
input: roundedCorners,
|
||||||
|
blend: 'dest-in'
|
||||||
|
}])
|
||||||
.png();
|
.png();
|
||||||
|
|
||||||
readableStream
|
readableStream
|
||||||
|
|||||||
@@ -140,7 +140,6 @@
|
|||||||
'../vendor/lib/libgsf-1.so',
|
'../vendor/lib/libgsf-1.so',
|
||||||
'../vendor/lib/libgthread-2.0.so',
|
'../vendor/lib/libgthread-2.0.so',
|
||||||
'../vendor/lib/libharfbuzz.so',
|
'../vendor/lib/libharfbuzz.so',
|
||||||
'../vendor/lib/libharfbuzz-subset.so.0',
|
|
||||||
'../vendor/lib/libjpeg.so',
|
'../vendor/lib/libjpeg.so',
|
||||||
'../vendor/lib/liblcms2.so',
|
'../vendor/lib/liblcms2.so',
|
||||||
'../vendor/lib/liborc-0.4.so',
|
'../vendor/lib/liborc-0.4.so',
|
||||||
|
|||||||
@@ -1,33 +1,41 @@
|
|||||||
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
|
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
|
||||||
|
|
||||||
## overlayWith
|
## composite
|
||||||
|
|
||||||
Overlay (composite) an image over the processed (resized, extracted etc.) image.
|
Composite image(s) over the processed (resized, extracted etc.) image.
|
||||||
|
|
||||||
The overlay image must be the same size or smaller than the processed image.
|
The images to composite must be the same size or smaller than the processed image.
|
||||||
If both `top` and `left` options are provided, they take precedence over `gravity`.
|
If both `top` and `left` options are provided, they take precedence over `gravity`.
|
||||||
|
|
||||||
If the overlay image contains an alpha channel then composition with premultiplication will occur.
|
The `blend` option can be one of `clear`, `source`, `over`, `in`, `out`, `atop`,
|
||||||
|
`dest`, `dest-over`, `dest-in`, `dest-out`, `dest-atop`,
|
||||||
|
`xor`, `add`, `saturate`, `multiply`, `screen`, `overlay`, `darken`, `lighten`,
|
||||||
|
`colour-dodge`, `color-dodge`, `colour-burn`,`color-burn`,
|
||||||
|
`hard-light`, `soft-light`, `difference`, `exclusion`.
|
||||||
|
|
||||||
|
More information about blend modes can be found at
|
||||||
|
[https://libvips.github.io/libvips/API/current/libvips-conversion.html#VipsBlendMode][1]
|
||||||
|
and [https://www.cairographics.org/operators/][2]
|
||||||
|
|
||||||
### Parameters
|
### Parameters
|
||||||
|
|
||||||
- `overlay` **([Buffer][1] \| [String][2])** Buffer containing image data or String containing the path to an image file.
|
- `images` **[Array][3]<[Object][4]>** Ordered list of images to composite
|
||||||
- `options` **[Object][3]?**
|
- `images[].input` **([Buffer][5] \| [String][6])?** Buffer containing image data or String containing the path to an image file.
|
||||||
- `options.gravity` **[String][2]** gravity at which to place the overlay. (optional, default `'centre'`)
|
- `images[].blend` **[String][6]** how to blend this image with the image below. (optional, default `'over'`)
|
||||||
- `options.top` **[Number][4]?** the pixel offset from the top edge.
|
- `images[].gravity` **[String][6]** gravity at which to place the overlay. (optional, default `'centre'`)
|
||||||
- `options.left` **[Number][4]?** the pixel offset from the left edge.
|
- `images[].top` **[Number][7]?** the pixel offset from the top edge.
|
||||||
- `options.tile` **[Boolean][5]** set to true to repeat the overlay image across the entire image with the given `gravity`. (optional, default `false`)
|
- `images[].left` **[Number][7]?** the pixel offset from the left edge.
|
||||||
- `options.cutout` **[Boolean][5]** set to true to apply only the alpha channel of the overlay image to the input image, giving the appearance of one image being cut out of another. (optional, default `false`)
|
- `images[].tile` **[Boolean][8]** set to true to repeat the overlay image across the entire image with the given `gravity`. (optional, default `false`)
|
||||||
- `options.density` **[Number][4]** number representing the DPI for vector overlay image. (optional, default `72`)
|
- `images[].density` **[Number][7]** number representing the DPI for vector overlay image. (optional, default `72`)
|
||||||
- `options.raw` **[Object][3]?** describes overlay when using raw pixel data.
|
- `images[].raw` **[Object][4]?** describes overlay when using raw pixel data.
|
||||||
- `options.raw.width` **[Number][4]?**
|
- `images[].raw.width` **[Number][7]?**
|
||||||
- `options.raw.height` **[Number][4]?**
|
- `images[].raw.height` **[Number][7]?**
|
||||||
- `options.raw.channels` **[Number][4]?**
|
- `images[].raw.channels` **[Number][7]?**
|
||||||
- `options.create` **[Object][3]?** describes a blank overlay to be created.
|
- `images[].create` **[Object][4]?** describes a blank overlay to be created.
|
||||||
- `options.create.width` **[Number][4]?**
|
- `images[].create.width` **[Number][7]?**
|
||||||
- `options.create.height` **[Number][4]?**
|
- `images[].create.height` **[Number][7]?**
|
||||||
- `options.create.channels` **[Number][4]?** 3-4
|
- `images[].create.channels` **[Number][7]?** 3-4
|
||||||
- `options.create.background` **([String][2] \| [Object][3])?** parsed by the [color][6] module to extract values for red, green, blue and alpha.
|
- `images[].create.background` **([String][6] \| [Object][4])?** parsed by the [color][9] module to extract values for red, green, blue and alpha.
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
@@ -36,7 +44,7 @@ sharp('input.png')
|
|||||||
.rotate(180)
|
.rotate(180)
|
||||||
.resize(300)
|
.resize(300)
|
||||||
.flatten( { background: '#ff6600' } )
|
.flatten( { background: '#ff6600' } )
|
||||||
.overlayWith('overlay.png', { gravity: sharp.gravity.southeast } )
|
.composite([{ input: 'overlay.png', gravity: 'southeast' }])
|
||||||
.sharpen()
|
.sharpen()
|
||||||
.withMetadata()
|
.withMetadata()
|
||||||
.webp( { quality: 90 } )
|
.webp( { quality: 90 } )
|
||||||
@@ -48,20 +56,26 @@ sharp('input.png')
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
- Throws **[Error][7]** Invalid parameters
|
- Throws **[Error][10]** Invalid parameters
|
||||||
|
|
||||||
Returns **Sharp**
|
Returns **Sharp**
|
||||||
|
|
||||||
[1]: https://nodejs.org/api/buffer.html
|
[1]: https://libvips.github.io/libvips/API/current/libvips-conversion.html#VipsBlendMode
|
||||||
|
|
||||||
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
|
[2]: https://www.cairographics.org/operators/
|
||||||
|
|
||||||
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
|
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
|
||||||
|
|
||||||
[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
|
[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
|
||||||
|
|
||||||
[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
|
[5]: https://nodejs.org/api/buffer.html
|
||||||
|
|
||||||
[6]: https://www.npmjs.org/package/color
|
[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
|
||||||
|
|
||||||
[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error
|
[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
|
||||||
|
|
||||||
|
[8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
|
||||||
|
|
||||||
|
[9]: https://www.npmjs.org/package/color
|
||||||
|
|
||||||
|
[10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error
|
||||||
|
|||||||
@@ -9,11 +9,11 @@
|
|||||||
a String containing the path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file.
|
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.
|
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` **[Object][3]?** if present, is an Object with optional attributes.
|
||||||
- `options.failOnError` **[Boolean][4]** by default apply a "best effort"
|
- `options.failOnError` **[Boolean][4]** by default halt processing and raise an error when loading invalid images.
|
||||||
to decode images, even if the data is corrupt or invalid. Set this flag to true
|
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`)
|
||||||
if you'd rather halt processing and raise an error when loading invalid images. (optional, default `false`)
|
|
||||||
- `options.density` **[Number][5]** number representing the DPI for vector images. (optional, default `72`)
|
- `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.pages` **[Number][5]** number of pages to extract for multi-page input (GIF, TIFF, PDF), use -1 for all pages. (optional, default `1`)
|
||||||
|
- `options.page` **[Number][5]** page number to start extracting from for multi-page input (GIF, TIFF, PDF), zero based. (optional, default `0`)
|
||||||
- `options.raw` **[Object][3]?** describes raw pixel input image data. See `raw()` for pixel ordering.
|
- `options.raw` **[Object][3]?** describes raw pixel input image data. See `raw()` for pixel ordering.
|
||||||
- `options.raw.width` **[Number][5]?**
|
- `options.raw.width` **[Number][5]?**
|
||||||
- `options.raw.height` **[Number][5]?**
|
- `options.raw.height` **[Number][5]?**
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ Returns **Sharp**
|
|||||||
## metadata
|
## metadata
|
||||||
|
|
||||||
Fast access to (uncached) image metadata without decoding any compressed image data.
|
Fast access to (uncached) image metadata without decoding any compressed image data.
|
||||||
A Promises/A+ promise is returned when `callback` is not provided.
|
A `Promise` is returned when `callback` is not provided.
|
||||||
|
|
||||||
- `format`: Name of decoder used to decompress image data e.g. `jpeg`, `png`, `webp`, `gif`, `svg`
|
- `format`: Name of decoder used to decompress image data e.g. `jpeg`, `png`, `webp`, `gif`, `svg`
|
||||||
- `size`: Total size of image in bytes, for Stream and Buffer input only
|
- `size`: Total size of image in bytes, for Stream and Buffer input only
|
||||||
@@ -34,7 +34,7 @@ A Promises/A+ promise is returned when `callback` is not provided.
|
|||||||
- `density`: Number of pixels per inch (DPI), if present
|
- `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
|
- `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
|
- `isProgressive`: Boolean indicating whether the image is interlaced using a progressive scan
|
||||||
- `pages`: Number of pages this TIFF, GIF or PDF image contains.
|
- `pages`: Number of pages/frames contained within the image, with support for TIFF, PDF, animated GIF and animated WebP
|
||||||
- `pageHeight`: Number of pixels high each page in this PDF image will be.
|
- `pageHeight`: Number of pixels high each page in this PDF image will be.
|
||||||
- `hasProfile`: Boolean indicating the presence of an embedded ICC profile
|
- `hasProfile`: Boolean indicating the presence of an embedded ICC profile
|
||||||
- `hasAlpha`: Boolean indicating the presence of an alpha transparency channel
|
- `hasAlpha`: Boolean indicating the presence of an alpha transparency channel
|
||||||
@@ -70,7 +70,7 @@ Returns **([Promise][5]<[Object][6]> | Sharp)**
|
|||||||
## stats
|
## stats
|
||||||
|
|
||||||
Access to pixel-derived image statistics for every channel in the image.
|
Access to pixel-derived image statistics for every channel in the image.
|
||||||
A Promise is returned when `callback` is not provided.
|
A `Promise` is returned when `callback` is not provided.
|
||||||
|
|
||||||
- `channels`: Array of channel statistics for each channel in the image. Each channel statistic contains
|
- `channels`: Array of channel statistics for each channel in the image. Each channel statistic contains
|
||||||
- `min` (minimum value in the channel)
|
- `min` (minimum value in the channel)
|
||||||
@@ -105,9 +105,9 @@ Returns **[Promise][5]<[Object][6]>**
|
|||||||
|
|
||||||
## limitInputPixels
|
## limitInputPixels
|
||||||
|
|
||||||
Do not process input images where the number of pixels (width _ height) exceeds this limit.
|
Do not process input images where the number of pixels (width x height) exceeds this limit.
|
||||||
Assumes image dimensions contained in the input metadata can be trusted.
|
Assumes image dimensions contained in the input metadata can be trusted.
|
||||||
The default limit is 268402689 (0x3FFF _ 0x3FFF) pixels.
|
The default limit is 268402689 (0x3FFF x 0x3FFF) pixels.
|
||||||
|
|
||||||
### Parameters
|
### Parameters
|
||||||
|
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ sharp(input)
|
|||||||
top: 10,
|
top: 10,
|
||||||
bottom: 20,
|
bottom: 20,
|
||||||
left: 10,
|
left: 10,
|
||||||
right: 10
|
right: 10,
|
||||||
background: { r: 0, g: 0, b: 0, alpha: 0 }
|
background: { r: 0, g: 0, b: 0, alpha: 0 }
|
||||||
})
|
})
|
||||||
...
|
...
|
||||||
|
|||||||
@@ -1,9 +1,38 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
### v0.22 - "*uptake*"
|
||||||
|
|
||||||
|
Requires libvips v8.7.4.
|
||||||
|
|
||||||
|
#### v0.22.0 - 18<sup>th</sup> March 2019
|
||||||
|
|
||||||
|
* Remove functions previously deprecated in v0.21.0:
|
||||||
|
`background`, `crop`, `embed`, `ignoreAspectRatio`, `max`, `min` and `withoutEnlargement`.
|
||||||
|
|
||||||
|
* Add `composite` operation supporting multiple images and blend modes; deprecate `overlayWith`.
|
||||||
|
[#728](https://github.com/lovell/sharp/issues/728)
|
||||||
|
|
||||||
|
* Add support for `pages` input option for multi-page input.
|
||||||
|
[#1566](https://github.com/lovell/sharp/issues/1566)
|
||||||
|
|
||||||
|
* Allow Stream-based input of raw pixel data.
|
||||||
|
[#1579](https://github.com/lovell/sharp/issues/1579)
|
||||||
|
|
||||||
|
* Add support for `page` input option to GIF and PDF.
|
||||||
|
[#1595](https://github.com/lovell/sharp/pull/1595)
|
||||||
|
[@ramiel](https://github.com/ramiel)
|
||||||
|
|
||||||
### v0.21 - "*teeth*"
|
### v0.21 - "*teeth*"
|
||||||
|
|
||||||
Requires libvips v8.7.0.
|
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
|
#### v0.21.2 - 13<sup>th</sup> January 2019
|
||||||
|
|
||||||
* Ensure all metadata is removed from PNG output unless `withMetadata` used.
|
* Ensure all metadata is removed from PNG output unless `withMetadata` used.
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ Building from source requires:
|
|||||||
[](https://travis-ci.org/lovell/sharp)
|
[](https://travis-ci.org/lovell/sharp)
|
||||||
|
|
||||||
libvips and its dependencies are fetched and stored within `node_modules/sharp/vendor` during `npm install`.
|
libvips and its dependencies are fetched and stored within `node_modules/sharp/vendor` during `npm install`.
|
||||||
This involves an automated HTTPS download of approximately 8MB.
|
This involves an automated HTTPS download of approximately 9MB.
|
||||||
|
|
||||||
Most Linux-based (glibc, musl) operating systems running on x64 and ARMv6+ CPUs should "just work", e.g.:
|
Most Linux-based (glibc, musl) operating systems running on x64 and ARMv6+ CPUs should "just work", e.g.:
|
||||||
|
|
||||||
@@ -86,7 +86,7 @@ via `sharp.cache(false)` to avoid a stack overflow.
|
|||||||
[](https://travis-ci.org/lovell/sharp)
|
[](https://travis-ci.org/lovell/sharp)
|
||||||
|
|
||||||
libvips and its dependencies are fetched and stored within `node_modules/sharp/vendor` during `npm install`.
|
libvips and its dependencies are fetched and stored within `node_modules/sharp/vendor` during `npm install`.
|
||||||
This involves an automated HTTPS download of approximately 7MB.
|
This involves an automated HTTPS download of approximately 8MB.
|
||||||
|
|
||||||
To use your own version of libvips instead of the provided binaries, make sure it is
|
To use your own version of libvips instead of the provided binaries, make sure it is
|
||||||
at least the version listed under `config.libvips` in the `package.json` file and
|
at least the version listed under `config.libvips` in the `package.json` file and
|
||||||
@@ -97,8 +97,9 @@ that it can be located using `pkg-config --modversion vips-cpp`.
|
|||||||
[](https://ci.appveyor.com/project/lovell/sharp)
|
[](https://ci.appveyor.com/project/lovell/sharp)
|
||||||
|
|
||||||
libvips and its dependencies are fetched and stored within `node_modules\sharp\vendor` during `npm install`.
|
libvips and its dependencies are fetched and stored within `node_modules\sharp\vendor` during `npm install`.
|
||||||
This involves an automated HTTPS download of approximately 13MB. If you are having issues during
|
This involves an automated HTTPS download of approximately 14MB.
|
||||||
installation consider removing the directory ```C:\Users\[user]\AppData\Roaming\npm-cache\_libvips```.
|
If you are having issues during installation consider removing the directory
|
||||||
|
`C:\Users\[user]\AppData\Roaming\npm-cache\_libvips`.
|
||||||
|
|
||||||
Only 64-bit (x64) `node.exe` is supported.
|
Only 64-bit (x64) `node.exe` is supported.
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ const distBaseUrl = process.env.npm_config_sharp_dist_base_url || process.env.SH
|
|||||||
|
|
||||||
const fail = function (err) {
|
const fail = function (err) {
|
||||||
npmLog.error('sharp', err.message);
|
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);
|
process.exit(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -33,7 +34,12 @@ const extractTarball = function (tarPath) {
|
|||||||
cwd: vendorPath,
|
cwd: vendorPath,
|
||||||
strict: true
|
strict: true
|
||||||
})
|
})
|
||||||
.catch(fail);
|
.catch(function (err) {
|
||||||
|
if (/unexpected end of file/.test(err.message)) {
|
||||||
|
npmLog.error('sharp', `Please delete ${tarPath} as it is not a valid tarball`);
|
||||||
|
}
|
||||||
|
fail(err);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const deprecate = require('util').deprecate;
|
|
||||||
|
|
||||||
const color = require('color');
|
const color = require('color');
|
||||||
const is = require('./is');
|
const is = require('./is');
|
||||||
|
|
||||||
@@ -17,24 +15,6 @@ const colourspace = {
|
|||||||
srgb: 'srgb'
|
srgb: 'srgb'
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
function background (rgba) {
|
|
||||||
const colour = color(rgba);
|
|
||||||
const background = [
|
|
||||||
colour.red(),
|
|
||||||
colour.green(),
|
|
||||||
colour.blue(),
|
|
||||||
Math.round(colour.alpha() * 255)
|
|
||||||
];
|
|
||||||
this.options.resizeBackground = background;
|
|
||||||
this.options.extendBackground = background;
|
|
||||||
this.options.flattenBackground = background.slice(0, 3);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tint the image using the provided chroma while preserving the image luminance.
|
* Tint the image using the provided chroma while preserving the image luminance.
|
||||||
* An alpha channel may be present and will be unchanged by the operation.
|
* An alpha channel may be present and will be unchanged by the operation.
|
||||||
@@ -136,6 +116,4 @@ module.exports = function (Sharp) {
|
|||||||
// Class attributes
|
// Class attributes
|
||||||
Sharp.colourspace = colourspace;
|
Sharp.colourspace = colourspace;
|
||||||
Sharp.colorspace = colourspace;
|
Sharp.colorspace = colourspace;
|
||||||
// Deprecated
|
|
||||||
Sharp.prototype.background = deprecate(background, 'background(background) is deprecated, use resize({ background }), extend({ background }) or flatten({ background }) instead');
|
|
||||||
};
|
};
|
||||||
|
|||||||
195
lib/composite.js
@@ -1,21 +1,66 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const deprecate = require('util').deprecate;
|
||||||
|
|
||||||
const is = require('./is');
|
const is = require('./is');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Overlay (composite) an image over the processed (resized, extracted etc.) image.
|
* Blend modes.
|
||||||
|
* @member
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
const blend = {
|
||||||
|
clear: 'clear',
|
||||||
|
source: 'source',
|
||||||
|
over: 'over',
|
||||||
|
in: 'in',
|
||||||
|
out: 'out',
|
||||||
|
atop: 'atop',
|
||||||
|
dest: 'dest',
|
||||||
|
'dest-over': 'dest-over',
|
||||||
|
'dest-in': 'dest-in',
|
||||||
|
'dest-out': 'dest-out',
|
||||||
|
'dest-atop': 'dest-atop',
|
||||||
|
xor: 'xor',
|
||||||
|
add: 'add',
|
||||||
|
saturate: 'saturate',
|
||||||
|
multiply: 'multiply',
|
||||||
|
screen: 'screen',
|
||||||
|
overlay: 'overlay',
|
||||||
|
darken: 'darken',
|
||||||
|
lighten: 'lighten',
|
||||||
|
'colour-dodge': 'colour-dodge',
|
||||||
|
'color-dodge': 'colour-dodge',
|
||||||
|
'colour-burn': 'colour-burn',
|
||||||
|
'color-burn': 'colour-burn',
|
||||||
|
'hard-light': 'hard-light',
|
||||||
|
'soft-light': 'soft-light',
|
||||||
|
difference: 'difference',
|
||||||
|
exclusion: 'exclusion'
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Composite image(s) over the processed (resized, extracted etc.) image.
|
||||||
*
|
*
|
||||||
* The overlay image must be the same size or smaller than the processed image.
|
* The images to composite must be the same size or smaller than the processed image.
|
||||||
* If both `top` and `left` options are provided, they take precedence over `gravity`.
|
* If both `top` and `left` options are provided, they take precedence over `gravity`.
|
||||||
*
|
*
|
||||||
* If the overlay image contains an alpha channel then composition with premultiplication will occur.
|
* The `blend` option can be one of `clear`, `source`, `over`, `in`, `out`, `atop`,
|
||||||
|
* `dest`, `dest-over`, `dest-in`, `dest-out`, `dest-atop`,
|
||||||
|
* `xor`, `add`, `saturate`, `multiply`, `screen`, `overlay`, `darken`, `lighten`,
|
||||||
|
* `colour-dodge`, `color-dodge`, `colour-burn`,`color-burn`,
|
||||||
|
* `hard-light`, `soft-light`, `difference`, `exclusion`.
|
||||||
|
*
|
||||||
|
* More information about blend modes can be found at
|
||||||
|
* https://libvips.github.io/libvips/API/current/libvips-conversion.html#VipsBlendMode
|
||||||
|
* and https://www.cairographics.org/operators/
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* sharp('input.png')
|
* sharp('input.png')
|
||||||
* .rotate(180)
|
* .rotate(180)
|
||||||
* .resize(300)
|
* .resize(300)
|
||||||
* .flatten( { background: '#ff6600' } )
|
* .flatten( { background: '#ff6600' } )
|
||||||
* .overlayWith('overlay.png', { gravity: sharp.gravity.southeast } )
|
* .composite([{ input: 'overlay.png', gravity: 'southeast' }])
|
||||||
* .sharpen()
|
* .sharpen()
|
||||||
* .withMetadata()
|
* .withMetadata()
|
||||||
* .webp( { quality: 90 } )
|
* .webp( { quality: 90 } )
|
||||||
@@ -26,70 +71,104 @@ const is = require('./is');
|
|||||||
* // sharpened, with metadata, 90% quality WebP image data. Phew!
|
* // 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 {Object[]} images - Ordered list of images to composite
|
||||||
* @param {Object} [options]
|
* @param {Buffer|String} [images[].input] - Buffer containing image data or String containing the path to an image file.
|
||||||
* @param {String} [options.gravity='centre'] - gravity at which to place the overlay.
|
* @param {String} [images[].blend='over'] - how to blend this image with the image below.
|
||||||
* @param {Number} [options.top] - the pixel offset from the top edge.
|
* @param {String} [images[].gravity='centre'] - gravity at which to place the overlay.
|
||||||
* @param {Number} [options.left] - the pixel offset from the left edge.
|
* @param {Number} [images[].top] - the pixel offset from the top edge.
|
||||||
* @param {Boolean} [options.tile=false] - set to true to repeat the overlay image across the entire image with the given `gravity`.
|
* @param {Number} [images[].left] - the pixel offset from the left edge.
|
||||||
* @param {Boolean} [options.cutout=false] - set to true to apply only the alpha channel of the overlay image to the input image, giving the appearance of one image being cut out of another.
|
* @param {Boolean} [images[].tile=false] - set to true to repeat the overlay image across the entire image with the given `gravity`.
|
||||||
* @param {Number} [options.density=72] - number representing the DPI for vector overlay image.
|
* @param {Number} [images[].density=72] - number representing the DPI for vector overlay image.
|
||||||
* @param {Object} [options.raw] - describes overlay when using raw pixel data.
|
* @param {Object} [images[].raw] - describes overlay when using raw pixel data.
|
||||||
* @param {Number} [options.raw.width]
|
* @param {Number} [images[].raw.width]
|
||||||
* @param {Number} [options.raw.height]
|
* @param {Number} [images[].raw.height]
|
||||||
* @param {Number} [options.raw.channels]
|
* @param {Number} [images[].raw.channels]
|
||||||
* @param {Object} [options.create] - describes a blank overlay to be created.
|
* @param {Object} [images[].create] - describes a blank overlay to be created.
|
||||||
* @param {Number} [options.create.width]
|
* @param {Number} [images[].create.width]
|
||||||
* @param {Number} [options.create.height]
|
* @param {Number} [images[].create.height]
|
||||||
* @param {Number} [options.create.channels] - 3-4
|
* @param {Number} [images[].create.channels] - 3-4
|
||||||
* @param {String|Object} [options.create.background] - parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
|
* @param {String|Object} [images[].create.background] - parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
|
||||||
* @returns {Sharp}
|
* @returns {Sharp}
|
||||||
* @throws {Error} Invalid parameters
|
* @throws {Error} Invalid parameters
|
||||||
*/
|
*/
|
||||||
function overlayWith (overlay, options) {
|
function composite (images) {
|
||||||
this.options.overlay = this._createInputDescriptor(overlay, options, {
|
if (!Array.isArray(images)) {
|
||||||
allowStream: false
|
throw is.invalidParameterError('images to composite', 'array', images);
|
||||||
});
|
|
||||||
if (is.object(options)) {
|
|
||||||
if (is.defined(options.tile)) {
|
|
||||||
if (is.bool(options.tile)) {
|
|
||||||
this.options.overlayTile = options.tile;
|
|
||||||
} else {
|
|
||||||
throw new Error('Invalid overlay tile ' + options.tile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (is.defined(options.cutout)) {
|
|
||||||
if (is.bool(options.cutout)) {
|
|
||||||
this.options.overlayCutout = options.cutout;
|
|
||||||
} else {
|
|
||||||
throw new Error('Invalid overlay cutout ' + options.cutout);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (is.defined(options.left) || is.defined(options.top)) {
|
|
||||||
if (is.integer(options.left) && options.left >= 0 && is.integer(options.top) && options.top >= 0) {
|
|
||||||
this.options.overlayXOffset = options.left;
|
|
||||||
this.options.overlayYOffset = options.top;
|
|
||||||
} else {
|
|
||||||
throw new Error('Invalid overlay left ' + options.left + ' and/or top ' + options.top);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (is.defined(options.gravity)) {
|
|
||||||
if (is.integer(options.gravity) && is.inRange(options.gravity, 0, 8)) {
|
|
||||||
this.options.overlayGravity = options.gravity;
|
|
||||||
} else if (is.string(options.gravity) && is.integer(this.constructor.gravity[options.gravity])) {
|
|
||||||
this.options.overlayGravity = this.constructor.gravity[options.gravity];
|
|
||||||
} else {
|
|
||||||
throw new Error('Unsupported overlay gravity ' + options.gravity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
this.options.composite = images.map(image => {
|
||||||
|
if (!is.object(image)) {
|
||||||
|
throw is.invalidParameterError('image to composite', 'object', image);
|
||||||
|
}
|
||||||
|
const { raw, density } = image;
|
||||||
|
const inputOptions = (raw || density) ? { raw, density } : undefined;
|
||||||
|
const composite = {
|
||||||
|
input: this._createInputDescriptor(image.input, inputOptions, { allowStream: false }),
|
||||||
|
blend: 'over',
|
||||||
|
tile: false,
|
||||||
|
left: -1,
|
||||||
|
top: -1,
|
||||||
|
gravity: 0
|
||||||
|
};
|
||||||
|
if (is.defined(image.blend)) {
|
||||||
|
if (is.string(blend[image.blend])) {
|
||||||
|
composite.blend = blend[image.blend];
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('blend', 'valid blend name', image.blend);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(image.tile)) {
|
||||||
|
if (is.bool(image.tile)) {
|
||||||
|
composite.tile = image.tile;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('tile', 'boolean', image.tile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(image.left)) {
|
||||||
|
if (is.integer(image.left) && image.left >= 0) {
|
||||||
|
composite.left = image.left;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('left', 'positive integer', image.left);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(image.top)) {
|
||||||
|
if (is.integer(image.top) && image.top >= 0) {
|
||||||
|
composite.top = image.top;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('top', 'positive integer', image.top);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (composite.left !== composite.top && Math.min(composite.left, composite.top) === -1) {
|
||||||
|
throw new Error('Expected both left and top to be set');
|
||||||
|
}
|
||||||
|
if (is.defined(image.gravity)) {
|
||||||
|
if (is.integer(image.gravity) && is.inRange(image.gravity, 0, 8)) {
|
||||||
|
composite.gravity = image.gravity;
|
||||||
|
} else if (is.string(image.gravity) && is.integer(this.constructor.gravity[image.gravity])) {
|
||||||
|
composite.gravity = this.constructor.gravity[image.gravity];
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('gravity', 'valid gravity', image.gravity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return composite;
|
||||||
|
});
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function overlayWith (input, options) {
|
||||||
|
const blend = (is.object(options) && options.cutout) ? 'dest-in' : 'over';
|
||||||
|
return this.composite([Object.assign({ input, blend }, options)]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decorate the Sharp prototype with composite-related functions.
|
* Decorate the Sharp prototype with composite-related functions.
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
module.exports = function (Sharp) {
|
module.exports = function (Sharp) {
|
||||||
Sharp.prototype.overlayWith = overlayWith;
|
Sharp.prototype.composite = composite;
|
||||||
|
Sharp.prototype.overlayWith = deprecate(overlayWith, 'overlayWith(input, options) is deprecated, use composite([{ input, ...options }]) instead');
|
||||||
|
Sharp.blend = blend;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -61,11 +61,11 @@ const debuglog = util.debuglog('sharp');
|
|||||||
* a String containing the path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file.
|
* 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.
|
* 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 {Object} [options] - if present, is an Object with optional attributes.
|
||||||
* @param {Boolean} [options.failOnError=false] - by default apply a "best effort"
|
* @param {Boolean} [options.failOnError=true] - by default halt processing and raise an error when loading invalid images.
|
||||||
* to decode images, even if the data is corrupt or invalid. Set this flag to true
|
* Set this flag to `false` if you'd rather apply a "best effort" to decode images, even if the data is corrupt or invalid.
|
||||||
* if you'd rather halt processing and raise an error when loading invalid images.
|
|
||||||
* @param {Number} [options.density=72] - number representing the DPI for vector images.
|
* @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 {Number} [options.pages=1] - number of pages to extract for multi-page input (GIF, TIFF, PDF), use -1 for all pages.
|
||||||
|
* @param {Number} [options.page=0] - page number to start extracting from for multi-page input (GIF, TIFF, PDF), zero based.
|
||||||
* @param {Object} [options.raw] - describes raw pixel input image data. See `raw()` for pixel ordering.
|
* @param {Object} [options.raw] - describes raw pixel input image data. See `raw()` for pixel ordering.
|
||||||
* @param {Number} [options.raw.width]
|
* @param {Number} [options.raw.width]
|
||||||
* @param {Number} [options.raw.height]
|
* @param {Number} [options.raw.height]
|
||||||
@@ -146,12 +146,7 @@ const Sharp = function (input, options) {
|
|||||||
removeAlpha: false,
|
removeAlpha: false,
|
||||||
ensureAlpha: false,
|
ensureAlpha: false,
|
||||||
colourspace: 'srgb',
|
colourspace: 'srgb',
|
||||||
// overlay
|
composite: [],
|
||||||
overlayGravity: 0,
|
|
||||||
overlayXOffset: -1,
|
|
||||||
overlayYOffset: -1,
|
|
||||||
overlayTile: false,
|
|
||||||
overlayCutout: false,
|
|
||||||
// output
|
// output
|
||||||
fileOut: '',
|
fileOut: '',
|
||||||
formatOut: 'input',
|
formatOut: 'input',
|
||||||
|
|||||||
23
lib/input.js
@@ -9,7 +9,7 @@ const sharp = require('../build/Release/sharp.node');
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
function _createInputDescriptor (input, inputOptions, containerOptions) {
|
function _createInputDescriptor (input, inputOptions, containerOptions) {
|
||||||
const inputDescriptor = { failOnError: false };
|
const inputDescriptor = { failOnError: true };
|
||||||
if (is.string(input)) {
|
if (is.string(input)) {
|
||||||
// filesystem
|
// filesystem
|
||||||
inputDescriptor.file = input;
|
inputDescriptor.file = input;
|
||||||
@@ -19,6 +19,10 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
|
|||||||
} else if (is.plainObject(input) && !is.defined(inputOptions)) {
|
} else if (is.plainObject(input) && !is.defined(inputOptions)) {
|
||||||
// Plain Object descriptor, e.g. create
|
// Plain Object descriptor, e.g. create
|
||||||
inputOptions = input;
|
inputOptions = input;
|
||||||
|
if (is.plainObject(inputOptions.raw)) {
|
||||||
|
// Raw Stream
|
||||||
|
inputDescriptor.buffer = [];
|
||||||
|
}
|
||||||
} else if (!is.defined(input) && is.object(containerOptions) && containerOptions.allowStream) {
|
} else if (!is.defined(input) && is.object(containerOptions) && containerOptions.allowStream) {
|
||||||
// Stream
|
// Stream
|
||||||
inputDescriptor.buffer = [];
|
inputDescriptor.buffer = [];
|
||||||
@@ -57,7 +61,12 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
|
|||||||
throw new Error('Expected width, height and channels for raw pixel input');
|
throw new Error('Expected width, height and channels for raw pixel input');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Page input for multi-page TIFF
|
// Multi-page input (GIF, TIFF, PDF)
|
||||||
|
if (is.defined(inputOptions.pages)) {
|
||||||
|
if (is.integer(inputOptions.pages) && is.inRange(inputOptions.pages, -1, 100000)) {
|
||||||
|
inputDescriptor.pages = inputOptions.pages;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (is.defined(inputOptions.page)) {
|
if (is.defined(inputOptions.page)) {
|
||||||
if (is.integer(inputOptions.page) && is.inRange(inputOptions.page, 0, 100000)) {
|
if (is.integer(inputOptions.page) && is.inRange(inputOptions.page, 0, 100000)) {
|
||||||
inputDescriptor.page = inputOptions.page;
|
inputDescriptor.page = inputOptions.page;
|
||||||
@@ -174,7 +183,7 @@ function clone () {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Fast access to (uncached) image metadata without decoding any compressed image data.
|
* Fast access to (uncached) image metadata without decoding any compressed image data.
|
||||||
* A Promises/A+ promise is returned when `callback` is not provided.
|
* A `Promise` is returned when `callback` is not provided.
|
||||||
*
|
*
|
||||||
* - `format`: Name of decoder used to decompress image data e.g. `jpeg`, `png`, `webp`, `gif`, `svg`
|
* - `format`: Name of decoder used to decompress image data e.g. `jpeg`, `png`, `webp`, `gif`, `svg`
|
||||||
* - `size`: Total size of image in bytes, for Stream and Buffer input only
|
* - `size`: Total size of image in bytes, for Stream and Buffer input only
|
||||||
@@ -186,7 +195,7 @@ function clone () {
|
|||||||
* - `density`: Number of pixels per inch (DPI), if present
|
* - `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
|
* - `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
|
* - `isProgressive`: Boolean indicating whether the image is interlaced using a progressive scan
|
||||||
* - `pages`: Number of pages this TIFF, GIF or PDF image contains.
|
* - `pages`: Number of pages/frames contained within the image, with support for TIFF, PDF, animated GIF and animated WebP
|
||||||
* - `pageHeight`: Number of pixels high each page in this PDF image will be.
|
* - `pageHeight`: Number of pixels high each page in this PDF image will be.
|
||||||
* - `hasProfile`: Boolean indicating the presence of an embedded ICC profile
|
* - `hasProfile`: Boolean indicating the presence of an embedded ICC profile
|
||||||
* - `hasAlpha`: Boolean indicating the presence of an alpha transparency channel
|
* - `hasAlpha`: Boolean indicating the presence of an alpha transparency channel
|
||||||
@@ -255,7 +264,7 @@ function metadata (callback) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Access to pixel-derived image statistics for every channel in the image.
|
* Access to pixel-derived image statistics for every channel in the image.
|
||||||
* A Promise is returned when `callback` is not provided.
|
* A `Promise` is returned when `callback` is not provided.
|
||||||
*
|
*
|
||||||
* - `channels`: Array of channel statistics for each channel in the image. Each channel statistic contains
|
* - `channels`: Array of channel statistics for each channel in the image. Each channel statistic contains
|
||||||
* - `min` (minimum value in the channel)
|
* - `min` (minimum value in the channel)
|
||||||
@@ -323,9 +332,9 @@ function stats (callback) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Do not process input images where the number of pixels (width * height) exceeds this limit.
|
* Do not process input images where the number of pixels (width x height) exceeds this limit.
|
||||||
* Assumes image dimensions contained in the input metadata can be trusted.
|
* Assumes image dimensions contained in the input metadata can be trusted.
|
||||||
* The default limit is 268402689 (0x3FFF * 0x3FFF) pixels.
|
* The default limit is 268402689 (0x3FFF x 0x3FFF) pixels.
|
||||||
* @param {(Number|Boolean)} limit - an integral Number of pixels, zero or false to remove limit, true to use default limit.
|
* @param {(Number|Boolean)} limit - an integral Number of pixels, zero or false to remove limit, true to use default limit.
|
||||||
* @returns {Sharp}
|
* @returns {Sharp}
|
||||||
* @throws {Error} Invalid limit
|
* @throws {Error} Invalid limit
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ const sharp = require('../build/Release/sharp.node');
|
|||||||
*/
|
*/
|
||||||
function toFile (fileOut, callback) {
|
function toFile (fileOut, callback) {
|
||||||
if (!fileOut || fileOut.length === 0) {
|
if (!fileOut || fileOut.length === 0) {
|
||||||
const errOutputInvalid = new Error('Invalid output');
|
const errOutputInvalid = new Error('Missing output file path');
|
||||||
if (is.fn(callback)) {
|
if (is.fn(callback)) {
|
||||||
callback(errOutputInvalid);
|
callback(errOutputInvalid);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -2,17 +2,22 @@
|
|||||||
|
|
||||||
const detectLibc = require('detect-libc');
|
const detectLibc = require('detect-libc');
|
||||||
|
|
||||||
|
const env = process.env;
|
||||||
|
|
||||||
module.exports = function () {
|
module.exports = function () {
|
||||||
const arch = process.env.npm_config_arch || process.arch;
|
const arch = env.npm_config_arch || process.arch;
|
||||||
const platform = process.env.npm_config_platform || process.platform;
|
const platform = env.npm_config_platform || process.platform;
|
||||||
const libc = (platform === 'linux' && detectLibc.isNonGlibcLinux) ? detectLibc.family : '';
|
const libc = (platform === 'linux' && detectLibc.isNonGlibcLinux) ? detectLibc.family : '';
|
||||||
|
|
||||||
const platformId = [`${platform}${libc}`];
|
const platformId = [`${platform}${libc}`];
|
||||||
if (arch === 'arm' || arch === 'armhf' || arch === 'arm64') {
|
|
||||||
const armVersion = (arch === 'arm64') ? '8' : process.env.npm_config_armv || process.config.variables.arm_version || '6';
|
if (arch === 'arm') {
|
||||||
platformId.push(`armv${armVersion}`);
|
platformId.push(`armv${env.npm_config_arm_version || process.config.variables.arm_version || '6'}`);
|
||||||
|
} else if (arch === 'arm64') {
|
||||||
|
platformId.push(`arm64v${env.npm_config_arm_version || '8'}`);
|
||||||
} else {
|
} else {
|
||||||
platformId.push(arch);
|
platformId.push(arch);
|
||||||
}
|
}
|
||||||
|
|
||||||
return platformId.join('-');
|
return platformId.join('-');
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const deprecate = require('util').deprecate;
|
|
||||||
const is = require('./is');
|
const is = require('./is');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -275,7 +274,7 @@ function resize (width, height, options) {
|
|||||||
* top: 10,
|
* top: 10,
|
||||||
* bottom: 20,
|
* bottom: 20,
|
||||||
* left: 10,
|
* left: 10,
|
||||||
* right: 10
|
* right: 10,
|
||||||
* background: { r: 0, g: 0, b: 0, alpha: 0 }
|
* background: { r: 0, g: 0, b: 0, alpha: 0 }
|
||||||
* })
|
* })
|
||||||
* ...
|
* ...
|
||||||
@@ -378,92 +377,6 @@ function trim (threshold) {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deprecated functions
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
function crop (crop) {
|
|
||||||
this.options.canvas = 'crop';
|
|
||||||
if (!is.defined(crop)) {
|
|
||||||
// Default
|
|
||||||
this.options.position = gravity.center;
|
|
||||||
} else if (is.integer(crop) && is.inRange(crop, 0, 8)) {
|
|
||||||
// Gravity (numeric)
|
|
||||||
this.options.position = crop;
|
|
||||||
} else if (is.string(crop) && is.integer(gravity[crop])) {
|
|
||||||
// Gravity (string)
|
|
||||||
this.options.position = gravity[crop];
|
|
||||||
} else if (is.integer(crop) && crop >= strategy.entropy) {
|
|
||||||
// Strategy
|
|
||||||
this.options.position = crop;
|
|
||||||
} else if (is.string(crop) && is.integer(strategy[crop])) {
|
|
||||||
// Strategy (string)
|
|
||||||
this.options.position = strategy[crop];
|
|
||||||
} else {
|
|
||||||
throw is.invalidParameterError('crop', 'valid crop id/name/strategy', crop);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
function embed (embed) {
|
|
||||||
this.options.canvas = 'embed';
|
|
||||||
if (!is.defined(embed)) {
|
|
||||||
// Default
|
|
||||||
this.options.position = gravity.center;
|
|
||||||
} else if (is.integer(embed) && is.inRange(embed, 0, 8)) {
|
|
||||||
// Gravity (numeric)
|
|
||||||
this.options.position = embed;
|
|
||||||
} else if (is.string(embed) && is.integer(gravity[embed])) {
|
|
||||||
// Gravity (string)
|
|
||||||
this.options.position = gravity[embed];
|
|
||||||
} else {
|
|
||||||
throw is.invalidParameterError('embed', 'valid embed id/name', embed);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
function max () {
|
|
||||||
this.options.canvas = 'max';
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
function min () {
|
|
||||||
this.options.canvas = 'min';
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
function ignoreAspectRatio () {
|
|
||||||
this.options.canvas = 'ignore_aspect';
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
function withoutEnlargement (withoutEnlargement) {
|
|
||||||
this.options.withoutEnlargement = is.bool(withoutEnlargement) ? withoutEnlargement : true;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decorate the Sharp prototype with resize-related functions.
|
* Decorate the Sharp prototype with resize-related functions.
|
||||||
* @private
|
* @private
|
||||||
@@ -481,11 +394,4 @@ module.exports = function (Sharp) {
|
|||||||
Sharp.kernel = kernel;
|
Sharp.kernel = kernel;
|
||||||
Sharp.fit = fit;
|
Sharp.fit = fit;
|
||||||
Sharp.position = position;
|
Sharp.position = position;
|
||||||
// Deprecated functions, to be removed in v0.22.0
|
|
||||||
Sharp.prototype.crop = deprecate(crop, 'crop(position) is deprecated, use resize({ fit: "cover", position }) instead');
|
|
||||||
Sharp.prototype.embed = deprecate(embed, 'embed(position) is deprecated, use resize({ fit: "contain", position }) instead');
|
|
||||||
Sharp.prototype.max = deprecate(max, 'max() is deprecated, use resize({ fit: "inside" }) instead');
|
|
||||||
Sharp.prototype.min = deprecate(min, 'min() is deprecated, use resize({ fit: "outside" }) instead');
|
|
||||||
Sharp.prototype.ignoreAspectRatio = deprecate(ignoreAspectRatio, 'ignoreAspectRatio() is deprecated, use resize({ fit: "fill" }) instead');
|
|
||||||
Sharp.prototype.withoutEnlargement = deprecate(withoutEnlargement, 'withoutEnlargement() is deprecated, use resize({ withoutEnlargement: true }) instead');
|
|
||||||
};
|
};
|
||||||
|
|||||||
24
package.json
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "sharp",
|
"name": "sharp",
|
||||||
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP and TIFF images",
|
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP and TIFF images",
|
||||||
"version": "0.21.2",
|
"version": "0.22.0",
|
||||||
"author": "Lovell Fuller <npm@lovell.info>",
|
"author": "Lovell Fuller <npm@lovell.info>",
|
||||||
"homepage": "https://github.com/lovell/sharp",
|
"homepage": "https://github.com/lovell/sharp",
|
||||||
"contributors": [
|
"contributors": [
|
||||||
@@ -93,37 +93,37 @@
|
|||||||
"vips"
|
"vips"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bindings": "^1.3.1",
|
"bindings": "^1.5.0",
|
||||||
"color": "^3.1.0",
|
"color": "^3.1.0",
|
||||||
"detect-libc": "^1.0.3",
|
"detect-libc": "^1.0.3",
|
||||||
"fs-copy-file-sync": "^1.1.1",
|
"fs-copy-file-sync": "^1.1.1",
|
||||||
"nan": "^2.12.1",
|
"nan": "^2.13.1",
|
||||||
"npmlog": "^4.1.2",
|
"npmlog": "^4.1.2",
|
||||||
"prebuild-install": "^5.2.2",
|
"prebuild-install": "^5.2.5",
|
||||||
"semver": "^5.6.0",
|
"semver": "^5.6.0",
|
||||||
"simple-get": "^3.0.3",
|
"simple-get": "^3.0.3",
|
||||||
"tar": "^4.4.8",
|
"tar": "^4.4.8",
|
||||||
"tunnel-agent": "^0.6.0"
|
"tunnel-agent": "^0.6.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"async": "^2.6.1",
|
"async": "^2.6.2",
|
||||||
"cc": "^1.0.2",
|
"cc": "^1.0.2",
|
||||||
"decompress-zip": "^0.3.1",
|
"decompress-zip": "^0.3.2",
|
||||||
"documentation": "^9.1.1",
|
"documentation": "^9.3.1",
|
||||||
"exif-reader": "^1.0.2",
|
"exif-reader": "^1.0.2",
|
||||||
"icc": "^1.0.0",
|
"icc": "^1.0.0",
|
||||||
"license-checker": "^25.0.1",
|
"license-checker": "^25.0.1",
|
||||||
"mocha": "^5.2.0",
|
"mocha": "^6.0.2",
|
||||||
"mock-fs": "^4.7.0",
|
"mock-fs": "^4.8.0",
|
||||||
"nyc": "^13.1.0",
|
"nyc": "^13.3.0",
|
||||||
"prebuild": "^8.1.2",
|
"prebuild": "8.1.0",
|
||||||
"prebuild-ci": "^2.3.0",
|
"prebuild-ci": "^2.3.0",
|
||||||
"rimraf": "^2.6.3",
|
"rimraf": "^2.6.3",
|
||||||
"semistandard": "^13.0.1"
|
"semistandard": "^13.0.1"
|
||||||
},
|
},
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"config": {
|
"config": {
|
||||||
"libvips": "8.7.0"
|
"libvips": "8.7.4"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
|
|||||||
@@ -71,7 +71,10 @@ namespace sharp {
|
|||||||
descriptor->rawWidth = AttrTo<uint32_t>(input, "rawWidth");
|
descriptor->rawWidth = AttrTo<uint32_t>(input, "rawWidth");
|
||||||
descriptor->rawHeight = AttrTo<uint32_t>(input, "rawHeight");
|
descriptor->rawHeight = AttrTo<uint32_t>(input, "rawHeight");
|
||||||
}
|
}
|
||||||
// Page input for multi-page TIFF
|
// Multi-page input (GIF, TIFF, PDF)
|
||||||
|
if (HasAttr(input, "pages")) {
|
||||||
|
descriptor->pages = AttrTo<int32_t>(input, "pages");
|
||||||
|
}
|
||||||
if (HasAttr(input, "page")) {
|
if (HasAttr(input, "page")) {
|
||||||
descriptor->page = AttrTo<uint32_t>(input, "page");
|
descriptor->page = AttrTo<uint32_t>(input, "page");
|
||||||
}
|
}
|
||||||
@@ -137,6 +140,7 @@ namespace sharp {
|
|||||||
case ImageType::VIPS: id = "v"; break;
|
case ImageType::VIPS: id = "v"; break;
|
||||||
case ImageType::RAW: id = "raw"; break;
|
case ImageType::RAW: id = "raw"; break;
|
||||||
case ImageType::UNKNOWN: id = "unknown"; break;
|
case ImageType::UNKNOWN: id = "unknown"; break;
|
||||||
|
case ImageType::MISSING: id = "missing"; break;
|
||||||
}
|
}
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
@@ -203,10 +207,24 @@ namespace sharp {
|
|||||||
} else if (EndsWith(loader, "Magick") || EndsWith(loader, "MagickFile")) {
|
} else if (EndsWith(loader, "Magick") || EndsWith(loader, "MagickFile")) {
|
||||||
imageType = ImageType::MAGICK;
|
imageType = ImageType::MAGICK;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (EndsWith(vips::VError().what(), " not found\n")) {
|
||||||
|
imageType = ImageType::MISSING;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return imageType;
|
return imageType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Does this image type support multiple pages?
|
||||||
|
*/
|
||||||
|
bool ImageTypeSupportsPage(ImageType imageType) {
|
||||||
|
return
|
||||||
|
imageType == ImageType::GIF ||
|
||||||
|
imageType == ImageType::TIFF ||
|
||||||
|
imageType == ImageType::PDF;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Open an image from the given InputDescriptor (filesystem, compressed buffer, raw pixel data)
|
Open an image from the given InputDescriptor (filesystem, compressed buffer, raw pixel data)
|
||||||
*/
|
*/
|
||||||
@@ -238,8 +256,9 @@ namespace sharp {
|
|||||||
if (imageType == ImageType::MAGICK) {
|
if (imageType == ImageType::MAGICK) {
|
||||||
option->set("density", std::to_string(descriptor->density).data());
|
option->set("density", std::to_string(descriptor->density).data());
|
||||||
}
|
}
|
||||||
if (imageType == ImageType::TIFF) {
|
if (ImageTypeSupportsPage(imageType)) {
|
||||||
option->set("page", descriptor->page);
|
option->set("n", descriptor->pages);
|
||||||
|
option->set("page", descriptor->page);
|
||||||
}
|
}
|
||||||
image = VImage::new_from_buffer(descriptor->buffer, descriptor->bufferLength, nullptr, option);
|
image = VImage::new_from_buffer(descriptor->buffer, descriptor->bufferLength, nullptr, option);
|
||||||
if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
|
if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
|
||||||
@@ -269,6 +288,9 @@ namespace sharp {
|
|||||||
} else {
|
} else {
|
||||||
// From filesystem
|
// From filesystem
|
||||||
imageType = DetermineImageType(descriptor->file.data());
|
imageType = DetermineImageType(descriptor->file.data());
|
||||||
|
if (imageType == ImageType::MISSING) {
|
||||||
|
throw vips::VError("Input file is missing");
|
||||||
|
}
|
||||||
if (imageType != ImageType::UNKNOWN) {
|
if (imageType != ImageType::UNKNOWN) {
|
||||||
try {
|
try {
|
||||||
vips::VOption *option = VImage::option()
|
vips::VOption *option = VImage::option()
|
||||||
@@ -280,8 +302,9 @@ namespace sharp {
|
|||||||
if (imageType == ImageType::MAGICK) {
|
if (imageType == ImageType::MAGICK) {
|
||||||
option->set("density", std::to_string(descriptor->density).data());
|
option->set("density", std::to_string(descriptor->density).data());
|
||||||
}
|
}
|
||||||
if (imageType == ImageType::TIFF) {
|
if (ImageTypeSupportsPage(imageType)) {
|
||||||
option->set("page", descriptor->page);
|
option->set("n", descriptor->pages);
|
||||||
|
option->set("page", descriptor->page);
|
||||||
}
|
}
|
||||||
image = VImage::new_from_file(descriptor->file.data(), option);
|
image = VImage::new_from_file(descriptor->file.data(), option);
|
||||||
if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
|
if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
|
||||||
@@ -291,7 +314,7 @@ namespace sharp {
|
|||||||
throw vips::VError(std::string("Input file has corrupt header: ") + err.what());
|
throw vips::VError(std::string("Input file has corrupt header: ") + err.what());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw vips::VError("Input file is missing or of an unsupported image format");
|
throw vips::VError("Input file contains unsupported image format");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
12
src/common.h
@@ -53,6 +53,7 @@ namespace sharp {
|
|||||||
int rawChannels;
|
int rawChannels;
|
||||||
int rawWidth;
|
int rawWidth;
|
||||||
int rawHeight;
|
int rawHeight;
|
||||||
|
int pages;
|
||||||
int page;
|
int page;
|
||||||
int createChannels;
|
int createChannels;
|
||||||
int createWidth;
|
int createWidth;
|
||||||
@@ -61,12 +62,13 @@ namespace sharp {
|
|||||||
|
|
||||||
InputDescriptor():
|
InputDescriptor():
|
||||||
buffer(nullptr),
|
buffer(nullptr),
|
||||||
failOnError(FALSE),
|
failOnError(TRUE),
|
||||||
bufferLength(0),
|
bufferLength(0),
|
||||||
density(72.0),
|
density(72.0),
|
||||||
rawChannels(0),
|
rawChannels(0),
|
||||||
rawWidth(0),
|
rawWidth(0),
|
||||||
rawHeight(0),
|
rawHeight(0),
|
||||||
|
pages(1),
|
||||||
page(0),
|
page(0),
|
||||||
createChannels(0),
|
createChannels(0),
|
||||||
createWidth(0),
|
createWidth(0),
|
||||||
@@ -106,7 +108,8 @@ namespace sharp {
|
|||||||
FITS,
|
FITS,
|
||||||
VIPS,
|
VIPS,
|
||||||
RAW,
|
RAW,
|
||||||
UNKNOWN
|
UNKNOWN,
|
||||||
|
MISSING
|
||||||
};
|
};
|
||||||
|
|
||||||
// How many tasks are in the queue?
|
// How many tasks are in the queue?
|
||||||
@@ -139,6 +142,11 @@ namespace sharp {
|
|||||||
*/
|
*/
|
||||||
ImageType DetermineImageType(char const *file);
|
ImageType DetermineImageType(char const *file);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Does this image type support multiple pages?
|
||||||
|
*/
|
||||||
|
bool ImageTypeSupportsPage(ImageType imageType);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Open an image from the given InputDescriptor (filesystem, compressed buffer, raw pixel data)
|
Open an image from the given InputDescriptor (filesystem, compressed buffer, raw pixel data)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -50,130 +50,6 @@ namespace sharp {
|
|||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
Composite overlayImage over image at given position
|
|
||||||
Assumes alpha channels are already premultiplied and will be unpremultiplied after
|
|
||||||
*/
|
|
||||||
VImage Composite(VImage image, VImage overlayImage, int const left, int const top) {
|
|
||||||
if (HasAlpha(overlayImage)) {
|
|
||||||
// Alpha composite
|
|
||||||
if (overlayImage.width() < image.width() || overlayImage.height() < image.height()) {
|
|
||||||
// Enlarge overlay
|
|
||||||
std::vector<double> const background { 0.0, 0.0, 0.0, 0.0 };
|
|
||||||
overlayImage = overlayImage.embed(left, top, image.width(), image.height(), VImage::option()
|
|
||||||
->set("extend", VIPS_EXTEND_BACKGROUND)
|
|
||||||
->set("background", background));
|
|
||||||
}
|
|
||||||
return AlphaComposite(image, overlayImage);
|
|
||||||
} else {
|
|
||||||
if (HasAlpha(image)) {
|
|
||||||
// Add alpha channel to overlayImage so channels match
|
|
||||||
double const multiplier = sharp::Is16Bit(overlayImage.interpretation()) ? 256.0 : 1.0;
|
|
||||||
overlayImage = overlayImage.bandjoin(
|
|
||||||
VImage::new_matrix(overlayImage.width(), overlayImage.height()).new_from_image(255 * multiplier));
|
|
||||||
}
|
|
||||||
return image.insert(overlayImage, left, top);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
VImage AlphaComposite(VImage dst, VImage src) {
|
|
||||||
// Split src into non-alpha and alpha channels
|
|
||||||
VImage srcWithoutAlpha = src.extract_band(0, VImage::option()->set("n", src.bands() - 1));
|
|
||||||
VImage srcAlpha = src[src.bands() - 1] * (1.0 / 255.0);
|
|
||||||
|
|
||||||
// Split dst into non-alpha and alpha channels
|
|
||||||
VImage dstWithoutAlpha = dst.extract_band(0, VImage::option()->set("n", dst.bands() - 1));
|
|
||||||
VImage dstAlpha = dst[dst.bands() - 1] * (1.0 / 255.0);
|
|
||||||
|
|
||||||
//
|
|
||||||
// Compute normalized output alpha channel:
|
|
||||||
//
|
|
||||||
// References:
|
|
||||||
// - http://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending
|
|
||||||
// - https://github.com/libvips/ruby-vips/issues/28#issuecomment-9014826
|
|
||||||
//
|
|
||||||
// out_a = src_a + dst_a * (1 - src_a)
|
|
||||||
// ^^^^^^^^^^^
|
|
||||||
// t0
|
|
||||||
VImage t0 = srcAlpha.linear(-1.0, 1.0);
|
|
||||||
VImage outAlphaNormalized = srcAlpha + dstAlpha * t0;
|
|
||||||
|
|
||||||
//
|
|
||||||
// Compute output RGB channels:
|
|
||||||
//
|
|
||||||
// Wikipedia:
|
|
||||||
// out_rgb = (src_rgb * src_a + dst_rgb * dst_a * (1 - src_a)) / out_a
|
|
||||||
// ^^^^^^^^^^^
|
|
||||||
// t0
|
|
||||||
//
|
|
||||||
// Omit division by `out_a` since `Compose` is supposed to output a
|
|
||||||
// premultiplied RGBA image as reversal of premultiplication is handled
|
|
||||||
// externally.
|
|
||||||
//
|
|
||||||
VImage outRGBPremultiplied = srcWithoutAlpha + dstWithoutAlpha * t0;
|
|
||||||
|
|
||||||
// Combine RGB and alpha channel into output image:
|
|
||||||
return outRGBPremultiplied.bandjoin(outAlphaNormalized * 255.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Cutout src over dst with given gravity.
|
|
||||||
*/
|
|
||||||
VImage Cutout(VImage mask, VImage dst, const int gravity) {
|
|
||||||
using sharp::CalculateCrop;
|
|
||||||
using sharp::HasAlpha;
|
|
||||||
using sharp::MaximumImageAlpha;
|
|
||||||
|
|
||||||
bool maskHasAlpha = HasAlpha(mask);
|
|
||||||
|
|
||||||
if (!maskHasAlpha && mask.bands() > 1) {
|
|
||||||
throw VError("Overlay image must have an alpha channel or one band");
|
|
||||||
}
|
|
||||||
if (!HasAlpha(dst)) {
|
|
||||||
throw VError("Image to be overlaid must have an alpha channel");
|
|
||||||
}
|
|
||||||
if (mask.width() > dst.width() || mask.height() > dst.height()) {
|
|
||||||
throw VError("Overlay image must have same dimensions or smaller");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enlarge overlay mask, if required
|
|
||||||
if (mask.width() < dst.width() || mask.height() < dst.height()) {
|
|
||||||
// Calculate the (left, top) coordinates of the output image within the input image, applying the given gravity.
|
|
||||||
int left;
|
|
||||||
int top;
|
|
||||||
std::tie(left, top) = CalculateCrop(dst.width(), dst.height(), mask.width(), mask.height(), gravity);
|
|
||||||
// Embed onto transparent background
|
|
||||||
std::vector<double> background { 0.0, 0.0, 0.0, 0.0 };
|
|
||||||
mask = mask.embed(left, top, dst.width(), dst.height(), VImage::option()
|
|
||||||
->set("extend", VIPS_EXTEND_BACKGROUND)
|
|
||||||
->set("background", background));
|
|
||||||
}
|
|
||||||
|
|
||||||
// we use the mask alpha if it has alpha
|
|
||||||
if (maskHasAlpha) {
|
|
||||||
mask = mask.extract_band(mask.bands() - 1, VImage::option()->set("n", 1));;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Split dst into an optional alpha
|
|
||||||
VImage dstAlpha = dst.extract_band(dst.bands() - 1, VImage::option()->set("n", 1));
|
|
||||||
|
|
||||||
// we use the dst non-alpha
|
|
||||||
dst = dst.extract_band(0, VImage::option()->set("n", dst.bands() - 1));
|
|
||||||
|
|
||||||
// the range of the mask and the image need to match .. one could be
|
|
||||||
// 16-bit, one 8-bit
|
|
||||||
double const dstMax = MaximumImageAlpha(dst.interpretation());
|
|
||||||
double const maskMax = MaximumImageAlpha(mask.interpretation());
|
|
||||||
|
|
||||||
// combine the new mask and the existing alpha ... there are
|
|
||||||
// many ways of doing this, mult is the simplest
|
|
||||||
mask = dstMax * ((mask / maskMax) * (dstAlpha / dstMax));
|
|
||||||
|
|
||||||
// append the mask to the image data ... the mask might be float now,
|
|
||||||
// we must cast the format down to match the image data
|
|
||||||
return dst.bandjoin(mask.cast(dst.format()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Tint an image using the specified chroma, preserving the original image luminance
|
* Tint an image using the specified chroma, preserving the original image luminance
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -35,27 +35,6 @@ namespace sharp {
|
|||||||
*/
|
*/
|
||||||
VImage EnsureAlpha(VImage image);
|
VImage EnsureAlpha(VImage image);
|
||||||
|
|
||||||
/*
|
|
||||||
Alpha composite src over dst with given gravity.
|
|
||||||
Assumes alpha channels are already premultiplied and will be unpremultiplied after.
|
|
||||||
*/
|
|
||||||
VImage Composite(VImage src, VImage dst, const int gravity);
|
|
||||||
|
|
||||||
/*
|
|
||||||
Composite overlayImage over image at given position
|
|
||||||
*/
|
|
||||||
VImage Composite(VImage image, VImage overlayImage, int const x, int const y);
|
|
||||||
|
|
||||||
/*
|
|
||||||
Alpha composite overlayImage over image, assumes matching dimensions
|
|
||||||
*/
|
|
||||||
VImage AlphaComposite(VImage image, VImage overlayImage);
|
|
||||||
|
|
||||||
/*
|
|
||||||
Cutout src over dst with given gravity.
|
|
||||||
*/
|
|
||||||
VImage Cutout(VImage src, VImage dst, const int gravity);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Tint an image using the specified chroma, preserving the original image luminance
|
* Tint an image using the specified chroma, preserving the original image luminance
|
||||||
*/
|
*/
|
||||||
|
|||||||
167
src/pipeline.cc
@@ -343,30 +343,19 @@ class PipelineWorker : public Nan::AsyncWorker {
|
|||||||
image = image.colourspace(VIPS_INTERPRETATION_B_W);
|
image = image.colourspace(VIPS_INTERPRETATION_B_W);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure image has an alpha channel when there is an overlay with an alpha channel
|
|
||||||
VImage overlayImage;
|
|
||||||
ImageType overlayImageType = ImageType::UNKNOWN;
|
|
||||||
bool shouldOverlayWithAlpha = FALSE;
|
|
||||||
if (baton->overlay != nullptr) {
|
|
||||||
std::tie(overlayImage, overlayImageType) = OpenInput(baton->overlay, baton->accessMethod);
|
|
||||||
if (HasAlpha(overlayImage)) {
|
|
||||||
shouldOverlayWithAlpha = !baton->overlayCutout;
|
|
||||||
if (!HasAlpha(image)) {
|
|
||||||
double const multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0;
|
|
||||||
image = image.bandjoin(
|
|
||||||
VImage::new_matrix(image.width(), image.height()).new_from_image(255 * multiplier));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool const shouldResize = xfactor != 1.0 || yfactor != 1.0;
|
bool const shouldResize = xfactor != 1.0 || yfactor != 1.0;
|
||||||
bool const shouldBlur = baton->blurSigma != 0.0;
|
bool const shouldBlur = baton->blurSigma != 0.0;
|
||||||
bool const shouldConv = baton->convKernelWidth * baton->convKernelHeight > 0;
|
bool const shouldConv = baton->convKernelWidth * baton->convKernelHeight > 0;
|
||||||
bool const shouldSharpen = baton->sharpenSigma != 0.0;
|
bool const shouldSharpen = baton->sharpenSigma != 0.0;
|
||||||
bool const shouldApplyMedian = baton->medianSize > 0;
|
bool const shouldApplyMedian = baton->medianSize > 0;
|
||||||
|
bool const shouldComposite = !baton->composite.empty();
|
||||||
|
|
||||||
|
if (shouldComposite && !HasAlpha(image)) {
|
||||||
|
image = sharp::EnsureAlpha(image);
|
||||||
|
}
|
||||||
|
|
||||||
bool const shouldPremultiplyAlpha = HasAlpha(image) &&
|
bool const shouldPremultiplyAlpha = HasAlpha(image) &&
|
||||||
(shouldResize || shouldBlur || shouldConv || shouldSharpen || shouldOverlayWithAlpha);
|
(shouldResize || shouldBlur || shouldConv || shouldSharpen || shouldComposite);
|
||||||
|
|
||||||
// Premultiply image alpha channel before all transformations to avoid
|
// Premultiply image alpha channel before all transformations to avoid
|
||||||
// dark fringing around bright pixels
|
// dark fringing around bright pixels
|
||||||
@@ -544,72 +533,67 @@ class PipelineWorker : public Nan::AsyncWorker {
|
|||||||
image = sharp::Sharpen(image, baton->sharpenSigma, baton->sharpenFlat, baton->sharpenJagged);
|
image = sharp::Sharpen(image, baton->sharpenSigma, baton->sharpenFlat, baton->sharpenJagged);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Composite with overlay, if present
|
// Composite
|
||||||
if (baton->overlay != nullptr) {
|
if (shouldComposite) {
|
||||||
// Verify overlay image is within current dimensions
|
for (Composite *composite : baton->composite) {
|
||||||
if (overlayImage.width() > image.width() || overlayImage.height() > image.height()) {
|
VImage compositeImage;
|
||||||
throw vips::VError("Overlay image must have same dimensions or smaller");
|
ImageType compositeImageType = ImageType::UNKNOWN;
|
||||||
}
|
std::tie(compositeImage, compositeImageType) = OpenInput(composite->input, baton->accessMethod);
|
||||||
// Check if overlay is tiled
|
// Verify within current dimensions
|
||||||
if (baton->overlayTile) {
|
if (compositeImage.width() > image.width() || compositeImage.height() > image.height()) {
|
||||||
int const overlayImageWidth = overlayImage.width();
|
throw vips::VError("Image to composite must have same dimensions or smaller");
|
||||||
int const overlayImageHeight = overlayImage.height();
|
|
||||||
int across = 0;
|
|
||||||
int down = 0;
|
|
||||||
// Use gravity in overlay
|
|
||||||
if (overlayImageWidth <= baton->width) {
|
|
||||||
across = static_cast<int>(ceil(static_cast<double>(image.width()) / overlayImageWidth));
|
|
||||||
}
|
}
|
||||||
if (overlayImageHeight <= baton->height) {
|
// Check if overlay is tiled
|
||||||
down = static_cast<int>(ceil(static_cast<double>(image.height()) / overlayImageHeight));
|
if (composite->tile) {
|
||||||
}
|
int across = 0;
|
||||||
if (across != 0 || down != 0) {
|
int down = 0;
|
||||||
int left;
|
// Use gravity in overlay
|
||||||
int top;
|
if (compositeImage.width() <= baton->width) {
|
||||||
overlayImage = overlayImage.replicate(across, down);
|
across = static_cast<int>(ceil(static_cast<double>(image.width()) / compositeImage.width()));
|
||||||
if (baton->overlayXOffset >= 0 && baton->overlayYOffset >= 0) {
|
|
||||||
// the overlayX/YOffsets will now be used to CalculateCrop for extract_area
|
|
||||||
std::tie(left, top) = sharp::CalculateCrop(
|
|
||||||
overlayImage.width(), overlayImage.height(), image.width(), image.height(),
|
|
||||||
baton->overlayXOffset, baton->overlayYOffset);
|
|
||||||
} else {
|
|
||||||
// the overlayGravity will now be used to CalculateCrop for extract_area
|
|
||||||
std::tie(left, top) = sharp::CalculateCrop(
|
|
||||||
overlayImage.width(), overlayImage.height(), image.width(), image.height(), baton->overlayGravity);
|
|
||||||
}
|
}
|
||||||
overlayImage = overlayImage.extract_area(left, top, image.width(), image.height());
|
if (compositeImage.height() <= baton->height) {
|
||||||
}
|
down = static_cast<int>(ceil(static_cast<double>(image.height()) / compositeImage.height()));
|
||||||
// the overlayGravity was used for extract_area, therefore set it back to its default value of 0
|
|
||||||
baton->overlayGravity = 0;
|
|
||||||
}
|
|
||||||
if (baton->overlayCutout) {
|
|
||||||
// 'cut out' the image, premultiplication is not required
|
|
||||||
image = sharp::Cutout(overlayImage, image, baton->overlayGravity);
|
|
||||||
} else {
|
|
||||||
// Ensure overlay is sRGB
|
|
||||||
overlayImage = overlayImage.colourspace(VIPS_INTERPRETATION_sRGB);
|
|
||||||
// Ensure overlay matches premultiplication state
|
|
||||||
if (shouldPremultiplyAlpha) {
|
|
||||||
// Ensure overlay has alpha channel
|
|
||||||
if (!HasAlpha(overlayImage)) {
|
|
||||||
double const multiplier = sharp::Is16Bit(overlayImage.interpretation()) ? 256.0 : 1.0;
|
|
||||||
overlayImage = overlayImage.bandjoin(
|
|
||||||
VImage::new_matrix(overlayImage.width(), overlayImage.height()).new_from_image(255 * multiplier));
|
|
||||||
}
|
}
|
||||||
overlayImage = overlayImage.premultiply();
|
if (across != 0 || down != 0) {
|
||||||
|
int left;
|
||||||
|
int top;
|
||||||
|
compositeImage = compositeImage.replicate(across, down);
|
||||||
|
if (composite->left >= 0 && composite->top >= 0) {
|
||||||
|
std::tie(left, top) = sharp::CalculateCrop(
|
||||||
|
compositeImage.width(), compositeImage.height(), image.width(), image.height(),
|
||||||
|
composite->left, composite->top);
|
||||||
|
} else {
|
||||||
|
std::tie(left, top) = sharp::CalculateCrop(
|
||||||
|
compositeImage.width(), compositeImage.height(), image.width(), image.height(), composite->gravity);
|
||||||
|
}
|
||||||
|
compositeImage = compositeImage.extract_area(left, top, image.width(), image.height());
|
||||||
|
}
|
||||||
|
// gravity was used for extract_area, set it back to its default value of 0
|
||||||
|
composite->gravity = 0;
|
||||||
}
|
}
|
||||||
|
// Ensure image to composite is sRGB with premultiplied alpha
|
||||||
|
compositeImage = compositeImage.colourspace(VIPS_INTERPRETATION_sRGB);
|
||||||
|
if (!HasAlpha(compositeImage)) {
|
||||||
|
compositeImage = sharp::EnsureAlpha(compositeImage);
|
||||||
|
}
|
||||||
|
compositeImage = compositeImage.premultiply();
|
||||||
|
// Calculate position
|
||||||
int left;
|
int left;
|
||||||
int top;
|
int top;
|
||||||
if (baton->overlayXOffset >= 0 && baton->overlayYOffset >= 0) {
|
if (composite->left >= 0 && composite->top >= 0) {
|
||||||
// Composite images at given offsets
|
// Composite image at given offsets
|
||||||
std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),
|
std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),
|
||||||
overlayImage.width(), overlayImage.height(), baton->overlayXOffset, baton->overlayYOffset);
|
compositeImage.width(), compositeImage.height(), composite->left, composite->top);
|
||||||
} else {
|
} else {
|
||||||
// Composite images with given gravity
|
// Composite image with given gravity
|
||||||
std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),
|
std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),
|
||||||
overlayImage.width(), overlayImage.height(), baton->overlayGravity);
|
compositeImage.width(), compositeImage.height(), composite->gravity);
|
||||||
}
|
}
|
||||||
image = sharp::Composite(image, overlayImage, left, top);
|
// Composite
|
||||||
|
image = image.composite2(compositeImage, composite->mode, VImage::option()
|
||||||
|
->set("premultiplied", TRUE)
|
||||||
|
->set("x", left)
|
||||||
|
->set("y", top));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1029,13 +1013,17 @@ class PipelineWorker : public Nan::AsyncWorker {
|
|||||||
GetFromPersistent(index);
|
GetFromPersistent(index);
|
||||||
return index + 1;
|
return index + 1;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Delete baton
|
||||||
delete baton->input;
|
delete baton->input;
|
||||||
delete baton->overlay;
|
|
||||||
delete baton->boolean;
|
delete baton->boolean;
|
||||||
for_each(baton->joinChannelIn.begin(), baton->joinChannelIn.end(),
|
for (Composite *composite : baton->composite) {
|
||||||
[this](sharp::InputDescriptor *joinChannelIn) {
|
delete composite->input;
|
||||||
delete joinChannelIn;
|
delete composite;
|
||||||
});
|
}
|
||||||
|
for (sharp::InputDescriptor *input : baton->joinChannelIn) {
|
||||||
|
delete input;
|
||||||
|
}
|
||||||
delete baton;
|
delete baton;
|
||||||
|
|
||||||
// Handle warnings
|
// Handle warnings
|
||||||
@@ -1182,14 +1170,21 @@ NAN_METHOD(pipeline) {
|
|||||||
// Tint chroma
|
// Tint chroma
|
||||||
baton->tintA = AttrTo<double>(options, "tintA");
|
baton->tintA = AttrTo<double>(options, "tintA");
|
||||||
baton->tintB = AttrTo<double>(options, "tintB");
|
baton->tintB = AttrTo<double>(options, "tintB");
|
||||||
// Overlay options
|
// Composite
|
||||||
if (HasAttr(options, "overlay")) {
|
v8::Local<v8::Array> compositeArray = Nan::Get(options, Nan::New("composite").ToLocalChecked())
|
||||||
baton->overlay = CreateInputDescriptor(AttrAs<v8::Object>(options, "overlay"), buffersToPersist);
|
.ToLocalChecked().As<v8::Array>();
|
||||||
baton->overlayGravity = AttrTo<int32_t>(options, "overlayGravity");
|
int const compositeArrayLength = AttrTo<uint32_t>(compositeArray, "length");
|
||||||
baton->overlayXOffset = AttrTo<int32_t>(options, "overlayXOffset");
|
for (int i = 0; i < compositeArrayLength; i++) {
|
||||||
baton->overlayYOffset = AttrTo<int32_t>(options, "overlayYOffset");
|
v8::Local<v8::Object> compositeObject = Nan::Get(compositeArray, i).ToLocalChecked().As<v8::Object>();
|
||||||
baton->overlayTile = AttrTo<bool>(options, "overlayTile");
|
Composite *composite = new Composite;
|
||||||
baton->overlayCutout = AttrTo<bool>(options, "overlayCutout");
|
composite->input = CreateInputDescriptor(AttrAs<v8::Object>(compositeObject, "input"), buffersToPersist);
|
||||||
|
composite->mode = static_cast<VipsBlendMode>(
|
||||||
|
vips_enum_from_nick(nullptr, VIPS_TYPE_BLEND_MODE, AttrAsStr(compositeObject, "blend").data()));
|
||||||
|
composite->gravity = AttrTo<uint32_t>(compositeObject, "gravity");
|
||||||
|
composite->left = AttrTo<int32_t>(compositeObject, "left");
|
||||||
|
composite->top = AttrTo<int32_t>(compositeObject, "top");
|
||||||
|
composite->tile = AttrTo<bool>(compositeObject, "tile");
|
||||||
|
baton->composite.push_back(composite);
|
||||||
}
|
}
|
||||||
// Resize options
|
// Resize options
|
||||||
baton->withoutEnlargement = AttrTo<bool>(options, "withoutEnlargement");
|
baton->withoutEnlargement = AttrTo<bool>(options, "withoutEnlargement");
|
||||||
|
|||||||
@@ -34,6 +34,23 @@ enum class Canvas {
|
|||||||
IGNORE_ASPECT
|
IGNORE_ASPECT
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct Composite {
|
||||||
|
sharp::InputDescriptor *input;
|
||||||
|
VipsBlendMode mode;
|
||||||
|
int gravity;
|
||||||
|
int left;
|
||||||
|
int top;
|
||||||
|
bool tile;
|
||||||
|
|
||||||
|
Composite():
|
||||||
|
input(nullptr),
|
||||||
|
mode(VIPS_BLEND_MODE_OVER),
|
||||||
|
gravity(0),
|
||||||
|
left(-1),
|
||||||
|
top(-1),
|
||||||
|
tile(false) {}
|
||||||
|
};
|
||||||
|
|
||||||
struct PipelineBaton {
|
struct PipelineBaton {
|
||||||
sharp::InputDescriptor *input;
|
sharp::InputDescriptor *input;
|
||||||
std::string iccProfilePath;
|
std::string iccProfilePath;
|
||||||
@@ -42,12 +59,7 @@ struct PipelineBaton {
|
|||||||
std::string fileOut;
|
std::string fileOut;
|
||||||
void *bufferOut;
|
void *bufferOut;
|
||||||
size_t bufferOutLength;
|
size_t bufferOutLength;
|
||||||
sharp::InputDescriptor *overlay;
|
std::vector<Composite *> composite;
|
||||||
int overlayGravity;
|
|
||||||
int overlayXOffset;
|
|
||||||
int overlayYOffset;
|
|
||||||
bool overlayTile;
|
|
||||||
bool overlayCutout;
|
|
||||||
std::vector<sharp::InputDescriptor *> joinChannelIn;
|
std::vector<sharp::InputDescriptor *> joinChannelIn;
|
||||||
int topOffsetPre;
|
int topOffsetPre;
|
||||||
int leftOffsetPre;
|
int leftOffsetPre;
|
||||||
@@ -161,12 +173,6 @@ struct PipelineBaton {
|
|||||||
input(nullptr),
|
input(nullptr),
|
||||||
limitInputPixels(0),
|
limitInputPixels(0),
|
||||||
bufferOutLength(0),
|
bufferOutLength(0),
|
||||||
overlay(nullptr),
|
|
||||||
overlayGravity(0),
|
|
||||||
overlayXOffset(-1),
|
|
||||||
overlayYOffset(-1),
|
|
||||||
overlayTile(false),
|
|
||||||
overlayCutout(false),
|
|
||||||
topOffsetPre(-1),
|
topOffsetPre(-1),
|
||||||
topOffsetPost(-1),
|
topOffsetPost(-1),
|
||||||
channels(0),
|
channels(0),
|
||||||
|
|||||||
BIN
test/fixtures/expected/composite-cutout.png
vendored
Normal file
|
After Width: | Height: | Size: 175 KiB |
BIN
test/fixtures/expected/composite-multiple.png
vendored
Normal file
|
After Width: | Height: | Size: 222 B |
BIN
test/fixtures/expected/composite.blend.dest-over.png
vendored
Normal file
|
After Width: | Height: | Size: 197 B |
BIN
test/fixtures/expected/composite.blend.over.png
vendored
Normal file
|
After Width: | Height: | Size: 197 B |
BIN
test/fixtures/expected/composite.blend.saturate.png
vendored
Normal file
|
After Width: | Height: | Size: 194 B |
BIN
test/fixtures/expected/composite.blend.xor.png
vendored
Normal file
|
After Width: | Height: | Size: 192 B |
1
test/fixtures/index.js
vendored
@@ -100,6 +100,7 @@ module.exports = {
|
|||||||
inputTiff8BitDepth: getPath('8bit_depth.tiff'),
|
inputTiff8BitDepth: getPath('8bit_depth.tiff'),
|
||||||
inputGif: getPath('Crash_test.gif'), // http://upload.wikimedia.org/wikipedia/commons/e/e3/Crash_test.gif
|
inputGif: getPath('Crash_test.gif'), // http://upload.wikimedia.org/wikipedia/commons/e/e3/Crash_test.gif
|
||||||
inputGifGreyPlusAlpha: getPath('grey-plus-alpha.gif'), // http://i.imgur.com/gZ5jlmE.gif
|
inputGifGreyPlusAlpha: getPath('grey-plus-alpha.gif'), // http://i.imgur.com/gZ5jlmE.gif
|
||||||
|
inputGifAnimated: getPath('rotating-squares.gif'), // CC0 https://loading.io/spinner/blocks/-rotating-squares-preloader-gif
|
||||||
inputSvg: getPath('check.svg'), // http://dev.w3.org/SVG/tools/svgweb/samples/svg-files/check.svg
|
inputSvg: getPath('check.svg'), // http://dev.w3.org/SVG/tools/svgweb/samples/svg-files/check.svg
|
||||||
inputSvgWithEmbeddedImages: getPath('struct-image-04-t.svg'), // https://dev.w3.org/SVG/profiles/1.2T/test/svg/struct-image-04-t.svg
|
inputSvgWithEmbeddedImages: getPath('struct-image-04-t.svg'), // https://dev.w3.org/SVG/profiles/1.2T/test/svg/struct-image-04-t.svg
|
||||||
|
|
||||||
|
|||||||
BIN
test/fixtures/rotating-squares.gif
vendored
Normal file
|
After Width: | Height: | Size: 41 KiB |
@@ -147,6 +147,47 @@
|
|||||||
...
|
...
|
||||||
fun:WebPDecode
|
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
|
# libvips
|
||||||
{
|
{
|
||||||
@@ -197,6 +238,11 @@
|
|||||||
...
|
...
|
||||||
fun:vips_region_prepare_to
|
fun:vips_region_prepare_to
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
cond_libvips_vips_stats_scan
|
||||||
|
Memcheck:Cond
|
||||||
|
fun:vips_stats_scan
|
||||||
|
}
|
||||||
{
|
{
|
||||||
value_libvips_vips_region_fill
|
value_libvips_vips_region_fill
|
||||||
Memcheck:Value8
|
Memcheck:Value8
|
||||||
@@ -204,6 +250,17 @@
|
|||||||
fun:vips_region_fill
|
fun:vips_region_fill
|
||||||
fun:vips_region_prepare
|
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
|
leak_libvips_init
|
||||||
Memcheck:Leak
|
Memcheck:Leak
|
||||||
@@ -377,6 +434,70 @@
|
|||||||
...
|
...
|
||||||
fun:_ZN4node12NodePlatformC1EiPN2v817TracingControllerE
|
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_nodejs_start
|
||||||
|
Memcheck:Leak
|
||||||
|
match-leak-kinds: definite
|
||||||
|
fun:_Znwm
|
||||||
|
fun:_ZN4node5StartEiPPc
|
||||||
|
}
|
||||||
|
{
|
||||||
|
leak_nodejs_start_background_task_runner
|
||||||
|
Memcheck:Leak
|
||||||
|
match-leak-kinds: possible
|
||||||
|
...
|
||||||
|
fun:_ZN4node20BackgroundTaskRunnerC1Ei
|
||||||
|
}
|
||||||
{
|
{
|
||||||
leak_nan_FunctionCallbackInfo
|
leak_nan_FunctionCallbackInfo
|
||||||
Memcheck:Leak
|
Memcheck:Leak
|
||||||
|
|||||||
298
test/unit/composite.js
Normal file
@@ -0,0 +1,298 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const assert = require('assert');
|
||||||
|
|
||||||
|
const fixtures = require('../fixtures');
|
||||||
|
const sharp = require('../../');
|
||||||
|
|
||||||
|
const red = { r: 255, g: 0, b: 0, alpha: 0.5 };
|
||||||
|
const green = { r: 0, g: 255, b: 0, alpha: 0.5 };
|
||||||
|
const blue = { r: 0, g: 0, b: 255, alpha: 0.5 };
|
||||||
|
|
||||||
|
const redRect = {
|
||||||
|
create: {
|
||||||
|
width: 80,
|
||||||
|
height: 60,
|
||||||
|
channels: 4,
|
||||||
|
background: red
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const greenRect = {
|
||||||
|
create: {
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
channels: 4,
|
||||||
|
background: green
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const blueRect = {
|
||||||
|
create: {
|
||||||
|
width: 60,
|
||||||
|
height: 40,
|
||||||
|
channels: 4,
|
||||||
|
background: blue
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const blends = [
|
||||||
|
'over',
|
||||||
|
'xor',
|
||||||
|
'saturate',
|
||||||
|
'dest-over'
|
||||||
|
];
|
||||||
|
|
||||||
|
// Test
|
||||||
|
describe('composite', () => {
|
||||||
|
it('blend', () => Promise.all(
|
||||||
|
blends.map(blend => {
|
||||||
|
const filename = `composite.blend.${blend}.png`;
|
||||||
|
const actual = fixtures.path(`output.${filename}`);
|
||||||
|
const expected = fixtures.expected(filename);
|
||||||
|
return sharp(redRect)
|
||||||
|
.composite([{
|
||||||
|
input: blueRect,
|
||||||
|
blend
|
||||||
|
}])
|
||||||
|
.toFile(actual)
|
||||||
|
.then(() => {
|
||||||
|
fixtures.assertMaxColourDistance(actual, expected);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
));
|
||||||
|
|
||||||
|
it('multiple', () => {
|
||||||
|
const filename = 'composite-multiple.png';
|
||||||
|
const actual = fixtures.path(`output.${filename}`);
|
||||||
|
const expected = fixtures.expected(filename);
|
||||||
|
return sharp(redRect)
|
||||||
|
.composite([{
|
||||||
|
input: blueRect,
|
||||||
|
gravity: 'northeast'
|
||||||
|
}, {
|
||||||
|
input: greenRect,
|
||||||
|
gravity: 'southwest'
|
||||||
|
}])
|
||||||
|
.toFile(actual)
|
||||||
|
.then(() => {
|
||||||
|
fixtures.assertMaxColourDistance(actual, expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('zero offset', done => {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(400)
|
||||||
|
.composite([{
|
||||||
|
input: fixtures.inputPngWithTransparency16bit,
|
||||||
|
top: 0,
|
||||||
|
left: 0
|
||||||
|
}])
|
||||||
|
.toBuffer((err, data, info) => {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(3, info.channels);
|
||||||
|
fixtures.assertSimilar(fixtures.expected('overlay-offset-0.jpg'), data, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('offset and gravity', done => {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(400)
|
||||||
|
.composite([{
|
||||||
|
input: fixtures.inputPngWithTransparency16bit,
|
||||||
|
left: 10,
|
||||||
|
top: 10,
|
||||||
|
gravity: 4
|
||||||
|
}])
|
||||||
|
.toBuffer((err, data, info) => {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(3, info.channels);
|
||||||
|
fixtures.assertSimilar(fixtures.expected('overlay-offset-with-gravity.jpg'), data, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('offset, gravity and tile', done => {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(400)
|
||||||
|
.composite([{
|
||||||
|
input: fixtures.inputPngWithTransparency16bit,
|
||||||
|
left: 10,
|
||||||
|
top: 10,
|
||||||
|
gravity: 4,
|
||||||
|
tile: true
|
||||||
|
}])
|
||||||
|
.toBuffer((err, data, info) => {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(3, info.channels);
|
||||||
|
fixtures.assertSimilar(fixtures.expected('overlay-offset-with-gravity-tile.jpg'), data, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('offset and tile', done => {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(400)
|
||||||
|
.composite([{
|
||||||
|
input: fixtures.inputPngWithTransparency16bit,
|
||||||
|
left: 10,
|
||||||
|
top: 10,
|
||||||
|
tile: true
|
||||||
|
}])
|
||||||
|
.toBuffer((err, data, info) => {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(3, info.channels);
|
||||||
|
fixtures.assertSimilar(fixtures.expected('overlay-offset-with-tile.jpg'), data, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('cutout via dest-in', done => {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(300, 300)
|
||||||
|
.composite([{
|
||||||
|
input: Buffer.from('<svg><rect x="0" y="0" width="200" height="200" rx="50" ry="50"/></svg>'),
|
||||||
|
density: 96,
|
||||||
|
blend: 'dest-in',
|
||||||
|
cutout: true
|
||||||
|
}])
|
||||||
|
.png()
|
||||||
|
.toBuffer((err, data, info) => {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('png', info.format);
|
||||||
|
assert.strictEqual(300, info.width);
|
||||||
|
assert.strictEqual(300, info.height);
|
||||||
|
assert.strictEqual(4, info.channels);
|
||||||
|
fixtures.assertSimilar(fixtures.expected('composite-cutout.png'), data, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('numeric gravity', () => {
|
||||||
|
Object.keys(sharp.gravity).forEach(gravity => {
|
||||||
|
it(gravity, done => {
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(80)
|
||||||
|
.composite([{
|
||||||
|
input: fixtures.inputPngWithTransparency16bit,
|
||||||
|
gravity: gravity
|
||||||
|
}])
|
||||||
|
.toBuffer((err, data, info) => {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(80, info.width);
|
||||||
|
assert.strictEqual(65, info.height);
|
||||||
|
assert.strictEqual(3, info.channels);
|
||||||
|
fixtures.assertSimilar(fixtures.expected(`overlay-gravity-${gravity}.jpg`), data, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('string gravity', () => {
|
||||||
|
Object.keys(sharp.gravity).forEach(gravity => {
|
||||||
|
it(gravity, done => {
|
||||||
|
const expected = fixtures.expected('overlay-gravity-' + gravity + '.jpg');
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(80)
|
||||||
|
.composite([{
|
||||||
|
input: fixtures.inputPngWithTransparency16bit,
|
||||||
|
gravity: sharp.gravity[gravity]
|
||||||
|
}])
|
||||||
|
.toBuffer((err, data, info) => {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(80, info.width);
|
||||||
|
assert.strictEqual(65, info.height);
|
||||||
|
assert.strictEqual(3, info.channels);
|
||||||
|
fixtures.assertSimilar(expected, data, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('tile and gravity', () => {
|
||||||
|
Object.keys(sharp.gravity).forEach(gravity => {
|
||||||
|
it(gravity, done => {
|
||||||
|
const expected = fixtures.expected('overlay-tile-gravity-' + gravity + '.jpg');
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(80)
|
||||||
|
.composite([{
|
||||||
|
input: fixtures.inputPngWithTransparency16bit,
|
||||||
|
tile: true,
|
||||||
|
gravity: gravity
|
||||||
|
}])
|
||||||
|
.toBuffer((err, data, info) => {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(80, info.width);
|
||||||
|
assert.strictEqual(65, info.height);
|
||||||
|
assert.strictEqual(3, info.channels);
|
||||||
|
fixtures.assertSimilar(expected, data, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('validation', () => {
|
||||||
|
it('missing images', () => {
|
||||||
|
assert.throws(() => {
|
||||||
|
sharp().composite();
|
||||||
|
}, /Expected array for images to composite but received undefined of type undefined/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('invalid images', () => {
|
||||||
|
assert.throws(() => {
|
||||||
|
sharp().composite(['invalid']);
|
||||||
|
}, /Expected object for image to composite but received invalid of type string/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('missing input', () => {
|
||||||
|
assert.throws(() => {
|
||||||
|
sharp().composite([{}]);
|
||||||
|
}, /Unsupported input/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('invalid blend', () => {
|
||||||
|
assert.throws(() => {
|
||||||
|
sharp().composite([{ input: 'test', blend: 'invalid' }]);
|
||||||
|
}, /Expected valid blend name for blend but received invalid of type string/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('invalid tile', () => {
|
||||||
|
assert.throws(() => {
|
||||||
|
sharp().composite([{ input: 'test', tile: 'invalid' }]);
|
||||||
|
}, /Expected boolean for tile but received invalid of type string/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('invalid left', () => {
|
||||||
|
assert.throws(() => {
|
||||||
|
sharp().composite([{ input: 'test', left: 0.5 }]);
|
||||||
|
}, /Expected positive integer for left but received 0.5 of type number/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('invalid top', () => {
|
||||||
|
assert.throws(() => {
|
||||||
|
sharp().composite([{ input: 'test', top: -1 }]);
|
||||||
|
}, /Expected positive integer for top but received -1 of type number/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('left but no top', () => {
|
||||||
|
assert.throws(() => {
|
||||||
|
sharp().composite([{ input: 'test', left: 1 }]);
|
||||||
|
}, /Expected both left and top to be set/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('top but no left', () => {
|
||||||
|
assert.throws(() => {
|
||||||
|
sharp().composite([{ input: 'test', top: 1 }]);
|
||||||
|
}, /Expected both left and top to be set/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('invalid gravity', () => {
|
||||||
|
assert.throws(() => {
|
||||||
|
sharp().composite([{ input: 'test', gravity: 'invalid' }]);
|
||||||
|
}, /Expected valid gravity for gravity but received invalid of type string/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const assert = require('assert');
|
|
||||||
const fixtures = require('../fixtures');
|
|
||||||
const sharp = require('../../');
|
|
||||||
|
|
||||||
describe('Deprecated background', function () {
|
|
||||||
it('Flatten to RGB orange', function (done) {
|
|
||||||
sharp(fixtures.inputPngWithTransparency)
|
|
||||||
.flatten()
|
|
||||||
.background({ r: 255, g: 102, b: 0 })
|
|
||||||
.resize(400, 300)
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(400, info.width);
|
|
||||||
assert.strictEqual(300, info.height);
|
|
||||||
fixtures.assertSimilar(fixtures.expected('flatten-orange.jpg'), data, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Flatten to CSS/hex orange', function (done) {
|
|
||||||
sharp(fixtures.inputPngWithTransparency)
|
|
||||||
.flatten()
|
|
||||||
.background('#ff6600')
|
|
||||||
.resize(400, 300)
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(400, info.width);
|
|
||||||
assert.strictEqual(300, info.height);
|
|
||||||
fixtures.assertSimilar(fixtures.expected('flatten-orange.jpg'), data, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Flatten 16-bit PNG with transparency to orange', function (done) {
|
|
||||||
const output = fixtures.path('output.flatten-rgb16-orange.jpg');
|
|
||||||
sharp(fixtures.inputPngWithTransparency16bit)
|
|
||||||
.flatten()
|
|
||||||
.background({ r: 255, g: 102, b: 0 })
|
|
||||||
.toFile(output, function (err, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, info.size > 0);
|
|
||||||
assert.strictEqual(32, info.width);
|
|
||||||
assert.strictEqual(32, info.height);
|
|
||||||
fixtures.assertMaxColourDistance(output, fixtures.expected('flatten-rgb16-orange.jpg'), 25);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Ignored for JPEG', function (done) {
|
|
||||||
sharp(fixtures.inputJpg)
|
|
||||||
.background('#ff0000')
|
|
||||||
.flatten()
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual('jpeg', info.format);
|
|
||||||
assert.strictEqual(3, info.channels);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('extend all sides equally with RGB', function (done) {
|
|
||||||
sharp(fixtures.inputJpg)
|
|
||||||
.resize(120)
|
|
||||||
.background({ r: 255, g: 0, b: 0 })
|
|
||||||
.extend(10)
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(140, info.width);
|
|
||||||
assert.strictEqual(118, info.height);
|
|
||||||
fixtures.assertSimilar(fixtures.expected('extend-equal.jpg'), data, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,279 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const assert = require('assert');
|
|
||||||
|
|
||||||
const sharp = require('../../');
|
|
||||||
const fixtures = require('../fixtures');
|
|
||||||
|
|
||||||
describe('Deprecated crop', function () {
|
|
||||||
[
|
|
||||||
{
|
|
||||||
name: 'North',
|
|
||||||
width: 320,
|
|
||||||
height: 80,
|
|
||||||
gravity: sharp.gravity.north,
|
|
||||||
fixture: 'gravity-north.jpg'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'East',
|
|
||||||
width: 80,
|
|
||||||
height: 320,
|
|
||||||
gravity: sharp.gravity.east,
|
|
||||||
fixture: 'gravity-east.jpg'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'South',
|
|
||||||
width: 320,
|
|
||||||
height: 80,
|
|
||||||
gravity: sharp.gravity.south,
|
|
||||||
fixture: 'gravity-south.jpg'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'West',
|
|
||||||
width: 80,
|
|
||||||
height: 320,
|
|
||||||
gravity: sharp.gravity.west,
|
|
||||||
fixture: 'gravity-west.jpg'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Center',
|
|
||||||
width: 320,
|
|
||||||
height: 80,
|
|
||||||
gravity: sharp.gravity.center,
|
|
||||||
fixture: 'gravity-center.jpg'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Centre',
|
|
||||||
width: 80,
|
|
||||||
height: 320,
|
|
||||||
gravity: sharp.gravity.centre,
|
|
||||||
fixture: 'gravity-centre.jpg'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Default (centre)',
|
|
||||||
width: 80,
|
|
||||||
height: 320,
|
|
||||||
gravity: undefined,
|
|
||||||
fixture: 'gravity-centre.jpg'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Northeast',
|
|
||||||
width: 320,
|
|
||||||
height: 80,
|
|
||||||
gravity: sharp.gravity.northeast,
|
|
||||||
fixture: 'gravity-north.jpg'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Northeast',
|
|
||||||
width: 80,
|
|
||||||
height: 320,
|
|
||||||
gravity: sharp.gravity.northeast,
|
|
||||||
fixture: 'gravity-east.jpg'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Southeast',
|
|
||||||
width: 320,
|
|
||||||
height: 80,
|
|
||||||
gravity: sharp.gravity.southeast,
|
|
||||||
fixture: 'gravity-south.jpg'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Southeast',
|
|
||||||
width: 80,
|
|
||||||
height: 320,
|
|
||||||
gravity: sharp.gravity.southeast,
|
|
||||||
fixture: 'gravity-east.jpg'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Southwest',
|
|
||||||
width: 320,
|
|
||||||
height: 80,
|
|
||||||
gravity: sharp.gravity.southwest,
|
|
||||||
fixture: 'gravity-south.jpg'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Southwest',
|
|
||||||
width: 80,
|
|
||||||
height: 320,
|
|
||||||
gravity: sharp.gravity.southwest,
|
|
||||||
fixture: 'gravity-west.jpg'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Northwest',
|
|
||||||
width: 320,
|
|
||||||
height: 80,
|
|
||||||
gravity: sharp.gravity.northwest,
|
|
||||||
fixture: 'gravity-north.jpg'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Northwest',
|
|
||||||
width: 80,
|
|
||||||
height: 320,
|
|
||||||
gravity: sharp.gravity.northwest,
|
|
||||||
fixture: 'gravity-west.jpg'
|
|
||||||
}
|
|
||||||
].forEach(function (settings) {
|
|
||||||
it(settings.name + ' gravity', function (done) {
|
|
||||||
sharp(fixtures.inputJpg)
|
|
||||||
.resize(settings.width, settings.height)
|
|
||||||
.crop(settings.gravity)
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(settings.width, info.width);
|
|
||||||
assert.strictEqual(settings.height, info.height);
|
|
||||||
fixtures.assertSimilar(fixtures.expected(settings.fixture), data, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Allows specifying the gravity as a string', function (done) {
|
|
||||||
sharp(fixtures.inputJpg)
|
|
||||||
.resize(80, 320)
|
|
||||||
.crop('east')
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(80, info.width);
|
|
||||||
assert.strictEqual(320, info.height);
|
|
||||||
fixtures.assertSimilar(fixtures.expected('gravity-east.jpg'), data, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Invalid values fail', function () {
|
|
||||||
assert.throws(function () {
|
|
||||||
sharp().crop(9);
|
|
||||||
}, /Expected valid crop id\/name\/strategy for crop but received 9 of type number/);
|
|
||||||
assert.throws(function () {
|
|
||||||
sharp().crop(1.1);
|
|
||||||
}, /Expected valid crop id\/name\/strategy for crop but received 1.1 of type number/);
|
|
||||||
assert.throws(function () {
|
|
||||||
sharp().crop(-1);
|
|
||||||
}, /Expected valid crop id\/name\/strategy for crop but received -1 of type number/);
|
|
||||||
assert.throws(function () {
|
|
||||||
sharp().crop('zoinks');
|
|
||||||
}, /Expected valid crop id\/name\/strategy for crop but received zoinks of type string/);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Uses default value when none specified', function () {
|
|
||||||
assert.doesNotThrow(function () {
|
|
||||||
sharp().crop();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Skip crop when post-resize dimensions are at target', function () {
|
|
||||||
return sharp(fixtures.inputJpg)
|
|
||||||
.resize(1600, 1200)
|
|
||||||
.toBuffer()
|
|
||||||
.then(function (input) {
|
|
||||||
return sharp(input)
|
|
||||||
.resize(1110)
|
|
||||||
.crop(sharp.strategy.attention)
|
|
||||||
.toBuffer({ resolveWithObject: true })
|
|
||||||
.then(function (result) {
|
|
||||||
assert.strictEqual(1110, result.info.width);
|
|
||||||
assert.strictEqual(832, result.info.height);
|
|
||||||
assert.strictEqual(undefined, result.info.cropOffsetLeft);
|
|
||||||
assert.strictEqual(undefined, result.info.cropOffsetTop);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Entropy-based strategy', function () {
|
|
||||||
it('JPEG', function (done) {
|
|
||||||
sharp(fixtures.inputJpg)
|
|
||||||
.resize(80, 320)
|
|
||||||
.crop(sharp.strategy.entropy)
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual('jpeg', info.format);
|
|
||||||
assert.strictEqual(3, info.channels);
|
|
||||||
assert.strictEqual(80, info.width);
|
|
||||||
assert.strictEqual(320, info.height);
|
|
||||||
assert.strictEqual(-117, info.cropOffsetLeft);
|
|
||||||
assert.strictEqual(0, info.cropOffsetTop);
|
|
||||||
fixtures.assertSimilar(fixtures.expected('crop-strategy-entropy.jpg'), data, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('PNG', function (done) {
|
|
||||||
sharp(fixtures.inputPngWithTransparency)
|
|
||||||
.resize(320, 80)
|
|
||||||
.crop(sharp.strategy.entropy)
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual('png', info.format);
|
|
||||||
assert.strictEqual(4, info.channels);
|
|
||||||
assert.strictEqual(320, info.width);
|
|
||||||
assert.strictEqual(80, info.height);
|
|
||||||
assert.strictEqual(0, info.cropOffsetLeft);
|
|
||||||
assert.strictEqual(-80, info.cropOffsetTop);
|
|
||||||
fixtures.assertSimilar(fixtures.expected('crop-strategy.png'), data, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('supports the strategy passed as a string', function (done) {
|
|
||||||
sharp(fixtures.inputPngWithTransparency)
|
|
||||||
.resize(320, 80)
|
|
||||||
.crop('entropy')
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual('png', info.format);
|
|
||||||
assert.strictEqual(4, info.channels);
|
|
||||||
assert.strictEqual(320, info.width);
|
|
||||||
assert.strictEqual(80, info.height);
|
|
||||||
assert.strictEqual(0, info.cropOffsetLeft);
|
|
||||||
assert.strictEqual(-80, info.cropOffsetTop);
|
|
||||||
fixtures.assertSimilar(fixtures.expected('crop-strategy.png'), data, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Attention strategy', function () {
|
|
||||||
it('JPEG', function (done) {
|
|
||||||
sharp(fixtures.inputJpg)
|
|
||||||
.resize(80, 320)
|
|
||||||
.crop(sharp.strategy.attention)
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual('jpeg', info.format);
|
|
||||||
assert.strictEqual(3, info.channels);
|
|
||||||
assert.strictEqual(80, info.width);
|
|
||||||
assert.strictEqual(320, info.height);
|
|
||||||
assert.strictEqual(-143, info.cropOffsetLeft);
|
|
||||||
assert.strictEqual(0, info.cropOffsetTop);
|
|
||||||
fixtures.assertSimilar(fixtures.expected('crop-strategy-attention.jpg'), data, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('PNG', function (done) {
|
|
||||||
sharp(fixtures.inputPngWithTransparency)
|
|
||||||
.resize(320, 80)
|
|
||||||
.crop(sharp.strategy.attention)
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual('png', info.format);
|
|
||||||
assert.strictEqual(4, info.channels);
|
|
||||||
assert.strictEqual(320, info.width);
|
|
||||||
assert.strictEqual(80, info.height);
|
|
||||||
assert.strictEqual(0, info.cropOffsetLeft);
|
|
||||||
assert.strictEqual(0, info.cropOffsetTop);
|
|
||||||
fixtures.assertSimilar(fixtures.expected('crop-strategy.png'), data, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('supports the strategy passed as a string', function (done) {
|
|
||||||
sharp(fixtures.inputPngWithTransparency)
|
|
||||||
.resize(320, 80)
|
|
||||||
.crop('attention')
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual('png', info.format);
|
|
||||||
assert.strictEqual(4, info.channels);
|
|
||||||
assert.strictEqual(320, info.width);
|
|
||||||
assert.strictEqual(80, info.height);
|
|
||||||
assert.strictEqual(0, info.cropOffsetLeft);
|
|
||||||
assert.strictEqual(0, info.cropOffsetTop);
|
|
||||||
fixtures.assertSimilar(fixtures.expected('crop-strategy.png'), data, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,440 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const assert = require('assert');
|
|
||||||
|
|
||||||
const sharp = require('../../');
|
|
||||||
const fixtures = require('../fixtures');
|
|
||||||
|
|
||||||
describe('Deprecated embed', function () {
|
|
||||||
it('Allows specifying the gravity as a string', function (done) {
|
|
||||||
sharp(fixtures.inputJpg)
|
|
||||||
.resize(320, 240)
|
|
||||||
.embed('center')
|
|
||||||
.png()
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(320, info.width);
|
|
||||||
assert.strictEqual(240, info.height);
|
|
||||||
fixtures.assertSimilar(fixtures.expected('embed-3-into-3.png'), data, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('JPEG within PNG, no alpha channel', function (done) {
|
|
||||||
sharp(fixtures.inputJpg)
|
|
||||||
.embed()
|
|
||||||
.resize(320, 240)
|
|
||||||
.png()
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('png', info.format);
|
|
||||||
assert.strictEqual(320, info.width);
|
|
||||||
assert.strictEqual(240, info.height);
|
|
||||||
assert.strictEqual(3, info.channels);
|
|
||||||
fixtures.assertSimilar(fixtures.expected('embed-3-into-3.png'), data, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
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 })
|
|
||||||
.embed()
|
|
||||||
.webp()
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('webp', info.format);
|
|
||||||
assert.strictEqual(320, info.width);
|
|
||||||
assert.strictEqual(240, info.height);
|
|
||||||
assert.strictEqual(4, info.channels);
|
|
||||||
fixtures.assertSimilar(fixtures.expected('embed-3-into-4.webp'), data, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('PNG with alpha channel', function (done) {
|
|
||||||
sharp(fixtures.inputPngWithTransparency)
|
|
||||||
.resize(50, 50)
|
|
||||||
.embed()
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('png', info.format);
|
|
||||||
assert.strictEqual(50, info.width);
|
|
||||||
assert.strictEqual(50, info.height);
|
|
||||||
assert.strictEqual(4, info.channels);
|
|
||||||
fixtures.assertSimilar(fixtures.expected('embed-4-into-4.png'), data, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('16-bit PNG with alpha channel', function (done) {
|
|
||||||
sharp(fixtures.inputPngWithTransparency16bit)
|
|
||||||
.resize(32, 16)
|
|
||||||
.embed()
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('png', info.format);
|
|
||||||
assert.strictEqual(32, info.width);
|
|
||||||
assert.strictEqual(16, info.height);
|
|
||||||
assert.strictEqual(4, info.channels);
|
|
||||||
fixtures.assertSimilar(fixtures.expected('embed-16bit.png'), data, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('16-bit PNG with alpha channel onto RGBA', function (done) {
|
|
||||||
sharp(fixtures.inputPngWithTransparency16bit)
|
|
||||||
.resize(32, 16)
|
|
||||||
.embed()
|
|
||||||
.background({ r: 0, g: 0, b: 0, alpha: 0 })
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('png', info.format);
|
|
||||||
assert.strictEqual(32, info.width);
|
|
||||||
assert.strictEqual(16, info.height);
|
|
||||||
assert.strictEqual(4, info.channels);
|
|
||||||
fixtures.assertSimilar(fixtures.expected('embed-16bit-rgba.png'), data, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('PNG with 2 channels', function (done) {
|
|
||||||
sharp(fixtures.inputPngWithGreyAlpha)
|
|
||||||
.resize(32, 16)
|
|
||||||
.embed()
|
|
||||||
.background({ r: 0, g: 0, b: 0, alpha: 0 })
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('png', info.format);
|
|
||||||
assert.strictEqual(32, info.width);
|
|
||||||
assert.strictEqual(16, info.height);
|
|
||||||
assert.strictEqual(4, info.channels);
|
|
||||||
fixtures.assertSimilar(fixtures.expected('embed-2channel.png'), data, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Enlarge and embed', function (done) {
|
|
||||||
sharp(fixtures.inputPngWithOneColor)
|
|
||||||
.embed()
|
|
||||||
.resize(320, 240)
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('png', info.format);
|
|
||||||
assert.strictEqual(320, info.width);
|
|
||||||
assert.strictEqual(240, info.height);
|
|
||||||
assert.strictEqual(3, info.channels);
|
|
||||||
fixtures.assertSimilar(fixtures.expected('embed-enlarge.png'), data, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Embed invalid param values should fail', function () {
|
|
||||||
assert.throws(function () {
|
|
||||||
sharp().embed(-1);
|
|
||||||
});
|
|
||||||
assert.throws(function () {
|
|
||||||
sharp().embed(8.1);
|
|
||||||
});
|
|
||||||
assert.throws(function () {
|
|
||||||
sharp().embed(9);
|
|
||||||
});
|
|
||||||
assert.throws(function () {
|
|
||||||
sharp().embed(1000000);
|
|
||||||
});
|
|
||||||
assert.throws(function () {
|
|
||||||
sharp().embed(false);
|
|
||||||
});
|
|
||||||
assert.throws(function () {
|
|
||||||
sharp().embed('vallejo');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Embed gravity horizontal northwest', function (done) {
|
|
||||||
sharp(fixtures.inputPngEmbed)
|
|
||||||
.resize(200, 100)
|
|
||||||
.background({ r: 0, g: 0, b: 0, alpha: 0 })
|
|
||||||
.embed(sharp.gravity.northwest)
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('png', info.format);
|
|
||||||
assert.strictEqual(200, info.width);
|
|
||||||
assert.strictEqual(100, info.height);
|
|
||||||
assert.strictEqual(4, info.channels);
|
|
||||||
fixtures.assertSimilar(fixtures.expected('./embedgravitybird/a1-nw.png'), data, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Embed gravity horizontal north', function (done) {
|
|
||||||
sharp(fixtures.inputPngEmbed)
|
|
||||||
.resize(200, 100)
|
|
||||||
.background({ r: 0, g: 0, b: 0, alpha: 0 })
|
|
||||||
.embed(sharp.gravity.north)
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('png', info.format);
|
|
||||||
assert.strictEqual(200, info.width);
|
|
||||||
assert.strictEqual(100, info.height);
|
|
||||||
assert.strictEqual(4, info.channels);
|
|
||||||
fixtures.assertSimilar(fixtures.expected('./embedgravitybird/a2-n.png'), data, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Embed gravity horizontal northeast', function (done) {
|
|
||||||
sharp(fixtures.inputPngEmbed)
|
|
||||||
.resize(200, 100)
|
|
||||||
.background({ r: 0, g: 0, b: 0, alpha: 0 })
|
|
||||||
.embed(sharp.gravity.northeast)
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('png', info.format);
|
|
||||||
assert.strictEqual(200, info.width);
|
|
||||||
assert.strictEqual(100, info.height);
|
|
||||||
assert.strictEqual(4, info.channels);
|
|
||||||
fixtures.assertSimilar(fixtures.expected('./embedgravitybird/a3-ne.png'), data, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Embed gravity horizontal east', function (done) {
|
|
||||||
sharp(fixtures.inputPngEmbed)
|
|
||||||
.resize(200, 100)
|
|
||||||
.background({ r: 0, g: 0, b: 0, alpha: 0 })
|
|
||||||
.embed(sharp.gravity.east)
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('png', info.format);
|
|
||||||
assert.strictEqual(200, info.width);
|
|
||||||
assert.strictEqual(100, info.height);
|
|
||||||
assert.strictEqual(4, info.channels);
|
|
||||||
fixtures.assertSimilar(fixtures.expected('./embedgravitybird/a4-e.png'), data, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Embed gravity horizontal southeast', function (done) {
|
|
||||||
sharp(fixtures.inputPngEmbed)
|
|
||||||
.resize(200, 100)
|
|
||||||
.background({ r: 0, g: 0, b: 0, alpha: 0 })
|
|
||||||
.embed(sharp.gravity.southeast)
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('png', info.format);
|
|
||||||
assert.strictEqual(200, info.width);
|
|
||||||
assert.strictEqual(100, info.height);
|
|
||||||
assert.strictEqual(4, info.channels);
|
|
||||||
fixtures.assertSimilar(fixtures.expected('./embedgravitybird/a5-se.png'), data, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Embed gravity horizontal south', function (done) {
|
|
||||||
sharp(fixtures.inputPngEmbed)
|
|
||||||
.resize(200, 100)
|
|
||||||
.background({ r: 0, g: 0, b: 0, alpha: 0 })
|
|
||||||
.embed(sharp.gravity.south)
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('png', info.format);
|
|
||||||
assert.strictEqual(200, info.width);
|
|
||||||
assert.strictEqual(100, info.height);
|
|
||||||
assert.strictEqual(4, info.channels);
|
|
||||||
fixtures.assertSimilar(fixtures.expected('./embedgravitybird/a6-s.png'), data, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Embed gravity horizontal southwest', function (done) {
|
|
||||||
sharp(fixtures.inputPngEmbed)
|
|
||||||
.resize(200, 100)
|
|
||||||
.background({ r: 0, g: 0, b: 0, alpha: 0 })
|
|
||||||
.embed(sharp.gravity.southwest)
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('png', info.format);
|
|
||||||
assert.strictEqual(200, info.width);
|
|
||||||
assert.strictEqual(100, info.height);
|
|
||||||
assert.strictEqual(4, info.channels);
|
|
||||||
fixtures.assertSimilar(fixtures.expected('./embedgravitybird/a7-sw.png'), data, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Embed gravity horizontal west', function (done) {
|
|
||||||
sharp(fixtures.inputPngEmbed)
|
|
||||||
.resize(200, 100)
|
|
||||||
.background({ r: 0, g: 0, b: 0, alpha: 0 })
|
|
||||||
.embed(sharp.gravity.west)
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('png', info.format);
|
|
||||||
assert.strictEqual(200, info.width);
|
|
||||||
assert.strictEqual(100, info.height);
|
|
||||||
assert.strictEqual(4, info.channels);
|
|
||||||
fixtures.assertSimilar(fixtures.expected('./embedgravitybird/a8-w.png'), data, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Embed gravity horizontal center', function (done) {
|
|
||||||
sharp(fixtures.inputPngEmbed)
|
|
||||||
.resize(200, 100)
|
|
||||||
.background({ r: 0, g: 0, b: 0, alpha: 0 })
|
|
||||||
.embed(sharp.gravity.center)
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('png', info.format);
|
|
||||||
assert.strictEqual(200, info.width);
|
|
||||||
assert.strictEqual(100, info.height);
|
|
||||||
assert.strictEqual(4, info.channels);
|
|
||||||
fixtures.assertSimilar(fixtures.expected('./embedgravitybird/a9-c.png'), data, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Embed gravity vertical northwest', function (done) {
|
|
||||||
sharp(fixtures.inputPngEmbed)
|
|
||||||
.resize(200, 200)
|
|
||||||
.background({ r: 0, g: 0, b: 0, alpha: 0 })
|
|
||||||
.embed(sharp.gravity.northwest)
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('png', info.format);
|
|
||||||
assert.strictEqual(200, info.width);
|
|
||||||
assert.strictEqual(200, info.height);
|
|
||||||
assert.strictEqual(4, info.channels);
|
|
||||||
fixtures.assertSimilar(fixtures.expected('./embedgravitybird/1-nw.png'), data, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Embed gravity vertical north', function (done) {
|
|
||||||
sharp(fixtures.inputPngEmbed)
|
|
||||||
.resize(200, 200)
|
|
||||||
.background({ r: 0, g: 0, b: 0, alpha: 0 })
|
|
||||||
.embed(sharp.gravity.north)
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('png', info.format);
|
|
||||||
assert.strictEqual(200, info.width);
|
|
||||||
assert.strictEqual(200, info.height);
|
|
||||||
assert.strictEqual(4, info.channels);
|
|
||||||
fixtures.assertSimilar(fixtures.expected('./embedgravitybird/2-n.png'), data, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Embed gravity vertical northeast', function (done) {
|
|
||||||
sharp(fixtures.inputPngEmbed)
|
|
||||||
.resize(200, 200)
|
|
||||||
.background({ r: 0, g: 0, b: 0, alpha: 0 })
|
|
||||||
.embed(sharp.gravity.northeast)
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('png', info.format);
|
|
||||||
assert.strictEqual(200, info.width);
|
|
||||||
assert.strictEqual(200, info.height);
|
|
||||||
assert.strictEqual(4, info.channels);
|
|
||||||
fixtures.assertSimilar(fixtures.expected('./embedgravitybird/3-ne.png'), data, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Embed gravity vertical east', function (done) {
|
|
||||||
sharp(fixtures.inputPngEmbed)
|
|
||||||
.resize(200, 200)
|
|
||||||
.background({ r: 0, g: 0, b: 0, alpha: 0 })
|
|
||||||
.embed(sharp.gravity.east)
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('png', info.format);
|
|
||||||
assert.strictEqual(200, info.width);
|
|
||||||
assert.strictEqual(200, info.height);
|
|
||||||
assert.strictEqual(4, info.channels);
|
|
||||||
fixtures.assertSimilar(fixtures.expected('./embedgravitybird/4-e.png'), data, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Embed gravity vertical southeast', function (done) {
|
|
||||||
sharp(fixtures.inputPngEmbed)
|
|
||||||
.resize(200, 200)
|
|
||||||
.background({ r: 0, g: 0, b: 0, alpha: 0 })
|
|
||||||
.embed(sharp.gravity.southeast)
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('png', info.format);
|
|
||||||
assert.strictEqual(200, info.width);
|
|
||||||
assert.strictEqual(200, info.height);
|
|
||||||
assert.strictEqual(4, info.channels);
|
|
||||||
fixtures.assertSimilar(fixtures.expected('./embedgravitybird/5-se.png'), data, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Embed gravity vertical south', function (done) {
|
|
||||||
sharp(fixtures.inputPngEmbed)
|
|
||||||
.resize(200, 200)
|
|
||||||
.background({ r: 0, g: 0, b: 0, alpha: 0 })
|
|
||||||
.embed(sharp.gravity.south)
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('png', info.format);
|
|
||||||
assert.strictEqual(200, info.width);
|
|
||||||
assert.strictEqual(200, info.height);
|
|
||||||
assert.strictEqual(4, info.channels);
|
|
||||||
fixtures.assertSimilar(fixtures.expected('./embedgravitybird/6-s.png'), data, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Embed gravity vertical southwest', function (done) {
|
|
||||||
sharp(fixtures.inputPngEmbed)
|
|
||||||
.resize(200, 200)
|
|
||||||
.background({ r: 0, g: 0, b: 0, alpha: 0 })
|
|
||||||
.embed(sharp.gravity.southwest)
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('png', info.format);
|
|
||||||
assert.strictEqual(200, info.width);
|
|
||||||
assert.strictEqual(200, info.height);
|
|
||||||
assert.strictEqual(4, info.channels);
|
|
||||||
fixtures.assertSimilar(fixtures.expected('./embedgravitybird/7-sw.png'), data, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Embed gravity vertical west', function (done) {
|
|
||||||
sharp(fixtures.inputPngEmbed)
|
|
||||||
.resize(200, 200)
|
|
||||||
.background({ r: 0, g: 0, b: 0, alpha: 0 })
|
|
||||||
.embed(sharp.gravity.west)
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('png', info.format);
|
|
||||||
assert.strictEqual(200, info.width);
|
|
||||||
assert.strictEqual(200, info.height);
|
|
||||||
assert.strictEqual(4, info.channels);
|
|
||||||
fixtures.assertSimilar(fixtures.expected('./embedgravitybird/8-w.png'), data, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Embed gravity vertical center', function (done) {
|
|
||||||
sharp(fixtures.inputPngEmbed)
|
|
||||||
.resize(200, 200)
|
|
||||||
.background({ r: 0, g: 0, b: 0, alpha: 0 })
|
|
||||||
.embed(sharp.gravity.center)
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('png', info.format);
|
|
||||||
assert.strictEqual(200, info.width);
|
|
||||||
assert.strictEqual(200, info.height);
|
|
||||||
assert.strictEqual(4, info.channels);
|
|
||||||
fixtures.assertSimilar(fixtures.expected('./embedgravitybird/9-c.png'), data, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,261 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const assert = require('assert');
|
|
||||||
|
|
||||||
const sharp = require('../../');
|
|
||||||
const fixtures = require('../fixtures');
|
|
||||||
|
|
||||||
describe('Deprecated resize-related functions', function () {
|
|
||||||
it('Max width or height considering ratio (portrait)', function (done) {
|
|
||||||
sharp(fixtures.inputTiff)
|
|
||||||
.resize(320, 320)
|
|
||||||
.max()
|
|
||||||
.jpeg()
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('jpeg', info.format);
|
|
||||||
assert.strictEqual(243, info.width);
|
|
||||||
assert.strictEqual(320, info.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Min width or height considering ratio (portrait)', function (done) {
|
|
||||||
sharp(fixtures.inputTiff)
|
|
||||||
.resize(320, 320)
|
|
||||||
.min()
|
|
||||||
.jpeg()
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('jpeg', info.format);
|
|
||||||
assert.strictEqual(320, info.width);
|
|
||||||
assert.strictEqual(422, info.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Max width or height considering ratio (landscape)', function (done) {
|
|
||||||
sharp(fixtures.inputJpg)
|
|
||||||
.resize(320, 320)
|
|
||||||
.max()
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('jpeg', info.format);
|
|
||||||
assert.strictEqual(320, info.width);
|
|
||||||
assert.strictEqual(261, info.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Provide only one dimension with max, should default to crop', function (done) {
|
|
||||||
sharp(fixtures.inputJpg)
|
|
||||||
.resize(320)
|
|
||||||
.max()
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('jpeg', info.format);
|
|
||||||
assert.strictEqual(320, info.width);
|
|
||||||
assert.strictEqual(261, info.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Min width or height considering ratio (landscape)', function (done) {
|
|
||||||
sharp(fixtures.inputJpg)
|
|
||||||
.resize(320, 320)
|
|
||||||
.min()
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('jpeg', info.format);
|
|
||||||
assert.strictEqual(392, info.width);
|
|
||||||
assert.strictEqual(320, info.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Provide only one dimension with min, should default to crop', function (done) {
|
|
||||||
sharp(fixtures.inputJpg)
|
|
||||||
.resize(320)
|
|
||||||
.min()
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('jpeg', info.format);
|
|
||||||
assert.strictEqual(320, info.width);
|
|
||||||
assert.strictEqual(261, info.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Do not enlarge when input width is already less than output width', function (done) {
|
|
||||||
sharp(fixtures.inputJpg)
|
|
||||||
.resize(2800)
|
|
||||||
.withoutEnlargement()
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('jpeg', info.format);
|
|
||||||
assert.strictEqual(2725, info.width);
|
|
||||||
assert.strictEqual(2225, info.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Do not enlarge when input height is already less than output height', function (done) {
|
|
||||||
sharp(fixtures.inputJpg)
|
|
||||||
.resize(null, 2300)
|
|
||||||
.withoutEnlargement()
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('jpeg', info.format);
|
|
||||||
assert.strictEqual(2725, info.width);
|
|
||||||
assert.strictEqual(2225, info.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Do enlarge when input width is less than output width', function (done) {
|
|
||||||
sharp(fixtures.inputJpg)
|
|
||||||
.resize(2800)
|
|
||||||
.withoutEnlargement(false)
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('jpeg', info.format);
|
|
||||||
assert.strictEqual(2800, info.width);
|
|
||||||
assert.strictEqual(2286, info.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Downscale width and height, ignoring aspect ratio', function (done) {
|
|
||||||
sharp(fixtures.inputJpg)
|
|
||||||
.resize(320, 320)
|
|
||||||
.ignoreAspectRatio()
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('jpeg', info.format);
|
|
||||||
assert.strictEqual(320, info.width);
|
|
||||||
assert.strictEqual(320, info.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Downscale width, ignoring aspect ratio', function (done) {
|
|
||||||
sharp(fixtures.inputJpg)
|
|
||||||
.resize(320)
|
|
||||||
.ignoreAspectRatio()
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('jpeg', info.format);
|
|
||||||
assert.strictEqual(320, info.width);
|
|
||||||
assert.strictEqual(2225, info.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Downscale height, ignoring aspect ratio', function (done) {
|
|
||||||
sharp(fixtures.inputJpg)
|
|
||||||
.resize(null, 320)
|
|
||||||
.ignoreAspectRatio()
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('jpeg', info.format);
|
|
||||||
assert.strictEqual(2725, info.width);
|
|
||||||
assert.strictEqual(320, info.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Upscale width and height, ignoring aspect ratio', function (done) {
|
|
||||||
sharp(fixtures.inputJpg)
|
|
||||||
.resize(3000, 3000)
|
|
||||||
.ignoreAspectRatio()
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('jpeg', info.format);
|
|
||||||
assert.strictEqual(3000, info.width);
|
|
||||||
assert.strictEqual(3000, info.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Upscale width, ignoring aspect ratio', function (done) {
|
|
||||||
sharp(fixtures.inputJpg)
|
|
||||||
.resize(3000)
|
|
||||||
.ignoreAspectRatio()
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('jpeg', info.format);
|
|
||||||
assert.strictEqual(3000, info.width);
|
|
||||||
assert.strictEqual(2225, info.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Upscale height, ignoring aspect ratio', function (done) {
|
|
||||||
sharp(fixtures.inputJpg)
|
|
||||||
.resize(null, 3000)
|
|
||||||
.ignoreAspectRatio()
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('jpeg', info.format);
|
|
||||||
assert.strictEqual(2725, info.width);
|
|
||||||
assert.strictEqual(3000, info.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Downscale width, upscale height, ignoring aspect ratio', function (done) {
|
|
||||||
sharp(fixtures.inputJpg)
|
|
||||||
.resize(320, 3000)
|
|
||||||
.ignoreAspectRatio()
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('jpeg', info.format);
|
|
||||||
assert.strictEqual(320, info.width);
|
|
||||||
assert.strictEqual(3000, info.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Upscale width, downscale height, ignoring aspect ratio', function (done) {
|
|
||||||
sharp(fixtures.inputJpg)
|
|
||||||
.resize(3000, 320)
|
|
||||||
.ignoreAspectRatio()
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('jpeg', info.format);
|
|
||||||
assert.strictEqual(3000, info.width);
|
|
||||||
assert.strictEqual(320, info.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Identity transform, ignoring aspect ratio', function (done) {
|
|
||||||
sharp(fixtures.inputJpg)
|
|
||||||
.ignoreAspectRatio()
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual('jpeg', info.format);
|
|
||||||
assert.strictEqual(2725, info.width);
|
|
||||||
assert.strictEqual(2225, info.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -6,10 +6,9 @@ const sharp = require('../../');
|
|||||||
const fixtures = require('../fixtures');
|
const fixtures = require('../fixtures');
|
||||||
|
|
||||||
describe('failOnError', function () {
|
describe('failOnError', function () {
|
||||||
it('handles truncated JPEG by default', function (done) {
|
it('handles truncated JPEG', function (done) {
|
||||||
sharp(fixtures.inputJpgTruncated)
|
sharp(fixtures.inputJpgTruncated, { failOnError: false })
|
||||||
.resize(320, 240)
|
.resize(320, 240)
|
||||||
// .toFile(fixtures.expected('truncated.jpg'), done);
|
|
||||||
.toBuffer(function (err, data, info) {
|
.toBuffer(function (err, data, info) {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
assert.strictEqual('jpeg', info.format);
|
assert.strictEqual('jpeg', info.format);
|
||||||
@@ -19,10 +18,9 @@ describe('failOnError', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles truncated PNG by default', function (done) {
|
it('handles truncated PNG', function (done) {
|
||||||
sharp(fixtures.inputPngTruncated)
|
sharp(fixtures.inputPngTruncated, { failOnError: false })
|
||||||
.resize(320, 240)
|
.resize(320, 240)
|
||||||
// .toFile(fixtures.expected('truncated.png'), done);
|
|
||||||
.toBuffer(function (err, data, info) {
|
.toBuffer(function (err, data, info) {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
assert.strictEqual('png', info.format);
|
assert.strictEqual('png', info.format);
|
||||||
@@ -46,8 +44,8 @@ describe('failOnError', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns errors to callback for truncated JPEG when failOnError is set', function (done) {
|
it('returns errors to callback for truncated JPEG', function (done) {
|
||||||
sharp(fixtures.inputJpgTruncated, { failOnError: true }).toBuffer(function (err, data, info) {
|
sharp(fixtures.inputJpgTruncated).toBuffer(function (err, data, info) {
|
||||||
assert.ok(err.message.includes('VipsJpeg: Premature end of JPEG file'), err);
|
assert.ok(err.message.includes('VipsJpeg: Premature end of JPEG file'), err);
|
||||||
assert.strictEqual(data, null);
|
assert.strictEqual(data, null);
|
||||||
assert.strictEqual(info, null);
|
assert.strictEqual(info, null);
|
||||||
@@ -55,8 +53,8 @@ describe('failOnError', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns errors to callback for truncated PNG when failOnError is set', function (done) {
|
it('returns errors to callback for truncated PNG', function (done) {
|
||||||
sharp(fixtures.inputPngTruncated, { failOnError: true }).toBuffer(function (err, data, info) {
|
sharp(fixtures.inputPngTruncated).toBuffer(function (err, data, info) {
|
||||||
assert.ok(err.message.includes('vipspng: libpng read error'), err);
|
assert.ok(err.message.includes('vipspng: libpng read error'), err);
|
||||||
assert.strictEqual(data, null);
|
assert.strictEqual(data, null);
|
||||||
assert.strictEqual(info, null);
|
assert.strictEqual(info, null);
|
||||||
@@ -64,8 +62,8 @@ describe('failOnError', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('rejects promises for truncated JPEG when failOnError is set', function (done) {
|
it('rejects promises for truncated JPEG', function (done) {
|
||||||
sharp(fixtures.inputJpgTruncated, { failOnError: true })
|
sharp(fixtures.inputJpgTruncated)
|
||||||
.toBuffer()
|
.toBuffer()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
throw new Error('Expected rejection');
|
throw new Error('Expected rejection');
|
||||||
|
|||||||
64
test/unit/gif.js
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const assert = require('assert');
|
||||||
|
|
||||||
|
const sharp = require('../../');
|
||||||
|
const fixtures = require('../fixtures');
|
||||||
|
|
||||||
|
describe('GIF input', () => {
|
||||||
|
it('GIF Buffer to JPEG Buffer', () =>
|
||||||
|
sharp(fs.readFileSync(fixtures.inputGif))
|
||||||
|
.resize(8, 4)
|
||||||
|
.jpeg()
|
||||||
|
.toBuffer({ resolveWithObject: true })
|
||||||
|
.then(({ data, info }) => {
|
||||||
|
assert.strictEqual(true, data.length > 0);
|
||||||
|
assert.strictEqual(data.length, info.size);
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(8, info.width);
|
||||||
|
assert.strictEqual(4, info.height);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
it('2 channel GIF file to PNG Buffer', () =>
|
||||||
|
sharp(fixtures.inputGifGreyPlusAlpha)
|
||||||
|
.resize(8, 4)
|
||||||
|
.png()
|
||||||
|
.toBuffer({ resolveWithObject: true })
|
||||||
|
.then(({ data, info }) => {
|
||||||
|
assert.strictEqual(true, data.length > 0);
|
||||||
|
assert.strictEqual(data.length, info.size);
|
||||||
|
assert.strictEqual('png', info.format);
|
||||||
|
assert.strictEqual(8, info.width);
|
||||||
|
assert.strictEqual(4, info.height);
|
||||||
|
assert.strictEqual(4, info.channels);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
it('Animated GIF first page to PNG', () =>
|
||||||
|
sharp(fixtures.inputGifAnimated)
|
||||||
|
.toBuffer({ resolveWithObject: true })
|
||||||
|
.then(({ data, info }) => {
|
||||||
|
assert.strictEqual(true, data.length > 0);
|
||||||
|
assert.strictEqual(data.length, info.size);
|
||||||
|
assert.strictEqual('png', info.format);
|
||||||
|
assert.strictEqual(80, info.width);
|
||||||
|
assert.strictEqual(80, info.height);
|
||||||
|
assert.strictEqual(4, info.channels);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
it('Animated GIF all pages to PNG "toilet roll"', () =>
|
||||||
|
sharp(fixtures.inputGifAnimated, { pages: -1 })
|
||||||
|
.toBuffer({ resolveWithObject: true })
|
||||||
|
.then(({ data, info }) => {
|
||||||
|
assert.strictEqual(true, data.length > 0);
|
||||||
|
assert.strictEqual(data.length, info.size);
|
||||||
|
assert.strictEqual('png', info.format);
|
||||||
|
assert.strictEqual(80, info.width);
|
||||||
|
assert.strictEqual(2400, info.height);
|
||||||
|
assert.strictEqual(4, info.channels);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -263,7 +263,8 @@ describe('Input/output', function () {
|
|||||||
|
|
||||||
it('Fail when output File is input File', function (done) {
|
it('Fail when output File is input File', function (done) {
|
||||||
sharp(fixtures.inputJpg).toFile(fixtures.inputJpg, function (err) {
|
sharp(fixtures.inputJpg).toFile(fixtures.inputJpg, function (err) {
|
||||||
assert(!!err);
|
assert(err instanceof Error);
|
||||||
|
assert.strictEqual('Cannot use same file for input and output', err.message);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -273,14 +274,16 @@ describe('Input/output', function () {
|
|||||||
assert(false);
|
assert(false);
|
||||||
done();
|
done();
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
assert(!!err);
|
assert(err instanceof Error);
|
||||||
|
assert.strictEqual('Cannot use same file for input and output', err.message);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Fail when output File is empty', function (done) {
|
it('Fail when output File is empty', function (done) {
|
||||||
sharp(fixtures.inputJpg).toFile('', function (err) {
|
sharp(fixtures.inputJpg).toFile('', function (err) {
|
||||||
assert(!!err);
|
assert(err instanceof Error);
|
||||||
|
assert.strictEqual('Missing output file path', err.message);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -290,7 +293,8 @@ describe('Input/output', function () {
|
|||||||
assert(false);
|
assert(false);
|
||||||
done();
|
done();
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
assert(!!err);
|
assert(err instanceof Error);
|
||||||
|
assert.strictEqual('Missing output file path', err.message);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -301,6 +305,7 @@ describe('Input/output', function () {
|
|||||||
done();
|
done();
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
assert(err instanceof Error);
|
assert(err instanceof Error);
|
||||||
|
assert.strictEqual('Input buffer contains unsupported image format', err.message);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -311,6 +316,18 @@ describe('Input/output', function () {
|
|||||||
done();
|
done();
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
assert(err instanceof Error);
|
assert(err instanceof Error);
|
||||||
|
assert.strictEqual('Input buffer contains unsupported image format', err.message);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Fail when input file path is missing', function (done) {
|
||||||
|
sharp('does-not-exist').toBuffer().then(function () {
|
||||||
|
assert(false);
|
||||||
|
done();
|
||||||
|
}).catch(function (err) {
|
||||||
|
assert(err instanceof Error);
|
||||||
|
assert.strictEqual('Input file is missing', err.message);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -500,37 +517,6 @@ describe('Input/output', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Load GIF from Buffer', function (done) {
|
|
||||||
const inputGifBuffer = fs.readFileSync(fixtures.inputGif);
|
|
||||||
sharp(inputGifBuffer)
|
|
||||||
.resize(320, 240)
|
|
||||||
.jpeg()
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual(data.length, info.size);
|
|
||||||
assert.strictEqual('jpeg', info.format);
|
|
||||||
assert.strictEqual(320, info.width);
|
|
||||||
assert.strictEqual(240, info.height);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Load GIF grey+alpha from file, auto convert to PNG', function (done) {
|
|
||||||
sharp(fixtures.inputGifGreyPlusAlpha)
|
|
||||||
.resize(8, 4)
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(true, data.length > 0);
|
|
||||||
assert.strictEqual(data.length, info.size);
|
|
||||||
assert.strictEqual('png', info.format);
|
|
||||||
assert.strictEqual(8, info.width);
|
|
||||||
assert.strictEqual(4, info.height);
|
|
||||||
assert.strictEqual(4, info.channels);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Load Vips V file', function (done) {
|
it('Load Vips V file', function (done) {
|
||||||
sharp(fixtures.inputV)
|
sharp(fixtures.inputV)
|
||||||
.jpeg()
|
.jpeg()
|
||||||
|
|||||||
@@ -140,37 +140,35 @@ describe('Overlays', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
if (sharp.format.webp.input.file) {
|
it('Composite WebP onto JPEG', function (done) {
|
||||||
it('Composite WebP onto JPEG', function (done) {
|
const paths = getPaths('overlay-jpeg-with-webp', 'jpg');
|
||||||
const paths = getPaths('overlay-jpeg-with-webp', 'jpg');
|
|
||||||
|
|
||||||
sharp(fixtures.inputJpg)
|
sharp(fixtures.inputJpg)
|
||||||
.resize(300, 300)
|
.resize(300, 300)
|
||||||
.overlayWith(fixtures.inputWebPWithTransparency)
|
.overlayWith(fixtures.inputWebPWithTransparency)
|
||||||
.toFile(paths.actual, function (error, info) {
|
.toFile(paths.actual, function (error, info) {
|
||||||
if (error) return done(error);
|
if (error) return done(error);
|
||||||
fixtures.assertMaxColourDistance(paths.actual, paths.expected, 102);
|
fixtures.assertMaxColourDistance(paths.actual, paths.expected, 102);
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
it('Composite JPEG onto PNG, no premultiply', function (done) {
|
|
||||||
sharp(fixtures.inputPngOverlayLayer1)
|
|
||||||
.overlayWith(fixtures.inputJpgWithLandscapeExif1)
|
|
||||||
.toBuffer(function (err, data, info) {
|
|
||||||
if (err) throw err;
|
|
||||||
assert.strictEqual(false, info.premultiplied);
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Composite opaque JPEG onto JPEG, no premultiply', function (done) {
|
it('Composite JPEG onto PNG, ensure premultiply', function (done) {
|
||||||
|
sharp(fixtures.inputPngOverlayLayer1)
|
||||||
|
.overlayWith(fixtures.inputJpgWithLandscapeExif1)
|
||||||
|
.toBuffer(function (err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, info.premultiplied);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Composite opaque JPEG onto JPEG, ensure premultiply', function (done) {
|
||||||
sharp(fixtures.inputJpg)
|
sharp(fixtures.inputJpg)
|
||||||
.overlayWith(fixtures.inputJpgWithLandscapeExif1)
|
.overlayWith(fixtures.inputJpgWithLandscapeExif1)
|
||||||
.toBuffer(function (err, data, info) {
|
.toBuffer(function (err, data, info) {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
assert.strictEqual(false, info.premultiplied);
|
assert.strictEqual(true, info.premultiplied);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -409,12 +407,6 @@ describe('Overlays', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Overlay with invalid cutout option', function () {
|
|
||||||
assert.throws(function () {
|
|
||||||
sharp().overlayWith('ignore', { cutout: 1 });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Overlay with invalid tile option', function () {
|
it('Overlay with invalid tile option', function () {
|
||||||
assert.throws(function () {
|
assert.throws(function () {
|
||||||
sharp().overlayWith('ignore', { tile: 1 });
|
sharp().overlayWith('ignore', { tile: 1 });
|
||||||
@@ -580,18 +572,17 @@ describe('Overlays', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Composite JPEG onto JPEG, no premultiply', function (done) {
|
it('Composite JPEG onto JPEG', function (done) {
|
||||||
sharp(fixtures.inputJpg)
|
sharp(fixtures.inputJpg)
|
||||||
.resize(480, 320)
|
.resize(480, 320)
|
||||||
.overlayWith(fixtures.inputJpgBooleanTest)
|
.overlayWith(fixtures.inputJpgBooleanTest)
|
||||||
.png()
|
|
||||||
.toBuffer(function (err, data, info) {
|
.toBuffer(function (err, data, info) {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
assert.strictEqual('png', info.format);
|
assert.strictEqual('jpeg', info.format);
|
||||||
assert.strictEqual(480, info.width);
|
assert.strictEqual(480, info.width);
|
||||||
assert.strictEqual(320, info.height);
|
assert.strictEqual(320, info.height);
|
||||||
assert.strictEqual(3, info.channels);
|
assert.strictEqual(3, info.channels);
|
||||||
assert.strictEqual(false, info.premultiplied);
|
assert.strictEqual(true, info.premultiplied);
|
||||||
fixtures.assertSimilar(fixtures.expected('overlay-jpeg-with-jpeg.jpg'), data, done);
|
fixtures.assertSimilar(fixtures.expected('overlay-jpeg-with-jpeg.jpg'), data, done);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -16,16 +16,24 @@ describe('Platform-detection', function () {
|
|||||||
delete process.env.npm_config_platform;
|
delete process.env.npm_config_platform;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Can override ARM version via npm_config_armv', function () {
|
it('Can override ARM version via --arm-version', function () {
|
||||||
process.env.npm_config_arch = 'arm';
|
process.env.npm_config_arch = 'arm';
|
||||||
process.env.npm_config_armv = 'test';
|
process.env.npm_config_arm_version = 'test';
|
||||||
assert.strictEqual('armvtest', platform().split('-')[1]);
|
assert.strictEqual('armvtest', platform().split('-')[1]);
|
||||||
delete process.env.npm_config_armv;
|
delete process.env.npm_config_arm_version;
|
||||||
|
delete process.env.npm_config_arch;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Can override ARM64 version via --arm-version', function () {
|
||||||
|
process.env.npm_config_arch = 'arm64';
|
||||||
|
process.env.npm_config_arm_version = 'test';
|
||||||
|
assert.strictEqual('arm64vtest', platform().split('-')[1]);
|
||||||
|
delete process.env.npm_config_arm_version;
|
||||||
delete process.env.npm_config_arch;
|
delete process.env.npm_config_arch;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Can detect ARM version via process.config', function () {
|
it('Can detect ARM version via process.config', function () {
|
||||||
process.env.npm_config_arch = 'armhf';
|
process.env.npm_config_arch = 'arm';
|
||||||
const armVersion = process.config.variables.arm_version;
|
const armVersion = process.config.variables.arm_version;
|
||||||
process.config.variables.arm_version = 'test';
|
process.config.variables.arm_version = 'test';
|
||||||
assert.strictEqual('armvtest', platform().split('-')[1]);
|
assert.strictEqual('armvtest', platform().split('-')[1]);
|
||||||
@@ -41,7 +49,7 @@ describe('Platform-detection', function () {
|
|||||||
|
|
||||||
it('Defaults to ARMv8 for 64-bit', function () {
|
it('Defaults to ARMv8 for 64-bit', function () {
|
||||||
process.env.npm_config_arch = 'arm64';
|
process.env.npm_config_arch = 'arm64';
|
||||||
assert.strictEqual('armv8', platform().split('-')[1]);
|
assert.strictEqual('arm64v8', platform().split('-')[1]);
|
||||||
delete process.env.npm_config_arch;
|
delete process.env.npm_config_arch;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -92,6 +92,31 @@ describe('Raw pixel data', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('JPEG to raw Stream and back again', function (done) {
|
||||||
|
const width = 32;
|
||||||
|
const height = 24;
|
||||||
|
const writable = sharp({
|
||||||
|
raw: {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
channels: 3
|
||||||
|
}
|
||||||
|
});
|
||||||
|
writable
|
||||||
|
.jpeg()
|
||||||
|
.toBuffer(function (err, data, info) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual('jpeg', info.format);
|
||||||
|
assert.strictEqual(32, info.width);
|
||||||
|
assert.strictEqual(24, info.height);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.resize(width, height)
|
||||||
|
.raw()
|
||||||
|
.pipe(writable);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Ouput raw, uncompressed image data', function () {
|
describe('Ouput raw, uncompressed image data', function () {
|
||||||
|
|||||||