sharp 0.26.1 → 0.27.1
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 +3 -3
- package/install/libvips.js +11 -4
- package/lib/channel.js +1 -1
- package/lib/colour.js +7 -0
- package/lib/composite.js +10 -7
- package/lib/constructor.js +58 -15
- package/lib/input.js +41 -10
- package/lib/is.js +10 -0
- package/lib/libvips.js +3 -2
- package/lib/operation.js +99 -0
- package/lib/output.js +71 -19
- package/lib/resize.js +2 -1
- package/lib/utility.js +21 -0
- package/package.json +25 -19
- package/src/common.cc +77 -50
- package/src/common.h +12 -4
- package/src/metadata.cc +6 -0
- package/src/metadata.h +1 -0
- package/src/pipeline.cc +52 -17
- package/src/pipeline.h +25 -4
- package/src/sharp.cc +1 -0
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
|
|
@@ -102,7 +102,7 @@ covers reporting bugs, requesting features and submitting code changes.
|
|
|
102
102
|
|
|
103
103
|
### Licensing
|
|
104
104
|
|
|
105
|
-
Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Lovell Fuller and contributors.
|
|
105
|
+
Copyright 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Lovell Fuller and contributors.
|
|
106
106
|
|
|
107
107
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
108
108
|
you may not use this file except in compliance with the License.
|
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,11 +82,16 @@ try {
|
|
|
80
82
|
throw new Error(`Use with glibc ${detectLibc.version} requires manual installation of libvips >= ${minimumLibvipsVersion}`);
|
|
81
83
|
}
|
|
82
84
|
}
|
|
83
|
-
|
|
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}`);
|
|
85
89
|
}
|
|
90
|
+
|
|
91
|
+
const extension = supportsBrotli ? 'br' : 'gz';
|
|
92
|
+
|
|
86
93
|
// Download to per-process temporary file
|
|
87
|
-
const tarFilename = ['libvips', minimumLibvipsVersion, platformAndArch].join('-') + '.tar.
|
|
94
|
+
const tarFilename = ['libvips', minimumLibvipsVersion, platformAndArch].join('-') + '.tar.' + extension;
|
|
88
95
|
const tarPathCache = path.join(libvips.cachePath(), tarFilename);
|
|
89
96
|
if (fs.existsSync(tarPathCache)) {
|
|
90
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/colour.js
CHANGED
|
@@ -57,6 +57,13 @@ function grayscale (grayscale) {
|
|
|
57
57
|
/**
|
|
58
58
|
* Set the output colourspace.
|
|
59
59
|
* By default output image will be web-friendly sRGB, with additional channels interpreted as alpha channels.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* // Output 16 bits per pixel RGB
|
|
63
|
+
* await sharp(input)
|
|
64
|
+
* .toColourspace('rgb16')
|
|
65
|
+
* .toFile('16-bpp.png')
|
|
66
|
+
*
|
|
60
67
|
* @param {string} [colourspace] - output colourspace e.g. `srgb`, `rgb`, `cmyk`, `lab`, `b-w` [...](https://github.com/libvips/libvips/blob/master/libvips/iofuncs/enumtypes.c#L568)
|
|
61
68
|
* @returns {Sharp}
|
|
62
69
|
* @throws {Error} Invalid parameters
|
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.
|
|
@@ -90,10 +90,39 @@ const debuglog = util.debuglog('sharp');
|
|
|
90
90
|
* // Convert an animated GIF to an animated WebP
|
|
91
91
|
* await sharp('in.gif', { animated: true }).toFile('out.webp');
|
|
92
92
|
*
|
|
93
|
-
* @
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
93
|
+
* @example
|
|
94
|
+
* // Read a raw array of pixels and save it to a png
|
|
95
|
+
* const input = Uint8Array.from([255, 255, 255, 0, 0, 0]); // or Uint8ClampedArray
|
|
96
|
+
* const image = sharp(input, {
|
|
97
|
+
* // because the input does not contain its dimensions or how many channels it has
|
|
98
|
+
* // we need to specify it in the constructor options
|
|
99
|
+
* raw: {
|
|
100
|
+
* width: 2,
|
|
101
|
+
* height: 1,
|
|
102
|
+
* channels: 3
|
|
103
|
+
* }
|
|
104
|
+
* });
|
|
105
|
+
* await image.toFile('my-two-pixels.png');
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* // Generate RGB Gaussian noise
|
|
109
|
+
* await sharp({
|
|
110
|
+
* create: {
|
|
111
|
+
* width: 300,
|
|
112
|
+
* height: 200,
|
|
113
|
+
* channels: 3,
|
|
114
|
+
* noise: {
|
|
115
|
+
* type: 'gaussian',
|
|
116
|
+
* mean: 128,
|
|
117
|
+
* sigma: 30
|
|
118
|
+
* }
|
|
119
|
+
* }
|
|
120
|
+
* }.toFile('noise.png');
|
|
121
|
+
*
|
|
122
|
+
* @param {(Buffer|Uint8Array|Uint8ClampedArray|string)} [input] - if present, can be
|
|
123
|
+
* a Buffer / Uint8Array / Uint8ClampedArray containing JPEG, PNG, WebP, AVIF, GIF, SVG, TIFF or raw pixel image data, or
|
|
124
|
+
* a String containing the filesystem path to an JPEG, PNG, WebP, AVIF, GIF, SVG or TIFF image file.
|
|
125
|
+
* JPEG, PNG, WebP, AVIF, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present.
|
|
97
126
|
* @param {Object} [options] - if present, is an Object with optional attributes.
|
|
98
127
|
* @param {boolean} [options.failOnError=true] - by default halt processing and raise an error when loading invalid images.
|
|
99
128
|
* Set this flag to `false` if you'd rather apply a "best effort" to decode images, even if the data is corrupt or invalid.
|
|
@@ -103,19 +132,23 @@ const debuglog = util.debuglog('sharp');
|
|
|
103
132
|
* @param {boolean} [options.sequentialRead=false] - Set this to `true` to use sequential rather than random access where possible.
|
|
104
133
|
* This can reduce memory usage and might improve performance on some systems.
|
|
105
134
|
* @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, 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.
|
|
135
|
+
* @param {number} [options.pages=1] - number of pages to extract for multi-page input (GIF, WebP, AVIF, TIFF, PDF), use -1 for all pages.
|
|
136
|
+
* @param {number} [options.page=0] - page number to start extracting from for multi-page input (GIF, WebP, AVIF, TIFF, PDF), zero based.
|
|
108
137
|
* @param {number} [options.level=0] - level to extract from a multi-level input (OpenSlide), zero based.
|
|
109
138
|
* @param {boolean} [options.animated=false] - Set to `true` to read all frames/pages of an animated image (equivalent of setting `pages` to `-1`).
|
|
110
139
|
* @param {Object} [options.raw] - describes raw pixel input image data. See `raw()` for pixel ordering.
|
|
111
|
-
* @param {number} [options.raw.width]
|
|
112
|
-
* @param {number} [options.raw.height]
|
|
113
|
-
* @param {number} [options.raw.channels] - 1
|
|
140
|
+
* @param {number} [options.raw.width] - integral number of pixels wide.
|
|
141
|
+
* @param {number} [options.raw.height] - integral number of pixels high.
|
|
142
|
+
* @param {number} [options.raw.channels] - integral number of channels, between 1 and 4.
|
|
114
143
|
* @param {Object} [options.create] - describes a new image to be created.
|
|
115
|
-
* @param {number} [options.create.width]
|
|
116
|
-
* @param {number} [options.create.height]
|
|
117
|
-
* @param {number} [options.create.channels] - 3
|
|
144
|
+
* @param {number} [options.create.width] - integral number of pixels wide.
|
|
145
|
+
* @param {number} [options.create.height] - integral number of pixels high.
|
|
146
|
+
* @param {number} [options.create.channels] - integral number of channels, either 3 (RGB) or 4 (RGBA).
|
|
118
147
|
* @param {string|Object} [options.create.background] - parsed by the [color](https://www.npmjs.org/package/color) module to extract values for red, green, blue and alpha.
|
|
148
|
+
* @param {Object} [options.create.noise] - describes a noise to be created.
|
|
149
|
+
* @param {string} [options.create.noise.type] - type of generated noise, currently only `gaussian` is supported.
|
|
150
|
+
* @param {number} [options.create.noise.mean] - mean of pixels in generated noise.
|
|
151
|
+
* @param {number} [options.create.noise.sigma] - standard deviation of pixels in generated noise.
|
|
119
152
|
* @returns {Sharp}
|
|
120
153
|
* @throws {Error} Invalid parameters
|
|
121
154
|
*/
|
|
@@ -155,6 +188,13 @@ const Sharp = function (input, options) {
|
|
|
155
188
|
extendRight: 0,
|
|
156
189
|
extendBackground: [0, 0, 0, 255],
|
|
157
190
|
withoutEnlargement: false,
|
|
191
|
+
affineMatrix: [],
|
|
192
|
+
affineBackground: [0, 0, 0, 255],
|
|
193
|
+
affineIdx: 0,
|
|
194
|
+
affineIdy: 0,
|
|
195
|
+
affineOdx: 0,
|
|
196
|
+
affineOdy: 0,
|
|
197
|
+
affineInterpolator: this.constructor.interpolators.bilinear,
|
|
158
198
|
kernel: 'lanczos3',
|
|
159
199
|
fastShrinkOnLoad: true,
|
|
160
200
|
// operations
|
|
@@ -226,9 +266,11 @@ const Sharp = function (input, options) {
|
|
|
226
266
|
tiffTileWidth: 256,
|
|
227
267
|
tiffXres: 1.0,
|
|
228
268
|
tiffYres: 1.0,
|
|
229
|
-
heifQuality:
|
|
269
|
+
heifQuality: 50,
|
|
230
270
|
heifLossless: false,
|
|
231
|
-
heifCompression: '
|
|
271
|
+
heifCompression: 'av1',
|
|
272
|
+
heifSpeed: 5,
|
|
273
|
+
heifChromaSubsampling: '4:2:0',
|
|
232
274
|
tileSize: 256,
|
|
233
275
|
tileOverlap: 0,
|
|
234
276
|
tileContainer: 'fs',
|
|
@@ -238,6 +280,7 @@ const Sharp = function (input, options) {
|
|
|
238
280
|
tileAngle: 0,
|
|
239
281
|
tileSkipBlanks: -1,
|
|
240
282
|
tileBackground: [255, 255, 255, 255],
|
|
283
|
+
tileCentre: false,
|
|
241
284
|
linearA: 1,
|
|
242
285
|
linearB: 0,
|
|
243
286
|
// Function to notify of libvips warnings
|
package/lib/input.js
CHANGED
|
@@ -31,6 +31,9 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
|
|
|
31
31
|
} else if (is.buffer(input)) {
|
|
32
32
|
// Buffer
|
|
33
33
|
inputDescriptor.buffer = input;
|
|
34
|
+
} else if (is.uint8Array(input)) {
|
|
35
|
+
// Uint8Array or Uint8ClampedArray
|
|
36
|
+
inputDescriptor.buffer = Buffer.from(input.buffer);
|
|
34
37
|
} else if (is.plainObject(input) && !is.defined(inputOptions)) {
|
|
35
38
|
// Plain Object descriptor, e.g. create
|
|
36
39
|
inputOptions = input;
|
|
@@ -134,22 +137,50 @@ function _createInputDescriptor (input, inputOptions, containerOptions) {
|
|
|
134
137
|
is.object(inputOptions.create) &&
|
|
135
138
|
is.integer(inputOptions.create.width) && inputOptions.create.width > 0 &&
|
|
136
139
|
is.integer(inputOptions.create.height) && inputOptions.create.height > 0 &&
|
|
137
|
-
is.integer(inputOptions.create.channels)
|
|
138
|
-
is.defined(inputOptions.create.background)
|
|
140
|
+
is.integer(inputOptions.create.channels)
|
|
139
141
|
) {
|
|
140
142
|
inputDescriptor.createWidth = inputOptions.create.width;
|
|
141
143
|
inputDescriptor.createHeight = inputOptions.create.height;
|
|
142
144
|
inputDescriptor.createChannels = inputOptions.create.channels;
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
145
|
+
// Noise
|
|
146
|
+
if (is.defined(inputOptions.create.noise)) {
|
|
147
|
+
if (!is.object(inputOptions.create.noise)) {
|
|
148
|
+
throw new Error('Expected noise to be an object');
|
|
149
|
+
}
|
|
150
|
+
if (!is.inArray(inputOptions.create.noise.type, ['gaussian'])) {
|
|
151
|
+
throw new Error('Only gaussian noise is supported at the moment');
|
|
152
|
+
}
|
|
153
|
+
if (!is.inRange(inputOptions.create.channels, 1, 4)) {
|
|
154
|
+
throw is.invalidParameterError('create.channels', 'number between 1 and 4', inputOptions.create.channels);
|
|
155
|
+
}
|
|
156
|
+
inputDescriptor.createNoiseType = inputOptions.create.noise.type;
|
|
157
|
+
if (is.number(inputOptions.create.noise.mean) && is.inRange(inputOptions.create.noise.mean, 0, 10000)) {
|
|
158
|
+
inputDescriptor.createNoiseMean = inputOptions.create.noise.mean;
|
|
159
|
+
} else {
|
|
160
|
+
throw is.invalidParameterError('create.noise.mean', 'number between 0 and 10000', inputOptions.create.noise.mean);
|
|
161
|
+
}
|
|
162
|
+
if (is.number(inputOptions.create.noise.sigma) && is.inRange(inputOptions.create.noise.sigma, 0, 10000)) {
|
|
163
|
+
inputDescriptor.createNoiseSigma = inputOptions.create.noise.sigma;
|
|
164
|
+
} else {
|
|
165
|
+
throw is.invalidParameterError('create.noise.sigma', 'number between 0 and 10000', inputOptions.create.noise.sigma);
|
|
166
|
+
}
|
|
167
|
+
} else if (is.defined(inputOptions.create.background)) {
|
|
168
|
+
if (!is.inRange(inputOptions.create.channels, 3, 4)) {
|
|
169
|
+
throw is.invalidParameterError('create.channels', 'number between 3 and 4', inputOptions.create.channels);
|
|
170
|
+
}
|
|
171
|
+
const background = color(inputOptions.create.background);
|
|
172
|
+
inputDescriptor.createBackground = [
|
|
173
|
+
background.red(),
|
|
174
|
+
background.green(),
|
|
175
|
+
background.blue(),
|
|
176
|
+
Math.round(background.alpha() * 255)
|
|
177
|
+
];
|
|
178
|
+
} else {
|
|
179
|
+
throw new Error('Expected valid noise or background to create a new input image');
|
|
180
|
+
}
|
|
150
181
|
delete inputDescriptor.buffer;
|
|
151
182
|
} else {
|
|
152
|
-
throw new Error('Expected width, height
|
|
183
|
+
throw new Error('Expected valid width, height and channels to create a new input image');
|
|
153
184
|
}
|
|
154
185
|
}
|
|
155
186
|
} else if (is.defined(inputOptions)) {
|
package/lib/is.js
CHANGED
|
@@ -48,6 +48,15 @@ const buffer = function (val) {
|
|
|
48
48
|
return val instanceof Buffer;
|
|
49
49
|
};
|
|
50
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Is this value a Uint8Array or Uint8ClampedArray object?
|
|
53
|
+
* @private
|
|
54
|
+
*/
|
|
55
|
+
const uint8Array = function (val) {
|
|
56
|
+
// allow both since Uint8ClampedArray simply clamps the values between 0-255
|
|
57
|
+
return val instanceof Uint8Array || val instanceof Uint8ClampedArray;
|
|
58
|
+
};
|
|
59
|
+
|
|
51
60
|
/**
|
|
52
61
|
* Is this value a non-empty string?
|
|
53
62
|
* @private
|
|
@@ -110,6 +119,7 @@ module.exports = {
|
|
|
110
119
|
fn: fn,
|
|
111
120
|
bool: bool,
|
|
112
121
|
buffer: buffer,
|
|
122
|
+
uint8Array: uint8Array,
|
|
113
123
|
string: string,
|
|
114
124
|
number: number,
|
|
115
125
|
integer: integer,
|
package/lib/libvips.js
CHANGED
|
@@ -39,8 +39,9 @@ const cachePath = function () {
|
|
|
39
39
|
|
|
40
40
|
const globalLibvipsVersion = function () {
|
|
41
41
|
if (process.platform !== 'win32') {
|
|
42
|
-
const globalLibvipsVersion = spawnSync(`PKG_CONFIG_PATH="${pkgConfigPath()}" pkg-config --modversion vips-cpp`, spawnSyncOptions).stdout
|
|
43
|
-
|
|
42
|
+
const globalLibvipsVersion = spawnSync(`PKG_CONFIG_PATH="${pkgConfigPath()}" pkg-config --modversion vips-cpp`, spawnSyncOptions).stdout;
|
|
43
|
+
/* istanbul ignore next */
|
|
44
|
+
return (globalLibvipsVersion || '').trim();
|
|
44
45
|
} else {
|
|
45
46
|
return '';
|
|
46
47
|
}
|
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
|
*
|
|
@@ -103,6 +104,20 @@ function toFile (fileOut, callback) {
|
|
|
103
104
|
* .then(({ data, info }) => { ... })
|
|
104
105
|
* .catch(err => { ... });
|
|
105
106
|
*
|
|
107
|
+
* @example
|
|
108
|
+
* const data = await sharp('my-image.jpg')
|
|
109
|
+
* // output the raw pixels
|
|
110
|
+
* .raw()
|
|
111
|
+
* .toBuffer();
|
|
112
|
+
*
|
|
113
|
+
* // create a more type safe way to work with the raw pixel data
|
|
114
|
+
* // this will not copy the data, instead it will change `data`s underlying ArrayBuffer
|
|
115
|
+
* // so `data` and `pixelArray` point to the same memory location
|
|
116
|
+
* const pixelArray = new Uint8ClampedArray(data.buffer);
|
|
117
|
+
*
|
|
118
|
+
* // When you are done changing the pixelArray, sharp takes the `pixelArray` as an input
|
|
119
|
+
* await sharp(pixelArray).toFile('my-changed-image.jpg');
|
|
120
|
+
*
|
|
106
121
|
* @param {Object} [options]
|
|
107
122
|
* @param {boolean} [options.resolveWithObject] Resolve the Promise with an Object containing `data` and `info` properties instead of resolving only with `data`.
|
|
108
123
|
* @param {Function} [callback]
|
|
@@ -471,15 +486,15 @@ function trySetAnimationOptions (source, target) {
|
|
|
471
486
|
* @param {Object} [options] - output options
|
|
472
487
|
* @param {number} [options.quality=80] - quality, integer 1-100
|
|
473
488
|
* @param {boolean} [options.force=true] - force TIFF output, otherwise attempt to use input format
|
|
474
|
-
* @param {
|
|
475
|
-
* @param {
|
|
489
|
+
* @param {string} [options.compression='jpeg'] - compression options: lzw, deflate, jpeg, ccittfax4
|
|
490
|
+
* @param {string} [options.predictor='horizontal'] - compression predictor options: none, horizontal, float
|
|
476
491
|
* @param {boolean} [options.pyramid=false] - write an image pyramid
|
|
477
492
|
* @param {boolean} [options.tile=false] - write a tiled tiff
|
|
478
|
-
* @param {
|
|
479
|
-
* @param {
|
|
493
|
+
* @param {number} [options.tileWidth=256] - horizontal tile size
|
|
494
|
+
* @param {number} [options.tileHeight=256] - vertical tile size
|
|
480
495
|
* @param {number} [options.xres=1.0] - horizontal resolution in pixels/mm
|
|
481
496
|
* @param {number} [options.yres=1.0] - vertical resolution in pixels/mm
|
|
482
|
-
* @param {
|
|
497
|
+
* @param {number} [options.bitdepth=8] - reduce bitdepth to 1, 2 or 4 bit
|
|
483
498
|
* @returns {Sharp}
|
|
484
499
|
* @throws {Error} Invalid options
|
|
485
500
|
*/
|
|
@@ -557,28 +572,43 @@ function tiff (options) {
|
|
|
557
572
|
}
|
|
558
573
|
|
|
559
574
|
/**
|
|
560
|
-
* Use these
|
|
575
|
+
* Use these AVIF options for output image.
|
|
561
576
|
*
|
|
562
|
-
*
|
|
563
|
-
*
|
|
577
|
+
* Whilst it is possible to create AVIF images smaller than 16x16 pixels,
|
|
578
|
+
* most web browsers do not display these properly.
|
|
564
579
|
*
|
|
565
|
-
*
|
|
580
|
+
* @since 0.27.0
|
|
566
581
|
*
|
|
567
|
-
*
|
|
582
|
+
* @param {Object} [options] - output options
|
|
583
|
+
* @param {number} [options.quality=50] - quality, integer 1-100
|
|
584
|
+
* @param {boolean} [options.lossless=false] - use lossless compression
|
|
585
|
+
* @param {boolean} [options.speed=5] - CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest)
|
|
586
|
+
* @param {string} [options.chromaSubsampling='4:2:0'] - set to '4:4:4' to prevent chroma subsampling otherwise defaults to '4:2:0' chroma subsampling, requires libvips v8.11.0
|
|
587
|
+
* @returns {Sharp}
|
|
588
|
+
* @throws {Error} Invalid options
|
|
589
|
+
*/
|
|
590
|
+
function avif (options) {
|
|
591
|
+
return this.heif({ ...options, compression: 'av1' });
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* Use these HEIF options for output image.
|
|
596
|
+
*
|
|
597
|
+
* Support for patent-encumbered HEIC images requires the use of a
|
|
598
|
+
* globally-installed libvips compiled with support for libheif, libde265 and x265.
|
|
568
599
|
*
|
|
569
600
|
* @since 0.23.0
|
|
570
601
|
*
|
|
571
602
|
* @param {Object} [options] - output options
|
|
572
|
-
* @param {number} [options.quality=
|
|
573
|
-
* @param {
|
|
603
|
+
* @param {number} [options.quality=50] - quality, integer 1-100
|
|
604
|
+
* @param {string} [options.compression='av1'] - compression format: av1, hevc
|
|
574
605
|
* @param {boolean} [options.lossless=false] - use lossless compression
|
|
606
|
+
* @param {number} [options.speed=5] - CPU effort vs file size, 0 (slowest/smallest) to 8 (fastest/largest)
|
|
607
|
+
* @param {string} [options.chromaSubsampling='4:2:0'] - set to '4:4:4' to prevent chroma subsampling otherwise defaults to '4:2:0' chroma subsampling, requires libvips v8.11.0
|
|
575
608
|
* @returns {Sharp}
|
|
576
609
|
* @throws {Error} Invalid options
|
|
577
610
|
*/
|
|
578
611
|
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
612
|
if (is.object(options)) {
|
|
583
613
|
if (is.defined(options.quality)) {
|
|
584
614
|
if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
|
|
@@ -595,10 +625,24 @@ function heif (options) {
|
|
|
595
625
|
}
|
|
596
626
|
}
|
|
597
627
|
if (is.defined(options.compression)) {
|
|
598
|
-
if (is.string(options.compression) && is.inArray(options.compression, ['
|
|
628
|
+
if (is.string(options.compression) && is.inArray(options.compression, ['av1', 'hevc'])) {
|
|
599
629
|
this.options.heifCompression = options.compression;
|
|
600
630
|
} else {
|
|
601
|
-
throw is.invalidParameterError('compression', 'one of:
|
|
631
|
+
throw is.invalidParameterError('compression', 'one of: av1, hevc', options.compression);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
if (is.defined(options.speed)) {
|
|
635
|
+
if (is.integer(options.speed) && is.inRange(options.speed, 0, 8)) {
|
|
636
|
+
this.options.heifSpeed = options.speed;
|
|
637
|
+
} else {
|
|
638
|
+
throw is.invalidParameterError('speed', 'integer between 0 and 8', options.speed);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
if (is.defined(options.chromaSubsampling)) {
|
|
642
|
+
if (is.string(options.chromaSubsampling) && is.inArray(options.chromaSubsampling, ['4:2:0', '4:4:4'])) {
|
|
643
|
+
this.options.heifChromaSubsampling = options.chromaSubsampling;
|
|
644
|
+
} else {
|
|
645
|
+
throw is.invalidParameterError('chromaSubsampling', 'one of: 4:2:0, 4:4:4', options.chromaSubsampling);
|
|
602
646
|
}
|
|
603
647
|
}
|
|
604
648
|
}
|
|
@@ -658,6 +702,8 @@ function raw () {
|
|
|
658
702
|
* @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
703
|
* @param {string} [options.container='fs'] tile container, with value `fs` (filesystem) or `zip` (compressed file).
|
|
660
704
|
* @param {string} [options.layout='dz'] filesystem layout, possible values are `dz`, `iiif`, `zoomify` or `google`.
|
|
705
|
+
* @param {boolean} [options.centre=false] centre image in tile.
|
|
706
|
+
* @param {boolean} [options.center=false] alternative spelling of centre.
|
|
661
707
|
* @returns {Sharp}
|
|
662
708
|
* @throws {Error} Invalid parameters
|
|
663
709
|
*/
|
|
@@ -726,6 +772,11 @@ function tile (options) {
|
|
|
726
772
|
} else if (is.defined(options.layout) && options.layout === 'google') {
|
|
727
773
|
this.options.tileSkipBlanks = 5;
|
|
728
774
|
}
|
|
775
|
+
// Center image in tile
|
|
776
|
+
const centre = is.bool(options.center) ? options.center : options.centre;
|
|
777
|
+
if (is.defined(centre)) {
|
|
778
|
+
this._setBooleanOption('tileCentre', centre);
|
|
779
|
+
}
|
|
729
780
|
}
|
|
730
781
|
// Format
|
|
731
782
|
if (is.inArray(this.options.formatOut, ['jpeg', 'png', 'webp'])) {
|
|
@@ -884,6 +935,7 @@ module.exports = function (Sharp) {
|
|
|
884
935
|
png,
|
|
885
936
|
webp,
|
|
886
937
|
tiff,
|
|
938
|
+
avif,
|
|
887
939
|
heif,
|
|
888
940
|
gif,
|
|
889
941
|
raw,
|