sharp 0.30.7 → 0.31.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/pipeline.cc CHANGED
@@ -92,7 +92,10 @@ class PipelineWorker : public Napi::AsyncWorker {
92
92
  }
93
93
 
94
94
  // Rotate pre-extract
95
- if (baton->rotateBeforePreExtract) {
95
+ bool const shouldRotateBefore = baton->rotateBeforePreExtract &&
96
+ (rotation != VIPS_ANGLE_D0 || flip || flop || baton->rotationAngle != 0.0);
97
+
98
+ if (shouldRotateBefore) {
96
99
  if (rotation != VIPS_ANGLE_D0) {
97
100
  image = image.rot(rotation);
98
101
  }
@@ -118,7 +121,7 @@ class PipelineWorker : public Napi::AsyncWorker {
118
121
  // Trim
119
122
  if (baton->trimThreshold > 0.0) {
120
123
  MultiPageUnsupported(nPages, "Trim");
121
- image = sharp::Trim(image, baton->trimThreshold);
124
+ image = sharp::Trim(image, baton->trimBackground, baton->trimThreshold);
122
125
  baton->trimOffsetLeft = image.xoffset();
123
126
  baton->trimOffsetTop = image.yoffset();
124
127
  }
@@ -167,7 +170,7 @@ class PipelineWorker : public Napi::AsyncWorker {
167
170
  // - input colourspace is not specified;
168
171
  bool const shouldPreShrink = (targetResizeWidth > 0 || targetResizeHeight > 0) &&
169
172
  baton->gamma == 0 && baton->topOffsetPre == -1 && baton->trimThreshold == 0.0 &&
170
- baton->colourspaceInput == VIPS_INTERPRETATION_LAST;
173
+ baton->colourspaceInput == VIPS_INTERPRETATION_LAST && !shouldRotateBefore;
171
174
 
172
175
  if (shouldPreShrink) {
173
176
  // The common part of the shrink: the bit by which both axes must be shrunk
@@ -204,6 +207,7 @@ class PipelineWorker : public Napi::AsyncWorker {
204
207
  vips::VOption *option = VImage::option()
205
208
  ->set("access", baton->input->access)
206
209
  ->set("shrink", jpegShrinkOnLoad)
210
+ ->set("unlimited", baton->input->unlimited)
207
211
  ->set("fail_on", baton->input->failOn);
208
212
  if (baton->input->buffer != nullptr) {
209
213
  // Reload JPEG buffer
@@ -302,8 +306,7 @@ class PipelineWorker : public Napi::AsyncWorker {
302
306
  if (
303
307
  sharp::HasProfile(image) &&
304
308
  image.interpretation() != VIPS_INTERPRETATION_LABS &&
305
- image.interpretation() != VIPS_INTERPRETATION_GREY16 &&
306
- image.interpretation() != VIPS_INTERPRETATION_B_W
309
+ image.interpretation() != VIPS_INTERPRETATION_GREY16
307
310
  ) {
308
311
  // Convert to sRGB/P3 using embedded profile
309
312
  try {
@@ -322,16 +325,7 @@ class PipelineWorker : public Napi::AsyncWorker {
322
325
 
323
326
  // Flatten image to remove alpha channel
324
327
  if (baton->flatten && sharp::HasAlpha(image)) {
325
- // Scale up 8-bit values to match 16-bit input image
326
- double const multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0;
327
- // Background colour
328
- std::vector<double> background {
329
- baton->flattenBackground[0] * multiplier,
330
- baton->flattenBackground[1] * multiplier,
331
- baton->flattenBackground[2] * multiplier
332
- };
333
- image = image.flatten(VImage::option()
334
- ->set("background", background));
328
+ image = sharp::Flatten(image, baton->flattenBackground);
335
329
  }
336
330
 
337
331
  // Negate the colours in the image
@@ -353,11 +347,7 @@ class PipelineWorker : public Napi::AsyncWorker {
353
347
  bool const shouldBlur = baton->blurSigma != 0.0;
354
348
  bool const shouldConv = baton->convKernelWidth * baton->convKernelHeight > 0;
355
349
  bool const shouldSharpen = baton->sharpenSigma != 0.0;
356
- bool const shouldApplyMedian = baton->medianSize > 0;
357
350
  bool const shouldComposite = !baton->composite.empty();
358
- bool const shouldModulate = baton->brightness != 1.0 || baton->saturation != 1.0 ||
359
- baton->hue != 0.0 || baton->lightness != 0.0;
360
- bool const shouldApplyClahe = baton->claheWidth != 0 && baton->claheHeight != 0;
361
351
 
362
352
  if (shouldComposite && !sharp::HasAlpha(image)) {
363
353
  image = sharp::EnsureAlpha(image, 1);
@@ -366,26 +356,27 @@ class PipelineWorker : public Napi::AsyncWorker {
366
356
  bool const shouldPremultiplyAlpha = sharp::HasAlpha(image) &&
367
357
  (shouldResize || shouldBlur || shouldConv || shouldSharpen);
368
358
 
369
- // Premultiply image alpha channel before all transformations to avoid
370
- // dark fringing around bright pixels
371
- // See: http://entropymine.com/imageworsener/resizealpha/
372
359
  if (shouldPremultiplyAlpha) {
373
360
  image = image.premultiply();
374
361
  }
375
362
 
376
363
  // Resize
377
364
  if (shouldResize) {
378
- VipsKernel kernel = static_cast<VipsKernel>(
379
- vips_enum_from_nick(nullptr, VIPS_TYPE_KERNEL, baton->kernel.data()));
380
- if (
381
- kernel != VIPS_KERNEL_NEAREST && kernel != VIPS_KERNEL_CUBIC && kernel != VIPS_KERNEL_LANCZOS2 &&
382
- kernel != VIPS_KERNEL_LANCZOS3 && kernel != VIPS_KERNEL_MITCHELL
383
- ) {
384
- throw vips::VError("Unknown kernel");
385
- }
386
365
  image = image.resize(1.0 / hshrink, VImage::option()
387
366
  ->set("vscale", 1.0 / vshrink)
388
- ->set("kernel", kernel));
367
+ ->set("kernel", baton->kernel));
368
+ }
369
+
370
+ // Flip (mirror about Y axis)
371
+ if (baton->flip || flip) {
372
+ image = image.flip(VIPS_DIRECTION_VERTICAL);
373
+ image = sharp::RemoveExifOrientation(image);
374
+ }
375
+
376
+ // Flop (mirror about X axis)
377
+ if (baton->flop || flop) {
378
+ image = image.flip(VIPS_DIRECTION_HORIZONTAL);
379
+ image = sharp::RemoveExifOrientation(image);
389
380
  }
390
381
 
391
382
  // Rotate post-extract 90-angle
@@ -402,18 +393,6 @@ class PipelineWorker : public Napi::AsyncWorker {
402
393
  image = sharp::RemoveExifOrientation(image);
403
394
  }
404
395
 
405
- // Flip (mirror about Y axis)
406
- if (baton->flip || flip) {
407
- image = image.flip(VIPS_DIRECTION_VERTICAL);
408
- image = sharp::RemoveExifOrientation(image);
409
- }
410
-
411
- // Flop (mirror about X axis)
412
- if (baton->flop || flop) {
413
- image = image.flip(VIPS_DIRECTION_HORIZONTAL);
414
- image = sharp::RemoveExifOrientation(image);
415
- }
416
-
417
396
  // Join additional color channels to the image
418
397
  if (baton->joinChannelIn.size() > 0) {
419
398
  VImage joinImage;
@@ -445,18 +424,12 @@ class PipelineWorker : public Napi::AsyncWorker {
445
424
  std::tie(image, background) = sharp::ApplyAlpha(image, baton->resizeBackground, shouldPremultiplyAlpha);
446
425
 
447
426
  // Embed
448
-
449
- // Calculate where to position the embedded image if gravity specified, else center.
450
427
  int left;
451
428
  int top;
452
-
453
- left = static_cast<int>(round((baton->width - inputWidth) / 2));
454
- top = static_cast<int>(round((baton->height - inputHeight) / 2));
455
-
456
- int width = std::max(inputWidth, baton->width);
457
- int height = std::max(inputHeight, baton->height);
458
429
  std::tie(left, top) = sharp::CalculateEmbedPosition(
459
430
  inputWidth, inputHeight, baton->width, baton->height, baton->position);
431
+ int width = std::max(inputWidth, baton->width);
432
+ int height = std::max(inputHeight, baton->height);
460
433
 
461
434
  image = nPages > 1
462
435
  ? sharp::EmbedMultiPage(image,
@@ -555,7 +528,7 @@ class PipelineWorker : public Napi::AsyncWorker {
555
528
  VImage::option()->set("extend", VIPS_EXTEND_BACKGROUND)->set("background", background));
556
529
  }
557
530
  // Median - must happen before blurring, due to the utility of blurring after thresholding
558
- if (shouldApplyMedian) {
531
+ if (baton->medianSize > 0) {
559
532
  image = image.median(baton->medianSize);
560
533
  }
561
534
  // Threshold - must happen before blurring, due to the utility of blurring after thresholding
@@ -581,7 +554,8 @@ class PipelineWorker : public Napi::AsyncWorker {
581
554
  image = sharp::Recomb(image, baton->recombMatrix);
582
555
  }
583
556
 
584
- if (shouldModulate) {
557
+ // Modulate
558
+ if (baton->brightness != 1.0 || baton->saturation != 1.0 || baton->hue != 0.0 || baton->lightness != 0.0) {
585
559
  image = sharp::Modulate(image, baton->brightness, baton->saturation, baton->hue, baton->lightness);
586
560
  }
587
561
 
@@ -591,6 +565,18 @@ class PipelineWorker : public Napi::AsyncWorker {
591
565
  baton->sharpenX1, baton->sharpenY2, baton->sharpenY3);
592
566
  }
593
567
 
568
+ // Reverse premultiplication after all transformations
569
+ if (shouldPremultiplyAlpha) {
570
+ image = image.unpremultiply();
571
+ // Cast pixel values to integer
572
+ if (sharp::Is16Bit(image.interpretation())) {
573
+ image = image.cast(VIPS_FORMAT_USHORT);
574
+ } else {
575
+ image = image.cast(VIPS_FORMAT_UCHAR);
576
+ }
577
+ }
578
+ baton->premultiplied = shouldPremultiplyAlpha;
579
+
594
580
  // Composite
595
581
  if (shouldComposite) {
596
582
  std::vector<VImage> images = { image };
@@ -668,28 +654,16 @@ class PipelineWorker : public Napi::AsyncWorker {
668
654
  xs.push_back(left);
669
655
  ys.push_back(top);
670
656
  }
671
- image = image.composite(images, modes, VImage::option()->set("x", xs)->set("y", ys));
657
+ image = VImage::composite(images, modes, VImage::option()->set("x", xs)->set("y", ys));
672
658
  }
673
659
 
674
- // Reverse premultiplication after all transformations:
675
- if (shouldPremultiplyAlpha) {
676
- image = image.unpremultiply();
677
- // Cast pixel values to integer
678
- if (sharp::Is16Bit(image.interpretation())) {
679
- image = image.cast(VIPS_FORMAT_USHORT);
680
- } else {
681
- image = image.cast(VIPS_FORMAT_UCHAR);
682
- }
683
- }
684
- baton->premultiplied = shouldPremultiplyAlpha;
685
-
686
660
  // Gamma decoding (brighten)
687
661
  if (baton->gammaOut >= 1 && baton->gammaOut <= 3) {
688
662
  image = sharp::Gamma(image, baton->gammaOut);
689
663
  }
690
664
 
691
665
  // Linear adjustment (a * in + b)
692
- if (baton->linearA != 1.0 || baton->linearB != 0.0) {
666
+ if (!baton->linearA.empty()) {
693
667
  image = sharp::Linear(image, baton->linearA, baton->linearB);
694
668
  }
695
669
 
@@ -699,7 +673,7 @@ class PipelineWorker : public Napi::AsyncWorker {
699
673
  }
700
674
 
701
675
  // Apply contrast limiting adaptive histogram equalization (CLAHE)
702
- if (shouldApplyClahe) {
676
+ if (baton->claheWidth != 0 && baton->claheHeight != 0) {
703
677
  image = sharp::Clahe(image, baton->claheWidth, baton->claheHeight, baton->claheMaxSlope);
704
678
  }
705
679
 
@@ -873,6 +847,8 @@ class PipelineWorker : public Napi::AsyncWorker {
873
847
  ->set("near_lossless", baton->webpNearLossless)
874
848
  ->set("smart_subsample", baton->webpSmartSubsample)
875
849
  ->set("effort", baton->webpEffort)
850
+ ->set("min_size", baton->webpMinSize)
851
+ ->set("mixed", baton->webpMixed)
876
852
  ->set("alpha_q", baton->webpAlphaQuality)));
877
853
  baton->bufferOut = static_cast<char*>(area->data);
878
854
  baton->bufferOutLength = area->length;
@@ -887,6 +863,7 @@ class PipelineWorker : public Napi::AsyncWorker {
887
863
  ->set("strip", !baton->withMetadata)
888
864
  ->set("bitdepth", baton->gifBitdepth)
889
865
  ->set("effort", baton->gifEffort)
866
+ ->set("reoptimise", baton->gifReoptimise)
890
867
  ->set("dither", baton->gifDither)));
891
868
  baton->bufferOut = static_cast<char*>(area->data);
892
869
  baton->bufferOutLength = area->length;
@@ -925,12 +902,13 @@ class PipelineWorker : public Napi::AsyncWorker {
925
902
  } else if (baton->formatOut == "heif" ||
926
903
  (baton->formatOut == "input" && inputImageType == sharp::ImageType::HEIF)) {
927
904
  // Write HEIF to buffer
928
- image = sharp::RemoveAnimationProperties(image);
905
+ image = sharp::RemoveAnimationProperties(image).cast(VIPS_FORMAT_UCHAR);
929
906
  VipsArea *area = reinterpret_cast<VipsArea*>(image.heifsave_buffer(VImage::option()
930
907
  ->set("strip", !baton->withMetadata)
931
908
  ->set("Q", baton->heifQuality)
932
909
  ->set("compression", baton->heifCompression)
933
910
  ->set("effort", baton->heifEffort)
911
+ ->set("bitdepth", 8)
934
912
  ->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
935
913
  ? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
936
914
  ->set("lossless", baton->heifLossless)));
@@ -939,6 +917,19 @@ class PipelineWorker : public Napi::AsyncWorker {
939
917
  area->free_fn = nullptr;
940
918
  vips_area_unref(area);
941
919
  baton->formatOut = "heif";
920
+ } else if (baton->formatOut == "dz") {
921
+ // Write DZ to buffer
922
+ baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP;
923
+ if (!sharp::HasAlpha(image)) {
924
+ baton->tileBackground.pop_back();
925
+ }
926
+ vips::VOption *options = BuildOptionsDZ(baton);
927
+ VipsArea *area = reinterpret_cast<VipsArea*>(image.dzsave_buffer(options));
928
+ baton->bufferOut = static_cast<char*>(area->data);
929
+ baton->bufferOutLength = area->length;
930
+ area->free_fn = nullptr;
931
+ vips_area_unref(area);
932
+ baton->formatOut = "dz";
942
933
  } else if (baton->formatOut == "raw" ||
943
934
  (baton->formatOut == "input" && inputImageType == sharp::ImageType::RAW)) {
944
935
  // Write raw, uncompressed image data to buffer
@@ -1040,6 +1031,8 @@ class PipelineWorker : public Napi::AsyncWorker {
1040
1031
  ->set("near_lossless", baton->webpNearLossless)
1041
1032
  ->set("smart_subsample", baton->webpSmartSubsample)
1042
1033
  ->set("effort", baton->webpEffort)
1034
+ ->set("min_size", baton->webpMinSize)
1035
+ ->set("mixed", baton->webpMixed)
1043
1036
  ->set("alpha_q", baton->webpAlphaQuality));
1044
1037
  baton->formatOut = "webp";
1045
1038
  } else if (baton->formatOut == "gif" || (mightMatchInput && isGif) ||
@@ -1050,6 +1043,7 @@ class PipelineWorker : public Napi::AsyncWorker {
1050
1043
  ->set("strip", !baton->withMetadata)
1051
1044
  ->set("bitdepth", baton->gifBitdepth)
1052
1045
  ->set("effort", baton->gifEffort)
1046
+ ->set("reoptimise", baton->gifReoptimise)
1053
1047
  ->set("dither", baton->gifDither));
1054
1048
  baton->formatOut = "gif";
1055
1049
  } else if (baton->formatOut == "tiff" || (mightMatchInput && isTiff) ||
@@ -1080,75 +1074,26 @@ class PipelineWorker : public Napi::AsyncWorker {
1080
1074
  } else if (baton->formatOut == "heif" || (mightMatchInput && isHeif) ||
1081
1075
  (willMatchInput && inputImageType == sharp::ImageType::HEIF)) {
1082
1076
  // Write HEIF to file
1083
- image = sharp::RemoveAnimationProperties(image);
1077
+ image = sharp::RemoveAnimationProperties(image).cast(VIPS_FORMAT_UCHAR);
1084
1078
  image.heifsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
1085
1079
  ->set("strip", !baton->withMetadata)
1086
1080
  ->set("Q", baton->heifQuality)
1087
1081
  ->set("compression", baton->heifCompression)
1088
1082
  ->set("effort", baton->heifEffort)
1083
+ ->set("bitdepth", 8)
1089
1084
  ->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
1090
1085
  ? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
1091
1086
  ->set("lossless", baton->heifLossless));
1092
1087
  baton->formatOut = "heif";
1093
1088
  } else if (baton->formatOut == "dz" || isDz || isDzZip) {
1089
+ // Write DZ to file
1094
1090
  if (isDzZip) {
1095
1091
  baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP;
1096
1092
  }
1097
- // Forward format options through suffix
1098
- std::string suffix;
1099
- if (baton->tileFormat == "png") {
1100
- std::vector<std::pair<std::string, std::string>> options {
1101
- {"interlace", baton->pngProgressive ? "TRUE" : "FALSE"},
1102
- {"compression", std::to_string(baton->pngCompressionLevel)},
1103
- {"filter", baton->pngAdaptiveFiltering ? "all" : "none"}
1104
- };
1105
- suffix = AssembleSuffixString(".png", options);
1106
- } else if (baton->tileFormat == "webp") {
1107
- std::vector<std::pair<std::string, std::string>> options {
1108
- {"Q", std::to_string(baton->webpQuality)},
1109
- {"alpha_q", std::to_string(baton->webpAlphaQuality)},
1110
- {"lossless", baton->webpLossless ? "TRUE" : "FALSE"},
1111
- {"near_lossless", baton->webpNearLossless ? "TRUE" : "FALSE"},
1112
- {"smart_subsample", baton->webpSmartSubsample ? "TRUE" : "FALSE"},
1113
- {"effort", std::to_string(baton->webpEffort)}
1114
- };
1115
- suffix = AssembleSuffixString(".webp", options);
1116
- } else {
1117
- std::vector<std::pair<std::string, std::string>> options {
1118
- {"Q", std::to_string(baton->jpegQuality)},
1119
- {"interlace", baton->jpegProgressive ? "TRUE" : "FALSE"},
1120
- {"subsample_mode", baton->jpegChromaSubsampling == "4:4:4" ? "off" : "on"},
1121
- {"trellis_quant", baton->jpegTrellisQuantisation ? "TRUE" : "FALSE"},
1122
- {"quant_table", std::to_string(baton->jpegQuantisationTable)},
1123
- {"overshoot_deringing", baton->jpegOvershootDeringing ? "TRUE": "FALSE"},
1124
- {"optimize_scans", baton->jpegOptimiseScans ? "TRUE": "FALSE"},
1125
- {"optimize_coding", baton->jpegOptimiseCoding ? "TRUE": "FALSE"}
1126
- };
1127
- std::string extname = baton->tileLayout == VIPS_FOREIGN_DZ_LAYOUT_DZ ? ".jpeg" : ".jpg";
1128
- suffix = AssembleSuffixString(extname, options);
1129
- }
1130
- // Remove alpha channel from tile background if image does not contain an alpha channel
1131
1093
  if (!sharp::HasAlpha(image)) {
1132
1094
  baton->tileBackground.pop_back();
1133
1095
  }
1134
- // Write DZ to file
1135
- vips::VOption *options = VImage::option()
1136
- ->set("strip", !baton->withMetadata)
1137
- ->set("tile_size", baton->tileSize)
1138
- ->set("overlap", baton->tileOverlap)
1139
- ->set("container", baton->tileContainer)
1140
- ->set("layout", baton->tileLayout)
1141
- ->set("suffix", const_cast<char*>(suffix.data()))
1142
- ->set("angle", CalculateAngleRotation(baton->tileAngle))
1143
- ->set("background", baton->tileBackground)
1144
- ->set("centre", baton->tileCentre)
1145
- ->set("id", const_cast<char*>(baton->tileId.data()))
1146
- ->set("skip_blanks", baton->tileSkipBlanks);
1147
- // libvips chooses a default depth based on layout. Instead of replicating that logic here by
1148
- // not passing anything - libvips will handle choice
1149
- if (baton->tileDepth < VIPS_FOREIGN_DZ_DEPTH_LAST) {
1150
- options->set("depth", baton->tileDepth);
1151
- }
1096
+ vips::VOption *options = BuildOptionsDZ(baton);
1152
1097
  image.dzsave(const_cast<char*>(baton->fileOut.data()), options);
1153
1098
  baton->formatOut = "dz";
1154
1099
  } else if (baton->formatOut == "v" || (mightMatchInput && isV) ||
@@ -1217,6 +1162,10 @@ class PipelineWorker : public Napi::AsyncWorker {
1217
1162
  info.Set("trimOffsetTop", static_cast<int32_t>(baton->trimOffsetTop));
1218
1163
  }
1219
1164
 
1165
+ if (baton->input->textAutofitDpi) {
1166
+ info.Set("textAutofitDpi", static_cast<uint32_t>(baton->input->textAutofitDpi));
1167
+ }
1168
+
1220
1169
  if (baton->bufferOutLength > 0) {
1221
1170
  // Add buffer size to info
1222
1171
  info.Set("size", static_cast<uint32_t>(baton->bufferOutLength));
@@ -1305,7 +1254,7 @@ class PipelineWorker : public Napi::AsyncWorker {
1305
1254
 
1306
1255
  /*
1307
1256
  Assemble the suffix argument to dzsave, which is the format (by extname)
1308
- alongisde comma-separated arguments to the corresponding `formatsave` vips
1257
+ alongside comma-separated arguments to the corresponding `formatsave` vips
1309
1258
  action.
1310
1259
  */
1311
1260
  std::string
@@ -1320,6 +1269,67 @@ class PipelineWorker : public Napi::AsyncWorker {
1320
1269
  return extname + "[" + argument + "]";
1321
1270
  }
1322
1271
 
1272
+ /*
1273
+ Build VOption for dzsave
1274
+ */
1275
+ vips::VOption*
1276
+ BuildOptionsDZ(PipelineBaton *baton) {
1277
+ // Forward format options through suffix
1278
+ std::string suffix;
1279
+ if (baton->tileFormat == "png") {
1280
+ std::vector<std::pair<std::string, std::string>> options {
1281
+ {"interlace", baton->pngProgressive ? "TRUE" : "FALSE"},
1282
+ {"compression", std::to_string(baton->pngCompressionLevel)},
1283
+ {"filter", baton->pngAdaptiveFiltering ? "all" : "none"}
1284
+ };
1285
+ suffix = AssembleSuffixString(".png", options);
1286
+ } else if (baton->tileFormat == "webp") {
1287
+ std::vector<std::pair<std::string, std::string>> options {
1288
+ {"Q", std::to_string(baton->webpQuality)},
1289
+ {"alpha_q", std::to_string(baton->webpAlphaQuality)},
1290
+ {"lossless", baton->webpLossless ? "TRUE" : "FALSE"},
1291
+ {"near_lossless", baton->webpNearLossless ? "TRUE" : "FALSE"},
1292
+ {"smart_subsample", baton->webpSmartSubsample ? "TRUE" : "FALSE"},
1293
+ {"min_size", baton->webpMinSize ? "TRUE" : "FALSE"},
1294
+ {"mixed", baton->webpMixed ? "TRUE" : "FALSE"},
1295
+ {"effort", std::to_string(baton->webpEffort)}
1296
+ };
1297
+ suffix = AssembleSuffixString(".webp", options);
1298
+ } else {
1299
+ std::vector<std::pair<std::string, std::string>> options {
1300
+ {"Q", std::to_string(baton->jpegQuality)},
1301
+ {"interlace", baton->jpegProgressive ? "TRUE" : "FALSE"},
1302
+ {"subsample_mode", baton->jpegChromaSubsampling == "4:4:4" ? "off" : "on"},
1303
+ {"trellis_quant", baton->jpegTrellisQuantisation ? "TRUE" : "FALSE"},
1304
+ {"quant_table", std::to_string(baton->jpegQuantisationTable)},
1305
+ {"overshoot_deringing", baton->jpegOvershootDeringing ? "TRUE": "FALSE"},
1306
+ {"optimize_scans", baton->jpegOptimiseScans ? "TRUE": "FALSE"},
1307
+ {"optimize_coding", baton->jpegOptimiseCoding ? "TRUE": "FALSE"}
1308
+ };
1309
+ std::string extname = baton->tileLayout == VIPS_FOREIGN_DZ_LAYOUT_DZ ? ".jpeg" : ".jpg";
1310
+ suffix = AssembleSuffixString(extname, options);
1311
+ }
1312
+ vips::VOption *options = VImage::option()
1313
+ ->set("strip", !baton->withMetadata)
1314
+ ->set("tile_size", baton->tileSize)
1315
+ ->set("overlap", baton->tileOverlap)
1316
+ ->set("container", baton->tileContainer)
1317
+ ->set("layout", baton->tileLayout)
1318
+ ->set("suffix", const_cast<char*>(suffix.data()))
1319
+ ->set("angle", CalculateAngleRotation(baton->tileAngle))
1320
+ ->set("background", baton->tileBackground)
1321
+ ->set("centre", baton->tileCentre)
1322
+ ->set("id", const_cast<char*>(baton->tileId.data()))
1323
+ ->set("skip_blanks", baton->tileSkipBlanks);
1324
+ if (baton->tileDepth < VIPS_FOREIGN_DZ_DEPTH_LAST) {
1325
+ options->set("depth", baton->tileDepth);
1326
+ }
1327
+ if (!baton->tileBasename.empty()) {
1328
+ options->set("basename", const_cast<char*>(baton->tileBasename.data()));
1329
+ }
1330
+ return options;
1331
+ }
1332
+
1323
1333
  /*
1324
1334
  Clear all thread-local data.
1325
1335
  */
@@ -1365,17 +1375,13 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1365
1375
  } else if (canvas == "ignore_aspect") {
1366
1376
  baton->canvas = sharp::Canvas::IGNORE_ASPECT;
1367
1377
  }
1368
- // Tint chroma
1369
- baton->tintA = sharp::AttrAsDouble(options, "tintA");
1370
- baton->tintB = sharp::AttrAsDouble(options, "tintB");
1371
1378
  // Composite
1372
1379
  Napi::Array compositeArray = options.Get("composite").As<Napi::Array>();
1373
1380
  for (unsigned int i = 0; i < compositeArray.Length(); i++) {
1374
1381
  Napi::Object compositeObject = compositeArray.Get(i).As<Napi::Object>();
1375
1382
  Composite *composite = new Composite;
1376
1383
  composite->input = sharp::CreateInputDescriptor(compositeObject.Get("input").As<Napi::Object>());
1377
- composite->mode = static_cast<VipsBlendMode>(
1378
- vips_enum_from_nick(nullptr, VIPS_TYPE_BLEND_MODE, sharp::AttrAsStr(compositeObject, "blend").data()));
1384
+ composite->mode = sharp::AttrAsEnum<VipsBlendMode>(compositeObject, "blend", VIPS_TYPE_BLEND_MODE);
1379
1385
  composite->gravity = sharp::AttrAsUint32(compositeObject, "gravity");
1380
1386
  composite->left = sharp::AttrAsInt32(compositeObject, "left");
1381
1387
  composite->top = sharp::AttrAsInt32(compositeObject, "top");
@@ -1389,7 +1395,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1389
1395
  baton->withoutReduction = sharp::AttrAsBool(options, "withoutReduction");
1390
1396
  baton->position = sharp::AttrAsInt32(options, "position");
1391
1397
  baton->resizeBackground = sharp::AttrAsVectorOfDouble(options, "resizeBackground");
1392
- baton->kernel = sharp::AttrAsStr(options, "kernel");
1398
+ baton->kernel = sharp::AttrAsEnum<VipsKernel>(options, "kernel", VIPS_TYPE_KERNEL);
1393
1399
  baton->fastShrinkOnLoad = sharp::AttrAsBool(options, "fastShrinkOnLoad");
1394
1400
  // Join Channel Options
1395
1401
  if (options.Has("joinChannelIn")) {
@@ -1418,13 +1424,16 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1418
1424
  baton->sharpenY3 = sharp::AttrAsDouble(options, "sharpenY3");
1419
1425
  baton->threshold = sharp::AttrAsInt32(options, "threshold");
1420
1426
  baton->thresholdGrayscale = sharp::AttrAsBool(options, "thresholdGrayscale");
1427
+ baton->trimBackground = sharp::AttrAsVectorOfDouble(options, "trimBackground");
1421
1428
  baton->trimThreshold = sharp::AttrAsDouble(options, "trimThreshold");
1422
1429
  baton->gamma = sharp::AttrAsDouble(options, "gamma");
1423
1430
  baton->gammaOut = sharp::AttrAsDouble(options, "gammaOut");
1424
- baton->linearA = sharp::AttrAsDouble(options, "linearA");
1425
- baton->linearB = sharp::AttrAsDouble(options, "linearB");
1431
+ baton->linearA = sharp::AttrAsVectorOfDouble(options, "linearA");
1432
+ baton->linearB = sharp::AttrAsVectorOfDouble(options, "linearB");
1426
1433
  baton->greyscale = sharp::AttrAsBool(options, "greyscale");
1427
1434
  baton->normalise = sharp::AttrAsBool(options, "normalise");
1435
+ baton->tintA = sharp::AttrAsDouble(options, "tintA");
1436
+ baton->tintB = sharp::AttrAsDouble(options, "tintB");
1428
1437
  baton->claheWidth = sharp::AttrAsUint32(options, "claheWidth");
1429
1438
  baton->claheHeight = sharp::AttrAsUint32(options, "claheHeight");
1430
1439
  baton->claheMaxSlope = sharp::AttrAsUint32(options, "claheMaxSlope");
@@ -1452,10 +1461,10 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1452
1461
  baton->ensureAlpha = sharp::AttrAsDouble(options, "ensureAlpha");
1453
1462
  if (options.Has("boolean")) {
1454
1463
  baton->boolean = sharp::CreateInputDescriptor(options.Get("boolean").As<Napi::Object>());
1455
- baton->booleanOp = sharp::GetBooleanOperation(sharp::AttrAsStr(options, "booleanOp"));
1464
+ baton->booleanOp = sharp::AttrAsEnum<VipsOperationBoolean>(options, "booleanOp", VIPS_TYPE_OPERATION_BOOLEAN);
1456
1465
  }
1457
1466
  if (options.Has("bandBoolOp")) {
1458
- baton->bandBoolOp = sharp::GetBooleanOperation(sharp::AttrAsStr(options, "bandBoolOp"));
1467
+ baton->bandBoolOp = sharp::AttrAsEnum<VipsOperationBoolean>(options, "bandBoolOp", VIPS_TYPE_OPERATION_BOOLEAN);
1459
1468
  }
1460
1469
  if (options.Has("convKernel")) {
1461
1470
  Napi::Object kernel = options.Get("convKernel").As<Napi::Object>();
@@ -1477,11 +1486,12 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1477
1486
  baton->recombMatrix[i] = sharp::AttrAsDouble(recombMatrix, i);
1478
1487
  }
1479
1488
  }
1480
- baton->colourspaceInput = sharp::GetInterpretation(sharp::AttrAsStr(options, "colourspaceInput"));
1489
+ baton->colourspaceInput = sharp::AttrAsEnum<VipsInterpretation>(
1490
+ options, "colourspaceInput", VIPS_TYPE_INTERPRETATION);
1481
1491
  if (baton->colourspaceInput == VIPS_INTERPRETATION_ERROR) {
1482
1492
  baton->colourspaceInput = VIPS_INTERPRETATION_LAST;
1483
1493
  }
1484
- baton->colourspace = sharp::GetInterpretation(sharp::AttrAsStr(options, "colourspace"));
1494
+ baton->colourspace = sharp::AttrAsEnum<VipsInterpretation>(options, "colourspace", VIPS_TYPE_INTERPRETATION);
1485
1495
  if (baton->colourspace == VIPS_INTERPRETATION_ERROR) {
1486
1496
  baton->colourspace = VIPS_INTERPRETATION_sRGB;
1487
1497
  }
@@ -1496,7 +1506,9 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1496
1506
  Napi::Array mdStrKeys = mdStrs.GetPropertyNames();
1497
1507
  for (unsigned int i = 0; i < mdStrKeys.Length(); i++) {
1498
1508
  std::string k = sharp::AttrAsStr(mdStrKeys, i);
1499
- baton->withMetadataStrs.insert(std::make_pair(k, sharp::AttrAsStr(mdStrs, k)));
1509
+ if (mdStrs.HasOwnProperty(k)) {
1510
+ baton->withMetadataStrs.insert(std::make_pair(k, sharp::AttrAsStr(mdStrs, k)));
1511
+ }
1500
1512
  }
1501
1513
  baton->timeoutSeconds = sharp::AttrAsUint32(options, "timeoutSeconds");
1502
1514
  // Format-specific
@@ -1527,9 +1539,12 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1527
1539
  baton->webpNearLossless = sharp::AttrAsBool(options, "webpNearLossless");
1528
1540
  baton->webpSmartSubsample = sharp::AttrAsBool(options, "webpSmartSubsample");
1529
1541
  baton->webpEffort = sharp::AttrAsUint32(options, "webpEffort");
1542
+ baton->webpMinSize = sharp::AttrAsBool(options, "webpMinSize");
1543
+ baton->webpMixed = sharp::AttrAsBool(options, "webpMixed");
1530
1544
  baton->gifBitdepth = sharp::AttrAsUint32(options, "gifBitdepth");
1531
1545
  baton->gifEffort = sharp::AttrAsUint32(options, "gifEffort");
1532
1546
  baton->gifDither = sharp::AttrAsDouble(options, "gifDither");
1547
+ baton->gifReoptimise = sharp::AttrAsBool(options, "gifReoptimise");
1533
1548
  baton->tiffQuality = sharp::AttrAsUint32(options, "tiffQuality");
1534
1549
  baton->tiffPyramid = sharp::AttrAsBool(options, "tiffPyramid");
1535
1550
  baton->tiffBitdepth = sharp::AttrAsUint32(options, "tiffBitdepth");
@@ -1541,28 +1556,19 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1541
1556
  if (baton->tiffXres == 1.0 && baton->tiffYres == 1.0 && baton->withMetadataDensity > 0) {
1542
1557
  baton->tiffXres = baton->tiffYres = baton->withMetadataDensity / 25.4;
1543
1558
  }
1544
- // tiff compression options
1545
- baton->tiffCompression = static_cast<VipsForeignTiffCompression>(
1546
- vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_COMPRESSION,
1547
- sharp::AttrAsStr(options, "tiffCompression").data()));
1548
- baton->tiffPredictor = static_cast<VipsForeignTiffPredictor>(
1549
- vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_PREDICTOR,
1550
- sharp::AttrAsStr(options, "tiffPredictor").data()));
1551
- baton->tiffResolutionUnit = static_cast<VipsForeignTiffResunit>(
1552
- vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_RESUNIT,
1553
- sharp::AttrAsStr(options, "tiffResolutionUnit").data()));
1554
-
1559
+ baton->tiffCompression = sharp::AttrAsEnum<VipsForeignTiffCompression>(
1560
+ options, "tiffCompression", VIPS_TYPE_FOREIGN_TIFF_COMPRESSION);
1561
+ baton->tiffPredictor = sharp::AttrAsEnum<VipsForeignTiffPredictor>(
1562
+ options, "tiffPredictor", VIPS_TYPE_FOREIGN_TIFF_PREDICTOR);
1563
+ baton->tiffResolutionUnit = sharp::AttrAsEnum<VipsForeignTiffResunit>(
1564
+ options, "tiffResolutionUnit", VIPS_TYPE_FOREIGN_TIFF_RESUNIT);
1555
1565
  baton->heifQuality = sharp::AttrAsUint32(options, "heifQuality");
1556
1566
  baton->heifLossless = sharp::AttrAsBool(options, "heifLossless");
1557
- baton->heifCompression = static_cast<VipsForeignHeifCompression>(
1558
- vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_HEIF_COMPRESSION,
1559
- sharp::AttrAsStr(options, "heifCompression").data()));
1567
+ baton->heifCompression = sharp::AttrAsEnum<VipsForeignHeifCompression>(
1568
+ options, "heifCompression", VIPS_TYPE_FOREIGN_HEIF_COMPRESSION);
1560
1569
  baton->heifEffort = sharp::AttrAsUint32(options, "heifEffort");
1561
1570
  baton->heifChromaSubsampling = sharp::AttrAsStr(options, "heifChromaSubsampling");
1562
- // Raw output
1563
- baton->rawDepth = static_cast<VipsBandFormat>(
1564
- vips_enum_from_nick(nullptr, VIPS_TYPE_BAND_FORMAT,
1565
- sharp::AttrAsStr(options, "rawDepth").data()));
1571
+ baton->rawDepth = sharp::AttrAsEnum<VipsBandFormat>(options, "rawDepth", VIPS_TYPE_BAND_FORMAT);
1566
1572
  // Animated output properties
1567
1573
  if (sharp::HasAttr(options, "loop")) {
1568
1574
  baton->loop = sharp::AttrAsUint32(options, "loop");
@@ -1570,24 +1576,19 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1570
1576
  if (sharp::HasAttr(options, "delay")) {
1571
1577
  baton->delay = sharp::AttrAsInt32Vector(options, "delay");
1572
1578
  }
1573
- // Tile output
1574
1579
  baton->tileSize = sharp::AttrAsUint32(options, "tileSize");
1575
1580
  baton->tileOverlap = sharp::AttrAsUint32(options, "tileOverlap");
1576
1581
  baton->tileAngle = sharp::AttrAsInt32(options, "tileAngle");
1577
1582
  baton->tileBackground = sharp::AttrAsVectorOfDouble(options, "tileBackground");
1578
1583
  baton->tileSkipBlanks = sharp::AttrAsInt32(options, "tileSkipBlanks");
1579
- baton->tileContainer = static_cast<VipsForeignDzContainer>(
1580
- vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_DZ_CONTAINER,
1581
- sharp::AttrAsStr(options, "tileContainer").data()));
1582
- baton->tileLayout = static_cast<VipsForeignDzLayout>(
1583
- vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_DZ_LAYOUT,
1584
- sharp::AttrAsStr(options, "tileLayout").data()));
1584
+ baton->tileContainer = sharp::AttrAsEnum<VipsForeignDzContainer>(
1585
+ options, "tileContainer", VIPS_TYPE_FOREIGN_DZ_CONTAINER);
1586
+ baton->tileLayout = sharp::AttrAsEnum<VipsForeignDzLayout>(options, "tileLayout", VIPS_TYPE_FOREIGN_DZ_LAYOUT);
1585
1587
  baton->tileFormat = sharp::AttrAsStr(options, "tileFormat");
1586
- baton->tileDepth = static_cast<VipsForeignDzDepth>(
1587
- vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_DZ_DEPTH,
1588
- sharp::AttrAsStr(options, "tileDepth").data()));
1588
+ baton->tileDepth = sharp::AttrAsEnum<VipsForeignDzDepth>(options, "tileDepth", VIPS_TYPE_FOREIGN_DZ_DEPTH);
1589
1589
  baton->tileCentre = sharp::AttrAsBool(options, "tileCentre");
1590
1590
  baton->tileId = sharp::AttrAsStr(options, "tileId");
1591
+ baton->tileBasename = sharp::AttrAsStr(options, "tileBasename");
1591
1592
 
1592
1593
  // Force random access for certain operations
1593
1594
  if (baton->input->access == VIPS_ACCESS_SEQUENTIAL) {