sharp 0.34.5 → 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/README.md +7 -17
- package/install/build.js +2 -2
- package/lib/constructor.js +8 -2
- package/lib/index.d.ts +60 -62
- package/lib/input.js +4 -10
- package/lib/libvips.js +27 -19
- package/lib/operation.js +4 -33
- package/lib/output.js +141 -8
- package/lib/resize.js +16 -0
- package/lib/sharp.js +62 -20
- package/lib/utility.js +1 -1
- package/package.json +46 -45
- package/src/binding.gyp +11 -6
- package/src/common.cc +64 -14
- package/src/common.h +21 -4
- package/src/metadata.cc +66 -8
- package/src/metadata.h +6 -1
- package/src/operations.cc +25 -8
- package/src/operations.h +1 -1
- package/src/pipeline.cc +186 -51
- package/src/pipeline.h +13 -1
- package/src/stats.cc +6 -6
- package/src/utilities.cc +4 -3
- package/install/check.js +0 -14
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
|
|
|
@@ -153,7 +153,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
153
153
|
if (baton->trimThreshold >= 0.0) {
|
|
154
154
|
MultiPageUnsupported(nPages, "Trim");
|
|
155
155
|
image = sharp::StaySequential(image);
|
|
156
|
-
image = sharp::Trim(image, baton->trimBackground, baton->trimThreshold, baton->trimLineArt);
|
|
156
|
+
image = sharp::Trim(image, baton->trimBackground, baton->trimThreshold, baton->trimLineArt, baton->trimMargin);
|
|
157
157
|
baton->trimOffsetLeft = image.xoffset();
|
|
158
158
|
baton->trimOffsetTop = image.yoffset();
|
|
159
159
|
}
|
|
@@ -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
|
|
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,12 +292,28 @@ 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
|
|
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;
|
|
303
|
+
if (sharp::HasGainMap(image)) {
|
|
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) {
|
|
310
|
+
image = image.uhdr2scRGB();
|
|
311
|
+
}
|
|
312
|
+
image = sharp::RemoveGainMap(image);
|
|
313
|
+
} else {
|
|
314
|
+
baton->keepGainMap = false;
|
|
315
|
+
baton->withGainMap = false;
|
|
316
|
+
}
|
|
299
317
|
|
|
300
318
|
// Any pre-shrinking may already have been done
|
|
301
319
|
inputWidth = image.width();
|
|
@@ -335,7 +353,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
335
353
|
image.interpretation() != VIPS_INTERPRETATION_LABS &&
|
|
336
354
|
image.interpretation() != VIPS_INTERPRETATION_GREY16 &&
|
|
337
355
|
baton->colourspacePipeline != VIPS_INTERPRETATION_CMYK &&
|
|
338
|
-
!baton->input->ignoreIcc
|
|
356
|
+
!baton->input->ignoreIcc && !baton->withGainMap
|
|
339
357
|
) {
|
|
340
358
|
// Convert to sRGB/P3 using embedded profile
|
|
341
359
|
try {
|
|
@@ -393,6 +411,11 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
393
411
|
image = image.resize(1.0 / hshrink, VImage::option()
|
|
394
412
|
->set("vscale", 1.0 / vshrink)
|
|
395
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
|
+
}
|
|
396
419
|
}
|
|
397
420
|
|
|
398
421
|
image = sharp::StaySequential(image,
|
|
@@ -405,14 +428,23 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
405
428
|
MultiPageUnsupported(nPages, "Rotate");
|
|
406
429
|
}
|
|
407
430
|
image = image.rot(autoRotation);
|
|
431
|
+
if (baton->keepGainMap) {
|
|
432
|
+
gainMap = gainMap.rot(autoRotation);
|
|
433
|
+
}
|
|
408
434
|
}
|
|
409
435
|
// Mirror vertically (up-down) about the x-axis
|
|
410
436
|
if (baton->flip) {
|
|
411
437
|
image = image.flip(VIPS_DIRECTION_VERTICAL);
|
|
438
|
+
if (baton->keepGainMap) {
|
|
439
|
+
gainMap = gainMap.flip(VIPS_DIRECTION_VERTICAL);
|
|
440
|
+
}
|
|
412
441
|
}
|
|
413
442
|
// Mirror horizontally (left-right) about the y-axis
|
|
414
443
|
if (baton->flop != autoFlop) {
|
|
415
444
|
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
|
|
445
|
+
if (baton->keepGainMap) {
|
|
446
|
+
gainMap = gainMap.flip(VIPS_DIRECTION_HORIZONTAL);
|
|
447
|
+
}
|
|
416
448
|
}
|
|
417
449
|
// Rotate post-extract 90-angle
|
|
418
450
|
if (rotation != VIPS_ANGLE_D0) {
|
|
@@ -420,6 +452,9 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
420
452
|
MultiPageUnsupported(nPages, "Rotate");
|
|
421
453
|
}
|
|
422
454
|
image = image.rot(rotation);
|
|
455
|
+
if (baton->keepGainMap) {
|
|
456
|
+
gainMap = gainMap.rot(rotation);
|
|
457
|
+
}
|
|
423
458
|
}
|
|
424
459
|
|
|
425
460
|
// Join additional color channels to the image
|
|
@@ -466,6 +501,12 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
466
501
|
: image.embed(left, top, width, height, VImage::option()
|
|
467
502
|
->set("extend", VIPS_EXTEND_BACKGROUND)
|
|
468
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
|
+
}
|
|
469
510
|
} else if (baton->canvas == sharp::Canvas::CROP) {
|
|
470
511
|
if (baton->width > inputWidth) {
|
|
471
512
|
baton->width = inputWidth;
|
|
@@ -486,12 +527,17 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
486
527
|
? sharp::CropMultiPage(image,
|
|
487
528
|
left, top, width, height, nPages, &targetPageHeight)
|
|
488
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
|
+
}
|
|
489
534
|
} else {
|
|
490
535
|
int attention_x;
|
|
491
536
|
int attention_y;
|
|
492
537
|
|
|
493
538
|
// Attention-based or Entropy-based crop
|
|
494
539
|
MultiPageUnsupported(nPages, "Resize strategy");
|
|
540
|
+
KeepGainMapUnsupported(baton->keepGainMap, "Resize strategy");
|
|
495
541
|
image = sharp::StaySequential(image);
|
|
496
542
|
image = image.smartcrop(baton->width, baton->height, VImage::option()
|
|
497
543
|
->set("interesting", baton->position == 16 ? VIPS_INTERESTING_ENTROPY : VIPS_INTERESTING_ATTENTION)
|
|
@@ -515,6 +561,9 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
515
561
|
std::vector<double> background;
|
|
516
562
|
std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground, shouldPremultiplyAlpha);
|
|
517
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
|
+
}
|
|
518
567
|
}
|
|
519
568
|
|
|
520
569
|
// Post extraction
|
|
@@ -523,18 +572,23 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
523
572
|
image = sharp::CropMultiPage(image,
|
|
524
573
|
baton->leftOffsetPost, baton->topOffsetPost, baton->widthPost, baton->heightPost,
|
|
525
574
|
nPages, &targetPageHeight);
|
|
526
|
-
|
|
527
575
|
// heightPost is used in the info object, so update to reflect the number of pages
|
|
528
576
|
baton->heightPost *= nPages;
|
|
529
577
|
} else {
|
|
530
578
|
image = image.extract_area(
|
|
531
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
|
+
}
|
|
532
585
|
}
|
|
533
586
|
}
|
|
534
587
|
|
|
535
588
|
// Affine transform
|
|
536
589
|
if (!baton->affineMatrix.empty()) {
|
|
537
590
|
MultiPageUnsupported(nPages, "Affine");
|
|
591
|
+
KeepGainMapUnsupported(baton->keepGainMap, "Affine");
|
|
538
592
|
image = sharp::StaySequential(image);
|
|
539
593
|
std::vector<double> background;
|
|
540
594
|
std::tie(image, background) = sharp::ApplyAlpha(image, baton->affineBackground, shouldPremultiplyAlpha);
|
|
@@ -565,6 +619,12 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
565
619
|
baton->extendWith, background, nPages, &targetPageHeight)
|
|
566
620
|
: image.embed(baton->extendLeft, baton->extendTop, baton->width, baton->height,
|
|
567
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
|
+
}
|
|
568
628
|
} else {
|
|
569
629
|
std::vector<double> ignoredBackground(1);
|
|
570
630
|
image = sharp::StaySequential(image);
|
|
@@ -574,6 +634,12 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
574
634
|
baton->extendWith, ignoredBackground, nPages, &targetPageHeight)
|
|
575
635
|
: image.embed(baton->extendLeft, baton->extendTop, baton->width, baton->height,
|
|
576
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
|
+
}
|
|
577
643
|
}
|
|
578
644
|
}
|
|
579
645
|
// Median - must happen before blurring, due to the utility of blurring after thresholding
|
|
@@ -600,6 +666,9 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
600
666
|
// Blur
|
|
601
667
|
if (shouldBlur) {
|
|
602
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
|
+
}
|
|
603
672
|
}
|
|
604
673
|
|
|
605
674
|
// Unflatten the image
|
|
@@ -609,6 +678,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
609
678
|
|
|
610
679
|
// Convolve
|
|
611
680
|
if (shouldConv) {
|
|
681
|
+
KeepGainMapUnsupported(baton->keepGainMap, "Convolve");
|
|
612
682
|
image = sharp::Convolve(image,
|
|
613
683
|
baton->convKernelWidth, baton->convKernelHeight,
|
|
614
684
|
baton->convKernelScale, baton->convKernelOffset,
|
|
@@ -617,11 +687,13 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
617
687
|
|
|
618
688
|
// Recomb
|
|
619
689
|
if (!baton->recombMatrix.empty()) {
|
|
690
|
+
KeepGainMapUnsupported(baton->keepGainMap, "Recomb");
|
|
620
691
|
image = sharp::Recomb(image, baton->recombMatrix);
|
|
621
692
|
}
|
|
622
693
|
|
|
623
694
|
// Modulate
|
|
624
695
|
if (baton->brightness != 1.0 || baton->saturation != 1.0 || baton->hue != 0.0 || baton->lightness != 0.0) {
|
|
696
|
+
KeepGainMapUnsupported(baton->keepGainMap, "Modulate");
|
|
625
697
|
image = sharp::Modulate(image, baton->brightness, baton->saturation, baton->hue, baton->lightness);
|
|
626
698
|
}
|
|
627
699
|
|
|
@@ -639,6 +711,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
639
711
|
|
|
640
712
|
// Composite
|
|
641
713
|
if (shouldComposite) {
|
|
714
|
+
KeepGainMapUnsupported(baton->keepGainMap, "Composite");
|
|
642
715
|
std::vector<VImage> images = { image };
|
|
643
716
|
std::vector<int> modes, xs, ys;
|
|
644
717
|
for (Composite *composite : baton->composite) {
|
|
@@ -667,7 +740,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
667
740
|
|
|
668
741
|
// Verify within current dimensions
|
|
669
742
|
if (compositeImage.width() > image.width() || compositeImage.height() > image.height()) {
|
|
670
|
-
throw
|
|
743
|
+
throw std::runtime_error("Image to composite must have same dimensions or smaller");
|
|
671
744
|
}
|
|
672
745
|
// Check if overlay is tiled
|
|
673
746
|
if (composite->tile) {
|
|
@@ -751,18 +824,21 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
751
824
|
|
|
752
825
|
// Apply normalisation - stretch luminance to cover full dynamic range
|
|
753
826
|
if (baton->normalise) {
|
|
827
|
+
KeepGainMapUnsupported(baton->keepGainMap, "Normalise");
|
|
754
828
|
image = sharp::StaySequential(image);
|
|
755
829
|
image = sharp::Normalise(image, baton->normaliseLower, baton->normaliseUpper);
|
|
756
830
|
}
|
|
757
831
|
|
|
758
832
|
// Apply contrast limiting adaptive histogram equalization (CLAHE)
|
|
759
833
|
if (baton->claheWidth != 0 && baton->claheHeight != 0) {
|
|
834
|
+
KeepGainMapUnsupported(baton->keepGainMap, "Clahe");
|
|
760
835
|
image = sharp::StaySequential(image);
|
|
761
836
|
image = sharp::Clahe(image, baton->claheWidth, baton->claheHeight, baton->claheMaxSlope);
|
|
762
837
|
}
|
|
763
838
|
|
|
764
839
|
// Apply bitwise boolean operation between images
|
|
765
840
|
if (baton->boolean != nullptr) {
|
|
841
|
+
KeepGainMapUnsupported(baton->keepGainMap, "Boolean");
|
|
766
842
|
VImage booleanImage;
|
|
767
843
|
sharp::ImageType booleanImageType = sharp::ImageType::UNKNOWN;
|
|
768
844
|
baton->boolean->access = access;
|
|
@@ -805,6 +881,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
805
881
|
|
|
806
882
|
// Extract channel
|
|
807
883
|
if (baton->extractChannel > -1) {
|
|
884
|
+
KeepGainMapUnsupported(baton->keepGainMap, "Extract channel");
|
|
808
885
|
if (baton->extractChannel >= image.bands()) {
|
|
809
886
|
if (baton->extractChannel == 3 && image.has_alpha()) {
|
|
810
887
|
baton->extractChannel = image.bands() - 1;
|
|
@@ -839,6 +916,9 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
839
916
|
// Negate the colours in the image
|
|
840
917
|
if (baton->negate) {
|
|
841
918
|
image = sharp::Negate(image, baton->negateAlpha);
|
|
919
|
+
if (baton->keepGainMap) {
|
|
920
|
+
gainMap = sharp::Negate(gainMap, false);
|
|
921
|
+
}
|
|
842
922
|
}
|
|
843
923
|
|
|
844
924
|
// Override EXIF Orientation tag
|
|
@@ -885,18 +965,27 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
885
965
|
if (baton->formatOut == "jpeg" || (baton->formatOut == "input" && inputImageType == sharp::ImageType::JPEG)) {
|
|
886
966
|
// Write JPEG to buffer
|
|
887
967
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
|
|
888
|
-
VipsArea *area
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
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
|
+
}
|
|
900
989
|
baton->bufferOut = static_cast<char*>(area->data);
|
|
901
990
|
baton->bufferOutLength = area->length;
|
|
902
991
|
area->free_fn = nullptr;
|
|
@@ -957,6 +1046,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
957
1046
|
->set("effort", baton->webpEffort)
|
|
958
1047
|
->set("min_size", baton->webpMinSize)
|
|
959
1048
|
->set("mixed", baton->webpMixed)
|
|
1049
|
+
->set("exact", baton->webpExact)
|
|
960
1050
|
->set("alpha_q", baton->webpAlphaQuality)));
|
|
961
1051
|
baton->bufferOut = static_cast<char*>(area->data);
|
|
962
1052
|
baton->bufferOutLength = area->length;
|
|
@@ -1024,6 +1114,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1024
1114
|
->set("compression", baton->heifCompression)
|
|
1025
1115
|
->set("effort", baton->heifEffort)
|
|
1026
1116
|
->set("bitdepth", baton->heifBitdepth)
|
|
1117
|
+
->set("tune", baton->heifTune.c_str())
|
|
1027
1118
|
->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
|
|
1028
1119
|
? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
|
|
1029
1120
|
->set("lossless", baton->heifLossless)));
|
|
@@ -1076,20 +1167,19 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1076
1167
|
// Get raw image data
|
|
1077
1168
|
baton->bufferOut = static_cast<char*>(image.write_to_memory(&baton->bufferOutLength));
|
|
1078
1169
|
if (baton->bufferOut == nullptr) {
|
|
1079
|
-
(
|
|
1080
|
-
return Error();
|
|
1170
|
+
throw std::runtime_error("Could not allocate enough memory for raw output");
|
|
1081
1171
|
}
|
|
1082
1172
|
baton->formatOut = "raw";
|
|
1083
1173
|
} else {
|
|
1084
1174
|
// Unsupported output format
|
|
1085
|
-
(
|
|
1175
|
+
auto unsupported = std::string("Unsupported output format ");
|
|
1086
1176
|
if (baton->formatOut == "input") {
|
|
1087
|
-
|
|
1088
|
-
|
|
1177
|
+
unsupported.append("when trying to match input format of ");
|
|
1178
|
+
unsupported.append(ImageTypeId(inputImageType));
|
|
1089
1179
|
} else {
|
|
1090
|
-
|
|
1180
|
+
unsupported.append(baton->formatOut);
|
|
1091
1181
|
}
|
|
1092
|
-
|
|
1182
|
+
throw std::runtime_error(unsupported);
|
|
1093
1183
|
}
|
|
1094
1184
|
} else {
|
|
1095
1185
|
// File output
|
|
@@ -1112,18 +1202,26 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1112
1202
|
(willMatchInput && inputImageType == sharp::ImageType::JPEG)) {
|
|
1113
1203
|
// Write JPEG to file
|
|
1114
1204
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
->
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
->
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
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
|
+
}
|
|
1127
1225
|
baton->formatOut = "jpeg";
|
|
1128
1226
|
baton->channels = std::min(baton->channels, 3);
|
|
1129
1227
|
} else if (baton->formatOut == "jp2" || (mightMatchInput && isJp2) ||
|
|
@@ -1168,6 +1266,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1168
1266
|
->set("effort", baton->webpEffort)
|
|
1169
1267
|
->set("min_size", baton->webpMinSize)
|
|
1170
1268
|
->set("mixed", baton->webpMixed)
|
|
1269
|
+
->set("exact", baton->webpExact)
|
|
1171
1270
|
->set("alpha_q", baton->webpAlphaQuality));
|
|
1172
1271
|
baton->formatOut = "webp";
|
|
1173
1272
|
} else if (baton->formatOut == "gif" || (mightMatchInput && isGif) ||
|
|
@@ -1223,6 +1322,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1223
1322
|
->set("compression", baton->heifCompression)
|
|
1224
1323
|
->set("effort", baton->heifEffort)
|
|
1225
1324
|
->set("bitdepth", baton->heifBitdepth)
|
|
1325
|
+
->set("tune", baton->heifTune.c_str())
|
|
1226
1326
|
->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
|
|
1227
1327
|
? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
|
|
1228
1328
|
->set("lossless", baton->heifLossless));
|
|
@@ -1262,7 +1362,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1262
1362
|
return Error();
|
|
1263
1363
|
}
|
|
1264
1364
|
}
|
|
1265
|
-
} catch (
|
|
1365
|
+
} catch (std::runtime_error const &err) {
|
|
1266
1366
|
char const *what = err.what();
|
|
1267
1367
|
if (what && what[0]) {
|
|
1268
1368
|
(baton->err).append(what);
|
|
@@ -1290,11 +1390,10 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1290
1390
|
if (baton->errUseWarning) {
|
|
1291
1391
|
(baton->err).append("\n").append(warning);
|
|
1292
1392
|
} else {
|
|
1293
|
-
debuglog.
|
|
1393
|
+
debuglog.SHARP_CALLBACK_FN_NAME(Receiver().Value(), { Napi::String::New(env, warning) });
|
|
1294
1394
|
}
|
|
1295
1395
|
warning = sharp::VipsWarningPop();
|
|
1296
1396
|
}
|
|
1297
|
-
|
|
1298
1397
|
if (baton->err.empty()) {
|
|
1299
1398
|
int width = baton->width;
|
|
1300
1399
|
int height = baton->height;
|
|
@@ -1337,12 +1436,19 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1337
1436
|
}
|
|
1338
1437
|
|
|
1339
1438
|
if (baton->bufferOutLength > 0) {
|
|
1340
|
-
// Add buffer size to info
|
|
1341
1439
|
info.Set("size", static_cast<uint32_t>(baton->bufferOutLength));
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1440
|
+
if (baton->typedArrayOut) {
|
|
1441
|
+
// ECMAScript ArrayBuffer with Uint8Array view
|
|
1442
|
+
Napi::TypedArrayOf<uint8_t> data = Napi::Buffer<char>::Copy(env,
|
|
1443
|
+
static_cast<char*>(baton->bufferOut), baton->bufferOutLength);
|
|
1444
|
+
sharp::FreeCallback(static_cast<char*>(baton->bufferOut), nullptr);
|
|
1445
|
+
Callback().SHARP_CALLBACK_FN_NAME(Receiver().Value(), { env.Null(), data, info });
|
|
1446
|
+
} else {
|
|
1447
|
+
// Node.js Buffer
|
|
1448
|
+
Napi::Buffer<char> data = Napi::Buffer<char>::NewOrCopy(env, static_cast<char*>(baton->bufferOut),
|
|
1449
|
+
baton->bufferOutLength, sharp::FreeCallback);
|
|
1450
|
+
Callback().SHARP_CALLBACK_FN_NAME(Receiver().Value(), { env.Null(), data, info });
|
|
1451
|
+
}
|
|
1346
1452
|
} else {
|
|
1347
1453
|
// Add file size to info
|
|
1348
1454
|
if (baton->formatOut != "dz" || sharp::IsDzZip(baton->fileOut)) {
|
|
@@ -1352,10 +1458,11 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1352
1458
|
info.Set("size", size);
|
|
1353
1459
|
} catch (...) {}
|
|
1354
1460
|
}
|
|
1355
|
-
Callback().
|
|
1461
|
+
Callback().SHARP_CALLBACK_FN_NAME(Receiver().Value(), { env.Null(), info });
|
|
1356
1462
|
}
|
|
1357
1463
|
} else {
|
|
1358
|
-
Callback().
|
|
1464
|
+
Callback().SHARP_CALLBACK_FN_NAME(Receiver().Value(),
|
|
1465
|
+
{ Napi::Error::New(env, sharp::TrimEnd(baton->err)).Value() });
|
|
1359
1466
|
}
|
|
1360
1467
|
|
|
1361
1468
|
// Delete baton
|
|
@@ -1376,7 +1483,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1376
1483
|
// Decrement processing task counter
|
|
1377
1484
|
sharp::counterProcess--;
|
|
1378
1485
|
Napi::Number queueLength = Napi::Number::New(env, static_cast<int>(sharp::counterQueue));
|
|
1379
|
-
queueListener.
|
|
1486
|
+
queueListener.SHARP_CALLBACK_FN_NAME(Receiver().Value(), { queueLength });
|
|
1380
1487
|
}
|
|
1381
1488
|
|
|
1382
1489
|
private:
|
|
@@ -1386,7 +1493,13 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1386
1493
|
|
|
1387
1494
|
void MultiPageUnsupported(int const pages, std::string op) {
|
|
1388
1495
|
if (pages > 1) {
|
|
1389
|
-
throw
|
|
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");
|
|
1390
1503
|
}
|
|
1391
1504
|
}
|
|
1392
1505
|
|
|
@@ -1469,6 +1582,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1469
1582
|
{"preset", vips_enum_nick(VIPS_TYPE_FOREIGN_WEBP_PRESET, baton->webpPreset)},
|
|
1470
1583
|
{"min_size", baton->webpMinSize ? "true" : "false"},
|
|
1471
1584
|
{"mixed", baton->webpMixed ? "true" : "false"},
|
|
1585
|
+
{"exact", baton->webpExact ? "true" : "false"},
|
|
1472
1586
|
{"effort", std::to_string(baton->webpEffort)}
|
|
1473
1587
|
};
|
|
1474
1588
|
suffix = AssembleSuffixString(".webp", options);
|
|
@@ -1507,6 +1621,21 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1507
1621
|
return options;
|
|
1508
1622
|
}
|
|
1509
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
|
+
|
|
1510
1639
|
/*
|
|
1511
1640
|
Clear all thread-local data.
|
|
1512
1641
|
*/
|
|
@@ -1615,6 +1744,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1615
1744
|
baton->trimBackground = sharp::AttrAsVectorOfDouble(options, "trimBackground");
|
|
1616
1745
|
baton->trimThreshold = sharp::AttrAsDouble(options, "trimThreshold");
|
|
1617
1746
|
baton->trimLineArt = sharp::AttrAsBool(options, "trimLineArt");
|
|
1747
|
+
baton->trimMargin = sharp::AttrAsUint32(options, "trimMargin");
|
|
1618
1748
|
baton->gamma = sharp::AttrAsDouble(options, "gamma");
|
|
1619
1749
|
baton->gammaOut = sharp::AttrAsDouble(options, "gammaOut");
|
|
1620
1750
|
baton->linearA = sharp::AttrAsVectorOfDouble(options, "linearA");
|
|
@@ -1692,6 +1822,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1692
1822
|
// Output
|
|
1693
1823
|
baton->formatOut = sharp::AttrAsStr(options, "formatOut");
|
|
1694
1824
|
baton->fileOut = sharp::AttrAsStr(options, "fileOut");
|
|
1825
|
+
baton->typedArrayOut = sharp::AttrAsBool(options, "typedArrayOut");
|
|
1695
1826
|
baton->keepMetadata = sharp::AttrAsUint32(options, "keepMetadata");
|
|
1696
1827
|
baton->withMetadataOrientation = sharp::AttrAsUint32(options, "withMetadataOrientation");
|
|
1697
1828
|
baton->withMetadataDensity = sharp::AttrAsDouble(options, "withMetadataDensity");
|
|
@@ -1706,6 +1837,8 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1706
1837
|
}
|
|
1707
1838
|
baton->withExifMerge = sharp::AttrAsBool(options, "withExifMerge");
|
|
1708
1839
|
baton->withXmp = sharp::AttrAsStr(options, "withXmp");
|
|
1840
|
+
baton->keepGainMap = sharp::AttrAsBool(options, "keepGainMap");
|
|
1841
|
+
baton->withGainMap = sharp::AttrAsBool(options, "withGainMap");
|
|
1709
1842
|
baton->timeoutSeconds = sharp::AttrAsUint32(options, "timeoutSeconds");
|
|
1710
1843
|
baton->loop = sharp::AttrAsUint32(options, "loop");
|
|
1711
1844
|
baton->delay = sharp::AttrAsInt32Vector(options, "delay");
|
|
@@ -1741,6 +1874,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1741
1874
|
baton->webpEffort = sharp::AttrAsUint32(options, "webpEffort");
|
|
1742
1875
|
baton->webpMinSize = sharp::AttrAsBool(options, "webpMinSize");
|
|
1743
1876
|
baton->webpMixed = sharp::AttrAsBool(options, "webpMixed");
|
|
1877
|
+
baton->webpExact = sharp::AttrAsBool(options, "webpExact");
|
|
1744
1878
|
baton->gifBitdepth = sharp::AttrAsUint32(options, "gifBitdepth");
|
|
1745
1879
|
baton->gifEffort = sharp::AttrAsUint32(options, "gifEffort");
|
|
1746
1880
|
baton->gifDither = sharp::AttrAsDouble(options, "gifDither");
|
|
@@ -1775,6 +1909,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1775
1909
|
baton->heifEffort = sharp::AttrAsUint32(options, "heifEffort");
|
|
1776
1910
|
baton->heifChromaSubsampling = sharp::AttrAsStr(options, "heifChromaSubsampling");
|
|
1777
1911
|
baton->heifBitdepth = sharp::AttrAsUint32(options, "heifBitdepth");
|
|
1912
|
+
baton->heifTune = sharp::AttrAsStr(options, "heifTune");
|
|
1778
1913
|
baton->jxlDistance = sharp::AttrAsDouble(options, "jxlDistance");
|
|
1779
1914
|
baton->jxlDecodingTier = sharp::AttrAsUint32(options, "jxlDecodingTier");
|
|
1780
1915
|
baton->jxlEffort = sharp::AttrAsUint32(options, "jxlEffort");
|
|
@@ -1808,7 +1943,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1808
1943
|
|
|
1809
1944
|
// Increment queued task counter
|
|
1810
1945
|
Napi::Number queueLength = Napi::Number::New(info.Env(), static_cast<int>(++sharp::counterQueue));
|
|
1811
|
-
queueListener.
|
|
1946
|
+
queueListener.SHARP_CALLBACK_FN_NAME(info.This(), { queueLength });
|
|
1812
1947
|
|
|
1813
1948
|
return info.Env().Undefined();
|
|
1814
1949
|
}
|