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/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, int const hue) {
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
- { 0.0, 0.0, static_cast<double>(hue) }
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
- { 0.0, 0.0, static_cast<double>(hue) }
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 hue
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 Modulate(VImage image, double const brightness, double const saturation, int const hue);
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 trim
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("srgb", VImage::option()
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("srgb", VImage::option()
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.invert();
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 || baton->hue != 0.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(vips_enum_nick(VIPS_TYPE_INTERPRETATION, baton->colourspace),
708
- VImage::option()->set("embedded", TRUE));
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", "srgb")
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
- ? VIPS_FOREIGN_JPEG_SUBSAMPLE_OFF
758
- : VIPS_FOREIGN_JPEG_SUBSAMPLE_ON)
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("colours", baton->pngColours)
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() != VIPS_FORMAT_UCHAR) {
879
- // Cast pixels to uint8 (unsigned char)
880
- image = image.cast(VIPS_FORMAT_UCHAR);
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
- ? VIPS_FOREIGN_JPEG_SUBSAMPLE_OFF
924
- : VIPS_FOREIGN_JPEG_SUBSAMPLE_ON)
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("colours", baton->pngColours)
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->pngColours = sharp::AttrAsUint32(options, "pngColours");
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 pngColours;
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
- pngColours(256),
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:2:0"),
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 =