Compare commits

...

116 Commits

Author SHA1 Message Date
Lovell Fuller
4b6d45ab8e Prerelease v0.26.0-beta1 2020-08-23 15:56:25 +01:00
Lovell Fuller
7980341923 Upgrade libvips to v8.10.0 2020-08-23 15:36:03 +01:00
Lovell Fuller
3917efdebd Benchmarks: ensure PNG tests use consistent settings 2020-08-21 21:10:27 +01:00
Robert O'Rourke
eaecb7347b Add support to withMetadata for custom ICC profile #2271 2020-08-19 21:32:15 +01:00
Lovell Fuller
05ca7d3129 CI: further attempts to get Windows to play nicely 2020-08-18 20:53:18 +01:00
Lovell Fuller
4beae0de71 Add 'animated' constructor property as shortcut for 'pages'
Provides easier-to-understand API when handling animated images
2020-08-18 20:28:35 +01:00
Lovell Fuller
b711661784 CI: improve cross-platform (i.e. Windows) process spawning 2020-08-18 17:11:42 +01:00
Lovell Fuller
0c4a45b1f4 CI: workaround Appveyor ignoring v-prefixed tag names 2020-08-18 14:31:33 +01:00
Lovell Fuller
ec2beb0039 Prerelease v0.26.0-alpha2 2020-08-18 14:01:19 +01:00
Lovell Fuller
cafb3e8bac CI: ensure git tag is available inside prebuild containers 2020-08-18 14:00:14 +01:00
Lovell Fuller
e896d5e920 Prerelease v0.26.0-alpha1 2020-08-18 09:39:09 +01:00
Lovell Fuller
42d4228595 Replace prebuild-ci with simple wrapper around prebuild
Drop Node.js 13, generate prebuilds for NAPI v3 only
2020-08-18 09:37:57 +01:00
Lovell Fuller
341ea3e4ea Doc refresh and changelog entry for #2012 2020-08-17 16:20:10 +01:00
Tomáš Szabo
cb1baede87 Add support for animated WebP and GIF (via magick) (#2012) 2020-08-17 14:48:38 +01:00
diegodev3
45653ca2e7 Ensure prebuilt binaries for ARM default to v7 when using Electron 2020-08-14 17:27:14 +01:00
Lovell Fuller
77c861b74b Docs: ensure unreleased changes included in next release 2020-08-14 15:03:47 +01:00
Lovell Fuller
b586c2146f Ensure node-addon-api include path is array context
Need to revert to scalar context after node-addon-api v3.0.2
2020-08-14 12:39:58 +01:00
Lovell Fuller
4d42bed4f8 Upgrade to libvips v8.10.0-rc4 2020-08-14 10:33:37 +01:00
Lovell Fuller
ea54c119ab Bump deps 2020-08-14 08:20:35 +01:00
Lovell Fuller
d95b124771 CI: ensure ARM64 Node.js 10 is installed 2020-08-11 10:09:21 +01:00
Lovell Fuller
61ccf09e6f CI: ensure ARM64 Node.js 10 is installed from specific repo 2020-08-11 09:37:19 +01:00
Lovell Fuller
8da30f0b41 Map libvips file loader nicknames to sharp image types
Use stable API rather than internal class names, reduces complexity
2020-08-09 09:39:27 +01:00
Lovell Fuller
90a57e7664 Upgrade to libvips 8.10.0-rc1 2020-07-17 14:46:53 +01:00
Lovell Fuller
c42de19d2a Add most dominant colour to image stats #640 2020-07-15 21:57:37 +01:00
Lovell Fuller
dcc42f8514 Ensure electron-builder and electron-rebuild use N-API v3 prebuilds 2020-07-15 09:05:53 +01:00
Lovell Fuller
3150fad909 Upgrade to libvips 8.10.0-beta2 2020-07-14 19:45:25 +01:00
Lovell Fuller
ba17db3ab3 Switch to Brotli-compressed binaries, requires Node.js 10.16.0+ 2020-06-29 20:53:58 +01:00
Kleis Auke Wolthuizen
7c1c48327e Add support for statically-linked dependencies (lovell/sharp-libvips#39) (#2223) 2020-06-28 21:17:15 +01:00
Lovell Fuller
85459e0ec6 macOS: remove linker flag ignored by clang 2020-06-27 11:55:10 +01:00
Lovell Fuller
13bdb68366 CI: pin Windows x86 Node.js 14 to 14.2.0
This is the least unstable version on the 14 release line.
Windows x86 users should probably delay upgrading to Node.js 14.
2020-06-27 11:42:00 +01:00
Lovell Fuller
c91373fba3 Docs: add section about worker threads 2020-06-18 13:42:04 +01:00
Lovell Fuller
e8149f5295 Docs: fix markdown in changelog 2020-06-16 19:01:36 +01:00
Lovell Fuller
ab015ef90f Avoid copy in metadata levels for loop 2020-06-16 18:59:20 +01:00
Lovell Fuller
fdfc89e577 Docs: add content security policy 2020-06-16 18:41:18 +01:00
Lovell Fuller
e647e1f7bd Add changelog entry and credit for #2258 #2259 2020-06-16 18:39:58 +01:00
Jerome Vouillon
a05d735e31 Allow multi-page input via ImageMagic (#2259) 2020-06-15 13:01:53 +01:00
Jerome Vouillon
4470048ba4 Add support for libvips ImageMagick v7 loaders (#2258) 2020-06-15 12:59:20 +01:00
Lovell Fuller
0f58f956a7 Docs: fix external CSS 2020-06-12 17:07:05 +01:00
Lovell Fuller
20284757a6 Docs: fix binary mirror example 2020-06-12 17:06:46 +01:00
Lovell Fuller
df6efa0285 Release v0.25.4 2020-06-12 14:17:23 +01:00
Lovell Fuller
3c10e118e3 Docs: ensure presence of constructor description 2020-06-12 13:52:19 +01:00
Lovell Fuller
19980190f7 Emit 'warning' event for non-critical problems #2032 2020-06-12 13:46:12 +01:00
Lovell Fuller
8f5495a446 Add experimental sharpness calc to stats #2251 2020-06-12 11:25:57 +01:00
Mikhail Bodrov
9431029917 Simplify type checking of plainObject, buffer (#2252) 2020-06-10 20:54:01 +01:00
Lovell Fuller
17ea70a102 Add named 'alpha' channel to extractChannel op #2138 2020-06-07 10:43:43 +01:00
Lovell Fuller
7f142bddb3 Add level constructor opt for multi-level input #2222 2020-06-06 16:10:56 +01:00
Lovell Fuller
98e0516ac1 Tests: latest libvips sets JPEG density 2020-06-06 16:09:52 +01:00
Mikhail Bodrov
7717516d1d Simplify is helpers (#2244) 2020-06-05 09:48:26 +01:00
Lovell Fuller
760550ca0d Expose levels metadata for multi-level images #2222 2020-06-04 20:29:02 +01:00
Lovell Fuller
f8144dd89c Bump dependencies 2020-06-04 20:17:15 +01:00
Lovell Fuller
ac4070cb49 Tests: skip empty Buffer test when *magick supported 2020-06-04 20:07:30 +01:00
Lovell Fuller
25b964e3df Docs: changelog and credit update for #2226 2020-05-28 22:42:37 +01:00
Lovell Fuller
17ec6c72df Docs: inline small external docute CSS/JS 2020-05-28 22:41:24 +01:00
Roman Malieiev
a7b1185602 Enable PNG palette when at least one of quality, colours, colors or dither is set (#2226) 2020-05-28 22:21:33 +01:00
Lovell Fuller
c76ae06fd1 Attempt to reduce file size of binaries at link time 2020-05-22 19:47:19 +01:00
Lovell Fuller
b3dd54d550 Changelog and doc update for #2217 2020-05-22 16:48:27 +01:00
malice00
d248eadb06 Allow libvips binary location override with version appended (#2217) 2020-05-22 16:07:32 +01:00
Lovell Fuller
507eef3053 Docs: reorder/highlight optional output properties 2020-05-19 18:55:33 +01:00
Lovell Fuller
fc2f672337 Improve error messaging when platform mismatch detected 2020-05-19 18:53:39 +01:00
Lovell Fuller
6f49be8f26 Docs: small perf boost by prefetching image DNS 2020-05-19 18:50:18 +01:00
Lovell Fuller
dad7f1e1f6 Release v0.25.3 2020-05-17 14:21:39 +01:00
Lovell Fuller
c3a9384dcf Upgrade deps that have dropped Node.js 8 support 2020-05-17 13:29:55 +01:00
Lovell Fuller
0ee08bfe46 Docs: lint JSDoc annotations when building docs 2020-05-15 14:07:27 +01:00
Lovell Fuller
0872f495f9 Tests: temporarily skip known failure on FreeBSD 2020-05-15 12:17:45 +01:00
Lovell Fuller
33ac8b93a9 CI: allow FreeBSD to update to latest packages 2020-05-13 16:12:17 +01:00
Lovell Fuller
bbff1c222d Docs: clarify description of withMetadata
Default and non-default behaviour was mixed in the same paragraph,
leading to confusion about when an ICC profile might be expected.
2020-05-13 16:01:03 +01:00
Lovell Fuller
b534f99870 Docs: correct typo in composite 2020-05-13 15:56:44 +01:00
Lovell Fuller
86acf2460e Allow parsing of SVG attribute values >10MB #2195 2020-05-06 20:56:08 +01:00
NickNaso
40c00035ee Docs: add N-API badge 2020-05-01 11:53:10 +01:00
Lovell Fuller
4673abae7d Docs: serve logo via CDN 2020-05-01 11:41:38 +01:00
Lovell Fuller
ef8a705957 Docs: move npm install command to examples 2020-05-01 11:38:09 +01:00
Lovell Fuller
48255fa009 Docs: serve logo via CDN 2020-05-01 11:27:00 +01:00
Lovell Fuller
ed6269bc9c Bump dependencies 2020-05-01 11:26:05 +01:00
Lovell Fuller
16b8d9afe5 Docs: changelog entry and credit for #2188 2020-05-01 11:25:43 +01:00
Dumitru
f29fcb1f73 Ensure npm platform flag is respected when copying DLLs 2020-04-29 18:24:27 +01:00
Lovell Fuller
b36ad25030 CI: Upgrade FreeBSD to version 13
It currently provides only vips v8.8.3 so will (still) fail.
2020-04-23 21:39:45 +01:00
Lovell Fuller
edf3fe24c9 CI: add Node.js 14 2020-04-23 21:30:04 +01:00
Lovell Fuller
5a1bdf0eb1 Bump deps: semver, documentation, nyc 2020-04-23 20:51:12 +01:00
Lovell Fuller
d8e24d7e76 Docs: slightly reduce whitespace before headings, lists 2020-04-23 20:47:10 +01:00
Floris de Bijl
786c5330e9 docs: change 'colourspace' to 'toColourspace' (#2174) 2020-04-21 09:44:22 +01:00
Lovell Fuller
70e730bb67 Docs: add basic search feature for install and API 2020-04-14 21:26:41 +01:00
Lovell Fuller
1d6ef630a5 Docs: extract might also be referred to as 'crop' 2020-04-14 21:24:26 +01:00
Vincent Voyer
1eedb22ef5 docs(clone): add promise example 2020-04-08 09:45:04 +01:00
Lovell Fuller
6ed1f49ad3 Ensure libvips is initialised only once #2143 2020-03-29 19:44:21 +01:00
Lovell Fuller
ecd01afad3 Docs: clarify JPEG quality/chromaSubsampling interdependence 2020-03-26 20:22:47 +00:00
Lovell Fuller
8bd8709f2b Release v0.25.2 2020-03-20 19:20:34 +00:00
Lovell Fuller
e82a585cec Ensure AsyncWorker options are persisted #2130 2020-03-19 21:21:21 +00:00
Lovell Fuller
c1d4a68558 Add changelog entry, docs and credit for #2098 2020-03-18 13:54:41 +00:00
Edward Silverton
eff36dc09f Add IIIF layout support to tile-based output (#2098) 2020-03-18 13:27:54 +00:00
Lovell Fuller
031a58f817 Docs: AWS Lambda cross-platform no longer requires target flag 2020-03-17 18:33:49 +00:00
Lovell Fuller
cf39fc4fb1 Simplify tiled layout JPEG file extension logic 2020-03-14 22:08:46 +00:00
Lovell Fuller
c9bff94e17 Docs: expand descriptions of resize cover/contain 2020-03-14 21:34:48 +00:00
Lovell Fuller
e78e919925 Clarify diff between install vs bug issue templates 2020-03-10 21:36:48 +00:00
Lovell Fuller
76bb25262e Docs: ARM64/RHEL7 prebuilt binaries changelog entry 2020-03-10 21:22:30 +00:00
Lovell Fuller
d8426b1ed5 CI: Switch Linux x64 to CentOS 7 for glibc 2.17, gcc 4.8.5
Expand Linux ARM64v8 to all supported versions of Node.js
2020-03-10 19:39:26 +00:00
Lovell Fuller
4894c10dd9 Ensure consistent, correct detection of input opts #2118 2020-03-08 14:32:17 +00:00
Lovell Fuller
24285bb0e0 Release v0.25.1 2020-03-07 21:21:44 +00:00
Lovell Fuller
bf75501262 Ensure prebuilt binary based on N-API version 2020-03-07 20:59:25 +00:00
Lovell Fuller
9e214a17f0 Release v0.25.1 2020-03-07 19:12:32 +00:00
Lovell Fuller
0aae1ecd9b Ensure prebuilt binaries use N-API v4 #2117 2020-03-07 18:52:44 +00:00
Lovell Fuller
062f990315 Release v0.25.0 2020-03-07 12:44:42 +00:00
Lovell Fuller
22685e44c0 Upgrade to libvips v8.9.1 2020-03-07 10:15:51 +00:00
Lovell Fuller
8d66433e7c Docs: 32-bit Windows is supported (again) 2020-03-06 21:33:15 +00:00
Lovell Fuller
82863f48f6 Update issue templates to ask common follow-up questions 2020-03-06 21:12:59 +00:00
Lovell Fuller
51b14329d6 Simplify and future-proof tile option parsing 2020-03-06 20:06:41 +00:00
Lovell Fuller
14dc7681ed Upgrade to libvips v8.9.0-alpha2 prebuild
Improve tests for 32-bit ARM
2020-03-04 23:17:23 +00:00
Lovell Fuller
258c9e86eb Improve docs relating to single-channel raw pixel output 2020-03-01 14:22:49 +00:00
Joe Flateau
03dad5f2b2 Docs: clarify stats.isOpaque (#2094) 2020-02-24 20:35:36 +00:00
Lovell Fuller
86264e7733 Docs: update Lambda section to use Node.js 12 2020-02-24 20:31:17 +00:00
Lovell Fuller
95eae905f0 Ensure limitInputPixels, sequentialRead for composite op #2099 2020-02-24 16:02:36 +00:00
Lovell Fuller
a90dbcc808 Docs: improve a11y and metadata 2020-02-24 09:24:27 +00:00
Lovell Fuller
e9b21f2211 Ensure correct ordering of rotate-then-trim ops #2087 2020-02-23 16:59:22 +00:00
Lovell Fuller
409e5174bb Add support for 32-bit Windows (win32-ia32) 2020-02-19 23:00:23 +00:00
Lovell Fuller
8b3c0daab2 Upgrade to libvips v8.9.1-alpha1 prebuild 2020-02-19 11:41:18 +00:00
Lovell Fuller
c17807c995 Remove previously deprecated limitInputPixels, sequentialRead 2020-02-15 20:01:46 +00:00
Lovell Fuller
4abb4edf64 Migrate internals to N-API #1282 2020-02-15 19:38:15 +00:00
88 changed files with 3527 additions and 2145 deletions

View File

@@ -1,12 +1,13 @@
freebsd_instance: freebsd_instance:
image_family: freebsd-12-0 image_family: freebsd-13-0-snap
task: task:
env:
IGNORE_OSVERSION: yes
prerequisites_script: prerequisites_script:
- sed -i '' 's/quarterly/latest/g' /etc/pkg/FreeBSD.conf
- pkg update -f - pkg update -f
- pkg upgrade -y - pkg upgrade -y
- pkg install -y pkgconf vips libnghttp2 node npm - pkg install -y pkgconf vips node npm
install_script: install_script:
- npm install --unsafe-perm - npm install --unsafe-perm
test_script: test_script:

View File

@@ -44,8 +44,7 @@ Any change that modifies the existing public API should be added to the relevant
| Release | WIP branch | | Release | WIP branch |
| ------: | :--------- | | ------: | :--------- |
| v0.24.0 | wit | | v0.26.0 | zoom |
| v0.25.0 | yield |
Please squash your changes into a single commit using a command like `git rebase -i upstream/<wip-branch>`. Please squash your changes into a single commit using a command like `git rebase -i upstream/<wip-branch>`.

View File

@@ -1,9 +1,7 @@
--- ---
name: Feature request name: Feature request
about: Suggest an idea about: Suggest an idea
title: ''
labels: enhancement labels: enhancement
assignees: ''
--- ---

View File

@@ -1,9 +1,7 @@
--- ---
name: Installation name: Installation
about: Something went wrong **installing** sharp about: Something went wrong during either 'npm install sharp' or 'require("sharp")'
title: ''
labels: installation labels: installation
assignees: ''
--- ---
@@ -11,10 +9,12 @@ Did you see the [documentation relating to installation](https://sharp.pixelplum
Have you ensured the platform and version of Node.js used for `npm install` is the same as the platform and version of Node.js used at runtime? Have you ensured the platform and version of Node.js used for `npm install` is the same as the platform and version of Node.js used at runtime?
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`?
If you are installing as a `root` or `sudo` user, have you tried with the `npm install --unsafe-perm` flag? If you are installing as a `root` or `sudo` user, have you tried with the `npm install --unsafe-perm` flag?
If you are using the `ignore-scripts` feature of `npm`, have you tried with the `npm install --ignore-scripts=false` flag? 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`? Have you checked this output for useful error messages?
What is the output of running `npx envinfo --binaries --languages --system --utilities`? What is the output of running `npx envinfo --binaries --system`?

View File

@@ -1,20 +1,20 @@
--- ---
name: Possible bug name: Possible bug
about: Something unexpected occurred **using** sharp about: Installation of sharp was successful but then something unexpected occurred using one of its features
title: ''
labels: triage labels: triage
assignees: ''
--- ---
<!-- If this issue relates to installation, please use https://github.com/lovell/sharp/issues/new?labels=installation&template=installation.md instead. --> <!-- If this issue relates to installation, please use https://github.com/lovell/sharp/issues/new?labels=installation&template=installation.md instead. -->
What is the output of running `npx envinfo --binaries --languages --system --utilities`? 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`?
What are the steps to reproduce? What are the steps to reproduce?
What is the expected behaviour? What is the expected behaviour?
Are you able to provide a standalone code sample, without other dependencies, that demonstrates this problem? Are you able to provide a minimal, standalone code sample, without other dependencies, that demonstrates this problem?
Are you able to provide a sample image that helps explain the problem? Are you able to provide a sample image that helps explain the problem?
What is the output of running `npx envinfo --binaries --system`?

View File

@@ -1,9 +1,7 @@
--- ---
name: Question name: Question
about: For help understanding an existing feature about: For help understanding an existing feature
title: ''
labels: question labels: question
assignees: ''
--- ---
@@ -13,6 +11,6 @@ What are you trying to achieve?
Have you searched for similar questions? Have you searched for similar questions?
Are you able to provide a standalone code sample that demonstrates this question? Are you able to provide a minimal, standalone code sample that demonstrates this question?
Are you able to provide a sample image that helps explain the question? Are you able to provide a sample image that helps explain the question?

View File

@@ -1,75 +1,139 @@
matrix: jobs:
include: include:
- name: "Linux (glibc 2.17+) - Node.js 10" - name: "Linux x64 (CentOS 7, glibc 2.17) - Node.js 10"
os: linux
dist: xenial
language: node_js
node_js: "10"
- name: "Linux (glibc 2.17+) - Node.js 12"
os: linux
dist: xenial
language: node_js
node_js: "12"
- name: "Linux (glibc 2.17+) - Node.js 13"
os: linux
dist: xenial
language: node_js
node_js: "13"
after_success:
- npm install coveralls
- cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js
- name: "Linux (musl 1.1.20+) - Node.js 10"
os: linux os: linux
dist: bionic dist: bionic
language: minimal language: shell
before_install: before_install:
- sudo docker run -dit --name sharp --env CI --env PREBUILD_TOKEN --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp node:10.17.0-alpine3.9 # https://github.com/nodejs/docker-node/issues/1158 - sudo docker run -dit --name sharp --env CI --env TRAVIS_TAG --env prebuild_upload --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp centos:7
- sudo docker exec sharp bash -c "curl -sL https://rpm.nodesource.com/setup_10.x | bash -"
- sudo docker exec sharp yum install -y gcc-c++ make git nodejs
install: sudo docker exec sharp bash -c "npm install --unsafe-perm"
script: sudo docker exec sharp bash -c "npm test"
- name: "Linux x64 (CentOS 7, glibc 2.17) - Node.js 12"
os: linux
dist: bionic
language: shell
before_install:
- sudo docker run -dit --name sharp --env CI --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp centos:7
- sudo docker exec sharp bash -c "curl -sL https://rpm.nodesource.com/setup_12.x | bash -"
- sudo docker exec sharp yum install -y gcc-c++ make git nodejs
install: sudo docker exec sharp bash -c "npm install --unsafe-perm"
script: sudo docker exec sharp bash -c "npm test"
- name: "Linux x64 (CentOS 7, glibc 2.17) - Node.js 14"
os: linux
dist: bionic
language: shell
before_install:
- sudo docker run -dit --name sharp --env CI --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp centos:7
- sudo docker exec sharp bash -c "curl -sL https://rpm.nodesource.com/setup_14.x | bash -"
- sudo docker exec sharp yum install -y gcc-c++ make git nodejs
install: sudo docker exec sharp bash -c "npm install --unsafe-perm"
script: sudo docker exec sharp bash -c "npm test"
- name: "Linux x64 (Alpine 3.9, musl 1.1.20) - Node.js 10"
os: linux
dist: bionic
language: shell
before_install:
- sudo docker run -dit --name sharp --env CI --env TRAVIS_TAG --env prebuild_upload --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp node:10.17.0-alpine3.9 # https://github.com/nodejs/docker-node/issues/1158
- sudo docker exec sharp apk add build-base git python2 --update-cache - sudo docker exec sharp apk add build-base git python2 --update-cache
install: sudo docker exec sharp sh -c "npm install --unsafe-perm" install: sudo docker exec sharp sh -c "npm install --unsafe-perm"
script: sudo docker exec sharp sh -c "npm test" script: sudo docker exec sharp sh -c "npm test"
- name: "Linux (musl 1.1.20+) - Node.js 12"
- name: "Linux x64 (Alpine 3.11, musl 1.1.20) - Node.js 12"
os: linux os: linux
dist: bionic dist: bionic
language: minimal language: shell
before_install: before_install:
- sudo docker run -dit --name sharp --env CI --env PREBUILD_TOKEN --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp node:12.0-alpine - sudo docker run -dit --name sharp --env CI --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp node:12.0-alpine
- sudo docker exec sharp apk add build-base git python2 --update-cache - sudo docker exec sharp apk add build-base git python2 --update-cache
install: sudo docker exec sharp sh -c "npm install --unsafe-perm" install: sudo docker exec sharp sh -c "npm install --unsafe-perm"
script: sudo docker exec sharp sh -c "npm test" script: sudo docker exec sharp sh -c "npm test"
- name: "Linux (musl 1.1.20+) - Node.js 13"
- name: "Linux x64 (Alpine 3.11, musl 1.1.20) - Node.js 14"
os: linux os: linux
dist: bionic dist: bionic
language: minimal language: shell
before_install: before_install:
- sudo docker run -dit --name sharp --env CI --env PREBUILD_TOKEN --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp node:13.0-alpine - sudo docker run -dit --name sharp --env CI --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp node:14.0-alpine
- sudo docker exec sharp apk add build-base git python2 --update-cache - sudo docker exec sharp apk add build-base git python2 --update-cache
install: sudo docker exec sharp sh -c "npm install --unsafe-perm" install: sudo docker exec sharp sh -c "npm install --unsafe-perm"
script: sudo docker exec sharp sh -c "npm test" script: sudo docker exec sharp sh -c "npm test"
- name: "Linux ARM64v8 (glibc 2.29+) - Node.js 10"
- name: "Linux ARM64v8 (Debian 11, glibc 2.29) - Node.js 10"
arch: arm64 arch: arm64
os: linux os: linux
dist: bionic dist: bionic
language: minimal language: shell
before_install: before_install:
- sudo docker run -dit --name sharp --env CI --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp arm64v8/debian:bullseye - sudo docker run -dit --name sharp --env CI --env TRAVIS_TAG --env prebuild_upload --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp arm64v8/debian:bullseye
- sudo docker exec sharp apt-get update - sudo docker exec sharp sh -c "apt-get update && apt-get install -y build-essential git python2 curl"
- sudo docker exec sharp apt-get install -y build-essential git python2 nodejs npm - 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_10.x buster main' >/etc/apt/sources.list.d/nodesource.list"
- sudo docker exec sharp sh -c "apt-get update && apt-get install -y nodejs=10.*"
install: sudo docker exec sharp sh -c "npm install --unsafe-perm" install: sudo docker exec sharp sh -c "npm install --unsafe-perm"
script: sudo docker exec sharp sh -c "npm test" script: sudo docker exec sharp sh -c "npm test"
- name: "macOS (10.13+) - Node.js 10"
- name: "Linux ARM64v8 (Debian 11, glibc 2.29) - Node.js 12"
arch: arm64
os: linux
dist: bionic
language: shell
before_install:
- sudo docker run -dit --name sharp --env CI --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp arm64v8/debian:bullseye
- sudo docker exec sharp sh -c "apt-get update && apt-get install -y build-essential git python2 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_12.x buster main' >/etc/apt/sources.list.d/nodesource.list"
- sudo docker exec sharp sh -c "apt-get update && apt-get install -y nodejs"
install: sudo docker exec sharp sh -c "npm install --unsafe-perm"
script: sudo docker exec sharp sh -c "npm test"
- name: "Linux ARM64v8 (Debian 11, glibc 2.29) - Node.js 14"
arch: arm64
os: linux
dist: bionic
language: shell
before_install:
- sudo docker run -dit --name sharp --env CI --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp arm64v8/debian:bullseye
- sudo docker exec sharp sh -c "apt-get update && apt-get install -y build-essential git python2 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_14.x buster main' >/etc/apt/sources.list.d/nodesource.list"
- sudo docker exec sharp sh -c "apt-get update && apt-get install -y nodejs"
install: sudo docker exec sharp sh -c "npm install --unsafe-perm"
script: sudo docker exec sharp sh -c "npm test"
- name: "macOS (10.13) - Node.js 10"
os: osx os: osx
osx_image: xcode10.1 osx_image: xcode10.1
language: node_js language: node_js
node_js: "10" node_js: "10"
- name: "macOS (10.13+) - Node.js 12"
- name: "macOS (10.13) - Node.js 12"
os: osx os: osx
osx_image: xcode10.1 osx_image: xcode10.1
language: node_js language: node_js
node_js: "12" node_js: "12"
- name: "macOS (10.13+) - Node.js 13" before_install: unset prebuild_upload
- name: "macOS (10.13) - Node.js 14"
os: osx os: osx
osx_image: xcode10.1 osx_image: xcode10.1
language: node_js language: node_js
node_js: "14"
before_install: unset prebuild_upload
- name: "Unit test coverage report"
os: linux
dist: bionic
language: node_js
node_js: "13" node_js: "13"
before_install: unset prebuild_upload
after_success:
- npm install coveralls
- cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js
cache: cache:
npm: false npm: false

View File

@@ -1,10 +1,6 @@
# sharp # sharp
<img src="https://raw.githubusercontent.com/lovell/sharp/master/docs/image/sharp-logo.svg?sanitize=true" width="160" height="160" alt="sharp logo" align="right"> <img src="https://cdn.jsdelivr.net/gh/lovell/sharp@master/docs/image/sharp-logo.svg" width="160" height="160" alt="sharp logo" align="right">
```sh
npm install sharp
```
The typical use case for this high speed Node.js module The typical use case for this high speed Node.js module
is to convert large images in common formats to is to convert large images in common formats to
@@ -20,12 +16,15 @@ Lanczos resampling ensures quality is not sacrificed for speed.
As well as image resizing, operations such as As well as image resizing, operations such as
rotation, extraction, compositing and gamma correction are available. rotation, extraction, compositing and gamma correction are available.
Most modern 64-bit macOS, Windows and Linux systems running Most modern macOS, Windows and Linux systems running Node.js v10.16.0+
Node versions 10, 12 and 13
do not require any additional install or runtime dependencies. do not require any additional install or runtime dependencies.
## Examples ## Examples
```sh
npm install sharp
```
```javascript ```javascript
const sharp = require('sharp'); const sharp = require('sharp');
``` ```
@@ -86,6 +85,7 @@ readableStream
``` ```
[![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=master)](https://coveralls.io/r/lovell/sharp?branch=master)
[![N-API v3](https://img.shields.io/badge/N--API-v3-green.svg)](https://nodejs.org/dist/latest/docs/api/n-api.html#n_api_n_api_version_matrix)
### Documentation ### Documentation

View File

@@ -1,15 +1,26 @@
os: Visual Studio 2017 os: Visual Studio 2017
version: "{build}" version: "{build}"
build: off build: off
platform: x64
environment: environment:
matrix: matrix:
- nodejs_version: "10" - nodejs_version: "10"
platform: x86
- nodejs_version: "10"
platform: x64
- nodejs_version: "12" - nodejs_version: "12"
- nodejs_version: "13" platform: x86
prebuild_upload: ""
- nodejs_version: "12"
platform: x64
prebuild_upload: ""
- nodejs_version: "14.2.0"
platform: x86
prebuild_upload: ""
- nodejs_version: "14"
platform: x64
prebuild_upload: ""
install: install:
- ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version) x64 - ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version) $env:platform
- npm install -g npm@6
- npm install - npm install
test_script: test_script:
- npm test - npm test

View File

@@ -1,4 +1,8 @@
{ {
'variables': {
'vips_version': '<!(node -p "require(\'./lib/libvips\').minimumLibvipsVersion")',
'sharp_vendor_dir': '<(module_root_dir)/vendor/<(vips_version)'
},
'targets': [{ 'targets': [{
'target_name': 'libvips-cpp', 'target_name': 'libvips-cpp',
'conditions': [ 'conditions': [
@@ -16,20 +20,38 @@
'src/libvips/cplusplus/VImage.cpp' 'src/libvips/cplusplus/VImage.cpp'
], ],
'include_dirs': [ 'include_dirs': [
'vendor/include', '<(sharp_vendor_dir)/include',
'vendor/include/glib-2.0', '<(sharp_vendor_dir)/include/glib-2.0',
'vendor/lib/glib-2.0/include' '<(sharp_vendor_dir)/lib/glib-2.0/include'
],
'libraries': [
'../vendor/lib/libvips.lib',
'../vendor/lib/libglib-2.0.lib',
'../vendor/lib/libgobject-2.0.lib'
], ],
'link_settings': {
'library_dirs': ['<(sharp_vendor_dir)/lib'],
'libraries': [
'libvips.lib',
'libglib-2.0.lib',
'libgobject-2.0.lib'
],
},
'configurations': { 'configurations': {
'Release': { 'Release': {
'msvs_settings': { 'msvs_settings': {
'VCCLCompilerTool': { 'VCCLCompilerTool': {
'ExceptionHandling': 1 'ExceptionHandling': 1,
'WholeProgramOptimization': 'true'
},
'VCLibrarianTool': {
'AdditionalOptions': [
'/LTCG:INCREMENTAL'
]
},
'VCLinkerTool': {
'ImageHasSafeExceptionHandlers': 'false',
'OptimizeReferences': 2,
'EnableCOMDATFolding': 2,
'LinkIncremental': 1,
'AdditionalOptions': [
'/LTCG:INCREMENTAL'
]
} }
}, },
'msvs_disabled_warnings': [ 'msvs_disabled_warnings': [
@@ -44,15 +66,19 @@
] ]
}, { }, {
'target_name': 'sharp', 'target_name': 'sharp',
'defines': [
'NAPI_VERSION=3'
],
'dependencies': [ 'dependencies': [
'<!(node -p "require(\'node-addon-api\').gyp")',
'libvips-cpp' 'libvips-cpp'
], ],
'variables': { 'variables': {
'runtime_link%': 'shared', 'runtime_link%': 'shared',
'conditions': [ 'conditions': [
['OS != "win"', { ['OS != "win"', {
'pkg_config_path': '<!(node -e "console.log(require(\'./lib/libvips\').pkgConfigPath())")', 'pkg_config_path': '<!(node -p "require(\'./lib/libvips\').pkgConfigPath()")',
'use_global_libvips': '<!(node -e "console.log(Boolean(require(\'./lib/libvips\').useGlobalLibvips()).toString())")' 'use_global_libvips': '<!(node -p "Boolean(require(\'./lib/libvips\').useGlobalLibvips()).toString()")'
}, { }, {
'pkg_config_path': '', 'pkg_config_path': '',
'use_global_libvips': '' 'use_global_libvips': ''
@@ -65,11 +91,11 @@
'src/stats.cc', 'src/stats.cc',
'src/operations.cc', 'src/operations.cc',
'src/pipeline.cc', 'src/pipeline.cc',
'src/sharp.cc', 'src/utilities.cc',
'src/utilities.cc' 'src/sharp.cc'
], ],
'include_dirs': [ 'include_dirs': [
'<!(node -e "require(\'nan\')")' '<!@(node -p "require(\'node-addon-api\').include")',
], ],
'conditions': [ 'conditions': [
['use_global_libvips == "true"', { ['use_global_libvips == "true"', {
@@ -91,9 +117,9 @@
}, { }, {
# Use pre-built libvips stored locally within node_modules # Use pre-built libvips stored locally within node_modules
'include_dirs': [ 'include_dirs': [
'vendor/include', '<(sharp_vendor_dir)/include',
'vendor/include/glib-2.0', '<(sharp_vendor_dir)/include/glib-2.0',
'vendor/lib/glib-2.0/include' '<(sharp_vendor_dir)/lib/glib-2.0/include'
], ],
'conditions': [ 'conditions': [
['OS == "win"', { ['OS == "win"', {
@@ -101,64 +127,45 @@
'_ALLOW_KEYWORD_MACROS', '_ALLOW_KEYWORD_MACROS',
'_FILE_OFFSET_BITS=64' '_FILE_OFFSET_BITS=64'
], ],
'libraries': [ 'link_settings': {
'../vendor/lib/libvips.lib', 'library_dirs': ['<(sharp_vendor_dir)/lib'],
'../vendor/lib/libglib-2.0.lib', 'libraries': [
'../vendor/lib/libgobject-2.0.lib' 'libvips.lib',
] 'libglib-2.0.lib',
'libgobject-2.0.lib'
]
}
}], }],
['OS == "mac"', { ['OS == "mac"', {
'libraries': [ 'link_settings': {
'../vendor/lib/libvips-cpp.42.dylib', 'library_dirs': ['<(sharp_vendor_dir)/lib'],
'../vendor/lib/libvips.42.dylib', 'libraries': [
'../vendor/lib/libglib-2.0.0.dylib', 'libvips-cpp.42.dylib',
'../vendor/lib/libgobject-2.0.0.dylib', 'libvips.42.dylib'
# Ensure runtime linking is relative to sharp.node ]
'-rpath \'@loader_path/../../vendor/lib\'' },
] 'xcode_settings': {
'OTHER_LDFLAGS': [
# Ensure runtime linking is relative to sharp.node
'-Wl,-rpath,\'@loader_path/../../vendor/<(vips_version)/lib\''
]
}
}], }],
['OS == "linux"', { ['OS == "linux"', {
'defines': [ 'defines': [
'_GLIBCXX_USE_CXX11_ABI=0' '_GLIBCXX_USE_CXX11_ABI=0'
], ],
'libraries': [ 'link_settings': {
'../vendor/lib/libvips-cpp.so', 'library_dirs': ['<(sharp_vendor_dir)/lib'],
'../vendor/lib/libvips.so', 'libraries': [
'../vendor/lib/libglib-2.0.so', '-l:libvips-cpp.so.42',
'../vendor/lib/libgobject-2.0.so', '-l:libvips.so.42'
# Dependencies of dependencies, included for openSUSE support ],
'../vendor/lib/libcairo.so', 'ldflags': [
'../vendor/lib/libexif.so', # Ensure runtime linking is relative to sharp.node
'../vendor/lib/libexpat.so', '-Wl,-s -Wl,--disable-new-dtags -Wl,-rpath=\'$$ORIGIN/../../vendor/<(vips_version)/lib\''
'../vendor/lib/libffi.so', ]
'../vendor/lib/libfontconfig.so', }
'../vendor/lib/libfreetype.so',
'../vendor/lib/libfribidi.so',
'../vendor/lib/libgdk_pixbuf-2.0.so',
'../vendor/lib/libgif.so',
'../vendor/lib/libgio-2.0.so',
'../vendor/lib/libgmodule-2.0.so',
'../vendor/lib/libgsf-1.so',
'../vendor/lib/libgthread-2.0.so',
'../vendor/lib/libharfbuzz.so',
'../vendor/lib/libjpeg.so',
'../vendor/lib/liblcms2.so',
'../vendor/lib/liborc-0.4.so',
'../vendor/lib/libpango-1.0.so',
'../vendor/lib/libpangocairo-1.0.so',
'../vendor/lib/libpangoft2-1.0.so',
'../vendor/lib/libpixman-1.so',
'../vendor/lib/libpng.so',
'../vendor/lib/librsvg-2.so',
'../vendor/lib/libtiff.so',
'../vendor/lib/libwebp.so',
'../vendor/lib/libwebpdemux.so',
'../vendor/lib/libwebpmux.so',
'../vendor/lib/libxml2.so',
'../vendor/lib/libz.so',
# Ensure runtime linking is relative to sharp.node
'-Wl,--disable-new-dtags -Wl,-rpath=\'$${ORIGIN}/../../vendor/lib\''
]
}] }]
] ]
}] }]
@@ -171,8 +178,7 @@
], ],
'xcode_settings': { 'xcode_settings': {
'CLANG_CXX_LANGUAGE_STANDARD': 'c++11', 'CLANG_CXX_LANGUAGE_STANDARD': 'c++11',
'CLANG_CXX_LIBRARY': 'libc++', 'MACOSX_DEPLOYMENT_TARGET': '10.9',
'MACOSX_DEPLOYMENT_TARGET': '10.7',
'GCC_ENABLE_CPP_EXCEPTIONS': 'YES', 'GCC_ENABLE_CPP_EXCEPTIONS': 'YES',
'GCC_ENABLE_CPP_RTTI': 'YES', 'GCC_ENABLE_CPP_RTTI': 'YES',
'OTHER_CPLUSPLUSFLAGS': [ 'OTHER_CPLUSPLUSFLAGS': [
@@ -189,10 +195,30 @@
'-Wno-cast-function-type' '-Wno-cast-function-type'
] ]
}], }],
['target_arch == "arm"', {
'cflags_cc': [
'-Wno-psabi'
]
}],
['OS == "win"', { ['OS == "win"', {
'msvs_settings': { 'msvs_settings': {
'VCCLCompilerTool': { 'VCCLCompilerTool': {
'ExceptionHandling': 1 'ExceptionHandling': 1,
'WholeProgramOptimization': 'true'
},
'VCLibrarianTool': {
'AdditionalOptions': [
'/LTCG:INCREMENTAL'
]
},
'VCLinkerTool': {
'ImageHasSafeExceptionHandlers': 'false',
'OptimizeReferences': 2,
'EnableCOMDATFolding': 2,
'LinkIncremental': 1,
'AdditionalOptions': [
'/LTCG:INCREMENTAL'
]
} }
}, },
'msvs_disabled_warnings': [ 'msvs_disabled_warnings': [

View File

@@ -1,6 +1,6 @@
# sharp # sharp
<img src="https://raw.githubusercontent.com/lovell/sharp/master/docs/image/sharp-logo.svg?sanitize=true" width="160" height="160" alt="sharp logo" align="right"> <img src="https://cdn.jsdelivr.net/gh/lovell/sharp@master/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 The typical use case for this high speed Node.js module
is to convert large images in common formats to is to convert large images in common formats to
@@ -16,12 +16,9 @@ Lanczos resampling ensures quality is not sacrificed for speed.
As well as image resizing, operations such as As well as image resizing, operations such as
rotation, extraction, compositing and gamma correction are available. rotation, extraction, compositing and gamma correction are available.
Most modern 64-bit macOS, Windows and Linux systems running Most modern macOS, Windows and Linux systems running Node.js v10.16.0+
Node versions 10, 12 and 13
do not require any additional install or runtime dependencies. do not require any additional install or runtime dependencies.
[![Test Coverage](https://coveralls.io/repos/lovell/sharp/badge.png?branch=master)](https://coveralls.io/r/lovell/sharp?branch=master)
### Formats ### Formats
This module supports reading JPEG, PNG, WebP, TIFF, GIF and SVG images. This module supports reading JPEG, PNG, WebP, TIFF, GIF and SVG images.
@@ -67,73 +64,6 @@ as [pngcrush](https://pmt.sourceforge.io/pngcrush/).
A [guide for contributors](https://github.com/lovell/sharp/blob/master/.github/CONTRIBUTING.md) A [guide for contributors](https://github.com/lovell/sharp/blob/master/.github/CONTRIBUTING.md)
covers reporting bugs, requesting features and submitting code changes. covers reporting bugs, requesting features and submitting code changes.
### Credits
This module would never have been possible without
the help and code contributions of the following people:
* [John Cupitt](https://github.com/jcupitt)
* [Pierre Inglebert](https://github.com/pierreinglebert)
* [Jonathan Ong](https://github.com/jonathanong)
* [Chanon Sajjamanochai](https://github.com/chanon)
* [Juliano Julio](https://github.com/julianojulio)
* [Daniel Gasienica](https://github.com/gasi)
* [Julian Walker](https://github.com/julianwa)
* [Amit Pitaru](https://github.com/apitaru)
* [Brandon Aaron](https://github.com/brandonaaron)
* [Andreas Lind](https://github.com/papandreou)
* [Maurus Cuelenaere](https://github.com/mcuelenaere)
* [Linus Unnebäck](https://github.com/LinusU)
* [Victor Mateevitsi](https://github.com/mvictoras)
* [Alaric Holloway](https://github.com/skedastik)
* [Bernhard K. Weisshuhn](https://github.com/bkw)
* [David A. Carley](https://github.com/dacarley)
* [John Tobin](https://github.com/jtobinisaniceguy)
* [Kenton Gray](https://github.com/kentongray)
* [Felix Bünemann](https://github.com/felixbuenemann)
* [Samy Al Zahrani](https://github.com/salzhrani)
* [Chintan Thakkar](https://github.com/lemnisk8)
* [F. Orlando Galashan](https://github.com/frulo)
* [Kleis Auke Wolthuizen](https://github.com/kleisauke)
* [Matt Hirsch](https://github.com/mhirsch)
* [Rahul Nanwani](https://github.com/rnanwani)
* [Matthias Thoemmes](https://github.com/cmtt)
* [Patrick Paskaris](https://github.com/ppaskaris)
* [Jérémy Lal](https://github.com/kapouer)
* [Alice Monday](https://github.com/alice0meta)
* [Kristo Jorgenson](https://github.com/kristojorg)
* [Yves Bos](https://github.com/YvesBos)
* [Nicolas Coden](https://github.com/ncoden)
* [Matt Parrish](https://github.com/pbomb)
* [Matthew McEachen](https://github.com/mceachen)
* [Jarda Kotěšovec](https://github.com/jardakotesovec)
* [Kenric D'Souza](https://github.com/AzureByte)
* [Oleh Aleinyk](https://github.com/oaleynik)
* [Marcel Bretschneider](https://github.com/3epnm)
* [Andrea Bianco](https://github.com/BiancoA)
* [Rik Heywood](https://github.com/rikh42)
* [Thomas Parisot](https://github.com/oncletom)
* [Nathan Graves](https://github.com/woolite64)
* [Tom Lokhorst](https://github.com/tomlokhorst)
* [Espen Hovlandsdal](https://github.com/rexxars)
* [Sylvain Dumont](https://github.com/sylvaindumont)
* [Alun Davies](https://github.com/alundavies)
* [Aidan Hoolachan](https://github.com/ajhool)
* [Axel Eirola](https://github.com/aeirola)
* [Freezy](https://github.com/freezy)
* [Julian Aubourg](https://github.com/jaubourg)
* [Keith Belovay](https://github.com/fromkeith)
* [Michael B. Klein](https://github.com/mbklein)
* [Jakub Michálek](https://github.com/Goues)
* [Ilya Ovdin](https://github.com/iovdin)
* [Andargor](https://github.com/Andargor)
* [Nicolas Stepien](https://github.com/MayhemYDG)
* [Paul Neave](https://github.com/neave)
* [Brendan Kennedy](https://github.com/rustyguts)
* [Brychan Bennett-Odlum](https://github.com/BrychanOdlum)
Thank you!
### Licensing ### Licensing
Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors. Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.

View File

@@ -42,16 +42,17 @@ Extract a single channel from a multi-channel image.
### Parameters ### Parameters
- `channel` **([Number][1] \| [String][2])** zero-indexed band number to extract, or `red`, `green` or `blue` as alternative to `0`, `1` or `2` respectively. - `channel` **([number][1] \| [string][2])** zero-indexed channel/band number to extract, or `red`, `green`, `blue` or `alpha`.
### Examples ### Examples
```javascript ```javascript
sharp(input) sharp(input)
.extractChannel('green') .extractChannel('green')
.toFile('input_green.jpg', function(err, info) { .toColourspace('b-w')
.toFile('green.jpg', function(err, info) {
// info.channels === 1 // info.channels === 1
// input_green.jpg contains the green channel of the input image // green.jpg is a greyscale image containing the green channel of the input
}); });
``` ```
@@ -74,7 +75,7 @@ For raw pixel input, the `options` object should contain a `raw` attribute, whic
### Parameters ### Parameters
- `images` **([Array][4]&lt;([String][2] \| [Buffer][5])> | [String][2] \| [Buffer][5])** one or more images (file paths, Buffers). - `images` **([Array][4]&lt;([string][2] \| [Buffer][5])> | [string][2] \| [Buffer][5])** one or more images (file paths, Buffers).
- `options` **[Object][6]** image options, see `sharp()` constructor. - `options` **[Object][6]** image options, see `sharp()` constructor.
@@ -88,7 +89,7 @@ Perform a bitwise boolean operation on all input image channels (bands) to produ
### Parameters ### Parameters
- `boolOp` **[String][2]** one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively. - `boolOp` **[string][2]** one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively.
### Examples ### Examples

View File

@@ -7,7 +7,7 @@ An alpha channel may be present and will be unchanged by the operation.
### Parameters ### Parameters
- `rgb` **([String][1] \| [Object][2])** parsed by the [color][3] module to extract chroma values. - `rgb` **([string][1] \| [Object][2])** parsed by the [color][3] module to extract chroma values.
- Throws **[Error][4]** Invalid parameter - Throws **[Error][4]** Invalid parameter
@@ -46,7 +46,7 @@ By default output image will be web-friendly sRGB, with additional channels inte
### Parameters ### Parameters
- `colourspace` **[String][1]?** output colourspace e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...][6] - `colourspace` **[string][1]?** output colourspace e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...][6]
- Throws **[Error][4]** Invalid parameters - Throws **[Error][4]** Invalid parameters
@@ -59,7 +59,7 @@ Alternative spelling of `toColourspace`.
### Parameters ### Parameters
- `colorspace` **[String][1]?** output colorspace. - `colorspace` **[string][1]?** output colorspace.
- Throws **[Error][4]** Invalid parameters - Throws **[Error][4]** Invalid parameters

View File

@@ -20,7 +20,7 @@ and [https://www.cairographics.org/operators/][2]
### Parameters ### Parameters
- `images` **[Array][3]&lt;[Object][4]>** Ordered list of images to composite - `images` **[Array][3]&lt;[Object][4]>** Ordered list of images to composite
- `images[].input` **([Buffer][5] \| [String][6])?** Buffer containing image data, String containing the path to an image file, or Create object (see bellow) - `images[].input` **([Buffer][5] \| [String][6])?** Buffer containing image data, String containing the path to an image file, or Create object (see below)
- `images[].input.create` **[Object][4]?** describes a blank overlay to be created. - `images[].input.create` **[Object][4]?** describes a blank overlay to be created.
- `images[].input.create.width` **[Number][7]?** - `images[].input.create.width` **[Number][7]?**
- `images[].input.create.height` **[Number][7]?** - `images[].input.create.height` **[Number][7]?**

View File

@@ -2,32 +2,43 @@
## Sharp ## Sharp
Constructor factory to create an instance of `sharp`, to which further methods are chained.
JPEG, PNG, WebP 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.
Implements the [stream.Duplex][1] class.
### Parameters ### Parameters
- `input` **([Buffer][1] \| [String][2])?** if present, can be - `input` **([Buffer][2] \| [string][3])?** if present, can be
a Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or a Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or
a String containing the filesystem path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file. a String containing the filesystem path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file.
JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present. JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present.
- `options` **[Object][3]?** if present, is an Object with optional attributes. - `options` **[Object][4]?** if present, is an Object with optional attributes.
- `options.failOnError` **[Boolean][4]** by default halt processing and raise an error when loading invalid images. - `options.failOnError` **[boolean][5]** by default halt processing and raise an error when loading invalid images.
Set this flag to `false` if you'd rather apply a "best effort" to decode images, even if the data is corrupt or invalid. (optional, default `true`) Set this flag to `false` if you'd rather apply a "best effort" to decode images, even if the data is corrupt or invalid. (optional, default `true`)
- `options.limitInputPixels` **([Number][5] \| [Boolean][4])** Do not process input images where the number of pixels - `options.limitInputPixels` **([number][6] \| [boolean][5])** 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. (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`) 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.sequentialRead` **[Boolean][4]** Set this to `true` to use sequential rather than random access where possible. - `options.sequentialRead` **[boolean][5]** 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`) This can reduce memory usage and might improve performance on some systems. (optional, default `false`)
- `options.density` **[Number][5]** number representing the DPI for vector images. (optional, default `72`) - `options.density` **[number][6]** number representing the DPI for vector images. (optional, default `72`)
- `options.pages` **[Number][5]** number of pages to extract for multi-page input (GIF, TIFF, PDF), use -1 for all pages. (optional, default `1`) - `options.pages` **[number][6]** number of pages to extract for multi-page input (GIF, TIFF, PDF), use -1 for all pages. (optional, default `1`)
- `options.page` **[Number][5]** page number to start extracting from for multi-page input (GIF, TIFF, PDF), zero based. (optional, default `0`) - `options.page` **[number][6]** page number to start extracting from for multi-page input (GIF, TIFF, PDF), zero based. (optional, default `0`)
- `options.raw` **[Object][3]?** describes raw pixel input image data. See `raw()` for pixel ordering. - `options.level` **[number][6]** level to extract from a multi-level input (OpenSlide), zero based. (optional, default `0`)
- `options.raw.width` **[Number][5]?** - `options.animated` **[boolean][5]** Set to `true` to read all frames/pages of an animated image (equivalent of setting `pages` to `-1`). (optional, default `false`)
- `options.raw.height` **[Number][5]?** - `options.raw` **[Object][4]?** describes raw pixel input image data. See `raw()` for pixel ordering.
- `options.raw.channels` **[Number][5]?** 1-4 - `options.raw.width` **[number][6]?**
- `options.create` **[Object][3]?** describes a new image to be created. - `options.raw.height` **[number][6]?**
- `options.create.width` **[Number][5]?** - `options.raw.channels` **[number][6]?** 1-4
- `options.create.height` **[Number][5]?** - `options.create` **[Object][4]?** describes a new image to be created.
- `options.create.channels` **[Number][5]?** 3-4 - `options.create.width` **[number][6]?**
- `options.create.background` **([String][2] \| [Object][3])?** parsed by the [color][6] module to extract values for red, green, blue and alpha. - `options.create.height` **[number][6]?**
- `options.create.channels` **[number][6]?** 3-4
- `options.create.background` **([string][3] \| [Object][4])?** parsed by the [color][7] module to extract values for red, green, blue and alpha.
### Examples ### Examples
@@ -68,9 +79,14 @@ sharp({
.then( ... ); .then( ... );
``` ```
- Throws **[Error][7]** Invalid parameters ```javascript
// Convert an animated GIF to an animated WebP
await sharp('in.gif', { animated: true }).toFile('out.webp');
```
Returns **[Sharp][8]** - Throws **[Error][8]** Invalid parameters
Returns **[Sharp][9]**
## clone ## clone
@@ -89,20 +105,71 @@ readableStream.pipe(pipeline);
// secondWritableStream receives auto-rotated, extracted region of readableStream // secondWritableStream receives auto-rotated, extracted region of readableStream
``` ```
Returns **[Sharp][8]** ```javascript
// Create a pipeline that will download an image, resize it and format it to different files
// Using Promises to know when the pipeline is complete
const fs = require("fs");
const got = require("got");
const sharpStream = sharp({
failOnError: false
});
[1]: https://nodejs.org/api/buffer.html const promises = [];
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String promises.push(
sharpStream
.clone()
.jpeg({ quality: 100 })
.toFile("originalFile.jpg")
);
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object promises.push(
sharpStream
.clone()
.resize({ width: 500 })
.jpeg({ quality: 80 })
.toFile("optimized-500.jpg")
);
[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean promises.push(
sharpStream
.clone()
.resize({ width: 500 })
.webp({ quality: 80 })
.toFile("optimized-500.webp")
);
[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number // https://github.com/sindresorhus/got#gotstreamurl-options
got.stream("https://www.example.com/some-file.jpg").pipe(sharpStream);
[6]: https://www.npmjs.org/package/color Promise.all(promises)
.then(res => { console.log("Done!", res); })
.catch(err => {
console.error("Error processing files, let's clean it up", err);
try {
fs.unlinkSync("originalFile.jpg");
fs.unlinkSync("optimized-500.jpg");
fs.unlinkSync("optimized-500.webp");
} catch (e) {}
});
```
[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error Returns **[Sharp][9]**
[8]: #sharp [1]: http://nodejs.org/api/stream.html#stream_class_stream_duplex
[2]: https://nodejs.org/api/buffer.html
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
[7]: https://www.npmjs.org/package/color
[8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error
[9]: #sharp

View File

@@ -20,6 +20,7 @@ A `Promise` is returned when `callback` is not provided.
- `loop`: Number of times to loop an animated image, zero refers to a continuous loop. - `loop`: Number of times to loop an animated image, zero refers to a continuous loop.
- `delay`: Delay in ms between each page in an animated image, provided as an array of integers. - `delay`: Delay in ms between each page in an animated image, provided as an array of integers.
- `pagePrimary`: Number of the primary page in a HEIF image - `pagePrimary`: Number of the primary page in a HEIF image
- `levels`: Details of each level in a multi-level image provided as an array of objects, requires libvips compiled with support for OpenSlide
- `hasProfile`: Boolean indicating the presence of an embedded ICC profile - `hasProfile`: Boolean indicating the presence of an embedded ICC profile
- `hasAlpha`: Boolean indicating the presence of an alpha transparency channel - `hasAlpha`: Boolean indicating the presence of an alpha transparency channel
- `orientation`: Number value of the EXIF Orientation header, if present - `orientation`: Number value of the EXIF Orientation header, if present
@@ -68,8 +69,10 @@ A `Promise` is returned when `callback` is not provided.
- `minY` (y-coordinate of one of the pixel where the minimum lies) - `minY` (y-coordinate of one of the pixel where the minimum lies)
- `maxX` (x-coordinate of one of the pixel where the maximum lies) - `maxX` (x-coordinate of one of the pixel where the maximum lies)
- `maxY` (y-coordinate of one of the pixel where the maximum lies) - `maxY` (y-coordinate of one of the pixel where the maximum lies)
- `isOpaque`: Value to identify if the image is opaque or transparent, based on the presence and use of alpha channel - `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) - `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)
### Parameters ### Parameters
@@ -86,6 +89,11 @@ image
}); });
``` ```
```javascript
const { entropy, sharpness, dominant } = await sharp(input).stats();
const { r, g, b } = dominant;
```
Returns **[Promise][5]&lt;[Object][6]>** Returns **[Promise][5]&lt;[Object][6]>**
[1]: https://libvips.github.io/libvips/API/current/VipsImage.html#VipsInterpretation [1]: https://libvips.github.io/libvips/API/current/VipsImage.html#VipsInterpretation

View File

@@ -21,9 +21,9 @@ for example `rotate(x).extract(y)` will produce a different result to `extract(y
### Parameters ### Parameters
- `angle` **[Number][1]** angle of rotation. (optional, default `auto`) - `angle` **[number][1]** angle of rotation. (optional, default `auto`)
- `options` **[Object][2]?** if present, is an Object with optional attributes. - `options` **[Object][2]?** if present, is an Object with optional attributes.
- `options.background` **([String][3] \| [Object][2])** parsed by the [color][4] module to extract values for red, green, blue and alpha. (optional, default `"#000000"`) - `options.background` **([string][3] \| [Object][2])** parsed by the [color][4] module to extract values for red, green, blue and alpha. (optional, default `"#000000"`)
### Examples ### Examples
@@ -74,9 +74,9 @@ Separate control over the level of sharpening in "flat" and "jagged" areas is av
### Parameters ### Parameters
- `sigma` **[Number][1]?** the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`. - `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`) - `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`) - `jagged` **[number][1]** the level of sharpening to apply to "jagged" areas. (optional, default `2.0`)
- Throws **[Error][5]** Invalid parameters - Throws **[Error][5]** Invalid parameters
@@ -90,7 +90,7 @@ When used without parameters the default window is 3x3.
### Parameters ### Parameters
- `size` **[Number][1]** square mask size: size x size (optional, default `3`) - `size` **[number][1]** square mask size: size x size (optional, default `3`)
- Throws **[Error][5]** Invalid parameters - Throws **[Error][5]** Invalid parameters
@@ -105,7 +105,7 @@ When a `sigma` is provided, performs a slower, more accurate Gaussian blur.
### Parameters ### Parameters
- `sigma` **[Number][1]?** a value between 0.3 and 1000 representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`. - `sigma` **[number][1]?** a value between 0.3 and 1000 representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
- Throws **[Error][5]** Invalid parameters - Throws **[Error][5]** Invalid parameters
@@ -119,7 +119,7 @@ Merge alpha transparency channel, if any, with a background.
### Parameters ### Parameters
- `options` **[Object][2]?** - `options` **[Object][2]?**
- `options.background` **([String][3] \| [Object][2])** background colour, parsed by the [color][4] module, defaults to black. (optional, default `{r:0,g:0,b:0}`) - `options.background` **([string][3] \| [Object][2])** background colour, parsed by the [color][4] module, defaults to black. (optional, default `{r:0,g:0,b:0}`)
Returns **Sharp** Returns **Sharp**
@@ -135,8 +135,8 @@ Supply a second argument to use a different output gamma value, otherwise the fi
### Parameters ### Parameters
- `gamma` **[Number][1]** value between 1.0 and 3.0. (optional, default `2.2`) - `gamma` **[number][1]** value between 1.0 and 3.0. (optional, default `2.2`)
- `gammaOut` **[Number][1]?** value between 1.0 and 3.0. (optional, defaults to same as `gamma`) - `gammaOut` **[number][1]?** value between 1.0 and 3.0. (optional, defaults to same as `gamma`)
- Throws **[Error][5]** Invalid parameters - Throws **[Error][5]** Invalid parameters
@@ -180,11 +180,11 @@ Convolve the image with the specified kernel.
### Parameters ### Parameters
- `kernel` **[Object][2]** - `kernel` **[Object][2]**
- `kernel.width` **[Number][1]** width of the kernel in pixels. - `kernel.width` **[number][1]** width of the kernel in pixels.
- `kernel.height` **[Number][1]** width of the kernel in pixels. - `kernel.height` **[number][1]** width of the kernel in pixels.
- `kernel.kernel` **[Array][7]&lt;[Number][1]>** Array of length `width*height` containing the kernel values. - `kernel.kernel` **[Array][7]&lt;[number][1]>** Array of length `width*height` containing the kernel values.
- `kernel.scale` **[Number][1]** the scale of the kernel in pixels. (optional, default `sum`) - `kernel.scale` **[number][1]** the scale of the kernel in pixels. (optional, default `sum`)
- `kernel.offset` **[Number][1]** the offset of the kernel in pixels. (optional, default `0`) - `kernel.offset` **[number][1]** the offset of the kernel in pixels. (optional, default `0`)
### Examples ### Examples
@@ -212,7 +212,7 @@ Any pixel value greather than or equal to the threshold value will be set to 255
### Parameters ### Parameters
- `threshold` **[Number][1]** a value in the range 0-255 representing the level at which the threshold will be applied. (optional, default `128`) - `threshold` **[number][1]** a value in the range 0-255 representing the level at which the threshold will be applied. (optional, default `128`)
- `options` **[Object][2]?** - `options` **[Object][2]?**
- `options.greyscale` **[Boolean][6]** convert to single channel greyscale. (optional, default `true`) - `options.greyscale` **[Boolean][6]** convert to single channel greyscale. (optional, default `true`)
- `options.grayscale` **[Boolean][6]** alternative spelling for greyscale. (optional, default `true`) - `options.grayscale` **[Boolean][6]** alternative spelling for greyscale. (optional, default `true`)
@@ -231,13 +231,13 @@ the selected bitwise boolean `operation` between the corresponding pixels of the
### Parameters ### Parameters
- `operand` **([Buffer][8] \| [String][3])** Buffer containing image data or String containing the path to an image file. - `operand` **([Buffer][8] \| [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. - `operator` **[string][3]** one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively.
- `options` **[Object][2]?** - `options` **[Object][2]?**
- `options.raw` **[Object][2]?** describes operand when using raw pixel data. - `options.raw` **[Object][2]?** describes operand when using raw pixel data.
- `options.raw.width` **[Number][1]?** - `options.raw.width` **[number][1]?**
- `options.raw.height` **[Number][1]?** - `options.raw.height` **[number][1]?**
- `options.raw.channels` **[Number][1]?** - `options.raw.channels` **[number][1]?**
- Throws **[Error][5]** Invalid parameters - Throws **[Error][5]** Invalid parameters
@@ -250,8 +250,8 @@ Apply the linear formula a \* input + b to the image (levels adjustment)
### Parameters ### Parameters
- `a` **[Number][1]** multiplier (optional, default `1.0`) - `a` **[number][1]** multiplier (optional, default `1.0`)
- `b` **[Number][1]** offset (optional, default `0.0`) - `b` **[number][1]** offset (optional, default `0.0`)
- Throws **[Error][5]** Invalid parameters - Throws **[Error][5]** Invalid parameters
@@ -264,8 +264,7 @@ Recomb the image with the specified matrix.
### Parameters ### Parameters
- `inputMatrix` - `inputMatrix` **[Array][7]&lt;[Array][7]&lt;[number][1]>>** 3x3 Recombination matrix
- `3x3` **[Array][7]&lt;[Array][7]&lt;[Number][1]>>** Recombination matrix
### Examples ### Examples
@@ -298,9 +297,9 @@ Transforms the image using brightness, saturation and hue rotation.
### Parameters ### Parameters
- `options` **[Object][2]?** - `options` **[Object][2]?**
- `options.brightness` **[Number][1]?** Brightness multiplier - `options.brightness` **[number][1]?** Brightness multiplier
- `options.saturation` **[Number][1]?** Saturation multiplier - `options.saturation` **[number][1]?** Saturation multiplier
- `options.hue` **[Number][1]?** Degrees for hue rotation - `options.hue` **[number][1]?** Degrees for hue rotation
### Examples ### Examples

View File

@@ -15,7 +15,7 @@ A `Promise` is returned when `callback` is not provided.
### Parameters ### Parameters
- `fileOut` **[String][2]** the path to write the image data to. - `fileOut` **[string][2]** the path to write the image data to.
- `callback` **[Function][3]?** called on completion with two arguments `(err, info)`. - `callback` **[Function][3]?** called on completion with two arguments `(err, info)`.
`info` contains the output image `format`, `size` (bytes), `width`, `height`, `info` contains the output image `format`, `size` (bytes), `width`, `height`,
`channels` and `premultiplied` (indicating if premultiplication was used). `channels` and `premultiplied` (indicating if premultiplication was used).
@@ -62,7 +62,7 @@ A `Promise` is returned when `callback` is not provided.
### Parameters ### Parameters
- `options` **[Object][6]?** - `options` **[Object][6]?**
- `options.resolveWithObject` **[Boolean][7]?** Resolve the Promise with an Object containing `data` and `info` properties instead of resolving only with `data`. - `options.resolveWithObject` **[boolean][7]?** Resolve the Promise with an Object containing `data` and `info` properties instead of resolving only with `data`.
- `callback` **[Function][3]?** - `callback` **[Function][3]?**
### Examples ### Examples
@@ -91,13 +91,17 @@ Returns **[Promise][5]&lt;[Buffer][8]>** when no callback is provided
## withMetadata ## withMetadata
Include all metadata (EXIF, XMP, IPTC) from the input image in the output image. Include all metadata (EXIF, XMP, IPTC) from the input image in the output image.
The default behaviour, when `withMetadata` is not used, is to strip all metadata and convert to the device-independent sRGB colour space. This will also convert to and add a web-friendly sRGB ICC profile unless a custom
This will also convert to and add a web-friendly sRGB ICC profile. output profile is provided.
The default behaviour, when `withMetadata` is not used, is to convert to the device-independent
sRGB colour space and strip all metadata, including the removal of any ICC profile.
### Parameters ### Parameters
- `options` **[Object][6]?** - `options` **[Object][6]?**
- `options.orientation` **[Number][9]?** value between 1 and 8, used to update the EXIF `Orientation` tag. - `options.orientation` **[number][9]?** value between 1 and 8, used to update the EXIF `Orientation` tag.
- `options.icc` **[string][2]?** filesystem path to output ICC profile, defaults to sRGB.
### Examples ### Examples
@@ -118,7 +122,7 @@ Force output to a given format.
### Parameters ### Parameters
- `format` **([String][2] \| [Object][6])** as a String or an Object with an 'id' attribute - `format` **([string][2] \| [Object][6])** as a string or an Object with an 'id' attribute
- `options` **[Object][6]** output options - `options` **[Object][6]** output options
### Examples ### Examples
@@ -138,21 +142,23 @@ Returns **Sharp**
Use these JPEG options for output image. Use these JPEG options for output image.
Some of these options require the use of a globally-installed libvips compiled with support for mozjpeg.
### Parameters ### Parameters
- `options` **[Object][6]?** output options - `options` **[Object][6]?** output options
- `options.quality` **[Number][9]** quality, integer 1-100 (optional, default `80`) - `options.quality` **[number][9]** quality, integer 1-100 (optional, default `80`)
- `options.progressive` **[Boolean][7]** use progressive (interlace) scan (optional, default `false`) - `options.progressive` **[boolean][7]** use progressive (interlace) scan (optional, default `false`)
- `options.chromaSubsampling` **[String][2]** set to '4:4:4' to prevent chroma subsampling when quality &lt;= 90 (optional, default `'4:2:0'`) - `options.chromaSubsampling` **[string][2]** set to '4:4:4' to prevent chroma subsampling otherwise defaults to '4:2:0' chroma subsampling (optional, default `'4:2:0'`)
- `options.trellisQuantisation` **[Boolean][7]** apply trellis quantisation, requires libvips compiled with support for mozjpeg (optional, default `false`) - `options.optimiseCoding` **[boolean][7]** optimise Huffman coding tables (optional, default `true`)
- `options.overshootDeringing` **[Boolean][7]** apply overshoot deringing, requires libvips compiled with support for mozjpeg (optional, default `false`) - `options.optimizeCoding` **[boolean][7]** alternative spelling of optimiseCoding (optional, default `true`)
- `options.optimiseScans` **[Boolean][7]** optimise progressive scans, forces progressive, requires libvips compiled with support for mozjpeg (optional, default `false`) - `options.trellisQuantisation` **[boolean][7]** apply trellis quantisation, requires libvips compiled with support for mozjpeg (optional, default `false`)
- `options.optimizeScans` **[Boolean][7]** alternative spelling of optimiseScans (optional, default `false`) - `options.overshootDeringing` **[boolean][7]** apply overshoot deringing, requires libvips compiled with support for mozjpeg (optional, default `false`)
- `options.optimiseCoding` **[Boolean][7]** optimise Huffman coding tables (optional, default `true`) - `options.optimiseScans` **[boolean][7]** optimise progressive scans, forces progressive, requires libvips compiled with support for mozjpeg (optional, default `false`)
- `options.optimizeCoding` **[Boolean][7]** alternative spelling of optimiseCoding (optional, default `true`) - `options.optimizeScans` **[boolean][7]** alternative spelling of optimiseScans, requires libvips compiled with support for mozjpeg (optional, default `false`)
- `options.quantisationTable` **[Number][9]** quantization table to use, integer 0-8, requires libvips compiled with support for mozjpeg (optional, default `0`) - `options.quantisationTable` **[number][9]** quantization table to use, integer 0-8, requires libvips compiled with support for mozjpeg (optional, default `0`)
- `options.quantizationTable` **[Number][9]** alternative spelling of quantisationTable (optional, default `0`) - `options.quantizationTable` **[number][9]** alternative spelling of quantisationTable, requires libvips compiled with support for mozjpeg (optional, default `0`)
- `options.force` **[Boolean][7]** force JPEG output, otherwise attempt to use input format (optional, default `true`) - `options.force` **[boolean][7]** force JPEG output, otherwise attempt to use input format (optional, default `true`)
### Examples ### Examples
@@ -177,18 +183,20 @@ Use these PNG options for output image.
PNG output is always full colour at 8 or 16 bits per pixel. PNG output is always full colour at 8 or 16 bits per pixel.
Indexed PNG input at 1, 2 or 4 bits per pixel is converted to 8 bits per pixel. Indexed PNG input at 1, 2 or 4 bits per pixel is converted to 8 bits per pixel.
Some of these options require the use of a globally-installed libvips compiled with support for libimagequant (GPL).
### Parameters ### Parameters
- `options` **[Object][6]?** - `options` **[Object][6]?**
- `options.progressive` **[Boolean][7]** use progressive (interlace) scan (optional, default `false`) - `options.progressive` **[boolean][7]** use progressive (interlace) scan (optional, default `false`)
- `options.compressionLevel` **[Number][9]** zlib compression level, 0-9 (optional, default `9`) - `options.compressionLevel` **[number][9]** zlib compression level, 0-9 (optional, default `9`)
- `options.adaptiveFiltering` **[Boolean][7]** use adaptive row filtering (optional, default `false`) - `options.adaptiveFiltering` **[boolean][7]** use adaptive row filtering (optional, default `false`)
- `options.palette` **[Boolean][7]** quantise to a palette-based image with alpha transparency support, requires libvips compiled with support for libimagequant (optional, default `false`) - `options.palette` **[boolean][7]** quantise to a palette-based image with alpha transparency support, requires libvips compiled with support for libimagequant (optional, default `false`)
- `options.quality` **[Number][9]** use the lowest number of colours needed to achieve given quality, requires libvips compiled with support for libimagequant (optional, default `100`) - `options.quality` **[number][9]** use the lowest number of colours needed to achieve given quality, sets `palette` to `true`, requires libvips compiled with support for libimagequant (optional, default `100`)
- `options.colours` **[Number][9]** maximum number of palette entries, requires libvips compiled with support for libimagequant (optional, default `256`) - `options.colours` **[number][9]** maximum number of palette entries, sets `palette` to `true`, requires libvips compiled with support for libimagequant (optional, default `256`)
- `options.colors` **[Number][9]** alternative spelling of `options.colours`, requires libvips compiled with support for libimagequant (optional, default `256`) - `options.colors` **[number][9]** alternative spelling of `options.colours`, sets `palette` to `true`, requires libvips compiled with support for libimagequant (optional, default `256`)
- `options.dither` **[Number][9]** level of Floyd-Steinberg error diffusion, requires libvips compiled with support for libimagequant (optional, default `1.0`) - `options.dither` **[number][9]** level of Floyd-Steinberg error diffusion, sets `palette` to `true`, requires libvips compiled with support for libimagequant (optional, default `1.0`)
- `options.force` **[Boolean][7]** force PNG output, otherwise attempt to use input format (optional, default `true`) - `options.force` **[boolean][7]** force PNG output, otherwise attempt to use input format (optional, default `true`)
### Examples ### Examples
@@ -210,13 +218,16 @@ Use these WebP options for output image.
### Parameters ### Parameters
- `options` **[Object][6]?** output options - `options` **[Object][6]?** output options
- `options.quality` **[Number][9]** quality, integer 1-100 (optional, default `80`) - `options.quality` **[number][9]** quality, integer 1-100 (optional, default `80`)
- `options.alphaQuality` **[Number][9]** quality of alpha layer, integer 0-100 (optional, default `100`) - `options.alphaQuality` **[number][9]** quality of alpha layer, integer 0-100 (optional, default `100`)
- `options.lossless` **[Boolean][7]** use lossless compression mode (optional, default `false`) - `options.lossless` **[boolean][7]** use lossless compression mode (optional, default `false`)
- `options.nearLossless` **[Boolean][7]** use near_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.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.reductionEffort` **[number][9]** level of CPU effort to reduce file size, integer 0-6 (optional, default `4`)
- `options.force` **[Boolean][7]** force WebP output, otherwise attempt to use input format (optional, default `true`) - `options.pageHeight` **[number][9]?** page height for animated output
- `options.loop` **[number][9]** number of animation iterations, use 0 for infinite animation (optional, default `0`)
- `options.delay` **[Array][10]&lt;[number][9]>?** list of delays between animation frames (in milliseconds)
- `options.force` **[boolean][7]** force WebP output, otherwise attempt to use input format (optional, default `true`)
### Examples ### Examples
@@ -227,6 +238,27 @@ const data = await sharp(input)
.toBuffer(); .toBuffer();
``` ```
- Throws **[Error][4]** Invalid options
Returns **Sharp**
## gif
Use these GIF options for output image.
Requires libvips compiled with support for ImageMagick or GraphicsMagick.
The prebuilt binaries do not include this - see
[installing a custom libvips][11].
### Parameters
- `options` **[Object][6]?** output options
- `options.pageHeight` **[number][9]?** page height for animated output
- `options.loop` **[number][9]** number of animation iterations, use 0 for infinite animation (optional, default `0`)
- `options.delay` **[Array][10]&lt;[number][9]>?** list of delays between animation frames (in milliseconds)
- `options.force` **[boolean][7]** force GIF output, otherwise attempt to use input format (optional, default `true`)
- Throws **[Error][4]** Invalid options - Throws **[Error][4]** Invalid options
Returns **Sharp** Returns **Sharp**
@@ -238,17 +270,17 @@ Use these TIFF options for output image.
### Parameters ### Parameters
- `options` **[Object][6]?** output options - `options` **[Object][6]?** output options
- `options.quality` **[Number][9]** quality, integer 1-100 (optional, default `80`) - `options.quality` **[number][9]** quality, integer 1-100 (optional, default `80`)
- `options.force` **[Boolean][7]** force TIFF output, otherwise attempt to use input format (optional, default `true`) - `options.force` **[boolean][7]** force TIFF output, otherwise attempt to use input format (optional, default `true`)
- `options.compression` **[Boolean][7]** compression options: lzw, deflate, jpeg, ccittfax4 (optional, default `'jpeg'`) - `options.compression` **[boolean][7]** compression options: lzw, deflate, jpeg, ccittfax4 (optional, default `'jpeg'`)
- `options.predictor` **[Boolean][7]** compression predictor options: none, horizontal, float (optional, default `'horizontal'`) - `options.predictor` **[boolean][7]** compression predictor options: none, horizontal, float (optional, default `'horizontal'`)
- `options.pyramid` **[Boolean][7]** write an image pyramid (optional, default `false`) - `options.pyramid` **[boolean][7]** write an image pyramid (optional, default `false`)
- `options.tile` **[Boolean][7]** write a tiled tiff (optional, default `false`) - `options.tile` **[boolean][7]** write a tiled tiff (optional, default `false`)
- `options.tileWidth` **[Boolean][7]** horizontal tile size (optional, default `256`) - `options.tileWidth` **[boolean][7]** horizontal tile size (optional, default `256`)
- `options.tileHeight` **[Boolean][7]** vertical tile size (optional, default `256`) - `options.tileHeight` **[boolean][7]** vertical tile size (optional, default `256`)
- `options.xres` **[Number][9]** horizontal resolution in pixels/mm (optional, default `1.0`) - `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.yres` **[number][9]** vertical resolution in pixels/mm (optional, default `1.0`)
- `options.squash` **[Boolean][7]** squash 8-bit images down to 1 bit (optional, default `false`) - `options.bitdepth` **[boolean][7]** reduce bitdepth to 1, 2 or 4 bit (optional, default `8`)
### Examples ### Examples
@@ -257,7 +289,7 @@ Use these TIFF options for output image.
sharp('input.svg') sharp('input.svg')
.tiff({ .tiff({
compression: 'lzw', compression: 'lzw',
squash: true bitdepth: 1
}) })
.toFile('1-bpp-output.tiff') .toFile('1-bpp-output.tiff')
.then(info => { ... }); .then(info => { ... });
@@ -281,9 +313,9 @@ Most versions of libheif support only the patent-encumbered HEVC compression for
### Parameters ### Parameters
- `options` **[Object][6]?** output options - `options` **[Object][6]?** output options
- `options.quality` **[Number][9]** quality, integer 1-100 (optional, default `80`) - `options.quality` **[number][9]** quality, integer 1-100 (optional, default `80`)
- `options.compression` **[Boolean][7]** compression format: hevc, avc, jpeg, av1 (optional, default `'hevc'`) - `options.compression` **[boolean][7]** compression format: hevc, avc, jpeg, av1 (optional, default `'hevc'`)
- `options.lossless` **[Boolean][7]** use lossless compression (optional, default `false`) - `options.lossless` **[boolean][7]** use lossless compression (optional, default `false`)
- Throws **[Error][4]** Invalid options - Throws **[Error][4]** Invalid options
@@ -296,7 +328,9 @@ Returns **Sharp**
## raw ## raw
Force output to be raw, uncompressed uint8 pixel data. Force output to be raw, uncompressed, 8-bit unsigned integer (unit8) pixel data.
Pixel ordering is left-to-right, top-to-bottom, without padding.
Channel ordering will be RGB or RGBA for non-greyscale colourspaces.
### Examples ### Examples
@@ -307,6 +341,16 @@ const { data, info } = await sharp('input.jpg')
.toBuffer({ resolveWithObject: true }); .toBuffer({ resolveWithObject: true });
``` ```
```javascript
// Extract alpha channel as raw pixel data from PNG input
const data = await sharp('input.png')
.ensureAlpha()
.extractChannel(3)
.toColourspace('b-w')
.raw()
.toBuffer();
```
Returns **Sharp** Returns **Sharp**
## tile ## tile
@@ -320,14 +364,14 @@ Warning: multiple sharp instances concurrently producing tile output can expose
### Parameters ### Parameters
- `options` **[Object][6]?** - `options` **[Object][6]?**
- `options.size` **[Number][9]** tile size in pixels, a value between 1 and 8192. (optional, default `256`) - `options.size` **[number][9]** tile size in pixels, a value between 1 and 8192. (optional, default `256`)
- `options.overlap` **[Number][9]** tile overlap in pixels, a value between 0 and 8192. (optional, default `0`) - `options.overlap` **[number][9]** tile overlap in pixels, a value between 0 and 8192. (optional, default `0`)
- `options.angle` **[Number][9]** tile angle of rotation, must be a multiple of 90. (optional, default `0`) - `options.angle` **[number][9]** tile angle of rotation, must be a multiple of 90. (optional, default `0`)
- `options.background` **([String][2] \| [Object][6])** background colour, parsed by the [color][10] module, defaults to white without transparency. (optional, default `{r:255,g:255,b:255,alpha:1}`) - `options.background` **([string][2] \| [Object][6])** background colour, parsed by the [color][12] module, defaults to white without transparency. (optional, default `{r:255,g:255,b:255,alpha:1}`)
- `options.depth` **[String][2]?** how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout. - `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.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.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`, `zoomify` or `google`. (optional, default `'dz'`) - `options.layout` **[string][2]** filesystem layout, possible values are `dz`, `iiif`, `zoomify` or `google`. (optional, default `'dz'`)
### Examples ### Examples
@@ -365,4 +409,8 @@ Returns **Sharp**
[9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number [9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
[10]: https://www.npmjs.org/package/color [10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
[11]: https://sharp.pixelplumbing.com/install#custom-libvips
[12]: https://www.npmjs.org/package/color

View File

@@ -6,19 +6,21 @@ Resize image to `width`, `height` or `width x height`.
When both a `width` and `height` are provided, the possible methods by which the image should **fit** these are: When both a `width` and `height` are provided, the possible methods by which the image should **fit** these are:
- `cover`: Crop to cover both provided dimensions (the default). - `cover`: (default) Preserving aspect ratio, ensure the image covers both provided dimensions by cropping/clipping to fit.
- `contain`: Embed within both provided dimensions. - `contain`: Preserving aspect ratio, contain within both provided dimensions using "letterboxing" where necessary.
- `fill`: Ignore the aspect ratio of the input and stretch to both provided dimensions. - `fill`: Ignore the aspect ratio of the input and stretch to both provided dimensions.
- `inside`: Preserving aspect ratio, resize the image to be as large as possible while ensuring its dimensions are less than or equal to both those specified. - `inside`: Preserving aspect ratio, resize the image to be as large as possible while ensuring its dimensions are less than or equal to both those specified.
- `outside`: Preserving aspect ratio, resize the image to be as small as possible while ensuring its dimensions are greater than or equal to both those specified. - `outside`: Preserving aspect ratio, resize the image to be as small as possible while ensuring its dimensions are greater than or equal to both those specified.
Some of these values are based on the [object-fit][1] CSS property.
Some of these values are based on the [object-fit][1] CSS property.
When using a `fit` of `cover` or `contain`, the default **position** is `centre`. Other options are: When using a `fit` of `cover` or `contain`, the default **position** is `centre`. Other options are:
- `sharp.position`: `top`, `right top`, `right`, `right bottom`, `bottom`, `left bottom`, `left`, `left top`. - `sharp.position`: `top`, `right top`, `right`, `right bottom`, `bottom`, `left bottom`, `left`, `left top`.
- `sharp.gravity`: `north`, `northeast`, `east`, `southeast`, `south`, `southwest`, `west`, `northwest`, `center` or `centre`. - `sharp.gravity`: `north`, `northeast`, `east`, `southeast`, `south`, `southwest`, `west`, `northwest`, `center` or `centre`.
- `sharp.strategy`: `cover` only, dynamically crop using either the `entropy` or `attention` strategy. - `sharp.strategy`: `cover` only, dynamically crop using either the `entropy` or `attention` strategy.
Some of these values are based on the [object-position][2] CSS property.
Some of these values are based on the [object-position][2] CSS property.
The experimental strategy-based approach resizes so one dimension is at its target length The experimental strategy-based approach resizes so one dimension is at its target length
then repeatedly ranks edge regions, discarding the edge with the lowest score based on the selected strategy. then repeatedly ranks edge regions, discarding the edge with the lowest score based on the selected strategy.
@@ -36,8 +38,8 @@ Possible interpolation kernels are:
### Parameters ### Parameters
- `width` **[Number][8]?** pixels wide the resultant image should be. Use `null` or `undefined` to auto-scale the width to match the height. - `width` **[number][8]?** pixels wide the resultant image should be. Use `null` or `undefined` to auto-scale the width to match the height.
- `height` **[Number][8]?** pixels high the resultant image should be. Use `null` or `undefined` to auto-scale the height to match the width. - `height` **[number][8]?** pixels high the resultant image should be. Use `null` or `undefined` to auto-scale the height to match the width.
- `options` **[Object][9]?** - `options` **[Object][9]?**
- `options.width` **[String][10]?** alternative means of specifying `width`. If both are present this take priority. - `options.width` **[String][10]?** alternative means of specifying `width`. If both are present this take priority.
- `options.height` **[String][10]?** alternative means of specifying `height`. If both are present this take priority. - `options.height` **[String][10]?** alternative means of specifying `height`. If both are present this take priority.
@@ -134,11 +136,11 @@ This operation will always occur after resizing and extraction, if any.
### Parameters ### Parameters
- `extend` **([Number][8] \| [Object][9])** single pixel count to add to all edges or an Object with per-edge counts - `extend` **([number][8] \| [Object][9])** single pixel count to add to all edges or an Object with per-edge counts
- `extend.top` **[Number][8]?** - `extend.top` **[number][8]?**
- `extend.left` **[Number][8]?** - `extend.left` **[number][8]?**
- `extend.bottom` **[Number][8]?** - `extend.bottom` **[number][8]?**
- `extend.right` **[Number][8]?** - `extend.right` **[number][8]?**
- `extend.background` **([String][10] \| [Object][9])** background colour, parsed by the [color][11] module, defaults to black without transparency. (optional, default `{r:0,g:0,b:0,alpha:1}`) - `extend.background` **([String][10] \| [Object][9])** background colour, parsed by the [color][11] module, defaults to black without transparency. (optional, default `{r:0,g:0,b:0,alpha:1}`)
### Examples ### Examples
@@ -164,7 +166,7 @@ Returns **Sharp**
## extract ## extract
Extract a region of the image. Extract/crop a region of the image.
- Use `extract` before `resize` for pre-resize extraction. - Use `extract` before `resize` for pre-resize extraction.
- Use `extract` after `resize` for post-resize extraction. - Use `extract` after `resize` for post-resize extraction.
@@ -173,10 +175,10 @@ Extract a region of the image.
### Parameters ### Parameters
- `options` **[Object][9]** describes the region to extract using integral pixel values - `options` **[Object][9]** describes the region to extract using integral pixel values
- `options.left` **[Number][8]** zero-indexed offset from left edge - `options.left` **[number][8]** zero-indexed offset from left edge
- `options.top` **[Number][8]** zero-indexed offset from top edge - `options.top` **[number][8]** zero-indexed offset from top edge
- `options.width` **[Number][8]** width of region to extract - `options.width` **[number][8]** width of region to extract
- `options.height` **[Number][8]** height of region to extract - `options.height` **[number][8]** height of region to extract
### Examples ### Examples
@@ -211,7 +213,7 @@ The `info` response Object will contain `trimOffsetLeft` and `trimOffsetTop` pro
### Parameters ### Parameters
- `threshold` **[Number][8]** the allowed difference from the top-left pixel, a number greater than zero. (optional, default `10`) - `threshold` **[number][8]** the allowed difference from the top-left pixel, a number greater than zero. (optional, default `10`)
- Throws **[Error][13]** Invalid parameters - Throws **[Error][13]** Invalid parameters

View File

@@ -31,10 +31,10 @@ useful for determining how much working memory is required for a particular task
### Parameters ### Parameters
- `options` **([Object][1] \| [Boolean][2])** Object with the following attributes, or Boolean where true uses default cache settings and false removes all caching (optional, default `true`) - `options` **([Object][1] \| [boolean][2])** Object with the following attributes, or boolean where true uses default cache settings and false removes all caching (optional, default `true`)
- `options.memory` **[Number][3]** is the maximum memory in MB to use for this cache (optional, default `50`) - `options.memory` **[number][3]** is the maximum memory in MB to use for this cache (optional, default `50`)
- `options.files` **[Number][3]** is the maximum number of files to hold open (optional, default `20`) - `options.files` **[number][3]** is the maximum number of files to hold open (optional, default `20`)
- `options.items` **[Number][3]** is the maximum number of operations to cache (optional, default `100`) - `options.items` **[number][3]** is the maximum number of operations to cache (optional, default `100`)
### Examples ### Examples
@@ -64,7 +64,7 @@ This method always returns the current concurrency.
### Parameters ### Parameters
- `concurrency` **[Number][3]?** - `concurrency` **[number][3]?**
### Examples ### Examples
@@ -74,7 +74,7 @@ sharp.concurrency(2); // 2
sharp.concurrency(0); // 4 sharp.concurrency(0); // 4
``` ```
Returns **[Number][3]** concurrency Returns **[number][3]** concurrency
## queue ## queue
@@ -116,7 +116,7 @@ by taking advantage of the SIMD vector unit of the CPU, e.g. Intel SSE and ARM N
### Parameters ### Parameters
- `simd` **[Boolean][2]** (optional, default `true`) - `simd` **[boolean][2]** (optional, default `true`)
### Examples ### Examples
@@ -130,7 +130,7 @@ const simd = sharp.simd(false);
// prevent libvips from using liborc at runtime // prevent libvips from using liborc at runtime
``` ```
Returns **[Boolean][2]** Returns **[boolean][2]**
[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object [1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object

View File

@@ -1,5 +1,117 @@
# Changelog # Changelog
## v0.26 - *zoom*
Requires libvips v8.10.0
### v0.26.0 - TBD
* Prebuilt libvips binaries are now statically-linked and Brotli-compressed, requiring Node.js 10.16.0+.
* TIFF output `squash` is replaced by `bitdepth` to reduce to 1, 2 or 4 bit.
* JPEG output `quality` >= 90 no longer automatically sets `chromaSubsampling` to `4:4:4`.
* Add most `dominant` colour to image `stats`.
[#640](https://github.com/lovell/sharp/issues/640)
* Add support for animated GIF (requires \*magick) and WebP output.
[#2012](https://github.com/lovell/sharp/pull/2012)
[@deftomat](https://github.com/deftomat)
* Add support for libvips ImageMagick v7 loaders.
[#2258](https://github.com/lovell/sharp/pull/2258)
[@vouillon](https://github.com/vouillon)
* Allow multi-page input via \*magick.
[#2259](https://github.com/lovell/sharp/pull/2259)
[@vouillon](https://github.com/vouillon)
* Add support to `withMetadata` for custom ICC profile.
[#2271](https://github.com/lovell/sharp/pull/2271)
[@roborourke](https://github.com/roborourke)
* Ensure prebuilt binaries for ARM default to v7 when using Electron.
[#2292](https://github.com/lovell/sharp/pull/2292)
[@diegodev3](https://github.com/diegodev3)
## v0.25 - *yield*
Requires libvips v8.9.1
### v0.25.4 - 12th June 2020
* Allow libvips binary location override where version is appended.
[#2217](https://github.com/lovell/sharp/pull/2217)
[@malice00](https://github.com/malice00)
* Enable PNG palette when setting quality, colours, colors or dither.
[#2226](https://github.com/lovell/sharp/pull/2226)
[@romaleev](https://github.com/romaleev)
* Add `level` constructor option to use a specific level of a multi-level image.
Expose `levels` metadata for multi-level images.
[#2222](https://github.com/lovell/sharp/issues/2222)
* Add support for named `alpha` channel to `extractChannel` operation.
[#2138](https://github.com/lovell/sharp/issues/2138)
* Add experimental `sharpness` calculation to `stats()` response.
[#2251](https://github.com/lovell/sharp/issues/2251)
* Emit `warning` event for non-critical processing problems.
[#2032](https://github.com/lovell/sharp/issues/2032)
### v0.25.3 - 17th May 2020
* Ensure libvips is initialised only once, improves worker thread safety.
[#2143](https://github.com/lovell/sharp/issues/2143)
* Ensure npm platform flag is respected when copying DLLs.
[#2188](https://github.com/lovell/sharp/pull/2188)
[@dimadeveatii](https://github.com/dimadeveatii)
* Allow SVG input with large inline images to be parsed.
[#2195](https://github.com/lovell/sharp/issues/2195)
### v0.25.2 - 20th March 2020
* Provide prebuilt binaries for Linux ARM64v8.
* Add IIIF layout support to tile-based output.
[#2098](https://github.com/lovell/sharp/pull/2098)
[@edsilv](https://github.com/edsilv)
* Ensure input options are consistently and correctly detected.
[#2118](https://github.com/lovell/sharp/issues/2118)
* Ensure N-API prebuilt binaries work on RHEL7 and its derivatives.
[#2119](https://github.com/lovell/sharp/issues/2119)
* Ensure AsyncWorker options are persisted.
[#2130](https://github.com/lovell/sharp/issues/2130)
### v0.25.1 - 7th March 2020
* Ensure prebuilt binaries are fetched based on N-API version.
[#2117](https://github.com/lovell/sharp/issues/2117)
### v0.25.0 - 7th March 2020
* Remove `limitInputPixels` and `sequentialRead` previously deprecated in v0.24.0.
* Migrate internals to N-API.
[#1282](https://github.com/lovell/sharp/issues/1282)
* Add support for 32-bit Windows.
[#2088](https://github.com/lovell/sharp/issues/2088)
* Ensure correct ordering of rotate-then-trim operations.
[#2087](https://github.com/lovell/sharp/issues/2087)
* Ensure composite accepts `limitInputPixels` and `sequentialRead` input options.
[#2099](https://github.com/lovell/sharp/issues/2099)
## v0.24 - "*wit*" ## v0.24 - "*wit*"
Requires libvips v8.9.0. Requires libvips v8.9.0.

View File

@@ -4,9 +4,10 @@
"public": ".", "public": ".",
"ignore": [ "ignore": [
".*", ".*",
"*.json", "firebase.json",
"*.md", "*.md",
"image/**" "image/**",
"search-index/**"
], ],
"headers": [ "headers": [
{ {

199
docs/humans.txt Normal file
View File

@@ -0,0 +1,199 @@
/* THANKS */
Name: John Cupitt
GitHub: https://github.com/jcupitt
Name: Pierre Inglebert
GitHub: https://github.com/pierreinglebert
Name: Jonathan Ong
GitHub: https://github.com/jonathanong
Name: Chanon Sajjamanochai
GitHub: https://github.com/chanon
Name: Juliano Julio
GitHub: https://github.com/julianojulio
Name: Daniel Gasienica
GitHub: https://github.com/gasi
Name: Julian Walker
GitHub: https://github.com/julianwa
Name: Amit Pitaru
GitHub: https://github.com/apitaru
Name: Brandon Aaron
GitHub: https://github.com/brandonaaron
Name: Andreas Lind
GitHub: https://github.com/papandreouGitHub:
Name: Maurus Cuelenaere
GitHub: https://github.com/mcuelenaere
Name: Linus Unnebäck
GitHub: https://github.com/LinusU
Name: Victor Mateevitsi
GitHub: https://github.com/mvictoras
Name: Alaric Holloway
GitHub: https://github.com/skedastik
Name: Bernhard K. Weisshuhn
GitHub: https://github.com/bkw
Name: David A. Carley
GitHub: https://github.com/dacarley
Name: John Tobin
GitHub: https://github.com/jtobinisaniceguy
Name: Kenton Gray
GitHub: https://github.com/kentongray
Name: Felix Bünemann
GitHub: https://github.com/felixbuenemann
Name: Samy Al Zahrani
GitHub: https://github.com/salzhrani
Name: Chintan Thakkar
GitHub: https://github.com/lemnisk8
Name: F. Orlando Galashan
GitHub: https://github.com/frulo
Name: Kleis Auke Wolthuizen
GitHub: https://github.com/kleisauke
Name: Matt Hirsch
GitHub: https://github.com/mhirsch
Name: Rahul Nanwani
GitHub: https://github.com/rnanwani
Name: Matthias Thoemmes
GitHub: https://github.com/cmtt
Name: Patrick Paskaris
GitHub: https://github.com/ppaskaris
Name: Jérémy Lal
GitHub: https://github.com/kapouer
Name: Alice Monday
GitHub: https://github.com/alice0meta
Name: Kristo Jorgenson
GitHub: https://github.com/kristojorg
Name: Yves Bos
GitHub: https://github.com/YvesBos
Name: Nicolas Coden
GitHub: https://github.com/ncoden
Name: Matt Parrish
GitHub: https://github.com/pbomb
Name: Matthew McEachen
GitHub: https://github.com/mceachen
Name: Jarda Kotěšovec
GitHub: https://github.com/jardakotesovec
Name: Kenric D'Souza
GitHub: https://github.com/AzureByte
Name: Oleh Aleinyk
GitHub: https://github.com/oaleynik
Name: Marcel Bretschneider
GitHub: https://github.com/3epnm
Name: Andrea Bianco
GitHub: https://github.com/BiancoA
Name: Rik Heywood
GitHub: https://github.com/rikh42
Name: Thomas Parisot
GitHub: https://github.com/oncletom
Name: Nathan Graves
GitHub: https://github.com/woolite64
Name: Tom Lokhorst
GitHub: https://github.com/tomlokhorst
Name: Espen Hovlandsdal
GitHub: https://github.com/rexxars
Name: Sylvain Dumont
GitHub: https://github.com/sylvaindumont
Name: Alun Davies
GitHub: https://github.com/alundavies
Name: Aidan Hoolachan
GitHub: https://github.com/ajhool
Name: Axel Eirola
GitHub: https://github.com/aeirola
Name: Freezy
GitHub: https://github.com/freezy
Name: Julian Aubourg
GitHub: https://github.com/jaubourg
Name: Keith Belovay
GitHub: https://github.com/fromkeith
Name: Michael B. Klein
GitHub: https://github.com/mbklein
Name: Jakub Michálek
GitHub: https://github.com/Goues
Name: Ilya Ovdin
GitHub: https://github.com/iovdin
Name: Andargor
GitHub: https://github.com/Andargor
Name: Nicolas Stepien
GitHub: https://github.com/MayhemYDG
Name: Paul Neave
GitHub: https://github.com/neave
Name: Brendan Kennedy
GitHub: https://github.com/rustyguts
Name: Brychan Bennett-Odlum
GitHub: https://github.com/BrychanOdlum
Name: Edward Silverton
GitHub: https://github.com/edsilv
Name: Dumitru Deveatii
GitHub: https://github.com/dimadeveatii
Name: Roland Asmann
GitHub: https://github.com/malice00
Name: Roman Malieiev
GitHub: https://github.com/romaleev
Name: Jerome Vouillon
GitHub: https://github.com/vouillon
Name: Tomáš Szabo
GitHub: https://github.com/deftomat
Name: Robert O'Rourke
GitHub: https://github.com/roborourke

File diff suppressed because one or more lines are too long

View File

@@ -10,18 +10,19 @@ yarn add sharp
## Prerequisites ## Prerequisites
* Node.js v10.13.0+ * Node.js v10.16.0+
## Prebuilt binaries ## Prebuilt binaries
Ready-compiled sharp and libvips binaries are provided for use with Ready-compiled sharp and libvips binaries are provided for use with
Node.js versions 10, 12 and 13 on the most common platforms: Node.js v10.16.0+ on the most common platforms:
* macOS x64 (>= 10.13) * macOS x64 (>= 10.13)
* Linux x64 (glibc >= 2.17, musl >= 1.1.24) * Linux x64 (glibc >= 2.17, musl >= 1.1.24)
* Windows x64 with 64-bit `node.exe` * Linux ARM64 (glibc >= 2.29)
* Windows
A ~10MB tarball containing libvips and its most commonly used dependencies A ~7MB tarball containing libvips and its most commonly used dependencies
is downloaded via HTTPS and stored within `node_modules/sharp/vendor` during `npm install`. is downloaded via HTTPS and stored within `node_modules/sharp/vendor` during `npm install`.
This provides support for the This provides support for the
@@ -30,8 +31,7 @@ JPEG, PNG, WebP, TIFF, GIF (input) and SVG (input) image formats.
The following platforms have prebuilt libvips but not sharp: The following platforms have prebuilt libvips but not sharp:
* Linux ARMv6 * Linux ARMv6
* Linux ARMv7 * Linux ARMv7 (glibc >= 2.28)
* Linux ARM64 (glibc >= 2.29)
The following platforms require compilation of both libvips and sharp from source: The following platforms require compilation of both libvips and sharp from source:
@@ -42,15 +42,10 @@ The following platforms require compilation of both libvips and sharp from sourc
* FreeBSD * FreeBSD
* OpenBSD * OpenBSD
The following platforms are completely unsupported:
* Windows x86
* Windows x64 with 32-bit `node.exe`
## Common problems ## Common problems
The platform and major version of Node.js used for `npm install` The architecture and platform of Node.js used for `npm install`
must be the same as the platform and major version of Node.js used at runtime. must be the same as the architecture and platform of Node.js used at runtime.
The `npm install --unsafe-perm` flag must be used when installing as `root` or a `sudo` user. The `npm install --unsafe-perm` flag must be used when installing as `root` or a `sudo` user.
@@ -79,31 +74,48 @@ This module will be compiled from source at `npm install` time when:
Building from source requires: Building from source requires:
* C++11 compatible compiler such as gcc 4.8+, clang 3.0+ or MSVC 2013+ * C++11 compiler
* [node-gyp](https://github.com/nodejs/node-gyp#installation) and its dependencies (includes Python 2.7) * [node-gyp](https://github.com/nodejs/node-gyp#installation) and its dependencies
## Custom prebuilt binaries ## Custom prebuilt binaries
This is an advanced approach that most people will not require. This is an advanced approach that most people will not require.
To install the prebuilt libvips binaries from a custom URL, To install the prebuilt sharp binaries from a custom URL,
set the `sharp_dist_base_url` npm config option set the `sharp_binary_host` npm config option
or the `SHARP_DIST_BASE_URL` environment variable. or the `npm_config_sharp_binary_host` environment variable.
For example, both of the following will result in an attempt to download the file located at To install the prebuilt libvips binaries from a custom URL,
`https://hostname/path/libvips-x.y.z-platform.tar.gz`. set the `sharp_libvips_binary_host` npm config option
or the `npm_config_sharp_libvips_binary_host` environment variable.
The version subpath and file name are appended to these.
For example, if `sharp_libvips_binary_host` is set to `https://hostname/path`
and the libvips version is `1.2.3` then the resultant URL will be
`https://hostname/path/v1.2.3/libvips-1.2.3-platform-arch.tar.br`.
See the Chinese mirror below for a further example.
## Chinese mirror
Alibaba provide a mirror site based in China containing binaries for both sharp and libvips.
To use this either set the following configuration:
```sh ```sh
npm config set sharp_dist_base_url "https://hostname/path/" 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 install sharp npm install sharp
``` ```
```sh or set the following environment variables:
SHARP_DIST_BASE_URL="https://hostname/path/" npm install sharp
```
To install the prebuilt sharp binaries from a custom URL, please see ```sh
[https://github.com/prebuild/prebuild-install#custom-binaries](https://github.com/prebuild/prebuild-install#custom-binaries) 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 install sharp
```
## FreeBSD ## FreeBSD
@@ -129,24 +141,24 @@ to `false` when using the `yarn` package manager.
## AWS Lambda ## AWS Lambda
Set the Lambda runtime to `nodejs10.x`. Set the Lambda runtime to `nodejs12.x`.
The binaries in the `node_modules` directory of the The binaries in the `node_modules` directory of the
[deployment package](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-create-deployment-pkg.html) [deployment package](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-package.html)
must be for the Linux x64 platform. must be for the Linux x64 platform.
On machines other than Linux x64, such as macOS and Windows, run the following: On machines other than Linux x64, such as macOS and Windows, run the following:
```sh ```sh
rm -rf node_modules/sharp rm -rf node_modules/sharp
npm install --arch=x64 --platform=linux --target=10.15.0 sharp npm install --arch=x64 --platform=linux sharp
``` ```
Alternatively a Docker container closely matching the Lambda runtime can be used: Alternatively a Docker container closely matching the Lambda runtime can be used:
```sh ```sh
rm -rf node_modules/sharp rm -rf node_modules/sharp
docker run -v "$PWD":/var/task lambci/lambda:build-nodejs10.x npm install sharp docker run -v "$PWD":/var/task lambci/lambda:build-nodejs12.x npm install sharp
``` ```
To get the best performance select the largest memory available. To get the best performance select the largest memory available.
@@ -165,3 +177,10 @@ npx electron-rebuild
Further help can be found at Further help can be found at
[https://electronjs.org/docs/tutorial/using-native-node-modules](https://electronjs.org/docs/tutorial/using-native-node-modules) [https://electronjs.org/docs/tutorial/using-native-node-modules](https://electronjs.org/docs/tutorial/using-native-node-modules)
## Worker threads
The main thread must call `require('sharp')`
before worker threads are created
to ensure shared libraries remain loaded in memory
until after all threads are complete.

2
docs/robots.txt Normal file
View File

@@ -0,0 +1,2 @@
User-agent: *
Disallow:

1
docs/search-index.json Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,59 @@
'use strict';
const fs = require('fs');
const { extractDescription, extractKeywords } = require('./extract');
const searchIndex = [];
// Install
const contents = fs.readFileSync(`${__dirname}/../install.md`, 'utf8');
const matches = contents.matchAll(
/## (?<title>[A-Za-z ]+)\n\n(?<body>[^#]+)/gs
);
for (const match of matches) {
const { title, body } = match.groups;
const description = extractDescription(body);
searchIndex.push({
t: title,
d: description,
k: extractKeywords(`${title} ${description}`),
l: `/install#${title.toLowerCase().replace(/ /g, '-')}`
});
}
// API
[
'constructor',
'input',
'output',
'resize',
'composite',
'operation',
'channel',
'colour',
'utility'
].forEach((section) => {
const contents = fs.readFileSync(`${__dirname}/../api-${section}.md`, 'utf8');
const matches = contents.matchAll(
/\n## (?<title>[A-Za-z]+)\n\n(?<firstparagraph>.+?)\n\n/gs
);
for (const match of matches) {
const { title, firstparagraph } = match.groups;
const description = firstparagraph.startsWith('###')
? 'Constructor'
: extractDescription(firstparagraph);
searchIndex.push({
t: title,
d: description,
k: extractKeywords(`${title} ${description}`),
l: `/api-${section}#${title.toLowerCase()}`
});
}
});
fs.writeFileSync(
`${__dirname}/../search-index.json`,
JSON.stringify(searchIndex)
);

View File

@@ -0,0 +1,80 @@
'use strict';
const stopWords = [
'a',
'about',
'all',
'already',
'always',
'an',
'and',
'any',
'are',
'as',
'at',
'be',
'been',
'by',
'can',
'do',
'does',
'each',
'either',
'etc',
'for',
'from',
'get',
'gets',
'has',
'have',
'how',
'if',
'in',
'is',
'it',
'its',
'may',
'more',
'much',
'no',
'not',
'of',
'on',
'or',
'over',
'set',
'sets',
'should',
'that',
'the',
'their',
'there',
'therefore',
'these',
'this',
'to',
'use',
'using',
'when',
'which',
'will',
'with'
];
const extractDescription = (str) =>
str
.replace(/\(http[^)]+/g, '')
.replace(/\s+/g, ' ')
.replace(/[^A-Za-z0-9_/\-,. ]/g, '')
.replace(/\s+/g, ' ')
.substr(0, 140)
.trim();
const extractKeywords = (str) =>
str
.split(/[ -/]/)
.map((word) => word.toLowerCase().replace(/[^a-z]/g, ''))
.filter((word) => word.length > 2 && !stopWords.includes(word))
.join(' ');
module.exports = { extractDescription, extractKeywords };

View File

@@ -6,7 +6,10 @@ const path = require('path');
const libvips = require('../lib/libvips'); const libvips = require('../lib/libvips');
const npmLog = require('npmlog'); const npmLog = require('npmlog');
if (process.platform === 'win32') { const minimumLibvipsVersion = libvips.minimumLibvipsVersion;
const platform = process.env.npm_config_platform || process.platform;
if (platform === 'win32') {
const buildDir = path.join(__dirname, '..', 'build'); const buildDir = path.join(__dirname, '..', 'build');
const buildReleaseDir = path.join(buildDir, 'Release'); const buildReleaseDir = path.join(buildDir, 'Release');
npmLog.info('sharp', `Creating ${buildReleaseDir}`); npmLog.info('sharp', `Creating ${buildReleaseDir}`);
@@ -14,7 +17,7 @@ if (process.platform === 'win32') {
libvips.mkdirSync(buildDir); libvips.mkdirSync(buildDir);
libvips.mkdirSync(buildReleaseDir); libvips.mkdirSync(buildReleaseDir);
} catch (err) {} } catch (err) {}
const vendorLibDir = path.join(__dirname, '..', 'vendor', 'lib'); const vendorLibDir = path.join(__dirname, '..', 'vendor', minimumLibvipsVersion, 'lib');
npmLog.info('sharp', `Copying DLLs from ${vendorLibDir} to ${buildReleaseDir}`); npmLog.info('sharp', `Copying DLLs from ${vendorLibDir} to ${buildReleaseDir}`);
try { try {
fs fs

View File

@@ -3,19 +3,28 @@
const fs = require('fs'); const fs = require('fs');
const os = require('os'); const os = require('os');
const path = require('path'); const path = require('path');
const stream = require('stream');
const zlib = require('zlib');
const detectLibc = require('detect-libc'); const detectLibc = require('detect-libc');
const npmLog = require('npmlog'); const npmLog = require('npmlog');
const semver = require('semver'); const semver = require('semver');
const simpleGet = require('simple-get'); const simpleGet = require('simple-get');
const tar = require('tar'); const tarFs = require('tar-fs');
const agent = require('../lib/agent'); const agent = require('../lib/agent');
const libvips = require('../lib/libvips'); const libvips = require('../lib/libvips');
const platform = require('../lib/platform'); const platform = require('../lib/platform');
const minimumLibvipsVersion = libvips.minimumLibvipsVersion; const minimumGlibcVersionByArch = {
const distBaseUrl = process.env.npm_config_sharp_dist_base_url || process.env.SHARP_DIST_BASE_URL || `https://github.com/lovell/sharp-libvips/releases/download/v${minimumLibvipsVersion}/`; arm: '2.28',
arm64: '2.29',
x64: '2.17'
};
const { minimumLibvipsVersion, minimumLibvipsVersionLabelled } = libvips;
const distHost = process.env.npm_config_sharp_libvips_binary_host || 'https://github.com/lovell/sharp-libvips/releases/download';
const distBaseUrl = process.env.npm_config_sharp_dist_base_url || process.env.SHARP_DIST_BASE_URL || `${distHost}/v${minimumLibvipsVersionLabelled}/`;
const fail = function (err) { const fail = function (err) {
npmLog.error('sharp', err.message); npmLog.error('sharp', err.message);
@@ -30,18 +39,21 @@ const fail = function (err) {
const extractTarball = function (tarPath) { const extractTarball = function (tarPath) {
const vendorPath = path.join(__dirname, '..', 'vendor'); const vendorPath = path.join(__dirname, '..', 'vendor');
libvips.mkdirSync(vendorPath); libvips.mkdirSync(vendorPath);
tar const versionedVendorPath = path.join(vendorPath, minimumLibvipsVersion);
.extract({ libvips.mkdirSync(versionedVendorPath);
file: tarPath, stream.pipeline(
cwd: vendorPath, fs.createReadStream(tarPath),
strict: true new zlib.BrotliDecompress(),
}) tarFs.extract(versionedVendorPath),
.catch(function (err) { function (err) {
if (/unexpected end of file/.test(err.message)) { if (err) {
npmLog.error('sharp', `Please delete ${tarPath} as it is not a valid tarball`); if (/unexpected end of file/.test(err.message)) {
npmLog.error('sharp', `Please delete ${tarPath} as it is not a valid tarball`);
}
fail(err);
} }
fail(err); }
}); );
}; };
try { try {
@@ -57,23 +69,19 @@ try {
// Is this arch/platform supported? // Is this arch/platform supported?
const arch = process.env.npm_config_arch || process.arch; const arch = process.env.npm_config_arch || process.arch;
const platformAndArch = platform(); const platformAndArch = platform();
if (platformAndArch === 'win32-ia32') { if (arch === 'ia32' && !platformAndArch.startsWith('win32')) {
throw new Error('Windows x86 (32-bit) node.exe is not supported');
}
if (arch === 'ia32') {
throw new Error(`Intel Architecture 32-bit systems require manual installation of libvips >= ${minimumLibvipsVersion}`); throw new Error(`Intel Architecture 32-bit systems require manual installation of libvips >= ${minimumLibvipsVersion}`);
} }
if (platformAndArch === 'freebsd-x64' || platformAndArch === 'openbsd-x64' || platformAndArch === 'sunos-x64') { if (platformAndArch === 'freebsd-x64' || platformAndArch === 'openbsd-x64' || platformAndArch === 'sunos-x64') {
throw new Error(`BSD/SunOS systems require manual installation of libvips >= ${minimumLibvipsVersion}`); throw new Error(`BSD/SunOS systems require manual installation of libvips >= ${minimumLibvipsVersion}`);
} }
if (detectLibc.family === detectLibc.GLIBC && detectLibc.version) { if (detectLibc.family === detectLibc.GLIBC && detectLibc.version) {
const minimumGlibcVersion = arch === 'arm64' ? '2.29.0' : '2.17.0'; if (semver.lt(`${detectLibc.version}.0`, `${minimumGlibcVersionByArch[arch]}.0`)) {
if (semver.lt(`${detectLibc.version}.0`, minimumGlibcVersion)) {
throw new Error(`Use with glibc ${detectLibc.version} requires manual installation of libvips >= ${minimumLibvipsVersion}`); throw new Error(`Use with glibc ${detectLibc.version} requires manual installation of libvips >= ${minimumLibvipsVersion}`);
} }
} }
// Download to per-process temporary file // Download to per-process temporary file
const tarFilename = ['libvips', minimumLibvipsVersion, platformAndArch].join('-') + '.tar.gz'; const tarFilename = ['libvips', minimumLibvipsVersion, platformAndArch].join('-') + '.tar.br';
const tarPathCache = path.join(libvips.cachePath(), tarFilename); const tarPathCache = path.join(libvips.cachePath(), tarFilename);
if (fs.existsSync(tarPathCache)) { if (fs.existsSync(tarPathCache)) {
npmLog.info('sharp', `Using cached ${tarPathCache}`); npmLog.info('sharp', `Using cached ${tarPathCache}`);

12
install/prebuild-ci.js Normal file
View File

@@ -0,0 +1,12 @@
'use strict';
const { spawnSync } = require('child_process');
const { prebuild_upload: hasToken, APPVEYOR_REPO_TAG_NAME, TRAVIS_TAG } = process.env;
if (hasToken && (APPVEYOR_REPO_TAG_NAME || TRAVIS_TAG)) {
spawnSync('node',
['./node_modules/prebuild/bin.js', '--runtime', 'napi', '--target', '3'],
{ shell: true, stdio: 'inherit' }
);
}

View File

@@ -54,27 +54,25 @@ function ensureAlpha () {
* @example * @example
* sharp(input) * sharp(input)
* .extractChannel('green') * .extractChannel('green')
* .toFile('input_green.jpg', function(err, info) { * .toColourspace('b-w')
* .toFile('green.jpg', function(err, info) {
* // info.channels === 1 * // info.channels === 1
* // input_green.jpg contains the green channel of the input image * // green.jpg is a greyscale image containing the green channel of the input
* }); * });
* *
* @param {Number|String} channel - zero-indexed band number to extract, or `red`, `green` or `blue` as alternative to `0`, `1` or `2` respectively. * @param {number|string} channel - zero-indexed channel/band number to extract, or `red`, `green`, `blue` or `alpha`.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid channel * @throws {Error} Invalid channel
*/ */
function extractChannel (channel) { function extractChannel (channel) {
if (channel === 'red') { const channelMap = { red: 0, green: 1, blue: 2, alpha: 3 };
channel = 0; if (Object.keys(channelMap).includes(channel)) {
} else if (channel === 'green') { channel = channelMap[channel];
channel = 1;
} else if (channel === 'blue') {
channel = 2;
} }
if (is.integer(channel) && is.inRange(channel, 0, 4)) { if (is.integer(channel) && is.inRange(channel, 0, 4)) {
this.options.extractChannel = channel; this.options.extractChannel = channel;
} else { } else {
throw is.invalidParameterError('channel', 'integer or one of: red, green, blue', channel); throw is.invalidParameterError('channel', 'integer or one of: red, green, blue, alpha', channel);
} }
return this; return this;
} }
@@ -90,7 +88,7 @@ function extractChannel (channel) {
* Buffers may be any of the image formats supported by sharp: JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data. * Buffers may be any of the image formats supported by sharp: JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data.
* For raw pixel input, the `options` object should contain a `raw` attribute, which follows the format of the attribute of the same name in the `sharp()` constructor. * For raw pixel input, the `options` object should contain a `raw` attribute, which follows the format of the attribute of the same name in the `sharp()` constructor.
* *
* @param {Array<String|Buffer>|String|Buffer} images - one or more images (file paths, Buffers). * @param {Array<string|Buffer>|string|Buffer} images - one or more images (file paths, Buffers).
* @param {Object} options - image options, see `sharp()` constructor. * @param {Object} options - image options, see `sharp()` constructor.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
@@ -118,7 +116,7 @@ function joinChannel (images, options) {
* // then `O(1,1) = 0b11110111 & 0b10101010 & 0b00001111 = 0b00000010 = 2`. * // then `O(1,1) = 0b11110111 & 0b10101010 & 0b00001111 = 0b00000010 = 2`.
* }); * });
* *
* @param {String} boolOp - one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively. * @param {string} boolOp - one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */

View File

@@ -19,7 +19,7 @@ const colourspace = {
* Tint the image using the provided chroma while preserving the image luminance. * 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. * An alpha channel may be present and will be unchanged by the operation.
* *
* @param {String|Object} rgb - parsed by the [color](https://www.npmjs.org/package/color) module to extract chroma values. * @param {string|Object} rgb - parsed by the [color](https://www.npmjs.org/package/color) module to extract chroma values.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameter * @throws {Error} Invalid parameter
*/ */
@@ -57,7 +57,7 @@ function grayscale (grayscale) {
/** /**
* Set the output colourspace. * Set the output colourspace.
* By default output image will be web-friendly sRGB, with additional channels interpreted as alpha channels. * By default output image will be web-friendly sRGB, with additional channels interpreted as alpha channels.
* @param {String} [colourspace] - output colourspace e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://github.com/libvips/libvips/blob/master/libvips/iofuncs/enumtypes.c#L568) * @param {string} [colourspace] - output colourspace e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://github.com/libvips/libvips/blob/master/libvips/iofuncs/enumtypes.c#L568)
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -71,7 +71,7 @@ function toColourspace (colourspace) {
/** /**
* Alternative spelling of `toColourspace`. * Alternative spelling of `toColourspace`.
* @param {String} [colorspace] - output colorspace. * @param {string} [colorspace] - output colorspace.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -82,8 +82,8 @@ function toColorspace (colorspace) {
/** /**
* Update a colour attribute of the this.options Object. * Update a colour attribute of the this.options Object.
* @private * @private
* @param {String} key * @param {string} key
* @param {String|Object} value * @param {string|Object} value
* @throws {Error} Invalid value * @throws {Error} Invalid value
*/ */
function _setBackgroundColourOption (key, value) { function _setBackgroundColourOption (key, value) {

View File

@@ -72,7 +72,7 @@ const blend = {
* }); * });
* *
* @param {Object[]} images - Ordered list of images to composite * @param {Object[]} images - Ordered list of images to composite
* @param {Buffer|String} [images[].input] - Buffer containing image data, String containing the path to an image file, or Create object (see bellow) * @param {Buffer|String} [images[].input] - Buffer containing image data, String containing the path to an image file, or Create object (see below)
* @param {Object} [images[].input.create] - describes a blank overlay to be created. * @param {Object} [images[].input.create] - describes a blank overlay to be created.
* @param {Number} [images[].input.create.width] * @param {Number} [images[].input.create.width]
* @param {Number} [images[].input.create.height] * @param {Number} [images[].input.create.height]
@@ -100,8 +100,7 @@ function composite (images) {
if (!is.object(image)) { if (!is.object(image)) {
throw is.invalidParameterError('image to composite', 'object', image); throw is.invalidParameterError('image to composite', 'object', image);
} }
const { raw, density } = image; const inputOptions = this._inputOptionsFromObject(image);
const inputOptions = (raw || density) ? { raw, density } : undefined;
const composite = { const composite = {
input: this._createInputDescriptor(image.input, inputOptions, { allowStream: false }), input: this._createInputDescriptor(image.input, inputOptions, { allowStream: false }),
blend: 'over', blend: 'over',

View File

@@ -38,15 +38,20 @@ try {
const debuglog = util.debuglog('sharp'); const debuglog = util.debuglog('sharp');
/** /**
* @constructs sharp
*
* Constructor factory to create an instance of `sharp`, to which further methods are chained. * Constructor factory to create an instance of `sharp`, to which further methods are chained.
* *
* JPEG, PNG, WebP or TIFF format image data can be streamed out from this object. * JPEG, PNG, WebP 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. * When using Stream based output, derived attributes are available from the `info` event.
* *
* Non-critical problems encountered during processing are emitted as `warning` events.
*
* Implements the [stream.Duplex](http://nodejs.org/api/stream.html#stream_class_stream_duplex) class. * Implements the [stream.Duplex](http://nodejs.org/api/stream.html#stream_class_stream_duplex) class.
* *
* @constructs Sharp
*
* @emits Sharp#info
* @emits Sharp#warning
*
* @example * @example
* sharp('input.jpg') * sharp('input.jpg')
* .resize(300, 200) * .resize(300, 200)
@@ -81,30 +86,36 @@ const debuglog = util.debuglog('sharp');
* .toBuffer() * .toBuffer()
* .then( ... ); * .then( ... );
* *
* @param {(Buffer|String)} [input] - if present, can be * @example
* // Convert an animated GIF to an animated WebP
* await sharp('in.gif', { animated: true }).toFile('out.webp');
*
* @param {(Buffer|string)} [input] - if present, can be
* a Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or * a Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or
* a String containing the filesystem path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file. * a String containing the filesystem path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file.
* JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present. * JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present.
* @param {Object} [options] - if present, is an Object with optional attributes. * @param {Object} [options] - if present, is an Object with optional attributes.
* @param {Boolean} [options.failOnError=true] - by default halt processing and raise an error when loading invalid images. * @param {boolean} [options.failOnError=true] - by default halt processing and raise an error when loading invalid images.
* Set this flag to `false` if you'd rather apply a "best effort" to decode images, even if the data is corrupt or invalid. * Set this flag to `false` if you'd rather apply a "best effort" to decode images, even if the data is corrupt or invalid.
* @param {Number|Boolean} [options.limitInputPixels=268402689] - Do not process input images where the number of pixels * @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. * (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). * An integral Number of pixels, zero or false to remove limit, true to use default limit of 268402689 (0x3FFF x 0x3FFF).
* @param {Boolean} [options.sequentialRead=false] - Set this to `true` to use sequential rather than random access where possible. * @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. * This can reduce memory usage and might improve performance on some systems.
* @param {Number} [options.density=72] - number representing the DPI for vector images. * @param {number} [options.density=72] - number representing the DPI for vector images.
* @param {Number} [options.pages=1] - number of pages to extract for multi-page input (GIF, TIFF, PDF), use -1 for all pages. * @param {number} [options.pages=1] - number of pages to extract for multi-page input (GIF, TIFF, PDF), use -1 for all pages.
* @param {Number} [options.page=0] - page number to start extracting from for multi-page input (GIF, TIFF, PDF), zero based. * @param {number} [options.page=0] - page number to start extracting from for multi-page input (GIF, TIFF, PDF), zero based.
* @param {number} [options.level=0] - level to extract from a multi-level input (OpenSlide), zero based.
* @param {boolean} [options.animated=false] - Set to `true` to read all frames/pages of an animated image (equivalent of setting `pages` to `-1`).
* @param {Object} [options.raw] - describes raw pixel input image data. See `raw()` for pixel ordering. * @param {Object} [options.raw] - describes raw pixel input image data. See `raw()` for pixel ordering.
* @param {Number} [options.raw.width] * @param {number} [options.raw.width]
* @param {Number} [options.raw.height] * @param {number} [options.raw.height]
* @param {Number} [options.raw.channels] - 1-4 * @param {number} [options.raw.channels] - 1-4
* @param {Object} [options.create] - describes a new image to be created. * @param {Object} [options.create] - describes a new image to be created.
* @param {Number} [options.create.width] * @param {number} [options.create.width]
* @param {Number} [options.create.height] * @param {number} [options.create.height]
* @param {Number} [options.create.channels] - 3-4 * @param {number} [options.create.channels] - 3-4
* @param {String|Object} [options.create.background] - parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha. * @param {string|Object} [options.create.background] - parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -163,7 +174,7 @@ const Sharp = function (input, options) {
gamma: 0, gamma: 0,
gammaOut: 0, gammaOut: 0,
greyscale: false, greyscale: false,
normalise: 0, normalise: false,
brightness: 1, brightness: 1,
saturation: 1, saturation: 1,
hue: 0, hue: 0,
@@ -181,6 +192,7 @@ const Sharp = function (input, options) {
streamOut: false, streamOut: false,
withMetadata: false, withMetadata: false,
withMetadataOrientation: -1, withMetadataOrientation: -1,
withMetadataIcc: '',
resolveWithObject: false, resolveWithObject: false,
// output format // output format
jpegQuality: 80, jpegQuality: 80,
@@ -208,7 +220,7 @@ const Sharp = function (input, options) {
tiffCompression: 'jpeg', tiffCompression: 'jpeg',
tiffPredictor: 'horizontal', tiffPredictor: 'horizontal',
tiffPyramid: false, tiffPyramid: false,
tiffSquash: false, tiffBitdepth: 8,
tiffTile: false, tiffTile: false,
tiffTileHeight: 256, tiffTileHeight: 256,
tiffTileWidth: 256, tiffTileWidth: 256,
@@ -219,12 +231,20 @@ const Sharp = function (input, options) {
heifCompression: 'hevc', heifCompression: 'hevc',
tileSize: 256, tileSize: 256,
tileOverlap: 0, tileOverlap: 0,
tileContainer: 'fs',
tileLayout: 'dz',
tileFormat: 'last',
tileDepth: 'last',
tileAngle: 0,
tileSkipBlanks: -1, tileSkipBlanks: -1,
tileBackground: [255, 255, 255, 255], tileBackground: [255, 255, 255, 255],
linearA: 1, linearA: 1,
linearB: 0, linearB: 0,
// Function to notify of libvips warnings // Function to notify of libvips warnings
debuglog: debuglog, debuglog: warning => {
this.emit('warning', warning);
debuglog(warning);
},
// Function to notify of queue length changes // Function to notify of queue length changes
queueListener: function (queueLength) { queueListener: function (queueLength) {
Sharp.queue.emit('change', queueLength); Sharp.queue.emit('change', queueLength);
@@ -248,6 +268,54 @@ util.inherits(Sharp, stream.Duplex);
* // firstWritableStream receives auto-rotated, resized readableStream * // firstWritableStream receives auto-rotated, resized readableStream
* // secondWritableStream receives auto-rotated, extracted region of readableStream * // secondWritableStream receives auto-rotated, extracted region of readableStream
* *
* @example
* // Create a pipeline that will download an image, resize it and format it to different files
* // Using Promises to know when the pipeline is complete
* const fs = require("fs");
* const got = require("got");
* const sharpStream = sharp({
* failOnError: false
* });
*
* const promises = [];
*
* promises.push(
* sharpStream
* .clone()
* .jpeg({ quality: 100 })
* .toFile("originalFile.jpg")
* );
*
* promises.push(
* sharpStream
* .clone()
* .resize({ width: 500 })
* .jpeg({ quality: 80 })
* .toFile("optimized-500.jpg")
* );
*
* promises.push(
* sharpStream
* .clone()
* .resize({ width: 500 })
* .webp({ quality: 80 })
* .toFile("optimized-500.webp")
* );
*
* // https://github.com/sindresorhus/got#gotstreamurl-options
* got.stream("https://www.example.com/some-file.jpg").pipe(sharpStream);
*
* Promise.all(promises)
* .then(res => { console.log("Done!", res); })
* .catch(err => {
* console.error("Error processing files, let's clean it up", err);
* try {
* fs.unlinkSync("originalFile.jpg");
* fs.unlinkSync("optimized-500.jpg");
* fs.unlinkSync("optimized-500.webp");
* } catch (e) {}
* });
*
* @returns {Sharp} * @returns {Sharp}
*/ */
function clone () { function clone () {

View File

@@ -1,10 +1,20 @@
'use strict'; 'use strict';
const util = require('util');
const color = require('color'); const color = require('color');
const is = require('./is'); const is = require('./is');
const sharp = require('../build/Release/sharp.node'); const sharp = require('../build/Release/sharp.node');
/**
* Extract input options, if any, from an object.
* @private
*/
function _inputOptionsFromObject (obj) {
const { raw, density, limitInputPixels, sequentialRead, failOnError } = obj;
return [raw, density, limitInputPixels, sequentialRead, failOnError].some(is.defined)
? { raw, density, limitInputPixels, sequentialRead, failOnError }
: undefined;
}
/** /**
* Create Object containing input and input-related options. * Create Object containing input and input-related options.
* @private * @private
@@ -24,12 +34,12 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
} else if (is.plainObject(input) && !is.defined(inputOptions)) { } else if (is.plainObject(input) && !is.defined(inputOptions)) {
// Plain Object descriptor, e.g. create // Plain Object descriptor, e.g. create
inputOptions = input; inputOptions = input;
if (is.plainObject(inputOptions.raw) || is.bool(inputOptions.failOnError)) { if (_inputOptionsFromObject(inputOptions)) {
// Raw Stream // Stream with options
inputDescriptor.buffer = []; inputDescriptor.buffer = [];
} }
} else if (!is.defined(input) && !is.defined(inputOptions) && is.object(containerOptions) && containerOptions.allowStream) { } else if (!is.defined(input) && !is.defined(inputOptions) && is.object(containerOptions) && containerOptions.allowStream) {
// Stream // Stream without options
inputDescriptor.buffer = []; inputDescriptor.buffer = [];
} else { } else {
throw new Error(`Unsupported input '${input}' of type ${typeof input}${ throw new Error(`Unsupported input '${input}' of type ${typeof input}${
@@ -89,6 +99,13 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
} }
} }
// Multi-page input (GIF, TIFF, PDF) // Multi-page input (GIF, TIFF, PDF)
if (is.defined(inputOptions.animated)) {
if (is.bool(inputOptions.animated)) {
inputDescriptor.pages = inputOptions.animated ? -1 : 1;
} else {
throw is.invalidParameterError('animated', 'boolean', inputOptions.animated);
}
}
if (is.defined(inputOptions.pages)) { if (is.defined(inputOptions.pages)) {
if (is.integer(inputOptions.pages) && is.inRange(inputOptions.pages, -1, 100000)) { if (is.integer(inputOptions.pages) && is.inRange(inputOptions.pages, -1, 100000)) {
inputDescriptor.pages = inputOptions.pages; inputDescriptor.pages = inputOptions.pages;
@@ -103,6 +120,14 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
throw is.invalidParameterError('page', 'integer between 0 and 100000', inputOptions.page); throw is.invalidParameterError('page', 'integer between 0 and 100000', inputOptions.page);
} }
} }
// Multi-level input (OpenSlide)
if (is.defined(inputOptions.level)) {
if (is.integer(inputOptions.level) && is.inRange(inputOptions.level, 0, 256)) {
inputDescriptor.level = inputOptions.level;
} else {
throw is.invalidParameterError('level', 'integer between 0 and 256', inputOptions.level);
}
}
// Create new image // Create new image
if (is.defined(inputOptions.create)) { if (is.defined(inputOptions.create)) {
if ( if (
@@ -137,7 +162,7 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
* Handle incoming Buffer chunk on Writable Stream. * Handle incoming Buffer chunk on Writable Stream.
* @private * @private
* @param {Buffer} chunk * @param {Buffer} chunk
* @param {String} encoding - unused * @param {string} encoding - unused
* @param {Function} callback * @param {Function} callback
*/ */
function _write (chunk, encoding, callback) { function _write (chunk, encoding, callback) {
@@ -173,7 +198,7 @@ function _flattenBufferIn () {
/** /**
* Are we expecting Stream-based input? * Are we expecting Stream-based input?
* @private * @private
* @returns {Boolean} * @returns {boolean}
*/ */
function _isStreamInput () { function _isStreamInput () {
return Array.isArray(this.options.input.buffer); return Array.isArray(this.options.input.buffer);
@@ -198,6 +223,7 @@ function _isStreamInput () {
* - `loop`: Number of times to loop an animated image, zero refers to a continuous loop. * - `loop`: Number of times to loop an animated image, zero refers to a continuous loop.
* - `delay`: Delay in ms between each page in an animated image, provided as an array of integers. * - `delay`: Delay in ms between each page in an animated image, provided as an array of integers.
* - `pagePrimary`: Number of the primary page in a HEIF image * - `pagePrimary`: Number of the primary page in a HEIF image
* - `levels`: Details of each level in a multi-level image provided as an array of objects, requires libvips compiled with support for OpenSlide
* - `hasProfile`: Boolean indicating the presence of an embedded ICC profile * - `hasProfile`: Boolean indicating the presence of an embedded ICC profile
* - `hasAlpha`: Boolean indicating the presence of an alpha transparency channel * - `hasAlpha`: Boolean indicating the presence of an alpha transparency channel
* - `orientation`: Number value of the EXIF Orientation header, if present * - `orientation`: Number value of the EXIF Orientation header, if present
@@ -278,8 +304,10 @@ function metadata (callback) {
* - `minY` (y-coordinate of one of the pixel where the minimum lies) * - `minY` (y-coordinate of one of the pixel where the minimum lies)
* - `maxX` (x-coordinate of one of the pixel where the maximum lies) * - `maxX` (x-coordinate of one of the pixel where the maximum lies)
* - `maxY` (y-coordinate of one of the pixel where the maximum lies) * - `maxY` (y-coordinate of one of the pixel where the maximum lies)
* - `isOpaque`: Value to identify if the image is opaque or transparent, based on the presence and use of alpha channel * - `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) * - `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)
* *
* @example * @example
* const image = sharp(inputJpg); * const image = sharp(inputJpg);
@@ -289,6 +317,10 @@ function metadata (callback) {
* // stats contains the channel-wise statistics array and the isOpaque value * // stats contains the channel-wise statistics array and the isOpaque value
* }); * });
* *
* @example
* const { entropy, sharpness, dominant } = await sharp(input).stats();
* const { r, g, b } = dominant;
*
* @param {Function} [callback] - called with the arguments `(err, stats)` * @param {Function} [callback] - called with the arguments `(err, stats)`
* @returns {Promise<Object>} * @returns {Promise<Object>}
*/ */
@@ -331,32 +363,6 @@ function stats (callback) {
} }
} }
/**
* @private
*/
const limitInputPixels = util.deprecate(function limitInputPixels (limit) {
// if we pass in false we represent the integer as 0 to disable
if (limit === false) {
limit = 0;
} else if (limit === true) {
limit = Math.pow(0x3FFF, 2);
}
if (is.integer(limit) && limit >= 0) {
this.options.input.limitInputPixels = limit;
} else {
throw is.invalidParameterError('limitInputPixels', 'integer', limit);
}
return this;
}, 'limitInputPixels is deprecated, use sharp(input, { limitInputPixels: false }) instead');
/**
* @private
*/
const sequentialRead = util.deprecate(function sequentialRead (sequentialRead) {
this.options.input.sequentialRead = is.bool(sequentialRead) ? sequentialRead : true;
return this;
}, 'sequentialRead is deprecated, use sharp(input, { sequentialRead: true }) instead');
/** /**
* Decorate the Sharp prototype with input-related functions. * Decorate the Sharp prototype with input-related functions.
* @private * @private
@@ -364,15 +370,13 @@ const sequentialRead = util.deprecate(function sequentialRead (sequentialRead) {
module.exports = function (Sharp) { module.exports = function (Sharp) {
Object.assign(Sharp.prototype, { Object.assign(Sharp.prototype, {
// Private // Private
_inputOptionsFromObject,
_createInputDescriptor, _createInputDescriptor,
_write, _write,
_flattenBufferIn, _flattenBufferIn,
_isStreamInput, _isStreamInput,
// Public // Public
metadata, metadata,
stats, stats
// Deprecated
limitInputPixels,
sequentialRead
}); });
}; };

View File

@@ -21,7 +21,7 @@ const object = function (val) {
* @private * @private
*/ */
const plainObject = function (val) { const plainObject = function (val) {
return object(val) && Object.prototype.toString.call(val) === '[object Object]'; return Object.prototype.toString.call(val) === '[object Object]';
}; };
/** /**
@@ -45,7 +45,7 @@ const bool = function (val) {
* @private * @private
*/ */
const buffer = function (val) { const buffer = function (val) {
return object(val) && val instanceof Buffer; return val instanceof Buffer;
}; };
/** /**
@@ -69,7 +69,7 @@ const number = function (val) {
* @private * @private
*/ */
const integer = function (val) { const integer = function (val) {
return number(val) && val % 1 === 0; return Number.isInteger(val);
}; };
/** /**
@@ -85,14 +85,14 @@ const inRange = function (val, min, max) {
* @private * @private
*/ */
const inArray = function (val, list) { const inArray = function (val, list) {
return list.indexOf(val) !== -1; return list.includes(val);
}; };
/** /**
* Create an Error with a message relating to an invalid parameter. * Create an Error with a message relating to an invalid parameter.
* *
* @param {String} name - parameter name. * @param {string} name - parameter name.
* @param {String} expected - description of the type/value/range expected. * @param {string} expected - description of the type/value/range expected.
* @param {*} actual - the value received. * @param {*} actual - the value received.
* @returns {Error} Containing the formatted message. * @returns {Error} Containing the formatted message.
* @private * @private

View File

@@ -8,8 +8,9 @@ const semver = require('semver');
const platform = require('./platform'); const platform = require('./platform');
const env = process.env; const env = process.env;
const minimumLibvipsVersion = env.npm_package_config_libvips || /* istanbul ignore next */ const minimumLibvipsVersionLabelled = env.npm_package_config_libvips || /* istanbul ignore next */
require('../package.json').config.libvips; require('../package.json').config.libvips;
const minimumLibvipsVersion = semver.coerce(minimumLibvipsVersionLabelled).version;
const spawnSyncOptions = { const spawnSyncOptions = {
encoding: 'utf8', encoding: 'utf8',
@@ -47,24 +48,18 @@ const globalLibvipsVersion = function () {
const hasVendoredLibvips = function () { const hasVendoredLibvips = function () {
const currentPlatformId = platform(); const currentPlatformId = platform();
const vendorPath = path.join(__dirname, '..', 'vendor'); const vendorPath = path.join(__dirname, '..', 'vendor', minimumLibvipsVersion);
let vendorVersionId;
let vendorPlatformId; let vendorPlatformId;
try { try {
vendorVersionId = require(path.join(vendorPath, 'versions.json')).vips;
vendorPlatformId = require(path.join(vendorPath, 'platform.json')); vendorPlatformId = require(path.join(vendorPath, 'platform.json'));
} catch (err) {} } catch (err) {}
/* istanbul ignore if */
if (vendorVersionId && vendorVersionId !== minimumLibvipsVersion) {
throw new Error(`Found vendored libvips v${vendorVersionId} but require v${minimumLibvipsVersion}. Please remove the 'node_modules/sharp/vendor' directory and run 'npm install'.`);
}
/* istanbul ignore else */ /* istanbul ignore else */
if (vendorPlatformId) { if (vendorPlatformId) {
/* istanbul ignore else */ /* istanbul ignore else */
if (currentPlatformId === vendorPlatformId) { if (currentPlatformId === vendorPlatformId) {
return true; return true;
} else { } else {
throw new Error(`'${vendorPlatformId}' binaries cannot be used on the '${currentPlatformId}' platform. Please remove the 'node_modules/sharp/vendor' directory and run 'npm install'.`); throw new Error(`'${vendorPlatformId}' binaries cannot be used on the '${currentPlatformId}' platform. Please remove the 'node_modules/sharp' directory and run 'npm install' on the '${currentPlatformId}' platform.`);
} }
} else { } else {
return false; return false;
@@ -93,11 +88,12 @@ const useGlobalLibvips = function () {
}; };
module.exports = { module.exports = {
minimumLibvipsVersion: minimumLibvipsVersion, minimumLibvipsVersion,
cachePath: cachePath, minimumLibvipsVersionLabelled,
globalLibvipsVersion: globalLibvipsVersion, cachePath,
hasVendoredLibvips: hasVendoredLibvips, globalLibvipsVersion,
pkgConfigPath: pkgConfigPath, hasVendoredLibvips,
useGlobalLibvips: useGlobalLibvips, pkgConfigPath,
mkdirSync: mkdirSync useGlobalLibvips,
mkdirSync
}; };

View File

@@ -32,9 +32,9 @@ const is = require('./is');
* }); * });
* readableStream.pipe(pipeline); * readableStream.pipe(pipeline);
* *
* @param {Number} [angle=auto] angle of rotation. * @param {number} [angle=auto] angle of rotation.
* @param {Object} [options] - if present, is an Object with optional attributes. * @param {Object} [options] - if present, is an Object with optional attributes.
* @param {String|Object} [options.background="#000000"] parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha. * @param {string|Object} [options.background="#000000"] parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -88,9 +88,9 @@ function flop (flop) {
* When a `sigma` is provided, performs a slower, more accurate sharpen of the L channel in the LAB colour space. * 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. * 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} [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} [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. * @param {number} [jagged=2.0] - the level of sharpening to apply to "jagged" areas.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -129,7 +129,7 @@ function sharpen (sigma, flat, jagged) {
/** /**
* Apply median filter. * Apply median filter.
* When used without parameters the default window is 3x3. * When used without parameters the default window is 3x3.
* @param {Number} [size=3] square mask size: size x size * @param {number} [size=3] square mask size: size x size
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -150,7 +150,7 @@ function median (size) {
* Blur the image. * Blur the image.
* When used without parameters, performs a fast, mild blur of the output image. * When used without parameters, performs a fast, mild blur of the output image.
* When a `sigma` is provided, performs a slower, more accurate Gaussian blur. * When a `sigma` is provided, performs a slower, more accurate Gaussian blur.
* @param {Number} [sigma] a value between 0.3 and 1000 representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`. * @param {number} [sigma] a value between 0.3 and 1000 representing the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -173,7 +173,7 @@ function blur (sigma) {
/** /**
* Merge alpha transparency channel, if any, with a background. * Merge alpha transparency channel, if any, with a background.
* @param {Object} [options] * @param {Object} [options]
* @param {String|Object} [options.background={r: 0, g: 0, b: 0}] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black. * @param {string|Object} [options.background={r: 0, g: 0, b: 0}] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black.
* @returns {Sharp} * @returns {Sharp}
*/ */
function flatten (options) { function flatten (options) {
@@ -193,8 +193,8 @@ function flatten (options) {
* *
* Supply a second argument to use a different output gamma value, otherwise the first value is used in both cases. * Supply a second argument to use a different output gamma value, otherwise the first value is used in both cases.
* *
* @param {Number} [gamma=2.2] value between 1.0 and 3.0. * @param {number} [gamma=2.2] value between 1.0 and 3.0.
* @param {Number} [gammaOut] value between 1.0 and 3.0. (optional, defaults to same as `gamma`) * @param {number} [gammaOut] value between 1.0 and 3.0. (optional, defaults to same as `gamma`)
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -264,11 +264,11 @@ function normalize (normalize) {
* }); * });
* *
* @param {Object} kernel * @param {Object} kernel
* @param {Number} kernel.width - width of the kernel in pixels. * @param {number} kernel.width - width of the kernel in pixels.
* @param {Number} kernel.height - width of the kernel in pixels. * @param {number} kernel.height - width of the kernel in pixels.
* @param {Array<Number>} kernel.kernel - Array of length `width*height` containing the kernel values. * @param {Array<number>} kernel.kernel - Array of length `width*height` containing the kernel values.
* @param {Number} [kernel.scale=sum] - the scale of the kernel in pixels. * @param {number} [kernel.scale=sum] - the scale of the kernel in pixels.
* @param {Number} [kernel.offset=0] - the offset of the kernel in pixels. * @param {number} [kernel.offset=0] - the offset of the kernel in pixels.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -300,7 +300,7 @@ function convolve (kernel) {
/** /**
* Any pixel value greather than or equal to the threshold value will be set to 255, otherwise it will be set to 0. * Any pixel value greather than or equal to the threshold value will be set to 255, otherwise it will be set to 0.
* @param {Number} [threshold=128] - a value in the range 0-255 representing the level at which the threshold will be applied. * @param {number} [threshold=128] - a value in the range 0-255 representing the level at which the threshold will be applied.
* @param {Object} [options] * @param {Object} [options]
* @param {Boolean} [options.greyscale=true] - convert to single channel greyscale. * @param {Boolean} [options.greyscale=true] - convert to single channel greyscale.
* @param {Boolean} [options.grayscale=true] - alternative spelling for greyscale. * @param {Boolean} [options.grayscale=true] - alternative spelling for greyscale.
@@ -331,13 +331,13 @@ function threshold (threshold, options) {
* This operation creates an output image where each pixel is the result of * This operation creates an output image where each pixel is the result of
* the selected bitwise boolean `operation` between the corresponding pixels of the input images. * the selected bitwise boolean `operation` between the corresponding pixels of the input images.
* *
* @param {Buffer|String} operand - Buffer containing image data or String containing the path to an image file. * @param {Buffer|string} operand - Buffer containing image data or string containing the path to an image file.
* @param {String} operator - one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively. * @param {string} operator - one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively.
* @param {Object} [options] * @param {Object} [options]
* @param {Object} [options.raw] - describes operand when using raw pixel data. * @param {Object} [options.raw] - describes operand when using raw pixel data.
* @param {Number} [options.raw.width] * @param {number} [options.raw.width]
* @param {Number} [options.raw.height] * @param {number} [options.raw.height]
* @param {Number} [options.raw.channels] * @param {number} [options.raw.channels]
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -353,8 +353,8 @@ function boolean (operand, operator, options) {
/** /**
* Apply the linear formula a * input + b to the image (levels adjustment) * Apply the linear formula a * input + b to the image (levels adjustment)
* @param {Number} [a=1.0] multiplier * @param {number} [a=1.0] multiplier
* @param {Number} [b=0.0] offset * @param {number} [b=0.0] offset
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -394,7 +394,7 @@ function linear (a, b) {
* // With this example input, a sepia filter has been applied * // With this example input, a sepia filter has been applied
* }); * });
* *
* @param {Array<Array<Number>>} 3x3 Recombination matrix * @param {Array<Array<number>>} inputMatrix - 3x3 Recombination matrix
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -440,9 +440,9 @@ function recomb (inputMatrix) {
* }); * });
* *
* @param {Object} [options] * @param {Object} [options]
* @param {Number} [options.brightness] Brightness multiplier * @param {number} [options.brightness] Brightness multiplier
* @param {Number} [options.saturation] Saturation multiplier * @param {number} [options.saturation] Saturation multiplier
* @param {Number} [options.hue] Degrees for hue rotation * @param {number} [options.hue] Degrees for hue rotation
* @returns {Sharp} * @returns {Sharp}
*/ */
function modulate (options) { function modulate (options) {

View File

@@ -11,7 +11,8 @@ const formats = new Map([
['png', 'png'], ['png', 'png'],
['raw', 'raw'], ['raw', 'raw'],
['tiff', 'tiff'], ['tiff', 'tiff'],
['webp', 'webp'] ['webp', 'webp'],
['gif', 'gif']
]); ]);
/** /**
@@ -36,7 +37,7 @@ const formats = new Map([
* .then(info => { ... }) * .then(info => { ... })
* .catch(err => { ... }); * .catch(err => { ... });
* *
* @param {String} fileOut - the path to write the image data to. * @param {string} fileOut - the path to write the image data to.
* @param {Function} [callback] - called on completion with two arguments `(err, info)`. * @param {Function} [callback] - called on completion with two arguments `(err, info)`.
* `info` contains the output image `format`, `size` (bytes), `width`, `height`, * `info` contains the output image `format`, `size` (bytes), `width`, `height`,
* `channels` and `premultiplied` (indicating if premultiplication was used). * `channels` and `premultiplied` (indicating if premultiplication was used).
@@ -103,7 +104,7 @@ function toFile (fileOut, callback) {
* .catch(err => { ... }); * .catch(err => { ... });
* *
* @param {Object} [options] * @param {Object} [options]
* @param {Boolean} [options.resolveWithObject] Resolve the Promise with an Object containing `data` and `info` properties instead of resolving only with `data`. * @param {boolean} [options.resolveWithObject] Resolve the Promise with an Object containing `data` and `info` properties instead of resolving only with `data`.
* @param {Function} [callback] * @param {Function} [callback]
* @returns {Promise<Buffer>} - when no callback is provided * @returns {Promise<Buffer>} - when no callback is provided
*/ */
@@ -118,8 +119,11 @@ function toBuffer (options, callback) {
/** /**
* Include all metadata (EXIF, XMP, IPTC) from the input image in the output image. * Include all metadata (EXIF, XMP, IPTC) from the input image in the output image.
* The default behaviour, when `withMetadata` is not used, is to strip all metadata and convert to the device-independent sRGB colour space. * This will also convert to and add a web-friendly sRGB ICC profile unless a custom
* This will also convert to and add a web-friendly sRGB ICC profile. * output profile is provided.
*
* The default behaviour, when `withMetadata` is not used, is to convert to the device-independent
* sRGB colour space and strip all metadata, including the removal of any ICC profile.
* *
* @example * @example
* sharp('input.jpg') * sharp('input.jpg')
@@ -128,7 +132,8 @@ function toBuffer (options, callback) {
* .then(info => { ... }); * .then(info => { ... });
* *
* @param {Object} [options] * @param {Object} [options]
* @param {Number} [options.orientation] value between 1 and 8, used to update the EXIF `Orientation` tag. * @param {number} [options.orientation] value between 1 and 8, used to update the EXIF `Orientation` tag.
* @param {string} [options.icc] filesystem path to output ICC profile, defaults to sRGB.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -142,6 +147,13 @@ function withMetadata (options) {
throw is.invalidParameterError('orientation', 'integer between 1 and 8', options.orientation); throw is.invalidParameterError('orientation', 'integer between 1 and 8', options.orientation);
} }
} }
if (is.defined(options.icc)) {
if (is.string(options.icc)) {
this.options.withMetadataIcc = options.icc;
} else {
throw is.invalidParameterError('icc', 'string filesystem path to ICC profile', options.icc);
}
}
} }
return this; return this;
} }
@@ -155,7 +167,7 @@ function withMetadata (options) {
* .toFormat('png') * .toFormat('png')
* .toBuffer(); * .toBuffer();
* *
* @param {(String|Object)} format - as a String or an Object with an 'id' attribute * @param {(string|Object)} format - as a string or an Object with an 'id' attribute
* @param {Object} options - output options * @param {Object} options - output options
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} unsupported format or options * @throws {Error} unsupported format or options
@@ -171,6 +183,8 @@ function toFormat (format, options) {
/** /**
* Use these JPEG options for output image. * Use these JPEG options for output image.
* *
* Some of these options require the use of a globally-installed libvips compiled with support for mozjpeg.
*
* @example * @example
* // Convert any input to very high quality JPEG output * // Convert any input to very high quality JPEG output
* const data = await sharp(input) * const data = await sharp(input)
@@ -181,18 +195,18 @@ function toFormat (format, options) {
* .toBuffer(); * .toBuffer();
* *
* @param {Object} [options] - output options * @param {Object} [options] - output options
* @param {Number} [options.quality=80] - quality, integer 1-100 * @param {number} [options.quality=80] - quality, integer 1-100
* @param {Boolean} [options.progressive=false] - use progressive (interlace) scan * @param {boolean} [options.progressive=false] - use progressive (interlace) scan
* @param {String} [options.chromaSubsampling='4:2:0'] - set to '4:4:4' to prevent chroma subsampling when quality <= 90 * @param {string} [options.chromaSubsampling='4:2:0'] - set to '4:4:4' to prevent chroma subsampling otherwise defaults to '4:2:0' chroma subsampling
* @param {Boolean} [options.trellisQuantisation=false] - apply trellis quantisation, requires libvips compiled with support for mozjpeg * @param {boolean} [options.optimiseCoding=true] - optimise Huffman coding tables
* @param {Boolean} [options.overshootDeringing=false] - apply overshoot deringing, requires libvips compiled with support for mozjpeg * @param {boolean} [options.optimizeCoding=true] - alternative spelling of optimiseCoding
* @param {Boolean} [options.optimiseScans=false] - optimise progressive scans, forces progressive, requires libvips compiled with support for mozjpeg * @param {boolean} [options.trellisQuantisation=false] - apply trellis quantisation, requires libvips compiled with support for mozjpeg
* @param {Boolean} [options.optimizeScans=false] - alternative spelling of optimiseScans * @param {boolean} [options.overshootDeringing=false] - apply overshoot deringing, requires libvips compiled with support for mozjpeg
* @param {Boolean} [options.optimiseCoding=true] - optimise Huffman coding tables * @param {boolean} [options.optimiseScans=false] - optimise progressive scans, forces progressive, requires libvips compiled with support for mozjpeg
* @param {Boolean} [options.optimizeCoding=true] - alternative spelling of optimiseCoding * @param {boolean} [options.optimizeScans=false] - alternative spelling of optimiseScans, requires libvips compiled with support for mozjpeg
* @param {Number} [options.quantisationTable=0] - quantization table to use, integer 0-8, requires libvips compiled with support for mozjpeg * @param {number} [options.quantisationTable=0] - quantization table to use, integer 0-8, requires libvips compiled with support for mozjpeg
* @param {Number} [options.quantizationTable=0] - alternative spelling of quantisationTable * @param {number} [options.quantizationTable=0] - alternative spelling of quantisationTable, requires libvips compiled with support for mozjpeg
* @param {Boolean} [options.force=true] - force JPEG output, otherwise attempt to use input format * @param {boolean} [options.force=true] - force JPEG output, otherwise attempt to use input format
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid options * @throws {Error} Invalid options
*/ */
@@ -251,6 +265,8 @@ function jpeg (options) {
* PNG output is always full colour at 8 or 16 bits per pixel. * PNG output is always full colour at 8 or 16 bits per pixel.
* Indexed PNG input at 1, 2 or 4 bits per pixel is converted to 8 bits per pixel. * Indexed PNG input at 1, 2 or 4 bits per pixel is converted to 8 bits per pixel.
* *
* Some of these options require the use of a globally-installed libvips compiled with support for libimagequant (GPL).
*
* @example * @example
* // Convert any input to PNG output * // Convert any input to PNG output
* const data = await sharp(input) * const data = await sharp(input)
@@ -258,15 +274,15 @@ function jpeg (options) {
* .toBuffer(); * .toBuffer();
* *
* @param {Object} [options] * @param {Object} [options]
* @param {Boolean} [options.progressive=false] - use progressive (interlace) scan * @param {boolean} [options.progressive=false] - use progressive (interlace) scan
* @param {Number} [options.compressionLevel=9] - zlib compression level, 0-9 * @param {number} [options.compressionLevel=9] - zlib compression level, 0-9
* @param {Boolean} [options.adaptiveFiltering=false] - use adaptive row filtering * @param {boolean} [options.adaptiveFiltering=false] - use adaptive row filtering
* @param {Boolean} [options.palette=false] - quantise to a palette-based image with alpha transparency support, requires libvips compiled with support for libimagequant * @param {boolean} [options.palette=false] - quantise to a palette-based image with alpha transparency support, requires libvips compiled with support for libimagequant
* @param {Number} [options.quality=100] - use the lowest number of colours needed to achieve given quality, requires libvips compiled with support for libimagequant * @param {number} [options.quality=100] - use the lowest number of colours needed to achieve given quality, sets `palette` to `true`, requires libvips compiled with support for libimagequant
* @param {Number} [options.colours=256] - maximum number of palette entries, requires libvips compiled with support for libimagequant * @param {number} [options.colours=256] - maximum number of palette entries, sets `palette` to `true`, requires libvips compiled with support for libimagequant
* @param {Number} [options.colors=256] - alternative spelling of `options.colours`, requires libvips compiled with support for libimagequant * @param {number} [options.colors=256] - alternative spelling of `options.colours`, sets `palette` to `true`, requires libvips compiled with support for libimagequant
* @param {Number} [options.dither=1.0] - level of Floyd-Steinberg error diffusion, requires libvips compiled with support for libimagequant * @param {number} [options.dither=1.0] - level of Floyd-Steinberg error diffusion, sets `palette` to `true`, requires libvips compiled with support for libimagequant
* @param {Boolean} [options.force=true] - force PNG output, otherwise attempt to use input format * @param {boolean} [options.force=true] - force PNG output, otherwise attempt to use input format
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid options * @throws {Error} Invalid options
*/ */
@@ -287,28 +303,30 @@ function png (options) {
} }
if (is.defined(options.palette)) { if (is.defined(options.palette)) {
this._setBooleanOption('pngPalette', options.palette); this._setBooleanOption('pngPalette', options.palette);
if (this.options.pngPalette) { } else if (is.defined(options.quality) || is.defined(options.colours || options.colors) || is.defined(options.dither)) {
if (is.defined(options.quality)) { this._setBooleanOption('pngPalette', true);
if (is.integer(options.quality) && is.inRange(options.quality, 0, 100)) { }
this.options.pngQuality = options.quality; if (this.options.pngPalette) {
} else { if (is.defined(options.quality)) {
throw is.invalidParameterError('quality', 'integer between 0 and 100', options.quality); if (is.integer(options.quality) && is.inRange(options.quality, 0, 100)) {
} this.options.pngQuality = options.quality;
} else {
throw is.invalidParameterError('quality', 'integer between 0 and 100', options.quality);
} }
const colours = options.colours || options.colors; }
if (is.defined(colours)) { const colours = options.colours || options.colors;
if (is.integer(colours) && is.inRange(colours, 2, 256)) { if (is.defined(colours)) {
this.options.pngColours = colours; if (is.integer(colours) && is.inRange(colours, 2, 256)) {
} else { this.options.pngColours = colours;
throw is.invalidParameterError('colours', 'integer between 2 and 256', colours); } else {
} throw is.invalidParameterError('colours', 'integer between 2 and 256', colours);
} }
if (is.defined(options.dither)) { }
if (is.number(options.dither) && is.inRange(options.dither, 0, 1)) { if (is.defined(options.dither)) {
this.options.pngDither = options.dither; if (is.number(options.dither) && is.inRange(options.dither, 0, 1)) {
} else { this.options.pngDither = options.dither;
throw is.invalidParameterError('dither', 'number between 0.0 and 1.0', options.dither); } else {
} throw is.invalidParameterError('dither', 'number between 0.0 and 1.0', options.dither);
} }
} }
} }
@@ -326,13 +344,16 @@ function png (options) {
* .toBuffer(); * .toBuffer();
* *
* @param {Object} [options] - output options * @param {Object} [options] - output options
* @param {Number} [options.quality=80] - quality, integer 1-100 * @param {number} [options.quality=80] - quality, integer 1-100
* @param {Number} [options.alphaQuality=100] - quality of alpha layer, integer 0-100 * @param {number} [options.alphaQuality=100] - quality of alpha layer, integer 0-100
* @param {Boolean} [options.lossless=false] - use lossless compression mode * @param {boolean} [options.lossless=false] - use lossless compression mode
* @param {Boolean} [options.nearLossless=false] - use near_lossless compression mode * @param {boolean} [options.nearLossless=false] - use near_lossless compression mode
* @param {Boolean} [options.smartSubsample=false] - use high quality chroma subsampling * @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.reductionEffort=4] - level of CPU effort to reduce file size, integer 0-6
* @param {Boolean} [options.force=true] - force WebP output, otherwise attempt to use input format * @param {number} [options.pageHeight] - page height for animated output
* @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 {boolean} [options.force=true] - force WebP output, otherwise attempt to use input format
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid options * @throws {Error} Invalid options
*/ */
@@ -367,9 +388,73 @@ function webp (options) {
throw is.invalidParameterError('reductionEffort', 'integer between 0 and 6', options.reductionEffort); throw is.invalidParameterError('reductionEffort', 'integer between 0 and 6', options.reductionEffort);
} }
} }
trySetAnimationOptions(options, this.options);
return this._updateFormatOut('webp', options); return this._updateFormatOut('webp', options);
} }
/**
* Use these GIF options for 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}.
*
* @param {Object} [options] - output options
* @param {number} [options.pageHeight] - page height for animated output
* @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 {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 new Error('The gif operation requires libvips to have been installed with support for ImageMagick');
}
trySetAnimationOptions(options, this.options);
return this._updateFormatOut('gif', options);
}
/**
* Set animation options if available.
* @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;
} else {
throw is.invalidParameterError('loop', 'integer between 0 and 65535', source.loop);
}
}
if (is.object(source) && is.defined(source.delay)) {
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);
}
}
}
/** /**
* Use these TIFF options for output image. * Use these TIFF options for output image.
* *
@@ -378,23 +463,23 @@ function webp (options) {
* sharp('input.svg') * sharp('input.svg')
* .tiff({ * .tiff({
* compression: 'lzw', * compression: 'lzw',
* squash: true * bitdepth: 1
* }) * })
* .toFile('1-bpp-output.tiff') * .toFile('1-bpp-output.tiff')
* .then(info => { ... }); * .then(info => { ... });
* *
* @param {Object} [options] - output options * @param {Object} [options] - output options
* @param {Number} [options.quality=80] - quality, integer 1-100 * @param {number} [options.quality=80] - quality, integer 1-100
* @param {Boolean} [options.force=true] - force TIFF output, otherwise attempt to use input format * @param {boolean} [options.force=true] - force TIFF output, otherwise attempt to use input format
* @param {Boolean} [options.compression='jpeg'] - compression options: lzw, deflate, jpeg, ccittfax4 * @param {boolean} [options.compression='jpeg'] - compression options: lzw, deflate, jpeg, ccittfax4
* @param {Boolean} [options.predictor='horizontal'] - compression predictor options: none, horizontal, float * @param {boolean} [options.predictor='horizontal'] - compression predictor options: none, horizontal, float
* @param {Boolean} [options.pyramid=false] - write an image pyramid * @param {boolean} [options.pyramid=false] - write an image pyramid
* @param {Boolean} [options.tile=false] - write a tiled tiff * @param {boolean} [options.tile=false] - write a tiled tiff
* @param {Boolean} [options.tileWidth=256] - horizontal tile size * @param {boolean} [options.tileWidth=256] - horizontal tile size
* @param {Boolean} [options.tileHeight=256] - vertical tile size * @param {boolean} [options.tileHeight=256] - vertical tile size
* @param {Number} [options.xres=1.0] - horizontal resolution in pixels/mm * @param {number} [options.xres=1.0] - horizontal resolution in pixels/mm
* @param {Number} [options.yres=1.0] - vertical resolution in pixels/mm * @param {number} [options.yres=1.0] - vertical resolution in pixels/mm
* @param {Boolean} [options.squash=false] - squash 8-bit images down to 1 bit * @param {boolean} [options.bitdepth=8] - reduce bitdepth to 1, 2 or 4 bit
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid options * @throws {Error} Invalid options
*/ */
@@ -407,8 +492,12 @@ function tiff (options) {
throw is.invalidParameterError('quality', 'integer between 1 and 100', options.quality); throw is.invalidParameterError('quality', 'integer between 1 and 100', options.quality);
} }
} }
if (is.defined(options.squash)) { if (is.defined(options.bitdepth)) {
this._setBooleanOption('tiffSquash', options.squash); if (is.integer(options.bitdepth) && is.inArray(options.bitdepth, [1, 2, 4, 8])) {
this.options.tiffBitdepth = options.bitdepth;
} else {
throw is.invalidParameterError('bitdepth', '1, 2, 4 or 8', options.bitdepth);
}
} }
// tiling // tiling
if (is.defined(options.tile)) { if (is.defined(options.tile)) {
@@ -480,9 +569,9 @@ function tiff (options) {
* @since 0.23.0 * @since 0.23.0
* *
* @param {Object} [options] - output options * @param {Object} [options] - output options
* @param {Number} [options.quality=80] - quality, integer 1-100 * @param {number} [options.quality=80] - quality, integer 1-100
* @param {Boolean} [options.compression='hevc'] - compression format: hevc, avc, jpeg, av1 * @param {boolean} [options.compression='hevc'] - compression format: hevc, avc, jpeg, av1
* @param {Boolean} [options.lossless=false] - use lossless compression * @param {boolean} [options.lossless=false] - use lossless compression
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid options * @throws {Error} Invalid options
*/ */
@@ -517,7 +606,9 @@ function heif (options) {
} }
/** /**
* Force output to be raw, uncompressed uint8 pixel data. * Force output to be raw, uncompressed, 8-bit unsigned integer (unit8) pixel data.
* Pixel ordering is left-to-right, top-to-bottom, without padding.
* Channel ordering will be RGB or RGBA for non-greyscale colourspaces.
* *
* @example * @example
* // Extract raw RGB pixel data from JPEG input * // Extract raw RGB pixel data from JPEG input
@@ -525,6 +616,15 @@ function heif (options) {
* .raw() * .raw()
* .toBuffer({ resolveWithObject: true }); * .toBuffer({ resolveWithObject: true });
* *
* @example
* // Extract alpha channel as raw pixel data from PNG input
* const data = await sharp('input.png')
* .ensureAlpha()
* .extractChannel(3)
* .toColourspace('b-w')
* .raw()
* .toBuffer();
*
* @returns {Sharp} * @returns {Sharp}
*/ */
function raw () { function raw () {
@@ -550,14 +650,14 @@ function raw () {
* }); * });
* *
* @param {Object} [options] * @param {Object} [options]
* @param {Number} [options.size=256] tile size in pixels, a value between 1 and 8192. * @param {number} [options.size=256] tile size in pixels, a value between 1 and 8192.
* @param {Number} [options.overlap=0] tile overlap in pixels, a value between 0 and 8192. * @param {number} [options.overlap=0] tile overlap in pixels, a value between 0 and 8192.
* @param {Number} [options.angle=0] tile angle of rotation, must be a multiple of 90. * @param {number} [options.angle=0] tile angle of rotation, must be a multiple of 90.
* @param {String|Object} [options.background={r: 255, g: 255, b: 255, alpha: 1}] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to white without transparency. * @param {string|Object} [options.background={r: 255, g: 255, b: 255, alpha: 1}] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to white without transparency.
* @param {String} [options.depth] how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout. * @param {string} [options.depth] how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout.
* @param {Number} [options.skipBlanks=-1] threshold to skip tile generation, a value 0 - 255 for 8-bit images or 0 - 65535 for 16-bit images * @param {number} [options.skipBlanks=-1] threshold to skip tile generation, 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.container='fs'] tile container, with value `fs` (filesystem) or `zip` (compressed file).
* @param {String} [options.layout='dz'] filesystem layout, possible values are `dz`, `zoomify` or `google`. * @param {string} [options.layout='dz'] filesystem layout, possible values are `dz`, `iiif`, `zoomify` or `google`.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -592,10 +692,10 @@ function tile (options) {
} }
// Layout // Layout
if (is.defined(options.layout)) { if (is.defined(options.layout)) {
if (is.string(options.layout) && is.inArray(options.layout, ['dz', 'google', 'zoomify'])) { if (is.string(options.layout) && is.inArray(options.layout, ['dz', 'google', 'iiif', 'zoomify'])) {
this.options.tileLayout = options.layout; this.options.tileLayout = options.layout;
} else { } else {
throw is.invalidParameterError('layout', 'one of: dz, google, zoomify', options.layout); throw is.invalidParameterError('layout', 'one of: dz, google, iiif, zoomify', options.layout);
} }
} }
// Angle of rotation, // Angle of rotation,
@@ -640,9 +740,9 @@ function tile (options) {
* Update the output format unless options.force is false, * Update the output format unless options.force is false,
* in which case revert to input format. * in which case revert to input format.
* @private * @private
* @param {String} formatOut * @param {string} formatOut
* @param {Object} [options] * @param {Object} [options]
* @param {Boolean} [options.force=true] - force output format, otherwise attempt to use input format * @param {boolean} [options.force=true] - force output format, otherwise attempt to use input format
* @returns {Sharp} * @returns {Sharp}
*/ */
function _updateFormatOut (formatOut, options) { function _updateFormatOut (formatOut, options) {
@@ -653,10 +753,10 @@ function _updateFormatOut (formatOut, options) {
} }
/** /**
* Update a Boolean attribute of the this.options Object. * Update a boolean attribute of the this.options Object.
* @private * @private
* @param {String} key * @param {string} key
* @param {Boolean} val * @param {boolean} val
* @throws {Error} Invalid key * @throws {Error} Invalid key
*/ */
function _setBooleanOption (key, val) { function _setBooleanOption (key, val) {
@@ -785,6 +885,7 @@ module.exports = function (Sharp) {
webp, webp,
tiff, tiff,
heif, heif,
gif,
raw, raw,
tile, tile,
// Private // Private

View File

@@ -13,7 +13,8 @@ module.exports = function () {
const platformId = [`${platform}${libc}`]; const platformId = [`${platform}${libc}`];
if (arch === 'arm') { if (arch === 'arm') {
platformId.push(`armv${env.npm_config_arm_version || process.config.variables.arm_version || '6'}`); const fallback = process.versions.electron ? '7' : '6';
platformId.push(`armv${env.npm_config_arm_version || process.config.variables.arm_version || fallback}`);
} else if (arch === 'arm64') { } else if (arch === 'arm64') {
platformId.push(`arm64v${env.npm_config_arm_version || '8'}`); platformId.push(`arm64v${env.npm_config_arm_version || '8'}`);
} else { } else {

View File

@@ -85,21 +85,30 @@ const mapFitToCanvas = {
outside: 'min' outside: 'min'
}; };
/**
* @private
*/
function isRotationExpected (options) {
return (options.angle % 360) !== 0 || options.useExifOrientation === true || options.rotationAngle !== 0;
}
/** /**
* Resize image to `width`, `height` or `width x height`. * Resize image to `width`, `height` or `width x height`.
* *
* When both a `width` and `height` are provided, the possible methods by which the image should **fit** these are: * When both a `width` and `height` are provided, the possible methods by which the image should **fit** these are:
* - `cover`: Crop to cover both provided dimensions (the default). * - `cover`: (default) Preserving aspect ratio, ensure the image covers both provided dimensions by cropping/clipping to fit.
* - `contain`: Embed within both provided dimensions. * - `contain`: Preserving aspect ratio, contain within both provided dimensions using "letterboxing" where necessary.
* - `fill`: Ignore the aspect ratio of the input and stretch to both provided dimensions. * - `fill`: Ignore the aspect ratio of the input and stretch to both provided dimensions.
* - `inside`: Preserving aspect ratio, resize the image to be as large as possible while ensuring its dimensions are less than or equal to both those specified. * - `inside`: Preserving aspect ratio, resize the image to be as large as possible while ensuring its dimensions are less than or equal to both those specified.
* - `outside`: Preserving aspect ratio, resize the image to be as small as possible while ensuring its dimensions are greater than or equal to both those specified. * - `outside`: Preserving aspect ratio, resize the image to be as small as possible while ensuring its dimensions are greater than or equal to both those specified.
*
* Some of these values are based on the [object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) CSS property. * Some of these values are based on the [object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) CSS property.
* *
* When using a `fit` of `cover` or `contain`, the default **position** is `centre`. Other options are: * When using a `fit` of `cover` or `contain`, the default **position** is `centre`. Other options are:
* - `sharp.position`: `top`, `right top`, `right`, `right bottom`, `bottom`, `left bottom`, `left`, `left top`. * - `sharp.position`: `top`, `right top`, `right`, `right bottom`, `bottom`, `left bottom`, `left`, `left top`.
* - `sharp.gravity`: `north`, `northeast`, `east`, `southeast`, `south`, `southwest`, `west`, `northwest`, `center` or `centre`. * - `sharp.gravity`: `north`, `northeast`, `east`, `southeast`, `south`, `southwest`, `west`, `northwest`, `center` or `centre`.
* - `sharp.strategy`: `cover` only, dynamically crop using either the `entropy` or `attention` strategy. * - `sharp.strategy`: `cover` only, dynamically crop using either the `entropy` or `attention` strategy.
*
* Some of these values are based on the [object-position](https://developer.mozilla.org/en-US/docs/Web/CSS/object-position) CSS property. * Some of these values are based on the [object-position](https://developer.mozilla.org/en-US/docs/Web/CSS/object-position) CSS property.
* *
* The experimental strategy-based approach resizes so one dimension is at its target length * The experimental strategy-based approach resizes so one dimension is at its target length
@@ -181,8 +190,8 @@ const mapFitToCanvas = {
* .toBuffer() * .toBuffer()
* ); * );
* *
* @param {Number} [width] - pixels wide the resultant image should be. Use `null` or `undefined` to auto-scale the width to match the height. * @param {number} [width] - pixels wide the resultant image should be. Use `null` or `undefined` to auto-scale the width to match the height.
* @param {Number} [height] - pixels high the resultant image should be. Use `null` or `undefined` to auto-scale the height to match the width. * @param {number} [height] - pixels high the resultant image should be. Use `null` or `undefined` to auto-scale the height to match the width.
* @param {Object} [options] * @param {Object} [options]
* @param {String} [options.width] - alternative means of specifying `width`. If both are present this take priority. * @param {String} [options.width] - alternative means of specifying `width`. If both are present this take priority.
* @param {String} [options.height] - alternative means of specifying `height`. If both are present this take priority. * @param {String} [options.height] - alternative means of specifying `height`. If both are present this take priority.
@@ -293,11 +302,11 @@ function resize (width, height, options) {
* }) * })
* ... * ...
* *
* @param {(Number|Object)} extend - single pixel count to add to all edges or an Object with per-edge counts * @param {(number|Object)} extend - single pixel count to add to all edges or an Object with per-edge counts
* @param {Number} [extend.top] * @param {number} [extend.top]
* @param {Number} [extend.left] * @param {number} [extend.left]
* @param {Number} [extend.bottom] * @param {number} [extend.bottom]
* @param {Number} [extend.right] * @param {number} [extend.right]
* @param {String|Object} [extend.background={r: 0, g: 0, b: 0, alpha: 1}] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black without transparency. * @param {String|Object} [extend.background={r: 0, g: 0, b: 0, alpha: 1}] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black without transparency.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
@@ -327,7 +336,7 @@ function extend (extend) {
} }
/** /**
* Extract a region of the image. * Extract/crop a region of the image.
* *
* - Use `extract` before `resize` for pre-resize extraction. * - Use `extract` before `resize` for pre-resize extraction.
* - Use `extract` after `resize` for post-resize extraction. * - Use `extract` after `resize` for post-resize extraction.
@@ -349,10 +358,10 @@ function extend (extend) {
* }); * });
* *
* @param {Object} options - describes the region to extract using integral pixel values * @param {Object} options - describes the region to extract using integral pixel values
* @param {Number} options.left - zero-indexed offset from left edge * @param {number} options.left - zero-indexed offset from left edge
* @param {Number} options.top - zero-indexed offset from top edge * @param {number} options.top - zero-indexed offset from top edge
* @param {Number} options.width - width of region to extract * @param {number} options.width - width of region to extract
* @param {Number} options.height - height of region to extract * @param {number} options.height - height of region to extract
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -367,7 +376,7 @@ function extract (options) {
} }
}, this); }, this);
// Ensure existing rotation occurs before pre-resize extraction // Ensure existing rotation occurs before pre-resize extraction
if (suffix === 'Pre' && ((this.options.angle % 360) !== 0 || this.options.useExifOrientation === true || this.options.rotationAngle !== 0)) { if (suffix === 'Pre' && isRotationExpected(this.options)) {
this.options.rotateBeforePreExtract = true; this.options.rotateBeforePreExtract = true;
} }
return this; return this;
@@ -379,7 +388,7 @@ function extract (options) {
* *
* The `info` response Object will contain `trimOffsetLeft` and `trimOffsetTop` properties. * The `info` response Object will contain `trimOffsetLeft` and `trimOffsetTop` properties.
* *
* @param {Number} [threshold=10] the allowed difference from the top-left pixel, a number greater than zero. * @param {number} [threshold=10] the allowed difference from the top-left pixel, a number greater than zero.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -391,6 +400,9 @@ function trim (threshold) {
} else { } else {
throw is.invalidParameterError('threshold', 'number greater than zero', threshold); throw is.invalidParameterError('threshold', 'number greater than zero', threshold);
} }
if (this.options.trimThreshold && isRotationExpected(this.options)) {
this.options.rotateBeforePreExtract = true;
}
return this; return this;
} }

View File

@@ -23,7 +23,7 @@ let versions = {
vips: sharp.libvipsVersion() vips: sharp.libvipsVersion()
}; };
try { try {
versions = require('../vendor/versions.json'); versions = require(`../vendor/${versions.vips}/versions.json`);
} catch (err) {} } catch (err) {}
/** /**
@@ -39,10 +39,10 @@ try {
* sharp.cache( { files: 0 } ); * sharp.cache( { files: 0 } );
* sharp.cache(false); * sharp.cache(false);
* *
* @param {Object|Boolean} [options=true] - Object with the following attributes, or Boolean where true uses default cache settings and false removes all caching * @param {Object|boolean} [options=true] - Object with the following attributes, or boolean where true uses default cache settings and false removes all caching
* @param {Number} [options.memory=50] - is the maximum memory in MB to use for this cache * @param {number} [options.memory=50] - is the maximum memory in MB to use for this cache
* @param {Number} [options.files=20] - is the maximum number of files to hold open * @param {number} [options.files=20] - is the maximum number of files to hold open
* @param {Number} [options.items=100] - is the maximum number of operations to cache * @param {number} [options.items=100] - is the maximum number of operations to cache
* @returns {Object} * @returns {Object}
*/ */
function cache (options) { function cache (options) {
@@ -77,8 +77,8 @@ cache(true);
* sharp.concurrency(2); // 2 * sharp.concurrency(2); // 2
* sharp.concurrency(0); // 4 * sharp.concurrency(0); // 4
* *
* @param {Number} [concurrency] * @param {number} [concurrency]
* @returns {Number} concurrency * @returns {number} concurrency
*/ */
function concurrency (concurrency) { function concurrency (concurrency) {
return sharp.concurrency(is.integer(concurrency) ? concurrency : null); return sharp.concurrency(is.integer(concurrency) ? concurrency : null);
@@ -124,8 +124,8 @@ function counters () {
* const simd = sharp.simd(false); * const simd = sharp.simd(false);
* // prevent libvips from using liborc at runtime * // prevent libvips from using liborc at runtime
* *
* @param {Boolean} [simd=true] * @param {boolean} [simd=true]
* @returns {Boolean} * @returns {boolean}
*/ */
function simd (simd) { function simd (simd) {
return sharp.simd(is.bool(simd) ? simd : null); return sharp.simd(is.bool(simd) ? simd : null);

View File

@@ -1,7 +1,7 @@
{ {
"name": "sharp", "name": "sharp",
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP and TIFF images", "description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP and TIFF images",
"version": "0.24.1", "version": "0.26.0-beta1",
"author": "Lovell Fuller <npm@lovell.info>", "author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://github.com/lovell/sharp", "homepage": "https://github.com/lovell/sharp",
"contributors": [ "contributors": [
@@ -65,17 +65,21 @@
"Andargor <andargor@yahoo.com>", "Andargor <andargor@yahoo.com>",
"Paul Neave <paul.neave@gmail.com>", "Paul Neave <paul.neave@gmail.com>",
"Brendan Kennedy <brenwken@gmail.com>", "Brendan Kennedy <brenwken@gmail.com>",
"Brychan Bennett-Odlum <git@brychan.io>" "Brychan Bennett-Odlum <git@brychan.io>",
"Edward Silverton <e.silverton@gmail.com>",
"Roman Malieiev <aromaleev@gmail.com>",
"Tomas Szabo <tomas.szabo@deftomat.com>",
"Robert O'Rourke <robert@o-rourke.org>"
], ],
"scripts": { "scripts": {
"install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node-gyp rebuild && node install/dll-copy)", "install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node-gyp rebuild && node install/dll-copy)",
"clean": "rm -rf node_modules/ build/ vendor/ .nyc_output/ coverage/ test/fixtures/output.*", "clean": "rm -rf node_modules/ build/ vendor/ .nyc_output/ coverage/ test/fixtures/output.*",
"test": "semistandard && cpplint && npm run test-unit && npm run test-licensing && prebuild-ci", "test": "semistandard && cpplint && npm run test-unit && npm run test-licensing && node install/prebuild-ci",
"test-unit": "nyc --reporter=lcov --branches=99 mocha --slow=5000 --timeout=60000 ./test/unit/*.js", "test-unit": "nyc --reporter=lcov --branches=99 mocha --slow=5000 --timeout=60000 ./test/unit/*.js",
"test-licensing": "license-checker --production --summary --onlyAllow=\"Apache-2.0;BSD;ISC;MIT\"", "test-licensing": "license-checker --production --summary --onlyAllow=\"Apache-2.0;BSD;ISC;MIT\"",
"test-coverage": "./test/coverage/report.sh", "test-coverage": "./test/coverage/report.sh",
"test-leak": "./test/leak/leak.sh", "test-leak": "./test/leak/leak.sh",
"docs-build": "for m in constructor input resize composite operation colour channel output utility; do documentation build --shallow --format=md --markdown-toc=false lib/$m.js >docs/api-$m.md; done", "docs-build": "documentation lint lib && for m in constructor input resize composite operation colour channel output utility; do documentation build --shallow --format=md --markdown-toc=false lib/$m.js >docs/api-$m.md; done && node docs/search-index/build",
"docs-serve": "cd docs && npx serve", "docs-serve": "cd docs && npx serve",
"docs-publish": "cd docs && npx firebase-tools deploy --project pixelplumbing --only hosting:pixelplumbing-sharp" "docs-publish": "cd docs && npx firebase-tools deploy --project pixelplumbing --only hosting:pixelplumbing-sharp"
}, },
@@ -83,6 +87,7 @@
"files": [ "files": [
"binding.gyp", "binding.gyp",
"install/**", "install/**",
"!install/prebuild-ci.js",
"lib/**", "lib/**",
"src/**" "src/**"
], ],
@@ -109,40 +114,46 @@
"dependencies": { "dependencies": {
"color": "^3.1.2", "color": "^3.1.2",
"detect-libc": "^1.0.3", "detect-libc": "^1.0.3",
"nan": "^2.14.0", "node-addon-api": "^3.0.0",
"npmlog": "^4.1.2", "npmlog": "^4.1.2",
"prebuild-install": "^5.3.3", "prebuild-install": "^5.3.5",
"semver": "^7.1.3", "semver": "^7.3.2",
"simple-get": "^3.1.0", "simple-get": "^4.0.0",
"tar": "^6.0.1", "tar-fs": "^2.1.0",
"tunnel-agent": "^0.6.0" "tunnel-agent": "^0.6.0"
}, },
"devDependencies": { "devDependencies": {
"async": "^3.1.1", "async": "^3.2.0",
"cc": "^2.0.1", "cc": "^2.0.1",
"decompress-zip": "^0.3.2", "decompress-zip": "^0.3.2",
"documentation": "^12.1.4", "documentation": "^13.0.2",
"exif-reader": "^1.0.3", "exif-reader": "^1.0.3",
"icc": "^1.0.0", "icc": "^2.0.0",
"license-checker": "^25.0.1", "license-checker": "^25.0.1",
"mocha": "^7.0.1", "mocha": "^8.1.1",
"mock-fs": "^4.10.4", "mock-fs": "^4.13.0",
"nyc": "^15.0.0", "nyc": "^15.1.0",
"prebuild": "^10.0.0", "prebuild": "^10.0.1",
"prebuild-ci": "^3.1.0",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"semistandard": "^14.2.0" "semistandard": "^14.2.3"
}, },
"license": "Apache-2.0", "license": "Apache-2.0",
"config": { "config": {
"libvips": "8.9.0" "libvips": "8.10.0",
"runtime": "napi",
"target": 3
}, },
"engines": { "engines": {
"node": ">=10.13.0" "node": ">=10.16.0"
}, },
"funding": { "funding": {
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"binary": {
"napi_versions": [
3
]
},
"semistandard": { "semistandard": {
"env": [ "env": [
"mocha" "mocha"

View File

@@ -17,11 +17,10 @@
#include <string.h> #include <string.h>
#include <vector> #include <vector>
#include <queue> #include <queue>
#include <map>
#include <mutex> // NOLINT(build/c++11) #include <mutex> // NOLINT(build/c++11)
#include <node.h> #include <napi.h>
#include <node_buffer.h>
#include <nan.h>
#include <vips/vips8> #include <vips/vips8>
#include "common.h" #include "common.h"
@@ -30,66 +29,92 @@ using vips::VImage;
namespace sharp { namespace sharp {
// Convenience methods to access the attributes of a v8::Object // Convenience methods to access the attributes of a Napi::Object
bool HasAttr(v8::Local<v8::Object> obj, std::string attr) { bool HasAttr(Napi::Object obj, std::string attr) {
return Nan::Has(obj, Nan::New(attr).ToLocalChecked()).FromJust(); return obj.Has(attr);
} }
std::string AttrAsStr(v8::Local<v8::Object> obj, std::string attr) { std::string AttrAsStr(Napi::Object obj, std::string attr) {
return *Nan::Utf8String(Nan::Get(obj, Nan::New(attr).ToLocalChecked()).ToLocalChecked()); return obj.Get(attr).As<Napi::String>();
} }
std::vector<double> AttrAsRgba(v8::Local<v8::Object> obj, std::string attr) { uint32_t AttrAsUint32(Napi::Object obj, std::string attr) {
v8::Local<v8::Object> background = AttrAs<v8::Object>(obj, attr); return obj.Get(attr).As<Napi::Number>().Uint32Value();
std::vector<double> rgba(4); }
for (unsigned int i = 0; i < 4; i++) { int32_t AttrAsInt32(Napi::Object obj, std::string attr) {
rgba[i] = AttrTo<double>(background, i); return obj.Get(attr).As<Napi::Number>().Int32Value();
}
int32_t AttrAsInt32(Napi::Object obj, unsigned int const attr) {
return obj.Get(attr).As<Napi::Number>().Int32Value();
}
double AttrAsDouble(Napi::Object obj, std::string attr) {
return obj.Get(attr).As<Napi::Number>().DoubleValue();
}
double AttrAsDouble(Napi::Object obj, unsigned int const attr) {
return obj.Get(attr).As<Napi::Number>().DoubleValue();
}
bool AttrAsBool(Napi::Object obj, std::string attr) {
return obj.Get(attr).As<Napi::Boolean>().Value();
}
std::vector<double> AttrAsRgba(Napi::Object obj, std::string attr) {
Napi::Array background = obj.Get(attr).As<Napi::Array>();
std::vector<double> rgba(background.Length());
for (unsigned int i = 0; i < background.Length(); i++) {
rgba[i] = AttrAsDouble(background, i);
} }
return rgba; return rgba;
} }
std::vector<int32_t> AttrAsInt32Vector(Napi::Object obj, std::string attr) {
Napi::Array array = obj.Get(attr).As<Napi::Array>();
std::vector<int32_t> vector(array.Length());
for (unsigned int i = 0; i < array.Length(); i++) {
vector[i] = AttrAsInt32(array, i);
}
return vector;
}
// Create an InputDescriptor instance from a v8::Object describing an input image // Create an InputDescriptor instance from a Napi::Object describing an input image
InputDescriptor* CreateInputDescriptor( InputDescriptor* CreateInputDescriptor(Napi::Object input) {
v8::Local<v8::Object> input, std::vector<v8::Local<v8::Object>> &buffersToPersist
) {
Nan::HandleScope();
InputDescriptor *descriptor = new InputDescriptor; InputDescriptor *descriptor = new InputDescriptor;
if (HasAttr(input, "file")) { if (HasAttr(input, "file")) {
descriptor->file = AttrAsStr(input, "file"); descriptor->file = AttrAsStr(input, "file");
} else if (HasAttr(input, "buffer")) { } else if (HasAttr(input, "buffer")) {
v8::Local<v8::Object> buffer = AttrAs<v8::Object>(input, "buffer"); Napi::Buffer<char> buffer = input.Get("buffer").As<Napi::Buffer<char>>();
descriptor->bufferLength = node::Buffer::Length(buffer); descriptor->bufferLength = buffer.Length();
descriptor->buffer = node::Buffer::Data(buffer); descriptor->buffer = buffer.Data();
descriptor->isBuffer = TRUE; descriptor->isBuffer = TRUE;
buffersToPersist.push_back(buffer);
} }
descriptor->failOnError = AttrTo<bool>(input, "failOnError"); descriptor->failOnError = AttrAsBool(input, "failOnError");
// Density for vector-based input // Density for vector-based input
if (HasAttr(input, "density")) { if (HasAttr(input, "density")) {
descriptor->density = AttrTo<double>(input, "density"); descriptor->density = AttrAsDouble(input, "density");
} }
// Raw pixel input // Raw pixel input
if (HasAttr(input, "rawChannels")) { if (HasAttr(input, "rawChannels")) {
descriptor->rawChannels = AttrTo<uint32_t>(input, "rawChannels"); descriptor->rawChannels = AttrAsUint32(input, "rawChannels");
descriptor->rawWidth = AttrTo<uint32_t>(input, "rawWidth"); descriptor->rawWidth = AttrAsUint32(input, "rawWidth");
descriptor->rawHeight = AttrTo<uint32_t>(input, "rawHeight"); descriptor->rawHeight = AttrAsUint32(input, "rawHeight");
} }
// Multi-page input (GIF, TIFF, PDF) // Multi-page input (GIF, TIFF, PDF)
if (HasAttr(input, "pages")) { if (HasAttr(input, "pages")) {
descriptor->pages = AttrTo<int32_t>(input, "pages"); descriptor->pages = AttrAsInt32(input, "pages");
} }
if (HasAttr(input, "page")) { if (HasAttr(input, "page")) {
descriptor->page = AttrTo<uint32_t>(input, "page"); descriptor->page = AttrAsUint32(input, "page");
}
// Multi-level input (OpenSlide)
if (HasAttr(input, "level")) {
descriptor->level = AttrAsUint32(input, "level");
} }
// Create new image // Create new image
if (HasAttr(input, "createChannels")) { if (HasAttr(input, "createChannels")) {
descriptor->createChannels = AttrTo<uint32_t>(input, "createChannels"); descriptor->createChannels = AttrAsUint32(input, "createChannels");
descriptor->createWidth = AttrTo<uint32_t>(input, "createWidth"); descriptor->createWidth = AttrAsUint32(input, "createWidth");
descriptor->createHeight = AttrTo<uint32_t>(input, "createHeight"); descriptor->createHeight = AttrAsUint32(input, "createHeight");
descriptor->createBackground = AttrAsRgba(input, "createBackground"); descriptor->createBackground = AttrAsRgba(input, "createBackground");
} }
// Limit input images to a given number of pixels, where pixels = width * height // Limit input images to a given number of pixels, where pixels = width * height
descriptor->limitInputPixels = AttrTo<uint32_t>(input, "limitInputPixels"); descriptor->limitInputPixels = AttrAsUint32(input, "limitInputPixels");
// Allow switch from random to sequential access // Allow switch from random to sequential access
descriptor->access = AttrTo<bool>(input, "sequentialRead") ? VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_RANDOM; descriptor->access = AttrAsBool(input, "sequentialRead") ? VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_RANDOM;
return descriptor; return descriptor;
} }
@@ -112,6 +137,9 @@ namespace sharp {
bool IsWebp(std::string const &str) { bool IsWebp(std::string const &str) {
return EndsWith(str, ".webp") || EndsWith(str, ".WEBP"); return EndsWith(str, ".webp") || EndsWith(str, ".WEBP");
} }
bool IsGif(std::string const &str) {
return EndsWith(str, ".gif") || EndsWith(str, ".GIF");
}
bool IsTiff(std::string const &str) { bool IsTiff(std::string const &str) {
return EndsWith(str, ".tif") || EndsWith(str, ".tiff") || EndsWith(str, ".TIF") || EndsWith(str, ".TIFF"); return EndsWith(str, ".tif") || EndsWith(str, ".tiff") || EndsWith(str, ".TIF") || EndsWith(str, ".TIFF");
} }
@@ -160,32 +188,42 @@ namespace sharp {
return id; return id;
} }
std::map<std::string, ImageType> loaderToType = {
{ "jpegload", ImageType::JPEG },
{ "jpegload_buffer", ImageType::JPEG },
{ "pngload", ImageType::PNG },
{ "pngload_buffer", ImageType::PNG },
{ "webpload", ImageType::WEBP },
{ "webpload_buffer", ImageType::WEBP },
{ "tiffload", ImageType::TIFF },
{ "tiffload_buffer", ImageType::TIFF },
{ "gifload", ImageType::GIF },
{ "gifload_buffer", ImageType::GIF },
{ "svgload", ImageType::SVG },
{ "svgload_buffer", ImageType::SVG },
{ "heifload", ImageType::HEIF },
{ "heifload_buffer", ImageType::HEIF },
{ "pdfload", ImageType::PDF },
{ "pdfload_buffer", ImageType::PDF },
{ "magickload", ImageType::MAGICK },
{ "magickload_buffer", ImageType::MAGICK },
{ "openslideload", ImageType::OPENSLIDE },
{ "ppmload", ImageType::PPM },
{ "fitsload", ImageType::FITS },
{ "vipsload", ImageType::VIPS },
{ "rawload", ImageType::RAW }
};
/* /*
Determine image format of a buffer. Determine image format of a buffer.
*/ */
ImageType DetermineImageType(void *buffer, size_t const length) { ImageType DetermineImageType(void *buffer, size_t const length) {
ImageType imageType = ImageType::UNKNOWN; ImageType imageType = ImageType::UNKNOWN;
char const *load = vips_foreign_find_load_buffer(buffer, length); char const *load = vips_foreign_find_load_buffer(buffer, length);
if (load != NULL) { if (load != nullptr) {
std::string const loader = load; auto it = loaderToType.find(vips_nickname_find(g_type_from_name(load)));
if (EndsWith(loader, "JpegBuffer")) { if (it != loaderToType.end()) {
imageType = ImageType::JPEG; imageType = it->second;
} else if (EndsWith(loader, "PngBuffer")) {
imageType = ImageType::PNG;
} else if (EndsWith(loader, "WebpBuffer")) {
imageType = ImageType::WEBP;
} else if (EndsWith(loader, "TiffBuffer")) {
imageType = ImageType::TIFF;
} else if (EndsWith(loader, "GifBuffer")) {
imageType = ImageType::GIF;
} else if (EndsWith(loader, "SvgBuffer")) {
imageType = ImageType::SVG;
} else if (EndsWith(loader, "HeifBuffer")) {
imageType = ImageType::HEIF;
} else if (EndsWith(loader, "PdfBuffer")) {
imageType = ImageType::PDF;
} else if (EndsWith(loader, "MagickBuffer")) {
imageType = ImageType::MAGICK;
} }
} }
return imageType; return imageType;
@@ -198,36 +236,12 @@ namespace sharp {
ImageType imageType = ImageType::UNKNOWN; ImageType imageType = ImageType::UNKNOWN;
char const *load = vips_foreign_find_load(file); char const *load = vips_foreign_find_load(file);
if (load != nullptr) { if (load != nullptr) {
std::string const loader = load; auto it = loaderToType.find(vips_nickname_find(g_type_from_name(load)));
if (EndsWith(loader, "JpegFile")) { if (it != loaderToType.end()) {
imageType = ImageType::JPEG; imageType = it->second;
} else if (EndsWith(loader, "PngFile")) {
imageType = ImageType::PNG;
} else if (EndsWith(loader, "WebpFile")) {
imageType = ImageType::WEBP;
} else if (EndsWith(loader, "Openslide")) {
imageType = ImageType::OPENSLIDE;
} else if (EndsWith(loader, "TiffFile")) {
imageType = ImageType::TIFF;
} else if (EndsWith(loader, "GifFile")) {
imageType = ImageType::GIF;
} else if (EndsWith(loader, "SvgFile")) {
imageType = ImageType::SVG;
} else if (EndsWith(loader, "HeifFile")) {
imageType = ImageType::HEIF;
} else if (EndsWith(loader, "PdfFile")) {
imageType = ImageType::PDF;
} else if (EndsWith(loader, "Ppm")) {
imageType = ImageType::PPM;
} else if (EndsWith(loader, "Fits")) {
imageType = ImageType::FITS;
} else if (EndsWith(loader, "Vips")) {
imageType = ImageType::VIPS;
} else if (EndsWith(loader, "Magick") || EndsWith(loader, "MagickFile")) {
imageType = ImageType::MAGICK;
} }
} else { } else {
if (EndsWith(vips::VError().what(), " not found\n")) { if (EndsWith(vips::VError().what(), " does not exist\n")) {
imageType = ImageType::MISSING; imageType = ImageType::MISSING;
} }
} }
@@ -239,6 +253,8 @@ namespace sharp {
*/ */
bool ImageTypeSupportsPage(ImageType imageType) { bool ImageTypeSupportsPage(ImageType imageType) {
return return
imageType == ImageType::WEBP ||
imageType == ImageType::MAGICK ||
imageType == ImageType::GIF || imageType == ImageType::GIF ||
imageType == ImageType::TIFF || imageType == ImageType::TIFF ||
imageType == ImageType::HEIF || imageType == ImageType::HEIF ||
@@ -270,6 +286,9 @@ namespace sharp {
vips::VOption *option = VImage::option() vips::VOption *option = VImage::option()
->set("access", descriptor->access) ->set("access", descriptor->access)
->set("fail", descriptor->failOnError); ->set("fail", descriptor->failOnError);
if (imageType == ImageType::SVG) {
option->set("unlimited", TRUE);
}
if (imageType == ImageType::SVG || imageType == ImageType::PDF) { if (imageType == ImageType::SVG || imageType == ImageType::PDF) {
option->set("dpi", descriptor->density); option->set("dpi", descriptor->density);
} }
@@ -280,6 +299,9 @@ namespace sharp {
option->set("n", descriptor->pages); option->set("n", descriptor->pages);
option->set("page", descriptor->page); option->set("page", descriptor->page);
} }
if (imageType == ImageType::OPENSLIDE) {
option->set("level", descriptor->level);
}
image = VImage::new_from_buffer(descriptor->buffer, descriptor->bufferLength, nullptr, option); image = VImage::new_from_buffer(descriptor->buffer, descriptor->bufferLength, nullptr, option);
if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) { if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
image = SetDensity(image, descriptor->density); image = SetDensity(image, descriptor->density);
@@ -316,6 +338,9 @@ namespace sharp {
vips::VOption *option = VImage::option() vips::VOption *option = VImage::option()
->set("access", descriptor->access) ->set("access", descriptor->access)
->set("fail", descriptor->failOnError); ->set("fail", descriptor->failOnError);
if (imageType == ImageType::SVG) {
option->set("unlimited", TRUE);
}
if (imageType == ImageType::SVG || imageType == ImageType::PDF) { if (imageType == ImageType::SVG || imageType == ImageType::PDF) {
option->set("dpi", descriptor->density); option->set("dpi", descriptor->density);
} }
@@ -326,6 +351,9 @@ namespace sharp {
option->set("n", descriptor->pages); option->set("n", descriptor->pages);
option->set("page", descriptor->page); option->set("page", descriptor->page);
} }
if (imageType == ImageType::OPENSLIDE) {
option->set("level", descriptor->level);
}
image = VImage::new_from_file(descriptor->file.data(), option); image = VImage::new_from_file(descriptor->file.data(), option);
if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) { if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
image = SetDensity(image, descriptor->density); image = SetDensity(image, descriptor->density);
@@ -395,6 +423,38 @@ namespace sharp {
return copy; return copy;
} }
/*
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;
if (pageHeight == 0 && image.get_typeof(VIPS_META_PAGE_HEIGHT) == G_TYPE_INT) {
pageHeight = image.get_int(VIPS_META_PAGE_HEIGHT);
}
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!
VImage copy = image.copy();
copy.set(VIPS_META_PAGE_HEIGHT, pageHeight);
if (hasDelay) copy.set("delay", delay);
if (loop != -1) copy.set("loop", loop);
return copy;
}
/* /*
Does this image have a non-default density? Does this image have a non-default density?
*/ */
@@ -425,25 +485,30 @@ namespace sharp {
Check the proposed format supports the current dimensions. Check the proposed format supports the current dimensions.
*/ */
void AssertImageTypeDimensions(VImage image, ImageType const imageType) { void AssertImageTypeDimensions(VImage image, ImageType const imageType) {
const int height = image.get_typeof("pageHeight") == G_TYPE_INT
? image.get_int("pageHeight")
: image.height();
if (imageType == ImageType::JPEG) { if (imageType == ImageType::JPEG) {
if (image.width() > 65535 || image.height() > 65535) { if (image.width() > 65535 || height > 65535) {
throw vips::VError("Processed image is too large for the JPEG format"); throw vips::VError("Processed image is too large for the JPEG format");
} }
} else if (imageType == ImageType::WEBP) { } else if (imageType == ImageType::WEBP) {
if (image.width() > 16383 || image.height() > 16383) { if (image.width() > 16383 || height > 16383) {
throw vips::VError("Processed image is too large for the WebP format"); throw vips::VError("Processed image is too large for the WebP format");
} }
} else if (imageType == ImageType::GIF) {
if (image.width() > 65535 || height > 65535) {
throw vips::VError("Processed image is too large for the GIF format");
}
} }
} }
/* /*
Called when a Buffer undergoes GC, required to support mixed runtime libraries in Windows Called when a Buffer undergoes GC, required to support mixed runtime libraries in Windows
*/ */
void FreeCallback(char* data, void* hint) { std::function<void(void*, char*)> FreeCallback = [](void*, char* data) {
if (data != nullptr) { g_free(data);
g_free(data); };
}
}
/* /*
Temporary buffer of warnings Temporary buffer of warnings
@@ -697,4 +762,25 @@ namespace sharp {
return std::make_tuple(image, alphaColour); return std::make_tuple(image, alphaColour);
} }
/*
Removes alpha channel, if any.
*/
VImage RemoveAlpha(VImage image) {
if (HasAlpha(image)) {
image = image.extract_band(0, VImage::option()->set("n", image.bands() - 1));
}
return image;
}
/*
Ensures alpha channel, if missing.
*/
VImage EnsureAlpha(VImage image) {
if (!HasAlpha(image)) {
std::vector<double> alpha;
alpha.push_back(sharp::MaximumImageAlpha(image.interpretation()));
image = image.bandjoin_const(alpha);
}
return image;
}
} // namespace sharp } // namespace sharp

View File

@@ -19,14 +19,13 @@
#include <tuple> #include <tuple>
#include <vector> #include <vector>
#include <node.h> #include <napi.h>
#include <nan.h>
#include <vips/vips8> #include <vips/vips8>
// Verify platform and compiler compatibility // Verify platform and compiler compatibility
#if (VIPS_MAJOR_VERSION < 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 9)) #if (VIPS_MAJOR_VERSION < 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 9))
#error "libvips version 8.9.0+ is required - please see https://sharp.pixelplumbing.com/install" #error "libvips version 8.9.2+ is required - please see https://sharp.pixelplumbing.com/install"
#endif #endif
#if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6))) #if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6)))
@@ -58,6 +57,7 @@ namespace sharp {
int rawHeight; int rawHeight;
int pages; int pages;
int page; int page;
int level;
int createChannels; int createChannels;
int createWidth; int createWidth;
int createHeight; int createHeight;
@@ -76,29 +76,27 @@ namespace sharp {
rawHeight(0), rawHeight(0),
pages(1), pages(1),
page(0), page(0),
level(0),
createChannels(0), createChannels(0),
createWidth(0), createWidth(0),
createHeight(0), createHeight(0),
createBackground{ 0.0, 0.0, 0.0, 255.0 } {} createBackground{ 0.0, 0.0, 0.0, 255.0 } {}
}; };
// Convenience methods to access the attributes of a v8::Object // Convenience methods to access the attributes of a Napi::Object
bool HasAttr(v8::Local<v8::Object> obj, std::string attr); bool HasAttr(Napi::Object obj, std::string attr);
std::string AttrAsStr(v8::Local<v8::Object> obj, std::string attr); std::string AttrAsStr(Napi::Object obj, std::string attr);
std::vector<double> AttrAsRgba(v8::Local<v8::Object> obj, std::string attr); uint32_t AttrAsUint32(Napi::Object obj, std::string attr);
template<typename T> v8::Local<T> AttrAs(v8::Local<v8::Object> obj, std::string attr) { int32_t AttrAsInt32(Napi::Object obj, std::string attr);
return Nan::Get(obj, Nan::New(attr).ToLocalChecked()).ToLocalChecked().As<T>(); int32_t AttrAsInt32(Napi::Object obj, unsigned int const attr);
} double AttrAsDouble(Napi::Object obj, std::string attr);
template<typename T> T AttrTo(v8::Local<v8::Object> obj, std::string attr) { double AttrAsDouble(Napi::Object obj, unsigned int const attr);
return Nan::To<T>(Nan::Get(obj, Nan::New(attr).ToLocalChecked()).ToLocalChecked()).FromJust(); bool AttrAsBool(Napi::Object obj, std::string attr);
} std::vector<double> AttrAsRgba(Napi::Object obj, std::string attr);
template<typename T> T AttrTo(v8::Local<v8::Object> obj, int attr) { std::vector<int32_t> AttrAsInt32Vector(Napi::Object obj, std::string attr);
return Nan::To<T>(Nan::Get(obj, attr).ToLocalChecked()).FromJust();
}
// Create an InputDescriptor instance from a v8::Object describing an input image // Create an InputDescriptor instance from a Napi::Object describing an input image
InputDescriptor* CreateInputDescriptor( InputDescriptor* CreateInputDescriptor(Napi::Object input);
v8::Local<v8::Object> input, std::vector<v8::Local<v8::Object>> &buffersToPersist); // NOLINT(runtime/references)
enum class ImageType { enum class ImageType {
JPEG, JPEG,
@@ -129,6 +127,7 @@ namespace sharp {
bool IsJpeg(std::string const &str); bool IsJpeg(std::string const &str);
bool IsPng(std::string const &str); bool IsPng(std::string const &str);
bool IsWebp(std::string const &str); bool IsWebp(std::string const &str);
bool IsGif(std::string const &str);
bool IsTiff(std::string const &str); bool IsTiff(std::string const &str);
bool IsHeic(std::string const &str); bool IsHeic(std::string const &str);
bool IsHeif(std::string const &str); bool IsHeif(std::string const &str);
@@ -188,6 +187,12 @@ namespace sharp {
*/ */
VImage RemoveExifOrientation(VImage image); VImage RemoveExifOrientation(VImage image);
/*
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);
/* /*
Does this image have a non-default density? Does this image have a non-default density?
*/ */
@@ -211,7 +216,7 @@ namespace sharp {
/* /*
Called when a Buffer undergoes GC, required to support mixed runtime libraries in Windows Called when a Buffer undergoes GC, required to support mixed runtime libraries in Windows
*/ */
void FreeCallback(char* data, void* hint); extern std::function<void(void*, char*)> FreeCallback;
/* /*
Called with warnings from the glib-registered "VIPS" domain Called with warnings from the glib-registered "VIPS" domain
@@ -275,6 +280,16 @@ namespace sharp {
*/ */
std::tuple<VImage, std::vector<double>> ApplyAlpha(VImage image, std::vector<double> colour); std::tuple<VImage, std::vector<double>> ApplyAlpha(VImage image, std::vector<double> colour);
/*
Removes alpha channel, if any.
*/
VImage RemoveAlpha(VImage image);
/*
Ensures alpha channel, if missing.
*/
VImage EnsureAlpha(VImage image);
} // namespace sharp } // namespace sharp
#endif // SRC_COMMON_H_ #endif // SRC_COMMON_H_

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
// bodies for vips operations // bodies for vips operations
// Wed 01 Jan 2020 12:22:12 PM CET // Sun 5 Jul 22:36:37 BST 2020
// this file is generated automatically, do not edit! // this file is generated automatically, do not edit!
VImage VImage::CMC2LCh( VOption *options ) const VImage VImage::CMC2LCh( VOption *options ) const
@@ -754,6 +754,18 @@ VImage VImage::csvload( const char *filename, VOption *options )
return( out ); return( out );
} }
VImage VImage::csvload_source( VSource source, VOption *options )
{
VImage out;
call( "csvload_source",
(options ? options : VImage::option())->
set( "out", &out )->
set( "source", source ) );
return( out );
}
void VImage::csvsave( const char *filename, VOption *options ) const void VImage::csvsave( const char *filename, VOption *options ) const
{ {
call( "csvsave", call( "csvsave",
@@ -762,6 +774,14 @@ void VImage::csvsave( const char *filename, VOption *options ) const
set( "filename", filename ) ); set( "filename", filename ) );
} }
void VImage::csvsave_target( VTarget target, VOption *options ) const
{
call( "csvsave_target",
(options ? options : VImage::option())->
set( "in", *this )->
set( "target", target ) );
}
VImage VImage::dE00( VImage right, VOption *options ) const VImage VImage::dE00( VImage right, VOption *options ) const
{ {
VImage out; VImage out;
@@ -1218,6 +1238,18 @@ VImage VImage::gifload_buffer( VipsBlob *buffer, VOption *options )
return( out ); return( out );
} }
VImage VImage::gifload_source( VSource source, VOption *options )
{
VImage out;
call( "gifload_source",
(options ? options : VImage::option())->
set( "out", &out )->
set( "source", source ) );
return( out );
}
VImage VImage::globalbalance( VOption *options ) const VImage VImage::globalbalance( VOption *options ) const
{ {
VImage out; VImage out;
@@ -1297,6 +1329,18 @@ VImage VImage::heifload_buffer( VipsBlob *buffer, VOption *options )
return( out ); return( out );
} }
VImage VImage::heifload_source( VSource source, VOption *options )
{
VImage out;
call( "heifload_source",
(options ? options : VImage::option())->
set( "out", &out )->
set( "source", source ) );
return( out );
}
void VImage::heifsave( const char *filename, VOption *options ) const void VImage::heifsave( const char *filename, VOption *options ) const
{ {
call( "heifsave", call( "heifsave",
@@ -1317,6 +1361,14 @@ VipsBlob *VImage::heifsave_buffer( VOption *options ) const
return( buffer ); return( buffer );
} }
void VImage::heifsave_target( VTarget target, VOption *options ) const
{
call( "heifsave_target",
(options ? options : VImage::option())->
set( "in", *this )->
set( "target", target ) );
}
VImage VImage::hist_cum( VOption *options ) const VImage VImage::hist_cum( VOption *options ) const
{ {
VImage out; VImage out;
@@ -2028,6 +2080,18 @@ VImage VImage::matload( const char *filename, VOption *options )
return( out ); return( out );
} }
VImage VImage::matrixinvert( VOption *options ) const
{
VImage out;
call( "matrixinvert",
(options ? options : VImage::option())->
set( "in", *this )->
set( "out", &out ) );
return( out );
}
VImage VImage::matrixload( const char *filename, VOption *options ) VImage VImage::matrixload( const char *filename, VOption *options )
{ {
VImage out; VImage out;
@@ -2040,6 +2104,18 @@ VImage VImage::matrixload( const char *filename, VOption *options )
return( out ); return( out );
} }
VImage VImage::matrixload_source( VSource source, VOption *options )
{
VImage out;
call( "matrixload_source",
(options ? options : VImage::option())->
set( "out", &out )->
set( "source", source ) );
return( out );
}
void VImage::matrixprint( VOption *options ) const void VImage::matrixprint( VOption *options ) const
{ {
call( "matrixprint", call( "matrixprint",
@@ -2055,6 +2131,14 @@ void VImage::matrixsave( const char *filename, VOption *options ) const
set( "filename", filename ) ); set( "filename", filename ) );
} }
void VImage::matrixsave_target( VTarget target, VOption *options ) const
{
call( "matrixsave_target",
(options ? options : VImage::option())->
set( "in", *this )->
set( "target", target ) );
}
double VImage::max( VOption *options ) const double VImage::max( VOption *options ) const
{ {
double out; double out;
@@ -2256,6 +2340,18 @@ VImage VImage::pdfload_buffer( VipsBlob *buffer, VOption *options )
return( out ); return( out );
} }
VImage VImage::pdfload_source( VSource source, VOption *options )
{
VImage out;
call( "pdfload_source",
(options ? options : VImage::option())->
set( "out", &out )->
set( "source", source ) );
return( out );
}
int VImage::percent( double percent, VOption *options ) const int VImage::percent( double percent, VOption *options ) const
{ {
int threshold; int threshold;
@@ -2371,6 +2467,18 @@ VImage VImage::ppmload( const char *filename, VOption *options )
return( out ); return( out );
} }
VImage VImage::ppmload_source( VSource source, VOption *options )
{
VImage out;
call( "ppmload_source",
(options ? options : VImage::option())->
set( "out", &out )->
set( "source", source ) );
return( out );
}
void VImage::ppmsave( const char *filename, VOption *options ) const void VImage::ppmsave( const char *filename, VOption *options ) const
{ {
call( "ppmsave", call( "ppmsave",
@@ -2379,6 +2487,14 @@ void VImage::ppmsave( const char *filename, VOption *options ) const
set( "filename", filename ) ); set( "filename", filename ) );
} }
void VImage::ppmsave_target( VTarget target, VOption *options ) const
{
call( "ppmsave_target",
(options ? options : VImage::option())->
set( "in", *this )->
set( "target", target ) );
}
VImage VImage::premultiply( VOption *options ) const VImage VImage::premultiply( VOption *options ) const
{ {
VImage out; VImage out;

View File

@@ -15,28 +15,16 @@
#include <numeric> #include <numeric>
#include <vector> #include <vector>
#include <node.h> #include <napi.h>
#include <nan.h>
#include <vips/vips8> #include <vips/vips8>
#include "common.h" #include "common.h"
#include "metadata.h" #include "metadata.h"
class MetadataWorker : public Nan::AsyncWorker { class MetadataWorker : public Napi::AsyncWorker {
public: public:
MetadataWorker( MetadataWorker(Napi::Function callback, MetadataBaton *baton, Napi::Function debuglog) :
Nan::Callback *callback, MetadataBaton *baton, Nan::Callback *debuglog, Napi::AsyncWorker(callback), baton(baton), debuglog(Napi::Persistent(debuglog)) {}
std::vector<v8::Local<v8::Object>> const buffersToPersist) :
Nan::AsyncWorker(callback, "sharp:MetadataWorker"),
baton(baton), debuglog(debuglog),
buffersToPersist(buffersToPersist) {
// Protect Buffer objects from GC, keyed on index
std::accumulate(buffersToPersist.begin(), buffersToPersist.end(), 0,
[this](uint32_t index, v8::Local<v8::Object> const buffer) -> uint32_t {
SaveToPersistent(index, buffer);
return index + 1;
});
}
~MetadataWorker() {} ~MetadataWorker() {}
void Execute() { void Execute() {
@@ -86,6 +74,15 @@ class MetadataWorker : public Nan::AsyncWorker {
if (image.get_typeof("heif-primary") == G_TYPE_INT) { if (image.get_typeof("heif-primary") == G_TYPE_INT) {
baton->pagePrimary = image.get_int("heif-primary"); baton->pagePrimary = image.get_int("heif-primary");
} }
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++) {
std::string prefix = "openslide.level[" + std::to_string(l) + "].";
int const width = std::stoi(image.get_string((prefix + "width").data()));
int const height = std::stoi(image.get_string((prefix + "height").data()));
baton->levels.push_back(std::pair<int, int>(width, height));
}
}
baton->hasProfile = sharp::HasProfile(image); baton->hasProfile = sharp::HasProfile(image);
// Derived attributes // Derived attributes
baton->hasAlpha = sharp::HasAlpha(image); baton->hasAlpha = sharp::HasAlpha(image);
@@ -137,140 +134,126 @@ class MetadataWorker : public Nan::AsyncWorker {
vips_thread_shutdown(); vips_thread_shutdown();
} }
void HandleOKCallback() { void OnOK() {
using Nan::New; Napi::Env env = Env();
using Nan::Set; Napi::HandleScope scope(env);
Nan::HandleScope();
v8::Local<v8::Value> argv[2] = { Nan::Null(), Nan::Null() };
if (!baton->err.empty()) {
argv[0] = Nan::Error(baton->err.data());
} else {
// Metadata Object
v8::Local<v8::Object> info = New<v8::Object>();
Set(info, New("format").ToLocalChecked(), New<v8::String>(baton->format).ToLocalChecked());
if (baton->input->bufferLength > 0) {
Set(info, New("size").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(baton->input->bufferLength)));
}
Set(info, New("width").ToLocalChecked(), New<v8::Uint32>(baton->width));
Set(info, New("height").ToLocalChecked(), New<v8::Uint32>(baton->height));
Set(info, New("space").ToLocalChecked(), New<v8::String>(baton->space).ToLocalChecked());
Set(info, New("channels").ToLocalChecked(), New<v8::Uint32>(baton->channels));
Set(info, New("depth").ToLocalChecked(), New<v8::String>(baton->depth).ToLocalChecked());
if (baton->density > 0) {
Set(info, New("density").ToLocalChecked(), New<v8::Uint32>(baton->density));
}
if (!baton->chromaSubsampling.empty()) {
Set(info,
New("chromaSubsampling").ToLocalChecked(),
New<v8::String>(baton->chromaSubsampling).ToLocalChecked());
}
Set(info, New("isProgressive").ToLocalChecked(), New<v8::Boolean>(baton->isProgressive));
if (baton->paletteBitDepth > 0) {
Set(info, New("paletteBitDepth").ToLocalChecked(), New<v8::Uint32>(baton->paletteBitDepth));
}
if (baton->pages > 0) {
Set(info, New("pages").ToLocalChecked(), New<v8::Uint32>(baton->pages));
}
if (baton->pageHeight > 0) {
Set(info, New("pageHeight").ToLocalChecked(), New<v8::Uint32>(baton->pageHeight));
}
if (baton->loop >= 0) {
Set(info, New("loop").ToLocalChecked(), New<v8::Uint32>(baton->loop));
}
if (!baton->delay.empty()) {
int i = 0;
v8::Local<v8::Array> delay = New<v8::Array>(baton->delay.size());
for (int const d : baton->delay) {
Set(delay, i++, New<v8::Number>(d));
}
Set(info, New("delay").ToLocalChecked(), delay);
}
if (baton->pagePrimary > -1) {
Set(info, New("pagePrimary").ToLocalChecked(), New<v8::Uint32>(baton->pagePrimary));
}
Set(info, New("hasProfile").ToLocalChecked(), New<v8::Boolean>(baton->hasProfile));
Set(info, New("hasAlpha").ToLocalChecked(), New<v8::Boolean>(baton->hasAlpha));
if (baton->orientation > 0) {
Set(info, New("orientation").ToLocalChecked(), New<v8::Uint32>(baton->orientation));
}
if (baton->exifLength > 0) {
Set(info,
New("exif").ToLocalChecked(),
Nan::NewBuffer(baton->exif, baton->exifLength, sharp::FreeCallback, nullptr).ToLocalChecked());
}
if (baton->iccLength > 0) {
Set(info,
New("icc").ToLocalChecked(),
Nan::NewBuffer(baton->icc, baton->iccLength, sharp::FreeCallback, nullptr).ToLocalChecked());
}
if (baton->iptcLength > 0) {
Set(info,
New("iptc").ToLocalChecked(),
Nan::NewBuffer(baton->iptc, baton->iptcLength, sharp::FreeCallback, nullptr).ToLocalChecked());
}
if (baton->xmpLength > 0) {
Set(info,
New("xmp").ToLocalChecked(),
Nan::NewBuffer(baton->xmp, baton->xmpLength, sharp::FreeCallback, nullptr).ToLocalChecked());
}
if (baton->tifftagPhotoshopLength > 0) {
Set(info,
New("tifftagPhotoshop").ToLocalChecked(),
Nan::NewBuffer(baton->tifftagPhotoshop, baton->tifftagPhotoshopLength, sharp::FreeCallback, nullptr)
.ToLocalChecked());
}
argv[1] = info;
}
// Dispose of Persistent wrapper around input Buffers so they can be garbage collected
std::accumulate(buffersToPersist.begin(), buffersToPersist.end(), 0,
[this](uint32_t index, v8::Local<v8::Object> const buffer) -> uint32_t {
GetFromPersistent(index);
return index + 1;
});
delete baton->input;
delete baton;
// Handle warnings // Handle warnings
std::string warning = sharp::VipsWarningPop(); std::string warning = sharp::VipsWarningPop();
while (!warning.empty()) { while (!warning.empty()) {
v8::Local<v8::Value> message[1] = { New(warning).ToLocalChecked() }; debuglog.Call({ Napi::String::New(env, warning) });
debuglog->Call(1, message, async_resource);
warning = sharp::VipsWarningPop(); warning = sharp::VipsWarningPop();
} }
// Return to JavaScript if (baton->err.empty()) {
callback->Call(2, argv, async_resource); Napi::Object info = Napi::Object::New(env);
info.Set("format", baton->format);
if (baton->input->bufferLength > 0) {
info.Set("size", baton->input->bufferLength);
}
info.Set("width", baton->width);
info.Set("height", baton->height);
info.Set("space", baton->space);
info.Set("channels", baton->channels);
info.Set("depth", baton->depth);
if (baton->density > 0) {
info.Set("density", baton->density);
}
if (!baton->chromaSubsampling.empty()) {
info.Set("chromaSubsampling", baton->chromaSubsampling);
}
info.Set("isProgressive", baton->isProgressive);
if (baton->paletteBitDepth > 0) {
info.Set("paletteBitDepth", baton->paletteBitDepth);
}
if (baton->pages > 0) {
info.Set("pages", baton->pages);
}
if (baton->pageHeight > 0) {
info.Set("pageHeight", baton->pageHeight);
}
if (baton->loop >= 0) {
info.Set("loop", baton->loop);
}
if (!baton->delay.empty()) {
int i = 0;
Napi::Array delay = Napi::Array::New(env, static_cast<size_t>(baton->delay.size()));
for (int const d : baton->delay) {
delay.Set(i++, d);
}
info.Set("delay", delay);
}
if (baton->pagePrimary > -1) {
info.Set("pagePrimary", baton->pagePrimary);
}
if (!baton->levels.empty()) {
int i = 0;
Napi::Array levels = Napi::Array::New(env, static_cast<size_t>(baton->levels.size()));
for (std::pair<int, int> const &l : baton->levels) {
Napi::Object level = Napi::Object::New(env);
level.Set("width", l.first);
level.Set("height", l.second);
levels.Set(i++, level);
}
info.Set("levels", levels);
}
info.Set("hasProfile", baton->hasProfile);
info.Set("hasAlpha", baton->hasAlpha);
if (baton->orientation > 0) {
info.Set("orientation", baton->orientation);
}
if (baton->exifLength > 0) {
info.Set("exif", Napi::Buffer<char>::New(env, baton->exif, baton->exifLength, sharp::FreeCallback));
}
if (baton->iccLength > 0) {
info.Set("icc", Napi::Buffer<char>::New(env, baton->icc, baton->iccLength, sharp::FreeCallback));
}
if (baton->iptcLength > 0) {
info.Set("iptc", Napi::Buffer<char>::New(env, baton->iptc, baton->iptcLength, sharp::FreeCallback));
}
if (baton->xmpLength > 0) {
info.Set("xmp", Napi::Buffer<char>::New(env, baton->xmp, baton->xmpLength, sharp::FreeCallback));
}
if (baton->tifftagPhotoshopLength > 0) {
info.Set("tifftagPhotoshop",
Napi::Buffer<char>::New(env, baton->tifftagPhotoshop, baton->tifftagPhotoshopLength, sharp::FreeCallback));
}
Callback().MakeCallback(Receiver().Value(), { env.Null(), info });
} else {
Callback().MakeCallback(Receiver().Value(), { Napi::Error::New(env, baton->err).Value() });
}
delete baton->input;
delete baton;
} }
private: private:
MetadataBaton* baton; MetadataBaton* baton;
Nan::Callback *debuglog; Napi::FunctionReference debuglog;
std::vector<v8::Local<v8::Object>> buffersToPersist;
}; };
/* /*
metadata(options, callback) metadata(options, callback)
*/ */
NAN_METHOD(metadata) { Napi::Value metadata(const Napi::CallbackInfo& info) {
// Input Buffers must not undergo GC compaction during processing
std::vector<v8::Local<v8::Object>> buffersToPersist;
// V8 objects are converted to non-V8 types held in the baton struct // V8 objects are converted to non-V8 types held in the baton struct
MetadataBaton *baton = new MetadataBaton; MetadataBaton *baton = new MetadataBaton;
v8::Local<v8::Object> options = info[0].As<v8::Object>(); Napi::Object options = info[0].As<Napi::Object>();
// Input // Input
baton->input = sharp::CreateInputDescriptor(sharp::AttrAs<v8::Object>(options, "input"), buffersToPersist); baton->input = sharp::CreateInputDescriptor(options.Get("input").As<Napi::Object>());
// Function to notify of libvips warnings // Function to notify of libvips warnings
Nan::Callback *debuglog = new Nan::Callback(sharp::AttrAs<v8::Function>(options, "debuglog")); Napi::Function debuglog = options.Get("debuglog").As<Napi::Function>();
// Join queue for worker thread // Join queue for worker thread
Nan::Callback *callback = new Nan::Callback(info[1].As<v8::Function>()); Napi::Function callback = info[1].As<Napi::Function>();
Nan::AsyncQueueWorker(new MetadataWorker(callback, baton, debuglog, buffersToPersist)); MetadataWorker *worker = new MetadataWorker(callback, baton, debuglog);
worker->Receiver().Set("options", options);
worker->Queue();
// Increment queued task counter // Increment queued task counter
g_atomic_int_inc(&sharp::counterQueue); g_atomic_int_inc(&sharp::counterQueue);
return info.Env().Undefined();
} }

View File

@@ -16,7 +16,7 @@
#define SRC_METADATA_H_ #define SRC_METADATA_H_
#include <string> #include <string>
#include <nan.h> #include <napi.h>
#include "./common.h" #include "./common.h"
@@ -39,6 +39,7 @@ struct MetadataBaton {
int loop; int loop;
std::vector<int> delay; std::vector<int> delay;
int pagePrimary; int pagePrimary;
std::vector<std::pair<int, int>> levels;
bool hasProfile; bool hasProfile;
bool hasAlpha; bool hasAlpha;
int orientation; int orientation;
@@ -81,6 +82,6 @@ struct MetadataBaton {
tifftagPhotoshopLength(0) {} tifftagPhotoshopLength(0) {}
}; };
NAN_METHOD(metadata); Napi::Value metadata(const Napi::CallbackInfo& info);
#endif // SRC_METADATA_H_ #endif // SRC_METADATA_H_

View File

@@ -27,29 +27,6 @@ using vips::VImage;
using vips::VError; using vips::VError;
namespace sharp { namespace sharp {
/*
Removes alpha channel, if any.
*/
VImage RemoveAlpha(VImage image) {
if (HasAlpha(image)) {
image = image.extract_band(0, VImage::option()->set("n", image.bands() - 1));
}
return image;
}
/*
Ensures alpha channel, if missing.
*/
VImage EnsureAlpha(VImage image) {
if (!HasAlpha(image)) {
std::vector<double> alpha;
alpha.push_back(sharp::MaximumImageAlpha(image.interpretation()));
image = image.bandjoin_const(alpha);
}
return image;
}
/* /*
* Tint an image using the specified chroma, preserving the original image luminance * Tint an image using the specified chroma, preserving the original image luminance
*/ */

View File

@@ -25,16 +25,6 @@ using vips::VImage;
namespace sharp { namespace sharp {
/*
Removes alpha channel, if any.
*/
VImage RemoveAlpha(VImage image);
/*
Ensures alpha channel, if missing.
*/
VImage EnsureAlpha(VImage image);
/* /*
* Tint an image using the specified chroma, preserving the original image luminance * Tint an image using the specified chroma, preserving the original image luminance
*/ */

View File

@@ -25,8 +25,7 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <vips/vips8> #include <vips/vips8>
#include <node.h> #include <napi.h>
#include <nan.h>
#include "common.h" #include "common.h"
#include "operations.h" #include "operations.h"
@@ -46,28 +45,18 @@
#define STAT64_FUNCTION stat64 #define STAT64_FUNCTION stat64
#endif #endif
class PipelineWorker : public Nan::AsyncWorker { class PipelineWorker : public Napi::AsyncWorker {
public: public:
PipelineWorker( PipelineWorker(Napi::Function callback, PipelineBaton *baton,
Nan::Callback *callback, PipelineBaton *baton, Nan::Callback *debuglog, Nan::Callback *queueListener, Napi::Function debuglog, Napi::Function queueListener) :
std::vector<v8::Local<v8::Object>> const buffersToPersist) : Napi::AsyncWorker(callback),
Nan::AsyncWorker(callback, "sharp:PipelineWorker"), baton(baton),
baton(baton), debuglog(debuglog), queueListener(queueListener), debuglog(Napi::Persistent(debuglog)),
buffersToPersist(buffersToPersist) { queueListener(Napi::Persistent(queueListener)) {}
// Protect Buffer objects from GC, keyed on index
std::accumulate(buffersToPersist.begin(), buffersToPersist.end(), 0,
[this](uint32_t index, v8::Local<v8::Object> const buffer) -> uint32_t {
SaveToPersistent(index, buffer);
return index + 1;
});
}
~PipelineWorker() {} ~PipelineWorker() {}
// libuv worker // libuv worker
void Execute() { void Execute() {
using sharp::HasAlpha;
using sharp::ImageType;
// Decrement queued task counter // Decrement queued task counter
g_atomic_int_dec_and_test(&sharp::counterQueue); g_atomic_int_dec_and_test(&sharp::counterQueue);
// Increment processing task counter // Increment processing task counter
@@ -76,7 +65,7 @@ class PipelineWorker : public Nan::AsyncWorker {
try { try {
// Open input // Open input
vips::VImage image; vips::VImage image;
ImageType inputImageType; sharp::ImageType inputImageType;
std::tie(image, inputImageType) = sharp::OpenInput(baton->input); std::tie(image, inputImageType) = sharp::OpenInput(baton->input);
// Calculate angle of rotation // Calculate angle of rotation
@@ -236,7 +225,7 @@ class PipelineWorker : public Nan::AsyncWorker {
} }
if ( if (
xshrink == yshrink && xshrink >= 2 * shrink_on_load_factor && xshrink == yshrink && xshrink >= 2 * shrink_on_load_factor &&
(inputImageType == ImageType::JPEG || inputImageType == ImageType::WEBP) && (inputImageType == sharp::ImageType::JPEG || inputImageType == sharp::ImageType::WEBP) &&
baton->gamma == 0 && baton->topOffsetPre == -1 && baton->trimThreshold == 0.0 baton->gamma == 0 && baton->topOffsetPre == -1 && baton->trimThreshold == 0.0
) { ) {
if (xshrink >= 8 * shrink_on_load_factor) { if (xshrink >= 8 * shrink_on_load_factor) {
@@ -267,7 +256,7 @@ class PipelineWorker : public Nan::AsyncWorker {
->set("fail", baton->input->failOnError); ->set("fail", baton->input->failOnError);
if (baton->input->buffer != nullptr) { if (baton->input->buffer != nullptr) {
VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength); VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength);
if (inputImageType == ImageType::JPEG) { if (inputImageType == sharp::ImageType::JPEG) {
// Reload JPEG buffer // Reload JPEG buffer
image = VImage::jpegload_buffer(blob, option); image = VImage::jpegload_buffer(blob, option);
} else { } else {
@@ -276,7 +265,7 @@ class PipelineWorker : public Nan::AsyncWorker {
} }
vips_area_unref(reinterpret_cast<VipsArea*>(blob)); vips_area_unref(reinterpret_cast<VipsArea*>(blob));
} else { } else {
if (inputImageType == ImageType::JPEG) { if (inputImageType == sharp::ImageType::JPEG) {
// Reload JPEG file // Reload JPEG file
image = VImage::jpegload(const_cast<char*>(baton->input->file.data()), option); image = VImage::jpegload(const_cast<char*>(baton->input->file.data()), option);
} else { } else {
@@ -320,7 +309,7 @@ class PipelineWorker : public Nan::AsyncWorker {
} }
// Flatten image to remove alpha channel // Flatten image to remove alpha channel
if (baton->flatten && HasAlpha(image)) { if (baton->flatten && sharp::HasAlpha(image)) {
// Scale up 8-bit values to match 16-bit input image // Scale up 8-bit values to match 16-bit input image
double const multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0; double const multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0;
// Background colour // Background colour
@@ -356,11 +345,11 @@ class PipelineWorker : public Nan::AsyncWorker {
bool const shouldComposite = !baton->composite.empty(); bool const shouldComposite = !baton->composite.empty();
bool const shouldModulate = baton->brightness != 1.0 || baton->saturation != 1.0 || baton->hue != 0.0; bool const shouldModulate = baton->brightness != 1.0 || baton->saturation != 1.0 || baton->hue != 0.0;
if (shouldComposite && !HasAlpha(image)) { if (shouldComposite && !sharp::HasAlpha(image)) {
image = sharp::EnsureAlpha(image); image = sharp::EnsureAlpha(image);
} }
bool const shouldPremultiplyAlpha = HasAlpha(image) && bool const shouldPremultiplyAlpha = sharp::HasAlpha(image) &&
(shouldResize || shouldBlur || shouldConv || shouldSharpen || shouldComposite); (shouldResize || shouldBlur || shouldConv || shouldSharpen || shouldComposite);
// Premultiply image alpha channel before all transformations to avoid // Premultiply image alpha channel before all transformations to avoid
@@ -416,7 +405,7 @@ class PipelineWorker : public Nan::AsyncWorker {
// Join additional color channels to the image // Join additional color channels to the image
if (baton->joinChannelIn.size() > 0) { if (baton->joinChannelIn.size() > 0) {
VImage joinImage; VImage joinImage;
ImageType joinImageType = ImageType::UNKNOWN; sharp::ImageType joinImageType = sharp::ImageType::UNKNOWN;
for (unsigned int i = 0; i < baton->joinChannelIn.size(); i++) { for (unsigned int i = 0; i < baton->joinChannelIn.size(); i++) {
std::tie(joinImage, joinImageType) = sharp::OpenInput(baton->joinChannelIn[i]); std::tie(joinImage, joinImageType) = sharp::OpenInput(baton->joinChannelIn[i]);
@@ -548,7 +537,7 @@ class PipelineWorker : public Nan::AsyncWorker {
if (shouldComposite) { if (shouldComposite) {
for (Composite *composite : baton->composite) { for (Composite *composite : baton->composite) {
VImage compositeImage; VImage compositeImage;
ImageType compositeImageType = ImageType::UNKNOWN; sharp::ImageType compositeImageType = sharp::ImageType::UNKNOWN;
std::tie(compositeImage, compositeImageType) = OpenInput(composite->input); std::tie(compositeImage, compositeImageType) = OpenInput(composite->input);
// Verify within current dimensions // Verify within current dimensions
if (compositeImage.width() > image.width() || compositeImage.height() > image.height()) { if (compositeImage.width() > image.width() || compositeImage.height() > image.height()) {
@@ -584,7 +573,7 @@ class PipelineWorker : public Nan::AsyncWorker {
} }
// Ensure image to composite is sRGB with premultiplied alpha // Ensure image to composite is sRGB with premultiplied alpha
compositeImage = compositeImage.colourspace(VIPS_INTERPRETATION_sRGB); compositeImage = compositeImage.colourspace(VIPS_INTERPRETATION_sRGB);
if (!HasAlpha(compositeImage)) { if (!sharp::HasAlpha(compositeImage)) {
compositeImage = sharp::EnsureAlpha(compositeImage); compositeImage = sharp::EnsureAlpha(compositeImage);
} }
if (!composite->premultiplied) compositeImage = compositeImage.premultiply(); if (!composite->premultiplied) compositeImage = compositeImage.premultiply();
@@ -638,7 +627,7 @@ class PipelineWorker : public Nan::AsyncWorker {
// Apply bitwise boolean operation between images // Apply bitwise boolean operation between images
if (baton->boolean != nullptr) { if (baton->boolean != nullptr) {
VImage booleanImage; VImage booleanImage;
ImageType booleanImageType = ImageType::UNKNOWN; sharp::ImageType booleanImageType = sharp::ImageType::UNKNOWN;
std::tie(booleanImage, booleanImageType) = sharp::OpenInput(baton->boolean); std::tie(booleanImage, booleanImageType) = sharp::OpenInput(baton->boolean);
image = sharp::Boolean(image, booleanImage, baton->booleanOp); image = sharp::Boolean(image, booleanImage, baton->booleanOp);
} }
@@ -656,8 +645,12 @@ class PipelineWorker : public Nan::AsyncWorker {
// Extract an image channel (aka vips band) // Extract an image channel (aka vips band)
if (baton->extractChannel > -1) { if (baton->extractChannel > -1) {
if (baton->extractChannel >= image.bands()) { if (baton->extractChannel >= image.bands()) {
(baton->err).append("Cannot extract channel from image. Too few channels in image."); if (baton->extractChannel == 3 && sharp::HasAlpha(image)) {
return Error(); baton->extractChannel = image.bands() - 1;
} else {
(baton->err).append("Cannot extract channel from image. Too few channels in image.");
return Error();
}
} }
VipsInterpretation const interpretation = sharp::Is16Bit(image.interpretation()) VipsInterpretation const interpretation = sharp::Is16Bit(image.interpretation())
? VIPS_INTERPRETATION_GREY16 ? VIPS_INTERPRETATION_GREY16
@@ -691,6 +684,15 @@ class PipelineWorker : public Nan::AsyncWorker {
} }
} }
// Apply output ICC profile
if (!baton->withMetadataIcc.empty()) {
image = image.icc_transform(
const_cast<char*>(baton->withMetadataIcc.data()),
VImage::option()
->set("input_profile", "srgb")
->set("intent", VIPS_INTENT_PERCEPTUAL));
}
// Override EXIF Orientation tag // Override EXIF Orientation tag
if (baton->withMetadata && baton->withMetadataOrientation != -1) { if (baton->withMetadata && baton->withMetadataOrientation != -1) {
image = sharp::SetExifOrientation(image, baton->withMetadataOrientation); image = sharp::SetExifOrientation(image, baton->withMetadataOrientation);
@@ -700,17 +702,29 @@ class PipelineWorker : public Nan::AsyncWorker {
baton->channels = image.bands(); baton->channels = image.bands();
baton->width = image.width(); baton->width = image.width();
baton->height = image.height(); 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);
// Output // Output
if (baton->fileOut.empty()) { if (baton->fileOut.empty()) {
// Buffer output // Buffer output
if (baton->formatOut == "jpeg" || (baton->formatOut == "input" && inputImageType == ImageType::JPEG)) { if (baton->formatOut == "jpeg" || (baton->formatOut == "input" && inputImageType == sharp::ImageType::JPEG)) {
// Write JPEG to buffer // Write JPEG to buffer
sharp::AssertImageTypeDimensions(image, ImageType::JPEG); sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
VipsArea *area = VIPS_AREA(image.jpegsave_buffer(VImage::option() VipsArea *area = VIPS_AREA(image.jpegsave_buffer(VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("Q", baton->jpegQuality) ->set("Q", baton->jpegQuality)
->set("interlace", baton->jpegProgressive) ->set("interlace", baton->jpegProgressive)
->set("no_subsample", baton->jpegChromaSubsampling == "4:4:4") ->set("subsample_mode", baton->jpegChromaSubsampling == "4:4:4"
? VIPS_FOREIGN_JPEG_SUBSAMPLE_OFF
: VIPS_FOREIGN_JPEG_SUBSAMPLE_ON)
->set("trellis_quant", baton->jpegTrellisQuantisation) ->set("trellis_quant", baton->jpegTrellisQuantisation)
->set("quant_table", baton->jpegQuantisationTable) ->set("quant_table", baton->jpegQuantisationTable)
->set("overshoot_deringing", baton->jpegOvershootDeringing) ->set("overshoot_deringing", baton->jpegOvershootDeringing)
@@ -727,9 +741,10 @@ class PipelineWorker : public Nan::AsyncWorker {
baton->channels = std::min(baton->channels, 3); baton->channels = std::min(baton->channels, 3);
} }
} else if (baton->formatOut == "png" || (baton->formatOut == "input" && } else if (baton->formatOut == "png" || (baton->formatOut == "input" &&
(inputImageType == ImageType::PNG || inputImageType == ImageType::GIF || inputImageType == ImageType::SVG))) { (inputImageType == sharp::ImageType::PNG || (inputImageType == sharp::ImageType::GIF && !supportsGifOutput) ||
inputImageType == sharp::ImageType::SVG))) {
// Write PNG to buffer // Write PNG to buffer
sharp::AssertImageTypeDimensions(image, ImageType::PNG); sharp::AssertImageTypeDimensions(image, sharp::ImageType::PNG);
VipsArea *area = VIPS_AREA(image.pngsave_buffer(VImage::option() VipsArea *area = VIPS_AREA(image.pngsave_buffer(VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("interlace", baton->pngProgressive) ->set("interlace", baton->pngProgressive)
@@ -744,9 +759,10 @@ class PipelineWorker : public Nan::AsyncWorker {
area->free_fn = nullptr; area->free_fn = nullptr;
vips_area_unref(area); vips_area_unref(area);
baton->formatOut = "png"; baton->formatOut = "png";
} else if (baton->formatOut == "webp" || (baton->formatOut == "input" && inputImageType == ImageType::WEBP)) { } else if (baton->formatOut == "webp" ||
(baton->formatOut == "input" && inputImageType == sharp::ImageType::WEBP)) {
// Write WEBP to buffer // Write WEBP to buffer
sharp::AssertImageTypeDimensions(image, ImageType::WEBP); sharp::AssertImageTypeDimensions(image, sharp::ImageType::WEBP);
VipsArea *area = VIPS_AREA(image.webpsave_buffer(VImage::option() VipsArea *area = VIPS_AREA(image.webpsave_buffer(VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("Q", baton->webpQuality) ->set("Q", baton->webpQuality)
@@ -760,10 +776,23 @@ class PipelineWorker : public Nan::AsyncWorker {
area->free_fn = nullptr; area->free_fn = nullptr;
vips_area_unref(area); vips_area_unref(area);
baton->formatOut = "webp"; baton->formatOut = "webp";
} else if (baton->formatOut == "tiff" || (baton->formatOut == "input" && inputImageType == ImageType::TIFF)) { } else if (baton->formatOut == "gif" ||
(baton->formatOut == "input" && inputImageType == sharp::ImageType::GIF && supportsGifOutput)) {
// Write GIF to buffer
sharp::AssertImageTypeDimensions(image, sharp::ImageType::GIF);
VipsArea *area = VIPS_AREA(image.magicksave_buffer(VImage::option()
->set("strip", !baton->withMetadata)
->set("format", "gif")));
baton->bufferOut = static_cast<char*>(area->data);
baton->bufferOutLength = area->length;
area->free_fn = nullptr;
vips_area_unref(area);
baton->formatOut = "gif";
} else if (baton->formatOut == "tiff" ||
(baton->formatOut == "input" && inputImageType == sharp::ImageType::TIFF)) {
// Write TIFF to buffer // Write TIFF to buffer
if (baton->tiffCompression == VIPS_FOREIGN_TIFF_COMPRESSION_JPEG) { if (baton->tiffCompression == VIPS_FOREIGN_TIFF_COMPRESSION_JPEG) {
sharp::AssertImageTypeDimensions(image, ImageType::JPEG); sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
baton->channels = std::min(baton->channels, 3); baton->channels = std::min(baton->channels, 3);
} }
// Cast pixel values to float, if required // Cast pixel values to float, if required
@@ -773,7 +802,7 @@ class PipelineWorker : public Nan::AsyncWorker {
VipsArea *area = VIPS_AREA(image.tiffsave_buffer(VImage::option() VipsArea *area = VIPS_AREA(image.tiffsave_buffer(VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("Q", baton->tiffQuality) ->set("Q", baton->tiffQuality)
->set("squash", baton->tiffSquash) ->set("bitdepth", baton->tiffBitdepth)
->set("compression", baton->tiffCompression) ->set("compression", baton->tiffCompression)
->set("predictor", baton->tiffPredictor) ->set("predictor", baton->tiffPredictor)
->set("pyramid", baton->tiffPyramid) ->set("pyramid", baton->tiffPyramid)
@@ -787,7 +816,8 @@ class PipelineWorker : public Nan::AsyncWorker {
area->free_fn = nullptr; area->free_fn = nullptr;
vips_area_unref(area); vips_area_unref(area);
baton->formatOut = "tiff"; baton->formatOut = "tiff";
} else if (baton->formatOut == "heif" || (baton->formatOut == "input" && inputImageType == ImageType::HEIF)) { } else if (baton->formatOut == "heif" ||
(baton->formatOut == "input" && inputImageType == sharp::ImageType::HEIF)) {
// Write HEIF to buffer // Write HEIF to buffer
VipsArea *area = VIPS_AREA(image.heifsave_buffer(VImage::option() VipsArea *area = VIPS_AREA(image.heifsave_buffer(VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
@@ -799,7 +829,8 @@ class PipelineWorker : public Nan::AsyncWorker {
area->free_fn = nullptr; area->free_fn = nullptr;
vips_area_unref(area); vips_area_unref(area);
baton->formatOut = "heif"; baton->formatOut = "heif";
} else if (baton->formatOut == "raw" || (baton->formatOut == "input" && inputImageType == ImageType::RAW)) { } else if (baton->formatOut == "raw" ||
(baton->formatOut == "input" && inputImageType == sharp::ImageType::RAW)) {
// Write raw, uncompressed image data to buffer // Write raw, uncompressed image data to buffer
if (baton->greyscale || image.interpretation() == VIPS_INTERPRETATION_B_W) { if (baton->greyscale || image.interpretation() == VIPS_INTERPRETATION_B_W) {
// Extract first band for greyscale image // Extract first band for greyscale image
@@ -832,22 +863,27 @@ class PipelineWorker : public Nan::AsyncWorker {
bool const isJpeg = sharp::IsJpeg(baton->fileOut); bool const isJpeg = sharp::IsJpeg(baton->fileOut);
bool const isPng = sharp::IsPng(baton->fileOut); bool const isPng = sharp::IsPng(baton->fileOut);
bool const isWebp = sharp::IsWebp(baton->fileOut); bool const isWebp = sharp::IsWebp(baton->fileOut);
bool const isGif = sharp::IsGif(baton->fileOut);
bool const isTiff = sharp::IsTiff(baton->fileOut); bool const isTiff = sharp::IsTiff(baton->fileOut);
bool const isHeif = sharp::IsHeif(baton->fileOut); bool const isHeif = sharp::IsHeif(baton->fileOut);
bool const isDz = sharp::IsDz(baton->fileOut); bool const isDz = sharp::IsDz(baton->fileOut);
bool const isDzZip = sharp::IsDzZip(baton->fileOut); bool const isDzZip = sharp::IsDzZip(baton->fileOut);
bool const isV = sharp::IsV(baton->fileOut); bool const isV = sharp::IsV(baton->fileOut);
bool const mightMatchInput = baton->formatOut == "input"; bool const mightMatchInput = baton->formatOut == "input";
bool const willMatchInput = mightMatchInput && !(isJpeg || isPng || isWebp || isTiff || isDz || isDzZip || isV); bool const willMatchInput = mightMatchInput &&
!(isJpeg || isPng || isWebp || isGif || isTiff || isDz || isDzZip || isV);
if (baton->formatOut == "jpeg" || (mightMatchInput && isJpeg) || if (baton->formatOut == "jpeg" || (mightMatchInput && isJpeg) ||
(willMatchInput && inputImageType == ImageType::JPEG)) { (willMatchInput && inputImageType == sharp::ImageType::JPEG)) {
// Write JPEG to file // Write JPEG to file
sharp::AssertImageTypeDimensions(image, ImageType::JPEG); sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
image.jpegsave(const_cast<char*>(baton->fileOut.data()), VImage::option() image.jpegsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("Q", baton->jpegQuality) ->set("Q", baton->jpegQuality)
->set("interlace", baton->jpegProgressive) ->set("interlace", baton->jpegProgressive)
->set("no_subsample", baton->jpegChromaSubsampling == "4:4:4") ->set("subsample_mode", baton->jpegChromaSubsampling == "4:4:4"
? VIPS_FOREIGN_JPEG_SUBSAMPLE_OFF
: VIPS_FOREIGN_JPEG_SUBSAMPLE_ON)
->set("trellis_quant", baton->jpegTrellisQuantisation) ->set("trellis_quant", baton->jpegTrellisQuantisation)
->set("quant_table", baton->jpegQuantisationTable) ->set("quant_table", baton->jpegQuantisationTable)
->set("overshoot_deringing", baton->jpegOvershootDeringing) ->set("overshoot_deringing", baton->jpegOvershootDeringing)
@@ -856,9 +892,10 @@ class PipelineWorker : public Nan::AsyncWorker {
baton->formatOut = "jpeg"; baton->formatOut = "jpeg";
baton->channels = std::min(baton->channels, 3); baton->channels = std::min(baton->channels, 3);
} else if (baton->formatOut == "png" || (mightMatchInput && isPng) || (willMatchInput && } else if (baton->formatOut == "png" || (mightMatchInput && isPng) || (willMatchInput &&
(inputImageType == ImageType::PNG || inputImageType == ImageType::GIF || inputImageType == ImageType::SVG))) { (inputImageType == sharp::ImageType::PNG || (inputImageType == sharp::ImageType::GIF && !supportsGifOutput) ||
inputImageType == sharp::ImageType::SVG))) {
// Write PNG to file // Write PNG to file
sharp::AssertImageTypeDimensions(image, ImageType::PNG); sharp::AssertImageTypeDimensions(image, sharp::ImageType::PNG);
image.pngsave(const_cast<char*>(baton->fileOut.data()), VImage::option() image.pngsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("interlace", baton->pngProgressive) ->set("interlace", baton->pngProgressive)
@@ -870,9 +907,9 @@ class PipelineWorker : public Nan::AsyncWorker {
->set("dither", baton->pngDither)); ->set("dither", baton->pngDither));
baton->formatOut = "png"; baton->formatOut = "png";
} else if (baton->formatOut == "webp" || (mightMatchInput && isWebp) || } else if (baton->formatOut == "webp" || (mightMatchInput && isWebp) ||
(willMatchInput && inputImageType == ImageType::WEBP)) { (willMatchInput && inputImageType == sharp::ImageType::WEBP)) {
// Write WEBP to file // Write WEBP to file
sharp::AssertImageTypeDimensions(image, ImageType::WEBP); sharp::AssertImageTypeDimensions(image, sharp::ImageType::WEBP);
image.webpsave(const_cast<char*>(baton->fileOut.data()), VImage::option() image.webpsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("Q", baton->webpQuality) ->set("Q", baton->webpQuality)
@@ -882,17 +919,25 @@ class PipelineWorker : public Nan::AsyncWorker {
->set("reduction_effort", baton->webpReductionEffort) ->set("reduction_effort", baton->webpReductionEffort)
->set("alpha_q", baton->webpAlphaQuality)); ->set("alpha_q", baton->webpAlphaQuality));
baton->formatOut = "webp"; baton->formatOut = "webp";
} else if (baton->formatOut == "gif" || (mightMatchInput && isGif) ||
(willMatchInput && inputImageType == sharp::ImageType::GIF && supportsGifOutput)) {
// Write GIF to file
sharp::AssertImageTypeDimensions(image, sharp::ImageType::GIF);
image.magicksave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata)
->set("format", "gif"));
baton->formatOut = "gif";
} else if (baton->formatOut == "tiff" || (mightMatchInput && isTiff) || } else if (baton->formatOut == "tiff" || (mightMatchInput && isTiff) ||
(willMatchInput && inputImageType == ImageType::TIFF)) { (willMatchInput && inputImageType == sharp::ImageType::TIFF)) {
// Write TIFF to file // Write TIFF to file
if (baton->tiffCompression == VIPS_FOREIGN_TIFF_COMPRESSION_JPEG) { if (baton->tiffCompression == VIPS_FOREIGN_TIFF_COMPRESSION_JPEG) {
sharp::AssertImageTypeDimensions(image, ImageType::JPEG); sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
baton->channels = std::min(baton->channels, 3); baton->channels = std::min(baton->channels, 3);
} }
image.tiffsave(const_cast<char*>(baton->fileOut.data()), VImage::option() image.tiffsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("Q", baton->tiffQuality) ->set("Q", baton->tiffQuality)
->set("squash", baton->tiffSquash) ->set("bitdepth", baton->tiffBitdepth)
->set("compression", baton->tiffCompression) ->set("compression", baton->tiffCompression)
->set("predictor", baton->tiffPredictor) ->set("predictor", baton->tiffPredictor)
->set("pyramid", baton->tiffPyramid) ->set("pyramid", baton->tiffPyramid)
@@ -903,7 +948,7 @@ class PipelineWorker : public Nan::AsyncWorker {
->set("yres", baton->tiffYres)); ->set("yres", baton->tiffYres));
baton->formatOut = "tiff"; baton->formatOut = "tiff";
} else if (baton->formatOut == "heif" || (mightMatchInput && isHeif) || } else if (baton->formatOut == "heif" || (mightMatchInput && isHeif) ||
(willMatchInput && inputImageType == ImageType::HEIF)) { (willMatchInput && inputImageType == sharp::ImageType::HEIF)) {
// Write HEIF to file // Write HEIF to file
if (sharp::IsAvif(baton->fileOut)) { if (sharp::IsAvif(baton->fileOut)) {
baton->heifCompression = VIPS_FOREIGN_HEIF_COMPRESSION_AV1; baton->heifCompression = VIPS_FOREIGN_HEIF_COMPRESSION_AV1;
@@ -938,23 +983,21 @@ class PipelineWorker : public Nan::AsyncWorker {
}; };
suffix = AssembleSuffixString(".webp", options); suffix = AssembleSuffixString(".webp", options);
} else { } else {
std::string extname = baton->tileLayout == VIPS_FOREIGN_DZ_LAYOUT_GOOGLE
|| baton->tileLayout == VIPS_FOREIGN_DZ_LAYOUT_ZOOMIFY
? ".jpg" : ".jpeg";
std::vector<std::pair<std::string, std::string>> options { std::vector<std::pair<std::string, std::string>> options {
{"Q", std::to_string(baton->jpegQuality)}, {"Q", std::to_string(baton->jpegQuality)},
{"interlace", baton->jpegProgressive ? "TRUE" : "FALSE"}, {"interlace", baton->jpegProgressive ? "TRUE" : "FALSE"},
{"no_subsample", baton->jpegChromaSubsampling == "4:4:4" ? "TRUE": "FALSE"}, {"subsample_mode", baton->jpegChromaSubsampling == "4:4:4" ? "off" : "on"},
{"trellis_quant", baton->jpegTrellisQuantisation ? "TRUE" : "FALSE"}, {"trellis_quant", baton->jpegTrellisQuantisation ? "TRUE" : "FALSE"},
{"quant_table", std::to_string(baton->jpegQuantisationTable)}, {"quant_table", std::to_string(baton->jpegQuantisationTable)},
{"overshoot_deringing", baton->jpegOvershootDeringing ? "TRUE": "FALSE"}, {"overshoot_deringing", baton->jpegOvershootDeringing ? "TRUE": "FALSE"},
{"optimize_scans", baton->jpegOptimiseScans ? "TRUE": "FALSE"}, {"optimize_scans", baton->jpegOptimiseScans ? "TRUE": "FALSE"},
{"optimize_coding", baton->jpegOptimiseCoding ? "TRUE": "FALSE"} {"optimize_coding", baton->jpegOptimiseCoding ? "TRUE": "FALSE"}
}; };
std::string extname = baton->tileLayout == VIPS_FOREIGN_DZ_LAYOUT_DZ ? ".jpeg" : ".jpg";
suffix = AssembleSuffixString(extname, options); suffix = AssembleSuffixString(extname, options);
} }
// Remove alpha channel from tile background if image does not contain an alpha channel // Remove alpha channel from tile background if image does not contain an alpha channel
if (!HasAlpha(image)) { if (!sharp::HasAlpha(image)) {
baton->tileBackground.pop_back(); baton->tileBackground.pop_back();
} }
// Write DZ to file // Write DZ to file
@@ -976,7 +1019,7 @@ class PipelineWorker : public Nan::AsyncWorker {
image.dzsave(const_cast<char*>(baton->fileOut.data()), options); image.dzsave(const_cast<char*>(baton->fileOut.data()), options);
baton->formatOut = "dz"; baton->formatOut = "dz";
} else if (baton->formatOut == "v" || (mightMatchInput && isV) || } else if (baton->formatOut == "v" || (mightMatchInput && isV) ||
(willMatchInput && inputImageType == ImageType::VIPS)) { (willMatchInput && inputImageType == sharp::ImageType::VIPS)) {
// Write V to file // Write V to file
image.vipssave(const_cast<char*>(baton->fileOut.data()), VImage::option() image.vipssave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata)); ->set("strip", !baton->withMetadata));
@@ -1000,16 +1043,18 @@ class PipelineWorker : public Nan::AsyncWorker {
vips_thread_shutdown(); vips_thread_shutdown();
} }
void HandleOKCallback() { void OnOK() {
using Nan::New; Napi::Env env = Env();
using Nan::Set; Napi::HandleScope scope(env);
Nan::HandleScope();
v8::Local<v8::Value> argv[3] = { Nan::Null(), Nan::Null(), Nan::Null() }; // Handle warnings
if (!baton->err.empty()) { std::string warning = sharp::VipsWarningPop();
// Error while (!warning.empty()) {
argv[0] = Nan::Error(baton->err.data()); debuglog.Call({ Napi::String::New(env, warning) });
} else { warning = sharp::VipsWarningPop();
}
if (baton->err.empty()) {
int width = baton->width; int width = baton->width;
int height = baton->height; int height = baton->height;
if (baton->topOffsetPre != -1 && (baton->width == -1 || baton->height == -1)) { if (baton->topOffsetPre != -1 && (baton->width == -1 || baton->height == -1)) {
@@ -1021,50 +1066,40 @@ class PipelineWorker : public Nan::AsyncWorker {
height = baton->heightPost; height = baton->heightPost;
} }
// Info Object // Info Object
v8::Local<v8::Object> info = New<v8::Object>(); Napi::Object info = Napi::Object::New(env);
Set(info, New("format").ToLocalChecked(), New<v8::String>(baton->formatOut).ToLocalChecked()); info.Set("format", baton->formatOut);
Set(info, New("width").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(width))); info.Set("width", static_cast<uint32_t>(width));
Set(info, New("height").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(height))); info.Set("height", static_cast<uint32_t>(height));
Set(info, New("channels").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(baton->channels))); info.Set("channels", static_cast<uint32_t>(baton->channels));
Set(info, New("premultiplied").ToLocalChecked(), New<v8::Boolean>(baton->premultiplied)); info.Set("premultiplied", baton->premultiplied);
if (baton->hasCropOffset) { if (baton->hasCropOffset) {
Set(info, New("cropOffsetLeft").ToLocalChecked(), info.Set("cropOffsetLeft", static_cast<int32_t>(baton->cropOffsetLeft));
New<v8::Int32>(static_cast<int32_t>(baton->cropOffsetLeft))); info.Set("cropOffsetTop", static_cast<int32_t>(baton->cropOffsetTop));
Set(info, New("cropOffsetTop").ToLocalChecked(),
New<v8::Int32>(static_cast<int32_t>(baton->cropOffsetTop)));
} }
if (baton->trimThreshold > 0.0) { if (baton->trimThreshold > 0.0) {
Set(info, New("trimOffsetLeft").ToLocalChecked(), info.Set("trimOffsetLeft", static_cast<int32_t>(baton->trimOffsetLeft));
New<v8::Int32>(static_cast<int32_t>(baton->trimOffsetLeft))); info.Set("trimOffsetTop", static_cast<int32_t>(baton->trimOffsetTop));
Set(info, New("trimOffsetTop").ToLocalChecked(),
New<v8::Int32>(static_cast<int32_t>(baton->trimOffsetTop)));
} }
if (baton->bufferOutLength > 0) { if (baton->bufferOutLength > 0) {
// Pass ownership of output data to Buffer instance
argv[1] = Nan::NewBuffer(
static_cast<char*>(baton->bufferOut), baton->bufferOutLength, sharp::FreeCallback, nullptr)
.ToLocalChecked();
// Add buffer size to info // Add buffer size to info
Set(info, New("size").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(baton->bufferOutLength))); info.Set("size", static_cast<uint32_t>(baton->bufferOutLength));
argv[2] = info; // Pass ownership of output data to Buffer instance
Napi::Buffer<char> data = Napi::Buffer<char>::New(env, static_cast<char*>(baton->bufferOut),
baton->bufferOutLength, sharp::FreeCallback);
Callback().MakeCallback(Receiver().Value(), { env.Null(), data, info });
} else { } else {
// Add file size to info // Add file size to info
struct STAT64_STRUCT st; struct STAT64_STRUCT st;
if (STAT64_FUNCTION(baton->fileOut.data(), &st) == 0) { if (STAT64_FUNCTION(baton->fileOut.data(), &st) == 0) {
Set(info, New("size").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(st.st_size))); info.Set("size", static_cast<uint32_t>(st.st_size));
} }
argv[1] = info; Callback().MakeCallback(Receiver().Value(), { env.Null(), info });
} }
} else {
Callback().MakeCallback(Receiver().Value(), { Napi::Error::New(env, baton->err).Value() });
} }
// Dispose of Persistent wrapper around input Buffers so they can be garbage collected
std::accumulate(buffersToPersist.begin(), buffersToPersist.end(), 0,
[this](uint32_t index, v8::Local<v8::Object> const buffer) -> uint32_t {
GetFromPersistent(index);
return index + 1;
});
// Delete baton // Delete baton
delete baton->input; delete baton->input;
delete baton->boolean; delete baton->boolean;
@@ -1077,29 +1112,16 @@ class PipelineWorker : public Nan::AsyncWorker {
} }
delete baton; delete baton;
// Handle warnings
std::string warning = sharp::VipsWarningPop();
while (!warning.empty()) {
v8::Local<v8::Value> message[1] = { New(warning).ToLocalChecked() };
debuglog->Call(1, message, async_resource);
warning = sharp::VipsWarningPop();
}
// Decrement processing task counter // Decrement processing task counter
g_atomic_int_dec_and_test(&sharp::counterProcess); g_atomic_int_dec_and_test(&sharp::counterProcess);
v8::Local<v8::Value> queueLength[1] = { New<v8::Uint32>(sharp::counterQueue) }; Napi::Number queueLength = Napi::Number::New(env, static_cast<double>(sharp::counterQueue));
queueListener->Call(1, queueLength, async_resource); queueListener.Call(Receiver().Value(), { queueLength });
delete queueListener;
// Return to JavaScript
callback->Call(3, argv, async_resource);
} }
private: private:
PipelineBaton *baton; PipelineBaton *baton;
Nan::Callback *debuglog; Napi::FunctionReference debuglog;
Nan::Callback *queueListener; Napi::FunctionReference queueListener;
std::vector<v8::Local<v8::Object>> buffersToPersist;
/* /*
Calculate the angle of rotation and need-to-flip for the given Exif orientation Calculate the angle of rotation and need-to-flip for the given Exif orientation
@@ -1169,37 +1191,27 @@ class PipelineWorker : public Nan::AsyncWorker {
/* /*
pipeline(options, output, callback) pipeline(options, output, callback)
*/ */
NAN_METHOD(pipeline) { Napi::Value pipeline(const Napi::CallbackInfo& info) {
using sharp::HasAttr;
using sharp::AttrTo;
using sharp::AttrAs;
using sharp::AttrAsStr;
using sharp::AttrAsRgba;
using sharp::CreateInputDescriptor;
// Input Buffers must not undergo GC compaction during processing
std::vector<v8::Local<v8::Object>> buffersToPersist;
// V8 objects are converted to non-V8 types held in the baton struct // V8 objects are converted to non-V8 types held in the baton struct
PipelineBaton *baton = new PipelineBaton; PipelineBaton *baton = new PipelineBaton;
v8::Local<v8::Object> options = info[0].As<v8::Object>(); Napi::Object options = info[0].As<Napi::Object>();
// Input // Input
baton->input = CreateInputDescriptor(AttrAs<v8::Object>(options, "input"), buffersToPersist); baton->input = sharp::CreateInputDescriptor(options.Get("input").As<Napi::Object>());
// Extract image options // Extract image options
baton->topOffsetPre = AttrTo<int32_t>(options, "topOffsetPre"); baton->topOffsetPre = sharp::AttrAsInt32(options, "topOffsetPre");
baton->leftOffsetPre = AttrTo<int32_t>(options, "leftOffsetPre"); baton->leftOffsetPre = sharp::AttrAsInt32(options, "leftOffsetPre");
baton->widthPre = AttrTo<int32_t>(options, "widthPre"); baton->widthPre = sharp::AttrAsInt32(options, "widthPre");
baton->heightPre = AttrTo<int32_t>(options, "heightPre"); baton->heightPre = sharp::AttrAsInt32(options, "heightPre");
baton->topOffsetPost = AttrTo<int32_t>(options, "topOffsetPost"); baton->topOffsetPost = sharp::AttrAsInt32(options, "topOffsetPost");
baton->leftOffsetPost = AttrTo<int32_t>(options, "leftOffsetPost"); baton->leftOffsetPost = sharp::AttrAsInt32(options, "leftOffsetPost");
baton->widthPost = AttrTo<int32_t>(options, "widthPost"); baton->widthPost = sharp::AttrAsInt32(options, "widthPost");
baton->heightPost = AttrTo<int32_t>(options, "heightPost"); baton->heightPost = sharp::AttrAsInt32(options, "heightPost");
// Output image dimensions // Output image dimensions
baton->width = AttrTo<int32_t>(options, "width"); baton->width = sharp::AttrAsInt32(options, "width");
baton->height = AttrTo<int32_t>(options, "height"); baton->height = sharp::AttrAsInt32(options, "height");
// Canvas option // Canvas option
std::string canvas = AttrAsStr(options, "canvas"); std::string canvas = sharp::AttrAsStr(options, "canvas");
if (canvas == "crop") { if (canvas == "crop") {
baton->canvas = Canvas::CROP; baton->canvas = Canvas::CROP;
} else if (canvas == "embed") { } else if (canvas == "embed") {
@@ -1212,191 +1224,181 @@ NAN_METHOD(pipeline) {
baton->canvas = Canvas::IGNORE_ASPECT; baton->canvas = Canvas::IGNORE_ASPECT;
} }
// Tint chroma // Tint chroma
baton->tintA = AttrTo<double>(options, "tintA"); baton->tintA = sharp::AttrAsDouble(options, "tintA");
baton->tintB = AttrTo<double>(options, "tintB"); baton->tintB = sharp::AttrAsDouble(options, "tintB");
// Composite // Composite
v8::Local<v8::Array> compositeArray = Nan::Get(options, Nan::New("composite").ToLocalChecked()) Napi::Array compositeArray = options.Get("composite").As<Napi::Array>();
.ToLocalChecked().As<v8::Array>(); for (unsigned int i = 0; i < compositeArray.Length(); i++) {
int const compositeArrayLength = AttrTo<uint32_t>(compositeArray, "length"); Napi::Object compositeObject = compositeArray.Get(i).As<Napi::Object>();
for (int i = 0; i < compositeArrayLength; i++) {
v8::Local<v8::Object> compositeObject = Nan::Get(compositeArray, i).ToLocalChecked().As<v8::Object>();
Composite *composite = new Composite; Composite *composite = new Composite;
composite->input = CreateInputDescriptor(AttrAs<v8::Object>(compositeObject, "input"), buffersToPersist); composite->input = sharp::CreateInputDescriptor(compositeObject.Get("input").As<Napi::Object>());
composite->mode = static_cast<VipsBlendMode>( composite->mode = static_cast<VipsBlendMode>(
vips_enum_from_nick(nullptr, VIPS_TYPE_BLEND_MODE, AttrAsStr(compositeObject, "blend").data())); vips_enum_from_nick(nullptr, VIPS_TYPE_BLEND_MODE, sharp::AttrAsStr(compositeObject, "blend").data()));
composite->gravity = AttrTo<uint32_t>(compositeObject, "gravity"); composite->gravity = sharp::AttrAsUint32(compositeObject, "gravity");
composite->left = AttrTo<int32_t>(compositeObject, "left"); composite->left = sharp::AttrAsInt32(compositeObject, "left");
composite->top = AttrTo<int32_t>(compositeObject, "top"); composite->top = sharp::AttrAsInt32(compositeObject, "top");
composite->tile = AttrTo<bool>(compositeObject, "tile"); composite->tile = sharp::AttrAsBool(compositeObject, "tile");
composite->premultiplied = AttrTo<bool>(compositeObject, "premultiplied"); composite->premultiplied = sharp::AttrAsBool(compositeObject, "premultiplied");
baton->composite.push_back(composite); baton->composite.push_back(composite);
} }
// Resize options // Resize options
baton->withoutEnlargement = AttrTo<bool>(options, "withoutEnlargement"); baton->withoutEnlargement = sharp::AttrAsBool(options, "withoutEnlargement");
baton->position = AttrTo<int32_t>(options, "position"); baton->position = sharp::AttrAsInt32(options, "position");
baton->resizeBackground = AttrAsRgba(options, "resizeBackground"); baton->resizeBackground = sharp::AttrAsRgba(options, "resizeBackground");
baton->kernel = AttrAsStr(options, "kernel"); baton->kernel = sharp::AttrAsStr(options, "kernel");
baton->fastShrinkOnLoad = AttrTo<bool>(options, "fastShrinkOnLoad"); baton->fastShrinkOnLoad = sharp::AttrAsBool(options, "fastShrinkOnLoad");
// Join Channel Options // Join Channel Options
if (HasAttr(options, "joinChannelIn")) { if (options.Has("joinChannelIn")) {
v8::Local<v8::Object> joinChannelObject = Nan::Get(options, Nan::New("joinChannelIn").ToLocalChecked()) Napi::Array joinChannelArray = options.Get("joinChannelIn").As<Napi::Array>();
.ToLocalChecked().As<v8::Object>(); for (unsigned int i = 0; i < joinChannelArray.Length(); i++) {
v8::Local<v8::Array> joinChannelArray = joinChannelObject.As<v8::Array>();
int joinChannelArrayLength = AttrTo<int32_t>(joinChannelObject, "length");
for (int i = 0; i < joinChannelArrayLength; i++) {
baton->joinChannelIn.push_back( baton->joinChannelIn.push_back(
CreateInputDescriptor( sharp::CreateInputDescriptor(joinChannelArray.Get(i).As<Napi::Object>()));
Nan::Get(joinChannelArray, i).ToLocalChecked().As<v8::Object>(),
buffersToPersist));
} }
} }
// Operators // Operators
baton->flatten = AttrTo<bool>(options, "flatten"); baton->flatten = sharp::AttrAsBool(options, "flatten");
baton->flattenBackground = AttrAsRgba(options, "flattenBackground"); baton->flattenBackground = sharp::AttrAsRgba(options, "flattenBackground");
baton->negate = AttrTo<bool>(options, "negate"); baton->negate = sharp::AttrAsBool(options, "negate");
baton->blurSigma = AttrTo<double>(options, "blurSigma"); baton->blurSigma = sharp::AttrAsDouble(options, "blurSigma");
baton->brightness = AttrTo<double>(options, "brightness"); baton->brightness = sharp::AttrAsDouble(options, "brightness");
baton->saturation = AttrTo<double>(options, "saturation"); baton->saturation = sharp::AttrAsDouble(options, "saturation");
baton->hue = AttrTo<int32_t>(options, "hue"); baton->hue = sharp::AttrAsInt32(options, "hue");
baton->medianSize = AttrTo<uint32_t>(options, "medianSize"); baton->medianSize = sharp::AttrAsUint32(options, "medianSize");
baton->sharpenSigma = AttrTo<double>(options, "sharpenSigma"); baton->sharpenSigma = sharp::AttrAsDouble(options, "sharpenSigma");
baton->sharpenFlat = AttrTo<double>(options, "sharpenFlat"); baton->sharpenFlat = sharp::AttrAsDouble(options, "sharpenFlat");
baton->sharpenJagged = AttrTo<double>(options, "sharpenJagged"); baton->sharpenJagged = sharp::AttrAsDouble(options, "sharpenJagged");
baton->threshold = AttrTo<int32_t>(options, "threshold"); baton->threshold = sharp::AttrAsInt32(options, "threshold");
baton->thresholdGrayscale = AttrTo<bool>(options, "thresholdGrayscale"); baton->thresholdGrayscale = sharp::AttrAsBool(options, "thresholdGrayscale");
baton->trimThreshold = AttrTo<double>(options, "trimThreshold"); baton->trimThreshold = sharp::AttrAsDouble(options, "trimThreshold");
baton->gamma = AttrTo<double>(options, "gamma"); baton->gamma = sharp::AttrAsDouble(options, "gamma");
baton->gammaOut = AttrTo<double>(options, "gammaOut"); baton->gammaOut = sharp::AttrAsDouble(options, "gammaOut");
baton->linearA = AttrTo<double>(options, "linearA"); baton->linearA = sharp::AttrAsDouble(options, "linearA");
baton->linearB = AttrTo<double>(options, "linearB"); baton->linearB = sharp::AttrAsDouble(options, "linearB");
baton->greyscale = AttrTo<bool>(options, "greyscale"); baton->greyscale = sharp::AttrAsBool(options, "greyscale");
baton->normalise = AttrTo<bool>(options, "normalise"); baton->normalise = sharp::AttrAsBool(options, "normalise");
baton->useExifOrientation = AttrTo<bool>(options, "useExifOrientation"); baton->useExifOrientation = sharp::AttrAsBool(options, "useExifOrientation");
baton->angle = AttrTo<int32_t>(options, "angle"); baton->angle = sharp::AttrAsInt32(options, "angle");
baton->rotationAngle = AttrTo<double>(options, "rotationAngle"); baton->rotationAngle = sharp::AttrAsDouble(options, "rotationAngle");
baton->rotationBackground = AttrAsRgba(options, "rotationBackground"); baton->rotationBackground = sharp::AttrAsRgba(options, "rotationBackground");
baton->rotateBeforePreExtract = AttrTo<bool>(options, "rotateBeforePreExtract"); baton->rotateBeforePreExtract = sharp::AttrAsBool(options, "rotateBeforePreExtract");
baton->flip = AttrTo<bool>(options, "flip"); baton->flip = sharp::AttrAsBool(options, "flip");
baton->flop = AttrTo<bool>(options, "flop"); baton->flop = sharp::AttrAsBool(options, "flop");
baton->extendTop = AttrTo<int32_t>(options, "extendTop"); baton->extendTop = sharp::AttrAsInt32(options, "extendTop");
baton->extendBottom = AttrTo<int32_t>(options, "extendBottom"); baton->extendBottom = sharp::AttrAsInt32(options, "extendBottom");
baton->extendLeft = AttrTo<int32_t>(options, "extendLeft"); baton->extendLeft = sharp::AttrAsInt32(options, "extendLeft");
baton->extendRight = AttrTo<int32_t>(options, "extendRight"); baton->extendRight = sharp::AttrAsInt32(options, "extendRight");
baton->extendBackground = AttrAsRgba(options, "extendBackground"); baton->extendBackground = sharp::AttrAsRgba(options, "extendBackground");
baton->extractChannel = AttrTo<int32_t>(options, "extractChannel"); baton->extractChannel = sharp::AttrAsInt32(options, "extractChannel");
baton->removeAlpha = AttrTo<bool>(options, "removeAlpha"); baton->removeAlpha = sharp::AttrAsBool(options, "removeAlpha");
baton->ensureAlpha = AttrTo<bool>(options, "ensureAlpha"); baton->ensureAlpha = sharp::AttrAsBool(options, "ensureAlpha");
if (HasAttr(options, "boolean")) { if (options.Has("boolean")) {
baton->boolean = CreateInputDescriptor(AttrAs<v8::Object>(options, "boolean"), buffersToPersist); baton->boolean = sharp::CreateInputDescriptor(options.Get("boolean").As<Napi::Object>());
baton->booleanOp = sharp::GetBooleanOperation(AttrAsStr(options, "booleanOp")); baton->booleanOp = sharp::GetBooleanOperation(sharp::AttrAsStr(options, "booleanOp"));
} }
if (HasAttr(options, "bandBoolOp")) { if (options.Has("bandBoolOp")) {
baton->bandBoolOp = sharp::GetBooleanOperation(AttrAsStr(options, "bandBoolOp")); baton->bandBoolOp = sharp::GetBooleanOperation(sharp::AttrAsStr(options, "bandBoolOp"));
} }
if (HasAttr(options, "convKernel")) { if (options.Has("convKernel")) {
v8::Local<v8::Object> kernel = AttrAs<v8::Object>(options, "convKernel"); Napi::Object kernel = options.Get("convKernel").As<Napi::Object>();
baton->convKernelWidth = AttrTo<uint32_t>(kernel, "width"); baton->convKernelWidth = sharp::AttrAsUint32(kernel, "width");
baton->convKernelHeight = AttrTo<uint32_t>(kernel, "height"); baton->convKernelHeight = sharp::AttrAsUint32(kernel, "height");
baton->convKernelScale = AttrTo<double>(kernel, "scale"); baton->convKernelScale = sharp::AttrAsDouble(kernel, "scale");
baton->convKernelOffset = AttrTo<double>(kernel, "offset"); baton->convKernelOffset = sharp::AttrAsDouble(kernel, "offset");
size_t const kernelSize = static_cast<size_t>(baton->convKernelWidth * baton->convKernelHeight); size_t const kernelSize = static_cast<size_t>(baton->convKernelWidth * baton->convKernelHeight);
baton->convKernel = std::unique_ptr<double[]>(new double[kernelSize]); baton->convKernel = std::unique_ptr<double[]>(new double[kernelSize]);
v8::Local<v8::Array> kdata = AttrAs<v8::Array>(kernel, "kernel"); Napi::Array kdata = kernel.Get("kernel").As<Napi::Array>();
for (unsigned int i = 0; i < kernelSize; i++) { for (unsigned int i = 0; i < kernelSize; i++) {
baton->convKernel[i] = AttrTo<double>(kdata, i); baton->convKernel[i] = sharp::AttrAsDouble(kdata, i);
} }
} }
if (HasAttr(options, "recombMatrix")) { if (options.Has("recombMatrix")) {
baton->recombMatrix = std::unique_ptr<double[]>(new double[9]); baton->recombMatrix = std::unique_ptr<double[]>(new double[9]);
v8::Local<v8::Array> recombMatrix = AttrAs<v8::Array>(options, "recombMatrix"); Napi::Array recombMatrix = options.Get("recombMatrix").As<Napi::Array>();
for (unsigned int i = 0; i < 9; i++) { for (unsigned int i = 0; i < 9; i++) {
baton->recombMatrix[i] = AttrTo<double>(recombMatrix, i); baton->recombMatrix[i] = sharp::AttrAsDouble(recombMatrix, i);
} }
} }
baton->colourspace = sharp::GetInterpretation(AttrAsStr(options, "colourspace")); baton->colourspace = sharp::GetInterpretation(sharp::AttrAsStr(options, "colourspace"));
if (baton->colourspace == VIPS_INTERPRETATION_ERROR) { if (baton->colourspace == VIPS_INTERPRETATION_ERROR) {
baton->colourspace = VIPS_INTERPRETATION_sRGB; baton->colourspace = VIPS_INTERPRETATION_sRGB;
} }
// Output // Output
baton->formatOut = AttrAsStr(options, "formatOut"); baton->formatOut = sharp::AttrAsStr(options, "formatOut");
baton->fileOut = AttrAsStr(options, "fileOut"); baton->fileOut = sharp::AttrAsStr(options, "fileOut");
baton->withMetadata = AttrTo<bool>(options, "withMetadata"); baton->withMetadata = sharp::AttrAsBool(options, "withMetadata");
baton->withMetadataOrientation = AttrTo<uint32_t>(options, "withMetadataOrientation"); baton->withMetadataOrientation = sharp::AttrAsUint32(options, "withMetadataOrientation");
baton->withMetadataIcc = sharp::AttrAsStr(options, "withMetadataIcc");
// Format-specific // Format-specific
baton->jpegQuality = AttrTo<uint32_t>(options, "jpegQuality"); baton->jpegQuality = sharp::AttrAsUint32(options, "jpegQuality");
baton->jpegProgressive = AttrTo<bool>(options, "jpegProgressive"); baton->jpegProgressive = sharp::AttrAsBool(options, "jpegProgressive");
baton->jpegChromaSubsampling = AttrAsStr(options, "jpegChromaSubsampling"); baton->jpegChromaSubsampling = sharp::AttrAsStr(options, "jpegChromaSubsampling");
baton->jpegTrellisQuantisation = AttrTo<bool>(options, "jpegTrellisQuantisation"); baton->jpegTrellisQuantisation = sharp::AttrAsBool(options, "jpegTrellisQuantisation");
baton->jpegQuantisationTable = AttrTo<uint32_t>(options, "jpegQuantisationTable"); baton->jpegQuantisationTable = sharp::AttrAsUint32(options, "jpegQuantisationTable");
baton->jpegOvershootDeringing = AttrTo<bool>(options, "jpegOvershootDeringing"); baton->jpegOvershootDeringing = sharp::AttrAsBool(options, "jpegOvershootDeringing");
baton->jpegOptimiseScans = AttrTo<bool>(options, "jpegOptimiseScans"); baton->jpegOptimiseScans = sharp::AttrAsBool(options, "jpegOptimiseScans");
baton->jpegOptimiseCoding = AttrTo<bool>(options, "jpegOptimiseCoding"); baton->jpegOptimiseCoding = sharp::AttrAsBool(options, "jpegOptimiseCoding");
baton->pngProgressive = AttrTo<bool>(options, "pngProgressive"); baton->pngProgressive = sharp::AttrAsBool(options, "pngProgressive");
baton->pngCompressionLevel = AttrTo<uint32_t>(options, "pngCompressionLevel"); baton->pngCompressionLevel = sharp::AttrAsUint32(options, "pngCompressionLevel");
baton->pngAdaptiveFiltering = AttrTo<bool>(options, "pngAdaptiveFiltering"); baton->pngAdaptiveFiltering = sharp::AttrAsBool(options, "pngAdaptiveFiltering");
baton->pngPalette = AttrTo<bool>(options, "pngPalette"); baton->pngPalette = sharp::AttrAsBool(options, "pngPalette");
baton->pngQuality = AttrTo<uint32_t>(options, "pngQuality"); baton->pngQuality = sharp::AttrAsUint32(options, "pngQuality");
baton->pngColours = AttrTo<uint32_t>(options, "pngColours"); baton->pngColours = sharp::AttrAsUint32(options, "pngColours");
baton->pngDither = AttrTo<double>(options, "pngDither"); baton->pngDither = sharp::AttrAsDouble(options, "pngDither");
baton->webpQuality = AttrTo<uint32_t>(options, "webpQuality"); baton->webpQuality = sharp::AttrAsUint32(options, "webpQuality");
baton->webpAlphaQuality = AttrTo<uint32_t>(options, "webpAlphaQuality"); baton->webpAlphaQuality = sharp::AttrAsUint32(options, "webpAlphaQuality");
baton->webpLossless = AttrTo<bool>(options, "webpLossless"); baton->webpLossless = sharp::AttrAsBool(options, "webpLossless");
baton->webpNearLossless = AttrTo<bool>(options, "webpNearLossless"); baton->webpNearLossless = sharp::AttrAsBool(options, "webpNearLossless");
baton->webpSmartSubsample = AttrTo<bool>(options, "webpSmartSubsample"); baton->webpSmartSubsample = sharp::AttrAsBool(options, "webpSmartSubsample");
baton->webpReductionEffort = AttrTo<uint32_t>(options, "webpReductionEffort"); baton->webpReductionEffort = sharp::AttrAsUint32(options, "webpReductionEffort");
baton->tiffQuality = AttrTo<uint32_t>(options, "tiffQuality"); baton->tiffQuality = sharp::AttrAsUint32(options, "tiffQuality");
baton->tiffPyramid = AttrTo<bool>(options, "tiffPyramid"); baton->tiffPyramid = sharp::AttrAsBool(options, "tiffPyramid");
baton->tiffSquash = AttrTo<bool>(options, "tiffSquash"); baton->tiffBitdepth = sharp::AttrAsUint32(options, "tiffBitdepth");
baton->tiffTile = AttrTo<bool>(options, "tiffTile"); baton->tiffTile = sharp::AttrAsBool(options, "tiffTile");
baton->tiffTileWidth = AttrTo<uint32_t>(options, "tiffTileWidth"); baton->tiffTileWidth = sharp::AttrAsUint32(options, "tiffTileWidth");
baton->tiffTileHeight = AttrTo<uint32_t>(options, "tiffTileHeight"); baton->tiffTileHeight = sharp::AttrAsUint32(options, "tiffTileHeight");
baton->tiffXres = AttrTo<double>(options, "tiffXres"); baton->tiffXres = sharp::AttrAsDouble(options, "tiffXres");
baton->tiffYres = AttrTo<double>(options, "tiffYres"); baton->tiffYres = sharp::AttrAsDouble(options, "tiffYres");
// tiff compression options // tiff compression options
baton->tiffCompression = static_cast<VipsForeignTiffCompression>( baton->tiffCompression = static_cast<VipsForeignTiffCompression>(
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_COMPRESSION, vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_COMPRESSION,
AttrAsStr(options, "tiffCompression").data())); sharp::AttrAsStr(options, "tiffCompression").data()));
baton->tiffPredictor = static_cast<VipsForeignTiffPredictor>( baton->tiffPredictor = static_cast<VipsForeignTiffPredictor>(
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_PREDICTOR, vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_PREDICTOR,
AttrAsStr(options, "tiffPredictor").data())); sharp::AttrAsStr(options, "tiffPredictor").data()));
baton->heifQuality = AttrTo<uint32_t>(options, "heifQuality"); baton->heifQuality = sharp::AttrAsUint32(options, "heifQuality");
baton->heifLossless = AttrTo<bool>(options, "heifLossless"); baton->heifLossless = sharp::AttrAsBool(options, "heifLossless");
baton->heifCompression = static_cast<VipsForeignHeifCompression>( baton->heifCompression = static_cast<VipsForeignHeifCompression>(
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_HEIF_COMPRESSION, vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_HEIF_COMPRESSION,
AttrAsStr(options, "heifCompression").data())); sharp::AttrAsStr(options, "heifCompression").data()));
// Animated output
if (sharp::HasAttr(options, "pageHeight")) {
baton->pageHeight = sharp::AttrAsUint32(options, "pageHeight");
}
if (sharp::HasAttr(options, "loop")) {
baton->loop = sharp::AttrAsUint32(options, "loop");
}
if (sharp::HasAttr(options, "delay")) {
baton->delay = sharp::AttrAsInt32Vector(options, "delay");
}
// Tile output // Tile output
baton->tileSize = AttrTo<uint32_t>(options, "tileSize"); baton->tileSize = sharp::AttrAsUint32(options, "tileSize");
baton->tileOverlap = AttrTo<uint32_t>(options, "tileOverlap"); baton->tileOverlap = sharp::AttrAsUint32(options, "tileOverlap");
std::string tileContainer = AttrAsStr(options, "tileContainer"); baton->tileAngle = sharp::AttrAsInt32(options, "tileAngle");
baton->tileAngle = AttrTo<int32_t>(options, "tileAngle"); baton->tileBackground = sharp::AttrAsRgba(options, "tileBackground");
baton->tileBackground = AttrAsRgba(options, "tileBackground"); baton->tileSkipBlanks = sharp::AttrAsInt32(options, "tileSkipBlanks");
baton->tileSkipBlanks = AttrTo<int32_t>(options, "tileSkipBlanks"); baton->tileContainer = static_cast<VipsForeignDzContainer>(
if (tileContainer == "zip") { vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_DZ_CONTAINER,
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP; sharp::AttrAsStr(options, "tileContainer").data()));
} else { baton->tileLayout = static_cast<VipsForeignDzLayout>(
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_FS; vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_DZ_LAYOUT,
} sharp::AttrAsStr(options, "tileLayout").data()));
std::string tileLayout = AttrAsStr(options, "tileLayout"); baton->tileFormat = sharp::AttrAsStr(options, "tileFormat");
if (tileLayout == "google") { baton->tileDepth = static_cast<VipsForeignDzDepth>(
baton->tileLayout = VIPS_FOREIGN_DZ_LAYOUT_GOOGLE; vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_DZ_DEPTH,
} else if (tileLayout == "zoomify") { sharp::AttrAsStr(options, "tileDepth").data()));
baton->tileLayout = VIPS_FOREIGN_DZ_LAYOUT_ZOOMIFY;
} else {
baton->tileLayout = VIPS_FOREIGN_DZ_LAYOUT_DZ;
}
baton->tileFormat = AttrAsStr(options, "tileFormat");
std::string tileDepth = AttrAsStr(options, "tileDepth");
if (tileDepth == "onetile") {
baton->tileDepth = VIPS_FOREIGN_DZ_DEPTH_ONETILE;
} else if (tileDepth == "one") {
baton->tileDepth = VIPS_FOREIGN_DZ_DEPTH_ONE;
} else if (tileDepth == "onepixel") {
baton->tileDepth = VIPS_FOREIGN_DZ_DEPTH_ONEPIXEL;
} else {
// signal that we do not want to pass any value to dzSave
baton->tileDepth = VIPS_FOREIGN_DZ_DEPTH_LAST;
}
// Force random access for certain operations // Force random access for certain operations
if (baton->input->access == VIPS_ACCESS_SEQUENTIAL) { if (baton->input->access == VIPS_ACCESS_SEQUENTIAL) {
@@ -1413,18 +1415,21 @@ NAN_METHOD(pipeline) {
} }
// Function to notify of libvips warnings // Function to notify of libvips warnings
Nan::Callback *debuglog = new Nan::Callback(AttrAs<v8::Function>(options, "debuglog")); Napi::Function debuglog = options.Get("debuglog").As<Napi::Function>();
// Function to notify of queue length changes // Function to notify of queue length changes
Nan::Callback *queueListener = new Nan::Callback(AttrAs<v8::Function>(options, "queueListener")); Napi::Function queueListener = options.Get("queueListener").As<Napi::Function>();
// Join queue for worker thread // Join queue for worker thread
Nan::Callback *callback = new Nan::Callback(info[1].As<v8::Function>()); Napi::Function callback = info[1].As<Napi::Function>();
Nan::AsyncQueueWorker(new PipelineWorker(callback, baton, debuglog, queueListener, buffersToPersist)); PipelineWorker *worker = new PipelineWorker(callback, baton, debuglog, queueListener);
worker->Receiver().Set("options", options);
worker->Queue();
// Increment queued task counter // Increment queued task counter
g_atomic_int_inc(&sharp::counterQueue); g_atomic_int_inc(&sharp::counterQueue);
v8::Local<v8::Value> queueLength[1] = { Nan::New<v8::Uint32>(sharp::counterQueue) }; Napi::Number queueLength = Napi::Number::New(info.Env(), static_cast<double>(sharp::counterQueue));
v8::Local<v8::Object> recv = Nan::New<v8::Object>(); queueListener.Call(info.This(), { queueLength });
Nan::Call(*queueListener, recv, 1, queueLength);
return info.Env().Undefined();
} }

View File

@@ -19,12 +19,12 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include <nan.h> #include <napi.h>
#include <vips/vips8> #include <vips/vips8>
#include "./common.h" #include "./common.h"
NAN_METHOD(pipeline); Napi::Value pipeline(const Napi::CallbackInfo& info);
enum class Canvas { enum class Canvas {
CROP, CROP,
@@ -143,18 +143,19 @@ struct PipelineBaton {
VipsForeignTiffCompression tiffCompression; VipsForeignTiffCompression tiffCompression;
VipsForeignTiffPredictor tiffPredictor; VipsForeignTiffPredictor tiffPredictor;
bool tiffPyramid; bool tiffPyramid;
bool tiffSquash; int tiffBitdepth;
bool tiffTile; bool tiffTile;
int tiffTileHeight; int tiffTileHeight;
int tiffTileWidth; int tiffTileWidth;
double tiffXres; double tiffXres;
double tiffYres; double tiffYres;
int heifQuality; int heifQuality;
int heifCompression; // TODO(libvips 8.9.0): VipsForeignHeifCompression VipsForeignHeifCompression heifCompression;
bool heifLossless; bool heifLossless;
std::string err; std::string err;
bool withMetadata; bool withMetadata;
int withMetadataOrientation; int withMetadataOrientation;
std::string withMetadataIcc;
std::unique_ptr<double[]> convKernel; std::unique_ptr<double[]> convKernel;
int convKernelWidth; int convKernelWidth;
int convKernelHeight; int convKernelHeight;
@@ -167,6 +168,9 @@ struct PipelineBaton {
bool removeAlpha; bool removeAlpha;
bool ensureAlpha; bool ensureAlpha;
VipsInterpretation colourspace; VipsInterpretation colourspace;
int pageHeight;
std::vector<int> delay;
int loop;
int tileSize; int tileSize;
int tileOverlap; int tileOverlap;
VipsForeignDzContainer tileContainer; VipsForeignDzContainer tileContainer;
@@ -251,14 +255,14 @@ struct PipelineBaton {
tiffCompression(VIPS_FOREIGN_TIFF_COMPRESSION_JPEG), tiffCompression(VIPS_FOREIGN_TIFF_COMPRESSION_JPEG),
tiffPredictor(VIPS_FOREIGN_TIFF_PREDICTOR_HORIZONTAL), tiffPredictor(VIPS_FOREIGN_TIFF_PREDICTOR_HORIZONTAL),
tiffPyramid(false), tiffPyramid(false),
tiffSquash(false), tiffBitdepth(8),
tiffTile(false), tiffTile(false),
tiffTileHeight(256), tiffTileHeight(256),
tiffTileWidth(256), tiffTileWidth(256),
tiffXres(1.0), tiffXres(1.0),
tiffYres(1.0), tiffYres(1.0),
heifQuality(80), heifQuality(80),
heifCompression(1), // TODO(libvips 8.9.0): VIPS_FOREIGN_HEIF_COMPRESSION_HEVC heifCompression(VIPS_FOREIGN_HEIF_COMPRESSION_HEVC),
heifLossless(false), heifLossless(false),
withMetadata(false), withMetadata(false),
withMetadataOrientation(-1), withMetadataOrientation(-1),
@@ -273,6 +277,9 @@ struct PipelineBaton {
removeAlpha(false), removeAlpha(false),
ensureAlpha(false), ensureAlpha(false),
colourspace(VIPS_INTERPRETATION_LAST), colourspace(VIPS_INTERPRETATION_LAST),
pageHeight(0),
delay{-1},
loop(-1),
tileSize(256), tileSize(256),
tileOverlap(0), tileOverlap(0),
tileContainer(VIPS_FOREIGN_DZ_CONTAINER_FS), tileContainer(VIPS_FOREIGN_DZ_CONTAINER_FS),

View File

@@ -12,8 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#include <node.h> #include <napi.h>
#include <nan.h>
#include <vips/vips8> #include <vips/vips8>
#include "common.h" #include "common.h"
@@ -22,33 +21,30 @@
#include "utilities.h" #include "utilities.h"
#include "stats.h" #include "stats.h"
NAN_MODULE_INIT(init) { static void* sharp_vips_init(void*) {
vips_init("sharp"); vips_init("sharp");
return nullptr;
}
Napi::Object init(Napi::Env env, Napi::Object exports) {
static GOnce sharp_vips_init_once = G_ONCE_INIT;
g_once(&sharp_vips_init_once, static_cast<GThreadFunc>(sharp_vips_init), nullptr);
g_log_set_handler("VIPS", static_cast<GLogLevelFlags>(G_LOG_LEVEL_WARNING), g_log_set_handler("VIPS", static_cast<GLogLevelFlags>(G_LOG_LEVEL_WARNING),
static_cast<GLogFunc>(sharp::VipsWarningCallback), nullptr); static_cast<GLogFunc>(sharp::VipsWarningCallback), nullptr);
// Methods available to JavaScript // Methods available to JavaScript
Nan::Set(target, Nan::New("metadata").ToLocalChecked(), exports.Set("metadata", Napi::Function::New(env, metadata));
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(metadata)).ToLocalChecked()); exports.Set("pipeline", Napi::Function::New(env, pipeline));
Nan::Set(target, Nan::New("pipeline").ToLocalChecked(), exports.Set("cache", Napi::Function::New(env, cache));
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(pipeline)).ToLocalChecked()); exports.Set("concurrency", Napi::Function::New(env, concurrency));
Nan::Set(target, Nan::New("cache").ToLocalChecked(), exports.Set("counters", Napi::Function::New(env, counters));
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(cache)).ToLocalChecked()); exports.Set("simd", Napi::Function::New(env, simd));
Nan::Set(target, Nan::New("concurrency").ToLocalChecked(), exports.Set("libvipsVersion", Napi::Function::New(env, libvipsVersion));
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(concurrency)).ToLocalChecked()); exports.Set("format", Napi::Function::New(env, format));
Nan::Set(target, Nan::New("counters").ToLocalChecked(), exports.Set("_maxColourDistance", Napi::Function::New(env, _maxColourDistance));
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(counters)).ToLocalChecked()); exports.Set("stats", Napi::Function::New(env, stats));
Nan::Set(target, Nan::New("simd").ToLocalChecked(), return exports;
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(simd)).ToLocalChecked());
Nan::Set(target, Nan::New("libvipsVersion").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(libvipsVersion)).ToLocalChecked());
Nan::Set(target, Nan::New("format").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(format)).ToLocalChecked());
Nan::Set(target, Nan::New("_maxColourDistance").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(_maxColourDistance)).ToLocalChecked());
Nan::Set(target, Nan::New("stats").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(stats)).ToLocalChecked());
} }
NAN_MODULE_WORKER_ENABLED(sharp, init) NODE_API_MODULE(sharp, init)

View File

@@ -16,28 +16,16 @@
#include <vector> #include <vector>
#include <iostream> #include <iostream>
#include <node.h> #include <napi.h>
#include <nan.h>
#include <vips/vips8> #include <vips/vips8>
#include "common.h" #include "common.h"
#include "stats.h" #include "stats.h"
class StatsWorker : public Nan::AsyncWorker { class StatsWorker : public Napi::AsyncWorker {
public: public:
StatsWorker( StatsWorker(Napi::Function callback, StatsBaton *baton, Napi::Function debuglog) :
Nan::Callback *callback, StatsBaton *baton, Nan::Callback *debuglog, Napi::AsyncWorker(callback), baton(baton), debuglog(Napi::Persistent(debuglog)) {}
std::vector<v8::Local<v8::Object>> const buffersToPersist) :
Nan::AsyncWorker(callback, "sharp:StatsWorker"),
baton(baton), debuglog(debuglog),
buffersToPersist(buffersToPersist) {
// Protect Buffer objects from GC, keyed on index
std::accumulate(buffersToPersist.begin(), buffersToPersist.end(), 0,
[this](uint32_t index, v8::Local<v8::Object> const buffer) -> uint32_t {
SaveToPersistent(index, buffer);
return index + 1;
});
}
~StatsWorker() {} ~StatsWorker() {}
const int STAT_MIN_INDEX = 0; const int STAT_MIN_INDEX = 0;
@@ -54,13 +42,9 @@ class StatsWorker : public Nan::AsyncWorker {
void Execute() { void Execute() {
// Decrement queued task counter // Decrement queued task counter
g_atomic_int_dec_and_test(&sharp::counterQueue); g_atomic_int_dec_and_test(&sharp::counterQueue);
using Nan::New;
using Nan::Set;
using sharp::MaximumImageAlpha;
vips::VImage image; vips::VImage image;
sharp::ImageType imageType = sharp::ImageType::UNKNOWN; sharp::ImageType imageType = sharp::ImageType::UNKNOWN;
try { try {
std::tie(image, imageType) = OpenInput(baton->input); std::tie(image, imageType) = OpenInput(baton->input);
} catch (vips::VError const &err) { } catch (vips::VError const &err) {
@@ -71,25 +55,49 @@ class StatsWorker : public Nan::AsyncWorker {
vips::VImage stats = image.stats(); vips::VImage stats = image.stats();
int const bands = image.bands(); int const bands = image.bands();
for (int b = 1; b <= bands; b++) { for (int b = 1; b <= bands; b++) {
ChannelStats cStats(static_cast<int>(stats.getpoint(STAT_MIN_INDEX, b).front()), ChannelStats cStats(
static_cast<int>(stats.getpoint(STAT_MAX_INDEX, b).front()), static_cast<int>(stats.getpoint(STAT_MIN_INDEX, b).front()),
stats.getpoint(STAT_SUM_INDEX, b).front(), stats.getpoint(STAT_SQ_SUM_INDEX, b).front(), static_cast<int>(stats.getpoint(STAT_MAX_INDEX, b).front()),
stats.getpoint(STAT_MEAN_INDEX, b).front(), stats.getpoint(STAT_STDEV_INDEX, b).front(), stats.getpoint(STAT_SUM_INDEX, b).front(),
static_cast<int>(stats.getpoint(STAT_MINX_INDEX, b).front()), stats.getpoint(STAT_SQ_SUM_INDEX, b).front(),
static_cast<int>(stats.getpoint(STAT_MINY_INDEX, b).front()), stats.getpoint(STAT_MEAN_INDEX, b).front(),
static_cast<int>(stats.getpoint(STAT_MAXX_INDEX, b).front()), stats.getpoint(STAT_STDEV_INDEX, b).front(),
static_cast<int>(stats.getpoint(STAT_MAXY_INDEX, b).front())); static_cast<int>(stats.getpoint(STAT_MINX_INDEX, b).front()),
static_cast<int>(stats.getpoint(STAT_MINY_INDEX, b).front()),
static_cast<int>(stats.getpoint(STAT_MAXX_INDEX, b).front()),
static_cast<int>(stats.getpoint(STAT_MAXY_INDEX, b).front()));
baton->channelStats.push_back(cStats); baton->channelStats.push_back(cStats);
} }
// Image is not opaque when alpha layer is present and contains a non-mamixa value // Image is not opaque when alpha layer is present and contains a non-mamixa value
if (sharp::HasAlpha(image)) { if (sharp::HasAlpha(image)) {
double const minAlpha = static_cast<double>(stats.getpoint(STAT_MIN_INDEX, bands).front()); double const minAlpha = static_cast<double>(stats.getpoint(STAT_MIN_INDEX, bands).front());
if (minAlpha != MaximumImageAlpha(image.interpretation())) { if (minAlpha != sharp::MaximumImageAlpha(image.interpretation())) {
baton->isOpaque = false; baton->isOpaque = false;
} }
} }
// Convert to greyscale
vips::VImage greyscale = image.colourspace(VIPS_INTERPRETATION_B_W)[0];
// Estimate entropy via histogram of greyscale value frequency // Estimate entropy via histogram of greyscale value frequency
baton->entropy = std::abs(image.colourspace(VIPS_INTERPRETATION_B_W)[0].hist_find().hist_entropy()); baton->entropy = std::abs(greyscale.hist_find().hist_entropy());
// Estimate sharpness via standard deviation of greyscale laplacian
VImage laplacian = VImage::new_matrixv(3, 3,
0.0, 1.0, 0.0,
1.0, -4.0, 1.0,
0.0, 1.0, 0.0);
laplacian.set("scale", 9.0);
baton->sharpness = greyscale.conv(laplacian).deviate();
// Most dominant sRGB colour via 4096-bin 3D histogram
vips::VImage hist = sharp::RemoveAlpha(image)
.colourspace(VIPS_INTERPRETATION_sRGB)
.hist_find_ndim(VImage::option()->set("bins", 16));
std::complex<double> maxpos = hist.maxpos();
int const dx = static_cast<int>(std::real(maxpos));
int const dy = static_cast<int>(std::imag(maxpos));
std::vector<double> pel = hist(dx, dy);
int const dz = std::distance(pel.begin(), std::find(pel.begin(), pel.end(), hist.max()));
baton->dominantRed = dx * 16 + 8;
baton->dominantGreen = dy * 16 + 8;
baton->dominantBlue = dz * 16 + 8;
} catch (vips::VError const &err) { } catch (vips::VError const &err) {
(baton->err).append(err.what()); (baton->err).append(err.what());
} }
@@ -100,92 +108,84 @@ class StatsWorker : public Nan::AsyncWorker {
vips_thread_shutdown(); vips_thread_shutdown();
} }
void HandleOKCallback() { void OnOK() {
using Nan::New; Napi::Env env = Env();
using Nan::Set; Napi::HandleScope scope(env);
Nan::HandleScope();
v8::Local<v8::Value> argv[2] = { Nan::Null(), Nan::Null() };
if (!baton->err.empty()) {
argv[0] = Nan::Error(baton->err.data());
} else {
// Stats Object
v8::Local<v8::Object> info = New<v8::Object>();
v8::Local<v8::Array> channels = New<v8::Array>();
std::vector<ChannelStats>::iterator it;
int i = 0;
for (it = baton->channelStats.begin(); it < baton->channelStats.end(); it++, i++) {
v8::Local<v8::Object> channelStat = New<v8::Object>();
Set(channelStat, New("min").ToLocalChecked(), New<v8::Number>(it->min));
Set(channelStat, New("max").ToLocalChecked(), New<v8::Number>(it->max));
Set(channelStat, New("sum").ToLocalChecked(), New<v8::Number>(it->sum));
Set(channelStat, New("squaresSum").ToLocalChecked(), New<v8::Number>(it->squaresSum));
Set(channelStat, New("mean").ToLocalChecked(), New<v8::Number>(it->mean));
Set(channelStat, New("stdev").ToLocalChecked(), New<v8::Number>(it->stdev));
Set(channelStat, New("minX").ToLocalChecked(), New<v8::Number>(it->minX));
Set(channelStat, New("minY").ToLocalChecked(), New<v8::Number>(it->minY));
Set(channelStat, New("maxX").ToLocalChecked(), New<v8::Number>(it->maxX));
Set(channelStat, New("maxY").ToLocalChecked(), New<v8::Number>(it->maxY));
Set(channels, i, channelStat);
}
Set(info, New("channels").ToLocalChecked(), channels);
Set(info, New("isOpaque").ToLocalChecked(), New<v8::Boolean>(baton->isOpaque));
Set(info, New("entropy").ToLocalChecked(), New<v8::Number>(baton->entropy));
argv[1] = info;
}
// Dispose of Persistent wrapper around input Buffers so they can be garbage collected
std::accumulate(buffersToPersist.begin(), buffersToPersist.end(), 0,
[this](uint32_t index, v8::Local<v8::Object> const buffer) -> uint32_t {
GetFromPersistent(index);
return index + 1;
});
delete baton->input;
delete baton;
// Handle warnings // Handle warnings
std::string warning = sharp::VipsWarningPop(); std::string warning = sharp::VipsWarningPop();
while (!warning.empty()) { while (!warning.empty()) {
v8::Local<v8::Value> message[1] = { New(warning).ToLocalChecked() }; debuglog.Call({ Napi::String::New(env, warning) });
debuglog->Call(1, message, async_resource);
warning = sharp::VipsWarningPop(); warning = sharp::VipsWarningPop();
} }
// Return to JavaScript if (baton->err.empty()) {
callback->Call(2, argv, async_resource); // Stats Object
Napi::Object info = Napi::Object::New(env);
Napi::Array channels = Napi::Array::New(env);
std::vector<ChannelStats>::iterator it;
int i = 0;
for (it = baton->channelStats.begin(); it < baton->channelStats.end(); it++, i++) {
Napi::Object channelStat = Napi::Object::New(env);
channelStat.Set("min", it->min);
channelStat.Set("max", it->max);
channelStat.Set("sum", it->sum);
channelStat.Set("squaresSum", it->squaresSum);
channelStat.Set("mean", it->mean);
channelStat.Set("stdev", it->stdev);
channelStat.Set("minX", it->minX);
channelStat.Set("minY", it->minY);
channelStat.Set("maxX", it->maxX);
channelStat.Set("maxY", it->maxY);
channels.Set(i, channelStat);
}
info.Set("channels", channels);
info.Set("isOpaque", baton->isOpaque);
info.Set("entropy", baton->entropy);
info.Set("sharpness", baton->sharpness);
Napi::Object dominant = Napi::Object::New(env);
dominant.Set("r", baton->dominantRed);
dominant.Set("g", baton->dominantGreen);
dominant.Set("b", baton->dominantBlue);
info.Set("dominant", dominant);
Callback().MakeCallback(Receiver().Value(), { env.Null(), info });
} else {
Callback().MakeCallback(Receiver().Value(), { Napi::Error::New(env, baton->err).Value() });
}
delete baton->input;
delete baton;
} }
private: private:
StatsBaton* baton; StatsBaton* baton;
Nan::Callback *debuglog; Napi::FunctionReference debuglog;
std::vector<v8::Local<v8::Object>> buffersToPersist;
}; };
/* /*
stats(options, callback) stats(options, callback)
*/ */
NAN_METHOD(stats) { Napi::Value stats(const Napi::CallbackInfo& info) {
using sharp::AttrTo;
// Input Buffers must not undergo GC compaction during processing
std::vector<v8::Local<v8::Object>> buffersToPersist;
// V8 objects are converted to non-V8 types held in the baton struct // V8 objects are converted to non-V8 types held in the baton struct
StatsBaton *baton = new StatsBaton; StatsBaton *baton = new StatsBaton;
v8::Local<v8::Object> options = info[0].As<v8::Object>(); Napi::Object options = info[0].As<Napi::Object>();
// Input // Input
baton->input = sharp::CreateInputDescriptor(sharp::AttrAs<v8::Object>(options, "input"), buffersToPersist); baton->input = sharp::CreateInputDescriptor(options.Get("input").As<Napi::Object>());
// Function to notify of libvips warnings // Function to notify of libvips warnings
Nan::Callback *debuglog = new Nan::Callback(sharp::AttrAs<v8::Function>(options, "debuglog")); Napi::Function debuglog = options.Get("debuglog").As<Napi::Function>();
// Join queue for worker thread // Join queue for worker thread
Nan::Callback *callback = new Nan::Callback(info[1].As<v8::Function>()); Napi::Function callback = info[1].As<Napi::Function>();
Nan::AsyncQueueWorker(new StatsWorker(callback, baton, debuglog, buffersToPersist)); StatsWorker *worker = new StatsWorker(callback, baton, debuglog);
worker->Receiver().Set("options", options);
worker->Queue();
// Increment queued task counter // Increment queued task counter
g_atomic_int_inc(&sharp::counterQueue); g_atomic_int_inc(&sharp::counterQueue);
return info.Env().Undefined();
} }

View File

@@ -16,7 +16,7 @@
#define SRC_STATS_H_ #define SRC_STATS_H_
#include <string> #include <string>
#include <nan.h> #include <napi.h>
#include "./common.h" #include "./common.h"
@@ -33,12 +33,8 @@ struct ChannelStats {
int maxX; int maxX;
int maxY; int maxY;
ChannelStats(): ChannelStats(int minVal, int maxVal, double sumVal, double squaresSumVal,
min(0), max(0), sum(0), squaresSum(0), mean(0), stdev(0) double meanVal, double stdevVal, int minXVal, int minYVal, int maxXVal, int maxYVal):
, minX(0), minY(0), maxX(0), maxY(0) {}
ChannelStats(int minVal, int maxVal, double sumVal, double squaresSumVal,
double meanVal, double stdevVal, int minXVal, int minYVal, int maxXVal, int maxYVal):
min(minVal), max(maxVal), sum(sumVal), squaresSum(squaresSumVal), min(minVal), max(maxVal), sum(sumVal), squaresSum(squaresSumVal),
mean(meanVal), stdev(stdevVal), minX(minXVal), minY(minYVal), maxX(maxXVal), maxY(maxYVal) {} mean(meanVal), stdev(stdevVal), minX(minXVal), minY(minYVal), maxX(maxXVal), maxY(maxYVal) {}
}; };
@@ -51,16 +47,24 @@ struct StatsBaton {
std::vector<ChannelStats> channelStats; std::vector<ChannelStats> channelStats;
bool isOpaque; bool isOpaque;
double entropy; double entropy;
double sharpness;
int dominantRed;
int dominantGreen;
int dominantBlue;
std::string err; std::string err;
StatsBaton(): StatsBaton():
input(nullptr), input(nullptr),
isOpaque(true), isOpaque(true),
entropy(0.0) entropy(0.0),
sharpness(0.0),
dominantRed(0),
dominantGreen(0),
dominantBlue(0)
{} {}
}; };
NAN_METHOD(stats); Napi::Value stats(const Napi::CallbackInfo& info);
#endif // SRC_STATS_H_ #endif // SRC_STATS_H_

View File

@@ -15,8 +15,7 @@
#include <cmath> #include <cmath>
#include <string> #include <string>
#include <node.h> #include <napi.h>
#include <nan.h>
#include <vips/vips8> #include <vips/vips8>
#include <vips/vector.h> #include <vips/vector.h>
@@ -24,183 +23,145 @@
#include "operations.h" #include "operations.h"
#include "utilities.h" #include "utilities.h"
using v8::Boolean;
using v8::Integer;
using v8::Local;
using v8::Number;
using v8::Object;
using v8::String;
using Nan::HandleScope;
using Nan::New;
using Nan::Set;
using Nan::ThrowError;
using Nan::To;
using Nan::Utf8String;
/* /*
Get and set cache limits Get and set cache limits
*/ */
NAN_METHOD(cache) { Napi::Value cache(const Napi::CallbackInfo& info) {
HandleScope(); Napi::Env env = info.Env();
// Set memory limit // Set memory limit
if (info[0]->IsInt32()) { if (info[0].IsNumber()) {
vips_cache_set_max_mem(To<int32_t>(info[0]).FromJust() * 1048576); vips_cache_set_max_mem(info[0].As<Napi::Number>().Int32Value() * 1048576);
} }
// Set file limit // Set file limit
if (info[1]->IsInt32()) { if (info[1].IsNumber()) {
vips_cache_set_max_files(To<int32_t>(info[1]).FromJust()); vips_cache_set_max_files(info[1].As<Napi::Number>().Int32Value());
} }
// Set items limit // Set items limit
if (info[2]->IsInt32()) { if (info[2].IsNumber()) {
vips_cache_set_max(To<int32_t>(info[2]).FromJust()); vips_cache_set_max(info[2].As<Napi::Number>().Int32Value());
} }
// Get memory stats // Get memory stats
Local<Object> memory = New<Object>(); Napi::Object memory = Napi::Object::New(env);
Set(memory, New("current").ToLocalChecked(), memory.Set("current", round(vips_tracked_get_mem() / 1048576));
New<Integer>(static_cast<int>(round(vips_tracked_get_mem() / 1048576)))); memory.Set("high", round(vips_tracked_get_mem_highwater() / 1048576));
Set(memory, New("high").ToLocalChecked(), memory.Set("max", round(vips_cache_get_max_mem() / 1048576));
New<Integer>(static_cast<int>(round(vips_tracked_get_mem_highwater() / 1048576))));
Set(memory, New("max").ToLocalChecked(),
New<Integer>(static_cast<int>(round(vips_cache_get_max_mem() / 1048576))));
// Get file stats // Get file stats
Local<Object> files = New<Object>(); Napi::Object files = Napi::Object::New(env);
Set(files, New("current").ToLocalChecked(), New<Integer>(vips_tracked_get_files())); files.Set("current", vips_tracked_get_files());
Set(files, New("max").ToLocalChecked(), New<Integer>(vips_cache_get_max_files())); files.Set("max", vips_cache_get_max_files());
// Get item stats // Get item stats
Local<Object> items = New<Object>(); Napi::Object items = Napi::Object::New(env);
Set(items, New("current").ToLocalChecked(), New<Integer>(vips_cache_get_size())); items.Set("current", vips_cache_get_size());
Set(items, New("max").ToLocalChecked(), New<Integer>(vips_cache_get_max())); items.Set("max", vips_cache_get_max());
Local<Object> cache = New<Object>(); Napi::Object cache = Napi::Object::New(env);
Set(cache, New("memory").ToLocalChecked(), memory); cache.Set("memory", memory);
Set(cache, New("files").ToLocalChecked(), files); cache.Set("files", files);
Set(cache, New("items").ToLocalChecked(), items); cache.Set("items", items);
info.GetReturnValue().Set(cache); return cache;
} }
/* /*
Get and set size of thread pool Get and set size of thread pool
*/ */
NAN_METHOD(concurrency) { Napi::Value concurrency(const Napi::CallbackInfo& info) {
HandleScope();
// Set concurrency // Set concurrency
if (info[0]->IsInt32()) { if (info[0].IsNumber()) {
vips_concurrency_set(To<int32_t>(info[0]).FromJust()); vips_concurrency_set(info[0].As<Napi::Number>().Int32Value());
} }
// Get concurrency // Get concurrency
info.GetReturnValue().Set(New<Integer>(vips_concurrency_get())); return Napi::Number::New(info.Env(), vips_concurrency_get());
} }
/* /*
Get internal counters (queued tasks, processing tasks) Get internal counters (queued tasks, processing tasks)
*/ */
NAN_METHOD(counters) { Napi::Value counters(const Napi::CallbackInfo& info) {
using sharp::counterProcess; Napi::Object counters = Napi::Object::New(info.Env());
using sharp::counterQueue; counters.Set("queue", sharp::counterQueue);
counters.Set("process", sharp::counterProcess);
HandleScope(); return counters;
Local<Object> counters = New<Object>();
Set(counters, New("queue").ToLocalChecked(), New<Integer>(counterQueue));
Set(counters, New("process").ToLocalChecked(), New<Integer>(counterProcess));
info.GetReturnValue().Set(counters);
} }
/* /*
Get and set use of SIMD vector unit instructions Get and set use of SIMD vector unit instructions
*/ */
NAN_METHOD(simd) { Napi::Value simd(const Napi::CallbackInfo& info) {
HandleScope();
// Set state // Set state
if (info[0]->IsBoolean()) { if (info[0].IsBoolean()) {
vips_vector_set_enabled(To<bool>(info[0]).FromJust()); vips_vector_set_enabled(info[0].As<Napi::Boolean>().Value());
} }
// Get state // Get state
info.GetReturnValue().Set(New<Boolean>(vips_vector_isenabled())); return Napi::Boolean::New(info.Env(), vips_vector_isenabled());
} }
/* /*
Get libvips version Get libvips version
*/ */
NAN_METHOD(libvipsVersion) { Napi::Value libvipsVersion(const Napi::CallbackInfo& info) {
HandleScope();
char version[9]; char version[9];
g_snprintf(version, sizeof(version), "%d.%d.%d", vips_version(0), vips_version(1), vips_version(2)); g_snprintf(version, sizeof(version), "%d.%d.%d", vips_version(0), vips_version(1), vips_version(2));
info.GetReturnValue().Set(New(version).ToLocalChecked()); return Napi::String::New(info.Env(), version);
} }
/* /*
Get available input/output file/buffer/stream formats Get available input/output file/buffer/stream formats
*/ */
NAN_METHOD(format) { Napi::Value format(const Napi::CallbackInfo& info) {
HandleScope(); Napi::Env env = info.Env();
Napi::Object format = Napi::Object::New(env);
// Attribute names
Local<String> attrId = New("id").ToLocalChecked();
Local<String> attrInput = New("input").ToLocalChecked();
Local<String> attrOutput = New("output").ToLocalChecked();
Local<String> attrFile = New("file").ToLocalChecked();
Local<String> attrBuffer = New("buffer").ToLocalChecked();
Local<String> attrStream = New("stream").ToLocalChecked();
// Which load/save operations are available for each compressed format?
Local<Object> format = New<Object>();
for (std::string const f : { for (std::string const f : {
"jpeg", "png", "webp", "tiff", "magick", "openslide", "dz", "jpeg", "png", "webp", "tiff", "magick", "openslide", "dz",
"ppm", "fits", "gif", "svg", "heif", "pdf", "vips" "ppm", "fits", "gif", "svg", "heif", "pdf", "vips"
}) { }) {
// Input // Input
Local<Boolean> hasInputFile = Napi::Boolean hasInputFile =
New<Boolean>(vips_type_find("VipsOperation", (f + "load").c_str())); Napi::Boolean::New(env, vips_type_find("VipsOperation", (f + "load").c_str()));
Local<Boolean> hasInputBuffer = Napi::Boolean hasInputBuffer =
New<Boolean>(vips_type_find("VipsOperation", (f + "load_buffer").c_str())); Napi::Boolean::New(env, vips_type_find("VipsOperation", (f + "load_buffer").c_str()));
Local<Object> input = New<Object>(); Napi::Object input = Napi::Object::New(env);
Set(input, attrFile, hasInputFile); input.Set("file", hasInputFile);
Set(input, attrBuffer, hasInputBuffer); input.Set("buffer", hasInputBuffer);
Set(input, attrStream, hasInputBuffer); input.Set("stream", hasInputBuffer);
// Output // Output
Local<Boolean> hasOutputFile = Napi::Boolean hasOutputFile =
New<Boolean>(vips_type_find("VipsOperation", (f + "save").c_str())); Napi::Boolean::New(env, vips_type_find("VipsOperation", (f + "save").c_str()));
Local<Boolean> hasOutputBuffer = Napi::Boolean hasOutputBuffer =
New<Boolean>(vips_type_find("VipsOperation", (f + "save_buffer").c_str())); Napi::Boolean::New(env, vips_type_find("VipsOperation", (f + "save_buffer").c_str()));
Local<Object> output = New<Object>(); Napi::Object output = Napi::Object::New(env);
Set(output, attrFile, hasOutputFile); output.Set("file", hasOutputFile);
Set(output, attrBuffer, hasOutputBuffer); output.Set("buffer", hasOutputBuffer);
Set(output, attrStream, hasOutputBuffer); output.Set("stream", hasOutputBuffer);
// Other attributes // Other attributes
Local<Object> container = New<Object>(); Napi::Object container = Napi::Object::New(env);
Local<String> formatId = New(f).ToLocalChecked(); container.Set("id", f);
Set(container, attrId, formatId); container.Set("input", input);
Set(container, attrInput, input); container.Set("output", output);
Set(container, attrOutput, output);
// Add to set of formats // Add to set of formats
Set(format, formatId, container); format.Set(f, container);
} }
// Raw, uncompressed data // Raw, uncompressed data
Local<Object> raw = New<Object>(); Napi::Boolean supported = Napi::Boolean::New(env, true);
Local<String> rawId = New("raw").ToLocalChecked(); Napi::Boolean unsupported = Napi::Boolean::New(env, false);
Set(raw, attrId, rawId); Napi::Object rawInput = Napi::Object::New(env);
Set(format, rawId, raw); rawInput.Set("file", unsupported);
Local<Boolean> supported = New<Boolean>(true); rawInput.Set("buffer", supported);
Local<Boolean> unsupported = New<Boolean>(false); rawInput.Set("stream", supported);
Local<Object> rawInput = New<Object>(); Napi::Object rawOutput = Napi::Object::New(env);
Set(rawInput, attrFile, unsupported); rawOutput.Set("file", unsupported);
Set(rawInput, attrBuffer, supported); rawOutput.Set("buffer", supported);
Set(rawInput, attrStream, supported); rawOutput.Set("stream", supported);
Set(raw, attrInput, rawInput); Napi::Object raw = Napi::Object::New(env);
Local<Object> rawOutput = New<Object>(); raw.Set("id", "raw");
Set(rawOutput, attrFile, unsupported); raw.Set("input", rawInput);
Set(rawOutput, attrBuffer, supported); raw.Set("output", rawOutput);
Set(rawOutput, attrStream, supported); format.Set("raw", raw);
Set(raw, attrOutput, rawOutput);
info.GetReturnValue().Set(format); return format;
} }
/* /*
@@ -208,65 +169,59 @@ NAN_METHOD(format) {
Calculates the maximum colour distance using the DE2000 algorithm Calculates the maximum colour distance using the DE2000 algorithm
between two images of the same dimensions and number of channels. between two images of the same dimensions and number of channels.
*/ */
NAN_METHOD(_maxColourDistance) { Napi::Value _maxColourDistance(const Napi::CallbackInfo& info) {
using vips::VImage; Napi::Env env = info.Env();
using vips::VError;
using sharp::DetermineImageType;
using sharp::ImageType;
using sharp::HasAlpha;
HandleScope();
// Open input files // Open input files
VImage image1; VImage image1;
ImageType imageType1 = DetermineImageType(*Utf8String(info[0])); sharp::ImageType imageType1 = sharp::DetermineImageType(info[0].As<Napi::String>().Utf8Value().data());
if (imageType1 != ImageType::UNKNOWN) { if (imageType1 != sharp::ImageType::UNKNOWN) {
try { try {
image1 = VImage::new_from_file(*Utf8String(info[0])); image1 = VImage::new_from_file(info[0].As<Napi::String>().Utf8Value().c_str());
} catch (...) { } catch (...) {
return ThrowError("Input file 1 has corrupt header"); throw Napi::Error::New(env, "Input file 1 has corrupt header");
} }
} else { } else {
return ThrowError("Input file 1 is of an unsupported image format"); throw Napi::Error::New(env, "Input file 1 is of an unsupported image format");
} }
VImage image2; VImage image2;
ImageType imageType2 = DetermineImageType(*Utf8String(info[1])); sharp::ImageType imageType2 = sharp::DetermineImageType(info[1].As<Napi::String>().Utf8Value().data());
if (imageType2 != ImageType::UNKNOWN) { if (imageType2 != sharp::ImageType::UNKNOWN) {
try { try {
image2 = VImage::new_from_file(*Utf8String(info[1])); image2 = VImage::new_from_file(info[1].As<Napi::String>().Utf8Value().c_str());
} catch (...) { } catch (...) {
return ThrowError("Input file 2 has corrupt header"); throw Napi::Error::New(env, "Input file 2 has corrupt header");
} }
} else { } else {
return ThrowError("Input file 2 is of an unsupported image format"); throw Napi::Error::New(env, "Input file 2 is of an unsupported image format");
} }
// Ensure same number of channels // Ensure same number of channels
if (image1.bands() != image2.bands()) { if (image1.bands() != image2.bands()) {
return ThrowError("mismatchedBands"); throw Napi::Error::New(env, "mismatchedBands");
} }
// Ensure same dimensions // Ensure same dimensions
if (image1.width() != image2.width() || image1.height() != image2.height()) { if (image1.width() != image2.width() || image1.height() != image2.height()) {
return ThrowError("mismatchedDimensions"); throw Napi::Error::New(env, "mismatchedDimensions");
} }
double maxColourDistance; double maxColourDistance;
try { try {
// Premultiply and remove alpha // Premultiply and remove alpha
if (HasAlpha(image1)) { if (sharp::HasAlpha(image1)) {
image1 = image1.premultiply().extract_band(1, VImage::option()->set("n", image1.bands() - 1)); image1 = image1.premultiply().extract_band(1, VImage::option()->set("n", image1.bands() - 1));
} }
if (HasAlpha(image2)) { if (sharp::HasAlpha(image2)) {
image2 = image2.premultiply().extract_band(1, VImage::option()->set("n", image2.bands() - 1)); image2 = image2.premultiply().extract_band(1, VImage::option()->set("n", image2.bands() - 1));
} }
// Calculate colour distance // Calculate colour distance
maxColourDistance = image1.dE00(image2).max(); maxColourDistance = image1.dE00(image2).max();
} catch (VError const &err) { } catch (vips::VError const &err) {
return ThrowError(err.what()); throw Napi::Error::New(env, err.what());
} }
// Clean up libvips' per-request data and threads // Clean up libvips' per-request data and threads
vips_error_clear(); vips_error_clear();
vips_thread_shutdown(); vips_thread_shutdown();
info.GetReturnValue().Set(New<Number>(maxColourDistance)); return Napi::Number::New(env, maxColourDistance);
} }

View File

@@ -15,14 +15,14 @@
#ifndef SRC_UTILITIES_H_ #ifndef SRC_UTILITIES_H_
#define SRC_UTILITIES_H_ #define SRC_UTILITIES_H_
#include <nan.h> #include <napi.h>
NAN_METHOD(cache); Napi::Value cache(const Napi::CallbackInfo& info);
NAN_METHOD(concurrency); Napi::Value concurrency(const Napi::CallbackInfo& info);
NAN_METHOD(counters); Napi::Value counters(const Napi::CallbackInfo& info);
NAN_METHOD(simd); Napi::Value simd(const Napi::CallbackInfo& info);
NAN_METHOD(libvipsVersion); Napi::Value libvipsVersion(const Napi::CallbackInfo& info);
NAN_METHOD(format); Napi::Value format(const Napi::CallbackInfo& info);
NAN_METHOD(_maxColourDistance); Napi::Value _maxColourDistance(const Napi::CallbackInfo& info);
#endif // SRC_UTILITIES_H_ #endif // SRC_UTILITIES_H_

View File

@@ -12,12 +12,12 @@
"benchmark": "^2.1.4", "benchmark": "^2.1.4",
"gm": "^1.23.1", "gm": "^1.23.1",
"imagemagick": "^0.1.3", "imagemagick": "^0.1.3",
"jimp": "^0.9.3", "jimp": "^0.16.0",
"mapnik": "^4.3.1", "mapnik": "^4.5.2",
"semver": "^7.1.1" "semver": "^7.1.1"
}, },
"license": "Apache-2.0", "license": "Apache-2.0",
"engines": { "engines": {
"node": ">=8.5.0" "node": ">=10.16.0"
} }
} }

View File

@@ -564,8 +564,9 @@ async.series({
}, },
// PNG // PNG
png: function (callback) { png: function (callback) {
const inputPngBuffer = fs.readFileSync(fixtures.inputPng); const inputPngBuffer = fs.readFileSync(fixtures.inputPngAlphaPremultiplicationLarge);
const pngSuite = new Benchmark.Suite('png'); const pngSuite = new Benchmark.Suite('png');
const minSamples = 64;
// jimp // jimp
pngSuite.add('jimp-buffer-buffer', { pngSuite.add('jimp-buffer-buffer', {
defer: true, defer: true,
@@ -576,6 +577,8 @@ async.series({
} else { } else {
image image
.resize(width, height) .resize(width, height)
.deflateLevel(6)
.filterType(0)
.getBuffer(jimp.MIME_PNG, function (err) { .getBuffer(jimp.MIME_PNG, function (err) {
if (err) { if (err) {
throw err; throw err;
@@ -589,12 +592,14 @@ async.series({
}).add('jimp-file-file', { }).add('jimp-file-file', {
defer: true, defer: true,
fn: function (deferred) { fn: function (deferred) {
jimp.read(fixtures.inputPng, function (err, image) { jimp.read(fixtures.inputPngAlphaPremultiplicationLarge, function (err, image) {
if (err) { if (err) {
throw err; throw err;
} else { } else {
image image
.resize(width, height) .resize(width, height)
.deflateLevel(6)
.filterType(0)
.write(fixtures.outputPng, function (err) { .write(fixtures.outputPng, function (err) {
if (err) { if (err) {
throw err; throw err;
@@ -610,7 +615,7 @@ async.series({
pngSuite.add('mapnik-file-file', { pngSuite.add('mapnik-file-file', {
defer: true, defer: true,
fn: function (deferred) { fn: function (deferred) {
mapnik.Image.open(fixtures.inputPng, function (err, img) { mapnik.Image.open(fixtures.inputPngAlphaPremultiplicationLarge, function (err, img) {
if (err) throw err; if (err) throw err;
img.premultiply(function (err, img) { img.premultiply(function (err, img) {
if (err) throw err; if (err) throw err;
@@ -657,11 +662,15 @@ async.series({
defer: true, defer: true,
fn: function (deferred) { fn: function (deferred) {
imagemagick.resize({ imagemagick.resize({
srcPath: fixtures.inputPng, srcPath: fixtures.inputPngAlphaPremultiplicationLarge,
dstPath: fixtures.outputPng, dstPath: fixtures.outputPng,
width: width, width: width,
height: height, height: height,
filter: 'Lanczos' filter: 'Lanczos',
customArgs: [
'-define', 'PNG:compression-level=6',
'-define', 'PNG:compression-filter=0'
]
}, function (err) { }, function (err) {
if (err) { if (err) {
throw err; throw err;
@@ -675,9 +684,11 @@ async.series({
pngSuite.add('gm-file-file', { pngSuite.add('gm-file-file', {
defer: true, defer: true,
fn: function (deferred) { fn: function (deferred) {
gm(fixtures.inputPng) gm(fixtures.inputPngAlphaPremultiplicationLarge)
.filter('Lanczos') .filter('Lanczos')
.resize(width, height) .resize(width, height)
.define('PNG:compression-level=6')
.define('PNG:compression-filter=0')
.write(fixtures.outputPng, function (err) { .write(fixtures.outputPng, function (err) {
if (err) { if (err) {
throw err; throw err;
@@ -689,9 +700,11 @@ async.series({
}).add('gm-file-buffer', { }).add('gm-file-buffer', {
defer: true, defer: true,
fn: function (deferred) { fn: function (deferred) {
gm(fixtures.inputPng) gm(fixtures.inputPngAlphaPremultiplicationLarge)
.filter('Lanczos') .filter('Lanczos')
.resize(width, height) .resize(width, height)
.define('PNG:compression-level=6')
.define('PNG:compression-filter=0')
.toBuffer(function (err, buffer) { .toBuffer(function (err, buffer) {
if (err) { if (err) {
throw err; throw err;
@@ -705,9 +718,11 @@ async.series({
// sharp // sharp
pngSuite.add('sharp-buffer-file', { pngSuite.add('sharp-buffer-file', {
defer: true, defer: true,
minSamples,
fn: function (deferred) { fn: function (deferred) {
sharp(inputPngBuffer) sharp(inputPngBuffer)
.resize(width, height) .resize(width, height)
.png({ compressionLevel: 6 })
.toFile(fixtures.outputPng, function (err) { .toFile(fixtures.outputPng, function (err) {
if (err) { if (err) {
throw err; throw err;
@@ -718,9 +733,11 @@ async.series({
} }
}).add('sharp-buffer-buffer', { }).add('sharp-buffer-buffer', {
defer: true, defer: true,
minSamples,
fn: function (deferred) { fn: function (deferred) {
sharp(inputPngBuffer) sharp(inputPngBuffer)
.resize(width, height) .resize(width, height)
.png({ compressionLevel: 6 })
.toBuffer(function (err, buffer) { .toBuffer(function (err, buffer) {
if (err) { if (err) {
throw err; throw err;
@@ -732,9 +749,11 @@ async.series({
} }
}).add('sharp-file-file', { }).add('sharp-file-file', {
defer: true, defer: true,
minSamples,
fn: function (deferred) { fn: function (deferred) {
sharp(fixtures.inputPng) sharp(fixtures.inputPngAlphaPremultiplicationLarge)
.resize(width, height) .resize(width, height)
.png({ compressionLevel: 6 })
.toFile(fixtures.outputPng, function (err) { .toFile(fixtures.outputPng, function (err) {
if (err) { if (err) {
throw err; throw err;
@@ -745,9 +764,11 @@ async.series({
} }
}).add('sharp-file-buffer', { }).add('sharp-file-buffer', {
defer: true, defer: true,
minSamples,
fn: function (deferred) { fn: function (deferred) {
sharp(fixtures.inputPng) sharp(fixtures.inputPngAlphaPremultiplicationLarge)
.resize(width, height) .resize(width, height)
.png({ compressionLevel: 6 })
.toBuffer(function (err, buffer) { .toBuffer(function (err, buffer) {
if (err) { if (err) {
throw err; throw err;
@@ -759,10 +780,11 @@ async.series({
} }
}).add('sharp-progressive', { }).add('sharp-progressive', {
defer: true, defer: true,
minSamples,
fn: function (deferred) { fn: function (deferred) {
sharp(inputPngBuffer) sharp(inputPngBuffer)
.resize(width, height) .resize(width, height)
.png({ progressive: true }) .png({ compressionLevel: 6, progressive: true })
.toBuffer(function (err, buffer) { .toBuffer(function (err, buffer) {
if (err) { if (err) {
throw err; throw err;
@@ -774,10 +796,27 @@ async.series({
} }
}).add('sharp-adaptiveFiltering', { }).add('sharp-adaptiveFiltering', {
defer: true, defer: true,
minSamples,
fn: function (deferred) { fn: function (deferred) {
sharp(inputPngBuffer) sharp(inputPngBuffer)
.resize(width, height) .resize(width, height)
.png({ adaptiveFiltering: true }) .png({ adaptiveFiltering: true, compressionLevel: 6 })
.toBuffer(function (err, buffer) {
if (err) {
throw err;
} else {
assert.notStrictEqual(null, buffer);
deferred.resolve();
}
});
}
}).add('sharp-compressionLevel=9', {
defer: true,
minSamples,
fn: function (deferred) {
sharp(inputPngBuffer)
.resize(width, height)
.png({ compressionLevel: 9 })
.toBuffer(function (err, buffer) { .toBuffer(function (err, buffer) {
if (err) { if (err) {
throw err; throw err;

BIN
test/fixtures/animated-loop-3.webp vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

BIN
test/fixtures/expected/hilutite.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

BIN
test/fixtures/expected/icc-cmyk.jpg vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

BIN
test/fixtures/hilutite.icm vendored Normal file

Binary file not shown.

View File

@@ -93,6 +93,8 @@ module.exports = {
inputWebP: getPath('4.webp'), // http://www.gstatic.com/webp/gallery/4.webp inputWebP: getPath('4.webp'), // http://www.gstatic.com/webp/gallery/4.webp
inputWebPWithTransparency: getPath('5_webp_a.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp inputWebPWithTransparency: getPath('5_webp_a.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp
inputWebPAnimated: getPath('rotating-squares.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp
inputWebPAnimatedLoop3: getPath('animated-loop-3.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp
inputTiff: getPath('G31D.TIF'), // http://www.fileformat.info/format/tiff/sample/e6c9a6e5253348f4aef6d17b534360ab/index.htm inputTiff: getPath('G31D.TIF'), // http://www.fileformat.info/format/tiff/sample/e6c9a6e5253348f4aef6d17b534360ab/index.htm
inputTiffMultipage: getPath('G31D_MULTI.TIF'), // gm convert G31D.TIF -resize 50% G31D_2.TIF ; tiffcp G31D.TIF G31D_2.TIF G31D_MULTI.TIF inputTiffMultipage: getPath('G31D_MULTI.TIF'), // gm convert G31D.TIF -resize 50% G31D_2.TIF ; tiffcp G31D.TIF G31D_2.TIF G31D_MULTI.TIF
inputTiffCielab: getPath('cielab-dagams.tiff'), // https://github.com/lovell/sharp/issues/646 inputTiffCielab: getPath('cielab-dagams.tiff'), // https://github.com/lovell/sharp/issues/646

BIN
test/fixtures/rotating-squares.webp vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

@@ -578,11 +578,14 @@
fun:_ZN4node20BackgroundTaskRunnerC1Ei fun:_ZN4node20BackgroundTaskRunnerC1Ei
} }
{ {
leak_nan_FunctionCallbackInfo leak_napi_module_register
Memcheck:Leak Memcheck:Leak
match-leak-kinds: definite match-leak-kinds: definite
... ...
fun:_ZN3Nan3impL23FunctionCallbackWrapperERKN2v820FunctionCallbackInfoINS1_5ValueEEE fun:napi_module_register
fun:call_init.part.0
fun:call_init
fun:_dl_init
} }
{ {
leak_v8_FunctionCallbackInfo leak_v8_FunctionCallbackInfo

View File

@@ -5,7 +5,10 @@ const sharp = require('../../');
const usingCache = detectLibc.family !== detectLibc.MUSL; const usingCache = detectLibc.family !== detectLibc.MUSL;
const usingSimd = !process.env.G_DEBUG; const usingSimd = !process.env.G_DEBUG;
const concurrency = detectLibc.family === detectLibc.MUSL ? 1 : undefined; const concurrency =
detectLibc.family === detectLibc.MUSL || process.arch === 'arm'
? 1
: undefined;
beforeEach(function () { beforeEach(function () {
sharp.cache(usingCache); sharp.cache(usingCache);

View File

@@ -80,6 +80,19 @@ describe('Image channel extraction', function () {
}); });
}); });
it('Alpha from 2-channel input', function (done) {
const output = fixtures.path('output.extract-alpha-2-channel.png');
sharp(fixtures.inputPngWithGreyAlpha)
.extractChannel('alpha')
.toColourspace('b-w')
.toFile(output, function (err, info) {
if (err) throw err;
assert.strictEqual(1, info.channels);
fixtures.assertMaxColourDistance(output, fixtures.expected('extract-alpha-2-channel.png'));
done();
});
});
it('Invalid channel number', function () { it('Invalid channel number', function () {
assert.throws(function () { assert.throws(function () {
sharp(fixtures.inputJpg) sharp(fixtures.inputJpg)

View File

@@ -19,11 +19,17 @@ describe('failOnError', function () {
}); });
}); });
it('handles truncated PNG', function (done) { it('handles truncated PNG, emits warnings', function (done) {
let isWarningEmitted = false;
sharp(fixtures.inputPngTruncated, { failOnError: false }) sharp(fixtures.inputPngTruncated, { failOnError: false })
.on('warning', function (warning) {
assert.ok(warning.includes('not enough data') || warning.includes('end of stream'));
isWarningEmitted = true;
})
.resize(320, 240) .resize(320, 240)
.toBuffer(function (err, data, info) { .toBuffer(function (err, data, info) {
if (err) throw err; if (err) throw err;
assert.strictEqual(true, isWarningEmitted);
assert.strictEqual('png', info.format); assert.strictEqual('png', info.format);
assert.strictEqual(320, info.width); assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height); assert.strictEqual(240, info.height);
@@ -48,17 +54,17 @@ describe('failOnError', function () {
it('returns errors to callback for truncated JPEG', function (done) { it('returns errors to callback for truncated JPEG', function (done) {
sharp(fixtures.inputJpgTruncated).toBuffer(function (err, data, info) { sharp(fixtures.inputJpgTruncated).toBuffer(function (err, data, info) {
assert.ok(err.message.includes('VipsJpeg: Premature end of JPEG file'), err); assert.ok(err.message.includes('VipsJpeg: Premature end of JPEG file'), err);
assert.strictEqual(data, null); assert.strictEqual(data, undefined);
assert.strictEqual(info, null); assert.strictEqual(info, undefined);
done(); done();
}); });
}); });
it('returns errors to callback for truncated PNG', function (done) { it('returns errors to callback for truncated PNG', function (done) {
sharp(fixtures.inputPngTruncated).toBuffer(function (err, data, info) { sharp(fixtures.inputPngTruncated).toBuffer(function (err, data, info) {
assert.ok(err.message.includes('vipspng: libpng read error'), err); assert.ok(err.message.includes('read error'), err);
assert.strictEqual(data, null); assert.strictEqual(data, undefined);
assert.strictEqual(info, null); assert.strictEqual(info, undefined);
done(); done();
}); });
}); });

View File

@@ -42,7 +42,7 @@ describe('GIF input', () => {
.then(({ data, info }) => { .then(({ data, info }) => {
assert.strictEqual(true, data.length > 0); assert.strictEqual(true, data.length > 0);
assert.strictEqual(data.length, info.size); assert.strictEqual(data.length, info.size);
assert.strictEqual('png', info.format); assert.strictEqual(sharp.format.magick.input.buffer ? 'gif' : 'png', info.format);
assert.strictEqual(80, info.width); assert.strictEqual(80, info.width);
assert.strictEqual(80, info.height); assert.strictEqual(80, info.height);
assert.strictEqual(4, info.channels); assert.strictEqual(4, info.channels);
@@ -55,10 +55,47 @@ describe('GIF input', () => {
.then(({ data, info }) => { .then(({ data, info }) => {
assert.strictEqual(true, data.length > 0); assert.strictEqual(true, data.length > 0);
assert.strictEqual(data.length, info.size); assert.strictEqual(data.length, info.size);
assert.strictEqual('png', info.format); assert.strictEqual(sharp.format.magick.input.buffer ? 'gif' : 'png', info.format);
assert.strictEqual(80, info.width); assert.strictEqual(80, info.width);
assert.strictEqual(2400, info.height); assert.strictEqual(2400, info.height);
assert.strictEqual(4, info.channels); assert.strictEqual(4, info.channels);
}) })
); );
if (!sharp.format.magick.output.buffer) {
it('GIF output should fail due to missing ImageMagick', () => {
assert.throws(
() => {
sharp().gif();
},
/The gif operation requires libvips to have been installed with support for ImageMagick/
);
});
}
it('invalid pageHeight throws', () => {
assert.throws(() => {
sharp().gif({ pageHeight: 0 });
});
});
it('invalid loop throws', () => {
assert.throws(() => {
sharp().gif({ loop: -1 });
});
assert.throws(() => {
sharp().gif({ loop: 65536 });
});
});
it('invalid delay throws', () => {
assert.throws(() => {
sharp().gif({ delay: [-1] });
});
assert.throws(() => {
sharp().gif({ delay: [65536] });
});
});
}); });

View File

@@ -234,22 +234,6 @@ describe('Input/output', function () {
}) })
); );
it('Sequential read, force JPEG - deprecated', function (done) {
sharp(fixtures.inputJpg)
.sequentialRead()
.resize(320, 240)
.toFormat(sharp.format.jpeg)
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual(data.length, info.size);
assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
done();
});
});
it('Not sequential read, force JPEG', () => it('Not sequential read, force JPEG', () =>
sharp(fixtures.inputJpg, { sequentialRead: false }) sharp(fixtures.inputJpg, { sequentialRead: false })
.resize(320, 240) .resize(320, 240)
@@ -264,22 +248,6 @@ describe('Input/output', function () {
}) })
); );
it('Not sequential read, force JPEG - deprecated', function (done) {
sharp(fixtures.inputJpg)
.sequentialRead(false)
.resize(320, 240)
.toFormat('jpeg')
.toBuffer(function (err, data, info) {
if (err) throw err;
assert.strictEqual(true, data.length > 0);
assert.strictEqual(data.length, info.size);
assert.strictEqual('jpeg', info.format);
assert.strictEqual(320, info.width);
assert.strictEqual(240, info.height);
done();
});
});
it('Support output to jpg format', function (done) { it('Support output to jpg format', function (done) {
sharp(fixtures.inputPng) sharp(fixtures.inputPng)
.resize(320, 240) .resize(320, 240)
@@ -334,6 +302,7 @@ describe('Input/output', function () {
}); });
it('Fail when input is empty Buffer', function (done) { it('Fail when input is empty Buffer', function (done) {
if (sharp.format.magick.input.buffer) return this.skip(); // can be removed with libvips 8.10.0+
sharp(Buffer.alloc(0)).toBuffer().then(function () { sharp(Buffer.alloc(0)).toBuffer().then(function () {
assert(false); assert(false);
done(); done();
@@ -501,7 +470,7 @@ describe('Input/output', function () {
.toFile(fixtures.outputZoinks, function (err, info) { .toFile(fixtures.outputZoinks, function (err, info) {
if (err) throw err; if (err) throw err;
assert.strictEqual(true, info.size > 0); assert.strictEqual(true, info.size > 0);
assert.strictEqual('png', info.format); assert.strictEqual(sharp.format.magick.input.buffer ? 'gif' : 'png', info.format);
assert.strictEqual(320, info.width); assert.strictEqual(320, info.width);
assert.strictEqual(80, info.height); assert.strictEqual(80, info.height);
rimraf(fixtures.outputZoinks, done); rimraf(fixtures.outputZoinks, done);
@@ -653,81 +622,6 @@ describe('Input/output', function () {
); );
}); });
describe('Limit pixel count of input image - deprecated', function () {
it('Invalid fails - negative', function (done) {
let isValid = false;
try {
sharp().limitInputPixels(-1);
isValid = true;
} catch (e) {}
assert(!isValid);
done();
});
it('Invalid fails - float', function (done) {
let isValid = false;
try {
sharp().limitInputPixels(12.3);
isValid = true;
} catch (e) {}
assert(!isValid);
done();
});
it('Invalid fails - string', function (done) {
let isValid = false;
try {
sharp().limitInputPixels('fail');
isValid = true;
} catch (e) {}
assert(!isValid);
done();
});
it('Same size as input works', function (done) {
sharp(fixtures.inputJpg).metadata(function (err, metadata) {
if (err) throw err;
sharp(fixtures.inputJpg)
.limitInputPixels(metadata.width * metadata.height)
.toBuffer(function (err) {
assert.strictEqual(true, !err);
done();
});
});
});
it('Disabling limit works', function (done) {
sharp(fixtures.inputJpgLarge)
.limitInputPixels(false)
.resize(2)
.toBuffer(function (err) {
assert.strictEqual(true, !err);
done();
});
});
it('Enabling default limit works and fails with a large image', function (done) {
sharp(fixtures.inputJpgLarge)
.limitInputPixels(true)
.toBuffer(function (err) {
assert.strictEqual(true, !!err);
done();
});
});
it('Smaller than input fails', function (done) {
sharp(fixtures.inputJpg).metadata(function (err, metadata) {
if (err) throw err;
sharp(fixtures.inputJpg)
.limitInputPixels((metadata.width * metadata.height) - 1)
.toBuffer(function (err) {
assert.strictEqual(true, !!err);
done();
});
});
});
});
describe('Input options', function () { describe('Input options', function () {
it('Option-less', function () { it('Option-less', function () {
sharp(); sharp();
@@ -755,6 +649,15 @@ describe('Input/output', function () {
sharp({ density: 'zoinks' }); sharp({ density: 'zoinks' });
}, /Expected number between 1 and 2400 for density but received zoinks of type string/); }, /Expected number between 1 and 2400 for density but received zoinks of type string/);
}); });
it('Setting animated property updates pages property', function () {
assert.strictEqual(sharp({ animated: false }).options.input.pages, 1);
assert.strictEqual(sharp({ animated: true }).options.input.pages, -1);
});
it('Invalid animated property throws', function () {
assert.throws(function () {
sharp({ animated: -1 });
}, /Expected boolean for animated but received -1 of type number/);
});
it('Invalid page property throws', function () { it('Invalid page property throws', function () {
assert.throws(function () { assert.throws(function () {
sharp({ page: -1 }); sharp({ page: -1 });
@@ -765,6 +668,19 @@ describe('Input/output', function () {
sharp({ pages: '1' }); sharp({ pages: '1' });
}, /Expected integer between -1 and 100000 for pages but received 1 of type string/); }, /Expected integer between -1 and 100000 for pages but received 1 of type string/);
}); });
it('Valid level property', function () {
sharp({ level: 1 });
});
it('Invalid level property (string) throws', function () {
assert.throws(function () {
sharp({ level: '1' });
}, /Expected integer between 0 and 256 for level but received 1 of type string/);
});
it('Invalid level property (negative) throws', function () {
assert.throws(function () {
sharp({ level: -1 });
}, /Expected integer between 0 and 256 for level but received -1 of type number/);
});
}); });
describe('create new image', function () { describe('create new image', function () {

View File

@@ -19,7 +19,7 @@ describe('Image metadata', function () {
assert.strictEqual('srgb', metadata.space); assert.strictEqual('srgb', metadata.space);
assert.strictEqual(3, metadata.channels); assert.strictEqual(3, metadata.channels);
assert.strictEqual('uchar', metadata.depth); assert.strictEqual('uchar', metadata.depth);
assert.strictEqual('undefined', typeof metadata.density); assert.strictEqual(true, ['undefined', 'number'].includes(typeof metadata.density));
assert.strictEqual('4:2:0', metadata.chromaSubsampling); assert.strictEqual('4:2:0', metadata.chromaSubsampling);
assert.strictEqual(false, metadata.isProgressive); assert.strictEqual(false, metadata.isProgressive);
assert.strictEqual(false, metadata.hasProfile); assert.strictEqual(false, metadata.hasProfile);
@@ -192,6 +192,54 @@ describe('Image metadata', function () {
}); });
}); });
it('Animated WebP', () =>
sharp(fixtures.inputWebPAnimated)
.metadata()
.then(({
format, width, height, space, channels, depth,
isProgressive, pages, pageHeight, loop, delay,
hasProfile, hasAlpha
}) => {
assert.strictEqual(format, 'webp');
assert.strictEqual(width, 80);
assert.strictEqual(height, 80);
assert.strictEqual(space, 'srgb');
assert.strictEqual(channels, 4);
assert.strictEqual(depth, 'uchar');
assert.strictEqual(isProgressive, false);
assert.strictEqual(pages, 9);
assert.strictEqual(pageHeight, 80);
assert.strictEqual(loop, 0);
assert.deepStrictEqual(delay, [120, 120, 90, 120, 120, 90, 120, 90, 30]);
assert.strictEqual(hasProfile, false);
assert.strictEqual(hasAlpha, true);
})
);
it('Animated WebP with limited looping', () =>
sharp(fixtures.inputWebPAnimatedLoop3)
.metadata()
.then(({
format, width, height, space, channels, depth,
isProgressive, pages, pageHeight, loop, delay,
hasProfile, hasAlpha
}) => {
assert.strictEqual(format, 'webp');
assert.strictEqual(width, 370);
assert.strictEqual(height, 285);
assert.strictEqual(space, 'srgb');
assert.strictEqual(channels, 4);
assert.strictEqual(depth, 'uchar');
assert.strictEqual(isProgressive, false);
assert.strictEqual(pages, 10);
assert.strictEqual(pageHeight, 285);
assert.strictEqual(loop, 3);
assert.deepStrictEqual(delay, [...Array(9).fill(3000), 15000]);
assert.strictEqual(hasProfile, false);
assert.strictEqual(hasAlpha, true);
})
);
it('GIF via giflib', function (done) { it('GIF via giflib', function (done) {
sharp(fixtures.inputGif).metadata(function (err, metadata) { sharp(fixtures.inputGif).metadata(function (err, metadata) {
if (err) throw err; if (err) throw err;
@@ -311,7 +359,7 @@ describe('Image metadata', function () {
assert.strictEqual('srgb', metadata.space); assert.strictEqual('srgb', metadata.space);
assert.strictEqual(3, metadata.channels); assert.strictEqual(3, metadata.channels);
assert.strictEqual('uchar', metadata.depth); assert.strictEqual('uchar', metadata.depth);
assert.strictEqual('undefined', typeof metadata.density); assert.strictEqual(true, ['undefined', 'number'].includes(typeof metadata.density));
assert.strictEqual('4:2:0', metadata.chromaSubsampling); assert.strictEqual('4:2:0', metadata.chromaSubsampling);
assert.strictEqual(false, metadata.isProgressive); assert.strictEqual(false, metadata.isProgressive);
assert.strictEqual(false, metadata.hasProfile); assert.strictEqual(false, metadata.hasProfile);
@@ -343,7 +391,7 @@ describe('Image metadata', function () {
assert.strictEqual('srgb', metadata.space); assert.strictEqual('srgb', metadata.space);
assert.strictEqual(3, metadata.channels); assert.strictEqual(3, metadata.channels);
assert.strictEqual('uchar', metadata.depth); assert.strictEqual('uchar', metadata.depth);
assert.strictEqual('undefined', typeof metadata.density); assert.strictEqual(true, ['undefined', 'number'].includes(typeof metadata.density));
assert.strictEqual('4:2:0', metadata.chromaSubsampling); assert.strictEqual('4:2:0', metadata.chromaSubsampling);
assert.strictEqual(false, metadata.isProgressive); assert.strictEqual(false, metadata.isProgressive);
assert.strictEqual(false, metadata.hasProfile); assert.strictEqual(false, metadata.hasProfile);
@@ -381,7 +429,7 @@ describe('Image metadata', function () {
assert.strictEqual('srgb', metadata.space); assert.strictEqual('srgb', metadata.space);
assert.strictEqual(3, metadata.channels); assert.strictEqual(3, metadata.channels);
assert.strictEqual('uchar', metadata.depth); assert.strictEqual('uchar', metadata.depth);
assert.strictEqual('undefined', typeof metadata.density); assert.strictEqual(true, ['undefined', 'number'].includes(typeof metadata.density));
assert.strictEqual('4:2:0', metadata.chromaSubsampling); assert.strictEqual('4:2:0', metadata.chromaSubsampling);
assert.strictEqual(false, metadata.isProgressive); assert.strictEqual(false, metadata.isProgressive);
assert.strictEqual(false, metadata.hasProfile); assert.strictEqual(false, metadata.hasProfile);
@@ -405,7 +453,7 @@ describe('Image metadata', function () {
assert.strictEqual('srgb', metadata.space); assert.strictEqual('srgb', metadata.space);
assert.strictEqual(3, metadata.channels); assert.strictEqual(3, metadata.channels);
assert.strictEqual('uchar', metadata.depth); assert.strictEqual('uchar', metadata.depth);
assert.strictEqual('undefined', typeof metadata.density); assert.strictEqual(true, ['undefined', 'number'].includes(typeof metadata.density));
assert.strictEqual('4:2:0', metadata.chromaSubsampling); assert.strictEqual('4:2:0', metadata.chromaSubsampling);
assert.strictEqual(false, metadata.isProgressive); assert.strictEqual(false, metadata.isProgressive);
assert.strictEqual(false, metadata.hasProfile); assert.strictEqual(false, metadata.hasProfile);
@@ -453,6 +501,42 @@ describe('Image metadata', function () {
}); });
}); });
it('Apply CMYK output ICC profile', function (done) {
const output = fixtures.path('output.icc-cmyk.jpg');
sharp(fixtures.inputJpg)
.withMetadata({ icc: 'cmyk' })
.toFile(output, function (err, info) {
if (err) throw err;
sharp(output).metadata(function (err, metadata) {
if (err) throw err;
assert.strictEqual(true, metadata.hasProfile);
assert.strictEqual('cmyk', metadata.space);
assert.strictEqual(4, metadata.channels);
// ICC
assert.strictEqual('object', typeof metadata.icc);
assert.strictEqual(true, metadata.icc instanceof Buffer);
const profile = icc.parse(metadata.icc);
assert.strictEqual('object', typeof profile);
assert.strictEqual('CMYK', profile.colorSpace);
assert.strictEqual('Relative', profile.intent);
assert.strictEqual('Printer', profile.deviceClass);
});
fixtures.assertSimilar(output, fixtures.path('expected/icc-cmyk.jpg'), { threshold: 0 }, done);
});
});
it('Apply custom output ICC profile', function (done) {
const output = fixtures.path('output.hilutite.jpg');
sharp(fixtures.inputJpg)
.withMetadata({ icc: fixtures.path('hilutite.icm') })
.toFile(output, function (err, info) {
if (err) throw err;
fixtures.assertMaxColourDistance(output, fixtures.path('expected/hilutite.jpg'), 0);
fixtures.assertMaxColourDistance(output, fixtures.inputJpg, 16.5);
done();
});
});
it('Include metadata in output, enabled via empty object', () => it('Include metadata in output, enabled via empty object', () =>
sharp(fixtures.inputJpgWithExif) sharp(fixtures.inputJpgWithExif)
.withMetadata({}) .withMetadata({})
@@ -627,5 +711,10 @@ describe('Image metadata', function () {
sharp().withMetadata({ orientation: 9 }); sharp().withMetadata({ orientation: 9 });
}); });
}); });
it('Non string icc', function () {
assert.throws(function () {
sharp().withMetadata({ icc: true });
});
});
}); });
}); });

View File

@@ -43,7 +43,10 @@ describe('Platform-detection', function () {
it('Defaults to ARMv6 for 32-bit', function () { it('Defaults to ARMv6 for 32-bit', function () {
process.env.npm_config_arch = 'arm'; process.env.npm_config_arch = 'arm';
const armVersion = process.config.variables.arm_version;
delete process.config.variables.arm_version;
assert.strictEqual('armv6', platform().split('-')[1]); assert.strictEqual('armv6', platform().split('-')[1]);
process.config.variables.arm_version = armVersion;
delete process.env.npm_config_arch; delete process.env.npm_config_arch;
}); });
@@ -52,4 +55,12 @@ describe('Platform-detection', function () {
assert.strictEqual('arm64v8', platform().split('-')[1]); assert.strictEqual('arm64v8', platform().split('-')[1]);
delete process.env.npm_config_arch; delete process.env.npm_config_arch;
}); });
it('Can ensure version ARMv7 if electron version is present', function () {
process.env.npm_config_arch = 'arm';
process.versions.electron = 'test';
assert.strictEqual('armv7', platform().split('-')[1]);
delete process.env.npm_config_arch;
delete process.versions.electron;
});
}); });

View File

@@ -127,8 +127,8 @@ describe('PNG', function () {
it('Valid PNG libimagequant quality value produces image of same size or smaller', function () { it('Valid PNG libimagequant quality value produces image of same size or smaller', function () {
const inputPngBuffer = fs.readFileSync(fixtures.inputPng); const inputPngBuffer = fs.readFileSync(fixtures.inputPng);
return Promise.all([ return Promise.all([
sharp(inputPngBuffer).resize(10).png({ palette: true, quality: 80 }).toBuffer(), sharp(inputPngBuffer).resize(10).png({ quality: 80 }).toBuffer(),
sharp(inputPngBuffer).resize(10).png({ palette: true, quality: 100 }).toBuffer() sharp(inputPngBuffer).resize(10).png({ quality: 100 }).toBuffer()
]).then(function (data) { ]).then(function (data) {
assert.strictEqual(true, data[0].length <= data[1].length); assert.strictEqual(true, data[0].length <= data[1].length);
}); });
@@ -136,15 +136,15 @@ describe('PNG', function () {
it('Invalid PNG libimagequant quality value throws error', function () { it('Invalid PNG libimagequant quality value throws error', function () {
assert.throws(function () { assert.throws(function () {
sharp().png({ palette: true, quality: 101 }); sharp().png({ quality: 101 });
}); });
}); });
it('Valid PNG libimagequant colours value produces image of same size or smaller', function () { it('Valid PNG libimagequant colours value produces image of same size or smaller', function () {
const inputPngBuffer = fs.readFileSync(fixtures.inputPng); const inputPngBuffer = fs.readFileSync(fixtures.inputPng);
return Promise.all([ return Promise.all([
sharp(inputPngBuffer).resize(10).png({ palette: true, colours: 100 }).toBuffer(), sharp(inputPngBuffer).resize(10).png({ colours: 100 }).toBuffer(),
sharp(inputPngBuffer).resize(10).png({ palette: true, colours: 200 }).toBuffer() sharp(inputPngBuffer).resize(10).png({ colours: 200 }).toBuffer()
]).then(function (data) { ]).then(function (data) {
assert.strictEqual(true, data[0].length <= data[1].length); assert.strictEqual(true, data[0].length <= data[1].length);
}); });
@@ -152,21 +152,21 @@ describe('PNG', function () {
it('Invalid PNG libimagequant colours value throws error', function () { it('Invalid PNG libimagequant colours value throws error', function () {
assert.throws(function () { assert.throws(function () {
sharp().png({ palette: true, colours: -1 }); sharp().png({ colours: -1 });
}); });
}); });
it('Invalid PNG libimagequant colors value throws error', function () { it('Invalid PNG libimagequant colors value throws error', function () {
assert.throws(function () { assert.throws(function () {
sharp().png({ palette: true, colors: 0.1 }); sharp().png({ colors: 0.1 });
}); });
}); });
it('Valid PNG libimagequant dither value produces image of same size or smaller', function () { it('Valid PNG libimagequant dither value produces image of same size or smaller', function () {
const inputPngBuffer = fs.readFileSync(fixtures.inputPng); const inputPngBuffer = fs.readFileSync(fixtures.inputPng);
return Promise.all([ return Promise.all([
sharp(inputPngBuffer).resize(10).png({ palette: true, dither: 0.1 }).toBuffer(), sharp(inputPngBuffer).resize(10).png({ dither: 0.1 }).toBuffer(),
sharp(inputPngBuffer).resize(10).png({ palette: true, dither: 0.9 }).toBuffer() sharp(inputPngBuffer).resize(10).png({ dither: 0.9 }).toBuffer()
]).then(function (data) { ]).then(function (data) {
assert.strictEqual(true, data[0].length <= data[1].length); assert.strictEqual(true, data[0].length <= data[1].length);
}); });
@@ -174,7 +174,7 @@ describe('PNG', function () {
it('Invalid PNG libimagequant dither value throws error', function () { it('Invalid PNG libimagequant dither value throws error', function () {
assert.throws(function () { assert.throws(function () {
sharp().png({ palette: true, dither: 'fail' }); sharp().png({ dither: 'fail' });
}); });
}); });
}); });

View File

@@ -168,5 +168,19 @@ describe('Raw pixel data', function () {
done(); done();
}); });
}); });
it('extract A from RGBA', () =>
sharp(fixtures.inputPngWithTransparency)
.resize(32, 24)
.extractChannel(3)
.toColourspace('b-w')
.raw()
.toBuffer({ resolveWithObject: true })
.then(({ info }) => {
assert.strictEqual('raw', info.format);
assert.strictEqual(1, info.channels);
assert.strictEqual(32 * 24, info.size);
})
);
}); });
}); });

View File

@@ -25,6 +25,12 @@ describe('Image Stats', function () {
assert.strictEqual(true, stats.isOpaque); assert.strictEqual(true, stats.isOpaque);
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 7.319914765248541)); assert.strictEqual(true, isInAcceptableRange(stats.entropy, 7.319914765248541));
assert.strictEqual(true, isInAcceptableRange(stats.sharpness, 0.7883011147075762));
const { r, g, b } = stats.dominant;
assert.strictEqual(40, r);
assert.strictEqual(40, g);
assert.strictEqual(40, b);
// red channel // red channel
assert.strictEqual(0, stats.channels[0].min); assert.strictEqual(0, stats.channels[0].min);
@@ -84,6 +90,12 @@ describe('Image Stats', function () {
assert.strictEqual(true, stats.isOpaque); assert.strictEqual(true, stats.isOpaque);
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 0.3409031108021736)); assert.strictEqual(true, isInAcceptableRange(stats.entropy, 0.3409031108021736));
assert.strictEqual(true, isInAcceptableRange(stats.sharpness, 9.111356137722868));
const { r, g, b } = stats.dominant;
assert.strictEqual(248, r);
assert.strictEqual(248, g);
assert.strictEqual(248, b);
// red channel // red channel
assert.strictEqual(0, stats.channels[0].min); assert.strictEqual(0, stats.channels[0].min);
@@ -110,6 +122,12 @@ describe('Image Stats', function () {
assert.strictEqual(false, stats.isOpaque); assert.strictEqual(false, stats.isOpaque);
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 0.06778064835816622)); assert.strictEqual(true, isInAcceptableRange(stats.entropy, 0.06778064835816622));
assert.strictEqual(true, isInAcceptableRange(stats.sharpness, 2.522916068931278));
const { r, g, b } = stats.dominant;
assert.strictEqual(248, r);
assert.strictEqual(248, g);
assert.strictEqual(248, b);
// red channel // red channel
assert.strictEqual(0, stats.channels[0].min); assert.strictEqual(0, stats.channels[0].min);
@@ -185,6 +203,12 @@ describe('Image Stats', function () {
assert.strictEqual(false, stats.isOpaque); assert.strictEqual(false, stats.isOpaque);
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 0)); assert.strictEqual(true, isInAcceptableRange(stats.entropy, 0));
assert.strictEqual(true, isInAcceptableRange(stats.sharpness, 0));
const { r, g, b } = stats.dominant;
assert.strictEqual(72, r);
assert.strictEqual(104, g);
assert.strictEqual(72, b);
// alpha channel // alpha channel
assert.strictEqual(0, stats.channels[3].min); assert.strictEqual(0, stats.channels[3].min);
@@ -212,6 +236,12 @@ describe('Image Stats', function () {
assert.strictEqual(true, stats.isOpaque); assert.strictEqual(true, stats.isOpaque);
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 0.3851250782608986)); assert.strictEqual(true, isInAcceptableRange(stats.entropy, 0.3851250782608986));
assert.strictEqual(true, isInAcceptableRange(stats.sharpness, 10.312521863719589));
const { r, g, b } = stats.dominant;
assert.strictEqual(248, r);
assert.strictEqual(248, g);
assert.strictEqual(248, b);
// red channel // red channel
assert.strictEqual(0, stats.channels[0].min); assert.strictEqual(0, stats.channels[0].min);
@@ -239,6 +269,12 @@ describe('Image Stats', function () {
assert.strictEqual(true, stats.isOpaque); assert.strictEqual(true, stats.isOpaque);
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 7.51758075132966)); assert.strictEqual(true, isInAcceptableRange(stats.entropy, 7.51758075132966));
assert.strictEqual(true, isInAcceptableRange(stats.sharpness, 9.959951636662941));
const { r, g, b } = stats.dominant;
assert.strictEqual(40, r);
assert.strictEqual(136, g);
assert.strictEqual(200, b);
// red channel // red channel
assert.strictEqual(0, stats.channels[0].min); assert.strictEqual(0, stats.channels[0].min);
@@ -298,6 +334,12 @@ describe('Image Stats', function () {
assert.strictEqual(true, stats.isOpaque); assert.strictEqual(true, stats.isOpaque);
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 6.087309412541799)); assert.strictEqual(true, isInAcceptableRange(stats.entropy, 6.087309412541799));
assert.strictEqual(true, isInAcceptableRange(stats.sharpness, 2.9250574456255682));
const { r, g, b } = stats.dominant;
assert.strictEqual(120, r);
assert.strictEqual(136, g);
assert.strictEqual(88, b);
// red channel // red channel
assert.strictEqual(35, stats.channels[0].min); assert.strictEqual(35, stats.channels[0].min);
@@ -357,6 +399,12 @@ describe('Image Stats', function () {
assert.strictEqual(false, stats.isOpaque); assert.strictEqual(false, stats.isOpaque);
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 1)); assert.strictEqual(true, isInAcceptableRange(stats.entropy, 1));
assert.strictEqual(true, isInAcceptableRange(stats.sharpness, 15.870619016486861));
const { r, g, b } = stats.dominant;
assert.strictEqual(8, r);
assert.strictEqual(8, g);
assert.strictEqual(8, b);
// gray channel // gray channel
assert.strictEqual(0, stats.channels[0].min); assert.strictEqual(0, stats.channels[0].min);
@@ -403,6 +451,17 @@ describe('Image Stats', function () {
}) })
); );
it('Dominant colour', () =>
sharp(fixtures.inputJpgBooleanTest)
.stats()
.then(({ dominant }) => {
const { r, g, b } = dominant;
assert.strictEqual(r, 8);
assert.strictEqual(g, 136);
assert.strictEqual(b, 248);
})
);
it('Stream in, Callback out', function (done) { it('Stream in, Callback out', function (done) {
const readable = fs.createReadStream(fixtures.inputJpg); const readable = fs.createReadStream(fixtures.inputJpg);
const pipeline = sharp().stats(function (err, stats) { const pipeline = sharp().stats(function (err, stats) {
@@ -410,6 +469,12 @@ describe('Image Stats', function () {
assert.strictEqual(true, stats.isOpaque); assert.strictEqual(true, stats.isOpaque);
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 7.319914765248541)); assert.strictEqual(true, isInAcceptableRange(stats.entropy, 7.319914765248541));
assert.strictEqual(true, isInAcceptableRange(stats.sharpness, 0.788301114707569));
const { r, g, b } = stats.dominant;
assert.strictEqual(40, r);
assert.strictEqual(40, g);
assert.strictEqual(40, b);
// red channel // red channel
assert.strictEqual(0, stats.channels[0].min); assert.strictEqual(0, stats.channels[0].min);
@@ -472,6 +537,12 @@ describe('Image Stats', function () {
return pipeline.stats().then(function (stats) { return pipeline.stats().then(function (stats) {
assert.strictEqual(true, stats.isOpaque); assert.strictEqual(true, stats.isOpaque);
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 7.319914765248541)); assert.strictEqual(true, isInAcceptableRange(stats.entropy, 7.319914765248541));
assert.strictEqual(true, isInAcceptableRange(stats.sharpness, 0.788301114707569));
const { r, g, b } = stats.dominant;
assert.strictEqual(40, r);
assert.strictEqual(40, g);
assert.strictEqual(40, b);
// red channel // red channel
assert.strictEqual(0, stats.channels[0].min); assert.strictEqual(0, stats.channels[0].min);
@@ -529,6 +600,12 @@ describe('Image Stats', function () {
return sharp(fixtures.inputJpg).stats().then(function (stats) { return sharp(fixtures.inputJpg).stats().then(function (stats) {
assert.strictEqual(true, stats.isOpaque); assert.strictEqual(true, stats.isOpaque);
assert.strictEqual(true, isInAcceptableRange(stats.entropy, 7.319914765248541)); assert.strictEqual(true, isInAcceptableRange(stats.entropy, 7.319914765248541));
assert.strictEqual(true, isInAcceptableRange(stats.sharpness, 0.788301114707569));
const { r, g, b } = stats.dominant;
assert.strictEqual(40, r);
assert.strictEqual(40, g);
assert.strictEqual(40, b);
// red channel // red channel
assert.strictEqual(0, stats.channels[0].min); assert.strictEqual(0, stats.channels[0].min);
@@ -582,6 +659,18 @@ describe('Image Stats', function () {
}); });
}); });
it('Blurred image has lower sharpness than original', () => {
const original = sharp(fixtures.inputJpg).stats();
const blurred = sharp(fixtures.inputJpg).blur().toBuffer().then(blur => sharp(blur).stats());
return Promise
.all([original, blurred])
.then(([original, blurred]) => {
assert.strictEqual(true, isInAcceptableRange(original.sharpness, 0.7883011147075476));
assert.strictEqual(true, isInAcceptableRange(blurred.sharpness, 0.4791559805997398));
});
});
it('File input with corrupt header fails gracefully', function (done) { it('File input with corrupt header fails gracefully', function (done) {
sharp(fixtures.inputJpgWithCorruptHeader) sharp(fixtures.inputJpgWithCorruptHeader)
.stats(function (err) { .stats(function (err) {

View File

@@ -116,7 +116,7 @@ describe('TIFF', function () {
sharp(fixtures.inputTiff8BitDepth) sharp(fixtures.inputTiff8BitDepth)
.toColourspace('b-w') // can only squash 1 band uchar images .toColourspace('b-w') // can only squash 1 band uchar images
.tiff({ .tiff({
squash: false, bitdepth: 8,
compression: 'none', compression: 'none',
predictor: 'none' predictor: 'none'
}) })
@@ -133,7 +133,7 @@ describe('TIFF', function () {
sharp(fixtures.inputTiff8BitDepth) sharp(fixtures.inputTiff8BitDepth)
.toColourspace('b-w') // can only squash 1 band uchar images .toColourspace('b-w') // can only squash 1 band uchar images
.tiff({ .tiff({
squash: true, bitdepth: 1,
compression: 'none', compression: 'none',
predictor: 'none' predictor: 'none'
}) })
@@ -145,10 +145,10 @@ describe('TIFF', function () {
}); });
}); });
it('Invalid TIFF squash value throws error', function () { it('Invalid TIFF bitdepth value throws error', function () {
assert.throws(function () { assert.throws(function () {
sharp().tiff({ squash: 'true' }); sharp().tiff({ bitdepth: 3 });
}); }, /Error: Expected 1, 2, 4 or 8 for bitdepth but received 3 of type number/);
}); });
it('TIFF setting xres and yres on file', () => it('TIFF setting xres and yres on file', () =>
@@ -255,7 +255,7 @@ describe('TIFF', function () {
sharp(fixtures.inputTiff) sharp(fixtures.inputTiff)
.toColourspace('b-w') .toColourspace('b-w')
.tiff({ .tiff({
squash: true, bitdepth: 1,
compression: 'ccittfax4' compression: 'ccittfax4'
}) })
.toFile(fixtures.outputTiff, (err, info) => { .toFile(fixtures.outputTiff, (err, info) => {

View File

@@ -765,6 +765,30 @@ describe('Tile', function () {
}); });
}); });
it('IIIF layout', function (done) {
const directory = fixtures.path('output.iiif.info');
rimraf(directory, function () {
sharp(fixtures.inputJpg)
.tile({
layout: 'iiif'
})
.toFile(directory, function (err, info) {
if (err) throw err;
assert.strictEqual('dz', info.format);
assert.strictEqual(2725, info.width);
assert.strictEqual(2225, info.height);
assert.strictEqual(3, info.channels);
assert.strictEqual('number', typeof info.size);
fs.stat(path.join(directory, '0,0,256,256', '256,', '0', 'default.jpg'), function (err, stat) {
if (err) throw err;
assert.strictEqual(true, stat.isFile());
assert.strictEqual(true, stat.size > 0);
done();
});
});
});
});
it('Write to ZIP container using file extension', function (done) { it('Write to ZIP container using file extension', function (done) {
const container = fixtures.path('output.dz.container.zip'); const container = fixtures.path('output.dz.container.zip');
const extractTo = fixtures.path('output.dz.container'); const extractTo = fixtures.path('output.dz.container');

View File

@@ -94,6 +94,32 @@ describe('Trim borders', function () {
.catch(done); .catch(done);
}); });
it('should rotate before trim', () =>
sharp({
create: {
width: 20,
height: 30,
channels: 3,
background: 'white'
}
})
.rotate(30)
.png()
.toBuffer()
.then(rotated30 =>
sharp(rotated30)
.rotate(-30)
.trim(128)
.toBuffer({ resolveWithObject: true })
.then(({ info }) => {
assert.strictEqual(20, info.width);
assert.strictEqual(31, info.height);
assert.strictEqual(-8, info.trimOffsetTop);
assert.strictEqual(-13, info.trimOffsetLeft);
})
)
);
describe('Invalid thresholds', function () { describe('Invalid thresholds', function () {
[-1, 'fail', {}].forEach(function (threshold) { [-1, 'fail', {}].forEach(function (threshold) {
it(JSON.stringify(threshold), function () { it(JSON.stringify(threshold), function () {

View File

@@ -125,4 +125,63 @@ describe('WebP', function () {
sharp().webp({ reductionEffort: -1 }); sharp().webp({ reductionEffort: -1 });
}); });
}); });
it('invalid pageHeight throws', () => {
assert.throws(() => {
sharp().webp({ pageHeight: 0 });
});
});
it('invalid loop throws', () => {
assert.throws(() => {
sharp().webp({ loop: -1 });
});
assert.throws(() => {
sharp().webp({ loop: 65536 });
});
});
it('invalid delay throws', () => {
assert.throws(() => {
sharp().webp({ delay: [-1] });
});
assert.throws(() => {
sharp().webp({ delay: [65536] });
});
});
it('should double the number of frames with default delay', async () => {
const original = await sharp(fixtures.inputWebPAnimated, { pages: -1 }).metadata();
const updated = await sharp(fixtures.inputWebPAnimated, { pages: -1 })
.webp({ pageHeight: original.pageHeight / 2 })
.toBuffer()
.then(data => sharp(data, { pages: -1 }).metadata());
assert.strictEqual(updated.pages, original.pages * 2);
assert.strictEqual(updated.pageHeight, original.pageHeight / 2);
assert.deepStrictEqual(updated.delay, [...original.delay, ...Array(9).fill(120)]);
});
it('should limit animation loop', async () => {
const updated = await sharp(fixtures.inputWebPAnimated, { pages: -1 })
.webp({ loop: 3 })
.toBuffer()
.then(data => sharp(data, { pages: -1 }).metadata());
assert.strictEqual(updated.loop, 3);
});
it('should change delay between frames', async () => {
const original = await sharp(fixtures.inputWebPAnimated, { pages: -1 }).metadata();
const expectedDelay = [...Array(original.pages).fill(40)];
const updated = await sharp(fixtures.inputWebPAnimated, { pages: -1 })
.webp({ delay: expectedDelay })
.toBuffer()
.then(data => sharp(data, { pages: -1 }).metadata());
assert.deepStrictEqual(updated.delay, expectedDelay);
});
}); });