Compare commits

...

20 Commits

Author SHA1 Message Date
Lovell Fuller
596b38a3bb Release v0.23.4 2019-12-05 10:00:29 +00:00
Lovell Fuller
d31a91a599 Expose raw TIFFTAG_PHOTOSHOP metadata #1600 2019-11-29 13:05:07 +00:00
Lovell Fuller
400ef71b6f Handle zero-length Buffers in Node.js v13.2.0+
These now use nullptr internally - see
https://github.com/nodejs/node/pull/30339
2019-11-28 21:20:32 +00:00
Lovell Fuller
bb15cd9067 Improve thread safety with copy-on-write for metadata #1986 2019-11-27 23:15:56 +00:00
Lovell Fuller
6ee6a226e1 Docs: add FreeBSD build status 2019-11-19 11:16:48 +00:00
Lovell Fuller
94d51a94c8 Docs: recommend jemalloc buildpack for Heroku 2019-11-19 11:13:25 +00:00
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
28 changed files with 271 additions and 81 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

@@ -87,3 +87,5 @@ matrix:
osx_image: xcode10 osx_image: xcode10
language: node_js language: node_js
node_js: "13" node_js: "13"
cache:
npm: false

View File

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

View File

@@ -44,6 +44,7 @@ A `Promise` is returned when `callback` is not provided.
- `icc`: Buffer containing raw [ICC][3] profile data, if present - `icc`: Buffer containing raw [ICC][3] profile data, if present
- `iptc`: Buffer containing raw IPTC data, if present - `iptc`: Buffer containing raw IPTC data, if present
- `xmp`: Buffer containing raw XMP data, if present - `xmp`: Buffer containing raw XMP data, if present
- `tifftagPhotoshop`: Buffer containing raw TIFFTAG_PHOTOSHOP data, if present
### Parameters ### Parameters

View File

@@ -303,7 +303,7 @@ const data = await sharp(input)
- Throws **[Error][4]** unsupported format or options - Throws **[Error][4]** unsupported format or options
Returns **Sharp** Returns **Sharp**
## tile ## tile
@@ -315,11 +315,11 @@ Warning: multiple sharp instances concurrently producing tile output can expose
### Parameters ### Parameters
- `options` **[Object][6]?** - `options` **[Object][6]?**
- `options.size` **[Number][9]** tile size in pixels, a value between 1 and 8192. (optional, default `256`) - `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.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.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.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.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.skipBlanks` **[Number][9]** threshold to skip tile generation, a value 0 - 255 for 8-bit images or 0 - 65535 for 16-bit images (optional, default `-1`)
- `options.container` **[String][2]** tile container, with value `fs` (filesystem) or `zip` (compressed file). (optional, default `'fs'`) - `options.container` **[String][2]** tile container, with value `fs` (filesystem) or `zip` (compressed file). (optional, default `'fs'`)

View File

@@ -196,6 +196,8 @@ Returns **Sharp**
## trim ## trim
Trim "boring" pixels from all edges that contain values similar to the top-left pixel. 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. The `info` response Object will contain `trimOffsetLeft` and `trimOffsetTop` properties.
### Parameters ### Parameters

View File

@@ -4,6 +4,32 @@
Requires libvips v8.8.1. Requires libvips v8.8.1.
#### v0.23.4 - 5<sup>th</sup> December 2019
* Handle zero-length Buffer objects when using Node.js v13.2.0+.
* Expose raw TIFFTAG_PHOTOSHOP metadata.
[#1600](https://github.com/lovell/sharp/issues/1600)
* Improve thread safety by using copy-on-write when updating metadata.
[#1986](https://github.com/lovell/sharp/issues/1986)
#### 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 #### v0.23.2 - 28<sup>th</sup> October 2019
* Add `background` option to tile output operation. * Add `background` option to tile output operation.

View File

@@ -128,6 +128,9 @@ the help and code contributions of the following people:
* [Jakub Michálek](https://github.com/Goues) * [Jakub Michálek](https://github.com/Goues)
* [Ilya Ovdin](https://github.com/iovdin) * [Ilya Ovdin](https://github.com/iovdin)
* [Andargor](https://github.com/Andargor) * [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! Thank you!

View File

@@ -106,6 +106,8 @@ Only 64-bit (x64) `node.exe` is supported.
### FreeBSD ### FreeBSD
[![FreeBSD Build Status](https://api.cirrus-ci.com/github/lovell/sharp.svg)](https://cirrus-ci.com/github/lovell/sharp)
libvips must be installed before `npm install` is run. libvips must be installed before `npm install` is run.
This can be achieved via package or ports: This can be achieved via package or ports:
@@ -126,6 +128,9 @@ https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=193528
Set [NODE_MODULES_CACHE](https://devcenter.heroku.com/articles/nodejs-support#cache-behavior) Set [NODE_MODULES_CACHE](https://devcenter.heroku.com/articles/nodejs-support#cache-behavior)
to `false` when using the `yarn` package manager. to `false` when using the `yarn` package manager.
To reduce the effects of memory fragmentation, add the
[jemalloc buildpack](https://github.com/gaffneyc/heroku-buildpack-jemalloc).
### Docker ### Docker
[Marc Bachmann](https://github.com/marcbachmann) maintains an [Marc Bachmann](https://github.com/marcbachmann) maintains an
@@ -246,6 +251,9 @@ SHARP_DIST_BASE_URL="https://hostname/path/" npm install sharp
to use `https://hostname/path/libvips-x.y.z-platform.tar.gz`. 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 ### Licences
This module is licensed under the terms of the This module is licensed under the terms of the

View File

@@ -207,6 +207,7 @@ function clone () {
* - `icc`: Buffer containing raw [ICC](https://www.npmjs.com/package/icc) profile data, if present * - `icc`: Buffer containing raw [ICC](https://www.npmjs.com/package/icc) profile data, if present
* - `iptc`: Buffer containing raw IPTC data, if present * - `iptc`: Buffer containing raw IPTC data, if present
* - `xmp`: Buffer containing raw XMP data, if present * - `xmp`: Buffer containing raw XMP data, if present
* - `tifftagPhotoshop`: Buffer containing raw TIFFTAG_PHOTOSHOP data, if present
* *
* @example * @example
* const image = sharp(inputJpg); * const image = sharp(inputJpg);

View File

@@ -575,7 +575,7 @@ function tile (options) {
if (options.overlap > this.options.tileSize) { if (options.overlap > this.options.tileSize) {
throw is.invalidParameterError('overlap', `<= size (${this.options.tileSize})`, options.overlap); throw is.invalidParameterError('overlap', `<= size (${this.options.tileSize})`, options.overlap);
} }
this.options.tileOverlap = tile.overlap; this.options.tileOverlap = options.overlap;
} else { } else {
throw is.invalidParameterError('overlap', 'integer between 0 and 8192', options.overlap); throw is.invalidParameterError('overlap', 'integer between 0 and 8192', options.overlap);
} }

View File

@@ -367,7 +367,10 @@ function extract (options) {
/** /**
* Trim "boring" pixels from all edges that contain values similar to the top-left pixel. * 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. * 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. * @param {Number} [threshold=10] the allowed difference from the top-left pixel, a number greater than zero.
* @returns {Sharp} * @returns {Sharp}
* @throws {Error} Invalid parameters * @throws {Error} Invalid parameters

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.23.2", "version": "0.23.4",
"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": [
@@ -63,12 +63,13 @@
"Jordan Prudhomme <jordan@raboland.fr>", "Jordan Prudhomme <jordan@raboland.fr>",
"Ilya Ovdin <iovdin@gmail.com>", "Ilya Ovdin <iovdin@gmail.com>",
"Andargor <andargor@yahoo.com>", "Andargor <andargor@yahoo.com>",
"Paul Neave <paul.neave@gmail.com>" "Paul Neave <paul.neave@gmail.com>",
"Brendan Kennedy <brenwken@gmail.com>"
], ],
"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)",
"clean": "rm -rf node_modules/ build/ vendor/ .nyc_output/ coverage/ test/fixtures/output.*", "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-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-licensing": "license-checker --production --summary --onlyAllow=\"Apache-2.0;BSD;ISC;MIT\"",
"test-coverage": "./test/coverage/report.sh", "test-coverage": "./test/coverage/report.sh",
@@ -109,7 +110,7 @@
"detect-libc": "^1.0.3", "detect-libc": "^1.0.3",
"nan": "^2.14.0", "nan": "^2.14.0",
"npmlog": "^4.1.2", "npmlog": "^4.1.2",
"prebuild-install": "^5.3.2", "prebuild-install": "^5.3.3",
"semver": "^6.3.0", "semver": "^6.3.0",
"simple-get": "^3.1.0", "simple-get": "^3.1.0",
"tar": "^5.0.5", "tar": "^5.0.5",
@@ -117,14 +118,14 @@
}, },
"devDependencies": { "devDependencies": {
"async": "^3.1.0", "async": "^3.1.0",
"cc": "^1.0.2", "cc": "^2.0.1",
"decompress-zip": "^0.3.2", "decompress-zip": "^0.3.2",
"documentation": "^12.1.2", "documentation": "^12.1.4",
"exif-reader": "^1.0.3", "exif-reader": "^1.0.3",
"icc": "^1.0.0", "icc": "^1.0.0",
"license-checker": "^25.0.1", "license-checker": "^25.0.1",
"mocha": "^6.2.2", "mocha": "^6.2.2",
"mock-fs": "^4.10.2", "mock-fs": "^4.10.4",
"nyc": "^14.1.1", "nyc": "^14.1.1",
"prebuild": "^9.1.1", "prebuild": "^9.1.1",
"prebuild-ci": "^3.1.0", "prebuild-ci": "^3.1.0",
@@ -138,6 +139,9 @@
"engines": { "engines": {
"node": ">=8.5.0" "node": ">=8.5.0"
}, },
"funding": {
"url": "https://opencollective.com/libvips"
},
"semistandard": { "semistandard": {
"env": [ "env": [
"mocha" "mocha"
@@ -146,10 +150,7 @@
"cc": { "cc": {
"linelength": "120", "linelength": "120",
"filter": [ "filter": [
"build/c++11", "build/include"
"build/include",
"runtime/indentation_namespace",
"runtime/references"
] ]
} }
} }

View File

@@ -17,7 +17,7 @@
#include <string.h> #include <string.h>
#include <vector> #include <vector>
#include <queue> #include <queue>
#include <mutex> #include <mutex> // NOLINT(build/c++11)
#include <node.h> #include <node.h>
#include <node_buffer.h> #include <node_buffer.h>
@@ -58,6 +58,7 @@ namespace sharp {
v8::Local<v8::Object> buffer = AttrAs<v8::Object>(input, "buffer"); v8::Local<v8::Object> buffer = AttrAs<v8::Object>(input, "buffer");
descriptor->bufferLength = node::Buffer::Length(buffer); descriptor->bufferLength = node::Buffer::Length(buffer);
descriptor->buffer = node::Buffer::Data(buffer); descriptor->buffer = node::Buffer::Data(buffer);
descriptor->isBuffer = TRUE;
buffersToPersist.push_back(buffer); buffersToPersist.push_back(buffer);
} }
descriptor->failOnError = AttrTo<bool>(input, "failOnError"); descriptor->failOnError = AttrTo<bool>(input, "failOnError");
@@ -246,7 +247,7 @@ namespace sharp {
std::tuple<VImage, ImageType> OpenInput(InputDescriptor *descriptor, VipsAccess accessMethod) { std::tuple<VImage, ImageType> OpenInput(InputDescriptor *descriptor, VipsAccess accessMethod) {
VImage image; VImage image;
ImageType imageType; ImageType imageType;
if (descriptor->buffer != nullptr) { if (descriptor->isBuffer) {
if (descriptor->rawChannels > 0) { if (descriptor->rawChannels > 0) {
// Raw, uncompressed pixel data // Raw, uncompressed pixel data
image = VImage::new_from_memory(descriptor->buffer, descriptor->bufferLength, image = VImage::new_from_memory(descriptor->buffer, descriptor->bufferLength,
@@ -277,7 +278,7 @@ namespace sharp {
} }
image = VImage::new_from_buffer(descriptor->buffer, descriptor->bufferLength, nullptr, option); image = VImage::new_from_buffer(descriptor->buffer, descriptor->bufferLength, nullptr, option);
if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) { if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
SetDensity(image, descriptor->density); image = SetDensity(image, descriptor->density);
} }
} catch (vips::VError const &err) { } catch (vips::VError const &err) {
throw vips::VError(std::string("Input buffer has corrupt header: ") + err.what()); throw vips::VError(std::string("Input buffer has corrupt header: ") + err.what());
@@ -323,7 +324,7 @@ namespace sharp {
} }
image = VImage::new_from_file(descriptor->file.data(), option); image = VImage::new_from_file(descriptor->file.data(), option);
if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) { if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
SetDensity(image, descriptor->density); image = SetDensity(image, descriptor->density);
} }
} catch (vips::VError const &err) { } catch (vips::VError const &err) {
throw vips::VError(std::string("Input file has corrupt header: ") + err.what()); throw vips::VError(std::string("Input file has corrupt header: ") + err.what());
@@ -370,15 +371,19 @@ namespace sharp {
/* /*
Set EXIF Orientation of image. Set EXIF Orientation of image.
*/ */
void SetExifOrientation(VImage image, int const orientation) { VImage SetExifOrientation(VImage image, int const orientation) {
image.set(VIPS_META_ORIENTATION, orientation); VImage copy = image.copy();
copy.set(VIPS_META_ORIENTATION, orientation);
return copy;
} }
/* /*
Remove EXIF Orientation from image. Remove EXIF Orientation from image.
*/ */
void RemoveExifOrientation(VImage image) { VImage RemoveExifOrientation(VImage image) {
vips_image_remove(image.get_image(), VIPS_META_ORIENTATION); VImage copy = image.copy();
copy.remove(VIPS_META_ORIENTATION);
return copy;
} }
/* /*
@@ -398,11 +403,13 @@ namespace sharp {
/* /*
Set pixels/mm resolution based on a pixels/inch density. Set pixels/mm resolution based on a pixels/inch density.
*/ */
void SetDensity(VImage image, const double density) { VImage SetDensity(VImage image, const double density) {
const double pixelsPerMm = density / 25.4; const double pixelsPerMm = density / 25.4;
image.set("Xres", pixelsPerMm); VImage copy = image.copy();
image.set("Yres", pixelsPerMm); copy.set("Xres", pixelsPerMm);
image.set(VIPS_META_RESOLUTION_UNIT, "in"); copy.set("Yres", pixelsPerMm);
copy.set(VIPS_META_RESOLUTION_UNIT, "in");
return copy;
} }
/* /*

View File

@@ -43,12 +43,13 @@ using vips::VImage;
namespace sharp { namespace sharp {
struct InputDescriptor { struct InputDescriptor { // NOLINT(runtime/indentation_namespace)
std::string name; std::string name;
std::string file; std::string file;
char *buffer; char *buffer;
bool failOnError; bool failOnError;
size_t bufferLength; size_t bufferLength;
bool isBuffer;
double density; double density;
int rawChannels; int rawChannels;
int rawWidth; int rawWidth;
@@ -64,6 +65,7 @@ namespace sharp {
buffer(nullptr), buffer(nullptr),
failOnError(TRUE), failOnError(TRUE),
bufferLength(0), bufferLength(0),
isBuffer(FALSE),
density(72.0), density(72.0),
rawChannels(0), rawChannels(0),
rawWidth(0), rawWidth(0),
@@ -92,7 +94,7 @@ namespace sharp {
// Create an InputDescriptor instance from a v8::Object describing an input image // Create an InputDescriptor instance from a v8::Object describing an input image
InputDescriptor* CreateInputDescriptor( 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 { enum class ImageType {
JPEG, JPEG,
@@ -175,12 +177,12 @@ namespace sharp {
/* /*
Set EXIF Orientation of image. Set EXIF Orientation of image.
*/ */
void SetExifOrientation(VImage image, int const orientation); VImage SetExifOrientation(VImage image, int const orientation);
/* /*
Remove EXIF Orientation from image. Remove EXIF Orientation from image.
*/ */
void RemoveExifOrientation(VImage image); VImage RemoveExifOrientation(VImage image);
/* /*
Does this image have a non-default density? Does this image have a non-default density?
@@ -195,7 +197,7 @@ namespace sharp {
/* /*
Set pixels/mm resolution based on a pixels/inch density. Set pixels/mm resolution based on a pixels/inch density.
*/ */
void SetDensity(VImage image, const double density); VImage SetDensity(VImage image, const double density);
/* /*
Check the proposed format supports the current dimensions. Check the proposed format supports the current dimensions.

View File

@@ -116,6 +116,14 @@ class MetadataWorker : public Nan::AsyncWorker {
memcpy(baton->xmp, xmp, xmpLength); memcpy(baton->xmp, xmp, xmpLength);
baton->xmpLength = xmpLength; baton->xmpLength = xmpLength;
} }
// TIFFTAG_PHOTOSHOP
if (image.get_typeof(VIPS_META_PHOTOSHOP_NAME) == VIPS_TYPE_BLOB) {
size_t tifftagPhotoshopLength;
void const *tifftagPhotoshop = image.get_blob(VIPS_META_PHOTOSHOP_NAME, &tifftagPhotoshopLength);
baton->tifftagPhotoshop = static_cast<char *>(g_malloc(tifftagPhotoshopLength));
memcpy(baton->tifftagPhotoshop, tifftagPhotoshop, tifftagPhotoshopLength);
baton->tifftagPhotoshopLength = tifftagPhotoshopLength;
}
} }
// Clean up // Clean up
@@ -189,6 +197,12 @@ class MetadataWorker : public Nan::AsyncWorker {
New("xmp").ToLocalChecked(), New("xmp").ToLocalChecked(),
Nan::NewBuffer(baton->xmp, baton->xmpLength, sharp::FreeCallback, nullptr).ToLocalChecked()); Nan::NewBuffer(baton->xmp, baton->xmpLength, sharp::FreeCallback, nullptr).ToLocalChecked());
} }
if (baton->tifftagPhotoshopLength > 0) {
Set(info,
New("tifftagPhotoshop").ToLocalChecked(),
Nan::NewBuffer(baton->tifftagPhotoshop, baton->tifftagPhotoshopLength, sharp::FreeCallback, nullptr)
.ToLocalChecked());
}
argv[1] = info; argv[1] = info;
} }

View File

@@ -48,6 +48,8 @@ struct MetadataBaton {
size_t iptcLength; size_t iptcLength;
char *xmp; char *xmp;
size_t xmpLength; size_t xmpLength;
char *tifftagPhotoshop;
size_t tifftagPhotoshopLength;
std::string err; std::string err;
MetadataBaton(): MetadataBaton():
@@ -71,7 +73,9 @@ struct MetadataBaton {
iptc(nullptr), iptc(nullptr),
iptcLength(0), iptcLength(0),
xmp(nullptr), xmp(nullptr),
xmpLength(0) {} xmpLength(0),
tifftagPhotoshop(nullptr),
tifftagPhotoshopLength(0) {}
}; };
NAN_METHOD(metadata); NAN_METHOD(metadata);

View File

@@ -191,12 +191,20 @@ namespace sharp {
VImage alpha = image[image.bands() - 1]; VImage alpha = image[image.bands() - 1];
return RemoveAlpha(image) return RemoveAlpha(image)
.colourspace(VIPS_INTERPRETATION_LCH) .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); .bandjoin(alpha);
} else { } else {
return image return image
.colourspace(VIPS_INTERPRETATION_LCH) .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)) { if (HasAlpha(background)) {
background = background.flatten(); background = background.flatten();
} }
int top, width, height; int left, top, width, height;
int const left = image.find_trim(&top, &width, &height, VImage::option() left = image.find_trim(&top, &width, &height, VImage::option()
->set("background", background(0, 0)) ->set("background", background(0, 0))
->set("threshold", threshold)); ->set("threshold", threshold));
if (width == 0 || height == 0) { 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); return image.extract_area(left, top, width, height);
} }

View File

@@ -38,6 +38,9 @@
#elif defined(__APPLE__) #elif defined(__APPLE__)
#define STAT64_STRUCT stat #define STAT64_STRUCT stat
#define STAT64_FUNCTION stat #define STAT64_FUNCTION stat
#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__)
#define STAT64_STRUCT stat
#define STAT64_FUNCTION stat
#else #else
#define STAT64_STRUCT stat64 #define STAT64_STRUCT stat64
#define STAT64_FUNCTION stat64 #define STAT64_FUNCTION stat64
@@ -101,7 +104,7 @@ class PipelineWorker : public Nan::AsyncWorker {
if (baton->rotateBeforePreExtract) { if (baton->rotateBeforePreExtract) {
if (rotation != VIPS_ANGLE_D0) { if (rotation != VIPS_ANGLE_D0) {
image = image.rot(rotation); image = image.rot(rotation);
sharp::RemoveExifOrientation(image); image = sharp::RemoveExifOrientation(image);
} }
if (baton->rotationAngle != 0.0) { if (baton->rotationAngle != 0.0) {
std::vector<double> background; std::vector<double> background;
@@ -401,20 +404,20 @@ class PipelineWorker : public Nan::AsyncWorker {
// Rotate post-extract 90-angle // Rotate post-extract 90-angle
if (!baton->rotateBeforePreExtract && rotation != VIPS_ANGLE_D0) { if (!baton->rotateBeforePreExtract && rotation != VIPS_ANGLE_D0) {
image = image.rot(rotation); image = image.rot(rotation);
sharp::RemoveExifOrientation(image); image = sharp::RemoveExifOrientation(image);
} }
// Flip (mirror about Y axis) // Flip (mirror about Y axis)
if (baton->flip) { if (baton->flip) {
image = image.flip(VIPS_DIRECTION_VERTICAL); image = image.flip(VIPS_DIRECTION_VERTICAL);
sharp::RemoveExifOrientation(image); image = sharp::RemoveExifOrientation(image);
} }
// Flop (mirror about X axis) // Flop (mirror about X axis)
if (baton->flop) { if (baton->flop) {
image = image.flip(VIPS_DIRECTION_HORIZONTAL); image = image.flip(VIPS_DIRECTION_HORIZONTAL);
sharp::RemoveExifOrientation(image); image = sharp::RemoveExifOrientation(image);
} }
// Join additional color channels to the image // Join additional color channels to the image
@@ -697,7 +700,7 @@ class PipelineWorker : public Nan::AsyncWorker {
// Override EXIF Orientation tag // Override EXIF Orientation tag
if (baton->withMetadata && baton->withMetadataOrientation != -1) { if (baton->withMetadata && baton->withMetadataOrientation != -1) {
sharp::SetExifOrientation(image, baton->withMetadataOrientation); image = sharp::SetExifOrientation(image, baton->withMetadataOrientation);
} }
// Number of channels used in output image // Number of channels used in output image

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 // 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) { const fingerprint = function (image, callback) {
sharp(image) sharp(image)
.flatten('gray')
.greyscale() .greyscale()
.normalise() .normalise()
.resize(9, 8, { fit: sharp.fit.fill }) .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 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 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) 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 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 inputWebPWithTransparency: getPath('5_webp_a.webp'), // http://www.gstatic.com/webp/gallery3/5_webp_a.webp
@@ -95,6 +97,7 @@ module.exports = {
inputTiffCielab: getPath('cielab-dagams.tiff'), // https://github.com/lovell/sharp/issues/646 inputTiffCielab: getPath('cielab-dagams.tiff'), // https://github.com/lovell/sharp/issues/646
inputTiffUncompressed: getPath('uncompressed_tiff.tiff'), // https://code.google.com/archive/p/imagetestsuite/wikis/TIFFTestSuite.wiki file: 0c84d07e1b22b76f24cccc70d8788e4a.tif inputTiffUncompressed: getPath('uncompressed_tiff.tiff'), // https://code.google.com/archive/p/imagetestsuite/wikis/TIFFTestSuite.wiki file: 0c84d07e1b22b76f24cccc70d8788e4a.tif
inputTiff8BitDepth: getPath('8bit_depth.tiff'), inputTiff8BitDepth: getPath('8bit_depth.tiff'),
inputTifftagPhotoshop: getPath('tifftag-photoshop.tiff'), // https://github.com/lovell/sharp/issues/1600
inputGif: getPath('Crash_test.gif'), // http://upload.wikimedia.org/wikipedia/commons/e/e3/Crash_test.gif inputGif: getPath('Crash_test.gif'), // http://upload.wikimedia.org/wikipedia/commons/e/e3/Crash_test.gif
inputGifGreyPlusAlpha: getPath('grey-plus-alpha.gif'), // http://i.imgur.com/gZ5jlmE.gif inputGifGreyPlusAlpha: getPath('grey-plus-alpha.gif'), // http://i.imgur.com/gZ5jlmE.gif
inputGifAnimated: getPath('rotating-squares.gif'), // CC0 https://loading.io/spinner/blocks/-rotating-squares-preloader-gif inputGifAnimated: getPath('rotating-squares.gif'), // CC0 https://loading.io/spinner/blocks/-rotating-squares-preloader-gif

BIN
test/fixtures/tifftag-photoshop.tiff vendored Normal file

Binary file not shown.

View File

@@ -515,6 +515,21 @@ describe('Image metadata', function () {
}); });
}); });
it('16-bit TIFF with TIFFTAG_PHOTOSHOP metadata', () =>
sharp(fixtures.inputTifftagPhotoshop)
.metadata()
.then(metadata => {
assert.strictEqual(metadata.format, 'tiff');
assert.strictEqual(metadata.width, 317);
assert.strictEqual(metadata.height, 211);
assert.strictEqual(metadata.space, 'rgb16');
assert.strictEqual(metadata.channels, 3);
assert.strictEqual(typeof metadata.tifftagPhotoshop, 'object');
assert.strictEqual(metadata.tifftagPhotoshop instanceof Buffer, true);
assert.strictEqual(metadata.tifftagPhotoshop.length, 6634);
})
);
it('File input with corrupt header fails gracefully', function (done) { it('File input with corrupt header fails gracefully', function (done) {
sharp(fixtures.inputJpgWithCorruptHeader) sharp(fixtures.inputJpgWithCorruptHeader)
.metadata(function (err) { .metadata(function (err) {

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 fs = require('fs');
const assert = require('assert'); const assert = require('assert');
const promisify = require('util').promisify;
const rimraf = require('rimraf'); const rimraf = require('rimraf');
const sharp = require('../../'); const sharp = require('../../');
@@ -150,40 +151,40 @@ describe('TIFF', function () {
}); });
}); });
it('TIFF setting xres and yres on file', function (done) { it('TIFF setting xres and yres on file', () =>
const res = 1000.0; // inputTiff has a dpi of 300 (res*2.54)
sharp(fixtures.inputTiff) sharp(fixtures.inputTiff)
.tiff({ .tiff({
xres: (res), xres: 1000,
yres: (res) yres: 1000
}) })
.toFile(fixtures.outputTiff, (err, info) => { .toFile(fixtures.outputTiff)
if (err) throw err; .then(() => sharp(fixtures.outputTiff)
assert.strictEqual('tiff', info.format); .metadata()
sharp(fixtures.outputTiff).metadata(function (err, metadata) { .then(({ density }) => {
if (err) throw err; assert.strictEqual(true,
assert.strictEqual(metadata.density, res * 2.54); // convert to dpi density === 2540 || // libvips <= 8.8.2
rimraf(fixtures.outputTiff, done); density === 25400); // libvips >= 8.8.3
}); return promisify(rimraf)(fixtures.outputTiff);
}); })
}); )
);
it('TIFF setting xres and yres on buffer', function (done) { it('TIFF setting xres and yres on buffer', () =>
const res = 1000.0; // inputTiff has a dpi of 300 (res*2.54)
sharp(fixtures.inputTiff) sharp(fixtures.inputTiff)
.tiff({ .tiff({
xres: (res), xres: 1000,
yres: (res) yres: 1000
}) })
.toBuffer(function (err, data, info) { .toBuffer()
if (err) throw err; .then(data => sharp(data)
sharp(data).metadata(function (err, metadata) { .metadata()
if (err) throw err; .then(({ density }) => {
assert.strictEqual(metadata.density, res * 2.54); // convert to dpi assert.strictEqual(true,
done(); density === 2540 || // libvips <= 8.8.2
}); density === 25400); // libvips >= 8.8.3
}); })
}); )
);
it('TIFF invalid xres value should throw an error', function () { it('TIFF invalid xres value should throw an error', function () {
assert.throws(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 () { describe('Tile', function () {
it('Valid size values pass', function () { it('Valid size values pass', function () {
[1, 8192].forEach(function (size) { [1, 8192].forEach(function (size) {
@@ -297,7 +320,9 @@ describe('Tile', function () {
assert.strictEqual(2225, info.height); assert.strictEqual(2225, info.height);
assert.strictEqual(3, info.channels); assert.strictEqual(3, info.channels);
assert.strictEqual('undefined', typeof info.size); 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) { it('16-bit PNG with alpha channel', function (done) {
sharp(fixtures.inputPngWithTransparency16bit) sharp(fixtures.inputPngWithTransparency16bit)
.resize(32, 32) .resize(32, 32)