Compare commits

...

36 Commits

Author SHA1 Message Date
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
44 changed files with 544 additions and 222 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

@@ -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

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

View File

@@ -157,8 +157,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 +165,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 +186,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 +201,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 +338,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'`)

View File

@@ -1,9 +1,47 @@
# 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.
* Ensure `@id` attribute can be set for IIIF tile-based output.
[#2612](https://github.com/lovell/sharp/issues/2612)
[@edsilv](https://github.com/edsilv)
## 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
@@ -192,6 +192,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 +213,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.

File diff suppressed because one or more lines are too long

View File

@@ -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

@@ -18,7 +18,9 @@ module.exports = [
'does',
'each',
'either',
'ensure',
'etc',
'every',
'for',
'from',
'get',

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 {

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;
}
@@ -188,7 +188,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 +198,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 +207,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 +249,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 +280,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 +295,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 +449,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 +605,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 +727,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 +801,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

@@ -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-alpha1",
"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=5000 --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",
"prebuild-install": "^6.0.1",
"semver": "^7.3.4",
"simple-get": "^4.0.0",
"simple-get": "^3.1.0",
"tar-fs": "^2.1.1",
"tunnel-agent": "^0.6.0"
},
@@ -136,7 +134,7 @@
"exif-reader": "^1.0.3",
"icc": "^2.0.0",
"license-checker": "^25.0.1",
"mocha": "^8.2.1",
"mocha": "^8.3.1",
"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-alpha3",
"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

@@ -1038,6 +1038,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 +1439,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_

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 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

@@ -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

@@ -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

@@ -279,7 +279,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 +289,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());

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

@@ -0,0 +1,26 @@
'use strict';
const assert = require('assert');
const sharp = require('../../');
const fixtures = require('../fixtures');
describe('toFormat', () => {
it('accepts upper case characters as format parameter (string)', function (done) {
sharp(fixtures.inputJpg)
.toFormat('PNG')
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual('png', info.format);
done();
});
});
it('accepts upper case characters as format parameter (object)', function (done) {
sharp(fixtures.inputJpg)
.toFormat({ id: 'PNG' })
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual('png', info.format);
done();
});
});
});

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());
});