sharp 0.30.5 → 0.31.0

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
@@ -118,7 +118,7 @@ class PipelineWorker : public Napi::AsyncWorker {
118
118
  // Trim
119
119
  if (baton->trimThreshold > 0.0) {
120
120
  MultiPageUnsupported(nPages, "Trim");
121
- image = sharp::Trim(image, baton->trimThreshold);
121
+ image = sharp::Trim(image, baton->trimBackground, baton->trimThreshold);
122
122
  baton->trimOffsetLeft = image.xoffset();
123
123
  baton->trimOffsetTop = image.yoffset();
124
124
  }
@@ -188,8 +188,10 @@ class PipelineWorker : public Napi::AsyncWorker {
188
188
  if (jpegShrinkOnLoad > 1 && static_cast<int>(shrink) == jpegShrinkOnLoad) {
189
189
  jpegShrinkOnLoad /= 2;
190
190
  }
191
- } else if (inputImageType == sharp::ImageType::WEBP ||
192
- inputImageType == sharp::ImageType::SVG ||
191
+ } else if (inputImageType == sharp::ImageType::WEBP && shrink > 1.0) {
192
+ // Avoid upscaling via webp
193
+ scale = 1.0 / shrink;
194
+ } else if (inputImageType == sharp::ImageType::SVG ||
193
195
  inputImageType == sharp::ImageType::PDF) {
194
196
  scale = 1.0 / shrink;
195
197
  }
@@ -202,6 +204,7 @@ class PipelineWorker : public Napi::AsyncWorker {
202
204
  vips::VOption *option = VImage::option()
203
205
  ->set("access", baton->input->access)
204
206
  ->set("shrink", jpegShrinkOnLoad)
207
+ ->set("unlimited", baton->input->unlimited)
205
208
  ->set("fail_on", baton->input->failOn);
206
209
  if (baton->input->buffer != nullptr) {
207
210
  // Reload JPEG buffer
@@ -300,8 +303,7 @@ class PipelineWorker : public Napi::AsyncWorker {
300
303
  if (
301
304
  sharp::HasProfile(image) &&
302
305
  image.interpretation() != VIPS_INTERPRETATION_LABS &&
303
- image.interpretation() != VIPS_INTERPRETATION_GREY16 &&
304
- image.interpretation() != VIPS_INTERPRETATION_B_W
306
+ image.interpretation() != VIPS_INTERPRETATION_GREY16
305
307
  ) {
306
308
  // Convert to sRGB/P3 using embedded profile
307
309
  try {
@@ -320,16 +322,7 @@ class PipelineWorker : public Napi::AsyncWorker {
320
322
 
321
323
  // Flatten image to remove alpha channel
322
324
  if (baton->flatten && sharp::HasAlpha(image)) {
323
- // Scale up 8-bit values to match 16-bit input image
324
- double const multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0;
325
- // Background colour
326
- std::vector<double> background {
327
- baton->flattenBackground[0] * multiplier,
328
- baton->flattenBackground[1] * multiplier,
329
- baton->flattenBackground[2] * multiplier
330
- };
331
- image = image.flatten(VImage::option()
332
- ->set("background", background));
325
+ image = sharp::Flatten(image, baton->flattenBackground);
333
326
  }
334
327
 
335
328
  // Negate the colours in the image
@@ -351,11 +344,7 @@ class PipelineWorker : public Napi::AsyncWorker {
351
344
  bool const shouldBlur = baton->blurSigma != 0.0;
352
345
  bool const shouldConv = baton->convKernelWidth * baton->convKernelHeight > 0;
353
346
  bool const shouldSharpen = baton->sharpenSigma != 0.0;
354
- bool const shouldApplyMedian = baton->medianSize > 0;
355
347
  bool const shouldComposite = !baton->composite.empty();
356
- bool const shouldModulate = baton->brightness != 1.0 || baton->saturation != 1.0 ||
357
- baton->hue != 0.0 || baton->lightness != 0.0;
358
- bool const shouldApplyClahe = baton->claheWidth != 0 && baton->claheHeight != 0;
359
348
 
360
349
  if (shouldComposite && !sharp::HasAlpha(image)) {
361
350
  image = sharp::EnsureAlpha(image, 1);
@@ -364,26 +353,27 @@ class PipelineWorker : public Napi::AsyncWorker {
364
353
  bool const shouldPremultiplyAlpha = sharp::HasAlpha(image) &&
365
354
  (shouldResize || shouldBlur || shouldConv || shouldSharpen);
366
355
 
367
- // Premultiply image alpha channel before all transformations to avoid
368
- // dark fringing around bright pixels
369
- // See: http://entropymine.com/imageworsener/resizealpha/
370
356
  if (shouldPremultiplyAlpha) {
371
357
  image = image.premultiply();
372
358
  }
373
359
 
374
360
  // Resize
375
361
  if (shouldResize) {
376
- VipsKernel kernel = static_cast<VipsKernel>(
377
- vips_enum_from_nick(nullptr, VIPS_TYPE_KERNEL, baton->kernel.data()));
378
- if (
379
- kernel != VIPS_KERNEL_NEAREST && kernel != VIPS_KERNEL_CUBIC && kernel != VIPS_KERNEL_LANCZOS2 &&
380
- kernel != VIPS_KERNEL_LANCZOS3 && kernel != VIPS_KERNEL_MITCHELL
381
- ) {
382
- throw vips::VError("Unknown kernel");
383
- }
384
362
  image = image.resize(1.0 / hshrink, VImage::option()
385
363
  ->set("vscale", 1.0 / vshrink)
386
- ->set("kernel", kernel));
364
+ ->set("kernel", baton->kernel));
365
+ }
366
+
367
+ // Flip (mirror about Y axis)
368
+ if (baton->flip || flip) {
369
+ image = image.flip(VIPS_DIRECTION_VERTICAL);
370
+ image = sharp::RemoveExifOrientation(image);
371
+ }
372
+
373
+ // Flop (mirror about X axis)
374
+ if (baton->flop || flop) {
375
+ image = image.flip(VIPS_DIRECTION_HORIZONTAL);
376
+ image = sharp::RemoveExifOrientation(image);
387
377
  }
388
378
 
389
379
  // Rotate post-extract 90-angle
@@ -400,18 +390,6 @@ class PipelineWorker : public Napi::AsyncWorker {
400
390
  image = sharp::RemoveExifOrientation(image);
401
391
  }
402
392
 
403
- // Flip (mirror about Y axis)
404
- if (baton->flip || flip) {
405
- image = image.flip(VIPS_DIRECTION_VERTICAL);
406
- image = sharp::RemoveExifOrientation(image);
407
- }
408
-
409
- // Flop (mirror about X axis)
410
- if (baton->flop || flop) {
411
- image = image.flip(VIPS_DIRECTION_HORIZONTAL);
412
- image = sharp::RemoveExifOrientation(image);
413
- }
414
-
415
393
  // Join additional color channels to the image
416
394
  if (baton->joinChannelIn.size() > 0) {
417
395
  VImage joinImage;
@@ -443,18 +421,12 @@ class PipelineWorker : public Napi::AsyncWorker {
443
421
  std::tie(image, background) = sharp::ApplyAlpha(image, baton->resizeBackground, shouldPremultiplyAlpha);
444
422
 
445
423
  // Embed
446
-
447
- // Calculate where to position the embedded image if gravity specified, else center.
448
424
  int left;
449
425
  int top;
450
-
451
- left = static_cast<int>(round((baton->width - inputWidth) / 2));
452
- top = static_cast<int>(round((baton->height - inputHeight) / 2));
453
-
454
- int width = std::max(inputWidth, baton->width);
455
- int height = std::max(inputHeight, baton->height);
456
426
  std::tie(left, top) = sharp::CalculateEmbedPosition(
457
427
  inputWidth, inputHeight, baton->width, baton->height, baton->position);
428
+ int width = std::max(inputWidth, baton->width);
429
+ int height = std::max(inputHeight, baton->height);
458
430
 
459
431
  image = nPages > 1
460
432
  ? sharp::EmbedMultiPage(image,
@@ -553,7 +525,7 @@ class PipelineWorker : public Napi::AsyncWorker {
553
525
  VImage::option()->set("extend", VIPS_EXTEND_BACKGROUND)->set("background", background));
554
526
  }
555
527
  // Median - must happen before blurring, due to the utility of blurring after thresholding
556
- if (shouldApplyMedian) {
528
+ if (baton->medianSize > 0) {
557
529
  image = image.median(baton->medianSize);
558
530
  }
559
531
  // Threshold - must happen before blurring, due to the utility of blurring after thresholding
@@ -579,7 +551,8 @@ class PipelineWorker : public Napi::AsyncWorker {
579
551
  image = sharp::Recomb(image, baton->recombMatrix);
580
552
  }
581
553
 
582
- if (shouldModulate) {
554
+ // Modulate
555
+ if (baton->brightness != 1.0 || baton->saturation != 1.0 || baton->hue != 0.0 || baton->lightness != 0.0) {
583
556
  image = sharp::Modulate(image, baton->brightness, baton->saturation, baton->hue, baton->lightness);
584
557
  }
585
558
 
@@ -589,6 +562,18 @@ class PipelineWorker : public Napi::AsyncWorker {
589
562
  baton->sharpenX1, baton->sharpenY2, baton->sharpenY3);
590
563
  }
591
564
 
565
+ // Reverse premultiplication after all transformations
566
+ if (shouldPremultiplyAlpha) {
567
+ image = image.unpremultiply();
568
+ // Cast pixel values to integer
569
+ if (sharp::Is16Bit(image.interpretation())) {
570
+ image = image.cast(VIPS_FORMAT_USHORT);
571
+ } else {
572
+ image = image.cast(VIPS_FORMAT_UCHAR);
573
+ }
574
+ }
575
+ baton->premultiplied = shouldPremultiplyAlpha;
576
+
592
577
  // Composite
593
578
  if (shouldComposite) {
594
579
  std::vector<VImage> images = { image };
@@ -607,14 +592,14 @@ class PipelineWorker : public Napi::AsyncWorker {
607
592
  int across = 0;
608
593
  int down = 0;
609
594
  // Use gravity in overlay
610
- if (compositeImage.width() <= baton->width) {
595
+ if (compositeImage.width() <= image.width()) {
611
596
  across = static_cast<int>(ceil(static_cast<double>(image.width()) / compositeImage.width()));
612
597
  // Ensure odd number of tiles across when gravity is centre, north or south
613
598
  if (composite->gravity == 0 || composite->gravity == 1 || composite->gravity == 3) {
614
599
  across |= 1;
615
600
  }
616
601
  }
617
- if (compositeImage.height() <= baton->height) {
602
+ if (compositeImage.height() <= image.height()) {
618
603
  down = static_cast<int>(ceil(static_cast<double>(image.height()) / compositeImage.height()));
619
604
  // Ensure odd number of tiles down when gravity is centre, east or west
620
605
  if (composite->gravity == 0 || composite->gravity == 2 || composite->gravity == 4) {
@@ -666,28 +651,16 @@ class PipelineWorker : public Napi::AsyncWorker {
666
651
  xs.push_back(left);
667
652
  ys.push_back(top);
668
653
  }
669
- image = image.composite(images, modes, VImage::option()->set("x", xs)->set("y", ys));
654
+ image = VImage::composite(images, modes, VImage::option()->set("x", xs)->set("y", ys));
670
655
  }
671
656
 
672
- // Reverse premultiplication after all transformations:
673
- if (shouldPremultiplyAlpha) {
674
- image = image.unpremultiply();
675
- // Cast pixel values to integer
676
- if (sharp::Is16Bit(image.interpretation())) {
677
- image = image.cast(VIPS_FORMAT_USHORT);
678
- } else {
679
- image = image.cast(VIPS_FORMAT_UCHAR);
680
- }
681
- }
682
- baton->premultiplied = shouldPremultiplyAlpha;
683
-
684
657
  // Gamma decoding (brighten)
685
658
  if (baton->gammaOut >= 1 && baton->gammaOut <= 3) {
686
659
  image = sharp::Gamma(image, baton->gammaOut);
687
660
  }
688
661
 
689
662
  // Linear adjustment (a * in + b)
690
- if (baton->linearA != 1.0 || baton->linearB != 0.0) {
663
+ if (!baton->linearA.empty()) {
691
664
  image = sharp::Linear(image, baton->linearA, baton->linearB);
692
665
  }
693
666
 
@@ -697,7 +670,7 @@ class PipelineWorker : public Napi::AsyncWorker {
697
670
  }
698
671
 
699
672
  // Apply contrast limiting adaptive histogram equalization (CLAHE)
700
- if (shouldApplyClahe) {
673
+ if (baton->claheWidth != 0 && baton->claheHeight != 0) {
701
674
  image = sharp::Clahe(image, baton->claheWidth, baton->claheHeight, baton->claheMaxSlope);
702
675
  }
703
676
 
@@ -871,6 +844,8 @@ class PipelineWorker : public Napi::AsyncWorker {
871
844
  ->set("near_lossless", baton->webpNearLossless)
872
845
  ->set("smart_subsample", baton->webpSmartSubsample)
873
846
  ->set("effort", baton->webpEffort)
847
+ ->set("min_size", baton->webpMinSize)
848
+ ->set("mixed", baton->webpMixed)
874
849
  ->set("alpha_q", baton->webpAlphaQuality)));
875
850
  baton->bufferOut = static_cast<char*>(area->data);
876
851
  baton->bufferOutLength = area->length;
@@ -885,6 +860,7 @@ class PipelineWorker : public Napi::AsyncWorker {
885
860
  ->set("strip", !baton->withMetadata)
886
861
  ->set("bitdepth", baton->gifBitdepth)
887
862
  ->set("effort", baton->gifEffort)
863
+ ->set("reoptimise", baton->gifReoptimise)
888
864
  ->set("dither", baton->gifDither)));
889
865
  baton->bufferOut = static_cast<char*>(area->data);
890
866
  baton->bufferOutLength = area->length;
@@ -937,6 +913,19 @@ class PipelineWorker : public Napi::AsyncWorker {
937
913
  area->free_fn = nullptr;
938
914
  vips_area_unref(area);
939
915
  baton->formatOut = "heif";
916
+ } else if (baton->formatOut == "dz") {
917
+ // Write DZ to buffer
918
+ baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP;
919
+ if (!sharp::HasAlpha(image)) {
920
+ baton->tileBackground.pop_back();
921
+ }
922
+ vips::VOption *options = BuildOptionsDZ(baton);
923
+ VipsArea *area = reinterpret_cast<VipsArea*>(image.dzsave_buffer(options));
924
+ baton->bufferOut = static_cast<char*>(area->data);
925
+ baton->bufferOutLength = area->length;
926
+ area->free_fn = nullptr;
927
+ vips_area_unref(area);
928
+ baton->formatOut = "dz";
940
929
  } else if (baton->formatOut == "raw" ||
941
930
  (baton->formatOut == "input" && inputImageType == sharp::ImageType::RAW)) {
942
931
  // Write raw, uncompressed image data to buffer
@@ -1038,6 +1027,8 @@ class PipelineWorker : public Napi::AsyncWorker {
1038
1027
  ->set("near_lossless", baton->webpNearLossless)
1039
1028
  ->set("smart_subsample", baton->webpSmartSubsample)
1040
1029
  ->set("effort", baton->webpEffort)
1030
+ ->set("min_size", baton->webpMinSize)
1031
+ ->set("mixed", baton->webpMixed)
1041
1032
  ->set("alpha_q", baton->webpAlphaQuality));
1042
1033
  baton->formatOut = "webp";
1043
1034
  } else if (baton->formatOut == "gif" || (mightMatchInput && isGif) ||
@@ -1048,6 +1039,7 @@ class PipelineWorker : public Napi::AsyncWorker {
1048
1039
  ->set("strip", !baton->withMetadata)
1049
1040
  ->set("bitdepth", baton->gifBitdepth)
1050
1041
  ->set("effort", baton->gifEffort)
1042
+ ->set("reoptimise", baton->gifReoptimise)
1051
1043
  ->set("dither", baton->gifDither));
1052
1044
  baton->formatOut = "gif";
1053
1045
  } else if (baton->formatOut == "tiff" || (mightMatchInput && isTiff) ||
@@ -1089,64 +1081,14 @@ class PipelineWorker : public Napi::AsyncWorker {
1089
1081
  ->set("lossless", baton->heifLossless));
1090
1082
  baton->formatOut = "heif";
1091
1083
  } else if (baton->formatOut == "dz" || isDz || isDzZip) {
1084
+ // Write DZ to file
1092
1085
  if (isDzZip) {
1093
1086
  baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP;
1094
1087
  }
1095
- // Forward format options through suffix
1096
- std::string suffix;
1097
- if (baton->tileFormat == "png") {
1098
- std::vector<std::pair<std::string, std::string>> options {
1099
- {"interlace", baton->pngProgressive ? "TRUE" : "FALSE"},
1100
- {"compression", std::to_string(baton->pngCompressionLevel)},
1101
- {"filter", baton->pngAdaptiveFiltering ? "all" : "none"}
1102
- };
1103
- suffix = AssembleSuffixString(".png", options);
1104
- } else if (baton->tileFormat == "webp") {
1105
- std::vector<std::pair<std::string, std::string>> options {
1106
- {"Q", std::to_string(baton->webpQuality)},
1107
- {"alpha_q", std::to_string(baton->webpAlphaQuality)},
1108
- {"lossless", baton->webpLossless ? "TRUE" : "FALSE"},
1109
- {"near_lossless", baton->webpNearLossless ? "TRUE" : "FALSE"},
1110
- {"smart_subsample", baton->webpSmartSubsample ? "TRUE" : "FALSE"},
1111
- {"effort", std::to_string(baton->webpEffort)}
1112
- };
1113
- suffix = AssembleSuffixString(".webp", options);
1114
- } else {
1115
- std::vector<std::pair<std::string, std::string>> options {
1116
- {"Q", std::to_string(baton->jpegQuality)},
1117
- {"interlace", baton->jpegProgressive ? "TRUE" : "FALSE"},
1118
- {"subsample_mode", baton->jpegChromaSubsampling == "4:4:4" ? "off" : "on"},
1119
- {"trellis_quant", baton->jpegTrellisQuantisation ? "TRUE" : "FALSE"},
1120
- {"quant_table", std::to_string(baton->jpegQuantisationTable)},
1121
- {"overshoot_deringing", baton->jpegOvershootDeringing ? "TRUE": "FALSE"},
1122
- {"optimize_scans", baton->jpegOptimiseScans ? "TRUE": "FALSE"},
1123
- {"optimize_coding", baton->jpegOptimiseCoding ? "TRUE": "FALSE"}
1124
- };
1125
- std::string extname = baton->tileLayout == VIPS_FOREIGN_DZ_LAYOUT_DZ ? ".jpeg" : ".jpg";
1126
- suffix = AssembleSuffixString(extname, options);
1127
- }
1128
- // Remove alpha channel from tile background if image does not contain an alpha channel
1129
1088
  if (!sharp::HasAlpha(image)) {
1130
1089
  baton->tileBackground.pop_back();
1131
1090
  }
1132
- // Write DZ to file
1133
- vips::VOption *options = VImage::option()
1134
- ->set("strip", !baton->withMetadata)
1135
- ->set("tile_size", baton->tileSize)
1136
- ->set("overlap", baton->tileOverlap)
1137
- ->set("container", baton->tileContainer)
1138
- ->set("layout", baton->tileLayout)
1139
- ->set("suffix", const_cast<char*>(suffix.data()))
1140
- ->set("angle", CalculateAngleRotation(baton->tileAngle))
1141
- ->set("background", baton->tileBackground)
1142
- ->set("centre", baton->tileCentre)
1143
- ->set("id", const_cast<char*>(baton->tileId.data()))
1144
- ->set("skip_blanks", baton->tileSkipBlanks);
1145
- // libvips chooses a default depth based on layout. Instead of replicating that logic here by
1146
- // not passing anything - libvips will handle choice
1147
- if (baton->tileDepth < VIPS_FOREIGN_DZ_DEPTH_LAST) {
1148
- options->set("depth", baton->tileDepth);
1149
- }
1091
+ vips::VOption *options = BuildOptionsDZ(baton);
1150
1092
  image.dzsave(const_cast<char*>(baton->fileOut.data()), options);
1151
1093
  baton->formatOut = "dz";
1152
1094
  } else if (baton->formatOut == "v" || (mightMatchInput && isV) ||
@@ -1215,6 +1157,10 @@ class PipelineWorker : public Napi::AsyncWorker {
1215
1157
  info.Set("trimOffsetTop", static_cast<int32_t>(baton->trimOffsetTop));
1216
1158
  }
1217
1159
 
1160
+ if (baton->input->textAutofitDpi) {
1161
+ info.Set("textAutofitDpi", static_cast<uint32_t>(baton->input->textAutofitDpi));
1162
+ }
1163
+
1218
1164
  if (baton->bufferOutLength > 0) {
1219
1165
  // Add buffer size to info
1220
1166
  info.Set("size", static_cast<uint32_t>(baton->bufferOutLength));
@@ -1303,7 +1249,7 @@ class PipelineWorker : public Napi::AsyncWorker {
1303
1249
 
1304
1250
  /*
1305
1251
  Assemble the suffix argument to dzsave, which is the format (by extname)
1306
- alongisde comma-separated arguments to the corresponding `formatsave` vips
1252
+ alongside comma-separated arguments to the corresponding `formatsave` vips
1307
1253
  action.
1308
1254
  */
1309
1255
  std::string
@@ -1318,6 +1264,67 @@ class PipelineWorker : public Napi::AsyncWorker {
1318
1264
  return extname + "[" + argument + "]";
1319
1265
  }
1320
1266
 
1267
+ /*
1268
+ Build VOption for dzsave
1269
+ */
1270
+ vips::VOption*
1271
+ BuildOptionsDZ(PipelineBaton *baton) {
1272
+ // Forward format options through suffix
1273
+ std::string suffix;
1274
+ if (baton->tileFormat == "png") {
1275
+ std::vector<std::pair<std::string, std::string>> options {
1276
+ {"interlace", baton->pngProgressive ? "TRUE" : "FALSE"},
1277
+ {"compression", std::to_string(baton->pngCompressionLevel)},
1278
+ {"filter", baton->pngAdaptiveFiltering ? "all" : "none"}
1279
+ };
1280
+ suffix = AssembleSuffixString(".png", options);
1281
+ } else if (baton->tileFormat == "webp") {
1282
+ std::vector<std::pair<std::string, std::string>> options {
1283
+ {"Q", std::to_string(baton->webpQuality)},
1284
+ {"alpha_q", std::to_string(baton->webpAlphaQuality)},
1285
+ {"lossless", baton->webpLossless ? "TRUE" : "FALSE"},
1286
+ {"near_lossless", baton->webpNearLossless ? "TRUE" : "FALSE"},
1287
+ {"smart_subsample", baton->webpSmartSubsample ? "TRUE" : "FALSE"},
1288
+ {"min_size", baton->webpMinSize ? "TRUE" : "FALSE"},
1289
+ {"mixed", baton->webpMixed ? "TRUE" : "FALSE"},
1290
+ {"effort", std::to_string(baton->webpEffort)}
1291
+ };
1292
+ suffix = AssembleSuffixString(".webp", options);
1293
+ } else {
1294
+ std::vector<std::pair<std::string, std::string>> options {
1295
+ {"Q", std::to_string(baton->jpegQuality)},
1296
+ {"interlace", baton->jpegProgressive ? "TRUE" : "FALSE"},
1297
+ {"subsample_mode", baton->jpegChromaSubsampling == "4:4:4" ? "off" : "on"},
1298
+ {"trellis_quant", baton->jpegTrellisQuantisation ? "TRUE" : "FALSE"},
1299
+ {"quant_table", std::to_string(baton->jpegQuantisationTable)},
1300
+ {"overshoot_deringing", baton->jpegOvershootDeringing ? "TRUE": "FALSE"},
1301
+ {"optimize_scans", baton->jpegOptimiseScans ? "TRUE": "FALSE"},
1302
+ {"optimize_coding", baton->jpegOptimiseCoding ? "TRUE": "FALSE"}
1303
+ };
1304
+ std::string extname = baton->tileLayout == VIPS_FOREIGN_DZ_LAYOUT_DZ ? ".jpeg" : ".jpg";
1305
+ suffix = AssembleSuffixString(extname, options);
1306
+ }
1307
+ vips::VOption *options = VImage::option()
1308
+ ->set("strip", !baton->withMetadata)
1309
+ ->set("tile_size", baton->tileSize)
1310
+ ->set("overlap", baton->tileOverlap)
1311
+ ->set("container", baton->tileContainer)
1312
+ ->set("layout", baton->tileLayout)
1313
+ ->set("suffix", const_cast<char*>(suffix.data()))
1314
+ ->set("angle", CalculateAngleRotation(baton->tileAngle))
1315
+ ->set("background", baton->tileBackground)
1316
+ ->set("centre", baton->tileCentre)
1317
+ ->set("id", const_cast<char*>(baton->tileId.data()))
1318
+ ->set("skip_blanks", baton->tileSkipBlanks);
1319
+ if (baton->tileDepth < VIPS_FOREIGN_DZ_DEPTH_LAST) {
1320
+ options->set("depth", baton->tileDepth);
1321
+ }
1322
+ if (!baton->tileBasename.empty()) {
1323
+ options->set("basename", const_cast<char*>(baton->tileBasename.data()));
1324
+ }
1325
+ return options;
1326
+ }
1327
+
1321
1328
  /*
1322
1329
  Clear all thread-local data.
1323
1330
  */
@@ -1363,17 +1370,13 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1363
1370
  } else if (canvas == "ignore_aspect") {
1364
1371
  baton->canvas = sharp::Canvas::IGNORE_ASPECT;
1365
1372
  }
1366
- // Tint chroma
1367
- baton->tintA = sharp::AttrAsDouble(options, "tintA");
1368
- baton->tintB = sharp::AttrAsDouble(options, "tintB");
1369
1373
  // Composite
1370
1374
  Napi::Array compositeArray = options.Get("composite").As<Napi::Array>();
1371
1375
  for (unsigned int i = 0; i < compositeArray.Length(); i++) {
1372
1376
  Napi::Object compositeObject = compositeArray.Get(i).As<Napi::Object>();
1373
1377
  Composite *composite = new Composite;
1374
1378
  composite->input = sharp::CreateInputDescriptor(compositeObject.Get("input").As<Napi::Object>());
1375
- composite->mode = static_cast<VipsBlendMode>(
1376
- vips_enum_from_nick(nullptr, VIPS_TYPE_BLEND_MODE, sharp::AttrAsStr(compositeObject, "blend").data()));
1379
+ composite->mode = sharp::AttrAsEnum<VipsBlendMode>(compositeObject, "blend", VIPS_TYPE_BLEND_MODE);
1377
1380
  composite->gravity = sharp::AttrAsUint32(compositeObject, "gravity");
1378
1381
  composite->left = sharp::AttrAsInt32(compositeObject, "left");
1379
1382
  composite->top = sharp::AttrAsInt32(compositeObject, "top");
@@ -1387,7 +1390,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1387
1390
  baton->withoutReduction = sharp::AttrAsBool(options, "withoutReduction");
1388
1391
  baton->position = sharp::AttrAsInt32(options, "position");
1389
1392
  baton->resizeBackground = sharp::AttrAsVectorOfDouble(options, "resizeBackground");
1390
- baton->kernel = sharp::AttrAsStr(options, "kernel");
1393
+ baton->kernel = sharp::AttrAsEnum<VipsKernel>(options, "kernel", VIPS_TYPE_KERNEL);
1391
1394
  baton->fastShrinkOnLoad = sharp::AttrAsBool(options, "fastShrinkOnLoad");
1392
1395
  // Join Channel Options
1393
1396
  if (options.Has("joinChannelIn")) {
@@ -1416,13 +1419,16 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1416
1419
  baton->sharpenY3 = sharp::AttrAsDouble(options, "sharpenY3");
1417
1420
  baton->threshold = sharp::AttrAsInt32(options, "threshold");
1418
1421
  baton->thresholdGrayscale = sharp::AttrAsBool(options, "thresholdGrayscale");
1422
+ baton->trimBackground = sharp::AttrAsVectorOfDouble(options, "trimBackground");
1419
1423
  baton->trimThreshold = sharp::AttrAsDouble(options, "trimThreshold");
1420
1424
  baton->gamma = sharp::AttrAsDouble(options, "gamma");
1421
1425
  baton->gammaOut = sharp::AttrAsDouble(options, "gammaOut");
1422
- baton->linearA = sharp::AttrAsDouble(options, "linearA");
1423
- baton->linearB = sharp::AttrAsDouble(options, "linearB");
1426
+ baton->linearA = sharp::AttrAsVectorOfDouble(options, "linearA");
1427
+ baton->linearB = sharp::AttrAsVectorOfDouble(options, "linearB");
1424
1428
  baton->greyscale = sharp::AttrAsBool(options, "greyscale");
1425
1429
  baton->normalise = sharp::AttrAsBool(options, "normalise");
1430
+ baton->tintA = sharp::AttrAsDouble(options, "tintA");
1431
+ baton->tintB = sharp::AttrAsDouble(options, "tintB");
1426
1432
  baton->claheWidth = sharp::AttrAsUint32(options, "claheWidth");
1427
1433
  baton->claheHeight = sharp::AttrAsUint32(options, "claheHeight");
1428
1434
  baton->claheMaxSlope = sharp::AttrAsUint32(options, "claheMaxSlope");
@@ -1450,10 +1456,10 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1450
1456
  baton->ensureAlpha = sharp::AttrAsDouble(options, "ensureAlpha");
1451
1457
  if (options.Has("boolean")) {
1452
1458
  baton->boolean = sharp::CreateInputDescriptor(options.Get("boolean").As<Napi::Object>());
1453
- baton->booleanOp = sharp::GetBooleanOperation(sharp::AttrAsStr(options, "booleanOp"));
1459
+ baton->booleanOp = sharp::AttrAsEnum<VipsOperationBoolean>(options, "booleanOp", VIPS_TYPE_OPERATION_BOOLEAN);
1454
1460
  }
1455
1461
  if (options.Has("bandBoolOp")) {
1456
- baton->bandBoolOp = sharp::GetBooleanOperation(sharp::AttrAsStr(options, "bandBoolOp"));
1462
+ baton->bandBoolOp = sharp::AttrAsEnum<VipsOperationBoolean>(options, "bandBoolOp", VIPS_TYPE_OPERATION_BOOLEAN);
1457
1463
  }
1458
1464
  if (options.Has("convKernel")) {
1459
1465
  Napi::Object kernel = options.Get("convKernel").As<Napi::Object>();
@@ -1475,11 +1481,12 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1475
1481
  baton->recombMatrix[i] = sharp::AttrAsDouble(recombMatrix, i);
1476
1482
  }
1477
1483
  }
1478
- baton->colourspaceInput = sharp::GetInterpretation(sharp::AttrAsStr(options, "colourspaceInput"));
1484
+ baton->colourspaceInput = sharp::AttrAsEnum<VipsInterpretation>(
1485
+ options, "colourspaceInput", VIPS_TYPE_INTERPRETATION);
1479
1486
  if (baton->colourspaceInput == VIPS_INTERPRETATION_ERROR) {
1480
1487
  baton->colourspaceInput = VIPS_INTERPRETATION_LAST;
1481
1488
  }
1482
- baton->colourspace = sharp::GetInterpretation(sharp::AttrAsStr(options, "colourspace"));
1489
+ baton->colourspace = sharp::AttrAsEnum<VipsInterpretation>(options, "colourspace", VIPS_TYPE_INTERPRETATION);
1483
1490
  if (baton->colourspace == VIPS_INTERPRETATION_ERROR) {
1484
1491
  baton->colourspace = VIPS_INTERPRETATION_sRGB;
1485
1492
  }
@@ -1494,7 +1501,9 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1494
1501
  Napi::Array mdStrKeys = mdStrs.GetPropertyNames();
1495
1502
  for (unsigned int i = 0; i < mdStrKeys.Length(); i++) {
1496
1503
  std::string k = sharp::AttrAsStr(mdStrKeys, i);
1497
- baton->withMetadataStrs.insert(std::make_pair(k, sharp::AttrAsStr(mdStrs, k)));
1504
+ if (mdStrs.HasOwnProperty(k)) {
1505
+ baton->withMetadataStrs.insert(std::make_pair(k, sharp::AttrAsStr(mdStrs, k)));
1506
+ }
1498
1507
  }
1499
1508
  baton->timeoutSeconds = sharp::AttrAsUint32(options, "timeoutSeconds");
1500
1509
  // Format-specific
@@ -1525,9 +1534,12 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1525
1534
  baton->webpNearLossless = sharp::AttrAsBool(options, "webpNearLossless");
1526
1535
  baton->webpSmartSubsample = sharp::AttrAsBool(options, "webpSmartSubsample");
1527
1536
  baton->webpEffort = sharp::AttrAsUint32(options, "webpEffort");
1537
+ baton->webpMinSize = sharp::AttrAsBool(options, "webpMinSize");
1538
+ baton->webpMixed = sharp::AttrAsBool(options, "webpMixed");
1528
1539
  baton->gifBitdepth = sharp::AttrAsUint32(options, "gifBitdepth");
1529
1540
  baton->gifEffort = sharp::AttrAsUint32(options, "gifEffort");
1530
1541
  baton->gifDither = sharp::AttrAsDouble(options, "gifDither");
1542
+ baton->gifReoptimise = sharp::AttrAsBool(options, "gifReoptimise");
1531
1543
  baton->tiffQuality = sharp::AttrAsUint32(options, "tiffQuality");
1532
1544
  baton->tiffPyramid = sharp::AttrAsBool(options, "tiffPyramid");
1533
1545
  baton->tiffBitdepth = sharp::AttrAsUint32(options, "tiffBitdepth");
@@ -1539,28 +1551,19 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1539
1551
  if (baton->tiffXres == 1.0 && baton->tiffYres == 1.0 && baton->withMetadataDensity > 0) {
1540
1552
  baton->tiffXres = baton->tiffYres = baton->withMetadataDensity / 25.4;
1541
1553
  }
1542
- // tiff compression options
1543
- baton->tiffCompression = static_cast<VipsForeignTiffCompression>(
1544
- vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_COMPRESSION,
1545
- sharp::AttrAsStr(options, "tiffCompression").data()));
1546
- baton->tiffPredictor = static_cast<VipsForeignTiffPredictor>(
1547
- vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_PREDICTOR,
1548
- sharp::AttrAsStr(options, "tiffPredictor").data()));
1549
- baton->tiffResolutionUnit = static_cast<VipsForeignTiffResunit>(
1550
- vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_RESUNIT,
1551
- sharp::AttrAsStr(options, "tiffResolutionUnit").data()));
1552
-
1554
+ baton->tiffCompression = sharp::AttrAsEnum<VipsForeignTiffCompression>(
1555
+ options, "tiffCompression", VIPS_TYPE_FOREIGN_TIFF_COMPRESSION);
1556
+ baton->tiffPredictor = sharp::AttrAsEnum<VipsForeignTiffPredictor>(
1557
+ options, "tiffPredictor", VIPS_TYPE_FOREIGN_TIFF_PREDICTOR);
1558
+ baton->tiffResolutionUnit = sharp::AttrAsEnum<VipsForeignTiffResunit>(
1559
+ options, "tiffResolutionUnit", VIPS_TYPE_FOREIGN_TIFF_RESUNIT);
1553
1560
  baton->heifQuality = sharp::AttrAsUint32(options, "heifQuality");
1554
1561
  baton->heifLossless = sharp::AttrAsBool(options, "heifLossless");
1555
- baton->heifCompression = static_cast<VipsForeignHeifCompression>(
1556
- vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_HEIF_COMPRESSION,
1557
- sharp::AttrAsStr(options, "heifCompression").data()));
1562
+ baton->heifCompression = sharp::AttrAsEnum<VipsForeignHeifCompression>(
1563
+ options, "heifCompression", VIPS_TYPE_FOREIGN_HEIF_COMPRESSION);
1558
1564
  baton->heifEffort = sharp::AttrAsUint32(options, "heifEffort");
1559
1565
  baton->heifChromaSubsampling = sharp::AttrAsStr(options, "heifChromaSubsampling");
1560
- // Raw output
1561
- baton->rawDepth = static_cast<VipsBandFormat>(
1562
- vips_enum_from_nick(nullptr, VIPS_TYPE_BAND_FORMAT,
1563
- sharp::AttrAsStr(options, "rawDepth").data()));
1566
+ baton->rawDepth = sharp::AttrAsEnum<VipsBandFormat>(options, "rawDepth", VIPS_TYPE_BAND_FORMAT);
1564
1567
  // Animated output properties
1565
1568
  if (sharp::HasAttr(options, "loop")) {
1566
1569
  baton->loop = sharp::AttrAsUint32(options, "loop");
@@ -1568,24 +1571,19 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1568
1571
  if (sharp::HasAttr(options, "delay")) {
1569
1572
  baton->delay = sharp::AttrAsInt32Vector(options, "delay");
1570
1573
  }
1571
- // Tile output
1572
1574
  baton->tileSize = sharp::AttrAsUint32(options, "tileSize");
1573
1575
  baton->tileOverlap = sharp::AttrAsUint32(options, "tileOverlap");
1574
1576
  baton->tileAngle = sharp::AttrAsInt32(options, "tileAngle");
1575
1577
  baton->tileBackground = sharp::AttrAsVectorOfDouble(options, "tileBackground");
1576
1578
  baton->tileSkipBlanks = sharp::AttrAsInt32(options, "tileSkipBlanks");
1577
- baton->tileContainer = static_cast<VipsForeignDzContainer>(
1578
- vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_DZ_CONTAINER,
1579
- sharp::AttrAsStr(options, "tileContainer").data()));
1580
- baton->tileLayout = static_cast<VipsForeignDzLayout>(
1581
- vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_DZ_LAYOUT,
1582
- sharp::AttrAsStr(options, "tileLayout").data()));
1579
+ baton->tileContainer = sharp::AttrAsEnum<VipsForeignDzContainer>(
1580
+ options, "tileContainer", VIPS_TYPE_FOREIGN_DZ_CONTAINER);
1581
+ baton->tileLayout = sharp::AttrAsEnum<VipsForeignDzLayout>(options, "tileLayout", VIPS_TYPE_FOREIGN_DZ_LAYOUT);
1583
1582
  baton->tileFormat = sharp::AttrAsStr(options, "tileFormat");
1584
- baton->tileDepth = static_cast<VipsForeignDzDepth>(
1585
- vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_DZ_DEPTH,
1586
- sharp::AttrAsStr(options, "tileDepth").data()));
1583
+ baton->tileDepth = sharp::AttrAsEnum<VipsForeignDzDepth>(options, "tileDepth", VIPS_TYPE_FOREIGN_DZ_DEPTH);
1587
1584
  baton->tileCentre = sharp::AttrAsBool(options, "tileCentre");
1588
1585
  baton->tileId = sharp::AttrAsStr(options, "tileId");
1586
+ baton->tileBasename = sharp::AttrAsStr(options, "tileBasename");
1589
1587
 
1590
1588
  // Force random access for certain operations
1591
1589
  if (baton->input->access == VIPS_ACCESS_SEQUENTIAL) {