sharp 0.27.0 → 0.28.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/common.cc CHANGED
@@ -36,6 +36,9 @@ namespace sharp {
36
36
  std::string AttrAsStr(Napi::Object obj, std::string attr) {
37
37
  return obj.Get(attr).As<Napi::String>();
38
38
  }
39
+ std::string AttrAsStr(Napi::Object obj, unsigned int const attr) {
40
+ return obj.Get(attr).As<Napi::String>();
41
+ }
39
42
  uint32_t AttrAsUint32(Napi::Object obj, std::string attr) {
40
43
  return obj.Get(attr).As<Napi::Number>().Uint32Value();
41
44
  }
@@ -104,12 +107,22 @@ namespace sharp {
104
107
  if (HasAttr(input, "level")) {
105
108
  descriptor->level = AttrAsUint32(input, "level");
106
109
  }
110
+ // subIFD (OME-TIFF)
111
+ if (HasAttr(input, "subifd")) {
112
+ descriptor->subifd = AttrAsInt32(input, "subifd");
113
+ }
107
114
  // Create new image
108
115
  if (HasAttr(input, "createChannels")) {
109
116
  descriptor->createChannels = AttrAsUint32(input, "createChannels");
110
117
  descriptor->createWidth = AttrAsUint32(input, "createWidth");
111
118
  descriptor->createHeight = AttrAsUint32(input, "createHeight");
112
- descriptor->createBackground = AttrAsVectorOfDouble(input, "createBackground");
119
+ if (HasAttr(input, "createNoiseType")) {
120
+ descriptor->createNoiseType = AttrAsStr(input, "createNoiseType");
121
+ descriptor->createNoiseMean = AttrAsDouble(input, "createNoiseMean");
122
+ descriptor->createNoiseSigma = AttrAsDouble(input, "createNoiseSigma");
123
+ } else {
124
+ descriptor->createBackground = AttrAsVectorOfDouble(input, "createBackground");
125
+ }
113
126
  }
114
127
  // Limit input images to a given number of pixels, where pixels = width * height
115
128
  descriptor->limitInputPixels = AttrAsUint32(input, "limitInputPixels");
@@ -189,31 +202,40 @@ namespace sharp {
189
202
  return id;
190
203
  }
191
204
 
205
+ /**
206
+ * Regenerate this table with something like:
207
+ *
208
+ * $ vips -l foreign | grep -i load | awk '{ print $2, $1; }'
209
+ *
210
+ * Plus a bit of editing.
211
+ */
192
212
  std::map<std::string, ImageType> loaderToType = {
193
- { "jpegload", ImageType::JPEG },
194
- { "jpegload_buffer", ImageType::JPEG },
195
- { "pngload", ImageType::PNG },
196
- { "pngload_buffer", ImageType::PNG },
197
- { "webpload", ImageType::WEBP },
198
- { "webpload_buffer", ImageType::WEBP },
199
- { "tiffload", ImageType::TIFF },
200
- { "tiffload_buffer", ImageType::TIFF },
201
- { "gifload", ImageType::GIF },
202
- { "gifload_buffer", ImageType::GIF },
203
- { "svgload", ImageType::SVG },
204
- { "svgload_buffer", ImageType::SVG },
205
- { "heifload", ImageType::HEIF },
206
- { "heifload_buffer", ImageType::HEIF },
207
- { "pdfload", ImageType::PDF },
208
- { "pdfload_buffer", ImageType::PDF },
209
- { "magickload", ImageType::MAGICK },
210
- { "magickload_buffer", ImageType::MAGICK },
211
- { "openslideload", ImageType::OPENSLIDE },
212
- { "ppmload", ImageType::PPM },
213
- { "fitsload", ImageType::FITS },
214
- { "openexrload", ImageType::EXR },
215
- { "vipsload", ImageType::VIPS },
216
- { "rawload", ImageType::RAW }
213
+ { "VipsForeignLoadJpegFile", ImageType::JPEG },
214
+ { "VipsForeignLoadJpegBuffer", ImageType::JPEG },
215
+ { "VipsForeignLoadPngFile", ImageType::PNG },
216
+ { "VipsForeignLoadPngBuffer", ImageType::PNG },
217
+ { "VipsForeignLoadWebpFile", ImageType::WEBP },
218
+ { "VipsForeignLoadWebpBuffer", ImageType::WEBP },
219
+ { "VipsForeignLoadTiffFile", ImageType::TIFF },
220
+ { "VipsForeignLoadTiffBuffer", ImageType::TIFF },
221
+ { "VipsForeignLoadGifFile", ImageType::GIF },
222
+ { "VipsForeignLoadGifBuffer", ImageType::GIF },
223
+ { "VipsForeignLoadNsgifFile", ImageType::GIF },
224
+ { "VipsForeignLoadNsgifBuffer", ImageType::GIF },
225
+ { "VipsForeignLoadSvgFile", ImageType::SVG },
226
+ { "VipsForeignLoadSvgBuffer", ImageType::SVG },
227
+ { "VipsForeignLoadHeifFile", ImageType::HEIF },
228
+ { "VipsForeignLoadHeifBuffer", ImageType::HEIF },
229
+ { "VipsForeignLoadPdfFile", ImageType::PDF },
230
+ { "VipsForeignLoadPdfBuffer", ImageType::PDF },
231
+ { "VipsForeignLoadMagickFile", ImageType::MAGICK },
232
+ { "VipsForeignLoadMagickBuffer", ImageType::MAGICK },
233
+ { "VipsForeignLoadOpenslide", ImageType::OPENSLIDE },
234
+ { "VipsForeignLoadPpmFile", ImageType::PPM },
235
+ { "VipsForeignLoadFits", ImageType::FITS },
236
+ { "VipsForeignLoadOpenexr", ImageType::EXR },
237
+ { "VipsForeignLoadVips", ImageType::VIPS },
238
+ { "VipsForeignLoadRaw", ImageType::RAW }
217
239
  };
218
240
 
219
241
  /*
@@ -223,7 +245,7 @@ namespace sharp {
223
245
  ImageType imageType = ImageType::UNKNOWN;
224
246
  char const *load = vips_foreign_find_load_buffer(buffer, length);
225
247
  if (load != nullptr) {
226
- auto it = loaderToType.find(vips_nickname_find(g_type_from_name(load)));
248
+ auto it = loaderToType.find(load);
227
249
  if (it != loaderToType.end()) {
228
250
  imageType = it->second;
229
251
  }
@@ -238,7 +260,7 @@ namespace sharp {
238
260
  ImageType imageType = ImageType::UNKNOWN;
239
261
  char const *load = vips_foreign_find_load(file);
240
262
  if (load != nullptr) {
241
- auto it = loaderToType.find(vips_nickname_find(g_type_from_name(load)));
263
+ auto it = loaderToType.find(load);
242
264
  if (it != loaderToType.end()) {
243
265
  imageType = it->second;
244
266
  }
@@ -304,6 +326,9 @@ namespace sharp {
304
326
  if (imageType == ImageType::OPENSLIDE) {
305
327
  option->set("level", descriptor->level);
306
328
  }
329
+ if (imageType == ImageType::TIFF) {
330
+ option->set("subifd", descriptor->subifd);
331
+ }
307
332
  image = VImage::new_from_buffer(descriptor->buffer, descriptor->bufferLength, nullptr, option);
308
333
  if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
309
334
  image = SetDensity(image, descriptor->density);
@@ -318,15 +343,35 @@ namespace sharp {
318
343
  } else {
319
344
  if (descriptor->createChannels > 0) {
320
345
  // Create new image
321
- std::vector<double> background = {
322
- descriptor->createBackground[0],
323
- descriptor->createBackground[1],
324
- descriptor->createBackground[2]
325
- };
326
- if (descriptor->createChannels == 4) {
327
- background.push_back(descriptor->createBackground[3]);
346
+ if (descriptor->createNoiseType == "gaussian") {
347
+ int const channels = descriptor->createChannels;
348
+ image = VImage::new_matrix(descriptor->createWidth, descriptor->createHeight);
349
+ std::vector<VImage> bands = {};
350
+ bands.reserve(channels);
351
+ for (int _band = 0; _band < channels; _band++) {
352
+ bands.push_back(image.gaussnoise(
353
+ descriptor->createWidth,
354
+ descriptor->createHeight,
355
+ VImage::option()->set("mean", descriptor->createNoiseMean)->set("sigma", descriptor->createNoiseSigma)));
356
+ }
357
+ image = image.bandjoin(bands);
358
+ image = image.cast(VipsBandFormat::VIPS_FORMAT_UCHAR);
359
+ if (channels < 3) {
360
+ image = image.colourspace(VIPS_INTERPRETATION_B_W);
361
+ } else {
362
+ image = image.colourspace(VIPS_INTERPRETATION_sRGB);
363
+ }
364
+ } else {
365
+ std::vector<double> background = {
366
+ descriptor->createBackground[0],
367
+ descriptor->createBackground[1],
368
+ descriptor->createBackground[2]
369
+ };
370
+ if (descriptor->createChannels == 4) {
371
+ background.push_back(descriptor->createBackground[3]);
372
+ }
373
+ image = VImage::new_matrix(descriptor->createWidth, descriptor->createHeight).new_from_image(background);
328
374
  }
329
- image = VImage::new_matrix(descriptor->createWidth, descriptor->createHeight).new_from_image(background);
330
375
  image.get_image()->Type = VIPS_INTERPRETATION_sRGB;
331
376
  imageType = ImageType::RAW;
332
377
  } else {
@@ -356,6 +401,9 @@ namespace sharp {
356
401
  if (imageType == ImageType::OPENSLIDE) {
357
402
  option->set("level", descriptor->level);
358
403
  }
404
+ if (imageType == ImageType::TIFF) {
405
+ option->set("subifd", descriptor->subifd);
406
+ }
359
407
  image = VImage::new_from_file(descriptor->file.data(), option);
360
408
  if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
361
409
  image = SetDensity(image, descriptor->density);
@@ -388,12 +436,7 @@ namespace sharp {
388
436
  Uses colour space interpretation with number of channels to guess this.
389
437
  */
390
438
  bool HasAlpha(VImage image) {
391
- int const bands = image.bands();
392
- VipsInterpretation const interpretation = image.interpretation();
393
- return (
394
- (bands == 2 && interpretation == VIPS_INTERPRETATION_B_W) ||
395
- (bands == 4 && interpretation != VIPS_INTERPRETATION_CMYK) ||
396
- (bands == 5 && interpretation == VIPS_INTERPRETATION_CMYK));
439
+ return image.has_alpha();
397
440
  }
398
441
 
399
442
  /*
@@ -769,10 +812,10 @@ namespace sharp {
769
812
  /*
770
813
  Ensures alpha channel, if missing.
771
814
  */
772
- VImage EnsureAlpha(VImage image) {
815
+ VImage EnsureAlpha(VImage image, double const value) {
773
816
  if (!HasAlpha(image)) {
774
817
  std::vector<double> alpha;
775
- alpha.push_back(sharp::MaximumImageAlpha(image.interpretation()));
818
+ alpha.push_back(value * sharp::MaximumImageAlpha(image.interpretation()));
776
819
  image = image.bandjoin_const(alpha);
777
820
  }
778
821
  return image;
package/src/common.h CHANGED
@@ -26,8 +26,8 @@
26
26
 
27
27
  #if (VIPS_MAJOR_VERSION < 8) || \
28
28
  (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 10) || \
29
- (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 10 && VIPS_MICRO_VERSION < 5)
30
- #error "libvips version 8.10.5+ is required - please see https://sharp.pixelplumbing.com/install"
29
+ (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 10 && VIPS_MICRO_VERSION < 6)
30
+ #error "libvips version 8.10.6+ is required - please see https://sharp.pixelplumbing.com/install"
31
31
  #endif
32
32
 
33
33
  #if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6)))
@@ -60,10 +60,14 @@ namespace sharp {
60
60
  int pages;
61
61
  int page;
62
62
  int level;
63
+ int subifd;
63
64
  int createChannels;
64
65
  int createWidth;
65
66
  int createHeight;
66
67
  std::vector<double> createBackground;
68
+ std::string createNoiseType;
69
+ double createNoiseMean;
70
+ double createNoiseSigma;
67
71
 
68
72
  InputDescriptor():
69
73
  buffer(nullptr),
@@ -79,15 +83,19 @@ namespace sharp {
79
83
  pages(1),
80
84
  page(0),
81
85
  level(0),
86
+ subifd(-1),
82
87
  createChannels(0),
83
88
  createWidth(0),
84
89
  createHeight(0),
85
- createBackground{ 0.0, 0.0, 0.0, 255.0 } {}
90
+ createBackground{ 0.0, 0.0, 0.0, 255.0 },
91
+ createNoiseMean(0.0),
92
+ createNoiseSigma(0.0) {}
86
93
  };
87
94
 
88
95
  // Convenience methods to access the attributes of a Napi::Object
89
96
  bool HasAttr(Napi::Object obj, std::string attr);
90
97
  std::string AttrAsStr(Napi::Object obj, std::string attr);
98
+ std::string AttrAsStr(Napi::Object obj, unsigned int const attr);
91
99
  uint32_t AttrAsUint32(Napi::Object obj, std::string attr);
92
100
  int32_t AttrAsInt32(Napi::Object obj, std::string attr);
93
101
  int32_t AttrAsInt32(Napi::Object obj, unsigned int const attr);
@@ -291,7 +299,7 @@ namespace sharp {
291
299
  /*
292
300
  Ensures alpha channel, if missing.
293
301
  */
294
- VImage EnsureAlpha(VImage image);
302
+ VImage EnsureAlpha(VImage image, double const value);
295
303
 
296
304
  } // namespace sharp
297
305
 
package/src/metadata.cc CHANGED
@@ -74,6 +74,9 @@ class MetadataWorker : public Napi::AsyncWorker {
74
74
  if (image.get_typeof("heif-primary") == G_TYPE_INT) {
75
75
  baton->pagePrimary = image.get_int("heif-primary");
76
76
  }
77
+ if (image.get_typeof("heif-compression") == VIPS_TYPE_REF_STRING) {
78
+ baton->compression = image.get_string("heif-compression");
79
+ }
77
80
  if (image.get_typeof("openslide.level-count") == VIPS_TYPE_REF_STRING) {
78
81
  int const levels = std::stoi(image.get_string("openslide.level-count"));
79
82
  for (int l = 0; l < levels; l++) {
@@ -83,6 +86,9 @@ class MetadataWorker : public Napi::AsyncWorker {
83
86
  baton->levels.push_back(std::pair<int, int>(width, height));
84
87
  }
85
88
  }
89
+ if (image.get_typeof(VIPS_META_N_SUBIFDS) == G_TYPE_INT) {
90
+ baton->subifds = image.get_int(VIPS_META_N_SUBIFDS);
91
+ }
86
92
  baton->hasProfile = sharp::HasProfile(image);
87
93
  // Derived attributes
88
94
  baton->hasAlpha = sharp::HasAlpha(image);
@@ -186,6 +192,9 @@ class MetadataWorker : public Napi::AsyncWorker {
186
192
  if (baton->pagePrimary > -1) {
187
193
  info.Set("pagePrimary", baton->pagePrimary);
188
194
  }
195
+ if (!baton->compression.empty()) {
196
+ info.Set("compression", baton->compression);
197
+ }
189
198
  if (!baton->levels.empty()) {
190
199
  int i = 0;
191
200
  Napi::Array levels = Napi::Array::New(env, static_cast<size_t>(baton->levels.size()));
@@ -197,6 +206,9 @@ class MetadataWorker : public Napi::AsyncWorker {
197
206
  }
198
207
  info.Set("levels", levels);
199
208
  }
209
+ if (baton->subifds > 0) {
210
+ info.Set("subifds", baton->subifds);
211
+ }
200
212
  info.Set("hasProfile", baton->hasProfile);
201
213
  info.Set("hasAlpha", baton->hasAlpha);
202
214
  if (baton->orientation > 0) {
package/src/metadata.h CHANGED
@@ -39,7 +39,9 @@ struct MetadataBaton {
39
39
  int loop;
40
40
  std::vector<int> delay;
41
41
  int pagePrimary;
42
+ std::string compression;
42
43
  std::vector<std::pair<int, int>> levels;
44
+ int subifds;
43
45
  bool hasProfile;
44
46
  bool hasAlpha;
45
47
  int orientation;
@@ -67,6 +69,7 @@ struct MetadataBaton {
67
69
  pageHeight(0),
68
70
  loop(-1),
69
71
  pagePrimary(-1),
72
+ subifds(0),
70
73
  hasProfile(false),
71
74
  hasAlpha(false),
72
75
  orientation(0),
package/src/operations.cc CHANGED
@@ -149,8 +149,8 @@ namespace sharp {
149
149
  */
150
150
  VImage Recomb(VImage image, std::unique_ptr<double[]> const &matrix) {
151
151
  double *m = matrix.get();
152
+ image = image.colourspace(VIPS_INTERPRETATION_sRGB);
152
153
  return image
153
- .colourspace(VIPS_INTERPRETATION_sRGB)
154
154
  .recomb(image.bands() == 3
155
155
  ? VImage::new_from_memory(
156
156
  m, 9 * sizeof(double), 3, 3, 1, VIPS_FORMAT_DOUBLE
package/src/pipeline.cc CHANGED
@@ -346,7 +346,7 @@ class PipelineWorker : public Napi::AsyncWorker {
346
346
  bool const shouldModulate = baton->brightness != 1.0 || baton->saturation != 1.0 || baton->hue != 0.0;
347
347
 
348
348
  if (shouldComposite && !sharp::HasAlpha(image)) {
349
- image = sharp::EnsureAlpha(image);
349
+ image = sharp::EnsureAlpha(image, 1);
350
350
  }
351
351
 
352
352
  bool const shouldPremultiplyAlpha = sharp::HasAlpha(image) &&
@@ -562,9 +562,17 @@ class PipelineWorker : public Napi::AsyncWorker {
562
562
  // Use gravity in overlay
563
563
  if (compositeImage.width() <= baton->width) {
564
564
  across = static_cast<int>(ceil(static_cast<double>(image.width()) / compositeImage.width()));
565
+ // Ensure odd number of tiles across when gravity is centre, north or south
566
+ if (composite->gravity == 0 || composite->gravity == 1 || composite->gravity == 3) {
567
+ across |= 1;
568
+ }
565
569
  }
566
570
  if (compositeImage.height() <= baton->height) {
567
571
  down = static_cast<int>(ceil(static_cast<double>(image.height()) / compositeImage.height()));
572
+ // Ensure odd number of tiles down when gravity is centre, east or west
573
+ if (composite->gravity == 0 || composite->gravity == 2 || composite->gravity == 4) {
574
+ down |= 1;
575
+ }
568
576
  }
569
577
  if (across != 0 || down != 0) {
570
578
  int left;
@@ -586,7 +594,7 @@ class PipelineWorker : public Napi::AsyncWorker {
586
594
  // Ensure image to composite is sRGB with premultiplied alpha
587
595
  compositeImage = compositeImage.colourspace(VIPS_INTERPRETATION_sRGB);
588
596
  if (!sharp::HasAlpha(compositeImage)) {
589
- compositeImage = sharp::EnsureAlpha(compositeImage);
597
+ compositeImage = sharp::EnsureAlpha(compositeImage, 1);
590
598
  }
591
599
  if (!composite->premultiplied) compositeImage = compositeImage.premultiply();
592
600
  // Calculate position
@@ -594,8 +602,13 @@ class PipelineWorker : public Napi::AsyncWorker {
594
602
  int top;
595
603
  if (composite->hasOffset) {
596
604
  // Composite image at given offsets
597
- std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),
598
- compositeImage.width(), compositeImage.height(), composite->left, composite->top);
605
+ if (composite->tile) {
606
+ std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),
607
+ compositeImage.width(), compositeImage.height(), composite->left, composite->top);
608
+ } else {
609
+ left = composite->left;
610
+ top = composite->top;
611
+ }
599
612
  } else {
600
613
  // Composite image with given gravity
601
614
  std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),
@@ -678,8 +691,8 @@ class PipelineWorker : public Napi::AsyncWorker {
678
691
  }
679
692
 
680
693
  // Ensure alpha channel, if missing
681
- if (baton->ensureAlpha) {
682
- image = sharp::EnsureAlpha(image);
694
+ if (baton->ensureAlpha != -1) {
695
+ image = sharp::EnsureAlpha(image, baton->ensureAlpha);
683
696
  }
684
697
 
685
698
  // Convert image to sRGB, if not already
@@ -704,11 +717,17 @@ class PipelineWorker : public Napi::AsyncWorker {
704
717
  ->set("input_profile", "srgb")
705
718
  ->set("intent", VIPS_INTENT_PERCEPTUAL));
706
719
  }
707
-
708
720
  // Override EXIF Orientation tag
709
721
  if (baton->withMetadata && baton->withMetadataOrientation != -1) {
710
722
  image = sharp::SetExifOrientation(image, baton->withMetadataOrientation);
711
723
  }
724
+ // Metadata key/value pairs, e.g. EXIF
725
+ if (!baton->withMetadataStrs.empty()) {
726
+ image = image.copy();
727
+ for (const auto& s : baton->withMetadataStrs) {
728
+ image.set(s.first.data(), s.second.data());
729
+ }
730
+ }
712
731
 
713
732
  // Number of channels used in output image
714
733
  baton->channels = image.bands();
@@ -730,7 +749,7 @@ class PipelineWorker : public Napi::AsyncWorker {
730
749
  if (baton->formatOut == "jpeg" || (baton->formatOut == "input" && inputImageType == sharp::ImageType::JPEG)) {
731
750
  // Write JPEG to buffer
732
751
  sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
733
- VipsArea *area = VIPS_AREA(image.jpegsave_buffer(VImage::option()
752
+ VipsArea *area = reinterpret_cast<VipsArea*>(image.jpegsave_buffer(VImage::option()
734
753
  ->set("strip", !baton->withMetadata)
735
754
  ->set("Q", baton->jpegQuality)
736
755
  ->set("interlace", baton->jpegProgressive)
@@ -757,7 +776,7 @@ class PipelineWorker : public Napi::AsyncWorker {
757
776
  inputImageType == sharp::ImageType::SVG))) {
758
777
  // Write PNG to buffer
759
778
  sharp::AssertImageTypeDimensions(image, sharp::ImageType::PNG);
760
- VipsArea *area = VIPS_AREA(image.pngsave_buffer(VImage::option()
779
+ VipsArea *area = reinterpret_cast<VipsArea*>(image.pngsave_buffer(VImage::option()
761
780
  ->set("strip", !baton->withMetadata)
762
781
  ->set("interlace", baton->pngProgressive)
763
782
  ->set("compression", baton->pngCompressionLevel)
@@ -775,7 +794,7 @@ class PipelineWorker : public Napi::AsyncWorker {
775
794
  (baton->formatOut == "input" && inputImageType == sharp::ImageType::WEBP)) {
776
795
  // Write WEBP to buffer
777
796
  sharp::AssertImageTypeDimensions(image, sharp::ImageType::WEBP);
778
- VipsArea *area = VIPS_AREA(image.webpsave_buffer(VImage::option()
797
+ VipsArea *area = reinterpret_cast<VipsArea*>(image.webpsave_buffer(VImage::option()
779
798
  ->set("strip", !baton->withMetadata)
780
799
  ->set("Q", baton->webpQuality)
781
800
  ->set("lossless", baton->webpLossless)
@@ -792,7 +811,7 @@ class PipelineWorker : public Napi::AsyncWorker {
792
811
  (baton->formatOut == "input" && inputImageType == sharp::ImageType::GIF && supportsGifOutput)) {
793
812
  // Write GIF to buffer
794
813
  sharp::AssertImageTypeDimensions(image, sharp::ImageType::GIF);
795
- VipsArea *area = VIPS_AREA(image.magicksave_buffer(VImage::option()
814
+ VipsArea *area = reinterpret_cast<VipsArea*>(image.magicksave_buffer(VImage::option()
796
815
  ->set("strip", !baton->withMetadata)
797
816
  ->set("optimize_gif_frames", TRUE)
798
817
  ->set("optimize_gif_transparency", TRUE)
@@ -813,7 +832,7 @@ class PipelineWorker : public Napi::AsyncWorker {
813
832
  if (baton->tiffPredictor == VIPS_FOREIGN_TIFF_PREDICTOR_FLOAT) {
814
833
  image = image.cast(VIPS_FORMAT_FLOAT);
815
834
  }
816
- VipsArea *area = VIPS_AREA(image.tiffsave_buffer(VImage::option()
835
+ VipsArea *area = reinterpret_cast<VipsArea*>(image.tiffsave_buffer(VImage::option()
817
836
  ->set("strip", !baton->withMetadata)
818
837
  ->set("Q", baton->tiffQuality)
819
838
  ->set("bitdepth", baton->tiffBitdepth)
@@ -833,11 +852,15 @@ class PipelineWorker : public Napi::AsyncWorker {
833
852
  } else if (baton->formatOut == "heif" ||
834
853
  (baton->formatOut == "input" && inputImageType == sharp::ImageType::HEIF)) {
835
854
  // Write HEIF to buffer
836
- VipsArea *area = VIPS_AREA(image.heifsave_buffer(VImage::option()
855
+ VipsArea *area = reinterpret_cast<VipsArea*>(image.heifsave_buffer(VImage::option()
837
856
  ->set("strip", !baton->withMetadata)
838
857
  ->set("compression", baton->heifCompression)
839
858
  ->set("Q", baton->heifQuality)
840
859
  ->set("speed", baton->heifSpeed)
860
+ #if defined(VIPS_TYPE_FOREIGN_SUBSAMPLE)
861
+ ->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
862
+ ? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
863
+ #endif
841
864
  ->set("lossless", baton->heifLossless)));
842
865
  baton->bufferOut = static_cast<char*>(area->data);
843
866
  baton->bufferOutLength = area->length;
@@ -951,6 +974,10 @@ class PipelineWorker : public Napi::AsyncWorker {
951
974
  sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
952
975
  baton->channels = std::min(baton->channels, 3);
953
976
  }
977
+ // Cast pixel values to float, if required
978
+ if (baton->tiffPredictor == VIPS_FOREIGN_TIFF_PREDICTOR_FLOAT) {
979
+ image = image.cast(VIPS_FORMAT_FLOAT);
980
+ }
954
981
  image.tiffsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
955
982
  ->set("strip", !baton->withMetadata)
956
983
  ->set("Q", baton->tiffQuality)
@@ -972,6 +999,10 @@ class PipelineWorker : public Napi::AsyncWorker {
972
999
  ->set("Q", baton->heifQuality)
973
1000
  ->set("compression", baton->heifCompression)
974
1001
  ->set("speed", baton->heifSpeed)
1002
+ #if defined(VIPS_TYPE_FOREIGN_SUBSAMPLE)
1003
+ ->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
1004
+ ? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
1005
+ #endif
975
1006
  ->set("lossless", baton->heifLossless));
976
1007
  baton->formatOut = "heif";
977
1008
  } else if (baton->formatOut == "dz" || isDz || isDzZip) {
@@ -1026,6 +1057,7 @@ class PipelineWorker : public Napi::AsyncWorker {
1026
1057
  ->set("angle", CalculateAngleRotation(baton->tileAngle))
1027
1058
  ->set("background", baton->tileBackground)
1028
1059
  ->set("centre", baton->tileCentre)
1060
+ ->set("id", const_cast<char*>(baton->tileId.data()))
1029
1061
  ->set("skip_blanks", baton->tileSkipBlanks);
1030
1062
  // libvips chooses a default depth based on layout. Instead of replicating that logic here by
1031
1063
  // not passing anything - libvips will handle choice
@@ -1315,7 +1347,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1315
1347
  baton->affineInterpolator = vips::VInterpolate::new_from_name(sharp::AttrAsStr(options, "affineInterpolator").data());
1316
1348
 
1317
1349
  baton->removeAlpha = sharp::AttrAsBool(options, "removeAlpha");
1318
- baton->ensureAlpha = sharp::AttrAsBool(options, "ensureAlpha");
1350
+ baton->ensureAlpha = sharp::AttrAsDouble(options, "ensureAlpha");
1319
1351
  if (options.Has("boolean")) {
1320
1352
  baton->boolean = sharp::CreateInputDescriptor(options.Get("boolean").As<Napi::Object>());
1321
1353
  baton->booleanOp = sharp::GetBooleanOperation(sharp::AttrAsStr(options, "booleanOp"));
@@ -1353,6 +1385,12 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1353
1385
  baton->withMetadata = sharp::AttrAsBool(options, "withMetadata");
1354
1386
  baton->withMetadataOrientation = sharp::AttrAsUint32(options, "withMetadataOrientation");
1355
1387
  baton->withMetadataIcc = sharp::AttrAsStr(options, "withMetadataIcc");
1388
+ Napi::Object mdStrs = options.Get("withMetadataStrs").As<Napi::Object>();
1389
+ Napi::Array mdStrKeys = mdStrs.GetPropertyNames();
1390
+ for (unsigned int i = 0; i < mdStrKeys.Length(); i++) {
1391
+ std::string k = sharp::AttrAsStr(mdStrKeys, i);
1392
+ baton->withMetadataStrs.insert(std::make_pair(k, sharp::AttrAsStr(mdStrs, k)));
1393
+ }
1356
1394
  // Format-specific
1357
1395
  baton->jpegQuality = sharp::AttrAsUint32(options, "jpegQuality");
1358
1396
  baton->jpegProgressive = sharp::AttrAsBool(options, "jpegProgressive");
@@ -1396,6 +1434,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1396
1434
  vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_HEIF_COMPRESSION,
1397
1435
  sharp::AttrAsStr(options, "heifCompression").data()));
1398
1436
  baton->heifSpeed = sharp::AttrAsUint32(options, "heifSpeed");
1437
+ baton->heifChromaSubsampling = sharp::AttrAsStr(options, "heifChromaSubsampling");
1399
1438
 
1400
1439
  // Animated output
1401
1440
  if (sharp::HasAttr(options, "pageHeight")) {
@@ -1425,6 +1464,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1425
1464
  vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_DZ_DEPTH,
1426
1465
  sharp::AttrAsStr(options, "tileDepth").data()));
1427
1466
  baton->tileCentre = sharp::AttrAsBool(options, "tileCentre");
1467
+ baton->tileId = sharp::AttrAsStr(options, "tileId");
1428
1468
 
1429
1469
  // Force random access for certain operations
1430
1470
  if (baton->input->access == VIPS_ACCESS_SEQUENTIAL) {
package/src/pipeline.h CHANGED
@@ -18,6 +18,7 @@
18
18
  #include <memory>
19
19
  #include <string>
20
20
  #include <vector>
21
+ #include <unordered_map>
21
22
 
22
23
  #include <napi.h>
23
24
  #include <vips/vips8>
@@ -162,11 +163,13 @@ struct PipelineBaton {
162
163
  int heifQuality;
163
164
  VipsForeignHeifCompression heifCompression;
164
165
  int heifSpeed;
166
+ std::string heifChromaSubsampling;
165
167
  bool heifLossless;
166
168
  std::string err;
167
169
  bool withMetadata;
168
170
  int withMetadataOrientation;
169
171
  std::string withMetadataIcc;
172
+ std::unordered_map<std::string, std::string> withMetadataStrs;
170
173
  std::unique_ptr<double[]> convKernel;
171
174
  int convKernelWidth;
172
175
  int convKernelHeight;
@@ -177,7 +180,7 @@ struct PipelineBaton {
177
180
  VipsOperationBoolean bandBoolOp;
178
181
  int extractChannel;
179
182
  bool removeAlpha;
180
- bool ensureAlpha;
183
+ double ensureAlpha;
181
184
  VipsInterpretation colourspace;
182
185
  int pageHeight;
183
186
  std::vector<int> delay;
@@ -191,6 +194,7 @@ struct PipelineBaton {
191
194
  std::vector<double> tileBackground;
192
195
  int tileSkipBlanks;
193
196
  VipsForeignDzDepth tileDepth;
197
+ std::string tileId;
194
198
  std::unique_ptr<double[]> recombMatrix;
195
199
 
196
200
  PipelineBaton():
@@ -257,7 +261,7 @@ struct PipelineBaton {
257
261
  jpegOptimiseScans(false),
258
262
  jpegOptimiseCoding(true),
259
263
  pngProgressive(false),
260
- pngCompressionLevel(9),
264
+ pngCompressionLevel(6),
261
265
  pngAdaptiveFiltering(false),
262
266
  pngPalette(false),
263
267
  pngQuality(100),
@@ -282,6 +286,7 @@ struct PipelineBaton {
282
286
  heifQuality(50),
283
287
  heifCompression(VIPS_FOREIGN_HEIF_COMPRESSION_AV1),
284
288
  heifSpeed(5),
289
+ heifChromaSubsampling("4:2:0"),
285
290
  heifLossless(false),
286
291
  withMetadata(false),
287
292
  withMetadataOrientation(-1),
@@ -294,7 +299,7 @@ struct PipelineBaton {
294
299
  bandBoolOp(VIPS_OPERATION_BOOLEAN_LAST),
295
300
  extractChannel(-1),
296
301
  removeAlpha(false),
297
- ensureAlpha(false),
302
+ ensureAlpha(-1.0),
298
303
  colourspace(VIPS_INTERPRETATION_LAST),
299
304
  pageHeight(0),
300
305
  delay{-1},
package/src/sharp.cc CHANGED
@@ -23,7 +23,6 @@
23
23
 
24
24
  static void* sharp_vips_init(void*) {
25
25
  g_setenv("VIPS_MIN_STACK_SIZE", "2m", FALSE);
26
- g_setenv("PANGOCAIRO_BACKEND", "fontconfig", FALSE);
27
26
  vips_init("sharp");
28
27
  return nullptr;
29
28
  }
@@ -45,6 +44,7 @@ Napi::Object init(Napi::Env env, Napi::Object exports) {
45
44
  exports.Set("libvipsVersion", Napi::Function::New(env, libvipsVersion));
46
45
  exports.Set("format", Napi::Function::New(env, format));
47
46
  exports.Set("_maxColourDistance", Napi::Function::New(env, _maxColourDistance));
47
+ exports.Set("_isUsingJemalloc", Napi::Function::New(env, _isUsingJemalloc));
48
48
  exports.Set("stats", Napi::Function::New(env, stats));
49
49
  return exports;
50
50
  }
package/src/utilities.cc CHANGED
@@ -225,3 +225,19 @@ Napi::Value _maxColourDistance(const Napi::CallbackInfo& info) {
225
225
 
226
226
  return Napi::Number::New(env, maxColourDistance);
227
227
  }
228
+
229
+ #if defined(__GNUC__)
230
+ // mallctl will be resolved by the runtime linker when jemalloc is being used
231
+ extern "C" {
232
+ int mallctl(const char *name, void *oldp, size_t *oldlenp, void *newp, size_t newlen) __attribute__((weak));
233
+ }
234
+ Napi::Value _isUsingJemalloc(const Napi::CallbackInfo& info) {
235
+ Napi::Env env = info.Env();
236
+ return Napi::Boolean::New(env, mallctl != nullptr);
237
+ }
238
+ #else
239
+ Napi::Value _isUsingJemalloc(const Napi::CallbackInfo& info) {
240
+ Napi::Env env = info.Env();
241
+ return Napi::Boolean::New(env, false);
242
+ }
243
+ #endif
package/src/utilities.h CHANGED
@@ -24,5 +24,6 @@ Napi::Value simd(const Napi::CallbackInfo& info);
24
24
  Napi::Value libvipsVersion(const Napi::CallbackInfo& info);
25
25
  Napi::Value format(const Napi::CallbackInfo& info);
26
26
  Napi::Value _maxColourDistance(const Napi::CallbackInfo& info);
27
+ Napi::Value _isUsingJemalloc(const Napi::CallbackInfo& info);
27
28
 
28
29
  #endif // SRC_UTILITIES_H_