Compare commits
22 Commits
v0.28.0-al
...
v0.28.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7555378e3b | ||
|
|
80c95ee66a | ||
|
|
31563b210d | ||
|
|
861cd93324 | ||
|
|
abb344bb1a | ||
|
|
6147491d9e | ||
|
|
f1f18fbb4a | ||
|
|
9fc611f257 | ||
|
|
34a2e14a14 | ||
|
|
83fe65b9e9 | ||
|
|
ec26c8aa49 | ||
|
|
da43a3055f | ||
|
|
a38126c82f | ||
|
|
cb592ce588 | ||
|
|
d69c58a6da | ||
|
|
bdb1986e08 | ||
|
|
55356c78a8 | ||
|
|
a0f55252b1 | ||
|
|
013f5cffa9 | ||
|
|
d5d008f568 | ||
|
|
3b02134cdc | ||
|
|
a57d7b51b1 |
27
README.md
@@ -19,6 +19,14 @@ rotation, extraction, compositing and gamma correction are available.
|
||||
Most modern macOS, Windows and Linux systems running Node.js v10+
|
||||
do not require any additional install or runtime dependencies.
|
||||
|
||||
## Documentation
|
||||
|
||||
Visit [sharp.pixelplumbing.com](https://sharp.pixelplumbing.com/) for complete
|
||||
[installation instructions](https://sharp.pixelplumbing.com/install),
|
||||
[API documentation](https://sharp.pixelplumbing.com/api-constructor),
|
||||
[benchmark tests](https://sharp.pixelplumbing.com/performance) and
|
||||
[changelog](https://sharp.pixelplumbing.com/changelog).
|
||||
|
||||
## Examples
|
||||
|
||||
```sh
|
||||
@@ -43,6 +51,7 @@ sharp(inputBuffer)
|
||||
sharp('input.jpg')
|
||||
.rotate()
|
||||
.resize(200)
|
||||
.jpeg({ mozjpeg: true })
|
||||
.toBuffer()
|
||||
.then( data => { ... })
|
||||
.catch( err => { ... });
|
||||
@@ -84,23 +93,15 @@ readableStream
|
||||
.pipe(writableStream);
|
||||
```
|
||||
|
||||
[](https://coveralls.io/r/lovell/sharp?branch=master)
|
||||
[](https://nodejs.org/dist/latest/docs/api/n-api.html#n_api_n_api_version_matrix)
|
||||
|
||||
### Documentation
|
||||
|
||||
Visit [sharp.pixelplumbing.com](https://sharp.pixelplumbing.com/) for complete
|
||||
[installation instructions](https://sharp.pixelplumbing.com/install),
|
||||
[API documentation](https://sharp.pixelplumbing.com/api-constructor),
|
||||
[benchmark tests](https://sharp.pixelplumbing.com/performance) and
|
||||
[changelog](https://sharp.pixelplumbing.com/changelog).
|
||||
|
||||
### Contributing
|
||||
## Contributing
|
||||
|
||||
A [guide for contributors](https://github.com/lovell/sharp/blob/master/.github/CONTRIBUTING.md)
|
||||
covers reporting bugs, requesting features and submitting code changes.
|
||||
|
||||
### Licensing
|
||||
[](https://coveralls.io/r/lovell/sharp?branch=master)
|
||||
[](https://nodejs.org/dist/latest/docs/api/n-api.html#n_api_n_api_version_matrix)
|
||||
|
||||
## Licensing
|
||||
|
||||
Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Lovell Fuller and contributors.
|
||||
|
||||
|
||||
@@ -50,6 +50,10 @@ no child processes are spawned and Promises/async/await are supported.
|
||||
|
||||
### Optimal
|
||||
|
||||
The features of `mozjpeg` and `pngquant` can be used
|
||||
to optimise the file size of JPEG and PNG images respectively,
|
||||
without having to invoke separate `imagemin` processes.
|
||||
|
||||
Huffman tables are optimised when generating JPEG output images
|
||||
without having to use separate command line tools like
|
||||
[jpegoptim](https://github.com/tjko/jpegoptim) and
|
||||
|
||||
@@ -169,13 +169,21 @@ Returns **Sharp**
|
||||
|
||||
## flatten
|
||||
|
||||
Merge alpha transparency channel, if any, with a background.
|
||||
Merge alpha transparency channel, if any, with a background, then remove the alpha channel.
|
||||
|
||||
### Parameters
|
||||
|
||||
- `options` **[Object][2]?**
|
||||
- `options.background` **([string][3] \| [Object][2])** background colour, parsed by the [color][4] module, defaults to black. (optional, default `{r:0,g:0,b:0}`)
|
||||
|
||||
### Examples
|
||||
|
||||
```javascript
|
||||
await sharp(rgbaInput)
|
||||
.flatten('#F0A703')
|
||||
.toBuffer();
|
||||
```
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
## gamma
|
||||
@@ -263,7 +271,7 @@ Returns **Sharp**
|
||||
|
||||
## threshold
|
||||
|
||||
Any pixel value greather than or equal to the threshold value will be set to 255, otherwise it will be set to 0.
|
||||
Any pixel value greater than or equal to the threshold value will be set to 255, otherwise it will be set to 0.
|
||||
|
||||
### Parameters
|
||||
|
||||
|
||||
@@ -87,10 +87,10 @@ sharp(input)
|
||||
```
|
||||
|
||||
```javascript
|
||||
const data = await sharp('my-image.jpg')
|
||||
const { data, info } = await sharp('my-image.jpg')
|
||||
// output the raw pixels
|
||||
.raw()
|
||||
.toBuffer();
|
||||
.toBuffer({ resolveWithObject: true });
|
||||
|
||||
// create a more type safe way to work with the raw pixel data
|
||||
// this will not copy the data, instead it will change `data`s underlying ArrayBuffer
|
||||
@@ -98,7 +98,9 @@ const data = await sharp('my-image.jpg')
|
||||
const pixelArray = new Uint8ClampedArray(data.buffer);
|
||||
|
||||
// When you are done changing the pixelArray, sharp takes the `pixelArray` as an input
|
||||
await sharp(pixelArray).toFile('my-changed-image.jpg');
|
||||
const { width, height, channels } = info;
|
||||
await sharp(pixelArray, { raw: { width, height, channels } })
|
||||
.toFile('my-changed-image.jpg');
|
||||
```
|
||||
|
||||
Returns **[Promise][5]<[Buffer][8]>** when no callback is provided
|
||||
@@ -423,6 +425,7 @@ Warning: multiple sharp instances concurrently producing tile output can expose
|
||||
- `options.layout` **[string][2]** filesystem layout, possible values are `dz`, `iiif`, `zoomify` or `google`. (optional, default `'dz'`)
|
||||
- `options.centre` **[boolean][7]** centre image in tile. (optional, default `false`)
|
||||
- `options.center` **[boolean][7]** alternative spelling of centre. (optional, default `false`)
|
||||
- `options.id` **[string][2]** when `layout` is `iiif`, sets the `@id` attribute of `info.json` (optional, default `'https://example.com/iiif'`)
|
||||
|
||||
### Examples
|
||||
|
||||
|
||||
@@ -137,10 +137,10 @@ This operation will always occur after resizing and extraction, if any.
|
||||
### Parameters
|
||||
|
||||
- `extend` **([number][8] \| [Object][9])** single pixel count to add to all edges or an Object with per-edge counts
|
||||
- `extend.top` **[number][8]?**
|
||||
- `extend.left` **[number][8]?**
|
||||
- `extend.bottom` **[number][8]?**
|
||||
- `extend.right` **[number][8]?**
|
||||
- `extend.top` **[number][8]** (optional, default `0`)
|
||||
- `extend.left` **[number][8]** (optional, default `0`)
|
||||
- `extend.bottom` **[number][8]** (optional, default `0`)
|
||||
- `extend.right` **[number][8]** (optional, default `0`)
|
||||
- `extend.background` **([String][10] \| [Object][9])** background colour, parsed by the [color][11] module, defaults to black without transparency. (optional, default `{r:0,g:0,b:0,alpha:1}`)
|
||||
|
||||
### Examples
|
||||
@@ -160,6 +160,16 @@ sharp(input)
|
||||
...
|
||||
```
|
||||
|
||||
```javascript
|
||||
// Add a row of 10 red pixels to the bottom
|
||||
sharp(input)
|
||||
.extend({
|
||||
bottom: 10,
|
||||
background: 'red'
|
||||
})
|
||||
...
|
||||
```
|
||||
|
||||
- Throws **[Error][13]** Invalid parameters
|
||||
|
||||
Returns **Sharp**
|
||||
|
||||
@@ -84,8 +84,12 @@ Returns **[Object][1]**
|
||||
|
||||
Gets or, when a concurrency is provided, sets
|
||||
the number of threads _libvips'_ should create to process each image.
|
||||
The default value is the number of CPU cores.
|
||||
A value of `0` will reset to this default.
|
||||
|
||||
The default value is the number of CPU cores,
|
||||
except when using glibc-based Linux without jemalloc,
|
||||
where the default is `1` to help reduce memory fragmentation.
|
||||
|
||||
A value of `0` will reset this to the number of CPU cores.
|
||||
|
||||
The maximum number of images that can be processed in parallel
|
||||
is limited by libuv's `UV_THREADPOOL_SIZE` environment variable.
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
Requires libvips v8.10.6
|
||||
|
||||
### v0.28.0 - TBD
|
||||
### v0.28.0 - 29th March 2021
|
||||
|
||||
* Prebuilt binaries now include mozjpeg and libimagequant (BSD 2-Clause).
|
||||
|
||||
@@ -16,10 +16,24 @@ Requires libvips v8.10.6
|
||||
|
||||
* Reduce concurrency on glibc-based Linux when using the default memory allocator to help prevent fragmentation.
|
||||
|
||||
* Default missing edge properties of extend operation to zero.
|
||||
[#2578](https://github.com/lovell/sharp/issues/2578)
|
||||
|
||||
* Ensure composite does not clip top and left offsets.
|
||||
[#2594](https://github.com/lovell/sharp/pull/2594)
|
||||
[@SHG42](https://github.com/SHG42)
|
||||
|
||||
* Improve error handling of network failure at install time.
|
||||
[#2608](https://github.com/lovell/sharp/pull/2608)
|
||||
[@abradley](https://github.com/abradley)
|
||||
|
||||
* Ensure `@id` attribute can be set for IIIF tile-based output.
|
||||
[#2612](https://github.com/lovell/sharp/issues/2612)
|
||||
[@edsilv](https://github.com/edsilv)
|
||||
|
||||
* Ensure composite replicates the correct number of tiles for centred gravities.
|
||||
[#2626](https://github.com/lovell/sharp/issues/2626)
|
||||
|
||||
## v0.27 - *avif*
|
||||
|
||||
Requires libvips v8.10.5
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<link rel="apple-touch-icon-precomposed" sizes="72x72" href="https://pixel.plumbing/px/72x72/sharp-logo.svg">
|
||||
<link rel="apple-touch-icon-precomposed" href="https://pixel.plumbing/px/57x57/sharp-logo.svg">
|
||||
<link rel="author" href="/humans.txt" type="text/plain">
|
||||
<link rel="preload" href="https://cdn.jsdelivr.net/gh/lovell/sharp@v0.27.2/docs/README.md" as="fetch" type="text/markdown" crossorigin>
|
||||
<link rel="preload" href="https://cdn.jsdelivr.net/gh/lovell/sharp@v0.28.0/docs/README.md" as="fetch" type="text/markdown" crossorigin>
|
||||
<link rel="preload" href="https://cdn.jsdelivr.net/gh/lovell/sharp@master/docs/image/sharp-logo.svg" as="image" type="image/svg+xml" crossorigin>
|
||||
<link rel="dns-prefetch" href="https://pixel.plumbing">
|
||||
<link rel="dns-prefetch" href="https://www.google-analytics.com">
|
||||
@@ -139,7 +139,7 @@
|
||||
docuteApiTitlePlugin,
|
||||
docuteApiSearchPlugin
|
||||
],
|
||||
sourcePath: 'https://cdn.jsdelivr.net/gh/lovell/sharp@v0.27.2/docs',
|
||||
sourcePath: 'https://cdn.jsdelivr.net/gh/lovell/sharp@v0.28.0/docs',
|
||||
nav: [
|
||||
{
|
||||
title: 'Funding',
|
||||
|
||||
@@ -23,7 +23,7 @@ Node.js v10+ on the most common platforms:
|
||||
* Windows x64
|
||||
* Windows x86
|
||||
|
||||
An ~8MB tarball containing libvips and its most commonly used dependencies
|
||||
An ~7.5MB tarball containing libvips and its most commonly used dependencies
|
||||
is downloaded via HTTPS and stored within `node_modules/sharp/vendor` during `npm install`.
|
||||
|
||||
This provides support for the
|
||||
@@ -149,6 +149,23 @@ pkg install -y pkgconf vips
|
||||
cd /usr/ports/graphics/vips/ && make install clean
|
||||
```
|
||||
|
||||
## Linux memory allocator
|
||||
|
||||
The default memory allocator on most glibc-based Linux systems
|
||||
(e.g. Debian, Red Hat) is unsuitable for long-running, multi-threaded
|
||||
processes that involve lots of small memory allocations.
|
||||
|
||||
For this reason, by default, sharp will limit the use of thread-based
|
||||
[concurrency](api-utility#concurrency) when the glibc allocator is
|
||||
detected at runtime.
|
||||
|
||||
To help avoid fragmentation and improve performance on these systems,
|
||||
the use of an alternative memory allocator such as
|
||||
[jemalloc](https://github.com/jemalloc/jemalloc) is recommended.
|
||||
|
||||
Those using musl-based Linux (e.g. Alpine) and non-Linux systems are
|
||||
unaffected.
|
||||
|
||||
## Heroku
|
||||
|
||||
Add the
|
||||
|
||||
@@ -5,10 +5,10 @@ A test to benchmark the performance of this module relative to alternatives.
|
||||
## The contenders
|
||||
|
||||
* [jimp](https://www.npmjs.com/package/jimp) v0.16.1 - Image processing in pure JavaScript. Provides bicubic interpolation.
|
||||
* [mapnik](https://www.npmjs.org/package/mapnik) v4.5.5 - Whilst primarily a map renderer, Mapnik contains bitmap image utilities.
|
||||
* [mapnik](https://www.npmjs.org/package/mapnik) v4.5.6 - Whilst primarily a map renderer, Mapnik contains bitmap image utilities.
|
||||
* [imagemagick](https://www.npmjs.com/package/imagemagick) v0.1.3 - Supports filesystem only and "*has been unmaintained for a long time*".
|
||||
* [gm](https://www.npmjs.com/package/gm) v1.23.1 - Fully featured wrapper around GraphicsMagick's `gm` command line utility.
|
||||
* sharp v0.27.0 / libvips v8.10.5 - Caching within libvips disabled to ensure a fair comparison.
|
||||
* sharp v0.28.0 / libvips v8.10.6 - Caching within libvips disabled to ensure a fair comparison.
|
||||
|
||||
## The task
|
||||
|
||||
@@ -18,25 +18,25 @@ then compress to JPEG at a "quality" setting of 80.
|
||||
|
||||
## Test environment
|
||||
|
||||
* AWS EC2 eu-west-1 [c5d.large](https://aws.amazon.com/ec2/instance-types/c5/) (2x Xeon Platinum 8275CL CPU @ 3.00GHz)
|
||||
* Ubuntu 20.10 (ami-046cdbcee95cdd75c)
|
||||
* Node.js v14.15.3
|
||||
* AWS EC2 eu-west-1 [c5ad.xlarge](https://aws.amazon.com/ec2/instance-types/c5/) (4x AMD EPYC 7R32)
|
||||
* Ubuntu 20.10 (ami-03f10415e8b0bfb86)
|
||||
* Node.js v14.16.0
|
||||
|
||||
## Results
|
||||
|
||||
| Module | Input | Output | Ops/sec | Speed-up |
|
||||
| :----------------- | :----- | :----- | ------: | -------: |
|
||||
| jimp | buffer | buffer | 0.77 | 1.0 |
|
||||
| mapnik | buffer | buffer | 3.39 | 4.4 |
|
||||
| gm | buffer | buffer | 4.30 | 5.6 |
|
||||
| gm | file | file | 4.33 | 5.6 |
|
||||
| imagemagick | file | file | 4.39 | 5.7 |
|
||||
| sharp | stream | stream | 23.81 | 30.9 |
|
||||
| sharp | file | file | 25.09 | 32.6 |
|
||||
| sharp | buffer | buffer | 25.60 | 33.2 |
|
||||
| jimp | buffer | buffer | 0.78 | 1.0 |
|
||||
| mapnik | buffer | buffer | 3.39 | 4.3 |
|
||||
| gm | buffer | buffer | 7.84 | 10.1 |
|
||||
| gm | file | file | 9.24 | 11.8 |
|
||||
| imagemagick | file | file | 9.37 | 12.0 |
|
||||
| sharp | stream | stream | 26.84 | 34.4 |
|
||||
| sharp | file | file | 29.76 | 38.2 |
|
||||
| sharp | buffer | buffer | 31.60 | 40.5 |
|
||||
|
||||
Greater libvips performance can be expected with caching enabled (default)
|
||||
and using 4+ core machines, especially those with larger L1/L2 CPU caches.
|
||||
and using 8+ core machines, especially those with larger L1/L2 CPU caches.
|
||||
|
||||
The I/O limits of the relevant (de)compression library will generally determine maximum throughput.
|
||||
|
||||
@@ -51,7 +51,7 @@ brew install mapnik
|
||||
```
|
||||
|
||||
```sh
|
||||
sudo apt-get install imagemagick libmagick++-dev graphicsmagick libmapnik-dev
|
||||
sudo apt-get install build-essential imagemagick libmagick++-dev graphicsmagick libmapnik-dev
|
||||
```
|
||||
|
||||
```sh
|
||||
@@ -61,7 +61,7 @@ sudo yum install ImageMagick-devel ImageMagick-c++-devel GraphicsMagick mapnik-d
|
||||
```sh
|
||||
git clone https://github.com/lovell/sharp.git
|
||||
cd sharp
|
||||
npm install
|
||||
npm install --build-from-source
|
||||
cd test/bench
|
||||
npm install
|
||||
npm test
|
||||
|
||||
@@ -8,7 +8,7 @@ const extractDescription = (str) =>
|
||||
.replace(/\s+/g, ' ')
|
||||
.replace(/[^A-Za-z0-9_/\-,. ]/g, '')
|
||||
.replace(/\s+/g, ' ')
|
||||
.substr(0, 140)
|
||||
.substr(0, 180)
|
||||
.trim();
|
||||
|
||||
const extractKeywords = (str) =>
|
||||
|
||||
@@ -4,14 +4,19 @@ module.exports = [
|
||||
'about',
|
||||
'after',
|
||||
'all',
|
||||
'allows',
|
||||
'already',
|
||||
'also',
|
||||
'alternative',
|
||||
'always',
|
||||
'and',
|
||||
'any',
|
||||
'are',
|
||||
'based',
|
||||
'been',
|
||||
'before',
|
||||
'both',
|
||||
'call',
|
||||
'can',
|
||||
'containing',
|
||||
'default',
|
||||
@@ -30,7 +35,10 @@ module.exports = [
|
||||
'have',
|
||||
'how',
|
||||
'image',
|
||||
'involve',
|
||||
'its',
|
||||
'least',
|
||||
'lots',
|
||||
'may',
|
||||
'more',
|
||||
'most',
|
||||
@@ -40,17 +48,24 @@ module.exports = [
|
||||
'not',
|
||||
'occur',
|
||||
'occurs',
|
||||
'options',
|
||||
'over',
|
||||
'perform',
|
||||
'performs',
|
||||
'provide',
|
||||
'provided',
|
||||
'ready',
|
||||
'same',
|
||||
'see',
|
||||
'set',
|
||||
'sets',
|
||||
'should',
|
||||
'since',
|
||||
'spelling',
|
||||
'such',
|
||||
'support',
|
||||
'supported',
|
||||
'sure',
|
||||
'take',
|
||||
'that',
|
||||
'the',
|
||||
@@ -59,6 +74,9 @@ module.exports = [
|
||||
'therefore',
|
||||
'these',
|
||||
'this',
|
||||
'under',
|
||||
'unless',
|
||||
'until',
|
||||
'use',
|
||||
'used',
|
||||
'using',
|
||||
@@ -69,5 +87,6 @@ module.exports = [
|
||||
'while',
|
||||
'will',
|
||||
'with',
|
||||
'without'
|
||||
'without',
|
||||
'you'
|
||||
];
|
||||
|
||||
@@ -268,7 +268,13 @@ function blur (sigma) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge alpha transparency channel, if any, with a background.
|
||||
* Merge alpha transparency channel, if any, with a background, then remove the alpha channel.
|
||||
*
|
||||
* @example
|
||||
* await sharp(rgbaInput)
|
||||
* .flatten('#F0A703')
|
||||
* .toBuffer();
|
||||
*
|
||||
* @param {Object} [options]
|
||||
* @param {string|Object} [options.background={r: 0, g: 0, b: 0}] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black.
|
||||
* @returns {Sharp}
|
||||
@@ -396,7 +402,7 @@ function convolve (kernel) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Any pixel value greather than or equal to the threshold value will be set to 255, otherwise it will be set to 0.
|
||||
* Any pixel value greater than or equal to the threshold value will be set to 255, otherwise it will be set to 0.
|
||||
* @param {number} [threshold=128] - a value in the range 0-255 representing the level at which the threshold will be applied.
|
||||
* @param {Object} [options]
|
||||
* @param {Boolean} [options.greyscale=true] - convert to single channel greyscale.
|
||||
|
||||
@@ -105,10 +105,10 @@ function toFile (fileOut, callback) {
|
||||
* .catch(err => { ... });
|
||||
*
|
||||
* @example
|
||||
* const data = await sharp('my-image.jpg')
|
||||
* const { data, info } = await sharp('my-image.jpg')
|
||||
* // output the raw pixels
|
||||
* .raw()
|
||||
* .toBuffer();
|
||||
* .toBuffer({ resolveWithObject: true });
|
||||
*
|
||||
* // create a more type safe way to work with the raw pixel data
|
||||
* // this will not copy the data, instead it will change `data`s underlying ArrayBuffer
|
||||
@@ -116,7 +116,9 @@ function toFile (fileOut, callback) {
|
||||
* const pixelArray = new Uint8ClampedArray(data.buffer);
|
||||
*
|
||||
* // When you are done changing the pixelArray, sharp takes the `pixelArray` as an input
|
||||
* await sharp(pixelArray).toFile('my-changed-image.jpg');
|
||||
* const { width, height, channels } = info;
|
||||
* await sharp(pixelArray, { raw: { width, height, channels } })
|
||||
* .toFile('my-changed-image.jpg');
|
||||
*
|
||||
* @param {Object} [options]
|
||||
* @param {boolean} [options.resolveWithObject] Resolve the Promise with an Object containing `data` and `info` properties instead of resolving only with `data`.
|
||||
|
||||
@@ -302,11 +302,20 @@ function resize (width, height, options) {
|
||||
* })
|
||||
* ...
|
||||
*
|
||||
* @example
|
||||
* // Add a row of 10 red pixels to the bottom
|
||||
* sharp(input)
|
||||
* .extend({
|
||||
* bottom: 10,
|
||||
* background: 'red'
|
||||
* })
|
||||
* ...
|
||||
*
|
||||
* @param {(number|Object)} extend - single pixel count to add to all edges or an Object with per-edge counts
|
||||
* @param {number} [extend.top]
|
||||
* @param {number} [extend.left]
|
||||
* @param {number} [extend.bottom]
|
||||
* @param {number} [extend.right]
|
||||
* @param {number} [extend.top=0]
|
||||
* @param {number} [extend.left=0]
|
||||
* @param {number} [extend.bottom=0]
|
||||
* @param {number} [extend.right=0]
|
||||
* @param {String|Object} [extend.background={r: 0, g: 0, b: 0, alpha: 1}] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black without transparency.
|
||||
* @returns {Sharp}
|
||||
* @throws {Error} Invalid parameters
|
||||
@@ -317,17 +326,35 @@ function extend (extend) {
|
||||
this.options.extendBottom = extend;
|
||||
this.options.extendLeft = extend;
|
||||
this.options.extendRight = extend;
|
||||
} else if (
|
||||
is.object(extend) &&
|
||||
is.integer(extend.top) && extend.top >= 0 &&
|
||||
is.integer(extend.bottom) && extend.bottom >= 0 &&
|
||||
is.integer(extend.left) && extend.left >= 0 &&
|
||||
is.integer(extend.right) && extend.right >= 0
|
||||
) {
|
||||
this.options.extendTop = extend.top;
|
||||
this.options.extendBottom = extend.bottom;
|
||||
this.options.extendLeft = extend.left;
|
||||
this.options.extendRight = extend.right;
|
||||
} else if (is.object(extend)) {
|
||||
if (is.defined(extend.top)) {
|
||||
if (is.integer(extend.top) && extend.top >= 0) {
|
||||
this.options.extendTop = extend.top;
|
||||
} else {
|
||||
throw is.invalidParameterError('top', 'positive integer', extend.top);
|
||||
}
|
||||
}
|
||||
if (is.defined(extend.bottom)) {
|
||||
if (is.integer(extend.bottom) && extend.bottom >= 0) {
|
||||
this.options.extendBottom = extend.bottom;
|
||||
} else {
|
||||
throw is.invalidParameterError('bottom', 'positive integer', extend.bottom);
|
||||
}
|
||||
}
|
||||
if (is.defined(extend.left)) {
|
||||
if (is.integer(extend.left) && extend.left >= 0) {
|
||||
this.options.extendLeft = extend.left;
|
||||
} else {
|
||||
throw is.invalidParameterError('left', 'positive integer', extend.left);
|
||||
}
|
||||
}
|
||||
if (is.defined(extend.right)) {
|
||||
if (is.integer(extend.right) && extend.right >= 0) {
|
||||
this.options.extendRight = extend.right;
|
||||
} else {
|
||||
throw is.invalidParameterError('right', 'positive integer', extend.right);
|
||||
}
|
||||
}
|
||||
this._setBackgroundColourOption('extendBackground', extend.background);
|
||||
} else {
|
||||
throw is.invalidParameterError('extend', 'integer or object', extend);
|
||||
|
||||
12
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "sharp",
|
||||
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, AVIF and TIFF images",
|
||||
"version": "0.28.0-alpha1",
|
||||
"version": "0.28.0",
|
||||
"author": "Lovell Fuller <npm@lovell.info>",
|
||||
"homepage": "https://github.com/lovell/sharp",
|
||||
"contributors": [
|
||||
@@ -80,7 +80,7 @@
|
||||
"install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node-gyp rebuild && node install/dll-copy)",
|
||||
"clean": "rm -rf node_modules/ build/ vendor/ .nyc_output/ coverage/ test/fixtures/output.*",
|
||||
"test": "semistandard && cpplint && npm run test-unit && npm run test-licensing",
|
||||
"test-unit": "nyc --reporter=lcov --branches=99 mocha --slow=5000 --timeout=60000 ./test/unit/*.js",
|
||||
"test-unit": "nyc --reporter=lcov --branches=99 mocha --slow=1000 --timeout=60000 ./test/unit/*.js",
|
||||
"test-licensing": "license-checker --production --summary --onlyAllow=\"Apache-2.0;BSD;ISC;MIT\"",
|
||||
"test-coverage": "./test/coverage/report.sh",
|
||||
"test-leak": "./test/leak/leak.sh",
|
||||
@@ -121,7 +121,7 @@
|
||||
"detect-libc": "^1.0.3",
|
||||
"node-addon-api": "^3.1.0",
|
||||
"prebuild-install": "^6.0.1",
|
||||
"semver": "^7.3.4",
|
||||
"semver": "^7.3.5",
|
||||
"simple-get": "^3.1.0",
|
||||
"tar-fs": "^2.1.1",
|
||||
"tunnel-agent": "^0.6.0"
|
||||
@@ -130,11 +130,11 @@
|
||||
"async": "^3.2.0",
|
||||
"cc": "^3.0.1",
|
||||
"decompress-zip": "^0.3.3",
|
||||
"documentation": "^13.1.1",
|
||||
"documentation": "^13.2.0",
|
||||
"exif-reader": "^1.0.3",
|
||||
"icc": "^2.0.0",
|
||||
"license-checker": "^25.0.1",
|
||||
"mocha": "^8.3.1",
|
||||
"mocha": "^8.3.2",
|
||||
"mock-fs": "^4.13.0",
|
||||
"nyc": "^15.1.0",
|
||||
"prebuild": "^10.0.1",
|
||||
@@ -143,7 +143,7 @@
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"config": {
|
||||
"libvips": "8.10.6-alpha3",
|
||||
"libvips": "8.10.6",
|
||||
"runtime": "napi",
|
||||
"target": 3
|
||||
},
|
||||
|
||||
@@ -213,6 +213,8 @@ namespace sharp {
|
||||
{ "VipsForeignLoadTiffBuffer", ImageType::TIFF },
|
||||
{ "VipsForeignLoadGifFile", ImageType::GIF },
|
||||
{ "VipsForeignLoadGifBuffer", ImageType::GIF },
|
||||
{ "VipsForeignLoadNsgifFile", ImageType::GIF },
|
||||
{ "VipsForeignLoadNsgifBuffer", ImageType::GIF },
|
||||
{ "VipsForeignLoadSvgFile", ImageType::SVG },
|
||||
{ "VipsForeignLoadSvgBuffer", ImageType::SVG },
|
||||
{ "VipsForeignLoadHeifFile", ImageType::HEIF },
|
||||
|
||||
@@ -562,9 +562,17 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
// Use gravity in overlay
|
||||
if (compositeImage.width() <= baton->width) {
|
||||
across = static_cast<int>(ceil(static_cast<double>(image.width()) / compositeImage.width()));
|
||||
// Ensure odd number of tiles across when gravity is centre, north or south
|
||||
if (composite->gravity == 0 || composite->gravity == 1 || composite->gravity == 3) {
|
||||
across |= 1;
|
||||
}
|
||||
}
|
||||
if (compositeImage.height() <= baton->height) {
|
||||
down = static_cast<int>(ceil(static_cast<double>(image.height()) / compositeImage.height()));
|
||||
// Ensure odd number of tiles down when gravity is centre, east or west
|
||||
if (composite->gravity == 0 || composite->gravity == 2 || composite->gravity == 4) {
|
||||
down |= 1;
|
||||
}
|
||||
}
|
||||
if (across != 0 || down != 0) {
|
||||
int left;
|
||||
@@ -594,8 +602,13 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
int top;
|
||||
if (composite->hasOffset) {
|
||||
// Composite image at given offsets
|
||||
std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),
|
||||
compositeImage.width(), compositeImage.height(), composite->left, composite->top);
|
||||
if (composite->tile) {
|
||||
std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),
|
||||
compositeImage.width(), compositeImage.height(), composite->left, composite->top);
|
||||
} else {
|
||||
left = composite->left;
|
||||
top = composite->top;
|
||||
}
|
||||
} else {
|
||||
// Composite image with given gravity
|
||||
std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"gm": "1.23.1",
|
||||
"imagemagick": "0.1.3",
|
||||
"jimp": "0.16.1",
|
||||
"mapnik": "4.5.5",
|
||||
"mapnik": "4.5.6",
|
||||
"semver": "7.3.4"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const os = require('os');
|
||||
const fs = require('fs');
|
||||
|
||||
const async = require('async');
|
||||
@@ -25,6 +26,9 @@ const height = 588;
|
||||
// Disable libvips cache to ensure tests are as fair as they can be
|
||||
sharp.cache(false);
|
||||
|
||||
// Spawn one thread per CPU
|
||||
sharp.concurrency(os.cpus().length);
|
||||
|
||||
async.series({
|
||||
jpeg: function (callback) {
|
||||
const inputJpgBuffer = fs.readFileSync(fixtures.inputJpg);
|
||||
|
||||
BIN
test/fixtures/expected/modulate-all.jpg
vendored
|
Before Width: | Height: | Size: 664 KiB |
BIN
test/fixtures/expected/modulate-brightness-0-5.jpg
vendored
|
Before Width: | Height: | Size: 426 KiB |
BIN
test/fixtures/expected/modulate-brightness-2.jpg
vendored
|
Before Width: | Height: | Size: 692 KiB |
BIN
test/fixtures/expected/modulate-hue-120.jpg
vendored
|
Before Width: | Height: | Size: 653 KiB |
BIN
test/fixtures/expected/modulate-hue-angle-120.png
vendored
|
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 29 KiB |
BIN
test/fixtures/expected/modulate-hue-angle-150.png
vendored
|
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 30 KiB |
BIN
test/fixtures/expected/modulate-hue-angle-180.png
vendored
|
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 29 KiB |
BIN
test/fixtures/expected/modulate-hue-angle-210.png
vendored
|
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 29 KiB |
BIN
test/fixtures/expected/modulate-hue-angle-240.png
vendored
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 30 KiB |
BIN
test/fixtures/expected/modulate-hue-angle-270.png
vendored
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 30 KiB |
BIN
test/fixtures/expected/modulate-hue-angle-30.png
vendored
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 30 KiB |
BIN
test/fixtures/expected/modulate-hue-angle-300.png
vendored
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 30 KiB |
BIN
test/fixtures/expected/modulate-hue-angle-330.png
vendored
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 30 KiB |
BIN
test/fixtures/expected/modulate-hue-angle-360.png
vendored
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 32 KiB |
BIN
test/fixtures/expected/modulate-hue-angle-60.png
vendored
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 30 KiB |
BIN
test/fixtures/expected/modulate-hue-angle-90.png
vendored
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 29 KiB |
BIN
test/fixtures/expected/modulate-linear.jpg
vendored
|
Before Width: | Height: | Size: 62 KiB |
BIN
test/fixtures/expected/modulate-saturation-0.5.jpg
vendored
|
Before Width: | Height: | Size: 606 KiB |
BIN
test/fixtures/expected/modulate-saturation-2.jpg
vendored
|
Before Width: | Height: | Size: 672 KiB |
@@ -42,18 +42,15 @@ describe('Colour space conversion', function () {
|
||||
});
|
||||
});
|
||||
|
||||
if (sharp.format.tiff.input.file && sharp.format.webp.output.buffer) {
|
||||
it('From 1-bit TIFF to sRGB WebP [slow]', function (done) {
|
||||
sharp(fixtures.inputTiff)
|
||||
.webp()
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, data.length > 0);
|
||||
assert.strictEqual('webp', info.format);
|
||||
done();
|
||||
});
|
||||
});
|
||||
}
|
||||
it('From 1-bit TIFF to sRGB WebP', async () => {
|
||||
const data = await sharp(fixtures.inputTiff)
|
||||
.resize(8, 8)
|
||||
.webp()
|
||||
.toBuffer();
|
||||
|
||||
const { format } = await sharp(data).metadata();
|
||||
assert.strictEqual(format, 'webp');
|
||||
});
|
||||
|
||||
it('From CMYK to sRGB', function (done) {
|
||||
sharp(fixtures.inputJpgWithCmykProfile)
|
||||
|
||||
@@ -225,6 +225,24 @@ describe('composite', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('centre gravity should replicate correct number of tiles', async () => {
|
||||
const red = { r: 255, g: 0, b: 0 };
|
||||
const [r, g, b] = await sharp({
|
||||
create: {
|
||||
width: 40, height: 40, channels: 4, background: red
|
||||
}
|
||||
})
|
||||
.composite([{
|
||||
input: fixtures.inputPngWithTransparency16bit,
|
||||
gravity: 'centre',
|
||||
tile: true
|
||||
}])
|
||||
.raw()
|
||||
.toBuffer();
|
||||
|
||||
assert.deepStrictEqual({ r, g, b }, red);
|
||||
});
|
||||
|
||||
it('cutout via dest-in', done => {
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(300, 300)
|
||||
@@ -390,4 +408,20 @@ describe('composite', () => {
|
||||
}, /Expected valid gravity for gravity but received invalid of type string/);
|
||||
});
|
||||
});
|
||||
|
||||
it('Allow offset beyond bottom/right edge', async () => {
|
||||
const red = { r: 255, g: 0, b: 0 };
|
||||
const blue = { r: 0, g: 0, b: 255 };
|
||||
|
||||
const [r, g, b] = await sharp({ create: { width: 2, height: 2, channels: 4, background: red } })
|
||||
.composite([{
|
||||
input: { create: { width: 2, height: 2, channels: 4, background: blue } },
|
||||
top: 1,
|
||||
left: 1
|
||||
}])
|
||||
.raw()
|
||||
.toBuffer();
|
||||
|
||||
assert.deepStrictEqual(red, { r, g, b });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -41,7 +41,6 @@ describe('Extend', function () {
|
||||
.resize(120)
|
||||
.extend({
|
||||
top: 50,
|
||||
bottom: 0,
|
||||
left: 10,
|
||||
right: 35,
|
||||
background: { r: 0, g: 0, b: 0, alpha: 0 }
|
||||
@@ -64,18 +63,38 @@ describe('Extend', function () {
|
||||
sharp().extend(-1);
|
||||
});
|
||||
});
|
||||
it('partial object fails', function () {
|
||||
assert.throws(function () {
|
||||
sharp().extend({ top: 1 });
|
||||
});
|
||||
it('invalid top fails', () => {
|
||||
assert.throws(
|
||||
() => sharp().extend({ top: 'fail' }),
|
||||
/Expected positive integer for top but received fail of type string/
|
||||
);
|
||||
});
|
||||
it('invalid bottom fails', () => {
|
||||
assert.throws(
|
||||
() => sharp().extend({ bottom: -1 }),
|
||||
/Expected positive integer for bottom but received -1 of type number/
|
||||
);
|
||||
});
|
||||
it('invalid left fails', () => {
|
||||
assert.throws(
|
||||
() => sharp().extend({ left: 0.1 }),
|
||||
/Expected positive integer for left but received 0.1 of type number/
|
||||
);
|
||||
});
|
||||
it('invalid right fails', () => {
|
||||
assert.throws(
|
||||
() => sharp().extend({ right: {} }),
|
||||
/Expected positive integer for right but received \[object Object\] of type object/
|
||||
);
|
||||
});
|
||||
it('can set all edges apart from right', () => {
|
||||
assert.doesNotThrow(() => sharp().extend({ top: 1, left: 2, bottom: 3 }));
|
||||
});
|
||||
|
||||
it('should add alpha channel before extending with a transparent Background', function (done) {
|
||||
sharp(fixtures.inputJpgWithLandscapeExif1)
|
||||
.extend({
|
||||
top: 0,
|
||||
bottom: 10,
|
||||
left: 0,
|
||||
right: 10,
|
||||
background: { r: 0, g: 0, b: 0, alpha: 0 }
|
||||
})
|
||||
@@ -91,9 +110,7 @@ describe('Extend', function () {
|
||||
it('PNG with 2 channels', function (done) {
|
||||
sharp(fixtures.inputPngWithGreyAlpha)
|
||||
.extend({
|
||||
top: 0,
|
||||
bottom: 20,
|
||||
left: 0,
|
||||
right: 20,
|
||||
background: 'transparent'
|
||||
})
|
||||
|
||||
@@ -28,32 +28,28 @@ describe('Partial image extraction', function () {
|
||||
});
|
||||
});
|
||||
|
||||
if (sharp.format.webp.output.file) {
|
||||
it('WebP', function (done) {
|
||||
sharp(fixtures.inputWebP)
|
||||
.extract({ left: 100, top: 50, width: 125, height: 200 })
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(125, info.width);
|
||||
assert.strictEqual(200, info.height);
|
||||
fixtures.assertSimilar(fixtures.expected('extract.webp'), data, done);
|
||||
});
|
||||
});
|
||||
}
|
||||
it('WebP', function (done) {
|
||||
sharp(fixtures.inputWebP)
|
||||
.extract({ left: 100, top: 50, width: 125, height: 200 })
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(125, info.width);
|
||||
assert.strictEqual(200, info.height);
|
||||
fixtures.assertSimilar(fixtures.expected('extract.webp'), data, done);
|
||||
});
|
||||
});
|
||||
|
||||
if (sharp.format.tiff.output.file) {
|
||||
it('TIFF', function (done) {
|
||||
sharp(fixtures.inputTiff)
|
||||
.extract({ left: 34, top: 63, width: 341, height: 529 })
|
||||
.jpeg()
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(341, info.width);
|
||||
assert.strictEqual(529, info.height);
|
||||
fixtures.assertSimilar(fixtures.expected('extract.tiff'), data, done);
|
||||
});
|
||||
});
|
||||
}
|
||||
it('TIFF', function (done) {
|
||||
sharp(fixtures.inputTiff)
|
||||
.extract({ left: 34, top: 63, width: 341, height: 529 })
|
||||
.jpeg()
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(341, info.width);
|
||||
assert.strictEqual(529, info.height);
|
||||
fixtures.assertSimilar(fixtures.expected('extract.tiff'), data, done);
|
||||
});
|
||||
});
|
||||
|
||||
it('Before resize', function (done) {
|
||||
sharp(fixtures.inputJpg)
|
||||
|
||||
@@ -28,115 +28,138 @@ describe('Modulate', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to hue-rotate', function () {
|
||||
const base = 'modulate-hue-120.jpg';
|
||||
const actual = fixtures.path('output.' + base);
|
||||
const expected = fixtures.expected(base);
|
||||
|
||||
return sharp(fixtures.inputJpg)
|
||||
it('should be able to hue-rotate', async () => {
|
||||
const [r, g, b] = await sharp({
|
||||
create: {
|
||||
width: 1,
|
||||
height: 1,
|
||||
channels: 3,
|
||||
background: { r: 153, g: 68, b: 68 }
|
||||
}
|
||||
})
|
||||
.modulate({ hue: 120 })
|
||||
.toFile(actual)
|
||||
.then(function () {
|
||||
fixtures.assertMaxColourDistance(actual, expected, 25);
|
||||
});
|
||||
.raw()
|
||||
.toBuffer();
|
||||
|
||||
assert.deepStrictEqual({ r: 41, g: 107, b: 57 }, { r, g, b });
|
||||
});
|
||||
|
||||
it('should be able to brighten', function () {
|
||||
const base = 'modulate-brightness-2.jpg';
|
||||
const actual = fixtures.path('output.' + base);
|
||||
const expected = fixtures.expected(base);
|
||||
|
||||
return sharp(fixtures.inputJpg)
|
||||
it('should be able to brighten', async () => {
|
||||
const [r, g, b] = await sharp({
|
||||
create: {
|
||||
width: 1,
|
||||
height: 1,
|
||||
channels: 3,
|
||||
background: { r: 153, g: 68, b: 68 }
|
||||
}
|
||||
})
|
||||
.modulate({ brightness: 2 })
|
||||
.toFile(actual)
|
||||
.then(function () {
|
||||
fixtures.assertMaxColourDistance(actual, expected, 25);
|
||||
});
|
||||
.raw()
|
||||
.toBuffer();
|
||||
|
||||
assert.deepStrictEqual({ r: 255, g: 173, b: 168 }, { r, g, b });
|
||||
});
|
||||
|
||||
it('should be able to unbrighten', function () {
|
||||
const base = 'modulate-brightness-0-5.jpg';
|
||||
const actual = fixtures.path('output.' + base);
|
||||
const expected = fixtures.expected(base);
|
||||
|
||||
return sharp(fixtures.inputJpg)
|
||||
it('should be able to darken', async () => {
|
||||
const [r, g, b] = await sharp({
|
||||
create: {
|
||||
width: 1,
|
||||
height: 1,
|
||||
channels: 3,
|
||||
background: { r: 153, g: 68, b: 68 }
|
||||
}
|
||||
})
|
||||
.modulate({ brightness: 0.5 })
|
||||
.toFile(actual)
|
||||
.then(function () {
|
||||
fixtures.assertMaxColourDistance(actual, expected, 25);
|
||||
});
|
||||
.raw()
|
||||
.toBuffer();
|
||||
|
||||
assert.deepStrictEqual({ r: 97, g: 17, b: 25 }, { r, g, b });
|
||||
});
|
||||
|
||||
it('should be able to saturate', function () {
|
||||
const base = 'modulate-saturation-2.jpg';
|
||||
const actual = fixtures.path('output.' + base);
|
||||
const expected = fixtures.expected(base);
|
||||
|
||||
return sharp(fixtures.inputJpg)
|
||||
it('should be able to saturate', async () => {
|
||||
const [r, g, b] = await sharp({
|
||||
create: {
|
||||
width: 1,
|
||||
height: 1,
|
||||
channels: 3,
|
||||
background: { r: 153, g: 68, b: 68 }
|
||||
}
|
||||
})
|
||||
.modulate({ saturation: 2 })
|
||||
.toFile(actual)
|
||||
.then(function () {
|
||||
fixtures.assertMaxColourDistance(actual, expected, 30);
|
||||
});
|
||||
.raw()
|
||||
.toBuffer();
|
||||
|
||||
assert.deepStrictEqual({ r: 198, g: 0, b: 43 }, { r, g, b });
|
||||
});
|
||||
|
||||
it('should be able to desaturate', function () {
|
||||
const base = 'modulate-saturation-0.5.jpg';
|
||||
const actual = fixtures.path('output.' + base);
|
||||
const expected = fixtures.expected(base);
|
||||
|
||||
return sharp(fixtures.inputJpg)
|
||||
it('should be able to desaturate', async () => {
|
||||
const [r, g, b] = await sharp({
|
||||
create: {
|
||||
width: 1,
|
||||
height: 1,
|
||||
channels: 3,
|
||||
background: { r: 153, g: 68, b: 68 }
|
||||
}
|
||||
})
|
||||
.modulate({ saturation: 0.5 })
|
||||
.toFile(actual)
|
||||
.then(function () {
|
||||
fixtures.assertMaxColourDistance(actual, expected, 25);
|
||||
});
|
||||
.raw()
|
||||
.toBuffer();
|
||||
|
||||
assert.deepStrictEqual({ r: 127, g: 83, b: 81 }, { r, g, b });
|
||||
});
|
||||
|
||||
it('should be able to modulate all channels', function () {
|
||||
const base = 'modulate-all.jpg';
|
||||
const actual = fixtures.path('output.' + base);
|
||||
const expected = fixtures.expected(base);
|
||||
|
||||
return sharp(fixtures.inputJpg)
|
||||
it('should be able to modulate all channels', async () => {
|
||||
const [r, g, b] = await sharp({
|
||||
create: {
|
||||
width: 1,
|
||||
height: 1,
|
||||
channels: 3,
|
||||
background: { r: 153, g: 68, b: 68 }
|
||||
}
|
||||
})
|
||||
.modulate({ brightness: 2, saturation: 0.5, hue: 180 })
|
||||
.toFile(actual)
|
||||
.then(function () {
|
||||
fixtures.assertMaxColourDistance(actual, expected, 25);
|
||||
});
|
||||
.raw()
|
||||
.toBuffer();
|
||||
|
||||
assert.deepStrictEqual({ r: 149, g: 209, b: 214 }, { r, g, b });
|
||||
});
|
||||
|
||||
describe('hue-rotate', function (done) {
|
||||
[30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330, 360].forEach(function (angle) {
|
||||
it('should properly hue rotate by ' + angle + 'deg', function () {
|
||||
const base = 'modulate-hue-angle-' + angle + '.png';
|
||||
const actual = fixtures.path('output.' + base);
|
||||
it('should be able to use linear and modulate together', async () => {
|
||||
const contrast = 1.5;
|
||||
const brightness = 0.5;
|
||||
|
||||
const [r, g, b] = await sharp({
|
||||
create: {
|
||||
width: 1,
|
||||
height: 1,
|
||||
channels: 3,
|
||||
background: { r: 153, g: 68, b: 68 }
|
||||
}
|
||||
})
|
||||
.linear(contrast, -(128 * contrast) + 128)
|
||||
.modulate({ brightness })
|
||||
.raw()
|
||||
.toBuffer();
|
||||
|
||||
assert.deepStrictEqual({ r: 81, g: 0, b: 0 }, { r, g, b });
|
||||
});
|
||||
|
||||
describe('hue-rotate', () => {
|
||||
[30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330, 360].forEach(angle => {
|
||||
it(`should hue rotate by ${angle} deg`, async () => {
|
||||
const base = `modulate-hue-angle-${angle}.png`;
|
||||
const actual = fixtures.path(`output.${base}`);
|
||||
const expected = fixtures.expected(base);
|
||||
|
||||
return sharp(fixtures.testPattern)
|
||||
await sharp(fixtures.testPattern)
|
||||
.resize(320)
|
||||
.modulate({ hue: angle })
|
||||
.png({ compressionLevel: 0 })
|
||||
.toFile(actual)
|
||||
.then(function () {
|
||||
fixtures.assertMaxColourDistance(actual, expected, 25);
|
||||
.then(() => {
|
||||
fixtures.assertMaxColourDistance(actual, expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to use linear and modulate together', function () {
|
||||
const base = 'modulate-linear.jpg';
|
||||
const actual = fixtures.path('output.' + base);
|
||||
const expected = fixtures.expected(base);
|
||||
|
||||
const contrast = 1.5;
|
||||
const brightness = 0.5;
|
||||
|
||||
return sharp(fixtures.testPattern)
|
||||
.linear(contrast, -(128 * contrast) + 128)
|
||||
.modulate({ brightness })
|
||||
.toFile(actual)
|
||||
.then(function () {
|
||||
fixtures.assertMaxColourDistance(actual, expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -54,6 +54,7 @@ describe('Normalization', function () {
|
||||
|
||||
it('keeps an existing alpha channel', function (done) {
|
||||
sharp(fixtures.inputPngWithTransparency)
|
||||
.resize(8, 8)
|
||||
.normalize()
|
||||
.toBuffer(function (err, data) {
|
||||
if (err) throw err;
|
||||
@@ -69,6 +70,7 @@ describe('Normalization', function () {
|
||||
|
||||
it('keeps the alpha channel of greyscale images intact', function (done) {
|
||||
sharp(fixtures.inputPngWithGreyAlpha)
|
||||
.resize(8, 8)
|
||||
.normalise()
|
||||
.toBuffer(function (err, data) {
|
||||
if (err) throw err;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const assert = require('assert');
|
||||
|
||||
const sharp = require('../../');
|
||||
@@ -99,4 +100,18 @@ describe('SVG input', function () {
|
||||
fixtures.assertSimilar(fixtures.expected('svg-embedded.png'), data, done);
|
||||
});
|
||||
});
|
||||
|
||||
it('Converts SVG with truncated embedded PNG', async () => {
|
||||
const truncatedPng = fs.readFileSync(fixtures.inputPngTruncated).toString('base64');
|
||||
const svg = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="294" height="240" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<image width="294" height="240" xlink:href="data:image/png;base64,${truncatedPng}"/>
|
||||
</svg>`;
|
||||
|
||||
const { info } = await sharp(Buffer.from(svg)).toBuffer({ resolveWithObject: true });
|
||||
assert.strictEqual(info.format, 'png');
|
||||
assert.strictEqual(info.width, 294);
|
||||
assert.strictEqual(info.height, 240);
|
||||
assert.strictEqual(info.channels, 4);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -90,9 +90,11 @@ describe('TIFF', function () {
|
||||
|
||||
it('Increasing TIFF quality increases file size', () =>
|
||||
sharp(fixtures.inputJpgWithLandscapeExif1)
|
||||
.resize(320, 240)
|
||||
.tiff({ quality: 40 })
|
||||
.toBuffer()
|
||||
.then(tiff40 => sharp(fixtures.inputJpgWithLandscapeExif1)
|
||||
.resize(320, 240)
|
||||
.tiff({ quality: 90 })
|
||||
.toBuffer()
|
||||
.then(tiff90 =>
|
||||
@@ -155,6 +157,7 @@ describe('TIFF', function () {
|
||||
|
||||
it('TIFF setting xres and yres on file', () =>
|
||||
sharp(fixtures.inputTiff)
|
||||
.resize(8, 8)
|
||||
.tiff({
|
||||
xres: 1000,
|
||||
yres: 1000
|
||||
@@ -171,6 +174,7 @@ describe('TIFF', function () {
|
||||
|
||||
it('TIFF setting xres and yres on buffer', () =>
|
||||
sharp(fixtures.inputTiff)
|
||||
.resize(8, 8)
|
||||
.tiff({
|
||||
xres: 1000,
|
||||
yres: 1000
|
||||
|
||||
@@ -5,22 +5,23 @@ const sharp = require('../../');
|
||||
const fixtures = require('../fixtures');
|
||||
|
||||
describe('toFormat', () => {
|
||||
it('accepts upper case characters as format parameter (string)', function (done) {
|
||||
sharp(fixtures.inputJpg)
|
||||
it('accepts upper case characters as format parameter (string)', async () => {
|
||||
const data = await sharp(fixtures.inputJpg)
|
||||
.resize(8, 8)
|
||||
.toFormat('PNG')
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('png', info.format);
|
||||
done();
|
||||
});
|
||||
.toBuffer();
|
||||
|
||||
const { format } = await sharp(data).metadata();
|
||||
assert.strictEqual(format, 'png');
|
||||
});
|
||||
it('accepts upper case characters as format parameter (object)', function (done) {
|
||||
sharp(fixtures.inputJpg)
|
||||
|
||||
it('accepts upper case characters as format parameter (object)', async () => {
|
||||
const data = await sharp(fixtures.inputJpg)
|
||||
.resize(8, 8)
|
||||
.toFormat({ id: 'PNG' })
|
||||
.toBuffer(function (err, data, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual('png', info.format);
|
||||
done();
|
||||
});
|
||||
.toBuffer();
|
||||
|
||||
const { format } = await sharp(data).metadata();
|
||||
assert.strictEqual(format, 'png');
|
||||
});
|
||||
});
|
||||
|
||||