Compare commits

..

11 Commits

Author SHA1 Message Date
Lovell Fuller
16cf9f0ef2 Release v0.28.1 2021-04-05 12:28:16 +01:00
Lovell Fuller
133f69d66c Bump prebuild-install (for sharp_local_prebuilds) 2021-04-05 12:26:28 +01:00
Lovell Fuller
bc60daff9e Allow EXIF metadata to be set/update #650 2021-04-05 11:39:53 +01:00
Lovell Fuller
43a085d1ae Add support for OME-TIFF subIFDs #2557 2021-04-02 08:04:21 +01:00
Lovell Fuller
8c33d0aa56 Allow ensureAlpha to set alpha transparency level #2634 2021-04-01 21:14:06 +01:00
Lovell Fuller
fe0767df13 Install: log errors with more obvious prefix 2021-04-01 16:20:58 +01:00
Lovell Fuller
08a25a0c8f Docs: add animated WebP example #2648 2021-04-01 16:04:46 +01:00
Lovell Fuller
cd410080bd Docs: remove reference to specific Lambda runtime 2021-03-29 20:16:07 +01:00
Lovell Fuller
7555378e3b Release v0.28.0 2021-03-29 14:10:34 +01:00
Lovell Fuller
80c95ee66a Docs: libvips tarballs are a bit smaller now 2021-03-29 12:16:48 +01:00
Lovell Fuller
31563b210d Ensure GIF input will work with future libvips v8.11.0 2021-03-29 12:16:10 +01:00
25 changed files with 285 additions and 48 deletions

View File

@@ -18,18 +18,33 @@ Returns **Sharp**
## ensureAlpha
Ensure alpha channel, if missing. The added alpha channel will be fully opaque. This is a no-op if the image already has an alpha channel.
Ensure the output image has an alpha transparency channel.
If missing, the added alpha channel will have the specified
transparency level, defaulting to fully-opaque (1).
This is a no-op if the image already has an alpha channel.
### Parameters
- `alpha` **[number][1]** alpha transparency level (0=fully-transparent, 1=fully-opaque) (optional, default `1`)
### Examples
```javascript
sharp('rgb.jpg')
// rgba.png will be a 4 channel image with a fully-opaque alpha channel
await sharp('rgb.jpg')
.ensureAlpha()
.toFile('rgba.png', function(err, info) {
// rgba.png is a 4 channel image with a fully opaque alpha channel
});
.toFile('rgba.png')
```
```javascript
// rgba is a 4 channel image with a fully-transparent alpha channel
const rgba = await sharp(rgb)
.ensureAlpha(0)
.toBuffer();
```
- Throws **[Error][2]** Invalid alpha transparency level
Returns **Sharp**
**Meta**
@@ -42,7 +57,7 @@ Extract a single channel from a multi-channel image.
### Parameters
- `channel` **([number][1] \| [string][2])** zero-indexed channel/band number to extract, or `red`, `green`, `blue` or `alpha`.
- `channel` **([number][1] \| [string][3])** zero-indexed channel/band number to extract, or `red`, `green`, `blue` or `alpha`.
### Examples
@@ -56,7 +71,7 @@ sharp(input)
});
```
- Throws **[Error][3]** Invalid channel
- Throws **[Error][2]** Invalid channel
Returns **Sharp**
@@ -75,11 +90,11 @@ For raw pixel input, the `options` object should contain a `raw` attribute, whic
### Parameters
- `images` **([Array][4]<([string][2] \| [Buffer][5])> | [string][2] \| [Buffer][5])** one or more images (file paths, Buffers).
- `images` **([Array][4]<([string][3] \| [Buffer][5])> | [string][3] \| [Buffer][5])** one or more images (file paths, Buffers).
- `options` **[Object][6]** image options, see `sharp()` constructor.
- Throws **[Error][3]** Invalid parameters
- Throws **[Error][2]** Invalid parameters
Returns **Sharp**
@@ -89,7 +104,7 @@ Perform a bitwise boolean operation on all input image channels (bands) to produ
### Parameters
- `boolOp` **[string][2]** one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively.
- `boolOp` **[string][3]** one of `and`, `or` or `eor` to perform that bitwise operation, like the C logic operators `&`, `|` and `^` respectively.
### Examples
@@ -103,15 +118,15 @@ sharp('3-channel-rgb-input.png')
});
```
- Throws **[Error][3]** Invalid parameters
- Throws **[Error][2]** Invalid parameters
Returns **Sharp**
[1]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array

View File

@@ -28,6 +28,7 @@ Implements the [stream.Duplex][1] class.
- `options.density` **[number][8]** number representing the DPI for vector images in the range 1 to 100000. (optional, default `72`)
- `options.pages` **[number][8]** number of pages to extract for multi-page input (GIF, WebP, AVIF, TIFF, PDF), use -1 for all pages. (optional, default `1`)
- `options.page` **[number][8]** page number to start extracting from for multi-page input (GIF, WebP, AVIF, TIFF, PDF), zero based. (optional, default `0`)
- `options.subifd` **[number][8]** subIFD (Sub Image File Directory) to extract for OME-TIFF, defaults to main image. (optional, default `-1`)
- `options.level` **[number][8]** level to extract from a multi-level input (OpenSlide), zero based. (optional, default `0`)
- `options.animated` **[boolean][7]** Set to `true` to read all frames/pages of an animated image (equivalent of setting `pages` to `-1`). (optional, default `false`)
- `options.raw` **[Object][6]?** describes raw pixel input image data. See `raw()` for pixel ordering.

View File

@@ -21,6 +21,7 @@ A `Promise` is returned when `callback` is not provided.
- `delay`: Delay in ms between each page in an animated image, provided as an array of integers.
- `pagePrimary`: Number of the primary page in a HEIF image
- `levels`: Details of each level in a multi-level image provided as an array of objects, requires libvips compiled with support for OpenSlide
- `subifds`: Number of Sub Image File Directories in an OME-TIFF image
- `hasProfile`: Boolean indicating the presence of an embedded ICC profile
- `hasAlpha`: Boolean indicating the presence of an alpha transparency channel
- `orientation`: Number value of the EXIF Orientation header, if present

View File

@@ -119,6 +119,7 @@ sRGB colour space and strip all metadata, including the removal of any ICC profi
- `options` **[Object][6]?**
- `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.
- `options.exif` **[Object][6]<[Object][6]>** Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data. (optional, default `{}`)
### Examples
@@ -129,6 +130,19 @@ sharp('input.jpg')
.then(info => { ... });
```
```javascript
// Set "IFD0-Copyright" in output EXIF metadata
await sharp(input)
.withMetadata({
exif: {
IFD0: {
Copyright: 'Wernham Hogg'
}
}
})
.toBuffer();
```
- Throws **[Error][4]** Invalid parameters
Returns **Sharp**
@@ -267,6 +281,13 @@ const data = await sharp(input)
.toBuffer();
```
```javascript
// Optimise the file size of an animated WebP
const outputWebp = await sharp(inputWebp, { animated: true })
.webp({ reductionEffort: 6 })
.toBuffer();
```
- Throws **[Error][4]** Invalid options
Returns **Sharp**

View File

@@ -4,7 +4,20 @@
Requires libvips v8.10.6
### v0.28.0 - TBD
### v0.28.1 - 5th April 2021
* Ensure all installation errors are logged with a more obvious prefix.
* Allow `withMetadata` to set and update EXIF metadata.
[#650](https://github.com/lovell/sharp/issues/650)
* Add support for OME-TIFF Sub Image File Directories (subIFD).
[#2557](https://github.com/lovell/sharp/issues/2557)
* Allow `ensureAlpha` to set the alpha transparency level.
[#2634](https://github.com/lovell/sharp/issues/2634)
### v0.28.0 - 29th March 2021
* Prebuilt binaries now include mozjpeg and libimagequant (BSD 2-Clause).

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

View File

@@ -23,7 +23,7 @@ Node.js v10+ on the most common platforms:
* Windows x64
* Windows x86
An ~8MB tarball containing libvips and its most commonly used dependencies
An ~7.5MB tarball containing libvips and its most commonly used dependencies
is downloaded via HTTPS and stored within `node_modules/sharp/vendor` during `npm install`.
This provides support for the
@@ -102,6 +102,10 @@ To install the prebuilt sharp binaries from a custom URL,
set the `sharp_binary_host` npm config option
or the `npm_config_sharp_binary_host` environment variable.
To install the prebuilt sharp binaries from a directory on the local filesystem,
set the `sharp_local_prebuilds` npm config option
or the `npm_config_sharp_local_prebuilds` environment variable.
To install the prebuilt libvips binaries from a custom URL,
set the `sharp_libvips_binary_host` npm config option
or the `npm_config_sharp_libvips_binary_host` environment variable.
@@ -178,8 +182,6 @@ to `false` when using the `yarn` package manager.
## AWS Lambda
Set the Lambda runtime to `nodejs12.x`.
The binaries in the `node_modules` directory of the
[deployment package](https://docs.aws.amazon.com/lambda/latest/dg/nodejs-package.html)
must be for the Linux x64 platform.

File diff suppressed because one or more lines are too long

View File

@@ -145,7 +145,9 @@ try {
tmpFileStream
.on('error', function (err) {
// Clean up temporary file
fs.unlinkSync(tarPathTemp);
try {
fs.unlinkSync(tarPathTemp);
} catch (e) {}
fail(err);
})
.on('close', function () {
@@ -157,7 +159,7 @@ try {
fs.copyFileSync(tarPathTemp, tarPathCache);
fs.unlinkSync(tarPathTemp);
}
extractTarball(tarPathCache);
extractTarball(tarPathCache, platformAndArch);
});
}
});

View File

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

View File

@@ -132,6 +132,7 @@ const debuglog = util.debuglog('sharp');
* @param {number} [options.density=72] - number representing the DPI for vector images in the range 1 to 100000.
* @param {number} [options.pages=1] - number of pages to extract for multi-page input (GIF, WebP, AVIF, TIFF, PDF), use -1 for all pages.
* @param {number} [options.page=0] - page number to start extracting from for multi-page input (GIF, WebP, AVIF, TIFF, PDF), zero based.
* @param {number} [options.subifd=-1] - subIFD (Sub Image File Directory) to extract for OME-TIFF, defaults to main image.
* @param {number} [options.level=0] - level to extract from a multi-level input (OpenSlide), zero based.
* @param {boolean} [options.animated=false] - Set to `true` to read all frames/pages of an animated image (equivalent of setting `pages` to `-1`).
* @param {Object} [options.raw] - describes raw pixel input image data. See `raw()` for pixel ordering.
@@ -221,7 +222,7 @@ const Sharp = function (input, options) {
joinChannelIn: [],
extractChannel: -1,
removeAlpha: false,
ensureAlpha: false,
ensureAlpha: -1,
colourspace: 'srgb',
composite: [],
// output
@@ -231,6 +232,7 @@ const Sharp = function (input, options) {
withMetadata: false,
withMetadataOrientation: -1,
withMetadataIcc: '',
withMetadataStrs: {},
resolveWithObject: false,
// output format
jpegQuality: 80,

View File

@@ -9,9 +9,9 @@ const sharp = require('../build/Release/sharp.node');
* @private
*/
function _inputOptionsFromObject (obj) {
const { raw, density, limitInputPixels, sequentialRead, failOnError, animated, page, pages } = obj;
return [raw, density, limitInputPixels, sequentialRead, failOnError, animated, page, pages].some(is.defined)
? { raw, density, limitInputPixels, sequentialRead, failOnError, animated, page, pages }
const { raw, density, limitInputPixels, sequentialRead, failOnError, animated, page, pages, subifd } = obj;
return [raw, density, limitInputPixels, sequentialRead, failOnError, animated, page, pages, subifd].some(is.defined)
? { raw, density, limitInputPixels, sequentialRead, failOnError, animated, page, pages, subifd }
: undefined;
}
@@ -131,6 +131,14 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
throw is.invalidParameterError('level', 'integer between 0 and 256', inputOptions.level);
}
}
// Sub Image File Directory (TIFF)
if (is.defined(inputOptions.subifd)) {
if (is.integer(inputOptions.subifd) && is.inRange(inputOptions.subifd, -1, 100000)) {
inputDescriptor.subifd = inputOptions.subifd;
} else {
throw is.invalidParameterError('subifd', 'integer between -1 and 100000', inputOptions.subifd);
}
}
// Create new image
if (is.defined(inputOptions.create)) {
if (
@@ -255,6 +263,7 @@ function _isStreamInput () {
* - `delay`: Delay in ms between each page in an animated image, provided as an array of integers.
* - `pagePrimary`: Number of the primary page in a HEIF image
* - `levels`: Details of each level in a multi-level image provided as an array of objects, requires libvips compiled with support for OpenSlide
* - `subifds`: Number of Sub Image File Directories in an OME-TIFF image
* - `hasProfile`: Boolean indicating the presence of an embedded ICC profile
* - `hasAlpha`: Boolean indicating the presence of an alpha transparency channel
* - `orientation`: Number value of the EXIF Orientation header, if present

View File

@@ -39,7 +39,7 @@ const cachePath = function () {
const log = function (item) {
if (item instanceof Error) {
console.error(`sharp: ${item.message}`);
console.error(`sharp: Installation error: ${item.message}`);
} else {
console.log(`sharp: ${item}`);
}

View File

@@ -148,9 +148,22 @@ function toBuffer (options, callback) {
* .toFile('output-with-metadata.jpg')
* .then(info => { ... });
*
* @example
* // Set "IFD0-Copyright" in output EXIF metadata
* await sharp(input)
* .withMetadata({
* exif: {
* IFD0: {
* Copyright: 'Wernham Hogg'
* }
* }
* })
* .toBuffer();
*
* @param {Object} [options]
* @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.
* @param {Object<Object>} [options.exif={}] Object keyed by IFD0, IFD1 etc. of key/value string pairs to write as EXIF data.
* @returns {Sharp}
* @throws {Error} Invalid parameters
*/
@@ -171,6 +184,25 @@ function withMetadata (options) {
throw is.invalidParameterError('icc', 'string filesystem path to ICC profile', options.icc);
}
}
if (is.defined(options.exif)) {
if (is.object(options.exif)) {
for (const [ifd, entries] of Object.entries(options.exif)) {
if (is.object(entries)) {
for (const [k, v] of Object.entries(entries)) {
if (is.string(v)) {
this.options.withMetadataStrs[`exif-${ifd.toLowerCase()}-${k}`] = v;
} else {
throw is.invalidParameterError(`exif.${ifd}.${k}`, 'string', v);
}
}
} else {
throw is.invalidParameterError(`exif.${ifd}`, 'object', entries);
}
}
} else {
throw is.invalidParameterError('exif', 'object', options.exif);
}
}
}
return this;
}
@@ -383,6 +415,12 @@ function png (options) {
* .webp({ lossless: true })
* .toBuffer();
*
* @example
* // Optimise the file size of an animated WebP
* const outputWebp = await sharp(inputWebp, { animated: true })
* .webp({ reductionEffort: 6 })
* .toBuffer();
*
* @param {Object} [options] - output options
* @param {number} [options.quality=80] - quality, integer 1-100
* @param {number} [options.alphaQuality=100] - quality of alpha layer, integer 0-100

View File

@@ -1,7 +1,7 @@
{
"name": "sharp",
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, AVIF and TIFF images",
"version": "0.28.0-beta1",
"version": "0.28.1",
"author": "Lovell Fuller <npm@lovell.info>",
"homepage": "https://github.com/lovell/sharp",
"contributors": [
@@ -120,7 +120,7 @@
"color": "^3.1.3",
"detect-libc": "^1.0.3",
"node-addon-api": "^3.1.0",
"prebuild-install": "^6.0.1",
"prebuild-install": "^6.1.1",
"semver": "^7.3.5",
"simple-get": "^3.1.0",
"tar-fs": "^2.1.1",

View File

@@ -36,6 +36,9 @@ namespace sharp {
std::string AttrAsStr(Napi::Object obj, std::string attr) {
return obj.Get(attr).As<Napi::String>();
}
std::string AttrAsStr(Napi::Object obj, unsigned int const attr) {
return obj.Get(attr).As<Napi::String>();
}
uint32_t AttrAsUint32(Napi::Object obj, std::string attr) {
return obj.Get(attr).As<Napi::Number>().Uint32Value();
}
@@ -104,6 +107,10 @@ namespace sharp {
if (HasAttr(input, "level")) {
descriptor->level = AttrAsUint32(input, "level");
}
// subIFD (OME-TIFF)
if (HasAttr(input, "subifd")) {
descriptor->subifd = AttrAsInt32(input, "subifd");
}
// Create new image
if (HasAttr(input, "createChannels")) {
descriptor->createChannels = AttrAsUint32(input, "createChannels");
@@ -213,6 +220,8 @@ namespace sharp {
{ "VipsForeignLoadTiffBuffer", ImageType::TIFF },
{ "VipsForeignLoadGifFile", ImageType::GIF },
{ "VipsForeignLoadGifBuffer", ImageType::GIF },
{ "VipsForeignLoadNsgifFile", ImageType::GIF },
{ "VipsForeignLoadNsgifBuffer", ImageType::GIF },
{ "VipsForeignLoadSvgFile", ImageType::SVG },
{ "VipsForeignLoadSvgBuffer", ImageType::SVG },
{ "VipsForeignLoadHeifFile", ImageType::HEIF },
@@ -317,6 +326,9 @@ namespace sharp {
if (imageType == ImageType::OPENSLIDE) {
option->set("level", descriptor->level);
}
if (imageType == ImageType::TIFF) {
option->set("subifd", descriptor->subifd);
}
image = VImage::new_from_buffer(descriptor->buffer, descriptor->bufferLength, nullptr, option);
if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
image = SetDensity(image, descriptor->density);
@@ -389,6 +401,9 @@ namespace sharp {
if (imageType == ImageType::OPENSLIDE) {
option->set("level", descriptor->level);
}
if (imageType == ImageType::TIFF) {
option->set("subifd", descriptor->subifd);
}
image = VImage::new_from_file(descriptor->file.data(), option);
if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
image = SetDensity(image, descriptor->density);
@@ -797,10 +812,10 @@ namespace sharp {
/*
Ensures alpha channel, if missing.
*/
VImage EnsureAlpha(VImage image) {
VImage EnsureAlpha(VImage image, double const value) {
if (!HasAlpha(image)) {
std::vector<double> alpha;
alpha.push_back(sharp::MaximumImageAlpha(image.interpretation()));
alpha.push_back(value * sharp::MaximumImageAlpha(image.interpretation()));
image = image.bandjoin_const(alpha);
}
return image;

View File

@@ -60,6 +60,7 @@ namespace sharp {
int pages;
int page;
int level;
int subifd;
int createChannels;
int createWidth;
int createHeight;
@@ -82,6 +83,7 @@ namespace sharp {
pages(1),
page(0),
level(0),
subifd(-1),
createChannels(0),
createWidth(0),
createHeight(0),
@@ -93,6 +95,7 @@ namespace sharp {
// Convenience methods to access the attributes of a Napi::Object
bool HasAttr(Napi::Object obj, std::string attr);
std::string AttrAsStr(Napi::Object obj, std::string attr);
std::string AttrAsStr(Napi::Object obj, unsigned int const attr);
uint32_t AttrAsUint32(Napi::Object obj, std::string attr);
int32_t AttrAsInt32(Napi::Object obj, std::string attr);
int32_t AttrAsInt32(Napi::Object obj, unsigned int const attr);
@@ -296,7 +299,7 @@ namespace sharp {
/*
Ensures alpha channel, if missing.
*/
VImage EnsureAlpha(VImage image);
VImage EnsureAlpha(VImage image, double const value);
} // namespace sharp

View File

@@ -86,6 +86,9 @@ class MetadataWorker : public Napi::AsyncWorker {
baton->levels.push_back(std::pair<int, int>(width, height));
}
}
if (image.get_typeof(VIPS_META_N_SUBIFDS) == G_TYPE_INT) {
baton->subifds = image.get_int(VIPS_META_N_SUBIFDS);
}
baton->hasProfile = sharp::HasProfile(image);
// Derived attributes
baton->hasAlpha = sharp::HasAlpha(image);
@@ -203,6 +206,9 @@ class MetadataWorker : public Napi::AsyncWorker {
}
info.Set("levels", levels);
}
if (baton->subifds > 0) {
info.Set("subifds", baton->subifds);
}
info.Set("hasProfile", baton->hasProfile);
info.Set("hasAlpha", baton->hasAlpha);
if (baton->orientation > 0) {

View File

@@ -41,6 +41,7 @@ struct MetadataBaton {
int pagePrimary;
std::string compression;
std::vector<std::pair<int, int>> levels;
int subifds;
bool hasProfile;
bool hasAlpha;
int orientation;
@@ -68,6 +69,7 @@ struct MetadataBaton {
pageHeight(0),
loop(-1),
pagePrimary(-1),
subifds(0),
hasProfile(false),
hasAlpha(false),
orientation(0),

View File

@@ -346,7 +346,7 @@ class PipelineWorker : public Napi::AsyncWorker {
bool const shouldModulate = baton->brightness != 1.0 || baton->saturation != 1.0 || baton->hue != 0.0;
if (shouldComposite && !sharp::HasAlpha(image)) {
image = sharp::EnsureAlpha(image);
image = sharp::EnsureAlpha(image, 1);
}
bool const shouldPremultiplyAlpha = sharp::HasAlpha(image) &&
@@ -594,7 +594,7 @@ class PipelineWorker : public Napi::AsyncWorker {
// Ensure image to composite is sRGB with premultiplied alpha
compositeImage = compositeImage.colourspace(VIPS_INTERPRETATION_sRGB);
if (!sharp::HasAlpha(compositeImage)) {
compositeImage = sharp::EnsureAlpha(compositeImage);
compositeImage = sharp::EnsureAlpha(compositeImage, 1);
}
if (!composite->premultiplied) compositeImage = compositeImage.premultiply();
// Calculate position
@@ -691,8 +691,8 @@ class PipelineWorker : public Napi::AsyncWorker {
}
// Ensure alpha channel, if missing
if (baton->ensureAlpha) {
image = sharp::EnsureAlpha(image);
if (baton->ensureAlpha != -1) {
image = sharp::EnsureAlpha(image, baton->ensureAlpha);
}
// Convert image to sRGB, if not already
@@ -717,11 +717,17 @@ class PipelineWorker : public Napi::AsyncWorker {
->set("input_profile", "srgb")
->set("intent", VIPS_INTENT_PERCEPTUAL));
}
// Override EXIF Orientation tag
if (baton->withMetadata && baton->withMetadataOrientation != -1) {
image = sharp::SetExifOrientation(image, baton->withMetadataOrientation);
}
// Metadata key/value pairs, e.g. EXIF
if (!baton->withMetadataStrs.empty()) {
image = image.copy();
for (const auto& s : baton->withMetadataStrs) {
image.set(s.first.data(), s.second.data());
}
}
// Number of channels used in output image
baton->channels = image.bands();
@@ -1341,7 +1347,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
baton->affineInterpolator = vips::VInterpolate::new_from_name(sharp::AttrAsStr(options, "affineInterpolator").data());
baton->removeAlpha = sharp::AttrAsBool(options, "removeAlpha");
baton->ensureAlpha = sharp::AttrAsBool(options, "ensureAlpha");
baton->ensureAlpha = sharp::AttrAsDouble(options, "ensureAlpha");
if (options.Has("boolean")) {
baton->boolean = sharp::CreateInputDescriptor(options.Get("boolean").As<Napi::Object>());
baton->booleanOp = sharp::GetBooleanOperation(sharp::AttrAsStr(options, "booleanOp"));
@@ -1379,6 +1385,12 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
baton->withMetadata = sharp::AttrAsBool(options, "withMetadata");
baton->withMetadataOrientation = sharp::AttrAsUint32(options, "withMetadataOrientation");
baton->withMetadataIcc = sharp::AttrAsStr(options, "withMetadataIcc");
Napi::Object mdStrs = options.Get("withMetadataStrs").As<Napi::Object>();
Napi::Array mdStrKeys = mdStrs.GetPropertyNames();
for (unsigned int i = 0; i < mdStrKeys.Length(); i++) {
std::string k = sharp::AttrAsStr(mdStrKeys, i);
baton->withMetadataStrs.insert(std::make_pair(k, sharp::AttrAsStr(mdStrs, k)));
}
// Format-specific
baton->jpegQuality = sharp::AttrAsUint32(options, "jpegQuality");
baton->jpegProgressive = sharp::AttrAsBool(options, "jpegProgressive");

View File

@@ -18,6 +18,7 @@
#include <memory>
#include <string>
#include <vector>
#include <unordered_map>
#include <napi.h>
#include <vips/vips8>
@@ -168,6 +169,7 @@ struct PipelineBaton {
bool withMetadata;
int withMetadataOrientation;
std::string withMetadataIcc;
std::unordered_map<std::string, std::string> withMetadataStrs;
std::unique_ptr<double[]> convKernel;
int convKernelWidth;
int convKernelHeight;
@@ -178,7 +180,7 @@ struct PipelineBaton {
VipsOperationBoolean bandBoolOp;
int extractChannel;
bool removeAlpha;
bool ensureAlpha;
double ensureAlpha;
VipsInterpretation colourspace;
int pageHeight;
std::vector<int> delay;
@@ -297,7 +299,7 @@ struct PipelineBaton {
bandBoolOp(VIPS_OPERATION_BOOLEAN_LAST),
extractChannel(-1),
removeAlpha(false),
ensureAlpha(false),
ensureAlpha(-1.0),
colourspace(VIPS_INTERPRETATION_LAST),
pageHeight(0),
delay{-1},

View File

@@ -155,4 +155,27 @@ describe('Alpha transparency', function () {
});
}));
});
it('Valid ensureAlpha value used for alpha channel', async () => {
const background = { r: 255, g: 0, b: 0 };
const [r, g, b, alpha] = await sharp({
create: {
width: 8,
height: 8,
channels: 3,
background
}
})
.ensureAlpha(0.5)
.raw()
.toBuffer();
assert.deepStrictEqual({ r, g, b, alpha }, { ...background, alpha: 127 });
});
it('Invalid ensureAlpha value throws', async () => {
assert.throws(() => {
sharp().ensureAlpha('fail');
});
});
});

View File

@@ -718,6 +718,19 @@ describe('Input/output', function () {
sharp({ level: -1 });
}, /Expected integer between 0 and 256 for level but received -1 of type number/);
});
it('Valid subifd property', function () {
sharp({ subifd: 1 });
});
it('Invalid subifd property (string) throws', function () {
assert.throws(function () {
sharp({ subifd: '1' });
}, /Expected integer between -1 and 100000 for subifd but received 1 of type string/);
});
it('Invalid subifd property (float) throws', function () {
assert.throws(function () {
sharp({ subifd: 1.2 });
}, /Expected integer between -1 and 100000 for subifd but received 1.2 of type number/);
});
});
describe('create new image', function () {

View File

@@ -125,7 +125,7 @@ describe('libvips binaries', function () {
it('logs an error message', function (done) {
console.error = function (msg) {
assert.strictEqual(msg, 'sharp: problem');
assert.strictEqual(msg, 'sharp: Installation error: problem');
done();
};
libvips.log(new Error('problem'));

View File

@@ -599,6 +599,30 @@ describe('Image metadata', function () {
});
});
it('Add EXIF metadata to JPEG', async () => {
const data = await sharp({
create: {
width: 8,
height: 8,
channels: 3,
background: 'red'
}
})
.jpeg()
.withMetadata({
exif: {
IFD0: { Software: 'sharp' },
IFD2: { ExposureTime: '0.2' }
}
})
.toBuffer();
const { exif } = await sharp(data).metadata();
const parsedExif = exifReader(exif);
assert.strictEqual(parsedExif.image.Software, 'sharp');
assert.strictEqual(parsedExif.exif.ExposureTime, 0.2);
});
it('chromaSubsampling 4:4:4:4 CMYK JPEG', function () {
return sharp(fixtures.inputJpgWithCmykProfile)
.metadata()
@@ -717,5 +741,20 @@ describe('Image metadata', function () {
sharp().withMetadata({ icc: true });
});
});
it('Non object exif', function () {
assert.throws(function () {
sharp().withMetadata({ exif: false });
});
});
it('Non string value in object exif', function () {
assert.throws(function () {
sharp().withMetadata({ exif: { ifd0: false } });
});
});
it('Non string value in nested object exif', function () {
assert.throws(function () {
sharp().withMetadata({ exif: { ifd0: { fail: false } } });
});
});
});
});