Compare commits

...

31 Commits

Author SHA1 Message Date
Lovell Fuller
2474bd4163 Prerelease v0.33.5-rc.1 2024-08-14 08:49:14 +01:00
Lovell Fuller
ff2e689d35 Remove yarn v1 specifics from help text 2024-08-14 08:48:12 +01:00
Lovell Fuller
6327f13717 CI: Add yarn v1 to packaging tests 2024-08-13 18:39:54 +01:00
Lovell Fuller
f1e69a218e Prerelease v0.33.5-rc.0 2024-08-13 15:42:49 +01:00
Lovell Fuller
3c14dbb21e Minimise use of engines property to improve yarn v1 support 2024-08-13 09:37:00 +01:00
Lovell Fuller
82cebc31d0 Upgrade to libvips v8.15.3 for Linux ARMv6
Previous version erroneously targetted ARMv7
2024-08-13 08:54:05 +01:00
Lovell Fuller
ad36fa0605 Ensure emnapiInit function is exported
Prevents latest emscripten optimising it away
2024-08-12 16:27:33 +01:00
Lovell Fuller
de42667767 Upgrade to libvips v8.15.3 2024-08-12 13:19:10 +01:00
Lovell Fuller
2eb03b0049 Ensure keepIccProfile avoids ICC transform #4186 2024-08-11 09:44:53 +01:00
Lovell Fuller
f7ed9b7fb6 Tests: reduce flakiness of withIccProfile warning check 2024-07-24 08:02:16 +01:00
Lovell Fuller
7fbb988180 Bump deps 2024-07-24 07:59:55 +01:00
Lovell Fuller
490210fc60 Docs: changelog for #4172 2024-07-23 11:37:30 +01:00
Marcos Casagrande
735fee74db Expose optional minAmplitude parameter of blur operation (#4172) 2024-07-23 11:31:11 +01:00
Lovell Fuller
67a5854b89 Docs: remove duplicate contributor 2024-07-20 20:02:11 +01:00
Lovell Fuller
f128ebdbd4 Tests: assertSimilar support for Promise and callback 2024-07-20 15:10:04 +01:00
Lovell Fuller
2672de2480 Docs: changelog and credit for #4168 2024-07-20 14:04:53 +01:00
Marcos Casagrande
67a4592756 Expose optional precision parameter of blur operation (#4168) 2024-07-20 13:53:23 +01:00
Lovell Fuller
10c6f474d9 Docs: changelog and credit for #4157 2024-07-18 18:29:38 +01:00
Nathan Keynes
d642108be2 Expose PNG metadata comments (#4157) 2024-07-18 18:08:03 +01:00
Lovell Fuller
c2a024101b CI: Upgrade Python to at least 3.8
- Latest node-gyp requires 3.8
- Python 3.7 is EOL
- Minimum glibc requirement for linux-arm will now be 2.31
  (Pi OS 'Legacy' provides Debian 11 with glibc 2.31)
2024-07-17 09:12:50 +01:00
Lovell Fuller
2f0bbebfc9 Refactor conv op to use slightly safer std::vector
Inspired by similar change to recomb op in commit 60c5c50
2024-07-05 21:34:24 +01:00
Denice
60c5c5083d Add support to recomb operation for 4x4 matrices 2024-07-05 15:31:51 +01:00
Lovell Fuller
eab7dc1b49 Bump devDeps 2024-07-01 10:16:15 +01:00
Lovell Fuller
5c7f37a0e0 Issue template: request people try newer npm first 2024-07-01 10:16:02 +01:00
Lovell Fuller
ae06f46914 Ensure sharp.format.heif is AVIF-only for prebuilt binaries 2024-06-19 09:40:02 +01:00
Lovell Fuller
9c05ea8dd2 Add pageHeight and pages to anim output response #3411 2024-06-17 16:32:49 +01:00
Don Denton
472aaf3311 Tests: ensure combination of flip and flop is covered (#4123) 2024-06-04 21:34:18 +01:00
Richard Hillmann
56fae3eda1 Ensure SHARP_FORCE_GLOBAL_LIBVIPS option works correctly #4111
Allows the install/check script to inject a logger function,
keeping its use within binding.gyp free of additional output.

Co-authored-by: Lovell Fuller <github@lovell.info>
2024-05-24 17:45:39 +01:00
Lovell Fuller
cc96c21e42 CI: Upgrade to macOS 12 (as 11 will be removed soon) 2024-05-21 11:40:11 +01:00
Lovell Fuller
1d344888ec Bump dep: emnapi 2024-05-21 11:39:41 +01:00
Lovell Fuller
bee235ee76 Docs: fix CSP 2024-05-21 11:39:08 +01:00
54 changed files with 564 additions and 248 deletions

View File

@@ -43,7 +43,7 @@ and try again before opening an issue.
<!-- Please place an [x] in the relevant box to confirm. --> <!-- Please place an [x] in the relevant box to confirm. -->
- [ ] I am using npm >= 9.6.5 with `--include=optional` - [ ] I am using npm >= 10.1.0 with `--include=optional`
- [ ] I am using yarn >= 3.2.0 - [ ] I am using yarn >= 3.2.0
- [ ] I am using pnpm >= 7.1.0 with `--no-optional=false` - [ ] I am using pnpm >= 7.1.0 with `--no-optional=false`
- [ ] I am using Deno - [ ] I am using Deno

View File

@@ -36,13 +36,13 @@ jobs:
container: node:20-alpine3.18 container: node:20-alpine3.18
nodejs_version_major: 20 nodejs_version_major: 20
platform: linuxmusl-x64 platform: linuxmusl-x64
- os: macos-11 - os: macos-12
nodejs_arch: x64 nodejs_arch: x64
nodejs_version: "^18.17.0" nodejs_version: "^18.17.0"
nodejs_version_major: 18 nodejs_version_major: 18
platform: darwin-x64 platform: darwin-x64
prebuild: true prebuild: true
- os: macos-11 - os: macos-12
nodejs_arch: x64 nodejs_arch: x64
nodejs_version: "^20.3.0" nodejs_version: "^20.3.0"
nodejs_version_major: 20 nodejs_version_major: 20
@@ -84,7 +84,7 @@ jobs:
- name: Dependencies (Rocky Linux glibc) - name: Dependencies (Rocky Linux glibc)
if: contains(matrix.container, 'rockylinux') if: contains(matrix.container, 'rockylinux')
run: | run: |
dnf install -y gcc-toolset-11-gcc-c++ make git python3 fontconfig google-noto-sans-fonts dnf install -y gcc-toolset-11-gcc-c++ make git python3.12 fontconfig google-noto-sans-fonts
echo "/opt/rh/gcc-toolset-11/root/usr/bin" >> $GITHUB_PATH echo "/opt/rh/gcc-toolset-11/root/usr/bin" >> $GITHUB_PATH
- name: Dependencies (Linux musl) - name: Dependencies (Linux musl)
if: contains(matrix.container, 'alpine') if: contains(matrix.container, 'alpine')
@@ -131,7 +131,7 @@ jobs:
matrix: matrix:
include: include:
- platform: linux-arm - platform: linux-arm
distro: buster distro: bullseye
run_on_arch: armv6 run_on_arch: armv6
nodejs_arch: armv6l nodejs_arch: armv6l
nodejs_hostname: unofficial-builds.nodejs.org nodejs_hostname: unofficial-builds.nodejs.org
@@ -171,7 +171,7 @@ jobs:
contents: write contents: write
name: wasm32 - prebuild name: wasm32 - prebuild
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
container: "emscripten/emsdk:3.1.56" container: "emscripten/emsdk:3.1.64"
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4

View File

@@ -31,6 +31,10 @@ jobs:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
runtime: node runtime: node
package-manager: yarn-pnp package-manager: yarn-pnp
- name: linux-x64-node-yarn-v1
runs-on: ubuntu-22.04
runtime: node
package-manager: yarn-v1
- name: linux-x64-deno - name: linux-x64-deno
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
runtime: deno runtime: deno
@@ -39,26 +43,30 @@ jobs:
runtime: bun runtime: bun
- name: darwin-x64-node-npm - name: darwin-x64-node-npm
runs-on: macos-11 runs-on: macos-12
runtime: node runtime: node
package-manager: npm package-manager: npm
- name: darwin-x64-node-pnpm - name: darwin-x64-node-pnpm
runs-on: macos-11 runs-on: macos-12
runtime: node runtime: node
package-manager: pnpm package-manager: pnpm
- name: darwin-x64-node-yarn - name: darwin-x64-node-yarn
runs-on: macos-11 runs-on: macos-12
runtime: node runtime: node
package-manager: yarn package-manager: yarn
- name: darwin-x64-node-yarn-pnp - name: darwin-x64-node-yarn-pnp
runs-on: macos-11 runs-on: macos-12
runtime: node runtime: node
package-manager: yarn-pnp package-manager: yarn-pnp
- name: darwin-x64-node-yarn-v1
runs-on: macos-12
runtime: node
package-manager: yarn-v1
- name: darwin-x64-deno - name: darwin-x64-deno
runs-on: macos-11 runs-on: macos-12
runtime: deno runtime: deno
- name: darwin-x64-bun - name: darwin-x64-bun
runs-on: macos-11 runs-on: macos-12
runtime: bun runtime: bun
- name: win32-x64-node-npm - name: win32-x64-node-npm
@@ -77,6 +85,10 @@ jobs:
runs-on: windows-2019 runs-on: windows-2019
runtime: node runtime: node
package-manager: yarn-pnp package-manager: yarn-pnp
- name: win32-x64-node-yarn-v1
runs-on: windows-2019
runtime: node
package-manager: yarn-v1
- name: win32-x64-deno - name: win32-x64-deno
runs-on: windows-2019 runs-on: windows-2019
runtime: deno runtime: deno
@@ -89,7 +101,7 @@ jobs:
node-version: 20 node-version: 20
- name: Install pnpm - name: Install pnpm
if: ${{ matrix.package-manager == 'pnpm' }} if: ${{ matrix.package-manager == 'pnpm' }}
uses: pnpm/action-setup@v2 uses: pnpm/action-setup@v4
with: with:
version: 8 version: 8
- name: Install Deno - name: Install Deno
@@ -99,13 +111,13 @@ jobs:
deno-version: v1.x deno-version: v1.x
- name: Install Bun - name: Install Bun
if: ${{ matrix.runtime == 'bun' }} if: ${{ matrix.runtime == 'bun' }}
uses: oven-sh/setup-bun@v1 uses: oven-sh/setup-bun@v2
with: with:
bun-version: latest bun-version: latest
- name: Version - name: Version
id: version id: version
uses: actions/github-script@v6 uses: actions/github-script@v7
with: with:
script: | script: |
core.setOutput('semver', context.ref.replace('refs/tags/v','')) core.setOutput('semver', context.ref.replace('refs/tags/v',''))
@@ -163,6 +175,14 @@ jobs:
yarn install yarn install
yarn node release.mjs yarn node release.mjs
- name: Run with Node.js + yarn v1
if: ${{ matrix.package-manager == 'yarn-v1' }}
run: |
corepack enable
yarn set version classic
yarn install
node release.mjs
- name: Run with Deno - name: Run with Deno
if: ${{ matrix.runtime == 'deno' }} if: ${{ matrix.runtime == 'deno' }}
run: deno run --allow-read --allow-ffi release.mjs run: deno run --allow-read --allow-ffi release.mjs

View File

@@ -42,6 +42,7 @@ A `Promise` is returned when `callback` is not provided.
- `xmp`: Buffer containing raw XMP data, if present - `xmp`: Buffer containing raw XMP data, if present
- `tifftagPhotoshop`: Buffer containing raw TIFFTAG_PHOTOSHOP data, if present - `tifftagPhotoshop`: Buffer containing raw TIFFTAG_PHOTOSHOP data, if present
- `formatMagick`: String containing format for images loaded via *magick - `formatMagick`: String containing format for images loaded via *magick
- `comments`: Array of keyword/text pairs representing PNG text blocks, if present.

View File

@@ -231,7 +231,7 @@ const output = await sharp(input).median(5).toBuffer();
## blur ## blur
> blur([sigma]) ⇒ <code>Sharp</code> > blur([options]) ⇒ <code>Sharp</code>
Blur the image. Blur the image.
@@ -245,9 +245,12 @@ When a `sigma` is provided, performs a slower, more accurate Gaussian blur.
- <code>Error</code> Invalid parameters - <code>Error</code> Invalid parameters
| Param | Type | Description | | Param | Type | Default | Description |
| --- | --- | --- | | --- | --- | --- | --- |
| [sigma] | <code>number</code> | a value between 0.3 and 1000 representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`. | | [options] | <code>Object</code> \| <code>number</code> \| <code>Boolean</code> | | |
| [options.sigma] | <code>number</code> | | a value between 0.3 and 1000 representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`. |
| [options.precision] | <code>string</code> | <code>&quot;&#x27;integer&#x27;&quot;</code> | How accurate the operation should be, one of: integer, float, approximate. |
| [options.minAmplitude] | <code>number</code> | <code>0.2</code> | A value between 0.001 and 1. A smaller value will generate a larger, more accurate mask. |
**Example** **Example**
```js ```js
@@ -580,7 +583,7 @@ Recombine the image with the specified matrix.
| Param | Type | Description | | Param | Type | Description |
| --- | --- | --- | | --- | --- | --- |
| inputMatrix | <code>Array.&lt;Array.&lt;number&gt;&gt;</code> | 3x3 Recombination matrix | | inputMatrix | <code>Array.&lt;Array.&lt;number&gt;&gt;</code> | 3x3 or 4x4 Recombination matrix |
**Example** **Example**
```js ```js

View File

@@ -24,7 +24,7 @@ A `Promise` is returned when `callback` is not provided.
| Param | Type | Description | | Param | Type | Description |
| --- | --- | --- | | --- | --- | --- |
| fileOut | <code>string</code> | the path to write the image data to. | | fileOut | <code>string</code> | the path to write the image data to. |
| [callback] | <code>function</code> | called on completion with two arguments `(err, info)`. `info` contains the output image `format`, `size` (bytes), `width`, `height`, `channels` and `premultiplied` (indicating if premultiplication was used). When using a crop strategy also contains `cropOffsetLeft` and `cropOffsetTop`. When using the attention crop strategy also contains `attentionX` and `attentionY`, the focal point of the cropped region. May also contain `textAutofitDpi` (dpi the font was rendered at) if image was created from text. | | [callback] | <code>function</code> | called on completion with two arguments `(err, info)`. `info` contains the output image `format`, `size` (bytes), `width`, `height`, `channels` and `premultiplied` (indicating if premultiplication was used). When using a crop strategy also contains `cropOffsetLeft` and `cropOffsetTop`. When using the attention crop strategy also contains `attentionX` and `attentionY`, the focal point of the cropped region. Animated output will also contain `pageHeight` and `pages`. May also contain `textAutofitDpi` (dpi the font was rendered at) if image was created from text. |
**Example** **Example**
```js ```js
@@ -59,6 +59,7 @@ See [withMetadata](#withmetadata) for control over this.
- `info` contains the output image `format`, `size` (bytes), `width`, `height`, - `info` contains the output image `format`, `size` (bytes), `width`, `height`,
`channels` and `premultiplied` (indicating if premultiplication was used). `channels` and `premultiplied` (indicating if premultiplication was used).
When using a crop strategy also contains `cropOffsetLeft` and `cropOffsetTop`. When using a crop strategy also contains `cropOffsetLeft` and `cropOffsetTop`.
Animated output will also contain `pageHeight` and `pages`.
May also contain `textAutofitDpi` (dpi the font was rendered at) if image was created from text. May also contain `textAutofitDpi` (dpi the font was rendered at) if image was created from text.
A `Promise` is returned when `callback` is not provided. A `Promise` is returned when `callback` is not provided.

View File

@@ -2,7 +2,40 @@
## v0.33 - *gauge* ## v0.33 - *gauge*
Requires libvips v8.15.2 Requires libvips v8.15.3
### v0.33.5 - TBD
* Upgrade to libvips v8.15.3 for upstream bug fixes.
* Add `pageHeight` and `pages` to response of multi-page output.
[#3411](https://github.com/lovell/sharp/issues/3411)
* Ensure option to force use of a globally-installed libvips works correctly.
[#4111](https://github.com/lovell/sharp/pull/4111)
[@project0](https://github.com/project0)
* Minimise use of `engines` property to improve yarn v1 support.
[#4130](https://github.com/lovell/sharp/issues/4130)
* Ensure `sharp.format.heif` includes only AVIF when using prebuilt binaries.
[#4132](https://github.com/lovell/sharp/issues/4132)
* Add support to recomb operation for 4x4 matrices.
[#4147](https://github.com/lovell/sharp/pull/4147)
[@ton11797](https://github.com/ton11797)
* Expose PNG text chunks as `comments` metadata.
[#4157](https://github.com/lovell/sharp/pull/4157)
[@nkeynes](https://github.com/nkeynes)
* Expose optional `precision` and `minAmplitude` parameters of `blur` operation.
[#4168](https://github.com/lovell/sharp/pull/4168)
[#4172](https://github.com/lovell/sharp/pull/4172)
[@marcosc90](https://github.com/marcosc90)
* Ensure `keepIccProfile` avoids colour transformation where possible.
[#4186](https://github.com/lovell/sharp/issues/4186)
### v0.33.4 - 16th May 2024 ### v0.33.4 - 16th May 2024

View File

@@ -28,7 +28,7 @@ Name: Brandon Aaron
GitHub: https://github.com/brandonaaron GitHub: https://github.com/brandonaaron
Name: Andreas Lind Name: Andreas Lind
GitHub: https://github.com/papandreouGitHub: GitHub: https://github.com/papandreou
Name: Maurus Cuelenaere Name: Maurus Cuelenaere
GitHub: https://github.com/mcuelenaere GitHub: https://github.com/mcuelenaere
@@ -261,7 +261,7 @@ GitHub: https://github.com/brahima
Name: Anton Marsden Name: Anton Marsden
GitHub: https://github.com/antonmarsden GitHub: https://github.com/antonmarsden
Name: Marcos Casagrande Name: Marcos Casagrande
GitHub: https://github.com/marcosc90 GitHub: https://github.com/marcosc90
Name: Emanuel Jöbstl Name: Emanuel Jöbstl
@@ -293,3 +293,12 @@ GitHub: https://github.com/mertalev
Name: Adriaan Meuris Name: Adriaan Meuris
GitHub: https://github.com/adriaanmeuris GitHub: https://github.com/adriaanmeuris
Name: Richard Hillmann
GitHub: https://github.com/project0
Name: Pongsatorn Manusopit
GitHub: https://github.com/ton11797
Name: Nathan Keynes
GitHub: https://github.com/nkeynes

View File

@@ -10,7 +10,7 @@
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; connect-src 'self'; object-src 'none'; <meta http-equiv="Content-Security-Policy" content="default-src 'self'; connect-src 'self'; object-src 'none';
style-src 'unsafe-inline'; style-src 'unsafe-inline';
img-src 'unsafe-inline' data: https://cdn.jsdelivr.net/gh/lovell/; 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;"> 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/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="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="author" href="/humans.txt" type="text/plain">

View File

@@ -20,11 +20,6 @@ pnpm add sharp
yarn add sharp yarn add sharp
``` ```
```sh
# yarn v1 (maintenance mode)
yarn add sharp --ignore-engines
```
```sh ```sh
bun add sharp bun add sharp
``` ```
@@ -92,7 +87,7 @@ Use the [supportedArchitectures](https://pnpm.io/package_json#pnpmsupportedarchi
## Custom libvips ## Custom libvips
To use a custom, globally-installed version of libvips instead of the provided binaries, To use a custom, globally-installed version of libvips instead of the provided binaries,
make sure it is at least the version listed under `engines.libvips` in the `package.json` file make sure it is at least the version listed under `config.libvips` in the `package.json` file
and that it can be located using `pkg-config --modversion vips-cpp`. and that it can be located using `pkg-config --modversion vips-cpp`.
For help compiling libvips and its dependencies, please see For help compiling libvips and its dependencies, please see

File diff suppressed because one or more lines are too long

View File

@@ -18,7 +18,7 @@ try {
} }
try { try {
const gyp = require('node-gyp'); const gyp = require('node-gyp');
log(`Found node-gyp version ${gyp().version}`); log(`Found node-gyp ${gyp().version}`);
} catch (err) { } catch (err) {
log('Please add node-gyp to your dependencies'); log('Please add node-gyp to your dependencies');
return; return;
@@ -30,7 +30,7 @@ try {
} }
}; };
if (useGlobalLibvips()) { if (useGlobalLibvips(log)) {
buildFromSource(`Detected globally-installed libvips v${globalLibvipsVersion()}`); buildFromSource(`Detected globally-installed libvips v${globalLibvipsVersion()}`);
} else if (process.env.npm_config_build_from_source) { } else if (process.env.npm_config_build_from_source) {
buildFromSource('Detected --build-from-source flag'); buildFromSource('Detected --build-from-source flag');

View File

@@ -226,6 +226,8 @@ const Sharp = function (input, options) {
negateAlpha: true, negateAlpha: true,
medianSize: 0, medianSize: 0,
blurSigma: 0, blurSigma: 0,
precision: 'integer',
minAmpl: 0.2,
sharpenSigma: 0, sharpenSigma: 0,
sharpenM1: 1, sharpenM1: 1,
sharpenM2: 2, sharpenM2: 2,

25
lib/index.d.ts vendored
View File

@@ -464,7 +464,7 @@ declare namespace sharp {
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
* @returns A sharp instance that can be used to chain operations * @returns A sharp instance that can be used to chain operations
*/ */
blur(sigma?: number | boolean): Sharp; blur(sigma?: number | boolean | BlurOptions): Sharp;
/** /**
* Merge alpha transparency channel, if any, with background. * Merge alpha transparency channel, if any, with background.
@@ -571,11 +571,11 @@ declare namespace sharp {
/** /**
* Recomb the image with the specified matrix. * Recomb the image with the specified matrix.
* @param inputMatrix 3x3 Recombination matrix * @param inputMatrix 3x3 Recombination matrix or 4x4 Recombination matrix
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
* @returns A sharp instance that can be used to chain operations * @returns A sharp instance that can be used to chain operations
*/ */
recomb(inputMatrix: Matrix3x3): Sharp; recomb(inputMatrix: Matrix3x3 | Matrix4x4): Sharp;
/** /**
* Transforms the image using brightness, saturation, hue rotation and lightness. * Transforms the image using brightness, saturation, hue rotation and lightness.
@@ -1108,6 +1108,8 @@ declare namespace sharp {
resolutionUnit?: 'inch' | 'cm' | undefined; resolutionUnit?: 'inch' | 'cm' | undefined;
/** String containing format for images loaded via *magick */ /** String containing format for images loaded via *magick */
formatMagick?: string | undefined; formatMagick?: string | undefined;
/** Array of keyword/text pairs representing PNG text blocks, if present. */
comments?: CommentsMetadata[] | undefined;
} }
interface LevelMetadata { interface LevelMetadata {
@@ -1115,6 +1117,11 @@ declare namespace sharp {
height: number; height: number;
} }
interface CommentsMetadata {
keyword: string;
text: string;
}
interface Stats { interface Stats {
/** Array of channel statistics for each channel in the image. */ /** Array of channel statistics for each channel in the image. */
channels: ChannelStats[]; channels: ChannelStats[];
@@ -1335,6 +1342,17 @@ declare namespace sharp {
background?: Color | undefined; background?: Color | undefined;
} }
type Precision = 'integer' | 'float' | 'approximate';
interface BlurOptions {
/** A value between 0.3 and 1000 representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2` */
sigma: number;
/** A value between 0.001 and 1. A smaller value will generate a larger, more accurate mask. */
minAmplitude?: number;
/** How accurate the operation should be, one of: integer, float, approximate. (optional, default "integer") */
precision?: Precision | undefined;
}
interface FlattenOptions { interface FlattenOptions {
/** background colour, parsed by the color module, defaults to black. (optional, default {r:0,g:0,b:0}) */ /** background colour, parsed by the color module, defaults to black. (optional, default {r:0,g:0,b:0}) */
background?: Color | undefined; background?: Color | undefined;
@@ -1730,6 +1748,7 @@ declare namespace sharp {
type Matrix2x2 = [[number, number], [number, number]]; type Matrix2x2 = [[number, number], [number, number]];
type Matrix3x3 = [[number, number, number], [number, number, number], [number, number, number]]; type Matrix3x3 = [[number, number, number], [number, number, number], [number, number, number]];
type Matrix4x4 = [[number, number, number, number], [number, number, number, number], [number, number, number, number], [number, number, number, number]];
} }
export = sharp; export = sharp;

View File

@@ -450,6 +450,7 @@ function _isStreamInput () {
* - `xmp`: Buffer containing raw XMP data, if present * - `xmp`: Buffer containing raw XMP data, if present
* - `tifftagPhotoshop`: Buffer containing raw TIFFTAG_PHOTOSHOP data, if present * - `tifftagPhotoshop`: Buffer containing raw TIFFTAG_PHOTOSHOP data, if present
* - `formatMagick`: String containing format for images loaded via *magick * - `formatMagick`: String containing format for images loaded via *magick
* - `comments`: Array of keyword/text pairs representing PNG text blocks, if present.
* *
* @example * @example
* const metadata = await sharp(input).metadata(); * const metadata = await sharp(input).metadata();

View File

@@ -10,10 +10,10 @@ const semverGreaterThanOrEqualTo = require('semver/functions/gte');
const semverSatisfies = require('semver/functions/satisfies'); const semverSatisfies = require('semver/functions/satisfies');
const detectLibc = require('detect-libc'); const detectLibc = require('detect-libc');
const { engines, optionalDependencies } = require('../package.json'); const { config, engines, optionalDependencies } = require('../package.json');
const minimumLibvipsVersionLabelled = process.env.npm_package_config_libvips || /* istanbul ignore next */ const minimumLibvipsVersionLabelled = process.env.npm_package_config_libvips || /* istanbul ignore next */
engines.libvips; config.libvips;
const minimumLibvipsVersion = semverCoerce(minimumLibvipsVersionLabelled).version; const minimumLibvipsVersion = semverCoerce(minimumLibvipsVersionLabelled).version;
const prebuiltPlatforms = [ const prebuiltPlatforms = [
@@ -162,21 +162,23 @@ const pkgConfigPath = () => {
} }
}; };
const skipSearch = (status, reason) => { const skipSearch = (status, reason, logger) => {
log(`Detected ${reason}, skipping search for globally-installed libvips`); if (logger) {
logger(`Detected ${reason}, skipping search for globally-installed libvips`);
}
return status; return status;
}; };
const useGlobalLibvips = () => { const useGlobalLibvips = (logger) => {
if (Boolean(process.env.SHARP_IGNORE_GLOBAL_LIBVIPS) === true) { if (Boolean(process.env.SHARP_IGNORE_GLOBAL_LIBVIPS) === true) {
return skipSearch(false, 'SHARP_IGNORE_GLOBAL_LIBVIPS'); return skipSearch(false, 'SHARP_IGNORE_GLOBAL_LIBVIPS', logger);
} }
if (Boolean(process.env.SHARP_FORCE_GLOBAL_LIBVIPS) === true) { if (Boolean(process.env.SHARP_FORCE_GLOBAL_LIBVIPS) === true) {
return skipSearch(true, 'SHARP_FORCE_GLOBAL_LIBVIPS'); return skipSearch(true, 'SHARP_FORCE_GLOBAL_LIBVIPS', logger);
} }
/* istanbul ignore next */ /* istanbul ignore next */
if (isRosetta()) { if (isRosetta()) {
return skipSearch(false, 'Rosetta'); return skipSearch(false, 'Rosetta', logger);
} }
const globalVipsVersion = globalLibvipsVersion(); const globalVipsVersion = globalLibvipsVersion();
return !!globalVipsVersion && /* istanbul ignore next */ return !!globalVipsVersion && /* istanbul ignore next */

View File

@@ -6,6 +6,17 @@
const color = require('color'); const color = require('color');
const is = require('./is'); const is = require('./is');
/**
* How accurate an operation should be.
* @member
* @private
*/
const vipsPrecision = {
integer: 'integer',
float: 'float',
approximate: 'approximate'
};
/** /**
* Rotate the output image by either an explicit angle * Rotate the output image by either an explicit angle
* or auto-orient based on the EXIF `Orientation` tag. * or auto-orient based on the EXIF `Orientation` tag.
@@ -367,23 +378,51 @@ function median (size) {
* .blur(5) * .blur(5)
* .toBuffer(); * .toBuffer();
* *
* @param {number} [sigma] a value between 0.3 and 1000 representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`. * @param {Object|number|Boolean} [options]
* @param {number} [options.sigma] a value between 0.3 and 1000 representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
* @param {string} [options.precision='integer'] How accurate the operation should be, one of: integer, float, approximate.
* @param {number} [options.minAmplitude=0.2] A value between 0.001 and 1. A smaller value will generate a larger, more accurate mask.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
function blur (sigma) { function blur (options) {
if (!is.defined(sigma)) { let sigma;
if (is.number(options)) {
sigma = options;
} else if (is.plainObject(options)) {
if (!is.number(options.sigma)) {
throw is.invalidParameterError('options.sigma', 'number between 0.3 and 1000', sigma);
}
sigma = options.sigma;
if ('precision' in options) {
if (is.string(vipsPrecision[options.precision])) {
this.options.precision = vipsPrecision[options.precision];
} else {
throw is.invalidParameterError('precision', 'one of: integer, float, approximate', options.precision);
}
}
if ('minAmplitude' in options) {
if (is.number(options.minAmplitude) && is.inRange(options.minAmplitude, 0.001, 1)) {
this.options.minAmpl = options.minAmplitude;
} else {
throw is.invalidParameterError('minAmplitude', 'number between 0.001 and 1', options.minAmplitude);
}
}
}
if (!is.defined(options)) {
// No arguments: default to mild blur // No arguments: default to mild blur
this.options.blurSigma = -1; this.options.blurSigma = -1;
} else if (is.bool(sigma)) { } else if (is.bool(options)) {
// Boolean argument: apply mild blur? // Boolean argument: apply mild blur?
this.options.blurSigma = sigma ? -1 : 0; this.options.blurSigma = options ? -1 : 0;
} else if (is.number(sigma) && is.inRange(sigma, 0.3, 1000)) { } else if (is.number(sigma) && is.inRange(sigma, 0.3, 1000)) {
// Numeric argument: specific sigma // Numeric argument: specific sigma
this.options.blurSigma = sigma; this.options.blurSigma = sigma;
} else { } else {
throw is.invalidParameterError('sigma', 'number between 0.3 and 1000', sigma); throw is.invalidParameterError('sigma', 'number between 0.3 and 1000', sigma);
} }
return this; return this;
} }
@@ -787,24 +826,22 @@ function linear (a, b) {
* // With this example input, a sepia filter has been applied * // With this example input, a sepia filter has been applied
* }); * });
* *
* @param {Array<Array<number>>} inputMatrix - 3x3 Recombination matrix * @param {Array<Array<number>>} inputMatrix - 3x3 or 4x4 Recombination matrix
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
function recomb (inputMatrix) { function recomb (inputMatrix) {
if (!Array.isArray(inputMatrix) || inputMatrix.length !== 3 || if (!Array.isArray(inputMatrix)) {
inputMatrix[0].length !== 3 || throw is.invalidParameterError('inputMatrix', 'array', inputMatrix);
inputMatrix[1].length !== 3 ||
inputMatrix[2].length !== 3
) {
// must pass in a kernel
throw new Error('Invalid recombination matrix');
} }
this.options.recombMatrix = [ if (inputMatrix.length !== 3 && inputMatrix.length !== 4) {
inputMatrix[0][0], inputMatrix[0][1], inputMatrix[0][2], throw is.invalidParameterError('inputMatrix', '3x3 or 4x4 array', inputMatrix.length);
inputMatrix[1][0], inputMatrix[1][1], inputMatrix[1][2], }
inputMatrix[2][0], inputMatrix[2][1], inputMatrix[2][2] const recombMatrix = inputMatrix.flat().map(Number);
].map(Number); if (recombMatrix.length !== 9 && recombMatrix.length !== 16) {
throw is.invalidParameterError('inputMatrix', 'cardinality of 9 or 16', recombMatrix.length);
}
this.options.recombMatrix = recombMatrix;
return this; return this;
} }

View File

@@ -65,6 +65,7 @@ const bitdepthFromColourCount = (colours) => 1 << 31 - Math.clz32(Math.ceil(Math
* `channels` and `premultiplied` (indicating if premultiplication was used). * `channels` and `premultiplied` (indicating if premultiplication was used).
* When using a crop strategy also contains `cropOffsetLeft` and `cropOffsetTop`. * When using a crop strategy also contains `cropOffsetLeft` and `cropOffsetTop`.
* When using the attention crop strategy also contains `attentionX` and `attentionY`, the focal point of the cropped region. * When using the attention crop strategy also contains `attentionX` and `attentionY`, the focal point of the cropped region.
* Animated output will also contain `pageHeight` and `pages`.
* May also contain `textAutofitDpi` (dpi the font was rendered at) if image was created from text. * May also contain `textAutofitDpi` (dpi the font was rendered at) if image was created from text.
* @returns {Promise<Object>} - when no callback is provided * @returns {Promise<Object>} - when no callback is provided
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
@@ -109,6 +110,7 @@ function toFile (fileOut, callback) {
* - `info` contains the output image `format`, `size` (bytes), `width`, `height`, * - `info` contains the output image `format`, `size` (bytes), `width`, `height`,
* `channels` and `premultiplied` (indicating if premultiplication was used). * `channels` and `premultiplied` (indicating if premultiplication was used).
* When using a crop strategy also contains `cropOffsetLeft` and `cropOffsetTop`. * When using a crop strategy also contains `cropOffsetLeft` and `cropOffsetTop`.
* Animated output will also contain `pageHeight` and `pages`.
* May also contain `textAutofitDpi` (dpi the font was rendered at) if image was created from text. * May also contain `textAutofitDpi` (dpi the font was rendered at) if image was created from text.
* *
* A `Promise` is returned when `callback` is not provided. * A `Promise` is returned when `callback` is not provided.

View File

@@ -57,7 +57,6 @@ if (sharp) {
help.push( help.push(
'- Ensure optional dependencies can be installed:', '- Ensure optional dependencies can be installed:',
' npm install --include=optional sharp', ' npm install --include=optional sharp',
' yarn add sharp --ignore-engines',
'- Ensure your package manager supports multi-platform installation:', '- Ensure your package manager supports multi-platform installation:',
' See https://sharp.pixelplumbing.com/install#cross-platform', ' See https://sharp.pixelplumbing.com/install#cross-platform',
'- Add platform-specific dependencies:', '- Add platform-specific dependencies:',
@@ -73,9 +72,9 @@ if (sharp) {
} }
if (isLinux && /(symbol not found|CXXABI_)/i.test(messages)) { if (isLinux && /(symbol not found|CXXABI_)/i.test(messages)) {
try { try {
const { engines } = require(`@img/sharp-libvips-${runtimePlatform}/package`); const { config } = require(`@img/sharp-libvips-${runtimePlatform}/package`);
const libcFound = `${familySync()} ${versionSync()}`; const libcFound = `${familySync()} ${versionSync()}`;
const libcRequires = `${engines.musl ? 'musl' : 'glibc'} ${engines.musl || engines.glibc}`; const libcRequires = `${config.musl ? 'musl' : 'glibc'} ${config.musl || config.glibc}`;
help.push( help.push(
'- Update your OS:', '- Update your OS:',
` Found ${libcFound}`, ` Found ${libcFound}`,

View File

@@ -75,6 +75,13 @@ if (!libvipsVersion.isGlobal) {
} }
versions.sharp = require('../package.json').version; versions.sharp = require('../package.json').version;
/* istanbul ignore next */
if (versions.heif && format.heif) {
// Prebuilt binaries provide AV1
format.heif.input.fileSuffix = ['.avif'];
format.heif.output.alias = ['avif'];
}
/** /**
* Gets or, when options are provided, sets the limits of _libvips'_ operation cache. * Gets or, when options are provided, sets the limits of _libvips'_ operation cache.
* Existing entries in the cache will be trimmed after any change in limits. * Existing entries in the cache will be trimmed after any change in limits.

View File

@@ -1,6 +1,6 @@
{ {
"name": "@img/sharp-darwin-arm64", "name": "@img/sharp-darwin-arm64",
"version": "0.33.4", "version": "0.33.5-rc.1",
"description": "Prebuilt sharp for use with macOS 64-bit ARM", "description": "Prebuilt sharp for use with macOS 64-bit ARM",
"author": "Lovell Fuller <npm@lovell.info>", "author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://sharp.pixelplumbing.com", "homepage": "https://sharp.pixelplumbing.com",
@@ -15,7 +15,7 @@
}, },
"preferUnplugged": true, "preferUnplugged": true,
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-darwin-arm64": "1.0.2" "@img/sharp-libvips-darwin-arm64": "1.0.4"
}, },
"files": [ "files": [
"lib" "lib"
@@ -29,11 +29,7 @@
"./package": "./package.json" "./package": "./package.json"
}, },
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0", "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
"npm": ">=9.6.5",
"yarn": ">=3.2.0",
"pnpm": ">=7.1.0",
"glibc": ">=2.26"
}, },
"os": [ "os": [
"darwin" "darwin"

View File

@@ -1,6 +1,6 @@
{ {
"name": "@img/sharp-darwin-x64", "name": "@img/sharp-darwin-x64",
"version": "0.33.4", "version": "0.33.5-rc.1",
"description": "Prebuilt sharp for use with macOS x64", "description": "Prebuilt sharp for use with macOS x64",
"author": "Lovell Fuller <npm@lovell.info>", "author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://sharp.pixelplumbing.com", "homepage": "https://sharp.pixelplumbing.com",
@@ -15,7 +15,7 @@
}, },
"preferUnplugged": true, "preferUnplugged": true,
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-darwin-x64": "1.0.2" "@img/sharp-libvips-darwin-x64": "1.0.4"
}, },
"files": [ "files": [
"lib" "lib"
@@ -29,11 +29,7 @@
"./package": "./package.json" "./package": "./package.json"
}, },
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0", "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
"npm": ">=9.6.5",
"yarn": ">=3.2.0",
"pnpm": ">=7.1.0",
"glibc": ">=2.26"
}, },
"os": [ "os": [
"darwin" "darwin"

View File

@@ -1,6 +1,6 @@
{ {
"name": "@img/sharp-linux-arm", "name": "@img/sharp-linux-arm",
"version": "0.33.4", "version": "0.33.5-rc.1",
"description": "Prebuilt sharp for use with Linux (glibc) ARM (32-bit)", "description": "Prebuilt sharp for use with Linux (glibc) ARM (32-bit)",
"author": "Lovell Fuller <npm@lovell.info>", "author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://sharp.pixelplumbing.com", "homepage": "https://sharp.pixelplumbing.com",
@@ -15,7 +15,7 @@
}, },
"preferUnplugged": true, "preferUnplugged": true,
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linux-arm": "1.0.2" "@img/sharp-libvips-linux-arm": "1.0.5"
}, },
"files": [ "files": [
"lib" "lib"
@@ -29,10 +29,9 @@
"./package": "./package.json" "./package": "./package.json"
}, },
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0", "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
"npm": ">=9.6.5", },
"yarn": ">=3.2.0", "config": {
"pnpm": ">=7.1.0",
"glibc": ">=2.28" "glibc": ">=2.28"
}, },
"os": [ "os": [

View File

@@ -1,6 +1,6 @@
{ {
"name": "@img/sharp-linux-arm64", "name": "@img/sharp-linux-arm64",
"version": "0.33.4", "version": "0.33.5-rc.1",
"description": "Prebuilt sharp for use with Linux (glibc) 64-bit ARM", "description": "Prebuilt sharp for use with Linux (glibc) 64-bit ARM",
"author": "Lovell Fuller <npm@lovell.info>", "author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://sharp.pixelplumbing.com", "homepage": "https://sharp.pixelplumbing.com",
@@ -15,7 +15,7 @@
}, },
"preferUnplugged": true, "preferUnplugged": true,
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linux-arm64": "1.0.2" "@img/sharp-libvips-linux-arm64": "1.0.4"
}, },
"files": [ "files": [
"lib" "lib"
@@ -29,10 +29,9 @@
"./package": "./package.json" "./package": "./package.json"
}, },
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0", "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
"npm": ">=9.6.5", },
"yarn": ">=3.2.0", "config": {
"pnpm": ">=7.1.0",
"glibc": ">=2.26" "glibc": ">=2.26"
}, },
"os": [ "os": [

View File

@@ -1,6 +1,6 @@
{ {
"name": "@img/sharp-linux-s390x", "name": "@img/sharp-linux-s390x",
"version": "0.33.4", "version": "0.33.5-rc.1",
"description": "Prebuilt sharp for use with Linux (glibc) s390x", "description": "Prebuilt sharp for use with Linux (glibc) s390x",
"author": "Lovell Fuller <npm@lovell.info>", "author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://sharp.pixelplumbing.com", "homepage": "https://sharp.pixelplumbing.com",
@@ -15,7 +15,7 @@
}, },
"preferUnplugged": true, "preferUnplugged": true,
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linux-s390x": "1.0.2" "@img/sharp-libvips-linux-s390x": "1.0.4"
}, },
"files": [ "files": [
"lib" "lib"
@@ -29,10 +29,9 @@
"./package": "./package.json" "./package": "./package.json"
}, },
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0", "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
"npm": ">=9.6.5", },
"yarn": ">=3.2.0", "config": {
"pnpm": ">=7.1.0",
"glibc": ">=2.31" "glibc": ">=2.31"
}, },
"os": [ "os": [

View File

@@ -1,6 +1,6 @@
{ {
"name": "@img/sharp-linux-x64", "name": "@img/sharp-linux-x64",
"version": "0.33.4", "version": "0.33.5-rc.1",
"description": "Prebuilt sharp for use with Linux (glibc) x64", "description": "Prebuilt sharp for use with Linux (glibc) x64",
"author": "Lovell Fuller <npm@lovell.info>", "author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://sharp.pixelplumbing.com", "homepage": "https://sharp.pixelplumbing.com",
@@ -15,7 +15,7 @@
}, },
"preferUnplugged": true, "preferUnplugged": true,
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linux-x64": "1.0.2" "@img/sharp-libvips-linux-x64": "1.0.4"
}, },
"files": [ "files": [
"lib" "lib"
@@ -29,10 +29,9 @@
"./package": "./package.json" "./package": "./package.json"
}, },
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0", "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
"npm": ">=9.6.5", },
"yarn": ">=3.2.0", "config": {
"pnpm": ">=7.1.0",
"glibc": ">=2.26" "glibc": ">=2.26"
}, },
"os": [ "os": [

View File

@@ -1,6 +1,6 @@
{ {
"name": "@img/sharp-linuxmusl-arm64", "name": "@img/sharp-linuxmusl-arm64",
"version": "0.33.4", "version": "0.33.5-rc.1",
"description": "Prebuilt sharp for use with Linux (musl) 64-bit ARM", "description": "Prebuilt sharp for use with Linux (musl) 64-bit ARM",
"author": "Lovell Fuller <npm@lovell.info>", "author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://sharp.pixelplumbing.com", "homepage": "https://sharp.pixelplumbing.com",
@@ -15,7 +15,7 @@
}, },
"preferUnplugged": true, "preferUnplugged": true,
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linuxmusl-arm64": "1.0.2" "@img/sharp-libvips-linuxmusl-arm64": "1.0.4"
}, },
"files": [ "files": [
"lib" "lib"
@@ -29,10 +29,9 @@
"./package": "./package.json" "./package": "./package.json"
}, },
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0", "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
"npm": ">=9.6.5", },
"yarn": ">=3.2.0", "config": {
"pnpm": ">=7.1.0",
"musl": ">=1.2.2" "musl": ">=1.2.2"
}, },
"os": [ "os": [

View File

@@ -1,6 +1,6 @@
{ {
"name": "@img/sharp-linuxmusl-x64", "name": "@img/sharp-linuxmusl-x64",
"version": "0.33.4", "version": "0.33.5-rc.1",
"description": "Prebuilt sharp for use with Linux (musl) x64", "description": "Prebuilt sharp for use with Linux (musl) x64",
"author": "Lovell Fuller <npm@lovell.info>", "author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://sharp.pixelplumbing.com", "homepage": "https://sharp.pixelplumbing.com",
@@ -15,7 +15,7 @@
}, },
"preferUnplugged": true, "preferUnplugged": true,
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-libvips-linuxmusl-x64": "1.0.2" "@img/sharp-libvips-linuxmusl-x64": "1.0.4"
}, },
"files": [ "files": [
"lib" "lib"
@@ -29,10 +29,9 @@
"./package": "./package.json" "./package": "./package.json"
}, },
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0", "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
"npm": ">=9.6.5", },
"yarn": ">=3.2.0", "config": {
"pnpm": ">=7.1.0",
"musl": ">=1.2.2" "musl": ">=1.2.2"
}, },
"os": [ "os": [

View File

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

View File

@@ -1,6 +1,6 @@
{ {
"name": "@img/sharp-wasm32", "name": "@img/sharp-wasm32",
"version": "0.33.4", "version": "0.33.5-rc.1",
"description": "Prebuilt sharp for use with wasm32", "description": "Prebuilt sharp for use with wasm32",
"author": "Lovell Fuller <npm@lovell.info>", "author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://sharp.pixelplumbing.com", "homepage": "https://sharp.pixelplumbing.com",
@@ -28,13 +28,10 @@
"./versions": "./versions.json" "./versions": "./versions.json"
}, },
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0", "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
"npm": ">=9.6.5",
"yarn": ">=3.2.0",
"pnpm": ">=7.1.0"
}, },
"dependencies": { "dependencies": {
"@emnapi/runtime": "^1.1.1" "@emnapi/runtime": "^1.2.0"
}, },
"cpu": [ "cpu": [
"wasm32" "wasm32"

View File

@@ -1,6 +1,6 @@
{ {
"name": "@img/sharp-win32-ia32", "name": "@img/sharp-win32-ia32",
"version": "0.33.4", "version": "0.33.5-rc.1",
"description": "Prebuilt sharp for use with Windows x86 (32-bit)", "description": "Prebuilt sharp for use with Windows x86 (32-bit)",
"author": "Lovell Fuller <npm@lovell.info>", "author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://sharp.pixelplumbing.com", "homepage": "https://sharp.pixelplumbing.com",
@@ -28,10 +28,7 @@
"./versions": "./versions.json" "./versions": "./versions.json"
}, },
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0", "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
"npm": ">=9.6.5",
"yarn": ">=3.2.0",
"pnpm": ">=7.1.0"
}, },
"os": [ "os": [
"win32" "win32"

View File

@@ -1,6 +1,6 @@
{ {
"name": "@img/sharp-win32-x64", "name": "@img/sharp-win32-x64",
"version": "0.33.4", "version": "0.33.5-rc.1",
"description": "Prebuilt sharp for use with Windows x64", "description": "Prebuilt sharp for use with Windows x64",
"author": "Lovell Fuller <npm@lovell.info>", "author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://sharp.pixelplumbing.com", "homepage": "https://sharp.pixelplumbing.com",
@@ -28,10 +28,7 @@
"./versions": "./versions.json" "./versions": "./versions.json"
}, },
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0", "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
"npm": ">=9.6.5",
"yarn": ">=3.2.0",
"pnpm": ">=7.1.0"
}, },
"os": [ "os": [
"win32" "win32"

View File

@@ -1,7 +1,7 @@
{ {
"name": "sharp", "name": "sharp",
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, GIF, AVIF and TIFF images", "description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, GIF, AVIF and TIFF images",
"version": "0.33.4", "version": "0.33.5-rc.1",
"author": "Lovell Fuller <npm@lovell.info>", "author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://sharp.pixelplumbing.com", "homepage": "https://sharp.pixelplumbing.com",
"contributors": [ "contributors": [
@@ -88,7 +88,8 @@
"Mart Jansink <m.jansink@gmail.com>", "Mart Jansink <m.jansink@gmail.com>",
"Lachlan Newman <lachnewman007@gmail.com>", "Lachlan Newman <lachnewman007@gmail.com>",
"Dennis Beatty <dennis@dcbeatty.com>", "Dennis Beatty <dennis@dcbeatty.com>",
"Ingvar Stepanyan <me@rreverser.com>" "Ingvar Stepanyan <me@rreverser.com>",
"Don Denton <don@happycollision.com>"
], ],
"scripts": { "scripts": {
"install": "node install/check", "install": "node install/check",
@@ -138,56 +139,58 @@
"dependencies": { "dependencies": {
"color": "^4.2.3", "color": "^4.2.3",
"detect-libc": "^2.0.3", "detect-libc": "^2.0.3",
"semver": "^7.6.0" "semver": "^7.6.3"
}, },
"optionalDependencies": { "optionalDependencies": {
"@img/sharp-darwin-arm64": "0.33.4", "@img/sharp-darwin-arm64": "0.33.5-rc.1",
"@img/sharp-darwin-x64": "0.33.4", "@img/sharp-darwin-x64": "0.33.5-rc.1",
"@img/sharp-libvips-darwin-arm64": "1.0.2", "@img/sharp-libvips-darwin-arm64": "1.0.4",
"@img/sharp-libvips-darwin-x64": "1.0.2", "@img/sharp-libvips-darwin-x64": "1.0.4",
"@img/sharp-libvips-linux-arm": "1.0.2", "@img/sharp-libvips-linux-arm": "1.0.5",
"@img/sharp-libvips-linux-arm64": "1.0.2", "@img/sharp-libvips-linux-arm64": "1.0.4",
"@img/sharp-libvips-linux-s390x": "1.0.2", "@img/sharp-libvips-linux-s390x": "1.0.4",
"@img/sharp-libvips-linux-x64": "1.0.2", "@img/sharp-libvips-linux-x64": "1.0.4",
"@img/sharp-libvips-linuxmusl-arm64": "1.0.2", "@img/sharp-libvips-linuxmusl-arm64": "1.0.4",
"@img/sharp-libvips-linuxmusl-x64": "1.0.2", "@img/sharp-libvips-linuxmusl-x64": "1.0.4",
"@img/sharp-linux-arm": "0.33.4", "@img/sharp-linux-arm": "0.33.5-rc.1",
"@img/sharp-linux-arm64": "0.33.4", "@img/sharp-linux-arm64": "0.33.5-rc.1",
"@img/sharp-linux-s390x": "0.33.4", "@img/sharp-linux-s390x": "0.33.5-rc.1",
"@img/sharp-linux-x64": "0.33.4", "@img/sharp-linux-x64": "0.33.5-rc.1",
"@img/sharp-linuxmusl-arm64": "0.33.4", "@img/sharp-linuxmusl-arm64": "0.33.5-rc.1",
"@img/sharp-linuxmusl-x64": "0.33.4", "@img/sharp-linuxmusl-x64": "0.33.5-rc.1",
"@img/sharp-wasm32": "0.33.4", "@img/sharp-wasm32": "0.33.5-rc.1",
"@img/sharp-win32-ia32": "0.33.4", "@img/sharp-win32-ia32": "0.33.5-rc.1",
"@img/sharp-win32-x64": "0.33.4" "@img/sharp-win32-x64": "0.33.5-rc.1"
}, },
"devDependencies": { "devDependencies": {
"@emnapi/runtime": "^1.1.1", "@emnapi/runtime": "^1.2.0",
"@img/sharp-libvips-dev": "1.0.2", "@img/sharp-libvips-dev": "1.0.4",
"@img/sharp-libvips-dev-wasm32": "1.0.3", "@img/sharp-libvips-dev-wasm32": "1.0.5",
"@img/sharp-libvips-win32-ia32": "1.0.2", "@img/sharp-libvips-win32-ia32": "1.0.4",
"@img/sharp-libvips-win32-x64": "1.0.2", "@img/sharp-libvips-win32-x64": "1.0.4",
"@types/node": "*", "@types/node": "*",
"async": "^3.2.5", "async": "^3.2.5",
"cc": "^3.0.1", "cc": "^3.0.1",
"emnapi": "^1.1.1", "emnapi": "^1.2.0",
"exif-reader": "^2.0.1", "exif-reader": "^2.0.1",
"extract-zip": "^2.0.1", "extract-zip": "^2.0.1",
"icc": "^3.0.0", "icc": "^3.0.0",
"jsdoc-to-markdown": "^8.0.1", "jsdoc-to-markdown": "^8.0.3",
"license-checker": "^25.0.1", "license-checker": "^25.0.1",
"mocha": "^10.4.0", "mocha": "^10.7.3",
"node-addon-api": "^8.0.0", "node-addon-api": "^8.1.0",
"nyc": "^15.1.0", "nyc": "^17.0.0",
"prebuild": "^13.0.1", "prebuild": "^13.0.1",
"semistandard": "^17.0.0", "semistandard": "^17.0.0",
"tar-fs": "^3.0.6", "tar-fs": "^3.0.6",
"tsd": "^0.31.0" "tsd": "^0.31.1"
}, },
"license": "Apache-2.0", "license": "Apache-2.0",
"engines": { "engines": {
"node": "^18.17.0 || ^20.3.0 || >=21.0.0", "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
"libvips": ">=8.15.2" },
"config": {
"libvips": ">=8.15.3"
}, },
"funding": { "funding": {
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"

View File

@@ -192,7 +192,7 @@
'-Oz', '-Oz',
'-sALLOW_MEMORY_GROWTH', '-sALLOW_MEMORY_GROWTH',
'-sENVIRONMENT=node', '-sENVIRONMENT=node',
'-sEXPORTED_FUNCTIONS=["_vips_shutdown", "_uv_library_shutdown"]', '-sEXPORTED_FUNCTIONS=["emnapiInit", "_vips_shutdown", "_uv_library_shutdown"]',
'-sNODERAWFS', '-sNODERAWFS',
'-sTEXTDECODER=0', '-sTEXTDECODER=0',
'-sWASM_ASYNC_COMPILATION=0', '-sWASM_ASYNC_COMPILATION=0',

View File

@@ -16,8 +16,8 @@
#if (VIPS_MAJOR_VERSION < 8) || \ #if (VIPS_MAJOR_VERSION < 8) || \
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 15) || \ (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 15) || \
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 15 && VIPS_MICRO_VERSION < 2) (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 15 && VIPS_MICRO_VERSION < 3)
#error "libvips version 8.15.2+ is required - please see https://sharp.pixelplumbing.com/install" #error "libvips version 8.15.3+ is required - please see https://sharp.pixelplumbing.com/install"
#endif #endif
#if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6))) #if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6)))

View File

@@ -9,8 +9,7 @@
'default_configuration': 'Release', 'default_configuration': 'Release',
'type': 'executable', 'type': 'executable',
'cflags': [ 'cflags': [
'-pthread', '-pthread'
'-sDEFAULT_TO_CXX=0'
], ],
'cflags_cc': [ 'cflags_cc': [
'-pthread' '-pthread'
@@ -19,6 +18,7 @@
'--js-library=<!(node -p "require(\'emnapi\').js_library")', '--js-library=<!(node -p "require(\'emnapi\').js_library")',
'-sAUTO_JS_LIBRARIES=0', '-sAUTO_JS_LIBRARIES=0',
'-sAUTO_NATIVE_LIBRARIES=0', '-sAUTO_NATIVE_LIBRARIES=0',
'-sDEFAULT_TO_CXX=0',
'-sNODEJS_CATCH_EXIT=0', '-sNODEJS_CATCH_EXIT=0',
'-sNODEJS_CATCH_REJECTION=0' '-sNODEJS_CATCH_REJECTION=0'
], ],
@@ -28,7 +28,7 @@
'EMNAPI_WORKER_POOL_SIZE=1' 'EMNAPI_WORKER_POOL_SIZE=1'
], ],
'include_dirs': [ 'include_dirs': [
'<!(node -p "require(\'emnapi\').include")' '<!(node -p "require(\'emnapi\').include_dir")'
], ],
'sources': [ 'sources': [
'<!@(node -p "require(\'emnapi\').sources.map(x => JSON.stringify(path.relative(process.cwd(), x))).join(\' \')")' '<!@(node -p "require(\'emnapi\').sources.map(x => JSON.stringify(path.relative(process.cwd(), x))).join(\' \')")'

View File

@@ -10,6 +10,8 @@
#include "common.h" #include "common.h"
#include "metadata.h" #include "metadata.h"
static void* readPNGComment(VipsImage *image, const char *field, GValue *value, void *p);
class MetadataWorker : public Napi::AsyncWorker { class MetadataWorker : public Napi::AsyncWorker {
public: public:
MetadataWorker(Napi::Function callback, MetadataBaton *baton, Napi::Function debuglog) : MetadataWorker(Napi::Function callback, MetadataBaton *baton, Napi::Function debuglog) :
@@ -131,6 +133,8 @@ class MetadataWorker : public Napi::AsyncWorker {
memcpy(baton->tifftagPhotoshop, tifftagPhotoshop, tifftagPhotoshopLength); memcpy(baton->tifftagPhotoshop, tifftagPhotoshop, tifftagPhotoshopLength);
baton->tifftagPhotoshopLength = tifftagPhotoshopLength; baton->tifftagPhotoshopLength = tifftagPhotoshopLength;
} }
// PNG comments
vips_image_map(image.get_image(), readPNGComment, &baton->comments);
} }
// Clean up // Clean up
@@ -246,6 +250,17 @@ class MetadataWorker : public Napi::AsyncWorker {
Napi::Buffer<char>::NewOrCopy(env, baton->tifftagPhotoshop, Napi::Buffer<char>::NewOrCopy(env, baton->tifftagPhotoshop,
baton->tifftagPhotoshopLength, sharp::FreeCallback)); baton->tifftagPhotoshopLength, sharp::FreeCallback));
} }
if (baton->comments.size() > 0) {
int i = 0;
Napi::Array comments = Napi::Array::New(env, baton->comments.size());
for (auto &c : baton->comments) {
Napi::Object comment = Napi::Object::New(env);
comment.Set("keyword", c.first);
comment.Set("text", c.second);
comments.Set(i++, comment);
}
info.Set("comments", comments);
}
Callback().Call(Receiver().Value(), { env.Null(), info }); Callback().Call(Receiver().Value(), { env.Null(), info });
} else { } else {
Callback().Call(Receiver().Value(), { Napi::Error::New(env, sharp::TrimEnd(baton->err)).Value() }); Callback().Call(Receiver().Value(), { Napi::Error::New(env, sharp::TrimEnd(baton->err)).Value() });
@@ -285,3 +300,21 @@ Napi::Value metadata(const Napi::CallbackInfo& info) {
return info.Env().Undefined(); return info.Env().Undefined();
} }
const char *PNG_COMMENT_START = "png-comment-";
const int PNG_COMMENT_START_LEN = strlen(PNG_COMMENT_START);
static void* readPNGComment(VipsImage *image, const char *field, GValue *value, void *p) {
MetadataComments *comments = static_cast<MetadataComments *>(p);
if (vips_isprefix(PNG_COMMENT_START, field)) {
const char *keyword = strchr(field + PNG_COMMENT_START_LEN, '-');
const char *str;
if (keyword != NULL && !vips_image_get_string(image, field, &str)) {
keyword++; // Skip the hyphen
comments->push_back(std::make_pair(keyword, str));
}
}
return NULL;
}

View File

@@ -9,6 +9,8 @@
#include "./common.h" #include "./common.h"
typedef std::vector<std::pair<std::string, std::string>> MetadataComments;
struct MetadataBaton { struct MetadataBaton {
// Input // Input
sharp::InputDescriptor *input; sharp::InputDescriptor *input;
@@ -47,6 +49,7 @@ struct MetadataBaton {
size_t xmpLength; size_t xmpLength;
char *tifftagPhotoshop; char *tifftagPhotoshop;
size_t tifftagPhotoshopLength; size_t tifftagPhotoshopLength;
MetadataComments comments;
std::string err; std::string err;
MetadataBaton(): MetadataBaton():

View File

@@ -144,7 +144,7 @@ namespace sharp {
/* /*
* Gaussian blur. Use sigma of -1.0 for fast blur. * Gaussian blur. Use sigma of -1.0 for fast blur.
*/ */
VImage Blur(VImage image, double const sigma) { VImage Blur(VImage image, double const sigma, VipsPrecision precision, double const minAmpl) {
if (sigma == -1.0) { if (sigma == -1.0) {
// Fast, mild blur - averages neighbouring pixels // Fast, mild blur - averages neighbouring pixels
VImage blur = VImage::new_matrixv(3, 3, VImage blur = VImage::new_matrixv(3, 3,
@@ -155,7 +155,9 @@ namespace sharp {
return image.conv(blur); return image.conv(blur);
} else { } else {
// Slower, accurate Gaussian blur // Slower, accurate Gaussian blur
return StaySequential(image).gaussblur(sigma); return StaySequential(image).gaussblur(sigma, VImage::option()
->set("precision", precision)
->set("min_ampl", minAmpl));
} }
} }
@@ -164,10 +166,10 @@ namespace sharp {
*/ */
VImage Convolve(VImage image, int const width, int const height, VImage Convolve(VImage image, int const width, int const height,
double const scale, double const offset, double const scale, double const offset,
std::unique_ptr<double[]> const &kernel_v std::vector<double> const &kernel_v
) { ) {
VImage kernel = VImage::new_from_memory( VImage kernel = VImage::new_from_memory(
kernel_v.get(), static_cast<void*>(const_cast<double*>(kernel_v.data())),
width * height * sizeof(double), width * height * sizeof(double),
width, width,
height, height,
@@ -183,19 +185,21 @@ namespace sharp {
* Recomb with a Matrix of the given bands/channel size. * Recomb with a Matrix of the given bands/channel size.
* Eg. RGB will be a 3x3 matrix. * Eg. RGB will be a 3x3 matrix.
*/ */
VImage Recomb(VImage image, std::unique_ptr<double[]> const &matrix) { VImage Recomb(VImage image, std::vector<double> const& matrix) {
double *m = matrix.get(); double* m = const_cast<double*>(matrix.data());
image = image.colourspace(VIPS_INTERPRETATION_sRGB); image = image.colourspace(VIPS_INTERPRETATION_sRGB);
return image if (matrix.size() == 9) {
.recomb(image.bands() == 3 return image
? VImage::new_from_memory( .recomb(image.bands() == 3
m, 9 * sizeof(double), 3, 3, 1, VIPS_FORMAT_DOUBLE ? VImage::new_matrix(3, 3, m, 9)
) : VImage::new_matrixv(4, 4,
: VImage::new_matrixv(4, 4, m[0], m[1], m[2], 0.0,
m[0], m[1], m[2], 0.0, m[3], m[4], m[5], 0.0,
m[3], m[4], m[5], 0.0, m[6], m[7], m[8], 0.0,
m[6], m[7], m[8], 0.0, 0.0, 0.0, 0.0, 1.0));
0.0, 0.0, 0.0, 1.0)); } else {
return image.recomb(VImage::new_matrix(4, 4, m, 16));
}
} }
VImage Modulate(VImage image, double const brightness, double const saturation, VImage Modulate(VImage image, double const brightness, double const saturation,

View File

@@ -47,13 +47,13 @@ namespace sharp {
/* /*
* Gaussian blur. Use sigma of -1.0 for fast blur. * Gaussian blur. Use sigma of -1.0 for fast blur.
*/ */
VImage Blur(VImage image, double const sigma); VImage Blur(VImage image, double const sigma, VipsPrecision precision, double const minAmpl);
/* /*
* Convolution with a kernel. * Convolution with a kernel.
*/ */
VImage Convolve(VImage image, int const width, int const height, VImage Convolve(VImage image, int const width, int const height,
double const scale, double const offset, std::unique_ptr<double[]> const &kernel_v); double const scale, double const offset, std::vector<double> const &kernel_v);
/* /*
* Sharpen flat and jagged areas. Use sigma of -1.0 for fast sharpen. * Sharpen flat and jagged areas. Use sigma of -1.0 for fast sharpen.
@@ -95,7 +95,7 @@ namespace sharp {
* Recomb with a Matrix of the given bands/channel size. * Recomb with a Matrix of the given bands/channel size.
* Eg. RGB will be a 3x3 matrix. * Eg. RGB will be a 3x3 matrix.
*/ */
VImage Recomb(VImage image, std::unique_ptr<double[]> const &matrix); VImage Recomb(VImage image, std::vector<double> const &matrix);
/* /*
* Modulate brightness, saturation, hue and lightness * Modulate brightness, saturation, hue and lightness

View File

@@ -325,6 +325,7 @@ class PipelineWorker : public Napi::AsyncWorker {
if ((baton->keepMetadata & VIPS_FOREIGN_KEEP_ICC) && baton->withIccProfile.empty()) { if ((baton->keepMetadata & VIPS_FOREIGN_KEEP_ICC) && baton->withIccProfile.empty()) {
// Cache input profile for use with output // Cache input profile for use with output
inputProfile = sharp::GetProfile(image); inputProfile = sharp::GetProfile(image);
baton->input->ignoreIcc = true;
} }
char const *processingProfile = image.interpretation() == VIPS_INTERPRETATION_RGB16 ? "p3" : "srgb"; char const *processingProfile = image.interpretation() == VIPS_INTERPRETATION_RGB16 ? "p3" : "srgb";
if ( if (
@@ -592,7 +593,7 @@ class PipelineWorker : public Napi::AsyncWorker {
// Blur // Blur
if (shouldBlur) { if (shouldBlur) {
image = sharp::Blur(image, baton->blurSigma); image = sharp::Blur(image, baton->blurSigma, baton->precision, baton->minAmpl);
} }
// Unflatten the image // Unflatten the image
@@ -609,7 +610,7 @@ class PipelineWorker : public Napi::AsyncWorker {
} }
// Recomb // Recomb
if (baton->recombMatrix != NULL) { if (!baton->recombMatrix.empty()) {
image = sharp::Recomb(image, baton->recombMatrix); image = sharp::Recomb(image, baton->recombMatrix);
} }
@@ -849,6 +850,11 @@ class PipelineWorker : public Napi::AsyncWorker {
image = sharp::SetAnimationProperties( image = sharp::SetAnimationProperties(
image, nPages, targetPageHeight, baton->delay, baton->loop); image, nPages, targetPageHeight, baton->delay, baton->loop);
if (image.get_typeof(VIPS_META_PAGE_HEIGHT) == G_TYPE_INT) {
baton->pageHeightOut = image.get_int(VIPS_META_PAGE_HEIGHT);
baton->pagesOut = image.get_int(VIPS_META_N_PAGES);
}
// Output // Output
sharp::SetTimeout(image, baton->timeoutSeconds); sharp::SetTimeout(image, baton->timeoutSeconds);
if (baton->fileOut.empty()) { if (baton->fileOut.empty()) {
@@ -1284,6 +1290,10 @@ class PipelineWorker : public Napi::AsyncWorker {
if (baton->input->textAutofitDpi) { if (baton->input->textAutofitDpi) {
info.Set("textAutofitDpi", static_cast<uint32_t>(baton->input->textAutofitDpi)); info.Set("textAutofitDpi", static_cast<uint32_t>(baton->input->textAutofitDpi));
} }
if (baton->pageHeightOut) {
info.Set("pageHeight", static_cast<int32_t>(baton->pageHeightOut));
info.Set("pages", static_cast<int32_t>(baton->pagesOut));
}
if (baton->bufferOutLength > 0) { if (baton->bufferOutLength > 0) {
// Add buffer size to info // Add buffer size to info
@@ -1532,6 +1542,8 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
baton->negate = sharp::AttrAsBool(options, "negate"); baton->negate = sharp::AttrAsBool(options, "negate");
baton->negateAlpha = sharp::AttrAsBool(options, "negateAlpha"); baton->negateAlpha = sharp::AttrAsBool(options, "negateAlpha");
baton->blurSigma = sharp::AttrAsDouble(options, "blurSigma"); baton->blurSigma = sharp::AttrAsDouble(options, "blurSigma");
baton->precision = sharp::AttrAsEnum<VipsPrecision>(options, "precision", VIPS_TYPE_PRECISION);
baton->minAmpl = sharp::AttrAsDouble(options, "minAmpl");
baton->brightness = sharp::AttrAsDouble(options, "brightness"); baton->brightness = sharp::AttrAsDouble(options, "brightness");
baton->saturation = sharp::AttrAsDouble(options, "saturation"); baton->saturation = sharp::AttrAsDouble(options, "saturation");
baton->hue = sharp::AttrAsInt32(options, "hue"); baton->hue = sharp::AttrAsInt32(options, "hue");
@@ -1597,17 +1609,18 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
baton->convKernelScale = sharp::AttrAsDouble(kernel, "scale"); baton->convKernelScale = sharp::AttrAsDouble(kernel, "scale");
baton->convKernelOffset = sharp::AttrAsDouble(kernel, "offset"); baton->convKernelOffset = sharp::AttrAsDouble(kernel, "offset");
size_t const kernelSize = static_cast<size_t>(baton->convKernelWidth * baton->convKernelHeight); size_t const kernelSize = static_cast<size_t>(baton->convKernelWidth * baton->convKernelHeight);
baton->convKernel = std::unique_ptr<double[]>(new double[kernelSize]); baton->convKernel.resize(kernelSize);
Napi::Array kdata = kernel.Get("kernel").As<Napi::Array>(); Napi::Array kdata = kernel.Get("kernel").As<Napi::Array>();
for (unsigned int i = 0; i < kernelSize; i++) { for (unsigned int i = 0; i < kernelSize; i++) {
baton->convKernel[i] = sharp::AttrAsDouble(kdata, i); baton->convKernel[i] = sharp::AttrAsDouble(kdata, i);
} }
} }
if (options.Has("recombMatrix")) { if (options.Has("recombMatrix")) {
baton->recombMatrix = std::unique_ptr<double[]>(new double[9]);
Napi::Array recombMatrix = options.Get("recombMatrix").As<Napi::Array>(); Napi::Array recombMatrix = options.Get("recombMatrix").As<Napi::Array>();
for (unsigned int i = 0; i < 9; i++) { unsigned int matrixElements = recombMatrix.Length();
baton->recombMatrix[i] = sharp::AttrAsDouble(recombMatrix, i); baton->recombMatrix.resize(matrixElements);
for (unsigned int i = 0; i < matrixElements; i++) {
baton->recombMatrix[i] = sharp::AttrAsDouble(recombMatrix, i);
} }
} }
baton->colourspacePipeline = sharp::AttrAsEnum<VipsInterpretation>( baton->colourspacePipeline = sharp::AttrAsEnum<VipsInterpretation>(

View File

@@ -43,6 +43,8 @@ struct PipelineBaton {
std::string fileOut; std::string fileOut;
void *bufferOut; void *bufferOut;
size_t bufferOutLength; size_t bufferOutLength;
int pageHeightOut;
int pagesOut;
std::vector<Composite *> composite; std::vector<Composite *> composite;
std::vector<sharp::InputDescriptor *> joinChannelIn; std::vector<sharp::InputDescriptor *> joinChannelIn;
int topOffsetPre; int topOffsetPre;
@@ -76,6 +78,8 @@ struct PipelineBaton {
bool negate; bool negate;
bool negateAlpha; bool negateAlpha;
double blurSigma; double blurSigma;
VipsPrecision precision;
double minAmpl;
double brightness; double brightness;
double saturation; double saturation;
int hue; int hue;
@@ -195,7 +199,7 @@ struct PipelineBaton {
std::unordered_map<std::string, std::string> withExif; std::unordered_map<std::string, std::string> withExif;
bool withExifMerge; bool withExifMerge;
int timeoutSeconds; int timeoutSeconds;
std::unique_ptr<double[]> convKernel; std::vector<double> convKernel;
int convKernelWidth; int convKernelWidth;
int convKernelHeight; int convKernelHeight;
double convKernelScale; double convKernelScale;
@@ -221,11 +225,13 @@ struct PipelineBaton {
VipsForeignDzDepth tileDepth; VipsForeignDzDepth tileDepth;
std::string tileId; std::string tileId;
std::string tileBasename; std::string tileBasename;
std::unique_ptr<double[]> recombMatrix; std::vector<double> recombMatrix;
PipelineBaton(): PipelineBaton():
input(nullptr), input(nullptr),
bufferOutLength(0), bufferOutLength(0),
pageHeightOut(0),
pagesOut(0),
topOffsetPre(-1), topOffsetPre(-1),
topOffsetPost(-1), topOffsetPost(-1),
channels(0), channels(0),

BIN
test/fixtures/d.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
test/fixtures/expected/d-opacity-30.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -14,29 +14,26 @@ const getPath = function (filename) {
// Generates a 64-bit-as-binary-string image fingerprint // Generates a 64-bit-as-binary-string image fingerprint
// Based on the dHash gradient method - see http://www.hackerfactor.com/blog/index.php?/archives/529-Kind-of-Like-That.html // Based on the dHash gradient method - see http://www.hackerfactor.com/blog/index.php?/archives/529-Kind-of-Like-That.html
const fingerprint = function (image, callback) { async function fingerprint (image) {
sharp(image) return sharp(image)
.flatten('gray') .flatten('gray')
.greyscale() .greyscale()
.normalise() .normalise()
.resize(9, 8, { fit: sharp.fit.fill }) .resize(9, 8, { fit: sharp.fit.fill })
.raw() .raw()
.toBuffer(function (err, data) { .toBuffer()
if (err) { .then(function (data) {
callback(err); let fingerprint = '';
} else { for (let col = 0; col < 8; col++) {
let fingerprint = ''; for (let row = 0; row < 8; row++) {
for (let col = 0; col < 8; col++) { const left = data[(row * 8) + col];
for (let row = 0; row < 8; row++) { const right = data[(row * 8) + col + 1];
const left = data[(row * 8) + col]; fingerprint = fingerprint + (left < right ? '1' : '0');
const right = data[(row * 8) + col + 1];
fingerprint = fingerprint + (left < right ? '1' : '0');
}
} }
callback(null, fingerprint);
} }
return fingerprint;
}); });
}; }
module.exports = { module.exports = {
@@ -102,6 +99,7 @@ module.exports = {
inputPngTrimSpecificColour16bit: getPath('Flag_of_the_Netherlands-16bit.png'), // convert Flag_of_the_Netherlands.png -depth 16 Flag_of_the_Netherlands-16bit.png inputPngTrimSpecificColour16bit: getPath('Flag_of_the_Netherlands-16bit.png'), // convert Flag_of_the_Netherlands.png -depth 16 Flag_of_the_Netherlands-16bit.png
inputPngTrimSpecificColourIncludeAlpha: getPath('Flag_of_the_Netherlands-alpha.png'), // convert Flag_of_the_Netherlands.png -alpha set -background none -channel A -evaluate multiply 0.5 +channel Flag_of_the_Netherlands-alpha.png inputPngTrimSpecificColourIncludeAlpha: getPath('Flag_of_the_Netherlands-alpha.png'), // convert Flag_of_the_Netherlands.png -alpha set -background none -channel A -evaluate multiply 0.5 +channel Flag_of_the_Netherlands-alpha.png
inputPngUint32Limit: getPath('65536-uint32-limit.png'), // https://alexandre.alapetite.fr/doc-alex/large-image/ inputPngUint32Limit: getPath('65536-uint32-limit.png'), // https://alexandre.alapetite.fr/doc-alex/large-image/
inputPngWithProPhotoProfile: getPath('prophoto.png'),
inputWebP: getPath('4.webp'), // http://www.gstatic.com/webp/gallery/4.webp inputWebP: getPath('4.webp'), // http://www.gstatic.com/webp/gallery/4.webp
inputWebPWithTransparency: getPath('5_webp_a.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp inputWebPWithTransparency: getPath('5_webp_a.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp
@@ -139,6 +137,7 @@ module.exports = {
testPattern: getPath('test-pattern.png'), testPattern: getPath('test-pattern.png'),
inputPngWithTransparent: getPath('d.png'),
// Path for tests requiring human inspection // Path for tests requiring human inspection
path: getPath, path: getPath,
@@ -150,46 +149,44 @@ module.exports = {
// Verify similarity of expected vs actual images via fingerprint // Verify similarity of expected vs actual images via fingerprint
// Specify distance threshold using `options={threshold: 42}`, default // Specify distance threshold using `options={threshold: 42}`, default
// `threshold` is 5; // `threshold` is 5;
assertSimilar: function (expectedImage, actualImage, options, callback) { assertSimilar: async function (expectedImage, actualImage, options, callback) {
if (typeof options === 'function') { if (typeof options === 'function') {
callback = options; callback = options;
options = {}; options = {};
} }
if (typeof options === 'undefined' || options === null) {
if (typeof options === 'undefined' && options === null) {
options = {}; options = {};
} }
if (options.threshold === null || typeof options.threshold === 'undefined') { if (options.threshold === null || typeof options.threshold === 'undefined') {
options.threshold = 5; // ~7% threshold options.threshold = 5; // ~7% threshold
} }
if (typeof options.threshold !== 'number') { if (typeof options.threshold !== 'number') {
throw new TypeError('`options.threshold` must be a number'); throw new TypeError('`options.threshold` must be a number');
} }
if (typeof callback !== 'function') { try {
throw new TypeError('`callback` must be a function'); const [expectedFingerprint, actualFingerprint] = await Promise.all([
fingerprint(expectedImage),
fingerprint(actualImage)
]);
let distance = 0;
for (let i = 0; i < 64; i++) {
if (expectedFingerprint[i] !== actualFingerprint[i]) {
distance++;
}
}
if (distance > options.threshold) {
throw new Error(`Expected maximum similarity distance: ${options.threshold}. Actual: ${distance}.`);
}
} catch (err) {
if (callback) {
return callback(err);
}
throw err;
}
if (callback) {
callback();
} }
fingerprint(expectedImage, function (err, expectedFingerprint) {
if (err) return callback(err);
fingerprint(actualImage, function (err, actualFingerprint) {
if (err) return callback(err);
let distance = 0;
for (let i = 0; i < 64; i++) {
if (expectedFingerprint[i] !== actualFingerprint[i]) {
distance++;
}
}
if (distance > options.threshold) {
return callback(new Error('Expected maximum similarity distance: ' + options.threshold + '. Actual: ' + distance + '.'));
}
callback();
});
});
}, },
assertMaxColourDistance: function (actualImagePath, expectedImagePath, acceptedDistance) { assertMaxColourDistance: function (actualImagePath, expectedImagePath, acceptedDistance) {

BIN
test/fixtures/prophoto.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 556 B

View File

@@ -59,6 +59,12 @@ sharp('input.jpg')
sharp('input.jpg').resize({ width: 300 }).blur(false).blur(true).toFile('output.jpg'); sharp('input.jpg').resize({ width: 300 }).blur(false).blur(true).toFile('output.jpg');
sharp().blur();
sharp().blur(1);
sharp().blur({ sigma: 1 });
sharp().blur({ sigma: 1, precision: 'approximate' });
sharp().blur({ sigma: 1, minAmplitude: 0.8 });
sharp({ sharp({
create: { create: {
width: 300, width: 300,
@@ -295,6 +301,13 @@ sharp('input.gif')
[0.2392, 0.4696, 0.0912], [0.2392, 0.4696, 0.0912],
]) ])
.recomb([
[1,0,0,0],
[0,1,0,0],
[0,0,1,0],
[0,0,0,1],
])
.modulate({ brightness: 2 }) .modulate({ brightness: 2 })
.modulate({ hue: 180 }) .modulate({ hue: 180 })
.modulate({ lightness: 10 }) .modulate({ lightness: 10 })

View File

@@ -35,6 +35,19 @@ describe('Blur', function () {
}); });
}); });
it('specific options.sigma 10', function (done) {
sharp(fixtures.inputJpg)
.resize(320, 240)
.blur({ sigma: 10 })
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
fixtures.assertSimilar(fixtures.expected('blur-10.jpg'), data, done);
});
});
it('specific radius 0.3', function (done) { it('specific radius 0.3', function (done) {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(320, 240) .resize(320, 240)
@@ -91,4 +104,54 @@ describe('Blur', function () {
}); });
}); });
}); });
it('invalid precision', function () {
assert.throws(function () {
sharp(fixtures.inputJpg).blur({ sigma: 1, precision: 'invalid' });
}, /Expected one of: integer, float, approximate for precision but received invalid of type string/);
});
it('invalid minAmplitude', function () {
assert.throws(function () {
sharp(fixtures.inputJpg).blur({ sigma: 1, minAmplitude: 0 });
}, /Expected number between 0.001 and 1 for minAmplitude but received 0 of type number/);
assert.throws(function () {
sharp(fixtures.inputJpg).blur({ sigma: 1, minAmplitude: 1.01 });
}, /Expected number between 0.001 and 1 for minAmplitude but received 1.01 of type number/);
});
it('specific radius 10 and precision approximate', async () => {
const approximate = await sharp(fixtures.inputJpg)
.resize(320, 240)
.blur({ sigma: 10, precision: 'approximate' })
.toBuffer();
const integer = await sharp(fixtures.inputJpg)
.resize(320, 240)
.blur(10)
.toBuffer();
assert.notDeepEqual(approximate, integer);
await fixtures.assertSimilar(fixtures.expected('blur-10.jpg'), approximate);
});
it('specific radius 10 and minAmplitude 0.01', async () => {
const minAmplitudeLow = await sharp(fixtures.inputJpg)
.resize(320, 240)
.blur({ sigma: 10, minAmplitude: 0.01 })
.toBuffer();
const minAmplitudeDefault = await sharp(fixtures.inputJpg)
.resize(320, 240)
.blur(10)
.toBuffer();
assert.notDeepEqual(minAmplitudeLow, minAmplitudeDefault);
await fixtures.assertSimilar(fixtures.expected('blur-10.jpg'), minAmplitudeLow);
});
it('options.sigma is required if options object is passed', function () {
assert.throws(function () {
sharp(fixtures.inputJpg).blur({ precision: 'invalid' });
}, /Expected number between 0.3 and 1000 for options.sigma but received undefined of type undefined/);
});
}); });

View File

@@ -39,7 +39,7 @@ describe('GIF input', () => {
}) })
); );
it('Animated GIF first page to PNG', () => it('Animated GIF first page to non-animated GIF', () =>
sharp(fixtures.inputGifAnimated) sharp(fixtures.inputGifAnimated)
.toBuffer({ resolveWithObject: true }) .toBuffer({ resolveWithObject: true })
.then(({ data, info }) => { .then(({ data, info }) => {
@@ -49,10 +49,12 @@ describe('GIF input', () => {
assert.strictEqual(80, info.width); assert.strictEqual(80, info.width);
assert.strictEqual(80, info.height); assert.strictEqual(80, info.height);
assert.strictEqual(4, info.channels); assert.strictEqual(4, info.channels);
assert.strictEqual(undefined, info.pages);
assert.strictEqual(undefined, info.pageHeight);
}) })
); );
it('Animated GIF all pages to PNG "toilet roll"', () => it('Animated GIF round trip', () =>
sharp(fixtures.inputGifAnimated, { pages: -1 }) sharp(fixtures.inputGifAnimated, { pages: -1 })
.toBuffer({ resolveWithObject: true }) .toBuffer({ resolveWithObject: true })
.then(({ data, info }) => { .then(({ data, info }) => {
@@ -62,6 +64,8 @@ describe('GIF input', () => {
assert.strictEqual(80, info.width); assert.strictEqual(80, info.width);
assert.strictEqual(2400, info.height); assert.strictEqual(2400, info.height);
assert.strictEqual(4, info.channels); assert.strictEqual(4, info.channels);
assert.strictEqual(30, info.pages);
assert.strictEqual(80, info.pageHeight);
}) })
); );

View File

@@ -72,6 +72,15 @@ describe('libvips binaries', function () {
const useGlobalLibvips = libvips.useGlobalLibvips(); const useGlobalLibvips = libvips.useGlobalLibvips();
assert.strictEqual(true, useGlobalLibvips); assert.strictEqual(true, useGlobalLibvips);
let logged = false;
const logger = function (message) {
assert.strictEqual(message, 'Detected SHARP_FORCE_GLOBAL_LIBVIPS, skipping search for globally-installed libvips');
logged = true;
};
const useGlobalLibvipsWithLogger = libvips.useGlobalLibvips(logger);
assert.strictEqual(true, useGlobalLibvipsWithLogger);
assert.strictEqual(true, logged);
delete process.env.SHARP_FORCE_GLOBAL_LIBVIPS; delete process.env.SHARP_FORCE_GLOBAL_LIBVIPS;
}); });
}); });
@@ -170,7 +179,7 @@ describe('libvips binaries', function () {
process.env.npm_config_arch = 's390x'; process.env.npm_config_arch = 's390x';
process.env.npm_config_libc = ''; process.env.npm_config_libc = '';
const locatorHash = libvips.yarnLocator(); const locatorHash = libvips.yarnLocator();
assert.strictEqual(locatorHash, '45978c229d'); assert.strictEqual(locatorHash, 'c4ea54fdc1');
delete process.env.npm_config_platform; delete process.env.npm_config_platform;
delete process.env.npm_config_arch; delete process.env.npm_config_arch;
delete process.env.npm_config_libc; delete process.env.npm_config_libc;

View File

@@ -154,6 +154,31 @@ describe('Image metadata', function () {
}); });
}); });
it('PNG with comment', function (done) {
sharp(fixtures.inputPngTestJoinChannel).metadata(function (err, metadata) {
if (err) throw err;
assert.strictEqual('png', metadata.format);
assert.strictEqual('undefined', typeof metadata.size);
assert.strictEqual(320, metadata.width);
assert.strictEqual(240, metadata.height);
assert.strictEqual('b-w', metadata.space);
assert.strictEqual(1, metadata.channels);
assert.strictEqual('uchar', metadata.depth);
assert.strictEqual(72, metadata.density);
assert.strictEqual('undefined', typeof metadata.chromaSubsampling);
assert.strictEqual(false, metadata.isProgressive);
assert.strictEqual(false, metadata.hasProfile);
assert.strictEqual(false, metadata.hasAlpha);
assert.strictEqual('undefined', typeof metadata.orientation);
assert.strictEqual('undefined', typeof metadata.exif);
assert.strictEqual('undefined', typeof metadata.icc);
assert.strictEqual(1, metadata.comments.length);
assert.strictEqual('Comment', metadata.comments[0].keyword);
assert.strictEqual('Created with GIMP', metadata.comments[0].text);
done();
});
});
it('Transparent PNG', function (done) { it('Transparent PNG', function (done) {
sharp(fixtures.inputPngWithTransparency).metadata(function (err, metadata) { sharp(fixtures.inputPngWithTransparency).metadata(function (err, metadata) {
if (err) throw err; if (err) throw err;
@@ -576,6 +601,17 @@ describe('Image metadata', function () {
assert.strictEqual(description, 'Generic RGB Profile'); assert.strictEqual(description, 'Generic RGB Profile');
}); });
it('keep existing ICC profile, avoid colour transform', async () => {
const [r, g, b] = await sharp(fixtures.inputPngWithProPhotoProfile)
.keepIccProfile()
.raw()
.toBuffer();
assert.strictEqual(r, 131);
assert.strictEqual(g, 141);
assert.strictEqual(b, 192);
});
it('keep existing CMYK ICC profile', async () => { it('keep existing CMYK ICC profile', async () => {
const data = await sharp(fixtures.inputJpgWithCmykProfile) const data = await sharp(fixtures.inputJpgWithCmykProfile)
.pipelineColourspace('cmyk') .pipelineColourspace('cmyk')
@@ -616,13 +652,13 @@ describe('Image metadata', function () {
.png() .png()
.withIccProfile(fixtures.path('invalid-illuminant.icc')); .withIccProfile(fixtures.path('invalid-illuminant.icc'));
let warningEmitted = ''; const warningsEmitted = [];
img.on('warning', (warning) => { img.on('warning', (warning) => {
warningEmitted = warning; warningsEmitted.push(warning);
}); });
const data = await img.toBuffer(); const data = await img.toBuffer();
assert.strictEqual('Invalid profile', warningEmitted); assert.strict(warningsEmitted.includes('Invalid profile'));
const metadata = await sharp(data).metadata(); const metadata = await sharp(data).metadata();
assert.strictEqual(3, metadata.channels); assert.strictEqual(3, metadata.channels);

View File

@@ -121,6 +121,29 @@ describe('Recomb', function () {
}); });
}); });
it('applies opacity 30% to the image', function (done) {
const output = fixtures.path('output.recomb-opacity.png');
sharp(fixtures.inputPngWithTransparent)
.recomb([
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 0.3]
])
.toFile(output, function (err, info) {
if (err) throw err;
assert.strictEqual('png', info.format);
assert.strictEqual(48, info.width);
assert.strictEqual(48, info.height);
fixtures.assertMaxColourDistance(
output,
fixtures.expected('d-opacity-30.png'),
17
);
done();
});
});
describe('invalid matrix specification', function () { describe('invalid matrix specification', function () {
it('missing', function () { it('missing', function () {
assert.throws(function () { assert.throws(function () {

View File

@@ -417,6 +417,7 @@ describe('Rotation', function () {
it('Flip and flop', function (done) { it('Flip and flop', function (done) {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)
.resize(320) .resize(320)
.flip()
.flop() .flop()
.toBuffer(function (err, data, info) { .toBuffer(function (err, data, info) {
if (err) throw err; if (err) throw err;