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/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
- : Nan::AsyncWorker(callback), baton(baton), queueListener(queueListener), buffersToPersist(buffersToPersist) {
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
- bool flip;
84
- bool flop;
85
- std::tie(rotation, flip, flop) = CalculateRotationAndFlip(baton->angle, image);
86
- if (flip && !baton->flip) {
87
- // Add flip operation due to EXIF mirroring
88
- baton->flip = TRUE;
89
- }
90
- if (flop && !baton->flop) {
91
- // Add flip operation due to EXIF mirroring
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
- bool hasOverlay = baton->overlay != nullptr;
337
- if (hasOverlay && !HasAlpha(image)) {
338
- double const multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0;
339
- image = image.bandjoin(
340
- VImage::new_matrix(image.width(), image.height()).new_from_image(255 * multiplier));
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 || (hasOverlay && !shouldCutout));
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 (kernel != VIPS_KERNEL_CUBIC && kernel != VIPS_KERNEL_LANCZOS2 && kernel != VIPS_KERNEL_LANCZOS3) {
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 affine enlargement
432
+ // Perform enlargement
410
433
  if (yresidual > 1.0 || xresidual > 1.0) {
411
- vips::VInterpolate interpolator = vips::VInterpolate::new_from_name(baton->interpolator.data());
412
- if (yresidual > 1.0) {
413
- image = image.affine({1.0, 0.0, 0.0, yresidual}, VImage::option()
414
- ->set("interpolate", interpolator));
415
- }
416
- if (xresidual > 1.0) {
417
- image = image.affine({xresidual, 0.0, 0.0, 1.0}, VImage::option()
418
- ->set("interpolate", interpolator));
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
- } else if (baton->crop == 16) {
500
- // Entropy-based crop
501
- std::tie(left, top) = sharp::Crop(image, baton->width, baton->height, sharp::EntropyStrategy());
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
- std::tie(left, top) = sharp::Crop(image, baton->width, baton->height, sharp::AttentionStrategy());
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 (hasOverlay) {
584
- VImage overlayImage;
585
- ImageType overlayImageType = ImageType::UNKNOWN;
586
- std::tie(overlayImage, overlayImageType) = OpenInput(baton->overlay, baton->accessMethod);
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 (shouldCutout) {
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 has alpha channel
624
- if (!HasAlpha(overlayImage)) {
625
- double const multiplier = sharp::Is16Bit(overlayImage.interpretation()) ? 256.0 : 1.0;
626
- overlayImage = overlayImage.bandjoin(
627
- VImage::new_matrix(overlayImage.width(), overlayImage.height()).new_from_image(255 * multiplier));
628
- }
629
- // Ensure image has alpha channel
630
- if (!HasAlpha(image)) {
631
- double const multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0;
632
- image = image.bandjoin(
633
- VImage::new_matrix(image.width(), image.height()).new_from_image(255 * multiplier));
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
- // Ensure overlay is premultiplied sRGB
636
- overlayImage = overlayImage.colourspace(VIPS_INTERPRETATION_sRGB).premultiply();
663
+ int left;
664
+ int top;
637
665
  if (baton->overlayXOffset >= 0 && baton->overlayYOffset >= 0) {
638
- // Composite images with given offsets
639
- image = sharp::Composite(overlayImage, image, baton->overlayXOffset, baton->overlayYOffset);
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
- image = sharp::Composite(overlayImage, image, baton->overlayGravity);
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("compression", VIPS_FOREIGN_TIFF_COMPRESSION_JPEG));
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 output image.
990
- In order of priority:
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
- CalculateRotationAndFlip(int const angle, vips::VImage image) {
1075
+ CalculateExifRotationAndFlip(int const exifOrientation) {
997
1076
  VipsAngle rotate = VIPS_ANGLE_D0;
998
1077
  bool flip = FALSE;
999
1078
  bool flop = FALSE;
1000
- if (angle == -1) {
1001
- switch (sharp::ExifOrientation(image)) {
1002
- case 6: rotate = VIPS_ANGLE_D90; break;
1003
- case 3: rotate = VIPS_ANGLE_D180; break;
1004
- case 8: rotate = VIPS_ANGLE_D270; break;
1005
- case 2: flop = TRUE; break; // flop 1
1006
- case 7: flip = TRUE; rotate = VIPS_ANGLE_D90; break; // flip 6
1007
- case 4: flop = TRUE; rotate = VIPS_ANGLE_D180; break; // flop 3
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<uint32_t>(background, i);
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());