@ohos-ports/sharp 0.34.5-beta.9 → 0.35.5-beta.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.
Files changed (46) hide show
  1. package/LICENSE +191 -0
  2. package/README.md +112 -0
  3. package/{lib/channel.js → dist/channel.cjs} +1 -1
  4. package/dist/channel.mjs +177 -0
  5. package/{lib/colour.js → dist/colour.cjs} +12 -8
  6. package/dist/colour.mjs +199 -0
  7. package/{lib/composite.js → dist/composite.cjs} +8 -7
  8. package/dist/composite.mjs +213 -0
  9. package/{lib/constructor.js → dist/constructor.cjs} +42 -30
  10. package/dist/constructor.mjs +511 -0
  11. package/dist/index.cjs +25 -0
  12. package/{lib/index.d.ts → dist/index.d.cts} +113 -85
  13. package/dist/index.d.mts +2046 -0
  14. package/dist/index.mjs +25 -0
  15. package/{lib/input.js → dist/input.cjs} +47 -37
  16. package/dist/input.mjs +819 -0
  17. package/{lib/is.js → dist/is.cjs} +9 -3
  18. package/dist/is.mjs +149 -0
  19. package/{lib/libvips.js → dist/libvips.cjs} +35 -30
  20. package/dist/libvips.mjs +213 -0
  21. package/{lib/operation.js → dist/operation.cjs} +39 -53
  22. package/dist/operation.mjs +1002 -0
  23. package/{lib/output.js → dist/output.cjs} +167 -47
  24. package/dist/output.mjs +1786 -0
  25. package/{lib/resize.js → dist/resize.cjs} +54 -32
  26. package/dist/resize.mjs +617 -0
  27. package/dist/sharp.cjs +177 -0
  28. package/dist/sharp.mjs +177 -0
  29. package/{lib/utility.js → dist/utility.cjs} +23 -25
  30. package/dist/utility.mjs +301 -0
  31. package/install/build.js +3 -3
  32. package/package.json +56 -56
  33. package/src/binding.gyp +21 -269
  34. package/src/common.cc +81 -25
  35. package/src/common.h +23 -4
  36. package/src/metadata.cc +66 -8
  37. package/src/metadata.h +6 -1
  38. package/src/operations.cc +25 -8
  39. package/src/operations.h +1 -1
  40. package/src/pipeline.cc +231 -72
  41. package/src/pipeline.h +15 -1
  42. package/src/stats.cc +6 -6
  43. package/src/utilities.cc +8 -7
  44. package/install/check.js +0 -73
  45. package/lib/index.js +0 -16
  46. package/lib/sharp.js +0 -154
package/src/pipeline.cc CHANGED
@@ -56,9 +56,9 @@ class PipelineWorker : public Napi::AsyncWorker {
56
56
  hasAlpha |= image.has_alpha();
57
57
  }
58
58
  if (hasAlpha) {
59
- for (auto &image : images) {
60
- if (!image.has_alpha()) {
61
- image = sharp::EnsureAlpha(image, 1);
59
+ for (size_t i = 0; i < images.size(); i++) {
60
+ if (!images[i].has_alpha()) {
61
+ images[i] = sharp::EnsureAlpha(images[i], 1);
62
62
  }
63
63
  }
64
64
  } else {
@@ -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
 
@@ -94,7 +94,10 @@ class PipelineWorker : public Napi::AsyncWorker {
94
94
  // Calculate angle of rotation
95
95
  VipsAngle rotation = VIPS_ANGLE_D0;
96
96
  VipsAngle autoRotation = VIPS_ANGLE_D0;
97
+ VipsAngle gainMapRotation = VIPS_ANGLE_D0;
97
98
  bool autoFlop = false;
99
+ bool gainMapFlip = false;
100
+ bool gainMapFlop = false;
98
101
 
99
102
  if (baton->input->autoOrient) {
100
103
  // Rotate and flip image according to Exif orientation
@@ -115,11 +118,13 @@ class PipelineWorker : public Napi::AsyncWorker {
115
118
  MultiPageUnsupported(nPages, "Rotate");
116
119
  }
117
120
  image = image.rot(autoRotation);
121
+ gainMapRotation = autoRotation;
118
122
  autoRotation = VIPS_ANGLE_D0;
119
123
  }
120
124
  if (autoFlop) {
121
125
  image = image.flip(VIPS_DIRECTION_HORIZONTAL);
122
126
  autoFlop = false;
127
+ gainMapFlop = true;
123
128
  }
124
129
  }
125
130
 
@@ -128,20 +133,24 @@ class PipelineWorker : public Napi::AsyncWorker {
128
133
  if (baton->flip) {
129
134
  image = image.flip(VIPS_DIRECTION_VERTICAL);
130
135
  baton->flip = false;
136
+ gainMapFlip = true;
131
137
  }
132
138
  if (baton->flop) {
133
139
  image = image.flip(VIPS_DIRECTION_HORIZONTAL);
134
140
  baton->flop = false;
141
+ gainMapFlop = true;
135
142
  }
136
143
  if (rotation != VIPS_ANGLE_D0) {
137
144
  if (rotation != VIPS_ANGLE_D180) {
138
145
  MultiPageUnsupported(nPages, "Rotate");
139
146
  }
140
147
  image = image.rot(rotation);
148
+ gainMapRotation = rotation;
141
149
  rotation = VIPS_ANGLE_D0;
142
150
  }
143
151
  if (baton->rotationAngle != 0.0) {
144
152
  MultiPageUnsupported(nPages, "Rotate");
153
+ KeepGainMapUnsupported(baton->keepGainMap, "Rotate");
145
154
  std::vector<double> background;
146
155
  std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground, false);
147
156
  image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background)).copy_memory();
@@ -152,8 +161,9 @@ class PipelineWorker : public Napi::AsyncWorker {
152
161
  // Trim
153
162
  if (baton->trimThreshold >= 0.0) {
154
163
  MultiPageUnsupported(nPages, "Trim");
164
+ KeepGainMapUnsupported(baton->keepGainMap, "Trim");
155
165
  image = sharp::StaySequential(image);
156
- image = sharp::Trim(image, baton->trimBackground, baton->trimThreshold, baton->trimLineArt);
166
+ image = sharp::Trim(image, baton->trimBackground, baton->trimThreshold, baton->trimLineArt, baton->trimMargin);
157
167
  baton->trimOffsetLeft = image.xoffset();
158
168
  baton->trimOffsetTop = image.yoffset();
159
169
  }
@@ -203,9 +213,11 @@ class PipelineWorker : public Napi::AsyncWorker {
203
213
  // - the width or height parameters are specified;
204
214
  // - gamma correction doesn't need to be applied;
205
215
  // - trimming or pre-resize extract isn't required;
216
+ // - gain map processing is not required;
206
217
  // - input colourspace is not specified;
207
218
  bool const shouldPreShrink = (targetResizeWidth > 0 || targetResizeHeight > 0) &&
208
219
  baton->gamma == 0 && baton->topOffsetPre == -1 && baton->trimThreshold < 0.0 &&
220
+ !baton->keepGainMap && !baton->withGainMap &&
209
221
  baton->colourspacePipeline == VIPS_INTERPRETATION_LAST && !(shouldOrientBefore || shouldRotateBefore);
210
222
 
211
223
  if (shouldPreShrink) {
@@ -274,7 +286,7 @@ class PipelineWorker : public Napi::AsyncWorker {
274
286
  }
275
287
  sharp::SetDensity(image, baton->input->density);
276
288
  if (image.width() > 32767 || image.height() > 32767) {
277
- throw vips::VError("Input SVG image will exceed 32767x32767 pixel limit when scaled");
289
+ throw std::runtime_error("Input SVG image will exceed 32767x32767 pixel limit when scaled");
278
290
  }
279
291
  } else if (inputImageType == sharp::ImageType::PDF) {
280
292
  if (baton->input->buffer != nullptr) {
@@ -290,12 +302,42 @@ class PipelineWorker : public Napi::AsyncWorker {
290
302
  }
291
303
  } else {
292
304
  if (inputImageType == sharp::ImageType::SVG && (image.width() > 32767 || image.height() > 32767)) {
293
- throw vips::VError("Input SVG image exceeds 32767x32767 pixel limit");
305
+ throw std::runtime_error("Input SVG image exceeds 32767x32767 pixel limit");
294
306
  }
295
307
  }
296
308
  if (baton->input->autoOrient) {
297
309
  image = sharp::RemoveExifOrientation(image);
298
310
  }
311
+ VImage gainMap;
312
+ int gainMapScaleFactor = 1;
313
+ if (sharp::HasGainMap(image)) {
314
+ if (baton->keepGainMap) {
315
+ gainMap = image.gainmap();
316
+ if (image.get_typeof("gainmap-scale-factor") == G_TYPE_INT) {
317
+ gainMapScaleFactor = image.get_int("gainmap-scale-factor");
318
+ }
319
+ if (gainMapFlip) {
320
+ gainMap = gainMap.flip(VIPS_DIRECTION_VERTICAL);
321
+ }
322
+ if (gainMapFlop) {
323
+ gainMap = gainMap.flip(VIPS_DIRECTION_HORIZONTAL);
324
+ }
325
+ if (gainMapRotation != VIPS_ANGLE_D0) {
326
+ gainMap = gainMap.rot(gainMapRotation);
327
+ }
328
+ if (baton->topOffsetPre != -1) {
329
+ gainMap = gainMap.extract_area(
330
+ baton->leftOffsetPre / gainMapScaleFactor, baton->topOffsetPre / gainMapScaleFactor,
331
+ baton->widthPre / gainMapScaleFactor, baton->heightPre / gainMapScaleFactor);
332
+ }
333
+ } else if (baton->withGainMap) {
334
+ image = image.uhdr2scRGB();
335
+ }
336
+ image = sharp::RemoveGainMap(image);
337
+ } else {
338
+ baton->keepGainMap = false;
339
+ baton->withGainMap = false;
340
+ }
299
341
 
300
342
  // Any pre-shrinking may already have been done
301
343
  inputWidth = image.width();
@@ -335,7 +377,7 @@ class PipelineWorker : public Napi::AsyncWorker {
335
377
  image.interpretation() != VIPS_INTERPRETATION_LABS &&
336
378
  image.interpretation() != VIPS_INTERPRETATION_GREY16 &&
337
379
  baton->colourspacePipeline != VIPS_INTERPRETATION_CMYK &&
338
- !baton->input->ignoreIcc
380
+ !baton->input->ignoreIcc && !baton->withGainMap
339
381
  ) {
340
382
  // Convert to sRGB/P3 using embedded profile
341
383
  try {
@@ -393,6 +435,11 @@ class PipelineWorker : public Napi::AsyncWorker {
393
435
  image = image.resize(1.0 / hshrink, VImage::option()
394
436
  ->set("vscale", 1.0 / vshrink)
395
437
  ->set("kernel", baton->kernel));
438
+ if (baton->keepGainMap) {
439
+ gainMap = gainMap.resize(1.0 / hshrink, VImage::option()
440
+ ->set("vscale", 1.0 / vshrink)
441
+ ->set("kernel", baton->kernel));
442
+ }
396
443
  }
397
444
 
398
445
  image = sharp::StaySequential(image,
@@ -405,14 +452,23 @@ class PipelineWorker : public Napi::AsyncWorker {
405
452
  MultiPageUnsupported(nPages, "Rotate");
406
453
  }
407
454
  image = image.rot(autoRotation);
455
+ if (baton->keepGainMap) {
456
+ gainMap = sharp::StaySequential(gainMap).rot(autoRotation);
457
+ }
408
458
  }
409
459
  // Mirror vertically (up-down) about the x-axis
410
460
  if (baton->flip) {
411
461
  image = image.flip(VIPS_DIRECTION_VERTICAL);
462
+ if (baton->keepGainMap) {
463
+ gainMap = sharp::StaySequential(gainMap).flip(VIPS_DIRECTION_VERTICAL);
464
+ }
412
465
  }
413
466
  // Mirror horizontally (left-right) about the y-axis
414
467
  if (baton->flop != autoFlop) {
415
468
  image = image.flip(VIPS_DIRECTION_HORIZONTAL);
469
+ if (baton->keepGainMap) {
470
+ gainMap = gainMap.flip(VIPS_DIRECTION_HORIZONTAL);
471
+ }
416
472
  }
417
473
  // Rotate post-extract 90-angle
418
474
  if (rotation != VIPS_ANGLE_D0) {
@@ -420,6 +476,9 @@ class PipelineWorker : public Napi::AsyncWorker {
420
476
  MultiPageUnsupported(nPages, "Rotate");
421
477
  }
422
478
  image = image.rot(rotation);
479
+ if (baton->keepGainMap) {
480
+ gainMap = sharp::StaySequential(gainMap).rot(rotation);
481
+ }
423
482
  }
424
483
 
425
484
  // Join additional color channels to the image
@@ -466,6 +525,12 @@ class PipelineWorker : public Napi::AsyncWorker {
466
525
  : image.embed(left, top, width, height, VImage::option()
467
526
  ->set("extend", VIPS_EXTEND_BACKGROUND)
468
527
  ->set("background", background));
528
+ if (baton->keepGainMap) {
529
+ gainMap = gainMap.embed(left / gainMapScaleFactor, top / gainMapScaleFactor,
530
+ width / gainMapScaleFactor, height / gainMapScaleFactor, VImage::option()
531
+ ->set("extend", VIPS_EXTEND_BACKGROUND)
532
+ ->set("background", 0));
533
+ }
469
534
  } else if (baton->canvas == sharp::Canvas::CROP) {
470
535
  if (baton->width > inputWidth) {
471
536
  baton->width = inputWidth;
@@ -486,12 +551,17 @@ class PipelineWorker : public Napi::AsyncWorker {
486
551
  ? sharp::CropMultiPage(image,
487
552
  left, top, width, height, nPages, &targetPageHeight)
488
553
  : image.extract_area(left, top, width, height);
554
+ if (baton->keepGainMap) {
555
+ gainMap = gainMap.extract_area(left / gainMapScaleFactor, top / gainMapScaleFactor,
556
+ width / gainMapScaleFactor, height / gainMapScaleFactor);
557
+ }
489
558
  } else {
490
559
  int attention_x;
491
560
  int attention_y;
492
561
 
493
562
  // Attention-based or Entropy-based crop
494
563
  MultiPageUnsupported(nPages, "Resize strategy");
564
+ KeepGainMapUnsupported(baton->keepGainMap, "Resize strategy");
495
565
  image = sharp::StaySequential(image);
496
566
  image = image.smartcrop(baton->width, baton->height, VImage::option()
497
567
  ->set("interesting", baton->position == 16 ? VIPS_INTERESTING_ENTROPY : VIPS_INTERESTING_ATTENTION)
@@ -511,10 +581,14 @@ class PipelineWorker : public Napi::AsyncWorker {
511
581
  // Rotate post-extract non-90 angle
512
582
  if (!baton->rotateBefore && baton->rotationAngle != 0.0) {
513
583
  MultiPageUnsupported(nPages, "Rotate");
584
+ KeepGainMapUnsupported(baton->keepGainMap, "Rotate");
514
585
  image = sharp::StaySequential(image);
515
586
  std::vector<double> background;
516
587
  std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground, shouldPremultiplyAlpha);
517
588
  image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background));
589
+ if (baton->keepGainMap) {
590
+ gainMap = gainMap.rotate(baton->rotationAngle, VImage::option()->set("background", 0));
591
+ }
518
592
  }
519
593
 
520
594
  // Post extraction
@@ -523,18 +597,23 @@ class PipelineWorker : public Napi::AsyncWorker {
523
597
  image = sharp::CropMultiPage(image,
524
598
  baton->leftOffsetPost, baton->topOffsetPost, baton->widthPost, baton->heightPost,
525
599
  nPages, &targetPageHeight);
526
-
527
600
  // heightPost is used in the info object, so update to reflect the number of pages
528
601
  baton->heightPost *= nPages;
529
602
  } else {
530
603
  image = image.extract_area(
531
604
  baton->leftOffsetPost, baton->topOffsetPost, baton->widthPost, baton->heightPost);
605
+ if (baton->keepGainMap) {
606
+ gainMap = gainMap.extract_area(baton->leftOffsetPost / gainMapScaleFactor,
607
+ baton->topOffsetPost / gainMapScaleFactor, baton->widthPost / gainMapScaleFactor,
608
+ baton->heightPost / gainMapScaleFactor);
609
+ }
532
610
  }
533
611
  }
534
612
 
535
613
  // Affine transform
536
614
  if (!baton->affineMatrix.empty()) {
537
615
  MultiPageUnsupported(nPages, "Affine");
616
+ KeepGainMapUnsupported(baton->keepGainMap, "Affine");
538
617
  image = sharp::StaySequential(image);
539
618
  std::vector<double> background;
540
619
  std::tie(image, background) = sharp::ApplyAlpha(image, baton->affineBackground, shouldPremultiplyAlpha);
@@ -554,26 +633,25 @@ class PipelineWorker : public Napi::AsyncWorker {
554
633
  baton->width = image.width() + baton->extendLeft + baton->extendRight;
555
634
  baton->height = (nPages > 1 ? targetPageHeight : image.height()) + baton->extendTop + baton->extendBottom;
556
635
 
636
+ std::vector<double> background;
557
637
  if (baton->extendWith == VIPS_EXTEND_BACKGROUND) {
558
- std::vector<double> background;
559
638
  std::tie(image, background) = sharp::ApplyAlpha(image, baton->extendBackground, shouldPremultiplyAlpha);
560
-
561
- image = sharp::StaySequential(image, nPages > 1);
562
- image = nPages > 1
563
- ? sharp::EmbedMultiPage(image,
564
- baton->extendLeft, baton->extendTop, baton->width, baton->height,
565
- baton->extendWith, background, nPages, &targetPageHeight)
566
- : image.embed(baton->extendLeft, baton->extendTop, baton->width, baton->height,
567
- VImage::option()->set("extend", baton->extendWith)->set("background", background));
568
- } else {
569
- std::vector<double> ignoredBackground(1);
570
- image = sharp::StaySequential(image);
571
- image = nPages > 1
572
- ? sharp::EmbedMultiPage(image,
573
- baton->extendLeft, baton->extendTop, baton->width, baton->height,
574
- baton->extendWith, ignoredBackground, nPages, &targetPageHeight)
575
- : image.embed(baton->extendLeft, baton->extendTop, baton->width, baton->height,
576
- VImage::option()->set("extend", baton->extendWith));
639
+ }
640
+ image = sharp::StaySequential(image, nPages > 1 || baton->extendWith != VIPS_EXTEND_BACKGROUND);
641
+ auto options = VImage::option()->set("extend", baton->extendWith);
642
+ if (baton->extendWith == VIPS_EXTEND_BACKGROUND) {
643
+ options->set("background", background);
644
+ }
645
+ image = nPages > 1
646
+ ? sharp::EmbedMultiPage(image,
647
+ baton->extendLeft, baton->extendTop, baton->width, baton->height,
648
+ baton->extendWith, background, nPages, &targetPageHeight)
649
+ : image.embed(baton->extendLeft, baton->extendTop, baton->width, baton->height, options);
650
+ if (baton->keepGainMap) {
651
+ gainMap = gainMap.embed(baton->extendLeft / gainMapScaleFactor, baton->extendTop / gainMapScaleFactor,
652
+ baton->width / gainMapScaleFactor, baton->height / gainMapScaleFactor, VImage::option()
653
+ ->set("extend", baton->extendWith)
654
+ ->set("background", 0));
577
655
  }
578
656
  }
579
657
  // Median - must happen before blurring, due to the utility of blurring after thresholding
@@ -600,6 +678,9 @@ class PipelineWorker : public Napi::AsyncWorker {
600
678
  // Blur
601
679
  if (shouldBlur) {
602
680
  image = sharp::Blur(image, baton->blurSigma, baton->precision, baton->minAmpl);
681
+ if (baton->keepGainMap) {
682
+ gainMap = sharp::Blur(gainMap, baton->blurSigma, baton->precision, baton->minAmpl);
683
+ }
603
684
  }
604
685
 
605
686
  // Unflatten the image
@@ -609,6 +690,7 @@ class PipelineWorker : public Napi::AsyncWorker {
609
690
 
610
691
  // Convolve
611
692
  if (shouldConv) {
693
+ KeepGainMapUnsupported(baton->keepGainMap, "Convolve");
612
694
  image = sharp::Convolve(image,
613
695
  baton->convKernelWidth, baton->convKernelHeight,
614
696
  baton->convKernelScale, baton->convKernelOffset,
@@ -617,11 +699,13 @@ class PipelineWorker : public Napi::AsyncWorker {
617
699
 
618
700
  // Recomb
619
701
  if (!baton->recombMatrix.empty()) {
702
+ KeepGainMapUnsupported(baton->keepGainMap, "Recomb");
620
703
  image = sharp::Recomb(image, baton->recombMatrix);
621
704
  }
622
705
 
623
706
  // Modulate
624
707
  if (baton->brightness != 1.0 || baton->saturation != 1.0 || baton->hue != 0.0 || baton->lightness != 0.0) {
708
+ KeepGainMapUnsupported(baton->keepGainMap, "Modulate");
625
709
  image = sharp::Modulate(image, baton->brightness, baton->saturation, baton->hue, baton->lightness);
626
710
  }
627
711
 
@@ -639,6 +723,7 @@ class PipelineWorker : public Napi::AsyncWorker {
639
723
 
640
724
  // Composite
641
725
  if (shouldComposite) {
726
+ KeepGainMapUnsupported(baton->keepGainMap, "Composite");
642
727
  std::vector<VImage> images = { image };
643
728
  std::vector<int> modes, xs, ys;
644
729
  for (Composite *composite : baton->composite) {
@@ -667,7 +752,7 @@ class PipelineWorker : public Napi::AsyncWorker {
667
752
 
668
753
  // Verify within current dimensions
669
754
  if (compositeImage.width() > image.width() || compositeImage.height() > image.height()) {
670
- throw vips::VError("Image to composite must have same dimensions or smaller");
755
+ throw std::runtime_error("Image to composite must have same dimensions or smaller");
671
756
  }
672
757
  // Check if overlay is tiled
673
758
  if (composite->tile) {
@@ -751,18 +836,21 @@ class PipelineWorker : public Napi::AsyncWorker {
751
836
 
752
837
  // Apply normalisation - stretch luminance to cover full dynamic range
753
838
  if (baton->normalise) {
839
+ KeepGainMapUnsupported(baton->keepGainMap, "Normalise");
754
840
  image = sharp::StaySequential(image);
755
841
  image = sharp::Normalise(image, baton->normaliseLower, baton->normaliseUpper);
756
842
  }
757
843
 
758
844
  // Apply contrast limiting adaptive histogram equalization (CLAHE)
759
845
  if (baton->claheWidth != 0 && baton->claheHeight != 0) {
846
+ KeepGainMapUnsupported(baton->keepGainMap, "Clahe");
760
847
  image = sharp::StaySequential(image);
761
848
  image = sharp::Clahe(image, baton->claheWidth, baton->claheHeight, baton->claheMaxSlope);
762
849
  }
763
850
 
764
851
  // Apply bitwise boolean operation between images
765
852
  if (baton->boolean != nullptr) {
853
+ KeepGainMapUnsupported(baton->keepGainMap, "Boolean");
766
854
  VImage booleanImage;
767
855
  sharp::ImageType booleanImageType = sharp::ImageType::UNKNOWN;
768
856
  baton->boolean->access = access;
@@ -805,6 +893,7 @@ class PipelineWorker : public Napi::AsyncWorker {
805
893
 
806
894
  // Extract channel
807
895
  if (baton->extractChannel > -1) {
896
+ KeepGainMapUnsupported(baton->keepGainMap, "Extract channel");
808
897
  if (baton->extractChannel >= image.bands()) {
809
898
  if (baton->extractChannel == 3 && image.has_alpha()) {
810
899
  baton->extractChannel = image.bands() - 1;
@@ -839,6 +928,9 @@ class PipelineWorker : public Napi::AsyncWorker {
839
928
  // Negate the colours in the image
840
929
  if (baton->negate) {
841
930
  image = sharp::Negate(image, baton->negateAlpha);
931
+ if (baton->keepGainMap) {
932
+ gainMap = sharp::Negate(gainMap, false);
933
+ }
842
934
  }
843
935
 
844
936
  // Override EXIF Orientation tag
@@ -877,6 +969,7 @@ class PipelineWorker : public Napi::AsyncWorker {
877
969
  baton->pageHeightOut = image.get_int(VIPS_META_PAGE_HEIGHT);
878
970
  baton->pagesOut = image.get_int(VIPS_META_N_PAGES);
879
971
  }
972
+ baton->hasAlphaOut = image.has_alpha();
880
973
 
881
974
  // Output
882
975
  sharp::SetTimeout(image, baton->timeoutSeconds);
@@ -885,18 +978,27 @@ class PipelineWorker : public Napi::AsyncWorker {
885
978
  if (baton->formatOut == "jpeg" || (baton->formatOut == "input" && inputImageType == sharp::ImageType::JPEG)) {
886
979
  // Write JPEG to buffer
887
980
  sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
888
- VipsArea *area = reinterpret_cast<VipsArea*>(image.jpegsave_buffer(VImage::option()
889
- ->set("keep", baton->keepMetadata)
890
- ->set("Q", baton->jpegQuality)
891
- ->set("interlace", baton->jpegProgressive)
892
- ->set("subsample_mode", baton->jpegChromaSubsampling == "4:4:4"
893
- ? VIPS_FOREIGN_SUBSAMPLE_OFF
894
- : VIPS_FOREIGN_SUBSAMPLE_ON)
895
- ->set("trellis_quant", baton->jpegTrellisQuantisation)
896
- ->set("quant_table", baton->jpegQuantisationTable)
897
- ->set("overshoot_deringing", baton->jpegOvershootDeringing)
898
- ->set("optimize_scans", baton->jpegOptimiseScans)
899
- ->set("optimize_coding", baton->jpegOptimiseCoding)));
981
+ VipsArea *area;
982
+ if (baton->keepGainMap) {
983
+ image = ReattachGainMap(image, gainMap, baton);
984
+ area = reinterpret_cast<VipsArea*>(image.uhdrsave_buffer(VImage::option()
985
+ ->set("keep", baton->keepMetadata)
986
+ ->set("Q", baton->jpegQuality)
987
+ ->set("gainmap_scale_factor", gainMapScaleFactor)));
988
+ } else {
989
+ area = reinterpret_cast<VipsArea*>(image.jpegsave_buffer(VImage::option()
990
+ ->set("keep", baton->keepMetadata)
991
+ ->set("Q", baton->jpegQuality)
992
+ ->set("interlace", baton->jpegProgressive)
993
+ ->set("subsample_mode", baton->jpegChromaSubsampling == "4:4:4"
994
+ ? VIPS_FOREIGN_SUBSAMPLE_OFF
995
+ : VIPS_FOREIGN_SUBSAMPLE_ON)
996
+ ->set("trellis_quant", baton->jpegTrellisQuantisation)
997
+ ->set("quant_table", baton->jpegQuantisationTable)
998
+ ->set("overshoot_deringing", baton->jpegOvershootDeringing)
999
+ ->set("optimize_scans", baton->jpegOptimiseScans)
1000
+ ->set("optimize_coding", baton->jpegOptimiseCoding)));
1001
+ }
900
1002
  baton->bufferOut = static_cast<char*>(area->data);
901
1003
  baton->bufferOutLength = area->length;
902
1004
  area->free_fn = nullptr;
@@ -907,6 +1009,7 @@ class PipelineWorker : public Napi::AsyncWorker {
907
1009
  } else {
908
1010
  baton->channels = std::min(baton->channels, 3);
909
1011
  }
1012
+ baton->hasAlphaOut = false;
910
1013
  } else if (baton->formatOut == "jp2" || (baton->formatOut == "input"
911
1014
  && inputImageType == sharp::ImageType::JP2)) {
912
1015
  // Write JP2 to Buffer
@@ -957,6 +1060,7 @@ class PipelineWorker : public Napi::AsyncWorker {
957
1060
  ->set("effort", baton->webpEffort)
958
1061
  ->set("min_size", baton->webpMinSize)
959
1062
  ->set("mixed", baton->webpMixed)
1063
+ ->set("exact", baton->webpExact)
960
1064
  ->set("alpha_q", baton->webpAlphaQuality)));
961
1065
  baton->bufferOut = static_cast<char*>(area->data);
962
1066
  baton->bufferOutLength = area->length;
@@ -988,6 +1092,7 @@ class PipelineWorker : public Napi::AsyncWorker {
988
1092
  if (baton->tiffCompression == VIPS_FOREIGN_TIFF_COMPRESSION_JPEG) {
989
1093
  sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
990
1094
  baton->channels = std::min(baton->channels, 3);
1095
+ baton->hasAlphaOut = false;
991
1096
  }
992
1097
  // Cast pixel values to float, if required
993
1098
  if (baton->tiffPredictor == VIPS_FOREIGN_TIFF_PREDICTOR_FLOAT) {
@@ -1024,6 +1129,7 @@ class PipelineWorker : public Napi::AsyncWorker {
1024
1129
  ->set("compression", baton->heifCompression)
1025
1130
  ->set("effort", baton->heifEffort)
1026
1131
  ->set("bitdepth", baton->heifBitdepth)
1132
+ ->set("tune", baton->heifTune.c_str())
1027
1133
  ->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
1028
1134
  ? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
1029
1135
  ->set("lossless", baton->heifLossless)));
@@ -1046,6 +1152,9 @@ class PipelineWorker : public Napi::AsyncWorker {
1046
1152
  area->free_fn = nullptr;
1047
1153
  vips_area_unref(area);
1048
1154
  baton->formatOut = "dz";
1155
+ if (baton->tileFormat == "jpeg") {
1156
+ baton->hasAlphaOut = false;
1157
+ }
1049
1158
  } else if (baton->formatOut == "jxl" ||
1050
1159
  (baton->formatOut == "input" && inputImageType == sharp::ImageType::JXL)) {
1051
1160
  // Write JXL to buffer
@@ -1076,20 +1185,19 @@ class PipelineWorker : public Napi::AsyncWorker {
1076
1185
  // Get raw image data
1077
1186
  baton->bufferOut = static_cast<char*>(image.write_to_memory(&baton->bufferOutLength));
1078
1187
  if (baton->bufferOut == nullptr) {
1079
- (baton->err).append("Could not allocate enough memory for raw output");
1080
- return Error();
1188
+ throw std::runtime_error("Could not allocate enough memory for raw output");
1081
1189
  }
1082
1190
  baton->formatOut = "raw";
1083
1191
  } else {
1084
1192
  // Unsupported output format
1085
- (baton->err).append("Unsupported output format ");
1193
+ auto unsupported = std::string("Unsupported output format ");
1086
1194
  if (baton->formatOut == "input") {
1087
- (baton->err).append("when trying to match input format of ");
1088
- (baton->err).append(ImageTypeId(inputImageType));
1195
+ unsupported.append("when trying to match input format of ");
1196
+ unsupported.append(ImageTypeId(inputImageType));
1089
1197
  } else {
1090
- (baton->err).append(baton->formatOut);
1198
+ unsupported.append(baton->formatOut);
1091
1199
  }
1092
- return Error();
1200
+ throw std::runtime_error(unsupported);
1093
1201
  }
1094
1202
  } else {
1095
1203
  // File output
@@ -1112,20 +1220,29 @@ class PipelineWorker : public Napi::AsyncWorker {
1112
1220
  (willMatchInput && inputImageType == sharp::ImageType::JPEG)) {
1113
1221
  // Write JPEG to file
1114
1222
  sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
1115
- image.jpegsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
1116
- ->set("keep", baton->keepMetadata)
1117
- ->set("Q", baton->jpegQuality)
1118
- ->set("interlace", baton->jpegProgressive)
1119
- ->set("subsample_mode", baton->jpegChromaSubsampling == "4:4:4"
1120
- ? VIPS_FOREIGN_SUBSAMPLE_OFF
1121
- : VIPS_FOREIGN_SUBSAMPLE_ON)
1122
- ->set("trellis_quant", baton->jpegTrellisQuantisation)
1123
- ->set("quant_table", baton->jpegQuantisationTable)
1124
- ->set("overshoot_deringing", baton->jpegOvershootDeringing)
1125
- ->set("optimize_scans", baton->jpegOptimiseScans)
1126
- ->set("optimize_coding", baton->jpegOptimiseCoding));
1223
+ if (baton->keepGainMap) {
1224
+ image = ReattachGainMap(image, gainMap, baton);
1225
+ image.uhdrsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
1226
+ ->set("keep", baton->keepMetadata)
1227
+ ->set("Q", baton->jpegQuality)
1228
+ ->set("gainmap_scale_factor", gainMapScaleFactor));
1229
+ } else {
1230
+ image.jpegsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
1231
+ ->set("keep", baton->keepMetadata)
1232
+ ->set("Q", baton->jpegQuality)
1233
+ ->set("interlace", baton->jpegProgressive)
1234
+ ->set("subsample_mode", baton->jpegChromaSubsampling == "4:4:4"
1235
+ ? VIPS_FOREIGN_SUBSAMPLE_OFF
1236
+ : VIPS_FOREIGN_SUBSAMPLE_ON)
1237
+ ->set("trellis_quant", baton->jpegTrellisQuantisation)
1238
+ ->set("quant_table", baton->jpegQuantisationTable)
1239
+ ->set("overshoot_deringing", baton->jpegOvershootDeringing)
1240
+ ->set("optimize_scans", baton->jpegOptimiseScans)
1241
+ ->set("optimize_coding", baton->jpegOptimiseCoding));
1242
+ }
1127
1243
  baton->formatOut = "jpeg";
1128
1244
  baton->channels = std::min(baton->channels, 3);
1245
+ baton->hasAlphaOut = false;
1129
1246
  } else if (baton->formatOut == "jp2" || (mightMatchInput && isJp2) ||
1130
1247
  (willMatchInput && (inputImageType == sharp::ImageType::JP2))) {
1131
1248
  // Write JP2 to file
@@ -1168,6 +1285,7 @@ class PipelineWorker : public Napi::AsyncWorker {
1168
1285
  ->set("effort", baton->webpEffort)
1169
1286
  ->set("min_size", baton->webpMinSize)
1170
1287
  ->set("mixed", baton->webpMixed)
1288
+ ->set("exact", baton->webpExact)
1171
1289
  ->set("alpha_q", baton->webpAlphaQuality));
1172
1290
  baton->formatOut = "webp";
1173
1291
  } else if (baton->formatOut == "gif" || (mightMatchInput && isGif) ||
@@ -1191,6 +1309,7 @@ class PipelineWorker : public Napi::AsyncWorker {
1191
1309
  if (baton->tiffCompression == VIPS_FOREIGN_TIFF_COMPRESSION_JPEG) {
1192
1310
  sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
1193
1311
  baton->channels = std::min(baton->channels, 3);
1312
+ baton->hasAlphaOut = false;
1194
1313
  }
1195
1314
  // Cast pixel values to float, if required
1196
1315
  if (baton->tiffPredictor == VIPS_FOREIGN_TIFF_PREDICTOR_FLOAT) {
@@ -1223,6 +1342,7 @@ class PipelineWorker : public Napi::AsyncWorker {
1223
1342
  ->set("compression", baton->heifCompression)
1224
1343
  ->set("effort", baton->heifEffort)
1225
1344
  ->set("bitdepth", baton->heifBitdepth)
1345
+ ->set("tune", baton->heifTune.c_str())
1226
1346
  ->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
1227
1347
  ? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
1228
1348
  ->set("lossless", baton->heifLossless));
@@ -1250,6 +1370,9 @@ class PipelineWorker : public Napi::AsyncWorker {
1250
1370
  vips::VOption *options = BuildOptionsDZ(baton);
1251
1371
  image.dzsave(const_cast<char*>(baton->fileOut.data()), options);
1252
1372
  baton->formatOut = "dz";
1373
+ if (baton->tileFormat == "jpeg") {
1374
+ baton->hasAlphaOut = false;
1375
+ }
1253
1376
  } else if (baton->formatOut == "v" || (mightMatchInput && isV) ||
1254
1377
  (willMatchInput && inputImageType == sharp::ImageType::VIPS)) {
1255
1378
  // Write V to file
@@ -1262,7 +1385,7 @@ class PipelineWorker : public Napi::AsyncWorker {
1262
1385
  return Error();
1263
1386
  }
1264
1387
  }
1265
- } catch (vips::VError const &err) {
1388
+ } catch (std::runtime_error const &err) {
1266
1389
  char const *what = err.what();
1267
1390
  if (what && what[0]) {
1268
1391
  (baton->err).append(what);
@@ -1290,11 +1413,10 @@ class PipelineWorker : public Napi::AsyncWorker {
1290
1413
  if (baton->errUseWarning) {
1291
1414
  (baton->err).append("\n").append(warning);
1292
1415
  } else {
1293
- debuglog.Call(Receiver().Value(), { Napi::String::New(env, warning) });
1416
+ debuglog.SHARP_CALLBACK_FN_NAME(Receiver().Value(), { Napi::String::New(env, warning) });
1294
1417
  }
1295
1418
  warning = sharp::VipsWarningPop();
1296
1419
  }
1297
-
1298
1420
  if (baton->err.empty()) {
1299
1421
  int width = baton->width;
1300
1422
  int height = baton->height;
@@ -1335,14 +1457,22 @@ class PipelineWorker : public Napi::AsyncWorker {
1335
1457
  info.Set("pageHeight", static_cast<int32_t>(baton->pageHeightOut));
1336
1458
  info.Set("pages", static_cast<int32_t>(baton->pagesOut));
1337
1459
  }
1460
+ info.Set("hasAlpha", baton->hasAlphaOut);
1338
1461
 
1339
1462
  if (baton->bufferOutLength > 0) {
1340
- // Add buffer size to info
1341
1463
  info.Set("size", static_cast<uint32_t>(baton->bufferOutLength));
1342
- // Pass ownership of output data to Buffer instance
1343
- Napi::Buffer<char> data = Napi::Buffer<char>::NewOrCopy(env, static_cast<char*>(baton->bufferOut),
1344
- baton->bufferOutLength, sharp::FreeCallback);
1345
- Callback().Call(Receiver().Value(), { env.Null(), data, info });
1464
+ if (baton->typedArrayOut) {
1465
+ // ECMAScript ArrayBuffer with Uint8Array view
1466
+ Napi::TypedArrayOf<uint8_t> data = Napi::Buffer<char>::Copy(env,
1467
+ static_cast<char*>(baton->bufferOut), baton->bufferOutLength);
1468
+ sharp::FreeCallback(static_cast<char*>(baton->bufferOut), nullptr);
1469
+ Callback().SHARP_CALLBACK_FN_NAME(Receiver().Value(), { env.Null(), data, info });
1470
+ } else {
1471
+ // Node.js Buffer
1472
+ Napi::Buffer<char> data = Napi::Buffer<char>::NewOrCopy(env, static_cast<char*>(baton->bufferOut),
1473
+ baton->bufferOutLength, sharp::FreeCallback);
1474
+ Callback().SHARP_CALLBACK_FN_NAME(Receiver().Value(), { env.Null(), data, info });
1475
+ }
1346
1476
  } else {
1347
1477
  // Add file size to info
1348
1478
  if (baton->formatOut != "dz" || sharp::IsDzZip(baton->fileOut)) {
@@ -1352,10 +1482,11 @@ class PipelineWorker : public Napi::AsyncWorker {
1352
1482
  info.Set("size", size);
1353
1483
  } catch (...) {}
1354
1484
  }
1355
- Callback().Call(Receiver().Value(), { env.Null(), info });
1485
+ Callback().SHARP_CALLBACK_FN_NAME(Receiver().Value(), { env.Null(), info });
1356
1486
  }
1357
1487
  } else {
1358
- Callback().Call(Receiver().Value(), { Napi::Error::New(env, sharp::TrimEnd(baton->err)).Value() });
1488
+ Callback().SHARP_CALLBACK_FN_NAME(Receiver().Value(),
1489
+ { Napi::Error::New(env, sharp::TrimEnd(baton->err)).Value() });
1359
1490
  }
1360
1491
 
1361
1492
  // Delete baton
@@ -1376,7 +1507,7 @@ class PipelineWorker : public Napi::AsyncWorker {
1376
1507
  // Decrement processing task counter
1377
1508
  sharp::counterProcess--;
1378
1509
  Napi::Number queueLength = Napi::Number::New(env, static_cast<int>(sharp::counterQueue));
1379
- queueListener.Call(Receiver().Value(), { queueLength });
1510
+ queueListener.SHARP_CALLBACK_FN_NAME(Receiver().Value(), { queueLength });
1380
1511
  }
1381
1512
 
1382
1513
  private:
@@ -1386,7 +1517,13 @@ class PipelineWorker : public Napi::AsyncWorker {
1386
1517
 
1387
1518
  void MultiPageUnsupported(int const pages, std::string op) {
1388
1519
  if (pages > 1) {
1389
- throw vips::VError(op + " is not supported for multi-page images");
1520
+ throw std::runtime_error(op + " is not supported for multi-page images");
1521
+ }
1522
+ }
1523
+
1524
+ void KeepGainMapUnsupported(bool const keepGainMap, std::string op) {
1525
+ if (keepGainMap) {
1526
+ throw std::runtime_error(op + " is not supported when keeping gain maps");
1390
1527
  }
1391
1528
  }
1392
1529
 
@@ -1469,6 +1606,7 @@ class PipelineWorker : public Napi::AsyncWorker {
1469
1606
  {"preset", vips_enum_nick(VIPS_TYPE_FOREIGN_WEBP_PRESET, baton->webpPreset)},
1470
1607
  {"min_size", baton->webpMinSize ? "true" : "false"},
1471
1608
  {"mixed", baton->webpMixed ? "true" : "false"},
1609
+ {"exact", baton->webpExact ? "true" : "false"},
1472
1610
  {"effort", std::to_string(baton->webpEffort)}
1473
1611
  };
1474
1612
  suffix = AssembleSuffixString(".webp", options);
@@ -1507,6 +1645,21 @@ class PipelineWorker : public Napi::AsyncWorker {
1507
1645
  return options;
1508
1646
  }
1509
1647
 
1648
+ VImage ReattachGainMap(VImage image, VImage gainMap, PipelineBaton *baton) {
1649
+ VipsArea *gainMapJpeg = reinterpret_cast<VipsArea*>(gainMap.jpegsave_buffer(VImage::option()
1650
+ ->set("keep", FALSE)
1651
+ ->set("Q", baton->jpegQuality)
1652
+ ->set("subsample_mode", VIPS_FOREIGN_SUBSAMPLE_OFF)
1653
+ ->set("trellis_quant", baton->jpegTrellisQuantisation)
1654
+ ->set("quant_table", baton->jpegQuantisationTable)
1655
+ ->set("overshoot_deringing", baton->jpegOvershootDeringing)
1656
+ ->set("optimize_coding", baton->jpegOptimiseCoding)));
1657
+ image = image.copy();
1658
+ image.set("gainmap-data", reinterpret_cast<VipsCallbackFn>(vips_area_free_cb),
1659
+ gainMapJpeg->data, gainMapJpeg->length);
1660
+ return image;
1661
+ }
1662
+
1510
1663
  /*
1511
1664
  Clear all thread-local data.
1512
1665
  */
@@ -1615,6 +1768,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1615
1768
  baton->trimBackground = sharp::AttrAsVectorOfDouble(options, "trimBackground");
1616
1769
  baton->trimThreshold = sharp::AttrAsDouble(options, "trimThreshold");
1617
1770
  baton->trimLineArt = sharp::AttrAsBool(options, "trimLineArt");
1771
+ baton->trimMargin = sharp::AttrAsUint32(options, "trimMargin");
1618
1772
  baton->gamma = sharp::AttrAsDouble(options, "gamma");
1619
1773
  baton->gammaOut = sharp::AttrAsDouble(options, "gammaOut");
1620
1774
  baton->linearA = sharp::AttrAsVectorOfDouble(options, "linearA");
@@ -1692,6 +1846,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1692
1846
  // Output
1693
1847
  baton->formatOut = sharp::AttrAsStr(options, "formatOut");
1694
1848
  baton->fileOut = sharp::AttrAsStr(options, "fileOut");
1849
+ baton->typedArrayOut = sharp::AttrAsBool(options, "typedArrayOut");
1695
1850
  baton->keepMetadata = sharp::AttrAsUint32(options, "keepMetadata");
1696
1851
  baton->withMetadataOrientation = sharp::AttrAsUint32(options, "withMetadataOrientation");
1697
1852
  baton->withMetadataDensity = sharp::AttrAsDouble(options, "withMetadataDensity");
@@ -1706,6 +1861,8 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1706
1861
  }
1707
1862
  baton->withExifMerge = sharp::AttrAsBool(options, "withExifMerge");
1708
1863
  baton->withXmp = sharp::AttrAsStr(options, "withXmp");
1864
+ baton->keepGainMap = sharp::AttrAsBool(options, "keepGainMap");
1865
+ baton->withGainMap = sharp::AttrAsBool(options, "withGainMap");
1709
1866
  baton->timeoutSeconds = sharp::AttrAsUint32(options, "timeoutSeconds");
1710
1867
  baton->loop = sharp::AttrAsUint32(options, "loop");
1711
1868
  baton->delay = sharp::AttrAsInt32Vector(options, "delay");
@@ -1741,6 +1898,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1741
1898
  baton->webpEffort = sharp::AttrAsUint32(options, "webpEffort");
1742
1899
  baton->webpMinSize = sharp::AttrAsBool(options, "webpMinSize");
1743
1900
  baton->webpMixed = sharp::AttrAsBool(options, "webpMixed");
1901
+ baton->webpExact = sharp::AttrAsBool(options, "webpExact");
1744
1902
  baton->gifBitdepth = sharp::AttrAsUint32(options, "gifBitdepth");
1745
1903
  baton->gifEffort = sharp::AttrAsUint32(options, "gifEffort");
1746
1904
  baton->gifDither = sharp::AttrAsDouble(options, "gifDither");
@@ -1775,6 +1933,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1775
1933
  baton->heifEffort = sharp::AttrAsUint32(options, "heifEffort");
1776
1934
  baton->heifChromaSubsampling = sharp::AttrAsStr(options, "heifChromaSubsampling");
1777
1935
  baton->heifBitdepth = sharp::AttrAsUint32(options, "heifBitdepth");
1936
+ baton->heifTune = sharp::AttrAsStr(options, "heifTune");
1778
1937
  baton->jxlDistance = sharp::AttrAsDouble(options, "jxlDistance");
1779
1938
  baton->jxlDecodingTier = sharp::AttrAsUint32(options, "jxlDecodingTier");
1780
1939
  baton->jxlEffort = sharp::AttrAsUint32(options, "jxlEffort");
@@ -1808,7 +1967,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1808
1967
 
1809
1968
  // Increment queued task counter
1810
1969
  Napi::Number queueLength = Napi::Number::New(info.Env(), static_cast<int>(++sharp::counterQueue));
1811
- queueListener.Call(info.This(), { queueLength });
1970
+ queueListener.SHARP_CALLBACK_FN_NAME(info.This(), { queueLength });
1812
1971
 
1813
1972
  return info.Env().Undefined();
1814
1973
  }