Compare commits
19 Commits
v0.33.0-al
...
v0.33.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
545e09fad2 | ||
|
|
133dc56ff4 | ||
|
|
9c877d93fa | ||
|
|
7ad86fed03 | ||
|
|
31cf07f0ba | ||
|
|
4ffb48711a | ||
|
|
3da96a86e6 | ||
|
|
45ed9ea9bf | ||
|
|
61057f25bc | ||
|
|
68ef72cb61 | ||
|
|
ef32dccb83 | ||
|
|
e78200cc84 | ||
|
|
3f7313d031 | ||
|
|
139e4b9f65 | ||
|
|
9680f00ddd | ||
|
|
0b0ebfe6f8 | ||
|
|
5aadb8294c | ||
|
|
ace1681886 | ||
|
|
75ef61a958 |
1
.github/workflows/ci.yml
vendored
@@ -185,6 +185,7 @@ jobs:
|
||||
emmake npm run package-from-local-build
|
||||
npm pkg set "optionalDependencies.@img/sharp-wasm32=file:./npm/wasm32"
|
||||
npm run clean
|
||||
rm -rf node_modules/@img/sharp-linux-x64
|
||||
npm install --cpu=wasm32
|
||||
npm test
|
||||
- name: Prebuild
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
## tint
|
||||
> tint(rgb) ⇒ <code>Sharp</code>
|
||||
> tint(tint) ⇒ <code>Sharp</code>
|
||||
|
||||
Tint the image using the provided chroma while preserving the image luminance.
|
||||
Tint the image using the provided colour.
|
||||
An alpha channel may be present and will be unchanged by the operation.
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ An alpha channel may be present and will be unchanged by the operation.
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| rgb | <code>string</code> \| <code>Object</code> | parsed by the [color](https://www.npmjs.org/package/color) module to extract chroma values. |
|
||||
| tint | <code>string</code> \| <code>Object</code> | Parsed by the [color](https://www.npmjs.org/package/color) module. |
|
||||
|
||||
**Example**
|
||||
```js
|
||||
|
||||
@@ -17,6 +17,10 @@ Non-critical problems encountered during processing are emitted as `warning` eve
|
||||
|
||||
Implements the [stream.Duplex](http://nodejs.org/api/stream.html#stream_class_stream_duplex) class.
|
||||
|
||||
When loading more than one page/frame of an animated image,
|
||||
these are combined as a vertically-stacked "toilet roll" image
|
||||
where the overall height is the `pageHeight` multiplied by the number of `pages`.
|
||||
|
||||
**Throws**:
|
||||
|
||||
- <code>Error</code> Invalid parameters
|
||||
|
||||
@@ -111,17 +111,157 @@ await sharp(pixelArray, { raw: { width, height, channels } })
|
||||
```
|
||||
|
||||
|
||||
## keepExif
|
||||
> keepExif() ⇒ <code>Sharp</code>
|
||||
|
||||
Keep all EXIF metadata from the input image in the output image.
|
||||
|
||||
EXIF metadata is unsupported for TIFF output.
|
||||
|
||||
|
||||
**Since**: 0.33.0
|
||||
**Example**
|
||||
```js
|
||||
const outputWithExif = await sharp(inputWithExif)
|
||||
.keepExif()
|
||||
.toBuffer();
|
||||
```
|
||||
|
||||
|
||||
## withExif
|
||||
> withExif(exif) ⇒ <code>Sharp</code>
|
||||
|
||||
Set EXIF metadata in the output image, ignoring any EXIF in the input image.
|
||||
|
||||
|
||||
**Throws**:
|
||||
|
||||
- <code>Error</code> Invalid parameters
|
||||
|
||||
**Since**: 0.33.0
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| exif | <code>Object.<string, Object.<string, string>></code> | Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data. |
|
||||
|
||||
**Example**
|
||||
```js
|
||||
const dataWithExif = await sharp(input)
|
||||
.withExif({
|
||||
IFD0: {
|
||||
Copyright: 'The National Gallery'
|
||||
},
|
||||
IFD3: {
|
||||
GPSLatitudeRef: 'N',
|
||||
GPSLatitude: '51/1 30/1 3230/100',
|
||||
GPSLongitudeRef: 'W',
|
||||
GPSLongitude: '0/1 7/1 4366/100'
|
||||
}
|
||||
})
|
||||
.toBuffer();
|
||||
```
|
||||
|
||||
|
||||
## withExifMerge
|
||||
> withExifMerge(exif) ⇒ <code>Sharp</code>
|
||||
|
||||
Update EXIF metadata from the input image in the output image.
|
||||
|
||||
|
||||
**Throws**:
|
||||
|
||||
- <code>Error</code> Invalid parameters
|
||||
|
||||
**Since**: 0.33.0
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| exif | <code>Object.<string, Object.<string, string>></code> | Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data. |
|
||||
|
||||
**Example**
|
||||
```js
|
||||
const dataWithMergedExif = await sharp(inputWithExif)
|
||||
.withExifMerge({
|
||||
IFD0: {
|
||||
Copyright: 'The National Gallery'
|
||||
}
|
||||
})
|
||||
.toBuffer();
|
||||
```
|
||||
|
||||
|
||||
## keepIccProfile
|
||||
> keepIccProfile() ⇒ <code>Sharp</code>
|
||||
|
||||
Keep ICC profile from the input image in the output image.
|
||||
|
||||
Where necessary, will attempt to convert the output colour space to match the profile.
|
||||
|
||||
|
||||
**Since**: 0.33.0
|
||||
**Example**
|
||||
```js
|
||||
const outputWithIccProfile = await sharp(inputWithIccProfile)
|
||||
.keepIccProfile()
|
||||
.toBuffer();
|
||||
```
|
||||
|
||||
|
||||
## withIccProfile
|
||||
> withIccProfile(icc, [options]) ⇒ <code>Sharp</code>
|
||||
|
||||
Transform using an ICC profile and attach to the output image.
|
||||
|
||||
This can either be an absolute filesystem path or
|
||||
built-in profile name (`srgb`, `p3`, `cmyk`).
|
||||
|
||||
|
||||
**Throws**:
|
||||
|
||||
- <code>Error</code> Invalid parameters
|
||||
|
||||
**Since**: 0.33.0
|
||||
|
||||
| Param | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| icc | <code>string</code> | | Absolute filesystem path to output ICC profile or built-in profile name (srgb, p3, cmyk). |
|
||||
| [options] | <code>Object</code> | | |
|
||||
| [options.attach] | <code>number</code> | <code>true</code> | Should the ICC profile be included in the output image metadata? |
|
||||
|
||||
**Example**
|
||||
```js
|
||||
const outputWithP3 = await sharp(input)
|
||||
.withIccProfile('p3')
|
||||
.toBuffer();
|
||||
```
|
||||
|
||||
|
||||
## keepMetadata
|
||||
> keepMetadata() ⇒ <code>Sharp</code>
|
||||
|
||||
Keep all metadata (EXIF, ICC, XMP, IPTC) from the input image in the output image.
|
||||
|
||||
The default behaviour, when `keepMetadata` is not used, is to convert to the device-independent
|
||||
sRGB colour space and strip all metadata, including the removal of any ICC profile.
|
||||
|
||||
|
||||
**Since**: 0.33.0
|
||||
**Example**
|
||||
```js
|
||||
const outputWithMetadata = await sharp(inputWithMetadata)
|
||||
.keepMetadata()
|
||||
.toBuffer();
|
||||
```
|
||||
|
||||
|
||||
## withMetadata
|
||||
> withMetadata([options]) ⇒ <code>Sharp</code>
|
||||
|
||||
Include all metadata (EXIF, XMP, IPTC) from the input image in the output image.
|
||||
This will also convert to and add a web-friendly sRGB ICC profile if appropriate,
|
||||
unless a custom output profile is provided.
|
||||
Keep most metadata (EXIF, XMP, IPTC) from the input image in the output image.
|
||||
|
||||
The default behaviour, when `withMetadata` is not used, is to convert to the device-independent
|
||||
sRGB colour space and strip all metadata, including the removal of any ICC profile.
|
||||
This will also convert to and add a web-friendly sRGB ICC profile if appropriate.
|
||||
|
||||
EXIF metadata is unsupported for TIFF output.
|
||||
Allows orientation and density to be set or updated.
|
||||
|
||||
|
||||
**Throws**:
|
||||
@@ -129,38 +269,16 @@ EXIF metadata is unsupported for TIFF output.
|
||||
- <code>Error</code> Invalid parameters
|
||||
|
||||
|
||||
| Param | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| [options] | <code>Object</code> | | |
|
||||
| [options.orientation] | <code>number</code> | | value between 1 and 8, used to update the EXIF `Orientation` tag. |
|
||||
| [options.icc] | <code>string</code> | <code>"'srgb'"</code> | Filesystem path to output ICC profile, relative to `process.cwd()`, defaults to built-in sRGB. |
|
||||
| [options.exif] | <code>Object.<Object></code> | <code>{}</code> | Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data. |
|
||||
| [options.density] | <code>number</code> | | Number of pixels per inch (DPI). |
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [options] | <code>Object</code> | |
|
||||
| [options.orientation] | <code>number</code> | Used to update the EXIF `Orientation` tag, integer between 1 and 8. |
|
||||
| [options.density] | <code>number</code> | Number of pixels per inch (DPI). |
|
||||
|
||||
**Example**
|
||||
```js
|
||||
sharp('input.jpg')
|
||||
const outputSrgbWithMetadata = await sharp(inputRgbWithMetadata)
|
||||
.withMetadata()
|
||||
.toFile('output-with-metadata.jpg')
|
||||
.then(info => { ... });
|
||||
```
|
||||
**Example**
|
||||
```js
|
||||
// Set output EXIF metadata
|
||||
const data = await sharp(input)
|
||||
.withMetadata({
|
||||
exif: {
|
||||
IFD0: {
|
||||
Copyright: 'The National Gallery'
|
||||
},
|
||||
IFD3: {
|
||||
GPSLatitudeRef: 'N',
|
||||
GPSLatitude: '51/1 30/1 3230/100',
|
||||
GPSLongitudeRef: 'W',
|
||||
GPSLongitude: '0/1 7/1 4366/100'
|
||||
}
|
||||
}
|
||||
})
|
||||
.toBuffer();
|
||||
```
|
||||
**Example**
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
Requires libvips v8.15.0
|
||||
|
||||
### v0.33.0 - TBD
|
||||
### v0.33.0 - 29th November 2023
|
||||
|
||||
* Drop support for Node.js 14 and 16, now requires Node.js >= 18.17.0
|
||||
|
||||
@@ -14,12 +14,18 @@ Requires libvips v8.15.0
|
||||
|
||||
* Remove `sharp.vendor`.
|
||||
|
||||
* Partially deprecate `withMetadata()`, use `withExif()` and `withIccProfile()`.
|
||||
|
||||
* Add experimental support for WebAssembly-based runtimes.
|
||||
[@RReverser](https://github.com/RReverser)
|
||||
|
||||
* Options for `trim` operation must be an Object, add new `lineArt` option.
|
||||
[#2363](https://github.com/lovell/sharp/issues/2363)
|
||||
|
||||
* Improve luminance of `tint` operation with weighting function.
|
||||
[#3338](https://github.com/lovell/sharp/issues/3338)
|
||||
[@jcupitt](https://github.com/jcupitt)
|
||||
|
||||
* Ensure all `Error` objects contain a `stack` property.
|
||||
[#3653](https://github.com/lovell/sharp/issues/3653)
|
||||
|
||||
@@ -37,6 +43,9 @@ Requires libvips v8.15.0
|
||||
[#3823](https://github.com/lovell/sharp/pull/3823)
|
||||
[@uhthomas](https://github.com/uhthomas)
|
||||
|
||||
* Add more fine-grained control over output metadata.
|
||||
[#3824](https://github.com/lovell/sharp/issues/3824)
|
||||
|
||||
* Ensure multi-page extract remains sequential.
|
||||
[#3837](https://github.com/lovell/sharp/issues/3837)
|
||||
|
||||
|
||||
@@ -42,6 +42,36 @@ Ready-compiled sharp and libvips binaries are provided for use on the most commo
|
||||
This provides support for the
|
||||
JPEG, PNG, WebP, AVIF (limited to 8-bit depth), TIFF, GIF and SVG (input) image formats.
|
||||
|
||||
## Cross-platform
|
||||
|
||||
At install time, package managers will automatically select prebuilt binaries for the current OS platform and CPU architecture, where available.
|
||||
|
||||
Some package managers support multiple platforms and architectures within the same installation tree.
|
||||
|
||||
### npm
|
||||
|
||||
Use the `--os`, `--cpu` and `--libc` flags:
|
||||
|
||||
Example to support both Intel and ARM CPUs on macOS:
|
||||
```sh
|
||||
npm install --cpu=x64 --os=darwin sharp
|
||||
npm install --cpu=arm64 --os=darwin sharp
|
||||
```
|
||||
|
||||
Example to support both glibc and musl-based Linux:
|
||||
```sh
|
||||
npm install --cpu=x64 --os=linux sharp
|
||||
npm install --cpu=x64 --os=linux --libc=musl sharp
|
||||
```
|
||||
|
||||
### yarn
|
||||
|
||||
Use the [supportedArchitectures](https://yarnpkg.com/configuration/yarnrc#supportedArchitectures) configuration.
|
||||
|
||||
### pnpm
|
||||
|
||||
Use the [supportedArchitectures](https://pnpm.io/package_json#pnpmsupportedarchitectures) configuration.
|
||||
|
||||
## Custom libvips
|
||||
|
||||
To use a custom, globally-installed version of libvips instead of the provided binaries,
|
||||
@@ -83,6 +113,10 @@ can be used to configure the target environment.
|
||||
Experimental support is provided for runtime environments that provide
|
||||
multi-threaded Wasm via Workers.
|
||||
|
||||
Use in web browsers is unsupported.
|
||||
|
||||
Native text rendering is unsupported.
|
||||
|
||||
```sh
|
||||
npm install --cpu=wasm32 sharp
|
||||
```
|
||||
|
||||
@@ -9,26 +9,26 @@ The I/O limits of the relevant (de)compression library will generally determine
|
||||
|
||||
## Contenders
|
||||
|
||||
* [jimp](https://www.npmjs.com/package/jimp) v0.22.7 - Image processing in pure JavaScript.
|
||||
* [jimp](https://www.npmjs.com/package/jimp) v0.22.10 - Image processing in pure JavaScript.
|
||||
* [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.25.0 - Fully featured wrapper around GraphicsMagick's `gm` command line utility.
|
||||
* [@squoosh/lib](https://www.npmjs.com/package/@squoosh/lib) v0.4.0 - Image libraries transpiled to WebAssembly, includes GPLv3 code, but "*Project no longer maintained*".
|
||||
* [@squoosh/lib](https://www.npmjs.com/package/@squoosh/lib) v0.5.3 - Image libraries transpiled to WebAssembly, includes GPLv3 code, but "*Project no longer maintained*".
|
||||
* [@squoosh/cli](https://www.npmjs.com/package/@squoosh/cli) v0.7.3 - Command line wrapper around `@squoosh/lib`, avoids GPLv3 by spawning process, but "*Project no longer maintained*".
|
||||
* sharp v0.32.0 / libvips v8.14.2 - Caching within libvips disabled to ensure a fair comparison.
|
||||
* sharp v0.33.0 / libvips v8.15.0 - Caching within libvips disabled to ensure a fair comparison.
|
||||
|
||||
## Environment
|
||||
|
||||
### AMD64
|
||||
|
||||
* AWS EC2 us-east-2 [c6a.xlarge](https://aws.amazon.com/ec2/instance-types/c6a/) (4x AMD EPYC 7R13)
|
||||
* Ubuntu 22.04 20230303 (ami-0122295b0eb922138)
|
||||
* Node.js 16.19.1
|
||||
* AWS EC2 us-east-2 [c7a.xlarge](https://aws.amazon.com/ec2/instance-types/c7a/) (4x AMD EPYC 9R14)
|
||||
* Ubuntu 23.10 [13f233a16be2](https://hub.docker.com/layers/library/ubuntu/23.10/images/sha256-13f233a16be210b57907b98b0d927ceff7571df390701e14fe1f3901b2c4a4d7)
|
||||
* Node.js 20.10.0
|
||||
|
||||
### ARM64
|
||||
|
||||
* AWS EC2 us-east-2 [c7g.xlarge](https://aws.amazon.com/ec2/instance-types/c7g/) (4x ARM Graviton3)
|
||||
* Ubuntu 22.04 20230303 (ami-0af198159897e7a29)
|
||||
* Node.js 16.19.1
|
||||
* Ubuntu 23.10 [7708743264cb](https://hub.docker.com/layers/library/ubuntu/23.10/images/sha256-7708743264cbb7f6cf7fc13e915faece45a6cdda455748bc55e58e8de3d27b63)
|
||||
* Node.js 20.10.0
|
||||
|
||||
## Task: JPEG
|
||||
|
||||
@@ -43,28 +43,28 @@ Note: jimp does not support Lanczos 3, bicubic resampling used instead.
|
||||
| Module | Input | Output | Ops/sec | Speed-up |
|
||||
| :----------------- | :----- | :----- | ------: | -------: |
|
||||
| jimp | buffer | buffer | 0.84 | 1.0 |
|
||||
| squoosh-cli | file | file | 1.07 | 1.3 |
|
||||
| squoosh-lib | buffer | buffer | 1.82 | 2.2 |
|
||||
| gm | buffer | buffer | 8.41 | 10.0 |
|
||||
| gm | file | file | 8.45 | 10.0 |
|
||||
| imagemagick | file | file | 8.77 | 10.4 |
|
||||
| sharp | stream | stream | 36.36 | 43.3 |
|
||||
| sharp | file | file | 38.67 | 46.0 |
|
||||
| sharp | buffer | buffer | 39.44 | 47.0 |
|
||||
| squoosh-cli | file | file | 1.54 | 1.8 |
|
||||
| squoosh-lib | buffer | buffer | 2.24 | 2.7 |
|
||||
| imagemagick | file | file | 11.75 | 14.0 |
|
||||
| gm | buffer | buffer | 12.66 | 15.1 |
|
||||
| gm | file | file | 12.72 | 15.1 |
|
||||
| sharp | stream | stream | 48.31 | 57.5 |
|
||||
| sharp | file | file | 51.42 | 61.2 |
|
||||
| sharp | buffer | buffer | 52.41 | 62.4 |
|
||||
|
||||
#### Results: JPEG (ARM64)
|
||||
|
||||
| Module | Input | Output | Ops/sec | Speed-up |
|
||||
| :----------------- | :----- | :----- | ------: | -------: |
|
||||
| jimp | buffer | buffer | 1.02 | 1.0 |
|
||||
| squoosh-cli | file | file | 1.11 | 1.1 |
|
||||
| squoosh-lib | buffer | buffer | 2.08 | 2.0 |
|
||||
| gm | buffer | buffer | 8.80 | 8.6 |
|
||||
| gm | file | file | 10.05 | 9.9 |
|
||||
| imagemagick | file | file | 10.28 | 10.1 |
|
||||
| sharp | stream | stream | 26.87 | 26.3 |
|
||||
| sharp | file | file | 27.88 | 27.3 |
|
||||
| sharp | buffer | buffer | 28.40 | 27.8 |
|
||||
| jimp | buffer | buffer | 0.88 | 1.0 |
|
||||
| squoosh-cli | file | file | 1.18 | 1.3 |
|
||||
| squoosh-lib | buffer | buffer | 1.99 | 2.3 |
|
||||
| gm | buffer | buffer | 6.06 | 6.9 |
|
||||
| gm | file | file | 10.81 | 12.3 |
|
||||
| imagemagick | file | file | 10.95 | 12.4 |
|
||||
| sharp | stream | stream | 33.15 | 37.7 |
|
||||
| sharp | file | file | 34.99 | 39.8 |
|
||||
| sharp | buffer | buffer | 36.05 | 41.0 |
|
||||
|
||||
## Task: PNG
|
||||
|
||||
@@ -80,25 +80,25 @@ Note: jimp does not support premultiply/unpremultiply.
|
||||
|
||||
| Module | Input | Output | Ops/sec | Speed-up |
|
||||
| :----------------- | :----- | :----- | ------: | -------: |
|
||||
| squoosh-cli | file | file | 0.40 | 1.0 |
|
||||
| squoosh-lib | buffer | buffer | 0.47 | 1.2 |
|
||||
| gm | file | file | 6.47 | 16.2 |
|
||||
| jimp | buffer | buffer | 6.60 | 16.5 |
|
||||
| imagemagick | file | file | 7.08 | 17.7 |
|
||||
| sharp | file | file | 17.80 | 44.5 |
|
||||
| sharp | buffer | buffer | 18.02 | 45.0 |
|
||||
| squoosh-cli | file | file | 0.34 | 1.0 |
|
||||
| squoosh-lib | buffer | buffer | 0.51 | 1.5 |
|
||||
| jimp | buffer | buffer | 3.59 | 10.6 |
|
||||
| gm | file | file | 8.54 | 25.1 |
|
||||
| imagemagick | file | file | 9.23 | 27.1 |
|
||||
| sharp | file | file | 25.43 | 74.8 |
|
||||
| sharp | buffer | buffer | 25.70 | 75.6 |
|
||||
|
||||
### Results: PNG (ARM64)
|
||||
|
||||
| Module | Input | Output | Ops/sec | Speed-up |
|
||||
| :----------------- | :----- | :----- | ------: | -------: |
|
||||
| squoosh-cli | file | file | 0.40 | 1.0 |
|
||||
| squoosh-lib | buffer | buffer | 0.48 | 1.2 |
|
||||
| gm | file | file | 7.20 | 18.0 |
|
||||
| jimp | buffer | buffer | 7.62 | 19.1 |
|
||||
| imagemagick | file | file | 7.96 | 19.9 |
|
||||
| sharp | file | file | 12.97 | 32.4 |
|
||||
| sharp | buffer | buffer | 13.12 | 32.8 |
|
||||
| squoosh-cli | file | file | 0.33 | 1.0 |
|
||||
| squoosh-lib | buffer | buffer | 0.46 | 1.4 |
|
||||
| jimp | buffer | buffer | 3.51 | 10.6 |
|
||||
| gm | file | file | 7.47 | 22.6 |
|
||||
| imagemagick | file | file | 8.06 | 24.4 |
|
||||
| sharp | file | file | 17.31 | 52.5 |
|
||||
| sharp | buffer | buffer | 17.66 | 53.5 |
|
||||
|
||||
## Running the benchmark test
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ const colourspace = {
|
||||
};
|
||||
|
||||
/**
|
||||
* Tint the image using the provided chroma while preserving the image luminance.
|
||||
* Tint the image using the provided colour.
|
||||
* An alpha channel may be present and will be unchanged by the operation.
|
||||
*
|
||||
* @example
|
||||
@@ -27,14 +27,12 @@ const colourspace = {
|
||||
* .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.
|
||||
* @param {string|Object} tint - Parsed by the [color](https://www.npmjs.org/package/color) module.
|
||||
* @returns {Sharp}
|
||||
* @throws {Error} Invalid parameter
|
||||
*/
|
||||
function tint (rgb) {
|
||||
const colour = color(rgb);
|
||||
this.options.tintA = colour.a();
|
||||
this.options.tintB = colour.b();
|
||||
function tint (tint) {
|
||||
this._setBackgroundColourOption('tint', tint);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,10 @@ const debuglog = util.debuglog('sharp');
|
||||
*
|
||||
* Implements the [stream.Duplex](http://nodejs.org/api/stream.html#stream_class_stream_duplex) class.
|
||||
*
|
||||
* When loading more than one page/frame of an animated image,
|
||||
* these are combined as a vertically-stacked "toilet roll" image
|
||||
* where the overall height is the `pageHeight` multiplied by the number of `pages`.
|
||||
*
|
||||
* @constructs Sharp
|
||||
*
|
||||
* @emits Sharp#info
|
||||
@@ -212,8 +216,7 @@ const Sharp = function (input, options) {
|
||||
kernel: 'lanczos3',
|
||||
fastShrinkOnLoad: true,
|
||||
// operations
|
||||
tintA: 128,
|
||||
tintB: 128,
|
||||
tint: [-1, 0, 0, 0],
|
||||
flatten: false,
|
||||
flattenBackground: [0, 0, 0],
|
||||
unflatten: false,
|
||||
@@ -258,11 +261,12 @@ const Sharp = function (input, options) {
|
||||
fileOut: '',
|
||||
formatOut: 'input',
|
||||
streamOut: false,
|
||||
withMetadata: false,
|
||||
keepMetadata: 0,
|
||||
withMetadataOrientation: -1,
|
||||
withMetadataDensity: 0,
|
||||
withMetadataIcc: '',
|
||||
withMetadataStrs: {},
|
||||
withIccProfile: '',
|
||||
withExif: {},
|
||||
withExifMerge: true,
|
||||
resolveWithObject: false,
|
||||
// output format
|
||||
jpegQuality: 80,
|
||||
|
||||
79
lib/index.d.ts
vendored
@@ -239,12 +239,12 @@ declare namespace sharp {
|
||||
//#region Color functions
|
||||
|
||||
/**
|
||||
* Tint the image using the provided chroma while preserving the image luminance.
|
||||
* Tint the image using the provided colour.
|
||||
* An alpha channel may be present and will be unchanged by the operation.
|
||||
* @param rgb Parsed by the color module to extract chroma values.
|
||||
* @param tint Parsed by the color module.
|
||||
* @returns A sharp instance that can be used to chain operations
|
||||
*/
|
||||
tint(rgb: Color): Sharp;
|
||||
tint(tint: Color): Sharp;
|
||||
|
||||
/**
|
||||
* Convert to 8-bit greyscale; 256 shades of grey.
|
||||
@@ -633,6 +633,43 @@ declare namespace sharp {
|
||||
*/
|
||||
toBuffer(options: { resolveWithObject: true }): Promise<{ data: Buffer; info: OutputInfo }>;
|
||||
|
||||
/**
|
||||
* Keep all EXIF metadata from the input image in the output image.
|
||||
* EXIF metadata is unsupported for TIFF output.
|
||||
* @returns A sharp instance that can be used to chain operations
|
||||
*/
|
||||
keepExif(): Sharp;
|
||||
|
||||
/**
|
||||
* Set EXIF metadata in the output image, ignoring any EXIF in the input image.
|
||||
* @param {Exif} exif Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data.
|
||||
* @returns A sharp instance that can be used to chain operations
|
||||
* @throws {Error} Invalid parameters
|
||||
*/
|
||||
withExif(exif: Exif): Sharp;
|
||||
|
||||
/**
|
||||
* Update EXIF metadata from the input image in the output image.
|
||||
* @param {Exif} exif Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data.
|
||||
* @returns A sharp instance that can be used to chain operations
|
||||
* @throws {Error} Invalid parameters
|
||||
*/
|
||||
withExifMerge(exif: Exif): Sharp;
|
||||
|
||||
/**
|
||||
* Keep ICC profile from the input image in the output image where possible.
|
||||
* @returns A sharp instance that can be used to chain operations
|
||||
*/
|
||||
keepIccProfile(): Sharp;
|
||||
|
||||
/**
|
||||
* Transform using an ICC profile and attach to the output image.
|
||||
* @param {string} icc - Absolute filesystem path to output ICC profile or built-in profile name (srgb, p3, cmyk).
|
||||
* @returns A sharp instance that can be used to chain operations
|
||||
* @throws {Error} Invalid parameters
|
||||
*/
|
||||
withIccProfile(icc: string, options?: WithIccProfileOptions): Sharp;
|
||||
|
||||
/**
|
||||
* Include all metadata (EXIF, XMP, IPTC) from the input image in the output image.
|
||||
* The default behaviour, when withMetadata is not used, is to strip all metadata and convert to the device-independent sRGB colour space.
|
||||
@@ -640,7 +677,7 @@ declare namespace sharp {
|
||||
* @param withMetadata
|
||||
* @throws {Error} Invalid parameters.
|
||||
*/
|
||||
withMetadata(withMetadata?: boolean | WriteableMetadata): Sharp;
|
||||
withMetadata(withMetadata?: WriteableMetadata): Sharp;
|
||||
|
||||
/**
|
||||
* Use these JPEG options for output image.
|
||||
@@ -978,15 +1015,32 @@ declare namespace sharp {
|
||||
wrap?: TextWrap;
|
||||
}
|
||||
|
||||
interface ExifDir {
|
||||
[k: string]: string;
|
||||
}
|
||||
|
||||
interface Exif {
|
||||
'IFD0'?: ExifDir;
|
||||
'IFD1'?: ExifDir;
|
||||
'IFD2'?: ExifDir;
|
||||
'IFD3'?: ExifDir;
|
||||
}
|
||||
|
||||
interface WriteableMetadata {
|
||||
/** Value between 1 and 8, used to update the EXIF Orientation tag. */
|
||||
orientation?: number | undefined;
|
||||
/** Filesystem path to output ICC profile, defaults to sRGB. */
|
||||
icc?: string | undefined;
|
||||
/** Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data. (optional, default {}) */
|
||||
exif?: Record<string, any> | undefined;
|
||||
/** Number of pixels per inch (DPI) */
|
||||
density?: number | undefined;
|
||||
/** Value between 1 and 8, used to update the EXIF Orientation tag. */
|
||||
orientation?: number | undefined;
|
||||
/**
|
||||
* Filesystem path to output ICC profile, defaults to sRGB.
|
||||
* @deprecated Use `withIccProfile()` instead.
|
||||
*/
|
||||
icc?: string | undefined;
|
||||
/**
|
||||
* Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data.
|
||||
* @deprecated Use `withExif()` or `withExifMerge()` instead.
|
||||
*/
|
||||
exif?: Exif | undefined;
|
||||
}
|
||||
|
||||
interface Metadata {
|
||||
@@ -1096,6 +1150,11 @@ declare namespace sharp {
|
||||
force?: boolean | undefined;
|
||||
}
|
||||
|
||||
interface WithIccProfileOptions {
|
||||
/** Should the ICC profile be included in the output image metadata? (optional, default true) */
|
||||
attach?: boolean | undefined;
|
||||
}
|
||||
|
||||
interface JpegOptions extends OutputOptions {
|
||||
/** Quality, integer 1-100 (optional, default 80) */
|
||||
quality?: number | undefined;
|
||||
|
||||
231
lib/output.js
@@ -163,39 +163,185 @@ function toBuffer (options, callback) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Include all metadata (EXIF, XMP, IPTC) from the input image in the output image.
|
||||
* This will also convert to and add a web-friendly sRGB ICC profile if appropriate,
|
||||
* unless a custom output profile is provided.
|
||||
*
|
||||
* The default behaviour, when `withMetadata` is not used, is to convert to the device-independent
|
||||
* sRGB colour space and strip all metadata, including the removal of any ICC profile.
|
||||
* Keep all EXIF metadata from the input image in the output image.
|
||||
*
|
||||
* EXIF metadata is unsupported for TIFF output.
|
||||
*
|
||||
* @example
|
||||
* sharp('input.jpg')
|
||||
* .withMetadata()
|
||||
* .toFile('output-with-metadata.jpg')
|
||||
* .then(info => { ... });
|
||||
* @since 0.33.0
|
||||
*
|
||||
* @example
|
||||
* // Set output EXIF metadata
|
||||
* const data = await sharp(input)
|
||||
* .withMetadata({
|
||||
* exif: {
|
||||
* IFD0: {
|
||||
* Copyright: 'The National Gallery'
|
||||
* },
|
||||
* IFD3: {
|
||||
* GPSLatitudeRef: 'N',
|
||||
* GPSLatitude: '51/1 30/1 3230/100',
|
||||
* GPSLongitudeRef: 'W',
|
||||
* GPSLongitude: '0/1 7/1 4366/100'
|
||||
* }
|
||||
* const outputWithExif = await sharp(inputWithExif)
|
||||
* .keepExif()
|
||||
* .toBuffer();
|
||||
*
|
||||
* @returns {Sharp}
|
||||
*/
|
||||
function keepExif () {
|
||||
this.options.keepMetadata |= 0b00001;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set EXIF metadata in the output image, ignoring any EXIF in the input image.
|
||||
*
|
||||
* @since 0.33.0
|
||||
*
|
||||
* @example
|
||||
* const dataWithExif = await sharp(input)
|
||||
* .withExif({
|
||||
* IFD0: {
|
||||
* Copyright: 'The National Gallery'
|
||||
* },
|
||||
* IFD3: {
|
||||
* GPSLatitudeRef: 'N',
|
||||
* GPSLatitude: '51/1 30/1 3230/100',
|
||||
* GPSLongitudeRef: 'W',
|
||||
* GPSLongitude: '0/1 7/1 4366/100'
|
||||
* }
|
||||
* })
|
||||
* .toBuffer();
|
||||
*
|
||||
* @param {Object<string, Object<string, string>>} exif Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data.
|
||||
* @returns {Sharp}
|
||||
* @throws {Error} Invalid parameters
|
||||
*/
|
||||
function withExif (exif) {
|
||||
if (is.object(exif)) {
|
||||
for (const [ifd, entries] of Object.entries(exif)) {
|
||||
if (is.object(entries)) {
|
||||
for (const [k, v] of Object.entries(entries)) {
|
||||
if (is.string(v)) {
|
||||
this.options.withExif[`exif-${ifd.toLowerCase()}-${k}`] = v;
|
||||
} else {
|
||||
throw is.invalidParameterError(`${ifd}.${k}`, 'string', v);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw is.invalidParameterError(ifd, 'object', entries);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw is.invalidParameterError('exif', 'object', exif);
|
||||
}
|
||||
this.options.withExifMerge = false;
|
||||
return this.keepExif();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update EXIF metadata from the input image in the output image.
|
||||
*
|
||||
* @since 0.33.0
|
||||
*
|
||||
* @example
|
||||
* const dataWithMergedExif = await sharp(inputWithExif)
|
||||
* .withExifMerge({
|
||||
* IFD0: {
|
||||
* Copyright: 'The National Gallery'
|
||||
* }
|
||||
* })
|
||||
* .toBuffer();
|
||||
*
|
||||
* @param {Object<string, Object<string, string>>} exif Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data.
|
||||
* @returns {Sharp}
|
||||
* @throws {Error} Invalid parameters
|
||||
*/
|
||||
function withExifMerge (exif) {
|
||||
this.withExif(exif);
|
||||
this.options.withExifMerge = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep ICC profile from the input image in the output image.
|
||||
*
|
||||
* Where necessary, will attempt to convert the output colour space to match the profile.
|
||||
*
|
||||
* @since 0.33.0
|
||||
*
|
||||
* @example
|
||||
* const outputWithIccProfile = await sharp(inputWithIccProfile)
|
||||
* .keepIccProfile()
|
||||
* .toBuffer();
|
||||
*
|
||||
* @returns {Sharp}
|
||||
*/
|
||||
function keepIccProfile () {
|
||||
this.options.keepMetadata |= 0b01000;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform using an ICC profile and attach to the output image.
|
||||
*
|
||||
* This can either be an absolute filesystem path or
|
||||
* built-in profile name (`srgb`, `p3`, `cmyk`).
|
||||
*
|
||||
* @since 0.33.0
|
||||
*
|
||||
* @example
|
||||
* const outputWithP3 = await sharp(input)
|
||||
* .withIccProfile('p3')
|
||||
* .toBuffer();
|
||||
*
|
||||
* @param {string} icc - Absolute filesystem path to output ICC profile or built-in profile name (srgb, p3, cmyk).
|
||||
* @param {Object} [options]
|
||||
* @param {number} [options.attach=true] Should the ICC profile be included in the output image metadata?
|
||||
* @returns {Sharp}
|
||||
* @throws {Error} Invalid parameters
|
||||
*/
|
||||
function withIccProfile (icc, options) {
|
||||
if (is.string(icc)) {
|
||||
this.options.withIccProfile = icc;
|
||||
} else {
|
||||
throw is.invalidParameterError('icc', 'string', icc);
|
||||
}
|
||||
this.keepIccProfile();
|
||||
if (is.object(options)) {
|
||||
if (is.defined(options.attach)) {
|
||||
if (is.bool(options.attach)) {
|
||||
if (!options.attach) {
|
||||
this.options.keepMetadata &= ~0b01000;
|
||||
}
|
||||
} else {
|
||||
throw is.invalidParameterError('attach', 'boolean', options.attach);
|
||||
}
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep all metadata (EXIF, ICC, XMP, IPTC) from the input image in the output image.
|
||||
*
|
||||
* The default behaviour, when `keepMetadata` is not used, is to convert to the device-independent
|
||||
* sRGB colour space and strip all metadata, including the removal of any ICC profile.
|
||||
*
|
||||
* @since 0.33.0
|
||||
*
|
||||
* @example
|
||||
* const outputWithMetadata = await sharp(inputWithMetadata)
|
||||
* .keepMetadata()
|
||||
* .toBuffer();
|
||||
*
|
||||
* @returns {Sharp}
|
||||
*/
|
||||
function keepMetadata () {
|
||||
this.options.keepMetadata = 0b11111;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep most metadata (EXIF, XMP, IPTC) from the input image in the output image.
|
||||
*
|
||||
* This will also convert to and add a web-friendly sRGB ICC profile if appropriate.
|
||||
*
|
||||
* Allows orientation and density to be set or updated.
|
||||
*
|
||||
* @example
|
||||
* const outputSrgbWithMetadata = await sharp(inputRgbWithMetadata)
|
||||
* .withMetadata()
|
||||
* .toBuffer();
|
||||
*
|
||||
* @example
|
||||
* // Set output metadata to 96 DPI
|
||||
* const data = await sharp(input)
|
||||
@@ -203,15 +349,14 @@ function toBuffer (options, callback) {
|
||||
* .toBuffer();
|
||||
*
|
||||
* @param {Object} [options]
|
||||
* @param {number} [options.orientation] value between 1 and 8, used to update the EXIF `Orientation` tag.
|
||||
* @param {string} [options.icc='srgb'] Filesystem path to output ICC profile, relative to `process.cwd()`, defaults to built-in sRGB.
|
||||
* @param {Object<Object>} [options.exif={}] Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data.
|
||||
* @param {number} [options.orientation] Used to update the EXIF `Orientation` tag, integer between 1 and 8.
|
||||
* @param {number} [options.density] Number of pixels per inch (DPI).
|
||||
* @returns {Sharp}
|
||||
* @throws {Error} Invalid parameters
|
||||
*/
|
||||
function withMetadata (options) {
|
||||
this.options.withMetadata = is.bool(options) ? options : true;
|
||||
this.keepMetadata();
|
||||
this.withIccProfile('srgb');
|
||||
if (is.object(options)) {
|
||||
if (is.defined(options.orientation)) {
|
||||
if (is.integer(options.orientation) && is.inRange(options.orientation, 1, 8)) {
|
||||
@@ -228,30 +373,10 @@ function withMetadata (options) {
|
||||
}
|
||||
}
|
||||
if (is.defined(options.icc)) {
|
||||
if (is.string(options.icc)) {
|
||||
this.options.withMetadataIcc = options.icc;
|
||||
} else {
|
||||
throw is.invalidParameterError('icc', 'string filesystem path to ICC profile', options.icc);
|
||||
}
|
||||
this.withIccProfile(options.icc);
|
||||
}
|
||||
if (is.defined(options.exif)) {
|
||||
if (is.object(options.exif)) {
|
||||
for (const [ifd, entries] of Object.entries(options.exif)) {
|
||||
if (is.object(entries)) {
|
||||
for (const [k, v] of Object.entries(entries)) {
|
||||
if (is.string(v)) {
|
||||
this.options.withMetadataStrs[`exif-${ifd.toLowerCase()}-${k}`] = v;
|
||||
} else {
|
||||
throw is.invalidParameterError(`exif.${ifd}.${k}`, 'string', v);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw is.invalidParameterError(`exif.${ifd}`, 'object', entries);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw is.invalidParameterError('exif', 'object', options.exif);
|
||||
}
|
||||
this.withExifMerge(options.exif);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
@@ -1407,6 +1532,12 @@ module.exports = function (Sharp) {
|
||||
// Public
|
||||
toFile,
|
||||
toBuffer,
|
||||
keepExif,
|
||||
withExif,
|
||||
withExifMerge,
|
||||
keepIccProfile,
|
||||
withIccProfile,
|
||||
keepMetadata,
|
||||
withMetadata,
|
||||
toFormat,
|
||||
jpeg,
|
||||
|
||||
@@ -17,10 +17,11 @@ const paths = [
|
||||
'@img/sharp-wasm32/sharp.node'
|
||||
];
|
||||
|
||||
let sharp;
|
||||
const errors = [];
|
||||
for (const path of paths) {
|
||||
try {
|
||||
module.exports = require(path);
|
||||
sharp = require(path);
|
||||
break;
|
||||
} catch (err) {
|
||||
/* istanbul ignore next */
|
||||
@@ -29,7 +30,9 @@ for (const path of paths) {
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (!module.exports) {
|
||||
if (sharp) {
|
||||
module.exports = sharp;
|
||||
} else {
|
||||
const [isLinux, isMacOs, isWindows] = ['linux', 'darwin', 'win32'].map(os => runtimePlatform.startsWith(os));
|
||||
|
||||
const help = [`Could not load the "sharp" module using the ${runtimePlatform} runtime`];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@img/sharp-darwin-arm64",
|
||||
"version": "0.33.0-alpha.11",
|
||||
"version": "0.33.0",
|
||||
"description": "Prebuilt sharp for use with macOS 64-bit ARM",
|
||||
"author": "Lovell Fuller <npm@lovell.info>",
|
||||
"homepage": "https://sharp.pixelplumbing.com",
|
||||
@@ -15,7 +15,7 @@
|
||||
},
|
||||
"preferUnplugged": true,
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-darwin-arm64": "0.0.3"
|
||||
"@img/sharp-libvips-darwin-arm64": "1.0.0"
|
||||
},
|
||||
"files": [
|
||||
"lib"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@img/sharp-darwin-x64",
|
||||
"version": "0.33.0-alpha.11",
|
||||
"version": "0.33.0",
|
||||
"description": "Prebuilt sharp for use with macOS x64",
|
||||
"author": "Lovell Fuller <npm@lovell.info>",
|
||||
"homepage": "https://sharp.pixelplumbing.com",
|
||||
@@ -15,7 +15,7 @@
|
||||
},
|
||||
"preferUnplugged": true,
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-darwin-x64": "0.0.3"
|
||||
"@img/sharp-libvips-darwin-x64": "1.0.0"
|
||||
},
|
||||
"files": [
|
||||
"lib"
|
||||
|
||||
@@ -61,7 +61,8 @@ workspaces.map(async platform => {
|
||||
await copyFile(path.join(__dirname, '..', 'LICENSE'), path.join(dir, 'LICENSE'));
|
||||
// Copy files for packages without an explicit sharp-libvips dependency (Windows, wasm)
|
||||
if (platform.startsWith('win') || platform.startsWith('wasm')) {
|
||||
const sharpLibvipsDir = path.join(require(`@img/sharp-libvips-${platform}/lib`), '..');
|
||||
const libvipsPlatform = platform === 'wasm32' ? 'dev-wasm32' : platform;
|
||||
const sharpLibvipsDir = path.join(require(`@img/sharp-libvips-${libvipsPlatform}/lib`), '..');
|
||||
// Copy versions.json
|
||||
await copyFile(path.join(sharpLibvipsDir, 'versions.json'), path.join(dir, 'versions.json'));
|
||||
// Append third party licensing to README
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@img/sharp-linux-arm",
|
||||
"version": "0.33.0-alpha.11",
|
||||
"version": "0.33.0",
|
||||
"description": "Prebuilt sharp for use with Linux (glibc) ARM (32-bit)",
|
||||
"author": "Lovell Fuller <npm@lovell.info>",
|
||||
"homepage": "https://sharp.pixelplumbing.com",
|
||||
@@ -15,7 +15,7 @@
|
||||
},
|
||||
"preferUnplugged": true,
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-arm": "0.0.3"
|
||||
"@img/sharp-libvips-linux-arm": "1.0.0"
|
||||
},
|
||||
"files": [
|
||||
"lib"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@img/sharp-linux-arm64",
|
||||
"version": "0.33.0-alpha.11",
|
||||
"version": "0.33.0",
|
||||
"description": "Prebuilt sharp for use with Linux (glibc) 64-bit ARM",
|
||||
"author": "Lovell Fuller <npm@lovell.info>",
|
||||
"homepage": "https://sharp.pixelplumbing.com",
|
||||
@@ -15,7 +15,7 @@
|
||||
},
|
||||
"preferUnplugged": true,
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-arm64": "0.0.3"
|
||||
"@img/sharp-libvips-linux-arm64": "1.0.0"
|
||||
},
|
||||
"files": [
|
||||
"lib"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@img/sharp-linux-s390x",
|
||||
"version": "0.33.0-alpha.11",
|
||||
"version": "0.33.0",
|
||||
"description": "Prebuilt sharp for use with Linux (glibc) s390x",
|
||||
"author": "Lovell Fuller <npm@lovell.info>",
|
||||
"homepage": "https://sharp.pixelplumbing.com",
|
||||
@@ -15,7 +15,7 @@
|
||||
},
|
||||
"preferUnplugged": true,
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-s390x": "0.0.3"
|
||||
"@img/sharp-libvips-linux-s390x": "1.0.0"
|
||||
},
|
||||
"files": [
|
||||
"lib"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@img/sharp-linux-x64",
|
||||
"version": "0.33.0-alpha.11",
|
||||
"version": "0.33.0",
|
||||
"description": "Prebuilt sharp for use with Linux (glibc) x64",
|
||||
"author": "Lovell Fuller <npm@lovell.info>",
|
||||
"homepage": "https://sharp.pixelplumbing.com",
|
||||
@@ -15,7 +15,7 @@
|
||||
},
|
||||
"preferUnplugged": true,
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linux-x64": "0.0.3"
|
||||
"@img/sharp-libvips-linux-x64": "1.0.0"
|
||||
},
|
||||
"files": [
|
||||
"lib"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@img/sharp-linuxmusl-arm64",
|
||||
"version": "0.33.0-alpha.11",
|
||||
"version": "0.33.0",
|
||||
"description": "Prebuilt sharp for use with Linux (musl) 64-bit ARM",
|
||||
"author": "Lovell Fuller <npm@lovell.info>",
|
||||
"homepage": "https://sharp.pixelplumbing.com",
|
||||
@@ -15,7 +15,7 @@
|
||||
},
|
||||
"preferUnplugged": true,
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "0.0.3"
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.0.0"
|
||||
},
|
||||
"files": [
|
||||
"lib"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@img/sharp-linuxmusl-x64",
|
||||
"version": "0.33.0-alpha.11",
|
||||
"version": "0.33.0",
|
||||
"description": "Prebuilt sharp for use with Linux (musl) x64",
|
||||
"author": "Lovell Fuller <npm@lovell.info>",
|
||||
"homepage": "https://sharp.pixelplumbing.com",
|
||||
@@ -15,7 +15,7 @@
|
||||
},
|
||||
"preferUnplugged": true,
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-libvips-linuxmusl-x64": "0.0.3"
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.0.0"
|
||||
},
|
||||
"files": [
|
||||
"lib"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@img/sharp",
|
||||
"version": "0.33.0-alpha.11",
|
||||
"version": "0.33.0",
|
||||
"private": "true",
|
||||
"workspaces": [
|
||||
"darwin-arm64",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@img/sharp-wasm32",
|
||||
"version": "0.33.0-alpha.11",
|
||||
"version": "0.33.0",
|
||||
"description": "Prebuilt sharp for use with wasm32",
|
||||
"author": "Lovell Fuller <npm@lovell.info>",
|
||||
"homepage": "https://sharp.pixelplumbing.com",
|
||||
@@ -34,7 +34,7 @@
|
||||
"pnpm": ">=7.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emnapi/runtime": "^0.43.1"
|
||||
"@emnapi/runtime": "^0.44.0"
|
||||
},
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@img/sharp-win32-ia32",
|
||||
"version": "0.33.0-alpha.11",
|
||||
"version": "0.33.0",
|
||||
"description": "Prebuilt sharp for use with Windows x86 (32-bit)",
|
||||
"author": "Lovell Fuller <npm@lovell.info>",
|
||||
"homepage": "https://sharp.pixelplumbing.com",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@img/sharp-win32-x64",
|
||||
"version": "0.33.0-alpha.11",
|
||||
"version": "0.33.0",
|
||||
"description": "Prebuilt sharp for use with Windows x64",
|
||||
"author": "Lovell Fuller <npm@lovell.info>",
|
||||
"homepage": "https://sharp.pixelplumbing.com",
|
||||
|
||||
52
package.json
@@ -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.33.0-alpha.11",
|
||||
"version": "0.33.0",
|
||||
"author": "Lovell Fuller <npm@lovell.info>",
|
||||
"homepage": "https://github.com/lovell/sharp",
|
||||
"contributors": [
|
||||
@@ -141,36 +141,36 @@
|
||||
"semver": "^7.5.4"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@img/sharp-darwin-arm64": "0.33.0-alpha.11",
|
||||
"@img/sharp-darwin-x64": "0.33.0-alpha.11",
|
||||
"@img/sharp-libvips-darwin-arm64": "0.0.3",
|
||||
"@img/sharp-libvips-darwin-x64": "0.0.3",
|
||||
"@img/sharp-libvips-linux-arm": "0.0.3",
|
||||
"@img/sharp-libvips-linux-arm64": "0.0.3",
|
||||
"@img/sharp-libvips-linux-s390x": "0.0.3",
|
||||
"@img/sharp-libvips-linux-x64": "0.0.3",
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "0.0.3",
|
||||
"@img/sharp-libvips-linuxmusl-x64": "0.0.3",
|
||||
"@img/sharp-linux-arm": "0.33.0-alpha.11",
|
||||
"@img/sharp-linux-arm64": "0.33.0-alpha.11",
|
||||
"@img/sharp-linux-s390x": "0.33.0-alpha.11",
|
||||
"@img/sharp-linux-x64": "0.33.0-alpha.11",
|
||||
"@img/sharp-linuxmusl-arm64": "0.33.0-alpha.11",
|
||||
"@img/sharp-linuxmusl-x64": "0.33.0-alpha.11",
|
||||
"@img/sharp-wasm32": "0.33.0-alpha.11",
|
||||
"@img/sharp-win32-ia32": "0.33.0-alpha.11",
|
||||
"@img/sharp-win32-x64": "0.33.0-alpha.11"
|
||||
"@img/sharp-darwin-arm64": "0.33.0",
|
||||
"@img/sharp-darwin-x64": "0.33.0",
|
||||
"@img/sharp-libvips-darwin-arm64": "1.0.0",
|
||||
"@img/sharp-libvips-darwin-x64": "1.0.0",
|
||||
"@img/sharp-libvips-linux-arm": "1.0.0",
|
||||
"@img/sharp-libvips-linux-arm64": "1.0.0",
|
||||
"@img/sharp-libvips-linux-s390x": "1.0.0",
|
||||
"@img/sharp-libvips-linux-x64": "1.0.0",
|
||||
"@img/sharp-libvips-linuxmusl-arm64": "1.0.0",
|
||||
"@img/sharp-libvips-linuxmusl-x64": "1.0.0",
|
||||
"@img/sharp-linux-arm": "0.33.0",
|
||||
"@img/sharp-linux-arm64": "0.33.0",
|
||||
"@img/sharp-linux-s390x": "0.33.0",
|
||||
"@img/sharp-linux-x64": "0.33.0",
|
||||
"@img/sharp-linuxmusl-arm64": "0.33.0",
|
||||
"@img/sharp-linuxmusl-x64": "0.33.0",
|
||||
"@img/sharp-wasm32": "0.33.0",
|
||||
"@img/sharp-win32-ia32": "0.33.0",
|
||||
"@img/sharp-win32-x64": "0.33.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@emnapi/runtime": "^0.43.1",
|
||||
"@img/sharp-libvips-dev": "0.0.3",
|
||||
"@img/sharp-libvips-dev-wasm32": "0.0.3",
|
||||
"@img/sharp-libvips-win32-ia32": "0.0.3",
|
||||
"@img/sharp-libvips-win32-x64": "0.0.3",
|
||||
"@emnapi/runtime": "^0.44.0",
|
||||
"@img/sharp-libvips-dev": "1.0.0",
|
||||
"@img/sharp-libvips-dev-wasm32": "1.0.0",
|
||||
"@img/sharp-libvips-win32-ia32": "1.0.0",
|
||||
"@img/sharp-libvips-win32-x64": "1.0.0",
|
||||
"@types/node": "*",
|
||||
"async": "^3.2.5",
|
||||
"cc": "^3.0.1",
|
||||
"emnapi": "^0.43.1",
|
||||
"emnapi": "^0.44.0",
|
||||
"exif-reader": "^2.0.0",
|
||||
"extract-zip": "^2.0.1",
|
||||
"icc": "^3.0.0",
|
||||
|
||||
@@ -223,11 +223,6 @@
|
||||
'configurations': {
|
||||
'Release': {
|
||||
'conditions': [
|
||||
['OS == "linux"', {
|
||||
'cflags_cc': [
|
||||
'-Wno-cast-function-type'
|
||||
]
|
||||
}],
|
||||
['target_arch == "arm"', {
|
||||
'cflags_cc': [
|
||||
'-Wno-psabi'
|
||||
|
||||
@@ -531,7 +531,33 @@ namespace sharp {
|
||||
Does this image have an embedded profile?
|
||||
*/
|
||||
bool HasProfile(VImage image) {
|
||||
return (image.get_typeof(VIPS_META_ICC_NAME) != 0) ? TRUE : FALSE;
|
||||
return image.get_typeof(VIPS_META_ICC_NAME) == VIPS_TYPE_BLOB;
|
||||
}
|
||||
|
||||
/*
|
||||
Get copy of embedded profile.
|
||||
*/
|
||||
std::pair<char*, size_t> GetProfile(VImage image) {
|
||||
std::pair<char*, size_t> icc(nullptr, 0);
|
||||
if (HasProfile(image)) {
|
||||
size_t length;
|
||||
const void *data = image.get_blob(VIPS_META_ICC_NAME, &length);
|
||||
icc.first = static_cast<char*>(g_malloc(length));
|
||||
icc.second = length;
|
||||
memcpy(icc.first, data, length);
|
||||
}
|
||||
return icc;
|
||||
}
|
||||
|
||||
/*
|
||||
Set embedded profile.
|
||||
*/
|
||||
VImage SetProfile(VImage image, std::pair<char*, size_t> icc) {
|
||||
if (icc.first != nullptr) {
|
||||
image = image.copy();
|
||||
image.set(VIPS_META_ICC_NAME, reinterpret_cast<VipsCallbackFn>(vips_area_free_cb), icc.first, icc.second);
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -542,6 +568,27 @@ namespace sharp {
|
||||
return image.has_alpha();
|
||||
}
|
||||
|
||||
static void* RemoveExifCallback(VipsImage *image, char const *field, GValue *value, void *data) {
|
||||
std::vector<std::string> *fieldNames = static_cast<std::vector<std::string> *>(data);
|
||||
std::string fieldName(field);
|
||||
if (fieldName.substr(0, 8) == ("exif-ifd")) {
|
||||
fieldNames->push_back(fieldName);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/*
|
||||
Remove all EXIF-related image fields.
|
||||
*/
|
||||
VImage RemoveExif(VImage image) {
|
||||
std::vector<std::string> fieldNames;
|
||||
vips_image_map(image.get_image(), static_cast<VipsImageMapFn>(RemoveExifCallback), &fieldNames);
|
||||
for (const auto& f : fieldNames) {
|
||||
image.remove(f.data());
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
/*
|
||||
Get EXIF Orientation of image, if any.
|
||||
*/
|
||||
|
||||
15
src/common.h
@@ -222,12 +222,27 @@ namespace sharp {
|
||||
*/
|
||||
bool HasProfile(VImage image);
|
||||
|
||||
/*
|
||||
Get copy of embedded profile.
|
||||
*/
|
||||
std::pair<char*, size_t> GetProfile(VImage image);
|
||||
|
||||
/*
|
||||
Set embedded profile.
|
||||
*/
|
||||
VImage SetProfile(VImage image, std::pair<char*, size_t> icc);
|
||||
|
||||
/*
|
||||
Does this image have an alpha channel?
|
||||
Uses colour space interpretation with number of channels to guess this.
|
||||
*/
|
||||
bool HasAlpha(VImage image);
|
||||
|
||||
/*
|
||||
Remove all EXIF-related image fields.
|
||||
*/
|
||||
VImage RemoveExif(VImage image);
|
||||
|
||||
/*
|
||||
Get EXIF Orientation of image, if any.
|
||||
*/
|
||||
|
||||
@@ -16,30 +16,44 @@ using vips::VError;
|
||||
|
||||
namespace sharp {
|
||||
/*
|
||||
* Tint an image using the specified chroma, preserving the original image luminance
|
||||
* Tint an image using the provided RGB.
|
||||
*/
|
||||
VImage Tint(VImage image, double const a, double const b) {
|
||||
// Get original colourspace
|
||||
VImage Tint(VImage image, std::vector<double> const tint) {
|
||||
std::vector<double> const tintLab = (VImage::black(1, 1) + tint)
|
||||
.colourspace(VIPS_INTERPRETATION_LAB, VImage::option()->set("source_space", VIPS_INTERPRETATION_sRGB))
|
||||
.getpoint(0, 0);
|
||||
// LAB identity function
|
||||
VImage identityLab = VImage::identity(VImage::option()->set("bands", 3))
|
||||
.colourspace(VIPS_INTERPRETATION_LAB, VImage::option()->set("source_space", VIPS_INTERPRETATION_sRGB));
|
||||
// Scale luminance range, 0.0 to 1.0
|
||||
VImage l = identityLab[0] / 100;
|
||||
// Weighting functions
|
||||
VImage weightL = 1.0 - 4.0 * ((l - 0.5) * (l - 0.5));
|
||||
VImage weightAB = (weightL * tintLab).extract_band(1, VImage::option()->set("n", 2));
|
||||
identityLab = identityLab[0].bandjoin(weightAB);
|
||||
// Convert lookup table to sRGB
|
||||
VImage lut = identityLab.colourspace(VIPS_INTERPRETATION_sRGB,
|
||||
VImage::option()->set("source_space", VIPS_INTERPRETATION_LAB));
|
||||
// Original colourspace
|
||||
VipsInterpretation typeBeforeTint = image.interpretation();
|
||||
if (typeBeforeTint == VIPS_INTERPRETATION_RGB) {
|
||||
typeBeforeTint = VIPS_INTERPRETATION_sRGB;
|
||||
}
|
||||
// Extract luminance
|
||||
VImage luminance = image.colourspace(VIPS_INTERPRETATION_LAB)[0];
|
||||
// Create the tinted version by combining the L from the original and the chroma from the tint
|
||||
std::vector<double> chroma {a, b};
|
||||
VImage tinted = luminance
|
||||
.bandjoin(chroma)
|
||||
.copy(VImage::option()->set("interpretation", VIPS_INTERPRETATION_LAB))
|
||||
.colourspace(typeBeforeTint);
|
||||
// Attach original alpha channel, if any
|
||||
// Apply lookup table
|
||||
if (HasAlpha(image)) {
|
||||
// Extract original alpha channel
|
||||
VImage alpha = image[image.bands() - 1];
|
||||
// Join alpha channel to normalised image
|
||||
tinted = tinted.bandjoin(alpha);
|
||||
image = RemoveAlpha(image)
|
||||
.colourspace(VIPS_INTERPRETATION_B_W)
|
||||
.maplut(lut)
|
||||
.colourspace(typeBeforeTint)
|
||||
.bandjoin(alpha);
|
||||
} else {
|
||||
image = image
|
||||
.colourspace(VIPS_INTERPRETATION_B_W)
|
||||
.maplut(lut)
|
||||
.colourspace(typeBeforeTint);
|
||||
}
|
||||
return tinted;
|
||||
return image;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -15,9 +15,9 @@ using vips::VImage;
|
||||
namespace sharp {
|
||||
|
||||
/*
|
||||
* Tint an image using the specified chroma, preserving the original image luminance
|
||||
* Tint an image using the provided RGB.
|
||||
*/
|
||||
VImage Tint(VImage image, double const a, double const b);
|
||||
VImage Tint(VImage image, std::vector<double> const tint);
|
||||
|
||||
/*
|
||||
* Stretch luminance to cover full dynamic range.
|
||||
|
||||
@@ -315,6 +315,11 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
}
|
||||
|
||||
// Ensure we're using a device-independent colour space
|
||||
std::pair<char*, size_t> inputProfile(nullptr, 0);
|
||||
if ((baton->keepMetadata & VIPS_FOREIGN_KEEP_ICC) && baton->withIccProfile.empty()) {
|
||||
// Cache input profile for use with output
|
||||
inputProfile = sharp::GetProfile(image);
|
||||
}
|
||||
char const *processingProfile = image.interpretation() == VIPS_INTERPRETATION_RGB16 ? "p3" : "srgb";
|
||||
if (
|
||||
sharp::HasProfile(image) &&
|
||||
@@ -736,8 +741,8 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
}
|
||||
|
||||
// Tint the image
|
||||
if (baton->tintA < 128.0 || baton->tintB < 128.0) {
|
||||
image = sharp::Tint(image, baton->tintA, baton->tintB);
|
||||
if (baton->tint[0] >= 0.0) {
|
||||
image = sharp::Tint(image, baton->tint);
|
||||
}
|
||||
|
||||
// Remove alpha channel, if any
|
||||
@@ -758,7 +763,8 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
// Convert colourspace, pass the current known interpretation so libvips doesn't have to guess
|
||||
image = image.colourspace(baton->colourspace, VImage::option()->set("source_space", image.interpretation()));
|
||||
// Transform colours from embedded profile to output profile
|
||||
if (baton->withMetadata && sharp::HasProfile(image) && baton->withMetadataIcc.empty()) {
|
||||
if ((baton->keepMetadata & VIPS_FOREIGN_KEEP_ICC) &&
|
||||
baton->withIccProfile.empty() && sharp::HasProfile(image)) {
|
||||
image = image.icc_transform("srgb", VImage::option()
|
||||
->set("embedded", TRUE)
|
||||
->set("depth", sharp::Is16Bit(image.interpretation()) ? 16 : 8)
|
||||
@@ -787,27 +793,30 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
}
|
||||
|
||||
// Apply output ICC profile
|
||||
if (baton->withMetadata) {
|
||||
image = image.icc_transform(
|
||||
baton->withMetadataIcc.empty() ? "srgb" : const_cast<char*>(baton->withMetadataIcc.data()),
|
||||
VImage::option()
|
||||
->set("input_profile", processingProfile)
|
||||
->set("embedded", TRUE)
|
||||
->set("depth", sharp::Is16Bit(image.interpretation()) ? 16 : 8)
|
||||
->set("intent", VIPS_INTENT_PERCEPTUAL));
|
||||
if (!baton->withIccProfile.empty()) {
|
||||
image = image.icc_transform(const_cast<char*>(baton->withIccProfile.data()), VImage::option()
|
||||
->set("input_profile", processingProfile)
|
||||
->set("embedded", TRUE)
|
||||
->set("depth", sharp::Is16Bit(image.interpretation()) ? 16 : 8)
|
||||
->set("intent", VIPS_INTENT_PERCEPTUAL));
|
||||
} else if (baton->keepMetadata & VIPS_FOREIGN_KEEP_ICC) {
|
||||
image = sharp::SetProfile(image, inputProfile);
|
||||
}
|
||||
// Override EXIF Orientation tag
|
||||
if (baton->withMetadata && baton->withMetadataOrientation != -1) {
|
||||
if (baton->withMetadataOrientation != -1) {
|
||||
image = sharp::SetExifOrientation(image, baton->withMetadataOrientation);
|
||||
}
|
||||
// Override pixel density
|
||||
if (baton->withMetadataDensity > 0) {
|
||||
image = sharp::SetDensity(image, baton->withMetadataDensity);
|
||||
}
|
||||
// Metadata key/value pairs, e.g. EXIF
|
||||
if (!baton->withMetadataStrs.empty()) {
|
||||
// EXIF key/value pairs
|
||||
if (baton->keepMetadata & VIPS_FOREIGN_KEEP_EXIF) {
|
||||
image = image.copy();
|
||||
for (const auto& s : baton->withMetadataStrs) {
|
||||
if (!baton->withExifMerge) {
|
||||
image = sharp::RemoveExif(image);
|
||||
}
|
||||
for (const auto& s : baton->withExif) {
|
||||
image.set(s.first.data(), s.second.data());
|
||||
}
|
||||
}
|
||||
@@ -828,7 +837,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
// Write JPEG to buffer
|
||||
sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
|
||||
VipsArea *area = reinterpret_cast<VipsArea*>(image.jpegsave_buffer(VImage::option()
|
||||
->set("strip", !baton->withMetadata)
|
||||
->set("keep", baton->keepMetadata)
|
||||
->set("Q", baton->jpegQuality)
|
||||
->set("interlace", baton->jpegProgressive)
|
||||
->set("subsample_mode", baton->jpegChromaSubsampling == "4:4:4"
|
||||
@@ -870,7 +879,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
// Write PNG to buffer
|
||||
sharp::AssertImageTypeDimensions(image, sharp::ImageType::PNG);
|
||||
VipsArea *area = reinterpret_cast<VipsArea*>(image.pngsave_buffer(VImage::option()
|
||||
->set("strip", !baton->withMetadata)
|
||||
->set("keep", baton->keepMetadata)
|
||||
->set("interlace", baton->pngProgressive)
|
||||
->set("compression", baton->pngCompressionLevel)
|
||||
->set("filter", baton->pngAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_ALL : VIPS_FOREIGN_PNG_FILTER_NONE)
|
||||
@@ -889,7 +898,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
// Write WEBP to buffer
|
||||
sharp::AssertImageTypeDimensions(image, sharp::ImageType::WEBP);
|
||||
VipsArea *area = reinterpret_cast<VipsArea*>(image.webpsave_buffer(VImage::option()
|
||||
->set("strip", !baton->withMetadata)
|
||||
->set("keep", baton->keepMetadata)
|
||||
->set("Q", baton->webpQuality)
|
||||
->set("lossless", baton->webpLossless)
|
||||
->set("near_lossless", baton->webpNearLossless)
|
||||
@@ -909,7 +918,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
// Write GIF to buffer
|
||||
sharp::AssertImageTypeDimensions(image, sharp::ImageType::GIF);
|
||||
VipsArea *area = reinterpret_cast<VipsArea*>(image.gifsave_buffer(VImage::option()
|
||||
->set("strip", !baton->withMetadata)
|
||||
->set("keep", baton->keepMetadata)
|
||||
->set("bitdepth", baton->gifBitdepth)
|
||||
->set("effort", baton->gifEffort)
|
||||
->set("reuse", baton->gifReuse)
|
||||
@@ -934,7 +943,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
image = image.cast(VIPS_FORMAT_FLOAT);
|
||||
}
|
||||
VipsArea *area = reinterpret_cast<VipsArea*>(image.tiffsave_buffer(VImage::option()
|
||||
->set("strip", !baton->withMetadata)
|
||||
->set("keep", baton->keepMetadata)
|
||||
->set("Q", baton->tiffQuality)
|
||||
->set("bitdepth", baton->tiffBitdepth)
|
||||
->set("compression", baton->tiffCompression)
|
||||
@@ -958,7 +967,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
sharp::AssertImageTypeDimensions(image, sharp::ImageType::HEIF);
|
||||
image = sharp::RemoveAnimationProperties(image).cast(VIPS_FORMAT_UCHAR);
|
||||
VipsArea *area = reinterpret_cast<VipsArea*>(image.heifsave_buffer(VImage::option()
|
||||
->set("strip", !baton->withMetadata)
|
||||
->set("keep", baton->keepMetadata)
|
||||
->set("Q", baton->heifQuality)
|
||||
->set("compression", baton->heifCompression)
|
||||
->set("effort", baton->heifEffort)
|
||||
@@ -990,7 +999,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
// Write JXL to buffer
|
||||
image = sharp::RemoveAnimationProperties(image);
|
||||
VipsArea *area = reinterpret_cast<VipsArea*>(image.jxlsave_buffer(VImage::option()
|
||||
->set("strip", !baton->withMetadata)
|
||||
->set("keep", baton->keepMetadata)
|
||||
->set("distance", baton->jxlDistance)
|
||||
->set("tier", baton->jxlDecodingTier)
|
||||
->set("effort", baton->jxlEffort)
|
||||
@@ -1051,7 +1060,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
// Write JPEG to file
|
||||
sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
|
||||
image.jpegsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
||||
->set("strip", !baton->withMetadata)
|
||||
->set("keep", baton->keepMetadata)
|
||||
->set("Q", baton->jpegQuality)
|
||||
->set("interlace", baton->jpegProgressive)
|
||||
->set("subsample_mode", baton->jpegChromaSubsampling == "4:4:4"
|
||||
@@ -1081,7 +1090,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
// Write PNG to file
|
||||
sharp::AssertImageTypeDimensions(image, sharp::ImageType::PNG);
|
||||
image.pngsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
||||
->set("strip", !baton->withMetadata)
|
||||
->set("keep", baton->keepMetadata)
|
||||
->set("interlace", baton->pngProgressive)
|
||||
->set("compression", baton->pngCompressionLevel)
|
||||
->set("filter", baton->pngAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_ALL : VIPS_FOREIGN_PNG_FILTER_NONE)
|
||||
@@ -1096,7 +1105,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
// Write WEBP to file
|
||||
sharp::AssertImageTypeDimensions(image, sharp::ImageType::WEBP);
|
||||
image.webpsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
||||
->set("strip", !baton->withMetadata)
|
||||
->set("keep", baton->keepMetadata)
|
||||
->set("Q", baton->webpQuality)
|
||||
->set("lossless", baton->webpLossless)
|
||||
->set("near_lossless", baton->webpNearLossless)
|
||||
@@ -1112,7 +1121,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
// Write GIF to file
|
||||
sharp::AssertImageTypeDimensions(image, sharp::ImageType::GIF);
|
||||
image.gifsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
||||
->set("strip", !baton->withMetadata)
|
||||
->set("keep", baton->keepMetadata)
|
||||
->set("bitdepth", baton->gifBitdepth)
|
||||
->set("effort", baton->gifEffort)
|
||||
->set("reuse", baton->gifReuse)
|
||||
@@ -1131,7 +1140,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
image = image.cast(VIPS_FORMAT_FLOAT);
|
||||
}
|
||||
image.tiffsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
||||
->set("strip", !baton->withMetadata)
|
||||
->set("keep", baton->keepMetadata)
|
||||
->set("Q", baton->tiffQuality)
|
||||
->set("bitdepth", baton->tiffBitdepth)
|
||||
->set("compression", baton->tiffCompression)
|
||||
@@ -1151,7 +1160,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
sharp::AssertImageTypeDimensions(image, sharp::ImageType::HEIF);
|
||||
image = sharp::RemoveAnimationProperties(image).cast(VIPS_FORMAT_UCHAR);
|
||||
image.heifsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
||||
->set("strip", !baton->withMetadata)
|
||||
->set("keep", baton->keepMetadata)
|
||||
->set("Q", baton->heifQuality)
|
||||
->set("compression", baton->heifCompression)
|
||||
->set("effort", baton->heifEffort)
|
||||
@@ -1165,7 +1174,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
// Write JXL to file
|
||||
image = sharp::RemoveAnimationProperties(image);
|
||||
image.jxlsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
||||
->set("strip", !baton->withMetadata)
|
||||
->set("keep", baton->keepMetadata)
|
||||
->set("distance", baton->jxlDistance)
|
||||
->set("tier", baton->jxlDecodingTier)
|
||||
->set("effort", baton->jxlEffort)
|
||||
@@ -1187,7 +1196,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
(willMatchInput && inputImageType == sharp::ImageType::VIPS)) {
|
||||
// Write V to file
|
||||
image.vipssave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
||||
->set("strip", !baton->withMetadata));
|
||||
->set("keep", baton->keepMetadata));
|
||||
baton->formatOut = "v";
|
||||
} else {
|
||||
// Unsupported output format
|
||||
@@ -1401,7 +1410,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
||||
suffix = AssembleSuffixString(extname, options);
|
||||
}
|
||||
vips::VOption *options = VImage::option()
|
||||
->set("strip", !baton->withMetadata)
|
||||
->set("keep", baton->keepMetadata)
|
||||
->set("tile_size", baton->tileSize)
|
||||
->set("overlap", baton->tileOverlap)
|
||||
->set("container", baton->tileContainer)
|
||||
@@ -1527,8 +1536,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
||||
baton->normalise = sharp::AttrAsBool(options, "normalise");
|
||||
baton->normaliseLower = sharp::AttrAsUint32(options, "normaliseLower");
|
||||
baton->normaliseUpper = sharp::AttrAsUint32(options, "normaliseUpper");
|
||||
baton->tintA = sharp::AttrAsDouble(options, "tintA");
|
||||
baton->tintB = sharp::AttrAsDouble(options, "tintB");
|
||||
baton->tint = sharp::AttrAsVectorOfDouble(options, "tint");
|
||||
baton->claheWidth = sharp::AttrAsUint32(options, "claheWidth");
|
||||
baton->claheHeight = sharp::AttrAsUint32(options, "claheHeight");
|
||||
baton->claheMaxSlope = sharp::AttrAsUint32(options, "claheMaxSlope");
|
||||
@@ -1594,18 +1602,19 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
||||
// Output
|
||||
baton->formatOut = sharp::AttrAsStr(options, "formatOut");
|
||||
baton->fileOut = sharp::AttrAsStr(options, "fileOut");
|
||||
baton->withMetadata = sharp::AttrAsBool(options, "withMetadata");
|
||||
baton->keepMetadata = sharp::AttrAsUint32(options, "keepMetadata");
|
||||
baton->withMetadataOrientation = sharp::AttrAsUint32(options, "withMetadataOrientation");
|
||||
baton->withMetadataDensity = sharp::AttrAsDouble(options, "withMetadataDensity");
|
||||
baton->withMetadataIcc = sharp::AttrAsStr(options, "withMetadataIcc");
|
||||
Napi::Object mdStrs = options.Get("withMetadataStrs").As<Napi::Object>();
|
||||
Napi::Array mdStrKeys = mdStrs.GetPropertyNames();
|
||||
for (unsigned int i = 0; i < mdStrKeys.Length(); i++) {
|
||||
std::string k = sharp::AttrAsStr(mdStrKeys, i);
|
||||
if (mdStrs.HasOwnProperty(k)) {
|
||||
baton->withMetadataStrs.insert(std::make_pair(k, sharp::AttrAsStr(mdStrs, k)));
|
||||
baton->withIccProfile = sharp::AttrAsStr(options, "withIccProfile");
|
||||
Napi::Object withExif = options.Get("withExif").As<Napi::Object>();
|
||||
Napi::Array withExifKeys = withExif.GetPropertyNames();
|
||||
for (unsigned int i = 0; i < withExifKeys.Length(); i++) {
|
||||
std::string k = sharp::AttrAsStr(withExifKeys, i);
|
||||
if (withExif.HasOwnProperty(k)) {
|
||||
baton->withExif.insert(std::make_pair(k, sharp::AttrAsStr(withExif, k)));
|
||||
}
|
||||
}
|
||||
baton->withExifMerge = sharp::AttrAsBool(options, "withExifMerge");
|
||||
baton->timeoutSeconds = sharp::AttrAsUint32(options, "timeoutSeconds");
|
||||
// Format-specific
|
||||
baton->jpegQuality = sharp::AttrAsUint32(options, "jpegQuality");
|
||||
|
||||
@@ -69,8 +69,7 @@ struct PipelineBaton {
|
||||
bool premultiplied;
|
||||
bool tileCentre;
|
||||
bool fastShrinkOnLoad;
|
||||
double tintA;
|
||||
double tintB;
|
||||
std::vector<double> tint;
|
||||
bool flatten;
|
||||
std::vector<double> flattenBackground;
|
||||
bool unflatten;
|
||||
@@ -188,11 +187,12 @@ struct PipelineBaton {
|
||||
bool jxlLossless;
|
||||
VipsBandFormat rawDepth;
|
||||
std::string err;
|
||||
bool withMetadata;
|
||||
int keepMetadata;
|
||||
int withMetadataOrientation;
|
||||
double withMetadataDensity;
|
||||
std::string withMetadataIcc;
|
||||
std::unordered_map<std::string, std::string> withMetadataStrs;
|
||||
std::string withIccProfile;
|
||||
std::unordered_map<std::string, std::string> withExif;
|
||||
bool withExifMerge;
|
||||
int timeoutSeconds;
|
||||
std::unique_ptr<double[]> convKernel;
|
||||
int convKernelWidth;
|
||||
@@ -239,8 +239,7 @@ struct PipelineBaton {
|
||||
attentionX(0),
|
||||
attentionY(0),
|
||||
premultiplied(false),
|
||||
tintA(128.0),
|
||||
tintB(128.0),
|
||||
tint{ -1.0, 0.0, 0.0, 0.0 },
|
||||
flatten(false),
|
||||
flattenBackground{ 0.0, 0.0, 0.0 },
|
||||
unflatten(false),
|
||||
@@ -355,9 +354,10 @@ struct PipelineBaton {
|
||||
jxlEffort(7),
|
||||
jxlLossless(false),
|
||||
rawDepth(VIPS_FORMAT_UCHAR),
|
||||
withMetadata(false),
|
||||
keepMetadata(0),
|
||||
withMetadataOrientation(-1),
|
||||
withMetadataDensity(0.0),
|
||||
withExifMerge(true),
|
||||
timeoutSeconds(0),
|
||||
convKernelWidth(0),
|
||||
convKernelHeight(0),
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
FROM ubuntu:22.04
|
||||
FROM ubuntu:23.10
|
||||
ARG BRANCH=main
|
||||
|
||||
# Install basic dependencies
|
||||
RUN apt-get -y update && apt-get install -y build-essential curl git
|
||||
RUN apt-get -y update && apt-get install -y build-essential curl git ca-certificates gnupg
|
||||
|
||||
# Install latest Node.js LTS
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash -
|
||||
RUN apt-get install -y nodejs
|
||||
RUN mkdir -p /etc/apt/keyrings
|
||||
RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
|
||||
RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
|
||||
RUN apt-get -y update && apt-get install -y nodejs
|
||||
|
||||
# Install benchmark dependencies
|
||||
RUN apt-get install -y imagemagick libmagick++-dev graphicsmagick
|
||||
|
||||
@@ -10,14 +10,14 @@
|
||||
"dependencies": {
|
||||
"@squoosh/cli": "0.7.3",
|
||||
"@squoosh/lib": "0.5.3",
|
||||
"async": "3.2.4",
|
||||
"async": "3.2.5",
|
||||
"benchmark": "2.1.4",
|
||||
"gm": "1.25.0",
|
||||
"imagemagick": "0.1.3",
|
||||
"jimp": "0.22.10"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@tensorflow/tfjs-node": "4.11.0",
|
||||
"@tensorflow/tfjs-node": "4.13.0",
|
||||
"mapnik": "4.5.9"
|
||||
},
|
||||
"license": "Apache-2.0"
|
||||
|
||||
BIN
test/fixtures/expected/tint-alpha.png
vendored
|
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 135 KiB |
BIN
test/fixtures/expected/tint-blue.jpg
vendored
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
BIN
test/fixtures/expected/tint-cmyk.jpg
vendored
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 30 KiB |
BIN
test/fixtures/expected/tint-green.jpg
vendored
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 14 KiB |
BIN
test/fixtures/expected/tint-red.jpg
vendored
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 15 KiB |
BIN
test/fixtures/expected/tint-sepia.jpg
vendored
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 15 KiB |
@@ -1,4 +1,5 @@
|
||||
#!/bin/sh
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
if ! type valgrind >/dev/null; then
|
||||
echo "Please install valgrind before running memory leak tests"
|
||||
@@ -7,14 +8,15 @@ fi
|
||||
|
||||
curl -s -o ./test/leak/libvips.supp https://raw.githubusercontent.com/libvips/libvips/master/suppressions/valgrind.supp
|
||||
|
||||
for test in ./test/unit/*.js; do
|
||||
TESTS=$(ls test/unit --ignore=svg.js --ignore=text.js)
|
||||
for test in $TESTS; do
|
||||
G_SLICE=always-malloc G_DEBUG=gc-friendly VIPS_LEAK=1 VIPS_NOVECTOR=1 valgrind \
|
||||
--suppressions=test/leak/libvips.supp \
|
||||
--suppressions=test/leak/sharp.supp \
|
||||
--gen-suppressions=yes \
|
||||
--leak-check=full \
|
||||
--show-leak-kinds=definite,indirect,possible \
|
||||
--show-leak-kinds=definite,indirect \
|
||||
--num-callers=20 \
|
||||
--trace-children=yes \
|
||||
node --expose-gc node_modules/.bin/mocha --no-config --slow=60000 --timeout=120000 --require test/beforeEach.js "$test";
|
||||
node --expose-gc --zero-fill-buffers node_modules/.bin/mocha --no-config --slow=60000 --timeout=120000 --require test/beforeEach.js "test/unit/$test";
|
||||
done
|
||||
|
||||
@@ -173,41 +173,6 @@
|
||||
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
|
||||
}
|
||||
{
|
||||
param_gsf_new_do_write
|
||||
Memcheck:Param
|
||||
write(buf)
|
||||
...
|
||||
fun:new_do_write
|
||||
...
|
||||
fun:gsf_output_close
|
||||
}
|
||||
{
|
||||
param_gsf_output_write
|
||||
Memcheck:Param
|
||||
write(buf)
|
||||
...
|
||||
fun:new_do_write
|
||||
...
|
||||
fun:gsf_output_write
|
||||
}
|
||||
|
||||
# fontconfig
|
||||
{
|
||||
leak_fontconfig_FcConfigSubstituteWithPat
|
||||
@@ -349,11 +314,39 @@
|
||||
fun:heif_context_read_from_reader
|
||||
}
|
||||
|
||||
# orc
|
||||
# glib
|
||||
{
|
||||
addr_orcexec
|
||||
Memcheck:Addr1
|
||||
obj:*/orcexec.*
|
||||
leak_glib__tls_get_addr
|
||||
Memcheck:Leak
|
||||
match-leak-kinds: possible
|
||||
...
|
||||
fun:malloc
|
||||
fun:allocate_dtv_entry
|
||||
fun:allocate_and_init
|
||||
fun:tls_get_addr_tail
|
||||
fun:__tls_get_addr
|
||||
}
|
||||
{
|
||||
value_g_utf8_make_valid_strlen
|
||||
Memcheck:Value8
|
||||
fun:strlen
|
||||
fun:g_utf8_make_valid
|
||||
}
|
||||
{
|
||||
value_g_utf8_make_valid_strncpy
|
||||
Memcheck:Value8
|
||||
fun:strncpy
|
||||
fun:g_strndup
|
||||
...
|
||||
fun:g_utf8_make_valid
|
||||
}
|
||||
{
|
||||
cond_g_utf8_make_valid_strncpy
|
||||
Memcheck:Cond
|
||||
fun:strncpy
|
||||
fun:g_strndup
|
||||
...
|
||||
fun:g_utf8_make_valid
|
||||
}
|
||||
|
||||
# libvips
|
||||
@@ -943,6 +936,79 @@
|
||||
...
|
||||
fun:_ZN2v88internal18ArrayBufferSweeper10ReleaseAllEv
|
||||
}
|
||||
{
|
||||
cond_node_Builtins_JSEntry
|
||||
Memcheck:Cond
|
||||
...
|
||||
fun:Builtins_JSEntry
|
||||
...
|
||||
fun:uv__poll_io_uring
|
||||
}
|
||||
{
|
||||
cond_node_Builtins_TestEqualStrictHandler
|
||||
Memcheck:Cond
|
||||
fun:Builtins_TestEqualStrictHandler
|
||||
...
|
||||
fun:uv__poll_io_uring
|
||||
}
|
||||
{
|
||||
cond_node_Builtins_TestGreaterThanHandler
|
||||
Memcheck:Cond
|
||||
fun:Builtins_TestGreaterThanHandler
|
||||
...
|
||||
fun:uv__poll_io_uring
|
||||
}
|
||||
{
|
||||
cond_node_AfterStat
|
||||
Memcheck:Cond
|
||||
...
|
||||
fun:_ZN4node2fs9AfterStatEP7uv_fs_s
|
||||
...
|
||||
fun:uv__poll_io_uring
|
||||
}
|
||||
{
|
||||
cond_node_AfterMkdirp
|
||||
Memcheck:Cond
|
||||
fun:_ZN4node2fs11AfterMkdirpEP7uv_fs_s
|
||||
fun:_ZN4node24MakeLibuvRequestCallbackI7uv_fs_sPFvPS1_EE7WrapperES2_
|
||||
fun:_ZZZN4node2fs11MKDirpAsyncEP9uv_loop_sP7uv_fs_sPKciPFvS4_EENKUlS4_E_clES4_ENUlS4_E_4_FUNES4_
|
||||
fun:uv__poll_io_uring
|
||||
}
|
||||
{
|
||||
cond_v8_ArrayBufferSweeper_Finalize
|
||||
Memcheck:Cond
|
||||
fun:_ZN2v88internal18ArrayBufferSweeper8FinalizeEv
|
||||
}
|
||||
{
|
||||
cond_v8_AdjustAmountOfExternalAllocatedMemory
|
||||
Memcheck:Cond
|
||||
fun:_ZN2v87Isolate37AdjustAmountOfExternalAllocatedMemoryEl
|
||||
}
|
||||
{
|
||||
cond_v8_IncrementalMarkingLimitReached
|
||||
Memcheck:Cond
|
||||
fun:_ZN2v88internal4Heap30IncrementalMarkingLimitReachedEv
|
||||
}
|
||||
{
|
||||
cond_v8_ShouldExpandOldGenerationOnSlowAllocation
|
||||
Memcheck:Cond
|
||||
fun:_ZN2v88internal4Heap41ShouldExpandOldGenerationOnSlowAllocationEPNS0_9LocalHeapENS0_16AllocationOriginE
|
||||
}
|
||||
{
|
||||
cond_v8_ArrayBufferSweeper_SweepingJob_SweepListFull
|
||||
Memcheck:Cond
|
||||
fun:_ZN2v88internal18ArrayBufferSweeper11SweepingJob13SweepListFullEPNS0_15ArrayBufferListE
|
||||
}
|
||||
{
|
||||
cond_v8_ArrayBufferSweeper_SweepingJob_SweepYoung
|
||||
Memcheck:Cond
|
||||
fun:_ZN2v88internal18ArrayBufferSweeper11SweepingJob10SweepYoungEv
|
||||
}
|
||||
{
|
||||
cond_v8_StartIncrementalMarkingIfAllocationLimitIsReachedBackground
|
||||
Memcheck:Cond
|
||||
fun:_ZN2v88internal4Heap59StartIncrementalMarkingIfAllocationLimitIsReachedBackgroundEv
|
||||
}
|
||||
{
|
||||
addr_v8_ZN2v88internal12_GLOBAL__N_119HandleApiCallHelperILb0EEENS0
|
||||
Memcheck:Addr8
|
||||
|
||||
@@ -659,6 +659,18 @@ sharp('input.tiff').webp({ preset: 'drawing' }).toFile('out.webp');
|
||||
sharp('input.tiff').webp({ preset: 'text' }).toFile('out.webp');
|
||||
sharp('input.tiff').webp({ preset: 'default' }).toFile('out.webp');
|
||||
|
||||
// Allow a boolean or an object for metadata options.
|
||||
// https://github.com/lovell/sharp/issues/3822
|
||||
sharp(input).withMetadata().withMetadata({}).withMetadata(false);
|
||||
sharp(input)
|
||||
.keepExif()
|
||||
.withExif({
|
||||
IFD0: {
|
||||
k1: 'v1'
|
||||
}
|
||||
})
|
||||
.withExifMerge({
|
||||
IFD1: {
|
||||
k2: 'v2'
|
||||
}
|
||||
})
|
||||
.keepIccProfile()
|
||||
.withIccProfile('filename')
|
||||
.withIccProfile('filename', { attach: false });
|
||||
|
||||
@@ -11,6 +11,8 @@ const icc = require('icc');
|
||||
const sharp = require('../../');
|
||||
const fixtures = require('../fixtures');
|
||||
|
||||
const create = { width: 1, height: 1, channels: 3, background: 'red' };
|
||||
|
||||
describe('Image metadata', function () {
|
||||
it('JPEG', function (done) {
|
||||
sharp(fixtures.inputJpg).metadata(function (err, metadata) {
|
||||
@@ -552,11 +554,55 @@ describe('Image metadata', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('keep existing ICC profile', async () => {
|
||||
const data = await sharp(fixtures.inputJpgWithExif)
|
||||
.keepIccProfile()
|
||||
.toBuffer();
|
||||
|
||||
const metadata = await sharp(data).metadata();
|
||||
const { description } = icc.parse(metadata.icc);
|
||||
assert.strictEqual(description, 'Generic RGB Profile');
|
||||
});
|
||||
|
||||
it('keep existing ICC profile, ignore colourspace conversion', async () => {
|
||||
const data = await sharp(fixtures.inputJpgWithExif)
|
||||
.keepIccProfile()
|
||||
.toColourspace('cmyk')
|
||||
.toBuffer();
|
||||
|
||||
const metadata = await sharp(data).metadata();
|
||||
assert.strictEqual(metadata.channels, 3);
|
||||
const { description } = icc.parse(metadata.icc);
|
||||
assert.strictEqual(description, 'Generic RGB Profile');
|
||||
});
|
||||
|
||||
it('transform to ICC profile and attach', async () => {
|
||||
const data = await sharp({ create })
|
||||
.png()
|
||||
.withIccProfile('p3', { attach: true })
|
||||
.toBuffer();
|
||||
|
||||
const metadata = await sharp(data).metadata();
|
||||
const { description } = icc.parse(metadata.icc);
|
||||
assert.strictEqual(description, 'sP3C');
|
||||
});
|
||||
|
||||
it('transform to ICC profile but do not attach', async () => {
|
||||
const data = await sharp({ create })
|
||||
.png()
|
||||
.withIccProfile('p3', { attach: false })
|
||||
.toBuffer();
|
||||
|
||||
const metadata = await sharp(data).metadata();
|
||||
assert.strictEqual(3, metadata.channels);
|
||||
assert.strictEqual(undefined, metadata.icc);
|
||||
});
|
||||
|
||||
it('Apply CMYK output ICC profile', function (done) {
|
||||
const output = fixtures.path('output.icc-cmyk.jpg');
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(64)
|
||||
.withMetadata({ icc: 'cmyk' })
|
||||
.withIccProfile('cmyk')
|
||||
.toFile(output, function (err) {
|
||||
if (err) throw err;
|
||||
sharp(output).metadata(function (err, metadata) {
|
||||
@@ -581,7 +627,7 @@ describe('Image metadata', function () {
|
||||
const output = fixtures.path('output.hilutite.jpg');
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(64)
|
||||
.withMetadata({ icc: fixtures.path('hilutite.icm') })
|
||||
.withIccProfile(fixtures.path('hilutite.icm'))
|
||||
.toFile(output, function (err, info) {
|
||||
if (err) throw err;
|
||||
fixtures.assertMaxColourDistance(output, fixtures.expected('hilutite.jpg'), 9);
|
||||
@@ -620,7 +666,6 @@ describe('Image metadata', function () {
|
||||
it('Remove EXIF metadata after a resize', function (done) {
|
||||
sharp(fixtures.inputJpgWithExif)
|
||||
.resize(320, 240)
|
||||
.withMetadata(false)
|
||||
.toBuffer(function (err, buffer) {
|
||||
if (err) throw err;
|
||||
sharp(buffer).metadata(function (err, metadata) {
|
||||
@@ -651,14 +696,7 @@ describe('Image metadata', function () {
|
||||
});
|
||||
|
||||
it('Add EXIF metadata to JPEG', async () => {
|
||||
const data = await sharp({
|
||||
create: {
|
||||
width: 8,
|
||||
height: 8,
|
||||
channels: 3,
|
||||
background: 'red'
|
||||
}
|
||||
})
|
||||
const data = await sharp({ create })
|
||||
.jpeg()
|
||||
.withMetadata({
|
||||
exif: {
|
||||
@@ -675,14 +713,7 @@ describe('Image metadata', function () {
|
||||
});
|
||||
|
||||
it('Set density of JPEG', async () => {
|
||||
const data = await sharp({
|
||||
create: {
|
||||
width: 8,
|
||||
height: 8,
|
||||
channels: 3,
|
||||
background: 'red'
|
||||
}
|
||||
})
|
||||
const data = await sharp({ create })
|
||||
.withMetadata({
|
||||
density: 300
|
||||
})
|
||||
@@ -694,14 +725,7 @@ describe('Image metadata', function () {
|
||||
});
|
||||
|
||||
it('Set density of PNG', async () => {
|
||||
const data = await sharp({
|
||||
create: {
|
||||
width: 8,
|
||||
height: 8,
|
||||
channels: 3,
|
||||
background: 'red'
|
||||
}
|
||||
})
|
||||
const data = await sharp({ create })
|
||||
.withMetadata({
|
||||
density: 96
|
||||
})
|
||||
@@ -809,11 +833,7 @@ describe('Image metadata', function () {
|
||||
});
|
||||
|
||||
it('withMetadata adds default sRGB profile to RGB16', async () => {
|
||||
const data = await sharp({
|
||||
create: {
|
||||
width: 8, height: 8, channels: 4, background: 'orange'
|
||||
}
|
||||
})
|
||||
const data = await sharp({ create })
|
||||
.toColorspace('rgb16')
|
||||
.png()
|
||||
.withMetadata()
|
||||
@@ -827,11 +847,7 @@ describe('Image metadata', function () {
|
||||
});
|
||||
|
||||
it('withMetadata adds P3 profile to 16-bit PNG', async () => {
|
||||
const data = await sharp({
|
||||
create: {
|
||||
width: 8, height: 8, channels: 4, background: 'orange'
|
||||
}
|
||||
})
|
||||
const data = await sharp({ create })
|
||||
.toColorspace('rgb16')
|
||||
.png()
|
||||
.withMetadata({ icc: 'p3' })
|
||||
@@ -871,7 +887,89 @@ describe('Image metadata', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Invalid withMetadata parameters', function () {
|
||||
it('keepExif maintains all EXIF metadata', async () => {
|
||||
const data1 = await sharp({ create })
|
||||
.withExif({
|
||||
IFD0: {
|
||||
Copyright: 'Test 1',
|
||||
Software: 'sharp'
|
||||
}
|
||||
})
|
||||
.jpeg()
|
||||
.toBuffer();
|
||||
|
||||
const data2 = await sharp(data1)
|
||||
.keepExif()
|
||||
.toBuffer();
|
||||
|
||||
const md2 = await sharp(data2).metadata();
|
||||
const exif2 = exifReader(md2.exif);
|
||||
assert.strictEqual(exif2.Image.Copyright, 'Test 1');
|
||||
assert.strictEqual(exif2.Image.Software, 'sharp');
|
||||
});
|
||||
|
||||
it('withExif replaces all EXIF metadata', async () => {
|
||||
const data1 = await sharp({ create })
|
||||
.withExif({
|
||||
IFD0: {
|
||||
Copyright: 'Test 1',
|
||||
Software: 'sharp'
|
||||
}
|
||||
})
|
||||
.jpeg()
|
||||
.toBuffer();
|
||||
|
||||
const md1 = await sharp(data1).metadata();
|
||||
const exif1 = exifReader(md1.exif);
|
||||
assert.strictEqual(exif1.Image.Copyright, 'Test 1');
|
||||
assert.strictEqual(exif1.Image.Software, 'sharp');
|
||||
|
||||
const data2 = await sharp(data1)
|
||||
.withExif({
|
||||
IFD0: {
|
||||
Copyright: 'Test 2'
|
||||
}
|
||||
})
|
||||
.toBuffer();
|
||||
|
||||
const md2 = await sharp(data2).metadata();
|
||||
const exif2 = exifReader(md2.exif);
|
||||
assert.strictEqual(exif2.Image.Copyright, 'Test 2');
|
||||
assert.strictEqual(exif2.Image.Software, undefined);
|
||||
});
|
||||
|
||||
it('withExifMerge merges all EXIF metadata', async () => {
|
||||
const data1 = await sharp({ create })
|
||||
.withExif({
|
||||
IFD0: {
|
||||
Copyright: 'Test 1'
|
||||
}
|
||||
})
|
||||
.jpeg()
|
||||
.toBuffer();
|
||||
|
||||
const md1 = await sharp(data1).metadata();
|
||||
const exif1 = exifReader(md1.exif);
|
||||
assert.strictEqual(exif1.Image.Copyright, 'Test 1');
|
||||
assert.strictEqual(exif1.Image.Software, undefined);
|
||||
|
||||
const data2 = await sharp(data1)
|
||||
.withExifMerge({
|
||||
IFD0: {
|
||||
Copyright: 'Test 2',
|
||||
Software: 'sharp'
|
||||
|
||||
}
|
||||
})
|
||||
.toBuffer();
|
||||
|
||||
const md2 = await sharp(data2).metadata();
|
||||
const exif2 = exifReader(md2.exif);
|
||||
assert.strictEqual(exif2.Image.Copyright, 'Test 2');
|
||||
assert.strictEqual(exif2.Image.Software, 'sharp');
|
||||
});
|
||||
|
||||
describe('Invalid parameters', function () {
|
||||
it('String orientation', function () {
|
||||
assert.throws(function () {
|
||||
sharp().withMetadata({ orientation: 'zoinks' });
|
||||
@@ -922,5 +1020,22 @@ describe('Image metadata', function () {
|
||||
sharp().withMetadata({ exif: { ifd0: { fail: false } } });
|
||||
});
|
||||
});
|
||||
it('withIccProfile invalid profile', () => {
|
||||
assert.throws(
|
||||
() => sharp().withIccProfile(false),
|
||||
/Expected string for icc but received false of type boolean/
|
||||
);
|
||||
});
|
||||
it('withIccProfile missing attach', () => {
|
||||
assert.doesNotThrow(
|
||||
() => sharp().withIccProfile('test', {})
|
||||
);
|
||||
});
|
||||
it('withIccProfile invalid attach', () => {
|
||||
assert.throws(
|
||||
() => sharp().withIccProfile('test', { attach: 1 }),
|
||||
/Expected boolean for attach but received 1 of type number/
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,16 +8,19 @@ const assert = require('assert');
|
||||
const sharp = require('../../');
|
||||
const fixtures = require('../fixtures');
|
||||
|
||||
// Allow for small rounding differences between platforms
|
||||
const maxDistance = 6;
|
||||
|
||||
describe('Tint', function () {
|
||||
it('tints rgb image red', function (done) {
|
||||
const output = fixtures.path('output.tint-red.jpg');
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240, { fastShrinkOnLoad: false })
|
||||
.resize(320, 240)
|
||||
.tint('#FF0000')
|
||||
.toFile(output, function (err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, info.size > 0);
|
||||
fixtures.assertMaxColourDistance(output, fixtures.expected('tint-red.jpg'), 18);
|
||||
fixtures.assertMaxColourDistance(output, fixtures.expected('tint-red.jpg'), maxDistance);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -25,12 +28,12 @@ describe('Tint', function () {
|
||||
it('tints rgb image green', function (done) {
|
||||
const output = fixtures.path('output.tint-green.jpg');
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240, { fastShrinkOnLoad: false })
|
||||
.resize(320, 240)
|
||||
.tint('#00FF00')
|
||||
.toFile(output, function (err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, info.size > 0);
|
||||
fixtures.assertMaxColourDistance(output, fixtures.expected('tint-green.jpg'), 27);
|
||||
fixtures.assertMaxColourDistance(output, fixtures.expected('tint-green.jpg'), maxDistance);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -38,12 +41,12 @@ describe('Tint', function () {
|
||||
it('tints rgb image blue', function (done) {
|
||||
const output = fixtures.path('output.tint-blue.jpg');
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240, { fastShrinkOnLoad: false })
|
||||
.resize(320, 240)
|
||||
.tint('#0000FF')
|
||||
.toFile(output, function (err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, info.size > 0);
|
||||
fixtures.assertMaxColourDistance(output, fixtures.expected('tint-blue.jpg'), 14);
|
||||
fixtures.assertMaxColourDistance(output, fixtures.expected('tint-blue.jpg'), maxDistance);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -51,13 +54,13 @@ describe('Tint', function () {
|
||||
it('tints rgb image with sepia tone', function (done) {
|
||||
const output = fixtures.path('output.tint-sepia-hex.jpg');
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240, { fastShrinkOnLoad: false })
|
||||
.resize(320, 240)
|
||||
.tint('#704214')
|
||||
.toFile(output, function (err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
fixtures.assertMaxColourDistance(output, fixtures.expected('tint-sepia.jpg'), 10);
|
||||
fixtures.assertMaxColourDistance(output, fixtures.expected('tint-sepia.jpg'), maxDistance);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -65,13 +68,13 @@ describe('Tint', function () {
|
||||
it('tints rgb image with sepia tone with rgb colour', function (done) {
|
||||
const output = fixtures.path('output.tint-sepia-rgb.jpg');
|
||||
sharp(fixtures.inputJpg)
|
||||
.resize(320, 240, { fastShrinkOnLoad: false })
|
||||
.resize(320, 240)
|
||||
.tint([112, 66, 20])
|
||||
.toFile(output, function (err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
fixtures.assertMaxColourDistance(output, fixtures.expected('tint-sepia.jpg'), 10);
|
||||
fixtures.assertMaxColourDistance(output, fixtures.expected('tint-sepia.jpg'), maxDistance);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -79,13 +82,13 @@ describe('Tint', function () {
|
||||
it('tints rgb image with alpha channel', function (done) {
|
||||
const output = fixtures.path('output.tint-alpha.png');
|
||||
sharp(fixtures.inputPngRGBWithAlpha)
|
||||
.resize(320, 240, { fastShrinkOnLoad: false })
|
||||
.resize(320, 240)
|
||||
.tint('#704214')
|
||||
.toFile(output, function (err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(320, info.width);
|
||||
assert.strictEqual(240, info.height);
|
||||
fixtures.assertMaxColourDistance(output, fixtures.expected('tint-alpha.png'), 10);
|
||||
fixtures.assertMaxColourDistance(output, fixtures.expected('tint-alpha.png'), maxDistance);
|
||||
done();
|
||||
});
|
||||
});
|
||||
@@ -93,12 +96,12 @@ describe('Tint', function () {
|
||||
it('tints cmyk image red', function (done) {
|
||||
const output = fixtures.path('output.tint-cmyk.jpg');
|
||||
sharp(fixtures.inputJpgWithCmykProfile)
|
||||
.resize(320, 240, { fastShrinkOnLoad: false })
|
||||
.resize(320, 240)
|
||||
.tint('#FF0000')
|
||||
.toFile(output, function (err, info) {
|
||||
if (err) throw err;
|
||||
assert.strictEqual(true, info.size > 0);
|
||||
fixtures.assertMaxColourDistance(output, fixtures.expected('tint-cmyk.jpg'), 15);
|
||||
fixtures.assertMaxColourDistance(output, fixtures.expected('tint-cmyk.jpg'), maxDistance);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||