Compare commits

...

113 Commits

Author SHA1 Message Date
Lovell Fuller
b609df4b48 Release v0.30.3 2022-03-14 11:54:15 +00:00
Lovell Fuller
c567d3b9ab Bump devDeps 2022-03-14 11:52:45 +00:00
Marvin ROGER
27d9fe2a4e Add additional debugging hint on symbol error (#3132) 2022-03-14 10:19:02 +00:00
Lovell Fuller
ac883c5215 Docs: composite image supports the animated property #3135 2022-03-14 08:53:00 +00:00
Lovell Fuller
e8720c9374 Docs: use SVG favicon with a PNG fallback
Remove unused apple-touch icons
2022-03-13 14:22:05 +00:00
Lovell Fuller
42e45d842a Docs: add more examples (composite, greyscale, tint) 2022-03-12 20:33:15 +00:00
Lovell Fuller
72fd8abe2c Docs: add section on (preventing) bundling with esbuild 2022-03-12 13:17:36 +00:00
Lovell Fuller
ac18bbbc7c Ensure backwards-compatibility of JSDoc introduced in ea599ad 2022-03-11 19:07:11 +00:00
Lovell Fuller
fcbe4e1e01 Tests: remove possible race condition
Simplify JP2 quality size check
2022-03-09 19:55:32 +00:00
Lovell Fuller
9280742385 CI: update FreeBSD environment 2022-03-09 19:26:43 +00:00
Lovell Fuller
7a1a1cf9e8 Docs: add/correct some operation examples 2022-03-09 19:18:41 +00:00
Lovell Fuller
ea599ade10 Allow sharpen options to be provided as an Object
Also exposes x1, y2, y3 parameters #2561 #2935
2022-03-09 19:07:08 +00:00
Lovell Fuller
1de49f3ed8 Docs: refresh for metadata example #3127 2022-03-09 16:02:27 +00:00
Rasmus Schultz
4ac65054bc Docs: add example of how to get the right-side-up width/height (#3127)
See #3124
2022-03-09 15:59:09 +00:00
Lovell Fuller
23033e2050 Prevent double unpremultiply with some composite blends 2022-03-04 23:17:07 +00:00
Lovell Fuller
dd3b78272a Docs: remove experimental status from existing stats properties 2022-03-04 07:58:03 +00:00
Lovell Fuller
80d169b7c2 Release v0.30.2 2022-03-02 11:24:35 +00:00
Lovell Fuller
003279a0b0 CI: switch 32-bit Windows from Appveyor to Actions 2022-03-02 11:16:21 +00:00
Lovell Fuller
af80d7e389 Improve error message for missing file that might be SVG 2022-03-02 09:58:55 +00:00
Lovell Fuller
21a960796c Ignore greyscale ICC profiles due to lcms bug #3112 2022-02-28 11:28:08 +00:00
Lovell Fuller
fc3b4a683d Expand pkgconfig search path for wider BSD support #3106 2022-02-27 09:39:21 +00:00
Lovell Fuller
808133e7dc Docs: changelog entry for #3110 2022-02-26 19:40:17 +00:00
Lovell Fuller
801b6fea6c Bump devDeps 2022-02-26 19:39:46 +00:00
Kleis Auke Wolthuizen
c2ecde6a16 Windows: ensure C++ runtime is linked statically (#3110)
And remove the empty invalid parameter handler, which should
be present in the C layer instead.

This partially reverts commit
659cdabd8e,
the added test case in that commit is still preserved.
2022-02-26 19:15:37 +00:00
Lovell Fuller
55efe5602b Bump deps 2022-02-16 19:12:27 +00:00
Lovell Fuller
c62002554b Improve performance and accuracy of multi-image composite #2286 2022-02-16 19:04:23 +00:00
Lovell Fuller
7f83ecd255 Issue templates: small formatting fixes 2022-02-15 10:54:36 +00:00
Lovell Fuller
dc5f4dcd28 Issue templates: improve guidance, increase filtering 2022-02-15 10:50:26 +00:00
Lovell Fuller
735793ba99 Release v0.30.1 2022-02-09 16:38:21 +00:00
Lovell Fuller
47792df689 Docs: update interpolator links 2022-02-09 15:45:05 +00:00
Lovell Fuller
5c6cdfaece Docs: changelog entry for #3083 2022-02-09 15:27:52 +00:00
Kleis Auke Wolthuizen
115a6b10f6 Ensure affineInterpolator is correctly finalised (#3083) 2022-02-09 14:45:11 +00:00
Lovell Fuller
4feee506cf Docs: changelog entry for #3081 2022-02-08 22:08:07 +00:00
Kleis Auke Wolthuizen
83db5f8a2a Ensure withoutReduction does not interfere with contain/crop/embed (#3081) 2022-02-08 21:22:23 +00:00
Kleis Auke Wolthuizen
7eb5efa3a3 Update tests and comments after 7faacd9 (#3080) 2022-02-08 21:12:38 +00:00
Lovell Fuller
5a9f89fe06 Docs: update references to default branch 2022-02-08 21:01:40 +00:00
Lovell Fuller
02e0c2dfc9 Tests: arm64 requires slight threshold increase 2022-02-08 21:00:30 +00:00
Lovell Fuller
968d9d7008 Bump devDeps 2022-02-08 18:29:25 +00:00
Kleis Auke Wolthuizen
7faacd91b0 Avoid fastShrinkOnLoad workaround 2022-02-08 18:20:25 +00:00
Lovell Fuller
154eaff4ec Issue template: include npm v8+ 2022-02-08 18:20:11 +00:00
Lovell Fuller
424660278d Allow use of toBuffer+toFile w/ same instance #3044 2022-02-03 22:01:46 +00:00
Lovell Fuller
2b01951306 Skip JPEG shrink-on-load for known rounding errors #3066
Co-authored-by: Kleis Auke Wolthuizen <github@kleisauke.nl>
2022-02-03 21:19:48 +00:00
Lovell Fuller
83bb6a4554 Release v0.30.0 2022-02-01 20:36:12 +00:00
Lovell Fuller
c72d42816d Docs: update performance test results for next release 2022-02-01 18:19:35 +00:00
Lovell Fuller
35a81a7af2 Upgrade to libvips v8.12.2 2022-02-01 10:52:02 +00:00
Lovell Fuller
9dc8db4370 Upgrade to libvips v8.12.2 2022-01-29 16:46:07 +00:00
Lovell Fuller
47ae1f52db Docs: metadata returns resolutionUnit, if present 2022-01-29 15:24:44 +00:00
Lovell Fuller
ec17d7f580 Bump deps: prebuild, for latest detect-libc 2022-01-29 15:11:50 +00:00
Lovell Fuller
da5453a7c0 Docs: changelog and refresh for #3023 2022-01-29 14:59:20 +00:00
ompal
f7bed69ffb Add resolutionUnit to metadata and as tiff option #3023
Co-authored-by: Lovell Fuller <github@lovell.info>
2022-01-29 14:31:47 +00:00
Kleis Auke Wolthuizen
7aa340232e Ensure delays of the origin image is preserved (#3062)
By not initializing the delay vector, since we assume it's empty by default.

See: #3061.
2022-01-28 11:50:31 +00:00
Kleis Auke Wolthuizen
68823a5edb Take page parameter into account when all frames are read (#3059) 2022-01-27 21:42:08 +00:00
Simon
1c3ba303ea Update simple-get version (#3057)
Co-authored-by: Simon Tannai <stannai@socialkitchen.fr>
2022-01-27 10:57:56 +00:00
Lovell Fuller
76f8112952 Docs: npm v8 2022-01-27 09:24:29 +00:00
Lovell Fuller
24150eac89 Docs: Linux x64 binaries require CPU with SSE4.2
Intel has offered this since 2010, AMD since 2011
2022-01-24 13:51:19 +00:00
Lovell Fuller
51121a1440 Bump deps 2022-01-24 10:29:43 +00:00
Lovell Fuller
3b370b6c01 CI: add Rosetta-emulated x64 2022-01-24 10:19:04 +00:00
Lovell Fuller
884947a069 Upgrade to modern detect-libc 2022-01-19 11:27:24 +00:00
Lovell Fuller
f8340e1a82 Bump deps 2022-01-18 15:52:26 +00:00
Lovell Fuller
5101f4e79c CI: Fix linux-arm64v8 docker permission problems with npm v8 2022-01-18 15:50:25 +00:00
Lovell Fuller
161d127bf3 Docs refresh 2022-01-18 10:29:18 +00:00
Lovell Fuller
638d540371 Docs: mild blur is a box filter, add examples 2022-01-18 10:29:10 +00:00
Lovell Fuller
d8f1298511 Ensure rotate-then-extract works with EXIF mirroring #3024 2022-01-08 18:48:11 +00:00
Lovell Fuller
d67e09ba7c Add support for IIIF v3 tile-based output 2022-01-07 20:45:48 +00:00
Lovell Fuller
4c3a8a7007 Docs: fix example formatting, remove outdated warning 2021-12-21 12:31:29 +00:00
Lovell Fuller
92399ee5e2 Docs: correct gif resize example 2021-12-13 18:26:35 +00:00
Lovell Fuller
96992845ed Install: include platformAndArch in help after failure 2021-12-12 21:12:47 +00:00
Lovell Fuller
2b8e4d20de Docs: clarify that metadata is based on input, not output 2021-12-12 20:38:23 +00:00
Lovell Fuller
dd6583044b Docs: changelog and credit for #3006 2021-12-12 19:29:46 +00:00
Chris Banks
446e4e3c3a Add withoutReduction option to resize operation (#3006) 2021-12-12 19:10:56 +00:00
Lovell Fuller
3b492ea423 Install: verify prebuilt binaries with Subresource Integrity check 2021-12-12 18:56:46 +00:00
Lovell Fuller
3da258f6fb Expose platform-arch of vendored binaries #2928 2021-12-12 13:38:00 +00:00
Lovell Fuller
9755629cfd Ensure ops without multi-page support reject (#3010) 2021-12-12 09:14:26 +00:00
Lovell Fuller
5bb6702717 Bump dep: color 2021-12-11 21:51:23 +00:00
Lovell Fuller
8c0660d71e Docs: changelog and refresh for #2789 2021-12-11 21:41:41 +00:00
Kleis Auke Wolthuizen
513fb40f40 Improve multi-frame image resizing (#2789)
* Ports vips_thumbnail logic to sharp 
* Deprecates the pageHeight output option for WebP/GIF
2021-12-10 20:32:04 +00:00
Kleis Auke Wolthuizen
659cdabd8e Windows: make invalid param errors recoverable by default (#3002)
* Tests: ensure writing to missing directory fails

Co-authored-by: Lovell Fuller <github@lovell.info>
2021-12-06 10:08:32 +00:00
Lovell Fuller
add4c7928f Upgrade to libvips v8.12.1 2021-11-25 22:05:21 +00:00
Lovell Fuller
336856dfc2 Tests: add PNG palette decode/encode roundtrip 2021-11-25 10:29:28 +00:00
Lovell Fuller
e1ba2a7fd8 Standardise HEIF effort option name, deprecate speed 2021-11-24 19:54:04 +00:00
Lovell Fuller
2b1f5cbe07 Standardise WebP effort option name, deprecate reductionEffort 2021-11-24 18:54:30 +00:00
Lovell Fuller
72025051c5 Expose control over CPU effort for palette PNG #2541 2021-11-24 17:12:53 +00:00
Lovell Fuller
140eeebb3d Docs refresh 2021-11-24 12:35:40 +00:00
Lovell Fuller
407bfcb42a Expose unlimited option for SVG and PNG input #2984 2021-11-24 12:35:20 +00:00
Lovell Fuller
549219f32a Tests: only need to assert 'mean' of 16-bit PNG roundtrip 2021-11-23 12:35:47 +00:00
Lovell Fuller
afab0d34dd Docs: changelog and credit for #2958 2021-11-23 12:21:36 +00:00
Max Gordon
2deced0fb9 Ensure 16-bit PNG output uses correct bitdepth #2958 2021-11-23 12:19:41 +00:00
Lovell Fuller
2a0077c481 Upgrade to libvips v8.12.0 2021-11-21 12:03:12 +00:00
Lovell Fuller
f7f3e43490 Add support for GIF output using cgif in prebuilt binaries 2021-11-21 10:22:34 +00:00
Lovell Fuller
b876abaf88 Tests: ensure median default window is covered 2021-11-17 13:58:56 +00:00
Lovell Fuller
dcd9a3c558 Docs: changelog and credit for #2976 2021-11-17 11:42:30 +00:00
Drian Naude
a06b8c296a Properly emit close events for duplex streams (#2976) 2021-11-17 11:19:58 +00:00
DSRKafuU
602f988aba Docs: Update Chinese mirror hostname (#2979) 2021-11-17 11:17:37 +00:00
Lovell Fuller
079bd7b9f5 Upgrade to libvips v8.12.0-rc1 2021-11-16 10:28:38 +00:00
Lovell Fuller
1ff84b20b7 Release v0.29.3 2021-11-14 11:40:19 +00:00
Lovell Fuller
97655d2dfd Bump deps 2021-11-14 09:17:44 +00:00
Michael B. Klein
d10d7b02d4 Docs: remove duplicate entry for mbklein (#2971) 2021-11-11 19:10:44 +00:00
Lovell Fuller
2ffdae2914 Docs: changelog and credit for #2952 2021-11-08 19:43:49 +00:00
Michael B. Klein
342de36973 Impute TIFF xres/yres from withMetadata({density}) 2021-11-08 19:43:42 +00:00
Lovell Fuller
b33231d4bd Ensure correct dimensions when contain 1px image #2951 2021-11-07 16:35:30 +00:00
Lovell Fuller
319db21f29 Release v0.29.2 2021-10-21 09:15:21 +01:00
Lovell Fuller
d359331426 Remove animation props from single page images #2890 2021-10-18 20:27:10 +01:00
Lovell Fuller
7ae151362b Bump devDeps 2021-10-17 15:17:50 +01:00
Lovell Fuller
648a1e05da Throw error rather than exit for invalid binaries #2931 2021-10-17 15:14:40 +01:00
Lovell Fuller
b9f211fe34 Docs: changelog for #2918 2021-10-17 15:11:38 +01:00
Dmitri Pyatkov
e475d9e47f Improve error message on Windows for version conflict (#2918) 2021-10-17 14:10:28 +01:00
Lovell Fuller
f37ca8249a Bump deps 2021-09-22 11:41:22 +01:00
Lovell Fuller
1dd4be670d Add timeout function to limit processing time 2021-09-22 10:33:59 +01:00
Lovell Fuller
197d4cf835 Docs: changelog and credit for #2893 2021-09-22 10:31:12 +01:00
Lovell Fuller
83eed86b53 Docs: clarify prebuilt libc support on ARMv6/v7 2021-09-22 10:08:52 +01:00
Lovell Fuller
bbf612cb9e Replace use of deprecated util.inherits 2021-09-22 10:08:44 +01:00
Erlend
2679bb567b Allow use of 'tif' to select TIFF output (#2893) 2021-09-16 18:49:14 +01:00
Lovell Fuller
481e350f39 Ensure 'versions' is populated from vendored libvips 2021-09-07 11:21:00 +01:00
119 changed files with 2789 additions and 915 deletions

View File

@@ -43,13 +43,14 @@ jobs:
steps:
- checkout
- run: |
sudo chown 0.0 ${PWD}
sudo docker run -dit --name sharp --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp arm64v8/debian:bullseye
sudo docker run -dit --name sharp --workdir /mnt/sharp arm64v8/debian:bullseye
sudo docker exec sharp sh -c "apt-get update && apt-get install -y build-essential git python3 curl"
sudo docker exec sharp sh -c "curl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add -"
sudo docker exec sharp sh -c "echo 'deb https://deb.nodesource.com/node_16.x sid main' >/etc/apt/sources.list.d/nodesource.list"
sudo docker exec sharp sh -c "apt-get update && apt-get install -y nodejs"
- run: sudo docker exec sharp sh -c "npm install --build-from-source --unsafe-perm"
sudo docker exec sharp sh -c "mkdir -p /mnt/sharp"
sudo docker cp . sharp:/mnt/sharp/.
- run: sudo docker exec sharp sh -c "npm install --build-from-source"
- run: sudo docker exec sharp sh -c "npm test"
linux-arm64-musl-node-12:
resource_class: arm.medium
@@ -70,8 +71,9 @@ jobs:
steps:
- checkout
- run: |
sudo chown 0.0 ${PWD}
sudo docker run -dit --name sharp --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp node:16-alpine3.11
sudo docker run -dit --name sharp --workdir /mnt/sharp node:16-alpine3.11
sudo docker exec sharp sh -c "apk add build-base git python3 --update-cache"
- run: sudo docker exec sharp sh -c "npm install --build-from-source --unsafe-perm"
sudo docker exec sharp sh -c "mkdir -p /mnt/sharp"
sudo docker cp . sharp:/mnt/sharp/.
- run: sudo docker exec sharp sh -c "npm install --build-from-source"
- run: sudo docker exec sharp sh -c "npm test"

View File

@@ -1,15 +1,15 @@
freebsd_instance:
image_family: freebsd-13-0-snap
image_family: freebsd-14-0-snap
task:
name: FreeBSD 13.0
name: FreeBSD
env:
IGNORE_OSVERSION: yes
skip_notifications: true
prerequisites_script:
- pkg update -f
- pkg upgrade -y
- pkg install -y pkgconf vips node npm
- pkg install -y devel/pkgconf graphics/vips www/node16 www/npm
install_script:
- npm install --build-from-source --unsafe-perm
test_script:

View File

@@ -23,22 +23,22 @@ the feature you need.
Thank you! To prevent the problem occurring again, please add unit tests that would have failed.
Please select the `master` branch as the destination for your Pull Request so your fix can be included in the next minor release.
Please select the `main` branch as the destination for your Pull Request so your fix can be included in the next minor release.
Please squash your changes into a single commit using a command like `git rebase -i upstream/master`.
Please squash your changes into a single commit using a command like `git rebase -i upstream/main`.
To test C++ changes, you can compile the module using `npm install --build-from-source` and then run the tests using `npm test`.
## Submit a Pull Request with a new feature
Please add JavaScript [unit tests](https://github.com/lovell/sharp/tree/master/test/unit) to cover your new feature.
Please add JavaScript [unit tests](https://github.com/lovell/sharp/tree/main/test/unit) to cover your new feature.
A test coverage report for the JavaScript code is generated in the `coverage/lcov-report` directory.
Where possible, the functional tests use gradient-based perceptual hashes
based on [dHash](http://www.hackerfactor.com/blog/index.php?/archives/529-Kind-of-Like-That.html)
to compare expected vs actual images.
You deserve to add your details to the [list of contributors](https://github.com/lovell/sharp/blob/master/package.json#L5).
You deserve to add your details to the [list of contributors](https://github.com/lovell/sharp/blob/main/package.json#L5).
Any change that modifies the existing public API should be added to the relevant work-in-progress branch for inclusion in the next major release.
@@ -93,5 +93,5 @@ Please feel free to ask any questions via a
[new issue](https://github.com/lovell/sharp/issues/new).
If you're unable to post details publicly, please
[e-mail](https://github.com/lovell/sharp/blob/master/package.json#L5)
[e-mail](https://github.com/lovell/sharp/blob/main/package.json#L5)
for private, paid consulting.

View File

@@ -5,12 +5,24 @@ labels: enhancement
---
What are you trying to achieve?
## Feature request
Have you searched for similar feature requests?
### What are you trying to achieve?
What would you expect the API to look like?
<!-- Please provide context here. -->
What alternatives have you considered?
### When you searched for similar feature requests, what did you find that might be related?
Is there a sample image that helps explain?
<!-- Please demonstrate your research here. -->
### What would you expect the API to look like?
<!-- Please provide your suggestions here. -->
### What alternatives have you considered?
<!-- Please provide your ideas here. -->
### Please provide sample image(s) that help explain this feature
<!-- Please provide links to one or more images here. -->

View File

@@ -5,18 +5,41 @@ labels: installation
---
Did you see the [documentation relating to installation](https://sharp.pixelplumbing.com/install)?
<!-- Please try to answer as many of these questions as possible. -->
Have you ensured the architecture and platform of Node.js used for `npm install` is the same as the architecture and platform of Node.js used at runtime?
## Possible install-time or require-time problem
Are you using the latest version? Is the version currently in use as reported by `npm ls sharp` the same as the latest version as reported by `npm view sharp dist-tags.latest`?
<!-- Please place an [x] in the box to confirm. -->
- [ ] I have read the [documentation relating to installation](https://sharp.pixelplumbing.com/install).
- [ ] I have ensured that the architecture and platform of Node.js used for `npm install` is the same as the architecture and platform of Node.js used at runtime.
### Are you using the latest version of sharp?
<!-- Please place an [x] in the box to confirm. -->
- [ ] I am using the latest version of `sharp` as reported by `npm view sharp dist-tags.latest`.
If you cannot confirm this, please upgrade to the latest version and try again before opening an issue.
If you are using another package which depends on a version of `sharp` that is not the latest, please open an issue against that package instead.
### Is this a problem with filesystem permissions?
If you are using npm v6 or earlier and installing as a `root` or `sudo` user, have you tried with the `npm install --unsafe-perm` flag?
If you are using npm v7, does the user running `npm install` own the directory it is run in?
If you are using npm v7 or later, does the user running `npm install` own the directory it is run in?
If you are using the `ignore-scripts` feature of `npm`, have you tried with the `npm install --ignore-scripts=false` flag?
What is the complete output of running `npm install --verbose sharp`? Have you checked this output for useful error messages?
### What is the complete output of running `npm install --verbose sharp`?
What is the output of running `npx envinfo --binaries --system`?
<details>
<!-- Please provide output of `npm install --verbose sharp` here. -->
</details>
### What is the output of running `npx envinfo --binaries --system --npmPackages=sharp --npmGlobalPackages=sharp`?
<!-- Please provide output of `npx envinfo --binaries --system --npmPackages=sharp --npmGlobalPackages=sharp` here. -->

View File

@@ -7,14 +7,43 @@ labels: triage
<!-- If this issue relates to installation, please use https://github.com/lovell/sharp/issues/new?labels=installation&template=installation.md instead. -->
Are you using the latest version? Is the version currently in use as reported by `npm ls sharp` the same as the latest version as reported by `npm view sharp dist-tags.latest`?
## Possible bug
What are the steps to reproduce?
### Is this a possible bug in a feature of sharp, unrelated to installation?
What is the expected behaviour?
<!-- Please place an [x] in the box to confirm. -->
Are you able to provide a minimal, standalone code sample, without other dependencies, that demonstrates this problem?
- [ ] Running `npm install sharp` completes without error.
- [ ] Running `node -e "require('sharp')"` completes without error.
Are you able to provide a sample image that helps explain the problem?
If you cannot confirm both of these, please open an [installation issue](https://github.com/lovell/sharp/issues/new?labels=installation&template=installation.md) instead.
What is the output of running `npx envinfo --binaries --system`?
### Are you using the latest version of sharp?
<!-- Please place an [x] in the box to confirm. -->
- [ ] I am using the latest version of `sharp` as reported by `npm view sharp dist-tags.latest`.
If you cannot confirm this, please upgrade to the latest version and try again before opening an issue.
If you are using another package which depends on a version of `sharp` that is not the latest, please open an issue against that package instead.
### What is the output of running `npx envinfo --binaries --system --npmPackages=sharp --npmGlobalPackages=sharp`?
<!-- Please provide output of the above command here. -->
### What are the steps to reproduce?
<!-- Please enter steps to reproduce here. -->
### What is the expected behaviour?
<!-- Please enter the expected behaviour here. -->
### Please provide a minimal, standalone code sample, without other dependencies, that demonstrates this problem
<!-- Please provide either formatted code or a link to a repo/gist that allows someone else to reproduce here. -->
### Please provide sample image(s) that help explain this problem
<!-- Please provide links to one or more images here. -->

View File

@@ -7,10 +7,20 @@ labels: question
<!-- If this issue relates to installation, please use https://github.com/lovell/sharp/issues/new?labels=installation&template=installation.md instead. -->
What are you trying to achieve?
## Question about an existing feature
Have you searched for similar questions?
### What are you trying to achieve?
Are you able to provide a minimal, standalone code sample that demonstrates this question?
<!-- Please provide context here. -->
Are you able to provide a sample image that helps explain the question?
### When you searched for similar issues, what did you find that might be related?
<!-- Please demonstrate your research here. -->
### Please provide a minimal, standalone code sample, without other dependencies, that demonstrates this question
<!-- Please provide either formatted code or a link to a repo/gist that helps someone else understand here. -->
### Please provide sample image(s) that help explain this question
<!-- Please provide links to one or more images here. -->

View File

@@ -5,6 +5,15 @@ on:
jobs:
CI:
runs-on: macos-m1
strategy:
fail-fast: false
matrix:
include:
- nodejs_version: 12
nodejs_architecture: x64
- nodejs_version: 16
nodejs_architecture: arm64
prebuild: true
defaults:
run:
shell: /usr/bin/arch -arch arm64e /bin/bash -l {0}
@@ -12,8 +21,8 @@ jobs:
- name: Dependencies
uses: actions/setup-node@v2
with:
node-version: 16
architecture: arm64
node-version: ${{ matrix.nodejs_version }}
architecture: ${{ matrix.nodejs_architecture }}
- name: Checkout
uses: actions/checkout@v2
- name: Install
@@ -21,7 +30,7 @@ jobs:
- name: Test
run: npm test
- name: Prebuild
if: startsWith(github.ref, 'refs/tags/')
if: matrix.prebuild && startsWith(github.ref, 'refs/tags/')
env:
prebuild_upload: ${{ secrets.GITHUB_TOKEN }}
run: npx prebuild --runtime napi --target 5

View File

@@ -33,17 +33,33 @@ jobs:
- os: macos-10.15
nodejs_version: 12
prebuild: true
nodejs_arch: x64
- os: macos-10.15
nodejs_version: 14
nodejs_arch: x64
- os: macos-10.15
nodejs_version: 16
nodejs_arch: x64
- os: windows-2019
nodejs_version: 12
nodejs_arch: x86
prebuild: true
- os: windows-2019
nodejs_version: 14
nodejs_arch: x86
- os: windows-2019
nodejs_version: 16
nodejs_arch: x86
- os: windows-2019
nodejs_version: 12
nodejs_arch: x64
prebuild: true
- os: windows-2019
nodejs_version: 14
nodejs_arch: x64
- os: windows-2019
nodejs_version: 16
nodejs_arch: x64
steps:
- name: Dependencies (Linux glibc)
if: contains(matrix.container, 'centos')
@@ -57,9 +73,10 @@ jobs:
run: apk add build-base git python3 --update-cache
- name: Dependencies (macOS, Windows)
if: contains(matrix.os, 'macos') || contains(matrix.os, 'windows')
uses: actions/setup-node@v1
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.nodejs_version }}
architecture: ${{ matrix.nodejs_arch }}
- name: Checkout
uses: actions/checkout@v2
- name: Fix working directory ownership

View File

@@ -1,10 +1,10 @@
# sharp
<img src="https://cdn.jsdelivr.net/gh/lovell/sharp@master/docs/image/sharp-logo.svg" width="160" height="160" alt="sharp logo" align="right">
<img src="https://cdn.jsdelivr.net/gh/lovell/sharp@main/docs/image/sharp-logo.svg" width="160" height="160" alt="sharp logo" align="right">
The typical use case for this high speed Node.js module
is to convert large images in common formats to
smaller, web-friendly JPEG, PNG, WebP and AVIF images of varying dimensions.
smaller, web-friendly JPEG, PNG, WebP, GIF and AVIF images of varying dimensions.
Resizing an image is typically 4x-5x faster than using the
quickest ImageMagick and GraphicsMagick settings
@@ -95,15 +95,15 @@ readableStream
## Contributing
A [guide for contributors](https://github.com/lovell/sharp/blob/master/.github/CONTRIBUTING.md)
A [guide for contributors](https://github.com/lovell/sharp/blob/main/.github/CONTRIBUTING.md)
covers reporting bugs, requesting features and submitting code changes.
[![Test Coverage](https://coveralls.io/repos/lovell/sharp/badge.svg?branch=master)](https://coveralls.io/r/lovell/sharp?branch=master)
[![Test Coverage](https://coveralls.io/repos/lovell/sharp/badge.svg?branch=main)](https://coveralls.io/r/lovell/sharp?branch=main)
[![Node-API v5](https://img.shields.io/badge/Node--API-v5-green.svg)](https://nodejs.org/dist/latest/docs/api/n-api.html#n_api_n_api_version_matrix)
## Licensing
Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Lovell Fuller and contributors.
Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Lovell Fuller and contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@@ -1,17 +0,0 @@
os: Visual Studio 2019
version: "{build}"
build: off
platform: x86
environment:
matrix:
- nodejs_version: "12"
prebuild: true
- nodejs_version: "14"
- nodejs_version: "16"
install:
- ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version)
- npm install --build-from-source
test_script:
- npm test
on_success:
- if [%prebuild%] == [true] if [%APPVEYOR_REPO_TAG%] == [true] npx prebuild --runtime napi --target 5

View File

@@ -1,10 +1,10 @@
# sharp
<img src="https://cdn.jsdelivr.net/gh/lovell/sharp@master/docs/image/sharp-logo.svg" width="160" height="160" alt="sharp logo" align="right">
<img src="https://cdn.jsdelivr.net/gh/lovell/sharp@main/docs/image/sharp-logo.svg" width="160" height="160" alt="sharp logo" align="right">
The typical use case for this high speed Node.js module
is to convert large images in common formats to
smaller, web-friendly JPEG, PNG, AVIF and WebP images of varying dimensions.
smaller, web-friendly JPEG, PNG, WebP, GIF and AVIF images of varying dimensions.
Resizing an image is typically 4x-5x faster than using the
quickest ImageMagick and GraphicsMagick settings
@@ -21,9 +21,9 @@ do not require any additional install or runtime dependencies.
### Formats
This module supports reading JPEG, PNG, WebP, AVIF, TIFF, GIF and SVG images.
This module supports reading JPEG, PNG, WebP, GIF, AVIF, TIFF and SVG images.
Output images can be in JPEG, PNG, WebP, AVIF and TIFF formats as well as uncompressed raw pixel data.
Output images can be in JPEG, PNG, WebP, GIF, AVIF and TIFF formats as well as uncompressed raw pixel data.
Streams, Buffer objects and the filesystem can be used for input and output.
@@ -65,12 +65,12 @@ as [pngcrush](https://pmt.sourceforge.io/pngcrush/).
### Contributing
A [guide for contributors](https://github.com/lovell/sharp/blob/master/.github/CONTRIBUTING.md)
A [guide for contributors](https://github.com/lovell/sharp/blob/main/.github/CONTRIBUTING.md)
covers reporting bugs, requesting features and submitting code changes.
### Licensing
Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Lovell Fuller and contributors.
Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Lovell Fuller and contributors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@@ -9,7 +9,13 @@ An alpha channel may be present and will be unchanged by the operation.
* `rgb` **([string][1] | [Object][2])** parsed by the [color][3] module to extract chroma values.
<!---->
### Examples
```javascript
const output = await sharp(input)
.tint({ r: 255, g: 240, b: 16 })
.toBuffer();
```
* Throws **[Error][4]** Invalid parameter
@@ -28,6 +34,12 @@ An alpha channel may be present, and will be unchanged by the operation.
* `greyscale` **[Boolean][5]** (optional, default `true`)
### Examples
```javascript
const output = await sharp(input).greyscale().toBuffer();
```
Returns **Sharp**
## grayscale

View File

@@ -41,11 +41,29 @@ and [https://www.cairographics.org/operators/][2]
* `images[].raw.width` **[Number][7]?**
* `images[].raw.height` **[Number][7]?**
* `images[].raw.channels` **[Number][7]?**
* `images[].animated` **[boolean][9]** Set to `true` to read all frames/pages of an animated image. (optional, default `false`)
* `images[].failOnError` **[boolean][9]** @see [constructor parameters][10] (optional, default `true`)
* `images[].limitInputPixels` **([number][7] | [boolean][9])** @see [constructor parameters][10] (optional, default `268402689`)
### Examples
```javascript
await sharp(background)
.composite([
{ input: layer1, gravity: 'northwest' },
{ input: layer2, gravity: 'southeast' },
])
.toFile('combined.png');
```
```javascript
const output = await sharp('input.gif', { animated: true })
.composite([
{ input: 'overlay.png', tile: true, blend: 'saturate' }
])
.toBuffer();
```
```javascript
sharp('input.png')
.rotate(180)

View File

@@ -4,7 +4,7 @@
Constructor factory to create an instance of `sharp`, to which further methods are chained.
JPEG, PNG, WebP, AVIF or TIFF format image data can be streamed out from this object.
JPEG, PNG, WebP, GIF, AVIF or TIFF format image data can be streamed out from this object.
When using Stream based output, derived attributes are available from the `info` event.
Non-critical problems encountered during processing are emitted as `warning` events.
@@ -25,6 +25,7 @@ Implements the [stream.Duplex][1] class.
* `options.limitInputPixels` **([number][15] | [boolean][14])** Do not process input images where the number of pixels
(width x height) exceeds this limit. Assumes image dimensions contained in the input metadata can be trusted.
An integral Number of pixels, zero or false to remove limit, true to use default limit of 268402689 (0x3FFF x 0x3FFF). (optional, default `268402689`)
* `options.unlimited` **[boolean][14]** Set this to `true` to remove safety features that help prevent memory exhaustion (SVG, PNG). (optional, default `false`)
* `options.sequentialRead` **[boolean][14]** Set this to `true` to use sequential rather than random access where possible.
This can reduce memory usage and might improve performance on some systems. (optional, default `false`)
* `options.density` **[number][15]** number representing the DPI for vector images in the range 1 to 100000. (optional, default `72`)

View File

@@ -2,13 +2,17 @@
## metadata
Fast access to (uncached) image metadata without decoding any compressed image data.
Fast access to (uncached) image metadata without decoding any compressed pixel data.
This is taken from the header of the input image.
It does not include operations, such as resize, to be applied to the output image.
A `Promise` is returned when `callback` is not provided.
* `format`: Name of decoder used to decompress image data e.g. `jpeg`, `png`, `webp`, `gif`, `svg`
* `size`: Total size of image in bytes, for Stream and Buffer input only
* `width`: Number of pixels wide (EXIF orientation is not taken into consideration)
* `height`: Number of pixels high (EXIF orientation is not taken into consideration)
* `width`: Number of pixels wide (EXIF orientation is not taken into consideration, see example below)
* `height`: Number of pixels high (EXIF orientation is not taken into consideration, see example below)
* `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...][1]
* `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK
* `depth`: Name of pixel depth format e.g. `uchar`, `char`, `ushort`, `float` [...][2]
@@ -24,6 +28,7 @@ A `Promise` is returned when `callback` is not provided.
* `subifds`: Number of Sub Image File Directories in an OME-TIFF image
* `background`: Default background colour, if present, for PNG (bKGD) and GIF images, either an RGB Object or a single greyscale value
* `compression`: The encoder used to compress an HEIF file, `av1` (AVIF) or `hevc` (HEIC)
* `resolutionUnit`: The unit of resolution (density), either `inch` or `cm`, if present
* `hasProfile`: Boolean indicating the presence of an embedded ICC profile
* `hasAlpha`: Boolean indicating the presence of an alpha transparency channel
* `orientation`: Number value of the EXIF Orientation header, if present
@@ -39,6 +44,10 @@ A `Promise` is returned when `callback` is not provided.
### Examples
```javascript
const metadata = await sharp(input).metadata();
```
```javascript
const image = sharp(inputJpg);
image
@@ -54,6 +63,18 @@ image
});
```
```javascript
// Based on EXIF rotation metadata, get the right-side-up width and height:
const size = getNormalSize(await sharp(input).metadata());
function getNormalSize({ width, height, orientation }) {
return orientation || 0 >= 5
? { width: height, height: width }
: { width, height };
}
```
Returns **([Promise][5]<[Object][6]> | Sharp)**
## stats
@@ -73,9 +94,9 @@ A `Promise` is returned when `callback` is not provided.
* `maxX` (x-coordinate of one of the pixel where the maximum lies)
* `maxY` (y-coordinate of one of the pixel where the maximum lies)
* `isOpaque`: Is the image fully opaque? Will be `true` if the image has no alpha channel or if every pixel is fully opaque.
* `entropy`: Histogram-based estimation of greyscale entropy, discarding alpha channel if any (experimental)
* `sharpness`: Estimation of greyscale sharpness based on the standard deviation of a Laplacian convolution, discarding alpha channel if any (experimental)
* `dominant`: Object containing most dominant sRGB colour based on a 4096-bin 3D histogram (experimental)
* `entropy`: Histogram-based estimation of greyscale entropy, discarding alpha channel if any.
* `sharpness`: Estimation of greyscale sharpness based on the standard deviation of a Laplacian convolution, discarding alpha channel if any.
* `dominant`: Object containing most dominant sRGB colour based on a 4096-bin 3D histogram.
**Note**: Statistics are derived from the original input image. Any operations performed on the image must first be
written to a buffer in order to run `stats` on the result (see third example).

View File

@@ -53,6 +53,12 @@ The use of `flip` implies the removal of the EXIF `Orientation` tag, if any.
* `flip` **[Boolean][6]** (optional, default `true`)
### Examples
```javascript
const output = await sharp(input).flip().toBuffer();
```
Returns **Sharp**
## flop
@@ -64,6 +70,12 @@ The use of `flop` implies the removal of the EXIF `Orientation` tag, if any.
* `flop` **[Boolean][6]** (optional, default `true`)
### Examples
```javascript
const output = await sharp(input).flop().toBuffer();
```
Returns **Sharp**
## affine
@@ -129,13 +141,43 @@ When used without parameters, performs a fast, mild sharpen of the output image.
When a `sigma` is provided, performs a slower, more accurate sharpen of the L channel in the LAB colour space.
Separate control over the level of sharpening in "flat" and "jagged" areas is available.
See [libvips sharpen][8] operation.
### Parameters
* `sigma` **[number][1]?** the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
* `flat` **[number][1]** the level of sharpening to apply to "flat" areas. (optional, default `1.0`)
* `jagged` **[number][1]** the level of sharpening to apply to "jagged" areas. (optional, default `2.0`)
* `options` **([Object][2] | [number][1])?** if present, is an Object with attributes or (deprecated) a number for `options.sigma`.
<!---->
* `options.sigma` **[number][1]?** the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
* `options.m1` **[number][1]** the level of sharpening to apply to "flat" areas. (optional, default `1.0`)
* `options.m2` **[number][1]** the level of sharpening to apply to "jagged" areas. (optional, default `2.0`)
* `options.x1` **[number][1]** threshold between "flat" and "jagged" (optional, default `2.0`)
* `options.y2` **[number][1]** maximum amount of brightening. (optional, default `10.0`)
* `options.y3` **[number][1]** maximum amount of darkening. (optional, default `20.0`)
* `flat` **[number][1]?** (deprecated) see `options.m1`.
* `jagged` **[number][1]?** (deprecated) see `options.m2`.
### Examples
```javascript
const data = await sharp(input).sharpen().toBuffer();
```
```javascript
const data = await sharp(input).sharpen({ sigma: 2 }).toBuffer();
```
```javascript
const data = await sharp(input)
.sharpen({
sigma: 2,
m1: 0
m2: 3,
x1: 3,
y2: 15,
y3: 15,
})
.toBuffer();
```
* Throws **[Error][5]** Invalid parameters
@@ -150,7 +192,15 @@ When used without parameters the default window is 3x3.
* `size` **[number][1]** square mask size: size x size (optional, default `3`)
<!---->
### Examples
```javascript
const output = await sharp(input).median().toBuffer();
```
```javascript
const output = await sharp(input).median(5).toBuffer();
```
* Throws **[Error][5]** Invalid parameters
@@ -159,14 +209,28 @@ Returns **Sharp**
## blur
Blur the image.
When used without parameters, performs a fast, mild blur of the output image.
When used without parameters, performs a fast 3x3 box blur (equivalent to a box linear filter).
When a `sigma` is provided, performs a slower, more accurate Gaussian blur.
### Parameters
* `sigma` **[number][1]?** a value between 0.3 and 1000 representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
<!---->
### Examples
```javascript
const boxBlurred = await sharp(input)
.blur()
.toBuffer();
```
```javascript
const gaussianBlurred = await sharp(input)
.blur(5)
.toBuffer();
```
* Throws **[Error][5]** Invalid parameters
@@ -176,7 +240,7 @@ Returns **Sharp**
Merge alpha transparency channel, if any, with a background, then remove the alpha channel.
See also [removeAlpha][8].
See also [removeAlpha][9].
### Parameters
@@ -235,6 +299,12 @@ Enhance output image contrast by stretching its luminance to cover the full dyna
* `normalise` **[Boolean][6]** (optional, default `true`)
### Examples
```javascript
const output = await sharp(input).normalise().toBuffer();
```
Returns **Sharp**
## normalize
@@ -245,12 +315,18 @@ Alternative spelling of normalise.
* `normalize` **[Boolean][6]** (optional, default `true`)
### Examples
```javascript
const output = await sharp(input).normalize().toBuffer();
```
Returns **Sharp**
## clahe
Perform contrast limiting adaptive histogram equalization
[CLAHE][9].
[CLAHE][10].
This will, in general, enhance the clarity of the image by bringing out darker details.
@@ -264,7 +340,16 @@ This will, in general, enhance the clarity of the image by bringing out darker d
cumulative histogram. A value of 0 disables contrast limiting. Valid values
are integers in the range 0-100 (inclusive) (optional, default `3`)
<!---->
### Examples
```javascript
const output = await sharp(input)
.clahe({
width: 3,
height: 3,
})
.toBuffer();
```
* Throws **[Error][5]** Invalid parameters
@@ -335,7 +420,7 @@ the selected bitwise boolean `operation` between the corresponding pixels of the
### Parameters
* `operand` **([Buffer][10] | [string][3])** Buffer containing image data or string containing the path to an image file.
* `operand` **([Buffer][11] | [string][3])** Buffer containing image data or string containing the path to an image file.
* `operator` **[string][3]** one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively.
* `options` **[Object][2]?**
@@ -416,28 +501,41 @@ brightness is multiplicative whereas lightness is additive.
### Examples
```javascript
sharp(input)
// increase brightness by a factor of 2
const output = await sharp(input)
.modulate({
brightness: 2 // increase brightness by a factor of 2
});
brightness: 2
})
.toBuffer();
```
sharp(input)
```javascript
// hue-rotate by 180 degrees
const output = await sharp(input)
.modulate({
hue: 180 // hue-rotate by 180 degrees
});
hue: 180
})
.toBuffer();
```
sharp(input)
```javascript
// increase lightness by +50
const output = await sharp(input)
.modulate({
lightness: 50 // increase lightness by +50
});
lightness: 50
})
.toBuffer();
```
```javascript
// decreate brightness and saturation while also hue-rotating by 90 degrees
sharp(input)
const output = await sharp(input)
.modulate({
brightness: 0.5,
saturation: 0.5,
hue: 90
});
hue: 90,
})
.toBuffer();
```
Returns **Sharp**
@@ -460,8 +558,10 @@ Returns **Sharp**
[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
[8]: /api-channel#removealpha
[8]: https://www.libvips.org/API/current/libvips-convolution.html#vips-sharpen
[9]: https://en.wikipedia.org/wiki/Adaptive_histogram_equalization#Contrast_Limited_AHE
[9]: /api-channel#removealpha
[10]: https://nodejs.org/api/buffer.html
[10]: https://en.wikipedia.org/wiki/Adaptive_histogram_equalization#Contrast_Limited_AHE
[11]: https://nodejs.org/api/buffer.html

View File

@@ -5,7 +5,7 @@
Write output image data to a file.
If an explicit output format is not selected, it will be inferred from the extension,
with JPEG, PNG, WebP, AVIF, TIFF, DZI, and libvips' V format supported.
with JPEG, PNG, WebP, AVIF, TIFF, GIF, DZI, and libvips' V format supported.
Note that raw pixel data is only supported for buffer output.
By default all metadata will be removed, which includes EXIF-based orientation.
@@ -44,9 +44,9 @@ Returns **[Promise][5]<[Object][6]>** when no callback is provided
## toBuffer
Write output to a Buffer.
JPEG, PNG, WebP, AVIF, TIFF and raw pixel data output are supported.
JPEG, PNG, WebP, AVIF, TIFF, GIF and raw pixel data output are supported.
If no explicit format is set, the output format will match the input image, except GIF and SVG input which become PNG output.
If no explicit format is set, the output format will match the input image, except SVG input which becomes PNG output.
By default all metadata will be removed, which includes EXIF-based orientation.
See [withMetadata][1] for control over this.
@@ -147,8 +147,9 @@ const data = await sharp(input)
}
})
.toBuffer();
```
* @example
```javascript
// Set output metadata to 96 DPI
const data = await sharp(input)
.withMetadata({ density: 96 })
@@ -243,6 +244,7 @@ Set `palette` to `true` for slower, indexed PNG output.
* `options.adaptiveFiltering` **[boolean][7]** use adaptive row filtering (optional, default `false`)
* `options.palette` **[boolean][7]** quantise to a palette-based image with alpha transparency support (optional, default `false`)
* `options.quality` **[number][9]** use the lowest number of colours needed to achieve given quality, sets `palette` to `true` (optional, default `100`)
* `options.effort` **[number][9]** CPU effort, between 1 (fastest) and 10 (slowest), sets `palette` to `true` (optional, default `7`)
* `options.colours` **[number][9]** maximum number of palette entries, sets `palette` to `true` (optional, default `256`)
* `options.colors` **[number][9]** alternative spelling of `options.colours`, sets `palette` to `true` (optional, default `256`)
* `options.dither` **[number][9]** level of Floyd-Steinberg error diffusion, sets `palette` to `true` (optional, default `1.0`)
@@ -281,10 +283,9 @@ Use these WebP options for output image.
* `options.lossless` **[boolean][7]** use lossless compression mode (optional, default `false`)
* `options.nearLossless` **[boolean][7]** use near_lossless compression mode (optional, default `false`)
* `options.smartSubsample` **[boolean][7]** use high quality chroma subsampling (optional, default `false`)
* `options.reductionEffort` **[number][9]** level of CPU effort to reduce file size, integer 0-6 (optional, default `4`)
* `options.pageHeight` **[number][9]?** page height for animated output
* `options.effort` **[number][9]** CPU effort, between 0 (fastest) and 6 (slowest) (optional, default `4`)
* `options.loop` **[number][9]** number of animation iterations, use 0 for infinite animation (optional, default `0`)
* `options.delay` **[Array][10]<[number][9]>?** list of delays between animation frames (in milliseconds)
* `options.delay` **([number][9] | [Array][10]<[number][9]>)?** delay(s) between animation frames (in milliseconds)
* `options.force` **[boolean][7]** force WebP output, otherwise attempt to use input format (optional, default `true`)
### Examples
@@ -299,7 +300,7 @@ const data = await sharp(input)
```javascript
// Optimise the file size of an animated WebP
const outputWebp = await sharp(inputWebp, { animated: true })
.webp({ reductionEffort: 6 })
.webp({ effort: 6 })
.toBuffer();
```
@@ -309,27 +310,53 @@ Returns **Sharp**
## gif
Use these GIF options for output image.
Use these GIF options for the output image.
Requires libvips compiled with support for ImageMagick or GraphicsMagick.
The prebuilt binaries do not include this - see
[installing a custom libvips][11].
The first entry in the palette is reserved for transparency.
### Parameters
* `options` **[Object][6]?** output options
* `options.pageHeight` **[number][9]?** page height for animated output
* `options.colours` **[number][9]** maximum number of palette entries, including transparency, between 2 and 256 (optional, default `256`)
* `options.colors` **[number][9]** alternative spelling of `options.colours` (optional, default `256`)
* `options.effort` **[number][9]** CPU effort, between 1 (fastest) and 10 (slowest) (optional, default `7`)
* `options.dither` **[number][9]** level of Floyd-Steinberg error diffusion, between 0 (least) and 1 (most) (optional, default `1.0`)
* `options.loop` **[number][9]** number of animation iterations, use 0 for infinite animation (optional, default `0`)
* `options.delay` **[Array][10]<[number][9]>?** list of delays between animation frames (in milliseconds)
* `options.delay` **([number][9] | [Array][10]<[number][9]>)?** delay(s) between animation frames (in milliseconds)
* `options.force` **[boolean][7]** force GIF output, otherwise attempt to use input format (optional, default `true`)
<!---->
### Examples
```javascript
// Convert PNG to GIF
await sharp(pngBuffer)
.gif()
.toBuffer());
```
```javascript
// Convert animated WebP to animated GIF
await sharp('animated.webp', { animated: true })
.toFile('animated.gif');
```
```javascript
// Create a 128x128, cropped, non-dithered, animated thumbnail of an animated GIF
const out = await sharp('in.gif', { animated: true })
.resize({ width: 128, height: 128 })
.gif({ dither: 0 })
.toBuffer();
```
* Throws **[Error][4]** Invalid options
Returns **Sharp**
**Meta**
* **since**: 0.30.0
## jp2
Use these JP2 options for output image.
@@ -379,6 +406,8 @@ Returns **Sharp**
Use these TIFF options for output image.
The `density` can be set in pixels/inch via [withMetadata][1] instead of providing `xres` and `yres` in pixels/mm.
### Parameters
* `options` **[Object][6]?** output options
@@ -393,6 +422,7 @@ Use these TIFF options for output image.
* `options.tileHeight` **[number][9]** vertical tile size (optional, default `256`)
* `options.xres` **[number][9]** horizontal resolution in pixels/mm (optional, default `1.0`)
* `options.yres` **[number][9]** vertical resolution in pixels/mm (optional, default `1.0`)
* `options.resolutionUnit` **[string][2]** resolution unit options: inch, cm (optional, default `'inch'`)
* `options.bitdepth` **[number][9]** reduce bitdepth to 1, 2 or 4 bit (optional, default `8`)
### Examples
@@ -427,7 +457,7 @@ AVIF image sequences are not supported.
* `options.quality` **[number][9]** quality, integer 1-100 (optional, default `50`)
* `options.lossless` **[boolean][7]** use lossless compression (optional, default `false`)
* `options.speed` **[number][9]** CPU effort vs file size, 0 (slowest/smallest) to 9 (fastest/largest) (optional, default `5`)
* `options.effort` **[number][9]** CPU effort, between 0 (fastest) and 9 (slowest) (optional, default `4`)
* `options.chromaSubsampling` **[string][2]** set to '4:2:0' to use chroma subsampling (optional, default `'4:4:4'`)
<!---->
@@ -454,7 +484,7 @@ globally-installed libvips compiled with support for libheif, libde265 and x265.
* `options.quality` **[number][9]** quality, integer 1-100 (optional, default `50`)
* `options.compression` **[string][2]** compression format: av1, hevc (optional, default `'av1'`)
* `options.lossless` **[boolean][7]** use lossless compression (optional, default `false`)
* `options.speed` **[number][9]** CPU effort vs file size, 0 (slowest/smallest) to 9 (fastest/largest) (optional, default `5`)
* `options.effort` **[number][9]** CPU effort, between 0 (fastest) and 9 (slowest) (optional, default `4`)
* `options.chromaSubsampling` **[string][2]** set to '4:2:0' to use chroma subsampling (optional, default `'4:4:4'`)
<!---->
@@ -506,8 +536,6 @@ Use tile-based deep zoom (image pyramid) output.
Set the format and options for tile images via the `toFormat`, `jpeg`, `png` or `webp` functions.
Use a `.zip` or `.szi` file extension with `toFile` to write to a compressed archive file format.
Warning: multiple sharp instances concurrently producing tile output can expose a possible race condition in some versions of libgsf.
### Parameters
* `options` **[Object][6]?**
@@ -519,10 +547,10 @@ Warning: multiple sharp instances concurrently producing tile output can expose
* `options.depth` **[string][2]?** how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout.
* `options.skipBlanks` **[number][9]** threshold to skip tile generation, a value 0 - 255 for 8-bit images or 0 - 65535 for 16-bit images (optional, default `-1`)
* `options.container` **[string][2]** tile container, with value `fs` (filesystem) or `zip` (compressed file). (optional, default `'fs'`)
* `options.layout` **[string][2]** filesystem layout, possible values are `dz`, `iiif`, `zoomify` or `google`. (optional, default `'dz'`)
* `options.layout` **[string][2]** filesystem layout, possible values are `dz`, `iiif`, `iiif3`, `zoomify` or `google`. (optional, default `'dz'`)
* `options.centre` **[boolean][7]** centre image in tile. (optional, default `false`)
* `options.center` **[boolean][7]** alternative spelling of centre. (optional, default `false`)
* `options.id` **[string][2]** when `layout` is `iiif`, sets the `@id` attribute of `info.json` (optional, default `'https://example.com/iiif'`)
* `options.id` **[string][2]** when `layout` is `iiif`/`iiif3`, sets the `@id`/`id` attribute of `info.json` (optional, default `'https://example.com/iiif'`)
### Examples
@@ -542,6 +570,26 @@ sharp('input.tiff')
Returns **Sharp**
## timeout
Set a timeout for processing, in seconds.
Use a value of zero to continue processing indefinitely, the default behaviour.
The clock starts when libvips opens an input image for processing.
Time spent waiting for a libuv thread to become available is not included.
### Parameters
* `options` **[Object][6]**
* `options.seconds` **[number][9]** Number of seconds after which processing will be stopped
Returns **Sharp**
**Meta**
* **since**: 0.29.2
[1]: #withmetadata
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String

View File

@@ -49,6 +49,7 @@ Possible interpolation kernels are:
* `options.background` **([String][10] | [Object][9])** background colour when using a `fit` of `contain`, parsed by the [color][11] module, defaults to black without transparency. (optional, default `{r:0,g:0,b:0,alpha:1}`)
* `options.kernel` **[String][10]** the kernel to use for image reduction. (optional, default `'lanczos3'`)
* `options.withoutEnlargement` **[Boolean][12]** do not enlarge if the width *or* height are already less than the specified dimensions, equivalent to GraphicsMagick's `>` geometry option. (optional, default `false`)
* `options.withoutReduction` **[Boolean][12]** do not reduce if the width *or* height are already greater than the specified dimensions, equivalent to GraphicsMagick's `<` geometry option. (optional, default `false`)
* `options.fastShrinkOnLoad` **[Boolean][12]** take greater advantage of the JPEG and WebP shrink-on-load feature, which can lead to a slight moiré pattern on some images. (optional, default `true`)
### Examples
@@ -117,6 +118,21 @@ sharp(input)
});
```
```javascript
sharp(input)
.resize(200, 200, {
fit: sharp.fit.outside,
withoutReduction: true
})
.toFormat('jpeg')
.toBuffer()
.then(function(outputBuffer) {
// outputBuffer contains JPEG image data
// of at least 200 pixels wide and 200 pixels high while maintaining aspect ratio
// and no smaller than the input image
});
```
```javascript
const scaleByHalf = await sharp(input)
.metadata()

View File

@@ -52,6 +52,17 @@ An Object containing the version numbers of libvips and its dependencies.
console.log(sharp.versions);
```
## vendor
An Object containing the platform and architecture
of the current and installed vendored binaries.
### Examples
```javascript
console.log(sharp.vendor);
```
## cache
Gets or, when options are provided, sets the limits of *libvips'* operation cache.
@@ -177,13 +188,13 @@ Returns **[boolean][10]**
[5]: http://en.wikipedia.org/wiki/Bicubic_interpolation
[6]: https://github.com/jcupitt/libvips/blob/master/libvips/resample/lbb.cpp#L100
[6]: https://github.com/libvips/libvips/blob/master/libvips/resample/lbb.cpp#L100
[7]: http://en.wikipedia.org/wiki/Acutance
[8]: http://eprints.soton.ac.uk/268086/
[9]: https://github.com/jcupitt/libvips/blob/master/libvips/resample/vsqbs.cpp#L48
[9]: https://github.com/libvips/libvips/blob/master/libvips/resample/vsqbs.cpp#L48
[10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean

View File

@@ -1,9 +1,131 @@
# Changelog
## v0.30 - *dresser*
Requires libvips v8.12.2
### v0.30.3 - 14th March 2022
* Allow `sharpen` options to be provided more consistently as an Object.
[#2561](https://github.com/lovell/sharp/issues/2561)
* Expose `x1`, `y2` and `y3` parameters of `sharpen` operation.
[#2935](https://github.com/lovell/sharp/issues/2935)
* Prevent double unpremultiply with some composite blend modes (regression in 0.30.2).
[#3118](https://github.com/lovell/sharp/issues/3118)
### v0.30.2 - 2nd March 2022
* Improve performance and accuracy when compositing multiple images.
[#2286](https://github.com/lovell/sharp/issues/2286)
* Expand pkgconfig search path for wider BSD support.
[#3106](https://github.com/lovell/sharp/issues/3106)
* Ensure Windows C++ runtime is linked statically (regression in 0.30.0).
[#3110](https://github.com/lovell/sharp/pull/3110)
[@kleisauke](https://github.com/kleisauke)
* Temporarily ignore greyscale ICC profiles to workaround lcms bug.
[#3112](https://github.com/lovell/sharp/issues/3112)
### v0.30.1 - 9th February 2022
* Allow use of `toBuffer` and `toFile` on the same instance.
[#3044](https://github.com/lovell/sharp/issues/3044)
* Skip shrink-on-load for known libjpeg rounding errors.
[#3066](https://github.com/lovell/sharp/issues/3066)
[@kleisauke](https://github.com/kleisauke)
* Ensure withoutReduction does not interfere with contain/crop/embed.
[#3081](https://github.com/lovell/sharp/pull/3081)
[@kleisauke](https://github.com/kleisauke)
* Ensure affine interpolator is correctly finalised.
[#3083](https://github.com/lovell/sharp/pull/3083)
[@kleisauke](https://github.com/kleisauke)
### v0.30.0 - 1st February 2022
* Add support for GIF output to prebuilt binaries.
* Reduce minimum Linux ARM64v8 glibc requirement to 2.17.
* Verify prebuilt binaries with a Subresource Integrity check.
* Standardise WebP `effort` option name, deprecate `reductionEffort`.
* Standardise HEIF `effort` option name, deprecate `speed`.
* Add support for IIIF v3 tile-based output.
* Expose control over CPU effort for palette-based PNG output.
[#2541](https://github.com/lovell/sharp/issues/2541)
* Improve animated (multi-page) image resize and extract.
[#2789](https://github.com/lovell/sharp/pull/2789)
[@kleisauke](https://github.com/kleisauke)
* Expose platform and architecture of vendored binaries as `sharp.vendor`.
[#2928](https://github.com/lovell/sharp/issues/2928)
* Ensure 16-bit PNG output uses correct bitdepth.
[#2958](https://github.com/lovell/sharp/pull/2958)
[@gforge](https://github.com/gforge)
* Properly emit close events for duplex streams.
[#2976](https://github.com/lovell/sharp/pull/2976)
[@driannaude](https://github.com/driannaude)
* Expose `unlimited` option for SVG and PNG input, switches off safety features.
[#2984](https://github.com/lovell/sharp/issues/2984)
* Add `withoutReduction` option to resize operation.
[#3006](https://github.com/lovell/sharp/pull/3006)
[@christopherbradleybanks](https://github.com/christopherbradleybanks)
* Add `resolutionUnit` as `tiff` option and expose in metadata.
[#3023](https://github.com/lovell/sharp/pull/3023)
[@ompal-sisodiya](https://github.com/ompal-sisodiya)
* Ensure rotate-then-extract works with EXIF mirroring.
[#3024](https://github.com/lovell/sharp/issues/3024)
## v0.29 - *circle*
Requires libvips v8.11.3
### v0.29.3 - 14th November 2021
* Ensure correct dimensions when containing image resized to 1px.
[#2951](https://github.com/lovell/sharp/issues/2951)
* Impute TIFF `xres`/`yres` from `density` provided to `withMetadata`.
[#2952](https://github.com/lovell/sharp/pull/2952)
[@mbklein](https://github.com/mbklein)
### v0.29.2 - 21st October 2021
* Add `timeout` function to limit processing time.
* Ensure `sharp.versions` is populated from vendored libvips.
* Remove animation properties from single page images.
[#2890](https://github.com/lovell/sharp/issues/2890)
* Allow use of 'tif' to select TIFF output.
[#2893](https://github.com/lovell/sharp/pull/2893)
[@erf](https://github.com/erf)
* Improve error message on Windows for version conflict.
[#2918](https://github.com/lovell/sharp/pull/2918)
[@dkrnl](https://github.com/dkrnl)
* Throw error rather than exit when invalid binaries detected.
[#2931](https://github.com/lovell/sharp/issues/2931)
### v0.29.1 - 7th September 2021
* Add `lightness` option to `modulate` operation.

View File

@@ -223,4 +223,16 @@ Name: Tenpi
GitHub: https://github.com/Tenpi
Name: Zaruike
https://github.com/Zaruike
GitHub: https://github.com/Zaruike
Name: Erlend F
GitHub: https://github.com/erf
Name: Drian Naude
GitHub: https://github.com/driannaude
Name: Max Gordon
GitHub: https://github.com/gforge
Name: Chris Banks
GitHub: https://github.com/christopherbradleybanks

BIN
docs/image/sharp-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 661 B

View File

@@ -4,21 +4,15 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="Resize large images in common formats to smaller, web-friendly JPEG, PNG, WebP and AVIF images of varying dimensions">
<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 http-equiv="Content-Security-Policy" content="default-src 'self'; object-src 'none'; style-src 'unsafe-inline';
img-src 'unsafe-inline' data: https://pixel.plumbing/px/ https://cdn.jsdelivr.net/gh/lovell/ https://www.google-analytics.com;
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;">
<link rel="icon" type="image/png" href="https://pixel.plumbing/px/32x32/sharp-logo.svg">
<link rel="apple-touch-icon-precomposed" sizes="152x152" href="https://pixel.plumbing/px/152x152/sharp-logo.svg">
<link rel="apple-touch-icon-precomposed" sizes="144x144" href="https://pixel.plumbing/px/144x144/sharp-logo.svg">
<link rel="apple-touch-icon-precomposed" sizes="120x120" href="https://pixel.plumbing/px/120x120/sharp-logo.svg">
<link rel="apple-touch-icon-precomposed" sizes="114x114" href="https://pixel.plumbing/px/114x114/sharp-logo.svg">
<link rel="apple-touch-icon-precomposed" sizes="72x72" href="https://pixel.plumbing/px/72x72/sharp-logo.svg">
<link rel="apple-touch-icon-precomposed" href="https://pixel.plumbing/px/57x57/sharp-logo.svg">
<link rel="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.png">
<link rel="author" href="/humans.txt" type="text/plain">
<link rel="dns-prefetch" href="https://pixel.plumbing">
<link rel="dns-prefetch" href="https://www.google-analytics.com">
<script type="application/ld+json">
{
@@ -35,7 +29,7 @@
"@type": "Person",
"name": "Lovell Fuller"
},
"copyrightYear": [2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021],
"copyrightYear": [2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022],
"license": "https://www.apache.org/licenses/LICENSE-2.0"
}
</script>
@@ -124,7 +118,7 @@
router: { mode: 'history' },
logo: '<div style="display:flex;align-items:center">'
+ '<strong>sharp</strong> '
+ '<img src="https://pixel.plumbing/px/16x16/sharp-logo.svg" style="padding:8px" alt="#"> '
+ '<img src="https://cdn.jsdelivr.net/gh/lovell/sharp@main/docs/image/sharp-logo.svg" style="padding:8px" width="32" height="32" alt="#"> '
+ '<span style="opacity:0.8;white-space:pre" class="shorten-strapline">High performance </span> '
+ '<span style="opacity:0.8">Node.js image processing</span> '
+ '</div>',

View File

@@ -18,28 +18,29 @@ Ready-compiled sharp and libvips binaries are provided for use on the most commo
* macOS x64 (>= 10.13)
* macOS ARM64
* Linux x64 (glibc >= 2.17, musl >= 1.1.24)
* Linux ARM64 (glibc >= 2.29, musl >= 1.1.24)
* Linux x64 (glibc >= 2.17, musl >= 1.1.24, CPU with SSE4.2)
* Linux ARM64 (glibc >= 2.17, musl >= 1.1.24)
* Windows x64
* Windows x86
An ~7MB tarball containing libvips and its most commonly used dependencies
is downloaded via HTTPS and stored within `node_modules/sharp/vendor` during `npm install`.
A ~7MB tarball containing libvips and its most commonly used dependencies
is downloaded via HTTPS, verified via Subresource Integrity
and decompressed into `node_modules/sharp/vendor` during `npm install`.
This provides support for the
JPEG, PNG, WebP, AVIF, TIFF, GIF (input) and SVG (input) image formats.
JPEG, PNG, WebP, AVIF, TIFF, GIF and SVG (input) image formats.
The following platforms have prebuilt libvips but not sharp:
* Linux ARMv6
* Linux ARMv7 (glibc >= 2.28)
* Linux ARMv6 (glibc >= 2.28)
* Windows ARM64
The following platforms require compilation of both libvips and sharp from source:
* Linux x86
* Linux x64 (glibc <= 2.16, includes RHEL/CentOS 6)
* Linux ARM64 (glibc <= 2.28)
* Linux ARMv7 (glibc <= 2.27, musl)
* Linux ARMv6 (glibc <= 2.27, musl)
* Linux PowerPC
* FreeBSD
* OpenBSD
@@ -52,7 +53,7 @@ See the [cross-platform](#cross-platform) section if this is not the case.
When using npm v6 or earlier, the `npm install --unsafe-perm` flag must be used when installing as `root` or a `sudo` user.
When using npm v7, the user running `npm install` must own the directory it is run in.
When using npm v7 or later, the user running `npm install` must own the directory it is run in.
The `npm install --ignore-scripts=false` flag must be used when `npm` has been configured to ignore installation scripts.
@@ -60,9 +61,7 @@ Check the output of running `npm install --verbose sharp` for useful error messa
## Apple M1
Prebuilt sharp and libvips binaries are provided for macOS on ARM64 from sharp v0.29.0.
Prebuilt libvips binaries were provided for macOS on ARM64 from sharp v0.28.0.
Prebuilt sharp and libvips binaries have been provided for macOS on ARM64 since sharp v0.29.0.
## Cross-platform
@@ -78,7 +77,7 @@ npm install --platform=... --arch=... --arm-version=... sharp
* `--platform`: one of `linux`, `linuxmusl`, `darwin` or `win32`.
* `--arch`: one of `x64`, `ia32`, `arm` or `arm64`.
* `--arm-version`: one of `6`, `7` or `8` (`arm` defaults to `6`, `arm64` defaults to `8`).
* `--sharp-install-force`: skip version compatibility checks.
* `--sharp-install-force`: skip version compatibility and Subresource Integrity checks.
These values can also be set via environment variables,
`npm_config_platform`, `npm_config_arch`, `npm_config_arm_version`
@@ -145,16 +144,16 @@ A mirror site based in China, provided by Alibaba, contains binaries for both sh
To use this either set the following configuration:
```sh
npm config set sharp_binary_host "https://npm.taobao.org/mirrors/sharp"
npm config set sharp_libvips_binary_host "https://npm.taobao.org/mirrors/sharp-libvips"
npm config set sharp_binary_host "https://npmmirror.com/mirrors/sharp"
npm config set sharp_libvips_binary_host "https://npmmirror.com/mirrors/sharp-libvips"
npm install sharp
```
or set the following environment variables:
```sh
npm_config_sharp_binary_host="https://npm.taobao.org/mirrors/sharp" \
npm_config_sharp_libvips_binary_host="https://npm.taobao.org/mirrors/sharp-libvips" \
npm_config_sharp_binary_host="https://npmmirror.com/mirrors/sharp" \
npm_config_sharp_libvips_binary_host="https://npmmirror.com/mirrors/sharp-libvips" \
npm install sharp
```
@@ -214,9 +213,11 @@ SHARP_IGNORE_GLOBAL_LIBVIPS=1 npm install --arch=x64 --platform=linux sharp
To get the best performance select the largest memory available.
A 1536 MB function provides ~12x more CPU time than a 128 MB function.
## Webpack
## Bundlers
Ensure sharp is added to the
### webpack
Ensure sharp is excluded from bundling via the
[externals](https://webpack.js.org/configuration/externals/)
configuration.
@@ -226,6 +227,25 @@ externals: {
}
```
### esbuild
Ensure sharp is excluded from bundling via the
[external](https://esbuild.github.io/api/#external)
configuration.
```js
buildSync({
entryPoints: ['app.js'],
bundle: true,
platform: 'node',
external: ['sharp'],
})
```
```sh
esbuild app.js --bundle --platform=node --external:sharp
```
## Worker threads
The main thread must call `require('sharp')`

View File

@@ -5,12 +5,12 @@ A test to benchmark the performance of this module relative to alternatives.
## The contenders
* [jimp](https://www.npmjs.com/package/jimp) v0.16.1 - Image processing in pure JavaScript. Provides bicubic interpolation.
* [mapnik](https://www.npmjs.org/package/mapnik) v4.5.8 - Whilst primarily a map renderer, Mapnik contains bitmap image utilities.
* [mapnik](https://www.npmjs.org/package/mapnik) v4.5.9 - Whilst primarily a map renderer, Mapnik contains bitmap image utilities.
* [imagemagick](https://www.npmjs.com/package/imagemagick) v0.1.3 - Supports filesystem only and "*has been unmaintained for a long time*".
* [gm](https://www.npmjs.com/package/gm) v1.23.1 - Fully featured wrapper around GraphicsMagick's `gm` command line utility.
* [@squoosh/lib](https://www.npmjs.com/package/@squoosh/lib) v0.4.0 - Image libraries transpiled to WebAssembly, includes GPLv3 code.
* [@squoosh/cli](https://www.npmjs.com/package/@squoosh/cli) v0.7.2 - Command line wrapper around `@squoosh/lib`, avoids GPLv3 by spawning process.
* sharp v0.28.0 / libvips v8.10.6 - Caching within libvips disabled to ensure a fair comparison.
* sharp v0.30.0 / libvips v8.12.2 - Caching within libvips disabled to ensure a fair comparison.
## The task
@@ -21,23 +21,23 @@ then compress to JPEG at a "quality" setting of 80.
## Test environment
* AWS EC2 eu-west-1 [c5ad.xlarge](https://aws.amazon.com/ec2/instance-types/c5/) (4x AMD EPYC 7R32)
* Ubuntu 21.04 (ami-0d7626a9c2ceab1ac)
* Node.js 16.6.2
* Ubuntu 21.10 (ami-0258eeb71ddf238b3)
* Node.js 16.13.2
## Results
| Module | Input | Output | Ops/sec | Speed-up |
| :----------------- | :----- | :----- | ------: | -------: |
| jimp | buffer | buffer | 0.83 | 1.0 |
| squoosh-cli | file | file | 1.09 | 1.3 |
| squoosh-lib | buffer | buffer | 1.83 | 2.2 |
| mapnik | buffer | buffer | 3.41 | 4.1 |
| gm | buffer | buffer | 8.34 | 10.0 |
| imagemagick | file | file | 8.67 | 10.4 |
| gm | file | file | 8.82 | 10.6 |
| sharp | stream | stream | 29.44 | 35.5 |
| sharp | file | file | 29.64 | 35.7 |
| sharp | buffer | buffer | 31.09 | 37.5 |
| jimp | buffer | buffer | 0.84 | 1.0 |
| squoosh-cli | file | file | 1.08 | 1.3 |
| squoosh-lib | buffer | buffer | 1.85 | 2.2 |
| mapnik | buffer | buffer | 3.45 | 4.1 |
| gm | buffer | buffer | 8.60 | 10.2 |
| gm | file | file | 8.66 | 10.3 |
| imagemagick | file | file | 8.79 | 10.5 |
| sharp | stream | stream | 28.90 | 34.4 |
| sharp | file | file | 30.08 | 35.8 |
| sharp | buffer | buffer | 30.42 | 36.2 |
Greater libvips performance can be expected with caching enabled (default)
and using 8+ core machines, especially those with larger L1/L2 CPU caches.

File diff suppressed because one or more lines are too long

View File

@@ -20,14 +20,19 @@ module.exports = [
'callback',
'can',
'containing',
'contains',
'current',
'date',
'default',
'does',
'each',
'either',
'ensure',
'entirely',
'etc',
'every',
'except',
'following',
'for',
'from',
'get',
@@ -37,10 +42,13 @@ module.exports = [
'have',
'how',
'image',
'implies',
'involve',
'its',
'last',
'least',
'lots',
'make',
'may',
'more',
'most',
@@ -56,6 +64,7 @@ module.exports = [
'over',
'perform',
'performs',
'produce',
'provide',
'provided',
'ready',
@@ -67,12 +76,15 @@ module.exports = [
'sets',
'should',
'since',
'site',
'specified',
'spelling',
'such',
'support',
'supported',
'sure',
'take',
'task',
'than',
'that',
'the',
@@ -84,12 +96,15 @@ module.exports = [
'this',
'under',
'unless',
'unmaintained',
'unsuitable',
'until',
'use',
'used',
'using',
'value',
'values',
'were',
'when',
'which',
'while',

View File

@@ -5,6 +5,7 @@ const os = require('os');
const path = require('path');
const stream = require('stream');
const zlib = require('zlib');
const { createHash } = require('crypto');
const detectLibc = require('detect-libc');
const semverLessThan = require('semver/functions/lt');
@@ -18,7 +19,7 @@ const platform = require('../lib/platform');
const minimumGlibcVersionByArch = {
arm: '2.28',
arm64: '2.29',
arm64: '2.17',
x64: '2.17'
};
@@ -55,6 +56,33 @@ const handleError = function (err) {
}
};
const verifyIntegrity = function (platformAndArch) {
const expected = libvips.integrity(platformAndArch);
if (installationForced || !expected) {
libvips.log(`Integrity check skipped for ${platformAndArch}`);
return new stream.PassThrough();
}
const hash = createHash('sha512');
return new stream.Transform({
transform: function (chunk, _encoding, done) {
hash.update(chunk);
done(null, chunk);
},
flush: function (done) {
const digest = `sha512-${hash.digest('base64')}`;
if (expected !== digest) {
libvips.removeVendoredLibvips();
libvips.log(`Integrity expected: ${expected}`);
libvips.log(`Integrity received: ${digest}`);
done(new Error(`Integrity check failed for ${platformAndArch}`));
} else {
libvips.log(`Integrity check passed for ${platformAndArch}`);
done();
}
}
});
};
const extractTarball = function (tarPath, platformAndArch) {
const versionedVendorPath = path.join(__dirname, '..', 'vendor', minimumLibvipsVersion, platformAndArch);
libvips.mkdirSync(versionedVendorPath);
@@ -66,6 +94,7 @@ const extractTarball = function (tarPath, platformAndArch) {
stream.pipeline(
fs.createReadStream(tarPath),
verifyIntegrity(platformAndArch),
new zlib.BrotliDecompress(),
tarFs.extract(versionedVendorPath, { ignore }),
function (err) {
@@ -103,14 +132,16 @@ try {
throw new Error(`BSD/SunOS systems require manual installation of libvips >= ${minimumLibvipsVersion}`);
}
// Linux libc version check
if (detectLibc.family === detectLibc.GLIBC && detectLibc.version && minimumGlibcVersionByArch[arch]) {
if (semverLessThan(`${detectLibc.version}.0`, `${minimumGlibcVersionByArch[arch]}.0`)) {
handleError(new Error(`Use with glibc ${detectLibc.version} requires manual installation of libvips >= ${minimumLibvipsVersion}`));
const libcFamily = detectLibc.familySync();
const libcVersion = detectLibc.versionSync();
if (libcFamily === detectLibc.GLIBC && libcVersion && minimumGlibcVersionByArch[arch]) {
if (semverLessThan(`${libcVersion}.0`, `${minimumGlibcVersionByArch[arch]}.0`)) {
handleError(new Error(`Use with glibc ${libcVersion} requires manual installation of libvips >= ${minimumLibvipsVersion}`));
}
}
if (detectLibc.family === detectLibc.MUSL && detectLibc.version) {
if (semverLessThan(detectLibc.version, '1.1.24')) {
handleError(new Error(`Use with musl ${detectLibc.version} requires manual installation of libvips >= ${minimumLibvipsVersion}`));
if (libcFamily === detectLibc.MUSL && libcVersion) {
if (semverLessThan(libcVersion, '1.1.24')) {
handleError(new Error(`Use with musl ${libcVersion} requires manual installation of libvips >= ${minimumLibvipsVersion}`));
}
}
// Node.js minimum version check
@@ -120,7 +151,7 @@ try {
}
// Download to per-process temporary file
const tarFilename = ['libvips', minimumLibvipsVersion, platformAndArch].join('-') + '.tar.br';
const tarFilename = ['libvips', minimumLibvipsVersionLabelled, platformAndArch].join('-') + '.tar.br';
const tarPathCache = path.join(libvips.cachePath(), tarFilename);
if (fs.existsSync(tarPathCache)) {
libvips.log(`Using cached ${tarPathCache}`);

View File

@@ -19,6 +19,11 @@ const colourspace = {
* Tint the image using the provided chroma while preserving the image luminance.
* An alpha channel may be present and will be unchanged by the operation.
*
* @example
* const output = await sharp(input)
* .tint({ r: 255, g: 240, b: 16 })
* .toBuffer();
*
* @param {string|Object} rgb - parsed by the [color](https://www.npmjs.org/package/color) module to extract chroma values.
* @returns {Sharp}
* @throws {Error} Invalid parameter
@@ -37,6 +42,10 @@ function tint (rgb) {
* This may be overridden by other sharp operations such as `toColourspace('b-w')`,
* which will produce an output image containing one color channel.
* An alpha channel may be present, and will be unchanged by the operation.
*
* @example
* const output = await sharp(input).greyscale().toBuffer();
*
* @param {Boolean} [greyscale=true]
* @returns {Sharp}
*/

View File

@@ -56,6 +56,21 @@ const blend = {
* @since 0.22.0
*
* @example
* await sharp(background)
* .composite([
* { input: layer1, gravity: 'northwest' },
* { input: layer2, gravity: 'southeast' },
* ])
* .toFile('combined.png');
*
* @example
* const output = await sharp('input.gif', { animated: true })
* .composite([
* { input: 'overlay.png', tile: true, blend: 'saturate' }
* ])
* .toBuffer();
*
* @example
* sharp('input.png')
* .rotate(180)
* .resize(300)
@@ -89,6 +104,7 @@ const blend = {
* @param {Number} [images[].raw.width]
* @param {Number} [images[].raw.height]
* @param {Number} [images[].raw.channels]
* @param {boolean} [images[].animated=false] - Set to `true` to read all frames/pages of an animated image.
* @param {boolean} [images[].failOnError=true] - @see {@link /api-constructor#parameters|constructor parameters}
* @param {number|boolean} [images[].limitInputPixels=268402689] - @see {@link /api-constructor#parameters|constructor parameters}
* @returns {Sharp}
@@ -162,7 +178,6 @@ function composite (images) {
throw is.invalidParameterError('premultiplied', 'boolean', image.premultiplied);
}
}
return composite;
});
return this;

View File

@@ -13,7 +13,7 @@ const debuglog = util.debuglog('sharp');
/**
* Constructor factory to create an instance of `sharp`, to which further methods are chained.
*
* JPEG, PNG, WebP, AVIF or TIFF format image data can be streamed out from this object.
* JPEG, PNG, WebP, GIF, AVIF or TIFF format image data can be streamed out from this object.
* When using Stream based output, derived attributes are available from the `info` event.
*
* Non-critical problems encountered during processing are emitted as `warning` events.
@@ -103,6 +103,7 @@ const debuglog = util.debuglog('sharp');
* @param {number|boolean} [options.limitInputPixels=268402689] - Do not process input images where the number of pixels
* (width x height) exceeds this limit. Assumes image dimensions contained in the input metadata can be trusted.
* An integral Number of pixels, zero or false to remove limit, true to use default limit of 268402689 (0x3FFF x 0x3FFF).
* @param {boolean} [options.unlimited=false] - Set this to `true` to remove safety features that help prevent memory exhaustion (SVG, PNG).
* @param {boolean} [options.sequentialRead=false] - Set this to `true` to use sequential rather than random access where possible.
* This can reduce memory usage and might improve performance on some systems.
* @param {number} [options.density=72] - number representing the DPI for vector images in the range 1 to 100000.
@@ -165,6 +166,7 @@ const Sharp = function (input, options) {
extendRight: 0,
extendBackground: [0, 0, 0, 255],
withoutEnlargement: false,
withoutReduction: false,
affineMatrix: [],
affineBackground: [0, 0, 0, 255],
affineIdx: 0,
@@ -184,8 +186,11 @@ const Sharp = function (input, options) {
medianSize: 0,
blurSigma: 0,
sharpenSigma: 0,
sharpenFlat: 1,
sharpenJagged: 2,
sharpenM1: 1,
sharpenM2: 2,
sharpenX1: 2,
sharpenY2: 10,
sharpenY3: 20,
threshold: 0,
thresholdGrayscale: true,
trimThreshold: 0,
@@ -233,6 +238,7 @@ const Sharp = function (input, options) {
pngAdaptiveFiltering: false,
pngPalette: false,
pngQuality: 100,
pngEffort: 7,
pngBitdepth: 8,
pngDither: 1,
jp2Quality: 80,
@@ -245,7 +251,10 @@ const Sharp = function (input, options) {
webpLossless: false,
webpNearLossless: false,
webpSmartSubsample: false,
webpReductionEffort: 4,
webpEffort: 4,
gifBitdepth: 8,
gifEffort: 7,
gifDither: 1,
tiffQuality: 80,
tiffCompression: 'jpeg',
tiffPredictor: 'horizontal',
@@ -256,10 +265,11 @@ const Sharp = function (input, options) {
tiffTileWidth: 256,
tiffXres: 1.0,
tiffYres: 1.0,
tiffResolutionUnit: 'inch',
heifQuality: 50,
heifLossless: false,
heifCompression: 'av1',
heifSpeed: 5,
heifEffort: 4,
heifChromaSubsampling: '4:4:4',
rawDepth: 'uchar',
tileSize: 256,
@@ -273,6 +283,7 @@ const Sharp = function (input, options) {
tileBackground: [255, 255, 255, 255],
tileCentre: false,
tileId: 'https://example.com/iiif',
timeoutSeconds: 0,
linearA: 1,
linearB: 0,
// Function to notify of libvips warnings
@@ -288,7 +299,8 @@ const Sharp = function (input, options) {
this.options.input = this._createInputDescriptor(input, options, { allowStream: true });
return this;
};
util.inherits(Sharp, stream.Duplex);
Object.setPrototypeOf(Sharp.prototype, stream.Duplex.prototype);
Object.setPrototypeOf(Sharp, stream.Duplex);
/**
* Take a "snapshot" of the Sharp instance, returning a new instance.

View File

@@ -9,9 +9,9 @@ const sharp = require('./sharp');
* @private
*/
function _inputOptionsFromObject (obj) {
const { raw, density, limitInputPixels, sequentialRead, failOnError, animated, page, pages, subifd } = obj;
return [raw, density, limitInputPixels, sequentialRead, failOnError, animated, page, pages, subifd].some(is.defined)
? { raw, density, limitInputPixels, sequentialRead, failOnError, animated, page, pages, subifd }
const { raw, density, limitInputPixels, unlimited, sequentialRead, failOnError, animated, page, pages, subifd } = obj;
return [raw, density, limitInputPixels, unlimited, sequentialRead, failOnError, animated, page, pages, subifd].some(is.defined)
? { raw, density, limitInputPixels, unlimited, sequentialRead, failOnError, animated, page, pages, subifd }
: undefined;
}
@@ -23,6 +23,7 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
const inputDescriptor = {
failOnError: true,
limitInputPixels: Math.pow(0x3FFF, 2),
unlimited: false,
sequentialRead: false
};
if (is.string(input)) {
@@ -83,6 +84,14 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
throw is.invalidParameterError('limitInputPixels', 'integer >= 0', inputOptions.limitInputPixels);
}
}
// unlimited
if (is.defined(inputOptions.unlimited)) {
if (is.bool(inputOptions.unlimited)) {
inputDescriptor.unlimited = inputOptions.unlimited;
} else {
throw is.invalidParameterError('unlimited', 'boolean', inputOptions.unlimited);
}
}
// sequentialRead
if (is.defined(inputOptions.sequentialRead)) {
if (is.bool(inputOptions.sequentialRead)) {
@@ -281,13 +290,17 @@ function _isStreamInput () {
}
/**
* Fast access to (uncached) image metadata without decoding any compressed image data.
* Fast access to (uncached) image metadata without decoding any compressed pixel data.
*
* This is taken from the header of the input image.
* It does not include operations, such as resize, to be applied to the output image.
*
* A `Promise` is returned when `callback` is not provided.
*
* - `format`: Name of decoder used to decompress image data e.g. `jpeg`, `png`, `webp`, `gif`, `svg`
* - `size`: Total size of image in bytes, for Stream and Buffer input only
* - `width`: Number of pixels wide (EXIF orientation is not taken into consideration)
* - `height`: Number of pixels high (EXIF orientation is not taken into consideration)
* - `width`: Number of pixels wide (EXIF orientation is not taken into consideration, see example below)
* - `height`: Number of pixels high (EXIF orientation is not taken into consideration, see example below)
* - `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://libvips.github.io/libvips/API/current/VipsImage.html#VipsInterpretation)
* - `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK
* - `depth`: Name of pixel depth format e.g. `uchar`, `char`, `ushort`, `float` [...](https://libvips.github.io/libvips/API/current/VipsImage.html#VipsBandFormat)
@@ -303,6 +316,7 @@ function _isStreamInput () {
* - `subifds`: Number of Sub Image File Directories in an OME-TIFF image
* - `background`: Default background colour, if present, for PNG (bKGD) and GIF images, either an RGB Object or a single greyscale value
* - `compression`: The encoder used to compress an HEIF file, `av1` (AVIF) or `hevc` (HEIC)
* - `resolutionUnit`: The unit of resolution (density), either `inch` or `cm`, if present
* - `hasProfile`: Boolean indicating the presence of an embedded ICC profile
* - `hasAlpha`: Boolean indicating the presence of an alpha transparency channel
* - `orientation`: Number value of the EXIF Orientation header, if present
@@ -313,6 +327,9 @@ function _isStreamInput () {
* - `tifftagPhotoshop`: Buffer containing raw TIFFTAG_PHOTOSHOP data, if present
*
* @example
* const metadata = await sharp(input).metadata();
*
* @example
* const image = sharp(inputJpg);
* image
* .metadata()
@@ -326,6 +343,17 @@ function _isStreamInput () {
* // data contains a WebP image half the width and height of the original JPEG
* });
*
* @example
* // Based on EXIF rotation metadata, get the right-side-up width and height:
*
* const size = getNormalSize(await sharp(input).metadata());
*
* function getNormalSize({ width, height, orientation }) {
* return orientation || 0 >= 5
* ? { width: height, height: width }
* : { width, height };
* }
*
* @param {Function} [callback] - called with the arguments `(err, metadata)`
* @returns {Promise<Object>|Sharp}
*/
@@ -384,9 +412,9 @@ function metadata (callback) {
* - `maxX` (x-coordinate of one of the pixel where the maximum lies)
* - `maxY` (y-coordinate of one of the pixel where the maximum lies)
* - `isOpaque`: Is the image fully opaque? Will be `true` if the image has no alpha channel or if every pixel is fully opaque.
* - `entropy`: Histogram-based estimation of greyscale entropy, discarding alpha channel if any (experimental)
* - `sharpness`: Estimation of greyscale sharpness based on the standard deviation of a Laplacian convolution, discarding alpha channel if any (experimental)
* - `dominant`: Object containing most dominant sRGB colour based on a 4096-bin 3D histogram (experimental)
* - `entropy`: Histogram-based estimation of greyscale entropy, discarding alpha channel if any.
* - `sharpness`: Estimation of greyscale sharpness based on the standard deviation of a Laplacian convolution, discarding alpha channel if any.
* - `dominant`: Object containing most dominant sRGB colour based on a 4096-bin 3D histogram.
*
* **Note**: Statistics are derived from the original input image. Any operations performed on the image must first be
* written to a buffer in order to run `stats` on the result (see third example).

View File

@@ -8,10 +8,11 @@ const semverCoerce = require('semver/functions/coerce');
const semverGreaterThanOrEqualTo = require('semver/functions/gte');
const platform = require('./platform');
const { config } = require('../package.json');
const env = process.env;
const minimumLibvipsVersionLabelled = env.npm_package_config_libvips || /* istanbul ignore next */
require('../package.json').config.libvips;
config.libvips;
const minimumLibvipsVersion = semverCoerce(minimumLibvipsVersionLabelled).version;
const spawnSyncOptions = {
@@ -19,6 +20,8 @@ const spawnSyncOptions = {
shell: true
};
const vendorPath = path.join(__dirname, '..', 'vendor', minimumLibvipsVersion, platform());
const mkdirSync = function (dirPath) {
try {
fs.mkdirSync(dirPath, { recursive: true });
@@ -39,6 +42,10 @@ const cachePath = function () {
return libvipsCachePath;
};
const integrity = function (platformAndArch) {
return env[`npm_package_config_integrity_${platformAndArch.replace('-', '_')}`] || config.integrity[platformAndArch];
};
const log = function (item) {
if (item instanceof Error) {
console.error(`sharp: Installation error: ${item.message}`);
@@ -67,16 +74,26 @@ const globalLibvipsVersion = function () {
};
const hasVendoredLibvips = function () {
const vendorPath = path.join(__dirname, '..', 'vendor', minimumLibvipsVersion, platform());
return fs.existsSync(vendorPath);
};
/* istanbul ignore next */
const removeVendoredLibvips = function () {
const rm = fs.rmSync ? fs.rmSync : fs.rmdirSync;
rm(vendorPath, { recursive: true, maxRetries: 3, force: true });
};
const pkgConfigPath = function () {
if (process.platform !== 'win32') {
const brewPkgConfigPath = spawnSync('which brew >/dev/null 2>&1 && eval $(brew --env) && echo $PKG_CONFIG_LIBDIR', spawnSyncOptions).stdout || '';
return [brewPkgConfigPath.trim(), env.PKG_CONFIG_PATH, '/usr/local/lib/pkgconfig', '/usr/lib/pkgconfig']
.filter(function (p) { return !!p; })
.join(':');
return [
brewPkgConfigPath.trim(),
env.PKG_CONFIG_PATH,
'/usr/local/lib/pkgconfig',
'/usr/lib/pkgconfig',
'/usr/local/libdata/pkgconfig',
'/usr/libdata/pkgconfig'
].filter(Boolean).join(':');
} else {
return '';
}
@@ -99,9 +116,11 @@ module.exports = {
minimumLibvipsVersion,
minimumLibvipsVersionLabelled,
cachePath,
integrity,
log,
globalLibvipsVersion,
hasVendoredLibvips,
removeVendoredLibvips,
pkgConfigPath,
useGlobalLibvips,
mkdirSync

View File

@@ -63,6 +63,10 @@ function rotate (angle, options) {
/**
* Flip the image about the vertical Y axis. This always occurs after rotation, if any.
* The use of `flip` implies the removal of the EXIF `Orientation` tag, if any.
*
* @example
* const output = await sharp(input).flip().toBuffer();
*
* @param {Boolean} [flip=true]
* @returns {Sharp}
*/
@@ -74,6 +78,10 @@ function flip (flip) {
/**
* Flop the image about the horizontal X axis. This always occurs after rotation, if any.
* The use of `flop` implies the removal of the EXIF `Orientation` tag, if any.
*
* @example
* const output = await sharp(input).flop().toBuffer();
*
* @param {Boolean} [flop=true]
* @returns {Sharp}
*/
@@ -185,40 +193,107 @@ function affine (matrix, options) {
* When a `sigma` is provided, performs a slower, more accurate sharpen of the L channel in the LAB colour space.
* Separate control over the level of sharpening in "flat" and "jagged" areas is available.
*
* @param {number} [sigma] - the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
* @param {number} [flat=1.0] - the level of sharpening to apply to "flat" areas.
* @param {number} [jagged=2.0] - the level of sharpening to apply to "jagged" areas.
* See {@link https://www.libvips.org/API/current/libvips-convolution.html#vips-sharpen|libvips sharpen} operation.
*
* @example
* const data = await sharp(input).sharpen().toBuffer();
*
* @example
* const data = await sharp(input).sharpen({ sigma: 2 }).toBuffer();
*
* @example
* const data = await sharp(input)
* .sharpen({
* sigma: 2,
* m1: 0
* m2: 3,
* x1: 3,
* y2: 15,
* y3: 15,
* })
* .toBuffer();
*
* @param {Object|number} [options] - if present, is an Object with attributes or (deprecated) a number for `options.sigma`.
* @param {number} [options.sigma] - the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
* @param {number} [options.m1=1.0] - the level of sharpening to apply to "flat" areas.
* @param {number} [options.m2=2.0] - the level of sharpening to apply to "jagged" areas.
* @param {number} [options.x1=2.0] - threshold between "flat" and "jagged"
* @param {number} [options.y2=10.0] - maximum amount of brightening.
* @param {number} [options.y3=20.0] - maximum amount of darkening.
* @param {number} [flat] - (deprecated) see `options.m1`.
* @param {number} [jagged] - (deprecated) see `options.m2`.
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
function sharpen (sigma, flat, jagged) {
if (!is.defined(sigma)) {
function sharpen (options, flat, jagged) {
if (!is.defined(options)) {
// No arguments: default to mild sharpen
this.options.sharpenSigma = -1;
} else if (is.bool(sigma)) {
// Boolean argument: apply mild sharpen?
this.options.sharpenSigma = sigma ? -1 : 0;
} else if (is.number(sigma) && is.inRange(sigma, 0.01, 10000)) {
// Numeric argument: specific sigma
this.options.sharpenSigma = sigma;
// Control over flat areas
} else if (is.bool(options)) {
// Deprecated boolean argument: apply mild sharpen?
this.options.sharpenSigma = options ? -1 : 0;
} else if (is.number(options) && is.inRange(options, 0.01, 10000)) {
// Deprecated numeric argument: specific sigma
this.options.sharpenSigma = options;
// Deprecated control over flat areas
if (is.defined(flat)) {
if (is.number(flat) && is.inRange(flat, 0, 10000)) {
this.options.sharpenFlat = flat;
this.options.sharpenM1 = flat;
} else {
throw is.invalidParameterError('flat', 'number between 0 and 10000', flat);
}
}
// Control over jagged areas
// Deprecated control over jagged areas
if (is.defined(jagged)) {
if (is.number(jagged) && is.inRange(jagged, 0, 10000)) {
this.options.sharpenJagged = jagged;
this.options.sharpenM2 = jagged;
} else {
throw is.invalidParameterError('jagged', 'number between 0 and 10000', jagged);
}
}
} else if (is.plainObject(options)) {
if (is.number(options.sigma) && is.inRange(options.sigma, 0.01, 10000)) {
this.options.sharpenSigma = options.sigma;
} else {
throw is.invalidParameterError('options.sigma', 'number between 0.01 and 10000', options.sigma);
}
if (is.defined(options.m1)) {
if (is.number(options.m1) && is.inRange(options.m1, 0, 10000)) {
this.options.sharpenM1 = options.m1;
} else {
throw is.invalidParameterError('options.m1', 'number between 0 and 10000', options.m1);
}
}
if (is.defined(options.m2)) {
if (is.number(options.m2) && is.inRange(options.m2, 0, 10000)) {
this.options.sharpenM2 = options.m2;
} else {
throw is.invalidParameterError('options.m2', 'number between 0 and 10000', options.m2);
}
}
if (is.defined(options.x1)) {
if (is.number(options.x1) && is.inRange(options.x1, 0, 10000)) {
this.options.sharpenX1 = options.x1;
} else {
throw is.invalidParameterError('options.x1', 'number between 0 and 10000', options.x1);
}
}
if (is.defined(options.y2)) {
if (is.number(options.y2) && is.inRange(options.y2, 0, 10000)) {
this.options.sharpenY2 = options.y2;
} else {
throw is.invalidParameterError('options.y2', 'number between 0 and 10000', options.y2);
}
}
if (is.defined(options.y3)) {
if (is.number(options.y3) && is.inRange(options.y3, 0, 10000)) {
this.options.sharpenY3 = options.y3;
} else {
throw is.invalidParameterError('options.y3', 'number between 0 and 10000', options.y3);
}
}
} else {
throw is.invalidParameterError('sigma', 'number between 0.01 and 10000', sigma);
throw is.invalidParameterError('sigma', 'number between 0.01 and 10000', options);
}
return this;
}
@@ -226,6 +301,13 @@ function sharpen (sigma, flat, jagged) {
/**
* Apply median filter.
* When used without parameters the default window is 3x3.
*
* @example
* const output = await sharp(input).median().toBuffer();
*
* @example
* const output = await sharp(input).median(5).toBuffer();
*
* @param {number} [size=3] square mask size: size x size
* @returns {Sharp}
* @throws {Error} Invalid parameters
@@ -245,8 +327,21 @@ function median (size) {
/**
* Blur the image.
* When used without parameters, performs a fast, mild blur of the output image.
*
* When used without parameters, performs a fast 3x3 box blur (equivalent to a box linear filter).
*
* When a `sigma` is provided, performs a slower, more accurate Gaussian blur.
*
* @example
* const boxBlurred = await sharp(input)
* .blur()
* .toBuffer();
*
* @example
* const gaussianBlurred = await sharp(input)
* .blur(5)
* .toBuffer();
*
* @param {number} [sigma] a value between 0.3 and 1000 representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
* @returns {Sharp}
* @throws {Error} Invalid parameters
@@ -343,6 +438,10 @@ function negate (options) {
/**
* Enhance output image contrast by stretching its luminance to cover the full dynamic range.
*
* @example
* const output = await sharp(input).normalise().toBuffer();
*
* @param {Boolean} [normalise=true]
* @returns {Sharp}
*/
@@ -353,6 +452,10 @@ function normalise (normalise) {
/**
* Alternative spelling of normalise.
*
* @example
* const output = await sharp(input).normalize().toBuffer();
*
* @param {Boolean} [normalize=true]
* @returns {Sharp}
*/
@@ -368,6 +471,14 @@ function normalize (normalize) {
*
* @since 0.28.3
*
* @example
* const output = await sharp(input)
* .clahe({
* width: 3,
* height: 3,
* })
* .toBuffer();
*
* @param {Object} options
* @param {number} options.width - integer width of the region in pixels.
* @param {number} options.height - integer height of the region in pixels.
@@ -577,28 +688,38 @@ function recomb (inputMatrix) {
* @since 0.22.1
*
* @example
* sharp(input)
* // increase brightness by a factor of 2
* const output = await sharp(input)
* .modulate({
* brightness: 2 // increase brightness by a factor of 2
* });
* brightness: 2
* })
* .toBuffer();
*
* sharp(input)
* @example
* // hue-rotate by 180 degrees
* const output = await sharp(input)
* .modulate({
* hue: 180 // hue-rotate by 180 degrees
* });
* hue: 180
* })
* .toBuffer();
*
* sharp(input)
* @example
* // increase lightness by +50
* const output = await sharp(input)
* .modulate({
* lightness: 50 // increase lightness by +50
* });
* lightness: 50
* })
* .toBuffer();
*
* @example
* // decreate brightness and saturation while also hue-rotating by 90 degrees
* sharp(input)
* const output = await sharp(input)
* .modulate({
* brightness: 0.5,
* saturation: 0.5,
* hue: 90
* });
* hue: 90,
* })
* .toBuffer();
*
* @param {Object} [options]
* @param {number} [options.brightness] Brightness multiplier

View File

@@ -13,6 +13,7 @@ const formats = new Map([
['png', 'png'],
['raw', 'raw'],
['tiff', 'tiff'],
['tif', 'tiff'],
['webp', 'webp'],
['gif', 'gif'],
['jp2', 'jp2'],
@@ -21,14 +22,15 @@ const formats = new Map([
['j2c', 'jp2']
]);
const errMagickSave = new Error('GIF output requires libvips with support for ImageMagick');
const errJp2Save = new Error('JP2 output requires libvips with support for OpenJPEG');
const bitdepthFromColourCount = (colours) => 1 << 31 - Math.clz32(Math.ceil(Math.log2(colours)));
/**
* Write output image data to a file.
*
* If an explicit output format is not selected, it will be inferred from the extension,
* with JPEG, PNG, WebP, AVIF, TIFF, DZI, and libvips' V format supported.
* with JPEG, PNG, WebP, AVIF, TIFF, GIF, DZI, and libvips' V format supported.
* Note that raw pixel data is only supported for buffer output.
*
* By default all metadata will be removed, which includes EXIF-based orientation.
@@ -62,8 +64,6 @@ function toFile (fileOut, callback) {
err = new Error('Missing output file path');
} else if (is.string(this.options.input.file) && path.resolve(this.options.input.file) === path.resolve(fileOut)) {
err = new Error('Cannot use same file for input and output');
} else if (this.options.formatOut === 'input' && fileOut.toLowerCase().endsWith('.gif') && !this.constructor.format.magick.output.file) {
err = errMagickSave;
}
if (err) {
if (is.fn(callback)) {
@@ -80,9 +80,9 @@ function toFile (fileOut, callback) {
/**
* Write output to a Buffer.
* JPEG, PNG, WebP, AVIF, TIFF and raw pixel data output are supported.
* JPEG, PNG, WebP, AVIF, TIFF, GIF and raw pixel data output are supported.
*
* If no explicit format is set, the output format will match the input image, except GIF and SVG input which become PNG output.
* If no explicit format is set, the output format will match the input image, except SVG input which becomes PNG output.
*
* By default all metadata will be removed, which includes EXIF-based orientation.
* See {@link withMetadata} for control over this.
@@ -139,6 +139,7 @@ function toBuffer (options, callback) {
} else if (this.options.resolveWithObject) {
this.options.resolveWithObject = false;
}
this.options.fileOut = '';
return this._pipeline(is.fn(options) ? options : callback);
}
@@ -168,7 +169,7 @@ function toBuffer (options, callback) {
* })
* .toBuffer();
*
* * @example
* @example
* // Set output metadata to 96 DPI
* const data = await sharp(input)
* .withMetadata({ density: 96 })
@@ -373,6 +374,7 @@ function jpeg (options) {
* @param {boolean} [options.adaptiveFiltering=false] - use adaptive row filtering
* @param {boolean} [options.palette=false] - quantise to a palette-based image with alpha transparency support
* @param {number} [options.quality=100] - use the lowest number of colours needed to achieve given quality, sets `palette` to `true`
* @param {number} [options.effort=7] - CPU effort, between 1 (fastest) and 10 (slowest), sets `palette` to `true`
* @param {number} [options.colours=256] - maximum number of palette entries, sets `palette` to `true`
* @param {number} [options.colors=256] - alternative spelling of `options.colours`, sets `palette` to `true`
* @param {number} [options.dither=1.0] - level of Floyd-Steinberg error diffusion, sets `palette` to `true`
@@ -397,7 +399,7 @@ function png (options) {
}
if (is.defined(options.palette)) {
this._setBooleanOption('pngPalette', options.palette);
} else if (is.defined(options.quality) || is.defined(options.colours || options.colors) || is.defined(options.dither)) {
} else if ([options.quality, options.effort, options.colours, options.colors, options.dither].some(is.defined)) {
this._setBooleanOption('pngPalette', true);
}
if (this.options.pngPalette) {
@@ -408,10 +410,17 @@ function png (options) {
throw is.invalidParameterError('quality', 'integer between 0 and 100', options.quality);
}
}
if (is.defined(options.effort)) {
if (is.integer(options.effort) && is.inRange(options.effort, 1, 10)) {
this.options.pngEffort = options.effort;
} else {
throw is.invalidParameterError('effort', 'integer between 1 and 10', options.effort);
}
}
const colours = options.colours || options.colors;
if (is.defined(colours)) {
if (is.integer(colours) && is.inRange(colours, 2, 256)) {
this.options.pngBitdepth = 1 << 31 - Math.clz32(Math.ceil(Math.log2(colours)));
this.options.pngBitdepth = bitdepthFromColourCount(colours);
} else {
throw is.invalidParameterError('colours', 'integer between 2 and 256', colours);
}
@@ -440,7 +449,7 @@ function png (options) {
* @example
* // Optimise the file size of an animated WebP
* const outputWebp = await sharp(inputWebp, { animated: true })
* .webp({ reductionEffort: 6 })
* .webp({ effort: 6 })
* .toBuffer();
*
* @param {Object} [options] - output options
@@ -449,69 +458,111 @@ function png (options) {
* @param {boolean} [options.lossless=false] - use lossless compression mode
* @param {boolean} [options.nearLossless=false] - use near_lossless compression mode
* @param {boolean} [options.smartSubsample=false] - use high quality chroma subsampling
* @param {number} [options.reductionEffort=4] - level of CPU effort to reduce file size, integer 0-6
* @param {number} [options.pageHeight] - page height for animated output
* @param {number} [options.effort=4] - CPU effort, between 0 (fastest) and 6 (slowest)
* @param {number} [options.loop=0] - number of animation iterations, use 0 for infinite animation
* @param {number[]} [options.delay] - list of delays between animation frames (in milliseconds)
* @param {number|number[]} [options.delay] - delay(s) between animation frames (in milliseconds)
* @param {boolean} [options.force=true] - force WebP output, otherwise attempt to use input format
* @returns {Sharp}
* @throws {Error} Invalid options
*/
function webp (options) {
if (is.object(options) && is.defined(options.quality)) {
if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
this.options.webpQuality = options.quality;
} else {
throw is.invalidParameterError('quality', 'integer between 1 and 100', options.quality);
if (is.object(options)) {
if (is.defined(options.quality)) {
if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
this.options.webpQuality = options.quality;
} else {
throw is.invalidParameterError('quality', 'integer between 1 and 100', options.quality);
}
}
if (is.defined(options.alphaQuality)) {
if (is.integer(options.alphaQuality) && is.inRange(options.alphaQuality, 0, 100)) {
this.options.webpAlphaQuality = options.alphaQuality;
} else {
throw is.invalidParameterError('alphaQuality', 'integer between 0 and 100', options.alphaQuality);
}
}
if (is.defined(options.lossless)) {
this._setBooleanOption('webpLossless', options.lossless);
}
if (is.defined(options.nearLossless)) {
this._setBooleanOption('webpNearLossless', options.nearLossless);
}
if (is.defined(options.smartSubsample)) {
this._setBooleanOption('webpSmartSubsample', options.smartSubsample);
}
const effort = options.effort || options.reductionEffort;
if (is.defined(effort)) {
if (is.integer(effort) && is.inRange(effort, 0, 6)) {
this.options.webpEffort = effort;
} else {
throw is.invalidParameterError('effort', 'integer between 0 and 6', effort);
}
}
}
if (is.object(options) && is.defined(options.alphaQuality)) {
if (is.integer(options.alphaQuality) && is.inRange(options.alphaQuality, 0, 100)) {
this.options.webpAlphaQuality = options.alphaQuality;
} else {
throw is.invalidParameterError('alphaQuality', 'integer between 0 and 100', options.alphaQuality);
}
}
if (is.object(options) && is.defined(options.lossless)) {
this._setBooleanOption('webpLossless', options.lossless);
}
if (is.object(options) && is.defined(options.nearLossless)) {
this._setBooleanOption('webpNearLossless', options.nearLossless);
}
if (is.object(options) && is.defined(options.smartSubsample)) {
this._setBooleanOption('webpSmartSubsample', options.smartSubsample);
}
if (is.object(options) && is.defined(options.reductionEffort)) {
if (is.integer(options.reductionEffort) && is.inRange(options.reductionEffort, 0, 6)) {
this.options.webpReductionEffort = options.reductionEffort;
} else {
throw is.invalidParameterError('reductionEffort', 'integer between 0 and 6', options.reductionEffort);
}
}
trySetAnimationOptions(options, this.options);
return this._updateFormatOut('webp', options);
}
/**
* Use these GIF options for output image.
* Use these GIF options for the output image.
*
* Requires libvips compiled with support for ImageMagick or GraphicsMagick.
* The prebuilt binaries do not include this - see
* {@link https://sharp.pixelplumbing.com/install#custom-libvips installing a custom libvips}.
* The first entry in the palette is reserved for transparency.
*
* @since 0.30.0
*
* @example
* // Convert PNG to GIF
* await sharp(pngBuffer)
* .gif()
* .toBuffer());
*
* @example
* // Convert animated WebP to animated GIF
* await sharp('animated.webp', { animated: true })
* .toFile('animated.gif');
*
* @example
* // Create a 128x128, cropped, non-dithered, animated thumbnail of an animated GIF
* const out = await sharp('in.gif', { animated: true })
* .resize({ width: 128, height: 128 })
* .gif({ dither: 0 })
* .toBuffer();
*
* @param {Object} [options] - output options
* @param {number} [options.pageHeight] - page height for animated output
* @param {number} [options.colours=256] - maximum number of palette entries, including transparency, between 2 and 256
* @param {number} [options.colors=256] - alternative spelling of `options.colours`
* @param {number} [options.effort=7] - CPU effort, between 1 (fastest) and 10 (slowest)
* @param {number} [options.dither=1.0] - level of Floyd-Steinberg error diffusion, between 0 (least) and 1 (most)
* @param {number} [options.loop=0] - number of animation iterations, use 0 for infinite animation
* @param {number[]} [options.delay] - list of delays between animation frames (in milliseconds)
* @param {number|number[]} [options.delay] - delay(s) between animation frames (in milliseconds)
* @param {boolean} [options.force=true] - force GIF output, otherwise attempt to use input format
* @returns {Sharp}
* @throws {Error} Invalid options
*/
/* istanbul ignore next */
function gif (options) {
if (!this.constructor.format.magick.output.buffer) {
throw errMagickSave;
if (is.object(options)) {
const colours = options.colours || options.colors;
if (is.defined(colours)) {
if (is.integer(colours) && is.inRange(colours, 2, 256)) {
this.options.gifBitdepth = bitdepthFromColourCount(colours);
} else {
throw is.invalidParameterError('colours', 'integer between 2 and 256', colours);
}
}
if (is.defined(options.effort)) {
if (is.number(options.effort) && is.inRange(options.effort, 1, 10)) {
this.options.gifEffort = options.effort;
} else {
throw is.invalidParameterError('effort', 'integer between 1 and 10', options.effort);
}
}
if (is.defined(options.dither)) {
if (is.number(options.dither) && is.inRange(options.dither, 0, 1)) {
this.options.gifDither = options.dither;
} else {
throw is.invalidParameterError('dither', 'number between 0.0 and 1.0', options.dither);
}
}
}
trySetAnimationOptions(options, this.options);
return this._updateFormatOut('gif', options);
@@ -600,20 +651,12 @@ function jp2 (options) {
* @private
*
* @param {Object} [source] - output options
* @param {number} [source.pageHeight] - page height for animated output
* @param {number} [source.loop=0] - number of animation iterations, use 0 for infinite animation
* @param {number[]} [source.delay] - list of delays between animation frames (in milliseconds)
* @param {Object} [target] - target object for valid options
* @throws {Error} Invalid options
*/
function trySetAnimationOptions (source, target) {
if (is.object(source) && is.defined(source.pageHeight)) {
if (is.integer(source.pageHeight) && source.pageHeight > 0) {
target.pageHeight = source.pageHeight;
} else {
throw is.invalidParameterError('pageHeight', 'integer larger than 0', source.pageHeight);
}
}
if (is.object(source) && is.defined(source.loop)) {
if (is.integer(source.loop) && is.inRange(source.loop, 0, 65535)) {
target.loop = source.loop;
@@ -622,13 +665,16 @@ function trySetAnimationOptions (source, target) {
}
}
if (is.object(source) && is.defined(source.delay)) {
if (
// We allow singular values as well
if (is.integer(source.delay) && is.inRange(source.delay, 0, 65535)) {
target.delay = [source.delay];
} else if (
Array.isArray(source.delay) &&
source.delay.every(is.integer) &&
source.delay.every(v => is.inRange(v, 0, 65535))) {
target.delay = source.delay;
} else {
throw is.invalidParameterError('delay', 'array of integers between 0 and 65535', source.delay);
throw is.invalidParameterError('delay', 'integer or an array of integers between 0 and 65535', source.delay);
}
}
}
@@ -636,6 +682,8 @@ function trySetAnimationOptions (source, target) {
/**
* Use these TIFF options for output image.
*
* The `density` can be set in pixels/inch via {@link withMetadata} instead of providing `xres` and `yres` in pixels/mm.
*
* @example
* // Convert SVG input to LZW-compressed, 1 bit per pixel TIFF output
* sharp('input.svg')
@@ -657,6 +705,7 @@ function trySetAnimationOptions (source, target) {
* @param {number} [options.tileHeight=256] - vertical tile size
* @param {number} [options.xres=1.0] - horizontal resolution in pixels/mm
* @param {number} [options.yres=1.0] - vertical resolution in pixels/mm
* @param {string} [options.resolutionUnit='inch'] - resolution unit options: inch, cm
* @param {number} [options.bitdepth=8] - reduce bitdepth to 1, 2 or 4 bit
* @returns {Sharp}
* @throws {Error} Invalid options
@@ -730,6 +779,14 @@ function tiff (options) {
throw is.invalidParameterError('predictor', 'one of: none, horizontal, float', options.predictor);
}
}
// resolutionUnit
if (is.defined(options.resolutionUnit)) {
if (is.string(options.resolutionUnit) && is.inArray(options.resolutionUnit, ['inch', 'cm'])) {
this.options.tiffResolutionUnit = options.resolutionUnit;
} else {
throw is.invalidParameterError('resolutionUnit', 'one of: inch, cm', options.resolutionUnit);
}
}
}
return this._updateFormatOut('tiff', options);
}
@@ -747,7 +804,7 @@ function tiff (options) {
* @param {Object} [options] - output options
* @param {number} [options.quality=50] - quality, integer 1-100
* @param {boolean} [options.lossless=false] - use lossless compression
* @param {number} [options.speed=5] - CPU effort vs file size, 0 (slowest/smallest) to 9 (fastest/largest)
* @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
* @returns {Sharp}
* @throws {Error} Invalid options
@@ -768,7 +825,7 @@ function avif (options) {
* @param {number} [options.quality=50] - quality, integer 1-100
* @param {string} [options.compression='av1'] - compression format: av1, hevc
* @param {boolean} [options.lossless=false] - use lossless compression
* @param {number} [options.speed=5] - CPU effort vs file size, 0 (slowest/smallest) to 9 (fastest/largest)
* @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
* @returns {Sharp}
* @throws {Error} Invalid options
@@ -796,9 +853,15 @@ function heif (options) {
throw is.invalidParameterError('compression', 'one of: av1, hevc', options.compression);
}
}
if (is.defined(options.speed)) {
if (is.defined(options.effort)) {
if (is.integer(options.effort) && is.inRange(options.effort, 0, 9)) {
this.options.heifEffort = options.effort;
} else {
throw is.invalidParameterError('effort', 'integer between 0 and 9', options.effort);
}
} else if (is.defined(options.speed)) {
if (is.integer(options.speed) && is.inRange(options.speed, 0, 9)) {
this.options.heifSpeed = options.speed;
this.options.heifEffort = 9 - options.speed;
} else {
throw is.invalidParameterError('speed', 'integer between 0 and 9', options.speed);
}
@@ -858,8 +921,6 @@ function raw (options) {
* Set the format and options for tile images via the `toFormat`, `jpeg`, `png` or `webp` functions.
* Use a `.zip` or `.szi` file extension with `toFile` to write to a compressed archive file format.
*
* Warning: multiple sharp instances concurrently producing tile output can expose a possible race condition in some versions of libgsf.
*
* @example
* sharp('input.tiff')
* .png()
@@ -879,10 +940,10 @@ function raw (options) {
* @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 {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`, `zoomify` or `google`.
* @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.
* @param {boolean} [options.center=false] alternative spelling of centre.
* @param {string} [options.id='https://example.com/iiif'] when `layout` is `iiif`, sets the `@id` attribute of `info.json`
* @param {string} [options.id='https://example.com/iiif'] when `layout` is `iiif`/`iiif3`, sets the `@id`/`id` attribute of `info.json`
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
@@ -917,10 +978,10 @@ function tile (options) {
}
// Layout
if (is.defined(options.layout)) {
if (is.string(options.layout) && is.inArray(options.layout, ['dz', 'google', 'iiif', 'zoomify'])) {
if (is.string(options.layout) && is.inArray(options.layout, ['dz', 'google', 'iiif', 'iiif3', 'zoomify'])) {
this.options.tileLayout = options.layout;
} else {
throw is.invalidParameterError('layout', 'one of: dz, google, iiif, zoomify', options.layout);
throw is.invalidParameterError('layout', 'one of: dz, google, iiif, iiif3, zoomify', options.layout);
}
}
// Angle of rotation,
@@ -974,6 +1035,31 @@ function tile (options) {
return this._updateFormatOut('dz');
}
/**
* Set a timeout for processing, in seconds.
* Use a value of zero to continue processing indefinitely, the default behaviour.
*
* The clock starts when libvips opens an input image for processing.
* Time spent waiting for a libuv thread to become available is not included.
*
* @since 0.29.2
*
* @param {Object} options
* @param {number} options.seconds - Number of seconds after which processing will be stopped
* @returns {Sharp}
*/
function timeout (options) {
if (!is.plainObject(options)) {
throw is.invalidParameterError('options', 'object', options);
}
if (is.integer(options.seconds) && is.inRange(options.seconds, 0, 3600)) {
this.options.timeoutSeconds = options.seconds;
} else {
throw is.invalidParameterError('seconds', 'integer between 0 and 3600', options.seconds);
}
return this;
}
/**
* Update the output format unless options.force is false,
* in which case revert to input format.
@@ -1050,6 +1136,7 @@ function _pipeline (callback) {
this.push(data);
}
this.push(null);
this.emit('close');
});
});
if (this.streamInFinished) {
@@ -1065,6 +1152,7 @@ function _pipeline (callback) {
this.push(data);
}
this.push(null);
this.emit('close');
});
}
return this;
@@ -1128,6 +1216,7 @@ module.exports = function (Sharp) {
gif,
raw,
tile,
timeout,
// Private
_updateFormatOut,
_setBooleanOption,

View File

@@ -8,7 +8,7 @@ module.exports = function () {
const arch = env.npm_config_arch || process.arch;
const platform = env.npm_config_platform || process.platform;
/* istanbul ignore next */
const libc = (platform === 'linux' && detectLibc.isNonGlibcLinux) ? detectLibc.family : '';
const libc = (platform === 'linux' && detectLibc.isNonGlibcLinuxSync()) ? detectLibc.familySync() : '';
const platformId = [`${platform}${libc}`];

View File

@@ -183,6 +183,20 @@ function isRotationExpected (options) {
* });
*
* @example
* sharp(input)
* .resize(200, 200, {
* fit: sharp.fit.outside,
* withoutReduction: true
* })
* .toFormat('jpeg')
* .toBuffer()
* .then(function(outputBuffer) {
* // outputBuffer contains JPEG image data
* // of at least 200 pixels wide and 200 pixels high while maintaining aspect ratio
* // and no smaller than the input image
* });
*
* @example
* const scaleByHalf = await sharp(input)
* .metadata()
* .then(({ width }) => sharp(input)
@@ -200,6 +214,7 @@ function isRotationExpected (options) {
* @param {String|Object} [options.background={r: 0, g: 0, b: 0, alpha: 1}] - background colour when using a `fit` of `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.
* @param {Boolean} [options.withoutEnlargement=false] - do not enlarge if the width *or* height are already less than the specified dimensions, equivalent to GraphicsMagick's `>` geometry option.
* @param {Boolean} [options.withoutReduction=false] - do not reduce if the width *or* height are already greater than the specified dimensions, equivalent to GraphicsMagick's `<` geometry option.
* @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 on some images.
* @returns {Sharp}
* @throws {Error} Invalid parameters
@@ -276,6 +291,10 @@ function resize (width, height, options) {
if (is.defined(options.withoutEnlargement)) {
this._setBooleanOption('withoutEnlargement', options.withoutEnlargement);
}
// Without reduction
if (is.defined(options.withoutReduction)) {
this._setBooleanOption('withoutReduction', options.withoutReduction);
}
// Shrink on load
if (is.defined(options.fastShrinkOnLoad)) {
this._setBooleanOption('fastShrinkOnLoad', options.fastShrinkOnLoad);

View File

@@ -11,14 +11,22 @@ try {
if (/dylib/.test(err.message) && /Incompatible library version/.test(err.message)) {
help.push('- Update Homebrew: "brew update && brew upgrade vips"');
} else {
const [platform, arch] = platformAndArch.split('-');
help.push(
'- Install with the --verbose flag and look for errors: "npm install --ignore-scripts=false --verbose sharp"',
`- Install for the current runtime: "npm install --platform=${process.platform} --arch=${process.arch} sharp"`
`- Install for the current ${platformAndArch} runtime: "npm install --platform=${platform} --arch=${arch} sharp"`
);
}
help.push(
'- Consult the installation documentation: https://sharp.pixelplumbing.com/install'
);
console.error(help.join('\n'));
process.exit(1);
// Check loaded
if (process.platform === 'win32' || /symbol/.test(err.message)) {
const loadedModule = Object.keys(require.cache).find((i) => /[\\/]build[\\/]Release[\\/]sharp(.*)\.node$/.test(i));
if (loadedModule) {
const [, loadedPackage] = loadedModule.match(/node_modules[\\/]([^\\/]+)[\\/]/);
help.push(`- Ensure the version of sharp aligns with the ${loadedPackage} package: "npm ls sharp"`);
}
}
throw new Error(help.join('\n'));
}

View File

@@ -1,9 +1,12 @@
'use strict';
const fs = require('fs');
const path = require('path');
const events = require('events');
const detectLibc = require('detect-libc');
const is = require('./is');
const platformAndArch = require('./platform')();
const sharp = require('./sharp');
/**
@@ -27,11 +30,11 @@ const interpolators = {
bilinear: 'bilinear',
/** [Bicubic interpolation](http://en.wikipedia.org/wiki/Bicubic_interpolation) (the default). */
bicubic: 'bicubic',
/** [LBB interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/lbb.cpp#L100). Prevents some "[acutance](http://en.wikipedia.org/wiki/Acutance)" but typically reduces performance by a factor of 2. */
/** [LBB interpolation](https://github.com/libvips/libvips/blob/master/libvips/resample/lbb.cpp#L100). Prevents some "[acutance](http://en.wikipedia.org/wiki/Acutance)" but typically reduces performance by a factor of 2. */
locallyBoundedBicubic: 'lbb',
/** [Nohalo interpolation](http://eprints.soton.ac.uk/268086/). Prevents acutance but typically reduces performance by a factor of 3. */
nohalo: 'nohalo',
/** [VSQBS interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/vsqbs.cpp#L48). Prevents "staircasing" when enlarging. */
/** [VSQBS interpolation](https://github.com/libvips/libvips/blob/master/libvips/resample/vsqbs.cpp#L48). Prevents "staircasing" when enlarging. */
vertexSplitQuadraticBasisSpline: 'vsqbs'
};
@@ -45,8 +48,23 @@ let versions = {
vips: sharp.libvipsVersion()
};
try {
versions = require(`../vendor/${versions.vips}/versions.json`);
} catch (err) {}
versions = require(`../vendor/${versions.vips}/${platformAndArch}/versions.json`);
} catch (_err) { /* ignore */ }
/**
* An Object containing the platform and architecture
* of the current and installed vendored binaries.
* @member
* @example
* console.log(sharp.vendor);
*/
const vendor = {
current: platformAndArch,
installed: []
};
try {
vendor.installed = fs.readdirSync(path.join(__dirname, `../vendor/${versions.vips}`));
} catch (_err) { /* ignore */ }
/**
* Gets or, when options are provided, sets the limits of _libvips'_ operation cache.
@@ -110,7 +128,7 @@ function concurrency (concurrency) {
return sharp.concurrency(is.integer(concurrency) ? concurrency : null);
}
/* istanbul ignore next */
if (detectLibc.family === detectLibc.GLIBC && !sharp._isUsingJemalloc()) {
if (detectLibc.familySync() === detectLibc.GLIBC && !sharp._isUsingJemalloc()) {
// Reduce default concurrency to 1 when using glibc memory allocator
sharp.concurrency(1);
}
@@ -175,5 +193,6 @@ module.exports = function (Sharp) {
Sharp.format = format;
Sharp.interpolators = interpolators;
Sharp.versions = versions;
Sharp.vendor = vendor;
Sharp.queue = queue;
};

View File

@@ -1,7 +1,7 @@
{
"name": "sharp",
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, AVIF and TIFF images",
"version": "0.29.1",
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, GIF, AVIF and TIFF images",
"version": "0.30.3",
"author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://github.com/lovell/sharp",
"contributors": [
@@ -79,7 +79,9 @@
"Michael Nutt <michael@nutt.im>",
"Brad Parham <baparham@gmail.com>",
"Taneli Vatanen <taneli.vatanen@gmail.com>",
"Joris Dugué <zaruike10@gmail.com>"
"Joris Dugué <zaruike10@gmail.com>",
"Chris Banks <christopher.bradley.banks@gmail.com>",
"Ompal Singh <ompal.hitm09@gmail.com>"
],
"scripts": {
"install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node install/can-compile && node-gyp rebuild && node install/dll-copy)",
@@ -124,33 +126,46 @@
"vips"
],
"dependencies": {
"color": "^4.0.1",
"detect-libc": "^1.0.3",
"node-addon-api": "^4.1.0",
"prebuild-install": "^6.1.4",
"color": "^4.2.1",
"detect-libc": "^2.0.1",
"node-addon-api": "^4.3.0",
"prebuild-install": "^7.0.1",
"semver": "^7.3.5",
"simple-get": "^3.1.0",
"simple-get": "^4.0.1",
"tar-fs": "^2.1.1",
"tunnel-agent": "^0.6.0"
},
"devDependencies": {
"async": "^3.2.1",
"async": "^3.2.3",
"cc": "^3.0.1",
"decompress-zip": "^0.3.3",
"documentation": "^13.2.5",
"exif-reader": "^1.0.3",
"icc": "^2.0.0",
"license-checker": "^25.0.1",
"mocha": "^9.1.1",
"mock-fs": "^5.0.0",
"mocha": "^9.2.2",
"mock-fs": "^5.1.2",
"nyc": "^15.1.0",
"prebuild": "^10.0.1",
"prebuild": "^11.0.3",
"rimraf": "^3.0.2",
"semistandard": "^16.0.1"
},
"license": "Apache-2.0",
"config": {
"libvips": "8.11.3",
"libvips": "8.12.2",
"integrity": {
"darwin-arm64v8": "sha512-p46s/bbJAjkOXzPISZt9HUpG9GWjwQkYnLLRLKzsBJHLtB3X6C6Y/zXI5Hd0DOojcFkks9a0kTN+uDQ/XJY19g==",
"darwin-x64": "sha512-6vOHVZnvXwe6EXRsy29jdkUzBE6ElNpXUwd+m8vV7qy32AnXu7B9YemHsZ44vWviIwPZeXF6Nhd9EFLM0wWohw==",
"linux-arm64v8": "sha512-XwZdS63yhqLtbFtx/0eoLF/Agf5qtTrI11FMnMRpuBJWd4jHB60RAH+uzYUgoChCmKIS+AeXYMLm4d8Ns2QX8w==",
"linux-armv6": "sha512-Rh0Q0kqwPG2MjXWOkPCuPEyiUKFgKJYWLgS835D4MrXgdKr8Tft/eVrc2iGIxt2re30VpDiZ1h0Rby1aCZt2zw==",
"linux-armv7": "sha512-heTS/MsmRvu4JljINxP+vDiS9ZZfuGhr3IStb5F7Gc0/QLRhllYAg4rcO8L1eTK9sIIzG5ARvI19+YUZe7WbzA==",
"linux-x64": "sha512-SSWAwBFi0hx8V/h/v82tTFGKWTFv9FiCK3Timz5OExuI+sX1Ngrd0PVQaWXOThGNdel/fcD3Bz9YjSt4feBR1g==",
"linuxmusl-arm64v8": "sha512-Rhks+5C7p7aO6AucLT1uvzo8ohlqcqCUPgZmN+LZjsPWob/Iix3MfiDYtv/+gTvdeEfXxbCU6/YuPBwHQ7/crA==",
"linuxmusl-x64": "sha512-IOyjSQqpWVntqOUpCHVWuQwACwmmjdi15H8Pc+Ma1JkhPogTfVsFQWyL7DuOTD3Yr23EuYGzovUX00duOtfy/g==",
"win32-arm64v8": "sha512-A+Qe8Ipewtvw9ldvF6nWed2J8kphzrUE04nFeKCtNx6pfGQ/MAlCKMjt/U8VgUKNjB01zJDUW9XE0+FhGZ/UpQ==",
"win32-ia32": "sha512-cMrAvwFdDeAVnLJt0IPMPRKaIFhyXYGTprsM0DND9VUHE8F7dJMR44tS5YkXsGh1QNDtjKT6YuxAVUglmiXtpA==",
"win32-x64": "sha512-vLFIfw6aW2zABa8jpgzWDhljnE6glktrddErVyazAIoHl6BFFe/Da+LK1DbXvIYHz7fyOoKhSfCJHCiJG1Vg6w=="
},
"runtime": "napi",
"target": 5
},

View File

@@ -132,6 +132,8 @@ namespace sharp {
descriptor->limitInputPixels = AttrAsUint32(input, "limitInputPixels");
// Allow switch from random to sequential access
descriptor->access = AttrAsBool(input, "sequentialRead") ? VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_RANDOM;
// Remove safety features and allow unlimited SVG/PNG input
descriptor->unlimited = AttrAsBool(input, "unlimited");
return descriptor;
}
@@ -328,7 +330,7 @@ namespace sharp {
vips::VOption *option = VImage::option()
->set("access", descriptor->access)
->set("fail", descriptor->failOnError);
if (imageType == ImageType::SVG) {
if (descriptor->unlimited && (imageType == ImageType::SVG || imageType == ImageType::PNG)) {
option->set("unlimited", TRUE);
}
if (imageType == ImageType::SVG || imageType == ImageType::PDF) {
@@ -396,6 +398,10 @@ namespace sharp {
// From filesystem
imageType = DetermineImageType(descriptor->file.data());
if (imageType == ImageType::MISSING) {
if (descriptor->file.find("<svg") != std::string::npos) {
throw vips::VError("Input file is missing, did you mean "
"sharp(Buffer.from('" + descriptor->file.substr(0, 8) + "...')?");
}
throw vips::VError("Input file is missing");
}
if (imageType != ImageType::UNKNOWN) {
@@ -403,7 +409,7 @@ namespace sharp {
vips::VOption *option = VImage::option()
->set("access", descriptor->access)
->set("fail", descriptor->failOnError);
if (imageType == ImageType::SVG) {
if (descriptor->unlimited && (imageType == ImageType::SVG || imageType == ImageType::PNG)) {
option->set("unlimited", TRUE);
}
if (imageType == ImageType::SVG || imageType == ImageType::PDF) {
@@ -488,30 +494,24 @@ namespace sharp {
/*
Set animation properties if necessary.
Non-provided properties will be loaded from image.
*/
VImage SetAnimationProperties(VImage image, int pageHeight, std::vector<int> delay, int loop) {
bool hasDelay = delay.size() != 1 || delay.front() != -1;
VImage SetAnimationProperties(VImage image, int nPages, int pageHeight, std::vector<int> delay, int loop) {
bool hasDelay = !delay.empty();
if (pageHeight == 0 && image.get_typeof(VIPS_META_PAGE_HEIGHT) == G_TYPE_INT) {
pageHeight = image.get_int(VIPS_META_PAGE_HEIGHT);
// Avoid a copy if none of the animation properties are needed.
if (nPages == 1 && !hasDelay && loop == -1) return image;
if (delay.size() == 1) {
// We have just one delay, repeat that value for all frames.
delay.insert(delay.end(), nPages - 1, delay[0]);
}
if (!hasDelay && image.get_typeof("delay") == VIPS_TYPE_ARRAY_INT) {
delay = image.get_array_int("delay");
hasDelay = true;
}
if (loop == -1 && image.get_typeof("loop") == G_TYPE_INT) {
loop = image.get_int("loop");
}
if (pageHeight == 0) return image;
// It is necessary to create the copy as otherwise, pageHeight will be ignored!
// Attaching metadata, need to copy the image.
VImage copy = image.copy();
copy.set(VIPS_META_PAGE_HEIGHT, pageHeight);
// Only set page-height if we have more than one page, or this could
// accidentally turn into an animated image later.
if (nPages > 1) copy.set(VIPS_META_PAGE_HEIGHT, pageHeight);
if (hasDelay) copy.set("delay", delay);
if (loop != -1) copy.set("loop", loop);
@@ -554,6 +554,14 @@ namespace sharp {
return copy;
}
/*
Multi-page images can have a page height. Fetch it, and sanity check it.
If page-height is not set, it defaults to the image height
*/
int GetPageHeight(VImage image) {
return vips_image_get_page_height(image.get_image());
}
/*
Check the proposed format supports the current dimensions.
*/
@@ -610,6 +618,33 @@ namespace sharp {
return warning;
}
/*
Attach an event listener for progress updates, used to detect timeout
*/
void SetTimeout(VImage image, int const seconds) {
if (seconds > 0) {
VipsImage *im = image.get_image();
if (im->progress_signal == NULL) {
int *timeout = VIPS_NEW(im, int);
*timeout = seconds;
g_signal_connect(im, "eval", G_CALLBACK(VipsProgressCallBack), timeout);
vips_image_set_progress(im, TRUE);
}
}
}
/*
Event listener for progress updates, used to detect timeout
*/
void VipsProgressCallBack(VipsImage *im, VipsProgress *progress, int *timeout) {
// printf("VipsProgressCallBack progress=%d run=%d timeout=%d\n", progress->percent, progress->run, *timeout);
if (*timeout > 0 && progress->run >= *timeout) {
vips_image_set_kill(im, TRUE);
vips_error("timeout", "%d%% complete", progress->percent);
*timeout = 0;
}
}
/*
Calculate the (left, top) coordinates of the output image
within the input image, applying the given gravity during an embed.
@@ -853,4 +888,79 @@ namespace sharp {
return image;
}
std::pair<double, double> ResolveShrink(int width, int height, int targetWidth, int targetHeight,
Canvas canvas, bool swap, bool withoutEnlargement, bool withoutReduction) {
if (swap) {
// Swap input width and height when requested.
std::swap(width, height);
}
double hshrink = 1.0;
double vshrink = 1.0;
if (targetWidth > 0 && targetHeight > 0) {
// Fixed width and height
hshrink = static_cast<double>(width) / targetWidth;
vshrink = static_cast<double>(height) / targetHeight;
switch (canvas) {
case Canvas::CROP:
case Canvas::MIN:
if (hshrink < vshrink) {
vshrink = hshrink;
} else {
hshrink = vshrink;
}
break;
case Canvas::EMBED:
case Canvas::MAX:
if (hshrink > vshrink) {
vshrink = hshrink;
} else {
hshrink = vshrink;
}
break;
case Canvas::IGNORE_ASPECT:
if (swap) {
std::swap(hshrink, vshrink);
}
break;
}
} else if (targetWidth > 0) {
// Fixed width
hshrink = static_cast<double>(width) / targetWidth;
if (canvas != Canvas::IGNORE_ASPECT) {
// Auto height
vshrink = hshrink;
}
} else if (targetHeight > 0) {
// Fixed height
vshrink = static_cast<double>(height) / targetHeight;
if (canvas != Canvas::IGNORE_ASPECT) {
// Auto width
hshrink = vshrink;
}
}
// We should not reduce or enlarge the output image, if
// withoutReduction or withoutEnlargement is specified.
if (withoutReduction) {
// Equivalent of VIPS_SIZE_UP
hshrink = std::min(1.0, hshrink);
vshrink = std::min(1.0, vshrink);
} else if (withoutEnlargement) {
// Equivalent of VIPS_SIZE_DOWN
hshrink = std::max(1.0, hshrink);
vshrink = std::max(1.0, vshrink);
}
// We don't want to shrink so much that we send an axis to 0
hshrink = std::min(hshrink, static_cast<double>(width));
vshrink = std::min(vshrink, static_cast<double>(height));
return std::make_pair(hshrink, vshrink);
}
} // namespace sharp

View File

@@ -25,9 +25,9 @@
// Verify platform and compiler compatibility
#if (VIPS_MAJOR_VERSION < 8) || \
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 11) || \
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 11 && VIPS_MICRO_VERSION < 3)
#error "libvips version 8.11.3+ is required - please see https://sharp.pixelplumbing.com/install"
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 12) || \
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 12 && VIPS_MICRO_VERSION < 2)
#error "libvips version 8.12.2+ is required - please see https://sharp.pixelplumbing.com/install"
#endif
#if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6)))
@@ -50,6 +50,7 @@ namespace sharp {
char *buffer;
bool failOnError;
int limitInputPixels;
bool unlimited;
VipsAccess access;
size_t bufferLength;
bool isBuffer;
@@ -75,6 +76,7 @@ namespace sharp {
buffer(nullptr),
failOnError(TRUE),
limitInputPixels(0x3FFF * 0x3FFF),
unlimited(FALSE),
access(VIPS_ACCESS_RANDOM),
bufferLength(0),
isBuffer(FALSE),
@@ -133,6 +135,14 @@ namespace sharp {
MISSING
};
enum class Canvas {
CROP,
EMBED,
MAX,
MIN,
IGNORE_ASPECT
};
// How many tasks are in the queue?
extern volatile int counterQueue;
@@ -206,9 +216,8 @@ namespace sharp {
/*
Set animation properties if necessary.
Non-provided properties will be loaded from image.
*/
VImage SetAnimationProperties(VImage image, int pageHeight, std::vector<int> delay, int loop);
VImage SetAnimationProperties(VImage image, int nPages, int pageHeight, std::vector<int> delay, int loop);
/*
Remove animation properties from image.
@@ -230,6 +239,12 @@ namespace sharp {
*/
VImage SetDensity(VImage image, const double density);
/*
Multi-page images can have a page height. Fetch it, and sanity check it.
If page-height is not set, it defaults to the image height
*/
int GetPageHeight(VImage image);
/*
Check the proposed format supports the current dimensions.
*/
@@ -250,6 +265,16 @@ namespace sharp {
*/
std::string VipsWarningPop();
/*
Attach an event listener for progress updates, used to detect timeout
*/
void SetTimeout(VImage image, int const timeoutSeconds);
/*
Event listener for progress updates, used to detect timeout
*/
void VipsProgressCallBack(VipsImage *image, VipsProgress *progress, int *timeoutSeconds);
/*
Calculate the (left, top) coordinates of the output image
within the input image, applying the given gravity during an embed.
@@ -313,6 +338,15 @@ namespace sharp {
*/
VImage EnsureAlpha(VImage image, double const value);
/*
Calculate the shrink factor, taking into account auto-rotate, the canvas
mode, and so on. The hshrink/vshrink are the amount to shrink the input
image axes by in order for the output axes (ie. after rotation) to match
the required thumbnail width/height and canvas mode.
*/
std::pair<double, double> ResolveShrink(int width, int height, int targetWidth, int targetHeight,
Canvas canvas, bool swap, bool withoutEnlargement, bool withoutReduction);
} // namespace sharp
#endif // SRC_COMMON_H_

View File

@@ -93,7 +93,7 @@ negate( std::vector<double> vector )
{
std::vector<double> new_vector( vector.size() );
for( unsigned int i = 0; i < vector.size(); i++ )
for( std::vector<double>::size_type i = 0; i < vector.size(); i++ )
new_vector[i] = vector[i] * -1;
return( new_vector );
@@ -104,7 +104,7 @@ invert( std::vector<double> vector )
{
std::vector<double> new_vector( vector.size() );
for( unsigned int i = 0; i < vector.size(); i++ )
for( std::vector<double>::size_type i = 0; i < vector.size(); i++ )
new_vector[i] = 1.0 / vector[i];
return( new_vector );
@@ -210,7 +210,6 @@ VOption::set( const char *name, std::vector<int> value )
Pair *pair = new Pair( name );
int *array;
unsigned int i;
pair->input = true;
@@ -219,7 +218,7 @@ VOption::set( const char *name, std::vector<int> value )
static_cast< int >( value.size() ) );
array = vips_value_get_array_int( &pair->value, NULL );
for( i = 0; i < value.size(); i++ )
for( std::vector<double>::size_type i = 0; i < value.size(); i++ )
array[i] = value[i];
options.push_back( pair );
@@ -234,7 +233,6 @@ VOption::set( const char *name, std::vector<double> value )
Pair *pair = new Pair( name );
double *array;
unsigned int i;
pair->input = true;
@@ -243,7 +241,7 @@ VOption::set( const char *name, std::vector<double> value )
static_cast< int >( value.size() ) );
array = vips_value_get_array_double( &pair->value, NULL );
for( i = 0; i < value.size(); i++ )
for( std::vector<double>::size_type i = 0; i < value.size(); i++ )
array[i] = value[i];
options.push_back( pair );
@@ -258,7 +256,6 @@ VOption::set( const char *name, std::vector<VImage> value )
Pair *pair = new Pair( name );
VipsImage **array;
unsigned int i;
pair->input = true;
@@ -267,7 +264,7 @@ VOption::set( const char *name, std::vector<VImage> value )
static_cast< int >( value.size() ) );
array = vips_value_get_array_image( &pair->value, NULL );
for( i = 0; i < value.size(); i++ ) {
for( std::vector<double>::size_type i = 0; i < value.size(); i++ ) {
VipsImage *vips_image = value[i].get_image();
array[i] = vips_image;
@@ -488,10 +485,9 @@ VOption::get_operation( VipsOperation *operation )
double *array =
vips_value_get_array_double( value,
&length );
int j;
((*i)->vvector)->resize( length );
for( j = 0; j < length; j++ )
for( int j = 0; j < length; j++ )
(*((*i)->vvector))[j] = array[j];
}
else if( type == VIPS_TYPE_BLOB ) {
@@ -718,17 +714,38 @@ VImage::write_to_buffer( const char *suffix, void **buf, size_t *size,
const char *operation_name;
VipsBlob *blob;
/* Save with the new target API if we can. Fall back to the older
* mechanism in case the saver we need has not been converted yet.
*
* We need to hide any errors from this first phase.
*/
vips__filename_split8( suffix, filename, option_string );
if( !(operation_name = vips_foreign_find_save_buffer( filename )) ) {
vips_error_freeze();
operation_name = vips_foreign_find_save_target( filename );
vips_error_thaw();
if( operation_name ) {
VTarget target = VTarget::new_to_memory();
call_option_string( operation_name, option_string,
(options ? options : VImage::option())->
set( "in", *this )->
set( "target", target ) );
g_object_get( target.get_target(), "blob", &blob, NULL );
}
else if( (operation_name = vips_foreign_find_save_buffer( filename )) ) {
call_option_string( operation_name, option_string,
(options ? options : VImage::option())->
set( "in", *this )->
set( "buffer", &blob ) );
}
else {
delete options;
throw VError();
}
call_option_string( operation_name, option_string,
(options ? options : VImage::option())->
set( "in", *this )->
set( "buffer", &blob ) );
if( blob ) {
if( buf ) {
*buf = VIPS_AREA( blob )->data;
@@ -767,6 +784,7 @@ std::vector<VImage>
VImage::bandsplit( VOption *options ) const
{
std::vector<VImage> b;
b.reserve(bands());
for( int i = 0; i < bands(); i++ )
b.push_back( extract_band( i ) );

View File

@@ -1,5 +1,5 @@
// bodies for vips operations
// Wed May 12 11:30:00 AM CEST 2021
// Mon Nov 1 03:31:09 PM CET 2021
// this file is generated automatically, do not edit!
VImage VImage::CMC2LCh( VOption *options ) const
@@ -1262,6 +1262,34 @@ VImage VImage::gifload_source( VSource source, VOption *options )
return( out );
}
void VImage::gifsave( const char *filename, VOption *options ) const
{
call( "gifsave",
(options ? options : VImage::option())->
set( "in", *this )->
set( "filename", filename ) );
}
VipsBlob *VImage::gifsave_buffer( VOption *options ) const
{
VipsBlob *buffer;
call( "gifsave_buffer",
(options ? options : VImage::option())->
set( "in", *this )->
set( "buffer", &buffer ) );
return( buffer );
}
void VImage::gifsave_target( VTarget target, VOption *options ) const
{
call( "gifsave_target",
(options ? options : VImage::option())->
set( "in", *this )->
set( "target", target ) );
}
VImage VImage::globalbalance( VOption *options ) const
{
VImage out;

View File

@@ -77,6 +77,9 @@ class MetadataWorker : public Napi::AsyncWorker {
if (image.get_typeof("heif-compression") == VIPS_TYPE_REF_STRING) {
baton->compression = image.get_string("heif-compression");
}
if (image.get_typeof(VIPS_META_RESOLUTION_UNIT) == VIPS_TYPE_REF_STRING) {
baton->resolutionUnit = image.get_string(VIPS_META_RESOLUTION_UNIT);
}
if (image.get_typeof("openslide.level-count") == VIPS_TYPE_REF_STRING) {
int const levels = std::stoi(image.get_string("openslide.level-count"));
for (int l = 0; l < levels; l++) {
@@ -198,6 +201,9 @@ class MetadataWorker : public Napi::AsyncWorker {
if (!baton->compression.empty()) {
info.Set("compression", baton->compression);
}
if (!baton->resolutionUnit.empty()) {
info.Set("resolutionUnit", baton->resolutionUnit == "in" ? "inch" : baton->resolutionUnit);
}
if (!baton->levels.empty()) {
int i = 0;
Napi::Array levels = Napi::Array::New(env, static_cast<size_t>(baton->levels.size()));

View File

@@ -40,6 +40,7 @@ struct MetadataBaton {
std::vector<int> delay;
int pagePrimary;
std::string compression;
std::string resolutionUnit;
std::vector<std::pair<int, int>> levels;
int subifds;
std::vector<double> background;

View File

@@ -209,7 +209,8 @@ namespace sharp {
/*
* Sharpen flat and jagged areas. Use sigma of -1.0 for fast sharpen.
*/
VImage Sharpen(VImage image, double const sigma, double const flat, double const jagged) {
VImage Sharpen(VImage image, double const sigma, double const m1, double const m2,
double const x1, double const y2, double const y3) {
if (sigma == -1.0) {
// Fast, mild sharpen
VImage sharpen = VImage::new_matrixv(3, 3,
@@ -224,8 +225,14 @@ namespace sharp {
if (colourspaceBeforeSharpen == VIPS_INTERPRETATION_RGB) {
colourspaceBeforeSharpen = VIPS_INTERPRETATION_sRGB;
}
return image.sharpen(
VImage::option()->set("sigma", sigma)->set("m1", flat)->set("m2", jagged))
return image
.sharpen(VImage::option()
->set("sigma", sigma)
->set("m1", m1)
->set("m2", m2)
->set("x1", x1)
->set("y2", y2)
->set("y3", y3))
.colourspace(colourspaceBeforeSharpen);
}
}
@@ -308,4 +315,98 @@ namespace sharp {
return image;
}
/*
* Split and crop each frame, reassemble, and update pageHeight.
*/
VImage CropMultiPage(VImage image, int left, int top, int width, int height,
int nPages, int *pageHeight) {
if (top == 0 && height == *pageHeight) {
// Fast path; no need to adjust the height of the multi-page image
return image.extract_area(left, 0, width, image.height());
} else {
std::vector<VImage> pages;
pages.reserve(nPages);
// Split the image into cropped frames
for (int i = 0; i < nPages; i++) {
pages.push_back(
image.extract_area(left, *pageHeight * i + top, width, height));
}
// Reassemble the frames into a tall, thin image
VImage assembled = VImage::arrayjoin(pages,
VImage::option()->set("across", 1));
// Update the page height
*pageHeight = height;
return assembled;
}
}
/*
* Split into frames, embed each frame, reassemble, and update pageHeight.
*/
VImage EmbedMultiPage(VImage image, int left, int top, int width, int height,
std::vector<double> background, int nPages, int *pageHeight) {
if (top == 0 && height == *pageHeight) {
// Fast path; no need to adjust the height of the multi-page image
return image.embed(left, 0, width, image.height(), VImage::option()
->set("extend", VIPS_EXTEND_BACKGROUND)
->set("background", background));
} else if (left == 0 && width == image.width()) {
// Fast path; no need to adjust the width of the multi-page image
std::vector<VImage> pages;
pages.reserve(nPages);
// Rearrange the tall image into a vertical grid
image = image.grid(*pageHeight, nPages, 1);
// Do the embed on the wide image
image = image.embed(0, top, image.width(), height, VImage::option()
->set("extend", VIPS_EXTEND_BACKGROUND)
->set("background", background));
// Split the wide image into frames
for (int i = 0; i < nPages; i++) {
pages.push_back(
image.extract_area(width * i, 0, width, height));
}
// Reassemble the frames into a tall, thin image
VImage assembled = VImage::arrayjoin(pages,
VImage::option()->set("across", 1));
// Update the page height
*pageHeight = height;
return assembled;
} else {
std::vector<VImage> pages;
pages.reserve(nPages);
// Split the image into frames
for (int i = 0; i < nPages; i++) {
pages.push_back(
image.extract_area(0, *pageHeight * i, image.width(), *pageHeight));
}
// Embed each frame in the target size
for (int i = 0; i < nPages; i++) {
pages[i] = pages[i].embed(left, top, width, height, VImage::option()
->set("extend", VIPS_EXTEND_BACKGROUND)
->set("background", background));
}
// Reassemble the frames into a tall, thin image
VImage assembled = VImage::arrayjoin(pages,
VImage::option()->set("across", 1));
// Update the page height
*pageHeight = height;
return assembled;
}
}
} // namespace sharp

View File

@@ -64,7 +64,8 @@ namespace sharp {
/*
* Sharpen flat and jagged areas. Use sigma of -1.0 for fast sharpen.
*/
VImage Sharpen(VImage image, double const sigma, double const flat, double const jagged);
VImage Sharpen(VImage image, double const sigma, double const m1, double const m2,
double const x1, double const y2, double const y3);
/*
Threshold an image
@@ -108,6 +109,18 @@ namespace sharp {
*/
VImage EnsureColourspace(VImage image, VipsInterpretation colourspace);
/*
* Split and crop each frame, reassemble, and update pageHeight.
*/
VImage CropMultiPage(VImage image, int left, int top, int width, int height,
int nPages, int *pageHeight);
/*
* Split into frames, embed each frame, reassemble, and update pageHeight.
*/
VImage EmbedMultiPage(VImage image, int left, int top, int width, int height,
std::vector<double> background, int nPages, int *pageHeight);
} // namespace sharp
#endif // SRC_OPERATIONS_H_

View File

@@ -69,15 +69,24 @@ class PipelineWorker : public Napi::AsyncWorker {
std::tie(image, inputImageType) = sharp::OpenInput(baton->input);
image = sharp::EnsureColourspace(image, baton->colourspaceInput);
int nPages = baton->input->pages;
if (nPages == -1) {
// Resolve the number of pages if we need to render until the end of the document
nPages = image.get_typeof(VIPS_META_N_PAGES) != 0
? image.get_int(VIPS_META_N_PAGES) - baton->input->page
: 1;
}
// Get pre-resize page height
int pageHeight = sharp::GetPageHeight(image);
// Calculate angle of rotation
VipsAngle rotation;
bool flip = FALSE;
bool flop = FALSE;
if (baton->useExifOrientation) {
// Rotate and flip image according to Exif orientation
bool flip;
bool flop;
std::tie(rotation, flip, flop) = CalculateExifRotationAndFlip(sharp::ExifOrientation(image));
baton->flip = baton->flip || flip;
baton->flop = baton->flop || flop;
} else {
rotation = CalculateAngleRotation(baton->angle);
}
@@ -86,9 +95,18 @@ class PipelineWorker : public Napi::AsyncWorker {
if (baton->rotateBeforePreExtract) {
if (rotation != VIPS_ANGLE_D0) {
image = image.rot(rotation);
if (flip) {
image = image.flip(VIPS_DIRECTION_VERTICAL);
flip = FALSE;
}
if (flop) {
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
flop = FALSE;
}
image = sharp::RemoveExifOrientation(image);
}
if (baton->rotationAngle != 0.0) {
MultiPageUnsupported(nPages, "Rotate");
std::vector<double> background;
std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground, FALSE);
image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background));
@@ -97,6 +115,7 @@ class PipelineWorker : public Napi::AsyncWorker {
// Trim
if (baton->trimThreshold > 0.0) {
MultiPageUnsupported(nPages, "Trim");
image = sharp::Trim(image, baton->trimThreshold);
baton->trimOffsetLeft = image.xoffset();
baton->trimOffsetTop = image.yoffset();
@@ -104,190 +123,168 @@ class PipelineWorker : public Napi::AsyncWorker {
// Pre extraction
if (baton->topOffsetPre != -1) {
image = image.extract_area(baton->leftOffsetPre, baton->topOffsetPre, baton->widthPre, baton->heightPre);
image = nPages > 1
? sharp::CropMultiPage(image,
baton->leftOffsetPre, baton->topOffsetPre, baton->widthPre, baton->heightPre, nPages, &pageHeight)
: image.extract_area(baton->leftOffsetPre, baton->topOffsetPre, baton->widthPre, baton->heightPre);
}
// Get pre-resize image width and height
int inputWidth = image.width();
int inputHeight = image.height();
if (!baton->rotateBeforePreExtract &&
(rotation == VIPS_ANGLE_D90 || rotation == VIPS_ANGLE_D270)) {
// Swap input output width and height when rotating by 90 or 270 degrees
std::swap(inputWidth, inputHeight);
}
// If withoutEnlargement is specified,
// Override target width and height if exceeds respective value from input file
if (baton->withoutEnlargement) {
if (baton->width > inputWidth) {
baton->width = inputWidth;
}
if (baton->height > inputHeight) {
baton->height = inputHeight;
}
// Is there just one page? Shrink to inputHeight instead
if (nPages == 1) {
pageHeight = inputHeight;
}
// Scaling calculations
double xfactor = 1.0;
double yfactor = 1.0;
double hshrink;
double vshrink;
int targetResizeWidth = baton->width;
int targetResizeHeight = baton->height;
if (baton->width > 0 && baton->height > 0) {
// Fixed width and height
xfactor = static_cast<double>(inputWidth) / static_cast<double>(baton->width);
yfactor = static_cast<double>(inputHeight) / static_cast<double>(baton->height);
switch (baton->canvas) {
case Canvas::CROP:
if (xfactor < yfactor) {
targetResizeHeight = static_cast<int>(round(static_cast<double>(inputHeight) / xfactor));
yfactor = xfactor;
} else {
targetResizeWidth = static_cast<int>(round(static_cast<double>(inputWidth) / yfactor));
xfactor = yfactor;
}
break;
case Canvas::EMBED:
if (xfactor > yfactor) {
targetResizeHeight = static_cast<int>(round(static_cast<double>(inputHeight) / xfactor));
yfactor = xfactor;
} else {
targetResizeWidth = static_cast<int>(round(static_cast<double>(inputWidth) / yfactor));
xfactor = yfactor;
}
break;
case Canvas::MAX:
if (xfactor > yfactor) {
targetResizeHeight = baton->height = static_cast<int>(round(static_cast<double>(inputHeight) / xfactor));
yfactor = xfactor;
} else {
targetResizeWidth = baton->width = static_cast<int>(round(static_cast<double>(inputWidth) / yfactor));
xfactor = yfactor;
}
break;
case Canvas::MIN:
if (xfactor < yfactor) {
targetResizeHeight = baton->height = static_cast<int>(round(static_cast<double>(inputHeight) / xfactor));
yfactor = xfactor;
} else {
targetResizeWidth = baton->width = static_cast<int>(round(static_cast<double>(inputWidth) / yfactor));
xfactor = yfactor;
}
break;
case Canvas::IGNORE_ASPECT:
if (!baton->rotateBeforePreExtract &&
(rotation == VIPS_ANGLE_D90 || rotation == VIPS_ANGLE_D270)) {
std::swap(xfactor, yfactor);
}
break;
}
} else if (baton->width > 0) {
// Fixed width
xfactor = static_cast<double>(inputWidth) / static_cast<double>(baton->width);
if (baton->canvas == Canvas::IGNORE_ASPECT) {
targetResizeHeight = baton->height = inputHeight;
} else {
// Auto height
yfactor = xfactor;
targetResizeHeight = baton->height = static_cast<int>(round(static_cast<double>(inputHeight) / yfactor));
}
} else if (baton->height > 0) {
// Fixed height
yfactor = static_cast<double>(inputHeight) / static_cast<double>(baton->height);
if (baton->canvas == Canvas::IGNORE_ASPECT) {
targetResizeWidth = baton->width = inputWidth;
} else {
// Auto width
xfactor = yfactor;
targetResizeWidth = baton->width = static_cast<int>(round(static_cast<double>(inputWidth) / xfactor));
}
} else {
// Identity transform
baton->width = inputWidth;
baton->height = inputHeight;
}
// Calculate integral box shrink
int xshrink = std::max(1, static_cast<int>(floor(xfactor)));
int yshrink = std::max(1, static_cast<int>(floor(yfactor)));
// Swap input output width and height when rotating by 90 or 270 degrees
bool swap = !baton->rotateBeforePreExtract && (rotation == VIPS_ANGLE_D90 || rotation == VIPS_ANGLE_D270);
// Calculate residual float affine transformation
double xresidual = static_cast<double>(xshrink) / xfactor;
double yresidual = static_cast<double>(yshrink) / yfactor;
// Shrink to pageHeight, so we work for multi-page images
std::tie(hshrink, vshrink) = sharp::ResolveShrink(
inputWidth, pageHeight, targetResizeWidth, targetResizeHeight,
baton->canvas, swap, baton->withoutEnlargement, baton->withoutReduction);
// If integral x and y shrink are equal, try to use shrink-on-load for JPEG and WebP,
// but not when applying gamma correction, pre-resize extract, trim or input colourspace
int shrink_on_load = 1;
// The jpeg preload shrink.
int jpegShrinkOnLoad = 1;
int shrink_on_load_factor = 1;
// Leave at least a factor of two for the final resize step, when fastShrinkOnLoad: false
// for more consistent results and avoid occasional small image shifting
if (!baton->fastShrinkOnLoad) {
shrink_on_load_factor = 2;
}
if (
xshrink == yshrink && xshrink >= 2 * shrink_on_load_factor &&
(inputImageType == sharp::ImageType::JPEG || inputImageType == sharp::ImageType::WEBP) &&
// WebP, PDF, SVG scale
double scale = 1.0;
// Try to reload input using shrink-on-load for JPEG, WebP, SVG and PDF, when:
// - the width or height parameters are specified;
// - gamma correction doesn't need to be applied;
// - trimming or pre-resize extract isn't required;
// - 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 &&
image.width() > 3 && image.height() > 3 && baton->input->pages == 1
) {
if (xshrink >= 8 * shrink_on_load_factor) {
xfactor = xfactor / 8;
yfactor = yfactor / 8;
shrink_on_load = 8;
} else if (xshrink >= 4 * shrink_on_load_factor) {
xfactor = xfactor / 4;
yfactor = yfactor / 4;
shrink_on_load = 4;
} else if (xshrink >= 2 * shrink_on_load_factor) {
xfactor = xfactor / 2;
yfactor = yfactor / 2;
shrink_on_load = 2;
baton->colourspaceInput == VIPS_INTERPRETATION_LAST;
if (shouldPreShrink) {
// The common part of the shrink: the bit by which both axes must be shrunk
double shrink = std::min(hshrink, vshrink);
if (inputImageType == sharp::ImageType::JPEG) {
// Leave at least a factor of two for the final resize step, when fastShrinkOnLoad: false
// for more consistent results and to avoid extra sharpness to the image
int factor = baton->fastShrinkOnLoad ? 1 : 2;
if (shrink >= 8 * factor) {
jpegShrinkOnLoad = 8;
} else if (shrink >= 4 * factor) {
jpegShrinkOnLoad = 4;
} else if (shrink >= 2 * factor) {
jpegShrinkOnLoad = 2;
}
// Lower shrink-on-load for known libjpeg rounding errors
if (jpegShrinkOnLoad > 1 && static_cast<int>(shrink) == jpegShrinkOnLoad) {
jpegShrinkOnLoad /= 2;
}
} else if (inputImageType == sharp::ImageType::WEBP ||
inputImageType == sharp::ImageType::SVG ||
inputImageType == sharp::ImageType::PDF) {
scale = 1.0 / shrink;
}
}
// Help ensure a final kernel-based reduction to prevent shrink aliasing
if (shrink_on_load > 1 && (xresidual == 1.0 || yresidual == 1.0)) {
shrink_on_load = shrink_on_load / 2;
xfactor = xfactor * 2;
yfactor = yfactor * 2;
}
if (shrink_on_load > 1) {
// Reload input using shrink-on-load
// Reload input using shrink-on-load, it'll be an integer shrink
// factor for jpegload*, a double scale factor for webpload*,
// pdfload* and svgload*
if (jpegShrinkOnLoad > 1) {
vips::VOption *option = VImage::option()
->set("access", baton->input->access)
->set("shrink", shrink_on_load)
->set("shrink", jpegShrinkOnLoad)
->set("fail", baton->input->failOnError);
if (baton->input->buffer != nullptr) {
// Reload JPEG buffer
VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength);
if (inputImageType == sharp::ImageType::JPEG) {
// Reload JPEG buffer
image = VImage::jpegload_buffer(blob, option);
} else {
// Reload WebP buffer
image = VImage::webpload_buffer(blob, option);
}
image = VImage::jpegload_buffer(blob, option);
vips_area_unref(reinterpret_cast<VipsArea*>(blob));
} else {
if (inputImageType == sharp::ImageType::JPEG) {
// Reload JPEG file
image = VImage::jpegload(const_cast<char*>(baton->input->file.data()), option);
// Reload JPEG file
image = VImage::jpegload(const_cast<char*>(baton->input->file.data()), option);
}
} else if (scale != 1.0) {
vips::VOption *option = VImage::option()
->set("access", baton->input->access)
->set("scale", scale)
->set("fail", baton->input->failOnError);
if (inputImageType == sharp::ImageType::WEBP) {
option->set("n", baton->input->pages);
option->set("page", baton->input->page);
if (baton->input->buffer != nullptr) {
// Reload WebP buffer
VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength);
image = VImage::webpload_buffer(blob, option);
vips_area_unref(reinterpret_cast<VipsArea*>(blob));
} else {
// Reload WebP file
image = VImage::webpload(const_cast<char*>(baton->input->file.data()), option);
}
} else if (inputImageType == sharp::ImageType::SVG) {
option->set("unlimited", baton->input->unlimited);
option->set("dpi", baton->input->density);
if (baton->input->buffer != nullptr) {
// Reload SVG buffer
VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength);
image = VImage::svgload_buffer(blob, option);
vips_area_unref(reinterpret_cast<VipsArea*>(blob));
} else {
// Reload SVG file
image = VImage::svgload(const_cast<char*>(baton->input->file.data()), option);
}
sharp::SetDensity(image, baton->input->density);
} else if (inputImageType == sharp::ImageType::PDF) {
option->set("n", baton->input->pages);
option->set("page", baton->input->page);
option->set("dpi", baton->input->density);
if (baton->input->buffer != nullptr) {
// Reload PDF buffer
VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength);
image = VImage::pdfload_buffer(blob, option);
vips_area_unref(reinterpret_cast<VipsArea*>(blob));
} else {
// Reload PDF file
image = VImage::pdfload(const_cast<char*>(baton->input->file.data()), option);
}
sharp::SetDensity(image, baton->input->density);
}
// Recalculate integral shrink and double residual
int const shrunkOnLoadWidth = image.width();
int const shrunkOnLoadHeight = image.height();
if (!baton->rotateBeforePreExtract &&
(rotation == VIPS_ANGLE_D90 || rotation == VIPS_ANGLE_D270)) {
// Swap when rotating by 90 or 270 degrees
xfactor = static_cast<double>(shrunkOnLoadWidth) / static_cast<double>(targetResizeHeight);
yfactor = static_cast<double>(shrunkOnLoadHeight) / static_cast<double>(targetResizeWidth);
} else {
xfactor = static_cast<double>(shrunkOnLoadWidth) / static_cast<double>(targetResizeWidth);
yfactor = static_cast<double>(shrunkOnLoadHeight) / static_cast<double>(targetResizeHeight);
}
}
// Any pre-shrinking may already have been done
inputWidth = image.width();
inputHeight = image.height();
// After pre-shrink, but before the main shrink stage
// Reuse the initial pageHeight if we didn't pre-shrink
if (shouldPreShrink) {
pageHeight = sharp::GetPageHeight(image);
}
// Shrink to pageHeight, so we work for multi-page images
std::tie(hshrink, vshrink) = sharp::ResolveShrink(
inputWidth, pageHeight, targetResizeWidth, targetResizeHeight,
baton->canvas, swap, baton->withoutEnlargement, baton->withoutReduction);
int targetHeight = static_cast<int>(std::rint(static_cast<double>(pageHeight) / vshrink));
int targetPageHeight = targetHeight;
// In toilet-roll mode, we must adjust vshrink so that we exactly hit
// pageHeight or we'll have pixels straddling pixel boundaries
if (inputHeight > pageHeight) {
targetHeight *= nPages;
vshrink = static_cast<double>(inputHeight) / targetHeight;
}
// Ensure we're using a device-independent colour space
@@ -295,7 +292,8 @@ class PipelineWorker : public Napi::AsyncWorker {
if (
sharp::HasProfile(image) &&
image.interpretation() != VIPS_INTERPRETATION_LABS &&
image.interpretation() != VIPS_INTERPRETATION_GREY16
image.interpretation() != VIPS_INTERPRETATION_GREY16 &&
image.interpretation() != VIPS_INTERPRETATION_B_W
) {
// Convert to sRGB/P3 using embedded profile
try {
@@ -341,7 +339,7 @@ class PipelineWorker : public Napi::AsyncWorker {
image = image.colourspace(VIPS_INTERPRETATION_B_W);
}
bool const shouldResize = xfactor != 1.0 || yfactor != 1.0;
bool const shouldResize = hshrink != 1.0 || vshrink != 1.0;
bool const shouldBlur = baton->blurSigma != 0.0;
bool const shouldConv = baton->convKernelWidth * baton->convKernelHeight > 0;
bool const shouldSharpen = baton->sharpenSigma != 0.0;
@@ -356,7 +354,7 @@ class PipelineWorker : public Napi::AsyncWorker {
}
bool const shouldPremultiplyAlpha = sharp::HasAlpha(image) &&
(shouldResize || shouldBlur || shouldConv || shouldSharpen || shouldComposite);
(shouldResize || shouldBlur || shouldConv || shouldSharpen);
// Premultiply image alpha channel before all transformations to avoid
// dark fringing around bright pixels
@@ -375,35 +373,33 @@ class PipelineWorker : public Napi::AsyncWorker {
) {
throw vips::VError("Unknown kernel");
}
// Ensure shortest edge is at least 1 pixel
if (image.width() / xfactor < 0.5) {
xfactor = 2 * image.width();
baton->width = 1;
}
if (image.height() / yfactor < 0.5) {
yfactor = 2 * image.height();
baton->height = 1;
}
image = image.resize(1.0 / xfactor, VImage::option()
->set("vscale", 1.0 / yfactor)
image = image.resize(1.0 / hshrink, VImage::option()
->set("vscale", 1.0 / vshrink)
->set("kernel", kernel));
}
// Rotate post-extract 90-angle
if (!baton->rotateBeforePreExtract && rotation != VIPS_ANGLE_D0) {
image = image.rot(rotation);
image = sharp::RemoveExifOrientation(image);
if (!baton->rotateBeforePreExtract && rotation != VIPS_ANGLE_D0) {
image = image.rot(rotation);
if (flip) {
image = image.flip(VIPS_DIRECTION_VERTICAL);
flip = FALSE;
}
if (flop) {
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
flop = FALSE;
}
image = sharp::RemoveExifOrientation(image);
}
// Flip (mirror about Y axis)
if (baton->flip) {
if (baton->flip || flip) {
image = image.flip(VIPS_DIRECTION_VERTICAL);
image = sharp::RemoveExifOrientation(image);
}
// Flop (mirror about X axis)
if (baton->flop) {
if (baton->flop || flop) {
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
image = sharp::RemoveExifOrientation(image);
}
@@ -421,52 +417,68 @@ class PipelineWorker : public Napi::AsyncWorker {
image = image.copy(VImage::option()->set("interpretation", baton->colourspace));
}
inputWidth = image.width();
inputHeight = nPages > 1 ? targetPageHeight : image.height();
// Resolve dimensions
if (baton->width <= 0) {
baton->width = inputWidth;
}
if (baton->height <= 0) {
baton->height = inputHeight;
}
// Crop/embed
if (image.width() != baton->width || image.height() != baton->height) {
if (baton->canvas == Canvas::EMBED) {
if (inputWidth != baton->width || inputHeight != baton->height) {
if (baton->canvas == sharp::Canvas::EMBED) {
std::vector<double> background;
std::tie(image, background) = sharp::ApplyAlpha(image, baton->resizeBackground, shouldPremultiplyAlpha);
// Embed
// Calculate where to position the embeded image if gravity specified, else center.
// Calculate where to position the embedded image if gravity specified, else center.
int left;
int top;
left = static_cast<int>(round((baton->width - image.width()) / 2));
top = static_cast<int>(round((baton->height - image.height()) / 2));
left = static_cast<int>(round((baton->width - inputWidth) / 2));
top = static_cast<int>(round((baton->height - inputHeight) / 2));
int width = std::max(image.width(), baton->width);
int height = std::max(image.height(), baton->height);
int width = std::max(inputWidth, baton->width);
int height = std::max(inputHeight, baton->height);
std::tie(left, top) = sharp::CalculateEmbedPosition(
image.width(), image.height(), baton->width, baton->height, baton->position);
inputWidth, inputHeight, baton->width, baton->height, baton->position);
image = image.embed(left, top, width, height, VImage::option()
->set("extend", VIPS_EXTEND_BACKGROUND)
->set("background", background));
image = nPages > 1
? sharp::EmbedMultiPage(image,
left, top, width, height, background, nPages, &targetPageHeight)
: image.embed(left, top, width, height, VImage::option()
->set("extend", VIPS_EXTEND_BACKGROUND)
->set("background", background));
} else if (baton->canvas == sharp::Canvas::CROP) {
if (baton->width > inputWidth) {
baton->width = inputWidth;
}
if (baton->height > inputHeight) {
baton->height = inputHeight;
}
} else if (
baton->canvas != Canvas::IGNORE_ASPECT &&
(image.width() > baton->width || image.height() > baton->height)
) {
// Crop/max/min
// Crop
if (baton->position < 9) {
// Gravity-based crop
int left;
int top;
std::tie(left, top) = sharp::CalculateCrop(
image.width(), image.height(), baton->width, baton->height, baton->position);
int width = std::min(image.width(), baton->width);
int height = std::min(image.height(), baton->height);
image = image.extract_area(left, top, width, height);
inputWidth, inputHeight, baton->width, baton->height, baton->position);
int width = std::min(inputWidth, baton->width);
int height = std::min(inputHeight, baton->height);
image = nPages > 1
? sharp::CropMultiPage(image,
left, top, width, height, nPages, &targetPageHeight)
: image.extract_area(left, top, width, height);
} else {
// Attention-based or Entropy-based crop
if (baton->width > image.width()) {
baton->width = image.width();
}
if (baton->height > image.height()) {
baton->height = image.height();
}
MultiPageUnsupported(nPages, "Resize strategy");
image = image.tilecache(VImage::option()
->set("access", VIPS_ACCESS_RANDOM)
->set("threaded", TRUE));
@@ -481,6 +493,7 @@ class PipelineWorker : public Napi::AsyncWorker {
// Rotate post-extract non-90 angle
if (!baton->rotateBeforePreExtract && baton->rotationAngle != 0.0) {
MultiPageUnsupported(nPages, "Rotate");
std::vector<double> background;
std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground, shouldPremultiplyAlpha);
image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background));
@@ -488,20 +501,32 @@ class PipelineWorker : public Napi::AsyncWorker {
// Post extraction
if (baton->topOffsetPost != -1) {
image = image.extract_area(
baton->leftOffsetPost, baton->topOffsetPost, baton->widthPost, baton->heightPost);
if (nPages > 1) {
image = sharp::CropMultiPage(image,
baton->leftOffsetPost, baton->topOffsetPost, baton->widthPost, baton->heightPost,
nPages, &targetPageHeight);
// heightPost is used in the info object, so update to reflect the number of pages
baton->heightPost *= nPages;
} else {
image = image.extract_area(
baton->leftOffsetPost, baton->topOffsetPost, baton->widthPost, baton->heightPost);
}
}
// Affine transform
if (baton->affineMatrix.size() > 0) {
MultiPageUnsupported(nPages, "Affine");
std::vector<double> background;
std::tie(image, background) = sharp::ApplyAlpha(image, baton->affineBackground, shouldPremultiplyAlpha);
vips::VInterpolate interp = vips::VInterpolate::new_from_name(
const_cast<char*>(baton->affineInterpolator.data()));
image = image.affine(baton->affineMatrix, VImage::option()->set("background", background)
->set("idx", baton->affineIdx)
->set("idy", baton->affineIdy)
->set("odx", baton->affineOdx)
->set("ody", baton->affineOdy)
->set("interpolate", baton->affineInterpolator));
->set("interpolate", interp));
}
// Extend edges
@@ -511,10 +536,13 @@ class PipelineWorker : public Napi::AsyncWorker {
// Embed
baton->width = image.width() + baton->extendLeft + baton->extendRight;
baton->height = image.height() + baton->extendTop + baton->extendBottom;
baton->height = (nPages > 1 ? targetPageHeight : image.height()) + baton->extendTop + baton->extendBottom;
image = image.embed(baton->extendLeft, baton->extendTop, baton->width, baton->height,
VImage::option()->set("extend", VIPS_EXTEND_BACKGROUND)->set("background", background));
image = nPages > 1
? sharp::EmbedMultiPage(image,
baton->extendLeft, baton->extendTop, baton->width, baton->height, background, nPages, &targetPageHeight)
: image.embed(baton->extendLeft, baton->extendTop, baton->width, baton->height,
VImage::option()->set("extend", VIPS_EXTEND_BACKGROUND)->set("background", background));
}
// Median - must happen before blurring, due to the utility of blurring after thresholding
if (shouldApplyMedian) {
@@ -549,11 +577,14 @@ class PipelineWorker : public Napi::AsyncWorker {
// Sharpen
if (shouldSharpen) {
image = sharp::Sharpen(image, baton->sharpenSigma, baton->sharpenFlat, baton->sharpenJagged);
image = sharp::Sharpen(image, baton->sharpenSigma, baton->sharpenM1, baton->sharpenM2,
baton->sharpenX1, baton->sharpenY2, baton->sharpenY3);
}
// Composite
if (shouldComposite) {
std::vector<VImage> images = { image };
std::vector<int> modes, xs, ys;
for (Composite *composite : baton->composite) {
VImage compositeImage;
sharp::ImageType compositeImageType = sharp::ImageType::UNKNOWN;
@@ -599,12 +630,12 @@ class PipelineWorker : public Napi::AsyncWorker {
// gravity was used for extract_area, set it back to its default value of 0
composite->gravity = 0;
}
// Ensure image to composite is sRGB with premultiplied alpha
// Ensure image to composite is sRGB with unpremultiplied alpha
compositeImage = compositeImage.colourspace(VIPS_INTERPRETATION_sRGB);
if (!sharp::HasAlpha(compositeImage)) {
compositeImage = sharp::EnsureAlpha(compositeImage, 1);
}
if (!composite->premultiplied) compositeImage = compositeImage.premultiply();
if (composite->premultiplied) compositeImage = compositeImage.unpremultiply();
// Calculate position
int left;
int top;
@@ -622,12 +653,12 @@ class PipelineWorker : public Napi::AsyncWorker {
std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),
compositeImage.width(), compositeImage.height(), composite->gravity);
}
// Composite
image = image.composite2(compositeImage, composite->mode, VImage::option()
->set("premultiplied", TRUE)
->set("x", left)
->set("y", top));
images.push_back(compositeImage);
modes.push_back(composite->mode);
xs.push_back(left);
ys.push_back(top);
}
image = image.composite(images, modes, VImage::option()->set("x", xs)->set("y", ys));
}
// Reverse premultiplication after all transformations:
@@ -754,16 +785,11 @@ class PipelineWorker : public Napi::AsyncWorker {
baton->width = image.width();
baton->height = image.height();
bool const supportsGifOutput = vips_type_find("VipsOperation", "magicksave") != 0 &&
vips_type_find("VipsOperation", "magicksave_buffer") != 0;
image = sharp::SetAnimationProperties(
image,
baton->pageHeight,
baton->delay,
baton->loop);
image, nPages, targetPageHeight, baton->delay, baton->loop);
// Output
sharp::SetTimeout(image, baton->timeoutSeconds);
if (baton->fileOut.empty()) {
// Buffer output
if (baton->formatOut == "jpeg" || (baton->formatOut == "input" && inputImageType == sharp::ImageType::JPEG)) {
@@ -808,8 +834,7 @@ class PipelineWorker : public Napi::AsyncWorker {
vips_area_unref(area);
baton->formatOut = "jp2";
} else if (baton->formatOut == "png" || (baton->formatOut == "input" &&
(inputImageType == sharp::ImageType::PNG || (inputImageType == sharp::ImageType::GIF && !supportsGifOutput) ||
inputImageType == sharp::ImageType::SVG))) {
(inputImageType == sharp::ImageType::PNG || inputImageType == sharp::ImageType::SVG))) {
// Write PNG to buffer
sharp::AssertImageTypeDimensions(image, sharp::ImageType::PNG);
VipsArea *area = reinterpret_cast<VipsArea*>(image.pngsave_buffer(VImage::option()
@@ -819,7 +844,8 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("filter", baton->pngAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_ALL : VIPS_FOREIGN_PNG_FILTER_NONE)
->set("palette", baton->pngPalette)
->set("Q", baton->pngQuality)
->set("bitdepth", baton->pngBitdepth)
->set("effort", baton->pngEffort)
->set("bitdepth", sharp::Is16Bit(image.interpretation()) ? 16 : baton->pngBitdepth)
->set("dither", baton->pngDither)));
baton->bufferOut = static_cast<char*>(area->data);
baton->bufferOutLength = area->length;
@@ -836,7 +862,7 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("lossless", baton->webpLossless)
->set("near_lossless", baton->webpNearLossless)
->set("smart_subsample", baton->webpSmartSubsample)
->set("reduction_effort", baton->webpReductionEffort)
->set("effort", baton->webpEffort)
->set("alpha_q", baton->webpAlphaQuality)));
baton->bufferOut = static_cast<char*>(area->data);
baton->bufferOutLength = area->length;
@@ -844,14 +870,14 @@ class PipelineWorker : public Napi::AsyncWorker {
vips_area_unref(area);
baton->formatOut = "webp";
} else if (baton->formatOut == "gif" ||
(baton->formatOut == "input" && inputImageType == sharp::ImageType::GIF && supportsGifOutput)) {
(baton->formatOut == "input" && inputImageType == sharp::ImageType::GIF)) {
// Write GIF to buffer
sharp::AssertImageTypeDimensions(image, sharp::ImageType::GIF);
VipsArea *area = reinterpret_cast<VipsArea*>(image.magicksave_buffer(VImage::option()
VipsArea *area = reinterpret_cast<VipsArea*>(image.gifsave_buffer(VImage::option()
->set("strip", !baton->withMetadata)
->set("optimize_gif_frames", TRUE)
->set("optimize_gif_transparency", TRUE)
->set("format", "gif")));
->set("bitdepth", baton->gifBitdepth)
->set("effort", baton->gifEffort)
->set("dither", baton->gifDither)));
baton->bufferOut = static_cast<char*>(area->data);
baton->bufferOutLength = area->length;
area->free_fn = nullptr;
@@ -879,7 +905,8 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("tile_height", baton->tiffTileHeight)
->set("tile_width", baton->tiffTileWidth)
->set("xres", baton->tiffXres)
->set("yres", baton->tiffYres)));
->set("yres", baton->tiffYres)
->set("resunit", baton->tiffResolutionUnit)));
baton->bufferOut = static_cast<char*>(area->data);
baton->bufferOutLength = area->length;
area->free_fn = nullptr;
@@ -893,7 +920,7 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("strip", !baton->withMetadata)
->set("Q", baton->heifQuality)
->set("compression", baton->heifCompression)
->set("speed", baton->heifSpeed)
->set("effort", baton->heifEffort)
->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
->set("lossless", baton->heifLossless)));
@@ -978,8 +1005,7 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("tile_width", baton->jp2TileWidth));
baton->formatOut = "jp2";
} else if (baton->formatOut == "png" || (mightMatchInput && isPng) || (willMatchInput &&
(inputImageType == sharp::ImageType::PNG || (inputImageType == sharp::ImageType::GIF && !supportsGifOutput) ||
inputImageType == sharp::ImageType::SVG))) {
(inputImageType == sharp::ImageType::PNG || inputImageType == sharp::ImageType::SVG))) {
// Write PNG to file
sharp::AssertImageTypeDimensions(image, sharp::ImageType::PNG);
image.pngsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
@@ -989,7 +1015,8 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("filter", baton->pngAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_ALL : VIPS_FOREIGN_PNG_FILTER_NONE)
->set("palette", baton->pngPalette)
->set("Q", baton->pngQuality)
->set("bitdepth", baton->pngBitdepth)
->set("bitdepth", sharp::Is16Bit(image.interpretation()) ? 16 : baton->pngBitdepth)
->set("effort", baton->pngEffort)
->set("dither", baton->pngDither));
baton->formatOut = "png";
} else if (baton->formatOut == "webp" || (mightMatchInput && isWebp) ||
@@ -1002,18 +1029,18 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("lossless", baton->webpLossless)
->set("near_lossless", baton->webpNearLossless)
->set("smart_subsample", baton->webpSmartSubsample)
->set("reduction_effort", baton->webpReductionEffort)
->set("effort", baton->webpEffort)
->set("alpha_q", baton->webpAlphaQuality));
baton->formatOut = "webp";
} else if (baton->formatOut == "gif" || (mightMatchInput && isGif) ||
(willMatchInput && inputImageType == sharp::ImageType::GIF && supportsGifOutput)) {
(willMatchInput && inputImageType == sharp::ImageType::GIF)) {
// Write GIF to file
sharp::AssertImageTypeDimensions(image, sharp::ImageType::GIF);
image.magicksave(const_cast<char*>(baton->fileOut.data()), VImage::option()
image.gifsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata)
->set("optimize_gif_frames", TRUE)
->set("optimize_gif_transparency", TRUE)
->set("format", "gif"));
->set("bitdepth", baton->gifBitdepth)
->set("effort", baton->gifEffort)
->set("dither", baton->gifDither));
baton->formatOut = "gif";
} else if (baton->formatOut == "tiff" || (mightMatchInput && isTiff) ||
(willMatchInput && inputImageType == sharp::ImageType::TIFF)) {
@@ -1037,7 +1064,8 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("tile_height", baton->tiffTileHeight)
->set("tile_width", baton->tiffTileWidth)
->set("xres", baton->tiffXres)
->set("yres", baton->tiffYres));
->set("yres", baton->tiffYres)
->set("resunit", baton->tiffResolutionUnit));
baton->formatOut = "tiff";
} else if (baton->formatOut == "heif" || (mightMatchInput && isHeif) ||
(willMatchInput && inputImageType == sharp::ImageType::HEIF)) {
@@ -1047,7 +1075,7 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("strip", !baton->withMetadata)
->set("Q", baton->heifQuality)
->set("compression", baton->heifCompression)
->set("speed", baton->heifSpeed)
->set("effort", baton->heifEffort)
->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
->set("lossless", baton->heifLossless));
@@ -1072,7 +1100,7 @@ class PipelineWorker : public Napi::AsyncWorker {
{"lossless", baton->webpLossless ? "TRUE" : "FALSE"},
{"near_lossless", baton->webpNearLossless ? "TRUE" : "FALSE"},
{"smart_subsample", baton->webpSmartSubsample ? "TRUE" : "FALSE"},
{"reduction_effort", std::to_string(baton->webpReductionEffort)}
{"effort", std::to_string(baton->webpEffort)}
};
suffix = AssembleSuffixString(".webp", options);
} else {
@@ -1221,6 +1249,12 @@ class PipelineWorker : public Napi::AsyncWorker {
Napi::FunctionReference debuglog;
Napi::FunctionReference queueListener;
void MultiPageUnsupported(int const pages, std::string op) {
if (pages > 1) {
throw vips::VError(op + " is not supported for multi-page images");
}
}
/*
Calculate the angle of rotation and need-to-flip for the given Exif orientation
By default, returns zero, i.e. no rotation.
@@ -1311,15 +1345,15 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
// Canvas option
std::string canvas = sharp::AttrAsStr(options, "canvas");
if (canvas == "crop") {
baton->canvas = Canvas::CROP;
baton->canvas = sharp::Canvas::CROP;
} else if (canvas == "embed") {
baton->canvas = Canvas::EMBED;
baton->canvas = sharp::Canvas::EMBED;
} else if (canvas == "max") {
baton->canvas = Canvas::MAX;
baton->canvas = sharp::Canvas::MAX;
} else if (canvas == "min") {
baton->canvas = Canvas::MIN;
baton->canvas = sharp::Canvas::MIN;
} else if (canvas == "ignore_aspect") {
baton->canvas = Canvas::IGNORE_ASPECT;
baton->canvas = sharp::Canvas::IGNORE_ASPECT;
}
// Tint chroma
baton->tintA = sharp::AttrAsDouble(options, "tintA");
@@ -1342,6 +1376,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
}
// Resize options
baton->withoutEnlargement = sharp::AttrAsBool(options, "withoutEnlargement");
baton->withoutReduction = sharp::AttrAsBool(options, "withoutReduction");
baton->position = sharp::AttrAsInt32(options, "position");
baton->resizeBackground = sharp::AttrAsVectorOfDouble(options, "resizeBackground");
baton->kernel = sharp::AttrAsStr(options, "kernel");
@@ -1366,8 +1401,11 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
baton->lightness = sharp::AttrAsDouble(options, "lightness");
baton->medianSize = sharp::AttrAsUint32(options, "medianSize");
baton->sharpenSigma = sharp::AttrAsDouble(options, "sharpenSigma");
baton->sharpenFlat = sharp::AttrAsDouble(options, "sharpenFlat");
baton->sharpenJagged = sharp::AttrAsDouble(options, "sharpenJagged");
baton->sharpenM1 = sharp::AttrAsDouble(options, "sharpenM1");
baton->sharpenM2 = sharp::AttrAsDouble(options, "sharpenM2");
baton->sharpenX1 = sharp::AttrAsDouble(options, "sharpenX1");
baton->sharpenY2 = sharp::AttrAsDouble(options, "sharpenY2");
baton->sharpenY3 = sharp::AttrAsDouble(options, "sharpenY3");
baton->threshold = sharp::AttrAsInt32(options, "threshold");
baton->thresholdGrayscale = sharp::AttrAsBool(options, "thresholdGrayscale");
baton->trimThreshold = sharp::AttrAsDouble(options, "trimThreshold");
@@ -1399,8 +1437,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
baton->affineIdy = sharp::AttrAsDouble(options, "affineIdy");
baton->affineOdx = sharp::AttrAsDouble(options, "affineOdx");
baton->affineOdy = sharp::AttrAsDouble(options, "affineOdy");
baton->affineInterpolator = vips::VInterpolate::new_from_name(sharp::AttrAsStr(options, "affineInterpolator").data());
baton->affineInterpolator = sharp::AttrAsStr(options, "affineInterpolator");
baton->removeAlpha = sharp::AttrAsBool(options, "removeAlpha");
baton->ensureAlpha = sharp::AttrAsDouble(options, "ensureAlpha");
if (options.Has("boolean")) {
@@ -1451,6 +1488,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
std::string k = sharp::AttrAsStr(mdStrKeys, i);
baton->withMetadataStrs.insert(std::make_pair(k, sharp::AttrAsStr(mdStrs, k)));
}
baton->timeoutSeconds = sharp::AttrAsUint32(options, "timeoutSeconds");
// Format-specific
baton->jpegQuality = sharp::AttrAsUint32(options, "jpegQuality");
baton->jpegProgressive = sharp::AttrAsBool(options, "jpegProgressive");
@@ -1465,6 +1503,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
baton->pngAdaptiveFiltering = sharp::AttrAsBool(options, "pngAdaptiveFiltering");
baton->pngPalette = sharp::AttrAsBool(options, "pngPalette");
baton->pngQuality = sharp::AttrAsUint32(options, "pngQuality");
baton->pngEffort = sharp::AttrAsUint32(options, "pngEffort");
baton->pngBitdepth = sharp::AttrAsUint32(options, "pngBitdepth");
baton->pngDither = sharp::AttrAsDouble(options, "pngDither");
baton->jp2Quality = sharp::AttrAsUint32(options, "jp2Quality");
@@ -1477,7 +1516,10 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
baton->webpLossless = sharp::AttrAsBool(options, "webpLossless");
baton->webpNearLossless = sharp::AttrAsBool(options, "webpNearLossless");
baton->webpSmartSubsample = sharp::AttrAsBool(options, "webpSmartSubsample");
baton->webpReductionEffort = sharp::AttrAsUint32(options, "webpReductionEffort");
baton->webpEffort = sharp::AttrAsUint32(options, "webpEffort");
baton->gifBitdepth = sharp::AttrAsUint32(options, "gifBitdepth");
baton->gifEffort = sharp::AttrAsUint32(options, "gifEffort");
baton->gifDither = sharp::AttrAsDouble(options, "gifDither");
baton->tiffQuality = sharp::AttrAsUint32(options, "tiffQuality");
baton->tiffPyramid = sharp::AttrAsBool(options, "tiffPyramid");
baton->tiffBitdepth = sharp::AttrAsUint32(options, "tiffBitdepth");
@@ -1486,6 +1528,9 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
baton->tiffTileHeight = sharp::AttrAsUint32(options, "tiffTileHeight");
baton->tiffXres = sharp::AttrAsDouble(options, "tiffXres");
baton->tiffYres = sharp::AttrAsDouble(options, "tiffYres");
if (baton->tiffXres == 1.0 && baton->tiffYres == 1.0 && baton->withMetadataDensity > 0) {
baton->tiffXres = baton->tiffYres = baton->withMetadataDensity / 25.4;
}
// tiff compression options
baton->tiffCompression = static_cast<VipsForeignTiffCompression>(
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_COMPRESSION,
@@ -1493,30 +1538,28 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
baton->tiffPredictor = static_cast<VipsForeignTiffPredictor>(
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_PREDICTOR,
sharp::AttrAsStr(options, "tiffPredictor").data()));
baton->tiffResolutionUnit = static_cast<VipsForeignTiffResunit>(
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_RESUNIT,
sharp::AttrAsStr(options, "tiffResolutionUnit").data()));
baton->heifQuality = sharp::AttrAsUint32(options, "heifQuality");
baton->heifLossless = sharp::AttrAsBool(options, "heifLossless");
baton->heifCompression = static_cast<VipsForeignHeifCompression>(
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_HEIF_COMPRESSION,
sharp::AttrAsStr(options, "heifCompression").data()));
baton->heifSpeed = sharp::AttrAsUint32(options, "heifSpeed");
baton->heifEffort = sharp::AttrAsUint32(options, "heifEffort");
baton->heifChromaSubsampling = sharp::AttrAsStr(options, "heifChromaSubsampling");
// Raw output
baton->rawDepth = static_cast<VipsBandFormat>(
vips_enum_from_nick(nullptr, VIPS_TYPE_BAND_FORMAT,
sharp::AttrAsStr(options, "rawDepth").data()));
// Animated output
if (sharp::HasAttr(options, "pageHeight")) {
baton->pageHeight = sharp::AttrAsUint32(options, "pageHeight");
}
// Animated output properties
if (sharp::HasAttr(options, "loop")) {
baton->loop = sharp::AttrAsUint32(options, "loop");
}
if (sharp::HasAttr(options, "delay")) {
baton->delay = sharp::AttrAsInt32Vector(options, "delay");
}
// Tile output
baton->tileSize = sharp::AttrAsUint32(options, "tileSize");
baton->tileOverlap = sharp::AttrAsUint32(options, "tileOverlap");

View File

@@ -27,14 +27,6 @@
Napi::Value pipeline(const Napi::CallbackInfo& info);
enum class Canvas {
CROP,
EMBED,
MAX,
MIN,
IGNORE_ASPECT
};
struct Composite {
sharp::InputDescriptor *input;
VipsBlendMode mode;
@@ -75,7 +67,7 @@ struct PipelineBaton {
int width;
int height;
int channels;
Canvas canvas;
sharp::Canvas canvas;
int position;
std::vector<double> resizeBackground;
bool hasCropOffset;
@@ -98,8 +90,11 @@ struct PipelineBaton {
double lightness;
int medianSize;
double sharpenSigma;
double sharpenFlat;
double sharpenJagged;
double sharpenM1;
double sharpenM2;
double sharpenX1;
double sharpenY2;
double sharpenY3;
int threshold;
bool thresholdGrayscale;
double trimThreshold;
@@ -127,13 +122,14 @@ struct PipelineBaton {
int extendRight;
std::vector<double> extendBackground;
bool withoutEnlargement;
bool withoutReduction;
std::vector<double> affineMatrix;
std::vector<double> affineBackground;
double affineIdx;
double affineIdy;
double affineOdx;
double affineOdy;
vips::VInterpolate affineInterpolator;
std::string affineInterpolator;
int jpegQuality;
bool jpegProgressive;
std::string jpegChromaSubsampling;
@@ -147,6 +143,7 @@ struct PipelineBaton {
bool pngAdaptiveFiltering;
bool pngPalette;
int pngQuality;
int pngEffort;
int pngBitdepth;
double pngDither;
int jp2Quality;
@@ -159,7 +156,10 @@ struct PipelineBaton {
bool webpNearLossless;
bool webpLossless;
bool webpSmartSubsample;
int webpReductionEffort;
int webpEffort;
int gifBitdepth;
int gifEffort;
double gifDither;
int tiffQuality;
VipsForeignTiffCompression tiffCompression;
VipsForeignTiffPredictor tiffPredictor;
@@ -170,9 +170,10 @@ struct PipelineBaton {
int tiffTileWidth;
double tiffXres;
double tiffYres;
VipsForeignTiffResunit tiffResolutionUnit;
int heifQuality;
VipsForeignHeifCompression heifCompression;
int heifSpeed;
int heifEffort;
std::string heifChromaSubsampling;
bool heifLossless;
VipsBandFormat rawDepth;
@@ -182,6 +183,7 @@ struct PipelineBaton {
double withMetadataDensity;
std::string withMetadataIcc;
std::unordered_map<std::string, std::string> withMetadataStrs;
int timeoutSeconds;
std::unique_ptr<double[]> convKernel;
int convKernelWidth;
int convKernelHeight;
@@ -195,7 +197,6 @@ struct PipelineBaton {
double ensureAlpha;
VipsInterpretation colourspaceInput;
VipsInterpretation colourspace;
int pageHeight;
std::vector<int> delay;
int loop;
int tileSize;
@@ -216,7 +217,7 @@ struct PipelineBaton {
topOffsetPre(-1),
topOffsetPost(-1),
channels(0),
canvas(Canvas::CROP),
canvas(sharp::Canvas::CROP),
position(0),
resizeBackground{ 0.0, 0.0, 0.0, 255.0 },
hasCropOffset(false),
@@ -236,8 +237,11 @@ struct PipelineBaton {
lightness(0),
medianSize(0),
sharpenSigma(0.0),
sharpenFlat(1.0),
sharpenJagged(2.0),
sharpenM1(1.0),
sharpenM2(2.0),
sharpenX1(2.0),
sharpenY2(10.0),
sharpenY3(20.0),
threshold(0),
thresholdGrayscale(true),
trimThreshold(0.0),
@@ -263,13 +267,14 @@ struct PipelineBaton {
extendRight(0),
extendBackground{ 0.0, 0.0, 0.0, 255.0 },
withoutEnlargement(false),
withoutReduction(false),
affineMatrix{ 1.0, 0.0, 0.0, 1.0 },
affineBackground{ 0.0, 0.0, 0.0, 255.0 },
affineIdx(0),
affineIdy(0),
affineOdx(0),
affineOdy(0),
affineInterpolator(vips::VInterpolate::new_from_name("bicubic")),
affineInterpolator("bicubic"),
jpegQuality(80),
jpegProgressive(false),
jpegChromaSubsampling("4:2:0"),
@@ -283,6 +288,7 @@ struct PipelineBaton {
pngAdaptiveFiltering(false),
pngPalette(false),
pngQuality(100),
pngEffort(7),
pngBitdepth(8),
pngDither(1.0),
jp2Quality(80),
@@ -295,7 +301,7 @@ struct PipelineBaton {
webpNearLossless(false),
webpLossless(false),
webpSmartSubsample(false),
webpReductionEffort(4),
webpEffort(4),
tiffQuality(80),
tiffCompression(VIPS_FOREIGN_TIFF_COMPRESSION_JPEG),
tiffPredictor(VIPS_FOREIGN_TIFF_PREDICTOR_HORIZONTAL),
@@ -306,15 +312,17 @@ struct PipelineBaton {
tiffTileWidth(256),
tiffXres(1.0),
tiffYres(1.0),
tiffResolutionUnit(VIPS_FOREIGN_TIFF_RESUNIT_INCH),
heifQuality(50),
heifCompression(VIPS_FOREIGN_HEIF_COMPRESSION_AV1),
heifSpeed(5),
heifEffort(4),
heifChromaSubsampling("4:4:4"),
heifLossless(false),
rawDepth(VIPS_FORMAT_UCHAR),
withMetadata(false),
withMetadataOrientation(-1),
withMetadataDensity(0.0),
timeoutSeconds(0),
convKernelWidth(0),
convKernelHeight(0),
convKernelScale(0.0),
@@ -327,8 +335,6 @@ struct PipelineBaton {
ensureAlpha(-1.0),
colourspaceInput(VIPS_INTERPRETATION_LAST),
colourspace(VIPS_INTERPRETATION_LAST),
pageHeight(0),
delay{-1},
loop(-1),
tileSize(256),
tileOverlap(0),

View File

@@ -10,12 +10,12 @@
"devDependencies": {
"@squoosh/cli": "0.7.2",
"@squoosh/lib": "0.4.0",
"async": "3.2.1",
"async": "3.2.3",
"benchmark": "2.1.4",
"gm": "1.23.1",
"imagemagick": "0.1.3",
"jimp": "0.16.1",
"mapnik": "4.5.8",
"mapnik": "4.5.9",
"semver": "7.3.5"
},
"license": "Apache-2.0",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 175 KiB

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 222 B

After

Width:  |  Height:  |  Size: 320 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 197 B

After

Width:  |  Height:  |  Size: 292 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 197 B

After

Width:  |  Height:  |  Size: 292 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 194 B

After

Width:  |  Height:  |  Size: 286 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 B

After

Width:  |  Height:  |  Size: 285 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 270 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 265 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 424 KiB

After

Width:  |  Height:  |  Size: 424 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 943 KiB

After

Width:  |  Height:  |  Size: 943 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 833 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 640 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 373 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -93,6 +93,7 @@ module.exports = {
inputPngImageInAlpha: getPath('image-in-alpha.png'), // https://github.com/lovell/sharp/issues/1597
inputPngSolidAlpha: getPath('with-alpha.png'), // https://github.com/lovell/sharp/issues/1599
inputPngP3: getPath('p3.png'), // https://github.com/lovell/sharp/issues/2862
inputPngPalette: getPath('swiss.png'), // https://github.com/randy408/libspng/issues/188
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

BIN
test/fixtures/swiss.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -8,7 +8,7 @@ fi
curl -s -o ./test/leak/libvips.supp https://raw.githubusercontent.com/libvips/libvips/master/suppressions/valgrind.supp
for test in ./test/unit/*.js; do
G_SLICE=always-malloc G_DEBUG=gc-friendly valgrind \
G_SLICE=always-malloc G_DEBUG=gc-friendly VIPS_LEAK=1 valgrind \
--suppressions=test/leak/libvips.supp \
--suppressions=test/leak/sharp.supp \
--gen-suppressions=yes \

View File

@@ -225,14 +225,10 @@
fun:FcInitLoadConfigAndFonts
}
{
leak_fontconfig_doContent
leak_fontconfig_XML_ParseBuffer
Memcheck:Leak
match-leak-kinds: definite
fun:malloc
...
fun:doContent
fun:doProlog
fun:prologInitProcessor
fun:XML_ParseBuffer
obj:*/libfontconfig.so.*
}
@@ -646,6 +642,13 @@
...
fun:_ZN4node9inspector5Agent5StartEPNS_12NodePlatformEPKcRKNS_12DebugOptionsE
}
{
leak_nodejs_node9inspector5Agent5StartERKSsRKNS
Memcheck:Leak
match-leak-kinds: possible
...
fun:_ZN4node9inspector5Agent5StartERKSsRKNS_12DebugOptionsESt10shared_ptrINS_15ExclusiveAccessINS_8HostPortENS_9MutexBaseINS_16LibuvMutexTraitsEEEEEEb
}
{
leak_nodejs_node12NodePlatform_TracingController
Memcheck:Leak

View File

@@ -155,6 +155,15 @@ describe('Affine transform', () => {
fixtures.assertSimilar(fixtures.expected('affine-background-all-offsets-expected.jpg'), data, done);
});
});
it('Animated image rejects', () =>
assert.rejects(() => sharp(fixtures.inputGifAnimated, { animated: true })
.affine([1, 1, 1, 1])
.toBuffer(),
/Affine is not supported for multi-page images/
)
);
describe('Interpolations', () => {
const input = fixtures.inputJpg320x240;
const inputWidth = 320;

View File

@@ -17,10 +17,9 @@ describe('AVIF', () => {
.resize(32)
.jpeg()
.toBuffer();
const metadata = await sharp(data)
const { size, ...metadata } = await sharp(data)
.metadata();
const { compression, size, ...metadataWithoutSize } = metadata;
assert.deepStrictEqual(metadataWithoutSize, {
assert.deepStrictEqual(metadata, {
channels: 3,
chromaSubsampling: '4:2:0',
density: 72,
@@ -28,7 +27,7 @@ describe('AVIF', () => {
format: 'jpeg',
hasAlpha: false,
hasProfile: false,
height: 13,
height: 14,
isProgressive: false,
space: 'srgb',
width: 32
@@ -38,20 +37,19 @@ describe('AVIF', () => {
it('can convert JPEG to AVIF', async () => {
const data = await sharp(inputJpg)
.resize(32)
.avif()
.avif({ effort: 0 })
.toBuffer();
const metadata = await sharp(data)
const { size, ...metadata } = await sharp(data)
.metadata();
const { compression, size, ...metadataWithoutSize } = metadata;
assert.deepStrictEqual(metadataWithoutSize, {
assert.deepStrictEqual(metadata, {
channels: 3,
compression: 'av1',
depth: 'uchar',
format: 'heif',
hasAlpha: false,
hasProfile: false,
height: 26,
isProgressive: false,
pageHeight: 26,
pagePrimary: 0,
pages: 1,
space: 'srgb',
@@ -63,18 +61,17 @@ describe('AVIF', () => {
const data = await sharp(inputAvif)
.resize(32)
.toBuffer();
const metadata = await sharp(data)
const { size, ...metadata } = await sharp(data)
.metadata();
const { compression, size, ...metadataWithoutSize } = metadata;
assert.deepStrictEqual(metadataWithoutSize, {
assert.deepStrictEqual(metadata, {
channels: 3,
compression: 'av1',
depth: 'uchar',
format: 'heif',
hasAlpha: false,
hasProfile: false,
height: 12,
height: 14,
isProgressive: false,
pageHeight: 12,
pagePrimary: 0,
pages: 1,
space: 'srgb',
@@ -85,12 +82,11 @@ describe('AVIF', () => {
it('can convert animated GIF to non-animated AVIF', async () => {
const data = await sharp(inputGifAnimated, { animated: true })
.resize(10)
.avif({ speed: 8 })
.avif({ effort: 0 })
.toBuffer();
const metadata = await sharp(data)
const { size, ...metadata } = await sharp(data)
.metadata();
const { size, ...metadataWithoutSize } = metadata;
assert.deepStrictEqual(metadataWithoutSize, {
assert.deepStrictEqual(metadata, {
channels: 4,
compression: 'av1',
depth: 'uchar',
@@ -99,7 +95,6 @@ describe('AVIF', () => {
hasProfile: false,
height: 300,
isProgressive: false,
pageHeight: 300,
pagePrimary: 0,
pages: 1,
space: 'srgb',

View File

@@ -3,10 +3,11 @@
const detectLibc = require('detect-libc');
const sharp = require('../../');
const usingCache = detectLibc.family !== detectLibc.MUSL;
const libcFamily = detectLibc.familySync();
const usingCache = libcFamily !== detectLibc.MUSL;
const usingSimd = !process.env.G_DEBUG;
const concurrency =
detectLibc.family === detectLibc.MUSL || process.arch === 'arm'
libcFamily === detectLibc.MUSL || process.arch === 'arm'
? 1
: undefined;

View File

@@ -45,22 +45,20 @@ const blends = [
// Test
describe('composite', () => {
it('blend', () => Promise.all(
blends.map(blend => {
blends.forEach(blend => {
it(`blend ${blend}`, async () => {
const filename = `composite.blend.${blend}.png`;
const actual = fixtures.path(`output.${filename}`);
const expected = fixtures.expected(filename);
return sharp(redRect)
await sharp(redRect)
.composite([{
input: blueRect,
blend
}])
.toFile(actual)
.then(() => {
fixtures.assertMaxColourDistance(actual, expected);
});
})
));
.toFile(actual);
fixtures.assertMaxColourDistance(actual, expected);
});
});
it('premultiplied true', () => {
const filename = 'composite.premultiplied.png';
@@ -121,11 +119,11 @@ describe('composite', () => {
});
});
it('multiple', () => {
it('multiple', async () => {
const filename = 'composite-multiple.png';
const actual = fixtures.path(`output.${filename}`);
const expected = fixtures.expected(filename);
return sharp(redRect)
await sharp(redRect)
.composite([{
input: blueRect,
gravity: 'northeast'
@@ -133,10 +131,8 @@ describe('composite', () => {
input: greenRect,
gravity: 'southwest'
}])
.toFile(actual)
.then(() => {
fixtures.assertMaxColourDistance(actual, expected);
});
.toFile(actual);
fixtures.assertMaxColourDistance(actual, expected);
});
it('zero offset', done => {
@@ -247,7 +243,7 @@ describe('composite', () => {
sharp(fixtures.inputJpg)
.resize(300, 300)
.composite([{
input: Buffer.from('<svg><rect x="0" y="0" width="200" height="200" rx="50" ry="50"/></svg>'),
input: Buffer.from('<svg width="200" height="200"><rect x="0" y="0" width="200" height="200" rx="50" ry="50"/></svg>'),
density: 96,
blend: 'dest-in',
cutout: true

View File

@@ -6,16 +6,30 @@ const sharp = require('../../');
const fixtures = require('../fixtures');
describe('Extend', function () {
it('extend all sides equally via a single value', function (done) {
sharp(fixtures.inputJpg)
.resize(120)
.extend(10)
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(140, info.width);
assert.strictEqual(118, info.height);
fixtures.assertSimilar(fixtures.expected('extend-equal-single.jpg'), data, done);
});
describe('extend all sides equally via a single value', function () {
it('JPEG', function (done) {
sharp(fixtures.inputJpg)
.resize(120)
.extend(10)
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(140, info.width);
assert.strictEqual(118, info.height);
fixtures.assertSimilar(fixtures.expected('extend-equal-single.jpg'), data, done);
});
});
it('Animated WebP', function (done) {
sharp(fixtures.inputWebPAnimated, { pages: -1 })
.resize(120)
.extend(10)
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(140, info.width);
assert.strictEqual(140 * 9, info.height);
fixtures.assertSimilar(fixtures.expected('extend-equal-single.webp'), data, done);
});
});
});
it('extend all sides equally with RGB', function (done) {
@@ -126,7 +140,7 @@ describe('Extend', function () {
});
it('Premultiply background when compositing', async () => {
const background = '#bf1942cc';
const background = { r: 191, g: 25, b: 66, alpha: 0.8 };
const data = await sharp({
create: {
width: 1, height: 1, channels: 4, background: '#fff0'
@@ -144,10 +158,6 @@ describe('Extend', function () {
})
.raw()
.toBuffer();
const [r1, g1, b1, a1, r2, g2, b2, a2] = data;
assert.strictEqual(true, Math.abs(r2 - r1) < 2);
assert.strictEqual(true, Math.abs(g2 - g1) < 2);
assert.strictEqual(true, Math.abs(b2 - b1) < 2);
assert.strictEqual(true, Math.abs(a2 - a1) < 2);
assert.deepStrictEqual(Array.from(data), [191, 25, 66, 204, 191, 25, 66, 204]);
});
});

View File

@@ -39,10 +39,35 @@ describe('Partial image extraction', function () {
});
});
describe('Animated WebP', function () {
it('Before resize', function (done) {
sharp(fixtures.inputWebPAnimated, { pages: -1 })
.extract({ left: 0, top: 30, width: 80, height: 20 })
.resize(320, 80)
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(320, info.width);
assert.strictEqual(80 * 9, info.height);
fixtures.assertSimilar(fixtures.expected('gravity-center-height.webp'), data, done);
});
});
it('After resize', function (done) {
sharp(fixtures.inputWebPAnimated, { pages: -1 })
.resize(320, 320)
.extract({ left: 0, top: 120, width: 320, height: 80 })
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(320, info.width);
assert.strictEqual(80 * 9, info.height);
fixtures.assertSimilar(fixtures.expected('gravity-center-height.webp'), data, done);
});
});
});
it('TIFF', function (done) {
sharp(fixtures.inputTiff)
.extract({ left: 34, top: 63, width: 341, height: 529 })
.jpeg()
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(341, info.width);
@@ -143,6 +168,16 @@ describe('Partial image extraction', function () {
});
});
it('Rotate with EXIF mirroring then extract', function (done) {
sharp(fixtures.inputJpgWithLandscapeExif7)
.rotate()
.extract({ left: 0, top: 208, width: 60, height: 40 })
.toBuffer(function (err, data) {
if (err) throw err;
fixtures.assertSimilar(fixtures.expected('rotate-mirror-extract.jpg'), data, done);
});
});
describe('Invalid parameters', function () {
describe('using the legacy extract(top,left,width,height) syntax', function () {
it('String top', function () {

View File

@@ -42,7 +42,7 @@ describe('GIF input', () => {
.then(({ data, info }) => {
assert.strictEqual(true, data.length > 0);
assert.strictEqual(data.length, info.size);
assert.strictEqual(sharp.format.magick.input.buffer ? 'gif' : 'png', info.format);
assert.strictEqual('gif', info.format);
assert.strictEqual(80, info.width);
assert.strictEqual(80, info.height);
assert.strictEqual(4, info.channels);
@@ -55,40 +55,35 @@ describe('GIF input', () => {
.then(({ data, info }) => {
assert.strictEqual(true, data.length > 0);
assert.strictEqual(data.length, info.size);
assert.strictEqual(sharp.format.magick.input.buffer ? 'gif' : 'png', info.format);
assert.strictEqual('gif', info.format);
assert.strictEqual(80, info.width);
assert.strictEqual(2400, info.height);
assert.strictEqual(4, info.channels);
})
);
if (!sharp.format.magick.output.buffer) {
it('GIF buffer output should fail due to missing ImageMagick', () => {
assert.throws(
() => sharp().gif(),
/GIF output requires libvips with support for ImageMagick/
);
});
it('GIF with reduced colours, no dither, low effort reduces file size', async () => {
const original = await sharp(fixtures.inputJpg)
.resize(120, 80)
.gif()
.toBuffer();
it('GIF file output should fail due to missing ImageMagick', () => {
assert.rejects(
async () => await sharp().toFile('test.gif'),
/GIF output requires libvips with support for ImageMagick/
);
});
}
const reduced = await sharp(fixtures.inputJpg)
.resize(120, 80)
.gif({
colours: 128,
dither: 0,
effort: 1
})
.toBuffer();
it('invalid pageHeight throws', () => {
assert.throws(() => {
sharp().gif({ pageHeight: 0 });
});
assert.strictEqual(true, reduced.length < original.length);
});
it('invalid loop throws', () => {
assert.throws(() => {
sharp().gif({ loop: -1 });
});
assert.throws(() => {
sharp().gif({ loop: 65536 });
});
@@ -96,43 +91,61 @@ describe('GIF input', () => {
it('invalid delay throws', () => {
assert.throws(() => {
sharp().gif({ delay: [-1] });
sharp().gif({ delay: -1 });
});
assert.throws(() => {
sharp().gif({ delay: [65536] });
});
});
it('invalid colour throws', () => {
assert.throws(() => {
sharp().gif({ colours: 1 });
});
assert.throws(() => {
sharp().gif({ colours: 'fail' });
});
});
it('invalid effort throws', () => {
assert.throws(() => {
sharp().gif({ effort: 0 });
});
assert.throws(() => {
sharp().gif({ effort: 'fail' });
});
});
it('invalid dither throws', () => {
assert.throws(() => {
sharp().gif({ dither: 1.1 });
});
assert.throws(() => {
sharp().gif({ effort: 'fail' });
});
});
it('should work with streams when only animated is set', function (done) {
if (sharp.format.magick.output.buffer) {
fs.createReadStream(fixtures.inputGifAnimated)
.pipe(sharp({ animated: true }))
.gif()
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual('gif', info.format);
fixtures.assertSimilar(fixtures.inputGifAnimated, data, done);
});
} else {
done();
}
fs.createReadStream(fixtures.inputGifAnimated)
.pipe(sharp({ animated: true }))
.gif()
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual('gif', info.format);
fixtures.assertSimilar(fixtures.inputGifAnimated, data, done);
});
});
it('should work with streams when only pages is set', function (done) {
if (sharp.format.magick.output.buffer) {
fs.createReadStream(fixtures.inputGifAnimated)
.pipe(sharp({ pages: -1 }))
.gif()
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual('gif', info.format);
fixtures.assertSimilar(fixtures.inputGifAnimated, data, done);
});
} else {
done();
}
fs.createReadStream(fixtures.inputGifAnimated)
.pipe(sharp({ pages: -1 }))
.gif()
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual('gif', info.format);
fixtures.assertSimilar(fixtures.inputGifAnimated, data, done);
});
});
});

Some files were not shown because too many files have changed in this diff Show More