mirror of
https://github.com/lovell/sharp.git
synced 2026-02-05 14:16:17 +01:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
50c7a08754 | ||
|
|
9a0bb60737 | ||
|
|
deb5d81221 | ||
|
|
916b04dbac | ||
|
|
52307fad5d | ||
|
|
afb21135c2 | ||
|
|
b7fbffb3f7 | ||
|
|
5d98bcd8d8 | ||
|
|
e044788f63 | ||
|
|
4a9267ce12 | ||
|
|
104464c2e0 | ||
|
|
60adc110f5 | ||
|
|
2031d7d112 | ||
|
|
3402656ec5 | ||
|
|
4e84f743e4 | ||
|
|
17e50de5f0 |
@@ -400,7 +400,9 @@ Returns **Sharp**
|
|||||||
|
|
||||||
## modulate
|
## modulate
|
||||||
|
|
||||||
Transforms the image using brightness, saturation and hue rotation.
|
Transforms the image using brightness, saturation, hue rotation, and lightness.
|
||||||
|
Brightness and lightness both operate on luminance, with the difference being that
|
||||||
|
brightness is multiplicative whereas lightness is additive.
|
||||||
|
|
||||||
### Parameters
|
### Parameters
|
||||||
|
|
||||||
@@ -409,13 +411,14 @@ Transforms the image using brightness, saturation and hue rotation.
|
|||||||
* `options.brightness` **[number][1]?** Brightness multiplier
|
* `options.brightness` **[number][1]?** Brightness multiplier
|
||||||
* `options.saturation` **[number][1]?** Saturation multiplier
|
* `options.saturation` **[number][1]?** Saturation multiplier
|
||||||
* `options.hue` **[number][1]?** Degrees for hue rotation
|
* `options.hue` **[number][1]?** Degrees for hue rotation
|
||||||
|
* `options.lightness` **[number][1]?** Lightness addend
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
sharp(input)
|
sharp(input)
|
||||||
.modulate({
|
.modulate({
|
||||||
brightness: 2 // increase lightness by a factor of 2
|
brightness: 2 // increase brightness by a factor of 2
|
||||||
});
|
});
|
||||||
|
|
||||||
sharp(input)
|
sharp(input)
|
||||||
@@ -423,6 +426,11 @@ sharp(input)
|
|||||||
hue: 180 // hue-rotate by 180 degrees
|
hue: 180 // hue-rotate by 180 degrees
|
||||||
});
|
});
|
||||||
|
|
||||||
|
sharp(input)
|
||||||
|
.modulate({
|
||||||
|
lightness: 50 // increase lightness by +50
|
||||||
|
});
|
||||||
|
|
||||||
// decreate brightness and saturation while also hue-rotating by 90 degrees
|
// decreate brightness and saturation while also hue-rotating by 90 degrees
|
||||||
sharp(input)
|
sharp(input)
|
||||||
.modulate({
|
.modulate({
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ Note that raw pixel data is only supported for buffer output.
|
|||||||
By default all metadata will be removed, which includes EXIF-based orientation.
|
By default all metadata will be removed, which includes EXIF-based orientation.
|
||||||
See [withMetadata][1] for control over this.
|
See [withMetadata][1] for control over this.
|
||||||
|
|
||||||
|
The caller is responsible for ensuring directory structures and permissions exist.
|
||||||
|
|
||||||
A `Promise` is returned when `callback` is not provided.
|
A `Promise` is returned when `callback` is not provided.
|
||||||
|
|
||||||
### Parameters
|
### Parameters
|
||||||
@@ -328,6 +330,51 @@ The prebuilt binaries do not include this - see
|
|||||||
|
|
||||||
Returns **Sharp**
|
Returns **Sharp**
|
||||||
|
|
||||||
|
## jp2
|
||||||
|
|
||||||
|
Use these JP2 options for output image.
|
||||||
|
|
||||||
|
Requires libvips compiled with support for OpenJPEG.
|
||||||
|
The prebuilt binaries do not include this - see
|
||||||
|
[installing a custom libvips][11].
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
|
||||||
|
* `options` **[Object][6]?** output options
|
||||||
|
|
||||||
|
* `options.quality` **[number][9]** quality, integer 1-100 (optional, default `80`)
|
||||||
|
* `options.lossless` **[boolean][7]** use lossless compression mode (optional, default `false`)
|
||||||
|
* `options.tileWidth` **[number][9]** horizontal tile size (optional, default `512`)
|
||||||
|
* `options.tileHeight` **[number][9]** vertical tile size (optional, default `512`)
|
||||||
|
* `options.chromaSubsampling` **[string][2]** set to '4:2:0' to use chroma subsampling (optional, default `'4:4:4'`)
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Convert any input to lossless JP2 output
|
||||||
|
const data = await sharp(input)
|
||||||
|
.jp2({ lossless: true })
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Convert any input to very high quality JP2 output
|
||||||
|
const data = await sharp(input)
|
||||||
|
.jp2({
|
||||||
|
quality: 100,
|
||||||
|
chromaSubsampling: '4:4:4'
|
||||||
|
})
|
||||||
|
.toBuffer();
|
||||||
|
```
|
||||||
|
|
||||||
|
* Throws **[Error][4]** Invalid options
|
||||||
|
|
||||||
|
Returns **Sharp**
|
||||||
|
|
||||||
|
**Meta**
|
||||||
|
|
||||||
|
* **since**: 0.29.1
|
||||||
|
|
||||||
## tiff
|
## tiff
|
||||||
|
|
||||||
Use these TIFF options for output image.
|
Use these TIFF options for output image.
|
||||||
@@ -372,13 +419,15 @@ Use these AVIF options for output image.
|
|||||||
Whilst it is possible to create AVIF images smaller than 16x16 pixels,
|
Whilst it is possible to create AVIF images smaller than 16x16 pixels,
|
||||||
most web browsers do not display these properly.
|
most web browsers do not display these properly.
|
||||||
|
|
||||||
|
AVIF image sequences are not supported.
|
||||||
|
|
||||||
### Parameters
|
### Parameters
|
||||||
|
|
||||||
* `options` **[Object][6]?** output options
|
* `options` **[Object][6]?** output options
|
||||||
|
|
||||||
* `options.quality` **[number][9]** quality, integer 1-100 (optional, default `50`)
|
* `options.quality` **[number][9]** quality, integer 1-100 (optional, default `50`)
|
||||||
* `options.lossless` **[boolean][7]** use lossless compression (optional, default `false`)
|
* `options.lossless` **[boolean][7]** use lossless compression (optional, default `false`)
|
||||||
* `options.speed` **[number][9]** CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest) (optional, default `5`)
|
* `options.speed` **[number][9]** CPU effort vs file size, 0 (slowest/smallest) to 9 (fastest/largest) (optional, default `5`)
|
||||||
* `options.chromaSubsampling` **[string][2]** set to '4:2:0' to use chroma subsampling (optional, default `'4:4:4'`)
|
* `options.chromaSubsampling` **[string][2]** set to '4:2:0' to use chroma subsampling (optional, default `'4:4:4'`)
|
||||||
|
|
||||||
<!---->
|
<!---->
|
||||||
@@ -405,7 +454,7 @@ globally-installed libvips compiled with support for libheif, libde265 and x265.
|
|||||||
* `options.quality` **[number][9]** quality, integer 1-100 (optional, default `50`)
|
* `options.quality` **[number][9]** quality, integer 1-100 (optional, default `50`)
|
||||||
* `options.compression` **[string][2]** compression format: av1, hevc (optional, default `'av1'`)
|
* `options.compression` **[string][2]** compression format: av1, hevc (optional, default `'av1'`)
|
||||||
* `options.lossless` **[boolean][7]** use lossless compression (optional, default `false`)
|
* `options.lossless` **[boolean][7]** use lossless compression (optional, default `false`)
|
||||||
* `options.speed` **[number][9]** CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest) (optional, default `5`)
|
* `options.speed` **[number][9]** CPU effort vs file size, 0 (slowest/smallest) to 9 (fastest/largest) (optional, default `5`)
|
||||||
* `options.chromaSubsampling` **[string][2]** set to '4:2:0' to use chroma subsampling (optional, default `'4:4:4'`)
|
* `options.chromaSubsampling` **[string][2]** set to '4:2:0' to use chroma subsampling (optional, default `'4:4:4'`)
|
||||||
|
|
||||||
<!---->
|
<!---->
|
||||||
|
|||||||
@@ -4,6 +4,34 @@
|
|||||||
|
|
||||||
Requires libvips v8.11.3
|
Requires libvips v8.11.3
|
||||||
|
|
||||||
|
### v0.29.1 - 7th September 2021
|
||||||
|
|
||||||
|
* Add `lightness` option to `modulate` operation.
|
||||||
|
[#2846](https://github.com/lovell/sharp/pull/2846)
|
||||||
|
|
||||||
|
* Ensure correct PNG bitdepth is set based on number of colours.
|
||||||
|
[#2855](https://github.com/lovell/sharp/issues/2855)
|
||||||
|
|
||||||
|
* Ensure background is always premultiplied when compositing.
|
||||||
|
[#2858](https://github.com/lovell/sharp/issues/2858)
|
||||||
|
|
||||||
|
* Ensure images with P3 profiles retain full gamut.
|
||||||
|
[#2862](https://github.com/lovell/sharp/issues/2862)
|
||||||
|
|
||||||
|
* Add support for libvips compiled with OpenJPEG.
|
||||||
|
[#2868](https://github.com/lovell/sharp/pull/2868)
|
||||||
|
|
||||||
|
* Remove unsupported animation properties from AVIF output.
|
||||||
|
[#2870](https://github.com/lovell/sharp/issues/2870)
|
||||||
|
|
||||||
|
* Resolve paths before comparing input/output filenames.
|
||||||
|
[#2878](https://github.com/lovell/sharp/pull/2878)
|
||||||
|
[@rexxars](https://github.com/rexxars)
|
||||||
|
|
||||||
|
* Allow use of speed 9 (fastest) for HEIF encoding.
|
||||||
|
[#2879](https://github.com/lovell/sharp/pull/2879)
|
||||||
|
[@rexxars](https://github.com/rexxars)
|
||||||
|
|
||||||
### v0.29.0 - 17th August 2021
|
### v0.29.0 - 17th August 2021
|
||||||
|
|
||||||
* Drop support for Node.js 10, now requires Node.js >= 12.13.0.
|
* Drop support for Node.js 10, now requires Node.js >= 12.13.0.
|
||||||
|
|||||||
1
docs/docute.min.js
vendored
Normal file
1
docs/docute.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -218,3 +218,9 @@ GitHub: https://github.com/Daiz
|
|||||||
|
|
||||||
Name: Mart Jansink
|
Name: Mart Jansink
|
||||||
GitHub: https://github.com/mart-jansink
|
GitHub: https://github.com/mart-jansink
|
||||||
|
|
||||||
|
Name: Tenpi
|
||||||
|
GitHub: https://github.com/Tenpi
|
||||||
|
|
||||||
|
Name: Zaruike
|
||||||
|
https://github.com/Zaruike
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -199,6 +199,7 @@ const Sharp = function (input, options) {
|
|||||||
brightness: 1,
|
brightness: 1,
|
||||||
saturation: 1,
|
saturation: 1,
|
||||||
hue: 0,
|
hue: 0,
|
||||||
|
lightness: 0,
|
||||||
booleanBufferIn: null,
|
booleanBufferIn: null,
|
||||||
booleanFileIn: '',
|
booleanFileIn: '',
|
||||||
joinChannelIn: [],
|
joinChannelIn: [],
|
||||||
@@ -232,8 +233,13 @@ const Sharp = function (input, options) {
|
|||||||
pngAdaptiveFiltering: false,
|
pngAdaptiveFiltering: false,
|
||||||
pngPalette: false,
|
pngPalette: false,
|
||||||
pngQuality: 100,
|
pngQuality: 100,
|
||||||
pngColours: 256,
|
pngBitdepth: 8,
|
||||||
pngDither: 1,
|
pngDither: 1,
|
||||||
|
jp2Quality: 80,
|
||||||
|
jp2TileHeight: 512,
|
||||||
|
jp2TileWidth: 512,
|
||||||
|
jp2Lossless: false,
|
||||||
|
jp2ChromaSubsampling: '4:4:4',
|
||||||
webpQuality: 80,
|
webpQuality: 80,
|
||||||
webpAlphaQuality: 100,
|
webpAlphaQuality: 100,
|
||||||
webpLossless: false,
|
webpLossless: false,
|
||||||
|
|||||||
@@ -570,14 +570,16 @@ function recomb (inputMatrix) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transforms the image using brightness, saturation and hue rotation.
|
* Transforms the image using brightness, saturation, hue rotation, and lightness.
|
||||||
|
* Brightness and lightness both operate on luminance, with the difference being that
|
||||||
|
* brightness is multiplicative whereas lightness is additive.
|
||||||
*
|
*
|
||||||
* @since 0.22.1
|
* @since 0.22.1
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* sharp(input)
|
* sharp(input)
|
||||||
* .modulate({
|
* .modulate({
|
||||||
* brightness: 2 // increase lightness by a factor of 2
|
* brightness: 2 // increase brightness by a factor of 2
|
||||||
* });
|
* });
|
||||||
*
|
*
|
||||||
* sharp(input)
|
* sharp(input)
|
||||||
@@ -585,6 +587,11 @@ function recomb (inputMatrix) {
|
|||||||
* hue: 180 // hue-rotate by 180 degrees
|
* hue: 180 // hue-rotate by 180 degrees
|
||||||
* });
|
* });
|
||||||
*
|
*
|
||||||
|
* sharp(input)
|
||||||
|
* .modulate({
|
||||||
|
* lightness: 50 // increase lightness by +50
|
||||||
|
* });
|
||||||
|
*
|
||||||
* // decreate brightness and saturation while also hue-rotating by 90 degrees
|
* // decreate brightness and saturation while also hue-rotating by 90 degrees
|
||||||
* sharp(input)
|
* sharp(input)
|
||||||
* .modulate({
|
* .modulate({
|
||||||
@@ -597,6 +604,7 @@ function recomb (inputMatrix) {
|
|||||||
* @param {number} [options.brightness] Brightness multiplier
|
* @param {number} [options.brightness] Brightness multiplier
|
||||||
* @param {number} [options.saturation] Saturation multiplier
|
* @param {number} [options.saturation] Saturation multiplier
|
||||||
* @param {number} [options.hue] Degrees for hue rotation
|
* @param {number} [options.hue] Degrees for hue rotation
|
||||||
|
* @param {number} [options.lightness] Lightness addend
|
||||||
* @returns {Sharp}
|
* @returns {Sharp}
|
||||||
*/
|
*/
|
||||||
function modulate (options) {
|
function modulate (options) {
|
||||||
@@ -624,6 +632,13 @@ function modulate (options) {
|
|||||||
throw is.invalidParameterError('hue', 'number', options.hue);
|
throw is.invalidParameterError('hue', 'number', options.hue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if ('lightness' in options) {
|
||||||
|
if (is.number(options.lightness)) {
|
||||||
|
this.options.lightness = options.lightness;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('lightness', 'number', options.lightness);
|
||||||
|
}
|
||||||
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
103
lib/output.js
103
lib/output.js
@@ -1,5 +1,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const path = require('path');
|
||||||
const is = require('./is');
|
const is = require('./is');
|
||||||
const sharp = require('./sharp');
|
const sharp = require('./sharp');
|
||||||
|
|
||||||
@@ -13,10 +14,15 @@ const formats = new Map([
|
|||||||
['raw', 'raw'],
|
['raw', 'raw'],
|
||||||
['tiff', 'tiff'],
|
['tiff', 'tiff'],
|
||||||
['webp', 'webp'],
|
['webp', 'webp'],
|
||||||
['gif', 'gif']
|
['gif', 'gif'],
|
||||||
|
['jp2', 'jp2'],
|
||||||
|
['jpx', 'jp2'],
|
||||||
|
['j2k', 'jp2'],
|
||||||
|
['j2c', 'jp2']
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const errMagickSave = new Error('GIF output requires libvips with support for ImageMagick');
|
const errMagickSave = new Error('GIF output requires libvips with support for ImageMagick');
|
||||||
|
const errJp2Save = new Error('JP2 output requires libvips with support for OpenJPEG');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write output image data to a file.
|
* Write output image data to a file.
|
||||||
@@ -28,6 +34,8 @@ const errMagickSave = new Error('GIF output requires libvips with support for Im
|
|||||||
* By default all metadata will be removed, which includes EXIF-based orientation.
|
* By default all metadata will be removed, which includes EXIF-based orientation.
|
||||||
* See {@link withMetadata} for control over this.
|
* See {@link withMetadata} for control over this.
|
||||||
*
|
*
|
||||||
|
* The caller is responsible for ensuring directory structures and permissions exist.
|
||||||
|
*
|
||||||
* A `Promise` is returned when `callback` is not provided.
|
* A `Promise` is returned when `callback` is not provided.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
@@ -52,7 +60,7 @@ function toFile (fileOut, callback) {
|
|||||||
let err;
|
let err;
|
||||||
if (!is.string(fileOut)) {
|
if (!is.string(fileOut)) {
|
||||||
err = new Error('Missing output file path');
|
err = new Error('Missing output file path');
|
||||||
} else if (this.options.input.file === fileOut) {
|
} else if (is.string(this.options.input.file) && path.resolve(this.options.input.file) === path.resolve(fileOut)) {
|
||||||
err = new Error('Cannot use same file for input and output');
|
err = new Error('Cannot use same file for input and output');
|
||||||
} else if (this.options.formatOut === 'input' && fileOut.toLowerCase().endsWith('.gif') && !this.constructor.format.magick.output.file) {
|
} else if (this.options.formatOut === 'input' && fileOut.toLowerCase().endsWith('.gif') && !this.constructor.format.magick.output.file) {
|
||||||
err = errMagickSave;
|
err = errMagickSave;
|
||||||
@@ -403,7 +411,7 @@ function png (options) {
|
|||||||
const colours = options.colours || options.colors;
|
const colours = options.colours || options.colors;
|
||||||
if (is.defined(colours)) {
|
if (is.defined(colours)) {
|
||||||
if (is.integer(colours) && is.inRange(colours, 2, 256)) {
|
if (is.integer(colours) && is.inRange(colours, 2, 256)) {
|
||||||
this.options.pngColours = colours;
|
this.options.pngBitdepth = 1 << 31 - Math.clz32(Math.ceil(Math.log2(colours)));
|
||||||
} else {
|
} else {
|
||||||
throw is.invalidParameterError('colours', 'integer between 2 and 256', colours);
|
throw is.invalidParameterError('colours', 'integer between 2 and 256', colours);
|
||||||
}
|
}
|
||||||
@@ -509,6 +517,84 @@ function gif (options) {
|
|||||||
return this._updateFormatOut('gif', options);
|
return this._updateFormatOut('gif', options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use these JP2 options for output image.
|
||||||
|
*
|
||||||
|
* Requires libvips compiled with support for OpenJPEG.
|
||||||
|
* The prebuilt binaries do not include this - see
|
||||||
|
* {@link https://sharp.pixelplumbing.com/install#custom-libvips installing a custom libvips}.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Convert any input to lossless JP2 output
|
||||||
|
* const data = await sharp(input)
|
||||||
|
* .jp2({ lossless: true })
|
||||||
|
* .toBuffer();
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Convert any input to very high quality JP2 output
|
||||||
|
* const data = await sharp(input)
|
||||||
|
* .jp2({
|
||||||
|
* quality: 100,
|
||||||
|
* chromaSubsampling: '4:4:4'
|
||||||
|
* })
|
||||||
|
* .toBuffer();
|
||||||
|
*
|
||||||
|
* @since 0.29.1
|
||||||
|
*
|
||||||
|
* @param {Object} [options] - output options
|
||||||
|
* @param {number} [options.quality=80] - quality, integer 1-100
|
||||||
|
* @param {boolean} [options.lossless=false] - use lossless compression mode
|
||||||
|
* @param {number} [options.tileWidth=512] - horizontal tile size
|
||||||
|
* @param {number} [options.tileHeight=512] - vertical tile size
|
||||||
|
* @param {string} [options.chromaSubsampling='4:4:4'] - set to '4:2:0' to use chroma subsampling
|
||||||
|
* @returns {Sharp}
|
||||||
|
* @throws {Error} Invalid options
|
||||||
|
*/
|
||||||
|
/* istanbul ignore next */
|
||||||
|
function jp2 (options) {
|
||||||
|
if (!this.constructor.format.jp2k.output.buffer) {
|
||||||
|
throw errJp2Save;
|
||||||
|
}
|
||||||
|
if (is.object(options)) {
|
||||||
|
if (is.defined(options.quality)) {
|
||||||
|
if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
|
||||||
|
this.options.jp2Quality = options.quality;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('quality', 'integer between 1 and 100', options.quality);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(options.lossless)) {
|
||||||
|
if (is.bool(options.lossless)) {
|
||||||
|
this.options.jp2Lossless = options.lossless;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('lossless', 'boolean', options.lossless);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(options.tileWidth)) {
|
||||||
|
if (is.integer(options.tileWidth) && is.inRange(options.tileWidth, 1, 32768)) {
|
||||||
|
this.options.jp2TileWidth = options.tileWidth;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('tileWidth', 'integer between 1 and 32768', options.tileWidth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(options.tileHeight)) {
|
||||||
|
if (is.integer(options.tileHeight) && is.inRange(options.tileHeight, 1, 32768)) {
|
||||||
|
this.options.jp2TileHeight = options.tileHeight;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('tileHeight', 'integer between 1 and 32768', options.tileHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (is.defined(options.chromaSubsampling)) {
|
||||||
|
if (is.string(options.chromaSubsampling) && is.inArray(options.chromaSubsampling, ['4:2:0', '4:4:4'])) {
|
||||||
|
this.options.heifChromaSubsampling = options.chromaSubsampling;
|
||||||
|
} else {
|
||||||
|
throw is.invalidParameterError('chromaSubsampling', 'one of: 4:2:0, 4:4:4', options.chromaSubsampling);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this._updateFormatOut('jp2', options);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set animation options if available.
|
* Set animation options if available.
|
||||||
* @private
|
* @private
|
||||||
@@ -654,12 +740,14 @@ function tiff (options) {
|
|||||||
* Whilst it is possible to create AVIF images smaller than 16x16 pixels,
|
* Whilst it is possible to create AVIF images smaller than 16x16 pixels,
|
||||||
* most web browsers do not display these properly.
|
* most web browsers do not display these properly.
|
||||||
*
|
*
|
||||||
|
* AVIF image sequences are not supported.
|
||||||
|
*
|
||||||
* @since 0.27.0
|
* @since 0.27.0
|
||||||
*
|
*
|
||||||
* @param {Object} [options] - output options
|
* @param {Object} [options] - output options
|
||||||
* @param {number} [options.quality=50] - quality, integer 1-100
|
* @param {number} [options.quality=50] - quality, integer 1-100
|
||||||
* @param {boolean} [options.lossless=false] - use lossless compression
|
* @param {boolean} [options.lossless=false] - use lossless compression
|
||||||
* @param {number} [options.speed=5] - CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest)
|
* @param {number} [options.speed=5] - CPU effort vs file size, 0 (slowest/smallest) to 9 (fastest/largest)
|
||||||
* @param {string} [options.chromaSubsampling='4:4:4'] - set to '4:2:0' to use chroma subsampling
|
* @param {string} [options.chromaSubsampling='4:4:4'] - set to '4:2:0' to use chroma subsampling
|
||||||
* @returns {Sharp}
|
* @returns {Sharp}
|
||||||
* @throws {Error} Invalid options
|
* @throws {Error} Invalid options
|
||||||
@@ -680,7 +768,7 @@ function avif (options) {
|
|||||||
* @param {number} [options.quality=50] - quality, integer 1-100
|
* @param {number} [options.quality=50] - quality, integer 1-100
|
||||||
* @param {string} [options.compression='av1'] - compression format: av1, hevc
|
* @param {string} [options.compression='av1'] - compression format: av1, hevc
|
||||||
* @param {boolean} [options.lossless=false] - use lossless compression
|
* @param {boolean} [options.lossless=false] - use lossless compression
|
||||||
* @param {number} [options.speed=5] - CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest)
|
* @param {number} [options.speed=5] - CPU effort vs file size, 0 (slowest/smallest) to 9 (fastest/largest)
|
||||||
* @param {string} [options.chromaSubsampling='4:4:4'] - set to '4:2:0' to use chroma subsampling
|
* @param {string} [options.chromaSubsampling='4:4:4'] - set to '4:2:0' to use chroma subsampling
|
||||||
* @returns {Sharp}
|
* @returns {Sharp}
|
||||||
* @throws {Error} Invalid options
|
* @throws {Error} Invalid options
|
||||||
@@ -709,10 +797,10 @@ function heif (options) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (is.defined(options.speed)) {
|
if (is.defined(options.speed)) {
|
||||||
if (is.integer(options.speed) && is.inRange(options.speed, 0, 8)) {
|
if (is.integer(options.speed) && is.inRange(options.speed, 0, 9)) {
|
||||||
this.options.heifSpeed = options.speed;
|
this.options.heifSpeed = options.speed;
|
||||||
} else {
|
} else {
|
||||||
throw is.invalidParameterError('speed', 'integer between 0 and 8', options.speed);
|
throw is.invalidParameterError('speed', 'integer between 0 and 9', options.speed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (is.defined(options.chromaSubsampling)) {
|
if (is.defined(options.chromaSubsampling)) {
|
||||||
@@ -1031,6 +1119,7 @@ module.exports = function (Sharp) {
|
|||||||
withMetadata,
|
withMetadata,
|
||||||
toFormat,
|
toFormat,
|
||||||
jpeg,
|
jpeg,
|
||||||
|
jp2,
|
||||||
png,
|
png,
|
||||||
webp,
|
webp,
|
||||||
tiff,
|
tiff,
|
||||||
|
|||||||
10
package.json
10
package.json
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "sharp",
|
"name": "sharp",
|
||||||
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, AVIF and TIFF images",
|
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, AVIF and TIFF images",
|
||||||
"version": "0.29.0",
|
"version": "0.29.1",
|
||||||
"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": [
|
||||||
@@ -78,7 +78,8 @@
|
|||||||
"Jacob Smith <jacob@frende.me>",
|
"Jacob Smith <jacob@frende.me>",
|
||||||
"Michael Nutt <michael@nutt.im>",
|
"Michael Nutt <michael@nutt.im>",
|
||||||
"Brad Parham <baparham@gmail.com>",
|
"Brad Parham <baparham@gmail.com>",
|
||||||
"Taneli Vatanen <taneli.vatanen@gmail.com>"
|
"Taneli Vatanen <taneli.vatanen@gmail.com>",
|
||||||
|
"Joris Dugué <zaruike10@gmail.com>"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node install/can-compile && node-gyp rebuild && node install/dll-copy)",
|
"install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node install/can-compile && node-gyp rebuild && node install/dll-copy)",
|
||||||
@@ -112,6 +113,7 @@
|
|||||||
"tiff",
|
"tiff",
|
||||||
"gif",
|
"gif",
|
||||||
"svg",
|
"svg",
|
||||||
|
"jp2",
|
||||||
"dzi",
|
"dzi",
|
||||||
"image",
|
"image",
|
||||||
"resize",
|
"resize",
|
||||||
@@ -124,7 +126,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"color": "^4.0.1",
|
"color": "^4.0.1",
|
||||||
"detect-libc": "^1.0.3",
|
"detect-libc": "^1.0.3",
|
||||||
"node-addon-api": "^4.0.0",
|
"node-addon-api": "^4.1.0",
|
||||||
"prebuild-install": "^6.1.4",
|
"prebuild-install": "^6.1.4",
|
||||||
"semver": "^7.3.5",
|
"semver": "^7.3.5",
|
||||||
"simple-get": "^3.1.0",
|
"simple-get": "^3.1.0",
|
||||||
@@ -139,7 +141,7 @@
|
|||||||
"exif-reader": "^1.0.3",
|
"exif-reader": "^1.0.3",
|
||||||
"icc": "^2.0.0",
|
"icc": "^2.0.0",
|
||||||
"license-checker": "^25.0.1",
|
"license-checker": "^25.0.1",
|
||||||
"mocha": "^9.0.3",
|
"mocha": "^9.1.1",
|
||||||
"mock-fs": "^5.0.0",
|
"mock-fs": "^5.0.0",
|
||||||
"nyc": "^15.1.0",
|
"nyc": "^15.1.0",
|
||||||
"prebuild": "^10.0.1",
|
"prebuild": "^10.0.1",
|
||||||
|
|||||||
@@ -157,6 +157,10 @@ namespace sharp {
|
|||||||
bool IsGif(std::string const &str) {
|
bool IsGif(std::string const &str) {
|
||||||
return EndsWith(str, ".gif") || EndsWith(str, ".GIF");
|
return EndsWith(str, ".gif") || EndsWith(str, ".GIF");
|
||||||
}
|
}
|
||||||
|
bool IsJp2(std::string const &str) {
|
||||||
|
return EndsWith(str, ".jp2") || EndsWith(str, ".jpx") || EndsWith(str, ".j2k") || EndsWith(str, ".j2c")
|
||||||
|
|| EndsWith(str, ".JP2") || EndsWith(str, ".JPX") || EndsWith(str, ".J2K") || EndsWith(str, ".J2C");
|
||||||
|
}
|
||||||
bool IsTiff(std::string const &str) {
|
bool IsTiff(std::string const &str) {
|
||||||
return EndsWith(str, ".tif") || EndsWith(str, ".tiff") || EndsWith(str, ".TIF") || EndsWith(str, ".TIFF");
|
return EndsWith(str, ".tif") || EndsWith(str, ".tiff") || EndsWith(str, ".TIF") || EndsWith(str, ".TIFF");
|
||||||
}
|
}
|
||||||
@@ -190,6 +194,7 @@ namespace sharp {
|
|||||||
case ImageType::WEBP: id = "webp"; break;
|
case ImageType::WEBP: id = "webp"; break;
|
||||||
case ImageType::TIFF: id = "tiff"; break;
|
case ImageType::TIFF: id = "tiff"; break;
|
||||||
case ImageType::GIF: id = "gif"; break;
|
case ImageType::GIF: id = "gif"; break;
|
||||||
|
case ImageType::JP2: id = "jp2"; break;
|
||||||
case ImageType::SVG: id = "svg"; break;
|
case ImageType::SVG: id = "svg"; break;
|
||||||
case ImageType::HEIF: id = "heif"; break;
|
case ImageType::HEIF: id = "heif"; break;
|
||||||
case ImageType::PDF: id = "pdf"; break;
|
case ImageType::PDF: id = "pdf"; break;
|
||||||
@@ -226,6 +231,8 @@ namespace sharp {
|
|||||||
{ "VipsForeignLoadGifBuffer", ImageType::GIF },
|
{ "VipsForeignLoadGifBuffer", ImageType::GIF },
|
||||||
{ "VipsForeignLoadNsgifFile", ImageType::GIF },
|
{ "VipsForeignLoadNsgifFile", ImageType::GIF },
|
||||||
{ "VipsForeignLoadNsgifBuffer", ImageType::GIF },
|
{ "VipsForeignLoadNsgifBuffer", ImageType::GIF },
|
||||||
|
{ "VipsForeignLoadJp2kBuffer", ImageType::JP2 },
|
||||||
|
{ "VipsForeignLoadJp2kFile", ImageType::JP2 },
|
||||||
{ "VipsForeignLoadSvgFile", ImageType::SVG },
|
{ "VipsForeignLoadSvgFile", ImageType::SVG },
|
||||||
{ "VipsForeignLoadSvgBuffer", ImageType::SVG },
|
{ "VipsForeignLoadSvgBuffer", ImageType::SVG },
|
||||||
{ "VipsForeignLoadHeifFile", ImageType::HEIF },
|
{ "VipsForeignLoadHeifFile", ImageType::HEIF },
|
||||||
@@ -234,6 +241,8 @@ namespace sharp {
|
|||||||
{ "VipsForeignLoadPdfBuffer", ImageType::PDF },
|
{ "VipsForeignLoadPdfBuffer", ImageType::PDF },
|
||||||
{ "VipsForeignLoadMagickFile", ImageType::MAGICK },
|
{ "VipsForeignLoadMagickFile", ImageType::MAGICK },
|
||||||
{ "VipsForeignLoadMagickBuffer", ImageType::MAGICK },
|
{ "VipsForeignLoadMagickBuffer", ImageType::MAGICK },
|
||||||
|
{ "VipsForeignLoadMagick7File", ImageType::MAGICK },
|
||||||
|
{ "VipsForeignLoadMagick7Buffer", ImageType::MAGICK },
|
||||||
{ "VipsForeignLoadOpenslide", ImageType::OPENSLIDE },
|
{ "VipsForeignLoadOpenslide", ImageType::OPENSLIDE },
|
||||||
{ "VipsForeignLoadPpmFile", ImageType::PPM },
|
{ "VipsForeignLoadPpmFile", ImageType::PPM },
|
||||||
{ "VipsForeignLoadFits", ImageType::FITS },
|
{ "VipsForeignLoadFits", ImageType::FITS },
|
||||||
@@ -285,6 +294,7 @@ namespace sharp {
|
|||||||
imageType == ImageType::WEBP ||
|
imageType == ImageType::WEBP ||
|
||||||
imageType == ImageType::MAGICK ||
|
imageType == ImageType::MAGICK ||
|
||||||
imageType == ImageType::GIF ||
|
imageType == ImageType::GIF ||
|
||||||
|
imageType == ImageType::JP2 ||
|
||||||
imageType == ImageType::TIFF ||
|
imageType == ImageType::TIFF ||
|
||||||
imageType == ImageType::HEIF ||
|
imageType == ImageType::HEIF ||
|
||||||
imageType == ImageType::PDF;
|
imageType == ImageType::PDF;
|
||||||
@@ -508,6 +518,17 @@ namespace sharp {
|
|||||||
return copy;
|
return copy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Remove animation properties from image.
|
||||||
|
*/
|
||||||
|
VImage RemoveAnimationProperties(VImage image) {
|
||||||
|
VImage copy = image.copy();
|
||||||
|
copy.remove(VIPS_META_PAGE_HEIGHT);
|
||||||
|
copy.remove("delay");
|
||||||
|
copy.remove("loop");
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Does this image have a non-default density?
|
Does this image have a non-default density?
|
||||||
*/
|
*/
|
||||||
@@ -757,23 +778,27 @@ namespace sharp {
|
|||||||
/*
|
/*
|
||||||
Convert RGBA value to another colourspace
|
Convert RGBA value to another colourspace
|
||||||
*/
|
*/
|
||||||
std::vector<double> GetRgbaAsColourspace(std::vector<double> const rgba, VipsInterpretation const interpretation) {
|
std::vector<double> GetRgbaAsColourspace(std::vector<double> const rgba,
|
||||||
|
VipsInterpretation const interpretation, bool premultiply) {
|
||||||
int const bands = static_cast<int>(rgba.size());
|
int const bands = static_cast<int>(rgba.size());
|
||||||
if (bands < 3 || interpretation == VIPS_INTERPRETATION_sRGB || interpretation == VIPS_INTERPRETATION_RGB) {
|
if (bands < 3) {
|
||||||
return rgba;
|
return rgba;
|
||||||
} else {
|
|
||||||
VImage pixel = VImage::new_matrix(1, 1);
|
|
||||||
pixel.set("bands", bands);
|
|
||||||
pixel = pixel.new_from_image(rgba);
|
|
||||||
pixel = pixel.colourspace(interpretation, VImage::option()->set("source_space", VIPS_INTERPRETATION_sRGB));
|
|
||||||
return pixel(0, 0);
|
|
||||||
}
|
}
|
||||||
|
VImage pixel = VImage::new_matrix(1, 1);
|
||||||
|
pixel.set("bands", bands);
|
||||||
|
pixel = pixel
|
||||||
|
.new_from_image(rgba)
|
||||||
|
.colourspace(interpretation, VImage::option()->set("source_space", VIPS_INTERPRETATION_sRGB));
|
||||||
|
if (premultiply) {
|
||||||
|
pixel = pixel.premultiply();
|
||||||
|
}
|
||||||
|
return pixel(0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Apply the alpha channel to a given colour
|
Apply the alpha channel to a given colour
|
||||||
*/
|
*/
|
||||||
std::tuple<VImage, std::vector<double>> ApplyAlpha(VImage image, std::vector<double> colour) {
|
std::tuple<VImage, std::vector<double>> ApplyAlpha(VImage image, std::vector<double> colour, bool premultiply) {
|
||||||
// Scale up 8-bit values to match 16-bit input image
|
// Scale up 8-bit values to match 16-bit input image
|
||||||
double const multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0;
|
double const multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0;
|
||||||
// Create alphaColour colour
|
// Create alphaColour colour
|
||||||
@@ -797,7 +822,7 @@ namespace sharp {
|
|||||||
alphaColour.push_back(colour[3] * multiplier);
|
alphaColour.push_back(colour[3] * multiplier);
|
||||||
}
|
}
|
||||||
// Ensure alphaColour colour uses correct colourspace
|
// Ensure alphaColour colour uses correct colourspace
|
||||||
alphaColour = sharp::GetRgbaAsColourspace(alphaColour, image.interpretation());
|
alphaColour = sharp::GetRgbaAsColourspace(alphaColour, image.interpretation(), premultiply);
|
||||||
// Add non-transparent alpha channel, if required
|
// Add non-transparent alpha channel, if required
|
||||||
if (colour[3] < 255.0 && !HasAlpha(image)) {
|
if (colour[3] < 255.0 && !HasAlpha(image)) {
|
||||||
image = image.bandjoin(
|
image = image.bandjoin(
|
||||||
@@ -827,4 +852,5 @@ namespace sharp {
|
|||||||
}
|
}
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace sharp
|
} // namespace sharp
|
||||||
|
|||||||
12
src/common.h
12
src/common.h
@@ -116,6 +116,7 @@ namespace sharp {
|
|||||||
JPEG,
|
JPEG,
|
||||||
PNG,
|
PNG,
|
||||||
WEBP,
|
WEBP,
|
||||||
|
JP2,
|
||||||
TIFF,
|
TIFF,
|
||||||
GIF,
|
GIF,
|
||||||
SVG,
|
SVG,
|
||||||
@@ -142,6 +143,7 @@ namespace sharp {
|
|||||||
bool IsJpeg(std::string const &str);
|
bool IsJpeg(std::string const &str);
|
||||||
bool IsPng(std::string const &str);
|
bool IsPng(std::string const &str);
|
||||||
bool IsWebp(std::string const &str);
|
bool IsWebp(std::string const &str);
|
||||||
|
bool IsJp2(std::string const &str);
|
||||||
bool IsGif(std::string const &str);
|
bool IsGif(std::string const &str);
|
||||||
bool IsTiff(std::string const &str);
|
bool IsTiff(std::string const &str);
|
||||||
bool IsHeic(std::string const &str);
|
bool IsHeic(std::string const &str);
|
||||||
@@ -208,6 +210,11 @@ namespace sharp {
|
|||||||
*/
|
*/
|
||||||
VImage SetAnimationProperties(VImage image, int pageHeight, std::vector<int> delay, int loop);
|
VImage SetAnimationProperties(VImage image, int pageHeight, std::vector<int> delay, int loop);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Remove animation properties from image.
|
||||||
|
*/
|
||||||
|
VImage RemoveAnimationProperties(VImage image);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Does this image have a non-default density?
|
Does this image have a non-default density?
|
||||||
*/
|
*/
|
||||||
@@ -288,12 +295,13 @@ namespace sharp {
|
|||||||
/*
|
/*
|
||||||
Convert RGBA value to another colourspace
|
Convert RGBA value to another colourspace
|
||||||
*/
|
*/
|
||||||
std::vector<double> GetRgbaAsColourspace(std::vector<double> const rgba, VipsInterpretation const interpretation);
|
std::vector<double> GetRgbaAsColourspace(std::vector<double> const rgba,
|
||||||
|
VipsInterpretation const interpretation, bool premultiply);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Apply the alpha channel to a given colour
|
Apply the alpha channel to a given colour
|
||||||
*/
|
*/
|
||||||
std::tuple<VImage, std::vector<double>> ApplyAlpha(VImage image, std::vector<double> colour);
|
std::tuple<VImage, std::vector<double>> ApplyAlpha(VImage image, std::vector<double> colour, bool premultiply);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Removes alpha channel, if any.
|
Removes alpha channel, if any.
|
||||||
|
|||||||
@@ -182,7 +182,8 @@ namespace sharp {
|
|||||||
0.0, 0.0, 0.0, 1.0));
|
0.0, 0.0, 0.0, 1.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
VImage Modulate(VImage image, double const brightness, double const saturation, int const hue) {
|
VImage Modulate(VImage image, double const brightness, double const saturation,
|
||||||
|
int const hue, double const lightness) {
|
||||||
if (HasAlpha(image)) {
|
if (HasAlpha(image)) {
|
||||||
// Separate alpha channel
|
// Separate alpha channel
|
||||||
VImage alpha = image[image.bands() - 1];
|
VImage alpha = image[image.bands() - 1];
|
||||||
@@ -190,7 +191,7 @@ namespace sharp {
|
|||||||
.colourspace(VIPS_INTERPRETATION_LCH)
|
.colourspace(VIPS_INTERPRETATION_LCH)
|
||||||
.linear(
|
.linear(
|
||||||
{ brightness, saturation, 1},
|
{ brightness, saturation, 1},
|
||||||
{ 0.0, 0.0, static_cast<double>(hue) }
|
{ lightness, 0.0, static_cast<double>(hue) }
|
||||||
)
|
)
|
||||||
.colourspace(VIPS_INTERPRETATION_sRGB)
|
.colourspace(VIPS_INTERPRETATION_sRGB)
|
||||||
.bandjoin(alpha);
|
.bandjoin(alpha);
|
||||||
@@ -199,7 +200,7 @@ namespace sharp {
|
|||||||
.colourspace(VIPS_INTERPRETATION_LCH)
|
.colourspace(VIPS_INTERPRETATION_LCH)
|
||||||
.linear(
|
.linear(
|
||||||
{ brightness, saturation, 1 },
|
{ brightness, saturation, 1 },
|
||||||
{ 0.0, 0.0, static_cast<double>(hue) }
|
{ lightness, 0.0, static_cast<double>(hue) }
|
||||||
)
|
)
|
||||||
.colourspace(VIPS_INTERPRETATION_sRGB);
|
.colourspace(VIPS_INTERPRETATION_sRGB);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,9 +98,10 @@ namespace sharp {
|
|||||||
VImage Recomb(VImage image, std::unique_ptr<double[]> const &matrix);
|
VImage Recomb(VImage image, std::unique_ptr<double[]> const &matrix);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Modulate brightness, saturation and hue
|
* Modulate brightness, saturation, hue and lightness
|
||||||
*/
|
*/
|
||||||
VImage Modulate(VImage image, double const brightness, double const saturation, int const hue);
|
VImage Modulate(VImage image, double const brightness, double const saturation,
|
||||||
|
int const hue, double const lightness);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Ensure the image is in a given colourspace
|
* Ensure the image is in a given colourspace
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
}
|
}
|
||||||
if (baton->rotationAngle != 0.0) {
|
if (baton->rotationAngle != 0.0) {
|
||||||
std::vector<double> background;
|
std::vector<double> background;
|
||||||
std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground);
|
std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground, FALSE);
|
||||||
image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background));
|
image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -291,14 +291,15 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ensure we're using a device-independent colour space
|
// Ensure we're using a device-independent colour space
|
||||||
|
char const *processingProfile = image.interpretation() == VIPS_INTERPRETATION_RGB16 ? "p3" : "srgb";
|
||||||
if (
|
if (
|
||||||
sharp::HasProfile(image) &&
|
sharp::HasProfile(image) &&
|
||||||
image.interpretation() != VIPS_INTERPRETATION_LABS &&
|
image.interpretation() != VIPS_INTERPRETATION_LABS &&
|
||||||
image.interpretation() != VIPS_INTERPRETATION_GREY16
|
image.interpretation() != VIPS_INTERPRETATION_GREY16
|
||||||
) {
|
) {
|
||||||
// Convert to sRGB using embedded profile
|
// Convert to sRGB/P3 using embedded profile
|
||||||
try {
|
try {
|
||||||
image = image.icc_transform("srgb", VImage::option()
|
image = image.icc_transform(processingProfile, VImage::option()
|
||||||
->set("embedded", TRUE)
|
->set("embedded", TRUE)
|
||||||
->set("depth", image.interpretation() == VIPS_INTERPRETATION_RGB16 ? 16 : 8)
|
->set("depth", image.interpretation() == VIPS_INTERPRETATION_RGB16 ? 16 : 8)
|
||||||
->set("intent", VIPS_INTENT_PERCEPTUAL));
|
->set("intent", VIPS_INTENT_PERCEPTUAL));
|
||||||
@@ -306,7 +307,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
// Ignore failure of embedded profile
|
// Ignore failure of embedded profile
|
||||||
}
|
}
|
||||||
} else if (image.interpretation() == VIPS_INTERPRETATION_CMYK) {
|
} else if (image.interpretation() == VIPS_INTERPRETATION_CMYK) {
|
||||||
image = image.icc_transform("srgb", VImage::option()
|
image = image.icc_transform(processingProfile, VImage::option()
|
||||||
->set("input_profile", "cmyk")
|
->set("input_profile", "cmyk")
|
||||||
->set("intent", VIPS_INTENT_PERCEPTUAL));
|
->set("intent", VIPS_INTENT_PERCEPTUAL));
|
||||||
}
|
}
|
||||||
@@ -346,7 +347,8 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
bool const shouldSharpen = baton->sharpenSigma != 0.0;
|
bool const shouldSharpen = baton->sharpenSigma != 0.0;
|
||||||
bool const shouldApplyMedian = baton->medianSize > 0;
|
bool const shouldApplyMedian = baton->medianSize > 0;
|
||||||
bool const shouldComposite = !baton->composite.empty();
|
bool const shouldComposite = !baton->composite.empty();
|
||||||
bool const shouldModulate = baton->brightness != 1.0 || baton->saturation != 1.0 || baton->hue != 0.0;
|
bool const shouldModulate = baton->brightness != 1.0 || baton->saturation != 1.0 ||
|
||||||
|
baton->hue != 0.0 || baton->lightness != 0.0;
|
||||||
bool const shouldApplyClahe = baton->claheWidth != 0 && baton->claheHeight != 0;
|
bool const shouldApplyClahe = baton->claheWidth != 0 && baton->claheHeight != 0;
|
||||||
|
|
||||||
if (shouldComposite && !sharp::HasAlpha(image)) {
|
if (shouldComposite && !sharp::HasAlpha(image)) {
|
||||||
@@ -423,7 +425,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
if (image.width() != baton->width || image.height() != baton->height) {
|
if (image.width() != baton->width || image.height() != baton->height) {
|
||||||
if (baton->canvas == Canvas::EMBED) {
|
if (baton->canvas == Canvas::EMBED) {
|
||||||
std::vector<double> background;
|
std::vector<double> background;
|
||||||
std::tie(image, background) = sharp::ApplyAlpha(image, baton->resizeBackground);
|
std::tie(image, background) = sharp::ApplyAlpha(image, baton->resizeBackground, shouldPremultiplyAlpha);
|
||||||
|
|
||||||
// Embed
|
// Embed
|
||||||
|
|
||||||
@@ -480,7 +482,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
// Rotate post-extract non-90 angle
|
// Rotate post-extract non-90 angle
|
||||||
if (!baton->rotateBeforePreExtract && baton->rotationAngle != 0.0) {
|
if (!baton->rotateBeforePreExtract && baton->rotationAngle != 0.0) {
|
||||||
std::vector<double> background;
|
std::vector<double> background;
|
||||||
std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground);
|
std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground, shouldPremultiplyAlpha);
|
||||||
image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background));
|
image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -493,7 +495,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
// Affine transform
|
// Affine transform
|
||||||
if (baton->affineMatrix.size() > 0) {
|
if (baton->affineMatrix.size() > 0) {
|
||||||
std::vector<double> background;
|
std::vector<double> background;
|
||||||
std::tie(image, background) = sharp::ApplyAlpha(image, baton->affineBackground);
|
std::tie(image, background) = sharp::ApplyAlpha(image, baton->affineBackground, shouldPremultiplyAlpha);
|
||||||
image = image.affine(baton->affineMatrix, VImage::option()->set("background", background)
|
image = image.affine(baton->affineMatrix, VImage::option()->set("background", background)
|
||||||
->set("idx", baton->affineIdx)
|
->set("idx", baton->affineIdx)
|
||||||
->set("idy", baton->affineIdy)
|
->set("idy", baton->affineIdy)
|
||||||
@@ -505,7 +507,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
// Extend edges
|
// Extend edges
|
||||||
if (baton->extendTop > 0 || baton->extendBottom > 0 || baton->extendLeft > 0 || baton->extendRight > 0) {
|
if (baton->extendTop > 0 || baton->extendBottom > 0 || baton->extendLeft > 0 || baton->extendRight > 0) {
|
||||||
std::vector<double> background;
|
std::vector<double> background;
|
||||||
std::tie(image, background) = sharp::ApplyAlpha(image, baton->extendBackground);
|
std::tie(image, background) = sharp::ApplyAlpha(image, baton->extendBackground, shouldPremultiplyAlpha);
|
||||||
|
|
||||||
// Embed
|
// Embed
|
||||||
baton->width = image.width() + baton->extendLeft + baton->extendRight;
|
baton->width = image.width() + baton->extendLeft + baton->extendRight;
|
||||||
@@ -542,7 +544,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (shouldModulate) {
|
if (shouldModulate) {
|
||||||
image = sharp::Modulate(image, baton->brightness, baton->saturation, baton->hue);
|
image = sharp::Modulate(image, baton->brightness, baton->saturation, baton->hue, baton->lightness);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sharpen
|
// Sharpen
|
||||||
@@ -715,9 +717,10 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
// Convert colourspace, pass the current known interpretation so libvips doesn't have to guess
|
// Convert colourspace, pass the current known interpretation so libvips doesn't have to guess
|
||||||
image = image.colourspace(baton->colourspace, VImage::option()->set("source_space", image.interpretation()));
|
image = image.colourspace(baton->colourspace, VImage::option()->set("source_space", image.interpretation()));
|
||||||
// Transform colours from embedded profile to output profile
|
// Transform colours from embedded profile to output profile
|
||||||
if (baton->withMetadata && sharp::HasProfile(image)) {
|
if (baton->withMetadata && sharp::HasProfile(image) && baton->withMetadataIcc.empty()) {
|
||||||
image = image.icc_transform(vips_enum_nick(VIPS_TYPE_INTERPRETATION, baton->colourspace),
|
image = image.icc_transform("srgb", VImage::option()
|
||||||
VImage::option()->set("embedded", TRUE));
|
->set("embedded", TRUE)
|
||||||
|
->set("intent", VIPS_INTENT_PERCEPTUAL));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -726,7 +729,8 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
image = image.icc_transform(
|
image = image.icc_transform(
|
||||||
const_cast<char*>(baton->withMetadataIcc.data()),
|
const_cast<char*>(baton->withMetadataIcc.data()),
|
||||||
VImage::option()
|
VImage::option()
|
||||||
->set("input_profile", "srgb")
|
->set("input_profile", processingProfile)
|
||||||
|
->set("embedded", TRUE)
|
||||||
->set("intent", VIPS_INTENT_PERCEPTUAL));
|
->set("intent", VIPS_INTENT_PERCEPTUAL));
|
||||||
}
|
}
|
||||||
// Override EXIF Orientation tag
|
// Override EXIF Orientation tag
|
||||||
@@ -787,6 +791,22 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
} else {
|
} else {
|
||||||
baton->channels = std::min(baton->channels, 3);
|
baton->channels = std::min(baton->channels, 3);
|
||||||
}
|
}
|
||||||
|
} else if (baton->formatOut == "jp2" || (baton->formatOut == "input"
|
||||||
|
&& inputImageType == sharp::ImageType::JP2)) {
|
||||||
|
// Write JP2 to Buffer
|
||||||
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::JP2);
|
||||||
|
VipsArea *area = reinterpret_cast<VipsArea*>(image.jp2ksave_buffer(VImage::option()
|
||||||
|
->set("Q", baton->jp2Quality)
|
||||||
|
->set("lossless", baton->jp2Lossless)
|
||||||
|
->set("subsample_mode", baton->jp2ChromaSubsampling == "4:4:4"
|
||||||
|
? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
|
||||||
|
->set("tile_height", baton->jp2TileHeight)
|
||||||
|
->set("tile_width", baton->jp2TileWidth)));
|
||||||
|
baton->bufferOut = static_cast<char*>(area->data);
|
||||||
|
baton->bufferOutLength = area->length;
|
||||||
|
area->free_fn = nullptr;
|
||||||
|
vips_area_unref(area);
|
||||||
|
baton->formatOut = "jp2";
|
||||||
} else if (baton->formatOut == "png" || (baton->formatOut == "input" &&
|
} else if (baton->formatOut == "png" || (baton->formatOut == "input" &&
|
||||||
(inputImageType == sharp::ImageType::PNG || (inputImageType == sharp::ImageType::GIF && !supportsGifOutput) ||
|
(inputImageType == sharp::ImageType::PNG || (inputImageType == sharp::ImageType::GIF && !supportsGifOutput) ||
|
||||||
inputImageType == sharp::ImageType::SVG))) {
|
inputImageType == sharp::ImageType::SVG))) {
|
||||||
@@ -799,7 +819,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
->set("filter", baton->pngAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_ALL : VIPS_FOREIGN_PNG_FILTER_NONE)
|
->set("filter", baton->pngAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_ALL : VIPS_FOREIGN_PNG_FILTER_NONE)
|
||||||
->set("palette", baton->pngPalette)
|
->set("palette", baton->pngPalette)
|
||||||
->set("Q", baton->pngQuality)
|
->set("Q", baton->pngQuality)
|
||||||
->set("colours", baton->pngColours)
|
->set("bitdepth", baton->pngBitdepth)
|
||||||
->set("dither", baton->pngDither)));
|
->set("dither", baton->pngDither)));
|
||||||
baton->bufferOut = static_cast<char*>(area->data);
|
baton->bufferOut = static_cast<char*>(area->data);
|
||||||
baton->bufferOutLength = area->length;
|
baton->bufferOutLength = area->length;
|
||||||
@@ -868,6 +888,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
} else if (baton->formatOut == "heif" ||
|
} else if (baton->formatOut == "heif" ||
|
||||||
(baton->formatOut == "input" && inputImageType == sharp::ImageType::HEIF)) {
|
(baton->formatOut == "input" && inputImageType == sharp::ImageType::HEIF)) {
|
||||||
// Write HEIF to buffer
|
// Write HEIF to buffer
|
||||||
|
image = sharp::RemoveAnimationProperties(image);
|
||||||
VipsArea *area = reinterpret_cast<VipsArea*>(image.heifsave_buffer(VImage::option()
|
VipsArea *area = reinterpret_cast<VipsArea*>(image.heifsave_buffer(VImage::option()
|
||||||
->set("strip", !baton->withMetadata)
|
->set("strip", !baton->withMetadata)
|
||||||
->set("Q", baton->heifQuality)
|
->set("Q", baton->heifQuality)
|
||||||
@@ -917,13 +938,14 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
bool const isWebp = sharp::IsWebp(baton->fileOut);
|
bool const isWebp = sharp::IsWebp(baton->fileOut);
|
||||||
bool const isGif = sharp::IsGif(baton->fileOut);
|
bool const isGif = sharp::IsGif(baton->fileOut);
|
||||||
bool const isTiff = sharp::IsTiff(baton->fileOut);
|
bool const isTiff = sharp::IsTiff(baton->fileOut);
|
||||||
|
bool const isJp2 = sharp::IsJp2(baton->fileOut);
|
||||||
bool const isHeif = sharp::IsHeif(baton->fileOut);
|
bool const isHeif = sharp::IsHeif(baton->fileOut);
|
||||||
bool const isDz = sharp::IsDz(baton->fileOut);
|
bool const isDz = sharp::IsDz(baton->fileOut);
|
||||||
bool const isDzZip = sharp::IsDzZip(baton->fileOut);
|
bool const isDzZip = sharp::IsDzZip(baton->fileOut);
|
||||||
bool const isV = sharp::IsV(baton->fileOut);
|
bool const isV = sharp::IsV(baton->fileOut);
|
||||||
bool const mightMatchInput = baton->formatOut == "input";
|
bool const mightMatchInput = baton->formatOut == "input";
|
||||||
bool const willMatchInput = mightMatchInput &&
|
bool const willMatchInput = mightMatchInput &&
|
||||||
!(isJpeg || isPng || isWebp || isGif || isTiff || isHeif || isDz || isDzZip || isV);
|
!(isJpeg || isPng || isWebp || isGif || isTiff || isJp2 || isHeif || isDz || isDzZip || isV);
|
||||||
|
|
||||||
if (baton->formatOut == "jpeg" || (mightMatchInput && isJpeg) ||
|
if (baton->formatOut == "jpeg" || (mightMatchInput && isJpeg) ||
|
||||||
(willMatchInput && inputImageType == sharp::ImageType::JPEG)) {
|
(willMatchInput && inputImageType == sharp::ImageType::JPEG)) {
|
||||||
@@ -943,6 +965,18 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
->set("optimize_coding", baton->jpegOptimiseCoding));
|
->set("optimize_coding", baton->jpegOptimiseCoding));
|
||||||
baton->formatOut = "jpeg";
|
baton->formatOut = "jpeg";
|
||||||
baton->channels = std::min(baton->channels, 3);
|
baton->channels = std::min(baton->channels, 3);
|
||||||
|
} else if (baton->formatOut == "jp2" || (mightMatchInput && isJp2) ||
|
||||||
|
(willMatchInput && (inputImageType == sharp::ImageType::JP2))) {
|
||||||
|
// Write JP2 to file
|
||||||
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::JP2);
|
||||||
|
image.jp2ksave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
||||||
|
->set("Q", baton->jp2Quality)
|
||||||
|
->set("lossless", baton->jp2Lossless)
|
||||||
|
->set("subsample_mode", baton->jp2ChromaSubsampling == "4:4:4"
|
||||||
|
? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
|
||||||
|
->set("tile_height", baton->jp2TileHeight)
|
||||||
|
->set("tile_width", baton->jp2TileWidth));
|
||||||
|
baton->formatOut = "jp2";
|
||||||
} else if (baton->formatOut == "png" || (mightMatchInput && isPng) || (willMatchInput &&
|
} else if (baton->formatOut == "png" || (mightMatchInput && isPng) || (willMatchInput &&
|
||||||
(inputImageType == sharp::ImageType::PNG || (inputImageType == sharp::ImageType::GIF && !supportsGifOutput) ||
|
(inputImageType == sharp::ImageType::PNG || (inputImageType == sharp::ImageType::GIF && !supportsGifOutput) ||
|
||||||
inputImageType == sharp::ImageType::SVG))) {
|
inputImageType == sharp::ImageType::SVG))) {
|
||||||
@@ -955,7 +989,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
->set("filter", baton->pngAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_ALL : VIPS_FOREIGN_PNG_FILTER_NONE)
|
->set("filter", baton->pngAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_ALL : VIPS_FOREIGN_PNG_FILTER_NONE)
|
||||||
->set("palette", baton->pngPalette)
|
->set("palette", baton->pngPalette)
|
||||||
->set("Q", baton->pngQuality)
|
->set("Q", baton->pngQuality)
|
||||||
->set("colours", baton->pngColours)
|
->set("bitdepth", baton->pngBitdepth)
|
||||||
->set("dither", baton->pngDither));
|
->set("dither", baton->pngDither));
|
||||||
baton->formatOut = "png";
|
baton->formatOut = "png";
|
||||||
} else if (baton->formatOut == "webp" || (mightMatchInput && isWebp) ||
|
} else if (baton->formatOut == "webp" || (mightMatchInput && isWebp) ||
|
||||||
@@ -1008,6 +1042,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|||||||
} else if (baton->formatOut == "heif" || (mightMatchInput && isHeif) ||
|
} else if (baton->formatOut == "heif" || (mightMatchInput && isHeif) ||
|
||||||
(willMatchInput && inputImageType == sharp::ImageType::HEIF)) {
|
(willMatchInput && inputImageType == sharp::ImageType::HEIF)) {
|
||||||
// Write HEIF to file
|
// Write HEIF to file
|
||||||
|
image = sharp::RemoveAnimationProperties(image);
|
||||||
image.heifsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
image.heifsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
||||||
->set("strip", !baton->withMetadata)
|
->set("strip", !baton->withMetadata)
|
||||||
->set("Q", baton->heifQuality)
|
->set("Q", baton->heifQuality)
|
||||||
@@ -1328,6 +1363,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|||||||
baton->brightness = sharp::AttrAsDouble(options, "brightness");
|
baton->brightness = sharp::AttrAsDouble(options, "brightness");
|
||||||
baton->saturation = sharp::AttrAsDouble(options, "saturation");
|
baton->saturation = sharp::AttrAsDouble(options, "saturation");
|
||||||
baton->hue = sharp::AttrAsInt32(options, "hue");
|
baton->hue = sharp::AttrAsInt32(options, "hue");
|
||||||
|
baton->lightness = sharp::AttrAsDouble(options, "lightness");
|
||||||
baton->medianSize = sharp::AttrAsUint32(options, "medianSize");
|
baton->medianSize = sharp::AttrAsUint32(options, "medianSize");
|
||||||
baton->sharpenSigma = sharp::AttrAsDouble(options, "sharpenSigma");
|
baton->sharpenSigma = sharp::AttrAsDouble(options, "sharpenSigma");
|
||||||
baton->sharpenFlat = sharp::AttrAsDouble(options, "sharpenFlat");
|
baton->sharpenFlat = sharp::AttrAsDouble(options, "sharpenFlat");
|
||||||
@@ -1429,8 +1465,13 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|||||||
baton->pngAdaptiveFiltering = sharp::AttrAsBool(options, "pngAdaptiveFiltering");
|
baton->pngAdaptiveFiltering = sharp::AttrAsBool(options, "pngAdaptiveFiltering");
|
||||||
baton->pngPalette = sharp::AttrAsBool(options, "pngPalette");
|
baton->pngPalette = sharp::AttrAsBool(options, "pngPalette");
|
||||||
baton->pngQuality = sharp::AttrAsUint32(options, "pngQuality");
|
baton->pngQuality = sharp::AttrAsUint32(options, "pngQuality");
|
||||||
baton->pngColours = sharp::AttrAsUint32(options, "pngColours");
|
baton->pngBitdepth = sharp::AttrAsUint32(options, "pngBitdepth");
|
||||||
baton->pngDither = sharp::AttrAsDouble(options, "pngDither");
|
baton->pngDither = sharp::AttrAsDouble(options, "pngDither");
|
||||||
|
baton->jp2Quality = sharp::AttrAsUint32(options, "jp2Quality");
|
||||||
|
baton->jp2Lossless = sharp::AttrAsBool(options, "jp2Lossless");
|
||||||
|
baton->jp2TileHeight = sharp::AttrAsUint32(options, "jp2TileHeight");
|
||||||
|
baton->jp2TileWidth = sharp::AttrAsUint32(options, "jp2TileWidth");
|
||||||
|
baton->jp2ChromaSubsampling = sharp::AttrAsStr(options, "jp2ChromaSubsampling");
|
||||||
baton->webpQuality = sharp::AttrAsUint32(options, "webpQuality");
|
baton->webpQuality = sharp::AttrAsUint32(options, "webpQuality");
|
||||||
baton->webpAlphaQuality = sharp::AttrAsUint32(options, "webpAlphaQuality");
|
baton->webpAlphaQuality = sharp::AttrAsUint32(options, "webpAlphaQuality");
|
||||||
baton->webpLossless = sharp::AttrAsBool(options, "webpLossless");
|
baton->webpLossless = sharp::AttrAsBool(options, "webpLossless");
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ struct PipelineBaton {
|
|||||||
double brightness;
|
double brightness;
|
||||||
double saturation;
|
double saturation;
|
||||||
int hue;
|
int hue;
|
||||||
|
double lightness;
|
||||||
int medianSize;
|
int medianSize;
|
||||||
double sharpenSigma;
|
double sharpenSigma;
|
||||||
double sharpenFlat;
|
double sharpenFlat;
|
||||||
@@ -146,8 +147,13 @@ struct PipelineBaton {
|
|||||||
bool pngAdaptiveFiltering;
|
bool pngAdaptiveFiltering;
|
||||||
bool pngPalette;
|
bool pngPalette;
|
||||||
int pngQuality;
|
int pngQuality;
|
||||||
int pngColours;
|
int pngBitdepth;
|
||||||
double pngDither;
|
double pngDither;
|
||||||
|
int jp2Quality;
|
||||||
|
bool jp2Lossless;
|
||||||
|
int jp2TileHeight;
|
||||||
|
int jp2TileWidth;
|
||||||
|
std::string jp2ChromaSubsampling;
|
||||||
int webpQuality;
|
int webpQuality;
|
||||||
int webpAlphaQuality;
|
int webpAlphaQuality;
|
||||||
bool webpNearLossless;
|
bool webpNearLossless;
|
||||||
@@ -227,6 +233,7 @@ struct PipelineBaton {
|
|||||||
brightness(1.0),
|
brightness(1.0),
|
||||||
saturation(1.0),
|
saturation(1.0),
|
||||||
hue(0),
|
hue(0),
|
||||||
|
lightness(0),
|
||||||
medianSize(0),
|
medianSize(0),
|
||||||
sharpenSigma(0.0),
|
sharpenSigma(0.0),
|
||||||
sharpenFlat(1.0),
|
sharpenFlat(1.0),
|
||||||
@@ -276,8 +283,13 @@ struct PipelineBaton {
|
|||||||
pngAdaptiveFiltering(false),
|
pngAdaptiveFiltering(false),
|
||||||
pngPalette(false),
|
pngPalette(false),
|
||||||
pngQuality(100),
|
pngQuality(100),
|
||||||
pngColours(256),
|
pngBitdepth(8),
|
||||||
pngDither(1.0),
|
pngDither(1.0),
|
||||||
|
jp2Quality(80),
|
||||||
|
jp2Lossless(false),
|
||||||
|
jp2TileHeight(512),
|
||||||
|
jp2TileWidth(512),
|
||||||
|
jp2ChromaSubsampling("4:4:4"),
|
||||||
webpQuality(80),
|
webpQuality(80),
|
||||||
webpAlphaQuality(100),
|
webpAlphaQuality(100),
|
||||||
webpNearLossless(false),
|
webpNearLossless(false),
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ Napi::Value format(const Napi::CallbackInfo& info) {
|
|||||||
Napi::Object format = Napi::Object::New(env);
|
Napi::Object format = Napi::Object::New(env);
|
||||||
for (std::string const f : {
|
for (std::string const f : {
|
||||||
"jpeg", "png", "webp", "tiff", "magick", "openslide", "dz",
|
"jpeg", "png", "webp", "tiff", "magick", "openslide", "dz",
|
||||||
"ppm", "fits", "gif", "svg", "heif", "pdf", "vips"
|
"ppm", "fits", "gif", "svg", "heif", "pdf", "vips", "jp2k"
|
||||||
}) {
|
}) {
|
||||||
// Input
|
// Input
|
||||||
Napi::Boolean hasInputFile =
|
Napi::Boolean hasInputFile =
|
||||||
|
|||||||
3
test/fixtures/index.js
vendored
3
test/fixtures/index.js
vendored
@@ -92,6 +92,7 @@ module.exports = {
|
|||||||
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
|
inputPngImageInAlpha: getPath('image-in-alpha.png'), // https://github.com/lovell/sharp/issues/1597
|
||||||
inputPngSolidAlpha: getPath('with-alpha.png'), // https://github.com/lovell/sharp/issues/1599
|
inputPngSolidAlpha: getPath('with-alpha.png'), // https://github.com/lovell/sharp/issues/1599
|
||||||
|
inputPngP3: getPath('p3.png'), // https://github.com/lovell/sharp/issues/2862
|
||||||
|
|
||||||
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
|
||||||
@@ -104,6 +105,8 @@ module.exports = {
|
|||||||
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
|
inputTifftagPhotoshop: getPath('tifftag-photoshop.tiff'), // https://github.com/lovell/sharp/issues/1600
|
||||||
|
|
||||||
|
inputJp2: getPath('relax.jp2'), // https://www.fnordware.com/j2k/relax.jp2
|
||||||
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/p3.png
vendored
Normal file
BIN
test/fixtures/p3.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 610 B |
BIN
test/fixtures/relax.jp2
vendored
Normal file
BIN
test/fixtures/relax.jp2
vendored
Normal file
Binary file not shown.
@@ -3,7 +3,7 @@
|
|||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
|
|
||||||
const sharp = require('../../');
|
const sharp = require('../../');
|
||||||
const { inputAvif, inputJpg } = require('../fixtures');
|
const { inputAvif, inputJpg, inputGifAnimated } = require('../fixtures');
|
||||||
|
|
||||||
describe('AVIF', () => {
|
describe('AVIF', () => {
|
||||||
it('called without options does not throw an error', () => {
|
it('called without options does not throw an error', () => {
|
||||||
@@ -81,4 +81,29 @@ describe('AVIF', () => {
|
|||||||
width: 32
|
width: 32
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('can convert animated GIF to non-animated AVIF', async () => {
|
||||||
|
const data = await sharp(inputGifAnimated, { animated: true })
|
||||||
|
.resize(10)
|
||||||
|
.avif({ speed: 8 })
|
||||||
|
.toBuffer();
|
||||||
|
const metadata = await sharp(data)
|
||||||
|
.metadata();
|
||||||
|
const { size, ...metadataWithoutSize } = metadata;
|
||||||
|
assert.deepStrictEqual(metadataWithoutSize, {
|
||||||
|
channels: 4,
|
||||||
|
compression: 'av1',
|
||||||
|
depth: 'uchar',
|
||||||
|
format: 'heif',
|
||||||
|
hasAlpha: true,
|
||||||
|
hasProfile: false,
|
||||||
|
height: 300,
|
||||||
|
isProgressive: false,
|
||||||
|
pageHeight: 300,
|
||||||
|
pagePrimary: 0,
|
||||||
|
pages: 1,
|
||||||
|
space: 'srgb',
|
||||||
|
width: 10
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -105,6 +105,25 @@ describe('Colour space conversion', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Convert P3 to sRGB', async () => {
|
||||||
|
const [r, g, b] = await sharp(fixtures.inputPngP3)
|
||||||
|
.raw()
|
||||||
|
.toBuffer();
|
||||||
|
assert.strictEqual(r, 255);
|
||||||
|
assert.strictEqual(g, 0);
|
||||||
|
assert.strictEqual(b, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Passthrough P3', async () => {
|
||||||
|
const [r, g, b] = await sharp(fixtures.inputPngP3)
|
||||||
|
.withMetadata({ icc: 'p3' })
|
||||||
|
.raw()
|
||||||
|
.toBuffer();
|
||||||
|
assert.strictEqual(r, 234);
|
||||||
|
assert.strictEqual(g, 51);
|
||||||
|
assert.strictEqual(b, 34);
|
||||||
|
});
|
||||||
|
|
||||||
it('Invalid pipelineColourspace input', function () {
|
it('Invalid pipelineColourspace input', function () {
|
||||||
assert.throws(function () {
|
assert.throws(function () {
|
||||||
sharp(fixtures.inputJpg)
|
sharp(fixtures.inputJpg)
|
||||||
|
|||||||
@@ -124,4 +124,30 @@ describe('Extend', function () {
|
|||||||
fixtures.assertSimilar(fixtures.expected('extend-2channel.png'), data, done);
|
fixtures.assertSimilar(fixtures.expected('extend-2channel.png'), data, done);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Premultiply background when compositing', async () => {
|
||||||
|
const background = '#bf1942cc';
|
||||||
|
const data = await sharp({
|
||||||
|
create: {
|
||||||
|
width: 1, height: 1, channels: 4, background: '#fff0'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.composite([{
|
||||||
|
input: {
|
||||||
|
create: {
|
||||||
|
width: 1, height: 1, channels: 4, background
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}])
|
||||||
|
.extend({
|
||||||
|
left: 1, background
|
||||||
|
})
|
||||||
|
.raw()
|
||||||
|
.toBuffer();
|
||||||
|
const [r1, g1, b1, a1, r2, g2, b2, a2] = data;
|
||||||
|
assert.strictEqual(true, Math.abs(r2 - r1) < 2);
|
||||||
|
assert.strictEqual(true, Math.abs(g2 - g1) < 2);
|
||||||
|
assert.strictEqual(true, Math.abs(b2 - b1) < 2);
|
||||||
|
assert.strictEqual(true, Math.abs(a2 - a1) < 2);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ describe('HEIF', () => {
|
|||||||
});
|
});
|
||||||
it('out of range speed should throw an error', () => {
|
it('out of range speed should throw an error', () => {
|
||||||
assert.throws(() => {
|
assert.throws(() => {
|
||||||
sharp().heif({ speed: 9 });
|
sharp().heif({ speed: 10 });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('invalid speed should throw an error', () => {
|
it('invalid speed should throw an error', () => {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
const rimraf = require('rimraf');
|
const rimraf = require('rimraf');
|
||||||
|
|
||||||
@@ -316,6 +317,48 @@ describe('Input/output', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Fail when output File is input File (relative output, absolute input)', function (done) {
|
||||||
|
const relativePath = path.relative(process.cwd(), fixtures.inputJpg);
|
||||||
|
sharp(fixtures.inputJpg).toFile(relativePath, function (err) {
|
||||||
|
assert(err instanceof Error);
|
||||||
|
assert.strictEqual('Cannot use same file for input and output', err.message);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Fail when output File is input File via Promise (relative output, absolute input)', function (done) {
|
||||||
|
const relativePath = path.relative(process.cwd(), fixtures.inputJpg);
|
||||||
|
sharp(fixtures.inputJpg).toFile(relativePath).then(function (data) {
|
||||||
|
assert(false);
|
||||||
|
done();
|
||||||
|
}).catch(function (err) {
|
||||||
|
assert(err instanceof Error);
|
||||||
|
assert.strictEqual('Cannot use same file for input and output', err.message);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Fail when output File is input File (relative input, absolute output)', function (done) {
|
||||||
|
const relativePath = path.relative(process.cwd(), fixtures.inputJpg);
|
||||||
|
sharp(relativePath).toFile(fixtures.inputJpg, function (err) {
|
||||||
|
assert(err instanceof Error);
|
||||||
|
assert.strictEqual('Cannot use same file for input and output', err.message);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Fail when output File is input File via Promise (relative input, absolute output)', function (done) {
|
||||||
|
const relativePath = path.relative(process.cwd(), fixtures.inputJpg);
|
||||||
|
sharp(relativePath).toFile(fixtures.inputJpg).then(function (data) {
|
||||||
|
assert(false);
|
||||||
|
done();
|
||||||
|
}).catch(function (err) {
|
||||||
|
assert(err instanceof Error);
|
||||||
|
assert.strictEqual('Cannot use same file for input and output', err.message);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('Fail when output File is empty', function (done) {
|
it('Fail when output File is empty', function (done) {
|
||||||
sharp(fixtures.inputJpg).toFile('', function (err) {
|
sharp(fixtures.inputJpg).toFile('', function (err) {
|
||||||
assert(err instanceof Error);
|
assert(err instanceof Error);
|
||||||
|
|||||||
99
test/unit/jp2.js
Normal file
99
test/unit/jp2.js
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const assert = require('assert');
|
||||||
|
|
||||||
|
const sharp = require('../../');
|
||||||
|
const fixtures = require('../fixtures');
|
||||||
|
|
||||||
|
describe('JP2 output', () => {
|
||||||
|
if (!sharp.format.jp2k.input.buffer) {
|
||||||
|
it('JP2 output should fail due to missing OpenJPEG', () => {
|
||||||
|
assert.rejects(() =>
|
||||||
|
sharp(fixtures.inputJpg)
|
||||||
|
.jp2()
|
||||||
|
.toBuffer(),
|
||||||
|
/JP2 output requires libvips with support for OpenJPEG/
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('JP2 file output should fail due to missing OpenJPEG', () => {
|
||||||
|
assert.rejects(async () => await sharp().toFile('test.jp2'),
|
||||||
|
/JP2 output requires libvips with support for OpenJPEG/
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
it('JP2 Buffer to PNG Buffer', () => {
|
||||||
|
sharp(fs.readFileSync(fixtures.inputJp2))
|
||||||
|
.resize(8, 15)
|
||||||
|
.png()
|
||||||
|
.toBuffer({ resolveWithObject: true })
|
||||||
|
.then(({ data, info }) => {
|
||||||
|
assert.strictEqual(true, data.length > 0);
|
||||||
|
assert.strictEqual(data.length, info.size);
|
||||||
|
assert.strictEqual('png', info.format);
|
||||||
|
assert.strictEqual(8, info.width);
|
||||||
|
assert.strictEqual(15, info.height);
|
||||||
|
assert.strictEqual(4, info.channels);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('JP2 quality', function (done) {
|
||||||
|
sharp(fixtures.inputJp2)
|
||||||
|
.resize(320, 240)
|
||||||
|
.jp2({ quality: 70 })
|
||||||
|
.toBuffer(function (err, buffer70) {
|
||||||
|
if (err) throw err;
|
||||||
|
sharp(fixtures.inputJp2)
|
||||||
|
.resize(320, 240)
|
||||||
|
.toBuffer(function (err, buffer80) {
|
||||||
|
if (err) throw err;
|
||||||
|
sharp(fixtures.inputJp2)
|
||||||
|
.resize(320, 240)
|
||||||
|
.jp2({ quality: 90 })
|
||||||
|
.toBuffer(function (err, buffer90) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert(buffer70.length < buffer80.length);
|
||||||
|
assert(buffer80.length < buffer90.length);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Without chroma subsampling generates larger file', function (done) {
|
||||||
|
// First generate with chroma subsampling (default)
|
||||||
|
sharp(fixtures.inputJp2)
|
||||||
|
.resize(320, 240)
|
||||||
|
.jp2({ chromaSubsampling: '4:2:0' })
|
||||||
|
.toBuffer(function (err, withChromaSubsamplingData, withChromaSubsamplingInfo) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, withChromaSubsamplingData.length > 0);
|
||||||
|
assert.strictEqual(withChromaSubsamplingData.length, withChromaSubsamplingInfo.size);
|
||||||
|
assert.strictEqual('jp2', withChromaSubsamplingInfo.format);
|
||||||
|
assert.strictEqual(320, withChromaSubsamplingInfo.width);
|
||||||
|
assert.strictEqual(240, withChromaSubsamplingInfo.height);
|
||||||
|
// Then generate without
|
||||||
|
sharp(fixtures.inputJp2)
|
||||||
|
.resize(320, 240)
|
||||||
|
.jp2({ chromaSubsampling: '4:4:4' })
|
||||||
|
.toBuffer(function (err, withoutChromaSubsamplingData, withoutChromaSubsamplingInfo) {
|
||||||
|
if (err) throw err;
|
||||||
|
assert.strictEqual(true, withoutChromaSubsamplingData.length > 0);
|
||||||
|
assert.strictEqual(withoutChromaSubsamplingData.length, withoutChromaSubsamplingInfo.size);
|
||||||
|
assert.strictEqual('jp2', withoutChromaSubsamplingInfo.format);
|
||||||
|
assert.strictEqual(320, withoutChromaSubsamplingInfo.width);
|
||||||
|
assert.strictEqual(240, withoutChromaSubsamplingInfo.height);
|
||||||
|
assert.strictEqual(true, withChromaSubsamplingData.length <= withoutChromaSubsamplingData.length);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Invalid JP2 chromaSubsampling value throws error', function () {
|
||||||
|
assert.throws(function () {
|
||||||
|
sharp().jpeg({ chromaSubsampling: '4:2:2' });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -18,7 +18,9 @@ describe('Modulate', function () {
|
|||||||
{ saturation: null },
|
{ saturation: null },
|
||||||
{ hue: '50deg' },
|
{ hue: '50deg' },
|
||||||
{ hue: 1.5 },
|
{ hue: 1.5 },
|
||||||
{ hue: null }
|
{ hue: null },
|
||||||
|
{ lightness: '+50' },
|
||||||
|
{ lightness: null }
|
||||||
].forEach(function (options) {
|
].forEach(function (options) {
|
||||||
it('should throw', function () {
|
it('should throw', function () {
|
||||||
assert.throws(function () {
|
assert.throws(function () {
|
||||||
@@ -108,6 +110,22 @@ describe('Modulate', function () {
|
|||||||
assert.deepStrictEqual({ r: 127, g: 83, b: 81 }, { r, g, b });
|
assert.deepStrictEqual({ r: 127, g: 83, b: 81 }, { r, g, b });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should be able to lighten', async () => {
|
||||||
|
const [r, g, b] = await sharp({
|
||||||
|
create: {
|
||||||
|
width: 1,
|
||||||
|
height: 1,
|
||||||
|
channels: 3,
|
||||||
|
background: { r: 153, g: 68, b: 68 }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.modulate({ lightness: 10 })
|
||||||
|
.raw()
|
||||||
|
.toBuffer();
|
||||||
|
|
||||||
|
assert.deepStrictEqual({ r: 182, g: 93, b: 92 }, { r, g, b });
|
||||||
|
});
|
||||||
|
|
||||||
it('should be able to modulate all channels', async () => {
|
it('should be able to modulate all channels', async () => {
|
||||||
const [r, g, b] = await sharp({
|
const [r, g, b] = await sharp({
|
||||||
create: {
|
create: {
|
||||||
|
|||||||
Reference in New Issue
Block a user