Compare commits

...

16 Commits

Author SHA1 Message Date
Lovell Fuller
b609df4b48 Release v0.30.3 2022-03-14 11:54:15 +00:00
Lovell Fuller
c567d3b9ab Bump devDeps 2022-03-14 11:52:45 +00:00
Marvin ROGER
27d9fe2a4e Add additional debugging hint on symbol error (#3132) 2022-03-14 10:19:02 +00:00
Lovell Fuller
ac883c5215 Docs: composite image supports the animated property #3135 2022-03-14 08:53:00 +00:00
Lovell Fuller
e8720c9374 Docs: use SVG favicon with a PNG fallback
Remove unused apple-touch icons
2022-03-13 14:22:05 +00:00
Lovell Fuller
42e45d842a Docs: add more examples (composite, greyscale, tint) 2022-03-12 20:33:15 +00:00
Lovell Fuller
72fd8abe2c Docs: add section on (preventing) bundling with esbuild 2022-03-12 13:17:36 +00:00
Lovell Fuller
ac18bbbc7c Ensure backwards-compatibility of JSDoc introduced in ea599ad 2022-03-11 19:07:11 +00:00
Lovell Fuller
fcbe4e1e01 Tests: remove possible race condition
Simplify JP2 quality size check
2022-03-09 19:55:32 +00:00
Lovell Fuller
9280742385 CI: update FreeBSD environment 2022-03-09 19:26:43 +00:00
Lovell Fuller
7a1a1cf9e8 Docs: add/correct some operation examples 2022-03-09 19:18:41 +00:00
Lovell Fuller
ea599ade10 Allow sharpen options to be provided as an Object
Also exposes x1, y2, y3 parameters #2561 #2935
2022-03-09 19:07:08 +00:00
Lovell Fuller
1de49f3ed8 Docs: refresh for metadata example #3127 2022-03-09 16:02:27 +00:00
Rasmus Schultz
4ac65054bc Docs: add example of how to get the right-side-up width/height (#3127)
See #3124
2022-03-09 15:59:09 +00:00
Lovell Fuller
23033e2050 Prevent double unpremultiply with some composite blends 2022-03-04 23:17:07 +00:00
Lovell Fuller
dd3b78272a Docs: remove experimental status from existing stats properties 2022-03-04 07:58:03 +00:00
30 changed files with 471 additions and 110 deletions

View File

@@ -1,15 +1,15 @@
freebsd_instance:
image_family: freebsd-13-0-snap
image_family: freebsd-14-0-snap
task:
name: FreeBSD 13.0
name: FreeBSD
env:
IGNORE_OSVERSION: yes
skip_notifications: true
prerequisites_script:
- pkg update -f
- pkg upgrade -y
- pkg install -y pkgconf vips node npm
- pkg install -y devel/pkgconf graphics/vips www/node16 www/npm
install_script:
- npm install --build-from-source --unsafe-perm
test_script:

View File

@@ -9,7 +9,13 @@ An alpha channel may be present and will be unchanged by the operation.
* `rgb` **([string][1] | [Object][2])** parsed by the [color][3] module to extract chroma values.
<!---->
### Examples
```javascript
const output = await sharp(input)
.tint({ r: 255, g: 240, b: 16 })
.toBuffer();
```
* Throws **[Error][4]** Invalid parameter
@@ -28,6 +34,12 @@ An alpha channel may be present, and will be unchanged by the operation.
* `greyscale` **[Boolean][5]** (optional, default `true`)
### Examples
```javascript
const output = await sharp(input).greyscale().toBuffer();
```
Returns **Sharp**
## grayscale

View File

@@ -41,11 +41,29 @@ and [https://www.cairographics.org/operators/][2]
* `images[].raw.width` **[Number][7]?**
* `images[].raw.height` **[Number][7]?**
* `images[].raw.channels` **[Number][7]?**
* `images[].animated` **[boolean][9]** Set to `true` to read all frames/pages of an animated image. (optional, default `false`)
* `images[].failOnError` **[boolean][9]** @see [constructor parameters][10] (optional, default `true`)
* `images[].limitInputPixels` **([number][7] | [boolean][9])** @see [constructor parameters][10] (optional, default `268402689`)
### Examples
```javascript
await sharp(background)
.composite([
{ input: layer1, gravity: 'northwest' },
{ input: layer2, gravity: 'southeast' },
])
.toFile('combined.png');
```
```javascript
const output = await sharp('input.gif', { animated: true })
.composite([
{ input: 'overlay.png', tile: true, blend: 'saturate' }
])
.toBuffer();
```
```javascript
sharp('input.png')
.rotate(180)

View File

@@ -11,8 +11,8 @@ 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`
* `size`: Total size of image in bytes, for Stream and Buffer input only
* `width`: Number of pixels wide (EXIF orientation is not taken into consideration)
* `height`: Number of pixels high (EXIF orientation is not taken into consideration)
* `width`: Number of pixels wide (EXIF orientation is not taken into consideration, see example below)
* `height`: Number of pixels high (EXIF orientation is not taken into consideration, see example below)
* `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...][1]
* `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK
* `depth`: Name of pixel depth format e.g. `uchar`, `char`, `ushort`, `float` [...][2]
@@ -63,6 +63,18 @@ image
});
```
```javascript
// Based on EXIF rotation metadata, get the right-side-up width and height:
const size = getNormalSize(await sharp(input).metadata());
function getNormalSize({ width, height, orientation }) {
return orientation || 0 >= 5
? { width: height, height: width }
: { width, height };
}
```
Returns **([Promise][5]<[Object][6]> | Sharp)**
## stats
@@ -82,9 +94,9 @@ A `Promise` is returned when `callback` is not provided.
* `maxX` (x-coordinate of one of the pixel where the maximum lies)
* `maxY` (y-coordinate of one of the pixel where the maximum lies)
* `isOpaque`: Is the image fully opaque? Will be `true` if the image has no alpha channel or if every pixel is fully opaque.
* `entropy`: Histogram-based estimation of greyscale entropy, discarding alpha channel if any (experimental)
* `sharpness`: Estimation of greyscale sharpness based on the standard deviation of a Laplacian convolution, discarding alpha channel if any (experimental)
* `dominant`: Object containing most dominant sRGB colour based on a 4096-bin 3D histogram (experimental)
* `entropy`: Histogram-based estimation of greyscale entropy, discarding alpha channel if any.
* `sharpness`: Estimation of greyscale sharpness based on the standard deviation of a Laplacian convolution, discarding alpha channel if any.
* `dominant`: Object containing most dominant sRGB colour based on a 4096-bin 3D histogram.
**Note**: Statistics are derived from the original input image. Any operations performed on the image must first be
written to a buffer in order to run `stats` on the result (see third example).

View File

@@ -53,6 +53,12 @@ The use of `flip` implies the removal of the EXIF `Orientation` tag, if any.
* `flip` **[Boolean][6]** (optional, default `true`)
### Examples
```javascript
const output = await sharp(input).flip().toBuffer();
```
Returns **Sharp**
## flop
@@ -64,6 +70,12 @@ The use of `flop` implies the removal of the EXIF `Orientation` tag, if any.
* `flop` **[Boolean][6]** (optional, default `true`)
### Examples
```javascript
const output = await sharp(input).flop().toBuffer();
```
Returns **Sharp**
## affine
@@ -129,13 +141,43 @@ When used without parameters, performs a fast, mild sharpen of the output image.
When a `sigma` is provided, performs a slower, more accurate sharpen of the L channel in the LAB colour space.
Separate control over the level of sharpening in "flat" and "jagged" areas is available.
See [libvips sharpen][8] operation.
### Parameters
* `sigma` **[number][1]?** the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
* `flat` **[number][1]** the level of sharpening to apply to "flat" areas. (optional, default `1.0`)
* `jagged` **[number][1]** the level of sharpening to apply to "jagged" areas. (optional, default `2.0`)
* `options` **([Object][2] | [number][1])?** if present, is an Object with attributes or (deprecated) a number for `options.sigma`.
<!---->
* `options.sigma` **[number][1]?** the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
* `options.m1` **[number][1]** the level of sharpening to apply to "flat" areas. (optional, default `1.0`)
* `options.m2` **[number][1]** the level of sharpening to apply to "jagged" areas. (optional, default `2.0`)
* `options.x1` **[number][1]** threshold between "flat" and "jagged" (optional, default `2.0`)
* `options.y2` **[number][1]** maximum amount of brightening. (optional, default `10.0`)
* `options.y3` **[number][1]** maximum amount of darkening. (optional, default `20.0`)
* `flat` **[number][1]?** (deprecated) see `options.m1`.
* `jagged` **[number][1]?** (deprecated) see `options.m2`.
### Examples
```javascript
const data = await sharp(input).sharpen().toBuffer();
```
```javascript
const data = await sharp(input).sharpen({ sigma: 2 }).toBuffer();
```
```javascript
const data = await sharp(input)
.sharpen({
sigma: 2,
m1: 0
m2: 3,
x1: 3,
y2: 15,
y3: 15,
})
.toBuffer();
```
* Throws **[Error][5]** Invalid parameters
@@ -150,7 +192,15 @@ When used without parameters the default window is 3x3.
* `size` **[number][1]** square mask size: size x size (optional, default `3`)
<!---->
### Examples
```javascript
const output = await sharp(input).median().toBuffer();
```
```javascript
const output = await sharp(input).median(5).toBuffer();
```
* Throws **[Error][5]** Invalid parameters
@@ -190,7 +240,7 @@ Returns **Sharp**
Merge alpha transparency channel, if any, with a background, then remove the alpha channel.
See also [removeAlpha][8].
See also [removeAlpha][9].
### Parameters
@@ -249,6 +299,12 @@ Enhance output image contrast by stretching its luminance to cover the full dyna
* `normalise` **[Boolean][6]** (optional, default `true`)
### Examples
```javascript
const output = await sharp(input).normalise().toBuffer();
```
Returns **Sharp**
## normalize
@@ -259,12 +315,18 @@ Alternative spelling of normalise.
* `normalize` **[Boolean][6]** (optional, default `true`)
### Examples
```javascript
const output = await sharp(input).normalize().toBuffer();
```
Returns **Sharp**
## clahe
Perform contrast limiting adaptive histogram equalization
[CLAHE][9].
[CLAHE][10].
This will, in general, enhance the clarity of the image by bringing out darker details.
@@ -278,7 +340,16 @@ This will, in general, enhance the clarity of the image by bringing out darker d
cumulative histogram. A value of 0 disables contrast limiting. Valid values
are integers in the range 0-100 (inclusive) (optional, default `3`)
<!---->
### Examples
```javascript
const output = await sharp(input)
.clahe({
width: 3,
height: 3,
})
.toBuffer();
```
* Throws **[Error][5]** Invalid parameters
@@ -349,7 +420,7 @@ the selected bitwise boolean `operation` between the corresponding pixels of the
### Parameters
* `operand` **([Buffer][10] | [string][3])** Buffer containing image data or string containing the path to an image file.
* `operand` **([Buffer][11] | [string][3])** Buffer containing image data or string containing the path to an image file.
* `operator` **[string][3]** one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively.
* `options` **[Object][2]?**
@@ -430,28 +501,41 @@ brightness is multiplicative whereas lightness is additive.
### Examples
```javascript
sharp(input)
// increase brightness by a factor of 2
const output = await sharp(input)
.modulate({
brightness: 2 // increase brightness by a factor of 2
});
brightness: 2
})
.toBuffer();
```
sharp(input)
```javascript
// hue-rotate by 180 degrees
const output = await sharp(input)
.modulate({
hue: 180 // hue-rotate by 180 degrees
});
hue: 180
})
.toBuffer();
```
sharp(input)
```javascript
// increase lightness by +50
const output = await sharp(input)
.modulate({
lightness: 50 // increase lightness by +50
});
lightness: 50
})
.toBuffer();
```
```javascript
// decreate brightness and saturation while also hue-rotating by 90 degrees
sharp(input)
const output = await sharp(input)
.modulate({
brightness: 0.5,
saturation: 0.5,
hue: 90
});
hue: 90,
})
.toBuffer();
```
Returns **Sharp**
@@ -474,8 +558,10 @@ Returns **Sharp**
[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
[8]: /api-channel#removealpha
[8]: https://www.libvips.org/API/current/libvips-convolution.html#vips-sharpen
[9]: https://en.wikipedia.org/wiki/Adaptive_histogram_equalization#Contrast_Limited_AHE
[9]: /api-channel#removealpha
[10]: https://nodejs.org/api/buffer.html
[10]: https://en.wikipedia.org/wiki/Adaptive_histogram_equalization#Contrast_Limited_AHE
[11]: https://nodejs.org/api/buffer.html

View File

@@ -4,6 +4,17 @@
Requires libvips v8.12.2
### v0.30.3 - 14th March 2022
* Allow `sharpen` options to be provided more consistently as an Object.
[#2561](https://github.com/lovell/sharp/issues/2561)
* Expose `x1`, `y2` and `y3` parameters of `sharpen` operation.
[#2935](https://github.com/lovell/sharp/issues/2935)
* Prevent double unpremultiply with some composite blend modes (regression in 0.30.2).
[#3118](https://github.com/lovell/sharp/issues/3118)
### v0.30.2 - 2nd March 2022
* Improve performance and accuracy when compositing multiple images.

BIN
docs/image/sharp-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 661 B

View File

@@ -6,19 +6,13 @@
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="Resize large images in common formats to smaller, web-friendly JPEG, PNG, WebP, GIF and AVIF images of varying dimensions">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; object-src 'none'; style-src 'unsafe-inline';
img-src 'unsafe-inline' data: https://pixel.plumbing/px/ https://cdn.jsdelivr.net/gh/lovell/ https://www.google-analytics.com;
img-src 'unsafe-inline' data: https://cdn.jsdelivr.net/gh/lovell/ https://www.google-analytics.com;
connect-src 'self' https://www.google-analytics.com;
script-src 'self' 'unsafe-inline' 'unsafe-eval'
https://www.google-analytics.com/analytics.js;">
<link rel="icon" type="image/png" href="https://pixel.plumbing/px/32x32/sharp-logo.svg">
<link rel="apple-touch-icon-precomposed" sizes="152x152" href="https://pixel.plumbing/px/152x152/sharp-logo.svg">
<link rel="apple-touch-icon-precomposed" sizes="144x144" href="https://pixel.plumbing/px/144x144/sharp-logo.svg">
<link rel="apple-touch-icon-precomposed" sizes="120x120" href="https://pixel.plumbing/px/120x120/sharp-logo.svg">
<link rel="apple-touch-icon-precomposed" sizes="114x114" href="https://pixel.plumbing/px/114x114/sharp-logo.svg">
<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="icon" type="image/svg+xml" href="https://cdn.jsdelivr.net/gh/lovell/sharp@main/docs/image/sharp-logo.svg">
<link rel="icon" type="image/png" sizes="32x32" href="https://cdn.jsdelivr.net/gh/lovell/sharp@main/docs/image/sharp-logo.png">
<link rel="author" href="/humans.txt" type="text/plain">
<link rel="dns-prefetch" href="https://pixel.plumbing">
<link rel="dns-prefetch" href="https://www.google-analytics.com">
<script type="application/ld+json">
{
@@ -124,7 +118,7 @@
router: { mode: 'history' },
logo: '<div style="display:flex;align-items:center">'
+ '<strong>sharp</strong> '
+ '<img src="https://pixel.plumbing/px/16x16/sharp-logo.svg" style="padding:8px" alt="#"> '
+ '<img src="https://cdn.jsdelivr.net/gh/lovell/sharp@main/docs/image/sharp-logo.svg" style="padding:8px" width="32" height="32" alt="#"> '
+ '<span style="opacity:0.8;white-space:pre" class="shorten-strapline">High performance </span> '
+ '<span style="opacity:0.8">Node.js image processing</span> '
+ '</div>',

View File

@@ -213,9 +213,11 @@ SHARP_IGNORE_GLOBAL_LIBVIPS=1 npm install --arch=x64 --platform=linux sharp
To get the best performance select the largest memory available.
A 1536 MB function provides ~12x more CPU time than a 128 MB function.
## Webpack
## Bundlers
Ensure sharp is added to the
### webpack
Ensure sharp is excluded from bundling via the
[externals](https://webpack.js.org/configuration/externals/)
configuration.
@@ -225,6 +227,25 @@ externals: {
}
```
### esbuild
Ensure sharp is excluded from bundling via the
[external](https://esbuild.github.io/api/#external)
configuration.
```js
buildSync({
entryPoints: ['app.js'],
bundle: true,
platform: 'node',
external: ['sharp'],
})
```
```sh
esbuild app.js --bundle --platform=node --external:sharp
```
## Worker threads
The main thread must call `require('sharp')`

File diff suppressed because one or more lines are too long

View File

@@ -19,6 +19,11 @@ const colourspace = {
* 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.
*
* @example
* const output = await sharp(input)
* .tint({ r: 255, g: 240, b: 16 })
* .toBuffer();
*
* @param {string|Object} rgb - parsed by the [color](https://www.npmjs.org/package/color) module to extract chroma values.
* @returns {Sharp}
* @throws {Error} Invalid parameter
@@ -37,6 +42,10 @@ function tint (rgb) {
* This may be overridden by other sharp operations such as `toColourspace('b-w')`,
* which will produce an output image containing one color channel.
* An alpha channel may be present, and will be unchanged by the operation.
*
* @example
* const output = await sharp(input).greyscale().toBuffer();
*
* @param {Boolean} [greyscale=true]
* @returns {Sharp}
*/

View File

@@ -56,6 +56,21 @@ const blend = {
* @since 0.22.0
*
* @example
* await sharp(background)
* .composite([
* { input: layer1, gravity: 'northwest' },
* { input: layer2, gravity: 'southeast' },
* ])
* .toFile('combined.png');
*
* @example
* const output = await sharp('input.gif', { animated: true })
* .composite([
* { input: 'overlay.png', tile: true, blend: 'saturate' }
* ])
* .toBuffer();
*
* @example
* sharp('input.png')
* .rotate(180)
* .resize(300)
@@ -89,6 +104,7 @@ const blend = {
* @param {Number} [images[].raw.width]
* @param {Number} [images[].raw.height]
* @param {Number} [images[].raw.channels]
* @param {boolean} [images[].animated=false] - Set to `true` to read all frames/pages of an animated image.
* @param {boolean} [images[].failOnError=true] - @see {@link /api-constructor#parameters|constructor parameters}
* @param {number|boolean} [images[].limitInputPixels=268402689] - @see {@link /api-constructor#parameters|constructor parameters}
* @returns {Sharp}

View File

@@ -186,8 +186,11 @@ const Sharp = function (input, options) {
medianSize: 0,
blurSigma: 0,
sharpenSigma: 0,
sharpenFlat: 1,
sharpenJagged: 2,
sharpenM1: 1,
sharpenM2: 2,
sharpenX1: 2,
sharpenY2: 10,
sharpenY3: 20,
threshold: 0,
thresholdGrayscale: true,
trimThreshold: 0,

View File

@@ -299,8 +299,8 @@ function _isStreamInput () {
*
* - `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
* - `width`: Number of pixels wide (EXIF orientation is not taken into consideration)
* - `height`: Number of pixels high (EXIF orientation is not taken into consideration)
* - `width`: Number of pixels wide (EXIF orientation is not taken into consideration, see example below)
* - `height`: Number of pixels high (EXIF orientation is not taken into consideration, see example below)
* - `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://libvips.github.io/libvips/API/current/VipsImage.html#VipsInterpretation)
* - `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK
* - `depth`: Name of pixel depth format e.g. `uchar`, `char`, `ushort`, `float` [...](https://libvips.github.io/libvips/API/current/VipsImage.html#VipsBandFormat)
@@ -343,6 +343,17 @@ function _isStreamInput () {
* // data contains a WebP image half the width and height of the original JPEG
* });
*
* @example
* // Based on EXIF rotation metadata, get the right-side-up width and height:
*
* const size = getNormalSize(await sharp(input).metadata());
*
* function getNormalSize({ width, height, orientation }) {
* return orientation || 0 >= 5
* ? { width: height, height: width }
* : { width, height };
* }
*
* @param {Function} [callback] - called with the arguments `(err, metadata)`
* @returns {Promise<Object>|Sharp}
*/
@@ -401,9 +412,9 @@ function metadata (callback) {
* - `maxX` (x-coordinate of one of the pixel where the maximum lies)
* - `maxY` (y-coordinate of one of the pixel where the maximum lies)
* - `isOpaque`: Is the image fully opaque? Will be `true` if the image has no alpha channel or if every pixel is fully opaque.
* - `entropy`: Histogram-based estimation of greyscale entropy, discarding alpha channel if any (experimental)
* - `sharpness`: Estimation of greyscale sharpness based on the standard deviation of a Laplacian convolution, discarding alpha channel if any (experimental)
* - `dominant`: Object containing most dominant sRGB colour based on a 4096-bin 3D histogram (experimental)
* - `entropy`: Histogram-based estimation of greyscale entropy, discarding alpha channel if any.
* - `sharpness`: Estimation of greyscale sharpness based on the standard deviation of a Laplacian convolution, discarding alpha channel if any.
* - `dominant`: Object containing most dominant sRGB colour based on a 4096-bin 3D histogram.
*
* **Note**: Statistics are derived from the original input image. Any operations performed on the image must first be
* written to a buffer in order to run `stats` on the result (see third example).

View File

@@ -63,6 +63,10 @@ function rotate (angle, options) {
/**
* Flip the image about the vertical Y axis. This always occurs after rotation, if any.
* The use of `flip` implies the removal of the EXIF `Orientation` tag, if any.
*
* @example
* const output = await sharp(input).flip().toBuffer();
*
* @param {Boolean} [flip=true]
* @returns {Sharp}
*/
@@ -74,6 +78,10 @@ function flip (flip) {
/**
* Flop the image about the horizontal X axis. This always occurs after rotation, if any.
* The use of `flop` implies the removal of the EXIF `Orientation` tag, if any.
*
* @example
* const output = await sharp(input).flop().toBuffer();
*
* @param {Boolean} [flop=true]
* @returns {Sharp}
*/
@@ -185,40 +193,107 @@ function affine (matrix, options) {
* When a `sigma` is provided, performs a slower, more accurate sharpen of the L channel in the LAB colour space.
* Separate control over the level of sharpening in "flat" and "jagged" areas is available.
*
* @param {number} [sigma] - the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
* @param {number} [flat=1.0] - the level of sharpening to apply to "flat" areas.
* @param {number} [jagged=2.0] - the level of sharpening to apply to "jagged" areas.
* See {@link https://www.libvips.org/API/current/libvips-convolution.html#vips-sharpen|libvips sharpen} operation.
*
* @example
* const data = await sharp(input).sharpen().toBuffer();
*
* @example
* const data = await sharp(input).sharpen({ sigma: 2 }).toBuffer();
*
* @example
* const data = await sharp(input)
* .sharpen({
* sigma: 2,
* m1: 0
* m2: 3,
* x1: 3,
* y2: 15,
* y3: 15,
* })
* .toBuffer();
*
* @param {Object|number} [options] - if present, is an Object with attributes or (deprecated) a number for `options.sigma`.
* @param {number} [options.sigma] - the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
* @param {number} [options.m1=1.0] - the level of sharpening to apply to "flat" areas.
* @param {number} [options.m2=2.0] - the level of sharpening to apply to "jagged" areas.
* @param {number} [options.x1=2.0] - threshold between "flat" and "jagged"
* @param {number} [options.y2=10.0] - maximum amount of brightening.
* @param {number} [options.y3=20.0] - maximum amount of darkening.
* @param {number} [flat] - (deprecated) see `options.m1`.
* @param {number} [jagged] - (deprecated) see `options.m2`.
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
function sharpen (sigma, flat, jagged) {
if (!is.defined(sigma)) {
function sharpen (options, flat, jagged) {
if (!is.defined(options)) {
// No arguments: default to mild sharpen
this.options.sharpenSigma = -1;
} else if (is.bool(sigma)) {
// Boolean argument: apply mild sharpen?
this.options.sharpenSigma = sigma ? -1 : 0;
} else if (is.number(sigma) && is.inRange(sigma, 0.01, 10000)) {
// Numeric argument: specific sigma
this.options.sharpenSigma = sigma;
// Control over flat areas
} else if (is.bool(options)) {
// Deprecated boolean argument: apply mild sharpen?
this.options.sharpenSigma = options ? -1 : 0;
} else if (is.number(options) && is.inRange(options, 0.01, 10000)) {
// Deprecated numeric argument: specific sigma
this.options.sharpenSigma = options;
// Deprecated control over flat areas
if (is.defined(flat)) {
if (is.number(flat) && is.inRange(flat, 0, 10000)) {
this.options.sharpenFlat = flat;
this.options.sharpenM1 = flat;
} else {
throw is.invalidParameterError('flat', 'number between 0 and 10000', flat);
}
}
// Control over jagged areas
// Deprecated control over jagged areas
if (is.defined(jagged)) {
if (is.number(jagged) && is.inRange(jagged, 0, 10000)) {
this.options.sharpenJagged = jagged;
this.options.sharpenM2 = jagged;
} else {
throw is.invalidParameterError('jagged', 'number between 0 and 10000', jagged);
}
}
} else if (is.plainObject(options)) {
if (is.number(options.sigma) && is.inRange(options.sigma, 0.01, 10000)) {
this.options.sharpenSigma = options.sigma;
} else {
throw is.invalidParameterError('options.sigma', 'number between 0.01 and 10000', options.sigma);
}
if (is.defined(options.m1)) {
if (is.number(options.m1) && is.inRange(options.m1, 0, 10000)) {
this.options.sharpenM1 = options.m1;
} else {
throw is.invalidParameterError('options.m1', 'number between 0 and 10000', options.m1);
}
}
if (is.defined(options.m2)) {
if (is.number(options.m2) && is.inRange(options.m2, 0, 10000)) {
this.options.sharpenM2 = options.m2;
} else {
throw is.invalidParameterError('options.m2', 'number between 0 and 10000', options.m2);
}
}
if (is.defined(options.x1)) {
if (is.number(options.x1) && is.inRange(options.x1, 0, 10000)) {
this.options.sharpenX1 = options.x1;
} else {
throw is.invalidParameterError('options.x1', 'number between 0 and 10000', options.x1);
}
}
if (is.defined(options.y2)) {
if (is.number(options.y2) && is.inRange(options.y2, 0, 10000)) {
this.options.sharpenY2 = options.y2;
} else {
throw is.invalidParameterError('options.y2', 'number between 0 and 10000', options.y2);
}
}
if (is.defined(options.y3)) {
if (is.number(options.y3) && is.inRange(options.y3, 0, 10000)) {
this.options.sharpenY3 = options.y3;
} else {
throw is.invalidParameterError('options.y3', 'number between 0 and 10000', options.y3);
}
}
} else {
throw is.invalidParameterError('sigma', 'number between 0.01 and 10000', sigma);
throw is.invalidParameterError('sigma', 'number between 0.01 and 10000', options);
}
return this;
}
@@ -226,6 +301,13 @@ function sharpen (sigma, flat, jagged) {
/**
* Apply median filter.
* When used without parameters the default window is 3x3.
*
* @example
* const output = await sharp(input).median().toBuffer();
*
* @example
* const output = await sharp(input).median(5).toBuffer();
*
* @param {number} [size=3] square mask size: size x size
* @returns {Sharp}
* @throws {Error} Invalid parameters
@@ -356,6 +438,10 @@ function negate (options) {
/**
* Enhance output image contrast by stretching its luminance to cover the full dynamic range.
*
* @example
* const output = await sharp(input).normalise().toBuffer();
*
* @param {Boolean} [normalise=true]
* @returns {Sharp}
*/
@@ -366,6 +452,10 @@ function normalise (normalise) {
/**
* Alternative spelling of normalise.
*
* @example
* const output = await sharp(input).normalize().toBuffer();
*
* @param {Boolean} [normalize=true]
* @returns {Sharp}
*/
@@ -381,6 +471,14 @@ function normalize (normalize) {
*
* @since 0.28.3
*
* @example
* const output = await sharp(input)
* .clahe({
* width: 3,
* height: 3,
* })
* .toBuffer();
*
* @param {Object} options
* @param {number} options.width - integer width of the region in pixels.
* @param {number} options.height - integer height of the region in pixels.
@@ -590,28 +688,38 @@ function recomb (inputMatrix) {
* @since 0.22.1
*
* @example
* sharp(input)
* // increase brightness by a factor of 2
* const output = await sharp(input)
* .modulate({
* brightness: 2 // increase brightness by a factor of 2
* });
* brightness: 2
* })
* .toBuffer();
*
* sharp(input)
* @example
* // hue-rotate by 180 degrees
* const output = await sharp(input)
* .modulate({
* hue: 180 // hue-rotate by 180 degrees
* });
* hue: 180
* })
* .toBuffer();
*
* sharp(input)
* @example
* // increase lightness by +50
* const output = await sharp(input)
* .modulate({
* lightness: 50 // increase lightness by +50
* });
* lightness: 50
* })
* .toBuffer();
*
* @example
* // decreate brightness and saturation while also hue-rotating by 90 degrees
* sharp(input)
* const output = await sharp(input)
* .modulate({
* brightness: 0.5,
* saturation: 0.5,
* hue: 90
* });
* hue: 90,
* })
* .toBuffer();
*
* @param {Object} [options]
* @param {number} [options.brightness] Brightness multiplier

View File

@@ -21,7 +21,7 @@ try {
'- Consult the installation documentation: https://sharp.pixelplumbing.com/install'
);
// Check loaded
if (process.platform === 'win32') {
if (process.platform === 'win32' || /symbol/.test(err.message)) {
const loadedModule = Object.keys(require.cache).find((i) => /[\\/]build[\\/]Release[\\/]sharp(.*)\.node$/.test(i));
if (loadedModule) {
const [, loadedPackage] = loadedModule.match(/node_modules[\\/]([^\\/]+)[\\/]/);

View File

@@ -1,7 +1,7 @@
{
"name": "sharp",
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, GIF, AVIF and TIFF images",
"version": "0.30.2",
"version": "0.30.3",
"author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://github.com/lovell/sharp",
"contributors": [
@@ -143,7 +143,7 @@
"exif-reader": "^1.0.3",
"icc": "^2.0.0",
"license-checker": "^25.0.1",
"mocha": "^9.2.1",
"mocha": "^9.2.2",
"mock-fs": "^5.1.2",
"nyc": "^15.1.0",
"prebuild": "^11.0.3",

View File

@@ -209,7 +209,8 @@ namespace sharp {
/*
* Sharpen flat and jagged areas. Use sigma of -1.0 for fast sharpen.
*/
VImage Sharpen(VImage image, double const sigma, double const flat, double const jagged) {
VImage Sharpen(VImage image, double const sigma, double const m1, double const m2,
double const x1, double const y2, double const y3) {
if (sigma == -1.0) {
// Fast, mild sharpen
VImage sharpen = VImage::new_matrixv(3, 3,
@@ -224,8 +225,14 @@ namespace sharp {
if (colourspaceBeforeSharpen == VIPS_INTERPRETATION_RGB) {
colourspaceBeforeSharpen = VIPS_INTERPRETATION_sRGB;
}
return image.sharpen(
VImage::option()->set("sigma", sigma)->set("m1", flat)->set("m2", jagged))
return image
.sharpen(VImage::option()
->set("sigma", sigma)
->set("m1", m1)
->set("m2", m2)
->set("x1", x1)
->set("y2", y2)
->set("y3", y3))
.colourspace(colourspaceBeforeSharpen);
}
}

View File

@@ -64,7 +64,8 @@ namespace sharp {
/*
* Sharpen flat and jagged areas. Use sigma of -1.0 for fast sharpen.
*/
VImage Sharpen(VImage image, double const sigma, double const flat, double const jagged);
VImage Sharpen(VImage image, double const sigma, double const m1, double const m2,
double const x1, double const y2, double const y3);
/*
Threshold an image

View File

@@ -354,7 +354,7 @@ class PipelineWorker : public Napi::AsyncWorker {
}
bool const shouldPremultiplyAlpha = sharp::HasAlpha(image) &&
(shouldResize || shouldBlur || shouldConv || shouldSharpen || shouldComposite);
(shouldResize || shouldBlur || shouldConv || shouldSharpen);
// Premultiply image alpha channel before all transformations to avoid
// dark fringing around bright pixels
@@ -577,7 +577,8 @@ class PipelineWorker : public Napi::AsyncWorker {
// Sharpen
if (shouldSharpen) {
image = sharp::Sharpen(image, baton->sharpenSigma, baton->sharpenFlat, baton->sharpenJagged);
image = sharp::Sharpen(image, baton->sharpenSigma, baton->sharpenM1, baton->sharpenM2,
baton->sharpenX1, baton->sharpenY2, baton->sharpenY3);
}
// Composite
@@ -1400,8 +1401,11 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
baton->lightness = sharp::AttrAsDouble(options, "lightness");
baton->medianSize = sharp::AttrAsUint32(options, "medianSize");
baton->sharpenSigma = sharp::AttrAsDouble(options, "sharpenSigma");
baton->sharpenFlat = sharp::AttrAsDouble(options, "sharpenFlat");
baton->sharpenJagged = sharp::AttrAsDouble(options, "sharpenJagged");
baton->sharpenM1 = sharp::AttrAsDouble(options, "sharpenM1");
baton->sharpenM2 = sharp::AttrAsDouble(options, "sharpenM2");
baton->sharpenX1 = sharp::AttrAsDouble(options, "sharpenX1");
baton->sharpenY2 = sharp::AttrAsDouble(options, "sharpenY2");
baton->sharpenY3 = sharp::AttrAsDouble(options, "sharpenY3");
baton->threshold = sharp::AttrAsInt32(options, "threshold");
baton->thresholdGrayscale = sharp::AttrAsBool(options, "thresholdGrayscale");
baton->trimThreshold = sharp::AttrAsDouble(options, "trimThreshold");

View File

@@ -90,8 +90,11 @@ struct PipelineBaton {
double lightness;
int medianSize;
double sharpenSigma;
double sharpenFlat;
double sharpenJagged;
double sharpenM1;
double sharpenM2;
double sharpenX1;
double sharpenY2;
double sharpenY3;
int threshold;
bool thresholdGrayscale;
double trimThreshold;
@@ -234,8 +237,11 @@ struct PipelineBaton {
lightness(0),
medianSize(0),
sharpenSigma(0.0),
sharpenFlat(1.0),
sharpenJagged(2.0),
sharpenM1(1.0),
sharpenM2(2.0),
sharpenX1(2.0),
sharpenY2(10.0),
sharpenY3(20.0),
threshold(0),
thresholdGrayscale(true),
trimThreshold(0.0),

Binary file not shown.

Before

Width:  |  Height:  |  Size: 320 B

After

Width:  |  Height:  |  Size: 320 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 291 B

After

Width:  |  Height:  |  Size: 292 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 292 B

After

Width:  |  Height:  |  Size: 292 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 288 B

After

Width:  |  Height:  |  Size: 286 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 286 B

After

Width:  |  Height:  |  Size: 285 B

View File

@@ -158,6 +158,6 @@ describe('Extend', function () {
})
.raw()
.toBuffer();
assert.deepStrictEqual(Array.from(data), [191, 25, 65, 204, 238, 31, 82, 204]);
assert.deepStrictEqual(Array.from(data), [191, 25, 66, 204, 191, 25, 66, 204]);
});
});

View File

@@ -48,15 +48,8 @@ describe('JP2 output', () => {
.resize(320, 240)
.toBuffer(function (err, buffer80) {
if (err) throw err;
sharp(fixtures.inputJp2)
.resize(320, 240)
.jp2({ quality: 90 })
.toBuffer(function (err, buffer90) {
if (err) throw err;
assert(buffer70.length < buffer80.length);
assert(buffer80.length < buffer90.length);
done();
});
assert(buffer70.length < buffer80.length);
done();
});
});
});

View File

@@ -45,6 +45,22 @@ describe('Sharpen', function () {
});
});
it('sigma=3.5, m1=2, m2=4', (done) => {
sharp(fixtures.inputJpg)
.resize(320, 240)
.sharpen({ sigma: 3.5, m1: 2, m2: 4 })
.toBuffer()
.then(data => fixtures.assertSimilar(fixtures.expected('sharpen-5-2-4.jpg'), data, done));
});
it('sigma=3.5, m1=2, m2=4, x1=2, y2=5, y3=25', (done) => {
sharp(fixtures.inputJpg)
.resize(320, 240)
.sharpen({ sigma: 3.5, m1: 2, m2: 4, x1: 2, y2: 5, y3: 25 })
.toBuffer()
.then(data => fixtures.assertSimilar(fixtures.expected('sharpen-5-2-4.jpg'), data, done));
});
if (!process.env.SHARP_TEST_WITHOUT_CACHE) {
it('specific radius/levels with alpha channel', function (done) {
sharp(fixtures.inputPngWithTransparency)
@@ -92,6 +108,36 @@ describe('Sharpen', function () {
});
});
it('invalid options.sigma', () => assert.throws(
() => sharp().sharpen({ sigma: -1 }),
/Expected number between 0\.01 and 10000 for options\.sigma but received -1 of type number/
));
it('invalid options.m1', () => assert.throws(
() => sharp().sharpen({ sigma: 1, m1: -1 }),
/Expected number between 0 and 10000 for options\.m1 but received -1 of type number/
));
it('invalid options.m2', () => assert.throws(
() => sharp().sharpen({ sigma: 1, m2: -1 }),
/Expected number between 0 and 10000 for options\.m2 but received -1 of type number/
));
it('invalid options.x1', () => assert.throws(
() => sharp().sharpen({ sigma: 1, x1: -1 }),
/Expected number between 0 and 10000 for options\.x1 but received -1 of type number/
));
it('invalid options.y2', () => assert.throws(
() => sharp().sharpen({ sigma: 1, y2: -1 }),
/Expected number between 0 and 10000 for options\.y2 but received -1 of type number/
));
it('invalid options.y3', () => assert.throws(
() => sharp().sharpen({ sigma: 1, y3: -1 }),
/Expected number between 0 and 10000 for options\.y3 but received -1 of type number/
));
it('sharpened image is larger than non-sharpened', function (done) {
sharp(fixtures.inputJpg)
.resize(320, 240)

View File

@@ -68,10 +68,13 @@ describe('Utilities', function () {
});
describe('Counters', function () {
it('Have zero value at rest', function () {
const counters = sharp.counters();
assert.strictEqual(0, counters.queue);
assert.strictEqual(0, counters.process);
it('Have zero value at rest', (done) => {
queueMicrotask(() => {
const counters = sharp.counters();
assert.strictEqual(0, counters.queue);
assert.strictEqual(0, counters.process);
done();
});
});
});