sharp 0.26.0 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/binding.gyp +1 -1
- package/install/libvips.js +12 -2
- package/lib/channel.js +1 -1
- package/lib/composite.js +10 -7
- package/lib/constructor.js +18 -9
- package/lib/input.js +5 -5
- package/lib/operation.js +99 -0
- package/lib/output.js +43 -14
- package/lib/resize.js +2 -1
- package/lib/utility.js +21 -0
- package/package.json +20 -16
- package/src/common.cc +13 -19
- package/src/common.h +6 -3
- package/src/pipeline.cc +37 -11
- package/src/pipeline.h +23 -4
- package/src/sharp.cc +2 -0
- package/src/stats.cc +8 -6
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
The typical use case for this high speed Node.js module
|
|
6
6
|
is to convert large images in common formats to
|
|
7
|
-
smaller, web-friendly JPEG, PNG and
|
|
7
|
+
smaller, web-friendly JPEG, PNG, WebP and AVIF images of varying dimensions.
|
|
8
8
|
|
|
9
9
|
Resizing an image is typically 4x-5x faster than using the
|
|
10
10
|
quickest ImageMagick and GraphicsMagick settings
|
|
@@ -16,7 +16,7 @@ Lanczos resampling ensures quality is not sacrificed for speed.
|
|
|
16
16
|
As well as image resizing, operations such as
|
|
17
17
|
rotation, extraction, compositing and gamma correction are available.
|
|
18
18
|
|
|
19
|
-
Most modern macOS, Windows and Linux systems running Node.js v10
|
|
19
|
+
Most modern macOS, Windows and Linux systems running Node.js v10+
|
|
20
20
|
do not require any additional install or runtime dependencies.
|
|
21
21
|
|
|
22
22
|
## Examples
|
package/binding.gyp
CHANGED
package/install/libvips.js
CHANGED
|
@@ -25,6 +25,7 @@ const minimumGlibcVersionByArch = {
|
|
|
25
25
|
const { minimumLibvipsVersion, minimumLibvipsVersionLabelled } = libvips;
|
|
26
26
|
const distHost = process.env.npm_config_sharp_libvips_binary_host || 'https://github.com/lovell/sharp-libvips/releases/download';
|
|
27
27
|
const distBaseUrl = process.env.npm_config_sharp_dist_base_url || process.env.SHARP_DIST_BASE_URL || `${distHost}/v${minimumLibvipsVersionLabelled}/`;
|
|
28
|
+
const supportsBrotli = ('BrotliDecompress' in zlib);
|
|
28
29
|
|
|
29
30
|
const fail = function (err) {
|
|
30
31
|
npmLog.error('sharp', err.message);
|
|
@@ -43,7 +44,7 @@ const extractTarball = function (tarPath) {
|
|
|
43
44
|
libvips.mkdirSync(versionedVendorPath);
|
|
44
45
|
stream.pipeline(
|
|
45
46
|
fs.createReadStream(tarPath),
|
|
46
|
-
new zlib.BrotliDecompress(),
|
|
47
|
+
supportsBrotli ? new zlib.BrotliDecompress() : new zlib.Gunzip(),
|
|
47
48
|
tarFs.extract(versionedVendorPath),
|
|
48
49
|
function (err) {
|
|
49
50
|
if (err) {
|
|
@@ -58,6 +59,7 @@ const extractTarball = function (tarPath) {
|
|
|
58
59
|
|
|
59
60
|
try {
|
|
60
61
|
const useGlobalLibvips = libvips.useGlobalLibvips();
|
|
62
|
+
|
|
61
63
|
if (useGlobalLibvips) {
|
|
62
64
|
const globalLibvipsVersion = libvips.globalLibvipsVersion();
|
|
63
65
|
npmLog.info('sharp', `Detected globally-installed libvips v${globalLibvipsVersion}`);
|
|
@@ -80,8 +82,16 @@ try {
|
|
|
80
82
|
throw new Error(`Use with glibc ${detectLibc.version} requires manual installation of libvips >= ${minimumLibvipsVersion}`);
|
|
81
83
|
}
|
|
82
84
|
}
|
|
85
|
+
|
|
86
|
+
const supportedNodeVersion = process.env.npm_package_engines_node || require('../package.json').engines.node;
|
|
87
|
+
if (!semver.satisfies(process.versions.node, supportedNodeVersion)) {
|
|
88
|
+
throw new Error(`Expected Node.js version ${supportedNodeVersion} but found ${process.versions.node}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const extension = supportsBrotli ? 'br' : 'gz';
|
|
92
|
+
|
|
83
93
|
// Download to per-process temporary file
|
|
84
|
-
const tarFilename = ['libvips', minimumLibvipsVersion, platformAndArch].join('-') + '.tar.
|
|
94
|
+
const tarFilename = ['libvips', minimumLibvipsVersion, platformAndArch].join('-') + '.tar.' + extension;
|
|
85
95
|
const tarPathCache = path.join(libvips.cachePath(), tarFilename);
|
|
86
96
|
if (fs.existsSync(tarPathCache)) {
|
|
87
97
|
npmLog.info('sharp', `Using cached ${tarPathCache}`);
|
package/lib/channel.js
CHANGED
|
@@ -85,7 +85,7 @@ function extractChannel (channel) {
|
|
|
85
85
|
* - sRGB: 0: Red, 1: Green, 2: Blue, 3: Alpha.
|
|
86
86
|
* - CMYK: 0: Magenta, 1: Cyan, 2: Yellow, 3: Black, 4: Alpha.
|
|
87
87
|
*
|
|
88
|
-
* Buffers may be any of the image formats supported by sharp
|
|
88
|
+
* Buffers may be any of the image formats supported by sharp.
|
|
89
89
|
* For raw pixel input, the `options` object should contain a `raw` attribute, which follows the format of the attribute of the same name in the `sharp()` constructor.
|
|
90
90
|
*
|
|
91
91
|
* @param {Array<string|Buffer>|string|Buffer} images - one or more images (file paths, Buffers).
|
package/lib/composite.js
CHANGED
|
@@ -105,8 +105,9 @@ function composite (images) {
|
|
|
105
105
|
input: this._createInputDescriptor(image.input, inputOptions, { allowStream: false }),
|
|
106
106
|
blend: 'over',
|
|
107
107
|
tile: false,
|
|
108
|
-
left:
|
|
109
|
-
top:
|
|
108
|
+
left: 0,
|
|
109
|
+
top: 0,
|
|
110
|
+
hasOffset: false,
|
|
110
111
|
gravity: 0,
|
|
111
112
|
premultiplied: false
|
|
112
113
|
};
|
|
@@ -125,21 +126,23 @@ function composite (images) {
|
|
|
125
126
|
}
|
|
126
127
|
}
|
|
127
128
|
if (is.defined(image.left)) {
|
|
128
|
-
if (is.integer(image.left)
|
|
129
|
+
if (is.integer(image.left)) {
|
|
129
130
|
composite.left = image.left;
|
|
130
131
|
} else {
|
|
131
|
-
throw is.invalidParameterError('left', '
|
|
132
|
+
throw is.invalidParameterError('left', 'integer', image.left);
|
|
132
133
|
}
|
|
133
134
|
}
|
|
134
135
|
if (is.defined(image.top)) {
|
|
135
|
-
if (is.integer(image.top)
|
|
136
|
+
if (is.integer(image.top)) {
|
|
136
137
|
composite.top = image.top;
|
|
137
138
|
} else {
|
|
138
|
-
throw is.invalidParameterError('top', '
|
|
139
|
+
throw is.invalidParameterError('top', 'integer', image.top);
|
|
139
140
|
}
|
|
140
141
|
}
|
|
141
|
-
if (
|
|
142
|
+
if (is.defined(image.top) !== is.defined(image.left)) {
|
|
142
143
|
throw new Error('Expected both left and top to be set');
|
|
144
|
+
} else {
|
|
145
|
+
composite.hasOffset = is.integer(image.top) && is.integer(image.left);
|
|
143
146
|
}
|
|
144
147
|
if (is.defined(image.gravity)) {
|
|
145
148
|
if (is.integer(image.gravity) && is.inRange(image.gravity, 0, 8)) {
|
package/lib/constructor.js
CHANGED
|
@@ -40,7 +40,7 @@ const debuglog = util.debuglog('sharp');
|
|
|
40
40
|
/**
|
|
41
41
|
* Constructor factory to create an instance of `sharp`, to which further methods are chained.
|
|
42
42
|
*
|
|
43
|
-
* JPEG, PNG, WebP or TIFF format image data can be streamed out from this object.
|
|
43
|
+
* JPEG, PNG, WebP, AVIF or TIFF format image data can be streamed out from this object.
|
|
44
44
|
* When using Stream based output, derived attributes are available from the `info` event.
|
|
45
45
|
*
|
|
46
46
|
* Non-critical problems encountered during processing are emitted as `warning` events.
|
|
@@ -91,9 +91,9 @@ const debuglog = util.debuglog('sharp');
|
|
|
91
91
|
* await sharp('in.gif', { animated: true }).toFile('out.webp');
|
|
92
92
|
*
|
|
93
93
|
* @param {(Buffer|string)} [input] - if present, can be
|
|
94
|
-
* a Buffer containing JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data, or
|
|
95
|
-
* a String containing the filesystem path to an JPEG, PNG, WebP, GIF, SVG or TIFF image file.
|
|
96
|
-
* JPEG, PNG, WebP, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present.
|
|
94
|
+
* a Buffer containing JPEG, PNG, WebP, AVIF, GIF, SVG, TIFF or raw pixel image data, or
|
|
95
|
+
* a String containing the filesystem path to an JPEG, PNG, WebP, AVIF, GIF, SVG or TIFF image file.
|
|
96
|
+
* JPEG, PNG, WebP, AVIF, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present.
|
|
97
97
|
* @param {Object} [options] - if present, is an Object with optional attributes.
|
|
98
98
|
* @param {boolean} [options.failOnError=true] - by default halt processing and raise an error when loading invalid images.
|
|
99
99
|
* Set this flag to `false` if you'd rather apply a "best effort" to decode images, even if the data is corrupt or invalid.
|
|
@@ -102,9 +102,9 @@ const debuglog = util.debuglog('sharp');
|
|
|
102
102
|
* An integral Number of pixels, zero or false to remove limit, true to use default limit of 268402689 (0x3FFF x 0x3FFF).
|
|
103
103
|
* @param {boolean} [options.sequentialRead=false] - Set this to `true` to use sequential rather than random access where possible.
|
|
104
104
|
* This can reduce memory usage and might improve performance on some systems.
|
|
105
|
-
* @param {number} [options.density=72] - number representing the DPI for vector images.
|
|
106
|
-
* @param {number} [options.pages=1] - number of pages to extract for multi-page input (GIF, TIFF, PDF), use -1 for all pages.
|
|
107
|
-
* @param {number} [options.page=0] - page number to start extracting from for multi-page input (GIF, TIFF, PDF), zero based.
|
|
105
|
+
* @param {number} [options.density=72] - number representing the DPI for vector images in the range 1 to 100000.
|
|
106
|
+
* @param {number} [options.pages=1] - number of pages to extract for multi-page input (GIF, WebP, AVIF, TIFF, PDF), use -1 for all pages.
|
|
107
|
+
* @param {number} [options.page=0] - page number to start extracting from for multi-page input (GIF, WebP, AVIF, TIFF, PDF), zero based.
|
|
108
108
|
* @param {number} [options.level=0] - level to extract from a multi-level input (OpenSlide), zero based.
|
|
109
109
|
* @param {boolean} [options.animated=false] - Set to `true` to read all frames/pages of an animated image (equivalent of setting `pages` to `-1`).
|
|
110
110
|
* @param {Object} [options.raw] - describes raw pixel input image data. See `raw()` for pixel ordering.
|
|
@@ -155,6 +155,13 @@ const Sharp = function (input, options) {
|
|
|
155
155
|
extendRight: 0,
|
|
156
156
|
extendBackground: [0, 0, 0, 255],
|
|
157
157
|
withoutEnlargement: false,
|
|
158
|
+
affineMatrix: [],
|
|
159
|
+
affineBackground: [0, 0, 0, 255],
|
|
160
|
+
affineIdx: 0,
|
|
161
|
+
affineIdy: 0,
|
|
162
|
+
affineOdx: 0,
|
|
163
|
+
affineOdy: 0,
|
|
164
|
+
affineInterpolator: this.constructor.interpolators.bilinear,
|
|
158
165
|
kernel: 'lanczos3',
|
|
159
166
|
fastShrinkOnLoad: true,
|
|
160
167
|
// operations
|
|
@@ -226,9 +233,10 @@ const Sharp = function (input, options) {
|
|
|
226
233
|
tiffTileWidth: 256,
|
|
227
234
|
tiffXres: 1.0,
|
|
228
235
|
tiffYres: 1.0,
|
|
229
|
-
heifQuality:
|
|
236
|
+
heifQuality: 50,
|
|
230
237
|
heifLossless: false,
|
|
231
|
-
heifCompression: '
|
|
238
|
+
heifCompression: 'av1',
|
|
239
|
+
heifSpeed: 5,
|
|
232
240
|
tileSize: 256,
|
|
233
241
|
tileOverlap: 0,
|
|
234
242
|
tileContainer: 'fs',
|
|
@@ -238,6 +246,7 @@ const Sharp = function (input, options) {
|
|
|
238
246
|
tileAngle: 0,
|
|
239
247
|
tileSkipBlanks: -1,
|
|
240
248
|
tileBackground: [255, 255, 255, 255],
|
|
249
|
+
tileCentre: false,
|
|
241
250
|
linearA: 1,
|
|
242
251
|
linearB: 0,
|
|
243
252
|
// Function to notify of libvips warnings
|
package/lib/input.js
CHANGED
|
@@ -9,9 +9,9 @@ const sharp = require('../build/Release/sharp.node');
|
|
|
9
9
|
* @private
|
|
10
10
|
*/
|
|
11
11
|
function _inputOptionsFromObject (obj) {
|
|
12
|
-
const { raw, density, limitInputPixels, sequentialRead, failOnError } = obj;
|
|
13
|
-
return [raw, density, limitInputPixels, sequentialRead, failOnError].some(is.defined)
|
|
14
|
-
? { raw, density, limitInputPixels, sequentialRead, failOnError }
|
|
12
|
+
const { raw, density, limitInputPixels, sequentialRead, failOnError, animated, page, pages } = obj;
|
|
13
|
+
return [raw, density, limitInputPixels, sequentialRead, failOnError, animated, page, pages].some(is.defined)
|
|
14
|
+
? { raw, density, limitInputPixels, sequentialRead, failOnError, animated, page, pages }
|
|
15
15
|
: undefined;
|
|
16
16
|
}
|
|
17
17
|
|
|
@@ -57,10 +57,10 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
|
|
|
57
57
|
}
|
|
58
58
|
// Density
|
|
59
59
|
if (is.defined(inputOptions.density)) {
|
|
60
|
-
if (is.inRange(inputOptions.density, 1,
|
|
60
|
+
if (is.inRange(inputOptions.density, 1, 100000)) {
|
|
61
61
|
inputDescriptor.density = inputOptions.density;
|
|
62
62
|
} else {
|
|
63
|
-
throw is.invalidParameterError('density', 'number between 1 and
|
|
63
|
+
throw is.invalidParameterError('density', 'number between 1 and 100000', inputOptions.density);
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
// limitInputPixels
|
package/lib/operation.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { flatten: flattenArray } = require('array-flatten');
|
|
3
4
|
const color = require('color');
|
|
4
5
|
const is = require('./is');
|
|
5
6
|
|
|
@@ -82,6 +83,103 @@ function flop (flop) {
|
|
|
82
83
|
return this;
|
|
83
84
|
}
|
|
84
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Perform an affine transform on an image. This operation will always occur after resizing, extraction and rotation, if any.
|
|
88
|
+
*
|
|
89
|
+
* You must provide an array of length 4 or a 2x2 affine transformation matrix.
|
|
90
|
+
* By default, new pixels are filled with a black background. You can provide a background color with the `background` option.
|
|
91
|
+
* A particular interpolator may also be specified. Set the `interpolator` option to an attribute of the `sharp.interpolator` Object e.g. `sharp.interpolator.nohalo`.
|
|
92
|
+
*
|
|
93
|
+
* In the case of a 2x2 matrix, the transform is:
|
|
94
|
+
* - X = `matrix[0, 0]` \* (x + `idx`) + `matrix[0, 1]` \* (y + `idy`) + `odx`
|
|
95
|
+
* - Y = `matrix[1, 0]` \* (x + `idx`) + `matrix[1, 1]` \* (y + `idy`) + `ody`
|
|
96
|
+
*
|
|
97
|
+
* where:
|
|
98
|
+
* - x and y are the coordinates in input image.
|
|
99
|
+
* - X and Y are the coordinates in output image.
|
|
100
|
+
* - (0,0) is the upper left corner.
|
|
101
|
+
*
|
|
102
|
+
* @since 0.27.0
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* const pipeline = sharp()
|
|
106
|
+
* .affine([[1, 0.3], [0.1, 0.7]], {
|
|
107
|
+
* background: 'white',
|
|
108
|
+
* interpolate: sharp.interpolators.nohalo
|
|
109
|
+
* })
|
|
110
|
+
* .toBuffer((err, outputBuffer, info) => {
|
|
111
|
+
* // outputBuffer contains the transformed image
|
|
112
|
+
* // info.width and info.height contain the new dimensions
|
|
113
|
+
* });
|
|
114
|
+
*
|
|
115
|
+
* inputStream
|
|
116
|
+
* .pipe(pipeline);
|
|
117
|
+
*
|
|
118
|
+
* @param {Array<Array<number>>|Array<number>} matrix - affine transformation matrix
|
|
119
|
+
* @param {Object} [options] - if present, is an Object with optional attributes.
|
|
120
|
+
* @param {String|Object} [options.background="#000000"] - parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
|
|
121
|
+
* @param {Number} [options.idx=0] - input horizontal offset
|
|
122
|
+
* @param {Number} [options.idy=0] - input vertical offset
|
|
123
|
+
* @param {Number} [options.odx=0] - output horizontal offset
|
|
124
|
+
* @param {Number} [options.ody=0] - output vertical offset
|
|
125
|
+
* @param {String} [options.interpolator=sharp.interpolators.bicubic] - interpolator
|
|
126
|
+
* @returns {Sharp}
|
|
127
|
+
* @throws {Error} Invalid parameters
|
|
128
|
+
*/
|
|
129
|
+
function affine (matrix, options) {
|
|
130
|
+
const flatMatrix = flattenArray(matrix);
|
|
131
|
+
if (flatMatrix.length === 4 && flatMatrix.every(is.number)) {
|
|
132
|
+
this.options.affineMatrix = flatMatrix;
|
|
133
|
+
} else {
|
|
134
|
+
throw is.invalidParameterError('matrix', '1x4 or 2x2 array', matrix);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (is.defined(options)) {
|
|
138
|
+
if (is.object(options)) {
|
|
139
|
+
this._setBackgroundColourOption('affineBackground', options.background);
|
|
140
|
+
if (is.defined(options.idx)) {
|
|
141
|
+
if (is.number(options.idx)) {
|
|
142
|
+
this.options.affineIdx = options.idx;
|
|
143
|
+
} else {
|
|
144
|
+
throw is.invalidParameterError('options.idx', 'number', options.idx);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (is.defined(options.idy)) {
|
|
148
|
+
if (is.number(options.idy)) {
|
|
149
|
+
this.options.affineIdy = options.idy;
|
|
150
|
+
} else {
|
|
151
|
+
throw is.invalidParameterError('options.idy', 'number', options.idy);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (is.defined(options.odx)) {
|
|
155
|
+
if (is.number(options.odx)) {
|
|
156
|
+
this.options.affineOdx = options.odx;
|
|
157
|
+
} else {
|
|
158
|
+
throw is.invalidParameterError('options.odx', 'number', options.odx);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (is.defined(options.ody)) {
|
|
162
|
+
if (is.number(options.ody)) {
|
|
163
|
+
this.options.affineOdy = options.ody;
|
|
164
|
+
} else {
|
|
165
|
+
throw is.invalidParameterError('options.ody', 'number', options.ody);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (is.defined(options.interpolator)) {
|
|
169
|
+
if (is.inArray(options.interpolator, Object.values(this.constructor.interpolators))) {
|
|
170
|
+
this.options.affineInterpolator = options.interpolator;
|
|
171
|
+
} else {
|
|
172
|
+
throw is.invalidParameterError('options.interpolator', 'valid interpolator name', options.interpolator);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
throw is.invalidParameterError('options', 'object', options);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return this;
|
|
181
|
+
}
|
|
182
|
+
|
|
85
183
|
/**
|
|
86
184
|
* Sharpen the image.
|
|
87
185
|
* When used without parameters, performs a fast, mild sharpen of the output image.
|
|
@@ -482,6 +580,7 @@ module.exports = function (Sharp) {
|
|
|
482
580
|
rotate,
|
|
483
581
|
flip,
|
|
484
582
|
flop,
|
|
583
|
+
affine,
|
|
485
584
|
sharpen,
|
|
486
585
|
median,
|
|
487
586
|
blur,
|
package/lib/output.js
CHANGED
|
@@ -6,6 +6,7 @@ const sharp = require('../build/Release/sharp.node');
|
|
|
6
6
|
const formats = new Map([
|
|
7
7
|
['heic', 'heif'],
|
|
8
8
|
['heif', 'heif'],
|
|
9
|
+
['avif', 'avif'],
|
|
9
10
|
['jpeg', 'jpeg'],
|
|
10
11
|
['jpg', 'jpeg'],
|
|
11
12
|
['png', 'png'],
|
|
@@ -19,7 +20,7 @@ const formats = new Map([
|
|
|
19
20
|
* Write output image data to a file.
|
|
20
21
|
*
|
|
21
22
|
* If an explicit output format is not selected, it will be inferred from the extension,
|
|
22
|
-
* with JPEG, PNG, WebP, TIFF, DZI, and libvips' V format supported.
|
|
23
|
+
* with JPEG, PNG, WebP, AVIF, TIFF, DZI, and libvips' V format supported.
|
|
23
24
|
* Note that raw pixel data is only supported for buffer output.
|
|
24
25
|
*
|
|
25
26
|
* By default all metadata will be removed, which includes EXIF-based orientation.
|
|
@@ -71,7 +72,7 @@ function toFile (fileOut, callback) {
|
|
|
71
72
|
|
|
72
73
|
/**
|
|
73
74
|
* Write output to a Buffer.
|
|
74
|
-
* JPEG, PNG, WebP, TIFF and
|
|
75
|
+
* JPEG, PNG, WebP, AVIF, TIFF and raw pixel data output are supported.
|
|
75
76
|
*
|
|
76
77
|
* If no explicit format is set, the output format will match the input image, except GIF and SVG input which become PNG output.
|
|
77
78
|
*
|
|
@@ -557,28 +558,41 @@ function tiff (options) {
|
|
|
557
558
|
}
|
|
558
559
|
|
|
559
560
|
/**
|
|
560
|
-
* Use these
|
|
561
|
+
* Use these AVIF options for output image.
|
|
561
562
|
*
|
|
562
|
-
*
|
|
563
|
-
*
|
|
563
|
+
* Whilst it is possible to create AVIF images smaller than 16x16 pixels,
|
|
564
|
+
* most web browsers do not display these properly.
|
|
564
565
|
*
|
|
565
|
-
*
|
|
566
|
+
* @since 0.27.0
|
|
566
567
|
*
|
|
567
|
-
*
|
|
568
|
+
* @param {Object} [options] - output options
|
|
569
|
+
* @param {number} [options.quality=50] - quality, integer 1-100
|
|
570
|
+
* @param {boolean} [options.lossless=false] - use lossless compression
|
|
571
|
+
* @param {boolean} [options.speed=5] - CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest)
|
|
572
|
+
* @returns {Sharp}
|
|
573
|
+
* @throws {Error} Invalid options
|
|
574
|
+
*/
|
|
575
|
+
function avif (options) {
|
|
576
|
+
return this.heif({ ...options, compression: 'av1' });
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Use these HEIF options for output image.
|
|
581
|
+
*
|
|
582
|
+
* Support for patent-encumbered HEIC images requires the use of a
|
|
583
|
+
* globally-installed libvips compiled with support for libheif, libde265 and x265.
|
|
568
584
|
*
|
|
569
585
|
* @since 0.23.0
|
|
570
586
|
*
|
|
571
587
|
* @param {Object} [options] - output options
|
|
572
|
-
* @param {number} [options.quality=
|
|
573
|
-
* @param {boolean} [options.compression='
|
|
588
|
+
* @param {number} [options.quality=50] - quality, integer 1-100
|
|
589
|
+
* @param {boolean} [options.compression='av1'] - compression format: av1, hevc
|
|
574
590
|
* @param {boolean} [options.lossless=false] - use lossless compression
|
|
591
|
+
* @param {boolean} [options.speed=5] - CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest)
|
|
575
592
|
* @returns {Sharp}
|
|
576
593
|
* @throws {Error} Invalid options
|
|
577
594
|
*/
|
|
578
595
|
function heif (options) {
|
|
579
|
-
if (!this.constructor.format.heif.output.buffer) {
|
|
580
|
-
throw new Error('The heif operation requires libvips to have been installed with support for libheif');
|
|
581
|
-
}
|
|
582
596
|
if (is.object(options)) {
|
|
583
597
|
if (is.defined(options.quality)) {
|
|
584
598
|
if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
|
|
@@ -595,10 +609,17 @@ function heif (options) {
|
|
|
595
609
|
}
|
|
596
610
|
}
|
|
597
611
|
if (is.defined(options.compression)) {
|
|
598
|
-
if (is.string(options.compression) && is.inArray(options.compression, ['
|
|
612
|
+
if (is.string(options.compression) && is.inArray(options.compression, ['av1', 'hevc'])) {
|
|
599
613
|
this.options.heifCompression = options.compression;
|
|
600
614
|
} else {
|
|
601
|
-
throw is.invalidParameterError('compression', 'one of:
|
|
615
|
+
throw is.invalidParameterError('compression', 'one of: av1, hevc', options.compression);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
if (is.defined(options.speed)) {
|
|
619
|
+
if (is.integer(options.speed) && is.inRange(options.speed, 0, 8)) {
|
|
620
|
+
this.options.heifSpeed = options.speed;
|
|
621
|
+
} else {
|
|
622
|
+
throw is.invalidParameterError('speed', 'integer between 0 and 8', options.speed);
|
|
602
623
|
}
|
|
603
624
|
}
|
|
604
625
|
}
|
|
@@ -658,6 +679,8 @@ function raw () {
|
|
|
658
679
|
* @param {number} [options.skipBlanks=-1] threshold to skip tile generation, a value 0 - 255 for 8-bit images or 0 - 65535 for 16-bit images
|
|
659
680
|
* @param {string} [options.container='fs'] tile container, with value `fs` (filesystem) or `zip` (compressed file).
|
|
660
681
|
* @param {string} [options.layout='dz'] filesystem layout, possible values are `dz`, `iiif`, `zoomify` or `google`.
|
|
682
|
+
* @param {boolean} [options.centre=false] centre image in tile.
|
|
683
|
+
* @param {boolean} [options.center=false] alternative spelling of centre.
|
|
661
684
|
* @returns {Sharp}
|
|
662
685
|
* @throws {Error} Invalid parameters
|
|
663
686
|
*/
|
|
@@ -726,6 +749,11 @@ function tile (options) {
|
|
|
726
749
|
} else if (is.defined(options.layout) && options.layout === 'google') {
|
|
727
750
|
this.options.tileSkipBlanks = 5;
|
|
728
751
|
}
|
|
752
|
+
// Center image in tile
|
|
753
|
+
const centre = is.bool(options.center) ? options.center : options.centre;
|
|
754
|
+
if (is.defined(centre)) {
|
|
755
|
+
this._setBooleanOption('tileCentre', centre);
|
|
756
|
+
}
|
|
729
757
|
}
|
|
730
758
|
// Format
|
|
731
759
|
if (is.inArray(this.options.formatOut, ['jpeg', 'png', 'webp'])) {
|
|
@@ -884,6 +912,7 @@ module.exports = function (Sharp) {
|
|
|
884
912
|
png,
|
|
885
913
|
webp,
|
|
886
914
|
tiff,
|
|
915
|
+
avif,
|
|
887
916
|
heif,
|
|
888
917
|
gif,
|
|
889
918
|
raw,
|
package/lib/resize.js
CHANGED
|
@@ -386,7 +386,8 @@ function extract (options) {
|
|
|
386
386
|
* Trim "boring" pixels from all edges that contain values similar to the top-left pixel.
|
|
387
387
|
* Images consisting entirely of a single colour will calculate "boring" using the alpha channel, if any.
|
|
388
388
|
*
|
|
389
|
-
* The `info` response Object
|
|
389
|
+
* The `info` response Object, obtained from callback of `.toFile()` or `.toBuffer()`,
|
|
390
|
+
* will contain `trimOffsetLeft` and `trimOffsetTop` properties.
|
|
390
391
|
*
|
|
391
392
|
* @param {number} [threshold=10] the allowed difference from the top-left pixel, a number greater than zero.
|
|
392
393
|
* @returns {Sharp}
|
package/lib/utility.js
CHANGED
|
@@ -13,6 +13,26 @@ const sharp = require('../build/Release/sharp.node');
|
|
|
13
13
|
*/
|
|
14
14
|
const format = sharp.format();
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* An Object containing the available interpolators and their proper values
|
|
18
|
+
* @readonly
|
|
19
|
+
* @enum {string}
|
|
20
|
+
*/
|
|
21
|
+
const interpolators = {
|
|
22
|
+
/** [Nearest neighbour interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation). Suitable for image enlargement only. */
|
|
23
|
+
nearest: 'nearest',
|
|
24
|
+
/** [Bilinear interpolation](http://en.wikipedia.org/wiki/Bilinear_interpolation). Faster than bicubic but with less smooth results. */
|
|
25
|
+
bilinear: 'bilinear',
|
|
26
|
+
/** [Bicubic interpolation](http://en.wikipedia.org/wiki/Bicubic_interpolation) (the default). */
|
|
27
|
+
bicubic: 'bicubic',
|
|
28
|
+
/** [LBB interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/lbb.cpp#L100). Prevents some "[acutance](http://en.wikipedia.org/wiki/Acutance)" but typically reduces performance by a factor of 2. */
|
|
29
|
+
locallyBoundedBicubic: 'lbb',
|
|
30
|
+
/** [Nohalo interpolation](http://eprints.soton.ac.uk/268086/). Prevents acutance but typically reduces performance by a factor of 3. */
|
|
31
|
+
nohalo: 'nohalo',
|
|
32
|
+
/** [VSQBS interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/vsqbs.cpp#L48). Prevents "staircasing" when enlarging. */
|
|
33
|
+
vertexSplitQuadraticBasisSpline: 'vsqbs'
|
|
34
|
+
};
|
|
35
|
+
|
|
16
36
|
/**
|
|
17
37
|
* An Object containing the version numbers of libvips and its dependencies.
|
|
18
38
|
* @member
|
|
@@ -146,6 +166,7 @@ module.exports = function (Sharp) {
|
|
|
146
166
|
Sharp[f.name] = f;
|
|
147
167
|
});
|
|
148
168
|
Sharp.format = format;
|
|
169
|
+
Sharp.interpolators = interpolators;
|
|
149
170
|
Sharp.versions = versions;
|
|
150
171
|
Sharp.queue = queue;
|
|
151
172
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sharp",
|
|
3
|
-
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP and TIFF images",
|
|
4
|
-
"version": "0.
|
|
3
|
+
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, AVIF and TIFF images",
|
|
4
|
+
"version": "0.27.0",
|
|
5
5
|
"author": "Lovell Fuller <npm@lovell.info>",
|
|
6
6
|
"homepage": "https://github.com/lovell/sharp",
|
|
7
7
|
"contributors": [
|
|
@@ -69,12 +69,15 @@
|
|
|
69
69
|
"Edward Silverton <e.silverton@gmail.com>",
|
|
70
70
|
"Roman Malieiev <aromaleev@gmail.com>",
|
|
71
71
|
"Tomas Szabo <tomas.szabo@deftomat.com>",
|
|
72
|
-
"Robert O'Rourke <robert@o-rourke.org>"
|
|
72
|
+
"Robert O'Rourke <robert@o-rourke.org>",
|
|
73
|
+
"Guillermo Alfonso Varela Chouciño <guillevch@gmail.com>",
|
|
74
|
+
"Christian Flintrup <chr@gigahost.dk>",
|
|
75
|
+
"Manan Jadhav <manan@motionden.com>"
|
|
73
76
|
],
|
|
74
77
|
"scripts": {
|
|
75
78
|
"install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node-gyp rebuild && node install/dll-copy)",
|
|
76
79
|
"clean": "rm -rf node_modules/ build/ vendor/ .nyc_output/ coverage/ test/fixtures/output.*",
|
|
77
|
-
"test": "semistandard && cpplint && npm run test-unit && npm run test-licensing
|
|
80
|
+
"test": "semistandard && cpplint && npm run test-unit && npm run test-licensing",
|
|
78
81
|
"test-unit": "nyc --reporter=lcov --branches=99 mocha --slow=5000 --timeout=60000 ./test/unit/*.js",
|
|
79
82
|
"test-licensing": "license-checker --production --summary --onlyAllow=\"Apache-2.0;BSD;ISC;MIT\"",
|
|
80
83
|
"test-coverage": "./test/coverage/report.sh",
|
|
@@ -87,7 +90,6 @@
|
|
|
87
90
|
"files": [
|
|
88
91
|
"binding.gyp",
|
|
89
92
|
"install/**",
|
|
90
|
-
"!install/prebuild-ci.js",
|
|
91
93
|
"lib/**",
|
|
92
94
|
"src/**"
|
|
93
95
|
],
|
|
@@ -99,6 +101,7 @@
|
|
|
99
101
|
"jpeg",
|
|
100
102
|
"png",
|
|
101
103
|
"webp",
|
|
104
|
+
"avif",
|
|
102
105
|
"tiff",
|
|
103
106
|
"gif",
|
|
104
107
|
"svg",
|
|
@@ -112,39 +115,40 @@
|
|
|
112
115
|
"vips"
|
|
113
116
|
],
|
|
114
117
|
"dependencies": {
|
|
115
|
-
"
|
|
118
|
+
"array-flatten": "^3.0.0",
|
|
119
|
+
"color": "^3.1.3",
|
|
116
120
|
"detect-libc": "^1.0.3",
|
|
117
|
-
"node-addon-api": "^3.
|
|
121
|
+
"node-addon-api": "^3.1.0",
|
|
118
122
|
"npmlog": "^4.1.2",
|
|
119
|
-
"prebuild-install": "^
|
|
120
|
-
"semver": "^7.3.
|
|
123
|
+
"prebuild-install": "^6.0.0",
|
|
124
|
+
"semver": "^7.3.4",
|
|
121
125
|
"simple-get": "^4.0.0",
|
|
122
|
-
"tar-fs": "^2.1.
|
|
126
|
+
"tar-fs": "^2.1.1",
|
|
123
127
|
"tunnel-agent": "^0.6.0"
|
|
124
128
|
},
|
|
125
129
|
"devDependencies": {
|
|
126
130
|
"async": "^3.2.0",
|
|
127
|
-
"cc": "^
|
|
131
|
+
"cc": "^3.0.1",
|
|
128
132
|
"decompress-zip": "^0.3.2",
|
|
129
|
-
"documentation": "^13.0
|
|
133
|
+
"documentation": "^13.1.0",
|
|
130
134
|
"exif-reader": "^1.0.3",
|
|
131
135
|
"icc": "^2.0.0",
|
|
132
136
|
"license-checker": "^25.0.1",
|
|
133
|
-
"mocha": "^8.
|
|
137
|
+
"mocha": "^8.2.1",
|
|
134
138
|
"mock-fs": "^4.13.0",
|
|
135
139
|
"nyc": "^15.1.0",
|
|
136
140
|
"prebuild": "^10.0.1",
|
|
137
141
|
"rimraf": "^3.0.2",
|
|
138
|
-
"semistandard": "^
|
|
142
|
+
"semistandard": "^16.0.0"
|
|
139
143
|
},
|
|
140
144
|
"license": "Apache-2.0",
|
|
141
145
|
"config": {
|
|
142
|
-
"libvips": "8.10.
|
|
146
|
+
"libvips": "8.10.5",
|
|
143
147
|
"runtime": "napi",
|
|
144
148
|
"target": 3
|
|
145
149
|
},
|
|
146
150
|
"engines": {
|
|
147
|
-
"node": ">=10
|
|
151
|
+
"node": ">=10"
|
|
148
152
|
},
|
|
149
153
|
"funding": {
|
|
150
154
|
"url": "https://opencollective.com/libvips"
|
package/src/common.cc
CHANGED
|
@@ -54,13 +54,13 @@ namespace sharp {
|
|
|
54
54
|
bool AttrAsBool(Napi::Object obj, std::string attr) {
|
|
55
55
|
return obj.Get(attr).As<Napi::Boolean>().Value();
|
|
56
56
|
}
|
|
57
|
-
std::vector<double>
|
|
58
|
-
Napi::Array
|
|
59
|
-
std::vector<double>
|
|
60
|
-
for (unsigned int i = 0; i <
|
|
61
|
-
|
|
57
|
+
std::vector<double> AttrAsVectorOfDouble(Napi::Object obj, std::string attr) {
|
|
58
|
+
Napi::Array napiArray = obj.Get(attr).As<Napi::Array>();
|
|
59
|
+
std::vector<double> vectorOfDouble(napiArray.Length());
|
|
60
|
+
for (unsigned int i = 0; i < napiArray.Length(); i++) {
|
|
61
|
+
vectorOfDouble[i] = AttrAsDouble(napiArray, i);
|
|
62
62
|
}
|
|
63
|
-
return
|
|
63
|
+
return vectorOfDouble;
|
|
64
64
|
}
|
|
65
65
|
std::vector<int32_t> AttrAsInt32Vector(Napi::Object obj, std::string attr) {
|
|
66
66
|
Napi::Array array = obj.Get(attr).As<Napi::Array>();
|
|
@@ -109,7 +109,7 @@ namespace sharp {
|
|
|
109
109
|
descriptor->createChannels = AttrAsUint32(input, "createChannels");
|
|
110
110
|
descriptor->createWidth = AttrAsUint32(input, "createWidth");
|
|
111
111
|
descriptor->createHeight = AttrAsUint32(input, "createHeight");
|
|
112
|
-
descriptor->createBackground =
|
|
112
|
+
descriptor->createBackground = AttrAsVectorOfDouble(input, "createBackground");
|
|
113
113
|
}
|
|
114
114
|
// Limit input images to a given number of pixels, where pixels = width * height
|
|
115
115
|
descriptor->limitInputPixels = AttrAsUint32(input, "limitInputPixels");
|
|
@@ -180,6 +180,7 @@ namespace sharp {
|
|
|
180
180
|
case ImageType::OPENSLIDE: id = "openslide"; break;
|
|
181
181
|
case ImageType::PPM: id = "ppm"; break;
|
|
182
182
|
case ImageType::FITS: id = "fits"; break;
|
|
183
|
+
case ImageType::EXR: id = "exr"; break;
|
|
183
184
|
case ImageType::VIPS: id = "vips"; break;
|
|
184
185
|
case ImageType::RAW: id = "raw"; break;
|
|
185
186
|
case ImageType::UNKNOWN: id = "unknown"; break;
|
|
@@ -210,6 +211,7 @@ namespace sharp {
|
|
|
210
211
|
{ "openslideload", ImageType::OPENSLIDE },
|
|
211
212
|
{ "ppmload", ImageType::PPM },
|
|
212
213
|
{ "fitsload", ImageType::FITS },
|
|
214
|
+
{ "openexrload", ImageType::EXR },
|
|
213
215
|
{ "vipsload", ImageType::VIPS },
|
|
214
216
|
{ "rawload", ImageType::RAW }
|
|
215
217
|
};
|
|
@@ -485,8 +487,8 @@ namespace sharp {
|
|
|
485
487
|
Check the proposed format supports the current dimensions.
|
|
486
488
|
*/
|
|
487
489
|
void AssertImageTypeDimensions(VImage image, ImageType const imageType) {
|
|
488
|
-
const int height = image.get_typeof(
|
|
489
|
-
? image.get_int(
|
|
490
|
+
const int height = image.get_typeof(VIPS_META_PAGE_HEIGHT) == G_TYPE_INT
|
|
491
|
+
? image.get_int(VIPS_META_PAGE_HEIGHT)
|
|
490
492
|
: image.height();
|
|
491
493
|
if (imageType == ImageType::JPEG) {
|
|
492
494
|
if (image.width() > 65535 || height > 65535) {
|
|
@@ -656,26 +658,18 @@ namespace sharp {
|
|
|
656
658
|
int top = 0;
|
|
657
659
|
|
|
658
660
|
// assign only if valid
|
|
659
|
-
if (x
|
|
661
|
+
if (x < (inWidth - outWidth)) {
|
|
660
662
|
left = x;
|
|
661
663
|
} else if (x >= (inWidth - outWidth)) {
|
|
662
664
|
left = inWidth - outWidth;
|
|
663
665
|
}
|
|
664
666
|
|
|
665
|
-
if (y
|
|
667
|
+
if (y < (inHeight - outHeight)) {
|
|
666
668
|
top = y;
|
|
667
669
|
} else if (y >= (inHeight - outHeight)) {
|
|
668
670
|
top = inHeight - outHeight;
|
|
669
671
|
}
|
|
670
672
|
|
|
671
|
-
// the resulting left and top could have been outside the image after calculation from bottom/right edges
|
|
672
|
-
if (left < 0) {
|
|
673
|
-
left = 0;
|
|
674
|
-
}
|
|
675
|
-
if (top < 0) {
|
|
676
|
-
top = 0;
|
|
677
|
-
}
|
|
678
|
-
|
|
679
673
|
return std::make_tuple(left, top);
|
|
680
674
|
}
|
|
681
675
|
|
package/src/common.h
CHANGED
|
@@ -24,8 +24,10 @@
|
|
|
24
24
|
|
|
25
25
|
// Verify platform and compiler compatibility
|
|
26
26
|
|
|
27
|
-
#if (VIPS_MAJOR_VERSION < 8 ||
|
|
28
|
-
|
|
27
|
+
#if (VIPS_MAJOR_VERSION < 8) || \
|
|
28
|
+
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 10) || \
|
|
29
|
+
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 10 && VIPS_MICRO_VERSION < 5)
|
|
30
|
+
#error "libvips version 8.10.5+ is required - please see https://sharp.pixelplumbing.com/install"
|
|
29
31
|
#endif
|
|
30
32
|
|
|
31
33
|
#if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6)))
|
|
@@ -92,7 +94,7 @@ namespace sharp {
|
|
|
92
94
|
double AttrAsDouble(Napi::Object obj, std::string attr);
|
|
93
95
|
double AttrAsDouble(Napi::Object obj, unsigned int const attr);
|
|
94
96
|
bool AttrAsBool(Napi::Object obj, std::string attr);
|
|
95
|
-
std::vector<double>
|
|
97
|
+
std::vector<double> AttrAsVectorOfDouble(Napi::Object obj, std::string attr);
|
|
96
98
|
std::vector<int32_t> AttrAsInt32Vector(Napi::Object obj, std::string attr);
|
|
97
99
|
|
|
98
100
|
// Create an InputDescriptor instance from a Napi::Object describing an input image
|
|
@@ -111,6 +113,7 @@ namespace sharp {
|
|
|
111
113
|
OPENSLIDE,
|
|
112
114
|
PPM,
|
|
113
115
|
FITS,
|
|
116
|
+
EXR,
|
|
114
117
|
VIPS,
|
|
115
118
|
RAW,
|
|
116
119
|
UNKNOWN,
|
package/src/pipeline.cc
CHANGED
|
@@ -485,6 +485,18 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
485
485
|
baton->leftOffsetPost, baton->topOffsetPost, baton->widthPost, baton->heightPost);
|
|
486
486
|
}
|
|
487
487
|
|
|
488
|
+
// Affine transform
|
|
489
|
+
if (baton->affineMatrix.size() > 0) {
|
|
490
|
+
std::vector<double> background;
|
|
491
|
+
std::tie(image, background) = sharp::ApplyAlpha(image, baton->affineBackground);
|
|
492
|
+
image = image.affine(baton->affineMatrix, VImage::option()->set("background", background)
|
|
493
|
+
->set("idx", baton->affineIdx)
|
|
494
|
+
->set("idy", baton->affineIdy)
|
|
495
|
+
->set("odx", baton->affineOdx)
|
|
496
|
+
->set("ody", baton->affineOdy)
|
|
497
|
+
->set("interpolate", baton->affineInterpolator));
|
|
498
|
+
}
|
|
499
|
+
|
|
488
500
|
// Extend edges
|
|
489
501
|
if (baton->extendTop > 0 || baton->extendBottom > 0 || baton->extendLeft > 0 || baton->extendRight > 0) {
|
|
490
502
|
std::vector<double> background;
|
|
@@ -558,7 +570,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
558
570
|
int left;
|
|
559
571
|
int top;
|
|
560
572
|
compositeImage = compositeImage.replicate(across, down);
|
|
561
|
-
if (composite->
|
|
573
|
+
if (composite->hasOffset) {
|
|
562
574
|
std::tie(left, top) = sharp::CalculateCrop(
|
|
563
575
|
compositeImage.width(), compositeImage.height(), image.width(), image.height(),
|
|
564
576
|
composite->left, composite->top);
|
|
@@ -580,7 +592,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
580
592
|
// Calculate position
|
|
581
593
|
int left;
|
|
582
594
|
int top;
|
|
583
|
-
if (composite->
|
|
595
|
+
if (composite->hasOffset) {
|
|
584
596
|
// Composite image at given offsets
|
|
585
597
|
std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),
|
|
586
598
|
compositeImage.width(), compositeImage.height(), composite->left, composite->top);
|
|
@@ -782,6 +794,8 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
782
794
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::GIF);
|
|
783
795
|
VipsArea *area = VIPS_AREA(image.magicksave_buffer(VImage::option()
|
|
784
796
|
->set("strip", !baton->withMetadata)
|
|
797
|
+
->set("optimize_gif_frames", TRUE)
|
|
798
|
+
->set("optimize_gif_transparency", TRUE)
|
|
785
799
|
->set("format", "gif")));
|
|
786
800
|
baton->bufferOut = static_cast<char*>(area->data);
|
|
787
801
|
baton->bufferOutLength = area->length;
|
|
@@ -823,6 +837,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
823
837
|
->set("strip", !baton->withMetadata)
|
|
824
838
|
->set("compression", baton->heifCompression)
|
|
825
839
|
->set("Q", baton->heifQuality)
|
|
840
|
+
->set("speed", baton->heifSpeed)
|
|
826
841
|
->set("lossless", baton->heifLossless)));
|
|
827
842
|
baton->bufferOut = static_cast<char*>(area->data);
|
|
828
843
|
baton->bufferOutLength = area->length;
|
|
@@ -871,7 +886,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
871
886
|
bool const isV = sharp::IsV(baton->fileOut);
|
|
872
887
|
bool const mightMatchInput = baton->formatOut == "input";
|
|
873
888
|
bool const willMatchInput = mightMatchInput &&
|
|
874
|
-
!(isJpeg || isPng || isWebp || isGif || isTiff || isDz || isDzZip || isV);
|
|
889
|
+
!(isJpeg || isPng || isWebp || isGif || isTiff || isHeif || isDz || isDzZip || isV);
|
|
875
890
|
|
|
876
891
|
if (baton->formatOut == "jpeg" || (mightMatchInput && isJpeg) ||
|
|
877
892
|
(willMatchInput && inputImageType == sharp::ImageType::JPEG)) {
|
|
@@ -925,6 +940,8 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
925
940
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::GIF);
|
|
926
941
|
image.magicksave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
|
927
942
|
->set("strip", !baton->withMetadata)
|
|
943
|
+
->set("optimize_gif_frames", TRUE)
|
|
944
|
+
->set("optimize_gif_transparency", TRUE)
|
|
928
945
|
->set("format", "gif"));
|
|
929
946
|
baton->formatOut = "gif";
|
|
930
947
|
} else if (baton->formatOut == "tiff" || (mightMatchInput && isTiff) ||
|
|
@@ -950,13 +967,11 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
950
967
|
} else if (baton->formatOut == "heif" || (mightMatchInput && isHeif) ||
|
|
951
968
|
(willMatchInput && inputImageType == sharp::ImageType::HEIF)) {
|
|
952
969
|
// Write HEIF to file
|
|
953
|
-
if (sharp::IsAvif(baton->fileOut)) {
|
|
954
|
-
baton->heifCompression = VIPS_FOREIGN_HEIF_COMPRESSION_AV1;
|
|
955
|
-
}
|
|
956
970
|
image.heifsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
|
957
971
|
->set("strip", !baton->withMetadata)
|
|
958
972
|
->set("Q", baton->heifQuality)
|
|
959
973
|
->set("compression", baton->heifCompression)
|
|
974
|
+
->set("speed", baton->heifSpeed)
|
|
960
975
|
->set("lossless", baton->heifLossless));
|
|
961
976
|
baton->formatOut = "heif";
|
|
962
977
|
} else if (baton->formatOut == "dz" || isDz || isDzZip) {
|
|
@@ -1010,6 +1025,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1010
1025
|
->set("suffix", const_cast<char*>(suffix.data()))
|
|
1011
1026
|
->set("angle", CalculateAngleRotation(baton->tileAngle))
|
|
1012
1027
|
->set("background", baton->tileBackground)
|
|
1028
|
+
->set("centre", baton->tileCentre)
|
|
1013
1029
|
->set("skip_blanks", baton->tileSkipBlanks);
|
|
1014
1030
|
// libvips chooses a default depth based on layout. Instead of replicating that logic here by
|
|
1015
1031
|
// not passing anything - libvips will handle choice
|
|
@@ -1237,6 +1253,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1237
1253
|
composite->gravity = sharp::AttrAsUint32(compositeObject, "gravity");
|
|
1238
1254
|
composite->left = sharp::AttrAsInt32(compositeObject, "left");
|
|
1239
1255
|
composite->top = sharp::AttrAsInt32(compositeObject, "top");
|
|
1256
|
+
composite->hasOffset = sharp::AttrAsBool(compositeObject, "hasOffset");
|
|
1240
1257
|
composite->tile = sharp::AttrAsBool(compositeObject, "tile");
|
|
1241
1258
|
composite->premultiplied = sharp::AttrAsBool(compositeObject, "premultiplied");
|
|
1242
1259
|
baton->composite.push_back(composite);
|
|
@@ -1244,7 +1261,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1244
1261
|
// Resize options
|
|
1245
1262
|
baton->withoutEnlargement = sharp::AttrAsBool(options, "withoutEnlargement");
|
|
1246
1263
|
baton->position = sharp::AttrAsInt32(options, "position");
|
|
1247
|
-
baton->resizeBackground = sharp::
|
|
1264
|
+
baton->resizeBackground = sharp::AttrAsVectorOfDouble(options, "resizeBackground");
|
|
1248
1265
|
baton->kernel = sharp::AttrAsStr(options, "kernel");
|
|
1249
1266
|
baton->fastShrinkOnLoad = sharp::AttrAsBool(options, "fastShrinkOnLoad");
|
|
1250
1267
|
// Join Channel Options
|
|
@@ -1257,7 +1274,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1257
1274
|
}
|
|
1258
1275
|
// Operators
|
|
1259
1276
|
baton->flatten = sharp::AttrAsBool(options, "flatten");
|
|
1260
|
-
baton->flattenBackground = sharp::
|
|
1277
|
+
baton->flattenBackground = sharp::AttrAsVectorOfDouble(options, "flattenBackground");
|
|
1261
1278
|
baton->negate = sharp::AttrAsBool(options, "negate");
|
|
1262
1279
|
baton->blurSigma = sharp::AttrAsDouble(options, "blurSigma");
|
|
1263
1280
|
baton->brightness = sharp::AttrAsDouble(options, "brightness");
|
|
@@ -1279,7 +1296,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1279
1296
|
baton->useExifOrientation = sharp::AttrAsBool(options, "useExifOrientation");
|
|
1280
1297
|
baton->angle = sharp::AttrAsInt32(options, "angle");
|
|
1281
1298
|
baton->rotationAngle = sharp::AttrAsDouble(options, "rotationAngle");
|
|
1282
|
-
baton->rotationBackground = sharp::
|
|
1299
|
+
baton->rotationBackground = sharp::AttrAsVectorOfDouble(options, "rotationBackground");
|
|
1283
1300
|
baton->rotateBeforePreExtract = sharp::AttrAsBool(options, "rotateBeforePreExtract");
|
|
1284
1301
|
baton->flip = sharp::AttrAsBool(options, "flip");
|
|
1285
1302
|
baton->flop = sharp::AttrAsBool(options, "flop");
|
|
@@ -1287,8 +1304,15 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1287
1304
|
baton->extendBottom = sharp::AttrAsInt32(options, "extendBottom");
|
|
1288
1305
|
baton->extendLeft = sharp::AttrAsInt32(options, "extendLeft");
|
|
1289
1306
|
baton->extendRight = sharp::AttrAsInt32(options, "extendRight");
|
|
1290
|
-
baton->extendBackground = sharp::
|
|
1307
|
+
baton->extendBackground = sharp::AttrAsVectorOfDouble(options, "extendBackground");
|
|
1291
1308
|
baton->extractChannel = sharp::AttrAsInt32(options, "extractChannel");
|
|
1309
|
+
baton->affineMatrix = sharp::AttrAsVectorOfDouble(options, "affineMatrix");
|
|
1310
|
+
baton->affineBackground = sharp::AttrAsVectorOfDouble(options, "affineBackground");
|
|
1311
|
+
baton->affineIdx = sharp::AttrAsDouble(options, "affineIdx");
|
|
1312
|
+
baton->affineIdy = sharp::AttrAsDouble(options, "affineIdy");
|
|
1313
|
+
baton->affineOdx = sharp::AttrAsDouble(options, "affineOdx");
|
|
1314
|
+
baton->affineOdy = sharp::AttrAsDouble(options, "affineOdy");
|
|
1315
|
+
baton->affineInterpolator = vips::VInterpolate::new_from_name(sharp::AttrAsStr(options, "affineInterpolator").data());
|
|
1292
1316
|
|
|
1293
1317
|
baton->removeAlpha = sharp::AttrAsBool(options, "removeAlpha");
|
|
1294
1318
|
baton->ensureAlpha = sharp::AttrAsBool(options, "ensureAlpha");
|
|
@@ -1371,6 +1395,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1371
1395
|
baton->heifCompression = static_cast<VipsForeignHeifCompression>(
|
|
1372
1396
|
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_HEIF_COMPRESSION,
|
|
1373
1397
|
sharp::AttrAsStr(options, "heifCompression").data()));
|
|
1398
|
+
baton->heifSpeed = sharp::AttrAsUint32(options, "heifSpeed");
|
|
1374
1399
|
|
|
1375
1400
|
// Animated output
|
|
1376
1401
|
if (sharp::HasAttr(options, "pageHeight")) {
|
|
@@ -1387,7 +1412,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1387
1412
|
baton->tileSize = sharp::AttrAsUint32(options, "tileSize");
|
|
1388
1413
|
baton->tileOverlap = sharp::AttrAsUint32(options, "tileOverlap");
|
|
1389
1414
|
baton->tileAngle = sharp::AttrAsInt32(options, "tileAngle");
|
|
1390
|
-
baton->tileBackground = sharp::
|
|
1415
|
+
baton->tileBackground = sharp::AttrAsVectorOfDouble(options, "tileBackground");
|
|
1391
1416
|
baton->tileSkipBlanks = sharp::AttrAsInt32(options, "tileSkipBlanks");
|
|
1392
1417
|
baton->tileContainer = static_cast<VipsForeignDzContainer>(
|
|
1393
1418
|
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_DZ_CONTAINER,
|
|
@@ -1399,6 +1424,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1399
1424
|
baton->tileDepth = static_cast<VipsForeignDzDepth>(
|
|
1400
1425
|
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_DZ_DEPTH,
|
|
1401
1426
|
sharp::AttrAsStr(options, "tileDepth").data()));
|
|
1427
|
+
baton->tileCentre = sharp::AttrAsBool(options, "tileCentre");
|
|
1402
1428
|
|
|
1403
1429
|
// Force random access for certain operations
|
|
1404
1430
|
if (baton->input->access == VIPS_ACCESS_SEQUENTIAL) {
|
package/src/pipeline.h
CHANGED
|
@@ -40,6 +40,7 @@ struct Composite {
|
|
|
40
40
|
int gravity;
|
|
41
41
|
int left;
|
|
42
42
|
int top;
|
|
43
|
+
bool hasOffset;
|
|
43
44
|
bool tile;
|
|
44
45
|
bool premultiplied;
|
|
45
46
|
|
|
@@ -47,8 +48,9 @@ struct Composite {
|
|
|
47
48
|
input(nullptr),
|
|
48
49
|
mode(VIPS_BLEND_MODE_OVER),
|
|
49
50
|
gravity(0),
|
|
50
|
-
left(
|
|
51
|
-
top(
|
|
51
|
+
left(0),
|
|
52
|
+
top(0),
|
|
53
|
+
hasOffset(false),
|
|
52
54
|
tile(false),
|
|
53
55
|
premultiplied(false) {}
|
|
54
56
|
};
|
|
@@ -79,6 +81,7 @@ struct PipelineBaton {
|
|
|
79
81
|
int cropOffsetLeft;
|
|
80
82
|
int cropOffsetTop;
|
|
81
83
|
bool premultiplied;
|
|
84
|
+
bool tileCentre;
|
|
82
85
|
std::string kernel;
|
|
83
86
|
bool fastShrinkOnLoad;
|
|
84
87
|
double tintA;
|
|
@@ -118,6 +121,13 @@ struct PipelineBaton {
|
|
|
118
121
|
int extendRight;
|
|
119
122
|
std::vector<double> extendBackground;
|
|
120
123
|
bool withoutEnlargement;
|
|
124
|
+
std::vector<double> affineMatrix;
|
|
125
|
+
std::vector<double> affineBackground;
|
|
126
|
+
double affineIdx;
|
|
127
|
+
double affineIdy;
|
|
128
|
+
double affineOdx;
|
|
129
|
+
double affineOdy;
|
|
130
|
+
vips::VInterpolate affineInterpolator;
|
|
121
131
|
int jpegQuality;
|
|
122
132
|
bool jpegProgressive;
|
|
123
133
|
std::string jpegChromaSubsampling;
|
|
@@ -151,6 +161,7 @@ struct PipelineBaton {
|
|
|
151
161
|
double tiffYres;
|
|
152
162
|
int heifQuality;
|
|
153
163
|
VipsForeignHeifCompression heifCompression;
|
|
164
|
+
int heifSpeed;
|
|
154
165
|
bool heifLossless;
|
|
155
166
|
std::string err;
|
|
156
167
|
bool withMetadata;
|
|
@@ -230,6 +241,13 @@ struct PipelineBaton {
|
|
|
230
241
|
extendRight(0),
|
|
231
242
|
extendBackground{ 0.0, 0.0, 0.0, 255.0 },
|
|
232
243
|
withoutEnlargement(false),
|
|
244
|
+
affineMatrix{ 1.0, 0.0, 0.0, 1.0 },
|
|
245
|
+
affineBackground{ 0.0, 0.0, 0.0, 255.0 },
|
|
246
|
+
affineIdx(0),
|
|
247
|
+
affineIdy(0),
|
|
248
|
+
affineOdx(0),
|
|
249
|
+
affineOdy(0),
|
|
250
|
+
affineInterpolator(vips::VInterpolate::new_from_name("bicubic")),
|
|
233
251
|
jpegQuality(80),
|
|
234
252
|
jpegProgressive(false),
|
|
235
253
|
jpegChromaSubsampling("4:2:0"),
|
|
@@ -261,8 +279,9 @@ struct PipelineBaton {
|
|
|
261
279
|
tiffTileWidth(256),
|
|
262
280
|
tiffXres(1.0),
|
|
263
281
|
tiffYres(1.0),
|
|
264
|
-
heifQuality(
|
|
265
|
-
heifCompression(
|
|
282
|
+
heifQuality(50),
|
|
283
|
+
heifCompression(VIPS_FOREIGN_HEIF_COMPRESSION_AV1),
|
|
284
|
+
heifSpeed(5),
|
|
266
285
|
heifLossless(false),
|
|
267
286
|
withMetadata(false),
|
|
268
287
|
withMetadataOrientation(-1),
|
package/src/sharp.cc
CHANGED
package/src/stats.cc
CHANGED
|
@@ -80,12 +80,14 @@ class StatsWorker : public Napi::AsyncWorker {
|
|
|
80
80
|
// Estimate entropy via histogram of greyscale value frequency
|
|
81
81
|
baton->entropy = std::abs(greyscale.hist_find().hist_entropy());
|
|
82
82
|
// Estimate sharpness via standard deviation of greyscale laplacian
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
83
|
+
if (image.width() > 1 || image.height() > 1) {
|
|
84
|
+
VImage laplacian = VImage::new_matrixv(3, 3,
|
|
85
|
+
0.0, 1.0, 0.0,
|
|
86
|
+
1.0, -4.0, 1.0,
|
|
87
|
+
0.0, 1.0, 0.0);
|
|
88
|
+
laplacian.set("scale", 9.0);
|
|
89
|
+
baton->sharpness = greyscale.conv(laplacian).deviate();
|
|
90
|
+
}
|
|
89
91
|
// Most dominant sRGB colour via 4096-bin 3D histogram
|
|
90
92
|
vips::VImage hist = sharp::RemoveAlpha(image)
|
|
91
93
|
.colourspace(VIPS_INTERPRETATION_sRGB)
|