sharp 0.17.2 → 0.18.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/CONTRIBUTING.md +1 -7
- package/binding.gyp +1 -1
- package/binding.js +14 -14
- package/docs/api-colour.md +2 -2
- package/docs/api-composite.md +11 -3
- package/docs/api-constructor.md +25 -5
- package/docs/api-input.md +4 -3
- package/docs/api-operation.md +20 -17
- package/docs/api-output.md +39 -30
- package/docs/api-resize.md +9 -8
- package/docs/api-utility.md +4 -4
- package/docs/changelog.md +103 -0
- package/docs/index.md +4 -1
- package/docs/install.md +44 -14
- package/lib/channel.js +6 -6
- package/lib/colour.js +10 -10
- package/lib/composite.js +11 -6
- package/lib/constructor.js +41 -18
- package/lib/input.js +52 -24
- package/lib/is.js +9 -0
- package/lib/operation.js +41 -38
- package/lib/output.js +103 -112
- package/lib/resize.js +25 -20
- package/lib/utility.js +8 -8
- package/package.json +22 -18
- package/src/common.cc +94 -20
- package/src/common.h +30 -3
- package/src/libvips/cplusplus/VImage.cpp +110 -104
- package/src/libvips/cplusplus/vips-operators.cpp +108 -44
- package/src/metadata.cc +19 -4
- package/src/metadata.h +1 -0
- package/src/operations.cc +18 -165
- package/src/operations.h +4 -26
- package/src/pipeline.cc +201 -97
- package/src/pipeline.h +14 -0
- package/src/sharp.cc +3 -0
package/src/pipeline.cc
CHANGED
|
@@ -33,9 +33,10 @@
|
|
|
33
33
|
class PipelineWorker : public Nan::AsyncWorker {
|
|
34
34
|
public:
|
|
35
35
|
PipelineWorker(
|
|
36
|
-
Nan::Callback *callback, PipelineBaton *baton, Nan::Callback *queueListener,
|
|
37
|
-
std::vector<v8::Local<v8::Object>> const buffersToPersist)
|
|
38
|
-
|
|
36
|
+
Nan::Callback *callback, PipelineBaton *baton, Nan::Callback *debuglog, Nan::Callback *queueListener,
|
|
37
|
+
std::vector<v8::Local<v8::Object>> const buffersToPersist) :
|
|
38
|
+
Nan::AsyncWorker(callback), baton(baton), debuglog(debuglog), queueListener(queueListener),
|
|
39
|
+
buffersToPersist(buffersToPersist) {
|
|
39
40
|
// Protect Buffer objects from GC, keyed on index
|
|
40
41
|
std::accumulate(buffersToPersist.begin(), buffersToPersist.end(), 0,
|
|
41
42
|
[this](uint32_t index, v8::Local<v8::Object> const buffer) -> uint32_t {
|
|
@@ -80,16 +81,15 @@ class PipelineWorker : public Nan::AsyncWorker {
|
|
|
80
81
|
|
|
81
82
|
// Calculate angle of rotation
|
|
82
83
|
VipsAngle rotation;
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
baton->flip =
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
baton->flop = TRUE;
|
|
84
|
+
if (baton->useExifOrientation) {
|
|
85
|
+
// Rotate and flip image according to Exif orientation
|
|
86
|
+
bool flip;
|
|
87
|
+
bool flop;
|
|
88
|
+
std::tie(rotation, flip, flop) = CalculateExifRotationAndFlip(sharp::ExifOrientation(image));
|
|
89
|
+
baton->flip = baton->flip || flip;
|
|
90
|
+
baton->flop = baton->flop || flop;
|
|
91
|
+
} else {
|
|
92
|
+
rotation = CalculateAngleRotation(baton->angle);
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
// Rotate pre-extract
|
|
@@ -241,6 +241,12 @@ class PipelineWorker : public Nan::AsyncWorker {
|
|
|
241
241
|
shrink_on_load = 2;
|
|
242
242
|
}
|
|
243
243
|
}
|
|
244
|
+
// Help ensure a final kernel-based reduction to prevent shrink aliasing
|
|
245
|
+
if (shrink_on_load > 1 && (xresidual == 1.0 || yresidual == 1.0)) {
|
|
246
|
+
shrink_on_load = shrink_on_load / 2;
|
|
247
|
+
xfactor = xfactor * 2;
|
|
248
|
+
yfactor = yfactor * 2;
|
|
249
|
+
}
|
|
244
250
|
if (shrink_on_load > 1) {
|
|
245
251
|
// Reload input using shrink-on-load
|
|
246
252
|
vips::VOption *option = VImage::option()->set("shrink", shrink_on_load);
|
|
@@ -284,6 +290,13 @@ class PipelineWorker : public Nan::AsyncWorker {
|
|
|
284
290
|
std::swap(xresidual, yresidual);
|
|
285
291
|
}
|
|
286
292
|
}
|
|
293
|
+
// Help ensure a final kernel-based reduction to prevent shrink aliasing
|
|
294
|
+
if (xshrink > 1 && yshrink > 1 && (xresidual == 1.0 || yresidual == 1.0)) {
|
|
295
|
+
xshrink = xshrink / 2;
|
|
296
|
+
yshrink = yshrink / 2;
|
|
297
|
+
xresidual = static_cast<double>(xshrink) / xfactor;
|
|
298
|
+
yresidual = static_cast<double>(yshrink) / yfactor;
|
|
299
|
+
}
|
|
287
300
|
|
|
288
301
|
// Ensure we're using a device-independent colour space
|
|
289
302
|
if (sharp::HasProfile(image)) {
|
|
@@ -332,12 +345,20 @@ class PipelineWorker : public Nan::AsyncWorker {
|
|
|
332
345
|
image = image.colourspace(VIPS_INTERPRETATION_B_W);
|
|
333
346
|
}
|
|
334
347
|
|
|
335
|
-
// Ensure image has an alpha channel when there is an overlay
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
348
|
+
// Ensure image has an alpha channel when there is an overlay with an alpha channel
|
|
349
|
+
VImage overlayImage;
|
|
350
|
+
ImageType overlayImageType = ImageType::UNKNOWN;
|
|
351
|
+
bool shouldOverlayWithAlpha = FALSE;
|
|
352
|
+
if (baton->overlay != nullptr) {
|
|
353
|
+
std::tie(overlayImage, overlayImageType) = OpenInput(baton->overlay, baton->accessMethod);
|
|
354
|
+
if (HasAlpha(overlayImage)) {
|
|
355
|
+
shouldOverlayWithAlpha = !baton->overlayCutout;
|
|
356
|
+
if (!HasAlpha(image)) {
|
|
357
|
+
double const multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0;
|
|
358
|
+
image = image.bandjoin(
|
|
359
|
+
VImage::new_matrix(image.width(), image.height()).new_from_image(255 * multiplier));
|
|
360
|
+
}
|
|
361
|
+
}
|
|
341
362
|
}
|
|
342
363
|
|
|
343
364
|
bool const shouldShrink = xshrink > 1 || yshrink > 1;
|
|
@@ -345,9 +366,8 @@ class PipelineWorker : public Nan::AsyncWorker {
|
|
|
345
366
|
bool const shouldBlur = baton->blurSigma != 0.0;
|
|
346
367
|
bool const shouldConv = baton->convKernelWidth * baton->convKernelHeight > 0;
|
|
347
368
|
bool const shouldSharpen = baton->sharpenSigma != 0.0;
|
|
348
|
-
bool const shouldCutout = baton->overlayCutout;
|
|
349
369
|
bool const shouldPremultiplyAlpha = HasAlpha(image) &&
|
|
350
|
-
(shouldShrink || shouldReduce || shouldBlur || shouldConv || shouldSharpen ||
|
|
370
|
+
(shouldShrink || shouldReduce || shouldBlur || shouldConv || shouldSharpen || shouldOverlayWithAlpha);
|
|
351
371
|
|
|
352
372
|
// Premultiply image alpha channel before all transformations to avoid
|
|
353
373
|
// dark fringing around bright pixels
|
|
@@ -392,7 +412,10 @@ class PipelineWorker : public Nan::AsyncWorker {
|
|
|
392
412
|
if (yresidual < 1.0 || xresidual < 1.0) {
|
|
393
413
|
VipsKernel kernel = static_cast<VipsKernel>(
|
|
394
414
|
vips_enum_from_nick(nullptr, VIPS_TYPE_KERNEL, baton->kernel.data()));
|
|
395
|
-
if (
|
|
415
|
+
if (
|
|
416
|
+
kernel != VIPS_KERNEL_NEAREST && kernel != VIPS_KERNEL_CUBIC && kernel != VIPS_KERNEL_LANCZOS2 &&
|
|
417
|
+
kernel != VIPS_KERNEL_LANCZOS3
|
|
418
|
+
) {
|
|
396
419
|
throw vips::VError("Unknown kernel");
|
|
397
420
|
}
|
|
398
421
|
if (yresidual < 1.0) {
|
|
@@ -406,16 +429,24 @@ class PipelineWorker : public Nan::AsyncWorker {
|
|
|
406
429
|
->set("centre", baton->centreSampling));
|
|
407
430
|
}
|
|
408
431
|
}
|
|
409
|
-
// Perform
|
|
432
|
+
// Perform enlargement
|
|
410
433
|
if (yresidual > 1.0 || xresidual > 1.0) {
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
image = image.
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
434
|
+
if (trunc(xresidual) == xresidual && trunc(yresidual) == yresidual && baton->interpolator == "nearest") {
|
|
435
|
+
// Fast, integral nearest neighbour enlargement
|
|
436
|
+
image = image.zoom(static_cast<int>(xresidual), static_cast<int>(yresidual));
|
|
437
|
+
} else {
|
|
438
|
+
// Floating point affine transformation
|
|
439
|
+
vips::VInterpolate interpolator = vips::VInterpolate::new_from_name(baton->interpolator.data());
|
|
440
|
+
if (yresidual > 1.0 && xresidual > 1.0) {
|
|
441
|
+
image = image.affine({xresidual, 0.0, 0.0, yresidual}, VImage::option()
|
|
442
|
+
->set("interpolate", interpolator));
|
|
443
|
+
} else if (yresidual > 1.0) {
|
|
444
|
+
image = image.affine({1.0, 0.0, 0.0, yresidual}, VImage::option()
|
|
445
|
+
->set("interpolate", interpolator));
|
|
446
|
+
} else if (xresidual > 1.0) {
|
|
447
|
+
image = image.affine({xresidual, 0.0, 0.0, 1.0}, VImage::option()
|
|
448
|
+
->set("interpolate", interpolator));
|
|
449
|
+
}
|
|
419
450
|
}
|
|
420
451
|
}
|
|
421
452
|
}
|
|
@@ -490,24 +521,20 @@ class PipelineWorker : public Nan::AsyncWorker {
|
|
|
490
521
|
->set("background", background));
|
|
491
522
|
} else if (baton->canvas != Canvas::IGNORE_ASPECT) {
|
|
492
523
|
// Crop/max/min
|
|
493
|
-
int left;
|
|
494
|
-
int top;
|
|
495
524
|
if (baton->crop < 9) {
|
|
496
525
|
// Gravity-based crop
|
|
526
|
+
int left;
|
|
527
|
+
int top;
|
|
497
528
|
std::tie(left, top) = sharp::CalculateCrop(
|
|
498
529
|
image.width(), image.height(), baton->width, baton->height, baton->crop);
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
530
|
+
int width = std::min(image.width(), baton->width);
|
|
531
|
+
int height = std::min(image.height(), baton->height);
|
|
532
|
+
image = image.extract_area(left, top, width, height);
|
|
502
533
|
} else {
|
|
503
|
-
// Attention-based crop
|
|
504
|
-
|
|
534
|
+
// Attention-based or Entropy-based crop
|
|
535
|
+
image = image.smartcrop(baton->width, baton->height, VImage::option()
|
|
536
|
+
->set("interesting", baton->crop == 16 ? VIPS_INTERESTING_ENTROPY : VIPS_INTERESTING_ATTENTION));
|
|
505
537
|
}
|
|
506
|
-
int width = std::min(image.width(), baton->width);
|
|
507
|
-
int height = std::min(image.height(), baton->height);
|
|
508
|
-
image = image.extract_area(left, top, width, height);
|
|
509
|
-
baton->cropCalcLeft = left;
|
|
510
|
-
baton->cropCalcTop = top;
|
|
511
538
|
}
|
|
512
539
|
}
|
|
513
540
|
|
|
@@ -580,10 +607,11 @@ class PipelineWorker : public Nan::AsyncWorker {
|
|
|
580
607
|
}
|
|
581
608
|
|
|
582
609
|
// Composite with overlay, if present
|
|
583
|
-
if (
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
610
|
+
if (baton->overlay != nullptr) {
|
|
611
|
+
// Verify overlay image is within current dimensions
|
|
612
|
+
if (overlayImage.width() > image.width() || overlayImage.height() > image.height()) {
|
|
613
|
+
throw vips::VError("Overlay image must have same dimensions or smaller");
|
|
614
|
+
}
|
|
587
615
|
// Check if overlay is tiled
|
|
588
616
|
if (baton->overlayTile) {
|
|
589
617
|
int const overlayImageWidth = overlayImage.width();
|
|
@@ -616,31 +644,34 @@ class PipelineWorker : public Nan::AsyncWorker {
|
|
|
616
644
|
// the overlayGravity was used for extract_area, therefore set it back to its default value of 0
|
|
617
645
|
baton->overlayGravity = 0;
|
|
618
646
|
}
|
|
619
|
-
if (
|
|
647
|
+
if (baton->overlayCutout) {
|
|
620
648
|
// 'cut out' the image, premultiplication is not required
|
|
621
649
|
image = sharp::Cutout(overlayImage, image, baton->overlayGravity);
|
|
622
650
|
} else {
|
|
623
|
-
// Ensure overlay
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
651
|
+
// Ensure overlay is sRGB
|
|
652
|
+
overlayImage = overlayImage.colourspace(VIPS_INTERPRETATION_sRGB);
|
|
653
|
+
// Ensure overlay matches premultiplication state
|
|
654
|
+
if (shouldPremultiplyAlpha) {
|
|
655
|
+
// Ensure overlay has alpha channel
|
|
656
|
+
if (!HasAlpha(overlayImage)) {
|
|
657
|
+
double const multiplier = sharp::Is16Bit(overlayImage.interpretation()) ? 256.0 : 1.0;
|
|
658
|
+
overlayImage = overlayImage.bandjoin(
|
|
659
|
+
VImage::new_matrix(overlayImage.width(), overlayImage.height()).new_from_image(255 * multiplier));
|
|
660
|
+
}
|
|
661
|
+
overlayImage = overlayImage.premultiply();
|
|
634
662
|
}
|
|
635
|
-
|
|
636
|
-
|
|
663
|
+
int left;
|
|
664
|
+
int top;
|
|
637
665
|
if (baton->overlayXOffset >= 0 && baton->overlayYOffset >= 0) {
|
|
638
|
-
// Composite images
|
|
639
|
-
|
|
666
|
+
// Composite images at given offsets
|
|
667
|
+
std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),
|
|
668
|
+
overlayImage.width(), overlayImage.height(), baton->overlayXOffset, baton->overlayYOffset);
|
|
640
669
|
} else {
|
|
641
670
|
// Composite images with given gravity
|
|
642
|
-
|
|
671
|
+
std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),
|
|
672
|
+
overlayImage.width(), overlayImage.height(), baton->overlayGravity);
|
|
643
673
|
}
|
|
674
|
+
image = sharp::Composite(image, overlayImage, left, top);
|
|
644
675
|
}
|
|
645
676
|
}
|
|
646
677
|
|
|
@@ -654,6 +685,7 @@ class PipelineWorker : public Nan::AsyncWorker {
|
|
|
654
685
|
image = image.cast(VIPS_FORMAT_UCHAR);
|
|
655
686
|
}
|
|
656
687
|
}
|
|
688
|
+
baton->premultiplied = shouldPremultiplyAlpha;
|
|
657
689
|
|
|
658
690
|
// Gamma decoding (brighten)
|
|
659
691
|
if (baton->gamma >= 1 && baton->gamma <= 3) {
|
|
@@ -710,10 +742,11 @@ class PipelineWorker : public Nan::AsyncWorker {
|
|
|
710
742
|
baton->width = image.width();
|
|
711
743
|
baton->height = image.height();
|
|
712
744
|
// Output
|
|
713
|
-
if (baton->fileOut
|
|
745
|
+
if (baton->fileOut.empty()) {
|
|
714
746
|
// Buffer output
|
|
715
747
|
if (baton->formatOut == "jpeg" || (baton->formatOut == "input" && inputImageType == ImageType::JPEG)) {
|
|
716
748
|
// Write JPEG to buffer
|
|
749
|
+
sharp::AssertImageTypeDimensions(image, ImageType::JPEG);
|
|
717
750
|
VipsArea *area = VIPS_AREA(image.jpegsave_buffer(VImage::option()
|
|
718
751
|
->set("strip", !baton->withMetadata)
|
|
719
752
|
->set("Q", baton->jpegQuality)
|
|
@@ -735,11 +768,12 @@ class PipelineWorker : public Nan::AsyncWorker {
|
|
|
735
768
|
}
|
|
736
769
|
} else if (baton->formatOut == "png" || (baton->formatOut == "input" &&
|
|
737
770
|
(inputImageType == ImageType::PNG || inputImageType == ImageType::GIF || inputImageType == ImageType::SVG))) {
|
|
771
|
+
// Write PNG to buffer
|
|
772
|
+
sharp::AssertImageTypeDimensions(image, ImageType::PNG);
|
|
738
773
|
// Strip profile
|
|
739
774
|
if (!baton->withMetadata) {
|
|
740
775
|
vips_image_remove(image.get_image(), VIPS_META_ICC_NAME);
|
|
741
776
|
}
|
|
742
|
-
// Write PNG to buffer
|
|
743
777
|
VipsArea *area = VIPS_AREA(image.pngsave_buffer(VImage::option()
|
|
744
778
|
->set("interlace", baton->pngProgressive)
|
|
745
779
|
->set("compression", baton->pngCompressionLevel)
|
|
@@ -751,6 +785,7 @@ class PipelineWorker : public Nan::AsyncWorker {
|
|
|
751
785
|
baton->formatOut = "png";
|
|
752
786
|
} else if (baton->formatOut == "webp" || (baton->formatOut == "input" && inputImageType == ImageType::WEBP)) {
|
|
753
787
|
// Write WEBP to buffer
|
|
788
|
+
sharp::AssertImageTypeDimensions(image, ImageType::WEBP);
|
|
754
789
|
VipsArea *area = VIPS_AREA(image.webpsave_buffer(VImage::option()
|
|
755
790
|
->set("strip", !baton->withMetadata)
|
|
756
791
|
->set("Q", baton->webpQuality)
|
|
@@ -762,6 +797,29 @@ class PipelineWorker : public Nan::AsyncWorker {
|
|
|
762
797
|
area->free_fn = nullptr;
|
|
763
798
|
vips_area_unref(area);
|
|
764
799
|
baton->formatOut = "webp";
|
|
800
|
+
} else if (baton->formatOut == "tiff" || (baton->formatOut == "input" && inputImageType == ImageType::TIFF)) {
|
|
801
|
+
// Write TIFF to buffer
|
|
802
|
+
if (baton->tiffCompression == VIPS_FOREIGN_TIFF_COMPRESSION_JPEG) {
|
|
803
|
+
sharp::AssertImageTypeDimensions(image, ImageType::JPEG);
|
|
804
|
+
}
|
|
805
|
+
// Cast pixel values to float, if required
|
|
806
|
+
if (baton->tiffPredictor == VIPS_FOREIGN_TIFF_PREDICTOR_FLOAT) {
|
|
807
|
+
image = image.cast(VIPS_FORMAT_FLOAT);
|
|
808
|
+
}
|
|
809
|
+
VipsArea *area = VIPS_AREA(image.tiffsave_buffer(VImage::option()
|
|
810
|
+
->set("strip", !baton->withMetadata)
|
|
811
|
+
->set("Q", baton->tiffQuality)
|
|
812
|
+
->set("squash", baton->tiffSquash)
|
|
813
|
+
->set("compression", baton->tiffCompression)
|
|
814
|
+
->set("predictor", baton->tiffPredictor)
|
|
815
|
+
->set("xres", baton->tiffXres)
|
|
816
|
+
->set("yres", baton->tiffYres)));
|
|
817
|
+
baton->bufferOut = static_cast<char*>(area->data);
|
|
818
|
+
baton->bufferOutLength = area->length;
|
|
819
|
+
area->free_fn = nullptr;
|
|
820
|
+
vips_area_unref(area);
|
|
821
|
+
baton->formatOut = "tiff";
|
|
822
|
+
baton->channels = std::min(baton->channels, 3);
|
|
765
823
|
} else if (baton->formatOut == "raw" || (baton->formatOut == "input" && inputImageType == ImageType::RAW)) {
|
|
766
824
|
// Write raw, uncompressed image data to buffer
|
|
767
825
|
if (baton->greyscale || image.interpretation() == VIPS_INTERPRETATION_B_W) {
|
|
@@ -802,6 +860,7 @@ class PipelineWorker : public Nan::AsyncWorker {
|
|
|
802
860
|
!(isJpeg || isPng || isWebp || isTiff || isDz || isDzZip || isV);
|
|
803
861
|
if (baton->formatOut == "jpeg" || isJpeg || (matchInput && inputImageType == ImageType::JPEG)) {
|
|
804
862
|
// Write JPEG to file
|
|
863
|
+
sharp::AssertImageTypeDimensions(image, ImageType::JPEG);
|
|
805
864
|
image.jpegsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
|
806
865
|
->set("strip", !baton->withMetadata)
|
|
807
866
|
->set("Q", baton->jpegQuality)
|
|
@@ -815,11 +874,12 @@ class PipelineWorker : public Nan::AsyncWorker {
|
|
|
815
874
|
baton->channels = std::min(baton->channels, 3);
|
|
816
875
|
} else if (baton->formatOut == "png" || isPng || (matchInput &&
|
|
817
876
|
(inputImageType == ImageType::PNG || inputImageType == ImageType::GIF || inputImageType == ImageType::SVG))) {
|
|
877
|
+
// Write PNG to file
|
|
878
|
+
sharp::AssertImageTypeDimensions(image, ImageType::PNG);
|
|
818
879
|
// Strip profile
|
|
819
880
|
if (!baton->withMetadata) {
|
|
820
881
|
vips_image_remove(image.get_image(), VIPS_META_ICC_NAME);
|
|
821
882
|
}
|
|
822
|
-
// Write PNG to file
|
|
823
883
|
image.pngsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
|
824
884
|
->set("interlace", baton->pngProgressive)
|
|
825
885
|
->set("compression", baton->pngCompressionLevel)
|
|
@@ -827,6 +887,7 @@ class PipelineWorker : public Nan::AsyncWorker {
|
|
|
827
887
|
baton->formatOut = "png";
|
|
828
888
|
} else if (baton->formatOut == "webp" || isWebp || (matchInput && inputImageType == ImageType::WEBP)) {
|
|
829
889
|
// Write WEBP to file
|
|
890
|
+
AssertImageTypeDimensions(image, ImageType::WEBP);
|
|
830
891
|
image.webpsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
|
831
892
|
->set("strip", !baton->withMetadata)
|
|
832
893
|
->set("Q", baton->webpQuality)
|
|
@@ -836,10 +897,21 @@ class PipelineWorker : public Nan::AsyncWorker {
|
|
|
836
897
|
baton->formatOut = "webp";
|
|
837
898
|
} else if (baton->formatOut == "tiff" || isTiff || (matchInput && inputImageType == ImageType::TIFF)) {
|
|
838
899
|
// Write TIFF to file
|
|
900
|
+
if (baton->tiffCompression == VIPS_FOREIGN_TIFF_COMPRESSION_JPEG) {
|
|
901
|
+
sharp::AssertImageTypeDimensions(image, ImageType::JPEG);
|
|
902
|
+
}
|
|
903
|
+
// Cast pixel values to float, if required
|
|
904
|
+
if (baton->tiffPredictor == VIPS_FOREIGN_TIFF_PREDICTOR_FLOAT) {
|
|
905
|
+
image = image.cast(VIPS_FORMAT_FLOAT);
|
|
906
|
+
}
|
|
839
907
|
image.tiffsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
|
840
908
|
->set("strip", !baton->withMetadata)
|
|
841
909
|
->set("Q", baton->tiffQuality)
|
|
842
|
-
->set("
|
|
910
|
+
->set("squash", baton->tiffSquash)
|
|
911
|
+
->set("compression", baton->tiffCompression)
|
|
912
|
+
->set("predictor", baton->tiffPredictor)
|
|
913
|
+
->set("xres", baton->tiffXres)
|
|
914
|
+
->set("yres", baton->tiffYres));
|
|
843
915
|
baton->formatOut = "tiff";
|
|
844
916
|
baton->channels = std::min(baton->channels, 3);
|
|
845
917
|
} else if (baton->formatOut == "dz" || isDz || isDzZip) {
|
|
@@ -932,6 +1004,7 @@ class PipelineWorker : public Nan::AsyncWorker {
|
|
|
932
1004
|
Set(info, New("width").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(width)));
|
|
933
1005
|
Set(info, New("height").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(height)));
|
|
934
1006
|
Set(info, New("channels").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(baton->channels)));
|
|
1007
|
+
Set(info, New("premultiplied").ToLocalChecked(), New<v8::Boolean>(baton->premultiplied));
|
|
935
1008
|
if (baton->cropCalcLeft != -1 && baton->cropCalcLeft != -1) {
|
|
936
1009
|
Set(info, New("cropCalcLeft").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(baton->cropCalcLeft)));
|
|
937
1010
|
Set(info, New("cropCalcTop").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(baton->cropCalcTop)));
|
|
@@ -970,6 +1043,14 @@ class PipelineWorker : public Nan::AsyncWorker {
|
|
|
970
1043
|
});
|
|
971
1044
|
delete baton;
|
|
972
1045
|
|
|
1046
|
+
// Handle warnings
|
|
1047
|
+
std::string warning = sharp::VipsWarningPop();
|
|
1048
|
+
while (!warning.empty()) {
|
|
1049
|
+
v8::Local<v8::Value> message[1] = { New(warning).ToLocalChecked() };
|
|
1050
|
+
debuglog->Call(1, message);
|
|
1051
|
+
warning = sharp::VipsWarningPop();
|
|
1052
|
+
}
|
|
1053
|
+
|
|
973
1054
|
// Decrement processing task counter
|
|
974
1055
|
g_atomic_int_dec_and_test(&sharp::counterProcess);
|
|
975
1056
|
v8::Local<v8::Value> queueLength[1] = { New<v8::Uint32>(sharp::counterQueue) };
|
|
@@ -982,43 +1063,48 @@ class PipelineWorker : public Nan::AsyncWorker {
|
|
|
982
1063
|
|
|
983
1064
|
private:
|
|
984
1065
|
PipelineBaton *baton;
|
|
1066
|
+
Nan::Callback *debuglog;
|
|
985
1067
|
Nan::Callback *queueListener;
|
|
986
1068
|
std::vector<v8::Local<v8::Object>> buffersToPersist;
|
|
987
1069
|
|
|
988
1070
|
/*
|
|
989
|
-
Calculate the angle of rotation and need-to-flip for the
|
|
990
|
-
|
|
991
|
-
1. Use explicitly requested angle (supports 90, 180, 270)
|
|
992
|
-
2. Use input image EXIF Orientation header - supports mirroring
|
|
993
|
-
3. Otherwise default to zero, i.e. no rotation
|
|
1071
|
+
Calculate the angle of rotation and need-to-flip for the given Exif orientation
|
|
1072
|
+
By default, returns zero, i.e. no rotation.
|
|
994
1073
|
*/
|
|
995
1074
|
std::tuple<VipsAngle, bool, bool>
|
|
996
|
-
|
|
1075
|
+
CalculateExifRotationAndFlip(int const exifOrientation) {
|
|
997
1076
|
VipsAngle rotate = VIPS_ANGLE_D0;
|
|
998
1077
|
bool flip = FALSE;
|
|
999
1078
|
bool flop = FALSE;
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
case 5: flip = TRUE; rotate = VIPS_ANGLE_D270; break; // flip 8
|
|
1009
|
-
}
|
|
1010
|
-
} else {
|
|
1011
|
-
if (angle == 90) {
|
|
1012
|
-
rotate = VIPS_ANGLE_D90;
|
|
1013
|
-
} else if (angle == 180) {
|
|
1014
|
-
rotate = VIPS_ANGLE_D180;
|
|
1015
|
-
} else if (angle == 270) {
|
|
1016
|
-
rotate = VIPS_ANGLE_D270;
|
|
1017
|
-
}
|
|
1079
|
+
switch (exifOrientation) {
|
|
1080
|
+
case 6: rotate = VIPS_ANGLE_D90; break;
|
|
1081
|
+
case 3: rotate = VIPS_ANGLE_D180; break;
|
|
1082
|
+
case 8: rotate = VIPS_ANGLE_D270; break;
|
|
1083
|
+
case 2: flop = TRUE; break; // flop 1
|
|
1084
|
+
case 7: flip = TRUE; rotate = VIPS_ANGLE_D90; break; // flip 6
|
|
1085
|
+
case 4: flop = TRUE; rotate = VIPS_ANGLE_D180; break; // flop 3
|
|
1086
|
+
case 5: flip = TRUE; rotate = VIPS_ANGLE_D270; break; // flip 8
|
|
1018
1087
|
}
|
|
1019
1088
|
return std::make_tuple(rotate, flip, flop);
|
|
1020
1089
|
}
|
|
1021
1090
|
|
|
1091
|
+
/*
|
|
1092
|
+
Calculate the rotation for the given angle.
|
|
1093
|
+
Supports any positive or negative angle that is a multiple of 90.
|
|
1094
|
+
*/
|
|
1095
|
+
VipsAngle
|
|
1096
|
+
CalculateAngleRotation(int angle) {
|
|
1097
|
+
angle = angle % 360;
|
|
1098
|
+
if (angle < 0)
|
|
1099
|
+
angle = 360 + angle;
|
|
1100
|
+
switch (angle) {
|
|
1101
|
+
case 90: return VIPS_ANGLE_D90;
|
|
1102
|
+
case 180: return VIPS_ANGLE_D180;
|
|
1103
|
+
case 270: return VIPS_ANGLE_D270;
|
|
1104
|
+
}
|
|
1105
|
+
return VIPS_ANGLE_D0;
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1022
1108
|
/*
|
|
1023
1109
|
Assemble the suffix argument to dzsave, which is the format (by extname)
|
|
1024
1110
|
alongisde comma-separated arguments to the corresponding `formatsave` vips
|
|
@@ -1100,7 +1186,7 @@ NAN_METHOD(pipeline) {
|
|
|
1100
1186
|
// Background colour
|
|
1101
1187
|
v8::Local<v8::Object> background = AttrAs<v8::Object>(options, "background");
|
|
1102
1188
|
for (unsigned int i = 0; i < 4; i++) {
|
|
1103
|
-
baton->background[i] = AttrTo<
|
|
1189
|
+
baton->background[i] = AttrTo<double>(background, i);
|
|
1104
1190
|
}
|
|
1105
1191
|
// Overlay options
|
|
1106
1192
|
if (HasAttr(options, "overlay")) {
|
|
@@ -1140,12 +1226,10 @@ NAN_METHOD(pipeline) {
|
|
|
1140
1226
|
baton->threshold = AttrTo<int32_t>(options, "threshold");
|
|
1141
1227
|
baton->thresholdGrayscale = AttrTo<bool>(options, "thresholdGrayscale");
|
|
1142
1228
|
baton->trimTolerance = AttrTo<int32_t>(options, "trimTolerance");
|
|
1143
|
-
if (baton->accessMethod == VIPS_ACCESS_SEQUENTIAL && baton->trimTolerance != 0) {
|
|
1144
|
-
baton->accessMethod = VIPS_ACCESS_RANDOM;
|
|
1145
|
-
}
|
|
1146
1229
|
baton->gamma = AttrTo<double>(options, "gamma");
|
|
1147
1230
|
baton->greyscale = AttrTo<bool>(options, "greyscale");
|
|
1148
1231
|
baton->normalise = AttrTo<bool>(options, "normalise");
|
|
1232
|
+
baton->useExifOrientation = AttrTo<bool>(options, "useExifOrientation");
|
|
1149
1233
|
baton->angle = AttrTo<int32_t>(options, "angle");
|
|
1150
1234
|
baton->rotateBeforePreExtract = AttrTo<bool>(options, "rotateBeforePreExtract");
|
|
1151
1235
|
baton->flip = AttrTo<bool>(options, "flip");
|
|
@@ -1199,6 +1283,17 @@ NAN_METHOD(pipeline) {
|
|
|
1199
1283
|
baton->webpLossless = AttrTo<bool>(options, "webpLossless");
|
|
1200
1284
|
baton->webpNearLossless = AttrTo<bool>(options, "webpNearLossless");
|
|
1201
1285
|
baton->tiffQuality = AttrTo<uint32_t>(options, "tiffQuality");
|
|
1286
|
+
baton->tiffSquash = AttrTo<bool>(options, "tiffSquash");
|
|
1287
|
+
baton->tiffXres = AttrTo<double>(options, "tiffXres");
|
|
1288
|
+
baton->tiffYres = AttrTo<double>(options, "tiffYres");
|
|
1289
|
+
// tiff compression options
|
|
1290
|
+
baton->tiffCompression = static_cast<VipsForeignTiffCompression>(
|
|
1291
|
+
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_COMPRESSION,
|
|
1292
|
+
AttrAsStr(options, "tiffCompression").data()));
|
|
1293
|
+
baton->tiffPredictor = static_cast<VipsForeignTiffPredictor>(
|
|
1294
|
+
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_PREDICTOR,
|
|
1295
|
+
AttrAsStr(options, "tiffPredictor").data()));
|
|
1296
|
+
|
|
1202
1297
|
// Tile output
|
|
1203
1298
|
baton->tileSize = AttrTo<uint32_t>(options, "tileSize");
|
|
1204
1299
|
baton->tileOverlap = AttrTo<uint32_t>(options, "tileOverlap");
|
|
@@ -1217,13 +1312,22 @@ NAN_METHOD(pipeline) {
|
|
|
1217
1312
|
baton->tileLayout = VIPS_FOREIGN_DZ_LAYOUT_DZ;
|
|
1218
1313
|
}
|
|
1219
1314
|
baton->tileFormat = AttrAsStr(options, "tileFormat");
|
|
1315
|
+
// Force random access for certain operations
|
|
1316
|
+
if (baton->accessMethod == VIPS_ACCESS_SEQUENTIAL && (
|
|
1317
|
+
baton->trimTolerance != 0 || baton->normalise ||
|
|
1318
|
+
baton->crop == 16 || baton->crop == 17)) {
|
|
1319
|
+
baton->accessMethod = VIPS_ACCESS_RANDOM;
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
// Function to notify of libvips warnings
|
|
1323
|
+
Nan::Callback *debuglog = new Nan::Callback(AttrAs<v8::Function>(options, "debuglog"));
|
|
1220
1324
|
|
|
1221
1325
|
// Function to notify of queue length changes
|
|
1222
1326
|
Nan::Callback *queueListener = new Nan::Callback(AttrAs<v8::Function>(options, "queueListener"));
|
|
1223
1327
|
|
|
1224
1328
|
// Join queue for worker thread
|
|
1225
1329
|
Nan::Callback *callback = new Nan::Callback(info[1].As<v8::Function>());
|
|
1226
|
-
Nan::AsyncQueueWorker(new PipelineWorker(callback, baton, queueListener, buffersToPersist));
|
|
1330
|
+
Nan::AsyncQueueWorker(new PipelineWorker(callback, baton, debuglog, queueListener, buffersToPersist));
|
|
1227
1331
|
|
|
1228
1332
|
// Increment queued task counter
|
|
1229
1333
|
g_atomic_int_inc(&sharp::counterQueue);
|
package/src/pipeline.h
CHANGED
|
@@ -64,6 +64,7 @@ struct PipelineBaton {
|
|
|
64
64
|
int crop;
|
|
65
65
|
int cropCalcLeft;
|
|
66
66
|
int cropCalcTop;
|
|
67
|
+
bool premultiplied;
|
|
67
68
|
std::string kernel;
|
|
68
69
|
std::string interpolator;
|
|
69
70
|
bool centreSampling;
|
|
@@ -80,6 +81,7 @@ struct PipelineBaton {
|
|
|
80
81
|
double gamma;
|
|
81
82
|
bool greyscale;
|
|
82
83
|
bool normalise;
|
|
84
|
+
bool useExifOrientation;
|
|
83
85
|
int angle;
|
|
84
86
|
bool rotateBeforePreExtract;
|
|
85
87
|
bool flip;
|
|
@@ -104,6 +106,11 @@ struct PipelineBaton {
|
|
|
104
106
|
bool webpNearLossless;
|
|
105
107
|
bool webpLossless;
|
|
106
108
|
int tiffQuality;
|
|
109
|
+
VipsForeignTiffCompression tiffCompression;
|
|
110
|
+
VipsForeignTiffPredictor tiffPredictor;
|
|
111
|
+
bool tiffSquash;
|
|
112
|
+
double tiffXres;
|
|
113
|
+
double tiffYres;
|
|
107
114
|
std::string err;
|
|
108
115
|
bool withMetadata;
|
|
109
116
|
int withMetadataOrientation;
|
|
@@ -140,6 +147,7 @@ struct PipelineBaton {
|
|
|
140
147
|
crop(0),
|
|
141
148
|
cropCalcLeft(-1),
|
|
142
149
|
cropCalcTop(-1),
|
|
150
|
+
premultiplied(false),
|
|
143
151
|
centreSampling(false),
|
|
144
152
|
flatten(false),
|
|
145
153
|
negate(false),
|
|
@@ -153,6 +161,7 @@ struct PipelineBaton {
|
|
|
153
161
|
gamma(0.0),
|
|
154
162
|
greyscale(false),
|
|
155
163
|
normalise(false),
|
|
164
|
+
useExifOrientation(false),
|
|
156
165
|
angle(0),
|
|
157
166
|
flip(false),
|
|
158
167
|
flop(false),
|
|
@@ -172,6 +181,11 @@ struct PipelineBaton {
|
|
|
172
181
|
pngAdaptiveFiltering(true),
|
|
173
182
|
webpQuality(80),
|
|
174
183
|
tiffQuality(80),
|
|
184
|
+
tiffCompression(VIPS_FOREIGN_TIFF_COMPRESSION_JPEG),
|
|
185
|
+
tiffPredictor(VIPS_FOREIGN_TIFF_PREDICTOR_NONE),
|
|
186
|
+
tiffSquash(false),
|
|
187
|
+
tiffXres(1.0),
|
|
188
|
+
tiffYres(1.0),
|
|
175
189
|
withMetadata(false),
|
|
176
190
|
withMetadataOrientation(-1),
|
|
177
191
|
convKernelWidth(0),
|
package/src/sharp.cc
CHANGED
|
@@ -24,6 +24,9 @@
|
|
|
24
24
|
NAN_MODULE_INIT(init) {
|
|
25
25
|
vips_init("sharp");
|
|
26
26
|
|
|
27
|
+
g_log_set_handler("VIPS", static_cast<GLogLevelFlags>(G_LOG_LEVEL_WARNING),
|
|
28
|
+
static_cast<GLogFunc>(sharp::VipsWarningCallback), nullptr);
|
|
29
|
+
|
|
27
30
|
// Methods available to JavaScript
|
|
28
31
|
Nan::Set(target, Nan::New("metadata").ToLocalChecked(),
|
|
29
32
|
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(metadata)).ToLocalChecked());
|