Compare commits

..

58 Commits

Author SHA1 Message Date
Lovell Fuller
19d0e272e6 Release v0.33.4 2024-05-16 12:48:29 +01:00
Lovell Fuller
16b764f1c1 Prerelease v0.33.4-rc.0 2024-05-16 12:16:41 +01:00
Lovell Fuller
1593ee3838 Bump devDeps 2024-05-16 12:15:49 +01:00
Lovell Fuller
668b5ba8bc Docs: changelog and credit for #4096 2024-05-14 16:59:20 +01:00
Adriaan Meuris
29336f4cc7 Ensure negate op occurs after profile conversion (#4096)
- Adds CMYK to CMYK profile conversion tests
- Fixes existing greyscale plus alpha test expectation
2024-05-13 12:23:11 +01:00
Lovell Fuller
b5fddd7c5e Install: log node-addon-api version if available 2024-05-10 10:43:15 +01:00
Lovell Fuller
da655a1859 Reduce default concurrency for musl thread over-subscription
https://github.com/lovell/sharp-libvips/issues/229
2024-04-30 19:23:44 +01:00
Lovell Fuller
93c615d39f Bump devDeps 2024-04-30 19:23:36 +01:00
Lovell Fuller
f325dc3ec9 Refactor to use C++ boolean literals rather than macros 2024-04-30 19:23:31 +01:00
Skyf0l
0fde71c783 Ensure all contributor email addresses are valid (#4088) 2024-04-28 18:17:44 +01:00
Lovell Fuller
9c8fbaa1cc Issue template: fix markdown links from a9e662e 2024-04-25 11:31:45 +01:00
Lovell Fuller
a9e662e612 Issue template: add a couple of the most commonly-reported problems
This should allow more people to help themselves and prevent
extraneous issues from being opened.
2024-04-25 11:25:14 +01:00
Lovell Fuller
ba843002be Docs: use libc flag for npm Linux cross-installs 2024-04-24 08:30:50 +01:00
Lovell Fuller
7f3d452bc5 Docs: changelog and credit for #4074 2024-04-19 15:21:21 +01:00
Kleis Auke Wolthuizen
aa8bc19362 Simplify StaySequential operation (#4074) 2024-04-19 14:47:49 +01:00
Lovell Fuller
36e60bf040 CI: Upgrade linux-s390x to Debian 11
Debian LTS does not include s390x and Debian 10 is EOL so this
platform will now require a minimum version of glibc 2.31.
2024-04-19 13:13:48 +01:00
Lovell Fuller
a1309aa3b8 Tighten constructor text property validation #4071 2024-04-19 12:48:47 +01:00
Lovell Fuller
3e8a0fc522 Ensure only one StaySequential operation per pipeline
This should reduce memory consumption for more complex pipelines
that combine multiple operations without sequential access support
e.g. flip and Gaussian blur.
2024-04-19 11:27:54 +01:00
Lovell Fuller
397ee492d9 Ensure extend op is sequential for multi-page TIFF #4069 2024-04-18 11:05:04 +01:00
Lovell Fuller
52b9dc0f63 Docs: add a few more stop-words 2024-04-11 10:31:38 +01:00
Lovell Fuller
a715c73fc2 Docs: remove GA, allow for possible use of CF 2024-04-11 10:23:27 +01:00
Lovell Fuller
579cf93030 Install: advanced option to force global libvips #4060 2024-04-10 09:25:53 +01:00
Lovell Fuller
f67228e5ea Remove experimental status from pipelineColourspace 2024-04-09 22:32:17 +01:00
Lovell Fuller
6257994746 Expose bilinear resize kernel and improve docs #4061 2024-04-09 22:21:52 +01:00
Lovell Fuller
7950fc0ea3 Docs: changelog entry for #4048 2024-04-02 19:23:11 +01:00
Lovell Fuller
fc93ab3b82 Bump devDeps 2024-04-02 19:22:42 +01:00
ike
0981b24f60 TypeScript: add missing definitions for OverlayOptions (#4048) 2024-04-02 14:42:10 +01:00
Lovell Fuller
02fd565476 CI: bump write-file-action 2024-03-29 13:01:51 +00:00
Lovell Fuller
e55bb93b10 Bump devDeps 2024-03-29 13:01:32 +00:00
Lovell Fuller
55466f122c Release v0.33.3 2024-03-23 11:58:44 +00:00
Lovell Fuller
ede8217ab3 Prerelease v0.33.3-rc.0 2024-03-22 12:41:51 +00:00
Lovell Fuller
2689fb4e65 Bump deps 2024-03-22 11:19:08 +00:00
Lovell Fuller
eaf31a59e5 Docs: changelog and credit for #4036 2024-03-21 18:34:35 +00:00
Lovell Fuller
aa1bbcb5c1 Guard heif bitdepth property for prebuilt binaries 2024-03-21 18:33:57 +00:00
Mert
3c26080c39 Add bitdepth option to heif output (#4036)
Prebuilt binaries support only AVIF with a bitdepth of 8
2024-03-21 14:36:17 +00:00
Lovell Fuller
dc07fd4e9c Upgrade to libvips v8.15.2 2024-03-18 15:55:01 +00:00
Lovell Fuller
7bc74feb11 Ensure clone takes deep copy of options #4029 2024-03-17 09:42:21 +00:00
Lovell Fuller
c5f318ed4d Docs: add changelog and credit for #4028 2024-03-17 09:10:42 +00:00
Aaron Che
8fbb1cd154 Ensure text.wrap property can accept word-char as value (#4028) 2024-03-17 08:15:03 +00:00
Lovell Fuller
88aee8a887 Bump devDeps 2024-03-06 21:00:44 +00:00
Lovell Fuller
0f77b18078 Rename internal property name to better reflect use
This property is used for the processing pipeline colourspace
rather than the input colourspace, so the name was confusing.
2024-03-06 20:49:47 +00:00
Lovell Fuller
3eeaee71c0 Ensure pipelineColourspace handles CMYK profiles #3906 2024-03-06 20:20:04 +00:00
Lovell Fuller
045d54e2e6 Docs: include use of fetch() in Stream-based example 2024-02-28 13:15:47 +00:00
Lovell Fuller
75fedf1b75 Ensure keepIccProfile retains P3 input profiles #4008 2024-02-26 15:23:50 +00:00
Lovell Fuller
debdacb726 Bump deps 2024-02-23 09:11:35 +00:00
Lovell Fuller
1c8ae67ed2 CI: Upgrade OS on Linux ARM64 runners 2024-02-23 09:08:11 +00:00
Lovell Fuller
0eb57698ec Docs: clarify how to achieve 16-bit PNG output 2024-02-14 10:03:18 +00:00
Lovell Fuller
bc95531f2d Docs: clarify skipBlanks default for tile-based output 2024-02-13 22:40:10 +00:00
Lovell Fuller
60f4048d6c CI: Upgrade to Python 3.12 2024-02-11 20:45:16 +00:00
Lovell Fuller
fb70fbb09f Ensure keepIccProfile retains CMYK input profiles #3906 2024-02-11 20:10:18 +00:00
idchlife
fc439bedf1 Docs: add section to help those bundling via Vite 2024-02-08 09:31:28 +00:00
Lovell Fuller
26d0b7147d Bump devDeps 2024-02-03 21:22:35 +00:00
Lovell Fuller
af6aa8a690 CI: Add macOS 14 (ARM64)
Replaces runner previously and very kindly donated by MacStadium
2024-02-01 13:21:43 +00:00
hugo-syn
bd4f1abba2 TypeScript: Fix typo s/introducted/introduced/ (#3962) 2024-01-19 11:07:41 +00:00
hugo-syn
d2656a3679 Tests: fix typo s/implict/implicit (#3961) 2024-01-19 10:30:04 +00:00
Niels de Vos
a3b45ceccc Docs: update link to new JSDoc site (#3950) 2024-01-16 08:20:45 +00:00
Lovell Fuller
0dcc7d50a8 Docs: add note about libvips 8.15.1 upgrade 2024-01-12 11:25:13 +00:00
Lovell Fuller
bcb22af034 Release v0.33.2 2024-01-12 10:58:45 +00:00
57 changed files with 628 additions and 316 deletions

View File

@@ -24,7 +24,7 @@ jobs:
linux-arm64-glibc-node-18:
resource_class: arm.medium
machine:
image: ubuntu-2004:current
image: ubuntu-2204:current
steps:
- checkout
- run: |
@@ -46,7 +46,7 @@ jobs:
linux-arm64-glibc-node-20:
resource_class: arm.medium
machine:
image: ubuntu-2004:current
image: ubuntu-2204:current
steps:
- checkout
- run: |
@@ -69,7 +69,7 @@ jobs:
linux-arm64-musl-node-18:
resource_class: arm.medium
machine:
image: ubuntu-2004:current
image: ubuntu-2204:current
steps:
- checkout
- run: |
@@ -87,7 +87,7 @@ jobs:
linux-arm64-musl-node-20:
resource_class: arm.medium
machine:
image: ubuntu-2004:current
image: ubuntu-2204:current
steps:
- checkout
- run: |

View File

@@ -63,7 +63,7 @@ By way of example, the `background()` method present in v0.20.0 was deprecated i
## Documentation
The public API is documented with [JSDoc](http://usejsdoc.org/) annotated comments.
The public API is documented with [JSDoc](https://jsdoc.app/) annotated comments.
These can be converted to Markdown by running:
```sh

View File

@@ -32,6 +32,22 @@ If you are using another package which depends on a version of `sharp` that is n
<!-- Please provide output of the above command here. -->
### Does this problem relate to file caching?
The default behaviour of libvips is to cache input files, which can lead to `EBUSY` or `EPERM` errors on Windows.
Use [`sharp.cache(false)`](https://sharp.pixelplumbing.com/api-utility#cache) to switch this feature off.
- [ ] Adding `sharp.cache(false)` does not fix this problem.
### Does this problem relate to images appearing to have been rotated by 90 degrees?
Images that contain EXIF Orientation metadata are not auto-oriented. By default, EXIF metadata is removed.
- To auto-orient pixel values use the parameter-less [`rotate()`](https://sharp.pixelplumbing.com/api-operation#rotate) operation.
- To retain EXIF Orientation use [`keepExif()`](https://sharp.pixelplumbing.com/api-output#keepexif).
- [ ] Using `rotate()` or `keepExif()` does not fix this problem.
### What are the steps to reproduce?
<!-- Please enter steps to reproduce here. -->

View File

@@ -47,6 +47,17 @@ jobs:
nodejs_version: "^20.3.0"
nodejs_version_major: 20
platform: darwin-x64
- os: macos-14
nodejs_arch: arm64
nodejs_version: "^18.17.0"
nodejs_version_major: 18
platform: darwin-arm64
prebuild: true
- os: macos-14
nodejs_arch: arm64
nodejs_version: "^20.3.0"
nodejs_version_major: 20
platform: darwin-arm64
- os: windows-2019
nodejs_arch: x86
nodejs_version: "18.18.2" # pinned to avoid 18.19.0 and npm 10
@@ -80,12 +91,12 @@ jobs:
run: apk add build-base git python3 font-noto --update-cache
- name: Dependencies (Python 3.11 - macOS, Windows)
if: contains(matrix.os, 'macos') || contains(matrix.os, 'windows')
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: '3.11'
python-version: "3.12"
- name: Dependencies (Node.js)
if: "!contains(matrix.platform, 'linuxmusl')"
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.nodejs_version }}
architecture: ${{ matrix.nodejs_arch }}
@@ -120,12 +131,14 @@ jobs:
matrix:
include:
- platform: linux-arm
distro: buster
run_on_arch: armv6
nodejs_arch: armv6l
nodejs_hostname: unofficial-builds.nodejs.org
nodejs_version: "18.17.0"
nodejs_version_major: 18
- platform: linux-s390x
distro: bullseye
run_on_arch: s390x
nodejs_arch: s390x
nodejs_hostname: nodejs.org
@@ -136,7 +149,7 @@ jobs:
- uses: uraimo/run-on-arch-action@v2
with:
arch: ${{ matrix.run_on_arch }}
distro: buster
distro: ${{ matrix.distro }}
env: |
prebuild_upload: "${{ startsWith(github.ref, 'refs/tags/') && secrets.GITHUB_TOKEN || '' }}"
run: |
@@ -158,14 +171,14 @@ jobs:
contents: write
name: wasm32 - prebuild
runs-on: ubuntu-22.04
container: "emscripten/emsdk:3.1.51"
container: "emscripten/emsdk:3.1.56"
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Dependencies
run: apt-get update && apt-get install -y pkg-config
- name: Dependencies (Node.js)
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Install
@@ -193,48 +206,3 @@ jobs:
npm_config_nodedir: emscripten
prebuild_upload: ${{ secrets.GITHUB_TOKEN }}
run: cd src && ln -s ../package.json && emmake npx prebuild --platform=emscripten --arch=wasm32 --strip=0
macstadium-runner:
permissions:
contents: write
name: ${{ matrix.platform }} - Node.js ${{ matrix.nodejs_version_major }} ${{ matrix.prebuild && '- prebuild' }}
runs-on: macos-m1
strategy:
fail-fast: false
matrix:
include:
- nodejs_arch: x64
nodejs_version: "^18.17.0"
nodejs_version_major: 18
platform: darwin-x64
- nodejs_arch: arm64
nodejs_version: "^18.17.0"
nodejs_version_major: 18
platform: darwin-arm64
prebuild: true
defaults:
run:
shell: /usr/bin/arch -arch arm64e /bin/bash -l {0}
steps:
- name: Dependencies (Node.js)
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.nodejs_version }}
architecture: ${{ matrix.nodejs_arch }}
- name: Checkout
uses: actions/checkout@v4
- name: Install
run: npm install --build-from-source
- name: Test
run: npm test
- name: Test packaging
run: |
npm run package-from-local-build
npm pkg set "optionalDependencies.@img/sharp-${{ matrix.platform }}=file:./npm/${{ matrix.platform }}"
npm run clean
npm install --ignore-scripts
npm test
- name: Prebuild
if: matrix.prebuild && startsWith(github.ref, 'refs/tags/')
env:
prebuild_upload: ${{ secrets.GITHUB_TOKEN }}
run: cd src && ln -s ../package.json && npx prebuild

View File

@@ -84,7 +84,7 @@ jobs:
steps:
- name: Install Node.js
if: ${{ matrix.runtime == 'node' }}
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install pnpm
@@ -110,7 +110,7 @@ jobs:
script: |
core.setOutput('semver', context.ref.replace('refs/tags/v',''))
- name: Create package.json
uses: DamianReeves/write-file-action@v1.2
uses: DamianReeves/write-file-action@v1.3
with:
path: package.json
contents: |
@@ -121,7 +121,7 @@ jobs:
"type": "module"
}
- name: Create release.mjs
uses: DamianReeves/write-file-action@v1.2
uses: DamianReeves/write-file-action@v1.3
with:
path: release.mjs
contents: |

View File

@@ -66,8 +66,6 @@ The input image will be converted to the provided colourspace at the start of th
All operations will use this colourspace before converting to the output colourspace,
as defined by [toColourspace](#tocolourspace).
This feature is experimental and has not yet been fully-tested with all operations.
**Throws**:

View File

@@ -66,7 +66,7 @@ where the overall height is the `pageHeight` multiplied by the number of `pages`
| [options.text.dpi] | <code>number</code> | <code>72</code> | the resolution (size) at which to render the text. Does not take effect if `height` is specified. |
| [options.text.rgba] | <code>boolean</code> | <code>false</code> | set this to true to enable RGBA output. This is useful for colour emoji rendering, or support for pango markup features like `<span foreground="red">Red!</span>`. |
| [options.text.spacing] | <code>number</code> | <code>0</code> | text line height in points. Will use the font line height if none is specified. |
| [options.text.wrap] | <code>string</code> | <code>&quot;&#x27;word&#x27;&quot;</code> | word wrapping style when width is provided, one of: 'word', 'char', 'charWord' (prefer char, fallback to word) or 'none'. |
| [options.text.wrap] | <code>string</code> | <code>&quot;&#x27;word&#x27;&quot;</code> | word wrapping style when width is provided, one of: 'word', 'char', 'word-char' (prefer word, fallback to char) or 'none'. |
**Example**
```js
@@ -79,14 +79,16 @@ sharp('input.jpg')
```
**Example**
```js
// Read image data from readableStream,
// Read image data from remote URL,
// resize to 300 pixels wide,
// emit an 'info' event with calculated dimensions
// and finally write image data to writableStream
var transformer = sharp()
const { body } = fetch('https://...');
const readableStream = Readable.fromWeb(body);
const transformer = sharp()
.resize(300)
.on('info', function(info) {
console.log('Image height is ' + info.height);
.on('info', ({ height }) => {
console.log(`Image height is ${height}`);
});
readableStream.pipe(transformer).pipe(writableStream);
```

View File

@@ -367,10 +367,14 @@ const data = await sharp(input)
Use these PNG options for output image.
By default, PNG output is full colour at 8 or 16 bits per pixel.
By default, PNG output is full colour at 8 bits per pixel.
Indexed PNG input at 1, 2 or 4 bits per pixel is converted to 8 bits per pixel.
Set `palette` to `true` for slower, indexed PNG output.
For 16 bits per pixel output, convert to `rgb16` via
[toColourspace](/api-colour#tocolourspace).
**Throws**:
@@ -405,6 +409,14 @@ const data = await sharp(input)
.png({ palette: true })
.toBuffer();
```
**Example**
```js
// Output 16 bits per pixel RGB(A)
const data = await sharp(input)
.toColourspace('rgb16')
.png()
.toBuffer();
```
## webp
@@ -605,6 +617,7 @@ sharp('input.svg')
Use these AVIF options for output image.
AVIF image sequences are not supported.
Prebuilt binaries support a bitdepth of 8 only.
**Throws**:
@@ -620,6 +633,7 @@ AVIF image sequences are not supported.
| [options.lossless] | <code>boolean</code> | <code>false</code> | use lossless compression |
| [options.effort] | <code>number</code> | <code>4</code> | CPU effort, between 0 (fastest) and 9 (slowest) |
| [options.chromaSubsampling] | <code>string</code> | <code>&quot;&#x27;4:4:4&#x27;&quot;</code> | set to '4:2:0' to use chroma subsampling |
| [options.bitdepth] | <code>number</code> | <code>8</code> | set bitdepth to 8, 10 or 12 bit |
**Example**
```js
@@ -658,6 +672,7 @@ globally-installed libvips compiled with support for libheif, libde265 and x265.
| [options.lossless] | <code>boolean</code> | <code>false</code> | use lossless compression |
| [options.effort] | <code>number</code> | <code>4</code> | CPU effort, between 0 (fastest) and 9 (slowest) |
| [options.chromaSubsampling] | <code>string</code> | <code>&quot;&#x27;4:4:4&#x27;&quot;</code> | set to '4:2:0' to use chroma subsampling |
| [options.bitdepth] | <code>number</code> | <code>8</code> | set bitdepth to 8, 10 or 12 bit |
**Example**
```js
@@ -763,7 +778,7 @@ The prebuilt binaries do not include this - see
| [options.angle] | <code>number</code> | <code>0</code> | tile angle of rotation, must be a multiple of 90. |
| [options.background] | <code>string</code> \| <code>Object</code> | <code>&quot;{r: 255, g: 255, b: 255, alpha: 1}&quot;</code> | background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to white without transparency. |
| [options.depth] | <code>string</code> | | how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout. |
| [options.skipBlanks] | <code>number</code> | <code>-1</code> | threshold to skip tile generation, a value 0 - 255 for 8-bit images or 0 - 65535 for 16-bit images |
| [options.skipBlanks] | <code>number</code> | <code>-1</code> | Threshold to skip tile generation. Range is 0-255 for 8-bit images, 0-65535 for 16-bit images. Default is 5 for `google` layout, -1 (no skip) otherwise. |
| [options.container] | <code>string</code> | <code>&quot;&#x27;fs&#x27;&quot;</code> | tile container, with value `fs` (filesystem) or `zip` (compressed file). |
| [options.layout] | <code>string</code> | <code>&quot;&#x27;dz&#x27;&quot;</code> | filesystem layout, possible values are `dz`, `iiif`, `iiif3`, `zoomify` or `google`. |
| [options.centre] | <code>boolean</code> | <code>false</code> | centre image in tile. |

View File

@@ -21,18 +21,22 @@ When using a **fit** of `cover` or `contain`, the default **position** is `centr
Some of these values are based on the [object-position](https://developer.mozilla.org/en-US/docs/Web/CSS/object-position) CSS property.
The experimental strategy-based approach resizes so one dimension is at its target length
The strategy-based approach initially resizes so one dimension is at its target length
then repeatedly ranks edge regions, discarding the edge with the lowest score based on the selected strategy.
- `entropy`: focus on the region with the highest [Shannon entropy](https://en.wikipedia.org/wiki/Entropy_%28information_theory%29).
- `attention`: focus on the region with the highest luminance frequency, colour saturation and presence of skin tones.
Possible interpolation kernels are:
Possible downsizing kernels are:
- `nearest`: Use [nearest neighbour interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation).
- `linear`: Use a [triangle filter](https://en.wikipedia.org/wiki/Triangular_function).
- `cubic`: Use a [Catmull-Rom spline](https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline).
- `mitchell`: Use a [Mitchell-Netravali spline](https://www.cs.utexas.edu/~fussell/courses/cs384g-fall2013/lectures/mitchell/Mitchell.pdf).
- `lanczos2`: Use a [Lanczos kernel](https://en.wikipedia.org/wiki/Lanczos_resampling#Lanczos_kernel) with `a=2`.
- `lanczos3`: Use a Lanczos kernel with `a=3` (the default).
When upsampling, these kernels map to `nearest`, `linear` and `cubic` interpolators.
Downsampling kernels without a matching upsampling interpolator map to `cubic`.
Only one resize can occur per pipeline.
Previous calls to `resize` in the same pipeline will be ignored.
@@ -52,7 +56,7 @@ Previous calls to `resize` in the same pipeline will be ignored.
| [options.fit] | <code>String</code> | <code>&#x27;cover&#x27;</code> | How the image should be resized/cropped to fit the target dimension(s), one of `cover`, `contain`, `fill`, `inside` or `outside`. |
| [options.position] | <code>String</code> | <code>&#x27;centre&#x27;</code> | A position, gravity or strategy to use when `fit` is `cover` or `contain`. |
| [options.background] | <code>String</code> \| <code>Object</code> | <code>{r: 0, g: 0, b: 0, alpha: 1}</code> | background colour when `fit` is `contain`, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black without transparency. |
| [options.kernel] | <code>String</code> | <code>&#x27;lanczos3&#x27;</code> | The kernel to use for image reduction. Use the `fastShrinkOnLoad` option to control kernel vs shrink-on-load. |
| [options.kernel] | <code>String</code> | <code>&#x27;lanczos3&#x27;</code> | The kernel to use for image reduction and the inferred interpolator to use for upsampling. Use the `fastShrinkOnLoad` option to control kernel vs shrink-on-load. |
| [options.withoutEnlargement] | <code>Boolean</code> | <code>false</code> | Do not scale up if the width *or* height are already less than the target dimensions, equivalent to GraphicsMagick's `>` geometry option. This may result in output dimensions smaller than the target dimensions. |
| [options.withoutReduction] | <code>Boolean</code> | <code>false</code> | Do not scale down if the width *or* height are already greater than the target dimensions, equivalent to GraphicsMagick's `<` geometry option. This may still result in a crop to reach the target dimensions. |
| [options.fastShrinkOnLoad] | <code>Boolean</code> | <code>true</code> | Take greater advantage of the JPEG and WebP shrink-on-load feature, which can lead to a slight moiré pattern or round-down of an auto-scaled dimension. |

View File

@@ -2,9 +2,60 @@
## v0.33 - *gauge*
Requires libvips v8.15.1
Requires libvips v8.15.2
### v0.33.2 - TBD
### v0.33.4 - 16th May 2024
* Remove experimental status from `pipelineColourspace`.
* Reduce default concurrency when musl thread over-subscription detected.
* TypeScript: add missing definitions for `OverlayOptions`.
[#4048](https://github.com/lovell/sharp/pull/4048)
[@ike-gg](https://github.com/ike-gg)
* Install: add advanced option to force use of a globally-installed libvips.
[#4060](https://github.com/lovell/sharp/issues/4060)
* Expose `bilinear` resizing kernel (and interpolator).
[#4061](https://github.com/lovell/sharp/issues/4061)
* Ensure `extend` operation stays sequential for multi-page TIFF (regression in 0.32.0).
[#4069](https://github.com/lovell/sharp/issues/4069)
* Tighten validation of constructor `text` integer properties.
[#4071](https://github.com/lovell/sharp/issues/4071)
* Simplify internal StaySequential logic.
[#4074](https://github.com/lovell/sharp/pull/4074)
[@kleisauke](https://github.com/kleisauke)
* Ensure negate operation occurs after profile conversion.
[#4096](https://github.com/lovell/sharp/pull/4096)
[@adriaanmeuris](https://github.com/adriaanmeuris)
### v0.33.3 - 23rd March 2024
* Upgrade to libvips v8.15.2 for upstream bug fixes.
* Ensure `keepIccProfile` retains P3 and CMYK input profiles.
[#3906](https://github.com/lovell/sharp/issues/3906)
[#4008](https://github.com/lovell/sharp/issues/4008)
* Ensure `text.wrap` property can accept `word-char` as value.
[#4028](https://github.com/lovell/sharp/pull/4028)
[@yolopunk](https://github.com/yolopunk)
* Ensure `clone` takes a deep copy of existing options.
[#4029](https://github.com/lovell/sharp/issues/4029)
* Add `bitdepth` option to `heif` output (prebuilt binaries support 8-bit only).
[#4036](https://github.com/lovell/sharp/pull/4036)
[@mertalev](https://github.com/mertalev)
### v0.33.2 - 12th January 2024
* Upgrade to libvips v8.15.1 for upstream bug fixes.
* TypeScript: add definition for `keepMetadata`.
[#3914](https://github.com/lovell/sharp/pull/3914)

View File

@@ -284,3 +284,12 @@ GitHub: https://github.com/RReverser
Name: Tamás András Horváth
GitHub: https://github.com/icetee
Name: Aaron Che
GitHub: https://github.com/yolopunk
Name: Mert Alev
GitHub: https://github.com/mertalev
Name: Adriaan Meuris
GitHub: https://github.com/adriaanmeuris

View File

@@ -7,15 +7,13 @@
<meta name="description" content="Resize large images in common formats to smaller, web-friendly JPEG, PNG, WebP, GIF and AVIF images of varying dimensions">
<meta property="og:title" content="sharp - High performance Node.js image processing">
<meta property="og:image" content="https://cdn.jsdelivr.net/gh/lovell/sharp@main/docs/image/sharp-logo-600.png">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; object-src 'none'; style-src 'unsafe-inline';
img-src 'unsafe-inline' data: https://cdn.jsdelivr.net/gh/lovell/ https://www.google-analytics.com;
connect-src 'self' https://www.google-analytics.com;
script-src 'self' 'unsafe-inline' 'unsafe-eval'
https://www.google-analytics.com/analytics.js;">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; connect-src 'self'; object-src 'none';
style-src 'unsafe-inline';
img-src 'unsafe-inline' data: https://cdn.jsdelivr.net/gh/lovell/;
script-src 'self' 'unsafe-inline' 'unsafe-eval' https://static.cloudflareinsights.com/beacon.min.js;">
<link rel="icon" type="image/svg+xml" href="https://cdn.jsdelivr.net/gh/lovell/sharp@main/docs/image/sharp-logo.svg">
<link rel="icon" type="image/png" sizes="32x32" href="https://cdn.jsdelivr.net/gh/lovell/sharp@main/docs/image/sharp-logo-32.png">
<link rel="author" href="/humans.txt" type="text/plain">
<link rel="dns-prefetch" href="https://www.google-analytics.com">
<script type="application/ld+json">
{
"@context": "https://schema.org",
@@ -67,7 +65,6 @@
<div id="docute"></div>
<script src="docute.min.js"></script>
<script>
/* docute-google-analytics@1/dist/index.min.js */ !function(e,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define(n):(e=e||self).docuteGoogleAnalytics=n()}(this,function(){"use strict";function e(e){var n;window.ga||((n=document.createElement("script")).async=!0,n.src="https://www.google-analytics.com/analytics.js",document.body.appendChild(n),window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)},ga.l=Number(new Date),ga("create",e,"auto"))}function n(n,t){e(t),ga("set","page",n),ga("send","pageview")}return function(e){return{name:"@google-analytics",extend:function(t){!function(e,t){"function"==typeof e?e(function(e){n(e,t)}):e.afterEach(function(e){n(e.fullPath,t)})}(t.router,e)}}}});
const docuteApiTitlePlugin = {
name: 'apiTitle',
extend(api) {
@@ -126,7 +123,6 @@
detectSystemDarkTheme: true,
footer: '<a href="https://pixelplumbing.com/" target="_blank">pixelplumbing.com<a>',
plugins: [
docuteGoogleAnalytics('UA-13034748-12'),
docuteApiTitlePlugin,
docuteApiSearchPlugin
],

View File

@@ -45,7 +45,7 @@ Ready-compiled sharp and libvips binaries are provided for use on the most commo
* macOS ARM64
* Linux ARM (glibc >= 2.28)
* Linux ARM64 (glibc >= 2.26, musl >= 1.2.2)
* Linux s390x (glibc >= 2.28)
* Linux s390x (glibc >= 2.31)
* Linux x64 (glibc >= 2.26, musl >= 1.2.2, CPU with SSE4.2)
* Windows x64
* Windows x86
@@ -67,15 +67,17 @@ within the same installation tree and/or using the same lockfile.
Provides limited support via `--os`, `--cpu` and `--libc` flags.
Example to support both Intel and ARM CPUs on macOS:
To support macOS with Intel x64 and ARM64 CPUs:
```sh
npm install --cpu=x64 --os=darwin sharp
npm install --cpu=arm64 --os=darwin sharp
```
Example to support both glibc and musl-based Linux:
When the cross-target is Linux, the C standard library must be specified.
To support glibc (e.g. Debian) and musl (e.g. Alpine) Linux with Intel x64 CPUs:
```sh
npm install --cpu=x64 --os=linux sharp
npm install --cpu=x64 --os=linux --libc=glibc sharp
npm install --cpu=x64 --os=linux --libc=musl sharp
```
@@ -103,9 +105,14 @@ and on macOS when running Node.js under Rosetta.
This module will be compiled from source at `npm install` time when:
* a globally-installed libvips is detected (set the `SHARP_IGNORE_GLOBAL_LIBVIPS` environment variable to skip this), or
* a globally-installed libvips is detected, or
* when the `npm install --build-from-source` flag is used.
The logic to detect a globally-installed libvips can be skipped by setting the
`SHARP_IGNORE_GLOBAL_LIBVIPS` (never try to use it) or
`SHARP_FORCE_GLOBAL_LIBVIPS` (always try to use it, even when missing or outdated)
environment variables.
Building from source requires:
* C++11 compiler
@@ -249,6 +256,26 @@ option.
}
```
### vite
Ensure `sharp` is excluded from bundling via the
[build.rollupOptions](https://vitejs.dev/config/build-options.html)
configuration.
```js
import { defineConfig } from 'vite';
export default defineConfig({
build: {
rollupOptions: {
external: [
"sharp"
]
}
}
});
```
## TypeScript
TypeScript definitions are published as part of

File diff suppressed because one or more lines are too long

View File

@@ -30,6 +30,7 @@ module.exports = [
'current',
'date',
'default',
'deprecated',
'does',
'each',
'either',
@@ -46,6 +47,7 @@ module.exports = [
'given',
'has',
'have',
'helps',
'how',
'image',
'implies',
@@ -58,6 +60,7 @@ module.exports = [
'lots',
'make',
'may',
'meaning',
'more',
'most',
'much',
@@ -79,6 +82,7 @@ module.exports = [
'pre',
'previously',
'produce',
'proper',
'provide',
'provided',
'ready',
@@ -116,6 +120,7 @@ module.exports = [
'unless',
'unmaintained',
'unsuitable',
'unsupported',
'until',
'use',
'used',

View File

@@ -10,8 +10,8 @@ try {
log(msg);
log('Attempting to build from source via node-gyp');
try {
require('node-addon-api');
log('Found node-addon-api');
const addonApi = require('node-addon-api');
log(`Found node-addon-api ${addonApi.version || ''}`);
} catch (err) {
log('Please add node-addon-api to your dependencies');
return;

View File

@@ -71,8 +71,6 @@ function grayscale (grayscale) {
* All operations will use this colourspace before converting to the output colourspace,
* as defined by {@link #tocolourspace|toColourspace}.
*
* This feature is experimental and has not yet been fully-tested with all operations.
*
* @since 0.29.0
*
* @example
@@ -90,7 +88,7 @@ function pipelineColourspace (colourspace) {
if (!is.string(colourspace)) {
throw is.invalidParameterError('colourspace', 'string', colourspace);
}
this.options.colourspaceInput = colourspace;
this.options.colourspacePipeline = colourspace;
return this;
}

View File

@@ -40,14 +40,16 @@ const debuglog = util.debuglog('sharp');
* });
*
* @example
* // Read image data from readableStream,
* // Read image data from remote URL,
* // resize to 300 pixels wide,
* // emit an 'info' event with calculated dimensions
* // and finally write image data to writableStream
* var transformer = sharp()
* const { body } = fetch('https://...');
* const readableStream = Readable.fromWeb(body);
* const transformer = sharp()
* .resize(300)
* .on('info', function(info) {
* console.log('Image height is ' + info.height);
* .on('info', ({ height }) => {
* console.log(`Image height is ${height}`);
* });
* readableStream.pipe(transformer).pipe(writableStream);
*
@@ -164,7 +166,7 @@ const debuglog = util.debuglog('sharp');
* @param {number} [options.text.dpi=72] - the resolution (size) at which to render the text. Does not take effect if `height` is specified.
* @param {boolean} [options.text.rgba=false] - set this to true to enable RGBA output. This is useful for colour emoji rendering, or support for pango markup features like `<span foreground="red">Red!</span>`.
* @param {number} [options.text.spacing=0] - text line height in points. Will use the font line height if none is specified.
* @param {string} [options.text.wrap='word'] - word wrapping style when width is provided, one of: 'word', 'char', 'charWord' (prefer char, fallback to word) or 'none'.
* @param {string} [options.text.wrap='word'] - word wrapping style when width is provided, one of: 'word', 'char', 'word-char' (prefer word, fallback to char) or 'none'.
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
@@ -255,7 +257,7 @@ const Sharp = function (input, options) {
removeAlpha: false,
ensureAlpha: -1,
colourspace: 'srgb',
colourspaceInput: 'last',
colourspacePipeline: 'last',
composite: [],
// output
fileOut: '',
@@ -323,6 +325,7 @@ const Sharp = function (input, options) {
heifCompression: 'av1',
heifEffort: 4,
heifChromaSubsampling: '4:4:4',
heifBitdepth: 8,
jxlDistance: 1,
jxlDecodingTier: 0,
jxlEffort: 7,
@@ -423,13 +426,16 @@ Object.setPrototypeOf(Sharp, stream.Duplex);
function clone () {
// Clone existing options
const clone = this.constructor.call();
clone.options = Object.assign({}, this.options);
const { debuglog, queueListener, ...options } = this.options;
clone.options = structuredClone(options);
clone.options.debuglog = debuglog;
clone.options.queueListener = queueListener;
// Pass 'finish' event to clone for Stream-based input
if (this._isStreamInput()) {
this.on('finish', () => {
// Clone inherits input data
this._flattenBufferIn();
clone.options.bufferIn = this.options.bufferIn;
clone.options.input.buffer = this.options.input.buffer;
clone.emit('finish');
});
}

16
lib/index.d.ts vendored
View File

@@ -1017,7 +1017,7 @@ declare namespace sharp {
rgba?: boolean;
/** Text line height in points. Will use the font line height if none is specified. (optional, default `0`) */
spacing?: number;
/** Word wrapping style when width is provided, one of: 'word', 'char', 'charWord' (prefer char, fallback to word) or 'none' */
/** Word wrapping style when width is provided, one of: 'word', 'char', 'word-char' (prefer word, fallback to char) or 'none' */
wrap?: TextWrap;
}
@@ -1244,6 +1244,8 @@ declare namespace sharp {
effort?: number | undefined;
/** set to '4:2:0' to use chroma subsampling, requires libvips v8.11.0 (optional, default '4:4:4') */
chromaSubsampling?: string | undefined;
/** Set bitdepth to 8, 10 or 12 bit (optional, default 8) */
bitdepth?: 8 | 10 | 12 | undefined;
}
interface HeifOptions extends OutputOptions {
@@ -1257,6 +1259,8 @@ declare namespace sharp {
effort?: number | undefined;
/** set to '4:2:0' to use chroma subsampling (optional, default '4:4:4') */
chromaSubsampling?: string | undefined;
/** Set bitdepth to 8, 10 or 12 bit (optional, default 8) */
bitdepth?: 8 | 10 | 12 | undefined;
}
interface GifOptions extends OutputOptions, AnimationOptions {
@@ -1475,6 +1479,14 @@ declare namespace sharp {
tile?: boolean | undefined;
/** Set to true to avoid premultipling the image below. Equivalent to the --premultiplied vips option. */
premultiplied?: boolean | undefined;
/** number representing the DPI for vector overlay image. (optional, default 72)*/
density?: number | undefined;
/** Set to true to read all frames/pages of an animated image. (optional, default false) */
animated?: boolean | undefined;
/** see sharp() constructor, (optional, default 'warning') */
failOn?: FailOnOptions | undefined;
/** see sharp() constructor, (optional, default 268402689) */
limitInputPixels?: number | boolean | undefined;
}
interface TileOptions {
@@ -1613,7 +1625,7 @@ declare namespace sharp {
type TextAlign = 'left' | 'centre' | 'center' | 'right';
type TextWrap = 'word' | 'char' | 'charWord' | 'none';
type TextWrap = 'word' | 'char' | 'word-char' | 'none';
type TileContainer = 'fs' | 'zip';

View File

@@ -296,17 +296,17 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
}
}
if (is.defined(inputOptions.text.width)) {
if (is.number(inputOptions.text.width)) {
if (is.integer(inputOptions.text.width) && inputOptions.text.width > 0) {
inputDescriptor.textWidth = inputOptions.text.width;
} else {
throw is.invalidParameterError('text.textWidth', 'number', inputOptions.text.width);
throw is.invalidParameterError('text.width', 'positive integer', inputOptions.text.width);
}
}
if (is.defined(inputOptions.text.height)) {
if (is.number(inputOptions.text.height)) {
if (is.integer(inputOptions.text.height) && inputOptions.text.height > 0) {
inputDescriptor.textHeight = inputOptions.text.height;
} else {
throw is.invalidParameterError('text.height', 'number', inputOptions.text.height);
throw is.invalidParameterError('text.height', 'positive integer', inputOptions.text.height);
}
}
if (is.defined(inputOptions.text.align)) {
@@ -324,10 +324,10 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
}
}
if (is.defined(inputOptions.text.dpi)) {
if (is.number(inputOptions.text.dpi) && is.inRange(inputOptions.text.dpi, 1, 100000)) {
if (is.integer(inputOptions.text.dpi) && is.inRange(inputOptions.text.dpi, 1, 1000000)) {
inputDescriptor.textDpi = inputOptions.text.dpi;
} else {
throw is.invalidParameterError('text.dpi', 'number between 1 and 100000', inputOptions.text.dpi);
throw is.invalidParameterError('text.dpi', 'integer between 1 and 1000000', inputOptions.text.dpi);
}
}
if (is.defined(inputOptions.text.rgba)) {
@@ -338,17 +338,17 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
}
}
if (is.defined(inputOptions.text.spacing)) {
if (is.number(inputOptions.text.spacing)) {
if (is.integer(inputOptions.text.spacing) && is.inRange(inputOptions.text.spacing, -1000000, 1000000)) {
inputDescriptor.textSpacing = inputOptions.text.spacing;
} else {
throw is.invalidParameterError('text.spacing', 'number', inputOptions.text.spacing);
throw is.invalidParameterError('text.spacing', 'integer between -1000000 and 1000000', inputOptions.text.spacing);
}
}
if (is.defined(inputOptions.text.wrap)) {
if (is.string(inputOptions.text.wrap) && is.inArray(inputOptions.text.wrap, ['word', 'char', 'wordChar', 'none'])) {
if (is.string(inputOptions.text.wrap) && is.inArray(inputOptions.text.wrap, ['word', 'char', 'word-char', 'none'])) {
inputDescriptor.textWrap = inputOptions.text.wrap;
} else {
throw is.invalidParameterError('text.wrap', 'one of: word, char, wordChar, none', inputOptions.text.wrap);
throw is.invalidParameterError('text.wrap', 'one of: word, char, word-char, none', inputOptions.text.wrap);
}
}
delete inputDescriptor.buffer;

View File

@@ -162,15 +162,21 @@ const pkgConfigPath = () => {
}
};
const skipSearch = (status, reason) => {
log(`Detected ${reason}, skipping search for globally-installed libvips`);
return status;
};
const useGlobalLibvips = () => {
if (Boolean(process.env.SHARP_IGNORE_GLOBAL_LIBVIPS) === true) {
log('Detected SHARP_IGNORE_GLOBAL_LIBVIPS, skipping search for globally-installed libvips');
return false;
return skipSearch(false, 'SHARP_IGNORE_GLOBAL_LIBVIPS');
}
if (Boolean(process.env.SHARP_FORCE_GLOBAL_LIBVIPS) === true) {
return skipSearch(true, 'SHARP_FORCE_GLOBAL_LIBVIPS');
}
/* istanbul ignore next */
if (isRosetta()) {
log('Detected Rosetta, skipping search for globally-installed libvips');
return false;
return skipSearch(false, 'Rosetta');
}
const globalVipsVersion = globalLibvipsVersion();
return !!globalVipsVersion && /* istanbul ignore next */

View File

@@ -504,10 +504,14 @@ function jpeg (options) {
/**
* Use these PNG options for output image.
*
* By default, PNG output is full colour at 8 or 16 bits per pixel.
* By default, PNG output is full colour at 8 bits per pixel.
*
* Indexed PNG input at 1, 2 or 4 bits per pixel is converted to 8 bits per pixel.
* Set `palette` to `true` for slower, indexed PNG output.
*
* For 16 bits per pixel output, convert to `rgb16` via
* {@link /api-colour#tocolourspace|toColourspace}.
*
* @example
* // Convert any input to full colour PNG output
* const data = await sharp(input)
@@ -520,6 +524,13 @@ function jpeg (options) {
* .png({ palette: true })
* .toBuffer();
*
* @example
* // Output 16 bits per pixel RGB(A)
* const data = await sharp(input)
* .toColourspace('rgb16')
* .png()
* .toBuffer();
*
* @param {Object} [options]
* @param {boolean} [options.progressive=false] - use progressive (interlace) scan
* @param {number} [options.compressionLevel=6] - zlib compression level, 0 (fastest, largest) to 9 (slowest, smallest)
@@ -1000,6 +1011,7 @@ function tiff (options) {
* Use these AVIF options for output image.
*
* AVIF image sequences are not supported.
* Prebuilt binaries support a bitdepth of 8 only.
*
* @example
* const data = await sharp(input)
@@ -1018,6 +1030,7 @@ function tiff (options) {
* @param {boolean} [options.lossless=false] - use lossless compression
* @param {number} [options.effort=4] - CPU effort, between 0 (fastest) and 9 (slowest)
* @param {string} [options.chromaSubsampling='4:4:4'] - set to '4:2:0' to use chroma subsampling
* @param {number} [options.bitdepth=8] - set bitdepth to 8, 10 or 12 bit
* @returns {Sharp}
* @throws {Error} Invalid options
*/
@@ -1044,6 +1057,7 @@ function avif (options) {
* @param {boolean} [options.lossless=false] - use lossless compression
* @param {number} [options.effort=4] - CPU effort, between 0 (fastest) and 9 (slowest)
* @param {string} [options.chromaSubsampling='4:4:4'] - set to '4:2:0' to use chroma subsampling
* @param {number} [options.bitdepth=8] - set bitdepth to 8, 10 or 12 bit
* @returns {Sharp}
* @throws {Error} Invalid options
*/
@@ -1082,6 +1096,16 @@ function heif (options) {
throw is.invalidParameterError('chromaSubsampling', 'one of: 4:2:0, 4:4:4', options.chromaSubsampling);
}
}
if (is.defined(options.bitdepth)) {
if (is.integer(options.bitdepth) && is.inArray(options.bitdepth, [8, 10, 12])) {
if (options.bitdepth !== 8 && this.constructor.versions.heif) {
throw is.invalidParameterError('bitdepth when using prebuilt binaries', 8, options.bitdepth);
}
this.options.heifBitdepth = options.bitdepth;
} else {
throw is.invalidParameterError('bitdepth', '8, 10 or 12', options.bitdepth);
}
}
} else {
throw is.invalidParameterError('options', 'Object', options);
}
@@ -1233,7 +1257,7 @@ function raw (options) {
* @param {number} [options.angle=0] tile angle of rotation, must be a multiple of 90.
* @param {string|Object} [options.background={r: 255, g: 255, b: 255, alpha: 1}] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to white without transparency.
* @param {string} [options.depth] how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout.
* @param {number} [options.skipBlanks=-1] threshold to skip tile generation, a value 0 - 255 for 8-bit images or 0 - 65535 for 16-bit images
* @param {number} [options.skipBlanks=-1] Threshold to skip tile generation. Range is 0-255 for 8-bit images, 0-65535 for 16-bit images. Default is 5 for `google` layout, -1 (no skip) otherwise.
* @param {string} [options.container='fs'] tile container, with value `fs` (filesystem) or `zip` (compressed file).
* @param {string} [options.layout='dz'] filesystem layout, possible values are `dz`, `iiif`, `iiif3`, `zoomify` or `google`.
* @param {boolean} [options.centre=false] centre image in tile.

View File

@@ -68,6 +68,7 @@ const strategy = {
*/
const kernel = {
nearest: 'nearest',
linear: 'linear',
cubic: 'cubic',
mitchell: 'mitchell',
lanczos2: 'lanczos2',
@@ -135,18 +136,22 @@ function isResizeExpected (options) {
*
* Some of these values are based on the [object-position](https://developer.mozilla.org/en-US/docs/Web/CSS/object-position) CSS property.
*
* The experimental strategy-based approach resizes so one dimension is at its target length
* The strategy-based approach initially resizes so one dimension is at its target length
* then repeatedly ranks edge regions, discarding the edge with the lowest score based on the selected strategy.
* - `entropy`: focus on the region with the highest [Shannon entropy](https://en.wikipedia.org/wiki/Entropy_%28information_theory%29).
* - `attention`: focus on the region with the highest luminance frequency, colour saturation and presence of skin tones.
*
* Possible interpolation kernels are:
* Possible downsizing kernels are:
* - `nearest`: Use [nearest neighbour interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation).
* - `linear`: Use a [triangle filter](https://en.wikipedia.org/wiki/Triangular_function).
* - `cubic`: Use a [Catmull-Rom spline](https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline).
* - `mitchell`: Use a [Mitchell-Netravali spline](https://www.cs.utexas.edu/~fussell/courses/cs384g-fall2013/lectures/mitchell/Mitchell.pdf).
* - `lanczos2`: Use a [Lanczos kernel](https://en.wikipedia.org/wiki/Lanczos_resampling#Lanczos_kernel) with `a=2`.
* - `lanczos3`: Use a Lanczos kernel with `a=3` (the default).
*
* When upsampling, these kernels map to `nearest`, `linear` and `cubic` interpolators.
* Downsampling kernels without a matching upsampling interpolator map to `cubic`.
*
* Only one resize can occur per pipeline.
* Previous calls to `resize` in the same pipeline will be ignored.
*
@@ -239,7 +244,7 @@ function isResizeExpected (options) {
* @param {String} [options.fit='cover'] - How the image should be resized/cropped to fit the target dimension(s), one of `cover`, `contain`, `fill`, `inside` or `outside`.
* @param {String} [options.position='centre'] - A position, gravity or strategy to use when `fit` is `cover` or `contain`.
* @param {String|Object} [options.background={r: 0, g: 0, b: 0, alpha: 1}] - background colour when `fit` is `contain`, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black without transparency.
* @param {String} [options.kernel='lanczos3'] - The kernel to use for image reduction. Use the `fastShrinkOnLoad` option to control kernel vs shrink-on-load.
* @param {String} [options.kernel='lanczos3'] - The kernel to use for image reduction and the inferred interpolator to use for upsampling. Use the `fastShrinkOnLoad` option to control kernel vs shrink-on-load.
* @param {Boolean} [options.withoutEnlargement=false] - Do not scale up if the width *or* height are already less than the target dimensions, equivalent to GraphicsMagick's `>` geometry option. This may result in output dimensions smaller than the target dimensions.
* @param {Boolean} [options.withoutReduction=false] - Do not scale down if the width *or* height are already greater than the target dimensions, equivalent to GraphicsMagick's `<` geometry option. This may still result in a crop to reach the target dimensions.
* @param {Boolean} [options.fastShrinkOnLoad=true] - Take greater advantage of the JPEG and WebP shrink-on-load feature, which can lead to a slight moiré pattern or round-down of an auto-scaled dimension.

View File

@@ -153,6 +153,9 @@ function concurrency (concurrency) {
if (detectLibc.familySync() === detectLibc.GLIBC && !sharp._isUsingJemalloc()) {
// Reduce default concurrency to 1 when using glibc memory allocator
sharp.concurrency(1);
} else if (detectLibc.familySync() === detectLibc.MUSL && sharp.concurrency() === 1024) {
// Reduce default concurrency when musl thread over-subscription detected
sharp.concurrency(require('node:os').availableParallelism());
}
/**

View File

@@ -1,6 +1,6 @@
{
"name": "@img/sharp-darwin-arm64",
"version": "0.33.2-rc.1",
"version": "0.33.4",
"description": "Prebuilt sharp for use with macOS 64-bit ARM",
"author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://sharp.pixelplumbing.com",
@@ -15,7 +15,7 @@
},
"preferUnplugged": true,
"optionalDependencies": {
"@img/sharp-libvips-darwin-arm64": "1.0.1"
"@img/sharp-libvips-darwin-arm64": "1.0.2"
},
"files": [
"lib"

View File

@@ -1,6 +1,6 @@
{
"name": "@img/sharp-darwin-x64",
"version": "0.33.2-rc.1",
"version": "0.33.4",
"description": "Prebuilt sharp for use with macOS x64",
"author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://sharp.pixelplumbing.com",
@@ -15,7 +15,7 @@
},
"preferUnplugged": true,
"optionalDependencies": {
"@img/sharp-libvips-darwin-x64": "1.0.1"
"@img/sharp-libvips-darwin-x64": "1.0.2"
},
"files": [
"lib"

View File

@@ -1,6 +1,6 @@
{
"name": "@img/sharp-linux-arm",
"version": "0.33.2-rc.1",
"version": "0.33.4",
"description": "Prebuilt sharp for use with Linux (glibc) ARM (32-bit)",
"author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://sharp.pixelplumbing.com",
@@ -15,7 +15,7 @@
},
"preferUnplugged": true,
"optionalDependencies": {
"@img/sharp-libvips-linux-arm": "1.0.1"
"@img/sharp-libvips-linux-arm": "1.0.2"
},
"files": [
"lib"

View File

@@ -1,6 +1,6 @@
{
"name": "@img/sharp-linux-arm64",
"version": "0.33.2-rc.1",
"version": "0.33.4",
"description": "Prebuilt sharp for use with Linux (glibc) 64-bit ARM",
"author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://sharp.pixelplumbing.com",
@@ -15,7 +15,7 @@
},
"preferUnplugged": true,
"optionalDependencies": {
"@img/sharp-libvips-linux-arm64": "1.0.1"
"@img/sharp-libvips-linux-arm64": "1.0.2"
},
"files": [
"lib"

View File

@@ -1,6 +1,6 @@
{
"name": "@img/sharp-linux-s390x",
"version": "0.33.2-rc.1",
"version": "0.33.4",
"description": "Prebuilt sharp for use with Linux (glibc) s390x",
"author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://sharp.pixelplumbing.com",
@@ -15,7 +15,7 @@
},
"preferUnplugged": true,
"optionalDependencies": {
"@img/sharp-libvips-linux-s390x": "1.0.1"
"@img/sharp-libvips-linux-s390x": "1.0.2"
},
"files": [
"lib"
@@ -33,7 +33,7 @@
"npm": ">=9.6.5",
"yarn": ">=3.2.0",
"pnpm": ">=7.1.0",
"glibc": ">=2.28"
"glibc": ">=2.31"
},
"os": [
"linux"

View File

@@ -1,6 +1,6 @@
{
"name": "@img/sharp-linux-x64",
"version": "0.33.2-rc.1",
"version": "0.33.4",
"description": "Prebuilt sharp for use with Linux (glibc) x64",
"author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://sharp.pixelplumbing.com",
@@ -15,7 +15,7 @@
},
"preferUnplugged": true,
"optionalDependencies": {
"@img/sharp-libvips-linux-x64": "1.0.1"
"@img/sharp-libvips-linux-x64": "1.0.2"
},
"files": [
"lib"

View File

@@ -1,6 +1,6 @@
{
"name": "@img/sharp-linuxmusl-arm64",
"version": "0.33.2-rc.1",
"version": "0.33.4",
"description": "Prebuilt sharp for use with Linux (musl) 64-bit ARM",
"author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://sharp.pixelplumbing.com",
@@ -15,7 +15,7 @@
},
"preferUnplugged": true,
"optionalDependencies": {
"@img/sharp-libvips-linuxmusl-arm64": "1.0.1"
"@img/sharp-libvips-linuxmusl-arm64": "1.0.2"
},
"files": [
"lib"

View File

@@ -1,6 +1,6 @@
{
"name": "@img/sharp-linuxmusl-x64",
"version": "0.33.2-rc.1",
"version": "0.33.4",
"description": "Prebuilt sharp for use with Linux (musl) x64",
"author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://sharp.pixelplumbing.com",
@@ -15,7 +15,7 @@
},
"preferUnplugged": true,
"optionalDependencies": {
"@img/sharp-libvips-linuxmusl-x64": "1.0.1"
"@img/sharp-libvips-linuxmusl-x64": "1.0.2"
},
"files": [
"lib"

View File

@@ -1,6 +1,6 @@
{
"name": "@img/sharp",
"version": "0.33.2-rc.1",
"version": "0.33.4",
"private": "true",
"workspaces": [
"darwin-arm64",

View File

@@ -1,6 +1,6 @@
{
"name": "@img/sharp-wasm32",
"version": "0.33.2-rc.1",
"version": "0.33.4",
"description": "Prebuilt sharp for use with wasm32",
"author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://sharp.pixelplumbing.com",
@@ -34,7 +34,7 @@
"pnpm": ">=7.1.0"
},
"dependencies": {
"@emnapi/runtime": "^0.45.0"
"@emnapi/runtime": "^1.1.1"
},
"cpu": [
"wasm32"

View File

@@ -1,6 +1,6 @@
{
"name": "@img/sharp-win32-ia32",
"version": "0.33.2-rc.1",
"version": "0.33.4",
"description": "Prebuilt sharp for use with Windows x86 (32-bit)",
"author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://sharp.pixelplumbing.com",

View File

@@ -1,6 +1,6 @@
{
"name": "@img/sharp-win32-x64",
"version": "0.33.2-rc.1",
"version": "0.33.4",
"description": "Prebuilt sharp for use with Windows x64",
"author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://sharp.pixelplumbing.com",

View File

@@ -1,7 +1,7 @@
{
"name": "sharp",
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, GIF, AVIF and TIFF images",
"version": "0.33.2-rc.1",
"version": "0.33.4",
"author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://sharp.pixelplumbing.com",
"contributors": [
@@ -82,7 +82,7 @@
"Joris Dugué <zaruike10@gmail.com>",
"Chris Banks <christopher.bradley.banks@gmail.com>",
"Ompal Singh <ompal.hitm09@gmail.com>",
"Brodan <christopher.hranj@gmail.com",
"Brodan <christopher.hranj@gmail.com>",
"Ankur Parihar <ankur.github@gmail.com>",
"Brahim Ait elhaj <brahima@gmail.com>",
"Mart Jansink <m.jansink@gmail.com>",
@@ -137,57 +137,57 @@
],
"dependencies": {
"color": "^4.2.3",
"detect-libc": "^2.0.2",
"semver": "^7.5.4"
"detect-libc": "^2.0.3",
"semver": "^7.6.0"
},
"optionalDependencies": {
"@img/sharp-darwin-arm64": "0.33.2-rc.1",
"@img/sharp-darwin-x64": "0.33.2-rc.1",
"@img/sharp-libvips-darwin-arm64": "1.0.1",
"@img/sharp-libvips-darwin-x64": "1.0.1",
"@img/sharp-libvips-linux-arm": "1.0.1",
"@img/sharp-libvips-linux-arm64": "1.0.1",
"@img/sharp-libvips-linux-s390x": "1.0.1",
"@img/sharp-libvips-linux-x64": "1.0.1",
"@img/sharp-libvips-linuxmusl-arm64": "1.0.1",
"@img/sharp-libvips-linuxmusl-x64": "1.0.1",
"@img/sharp-linux-arm": "0.33.2-rc.1",
"@img/sharp-linux-arm64": "0.33.2-rc.1",
"@img/sharp-linux-s390x": "0.33.2-rc.1",
"@img/sharp-linux-x64": "0.33.2-rc.1",
"@img/sharp-linuxmusl-arm64": "0.33.2-rc.1",
"@img/sharp-linuxmusl-x64": "0.33.2-rc.1",
"@img/sharp-wasm32": "0.33.2-rc.1",
"@img/sharp-win32-ia32": "0.33.2-rc.1",
"@img/sharp-win32-x64": "0.33.2-rc.1"
"@img/sharp-darwin-arm64": "0.33.4",
"@img/sharp-darwin-x64": "0.33.4",
"@img/sharp-libvips-darwin-arm64": "1.0.2",
"@img/sharp-libvips-darwin-x64": "1.0.2",
"@img/sharp-libvips-linux-arm": "1.0.2",
"@img/sharp-libvips-linux-arm64": "1.0.2",
"@img/sharp-libvips-linux-s390x": "1.0.2",
"@img/sharp-libvips-linux-x64": "1.0.2",
"@img/sharp-libvips-linuxmusl-arm64": "1.0.2",
"@img/sharp-libvips-linuxmusl-x64": "1.0.2",
"@img/sharp-linux-arm": "0.33.4",
"@img/sharp-linux-arm64": "0.33.4",
"@img/sharp-linux-s390x": "0.33.4",
"@img/sharp-linux-x64": "0.33.4",
"@img/sharp-linuxmusl-arm64": "0.33.4",
"@img/sharp-linuxmusl-x64": "0.33.4",
"@img/sharp-wasm32": "0.33.4",
"@img/sharp-win32-ia32": "0.33.4",
"@img/sharp-win32-x64": "0.33.4"
},
"devDependencies": {
"@emnapi/runtime": "^0.45.0",
"@img/sharp-libvips-dev": "1.0.1",
"@img/sharp-libvips-dev-wasm32": "1.0.1",
"@img/sharp-libvips-win32-ia32": "1.0.1",
"@img/sharp-libvips-win32-x64": "1.0.1",
"@emnapi/runtime": "^1.1.1",
"@img/sharp-libvips-dev": "1.0.2",
"@img/sharp-libvips-dev-wasm32": "1.0.3",
"@img/sharp-libvips-win32-ia32": "1.0.2",
"@img/sharp-libvips-win32-x64": "1.0.2",
"@types/node": "*",
"async": "^3.2.5",
"cc": "^3.0.1",
"emnapi": "^0.45.0",
"exif-reader": "^2.0.0",
"emnapi": "^1.1.1",
"exif-reader": "^2.0.1",
"extract-zip": "^2.0.1",
"icc": "^3.0.0",
"jsdoc-to-markdown": "^8.0.0",
"jsdoc-to-markdown": "^8.0.1",
"license-checker": "^25.0.1",
"mocha": "^10.2.0",
"node-addon-api": "^7.0.0",
"mocha": "^10.4.0",
"node-addon-api": "^8.0.0",
"nyc": "^15.1.0",
"prebuild": "^12.1.0",
"prebuild": "^13.0.1",
"semistandard": "^17.0.0",
"tar-fs": "^3.0.4",
"tsd": "^0.30.3"
"tar-fs": "^3.0.6",
"tsd": "^0.31.0"
},
"license": "Apache-2.0",
"engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0",
"libvips": ">=8.15.1"
"libvips": ">=8.15.2"
},
"funding": {
"url": "https://opencollective.com/libvips"

View File

@@ -75,7 +75,7 @@ namespace sharp {
Napi::Buffer<char> buffer = input.Get("buffer").As<Napi::Buffer<char>>();
descriptor->bufferLength = buffer.Length();
descriptor->buffer = buffer.Data();
descriptor->isBuffer = TRUE;
descriptor->isBuffer = true;
}
descriptor->failOn = AttrAsEnum<VipsFailOn>(input, "failOn", VIPS_TYPE_FAIL_ON);
// Density for vector-based input
@@ -384,7 +384,7 @@ namespace sharp {
->set("access", descriptor->access)
->set("fail_on", descriptor->failOn);
if (descriptor->unlimited && ImageTypeSupportsUnlimited(imageType)) {
option->set("unlimited", TRUE);
option->set("unlimited", true);
}
if (imageType == ImageType::SVG || imageType == ImageType::PDF) {
option->set("dpi", descriptor->density);
@@ -488,7 +488,7 @@ namespace sharp {
->set("access", descriptor->access)
->set("fail_on", descriptor->failOn);
if (descriptor->unlimited && ImageTypeSupportsUnlimited(imageType)) {
option->set("unlimited", TRUE);
option->set("unlimited", true);
}
if (imageType == ImageType::SVG || imageType == ImageType::PDF) {
option->set("dpi", descriptor->density);
@@ -768,7 +768,7 @@ namespace sharp {
int *timeout = VIPS_NEW(im, int);
*timeout = seconds;
g_signal_connect(im, "eval", G_CALLBACK(VipsProgressCallBack), timeout);
vips_image_set_progress(im, TRUE);
vips_image_set_progress(im, true);
}
}
}
@@ -778,7 +778,7 @@ namespace sharp {
*/
void VipsProgressCallBack(VipsImage *im, VipsProgress *progress, int *timeout) {
if (*timeout > 0 && progress->run >= *timeout) {
vips_image_set_kill(im, TRUE);
vips_image_set_kill(im, true);
vips_error("timeout", "%d%% complete", progress->percent);
*timeout = 0;
}
@@ -1081,9 +1081,10 @@ namespace sharp {
/*
Ensure decoding remains sequential.
*/
VImage StaySequential(VImage image, VipsAccess access, bool condition) {
if (access == VIPS_ACCESS_SEQUENTIAL && condition) {
return image.copy_memory();
VImage StaySequential(VImage image, bool condition) {
if (vips_image_is_sequential(image.get_image()) && condition) {
image = image.copy_memory().copy();
image.remove(VIPS_META_SEQUENTIAL);
}
return image;
}

View File

@@ -16,8 +16,8 @@
#if (VIPS_MAJOR_VERSION < 8) || \
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 15) || \
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 15 && VIPS_MICRO_VERSION < 1)
#error "libvips version 8.15.1+ is required - please see https://sharp.pixelplumbing.com/install"
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 15 && VIPS_MICRO_VERSION < 2)
#error "libvips version 8.15.2+ is required - please see https://sharp.pixelplumbing.com/install"
#endif
#if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6)))
@@ -79,12 +79,12 @@ namespace sharp {
buffer(nullptr),
failOn(VIPS_FAIL_ON_WARNING),
limitInputPixels(0x3FFF * 0x3FFF),
unlimited(FALSE),
unlimited(false),
access(VIPS_ACCESS_RANDOM),
bufferLength(0),
isBuffer(FALSE),
isBuffer(false),
density(72.0),
ignoreIcc(FALSE),
ignoreIcc(false),
rawDepth(VIPS_FORMAT_UCHAR),
rawChannels(0),
rawWidth(0),
@@ -103,9 +103,9 @@ namespace sharp {
textWidth(0),
textHeight(0),
textAlign(VIPS_ALIGN_LOW),
textJustify(FALSE),
textJustify(false),
textDpi(72),
textRgba(FALSE),
textRgba(false),
textSpacing(0),
textWrap(VIPS_TEXT_WRAP_WORD),
textAutofitDpi(0) {}
@@ -386,7 +386,7 @@ namespace sharp {
/*
Ensure decoding remains sequential.
*/
VImage StaySequential(VImage image, VipsAccess access, bool condition = TRUE);
VImage StaySequential(VImage image, bool condition = true);
} // namespace sharp

View File

@@ -155,7 +155,7 @@ namespace sharp {
return image.conv(blur);
} else {
// Slower, accurate Gaussian blur
return StaySequential(image, VIPS_ACCESS_SEQUENTIAL).gaussblur(sigma);
return StaySequential(image).gaussblur(sigma);
}
}
@@ -386,7 +386,7 @@ namespace sharp {
pages.reserve(nPages);
// Split the image into cropped frames
image = StaySequential(image, VIPS_ACCESS_SEQUENTIAL);
image = StaySequential(image);
for (int i = 0; i < nPages; i++) {
pages.push_back(
image.extract_area(left, *pageHeight * i + top, width, height));

View File

@@ -54,7 +54,7 @@ class PipelineWorker : public Napi::AsyncWorker {
sharp::ImageType inputImageType;
std::tie(image, inputImageType) = sharp::OpenInput(baton->input);
VipsAccess access = baton->input->access;
image = sharp::EnsureColourspace(image, baton->colourspaceInput);
image = sharp::EnsureColourspace(image, baton->colourspacePipeline);
int nPages = baton->input->pages;
if (nPages == -1) {
@@ -70,8 +70,8 @@ class PipelineWorker : public Napi::AsyncWorker {
// Calculate angle of rotation
VipsAngle rotation = VIPS_ANGLE_D0;
VipsAngle autoRotation = VIPS_ANGLE_D0;
bool autoFlip = FALSE;
bool autoFlop = FALSE;
bool autoFlip = false;
bool autoFlop = false;
if (baton->useExifOrientation) {
// Rotate and flip image according to Exif orientation
@@ -88,7 +88,7 @@ class PipelineWorker : public Napi::AsyncWorker {
baton->rotationAngle != 0.0);
if (shouldRotateBefore) {
image = sharp::StaySequential(image, access,
image = sharp::StaySequential(image,
rotation != VIPS_ANGLE_D0 ||
autoRotation != VIPS_ANGLE_D0 ||
autoFlip ||
@@ -104,17 +104,17 @@ class PipelineWorker : public Napi::AsyncWorker {
}
if (autoFlip) {
image = image.flip(VIPS_DIRECTION_VERTICAL);
autoFlip = FALSE;
autoFlip = false;
} else if (baton->flip) {
image = image.flip(VIPS_DIRECTION_VERTICAL);
baton->flip = FALSE;
baton->flip = false;
}
if (autoFlop) {
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
autoFlop = FALSE;
autoFlop = false;
} else if (baton->flop) {
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
baton->flop = FALSE;
baton->flop = false;
}
if (rotation != VIPS_ANGLE_D0) {
if (rotation != VIPS_ANGLE_D180) {
@@ -126,7 +126,7 @@ class PipelineWorker : public Napi::AsyncWorker {
if (baton->rotationAngle != 0.0) {
MultiPageUnsupported(nPages, "Rotate");
std::vector<double> background;
std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground, FALSE);
std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground, false);
image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background)).copy_memory();
}
}
@@ -134,7 +134,7 @@ class PipelineWorker : public Napi::AsyncWorker {
// Trim
if (baton->trimThreshold >= 0.0) {
MultiPageUnsupported(nPages, "Trim");
image = sharp::StaySequential(image, access);
image = sharp::StaySequential(image);
image = sharp::Trim(image, baton->trimBackground, baton->trimThreshold, baton->trimLineArt);
baton->trimOffsetLeft = image.xoffset();
baton->trimOffsetTop = image.yoffset();
@@ -189,7 +189,7 @@ class PipelineWorker : public Napi::AsyncWorker {
// - input colourspace is not specified;
bool const shouldPreShrink = (targetResizeWidth > 0 || targetResizeHeight > 0) &&
baton->gamma == 0 && baton->topOffsetPre == -1 && baton->trimThreshold < 0.0 &&
baton->colourspaceInput == VIPS_INTERPRETATION_LAST && !shouldRotateBefore;
baton->colourspacePipeline == VIPS_INTERPRETATION_LAST && !shouldRotateBefore;
if (shouldPreShrink) {
// The common part of the shrink: the bit by which both axes must be shrunk
@@ -331,12 +331,13 @@ class PipelineWorker : public Napi::AsyncWorker {
sharp::HasProfile(image) &&
image.interpretation() != VIPS_INTERPRETATION_LABS &&
image.interpretation() != VIPS_INTERPRETATION_GREY16 &&
baton->colourspacePipeline != VIPS_INTERPRETATION_CMYK &&
!baton->input->ignoreIcc
) {
// Convert to sRGB/P3 using embedded profile
try {
image = image.icc_transform(processingProfile, VImage::option()
->set("embedded", TRUE)
->set("embedded", true)
->set("depth", sharp::Is16Bit(image.interpretation()) ? 16 : 8)
->set("intent", VIPS_INTENT_PERCEPTUAL));
} catch(...) {
@@ -344,7 +345,7 @@ class PipelineWorker : public Napi::AsyncWorker {
}
} else if (
image.interpretation() == VIPS_INTERPRETATION_CMYK &&
baton->colourspaceInput != VIPS_INTERPRETATION_CMYK
baton->colourspacePipeline != VIPS_INTERPRETATION_CMYK
) {
image = image.icc_transform(processingProfile, VImage::option()
->set("input_profile", "cmyk")
@@ -356,11 +357,6 @@ class PipelineWorker : public Napi::AsyncWorker {
image = sharp::Flatten(image, baton->flattenBackground);
}
// Negate the colours in the image
if (baton->negate) {
image = sharp::Negate(image, baton->negateAlpha);
}
// Gamma encoding (darken)
if (baton->gamma >= 1 && baton->gamma <= 3) {
image = sharp::Gamma(image, 1.0 / baton->gamma);
@@ -396,7 +392,7 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("kernel", baton->kernel));
}
image = sharp::StaySequential(image, access,
image = sharp::StaySequential(image,
autoRotation != VIPS_ANGLE_D0 ||
baton->flip ||
autoFlip ||
@@ -432,7 +428,7 @@ class PipelineWorker : public Napi::AsyncWorker {
for (unsigned int i = 0; i < baton->joinChannelIn.size(); i++) {
baton->joinChannelIn[i]->access = access;
std::tie(joinImage, joinImageType) = sharp::OpenInput(baton->joinChannelIn[i]);
joinImage = sharp::EnsureColourspace(joinImage, baton->colourspaceInput);
joinImage = sharp::EnsureColourspace(joinImage, baton->colourspacePipeline);
image = image.bandjoin(joinImage);
}
image = image.copy(VImage::option()->set("interpretation", baton->colourspace));
@@ -499,7 +495,7 @@ class PipelineWorker : public Napi::AsyncWorker {
// Attention-based or Entropy-based crop
MultiPageUnsupported(nPages, "Resize strategy");
image = sharp::StaySequential(image, access);
image = sharp::StaySequential(image);
image = image.smartcrop(baton->width, baton->height, VImage::option()
->set("interesting", baton->position == 16 ? VIPS_INTERESTING_ENTROPY : VIPS_INTERESTING_ATTENTION)
->set("premultiplied", shouldPremultiplyAlpha)
@@ -518,7 +514,7 @@ class PipelineWorker : public Napi::AsyncWorker {
// Rotate post-extract non-90 angle
if (!baton->rotateBeforePreExtract && baton->rotationAngle != 0.0) {
MultiPageUnsupported(nPages, "Rotate");
image = sharp::StaySequential(image, access);
image = sharp::StaySequential(image);
std::vector<double> background;
std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground, shouldPremultiplyAlpha);
image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background));
@@ -542,7 +538,7 @@ class PipelineWorker : public Napi::AsyncWorker {
// Affine transform
if (!baton->affineMatrix.empty()) {
MultiPageUnsupported(nPages, "Affine");
image = sharp::StaySequential(image, access);
image = sharp::StaySequential(image);
std::vector<double> background;
std::tie(image, background) = sharp::ApplyAlpha(image, baton->affineBackground, shouldPremultiplyAlpha);
vips::VInterpolate interp = vips::VInterpolate::new_from_name(
@@ -565,6 +561,7 @@ class PipelineWorker : public Napi::AsyncWorker {
std::vector<double> background;
std::tie(image, background) = sharp::ApplyAlpha(image, baton->extendBackground, shouldPremultiplyAlpha);
image = sharp::StaySequential(image, nPages > 1);
image = nPages > 1
? sharp::EmbedMultiPage(image,
baton->extendLeft, baton->extendTop, baton->width, baton->height,
@@ -573,7 +570,7 @@ class PipelineWorker : public Napi::AsyncWorker {
VImage::option()->set("extend", baton->extendWith)->set("background", background));
} else {
std::vector<double> ignoredBackground(1);
image = sharp::StaySequential(image, baton->input->access);
image = sharp::StaySequential(image);
image = nPages > 1
? sharp::EmbedMultiPage(image,
baton->extendLeft, baton->extendTop, baton->width, baton->height,
@@ -642,7 +639,7 @@ class PipelineWorker : public Napi::AsyncWorker {
sharp::ImageType compositeImageType = sharp::ImageType::UNKNOWN;
composite->input->access = access;
std::tie(compositeImage, compositeImageType) = sharp::OpenInput(composite->input);
compositeImage = sharp::EnsureColourspace(compositeImage, baton->colourspaceInput);
compositeImage = sharp::EnsureColourspace(compositeImage, baton->colourspacePipeline);
// Verify within current dimensions
if (compositeImage.width() > image.width() || compositeImage.height() > image.height()) {
throw vips::VError("Image to composite must have same dimensions or smaller");
@@ -669,7 +666,7 @@ class PipelineWorker : public Napi::AsyncWorker {
if (across != 0 || down != 0) {
int left;
int top;
compositeImage = sharp::StaySequential(compositeImage, access).replicate(across, down);
compositeImage = sharp::StaySequential(compositeImage).replicate(across, down);
if (composite->hasOffset) {
std::tie(left, top) = sharp::CalculateCrop(
compositeImage.width(), compositeImage.height(), image.width(), image.height(),
@@ -727,13 +724,13 @@ class PipelineWorker : public Napi::AsyncWorker {
// Apply normalisation - stretch luminance to cover full dynamic range
if (baton->normalise) {
image = sharp::StaySequential(image, access);
image = sharp::StaySequential(image);
image = sharp::Normalise(image, baton->normaliseLower, baton->normaliseUpper);
}
// Apply contrast limiting adaptive histogram equalization (CLAHE)
if (baton->claheWidth != 0 && baton->claheHeight != 0) {
image = sharp::StaySequential(image, access);
image = sharp::StaySequential(image);
image = sharp::Clahe(image, baton->claheWidth, baton->claheHeight, baton->claheMaxSlope);
}
@@ -743,7 +740,7 @@ class PipelineWorker : public Napi::AsyncWorker {
sharp::ImageType booleanImageType = sharp::ImageType::UNKNOWN;
baton->boolean->access = access;
std::tie(booleanImage, booleanImageType) = sharp::OpenInput(baton->boolean);
booleanImage = sharp::EnsureColourspace(booleanImage, baton->colourspaceInput);
booleanImage = sharp::EnsureColourspace(booleanImage, baton->colourspacePipeline);
image = sharp::Boolean(image, booleanImage, baton->booleanOp);
image = sharp::RemoveGifPalette(image);
}
@@ -776,10 +773,10 @@ class PipelineWorker : public Napi::AsyncWorker {
// Convert colourspace, pass the current known interpretation so libvips doesn't have to guess
image = image.colourspace(baton->colourspace, VImage::option()->set("source_space", image.interpretation()));
// Transform colours from embedded profile to output profile
if ((baton->keepMetadata & VIPS_FOREIGN_KEEP_ICC) &&
if ((baton->keepMetadata & VIPS_FOREIGN_KEEP_ICC) && baton->colourspacePipeline != VIPS_INTERPRETATION_CMYK &&
baton->withIccProfile.empty() && sharp::HasProfile(image)) {
image = image.icc_transform("srgb", VImage::option()
->set("embedded", TRUE)
image = image.icc_transform(processingProfile, VImage::option()
->set("embedded", true)
->set("depth", sharp::Is16Bit(image.interpretation()) ? 16 : 8)
->set("intent", VIPS_INTENT_PERCEPTUAL));
}
@@ -810,7 +807,7 @@ class PipelineWorker : public Napi::AsyncWorker {
try {
image = image.icc_transform(const_cast<char*>(baton->withIccProfile.data()), VImage::option()
->set("input_profile", processingProfile)
->set("embedded", TRUE)
->set("embedded", true)
->set("depth", sharp::Is16Bit(image.interpretation()) ? 16 : 8)
->set("intent", VIPS_INTENT_PERCEPTUAL));
} catch(...) {
@@ -819,6 +816,12 @@ class PipelineWorker : public Napi::AsyncWorker {
} else if (baton->keepMetadata & VIPS_FOREIGN_KEEP_ICC) {
image = sharp::SetProfile(image, inputProfile);
}
// Negate the colours in the image
if (baton->negate) {
image = sharp::Negate(image, baton->negateAlpha);
}
// Override EXIF Orientation tag
if (baton->withMetadataOrientation != -1) {
image = sharp::SetExifOrientation(image, baton->withMetadataOrientation);
@@ -988,7 +991,7 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("Q", baton->heifQuality)
->set("compression", baton->heifCompression)
->set("effort", baton->heifEffort)
->set("bitdepth", 8)
->set("bitdepth", baton->heifBitdepth)
->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
->set("lossless", baton->heifLossless)));
@@ -1003,7 +1006,7 @@ class PipelineWorker : public Napi::AsyncWorker {
if (!sharp::HasAlpha(image)) {
baton->tileBackground.pop_back();
}
image = sharp::StaySequential(image, access, baton->tileAngle != 0);
image = sharp::StaySequential(image, baton->tileAngle != 0);
vips::VOption *options = BuildOptionsDZ(baton);
VipsArea *area = reinterpret_cast<VipsArea*>(image.dzsave_buffer(options));
baton->bufferOut = static_cast<char*>(area->data);
@@ -1181,7 +1184,7 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("Q", baton->heifQuality)
->set("compression", baton->heifCompression)
->set("effort", baton->heifEffort)
->set("bitdepth", 8)
->set("bitdepth", baton->heifBitdepth)
->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
->set("lossless", baton->heifLossless));
@@ -1205,7 +1208,7 @@ class PipelineWorker : public Napi::AsyncWorker {
if (!sharp::HasAlpha(image)) {
baton->tileBackground.pop_back();
}
image = sharp::StaySequential(image, access, baton->tileAngle != 0);
image = sharp::StaySequential(image, baton->tileAngle != 0);
vips::VOption *options = BuildOptionsDZ(baton);
image.dzsave(const_cast<char*>(baton->fileOut.data()), options);
baton->formatOut = "dz";
@@ -1337,16 +1340,16 @@ class PipelineWorker : public Napi::AsyncWorker {
std::tuple<VipsAngle, bool, bool>
CalculateExifRotationAndFlip(int const exifOrientation) {
VipsAngle rotate = VIPS_ANGLE_D0;
bool flip = FALSE;
bool flop = FALSE;
bool flip = false;
bool flop = false;
switch (exifOrientation) {
case 6: rotate = VIPS_ANGLE_D90; break;
case 3: rotate = VIPS_ANGLE_D180; break;
case 8: rotate = VIPS_ANGLE_D270; break;
case 2: flop = TRUE; break; // flop 1
case 7: flip = TRUE; rotate = VIPS_ANGLE_D90; break; // flip 6
case 4: flop = TRUE; rotate = VIPS_ANGLE_D180; break; // flop 3
case 5: flip = TRUE; rotate = VIPS_ANGLE_D270; break; // flip 8
case 2: flop = true; break; // flop 1
case 7: flip = true; rotate = VIPS_ANGLE_D90; break; // flip 6
case 4: flop = true; rotate = VIPS_ANGLE_D180; break; // flop 3
case 5: flip = true; rotate = VIPS_ANGLE_D270; break; // flip 8
}
return std::make_tuple(rotate, flip, flop);
}
@@ -1394,7 +1397,7 @@ class PipelineWorker : public Napi::AsyncWorker {
std::string suffix;
if (baton->tileFormat == "png") {
std::vector<std::pair<std::string, std::string>> options {
{"interlace", baton->pngProgressive ? "TRUE" : "FALSE"},
{"interlace", baton->pngProgressive ? "true" : "false"},
{"compression", std::to_string(baton->pngCompressionLevel)},
{"filter", baton->pngAdaptiveFiltering ? "all" : "none"}
};
@@ -1403,25 +1406,25 @@ class PipelineWorker : public Napi::AsyncWorker {
std::vector<std::pair<std::string, std::string>> options {
{"Q", std::to_string(baton->webpQuality)},
{"alpha_q", std::to_string(baton->webpAlphaQuality)},
{"lossless", baton->webpLossless ? "TRUE" : "FALSE"},
{"near_lossless", baton->webpNearLossless ? "TRUE" : "FALSE"},
{"smart_subsample", baton->webpSmartSubsample ? "TRUE" : "FALSE"},
{"lossless", baton->webpLossless ? "true" : "false"},
{"near_lossless", baton->webpNearLossless ? "true" : "false"},
{"smart_subsample", baton->webpSmartSubsample ? "true" : "false"},
{"preset", vips_enum_nick(VIPS_TYPE_FOREIGN_WEBP_PRESET, baton->webpPreset)},
{"min_size", baton->webpMinSize ? "TRUE" : "FALSE"},
{"mixed", baton->webpMixed ? "TRUE" : "FALSE"},
{"min_size", baton->webpMinSize ? "true" : "false"},
{"mixed", baton->webpMixed ? "true" : "false"},
{"effort", std::to_string(baton->webpEffort)}
};
suffix = AssembleSuffixString(".webp", options);
} else {
std::vector<std::pair<std::string, std::string>> options {
{"Q", std::to_string(baton->jpegQuality)},
{"interlace", baton->jpegProgressive ? "TRUE" : "FALSE"},
{"interlace", baton->jpegProgressive ? "true" : "false"},
{"subsample_mode", baton->jpegChromaSubsampling == "4:4:4" ? "off" : "on"},
{"trellis_quant", baton->jpegTrellisQuantisation ? "TRUE" : "FALSE"},
{"trellis_quant", baton->jpegTrellisQuantisation ? "true" : "false"},
{"quant_table", std::to_string(baton->jpegQuantisationTable)},
{"overshoot_deringing", baton->jpegOvershootDeringing ? "TRUE": "FALSE"},
{"optimize_scans", baton->jpegOptimiseScans ? "TRUE": "FALSE"},
{"optimize_coding", baton->jpegOptimiseCoding ? "TRUE": "FALSE"}
{"overshoot_deringing", baton->jpegOvershootDeringing ? "true": "false"},
{"optimize_scans", baton->jpegOptimiseScans ? "true": "false"},
{"optimize_coding", baton->jpegOptimiseCoding ? "true": "false"}
};
std::string extname = baton->tileLayout == VIPS_FOREIGN_DZ_LAYOUT_DZ ? ".jpeg" : ".jpg";
suffix = AssembleSuffixString(extname, options);
@@ -1607,10 +1610,10 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
baton->recombMatrix[i] = sharp::AttrAsDouble(recombMatrix, i);
}
}
baton->colourspaceInput = sharp::AttrAsEnum<VipsInterpretation>(
options, "colourspaceInput", VIPS_TYPE_INTERPRETATION);
if (baton->colourspaceInput == VIPS_INTERPRETATION_ERROR) {
baton->colourspaceInput = VIPS_INTERPRETATION_LAST;
baton->colourspacePipeline = sharp::AttrAsEnum<VipsInterpretation>(
options, "colourspacePipeline", VIPS_TYPE_INTERPRETATION);
if (baton->colourspacePipeline == VIPS_INTERPRETATION_ERROR) {
baton->colourspacePipeline = VIPS_INTERPRETATION_LAST;
}
baton->colourspace = sharp::AttrAsEnum<VipsInterpretation>(options, "colourspace", VIPS_TYPE_INTERPRETATION);
if (baton->colourspace == VIPS_INTERPRETATION_ERROR) {
@@ -1695,6 +1698,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
options, "heifCompression", VIPS_TYPE_FOREIGN_HEIF_COMPRESSION);
baton->heifEffort = sharp::AttrAsUint32(options, "heifEffort");
baton->heifChromaSubsampling = sharp::AttrAsStr(options, "heifChromaSubsampling");
baton->heifBitdepth = sharp::AttrAsUint32(options, "heifBitdepth");
baton->jxlDistance = sharp::AttrAsDouble(options, "jxlDistance");
baton->jxlDecodingTier = sharp::AttrAsUint32(options, "jxlDecodingTier");
baton->jxlEffort = sharp::AttrAsUint32(options, "jxlEffort");

View File

@@ -181,6 +181,7 @@ struct PipelineBaton {
int heifEffort;
std::string heifChromaSubsampling;
bool heifLossless;
int heifBitdepth;
double jxlDistance;
int jxlDecodingTier;
int jxlEffort;
@@ -205,7 +206,7 @@ struct PipelineBaton {
int extractChannel;
bool removeAlpha;
double ensureAlpha;
VipsInterpretation colourspaceInput;
VipsInterpretation colourspacePipeline;
VipsInterpretation colourspace;
std::vector<int> delay;
int loop;
@@ -349,6 +350,7 @@ struct PipelineBaton {
heifEffort(4),
heifChromaSubsampling("4:4:4"),
heifLossless(false),
heifBitdepth(8),
jxlDistance(1.0),
jxlDecodingTier(0),
jxlEffort(7),
@@ -369,7 +371,7 @@ struct PipelineBaton {
extractChannel(-1),
removeAlpha(false),
ensureAlpha(-1.0),
colourspaceInput(VIPS_INTERPRETATION_LAST),
colourspacePipeline(VIPS_INTERPRETATION_LAST),
colourspace(VIPS_INTERPRETATION_LAST),
loop(-1),
tileSize(256),

BIN
test/fixtures/XCMYK 2017.icc vendored Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

BIN
test/fixtures/fogra-0-100-100-0.tif vendored Normal file

Binary file not shown.

View File

@@ -114,6 +114,7 @@ module.exports = {
inputTiffUncompressed: getPath('uncompressed_tiff.tiff'), // https://code.google.com/archive/p/imagetestsuite/wikis/TIFFTestSuite.wiki file: 0c84d07e1b22b76f24cccc70d8788e4a.tif
inputTiff8BitDepth: getPath('8bit_depth.tiff'),
inputTifftagPhotoshop: getPath('tifftag-photoshop.tiff'), // https://github.com/lovell/sharp/issues/1600
inputTiffFogra: getPath('fogra-0-100-100-0.tif'), // https://github.com/lovell/sharp/issues/4045
inputJp2: getPath('relax.jp2'), // https://www.fnordware.com/j2k/relax.jp2
inputGif: getPath('Crash_test.gif'), // http://upload.wikimedia.org/wikipedia/commons/e/e3/Crash_test.gif

View File

@@ -410,7 +410,7 @@ sharp({
// Taken from API documentation at
// https://sharp.pixelplumbing.com/api-operation#clahe
// introducted
// introduced
sharp('input.jpg').clahe({ width: 10, height: 10 }).toFile('output.jpg');
sharp('input.jpg').clahe({ width: 10, height: 10, maxSlope: 5 }).toFile('outfile.jpg');
@@ -595,7 +595,7 @@ sharp({
rgba: true,
justify: true,
spacing: 10,
wrap: 'charWord',
wrap: 'word-char',
},
})
.png()
@@ -680,3 +680,24 @@ sharp(input)
.keepIccProfile()
.withIccProfile('filename')
.withIccProfile('filename', { attach: false });
// Added missing types for OverlayOptions
// https://github.com/lovell/sharp/pull/4048
sharp(input).composite([
{
input: 'image.gif',
animated: true,
limitInputPixels: 536805378,
density: 144,
failOn: "warning"
}
])
sharp(input).composite([
{
input: 'image.png',
animated: false,
limitInputPixels: 178935126,
density: 72,
failOn: "truncated"
}
])

View File

@@ -144,4 +144,10 @@ describe('AVIF', () => {
/Processed image is too large for the HEIF format/
)
);
it('Invalid bitdepth value throws error', async () => {
assert.rejects(
() => sharp().avif({ bitdepth: 11 }),
/Error: Expected 8, 10 or 12 for bitdepth but received 11 of type number/);
});
});

View File

@@ -77,4 +77,26 @@ describe('Clone', function () {
assert.strictEqual(0, original.listenerCount('finish'));
assert.strictEqual(0, clone.listenerCount('finish'));
});
it('Ensure deep clone of properties, including arrays', async () => {
const alpha = await sharp({
create: { width: 320, height: 240, channels: 3, background: 'red' }
}).toColourspace('b-w').png().toBuffer();
const original = sharp();
const joiner = original.clone().joinChannel(alpha);
const negater = original.clone().negate();
fs.createReadStream(fixtures.inputJpg320x240).pipe(original);
const joined = await joiner.png({ effort: 1 }).toBuffer();
const negated = await negater.png({ effort: 1 }).toBuffer();
const joinedMetadata = await sharp(joined).metadata();
assert.strictEqual(joinedMetadata.channels, 4);
assert.strictEqual(joinedMetadata.hasAlpha, true);
const negatedMetadata = await sharp(negated).metadata();
assert.strictEqual(negatedMetadata.channels, 3);
assert.strictEqual(negatedMetadata.hasAlpha, false);
});
});

View File

@@ -106,6 +106,43 @@ describe('Colour space conversion', function () {
);
});
it('CMYK profile to CMYK profile conversion using perceptual intent', async () => {
const data = await sharp(fixtures.inputTiffFogra)
.resize(320, 240)
.toColourspace('cmyk')
.pipelineColourspace('cmyk')
.withIccProfile(fixtures.path('XCMYK 2017.icc'))
.raw()
.toBuffer();
const [c, m, y, k] = data;
assert.deepStrictEqual(
{ c, m, y, k },
{ c: 1, m: 239, y: 227, k: 5 }
);
});
it('CMYK profile to CMYK profile with negate', (done) => {
sharp(fixtures.inputTiffFogra)
.resize(320, 240)
.toColourspace('cmyk')
.pipelineColourspace('cmyk')
.withIccProfile(fixtures.path('XCMYK 2017.icc'))
.negate()
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual('tiff', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
fixtures.assertSimilar(
fixtures.expected('colourspace.cmyk-to-cmyk-negated.tif'),
data,
{ threshold: 0 },
done
);
});
});
it('From sRGB with RGB16 pipeline, resize with gamma, to sRGB', function (done) {
sharp(fixtures.inputPngGradients)
.pipelineColourspace('rgb16')

View File

@@ -451,7 +451,7 @@ describe('composite', () => {
assert.strictEqual(info.height, 40);
});
it('Ensure implict unpremultiply after resize but before composite', async () => {
it('Ensure implicit unpremultiply after resize but before composite', async () => {
const [r, g, b, a] = await sharp({
create: {
width: 1, height: 1, channels: 4, background: 'saddlebrown'

View File

@@ -73,20 +73,6 @@ describe('Extend', function () {
});
});
it('extend top with mirroring uses ordered read', async () => {
const data = await sharp(fixtures.inputJpg)
.extend({
extendWith: 'mirror',
top: 1
})
.png({ compressionLevel: 0 })
.toBuffer();
const { width, height } = await sharp(data).metadata();
assert.strictEqual(2725, width);
assert.strictEqual(2226, height);
});
it(`extend sides unequally with RGBA (${extendWith})`, function (done) {
sharp(fixtures.inputPngWithTransparency16bit)
.resize(120)
@@ -127,6 +113,39 @@ describe('Extend', function () {
});
});
it('extend top with mirroring uses ordered read', async () => {
const data = await sharp(fixtures.inputJpg)
.extend({
extendWith: 'mirror',
top: 1
})
.png({ compressionLevel: 0 })
.toBuffer();
const { width, height } = await sharp(data).metadata();
assert.strictEqual(2725, width);
assert.strictEqual(2226, height);
});
it('multi-page extend uses ordered read', async () => {
const multiPageTiff = await sharp(fixtures.inputGifAnimated, { animated: true })
.resize({ width: 8, height: 48 })
.tiff()
.toBuffer();
const data = await sharp(multiPageTiff, { pages: -1 })
.extend({
background: 'red',
top: 1
})
.png({ compressionLevel: 0 })
.toBuffer();
const { width, height } = await sharp(data).metadata();
assert.strictEqual(8, width);
assert.strictEqual(1470, height);
});
it('missing parameter fails', function () {
assert.throws(function () {
sharp().extend();

View File

@@ -78,4 +78,21 @@ describe('HEIF', () => {
sharp().heif({ compression: 'av1', chromaSubsampling: '4:4:4' });
});
});
it('valid bitdepth value does not throw an error', () => {
const { heif } = sharp.versions;
delete sharp.versions.heif;
assert.doesNotThrow(() => {
sharp().heif({ compression: 'av1', bitdepth: 12 });
});
sharp.versions.heif = '1.2.3';
assert.throws(() => {
sharp().heif({ compression: 'av1', bitdepth: 10 });
}, /Error: Expected 8 for bitdepth when using prebuilt binaries but received 10 of type number/);
sharp.versions.heif = heif;
});
it('invalid bitdepth value should throw an error', () => {
assert.throws(() => {
sharp().heif({ compression: 'av1', bitdepth: 11 });
}, /Error: Expected 8, 10 or 12 for bitdepth but received 11 of type number/);
});
});

View File

@@ -66,6 +66,14 @@ describe('libvips binaries', function () {
delete process.env.SHARP_IGNORE_GLOBAL_LIBVIPS;
});
it('useGlobalLibvips can be forced via an env var', function () {
process.env.SHARP_FORCE_GLOBAL_LIBVIPS = 1;
const useGlobalLibvips = libvips.useGlobalLibvips();
assert.strictEqual(true, useGlobalLibvips);
delete process.env.SHARP_FORCE_GLOBAL_LIBVIPS;
});
});
describe('Build time platform detection', () => {
@@ -162,7 +170,7 @@ describe('libvips binaries', function () {
process.env.npm_config_arch = 's390x';
process.env.npm_config_libc = '';
const locatorHash = libvips.yarnLocator();
assert.strictEqual(locatorHash, 'f2bd19138a');
assert.strictEqual(locatorHash, '45978c229d');
delete process.env.npm_config_platform;
delete process.env.npm_config_arch;
delete process.env.npm_config_libc;

View File

@@ -576,6 +576,19 @@ describe('Image metadata', function () {
assert.strictEqual(description, 'Generic RGB Profile');
});
it('keep existing CMYK ICC profile', async () => {
const data = await sharp(fixtures.inputJpgWithCmykProfile)
.pipelineColourspace('cmyk')
.toColourspace('cmyk')
.keepIccProfile()
.toBuffer();
const metadata = await sharp(data).metadata();
assert.strictEqual(metadata.channels, 4);
const { description } = icc.parse(metadata.icc);
assert.strictEqual(description, 'U.S. Web Coated (SWOP) v2');
});
it('transform to ICC profile and attach', async () => {
const data = await sharp({ create })
.png()

View File

@@ -228,26 +228,34 @@ describe('Text to image', function () {
});
});
it('bad width input', function () {
assert.throws(function () {
sharp({
text: {
text: 'text',
width: 'bad'
}
});
});
it('invalid width', () => {
assert.throws(
() => sharp({ text: { text: 'text', width: 'bad' } }),
/Expected positive integer for text\.width but received bad of type string/
);
assert.throws(
() => sharp({ text: { text: 'text', width: 0.1 } }),
/Expected positive integer for text\.width but received 0.1 of type number/
);
assert.throws(
() => sharp({ text: { text: 'text', width: -1 } }),
/Expected positive integer for text\.width but received -1 of type number/
);
});
it('bad height input', function () {
assert.throws(function () {
sharp({
text: {
text: 'text',
height: 'bad'
}
});
});
it('invalid height', () => {
assert.throws(
() => sharp({ text: { text: 'text', height: 'bad' } }),
/Expected positive integer for text\.height but received bad of type string/
);
assert.throws(
() => sharp({ text: { text: 'text', height: 0.1 } }),
/Expected positive integer for text\.height but received 0.1 of type number/
);
assert.throws(
() => sharp({ text: { text: 'text', height: -1 } }),
/Expected positive integer for text\.height but received -1 of type number/
);
});
it('bad align input', function () {
@@ -272,15 +280,19 @@ describe('Text to image', function () {
});
});
it('bad dpi input', function () {
assert.throws(function () {
sharp({
text: {
text: 'text',
dpi: -10
}
});
});
it('invalid dpi', () => {
assert.throws(
() => sharp({ text: { text: 'text', dpi: 'bad' } }),
/Expected integer between 1 and 1000000 for text\.dpi but received bad of type string/
);
assert.throws(
() => sharp({ text: { text: 'text', dpi: 0.1 } }),
/Expected integer between 1 and 1000000 for text\.dpi but received 0.1 of type number/
);
assert.throws(
() => sharp({ text: { text: 'text', dpi: -1 } }),
/Expected integer between 1 and 1000000 for text\.dpi but received -1 of type number/
);
});
it('bad rgba input', function () {
@@ -294,15 +306,19 @@ describe('Text to image', function () {
});
});
it('bad spacing input', function () {
assert.throws(function () {
sharp({
text: {
text: 'text',
spacing: 'number expected'
}
});
});
it('invalid spacing', () => {
assert.throws(
() => sharp({ text: { text: 'text', spacing: 'bad' } }),
/Expected integer between -1000000 and 1000000 for text\.spacing but received bad of type string/
);
assert.throws(
() => sharp({ text: { text: 'text', spacing: 0.1 } }),
/Expected integer between -1000000 and 1000000 for text\.spacing but received 0.1 of type number/
);
assert.throws(
() => sharp({ text: { text: 'text', spacing: -1000001 } }),
/Expected integer between -1000000 and 1000000 for text\.spacing but received -1000001 of type number/
);
});
it('only height or dpi not both', function () {
@@ -319,21 +335,21 @@ describe('Text to image', function () {
it('valid wrap throws', () => {
assert.doesNotThrow(() => sharp({ text: { text: 'text', wrap: 'none' } }));
assert.doesNotThrow(() => sharp({ text: { text: 'text', wrap: 'wordChar' } }));
assert.doesNotThrow(() => sharp({ text: { text: 'text', wrap: 'word-char' } }));
});
it('invalid wrap throws', () => {
assert.throws(
() => sharp({ text: { text: 'text', wrap: 1 } }),
/Expected one of: word, char, wordChar, none for text\.wrap but received 1 of type number/
/Expected one of: word, char, word-char, none for text\.wrap but received 1 of type number/
);
assert.throws(
() => sharp({ text: { text: 'text', wrap: false } }),
/Expected one of: word, char, wordChar, none for text\.wrap but received false of type boolean/
/Expected one of: word, char, word-char, none for text\.wrap but received false of type boolean/
);
assert.throws(
() => sharp({ text: { text: 'text', wrap: 'invalid' } }),
/Expected one of: word, char, wordChar, none for text\.wrap but received invalid of type string/
/Expected one of: word, char, word-char, none for text\.wrap but received invalid of type string/
);
});
});