sharp 0.32.6 → 0.33.4

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/pipeline.cc CHANGED
@@ -44,9 +44,9 @@ class PipelineWorker : public Napi::AsyncWorker {
44
44
  // libuv worker
45
45
  void Execute() {
46
46
  // Decrement queued task counter
47
- g_atomic_int_dec_and_test(&sharp::counterQueue);
47
+ sharp::counterQueue--;
48
48
  // Increment processing task counter
49
- g_atomic_int_inc(&sharp::counterProcess);
49
+ sharp::counterProcess++;
50
50
 
51
51
  try {
52
52
  // Open input
@@ -54,7 +54,7 @@ class PipelineWorker : public Napi::AsyncWorker {
54
54
  sharp::ImageType inputImageType;
55
55
  std::tie(image, inputImageType) = sharp::OpenInput(baton->input);
56
56
  VipsAccess access = baton->input->access;
57
- image = sharp::EnsureColourspace(image, baton->colourspaceInput);
57
+ image = sharp::EnsureColourspace(image, baton->colourspacePipeline);
58
58
 
59
59
  int nPages = baton->input->pages;
60
60
  if (nPages == -1) {
@@ -70,8 +70,8 @@ class PipelineWorker : public Napi::AsyncWorker {
70
70
  // Calculate angle of rotation
71
71
  VipsAngle rotation = VIPS_ANGLE_D0;
72
72
  VipsAngle autoRotation = VIPS_ANGLE_D0;
73
- bool autoFlip = FALSE;
74
- bool autoFlop = FALSE;
73
+ bool autoFlip = false;
74
+ bool autoFlop = false;
75
75
 
76
76
  if (baton->useExifOrientation) {
77
77
  // Rotate and flip image according to Exif orientation
@@ -88,7 +88,7 @@ class PipelineWorker : public Napi::AsyncWorker {
88
88
  baton->rotationAngle != 0.0);
89
89
 
90
90
  if (shouldRotateBefore) {
91
- image = sharp::StaySequential(image, access,
91
+ image = sharp::StaySequential(image,
92
92
  rotation != VIPS_ANGLE_D0 ||
93
93
  autoRotation != VIPS_ANGLE_D0 ||
94
94
  autoFlip ||
@@ -96,40 +96,46 @@ class PipelineWorker : public Napi::AsyncWorker {
96
96
  baton->rotationAngle != 0.0);
97
97
 
98
98
  if (autoRotation != VIPS_ANGLE_D0) {
99
+ if (autoRotation != VIPS_ANGLE_D180) {
100
+ MultiPageUnsupported(nPages, "Rotate");
101
+ }
99
102
  image = image.rot(autoRotation);
100
103
  autoRotation = VIPS_ANGLE_D0;
101
104
  }
102
105
  if (autoFlip) {
103
106
  image = image.flip(VIPS_DIRECTION_VERTICAL);
104
- autoFlip = FALSE;
107
+ autoFlip = false;
105
108
  } else if (baton->flip) {
106
109
  image = image.flip(VIPS_DIRECTION_VERTICAL);
107
- baton->flip = FALSE;
110
+ baton->flip = false;
108
111
  }
109
112
  if (autoFlop) {
110
113
  image = image.flip(VIPS_DIRECTION_HORIZONTAL);
111
- autoFlop = FALSE;
114
+ autoFlop = false;
112
115
  } else if (baton->flop) {
113
116
  image = image.flip(VIPS_DIRECTION_HORIZONTAL);
114
- baton->flop = FALSE;
117
+ baton->flop = false;
115
118
  }
116
119
  if (rotation != VIPS_ANGLE_D0) {
120
+ if (rotation != VIPS_ANGLE_D180) {
121
+ MultiPageUnsupported(nPages, "Rotate");
122
+ }
117
123
  image = image.rot(rotation);
118
124
  rotation = VIPS_ANGLE_D0;
119
125
  }
120
126
  if (baton->rotationAngle != 0.0) {
121
127
  MultiPageUnsupported(nPages, "Rotate");
122
128
  std::vector<double> background;
123
- std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground, FALSE);
129
+ std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground, false);
124
130
  image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background)).copy_memory();
125
131
  }
126
132
  }
127
133
 
128
134
  // Trim
129
- if (baton->trimThreshold > 0.0) {
135
+ if (baton->trimThreshold >= 0.0) {
130
136
  MultiPageUnsupported(nPages, "Trim");
131
- image = sharp::StaySequential(image, access);
132
- image = sharp::Trim(image, baton->trimBackground, baton->trimThreshold);
137
+ image = sharp::StaySequential(image);
138
+ image = sharp::Trim(image, baton->trimBackground, baton->trimThreshold, baton->trimLineArt);
133
139
  baton->trimOffsetLeft = image.xoffset();
134
140
  baton->trimOffsetTop = image.yoffset();
135
141
  }
@@ -182,8 +188,8 @@ class PipelineWorker : public Napi::AsyncWorker {
182
188
  // - trimming or pre-resize extract isn't required;
183
189
  // - input colourspace is not specified;
184
190
  bool const shouldPreShrink = (targetResizeWidth > 0 || targetResizeHeight > 0) &&
185
- baton->gamma == 0 && baton->topOffsetPre == -1 && baton->trimThreshold == 0.0 &&
186
- baton->colourspaceInput == VIPS_INTERPRETATION_LAST && !shouldRotateBefore;
191
+ baton->gamma == 0 && baton->topOffsetPre == -1 && baton->trimThreshold < 0.0 &&
192
+ baton->colourspacePipeline == VIPS_INTERPRETATION_LAST && !shouldRotateBefore;
187
193
 
188
194
  if (shouldPreShrink) {
189
195
  // The common part of the shrink: the bit by which both axes must be shrunk
@@ -315,17 +321,23 @@ class PipelineWorker : public Napi::AsyncWorker {
315
321
  }
316
322
 
317
323
  // Ensure we're using a device-independent colour space
324
+ std::pair<char*, size_t> inputProfile(nullptr, 0);
325
+ if ((baton->keepMetadata & VIPS_FOREIGN_KEEP_ICC) && baton->withIccProfile.empty()) {
326
+ // Cache input profile for use with output
327
+ inputProfile = sharp::GetProfile(image);
328
+ }
318
329
  char const *processingProfile = image.interpretation() == VIPS_INTERPRETATION_RGB16 ? "p3" : "srgb";
319
330
  if (
320
331
  sharp::HasProfile(image) &&
321
332
  image.interpretation() != VIPS_INTERPRETATION_LABS &&
322
333
  image.interpretation() != VIPS_INTERPRETATION_GREY16 &&
334
+ baton->colourspacePipeline != VIPS_INTERPRETATION_CMYK &&
323
335
  !baton->input->ignoreIcc
324
336
  ) {
325
337
  // Convert to sRGB/P3 using embedded profile
326
338
  try {
327
339
  image = image.icc_transform(processingProfile, VImage::option()
328
- ->set("embedded", TRUE)
340
+ ->set("embedded", true)
329
341
  ->set("depth", sharp::Is16Bit(image.interpretation()) ? 16 : 8)
330
342
  ->set("intent", VIPS_INTENT_PERCEPTUAL));
331
343
  } catch(...) {
@@ -333,7 +345,7 @@ class PipelineWorker : public Napi::AsyncWorker {
333
345
  }
334
346
  } else if (
335
347
  image.interpretation() == VIPS_INTERPRETATION_CMYK &&
336
- baton->colourspaceInput != VIPS_INTERPRETATION_CMYK
348
+ baton->colourspacePipeline != VIPS_INTERPRETATION_CMYK
337
349
  ) {
338
350
  image = image.icc_transform(processingProfile, VImage::option()
339
351
  ->set("input_profile", "cmyk")
@@ -345,11 +357,6 @@ class PipelineWorker : public Napi::AsyncWorker {
345
357
  image = sharp::Flatten(image, baton->flattenBackground);
346
358
  }
347
359
 
348
- // Negate the colours in the image
349
- if (baton->negate) {
350
- image = sharp::Negate(image, baton->negateAlpha);
351
- }
352
-
353
360
  // Gamma encoding (darken)
354
361
  if (baton->gamma >= 1 && baton->gamma <= 3) {
355
362
  image = sharp::Gamma(image, 1.0 / baton->gamma);
@@ -385,13 +392,16 @@ class PipelineWorker : public Napi::AsyncWorker {
385
392
  ->set("kernel", baton->kernel));
386
393
  }
387
394
 
388
- image = sharp::StaySequential(image, access,
395
+ image = sharp::StaySequential(image,
389
396
  autoRotation != VIPS_ANGLE_D0 ||
390
397
  baton->flip ||
391
398
  autoFlip ||
392
399
  rotation != VIPS_ANGLE_D0);
393
400
  // Auto-rotate post-extract
394
401
  if (autoRotation != VIPS_ANGLE_D0) {
402
+ if (autoRotation != VIPS_ANGLE_D180) {
403
+ MultiPageUnsupported(nPages, "Rotate");
404
+ }
395
405
  image = image.rot(autoRotation);
396
406
  }
397
407
  // Mirror vertically (up-down) about the x-axis
@@ -404,6 +414,9 @@ class PipelineWorker : public Napi::AsyncWorker {
404
414
  }
405
415
  // Rotate post-extract 90-angle
406
416
  if (rotation != VIPS_ANGLE_D0) {
417
+ if (rotation != VIPS_ANGLE_D180) {
418
+ MultiPageUnsupported(nPages, "Rotate");
419
+ }
407
420
  image = image.rot(rotation);
408
421
  }
409
422
 
@@ -415,7 +428,7 @@ class PipelineWorker : public Napi::AsyncWorker {
415
428
  for (unsigned int i = 0; i < baton->joinChannelIn.size(); i++) {
416
429
  baton->joinChannelIn[i]->access = access;
417
430
  std::tie(joinImage, joinImageType) = sharp::OpenInput(baton->joinChannelIn[i]);
418
- joinImage = sharp::EnsureColourspace(joinImage, baton->colourspaceInput);
431
+ joinImage = sharp::EnsureColourspace(joinImage, baton->colourspacePipeline);
419
432
  image = image.bandjoin(joinImage);
420
433
  }
421
434
  image = image.copy(VImage::option()->set("interpretation", baton->colourspace));
@@ -482,12 +495,10 @@ class PipelineWorker : public Napi::AsyncWorker {
482
495
 
483
496
  // Attention-based or Entropy-based crop
484
497
  MultiPageUnsupported(nPages, "Resize strategy");
485
- image = sharp::StaySequential(image, access);
498
+ image = sharp::StaySequential(image);
486
499
  image = image.smartcrop(baton->width, baton->height, VImage::option()
487
500
  ->set("interesting", baton->position == 16 ? VIPS_INTERESTING_ENTROPY : VIPS_INTERESTING_ATTENTION)
488
- #if (VIPS_MAJOR_VERSION >= 8 && VIPS_MINOR_VERSION >= 15)
489
501
  ->set("premultiplied", shouldPremultiplyAlpha)
490
- #endif
491
502
  ->set("attention_x", &attention_x)
492
503
  ->set("attention_y", &attention_y));
493
504
  baton->hasCropOffset = true;
@@ -503,7 +514,7 @@ class PipelineWorker : public Napi::AsyncWorker {
503
514
  // Rotate post-extract non-90 angle
504
515
  if (!baton->rotateBeforePreExtract && baton->rotationAngle != 0.0) {
505
516
  MultiPageUnsupported(nPages, "Rotate");
506
- image = sharp::StaySequential(image, access);
517
+ image = sharp::StaySequential(image);
507
518
  std::vector<double> background;
508
519
  std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground, shouldPremultiplyAlpha);
509
520
  image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background));
@@ -527,7 +538,7 @@ class PipelineWorker : public Napi::AsyncWorker {
527
538
  // Affine transform
528
539
  if (!baton->affineMatrix.empty()) {
529
540
  MultiPageUnsupported(nPages, "Affine");
530
- image = sharp::StaySequential(image, access);
541
+ image = sharp::StaySequential(image);
531
542
  std::vector<double> background;
532
543
  std::tie(image, background) = sharp::ApplyAlpha(image, baton->affineBackground, shouldPremultiplyAlpha);
533
544
  vips::VInterpolate interp = vips::VInterpolate::new_from_name(
@@ -550,6 +561,7 @@ class PipelineWorker : public Napi::AsyncWorker {
550
561
  std::vector<double> background;
551
562
  std::tie(image, background) = sharp::ApplyAlpha(image, baton->extendBackground, shouldPremultiplyAlpha);
552
563
 
564
+ image = sharp::StaySequential(image, nPages > 1);
553
565
  image = nPages > 1
554
566
  ? sharp::EmbedMultiPage(image,
555
567
  baton->extendLeft, baton->extendTop, baton->width, baton->height,
@@ -558,6 +570,7 @@ class PipelineWorker : public Napi::AsyncWorker {
558
570
  VImage::option()->set("extend", baton->extendWith)->set("background", background));
559
571
  } else {
560
572
  std::vector<double> ignoredBackground(1);
573
+ image = sharp::StaySequential(image);
561
574
  image = nPages > 1
562
575
  ? sharp::EmbedMultiPage(image,
563
576
  baton->extendLeft, baton->extendTop, baton->width, baton->height,
@@ -626,7 +639,7 @@ class PipelineWorker : public Napi::AsyncWorker {
626
639
  sharp::ImageType compositeImageType = sharp::ImageType::UNKNOWN;
627
640
  composite->input->access = access;
628
641
  std::tie(compositeImage, compositeImageType) = sharp::OpenInput(composite->input);
629
- compositeImage = sharp::EnsureColourspace(compositeImage, baton->colourspaceInput);
642
+ compositeImage = sharp::EnsureColourspace(compositeImage, baton->colourspacePipeline);
630
643
  // Verify within current dimensions
631
644
  if (compositeImage.width() > image.width() || compositeImage.height() > image.height()) {
632
645
  throw vips::VError("Image to composite must have same dimensions or smaller");
@@ -653,7 +666,7 @@ class PipelineWorker : public Napi::AsyncWorker {
653
666
  if (across != 0 || down != 0) {
654
667
  int left;
655
668
  int top;
656
- compositeImage = sharp::StaySequential(compositeImage, access).replicate(across, down);
669
+ compositeImage = sharp::StaySequential(compositeImage).replicate(across, down);
657
670
  if (composite->hasOffset) {
658
671
  std::tie(left, top) = sharp::CalculateCrop(
659
672
  compositeImage.width(), compositeImage.height(), image.width(), image.height(),
@@ -711,13 +724,13 @@ class PipelineWorker : public Napi::AsyncWorker {
711
724
 
712
725
  // Apply normalisation - stretch luminance to cover full dynamic range
713
726
  if (baton->normalise) {
714
- image = sharp::StaySequential(image, access);
727
+ image = sharp::StaySequential(image);
715
728
  image = sharp::Normalise(image, baton->normaliseLower, baton->normaliseUpper);
716
729
  }
717
730
 
718
731
  // Apply contrast limiting adaptive histogram equalization (CLAHE)
719
732
  if (baton->claheWidth != 0 && baton->claheHeight != 0) {
720
- image = sharp::StaySequential(image, access);
733
+ image = sharp::StaySequential(image);
721
734
  image = sharp::Clahe(image, baton->claheWidth, baton->claheHeight, baton->claheMaxSlope);
722
735
  }
723
736
 
@@ -727,7 +740,7 @@ class PipelineWorker : public Napi::AsyncWorker {
727
740
  sharp::ImageType booleanImageType = sharp::ImageType::UNKNOWN;
728
741
  baton->boolean->access = access;
729
742
  std::tie(booleanImage, booleanImageType) = sharp::OpenInput(baton->boolean);
730
- booleanImage = sharp::EnsureColourspace(booleanImage, baton->colourspaceInput);
743
+ booleanImage = sharp::EnsureColourspace(booleanImage, baton->colourspacePipeline);
731
744
  image = sharp::Boolean(image, booleanImage, baton->booleanOp);
732
745
  image = sharp::RemoveGifPalette(image);
733
746
  }
@@ -738,8 +751,8 @@ class PipelineWorker : public Napi::AsyncWorker {
738
751
  }
739
752
 
740
753
  // Tint the image
741
- if (baton->tintA < 128.0 || baton->tintB < 128.0) {
742
- image = sharp::Tint(image, baton->tintA, baton->tintB);
754
+ if (baton->tint[0] >= 0.0) {
755
+ image = sharp::Tint(image, baton->tint);
743
756
  }
744
757
 
745
758
  // Remove alpha channel, if any
@@ -760,9 +773,10 @@ class PipelineWorker : public Napi::AsyncWorker {
760
773
  // Convert colourspace, pass the current known interpretation so libvips doesn't have to guess
761
774
  image = image.colourspace(baton->colourspace, VImage::option()->set("source_space", image.interpretation()));
762
775
  // Transform colours from embedded profile to output profile
763
- if (baton->withMetadata && sharp::HasProfile(image) && baton->withMetadataIcc.empty()) {
764
- image = image.icc_transform("srgb", VImage::option()
765
- ->set("embedded", TRUE)
776
+ if ((baton->keepMetadata & VIPS_FOREIGN_KEEP_ICC) && baton->colourspacePipeline != VIPS_INTERPRETATION_CMYK &&
777
+ baton->withIccProfile.empty() && sharp::HasProfile(image)) {
778
+ image = image.icc_transform(processingProfile, VImage::option()
779
+ ->set("embedded", true)
766
780
  ->set("depth", sharp::Is16Bit(image.interpretation()) ? 16 : 8)
767
781
  ->set("intent", VIPS_INTENT_PERCEPTUAL));
768
782
  }
@@ -789,27 +803,40 @@ class PipelineWorker : public Napi::AsyncWorker {
789
803
  }
790
804
 
791
805
  // Apply output ICC profile
792
- if (baton->withMetadata) {
793
- image = image.icc_transform(
794
- baton->withMetadataIcc.empty() ? "srgb" : const_cast<char*>(baton->withMetadataIcc.data()),
795
- VImage::option()
806
+ if (!baton->withIccProfile.empty()) {
807
+ try {
808
+ image = image.icc_transform(const_cast<char*>(baton->withIccProfile.data()), VImage::option()
796
809
  ->set("input_profile", processingProfile)
797
- ->set("embedded", TRUE)
810
+ ->set("embedded", true)
798
811
  ->set("depth", sharp::Is16Bit(image.interpretation()) ? 16 : 8)
799
812
  ->set("intent", VIPS_INTENT_PERCEPTUAL));
813
+ } catch(...) {
814
+ sharp::VipsWarningCallback(nullptr, G_LOG_LEVEL_WARNING, "Invalid profile", nullptr);
815
+ }
816
+ } else if (baton->keepMetadata & VIPS_FOREIGN_KEEP_ICC) {
817
+ image = sharp::SetProfile(image, inputProfile);
818
+ }
819
+
820
+ // Negate the colours in the image
821
+ if (baton->negate) {
822
+ image = sharp::Negate(image, baton->negateAlpha);
800
823
  }
824
+
801
825
  // Override EXIF Orientation tag
802
- if (baton->withMetadata && baton->withMetadataOrientation != -1) {
826
+ if (baton->withMetadataOrientation != -1) {
803
827
  image = sharp::SetExifOrientation(image, baton->withMetadataOrientation);
804
828
  }
805
829
  // Override pixel density
806
830
  if (baton->withMetadataDensity > 0) {
807
831
  image = sharp::SetDensity(image, baton->withMetadataDensity);
808
832
  }
809
- // Metadata key/value pairs, e.g. EXIF
810
- if (!baton->withMetadataStrs.empty()) {
833
+ // EXIF key/value pairs
834
+ if (baton->keepMetadata & VIPS_FOREIGN_KEEP_EXIF) {
811
835
  image = image.copy();
812
- for (const auto& s : baton->withMetadataStrs) {
836
+ if (!baton->withExifMerge) {
837
+ image = sharp::RemoveExif(image);
838
+ }
839
+ for (const auto& s : baton->withExif) {
813
840
  image.set(s.first.data(), s.second.data());
814
841
  }
815
842
  }
@@ -830,7 +857,7 @@ class PipelineWorker : public Napi::AsyncWorker {
830
857
  // Write JPEG to buffer
831
858
  sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
832
859
  VipsArea *area = reinterpret_cast<VipsArea*>(image.jpegsave_buffer(VImage::option()
833
- ->set("strip", !baton->withMetadata)
860
+ ->set("keep", baton->keepMetadata)
834
861
  ->set("Q", baton->jpegQuality)
835
862
  ->set("interlace", baton->jpegProgressive)
836
863
  ->set("subsample_mode", baton->jpegChromaSubsampling == "4:4:4"
@@ -872,7 +899,7 @@ class PipelineWorker : public Napi::AsyncWorker {
872
899
  // Write PNG to buffer
873
900
  sharp::AssertImageTypeDimensions(image, sharp::ImageType::PNG);
874
901
  VipsArea *area = reinterpret_cast<VipsArea*>(image.pngsave_buffer(VImage::option()
875
- ->set("strip", !baton->withMetadata)
902
+ ->set("keep", baton->keepMetadata)
876
903
  ->set("interlace", baton->pngProgressive)
877
904
  ->set("compression", baton->pngCompressionLevel)
878
905
  ->set("filter", baton->pngAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_ALL : VIPS_FOREIGN_PNG_FILTER_NONE)
@@ -891,7 +918,7 @@ class PipelineWorker : public Napi::AsyncWorker {
891
918
  // Write WEBP to buffer
892
919
  sharp::AssertImageTypeDimensions(image, sharp::ImageType::WEBP);
893
920
  VipsArea *area = reinterpret_cast<VipsArea*>(image.webpsave_buffer(VImage::option()
894
- ->set("strip", !baton->withMetadata)
921
+ ->set("keep", baton->keepMetadata)
895
922
  ->set("Q", baton->webpQuality)
896
923
  ->set("lossless", baton->webpLossless)
897
924
  ->set("near_lossless", baton->webpNearLossless)
@@ -911,7 +938,7 @@ class PipelineWorker : public Napi::AsyncWorker {
911
938
  // Write GIF to buffer
912
939
  sharp::AssertImageTypeDimensions(image, sharp::ImageType::GIF);
913
940
  VipsArea *area = reinterpret_cast<VipsArea*>(image.gifsave_buffer(VImage::option()
914
- ->set("strip", !baton->withMetadata)
941
+ ->set("keep", baton->keepMetadata)
915
942
  ->set("bitdepth", baton->gifBitdepth)
916
943
  ->set("effort", baton->gifEffort)
917
944
  ->set("reuse", baton->gifReuse)
@@ -936,10 +963,11 @@ class PipelineWorker : public Napi::AsyncWorker {
936
963
  image = image.cast(VIPS_FORMAT_FLOAT);
937
964
  }
938
965
  VipsArea *area = reinterpret_cast<VipsArea*>(image.tiffsave_buffer(VImage::option()
939
- ->set("strip", !baton->withMetadata)
966
+ ->set("keep", baton->keepMetadata)
940
967
  ->set("Q", baton->tiffQuality)
941
968
  ->set("bitdepth", baton->tiffBitdepth)
942
969
  ->set("compression", baton->tiffCompression)
970
+ ->set("miniswhite", baton->tiffMiniswhite)
943
971
  ->set("predictor", baton->tiffPredictor)
944
972
  ->set("pyramid", baton->tiffPyramid)
945
973
  ->set("tile", baton->tiffTile)
@@ -959,11 +987,11 @@ class PipelineWorker : public Napi::AsyncWorker {
959
987
  sharp::AssertImageTypeDimensions(image, sharp::ImageType::HEIF);
960
988
  image = sharp::RemoveAnimationProperties(image).cast(VIPS_FORMAT_UCHAR);
961
989
  VipsArea *area = reinterpret_cast<VipsArea*>(image.heifsave_buffer(VImage::option()
962
- ->set("strip", !baton->withMetadata)
990
+ ->set("keep", baton->keepMetadata)
963
991
  ->set("Q", baton->heifQuality)
964
992
  ->set("compression", baton->heifCompression)
965
993
  ->set("effort", baton->heifEffort)
966
- ->set("bitdepth", 8)
994
+ ->set("bitdepth", baton->heifBitdepth)
967
995
  ->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
968
996
  ? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
969
997
  ->set("lossless", baton->heifLossless)));
@@ -978,7 +1006,7 @@ class PipelineWorker : public Napi::AsyncWorker {
978
1006
  if (!sharp::HasAlpha(image)) {
979
1007
  baton->tileBackground.pop_back();
980
1008
  }
981
- image = sharp::StaySequential(image, access, baton->tileAngle != 0);
1009
+ image = sharp::StaySequential(image, baton->tileAngle != 0);
982
1010
  vips::VOption *options = BuildOptionsDZ(baton);
983
1011
  VipsArea *area = reinterpret_cast<VipsArea*>(image.dzsave_buffer(options));
984
1012
  baton->bufferOut = static_cast<char*>(area->data);
@@ -991,7 +1019,7 @@ class PipelineWorker : public Napi::AsyncWorker {
991
1019
  // Write JXL to buffer
992
1020
  image = sharp::RemoveAnimationProperties(image);
993
1021
  VipsArea *area = reinterpret_cast<VipsArea*>(image.jxlsave_buffer(VImage::option()
994
- ->set("strip", !baton->withMetadata)
1022
+ ->set("keep", baton->keepMetadata)
995
1023
  ->set("distance", baton->jxlDistance)
996
1024
  ->set("tier", baton->jxlDecodingTier)
997
1025
  ->set("effort", baton->jxlEffort)
@@ -1052,7 +1080,7 @@ class PipelineWorker : public Napi::AsyncWorker {
1052
1080
  // Write JPEG to file
1053
1081
  sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
1054
1082
  image.jpegsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
1055
- ->set("strip", !baton->withMetadata)
1083
+ ->set("keep", baton->keepMetadata)
1056
1084
  ->set("Q", baton->jpegQuality)
1057
1085
  ->set("interlace", baton->jpegProgressive)
1058
1086
  ->set("subsample_mode", baton->jpegChromaSubsampling == "4:4:4"
@@ -1082,7 +1110,7 @@ class PipelineWorker : public Napi::AsyncWorker {
1082
1110
  // Write PNG to file
1083
1111
  sharp::AssertImageTypeDimensions(image, sharp::ImageType::PNG);
1084
1112
  image.pngsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
1085
- ->set("strip", !baton->withMetadata)
1113
+ ->set("keep", baton->keepMetadata)
1086
1114
  ->set("interlace", baton->pngProgressive)
1087
1115
  ->set("compression", baton->pngCompressionLevel)
1088
1116
  ->set("filter", baton->pngAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_ALL : VIPS_FOREIGN_PNG_FILTER_NONE)
@@ -1097,7 +1125,7 @@ class PipelineWorker : public Napi::AsyncWorker {
1097
1125
  // Write WEBP to file
1098
1126
  sharp::AssertImageTypeDimensions(image, sharp::ImageType::WEBP);
1099
1127
  image.webpsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
1100
- ->set("strip", !baton->withMetadata)
1128
+ ->set("keep", baton->keepMetadata)
1101
1129
  ->set("Q", baton->webpQuality)
1102
1130
  ->set("lossless", baton->webpLossless)
1103
1131
  ->set("near_lossless", baton->webpNearLossless)
@@ -1113,7 +1141,7 @@ class PipelineWorker : public Napi::AsyncWorker {
1113
1141
  // Write GIF to file
1114
1142
  sharp::AssertImageTypeDimensions(image, sharp::ImageType::GIF);
1115
1143
  image.gifsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
1116
- ->set("strip", !baton->withMetadata)
1144
+ ->set("keep", baton->keepMetadata)
1117
1145
  ->set("bitdepth", baton->gifBitdepth)
1118
1146
  ->set("effort", baton->gifEffort)
1119
1147
  ->set("reuse", baton->gifReuse)
@@ -1132,10 +1160,11 @@ class PipelineWorker : public Napi::AsyncWorker {
1132
1160
  image = image.cast(VIPS_FORMAT_FLOAT);
1133
1161
  }
1134
1162
  image.tiffsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
1135
- ->set("strip", !baton->withMetadata)
1163
+ ->set("keep", baton->keepMetadata)
1136
1164
  ->set("Q", baton->tiffQuality)
1137
1165
  ->set("bitdepth", baton->tiffBitdepth)
1138
1166
  ->set("compression", baton->tiffCompression)
1167
+ ->set("miniswhite", baton->tiffMiniswhite)
1139
1168
  ->set("predictor", baton->tiffPredictor)
1140
1169
  ->set("pyramid", baton->tiffPyramid)
1141
1170
  ->set("tile", baton->tiffTile)
@@ -1151,11 +1180,11 @@ class PipelineWorker : public Napi::AsyncWorker {
1151
1180
  sharp::AssertImageTypeDimensions(image, sharp::ImageType::HEIF);
1152
1181
  image = sharp::RemoveAnimationProperties(image).cast(VIPS_FORMAT_UCHAR);
1153
1182
  image.heifsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
1154
- ->set("strip", !baton->withMetadata)
1183
+ ->set("keep", baton->keepMetadata)
1155
1184
  ->set("Q", baton->heifQuality)
1156
1185
  ->set("compression", baton->heifCompression)
1157
1186
  ->set("effort", baton->heifEffort)
1158
- ->set("bitdepth", 8)
1187
+ ->set("bitdepth", baton->heifBitdepth)
1159
1188
  ->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
1160
1189
  ? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
1161
1190
  ->set("lossless", baton->heifLossless));
@@ -1165,7 +1194,7 @@ class PipelineWorker : public Napi::AsyncWorker {
1165
1194
  // Write JXL to file
1166
1195
  image = sharp::RemoveAnimationProperties(image);
1167
1196
  image.jxlsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
1168
- ->set("strip", !baton->withMetadata)
1197
+ ->set("keep", baton->keepMetadata)
1169
1198
  ->set("distance", baton->jxlDistance)
1170
1199
  ->set("tier", baton->jxlDecodingTier)
1171
1200
  ->set("effort", baton->jxlEffort)
@@ -1179,7 +1208,7 @@ class PipelineWorker : public Napi::AsyncWorker {
1179
1208
  if (!sharp::HasAlpha(image)) {
1180
1209
  baton->tileBackground.pop_back();
1181
1210
  }
1182
- image = sharp::StaySequential(image, access, baton->tileAngle != 0);
1211
+ image = sharp::StaySequential(image, baton->tileAngle != 0);
1183
1212
  vips::VOption *options = BuildOptionsDZ(baton);
1184
1213
  image.dzsave(const_cast<char*>(baton->fileOut.data()), options);
1185
1214
  baton->formatOut = "dz";
@@ -1187,7 +1216,7 @@ class PipelineWorker : public Napi::AsyncWorker {
1187
1216
  (willMatchInput && inputImageType == sharp::ImageType::VIPS)) {
1188
1217
  // Write V to file
1189
1218
  image.vipssave(const_cast<char*>(baton->fileOut.data()), VImage::option()
1190
- ->set("strip", !baton->withMetadata));
1219
+ ->set("keep", baton->keepMetadata));
1191
1220
  baton->formatOut = "v";
1192
1221
  } else {
1193
1222
  // Unsupported output format
@@ -1215,7 +1244,7 @@ class PipelineWorker : public Napi::AsyncWorker {
1215
1244
  // Handle warnings
1216
1245
  std::string warning = sharp::VipsWarningPop();
1217
1246
  while (!warning.empty()) {
1218
- debuglog.MakeCallback(Receiver().Value(), { Napi::String::New(env, warning) });
1247
+ debuglog.Call(Receiver().Value(), { Napi::String::New(env, warning) });
1219
1248
  warning = sharp::VipsWarningPop();
1220
1249
  }
1221
1250
 
@@ -1248,11 +1277,10 @@ class PipelineWorker : public Napi::AsyncWorker {
1248
1277
  info.Set("attentionX", static_cast<int32_t>(baton->attentionX));
1249
1278
  info.Set("attentionY", static_cast<int32_t>(baton->attentionY));
1250
1279
  }
1251
- if (baton->trimThreshold > 0.0) {
1280
+ if (baton->trimThreshold >= 0.0) {
1252
1281
  info.Set("trimOffsetLeft", static_cast<int32_t>(baton->trimOffsetLeft));
1253
1282
  info.Set("trimOffsetTop", static_cast<int32_t>(baton->trimOffsetTop));
1254
1283
  }
1255
-
1256
1284
  if (baton->input->textAutofitDpi) {
1257
1285
  info.Set("textAutofitDpi", static_cast<uint32_t>(baton->input->textAutofitDpi));
1258
1286
  }
@@ -1263,17 +1291,17 @@ class PipelineWorker : public Napi::AsyncWorker {
1263
1291
  // Pass ownership of output data to Buffer instance
1264
1292
  Napi::Buffer<char> data = Napi::Buffer<char>::NewOrCopy(env, static_cast<char*>(baton->bufferOut),
1265
1293
  baton->bufferOutLength, sharp::FreeCallback);
1266
- Callback().MakeCallback(Receiver().Value(), { env.Null(), data, info });
1294
+ Callback().Call(Receiver().Value(), { env.Null(), data, info });
1267
1295
  } else {
1268
1296
  // Add file size to info
1269
1297
  struct STAT64_STRUCT st;
1270
1298
  if (STAT64_FUNCTION(baton->fileOut.data(), &st) == 0) {
1271
1299
  info.Set("size", static_cast<uint32_t>(st.st_size));
1272
1300
  }
1273
- Callback().MakeCallback(Receiver().Value(), { env.Null(), info });
1301
+ Callback().Call(Receiver().Value(), { env.Null(), info });
1274
1302
  }
1275
1303
  } else {
1276
- Callback().MakeCallback(Receiver().Value(), { Napi::Error::New(env, sharp::TrimEnd(baton->err)).Value() });
1304
+ Callback().Call(Receiver().Value(), { Napi::Error::New(env, sharp::TrimEnd(baton->err)).Value() });
1277
1305
  }
1278
1306
 
1279
1307
  // Delete baton
@@ -1289,9 +1317,9 @@ class PipelineWorker : public Napi::AsyncWorker {
1289
1317
  delete baton;
1290
1318
 
1291
1319
  // Decrement processing task counter
1292
- g_atomic_int_dec_and_test(&sharp::counterProcess);
1293
- Napi::Number queueLength = Napi::Number::New(env, static_cast<double>(sharp::counterQueue));
1294
- queueListener.MakeCallback(Receiver().Value(), { queueLength });
1320
+ sharp::counterProcess--;
1321
+ Napi::Number queueLength = Napi::Number::New(env, static_cast<int>(sharp::counterQueue));
1322
+ queueListener.Call(Receiver().Value(), { queueLength });
1295
1323
  }
1296
1324
 
1297
1325
  private:
@@ -1312,16 +1340,16 @@ class PipelineWorker : public Napi::AsyncWorker {
1312
1340
  std::tuple<VipsAngle, bool, bool>
1313
1341
  CalculateExifRotationAndFlip(int const exifOrientation) {
1314
1342
  VipsAngle rotate = VIPS_ANGLE_D0;
1315
- bool flip = FALSE;
1316
- bool flop = FALSE;
1343
+ bool flip = false;
1344
+ bool flop = false;
1317
1345
  switch (exifOrientation) {
1318
1346
  case 6: rotate = VIPS_ANGLE_D90; break;
1319
1347
  case 3: rotate = VIPS_ANGLE_D180; break;
1320
1348
  case 8: rotate = VIPS_ANGLE_D270; break;
1321
- case 2: flop = TRUE; break; // flop 1
1322
- case 7: flip = TRUE; rotate = VIPS_ANGLE_D90; break; // flip 6
1323
- case 4: flop = TRUE; rotate = VIPS_ANGLE_D180; break; // flop 3
1324
- case 5: flip = TRUE; rotate = VIPS_ANGLE_D270; break; // flip 8
1349
+ case 2: flop = true; break; // flop 1
1350
+ case 7: flip = true; rotate = VIPS_ANGLE_D90; break; // flip 6
1351
+ case 4: flop = true; rotate = VIPS_ANGLE_D180; break; // flop 3
1352
+ case 5: flip = true; rotate = VIPS_ANGLE_D270; break; // flip 8
1325
1353
  }
1326
1354
  return std::make_tuple(rotate, flip, flop);
1327
1355
  }
@@ -1369,7 +1397,7 @@ class PipelineWorker : public Napi::AsyncWorker {
1369
1397
  std::string suffix;
1370
1398
  if (baton->tileFormat == "png") {
1371
1399
  std::vector<std::pair<std::string, std::string>> options {
1372
- {"interlace", baton->pngProgressive ? "TRUE" : "FALSE"},
1400
+ {"interlace", baton->pngProgressive ? "true" : "false"},
1373
1401
  {"compression", std::to_string(baton->pngCompressionLevel)},
1374
1402
  {"filter", baton->pngAdaptiveFiltering ? "all" : "none"}
1375
1403
  };
@@ -1378,31 +1406,31 @@ class PipelineWorker : public Napi::AsyncWorker {
1378
1406
  std::vector<std::pair<std::string, std::string>> options {
1379
1407
  {"Q", std::to_string(baton->webpQuality)},
1380
1408
  {"alpha_q", std::to_string(baton->webpAlphaQuality)},
1381
- {"lossless", baton->webpLossless ? "TRUE" : "FALSE"},
1382
- {"near_lossless", baton->webpNearLossless ? "TRUE" : "FALSE"},
1383
- {"smart_subsample", baton->webpSmartSubsample ? "TRUE" : "FALSE"},
1409
+ {"lossless", baton->webpLossless ? "true" : "false"},
1410
+ {"near_lossless", baton->webpNearLossless ? "true" : "false"},
1411
+ {"smart_subsample", baton->webpSmartSubsample ? "true" : "false"},
1384
1412
  {"preset", vips_enum_nick(VIPS_TYPE_FOREIGN_WEBP_PRESET, baton->webpPreset)},
1385
- {"min_size", baton->webpMinSize ? "TRUE" : "FALSE"},
1386
- {"mixed", baton->webpMixed ? "TRUE" : "FALSE"},
1413
+ {"min_size", baton->webpMinSize ? "true" : "false"},
1414
+ {"mixed", baton->webpMixed ? "true" : "false"},
1387
1415
  {"effort", std::to_string(baton->webpEffort)}
1388
1416
  };
1389
1417
  suffix = AssembleSuffixString(".webp", options);
1390
1418
  } else {
1391
1419
  std::vector<std::pair<std::string, std::string>> options {
1392
1420
  {"Q", std::to_string(baton->jpegQuality)},
1393
- {"interlace", baton->jpegProgressive ? "TRUE" : "FALSE"},
1421
+ {"interlace", baton->jpegProgressive ? "true" : "false"},
1394
1422
  {"subsample_mode", baton->jpegChromaSubsampling == "4:4:4" ? "off" : "on"},
1395
- {"trellis_quant", baton->jpegTrellisQuantisation ? "TRUE" : "FALSE"},
1423
+ {"trellis_quant", baton->jpegTrellisQuantisation ? "true" : "false"},
1396
1424
  {"quant_table", std::to_string(baton->jpegQuantisationTable)},
1397
- {"overshoot_deringing", baton->jpegOvershootDeringing ? "TRUE": "FALSE"},
1398
- {"optimize_scans", baton->jpegOptimiseScans ? "TRUE": "FALSE"},
1399
- {"optimize_coding", baton->jpegOptimiseCoding ? "TRUE": "FALSE"}
1425
+ {"overshoot_deringing", baton->jpegOvershootDeringing ? "true": "false"},
1426
+ {"optimize_scans", baton->jpegOptimiseScans ? "true": "false"},
1427
+ {"optimize_coding", baton->jpegOptimiseCoding ? "true": "false"}
1400
1428
  };
1401
1429
  std::string extname = baton->tileLayout == VIPS_FOREIGN_DZ_LAYOUT_DZ ? ".jpeg" : ".jpg";
1402
1430
  suffix = AssembleSuffixString(extname, options);
1403
1431
  }
1404
1432
  vips::VOption *options = VImage::option()
1405
- ->set("strip", !baton->withMetadata)
1433
+ ->set("keep", baton->keepMetadata)
1406
1434
  ->set("tile_size", baton->tileSize)
1407
1435
  ->set("overlap", baton->tileOverlap)
1408
1436
  ->set("container", baton->tileContainer)
@@ -1519,6 +1547,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1519
1547
  baton->thresholdGrayscale = sharp::AttrAsBool(options, "thresholdGrayscale");
1520
1548
  baton->trimBackground = sharp::AttrAsVectorOfDouble(options, "trimBackground");
1521
1549
  baton->trimThreshold = sharp::AttrAsDouble(options, "trimThreshold");
1550
+ baton->trimLineArt = sharp::AttrAsBool(options, "trimLineArt");
1522
1551
  baton->gamma = sharp::AttrAsDouble(options, "gamma");
1523
1552
  baton->gammaOut = sharp::AttrAsDouble(options, "gammaOut");
1524
1553
  baton->linearA = sharp::AttrAsVectorOfDouble(options, "linearA");
@@ -1527,8 +1556,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1527
1556
  baton->normalise = sharp::AttrAsBool(options, "normalise");
1528
1557
  baton->normaliseLower = sharp::AttrAsUint32(options, "normaliseLower");
1529
1558
  baton->normaliseUpper = sharp::AttrAsUint32(options, "normaliseUpper");
1530
- baton->tintA = sharp::AttrAsDouble(options, "tintA");
1531
- baton->tintB = sharp::AttrAsDouble(options, "tintB");
1559
+ baton->tint = sharp::AttrAsVectorOfDouble(options, "tint");
1532
1560
  baton->claheWidth = sharp::AttrAsUint32(options, "claheWidth");
1533
1561
  baton->claheHeight = sharp::AttrAsUint32(options, "claheHeight");
1534
1562
  baton->claheMaxSlope = sharp::AttrAsUint32(options, "claheMaxSlope");
@@ -1582,10 +1610,10 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1582
1610
  baton->recombMatrix[i] = sharp::AttrAsDouble(recombMatrix, i);
1583
1611
  }
1584
1612
  }
1585
- baton->colourspaceInput = sharp::AttrAsEnum<VipsInterpretation>(
1586
- options, "colourspaceInput", VIPS_TYPE_INTERPRETATION);
1587
- if (baton->colourspaceInput == VIPS_INTERPRETATION_ERROR) {
1588
- baton->colourspaceInput = VIPS_INTERPRETATION_LAST;
1613
+ baton->colourspacePipeline = sharp::AttrAsEnum<VipsInterpretation>(
1614
+ options, "colourspacePipeline", VIPS_TYPE_INTERPRETATION);
1615
+ if (baton->colourspacePipeline == VIPS_INTERPRETATION_ERROR) {
1616
+ baton->colourspacePipeline = VIPS_INTERPRETATION_LAST;
1589
1617
  }
1590
1618
  baton->colourspace = sharp::AttrAsEnum<VipsInterpretation>(options, "colourspace", VIPS_TYPE_INTERPRETATION);
1591
1619
  if (baton->colourspace == VIPS_INTERPRETATION_ERROR) {
@@ -1594,18 +1622,19 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1594
1622
  // Output
1595
1623
  baton->formatOut = sharp::AttrAsStr(options, "formatOut");
1596
1624
  baton->fileOut = sharp::AttrAsStr(options, "fileOut");
1597
- baton->withMetadata = sharp::AttrAsBool(options, "withMetadata");
1625
+ baton->keepMetadata = sharp::AttrAsUint32(options, "keepMetadata");
1598
1626
  baton->withMetadataOrientation = sharp::AttrAsUint32(options, "withMetadataOrientation");
1599
1627
  baton->withMetadataDensity = sharp::AttrAsDouble(options, "withMetadataDensity");
1600
- baton->withMetadataIcc = sharp::AttrAsStr(options, "withMetadataIcc");
1601
- Napi::Object mdStrs = options.Get("withMetadataStrs").As<Napi::Object>();
1602
- Napi::Array mdStrKeys = mdStrs.GetPropertyNames();
1603
- for (unsigned int i = 0; i < mdStrKeys.Length(); i++) {
1604
- std::string k = sharp::AttrAsStr(mdStrKeys, i);
1605
- if (mdStrs.HasOwnProperty(k)) {
1606
- baton->withMetadataStrs.insert(std::make_pair(k, sharp::AttrAsStr(mdStrs, k)));
1628
+ baton->withIccProfile = sharp::AttrAsStr(options, "withIccProfile");
1629
+ Napi::Object withExif = options.Get("withExif").As<Napi::Object>();
1630
+ Napi::Array withExifKeys = withExif.GetPropertyNames();
1631
+ for (unsigned int i = 0; i < withExifKeys.Length(); i++) {
1632
+ std::string k = sharp::AttrAsStr(withExifKeys, i);
1633
+ if (withExif.HasOwnProperty(k)) {
1634
+ baton->withExif.insert(std::make_pair(k, sharp::AttrAsStr(withExif, k)));
1607
1635
  }
1608
1636
  }
1637
+ baton->withExifMerge = sharp::AttrAsBool(options, "withExifMerge");
1609
1638
  baton->timeoutSeconds = sharp::AttrAsUint32(options, "timeoutSeconds");
1610
1639
  // Format-specific
1611
1640
  baton->jpegQuality = sharp::AttrAsUint32(options, "jpegQuality");
@@ -1647,6 +1676,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1647
1676
  baton->gifProgressive = sharp::AttrAsBool(options, "gifProgressive");
1648
1677
  baton->tiffQuality = sharp::AttrAsUint32(options, "tiffQuality");
1649
1678
  baton->tiffPyramid = sharp::AttrAsBool(options, "tiffPyramid");
1679
+ baton->tiffMiniswhite = sharp::AttrAsBool(options, "tiffMiniswhite");
1650
1680
  baton->tiffBitdepth = sharp::AttrAsUint32(options, "tiffBitdepth");
1651
1681
  baton->tiffTile = sharp::AttrAsBool(options, "tiffTile");
1652
1682
  baton->tiffTileWidth = sharp::AttrAsUint32(options, "tiffTileWidth");
@@ -1668,6 +1698,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1668
1698
  options, "heifCompression", VIPS_TYPE_FOREIGN_HEIF_COMPRESSION);
1669
1699
  baton->heifEffort = sharp::AttrAsUint32(options, "heifEffort");
1670
1700
  baton->heifChromaSubsampling = sharp::AttrAsStr(options, "heifChromaSubsampling");
1701
+ baton->heifBitdepth = sharp::AttrAsUint32(options, "heifBitdepth");
1671
1702
  baton->jxlDistance = sharp::AttrAsDouble(options, "jxlDistance");
1672
1703
  baton->jxlDecodingTier = sharp::AttrAsUint32(options, "jxlDecodingTier");
1673
1704
  baton->jxlEffort = sharp::AttrAsUint32(options, "jxlEffort");
@@ -1707,9 +1738,8 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1707
1738
  worker->Queue();
1708
1739
 
1709
1740
  // Increment queued task counter
1710
- g_atomic_int_inc(&sharp::counterQueue);
1711
- Napi::Number queueLength = Napi::Number::New(info.Env(), static_cast<double>(sharp::counterQueue));
1712
- queueListener.MakeCallback(info.This(), { queueLength });
1741
+ Napi::Number queueLength = Napi::Number::New(info.Env(), static_cast<int>(++sharp::counterQueue));
1742
+ queueListener.Call(info.This(), { queueLength });
1713
1743
 
1714
1744
  return info.Env().Undefined();
1715
1745
  }