Compare commits

...

55 Commits

Author SHA1 Message Date
Lovell Fuller
861cd93324 Pre-release v0.28.0-beta1 2021-03-27 07:11:34 +00:00
Lovell Fuller
abb344bb1a Upgrade to libvips v8.10.6 2021-03-26 21:57:12 +00:00
Lovell Fuller
6147491d9e Extend: default missing edge props to zero #2578 2021-03-25 16:34:02 +00:00
Lovell Fuller
f1f18fbb4a Docs: clarify that flatten removes alpha channel #2601 2021-03-25 14:38:55 +00:00
Lovell Fuller
9fc611f257 Docs: changelog entries for #2594 #2608 2021-03-22 20:30:46 +00:00
SHG42
34a2e14a14 Fix erroneous top/left clipping in composite #2571
Fixes bug where certain input values for top/left parameters
in composite can conflict with clipping logic, resulting in
inaccurate alignment in output.
2021-03-22 18:27:49 +00:00
Lovell Fuller
83fe65b9e9 Docs: include more relevant content in search index 2021-03-21 20:59:05 +00:00
Lovell Fuller
ec26c8aa49 Docs: ensure toBuffer pixel example works #2624 2021-03-21 20:54:09 +00:00
Lovell Fuller
da43a3055f Docs: correct typo in description of threshold operation 2021-03-21 20:51:30 +00:00
Lovell Fuller
a38126c82f Ensure composite replicates correct tiles with centre gravity #2626 2021-03-20 13:24:04 +00:00
Lovell Fuller
cb592ce588 Tests: add case for SVG with truncated embedded PNG 2021-03-18 19:34:56 +00:00
Lovell Fuller
d69c58a6da Docs: add section about Linux memory allocators 2021-03-18 19:34:07 +00:00
Lovell Fuller
bdb1986e08 Tests: run in parallel again 2021-03-17 23:25:34 +00:00
Lovell Fuller
55356c78a8 Docs: refresh markdown 2021-03-15 20:24:53 +00:00
Lovell Fuller
a0f55252b1 Tests: a few more speed improvements 2021-03-15 20:24:13 +00:00
Lovell Fuller
013f5cffa9 Tests: refactor modulate suite, ~20x faster 2021-03-15 18:20:06 +00:00
Lovell Fuller
d5d008f568 Docs: reorder readme sections 2021-03-15 13:07:16 +00:00
Lovell Fuller
3b02134cdc Tests: update latest benchmark test results 2021-03-14 21:10:26 +00:00
Lovell Fuller
a57d7b51b1 Tests: match concurrency with CPU count 2021-03-14 19:51:45 +00:00
Lovell Fuller
1a3c38d35f Pre-release v0.28.0-alpha1 2021-03-14 11:50:33 +00:00
Lovell Fuller
00aece0538 Ensure id attr can be set for IIIF tile output #2612 2021-03-14 11:19:53 +00:00
Lovell Fuller
5a9cc835b3 Reduce concurrency when using glibc-based Linux
to help prevent memory fragmentation
2021-03-14 11:19:53 +00:00
Lovell Fuller
58526cc849 Upgrade to libvips v8.10.6-alpha3 2021-03-14 11:19:53 +00:00
Lovell Fuller
955b5f43a5 Tests: small speed up to a couple of tile-related tests 2021-03-14 11:19:53 +00:00
Lovell Fuller
447aec3fde Tests: update leak suppressions 2021-03-14 11:19:53 +00:00
Lovell Fuller
473260a836 Docs: update with install-time improvements 2021-03-14 11:19:53 +00:00
Lovell Fuller
4d2784c10c Prebuilt libvips v8.10.6 binaries work with musl 1.1.x and 1.2.x 2021-03-14 11:19:53 +00:00
Lovell Fuller
d9af897595 Tests: ensure AVIF order is read, write, read+write 2021-03-14 11:19:53 +00:00
Lovell Fuller
23a48be315 Upgrade to libvips v8.10.6-alpha2
- Prebuilt Linux libvips binaries now use 'new' C++11 ABI
2021-03-14 11:19:53 +00:00
Lovell Fuller
ce8f48e5d1 CI: Add linuxmusl-arm64v8 environment 2021-03-14 11:19:53 +00:00
Kleis Auke Wolthuizen
6aaf839662 Use a single shared library 2021-03-14 11:19:53 +00:00
Lovell Fuller
984a9e653e Upgrade to libvips 8.10.6-alpha1
- Prebuilt binaries now include mozjpeg and libimagequant (BSD 2-Clause)
- Prebuilt binaries limit AVIF support to the most common 8-bit depth
- Add `mozjpeg` option to `jpeg` method, sets mozjpeg defaults
- Reduce the default PNG `compressionLevel` to the more commonly used 6
2021-03-14 11:19:53 +00:00
Lovell Fuller
8dffa28b4d Remove npmlog as a direct dependency
It remains a transitive dependency via prebuild-install
2021-03-14 11:19:53 +00:00
Lovell Fuller
b05a4bdadd Use same version of simple-get as prebuild-install
to prevent two different versions being installed
2021-03-14 11:19:53 +00:00
Lovell Fuller
36087fe518 Remove array-flatten dependency 2021-03-14 11:19:53 +00:00
Lovell Fuller
5eed87ec4d Install: skip header files when using prebuilds 2021-03-14 11:19:53 +00:00
Tobias Nießen
af66a73225 Tests: fix unit test description (#2619) 2021-03-13 16:18:40 +00:00
Alex Bradley
dcf913c17e Install: fail on incomplete download and clean up tempfile (#2608)
- Fail when the connection closes before the response is complete
- Create tempfile only when needed, and clean it up on failure
2021-03-05 15:21:34 +00:00
Lovell Fuller
68ccba8f74 Docs: refresh search index 2021-02-22 21:17:31 +00:00
Lovell Fuller
956f7e29db Release v0.27.2 2021-02-22 20:30:42 +00:00
Lovell Fuller
4264c0577e Improve experience for those using Apple M1 devices #2460
- For Rosetta x64, prevent use of global ARM64 libvips
- For ARM64, improve error message when global libvips not found
2021-02-22 13:49:31 +00:00
Lovell Fuller
cc37b59309 Switch to libvips' recently-exposed has_alpha #2569 2021-02-22 12:32:20 +00:00
Lovell Fuller
9f2f92095d Skip prebuilt binaries for musl >=1.2.0 #2570 2021-02-20 19:40:40 +00:00
Lovell Fuller
0c1075c089 Docs: local compilation requires --build-from-source flag 2021-02-20 15:43:48 +00:00
allx
9c64710c8b Allow code bundling of utility functions (#2586) 2021-02-20 15:39:25 +00:00
Lovell Fuller
f6f16b91db Allow use of recomb op with 1/2 channel input #2584 2021-02-19 16:37:29 +00:00
Lovell Fuller
1986b5cfe6 Bump deps 2021-02-19 15:49:21 +00:00
Lovell Fuller
6445b72d41 Docs: Changelog entry and credit for #2581 2021-02-19 15:48:59 +00:00
Florian Busch
df7b8ba738 Add support for non lower case extensions with toFormat 2021-02-17 20:46:13 +00:00
Pedro Poveda
202083999e Docs: add closing parenthesis so code example runs 2021-02-14 15:57:06 +00:00
aprat84
315f519e1d Docs: correct type for AVIF speed output option (#2568) 2021-02-08 20:37:56 +00:00
Lovell Fuller
d7d580ae6f Tests: using parallel fails on latest Node.js 15.8.0 2021-02-08 13:12:35 +00:00
Lovell Fuller
7017af303d Improve error message when attempting toFile/GIF without magick 2021-02-08 11:46:13 +00:00
Lovell Fuller
0dc325daa4 Docs: add section about Webpack configuration 2021-01-29 11:29:24 +00:00
Lovell Fuller
6dffb47973 Docs: small search index improvements 2021-01-29 11:28:48 +00:00
79 changed files with 963 additions and 420 deletions

View File

@@ -27,7 +27,7 @@ Please select the `master` branch as the destination for your Pull Request so yo
Please squash your changes into a single commit using a command like `git rebase -i upstream/master`.
To test C++ changes, you can compile the module using `npm install` and then run the tests using `npm test`.
To test C++ changes, you can compile the module using `npm install --build-from-source` and then run the tests using `npm test`.
## Submit a Pull Request with a new feature

View File

@@ -30,6 +30,8 @@ jobs:
container: node:12-alpine3.11
- os: ubuntu-20.04
container: node:14-alpine3.11
- os: ubuntu-20.04
container: node:14-alpine3.13
- os: ubuntu-20.04
container: node:15-alpine3.11
- os: macos-10.15

View File

@@ -58,5 +58,51 @@ jobs:
install: sudo docker exec sharp sh -c "npm install --build-from-source --unsafe-perm"
script: sudo docker exec sharp sh -c "npm test"
- name: "Linux ARM64v8 (Alpine 3.11, musl 1.1.24) - Node.js 10"
arch: arm64
os: linux
dist: focal
language: shell
before_install:
- sudo docker run -dit --name sharp --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp node:10-alpine3.11
- sudo docker exec sharp sh -c "apk add build-base git python3 --update-cache"
install: sudo docker exec sharp sh -c "npm install --build-from-source --unsafe-perm"
script: sudo docker exec sharp sh -c "npm test"
after_success: "[[ -n $TRAVIS_TAG ]] && sudo docker exec --env prebuild_upload sharp sh -c \"npx prebuild --runtime napi --target 3\""
- name: "Linux ARM64v8 (Alpine 3.11, musl 1.1.24) - Node.js 12"
arch: arm64
os: linux
dist: focal
language: shell
before_install:
- sudo docker run -dit --name sharp --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp node:12-alpine3.11
- sudo docker exec sharp sh -c "apk add build-base git python3 --update-cache"
install: sudo docker exec sharp sh -c "npm install --build-from-source --unsafe-perm"
script: sudo docker exec sharp sh -c "npm test"
- name: "Linux ARM64v8 (Alpine 3.11, musl 1.1.24) - Node.js 14"
arch: arm64
os: linux
dist: focal
language: shell
before_install:
- sudo docker run -dit --name sharp --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp node:14-alpine3.11
- sudo docker exec sharp sh -c "apk add build-base git python3 --update-cache"
install: sudo docker exec sharp sh -c "npm install --build-from-source --unsafe-perm"
script: sudo docker exec sharp sh -c "npm test"
- name: "Linux ARM64v8 (Alpine 3.11, musl 1.1.24) - Node.js 15"
arch: arm64
os: linux
dist: focal
language: shell
before_install:
- sudo chown 0.0 ${PWD}
- sudo docker run -dit --name sharp --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp node:15-alpine3.11
- sudo docker exec sharp sh -c "apk add build-base git python3 --update-cache"
install: sudo docker exec sharp sh -c "npm install --build-from-source --unsafe-perm"
script: sudo docker exec sharp sh -c "npm test"
cache:
npm: false

View File

@@ -19,6 +19,14 @@ rotation, extraction, compositing and gamma correction are available.
Most modern macOS, Windows and Linux systems running Node.js v10+
do not require any additional install or runtime dependencies.
## Documentation
Visit [sharp.pixelplumbing.com](https://sharp.pixelplumbing.com/) for complete
[installation instructions](https://sharp.pixelplumbing.com/install),
[API documentation](https://sharp.pixelplumbing.com/api-constructor),
[benchmark tests](https://sharp.pixelplumbing.com/performance) and
[changelog](https://sharp.pixelplumbing.com/changelog).
## Examples
```sh
@@ -43,6 +51,7 @@ sharp(inputBuffer)
sharp('input.jpg')
.rotate()
.resize(200)
.jpeg({ mozjpeg: true })
.toBuffer()
.then( data => { ... })
.catch( err => { ... });
@@ -84,23 +93,15 @@ readableStream
.pipe(writableStream);
```
[![Test Coverage](https://coveralls.io/repos/lovell/sharp/badge.svg?branch=master)](https://coveralls.io/r/lovell/sharp?branch=master)
[![N-API v3](https://img.shields.io/badge/N--API-v3-green.svg)](https://nodejs.org/dist/latest/docs/api/n-api.html#n_api_n_api_version_matrix)
### Documentation
Visit [sharp.pixelplumbing.com](https://sharp.pixelplumbing.com/) for complete
[installation instructions](https://sharp.pixelplumbing.com/install),
[API documentation](https://sharp.pixelplumbing.com/api-constructor),
[benchmark tests](https://sharp.pixelplumbing.com/performance) and
[changelog](https://sharp.pixelplumbing.com/changelog).
### Contributing
## Contributing
A [guide for contributors](https://github.com/lovell/sharp/blob/master/.github/CONTRIBUTING.md)
covers reporting bugs, requesting features and submitting code changes.
### Licensing
[![Test Coverage](https://coveralls.io/repos/lovell/sharp/badge.svg?branch=master)](https://coveralls.io/r/lovell/sharp?branch=master)
[![N-API v3](https://img.shields.io/badge/N--API-v3-green.svg)](https://nodejs.org/dist/latest/docs/api/n-api.html#n_api_n_api_version_matrix)
## Licensing
Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Lovell Fuller and contributors.

View File

@@ -140,8 +140,7 @@
'link_settings': {
'library_dirs': ['<(sharp_vendor_dir)/lib'],
'libraries': [
'libvips-cpp.42.dylib',
'libvips.42.dylib'
'libvips-cpp.42.dylib'
]
},
'xcode_settings': {
@@ -153,13 +152,12 @@
}],
['OS == "linux"', {
'defines': [
'_GLIBCXX_USE_CXX11_ABI=0'
'_GLIBCXX_USE_CXX11_ABI=1'
],
'link_settings': {
'library_dirs': ['<(sharp_vendor_dir)/lib'],
'libraries': [
'-l:libvips-cpp.so.42',
'-l:libvips.so.42'
'-l:libvips-cpp.so.42'
],
'ldflags': [
# Ensure runtime linking is relative to sharp.node

View File

@@ -50,6 +50,10 @@ no child processes are spawned and Promises/async/await are supported.
### Optimal
The features of `mozjpeg` and `pngquant` can be used
to optimise the file size of JPEG and PNG images respectively,
without having to invoke separate `imagemin` processes.
Huffman tables are optimised when generating JPEG output images
without having to use separate command line tools like
[jpegoptim](https://github.com/tjko/jpegoptim) and

View File

@@ -116,7 +116,7 @@ await sharp({
sigma: 30
}
}
}.toFile('noise.png');
}).toFile('noise.png');
```
- Throws **[Error][10]** Invalid parameters

View File

@@ -169,13 +169,21 @@ Returns **Sharp**
## flatten
Merge alpha transparency channel, if any, with a background.
Merge alpha transparency channel, if any, with a background, then remove the alpha channel.
### Parameters
- `options` **[Object][2]?**
- `options.background` **([string][3] \| [Object][2])** background colour, parsed by the [color][4] module, defaults to black. (optional, default `{r:0,g:0,b:0}`)
### Examples
```javascript
await sharp(rgbaInput)
.flatten('#F0A703')
.toBuffer();
```
Returns **Sharp**
## gamma
@@ -263,7 +271,7 @@ Returns **Sharp**
## threshold
Any pixel value greather than or equal to the threshold value will be set to 255, otherwise it will be set to 0.
Any pixel value greater than or equal to the threshold value will be set to 255, otherwise it will be set to 0.
### Parameters

View File

@@ -87,10 +87,10 @@ sharp(input)
```
```javascript
const data = await sharp('my-image.jpg')
const { data, info } = await sharp('my-image.jpg')
// output the raw pixels
.raw()
.toBuffer();
.toBuffer({ resolveWithObject: true });
// create a more type safe way to work with the raw pixel data
// this will not copy the data, instead it will change `data`s underlying ArrayBuffer
@@ -98,7 +98,9 @@ const data = await sharp('my-image.jpg')
const pixelArray = new Uint8ClampedArray(data.buffer);
// When you are done changing the pixelArray, sharp takes the `pixelArray` as an input
await sharp(pixelArray).toFile('my-changed-image.jpg');
const { width, height, channels } = info;
await sharp(pixelArray, { raw: { width, height, channels } })
.toFile('my-changed-image.jpg');
```
Returns **[Promise][5]&lt;[Buffer][8]>** when no callback is provided
@@ -157,8 +159,6 @@ Returns **Sharp**
Use these JPEG options for output image.
Some of these options require the use of a globally-installed libvips compiled with support for mozjpeg.
### Parameters
- `options` **[Object][6]?** output options
@@ -167,12 +167,13 @@ Some of these options require the use of a globally-installed libvips compiled w
- `options.chromaSubsampling` **[string][2]** set to '4:4:4' to prevent chroma subsampling otherwise defaults to '4:2:0' chroma subsampling (optional, default `'4:2:0'`)
- `options.optimiseCoding` **[boolean][7]** optimise Huffman coding tables (optional, default `true`)
- `options.optimizeCoding` **[boolean][7]** alternative spelling of optimiseCoding (optional, default `true`)
- `options.trellisQuantisation` **[boolean][7]** apply trellis quantisation, requires libvips compiled with support for mozjpeg (optional, default `false`)
- `options.overshootDeringing` **[boolean][7]** apply overshoot deringing, requires libvips compiled with support for mozjpeg (optional, default `false`)
- `options.optimiseScans` **[boolean][7]** optimise progressive scans, forces progressive, requires libvips compiled with support for mozjpeg (optional, default `false`)
- `options.optimizeScans` **[boolean][7]** alternative spelling of optimiseScans, requires libvips compiled with support for mozjpeg (optional, default `false`)
- `options.quantisationTable` **[number][9]** quantization table to use, integer 0-8, requires libvips compiled with support for mozjpeg (optional, default `0`)
- `options.quantizationTable` **[number][9]** alternative spelling of quantisationTable, requires libvips compiled with support for mozjpeg (optional, default `0`)
- `options.mozjpeg` **[boolean][7]** use mozjpeg defaults, equivalent to `{ trellisQuantisation: true, overshootDeringing: true, optimiseScans: true, quantisationTable: 3 }` (optional, default `false`)
- `options.trellisQuantisation` **[boolean][7]** apply trellis quantisation (optional, default `false`)
- `options.overshootDeringing` **[boolean][7]** apply overshoot deringing (optional, default `false`)
- `options.optimiseScans` **[boolean][7]** optimise progressive scans, forces progressive (optional, default `false`)
- `options.optimizeScans` **[boolean][7]** alternative spelling of optimiseScans (optional, default `false`)
- `options.quantisationTable` **[number][9]** quantization table to use, integer 0-8 (optional, default `0`)
- `options.quantizationTable` **[number][9]** alternative spelling of quantisationTable (optional, default `0`)
- `options.force` **[boolean][7]** force JPEG output, otherwise attempt to use input format (optional, default `true`)
### Examples
@@ -187,6 +188,13 @@ const data = await sharp(input)
.toBuffer();
```
```javascript
// Use mozjpeg to reduce output JPEG file size (slower)
const data = await sharp(input)
.jpeg({ mozjpeg: true })
.toBuffer();
```
- Throws **[Error][4]** Invalid options
Returns **Sharp**
@@ -195,33 +203,39 @@ Returns **Sharp**
Use these PNG options for output image.
PNG output is always full colour at 8 or 16 bits per pixel.
By default, PNG output is full colour at 8 or 16 bits per pixel.
Indexed PNG input at 1, 2 or 4 bits per pixel is converted to 8 bits per pixel.
Some of these options require the use of a globally-installed libvips compiled with support for libimagequant (GPL).
Set `palette` to `true` for slower, indexed PNG output.
### Parameters
- `options` **[Object][6]?**
- `options.progressive` **[boolean][7]** use progressive (interlace) scan (optional, default `false`)
- `options.compressionLevel` **[number][9]** zlib compression level, 0-9 (optional, default `9`)
- `options.compressionLevel` **[number][9]** zlib compression level, 0 (fastest, largest) to 9 (slowest, smallest) (optional, default `6`)
- `options.adaptiveFiltering` **[boolean][7]** use adaptive row filtering (optional, default `false`)
- `options.palette` **[boolean][7]** quantise to a palette-based image with alpha transparency support, requires libvips compiled with support for libimagequant (optional, default `false`)
- `options.quality` **[number][9]** use the lowest number of colours needed to achieve given quality, sets `palette` to `true`, requires libvips compiled with support for libimagequant (optional, default `100`)
- `options.colours` **[number][9]** maximum number of palette entries, sets `palette` to `true`, requires libvips compiled with support for libimagequant (optional, default `256`)
- `options.colors` **[number][9]** alternative spelling of `options.colours`, sets `palette` to `true`, requires libvips compiled with support for libimagequant (optional, default `256`)
- `options.dither` **[number][9]** level of Floyd-Steinberg error diffusion, sets `palette` to `true`, requires libvips compiled with support for libimagequant (optional, default `1.0`)
- `options.palette` **[boolean][7]** quantise to a palette-based image with alpha transparency support (optional, default `false`)
- `options.quality` **[number][9]** use the lowest number of colours needed to achieve given quality, sets `palette` to `true` (optional, default `100`)
- `options.colours` **[number][9]** maximum number of palette entries, sets `palette` to `true` (optional, default `256`)
- `options.colors` **[number][9]** alternative spelling of `options.colours`, sets `palette` to `true` (optional, default `256`)
- `options.dither` **[number][9]** level of Floyd-Steinberg error diffusion, sets `palette` to `true` (optional, default `1.0`)
- `options.force` **[boolean][7]** force PNG output, otherwise attempt to use input format (optional, default `true`)
### Examples
```javascript
// Convert any input to PNG output
// Convert any input to full colour PNG output
const data = await sharp(input)
.png()
.toBuffer();
```
```javascript
// Convert any input to indexed PNG output (slower)
const data = await sharp(input)
.png({ palette: true })
.toBuffer();
```
- Throws **[Error][4]** Invalid options
Returns **Sharp**
@@ -326,7 +340,7 @@ most web browsers do not display these properly.
- `options` **[Object][6]?** output options
- `options.quality` **[number][9]** quality, integer 1-100 (optional, default `50`)
- `options.lossless` **[boolean][7]** use lossless compression (optional, default `false`)
- `options.speed` **[boolean][7]** CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest) (optional, default `5`)
- `options.speed` **[number][9]** CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest) (optional, default `5`)
- `options.chromaSubsampling` **[string][2]** set to '4:4:4' to prevent chroma subsampling otherwise defaults to '4:2:0' chroma subsampling, requires libvips v8.11.0 (optional, default `'4:2:0'`)
@@ -411,6 +425,7 @@ Warning: multiple sharp instances concurrently producing tile output can expose
- `options.layout` **[string][2]** filesystem layout, possible values are `dz`, `iiif`, `zoomify` or `google`. (optional, default `'dz'`)
- `options.centre` **[boolean][7]** centre image in tile. (optional, default `false`)
- `options.center` **[boolean][7]** alternative spelling of centre. (optional, default `false`)
- `options.id` **[string][2]** when `layout` is `iiif`, sets the `@id` attribute of `info.json` (optional, default `'https://example.com/iiif'`)
### Examples

View File

@@ -137,10 +137,10 @@ This operation will always occur after resizing and extraction, if any.
### Parameters
- `extend` **([number][8] \| [Object][9])** single pixel count to add to all edges or an Object with per-edge counts
- `extend.top` **[number][8]?**
- `extend.left` **[number][8]?**
- `extend.bottom` **[number][8]?**
- `extend.right` **[number][8]?**
- `extend.top` **[number][8]** (optional, default `0`)
- `extend.left` **[number][8]** (optional, default `0`)
- `extend.bottom` **[number][8]** (optional, default `0`)
- `extend.right` **[number][8]** (optional, default `0`)
- `extend.background` **([String][10] \| [Object][9])** background colour, parsed by the [color][11] module, defaults to black without transparency. (optional, default `{r:0,g:0,b:0,alpha:1}`)
### Examples
@@ -160,6 +160,16 @@ sharp(input)
...
```
```javascript
// Add a row of 10 red pixels to the bottom
sharp(input)
.extend({
bottom: 10,
background: 'red'
})
...
```
- Throws **[Error][13]** Invalid parameters
Returns **Sharp**

View File

@@ -84,8 +84,12 @@ Returns **[Object][1]**
Gets or, when a concurrency is provided, sets
the number of threads _libvips'_ should create to process each image.
The default value is the number of CPU cores.
A value of `0` will reset to this default.
The default value is the number of CPU cores,
except when using glibc-based Linux without jemalloc,
where the default is `1` to help reduce memory fragmentation.
A value of `0` will reset this to the number of CPU cores.
The maximum number of images that can be processed in parallel
is limited by libuv's `UV_THREADPOOL_SIZE` environment variable.

View File

@@ -1,9 +1,61 @@
# Changelog
## v0.28 - *bijou*
Requires libvips v8.10.6
### v0.28.0 - TBD
* Prebuilt binaries now include mozjpeg and libimagequant (BSD 2-Clause).
* Prebuilt binaries limit AVIF support to the most common 8-bit depth.
* Add `mozjpeg` option to `jpeg` method, sets mozjpeg defaults.
* Reduce the default PNG `compressionLevel` to the more commonly used 6.
* Reduce concurrency on glibc-based Linux when using the default memory allocator to help prevent fragmentation.
* Default missing edge properties of extend operation to zero.
[#2578](https://github.com/lovell/sharp/issues/2578)
* Ensure composite does not clip top and left offsets.
[#2594](https://github.com/lovell/sharp/pull/2594)
[@SHG42](https://github.com/SHG42)
* Improve error handling of network failure at install time.
[#2608](https://github.com/lovell/sharp/pull/2608)
[@abradley](https://github.com/abradley)
* Ensure `@id` attribute can be set for IIIF tile-based output.
[#2612](https://github.com/lovell/sharp/issues/2612)
[@edsilv](https://github.com/edsilv)
* Ensure composite replicates the correct number of tiles for centred gravities.
[#2626](https://github.com/lovell/sharp/issues/2626)
## v0.27 - *avif*
Requires libvips v8.10.5
### v0.27.2 - 22nd February 2021
* macOS: Prevent use of globally-installed ARM64 libvips with Rosetta x64 emulation.
[#2460](https://github.com/lovell/sharp/issues/2460)
* Linux (musl): Prevent use of prebuilt linuxmusl-x64 binaries with musl >= 1.2.0.
[#2570](https://github.com/lovell/sharp/issues/2570)
* Improve 16-bit grey+alpha support by using libvips' `has_alpha` detection.
[#2569](https://github.com/lovell/sharp/issues/2569)
* Allow the use of non lower case extensions with `toFormat`.
[#2581](https://github.com/lovell/sharp/pull/2581)
[@florian-busch](https://github.com/florian-busch)
* Allow use of `recomb` operation with single channel input.
[#2584](https://github.com/lovell/sharp/issues/2584)
### v0.27.1 - 27th January 2021
* Ensure TIFF is cast when using float predictor.

View File

@@ -206,3 +206,6 @@ GitHub: https://github.com/stefanprobst
Name: Thomas Beiganz
GitHub: https://github.com/beig
Name: Florian Busch
GitHub: https://github.com/florian-busch

View File

@@ -19,7 +19,7 @@
<link rel="apple-touch-icon-precomposed" sizes="72x72" href="https://pixel.plumbing/px/72x72/sharp-logo.svg">
<link rel="apple-touch-icon-precomposed" href="https://pixel.plumbing/px/57x57/sharp-logo.svg">
<link rel="author" href="/humans.txt" type="text/plain">
<link rel="preload" href="https://cdn.jsdelivr.net/gh/lovell/sharp@v0.27.1/docs/README.md" as="fetch" type="text/markdown" crossorigin>
<link rel="preload" href="https://cdn.jsdelivr.net/gh/lovell/sharp@v0.27.2/docs/README.md" as="fetch" type="text/markdown" crossorigin>
<link rel="preload" href="https://cdn.jsdelivr.net/gh/lovell/sharp@master/docs/image/sharp-logo.svg" as="image" type="image/svg+xml" crossorigin>
<link rel="dns-prefetch" href="https://pixel.plumbing">
<link rel="dns-prefetch" href="https://www.google-analytics.com">
@@ -139,7 +139,7 @@
docuteApiTitlePlugin,
docuteApiSearchPlugin
],
sourcePath: 'https://cdn.jsdelivr.net/gh/lovell/sharp@v0.27.1/docs',
sourcePath: 'https://cdn.jsdelivr.net/gh/lovell/sharp@v0.27.2/docs',
nav: [
{
title: 'Funding',

View File

@@ -19,11 +19,11 @@ Node.js v10+ on the most common platforms:
* macOS x64 (>= 10.13)
* Linux x64 (glibc >= 2.17, musl >= 1.1.24)
* Linux ARM64 (glibc >= 2.29)
* Linux ARM64 (glibc >= 2.29, musl >= 1.1.24)
* Windows x64
* Windows x86
A ~9MB tarball containing libvips and its most commonly used dependencies
An ~8MB tarball containing libvips and its most commonly used dependencies
is downloaded via HTTPS and stored within `node_modules/sharp/vendor` during `npm install`.
This provides support for the
@@ -31,16 +31,16 @@ JPEG, PNG, WebP, AVIF, TIFF, GIF (input) and SVG (input) image formats.
The following platforms have prebuilt libvips but not sharp:
* macOS ARM64
* Linux ARMv6
* Linux ARMv7 (glibc >= 2.28)
* Windows ARM64
The following platforms require compilation of both libvips and sharp from source:
* macOS ARM64
* Linux x86
* Linux x64 (glibc <= 2.16, includes RHEL/CentOS 6)
* Linux ARM64 (glibc <= 2.28, musl)
* Linux ARM64 (glibc <= 2.28)
* Linux PowerPC
* FreeBSD
* OpenBSD
@@ -60,15 +60,15 @@ Check the output of running `npm install --verbose sharp` for useful error messa
## Apple M1
libvips must currently be installed via Homebrew before installing sharp.
Prebuilt libvips binaries are provided for macOS on ARM64 (since sharp v0.28.0).
```sh
brew install vips
```
During `npm install` sharp will be built locally,
which requires Xcode and Python - see
[building from source](#building-from-source).
When this new ARM64 CPU is made freely available
to open source projects via a CI service
then prebuilt binaries can be provided.
then prebuilt sharp binaries can also be provided.
## Custom libvips
@@ -149,6 +149,23 @@ pkg install -y pkgconf vips
cd /usr/ports/graphics/vips/ && make install clean
```
## Linux memory allocator
The default memory allocator on most glibc-based Linux systems
(e.g. Debian, Red Hat) is unsuitable for long-running, multi-threaded
processes that involve lots of small memory allocations.
For this reason, by default, sharp will limit the use of thread-based
[concurrency](api-utility#concurrency) when the glibc allocator is
detected at runtime.
To help avoid fragmentation and improve performance on these systems,
the use of an alternative memory allocator such as
[jemalloc](https://github.com/jemalloc/jemalloc) is recommended.
Those using musl-based Linux (e.g. Alpine) and non-Linux systems are
unaffected.
## Heroku
Add the
@@ -192,6 +209,18 @@ docker run -v "$PWD":/var/task lambci/lambda:build-nodejs12.x npm install sharp
To get the best performance select the largest memory available.
A 1536 MB function provides ~12x more CPU time than a 128 MB function.
## Webpack
Ensure sharp is added to the
[externals](https://webpack.js.org/configuration/externals/)
configuration.
```js
externals: {
'sharp': 'commonjs sharp'
}
```
## Worker threads
The main thread must call `require('sharp')`
@@ -201,22 +230,6 @@ until after all threads are complete.
## Known conflicts
### Electron and Linux
The prebuilt binaries provided by Electron for Linux depend on many shared system libraries.
One of these, `libgobject-2.0.so`,
is known to conflict with the statically-linked binaries provided by sharp
and the following error can occur:
```
basic_string::_S_construct null not valid
```
To workaround this, set the `LD_PRELOAD` environment variable before the `electron` binary is run.
```sh
LD_PRELOAD=node_modules/sharp/vendor/8.10.5/lib/libvips.so.42 electron script.js
```
### Canvas and Windows
The prebuilt binaries provided by `canvas` for Windows depend on the unmaintained GTK 2, last updated in 2011.

View File

@@ -5,10 +5,10 @@ A test to benchmark the performance of this module relative to alternatives.
## The contenders
* [jimp](https://www.npmjs.com/package/jimp) v0.16.1 - Image processing in pure JavaScript. Provides bicubic interpolation.
* [mapnik](https://www.npmjs.org/package/mapnik) v4.5.5 - Whilst primarily a map renderer, Mapnik contains bitmap image utilities.
* [mapnik](https://www.npmjs.org/package/mapnik) v4.5.6 - Whilst primarily a map renderer, Mapnik contains bitmap image utilities.
* [imagemagick](https://www.npmjs.com/package/imagemagick) v0.1.3 - Supports filesystem only and "*has been unmaintained for a long time*".
* [gm](https://www.npmjs.com/package/gm) v1.23.1 - Fully featured wrapper around GraphicsMagick's `gm` command line utility.
* sharp v0.27.0 / libvips v8.10.5 - Caching within libvips disabled to ensure a fair comparison.
* sharp v0.28.0 / libvips v8.10.6 - Caching within libvips disabled to ensure a fair comparison.
## The task
@@ -18,25 +18,25 @@ then compress to JPEG at a "quality" setting of 80.
## Test environment
* AWS EC2 eu-west-1 [c5d.large](https://aws.amazon.com/ec2/instance-types/c5/) (2x Xeon Platinum 8275CL CPU @ 3.00GHz)
* Ubuntu 20.10 (ami-046cdbcee95cdd75c)
* Node.js v14.15.3
* AWS EC2 eu-west-1 [c5ad.xlarge](https://aws.amazon.com/ec2/instance-types/c5/) (4x AMD EPYC 7R32)
* Ubuntu 20.10 (ami-03f10415e8b0bfb86)
* Node.js v14.16.0
## Results
| Module | Input | Output | Ops/sec | Speed-up |
| :----------------- | :----- | :----- | ------: | -------: |
| jimp | buffer | buffer | 0.77 | 1.0 |
| mapnik | buffer | buffer | 3.39 | 4.4 |
| gm | buffer | buffer | 4.30 | 5.6 |
| gm | file | file | 4.33 | 5.6 |
| imagemagick | file | file | 4.39 | 5.7 |
| sharp | stream | stream | 23.81 | 30.9 |
| sharp | file | file | 25.09 | 32.6 |
| sharp | buffer | buffer | 25.60 | 33.2 |
| jimp | buffer | buffer | 0.78 | 1.0 |
| mapnik | buffer | buffer | 3.39 | 4.3 |
| gm | buffer | buffer | 7.84 | 10.1 |
| gm | file | file | 9.24 | 11.8 |
| imagemagick | file | file | 9.37 | 12.0 |
| sharp | stream | stream | 26.84 | 34.4 |
| sharp | file | file | 29.76 | 38.2 |
| sharp | buffer | buffer | 31.60 | 40.5 |
Greater libvips performance can be expected with caching enabled (default)
and using 4+ core machines, especially those with larger L1/L2 CPU caches.
and using 8+ core machines, especially those with larger L1/L2 CPU caches.
The I/O limits of the relevant (de)compression library will generally determine maximum throughput.
@@ -51,7 +51,7 @@ brew install mapnik
```
```sh
sudo apt-get install imagemagick libmagick++-dev graphicsmagick libmapnik-dev
sudo apt-get install build-essential imagemagick libmagick++-dev graphicsmagick libmapnik-dev
```
```sh
@@ -61,7 +61,7 @@ sudo yum install ImageMagick-devel ImageMagick-c++-devel GraphicsMagick mapnik-d
```sh
git clone https://github.com/lovell/sharp.git
cd sharp
npm install
npm install --build-from-source
cd test/bench
npm install
npm test

File diff suppressed because one or more lines are too long

View File

@@ -8,7 +8,7 @@ const extractDescription = (str) =>
.replace(/\s+/g, ' ')
.replace(/[^A-Za-z0-9_/\-,. ]/g, '')
.replace(/\s+/g, ' ')
.substr(0, 140)
.substr(0, 180)
.trim();
const extractKeywords = (str) =>
@@ -17,7 +17,7 @@ const extractKeywords = (str) =>
str
.split(/[ -/]/)
.map((word) => word.toLowerCase().replace(/[^a-z]/g, ''))
.filter((word) => word.length > 2 && !stopWords.includes(word))
.filter((word) => word.length > 2 && word.length < 15 && !stopWords.includes(word))
)
].join(' ');

View File

@@ -4,21 +4,28 @@ module.exports = [
'about',
'after',
'all',
'allows',
'already',
'also',
'alternative',
'always',
'and',
'any',
'are',
'based',
'been',
'before',
'both',
'call',
'can',
'containing',
'default',
'does',
'each',
'either',
'ensure',
'etc',
'every',
'for',
'from',
'get',
@@ -28,7 +35,10 @@ module.exports = [
'have',
'how',
'image',
'involve',
'its',
'least',
'lots',
'may',
'more',
'most',
@@ -38,17 +48,24 @@ module.exports = [
'not',
'occur',
'occurs',
'options',
'over',
'perform',
'performs',
'provide',
'provided',
'ready',
'same',
'see',
'set',
'sets',
'should',
'since',
'spelling',
'such',
'support',
'supported',
'sure',
'take',
'that',
'the',
@@ -57,6 +74,9 @@ module.exports = [
'therefore',
'these',
'this',
'under',
'unless',
'until',
'use',
'used',
'using',
@@ -67,5 +87,6 @@ module.exports = [
'while',
'will',
'with',
'without'
'without',
'you'
];

View File

@@ -4,7 +4,6 @@ const fs = require('fs');
const path = require('path');
const libvips = require('../lib/libvips');
const npmLog = require('npmlog');
const minimumLibvipsVersion = libvips.minimumLibvipsVersion;
@@ -12,13 +11,13 @@ const platform = process.env.npm_config_platform || process.platform;
if (platform === 'win32') {
const buildDir = path.join(__dirname, '..', 'build');
const buildReleaseDir = path.join(buildDir, 'Release');
npmLog.info('sharp', `Creating ${buildReleaseDir}`);
libvips.log(`Creating ${buildReleaseDir}`);
try {
libvips.mkdirSync(buildDir);
libvips.mkdirSync(buildReleaseDir);
} catch (err) {}
const vendorLibDir = path.join(__dirname, '..', 'vendor', minimumLibvipsVersion, 'lib');
npmLog.info('sharp', `Copying DLLs from ${vendorLibDir} to ${buildReleaseDir}`);
libvips.log(`Copying DLLs from ${vendorLibDir} to ${buildReleaseDir}`);
try {
fs
.readdirSync(vendorLibDir)
@@ -32,6 +31,7 @@ if (platform === 'win32') {
);
});
} catch (err) {
npmLog.error('sharp', err.message);
libvips.log(err);
process.exit(1);
}
}

View File

@@ -7,7 +7,6 @@ const stream = require('stream');
const zlib = require('zlib');
const detectLibc = require('detect-libc');
const npmLog = require('npmlog');
const semver = require('semver');
const simpleGet = require('simple-get');
const tarFs = require('tar-fs');
@@ -22,34 +21,50 @@ const minimumGlibcVersionByArch = {
x64: '2.17'
};
const hasSharpPrebuild = [
'darwin-x64',
'linux-arm64',
'linux-x64',
'linuxmusl-x64',
'linuxmusl-arm64',
'win32-ia32',
'win32-x64'
];
const { minimumLibvipsVersion, minimumLibvipsVersionLabelled } = libvips;
const distHost = process.env.npm_config_sharp_libvips_binary_host || 'https://github.com/lovell/sharp-libvips/releases/download';
const distBaseUrl = process.env.npm_config_sharp_dist_base_url || process.env.SHARP_DIST_BASE_URL || `${distHost}/v${minimumLibvipsVersionLabelled}/`;
const supportsBrotli = ('BrotliDecompress' in zlib);
const fail = function (err) {
npmLog.error('sharp', err.message);
libvips.log(err);
if (err.code === 'EACCES') {
npmLog.info('sharp', 'Are you trying to install as a root or sudo user? Try again with the --unsafe-perm flag');
libvips.log('Are you trying to install as a root or sudo user? Try again with the --unsafe-perm flag');
}
npmLog.info('sharp', 'Attempting to build from source via node-gyp but this may fail due to the above error');
npmLog.info('sharp', 'Please see https://sharp.pixelplumbing.com/install for required dependencies');
libvips.log('Attempting to build from source via node-gyp but this may fail due to the above error');
libvips.log('Please see https://sharp.pixelplumbing.com/install for required dependencies');
process.exit(1);
};
const extractTarball = function (tarPath) {
const extractTarball = function (tarPath, platformAndArch) {
const vendorPath = path.join(__dirname, '..', 'vendor');
libvips.mkdirSync(vendorPath);
const versionedVendorPath = path.join(vendorPath, minimumLibvipsVersion);
libvips.mkdirSync(versionedVendorPath);
const ignoreVendorInclude = hasSharpPrebuild.includes(platformAndArch) && !process.env.npm_config_build_from_source;
const ignore = function (name) {
return ignoreVendorInclude && name.includes('include/');
};
stream.pipeline(
fs.createReadStream(tarPath),
supportsBrotli ? new zlib.BrotliDecompress() : new zlib.Gunzip(),
tarFs.extract(versionedVendorPath),
tarFs.extract(versionedVendorPath, { ignore }),
function (err) {
if (err) {
if (/unexpected end of file/.test(err.message)) {
npmLog.error('sharp', `Please delete ${tarPath} as it is not a valid tarball`);
fail(new Error(`Please delete ${tarPath} as it is not a valid tarball`));
}
fail(err);
}
@@ -62,11 +77,11 @@ try {
if (useGlobalLibvips) {
const globalLibvipsVersion = libvips.globalLibvipsVersion();
npmLog.info('sharp', `Detected globally-installed libvips v${globalLibvipsVersion}`);
npmLog.info('sharp', 'Building from source via node-gyp');
libvips.log(`Detected globally-installed libvips v${globalLibvipsVersion}`);
libvips.log('Building from source via node-gyp');
process.exit(1);
} else if (libvips.hasVendoredLibvips()) {
npmLog.info('sharp', `Using existing vendored libvips v${minimumLibvipsVersion}`);
libvips.log(`Using existing vendored libvips v${minimumLibvipsVersion}`);
} else {
// Is this arch/platform supported?
const arch = process.env.npm_config_arch || process.arch;
@@ -74,6 +89,9 @@ try {
if (arch === 'ia32' && !platformAndArch.startsWith('win32')) {
throw new Error(`Intel Architecture 32-bit systems require manual installation of libvips >= ${minimumLibvipsVersion}`);
}
if (platformAndArch === 'darwin-arm64') {
throw new Error("Please run 'brew install vips' to install libvips on Apple M1 (ARM64) systems");
}
if (platformAndArch === 'freebsd-x64' || platformAndArch === 'openbsd-x64' || platformAndArch === 'sunos-x64') {
throw new Error(`BSD/SunOS systems require manual installation of libvips >= ${minimumLibvipsVersion}`);
}
@@ -82,6 +100,11 @@ try {
throw new Error(`Use with glibc ${detectLibc.version} requires manual installation of libvips >= ${minimumLibvipsVersion}`);
}
}
if (detectLibc.family === detectLibc.MUSL && detectLibc.version) {
if (semver.lt(detectLibc.version, '1.1.24')) {
throw new Error(`Use with musl ${detectLibc.version} requires manual installation of libvips >= ${minimumLibvipsVersion}`);
}
}
const supportedNodeVersion = process.env.npm_package_engines_node || require('../package.json').engines.node;
if (!semver.satisfies(process.versions.node, supportedNodeVersion)) {
@@ -94,13 +117,11 @@ try {
const tarFilename = ['libvips', minimumLibvipsVersion, platformAndArch].join('-') + '.tar.' + extension;
const tarPathCache = path.join(libvips.cachePath(), tarFilename);
if (fs.existsSync(tarPathCache)) {
npmLog.info('sharp', `Using cached ${tarPathCache}`);
extractTarball(tarPathCache);
libvips.log(`Using cached ${tarPathCache}`);
extractTarball(tarPathCache, platformAndArch);
} else {
const tarPathTemp = path.join(os.tmpdir(), `${process.pid}-${tarFilename}`);
const tmpFile = fs.createWriteStream(tarPathTemp);
const url = distBaseUrl + tarFilename;
npmLog.info('sharp', `Downloading ${url}`);
libvips.log(`Downloading ${url}`);
simpleGet({ url: url, agent: agent() }, function (err, response) {
if (err) {
fail(err);
@@ -109,24 +130,37 @@ try {
} else if (response.statusCode !== 200) {
fail(new Error(`Status ${response.statusCode} ${response.statusMessage}`));
} else {
const tarPathTemp = path.join(os.tmpdir(), `${process.pid}-${tarFilename}`);
const tmpFileStream = fs.createWriteStream(tarPathTemp);
response
.on('error', fail)
.pipe(tmpFile);
.on('error', function (err) {
tmpFileStream.destroy(err);
})
.on('close', function () {
if (!response.complete) {
tmpFileStream.destroy(new Error('Download incomplete (connection was terminated)'));
}
})
.pipe(tmpFileStream);
tmpFileStream
.on('error', function (err) {
// Clean up temporary file
fs.unlinkSync(tarPathTemp);
fail(err);
})
.on('close', function () {
try {
// Attempt to rename
fs.renameSync(tarPathTemp, tarPathCache);
} catch (err) {
// Fall back to copy and unlink
fs.copyFileSync(tarPathTemp, tarPathCache);
fs.unlinkSync(tarPathTemp);
}
extractTarball(tarPathCache);
});
}
});
tmpFile
.on('error', fail)
.on('close', function () {
try {
// Attempt to rename
fs.renameSync(tarPathTemp, tarPathCache);
} catch (err) {
// Fall back to copy and unlink
fs.copyFileSync(tarPathTemp, tarPathCache);
fs.unlinkSync(tarPathTemp);
}
extractTarball(tarPathCache);
});
}
}
} catch (err) {

View File

@@ -18,12 +18,10 @@ try {
help.push(`- Ensure "${process.platform}" is used at install time as well as runtime`);
} else if (/dylib/.test(err.message) && /Incompatible library version/.test(err.message)) {
help.push('- Run "brew update && brew upgrade vips"');
} else if (/Cannot find module/.test(err.message)) {
help.push('- Run "npm rebuild --verbose sharp" and look for errors');
} else {
help.push(
'- Remove the "node_modules/sharp" directory then run',
' "npm install --ignore-scripts=false --verbose" and look for errors'
' "npm install --ignore-scripts=false --verbose sharp" and look for errors'
);
}
help.push(
@@ -117,7 +115,7 @@ const debuglog = util.debuglog('sharp');
* sigma: 30
* }
* }
* }.toFile('noise.png');
* }).toFile('noise.png');
*
* @param {(Buffer|Uint8Array|Uint8ClampedArray|string)} [input] - if present, can be
* a Buffer / Uint8Array / Uint8ClampedArray containing JPEG, PNG, WebP, AVIF, GIF, SVG, TIFF or raw pixel image data, or
@@ -244,7 +242,7 @@ const Sharp = function (input, options) {
jpegOptimiseCoding: true,
jpegQuantisationTable: 0,
pngProgressive: false,
pngCompressionLevel: 9,
pngCompressionLevel: 6,
pngAdaptiveFiltering: false,
pngPalette: false,
pngQuality: 100,
@@ -281,6 +279,7 @@ const Sharp = function (input, options) {
tileSkipBlanks: -1,
tileBackground: [255, 255, 255, 255],
tileCentre: false,
tileId: 'https://example.com/iiif',
linearA: 1,
linearB: 0,
// Function to notify of libvips warnings

View File

@@ -37,6 +37,23 @@ const cachePath = function () {
return libvipsCachePath;
};
const log = function (item) {
if (item instanceof Error) {
console.error(`sharp: ${item.message}`);
} else {
console.log(`sharp: ${item}`);
}
};
const isRosetta = function () {
/* istanbul ignore next */
if (process.platform === 'darwin' && process.arch === 'x64') {
const translated = spawnSync('sysctl sysctl.proc_translated', spawnSyncOptions).stdout;
return (translated || '').trim() === 'sysctl.proc_translated: 1';
}
return false;
};
const globalLibvipsVersion = function () {
if (process.platform !== 'win32') {
const globalLibvipsVersion = spawnSync(`PKG_CONFIG_PATH="${pkgConfigPath()}" pkg-config --modversion vips-cpp`, spawnSyncOptions).stdout;
@@ -82,7 +99,10 @@ const useGlobalLibvips = function () {
if (Boolean(env.SHARP_IGNORE_GLOBAL_LIBVIPS) === true) {
return false;
}
/* istanbul ignore next */
if (isRosetta()) {
return false;
}
const globalVipsVersion = globalLibvipsVersion();
return !!globalVipsVersion && /* istanbul ignore next */
semver.gte(globalVipsVersion, minimumLibvipsVersion);
@@ -92,6 +112,7 @@ module.exports = {
minimumLibvipsVersion,
minimumLibvipsVersionLabelled,
cachePath,
log,
globalLibvipsVersion,
hasVendoredLibvips,
pkgConfigPath,

View File

@@ -1,6 +1,5 @@
'use strict';
const { flatten: flattenArray } = require('array-flatten');
const color = require('color');
const is = require('./is');
@@ -127,7 +126,7 @@ function flop (flop) {
* @throws {Error} Invalid parameters
*/
function affine (matrix, options) {
const flatMatrix = flattenArray(matrix);
const flatMatrix = [].concat(...matrix);
if (flatMatrix.length === 4 && flatMatrix.every(is.number)) {
this.options.affineMatrix = flatMatrix;
} else {
@@ -269,7 +268,13 @@ function blur (sigma) {
}
/**
* Merge alpha transparency channel, if any, with a background.
* Merge alpha transparency channel, if any, with a background, then remove the alpha channel.
*
* @example
* await sharp(rgbaInput)
* .flatten('#F0A703')
* .toBuffer();
*
* @param {Object} [options]
* @param {string|Object} [options.background={r: 0, g: 0, b: 0}] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black.
* @returns {Sharp}
@@ -397,7 +402,7 @@ function convolve (kernel) {
}
/**
* Any pixel value greather than or equal to the threshold value will be set to 255, otherwise it will be set to 0.
* Any pixel value greater than or equal to the threshold value will be set to 255, otherwise it will be set to 0.
* @param {number} [threshold=128] - a value in the range 0-255 representing the level at which the threshold will be applied.
* @param {Object} [options]
* @param {Boolean} [options.greyscale=true] - convert to single channel greyscale.

View File

@@ -16,6 +16,8 @@ const formats = new Map([
['gif', 'gif']
]);
const errMagickSave = new Error('GIF output requires libvips with support for ImageMagick');
/**
* Write output image data to a file.
*
@@ -47,25 +49,23 @@ const formats = new Map([
* @throws {Error} Invalid parameters
*/
function toFile (fileOut, callback) {
if (!fileOut || fileOut.length === 0) {
const errOutputInvalid = new Error('Missing output file path');
let err;
if (!is.string(fileOut)) {
err = new Error('Missing output file path');
} else if (this.options.input.file === fileOut) {
err = new Error('Cannot use same file for input and output');
} else if (this.options.formatOut === 'input' && fileOut.toLowerCase().endsWith('.gif') && !this.constructor.format.magick.output.file) {
err = errMagickSave;
}
if (err) {
if (is.fn(callback)) {
callback(errOutputInvalid);
callback(err);
} else {
return Promise.reject(errOutputInvalid);
return Promise.reject(err);
}
} else {
if (this.options.input.file === fileOut) {
const errOutputIsInput = new Error('Cannot use same file for input and output');
if (is.fn(callback)) {
callback(errOutputIsInput);
} else {
return Promise.reject(errOutputIsInput);
}
} else {
this.options.fileOut = fileOut;
return this._pipeline(callback);
}
this.options.fileOut = fileOut;
return this._pipeline(callback);
}
return this;
}
@@ -105,10 +105,10 @@ function toFile (fileOut, callback) {
* .catch(err => { ... });
*
* @example
* const data = await sharp('my-image.jpg')
* const { data, info } = await sharp('my-image.jpg')
* // output the raw pixels
* .raw()
* .toBuffer();
* .toBuffer({ resolveWithObject: true });
*
* // create a more type safe way to work with the raw pixel data
* // this will not copy the data, instead it will change `data`s underlying ArrayBuffer
@@ -116,7 +116,9 @@ function toFile (fileOut, callback) {
* const pixelArray = new Uint8ClampedArray(data.buffer);
*
* // When you are done changing the pixelArray, sharp takes the `pixelArray` as an input
* await sharp(pixelArray).toFile('my-changed-image.jpg');
* const { width, height, channels } = info;
* await sharp(pixelArray, { raw: { width, height, channels } })
* .toFile('my-changed-image.jpg');
*
* @param {Object} [options]
* @param {boolean} [options.resolveWithObject] Resolve the Promise with an Object containing `data` and `info` properties instead of resolving only with `data`.
@@ -188,7 +190,7 @@ function withMetadata (options) {
* @throws {Error} unsupported format or options
*/
function toFormat (format, options) {
const actualFormat = formats.get(is.object(format) && is.string(format.id) ? format.id : format);
const actualFormat = formats.get((is.object(format) && is.string(format.id) ? format.id : format).toLowerCase());
if (!actualFormat) {
throw is.invalidParameterError('format', `one of: ${[...formats.keys()].join(', ')}`, format);
}
@@ -198,8 +200,6 @@ function toFormat (format, options) {
/**
* Use these JPEG options for output image.
*
* Some of these options require the use of a globally-installed libvips compiled with support for mozjpeg.
*
* @example
* // Convert any input to very high quality JPEG output
* const data = await sharp(input)
@@ -209,18 +209,25 @@ function toFormat (format, options) {
* })
* .toBuffer();
*
* @example
* // Use mozjpeg to reduce output JPEG file size (slower)
* const data = await sharp(input)
* .jpeg({ mozjpeg: true })
* .toBuffer();
*
* @param {Object} [options] - output options
* @param {number} [options.quality=80] - quality, integer 1-100
* @param {boolean} [options.progressive=false] - use progressive (interlace) scan
* @param {string} [options.chromaSubsampling='4:2:0'] - set to '4:4:4' to prevent chroma subsampling otherwise defaults to '4:2:0' chroma subsampling
* @param {boolean} [options.optimiseCoding=true] - optimise Huffman coding tables
* @param {boolean} [options.optimizeCoding=true] - alternative spelling of optimiseCoding
* @param {boolean} [options.trellisQuantisation=false] - apply trellis quantisation, requires libvips compiled with support for mozjpeg
* @param {boolean} [options.overshootDeringing=false] - apply overshoot deringing, requires libvips compiled with support for mozjpeg
* @param {boolean} [options.optimiseScans=false] - optimise progressive scans, forces progressive, requires libvips compiled with support for mozjpeg
* @param {boolean} [options.optimizeScans=false] - alternative spelling of optimiseScans, requires libvips compiled with support for mozjpeg
* @param {number} [options.quantisationTable=0] - quantization table to use, integer 0-8, requires libvips compiled with support for mozjpeg
* @param {number} [options.quantizationTable=0] - alternative spelling of quantisationTable, requires libvips compiled with support for mozjpeg
* @param {boolean} [options.mozjpeg=false] - use mozjpeg defaults, equivalent to `{ trellisQuantisation: true, overshootDeringing: true, optimiseScans: true, quantisationTable: 3 }`
* @param {boolean} [options.trellisQuantisation=false] - apply trellis quantisation
* @param {boolean} [options.overshootDeringing=false] - apply overshoot deringing
* @param {boolean} [options.optimiseScans=false] - optimise progressive scans, forces progressive
* @param {boolean} [options.optimizeScans=false] - alternative spelling of optimiseScans
* @param {number} [options.quantisationTable=0] - quantization table to use, integer 0-8
* @param {number} [options.quantizationTable=0] - alternative spelling of quantisationTable
* @param {boolean} [options.force=true] - force JPEG output, otherwise attempt to use input format
* @returns {Sharp}
* @throws {Error} Invalid options
@@ -244,6 +251,23 @@ function jpeg (options) {
throw is.invalidParameterError('chromaSubsampling', 'one of: 4:2:0, 4:4:4', options.chromaSubsampling);
}
}
const optimiseCoding = is.bool(options.optimizeCoding) ? options.optimizeCoding : options.optimiseCoding;
if (is.defined(optimiseCoding)) {
this._setBooleanOption('jpegOptimiseCoding', optimiseCoding);
}
if (is.defined(options.mozjpeg)) {
if (is.bool(options.mozjpeg)) {
if (options.mozjpeg) {
this.options.jpegTrellisQuantisation = true;
this.options.jpegOvershootDeringing = true;
this.options.jpegOptimiseScans = true;
this.options.jpegProgressive = true;
this.options.jpegQuantisationTable = 3;
}
} else {
throw is.invalidParameterError('mozjpeg', 'boolean', options.mozjpeg);
}
}
const trellisQuantisation = is.bool(options.trellisQuantization) ? options.trellisQuantization : options.trellisQuantisation;
if (is.defined(trellisQuantisation)) {
this._setBooleanOption('jpegTrellisQuantisation', trellisQuantisation);
@@ -258,10 +282,6 @@ function jpeg (options) {
this.options.jpegProgressive = true;
}
}
const optimiseCoding = is.bool(options.optimizeCoding) ? options.optimizeCoding : options.optimiseCoding;
if (is.defined(optimiseCoding)) {
this._setBooleanOption('jpegOptimiseCoding', optimiseCoding);
}
const quantisationTable = is.number(options.quantizationTable) ? options.quantizationTable : options.quantisationTable;
if (is.defined(quantisationTable)) {
if (is.integer(quantisationTable) && is.inRange(quantisationTable, 0, 8)) {
@@ -277,26 +297,31 @@ function jpeg (options) {
/**
* Use these PNG options for output image.
*
* PNG output is always full colour at 8 or 16 bits per pixel.
* By default, PNG output is full colour at 8 or 16 bits per pixel.
* Indexed PNG input at 1, 2 or 4 bits per pixel is converted to 8 bits per pixel.
*
* Some of these options require the use of a globally-installed libvips compiled with support for libimagequant (GPL).
* Set `palette` to `true` for slower, indexed PNG output.
*
* @example
* // Convert any input to PNG output
* // Convert any input to full colour PNG output
* const data = await sharp(input)
* .png()
* .toBuffer();
*
* @example
* // Convert any input to indexed PNG output (slower)
* const data = await sharp(input)
* .png({ palette: true })
* .toBuffer();
*
* @param {Object} [options]
* @param {boolean} [options.progressive=false] - use progressive (interlace) scan
* @param {number} [options.compressionLevel=9] - zlib compression level, 0-9
* @param {number} [options.compressionLevel=6] - zlib compression level, 0 (fastest, largest) to 9 (slowest, smallest)
* @param {boolean} [options.adaptiveFiltering=false] - use adaptive row filtering
* @param {boolean} [options.palette=false] - quantise to a palette-based image with alpha transparency support, requires libvips compiled with support for libimagequant
* @param {number} [options.quality=100] - use the lowest number of colours needed to achieve given quality, sets `palette` to `true`, requires libvips compiled with support for libimagequant
* @param {number} [options.colours=256] - maximum number of palette entries, sets `palette` to `true`, requires libvips compiled with support for libimagequant
* @param {number} [options.colors=256] - alternative spelling of `options.colours`, sets `palette` to `true`, requires libvips compiled with support for libimagequant
* @param {number} [options.dither=1.0] - level of Floyd-Steinberg error diffusion, sets `palette` to `true`, requires libvips compiled with support for libimagequant
* @param {boolean} [options.palette=false] - quantise to a palette-based image with alpha transparency support
* @param {number} [options.quality=100] - use the lowest number of colours needed to achieve given quality, sets `palette` to `true`
* @param {number} [options.colours=256] - maximum number of palette entries, sets `palette` to `true`
* @param {number} [options.colors=256] - alternative spelling of `options.colours`, sets `palette` to `true`
* @param {number} [options.dither=1.0] - level of Floyd-Steinberg error diffusion, sets `palette` to `true`
* @param {boolean} [options.force=true] - force PNG output, otherwise attempt to use input format
* @returns {Sharp}
* @throws {Error} Invalid options
@@ -426,7 +451,7 @@ function webp (options) {
/* istanbul ignore next */
function gif (options) {
if (!this.constructor.format.magick.output.buffer) {
throw new Error('The gif operation requires libvips to have been installed with support for ImageMagick');
throw errMagickSave;
}
trySetAnimationOptions(options, this.options);
return this._updateFormatOut('gif', options);
@@ -582,7 +607,7 @@ function tiff (options) {
* @param {Object} [options] - output options
* @param {number} [options.quality=50] - quality, integer 1-100
* @param {boolean} [options.lossless=false] - use lossless compression
* @param {boolean} [options.speed=5] - CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest)
* @param {number} [options.speed=5] - CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest)
* @param {string} [options.chromaSubsampling='4:2:0'] - set to '4:4:4' to prevent chroma subsampling otherwise defaults to '4:2:0' chroma subsampling, requires libvips v8.11.0
* @returns {Sharp}
* @throws {Error} Invalid options
@@ -704,6 +729,7 @@ function raw () {
* @param {string} [options.layout='dz'] filesystem layout, possible values are `dz`, `iiif`, `zoomify` or `google`.
* @param {boolean} [options.centre=false] centre image in tile.
* @param {boolean} [options.center=false] alternative spelling of centre.
* @param {string} [options.id='https://example.com/iiif'] when `layout` is `iiif`, sets the `@id` attribute of `info.json`
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
@@ -777,6 +803,14 @@ function tile (options) {
if (is.defined(centre)) {
this._setBooleanOption('tileCentre', centre);
}
// @id attribute for IIIF layout
if (is.defined(options.id)) {
if (is.string(options.id)) {
this.options.tileId = options.id;
} else {
throw is.invalidParameterError('id', 'string', options.id);
}
}
}
// Format
if (is.inArray(this.options.formatOut, ['jpeg', 'png', 'webp'])) {

View File

@@ -302,11 +302,20 @@ function resize (width, height, options) {
* })
* ...
*
* @example
* // Add a row of 10 red pixels to the bottom
* sharp(input)
* .extend({
* bottom: 10,
* background: 'red'
* })
* ...
*
* @param {(number|Object)} extend - single pixel count to add to all edges or an Object with per-edge counts
* @param {number} [extend.top]
* @param {number} [extend.left]
* @param {number} [extend.bottom]
* @param {number} [extend.right]
* @param {number} [extend.top=0]
* @param {number} [extend.left=0]
* @param {number} [extend.bottom=0]
* @param {number} [extend.right=0]
* @param {String|Object} [extend.background={r: 0, g: 0, b: 0, alpha: 1}] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black without transparency.
* @returns {Sharp}
* @throws {Error} Invalid parameters
@@ -317,17 +326,35 @@ function extend (extend) {
this.options.extendBottom = extend;
this.options.extendLeft = extend;
this.options.extendRight = extend;
} else if (
is.object(extend) &&
is.integer(extend.top) && extend.top >= 0 &&
is.integer(extend.bottom) && extend.bottom >= 0 &&
is.integer(extend.left) && extend.left >= 0 &&
is.integer(extend.right) && extend.right >= 0
) {
this.options.extendTop = extend.top;
this.options.extendBottom = extend.bottom;
this.options.extendLeft = extend.left;
this.options.extendRight = extend.right;
} else if (is.object(extend)) {
if (is.defined(extend.top)) {
if (is.integer(extend.top) && extend.top >= 0) {
this.options.extendTop = extend.top;
} else {
throw is.invalidParameterError('top', 'positive integer', extend.top);
}
}
if (is.defined(extend.bottom)) {
if (is.integer(extend.bottom) && extend.bottom >= 0) {
this.options.extendBottom = extend.bottom;
} else {
throw is.invalidParameterError('bottom', 'positive integer', extend.bottom);
}
}
if (is.defined(extend.left)) {
if (is.integer(extend.left) && extend.left >= 0) {
this.options.extendLeft = extend.left;
} else {
throw is.invalidParameterError('left', 'positive integer', extend.left);
}
}
if (is.defined(extend.right)) {
if (is.integer(extend.right) && extend.right >= 0) {
this.options.extendRight = extend.right;
} else {
throw is.invalidParameterError('right', 'positive integer', extend.right);
}
}
this._setBackgroundColourOption('extendBackground', extend.background);
} else {
throw is.invalidParameterError('extend', 'integer or object', extend);

View File

@@ -1,6 +1,8 @@
'use strict';
const events = require('events');
const detectLibc = require('detect-libc');
const is = require('./is');
const sharp = require('../build/Release/sharp.node');
@@ -84,8 +86,12 @@ cache(true);
/**
* Gets or, when a concurrency is provided, sets
* the number of threads _libvips'_ should create to process each image.
* The default value is the number of CPU cores.
* A value of `0` will reset to this default.
*
* The default value is the number of CPU cores,
* except when using glibc-based Linux without jemalloc,
* where the default is `1` to help reduce memory fragmentation.
*
* A value of `0` will reset this to the number of CPU cores.
*
* The maximum number of images that can be processed in parallel
* is limited by libuv's `UV_THREADPOOL_SIZE` environment variable.
@@ -103,6 +109,11 @@ cache(true);
function concurrency (concurrency) {
return sharp.concurrency(is.integer(concurrency) ? concurrency : null);
}
/* istanbul ignore next */
if (detectLibc.family === detectLibc.GLIBC && !sharp._isUsingJemalloc()) {
// Reduce default concurrency to 1 when using glibc memory allocator
sharp.concurrency(1);
}
/**
* An EventEmitter that emits a `change` event when a task is either:
@@ -157,14 +168,10 @@ simd(true);
* @private
*/
module.exports = function (Sharp) {
[
cache,
concurrency,
counters,
simd
].forEach(function (f) {
Sharp[f.name] = f;
});
Sharp.cache = cache;
Sharp.concurrency = concurrency;
Sharp.counters = counters;
Sharp.simd = simd;
Sharp.format = format;
Sharp.interpolators = interpolators;
Sharp.versions = versions;

View File

@@ -1,7 +1,7 @@
{
"name": "sharp",
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, AVIF and TIFF images",
"version": "0.27.1",
"version": "0.28.0-beta1",
"author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://github.com/lovell/sharp",
"contributors": [
@@ -80,7 +80,7 @@
"install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node-gyp rebuild && node install/dll-copy)",
"clean": "rm -rf node_modules/ build/ vendor/ .nyc_output/ coverage/ test/fixtures/output.*",
"test": "semistandard && cpplint && npm run test-unit && npm run test-licensing",
"test-unit": "nyc --reporter=lcov --branches=99 mocha --parallel --slow=5000 --timeout=60000 ./test/unit/*.js",
"test-unit": "nyc --reporter=lcov --branches=99 mocha --slow=1000 --timeout=60000 ./test/unit/*.js",
"test-licensing": "license-checker --production --summary --onlyAllow=\"Apache-2.0;BSD;ISC;MIT\"",
"test-coverage": "./test/coverage/report.sh",
"test-leak": "./test/leak/leak.sh",
@@ -117,14 +117,12 @@
"vips"
],
"dependencies": {
"array-flatten": "^3.0.0",
"color": "^3.1.3",
"detect-libc": "^1.0.3",
"node-addon-api": "^3.1.0",
"npmlog": "^4.1.2",
"prebuild-install": "^6.0.0",
"semver": "^7.3.4",
"simple-get": "^4.0.0",
"prebuild-install": "^6.0.1",
"semver": "^7.3.5",
"simple-get": "^3.1.0",
"tar-fs": "^2.1.1",
"tunnel-agent": "^0.6.0"
},
@@ -132,11 +130,11 @@
"async": "^3.2.0",
"cc": "^3.0.1",
"decompress-zip": "^0.3.3",
"documentation": "^13.1.1",
"documentation": "^13.2.0",
"exif-reader": "^1.0.3",
"icc": "^2.0.0",
"license-checker": "^25.0.1",
"mocha": "^8.2.1",
"mocha": "^8.3.2",
"mock-fs": "^4.13.0",
"nyc": "^15.1.0",
"prebuild": "^10.0.1",
@@ -145,7 +143,7 @@
},
"license": "Apache-2.0",
"config": {
"libvips": "8.10.5",
"libvips": "8.10.6",
"runtime": "napi",
"target": 3
},

View File

@@ -421,12 +421,7 @@ namespace sharp {
Uses colour space interpretation with number of channels to guess this.
*/
bool HasAlpha(VImage image) {
int const bands = image.bands();
VipsInterpretation const interpretation = image.interpretation();
return (
(bands == 2 && interpretation == VIPS_INTERPRETATION_B_W) ||
(bands == 4 && interpretation != VIPS_INTERPRETATION_CMYK) ||
(bands == 5 && interpretation == VIPS_INTERPRETATION_CMYK));
return image.has_alpha();
}
/*

View File

@@ -26,8 +26,8 @@
#if (VIPS_MAJOR_VERSION < 8) || \
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 10) || \
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 10 && VIPS_MICRO_VERSION < 5)
#error "libvips version 8.10.5+ is required - please see https://sharp.pixelplumbing.com/install"
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 10 && VIPS_MICRO_VERSION < 6)
#error "libvips version 8.10.6+ is required - please see https://sharp.pixelplumbing.com/install"
#endif
#if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6)))

View File

@@ -149,8 +149,8 @@ namespace sharp {
*/
VImage Recomb(VImage image, std::unique_ptr<double[]> const &matrix) {
double *m = matrix.get();
image = image.colourspace(VIPS_INTERPRETATION_sRGB);
return image
.colourspace(VIPS_INTERPRETATION_sRGB)
.recomb(image.bands() == 3
? VImage::new_from_memory(
m, 9 * sizeof(double), 3, 3, 1, VIPS_FORMAT_DOUBLE

View File

@@ -562,9 +562,17 @@ class PipelineWorker : public Napi::AsyncWorker {
// Use gravity in overlay
if (compositeImage.width() <= baton->width) {
across = static_cast<int>(ceil(static_cast<double>(image.width()) / compositeImage.width()));
// Ensure odd number of tiles across when gravity is centre, north or south
if (composite->gravity == 0 || composite->gravity == 1 || composite->gravity == 3) {
across |= 1;
}
}
if (compositeImage.height() <= baton->height) {
down = static_cast<int>(ceil(static_cast<double>(image.height()) / compositeImage.height()));
// Ensure odd number of tiles down when gravity is centre, east or west
if (composite->gravity == 0 || composite->gravity == 2 || composite->gravity == 4) {
down |= 1;
}
}
if (across != 0 || down != 0) {
int left;
@@ -594,8 +602,13 @@ class PipelineWorker : public Napi::AsyncWorker {
int top;
if (composite->hasOffset) {
// Composite image at given offsets
std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),
compositeImage.width(), compositeImage.height(), composite->left, composite->top);
if (composite->tile) {
std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),
compositeImage.width(), compositeImage.height(), composite->left, composite->top);
} else {
left = composite->left;
top = composite->top;
}
} else {
// Composite image with given gravity
std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),
@@ -1038,6 +1051,7 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("angle", CalculateAngleRotation(baton->tileAngle))
->set("background", baton->tileBackground)
->set("centre", baton->tileCentre)
->set("id", const_cast<char*>(baton->tileId.data()))
->set("skip_blanks", baton->tileSkipBlanks);
// libvips chooses a default depth based on layout. Instead of replicating that logic here by
// not passing anything - libvips will handle choice
@@ -1438,6 +1452,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_DZ_DEPTH,
sharp::AttrAsStr(options, "tileDepth").data()));
baton->tileCentre = sharp::AttrAsBool(options, "tileCentre");
baton->tileId = sharp::AttrAsStr(options, "tileId");
// Force random access for certain operations
if (baton->input->access == VIPS_ACCESS_SEQUENTIAL) {

View File

@@ -192,6 +192,7 @@ struct PipelineBaton {
std::vector<double> tileBackground;
int tileSkipBlanks;
VipsForeignDzDepth tileDepth;
std::string tileId;
std::unique_ptr<double[]> recombMatrix;
PipelineBaton():
@@ -258,7 +259,7 @@ struct PipelineBaton {
jpegOptimiseScans(false),
jpegOptimiseCoding(true),
pngProgressive(false),
pngCompressionLevel(9),
pngCompressionLevel(6),
pngAdaptiveFiltering(false),
pngPalette(false),
pngQuality(100),

View File

@@ -44,6 +44,7 @@ Napi::Object init(Napi::Env env, Napi::Object exports) {
exports.Set("libvipsVersion", Napi::Function::New(env, libvipsVersion));
exports.Set("format", Napi::Function::New(env, format));
exports.Set("_maxColourDistance", Napi::Function::New(env, _maxColourDistance));
exports.Set("_isUsingJemalloc", Napi::Function::New(env, _isUsingJemalloc));
exports.Set("stats", Napi::Function::New(env, stats));
return exports;
}

View File

@@ -225,3 +225,19 @@ Napi::Value _maxColourDistance(const Napi::CallbackInfo& info) {
return Napi::Number::New(env, maxColourDistance);
}
#if defined(__GNUC__)
// mallctl will be resolved by the runtime linker when jemalloc is being used
extern "C" {
int mallctl(const char *name, void *oldp, size_t *oldlenp, void *newp, size_t newlen) __attribute__((weak));
}
Napi::Value _isUsingJemalloc(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
return Napi::Boolean::New(env, mallctl != nullptr);
}
#else
Napi::Value _isUsingJemalloc(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
return Napi::Boolean::New(env, false);
}
#endif

View File

@@ -24,5 +24,6 @@ Napi::Value simd(const Napi::CallbackInfo& info);
Napi::Value libvipsVersion(const Napi::CallbackInfo& info);
Napi::Value format(const Napi::CallbackInfo& info);
Napi::Value _maxColourDistance(const Napi::CallbackInfo& info);
Napi::Value _isUsingJemalloc(const Napi::CallbackInfo& info);
#endif // SRC_UTILITIES_H_

View File

@@ -13,7 +13,7 @@
"gm": "1.23.1",
"imagemagick": "0.1.3",
"jimp": "0.16.1",
"mapnik": "4.5.5",
"mapnik": "4.5.6",
"semver": "7.3.4"
},
"license": "Apache-2.0",

View File

@@ -1,5 +1,6 @@
'use strict';
const os = require('os');
const fs = require('fs');
const async = require('async');
@@ -25,6 +26,9 @@ const height = 588;
// Disable libvips cache to ensure tests are as fair as they can be
sharp.cache(false);
// Spawn one thread per CPU
sharp.concurrency(os.cpus().length);
async.series({
jpeg: function (callback) {
const inputJpgBuffer = fs.readFileSync(fixtures.inputJpg);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 664 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 426 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 692 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 653 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 606 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 672 KiB

View File

@@ -109,7 +109,7 @@ module.exports = {
inputSvg: getPath('check.svg'), // http://dev.w3.org/SVG/tools/svgweb/samples/svg-files/check.svg
inputSvgSmallViewBox: getPath('circle.svg'),
inputSvgWithEmbeddedImages: getPath('struct-image-04-t.svg'), // https://dev.w3.org/SVG/profiles/1.2T/test/svg/struct-image-04-t.svg
inputAvif: getPath('cosmos_frame12924_yuv420_10bpc_bt2020_pq_q50.avif'), // CC by-nc-nd https://github.com/AOMediaCodec/av1-avif/tree/master/testFiles/Netflix
inputAvif: getPath('sdr_cosmos12920_cicp1-13-6_yuv444_full_qp10.avif'), // CC by-nc-nd https://github.com/AOMediaCodec/av1-avif/tree/master/testFiles/Netflix
inputJPGBig: getPath('flowers.jpeg'),

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 KiB

View File

@@ -426,6 +426,16 @@
...
fun:vips_target_finish
}
{
value8_libvips_static
Memcheck:Value8
obj:*/libvips-cpp.so.*
}
{
cond_libvips_static
Memcheck:Cond
obj:*/libvips-cpp.so.*
}
{
leak_libvips_init
Memcheck:Leak
@@ -440,7 +450,7 @@
match-leak-kinds: definite
fun:malloc
...
fun:rsvg_rust_handle_new_from_stream_sync
fun:rsvg_handle_new_from_stream_sync
}
{
leak_rsvg_rsvg_rust_handle_new_from_gfile_sync
@@ -448,7 +458,7 @@
match-leak-kinds: definite
fun:malloc
...
fun:rsvg_rust_handle_new_from_gfile_sync
fun:rsvg_handle_new_from_gfile_sync
}
{
leak_rsvg_rust_handle_new_from_stream_sync
@@ -458,7 +468,7 @@
...
fun:xmlParseElement
...
fun:rsvg_rust_handle_new_from_stream_sync
fun:rsvg_handle_new_from_stream_sync
}
{
leak_rsvg_rust_handle_new_from_gfile_sync
@@ -468,7 +478,7 @@
...
fun:xmlParseElement
...
fun:rsvg_rust_handle_new_from_gfile_sync
fun:rsvg_handle_new_from_gfile_sync
}
# libuv warnings

View File

@@ -12,29 +12,6 @@ describe('AVIF', () => {
});
});
it('can passthrough AVIF', async () => {
const data = await sharp(inputAvif)
.resize(32)
.toBuffer();
const metadata = await sharp(data)
.metadata();
const { compression, size, ...metadataWithoutSize } = metadata;
assert.deepStrictEqual(metadataWithoutSize, {
channels: 3,
depth: 'uchar',
format: 'heif',
hasAlpha: false,
hasProfile: false,
height: 12,
isProgressive: false,
pageHeight: 12,
pagePrimary: 0,
pages: 1,
space: 'srgb',
width: 32
});
});
it('can convert AVIF to JPEG', async () => {
const data = await sharp(inputAvif)
.resize(32)
@@ -81,4 +58,27 @@ describe('AVIF', () => {
width: 32
});
});
it('can passthrough AVIF', async () => {
const data = await sharp(inputAvif)
.resize(32)
.toBuffer();
const metadata = await sharp(data)
.metadata();
const { compression, size, ...metadataWithoutSize } = metadata;
assert.deepStrictEqual(metadataWithoutSize, {
channels: 3,
depth: 'uchar',
format: 'heif',
hasAlpha: false,
hasProfile: false,
height: 12,
isProgressive: false,
pageHeight: 12,
pagePrimary: 0,
pages: 1,
space: 'srgb',
width: 32
});
});
});

View File

@@ -42,18 +42,15 @@ describe('Colour space conversion', function () {
});
});
if (sharp.format.tiff.input.file && sharp.format.webp.output.buffer) {
it('From 1-bit TIFF to sRGB WebP [slow]', function (done) {
sharp(fixtures.inputTiff)
.webp()
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual('webp', info.format);
done();
});
});
}
it('From 1-bit TIFF to sRGB WebP', async () => {
const data = await sharp(fixtures.inputTiff)
.resize(8, 8)
.webp()
.toBuffer();
const { format } = await sharp(data).metadata();
assert.strictEqual(format, 'webp');
});
it('From CMYK to sRGB', function (done) {
sharp(fixtures.inputJpgWithCmykProfile)

View File

@@ -225,6 +225,24 @@ describe('composite', () => {
});
});
it('centre gravity should replicate correct number of tiles', async () => {
const red = { r: 255, g: 0, b: 0 };
const [r, g, b] = await sharp({
create: {
width: 40, height: 40, channels: 4, background: red
}
})
.composite([{
input: fixtures.inputPngWithTransparency16bit,
gravity: 'centre',
tile: true
}])
.raw()
.toBuffer();
assert.deepStrictEqual({ r, g, b }, red);
});
it('cutout via dest-in', done => {
sharp(fixtures.inputJpg)
.resize(300, 300)
@@ -390,4 +408,20 @@ describe('composite', () => {
}, /Expected valid gravity for gravity but received invalid of type string/);
});
});
it('Allow offset beyond bottom/right edge', async () => {
const red = { r: 255, g: 0, b: 0 };
const blue = { r: 0, g: 0, b: 255 };
const [r, g, b] = await sharp({ create: { width: 2, height: 2, channels: 4, background: red } })
.composite([{
input: { create: { width: 2, height: 2, channels: 4, background: blue } },
top: 1,
left: 1
}])
.raw()
.toBuffer();
assert.deepStrictEqual(red, { r, g, b });
});
});

View File

@@ -41,7 +41,6 @@ describe('Extend', function () {
.resize(120)
.extend({
top: 50,
bottom: 0,
left: 10,
right: 35,
background: { r: 0, g: 0, b: 0, alpha: 0 }
@@ -64,18 +63,38 @@ describe('Extend', function () {
sharp().extend(-1);
});
});
it('partial object fails', function () {
assert.throws(function () {
sharp().extend({ top: 1 });
});
it('invalid top fails', () => {
assert.throws(
() => sharp().extend({ top: 'fail' }),
/Expected positive integer for top but received fail of type string/
);
});
it('invalid bottom fails', () => {
assert.throws(
() => sharp().extend({ bottom: -1 }),
/Expected positive integer for bottom but received -1 of type number/
);
});
it('invalid left fails', () => {
assert.throws(
() => sharp().extend({ left: 0.1 }),
/Expected positive integer for left but received 0.1 of type number/
);
});
it('invalid right fails', () => {
assert.throws(
() => sharp().extend({ right: {} }),
/Expected positive integer for right but received \[object Object\] of type object/
);
});
it('can set all edges apart from right', () => {
assert.doesNotThrow(() => sharp().extend({ top: 1, left: 2, bottom: 3 }));
});
it('should add alpha channel before extending with a transparent Background', function (done) {
sharp(fixtures.inputJpgWithLandscapeExif1)
.extend({
top: 0,
bottom: 10,
left: 0,
right: 10,
background: { r: 0, g: 0, b: 0, alpha: 0 }
})
@@ -91,9 +110,7 @@ describe('Extend', function () {
it('PNG with 2 channels', function (done) {
sharp(fixtures.inputPngWithGreyAlpha)
.extend({
top: 0,
bottom: 20,
left: 0,
right: 20,
background: 'transparent'
})

View File

@@ -28,32 +28,28 @@ describe('Partial image extraction', function () {
});
});
if (sharp.format.webp.output.file) {
it('WebP', function (done) {
sharp(fixtures.inputWebP)
.extract({ left: 100, top: 50, width: 125, height: 200 })
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(125, info.width);
assert.strictEqual(200, info.height);
fixtures.assertSimilar(fixtures.expected('extract.webp'), data, done);
});
});
}
it('WebP', function (done) {
sharp(fixtures.inputWebP)
.extract({ left: 100, top: 50, width: 125, height: 200 })
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(125, info.width);
assert.strictEqual(200, info.height);
fixtures.assertSimilar(fixtures.expected('extract.webp'), data, done);
});
});
if (sharp.format.tiff.output.file) {
it('TIFF', function (done) {
sharp(fixtures.inputTiff)
.extract({ left: 34, top: 63, width: 341, height: 529 })
.jpeg()
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(341, info.width);
assert.strictEqual(529, info.height);
fixtures.assertSimilar(fixtures.expected('extract.tiff'), data, done);
});
});
}
it('TIFF', function (done) {
sharp(fixtures.inputTiff)
.extract({ left: 34, top: 63, width: 341, height: 529 })
.jpeg()
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(341, info.width);
assert.strictEqual(529, info.height);
fixtures.assertSimilar(fixtures.expected('extract.tiff'), data, done);
});
});
it('Before resize', function (done) {
sharp(fixtures.inputJpg)

View File

@@ -107,7 +107,7 @@ describe('Image channel extraction', function () {
});
});
it('Non-existant channel', function (done) {
it('Non-existent channel', function (done) {
sharp(fixtures.inputPng)
.extractChannel(1)
.toBuffer(function (err) {

View File

@@ -63,12 +63,17 @@ describe('GIF input', () => {
);
if (!sharp.format.magick.output.buffer) {
it('GIF output should fail due to missing ImageMagick', () => {
it('GIF buffer output should fail due to missing ImageMagick', () => {
assert.throws(
() => {
sharp().gif();
},
/The gif operation requires libvips to have been installed with support for ImageMagick/
() => sharp().gif(),
/GIF output requires libvips with support for ImageMagick/
);
});
it('GIF file output should fail due to missing ImageMagick', () => {
assert.rejects(
async () => await sharp().toFile('test.gif'),
/GIF output requires libvips with support for ImageMagick/
);
});
}

View File

@@ -290,4 +290,24 @@ describe('JPEG', function () {
});
});
});
it('Can use mozjpeg defaults', async () => {
const withoutData = await sharp(fixtures.inputJpg)
.resize(32, 24)
.jpeg({ mozjpeg: false })
.toBuffer();
const withoutMeta = await sharp(withoutData).metadata();
assert.strictEqual(false, withoutMeta.isProgressive);
const withData = await sharp(fixtures.inputJpg)
.resize(32, 24)
.jpeg({ mozjpeg: true })
.toBuffer();
const withMeta = await sharp(withData).metadata();
assert.strictEqual(true, withMeta.isProgressive);
});
it('Invalid mozjpeg value throws error', () => {
assert.throws(() => sharp().jpeg({ mozjpeg: 'fail' }));
});
});

View File

@@ -105,4 +105,30 @@ describe('libvips binaries', function () {
assert.strictEqual(true, fs.existsSync(nestedDirPath));
});
});
describe('logger', function () {
const consoleLog = console.log;
const consoleError = console.error;
after(function () {
console.log = consoleLog;
console.error = consoleError;
});
it('logs an info message', function (done) {
console.log = function (msg) {
assert.strictEqual(msg, 'sharp: progress');
done();
};
libvips.log('progress');
});
it('logs an error message', function (done) {
console.error = function (msg) {
assert.strictEqual(msg, 'sharp: problem');
done();
};
libvips.log(new Error('problem'));
});
});
});

View File

@@ -28,115 +28,138 @@ describe('Modulate', function () {
});
});
it('should be able to hue-rotate', function () {
const base = 'modulate-hue-120.jpg';
const actual = fixtures.path('output.' + base);
const expected = fixtures.expected(base);
return sharp(fixtures.inputJpg)
it('should be able to hue-rotate', async () => {
const [r, g, b] = await sharp({
create: {
width: 1,
height: 1,
channels: 3,
background: { r: 153, g: 68, b: 68 }
}
})
.modulate({ hue: 120 })
.toFile(actual)
.then(function () {
fixtures.assertMaxColourDistance(actual, expected, 25);
});
.raw()
.toBuffer();
assert.deepStrictEqual({ r: 41, g: 107, b: 57 }, { r, g, b });
});
it('should be able to brighten', function () {
const base = 'modulate-brightness-2.jpg';
const actual = fixtures.path('output.' + base);
const expected = fixtures.expected(base);
return sharp(fixtures.inputJpg)
it('should be able to brighten', async () => {
const [r, g, b] = await sharp({
create: {
width: 1,
height: 1,
channels: 3,
background: { r: 153, g: 68, b: 68 }
}
})
.modulate({ brightness: 2 })
.toFile(actual)
.then(function () {
fixtures.assertMaxColourDistance(actual, expected, 25);
});
.raw()
.toBuffer();
assert.deepStrictEqual({ r: 255, g: 173, b: 168 }, { r, g, b });
});
it('should be able to unbrighten', function () {
const base = 'modulate-brightness-0-5.jpg';
const actual = fixtures.path('output.' + base);
const expected = fixtures.expected(base);
return sharp(fixtures.inputJpg)
it('should be able to darken', async () => {
const [r, g, b] = await sharp({
create: {
width: 1,
height: 1,
channels: 3,
background: { r: 153, g: 68, b: 68 }
}
})
.modulate({ brightness: 0.5 })
.toFile(actual)
.then(function () {
fixtures.assertMaxColourDistance(actual, expected, 25);
});
.raw()
.toBuffer();
assert.deepStrictEqual({ r: 97, g: 17, b: 25 }, { r, g, b });
});
it('should be able to saturate', function () {
const base = 'modulate-saturation-2.jpg';
const actual = fixtures.path('output.' + base);
const expected = fixtures.expected(base);
return sharp(fixtures.inputJpg)
it('should be able to saturate', async () => {
const [r, g, b] = await sharp({
create: {
width: 1,
height: 1,
channels: 3,
background: { r: 153, g: 68, b: 68 }
}
})
.modulate({ saturation: 2 })
.toFile(actual)
.then(function () {
fixtures.assertMaxColourDistance(actual, expected, 30);
});
.raw()
.toBuffer();
assert.deepStrictEqual({ r: 198, g: 0, b: 43 }, { r, g, b });
});
it('should be able to desaturate', function () {
const base = 'modulate-saturation-0.5.jpg';
const actual = fixtures.path('output.' + base);
const expected = fixtures.expected(base);
return sharp(fixtures.inputJpg)
it('should be able to desaturate', async () => {
const [r, g, b] = await sharp({
create: {
width: 1,
height: 1,
channels: 3,
background: { r: 153, g: 68, b: 68 }
}
})
.modulate({ saturation: 0.5 })
.toFile(actual)
.then(function () {
fixtures.assertMaxColourDistance(actual, expected, 25);
});
.raw()
.toBuffer();
assert.deepStrictEqual({ r: 127, g: 83, b: 81 }, { r, g, b });
});
it('should be able to modulate all channels', function () {
const base = 'modulate-all.jpg';
const actual = fixtures.path('output.' + base);
const expected = fixtures.expected(base);
return sharp(fixtures.inputJpg)
it('should be able to modulate all channels', async () => {
const [r, g, b] = await sharp({
create: {
width: 1,
height: 1,
channels: 3,
background: { r: 153, g: 68, b: 68 }
}
})
.modulate({ brightness: 2, saturation: 0.5, hue: 180 })
.toFile(actual)
.then(function () {
fixtures.assertMaxColourDistance(actual, expected, 25);
});
.raw()
.toBuffer();
assert.deepStrictEqual({ r: 149, g: 209, b: 214 }, { r, g, b });
});
describe('hue-rotate', function (done) {
[30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330, 360].forEach(function (angle) {
it('should properly hue rotate by ' + angle + 'deg', function () {
const base = 'modulate-hue-angle-' + angle + '.png';
const actual = fixtures.path('output.' + base);
it('should be able to use linear and modulate together', async () => {
const contrast = 1.5;
const brightness = 0.5;
const [r, g, b] = await sharp({
create: {
width: 1,
height: 1,
channels: 3,
background: { r: 153, g: 68, b: 68 }
}
})
.linear(contrast, -(128 * contrast) + 128)
.modulate({ brightness })
.raw()
.toBuffer();
assert.deepStrictEqual({ r: 81, g: 0, b: 0 }, { r, g, b });
});
describe('hue-rotate', () => {
[30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330, 360].forEach(angle => {
it(`should hue rotate by ${angle} deg`, async () => {
const base = `modulate-hue-angle-${angle}.png`;
const actual = fixtures.path(`output.${base}`);
const expected = fixtures.expected(base);
return sharp(fixtures.testPattern)
await sharp(fixtures.testPattern)
.resize(320)
.modulate({ hue: angle })
.png({ compressionLevel: 0 })
.toFile(actual)
.then(function () {
fixtures.assertMaxColourDistance(actual, expected, 25);
.then(() => {
fixtures.assertMaxColourDistance(actual, expected);
});
});
});
});
it('should be able to use linear and modulate together', function () {
const base = 'modulate-linear.jpg';
const actual = fixtures.path('output.' + base);
const expected = fixtures.expected(base);
const contrast = 1.5;
const brightness = 0.5;
return sharp(fixtures.testPattern)
.linear(contrast, -(128 * contrast) + 128)
.modulate({ brightness })
.toFile(actual)
.then(function () {
fixtures.assertMaxColourDistance(actual, expected);
});
});
});

View File

@@ -54,6 +54,7 @@ describe('Normalization', function () {
it('keeps an existing alpha channel', function (done) {
sharp(fixtures.inputPngWithTransparency)
.resize(8, 8)
.normalize()
.toBuffer(function (err, data) {
if (err) throw err;
@@ -69,6 +70,7 @@ describe('Normalization', function () {
it('keeps the alpha channel of greyscale images intact', function (done) {
sharp(fixtures.inputPngWithGreyAlpha)
.resize(8, 8)
.normalise()
.toBuffer(function (err, data) {
if (err) throw err;

View File

@@ -19,7 +19,7 @@ describe('PNG', function () {
});
});
it('default compressionLevel generates smaller file than compressionLevel=6', function (done) {
it('default compressionLevel generates smaller file than compressionLevel=0', function (done) {
// First generate with default compressionLevel
sharp(fixtures.inputPng)
.resize(320, 240)
@@ -31,7 +31,7 @@ describe('PNG', function () {
// Then generate with compressionLevel=6
sharp(fixtures.inputPng)
.resize(320, 240)
.png({ compressionLevel: 6 })
.png({ compressionLevel: 0 })
.toBuffer(function (err, largerData, largerInfo) {
if (err) throw err;
assert.strictEqual(true, largerData.length > 0);

View File

@@ -5,15 +5,17 @@ const assert = require('assert');
const sharp = require('../../');
const fixtures = require('../fixtures');
const sepia = [
[0.3588, 0.7044, 0.1368],
[0.299, 0.587, 0.114],
[0.2392, 0.4696, 0.0912]
];
describe('Recomb', function () {
it('applies a sepia filter using recomb', function (done) {
const output = fixtures.path('output.recomb-sepia.jpg');
sharp(fixtures.inputJpgWithLandscapeExif1)
.recomb([
[0.3588, 0.7044, 0.1368],
[0.299, 0.587, 0.114],
[0.2392, 0.4696, 0.0912]
])
.recomb(sepia)
.toFile(output, function (err, info) {
if (err) throw err;
assert.strictEqual('jpeg', info.format);
@@ -31,11 +33,7 @@ describe('Recomb', function () {
it('applies a sepia filter using recomb to an PNG with Alpha', function (done) {
const output = fixtures.path('output.recomb-sepia.png');
sharp(fixtures.inputPngAlphaPremultiplicationSmall)
.recomb([
[0.3588, 0.7044, 0.1368],
[0.299, 0.587, 0.114],
[0.2392, 0.4696, 0.0912]
])
.recomb(sepia)
.toFile(output, function (err, info) {
if (err) throw err;
assert.strictEqual('png', info.format);
@@ -50,6 +48,20 @@ describe('Recomb', function () {
});
});
it('recomb with a single channel input', async () => {
const { info } = await sharp(Buffer.alloc(64), {
raw: {
width: 8,
height: 8,
channels: 1
}
})
.recomb(sepia)
.toBuffer({ resolveWithObject: true });
assert.strictEqual(3, info.channels);
});
it('applies a different sepia filter using recomb', function (done) {
const output = fixtures.path('output.recomb-sepia2.jpg');
sharp(fixtures.inputJpgWithLandscapeExif1)

View File

@@ -1,5 +1,6 @@
'use strict';
const fs = require('fs');
const assert = require('assert');
const sharp = require('../../');
@@ -99,4 +100,18 @@ describe('SVG input', function () {
fixtures.assertSimilar(fixtures.expected('svg-embedded.png'), data, done);
});
});
it('Converts SVG with truncated embedded PNG', async () => {
const truncatedPng = fs.readFileSync(fixtures.inputPngTruncated).toString('base64');
const svg = `<?xml version="1.0" encoding="UTF-8"?>
<svg width="294" height="240" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<image width="294" height="240" xlink:href="data:image/png;base64,${truncatedPng}"/>
</svg>`;
const { info } = await sharp(Buffer.from(svg)).toBuffer({ resolveWithObject: true });
assert.strictEqual(info.format, 'png');
assert.strictEqual(info.width, 294);
assert.strictEqual(info.height, 240);
assert.strictEqual(info.channels, 4);
});
});

View File

@@ -90,9 +90,11 @@ describe('TIFF', function () {
it('Increasing TIFF quality increases file size', () =>
sharp(fixtures.inputJpgWithLandscapeExif1)
.resize(320, 240)
.tiff({ quality: 40 })
.toBuffer()
.then(tiff40 => sharp(fixtures.inputJpgWithLandscapeExif1)
.resize(320, 240)
.tiff({ quality: 90 })
.toBuffer()
.then(tiff90 =>
@@ -155,6 +157,7 @@ describe('TIFF', function () {
it('TIFF setting xres and yres on file', () =>
sharp(fixtures.inputTiff)
.resize(8, 8)
.tiff({
xres: 1000,
yres: 1000
@@ -171,6 +174,7 @@ describe('TIFF', function () {
it('TIFF setting xres and yres on buffer', () =>
sharp(fixtures.inputTiff)
.resize(8, 8)
.tiff({
xres: 1000,
yres: 1000
@@ -279,7 +283,7 @@ describe('TIFF', function () {
});
});
it('TIFF deflate compression of integral input with float predictor increases file size', function (done) {
it('TIFF deflate compression with float predictor shrinks test file', function (done) {
const startSize = fs.statSync(fixtures.inputTiffUncompressed).size;
sharp(fixtures.inputTiffUncompressed)
.tiff({
@@ -289,7 +293,7 @@ describe('TIFF', function () {
.toFile(outputTiff, (err, info) => {
if (err) throw err;
assert.strictEqual('tiff', info.format);
assert(info.size > startSize);
assert(startSize > info.size);
rimraf(outputTiff, done);
});
});

View File

@@ -297,6 +297,22 @@ describe('Tile', function () {
});
});
it('Valid id parameter value passes', function () {
assert.doesNotThrow(function () {
sharp().tile({
id: 'test'
});
});
});
it('Invalid id parameter value fails', function () {
assert.throws(function () {
sharp().tile({
id: true
});
});
});
it('Deep Zoom layout', function (done) {
const directory = fixtures.path('output.dzi_files');
rimraf(directory, function () {
@@ -613,7 +629,7 @@ describe('Tile', function () {
rimraf(directory, function () {
sharp(fixtures.inputJpg)
.png({
compressionLevel: 1
compressionLevel: 0
})
.tile({
layout: 'google'
@@ -650,7 +666,8 @@ describe('Tile', function () {
rimraf(directory, function () {
sharp(fixtures.inputJpg)
.webp({
quality: 1
quality: 1,
reductionEffort: 0
})
.tile({
layout: 'google'
@@ -814,11 +831,14 @@ describe('Tile', function () {
});
it('IIIF layout', function (done) {
const directory = fixtures.path('output.iiif.info');
const name = 'output.iiif.info';
const directory = fixtures.path(name);
rimraf(directory, function () {
const id = 'https://sharp.test.com/iiif';
sharp(fixtures.inputJpg)
.tile({
layout: 'iiif'
layout: 'iiif',
id
})
.toFile(directory, function (err, info) {
if (err) throw err;
@@ -827,6 +847,8 @@ describe('Tile', function () {
assert.strictEqual(2225, info.height);
assert.strictEqual(3, info.channels);
assert.strictEqual('number', typeof info.size);
const infoJson = require(path.join(directory, 'info.json'));
assert.strictEqual(`${id}/${name}`, infoJson['@id']);
fs.stat(path.join(directory, '0,0,256,256', '256,', '0', 'default.jpg'), function (err, stat) {
if (err) throw err;
assert.strictEqual(true, stat.isFile());

27
test/unit/toFormat.js Normal file
View File

@@ -0,0 +1,27 @@
'use strict';
const assert = require('assert');
const sharp = require('../../');
const fixtures = require('../fixtures');
describe('toFormat', () => {
it('accepts upper case characters as format parameter (string)', async () => {
const data = await sharp(fixtures.inputJpg)
.resize(8, 8)
.toFormat('PNG')
.toBuffer();
const { format } = await sharp(data).metadata();
assert.strictEqual(format, 'png');
});
it('accepts upper case characters as format parameter (object)', async () => {
const data = await sharp(fixtures.inputJpg)
.resize(8, 8)
.toFormat({ id: 'PNG' })
.toBuffer();
const { format } = await sharp(data).metadata();
assert.strictEqual(format, 'png');
});
});

View File

@@ -3,8 +3,6 @@
const assert = require('assert');
const sharp = require('../../');
const defaultConcurrency = sharp.concurrency();
describe('Utilities', function () {
describe('Cache', function () {
it('Can be disabled', function () {
@@ -60,10 +58,10 @@ describe('Utilities', function () {
});
it('Can be reset to default', function () {
sharp.concurrency(0);
assert.strictEqual(defaultConcurrency, sharp.concurrency());
assert.strictEqual(true, sharp.concurrency() > 0);
});
it('Ignores invalid values', function () {
sharp.concurrency(0);
const defaultConcurrency = sharp.concurrency();
sharp.concurrency('spoons');
assert.strictEqual(defaultConcurrency, sharp.concurrency());
});