Compare commits

..

41 Commits

Author SHA1 Message Date
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
Lovell Fuller
d5ecc537af Release v0.24.1 2020-02-15 13:48:46 +00:00
Lovell Fuller
c7a49054fd Docs: update libvips URLs for band format/interpretation 2020-02-15 13:43:47 +00:00
Lovell Fuller
a2314c4aa0 Ensure RGBA LZW TIFF info.channel count #2064 2020-02-15 11:46:13 +00:00
Lovell Fuller
1717173f17 Tests: tighten composite offset thresholds 2020-02-15 10:53:30 +00:00
Lovell Fuller
e44c12f029 Bump dependencies, tar is now node>=10 2020-02-15 10:53:00 +00:00
Lovell Fuller
1a98c390fc Prevent sequentialRead for EXIF-based rotate op #2042 2020-01-20 21:50:43 +00:00
Lovell Fuller
91902740e4 Attempt to detect out-of-date homebrew-managed vips 2020-01-19 19:58:24 +00:00
Lovell Fuller
6aa6a93b44 Docs: add details of ignore-scripts to installation guide 2020-01-19 19:27:50 +00:00
Lovell Fuller
b4135ac9b3 Docs: fix any remaining redirects 2020-01-16 20:57:02 +00:00
Lovell Fuller
78906e6551 Update any remaining documentation links 2020-01-16 20:52:19 +00:00
55 changed files with 1260 additions and 1115 deletions

View File

@@ -44,8 +44,7 @@ Any change that modifies the existing public API should be added to the relevant
| Release | WIP branch |
| ------: | :--------- |
| v0.24.0 | wit |
| v0.25.0 | yield |
| v0.26.0 | zoom |
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
about: Suggest an idea
title: ''
labels: enhancement
assignees: ''
---

View File

@@ -1,20 +1,20 @@
---
name: Installation
about: Something went wrong **installing** sharp
title: ''
about: Something went wrong during either 'npm install sharp' or 'require("sharp")'
labels: installation
assignees: ''
---
Did you see the [documentation relating to installation](https://sharp.pixelplumbing.com/en/stable/install/)?
Did you see the [documentation relating to installation](https://sharp.pixelplumbing.com/install)?
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 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 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
about: Something unexpected occurred **using** sharp
title: ''
about: Installation of sharp was successful but then something unexpected occurred using one of its features
labels: triage
assignees: ''
---
<!-- 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 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?
What is the output of running `npx envinfo --binaries --system`?

View File

@@ -1,9 +1,7 @@
---
name: Question
about: For help understanding an existing feature
title: ''
labels: question
assignees: ''
---
@@ -13,6 +11,6 @@ What are you trying to achieve?
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?

View File

@@ -1,75 +1,137 @@
matrix:
jobs:
include:
- name: "Linux (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"
- name: "Linux x64 (CentOS 7, glibc 2.17) - Node.js 10"
os: linux
dist: bionic
language: minimal
language: shell
before_install:
- sudo docker run -dit --name sharp --env CI --env PREBUILD_TOKEN --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 --env PREBUILD_TOKEN --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 13"
os: linux
dist: bionic
language: shell
before_install:
- sudo docker run -dit --name sharp --env CI --env PREBUILD_TOKEN --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp centos:7
- sudo docker exec sharp bash -c "curl -sL https://rpm.nodesource.com/setup_13.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 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 exec sharp apk add build-base git python2 --update-cache
install: sudo docker exec sharp sh -c "npm install --unsafe-perm"
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
dist: bionic
language: minimal
language: shell
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 exec sharp apk add build-base git python2 --update-cache
install: sudo docker exec sharp sh -c "npm install --unsafe-perm"
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 13"
os: linux
dist: bionic
language: minimal
language: shell
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 exec sharp apk add build-base git python2 --update-cache
install: sudo docker exec sharp sh -c "npm install --unsafe-perm"
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
os: linux
dist: bionic
language: minimal
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 apt-get update
- sudo docker exec sharp apt-get install -y build-essential git python2 nodejs npm
- sudo docker run -dit --name sharp --env CI --env PREBUILD_TOKEN --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_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"
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"
- 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 --env PREBUILD_TOKEN --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 13"
arch: arm64
os: linux
dist: bionic
language: shell
before_install:
- sudo docker run -dit --name sharp --env CI --env PREBUILD_TOKEN --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_13.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
osx_image: xcode10.1
language: node_js
node_js: "10"
- name: "macOS (10.13+) - Node.js 12"
- name: "macOS (10.13) - Node.js 12"
os: osx
osx_image: xcode10.1
language: node_js
node_js: "12"
- name: "macOS (10.13+) - Node.js 13"
- name: "macOS (10.13) - Node.js 13"
os: osx
osx_image: xcode10.1
language: node_js
node_js: "13"
- name: "Unit test coverage report"
os: linux
dist: bionic
language: node_js
node_js: "13"
before_install: unset PREBUILD_TOKEN
after_success:
- npm install coveralls
- cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js
cache:
npm: false

View File

@@ -20,8 +20,7 @@ Lanczos resampling ensures quality is not sacrificed for speed.
As well as image resizing, operations such as
rotation, extraction, compositing and gamma correction are available.
Most modern 64-bit macOS, Windows and Linux systems running
Node versions 10, 12 and 13
Most modern macOS, Windows and Linux systems running Node.js v10+
do not require any additional install or runtime dependencies.
## Examples
@@ -90,10 +89,10 @@ readableStream
### Documentation
Visit [sharp.pixelplumbing.com](https://sharp.pixelplumbing.com/) for complete
[installation instructions](https://sharp.pixelplumbing.com/page/install),
[API documentation](https://sharp.pixelplumbing.com/page/api),
[benchmark tests](https://sharp.pixelplumbing.com/page/performance) and
[changelog](https://sharp.pixelplumbing.com/page/changelog).
[installation instructions](https://sharp.pixelplumbing.com/install),
[API documentation](https://sharp.pixelplumbing.com/api-constructor),
[benchmark tests](https://sharp.pixelplumbing.com/performance) and
[changelog](https://sharp.pixelplumbing.com/changelog).
### Contributing

View File

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

View File

@@ -30,6 +30,9 @@
'msvs_settings': {
'VCCLCompilerTool': {
'ExceptionHandling': 1
},
'VCLinkerTool': {
'ImageHasSafeExceptionHandlers': 'false'
}
},
'msvs_disabled_warnings': [
@@ -44,7 +47,11 @@
]
}, {
'target_name': 'sharp',
'defines': [
'NAPI_VERSION=3'
],
'dependencies': [
'<!(node -p "require(\'node-addon-api\').gyp")',
'libvips-cpp'
],
'variables': {
@@ -65,11 +72,11 @@
'src/stats.cc',
'src/operations.cc',
'src/pipeline.cc',
'src/sharp.cc',
'src/utilities.cc'
'src/utilities.cc',
'src/sharp.cc'
],
'include_dirs': [
'<!(node -e "require(\'nan\')")'
'<!@(node -p "require(\'node-addon-api\').include")',
],
'conditions': [
['use_global_libvips == "true"', {
@@ -189,10 +196,18 @@
'-Wno-cast-function-type'
]
}],
['target_arch == "arm"', {
'cflags_cc': [
'-Wno-psabi'
]
}],
['OS == "win"', {
'msvs_settings': {
'VCCLCompilerTool': {
'ExceptionHandling': 1
},
'VCLinkerTool': {
'ImageHasSafeExceptionHandlers': 'false'
}
},
'msvs_disabled_warnings': [

View File

@@ -16,12 +16,9 @@ Lanczos resampling ensures quality is not sacrificed for speed.
As well as image resizing, operations such as
rotation, extraction, compositing and gamma correction are available.
Most modern 64-bit macOS, Windows and Linux systems running
Node versions 10, 12 and 13
Most modern macOS, Windows and Linux systems running Node.js v10+
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
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)
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
Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.

View File

@@ -49,9 +49,10 @@ Extract a single channel from a multi-channel image.
```javascript
sharp(input)
.extractChannel('green')
.toFile('input_green.jpg', function(err, info) {
.toColourspace('b-w')
.toFile('green.jpg', function(err, info) {
// 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
});
```

View File

@@ -68,7 +68,7 @@ A `Promise` is returned when `callback` is not provided.
- `minY` (y-coordinate of one of the pixel where the minimum 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)
- `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)
### Parameters
@@ -88,9 +88,9 @@ image
Returns **[Promise][5]&lt;[Object][6]>**
[1]: https://github.com/libvips/libvips/blob/master/libvips/iofuncs/enumtypes.c#L636
[1]: https://libvips.github.io/libvips/API/current/VipsImage.html#VipsInterpretation
[2]: https://github.com/libvips/libvips/blob/master/libvips/iofuncs/enumtypes.c#L672
[2]: https://libvips.github.io/libvips/API/current/VipsImage.html#VipsBandFormat
[3]: https://www.npmjs.com/package/icc

View File

@@ -296,7 +296,9 @@ Returns **Sharp**
## 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
@@ -307,6 +309,16 @@ const { data, info } = await sharp('input.jpg')
.toBuffer({ resolveWithObject: true });
```
```javascript
// Extract alpha channel as raw pixel data from PNG input
const data = await sharp('input.png')
.ensureAlpha()
.extractChannel(3)
.colourspace('b-w')
.raw()
.toBuffer();
```
Returns **Sharp**
## tile
@@ -327,7 +339,7 @@ Warning: multiple sharp instances concurrently producing tile output can expose
- `options.depth` **[String][2]?** how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout.
- `options.skipBlanks` **[Number][9]** threshold to skip tile generation, a value 0 - 255 for 8-bit images or 0 - 65535 for 16-bit images (optional, default `-1`)
- `options.container` **[String][2]** tile container, with value `fs` (filesystem) or `zip` (compressed file). (optional, default `'fs'`)
- `options.layout` **[String][2]** filesystem layout, possible values are `dz`, `zoomify` or `google`. (optional, default `'dz'`)
- `options.layout` **[String][2]** filesystem layout, possible values are `dz`, `iiif`, `zoomify` or `google`. (optional, default `'dz'`)
### Examples

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:
- `cover`: Crop to cover both provided dimensions (the default).
- `contain`: Embed within both provided dimensions.
- `cover`: (default) Preserving aspect ratio, ensure the image covers both provided dimensions by cropping/clipping to fit.
- `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.
- `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.
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:
- `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.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
then repeatedly ranks edge regions, discarding the edge with the lowest score based on the selected strategy.

View File

@@ -1,9 +1,59 @@
# Changelog
## v0.25 - *yield*
Requires libvips v8.9.1
### 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*"
Requires libvips v8.9.0.
### v0.24.1 - 15<sup>th</sup> February 2020
* Prevent use of sequentialRead for EXIF-based rotate operation.
[#2042](https://github.com/lovell/sharp/issues/2042)
* Ensure RGBA LZW TIFF returns correct channel count.
[#2064](https://github.com/lovell/sharp/issues/2064)
### v0.24.0 - 16<sup>th</sup> January 2020
* Drop support for Node.js 8.

View File

@@ -25,6 +25,11 @@
"destination": "/install",
"type": 301
},
{
"source": "/page/install",
"destination": "/install",
"type": 301
},
{
"source": "**/api-constructor/**",
"destination": "/api-constructor",
@@ -70,16 +75,31 @@
"destination": "/api-utility",
"type": 301
},
{
"source": "/page/api",
"destination": "/api-constructor",
"type": 301
},
{
"source": "**/performance/**",
"destination": "/performance",
"type": 301
},
{
"source": "/page/performance",
"destination": "/performance",
"type": 301
},
{
"source": "**/changelog/**",
"destination": "/changelog",
"type": 301
},
{
"source": "/page/changelog",
"destination": "/changelog",
"type": 301
},
{
"source": "/en/**",
"destination": "/",

181
docs/humans.txt Normal file
View File

@@ -0,0 +1,181 @@
/* 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

View File

@@ -1,9 +1,10 @@
<!DOCTYPE html>
<html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="Resize large images in common formats to smaller, web-friendly JPEG, PNG and WebP images of varying dimensions">
<link rel="icon" type="image/png" href="https://pixel.plumbing/px/32x32/sharp-logo.svg">
<link rel="apple-touch-icon-precomposed" sizes="152x152" href="https://pixel.plumbing/px/152x152/sharp-logo.svg">
<link rel="apple-touch-icon-precomposed" sizes="144x144" href="https://pixel.plumbing/px/144x144/sharp-logo.svg">
@@ -12,7 +13,31 @@
<link rel="apple-touch-icon-precomposed" sizes="72x72" href="https://pixel.plumbing/px/72x72/sharp-logo.svg">
<link rel="apple-touch-icon-precomposed" href="https://pixel.plumbing/px/57x57/sharp-logo.svg">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/docute@4/dist/docute.min.css">
<link rel="author" href="/humans.txt" type="text/plain">
<link rel="dns-prefetch" href="https://www.google-analytics.com">
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "SoftwareSourceCode",
"name": "sharp",
"description": "High performance Node.js image processing",
"url": "https://sharp.pixelplumbing.com",
"codeRepository": "https://github.com/lovell/sharp",
"programmingLanguage": ["JavaScript", "C++"],
"runtimePlatform": "Node.js",
"copyrightHolder": {
"@context": "https://schema.org",
"@type": "Person",
"name": "Lovell Fuller"
},
"copyrightYear": [2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020],
"license": "https://www.apache.org/licenses/LICENSE-2.0"
}
</script>
<style>
:root {
--link-color: #077;
}
@media (max-width: 576px) {
.shorten-strapline {
display: none;
@@ -62,7 +87,7 @@
docuteGoogleAnalytics('UA-13034748-12'),
docuteApiTitlePlugin
],
sourcePath: 'https://cdn.jsdelivr.net/gh/lovell/sharp@v0.24.0/docs',
sourcePath: 'https://cdn.jsdelivr.net/gh/lovell/sharp@v0.25.2/docs',
nav: [
{
title: 'Funding',

View File

@@ -10,16 +10,17 @@ yarn add sharp
## Prerequisites
* Node.js v10.13.0+
* Node.js v10+
## Prebuilt binaries
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+ on the most common platforms:
* macOS x64 (>= 10.13)
* 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
is downloaded via HTTPS and stored within `node_modules/sharp/vendor` during `npm install`.
@@ -30,8 +31,7 @@ JPEG, PNG, WebP, TIFF, GIF (input) and SVG (input) image formats.
The following platforms have prebuilt libvips but not sharp:
* Linux ARMv6
* Linux ARMv7
* Linux ARM64 (glibc >= 2.29)
* Linux ARMv7 (glibc >= 2.28)
The following platforms require compilation of both libvips and sharp from source:
@@ -42,18 +42,15 @@ The following platforms require compilation of both libvips and sharp from sourc
* FreeBSD
* OpenBSD
The following platforms are completely unsupported:
* Windows x86
* Windows x64 with 32-bit `node.exe`
## Common problems
The platform and major version of Node.js used for `npm install`
must be the same as the platform and major version of Node.js used at runtime.
The architecture and platform of Node.js used for `npm install`
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 --ignore-scripts=false` flag must be used when `npm` has been configured to ignore installation scripts.
Check the output of running `npm install --verbose sharp` for useful error messages.
## Custom libvips
@@ -77,8 +74,8 @@ This module will be compiled from source at `npm install` time when:
Building from source requires:
* C++11 compatible compiler such as gcc 4.8+, clang 3.0+ or MSVC 2013+
* [node-gyp](https://github.com/nodejs/node-gyp#installation) and its dependencies (includes Python 2.7)
* C++11 compiler
* [node-gyp](https://github.com/nodejs/node-gyp#installation) and its dependencies
## Custom prebuilt binaries
@@ -127,24 +124,24 @@ to `false` when using the `yarn` package manager.
## 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
[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.
On machines other than Linux x64, such as macOS and Windows, run the following:
```sh
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:
```sh
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.

2
docs/robots.txt Normal file
View File

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

View File

@@ -14,8 +14,14 @@ const agent = require('../lib/agent');
const libvips = require('../lib/libvips');
const platform = require('../lib/platform');
const minimumLibvipsVersion = libvips.minimumLibvipsVersion;
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}/`;
const minimumGlibcVersionByArch = {
arm: '2.28',
arm64: '2.29',
x64: '2.17'
};
const { minimumLibvipsVersion, minimumLibvipsVersionLabelled } = libvips;
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${minimumLibvipsVersionLabelled}/`;
const fail = function (err) {
npmLog.error('sharp', err.message);
@@ -57,18 +63,14 @@ try {
// Is this arch/platform supported?
const arch = process.env.npm_config_arch || process.arch;
const platformAndArch = platform();
if (platformAndArch === 'win32-ia32') {
throw new Error('Windows x86 (32-bit) node.exe is not supported');
}
if (arch === 'ia32') {
if (arch === 'ia32' && !platformAndArch.startsWith('win32')) {
throw new Error(`Intel Architecture 32-bit systems require manual installation of libvips >= ${minimumLibvipsVersion}`);
}
if (platformAndArch === 'freebsd-x64' || platformAndArch === 'openbsd-x64' || platformAndArch === 'sunos-x64') {
throw new Error(`BSD/SunOS systems require manual installation of libvips >= ${minimumLibvipsVersion}`);
}
if (detectLibc.family === detectLibc.GLIBC && detectLibc.version) {
const minimumGlibcVersion = arch === 'arm64' ? '2.29.0' : '2.17.0';
if (semver.lt(`${detectLibc.version}.0`, minimumGlibcVersion)) {
if (semver.lt(`${detectLibc.version}.0`, `${minimumGlibcVersionByArch[arch]}.0`)) {
throw new Error(`Use with glibc ${detectLibc.version} requires manual installation of libvips >= ${minimumLibvipsVersion}`);
}
}

View File

@@ -54,9 +54,10 @@ function ensureAlpha () {
* @example
* sharp(input)
* .extractChannel('green')
* .toFile('input_green.jpg', function(err, info) {
* .toColourspace('b-w')
* .toFile('green.jpg', function(err, info) {
* // 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.

View File

@@ -100,8 +100,7 @@ function composite (images) {
if (!is.object(image)) {
throw is.invalidParameterError('image to composite', 'object', image);
}
const { raw, density } = image;
const inputOptions = (raw || density) ? { raw, density } : undefined;
const inputOptions = this._inputOptionsFromObject(image);
const composite = {
input: this._createInputDescriptor(image.input, inputOptions, { allowStream: false }),
blend: 'over',

View File

@@ -16,6 +16,8 @@ try {
help.push('- Ensure the version of Node.js used at install time matches that used at runtime');
} else if (/invalid ELF header/.test(err.message)) {
help.push(`- Ensure "${process.platform}" is used at install time as well as runtime`);
} else if (/dylib/.test(err.message) && /Incompatible library version/.test(err.message)) {
help.push('- Run "brew update && brew upgrade vips"');
} else if (/Cannot find module/.test(err.message)) {
help.push('- Run "npm rebuild --verbose sharp" and look for errors');
} else {
@@ -161,7 +163,7 @@ const Sharp = function (input, options) {
gamma: 0,
gammaOut: 0,
greyscale: false,
normalise: 0,
normalise: false,
brightness: 1,
saturation: 1,
hue: 0,
@@ -217,6 +219,11 @@ const Sharp = function (input, options) {
heifCompression: 'hevc',
tileSize: 256,
tileOverlap: 0,
tileContainer: 'fs',
tileLayout: 'dz',
tileFormat: 'last',
tileDepth: 'last',
tileAngle: 0,
tileSkipBlanks: -1,
tileBackground: [255, 255, 255, 255],
linearA: 1,

View File

@@ -1,10 +1,20 @@
'use strict';
const util = require('util');
const color = require('color');
const is = require('./is');
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.
* @private
@@ -24,12 +34,12 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
} else if (is.plainObject(input) && !is.defined(inputOptions)) {
// Plain Object descriptor, e.g. create
inputOptions = input;
if (is.plainObject(inputOptions.raw) || is.bool(inputOptions.failOnError)) {
// Raw Stream
if (_inputOptionsFromObject(inputOptions)) {
// Stream with options
inputDescriptor.buffer = [];
}
} else if (!is.defined(input) && !is.defined(inputOptions) && is.object(containerOptions) && containerOptions.allowStream) {
// Stream
// Stream without options
inputDescriptor.buffer = [];
} else {
throw new Error(`Unsupported input '${input}' of type ${typeof input}${
@@ -187,9 +197,9 @@ function _isStreamInput () {
* - `size`: Total size of image in bytes, for Stream and Buffer input only
* - `width`: Number of pixels wide (EXIF orientation is not taken into consideration)
* - `height`: Number of pixels high (EXIF orientation is not taken into consideration)
* - `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://github.com/libvips/libvips/blob/master/libvips/iofuncs/enumtypes.c#L636)
* - `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://libvips.github.io/libvips/API/current/VipsImage.html#VipsInterpretation)
* - `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK
* - `depth`: Name of pixel depth format e.g. `uchar`, `char`, `ushort`, `float` [...](https://github.com/libvips/libvips/blob/master/libvips/iofuncs/enumtypes.c#L672)
* - `depth`: Name of pixel depth format e.g. `uchar`, `char`, `ushort`, `float` [...](https://libvips.github.io/libvips/API/current/VipsImage.html#VipsBandFormat)
* - `density`: Number of pixels per inch (DPI), if present
* - `chromaSubsampling`: String containing JPEG chroma subsampling, `4:2:0` or `4:4:4` for RGB, `4:2:0:4` or `4:4:4:4` for CMYK
* - `isProgressive`: Boolean indicating whether the image is interlaced using a progressive scan
@@ -278,7 +288,7 @@ function metadata (callback) {
* - `minY` (y-coordinate of one of the pixel where the minimum 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)
* - `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)
*
* @example
@@ -331,32 +341,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.
* @private
@@ -364,15 +348,13 @@ const sequentialRead = util.deprecate(function sequentialRead (sequentialRead) {
module.exports = function (Sharp) {
Object.assign(Sharp.prototype, {
// Private
_inputOptionsFromObject,
_createInputDescriptor,
_write,
_flattenBufferIn,
_isStreamInput,
// Public
metadata,
stats,
// Deprecated
limitInputPixels,
sequentialRead
stats
});
};

View File

@@ -8,8 +8,9 @@ const semver = require('semver');
const platform = require('./platform');
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;
const minimumLibvipsVersion = semver.coerce(minimumLibvipsVersionLabelled).version;
const spawnSyncOptions = {
encoding: 'utf8',
@@ -93,11 +94,12 @@ const useGlobalLibvips = function () {
};
module.exports = {
minimumLibvipsVersion: minimumLibvipsVersion,
cachePath: cachePath,
globalLibvipsVersion: globalLibvipsVersion,
hasVendoredLibvips: hasVendoredLibvips,
pkgConfigPath: pkgConfigPath,
useGlobalLibvips: useGlobalLibvips,
mkdirSync: mkdirSync
minimumLibvipsVersion,
minimumLibvipsVersionLabelled,
cachePath,
globalLibvipsVersion,
hasVendoredLibvips,
pkgConfigPath,
useGlobalLibvips,
mkdirSync
};

View File

@@ -517,7 +517,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
* // Extract raw RGB pixel data from JPEG input
@@ -525,6 +527,15 @@ function heif (options) {
* .raw()
* .toBuffer({ resolveWithObject: true });
*
* @example
* // Extract alpha channel as raw pixel data from PNG input
* const data = await sharp('input.png')
* .ensureAlpha()
* .extractChannel(3)
* .colourspace('b-w')
* .raw()
* .toBuffer();
*
* @returns {Sharp}
*/
function raw () {
@@ -557,7 +568,7 @@ function raw () {
* @param {String} [options.depth] how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout.
* @param {Number} [options.skipBlanks=-1] threshold to skip tile generation, a value 0 - 255 for 8-bit images or 0 - 65535 for 16-bit images
* @param {String} [options.container='fs'] tile container, with value `fs` (filesystem) or `zip` (compressed file).
* @param {String} [options.layout='dz'] filesystem layout, possible values are `dz`, `zoomify` or `google`.
* @param {String} [options.layout='dz'] filesystem layout, possible values are `dz`, `iiif`, `zoomify` or `google`.
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
@@ -592,10 +603,10 @@ function tile (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;
} 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,

View File

@@ -85,21 +85,30 @@ const mapFitToCanvas = {
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`.
*
* 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).
* - `contain`: Embed within both provided dimensions.
* - `cover`: (default) Preserving aspect ratio, ensure the image covers both provided dimensions by cropping/clipping to fit.
* - `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.
* - `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.
*
* 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:
* - `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.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.
*
* The experimental strategy-based approach resizes so one dimension is at its target length
@@ -367,7 +376,7 @@ function extract (options) {
}
}, this);
// 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;
}
return this;
@@ -391,6 +400,9 @@ function trim (threshold) {
} else {
throw is.invalidParameterError('threshold', 'number greater than zero', threshold);
}
if (this.options.trimThreshold && isRotationExpected(this.options)) {
this.options.rotateBeforePreExtract = true;
}
return this;
}

View File

@@ -1,7 +1,7 @@
{
"name": "sharp",
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP and TIFF images",
"version": "0.24.0",
"version": "0.25.2",
"author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://github.com/lovell/sharp",
"contributors": [
@@ -65,10 +65,11 @@
"Andargor <andargor@yahoo.com>",
"Paul Neave <paul.neave@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>"
],
"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 --runtime=napi) || (node-gyp rebuild && node install/dll-copy)",
"clean": "rm -rf node_modules/ build/ vendor/ .nyc_output/ coverage/ test/fixtures/output.*",
"test": "semistandard && cpplint && npm run test-unit && npm run test-licensing && prebuild-ci",
"test-unit": "nyc --reporter=lcov --branches=99 mocha --slow=5000 --timeout=60000 ./test/unit/*.js",
@@ -109,40 +110,45 @@
"dependencies": {
"color": "^3.1.2",
"detect-libc": "^1.0.3",
"nan": "^2.14.0",
"node-addon-api": "^2.0.0",
"npmlog": "^4.1.2",
"prebuild-install": "^5.3.3",
"semver": "^7.1.1",
"semver": "^7.1.3",
"simple-get": "^3.1.0",
"tar": "^5.0.5",
"tar": "^6.0.1",
"tunnel-agent": "^0.6.0"
},
"devDependencies": {
"async": "^3.1.0",
"async": "^3.2.0",
"cc": "^2.0.1",
"decompress-zip": "^0.3.2",
"documentation": "^12.1.4",
"exif-reader": "^1.0.3",
"icc": "^1.0.0",
"license-checker": "^25.0.1",
"mocha": "^7.0.0",
"mock-fs": "^4.10.4",
"mocha": "^7.1.1",
"mock-fs": "^4.11.0",
"nyc": "^15.0.0",
"prebuild": "^10.0.0",
"prebuild-ci": "^3.1.0",
"rimraf": "^3.0.0",
"rimraf": "^3.0.2",
"semistandard": "^14.2.0"
},
"license": "Apache-2.0",
"config": {
"libvips": "8.9.0"
"libvips": "8.9.1"
},
"engines": {
"node": ">=10.13.0"
"node": ">=10"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"binary": {
"napi_versions": [
3
]
},
"semistandard": {
"env": [
"mocha"

View File

@@ -19,9 +19,7 @@
#include <queue>
#include <mutex> // NOLINT(build/c++11)
#include <node.h>
#include <node_buffer.h>
#include <nan.h>
#include <napi.h>
#include <vips/vips8>
#include "common.h"
@@ -30,66 +28,77 @@ using vips::VImage;
namespace sharp {
// Convenience methods to access the attributes of a v8::Object
bool HasAttr(v8::Local<v8::Object> obj, std::string attr) {
return Nan::Has(obj, Nan::New(attr).ToLocalChecked()).FromJust();
// Convenience methods to access the attributes of a Napi::Object
bool HasAttr(Napi::Object obj, std::string attr) {
return obj.Has(attr);
}
std::string AttrAsStr(v8::Local<v8::Object> obj, std::string attr) {
return *Nan::Utf8String(Nan::Get(obj, Nan::New(attr).ToLocalChecked()).ToLocalChecked());
std::string AttrAsStr(Napi::Object obj, std::string attr) {
return obj.Get(attr).As<Napi::String>();
}
std::vector<double> AttrAsRgba(v8::Local<v8::Object> obj, std::string attr) {
v8::Local<v8::Object> background = AttrAs<v8::Object>(obj, attr);
std::vector<double> rgba(4);
for (unsigned int i = 0; i < 4; i++) {
rgba[i] = AttrTo<double>(background, i);
uint32_t AttrAsUint32(Napi::Object obj, std::string attr) {
return obj.Get(attr).As<Napi::Number>().Uint32Value();
}
int32_t AttrAsInt32(Napi::Object obj, std::string 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;
}
// Create an InputDescriptor instance from a v8::Object describing an input image
InputDescriptor* CreateInputDescriptor(
v8::Local<v8::Object> input, std::vector<v8::Local<v8::Object>> &buffersToPersist
) {
Nan::HandleScope();
// Create an InputDescriptor instance from a Napi::Object describing an input image
InputDescriptor* CreateInputDescriptor(Napi::Object input) {
InputDescriptor *descriptor = new InputDescriptor;
if (HasAttr(input, "file")) {
descriptor->file = AttrAsStr(input, "file");
} else if (HasAttr(input, "buffer")) {
v8::Local<v8::Object> buffer = AttrAs<v8::Object>(input, "buffer");
descriptor->bufferLength = node::Buffer::Length(buffer);
descriptor->buffer = node::Buffer::Data(buffer);
Napi::Buffer<char> buffer = input.Get("buffer").As<Napi::Buffer<char>>();
descriptor->bufferLength = buffer.Length();
descriptor->buffer = buffer.Data();
descriptor->isBuffer = TRUE;
buffersToPersist.push_back(buffer);
}
descriptor->failOnError = AttrTo<bool>(input, "failOnError");
descriptor->failOnError = AttrAsBool(input, "failOnError");
// Density for vector-based input
if (HasAttr(input, "density")) {
descriptor->density = AttrTo<double>(input, "density");
descriptor->density = AttrAsDouble(input, "density");
}
// Raw pixel input
if (HasAttr(input, "rawChannels")) {
descriptor->rawChannels = AttrTo<uint32_t>(input, "rawChannels");
descriptor->rawWidth = AttrTo<uint32_t>(input, "rawWidth");
descriptor->rawHeight = AttrTo<uint32_t>(input, "rawHeight");
descriptor->rawChannels = AttrAsUint32(input, "rawChannels");
descriptor->rawWidth = AttrAsUint32(input, "rawWidth");
descriptor->rawHeight = AttrAsUint32(input, "rawHeight");
}
// Multi-page input (GIF, TIFF, PDF)
if (HasAttr(input, "pages")) {
descriptor->pages = AttrTo<int32_t>(input, "pages");
descriptor->pages = AttrAsInt32(input, "pages");
}
if (HasAttr(input, "page")) {
descriptor->page = AttrTo<uint32_t>(input, "page");
descriptor->page = AttrAsUint32(input, "page");
}
// Create new image
if (HasAttr(input, "createChannels")) {
descriptor->createChannels = AttrTo<uint32_t>(input, "createChannels");
descriptor->createWidth = AttrTo<uint32_t>(input, "createWidth");
descriptor->createHeight = AttrTo<uint32_t>(input, "createHeight");
descriptor->createChannels = AttrAsUint32(input, "createChannels");
descriptor->createWidth = AttrAsUint32(input, "createWidth");
descriptor->createHeight = AttrAsUint32(input, "createHeight");
descriptor->createBackground = AttrAsRgba(input, "createBackground");
}
// 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
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;
}
@@ -439,11 +448,9 @@ namespace sharp {
/*
Called when a Buffer undergoes GC, required to support mixed runtime libraries in Windows
*/
void FreeCallback(char* data, void* hint) {
if (data != nullptr) {
g_free(data);
}
}
std::function<void(void*, char*)> FreeCallback = [](void*, char* data) {
g_free(data);
};
/*
Temporary buffer of warnings

View File

@@ -19,14 +19,13 @@
#include <tuple>
#include <vector>
#include <node.h>
#include <nan.h>
#include <napi.h>
#include <vips/vips8>
// Verify platform and compiler compatibility
#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.1+ is required - please see https://sharp.pixelplumbing.com/install"
#endif
#if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6)))
@@ -82,23 +81,18 @@ namespace sharp {
createBackground{ 0.0, 0.0, 0.0, 255.0 } {}
};
// Convenience methods to access the attributes of a v8::Object
bool HasAttr(v8::Local<v8::Object> obj, std::string attr);
std::string AttrAsStr(v8::Local<v8::Object> obj, std::string attr);
std::vector<double> AttrAsRgba(v8::Local<v8::Object> obj, std::string attr);
template<typename T> v8::Local<T> AttrAs(v8::Local<v8::Object> obj, std::string attr) {
return Nan::Get(obj, Nan::New(attr).ToLocalChecked()).ToLocalChecked().As<T>();
}
template<typename T> T AttrTo(v8::Local<v8::Object> obj, std::string attr) {
return Nan::To<T>(Nan::Get(obj, Nan::New(attr).ToLocalChecked()).ToLocalChecked()).FromJust();
}
template<typename T> T AttrTo(v8::Local<v8::Object> obj, int attr) {
return Nan::To<T>(Nan::Get(obj, attr).ToLocalChecked()).FromJust();
}
// Convenience methods to access the attributes of a Napi::Object
bool HasAttr(Napi::Object obj, std::string attr);
std::string AttrAsStr(Napi::Object obj, std::string attr);
uint32_t AttrAsUint32(Napi::Object obj, std::string attr);
int32_t AttrAsInt32(Napi::Object obj, std::string attr);
double AttrAsDouble(Napi::Object obj, std::string attr);
double AttrAsDouble(Napi::Object obj, unsigned int const attr);
bool AttrAsBool(Napi::Object obj, std::string attr);
std::vector<double> AttrAsRgba(Napi::Object obj, std::string attr);
// Create an InputDescriptor instance from a v8::Object describing an input image
InputDescriptor* CreateInputDescriptor(
v8::Local<v8::Object> input, std::vector<v8::Local<v8::Object>> &buffersToPersist); // NOLINT(runtime/references)
// Create an InputDescriptor instance from a Napi::Object describing an input image
InputDescriptor* CreateInputDescriptor(Napi::Object input);
enum class ImageType {
JPEG,
@@ -211,7 +205,7 @@ namespace sharp {
/*
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

View File

@@ -15,28 +15,16 @@
#include <numeric>
#include <vector>
#include <node.h>
#include <nan.h>
#include <napi.h>
#include <vips/vips8>
#include "common.h"
#include "metadata.h"
class MetadataWorker : public Nan::AsyncWorker {
class MetadataWorker : public Napi::AsyncWorker {
public:
MetadataWorker(
Nan::Callback *callback, MetadataBaton *baton, Nan::Callback *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(Napi::Function callback, MetadataBaton *baton, Napi::Function debuglog) :
Napi::AsyncWorker(callback), baton(baton), debuglog(Napi::Persistent(debuglog)) {}
~MetadataWorker() {}
void Execute() {
@@ -137,140 +125,115 @@ class MetadataWorker : public Nan::AsyncWorker {
vips_thread_shutdown();
}
void HandleOKCallback() {
using Nan::New;
using Nan::Set;
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;
void OnOK() {
Napi::Env env = Env();
Napi::HandleScope scope(env);
// 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);
debuglog.Call({ Napi::String::New(env, warning) });
warning = sharp::VipsWarningPop();
}
// Return to JavaScript
callback->Call(2, argv, async_resource);
if (baton->err.empty()) {
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);
}
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:
MetadataBaton* baton;
Nan::Callback *debuglog;
std::vector<v8::Local<v8::Object>> buffersToPersist;
Napi::FunctionReference debuglog;
};
/*
metadata(options, callback)
*/
NAN_METHOD(metadata) {
// Input Buffers must not undergo GC compaction during processing
std::vector<v8::Local<v8::Object>> buffersToPersist;
Napi::Value metadata(const Napi::CallbackInfo& info) {
// V8 objects are converted to non-V8 types held in the baton struct
MetadataBaton *baton = new MetadataBaton;
v8::Local<v8::Object> options = info[0].As<v8::Object>();
Napi::Object options = info[0].As<Napi::Object>();
// 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
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
Nan::Callback *callback = new Nan::Callback(info[1].As<v8::Function>());
Nan::AsyncQueueWorker(new MetadataWorker(callback, baton, debuglog, buffersToPersist));
Napi::Function callback = info[1].As<Napi::Function>();
MetadataWorker *worker = new MetadataWorker(callback, baton, debuglog);
worker->Receiver().Set("options", options);
worker->Queue();
// Increment queued task counter
g_atomic_int_inc(&sharp::counterQueue);
return info.Env().Undefined();
}

View File

@@ -16,7 +16,7 @@
#define SRC_METADATA_H_
#include <string>
#include <nan.h>
#include <napi.h>
#include "./common.h"
@@ -81,6 +81,6 @@ struct MetadataBaton {
tifftagPhotoshopLength(0) {}
};
NAN_METHOD(metadata);
Napi::Value metadata(const Napi::CallbackInfo& info);
#endif // SRC_METADATA_H_

View File

@@ -25,8 +25,7 @@
#include <sys/stat.h>
#include <vips/vips8>
#include <node.h>
#include <nan.h>
#include <napi.h>
#include "common.h"
#include "operations.h"
@@ -46,28 +45,18 @@
#define STAT64_FUNCTION stat64
#endif
class PipelineWorker : public Nan::AsyncWorker {
class PipelineWorker : public Napi::AsyncWorker {
public:
PipelineWorker(
Nan::Callback *callback, PipelineBaton *baton, Nan::Callback *debuglog, Nan::Callback *queueListener,
std::vector<v8::Local<v8::Object>> const buffersToPersist) :
Nan::AsyncWorker(callback, "sharp:PipelineWorker"),
baton(baton), debuglog(debuglog), queueListener(queueListener),
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;
});
}
PipelineWorker(Napi::Function callback, PipelineBaton *baton,
Napi::Function debuglog, Napi::Function queueListener) :
Napi::AsyncWorker(callback),
baton(baton),
debuglog(Napi::Persistent(debuglog)),
queueListener(Napi::Persistent(queueListener)) {}
~PipelineWorker() {}
// libuv worker
void Execute() {
using sharp::HasAlpha;
using sharp::ImageType;
// Decrement queued task counter
g_atomic_int_dec_and_test(&sharp::counterQueue);
// Increment processing task counter
@@ -76,7 +65,7 @@ class PipelineWorker : public Nan::AsyncWorker {
try {
// Open input
vips::VImage image;
ImageType inputImageType;
sharp::ImageType inputImageType;
std::tie(image, inputImageType) = sharp::OpenInput(baton->input);
// Calculate angle of rotation
@@ -236,7 +225,7 @@ class PipelineWorker : public Nan::AsyncWorker {
}
if (
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
) {
if (xshrink >= 8 * shrink_on_load_factor) {
@@ -267,7 +256,7 @@ class PipelineWorker : public Nan::AsyncWorker {
->set("fail", baton->input->failOnError);
if (baton->input->buffer != nullptr) {
VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength);
if (inputImageType == ImageType::JPEG) {
if (inputImageType == sharp::ImageType::JPEG) {
// Reload JPEG buffer
image = VImage::jpegload_buffer(blob, option);
} else {
@@ -276,7 +265,7 @@ class PipelineWorker : public Nan::AsyncWorker {
}
vips_area_unref(reinterpret_cast<VipsArea*>(blob));
} else {
if (inputImageType == ImageType::JPEG) {
if (inputImageType == sharp::ImageType::JPEG) {
// Reload JPEG file
image = VImage::jpegload(const_cast<char*>(baton->input->file.data()), option);
} else {
@@ -320,7 +309,7 @@ class PipelineWorker : public Nan::AsyncWorker {
}
// 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
double const multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0;
// Background colour
@@ -356,11 +345,11 @@ class PipelineWorker : public Nan::AsyncWorker {
bool const shouldComposite = !baton->composite.empty();
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);
}
bool const shouldPremultiplyAlpha = HasAlpha(image) &&
bool const shouldPremultiplyAlpha = sharp::HasAlpha(image) &&
(shouldResize || shouldBlur || shouldConv || shouldSharpen || shouldComposite);
// 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
if (baton->joinChannelIn.size() > 0) {
VImage joinImage;
ImageType joinImageType = ImageType::UNKNOWN;
sharp::ImageType joinImageType = sharp::ImageType::UNKNOWN;
for (unsigned int i = 0; i < baton->joinChannelIn.size(); i++) {
std::tie(joinImage, joinImageType) = sharp::OpenInput(baton->joinChannelIn[i]);
@@ -548,7 +537,7 @@ class PipelineWorker : public Nan::AsyncWorker {
if (shouldComposite) {
for (Composite *composite : baton->composite) {
VImage compositeImage;
ImageType compositeImageType = ImageType::UNKNOWN;
sharp::ImageType compositeImageType = sharp::ImageType::UNKNOWN;
std::tie(compositeImage, compositeImageType) = OpenInput(composite->input);
// Verify within current dimensions
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
compositeImage = compositeImage.colourspace(VIPS_INTERPRETATION_sRGB);
if (!HasAlpha(compositeImage)) {
if (!sharp::HasAlpha(compositeImage)) {
compositeImage = sharp::EnsureAlpha(compositeImage);
}
if (!composite->premultiplied) compositeImage = compositeImage.premultiply();
@@ -638,7 +627,7 @@ class PipelineWorker : public Nan::AsyncWorker {
// Apply bitwise boolean operation between images
if (baton->boolean != nullptr) {
VImage booleanImage;
ImageType booleanImageType = ImageType::UNKNOWN;
sharp::ImageType booleanImageType = sharp::ImageType::UNKNOWN;
std::tie(booleanImage, booleanImageType) = sharp::OpenInput(baton->boolean);
image = sharp::Boolean(image, booleanImage, baton->booleanOp);
}
@@ -703,9 +692,9 @@ class PipelineWorker : public Nan::AsyncWorker {
// Output
if (baton->fileOut.empty()) {
// 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
sharp::AssertImageTypeDimensions(image, ImageType::JPEG);
sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
VipsArea *area = VIPS_AREA(image.jpegsave_buffer(VImage::option()
->set("strip", !baton->withMetadata)
->set("Q", baton->jpegQuality)
@@ -727,9 +716,10 @@ class PipelineWorker : public Nan::AsyncWorker {
baton->channels = std::min(baton->channels, 3);
}
} 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 ||
inputImageType == sharp::ImageType::SVG))) {
// Write PNG to buffer
sharp::AssertImageTypeDimensions(image, ImageType::PNG);
sharp::AssertImageTypeDimensions(image, sharp::ImageType::PNG);
VipsArea *area = VIPS_AREA(image.pngsave_buffer(VImage::option()
->set("strip", !baton->withMetadata)
->set("interlace", baton->pngProgressive)
@@ -744,9 +734,10 @@ class PipelineWorker : public Nan::AsyncWorker {
area->free_fn = nullptr;
vips_area_unref(area);
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
sharp::AssertImageTypeDimensions(image, ImageType::WEBP);
sharp::AssertImageTypeDimensions(image, sharp::ImageType::WEBP);
VipsArea *area = VIPS_AREA(image.webpsave_buffer(VImage::option()
->set("strip", !baton->withMetadata)
->set("Q", baton->webpQuality)
@@ -760,10 +751,12 @@ class PipelineWorker : public Nan::AsyncWorker {
area->free_fn = nullptr;
vips_area_unref(area);
baton->formatOut = "webp";
} else if (baton->formatOut == "tiff" || (baton->formatOut == "input" && inputImageType == ImageType::TIFF)) {
} else if (baton->formatOut == "tiff" ||
(baton->formatOut == "input" && inputImageType == sharp::ImageType::TIFF)) {
// Write TIFF to buffer
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);
}
// Cast pixel values to float, if required
if (baton->tiffPredictor == VIPS_FOREIGN_TIFF_PREDICTOR_FLOAT) {
@@ -786,8 +779,8 @@ class PipelineWorker : public Nan::AsyncWorker {
area->free_fn = nullptr;
vips_area_unref(area);
baton->formatOut = "tiff";
baton->channels = std::min(baton->channels, 3);
} 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
VipsArea *area = VIPS_AREA(image.heifsave_buffer(VImage::option()
->set("strip", !baton->withMetadata)
@@ -799,7 +792,8 @@ class PipelineWorker : public Nan::AsyncWorker {
area->free_fn = nullptr;
vips_area_unref(area);
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
if (baton->greyscale || image.interpretation() == VIPS_INTERPRETATION_B_W) {
// Extract first band for greyscale image
@@ -840,9 +834,9 @@ class PipelineWorker : public Nan::AsyncWorker {
bool const mightMatchInput = baton->formatOut == "input";
bool const willMatchInput = mightMatchInput && !(isJpeg || isPng || isWebp || isTiff || isDz || isDzZip || isV);
if (baton->formatOut == "jpeg" || (mightMatchInput && isJpeg) ||
(willMatchInput && inputImageType == ImageType::JPEG)) {
(willMatchInput && inputImageType == sharp::ImageType::JPEG)) {
// 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()
->set("strip", !baton->withMetadata)
->set("Q", baton->jpegQuality)
@@ -856,9 +850,10 @@ class PipelineWorker : public Nan::AsyncWorker {
baton->formatOut = "jpeg";
baton->channels = std::min(baton->channels, 3);
} 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 ||
inputImageType == sharp::ImageType::SVG))) {
// 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()
->set("strip", !baton->withMetadata)
->set("interlace", baton->pngProgressive)
@@ -870,9 +865,9 @@ class PipelineWorker : public Nan::AsyncWorker {
->set("dither", baton->pngDither));
baton->formatOut = "png";
} else if (baton->formatOut == "webp" || (mightMatchInput && isWebp) ||
(willMatchInput && inputImageType == ImageType::WEBP)) {
(willMatchInput && inputImageType == sharp::ImageType::WEBP)) {
// 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()
->set("strip", !baton->withMetadata)
->set("Q", baton->webpQuality)
@@ -883,10 +878,11 @@ class PipelineWorker : public Nan::AsyncWorker {
->set("alpha_q", baton->webpAlphaQuality));
baton->formatOut = "webp";
} else if (baton->formatOut == "tiff" || (mightMatchInput && isTiff) ||
(willMatchInput && inputImageType == ImageType::TIFF)) {
(willMatchInput && inputImageType == sharp::ImageType::TIFF)) {
// Write TIFF to file
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);
}
image.tiffsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata)
@@ -901,9 +897,8 @@ class PipelineWorker : public Nan::AsyncWorker {
->set("xres", baton->tiffXres)
->set("yres", baton->tiffYres));
baton->formatOut = "tiff";
baton->channels = std::min(baton->channels, 3);
} else if (baton->formatOut == "heif" || (mightMatchInput && isHeif) ||
(willMatchInput && inputImageType == ImageType::HEIF)) {
(willMatchInput && inputImageType == sharp::ImageType::HEIF)) {
// Write HEIF to file
if (sharp::IsAvif(baton->fileOut)) {
baton->heifCompression = VIPS_FOREIGN_HEIF_COMPRESSION_AV1;
@@ -938,9 +933,6 @@ class PipelineWorker : public Nan::AsyncWorker {
};
suffix = AssembleSuffixString(".webp", options);
} 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 {
{"Q", std::to_string(baton->jpegQuality)},
{"interlace", baton->jpegProgressive ? "TRUE" : "FALSE"},
@@ -951,10 +943,11 @@ class PipelineWorker : public Nan::AsyncWorker {
{"optimize_scans", baton->jpegOptimiseScans ? "TRUE": "FALSE"},
{"optimize_coding", baton->jpegOptimiseCoding ? "TRUE": "FALSE"}
};
std::string extname = baton->tileLayout == VIPS_FOREIGN_DZ_LAYOUT_DZ ? ".jpeg" : ".jpg";
suffix = AssembleSuffixString(extname, options);
}
// 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();
}
// Write DZ to file
@@ -976,7 +969,7 @@ class PipelineWorker : public Nan::AsyncWorker {
image.dzsave(const_cast<char*>(baton->fileOut.data()), options);
baton->formatOut = "dz";
} else if (baton->formatOut == "v" || (mightMatchInput && isV) ||
(willMatchInput && inputImageType == ImageType::VIPS)) {
(willMatchInput && inputImageType == sharp::ImageType::VIPS)) {
// Write V to file
image.vipssave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata));
@@ -1000,16 +993,18 @@ class PipelineWorker : public Nan::AsyncWorker {
vips_thread_shutdown();
}
void HandleOKCallback() {
using Nan::New;
using Nan::Set;
Nan::HandleScope();
void OnOK() {
Napi::Env env = Env();
Napi::HandleScope scope(env);
v8::Local<v8::Value> argv[3] = { Nan::Null(), Nan::Null(), Nan::Null() };
if (!baton->err.empty()) {
// Error
argv[0] = Nan::Error(baton->err.data());
} else {
// Handle warnings
std::string warning = sharp::VipsWarningPop();
while (!warning.empty()) {
debuglog.Call({ Napi::String::New(env, warning) });
warning = sharp::VipsWarningPop();
}
if (baton->err.empty()) {
int width = baton->width;
int height = baton->height;
if (baton->topOffsetPre != -1 && (baton->width == -1 || baton->height == -1)) {
@@ -1021,50 +1016,40 @@ class PipelineWorker : public Nan::AsyncWorker {
height = baton->heightPost;
}
// Info Object
v8::Local<v8::Object> info = New<v8::Object>();
Set(info, New("format").ToLocalChecked(), New<v8::String>(baton->formatOut).ToLocalChecked());
Set(info, New("width").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(width)));
Set(info, New("height").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(height)));
Set(info, New("channels").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(baton->channels)));
Set(info, New("premultiplied").ToLocalChecked(), New<v8::Boolean>(baton->premultiplied));
Napi::Object info = Napi::Object::New(env);
info.Set("format", baton->formatOut);
info.Set("width", static_cast<uint32_t>(width));
info.Set("height", static_cast<uint32_t>(height));
info.Set("channels", static_cast<uint32_t>(baton->channels));
info.Set("premultiplied", baton->premultiplied);
if (baton->hasCropOffset) {
Set(info, New("cropOffsetLeft").ToLocalChecked(),
New<v8::Int32>(static_cast<int32_t>(baton->cropOffsetLeft)));
Set(info, New("cropOffsetTop").ToLocalChecked(),
New<v8::Int32>(static_cast<int32_t>(baton->cropOffsetTop)));
info.Set("cropOffsetLeft", static_cast<int32_t>(baton->cropOffsetLeft));
info.Set("cropOffsetTop", static_cast<int32_t>(baton->cropOffsetTop));
}
if (baton->trimThreshold > 0.0) {
Set(info, New("trimOffsetLeft").ToLocalChecked(),
New<v8::Int32>(static_cast<int32_t>(baton->trimOffsetLeft)));
Set(info, New("trimOffsetTop").ToLocalChecked(),
New<v8::Int32>(static_cast<int32_t>(baton->trimOffsetTop)));
info.Set("trimOffsetLeft", static_cast<int32_t>(baton->trimOffsetLeft));
info.Set("trimOffsetTop", static_cast<int32_t>(baton->trimOffsetTop));
}
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
Set(info, New("size").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(baton->bufferOutLength)));
argv[2] = info;
info.Set("size", static_cast<uint32_t>(baton->bufferOutLength));
// 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 {
// Add file size to info
struct STAT64_STRUCT st;
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->input;
delete baton->boolean;
@@ -1077,29 +1062,16 @@ class PipelineWorker : public Nan::AsyncWorker {
}
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
g_atomic_int_dec_and_test(&sharp::counterProcess);
v8::Local<v8::Value> queueLength[1] = { New<v8::Uint32>(sharp::counterQueue) };
queueListener->Call(1, queueLength, async_resource);
delete queueListener;
// Return to JavaScript
callback->Call(3, argv, async_resource);
Napi::Number queueLength = Napi::Number::New(env, static_cast<double>(sharp::counterQueue));
queueListener.Call(Receiver().Value(), { queueLength });
}
private:
PipelineBaton *baton;
Nan::Callback *debuglog;
Nan::Callback *queueListener;
std::vector<v8::Local<v8::Object>> buffersToPersist;
Napi::FunctionReference debuglog;
Napi::FunctionReference queueListener;
/*
Calculate the angle of rotation and need-to-flip for the given Exif orientation
@@ -1169,37 +1141,27 @@ class PipelineWorker : public Nan::AsyncWorker {
/*
pipeline(options, output, callback)
*/
NAN_METHOD(pipeline) {
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;
Napi::Value pipeline(const Napi::CallbackInfo& info) {
// V8 objects are converted to non-V8 types held in the baton struct
PipelineBaton *baton = new PipelineBaton;
v8::Local<v8::Object> options = info[0].As<v8::Object>();
Napi::Object options = info[0].As<Napi::Object>();
// Input
baton->input = CreateInputDescriptor(AttrAs<v8::Object>(options, "input"), buffersToPersist);
baton->input = sharp::CreateInputDescriptor(options.Get("input").As<Napi::Object>());
// Extract image options
baton->topOffsetPre = AttrTo<int32_t>(options, "topOffsetPre");
baton->leftOffsetPre = AttrTo<int32_t>(options, "leftOffsetPre");
baton->widthPre = AttrTo<int32_t>(options, "widthPre");
baton->heightPre = AttrTo<int32_t>(options, "heightPre");
baton->topOffsetPost = AttrTo<int32_t>(options, "topOffsetPost");
baton->leftOffsetPost = AttrTo<int32_t>(options, "leftOffsetPost");
baton->widthPost = AttrTo<int32_t>(options, "widthPost");
baton->heightPost = AttrTo<int32_t>(options, "heightPost");
baton->topOffsetPre = sharp::AttrAsInt32(options, "topOffsetPre");
baton->leftOffsetPre = sharp::AttrAsInt32(options, "leftOffsetPre");
baton->widthPre = sharp::AttrAsInt32(options, "widthPre");
baton->heightPre = sharp::AttrAsInt32(options, "heightPre");
baton->topOffsetPost = sharp::AttrAsInt32(options, "topOffsetPost");
baton->leftOffsetPost = sharp::AttrAsInt32(options, "leftOffsetPost");
baton->widthPost = sharp::AttrAsInt32(options, "widthPost");
baton->heightPost = sharp::AttrAsInt32(options, "heightPost");
// Output image dimensions
baton->width = AttrTo<int32_t>(options, "width");
baton->height = AttrTo<int32_t>(options, "height");
baton->width = sharp::AttrAsInt32(options, "width");
baton->height = sharp::AttrAsInt32(options, "height");
// Canvas option
std::string canvas = AttrAsStr(options, "canvas");
std::string canvas = sharp::AttrAsStr(options, "canvas");
if (canvas == "crop") {
baton->canvas = Canvas::CROP;
} else if (canvas == "embed") {
@@ -1212,191 +1174,168 @@ NAN_METHOD(pipeline) {
baton->canvas = Canvas::IGNORE_ASPECT;
}
// Tint chroma
baton->tintA = AttrTo<double>(options, "tintA");
baton->tintB = AttrTo<double>(options, "tintB");
baton->tintA = sharp::AttrAsDouble(options, "tintA");
baton->tintB = sharp::AttrAsDouble(options, "tintB");
// Composite
v8::Local<v8::Array> compositeArray = Nan::Get(options, Nan::New("composite").ToLocalChecked())
.ToLocalChecked().As<v8::Array>();
int const compositeArrayLength = AttrTo<uint32_t>(compositeArray, "length");
for (int i = 0; i < compositeArrayLength; i++) {
v8::Local<v8::Object> compositeObject = Nan::Get(compositeArray, i).ToLocalChecked().As<v8::Object>();
Napi::Array compositeArray = options.Get("composite").As<Napi::Array>();
for (unsigned int i = 0; i < compositeArray.Length(); i++) {
Napi::Object compositeObject = compositeArray.Get(i).As<Napi::Object>();
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>(
vips_enum_from_nick(nullptr, VIPS_TYPE_BLEND_MODE, AttrAsStr(compositeObject, "blend").data()));
composite->gravity = AttrTo<uint32_t>(compositeObject, "gravity");
composite->left = AttrTo<int32_t>(compositeObject, "left");
composite->top = AttrTo<int32_t>(compositeObject, "top");
composite->tile = AttrTo<bool>(compositeObject, "tile");
composite->premultiplied = AttrTo<bool>(compositeObject, "premultiplied");
vips_enum_from_nick(nullptr, VIPS_TYPE_BLEND_MODE, sharp::AttrAsStr(compositeObject, "blend").data()));
composite->gravity = sharp::AttrAsUint32(compositeObject, "gravity");
composite->left = sharp::AttrAsInt32(compositeObject, "left");
composite->top = sharp::AttrAsInt32(compositeObject, "top");
composite->tile = sharp::AttrAsBool(compositeObject, "tile");
composite->premultiplied = sharp::AttrAsBool(compositeObject, "premultiplied");
baton->composite.push_back(composite);
}
// Resize options
baton->withoutEnlargement = AttrTo<bool>(options, "withoutEnlargement");
baton->position = AttrTo<int32_t>(options, "position");
baton->resizeBackground = AttrAsRgba(options, "resizeBackground");
baton->kernel = AttrAsStr(options, "kernel");
baton->fastShrinkOnLoad = AttrTo<bool>(options, "fastShrinkOnLoad");
baton->withoutEnlargement = sharp::AttrAsBool(options, "withoutEnlargement");
baton->position = sharp::AttrAsInt32(options, "position");
baton->resizeBackground = sharp::AttrAsRgba(options, "resizeBackground");
baton->kernel = sharp::AttrAsStr(options, "kernel");
baton->fastShrinkOnLoad = sharp::AttrAsBool(options, "fastShrinkOnLoad");
// Join Channel Options
if (HasAttr(options, "joinChannelIn")) {
v8::Local<v8::Object> joinChannelObject = Nan::Get(options, Nan::New("joinChannelIn").ToLocalChecked())
.ToLocalChecked().As<v8::Object>();
v8::Local<v8::Array> joinChannelArray = joinChannelObject.As<v8::Array>();
int joinChannelArrayLength = AttrTo<int32_t>(joinChannelObject, "length");
for (int i = 0; i < joinChannelArrayLength; i++) {
if (options.Has("joinChannelIn")) {
Napi::Array joinChannelArray = options.Get("joinChannelIn").As<Napi::Array>();
for (unsigned int i = 0; i < joinChannelArray.Length(); i++) {
baton->joinChannelIn.push_back(
CreateInputDescriptor(
Nan::Get(joinChannelArray, i).ToLocalChecked().As<v8::Object>(),
buffersToPersist));
sharp::CreateInputDescriptor(joinChannelArray.Get(i).As<Napi::Object>()));
}
}
// Operators
baton->flatten = AttrTo<bool>(options, "flatten");
baton->flattenBackground = AttrAsRgba(options, "flattenBackground");
baton->negate = AttrTo<bool>(options, "negate");
baton->blurSigma = AttrTo<double>(options, "blurSigma");
baton->brightness = AttrTo<double>(options, "brightness");
baton->saturation = AttrTo<double>(options, "saturation");
baton->hue = AttrTo<int32_t>(options, "hue");
baton->medianSize = AttrTo<uint32_t>(options, "medianSize");
baton->sharpenSigma = AttrTo<double>(options, "sharpenSigma");
baton->sharpenFlat = AttrTo<double>(options, "sharpenFlat");
baton->sharpenJagged = AttrTo<double>(options, "sharpenJagged");
baton->threshold = AttrTo<int32_t>(options, "threshold");
baton->thresholdGrayscale = AttrTo<bool>(options, "thresholdGrayscale");
baton->trimThreshold = AttrTo<double>(options, "trimThreshold");
baton->gamma = AttrTo<double>(options, "gamma");
baton->gammaOut = AttrTo<double>(options, "gammaOut");
baton->linearA = AttrTo<double>(options, "linearA");
baton->linearB = AttrTo<double>(options, "linearB");
baton->greyscale = AttrTo<bool>(options, "greyscale");
baton->normalise = AttrTo<bool>(options, "normalise");
baton->useExifOrientation = AttrTo<bool>(options, "useExifOrientation");
baton->angle = AttrTo<int32_t>(options, "angle");
baton->rotationAngle = AttrTo<double>(options, "rotationAngle");
baton->rotationBackground = AttrAsRgba(options, "rotationBackground");
baton->rotateBeforePreExtract = AttrTo<bool>(options, "rotateBeforePreExtract");
baton->flip = AttrTo<bool>(options, "flip");
baton->flop = AttrTo<bool>(options, "flop");
baton->extendTop = AttrTo<int32_t>(options, "extendTop");
baton->extendBottom = AttrTo<int32_t>(options, "extendBottom");
baton->extendLeft = AttrTo<int32_t>(options, "extendLeft");
baton->extendRight = AttrTo<int32_t>(options, "extendRight");
baton->extendBackground = AttrAsRgba(options, "extendBackground");
baton->extractChannel = AttrTo<int32_t>(options, "extractChannel");
baton->flatten = sharp::AttrAsBool(options, "flatten");
baton->flattenBackground = sharp::AttrAsRgba(options, "flattenBackground");
baton->negate = sharp::AttrAsBool(options, "negate");
baton->blurSigma = sharp::AttrAsDouble(options, "blurSigma");
baton->brightness = sharp::AttrAsDouble(options, "brightness");
baton->saturation = sharp::AttrAsDouble(options, "saturation");
baton->hue = sharp::AttrAsInt32(options, "hue");
baton->medianSize = sharp::AttrAsUint32(options, "medianSize");
baton->sharpenSigma = sharp::AttrAsDouble(options, "sharpenSigma");
baton->sharpenFlat = sharp::AttrAsDouble(options, "sharpenFlat");
baton->sharpenJagged = sharp::AttrAsDouble(options, "sharpenJagged");
baton->threshold = sharp::AttrAsInt32(options, "threshold");
baton->thresholdGrayscale = sharp::AttrAsBool(options, "thresholdGrayscale");
baton->trimThreshold = sharp::AttrAsDouble(options, "trimThreshold");
baton->gamma = sharp::AttrAsDouble(options, "gamma");
baton->gammaOut = sharp::AttrAsDouble(options, "gammaOut");
baton->linearA = sharp::AttrAsDouble(options, "linearA");
baton->linearB = sharp::AttrAsDouble(options, "linearB");
baton->greyscale = sharp::AttrAsBool(options, "greyscale");
baton->normalise = sharp::AttrAsBool(options, "normalise");
baton->useExifOrientation = sharp::AttrAsBool(options, "useExifOrientation");
baton->angle = sharp::AttrAsInt32(options, "angle");
baton->rotationAngle = sharp::AttrAsDouble(options, "rotationAngle");
baton->rotationBackground = sharp::AttrAsRgba(options, "rotationBackground");
baton->rotateBeforePreExtract = sharp::AttrAsBool(options, "rotateBeforePreExtract");
baton->flip = sharp::AttrAsBool(options, "flip");
baton->flop = sharp::AttrAsBool(options, "flop");
baton->extendTop = sharp::AttrAsInt32(options, "extendTop");
baton->extendBottom = sharp::AttrAsInt32(options, "extendBottom");
baton->extendLeft = sharp::AttrAsInt32(options, "extendLeft");
baton->extendRight = sharp::AttrAsInt32(options, "extendRight");
baton->extendBackground = sharp::AttrAsRgba(options, "extendBackground");
baton->extractChannel = sharp::AttrAsInt32(options, "extractChannel");
baton->removeAlpha = AttrTo<bool>(options, "removeAlpha");
baton->ensureAlpha = AttrTo<bool>(options, "ensureAlpha");
if (HasAttr(options, "boolean")) {
baton->boolean = CreateInputDescriptor(AttrAs<v8::Object>(options, "boolean"), buffersToPersist);
baton->booleanOp = sharp::GetBooleanOperation(AttrAsStr(options, "booleanOp"));
baton->removeAlpha = sharp::AttrAsBool(options, "removeAlpha");
baton->ensureAlpha = sharp::AttrAsBool(options, "ensureAlpha");
if (options.Has("boolean")) {
baton->boolean = sharp::CreateInputDescriptor(options.Get("boolean").As<Napi::Object>());
baton->booleanOp = sharp::GetBooleanOperation(sharp::AttrAsStr(options, "booleanOp"));
}
if (HasAttr(options, "bandBoolOp")) {
baton->bandBoolOp = sharp::GetBooleanOperation(AttrAsStr(options, "bandBoolOp"));
if (options.Has("bandBoolOp")) {
baton->bandBoolOp = sharp::GetBooleanOperation(sharp::AttrAsStr(options, "bandBoolOp"));
}
if (HasAttr(options, "convKernel")) {
v8::Local<v8::Object> kernel = AttrAs<v8::Object>(options, "convKernel");
baton->convKernelWidth = AttrTo<uint32_t>(kernel, "width");
baton->convKernelHeight = AttrTo<uint32_t>(kernel, "height");
baton->convKernelScale = AttrTo<double>(kernel, "scale");
baton->convKernelOffset = AttrTo<double>(kernel, "offset");
if (options.Has("convKernel")) {
Napi::Object kernel = options.Get("convKernel").As<Napi::Object>();
baton->convKernelWidth = sharp::AttrAsUint32(kernel, "width");
baton->convKernelHeight = sharp::AttrAsUint32(kernel, "height");
baton->convKernelScale = sharp::AttrAsDouble(kernel, "scale");
baton->convKernelOffset = sharp::AttrAsDouble(kernel, "offset");
size_t const kernelSize = static_cast<size_t>(baton->convKernelWidth * baton->convKernelHeight);
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++) {
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]);
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++) {
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) {
baton->colourspace = VIPS_INTERPRETATION_sRGB;
}
// Output
baton->formatOut = AttrAsStr(options, "formatOut");
baton->fileOut = AttrAsStr(options, "fileOut");
baton->withMetadata = AttrTo<bool>(options, "withMetadata");
baton->withMetadataOrientation = AttrTo<uint32_t>(options, "withMetadataOrientation");
baton->formatOut = sharp::AttrAsStr(options, "formatOut");
baton->fileOut = sharp::AttrAsStr(options, "fileOut");
baton->withMetadata = sharp::AttrAsBool(options, "withMetadata");
baton->withMetadataOrientation = sharp::AttrAsUint32(options, "withMetadataOrientation");
// Format-specific
baton->jpegQuality = AttrTo<uint32_t>(options, "jpegQuality");
baton->jpegProgressive = AttrTo<bool>(options, "jpegProgressive");
baton->jpegChromaSubsampling = AttrAsStr(options, "jpegChromaSubsampling");
baton->jpegTrellisQuantisation = AttrTo<bool>(options, "jpegTrellisQuantisation");
baton->jpegQuantisationTable = AttrTo<uint32_t>(options, "jpegQuantisationTable");
baton->jpegOvershootDeringing = AttrTo<bool>(options, "jpegOvershootDeringing");
baton->jpegOptimiseScans = AttrTo<bool>(options, "jpegOptimiseScans");
baton->jpegOptimiseCoding = AttrTo<bool>(options, "jpegOptimiseCoding");
baton->pngProgressive = AttrTo<bool>(options, "pngProgressive");
baton->pngCompressionLevel = AttrTo<uint32_t>(options, "pngCompressionLevel");
baton->pngAdaptiveFiltering = AttrTo<bool>(options, "pngAdaptiveFiltering");
baton->pngPalette = AttrTo<bool>(options, "pngPalette");
baton->pngQuality = AttrTo<uint32_t>(options, "pngQuality");
baton->pngColours = AttrTo<uint32_t>(options, "pngColours");
baton->pngDither = AttrTo<double>(options, "pngDither");
baton->webpQuality = AttrTo<uint32_t>(options, "webpQuality");
baton->webpAlphaQuality = AttrTo<uint32_t>(options, "webpAlphaQuality");
baton->webpLossless = AttrTo<bool>(options, "webpLossless");
baton->webpNearLossless = AttrTo<bool>(options, "webpNearLossless");
baton->webpSmartSubsample = AttrTo<bool>(options, "webpSmartSubsample");
baton->webpReductionEffort = AttrTo<uint32_t>(options, "webpReductionEffort");
baton->tiffQuality = AttrTo<uint32_t>(options, "tiffQuality");
baton->tiffPyramid = AttrTo<bool>(options, "tiffPyramid");
baton->tiffSquash = AttrTo<bool>(options, "tiffSquash");
baton->tiffTile = AttrTo<bool>(options, "tiffTile");
baton->tiffTileWidth = AttrTo<uint32_t>(options, "tiffTileWidth");
baton->tiffTileHeight = AttrTo<uint32_t>(options, "tiffTileHeight");
baton->tiffXres = AttrTo<double>(options, "tiffXres");
baton->tiffYres = AttrTo<double>(options, "tiffYres");
baton->jpegQuality = sharp::AttrAsUint32(options, "jpegQuality");
baton->jpegProgressive = sharp::AttrAsBool(options, "jpegProgressive");
baton->jpegChromaSubsampling = sharp::AttrAsStr(options, "jpegChromaSubsampling");
baton->jpegTrellisQuantisation = sharp::AttrAsBool(options, "jpegTrellisQuantisation");
baton->jpegQuantisationTable = sharp::AttrAsUint32(options, "jpegQuantisationTable");
baton->jpegOvershootDeringing = sharp::AttrAsBool(options, "jpegOvershootDeringing");
baton->jpegOptimiseScans = sharp::AttrAsBool(options, "jpegOptimiseScans");
baton->jpegOptimiseCoding = sharp::AttrAsBool(options, "jpegOptimiseCoding");
baton->pngProgressive = sharp::AttrAsBool(options, "pngProgressive");
baton->pngCompressionLevel = sharp::AttrAsUint32(options, "pngCompressionLevel");
baton->pngAdaptiveFiltering = sharp::AttrAsBool(options, "pngAdaptiveFiltering");
baton->pngPalette = sharp::AttrAsBool(options, "pngPalette");
baton->pngQuality = sharp::AttrAsUint32(options, "pngQuality");
baton->pngColours = sharp::AttrAsUint32(options, "pngColours");
baton->pngDither = sharp::AttrAsDouble(options, "pngDither");
baton->webpQuality = sharp::AttrAsUint32(options, "webpQuality");
baton->webpAlphaQuality = sharp::AttrAsUint32(options, "webpAlphaQuality");
baton->webpLossless = sharp::AttrAsBool(options, "webpLossless");
baton->webpNearLossless = sharp::AttrAsBool(options, "webpNearLossless");
baton->webpSmartSubsample = sharp::AttrAsBool(options, "webpSmartSubsample");
baton->webpReductionEffort = sharp::AttrAsUint32(options, "webpReductionEffort");
baton->tiffQuality = sharp::AttrAsUint32(options, "tiffQuality");
baton->tiffPyramid = sharp::AttrAsBool(options, "tiffPyramid");
baton->tiffSquash = sharp::AttrAsBool(options, "tiffSquash");
baton->tiffTile = sharp::AttrAsBool(options, "tiffTile");
baton->tiffTileWidth = sharp::AttrAsUint32(options, "tiffTileWidth");
baton->tiffTileHeight = sharp::AttrAsUint32(options, "tiffTileHeight");
baton->tiffXres = sharp::AttrAsDouble(options, "tiffXres");
baton->tiffYres = sharp::AttrAsDouble(options, "tiffYres");
// tiff compression options
baton->tiffCompression = static_cast<VipsForeignTiffCompression>(
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_COMPRESSION,
AttrAsStr(options, "tiffCompression").data()));
sharp::AttrAsStr(options, "tiffCompression").data()));
baton->tiffPredictor = static_cast<VipsForeignTiffPredictor>(
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_PREDICTOR,
AttrAsStr(options, "tiffPredictor").data()));
baton->heifQuality = AttrTo<uint32_t>(options, "heifQuality");
baton->heifLossless = AttrTo<bool>(options, "heifLossless");
sharp::AttrAsStr(options, "tiffPredictor").data()));
baton->heifQuality = sharp::AttrAsUint32(options, "heifQuality");
baton->heifLossless = sharp::AttrAsBool(options, "heifLossless");
baton->heifCompression = static_cast<VipsForeignHeifCompression>(
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_HEIF_COMPRESSION,
AttrAsStr(options, "heifCompression").data()));
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_HEIF_COMPRESSION,
sharp::AttrAsStr(options, "heifCompression").data()));
// Tile output
baton->tileSize = AttrTo<uint32_t>(options, "tileSize");
baton->tileOverlap = AttrTo<uint32_t>(options, "tileOverlap");
std::string tileContainer = AttrAsStr(options, "tileContainer");
baton->tileAngle = AttrTo<int32_t>(options, "tileAngle");
baton->tileBackground = AttrAsRgba(options, "tileBackground");
baton->tileSkipBlanks = AttrTo<int32_t>(options, "tileSkipBlanks");
if (tileContainer == "zip") {
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP;
} else {
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_FS;
}
std::string tileLayout = AttrAsStr(options, "tileLayout");
if (tileLayout == "google") {
baton->tileLayout = VIPS_FOREIGN_DZ_LAYOUT_GOOGLE;
} else if (tileLayout == "zoomify") {
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;
}
baton->tileSize = sharp::AttrAsUint32(options, "tileSize");
baton->tileOverlap = sharp::AttrAsUint32(options, "tileOverlap");
baton->tileAngle = sharp::AttrAsInt32(options, "tileAngle");
baton->tileBackground = sharp::AttrAsRgba(options, "tileBackground");
baton->tileSkipBlanks = sharp::AttrAsInt32(options, "tileSkipBlanks");
baton->tileContainer = static_cast<VipsForeignDzContainer>(
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_DZ_CONTAINER,
sharp::AttrAsStr(options, "tileContainer").data()));
baton->tileLayout = static_cast<VipsForeignDzLayout>(
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_DZ_LAYOUT,
sharp::AttrAsStr(options, "tileLayout").data()));
baton->tileFormat = sharp::AttrAsStr(options, "tileFormat");
baton->tileDepth = static_cast<VipsForeignDzDepth>(
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_DZ_DEPTH,
sharp::AttrAsStr(options, "tileDepth").data()));
// Force random access for certain operations
if (baton->input->access == VIPS_ACCESS_SEQUENTIAL) {
@@ -1404,25 +1343,30 @@ NAN_METHOD(pipeline) {
baton->trimThreshold > 0.0 ||
baton->normalise ||
baton->position == 16 || baton->position == 17 ||
baton->angle != 0 || baton->rotationAngle != 0.0
baton->angle % 360 != 0 ||
fmod(baton->rotationAngle, 360.0) != 0.0 ||
baton->useExifOrientation
) {
baton->input->access = VIPS_ACCESS_RANDOM;
}
}
// 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
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
Nan::Callback *callback = new Nan::Callback(info[1].As<v8::Function>());
Nan::AsyncQueueWorker(new PipelineWorker(callback, baton, debuglog, queueListener, buffersToPersist));
Napi::Function callback = info[1].As<Napi::Function>();
PipelineWorker *worker = new PipelineWorker(callback, baton, debuglog, queueListener);
worker->Receiver().Set("options", options);
worker->Queue();
// Increment queued task counter
g_atomic_int_inc(&sharp::counterQueue);
v8::Local<v8::Value> queueLength[1] = { Nan::New<v8::Uint32>(sharp::counterQueue) };
v8::Local<v8::Object> recv = Nan::New<v8::Object>();
Nan::Call(*queueListener, recv, 1, queueLength);
Napi::Number queueLength = Napi::Number::New(info.Env(), static_cast<double>(sharp::counterQueue));
queueListener.Call(info.This(), { queueLength });
return info.Env().Undefined();
}

View File

@@ -19,12 +19,12 @@
#include <string>
#include <vector>
#include <nan.h>
#include <napi.h>
#include <vips/vips8>
#include "./common.h"
NAN_METHOD(pipeline);
Napi::Value pipeline(const Napi::CallbackInfo& info);
enum class Canvas {
CROP,
@@ -150,7 +150,7 @@ struct PipelineBaton {
double tiffXres;
double tiffYres;
int heifQuality;
int heifCompression; // TODO(libvips 8.9.0): VipsForeignHeifCompression
VipsForeignHeifCompression heifCompression;
bool heifLossless;
std::string err;
bool withMetadata;
@@ -258,7 +258,7 @@ struct PipelineBaton {
tiffXres(1.0),
tiffYres(1.0),
heifQuality(80),
heifCompression(1), // TODO(libvips 8.9.0): VIPS_FOREIGN_HEIF_COMPRESSION_HEVC
heifCompression(VIPS_FOREIGN_HEIF_COMPRESSION_HEVC),
heifLossless(false),
withMetadata(false),
withMetadataOrientation(-1),

View File

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

View File

@@ -16,28 +16,16 @@
#include <vector>
#include <iostream>
#include <node.h>
#include <nan.h>
#include <napi.h>
#include <vips/vips8>
#include "common.h"
#include "stats.h"
class StatsWorker : public Nan::AsyncWorker {
class StatsWorker : public Napi::AsyncWorker {
public:
StatsWorker(
Nan::Callback *callback, StatsBaton *baton, Nan::Callback *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(Napi::Function callback, StatsBaton *baton, Napi::Function debuglog) :
Napi::AsyncWorker(callback), baton(baton), debuglog(Napi::Persistent(debuglog)) {}
~StatsWorker() {}
const int STAT_MIN_INDEX = 0;
@@ -54,13 +42,9 @@ class StatsWorker : public Nan::AsyncWorker {
void Execute() {
// Decrement queued task counter
g_atomic_int_dec_and_test(&sharp::counterQueue);
using Nan::New;
using Nan::Set;
using sharp::MaximumImageAlpha;
vips::VImage image;
sharp::ImageType imageType = sharp::ImageType::UNKNOWN;
try {
std::tie(image, imageType) = OpenInput(baton->input);
} catch (vips::VError const &err) {
@@ -71,20 +55,23 @@ class StatsWorker : public Nan::AsyncWorker {
vips::VImage stats = image.stats();
int const bands = image.bands();
for (int b = 1; b <= bands; b++) {
ChannelStats cStats(static_cast<int>(stats.getpoint(STAT_MIN_INDEX, b).front()),
static_cast<int>(stats.getpoint(STAT_MAX_INDEX, b).front()),
stats.getpoint(STAT_SUM_INDEX, b).front(), stats.getpoint(STAT_SQ_SUM_INDEX, b).front(),
stats.getpoint(STAT_MEAN_INDEX, b).front(), stats.getpoint(STAT_STDEV_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()));
ChannelStats cStats(
static_cast<int>(stats.getpoint(STAT_MIN_INDEX, b).front()),
static_cast<int>(stats.getpoint(STAT_MAX_INDEX, b).front()),
stats.getpoint(STAT_SUM_INDEX, b).front(),
stats.getpoint(STAT_SQ_SUM_INDEX, b).front(),
stats.getpoint(STAT_MEAN_INDEX, b).front(),
stats.getpoint(STAT_STDEV_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);
}
// Image is not opaque when alpha layer is present and contains a non-mamixa value
if (sharp::HasAlpha(image)) {
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;
}
}
@@ -100,92 +87,78 @@ class StatsWorker : public Nan::AsyncWorker {
vips_thread_shutdown();
}
void HandleOKCallback() {
using Nan::New;
using Nan::Set;
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;
void OnOK() {
Napi::Env env = Env();
Napi::HandleScope scope(env);
// 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);
debuglog.Call({ Napi::String::New(env, warning) });
warning = sharp::VipsWarningPop();
}
// Return to JavaScript
callback->Call(2, argv, async_resource);
if (baton->err.empty()) {
// 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);
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:
StatsBaton* baton;
Nan::Callback *debuglog;
std::vector<v8::Local<v8::Object>> buffersToPersist;
Napi::FunctionReference debuglog;
};
/*
stats(options, callback)
*/
NAN_METHOD(stats) {
using sharp::AttrTo;
// Input Buffers must not undergo GC compaction during processing
std::vector<v8::Local<v8::Object>> buffersToPersist;
Napi::Value stats(const Napi::CallbackInfo& info) {
// V8 objects are converted to non-V8 types held in the baton struct
StatsBaton *baton = new StatsBaton;
v8::Local<v8::Object> options = info[0].As<v8::Object>();
Napi::Object options = info[0].As<Napi::Object>();
// 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
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
Nan::Callback *callback = new Nan::Callback(info[1].As<v8::Function>());
Nan::AsyncQueueWorker(new StatsWorker(callback, baton, debuglog, buffersToPersist));
Napi::Function callback = info[1].As<Napi::Function>();
StatsWorker *worker = new StatsWorker(callback, baton, debuglog);
worker->Receiver().Set("options", options);
worker->Queue();
// Increment queued task counter
g_atomic_int_inc(&sharp::counterQueue);
return info.Env().Undefined();
}

View File

@@ -16,7 +16,7 @@
#define SRC_STATS_H_
#include <string>
#include <nan.h>
#include <napi.h>
#include "./common.h"
@@ -33,12 +33,8 @@ struct ChannelStats {
int maxX;
int maxY;
ChannelStats():
min(0), max(0), sum(0), squaresSum(0), mean(0), stdev(0)
, 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):
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),
mean(meanVal), stdev(stdevVal), minX(minXVal), minY(minYVal), maxX(maxXVal), maxY(maxYVal) {}
};
@@ -61,6 +57,6 @@ struct StatsBaton {
{}
};
NAN_METHOD(stats);
Napi::Value stats(const Napi::CallbackInfo& info);
#endif // SRC_STATS_H_

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -578,11 +578,14 @@
fun:_ZN4node20BackgroundTaskRunnerC1Ei
}
{
leak_nan_FunctionCallbackInfo
leak_napi_module_register
Memcheck:Leak
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

View File

@@ -5,7 +5,10 @@ const sharp = require('../../');
const usingCache = detectLibc.family !== detectLibc.MUSL;
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 () {
sharp.cache(usingCache);

View File

@@ -141,7 +141,7 @@ describe('composite', () => {
it('zero offset', done => {
sharp(fixtures.inputJpg)
.resize(400)
.resize(80)
.composite([{
input: fixtures.inputPngWithTransparency16bit,
top: 0,
@@ -157,7 +157,7 @@ describe('composite', () => {
it('offset and gravity', done => {
sharp(fixtures.inputJpg)
.resize(400)
.resize(80)
.composite([{
input: fixtures.inputPngWithTransparency16bit,
left: 10,
@@ -174,7 +174,7 @@ describe('composite', () => {
it('offset, gravity and tile', done => {
sharp(fixtures.inputJpg)
.resize(400)
.resize(80)
.composite([{
input: fixtures.inputPngWithTransparency16bit,
left: 10,
@@ -192,7 +192,7 @@ describe('composite', () => {
it('offset and tile', done => {
sharp(fixtures.inputJpg)
.resize(400)
.resize(80)
.composite([{
input: fixtures.inputPngWithTransparency16bit,
left: 10,

View File

@@ -48,8 +48,8 @@ describe('failOnError', function () {
it('returns errors to callback for truncated JPEG', function (done) {
sharp(fixtures.inputJpgTruncated).toBuffer(function (err, data, info) {
assert.ok(err.message.includes('VipsJpeg: Premature end of JPEG file'), err);
assert.strictEqual(data, null);
assert.strictEqual(info, null);
assert.strictEqual(data, undefined);
assert.strictEqual(info, undefined);
done();
});
});
@@ -57,8 +57,8 @@ describe('failOnError', function () {
it('returns errors to callback for truncated PNG', function (done) {
sharp(fixtures.inputPngTruncated).toBuffer(function (err, data, info) {
assert.ok(err.message.includes('vipspng: libpng read error'), err);
assert.strictEqual(data, null);
assert.strictEqual(info, null);
assert.strictEqual(data, undefined);
assert.strictEqual(info, undefined);
done();
});
});

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', () =>
sharp(fixtures.inputJpg, { sequentialRead: false })
.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) {
sharp(fixtures.inputPng)
.resize(320, 240)
@@ -653,81 +621,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 () {
it('Option-less', function () {
sharp();

View File

@@ -43,7 +43,10 @@ describe('Platform-detection', function () {
it('Defaults to ARMv6 for 32-bit', function () {
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]);
process.config.variables.arm_version = armVersion;
delete process.env.npm_config_arch;
});

View File

@@ -168,5 +168,19 @@ describe('Raw pixel data', function () {
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

@@ -208,11 +208,48 @@ describe('TIFF', function () {
.toFile(fixtures.outputTiff, (err, info) => {
if (err) throw err;
assert.strictEqual('tiff', info.format);
assert.strictEqual(3, info.channels);
assert(info.size < startSize);
rimraf(fixtures.outputTiff, done);
});
});
it('TIFF LZW RGBA toFile', () =>
sharp({
create: {
width: 1,
height: 1,
channels: 4,
background: 'red'
}
})
.tiff({
compression: 'lzw'
})
.toFile(fixtures.outputTiff)
.then(info => {
assert.strictEqual(4, info.channels);
})
);
it('TIFF LZW RGBA toBuffer', () =>
sharp({
create: {
width: 1,
height: 1,
channels: 4,
background: 'red'
}
})
.tiff({
compression: 'lzw'
})
.toBuffer({ resolveWithObject: true })
.then(({ info }) => {
assert.strictEqual(4, info.channels);
})
);
it('TIFF ccittfax4 compression shrinks b-w test file', function (done) {
const startSize = fs.statSync(fixtures.inputTiff).size;
sharp(fixtures.inputTiff)

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) {
const container = fixtures.path('output.dz.container.zip');
const extractTo = fixtures.path('output.dz.container');

View File

@@ -94,6 +94,32 @@ describe('Trim borders', function () {
.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 () {
[-1, 'fail', {}].forEach(function (threshold) {
it(JSON.stringify(threshold), function () {