Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: lovell/sharp
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: fbe48d75ddf5c1148d4f6006c5269b20155a73ae
Choose a base ref
...
head repository: lovell/sharp
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: db654de385e06ee6c56a05aa11a11a2e2f781b14
Choose a head ref

Commits on Oct 23, 2020

  1. CI: add Node.js 15 (#2415)

    nstepien authored Oct 23, 2020
    Copy the full SHA
    e6a035e View commit details

Commits on Nov 16, 2020

  1. Docs: use of N-API removes Electron-specific binaries

    Clarify Lambda deployment for Windows/macOS users
    lovell committed Nov 16, 2020

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    ab653ca View commit details
  2. Copy the full SHA
    2872602 View commit details
  3. Copy the full SHA
    0f473fe View commit details
  4. Docs: changelog for #2336

    lovell committed Nov 16, 2020
    Copy the full SHA
    4671810 View commit details
  5. Bump dev dependencies

    lovell committed Nov 16, 2020
    Copy the full SHA
    2678d7a View commit details
  6. Copy the full SHA
    53dd313 View commit details
  7. Copy the full SHA
    fabe720 View commit details
  8. Copy the full SHA
    65acd96 View commit details
  9. Docs: changelog entry for #2412

    lovell committed Nov 16, 2020
    Copy the full SHA
    93455f8 View commit details
  10. Release v0.26.3

    lovell committed Nov 16, 2020
    Copy the full SHA
    c10888e View commit details

Commits on Dec 9, 2020

  1. Copy the full SHA
    a0d89ed View commit details

Commits on Dec 18, 2020

  1. Upgrade to libvips 8.10.5, AVIF support in prebuilt binaries

    Remove experimental status from HEIF, changing defaults
    to prefer royalty-free AV1 over patent-encumbered HEVC
    lovell committed Dec 18, 2020
    Copy the full SHA
    103ec0d View commit details
  2. Copy the full SHA
    e59e146 View commit details

Commits on Dec 20, 2020

  1. Copy the full SHA
    ee54ce9 View commit details
  2. Copy the full SHA
    ef964b5 View commit details
  3. Copy the full SHA
    7c08a09 View commit details
  4. Copy the full SHA
    182beaa View commit details
  5. Allow for negative top/left offsets in composite overlays

    A top or left offset value of -1 will no longer mean that the
    value is not set, but will now be an actual offset of -1.
    
    INT_MIN for left & top will mean that the values are not set.
    
    Co-authored-by: Christian Flintrup <chr@gigahost.dk>
    2 people authored and lovell committed Dec 20, 2020
    Copy the full SHA
    0267614 View commit details

Commits on Dec 21, 2020

  1. Copy the full SHA
    2bbd9b2 View commit details
  2. Copy the full SHA
    0e62bde View commit details
  3. Copy the full SHA
    774d782 View commit details
  4. Copy the full SHA
    f4e259d View commit details

Commits on Dec 22, 2020

  1. Copy the full SHA
    4debc46 View commit details
  2. Release v0.27.0

    lovell committed Dec 22, 2020
    Copy the full SHA
    b2a0b8c View commit details

Commits on Jan 1, 2021

  1. Copy the full SHA
    39ddb6a View commit details
  2. Copy the full SHA
    bf1b326 View commit details

Commits on Jan 6, 2021

  1. Copy the full SHA
    4821a11 View commit details
  2. Docs: changelog entry for #2511

    lovell committed Jan 6, 2021
    Copy the full SHA
    a7003e9 View commit details
  3. Copy the full SHA
    d6376c3 View commit details
  4. Copy the full SHA
    138e60a View commit details

Commits on Jan 13, 2021

  1. Ensure tests pass with latest libvips master branch

    Expose forthcoming HEIF features where available
    lovell committed Jan 13, 2021
    Copy the full SHA
    8d49b7d View commit details
  2. Bump devDependencies

    lovell committed Jan 13, 2021
    Copy the full SHA
    f7e2b36 View commit details
  3. Copy the full SHA
    bba00c2 View commit details
  4. Copy the full SHA
    79170af View commit details
  5. Copy the full SHA
    290df1b View commit details
  6. Copy the full SHA
    762d591 View commit details

Commits on Jan 15, 2021

  1. Copy the full SHA
    5031c83 View commit details

Commits on Jan 16, 2021

  1. Copy the full SHA
    419cbe5 View commit details
  2. Copy the full SHA
    c9f85fe View commit details
  3. Copy the full SHA
    1dd93c1 View commit details

Commits on Jan 18, 2021

  1. Copy the full SHA
    4c57ac2 View commit details

Commits on Jan 24, 2021

  1. Copy the full SHA
    f09be93 View commit details
  2. Copy the full SHA
    98349bd View commit details

Commits on Jan 26, 2021

  1. Copy the full SHA
    0bb8cb9 View commit details
  2. Copy the full SHA
    ceff628 View commit details
  3. Avoid calling g_type_from_name #2535

    kleisauke authored and lovell committed Jan 26, 2021
    Copy the full SHA
    573ed5f View commit details
  4. Copy the full SHA
    24d9e53 View commit details
  5. Copy the full SHA
    67213ae View commit details
  6. Copy the full SHA
    171aade View commit details
Showing with 8,650 additions and 2,440 deletions.
  1. +79 −0 .circleci/config.yml
  2. +5 −4 .cirrus.yml
  3. +7 −7 .github/CONTRIBUTING.md
  4. +5 −0 .github/ISSUE_TEMPLATE/config.yml
  5. +17 −5 .github/ISSUE_TEMPLATE/feature_request.md
  6. +31 −6 .github/ISSUE_TEMPLATE/installation.md
  7. +35 −6 .github/ISSUE_TEMPLATE/possible-bug.md
  8. +14 −4 .github/ISSUE_TEMPLATE/question.md
  9. +36 −0 .github/workflows/ci-darwin-arm64v8.yml
  10. +98 −0 .github/workflows/ci.yml
  11. +1 −1 .prebuildrc
  12. +0 −139 .travis.yml
  13. +19 −18 README.md
  14. +0 −26 appveyor.yml
  15. +15 −14 binding.gyp
  16. +11 −7 docs/README.md
  17. +52 −27 docs/api-channel.md
  18. +80 −9 docs/api-colour.md
  19. +49 −23 docs/api-composite.md
  20. +99 −40 docs/api-constructor.md
  21. +81 −46 docs/api-input.md
  22. +302 −67 docs/api-operation.md
  23. +333 −125 docs/api-output.md
  24. +75 −45 docs/api-resize.md
  25. +98 −21 docs/api-utility.md
  26. +25 −0 docs/build.js
  27. +362 −0 docs/changelog.md
  28. +1 −0 docs/docute.min.js
  29. +4 −5 docs/firebase.json
  30. +39 −0 docs/humans.txt
  31. +11 −0 docs/image/sharp-logo-mono.svg
  32. BIN docs/image/sharp-logo.png
  33. +1 −1 docs/image/sharp-logo.svg
  34. +12 −22 docs/index.html
  35. +150 −46 docs/install.md
  36. +21 −17 docs/performance.md
  37. +1 −1 docs/search-index.json
  38. +10 −8 docs/search-index/build.js
  39. +17 −67 docs/search-index/extract.js
  40. +120 −0 docs/search-index/stop-words.js
  41. +11 −0 install/can-compile.js
  42. +10 −10 install/dll-copy.js
  43. +128 −43 install/libvips.js
  44. +0 −12 install/prebuild-ci.js
  45. +1 −1 lib/agent.js
  46. +39 −15 lib/channel.js
  47. +58 −1 lib/colour.js
  48. +29 −9 lib/composite.js
  49. +98 −55 lib/constructor.js
  50. +150 −25 lib/input.js
  51. +24 −0 lib/is.js
  52. +66 −30 lib/libvips.js
  53. +336 −33 lib/operation.js
  54. +449 −122 lib/output.js
  55. +5 −3 lib/platform.js
  56. +61 −15 lib/resize.js
  57. +32 −0 lib/sharp.js
  58. +77 −17 lib/utility.js
  59. +56 −28 package.json
  60. +272 −97 src/common.cc
  61. +67 −11 src/common.h
  62. +0 −26 src/libvips/cplusplus/VConnection.cpp
  63. +88 −32 src/libvips/cplusplus/VImage.cpp
  64. +0 −13 src/libvips/cplusplus/VInterpolate.cpp
  65. +213 −1 src/libvips/cplusplus/vips-operators.cpp
  66. +32 −0 src/metadata.cc
  67. +5 −0 src/metadata.h
  68. +141 −7 src/operations.cc
  69. +32 −3 src/operations.h
  70. +492 −311 src/pipeline.cc
  71. +83 −28 src/pipeline.h
  72. +2 −0 src/sharp.cc
  73. +17 −1 src/utilities.cc
  74. +1 −0 src/utilities.h
  75. +10 −8 test/bench/package.json
  76. +85 −16 test/bench/perf.js
  77. +1 −1 test/bench/random.js
  78. BIN test/fixtures/concert.jpg
  79. BIN test/fixtures/expected/affine-background-all-offsets-expected.jpg
  80. BIN test/fixtures/expected/affine-background-expected.jpg
  81. BIN test/fixtures/expected/affine-background-output-offsets-expected.jpg
  82. BIN test/fixtures/expected/affine-bicubic-2x-upscale-expected.jpg
  83. BIN test/fixtures/expected/affine-bilinear-2x-upscale-expected.jpg
  84. BIN test/fixtures/expected/affine-extract-expected.jpg
  85. BIN test/fixtures/expected/affine-extract-rotate-expected.jpg
  86. BIN test/fixtures/expected/affine-lbb-2x-upscale-expected.jpg
  87. BIN test/fixtures/expected/affine-nearest-2x-upscale-expected.jpg
  88. BIN test/fixtures/expected/affine-nohalo-2x-upscale-expected.jpg
  89. BIN test/fixtures/expected/affine-resize-expected.jpg
  90. BIN test/fixtures/expected/affine-rotate-expected.jpg
  91. BIN test/fixtures/expected/affine-vsqbs-2x-upscale-expected.jpg
  92. BIN test/fixtures/expected/clahe-100-100-0.jpg
  93. BIN test/fixtures/expected/clahe-100-50-3.jpg
  94. BIN test/fixtures/expected/clahe-11-25-14.jpg
  95. BIN test/fixtures/expected/clahe-5-5-0.jpg
  96. BIN test/fixtures/expected/clahe-5-5-5.jpg
  97. BIN test/fixtures/expected/clahe-50-50-0.jpg
  98. BIN test/fixtures/expected/clahe-50-50-14.jpg
  99. BIN test/fixtures/expected/colourspace-gradients-gamma-resize.png
  100. BIN test/fixtures/expected/composite-cutout.png
  101. BIN test/fixtures/expected/composite-multiple.png
  102. BIN test/fixtures/expected/composite.blend.dest-over.png
  103. BIN test/fixtures/expected/composite.blend.over.png
  104. BIN test/fixtures/expected/composite.blend.saturate.png
  105. BIN test/fixtures/expected/composite.blend.xor.png
  106. BIN test/fixtures/expected/embed-animated-height.webp
  107. BIN test/fixtures/expected/embed-animated-width.webp
  108. BIN test/fixtures/expected/extend-equal-single.webp
  109. BIN test/fixtures/expected/extract-alpha-16bit.jpg
  110. BIN test/fixtures/expected/extract-alpha-16bit.png
  111. BIN test/fixtures/expected/extract-lch.jpg
  112. BIN test/fixtures/expected/fast-shrink-on-load-false.png
  113. BIN test/fixtures/expected/fast-shrink-on-load-true.png
  114. BIN test/fixtures/expected/fast-shrink-on-load.png
  115. BIN test/fixtures/expected/gravity-center-height.webp
  116. BIN test/fixtures/expected/gravity-center-width.webp
  117. BIN test/fixtures/expected/hilutite.jpg
  118. BIN test/fixtures/expected/icc-cmyk.jpg
  119. BIN test/fixtures/expected/median_1.jpg
  120. BIN test/fixtures/expected/median_3.jpg
  121. BIN test/fixtures/expected/median_5.jpg
  122. BIN test/fixtures/expected/median_color.jpg
  123. BIN test/fixtures/expected/modulate-all.jpg
  124. BIN test/fixtures/expected/modulate-brightness-0-5.jpg
  125. BIN test/fixtures/expected/modulate-brightness-2.jpg
  126. BIN test/fixtures/expected/modulate-hue-120.jpg
  127. BIN test/fixtures/expected/modulate-hue-angle-120.png
  128. BIN test/fixtures/expected/modulate-hue-angle-150.png
  129. BIN test/fixtures/expected/modulate-hue-angle-180.png
  130. BIN test/fixtures/expected/modulate-hue-angle-210.png
  131. BIN test/fixtures/expected/modulate-hue-angle-240.png
  132. BIN test/fixtures/expected/modulate-hue-angle-270.png
  133. BIN test/fixtures/expected/modulate-hue-angle-30.png
  134. BIN test/fixtures/expected/modulate-hue-angle-300.png
  135. BIN test/fixtures/expected/modulate-hue-angle-330.png
  136. BIN test/fixtures/expected/modulate-hue-angle-360.png
  137. BIN test/fixtures/expected/modulate-hue-angle-60.png
  138. BIN test/fixtures/expected/modulate-hue-angle-90.png
  139. BIN test/fixtures/expected/modulate-linear.jpg
  140. BIN test/fixtures/expected/modulate-saturation-0.5.jpg
  141. BIN test/fixtures/expected/modulate-saturation-2.jpg
  142. BIN test/fixtures/expected/negate-preserve-alpha-grey.png
  143. BIN test/fixtures/expected/negate-preserve-alpha-trans.png
  144. BIN test/fixtures/expected/negate-preserve-alpha-trans.webp
  145. BIN test/fixtures/expected/negate-preserve-alpha.png
  146. BIN test/fixtures/expected/negate-preserve-alpha.webp
  147. BIN test/fixtures/expected/overlay-gravity-center.jpg
  148. BIN test/fixtures/expected/overlay-gravity-centre.jpg
  149. BIN test/fixtures/expected/overlay-gravity-east.jpg
  150. BIN test/fixtures/expected/overlay-gravity-north.jpg
  151. BIN test/fixtures/expected/overlay-gravity-northeast.jpg
  152. BIN test/fixtures/expected/overlay-gravity-northwest.jpg
  153. BIN test/fixtures/expected/overlay-gravity-south.jpg
  154. BIN test/fixtures/expected/overlay-gravity-southeast.jpg
  155. BIN test/fixtures/expected/overlay-gravity-southwest.jpg
  156. BIN test/fixtures/expected/overlay-gravity-west.jpg
  157. BIN test/fixtures/expected/overlay-negative-offset-with-gravity.jpg
  158. BIN test/fixtures/expected/resize-crop-extract.jpg
  159. BIN test/fixtures/expected/rotate-mirror-extract.jpg
  160. BIN test/fixtures/expected/svg72.png
  161. BIN test/fixtures/expected/tint-cmyk.jpg
  162. BIN test/fixtures/expected/tint-sepia.jpg
  163. BIN test/fixtures/gradients-rgb8.png
  164. +9 −7 test/fixtures/index.js
  165. BIN test/fixtures/p3.png
  166. BIN test/fixtures/relax.jp2
  167. BIN test/fixtures/sdr_cosmos12920_cicp1-13-6_yuv444_full_qp10.avif
  168. BIN test/fixtures/swiss.png
  169. BIN test/fixtures/with-alpha.png
  170. +1 −1 test/leak/leak.sh
  171. +223 −7 test/leak/sharp.supp
  172. +184 −0 test/unit/affine.js
  173. +11 −0 test/unit/agent.js
  174. +23 −0 test/unit/alpha.js
  175. +104 −0 test/unit/avif.js
  176. +3 −2 test/unit/beforeEach.js
  177. +139 −0 test/unit/clahe.js
  178. +51 −13 test/unit/colourspace.js
  179. +79 −19 test/unit/composite.js
  180. +72 −19 test/unit/extend.js
  181. +85 −17 test/unit/extract.js
  182. +6 −5 test/unit/extractChannel.js
  183. +102 −0 test/unit/failOn.js
  184. +0 −88 test/unit/failOnError.js
  185. +63 −45 test/unit/gif.js
  186. +78 −64 test/unit/heif.js
  187. +218 −59 test/unit/io.js
  188. +92 −0 test/unit/jp2.js
  189. +20 −0 test/unit/jpeg.js
  190. +47 −0 test/unit/libvips.js
  191. +30 −52 test/unit/median.js
  192. +148 −21 test/unit/metadata.js
  193. +126 −85 test/unit/modulate.js
  194. +100 −0 test/unit/negate.js
  195. +259 −0 test/unit/noise.js
  196. +2 −0 test/unit/normalize.js
  197. +38 −16 test/unit/platform.js
  198. +32 −4 test/unit/png.js
  199. +97 −2 test/unit/raw.js
  200. +22 −10 test/unit/recomb.js
  201. +36 −0 test/unit/resize-contain.js
  202. +48 −0 test/unit/resize-cover.js
  203. +177 −22 test/unit/resize.js
  204. +28 −0 test/unit/rotate.js
  205. +46 −0 test/unit/sharpen.js
  206. +27 −27 test/unit/stats.js
  207. +57 −0 test/unit/svg.js
  208. +79 −23 test/unit/tiff.js
  209. +59 −5 test/unit/tile.js
  210. +26 −0 test/unit/timeout.js
  211. +11 −14 test/unit/toBuffer.js
  212. +27 −0 test/unit/toFormat.js
  213. +8 −0 test/unit/trim.js
  214. +18 −8 test/unit/util.js
  215. +52 −24 test/unit/webp.js
79 changes: 79 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
version: 2.1

workflows:
build:
jobs:
- linux-arm64-glibc-node-12:
filters:
tags:
only: /^v.*/
- linux-arm64-musl-node-12:
filters:
tags:
only: /^v.*/
- linux-arm64-glibc-node-16:
filters:
tags:
only: /^v.*/
- linux-arm64-musl-node-16:
filters:
tags:
only: /^v.*/

jobs:
linux-arm64-glibc-node-12:
resource_class: arm.medium
machine:
image: ubuntu-2004:202101-01
steps:
- checkout
- run: |
sudo docker run -dit --name sharp --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 python3 curl"
sudo docker exec sharp sh -c "curl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add -"
sudo docker exec sharp sh -c "echo 'deb https://deb.nodesource.com/node_12.x sid main' >/etc/apt/sources.list.d/nodesource.list"
sudo docker exec sharp sh -c "apt-get update && apt-get install -y nodejs"
- run: sudo docker exec sharp sh -c "npm install --build-from-source --unsafe-perm"
- run: sudo docker exec sharp sh -c "npm test"
- run: "[[ -n $CIRCLE_TAG ]] && sudo docker exec --env prebuild_upload sharp sh -c \"npx prebuild --runtime napi --target 5 --upload=$prebuild_upload\" || true"
linux-arm64-glibc-node-16:
resource_class: arm.medium
machine:
image: ubuntu-2004:202101-01
steps:
- checkout
- run: |
sudo docker run -dit --name sharp --workdir /mnt/sharp arm64v8/debian:bullseye
sudo docker exec sharp sh -c "apt-get update && apt-get install -y build-essential git python3 curl"
sudo docker exec sharp sh -c "curl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add -"
sudo docker exec sharp sh -c "echo 'deb https://deb.nodesource.com/node_16.x sid main' >/etc/apt/sources.list.d/nodesource.list"
sudo docker exec sharp sh -c "apt-get update && apt-get install -y nodejs"
sudo docker exec sharp sh -c "mkdir -p /mnt/sharp"
sudo docker cp . sharp:/mnt/sharp/.
- run: sudo docker exec sharp sh -c "npm install --build-from-source"
- run: sudo docker exec sharp sh -c "npm test"
linux-arm64-musl-node-12:
resource_class: arm.medium
machine:
image: ubuntu-2004:202101-01
steps:
- checkout
- run: |
sudo docker run -dit --name sharp --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp node:12-alpine3.11
sudo docker exec sharp sh -c "apk add build-base git python3 --update-cache"
- run: sudo docker exec sharp sh -c "npm install --build-from-source --unsafe-perm"
- run: sudo docker exec sharp sh -c "npm test"
- run: "[[ -n $CIRCLE_TAG ]] && sudo docker exec --env prebuild_upload sharp sh -c \"npx prebuild --runtime napi --target 5 --upload=$prebuild_upload\" || true"
linux-arm64-musl-node-16:
resource_class: arm.medium
machine:
image: ubuntu-2004:202101-01
steps:
- checkout
- run: |
sudo docker run -dit --name sharp --workdir /mnt/sharp node:16-alpine3.11
sudo docker exec sharp sh -c "apk add build-base git python3 --update-cache"
sudo docker exec sharp sh -c "mkdir -p /mnt/sharp"
sudo docker cp . sharp:/mnt/sharp/.
- run: sudo docker exec sharp sh -c "npm install --build-from-source"
- run: sudo docker exec sharp sh -c "npm test"
9 changes: 5 additions & 4 deletions .cirrus.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
freebsd_instance:
image_family: freebsd-13-0-snap
image_family: freebsd-14-0-snap

task:
name: FreeBSD 13.0
name: FreeBSD
env:
IGNORE_OSVERSION: yes
skip_notifications: true
prerequisites_script:
- pkg update -f
- pkg upgrade -y
- pkg install -y pkgconf vips node npm
- pkg install -y devel/pkgconf graphics/vips www/node16 www/npm
install_script:
- npm install --unsafe-perm
- npm install --build-from-source --unsafe-perm
test_script:
- npm test
14 changes: 7 additions & 7 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -16,29 +16,29 @@ If a [similar request](https://github.com/lovell/sharp/labels/enhancement) exist
it's probably fastest to add a comment to it about your requirement.

Implementation is usually straightforward if libvips
[already supports](https://libvips.github.io/libvips/API/current/func-list.html)
[already supports](https://www.libvips.org/API/current/func-list.html)
the feature you need.

## Submit a Pull Request to fix a bug

Thank you! To prevent the problem occurring again, please add unit tests that would have failed.

Please select the `master` branch as the destination for your Pull Request so your fix can be included in the next minor release.
Please select the `main` branch as the destination for your Pull Request so your fix can be included in the next minor release.

Please squash your changes into a single commit using a command like `git rebase -i upstream/master`.
Please squash your changes into a single commit using a command like `git rebase -i upstream/main`.

To test C++ changes, you can compile the module using `npm install` and then run the tests using `npm test`.
To test C++ changes, you can compile the module using `npm install --build-from-source` and then run the tests using `npm test`.

## Submit a Pull Request with a new feature

Please add JavaScript [unit tests](https://github.com/lovell/sharp/tree/master/test/unit) to cover your new feature.
Please add JavaScript [unit tests](https://github.com/lovell/sharp/tree/main/test/unit) to cover your new feature.
A test coverage report for the JavaScript code is generated in the `coverage/lcov-report` directory.

Where possible, the functional tests use gradient-based perceptual hashes
based on [dHash](http://www.hackerfactor.com/blog/index.php?/archives/529-Kind-of-Like-That.html)
to compare expected vs actual images.

You deserve to add your details to the [list of contributors](https://github.com/lovell/sharp/blob/master/package.json#L5).
You deserve to add your details to the [list of contributors](https://github.com/lovell/sharp/blob/main/package.json#L5).

Any change that modifies the existing public API should be added to the relevant work-in-progress branch for inclusion in the next major release.

@@ -93,5 +93,5 @@ Please feel free to ask any questions via a
[new issue](https://github.com/lovell/sharp/issues/new).

If you're unable to post details publicly, please
[e-mail](https://github.com/lovell/sharp/blob/master/package.json#L5)
[e-mail](https://github.com/lovell/sharp/blob/main/package.json#L5)
for private, paid consulting.
5 changes: 5 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Documentation
url: https://sharp.pixelplumbing.com/
about: Installation instructions, complete API documentation with examples, changelog
22 changes: 17 additions & 5 deletions .github/ISSUE_TEMPLATE/feature_request.md
Original file line number Diff line number Diff line change
@@ -5,12 +5,24 @@ labels: enhancement

---

What are you trying to achieve?
## Feature request

Have you searched for similar feature requests?
### What are you trying to achieve?

What would you expect the API to look like?
<!-- Please provide context here. -->

What alternatives have you considered?
### When you searched for similar feature requests, what did you find that might be related?

Is there a sample image that helps explain?
<!-- Please demonstrate your research here. -->

### What would you expect the API to look like?

<!-- Please provide your suggestions here. -->

### What alternatives have you considered?

<!-- Please provide your ideas here. -->

### Please provide sample image(s) that help explain this feature

<!-- Please provide links to one or more images here. -->
37 changes: 31 additions & 6 deletions .github/ISSUE_TEMPLATE/installation.md
Original file line number Diff line number Diff line change
@@ -5,16 +5,41 @@ labels: installation

---

Did you see the [documentation relating to installation](https://sharp.pixelplumbing.com/install)?
<!-- Please try to answer as many of these questions as possible. -->

Have you ensured the architecture and platform of Node.js used for `npm install` is the same as the architecture and platform of Node.js used at runtime?
## Possible install-time or require-time problem

Are you using the latest version? Is the version currently in use as reported by `npm ls sharp` the same as the latest version as reported by `npm view sharp dist-tags.latest`?
<!-- Please place an [x] in the box to confirm. -->

If you are installing as a `root` or `sudo` user, have you tried with the `npm install --unsafe-perm` flag?
- [ ] I have read the [documentation relating to installation](https://sharp.pixelplumbing.com/install).
- [ ] I have ensured that the architecture and platform of Node.js used for `npm install` is the same as the architecture and platform of Node.js used at runtime.

### Are you using the latest version of sharp?

<!-- Please place an [x] in the box to confirm. -->

- [ ] I am using the latest version of `sharp` as reported by `npm view sharp dist-tags.latest`.

If you cannot confirm this, please upgrade to the latest version and try again before opening an issue.

If you are using another package which depends on a version of `sharp` that is not the latest, please open an issue against that package instead.

### Is this a problem with filesystem permissions?

If you are using npm v6 or earlier and installing as a `root` or `sudo` user, have you tried with the `npm install --unsafe-perm` flag?

If you are using npm v7 or later, does the user running `npm install` own the directory it is run in?

If you are using the `ignore-scripts` feature of `npm`, have you tried with the `npm install --ignore-scripts=false` flag?

What is the complete output of running `npm install --verbose sharp`? Have you checked this output for useful error messages?
### What is the complete output of running `npm install --verbose --foreground-scripts sharp` in an empty directory?

<details>

<!-- Please provide output of `npm install --verbose --foreground-scripts sharp` in an empty directory here. -->

</details>

### What is the output of running `npx envinfo --binaries --system --npmPackages=sharp --npmGlobalPackages=sharp`?

What is the output of running `npx envinfo --binaries --system`?
<!-- Please provide output of `npx envinfo --binaries --system --npmPackages=sharp --npmGlobalPackages=sharp` here. -->
41 changes: 35 additions & 6 deletions .github/ISSUE_TEMPLATE/possible-bug.md
Original file line number Diff line number Diff line change
@@ -7,14 +7,43 @@ labels: triage

<!-- If this issue relates to installation, please use https://github.com/lovell/sharp/issues/new?labels=installation&template=installation.md instead. -->

Are you using the latest version? Is the version currently in use as reported by `npm ls sharp` the same as the latest version as reported by `npm view sharp dist-tags.latest`?
## Possible bug

What are the steps to reproduce?
### Is this a possible bug in a feature of sharp, unrelated to installation?

What is the expected behaviour?
<!-- Please place an [x] in the box to confirm. -->

Are you able to provide a minimal, standalone code sample, without other dependencies, that demonstrates this problem?
- [ ] Running `npm install sharp` completes without error.
- [ ] Running `node -e "require('sharp')"` completes without error.

Are you able to provide a sample image that helps explain the problem?
If you cannot confirm both of these, please open an [installation issue](https://github.com/lovell/sharp/issues/new?labels=installation&template=installation.md) instead.

What is the output of running `npx envinfo --binaries --system`?
### Are you using the latest version of sharp?

<!-- Please place an [x] in the box to confirm. -->

- [ ] I am using the latest version of `sharp` as reported by `npm view sharp dist-tags.latest`.

If you cannot confirm this, please upgrade to the latest version and try again before opening an issue.

If you are using another package which depends on a version of `sharp` that is not the latest, please open an issue against that package instead.

### What is the output of running `npx envinfo --binaries --system --npmPackages=sharp --npmGlobalPackages=sharp`?

<!-- Please provide output of the above command here. -->

### What are the steps to reproduce?

<!-- Please enter steps to reproduce here. -->

### What is the expected behaviour?

<!-- Please enter the expected behaviour here. -->

### Please provide a minimal, standalone code sample, without other dependencies, that demonstrates this problem

<!-- Please provide either formatted code or a link to a repo/gist that allows someone else to reproduce here. -->

### Please provide sample image(s) that help explain this problem

<!-- Please provide links to one or more images here. -->
18 changes: 14 additions & 4 deletions .github/ISSUE_TEMPLATE/question.md
Original file line number Diff line number Diff line change
@@ -7,10 +7,20 @@ labels: question

<!-- If this issue relates to installation, please use https://github.com/lovell/sharp/issues/new?labels=installation&template=installation.md instead. -->

What are you trying to achieve?
## Question about an existing feature

Have you searched for similar questions?
### What are you trying to achieve?

Are you able to provide a minimal, standalone code sample that demonstrates this question?
<!-- Please provide context here. -->

Are you able to provide a sample image that helps explain the question?
### When you searched for similar issues, what did you find that might be related?

<!-- Please demonstrate your research here. -->

### Please provide a minimal, standalone code sample, without other dependencies, that demonstrates this question

<!-- Please provide either formatted code or a link to a repo/gist that helps someone else understand here. -->

### Please provide sample image(s) that help explain this question

<!-- Please provide links to one or more images here. -->
36 changes: 36 additions & 0 deletions .github/workflows/ci-darwin-arm64v8.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: CI (MacStadium)
on:
- push
- pull_request
jobs:
CI:
runs-on: macos-m1
strategy:
fail-fast: false
matrix:
include:
- nodejs_version: 12
nodejs_architecture: x64
- nodejs_version: 16
nodejs_architecture: arm64
prebuild: true
defaults:
run:
shell: /usr/bin/arch -arch arm64e /bin/bash -l {0}
steps:
- name: Dependencies
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.nodejs_version }}
architecture: ${{ matrix.nodejs_architecture }}
- name: Checkout
uses: actions/checkout@v2
- name: Install
run: npm install --build-from-source --unsafe-perm
- name: Test
run: npm test
- name: Prebuild
if: matrix.prebuild && startsWith(github.ref, 'refs/tags/')
env:
prebuild_upload: ${{ secrets.GITHUB_TOKEN }}
run: npx prebuild --runtime napi --target 5
98 changes: 98 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
name: CI (GitHub)
on:
- push
- pull_request
jobs:
CI:
runs-on: ${{ matrix.os }}
container: ${{ matrix.container }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-20.04
container: centos:7
nodejs_version: 12
coverage: true
prebuild: true
- os: ubuntu-20.04
container: centos:7
nodejs_version: 14
- os: ubuntu-20.04
container: centos:7
nodejs_version: 16
- os: ubuntu-20.04
container: node:12-alpine3.11
prebuild: true
- os: ubuntu-20.04
container: node:14-alpine3.11
- os: ubuntu-20.04
container: node:14-alpine3.13
- os: ubuntu-20.04
container: node:16-alpine3.11
- os: macos-10.15
nodejs_version: 12
prebuild: true
nodejs_arch: x64
- os: macos-10.15
nodejs_version: 14
nodejs_arch: x64
- os: macos-10.15
nodejs_version: 16
nodejs_arch: x64
- os: windows-2019
nodejs_version: 12
nodejs_arch: x86
prebuild: true
- os: windows-2019
nodejs_version: 14
nodejs_arch: x86
- os: windows-2019
nodejs_version: 16
nodejs_arch: x86
- os: windows-2019
nodejs_version: 12
nodejs_arch: x64
prebuild: true
- os: windows-2019
nodejs_version: 14
nodejs_arch: x64
- os: windows-2019
nodejs_version: 16
nodejs_arch: x64
steps:
- name: Dependencies (Linux glibc)
if: contains(matrix.container, 'centos')
run: |
curl -sL https://rpm.nodesource.com/setup_${{ matrix.nodejs_version }}.x | bash -
yum install -y centos-release-scl
yum install -y devtoolset-10-gcc-c++ make git python3 nodejs
echo "/opt/rh/devtoolset-10/root/usr/bin" >> $GITHUB_PATH
- name: Dependencies (Linux musl)
if: contains(matrix.container, 'alpine')
run: apk add build-base git python3 --update-cache
- name: Dependencies (macOS, Windows)
if: contains(matrix.os, 'macos') || contains(matrix.os, 'windows')
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.nodejs_version }}
architecture: ${{ matrix.nodejs_arch }}
- name: Checkout
uses: actions/checkout@v2
- name: Fix working directory ownership
if: matrix.container
run: chown root.root .
- name: Install
run: npm install --build-from-source --unsafe-perm
- name: Test
run: npm test
- name: Coverage
if: matrix.coverage
uses: coverallsapp/github-action@v1.1.2
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Prebuild
if: matrix.prebuild && startsWith(github.ref, 'refs/tags/')
env:
prebuild_upload: ${{ secrets.GITHUB_TOKEN }}
run: npx prebuild --runtime napi --target 5
2 changes: 1 addition & 1 deletion .prebuildrc
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"include-regex": "(sharp\\.node|libvips-cpp\\.dll)",
"include-regex": "(sharp-.+\\.node|libvips-cpp\\.dll)",
"strip": true
}
139 changes: 0 additions & 139 deletions .travis.yml

This file was deleted.

37 changes: 19 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# sharp

<img src="https://cdn.jsdelivr.net/gh/lovell/sharp@master/docs/image/sharp-logo.svg" width="160" height="160" alt="sharp logo" align="right">
<img src="https://cdn.jsdelivr.net/gh/lovell/sharp@main/docs/image/sharp-logo.svg" width="160" height="160" alt="sharp logo" align="right">

The typical use case for this high speed Node.js module
is to convert large images in common formats to
smaller, web-friendly JPEG, PNG and WebP images of varying dimensions.
smaller, web-friendly JPEG, PNG, WebP, GIF and AVIF images of varying dimensions.

Resizing an image is typically 4x-5x faster than using the
quickest ImageMagick and GraphicsMagick settings
@@ -16,9 +16,17 @@ 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 macOS, Windows and Linux systems running Node.js v10.16.0+
Most modern macOS, Windows and Linux systems running Node.js >= 12.13.0
do not require any additional install or runtime dependencies.

## Documentation

Visit [sharp.pixelplumbing.com](https://sharp.pixelplumbing.com/) for complete
[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).

## Examples

```sh
@@ -43,6 +51,7 @@ sharp(inputBuffer)
sharp('input.jpg')
.rotate()
.resize(200)
.jpeg({ mozjpeg: true })
.toBuffer()
.then( data => { ... })
.catch( err => { ... });
@@ -84,25 +93,17 @@ readableStream
.pipe(writableStream);
```

[![Test Coverage](https://coveralls.io/repos/lovell/sharp/badge.svg?branch=master)](https://coveralls.io/r/lovell/sharp?branch=master)
[![N-API v3](https://img.shields.io/badge/N--API-v3-green.svg)](https://nodejs.org/dist/latest/docs/api/n-api.html#n_api_n_api_version_matrix)

### Documentation
## Contributing

Visit [sharp.pixelplumbing.com](https://sharp.pixelplumbing.com/) for complete
[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

A [guide for contributors](https://github.com/lovell/sharp/blob/master/.github/CONTRIBUTING.md)
A [guide for contributors](https://github.com/lovell/sharp/blob/main/.github/CONTRIBUTING.md)
covers reporting bugs, requesting features and submitting code changes.

### Licensing
[![Test Coverage](https://coveralls.io/repos/lovell/sharp/badge.svg?branch=main)](https://coveralls.io/r/lovell/sharp?branch=main)
[![Node-API v5](https://img.shields.io/badge/Node--API-v5-green.svg)](https://nodejs.org/dist/latest/docs/api/n-api.html#n_api_n_api_version_matrix)

## Licensing

Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.
Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Lovell Fuller and contributors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
26 changes: 0 additions & 26 deletions appveyor.yml

This file was deleted.

29 changes: 15 additions & 14 deletions binding.gyp
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
'variables': {
'vips_version': '<!(node -p "require(\'./lib/libvips\').minimumLibvipsVersion")',
'sharp_vendor_dir': '<(module_root_dir)/vendor/<(vips_version)'
'platform_and_arch': '<!(node -p "require(\'./lib/platform\')()")',
'sharp_vendor_dir': './vendor/<(vips_version)/<(platform_and_arch)'
},
'targets': [{
'target_name': 'libvips-cpp',
@@ -37,6 +38,7 @@
'msvs_settings': {
'VCCLCompilerTool': {
'ExceptionHandling': 1,
'Optimization': 1,
'WholeProgramOptimization': 'true'
},
'VCLibrarianTool': {
@@ -65,9 +67,9 @@
}]
]
}, {
'target_name': 'sharp',
'target_name': 'sharp-<(platform_and_arch)',
'defines': [
'NAPI_VERSION=3'
'NAPI_VERSION=5'
],
'dependencies': [
'<!(node -p "require(\'node-addon-api\').gyp")',
@@ -138,32 +140,30 @@
}],
['OS == "mac"', {
'link_settings': {
'library_dirs': ['<(sharp_vendor_dir)/lib'],
'library_dirs': ['../<(sharp_vendor_dir)/lib'],
'libraries': [
'libvips-cpp.42.dylib',
'libvips.42.dylib'
'libvips-cpp.42.dylib'
]
},
'xcode_settings': {
'OTHER_LDFLAGS': [
# Ensure runtime linking is relative to sharp.node
'-Wl,-rpath,\'@loader_path/../../vendor/<(vips_version)/lib\''
'-Wl,-rpath,\'@loader_path/../../<(sharp_vendor_dir)/lib\''
]
}
}],
['OS == "linux"', {
'defines': [
'_GLIBCXX_USE_CXX11_ABI=0'
'_GLIBCXX_USE_CXX11_ABI=1'
],
'link_settings': {
'library_dirs': ['<(sharp_vendor_dir)/lib'],
'library_dirs': ['../<(sharp_vendor_dir)/lib'],
'libraries': [
'-l:libvips-cpp.so.42',
'-l:libvips.so.42'
'-l:libvips-cpp.so.42'
],
'ldflags': [
# Ensure runtime linking is relative to sharp.node
'-Wl,-s -Wl,--disable-new-dtags -Wl,-rpath=\'$$ORIGIN/../../vendor/<(vips_version)/lib\''
'-Wl,-s -Wl,--disable-new-dtags -Wl,-rpath=\'$$ORIGIN/../../<(sharp_vendor_dir)/lib\''
]
}
}]
@@ -174,7 +174,7 @@
'-std=c++0x',
'-fexceptions',
'-Wall',
'-O3'
'-Os'
],
'xcode_settings': {
'CLANG_CXX_LANGUAGE_STANDARD': 'c++11',
@@ -184,7 +184,7 @@
'OTHER_CPLUSPLUSFLAGS': [
'-fexceptions',
'-Wall',
'-O3'
'-Oz'
]
},
'configurations': {
@@ -204,6 +204,7 @@
'msvs_settings': {
'VCCLCompilerTool': {
'ExceptionHandling': 1,
'Optimization': 1,
'WholeProgramOptimization': 'true'
},
'VCLibrarianTool': {
18 changes: 11 additions & 7 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# sharp

<img src="https://cdn.jsdelivr.net/gh/lovell/sharp@master/docs/image/sharp-logo.svg" width="160" height="160" alt="sharp logo" align="right">
<img src="https://cdn.jsdelivr.net/gh/lovell/sharp@main/docs/image/sharp-logo.svg" width="160" height="160" alt="sharp logo" align="right">

The typical use case for this high speed Node.js module
is to convert large images in common formats to
smaller, web-friendly JPEG, PNG and WebP images of varying dimensions.
smaller, web-friendly JPEG, PNG, WebP, GIF and AVIF images of varying dimensions.

Resizing an image is typically 4x-5x faster than using the
quickest ImageMagick and GraphicsMagick settings
@@ -16,14 +16,14 @@ 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 macOS, Windows and Linux systems running Node.js v10.16.0+
Most modern macOS, Windows and Linux systems running Node.js >= 12.13.0
do not require any additional install or runtime dependencies.

### Formats

This module supports reading JPEG, PNG, WebP, TIFF, GIF and SVG images.
This module supports reading JPEG, PNG, WebP, GIF, AVIF, TIFF and SVG images.

Output images can be in JPEG, PNG, WebP and TIFF formats as well as uncompressed raw pixel data.
Output images can be in JPEG, PNG, WebP, GIF, AVIF and TIFF formats as well as uncompressed raw pixel data.

Streams, Buffer objects and the filesystem can be used for input and output.

@@ -50,6 +50,10 @@ no child processes are spawned and Promises/async/await are supported.

### Optimal

The features of `mozjpeg` and `pngquant` can be used
to optimise the file size of JPEG and PNG images respectively,
without having to invoke separate `imagemin` processes.

Huffman tables are optimised when generating JPEG output images
without having to use separate command line tools like
[jpegoptim](https://github.com/tjko/jpegoptim) and
@@ -61,12 +65,12 @@ as [pngcrush](https://pmt.sourceforge.io/pngcrush/).

### Contributing

A [guide for contributors](https://github.com/lovell/sharp/blob/master/.github/CONTRIBUTING.md)
A [guide for contributors](https://github.com/lovell/sharp/blob/main/.github/CONTRIBUTING.md)
covers reporting bugs, requesting features and submitting code changes.

### Licensing

Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.
Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Lovell Fuller and contributors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
79 changes: 52 additions & 27 deletions docs/api-channel.md
Original file line number Diff line number Diff line change
@@ -4,6 +4,8 @@

Remove alpha channel, if any. This is a no-op if the image does not have an alpha channel.

See also [flatten][1].

### Examples

```javascript
@@ -18,45 +20,65 @@ Returns **Sharp**

## ensureAlpha

Ensure alpha channel, if missing. The added alpha channel will be fully opaque. This is a no-op if the image already has an alpha channel.
Ensure the output image has an alpha transparency channel.
If missing, the added alpha channel will have the specified
transparency level, defaulting to fully-opaque (1).
This is a no-op if the image already has an alpha channel.

### Parameters

* `alpha` **[number][2]** alpha transparency level (0=fully-transparent, 1=fully-opaque) (optional, default `1`)

### Examples

```javascript
sharp('rgb.jpg')
// rgba.png will be a 4 channel image with a fully-opaque alpha channel
await sharp('rgb.jpg')
.ensureAlpha()
.toFile('rgba.png', function(err, info) {
// rgba.png is a 4 channel image with a fully opaque alpha channel
});
.toFile('rgba.png')
```

```javascript
// rgba is a 4 channel image with a fully-transparent alpha channel
const rgba = await sharp(rgb)
.ensureAlpha(0)
.toBuffer();
```

* Throws **[Error][3]** Invalid alpha transparency level

Returns **Sharp**

**Meta**

- **since**: 0.21.2
* **since**: 0.21.2

## extractChannel

Extract a single channel from a multi-channel image.

### Parameters

- `channel` **([number][1] \| [string][2])** zero-indexed channel/band number to extract, or `red`, `green`, `blue` or `alpha`.
* `channel` **([number][2] | [string][4])** zero-indexed channel/band number to extract, or `red`, `green`, `blue` or `alpha`.

### Examples

```javascript
sharp(input)
// green.jpg is a greyscale image containing the green channel of the input
await sharp(input)
.extractChannel('green')
.toColourspace('b-w')
.toFile('green.jpg', function(err, info) {
// info.channels === 1
// green.jpg is a greyscale image containing the green channel of the input
});
.toFile('green.jpg');
```

```javascript
// red1 is the red value of the first pixel, red2 the second pixel etc.
const [red1, red2, ...] = await sharp(input)
.extractChannel(0)
.raw()
.toBuffer();
```

- Throws **[Error][3]** Invalid channel
* Throws **[Error][3]** Invalid channel

Returns **Sharp**

@@ -67,19 +89,20 @@ The meaning of the added channels depends on the output colourspace, set with `t
By default the output image will be web-friendly sRGB, with additional channels interpreted as alpha channels.
Channel ordering follows vips convention:

- sRGB: 0: Red, 1: Green, 2: Blue, 3: Alpha.
- CMYK: 0: Magenta, 1: Cyan, 2: Yellow, 3: Black, 4: Alpha.
* sRGB: 0: Red, 1: Green, 2: Blue, 3: Alpha.
* CMYK: 0: Magenta, 1: Cyan, 2: Yellow, 3: Black, 4: Alpha.

Buffers may be any of the image formats supported by sharp: JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data.
Buffers may be any of the image formats supported by sharp.
For raw pixel input, the `options` object should contain a `raw` attribute, which follows the format of the attribute of the same name in the `sharp()` constructor.

### Parameters

- `images` **([Array][4]&lt;([string][2] \| [Buffer][5])> | [string][2] \| [Buffer][5])** one or more images (file paths, Buffers).
- `options` **[Object][6]** image options, see `sharp()` constructor.
* `images` **([Array][5]<([string][4] | [Buffer][6])> | [string][4] | [Buffer][6])** one or more images (file paths, Buffers).
* `options` **[Object][7]** image options, see `sharp()` constructor.

<!---->

- Throws **[Error][3]** Invalid parameters
* Throws **[Error][3]** Invalid parameters

Returns **Sharp**

@@ -89,7 +112,7 @@ Perform a bitwise boolean operation on all input image channels (bands) to produ

### Parameters

- `boolOp` **[string][2]** one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively.
* `boolOp` **[string][4]** one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively.

### Examples

@@ -103,18 +126,20 @@ sharp('3-channel-rgb-input.png')
});
```

- Throws **[Error][3]** Invalid parameters
* Throws **[Error][3]** Invalid parameters

Returns **Sharp**

[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
[1]: /api-operation#flatten

[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number

[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error

[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array
[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String

[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array

[5]: https://nodejs.org/api/buffer.html
[6]: https://nodejs.org/api/buffer.html

[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
89 changes: 80 additions & 9 deletions docs/api-colour.md
Original file line number Diff line number Diff line change
@@ -7,10 +7,17 @@ An alpha channel may be present and will be unchanged by the operation.

### Parameters

- `rgb` **([string][1] \| [Object][2])** parsed by the [color][3] module to extract chroma values.
* `rgb` **([string][1] | [Object][2])** parsed by the [color][3] module to extract chroma values.

### Examples

- Throws **[Error][4]** Invalid parameter
```javascript
const output = await sharp(input)
.tint({ r: 255, g: 240, b: 16 })
.toBuffer();
```

* Throws **[Error][4]** Invalid parameter

Returns **Sharp**

@@ -25,7 +32,13 @@ An alpha channel may be present, and will be unchanged by the operation.

### Parameters

- `greyscale` **[Boolean][5]** (optional, default `true`)
* `greyscale` **[Boolean][5]** (optional, default `true`)

### Examples

```javascript
const output = await sharp(input).greyscale().toBuffer();
```

Returns **Sharp**

@@ -35,7 +48,52 @@ Alternative spelling of `greyscale`.

### Parameters

- `grayscale` **[Boolean][5]** (optional, default `true`)
* `grayscale` **[Boolean][5]** (optional, default `true`)

Returns **Sharp**

## pipelineColourspace

Set the pipeline colourspace.

The input image will be converted to the provided colourspace at the start of the pipeline.
All operations will use this colourspace before converting to the output colourspace, as defined by [toColourspace][6].

This feature is experimental and has not yet been fully-tested with all operations.

### Parameters

* `colourspace` **[string][1]?** pipeline colourspace e.g. `rgb16`, `scrgb`, `lab`, `grey16` [...][7]

### Examples

```javascript
// Run pipeline in 16 bits per channel RGB while converting final result to 8 bits per channel sRGB.
await sharp(input)
.pipelineColourspace('rgb16')
.toColourspace('srgb')
.toFile('16bpc-pipeline-to-8bpc-output.png')
```

* Throws **[Error][4]** Invalid parameters

Returns **Sharp**

**Meta**

* **since**: 0.29.0

## pipelineColorspace

Alternative spelling of `pipelineColourspace`.

### Parameters

* `colorspace` **[string][1]?** pipeline colorspace.

<!---->

* Throws **[Error][4]** Invalid parameters

Returns **Sharp**

@@ -46,10 +104,18 @@ By default output image will be web-friendly sRGB, with additional channels inte

### Parameters

- `colourspace` **[string][1]?** output colourspace e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...][6]
* `colourspace` **[string][1]?** output colourspace e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...][8]

### Examples

- Throws **[Error][4]** Invalid parameters
```javascript
// Output 16 bits per pixel RGB
await sharp(input)
.toColourspace('rgb16')
.toFile('16-bpp.png')
```

* Throws **[Error][4]** Invalid parameters

Returns **Sharp**

@@ -59,10 +125,11 @@ Alternative spelling of `toColourspace`.

### Parameters

- `colorspace` **[string][1]?** output colorspace.
* `colorspace` **[string][1]?** output colorspace.

<!---->

- Throws **[Error][4]** Invalid parameters
* Throws **[Error][4]** Invalid parameters

Returns **Sharp**

@@ -76,4 +143,8 @@ Returns **Sharp**

[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean

[6]: https://github.com/libvips/libvips/blob/master/libvips/iofuncs/enumtypes.c#L568
[6]: #tocolourspace

[7]: https://github.com/libvips/libvips/blob/41cff4e9d0838498487a00623462204eb10ee5b8/libvips/iofuncs/enumtypes.c#L774

[8]: https://github.com/libvips/libvips/blob/3c0bfdf74ce1dc37a6429bed47fa76f16e2cd70a/libvips/iofuncs/enumtypes.c#L777-L794
72 changes: 49 additions & 23 deletions docs/api-composite.md
Original file line number Diff line number Diff line change
@@ -14,32 +14,56 @@ The `blend` option can be one of `clear`, `source`, `over`, `in`, `out`, `atop`,
`hard-light`, `soft-light`, `difference`, `exclusion`.

More information about blend modes can be found at
[https://libvips.github.io/libvips/API/current/libvips-conversion.html#VipsBlendMode][1]
[https://www.libvips.org/API/current/libvips-conversion.html#VipsBlendMode][1]
and [https://www.cairographics.org/operators/][2]

### Parameters

- `images` **[Array][3]&lt;[Object][4]>** Ordered list of images to composite
- `images[].input` **([Buffer][5] \| [String][6])?** Buffer containing image data, String containing the path to an image file, or Create object (see below)
- `images[].input.create` **[Object][4]?** describes a blank overlay to be created.
- `images[].input.create.width` **[Number][7]?**
- `images[].input.create.height` **[Number][7]?**
- `images[].input.create.channels` **[Number][7]?** 3-4
- `images[].input.create.background` **([String][6] \| [Object][4])?** parsed by the [color][8] module to extract values for red, green, blue and alpha.
- `images[].blend` **[String][6]** how to blend this image with the image below. (optional, default `'over'`)
- `images[].gravity` **[String][6]** gravity at which to place the overlay. (optional, default `'centre'`)
- `images[].top` **[Number][7]?** the pixel offset from the top edge.
- `images[].left` **[Number][7]?** the pixel offset from the left edge.
- `images[].tile` **[Boolean][9]** set to true to repeat the overlay image across the entire image with the given `gravity`. (optional, default `false`)
- `images[].premultiplied` **[Boolean][9]** set to true to avoid premultipling the image below. Equivalent to the `--premultiplied` vips option. (optional, default `false`)
- `images[].density` **[Number][7]** number representing the DPI for vector overlay image. (optional, default `72`)
- `images[].raw` **[Object][4]?** describes overlay when using raw pixel data.
- `images[].raw.width` **[Number][7]?**
- `images[].raw.height` **[Number][7]?**
- `images[].raw.channels` **[Number][7]?**
* `images` **[Array][3]<[Object][4]>** Ordered list of images to composite

* `images[].input` **([Buffer][5] | [String][6])?** Buffer containing image data, String containing the path to an image file, or Create object (see below)

* `images[].input.create` **[Object][4]?** describes a blank overlay to be created.

* `images[].input.create.width` **[Number][7]?**
* `images[].input.create.height` **[Number][7]?**
* `images[].input.create.channels` **[Number][7]?** 3-4
* `images[].input.create.background` **([String][6] | [Object][4])?** parsed by the [color][8] module to extract values for red, green, blue and alpha.
* `images[].blend` **[String][6]** how to blend this image with the image below. (optional, default `'over'`)
* `images[].gravity` **[String][6]** gravity at which to place the overlay. (optional, default `'centre'`)
* `images[].top` **[Number][7]?** the pixel offset from the top edge.
* `images[].left` **[Number][7]?** the pixel offset from the left edge.
* `images[].tile` **[Boolean][9]** set to true to repeat the overlay image across the entire image with the given `gravity`. (optional, default `false`)
* `images[].premultiplied` **[Boolean][9]** set to true to avoid premultipling the image below. Equivalent to the `--premultiplied` vips option. (optional, default `false`)
* `images[].density` **[Number][7]** number representing the DPI for vector overlay image. (optional, default `72`)
* `images[].raw` **[Object][4]?** describes overlay when using raw pixel data.

* `images[].raw.width` **[Number][7]?**
* `images[].raw.height` **[Number][7]?**
* `images[].raw.channels` **[Number][7]?**
* `images[].animated` **[boolean][9]** Set to `true` to read all frames/pages of an animated image. (optional, default `false`)
* `images[].failOn` **[string][6]** @see [constructor parameters][10] (optional, default `'warning'`)
* `images[].limitInputPixels` **([number][7] | [boolean][9])** @see [constructor parameters][10] (optional, default `268402689`)

### Examples

```javascript
await sharp(background)
.composite([
{ input: layer1, gravity: 'northwest' },
{ input: layer2, gravity: 'southeast' },
])
.toFile('combined.png');
```

```javascript
const output = await sharp('input.gif', { animated: true })
.composite([
{ input: 'overlay.png', tile: true, blend: 'saturate' }
])
.toBuffer();
```

```javascript
sharp('input.png')
.rotate(180)
@@ -57,15 +81,15 @@ sharp('input.png')
});
```

- Throws **[Error][10]** Invalid parameters
* Throws **[Error][11]** Invalid parameters

Returns **Sharp**

**Meta**

- **since**: 0.22.0
* **since**: 0.22.0

[1]: https://libvips.github.io/libvips/API/current/libvips-conversion.html#VipsBlendMode
[1]: https://www.libvips.org/API/current/libvips-conversion.html#VipsBlendMode

[2]: https://www.cairographics.org/operators/

@@ -83,4 +107,6 @@ Returns **Sharp**

[9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean

[10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error
[10]: /api-constructor#parameters

[11]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error
139 changes: 99 additions & 40 deletions docs/api-constructor.md
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@

Constructor factory to create an instance of `sharp`, to which further methods are chained.

JPEG, PNG, WebP or TIFF format image data can be streamed out from this object.
JPEG, PNG, WebP, GIF, AVIF or TIFF format image data can be streamed out from this object.
When using Stream based output, derived attributes are available from the `info` event.

Non-critical problems encountered during processing are emitted as `warning` events.
@@ -13,32 +13,44 @@ Implements the [stream.Duplex][1] class.

### Parameters

- `input` **([Buffer][2] \| [string][3])?** if present, can be
a Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or
a String containing the filesystem path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file.
JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present.
- `options` **[Object][4]?** if present, is an Object with optional attributes.
- `options.failOnError` **[boolean][5]** by default halt processing and raise an error when loading invalid images.
Set this flag to `false` if you'd rather apply a "best effort" to decode images, even if the data is corrupt or invalid. (optional, default `true`)
- `options.limitInputPixels` **([number][6] \| [boolean][5])** Do not process input images where the number of pixels
(width x height) exceeds this limit. Assumes image dimensions contained in the input metadata can be trusted.
An integral Number of pixels, zero or false to remove limit, true to use default limit of 268402689 (0x3FFF x 0x3FFF). (optional, default `268402689`)
- `options.sequentialRead` **[boolean][5]** Set this to `true` to use sequential rather than random access where possible.
This can reduce memory usage and might improve performance on some systems. (optional, default `false`)
- `options.density` **[number][6]** number representing the DPI for vector images in the range 1 to 100000. (optional, default `72`)
- `options.pages` **[number][6]** number of pages to extract for multi-page input (GIF, TIFF, PDF), use -1 for all pages. (optional, default `1`)
- `options.page` **[number][6]** page number to start extracting from for multi-page input (GIF, TIFF, PDF), zero based. (optional, default `0`)
- `options.level` **[number][6]** level to extract from a multi-level input (OpenSlide), zero based. (optional, default `0`)
- `options.animated` **[boolean][5]** Set to `true` to read all frames/pages of an animated image (equivalent of setting `pages` to `-1`). (optional, default `false`)
- `options.raw` **[Object][4]?** describes raw pixel input image data. See `raw()` for pixel ordering.
- `options.raw.width` **[number][6]?**
- `options.raw.height` **[number][6]?**
- `options.raw.channels` **[number][6]?** 1-4
- `options.create` **[Object][4]?** describes a new image to be created.
- `options.create.width` **[number][6]?**
- `options.create.height` **[number][6]?**
- `options.create.channels` **[number][6]?** 3-4
- `options.create.background` **([string][3] \| [Object][4])?** parsed by the [color][7] module to extract values for red, green, blue and alpha.
* `input` **([Buffer][2] | [Uint8Array][3] | [Uint8ClampedArray][4] | [Int8Array][5] | [Uint16Array][6] | [Int16Array][7] | [Uint32Array][8] | [Int32Array][9] | [Float32Array][10] | [Float64Array][11] | [string][12])?** if present, can be
a Buffer / Uint8Array / Uint8ClampedArray containing JPEG, PNG, WebP, AVIF, GIF, SVG or TIFF image data, or
a TypedArray containing raw pixel image data, or
a String containing the filesystem path to an JPEG, PNG, WebP, AVIF, GIF, SVG or TIFF image file.
JPEG, PNG, WebP, AVIF, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present.
* `options` **[Object][13]?** if present, is an Object with optional attributes.

* `options.failOn` **[string][12]** level of sensitivity to invalid images, one of (in order of sensitivity): 'none' (least), 'truncated', 'error' or 'warning' (most), highers level imply lower levels. (optional, default `'warning'`)
* `options.limitInputPixels` **([number][14] | [boolean][15])** Do not process input images where the number of pixels
(width x height) exceeds this limit. Assumes image dimensions contained in the input metadata can be trusted.
An integral Number of pixels, zero or false to remove limit, true to use default limit of 268402689 (0x3FFF x 0x3FFF). (optional, default `268402689`)
* `options.unlimited` **[boolean][15]** Set this to `true` to remove safety features that help prevent memory exhaustion (SVG, PNG). (optional, default `false`)
* `options.sequentialRead` **[boolean][15]** Set this to `true` to use sequential rather than random access where possible.
This can reduce memory usage and might improve performance on some systems. (optional, default `false`)
* `options.density` **[number][14]** number representing the DPI for vector images in the range 1 to 100000. (optional, default `72`)
* `options.pages` **[number][14]** number of pages to extract for multi-page input (GIF, WebP, AVIF, TIFF, PDF), use -1 for all pages. (optional, default `1`)
* `options.page` **[number][14]** page number to start extracting from for multi-page input (GIF, WebP, AVIF, TIFF, PDF), zero based. (optional, default `0`)
* `options.subifd` **[number][14]** subIFD (Sub Image File Directory) to extract for OME-TIFF, defaults to main image. (optional, default `-1`)
* `options.level` **[number][14]** level to extract from a multi-level input (OpenSlide), zero based. (optional, default `0`)
* `options.animated` **[boolean][15]** Set to `true` to read all frames/pages of an animated image (equivalent of setting `pages` to `-1`). (optional, default `false`)
* `options.raw` **[Object][13]?** describes raw pixel input image data. See `raw()` for pixel ordering.

* `options.raw.width` **[number][14]?** integral number of pixels wide.
* `options.raw.height` **[number][14]?** integral number of pixels high.
* `options.raw.channels` **[number][14]?** integral number of channels, between 1 and 4.
* `options.raw.premultiplied` **[boolean][15]?** specifies that the raw input has already been premultiplied, set to `true`
to avoid sharp premultiplying the image. (optional, default `false`)
* `options.create` **[Object][13]?** describes a new image to be created.

* `options.create.width` **[number][14]?** integral number of pixels wide.
* `options.create.height` **[number][14]?** integral number of pixels high.
* `options.create.channels` **[number][14]?** integral number of channels, either 3 (RGB) or 4 (RGBA).
* `options.create.background` **([string][12] | [Object][13])?** parsed by the [color][16] module to extract values for red, green, blue and alpha.
* `options.create.noise` **[Object][13]?** describes a noise to be created.

* `options.create.noise.type` **[string][12]?** type of generated noise, currently only `gaussian` is supported.
* `options.create.noise.mean` **[number][14]?** mean of pixels in generated noise.
* `options.create.noise.sigma` **[number][14]?** standard deviation of pixels in generated noise.

### Examples

@@ -84,9 +96,40 @@ sharp({
await sharp('in.gif', { animated: true }).toFile('out.webp');
```

- Throws **[Error][8]** Invalid parameters
```javascript
// Read a raw array of pixels and save it to a png
const input = Uint8Array.from([255, 255, 255, 0, 0, 0]); // or Uint8ClampedArray
const image = sharp(input, {
// because the input does not contain its dimensions or how many channels it has
// we need to specify it in the constructor options
raw: {
width: 2,
height: 1,
channels: 3
}
});
await image.toFile('my-two-pixels.png');
```

```javascript
// Generate RGB Gaussian noise
await sharp({
create: {
width: 300,
height: 200,
channels: 3,
noise: {
type: 'gaussian',
mean: 128,
sigma: 30
}
}
}).toFile('noise.png');
```

* Throws **[Error][17]** Invalid parameters

Returns **[Sharp][9]**
Returns **[Sharp][18]**

## clone

@@ -110,9 +153,7 @@ readableStream.pipe(pipeline);
// Using Promises to know when the pipeline is complete
const fs = require("fs");
const got = require("got");
const sharpStream = sharp({
failOnError: false
});
const sharpStream = sharp({ failOn: 'none' });

const promises = [];

@@ -154,22 +195,40 @@ Promise.all(promises)
});
```

Returns **[Sharp][9]**
Returns **[Sharp][18]**

[1]: http://nodejs.org/api/stream.html#stream_class_stream_duplex

[2]: https://nodejs.org/api/buffer.html

[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array

[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Uint8ClampedArray

[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Int8Array

[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Uint16Array

[7]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Int16Array

[8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Uint32Array

[9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Int32Array

[10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Float32Array

[11]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Float64Array

[12]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String

[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[13]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object

[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[14]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number

[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
[15]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean

[7]: https://www.npmjs.org/package/color
[16]: https://www.npmjs.org/package/color

[8]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error
[17]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error

[9]: #sharp
[18]: #sharp
127 changes: 81 additions & 46 deletions docs/api-input.md
Original file line number Diff line number Diff line change
@@ -2,40 +2,52 @@

## metadata

Fast access to (uncached) image metadata without decoding any compressed image data.
Fast access to (uncached) image metadata without decoding any compressed pixel data.

This is taken from the header of the input image.
It does not include operations, such as resize, to be applied to the output image.

A `Promise` is returned when `callback` is not provided.

- `format`: Name of decoder used to decompress image data e.g. `jpeg`, `png`, `webp`, `gif`, `svg`
- `size`: Total size of image in bytes, for Stream and Buffer input only
- `width`: Number of pixels wide (EXIF orientation is not taken into consideration)
- `height`: Number of pixels high (EXIF orientation is not taken into consideration)
- `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...][1]
- `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK
- `depth`: Name of pixel depth format e.g. `uchar`, `char`, `ushort`, `float` [...][2]
- `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
- `pages`: Number of pages/frames contained within the image, with support for TIFF, HEIF, PDF, animated GIF and animated WebP
- `pageHeight`: Number of pixels high each page in a multi-page image will be.
- `loop`: Number of times to loop an animated image, zero refers to a continuous loop.
- `delay`: Delay in ms between each page in an animated image, provided as an array of integers.
- `pagePrimary`: Number of the primary page in a HEIF image
- `levels`: Details of each level in a multi-level image provided as an array of objects, requires libvips compiled with support for OpenSlide
- `hasProfile`: Boolean indicating the presence of an embedded ICC profile
- `hasAlpha`: Boolean indicating the presence of an alpha transparency channel
- `orientation`: Number value of the EXIF Orientation header, if present
- `exif`: Buffer containing raw EXIF data, if present
- `icc`: Buffer containing raw [ICC][3] profile data, if present
- `iptc`: Buffer containing raw IPTC data, if present
- `xmp`: Buffer containing raw XMP data, if present
- `tifftagPhotoshop`: Buffer containing raw TIFFTAG_PHOTOSHOP data, if present
* `format`: Name of decoder used to decompress image data e.g. `jpeg`, `png`, `webp`, `gif`, `svg`
* `size`: Total size of image in bytes, for Stream and Buffer input only
* `width`: Number of pixels wide (EXIF orientation is not taken into consideration, see example below)
* `height`: Number of pixels high (EXIF orientation is not taken into consideration, see example below)
* `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...][1]
* `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK
* `depth`: Name of pixel depth format e.g. `uchar`, `char`, `ushort`, `float` [...][2]
* `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
* `pages`: Number of pages/frames contained within the image, with support for TIFF, HEIF, PDF, animated GIF and animated WebP
* `pageHeight`: Number of pixels high each page in a multi-page image will be.
* `loop`: Number of times to loop an animated image, zero refers to a continuous loop.
* `delay`: Delay in ms between each page in an animated image, provided as an array of integers.
* `pagePrimary`: Number of the primary page in a HEIF image
* `levels`: Details of each level in a multi-level image provided as an array of objects, requires libvips compiled with support for OpenSlide
* `subifds`: Number of Sub Image File Directories in an OME-TIFF image
* `background`: Default background colour, if present, for PNG (bKGD) and GIF images, either an RGB Object or a single greyscale value
* `compression`: The encoder used to compress an HEIF file, `av1` (AVIF) or `hevc` (HEIC)
* `resolutionUnit`: The unit of resolution (density), either `inch` or `cm`, if present
* `hasProfile`: Boolean indicating the presence of an embedded ICC profile
* `hasAlpha`: Boolean indicating the presence of an alpha transparency channel
* `orientation`: Number value of the EXIF Orientation header, if present
* `exif`: Buffer containing raw EXIF data, if present
* `icc`: Buffer containing raw [ICC][3] profile data, if present
* `iptc`: Buffer containing raw IPTC data, if present
* `xmp`: Buffer containing raw XMP data, if present
* `tifftagPhotoshop`: Buffer containing raw TIFFTAG_PHOTOSHOP data, if present

### Parameters

- `callback` **[Function][4]?** called with the arguments `(err, metadata)`
* `callback` **[Function][4]?** called with the arguments `(err, metadata)`

### Examples

```javascript
const metadata = await sharp(input).metadata();
```

```javascript
const image = sharp(inputJpg);
image
@@ -51,32 +63,47 @@ image
});
```

Returns **([Promise][5]&lt;[Object][6]> | Sharp)**
```javascript
// Based on EXIF rotation metadata, get the right-side-up width and height:

const size = getNormalSize(await sharp(input).metadata());

function getNormalSize({ width, height, orientation }) {
return orientation || 0 >= 5
? { width: height, height: width }
: { width, height };
}
```

Returns **([Promise][5]<[Object][6]> | Sharp)**

## stats

Access to pixel-derived image statistics for every channel in the image.
A `Promise` is returned when `callback` is not provided.

- `channels`: Array of channel statistics for each channel in the image. Each channel statistic contains
- `min` (minimum value in the channel)
- `max` (maximum value in the channel)
- `sum` (sum of all values in a channel)
- `squaresSum` (sum of squared values in a channel)
- `mean` (mean of the values in a channel)
- `stdev` (standard deviation for the values in a channel)
- `minX` (x-coordinate of one of the pixel where the minimum lies)
- `minY` (y-coordinate of one of the pixel where the minimum lies)
- `maxX` (x-coordinate of one of the pixel where the maximum lies)
- `maxY` (y-coordinate of one of the pixel where the maximum lies)
- `isOpaque`: Is the image fully opaque? Will be `true` if the image has no alpha channel or if every pixel is fully opaque.
- `entropy`: Histogram-based estimation of greyscale entropy, discarding alpha channel if any (experimental)
- `sharpness`: Estimation of greyscale sharpness based on the standard deviation of a Laplacian convolution, discarding alpha channel if any (experimental)
- `dominant`: Object containing most dominant sRGB colour based on a 4096-bin 3D histogram (experimental)
* `channels`: Array of channel statistics for each channel in the image. Each channel statistic contains
* `min` (minimum value in the channel)
* `max` (maximum value in the channel)
* `sum` (sum of all values in a channel)
* `squaresSum` (sum of squared values in a channel)
* `mean` (mean of the values in a channel)
* `stdev` (standard deviation for the values in a channel)
* `minX` (x-coordinate of one of the pixel where the minimum lies)
* `minY` (y-coordinate of one of the pixel where the minimum lies)
* `maxX` (x-coordinate of one of the pixel where the maximum lies)
* `maxY` (y-coordinate of one of the pixel where the maximum lies)
* `isOpaque`: Is the image fully opaque? Will be `true` if the image has no alpha channel or if every pixel is fully opaque.
* `entropy`: Histogram-based estimation of greyscale entropy, discarding alpha channel if any.
* `sharpness`: Estimation of greyscale sharpness based on the standard deviation of a Laplacian convolution, discarding alpha channel if any.
* `dominant`: Object containing most dominant sRGB colour based on a 4096-bin 3D histogram.

**Note**: Statistics are derived from the original input image. Any operations performed on the image must first be
written to a buffer in order to run `stats` on the result (see third example).

### Parameters

- `callback` **[Function][4]?** called with the arguments `(err, stats)`
* `callback` **[Function][4]?** called with the arguments `(err, stats)`

### Examples

@@ -94,11 +121,19 @@ const { entropy, sharpness, dominant } = await sharp(input).stats();
const { r, g, b } = dominant;
```

Returns **[Promise][5]&lt;[Object][6]>**
```javascript
const image = sharp(input);
// store intermediate result
const part = await image.extract(region).toBuffer();
// create new instance to obtain statistics of extracted region
const stats = await sharp(part).stats();
```

Returns **[Promise][5]<[Object][6]>**

[1]: https://libvips.github.io/libvips/API/current/VipsImage.html#VipsInterpretation
[1]: https://www.libvips.org/API/current/VipsImage.html#VipsInterpretation

[2]: https://libvips.github.io/libvips/API/current/VipsImage.html#VipsBandFormat
[2]: https://www.libvips.org/API/current/VipsImage.html#VipsBandFormat

[3]: https://www.npmjs.com/package/icc

369 changes: 302 additions & 67 deletions docs/api-operation.md

Large diffs are not rendered by default.

458 changes: 333 additions & 125 deletions docs/api-output.md

Large diffs are not rendered by default.

120 changes: 75 additions & 45 deletions docs/api-resize.md
Original file line number Diff line number Diff line change
@@ -6,49 +6,51 @@ 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`: (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.
* `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.

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.
* `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.

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.

- `entropy`: focus on the region with the highest [Shannon entropy][3].
- `attention`: focus on the region with the highest luminance frequency, colour saturation and presence of skin tones.
* `entropy`: focus on the region with the highest [Shannon entropy][3].
* `attention`: focus on the region with the highest luminance frequency, colour saturation and presence of skin tones.

Possible interpolation kernels are:

- `nearest`: Use [nearest neighbour interpolation][4].
- `cubic`: Use a [Catmull-Rom spline][5].
- `mitchell`: Use a [Mitchell-Netravali spline][6].
- `lanczos2`: Use a [Lanczos kernel][7] with `a=2`.
- `lanczos3`: Use a Lanczos kernel with `a=3` (the default).
* `nearest`: Use [nearest neighbour interpolation][4].
* `cubic`: Use a [Catmull-Rom spline][5].
* `mitchell`: Use a [Mitchell-Netravali spline][6].
* `lanczos2`: Use a [Lanczos kernel][7] with `a=2`.
* `lanczos3`: Use a Lanczos kernel with `a=3` (the default).

### Parameters

- `width` **[number][8]?** pixels wide the resultant image should be. Use `null` or `undefined` to auto-scale the width to match the height.
- `height` **[number][8]?** pixels high the resultant image should be. Use `null` or `undefined` to auto-scale the height to match the width.
- `options` **[Object][9]?**
- `options.width` **[String][10]?** alternative means of specifying `width`. If both are present this take priority.
- `options.height` **[String][10]?** alternative means of specifying `height`. If both are present this take priority.
- `options.fit` **[String][10]** how the image should be resized to fit both provided dimensions, one of `cover`, `contain`, `fill`, `inside` or `outside`. (optional, default `'cover'`)
- `options.position` **[String][10]** position, gravity or strategy to use when `fit` is `cover` or `contain`. (optional, default `'centre'`)
- `options.background` **([String][10] \| [Object][9])** background colour when using a `fit` of `contain`, parsed by the [color][11] module, defaults to black without transparency. (optional, default `{r:0,g:0,b:0,alpha:1}`)
- `options.kernel` **[String][10]** the kernel to use for image reduction. (optional, default `'lanczos3'`)
- `options.withoutEnlargement` **[Boolean][12]** do not enlarge if the width _or_ height are already less than the specified dimensions, equivalent to GraphicsMagick's `>` geometry option. (optional, default `false`)
- `options.fastShrinkOnLoad` **[Boolean][12]** take greater advantage of the JPEG and WebP shrink-on-load feature, which can lead to a slight moiré pattern on some images. (optional, default `true`)
* `width` **[number][8]?** pixels wide the resultant image should be. Use `null` or `undefined` to auto-scale the width to match the height.
* `height` **[number][8]?** pixels high the resultant image should be. Use `null` or `undefined` to auto-scale the height to match the width.
* `options` **[Object][9]?**

* `options.width` **[String][10]?** alternative means of specifying `width`. If both are present this take priority.
* `options.height` **[String][10]?** alternative means of specifying `height`. If both are present this take priority.
* `options.fit` **[String][10]** how the image should be resized to fit both provided dimensions, one of `cover`, `contain`, `fill`, `inside` or `outside`. (optional, default `'cover'`)
* `options.position` **[String][10]** position, gravity or strategy to use when `fit` is `cover` or `contain`. (optional, default `'centre'`)
* `options.background` **([String][10] | [Object][9])** background colour when using a `fit` of `contain`, parsed by the [color][11] module, defaults to black without transparency. (optional, default `{r:0,g:0,b:0,alpha:1}`)
* `options.kernel` **[String][10]** the kernel to use for image reduction. (optional, default `'lanczos3'`)
* `options.withoutEnlargement` **[Boolean][12]** do not enlarge if the width *or* height are already less than the specified dimensions, equivalent to GraphicsMagick's `>` geometry option. (optional, default `false`)
* `options.withoutReduction` **[Boolean][12]** do not reduce if the width *or* height are already greater than the specified dimensions, equivalent to GraphicsMagick's `<` geometry option. (optional, default `false`)
* `options.fastShrinkOnLoad` **[Boolean][12]** take greater advantage of the JPEG and WebP shrink-on-load feature, which can lead to a slight moiré pattern on some images. (optional, default `true`)

### Examples

@@ -116,6 +118,21 @@ sharp(input)
});
```

```javascript
sharp(input)
.resize(200, 200, {
fit: sharp.fit.outside,
withoutReduction: true
})
.toFormat('jpeg')
.toBuffer()
.then(function(outputBuffer) {
// outputBuffer contains JPEG image data
// of at least 200 pixels wide and 200 pixels high while maintaining aspect ratio
// and no smaller than the input image
});
```

```javascript
const scaleByHalf = await sharp(input)
.metadata()
@@ -125,7 +142,7 @@ const scaleByHalf = await sharp(input)
);
```

- Throws **[Error][13]** Invalid parameters
* Throws **[Error][13]** Invalid parameters

Returns **Sharp**

@@ -136,12 +153,13 @@ This operation will always occur after resizing and extraction, if any.

### Parameters

- `extend` **([number][8] \| [Object][9])** single pixel count to add to all edges or an Object with per-edge counts
- `extend.top` **[number][8]?**
- `extend.left` **[number][8]?**
- `extend.bottom` **[number][8]?**
- `extend.right` **[number][8]?**
- `extend.background` **([String][10] \| [Object][9])** background colour, parsed by the [color][11] module, defaults to black without transparency. (optional, default `{r:0,g:0,b:0,alpha:1}`)
* `extend` **([number][8] | [Object][9])** single pixel count to add to all edges or an Object with per-edge counts

* `extend.top` **[number][8]** (optional, default `0`)
* `extend.left` **[number][8]** (optional, default `0`)
* `extend.bottom` **[number][8]** (optional, default `0`)
* `extend.right` **[number][8]** (optional, default `0`)
* `extend.background` **([String][10] | [Object][9])** background colour, parsed by the [color][11] module, defaults to black without transparency. (optional, default `{r:0,g:0,b:0,alpha:1}`)

### Examples

@@ -160,25 +178,36 @@ sharp(input)
...
```

- Throws **[Error][13]** Invalid parameters
```javascript
// Add a row of 10 red pixels to the bottom
sharp(input)
.extend({
bottom: 10,
background: 'red'
})
...
```

* Throws **[Error][13]** Invalid parameters

Returns **Sharp**

## extract

Extract/crop a region of the image.

- Use `extract` before `resize` for pre-resize extraction.
- Use `extract` after `resize` for post-resize extraction.
- Use `extract` before and after for both.
* Use `extract` before `resize` for pre-resize extraction.
* Use `extract` after `resize` for post-resize extraction.
* Use `extract` before and after for both.

### Parameters

- `options` **[Object][9]** describes the region to extract using integral pixel values
- `options.left` **[number][8]** zero-indexed offset from left edge
- `options.top` **[number][8]** zero-indexed offset from top edge
- `options.width` **[number][8]** width of region to extract
- `options.height` **[number][8]** height of region to extract
* `options` **[Object][9]** describes the region to extract using integral pixel values

* `options.left` **[number][8]** zero-indexed offset from left edge
* `options.top` **[number][8]** zero-indexed offset from top edge
* `options.width` **[number][8]** width of region to extract
* `options.height` **[number][8]** height of region to extract

### Examples

@@ -200,7 +229,7 @@ sharp(input)
});
```

- Throws **[Error][13]** Invalid parameters
* Throws **[Error][13]** Invalid parameters

Returns **Sharp**

@@ -214,10 +243,11 @@ will contain `trimOffsetLeft` and `trimOffsetTop` properties.

### Parameters

- `threshold` **[number][8]** the allowed difference from the top-left pixel, a number greater than zero. (optional, default `10`)
* `threshold` **[number][8]** the allowed difference from the top-left pixel, a number greater than zero. (optional, default `10`)

<!---->

- Throws **[Error][13]** Invalid parameters
* Throws **[Error][13]** Invalid parameters

Returns **Sharp**

119 changes: 98 additions & 21 deletions docs/api-utility.md
Original file line number Diff line number Diff line change
@@ -12,6 +12,36 @@ console.log(sharp.format);

Returns **[Object][1]**

## interpolators

An Object containing the available interpolators and their proper values

Type: [string][2]

### nearest

[Nearest neighbour interpolation][3]. Suitable for image enlargement only.

### bilinear

[Bilinear interpolation][4]. Faster than bicubic but with less smooth results.

### bicubic

[Bicubic interpolation][5] (the default).

### locallyBoundedBicubic

[LBB interpolation][6]. Prevents some "[acutance][7]" but typically reduces performance by a factor of 2.

### nohalo

[Nohalo interpolation][8]. Prevents acutance but typically reduces performance by a factor of 3.

### vertexSplitQuadraticBasisSpline

[VSQBS interpolation][9]. Prevents "staircasing" when enlarging.

## versions

An Object containing the version numbers of libvips and its dependencies.
@@ -22,19 +52,31 @@ An Object containing the version numbers of libvips and its dependencies.
console.log(sharp.versions);
```

## vendor

An Object containing the platform and architecture
of the current and installed vendored binaries.

### Examples

```javascript
console.log(sharp.vendor);
```

## cache

Gets or, when options are provided, sets the limits of _libvips'_ operation cache.
Gets or, when options are provided, sets the limits of *libvips'* operation cache.
Existing entries in the cache will be trimmed after any change in limits.
This method always returns cache statistics,
useful for determining how much working memory is required for a particular task.

### Parameters

- `options` **([Object][1] \| [boolean][2])** Object with the following attributes, or boolean where true uses default cache settings and false removes all caching (optional, default `true`)
- `options.memory` **[number][3]** is the maximum memory in MB to use for this cache (optional, default `50`)
- `options.files` **[number][3]** is the maximum number of files to hold open (optional, default `20`)
- `options.items` **[number][3]** is the maximum number of operations to cache (optional, default `100`)
* `options` **([Object][1] | [boolean][10])** Object with the following attributes, or boolean where true uses default cache settings and false removes all caching (optional, default `true`)

* `options.memory` **[number][11]** is the maximum memory in MB to use for this cache (optional, default `50`)
* `options.files` **[number][11]** is the maximum number of files to hold open (optional, default `20`)
* `options.items` **[number][11]** is the maximum number of operations to cache (optional, default `100`)

### Examples

@@ -53,18 +95,35 @@ Returns **[Object][1]**
## concurrency

Gets or, when a concurrency is provided, sets
the number of threads _libvips'_ should create to process each image.
The default value is the number of CPU cores.
A value of `0` will reset to this default.

The maximum number of images that can be processed in parallel
is limited by libuv's `UV_THREADPOOL_SIZE` environment variable.
the maximum number of threads *libvips* should use to process *each image*.
These are from a thread pool managed by glib,
which helps avoid the overhead of creating new threads.

This method always returns the current concurrency.

The default value is the number of CPU cores,
except when using glibc-based Linux without jemalloc,
where the default is `1` to help reduce memory fragmentation.

A value of `0` will reset this to the number of CPU cores.

Some image format libraries spawn additional threads,
e.g. libaom manages its own 4 threads when encoding AVIF images,
and these are independent of the value set here.

The maximum number of images that sharp can process in parallel
is controlled by libuv's `UV_THREADPOOL_SIZE` environment variable,
which defaults to 4.

[https://nodejs.org/api/cli.html#uv_threadpool_sizesize][12]

For example, by default, a machine with 8 CPU cores will process
4 images in parallel and use up to 8 threads per image,
so there will be up to 32 concurrent threads.

### Parameters

- `concurrency` **[number][3]?**
* `concurrency` **[number][11]?**

### Examples

@@ -74,14 +133,14 @@ sharp.concurrency(2); // 2
sharp.concurrency(0); // 4
```

Returns **[number][3]** concurrency
Returns **[number][11]** concurrency

## queue

An EventEmitter that emits a `change` event when a task is either:

- queued, waiting for _libuv_ to provide a worker thread
- complete
* queued, waiting for *libuv* to provide a worker thread
* complete

### Examples

@@ -95,8 +154,8 @@ sharp.queue.on('change', function(queueLength) {

Provides access to internal task counters.

- queue is the number of tasks this module has queued waiting for _libuv_ to provide a worker thread from its pool.
- process is the number of resize tasks currently being processed.
* queue is the number of tasks this module has queued waiting for *libuv* to provide a worker thread from its pool.
* process is the number of resize tasks currently being processed.

### Examples

@@ -116,7 +175,7 @@ by taking advantage of the SIMD vector unit of the CPU, e.g. Intel SSE and ARM N

### Parameters

- `simd` **[boolean][2]** (optional, default `true`)
* `simd` **[boolean][10]** (optional, default `true`)

### Examples

@@ -130,10 +189,28 @@ const simd = sharp.simd(false);
// prevent libvips from using liborc at runtime
```

Returns **[boolean][2]**
Returns **[boolean][10]**

[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object

[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String

[3]: http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation

[4]: http://en.wikipedia.org/wiki/Bilinear_interpolation

[5]: http://en.wikipedia.org/wiki/Bicubic_interpolation

[6]: https://github.com/libvips/libvips/blob/master/libvips/resample/lbb.cpp#L100

[7]: http://en.wikipedia.org/wiki/Acutance

[8]: http://eprints.soton.ac.uk/268086/

[9]: https://github.com/libvips/libvips/blob/master/libvips/resample/vsqbs.cpp#L48

[10]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean

[11]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number

[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
[12]: https://nodejs.org/api/cli.html#uv_threadpool_sizesize
25 changes: 25 additions & 0 deletions docs/build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use strict';

const fs = require('fs').promises;
const path = require('path');
const documentation = require('documentation');

[
'constructor',
'input',
'resize',
'composite',
'operation',
'colour',
'channel',
'output',
'utility'
].forEach(async (m) => {
const input = path.join('lib', `${m}.js`);
const output = path.join('docs', `api-${m}.md`);

const ast = await documentation.build(input, { shallow: true });
const markdown = await documentation.formats.md(ast, { markdownToc: false });

await fs.writeFile(output, markdown);
});
362 changes: 362 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,371 @@
# Changelog

## v0.30 - *dresser*

Requires libvips v8.12.2

### v0.30.5 - 23rd May 2022

* Install: pass `PKG_CONFIG_PATH` via env rather than substitution.
[@dwisiswant0](https://github.com/dwisiswant0)

* Allow installation of prebuilt libvips binaries from filesystem.
[#3196](https://github.com/lovell/sharp/pull/3196)
[@ankurparihar](https://github.com/ankurparihar)

* Fix rotate-then-extract for EXIF orientation 2.
[#3218](https://github.com/lovell/sharp/pull/3218)
[@jakob0fischl](https://github.com/jakob0fischl)

### v0.30.4 - 18th April 2022

* Increase control over sensitivity to invalid images via `failOn`, deprecate `failOnError` (equivalent to `failOn: 'warning'`).

* Ensure `create` input image has correct bit depth and colour space.
[#3139](https://github.com/lovell/sharp/issues/3139)

* Add support for `TypedArray` input with `byteOffset` and `length`.
[#3146](https://github.com/lovell/sharp/pull/3146)
[@codepage949](https://github.com/codepage949)

* Improve error message when attempting to render SVG input greater than 32767x32767.
[#3167](https://github.com/lovell/sharp/issues/3167)

* Add missing file name to 'Input file is missing' error message.
[#3178](https://github.com/lovell/sharp/pull/3178)
[@Brodan](https://github.com/Brodan)

### v0.30.3 - 14th March 2022

* Allow `sharpen` options to be provided more consistently as an Object.
[#2561](https://github.com/lovell/sharp/issues/2561)

* Expose `x1`, `y2` and `y3` parameters of `sharpen` operation.
[#2935](https://github.com/lovell/sharp/issues/2935)

* Prevent double unpremultiply with some composite blend modes (regression in 0.30.2).
[#3118](https://github.com/lovell/sharp/issues/3118)

### v0.30.2 - 2nd March 2022

* Improve performance and accuracy when compositing multiple images.
[#2286](https://github.com/lovell/sharp/issues/2286)

* Expand pkgconfig search path for wider BSD support.
[#3106](https://github.com/lovell/sharp/issues/3106)

* Ensure Windows C++ runtime is linked statically (regression in 0.30.0).
[#3110](https://github.com/lovell/sharp/pull/3110)
[@kleisauke](https://github.com/kleisauke)

* Temporarily ignore greyscale ICC profiles to workaround lcms bug.
[#3112](https://github.com/lovell/sharp/issues/3112)

### v0.30.1 - 9th February 2022

* Allow use of `toBuffer` and `toFile` on the same instance.
[#3044](https://github.com/lovell/sharp/issues/3044)

* Skip shrink-on-load for known libjpeg rounding errors.
[#3066](https://github.com/lovell/sharp/issues/3066)
[@kleisauke](https://github.com/kleisauke)

* Ensure withoutReduction does not interfere with contain/crop/embed.
[#3081](https://github.com/lovell/sharp/pull/3081)
[@kleisauke](https://github.com/kleisauke)

* Ensure affine interpolator is correctly finalised.
[#3083](https://github.com/lovell/sharp/pull/3083)
[@kleisauke](https://github.com/kleisauke)

### v0.30.0 - 1st February 2022

* Add support for GIF output to prebuilt binaries.

* Reduce minimum Linux ARM64v8 glibc requirement to 2.17.

* Verify prebuilt binaries with a Subresource Integrity check.

* Standardise WebP `effort` option name, deprecate `reductionEffort`.

* Standardise HEIF `effort` option name, deprecate `speed`.

* Add support for IIIF v3 tile-based output.

* Expose control over CPU effort for palette-based PNG output.
[#2541](https://github.com/lovell/sharp/issues/2541)

* Improve animated (multi-page) image resize and extract.
[#2789](https://github.com/lovell/sharp/pull/2789)
[@kleisauke](https://github.com/kleisauke)

* Expose platform and architecture of vendored binaries as `sharp.vendor`.
[#2928](https://github.com/lovell/sharp/issues/2928)

* Ensure 16-bit PNG output uses correct bitdepth.
[#2958](https://github.com/lovell/sharp/pull/2958)
[@gforge](https://github.com/gforge)

* Properly emit close events for duplex streams.
[#2976](https://github.com/lovell/sharp/pull/2976)
[@driannaude](https://github.com/driannaude)

* Expose `unlimited` option for SVG and PNG input, switches off safety features.
[#2984](https://github.com/lovell/sharp/issues/2984)

* Add `withoutReduction` option to resize operation.
[#3006](https://github.com/lovell/sharp/pull/3006)
[@christopherbradleybanks](https://github.com/christopherbradleybanks)

* Add `resolutionUnit` as `tiff` option and expose in metadata.
[#3023](https://github.com/lovell/sharp/pull/3023)
[@ompal-sisodiya](https://github.com/ompal-sisodiya)

* Ensure rotate-then-extract works with EXIF mirroring.
[#3024](https://github.com/lovell/sharp/issues/3024)

## v0.29 - *circle*

Requires libvips v8.11.3

### v0.29.3 - 14th November 2021

* Ensure correct dimensions when containing image resized to 1px.
[#2951](https://github.com/lovell/sharp/issues/2951)

* Impute TIFF `xres`/`yres` from `density` provided to `withMetadata`.
[#2952](https://github.com/lovell/sharp/pull/2952)
[@mbklein](https://github.com/mbklein)

### v0.29.2 - 21st October 2021

* Add `timeout` function to limit processing time.

* Ensure `sharp.versions` is populated from vendored libvips.

* Remove animation properties from single page images.
[#2890](https://github.com/lovell/sharp/issues/2890)

* Allow use of 'tif' to select TIFF output.
[#2893](https://github.com/lovell/sharp/pull/2893)
[@erf](https://github.com/erf)

* Improve error message on Windows for version conflict.
[#2918](https://github.com/lovell/sharp/pull/2918)
[@dkrnl](https://github.com/dkrnl)

* Throw error rather than exit when invalid binaries detected.
[#2931](https://github.com/lovell/sharp/issues/2931)

### v0.29.1 - 7th September 2021

* Add `lightness` option to `modulate` operation.
[#2846](https://github.com/lovell/sharp/pull/2846)

* Ensure correct PNG bitdepth is set based on number of colours.
[#2855](https://github.com/lovell/sharp/issues/2855)

* Ensure background is always premultiplied when compositing.
[#2858](https://github.com/lovell/sharp/issues/2858)

* Ensure images with P3 profiles retain full gamut.
[#2862](https://github.com/lovell/sharp/issues/2862)

* Add support for libvips compiled with OpenJPEG.
[#2868](https://github.com/lovell/sharp/pull/2868)

* Remove unsupported animation properties from AVIF output.
[#2870](https://github.com/lovell/sharp/issues/2870)

* Resolve paths before comparing input/output filenames.
[#2878](https://github.com/lovell/sharp/pull/2878)
[@rexxars](https://github.com/rexxars)

* Allow use of speed 9 (fastest) for HEIF encoding.
[#2879](https://github.com/lovell/sharp/pull/2879)
[@rexxars](https://github.com/rexxars)

### v0.29.0 - 17th August 2021

* Drop support for Node.js 10, now requires Node.js >= 12.13.0.

* Add `background` property to PNG and GIF image metadata.

* Add `compression` property to HEIF image metadata.
[#2504](https://github.com/lovell/sharp/issues/2504)

* AVIF encoding now defaults to `4:4:4` chroma subsampling.
[#2562](https://github.com/lovell/sharp/issues/2562)

* Allow multiple platform-arch binaries in same `node_modules` installation tree.
[#2575](https://github.com/lovell/sharp/issues/2575)

* Default to single-channel `b-w` space when `extractChannel` is used.
[#2658](https://github.com/lovell/sharp/issues/2658)

* Allow installation directory to contain spaces (regression in v0.26.0).
[#2777](https://github.com/lovell/sharp/issues/2777)

* Add `pipelineColourspace` operator to set the processing space.
[#2704](https://github.com/lovell/sharp/pull/2704)
[@Daiz](https://github.com/Daiz)

* Allow bit depth to be set when using raw input and output.
[#2762](https://github.com/lovell/sharp/pull/2762)
[@mart-jansink](https://github.com/mart-jansink)

* Allow `negate` to act only on non-alpha channels.
[#2808](https://github.com/lovell/sharp/pull/2808)
[@rexxars](https://github.com/rexxars)

## v0.28 - *bijou*

Requires libvips v8.10.6

### v0.28.3 - 24th May 2021

* Ensure presence of libvips, vendored or global, before invoking node-gyp.

* Skip shrink-on-load for multi-page WebP.
[#2714](https://github.com/lovell/sharp/issues/2714)

* Add contrast limiting adaptive histogram equalization (CLAHE) operator.
[#2726](https://github.com/lovell/sharp/pull/2726)
[@baparham](https://github.com/baparham)

### v0.28.2 - 10th May 2021

* Allow `withMetadata` to set `density`.
[#967](https://github.com/lovell/sharp/issues/967)

* Skip shrink-on-load where one dimension <4px.
[#2653](https://github.com/lovell/sharp/issues/2653)

* Allow escaped proxy credentials.
[#2664](https://github.com/lovell/sharp/pull/2664)
[@msalettes](https://github.com/msalettes)

* Add `premultiplied` flag for raw pixel data input.
[#2685](https://github.com/lovell/sharp/pull/2685)
[@mnutt](https://github.com/mnutt)

* Detect empty input and throw a helpful error.
[#2687](https://github.com/lovell/sharp/pull/2687)
[@JakobJingleheimer](https://github.com/JakobJingleheimer)

* Add install-time flag to skip version compatibility checks.
[#2692](https://github.com/lovell/sharp/pull/2692)
[@xemle](https://github.com/xemle)

### v0.28.1 - 5th April 2021

* Ensure all installation errors are logged with a more obvious prefix.

* Allow `withMetadata` to set and update EXIF metadata.
[#650](https://github.com/lovell/sharp/issues/650)

* Add support for OME-TIFF Sub Image File Directories (subIFD).
[#2557](https://github.com/lovell/sharp/issues/2557)

* Allow `ensureAlpha` to set the alpha transparency level.
[#2634](https://github.com/lovell/sharp/issues/2634)

### v0.28.0 - 29th March 2021

* Prebuilt binaries now include mozjpeg and libimagequant (BSD 2-Clause).

* Prebuilt binaries limit AVIF support to the most common 8-bit depth.

* Add `mozjpeg` option to `jpeg` method, sets mozjpeg defaults.

* Reduce the default PNG `compressionLevel` to the more commonly used 6.

* Reduce concurrency on glibc-based Linux when using the default memory allocator to help prevent fragmentation.

* Default missing edge properties of extend operation to zero.
[#2578](https://github.com/lovell/sharp/issues/2578)

* Ensure composite does not clip top and left offsets.
[#2594](https://github.com/lovell/sharp/pull/2594)
[@SHG42](https://github.com/SHG42)

* Improve error handling of network failure at install time.
[#2608](https://github.com/lovell/sharp/pull/2608)
[@abradley](https://github.com/abradley)

* Ensure `@id` attribute can be set for IIIF tile-based output.
[#2612](https://github.com/lovell/sharp/issues/2612)
[@edsilv](https://github.com/edsilv)

* Ensure composite replicates the correct number of tiles for centred gravities.
[#2626](https://github.com/lovell/sharp/issues/2626)

## v0.27 - *avif*

Requires libvips v8.10.5

### v0.27.2 - 22nd February 2021

* macOS: Prevent use of globally-installed ARM64 libvips with Rosetta x64 emulation.
[#2460](https://github.com/lovell/sharp/issues/2460)

* Linux (musl): Prevent use of prebuilt linuxmusl-x64 binaries with musl >= 1.2.0.
[#2570](https://github.com/lovell/sharp/issues/2570)

* Improve 16-bit grey+alpha support by using libvips' `has_alpha` detection.
[#2569](https://github.com/lovell/sharp/issues/2569)

* Allow the use of non lower case extensions with `toFormat`.
[#2581](https://github.com/lovell/sharp/pull/2581)
[@florian-busch](https://github.com/florian-busch)

* Allow use of `recomb` operation with single channel input.
[#2584](https://github.com/lovell/sharp/issues/2584)

### v0.27.1 - 27th January 2021

* Ensure TIFF is cast when using float predictor.
[#2502](https://github.com/lovell/sharp/pull/2502)
[@randyridge](https://github.com/randyridge)

* Add support for Uint8Array and Uint8ClampedArray input.
[#2511](https://github.com/lovell/sharp/pull/2511)
[@leon](https://github.com/leon)

* Revert: ensure all platforms use fontconfig for font rendering.
[#2515](https://github.com/lovell/sharp/issues/2515)

* Expose libvips gaussnoise operation to allow creation of Gaussian noise.
[#2527](https://github.com/lovell/sharp/pull/2527)
[@alza54](https://github.com/alza54)

### v0.27.0 - 22nd December 2020

* Add support for AVIF to prebuilt binaries.

* Remove experimental status from `heif` output, defaults are now AVIF-centric.

* Allow negative top/left offsets for composite operation.
[#2391](https://github.com/lovell/sharp/pull/2391)
[@CurosMJ](https://github.com/CurosMJ)

* Ensure all platforms use fontconfig for font rendering.
[#2399](https://github.com/lovell/sharp/issues/2399)

## v0.26 - *zoom*

Requires libvips v8.10.0

### v0.26.3 - 16th November 2020

* Expose libvips' affine operation.
[#2336](https://github.com/lovell/sharp/pull/2336)
[@guillevc](https://github.com/guillevc)

* Fallback to tar.gz for prebuilt libvips when Brotli not available.
[#2412](https://github.com/lovell/sharp/pull/2412)
[@ascorbic](https://github.com/ascorbic)

### v0.26.2 - 14th October 2020

* Add support for EXR input. Requires libvips compiled with OpenEXR.
1 change: 1 addition & 0 deletions docs/docute.min.js

Large diffs are not rendered by default.

9 changes: 4 additions & 5 deletions docs/firebase.json
Original file line number Diff line number Diff line change
@@ -4,19 +4,18 @@
"public": ".",
"ignore": [
".*",
"build.js",
"firebase.json",
"*.md",
"image/**",
"search-index/**"
],
"headers": [
{
"source": "**",
"headers": [
{
"key": "Cache-Control",
"value": "max-age=86400"
}
{ "key": "Cache-Control", "value": "max-age=86400" },
{ "key": "X-Content-Type-Options", "value": "nosniff" },
{ "key": "X-Frame-Options", "value": "SAMEORIGIN" }
]
}
],
39 changes: 39 additions & 0 deletions docs/humans.txt
Original file line number Diff line number Diff line change
@@ -206,3 +206,42 @@ GitHub: https://github.com/stefanprobst

Name: Thomas Beiganz
GitHub: https://github.com/beig

Name: Florian Busch
GitHub: https://github.com/florian-busch

Name: Matthieu Salettes
GitHub: https://github.com/msalettes

Name: Taneli Vatanen
GitHub: https://github.com/Daiz

Name: Mart Jansink
GitHub: https://github.com/mart-jansink

Name: Tenpi
GitHub: https://github.com/Tenpi

Name: Zaruike
GitHub: https://github.com/Zaruike

Name: Erlend F
GitHub: https://github.com/erf

Name: Drian Naude
GitHub: https://github.com/driannaude

Name: Max Gordon
GitHub: https://github.com/gforge

Name: Chris Banks
GitHub: https://github.com/christopherbradleybanks

Name: codepage949
GitHub: https://github.com/codepage949

Name: Chris Hranj
GitHub: https://github.com/Brodan

Name: Ankur Parihar
GitHub: https://github.com/ankurparihar
11 changes: 11 additions & 0 deletions docs/image/sharp-logo-mono.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/image/sharp-logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion docs/image/sharp-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
34 changes: 12 additions & 22 deletions docs/index.html

Large diffs are not rendered by default.

196 changes: 150 additions & 46 deletions docs/install.md
Original file line number Diff line number Diff line change
@@ -10,34 +10,37 @@ yarn add sharp

## Prerequisites

* Node.js v10.16.0+
* Node.js >= 12.13.0

## Prebuilt binaries

Ready-compiled sharp and libvips binaries are provided for use with
Node.js v10.16.0+ on the most common platforms:
Ready-compiled sharp and libvips binaries are provided for use on the most common platforms:

* macOS x64 (>= 10.13)
* Linux x64 (glibc >= 2.17, musl >= 1.1.24)
* Linux ARM64 (glibc >= 2.29)
* Windows
* macOS ARM64
* Linux x64 (glibc >= 2.17, musl >= 1.1.24, CPU with SSE4.2)
* Linux ARM64 (glibc >= 2.17, musl >= 1.1.24)
* Windows x64
* Windows x86

A ~7MB tarball containing libvips and its most commonly used dependencies
is downloaded via HTTPS and stored within `node_modules/sharp/vendor` during `npm install`.
is downloaded via HTTPS, verified via Subresource Integrity
and decompressed into `node_modules/sharp/vendor` during `npm install`.

This provides support for the
JPEG, PNG, WebP, TIFF, GIF (input) and SVG (input) image formats.
JPEG, PNG, WebP, AVIF, TIFF, GIF and SVG (input) image formats.

The following platforms have prebuilt libvips but not sharp:

* Linux ARMv6
* Linux ARMv7 (glibc >= 2.28)
* Linux ARMv6 (glibc >= 2.28)
* Windows ARM64

The following platforms require compilation of both libvips and sharp from source:

* Linux x86
* Linux x64 (glibc <= 2.16, includes RHEL/CentOS 6)
* Linux ARM64 (glibc <= 2.28, musl)
* Linux ARMv7 (glibc <= 2.27, musl)
* Linux ARMv6 (glibc <= 2.27, musl)
* Linux PowerPC
* FreeBSD
* OpenBSD
@@ -46,21 +49,63 @@ The following platforms require compilation of both libvips and sharp from sourc

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.
See the [cross-platform](#cross-platform) section if this is not the case.

The `npm install --unsafe-perm` flag must be used when installing as `root` or a `sudo` user.
When using npm v6 or earlier, the `npm install --unsafe-perm` flag must be used when installing as `root` or a `sudo` user.

When using npm v7 or later, the user running `npm install` must own the directory it is run in.

The `npm install --ignore-scripts=false` flag must be used when `npm` has been configured to ignore installation scripts.

Check the output of running `npm install --verbose sharp` for useful error messages.
Check the output of running `npm install --verbose --foreground-scripts sharp` for useful error messages.

## Apple M1

Prebuilt sharp and libvips binaries have been provided for macOS on ARM64 since sharp v0.29.0.

## Cross-platform

At `npm install` time, prebuilt binaries are automatically selected for the
current OS platform and CPU architecture, where available.

The target platform and/or architecture can be manually selected using the following flags.

```sh
npm install --platform=... --arch=... --arm-version=... sharp
```

* `--platform`: one of `linux`, `darwin` or `win32`.
* `--arch`: one of `x64`, `ia32`, `arm` or `arm64`.
* `--arm-version`: one of `6`, `7` or `8` (`arm` defaults to `6`, `arm64` defaults to `8`).
* `--libc`: one of `glibc` or `musl`. This option only works with platform `linux`, defaults to `glibc`
* `--sharp-install-force`: skip version compatibility and Subresource Integrity checks.

These values can also be set via environment variables,
`npm_config_platform`, `npm_config_arch`, `npm_config_arm_version`, `npm_config_libc`
and `SHARP_INSTALL_FORCE` respectively.

For example, if the target machine has a 64-bit ARM CPU and is running Alpine Linux,
use the following flags:

```sh
npm install --arch=arm64 --platform=linux --libc=musl sharp
```

If the current machine is Alpine Linux and the target machine is Debian Linux on x64 cpu,
use the following flags:

```sh
npm install --arch=x64 --platform=linux --libc=glibc sharp
```

## Custom libvips

To use a custom, globally-installed version of libvips instead of the provided binaries,
make sure it is at least the version listed under `config.libvips` in the `package.json` file
and that it can be located using `pkg-config --modversion vips-cpp`.

For help compiling libvips from source, please see
[https://libvips.github.io/libvips/install.html#building-libvips-from-a-source-tarball](https://libvips.github.io/libvips/install.html#building-libvips-from-a-source-tarball).
For help compiling libvips and its dependencies, please see
[building libvips from source](https://www.libvips.org/install.html#building-libvips-from-source).

The use of a globally-installed libvips is unsupported on Windows.

@@ -69,7 +114,7 @@ The use of a globally-installed libvips is unsupported on Windows.
This module will be compiled from source at `npm install` time when:

* a globally-installed libvips is detected (set the `SHARP_IGNORE_GLOBAL_LIBVIPS` environment variable to skip this),
* prebuilt binaries do not exist for the current platform and Node.js version, or
* prebuilt sharp binaries do not exist for the current platform, or
* when the `npm install --build-from-source` flag is used.

Building from source requires:
@@ -81,16 +126,27 @@ Building from source requires:

This is an advanced approach that most people will not require.

### Prebuilt sharp binaries

To install the prebuilt sharp binaries from a custom URL,
set the `sharp_binary_host` npm config option
or the `npm_config_sharp_binary_host` environment variable.

To install the prebuilt sharp binaries from a directory on the local filesystem,
set the `sharp_local_prebuilds` npm config option
or the `npm_config_sharp_local_prebuilds` environment variable.

### Prebuilt libvips binaries

To install the prebuilt libvips binaries from a custom URL,
set the `sharp_libvips_binary_host` npm config option
or the `npm_config_sharp_libvips_binary_host` environment variable.

The version subpath and file name are appended to these.
To install the prebuilt libvips binaries from a directory on the local filesystem,
set the `sharp_libvips_local_prebuilds` npm config option
or the `npm_config_sharp_libvips_local_prebuilds` environment variable.

The version subpath and file name are appended to these.
For example, if `sharp_libvips_binary_host` is set to `https://hostname/path`
and the libvips version is `1.2.3` then the resultant URL will be
`https://hostname/path/v1.2.3/libvips-1.2.3-platform-arch.tar.br`.
@@ -99,21 +155,21 @@ See the Chinese mirror below for a further example.

## Chinese mirror

Alibaba provide a mirror site based in China containing binaries for both sharp and libvips.
A mirror site based in China, provided by Alibaba, contains binaries for both sharp and libvips.

To use this either set the following configuration:

```sh
npm config set sharp_binary_host "https://npm.taobao.org/mirrors/sharp"
npm config set sharp_libvips_binary_host "https://npm.taobao.org/mirrors/sharp-libvips"
npm config set sharp_binary_host "https://npmmirror.com/mirrors/sharp"
npm config set sharp_libvips_binary_host "https://npmmirror.com/mirrors/sharp-libvips"
npm install sharp
```

or set the following environment variables:

```sh
npm_config_sharp_binary_host="https://npm.taobao.org/mirrors/sharp" \
npm_config_sharp_libvips_binary_host="https://npm.taobao.org/mirrors/sharp-libvips" \
npm_config_sharp_binary_host="https://npmmirror.com/mirrors/sharp" \
npm_config_sharp_libvips_binary_host="https://npmmirror.com/mirrors/sharp-libvips" \
npm install sharp
```

@@ -129,6 +185,23 @@ pkg install -y pkgconf vips
cd /usr/ports/graphics/vips/ && make install clean
```

## Linux memory allocator

The default memory allocator on most glibc-based Linux systems
(e.g. Debian, Red Hat) is unsuitable for long-running, multi-threaded
processes that involve lots of small memory allocations.

For this reason, by default, sharp will limit the use of thread-based
[concurrency](api-utility#concurrency) when the glibc allocator is
detected at runtime.

To help avoid fragmentation and improve performance on these systems,
the use of an alternative memory allocator such as
[jemalloc](https://github.com/jemalloc/jemalloc) is recommended.

Those using musl-based Linux (e.g. Alpine) and non-Linux systems are
unaffected.

## Heroku

Add the
@@ -141,46 +214,77 @@ to `false` when using the `yarn` package manager.

## AWS Lambda

Set the Lambda runtime to `nodejs12.x`.

The binaries in the `node_modules` directory of the
The `node_modules` directory of the
[deployment package](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-package.html)
must be for the Linux x64 platform.
must include binaries for the Linux x64 platform.

On machines other than Linux x64, such as macOS and Windows, run the following:
When building your deployment package on machines other than Linux x64 (glibc),
run the following additional command after `npm install`:

```sh
npm install
rm -rf node_modules/sharp
npm install --arch=x64 --platform=linux sharp
SHARP_IGNORE_GLOBAL_LIBVIPS=1 npm install --arch=x64 --platform=linux --libc=glibc sharp
```

Alternatively a Docker container closely matching the Lambda runtime can be used:
To get the best performance select the largest memory available.
A 1536 MB function provides ~12x more CPU time than a 128 MB function.

```sh
rm -rf node_modules/sharp
docker run -v "$PWD":/var/task lambci/lambda:build-nodejs12.x npm install sharp
## Bundlers

### webpack

Ensure sharp is excluded from bundling via the
[externals](https://webpack.js.org/configuration/externals/)
configuration.

```js
externals: {
'sharp': 'commonjs sharp'
}
```

To get the best performance select the largest memory available.
A 1536 MB function provides ~12x more CPU time than a 128 MB function.
### esbuild

## Electron
Ensure sharp is excluded from bundling via the
[external](https://esbuild.github.io/api/#external)
configuration.

Electron provides versions of the V8 JavaScript engine
that are incompatible with Node.js.
To ensure the correct binaries are used, run the following:
```js
buildSync({
entryPoints: ['app.js'],
bundle: true,
platform: 'node',
external: ['sharp'],
})
```

```sh
npm install
npx electron-rebuild
esbuild app.js --bundle --platform=node --external:sharp
```

Further help can be found at
[https://electronjs.org/docs/tutorial/using-native-node-modules](https://electronjs.org/docs/tutorial/using-native-node-modules)

## Worker threads

The main thread must call `require('sharp')`
before worker threads are created
to ensure shared libraries remain loaded in memory
On some platforms, including glibc-based Linux,
the main thread must call `require('sharp')`
_before_ worker threads are created.
This is to ensure shared libraries remain loaded in memory
until after all threads are complete.

Without this, the following error may occur:
```
Module did not self-register
```

## Known conflicts

### Canvas and Windows

The prebuilt binaries provided by `canvas` for Windows depend on the unmaintained GTK 2, last updated in 2011.

These conflict with the modern, up-to-date binaries provided by sharp.

If both modules are used in the same Windows process, the following error will occur:
```
The specified procedure could not be found.
```
38 changes: 21 additions & 17 deletions docs/performance.md
Original file line number Diff line number Diff line change
@@ -4,11 +4,13 @@ A test to benchmark the performance of this module relative to alternatives.

## The contenders

* [jimp](https://www.npmjs.com/package/jimp) v0.16.0 - Image processing in pure JavaScript. Provides bicubic interpolation.
* [mapnik](https://www.npmjs.org/package/mapnik) v4.5.2 - Whilst primarily a map renderer, Mapnik contains bitmap image utilities.
* [jimp](https://www.npmjs.com/package/jimp) v0.16.1 - Image processing in pure JavaScript. Provides bicubic interpolation.
* [mapnik](https://www.npmjs.org/package/mapnik) v4.5.9 - Whilst primarily a map renderer, Mapnik contains bitmap image utilities.
* [imagemagick](https://www.npmjs.com/package/imagemagick) v0.1.3 - Supports filesystem only and "*has been unmaintained for a long time*".
* [gm](https://www.npmjs.com/package/gm) v1.23.1 - Fully featured wrapper around GraphicsMagick's `gm` command line utility.
* sharp v0.26.0 / libvips v8.10.0 - Caching within libvips disabled to ensure a fair comparison.
* [@squoosh/lib](https://www.npmjs.com/package/@squoosh/lib) v0.4.0 - Image libraries transpiled to WebAssembly, includes GPLv3 code.
* [@squoosh/cli](https://www.npmjs.com/package/@squoosh/cli) v0.7.2 - Command line wrapper around `@squoosh/lib`, avoids GPLv3 by spawning process.
* sharp v0.30.0 / libvips v8.12.2 - Caching within libvips disabled to ensure a fair comparison.

## The task

@@ -18,25 +20,27 @@ then compress to JPEG at a "quality" setting of 80.

## Test environment

* AWS EC2 eu-west-1 [c5d.large](https://aws.amazon.com/ec2/instance-types/c5/) (2x Xeon Platinum 8124M CPU @ 3.00GHz)
* Ubuntu 20.04 (ami-0f1d11c92a9467c07)
* Node.js v14.8.0
* AWS EC2 eu-west-1 [c5ad.xlarge](https://aws.amazon.com/ec2/instance-types/c5/) (4x AMD EPYC 7R32)
* Ubuntu 21.10 (ami-0258eeb71ddf238b3)
* Node.js 16.13.2

## Results

| Module | Input | Output | Ops/sec | Speed-up |
| :----------------- | :----- | :----- | ------: | -------: |
| jimp | buffer | buffer | 0.75 | 1.0 |
| mapnik | buffer | buffer | 3.00 | 4.0 |
| gm | buffer | buffer | 4.12 | 5.5 |
| gm | file | file | 4.13 | 5.5 |
| imagemagick | file | file | 4.30 | 5.7 |
| sharp | stream | stream | 22.37 | 29.8 |
| sharp | file | file | 23.40 | 31.2 |
| sharp | buffer | buffer | 24.01 | 32.0 |
| jimp | buffer | buffer | 0.84 | 1.0 |
| squoosh-cli | file | file | 1.08 | 1.3 |
| squoosh-lib | buffer | buffer | 1.85 | 2.2 |
| mapnik | buffer | buffer | 3.45 | 4.1 |
| gm | buffer | buffer | 8.60 | 10.2 |
| gm | file | file | 8.66 | 10.3 |
| imagemagick | file | file | 8.79 | 10.5 |
| sharp | stream | stream | 28.90 | 34.4 |
| sharp | file | file | 30.08 | 35.8 |
| sharp | buffer | buffer | 30.42 | 36.2 |

Greater libvips performance can be expected with caching enabled (default)
and using 4+ core machines, especially those with larger L1/L2 CPU caches.
and using 8+ core machines, especially those with larger L1/L2 CPU caches.

The I/O limits of the relevant (de)compression library will generally determine maximum throughput.

@@ -51,7 +55,7 @@ brew install mapnik
```

```sh
sudo apt-get install imagemagick libmagick++-dev graphicsmagick libmapnik-dev
sudo apt-get install build-essential imagemagick libmagick++-dev graphicsmagick libmapnik-dev
```

```sh
@@ -61,7 +65,7 @@ sudo yum install ImageMagick-devel ImageMagick-c++-devel GraphicsMagick mapnik-d
```sh
git clone https://github.com/lovell/sharp.git
cd sharp
npm install
npm install --build-from-source
cd test/bench
npm install
npm test
2 changes: 1 addition & 1 deletion docs/search-index.json

Large diffs are not rendered by default.

18 changes: 10 additions & 8 deletions docs/search-index/build.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
'use strict';

const fs = require('fs');
const { extractDescription, extractKeywords } = require('./extract');
const path = require('path');
const { extractDescription, extractKeywords, extractParameters } = require('./extract');

const searchIndex = [];

// Install
const contents = fs.readFileSync(`${__dirname}/../install.md`, 'utf8');
const contents = fs.readFileSync(path.join(__dirname, '..', 'install.md'), 'utf8');
const matches = contents.matchAll(
/## (?<title>[A-Za-z ]+)\n\n(?<body>[^#]+)/gs
/## (?<title>[A-Za-z0-9 ]+)\n\n(?<body>[^#]+)/gs
);
for (const match of matches) {
const { title, body } = match.groups;
@@ -34,26 +35,27 @@ for (const match of matches) {
'colour',
'utility'
].forEach((section) => {
const contents = fs.readFileSync(`${__dirname}/../api-${section}.md`, 'utf8');
const contents = fs.readFileSync(path.join(__dirname, '..', `api-${section}.md`), 'utf8');
const matches = contents.matchAll(
/\n## (?<title>[A-Za-z]+)\n\n(?<firstparagraph>.+?)\n\n/gs
/\n## (?<title>[A-Za-z]+)\n\n(?<firstparagraph>.+?)\n\n(?<parameters>### Parameters.+?Returns)?/gs
);
for (const match of matches) {
const { title, firstparagraph } = match.groups;
const { title, firstparagraph, parameters } = match.groups;
const description = firstparagraph.startsWith('###')
? 'Constructor'
: extractDescription(firstparagraph);
const parameterNames = parameters ? extractParameters(parameters) : '';

searchIndex.push({
t: title,
d: description,
k: extractKeywords(`${title} ${description}`),
k: extractKeywords(`${title} ${description} ${parameterNames}`),
l: `/api-${section}#${title.toLowerCase()}`
});
}
});

fs.writeFileSync(
`${__dirname}/../search-index.json`,
path.join(__dirname, '..', 'search-index.json'),
JSON.stringify(searchIndex)
);
84 changes: 17 additions & 67 deletions docs/search-index/extract.js
Original file line number Diff line number Diff line change
@@ -1,80 +1,30 @@
'use strict';

const stopWords = [
'a',
'about',
'all',
'already',
'always',
'an',
'and',
'any',
'are',
'as',
'at',
'be',
'been',
'by',
'can',
'do',
'does',
'each',
'either',
'etc',
'for',
'from',
'get',
'gets',
'has',
'have',
'how',
'if',
'in',
'is',
'it',
'its',
'may',
'more',
'much',
'no',
'not',
'of',
'on',
'or',
'over',
'set',
'sets',
'should',
'that',
'the',
'their',
'there',
'therefore',
'these',
'this',
'to',
'use',
'using',
'when',
'which',
'will',
'with'
];
const stopWords = require('./stop-words');

const extractDescription = (str) =>
str
.replace(/### Examples.*/sg, '')
.replace(/\(http[^)]+/g, '')
.replace(/\s+/g, ' ')
.replace(/[^A-Za-z0-9_/\-,. ]/g, '')
.replace(/\s+/g, ' ')
.substr(0, 140)
.substring(0, 200)
.trim();

const extractKeywords = (str) =>
str
.split(/[ -/]/)
.map((word) => word.toLowerCase().replace(/[^a-z]/g, ''))
.filter((word) => word.length > 2 && !stopWords.includes(word))
const extractParameters = (str) =>
[...str.matchAll(/options\.(?<name>[^.`]+)/gs)]
.map((match) => match.groups.name)
.join(' ');

module.exports = { extractDescription, extractKeywords };
const extractKeywords = (str) =>
[
...new Set(
str
.split(/[ -/]/)
.map((word) => word.toLowerCase().replace(/[^a-z]/g, ''))
.filter((word) => word.length > 2 && word.length < 15 && !stopWords.includes(word))
)
].join(' ');

module.exports = { extractDescription, extractKeywords, extractParameters };
120 changes: 120 additions & 0 deletions docs/search-index/stop-words.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
'use strict';

module.exports = [
'about',
'after',
'all',
'allows',
'already',
'also',
'alternative',
'always',
'and',
'any',
'are',
'based',
'been',
'before',
'both',
'call',
'callback',
'can',
'containing',
'contains',
'created',
'current',
'date',
'default',
'does',
'each',
'either',
'ensure',
'entirely',
'etc',
'every',
'except',
'following',
'for',
'from',
'get',
'gets',
'given',
'has',
'have',
'how',
'image',
'implies',
'include',
'including',
'involve',
'its',
'last',
'least',
'lots',
'make',
'may',
'more',
'most',
'much',
'must',
'non',
'not',
'occur',
'occurs',
'options',
'other',
'out',
'over',
'perform',
'performs',
'produce',
'provide',
'provided',
'ready',
'requires',
'requiresharp',
'returned',
'same',
'see',
'set',
'sets',
'should',
'since',
'site',
'some',
'specified',
'spelling',
'such',
'support',
'supported',
'sure',
'take',
'task',
'than',
'that',
'the',
'their',
'then',
'there',
'therefore',
'these',
'this',
'under',
'unless',
'unmaintained',
'unsuitable',
'until',
'use',
'used',
'using',
'value',
'values',
'were',
'when',
'which',
'while',
'will',
'with',
'without',
'you'
];
11 changes: 11 additions & 0 deletions install/can-compile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
'use strict';

const libvips = require('../lib/libvips');

try {
if (!(libvips.useGlobalLibvips() || libvips.hasVendoredLibvips())) {
process.exitCode = 1;
}
} catch (err) {
process.exitCode = 1;
}
20 changes: 10 additions & 10 deletions install/dll-copy.js
Original file line number Diff line number Diff line change
@@ -4,21 +4,20 @@ const fs = require('fs');
const path = require('path');

const libvips = require('../lib/libvips');
const npmLog = require('npmlog');
const platform = require('../lib/platform');

const minimumLibvipsVersion = libvips.minimumLibvipsVersion;

const platform = process.env.npm_config_platform || process.platform;
if (platform === 'win32') {
const buildDir = path.join(__dirname, '..', 'build');
const buildReleaseDir = path.join(buildDir, 'Release');
npmLog.info('sharp', `Creating ${buildReleaseDir}`);
const platformAndArch = platform();

if (platformAndArch.startsWith('win32')) {
const buildReleaseDir = path.join(__dirname, '..', 'build', 'Release');
libvips.log(`Creating ${buildReleaseDir}`);
try {
libvips.mkdirSync(buildDir);
libvips.mkdirSync(buildReleaseDir);
} catch (err) {}
const vendorLibDir = path.join(__dirname, '..', 'vendor', minimumLibvipsVersion, 'lib');
npmLog.info('sharp', `Copying DLLs from ${vendorLibDir} to ${buildReleaseDir}`);
const vendorLibDir = path.join(__dirname, '..', 'vendor', minimumLibvipsVersion, platformAndArch, 'lib');
libvips.log(`Copying DLLs from ${vendorLibDir} to ${buildReleaseDir}`);
try {
fs
.readdirSync(vendorLibDir)
@@ -32,6 +31,7 @@ if (platform === 'win32') {
);
});
} catch (err) {
npmLog.error('sharp', err.message);
libvips.log(err);
process.exit(1);
}
}
171 changes: 128 additions & 43 deletions install/libvips.js
Original file line number Diff line number Diff line change
@@ -5,10 +5,11 @@ const os = require('os');
const path = require('path');
const stream = require('stream');
const zlib = require('zlib');
const { createHash } = require('crypto');

const detectLibc = require('detect-libc');
const npmLog = require('npmlog');
const semver = require('semver');
const semverLessThan = require('semver/functions/lt');
const semverSatisfies = require('semver/functions/satisfies');
const simpleGet = require('simple-get');
const tarFs = require('tar-fs');

@@ -18,37 +19,91 @@ const platform = require('../lib/platform');

const minimumGlibcVersionByArch = {
arm: '2.28',
arm64: '2.29',
arm64: '2.17',
x64: '2.17'
};

const hasSharpPrebuild = [
'darwin-x64',
'darwin-arm64',
'linux-arm64',
'linux-x64',
'linuxmusl-x64',
'linuxmusl-arm64',
'win32-ia32',
'win32-x64'
];

const { minimumLibvipsVersion, minimumLibvipsVersionLabelled } = libvips;
const localLibvipsDir = process.env.npm_config_sharp_libvips_local_prebuilds || '';
const distHost = process.env.npm_config_sharp_libvips_binary_host || 'https://github.com/lovell/sharp-libvips/releases/download';
const distBaseUrl = process.env.npm_config_sharp_dist_base_url || process.env.SHARP_DIST_BASE_URL || `${distHost}/v${minimumLibvipsVersionLabelled}/`;
const installationForced = !!(process.env.npm_config_sharp_install_force || process.env.SHARP_INSTALL_FORCE);

const fail = function (err) {
npmLog.error('sharp', err.message);
libvips.log(err);
if (err.code === 'EACCES') {
npmLog.info('sharp', 'Are you trying to install as a root or sudo user? Try again with the --unsafe-perm flag');
libvips.log('Are you trying to install as a root or sudo user?');
libvips.log('- For npm <= v6, try again with the "--unsafe-perm" flag');
libvips.log('- For npm >= v8, the user must own the directory "npm install" is run in');
}
npmLog.info('sharp', 'Attempting to build from source via node-gyp but this may fail due to the above error');
npmLog.info('sharp', 'Please see https://sharp.pixelplumbing.com/install for required dependencies');
libvips.log('Please see https://sharp.pixelplumbing.com/install for required dependencies');
process.exit(1);
};

const extractTarball = function (tarPath) {
const vendorPath = path.join(__dirname, '..', 'vendor');
libvips.mkdirSync(vendorPath);
const versionedVendorPath = path.join(vendorPath, minimumLibvipsVersion);
const handleError = function (err) {
if (installationForced) {
libvips.log(`Installation warning: ${err.message}`);
} else {
throw err;
}
};

const verifyIntegrity = function (platformAndArch) {
const expected = libvips.integrity(platformAndArch);
if (installationForced || !expected) {
libvips.log(`Integrity check skipped for ${platformAndArch}`);
return new stream.PassThrough();
}
const hash = createHash('sha512');
return new stream.Transform({
transform: function (chunk, _encoding, done) {
hash.update(chunk);
done(null, chunk);
},
flush: function (done) {
const digest = `sha512-${hash.digest('base64')}`;
if (expected !== digest) {
libvips.removeVendoredLibvips();
libvips.log(`Integrity expected: ${expected}`);
libvips.log(`Integrity received: ${digest}`);
done(new Error(`Integrity check failed for ${platformAndArch}`));
} else {
libvips.log(`Integrity check passed for ${platformAndArch}`);
done();
}
}
});
};

const extractTarball = function (tarPath, platformAndArch) {
const versionedVendorPath = path.join(__dirname, '..', 'vendor', minimumLibvipsVersion, platformAndArch);
libvips.mkdirSync(versionedVendorPath);

const ignoreVendorInclude = hasSharpPrebuild.includes(platformAndArch) && !process.env.npm_config_build_from_source;
const ignore = function (name) {
return ignoreVendorInclude && name.includes('include/');
};

stream.pipeline(
fs.createReadStream(tarPath),
verifyIntegrity(platformAndArch),
new zlib.BrotliDecompress(),
tarFs.extract(versionedVendorPath),
tarFs.extract(versionedVendorPath, { ignore }),
function (err) {
if (err) {
if (/unexpected end of file/.test(err.message)) {
npmLog.error('sharp', `Please delete ${tarPath} as it is not a valid tarball`);
fail(new Error(`Please delete ${tarPath} as it is not a valid tarball`));
}
fail(err);
}
@@ -58,45 +113,60 @@ const extractTarball = function (tarPath) {

try {
const useGlobalLibvips = libvips.useGlobalLibvips();

if (useGlobalLibvips) {
const globalLibvipsVersion = libvips.globalLibvipsVersion();
npmLog.info('sharp', `Detected globally-installed libvips v${globalLibvipsVersion}`);
npmLog.info('sharp', 'Building from source via node-gyp');
libvips.log(`Detected globally-installed libvips v${globalLibvipsVersion}`);
libvips.log('Building from source via node-gyp');
process.exit(1);
} else if (libvips.hasVendoredLibvips()) {
npmLog.info('sharp', `Using existing vendored libvips v${minimumLibvipsVersion}`);
libvips.log(`Using existing vendored libvips v${minimumLibvipsVersion}`);
} else {
// Is this arch/platform supported?
const arch = process.env.npm_config_arch || process.arch;
const platformAndArch = platform();
if (arch === 'ia32' && !platformAndArch.startsWith('win32')) {
throw new Error(`Intel Architecture 32-bit systems require manual installation of libvips >= ${minimumLibvipsVersion}`);
}
if (platformAndArch === 'darwin-arm64') {
throw new Error("Please run 'brew install vips' to install libvips on Apple M1 (ARM64) systems");
}
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) {
if (semver.lt(`${detectLibc.version}.0`, `${minimumGlibcVersionByArch[arch]}.0`)) {
throw new Error(`Use with glibc ${detectLibc.version} requires manual installation of libvips >= ${minimumLibvipsVersion}`);
// Linux libc version check
const libcFamily = detectLibc.familySync();
const libcVersion = detectLibc.versionSync();
if (libcFamily === detectLibc.GLIBC && libcVersion && minimumGlibcVersionByArch[arch]) {
if (semverLessThan(`${libcVersion}.0`, `${minimumGlibcVersionByArch[arch]}.0`)) {
handleError(new Error(`Use with glibc ${libcVersion} requires manual installation of libvips >= ${minimumLibvipsVersion}`));
}
}

if (libcFamily === detectLibc.MUSL && libcVersion) {
if (semverLessThan(libcVersion, '1.1.24')) {
handleError(new Error(`Use with musl ${libcVersion} requires manual installation of libvips >= ${minimumLibvipsVersion}`));
}
}
// Node.js minimum version check
const supportedNodeVersion = process.env.npm_package_engines_node || require('../package.json').engines.node;
if (!semver.satisfies(process.versions.node, supportedNodeVersion)) {
throw new Error(`Expected Node.js version ${supportedNodeVersion} but found ${process.versions.node}`);
if (!semverSatisfies(process.versions.node, supportedNodeVersion)) {
handleError(new Error(`Expected Node.js version ${supportedNodeVersion} but found ${process.versions.node}`));
}

// Download to per-process temporary file
const tarFilename = ['libvips', minimumLibvipsVersion, platformAndArch].join('-') + '.tar.br';
const tarFilename = ['libvips', minimumLibvipsVersionLabelled, platformAndArch].join('-') + '.tar.br';
const tarPathCache = path.join(libvips.cachePath(), tarFilename);
if (fs.existsSync(tarPathCache)) {
npmLog.info('sharp', `Using cached ${tarPathCache}`);
extractTarball(tarPathCache);
libvips.log(`Using cached ${tarPathCache}`);
extractTarball(tarPathCache, platformAndArch);
} else if (localLibvipsDir) {
// If localLibvipsDir is given try to use binaries from local directory
const tarPathLocal = path.join(path.resolve(localLibvipsDir), `v${minimumLibvipsVersionLabelled}`, tarFilename);
libvips.log(`Using local libvips from ${tarPathLocal}`);
extractTarball(tarPathLocal, platformAndArch);
} else {
const tarPathTemp = path.join(os.tmpdir(), `${process.pid}-${tarFilename}`);
const tmpFile = fs.createWriteStream(tarPathTemp);
const url = distBaseUrl + tarFilename;
npmLog.info('sharp', `Downloading ${url}`);
libvips.log(`Downloading ${url}`);
simpleGet({ url: url, agent: agent() }, function (err, response) {
if (err) {
fail(err);
@@ -105,24 +175,39 @@ try {
} else if (response.statusCode !== 200) {
fail(new Error(`Status ${response.statusCode} ${response.statusMessage}`));
} else {
const tarPathTemp = path.join(os.tmpdir(), `${process.pid}-${tarFilename}`);
const tmpFileStream = fs.createWriteStream(tarPathTemp);
response
.on('error', fail)
.pipe(tmpFile);
.on('error', function (err) {
tmpFileStream.destroy(err);
})
.on('close', function () {
if (!response.complete) {
tmpFileStream.destroy(new Error('Download incomplete (connection was terminated)'));
}
})
.pipe(tmpFileStream);
tmpFileStream
.on('error', function (err) {
// Clean up temporary file
try {
fs.unlinkSync(tarPathTemp);
} catch (e) {}
fail(err);
})
.on('close', function () {
try {
// Attempt to rename
fs.renameSync(tarPathTemp, tarPathCache);
} catch (err) {
// Fall back to copy and unlink
fs.copyFileSync(tarPathTemp, tarPathCache);
fs.unlinkSync(tarPathTemp);
}
extractTarball(tarPathCache, platformAndArch);
});
}
});
tmpFile
.on('error', fail)
.on('close', function () {
try {
// Attempt to rename
fs.renameSync(tarPathTemp, tarPathCache);
} catch (err) {
// Fall back to copy and unlink
fs.copyFileSync(tarPathTemp, tarPathCache);
fs.unlinkSync(tarPathTemp);
}
extractTarball(tarPathCache);
});
}
}
} catch (err) {
12 changes: 0 additions & 12 deletions install/prebuild-ci.js

This file was deleted.

2 changes: 1 addition & 1 deletion lib/agent.js
Original file line number Diff line number Diff line change
@@ -25,7 +25,7 @@ module.exports = function () {
? tunnelAgent.httpsOverHttps
: tunnelAgent.httpsOverHttp;
const proxyAuth = proxy.username && proxy.password
? `${proxy.username}:${proxy.password}`
? `${decodeURIComponent(proxy.username)}:${decodeURIComponent(proxy.password)}`
: null;
return tunnel({
proxy: {
54 changes: 39 additions & 15 deletions lib/channel.js
Original file line number Diff line number Diff line change
@@ -15,6 +15,8 @@ const bool = {
/**
* Remove alpha channel, if any. This is a no-op if the image does not have an alpha channel.
*
* See also {@link /api-operation#flatten|flatten}.
*
* @example
* sharp('rgba.png')
* .removeAlpha()
@@ -30,35 +32,57 @@ function removeAlpha () {
}

/**
* Ensure alpha channel, if missing. The added alpha channel will be fully opaque. This is a no-op if the image already has an alpha channel.
* Ensure the output image has an alpha transparency channel.
* If missing, the added alpha channel will have the specified
* transparency level, defaulting to fully-opaque (1).
* This is a no-op if the image already has an alpha channel.
*
* @since 0.21.2
*
* @example
* sharp('rgb.jpg')
* // rgba.png will be a 4 channel image with a fully-opaque alpha channel
* await sharp('rgb.jpg')
* .ensureAlpha()
* .toFile('rgba.png', function(err, info) {
* // rgba.png is a 4 channel image with a fully opaque alpha channel
* });
* .toFile('rgba.png')
*
* @example
* // rgba is a 4 channel image with a fully-transparent alpha channel
* const rgba = await sharp(rgb)
* .ensureAlpha(0)
* .toBuffer();
*
* @param {number} [alpha=1] - alpha transparency level (0=fully-transparent, 1=fully-opaque)
* @returns {Sharp}
* @throws {Error} Invalid alpha transparency level
*/
function ensureAlpha () {
this.options.ensureAlpha = true;
function ensureAlpha (alpha) {
if (is.defined(alpha)) {
if (is.number(alpha) && is.inRange(alpha, 0, 1)) {
this.options.ensureAlpha = alpha;
} else {
throw is.invalidParameterError('alpha', 'number between 0 and 1', alpha);
}
} else {
this.options.ensureAlpha = 1;
}
return this;
}

/**
* Extract a single channel from a multi-channel image.
*
* @example
* sharp(input)
* // green.jpg is a greyscale image containing the green channel of the input
* await sharp(input)
* .extractChannel('green')
* .toColourspace('b-w')
* .toFile('green.jpg', function(err, info) {
* // info.channels === 1
* // green.jpg is a greyscale image containing the green channel of the input
* });
* .toFile('green.jpg');
*
* @example
* // red1 is the red value of the first pixel, red2 the second pixel etc.
* const [red1, red2, ...] = await sharp(input)
* .extractChannel(0)
* .raw()
* .toBuffer();
*
* @param {number|string} channel - zero-indexed channel/band number to extract, or `red`, `green`, `blue` or `alpha`.
* @returns {Sharp}
@@ -74,7 +98,7 @@ function extractChannel (channel) {
} else {
throw is.invalidParameterError('channel', 'integer or one of: red, green, blue, alpha', channel);
}
return this;
return this.toColourspace('b-w');
}

/**
@@ -85,7 +109,7 @@ function extractChannel (channel) {
* - sRGB: 0: Red, 1: Green, 2: Blue, 3: Alpha.
* - CMYK: 0: Magenta, 1: Cyan, 2: Yellow, 3: Black, 4: Alpha.
*
* Buffers may be any of the image formats supported by sharp: JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data.
* Buffers may be any of the image formats supported by sharp.
* For raw pixel input, the `options` object should contain a `raw` attribute, which follows the format of the attribute of the same name in the `sharp()` constructor.
*
* @param {Array<string|Buffer>|string|Buffer} images - one or more images (file paths, Buffers).
59 changes: 58 additions & 1 deletion lib/colour.js
Original file line number Diff line number Diff line change
@@ -19,6 +19,11 @@ const colourspace = {
* Tint the image using the provided chroma while preserving the image luminance.
* An alpha channel may be present and will be unchanged by the operation.
*
* @example
* const output = await sharp(input)
* .tint({ r: 255, g: 240, b: 16 })
* .toBuffer();
*
* @param {string|Object} rgb - parsed by the [color](https://www.npmjs.org/package/color) module to extract chroma values.
* @returns {Sharp}
* @throws {Error} Invalid parameter
@@ -37,6 +42,10 @@ function tint (rgb) {
* This may be overridden by other sharp operations such as `toColourspace('b-w')`,
* which will produce an output image containing one color channel.
* An alpha channel may be present, and will be unchanged by the operation.
*
* @example
* const output = await sharp(input).greyscale().toBuffer();
*
* @param {Boolean} [greyscale=true]
* @returns {Sharp}
*/
@@ -54,10 +63,56 @@ function grayscale (grayscale) {
return this.greyscale(grayscale);
}

/**
* Set the pipeline colourspace.
*
* The input image will be converted to the provided colourspace at the start of the pipeline.
* All operations will use this colourspace before converting to the output colourspace, as defined by {@link toColourspace}.
*
* This feature is experimental and has not yet been fully-tested with all operations.
*
* @since 0.29.0
*
* @example
* // Run pipeline in 16 bits per channel RGB while converting final result to 8 bits per channel sRGB.
* await sharp(input)
* .pipelineColourspace('rgb16')
* .toColourspace('srgb')
* .toFile('16bpc-pipeline-to-8bpc-output.png')
*
* @param {string} [colourspace] - pipeline colourspace e.g. `rgb16`, `scrgb`, `lab`, `grey16` [...](https://github.com/libvips/libvips/blob/41cff4e9d0838498487a00623462204eb10ee5b8/libvips/iofuncs/enumtypes.c#L774)
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
function pipelineColourspace (colourspace) {
if (!is.string(colourspace)) {
throw is.invalidParameterError('colourspace', 'string', colourspace);
}
this.options.colourspaceInput = colourspace;
return this;
}

/**
* Alternative spelling of `pipelineColourspace`.
* @param {string} [colorspace] - pipeline colorspace.
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
function pipelineColorspace (colorspace) {
return this.pipelineColourspace(colorspace);
}

/**
* Set the output colourspace.
* By default output image will be web-friendly sRGB, with additional channels interpreted as alpha channels.
* @param {string} [colourspace] - output colourspace e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://github.com/libvips/libvips/blob/master/libvips/iofuncs/enumtypes.c#L568)
*
* @example
* // Output 16 bits per pixel RGB
* await sharp(input)
* .toColourspace('rgb16')
* .toFile('16-bpp.png')
*
* @param {string} [colourspace] - output colourspace e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://github.com/libvips/libvips/blob/3c0bfdf74ce1dc37a6429bed47fa76f16e2cd70a/libvips/iofuncs/enumtypes.c#L777-L794)
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
@@ -112,6 +167,8 @@ module.exports = function (Sharp) {
tint,
greyscale,
grayscale,
pipelineColourspace,
pipelineColorspace,
toColourspace,
toColorspace,
// Private
38 changes: 29 additions & 9 deletions lib/composite.js
Original file line number Diff line number Diff line change
@@ -50,12 +50,27 @@ const blend = {
* `hard-light`, `soft-light`, `difference`, `exclusion`.
*
* More information about blend modes can be found at
* https://libvips.github.io/libvips/API/current/libvips-conversion.html#VipsBlendMode
* https://www.libvips.org/API/current/libvips-conversion.html#VipsBlendMode
* and https://www.cairographics.org/operators/
*
* @since 0.22.0
*
* @example
* await sharp(background)
* .composite([
* { input: layer1, gravity: 'northwest' },
* { input: layer2, gravity: 'southeast' },
* ])
* .toFile('combined.png');
*
* @example
* const output = await sharp('input.gif', { animated: true })
* .composite([
* { input: 'overlay.png', tile: true, blend: 'saturate' }
* ])
* .toBuffer();
*
* @example
* sharp('input.png')
* .rotate(180)
* .resize(300)
@@ -89,6 +104,9 @@ const blend = {
* @param {Number} [images[].raw.width]
* @param {Number} [images[].raw.height]
* @param {Number} [images[].raw.channels]
* @param {boolean} [images[].animated=false] - Set to `true` to read all frames/pages of an animated image.
* @param {string} [images[].failOn='warning'] - @see {@link /api-constructor#parameters|constructor parameters}
* @param {number|boolean} [images[].limitInputPixels=268402689] - @see {@link /api-constructor#parameters|constructor parameters}
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
@@ -105,8 +123,9 @@ function composite (images) {
input: this._createInputDescriptor(image.input, inputOptions, { allowStream: false }),
blend: 'over',
tile: false,
left: -1,
top: -1,
left: 0,
top: 0,
hasOffset: false,
gravity: 0,
premultiplied: false
};
@@ -125,21 +144,23 @@ function composite (images) {
}
}
if (is.defined(image.left)) {
if (is.integer(image.left) && image.left >= 0) {
if (is.integer(image.left)) {
composite.left = image.left;
} else {
throw is.invalidParameterError('left', 'positive integer', image.left);
throw is.invalidParameterError('left', 'integer', image.left);
}
}
if (is.defined(image.top)) {
if (is.integer(image.top) && image.top >= 0) {
if (is.integer(image.top)) {
composite.top = image.top;
} else {
throw is.invalidParameterError('top', 'positive integer', image.top);
throw is.invalidParameterError('top', 'integer', image.top);
}
}
if (composite.left !== composite.top && Math.min(composite.left, composite.top) === -1) {
if (is.defined(image.top) !== is.defined(image.left)) {
throw new Error('Expected both left and top to be set');
} else {
composite.hasOffset = is.integer(image.top) && is.integer(image.left);
}
if (is.defined(image.gravity)) {
if (is.integer(image.gravity) && is.inRange(image.gravity, 0, 8)) {
@@ -157,7 +178,6 @@ function composite (images) {
throw is.invalidParameterError('premultiplied', 'boolean', image.premultiplied);
}
}

return composite;
});
return this;
153 changes: 98 additions & 55 deletions lib/constructor.js
Original file line number Diff line number Diff line change
@@ -5,42 +5,15 @@ const stream = require('stream');
const is = require('./is');

require('./libvips').hasVendoredLibvips();

/* istanbul ignore next */
try {
require('../build/Release/sharp.node');
} catch (err) {
// Bail early if bindings aren't available
const help = ['', 'Something went wrong installing the "sharp" module', '', err.message, ''];
if (/NODE_MODULE_VERSION/.test(err.message)) {
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 {
help.push(
'- Remove the "node_modules/sharp" directory then run',
' "npm install --ignore-scripts=false --verbose" and look for errors'
);
}
help.push(
'- Consult the installation documentation at https://sharp.pixelplumbing.com/install',
'- Search for this error at https://github.com/lovell/sharp/issues', ''
);
const error = help.join('\n');
throw new Error(error);
}
require('./sharp');

// Use NODE_DEBUG=sharp to enable libvips warnings
const debuglog = util.debuglog('sharp');

/**
* Constructor factory to create an instance of `sharp`, to which further methods are chained.
*
* JPEG, PNG, WebP or TIFF format image data can be streamed out from this object.
* JPEG, PNG, WebP, GIF, AVIF or TIFF format image data can be streamed out from this object.
* When using Stream based output, derived attributes are available from the `info` event.
*
* Non-critical problems encountered during processing are emitted as `warning` events.
@@ -90,32 +63,69 @@ const debuglog = util.debuglog('sharp');
* // Convert an animated GIF to an animated WebP
* await sharp('in.gif', { animated: true }).toFile('out.webp');
*
* @param {(Buffer|string)} [input] - if present, can be
* a Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or
* a String containing the filesystem path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file.
* JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present.
* @example
* // Read a raw array of pixels and save it to a png
* const input = Uint8Array.from([255, 255, 255, 0, 0, 0]); // or Uint8ClampedArray
* const image = sharp(input, {
* // because the input does not contain its dimensions or how many channels it has
* // we need to specify it in the constructor options
* raw: {
* width: 2,
* height: 1,
* channels: 3
* }
* });
* await image.toFile('my-two-pixels.png');
*
* @example
* // Generate RGB Gaussian noise
* await sharp({
* create: {
* width: 300,
* height: 200,
* channels: 3,
* noise: {
* type: 'gaussian',
* mean: 128,
* sigma: 30
* }
* }
* }).toFile('noise.png');
*
* @param {(Buffer|Uint8Array|Uint8ClampedArray|Int8Array|Uint16Array|Int16Array|Uint32Array|Int32Array|Float32Array|Float64Array|string)} [input] - if present, can be
* a Buffer / Uint8Array / Uint8ClampedArray containing JPEG, PNG, WebP, AVIF, GIF, SVG or TIFF image data, or
* a TypedArray containing raw pixel image data, or
* a String containing the filesystem path to an JPEG, PNG, WebP, AVIF, GIF, SVG or TIFF image file.
* JPEG, PNG, WebP, AVIF, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present.
* @param {Object} [options] - if present, is an Object with optional attributes.
* @param {boolean} [options.failOnError=true] - by default halt processing and raise an error when loading invalid images.
* Set this flag to `false` if you'd rather apply a "best effort" to decode images, even if the data is corrupt or invalid.
* @param {string} [options.failOn='warning'] - level of sensitivity to invalid images, one of (in order of sensitivity): 'none' (least), 'truncated', 'error' or 'warning' (most), highers level imply lower levels.
* @param {number|boolean} [options.limitInputPixels=268402689] - Do not process input images where the number of pixels
* (width x height) exceeds this limit. Assumes image dimensions contained in the input metadata can be trusted.
* An integral Number of pixels, zero or false to remove limit, true to use default limit of 268402689 (0x3FFF x 0x3FFF).
* @param {boolean} [options.unlimited=false] - Set this to `true` to remove safety features that help prevent memory exhaustion (SVG, PNG).
* @param {boolean} [options.sequentialRead=false] - Set this to `true` to use sequential rather than random access where possible.
* This can reduce memory usage and might improve performance on some systems.
* @param {number} [options.density=72] - number representing the DPI for vector images in the range 1 to 100000.
* @param {number} [options.pages=1] - number of pages to extract for multi-page input (GIF, TIFF, PDF), use -1 for all pages.
* @param {number} [options.page=0] - page number to start extracting from for multi-page input (GIF, TIFF, PDF), zero based.
* @param {number} [options.pages=1] - number of pages to extract for multi-page input (GIF, WebP, AVIF, TIFF, PDF), use -1 for all pages.
* @param {number} [options.page=0] - page number to start extracting from for multi-page input (GIF, WebP, AVIF, TIFF, PDF), zero based.
* @param {number} [options.subifd=-1] - subIFD (Sub Image File Directory) to extract for OME-TIFF, defaults to main image.
* @param {number} [options.level=0] - level to extract from a multi-level input (OpenSlide), zero based.
* @param {boolean} [options.animated=false] - Set to `true` to read all frames/pages of an animated image (equivalent of setting `pages` to `-1`).
* @param {Object} [options.raw] - describes raw pixel input image data. See `raw()` for pixel ordering.
* @param {number} [options.raw.width]
* @param {number} [options.raw.height]
* @param {number} [options.raw.channels] - 1-4
* @param {number} [options.raw.width] - integral number of pixels wide.
* @param {number} [options.raw.height] - integral number of pixels high.
* @param {number} [options.raw.channels] - integral number of channels, between 1 and 4.
* @param {boolean} [options.raw.premultiplied] - specifies that the raw input has already been premultiplied, set to `true`
* to avoid sharp premultiplying the image. (optional, default `false`)
* @param {Object} [options.create] - describes a new image to be created.
* @param {number} [options.create.width]
* @param {number} [options.create.height]
* @param {number} [options.create.channels] - 3-4
* @param {number} [options.create.width] - integral number of pixels wide.
* @param {number} [options.create.height] - integral number of pixels high.
* @param {number} [options.create.channels] - integral number of channels, either 3 (RGB) or 4 (RGBA).
* @param {string|Object} [options.create.background] - parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
* @param {Object} [options.create.noise] - describes a noise to be created.
* @param {string} [options.create.noise.type] - type of generated noise, currently only `gaussian` is supported.
* @param {number} [options.create.noise.mean] - mean of pixels in generated noise.
* @param {number} [options.create.noise.sigma] - standard deviation of pixels in generated noise.
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
@@ -155,6 +165,14 @@ const Sharp = function (input, options) {
extendRight: 0,
extendBackground: [0, 0, 0, 255],
withoutEnlargement: false,
withoutReduction: false,
affineMatrix: [],
affineBackground: [0, 0, 0, 255],
affineIdx: 0,
affineIdy: 0,
affineOdx: 0,
affineOdy: 0,
affineInterpolator: this.constructor.interpolators.bilinear,
kernel: 'lanczos3',
fastShrinkOnLoad: true,
// operations
@@ -163,36 +181,47 @@ const Sharp = function (input, options) {
flatten: false,
flattenBackground: [0, 0, 0],
negate: false,
negateAlpha: true,
medianSize: 0,
blurSigma: 0,
sharpenSigma: 0,
sharpenFlat: 1,
sharpenJagged: 2,
sharpenM1: 1,
sharpenM2: 2,
sharpenX1: 2,
sharpenY2: 10,
sharpenY3: 20,
threshold: 0,
thresholdGrayscale: true,
trimThreshold: 0,
gamma: 0,
gammaOut: 0,
greyscale: false,
normalise: false,
claheWidth: 0,
claheHeight: 0,
claheMaxSlope: 3,
brightness: 1,
saturation: 1,
hue: 0,
lightness: 0,
booleanBufferIn: null,
booleanFileIn: '',
joinChannelIn: [],
extractChannel: -1,
removeAlpha: false,
ensureAlpha: false,
ensureAlpha: -1,
colourspace: 'srgb',
colourspaceInput: 'last',
composite: [],
// output
fileOut: '',
formatOut: 'input',
streamOut: false,
withMetadata: false,
withMetadataOrientation: -1,
withMetadataDensity: 0,
withMetadataIcc: '',
withMetadataStrs: {},
resolveWithObject: false,
// output format
jpegQuality: 80,
@@ -204,18 +233,27 @@ const Sharp = function (input, options) {
jpegOptimiseCoding: true,
jpegQuantisationTable: 0,
pngProgressive: false,
pngCompressionLevel: 9,
pngCompressionLevel: 6,
pngAdaptiveFiltering: false,
pngPalette: false,
pngQuality: 100,
pngColours: 256,
pngEffort: 7,
pngBitdepth: 8,
pngDither: 1,
jp2Quality: 80,
jp2TileHeight: 512,
jp2TileWidth: 512,
jp2Lossless: false,
jp2ChromaSubsampling: '4:4:4',
webpQuality: 80,
webpAlphaQuality: 100,
webpLossless: false,
webpNearLossless: false,
webpSmartSubsample: false,
webpReductionEffort: 4,
webpEffort: 4,
gifBitdepth: 8,
gifEffort: 7,
gifDither: 1,
tiffQuality: 80,
tiffCompression: 'jpeg',
tiffPredictor: 'horizontal',
@@ -226,9 +264,13 @@ const Sharp = function (input, options) {
tiffTileWidth: 256,
tiffXres: 1.0,
tiffYres: 1.0,
heifQuality: 80,
tiffResolutionUnit: 'inch',
heifQuality: 50,
heifLossless: false,
heifCompression: 'hevc',
heifCompression: 'av1',
heifEffort: 4,
heifChromaSubsampling: '4:4:4',
rawDepth: 'uchar',
tileSize: 256,
tileOverlap: 0,
tileContainer: 'fs',
@@ -239,6 +281,8 @@ const Sharp = function (input, options) {
tileSkipBlanks: -1,
tileBackground: [255, 255, 255, 255],
tileCentre: false,
tileId: 'https://example.com/iiif',
timeoutSeconds: 0,
linearA: 1,
linearB: 0,
// Function to notify of libvips warnings
@@ -254,7 +298,8 @@ const Sharp = function (input, options) {
this.options.input = this._createInputDescriptor(input, options, { allowStream: true });
return this;
};
util.inherits(Sharp, stream.Duplex);
Object.setPrototypeOf(Sharp.prototype, stream.Duplex.prototype);
Object.setPrototypeOf(Sharp, stream.Duplex);

/**
* Take a "snapshot" of the Sharp instance, returning a new instance.
@@ -274,9 +319,7 @@ util.inherits(Sharp, stream.Duplex);
* // Using Promises to know when the pipeline is complete
* const fs = require("fs");
* const got = require("got");
* const sharpStream = sharp({
* failOnError: false
* });
* const sharpStream = sharp({ failOn: 'none' });
*
* const promises = [];
*
175 changes: 150 additions & 25 deletions lib/input.js
Original file line number Diff line number Diff line change
@@ -2,16 +2,16 @@

const color = require('color');
const is = require('./is');
const sharp = require('../build/Release/sharp.node');
const sharp = require('./sharp');

/**
* Extract input options, if any, from an object.
* @private
*/
function _inputOptionsFromObject (obj) {
const { raw, density, limitInputPixels, sequentialRead, failOnError, animated, page, pages } = obj;
return [raw, density, limitInputPixels, sequentialRead, failOnError, animated, page, pages].some(is.defined)
? { raw, density, limitInputPixels, sequentialRead, failOnError, animated, page, pages }
const { raw, density, limitInputPixels, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd } = obj;
return [raw, density, limitInputPixels, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd].some(is.defined)
? { raw, density, limitInputPixels, unlimited, sequentialRead, failOn, failOnError, animated, page, pages, subifd }
: undefined;
}

@@ -21,16 +21,25 @@ function _inputOptionsFromObject (obj) {
*/
function _createInputDescriptor (input, inputOptions, containerOptions) {
const inputDescriptor = {
failOnError: true,
failOn: 'warning',
limitInputPixels: Math.pow(0x3FFF, 2),
unlimited: false,
sequentialRead: false
};
if (is.string(input)) {
// filesystem
inputDescriptor.file = input;
} else if (is.buffer(input)) {
// Buffer
if (input.length === 0) {
throw Error('Input Buffer is empty');
}
inputDescriptor.buffer = input;
} else if (is.typedArray(input)) {
if (input.length === 0) {
throw Error('Input Bit Array is empty');
}
inputDescriptor.buffer = Buffer.from(input.buffer, input.byteOffset, input.byteLength);
} else if (is.plainObject(input) && !is.defined(inputOptions)) {
// Plain Object descriptor, e.g. create
inputOptions = input;
@@ -47,14 +56,22 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
}`);
}
if (is.object(inputOptions)) {
// Fail on error
// Deprecated: failOnError
if (is.defined(inputOptions.failOnError)) {
if (is.bool(inputOptions.failOnError)) {
inputDescriptor.failOnError = inputOptions.failOnError;
inputDescriptor.failOn = inputOptions.failOnError ? 'warning' : 'none';
} else {
throw is.invalidParameterError('failOnError', 'boolean', inputOptions.failOnError);
}
}
// failOn
if (is.defined(inputOptions.failOn)) {
if (is.string(inputOptions.failOn) && is.inArray(inputOptions.failOn, ['none', 'truncated', 'error', 'warning'])) {
inputDescriptor.failOn = inputOptions.failOn;
} else {
throw is.invalidParameterError('failOn', 'one of: none, truncated, error, warning', inputOptions.failOn);
}
}
// Density
if (is.defined(inputOptions.density)) {
if (is.inRange(inputOptions.density, 1, 100000)) {
@@ -75,6 +92,14 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
throw is.invalidParameterError('limitInputPixels', 'integer >= 0', inputOptions.limitInputPixels);
}
}
// unlimited
if (is.defined(inputOptions.unlimited)) {
if (is.bool(inputOptions.unlimited)) {
inputDescriptor.unlimited = inputOptions.unlimited;
} else {
throw is.invalidParameterError('unlimited', 'boolean', inputOptions.unlimited);
}
}
// sequentialRead
if (is.defined(inputOptions.sequentialRead)) {
if (is.bool(inputOptions.sequentialRead)) {
@@ -94,6 +119,38 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
inputDescriptor.rawWidth = inputOptions.raw.width;
inputDescriptor.rawHeight = inputOptions.raw.height;
inputDescriptor.rawChannels = inputOptions.raw.channels;
inputDescriptor.rawPremultiplied = !!inputOptions.raw.premultiplied;

switch (input.constructor) {
case Uint8Array:
case Uint8ClampedArray:
inputDescriptor.rawDepth = 'uchar';
break;
case Int8Array:
inputDescriptor.rawDepth = 'char';
break;
case Uint16Array:
inputDescriptor.rawDepth = 'ushort';
break;
case Int16Array:
inputDescriptor.rawDepth = 'short';
break;
case Uint32Array:
inputDescriptor.rawDepth = 'uint';
break;
case Int32Array:
inputDescriptor.rawDepth = 'int';
break;
case Float32Array:
inputDescriptor.rawDepth = 'float';
break;
case Float64Array:
inputDescriptor.rawDepth = 'double';
break;
default:
inputDescriptor.rawDepth = 'uchar';
break;
}
} else {
throw new Error('Expected width, height and channels for raw pixel input');
}
@@ -128,28 +185,64 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
throw is.invalidParameterError('level', 'integer between 0 and 256', inputOptions.level);
}
}
// Sub Image File Directory (TIFF)
if (is.defined(inputOptions.subifd)) {
if (is.integer(inputOptions.subifd) && is.inRange(inputOptions.subifd, -1, 100000)) {
inputDescriptor.subifd = inputOptions.subifd;
} else {
throw is.invalidParameterError('subifd', 'integer between -1 and 100000', inputOptions.subifd);
}
}
// Create new image
if (is.defined(inputOptions.create)) {
if (
is.object(inputOptions.create) &&
is.integer(inputOptions.create.width) && inputOptions.create.width > 0 &&
is.integer(inputOptions.create.height) && inputOptions.create.height > 0 &&
is.integer(inputOptions.create.channels) && is.inRange(inputOptions.create.channels, 3, 4) &&
is.defined(inputOptions.create.background)
is.integer(inputOptions.create.channels)
) {
inputDescriptor.createWidth = inputOptions.create.width;
inputDescriptor.createHeight = inputOptions.create.height;
inputDescriptor.createChannels = inputOptions.create.channels;
const background = color(inputOptions.create.background);
inputDescriptor.createBackground = [
background.red(),
background.green(),
background.blue(),
Math.round(background.alpha() * 255)
];
// Noise
if (is.defined(inputOptions.create.noise)) {
if (!is.object(inputOptions.create.noise)) {
throw new Error('Expected noise to be an object');
}
if (!is.inArray(inputOptions.create.noise.type, ['gaussian'])) {
throw new Error('Only gaussian noise is supported at the moment');
}
if (!is.inRange(inputOptions.create.channels, 1, 4)) {
throw is.invalidParameterError('create.channels', 'number between 1 and 4', inputOptions.create.channels);
}
inputDescriptor.createNoiseType = inputOptions.create.noise.type;
if (is.number(inputOptions.create.noise.mean) && is.inRange(inputOptions.create.noise.mean, 0, 10000)) {
inputDescriptor.createNoiseMean = inputOptions.create.noise.mean;
} else {
throw is.invalidParameterError('create.noise.mean', 'number between 0 and 10000', inputOptions.create.noise.mean);
}
if (is.number(inputOptions.create.noise.sigma) && is.inRange(inputOptions.create.noise.sigma, 0, 10000)) {
inputDescriptor.createNoiseSigma = inputOptions.create.noise.sigma;
} else {
throw is.invalidParameterError('create.noise.sigma', 'number between 0 and 10000', inputOptions.create.noise.sigma);
}
} else if (is.defined(inputOptions.create.background)) {
if (!is.inRange(inputOptions.create.channels, 3, 4)) {
throw is.invalidParameterError('create.channels', 'number between 3 and 4', inputOptions.create.channels);
}
const background = color(inputOptions.create.background);
inputDescriptor.createBackground = [
background.red(),
background.green(),
background.blue(),
Math.round(background.alpha() * 255)
];
} else {
throw new Error('Expected valid noise or background to create a new input image');
}
delete inputDescriptor.buffer;
} else {
throw new Error('Expected width, height, channels and background to create a new input image');
throw new Error('Expected valid width, height and channels to create a new input image');
}
}
} else if (is.defined(inputOptions)) {
@@ -205,16 +298,20 @@ function _isStreamInput () {
}

/**
* Fast access to (uncached) image metadata without decoding any compressed image data.
* Fast access to (uncached) image metadata without decoding any compressed pixel data.
*
* This is taken from the header of the input image.
* It does not include operations, such as resize, to be applied to the output image.
*
* A `Promise` is returned when `callback` is not provided.
*
* - `format`: Name of decoder used to decompress image data e.g. `jpeg`, `png`, `webp`, `gif`, `svg`
* - `size`: Total size of image in bytes, for Stream and Buffer input only
* - `width`: Number of pixels wide (EXIF orientation is not taken into consideration)
* - `height`: Number of pixels high (EXIF orientation is not taken into consideration)
* - `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://libvips.github.io/libvips/API/current/VipsImage.html#VipsInterpretation)
* - `width`: Number of pixels wide (EXIF orientation is not taken into consideration, see example below)
* - `height`: Number of pixels high (EXIF orientation is not taken into consideration, see example below)
* - `space`: Name of colour space interpretation e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://www.libvips.org/API/current/VipsImage.html#VipsInterpretation)
* - `channels`: Number of bands e.g. `3` for sRGB, `4` for CMYK
* - `depth`: Name of pixel depth format e.g. `uchar`, `char`, `ushort`, `float` [...](https://libvips.github.io/libvips/API/current/VipsImage.html#VipsBandFormat)
* - `depth`: Name of pixel depth format e.g. `uchar`, `char`, `ushort`, `float` [...](https://www.libvips.org/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
@@ -224,6 +321,10 @@ function _isStreamInput () {
* - `delay`: Delay in ms between each page in an animated image, provided as an array of integers.
* - `pagePrimary`: Number of the primary page in a HEIF image
* - `levels`: Details of each level in a multi-level image provided as an array of objects, requires libvips compiled with support for OpenSlide
* - `subifds`: Number of Sub Image File Directories in an OME-TIFF image
* - `background`: Default background colour, if present, for PNG (bKGD) and GIF images, either an RGB Object or a single greyscale value
* - `compression`: The encoder used to compress an HEIF file, `av1` (AVIF) or `hevc` (HEIC)
* - `resolutionUnit`: The unit of resolution (density), either `inch` or `cm`, if present
* - `hasProfile`: Boolean indicating the presence of an embedded ICC profile
* - `hasAlpha`: Boolean indicating the presence of an alpha transparency channel
* - `orientation`: Number value of the EXIF Orientation header, if present
@@ -234,6 +335,9 @@ function _isStreamInput () {
* - `tifftagPhotoshop`: Buffer containing raw TIFFTAG_PHOTOSHOP data, if present
*
* @example
* const metadata = await sharp(input).metadata();
*
* @example
* const image = sharp(inputJpg);
* image
* .metadata()
@@ -247,6 +351,17 @@ function _isStreamInput () {
* // data contains a WebP image half the width and height of the original JPEG
* });
*
* @example
* // Based on EXIF rotation metadata, get the right-side-up width and height:
*
* const size = getNormalSize(await sharp(input).metadata());
*
* function getNormalSize({ width, height, orientation }) {
* return orientation || 0 >= 5
* ? { width: height, height: width }
* : { width, height };
* }
*
* @param {Function} [callback] - called with the arguments `(err, metadata)`
* @returns {Promise<Object>|Sharp}
*/
@@ -305,9 +420,12 @@ function metadata (callback) {
* - `maxX` (x-coordinate of one of the pixel where the maximum lies)
* - `maxY` (y-coordinate of one of the pixel where the maximum lies)
* - `isOpaque`: Is the image fully opaque? Will be `true` if the image has no alpha channel or if every pixel is fully opaque.
* - `entropy`: Histogram-based estimation of greyscale entropy, discarding alpha channel if any (experimental)
* - `sharpness`: Estimation of greyscale sharpness based on the standard deviation of a Laplacian convolution, discarding alpha channel if any (experimental)
* - `dominant`: Object containing most dominant sRGB colour based on a 4096-bin 3D histogram (experimental)
* - `entropy`: Histogram-based estimation of greyscale entropy, discarding alpha channel if any.
* - `sharpness`: Estimation of greyscale sharpness based on the standard deviation of a Laplacian convolution, discarding alpha channel if any.
* - `dominant`: Object containing most dominant sRGB colour based on a 4096-bin 3D histogram.
*
* **Note**: Statistics are derived from the original input image. Any operations performed on the image must first be
* written to a buffer in order to run `stats` on the result (see third example).
*
* @example
* const image = sharp(inputJpg);
@@ -321,6 +439,13 @@ function metadata (callback) {
* const { entropy, sharpness, dominant } = await sharp(input).stats();
* const { r, g, b } = dominant;
*
* @example
* const image = sharp(input);
* // store intermediate result
* const part = await image.extract(region).toBuffer();
* // create new instance to obtain statistics of extracted region
* const stats = await sharp(part).stats();
*
* @param {Function} [callback] - called with the arguments `(err, stats)`
* @returns {Promise<Object>}
*/
24 changes: 24 additions & 0 deletions lib/is.js
Original file line number Diff line number Diff line change
@@ -48,6 +48,29 @@ const buffer = function (val) {
return val instanceof Buffer;
};

/**
* Is this value a typed array object?. E.g. Uint8Array or Uint8ClampedArray?
* @private
*/
const typedArray = function (val) {
if (defined(val)) {
switch (val.constructor) {
case Uint8Array:
case Uint8ClampedArray:
case Int8Array:
case Uint16Array:
case Int16Array:
case Uint32Array:
case Int32Array:
case Float32Array:
case Float64Array:
return true;
}
}

return false;
};

/**
* Is this value a non-empty string?
* @private
@@ -110,6 +133,7 @@ module.exports = {
fn: fn,
bool: bool,
buffer: buffer,
typedArray: typedArray,
string: string,
number: number,
integer: integer,
96 changes: 66 additions & 30 deletions lib/libvips.js
Original file line number Diff line number Diff line change
@@ -4,24 +4,29 @@ const fs = require('fs');
const os = require('os');
const path = require('path');
const spawnSync = require('child_process').spawnSync;
const semver = require('semver');
const semverCoerce = require('semver/functions/coerce');
const semverGreaterThanOrEqualTo = require('semver/functions/gte');

const platform = require('./platform');
const { config } = require('../package.json');

const env = process.env;
const minimumLibvipsVersionLabelled = env.npm_package_config_libvips || /* istanbul ignore next */
require('../package.json').config.libvips;
const minimumLibvipsVersion = semver.coerce(minimumLibvipsVersionLabelled).version;
config.libvips;
const minimumLibvipsVersion = semverCoerce(minimumLibvipsVersionLabelled).version;

const spawnSyncOptions = {
encoding: 'utf8',
shell: true
};

const vendorPath = path.join(__dirname, '..', 'vendor', minimumLibvipsVersion, platform());

const mkdirSync = function (dirPath) {
try {
fs.mkdirSync(dirPath);
fs.mkdirSync(dirPath, { recursive: true });
} catch (err) {
/* istanbul ignore if */
/* istanbul ignore next */
if (err.code !== 'EEXIST') {
throw err;
}
@@ -37,41 +42,66 @@ const cachePath = function () {
return libvipsCachePath;
};

const integrity = function (platformAndArch) {
return env[`npm_package_config_integrity_${platformAndArch.replace('-', '_')}`] || config.integrity[platformAndArch];
};

const log = function (item) {
if (item instanceof Error) {
console.error(`sharp: Installation error: ${item.message}`);
} else {
console.log(`sharp: ${item}`);
}
};

const isRosetta = function () {
/* istanbul ignore next */
if (process.platform === 'darwin' && process.arch === 'x64') {
const translated = spawnSync('sysctl sysctl.proc_translated', spawnSyncOptions).stdout;
return (translated || '').trim() === 'sysctl.proc_translated: 1';
}
return false;
};

const globalLibvipsVersion = function () {
if (process.platform !== 'win32') {
const globalLibvipsVersion = spawnSync(`PKG_CONFIG_PATH="${pkgConfigPath()}" pkg-config --modversion vips-cpp`, spawnSyncOptions).stdout || '';
return globalLibvipsVersion.trim();
const globalLibvipsVersion = spawnSync('pkg-config --modversion vips-cpp', {
...spawnSyncOptions,
env: {
PKG_CONFIG_PATH: pkgConfigPath()
}
}).stdout;
/* istanbul ignore next */
return (globalLibvipsVersion || '').trim();
} else {
return '';
}
};

const hasVendoredLibvips = function () {
const currentPlatformId = platform();
const vendorPath = path.join(__dirname, '..', 'vendor', minimumLibvipsVersion);
let vendorPlatformId;
try {
vendorPlatformId = require(path.join(vendorPath, 'platform.json'));
} catch (err) {}
/* istanbul ignore else */
if (vendorPlatformId) {
/* istanbul ignore else */
if (currentPlatformId === vendorPlatformId) {
return true;
} else {
throw new Error(`'${vendorPlatformId}' binaries cannot be used on the '${currentPlatformId}' platform. Please remove the 'node_modules/sharp' directory and run 'npm install' on the '${currentPlatformId}' platform.`);
}
} else {
return false;
}
return fs.existsSync(vendorPath);
};

/* istanbul ignore next */
const removeVendoredLibvips = function () {
const rm = fs.rmSync ? fs.rmSync : fs.rmdirSync;
rm(vendorPath, { recursive: true, maxRetries: 3, force: true });
};

const pkgConfigPath = function () {
if (process.platform !== 'win32') {
const brewPkgConfigPath = spawnSync('which brew >/dev/null 2>&1 && eval $(brew --env) && echo $PKG_CONFIG_LIBDIR', spawnSyncOptions).stdout || '';
return [brewPkgConfigPath.trim(), env.PKG_CONFIG_PATH, '/usr/local/lib/pkgconfig', '/usr/lib/pkgconfig']
.filter(function (p) { return !!p; })
.join(':');
const brewPkgConfigPath = spawnSync(
'which brew >/dev/null 2>&1 && brew environment --plain | grep PKG_CONFIG_LIBDIR | cut -d" " -f2',
spawnSyncOptions
).stdout || '';
return [
brewPkgConfigPath.trim(),
env.PKG_CONFIG_PATH,
'/usr/local/lib/pkgconfig',
'/usr/lib/pkgconfig',
'/usr/local/libdata/pkgconfig',
'/usr/libdata/pkgconfig'
].filter(Boolean).join(':');
} else {
return '';
}
@@ -81,18 +111,24 @@ const useGlobalLibvips = function () {
if (Boolean(env.SHARP_IGNORE_GLOBAL_LIBVIPS) === true) {
return false;
}

/* istanbul ignore next */
if (isRosetta()) {
return false;
}
const globalVipsVersion = globalLibvipsVersion();
return !!globalVipsVersion && /* istanbul ignore next */
semver.gte(globalVipsVersion, minimumLibvipsVersion);
semverGreaterThanOrEqualTo(globalVipsVersion, minimumLibvipsVersion);
};

module.exports = {
minimumLibvipsVersion,
minimumLibvipsVersionLabelled,
cachePath,
integrity,
log,
globalLibvipsVersion,
hasVendoredLibvips,
removeVendoredLibvips,
pkgConfigPath,
useGlobalLibvips,
mkdirSync
369 changes: 336 additions & 33 deletions lib/operation.js

Large diffs are not rendered by default.

571 changes: 449 additions & 122 deletions lib/output.js

Large diffs are not rendered by default.

8 changes: 5 additions & 3 deletions lib/platform.js
Original file line number Diff line number Diff line change
@@ -7,10 +7,12 @@ const env = process.env;
module.exports = function () {
const arch = env.npm_config_arch || process.arch;
const platform = env.npm_config_platform || process.platform;
/* istanbul ignore next */
const libc = (platform === 'linux' && detectLibc.isNonGlibcLinux) ? detectLibc.family : '';
const libc = process.env.npm_config_libc ||
/* istanbul ignore next */
(detectLibc.isNonGlibcLinuxSync() ? detectLibc.familySync() : '');
const libcId = platform !== 'linux' || libc === detectLibc.GLIBC ? '' : libc;

const platformId = [`${platform}${libc}`];
const platformId = [`${platform}${libcId}`];

if (arch === 'arm') {
const fallback = process.versions.electron ? '7' : '6';
76 changes: 61 additions & 15 deletions lib/resize.js
Original file line number Diff line number Diff line change
@@ -183,6 +183,20 @@ function isRotationExpected (options) {
* });
*
* @example
* sharp(input)
* .resize(200, 200, {
* fit: sharp.fit.outside,
* withoutReduction: true
* })
* .toFormat('jpeg')
* .toBuffer()
* .then(function(outputBuffer) {
* // outputBuffer contains JPEG image data
* // of at least 200 pixels wide and 200 pixels high while maintaining aspect ratio
* // and no smaller than the input image
* });
*
* @example
* const scaleByHalf = await sharp(input)
* .metadata()
* .then(({ width }) => sharp(input)
@@ -200,6 +214,7 @@ function isRotationExpected (options) {
* @param {String|Object} [options.background={r: 0, g: 0, b: 0, alpha: 1}] - background colour when using a `fit` of `contain`, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black without transparency.
* @param {String} [options.kernel='lanczos3'] - the kernel to use for image reduction.
* @param {Boolean} [options.withoutEnlargement=false] - do not enlarge if the width *or* height are already less than the specified dimensions, equivalent to GraphicsMagick's `>` geometry option.
* @param {Boolean} [options.withoutReduction=false] - do not reduce if the width *or* height are already greater than the specified dimensions, equivalent to GraphicsMagick's `<` geometry option.
* @param {Boolean} [options.fastShrinkOnLoad=true] - take greater advantage of the JPEG and WebP shrink-on-load feature, which can lead to a slight moiré pattern on some images.
* @returns {Sharp}
* @throws {Error} Invalid parameters
@@ -276,6 +291,10 @@ function resize (width, height, options) {
if (is.defined(options.withoutEnlargement)) {
this._setBooleanOption('withoutEnlargement', options.withoutEnlargement);
}
// Without reduction
if (is.defined(options.withoutReduction)) {
this._setBooleanOption('withoutReduction', options.withoutReduction);
}
// Shrink on load
if (is.defined(options.fastShrinkOnLoad)) {
this._setBooleanOption('fastShrinkOnLoad', options.fastShrinkOnLoad);
@@ -302,11 +321,20 @@ function resize (width, height, options) {
* })
* ...
*
* @example
* // Add a row of 10 red pixels to the bottom
* sharp(input)
* .extend({
* bottom: 10,
* background: 'red'
* })
* ...
*
* @param {(number|Object)} extend - single pixel count to add to all edges or an Object with per-edge counts
* @param {number} [extend.top]
* @param {number} [extend.left]
* @param {number} [extend.bottom]
* @param {number} [extend.right]
* @param {number} [extend.top=0]
* @param {number} [extend.left=0]
* @param {number} [extend.bottom=0]
* @param {number} [extend.right=0]
* @param {String|Object} [extend.background={r: 0, g: 0, b: 0, alpha: 1}] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black without transparency.
* @returns {Sharp}
* @throws {Error} Invalid parameters
@@ -317,17 +345,35 @@ function extend (extend) {
this.options.extendBottom = extend;
this.options.extendLeft = extend;
this.options.extendRight = extend;
} else if (
is.object(extend) &&
is.integer(extend.top) && extend.top >= 0 &&
is.integer(extend.bottom) && extend.bottom >= 0 &&
is.integer(extend.left) && extend.left >= 0 &&
is.integer(extend.right) && extend.right >= 0
) {
this.options.extendTop = extend.top;
this.options.extendBottom = extend.bottom;
this.options.extendLeft = extend.left;
this.options.extendRight = extend.right;
} else if (is.object(extend)) {
if (is.defined(extend.top)) {
if (is.integer(extend.top) && extend.top >= 0) {
this.options.extendTop = extend.top;
} else {
throw is.invalidParameterError('top', 'positive integer', extend.top);
}
}
if (is.defined(extend.bottom)) {
if (is.integer(extend.bottom) && extend.bottom >= 0) {
this.options.extendBottom = extend.bottom;
} else {
throw is.invalidParameterError('bottom', 'positive integer', extend.bottom);
}
}
if (is.defined(extend.left)) {
if (is.integer(extend.left) && extend.left >= 0) {
this.options.extendLeft = extend.left;
} else {
throw is.invalidParameterError('left', 'positive integer', extend.left);
}
}
if (is.defined(extend.right)) {
if (is.integer(extend.right) && extend.right >= 0) {
this.options.extendRight = extend.right;
} else {
throw is.invalidParameterError('right', 'positive integer', extend.right);
}
}
this._setBackgroundColourOption('extendBackground', extend.background);
} else {
throw is.invalidParameterError('extend', 'integer or object', extend);
32 changes: 32 additions & 0 deletions lib/sharp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
'use strict';

const platformAndArch = require('./platform')();

/* istanbul ignore next */
try {
module.exports = require(`../build/Release/sharp-${platformAndArch}.node`);
} catch (err) {
// Bail early if bindings aren't available
const help = ['', 'Something went wrong installing the "sharp" module', '', err.message, '', 'Possible solutions:'];
if (/dylib/.test(err.message) && /Incompatible library version/.test(err.message)) {
help.push('- Update Homebrew: "brew update && brew upgrade vips"');
} else {
const [platform, arch] = platformAndArch.split('-');
help.push(
'- Install with verbose logging and look for errors: "npm install --ignore-scripts=false --foreground-scripts --verbose sharp"',
`- Install for the current ${platformAndArch} runtime: "npm install --platform=${platform} --arch=${arch} sharp"`
);
}
help.push(
'- Consult the installation documentation: https://sharp.pixelplumbing.com/install'
);
// Check loaded
if (process.platform === 'win32' || /symbol/.test(err.message)) {
const loadedModule = Object.keys(require.cache).find((i) => /[\\/]build[\\/]Release[\\/]sharp(.*)\.node$/.test(i));
if (loadedModule) {
const [, loadedPackage] = loadedModule.match(/node_modules[\\/]([^\\/]+)[\\/]/);
help.push(`- Ensure the version of sharp aligns with the ${loadedPackage} package: "npm ls sharp"`);
}
}
throw new Error(help.join('\n'));
}
94 changes: 77 additions & 17 deletions lib/utility.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
'use strict';

const fs = require('fs');
const path = require('path');
const events = require('events');
const detectLibc = require('detect-libc');

const is = require('./is');
const sharp = require('../build/Release/sharp.node');
const platformAndArch = require('./platform')();
const sharp = require('./sharp');

/**
* An Object containing nested boolean values representing the available input and output formats/methods.
@@ -13,6 +18,26 @@ const sharp = require('../build/Release/sharp.node');
*/
const format = sharp.format();

/**
* An Object containing the available interpolators and their proper values
* @readonly
* @enum {string}
*/
const interpolators = {
/** [Nearest neighbour interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation). Suitable for image enlargement only. */
nearest: 'nearest',
/** [Bilinear interpolation](http://en.wikipedia.org/wiki/Bilinear_interpolation). Faster than bicubic but with less smooth results. */
bilinear: 'bilinear',
/** [Bicubic interpolation](http://en.wikipedia.org/wiki/Bicubic_interpolation) (the default). */
bicubic: 'bicubic',
/** [LBB interpolation](https://github.com/libvips/libvips/blob/master/libvips/resample/lbb.cpp#L100). Prevents some "[acutance](http://en.wikipedia.org/wiki/Acutance)" but typically reduces performance by a factor of 2. */
locallyBoundedBicubic: 'lbb',
/** [Nohalo interpolation](http://eprints.soton.ac.uk/268086/). Prevents acutance but typically reduces performance by a factor of 3. */
nohalo: 'nohalo',
/** [VSQBS interpolation](https://github.com/libvips/libvips/blob/master/libvips/resample/vsqbs.cpp#L48). Prevents "staircasing" when enlarging. */
vertexSplitQuadraticBasisSpline: 'vsqbs'
};

/**
* An Object containing the version numbers of libvips and its dependencies.
* @member
@@ -23,8 +48,23 @@ let versions = {
vips: sharp.libvipsVersion()
};
try {
versions = require(`../vendor/${versions.vips}/versions.json`);
} catch (err) {}
versions = require(`../vendor/${versions.vips}/${platformAndArch}/versions.json`);
} catch (_err) { /* ignore */ }

/**
* An Object containing the platform and architecture
* of the current and installed vendored binaries.
* @member
* @example
* console.log(sharp.vendor);
*/
const vendor = {
current: platformAndArch,
installed: []
};
try {
vendor.installed = fs.readdirSync(path.join(__dirname, `../vendor/${versions.vips}`));
} catch (_err) { /* ignore */ }

/**
* Gets or, when options are provided, sets the limits of _libvips'_ operation cache.
@@ -63,15 +103,32 @@ cache(true);

/**
* Gets or, when a concurrency is provided, sets
* the number of threads _libvips'_ should create to process each image.
* The default value is the number of CPU cores.
* A value of `0` will reset to this default.
*
* The maximum number of images that can be processed in parallel
* is limited by libuv's `UV_THREADPOOL_SIZE` environment variable.
* the maximum number of threads _libvips_ should use to process _each image_.
* These are from a thread pool managed by glib,
* which helps avoid the overhead of creating new threads.
*
* This method always returns the current concurrency.
*
* The default value is the number of CPU cores,
* except when using glibc-based Linux without jemalloc,
* where the default is `1` to help reduce memory fragmentation.
*
* A value of `0` will reset this to the number of CPU cores.
*
* Some image format libraries spawn additional threads,
* e.g. libaom manages its own 4 threads when encoding AVIF images,
* and these are independent of the value set here.
*
* The maximum number of images that sharp can process in parallel
* is controlled by libuv's `UV_THREADPOOL_SIZE` environment variable,
* which defaults to 4.
*
* https://nodejs.org/api/cli.html#uv_threadpool_sizesize
*
* For example, by default, a machine with 8 CPU cores will process
* 4 images in parallel and use up to 8 threads per image,
* so there will be up to 32 concurrent threads.
*
* @example
* const threads = sharp.concurrency(); // 4
* sharp.concurrency(2); // 2
@@ -83,6 +140,11 @@ cache(true);
function concurrency (concurrency) {
return sharp.concurrency(is.integer(concurrency) ? concurrency : null);
}
/* istanbul ignore next */
if (detectLibc.familySync() === detectLibc.GLIBC && !sharp._isUsingJemalloc()) {
// Reduce default concurrency to 1 when using glibc memory allocator
sharp.concurrency(1);
}

/**
* An EventEmitter that emits a `change` event when a task is either:
@@ -137,15 +199,13 @@ simd(true);
* @private
*/
module.exports = function (Sharp) {
[
cache,
concurrency,
counters,
simd
].forEach(function (f) {
Sharp[f.name] = f;
});
Sharp.cache = cache;
Sharp.concurrency = concurrency;
Sharp.counters = counters;
Sharp.simd = simd;
Sharp.format = format;
Sharp.interpolators = interpolators;
Sharp.versions = versions;
Sharp.vendor = vendor;
Sharp.queue = queue;
};
84 changes: 56 additions & 28 deletions package.json
Original file line number Diff line number Diff line change
@@ -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.26.2",
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, GIF, AVIF and TIFF images",
"version": "0.30.5",
"author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://github.com/lovell/sharp",
"contributors": [
@@ -69,25 +69,39 @@
"Edward Silverton <e.silverton@gmail.com>",
"Roman Malieiev <aromaleev@gmail.com>",
"Tomas Szabo <tomas.szabo@deftomat.com>",
"Robert O'Rourke <robert@o-rourke.org>"
"Robert O'Rourke <robert@o-rourke.org>",
"Guillermo Alfonso Varela Chouciño <guillevch@gmail.com>",
"Christian Flintrup <chr@gigahost.dk>",
"Manan Jadhav <manan@motionden.com>",
"Leon Radley <leon@radley.se>",
"alza54 <alza54@thiocod.in>",
"Jacob Smith <jacob@frende.me>",
"Michael Nutt <michael@nutt.im>",
"Brad Parham <baparham@gmail.com>",
"Taneli Vatanen <taneli.vatanen@gmail.com>",
"Joris Dugué <zaruike10@gmail.com>",
"Chris Banks <christopher.bradley.banks@gmail.com>",
"Ompal Singh <ompal.hitm09@gmail.com>",
"Brodan <christopher.hranj@gmail.com",
"Ankur Parihar <ankur.github@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) || (node install/can-compile && 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 && node install/prebuild-ci",
"test-unit": "nyc --reporter=lcov --branches=99 mocha --slow=5000 --timeout=60000 ./test/unit/*.js",
"test": "npm run test-lint && npm run test-unit && npm run test-licensing",
"test-lint": "semistandard && cpplint",
"test-unit": "nyc --reporter=lcov --branches=99 mocha --slow=1000 --timeout=60000 ./test/unit/*.js",
"test-licensing": "license-checker --production --summary --onlyAllow=\"Apache-2.0;BSD;ISC;MIT\"",
"test-coverage": "./test/coverage/report.sh",
"test-leak": "./test/leak/leak.sh",
"docs-build": "documentation lint lib && for m in constructor input resize composite operation colour channel output utility; do documentation build --shallow --format=md --markdown-toc=false lib/$m.js >docs/api-$m.md; done && node docs/search-index/build",
"docs-build": "documentation lint lib && node docs/build && node docs/search-index/build",
"docs-serve": "cd docs && npx serve",
"docs-publish": "cd docs && npx firebase-tools deploy --project pixelplumbing --only hosting:pixelplumbing-sharp"
},
"main": "lib/index.js",
"files": [
"binding.gyp",
"install/**",
"!install/prebuild-ci.js",
"lib/**",
"src/**"
],
@@ -99,9 +113,11 @@
"jpeg",
"png",
"webp",
"avif",
"tiff",
"gif",
"svg",
"jp2",
"dzi",
"image",
"resize",
@@ -112,46 +128,58 @@
"vips"
],
"dependencies": {
"color": "^3.1.2",
"detect-libc": "^1.0.3",
"node-addon-api": "^3.0.2",
"npmlog": "^4.1.2",
"prebuild-install": "^5.3.5",
"semver": "^7.3.2",
"simple-get": "^4.0.0",
"tar-fs": "^2.1.0",
"color": "^4.2.3",
"detect-libc": "^2.0.1",
"node-addon-api": "^5.0.0",
"prebuild-install": "^7.1.0",
"semver": "^7.3.7",
"simple-get": "^4.0.1",
"tar-fs": "^2.1.1",
"tunnel-agent": "^0.6.0"
},
"devDependencies": {
"async": "^3.2.0",
"cc": "^2.0.1",
"decompress-zip": "^0.3.2",
"documentation": "^13.0.2",
"async": "^3.2.3",
"cc": "^3.0.1",
"decompress-zip": "^0.3.3",
"documentation": "^13.2.5",
"exif-reader": "^1.0.3",
"icc": "^2.0.0",
"license-checker": "^25.0.1",
"mocha": "^8.1.1",
"mock-fs": "^4.13.0",
"mocha": "^10.0.0",
"mock-fs": "^5.1.2",
"nyc": "^15.1.0",
"prebuild": "^10.0.1",
"prebuild": "^11.0.3",
"rimraf": "^3.0.2",
"semistandard": "^14.2.3"
"semistandard": "^16.0.1"
},
"license": "Apache-2.0",
"config": {
"libvips": "8.10.0",
"libvips": "8.12.2",
"integrity": {
"darwin-arm64v8": "sha512-p46s/bbJAjkOXzPISZt9HUpG9GWjwQkYnLLRLKzsBJHLtB3X6C6Y/zXI5Hd0DOojcFkks9a0kTN+uDQ/XJY19g==",
"darwin-x64": "sha512-6vOHVZnvXwe6EXRsy29jdkUzBE6ElNpXUwd+m8vV7qy32AnXu7B9YemHsZ44vWviIwPZeXF6Nhd9EFLM0wWohw==",
"linux-arm64v8": "sha512-XwZdS63yhqLtbFtx/0eoLF/Agf5qtTrI11FMnMRpuBJWd4jHB60RAH+uzYUgoChCmKIS+AeXYMLm4d8Ns2QX8w==",
"linux-armv6": "sha512-Rh0Q0kqwPG2MjXWOkPCuPEyiUKFgKJYWLgS835D4MrXgdKr8Tft/eVrc2iGIxt2re30VpDiZ1h0Rby1aCZt2zw==",
"linux-armv7": "sha512-heTS/MsmRvu4JljINxP+vDiS9ZZfuGhr3IStb5F7Gc0/QLRhllYAg4rcO8L1eTK9sIIzG5ARvI19+YUZe7WbzA==",
"linux-x64": "sha512-SSWAwBFi0hx8V/h/v82tTFGKWTFv9FiCK3Timz5OExuI+sX1Ngrd0PVQaWXOThGNdel/fcD3Bz9YjSt4feBR1g==",
"linuxmusl-arm64v8": "sha512-Rhks+5C7p7aO6AucLT1uvzo8ohlqcqCUPgZmN+LZjsPWob/Iix3MfiDYtv/+gTvdeEfXxbCU6/YuPBwHQ7/crA==",
"linuxmusl-x64": "sha512-IOyjSQqpWVntqOUpCHVWuQwACwmmjdi15H8Pc+Ma1JkhPogTfVsFQWyL7DuOTD3Yr23EuYGzovUX00duOtfy/g==",
"win32-arm64v8": "sha512-A+Qe8Ipewtvw9ldvF6nWed2J8kphzrUE04nFeKCtNx6pfGQ/MAlCKMjt/U8VgUKNjB01zJDUW9XE0+FhGZ/UpQ==",
"win32-ia32": "sha512-cMrAvwFdDeAVnLJt0IPMPRKaIFhyXYGTprsM0DND9VUHE8F7dJMR44tS5YkXsGh1QNDtjKT6YuxAVUglmiXtpA==",
"win32-x64": "sha512-vLFIfw6aW2zABa8jpgzWDhljnE6glktrddErVyazAIoHl6BFFe/Da+LK1DbXvIYHz7fyOoKhSfCJHCiJG1Vg6w=="
},
"runtime": "napi",
"target": 3
"target": 5
},
"engines": {
"node": ">=10.16.0"
"node": ">=12.13.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"binary": {
"napi_versions": [
3
5
]
},
"semistandard": {
369 changes: 272 additions & 97 deletions src/common.cc

Large diffs are not rendered by default.

78 changes: 67 additions & 11 deletions src/common.h
Original file line number Diff line number Diff line change
@@ -24,8 +24,10 @@

// Verify platform and compiler compatibility

#if (VIPS_MAJOR_VERSION < 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 10))
#error "libvips version 8.10.0+ is required - please see https://sharp.pixelplumbing.com/install"
#if (VIPS_MAJOR_VERSION < 8) || \
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 12) || \
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 12 && VIPS_MICRO_VERSION < 2)
#error "libvips version 8.12.2+ is required - please see https://sharp.pixelplumbing.com/install"
#endif

#if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6)))
@@ -46,53 +48,67 @@ namespace sharp {
std::string name;
std::string file;
char *buffer;
bool failOnError;
VipsFailOn failOn;
int limitInputPixels;
bool unlimited;
VipsAccess access;
size_t bufferLength;
bool isBuffer;
double density;
VipsBandFormat rawDepth;
int rawChannels;
int rawWidth;
int rawHeight;
bool rawPremultiplied;
int pages;
int page;
int level;
int subifd;
int createChannels;
int createWidth;
int createHeight;
std::vector<double> createBackground;
std::string createNoiseType;
double createNoiseMean;
double createNoiseSigma;

InputDescriptor():
buffer(nullptr),
failOnError(TRUE),
failOn(VIPS_FAIL_ON_WARNING),
limitInputPixels(0x3FFF * 0x3FFF),
unlimited(FALSE),
access(VIPS_ACCESS_RANDOM),
bufferLength(0),
isBuffer(FALSE),
density(72.0),
rawDepth(VIPS_FORMAT_UCHAR),
rawChannels(0),
rawWidth(0),
rawHeight(0),
rawPremultiplied(false),
pages(1),
page(0),
level(0),
subifd(-1),
createChannels(0),
createWidth(0),
createHeight(0),
createBackground{ 0.0, 0.0, 0.0, 255.0 } {}
createBackground{ 0.0, 0.0, 0.0, 255.0 },
createNoiseMean(0.0),
createNoiseSigma(0.0) {}
};

// 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);
std::string AttrAsStr(Napi::Object obj, unsigned int const attr);
uint32_t AttrAsUint32(Napi::Object obj, std::string attr);
int32_t AttrAsInt32(Napi::Object obj, std::string attr);
int32_t AttrAsInt32(Napi::Object obj, unsigned int const 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);
std::vector<double> AttrAsVectorOfDouble(Napi::Object obj, std::string attr);
std::vector<int32_t> AttrAsInt32Vector(Napi::Object obj, std::string attr);

// Create an InputDescriptor instance from a Napi::Object describing an input image
@@ -102,6 +118,7 @@ namespace sharp {
JPEG,
PNG,
WEBP,
JP2,
TIFF,
GIF,
SVG,
@@ -118,6 +135,14 @@ namespace sharp {
MISSING
};

enum class Canvas {
CROP,
EMBED,
MAX,
MIN,
IGNORE_ASPECT
};

// How many tasks are in the queue?
extern volatile int counterQueue;

@@ -128,6 +153,7 @@ namespace sharp {
bool IsJpeg(std::string const &str);
bool IsPng(std::string const &str);
bool IsWebp(std::string const &str);
bool IsJp2(std::string const &str);
bool IsGif(std::string const &str);
bool IsTiff(std::string const &str);
bool IsHeic(std::string const &str);
@@ -190,9 +216,13 @@ namespace sharp {

/*
Set animation properties if necessary.
Non-provided properties will be loaded from image.
*/
VImage SetAnimationProperties(VImage image, int pageHeight, std::vector<int> delay, int loop);
VImage SetAnimationProperties(VImage image, int nPages, int pageHeight, std::vector<int> delay, int loop);

/*
Remove animation properties from image.
*/
VImage RemoveAnimationProperties(VImage image);

/*
Does this image have a non-default density?
@@ -209,6 +239,12 @@ namespace sharp {
*/
VImage SetDensity(VImage image, const double density);

/*
Multi-page images can have a page height. Fetch it, and sanity check it.
If page-height is not set, it defaults to the image height
*/
int GetPageHeight(VImage image);

/*
Check the proposed format supports the current dimensions.
*/
@@ -229,6 +265,16 @@ namespace sharp {
*/
std::string VipsWarningPop();

/*
Attach an event listener for progress updates, used to detect timeout
*/
void SetTimeout(VImage image, int const timeoutSeconds);

/*
Event listener for progress updates, used to detect timeout
*/
void VipsProgressCallBack(VipsImage *image, VipsProgress *progress, int *timeoutSeconds);

/*
Calculate the (left, top) coordinates of the output image
within the input image, applying the given gravity during an embed.
@@ -274,12 +320,13 @@ namespace sharp {
/*
Convert RGBA value to another colourspace
*/
std::vector<double> GetRgbaAsColourspace(std::vector<double> const rgba, VipsInterpretation const interpretation);
std::vector<double> GetRgbaAsColourspace(std::vector<double> const rgba,
VipsInterpretation const interpretation, bool premultiply);

/*
Apply the alpha channel to a given colour
*/
std::tuple<VImage, std::vector<double>> ApplyAlpha(VImage image, std::vector<double> colour);
std::tuple<VImage, std::vector<double>> ApplyAlpha(VImage image, std::vector<double> colour, bool premultiply);

/*
Removes alpha channel, if any.
@@ -289,7 +336,16 @@ namespace sharp {
/*
Ensures alpha channel, if missing.
*/
VImage EnsureAlpha(VImage image);
VImage EnsureAlpha(VImage image, double const value);

/*
Calculate the shrink factor, taking into account auto-rotate, the canvas
mode, and so on. The hshrink/vshrink are the amount to shrink the input
image axes by in order for the output axes (ie. after rotation) to match
the required thumbnail width/height and canvas mode.
*/
std::pair<double, double> ResolveShrink(int width, int height, int targetWidth, int targetHeight,
Canvas canvas, bool swap, bool withoutEnlargement, bool withoutReduction);

} // namespace sharp

26 changes: 0 additions & 26 deletions src/libvips/cplusplus/VConnection.cpp
Original file line number Diff line number Diff line change
@@ -110,19 +110,6 @@ VSource::new_from_options( const char *options )
return( out );
}

VOption *
VOption::set( const char *name, const VSource value )
{
Pair *pair = new Pair( name );

pair->input = true;
g_value_init( &pair->value, VIPS_TYPE_SOURCE );
g_value_set_object( &pair->value, value.get_source() );
options.push_back( pair );

return( this );
}

VTarget
VTarget::new_to_descriptor( int descriptor )
{
@@ -162,17 +149,4 @@ VTarget::new_to_memory()
return( out );
}

VOption *
VOption::set( const char *name, const VTarget value )
{
Pair *pair = new Pair( name );

pair->input = true;
g_value_init( &pair->value, VIPS_TYPE_TARGET );
g_value_set_object( &pair->value, value.get_target() );
options.push_back( pair );

return( this );
}

VIPS_NAMESPACE_END
120 changes: 88 additions & 32 deletions src/libvips/cplusplus/VImage.cpp
Original file line number Diff line number Diff line change
@@ -51,6 +51,12 @@

VIPS_NAMESPACE_START

/**
* \namespace vips
*
* General docs for the vips namespace.
*/

std::vector<double>
to_vectorv( int n, ... )
{
@@ -87,7 +93,7 @@ negate( std::vector<double> vector )
{
std::vector<double> new_vector( vector.size() );

for( unsigned int i = 0; i < vector.size(); i++ )
for( std::vector<double>::size_type i = 0; i < vector.size(); i++ )
new_vector[i] = vector[i] * -1;

return( new_vector );
@@ -98,7 +104,7 @@ invert( std::vector<double> vector )
{
std::vector<double> new_vector( vector.size() );

for( unsigned int i = 0; i < vector.size(); i++ )
for( std::vector<double>::size_type i = 0; i < vector.size(); i++ )
new_vector[i] = 1.0 / vector[i];

return( new_vector );
@@ -140,6 +146,20 @@ VOption::set( const char *name, int value )
return( this );
}

// input guint64
VOption *
VOption::set( const char *name, guint64 value )
{
Pair *pair = new Pair( name );

pair->input = true;
g_value_init( &pair->value, G_TYPE_UINT64 );
g_value_set_uint64( &pair->value, value );
options.push_back( pair );

return( this );
}

// input double
VOption *
VOption::set( const char *name, double value )
@@ -167,61 +187,61 @@ VOption::set( const char *name, const char *value )
return( this );
}

// input image
// input vips object (image, source, target, etc. etc.)
VOption *
VOption::set( const char *name, const VImage value )
VOption::set( const char *name, const VObject value )
{
Pair *pair = new Pair( name );
VipsObject *object = value.get_object();
GType type = G_OBJECT_TYPE( object );

pair->input = true;
g_value_init( &pair->value, VIPS_TYPE_IMAGE );
g_value_set_object( &pair->value, value.get_image() );
g_value_init( &pair->value, type );
g_value_set_object( &pair->value, object );
options.push_back( pair );

return( this );
}

// input double array
// input int array
VOption *
VOption::set( const char *name, std::vector<double> value )
VOption::set( const char *name, std::vector<int> value )
{
Pair *pair = new Pair( name );

double *array;
unsigned int i;
int *array;

pair->input = true;

g_value_init( &pair->value, VIPS_TYPE_ARRAY_DOUBLE );
vips_value_set_array_double( &pair->value, NULL,
g_value_init( &pair->value, VIPS_TYPE_ARRAY_INT );
vips_value_set_array_int( &pair->value, NULL,
static_cast< int >( value.size() ) );
array = vips_value_get_array_double( &pair->value, NULL );
array = vips_value_get_array_int( &pair->value, NULL );

for( i = 0; i < value.size(); i++ )
for( std::vector<double>::size_type i = 0; i < value.size(); i++ )
array[i] = value[i];

options.push_back( pair );

return( this );
}

// input int array
// input double array
VOption *
VOption::set( const char *name, std::vector<int> value )
VOption::set( const char *name, std::vector<double> value )
{
Pair *pair = new Pair( name );

int *array;
unsigned int i;
double *array;

pair->input = true;

g_value_init( &pair->value, VIPS_TYPE_ARRAY_INT );
vips_value_set_array_int( &pair->value, NULL,
g_value_init( &pair->value, VIPS_TYPE_ARRAY_DOUBLE );
vips_value_set_array_double( &pair->value, NULL,
static_cast< int >( value.size() ) );
array = vips_value_get_array_int( &pair->value, NULL );
array = vips_value_get_array_double( &pair->value, NULL );

for( i = 0; i < value.size(); i++ )
for( std::vector<double>::size_type i = 0; i < value.size(); i++ )
array[i] = value[i];

options.push_back( pair );
@@ -236,7 +256,6 @@ VOption::set( const char *name, std::vector<VImage> value )
Pair *pair = new Pair( name );

VipsImage **array;
unsigned int i;

pair->input = true;

@@ -245,7 +264,7 @@ VOption::set( const char *name, std::vector<VImage> value )
static_cast< int >( value.size() ) );
array = vips_value_get_array_image( &pair->value, NULL );

for( i = 0; i < value.size(); i++ ) {
for( std::vector<double>::size_type i = 0; i < value.size(); i++ ) {
VipsImage *vips_image = value[i].get_image();

array[i] = vips_image;
@@ -466,10 +485,9 @@ VOption::get_operation( VipsOperation *operation )
double *array =
vips_value_get_array_double( value,
&length );
int j;

((*i)->vvector)->resize( length );
for( j = 0; j < length; j++ )
for( int j = 0; j < length; j++ )
(*((*i)->vvector))[j] = array[j];
}
else if( type == VIPS_TYPE_BLOB ) {
@@ -619,6 +637,22 @@ VImage::new_from_source( VSource source, const char *option_string,
return( out );
}

VImage
VImage::new_from_memory_steal( void *data, size_t size,
int width, int height, int bands, VipsBandFormat format )
{
VipsImage *image;

if( !(image = vips_image_new_from_memory( data, size,
width, height, bands, format )) )
throw( VError() );

g_signal_connect( image, "postclose",
G_CALLBACK( vips_image_free_buffer ), data);

return( VImage( image ) );
}

VImage
VImage::new_matrix( int width, int height )
{
@@ -680,17 +714,38 @@ VImage::write_to_buffer( const char *suffix, void **buf, size_t *size,
const char *operation_name;
VipsBlob *blob;

/* Save with the new target API if we can. Fall back to the older
* mechanism in case the saver we need has not been converted yet.
*
* We need to hide any errors from this first phase.
*/
vips__filename_split8( suffix, filename, option_string );
if( !(operation_name = vips_foreign_find_save_buffer( filename )) ) {

vips_error_freeze();
operation_name = vips_foreign_find_save_target( filename );
vips_error_thaw();

if( operation_name ) {
VTarget target = VTarget::new_to_memory();

call_option_string( operation_name, option_string,
(options ? options : VImage::option())->
set( "in", *this )->
set( "target", target ) );

g_object_get( target.get_target(), "blob", &blob, NULL );
}
else if( (operation_name = vips_foreign_find_save_buffer( filename )) ) {
call_option_string( operation_name, option_string,
(options ? options : VImage::option())->
set( "in", *this )->
set( "buffer", &blob ) );
}
else {
delete options;
throw VError();
}

call_option_string( operation_name, option_string,
(options ? options : VImage::option())->
set( "in", *this )->
set( "buffer", &blob ) );

if( blob ) {
if( buf ) {
*buf = VIPS_AREA( blob )->data;
@@ -729,6 +784,7 @@ std::vector<VImage>
VImage::bandsplit( VOption *options ) const
{
std::vector<VImage> b;
b.reserve(bands());

for( int i = 0; i < bands(); i++ )
b.push_back( extract_band( i ) );
13 changes: 0 additions & 13 deletions src/libvips/cplusplus/VInterpolate.cpp
Original file line number Diff line number Diff line change
@@ -60,17 +60,4 @@ VInterpolate::new_from_name( const char *name, VOption *options )
return( out );
}

VOption *
VOption::set( const char *name, const VInterpolate value )
{
Pair *pair = new Pair( name );

pair->input = true;
g_value_init( &pair->value, VIPS_TYPE_INTERPOLATE );
g_value_set_object( &pair->value, value.get_interpolate() );
options.push_back( pair );

return( this );
}

VIPS_NAMESPACE_END
Loading