Compare commits

..

14 Commits

Author SHA1 Message Date
Lovell Fuller
b2a0b8c0f0 Release v0.27.0 2020-12-22 11:50:23 +00:00
Lovell Fuller
4debc46d0e Docs: add AVIF to supported formats 2020-12-22 11:47:54 +00:00
Lovell Fuller
f4e259d10f Pre-release v0.27.0-beta1 (prebuild test) 2020-12-21 21:14:15 +00:00
Lovell Fuller
774d78228e Docs: update performance test results 2020-12-21 21:00:44 +00:00
Lovell Fuller
0e62bde5c3 Update (and pin) benchmark module versions 2020-12-21 20:26:57 +00:00
Lovell Fuller
2bbd9b23e6 Add new leak test suppressions (fontconfig, gsf, rsvg) 2020-12-21 11:24:34 +00:00
Manan Jadhav
02676140e8 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>
2020-12-20 17:36:39 +00:00
Lovell Fuller
182beaa4a1 Docs: add note about AVIF images smaller than 16x16 2020-12-20 17:17:49 +00:00
Lovell Fuller
7c08a09529 Add new leak test suppressions (rsvg, vips, heif, aom) 2020-12-20 17:05:37 +00:00
Lovell Fuller
ef964b5472 Ensure all platforms use fontconfig to render #2399 2020-12-20 10:23:26 +00:00
Lovell Fuller
ee54ce9913 Upgrade to stable libvips v8.10.5 prebuild 2020-12-20 09:51:33 +00:00
Lovell Fuller
e59e146887 CI: migrate x64 Linux, macOS and Windows to GitHub Actions 2020-12-18 17:39:11 +00:00
Lovell Fuller
103ec0d58f 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
2020-12-18 17:32:16 +00:00
Lovell Fuller
a0d89ed514 Add link to documentation on new issue page 2020-12-09 15:01:14 +00:00
36 changed files with 636 additions and 357 deletions

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -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

85
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,85 @@
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: 10
coverage: true
prebuild: true
- os: ubuntu-20.04
container: centos:7
nodejs_version: 12
- os: ubuntu-20.04
container: centos:7
nodejs_version: 14
- os: ubuntu-20.04
container: centos:7
nodejs_version: 15
- os: ubuntu-20.04
container: node:10-alpine3.11
prebuild: true
- os: ubuntu-20.04
container: node:12-alpine3.11
- os: ubuntu-20.04
container: node:14-alpine3.11
- os: ubuntu-20.04
container: node:15-alpine3.11
- os: macos-10.15
nodejs_version: 10
prebuild: true
- os: macos-10.15
nodejs_version: 12
- os: macos-10.15
nodejs_version: 14
- os: macos-10.15
nodejs_version: 15
- os: windows-2019
nodejs_version: 10
prebuild: true
- os: windows-2019
nodejs_version: 12
- os: windows-2019
nodejs_version: 14
- os: windows-2019
nodejs_version: 15
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 gcc-c++ make git python3 nodejs
- 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@v1
with:
node-version: ${{ matrix.nodejs_version }}
- 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 3

View File

@@ -1,104 +1,19 @@
jobs:
include:
- name: "Linux x64 (CentOS 7, glibc 2.17) - Node.js 10"
os: linux
dist: bionic
language: shell
before_install:
- sudo docker run -dit --name sharp --env CI --env TRAVIS_TAG --env prebuild_upload --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp centos:7
- sudo docker exec sharp bash -c "curl -sL https://rpm.nodesource.com/setup_10.x | bash -"
- sudo docker exec sharp yum install -y gcc-c++ make git nodejs
install: sudo docker exec sharp bash -c "npm install --build-from-source --unsafe-perm"
script: sudo docker exec sharp bash -c "npm test"
- name: "Linux x64 (CentOS 7, glibc 2.17) - Node.js 12"
os: linux
dist: bionic
language: shell
before_install:
- sudo docker run -dit --name sharp --env CI --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp centos:7
- sudo docker exec sharp bash -c "curl -sL https://rpm.nodesource.com/setup_12.x | bash -"
- sudo docker exec sharp yum install -y gcc-c++ make git nodejs
install: sudo docker exec sharp bash -c "npm install --build-from-source --unsafe-perm"
script: sudo docker exec sharp bash -c "npm test"
- name: "Linux x64 (CentOS 7, glibc 2.17) - Node.js 14"
os: linux
dist: bionic
language: shell
before_install:
- sudo docker run -dit --name sharp --env CI --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp centos:7
- sudo docker exec sharp bash -c "curl -sL https://rpm.nodesource.com/setup_14.x | bash -"
- sudo docker exec sharp yum install -y gcc-c++ make git nodejs
install: sudo docker exec sharp bash -c "npm install --build-from-source --unsafe-perm"
script: sudo docker exec sharp bash -c "npm test"
- name: "Linux x64 (CentOS 7, glibc 2.17) - Node.js 15"
os: linux
dist: bionic
language: shell
before_install:
- sudo chown 0.0 ${PWD}
- sudo docker run -dit --name sharp --env CI --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp centos:7
- sudo docker exec sharp bash -c "curl -sL https://rpm.nodesource.com/setup_15.x | bash -"
- sudo docker exec sharp yum install -y gcc-c++ make git nodejs
install: sudo docker exec sharp bash -c "npm install --build-from-source --unsafe-perm"
script: sudo docker exec sharp bash -c "npm test"
- name: "Linux x64 (Alpine 3.9, musl 1.1.20) - Node.js 10"
os: linux
dist: bionic
language: shell
before_install:
- sudo docker run -dit --name sharp --env CI --env TRAVIS_TAG --env prebuild_upload --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp node:10.17.0-alpine3.9 # https://github.com/nodejs/docker-node/issues/1158
- sudo docker exec sharp apk add build-base git python2 --update-cache
install: sudo docker exec sharp sh -c "npm install --build-from-source --unsafe-perm"
script: sudo docker exec sharp sh -c "npm test"
- name: "Linux x64 (Alpine 3.11, musl 1.1.20) - Node.js 12"
os: linux
dist: bionic
language: shell
before_install:
- sudo docker run -dit --name sharp --env CI --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp node:12.0-alpine
- sudo docker exec sharp apk add build-base git python2 --update-cache
install: sudo docker exec sharp sh -c "npm install --build-from-source --unsafe-perm"
script: sudo docker exec sharp sh -c "npm test"
- name: "Linux x64 (Alpine 3.11, musl 1.1.20) - Node.js 14"
os: linux
dist: bionic
language: shell
before_install:
- sudo docker run -dit --name sharp --env CI --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp node:14.0-alpine
- sudo docker exec sharp apk add build-base git python2 --update-cache
install: sudo docker exec sharp sh -c "npm install --build-from-source --unsafe-perm"
script: sudo docker exec sharp sh -c "npm test"
- name: "Linux x64 (Alpine 3.11, musl 1.1.20) - Node.js 15"
os: linux
dist: bionic
language: shell
before_install:
- sudo chown 0.0 ${PWD}
- sudo docker run -dit --name sharp --env CI --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp node:15.0-alpine
- sudo docker exec sharp apk add build-base git python2 --update-cache
install: sudo docker exec sharp sh -c "npm install --build-from-source --unsafe-perm"
script: sudo docker exec sharp sh -c "npm test"
- name: "Linux ARM64v8 (Debian 11, glibc 2.29) - Node.js 10"
arch: arm64
os: linux
dist: bionic
language: shell
before_install:
- sudo docker run -dit --name sharp --env CI --env TRAVIS_TAG --env prebuild_upload --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp arm64v8/debian:bullseye
- sudo docker exec sharp sh -c "apt-get update && apt-get install -y build-essential git python2 curl"
- 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_10.x sid main' >/etc/apt/sources.list.d/nodesource.list"
- sudo docker exec sharp sh -c "apt-get update && apt-get install -y nodejs=10.*"
install: sudo docker exec sharp sh -c "npm install --build-from-source --unsafe-perm"
script: sudo docker exec sharp sh -c "npm test"
after_success: "[[ -n $TRAVIS_TAG ]] && sudo docker exec --env prebuild_upload sharp sh -c \"npx prebuild --runtime napi --target 3\""
- name: "Linux ARM64v8 (Debian 11, glibc 2.29) - Node.js 12"
arch: arm64
@@ -106,8 +21,8 @@ jobs:
dist: bionic
language: shell
before_install:
- sudo docker run -dit --name sharp --env CI --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp arm64v8/debian:bullseye
- sudo docker exec sharp sh -c "apt-get update && apt-get install -y build-essential git python2 curl"
- sudo docker 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"
@@ -120,8 +35,8 @@ jobs:
dist: bionic
language: shell
before_install:
- sudo docker run -dit --name sharp --env CI --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp arm64v8/debian:bullseye
- sudo docker exec sharp sh -c "apt-get update && apt-get install -y build-essential git python2 curl"
- sudo docker 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_14.x sid main' >/etc/apt/sources.list.d/nodesource.list"
- sudo docker exec sharp sh -c "apt-get update && apt-get install -y nodejs"
@@ -135,55 +50,13 @@ jobs:
language: shell
before_install:
- sudo chown 0.0 ${PWD}
- sudo docker run -dit --name sharp --env CI --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp arm64v8/debian:bullseye
- sudo docker exec sharp sh -c "apt-get update && apt-get install -y build-essential git python2 curl"
- sudo docker 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_15.x sid main' >/etc/apt/sources.list.d/nodesource.list"
- sudo docker exec sharp sh -c "apt-get update && apt-get install -y nodejs"
install: sudo docker exec sharp sh -c "npm install --build-from-source --unsafe-perm"
script: sudo docker exec sharp sh -c "npm test"
- name: "macOS (10.13) - Node.js 10"
os: osx
osx_image: xcode10.1
language: node_js
node_js: "10"
install: npm install --build-from-source
- name: "macOS (10.13) - Node.js 12"
os: osx
osx_image: xcode10.1
language: node_js
node_js: "12"
before_install: unset prebuild_upload
install: npm install --build-from-source
- name: "macOS (10.13) - Node.js 14"
os: osx
osx_image: xcode10.1
language: node_js
node_js: "14"
before_install: unset prebuild_upload
install: npm install --build-from-source
- name: "macOS (10.13) - Node.js 15"
os: osx
osx_image: xcode10.1
language: node_js
node_js: "15"
before_install: unset prebuild_upload
install: npm install --build-from-source
- name: "Unit test coverage report"
os: linux
dist: bionic
language: node_js
node_js: "14"
before_install: unset prebuild_upload
install: npm install --build-from-source
after_success:
- npm install coveralls
- cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js
cache:
npm: false

View File

@@ -4,7 +4,7 @@
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 and AVIF images of varying dimensions.
Resizing an image is typically 4x-5x faster than using the
quickest ImageMagick and GraphicsMagick settings

View File

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

View File

@@ -4,7 +4,7 @@
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, AVIF and WebP images of varying dimensions.
Resizing an image is typically 4x-5x faster than using the
quickest ImageMagick and GraphicsMagick settings
@@ -21,9 +21,9 @@ do not require any additional install or runtime dependencies.
### Formats
This module supports reading JPEG, PNG, WebP, TIFF, GIF and SVG images.
This module supports reading JPEG, PNG, WebP, AVIF, TIFF, GIF 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, AVIF and TIFF formats as well as uncompressed raw pixel data.
Streams, Buffer objects and the filesystem can be used for input and output.

View File

@@ -70,7 +70,7 @@ 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.
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

View File

@@ -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, 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.
@@ -14,9 +14,9 @@ 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.
a Buffer containing JPEG, PNG, WebP, AVIF, GIF, SVG, TIFF or 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][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`)
@@ -26,8 +26,8 @@ Implements the [stream.Duplex][1] class.
- `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.pages` **[number][6]** 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][6]** page number to start extracting from for multi-page input (GIF, WebP, AVIF, 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.

View File

@@ -5,7 +5,7 @@
Write output image data to a file.
If an explicit output format is not selected, it will be inferred from the extension,
with JPEG, PNG, WebP, TIFF, DZI, and libvips' V format supported.
with JPEG, PNG, WebP, AVIF, TIFF, DZI, and libvips' V format supported.
Note that raw pixel data is only supported for buffer output.
By default all metadata will be removed, which includes EXIF-based orientation.
@@ -42,7 +42,7 @@ Returns **[Promise][5]&lt;[Object][6]>** when no callback is provided
## toBuffer
Write output to a Buffer.
JPEG, PNG, WebP, TIFF and RAW output are supported.
JPEG, PNG, WebP, AVIF, TIFF and raw pixel data output are supported.
If no explicit format is set, the output format will match the input image, except GIF and SVG input which become PNG output.
@@ -299,23 +299,43 @@ sharp('input.svg')
Returns **Sharp**
## heif
## avif
Use these HEIF options for output image.
Use these AVIF options for output image.
Support for HEIF (HEIC/AVIF) is experimental.
Do not use this in production systems.
Requires a custom, globally-installed libvips compiled with support for libheif.
Most versions of libheif support only the patent-encumbered HEVC compression format.
Whilst it is possible to create AVIF images smaller than 16x16 pixels,
most web browsers do not display these properly.
### Parameters
- `options` **[Object][6]?** output options
- `options.quality` **[number][9]** quality, integer 1-100 (optional, default `80`)
- `options.compression` **[boolean][7]** compression format: hevc, avc, jpeg, av1 (optional, default `'hevc'`)
- `options.quality` **[number][9]** quality, integer 1-100 (optional, default `50`)
- `options.lossless` **[boolean][7]** use lossless compression (optional, default `false`)
- `options.speed` **[boolean][7]** CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest) (optional, default `5`)
- Throws **[Error][4]** Invalid options
Returns **Sharp**
**Meta**
- **since**: 0.27.0
## heif
Use these HEIF options for output image.
Support for patent-encumbered HEIC images requires the use of a
globally-installed libvips compiled with support for libheif, libde265 and x265.
### Parameters
- `options` **[Object][6]?** output options
- `options.quality` **[number][9]** quality, integer 1-100 (optional, default `50`)
- `options.compression` **[boolean][7]** compression format: av1, hevc (optional, default `'av1'`)
- `options.lossless` **[boolean][7]** use lossless compression (optional, default `false`)
- `options.speed` **[boolean][7]** CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest) (optional, default `5`)
- Throws **[Error][4]** Invalid options

View File

@@ -1,5 +1,22 @@
# Changelog
## v0.27 - *avif*
Requires libvips v8.10.5
### 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

View File

@@ -4,7 +4,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="Resize large images in common formats to smaller, web-friendly JPEG, PNG and WebP images of varying dimensions">
<meta name="description" content="Resize large images in common formats to smaller, web-friendly JPEG, PNG, WebP and AVIF images of varying dimensions">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; object-src 'none'; style-src 'unsafe-inline';
img-src 'unsafe-inline' data: https://pixel.plumbing/px/ https://cdn.jsdelivr.net/gh/lovell/ https://www.google-analytics.com;
connect-src 'self' https://cdn.jsdelivr.net/gh/lovell/ https://www.google-analytics.com;
@@ -19,7 +19,7 @@
<link rel="apple-touch-icon-precomposed" sizes="72x72" href="https://pixel.plumbing/px/72x72/sharp-logo.svg">
<link rel="apple-touch-icon-precomposed" href="https://pixel.plumbing/px/57x57/sharp-logo.svg">
<link rel="author" href="/humans.txt" type="text/plain">
<link rel="preload" href="https://cdn.jsdelivr.net/gh/lovell/sharp@v0.26.3/docs/README.md" as="fetch" type="text/markdown" crossorigin>
<link rel="preload" href="https://cdn.jsdelivr.net/gh/lovell/sharp@v0.27.0/docs/README.md" as="fetch" type="text/markdown" crossorigin>
<link rel="preload" href="https://cdn.jsdelivr.net/gh/lovell/sharp@master/docs/image/sharp-logo.svg" as="image" type="image/svg+xml" crossorigin>
<link rel="dns-prefetch" href="https://pixel.plumbing">
<link rel="dns-prefetch" href="https://www.google-analytics.com">
@@ -139,7 +139,7 @@
docuteApiTitlePlugin,
docuteApiSearchPlugin
],
sourcePath: 'https://cdn.jsdelivr.net/gh/lovell/sharp@v0.26.3/docs',
sourcePath: 'https://cdn.jsdelivr.net/gh/lovell/sharp@v0.27.0/docs',
nav: [
{
title: 'Funding',

View File

@@ -20,21 +20,24 @@ Node.js v10+ on the most common platforms:
* macOS x64 (>= 10.13)
* Linux x64 (glibc >= 2.17, musl >= 1.1.24)
* Linux ARM64 (glibc >= 2.29)
* Windows
* Windows x64
* Windows x86
A 7-8MB tarball containing libvips and its most commonly used dependencies
A ~9MB tarball containing libvips and its most commonly used dependencies
is downloaded via HTTPS and stored within `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 (input) and SVG (input) image formats.
The following platforms have prebuilt libvips but not sharp:
* Linux ARMv6
* Linux ARMv7 (glibc >= 2.28)
* Windows ARM64
The following platforms require compilation of both libvips and sharp from source:
* macOS ARM64
* Linux x86
* Linux x64 (glibc <= 2.16, includes RHEL/CentOS 6)
* Linux ARM64 (glibc <= 2.28, musl)
@@ -69,7 +72,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:

View File

@@ -4,11 +4,11 @@ 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.5 - 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.
* sharp v0.27.0 / libvips v8.10.5 - Caching within libvips disabled to ensure a fair comparison.
## The task
@@ -18,22 +18,22 @@ 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 [c5d.large](https://aws.amazon.com/ec2/instance-types/c5/) (2x Xeon Platinum 8275CL CPU @ 3.00GHz)
* Ubuntu 20.10 (ami-046cdbcee95cdd75c)
* Node.js v14.15.3
## 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.77 | 1.0 |
| mapnik | buffer | buffer | 3.39 | 4.4 |
| gm | buffer | buffer | 4.30 | 5.6 |
| gm | file | file | 4.33 | 5.6 |
| imagemagick | file | file | 4.39 | 5.7 |
| sharp | stream | stream | 23.81 | 30.9 |
| sharp | file | file | 25.09 | 32.6 |
| sharp | buffer | buffer | 25.60 | 33.2 |
Greater libvips performance can be expected with caching enabled (default)
and using 4+ core machines, especially those with larger L1/L2 CPU caches.

File diff suppressed because one or more lines are too long

View File

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

View File

@@ -85,7 +85,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).

View File

@@ -105,8 +105,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 +126,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)) {

View File

@@ -40,7 +40,7 @@ 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, 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.
@@ -91,9 +91,9 @@ const debuglog = util.debuglog('sharp');
* 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.
* a Buffer containing JPEG, PNG, WebP, AVIF, GIF, SVG, TIFF or 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.
@@ -103,8 +103,8 @@ const debuglog = util.debuglog('sharp');
* @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.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.
@@ -233,9 +233,10 @@ const Sharp = function (input, options) {
tiffTileWidth: 256,
tiffXres: 1.0,
tiffYres: 1.0,
heifQuality: 80,
heifQuality: 50,
heifLossless: false,
heifCompression: 'hevc',
heifCompression: 'av1',
heifSpeed: 5,
tileSize: 256,
tileOverlap: 0,
tileContainer: 'fs',

View File

@@ -6,6 +6,7 @@ const sharp = require('../build/Release/sharp.node');
const formats = new Map([
['heic', 'heif'],
['heif', 'heif'],
['avif', 'avif'],
['jpeg', 'jpeg'],
['jpg', 'jpeg'],
['png', 'png'],
@@ -19,7 +20,7 @@ const formats = new Map([
* Write output image data to a file.
*
* If an explicit output format is not selected, it will be inferred from the extension,
* with JPEG, PNG, WebP, TIFF, DZI, and libvips' V format supported.
* with JPEG, PNG, WebP, AVIF, TIFF, DZI, and libvips' V format supported.
* Note that raw pixel data is only supported for buffer output.
*
* By default all metadata will be removed, which includes EXIF-based orientation.
@@ -71,7 +72,7 @@ function toFile (fileOut, callback) {
/**
* Write output to a Buffer.
* JPEG, PNG, WebP, TIFF and RAW output are supported.
* JPEG, PNG, WebP, AVIF, TIFF and raw pixel data output are supported.
*
* If no explicit format is set, the output format will match the input image, except GIF and SVG input which become PNG output.
*
@@ -556,29 +557,42 @@ function tiff (options) {
return this._updateFormatOut('tiff', options);
}
/**
* Use these AVIF options for output image.
*
* Whilst it is possible to create AVIF images smaller than 16x16 pixels,
* most web browsers do not display these properly.
*
* @since 0.27.0
*
* @param {Object} [options] - output options
* @param {number} [options.quality=50] - quality, integer 1-100
* @param {boolean} [options.lossless=false] - use lossless compression
* @param {boolean} [options.speed=5] - CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest)
* @returns {Sharp}
* @throws {Error} Invalid options
*/
function avif (options) {
return this.heif({ ...options, compression: 'av1' });
}
/**
* Use these HEIF options for output image.
*
* Support for HEIF (HEIC/AVIF) is experimental.
* Do not use this in production systems.
*
* Requires a custom, globally-installed libvips compiled with support for libheif.
*
* Most versions of libheif support only the patent-encumbered HEVC compression format.
* Support for patent-encumbered HEIC images requires the use of a
* globally-installed libvips compiled with support for libheif, libde265 and x265.
*
* @since 0.23.0
*
* @param {Object} [options] - output options
* @param {number} [options.quality=80] - quality, integer 1-100
* @param {boolean} [options.compression='hevc'] - compression format: hevc, avc, jpeg, av1
* @param {number} [options.quality=50] - quality, integer 1-100
* @param {boolean} [options.compression='av1'] - compression format: av1, hevc
* @param {boolean} [options.lossless=false] - use lossless compression
* @param {boolean} [options.speed=5] - CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest)
* @returns {Sharp}
* @throws {Error} Invalid options
*/
function heif (options) {
if (!this.constructor.format.heif.output.buffer) {
throw new Error('The heif operation requires libvips to have been installed with support for libheif');
}
if (is.object(options)) {
if (is.defined(options.quality)) {
if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
@@ -595,10 +609,17 @@ function heif (options) {
}
}
if (is.defined(options.compression)) {
if (is.string(options.compression) && is.inArray(options.compression, ['hevc', 'avc', 'jpeg', 'av1'])) {
if (is.string(options.compression) && is.inArray(options.compression, ['av1', 'hevc'])) {
this.options.heifCompression = options.compression;
} else {
throw is.invalidParameterError('compression', 'one of: hevc, avc, jpeg, av1', options.compression);
throw is.invalidParameterError('compression', 'one of: av1, hevc', options.compression);
}
}
if (is.defined(options.speed)) {
if (is.integer(options.speed) && is.inRange(options.speed, 0, 8)) {
this.options.heifSpeed = options.speed;
} else {
throw is.invalidParameterError('speed', 'integer between 0 and 8', options.speed);
}
}
}
@@ -891,6 +912,7 @@ module.exports = function (Sharp) {
png,
webp,
tiff,
avif,
heif,
gif,
raw,

View File

@@ -1,7 +1,7 @@
{
"name": "sharp",
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP and TIFF images",
"version": "0.26.3",
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, AVIF and TIFF images",
"version": "0.27.0",
"author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://github.com/lovell/sharp",
"contributors": [
@@ -70,12 +70,14 @@
"Roman Malieiev <aromaleev@gmail.com>",
"Tomas Szabo <tomas.szabo@deftomat.com>",
"Robert O'Rourke <robert@o-rourke.org>",
"Guillermo Alfonso Varela Chouciño <guillevch@gmail.com>"
"Guillermo Alfonso Varela Chouciño <guillevch@gmail.com>",
"Christian Flintrup <chr@gigahost.dk>",
"Manan Jadhav <manan@motionden.com>"
],
"scripts": {
"install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node-gyp rebuild && node install/dll-copy)",
"clean": "rm -rf node_modules/ build/ vendor/ .nyc_output/ coverage/ test/fixtures/output.*",
"test": "semistandard && cpplint && npm run test-unit && npm run test-licensing && node install/prebuild-ci",
"test": "semistandard && cpplint && npm run test-unit && npm run test-licensing",
"test-unit": "nyc --reporter=lcov --branches=99 mocha --slow=5000 --timeout=60000 ./test/unit/*.js",
"test-licensing": "license-checker --production --summary --onlyAllow=\"Apache-2.0;BSD;ISC;MIT\"",
"test-coverage": "./test/coverage/report.sh",
@@ -88,7 +90,6 @@
"files": [
"binding.gyp",
"install/**",
"!install/prebuild-ci.js",
"lib/**",
"src/**"
],
@@ -100,6 +101,7 @@
"jpeg",
"png",
"webp",
"avif",
"tiff",
"gif",
"svg",
@@ -116,17 +118,17 @@
"array-flatten": "^3.0.0",
"color": "^3.1.3",
"detect-libc": "^1.0.3",
"node-addon-api": "^3.0.2",
"node-addon-api": "^3.1.0",
"npmlog": "^4.1.2",
"prebuild-install": "^6.0.0",
"semver": "^7.3.2",
"semver": "^7.3.4",
"simple-get": "^4.0.0",
"tar-fs": "^2.1.1",
"tunnel-agent": "^0.6.0"
},
"devDependencies": {
"async": "^3.2.0",
"cc": "^2.0.1",
"cc": "^3.0.1",
"decompress-zip": "^0.3.2",
"documentation": "^13.1.0",
"exif-reader": "^1.0.3",
@@ -141,7 +143,7 @@
},
"license": "Apache-2.0",
"config": {
"libvips": "8.10.0",
"libvips": "8.10.5",
"runtime": "napi",
"target": 3
},

View File

@@ -658,26 +658,18 @@ namespace sharp {
int top = 0;
// assign only if valid
if (x >= 0 && x < (inWidth - outWidth)) {
if (x < (inWidth - outWidth)) {
left = x;
} else if (x >= (inWidth - outWidth)) {
left = inWidth - outWidth;
}
if (y >= 0 && y < (inHeight - outHeight)) {
if (y < (inHeight - outHeight)) {
top = y;
} else if (y >= (inHeight - outHeight)) {
top = inHeight - outHeight;
}
// the resulting left and top could have been outside the image after calculation from bottom/right edges
if (left < 0) {
left = 0;
}
if (top < 0) {
top = 0;
}
return std::make_tuple(left, top);
}

View File

@@ -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 < 10) || \
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 10 && VIPS_MICRO_VERSION < 5)
#error "libvips version 8.10.5+ is required - please see https://sharp.pixelplumbing.com/install"
#endif
#if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6)))

View File

@@ -570,7 +570,7 @@ class PipelineWorker : public Napi::AsyncWorker {
int left;
int top;
compositeImage = compositeImage.replicate(across, down);
if (composite->left >= 0 && composite->top >= 0) {
if (composite->hasOffset) {
std::tie(left, top) = sharp::CalculateCrop(
compositeImage.width(), compositeImage.height(), image.width(), image.height(),
composite->left, composite->top);
@@ -592,7 +592,7 @@ class PipelineWorker : public Napi::AsyncWorker {
// Calculate position
int left;
int top;
if (composite->left >= 0 && composite->top >= 0) {
if (composite->hasOffset) {
// Composite image at given offsets
std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),
compositeImage.width(), compositeImage.height(), composite->left, composite->top);
@@ -837,6 +837,7 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("strip", !baton->withMetadata)
->set("compression", baton->heifCompression)
->set("Q", baton->heifQuality)
->set("speed", baton->heifSpeed)
->set("lossless", baton->heifLossless)));
baton->bufferOut = static_cast<char*>(area->data);
baton->bufferOutLength = area->length;
@@ -885,7 +886,7 @@ class PipelineWorker : public Napi::AsyncWorker {
bool const isV = sharp::IsV(baton->fileOut);
bool const mightMatchInput = baton->formatOut == "input";
bool const willMatchInput = mightMatchInput &&
!(isJpeg || isPng || isWebp || isGif || isTiff || isDz || isDzZip || isV);
!(isJpeg || isPng || isWebp || isGif || isTiff || isHeif || isDz || isDzZip || isV);
if (baton->formatOut == "jpeg" || (mightMatchInput && isJpeg) ||
(willMatchInput && inputImageType == sharp::ImageType::JPEG)) {
@@ -966,13 +967,11 @@ class PipelineWorker : public Napi::AsyncWorker {
} else if (baton->formatOut == "heif" || (mightMatchInput && isHeif) ||
(willMatchInput && inputImageType == sharp::ImageType::HEIF)) {
// Write HEIF to file
if (sharp::IsAvif(baton->fileOut)) {
baton->heifCompression = VIPS_FOREIGN_HEIF_COMPRESSION_AV1;
}
image.heifsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata)
->set("Q", baton->heifQuality)
->set("compression", baton->heifCompression)
->set("speed", baton->heifSpeed)
->set("lossless", baton->heifLossless));
baton->formatOut = "heif";
} else if (baton->formatOut == "dz" || isDz || isDzZip) {
@@ -1254,6 +1253,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
composite->gravity = sharp::AttrAsUint32(compositeObject, "gravity");
composite->left = sharp::AttrAsInt32(compositeObject, "left");
composite->top = sharp::AttrAsInt32(compositeObject, "top");
composite->hasOffset = sharp::AttrAsBool(compositeObject, "hasOffset");
composite->tile = sharp::AttrAsBool(compositeObject, "tile");
composite->premultiplied = sharp::AttrAsBool(compositeObject, "premultiplied");
baton->composite.push_back(composite);
@@ -1395,6 +1395,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
baton->heifCompression = static_cast<VipsForeignHeifCompression>(
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_HEIF_COMPRESSION,
sharp::AttrAsStr(options, "heifCompression").data()));
baton->heifSpeed = sharp::AttrAsUint32(options, "heifSpeed");
// Animated output
if (sharp::HasAttr(options, "pageHeight")) {

View File

@@ -40,6 +40,7 @@ struct Composite {
int gravity;
int left;
int top;
bool hasOffset;
bool tile;
bool premultiplied;
@@ -47,8 +48,9 @@ struct Composite {
input(nullptr),
mode(VIPS_BLEND_MODE_OVER),
gravity(0),
left(-1),
top(-1),
left(0),
top(0),
hasOffset(false),
tile(false),
premultiplied(false) {}
};
@@ -159,6 +161,7 @@ struct PipelineBaton {
double tiffYres;
int heifQuality;
VipsForeignHeifCompression heifCompression;
int heifSpeed;
bool heifLossless;
std::string err;
bool withMetadata;
@@ -276,8 +279,9 @@ struct PipelineBaton {
tiffTileWidth(256),
tiffXres(1.0),
tiffYres(1.0),
heifQuality(80),
heifCompression(VIPS_FOREIGN_HEIF_COMPRESSION_HEVC),
heifQuality(50),
heifCompression(VIPS_FOREIGN_HEIF_COMPRESSION_AV1),
heifSpeed(5),
heifLossless(false),
withMetadata(false),
withMetadataOrientation(-1),

View File

@@ -22,6 +22,8 @@
#include "stats.h"
static void* sharp_vips_init(void*) {
g_setenv("VIPS_MIN_STACK_SIZE", "2m", FALSE);
g_setenv("PANGOCAIRO_BACKEND", "fontconfig", FALSE);
vips_init("sharp");
return nullptr;
}

View File

@@ -8,16 +8,16 @@
"test": "node perf && node random && node parallel"
},
"devDependencies": {
"async": "^3.1.0",
"benchmark": "^2.1.4",
"gm": "^1.23.1",
"imagemagick": "^0.1.3",
"jimp": "^0.16.0",
"mapnik": "^4.5.2",
"semver": "^7.1.1"
"async": "3.2.0",
"benchmark": "2.1.4",
"gm": "1.23.1",
"imagemagick": "0.1.3",
"jimp": "0.16.1",
"mapnik": "4.5.5",
"semver": "7.3.4"
},
"license": "Apache-2.0",
"engines": {
"node": ">=10.16.0"
"node": "14"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

@@ -109,6 +109,7 @@ module.exports = {
inputSvg: getPath('check.svg'), // http://dev.w3.org/SVG/tools/svgweb/samples/svg-files/check.svg
inputSvgSmallViewBox: getPath('circle.svg'),
inputSvgWithEmbeddedImages: getPath('struct-image-04-t.svg'), // https://dev.w3.org/SVG/profiles/1.2T/test/svg/struct-image-04-t.svg
inputAvif: getPath('cosmos_frame12924_yuv420_10bpc_bt2020_pq_q50.avif'), // CC by-nc-nd https://github.com/AOMediaCodec/av1-avif/tree/master/testFiles/Netflix
inputJPGBig: getPath('flowers.jpeg'),

View File

@@ -189,6 +189,24 @@
...
fun:gsf_output_write
}
{
param_gsf_new_do_write
Memcheck:Param
write(buf)
...
fun:new_do_write
...
fun:gsf_output_close
}
{
param_gsf_output_write
Memcheck:Param
write(buf)
...
fun:new_do_write
...
fun:gsf_output_write
}
# fontconfig
{
@@ -218,6 +236,84 @@
fun:XML_ParseBuffer
obj:*/libfontconfig.so.*
}
{
leak_fontconfig_FcInitLoadConfigAndFonts
Memcheck:Leak
match-leak-kinds: definite
fun:malloc
...
fun:XML_ParseBuffer
...
fun:FcInitLoadConfigAndFonts
}
# heif
{
cond_heif_encode_image
Memcheck:Cond
...
fun:heif_context_encode_image
}
{
value8_heif_encode_image
Memcheck:Value8
...
fun:heif_context_encode_image
}
{
cond_heif_aom_codec_encode
Memcheck:Cond
...
fun:aom_codec_encode
}
{
value8_heif_aom_codec_encode
Memcheck:Value8
...
fun:aom_codec_encode
}
{
value1_heif_aom_codec_encode
Memcheck:Value1
...
fun:aom_codec_encode
}
{
cond_heif_av1_encode_frame
Memcheck:Cond
...
fun:av1_encode_frame
}
{
value8_heif_av1_encode_frame
Memcheck:Value8
...
fun:av1_encode_frame
}
{
cond_heif_context_write
Memcheck:Cond
...
fun:heif_context_write
}
{
value8_heif_context_write
Memcheck:Value8
...
fun:heif_context_write
}
{
cond_heif_context_read
Memcheck:Cond
...
fun:heif_context_read_from_reader
}
{
value8_heif_context_read
Memcheck:Value8
...
fun:heif_context_read_from_reader
}
# libvips
{
@@ -306,6 +402,30 @@
...
fun:start_thread
}
{
cond_libvips_source_read
Memcheck:Cond
...
fun:vips_source_read
}
{
value8_libvips_source_read
Memcheck:Value8
...
fun:vips_source_read
}
{
cond_libvips_target_finish
Memcheck:Cond
...
fun:vips_target_finish
}
{
value8_libvips_target_finish
Memcheck:Value8
...
fun:vips_target_finish
}
{
leak_libvips_init
Memcheck:Leak
@@ -321,8 +441,34 @@
fun:malloc
...
fun:rsvg_rust_handle_new_from_stream_sync
}
{
leak_rsvg_rsvg_rust_handle_new_from_gfile_sync
Memcheck:Leak
match-leak-kinds: definite
fun:malloc
...
fun:vips_object_build
fun:rsvg_rust_handle_new_from_gfile_sync
}
{
leak_rsvg_rust_handle_new_from_stream_sync
Memcheck:Leak
match-leak-kinds: possible
fun:malloc
...
fun:xmlParseElement
...
fun:rsvg_rust_handle_new_from_stream_sync
}
{
leak_rsvg_rust_handle_new_from_gfile_sync
Memcheck:Leak
match-leak-kinds: possible
fun:malloc
...
fun:xmlParseElement
...
fun:rsvg_rust_handle_new_from_gfile_sync
}
# libuv warnings
@@ -691,3 +837,27 @@
...
fun:_ZN12v8_inspector10toString16ERKNS_10StringViewE
}
{
cond_v8_Builtins_InterpreterEntryTrampoline
Memcheck:Cond
...
fun:Builtins_InterpreterEntryTrampoline
}
{
cond_v8_ZN2v88internal18ArrayBufferSweeper9SweepFullEv
Memcheck:Cond
...
fun:_ZN2v88internal18ArrayBufferSweeper9SweepFullEv
}
{
cond_v8_ZN4node11Environment27RunAndClearNativeImmediatesEb
Memcheck:Cond
...
fun:_ZN4node11Environment27RunAndClearNativeImmediatesEb
}
{
cond_v8_ZN2v88internal18ArrayBufferSweeper10ReleaseAllEv
Memcheck:Cond
...
fun:_ZN2v88internal18ArrayBufferSweeper10ReleaseAllEv
}

84
test/unit/avif.js Normal file
View File

@@ -0,0 +1,84 @@
'use strict';
const assert = require('assert');
const sharp = require('../../');
const { inputAvif, inputJpg } = require('../fixtures');
describe('AVIF', () => {
it('called without options does not throw an error', () => {
assert.doesNotThrow(() => {
sharp().avif();
});
});
it('can passthrough AVIF', async () => {
const data = await sharp(inputAvif)
.resize(32)
.toBuffer();
const metadata = await sharp(data)
.metadata();
const { size, ...metadataWithoutSize } = metadata;
assert.deepStrictEqual(metadataWithoutSize, {
channels: 3,
depth: 'uchar',
format: 'heif',
hasAlpha: false,
hasProfile: false,
height: 12,
isProgressive: false,
pageHeight: 12,
pagePrimary: 0,
pages: 1,
space: 'srgb',
width: 32
});
});
it('can convert AVIF to JPEG', async () => {
const data = await sharp(inputAvif)
.resize(32)
.jpeg()
.toBuffer();
const metadata = await sharp(data)
.metadata();
const { size, ...metadataWithoutSize } = metadata;
assert.deepStrictEqual(metadataWithoutSize, {
channels: 3,
chromaSubsampling: '4:2:0',
density: 72,
depth: 'uchar',
format: 'jpeg',
hasAlpha: false,
hasProfile: false,
height: 13,
isProgressive: false,
space: 'srgb',
width: 32
});
});
it('can convert JPEG to AVIF', async () => {
const data = await sharp(inputJpg)
.resize(32)
.avif()
.toBuffer();
const metadata = await sharp(data)
.metadata();
const { size, ...metadataWithoutSize } = metadata;
assert.deepStrictEqual(metadataWithoutSize, {
channels: 3,
depth: 'uchar',
format: 'heif',
hasAlpha: false,
hasProfile: false,
height: 26,
isProgressive: false,
pageHeight: 26,
pagePrimary: 0,
pages: 1,
space: 'srgb',
width: 32
});
});
});

View File

@@ -172,6 +172,24 @@ describe('composite', () => {
});
});
it('negative offset and gravity', done => {
sharp(fixtures.inputJpg)
.resize(400)
.composite([{
input: fixtures.inputPngWithTransparency16bit,
left: -10,
top: -10,
gravity: 4
}])
.toBuffer((err, data, info) => {
if (err) throw err;
assert.strictEqual('jpeg', info.format);
assert.strictEqual(3, info.channels);
fixtures.assertSimilar(
fixtures.expected('overlay-negative-offset-with-gravity.jpg'), data, done);
});
});
it('offset, gravity and tile', done => {
sharp(fixtures.inputJpg)
.resize(80)
@@ -333,13 +351,25 @@ describe('composite', () => {
it('invalid left', () => {
assert.throws(() => {
sharp().composite([{ input: 'test', left: 0.5 }]);
}, /Expected positive integer for left but received 0.5 of type number/);
}, /Expected integer for left but received 0.5 of type number/);
assert.throws(() => {
sharp().composite([{ input: 'test', left: 'invalid' }]);
}, /Expected integer for left but received invalid of type string/);
assert.throws(() => {
sharp().composite([{ input: 'test', left: 'invalid', top: 10 }]);
}, /Expected integer for left but received invalid of type string/);
});
it('invalid top', () => {
assert.throws(() => {
sharp().composite([{ input: 'test', top: -1 }]);
}, /Expected positive integer for top but received -1 of type number/);
sharp().composite([{ input: 'test', top: 0.5 }]);
}, /Expected integer for top but received 0.5 of type number/);
assert.throws(() => {
sharp().composite([{ input: 'test', top: 'invalid' }]);
}, /Expected integer for top but received invalid of type string/);
assert.throws(() => {
sharp().composite([{ input: 'test', top: 'invalid', left: 10 }]);
}, /Expected integer for top but received invalid of type string/);
});
it('left but no top', () => {

View File

@@ -4,76 +4,65 @@ const assert = require('assert');
const sharp = require('../../');
const formatHeifOutputBuffer = sharp.format.heif.output.buffer;
describe('HEIF (experimental)', () => {
describe('Stubbed without support for HEIF', () => {
before(() => {
sharp.format.heif.output.buffer = false;
});
after(() => {
sharp.format.heif.output.buffer = formatHeifOutputBuffer;
});
it('should throw an error', () => {
assert.throws(() => {
sharp().heif();
});
describe('HEIF', () => {
it('called without options does not throw an error', () => {
assert.doesNotThrow(() => {
sharp().heif();
});
});
describe('Stubbed with support for HEIF', () => {
before(() => {
sharp.format.heif.output.buffer = true;
it('valid quality does not throw an error', () => {
assert.doesNotThrow(() => {
sharp().heif({ quality: 80 });
});
after(() => {
sharp.format.heif.output.buffer = formatHeifOutputBuffer;
});
it('invalid quality should throw an error', () => {
assert.throws(() => {
sharp().heif({ quality: 101 });
});
it('called without options does not throw an error', () => {
assert.doesNotThrow(() => {
sharp().heif();
});
});
it('non-numeric quality should throw an error', () => {
assert.throws(() => {
sharp().heif({ quality: 'fail' });
});
it('valid quality does not throw an error', () => {
assert.doesNotThrow(() => {
sharp().heif({ quality: 50 });
});
});
it('valid lossless does not throw an error', () => {
assert.doesNotThrow(() => {
sharp().heif({ lossless: true });
});
it('invalid quality should throw an error', () => {
assert.throws(() => {
sharp().heif({ quality: 101 });
});
});
it('non-boolean lossless should throw an error', () => {
assert.throws(() => {
sharp().heif({ lossless: 'fail' });
});
it('non-numeric quality should throw an error', () => {
assert.throws(() => {
sharp().heif({ quality: 'fail' });
});
});
it('valid compression does not throw an error', () => {
assert.doesNotThrow(() => {
sharp().heif({ compression: 'hevc' });
});
it('valid lossless does not throw an error', () => {
assert.doesNotThrow(() => {
sharp().heif({ lossless: true });
});
});
it('unknown compression should throw an error', () => {
assert.throws(() => {
sharp().heif({ compression: 'fail' });
});
it('non-boolean lossless should throw an error', () => {
assert.throws(() => {
sharp().heif({ lossless: 'fail' });
});
});
it('invalid compression should throw an error', () => {
assert.throws(() => {
sharp().heif({ compression: 1 });
});
it('valid compression does not throw an error', () => {
assert.doesNotThrow(() => {
sharp().heif({ compression: 'avc' });
});
});
it('valid speed does not throw an error', () => {
assert.doesNotThrow(() => {
sharp().heif({ speed: 6 });
});
it('unknown compression should throw an error', () => {
assert.throws(() => {
sharp().heif({ compression: 'fail' });
});
});
it('out of range speed should throw an error', () => {
assert.throws(() => {
sharp().heif({ speed: 9 });
});
it('invalid compression should throw an error', () => {
assert.throws(() => {
sharp().heif({ compression: 1 });
});
});
it('invalid speed should throw an error', () => {
assert.throws(() => {
sharp().heif({ compression: 'fail' });
});
});
});

View File

@@ -302,7 +302,6 @@ describe('Input/output', function () {
});
it('Fail when input is empty Buffer', function (done) {
if (sharp.format.magick.input.buffer) return this.skip(); // can be removed with libvips 8.10.1+
sharp(Buffer.alloc(0)).toBuffer().then(function () {
assert(false);
done();

View File

@@ -279,10 +279,10 @@ describe('Image Stats', function () {
// red channel
assert.strictEqual(0, stats.channels[0].min);
assert.strictEqual(true, isInRange(stats.channels[0].max, 254, 255));
assert.strictEqual(true, isInAcceptableRange(stats.channels[0].sum, 82506996));
assert.strictEqual(true, isInAcceptableRange(stats.channels[0].squaresSum, 11213984832));
assert.strictEqual(true, isInAcceptableRange(stats.channels[0].mean, 104.36947963892487));
assert.strictEqual(true, isInAcceptableRange(stats.channels[0].stdev, 57.379896254993135));
assert.strictEqual(true, isInAcceptableRange(stats.channels[0].sum, 83291370));
assert.strictEqual(true, isInAcceptableRange(stats.channels[0].squaresSum, 11379783198));
assert.strictEqual(true, isInAcceptableRange(stats.channels[0].mean, 105.36169496842616));
assert.strictEqual(true, isInAcceptableRange(stats.channels[0].stdev, 57.39412151419967));
assert.strictEqual(true, isInteger(stats.channels[0].minX));
assert.strictEqual(true, isInRange(stats.channels[0].minX, 0, 1024));
assert.strictEqual(true, isInteger(stats.channels[0].minY));
@@ -295,10 +295,10 @@ describe('Image Stats', function () {
// green channel
assert.strictEqual(0, stats.channels[1].min);
assert.strictEqual(true, isInRange(stats.channels[1].max, 254, 255));
assert.strictEqual(true, isInAcceptableRange(stats.channels[1].sum, 120089056));
assert.strictEqual(true, isInAcceptableRange(stats.channels[1].squaresSum, 20533721114));
assert.strictEqual(true, isInAcceptableRange(stats.channels[1].mean, 151.90993361398964));
assert.strictEqual(true, isInAcceptableRange(stats.channels[1].stdev, 53.83370206587037));
assert.strictEqual(true, isInAcceptableRange(stats.channels[1].sum, 120877425));
assert.strictEqual(true, isInAcceptableRange(stats.channels[1].squaresSum, 20774687595));
assert.strictEqual(true, isInAcceptableRange(stats.channels[1].mean, 152.9072025279307));
assert.strictEqual(true, isInAcceptableRange(stats.channels[1].stdev, 53.84143349689916));
assert.strictEqual(true, isInteger(stats.channels[1].minX));
assert.strictEqual(true, isInRange(stats.channels[1].minX, 0, 1024));
assert.strictEqual(true, isInteger(stats.channels[1].minY));
@@ -311,10 +311,10 @@ describe('Image Stats', function () {
// blue channel
assert.strictEqual(0, stats.channels[2].min);
assert.strictEqual(true, isInRange(stats.channels[2].max, 254, 255));
assert.strictEqual(true, isInAcceptableRange(stats.channels[2].sum, 138153653));
assert.strictEqual(true, isInAcceptableRange(stats.channels[2].squaresSum, 28172033081));
assert.strictEqual(true, isInAcceptableRange(stats.channels[2].mean, 174.76123932359133));
assert.strictEqual(true, isInAcceptableRange(stats.channels[2].stdev, 71.38276338513747));
assert.strictEqual(true, isInAcceptableRange(stats.channels[2].sum, 138938859));
assert.strictEqual(true, isInAcceptableRange(stats.channels[2].squaresSum, 28449125593));
assert.strictEqual(true, isInAcceptableRange(stats.channels[2].mean, 175.75450711423252));
assert.strictEqual(true, isInAcceptableRange(stats.channels[2].stdev, 71.39929031070358));
assert.strictEqual(true, isInteger(stats.channels[2].minX));
assert.strictEqual(true, isInRange(stats.channels[2].minX, 0, 1024));
assert.strictEqual(true, isInteger(stats.channels[2].minY));