sharp 0.28.1 → 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/README.md +2 -2
- package/binding.gyp +12 -9
- package/install/can-compile.js +11 -0
- package/install/dll-copy.js +6 -6
- package/install/libvips.js +24 -18
- package/lib/agent.js +1 -1
- package/lib/channel.js +13 -7
- package/lib/colour.js +42 -1
- package/lib/composite.js +2 -0
- package/lib/constructor.js +21 -30
- package/lib/input.js +52 -3
- package/lib/is.js +19 -5
- package/lib/libvips.js +9 -22
- package/lib/operation.js +74 -7
- package/lib/output.js +133 -17
- package/lib/sharp.js +24 -0
- package/lib/utility.js +1 -1
- package/package.json +23 -16
- package/src/common.cc +47 -14
- package/src/common.h +17 -5
- package/src/libvips/cplusplus/VConnection.cpp +0 -26
- package/src/libvips/cplusplus/VImage.cpp +54 -16
- package/src/libvips/cplusplus/VInterpolate.cpp +0 -13
- package/src/libvips/cplusplus/vips-operators.cpp +185 -1
- package/src/metadata.cc +14 -0
- package/src/metadata.h +1 -0
- package/src/operations.cc +36 -3
- package/src/operations.h +18 -2
- package/src/pipeline.cc +104 -34
- package/src/pipeline.h +29 -3
- package/src/utilities.cc +1 -1
package/src/operations.cc
CHANGED
|
@@ -92,6 +92,13 @@ namespace sharp {
|
|
|
92
92
|
return image;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
/*
|
|
96
|
+
* Contrast limiting adapative histogram equalization (CLAHE)
|
|
97
|
+
*/
|
|
98
|
+
VImage Clahe(VImage image, int const width, int const height, int const maxSlope) {
|
|
99
|
+
return image.hist_local(width, height, VImage::option()->set("max_slope", maxSlope));
|
|
100
|
+
}
|
|
101
|
+
|
|
95
102
|
/*
|
|
96
103
|
* Gamma encoding/decoding
|
|
97
104
|
*/
|
|
@@ -105,6 +112,19 @@ namespace sharp {
|
|
|
105
112
|
}
|
|
106
113
|
}
|
|
107
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Produce the "negative" of the image.
|
|
117
|
+
*/
|
|
118
|
+
VImage Negate(VImage image, bool const negateAlpha) {
|
|
119
|
+
if (HasAlpha(image) && !negateAlpha) {
|
|
120
|
+
// Separate alpha channel
|
|
121
|
+
VImage alpha = image[image.bands() - 1];
|
|
122
|
+
return RemoveAlpha(image).invert().bandjoin(alpha);
|
|
123
|
+
} else {
|
|
124
|
+
return image.invert();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
108
128
|
/*
|
|
109
129
|
* Gaussian blur. Use sigma of -1.0 for fast blur.
|
|
110
130
|
*/
|
|
@@ -162,7 +182,8 @@ namespace sharp {
|
|
|
162
182
|
0.0, 0.0, 0.0, 1.0));
|
|
163
183
|
}
|
|
164
184
|
|
|
165
|
-
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) {
|
|
166
187
|
if (HasAlpha(image)) {
|
|
167
188
|
// Separate alpha channel
|
|
168
189
|
VImage alpha = image[image.bands() - 1];
|
|
@@ -170,7 +191,7 @@ namespace sharp {
|
|
|
170
191
|
.colourspace(VIPS_INTERPRETATION_LCH)
|
|
171
192
|
.linear(
|
|
172
193
|
{ brightness, saturation, 1},
|
|
173
|
-
{
|
|
194
|
+
{ lightness, 0.0, static_cast<double>(hue) }
|
|
174
195
|
)
|
|
175
196
|
.colourspace(VIPS_INTERPRETATION_sRGB)
|
|
176
197
|
.bandjoin(alpha);
|
|
@@ -179,7 +200,7 @@ namespace sharp {
|
|
|
179
200
|
.colourspace(VIPS_INTERPRETATION_LCH)
|
|
180
201
|
.linear(
|
|
181
202
|
{ brightness, saturation, 1 },
|
|
182
|
-
{
|
|
203
|
+
{ lightness, 0.0, static_cast<double>(hue) }
|
|
183
204
|
)
|
|
184
205
|
.colourspace(VIPS_INTERPRETATION_sRGB);
|
|
185
206
|
}
|
|
@@ -275,4 +296,16 @@ namespace sharp {
|
|
|
275
296
|
return image.linear(a, b);
|
|
276
297
|
}
|
|
277
298
|
}
|
|
299
|
+
|
|
300
|
+
/*
|
|
301
|
+
* Ensure the image is in a given colourspace
|
|
302
|
+
*/
|
|
303
|
+
VImage EnsureColourspace(VImage image, VipsInterpretation colourspace) {
|
|
304
|
+
if (colourspace != VIPS_INTERPRETATION_LAST && image.interpretation() != colourspace) {
|
|
305
|
+
image = image.colourspace(colourspace,
|
|
306
|
+
VImage::option()->set("source_space", image.interpretation()));
|
|
307
|
+
}
|
|
308
|
+
return image;
|
|
309
|
+
}
|
|
310
|
+
|
|
278
311
|
} // namespace sharp
|
package/src/operations.h
CHANGED
|
@@ -35,11 +35,21 @@ namespace sharp {
|
|
|
35
35
|
*/
|
|
36
36
|
VImage Normalise(VImage image);
|
|
37
37
|
|
|
38
|
+
/*
|
|
39
|
+
* Contrast limiting adapative histogram equalization (CLAHE)
|
|
40
|
+
*/
|
|
41
|
+
VImage Clahe(VImage image, int const width, int const height, int const maxSlope);
|
|
42
|
+
|
|
38
43
|
/*
|
|
39
44
|
* Gamma encoding/decoding
|
|
40
45
|
*/
|
|
41
46
|
VImage Gamma(VImage image, double const exponent);
|
|
42
47
|
|
|
48
|
+
/*
|
|
49
|
+
* Produce the "negative" of the image.
|
|
50
|
+
*/
|
|
51
|
+
VImage Negate(VImage image, bool const negateAlpha);
|
|
52
|
+
|
|
43
53
|
/*
|
|
44
54
|
* Gaussian blur. Use sigma of -1.0 for fast blur.
|
|
45
55
|
*/
|
|
@@ -88,9 +98,15 @@ namespace sharp {
|
|
|
88
98
|
VImage Recomb(VImage image, std::unique_ptr<double[]> const &matrix);
|
|
89
99
|
|
|
90
100
|
/*
|
|
91
|
-
* Modulate brightness, saturation and
|
|
101
|
+
* Modulate brightness, saturation, hue and lightness
|
|
102
|
+
*/
|
|
103
|
+
VImage Modulate(VImage image, double const brightness, double const saturation,
|
|
104
|
+
int const hue, double const lightness);
|
|
105
|
+
|
|
106
|
+
/*
|
|
107
|
+
* Ensure the image is in a given colourspace
|
|
92
108
|
*/
|
|
93
|
-
VImage
|
|
109
|
+
VImage EnsureColourspace(VImage image, VipsInterpretation colourspace);
|
|
94
110
|
|
|
95
111
|
} // namespace sharp
|
|
96
112
|
|
package/src/pipeline.cc
CHANGED
|
@@ -67,6 +67,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
67
67
|
vips::VImage image;
|
|
68
68
|
sharp::ImageType inputImageType;
|
|
69
69
|
std::tie(image, inputImageType) = sharp::OpenInput(baton->input);
|
|
70
|
+
image = sharp::EnsureColourspace(image, baton->colourspaceInput);
|
|
70
71
|
|
|
71
72
|
// Calculate angle of rotation
|
|
72
73
|
VipsAngle rotation;
|
|
@@ -89,7 +90,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
89
90
|
}
|
|
90
91
|
if (baton->rotationAngle != 0.0) {
|
|
91
92
|
std::vector<double> background;
|
|
92
|
-
std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground);
|
|
93
|
+
std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground, FALSE);
|
|
93
94
|
image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background));
|
|
94
95
|
}
|
|
95
96
|
}
|
|
@@ -214,7 +215,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
214
215
|
double yresidual = static_cast<double>(yshrink) / yfactor;
|
|
215
216
|
|
|
216
217
|
// If integral x and y shrink are equal, try to use shrink-on-load for JPEG and WebP,
|
|
217
|
-
// but not when applying gamma correction, pre-resize extract or
|
|
218
|
+
// but not when applying gamma correction, pre-resize extract, trim or input colourspace
|
|
218
219
|
int shrink_on_load = 1;
|
|
219
220
|
|
|
220
221
|
int shrink_on_load_factor = 1;
|
|
@@ -226,7 +227,9 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
226
227
|
if (
|
|
227
228
|
xshrink == yshrink && xshrink >= 2 * shrink_on_load_factor &&
|
|
228
229
|
(inputImageType == sharp::ImageType::JPEG || inputImageType == sharp::ImageType::WEBP) &&
|
|
229
|
-
baton->gamma == 0 && baton->topOffsetPre == -1 && baton->trimThreshold == 0.0
|
|
230
|
+
baton->gamma == 0 && baton->topOffsetPre == -1 && baton->trimThreshold == 0.0 &&
|
|
231
|
+
baton->colourspaceInput == VIPS_INTERPRETATION_LAST &&
|
|
232
|
+
image.width() > 3 && image.height() > 3 && baton->input->pages == 1
|
|
230
233
|
) {
|
|
231
234
|
if (xshrink >= 8 * shrink_on_load_factor) {
|
|
232
235
|
xfactor = xfactor / 8;
|
|
@@ -288,14 +291,15 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
288
291
|
}
|
|
289
292
|
|
|
290
293
|
// Ensure we're using a device-independent colour space
|
|
294
|
+
char const *processingProfile = image.interpretation() == VIPS_INTERPRETATION_RGB16 ? "p3" : "srgb";
|
|
291
295
|
if (
|
|
292
296
|
sharp::HasProfile(image) &&
|
|
293
297
|
image.interpretation() != VIPS_INTERPRETATION_LABS &&
|
|
294
298
|
image.interpretation() != VIPS_INTERPRETATION_GREY16
|
|
295
299
|
) {
|
|
296
|
-
// Convert to sRGB using embedded profile
|
|
300
|
+
// Convert to sRGB/P3 using embedded profile
|
|
297
301
|
try {
|
|
298
|
-
image = image.icc_transform(
|
|
302
|
+
image = image.icc_transform(processingProfile, VImage::option()
|
|
299
303
|
->set("embedded", TRUE)
|
|
300
304
|
->set("depth", image.interpretation() == VIPS_INTERPRETATION_RGB16 ? 16 : 8)
|
|
301
305
|
->set("intent", VIPS_INTENT_PERCEPTUAL));
|
|
@@ -303,7 +307,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
303
307
|
// Ignore failure of embedded profile
|
|
304
308
|
}
|
|
305
309
|
} else if (image.interpretation() == VIPS_INTERPRETATION_CMYK) {
|
|
306
|
-
image = image.icc_transform(
|
|
310
|
+
image = image.icc_transform(processingProfile, VImage::option()
|
|
307
311
|
->set("input_profile", "cmyk")
|
|
308
312
|
->set("intent", VIPS_INTENT_PERCEPTUAL));
|
|
309
313
|
}
|
|
@@ -324,7 +328,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
324
328
|
|
|
325
329
|
// Negate the colours in the image
|
|
326
330
|
if (baton->negate) {
|
|
327
|
-
image = image
|
|
331
|
+
image = sharp::Negate(image, baton->negateAlpha);
|
|
328
332
|
}
|
|
329
333
|
|
|
330
334
|
// Gamma encoding (darken)
|
|
@@ -343,7 +347,9 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
343
347
|
bool const shouldSharpen = baton->sharpenSigma != 0.0;
|
|
344
348
|
bool const shouldApplyMedian = baton->medianSize > 0;
|
|
345
349
|
bool const shouldComposite = !baton->composite.empty();
|
|
346
|
-
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;
|
|
352
|
+
bool const shouldApplyClahe = baton->claheWidth != 0 && baton->claheHeight != 0;
|
|
347
353
|
|
|
348
354
|
if (shouldComposite && !sharp::HasAlpha(image)) {
|
|
349
355
|
image = sharp::EnsureAlpha(image, 1);
|
|
@@ -409,6 +415,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
409
415
|
|
|
410
416
|
for (unsigned int i = 0; i < baton->joinChannelIn.size(); i++) {
|
|
411
417
|
std::tie(joinImage, joinImageType) = sharp::OpenInput(baton->joinChannelIn[i]);
|
|
418
|
+
joinImage = sharp::EnsureColourspace(joinImage, baton->colourspaceInput);
|
|
412
419
|
image = image.bandjoin(joinImage);
|
|
413
420
|
}
|
|
414
421
|
image = image.copy(VImage::option()->set("interpretation", baton->colourspace));
|
|
@@ -418,7 +425,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
418
425
|
if (image.width() != baton->width || image.height() != baton->height) {
|
|
419
426
|
if (baton->canvas == Canvas::EMBED) {
|
|
420
427
|
std::vector<double> background;
|
|
421
|
-
std::tie(image, background) = sharp::ApplyAlpha(image, baton->resizeBackground);
|
|
428
|
+
std::tie(image, background) = sharp::ApplyAlpha(image, baton->resizeBackground, shouldPremultiplyAlpha);
|
|
422
429
|
|
|
423
430
|
// Embed
|
|
424
431
|
|
|
@@ -475,7 +482,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
475
482
|
// Rotate post-extract non-90 angle
|
|
476
483
|
if (!baton->rotateBeforePreExtract && baton->rotationAngle != 0.0) {
|
|
477
484
|
std::vector<double> background;
|
|
478
|
-
std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground);
|
|
485
|
+
std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground, shouldPremultiplyAlpha);
|
|
479
486
|
image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background));
|
|
480
487
|
}
|
|
481
488
|
|
|
@@ -488,7 +495,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
488
495
|
// Affine transform
|
|
489
496
|
if (baton->affineMatrix.size() > 0) {
|
|
490
497
|
std::vector<double> background;
|
|
491
|
-
std::tie(image, background) = sharp::ApplyAlpha(image, baton->affineBackground);
|
|
498
|
+
std::tie(image, background) = sharp::ApplyAlpha(image, baton->affineBackground, shouldPremultiplyAlpha);
|
|
492
499
|
image = image.affine(baton->affineMatrix, VImage::option()->set("background", background)
|
|
493
500
|
->set("idx", baton->affineIdx)
|
|
494
501
|
->set("idy", baton->affineIdy)
|
|
@@ -500,7 +507,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
500
507
|
// Extend edges
|
|
501
508
|
if (baton->extendTop > 0 || baton->extendBottom > 0 || baton->extendLeft > 0 || baton->extendRight > 0) {
|
|
502
509
|
std::vector<double> background;
|
|
503
|
-
std::tie(image, background) = sharp::ApplyAlpha(image, baton->extendBackground);
|
|
510
|
+
std::tie(image, background) = sharp::ApplyAlpha(image, baton->extendBackground, shouldPremultiplyAlpha);
|
|
504
511
|
|
|
505
512
|
// Embed
|
|
506
513
|
baton->width = image.width() + baton->extendLeft + baton->extendRight;
|
|
@@ -537,7 +544,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
537
544
|
}
|
|
538
545
|
|
|
539
546
|
if (shouldModulate) {
|
|
540
|
-
image = sharp::Modulate(image, baton->brightness, baton->saturation, baton->hue);
|
|
547
|
+
image = sharp::Modulate(image, baton->brightness, baton->saturation, baton->hue, baton->lightness);
|
|
541
548
|
}
|
|
542
549
|
|
|
543
550
|
// Sharpen
|
|
@@ -550,7 +557,8 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
550
557
|
for (Composite *composite : baton->composite) {
|
|
551
558
|
VImage compositeImage;
|
|
552
559
|
sharp::ImageType compositeImageType = sharp::ImageType::UNKNOWN;
|
|
553
|
-
std::tie(compositeImage, compositeImageType) = OpenInput(composite->input);
|
|
560
|
+
std::tie(compositeImage, compositeImageType) = sharp::OpenInput(composite->input);
|
|
561
|
+
compositeImage = sharp::EnsureColourspace(compositeImage, baton->colourspaceInput);
|
|
554
562
|
// Verify within current dimensions
|
|
555
563
|
if (compositeImage.width() > image.width() || compositeImage.height() > image.height()) {
|
|
556
564
|
throw vips::VError("Image to composite must have same dimensions or smaller");
|
|
@@ -649,11 +657,17 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
649
657
|
image = sharp::Normalise(image);
|
|
650
658
|
}
|
|
651
659
|
|
|
660
|
+
// Apply contrast limiting adaptive histogram equalization (CLAHE)
|
|
661
|
+
if (shouldApplyClahe) {
|
|
662
|
+
image = sharp::Clahe(image, baton->claheWidth, baton->claheHeight, baton->claheMaxSlope);
|
|
663
|
+
}
|
|
664
|
+
|
|
652
665
|
// Apply bitwise boolean operation between images
|
|
653
666
|
if (baton->boolean != nullptr) {
|
|
654
667
|
VImage booleanImage;
|
|
655
668
|
sharp::ImageType booleanImageType = sharp::ImageType::UNKNOWN;
|
|
656
669
|
std::tie(booleanImage, booleanImageType) = sharp::OpenInput(baton->boolean);
|
|
670
|
+
booleanImage = sharp::EnsureColourspace(booleanImage, baton->colourspaceInput);
|
|
657
671
|
image = sharp::Boolean(image, booleanImage, baton->booleanOp);
|
|
658
672
|
}
|
|
659
673
|
|
|
@@ -703,9 +717,10 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
703
717
|
// Convert colourspace, pass the current known interpretation so libvips doesn't have to guess
|
|
704
718
|
image = image.colourspace(baton->colourspace, VImage::option()->set("source_space", image.interpretation()));
|
|
705
719
|
// Transform colours from embedded profile to output profile
|
|
706
|
-
if (baton->withMetadata && sharp::HasProfile(image)) {
|
|
707
|
-
image = image.icc_transform(
|
|
708
|
-
|
|
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));
|
|
709
724
|
}
|
|
710
725
|
}
|
|
711
726
|
|
|
@@ -714,13 +729,18 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
714
729
|
image = image.icc_transform(
|
|
715
730
|
const_cast<char*>(baton->withMetadataIcc.data()),
|
|
716
731
|
VImage::option()
|
|
717
|
-
->set("input_profile",
|
|
732
|
+
->set("input_profile", processingProfile)
|
|
733
|
+
->set("embedded", TRUE)
|
|
718
734
|
->set("intent", VIPS_INTENT_PERCEPTUAL));
|
|
719
735
|
}
|
|
720
736
|
// Override EXIF Orientation tag
|
|
721
737
|
if (baton->withMetadata && baton->withMetadataOrientation != -1) {
|
|
722
738
|
image = sharp::SetExifOrientation(image, baton->withMetadataOrientation);
|
|
723
739
|
}
|
|
740
|
+
// Override pixel density
|
|
741
|
+
if (baton->withMetadataDensity > 0) {
|
|
742
|
+
image = sharp::SetDensity(image, baton->withMetadataDensity);
|
|
743
|
+
}
|
|
724
744
|
// Metadata key/value pairs, e.g. EXIF
|
|
725
745
|
if (!baton->withMetadataStrs.empty()) {
|
|
726
746
|
image = image.copy();
|
|
@@ -754,8 +774,8 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
754
774
|
->set("Q", baton->jpegQuality)
|
|
755
775
|
->set("interlace", baton->jpegProgressive)
|
|
756
776
|
->set("subsample_mode", baton->jpegChromaSubsampling == "4:4:4"
|
|
757
|
-
?
|
|
758
|
-
:
|
|
777
|
+
? VIPS_FOREIGN_SUBSAMPLE_OFF
|
|
778
|
+
: VIPS_FOREIGN_SUBSAMPLE_ON)
|
|
759
779
|
->set("trellis_quant", baton->jpegTrellisQuantisation)
|
|
760
780
|
->set("quant_table", baton->jpegQuantisationTable)
|
|
761
781
|
->set("overshoot_deringing", baton->jpegOvershootDeringing)
|
|
@@ -771,6 +791,22 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
771
791
|
} else {
|
|
772
792
|
baton->channels = std::min(baton->channels, 3);
|
|
773
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";
|
|
774
810
|
} else if (baton->formatOut == "png" || (baton->formatOut == "input" &&
|
|
775
811
|
(inputImageType == sharp::ImageType::PNG || (inputImageType == sharp::ImageType::GIF && !supportsGifOutput) ||
|
|
776
812
|
inputImageType == sharp::ImageType::SVG))) {
|
|
@@ -783,7 +819,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
783
819
|
->set("filter", baton->pngAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_ALL : VIPS_FOREIGN_PNG_FILTER_NONE)
|
|
784
820
|
->set("palette", baton->pngPalette)
|
|
785
821
|
->set("Q", baton->pngQuality)
|
|
786
|
-
->set("
|
|
822
|
+
->set("bitdepth", baton->pngBitdepth)
|
|
787
823
|
->set("dither", baton->pngDither)));
|
|
788
824
|
baton->bufferOut = static_cast<char*>(area->data);
|
|
789
825
|
baton->bufferOutLength = area->length;
|
|
@@ -852,15 +888,14 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
852
888
|
} else if (baton->formatOut == "heif" ||
|
|
853
889
|
(baton->formatOut == "input" && inputImageType == sharp::ImageType::HEIF)) {
|
|
854
890
|
// Write HEIF to buffer
|
|
891
|
+
image = sharp::RemoveAnimationProperties(image);
|
|
855
892
|
VipsArea *area = reinterpret_cast<VipsArea*>(image.heifsave_buffer(VImage::option()
|
|
856
893
|
->set("strip", !baton->withMetadata)
|
|
857
|
-
->set("compression", baton->heifCompression)
|
|
858
894
|
->set("Q", baton->heifQuality)
|
|
895
|
+
->set("compression", baton->heifCompression)
|
|
859
896
|
->set("speed", baton->heifSpeed)
|
|
860
|
-
#if defined(VIPS_TYPE_FOREIGN_SUBSAMPLE)
|
|
861
897
|
->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
|
|
862
898
|
? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
|
|
863
|
-
#endif
|
|
864
899
|
->set("lossless", baton->heifLossless)));
|
|
865
900
|
baton->bufferOut = static_cast<char*>(area->data);
|
|
866
901
|
baton->bufferOutLength = area->length;
|
|
@@ -875,9 +910,9 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
875
910
|
image = image[0];
|
|
876
911
|
baton->channels = 1;
|
|
877
912
|
}
|
|
878
|
-
if (image.format() !=
|
|
879
|
-
// Cast pixels to
|
|
880
|
-
image = image.cast(
|
|
913
|
+
if (image.format() != baton->rawDepth) {
|
|
914
|
+
// Cast pixels to requested format
|
|
915
|
+
image = image.cast(baton->rawDepth);
|
|
881
916
|
}
|
|
882
917
|
// Get raw image data
|
|
883
918
|
baton->bufferOut = static_cast<char*>(image.write_to_memory(&baton->bufferOutLength));
|
|
@@ -903,13 +938,14 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
903
938
|
bool const isWebp = sharp::IsWebp(baton->fileOut);
|
|
904
939
|
bool const isGif = sharp::IsGif(baton->fileOut);
|
|
905
940
|
bool const isTiff = sharp::IsTiff(baton->fileOut);
|
|
941
|
+
bool const isJp2 = sharp::IsJp2(baton->fileOut);
|
|
906
942
|
bool const isHeif = sharp::IsHeif(baton->fileOut);
|
|
907
943
|
bool const isDz = sharp::IsDz(baton->fileOut);
|
|
908
944
|
bool const isDzZip = sharp::IsDzZip(baton->fileOut);
|
|
909
945
|
bool const isV = sharp::IsV(baton->fileOut);
|
|
910
946
|
bool const mightMatchInput = baton->formatOut == "input";
|
|
911
947
|
bool const willMatchInput = mightMatchInput &&
|
|
912
|
-
!(isJpeg || isPng || isWebp || isGif || isTiff || isHeif || isDz || isDzZip || isV);
|
|
948
|
+
!(isJpeg || isPng || isWebp || isGif || isTiff || isJp2 || isHeif || isDz || isDzZip || isV);
|
|
913
949
|
|
|
914
950
|
if (baton->formatOut == "jpeg" || (mightMatchInput && isJpeg) ||
|
|
915
951
|
(willMatchInput && inputImageType == sharp::ImageType::JPEG)) {
|
|
@@ -920,8 +956,8 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
920
956
|
->set("Q", baton->jpegQuality)
|
|
921
957
|
->set("interlace", baton->jpegProgressive)
|
|
922
958
|
->set("subsample_mode", baton->jpegChromaSubsampling == "4:4:4"
|
|
923
|
-
?
|
|
924
|
-
:
|
|
959
|
+
? VIPS_FOREIGN_SUBSAMPLE_OFF
|
|
960
|
+
: VIPS_FOREIGN_SUBSAMPLE_ON)
|
|
925
961
|
->set("trellis_quant", baton->jpegTrellisQuantisation)
|
|
926
962
|
->set("quant_table", baton->jpegQuantisationTable)
|
|
927
963
|
->set("overshoot_deringing", baton->jpegOvershootDeringing)
|
|
@@ -929,6 +965,18 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
929
965
|
->set("optimize_coding", baton->jpegOptimiseCoding));
|
|
930
966
|
baton->formatOut = "jpeg";
|
|
931
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";
|
|
932
980
|
} else if (baton->formatOut == "png" || (mightMatchInput && isPng) || (willMatchInput &&
|
|
933
981
|
(inputImageType == sharp::ImageType::PNG || (inputImageType == sharp::ImageType::GIF && !supportsGifOutput) ||
|
|
934
982
|
inputImageType == sharp::ImageType::SVG))) {
|
|
@@ -941,7 +989,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
941
989
|
->set("filter", baton->pngAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_ALL : VIPS_FOREIGN_PNG_FILTER_NONE)
|
|
942
990
|
->set("palette", baton->pngPalette)
|
|
943
991
|
->set("Q", baton->pngQuality)
|
|
944
|
-
->set("
|
|
992
|
+
->set("bitdepth", baton->pngBitdepth)
|
|
945
993
|
->set("dither", baton->pngDither));
|
|
946
994
|
baton->formatOut = "png";
|
|
947
995
|
} else if (baton->formatOut == "webp" || (mightMatchInput && isWebp) ||
|
|
@@ -994,15 +1042,14 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
994
1042
|
} else if (baton->formatOut == "heif" || (mightMatchInput && isHeif) ||
|
|
995
1043
|
(willMatchInput && inputImageType == sharp::ImageType::HEIF)) {
|
|
996
1044
|
// Write HEIF to file
|
|
1045
|
+
image = sharp::RemoveAnimationProperties(image);
|
|
997
1046
|
image.heifsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
|
998
1047
|
->set("strip", !baton->withMetadata)
|
|
999
1048
|
->set("Q", baton->heifQuality)
|
|
1000
1049
|
->set("compression", baton->heifCompression)
|
|
1001
1050
|
->set("speed", baton->heifSpeed)
|
|
1002
|
-
#if defined(VIPS_TYPE_FOREIGN_SUBSAMPLE)
|
|
1003
1051
|
->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
|
|
1004
1052
|
? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
|
|
1005
|
-
#endif
|
|
1006
1053
|
->set("lossless", baton->heifLossless));
|
|
1007
1054
|
baton->formatOut = "heif";
|
|
1008
1055
|
} else if (baton->formatOut == "dz" || isDz || isDzZip) {
|
|
@@ -1119,6 +1166,9 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1119
1166
|
info.Set("width", static_cast<uint32_t>(width));
|
|
1120
1167
|
info.Set("height", static_cast<uint32_t>(height));
|
|
1121
1168
|
info.Set("channels", static_cast<uint32_t>(baton->channels));
|
|
1169
|
+
if (baton->formatOut == "raw") {
|
|
1170
|
+
info.Set("depth", vips_enum_nick(VIPS_TYPE_BAND_FORMAT, baton->rawDepth));
|
|
1171
|
+
}
|
|
1122
1172
|
info.Set("premultiplied", baton->premultiplied);
|
|
1123
1173
|
if (baton->hasCropOffset) {
|
|
1124
1174
|
info.Set("cropOffsetLeft", static_cast<int32_t>(baton->cropOffsetLeft));
|
|
@@ -1308,10 +1358,12 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1308
1358
|
baton->flatten = sharp::AttrAsBool(options, "flatten");
|
|
1309
1359
|
baton->flattenBackground = sharp::AttrAsVectorOfDouble(options, "flattenBackground");
|
|
1310
1360
|
baton->negate = sharp::AttrAsBool(options, "negate");
|
|
1361
|
+
baton->negateAlpha = sharp::AttrAsBool(options, "negateAlpha");
|
|
1311
1362
|
baton->blurSigma = sharp::AttrAsDouble(options, "blurSigma");
|
|
1312
1363
|
baton->brightness = sharp::AttrAsDouble(options, "brightness");
|
|
1313
1364
|
baton->saturation = sharp::AttrAsDouble(options, "saturation");
|
|
1314
1365
|
baton->hue = sharp::AttrAsInt32(options, "hue");
|
|
1366
|
+
baton->lightness = sharp::AttrAsDouble(options, "lightness");
|
|
1315
1367
|
baton->medianSize = sharp::AttrAsUint32(options, "medianSize");
|
|
1316
1368
|
baton->sharpenSigma = sharp::AttrAsDouble(options, "sharpenSigma");
|
|
1317
1369
|
baton->sharpenFlat = sharp::AttrAsDouble(options, "sharpenFlat");
|
|
@@ -1325,6 +1377,9 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1325
1377
|
baton->linearB = sharp::AttrAsDouble(options, "linearB");
|
|
1326
1378
|
baton->greyscale = sharp::AttrAsBool(options, "greyscale");
|
|
1327
1379
|
baton->normalise = sharp::AttrAsBool(options, "normalise");
|
|
1380
|
+
baton->claheWidth = sharp::AttrAsUint32(options, "claheWidth");
|
|
1381
|
+
baton->claheHeight = sharp::AttrAsUint32(options, "claheHeight");
|
|
1382
|
+
baton->claheMaxSlope = sharp::AttrAsUint32(options, "claheMaxSlope");
|
|
1328
1383
|
baton->useExifOrientation = sharp::AttrAsBool(options, "useExifOrientation");
|
|
1329
1384
|
baton->angle = sharp::AttrAsInt32(options, "angle");
|
|
1330
1385
|
baton->rotationAngle = sharp::AttrAsDouble(options, "rotationAngle");
|
|
@@ -1375,6 +1430,10 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1375
1430
|
baton->recombMatrix[i] = sharp::AttrAsDouble(recombMatrix, i);
|
|
1376
1431
|
}
|
|
1377
1432
|
}
|
|
1433
|
+
baton->colourspaceInput = sharp::GetInterpretation(sharp::AttrAsStr(options, "colourspaceInput"));
|
|
1434
|
+
if (baton->colourspaceInput == VIPS_INTERPRETATION_ERROR) {
|
|
1435
|
+
baton->colourspaceInput = VIPS_INTERPRETATION_LAST;
|
|
1436
|
+
}
|
|
1378
1437
|
baton->colourspace = sharp::GetInterpretation(sharp::AttrAsStr(options, "colourspace"));
|
|
1379
1438
|
if (baton->colourspace == VIPS_INTERPRETATION_ERROR) {
|
|
1380
1439
|
baton->colourspace = VIPS_INTERPRETATION_sRGB;
|
|
@@ -1384,6 +1443,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1384
1443
|
baton->fileOut = sharp::AttrAsStr(options, "fileOut");
|
|
1385
1444
|
baton->withMetadata = sharp::AttrAsBool(options, "withMetadata");
|
|
1386
1445
|
baton->withMetadataOrientation = sharp::AttrAsUint32(options, "withMetadataOrientation");
|
|
1446
|
+
baton->withMetadataDensity = sharp::AttrAsDouble(options, "withMetadataDensity");
|
|
1387
1447
|
baton->withMetadataIcc = sharp::AttrAsStr(options, "withMetadataIcc");
|
|
1388
1448
|
Napi::Object mdStrs = options.Get("withMetadataStrs").As<Napi::Object>();
|
|
1389
1449
|
Napi::Array mdStrKeys = mdStrs.GetPropertyNames();
|
|
@@ -1405,8 +1465,13 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1405
1465
|
baton->pngAdaptiveFiltering = sharp::AttrAsBool(options, "pngAdaptiveFiltering");
|
|
1406
1466
|
baton->pngPalette = sharp::AttrAsBool(options, "pngPalette");
|
|
1407
1467
|
baton->pngQuality = sharp::AttrAsUint32(options, "pngQuality");
|
|
1408
|
-
baton->
|
|
1468
|
+
baton->pngBitdepth = sharp::AttrAsUint32(options, "pngBitdepth");
|
|
1409
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");
|
|
1410
1475
|
baton->webpQuality = sharp::AttrAsUint32(options, "webpQuality");
|
|
1411
1476
|
baton->webpAlphaQuality = sharp::AttrAsUint32(options, "webpAlphaQuality");
|
|
1412
1477
|
baton->webpLossless = sharp::AttrAsBool(options, "webpLossless");
|
|
@@ -1436,6 +1501,11 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1436
1501
|
baton->heifSpeed = sharp::AttrAsUint32(options, "heifSpeed");
|
|
1437
1502
|
baton->heifChromaSubsampling = sharp::AttrAsStr(options, "heifChromaSubsampling");
|
|
1438
1503
|
|
|
1504
|
+
// Raw output
|
|
1505
|
+
baton->rawDepth = static_cast<VipsBandFormat>(
|
|
1506
|
+
vips_enum_from_nick(nullptr, VIPS_TYPE_BAND_FORMAT,
|
|
1507
|
+
sharp::AttrAsStr(options, "rawDepth").data()));
|
|
1508
|
+
|
|
1439
1509
|
// Animated output
|
|
1440
1510
|
if (sharp::HasAttr(options, "pageHeight")) {
|
|
1441
1511
|
baton->pageHeight = sharp::AttrAsUint32(options, "pageHeight");
|
package/src/pipeline.h
CHANGED
|
@@ -90,10 +90,12 @@ struct PipelineBaton {
|
|
|
90
90
|
bool flatten;
|
|
91
91
|
std::vector<double> flattenBackground;
|
|
92
92
|
bool negate;
|
|
93
|
+
bool negateAlpha;
|
|
93
94
|
double blurSigma;
|
|
94
95
|
double brightness;
|
|
95
96
|
double saturation;
|
|
96
97
|
int hue;
|
|
98
|
+
double lightness;
|
|
97
99
|
int medianSize;
|
|
98
100
|
double sharpenSigma;
|
|
99
101
|
double sharpenFlat;
|
|
@@ -109,6 +111,9 @@ struct PipelineBaton {
|
|
|
109
111
|
double gammaOut;
|
|
110
112
|
bool greyscale;
|
|
111
113
|
bool normalise;
|
|
114
|
+
int claheWidth;
|
|
115
|
+
int claheHeight;
|
|
116
|
+
int claheMaxSlope;
|
|
112
117
|
bool useExifOrientation;
|
|
113
118
|
int angle;
|
|
114
119
|
double rotationAngle;
|
|
@@ -142,8 +147,13 @@ struct PipelineBaton {
|
|
|
142
147
|
bool pngAdaptiveFiltering;
|
|
143
148
|
bool pngPalette;
|
|
144
149
|
int pngQuality;
|
|
145
|
-
int
|
|
150
|
+
int pngBitdepth;
|
|
146
151
|
double pngDither;
|
|
152
|
+
int jp2Quality;
|
|
153
|
+
bool jp2Lossless;
|
|
154
|
+
int jp2TileHeight;
|
|
155
|
+
int jp2TileWidth;
|
|
156
|
+
std::string jp2ChromaSubsampling;
|
|
147
157
|
int webpQuality;
|
|
148
158
|
int webpAlphaQuality;
|
|
149
159
|
bool webpNearLossless;
|
|
@@ -165,9 +175,11 @@ struct PipelineBaton {
|
|
|
165
175
|
int heifSpeed;
|
|
166
176
|
std::string heifChromaSubsampling;
|
|
167
177
|
bool heifLossless;
|
|
178
|
+
VipsBandFormat rawDepth;
|
|
168
179
|
std::string err;
|
|
169
180
|
bool withMetadata;
|
|
170
181
|
int withMetadataOrientation;
|
|
182
|
+
double withMetadataDensity;
|
|
171
183
|
std::string withMetadataIcc;
|
|
172
184
|
std::unordered_map<std::string, std::string> withMetadataStrs;
|
|
173
185
|
std::unique_ptr<double[]> convKernel;
|
|
@@ -181,6 +193,7 @@ struct PipelineBaton {
|
|
|
181
193
|
int extractChannel;
|
|
182
194
|
bool removeAlpha;
|
|
183
195
|
double ensureAlpha;
|
|
196
|
+
VipsInterpretation colourspaceInput;
|
|
184
197
|
VipsInterpretation colourspace;
|
|
185
198
|
int pageHeight;
|
|
186
199
|
std::vector<int> delay;
|
|
@@ -215,10 +228,12 @@ struct PipelineBaton {
|
|
|
215
228
|
flatten(false),
|
|
216
229
|
flattenBackground{ 0.0, 0.0, 0.0 },
|
|
217
230
|
negate(false),
|
|
231
|
+
negateAlpha(true),
|
|
218
232
|
blurSigma(0.0),
|
|
219
233
|
brightness(1.0),
|
|
220
234
|
saturation(1.0),
|
|
221
235
|
hue(0),
|
|
236
|
+
lightness(0),
|
|
222
237
|
medianSize(0),
|
|
223
238
|
sharpenSigma(0.0),
|
|
224
239
|
sharpenFlat(1.0),
|
|
@@ -233,6 +248,9 @@ struct PipelineBaton {
|
|
|
233
248
|
gamma(0.0),
|
|
234
249
|
greyscale(false),
|
|
235
250
|
normalise(false),
|
|
251
|
+
claheWidth(0),
|
|
252
|
+
claheHeight(0),
|
|
253
|
+
claheMaxSlope(3),
|
|
236
254
|
useExifOrientation(false),
|
|
237
255
|
angle(0),
|
|
238
256
|
rotationAngle(0.0),
|
|
@@ -265,8 +283,13 @@ struct PipelineBaton {
|
|
|
265
283
|
pngAdaptiveFiltering(false),
|
|
266
284
|
pngPalette(false),
|
|
267
285
|
pngQuality(100),
|
|
268
|
-
|
|
286
|
+
pngBitdepth(8),
|
|
269
287
|
pngDither(1.0),
|
|
288
|
+
jp2Quality(80),
|
|
289
|
+
jp2Lossless(false),
|
|
290
|
+
jp2TileHeight(512),
|
|
291
|
+
jp2TileWidth(512),
|
|
292
|
+
jp2ChromaSubsampling("4:4:4"),
|
|
270
293
|
webpQuality(80),
|
|
271
294
|
webpAlphaQuality(100),
|
|
272
295
|
webpNearLossless(false),
|
|
@@ -286,10 +309,12 @@ struct PipelineBaton {
|
|
|
286
309
|
heifQuality(50),
|
|
287
310
|
heifCompression(VIPS_FOREIGN_HEIF_COMPRESSION_AV1),
|
|
288
311
|
heifSpeed(5),
|
|
289
|
-
heifChromaSubsampling("4:
|
|
312
|
+
heifChromaSubsampling("4:4:4"),
|
|
290
313
|
heifLossless(false),
|
|
314
|
+
rawDepth(VIPS_FORMAT_UCHAR),
|
|
291
315
|
withMetadata(false),
|
|
292
316
|
withMetadataOrientation(-1),
|
|
317
|
+
withMetadataDensity(0.0),
|
|
293
318
|
convKernelWidth(0),
|
|
294
319
|
convKernelHeight(0),
|
|
295
320
|
convKernelScale(0.0),
|
|
@@ -300,6 +325,7 @@ struct PipelineBaton {
|
|
|
300
325
|
extractChannel(-1),
|
|
301
326
|
removeAlpha(false),
|
|
302
327
|
ensureAlpha(-1.0),
|
|
328
|
+
colourspaceInput(VIPS_INTERPRETATION_LAST),
|
|
303
329
|
colourspace(VIPS_INTERPRETATION_LAST),
|
|
304
330
|
pageHeight(0),
|
|
305
331
|
delay{-1},
|
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 =
|