sharp 0.26.1 → 0.27.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/lib/resize.js CHANGED
@@ -386,7 +386,8 @@ function extract (options) {
386
386
  * Trim "boring" pixels from all edges that contain values similar to the top-left pixel.
387
387
  * Images consisting entirely of a single colour will calculate "boring" using the alpha channel, if any.
388
388
  *
389
- * The `info` response Object will contain `trimOffsetLeft` and `trimOffsetTop` properties.
389
+ * The `info` response Object, obtained from callback of `.toFile()` or `.toBuffer()`,
390
+ * will contain `trimOffsetLeft` and `trimOffsetTop` properties.
390
391
  *
391
392
  * @param {number} [threshold=10] the allowed difference from the top-left pixel, a number greater than zero.
392
393
  * @returns {Sharp}
package/lib/utility.js CHANGED
@@ -13,6 +13,26 @@ const sharp = require('../build/Release/sharp.node');
13
13
  */
14
14
  const format = sharp.format();
15
15
 
16
+ /**
17
+ * An Object containing the available interpolators and their proper values
18
+ * @readonly
19
+ * @enum {string}
20
+ */
21
+ const interpolators = {
22
+ /** [Nearest neighbour interpolation](http://en.wikipedia.org/wiki/Nearest-neighbor_interpolation). Suitable for image enlargement only. */
23
+ nearest: 'nearest',
24
+ /** [Bilinear interpolation](http://en.wikipedia.org/wiki/Bilinear_interpolation). Faster than bicubic but with less smooth results. */
25
+ bilinear: 'bilinear',
26
+ /** [Bicubic interpolation](http://en.wikipedia.org/wiki/Bicubic_interpolation) (the default). */
27
+ bicubic: 'bicubic',
28
+ /** [LBB interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/lbb.cpp#L100). Prevents some "[acutance](http://en.wikipedia.org/wiki/Acutance)" but typically reduces performance by a factor of 2. */
29
+ locallyBoundedBicubic: 'lbb',
30
+ /** [Nohalo interpolation](http://eprints.soton.ac.uk/268086/). Prevents acutance but typically reduces performance by a factor of 3. */
31
+ nohalo: 'nohalo',
32
+ /** [VSQBS interpolation](https://github.com/jcupitt/libvips/blob/master/libvips/resample/vsqbs.cpp#L48). Prevents "staircasing" when enlarging. */
33
+ vertexSplitQuadraticBasisSpline: 'vsqbs'
34
+ };
35
+
16
36
  /**
17
37
  * An Object containing the version numbers of libvips and its dependencies.
18
38
  * @member
@@ -146,6 +166,7 @@ module.exports = function (Sharp) {
146
166
  Sharp[f.name] = f;
147
167
  });
148
168
  Sharp.format = format;
169
+ Sharp.interpolators = interpolators;
149
170
  Sharp.versions = versions;
150
171
  Sharp.queue = queue;
151
172
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sharp",
3
- "description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP and TIFF images",
4
- "version": "0.26.1",
3
+ "description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, AVIF and TIFF images",
4
+ "version": "0.27.1",
5
5
  "author": "Lovell Fuller <npm@lovell.info>",
6
6
  "homepage": "https://github.com/lovell/sharp",
7
7
  "contributors": [
@@ -69,17 +69,22 @@
69
69
  "Edward Silverton <e.silverton@gmail.com>",
70
70
  "Roman Malieiev <aromaleev@gmail.com>",
71
71
  "Tomas Szabo <tomas.szabo@deftomat.com>",
72
- "Robert O'Rourke <robert@o-rourke.org>"
72
+ "Robert O'Rourke <robert@o-rourke.org>",
73
+ "Guillermo Alfonso Varela Chouciño <guillevch@gmail.com>",
74
+ "Christian Flintrup <chr@gigahost.dk>",
75
+ "Manan Jadhav <manan@motionden.com>",
76
+ "Leon Radley <leon@radley.se>",
77
+ "alza54 <alza54@thiocod.in>"
73
78
  ],
74
79
  "scripts": {
75
80
  "install": "(node install/libvips && node install/dll-copy && prebuild-install) || (node-gyp rebuild && node install/dll-copy)",
76
81
  "clean": "rm -rf node_modules/ build/ vendor/ .nyc_output/ coverage/ test/fixtures/output.*",
77
- "test": "semistandard && cpplint && npm run test-unit && npm run test-licensing && node install/prebuild-ci",
78
- "test-unit": "nyc --reporter=lcov --branches=99 mocha --slow=5000 --timeout=60000 ./test/unit/*.js",
82
+ "test": "semistandard && cpplint && npm run test-unit && npm run test-licensing",
83
+ "test-unit": "nyc --reporter=lcov --branches=99 mocha --parallel --slow=5000 --timeout=60000 ./test/unit/*.js",
79
84
  "test-licensing": "license-checker --production --summary --onlyAllow=\"Apache-2.0;BSD;ISC;MIT\"",
80
85
  "test-coverage": "./test/coverage/report.sh",
81
86
  "test-leak": "./test/leak/leak.sh",
82
- "docs-build": "documentation lint lib && for m in constructor input resize composite operation colour channel output utility; do documentation build --shallow --format=md --markdown-toc=false lib/$m.js >docs/api-$m.md; done && node docs/search-index/build",
87
+ "docs-build": "documentation lint lib && node docs/build && node docs/search-index/build",
83
88
  "docs-serve": "cd docs && npx serve",
84
89
  "docs-publish": "cd docs && npx firebase-tools deploy --project pixelplumbing --only hosting:pixelplumbing-sharp"
85
90
  },
@@ -87,7 +92,6 @@
87
92
  "files": [
88
93
  "binding.gyp",
89
94
  "install/**",
90
- "!install/prebuild-ci.js",
91
95
  "lib/**",
92
96
  "src/**"
93
97
  ],
@@ -99,6 +103,7 @@
99
103
  "jpeg",
100
104
  "png",
101
105
  "webp",
106
+ "avif",
102
107
  "tiff",
103
108
  "gif",
104
109
  "svg",
@@ -112,39 +117,40 @@
112
117
  "vips"
113
118
  ],
114
119
  "dependencies": {
115
- "color": "^3.1.2",
120
+ "array-flatten": "^3.0.0",
121
+ "color": "^3.1.3",
116
122
  "detect-libc": "^1.0.3",
117
- "node-addon-api": "^3.0.2",
123
+ "node-addon-api": "^3.1.0",
118
124
  "npmlog": "^4.1.2",
119
- "prebuild-install": "^5.3.5",
120
- "semver": "^7.3.2",
125
+ "prebuild-install": "^6.0.0",
126
+ "semver": "^7.3.4",
121
127
  "simple-get": "^4.0.0",
122
- "tar-fs": "^2.1.0",
128
+ "tar-fs": "^2.1.1",
123
129
  "tunnel-agent": "^0.6.0"
124
130
  },
125
131
  "devDependencies": {
126
132
  "async": "^3.2.0",
127
- "cc": "^2.0.1",
128
- "decompress-zip": "^0.3.2",
129
- "documentation": "^13.0.2",
133
+ "cc": "^3.0.1",
134
+ "decompress-zip": "^0.3.3",
135
+ "documentation": "^13.1.1",
130
136
  "exif-reader": "^1.0.3",
131
137
  "icc": "^2.0.0",
132
138
  "license-checker": "^25.0.1",
133
- "mocha": "^8.1.1",
139
+ "mocha": "^8.2.1",
134
140
  "mock-fs": "^4.13.0",
135
141
  "nyc": "^15.1.0",
136
142
  "prebuild": "^10.0.1",
137
143
  "rimraf": "^3.0.2",
138
- "semistandard": "^14.2.3"
144
+ "semistandard": "^16.0.0"
139
145
  },
140
146
  "license": "Apache-2.0",
141
147
  "config": {
142
- "libvips": "8.10.0",
148
+ "libvips": "8.10.5",
143
149
  "runtime": "napi",
144
150
  "target": 3
145
151
  },
146
152
  "engines": {
147
- "node": ">=10.16.0"
153
+ "node": ">=10"
148
154
  },
149
155
  "funding": {
150
156
  "url": "https://opencollective.com/libvips"
package/src/common.cc CHANGED
@@ -54,13 +54,13 @@ namespace sharp {
54
54
  bool AttrAsBool(Napi::Object obj, std::string attr) {
55
55
  return obj.Get(attr).As<Napi::Boolean>().Value();
56
56
  }
57
- std::vector<double> AttrAsRgba(Napi::Object obj, std::string attr) {
58
- Napi::Array background = obj.Get(attr).As<Napi::Array>();
59
- std::vector<double> rgba(background.Length());
60
- for (unsigned int i = 0; i < background.Length(); i++) {
61
- rgba[i] = AttrAsDouble(background, i);
57
+ std::vector<double> AttrAsVectorOfDouble(Napi::Object obj, std::string attr) {
58
+ Napi::Array napiArray = obj.Get(attr).As<Napi::Array>();
59
+ std::vector<double> vectorOfDouble(napiArray.Length());
60
+ for (unsigned int i = 0; i < napiArray.Length(); i++) {
61
+ vectorOfDouble[i] = AttrAsDouble(napiArray, i);
62
62
  }
63
- return rgba;
63
+ return vectorOfDouble;
64
64
  }
65
65
  std::vector<int32_t> AttrAsInt32Vector(Napi::Object obj, std::string attr) {
66
66
  Napi::Array array = obj.Get(attr).As<Napi::Array>();
@@ -109,7 +109,13 @@ namespace sharp {
109
109
  descriptor->createChannels = AttrAsUint32(input, "createChannels");
110
110
  descriptor->createWidth = AttrAsUint32(input, "createWidth");
111
111
  descriptor->createHeight = AttrAsUint32(input, "createHeight");
112
- descriptor->createBackground = AttrAsRgba(input, "createBackground");
112
+ if (HasAttr(input, "createNoiseType")) {
113
+ descriptor->createNoiseType = AttrAsStr(input, "createNoiseType");
114
+ descriptor->createNoiseMean = AttrAsDouble(input, "createNoiseMean");
115
+ descriptor->createNoiseSigma = AttrAsDouble(input, "createNoiseSigma");
116
+ } else {
117
+ descriptor->createBackground = AttrAsVectorOfDouble(input, "createBackground");
118
+ }
113
119
  }
114
120
  // Limit input images to a given number of pixels, where pixels = width * height
115
121
  descriptor->limitInputPixels = AttrAsUint32(input, "limitInputPixels");
@@ -180,6 +186,7 @@ namespace sharp {
180
186
  case ImageType::OPENSLIDE: id = "openslide"; break;
181
187
  case ImageType::PPM: id = "ppm"; break;
182
188
  case ImageType::FITS: id = "fits"; break;
189
+ case ImageType::EXR: id = "exr"; break;
183
190
  case ImageType::VIPS: id = "vips"; break;
184
191
  case ImageType::RAW: id = "raw"; break;
185
192
  case ImageType::UNKNOWN: id = "unknown"; break;
@@ -188,30 +195,38 @@ namespace sharp {
188
195
  return id;
189
196
  }
190
197
 
198
+ /**
199
+ * Regenerate this table with something like:
200
+ *
201
+ * $ vips -l foreign | grep -i load | awk '{ print $2, $1; }'
202
+ *
203
+ * Plus a bit of editing.
204
+ */
191
205
  std::map<std::string, ImageType> loaderToType = {
192
- { "jpegload", ImageType::JPEG },
193
- { "jpegload_buffer", ImageType::JPEG },
194
- { "pngload", ImageType::PNG },
195
- { "pngload_buffer", ImageType::PNG },
196
- { "webpload", ImageType::WEBP },
197
- { "webpload_buffer", ImageType::WEBP },
198
- { "tiffload", ImageType::TIFF },
199
- { "tiffload_buffer", ImageType::TIFF },
200
- { "gifload", ImageType::GIF },
201
- { "gifload_buffer", ImageType::GIF },
202
- { "svgload", ImageType::SVG },
203
- { "svgload_buffer", ImageType::SVG },
204
- { "heifload", ImageType::HEIF },
205
- { "heifload_buffer", ImageType::HEIF },
206
- { "pdfload", ImageType::PDF },
207
- { "pdfload_buffer", ImageType::PDF },
208
- { "magickload", ImageType::MAGICK },
209
- { "magickload_buffer", ImageType::MAGICK },
210
- { "openslideload", ImageType::OPENSLIDE },
211
- { "ppmload", ImageType::PPM },
212
- { "fitsload", ImageType::FITS },
213
- { "vipsload", ImageType::VIPS },
214
- { "rawload", ImageType::RAW }
206
+ { "VipsForeignLoadJpegFile", ImageType::JPEG },
207
+ { "VipsForeignLoadJpegBuffer", ImageType::JPEG },
208
+ { "VipsForeignLoadPngFile", ImageType::PNG },
209
+ { "VipsForeignLoadPngBuffer", ImageType::PNG },
210
+ { "VipsForeignLoadWebpFile", ImageType::WEBP },
211
+ { "VipsForeignLoadWebpBuffer", ImageType::WEBP },
212
+ { "VipsForeignLoadTiffFile", ImageType::TIFF },
213
+ { "VipsForeignLoadTiffBuffer", ImageType::TIFF },
214
+ { "VipsForeignLoadGifFile", ImageType::GIF },
215
+ { "VipsForeignLoadGifBuffer", ImageType::GIF },
216
+ { "VipsForeignLoadSvgFile", ImageType::SVG },
217
+ { "VipsForeignLoadSvgBuffer", ImageType::SVG },
218
+ { "VipsForeignLoadHeifFile", ImageType::HEIF },
219
+ { "VipsForeignLoadHeifBuffer", ImageType::HEIF },
220
+ { "VipsForeignLoadPdfFile", ImageType::PDF },
221
+ { "VipsForeignLoadPdfBuffer", ImageType::PDF },
222
+ { "VipsForeignLoadMagickFile", ImageType::MAGICK },
223
+ { "VipsForeignLoadMagickBuffer", ImageType::MAGICK },
224
+ { "VipsForeignLoadOpenslide", ImageType::OPENSLIDE },
225
+ { "VipsForeignLoadPpmFile", ImageType::PPM },
226
+ { "VipsForeignLoadFits", ImageType::FITS },
227
+ { "VipsForeignLoadOpenexr", ImageType::EXR },
228
+ { "VipsForeignLoadVips", ImageType::VIPS },
229
+ { "VipsForeignLoadRaw", ImageType::RAW }
215
230
  };
216
231
 
217
232
  /*
@@ -221,7 +236,7 @@ namespace sharp {
221
236
  ImageType imageType = ImageType::UNKNOWN;
222
237
  char const *load = vips_foreign_find_load_buffer(buffer, length);
223
238
  if (load != nullptr) {
224
- auto it = loaderToType.find(vips_nickname_find(g_type_from_name(load)));
239
+ auto it = loaderToType.find(load);
225
240
  if (it != loaderToType.end()) {
226
241
  imageType = it->second;
227
242
  }
@@ -236,7 +251,7 @@ namespace sharp {
236
251
  ImageType imageType = ImageType::UNKNOWN;
237
252
  char const *load = vips_foreign_find_load(file);
238
253
  if (load != nullptr) {
239
- auto it = loaderToType.find(vips_nickname_find(g_type_from_name(load)));
254
+ auto it = loaderToType.find(load);
240
255
  if (it != loaderToType.end()) {
241
256
  imageType = it->second;
242
257
  }
@@ -316,15 +331,35 @@ namespace sharp {
316
331
  } else {
317
332
  if (descriptor->createChannels > 0) {
318
333
  // Create new image
319
- std::vector<double> background = {
320
- descriptor->createBackground[0],
321
- descriptor->createBackground[1],
322
- descriptor->createBackground[2]
323
- };
324
- if (descriptor->createChannels == 4) {
325
- background.push_back(descriptor->createBackground[3]);
334
+ if (descriptor->createNoiseType == "gaussian") {
335
+ int const channels = descriptor->createChannels;
336
+ image = VImage::new_matrix(descriptor->createWidth, descriptor->createHeight);
337
+ std::vector<VImage> bands = {};
338
+ bands.reserve(channels);
339
+ for (int _band = 0; _band < channels; _band++) {
340
+ bands.push_back(image.gaussnoise(
341
+ descriptor->createWidth,
342
+ descriptor->createHeight,
343
+ VImage::option()->set("mean", descriptor->createNoiseMean)->set("sigma", descriptor->createNoiseSigma)));
344
+ }
345
+ image = image.bandjoin(bands);
346
+ image = image.cast(VipsBandFormat::VIPS_FORMAT_UCHAR);
347
+ if (channels < 3) {
348
+ image = image.colourspace(VIPS_INTERPRETATION_B_W);
349
+ } else {
350
+ image = image.colourspace(VIPS_INTERPRETATION_sRGB);
351
+ }
352
+ } else {
353
+ std::vector<double> background = {
354
+ descriptor->createBackground[0],
355
+ descriptor->createBackground[1],
356
+ descriptor->createBackground[2]
357
+ };
358
+ if (descriptor->createChannels == 4) {
359
+ background.push_back(descriptor->createBackground[3]);
360
+ }
361
+ image = VImage::new_matrix(descriptor->createWidth, descriptor->createHeight).new_from_image(background);
326
362
  }
327
- image = VImage::new_matrix(descriptor->createWidth, descriptor->createHeight).new_from_image(background);
328
363
  image.get_image()->Type = VIPS_INTERPRETATION_sRGB;
329
364
  imageType = ImageType::RAW;
330
365
  } else {
@@ -656,26 +691,18 @@ namespace sharp {
656
691
  int top = 0;
657
692
 
658
693
  // assign only if valid
659
- if (x >= 0 && x < (inWidth - outWidth)) {
694
+ if (x < (inWidth - outWidth)) {
660
695
  left = x;
661
696
  } else if (x >= (inWidth - outWidth)) {
662
697
  left = inWidth - outWidth;
663
698
  }
664
699
 
665
- if (y >= 0 && y < (inHeight - outHeight)) {
700
+ if (y < (inHeight - outHeight)) {
666
701
  top = y;
667
702
  } else if (y >= (inHeight - outHeight)) {
668
703
  top = inHeight - outHeight;
669
704
  }
670
705
 
671
- // the resulting left and top could have been outside the image after calculation from bottom/right edges
672
- if (left < 0) {
673
- left = 0;
674
- }
675
- if (top < 0) {
676
- top = 0;
677
- }
678
-
679
706
  return std::make_tuple(left, top);
680
707
  }
681
708
 
package/src/common.h CHANGED
@@ -24,8 +24,10 @@
24
24
 
25
25
  // Verify platform and compiler compatibility
26
26
 
27
- #if (VIPS_MAJOR_VERSION < 8 || (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 10))
28
- #error "libvips version 8.10.0+ is required - please see https://sharp.pixelplumbing.com/install"
27
+ #if (VIPS_MAJOR_VERSION < 8) || \
28
+ (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 10) || \
29
+ (VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 10 && VIPS_MICRO_VERSION < 5)
30
+ #error "libvips version 8.10.5+ is required - please see https://sharp.pixelplumbing.com/install"
29
31
  #endif
30
32
 
31
33
  #if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6)))
@@ -62,6 +64,9 @@ namespace sharp {
62
64
  int createWidth;
63
65
  int createHeight;
64
66
  std::vector<double> createBackground;
67
+ std::string createNoiseType;
68
+ double createNoiseMean;
69
+ double createNoiseSigma;
65
70
 
66
71
  InputDescriptor():
67
72
  buffer(nullptr),
@@ -80,7 +85,9 @@ namespace sharp {
80
85
  createChannels(0),
81
86
  createWidth(0),
82
87
  createHeight(0),
83
- createBackground{ 0.0, 0.0, 0.0, 255.0 } {}
88
+ createBackground{ 0.0, 0.0, 0.0, 255.0 },
89
+ createNoiseMean(0.0),
90
+ createNoiseSigma(0.0) {}
84
91
  };
85
92
 
86
93
  // Convenience methods to access the attributes of a Napi::Object
@@ -92,7 +99,7 @@ namespace sharp {
92
99
  double AttrAsDouble(Napi::Object obj, std::string attr);
93
100
  double AttrAsDouble(Napi::Object obj, unsigned int const attr);
94
101
  bool AttrAsBool(Napi::Object obj, std::string attr);
95
- std::vector<double> AttrAsRgba(Napi::Object obj, std::string attr);
102
+ std::vector<double> AttrAsVectorOfDouble(Napi::Object obj, std::string attr);
96
103
  std::vector<int32_t> AttrAsInt32Vector(Napi::Object obj, std::string attr);
97
104
 
98
105
  // Create an InputDescriptor instance from a Napi::Object describing an input image
@@ -111,6 +118,7 @@ namespace sharp {
111
118
  OPENSLIDE,
112
119
  PPM,
113
120
  FITS,
121
+ EXR,
114
122
  VIPS,
115
123
  RAW,
116
124
  UNKNOWN,
package/src/metadata.cc CHANGED
@@ -74,6 +74,9 @@ class MetadataWorker : public Napi::AsyncWorker {
74
74
  if (image.get_typeof("heif-primary") == G_TYPE_INT) {
75
75
  baton->pagePrimary = image.get_int("heif-primary");
76
76
  }
77
+ if (image.get_typeof("heif-compression") == VIPS_TYPE_REF_STRING) {
78
+ baton->compression = image.get_string("heif-compression");
79
+ }
77
80
  if (image.get_typeof("openslide.level-count") == VIPS_TYPE_REF_STRING) {
78
81
  int const levels = std::stoi(image.get_string("openslide.level-count"));
79
82
  for (int l = 0; l < levels; l++) {
@@ -186,6 +189,9 @@ class MetadataWorker : public Napi::AsyncWorker {
186
189
  if (baton->pagePrimary > -1) {
187
190
  info.Set("pagePrimary", baton->pagePrimary);
188
191
  }
192
+ if (!baton->compression.empty()) {
193
+ info.Set("compression", baton->compression);
194
+ }
189
195
  if (!baton->levels.empty()) {
190
196
  int i = 0;
191
197
  Napi::Array levels = Napi::Array::New(env, static_cast<size_t>(baton->levels.size()));
package/src/metadata.h CHANGED
@@ -39,6 +39,7 @@ struct MetadataBaton {
39
39
  int loop;
40
40
  std::vector<int> delay;
41
41
  int pagePrimary;
42
+ std::string compression;
42
43
  std::vector<std::pair<int, int>> levels;
43
44
  bool hasProfile;
44
45
  bool hasAlpha;
package/src/pipeline.cc CHANGED
@@ -485,6 +485,18 @@ class PipelineWorker : public Napi::AsyncWorker {
485
485
  baton->leftOffsetPost, baton->topOffsetPost, baton->widthPost, baton->heightPost);
486
486
  }
487
487
 
488
+ // Affine transform
489
+ if (baton->affineMatrix.size() > 0) {
490
+ std::vector<double> background;
491
+ std::tie(image, background) = sharp::ApplyAlpha(image, baton->affineBackground);
492
+ image = image.affine(baton->affineMatrix, VImage::option()->set("background", background)
493
+ ->set("idx", baton->affineIdx)
494
+ ->set("idy", baton->affineIdy)
495
+ ->set("odx", baton->affineOdx)
496
+ ->set("ody", baton->affineOdy)
497
+ ->set("interpolate", baton->affineInterpolator));
498
+ }
499
+
488
500
  // Extend edges
489
501
  if (baton->extendTop > 0 || baton->extendBottom > 0 || baton->extendLeft > 0 || baton->extendRight > 0) {
490
502
  std::vector<double> background;
@@ -558,7 +570,7 @@ class PipelineWorker : public Napi::AsyncWorker {
558
570
  int left;
559
571
  int top;
560
572
  compositeImage = compositeImage.replicate(across, down);
561
- if (composite->left >= 0 && composite->top >= 0) {
573
+ if (composite->hasOffset) {
562
574
  std::tie(left, top) = sharp::CalculateCrop(
563
575
  compositeImage.width(), compositeImage.height(), image.width(), image.height(),
564
576
  composite->left, composite->top);
@@ -580,7 +592,7 @@ class PipelineWorker : public Napi::AsyncWorker {
580
592
  // Calculate position
581
593
  int left;
582
594
  int top;
583
- if (composite->left >= 0 && composite->top >= 0) {
595
+ if (composite->hasOffset) {
584
596
  // Composite image at given offsets
585
597
  std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),
586
598
  compositeImage.width(), compositeImage.height(), composite->left, composite->top);
@@ -718,7 +730,7 @@ class PipelineWorker : public Napi::AsyncWorker {
718
730
  if (baton->formatOut == "jpeg" || (baton->formatOut == "input" && inputImageType == sharp::ImageType::JPEG)) {
719
731
  // Write JPEG to buffer
720
732
  sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
721
- VipsArea *area = VIPS_AREA(image.jpegsave_buffer(VImage::option()
733
+ VipsArea *area = reinterpret_cast<VipsArea*>(image.jpegsave_buffer(VImage::option()
722
734
  ->set("strip", !baton->withMetadata)
723
735
  ->set("Q", baton->jpegQuality)
724
736
  ->set("interlace", baton->jpegProgressive)
@@ -745,7 +757,7 @@ class PipelineWorker : public Napi::AsyncWorker {
745
757
  inputImageType == sharp::ImageType::SVG))) {
746
758
  // Write PNG to buffer
747
759
  sharp::AssertImageTypeDimensions(image, sharp::ImageType::PNG);
748
- VipsArea *area = VIPS_AREA(image.pngsave_buffer(VImage::option()
760
+ VipsArea *area = reinterpret_cast<VipsArea*>(image.pngsave_buffer(VImage::option()
749
761
  ->set("strip", !baton->withMetadata)
750
762
  ->set("interlace", baton->pngProgressive)
751
763
  ->set("compression", baton->pngCompressionLevel)
@@ -763,7 +775,7 @@ class PipelineWorker : public Napi::AsyncWorker {
763
775
  (baton->formatOut == "input" && inputImageType == sharp::ImageType::WEBP)) {
764
776
  // Write WEBP to buffer
765
777
  sharp::AssertImageTypeDimensions(image, sharp::ImageType::WEBP);
766
- VipsArea *area = VIPS_AREA(image.webpsave_buffer(VImage::option()
778
+ VipsArea *area = reinterpret_cast<VipsArea*>(image.webpsave_buffer(VImage::option()
767
779
  ->set("strip", !baton->withMetadata)
768
780
  ->set("Q", baton->webpQuality)
769
781
  ->set("lossless", baton->webpLossless)
@@ -780,7 +792,7 @@ class PipelineWorker : public Napi::AsyncWorker {
780
792
  (baton->formatOut == "input" && inputImageType == sharp::ImageType::GIF && supportsGifOutput)) {
781
793
  // Write GIF to buffer
782
794
  sharp::AssertImageTypeDimensions(image, sharp::ImageType::GIF);
783
- VipsArea *area = VIPS_AREA(image.magicksave_buffer(VImage::option()
795
+ VipsArea *area = reinterpret_cast<VipsArea*>(image.magicksave_buffer(VImage::option()
784
796
  ->set("strip", !baton->withMetadata)
785
797
  ->set("optimize_gif_frames", TRUE)
786
798
  ->set("optimize_gif_transparency", TRUE)
@@ -801,7 +813,7 @@ class PipelineWorker : public Napi::AsyncWorker {
801
813
  if (baton->tiffPredictor == VIPS_FOREIGN_TIFF_PREDICTOR_FLOAT) {
802
814
  image = image.cast(VIPS_FORMAT_FLOAT);
803
815
  }
804
- VipsArea *area = VIPS_AREA(image.tiffsave_buffer(VImage::option()
816
+ VipsArea *area = reinterpret_cast<VipsArea*>(image.tiffsave_buffer(VImage::option()
805
817
  ->set("strip", !baton->withMetadata)
806
818
  ->set("Q", baton->tiffQuality)
807
819
  ->set("bitdepth", baton->tiffBitdepth)
@@ -821,10 +833,15 @@ class PipelineWorker : public Napi::AsyncWorker {
821
833
  } else if (baton->formatOut == "heif" ||
822
834
  (baton->formatOut == "input" && inputImageType == sharp::ImageType::HEIF)) {
823
835
  // Write HEIF to buffer
824
- VipsArea *area = VIPS_AREA(image.heifsave_buffer(VImage::option()
836
+ VipsArea *area = reinterpret_cast<VipsArea*>(image.heifsave_buffer(VImage::option()
825
837
  ->set("strip", !baton->withMetadata)
826
838
  ->set("compression", baton->heifCompression)
827
839
  ->set("Q", baton->heifQuality)
840
+ ->set("speed", baton->heifSpeed)
841
+ #if defined(VIPS_TYPE_FOREIGN_SUBSAMPLE)
842
+ ->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
843
+ ? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
844
+ #endif
828
845
  ->set("lossless", baton->heifLossless)));
829
846
  baton->bufferOut = static_cast<char*>(area->data);
830
847
  baton->bufferOutLength = area->length;
@@ -873,7 +890,7 @@ class PipelineWorker : public Napi::AsyncWorker {
873
890
  bool const isV = sharp::IsV(baton->fileOut);
874
891
  bool const mightMatchInput = baton->formatOut == "input";
875
892
  bool const willMatchInput = mightMatchInput &&
876
- !(isJpeg || isPng || isWebp || isGif || isTiff || isDz || isDzZip || isV);
893
+ !(isJpeg || isPng || isWebp || isGif || isTiff || isHeif || isDz || isDzZip || isV);
877
894
 
878
895
  if (baton->formatOut == "jpeg" || (mightMatchInput && isJpeg) ||
879
896
  (willMatchInput && inputImageType == sharp::ImageType::JPEG)) {
@@ -938,6 +955,10 @@ class PipelineWorker : public Napi::AsyncWorker {
938
955
  sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
939
956
  baton->channels = std::min(baton->channels, 3);
940
957
  }
958
+ // Cast pixel values to float, if required
959
+ if (baton->tiffPredictor == VIPS_FOREIGN_TIFF_PREDICTOR_FLOAT) {
960
+ image = image.cast(VIPS_FORMAT_FLOAT);
961
+ }
941
962
  image.tiffsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
942
963
  ->set("strip", !baton->withMetadata)
943
964
  ->set("Q", baton->tiffQuality)
@@ -954,13 +975,15 @@ class PipelineWorker : public Napi::AsyncWorker {
954
975
  } else if (baton->formatOut == "heif" || (mightMatchInput && isHeif) ||
955
976
  (willMatchInput && inputImageType == sharp::ImageType::HEIF)) {
956
977
  // Write HEIF to file
957
- if (sharp::IsAvif(baton->fileOut)) {
958
- baton->heifCompression = VIPS_FOREIGN_HEIF_COMPRESSION_AV1;
959
- }
960
978
  image.heifsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
961
979
  ->set("strip", !baton->withMetadata)
962
980
  ->set("Q", baton->heifQuality)
963
981
  ->set("compression", baton->heifCompression)
982
+ ->set("speed", baton->heifSpeed)
983
+ #if defined(VIPS_TYPE_FOREIGN_SUBSAMPLE)
984
+ ->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
985
+ ? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
986
+ #endif
964
987
  ->set("lossless", baton->heifLossless));
965
988
  baton->formatOut = "heif";
966
989
  } else if (baton->formatOut == "dz" || isDz || isDzZip) {
@@ -1014,6 +1037,7 @@ class PipelineWorker : public Napi::AsyncWorker {
1014
1037
  ->set("suffix", const_cast<char*>(suffix.data()))
1015
1038
  ->set("angle", CalculateAngleRotation(baton->tileAngle))
1016
1039
  ->set("background", baton->tileBackground)
1040
+ ->set("centre", baton->tileCentre)
1017
1041
  ->set("skip_blanks", baton->tileSkipBlanks);
1018
1042
  // libvips chooses a default depth based on layout. Instead of replicating that logic here by
1019
1043
  // not passing anything - libvips will handle choice
@@ -1241,6 +1265,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1241
1265
  composite->gravity = sharp::AttrAsUint32(compositeObject, "gravity");
1242
1266
  composite->left = sharp::AttrAsInt32(compositeObject, "left");
1243
1267
  composite->top = sharp::AttrAsInt32(compositeObject, "top");
1268
+ composite->hasOffset = sharp::AttrAsBool(compositeObject, "hasOffset");
1244
1269
  composite->tile = sharp::AttrAsBool(compositeObject, "tile");
1245
1270
  composite->premultiplied = sharp::AttrAsBool(compositeObject, "premultiplied");
1246
1271
  baton->composite.push_back(composite);
@@ -1248,7 +1273,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1248
1273
  // Resize options
1249
1274
  baton->withoutEnlargement = sharp::AttrAsBool(options, "withoutEnlargement");
1250
1275
  baton->position = sharp::AttrAsInt32(options, "position");
1251
- baton->resizeBackground = sharp::AttrAsRgba(options, "resizeBackground");
1276
+ baton->resizeBackground = sharp::AttrAsVectorOfDouble(options, "resizeBackground");
1252
1277
  baton->kernel = sharp::AttrAsStr(options, "kernel");
1253
1278
  baton->fastShrinkOnLoad = sharp::AttrAsBool(options, "fastShrinkOnLoad");
1254
1279
  // Join Channel Options
@@ -1261,7 +1286,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1261
1286
  }
1262
1287
  // Operators
1263
1288
  baton->flatten = sharp::AttrAsBool(options, "flatten");
1264
- baton->flattenBackground = sharp::AttrAsRgba(options, "flattenBackground");
1289
+ baton->flattenBackground = sharp::AttrAsVectorOfDouble(options, "flattenBackground");
1265
1290
  baton->negate = sharp::AttrAsBool(options, "negate");
1266
1291
  baton->blurSigma = sharp::AttrAsDouble(options, "blurSigma");
1267
1292
  baton->brightness = sharp::AttrAsDouble(options, "brightness");
@@ -1283,7 +1308,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1283
1308
  baton->useExifOrientation = sharp::AttrAsBool(options, "useExifOrientation");
1284
1309
  baton->angle = sharp::AttrAsInt32(options, "angle");
1285
1310
  baton->rotationAngle = sharp::AttrAsDouble(options, "rotationAngle");
1286
- baton->rotationBackground = sharp::AttrAsRgba(options, "rotationBackground");
1311
+ baton->rotationBackground = sharp::AttrAsVectorOfDouble(options, "rotationBackground");
1287
1312
  baton->rotateBeforePreExtract = sharp::AttrAsBool(options, "rotateBeforePreExtract");
1288
1313
  baton->flip = sharp::AttrAsBool(options, "flip");
1289
1314
  baton->flop = sharp::AttrAsBool(options, "flop");
@@ -1291,8 +1316,15 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1291
1316
  baton->extendBottom = sharp::AttrAsInt32(options, "extendBottom");
1292
1317
  baton->extendLeft = sharp::AttrAsInt32(options, "extendLeft");
1293
1318
  baton->extendRight = sharp::AttrAsInt32(options, "extendRight");
1294
- baton->extendBackground = sharp::AttrAsRgba(options, "extendBackground");
1319
+ baton->extendBackground = sharp::AttrAsVectorOfDouble(options, "extendBackground");
1295
1320
  baton->extractChannel = sharp::AttrAsInt32(options, "extractChannel");
1321
+ baton->affineMatrix = sharp::AttrAsVectorOfDouble(options, "affineMatrix");
1322
+ baton->affineBackground = sharp::AttrAsVectorOfDouble(options, "affineBackground");
1323
+ baton->affineIdx = sharp::AttrAsDouble(options, "affineIdx");
1324
+ baton->affineIdy = sharp::AttrAsDouble(options, "affineIdy");
1325
+ baton->affineOdx = sharp::AttrAsDouble(options, "affineOdx");
1326
+ baton->affineOdy = sharp::AttrAsDouble(options, "affineOdy");
1327
+ baton->affineInterpolator = vips::VInterpolate::new_from_name(sharp::AttrAsStr(options, "affineInterpolator").data());
1296
1328
 
1297
1329
  baton->removeAlpha = sharp::AttrAsBool(options, "removeAlpha");
1298
1330
  baton->ensureAlpha = sharp::AttrAsBool(options, "ensureAlpha");
@@ -1375,6 +1407,8 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1375
1407
  baton->heifCompression = static_cast<VipsForeignHeifCompression>(
1376
1408
  vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_HEIF_COMPRESSION,
1377
1409
  sharp::AttrAsStr(options, "heifCompression").data()));
1410
+ baton->heifSpeed = sharp::AttrAsUint32(options, "heifSpeed");
1411
+ baton->heifChromaSubsampling = sharp::AttrAsStr(options, "heifChromaSubsampling");
1378
1412
 
1379
1413
  // Animated output
1380
1414
  if (sharp::HasAttr(options, "pageHeight")) {
@@ -1391,7 +1425,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1391
1425
  baton->tileSize = sharp::AttrAsUint32(options, "tileSize");
1392
1426
  baton->tileOverlap = sharp::AttrAsUint32(options, "tileOverlap");
1393
1427
  baton->tileAngle = sharp::AttrAsInt32(options, "tileAngle");
1394
- baton->tileBackground = sharp::AttrAsRgba(options, "tileBackground");
1428
+ baton->tileBackground = sharp::AttrAsVectorOfDouble(options, "tileBackground");
1395
1429
  baton->tileSkipBlanks = sharp::AttrAsInt32(options, "tileSkipBlanks");
1396
1430
  baton->tileContainer = static_cast<VipsForeignDzContainer>(
1397
1431
  vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_DZ_CONTAINER,
@@ -1403,6 +1437,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1403
1437
  baton->tileDepth = static_cast<VipsForeignDzDepth>(
1404
1438
  vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_DZ_DEPTH,
1405
1439
  sharp::AttrAsStr(options, "tileDepth").data()));
1440
+ baton->tileCentre = sharp::AttrAsBool(options, "tileCentre");
1406
1441
 
1407
1442
  // Force random access for certain operations
1408
1443
  if (baton->input->access == VIPS_ACCESS_SEQUENTIAL) {