sharp 0.35.0-rc.0 → 0.35.0-rc.2

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
@@ -84,7 +84,7 @@ class PipelineWorker : public Napi::AsyncWorker {
84
84
  if (nPages == -1) {
85
85
  // Resolve the number of pages if we need to render until the end of the document
86
86
  nPages = image.get_typeof(VIPS_META_N_PAGES) != 0
87
- ? image.get_int(VIPS_META_N_PAGES) - baton->input->page
87
+ ? image.get_int(VIPS_META_N_PAGES) - std::max(0, baton->input->page)
88
88
  : 1;
89
89
  }
90
90
 
@@ -203,9 +203,11 @@ class PipelineWorker : public Napi::AsyncWorker {
203
203
  // - the width or height parameters are specified;
204
204
  // - gamma correction doesn't need to be applied;
205
205
  // - trimming or pre-resize extract isn't required;
206
+ // - gain map processing is not required;
206
207
  // - input colourspace is not specified;
207
208
  bool const shouldPreShrink = (targetResizeWidth > 0 || targetResizeHeight > 0) &&
208
209
  baton->gamma == 0 && baton->topOffsetPre == -1 && baton->trimThreshold < 0.0 &&
210
+ !baton->keepGainMap && !baton->withGainMap &&
209
211
  baton->colourspacePipeline == VIPS_INTERPRETATION_LAST && !(shouldOrientBefore || shouldRotateBefore);
210
212
 
211
213
  if (shouldPreShrink) {
@@ -274,7 +276,7 @@ class PipelineWorker : public Napi::AsyncWorker {
274
276
  }
275
277
  sharp::SetDensity(image, baton->input->density);
276
278
  if (image.width() > 32767 || image.height() > 32767) {
277
- throw vips::VError("Input SVG image will exceed 32767x32767 pixel limit when scaled");
279
+ throw std::runtime_error("Input SVG image will exceed 32767x32767 pixel limit when scaled");
278
280
  }
279
281
  } else if (inputImageType == sharp::ImageType::PDF) {
280
282
  if (baton->input->buffer != nullptr) {
@@ -290,18 +292,26 @@ class PipelineWorker : public Napi::AsyncWorker {
290
292
  }
291
293
  } else {
292
294
  if (inputImageType == sharp::ImageType::SVG && (image.width() > 32767 || image.height() > 32767)) {
293
- throw vips::VError("Input SVG image exceeds 32767x32767 pixel limit");
295
+ throw std::runtime_error("Input SVG image exceeds 32767x32767 pixel limit");
294
296
  }
295
297
  }
296
298
  if (baton->input->autoOrient) {
297
299
  image = sharp::RemoveExifOrientation(image);
298
300
  }
301
+ VImage gainMap;
302
+ int gainMapScaleFactor = 1;
299
303
  if (sharp::HasGainMap(image)) {
300
- if (baton->withGainMap) {
304
+ if (baton->keepGainMap) {
305
+ gainMap = image.gainmap();
306
+ if (image.get_typeof("gainmap-scale-factor") == G_TYPE_INT) {
307
+ gainMapScaleFactor = image.get_int("gainmap-scale-factor");
308
+ }
309
+ } else if (baton->withGainMap) {
301
310
  image = image.uhdr2scRGB();
302
311
  }
303
312
  image = sharp::RemoveGainMap(image);
304
313
  } else {
314
+ baton->keepGainMap = false;
305
315
  baton->withGainMap = false;
306
316
  }
307
317
 
@@ -401,6 +411,11 @@ class PipelineWorker : public Napi::AsyncWorker {
401
411
  image = image.resize(1.0 / hshrink, VImage::option()
402
412
  ->set("vscale", 1.0 / vshrink)
403
413
  ->set("kernel", baton->kernel));
414
+ if (baton->keepGainMap) {
415
+ gainMap = gainMap.resize(1.0 / hshrink, VImage::option()
416
+ ->set("vscale", 1.0 / vshrink)
417
+ ->set("kernel", baton->kernel));
418
+ }
404
419
  }
405
420
 
406
421
  image = sharp::StaySequential(image,
@@ -413,14 +428,23 @@ class PipelineWorker : public Napi::AsyncWorker {
413
428
  MultiPageUnsupported(nPages, "Rotate");
414
429
  }
415
430
  image = image.rot(autoRotation);
431
+ if (baton->keepGainMap) {
432
+ gainMap = gainMap.rot(autoRotation);
433
+ }
416
434
  }
417
435
  // Mirror vertically (up-down) about the x-axis
418
436
  if (baton->flip) {
419
437
  image = image.flip(VIPS_DIRECTION_VERTICAL);
438
+ if (baton->keepGainMap) {
439
+ gainMap = gainMap.flip(VIPS_DIRECTION_VERTICAL);
440
+ }
420
441
  }
421
442
  // Mirror horizontally (left-right) about the y-axis
422
443
  if (baton->flop != autoFlop) {
423
444
  image = image.flip(VIPS_DIRECTION_HORIZONTAL);
445
+ if (baton->keepGainMap) {
446
+ gainMap = gainMap.flip(VIPS_DIRECTION_HORIZONTAL);
447
+ }
424
448
  }
425
449
  // Rotate post-extract 90-angle
426
450
  if (rotation != VIPS_ANGLE_D0) {
@@ -428,6 +452,9 @@ class PipelineWorker : public Napi::AsyncWorker {
428
452
  MultiPageUnsupported(nPages, "Rotate");
429
453
  }
430
454
  image = image.rot(rotation);
455
+ if (baton->keepGainMap) {
456
+ gainMap = gainMap.rot(rotation);
457
+ }
431
458
  }
432
459
 
433
460
  // Join additional color channels to the image
@@ -474,6 +501,12 @@ class PipelineWorker : public Napi::AsyncWorker {
474
501
  : image.embed(left, top, width, height, VImage::option()
475
502
  ->set("extend", VIPS_EXTEND_BACKGROUND)
476
503
  ->set("background", background));
504
+ if (baton->keepGainMap) {
505
+ gainMap = gainMap.embed(left / gainMapScaleFactor, top / gainMapScaleFactor,
506
+ width / gainMapScaleFactor, height / gainMapScaleFactor, VImage::option()
507
+ ->set("extend", VIPS_EXTEND_BACKGROUND)
508
+ ->set("background", 0));
509
+ }
477
510
  } else if (baton->canvas == sharp::Canvas::CROP) {
478
511
  if (baton->width > inputWidth) {
479
512
  baton->width = inputWidth;
@@ -494,12 +527,17 @@ class PipelineWorker : public Napi::AsyncWorker {
494
527
  ? sharp::CropMultiPage(image,
495
528
  left, top, width, height, nPages, &targetPageHeight)
496
529
  : image.extract_area(left, top, width, height);
530
+ if (baton->keepGainMap) {
531
+ gainMap = gainMap.extract_area(left / gainMapScaleFactor, top / gainMapScaleFactor,
532
+ width / gainMapScaleFactor, height / gainMapScaleFactor);
533
+ }
497
534
  } else {
498
535
  int attention_x;
499
536
  int attention_y;
500
537
 
501
538
  // Attention-based or Entropy-based crop
502
539
  MultiPageUnsupported(nPages, "Resize strategy");
540
+ KeepGainMapUnsupported(baton->keepGainMap, "Resize strategy");
503
541
  image = sharp::StaySequential(image);
504
542
  image = image.smartcrop(baton->width, baton->height, VImage::option()
505
543
  ->set("interesting", baton->position == 16 ? VIPS_INTERESTING_ENTROPY : VIPS_INTERESTING_ATTENTION)
@@ -523,6 +561,9 @@ class PipelineWorker : public Napi::AsyncWorker {
523
561
  std::vector<double> background;
524
562
  std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground, shouldPremultiplyAlpha);
525
563
  image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background));
564
+ if (baton->keepGainMap) {
565
+ gainMap = gainMap.rotate(baton->rotationAngle, VImage::option()->set("background", 0));
566
+ }
526
567
  }
527
568
 
528
569
  // Post extraction
@@ -531,18 +572,23 @@ class PipelineWorker : public Napi::AsyncWorker {
531
572
  image = sharp::CropMultiPage(image,
532
573
  baton->leftOffsetPost, baton->topOffsetPost, baton->widthPost, baton->heightPost,
533
574
  nPages, &targetPageHeight);
534
-
535
575
  // heightPost is used in the info object, so update to reflect the number of pages
536
576
  baton->heightPost *= nPages;
537
577
  } else {
538
578
  image = image.extract_area(
539
579
  baton->leftOffsetPost, baton->topOffsetPost, baton->widthPost, baton->heightPost);
580
+ if (baton->keepGainMap) {
581
+ gainMap = gainMap.extract_area(baton->leftOffsetPost / gainMapScaleFactor,
582
+ baton->topOffsetPost / gainMapScaleFactor, baton->widthPost / gainMapScaleFactor,
583
+ baton->heightPost / gainMapScaleFactor);
584
+ }
540
585
  }
541
586
  }
542
587
 
543
588
  // Affine transform
544
589
  if (!baton->affineMatrix.empty()) {
545
590
  MultiPageUnsupported(nPages, "Affine");
591
+ KeepGainMapUnsupported(baton->keepGainMap, "Affine");
546
592
  image = sharp::StaySequential(image);
547
593
  std::vector<double> background;
548
594
  std::tie(image, background) = sharp::ApplyAlpha(image, baton->affineBackground, shouldPremultiplyAlpha);
@@ -573,6 +619,12 @@ class PipelineWorker : public Napi::AsyncWorker {
573
619
  baton->extendWith, background, nPages, &targetPageHeight)
574
620
  : image.embed(baton->extendLeft, baton->extendTop, baton->width, baton->height,
575
621
  VImage::option()->set("extend", baton->extendWith)->set("background", background));
622
+ if (baton->keepGainMap) {
623
+ gainMap = gainMap.embed(baton->extendLeft / gainMapScaleFactor, baton->extendTop / gainMapScaleFactor,
624
+ baton->width / gainMapScaleFactor, baton->height / gainMapScaleFactor, VImage::option()
625
+ ->set("extend", baton->extendWith)
626
+ ->set("background", 0));
627
+ }
576
628
  } else {
577
629
  std::vector<double> ignoredBackground(1);
578
630
  image = sharp::StaySequential(image);
@@ -582,6 +634,12 @@ class PipelineWorker : public Napi::AsyncWorker {
582
634
  baton->extendWith, ignoredBackground, nPages, &targetPageHeight)
583
635
  : image.embed(baton->extendLeft, baton->extendTop, baton->width, baton->height,
584
636
  VImage::option()->set("extend", baton->extendWith));
637
+ if (baton->keepGainMap) {
638
+ gainMap = gainMap.embed(baton->extendLeft / gainMapScaleFactor, baton->extendTop / gainMapScaleFactor,
639
+ baton->width / gainMapScaleFactor, baton->height / gainMapScaleFactor, VImage::option()
640
+ ->set("extend", baton->extendWith)
641
+ ->set("background", 0));
642
+ }
585
643
  }
586
644
  }
587
645
  // Median - must happen before blurring, due to the utility of blurring after thresholding
@@ -608,6 +666,9 @@ class PipelineWorker : public Napi::AsyncWorker {
608
666
  // Blur
609
667
  if (shouldBlur) {
610
668
  image = sharp::Blur(image, baton->blurSigma, baton->precision, baton->minAmpl);
669
+ if (baton->keepGainMap) {
670
+ gainMap = sharp::Blur(gainMap, baton->blurSigma, baton->precision, baton->minAmpl);
671
+ }
611
672
  }
612
673
 
613
674
  // Unflatten the image
@@ -617,6 +678,7 @@ class PipelineWorker : public Napi::AsyncWorker {
617
678
 
618
679
  // Convolve
619
680
  if (shouldConv) {
681
+ KeepGainMapUnsupported(baton->keepGainMap, "Convolve");
620
682
  image = sharp::Convolve(image,
621
683
  baton->convKernelWidth, baton->convKernelHeight,
622
684
  baton->convKernelScale, baton->convKernelOffset,
@@ -625,11 +687,13 @@ class PipelineWorker : public Napi::AsyncWorker {
625
687
 
626
688
  // Recomb
627
689
  if (!baton->recombMatrix.empty()) {
690
+ KeepGainMapUnsupported(baton->keepGainMap, "Recomb");
628
691
  image = sharp::Recomb(image, baton->recombMatrix);
629
692
  }
630
693
 
631
694
  // Modulate
632
695
  if (baton->brightness != 1.0 || baton->saturation != 1.0 || baton->hue != 0.0 || baton->lightness != 0.0) {
696
+ KeepGainMapUnsupported(baton->keepGainMap, "Modulate");
633
697
  image = sharp::Modulate(image, baton->brightness, baton->saturation, baton->hue, baton->lightness);
634
698
  }
635
699
 
@@ -647,6 +711,7 @@ class PipelineWorker : public Napi::AsyncWorker {
647
711
 
648
712
  // Composite
649
713
  if (shouldComposite) {
714
+ KeepGainMapUnsupported(baton->keepGainMap, "Composite");
650
715
  std::vector<VImage> images = { image };
651
716
  std::vector<int> modes, xs, ys;
652
717
  for (Composite *composite : baton->composite) {
@@ -675,7 +740,7 @@ class PipelineWorker : public Napi::AsyncWorker {
675
740
 
676
741
  // Verify within current dimensions
677
742
  if (compositeImage.width() > image.width() || compositeImage.height() > image.height()) {
678
- throw vips::VError("Image to composite must have same dimensions or smaller");
743
+ throw std::runtime_error("Image to composite must have same dimensions or smaller");
679
744
  }
680
745
  // Check if overlay is tiled
681
746
  if (composite->tile) {
@@ -759,18 +824,21 @@ class PipelineWorker : public Napi::AsyncWorker {
759
824
 
760
825
  // Apply normalisation - stretch luminance to cover full dynamic range
761
826
  if (baton->normalise) {
827
+ KeepGainMapUnsupported(baton->keepGainMap, "Normalise");
762
828
  image = sharp::StaySequential(image);
763
829
  image = sharp::Normalise(image, baton->normaliseLower, baton->normaliseUpper);
764
830
  }
765
831
 
766
832
  // Apply contrast limiting adaptive histogram equalization (CLAHE)
767
833
  if (baton->claheWidth != 0 && baton->claheHeight != 0) {
834
+ KeepGainMapUnsupported(baton->keepGainMap, "Clahe");
768
835
  image = sharp::StaySequential(image);
769
836
  image = sharp::Clahe(image, baton->claheWidth, baton->claheHeight, baton->claheMaxSlope);
770
837
  }
771
838
 
772
839
  // Apply bitwise boolean operation between images
773
840
  if (baton->boolean != nullptr) {
841
+ KeepGainMapUnsupported(baton->keepGainMap, "Boolean");
774
842
  VImage booleanImage;
775
843
  sharp::ImageType booleanImageType = sharp::ImageType::UNKNOWN;
776
844
  baton->boolean->access = access;
@@ -813,6 +881,7 @@ class PipelineWorker : public Napi::AsyncWorker {
813
881
 
814
882
  // Extract channel
815
883
  if (baton->extractChannel > -1) {
884
+ KeepGainMapUnsupported(baton->keepGainMap, "Extract channel");
816
885
  if (baton->extractChannel >= image.bands()) {
817
886
  if (baton->extractChannel == 3 && image.has_alpha()) {
818
887
  baton->extractChannel = image.bands() - 1;
@@ -847,6 +916,9 @@ class PipelineWorker : public Napi::AsyncWorker {
847
916
  // Negate the colours in the image
848
917
  if (baton->negate) {
849
918
  image = sharp::Negate(image, baton->negateAlpha);
919
+ if (baton->keepGainMap) {
920
+ gainMap = sharp::Negate(gainMap, false);
921
+ }
850
922
  }
851
923
 
852
924
  // Override EXIF Orientation tag
@@ -893,18 +965,27 @@ class PipelineWorker : public Napi::AsyncWorker {
893
965
  if (baton->formatOut == "jpeg" || (baton->formatOut == "input" && inputImageType == sharp::ImageType::JPEG)) {
894
966
  // Write JPEG to buffer
895
967
  sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
896
- VipsArea *area = reinterpret_cast<VipsArea*>(image.jpegsave_buffer(VImage::option()
897
- ->set("keep", baton->keepMetadata)
898
- ->set("Q", baton->jpegQuality)
899
- ->set("interlace", baton->jpegProgressive)
900
- ->set("subsample_mode", baton->jpegChromaSubsampling == "4:4:4"
901
- ? VIPS_FOREIGN_SUBSAMPLE_OFF
902
- : VIPS_FOREIGN_SUBSAMPLE_ON)
903
- ->set("trellis_quant", baton->jpegTrellisQuantisation)
904
- ->set("quant_table", baton->jpegQuantisationTable)
905
- ->set("overshoot_deringing", baton->jpegOvershootDeringing)
906
- ->set("optimize_scans", baton->jpegOptimiseScans)
907
- ->set("optimize_coding", baton->jpegOptimiseCoding)));
968
+ VipsArea *area;
969
+ if (baton->keepGainMap) {
970
+ image = ReattachGainMap(image, gainMap, baton);
971
+ area = reinterpret_cast<VipsArea*>(image.uhdrsave_buffer(VImage::option()
972
+ ->set("keep", baton->keepMetadata)
973
+ ->set("Q", baton->jpegQuality)
974
+ ->set("gainmap_scale_factor", gainMapScaleFactor)));
975
+ } else {
976
+ area = reinterpret_cast<VipsArea*>(image.jpegsave_buffer(VImage::option()
977
+ ->set("keep", baton->keepMetadata)
978
+ ->set("Q", baton->jpegQuality)
979
+ ->set("interlace", baton->jpegProgressive)
980
+ ->set("subsample_mode", baton->jpegChromaSubsampling == "4:4:4"
981
+ ? VIPS_FOREIGN_SUBSAMPLE_OFF
982
+ : VIPS_FOREIGN_SUBSAMPLE_ON)
983
+ ->set("trellis_quant", baton->jpegTrellisQuantisation)
984
+ ->set("quant_table", baton->jpegQuantisationTable)
985
+ ->set("overshoot_deringing", baton->jpegOvershootDeringing)
986
+ ->set("optimize_scans", baton->jpegOptimiseScans)
987
+ ->set("optimize_coding", baton->jpegOptimiseCoding)));
988
+ }
908
989
  baton->bufferOut = static_cast<char*>(area->data);
909
990
  baton->bufferOutLength = area->length;
910
991
  area->free_fn = nullptr;
@@ -1086,20 +1167,19 @@ class PipelineWorker : public Napi::AsyncWorker {
1086
1167
  // Get raw image data
1087
1168
  baton->bufferOut = static_cast<char*>(image.write_to_memory(&baton->bufferOutLength));
1088
1169
  if (baton->bufferOut == nullptr) {
1089
- (baton->err).append("Could not allocate enough memory for raw output");
1090
- return Error();
1170
+ throw std::runtime_error("Could not allocate enough memory for raw output");
1091
1171
  }
1092
1172
  baton->formatOut = "raw";
1093
1173
  } else {
1094
1174
  // Unsupported output format
1095
- (baton->err).append("Unsupported output format ");
1175
+ auto unsupported = std::string("Unsupported output format ");
1096
1176
  if (baton->formatOut == "input") {
1097
- (baton->err).append("when trying to match input format of ");
1098
- (baton->err).append(ImageTypeId(inputImageType));
1177
+ unsupported.append("when trying to match input format of ");
1178
+ unsupported.append(ImageTypeId(inputImageType));
1099
1179
  } else {
1100
- (baton->err).append(baton->formatOut);
1180
+ unsupported.append(baton->formatOut);
1101
1181
  }
1102
- return Error();
1182
+ throw std::runtime_error(unsupported);
1103
1183
  }
1104
1184
  } else {
1105
1185
  // File output
@@ -1122,18 +1202,26 @@ class PipelineWorker : public Napi::AsyncWorker {
1122
1202
  (willMatchInput && inputImageType == sharp::ImageType::JPEG)) {
1123
1203
  // Write JPEG to file
1124
1204
  sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
1125
- image.jpegsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
1126
- ->set("keep", baton->keepMetadata)
1127
- ->set("Q", baton->jpegQuality)
1128
- ->set("interlace", baton->jpegProgressive)
1129
- ->set("subsample_mode", baton->jpegChromaSubsampling == "4:4:4"
1130
- ? VIPS_FOREIGN_SUBSAMPLE_OFF
1131
- : VIPS_FOREIGN_SUBSAMPLE_ON)
1132
- ->set("trellis_quant", baton->jpegTrellisQuantisation)
1133
- ->set("quant_table", baton->jpegQuantisationTable)
1134
- ->set("overshoot_deringing", baton->jpegOvershootDeringing)
1135
- ->set("optimize_scans", baton->jpegOptimiseScans)
1136
- ->set("optimize_coding", baton->jpegOptimiseCoding));
1205
+ if (baton->keepGainMap) {
1206
+ image = ReattachGainMap(image, gainMap, baton);
1207
+ image.uhdrsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
1208
+ ->set("keep", baton->keepMetadata)
1209
+ ->set("Q", baton->jpegQuality)
1210
+ ->set("gainmap_scale_factor", gainMapScaleFactor));
1211
+ } else {
1212
+ image.jpegsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
1213
+ ->set("keep", baton->keepMetadata)
1214
+ ->set("Q", baton->jpegQuality)
1215
+ ->set("interlace", baton->jpegProgressive)
1216
+ ->set("subsample_mode", baton->jpegChromaSubsampling == "4:4:4"
1217
+ ? VIPS_FOREIGN_SUBSAMPLE_OFF
1218
+ : VIPS_FOREIGN_SUBSAMPLE_ON)
1219
+ ->set("trellis_quant", baton->jpegTrellisQuantisation)
1220
+ ->set("quant_table", baton->jpegQuantisationTable)
1221
+ ->set("overshoot_deringing", baton->jpegOvershootDeringing)
1222
+ ->set("optimize_scans", baton->jpegOptimiseScans)
1223
+ ->set("optimize_coding", baton->jpegOptimiseCoding));
1224
+ }
1137
1225
  baton->formatOut = "jpeg";
1138
1226
  baton->channels = std::min(baton->channels, 3);
1139
1227
  } else if (baton->formatOut == "jp2" || (mightMatchInput && isJp2) ||
@@ -1274,7 +1362,7 @@ class PipelineWorker : public Napi::AsyncWorker {
1274
1362
  return Error();
1275
1363
  }
1276
1364
  }
1277
- } catch (vips::VError const &err) {
1365
+ } catch (std::runtime_error const &err) {
1278
1366
  char const *what = err.what();
1279
1367
  if (what && what[0]) {
1280
1368
  (baton->err).append(what);
@@ -1302,11 +1390,10 @@ class PipelineWorker : public Napi::AsyncWorker {
1302
1390
  if (baton->errUseWarning) {
1303
1391
  (baton->err).append("\n").append(warning);
1304
1392
  } else {
1305
- debuglog.Call(Receiver().Value(), { Napi::String::New(env, warning) });
1393
+ debuglog.SHARP_CALLBACK_FN_NAME(Receiver().Value(), { Napi::String::New(env, warning) });
1306
1394
  }
1307
1395
  warning = sharp::VipsWarningPop();
1308
1396
  }
1309
-
1310
1397
  if (baton->err.empty()) {
1311
1398
  int width = baton->width;
1312
1399
  int height = baton->height;
@@ -1352,17 +1439,15 @@ class PipelineWorker : public Napi::AsyncWorker {
1352
1439
  info.Set("size", static_cast<uint32_t>(baton->bufferOutLength));
1353
1440
  if (baton->typedArrayOut) {
1354
1441
  // ECMAScript ArrayBuffer with Uint8Array view
1355
- Napi::ArrayBuffer ab = Napi::ArrayBuffer::New(env, baton->bufferOutLength);
1356
- memcpy(ab.Data(), baton->bufferOut, baton->bufferOutLength);
1442
+ Napi::TypedArrayOf<uint8_t> data = Napi::Buffer<char>::Copy(env,
1443
+ static_cast<char*>(baton->bufferOut), baton->bufferOutLength);
1357
1444
  sharp::FreeCallback(static_cast<char*>(baton->bufferOut), nullptr);
1358
- Napi::TypedArrayOf<uint8_t> data = Napi::TypedArrayOf<uint8_t>::New(env,
1359
- baton->bufferOutLength, ab, 0, napi_uint8_array);
1360
- Callback().Call(Receiver().Value(), { env.Null(), data, info });
1445
+ Callback().SHARP_CALLBACK_FN_NAME(Receiver().Value(), { env.Null(), data, info });
1361
1446
  } else {
1362
1447
  // Node.js Buffer
1363
1448
  Napi::Buffer<char> data = Napi::Buffer<char>::NewOrCopy(env, static_cast<char*>(baton->bufferOut),
1364
1449
  baton->bufferOutLength, sharp::FreeCallback);
1365
- Callback().Call(Receiver().Value(), { env.Null(), data, info });
1450
+ Callback().SHARP_CALLBACK_FN_NAME(Receiver().Value(), { env.Null(), data, info });
1366
1451
  }
1367
1452
  } else {
1368
1453
  // Add file size to info
@@ -1373,10 +1458,11 @@ class PipelineWorker : public Napi::AsyncWorker {
1373
1458
  info.Set("size", size);
1374
1459
  } catch (...) {}
1375
1460
  }
1376
- Callback().Call(Receiver().Value(), { env.Null(), info });
1461
+ Callback().SHARP_CALLBACK_FN_NAME(Receiver().Value(), { env.Null(), info });
1377
1462
  }
1378
1463
  } else {
1379
- Callback().Call(Receiver().Value(), { Napi::Error::New(env, sharp::TrimEnd(baton->err)).Value() });
1464
+ Callback().SHARP_CALLBACK_FN_NAME(Receiver().Value(),
1465
+ { Napi::Error::New(env, sharp::TrimEnd(baton->err)).Value() });
1380
1466
  }
1381
1467
 
1382
1468
  // Delete baton
@@ -1397,7 +1483,7 @@ class PipelineWorker : public Napi::AsyncWorker {
1397
1483
  // Decrement processing task counter
1398
1484
  sharp::counterProcess--;
1399
1485
  Napi::Number queueLength = Napi::Number::New(env, static_cast<int>(sharp::counterQueue));
1400
- queueListener.Call(Receiver().Value(), { queueLength });
1486
+ queueListener.SHARP_CALLBACK_FN_NAME(Receiver().Value(), { queueLength });
1401
1487
  }
1402
1488
 
1403
1489
  private:
@@ -1407,7 +1493,13 @@ class PipelineWorker : public Napi::AsyncWorker {
1407
1493
 
1408
1494
  void MultiPageUnsupported(int const pages, std::string op) {
1409
1495
  if (pages > 1) {
1410
- throw vips::VError(op + " is not supported for multi-page images");
1496
+ throw std::runtime_error(op + " is not supported for multi-page images");
1497
+ }
1498
+ }
1499
+
1500
+ void KeepGainMapUnsupported(bool const keepGainMap, std::string op) {
1501
+ if (keepGainMap) {
1502
+ throw std::runtime_error(op + " is not supported when keeping gain maps");
1411
1503
  }
1412
1504
  }
1413
1505
 
@@ -1529,6 +1621,21 @@ class PipelineWorker : public Napi::AsyncWorker {
1529
1621
  return options;
1530
1622
  }
1531
1623
 
1624
+ VImage ReattachGainMap(VImage image, VImage gainMap, PipelineBaton *baton) {
1625
+ VipsArea *gainMapJpeg = reinterpret_cast<VipsArea*>(gainMap.jpegsave_buffer(VImage::option()
1626
+ ->set("keep", FALSE)
1627
+ ->set("Q", baton->jpegQuality)
1628
+ ->set("subsample_mode", VIPS_FOREIGN_SUBSAMPLE_OFF)
1629
+ ->set("trellis_quant", baton->jpegTrellisQuantisation)
1630
+ ->set("quant_table", baton->jpegQuantisationTable)
1631
+ ->set("overshoot_deringing", baton->jpegOvershootDeringing)
1632
+ ->set("optimize_coding", baton->jpegOptimiseCoding)));
1633
+ image = image.copy();
1634
+ image.set("gainmap-data", reinterpret_cast<VipsCallbackFn>(vips_area_free_cb),
1635
+ gainMapJpeg->data, gainMapJpeg->length);
1636
+ return image;
1637
+ }
1638
+
1532
1639
  /*
1533
1640
  Clear all thread-local data.
1534
1641
  */
@@ -1730,6 +1837,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1730
1837
  }
1731
1838
  baton->withExifMerge = sharp::AttrAsBool(options, "withExifMerge");
1732
1839
  baton->withXmp = sharp::AttrAsStr(options, "withXmp");
1840
+ baton->keepGainMap = sharp::AttrAsBool(options, "keepGainMap");
1733
1841
  baton->withGainMap = sharp::AttrAsBool(options, "withGainMap");
1734
1842
  baton->timeoutSeconds = sharp::AttrAsUint32(options, "timeoutSeconds");
1735
1843
  baton->loop = sharp::AttrAsUint32(options, "loop");
@@ -1835,7 +1943,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1835
1943
 
1836
1944
  // Increment queued task counter
1837
1945
  Napi::Number queueLength = Napi::Number::New(info.Env(), static_cast<int>(++sharp::counterQueue));
1838
- queueListener.Call(info.This(), { queueLength });
1946
+ queueListener.SHARP_CALLBACK_FN_NAME(info.This(), { queueLength });
1839
1947
 
1840
1948
  return info.Env().Undefined();
1841
1949
  }
package/src/pipeline.h CHANGED
@@ -213,6 +213,7 @@ struct PipelineBaton {
213
213
  bool withExifMerge;
214
214
  std::string withXmp;
215
215
  bool withGainMap;
216
+ bool keepGainMap;
216
217
  int timeoutSeconds;
217
218
  std::vector<double> convKernel;
218
219
  int convKernelWidth;
@@ -365,7 +366,7 @@ struct PipelineBaton {
365
366
  tiffBigtiff(false),
366
367
  tiffPredictor(VIPS_FOREIGN_TIFF_PREDICTOR_HORIZONTAL),
367
368
  tiffPyramid(false),
368
- tiffBitdepth(8),
369
+ tiffBitdepth(0),
369
370
  tiffMiniswhite(false),
370
371
  tiffTile(false),
371
372
  tiffTileHeight(256),
@@ -391,6 +392,7 @@ struct PipelineBaton {
391
392
  withMetadataDensity(0.0),
392
393
  withExifMerge(true),
393
394
  withGainMap(false),
395
+ keepGainMap(false),
394
396
  timeoutSeconds(0),
395
397
  convKernelWidth(0),
396
398
  convKernelHeight(0),
package/src/stats.cc CHANGED
@@ -39,7 +39,7 @@ class StatsWorker : public Napi::AsyncWorker {
39
39
  sharp::ImageType imageType = sharp::ImageType::UNKNOWN;
40
40
  try {
41
41
  std::tie(image, imageType) = OpenInput(baton->input);
42
- } catch (vips::VError const &err) {
42
+ } catch (std::runtime_error const &err) {
43
43
  (baton->err).append(err.what());
44
44
  }
45
45
  if (imageType != sharp::ImageType::UNKNOWN) {
@@ -92,7 +92,7 @@ class StatsWorker : public Napi::AsyncWorker {
92
92
  baton->dominantRed = dx * 16 + 8;
93
93
  baton->dominantGreen = dy * 16 + 8;
94
94
  baton->dominantBlue = dz * 16 + 8;
95
- } catch (vips::VError const &err) {
95
+ } catch (std::runtime_error const &err) {
96
96
  (baton->err).append(err.what());
97
97
  }
98
98
  }
@@ -109,10 +109,9 @@ class StatsWorker : public Napi::AsyncWorker {
109
109
  // Handle warnings
110
110
  std::string warning = sharp::VipsWarningPop();
111
111
  while (!warning.empty()) {
112
- debuglog.Call(Receiver().Value(), { Napi::String::New(env, warning) });
112
+ debuglog.SHARP_CALLBACK_FN_NAME(Receiver().Value(), { Napi::String::New(env, warning) });
113
113
  warning = sharp::VipsWarningPop();
114
114
  }
115
-
116
115
  if (baton->err.empty()) {
117
116
  // Stats Object
118
117
  Napi::Object info = Napi::Object::New(env);
@@ -144,9 +143,10 @@ class StatsWorker : public Napi::AsyncWorker {
144
143
  dominant.Set("g", baton->dominantGreen);
145
144
  dominant.Set("b", baton->dominantBlue);
146
145
  info.Set("dominant", dominant);
147
- Callback().Call(Receiver().Value(), { env.Null(), info });
146
+ Callback().SHARP_CALLBACK_FN_NAME(Receiver().Value(), { env.Null(), info });
148
147
  } else {
149
- Callback().Call(Receiver().Value(), { Napi::Error::New(env, sharp::TrimEnd(baton->err)).Value() });
148
+ Callback().SHARP_CALLBACK_FN_NAME(Receiver().Value(),
149
+ { Napi::Error::New(env, sharp::TrimEnd(baton->err)).Value() });
150
150
  }
151
151
 
152
152
  delete baton->input;
package/src/utilities.cc CHANGED
@@ -244,7 +244,7 @@ Napi::Value _maxColourDistance(const Napi::CallbackInfo& info) {
244
244
  }
245
245
  // Calculate colour distance
246
246
  maxColourDistance = image1.dE00(image2).max();
247
- } catch (vips::VError const &err) {
247
+ } catch (std::runtime_error const &err) {
248
248
  throw Napi::Error::New(env, err.what());
249
249
  }
250
250