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/common.cc CHANGED
@@ -48,6 +48,9 @@ namespace sharp {
48
48
  int32_t AttrAsInt32(Napi::Object obj, unsigned int const attr) {
49
49
  return obj.Get(attr).As<Napi::Number>().Int32Value();
50
50
  }
51
+ int64_t AttrAsInt64(Napi::Object obj, std::string attr) {
52
+ return obj.Get(attr).As<Napi::Number>().Int64Value();
53
+ }
51
54
  double AttrAsDouble(Napi::Object obj, std::string attr) {
52
55
  return obj.Get(attr).As<Napi::Number>().DoubleValue();
53
56
  }
@@ -85,18 +88,14 @@ namespace sharp {
85
88
  descriptor->buffer = buffer.Data();
86
89
  descriptor->isBuffer = TRUE;
87
90
  }
88
- descriptor->failOn = static_cast<VipsFailOn>(
89
- vips_enum_from_nick(nullptr, VIPS_TYPE_FAIL_ON,
90
- AttrAsStr(input, "failOn").data()));
91
+ descriptor->failOn = AttrAsEnum<VipsFailOn>(input, "failOn", VIPS_TYPE_FAIL_ON);
91
92
  // Density for vector-based input
92
93
  if (HasAttr(input, "density")) {
93
94
  descriptor->density = AttrAsDouble(input, "density");
94
95
  }
95
96
  // Raw pixel input
96
97
  if (HasAttr(input, "rawChannels")) {
97
- descriptor->rawDepth = static_cast<VipsBandFormat>(
98
- vips_enum_from_nick(nullptr, VIPS_TYPE_BAND_FORMAT,
99
- AttrAsStr(input, "rawDepth").data()));
98
+ descriptor->rawDepth = AttrAsEnum<VipsBandFormat>(input, "rawDepth", VIPS_TYPE_BAND_FORMAT);
100
99
  descriptor->rawChannels = AttrAsUint32(input, "rawChannels");
101
100
  descriptor->rawWidth = AttrAsUint32(input, "rawWidth");
102
101
  descriptor->rawHeight = AttrAsUint32(input, "rawHeight");
@@ -130,11 +129,42 @@ namespace sharp {
130
129
  descriptor->createBackground = AttrAsVectorOfDouble(input, "createBackground");
131
130
  }
132
131
  }
132
+ // Create new image with text
133
+ if (HasAttr(input, "textValue")) {
134
+ descriptor->textValue = AttrAsStr(input, "textValue");
135
+ if (HasAttr(input, "textFont")) {
136
+ descriptor->textFont = AttrAsStr(input, "textFont");
137
+ }
138
+ if (HasAttr(input, "textFontfile")) {
139
+ descriptor->textFontfile = AttrAsStr(input, "textFontfile");
140
+ }
141
+ if (HasAttr(input, "textWidth")) {
142
+ descriptor->textWidth = AttrAsUint32(input, "textWidth");
143
+ }
144
+ if (HasAttr(input, "textHeight")) {
145
+ descriptor->textHeight = AttrAsUint32(input, "textHeight");
146
+ }
147
+ if (HasAttr(input, "textAlign")) {
148
+ descriptor->textAlign = AttrAsEnum<VipsAlign>(input, "textAlign", VIPS_TYPE_ALIGN);
149
+ }
150
+ if (HasAttr(input, "textJustify")) {
151
+ descriptor->textJustify = AttrAsBool(input, "textJustify");
152
+ }
153
+ if (HasAttr(input, "textDpi")) {
154
+ descriptor->textDpi = AttrAsUint32(input, "textDpi");
155
+ }
156
+ if (HasAttr(input, "textRgba")) {
157
+ descriptor->textRgba = AttrAsBool(input, "textRgba");
158
+ }
159
+ if (HasAttr(input, "textSpacing")) {
160
+ descriptor->textSpacing = AttrAsUint32(input, "textSpacing");
161
+ }
162
+ }
133
163
  // Limit input images to a given number of pixels, where pixels = width * height
134
- descriptor->limitInputPixels = AttrAsUint32(input, "limitInputPixels");
164
+ descriptor->limitInputPixels = static_cast<uint64_t>(AttrAsInt64(input, "limitInputPixels"));
135
165
  // Allow switch from random to sequential access
136
166
  descriptor->access = AttrAsBool(input, "sequentialRead") ? VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_RANDOM;
137
- // Remove safety features and allow unlimited SVG/PNG input
167
+ // Remove safety features and allow unlimited input
138
168
  descriptor->unlimited = AttrAsBool(input, "unlimited");
139
169
  return descriptor;
140
170
  }
@@ -247,9 +277,9 @@ namespace sharp {
247
277
  { "VipsForeignLoadMagickBuffer", ImageType::MAGICK },
248
278
  { "VipsForeignLoadMagick7File", ImageType::MAGICK },
249
279
  { "VipsForeignLoadMagick7Buffer", ImageType::MAGICK },
250
- { "VipsForeignLoadOpenslide", ImageType::OPENSLIDE },
280
+ { "VipsForeignLoadOpenslideFile", ImageType::OPENSLIDE },
251
281
  { "VipsForeignLoadPpmFile", ImageType::PPM },
252
- { "VipsForeignLoadFits", ImageType::FITS },
282
+ { "VipsForeignLoadFitsFile", ImageType::FITS },
253
283
  { "VipsForeignLoadOpenexr", ImageType::EXR },
254
284
  { "VipsForeignLoadVips", ImageType::VIPS },
255
285
  { "VipsForeignLoadVipsFile", ImageType::VIPS },
@@ -304,6 +334,17 @@ namespace sharp {
304
334
  imageType == ImageType::PDF;
305
335
  }
306
336
 
337
+ /*
338
+ Does this image type support removal of safety limits?
339
+ */
340
+ bool ImageTypeSupportsUnlimited(ImageType imageType) {
341
+ return
342
+ imageType == ImageType::JPEG ||
343
+ imageType == ImageType::PNG ||
344
+ imageType == ImageType::SVG ||
345
+ imageType == ImageType::HEIF;
346
+ }
347
+
307
348
  /*
308
349
  Open an image from the given InputDescriptor (filesystem, compressed buffer, raw pixel data)
309
350
  */
@@ -332,7 +373,7 @@ namespace sharp {
332
373
  vips::VOption *option = VImage::option()
333
374
  ->set("access", descriptor->access)
334
375
  ->set("fail_on", descriptor->failOn);
335
- if (descriptor->unlimited && (imageType == ImageType::SVG || imageType == ImageType::PNG)) {
376
+ if (descriptor->unlimited && ImageTypeSupportsUnlimited(imageType)) {
336
377
  option->set("unlimited", TRUE);
337
378
  }
338
379
  if (imageType == ImageType::SVG || imageType == ImageType::PDF) {
@@ -363,34 +404,63 @@ namespace sharp {
363
404
  }
364
405
  }
365
406
  } else {
366
- if (descriptor->createChannels > 0) {
407
+ int const channels = descriptor->createChannels;
408
+ if (channels > 0) {
367
409
  // Create new image
368
410
  if (descriptor->createNoiseType == "gaussian") {
369
- int const channels = descriptor->createChannels;
370
- image = VImage::new_matrix(descriptor->createWidth, descriptor->createHeight);
371
411
  std::vector<VImage> bands = {};
372
412
  bands.reserve(channels);
373
413
  for (int _band = 0; _band < channels; _band++) {
374
- bands.push_back(image.gaussnoise(
375
- descriptor->createWidth,
376
- descriptor->createHeight,
377
- VImage::option()->set("mean", descriptor->createNoiseMean)->set("sigma", descriptor->createNoiseSigma)));
414
+ bands.push_back(VImage::gaussnoise(descriptor->createWidth, descriptor->createHeight, VImage::option()
415
+ ->set("mean", descriptor->createNoiseMean)
416
+ ->set("sigma", descriptor->createNoiseSigma)));
378
417
  }
379
- image = image.bandjoin(bands);
418
+ image = VImage::bandjoin(bands).copy(VImage::option()->set("interpretation",
419
+ channels < 3 ? VIPS_INTERPRETATION_B_W: VIPS_INTERPRETATION_sRGB));
380
420
  } else {
381
421
  std::vector<double> background = {
382
422
  descriptor->createBackground[0],
383
423
  descriptor->createBackground[1],
384
424
  descriptor->createBackground[2]
385
425
  };
386
- if (descriptor->createChannels == 4) {
426
+ if (channels == 4) {
387
427
  background.push_back(descriptor->createBackground[3]);
388
428
  }
389
- image = VImage::new_matrix(descriptor->createWidth, descriptor->createHeight).new_from_image(background);
429
+ image = VImage::new_matrix(descriptor->createWidth, descriptor->createHeight)
430
+ .copy(VImage::option()->set("interpretation",
431
+ channels < 3 ? VIPS_INTERPRETATION_B_W : VIPS_INTERPRETATION_sRGB))
432
+ .new_from_image(background);
390
433
  }
391
- image.get_image()->Type = image.guess_interpretation();
392
434
  image = image.cast(VIPS_FORMAT_UCHAR);
393
435
  imageType = ImageType::RAW;
436
+ } else if (descriptor->textValue.length() > 0) {
437
+ // Create a new image with text
438
+ vips::VOption *textOptions = VImage::option()
439
+ ->set("align", descriptor->textAlign)
440
+ ->set("justify", descriptor->textJustify)
441
+ ->set("rgba", descriptor->textRgba)
442
+ ->set("spacing", descriptor->textSpacing)
443
+ ->set("autofit_dpi", &descriptor->textAutofitDpi);
444
+ if (descriptor->textWidth > 0) {
445
+ textOptions->set("width", descriptor->textWidth);
446
+ }
447
+ // Ignore dpi if height is set
448
+ if (descriptor->textWidth > 0 && descriptor->textHeight > 0) {
449
+ textOptions->set("height", descriptor->textHeight);
450
+ } else if (descriptor->textDpi > 0) {
451
+ textOptions->set("dpi", descriptor->textDpi);
452
+ }
453
+ if (descriptor->textFont.length() > 0) {
454
+ textOptions->set("font", const_cast<char*>(descriptor->textFont.data()));
455
+ }
456
+ if (descriptor->textFontfile.length() > 0) {
457
+ textOptions->set("fontfile", const_cast<char*>(descriptor->textFontfile.data()));
458
+ }
459
+ image = VImage::text(const_cast<char *>(descriptor->textValue.data()), textOptions);
460
+ if (!descriptor->textRgba) {
461
+ image = image.copy(VImage::option()->set("interpretation", VIPS_INTERPRETATION_B_W));
462
+ }
463
+ imageType = ImageType::RAW;
394
464
  } else {
395
465
  // From filesystem
396
466
  imageType = DetermineImageType(descriptor->file.data());
@@ -406,7 +476,7 @@ namespace sharp {
406
476
  vips::VOption *option = VImage::option()
407
477
  ->set("access", descriptor->access)
408
478
  ->set("fail_on", descriptor->failOn);
409
- if (descriptor->unlimited && (imageType == ImageType::SVG || imageType == ImageType::PNG)) {
479
+ if (descriptor->unlimited && ImageTypeSupportsUnlimited(imageType)) {
410
480
  option->set("unlimited", TRUE);
411
481
  }
412
482
  if (imageType == ImageType::SVG || imageType == ImageType::PDF) {
@@ -439,7 +509,7 @@ namespace sharp {
439
509
  }
440
510
  // Limit input images to a given number of pixels, where pixels = width * height
441
511
  if (descriptor->limitInputPixels > 0 &&
442
- static_cast<uint64_t>(image.width() * image.height()) > static_cast<uint64_t>(descriptor->limitInputPixels)) {
512
+ static_cast<uint64_t>(image.width() * image.height()) > descriptor->limitInputPixels) {
443
513
  throw vips::VError("Input image exceeds pixel limit");
444
514
  }
445
515
  return std::make_tuple(image, imageType);
@@ -634,7 +704,6 @@ namespace sharp {
634
704
  Event listener for progress updates, used to detect timeout
635
705
  */
636
706
  void VipsProgressCallBack(VipsImage *im, VipsProgress *progress, int *timeout) {
637
- // printf("VipsProgressCallBack progress=%d run=%d timeout=%d\n", progress->percent, progress->run, *timeout);
638
707
  if (*timeout > 0 && progress->run >= *timeout) {
639
708
  vips_image_set_kill(im, TRUE);
640
709
  vips_error("timeout", "%d%% complete", progress->percent);
@@ -791,22 +860,6 @@ namespace sharp {
791
860
  return Is16Bit(interpretation) ? 65535.0 : 255.0;
792
861
  }
793
862
 
794
- /*
795
- Get boolean operation type from string
796
- */
797
- VipsOperationBoolean GetBooleanOperation(std::string const opStr) {
798
- return static_cast<VipsOperationBoolean>(
799
- vips_enum_from_nick(nullptr, VIPS_TYPE_OPERATION_BOOLEAN, opStr.data()));
800
- }
801
-
802
- /*
803
- Get interpretation type from string
804
- */
805
- VipsInterpretation GetInterpretation(std::string const typeStr) {
806
- return static_cast<VipsInterpretation>(
807
- vips_enum_from_nick(nullptr, VIPS_TYPE_INTERPRETATION, typeStr.data()));
808
- }
809
-
810
863
  /*
811
864
  Convert RGBA value to another colourspace
812
865
  */
@@ -887,7 +940,7 @@ namespace sharp {
887
940
 
888
941
  std::pair<double, double> ResolveShrink(int width, int height, int targetWidth, int targetHeight,
889
942
  Canvas canvas, bool swap, bool withoutEnlargement, bool withoutReduction) {
890
- if (swap) {
943
+ if (swap && canvas != Canvas::IGNORE_ASPECT) {
891
944
  // Swap input width and height when requested.
892
945
  std::swap(width, height);
893
946
  }
@@ -918,9 +971,6 @@ namespace sharp {
918
971
  }
919
972
  break;
920
973
  case Canvas::IGNORE_ASPECT:
921
- if (swap) {
922
- std::swap(hshrink, vshrink);
923
- }
924
974
  break;
925
975
  }
926
976
  } else if (targetWidth > 0) {
package/src/common.h CHANGED
@@ -25,9 +25,9 @@
25
25
  // Verify platform and compiler compatibility
26
26
 
27
27
  #if (VIPS_MAJOR_VERSION < 8) || \
28
- (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 12) || \
29
- (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 12 && VIPS_MICRO_VERSION < 2)
30
- #error "libvips version 8.12.2+ is required - please see https://sharp.pixelplumbing.com/install"
28
+ (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 13) || \
29
+ (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 13 && VIPS_MICRO_VERSION < 1)
30
+ #error "libvips version 8.13.1+ is required - please see https://sharp.pixelplumbing.com/install"
31
31
  #endif
32
32
 
33
33
  #if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6)))
@@ -49,7 +49,7 @@ namespace sharp {
49
49
  std::string file;
50
50
  char *buffer;
51
51
  VipsFailOn failOn;
52
- int limitInputPixels;
52
+ uint64_t limitInputPixels;
53
53
  bool unlimited;
54
54
  VipsAccess access;
55
55
  size_t bufferLength;
@@ -71,6 +71,17 @@ namespace sharp {
71
71
  std::string createNoiseType;
72
72
  double createNoiseMean;
73
73
  double createNoiseSigma;
74
+ std::string textValue;
75
+ std::string textFont;
76
+ std::string textFontfile;
77
+ int textWidth;
78
+ int textHeight;
79
+ VipsAlign textAlign;
80
+ bool textJustify;
81
+ int textDpi;
82
+ bool textRgba;
83
+ int textSpacing;
84
+ int textAutofitDpi;
74
85
 
75
86
  InputDescriptor():
76
87
  buffer(nullptr),
@@ -95,7 +106,15 @@ namespace sharp {
95
106
  createHeight(0),
96
107
  createBackground{ 0.0, 0.0, 0.0, 255.0 },
97
108
  createNoiseMean(0.0),
98
- createNoiseSigma(0.0) {}
109
+ createNoiseSigma(0.0),
110
+ textWidth(0),
111
+ textHeight(0),
112
+ textAlign(VIPS_ALIGN_LOW),
113
+ textJustify(FALSE),
114
+ textDpi(72),
115
+ textRgba(FALSE),
116
+ textSpacing(0),
117
+ textAutofitDpi(0) {}
99
118
  };
100
119
 
101
120
  // Convenience methods to access the attributes of a Napi::Object
@@ -110,6 +129,10 @@ namespace sharp {
110
129
  bool AttrAsBool(Napi::Object obj, std::string attr);
111
130
  std::vector<double> AttrAsVectorOfDouble(Napi::Object obj, std::string attr);
112
131
  std::vector<int32_t> AttrAsInt32Vector(Napi::Object obj, std::string attr);
132
+ template <class T> T AttrAsEnum(Napi::Object obj, std::string attr, GType type) {
133
+ return static_cast<T>(
134
+ vips_enum_from_nick(nullptr, type, AttrAsStr(obj, attr).data()));
135
+ }
113
136
 
114
137
  // Create an InputDescriptor instance from a Napi::Object describing an input image
115
138
  InputDescriptor* CreateInputDescriptor(Napi::Object input);
@@ -183,6 +206,11 @@ namespace sharp {
183
206
  */
184
207
  bool ImageTypeSupportsPage(ImageType imageType);
185
208
 
209
+ /*
210
+ Does this image type support removal of safety limits?
211
+ */
212
+ bool ImageTypeSupportsUnlimited(ImageType imageType);
213
+
186
214
  /*
187
215
  Open an image from the given InputDescriptor (filesystem, compressed buffer, raw pixel data)
188
216
  */
@@ -307,16 +335,6 @@ namespace sharp {
307
335
  */
308
336
  double MaximumImageAlpha(VipsInterpretation const interpretation);
309
337
 
310
- /*
311
- Get boolean operation type from string
312
- */
313
- VipsOperationBoolean GetBooleanOperation(std::string const opStr);
314
-
315
- /*
316
- Get interpretation type from string
317
- */
318
- VipsInterpretation GetInterpretation(std::string const typeStr);
319
-
320
338
  /*
321
339
  Convert RGBA value to another colourspace
322
340
  */
@@ -31,7 +31,6 @@
31
31
  #ifdef HAVE_CONFIG_H
32
32
  #include <config.h>
33
33
  #endif /*HAVE_CONFIG_H*/
34
- #include <vips/intl.h>
35
34
 
36
35
  #include <vips/vips8>
37
36
 
@@ -30,7 +30,6 @@
30
30
  #ifdef HAVE_CONFIG_H
31
31
  #include <config.h>
32
32
  #endif /*HAVE_CONFIG_H*/
33
- #include <vips/intl.h>
34
33
 
35
34
  #include <vips/vips8>
36
35
 
@@ -38,7 +38,6 @@
38
38
  #ifdef HAVE_CONFIG_H
39
39
  #include <config.h>
40
40
  #endif /*HAVE_CONFIG_H*/
41
- #include <vips/intl.h>
42
41
 
43
42
  #include <vips/vips8>
44
43
 
@@ -733,7 +732,7 @@ VImage::write_to_buffer( const char *suffix, void **buf, size_t *size,
733
732
  set( "in", *this )->
734
733
  set( "target", target ) );
735
734
 
736
- g_object_get( target.get_target(), "blob", &blob, NULL );
735
+ g_object_get( target.get_target(), "blob", &blob, (void *) NULL );
737
736
  }
738
737
  else if( (operation_name = vips_foreign_find_save_buffer( filename )) ) {
739
738
  call_option_string( operation_name, option_string,
@@ -778,6 +777,32 @@ VImage::write_to_target( const char *suffix, VTarget target,
778
777
  set( "target", target ) );
779
778
  }
780
779
 
780
+ VRegion
781
+ VImage::region() const
782
+ {
783
+ return VRegion::new_from_image( *this );
784
+ }
785
+
786
+ VRegion
787
+ VImage::region( VipsRect *rect ) const
788
+ {
789
+ VRegion region = VRegion::new_from_image( *this );
790
+
791
+ region.prepare( rect );
792
+
793
+ return region;
794
+ }
795
+
796
+ VRegion
797
+ VImage::region( int left, int top, int width, int height ) const
798
+ {
799
+ VRegion region = VRegion::new_from_image( *this );
800
+
801
+ region.prepare( left, top, width, height );
802
+
803
+ return region;
804
+ }
805
+
781
806
  #include "vips-operators.cpp"
782
807
 
783
808
  std::vector<VImage>
@@ -31,7 +31,6 @@
31
31
  #ifdef HAVE_CONFIG_H
32
32
  #include <config.h>
33
33
  #endif /*HAVE_CONFIG_H*/
34
- #include <vips/intl.h>
35
34
 
36
35
  #include <vips/vips8>
37
36
 
@@ -0,0 +1,27 @@
1
+ // Object part of VRegion class
2
+
3
+ #ifdef HAVE_CONFIG_H
4
+ #include <config.h>
5
+ #endif /*HAVE_CONFIG_H*/
6
+
7
+ #include <vips/vips8>
8
+
9
+ #include <vips/debug.h>
10
+
11
+ VIPS_NAMESPACE_START
12
+
13
+ VRegion
14
+ VRegion::new_from_image( VImage image )
15
+ {
16
+ VipsRegion *region;
17
+
18
+ if( !(region = vips_region_new( image.get_image() )) ) {
19
+ throw VError();
20
+ }
21
+
22
+ VRegion out( region );
23
+
24
+ return( out );
25
+ }
26
+
27
+ VIPS_NAMESPACE_END
@@ -1,5 +1,4 @@
1
1
  // bodies for vips operations
2
- // Mon Nov 1 03:31:09 PM CET 2021
3
2
  // this file is generated automatically, do not edit!
4
3
 
5
4
  VImage VImage::CMC2LCh( VOption *options ) const
@@ -943,6 +942,14 @@ VipsBlob *VImage::dzsave_buffer( VOption *options ) const
943
942
  return( buffer );
944
943
  }
945
944
 
945
+ void VImage::dzsave_target( VTarget target, VOption *options ) const
946
+ {
947
+ call( "dzsave_target",
948
+ (options ? options : VImage::option())->
949
+ set( "in", *this )->
950
+ set( "target", target ) );
951
+ }
952
+
946
953
  VImage VImage::embed( int x, int y, int width, int height, VOption *options ) const
947
954
  {
948
955
  VImage out;
@@ -3521,6 +3528,14 @@ VipsBlob *VImage::tiffsave_buffer( VOption *options ) const
3521
3528
  return( buffer );
3522
3529
  }
3523
3530
 
3531
+ void VImage::tiffsave_target( VTarget target, VOption *options ) const
3532
+ {
3533
+ call( "tiffsave_target",
3534
+ (options ? options : VImage::option())->
3535
+ set( "in", *this )->
3536
+ set( "target", target ) );
3537
+ }
3538
+
3524
3539
  VImage VImage::tilecache( VOption *options ) const
3525
3540
  {
3526
3541
  VImage out;
package/src/operations.cc CHANGED
@@ -68,10 +68,9 @@ namespace sharp {
68
68
  // Extract luminance
69
69
  VImage luminance = lab[0];
70
70
  // Find luminance range
71
- VImage stats = luminance.stats();
72
- double min = stats(0, 0)[0];
73
- double max = stats(1, 0)[0];
74
- if (min != max) {
71
+ int const min = luminance.percent(1);
72
+ int const max = luminance.percent(99);
73
+ if (std::abs(max - min) > 1) {
75
74
  // Extract chroma
76
75
  VImage chroma = lab.extract_band(1, VImage::option()->set("n", 2));
77
76
  // Calculate multiplication factor and addition
@@ -112,6 +111,19 @@ namespace sharp {
112
111
  }
113
112
  }
114
113
 
114
+ /*
115
+ * Flatten image to remove alpha channel
116
+ */
117
+ VImage Flatten(VImage image, std::vector<double> flattenBackground) {
118
+ double const multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0;
119
+ std::vector<double> background {
120
+ flattenBackground[0] * multiplier,
121
+ flattenBackground[1] * multiplier,
122
+ flattenBackground[2] * multiplier
123
+ };
124
+ return image.flatten(VImage::option()->set("background", background));
125
+ }
126
+
115
127
  /**
116
128
  * Produce the "negative" of the image.
117
129
  */
@@ -262,42 +274,74 @@ namespace sharp {
262
274
  /*
263
275
  Trim an image
264
276
  */
265
- VImage Trim(VImage image, double const threshold) {
277
+ VImage Trim(VImage image, std::vector<double> background, double threshold) {
266
278
  if (image.width() < 3 && image.height() < 3) {
267
279
  throw VError("Image to trim must be at least 3x3 pixels");
268
280
  }
269
- // Top-left pixel provides the background colour
270
- VImage background = image.extract_area(0, 0, 1, 1);
271
- if (HasAlpha(background)) {
272
- background = background.flatten();
281
+
282
+ // Scale up 8-bit values to match 16-bit input image
283
+ double multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0;
284
+ threshold *= multiplier;
285
+
286
+ std::vector<double> backgroundAlpha(1);
287
+ if (background.size() == 0) {
288
+ // Top-left pixel provides the default background colour if none is given
289
+ background = image.extract_area(0, 0, 1, 1)(0, 0);
290
+ multiplier = 1.0;
291
+ }
292
+ if (background.size() == 4) {
293
+ // Just discard the alpha because flattening the background colour with
294
+ // itself (effectively what find_trim() does) gives the same result
295
+ backgroundAlpha[0] = background[3] * multiplier;
273
296
  }
297
+ background = {
298
+ background[0] * multiplier,
299
+ background[1] * multiplier,
300
+ background[2] * multiplier
301
+ };
302
+
274
303
  int left, top, width, height;
275
304
  left = image.find_trim(&top, &width, &height, VImage::option()
276
- ->set("background", background(0, 0))
305
+ ->set("background", background)
277
306
  ->set("threshold", threshold));
278
- if (width == 0 || height == 0) {
279
- if (HasAlpha(image)) {
280
- // Search alpha channel
281
- VImage alpha = image[image.bands() - 1];
282
- VImage backgroundAlpha = alpha.extract_area(0, 0, 1, 1);
283
- left = alpha.find_trim(&top, &width, &height, VImage::option()
284
- ->set("background", backgroundAlpha(0, 0))
285
- ->set("threshold", threshold));
286
- }
287
- if (width == 0 || height == 0) {
288
- throw VError("Unexpected error while trimming. Try to lower the tolerance");
307
+ if (HasAlpha(image)) {
308
+ // Search alpha channel (A)
309
+ int leftA, topA, widthA, heightA;
310
+ VImage alpha = image[image.bands() - 1];
311
+ leftA = alpha.find_trim(&topA, &widthA, &heightA, VImage::option()
312
+ ->set("background", backgroundAlpha)
313
+ ->set("threshold", threshold));
314
+ if (widthA > 0 && heightA > 0) {
315
+ if (width > 0 && height > 0) {
316
+ // Combined bounding box (B)
317
+ int const leftB = std::min(left, leftA);
318
+ int const topB = std::min(top, topA);
319
+ int const widthB = std::max(left + width, leftA + widthA) - leftB;
320
+ int const heightB = std::max(top + height, topA + heightA) - topB;
321
+ return image.extract_area(leftB, topB, widthB, heightB);
322
+ } else {
323
+ // Use alpha only
324
+ return image.extract_area(leftA, topA, widthA, heightA);
325
+ }
289
326
  }
290
327
  }
291
- return image.extract_area(left, top, width, height);
328
+ if (width > 0 && height > 0) {
329
+ return image.extract_area(left, top, width, height);
330
+ }
331
+ return image;
292
332
  }
293
333
 
294
334
  /*
295
335
  * Calculate (a * in + b)
296
336
  */
297
- VImage Linear(VImage image, double const a, double const b) {
298
- if (HasAlpha(image)) {
337
+ VImage Linear(VImage image, std::vector<double> const a, std::vector<double> const b) {
338
+ size_t const bands = static_cast<size_t>(image.bands());
339
+ if (a.size() > bands) {
340
+ throw VError("Band expansion using linear is unsupported");
341
+ }
342
+ if (HasAlpha(image) && a.size() != bands && (a.size() == 1 || a.size() == bands - 1 || bands - 1 == 1)) {
299
343
  // Separate alpha channel
300
- VImage alpha = image[image.bands() - 1];
344
+ VImage alpha = image[bands - 1];
301
345
  return RemoveAlpha(image).linear(a, b).bandjoin(alpha);
302
346
  } else {
303
347
  return image.linear(a, b);
package/src/operations.h CHANGED
@@ -45,6 +45,11 @@ namespace sharp {
45
45
  */
46
46
  VImage Gamma(VImage image, double const exponent);
47
47
 
48
+ /*
49
+ * Flatten image to remove alpha channel
50
+ */
51
+ VImage Flatten(VImage image, std::vector<double> flattenBackground);
52
+
48
53
  /*
49
54
  * Produce the "negative" of the image.
50
55
  */
@@ -85,12 +90,12 @@ namespace sharp {
85
90
  /*
86
91
  Trim an image
87
92
  */
88
- VImage Trim(VImage image, double const threshold);
93
+ VImage Trim(VImage image, std::vector<double> background, double const threshold);
89
94
 
90
95
  /*
91
96
  * Linear adjustment (a * in + b)
92
97
  */
93
- VImage Linear(VImage image, double const a, double const b);
98
+ VImage Linear(VImage image, std::vector<double> const a, std::vector<double> const b);
94
99
 
95
100
  /*
96
101
  * Recomb with a Matrix of the given bands/channel size.