Compare commits

...

31 Commits

Author SHA1 Message Date
Lovell Fuller
d0feb4156c Release v0.23.3 2019-11-17 15:42:07 +00:00
Lovell Fuller
0d1278dade Improve C++ linting, move exceptions inline 2019-11-14 22:06:38 +00:00
Lovell Fuller
1b401b1195 Add FreeBSD to CI via Cirrus #1953 2019-11-14 21:55:20 +00:00
Lovell Fuller
11daa3b4d1 Tests: flatten to mid-grey before generating fingerprint 2019-11-14 13:18:14 +00:00
Lovell Fuller
88a3919ce0 Docs refresh 2019-11-14 11:32:49 +00:00
Lovell Fuller
c41b87303d Ensure trim op supports image-in-alpha #1597 2019-11-14 11:29:45 +00:00
Lovell Fuller
833aaead56 Ensure modulate can co-exist with other colour ops #1958 2019-11-11 22:16:28 +00:00
Lovell Fuller
ff98d2e44a Docs: using custom binaries with prebuild-install 2019-11-07 20:00:03 +00:00
Lovell Fuller
fcf05f608a Changelog entry for #1952 2019-11-07 19:59:18 +00:00
Pouya Eghbali
9baf38db44 Allow compilation of v0.23.x on FreeBSD and variants (#1952) 2019-11-06 23:11:44 +00:00
Lovell Fuller
69050ef1c8 Add funding link to libvips' Open Collective 2019-11-05 21:51:14 +00:00
Lovell Fuller
b35b9f7850 Ensure Travis does not cache builds #1948 2019-11-01 16:17:36 +00:00
Lovell Fuller
500ae97cac Changelog entry for #1921 2019-11-01 16:15:02 +00:00
Brendan Kennedy
d5b7040557 Ensure tile overlap option works as expected (#1921) 2019-10-30 20:02:07 +00:00
Lovell Fuller
ca52894651 Release v0.23.2 2019-10-28 18:21:11 +00:00
Lovell Fuller
4009acdd30 Docs: support added for Node.js 13 2019-10-28 18:19:33 +00:00
Lovell Fuller
147c93ecd3 Tests: increase coverage for jpeg-related logic 2019-10-27 20:03:10 +00:00
Lovell Fuller
8e04e4b07f Tests: add coverage for tiff quality option 2019-10-27 19:54:23 +00:00
Lovell Fuller
e7413ea1e5 Tests: increase coverage for metadata-related logic 2019-10-27 19:32:48 +00:00
Lovell Fuller
220bb03a32 Switch from excl npmignore to incl package.json files 2019-10-27 18:22:10 +00:00
Lovell Fuller
20f512fe5f Bump dependency patch versions 2019-10-26 23:15:41 +01:00
Lovell Fuller
efb3523eaa Remove duplicate validation from resize background 2019-10-26 23:13:12 +01:00
Lovell Fuller
2f2276e091 Changelog entries for #1924 #1932 2019-10-26 22:53:39 +01:00
Paul Neave
08a6597626 Add background option to tile output operation (#1924) 2019-10-25 14:30:33 +01:00
Nicolas Stepien
d82a6ee4fc Add Node.js 13 to CI (#1932) 2019-10-23 19:28:19 +01:00
Lovell Fuller
e627f6d68d Docs: clarify that input 'path' refers to the filesystem 2019-10-05 08:43:40 +01:00
Lovell Fuller
e650f58bd8 Improve error messaging for root/sudo permission problems 2019-10-04 12:14:08 +01:00
Lovell Fuller
5a9b6c8afd Tighten validation of page/pages constructor options 2019-10-03 16:41:32 +01:00
Lovell Fuller
075771d1e9 Improve error messaging for 404 errors on non-standard platforms 2019-10-03 15:32:15 +01:00
Lovell Fuller
4fcf091fef Bump tar dep (appears to be non-breaking despite major increment) 2019-10-03 15:30:56 +01:00
Marc Bornträger
0e66454fe4 Docs: Simplify Alpine Linux info, uses 3.10 instead of edge 2019-10-01 12:00:38 +01:00
34 changed files with 370 additions and 97 deletions

13
.cirrus.yml Normal file
View File

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

View File

@@ -11,7 +11,7 @@ Did you see the [documentation relating to installation](https://sharp.pixelplum
Have you ensured the platform and version of Node.js used for `npm install` is the same as the platform and version of Node.js used at runtime?
If you're (mis)using `sudo npm install` have you tried with the `sudo npm install --unsafe-perm` flag?
If you are installing as a `root` or `sudo` user, have you tried with the `npm install --unsafe-perm` flag?
What is the complete output of running `npm install --verbose sharp`? Have you checked this output for useful error messages?

View File

@@ -1,16 +0,0 @@
build
node_modules
coverage
.editorconfig
.gitattributes
.gitignore
test
.travis.yml
appveyor.yml
mkdocs.yml
docs/css/
vendor
.prebuildrc
.nyc_output
.github/
.vscode/

View File

@@ -18,6 +18,12 @@ matrix:
sudo: false
language: node_js
node_js: "12"
- name: "Linux (glibc) - Node 13"
os: linux
dist: trusty
sudo: false
language: node_js
node_js: "13"
after_success:
- npm install coveralls
- cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js
@@ -51,6 +57,16 @@ matrix:
- sudo docker exec sharp apk add build-base git python2 --update-cache
install: sudo docker exec sharp sh -c "npm install --unsafe-perm"
script: sudo docker exec sharp sh -c "npm test"
- name: "Linux (musl) - Node 13"
os: linux
dist: trusty
sudo: true
language: minimal
before_install:
- sudo docker run -dit --name sharp --env CI --env PREBUILD_TOKEN --volume "${PWD}:/mnt/sharp" --workdir /mnt/sharp node:13.0-alpine
- sudo docker exec sharp apk add build-base git python2 --update-cache
install: sudo docker exec sharp sh -c "npm install --unsafe-perm"
script: sudo docker exec sharp sh -c "npm test"
- name: "OS X - Node 8"
os: osx
osx_image: xcode9.2
@@ -66,3 +82,10 @@ matrix:
osx_image: xcode9.2
language: node_js
node_js: "12"
- name: "OS X - Node 13"
os: osx
osx_image: xcode10
language: node_js
node_js: "13"
cache:
npm: false

View File

@@ -21,7 +21,7 @@ As well as image resizing, operations such as
rotation, extraction, compositing and gamma correction are available.
Most modern 64-bit OS X, Windows and Linux systems running
Node versions 8, 10 and 12
Node versions 8, 10, 12 and 13
do not require any additional install or runtime dependencies.
## Examples

View File

@@ -7,6 +7,7 @@ environment:
- nodejs_version: "8"
- nodejs_version: "10"
- nodejs_version: "12"
- nodejs_version: "13"
install:
- ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version) x64
- npm install -g npm@6

View File

@@ -183,16 +183,22 @@
},
'configurations': {
'Release': {
'cflags_cc': [
'-Wno-cast-function-type'
],
'msvs_settings': {
'VCCLCompilerTool': {
'ExceptionHandling': 1
}
},
'msvs_disabled_warnings': [
4275
'conditions': [
['OS == "linux"', {
'cflags_cc': [
'-Wno-cast-function-type'
]
}],
['OS == "win"', {
'msvs_settings': {
'VCCLCompilerTool': {
'ExceptionHandling': 1
}
},
'msvs_disabled_warnings': [
4275
]
}]
]
}
},

View File

@@ -6,7 +6,7 @@
- `input` **([Buffer][1] \| [String][2])?** if present, can be
a Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or
a String containing the path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file.
a String containing the filesystem path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file.
JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present.
- `options` **[Object][3]?** if present, is an Object with optional attributes.
- `options.failOnError` **[Boolean][4]** by default halt processing and raise an error when loading invalid images.

View File

@@ -319,6 +319,7 @@ Warning: multiple sharp instances concurrently producing tile output can expose
- `options.size` **[Number][9]** tile size in pixels, a value between 1 and 8192. (optional, default `256`)
- `options.overlap` **[Number][9]** tile overlap in pixels, a value between 0 and 8192. (optional, default `0`)
- `options.angle` **[Number][9]** tile angle of rotation, must be a multiple of 90. (optional, default `0`)
- `options.background` **([String][2] \| [Object][6])** background colour, parsed by the [color][10] module, defaults to white without transparency. (optional, default `{r:255,g:255,b:255,alpha:1}`)
- `options.depth` **[String][2]?** how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout.
- `options.skipBlanks` **[Number][9]** threshold to skip tile generation, a value 0 - 255 for 8-bit images or 0 - 65535 for 16-bit images (optional, default `-1`)
- `options.container` **[String][2]** tile container, with value `fs` (filesystem) or `zip` (compressed file). (optional, default `'fs'`)
@@ -359,3 +360,5 @@ Returns **Sharp**
[8]: https://nodejs.org/api/buffer.html
[9]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
[10]: https://www.npmjs.org/package/color

View File

@@ -196,6 +196,8 @@ Returns **Sharp**
## trim
Trim "boring" pixels from all edges that contain values similar to the top-left pixel.
Images consisting entirely of a single colour will calculate "boring" using the alpha channel, if any.
The `info` response Object will contain `trimOffsetLeft` and `trimOffsetTop` properties.
### Parameters

View File

@@ -4,6 +4,32 @@
Requires libvips v8.8.1.
#### v0.23.3 - 17<sup>th</sup> November 2019
* Ensure `trim` operation supports images contained in the alpha channel.
[#1597](https://github.com/lovell/sharp/issues/1597)
* Ensure tile `overlap` option works as expected.
[#1921](https://github.com/lovell/sharp/pull/1921)
[@rustyguts](https://github.com/rustyguts)
* Allow compilation on FreeBSD and variants (broken since v0.23.0)
[#1952](https://github.com/lovell/sharp/pull/1952)
[@pouya-eghbali](https://github.com/pouya-eghbali)
* Ensure `modulate` and other colour-based operations can co-exist.
[#1958](https://github.com/lovell/sharp/issues/1958)
#### v0.23.2 - 28<sup>th</sup> October 2019
* Add `background` option to tile output operation.
[#1924](https://github.com/lovell/sharp/pull/1924)
[@neave](https://github.com/neave)
* Add support for Node.js 13.
[#1932](https://github.com/lovell/sharp/pull/1932)
[@MayhemYDG](https://github.com/MayhemYDG)
#### v0.23.1 - 26<sup>th</sup> September 2019
* Ensure `sharp.format.vips` is present and correct (filesystem only).

View File

@@ -17,7 +17,7 @@ As well as image resizing, operations such as
rotation, extraction, compositing and gamma correction are available.
Most modern 64-bit OS X, Windows and Linux systems running
Node versions 8, 10 and 12
Node versions 8, 10, 12 and 13
do not require any additional install or runtime dependencies.
[![Test Coverage](https://coveralls.io/repos/lovell/sharp/badge.png?branch=master)](https://coveralls.io/r/lovell/sharp?branch=master)
@@ -128,6 +128,9 @@ the help and code contributions of the following people:
* [Jakub Michálek](https://github.com/Goues)
* [Ilya Ovdin](https://github.com/iovdin)
* [Andargor](https://github.com/Andargor)
* [Nicolas Stepien](https://github.com/MayhemYDG)
* [Paul Neave](https://github.com/neave)
* [Brendan Kennedy](https://github.com/rustyguts)
Thank you!

View File

@@ -15,7 +15,7 @@ yarn add sharp
### Building from source
Pre-compiled binaries for sharp are provided for use with
Node versions 8, 10 and 12 on
Node versions 8, 10, 12 and 13 on
64-bit Windows, OS X and Linux platforms.
Sharp will be built from source at install time when:
@@ -74,9 +74,8 @@ libvips is available in the
[community repository](https://pkgs.alpinelinux.org/packages?name=vips-dev):
```sh
apk add vips-dev fftw-dev build-base --update-cache \
--repository https://alpine.global.ssl.fastly.net/alpine/edge/community/ \
--repository https://alpine.global.ssl.fastly.net/alpine/edge/main
apk add --upgrade --no-cache vips-dev build-base \
--repository https://alpine.global.ssl.fastly.net/alpine/v3.10/community/
```
The smaller stack size of musl libc means
@@ -247,6 +246,9 @@ SHARP_DIST_BASE_URL="https://hostname/path/" npm install sharp
to use `https://hostname/path/libvips-x.y.z-platform.tar.gz`.
To install the prebuilt sharp binaries from a custom URL, please see
[https://github.com/prebuild/prebuild-install#custom-binaries](https://github.com/prebuild/prebuild-install#custom-binaries)
### Licences
This module is licensed under the terms of the

View File

@@ -19,6 +19,9 @@ const distBaseUrl = process.env.npm_config_sharp_dist_base_url || process.env.SH
const fail = function (err) {
npmLog.error('sharp', err.message);
if (err.code === 'EACCES') {
npmLog.info('sharp', 'Are you trying to install as a root or sudo user? Try again with the --unsafe-perm flag');
}
npmLog.info('sharp', 'Attempting to build from source via node-gyp but this may fail due to the above error');
npmLog.info('sharp', 'Please see https://sharp.pixelplumbing.com/page/install for required dependencies');
process.exit(1);
@@ -79,14 +82,16 @@ try {
npmLog.info('sharp', `Downloading ${url}`);
simpleGet({ url: url, agent: agent() }, function (err, response) {
if (err) {
throw err;
fail(err);
} else if (response.statusCode === 404) {
fail(new Error(`Prebuilt libvips binaries are not yet available for ${platformAndArch}`));
} else if (response.statusCode !== 200) {
fail(new Error(`Status ${response.statusCode} ${response.statusMessage}`));
} else {
response
.on('error', fail)
.pipe(tmpFile);
}
if (response.statusCode !== 200) {
throw new Error(`Status ${response.statusCode}`);
}
response
.on('error', fail)
.pipe(tmpFile);
});
tmpFile
.on('error', fail)

View File

@@ -78,7 +78,7 @@ const debuglog = util.debuglog('sharp');
*
* @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 path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file.
* a String containing the filesystem path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file.
* JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present.
* @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.
@@ -213,6 +213,7 @@ const Sharp = function (input, options) {
tileSize: 256,
tileOverlap: 0,
tileSkipBlanks: -1,
tileBackground: [255, 255, 255, 255],
linearA: 1,
linearB: 0,
// Function to notify of libvips warnings

View File

@@ -65,11 +65,15 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
if (is.defined(inputOptions.pages)) {
if (is.integer(inputOptions.pages) && is.inRange(inputOptions.pages, -1, 100000)) {
inputDescriptor.pages = inputOptions.pages;
} else {
throw is.invalidParameterError('pages', 'integer between -1 and 100000', inputOptions.pages);
}
}
if (is.defined(inputOptions.page)) {
if (is.integer(inputOptions.page) && is.inRange(inputOptions.page, 0, 100000)) {
inputDescriptor.page = inputOptions.page;
} else {
throw is.invalidParameterError('page', 'integer between 0 and 100000', inputOptions.page);
}
}
// Create new image

View File

@@ -551,6 +551,7 @@ function toFormat (format, options) {
* @param {Number} [options.size=256] tile size in pixels, a value between 1 and 8192.
* @param {Number} [options.overlap=0] tile overlap in pixels, a value between 0 and 8192.
* @param {Number} [options.angle=0] tile angle of rotation, must be a multiple of 90.
* @param {String|Object} [options.background={r: 255, g: 255, b: 255, alpha: 1}] - background colour, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to white without transparency.
* @param {String} [options.depth] how deep to make the pyramid, possible values are `onepixel`, `onetile` or `one`, default based on layout.
* @param {Number} [options.skipBlanks=-1] threshold to skip tile generation, a value 0 - 255 for 8-bit images or 0 - 65535 for 16-bit images
* @param {String} [options.container='fs'] tile container, with value `fs` (filesystem) or `zip` (compressed file).
@@ -574,7 +575,7 @@ function tile (options) {
if (options.overlap > this.options.tileSize) {
throw is.invalidParameterError('overlap', `<= size (${this.options.tileSize})`, options.overlap);
}
this.options.tileOverlap = tile.overlap;
this.options.tileOverlap = options.overlap;
} else {
throw is.invalidParameterError('overlap', 'integer between 0 and 8192', options.overlap);
}
@@ -603,6 +604,8 @@ function tile (options) {
throw is.invalidParameterError('angle', 'positive/negative multiple of 90', options.angle);
}
}
// Background colour
this._setBackgroundColourOption('tileBackground', options.background);
// Depth of tiles
if (is.defined(options.depth)) {
if (is.string(options.depth) && is.inArray(options.depth, ['onepixel', 'onetile', 'one'])) {

View File

@@ -246,9 +246,7 @@ function resize (width, height, options) {
}
}
// Background
if (is.defined(options.background)) {
this._setBackgroundColourOption('resizeBackground', options.background);
}
this._setBackgroundColourOption('resizeBackground', options.background);
// Kernel
if (is.defined(options.kernel)) {
if (is.string(kernel[options.kernel])) {
@@ -369,7 +367,10 @@ function extract (options) {
/**
* Trim "boring" pixels from all edges that contain values similar to the top-left pixel.
* Images consisting entirely of a single colour will calculate "boring" using the alpha channel, if any.
*
* The `info` response Object will contain `trimOffsetLeft` and `trimOffsetTop` properties.
*
* @param {Number} [threshold=10] the allowed difference from the top-left pixel, a number greater than zero.
* @returns {Sharp}
* @throws {Error} Invalid parameters

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.23.1",
"version": "0.23.3",
"author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://github.com/lovell/sharp",
"contributors": [
@@ -62,12 +62,14 @@
"Michael B. Klein <mbklein@gmail.com>",
"Jordan Prudhomme <jordan@raboland.fr>",
"Ilya Ovdin <iovdin@gmail.com>",
"Andargor <andargor@yahoo.com>"
"Andargor <andargor@yahoo.com>",
"Paul Neave <paul.neave@gmail.com>",
"Brendan Kennedy <brenwken@gmail.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 && cc && npm run test-unit && npm run test-licensing && prebuild-ci",
"test": "semistandard && cpplint && npm run test-unit && npm run test-licensing && prebuild-ci",
"test-unit": "nyc --reporter=lcov --branches=99 mocha --slow=5000 --timeout=60000 ./test/unit/*.js",
"test-licensing": "license-checker --production --summary --onlyAllow=\"Apache-2.0;BSD;ISC;MIT\"",
"test-coverage": "./test/coverage/report.sh",
@@ -75,6 +77,14 @@
"docs": "for m in constructor input resize composite operation colour channel output utility; do documentation build --shallow --format=md --markdown-toc=false lib/$m.js >docs/api-$m.md; done"
},
"main": "lib/index.js",
"files": [
"binding.gyp",
"docs/**",
"!docs/css/**",
"install/**",
"lib/**",
"src/**"
],
"repository": {
"type": "git",
"url": "git://github.com/lovell/sharp"
@@ -100,24 +110,24 @@
"detect-libc": "^1.0.3",
"nan": "^2.14.0",
"npmlog": "^4.1.2",
"prebuild-install": "^5.3.2",
"prebuild-install": "^5.3.3",
"semver": "^6.3.0",
"simple-get": "^3.1.0",
"tar": "^4.4.13",
"tar": "^5.0.5",
"tunnel-agent": "^0.6.0"
},
"devDependencies": {
"async": "^3.1.0",
"cc": "^1.0.2",
"cc": "^2.0.1",
"decompress-zip": "^0.3.2",
"documentation": "^12.1.2",
"exif-reader": "^1.0.2",
"documentation": "^12.1.4",
"exif-reader": "^1.0.3",
"icc": "^1.0.0",
"license-checker": "^25.0.1",
"mocha": "^6.2.0",
"mock-fs": "^4.10.1",
"mocha": "^6.2.2",
"mock-fs": "^4.10.3",
"nyc": "^14.1.1",
"prebuild": "^9.1.0",
"prebuild": "^9.1.1",
"prebuild-ci": "^3.1.0",
"rimraf": "^3.0.0",
"semistandard": "^14.2.0"
@@ -129,6 +139,9 @@
"engines": {
"node": ">=8.5.0"
},
"funding": {
"url": "https://opencollective.com/libvips"
},
"semistandard": {
"env": [
"mocha"
@@ -137,10 +150,7 @@
"cc": {
"linelength": "120",
"filter": [
"build/c++11",
"build/include",
"runtime/indentation_namespace",
"runtime/references"
"build/include"
]
}
}

View File

@@ -17,7 +17,7 @@
#include <string.h>
#include <vector>
#include <queue>
#include <mutex>
#include <mutex> // NOLINT(build/c++11)
#include <node.h>
#include <node_buffer.h>

View File

@@ -43,7 +43,7 @@ using vips::VImage;
namespace sharp {
struct InputDescriptor {
struct InputDescriptor { // NOLINT(runtime/indentation_namespace)
std::string name;
std::string file;
char *buffer;
@@ -92,7 +92,7 @@ namespace sharp {
// Create an InputDescriptor instance from a v8::Object describing an input image
InputDescriptor* CreateInputDescriptor(
v8::Local<v8::Object> input, std::vector<v8::Local<v8::Object>> &buffersToPersist);
v8::Local<v8::Object> input, std::vector<v8::Local<v8::Object>> &buffersToPersist); // NOLINT(runtime/references)
enum class ImageType {
JPEG,

View File

@@ -191,12 +191,20 @@ namespace sharp {
VImage alpha = image[image.bands() - 1];
return RemoveAlpha(image)
.colourspace(VIPS_INTERPRETATION_LCH)
.linear({brightness, saturation, 1}, {0, 0, static_cast<double>(hue)})
.linear(
{ brightness, saturation, 1},
{ 0.0, 0.0, static_cast<double>(hue) }
)
.colourspace(VIPS_INTERPRETATION_sRGB)
.bandjoin(alpha);
} else {
return image
.colourspace(VIPS_INTERPRETATION_LCH)
.linear({brightness, saturation, 1}, {0, 0, static_cast<double>(hue)});
.linear(
{ brightness, saturation, 1 },
{ 0.0, 0.0, static_cast<double>(hue) }
)
.colourspace(VIPS_INTERPRETATION_sRGB);
}
}
@@ -258,12 +266,22 @@ namespace sharp {
if (HasAlpha(background)) {
background = background.flatten();
}
int top, width, height;
int const left = image.find_trim(&top, &width, &height, VImage::option()
int left, top, width, height;
left = image.find_trim(&top, &width, &height, VImage::option()
->set("background", background(0, 0))
->set("threshold", threshold));
if (width == 0 || height == 0) {
throw VError("Unexpected error while trimming. Try to lower the tolerance");
if (HasAlpha(image)) {
// Search alpha channel
VImage alpha = image[image.bands() - 1];
VImage backgroundAlpha = alpha.extract_area(0, 0, 1, 1);
left = alpha.find_trim(&top, &width, &height, VImage::option()
->set("background", backgroundAlpha(0, 0))
->set("threshold", threshold));
}
if (width == 0 || height == 0) {
throw VError("Unexpected error while trimming. Try to lower the tolerance");
}
}
return image.extract_area(left, top, width, height);
}

View File

@@ -38,6 +38,9 @@
#elif defined(__APPLE__)
#define STAT64_STRUCT stat
#define STAT64_FUNCTION stat
#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__)
#define STAT64_STRUCT stat
#define STAT64_FUNCTION stat
#else
#define STAT64_STRUCT stat64
#define STAT64_FUNCTION stat64
@@ -959,6 +962,11 @@ class PipelineWorker : public Nan::AsyncWorker {
};
suffix = AssembleSuffixString(extname, options);
}
// Remove alpha channel from tile background if image does not contain an alpha channel
if (!HasAlpha(image)) {
baton->tileBackground.pop_back();
}
// Write DZ to file
vips::VOption *options = VImage::option()
->set("strip", !baton->withMetadata)
@@ -968,6 +976,7 @@ class PipelineWorker : public Nan::AsyncWorker {
->set("layout", baton->tileLayout)
->set("suffix", const_cast<char*>(suffix.data()))
->set("angle", CalculateAngleRotation(baton->tileAngle))
->set("background", baton->tileBackground)
->set("skip_blanks", baton->tileSkipBlanks);
// libvips chooses a default depth based on layout. Instead of replicating that logic here by
@@ -1374,6 +1383,7 @@ NAN_METHOD(pipeline) {
baton->tileOverlap = AttrTo<uint32_t>(options, "tileOverlap");
std::string tileContainer = AttrAsStr(options, "tileContainer");
baton->tileAngle = AttrTo<int32_t>(options, "tileAngle");
baton->tileBackground = AttrAsRgba(options, "tileBackground");
baton->tileSkipBlanks = AttrTo<int32_t>(options, "tileSkipBlanks");
if (tileContainer == "zip") {
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP;

View File

@@ -175,6 +175,7 @@ struct PipelineBaton {
VipsForeignDzLayout tileLayout;
std::string tileFormat;
int tileAngle;
std::vector<double> tileBackground;
int tileSkipBlanks;
VipsForeignDzDepth tileDepth;
std::unique_ptr<double[]> recombMatrix;
@@ -280,6 +281,7 @@ struct PipelineBaton {
tileContainer(VIPS_FOREIGN_DZ_CONTAINER_FS),
tileLayout(VIPS_FOREIGN_DZ_LAYOUT_DZ),
tileAngle(0),
tileBackground{ 255.0, 255.0, 255.0, 255.0 },
tileSkipBlanks(-1),
tileDepth(VIPS_FOREIGN_DZ_DEPTH_LAST) {}
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

BIN
test/fixtures/image-in-alpha.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

View File

@@ -13,6 +13,7 @@ const getPath = function (filename) {
// Based on the dHash gradient method - see http://www.hackerfactor.com/blog/index.php?/archives/529-Kind-of-Like-That.html
const fingerprint = function (image, callback) {
sharp(image)
.flatten('gray')
.greyscale()
.normalise()
.resize(9, 8, { fit: sharp.fit.fill })
@@ -87,6 +88,7 @@ module.exports = {
inputPngTruncated: getPath('truncated.png'), // gm convert 2569067123_aca715a2ee_o.jpg -resize 320x240 saw.png ; head -c 10000 saw.png > truncated.png
inputPngEmbed: getPath('embedgravitybird.png'), // Released to sharp under a CC BY 4.0
inputPngRGBWithAlpha: getPath('2569067123_aca715a2ee_o.png'), // http://www.flickr.com/photos/grizdave/2569067123/ (same as inputJpg)
inputPngImageInAlpha: getPath('image-in-alpha.png'), // https://github.com/lovell/sharp/issues/1597
inputWebP: getPath('4.webp'), // http://www.gstatic.com/webp/gallery/4.webp
inputWebPWithTransparency: getPath('5_webp_a.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp

View File

@@ -648,6 +648,16 @@ describe('Input/output', function () {
it('Ignore unknown attribute', function () {
sharp(null, { unknown: true });
});
it('Invalid page property throws', function () {
assert.throws(function () {
sharp(null, { page: -1 });
}, /Expected integer between 0 and 100000 for page but received -1 of type number/);
});
it('Invalid pages property throws', function () {
assert.throws(function () {
sharp(null, { pages: '1' });
}, /Expected integer between -1 and 100000 for pages but received 1 of type string/);
});
});
describe('create new image', function () {

View File

@@ -259,4 +259,35 @@ describe('JPEG', function () {
});
});
});
it('Specifying quantization table provides different JPEG', function (done) {
// First generate with default quantization table
sharp(fixtures.inputJpg)
.resize(320, 240)
.jpeg({ optimiseCoding: false })
.toBuffer(function (err, withDefaultQuantizationTable, withInfo) {
if (err) throw err;
assert.strictEqual(true, withDefaultQuantizationTable.length > 0);
assert.strictEqual(withDefaultQuantizationTable.length, withInfo.size);
assert.strictEqual('jpeg', withInfo.format);
assert.strictEqual(320, withInfo.width);
assert.strictEqual(240, withInfo.height);
// Then generate with different quantization table
sharp(fixtures.inputJpg)
.resize(320, 240)
.jpeg({ optimiseCoding: false, quantizationTable: 3 })
.toBuffer(function (err, withQuantTable3, withoutInfo) {
if (err) throw err;
assert.strictEqual(true, withQuantTable3.length > 0);
assert.strictEqual(withQuantTable3.length, withoutInfo.size);
assert.strictEqual('jpeg', withoutInfo.format);
assert.strictEqual(320, withoutInfo.width);
assert.strictEqual(240, withoutInfo.height);
// Verify image is same (as mozjpeg may not be present) size or less
assert.strictEqual(true, withQuantTable3.length <= withDefaultQuantizationTable.length);
done();
});
});
});
});

View File

@@ -307,6 +307,20 @@ describe('Image metadata', function () {
readable.pipe(pipeline);
});
it('Stream in, rejected Promise out', () => {
const pipeline = sharp();
fs
.createReadStream(__filename)
.pipe(pipeline);
return pipeline
.metadata()
.then(
() => Promise.reject(new Error('Expected metadata to reject')),
err => assert.strictEqual(err.message, 'Input buffer contains unsupported image format')
);
});
it('Stream', function (done) {
const readable = fs.createReadStream(fixtures.inputJpg);
const pipeline = sharp().metadata(function (err, metadata) {
@@ -390,6 +404,34 @@ describe('Image metadata', function () {
});
});
it('Include metadata in output, enabled via empty object', () =>
sharp(fixtures.inputJpgWithExif)
.withMetadata({})
.toBuffer()
.then((buffer) => sharp(buffer)
.metadata()
.then(metadata => {
assert.strictEqual(true, metadata.hasProfile);
assert.strictEqual(8, metadata.orientation);
assert.strictEqual('object', typeof metadata.exif);
assert.strictEqual(true, metadata.exif instanceof Buffer);
// EXIF
const exif = exifReader(metadata.exif);
assert.strictEqual('object', typeof exif);
assert.strictEqual('object', typeof exif.image);
assert.strictEqual('number', typeof exif.image.XResolution);
// 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('RGB', profile.colorSpace);
assert.strictEqual('Perceptual', profile.intent);
assert.strictEqual('Monitor', profile.deviceClass);
})
)
);
it('Remove EXIF metadata after a resize', function (done) {
sharp(fixtures.inputJpgWithExif)
.resize(320, 240)

View File

@@ -122,4 +122,21 @@ describe('Modulate', function () {
});
});
});
it('should be able to use linear and modulate together', function () {
const base = 'modulate-linear.jpg';
const actual = fixtures.path('output.' + base);
const expected = fixtures.expected(base);
const contrast = 1.5;
const brightness = 0.5;
return sharp(fixtures.testPattern)
.linear(contrast, -(128 * contrast) + 128)
.modulate({ brightness })
.toFile(actual)
.then(function () {
fixtures.assertMaxColourDistance(actual, expected);
});
});
});

View File

@@ -2,6 +2,7 @@
const fs = require('fs');
const assert = require('assert');
const promisify = require('util').promisify;
const rimraf = require('rimraf');
const sharp = require('../../');
@@ -85,6 +86,19 @@ describe('TIFF', function () {
});
});
it('Increasing TIFF quality increases file size', () =>
sharp(fixtures.inputJpgWithLandscapeExif1)
.tiff({ quality: 40 })
.toBuffer()
.then(tiff40 => sharp(fixtures.inputJpgWithLandscapeExif1)
.tiff({ quality: 90 })
.toBuffer()
.then(tiff90 =>
assert.strictEqual(true, tiff40.length < tiff90.length)
)
)
);
it('Invalid TIFF quality throws error', function () {
assert.throws(function () {
sharp().tiff({ quality: 101 });
@@ -137,40 +151,40 @@ describe('TIFF', function () {
});
});
it('TIFF setting xres and yres on file', function (done) {
const res = 1000.0; // inputTiff has a dpi of 300 (res*2.54)
it('TIFF setting xres and yres on file', () =>
sharp(fixtures.inputTiff)
.tiff({
xres: (res),
yres: (res)
xres: 1000,
yres: 1000
})
.toFile(fixtures.outputTiff, (err, info) => {
if (err) throw err;
assert.strictEqual('tiff', info.format);
sharp(fixtures.outputTiff).metadata(function (err, metadata) {
if (err) throw err;
assert.strictEqual(metadata.density, res * 2.54); // convert to dpi
rimraf(fixtures.outputTiff, done);
});
});
});
.toFile(fixtures.outputTiff)
.then(() => sharp(fixtures.outputTiff)
.metadata()
.then(({ density }) => {
assert.strictEqual(true,
density === 2540 || // libvips <= 8.8.2
density === 25400); // libvips >= 8.8.3
return promisify(rimraf)(fixtures.outputTiff);
})
)
);
it('TIFF setting xres and yres on buffer', function (done) {
const res = 1000.0; // inputTiff has a dpi of 300 (res*2.54)
it('TIFF setting xres and yres on buffer', () =>
sharp(fixtures.inputTiff)
.tiff({
xres: (res),
yres: (res)
xres: 1000,
yres: 1000
})
.toBuffer(function (err, data, info) {
if (err) throw err;
sharp(data).metadata(function (err, metadata) {
if (err) throw err;
assert.strictEqual(metadata.density, res * 2.54); // convert to dpi
done();
});
});
});
.toBuffer()
.then(data => sharp(data)
.metadata()
.then(({ density }) => {
assert.strictEqual(true,
density === 2540 || // libvips <= 8.8.2
density === 25400); // libvips >= 8.8.3
})
)
);
it('TIFF invalid xres value should throw an error', function () {
assert.throws(function () {

View File

@@ -91,6 +91,29 @@ const assertGoogleTiles = function (directory, expectedTileSize, expectedLevels,
});
};
// Verifies tiles at specified level in a given output directory are > size+overlap
const assertTileOverlap = function (directory, tileSize, done) {
// Get sorted levels
const levels = fs.readdirSync(directory).sort((a, b) => a - b);
// Select the highest tile level
const highestLevel = levels[levels.length - 1];
// Get sorted tiles from greatest level
const tiles = fs.readdirSync(path.join(directory, highestLevel)).sort();
// Select a tile from the approximate center of the image
const squareTile = path.join(directory, highestLevel, tiles[Math.floor(tiles.length / 2)]);
sharp(squareTile).metadata(function (err, metadata) {
if (err) {
throw err;
} else {
// Tile with an overlap should be larger than original size
assert.strictEqual(true, metadata.width > tileSize);
assert.strictEqual(true, metadata.height > tileSize);
done();
}
});
};
describe('Tile', function () {
it('Valid size values pass', function () {
[1, 8192].forEach(function (size) {
@@ -297,7 +320,9 @@ describe('Tile', function () {
assert.strictEqual(2225, info.height);
assert.strictEqual(3, info.channels);
assert.strictEqual('undefined', typeof info.size);
assertDeepZoomTiles(directory, 512 + (2 * 16), 13, done);
assertDeepZoomTiles(directory, 512 + (2 * 16), 13, function () {
assertTileOverlap(directory, 512, done);
});
});
});
});

View File

@@ -41,6 +41,21 @@ describe('Trim borders', function () {
});
});
it('single colour PNG where alpha channel provides the image', () =>
sharp(fixtures.inputPngImageInAlpha)
.trim()
.toBuffer({ resolveWithObject: true })
.then(({ data, info }) => {
assert.strictEqual(true, data.length > 0);
assert.strictEqual('png', info.format);
assert.strictEqual(916, info.width);
assert.strictEqual(137, info.height);
assert.strictEqual(4, info.channels);
assert.strictEqual(-6, info.trimOffsetLeft);
assert.strictEqual(-20, info.trimOffsetTop);
})
);
it('16-bit PNG with alpha channel', function (done) {
sharp(fixtures.inputPngWithTransparency16bit)
.resize(32, 32)