Compare commits

...

16 Commits

Author SHA1 Message Date
Lovell Fuller
866e9824d1 Release v0.26.0 2020-08-25 18:42:06 +01:00
Lovell Fuller
482e6078e2 Tests: Update leak suppressions for librsvg static data 2020-08-25 18:40:12 +01:00
Lovell Fuller
bc7ab296ef Tests: Update leak suppressions for static libs 2020-08-24 21:10:20 +01:00
Lovell Fuller
a5f4f53b56 Tests: remove no-longer-used conditional assertions 2020-08-23 20:11:23 +01:00
Lovell Fuller
b1227f526d Verify minimum libvips version at compile time 2020-08-23 20:10:27 +01:00
Lovell Fuller
78b42c8306 Docs: update performance test results 2020-08-23 16:48:46 +01:00
Lovell Fuller
4b6d45ab8e Prerelease v0.26.0-beta1 2020-08-23 15:56:25 +01:00
Lovell Fuller
7980341923 Upgrade libvips to v8.10.0 2020-08-23 15:36:03 +01:00
Lovell Fuller
3917efdebd Benchmarks: ensure PNG tests use consistent settings 2020-08-21 21:10:27 +01:00
Robert O'Rourke
eaecb7347b Add support to withMetadata for custom ICC profile #2271 2020-08-19 21:32:15 +01:00
Lovell Fuller
05ca7d3129 CI: further attempts to get Windows to play nicely 2020-08-18 20:53:18 +01:00
Lovell Fuller
4beae0de71 Add 'animated' constructor property as shortcut for 'pages'
Provides easier-to-understand API when handling animated images
2020-08-18 20:28:35 +01:00
Lovell Fuller
b711661784 CI: improve cross-platform (i.e. Windows) process spawning 2020-08-18 17:11:42 +01:00
Lovell Fuller
0c4a45b1f4 CI: workaround Appveyor ignoring v-prefixed tag names 2020-08-18 14:31:33 +01:00
Lovell Fuller
ec2beb0039 Prerelease v0.26.0-alpha2 2020-08-18 14:01:19 +01:00
Lovell Fuller
cafb3e8bac CI: ensure git tag is available inside prebuild containers 2020-08-18 14:00:14 +01:00
24 changed files with 217 additions and 52 deletions

View File

@@ -5,7 +5,7 @@ jobs:
dist: bionic dist: bionic
language: shell language: shell
before_install: before_install:
- sudo docker run -dit --name sharp --env CI --env prebuild_upload --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp centos:7 - 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 bash -c "curl -sL https://rpm.nodesource.com/setup_10.x | bash -"
- sudo docker exec sharp yum install -y gcc-c++ make git nodejs - sudo docker exec sharp yum install -y gcc-c++ make git nodejs
install: sudo docker exec sharp bash -c "npm install --unsafe-perm" install: sudo docker exec sharp bash -c "npm install --unsafe-perm"
@@ -38,7 +38,7 @@ jobs:
dist: bionic dist: bionic
language: shell language: shell
before_install: before_install:
- sudo docker run -dit --name sharp --env CI --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 run -dit --name sharp --env CI --env TRAVIS_TAG --env prebuild_upload --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp node:10.17.0-alpine3.9 # https://github.com/nodejs/docker-node/issues/1158
- sudo docker exec sharp apk add build-base git python2 --update-cache - sudo docker exec sharp apk add build-base git python2 --update-cache
install: sudo docker exec sharp sh -c "npm install --unsafe-perm" install: sudo docker exec sharp sh -c "npm install --unsafe-perm"
script: sudo docker exec sharp sh -c "npm test" script: sudo docker exec sharp sh -c "npm test"
@@ -69,7 +69,7 @@ jobs:
dist: bionic dist: bionic
language: shell language: shell
before_install: before_install:
- sudo docker run -dit --name sharp --env CI --env prebuild_upload --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp arm64v8/debian:bullseye - sudo docker run -dit --name sharp --env CI --env TRAVIS_TAG --env prebuild_upload --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp arm64v8/debian:bullseye
- sudo docker exec sharp sh -c "apt-get update && apt-get install -y build-essential git python2 curl" - sudo docker exec sharp sh -c "apt-get update && apt-get install -y build-essential git python2 curl"
- sudo docker exec sharp sh -c "curl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add -" - sudo docker exec sharp sh -c "curl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add -"
- sudo docker exec sharp sh -c "echo 'deb https://deb.nodesource.com/node_10.x buster main' >/etc/apt/sources.list.d/nodesource.list" - sudo docker exec sharp sh -c "echo 'deb https://deb.nodesource.com/node_10.x buster main' >/etc/apt/sources.list.d/nodesource.list"

View File

@@ -29,6 +29,7 @@ Implements the [stream.Duplex][1] class.
- `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.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.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.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` **[Object][4]?** describes raw pixel input image data. See `raw()` for pixel ordering.
- `options.raw.width` **[number][6]?** - `options.raw.width` **[number][6]?**
- `options.raw.height` **[number][6]?** - `options.raw.height` **[number][6]?**
@@ -78,6 +79,11 @@ sharp({
.then( ... ); .then( ... );
``` ```
```javascript
// Convert an animated GIF to an animated WebP
await sharp('in.gif', { animated: true }).toFile('out.webp');
```
- Throws **[Error][8]** Invalid parameters - Throws **[Error][8]** Invalid parameters
Returns **[Sharp][9]** Returns **[Sharp][9]**

View File

@@ -91,7 +91,8 @@ Returns **[Promise][5]<[Buffer][8]>** when no callback is provided
## withMetadata ## withMetadata
Include all metadata (EXIF, XMP, IPTC) from the input image in the output image. Include all metadata (EXIF, XMP, IPTC) from the input image in the output image.
This will also convert to and add a web-friendly sRGB ICC profile. This will also convert to and add a web-friendly sRGB ICC profile unless a custom
output profile is provided.
The default behaviour, when `withMetadata` is not used, is to convert to the device-independent The default behaviour, when `withMetadata` is not used, is to convert to the device-independent
sRGB colour space and strip all metadata, including the removal of any ICC profile. sRGB colour space and strip all metadata, including the removal of any ICC profile.
@@ -100,6 +101,7 @@ sRGB colour space and strip all metadata, including the removal of any ICC profi
- `options` **[Object][6]?** - `options` **[Object][6]?**
- `options.orientation` **[number][9]?** value between 1 and 8, used to update the EXIF `Orientation` tag. - `options.orientation` **[number][9]?** value between 1 and 8, used to update the EXIF `Orientation` tag.
- `options.icc` **[string][2]?** filesystem path to output ICC profile, defaults to sRGB.
### Examples ### Examples

View File

@@ -4,7 +4,7 @@
Requires libvips v8.10.0 Requires libvips v8.10.0
### v0.26.0 - TBD ### v0.26.0 - 25th August 2020
* Prebuilt libvips binaries are now statically-linked and Brotli-compressed, requiring Node.js 10.16.0+. * Prebuilt libvips binaries are now statically-linked and Brotli-compressed, requiring Node.js 10.16.0+.
@@ -27,6 +27,10 @@ Requires libvips v8.10.0
[#2259](https://github.com/lovell/sharp/pull/2259) [#2259](https://github.com/lovell/sharp/pull/2259)
[@vouillon](https://github.com/vouillon) [@vouillon](https://github.com/vouillon)
* Add support to `withMetadata` for custom ICC profile.
[#2271](https://github.com/lovell/sharp/pull/2271)
[@roborourke](https://github.com/roborourke)
* Ensure prebuilt binaries for ARM default to v7 when using Electron. * Ensure prebuilt binaries for ARM default to v7 when using Electron.
[#2292](https://github.com/lovell/sharp/pull/2292) [#2292](https://github.com/lovell/sharp/pull/2292)
[@diegodev3](https://github.com/diegodev3) [@diegodev3](https://github.com/diegodev3)

View File

@@ -194,3 +194,6 @@ GitHub: https://github.com/vouillon
Name: Tomáš Szabo Name: Tomáš Szabo
GitHub: https://github.com/deftomat GitHub: https://github.com/deftomat
Name: Robert O'Rourke
GitHub: https://github.com/roborourke

View File

@@ -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" 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="apple-touch-icon-precomposed" href="https://pixel.plumbing/px/57x57/sharp-logo.svg">
<link rel="author" href="/humans.txt" type="text/plain"> <link rel="author" href="/humans.txt" type="text/plain">
<link rel="preload" href="https://cdn.jsdelivr.net/gh/lovell/sharp@v0.25.4/docs/README.md" as="fetch" type="text/markdown" crossorigin> <link rel="preload" href="https://cdn.jsdelivr.net/gh/lovell/sharp@v0.26.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="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://pixel.plumbing">
<link rel="dns-prefetch" href="https://www.google-analytics.com"> <link rel="dns-prefetch" href="https://www.google-analytics.com">
@@ -139,7 +139,7 @@
docuteApiTitlePlugin, docuteApiTitlePlugin,
docuteApiSearchPlugin docuteApiSearchPlugin
], ],
sourcePath: 'https://cdn.jsdelivr.net/gh/lovell/sharp@v0.25.4/docs', sourcePath: 'https://cdn.jsdelivr.net/gh/lovell/sharp@v0.26.0/docs',
nav: [ nav: [
{ {
title: 'Funding', title: 'Funding',

View File

@@ -4,11 +4,11 @@ A test to benchmark the performance of this module relative to alternatives.
## The contenders ## The contenders
* [jimp](https://www.npmjs.com/package/jimp) v0.9.3 - Image processing in pure JavaScript. Provides bicubic interpolation. * [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.3.1 - Whilst primarily a map renderer, Mapnik contains bitmap image utilities. * [mapnik](https://www.npmjs.org/package/mapnik) v4.5.2 - 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*". * [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. * [gm](https://www.npmjs.com/package/gm) v1.23.1 - Fully featured wrapper around GraphicsMagick's `gm` command line utility.
* sharp v0.24.0 / libvips v8.9.0 - Caching within libvips disabled to ensure a fair comparison. * sharp v0.26.0 / libvips v8.10.0 - Caching within libvips disabled to ensure a fair comparison.
## The task ## The task
@@ -18,25 +18,25 @@ then compress to JPEG at a "quality" setting of 80.
## Test environment ## Test environment
* AWS EC2 eu-west-1 [c5.large](https://aws.amazon.com/ec2/instance-types/c5/) (2x Xeon Platinum 8124M CPU @ 3.00GHz) * AWS EC2 eu-west-1 [c5d.large](https://aws.amazon.com/ec2/instance-types/c5/) (2x Xeon Platinum 8124M CPU @ 3.00GHz)
* Ubuntu 18.04 (hvm-ssd/ubuntu-bionic-18.04-amd64-server-20180912 ami-00035f41c82244dab) * Ubuntu 20.04 (ami-0f1d11c92a9467c07)
* Node.js v12.14.1 * Node.js v14.8.0
## Results ## Results
| Module | Input | Output | Ops/sec | Speed-up | | Module | Input | Output | Ops/sec | Speed-up |
| :----------------- | :----- | :----- | ------: | -------: | | :----------------- | :----- | :----- | ------: | -------: |
| jimp | buffer | buffer | 0.72 | 1.0 | | jimp | buffer | buffer | 0.75 | 1.0 |
| mapnik | buffer | buffer | 3.02 | 4.2 | | mapnik | buffer | buffer | 3.00 | 4.0 |
| gm | buffer | buffer | 3.90 | 5.4 | | gm | buffer | buffer | 4.12 | 5.5 |
| gm | file | file | 3.94 | 5.5 | | gm | file | file | 4.13 | 5.5 |
| imagemagick | file | file | 4.30 | 6.0 | | imagemagick | file | file | 4.30 | 5.7 |
| sharp | stream | stream | 23.65 | 32.8 | | sharp | stream | stream | 22.37 | 29.8 |
| sharp | file | file | 24.66 | 34.3 | | sharp | file | file | 23.40 | 31.2 |
| sharp | buffer | buffer | 25.14 | 34.9 | | sharp | buffer | buffer | 24.01 | 32.0 |
Greater libvips performance can be expected with caching enabled (default) Greater libvips performance can be expected with caching enabled (default)
and using 8+ core machines, especially those with larger L1/L2 CPU caches. and using 4+ 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. The I/O limits of the relevant (de)compression library will generally determine maximum throughput.

View File

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

View File

@@ -86,6 +86,10 @@ const debuglog = util.debuglog('sharp');
* .toBuffer() * .toBuffer()
* .then( ... ); * .then( ... );
* *
* @example
* // Convert an animated GIF to an animated WebP
* await sharp('in.gif', { animated: true }).toFile('out.webp');
*
* @param {(Buffer|string)} [input] - if present, can be * @param {(Buffer|string)} [input] - if present, can be
* a Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or * a Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or
* a String containing the filesystem path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file. * a String containing the filesystem path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file.
@@ -102,6 +106,7 @@ const debuglog = util.debuglog('sharp');
* @param {number} [options.pages=1] - number of pages to extract for multi-page input (GIF, TIFF, PDF), use -1 for all pages. * @param {number} [options.pages=1] - number of pages to extract for multi-page input (GIF, TIFF, PDF), use -1 for all pages.
* @param {number} [options.page=0] - page number to start extracting from for multi-page input (GIF, TIFF, PDF), zero based. * @param {number} [options.page=0] - page number to start extracting from for multi-page input (GIF, TIFF, PDF), zero based.
* @param {number} [options.level=0] - level to extract from a multi-level input (OpenSlide), zero based. * @param {number} [options.level=0] - level to extract from a multi-level input (OpenSlide), zero based.
* @param {boolean} [options.animated=false] - Set to `true` to read all frames/pages of an animated image (equivalent of setting `pages` to `-1`).
* @param {Object} [options.raw] - describes raw pixel input image data. See `raw()` for pixel ordering. * @param {Object} [options.raw] - describes raw pixel input image data. See `raw()` for pixel ordering.
* @param {number} [options.raw.width] * @param {number} [options.raw.width]
* @param {number} [options.raw.height] * @param {number} [options.raw.height]
@@ -187,6 +192,7 @@ const Sharp = function (input, options) {
streamOut: false, streamOut: false,
withMetadata: false, withMetadata: false,
withMetadataOrientation: -1, withMetadataOrientation: -1,
withMetadataIcc: '',
resolveWithObject: false, resolveWithObject: false,
// output format // output format
jpegQuality: 80, jpegQuality: 80,

View File

@@ -99,6 +99,13 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
} }
} }
// Multi-page input (GIF, TIFF, PDF) // Multi-page input (GIF, TIFF, PDF)
if (is.defined(inputOptions.animated)) {
if (is.bool(inputOptions.animated)) {
inputDescriptor.pages = inputOptions.animated ? -1 : 1;
} else {
throw is.invalidParameterError('animated', 'boolean', inputOptions.animated);
}
}
if (is.defined(inputOptions.pages)) { if (is.defined(inputOptions.pages)) {
if (is.integer(inputOptions.pages) && is.inRange(inputOptions.pages, -1, 100000)) { if (is.integer(inputOptions.pages) && is.inRange(inputOptions.pages, -1, 100000)) {
inputDescriptor.pages = inputOptions.pages; inputDescriptor.pages = inputOptions.pages;

View File

@@ -119,7 +119,8 @@ function toBuffer (options, callback) {
/** /**
* Include all metadata (EXIF, XMP, IPTC) from the input image in the output image. * Include all metadata (EXIF, XMP, IPTC) from the input image in the output image.
* This will also convert to and add a web-friendly sRGB ICC profile. * This will also convert to and add a web-friendly sRGB ICC profile unless a custom
* output profile is provided.
* *
* The default behaviour, when `withMetadata` is not used, is to convert to the device-independent * The default behaviour, when `withMetadata` is not used, is to convert to the device-independent
* sRGB colour space and strip all metadata, including the removal of any ICC profile. * sRGB colour space and strip all metadata, including the removal of any ICC profile.
@@ -132,6 +133,7 @@ function toBuffer (options, callback) {
* *
* @param {Object} [options] * @param {Object} [options]
* @param {number} [options.orientation] value between 1 and 8, used to update the EXIF `Orientation` tag. * @param {number} [options.orientation] value between 1 and 8, used to update the EXIF `Orientation` tag.
* @param {string} [options.icc] filesystem path to output ICC profile, defaults to sRGB.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters
*/ */
@@ -145,6 +147,13 @@ function withMetadata (options) {
throw is.invalidParameterError('orientation', 'integer between 1 and 8', options.orientation); throw is.invalidParameterError('orientation', 'integer between 1 and 8', options.orientation);
} }
} }
if (is.defined(options.icc)) {
if (is.string(options.icc)) {
this.options.withMetadataIcc = options.icc;
} else {
throw is.invalidParameterError('icc', 'string filesystem path to ICC profile', options.icc);
}
}
} }
return this; return this;
} }
@@ -399,6 +408,7 @@ function webp (options) {
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid options * @throws {Error} Invalid options
*/ */
/* istanbul ignore next */
function gif (options) { function gif (options) {
if (!this.constructor.format.magick.output.buffer) { if (!this.constructor.format.magick.output.buffer) {
throw new Error('The gif operation requires libvips to have been installed with support for ImageMagick'); throw new Error('The gif operation requires libvips to have been installed with support for ImageMagick');

View File

@@ -1,7 +1,7 @@
{ {
"name": "sharp", "name": "sharp",
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP and TIFF images", "description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP and TIFF images",
"version": "0.26.0-alpha1", "version": "0.26.0",
"author": "Lovell Fuller <npm@lovell.info>", "author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://github.com/lovell/sharp", "homepage": "https://github.com/lovell/sharp",
"contributors": [ "contributors": [
@@ -68,7 +68,8 @@
"Brychan Bennett-Odlum <git@brychan.io>", "Brychan Bennett-Odlum <git@brychan.io>",
"Edward Silverton <e.silverton@gmail.com>", "Edward Silverton <e.silverton@gmail.com>",
"Roman Malieiev <aromaleev@gmail.com>", "Roman Malieiev <aromaleev@gmail.com>",
"Tomas Szabo <tomas.szabo@deftomat.com>" "Tomas Szabo <tomas.szabo@deftomat.com>",
"Robert O'Rourke <robert@o-rourke.org>"
], ],
"scripts": { "scripts": {
"install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node-gyp rebuild && node install/dll-copy)", "install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node-gyp rebuild && node install/dll-copy)",
@@ -130,15 +131,15 @@
"icc": "^2.0.0", "icc": "^2.0.0",
"license-checker": "^25.0.1", "license-checker": "^25.0.1",
"mocha": "^8.1.1", "mocha": "^8.1.1",
"mock-fs": "^4.12.0", "mock-fs": "^4.13.0",
"nyc": "^15.1.0", "nyc": "^15.1.0",
"prebuild": "^10.0.0", "prebuild": "^10.0.1",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"semistandard": "^14.2.3" "semistandard": "^14.2.3"
}, },
"license": "Apache-2.0", "license": "Apache-2.0",
"config": { "config": {
"libvips": "8.10.0-rc4", "libvips": "8.10.0",
"runtime": "napi", "runtime": "napi",
"target": 3 "target": 3
}, },

View File

@@ -24,8 +24,8 @@
// Verify platform and compiler compatibility // Verify platform and compiler compatibility
#if (VIPS_MAJOR_VERSION < 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 9)) #if (VIPS_MAJOR_VERSION < 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 10))
#error "libvips version 8.9.2+ is required - please see https://sharp.pixelplumbing.com/install" #error "libvips version 8.10.0+ is required - please see https://sharp.pixelplumbing.com/install"
#endif #endif
#if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6))) #if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6)))

View File

@@ -684,6 +684,15 @@ class PipelineWorker : public Napi::AsyncWorker {
} }
} }
// Apply output ICC profile
if (!baton->withMetadataIcc.empty()) {
image = image.icc_transform(
const_cast<char*>(baton->withMetadataIcc.data()),
VImage::option()
->set("input_profile", "srgb")
->set("intent", VIPS_INTENT_PERCEPTUAL));
}
// Override EXIF Orientation tag // Override EXIF Orientation tag
if (baton->withMetadata && baton->withMetadataOrientation != -1) { if (baton->withMetadata && baton->withMetadataOrientation != -1) {
image = sharp::SetExifOrientation(image, baton->withMetadataOrientation); image = sharp::SetExifOrientation(image, baton->withMetadataOrientation);
@@ -1319,6 +1328,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
baton->fileOut = sharp::AttrAsStr(options, "fileOut"); baton->fileOut = sharp::AttrAsStr(options, "fileOut");
baton->withMetadata = sharp::AttrAsBool(options, "withMetadata"); baton->withMetadata = sharp::AttrAsBool(options, "withMetadata");
baton->withMetadataOrientation = sharp::AttrAsUint32(options, "withMetadataOrientation"); baton->withMetadataOrientation = sharp::AttrAsUint32(options, "withMetadataOrientation");
baton->withMetadataIcc = sharp::AttrAsStr(options, "withMetadataIcc");
// Format-specific // Format-specific
baton->jpegQuality = sharp::AttrAsUint32(options, "jpegQuality"); baton->jpegQuality = sharp::AttrAsUint32(options, "jpegQuality");
baton->jpegProgressive = sharp::AttrAsBool(options, "jpegProgressive"); baton->jpegProgressive = sharp::AttrAsBool(options, "jpegProgressive");

View File

@@ -155,6 +155,7 @@ struct PipelineBaton {
std::string err; std::string err;
bool withMetadata; bool withMetadata;
int withMetadataOrientation; int withMetadataOrientation;
std::string withMetadataIcc;
std::unique_ptr<double[]> convKernel; std::unique_ptr<double[]> convKernel;
int convKernelWidth; int convKernelWidth;
int convKernelHeight; int convKernelHeight;

View File

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

View File

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

BIN
test/fixtures/hilutite.icm vendored Normal file

Binary file not shown.

View File

@@ -39,6 +39,16 @@
Memcheck:Cond Memcheck:Cond
obj:*/libjpeg.so* obj:*/libjpeg.so*
} }
{
value_jpeg_obj_static
Memcheck:Value8
obj:*/libvips.so*
}
{
cond_jpeg_obj_static
Memcheck:Cond
obj:*/libvips.so*
}
{ {
param_jpeg_jpeg_finish_compress param_jpeg_jpeg_finish_compress
Memcheck:Param Memcheck:Param
@@ -304,6 +314,16 @@
... ...
fun:vips__init fun:vips__init
} }
{
leak_rsvg_static_data
Memcheck:Leak
match-leak-kinds: definite
fun:malloc
...
fun:rsvg_rust_handle_new_from_stream_sync
...
fun:vips_object_build
}
# libuv warnings # libuv warnings
{ {
@@ -418,6 +438,13 @@
... ...
fun:_ZN4node17CreateEnvironmentEPN2v87IsolateEP9uv_loop_sNS0_5LocalINS0_7ContextEEEiPKPKciSB_ fun:_ZN4node17CreateEnvironmentEPN2v87IsolateEP9uv_loop_sNS0_5LocalINS0_7ContextEEEiPKPKciSB_
} }
{
leak_nodejs_CreateEnvironment_IsolateData
Memcheck:Leak
match-leak-kinds: possible
...
fun:_ZN4node17CreateEnvironmentEPNS_11IsolateDataEN2v85LocalINS2_7ContextEEERKSt6vectorISsSaISsEESA_NS_16EnvironmentFlags5FlagsENS_8ThreadIdESt10unique_ptrINS_21InspectorParentHandleESt14default_deleteISF_EE
}
{ {
leak_nodejs_Environment_Start leak_nodejs_Environment_Start
Memcheck:Leak Memcheck:Leak

View File

@@ -649,6 +649,15 @@ describe('Input/output', function () {
sharp({ density: 'zoinks' }); sharp({ density: 'zoinks' });
}, /Expected number between 1 and 2400 for density but received zoinks of type string/); }, /Expected number between 1 and 2400 for density but received zoinks of type string/);
}); });
it('Setting animated property updates pages property', function () {
assert.strictEqual(sharp({ animated: false }).options.input.pages, 1);
assert.strictEqual(sharp({ animated: true }).options.input.pages, -1);
});
it('Invalid animated property throws', function () {
assert.throws(function () {
sharp({ animated: -1 });
}, /Expected boolean for animated but received -1 of type number/);
});
it('Invalid page property throws', function () { it('Invalid page property throws', function () {
assert.throws(function () { assert.throws(function () {
sharp({ page: -1 }); sharp({ page: -1 });

View File

@@ -501,6 +501,42 @@ describe('Image metadata', function () {
}); });
}); });
it('Apply CMYK output ICC profile', function (done) {
const output = fixtures.path('output.icc-cmyk.jpg');
sharp(fixtures.inputJpg)
.withMetadata({ icc: 'cmyk' })
.toFile(output, function (err, info) {
if (err) throw err;
sharp(output).metadata(function (err, metadata) {
if (err) throw err;
assert.strictEqual(true, metadata.hasProfile);
assert.strictEqual('cmyk', metadata.space);
assert.strictEqual(4, metadata.channels);
// ICC
assert.strictEqual('object', typeof metadata.icc);
assert.strictEqual(true, metadata.icc instanceof Buffer);
const profile = icc.parse(metadata.icc);
assert.strictEqual('object', typeof profile);
assert.strictEqual('CMYK', profile.colorSpace);
assert.strictEqual('Relative', profile.intent);
assert.strictEqual('Printer', profile.deviceClass);
});
fixtures.assertSimilar(output, fixtures.path('expected/icc-cmyk.jpg'), { threshold: 0 }, done);
});
});
it('Apply custom output ICC profile', function (done) {
const output = fixtures.path('output.hilutite.jpg');
sharp(fixtures.inputJpg)
.withMetadata({ icc: fixtures.path('hilutite.icm') })
.toFile(output, function (err, info) {
if (err) throw err;
fixtures.assertMaxColourDistance(output, fixtures.path('expected/hilutite.jpg'), 0);
fixtures.assertMaxColourDistance(output, fixtures.inputJpg, 16.5);
done();
});
});
it('Include metadata in output, enabled via empty object', () => it('Include metadata in output, enabled via empty object', () =>
sharp(fixtures.inputJpgWithExif) sharp(fixtures.inputJpgWithExif)
.withMetadata({}) .withMetadata({})
@@ -675,5 +711,10 @@ describe('Image metadata', function () {
sharp().withMetadata({ orientation: 9 }); sharp().withMetadata({ orientation: 9 });
}); });
}); });
it('Non string icc', function () {
assert.throws(function () {
sharp().withMetadata({ icc: true });
});
});
}); });
}); });

View File

@@ -161,9 +161,7 @@ describe('TIFF', function () {
.then(() => sharp(fixtures.outputTiff) .then(() => sharp(fixtures.outputTiff)
.metadata() .metadata()
.then(({ density }) => { .then(({ density }) => {
assert.strictEqual(true, assert.strictEqual(25400, density);
density === 2540 || // libvips <= 8.8.2
density === 25400); // libvips >= 8.8.3
return promisify(rimraf)(fixtures.outputTiff); return promisify(rimraf)(fixtures.outputTiff);
}) })
) )
@@ -179,9 +177,7 @@ describe('TIFF', function () {
.then(data => sharp(data) .then(data => sharp(data)
.metadata() .metadata()
.then(({ density }) => { .then(({ density }) => {
assert.strictEqual(true, assert.strictEqual(25400, density);
density === 2540 || // libvips <= 8.8.2
density === 25400); // libvips >= 8.8.3
}) })
) )
); );