sharp 0.29.0 → 0.29.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/lib/constructor.js +7 -1
- package/lib/operation.js +17 -2
- package/lib/output.js +96 -7
- package/package.json +6 -4
- package/src/common.cc +36 -10
- package/src/common.h +10 -2
- package/src/operations.cc +4 -3
- package/src/operations.h +3 -2
- package/src/pipeline.cc +59 -18
- package/src/pipeline.h +14 -2
- package/src/utilities.cc +1 -1
package/lib/constructor.js
CHANGED
|
@@ -199,6 +199,7 @@ const Sharp = function (input, options) {
|
|
|
199
199
|
brightness: 1,
|
|
200
200
|
saturation: 1,
|
|
201
201
|
hue: 0,
|
|
202
|
+
lightness: 0,
|
|
202
203
|
booleanBufferIn: null,
|
|
203
204
|
booleanFileIn: '',
|
|
204
205
|
joinChannelIn: [],
|
|
@@ -232,8 +233,13 @@ const Sharp = function (input, options) {
|
|
|
232
233
|
pngAdaptiveFiltering: false,
|
|
233
234
|
pngPalette: false,
|
|
234
235
|
pngQuality: 100,
|
|
235
|
-
|
|
236
|
+
pngBitdepth: 8,
|
|
236
237
|
pngDither: 1,
|
|
238
|
+
jp2Quality: 80,
|
|
239
|
+
jp2TileHeight: 512,
|
|
240
|
+
jp2TileWidth: 512,
|
|
241
|
+
jp2Lossless: false,
|
|
242
|
+
jp2ChromaSubsampling: '4:4:4',
|
|
237
243
|
webpQuality: 80,
|
|
238
244
|
webpAlphaQuality: 100,
|
|
239
245
|
webpLossless: false,
|
package/lib/operation.js
CHANGED
|
@@ -570,14 +570,16 @@ function recomb (inputMatrix) {
|
|
|
570
570
|
}
|
|
571
571
|
|
|
572
572
|
/**
|
|
573
|
-
* Transforms the image using brightness, saturation
|
|
573
|
+
* Transforms the image using brightness, saturation, hue rotation, and lightness.
|
|
574
|
+
* Brightness and lightness both operate on luminance, with the difference being that
|
|
575
|
+
* brightness is multiplicative whereas lightness is additive.
|
|
574
576
|
*
|
|
575
577
|
* @since 0.22.1
|
|
576
578
|
*
|
|
577
579
|
* @example
|
|
578
580
|
* sharp(input)
|
|
579
581
|
* .modulate({
|
|
580
|
-
* brightness: 2 // increase
|
|
582
|
+
* brightness: 2 // increase brightness by a factor of 2
|
|
581
583
|
* });
|
|
582
584
|
*
|
|
583
585
|
* sharp(input)
|
|
@@ -585,6 +587,11 @@ function recomb (inputMatrix) {
|
|
|
585
587
|
* hue: 180 // hue-rotate by 180 degrees
|
|
586
588
|
* });
|
|
587
589
|
*
|
|
590
|
+
* sharp(input)
|
|
591
|
+
* .modulate({
|
|
592
|
+
* lightness: 50 // increase lightness by +50
|
|
593
|
+
* });
|
|
594
|
+
*
|
|
588
595
|
* // decreate brightness and saturation while also hue-rotating by 90 degrees
|
|
589
596
|
* sharp(input)
|
|
590
597
|
* .modulate({
|
|
@@ -597,6 +604,7 @@ function recomb (inputMatrix) {
|
|
|
597
604
|
* @param {number} [options.brightness] Brightness multiplier
|
|
598
605
|
* @param {number} [options.saturation] Saturation multiplier
|
|
599
606
|
* @param {number} [options.hue] Degrees for hue rotation
|
|
607
|
+
* @param {number} [options.lightness] Lightness addend
|
|
600
608
|
* @returns {Sharp}
|
|
601
609
|
*/
|
|
602
610
|
function modulate (options) {
|
|
@@ -624,6 +632,13 @@ function modulate (options) {
|
|
|
624
632
|
throw is.invalidParameterError('hue', 'number', options.hue);
|
|
625
633
|
}
|
|
626
634
|
}
|
|
635
|
+
if ('lightness' in options) {
|
|
636
|
+
if (is.number(options.lightness)) {
|
|
637
|
+
this.options.lightness = options.lightness;
|
|
638
|
+
} else {
|
|
639
|
+
throw is.invalidParameterError('lightness', 'number', options.lightness);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
627
642
|
return this;
|
|
628
643
|
}
|
|
629
644
|
|
package/lib/output.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const path = require('path');
|
|
3
4
|
const is = require('./is');
|
|
4
5
|
const sharp = require('./sharp');
|
|
5
6
|
|
|
@@ -13,10 +14,15 @@ const formats = new Map([
|
|
|
13
14
|
['raw', 'raw'],
|
|
14
15
|
['tiff', 'tiff'],
|
|
15
16
|
['webp', 'webp'],
|
|
16
|
-
['gif', 'gif']
|
|
17
|
+
['gif', 'gif'],
|
|
18
|
+
['jp2', 'jp2'],
|
|
19
|
+
['jpx', 'jp2'],
|
|
20
|
+
['j2k', 'jp2'],
|
|
21
|
+
['j2c', 'jp2']
|
|
17
22
|
]);
|
|
18
23
|
|
|
19
24
|
const errMagickSave = new Error('GIF output requires libvips with support for ImageMagick');
|
|
25
|
+
const errJp2Save = new Error('JP2 output requires libvips with support for OpenJPEG');
|
|
20
26
|
|
|
21
27
|
/**
|
|
22
28
|
* Write output image data to a file.
|
|
@@ -28,6 +34,8 @@ const errMagickSave = new Error('GIF output requires libvips with support for Im
|
|
|
28
34
|
* By default all metadata will be removed, which includes EXIF-based orientation.
|
|
29
35
|
* See {@link withMetadata} for control over this.
|
|
30
36
|
*
|
|
37
|
+
* The caller is responsible for ensuring directory structures and permissions exist.
|
|
38
|
+
*
|
|
31
39
|
* A `Promise` is returned when `callback` is not provided.
|
|
32
40
|
*
|
|
33
41
|
* @example
|
|
@@ -52,7 +60,7 @@ function toFile (fileOut, callback) {
|
|
|
52
60
|
let err;
|
|
53
61
|
if (!is.string(fileOut)) {
|
|
54
62
|
err = new Error('Missing output file path');
|
|
55
|
-
} else if (this.options.input.file === fileOut) {
|
|
63
|
+
} else if (is.string(this.options.input.file) && path.resolve(this.options.input.file) === path.resolve(fileOut)) {
|
|
56
64
|
err = new Error('Cannot use same file for input and output');
|
|
57
65
|
} else if (this.options.formatOut === 'input' && fileOut.toLowerCase().endsWith('.gif') && !this.constructor.format.magick.output.file) {
|
|
58
66
|
err = errMagickSave;
|
|
@@ -403,7 +411,7 @@ function png (options) {
|
|
|
403
411
|
const colours = options.colours || options.colors;
|
|
404
412
|
if (is.defined(colours)) {
|
|
405
413
|
if (is.integer(colours) && is.inRange(colours, 2, 256)) {
|
|
406
|
-
this.options.
|
|
414
|
+
this.options.pngBitdepth = 1 << 31 - Math.clz32(Math.ceil(Math.log2(colours)));
|
|
407
415
|
} else {
|
|
408
416
|
throw is.invalidParameterError('colours', 'integer between 2 and 256', colours);
|
|
409
417
|
}
|
|
@@ -509,6 +517,84 @@ function gif (options) {
|
|
|
509
517
|
return this._updateFormatOut('gif', options);
|
|
510
518
|
}
|
|
511
519
|
|
|
520
|
+
/**
|
|
521
|
+
* Use these JP2 options for output image.
|
|
522
|
+
*
|
|
523
|
+
* Requires libvips compiled with support for OpenJPEG.
|
|
524
|
+
* The prebuilt binaries do not include this - see
|
|
525
|
+
* {@link https://sharp.pixelplumbing.com/install#custom-libvips installing a custom libvips}.
|
|
526
|
+
*
|
|
527
|
+
* @example
|
|
528
|
+
* // Convert any input to lossless JP2 output
|
|
529
|
+
* const data = await sharp(input)
|
|
530
|
+
* .jp2({ lossless: true })
|
|
531
|
+
* .toBuffer();
|
|
532
|
+
*
|
|
533
|
+
* @example
|
|
534
|
+
* // Convert any input to very high quality JP2 output
|
|
535
|
+
* const data = await sharp(input)
|
|
536
|
+
* .jp2({
|
|
537
|
+
* quality: 100,
|
|
538
|
+
* chromaSubsampling: '4:4:4'
|
|
539
|
+
* })
|
|
540
|
+
* .toBuffer();
|
|
541
|
+
*
|
|
542
|
+
* @since 0.29.1
|
|
543
|
+
*
|
|
544
|
+
* @param {Object} [options] - output options
|
|
545
|
+
* @param {number} [options.quality=80] - quality, integer 1-100
|
|
546
|
+
* @param {boolean} [options.lossless=false] - use lossless compression mode
|
|
547
|
+
* @param {number} [options.tileWidth=512] - horizontal tile size
|
|
548
|
+
* @param {number} [options.tileHeight=512] - vertical tile size
|
|
549
|
+
* @param {string} [options.chromaSubsampling='4:4:4'] - set to '4:2:0' to use chroma subsampling
|
|
550
|
+
* @returns {Sharp}
|
|
551
|
+
* @throws {Error} Invalid options
|
|
552
|
+
*/
|
|
553
|
+
/* istanbul ignore next */
|
|
554
|
+
function jp2 (options) {
|
|
555
|
+
if (!this.constructor.format.jp2k.output.buffer) {
|
|
556
|
+
throw errJp2Save;
|
|
557
|
+
}
|
|
558
|
+
if (is.object(options)) {
|
|
559
|
+
if (is.defined(options.quality)) {
|
|
560
|
+
if (is.integer(options.quality) && is.inRange(options.quality, 1, 100)) {
|
|
561
|
+
this.options.jp2Quality = options.quality;
|
|
562
|
+
} else {
|
|
563
|
+
throw is.invalidParameterError('quality', 'integer between 1 and 100', options.quality);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
if (is.defined(options.lossless)) {
|
|
567
|
+
if (is.bool(options.lossless)) {
|
|
568
|
+
this.options.jp2Lossless = options.lossless;
|
|
569
|
+
} else {
|
|
570
|
+
throw is.invalidParameterError('lossless', 'boolean', options.lossless);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
if (is.defined(options.tileWidth)) {
|
|
574
|
+
if (is.integer(options.tileWidth) && is.inRange(options.tileWidth, 1, 32768)) {
|
|
575
|
+
this.options.jp2TileWidth = options.tileWidth;
|
|
576
|
+
} else {
|
|
577
|
+
throw is.invalidParameterError('tileWidth', 'integer between 1 and 32768', options.tileWidth);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
if (is.defined(options.tileHeight)) {
|
|
581
|
+
if (is.integer(options.tileHeight) && is.inRange(options.tileHeight, 1, 32768)) {
|
|
582
|
+
this.options.jp2TileHeight = options.tileHeight;
|
|
583
|
+
} else {
|
|
584
|
+
throw is.invalidParameterError('tileHeight', 'integer between 1 and 32768', options.tileHeight);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
if (is.defined(options.chromaSubsampling)) {
|
|
588
|
+
if (is.string(options.chromaSubsampling) && is.inArray(options.chromaSubsampling, ['4:2:0', '4:4:4'])) {
|
|
589
|
+
this.options.heifChromaSubsampling = options.chromaSubsampling;
|
|
590
|
+
} else {
|
|
591
|
+
throw is.invalidParameterError('chromaSubsampling', 'one of: 4:2:0, 4:4:4', options.chromaSubsampling);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
return this._updateFormatOut('jp2', options);
|
|
596
|
+
}
|
|
597
|
+
|
|
512
598
|
/**
|
|
513
599
|
* Set animation options if available.
|
|
514
600
|
* @private
|
|
@@ -654,12 +740,14 @@ function tiff (options) {
|
|
|
654
740
|
* Whilst it is possible to create AVIF images smaller than 16x16 pixels,
|
|
655
741
|
* most web browsers do not display these properly.
|
|
656
742
|
*
|
|
743
|
+
* AVIF image sequences are not supported.
|
|
744
|
+
*
|
|
657
745
|
* @since 0.27.0
|
|
658
746
|
*
|
|
659
747
|
* @param {Object} [options] - output options
|
|
660
748
|
* @param {number} [options.quality=50] - quality, integer 1-100
|
|
661
749
|
* @param {boolean} [options.lossless=false] - use lossless compression
|
|
662
|
-
* @param {number} [options.speed=5] - CPU effort vs file size, 0 (slowest/smallest) to
|
|
750
|
+
* @param {number} [options.speed=5] - CPU effort vs file size, 0 (slowest/smallest) to 9 (fastest/largest)
|
|
663
751
|
* @param {string} [options.chromaSubsampling='4:4:4'] - set to '4:2:0' to use chroma subsampling
|
|
664
752
|
* @returns {Sharp}
|
|
665
753
|
* @throws {Error} Invalid options
|
|
@@ -680,7 +768,7 @@ function avif (options) {
|
|
|
680
768
|
* @param {number} [options.quality=50] - quality, integer 1-100
|
|
681
769
|
* @param {string} [options.compression='av1'] - compression format: av1, hevc
|
|
682
770
|
* @param {boolean} [options.lossless=false] - use lossless compression
|
|
683
|
-
* @param {number} [options.speed=5] - CPU effort vs file size, 0 (slowest/smallest) to
|
|
771
|
+
* @param {number} [options.speed=5] - CPU effort vs file size, 0 (slowest/smallest) to 9 (fastest/largest)
|
|
684
772
|
* @param {string} [options.chromaSubsampling='4:4:4'] - set to '4:2:0' to use chroma subsampling
|
|
685
773
|
* @returns {Sharp}
|
|
686
774
|
* @throws {Error} Invalid options
|
|
@@ -709,10 +797,10 @@ function heif (options) {
|
|
|
709
797
|
}
|
|
710
798
|
}
|
|
711
799
|
if (is.defined(options.speed)) {
|
|
712
|
-
if (is.integer(options.speed) && is.inRange(options.speed, 0,
|
|
800
|
+
if (is.integer(options.speed) && is.inRange(options.speed, 0, 9)) {
|
|
713
801
|
this.options.heifSpeed = options.speed;
|
|
714
802
|
} else {
|
|
715
|
-
throw is.invalidParameterError('speed', 'integer between 0 and
|
|
803
|
+
throw is.invalidParameterError('speed', 'integer between 0 and 9', options.speed);
|
|
716
804
|
}
|
|
717
805
|
}
|
|
718
806
|
if (is.defined(options.chromaSubsampling)) {
|
|
@@ -1031,6 +1119,7 @@ module.exports = function (Sharp) {
|
|
|
1031
1119
|
withMetadata,
|
|
1032
1120
|
toFormat,
|
|
1033
1121
|
jpeg,
|
|
1122
|
+
jp2,
|
|
1034
1123
|
png,
|
|
1035
1124
|
webp,
|
|
1036
1125
|
tiff,
|
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, AVIF and TIFF images",
|
|
4
|
-
"version": "0.29.
|
|
4
|
+
"version": "0.29.1",
|
|
5
5
|
"author": "Lovell Fuller <npm@lovell.info>",
|
|
6
6
|
"homepage": "https://github.com/lovell/sharp",
|
|
7
7
|
"contributors": [
|
|
@@ -78,7 +78,8 @@
|
|
|
78
78
|
"Jacob Smith <jacob@frende.me>",
|
|
79
79
|
"Michael Nutt <michael@nutt.im>",
|
|
80
80
|
"Brad Parham <baparham@gmail.com>",
|
|
81
|
-
"Taneli Vatanen <taneli.vatanen@gmail.com>"
|
|
81
|
+
"Taneli Vatanen <taneli.vatanen@gmail.com>",
|
|
82
|
+
"Joris Dugué <zaruike10@gmail.com>"
|
|
82
83
|
],
|
|
83
84
|
"scripts": {
|
|
84
85
|
"install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node install/can-compile && node-gyp rebuild && node install/dll-copy)",
|
|
@@ -112,6 +113,7 @@
|
|
|
112
113
|
"tiff",
|
|
113
114
|
"gif",
|
|
114
115
|
"svg",
|
|
116
|
+
"jp2",
|
|
115
117
|
"dzi",
|
|
116
118
|
"image",
|
|
117
119
|
"resize",
|
|
@@ -124,7 +126,7 @@
|
|
|
124
126
|
"dependencies": {
|
|
125
127
|
"color": "^4.0.1",
|
|
126
128
|
"detect-libc": "^1.0.3",
|
|
127
|
-
"node-addon-api": "^4.
|
|
129
|
+
"node-addon-api": "^4.1.0",
|
|
128
130
|
"prebuild-install": "^6.1.4",
|
|
129
131
|
"semver": "^7.3.5",
|
|
130
132
|
"simple-get": "^3.1.0",
|
|
@@ -139,7 +141,7 @@
|
|
|
139
141
|
"exif-reader": "^1.0.3",
|
|
140
142
|
"icc": "^2.0.0",
|
|
141
143
|
"license-checker": "^25.0.1",
|
|
142
|
-
"mocha": "^9.
|
|
144
|
+
"mocha": "^9.1.1",
|
|
143
145
|
"mock-fs": "^5.0.0",
|
|
144
146
|
"nyc": "^15.1.0",
|
|
145
147
|
"prebuild": "^10.0.1",
|
package/src/common.cc
CHANGED
|
@@ -157,6 +157,10 @@ namespace sharp {
|
|
|
157
157
|
bool IsGif(std::string const &str) {
|
|
158
158
|
return EndsWith(str, ".gif") || EndsWith(str, ".GIF");
|
|
159
159
|
}
|
|
160
|
+
bool IsJp2(std::string const &str) {
|
|
161
|
+
return EndsWith(str, ".jp2") || EndsWith(str, ".jpx") || EndsWith(str, ".j2k") || EndsWith(str, ".j2c")
|
|
162
|
+
|| EndsWith(str, ".JP2") || EndsWith(str, ".JPX") || EndsWith(str, ".J2K") || EndsWith(str, ".J2C");
|
|
163
|
+
}
|
|
160
164
|
bool IsTiff(std::string const &str) {
|
|
161
165
|
return EndsWith(str, ".tif") || EndsWith(str, ".tiff") || EndsWith(str, ".TIF") || EndsWith(str, ".TIFF");
|
|
162
166
|
}
|
|
@@ -190,6 +194,7 @@ namespace sharp {
|
|
|
190
194
|
case ImageType::WEBP: id = "webp"; break;
|
|
191
195
|
case ImageType::TIFF: id = "tiff"; break;
|
|
192
196
|
case ImageType::GIF: id = "gif"; break;
|
|
197
|
+
case ImageType::JP2: id = "jp2"; break;
|
|
193
198
|
case ImageType::SVG: id = "svg"; break;
|
|
194
199
|
case ImageType::HEIF: id = "heif"; break;
|
|
195
200
|
case ImageType::PDF: id = "pdf"; break;
|
|
@@ -226,6 +231,8 @@ namespace sharp {
|
|
|
226
231
|
{ "VipsForeignLoadGifBuffer", ImageType::GIF },
|
|
227
232
|
{ "VipsForeignLoadNsgifFile", ImageType::GIF },
|
|
228
233
|
{ "VipsForeignLoadNsgifBuffer", ImageType::GIF },
|
|
234
|
+
{ "VipsForeignLoadJp2kBuffer", ImageType::JP2 },
|
|
235
|
+
{ "VipsForeignLoadJp2kFile", ImageType::JP2 },
|
|
229
236
|
{ "VipsForeignLoadSvgFile", ImageType::SVG },
|
|
230
237
|
{ "VipsForeignLoadSvgBuffer", ImageType::SVG },
|
|
231
238
|
{ "VipsForeignLoadHeifFile", ImageType::HEIF },
|
|
@@ -234,6 +241,8 @@ namespace sharp {
|
|
|
234
241
|
{ "VipsForeignLoadPdfBuffer", ImageType::PDF },
|
|
235
242
|
{ "VipsForeignLoadMagickFile", ImageType::MAGICK },
|
|
236
243
|
{ "VipsForeignLoadMagickBuffer", ImageType::MAGICK },
|
|
244
|
+
{ "VipsForeignLoadMagick7File", ImageType::MAGICK },
|
|
245
|
+
{ "VipsForeignLoadMagick7Buffer", ImageType::MAGICK },
|
|
237
246
|
{ "VipsForeignLoadOpenslide", ImageType::OPENSLIDE },
|
|
238
247
|
{ "VipsForeignLoadPpmFile", ImageType::PPM },
|
|
239
248
|
{ "VipsForeignLoadFits", ImageType::FITS },
|
|
@@ -285,6 +294,7 @@ namespace sharp {
|
|
|
285
294
|
imageType == ImageType::WEBP ||
|
|
286
295
|
imageType == ImageType::MAGICK ||
|
|
287
296
|
imageType == ImageType::GIF ||
|
|
297
|
+
imageType == ImageType::JP2 ||
|
|
288
298
|
imageType == ImageType::TIFF ||
|
|
289
299
|
imageType == ImageType::HEIF ||
|
|
290
300
|
imageType == ImageType::PDF;
|
|
@@ -508,6 +518,17 @@ namespace sharp {
|
|
|
508
518
|
return copy;
|
|
509
519
|
}
|
|
510
520
|
|
|
521
|
+
/*
|
|
522
|
+
Remove animation properties from image.
|
|
523
|
+
*/
|
|
524
|
+
VImage RemoveAnimationProperties(VImage image) {
|
|
525
|
+
VImage copy = image.copy();
|
|
526
|
+
copy.remove(VIPS_META_PAGE_HEIGHT);
|
|
527
|
+
copy.remove("delay");
|
|
528
|
+
copy.remove("loop");
|
|
529
|
+
return copy;
|
|
530
|
+
}
|
|
531
|
+
|
|
511
532
|
/*
|
|
512
533
|
Does this image have a non-default density?
|
|
513
534
|
*/
|
|
@@ -757,23 +778,27 @@ namespace sharp {
|
|
|
757
778
|
/*
|
|
758
779
|
Convert RGBA value to another colourspace
|
|
759
780
|
*/
|
|
760
|
-
std::vector<double> GetRgbaAsColourspace(std::vector<double> const rgba,
|
|
781
|
+
std::vector<double> GetRgbaAsColourspace(std::vector<double> const rgba,
|
|
782
|
+
VipsInterpretation const interpretation, bool premultiply) {
|
|
761
783
|
int const bands = static_cast<int>(rgba.size());
|
|
762
|
-
if (bands < 3
|
|
784
|
+
if (bands < 3) {
|
|
763
785
|
return rgba;
|
|
764
|
-
} else {
|
|
765
|
-
VImage pixel = VImage::new_matrix(1, 1);
|
|
766
|
-
pixel.set("bands", bands);
|
|
767
|
-
pixel = pixel.new_from_image(rgba);
|
|
768
|
-
pixel = pixel.colourspace(interpretation, VImage::option()->set("source_space", VIPS_INTERPRETATION_sRGB));
|
|
769
|
-
return pixel(0, 0);
|
|
770
786
|
}
|
|
787
|
+
VImage pixel = VImage::new_matrix(1, 1);
|
|
788
|
+
pixel.set("bands", bands);
|
|
789
|
+
pixel = pixel
|
|
790
|
+
.new_from_image(rgba)
|
|
791
|
+
.colourspace(interpretation, VImage::option()->set("source_space", VIPS_INTERPRETATION_sRGB));
|
|
792
|
+
if (premultiply) {
|
|
793
|
+
pixel = pixel.premultiply();
|
|
794
|
+
}
|
|
795
|
+
return pixel(0, 0);
|
|
771
796
|
}
|
|
772
797
|
|
|
773
798
|
/*
|
|
774
799
|
Apply the alpha channel to a given colour
|
|
775
800
|
*/
|
|
776
|
-
std::tuple<VImage, std::vector<double>> ApplyAlpha(VImage image, std::vector<double> colour) {
|
|
801
|
+
std::tuple<VImage, std::vector<double>> ApplyAlpha(VImage image, std::vector<double> colour, bool premultiply) {
|
|
777
802
|
// Scale up 8-bit values to match 16-bit input image
|
|
778
803
|
double const multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0;
|
|
779
804
|
// Create alphaColour colour
|
|
@@ -797,7 +822,7 @@ namespace sharp {
|
|
|
797
822
|
alphaColour.push_back(colour[3] * multiplier);
|
|
798
823
|
}
|
|
799
824
|
// Ensure alphaColour colour uses correct colourspace
|
|
800
|
-
alphaColour = sharp::GetRgbaAsColourspace(alphaColour, image.interpretation());
|
|
825
|
+
alphaColour = sharp::GetRgbaAsColourspace(alphaColour, image.interpretation(), premultiply);
|
|
801
826
|
// Add non-transparent alpha channel, if required
|
|
802
827
|
if (colour[3] < 255.0 && !HasAlpha(image)) {
|
|
803
828
|
image = image.bandjoin(
|
|
@@ -827,4 +852,5 @@ namespace sharp {
|
|
|
827
852
|
}
|
|
828
853
|
return image;
|
|
829
854
|
}
|
|
855
|
+
|
|
830
856
|
} // namespace sharp
|
package/src/common.h
CHANGED
|
@@ -116,6 +116,7 @@ namespace sharp {
|
|
|
116
116
|
JPEG,
|
|
117
117
|
PNG,
|
|
118
118
|
WEBP,
|
|
119
|
+
JP2,
|
|
119
120
|
TIFF,
|
|
120
121
|
GIF,
|
|
121
122
|
SVG,
|
|
@@ -142,6 +143,7 @@ namespace sharp {
|
|
|
142
143
|
bool IsJpeg(std::string const &str);
|
|
143
144
|
bool IsPng(std::string const &str);
|
|
144
145
|
bool IsWebp(std::string const &str);
|
|
146
|
+
bool IsJp2(std::string const &str);
|
|
145
147
|
bool IsGif(std::string const &str);
|
|
146
148
|
bool IsTiff(std::string const &str);
|
|
147
149
|
bool IsHeic(std::string const &str);
|
|
@@ -208,6 +210,11 @@ namespace sharp {
|
|
|
208
210
|
*/
|
|
209
211
|
VImage SetAnimationProperties(VImage image, int pageHeight, std::vector<int> delay, int loop);
|
|
210
212
|
|
|
213
|
+
/*
|
|
214
|
+
Remove animation properties from image.
|
|
215
|
+
*/
|
|
216
|
+
VImage RemoveAnimationProperties(VImage image);
|
|
217
|
+
|
|
211
218
|
/*
|
|
212
219
|
Does this image have a non-default density?
|
|
213
220
|
*/
|
|
@@ -288,12 +295,13 @@ namespace sharp {
|
|
|
288
295
|
/*
|
|
289
296
|
Convert RGBA value to another colourspace
|
|
290
297
|
*/
|
|
291
|
-
std::vector<double> GetRgbaAsColourspace(std::vector<double> const rgba,
|
|
298
|
+
std::vector<double> GetRgbaAsColourspace(std::vector<double> const rgba,
|
|
299
|
+
VipsInterpretation const interpretation, bool premultiply);
|
|
292
300
|
|
|
293
301
|
/*
|
|
294
302
|
Apply the alpha channel to a given colour
|
|
295
303
|
*/
|
|
296
|
-
std::tuple<VImage, std::vector<double>> ApplyAlpha(VImage image, std::vector<double> colour);
|
|
304
|
+
std::tuple<VImage, std::vector<double>> ApplyAlpha(VImage image, std::vector<double> colour, bool premultiply);
|
|
297
305
|
|
|
298
306
|
/*
|
|
299
307
|
Removes alpha channel, if any.
|
package/src/operations.cc
CHANGED
|
@@ -182,7 +182,8 @@ namespace sharp {
|
|
|
182
182
|
0.0, 0.0, 0.0, 1.0));
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
-
VImage Modulate(VImage image, double const brightness, double const saturation,
|
|
185
|
+
VImage Modulate(VImage image, double const brightness, double const saturation,
|
|
186
|
+
int const hue, double const lightness) {
|
|
186
187
|
if (HasAlpha(image)) {
|
|
187
188
|
// Separate alpha channel
|
|
188
189
|
VImage alpha = image[image.bands() - 1];
|
|
@@ -190,7 +191,7 @@ namespace sharp {
|
|
|
190
191
|
.colourspace(VIPS_INTERPRETATION_LCH)
|
|
191
192
|
.linear(
|
|
192
193
|
{ brightness, saturation, 1},
|
|
193
|
-
{
|
|
194
|
+
{ lightness, 0.0, static_cast<double>(hue) }
|
|
194
195
|
)
|
|
195
196
|
.colourspace(VIPS_INTERPRETATION_sRGB)
|
|
196
197
|
.bandjoin(alpha);
|
|
@@ -199,7 +200,7 @@ namespace sharp {
|
|
|
199
200
|
.colourspace(VIPS_INTERPRETATION_LCH)
|
|
200
201
|
.linear(
|
|
201
202
|
{ brightness, saturation, 1 },
|
|
202
|
-
{
|
|
203
|
+
{ lightness, 0.0, static_cast<double>(hue) }
|
|
203
204
|
)
|
|
204
205
|
.colourspace(VIPS_INTERPRETATION_sRGB);
|
|
205
206
|
}
|
package/src/operations.h
CHANGED
|
@@ -98,9 +98,10 @@ namespace sharp {
|
|
|
98
98
|
VImage Recomb(VImage image, std::unique_ptr<double[]> const &matrix);
|
|
99
99
|
|
|
100
100
|
/*
|
|
101
|
-
* Modulate brightness, saturation and
|
|
101
|
+
* Modulate brightness, saturation, hue and lightness
|
|
102
102
|
*/
|
|
103
|
-
VImage Modulate(VImage image, double const brightness, double const saturation,
|
|
103
|
+
VImage Modulate(VImage image, double const brightness, double const saturation,
|
|
104
|
+
int const hue, double const lightness);
|
|
104
105
|
|
|
105
106
|
/*
|
|
106
107
|
* Ensure the image is in a given colourspace
|
package/src/pipeline.cc
CHANGED
|
@@ -90,7 +90,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
90
90
|
}
|
|
91
91
|
if (baton->rotationAngle != 0.0) {
|
|
92
92
|
std::vector<double> background;
|
|
93
|
-
std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground);
|
|
93
|
+
std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground, FALSE);
|
|
94
94
|
image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background));
|
|
95
95
|
}
|
|
96
96
|
}
|
|
@@ -291,14 +291,15 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
291
291
|
}
|
|
292
292
|
|
|
293
293
|
// Ensure we're using a device-independent colour space
|
|
294
|
+
char const *processingProfile = image.interpretation() == VIPS_INTERPRETATION_RGB16 ? "p3" : "srgb";
|
|
294
295
|
if (
|
|
295
296
|
sharp::HasProfile(image) &&
|
|
296
297
|
image.interpretation() != VIPS_INTERPRETATION_LABS &&
|
|
297
298
|
image.interpretation() != VIPS_INTERPRETATION_GREY16
|
|
298
299
|
) {
|
|
299
|
-
// Convert to sRGB using embedded profile
|
|
300
|
+
// Convert to sRGB/P3 using embedded profile
|
|
300
301
|
try {
|
|
301
|
-
image = image.icc_transform(
|
|
302
|
+
image = image.icc_transform(processingProfile, VImage::option()
|
|
302
303
|
->set("embedded", TRUE)
|
|
303
304
|
->set("depth", image.interpretation() == VIPS_INTERPRETATION_RGB16 ? 16 : 8)
|
|
304
305
|
->set("intent", VIPS_INTENT_PERCEPTUAL));
|
|
@@ -306,7 +307,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
306
307
|
// Ignore failure of embedded profile
|
|
307
308
|
}
|
|
308
309
|
} else if (image.interpretation() == VIPS_INTERPRETATION_CMYK) {
|
|
309
|
-
image = image.icc_transform(
|
|
310
|
+
image = image.icc_transform(processingProfile, VImage::option()
|
|
310
311
|
->set("input_profile", "cmyk")
|
|
311
312
|
->set("intent", VIPS_INTENT_PERCEPTUAL));
|
|
312
313
|
}
|
|
@@ -346,7 +347,8 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
346
347
|
bool const shouldSharpen = baton->sharpenSigma != 0.0;
|
|
347
348
|
bool const shouldApplyMedian = baton->medianSize > 0;
|
|
348
349
|
bool const shouldComposite = !baton->composite.empty();
|
|
349
|
-
bool const shouldModulate = baton->brightness != 1.0 || baton->saturation != 1.0 ||
|
|
350
|
+
bool const shouldModulate = baton->brightness != 1.0 || baton->saturation != 1.0 ||
|
|
351
|
+
baton->hue != 0.0 || baton->lightness != 0.0;
|
|
350
352
|
bool const shouldApplyClahe = baton->claheWidth != 0 && baton->claheHeight != 0;
|
|
351
353
|
|
|
352
354
|
if (shouldComposite && !sharp::HasAlpha(image)) {
|
|
@@ -423,7 +425,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
423
425
|
if (image.width() != baton->width || image.height() != baton->height) {
|
|
424
426
|
if (baton->canvas == Canvas::EMBED) {
|
|
425
427
|
std::vector<double> background;
|
|
426
|
-
std::tie(image, background) = sharp::ApplyAlpha(image, baton->resizeBackground);
|
|
428
|
+
std::tie(image, background) = sharp::ApplyAlpha(image, baton->resizeBackground, shouldPremultiplyAlpha);
|
|
427
429
|
|
|
428
430
|
// Embed
|
|
429
431
|
|
|
@@ -480,7 +482,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
480
482
|
// Rotate post-extract non-90 angle
|
|
481
483
|
if (!baton->rotateBeforePreExtract && baton->rotationAngle != 0.0) {
|
|
482
484
|
std::vector<double> background;
|
|
483
|
-
std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground);
|
|
485
|
+
std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground, shouldPremultiplyAlpha);
|
|
484
486
|
image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background));
|
|
485
487
|
}
|
|
486
488
|
|
|
@@ -493,7 +495,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
493
495
|
// Affine transform
|
|
494
496
|
if (baton->affineMatrix.size() > 0) {
|
|
495
497
|
std::vector<double> background;
|
|
496
|
-
std::tie(image, background) = sharp::ApplyAlpha(image, baton->affineBackground);
|
|
498
|
+
std::tie(image, background) = sharp::ApplyAlpha(image, baton->affineBackground, shouldPremultiplyAlpha);
|
|
497
499
|
image = image.affine(baton->affineMatrix, VImage::option()->set("background", background)
|
|
498
500
|
->set("idx", baton->affineIdx)
|
|
499
501
|
->set("idy", baton->affineIdy)
|
|
@@ -505,7 +507,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
505
507
|
// Extend edges
|
|
506
508
|
if (baton->extendTop > 0 || baton->extendBottom > 0 || baton->extendLeft > 0 || baton->extendRight > 0) {
|
|
507
509
|
std::vector<double> background;
|
|
508
|
-
std::tie(image, background) = sharp::ApplyAlpha(image, baton->extendBackground);
|
|
510
|
+
std::tie(image, background) = sharp::ApplyAlpha(image, baton->extendBackground, shouldPremultiplyAlpha);
|
|
509
511
|
|
|
510
512
|
// Embed
|
|
511
513
|
baton->width = image.width() + baton->extendLeft + baton->extendRight;
|
|
@@ -542,7 +544,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
542
544
|
}
|
|
543
545
|
|
|
544
546
|
if (shouldModulate) {
|
|
545
|
-
image = sharp::Modulate(image, baton->brightness, baton->saturation, baton->hue);
|
|
547
|
+
image = sharp::Modulate(image, baton->brightness, baton->saturation, baton->hue, baton->lightness);
|
|
546
548
|
}
|
|
547
549
|
|
|
548
550
|
// Sharpen
|
|
@@ -715,9 +717,10 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
715
717
|
// Convert colourspace, pass the current known interpretation so libvips doesn't have to guess
|
|
716
718
|
image = image.colourspace(baton->colourspace, VImage::option()->set("source_space", image.interpretation()));
|
|
717
719
|
// Transform colours from embedded profile to output profile
|
|
718
|
-
if (baton->withMetadata && sharp::HasProfile(image)) {
|
|
719
|
-
image = image.icc_transform(
|
|
720
|
-
|
|
720
|
+
if (baton->withMetadata && sharp::HasProfile(image) && baton->withMetadataIcc.empty()) {
|
|
721
|
+
image = image.icc_transform("srgb", VImage::option()
|
|
722
|
+
->set("embedded", TRUE)
|
|
723
|
+
->set("intent", VIPS_INTENT_PERCEPTUAL));
|
|
721
724
|
}
|
|
722
725
|
}
|
|
723
726
|
|
|
@@ -726,7 +729,8 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
726
729
|
image = image.icc_transform(
|
|
727
730
|
const_cast<char*>(baton->withMetadataIcc.data()),
|
|
728
731
|
VImage::option()
|
|
729
|
-
->set("input_profile",
|
|
732
|
+
->set("input_profile", processingProfile)
|
|
733
|
+
->set("embedded", TRUE)
|
|
730
734
|
->set("intent", VIPS_INTENT_PERCEPTUAL));
|
|
731
735
|
}
|
|
732
736
|
// Override EXIF Orientation tag
|
|
@@ -787,6 +791,22 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
787
791
|
} else {
|
|
788
792
|
baton->channels = std::min(baton->channels, 3);
|
|
789
793
|
}
|
|
794
|
+
} else if (baton->formatOut == "jp2" || (baton->formatOut == "input"
|
|
795
|
+
&& inputImageType == sharp::ImageType::JP2)) {
|
|
796
|
+
// Write JP2 to Buffer
|
|
797
|
+
sharp::AssertImageTypeDimensions(image, sharp::ImageType::JP2);
|
|
798
|
+
VipsArea *area = reinterpret_cast<VipsArea*>(image.jp2ksave_buffer(VImage::option()
|
|
799
|
+
->set("Q", baton->jp2Quality)
|
|
800
|
+
->set("lossless", baton->jp2Lossless)
|
|
801
|
+
->set("subsample_mode", baton->jp2ChromaSubsampling == "4:4:4"
|
|
802
|
+
? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
|
|
803
|
+
->set("tile_height", baton->jp2TileHeight)
|
|
804
|
+
->set("tile_width", baton->jp2TileWidth)));
|
|
805
|
+
baton->bufferOut = static_cast<char*>(area->data);
|
|
806
|
+
baton->bufferOutLength = area->length;
|
|
807
|
+
area->free_fn = nullptr;
|
|
808
|
+
vips_area_unref(area);
|
|
809
|
+
baton->formatOut = "jp2";
|
|
790
810
|
} else if (baton->formatOut == "png" || (baton->formatOut == "input" &&
|
|
791
811
|
(inputImageType == sharp::ImageType::PNG || (inputImageType == sharp::ImageType::GIF && !supportsGifOutput) ||
|
|
792
812
|
inputImageType == sharp::ImageType::SVG))) {
|
|
@@ -799,7 +819,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
799
819
|
->set("filter", baton->pngAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_ALL : VIPS_FOREIGN_PNG_FILTER_NONE)
|
|
800
820
|
->set("palette", baton->pngPalette)
|
|
801
821
|
->set("Q", baton->pngQuality)
|
|
802
|
-
->set("
|
|
822
|
+
->set("bitdepth", baton->pngBitdepth)
|
|
803
823
|
->set("dither", baton->pngDither)));
|
|
804
824
|
baton->bufferOut = static_cast<char*>(area->data);
|
|
805
825
|
baton->bufferOutLength = area->length;
|
|
@@ -868,6 +888,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
868
888
|
} else if (baton->formatOut == "heif" ||
|
|
869
889
|
(baton->formatOut == "input" && inputImageType == sharp::ImageType::HEIF)) {
|
|
870
890
|
// Write HEIF to buffer
|
|
891
|
+
image = sharp::RemoveAnimationProperties(image);
|
|
871
892
|
VipsArea *area = reinterpret_cast<VipsArea*>(image.heifsave_buffer(VImage::option()
|
|
872
893
|
->set("strip", !baton->withMetadata)
|
|
873
894
|
->set("Q", baton->heifQuality)
|
|
@@ -917,13 +938,14 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
917
938
|
bool const isWebp = sharp::IsWebp(baton->fileOut);
|
|
918
939
|
bool const isGif = sharp::IsGif(baton->fileOut);
|
|
919
940
|
bool const isTiff = sharp::IsTiff(baton->fileOut);
|
|
941
|
+
bool const isJp2 = sharp::IsJp2(baton->fileOut);
|
|
920
942
|
bool const isHeif = sharp::IsHeif(baton->fileOut);
|
|
921
943
|
bool const isDz = sharp::IsDz(baton->fileOut);
|
|
922
944
|
bool const isDzZip = sharp::IsDzZip(baton->fileOut);
|
|
923
945
|
bool const isV = sharp::IsV(baton->fileOut);
|
|
924
946
|
bool const mightMatchInput = baton->formatOut == "input";
|
|
925
947
|
bool const willMatchInput = mightMatchInput &&
|
|
926
|
-
!(isJpeg || isPng || isWebp || isGif || isTiff || isHeif || isDz || isDzZip || isV);
|
|
948
|
+
!(isJpeg || isPng || isWebp || isGif || isTiff || isJp2 || isHeif || isDz || isDzZip || isV);
|
|
927
949
|
|
|
928
950
|
if (baton->formatOut == "jpeg" || (mightMatchInput && isJpeg) ||
|
|
929
951
|
(willMatchInput && inputImageType == sharp::ImageType::JPEG)) {
|
|
@@ -943,6 +965,18 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
943
965
|
->set("optimize_coding", baton->jpegOptimiseCoding));
|
|
944
966
|
baton->formatOut = "jpeg";
|
|
945
967
|
baton->channels = std::min(baton->channels, 3);
|
|
968
|
+
} else if (baton->formatOut == "jp2" || (mightMatchInput && isJp2) ||
|
|
969
|
+
(willMatchInput && (inputImageType == sharp::ImageType::JP2))) {
|
|
970
|
+
// Write JP2 to file
|
|
971
|
+
sharp::AssertImageTypeDimensions(image, sharp::ImageType::JP2);
|
|
972
|
+
image.jp2ksave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
|
973
|
+
->set("Q", baton->jp2Quality)
|
|
974
|
+
->set("lossless", baton->jp2Lossless)
|
|
975
|
+
->set("subsample_mode", baton->jp2ChromaSubsampling == "4:4:4"
|
|
976
|
+
? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
|
|
977
|
+
->set("tile_height", baton->jp2TileHeight)
|
|
978
|
+
->set("tile_width", baton->jp2TileWidth));
|
|
979
|
+
baton->formatOut = "jp2";
|
|
946
980
|
} else if (baton->formatOut == "png" || (mightMatchInput && isPng) || (willMatchInput &&
|
|
947
981
|
(inputImageType == sharp::ImageType::PNG || (inputImageType == sharp::ImageType::GIF && !supportsGifOutput) ||
|
|
948
982
|
inputImageType == sharp::ImageType::SVG))) {
|
|
@@ -955,7 +989,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
955
989
|
->set("filter", baton->pngAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_ALL : VIPS_FOREIGN_PNG_FILTER_NONE)
|
|
956
990
|
->set("palette", baton->pngPalette)
|
|
957
991
|
->set("Q", baton->pngQuality)
|
|
958
|
-
->set("
|
|
992
|
+
->set("bitdepth", baton->pngBitdepth)
|
|
959
993
|
->set("dither", baton->pngDither));
|
|
960
994
|
baton->formatOut = "png";
|
|
961
995
|
} else if (baton->formatOut == "webp" || (mightMatchInput && isWebp) ||
|
|
@@ -1008,6 +1042,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1008
1042
|
} else if (baton->formatOut == "heif" || (mightMatchInput && isHeif) ||
|
|
1009
1043
|
(willMatchInput && inputImageType == sharp::ImageType::HEIF)) {
|
|
1010
1044
|
// Write HEIF to file
|
|
1045
|
+
image = sharp::RemoveAnimationProperties(image);
|
|
1011
1046
|
image.heifsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
|
1012
1047
|
->set("strip", !baton->withMetadata)
|
|
1013
1048
|
->set("Q", baton->heifQuality)
|
|
@@ -1328,6 +1363,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1328
1363
|
baton->brightness = sharp::AttrAsDouble(options, "brightness");
|
|
1329
1364
|
baton->saturation = sharp::AttrAsDouble(options, "saturation");
|
|
1330
1365
|
baton->hue = sharp::AttrAsInt32(options, "hue");
|
|
1366
|
+
baton->lightness = sharp::AttrAsDouble(options, "lightness");
|
|
1331
1367
|
baton->medianSize = sharp::AttrAsUint32(options, "medianSize");
|
|
1332
1368
|
baton->sharpenSigma = sharp::AttrAsDouble(options, "sharpenSigma");
|
|
1333
1369
|
baton->sharpenFlat = sharp::AttrAsDouble(options, "sharpenFlat");
|
|
@@ -1429,8 +1465,13 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1429
1465
|
baton->pngAdaptiveFiltering = sharp::AttrAsBool(options, "pngAdaptiveFiltering");
|
|
1430
1466
|
baton->pngPalette = sharp::AttrAsBool(options, "pngPalette");
|
|
1431
1467
|
baton->pngQuality = sharp::AttrAsUint32(options, "pngQuality");
|
|
1432
|
-
baton->
|
|
1468
|
+
baton->pngBitdepth = sharp::AttrAsUint32(options, "pngBitdepth");
|
|
1433
1469
|
baton->pngDither = sharp::AttrAsDouble(options, "pngDither");
|
|
1470
|
+
baton->jp2Quality = sharp::AttrAsUint32(options, "jp2Quality");
|
|
1471
|
+
baton->jp2Lossless = sharp::AttrAsBool(options, "jp2Lossless");
|
|
1472
|
+
baton->jp2TileHeight = sharp::AttrAsUint32(options, "jp2TileHeight");
|
|
1473
|
+
baton->jp2TileWidth = sharp::AttrAsUint32(options, "jp2TileWidth");
|
|
1474
|
+
baton->jp2ChromaSubsampling = sharp::AttrAsStr(options, "jp2ChromaSubsampling");
|
|
1434
1475
|
baton->webpQuality = sharp::AttrAsUint32(options, "webpQuality");
|
|
1435
1476
|
baton->webpAlphaQuality = sharp::AttrAsUint32(options, "webpAlphaQuality");
|
|
1436
1477
|
baton->webpLossless = sharp::AttrAsBool(options, "webpLossless");
|
package/src/pipeline.h
CHANGED
|
@@ -95,6 +95,7 @@ struct PipelineBaton {
|
|
|
95
95
|
double brightness;
|
|
96
96
|
double saturation;
|
|
97
97
|
int hue;
|
|
98
|
+
double lightness;
|
|
98
99
|
int medianSize;
|
|
99
100
|
double sharpenSigma;
|
|
100
101
|
double sharpenFlat;
|
|
@@ -146,8 +147,13 @@ struct PipelineBaton {
|
|
|
146
147
|
bool pngAdaptiveFiltering;
|
|
147
148
|
bool pngPalette;
|
|
148
149
|
int pngQuality;
|
|
149
|
-
int
|
|
150
|
+
int pngBitdepth;
|
|
150
151
|
double pngDither;
|
|
152
|
+
int jp2Quality;
|
|
153
|
+
bool jp2Lossless;
|
|
154
|
+
int jp2TileHeight;
|
|
155
|
+
int jp2TileWidth;
|
|
156
|
+
std::string jp2ChromaSubsampling;
|
|
151
157
|
int webpQuality;
|
|
152
158
|
int webpAlphaQuality;
|
|
153
159
|
bool webpNearLossless;
|
|
@@ -227,6 +233,7 @@ struct PipelineBaton {
|
|
|
227
233
|
brightness(1.0),
|
|
228
234
|
saturation(1.0),
|
|
229
235
|
hue(0),
|
|
236
|
+
lightness(0),
|
|
230
237
|
medianSize(0),
|
|
231
238
|
sharpenSigma(0.0),
|
|
232
239
|
sharpenFlat(1.0),
|
|
@@ -276,8 +283,13 @@ struct PipelineBaton {
|
|
|
276
283
|
pngAdaptiveFiltering(false),
|
|
277
284
|
pngPalette(false),
|
|
278
285
|
pngQuality(100),
|
|
279
|
-
|
|
286
|
+
pngBitdepth(8),
|
|
280
287
|
pngDither(1.0),
|
|
288
|
+
jp2Quality(80),
|
|
289
|
+
jp2Lossless(false),
|
|
290
|
+
jp2TileHeight(512),
|
|
291
|
+
jp2TileWidth(512),
|
|
292
|
+
jp2ChromaSubsampling("4:4:4"),
|
|
281
293
|
webpQuality(80),
|
|
282
294
|
webpAlphaQuality(100),
|
|
283
295
|
webpNearLossless(false),
|
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"
|
|
118
|
+
"ppm", "fits", "gif", "svg", "heif", "pdf", "vips", "jp2k"
|
|
119
119
|
}) {
|
|
120
120
|
// Input
|
|
121
121
|
Napi::Boolean hasInputFile =
|