sharp 0.31.1 → 0.31.3
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/install/libvips.js +3 -2
- package/lib/agent.js +2 -1
- package/lib/channel.js +1 -1
- package/lib/constructor.js +7 -1
- package/lib/input.js +7 -2
- package/lib/operation.js +22 -20
- package/lib/output.js +96 -3
- package/lib/resize.js +5 -3
- package/package.json +18 -18
- package/src/common.cc +15 -1
- package/src/common.h +5 -2
- package/src/metadata.cc +5 -5
- package/src/operations.cc +2 -2
- package/src/pipeline.cc +92 -50
- package/src/pipeline.h +12 -0
- package/src/stats.cc +1 -0
- package/src/utilities.cc +1 -1
package/install/libvips.js
CHANGED
|
@@ -138,7 +138,8 @@ try {
|
|
|
138
138
|
const libcFamily = detectLibc.familySync();
|
|
139
139
|
const libcVersion = detectLibc.versionSync();
|
|
140
140
|
if (libcFamily === detectLibc.GLIBC && libcVersion && minimumGlibcVersionByArch[arch]) {
|
|
141
|
-
|
|
141
|
+
const libcVersionWithoutPatch = libcVersion.split('.').slice(0, 2).join('.');
|
|
142
|
+
if (semverLessThan(`${libcVersionWithoutPatch}.0`, `${minimumGlibcVersionByArch[arch]}.0`)) {
|
|
142
143
|
handleError(new Error(`Use with glibc ${libcVersion} requires manual installation of libvips >= ${minimumLibvipsVersion}`));
|
|
143
144
|
}
|
|
144
145
|
}
|
|
@@ -167,7 +168,7 @@ try {
|
|
|
167
168
|
} else {
|
|
168
169
|
const url = distBaseUrl + tarFilename;
|
|
169
170
|
libvips.log(`Downloading ${url}`);
|
|
170
|
-
simpleGet({ url: url, agent: agent() }, function (err, response) {
|
|
171
|
+
simpleGet({ url: url, agent: agent(libvips.log) }, function (err, response) {
|
|
171
172
|
if (err) {
|
|
172
173
|
fail(err);
|
|
173
174
|
} else if (response.statusCode === 404) {
|
package/lib/agent.js
CHANGED
|
@@ -18,7 +18,7 @@ function env (key) {
|
|
|
18
18
|
return process.env[key];
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
module.exports = function () {
|
|
21
|
+
module.exports = function (log) {
|
|
22
22
|
try {
|
|
23
23
|
const proxy = new url.URL(proxies.map(env).find(is.string));
|
|
24
24
|
const tunnel = proxy.protocol === 'https:'
|
|
@@ -27,6 +27,7 @@ module.exports = function () {
|
|
|
27
27
|
const proxyAuth = proxy.username && proxy.password
|
|
28
28
|
? `${decodeURIComponent(proxy.username)}:${decodeURIComponent(proxy.password)}`
|
|
29
29
|
: null;
|
|
30
|
+
log(`Via proxy ${proxy.protocol}://${proxy.hostname}:${proxy.port} ${proxyAuth ? 'with' : 'no'} credentials`);
|
|
30
31
|
return tunnel({
|
|
31
32
|
proxy: {
|
|
32
33
|
port: Number(proxy.port),
|
package/lib/channel.js
CHANGED
package/lib/constructor.js
CHANGED
|
@@ -119,7 +119,7 @@ const debuglog = util.debuglog('sharp');
|
|
|
119
119
|
* a String containing the filesystem path to an JPEG, PNG, WebP, AVIF, GIF, SVG or TIFF image file.
|
|
120
120
|
* JPEG, PNG, WebP, AVIF, GIF, SVG, TIFF or raw pixel image data can be streamed into the object when not present.
|
|
121
121
|
* @param {Object} [options] - if present, is an Object with optional attributes.
|
|
122
|
-
* @param {string} [options.failOn='warning'] -
|
|
122
|
+
* @param {string} [options.failOn='warning'] - when to abort processing of invalid pixel data, one of (in order of sensitivity): 'none' (least), 'truncated', 'error' or 'warning' (most), highers level imply lower levels, invalid metadata will always abort.
|
|
123
123
|
* @param {number|boolean} [options.limitInputPixels=268402689] - Do not process input images where the number of pixels
|
|
124
124
|
* (width x height) exceeds this limit. Assumes image dimensions contained in the input metadata can be trusted.
|
|
125
125
|
* An integral Number of pixels, zero or false to remove limit, true to use default limit of 268402689 (0x3FFF x 0x3FFF).
|
|
@@ -289,6 +289,8 @@ const Sharp = function (input, options) {
|
|
|
289
289
|
gifBitdepth: 8,
|
|
290
290
|
gifEffort: 7,
|
|
291
291
|
gifDither: 1,
|
|
292
|
+
gifInterFrameMaxError: 0,
|
|
293
|
+
gifInterPaletteMaxError: 3,
|
|
292
294
|
gifReoptimise: false,
|
|
293
295
|
tiffQuality: 80,
|
|
294
296
|
tiffCompression: 'jpeg',
|
|
@@ -306,6 +308,10 @@ const Sharp = function (input, options) {
|
|
|
306
308
|
heifCompression: 'av1',
|
|
307
309
|
heifEffort: 4,
|
|
308
310
|
heifChromaSubsampling: '4:4:4',
|
|
311
|
+
jxlDistance: 1,
|
|
312
|
+
jxlDecodingTier: 0,
|
|
313
|
+
jxlEffort: 7,
|
|
314
|
+
jxlLossless: false,
|
|
309
315
|
rawDepth: 'uchar',
|
|
310
316
|
tileSize: 256,
|
|
311
317
|
tileOverlap: 0,
|
package/lib/input.js
CHANGED
|
@@ -469,7 +469,7 @@ function metadata (callback) {
|
|
|
469
469
|
} else {
|
|
470
470
|
if (this._isStreamInput()) {
|
|
471
471
|
return new Promise((resolve, reject) => {
|
|
472
|
-
|
|
472
|
+
const finished = () => {
|
|
473
473
|
this._flattenBufferIn();
|
|
474
474
|
sharp.metadata(this.options, (err, metadata) => {
|
|
475
475
|
if (err) {
|
|
@@ -478,7 +478,12 @@ function metadata (callback) {
|
|
|
478
478
|
resolve(metadata);
|
|
479
479
|
}
|
|
480
480
|
});
|
|
481
|
-
}
|
|
481
|
+
};
|
|
482
|
+
if (this.writableFinished) {
|
|
483
|
+
finished();
|
|
484
|
+
} else {
|
|
485
|
+
this.once('finish', finished);
|
|
486
|
+
}
|
|
482
487
|
});
|
|
483
488
|
} else {
|
|
484
489
|
return new Promise((resolve, reject) => {
|
package/lib/operation.js
CHANGED
|
@@ -205,9 +205,11 @@ function affine (matrix, options) {
|
|
|
205
205
|
|
|
206
206
|
/**
|
|
207
207
|
* Sharpen the image.
|
|
208
|
+
*
|
|
208
209
|
* When used without parameters, performs a fast, mild sharpen of the output image.
|
|
210
|
+
*
|
|
209
211
|
* When a `sigma` is provided, performs a slower, more accurate sharpen of the L channel in the LAB colour space.
|
|
210
|
-
*
|
|
212
|
+
* Fine-grained control over the level of sharpening in "flat" (m1) and "jagged" (m2) areas is available.
|
|
211
213
|
*
|
|
212
214
|
* See {@link https://www.libvips.org/API/current/libvips-convolution.html#vips-sharpen|libvips sharpen} operation.
|
|
213
215
|
*
|
|
@@ -229,13 +231,13 @@ function affine (matrix, options) {
|
|
|
229
231
|
* })
|
|
230
232
|
* .toBuffer();
|
|
231
233
|
*
|
|
232
|
-
* @param {Object|number} [options] - if present, is an Object with attributes
|
|
233
|
-
* @param {number} [options.sigma] - the sigma of the Gaussian mask, where `sigma = 1 + radius / 2
|
|
234
|
-
* @param {number} [options.m1=1.0] - the level of sharpening to apply to "flat" areas
|
|
235
|
-
* @param {number} [options.m2=2.0] - the level of sharpening to apply to "jagged" areas
|
|
236
|
-
* @param {number} [options.x1=2.0] - threshold between "flat" and "jagged"
|
|
237
|
-
* @param {number} [options.y2=10.0] - maximum amount of brightening
|
|
238
|
-
* @param {number} [options.y3=20.0] - maximum amount of darkening
|
|
234
|
+
* @param {Object|number} [options] - if present, is an Object with attributes
|
|
235
|
+
* @param {number} [options.sigma] - the sigma of the Gaussian mask, where `sigma = 1 + radius / 2`, between 0.000001 and 10000
|
|
236
|
+
* @param {number} [options.m1=1.0] - the level of sharpening to apply to "flat" areas, between 0 and 1000000
|
|
237
|
+
* @param {number} [options.m2=2.0] - the level of sharpening to apply to "jagged" areas, between 0 and 1000000
|
|
238
|
+
* @param {number} [options.x1=2.0] - threshold between "flat" and "jagged", between 0 and 1000000
|
|
239
|
+
* @param {number} [options.y2=10.0] - maximum amount of brightening, between 0 and 1000000
|
|
240
|
+
* @param {number} [options.y3=20.0] - maximum amount of darkening, between 0 and 1000000
|
|
239
241
|
* @param {number} [flat] - (deprecated) see `options.m1`.
|
|
240
242
|
* @param {number} [jagged] - (deprecated) see `options.m2`.
|
|
241
243
|
* @returns {Sharp}
|
|
@@ -268,44 +270,44 @@ function sharpen (options, flat, jagged) {
|
|
|
268
270
|
}
|
|
269
271
|
}
|
|
270
272
|
} else if (is.plainObject(options)) {
|
|
271
|
-
if (is.number(options.sigma) && is.inRange(options.sigma, 0.
|
|
273
|
+
if (is.number(options.sigma) && is.inRange(options.sigma, 0.000001, 10000)) {
|
|
272
274
|
this.options.sharpenSigma = options.sigma;
|
|
273
275
|
} else {
|
|
274
|
-
throw is.invalidParameterError('options.sigma', 'number between 0.
|
|
276
|
+
throw is.invalidParameterError('options.sigma', 'number between 0.000001 and 10000', options.sigma);
|
|
275
277
|
}
|
|
276
278
|
if (is.defined(options.m1)) {
|
|
277
|
-
if (is.number(options.m1) && is.inRange(options.m1, 0,
|
|
279
|
+
if (is.number(options.m1) && is.inRange(options.m1, 0, 1000000)) {
|
|
278
280
|
this.options.sharpenM1 = options.m1;
|
|
279
281
|
} else {
|
|
280
|
-
throw is.invalidParameterError('options.m1', 'number between 0 and
|
|
282
|
+
throw is.invalidParameterError('options.m1', 'number between 0 and 1000000', options.m1);
|
|
281
283
|
}
|
|
282
284
|
}
|
|
283
285
|
if (is.defined(options.m2)) {
|
|
284
|
-
if (is.number(options.m2) && is.inRange(options.m2, 0,
|
|
286
|
+
if (is.number(options.m2) && is.inRange(options.m2, 0, 1000000)) {
|
|
285
287
|
this.options.sharpenM2 = options.m2;
|
|
286
288
|
} else {
|
|
287
|
-
throw is.invalidParameterError('options.m2', 'number between 0 and
|
|
289
|
+
throw is.invalidParameterError('options.m2', 'number between 0 and 1000000', options.m2);
|
|
288
290
|
}
|
|
289
291
|
}
|
|
290
292
|
if (is.defined(options.x1)) {
|
|
291
|
-
if (is.number(options.x1) && is.inRange(options.x1, 0,
|
|
293
|
+
if (is.number(options.x1) && is.inRange(options.x1, 0, 1000000)) {
|
|
292
294
|
this.options.sharpenX1 = options.x1;
|
|
293
295
|
} else {
|
|
294
|
-
throw is.invalidParameterError('options.x1', 'number between 0 and
|
|
296
|
+
throw is.invalidParameterError('options.x1', 'number between 0 and 1000000', options.x1);
|
|
295
297
|
}
|
|
296
298
|
}
|
|
297
299
|
if (is.defined(options.y2)) {
|
|
298
|
-
if (is.number(options.y2) && is.inRange(options.y2, 0,
|
|
300
|
+
if (is.number(options.y2) && is.inRange(options.y2, 0, 1000000)) {
|
|
299
301
|
this.options.sharpenY2 = options.y2;
|
|
300
302
|
} else {
|
|
301
|
-
throw is.invalidParameterError('options.y2', 'number between 0 and
|
|
303
|
+
throw is.invalidParameterError('options.y2', 'number between 0 and 1000000', options.y2);
|
|
302
304
|
}
|
|
303
305
|
}
|
|
304
306
|
if (is.defined(options.y3)) {
|
|
305
|
-
if (is.number(options.y3) && is.inRange(options.y3, 0,
|
|
307
|
+
if (is.number(options.y3) && is.inRange(options.y3, 0, 1000000)) {
|
|
306
308
|
this.options.sharpenY3 = options.y3;
|
|
307
309
|
} else {
|
|
308
|
-
throw is.invalidParameterError('options.y3', 'number between 0 and
|
|
310
|
+
throw is.invalidParameterError('options.y3', 'number between 0 and 1000000', options.y3);
|
|
309
311
|
}
|
|
310
312
|
}
|
|
311
313
|
} else {
|
package/lib/output.js
CHANGED
|
@@ -22,10 +22,13 @@ const formats = new Map([
|
|
|
22
22
|
['jp2', 'jp2'],
|
|
23
23
|
['jpx', 'jp2'],
|
|
24
24
|
['j2k', 'jp2'],
|
|
25
|
-
['j2c', 'jp2']
|
|
25
|
+
['j2c', 'jp2'],
|
|
26
|
+
['jxl', 'jxl']
|
|
26
27
|
]);
|
|
27
28
|
|
|
28
|
-
const
|
|
29
|
+
const jp2Regex = /\.jp[2x]|j2[kc]$/i;
|
|
30
|
+
|
|
31
|
+
const errJp2Save = () => new Error('JP2 output requires libvips with support for OpenJPEG');
|
|
29
32
|
|
|
30
33
|
const bitdepthFromColourCount = (colours) => 1 << 31 - Math.clz32(Math.ceil(Math.log2(colours)));
|
|
31
34
|
|
|
@@ -68,6 +71,8 @@ function toFile (fileOut, callback) {
|
|
|
68
71
|
err = new Error('Missing output file path');
|
|
69
72
|
} else if (is.string(this.options.input.file) && path.resolve(this.options.input.file) === path.resolve(fileOut)) {
|
|
70
73
|
err = new Error('Cannot use same file for input and output');
|
|
74
|
+
} else if (jp2Regex.test(fileOut) && !this.constructor.format.jp2k.output.file) {
|
|
75
|
+
err = errJp2Save();
|
|
71
76
|
}
|
|
72
77
|
if (err) {
|
|
73
78
|
if (is.fn(callback)) {
|
|
@@ -547,6 +552,12 @@ function webp (options) {
|
|
|
547
552
|
* .gif({ dither: 0 })
|
|
548
553
|
* .toBuffer();
|
|
549
554
|
*
|
|
555
|
+
* @example
|
|
556
|
+
* // Lossy file size reduction of animated GIF
|
|
557
|
+
* await sharp('in.gif', { animated: true })
|
|
558
|
+
* .gif({ interFrameMaxError: 8 })
|
|
559
|
+
* .toFile('optim.gif');
|
|
560
|
+
*
|
|
550
561
|
* @param {Object} [options] - output options
|
|
551
562
|
* @param {boolean} [options.reoptimise=false] - always generate new palettes (slow), re-use existing by default
|
|
552
563
|
* @param {boolean} [options.reoptimize=false] - alternative spelling of `options.reoptimise`
|
|
@@ -554,6 +565,8 @@ function webp (options) {
|
|
|
554
565
|
* @param {number} [options.colors=256] - alternative spelling of `options.colours`
|
|
555
566
|
* @param {number} [options.effort=7] - CPU effort, between 1 (fastest) and 10 (slowest)
|
|
556
567
|
* @param {number} [options.dither=1.0] - level of Floyd-Steinberg error diffusion, between 0 (least) and 1 (most)
|
|
568
|
+
* @param {number} [options.interFrameMaxError=0] - maximum inter-frame error for transparency, between 0 (lossless) and 32
|
|
569
|
+
* @param {number} [options.interPaletteMaxError=3] - maximum inter-palette error for palette reuse, between 0 and 256
|
|
557
570
|
* @param {number} [options.loop=0] - number of animation iterations, use 0 for infinite animation
|
|
558
571
|
* @param {number|number[]} [options.delay] - delay(s) between animation frames (in milliseconds)
|
|
559
572
|
* @param {boolean} [options.force=true] - force GIF output, otherwise attempt to use input format
|
|
@@ -589,6 +602,20 @@ function gif (options) {
|
|
|
589
602
|
throw is.invalidParameterError('dither', 'number between 0.0 and 1.0', options.dither);
|
|
590
603
|
}
|
|
591
604
|
}
|
|
605
|
+
if (is.defined(options.interFrameMaxError)) {
|
|
606
|
+
if (is.number(options.interFrameMaxError) && is.inRange(options.interFrameMaxError, 0, 32)) {
|
|
607
|
+
this.options.gifInterFrameMaxError = options.interFrameMaxError;
|
|
608
|
+
} else {
|
|
609
|
+
throw is.invalidParameterError('interFrameMaxError', 'number between 0.0 and 32.0', options.interFrameMaxError);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
if (is.defined(options.interPaletteMaxError)) {
|
|
613
|
+
if (is.number(options.interPaletteMaxError) && is.inRange(options.interPaletteMaxError, 0, 256)) {
|
|
614
|
+
this.options.gifInterPaletteMaxError = options.interPaletteMaxError;
|
|
615
|
+
} else {
|
|
616
|
+
throw is.invalidParameterError('interPaletteMaxError', 'number between 0.0 and 256.0', options.interPaletteMaxError);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
592
619
|
}
|
|
593
620
|
trySetAnimationOptions(options, this.options);
|
|
594
621
|
return this._updateFormatOut('gif', options);
|
|
@@ -630,7 +657,7 @@ function gif (options) {
|
|
|
630
657
|
/* istanbul ignore next */
|
|
631
658
|
function jp2 (options) {
|
|
632
659
|
if (!this.constructor.format.jp2k.output.buffer) {
|
|
633
|
-
throw errJp2Save;
|
|
660
|
+
throw errJp2Save();
|
|
634
661
|
}
|
|
635
662
|
if (is.object(options)) {
|
|
636
663
|
if (is.defined(options.quality)) {
|
|
@@ -912,6 +939,71 @@ function heif (options) {
|
|
|
912
939
|
return this._updateFormatOut('heif', options);
|
|
913
940
|
}
|
|
914
941
|
|
|
942
|
+
/**
|
|
943
|
+
* Use these JPEG-XL (JXL) options for output image.
|
|
944
|
+
*
|
|
945
|
+
* This feature is experimental, please do not use in production systems.
|
|
946
|
+
*
|
|
947
|
+
* Requires libvips compiled with support for libjxl.
|
|
948
|
+
* The prebuilt binaries do not include this - see
|
|
949
|
+
* {@link https://sharp.pixelplumbing.com/install#custom-libvips installing a custom libvips}.
|
|
950
|
+
*
|
|
951
|
+
* Image metadata (EXIF, XMP) is unsupported.
|
|
952
|
+
*
|
|
953
|
+
* @since 0.31.3
|
|
954
|
+
*
|
|
955
|
+
* @param {Object} [options] - output options
|
|
956
|
+
* @param {number} [options.distance=1.0] - maximum encoding error, between 0 (highest quality) and 15 (lowest quality)
|
|
957
|
+
* @param {number} [options.quality] - calculate `distance` based on JPEG-like quality, between 1 and 100, overrides distance if specified
|
|
958
|
+
* @param {number} [options.decodingTier=0] - target decode speed tier, between 0 (highest quality) and 4 (lowest quality)
|
|
959
|
+
* @param {boolean} [options.lossless=false] - use lossless compression
|
|
960
|
+
* @param {number} [options.effort=7] - CPU effort, between 3 (fastest) and 9 (slowest)
|
|
961
|
+
* @returns {Sharp}
|
|
962
|
+
* @throws {Error} Invalid options
|
|
963
|
+
*/
|
|
964
|
+
function jxl (options) {
|
|
965
|
+
if (is.object(options)) {
|
|
966
|
+
if (is.defined(options.quality)) {
|
|
967
|
+
if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
|
|
968
|
+
// https://github.com/libjxl/libjxl/blob/0aeea7f180bafd6893c1db8072dcb67d2aa5b03d/tools/cjxl_main.cc#L640-L644
|
|
969
|
+
this.options.jxlDistance = options.quality >= 30
|
|
970
|
+
? 0.1 + (100 - options.quality) * 0.09
|
|
971
|
+
: 53 / 3000 * options.quality * options.quality - 23 / 20 * options.quality + 25;
|
|
972
|
+
} else {
|
|
973
|
+
throw is.invalidParameterError('quality', 'integer between 1 and 100', options.quality);
|
|
974
|
+
}
|
|
975
|
+
} else if (is.defined(options.distance)) {
|
|
976
|
+
if (is.number(options.distance) && is.inRange(options.distance, 0, 15)) {
|
|
977
|
+
this.options.jxlDistance = options.distance;
|
|
978
|
+
} else {
|
|
979
|
+
throw is.invalidParameterError('distance', 'number between 0.0 and 15.0', options.distance);
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
if (is.defined(options.decodingTier)) {
|
|
983
|
+
if (is.integer(options.decodingTier) && is.inRange(options.decodingTier, 0, 4)) {
|
|
984
|
+
this.options.jxlDecodingTier = options.decodingTier;
|
|
985
|
+
} else {
|
|
986
|
+
throw is.invalidParameterError('decodingTier', 'integer between 0 and 4', options.decodingTier);
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
if (is.defined(options.lossless)) {
|
|
990
|
+
if (is.bool(options.lossless)) {
|
|
991
|
+
this.options.jxlLossless = options.lossless;
|
|
992
|
+
} else {
|
|
993
|
+
throw is.invalidParameterError('lossless', 'boolean', options.lossless);
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
if (is.defined(options.effort)) {
|
|
997
|
+
if (is.integer(options.effort) && is.inRange(options.effort, 3, 9)) {
|
|
998
|
+
this.options.jxlEffort = options.effort;
|
|
999
|
+
} else {
|
|
1000
|
+
throw is.invalidParameterError('effort', 'integer between 3 and 9', options.effort);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
return this._updateFormatOut('jxl', options);
|
|
1005
|
+
}
|
|
1006
|
+
|
|
915
1007
|
/**
|
|
916
1008
|
* Force output to be raw, uncompressed pixel data.
|
|
917
1009
|
* Pixel ordering is left-to-right, top-to-bottom, without padding.
|
|
@@ -1282,6 +1374,7 @@ module.exports = function (Sharp) {
|
|
|
1282
1374
|
tiff,
|
|
1283
1375
|
avif,
|
|
1284
1376
|
heif,
|
|
1377
|
+
jxl,
|
|
1285
1378
|
gif,
|
|
1286
1379
|
raw,
|
|
1287
1380
|
tile,
|
package/lib/resize.js
CHANGED
|
@@ -111,7 +111,9 @@ function isResizeExpected (options) {
|
|
|
111
111
|
*
|
|
112
112
|
* Some of these values are based on the [object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) CSS property.
|
|
113
113
|
*
|
|
114
|
-
*
|
|
114
|
+
* <img alt="Examples of various values for the fit property when resizing" width="100%" style="aspect-ratio: 998/243" src="https://cdn.jsdelivr.net/gh/lovell/sharp@main/docs/image/api-resize-fit.png">
|
|
115
|
+
*
|
|
116
|
+
* When using a **fit** of `cover` or `contain`, the default **position** is `centre`. Other options are:
|
|
115
117
|
* - `sharp.position`: `top`, `right top`, `right`, `right bottom`, `bottom`, `left bottom`, `left`, `left top`.
|
|
116
118
|
* - `sharp.gravity`: `north`, `northeast`, `east`, `southeast`, `south`, `southwest`, `west`, `northwest`, `center` or `centre`.
|
|
117
119
|
* - `sharp.strategy`: `cover` only, dynamically crop using either the `entropy` or `attention` strategy.
|
|
@@ -217,8 +219,8 @@ function isResizeExpected (options) {
|
|
|
217
219
|
* @param {number} [width] - pixels wide the resultant image should be. Use `null` or `undefined` to auto-scale the width to match the height.
|
|
218
220
|
* @param {number} [height] - pixels high the resultant image should be. Use `null` or `undefined` to auto-scale the height to match the width.
|
|
219
221
|
* @param {Object} [options]
|
|
220
|
-
* @param {String} [options.width] - alternative means of specifying `width`. If both are present this
|
|
221
|
-
* @param {String} [options.height] - alternative means of specifying `height`. If both are present this
|
|
222
|
+
* @param {String} [options.width] - alternative means of specifying `width`. If both are present this takes priority.
|
|
223
|
+
* @param {String} [options.height] - alternative means of specifying `height`. If both are present this takes priority.
|
|
222
224
|
* @param {String} [options.fit='cover'] - how the image should be resized to fit both provided dimensions, one of `cover`, `contain`, `fill`, `inside` or `outside`.
|
|
223
225
|
* @param {String} [options.position='centre'] - position, gravity or strategy to use when `fit` is `cover` or `contain`.
|
|
224
226
|
* @param {String|Object} [options.background={r: 0, g: 0, b: 0, alpha: 1}] - background colour when `fit` is `contain`, parsed by the [color](https://www.npmjs.org/package/color) module, defaults to black without transparency.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sharp",
|
|
3
3
|
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, GIF, AVIF and TIFF images",
|
|
4
|
-
"version": "0.31.
|
|
4
|
+
"version": "0.31.3",
|
|
5
5
|
"author": "Lovell Fuller <npm@lovell.info>",
|
|
6
6
|
"homepage": "https://github.com/lovell/sharp",
|
|
7
7
|
"contributors": [
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
"clean": "rm -rf node_modules/ build/ vendor/ .nyc_output/ coverage/ test/fixtures/output.*",
|
|
93
93
|
"test": "npm run test-lint && npm run test-unit && npm run test-licensing",
|
|
94
94
|
"test-lint": "semistandard && cpplint",
|
|
95
|
-
"test-unit": "nyc --reporter=lcov --reporter=text --check-coverage --branches=100 mocha
|
|
95
|
+
"test-unit": "nyc --reporter=lcov --reporter=text --check-coverage --branches=100 mocha",
|
|
96
96
|
"test-licensing": "license-checker --production --summary --onlyAllow=\"Apache-2.0;BSD;ISC;MIT\"",
|
|
97
97
|
"test-leak": "./test/leak/leak.sh",
|
|
98
98
|
"docs-build": "documentation lint lib && node docs/build && node docs/search-index/build",
|
|
@@ -133,7 +133,7 @@
|
|
|
133
133
|
"detect-libc": "^2.0.1",
|
|
134
134
|
"node-addon-api": "^5.0.0",
|
|
135
135
|
"prebuild-install": "^7.1.1",
|
|
136
|
-
"semver": "^7.3.
|
|
136
|
+
"semver": "^7.3.8",
|
|
137
137
|
"simple-get": "^4.0.1",
|
|
138
138
|
"tar-fs": "^2.1.1",
|
|
139
139
|
"tunnel-agent": "^0.6.0"
|
|
@@ -141,13 +141,13 @@
|
|
|
141
141
|
"devDependencies": {
|
|
142
142
|
"async": "^3.2.4",
|
|
143
143
|
"cc": "^3.0.1",
|
|
144
|
-
"documentation": "^14.0.
|
|
144
|
+
"documentation": "^14.0.1",
|
|
145
145
|
"exif-reader": "^1.0.3",
|
|
146
146
|
"extract-zip": "^2.0.1",
|
|
147
147
|
"icc": "^2.0.0",
|
|
148
148
|
"license-checker": "^25.0.1",
|
|
149
|
-
"mocha": "^10.
|
|
150
|
-
"mock-fs": "^5.
|
|
149
|
+
"mocha": "^10.2.0",
|
|
150
|
+
"mock-fs": "^5.2.0",
|
|
151
151
|
"nyc": "^15.1.0",
|
|
152
152
|
"prebuild": "^11.0.4",
|
|
153
153
|
"rimraf": "^3.0.2",
|
|
@@ -155,19 +155,19 @@
|
|
|
155
155
|
},
|
|
156
156
|
"license": "Apache-2.0",
|
|
157
157
|
"config": {
|
|
158
|
-
"libvips": "8.13.
|
|
158
|
+
"libvips": "8.13.3",
|
|
159
159
|
"integrity": {
|
|
160
|
-
"darwin-arm64v8": "sha512-
|
|
161
|
-
"darwin-x64": "sha512-
|
|
162
|
-
"linux-arm64v8": "sha512-
|
|
163
|
-
"linux-armv6": "sha512-
|
|
164
|
-
"linux-armv7": "sha512-
|
|
165
|
-
"linux-x64": "sha512-
|
|
166
|
-
"linuxmusl-arm64v8": "sha512-
|
|
167
|
-
"linuxmusl-x64": "sha512
|
|
168
|
-
"win32-arm64v8": "sha512-
|
|
169
|
-
"win32-ia32": "sha512-
|
|
170
|
-
"win32-x64": "sha512-
|
|
160
|
+
"darwin-arm64v8": "sha512-xFgYt7CtQSZcWoyUdzPTDNHbioZIrZSEU+gkMxzH4Cgjhi4/N49UsonnIZhKQoTBGloAqEexHeMx4rYTQ2Kgvw==",
|
|
161
|
+
"darwin-x64": "sha512-6SivWKzu15aUMMohe0wg7sNYMPETVnOe40BuWsnKOgzl3o5FpQqNSgs+68Mi8Za3Qti9/DaR+H/fyD0x48Af2w==",
|
|
162
|
+
"linux-arm64v8": "sha512-b+iI9V/ehgDabXYRQcvqa5CEysh+1FQsgFmYc358StCrJCDahwNmsQdsiH1GOVd5WaWh5wHUGByPwMmFOO16Aw==",
|
|
163
|
+
"linux-armv6": "sha512-zRP2F+EiustLE4bXSH8AHCxwfemh9d+QuvmPjira/HL6uJOUuA7SyQgVV1TPwTQle2ioCNnKPm7FEB/MAiT+ug==",
|
|
164
|
+
"linux-armv7": "sha512-6OCChowE5lBXXXAZrnGdA9dVktg7UdODEBpE5qTroiAJYZv4yXRMgyDFYajok7du2NTgoklhxGk8d9+4vGv5hg==",
|
|
165
|
+
"linux-x64": "sha512-OTmlmP2r8ozGKdB96X+K5oQE1ojVZanqLqqKlwDpEnfixyIaDGYbVzcjWBNGU3ai/26bvkaCkjynnc2ecYcsuA==",
|
|
166
|
+
"linuxmusl-arm64v8": "sha512-Qh5Wi+bkKTohFYHzSPssfjMhIkD6z6EHbVmnwmWSsgY9zsUBStFp6+mKcNTQfP5YM5Mz06vJOkLHX2OzEr5TzA==",
|
|
167
|
+
"linuxmusl-x64": "sha512-DwB4Fs3+ISw9etaLCANkueZDdk758iOS+wNp4TKZkHdq0al6B/3Pk7OHLR8a9E3H6wYDD328u++dcJzip5tacA==",
|
|
168
|
+
"win32-arm64v8": "sha512-96r3W+O4BtX602B1MtxU5Ru4lKzRRTZqM4OQEBJ//TNL3fiCZdd9agD+RQBjaeR4KFIyBSt3F7IE425ZWmxz+w==",
|
|
169
|
+
"win32-ia32": "sha512-qfN1MsfQGek1QQd1UNW7JT+5K5Ne1suFQ2GpgpYm3JLSpIve/tz2vOGEGzvTVssOBADJvAkTDFt+yIi3PgU9pA==",
|
|
170
|
+
"win32-x64": "sha512-eb3aAmjbVVBVRbiYgebQwoxkAt69WI8nwmKlilSQ3kWqoc0pXfIe322rF2UR8ebbISCGvYRUfzD2r1k92RXISQ=="
|
|
171
171
|
},
|
|
172
172
|
"runtime": "napi",
|
|
173
173
|
"target": 7
|
package/src/common.cc
CHANGED
|
@@ -76,6 +76,14 @@ namespace sharp {
|
|
|
76
76
|
}
|
|
77
77
|
return vector;
|
|
78
78
|
}
|
|
79
|
+
Napi::Buffer<char> NewOrCopyBuffer(Napi::Env env, char* data, size_t len) {
|
|
80
|
+
try {
|
|
81
|
+
return Napi::Buffer<char>::New(env, data, len, FreeCallback);
|
|
82
|
+
} catch (Napi::Error const &err) {}
|
|
83
|
+
Napi::Buffer<char> buf = Napi::Buffer<char>::Copy(env, data, len);
|
|
84
|
+
FreeCallback(nullptr, data);
|
|
85
|
+
return buf;
|
|
86
|
+
}
|
|
79
87
|
|
|
80
88
|
// Create an InputDescriptor instance from a Napi::Object describing an input image
|
|
81
89
|
InputDescriptor* CreateInputDescriptor(Napi::Object input) {
|
|
@@ -207,6 +215,9 @@ namespace sharp {
|
|
|
207
215
|
bool IsAvif(std::string const &str) {
|
|
208
216
|
return EndsWith(str, ".avif") || EndsWith(str, ".AVIF");
|
|
209
217
|
}
|
|
218
|
+
bool IsJxl(std::string const &str) {
|
|
219
|
+
return EndsWith(str, ".jxl") || EndsWith(str, ".JXL");
|
|
220
|
+
}
|
|
210
221
|
bool IsDz(std::string const &str) {
|
|
211
222
|
return EndsWith(str, ".dzi") || EndsWith(str, ".DZI");
|
|
212
223
|
}
|
|
@@ -237,6 +248,7 @@ namespace sharp {
|
|
|
237
248
|
case ImageType::PPM: id = "ppm"; break;
|
|
238
249
|
case ImageType::FITS: id = "fits"; break;
|
|
239
250
|
case ImageType::EXR: id = "exr"; break;
|
|
251
|
+
case ImageType::JXL: id = "jxl"; break;
|
|
240
252
|
case ImageType::VIPS: id = "vips"; break;
|
|
241
253
|
case ImageType::RAW: id = "raw"; break;
|
|
242
254
|
case ImageType::UNKNOWN: id = "unknown"; break;
|
|
@@ -281,6 +293,8 @@ namespace sharp {
|
|
|
281
293
|
{ "VipsForeignLoadPpmFile", ImageType::PPM },
|
|
282
294
|
{ "VipsForeignLoadFitsFile", ImageType::FITS },
|
|
283
295
|
{ "VipsForeignLoadOpenexr", ImageType::EXR },
|
|
296
|
+
{ "VipsForeignLoadJxlFile", ImageType::JXL },
|
|
297
|
+
{ "VipsForeignLoadJxlBuffer", ImageType::JXL },
|
|
284
298
|
{ "VipsForeignLoadVips", ImageType::VIPS },
|
|
285
299
|
{ "VipsForeignLoadVipsFile", ImageType::VIPS },
|
|
286
300
|
{ "VipsForeignLoadRaw", ImageType::RAW }
|
|
@@ -913,7 +927,7 @@ namespace sharp {
|
|
|
913
927
|
// Add non-transparent alpha channel, if required
|
|
914
928
|
if (colour[3] < 255.0 && !HasAlpha(image)) {
|
|
915
929
|
image = image.bandjoin(
|
|
916
|
-
VImage::new_matrix(image.width(), image.height()).new_from_image(255 * multiplier));
|
|
930
|
+
VImage::new_matrix(image.width(), image.height()).new_from_image(255 * multiplier).cast(image.format()));
|
|
917
931
|
}
|
|
918
932
|
return std::make_tuple(image, alphaColour);
|
|
919
933
|
}
|
package/src/common.h
CHANGED
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
|
|
27
27
|
#if (VIPS_MAJOR_VERSION < 8) || \
|
|
28
28
|
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 13) || \
|
|
29
|
-
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 13 && VIPS_MICRO_VERSION <
|
|
30
|
-
#error "libvips version 8.13.
|
|
29
|
+
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 13 && VIPS_MICRO_VERSION < 3)
|
|
30
|
+
#error "libvips version 8.13.3+ is required - please see https://sharp.pixelplumbing.com/install"
|
|
31
31
|
#endif
|
|
32
32
|
|
|
33
33
|
#if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6)))
|
|
@@ -133,6 +133,7 @@ namespace sharp {
|
|
|
133
133
|
return static_cast<T>(
|
|
134
134
|
vips_enum_from_nick(nullptr, type, AttrAsStr(obj, attr).data()));
|
|
135
135
|
}
|
|
136
|
+
Napi::Buffer<char> NewOrCopyBuffer(Napi::Env env, char* data, size_t len);
|
|
136
137
|
|
|
137
138
|
// Create an InputDescriptor instance from a Napi::Object describing an input image
|
|
138
139
|
InputDescriptor* CreateInputDescriptor(Napi::Object input);
|
|
@@ -152,6 +153,7 @@ namespace sharp {
|
|
|
152
153
|
PPM,
|
|
153
154
|
FITS,
|
|
154
155
|
EXR,
|
|
156
|
+
JXL,
|
|
155
157
|
VIPS,
|
|
156
158
|
RAW,
|
|
157
159
|
UNKNOWN,
|
|
@@ -182,6 +184,7 @@ namespace sharp {
|
|
|
182
184
|
bool IsHeic(std::string const &str);
|
|
183
185
|
bool IsHeif(std::string const &str);
|
|
184
186
|
bool IsAvif(std::string const &str);
|
|
187
|
+
bool IsJxl(std::string const &str);
|
|
185
188
|
bool IsDz(std::string const &str);
|
|
186
189
|
bool IsDzZip(std::string const &str);
|
|
187
190
|
bool IsV(std::string const &str);
|
package/src/metadata.cc
CHANGED
|
@@ -235,20 +235,20 @@ class MetadataWorker : public Napi::AsyncWorker {
|
|
|
235
235
|
info.Set("orientation", baton->orientation);
|
|
236
236
|
}
|
|
237
237
|
if (baton->exifLength > 0) {
|
|
238
|
-
info.Set("exif",
|
|
238
|
+
info.Set("exif", sharp::NewOrCopyBuffer(env, baton->exif, baton->exifLength));
|
|
239
239
|
}
|
|
240
240
|
if (baton->iccLength > 0) {
|
|
241
|
-
info.Set("icc",
|
|
241
|
+
info.Set("icc", sharp::NewOrCopyBuffer(env, baton->icc, baton->iccLength));
|
|
242
242
|
}
|
|
243
243
|
if (baton->iptcLength > 0) {
|
|
244
|
-
info.Set("iptc",
|
|
244
|
+
info.Set("iptc", sharp::NewOrCopyBuffer(env, baton->iptc, baton->iptcLength));
|
|
245
245
|
}
|
|
246
246
|
if (baton->xmpLength > 0) {
|
|
247
|
-
info.Set("xmp",
|
|
247
|
+
info.Set("xmp", sharp::NewOrCopyBuffer(env, baton->xmp, baton->xmpLength));
|
|
248
248
|
}
|
|
249
249
|
if (baton->tifftagPhotoshopLength > 0) {
|
|
250
250
|
info.Set("tifftagPhotoshop",
|
|
251
|
-
|
|
251
|
+
sharp::NewOrCopyBuffer(env, baton->tifftagPhotoshop, baton->tifftagPhotoshopLength));
|
|
252
252
|
}
|
|
253
253
|
Callback().MakeCallback(Receiver().Value(), { env.Null(), info });
|
|
254
254
|
} else {
|
package/src/operations.cc
CHANGED
|
@@ -345,9 +345,9 @@ namespace sharp {
|
|
|
345
345
|
if (HasAlpha(image) && a.size() != bands && (a.size() == 1 || a.size() == bands - 1 || bands - 1 == 1)) {
|
|
346
346
|
// Separate alpha channel
|
|
347
347
|
VImage alpha = image[bands - 1];
|
|
348
|
-
return RemoveAlpha(image).linear(a, b).bandjoin(alpha);
|
|
348
|
+
return RemoveAlpha(image).linear(a, b, VImage::option()->set("uchar", TRUE)).bandjoin(alpha);
|
|
349
349
|
} else {
|
|
350
|
-
return image.linear(a, b);
|
|
350
|
+
return image.linear(a, b, VImage::option()->set("uchar", TRUE));
|
|
351
351
|
}
|
|
352
352
|
}
|
|
353
353
|
|
package/src/pipeline.cc
CHANGED
|
@@ -81,35 +81,47 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
81
81
|
int pageHeight = sharp::GetPageHeight(image);
|
|
82
82
|
|
|
83
83
|
// Calculate angle of rotation
|
|
84
|
-
VipsAngle rotation;
|
|
85
|
-
|
|
86
|
-
bool
|
|
84
|
+
VipsAngle rotation = VIPS_ANGLE_D0;
|
|
85
|
+
VipsAngle autoRotation = VIPS_ANGLE_D0;
|
|
86
|
+
bool autoFlip = FALSE;
|
|
87
|
+
bool autoFlop = FALSE;
|
|
87
88
|
if (baton->useExifOrientation) {
|
|
88
89
|
// Rotate and flip image according to Exif orientation
|
|
89
|
-
std::tie(
|
|
90
|
+
std::tie(autoRotation, autoFlip, autoFlop) = CalculateExifRotationAndFlip(sharp::ExifOrientation(image));
|
|
91
|
+
image = sharp::RemoveExifOrientation(image);
|
|
90
92
|
} else {
|
|
91
93
|
rotation = CalculateAngleRotation(baton->angle);
|
|
92
94
|
}
|
|
93
95
|
|
|
94
96
|
// Rotate pre-extract
|
|
95
97
|
bool const shouldRotateBefore = baton->rotateBeforePreExtract &&
|
|
96
|
-
(rotation != VIPS_ANGLE_D0 ||
|
|
98
|
+
(rotation != VIPS_ANGLE_D0 || autoRotation != VIPS_ANGLE_D0 ||
|
|
99
|
+
autoFlip || baton->flip || autoFlop || baton->flop ||
|
|
100
|
+
baton->rotationAngle != 0.0);
|
|
97
101
|
|
|
98
102
|
if (shouldRotateBefore) {
|
|
99
|
-
if (
|
|
100
|
-
image = image.rot(
|
|
103
|
+
if (autoRotation != VIPS_ANGLE_D0) {
|
|
104
|
+
image = image.rot(autoRotation);
|
|
105
|
+
autoRotation = VIPS_ANGLE_D0;
|
|
101
106
|
}
|
|
102
|
-
if (
|
|
107
|
+
if (autoFlip) {
|
|
108
|
+
image = image.flip(VIPS_DIRECTION_VERTICAL);
|
|
109
|
+
autoFlip = FALSE;
|
|
110
|
+
} else if (baton->flip) {
|
|
103
111
|
image = image.flip(VIPS_DIRECTION_VERTICAL);
|
|
112
|
+
baton->flip = FALSE;
|
|
104
113
|
}
|
|
105
|
-
if (
|
|
114
|
+
if (autoFlop) {
|
|
106
115
|
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
|
|
116
|
+
autoFlop = FALSE;
|
|
117
|
+
} else if (baton->flop) {
|
|
118
|
+
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
|
|
119
|
+
baton->flop = FALSE;
|
|
107
120
|
}
|
|
108
|
-
if (rotation != VIPS_ANGLE_D0
|
|
109
|
-
image =
|
|
121
|
+
if (rotation != VIPS_ANGLE_D0) {
|
|
122
|
+
image = image.rot(rotation);
|
|
123
|
+
rotation = VIPS_ANGLE_D0;
|
|
110
124
|
}
|
|
111
|
-
flop = FALSE;
|
|
112
|
-
flip = FALSE;
|
|
113
125
|
if (baton->rotationAngle != 0.0) {
|
|
114
126
|
MultiPageUnsupported(nPages, "Rotate");
|
|
115
127
|
std::vector<double> background;
|
|
@@ -150,7 +162,9 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
150
162
|
int targetResizeHeight = baton->height;
|
|
151
163
|
|
|
152
164
|
// Swap input output width and height when rotating by 90 or 270 degrees
|
|
153
|
-
bool swap = !baton->rotateBeforePreExtract &&
|
|
165
|
+
bool swap = !baton->rotateBeforePreExtract &&
|
|
166
|
+
(rotation == VIPS_ANGLE_D90 || rotation == VIPS_ANGLE_D270 ||
|
|
167
|
+
autoRotation == VIPS_ANGLE_D90 || autoRotation == VIPS_ANGLE_D270);
|
|
154
168
|
|
|
155
169
|
// Shrink to pageHeight, so we work for multi-page images
|
|
156
170
|
std::tie(hshrink, vshrink) = sharp::ResolveShrink(
|
|
@@ -367,30 +381,21 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
367
381
|
->set("kernel", baton->kernel));
|
|
368
382
|
}
|
|
369
383
|
|
|
384
|
+
// Auto-rotate post-extract
|
|
385
|
+
if (autoRotation != VIPS_ANGLE_D0) {
|
|
386
|
+
image = image.rot(autoRotation);
|
|
387
|
+
}
|
|
370
388
|
// Flip (mirror about Y axis)
|
|
371
|
-
if (baton->flip ||
|
|
389
|
+
if (baton->flip || autoFlip) {
|
|
372
390
|
image = image.flip(VIPS_DIRECTION_VERTICAL);
|
|
373
|
-
image = sharp::RemoveExifOrientation(image);
|
|
374
391
|
}
|
|
375
|
-
|
|
376
392
|
// Flop (mirror about X axis)
|
|
377
|
-
if (baton->flop ||
|
|
393
|
+
if (baton->flop || autoFlop) {
|
|
378
394
|
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
|
|
379
|
-
image = sharp::RemoveExifOrientation(image);
|
|
380
395
|
}
|
|
381
|
-
|
|
382
396
|
// Rotate post-extract 90-angle
|
|
383
|
-
if (
|
|
397
|
+
if (rotation != VIPS_ANGLE_D0) {
|
|
384
398
|
image = image.rot(rotation);
|
|
385
|
-
if (flip) {
|
|
386
|
-
image = image.flip(VIPS_DIRECTION_VERTICAL);
|
|
387
|
-
flip = FALSE;
|
|
388
|
-
}
|
|
389
|
-
if (flop) {
|
|
390
|
-
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
|
|
391
|
-
flop = FALSE;
|
|
392
|
-
}
|
|
393
|
-
image = sharp::RemoveExifOrientation(image);
|
|
394
399
|
}
|
|
395
400
|
|
|
396
401
|
// Join additional color channels to the image
|
|
@@ -696,24 +701,6 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
696
701
|
image = sharp::Tint(image, baton->tintA, baton->tintB);
|
|
697
702
|
}
|
|
698
703
|
|
|
699
|
-
// Extract an image channel (aka vips band)
|
|
700
|
-
if (baton->extractChannel > -1) {
|
|
701
|
-
if (baton->extractChannel >= image.bands()) {
|
|
702
|
-
if (baton->extractChannel == 3 && sharp::HasAlpha(image)) {
|
|
703
|
-
baton->extractChannel = image.bands() - 1;
|
|
704
|
-
} else {
|
|
705
|
-
(baton->err).append("Cannot extract channel from image. Too few channels in image.");
|
|
706
|
-
return Error();
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
VipsInterpretation const interpretation = sharp::Is16Bit(image.interpretation())
|
|
710
|
-
? VIPS_INTERPRETATION_GREY16
|
|
711
|
-
: VIPS_INTERPRETATION_B_W;
|
|
712
|
-
image = image
|
|
713
|
-
.extract_band(baton->extractChannel)
|
|
714
|
-
.copy(VImage::option()->set("interpretation", interpretation));
|
|
715
|
-
}
|
|
716
|
-
|
|
717
704
|
// Remove alpha channel, if any
|
|
718
705
|
if (baton->removeAlpha) {
|
|
719
706
|
image = sharp::RemoveAlpha(image);
|
|
@@ -739,6 +726,26 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
739
726
|
}
|
|
740
727
|
}
|
|
741
728
|
|
|
729
|
+
// Extract channel
|
|
730
|
+
if (baton->extractChannel > -1) {
|
|
731
|
+
if (baton->extractChannel >= image.bands()) {
|
|
732
|
+
if (baton->extractChannel == 3 && sharp::HasAlpha(image)) {
|
|
733
|
+
baton->extractChannel = image.bands() - 1;
|
|
734
|
+
} else {
|
|
735
|
+
(baton->err)
|
|
736
|
+
.append("Cannot extract channel ").append(std::to_string(baton->extractChannel))
|
|
737
|
+
.append(" from image with channels 0-").append(std::to_string(image.bands() - 1));
|
|
738
|
+
return Error();
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
VipsInterpretation colourspace = sharp::Is16Bit(image.interpretation())
|
|
742
|
+
? VIPS_INTERPRETATION_GREY16
|
|
743
|
+
: VIPS_INTERPRETATION_B_W;
|
|
744
|
+
image = image
|
|
745
|
+
.extract_band(baton->extractChannel)
|
|
746
|
+
.copy(VImage::option()->set("interpretation", colourspace));
|
|
747
|
+
}
|
|
748
|
+
|
|
742
749
|
// Apply output ICC profile
|
|
743
750
|
if (!baton->withMetadataIcc.empty()) {
|
|
744
751
|
image = image.icc_transform(
|
|
@@ -864,6 +871,8 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
864
871
|
->set("bitdepth", baton->gifBitdepth)
|
|
865
872
|
->set("effort", baton->gifEffort)
|
|
866
873
|
->set("reoptimise", baton->gifReoptimise)
|
|
874
|
+
->set("interframe_maxerror", baton->gifInterFrameMaxError)
|
|
875
|
+
->set("interpalette_maxerror", baton->gifInterPaletteMaxError)
|
|
867
876
|
->set("dither", baton->gifDither)));
|
|
868
877
|
baton->bufferOut = static_cast<char*>(area->data);
|
|
869
878
|
baton->bufferOutLength = area->length;
|
|
@@ -930,6 +939,21 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
930
939
|
area->free_fn = nullptr;
|
|
931
940
|
vips_area_unref(area);
|
|
932
941
|
baton->formatOut = "dz";
|
|
942
|
+
} else if (baton->formatOut == "jxl" ||
|
|
943
|
+
(baton->formatOut == "input" && inputImageType == sharp::ImageType::JXL)) {
|
|
944
|
+
// Write JXL to buffer
|
|
945
|
+
image = sharp::RemoveAnimationProperties(image);
|
|
946
|
+
VipsArea *area = reinterpret_cast<VipsArea*>(image.jxlsave_buffer(VImage::option()
|
|
947
|
+
->set("strip", !baton->withMetadata)
|
|
948
|
+
->set("distance", baton->jxlDistance)
|
|
949
|
+
->set("tier", baton->jxlDecodingTier)
|
|
950
|
+
->set("effort", baton->jxlEffort)
|
|
951
|
+
->set("lossless", baton->jxlLossless)));
|
|
952
|
+
baton->bufferOut = static_cast<char*>(area->data);
|
|
953
|
+
baton->bufferOutLength = area->length;
|
|
954
|
+
area->free_fn = nullptr;
|
|
955
|
+
vips_area_unref(area);
|
|
956
|
+
baton->formatOut = "jxl";
|
|
933
957
|
} else if (baton->formatOut == "raw" ||
|
|
934
958
|
(baton->formatOut == "input" && inputImageType == sharp::ImageType::RAW)) {
|
|
935
959
|
// Write raw, uncompressed image data to buffer
|
|
@@ -968,6 +992,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
968
992
|
bool const isTiff = sharp::IsTiff(baton->fileOut);
|
|
969
993
|
bool const isJp2 = sharp::IsJp2(baton->fileOut);
|
|
970
994
|
bool const isHeif = sharp::IsHeif(baton->fileOut);
|
|
995
|
+
bool const isJxl = sharp::IsJxl(baton->fileOut);
|
|
971
996
|
bool const isDz = sharp::IsDz(baton->fileOut);
|
|
972
997
|
bool const isDzZip = sharp::IsDzZip(baton->fileOut);
|
|
973
998
|
bool const isV = sharp::IsV(baton->fileOut);
|
|
@@ -1085,6 +1110,17 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1085
1110
|
? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
|
|
1086
1111
|
->set("lossless", baton->heifLossless));
|
|
1087
1112
|
baton->formatOut = "heif";
|
|
1113
|
+
} else if (baton->formatOut == "jxl" || (mightMatchInput && isJxl) ||
|
|
1114
|
+
(willMatchInput && inputImageType == sharp::ImageType::JXL)) {
|
|
1115
|
+
// Write JXL to file
|
|
1116
|
+
image = sharp::RemoveAnimationProperties(image);
|
|
1117
|
+
image.jxlsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
|
1118
|
+
->set("strip", !baton->withMetadata)
|
|
1119
|
+
->set("distance", baton->jxlDistance)
|
|
1120
|
+
->set("tier", baton->jxlDecodingTier)
|
|
1121
|
+
->set("effort", baton->jxlEffort)
|
|
1122
|
+
->set("lossless", baton->jxlLossless));
|
|
1123
|
+
baton->formatOut = "jxl";
|
|
1088
1124
|
} else if (baton->formatOut == "dz" || isDz || isDzZip) {
|
|
1089
1125
|
// Write DZ to file
|
|
1090
1126
|
if (isDzZip) {
|
|
@@ -1170,8 +1206,8 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1170
1206
|
// Add buffer size to info
|
|
1171
1207
|
info.Set("size", static_cast<uint32_t>(baton->bufferOutLength));
|
|
1172
1208
|
// Pass ownership of output data to Buffer instance
|
|
1173
|
-
Napi::Buffer<char> data =
|
|
1174
|
-
baton->bufferOutLength
|
|
1209
|
+
Napi::Buffer<char> data = sharp::NewOrCopyBuffer(env, static_cast<char*>(baton->bufferOut),
|
|
1210
|
+
baton->bufferOutLength);
|
|
1175
1211
|
Callback().MakeCallback(Receiver().Value(), { env.Null(), data, info });
|
|
1176
1212
|
} else {
|
|
1177
1213
|
// Add file size to info
|
|
@@ -1544,6 +1580,8 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1544
1580
|
baton->gifBitdepth = sharp::AttrAsUint32(options, "gifBitdepth");
|
|
1545
1581
|
baton->gifEffort = sharp::AttrAsUint32(options, "gifEffort");
|
|
1546
1582
|
baton->gifDither = sharp::AttrAsDouble(options, "gifDither");
|
|
1583
|
+
baton->gifInterFrameMaxError = sharp::AttrAsDouble(options, "gifInterFrameMaxError");
|
|
1584
|
+
baton->gifInterPaletteMaxError = sharp::AttrAsDouble(options, "gifInterPaletteMaxError");
|
|
1547
1585
|
baton->gifReoptimise = sharp::AttrAsBool(options, "gifReoptimise");
|
|
1548
1586
|
baton->tiffQuality = sharp::AttrAsUint32(options, "tiffQuality");
|
|
1549
1587
|
baton->tiffPyramid = sharp::AttrAsBool(options, "tiffPyramid");
|
|
@@ -1568,6 +1606,10 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1568
1606
|
options, "heifCompression", VIPS_TYPE_FOREIGN_HEIF_COMPRESSION);
|
|
1569
1607
|
baton->heifEffort = sharp::AttrAsUint32(options, "heifEffort");
|
|
1570
1608
|
baton->heifChromaSubsampling = sharp::AttrAsStr(options, "heifChromaSubsampling");
|
|
1609
|
+
baton->jxlDistance = sharp::AttrAsDouble(options, "jxlDistance");
|
|
1610
|
+
baton->jxlDecodingTier = sharp::AttrAsUint32(options, "jxlDecodingTier");
|
|
1611
|
+
baton->jxlEffort = sharp::AttrAsUint32(options, "jxlEffort");
|
|
1612
|
+
baton->jxlLossless = sharp::AttrAsBool(options, "jxlLossless");
|
|
1571
1613
|
baton->rawDepth = sharp::AttrAsEnum<VipsBandFormat>(options, "rawDepth", VIPS_TYPE_BAND_FORMAT);
|
|
1572
1614
|
// Animated output properties
|
|
1573
1615
|
if (sharp::HasAttr(options, "loop")) {
|
package/src/pipeline.h
CHANGED
|
@@ -163,6 +163,8 @@ struct PipelineBaton {
|
|
|
163
163
|
int gifBitdepth;
|
|
164
164
|
int gifEffort;
|
|
165
165
|
double gifDither;
|
|
166
|
+
double gifInterFrameMaxError;
|
|
167
|
+
double gifInterPaletteMaxError;
|
|
166
168
|
bool gifReoptimise;
|
|
167
169
|
int tiffQuality;
|
|
168
170
|
VipsForeignTiffCompression tiffCompression;
|
|
@@ -180,6 +182,10 @@ struct PipelineBaton {
|
|
|
180
182
|
int heifEffort;
|
|
181
183
|
std::string heifChromaSubsampling;
|
|
182
184
|
bool heifLossless;
|
|
185
|
+
double jxlDistance;
|
|
186
|
+
int jxlDecodingTier;
|
|
187
|
+
int jxlEffort;
|
|
188
|
+
bool jxlLossless;
|
|
183
189
|
VipsBandFormat rawDepth;
|
|
184
190
|
std::string err;
|
|
185
191
|
bool withMetadata;
|
|
@@ -314,6 +320,8 @@ struct PipelineBaton {
|
|
|
314
320
|
gifBitdepth(8),
|
|
315
321
|
gifEffort(7),
|
|
316
322
|
gifDither(1.0),
|
|
323
|
+
gifInterFrameMaxError(0.0),
|
|
324
|
+
gifInterPaletteMaxError(3.0),
|
|
317
325
|
gifReoptimise(false),
|
|
318
326
|
tiffQuality(80),
|
|
319
327
|
tiffCompression(VIPS_FOREIGN_TIFF_COMPRESSION_JPEG),
|
|
@@ -331,6 +339,10 @@ struct PipelineBaton {
|
|
|
331
339
|
heifEffort(4),
|
|
332
340
|
heifChromaSubsampling("4:4:4"),
|
|
333
341
|
heifLossless(false),
|
|
342
|
+
jxlDistance(1.0),
|
|
343
|
+
jxlDecodingTier(0),
|
|
344
|
+
jxlEffort(7),
|
|
345
|
+
jxlLossless(false),
|
|
334
346
|
rawDepth(VIPS_FORMAT_UCHAR),
|
|
335
347
|
withMetadata(false),
|
|
336
348
|
withMetadataOrientation(-1),
|
package/src/stats.cc
CHANGED
|
@@ -176,6 +176,7 @@ Napi::Value stats(const Napi::CallbackInfo& info) {
|
|
|
176
176
|
|
|
177
177
|
// Input
|
|
178
178
|
baton->input = sharp::CreateInputDescriptor(options.Get("input").As<Napi::Object>());
|
|
179
|
+
baton->input->access = VIPS_ACCESS_RANDOM;
|
|
179
180
|
|
|
180
181
|
// Function to notify of libvips warnings
|
|
181
182
|
Napi::Function debuglog = options.Get("debuglog").As<Napi::Function>();
|
package/src/utilities.cc
CHANGED
|
@@ -115,7 +115,7 @@ Napi::Value format(const Napi::CallbackInfo& info) {
|
|
|
115
115
|
Napi::Object format = Napi::Object::New(env);
|
|
116
116
|
for (std::string const f : {
|
|
117
117
|
"jpeg", "png", "webp", "tiff", "magick", "openslide", "dz",
|
|
118
|
-
"ppm", "fits", "gif", "svg", "heif", "pdf", "vips", "jp2k"
|
|
118
|
+
"ppm", "fits", "gif", "svg", "heif", "pdf", "vips", "jp2k", "jxl"
|
|
119
119
|
}) {
|
|
120
120
|
// Input
|
|
121
121
|
const VipsObjectClass *oc = vips_class_find("VipsOperation", (f + "load").c_str());
|