canopycms-cdk 0.0.58 → 0.0.59

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.
Files changed (144) hide show
  1. package/dist/constructs/asset-support.d.ts +10 -4
  2. package/dist/constructs/asset-support.d.ts.map +1 -1
  3. package/dist/constructs/asset-support.js +4 -1
  4. package/dist/constructs/asset-support.js.map +1 -1
  5. package/dist/constructs/cms-distribution.d.ts +2 -0
  6. package/dist/constructs/cms-distribution.d.ts.map +1 -1
  7. package/dist/constructs/cms-distribution.js +39 -11
  8. package/dist/constructs/cms-distribution.js.map +1 -1
  9. package/dist/constructs/cms-service.d.ts +27 -2
  10. package/dist/constructs/cms-service.d.ts.map +1 -1
  11. package/dist/constructs/cms-service.js +62 -11
  12. package/dist/constructs/cms-service.js.map +1 -1
  13. package/lambda/asset-transform/dist/handler.js +30452 -0
  14. package/lambda/asset-transform/dist/node_modules/.package-lock.json +129 -0
  15. package/lambda/asset-transform/dist/node_modules/@img/colour/LICENSE.md +82 -0
  16. package/lambda/asset-transform/dist/node_modules/@img/colour/README.md +15 -0
  17. package/lambda/asset-transform/dist/node_modules/@img/colour/color.cjs +1596 -0
  18. package/lambda/asset-transform/dist/node_modules/@img/colour/index.cjs +1 -0
  19. package/lambda/asset-transform/dist/node_modules/@img/colour/index.d.ts +929 -0
  20. package/lambda/asset-transform/dist/node_modules/@img/colour/package.json +58 -0
  21. package/lambda/asset-transform/dist/node_modules/@img/sharp-libvips-linux-arm64/README.md +46 -0
  22. package/lambda/asset-transform/dist/node_modules/@img/sharp-libvips-linux-arm64/lib/glib-2.0/include/glibconfig.h +218 -0
  23. package/lambda/asset-transform/dist/node_modules/@img/sharp-libvips-linux-arm64/lib/index.js +1 -0
  24. package/lambda/asset-transform/dist/node_modules/@img/sharp-libvips-linux-arm64/lib/libvips-cpp.so.8.18.3 +0 -0
  25. package/lambda/asset-transform/dist/node_modules/@img/sharp-libvips-linux-arm64/package.json +43 -0
  26. package/lambda/asset-transform/dist/node_modules/@img/sharp-libvips-linux-arm64/versions.json +30 -0
  27. package/lambda/asset-transform/dist/node_modules/@img/sharp-linux-arm64/LICENSE +191 -0
  28. package/lambda/asset-transform/dist/node_modules/@img/sharp-linux-arm64/README.md +18 -0
  29. package/lambda/asset-transform/dist/node_modules/@img/sharp-linux-arm64/index.cjs +2 -0
  30. package/lambda/asset-transform/dist/node_modules/@img/sharp-linux-arm64/lib/sharp-linux-arm64-0.35.3.node +0 -0
  31. package/lambda/asset-transform/dist/node_modules/@img/sharp-linux-arm64/package.json +47 -0
  32. package/lambda/asset-transform/dist/node_modules/detect-libc/LICENSE +201 -0
  33. package/lambda/asset-transform/dist/node_modules/detect-libc/README.md +163 -0
  34. package/lambda/asset-transform/dist/node_modules/detect-libc/index.d.ts +14 -0
  35. package/lambda/asset-transform/dist/node_modules/detect-libc/lib/detect-libc.js +313 -0
  36. package/lambda/asset-transform/dist/node_modules/detect-libc/lib/elf.js +39 -0
  37. package/lambda/asset-transform/dist/node_modules/detect-libc/lib/filesystem.js +51 -0
  38. package/lambda/asset-transform/dist/node_modules/detect-libc/lib/process.js +24 -0
  39. package/lambda/asset-transform/dist/node_modules/detect-libc/package.json +44 -0
  40. package/lambda/asset-transform/dist/node_modules/semver/LICENSE +15 -0
  41. package/lambda/asset-transform/dist/node_modules/semver/README.md +680 -0
  42. package/lambda/asset-transform/dist/node_modules/semver/bin/semver.js +195 -0
  43. package/lambda/asset-transform/dist/node_modules/semver/classes/comparator.js +143 -0
  44. package/lambda/asset-transform/dist/node_modules/semver/classes/index.js +7 -0
  45. package/lambda/asset-transform/dist/node_modules/semver/classes/range.js +577 -0
  46. package/lambda/asset-transform/dist/node_modules/semver/classes/semver.js +350 -0
  47. package/lambda/asset-transform/dist/node_modules/semver/functions/clean.js +8 -0
  48. package/lambda/asset-transform/dist/node_modules/semver/functions/cmp.js +54 -0
  49. package/lambda/asset-transform/dist/node_modules/semver/functions/coerce.js +62 -0
  50. package/lambda/asset-transform/dist/node_modules/semver/functions/compare-build.js +9 -0
  51. package/lambda/asset-transform/dist/node_modules/semver/functions/compare-loose.js +5 -0
  52. package/lambda/asset-transform/dist/node_modules/semver/functions/compare.js +7 -0
  53. package/lambda/asset-transform/dist/node_modules/semver/functions/diff.js +60 -0
  54. package/lambda/asset-transform/dist/node_modules/semver/functions/eq.js +5 -0
  55. package/lambda/asset-transform/dist/node_modules/semver/functions/gt.js +5 -0
  56. package/lambda/asset-transform/dist/node_modules/semver/functions/gte.js +5 -0
  57. package/lambda/asset-transform/dist/node_modules/semver/functions/inc.js +21 -0
  58. package/lambda/asset-transform/dist/node_modules/semver/functions/lt.js +5 -0
  59. package/lambda/asset-transform/dist/node_modules/semver/functions/lte.js +5 -0
  60. package/lambda/asset-transform/dist/node_modules/semver/functions/major.js +5 -0
  61. package/lambda/asset-transform/dist/node_modules/semver/functions/minor.js +5 -0
  62. package/lambda/asset-transform/dist/node_modules/semver/functions/neq.js +5 -0
  63. package/lambda/asset-transform/dist/node_modules/semver/functions/parse.js +18 -0
  64. package/lambda/asset-transform/dist/node_modules/semver/functions/patch.js +5 -0
  65. package/lambda/asset-transform/dist/node_modules/semver/functions/prerelease.js +8 -0
  66. package/lambda/asset-transform/dist/node_modules/semver/functions/rcompare.js +5 -0
  67. package/lambda/asset-transform/dist/node_modules/semver/functions/rsort.js +5 -0
  68. package/lambda/asset-transform/dist/node_modules/semver/functions/satisfies.js +12 -0
  69. package/lambda/asset-transform/dist/node_modules/semver/functions/sort.js +5 -0
  70. package/lambda/asset-transform/dist/node_modules/semver/functions/truncate.js +48 -0
  71. package/lambda/asset-transform/dist/node_modules/semver/functions/valid.js +8 -0
  72. package/lambda/asset-transform/dist/node_modules/semver/index.js +93 -0
  73. package/lambda/asset-transform/dist/node_modules/semver/internal/constants.js +37 -0
  74. package/lambda/asset-transform/dist/node_modules/semver/internal/debug.js +11 -0
  75. package/lambda/asset-transform/dist/node_modules/semver/internal/identifiers.js +29 -0
  76. package/lambda/asset-transform/dist/node_modules/semver/internal/lrucache.js +42 -0
  77. package/lambda/asset-transform/dist/node_modules/semver/internal/parse-options.js +17 -0
  78. package/lambda/asset-transform/dist/node_modules/semver/internal/re.js +223 -0
  79. package/lambda/asset-transform/dist/node_modules/semver/package.json +78 -0
  80. package/lambda/asset-transform/dist/node_modules/semver/preload.js +4 -0
  81. package/lambda/asset-transform/dist/node_modules/semver/range.bnf +17 -0
  82. package/lambda/asset-transform/dist/node_modules/semver/ranges/gtr.js +6 -0
  83. package/lambda/asset-transform/dist/node_modules/semver/ranges/intersects.js +9 -0
  84. package/lambda/asset-transform/dist/node_modules/semver/ranges/ltr.js +6 -0
  85. package/lambda/asset-transform/dist/node_modules/semver/ranges/max-satisfying.js +27 -0
  86. package/lambda/asset-transform/dist/node_modules/semver/ranges/min-satisfying.js +26 -0
  87. package/lambda/asset-transform/dist/node_modules/semver/ranges/min-version.js +63 -0
  88. package/lambda/asset-transform/dist/node_modules/semver/ranges/outside.js +82 -0
  89. package/lambda/asset-transform/dist/node_modules/semver/ranges/simplify.js +49 -0
  90. package/lambda/asset-transform/dist/node_modules/semver/ranges/subset.js +249 -0
  91. package/lambda/asset-transform/dist/node_modules/semver/ranges/to-comparators.js +10 -0
  92. package/lambda/asset-transform/dist/node_modules/semver/ranges/valid.js +13 -0
  93. package/lambda/asset-transform/dist/node_modules/sharp/LICENSE +191 -0
  94. package/lambda/asset-transform/dist/node_modules/sharp/README.md +112 -0
  95. package/lambda/asset-transform/dist/node_modules/sharp/dist/channel.cjs +177 -0
  96. package/lambda/asset-transform/dist/node_modules/sharp/dist/channel.mjs +177 -0
  97. package/lambda/asset-transform/dist/node_modules/sharp/dist/colour.cjs +199 -0
  98. package/lambda/asset-transform/dist/node_modules/sharp/dist/colour.mjs +199 -0
  99. package/lambda/asset-transform/dist/node_modules/sharp/dist/composite.cjs +213 -0
  100. package/lambda/asset-transform/dist/node_modules/sharp/dist/composite.mjs +213 -0
  101. package/lambda/asset-transform/dist/node_modules/sharp/dist/constructor.cjs +511 -0
  102. package/lambda/asset-transform/dist/node_modules/sharp/dist/constructor.mjs +511 -0
  103. package/lambda/asset-transform/dist/node_modules/sharp/dist/index.cjs +25 -0
  104. package/lambda/asset-transform/dist/node_modules/sharp/dist/index.d.cts +1999 -0
  105. package/lambda/asset-transform/dist/node_modules/sharp/dist/index.d.mts +2046 -0
  106. package/lambda/asset-transform/dist/node_modules/sharp/dist/index.mjs +25 -0
  107. package/lambda/asset-transform/dist/node_modules/sharp/dist/input.cjs +814 -0
  108. package/lambda/asset-transform/dist/node_modules/sharp/dist/input.mjs +814 -0
  109. package/lambda/asset-transform/dist/node_modules/sharp/dist/is.cjs +143 -0
  110. package/lambda/asset-transform/dist/node_modules/sharp/dist/is.mjs +143 -0
  111. package/lambda/asset-transform/dist/node_modules/sharp/dist/libvips.cjs +212 -0
  112. package/lambda/asset-transform/dist/node_modules/sharp/dist/libvips.mjs +212 -0
  113. package/lambda/asset-transform/dist/node_modules/sharp/dist/operation.cjs +998 -0
  114. package/lambda/asset-transform/dist/node_modules/sharp/dist/operation.mjs +998 -0
  115. package/lambda/asset-transform/dist/node_modules/sharp/dist/output.cjs +1788 -0
  116. package/lambda/asset-transform/dist/node_modules/sharp/dist/output.mjs +1788 -0
  117. package/lambda/asset-transform/dist/node_modules/sharp/dist/resize.cjs +617 -0
  118. package/lambda/asset-transform/dist/node_modules/sharp/dist/resize.mjs +617 -0
  119. package/lambda/asset-transform/dist/node_modules/sharp/dist/sharp.cjs +174 -0
  120. package/lambda/asset-transform/dist/node_modules/sharp/dist/sharp.mjs +174 -0
  121. package/lambda/asset-transform/dist/node_modules/sharp/dist/utility.cjs +301 -0
  122. package/lambda/asset-transform/dist/node_modules/sharp/dist/utility.mjs +301 -0
  123. package/lambda/asset-transform/dist/node_modules/sharp/install/build.js +38 -0
  124. package/lambda/asset-transform/dist/node_modules/sharp/lib/index.d.ts +1999 -0
  125. package/lambda/asset-transform/dist/node_modules/sharp/package.json +227 -0
  126. package/lambda/asset-transform/dist/node_modules/sharp/src/binding.gyp +303 -0
  127. package/lambda/asset-transform/dist/node_modules/sharp/src/common.cc +1183 -0
  128. package/lambda/asset-transform/dist/node_modules/sharp/src/common.h +421 -0
  129. package/lambda/asset-transform/dist/node_modules/sharp/src/metadata.cc +404 -0
  130. package/lambda/asset-transform/dist/node_modules/sharp/src/metadata.h +95 -0
  131. package/lambda/asset-transform/dist/node_modules/sharp/src/operations.cc +516 -0
  132. package/lambda/asset-transform/dist/node_modules/sharp/src/operations.h +137 -0
  133. package/lambda/asset-transform/dist/node_modules/sharp/src/pipeline.cc +1948 -0
  134. package/lambda/asset-transform/dist/node_modules/sharp/src/pipeline.h +422 -0
  135. package/lambda/asset-transform/dist/node_modules/sharp/src/sharp.cc +43 -0
  136. package/lambda/asset-transform/dist/node_modules/sharp/src/stats.cc +186 -0
  137. package/lambda/asset-transform/dist/node_modules/sharp/src/stats.h +62 -0
  138. package/lambda/asset-transform/dist/node_modules/sharp/src/utilities.cc +289 -0
  139. package/lambda/asset-transform/dist/node_modules/sharp/src/utilities.h +22 -0
  140. package/lambda/asset-transform/dist/package.json +5 -0
  141. package/package.json +5 -4
  142. package/worker/canopy-worker.service +16 -2
  143. package/worker/dist/index.js +50573 -0
  144. package/LICENSE +0 -21
@@ -0,0 +1,1948 @@
1
+ /*!
2
+ Copyright 2013 Lovell Fuller and others.
3
+ SPDX-License-Identifier: Apache-2.0
4
+ */
5
+
6
+ #include <algorithm>
7
+ #include <cmath>
8
+ #include <filesystem> // NOLINT(build/c++17)
9
+ #include <map>
10
+ #include <memory>
11
+ #include <numeric>
12
+ #include <string>
13
+ #include <tuple>
14
+ #include <utility>
15
+ #include <vector>
16
+ #include <sys/types.h>
17
+ #include <sys/stat.h>
18
+
19
+ #include <vips/vips8>
20
+ #include <napi.h>
21
+
22
+ #include "./common.h"
23
+ #include "./operations.h"
24
+ #include "./pipeline.h"
25
+
26
+ class PipelineWorker : public Napi::AsyncWorker {
27
+ public:
28
+ PipelineWorker(Napi::Function callback, PipelineBaton *baton,
29
+ Napi::Function debuglog, Napi::Function queueListener) :
30
+ Napi::AsyncWorker(callback),
31
+ baton(baton),
32
+ debuglog(Napi::Persistent(debuglog)),
33
+ queueListener(Napi::Persistent(queueListener)) {}
34
+ ~PipelineWorker() {}
35
+
36
+ // libuv worker
37
+ void Execute() {
38
+ // Decrement queued task counter
39
+ sharp::counterQueue--;
40
+ // Increment processing task counter
41
+ sharp::counterProcess++;
42
+
43
+ try {
44
+ // Open input
45
+ vips::VImage image;
46
+ sharp::ImageType inputImageType;
47
+ if (baton->join.empty()) {
48
+ std::tie(image, inputImageType) = sharp::OpenInput(baton->input);
49
+ } else {
50
+ std::vector<VImage> images;
51
+ bool hasAlpha = false;
52
+ for (auto &join : baton->join) {
53
+ std::tie(image, inputImageType) = sharp::OpenInput(join);
54
+ image = sharp::EnsureColourspace(image, baton->colourspacePipeline);
55
+ images.push_back(image);
56
+ hasAlpha |= image.has_alpha();
57
+ }
58
+ if (hasAlpha) {
59
+ for (auto &image : images) {
60
+ if (!image.has_alpha()) {
61
+ image = sharp::EnsureAlpha(image, 1);
62
+ }
63
+ }
64
+ } else {
65
+ baton->input->joinBackground.pop_back();
66
+ }
67
+ inputImageType = sharp::ImageType::PNG;
68
+ image = VImage::arrayjoin(images, VImage::option()
69
+ ->set("across", baton->input->joinAcross)
70
+ ->set("shim", baton->input->joinShim)
71
+ ->set("background", baton->input->joinBackground)
72
+ ->set("halign", baton->input->joinHalign)
73
+ ->set("valign", baton->input->joinValign));
74
+ if (baton->input->joinAnimated) {
75
+ image = image.copy();
76
+ image.set(VIPS_META_N_PAGES, static_cast<int>(images.size()));
77
+ image.set(VIPS_META_PAGE_HEIGHT, static_cast<int>(image.height() / images.size()));
78
+ }
79
+ }
80
+ VipsAccess access = baton->input->access;
81
+ image = sharp::EnsureColourspace(image, baton->colourspacePipeline);
82
+
83
+ int nPages = baton->input->pages;
84
+ if (nPages == -1) {
85
+ // Resolve the number of pages if we need to render until the end of the document
86
+ nPages = image.get_typeof(VIPS_META_N_PAGES) != 0
87
+ ? image.get_int(VIPS_META_N_PAGES) - std::max(0, baton->input->page)
88
+ : 1;
89
+ }
90
+
91
+ // Get pre-resize page height
92
+ int pageHeight = sharp::GetPageHeight(image);
93
+
94
+ // Calculate angle of rotation
95
+ VipsAngle rotation = VIPS_ANGLE_D0;
96
+ VipsAngle autoRotation = VIPS_ANGLE_D0;
97
+ bool autoFlop = false;
98
+
99
+ if (baton->input->autoOrient) {
100
+ // Rotate and flip image according to Exif orientation
101
+ std::tie(autoRotation, autoFlop) = CalculateExifRotationAndFlop(sharp::ExifOrientation(image));
102
+ }
103
+
104
+ rotation = CalculateAngleRotation(baton->angle);
105
+
106
+ bool const shouldRotateBefore = baton->rotateBefore &&
107
+ (rotation != VIPS_ANGLE_D0 || baton->flip || baton->flop || baton->rotationAngle != 0.0);
108
+ bool const shouldOrientBefore = (shouldRotateBefore || baton->orientBefore) &&
109
+ (autoRotation != VIPS_ANGLE_D0 || autoFlop);
110
+
111
+ if (shouldOrientBefore) {
112
+ image = sharp::StaySequential(image, autoRotation != VIPS_ANGLE_D0);
113
+ if (autoRotation != VIPS_ANGLE_D0) {
114
+ if (autoRotation != VIPS_ANGLE_D180) {
115
+ MultiPageUnsupported(nPages, "Rotate");
116
+ }
117
+ image = image.rot(autoRotation);
118
+ autoRotation = VIPS_ANGLE_D0;
119
+ }
120
+ if (autoFlop) {
121
+ image = image.flip(VIPS_DIRECTION_HORIZONTAL);
122
+ autoFlop = false;
123
+ }
124
+ }
125
+
126
+ if (shouldRotateBefore) {
127
+ image = sharp::StaySequential(image, rotation != VIPS_ANGLE_D0 || baton->flip || baton->rotationAngle != 0.0);
128
+ if (baton->flip) {
129
+ image = image.flip(VIPS_DIRECTION_VERTICAL);
130
+ baton->flip = false;
131
+ }
132
+ if (baton->flop) {
133
+ image = image.flip(VIPS_DIRECTION_HORIZONTAL);
134
+ baton->flop = false;
135
+ }
136
+ if (rotation != VIPS_ANGLE_D0) {
137
+ if (rotation != VIPS_ANGLE_D180) {
138
+ MultiPageUnsupported(nPages, "Rotate");
139
+ }
140
+ image = image.rot(rotation);
141
+ rotation = VIPS_ANGLE_D0;
142
+ }
143
+ if (baton->rotationAngle != 0.0) {
144
+ MultiPageUnsupported(nPages, "Rotate");
145
+ std::vector<double> background;
146
+ std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground, false);
147
+ image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background)).copy_memory();
148
+ baton->rotationAngle = 0.0;
149
+ }
150
+ }
151
+
152
+ // Trim
153
+ if (baton->trimThreshold >= 0.0) {
154
+ MultiPageUnsupported(nPages, "Trim");
155
+ image = sharp::StaySequential(image);
156
+ image = sharp::Trim(image, baton->trimBackground, baton->trimThreshold, baton->trimLineArt, baton->trimMargin);
157
+ baton->trimOffsetLeft = image.xoffset();
158
+ baton->trimOffsetTop = image.yoffset();
159
+ }
160
+
161
+ // Pre extraction
162
+ if (baton->topOffsetPre != -1) {
163
+ image = nPages > 1
164
+ ? sharp::CropMultiPage(image,
165
+ baton->leftOffsetPre, baton->topOffsetPre, baton->widthPre, baton->heightPre, nPages, &pageHeight)
166
+ : image.extract_area(baton->leftOffsetPre, baton->topOffsetPre, baton->widthPre, baton->heightPre);
167
+ }
168
+
169
+ // Get pre-resize image width and height
170
+ int inputWidth = image.width();
171
+ int inputHeight = image.height();
172
+
173
+ // Is there just one page? Shrink to inputHeight instead
174
+ if (nPages == 1) {
175
+ pageHeight = inputHeight;
176
+ }
177
+
178
+ // Scaling calculations
179
+ double hshrink;
180
+ double vshrink;
181
+ int targetResizeWidth = baton->width;
182
+ int targetResizeHeight = baton->height;
183
+
184
+ // When auto-rotating by 90 or 270 degrees, swap the target width and
185
+ // height to ensure the behavior aligns with how it would have been if
186
+ // the rotation had taken place *before* resizing.
187
+ if (autoRotation == VIPS_ANGLE_D90 || autoRotation == VIPS_ANGLE_D270) {
188
+ std::swap(targetResizeWidth, targetResizeHeight);
189
+ }
190
+
191
+ // Shrink to pageHeight, so we work for multi-page images
192
+ std::tie(hshrink, vshrink) = sharp::ResolveShrink(
193
+ inputWidth, pageHeight, targetResizeWidth, targetResizeHeight,
194
+ baton->canvas, baton->withoutEnlargement, baton->withoutReduction);
195
+
196
+ // The jpeg preload shrink.
197
+ int jpegShrinkOnLoad = 1;
198
+
199
+ // WebP, PDF, SVG scale
200
+ double scale = 1.0;
201
+
202
+ // Try to reload input using shrink-on-load for JPEG, WebP, SVG and PDF, when:
203
+ // - the width or height parameters are specified;
204
+ // - gamma correction doesn't need to be applied;
205
+ // - trimming or pre-resize extract isn't required;
206
+ // - gain map processing is not required;
207
+ // - input colourspace is not specified;
208
+ bool const shouldPreShrink = (targetResizeWidth > 0 || targetResizeHeight > 0) &&
209
+ baton->gamma == 0 && baton->topOffsetPre == -1 && baton->trimThreshold < 0.0 &&
210
+ !baton->keepGainMap && !baton->withGainMap &&
211
+ baton->colourspacePipeline == VIPS_INTERPRETATION_LAST && !(shouldOrientBefore || shouldRotateBefore);
212
+
213
+ if (shouldPreShrink) {
214
+ // The common part of the shrink: the bit by which both axes must be shrunk
215
+ double shrink = std::min(hshrink, vshrink);
216
+
217
+ if (inputImageType == sharp::ImageType::JPEG) {
218
+ // Leave at least a factor of two for the final resize step, when fastShrinkOnLoad: false
219
+ // for more consistent results and to avoid extra sharpness to the image
220
+ int factor = baton->fastShrinkOnLoad ? 1 : 2;
221
+ if (shrink >= 8 * factor) {
222
+ jpegShrinkOnLoad = 8;
223
+ } else if (shrink >= 4 * factor) {
224
+ jpegShrinkOnLoad = 4;
225
+ } else if (shrink >= 2 * factor) {
226
+ jpegShrinkOnLoad = 2;
227
+ }
228
+ // Lower shrink-on-load for known libjpeg rounding errors
229
+ if (jpegShrinkOnLoad > 1 && static_cast<int>(shrink) == jpegShrinkOnLoad) {
230
+ jpegShrinkOnLoad /= 2;
231
+ }
232
+ } else if (inputImageType == sharp::ImageType::WEBP && baton->fastShrinkOnLoad && shrink > 1.0) {
233
+ // Avoid upscaling via webp
234
+ scale = 1.0 / shrink;
235
+ } else if (inputImageType == sharp::ImageType::SVG ||
236
+ inputImageType == sharp::ImageType::PDF) {
237
+ scale = 1.0 / shrink;
238
+ }
239
+ }
240
+
241
+ // Reload input using shrink-on-load, it'll be an integer shrink
242
+ // factor for jpegload*, a double scale factor for webpload*,
243
+ // pdfload* and svgload*
244
+ if (jpegShrinkOnLoad > 1) {
245
+ vips::VOption *option = GetOptionsForImageType(inputImageType, baton->input)->set("shrink", jpegShrinkOnLoad);
246
+ if (baton->input->buffer != nullptr) {
247
+ // Reload JPEG buffer
248
+ VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength);
249
+ image = VImage::jpegload_buffer(blob, option);
250
+ vips_area_unref(reinterpret_cast<VipsArea*>(blob));
251
+ } else {
252
+ // Reload JPEG file
253
+ image = VImage::jpegload(const_cast<char*>(baton->input->file.data()), option);
254
+ }
255
+ } else if (scale != 1.0) {
256
+ vips::VOption *option = GetOptionsForImageType(inputImageType, baton->input)->set("scale", scale);
257
+ if (inputImageType == sharp::ImageType::WEBP) {
258
+ if (baton->input->buffer != nullptr) {
259
+ // Reload WebP buffer
260
+ VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength);
261
+ image = VImage::webpload_buffer(blob, option);
262
+ vips_area_unref(reinterpret_cast<VipsArea*>(blob));
263
+ } else {
264
+ // Reload WebP file
265
+ image = VImage::webpload(const_cast<char*>(baton->input->file.data()), option);
266
+ }
267
+ } else if (inputImageType == sharp::ImageType::SVG) {
268
+ if (baton->input->buffer != nullptr) {
269
+ // Reload SVG buffer
270
+ VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength);
271
+ image = VImage::svgload_buffer(blob, option);
272
+ vips_area_unref(reinterpret_cast<VipsArea*>(blob));
273
+ } else {
274
+ // Reload SVG file
275
+ image = VImage::svgload(const_cast<char*>(baton->input->file.data()), option);
276
+ }
277
+ sharp::SetDensity(image, baton->input->density);
278
+ if (image.width() > 32767 || image.height() > 32767) {
279
+ throw std::runtime_error("Input SVG image will exceed 32767x32767 pixel limit when scaled");
280
+ }
281
+ } else if (inputImageType == sharp::ImageType::PDF) {
282
+ if (baton->input->buffer != nullptr) {
283
+ // Reload PDF buffer
284
+ VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength);
285
+ image = VImage::pdfload_buffer(blob, option);
286
+ vips_area_unref(reinterpret_cast<VipsArea*>(blob));
287
+ } else {
288
+ // Reload PDF file
289
+ image = VImage::pdfload(const_cast<char*>(baton->input->file.data()), option);
290
+ }
291
+ sharp::SetDensity(image, baton->input->density);
292
+ }
293
+ } else {
294
+ if (inputImageType == sharp::ImageType::SVG && (image.width() > 32767 || image.height() > 32767)) {
295
+ throw std::runtime_error("Input SVG image exceeds 32767x32767 pixel limit");
296
+ }
297
+ }
298
+ if (baton->input->autoOrient) {
299
+ image = sharp::RemoveExifOrientation(image);
300
+ }
301
+ VImage gainMap;
302
+ int gainMapScaleFactor = 1;
303
+ if (sharp::HasGainMap(image)) {
304
+ if (baton->keepGainMap) {
305
+ gainMap = image.gainmap();
306
+ if (image.get_typeof("gainmap-scale-factor") == G_TYPE_INT) {
307
+ gainMapScaleFactor = image.get_int("gainmap-scale-factor");
308
+ }
309
+ } else if (baton->withGainMap) {
310
+ image = image.uhdr2scRGB();
311
+ }
312
+ image = sharp::RemoveGainMap(image);
313
+ } else {
314
+ baton->keepGainMap = false;
315
+ baton->withGainMap = false;
316
+ }
317
+
318
+ // Any pre-shrinking may already have been done
319
+ inputWidth = image.width();
320
+ inputHeight = image.height();
321
+
322
+ // After pre-shrink, but before the main shrink stage
323
+ // Reuse the initial pageHeight if we didn't pre-shrink
324
+ if (shouldPreShrink) {
325
+ pageHeight = sharp::GetPageHeight(image);
326
+ }
327
+
328
+ // Shrink to pageHeight, so we work for multi-page images
329
+ std::tie(hshrink, vshrink) = sharp::ResolveShrink(
330
+ inputWidth, pageHeight, targetResizeWidth, targetResizeHeight,
331
+ baton->canvas, baton->withoutEnlargement, baton->withoutReduction);
332
+
333
+ int targetHeight = static_cast<int>(std::rint(static_cast<double>(pageHeight) / vshrink));
334
+ int targetPageHeight = targetHeight;
335
+
336
+ // In toilet-roll mode, we must adjust vshrink so that we exactly hit
337
+ // pageHeight or we'll have pixels straddling pixel boundaries
338
+ if (inputHeight > pageHeight) {
339
+ targetHeight *= nPages;
340
+ vshrink = static_cast<double>(inputHeight) / targetHeight;
341
+ }
342
+
343
+ // Ensure we're using a device-independent colour space
344
+ std::pair<char*, size_t> inputProfile(nullptr, 0);
345
+ if ((baton->keepMetadata & VIPS_FOREIGN_KEEP_ICC) && baton->withIccProfile.empty()) {
346
+ // Cache input profile for use with output
347
+ inputProfile = sharp::GetProfile(image);
348
+ baton->input->ignoreIcc = true;
349
+ }
350
+ char const *processingProfile = image.interpretation() == VIPS_INTERPRETATION_RGB16 ? "p3" : "srgb";
351
+ if (
352
+ sharp::HasProfile(image) &&
353
+ image.interpretation() != VIPS_INTERPRETATION_LABS &&
354
+ image.interpretation() != VIPS_INTERPRETATION_GREY16 &&
355
+ baton->colourspacePipeline != VIPS_INTERPRETATION_CMYK &&
356
+ !baton->input->ignoreIcc && !baton->withGainMap
357
+ ) {
358
+ // Convert to sRGB/P3 using embedded profile
359
+ try {
360
+ image = image.icc_transform(processingProfile, VImage::option()
361
+ ->set("embedded", true)
362
+ ->set("depth", sharp::Is16Bit(image.interpretation()) ? 16 : 8)
363
+ ->set("intent", VIPS_INTENT_PERCEPTUAL));
364
+ } catch(...) {
365
+ sharp::VipsWarningCallback(nullptr, G_LOG_LEVEL_WARNING, "Invalid embedded profile", nullptr);
366
+ }
367
+ } else if (
368
+ image.interpretation() == VIPS_INTERPRETATION_CMYK &&
369
+ baton->colourspacePipeline != VIPS_INTERPRETATION_CMYK
370
+ ) {
371
+ image = image.icc_transform(processingProfile, VImage::option()
372
+ ->set("input_profile", "cmyk")
373
+ ->set("intent", VIPS_INTENT_PERCEPTUAL));
374
+ }
375
+
376
+ // Flatten image to remove alpha channel
377
+ if (baton->flatten && image.has_alpha()) {
378
+ image = sharp::Flatten(image, baton->flattenBackground);
379
+ }
380
+
381
+ // Gamma encoding (darken)
382
+ if (baton->gamma >= 1 && baton->gamma <= 3) {
383
+ image = sharp::Gamma(image, 1.0 / baton->gamma);
384
+ }
385
+
386
+ // Convert to greyscale (linear, therefore after gamma encoding, if any)
387
+ if (baton->greyscale) {
388
+ image = image.colourspace(VIPS_INTERPRETATION_B_W);
389
+ }
390
+
391
+ bool const shouldResize = hshrink != 1.0 || vshrink != 1.0;
392
+ bool const shouldBlur = baton->blurSigma != 0.0;
393
+ bool const shouldConv = baton->convKernelWidth * baton->convKernelHeight > 0;
394
+ bool const shouldSharpen = baton->sharpenSigma != 0.0;
395
+ bool const shouldComposite = !baton->composite.empty();
396
+
397
+ if (shouldComposite && !image.has_alpha()) {
398
+ image = sharp::EnsureAlpha(image, 1);
399
+ }
400
+
401
+ VipsBandFormat premultiplyFormat = image.format();
402
+ bool const shouldPremultiplyAlpha = image.has_alpha() &&
403
+ (shouldResize || shouldBlur || shouldConv || shouldSharpen);
404
+
405
+ if (shouldPremultiplyAlpha) {
406
+ image = image.premultiply().cast(premultiplyFormat);
407
+ }
408
+
409
+ // Resize
410
+ if (shouldResize) {
411
+ image = image.resize(1.0 / hshrink, VImage::option()
412
+ ->set("vscale", 1.0 / vshrink)
413
+ ->set("kernel", baton->kernel));
414
+ if (baton->keepGainMap) {
415
+ gainMap = gainMap.resize(1.0 / hshrink, VImage::option()
416
+ ->set("vscale", 1.0 / vshrink)
417
+ ->set("kernel", baton->kernel));
418
+ }
419
+ }
420
+
421
+ image = sharp::StaySequential(image,
422
+ autoRotation != VIPS_ANGLE_D0 ||
423
+ baton->flip ||
424
+ rotation != VIPS_ANGLE_D0);
425
+ // Auto-rotate post-extract
426
+ if (autoRotation != VIPS_ANGLE_D0) {
427
+ if (autoRotation != VIPS_ANGLE_D180) {
428
+ MultiPageUnsupported(nPages, "Rotate");
429
+ }
430
+ image = image.rot(autoRotation);
431
+ if (baton->keepGainMap) {
432
+ gainMap = gainMap.rot(autoRotation);
433
+ }
434
+ }
435
+ // Mirror vertically (up-down) about the x-axis
436
+ if (baton->flip) {
437
+ image = image.flip(VIPS_DIRECTION_VERTICAL);
438
+ if (baton->keepGainMap) {
439
+ gainMap = gainMap.flip(VIPS_DIRECTION_VERTICAL);
440
+ }
441
+ }
442
+ // Mirror horizontally (left-right) about the y-axis
443
+ if (baton->flop != autoFlop) {
444
+ image = image.flip(VIPS_DIRECTION_HORIZONTAL);
445
+ if (baton->keepGainMap) {
446
+ gainMap = gainMap.flip(VIPS_DIRECTION_HORIZONTAL);
447
+ }
448
+ }
449
+ // Rotate post-extract 90-angle
450
+ if (rotation != VIPS_ANGLE_D0) {
451
+ if (rotation != VIPS_ANGLE_D180) {
452
+ MultiPageUnsupported(nPages, "Rotate");
453
+ }
454
+ image = image.rot(rotation);
455
+ if (baton->keepGainMap) {
456
+ gainMap = gainMap.rot(rotation);
457
+ }
458
+ }
459
+
460
+ // Join additional color channels to the image
461
+ if (!baton->joinChannelIn.empty()) {
462
+ VImage joinImage;
463
+ sharp::ImageType joinImageType = sharp::ImageType::UNKNOWN;
464
+
465
+ for (unsigned int i = 0; i < baton->joinChannelIn.size(); i++) {
466
+ baton->joinChannelIn[i]->access = access;
467
+ std::tie(joinImage, joinImageType) = sharp::OpenInput(baton->joinChannelIn[i]);
468
+ joinImage = sharp::EnsureColourspace(joinImage, baton->colourspacePipeline);
469
+ image = image.bandjoin(joinImage);
470
+ }
471
+ image = image.copy(VImage::option()->set("interpretation", baton->colourspace));
472
+ image = sharp::RemoveGifPalette(image);
473
+ }
474
+
475
+ inputWidth = image.width();
476
+ inputHeight = nPages > 1 ? targetPageHeight : image.height();
477
+
478
+ // Resolve dimensions
479
+ if (baton->width <= 0) {
480
+ baton->width = inputWidth;
481
+ }
482
+ if (baton->height <= 0) {
483
+ baton->height = inputHeight;
484
+ }
485
+
486
+ // Crop/embed
487
+ if (inputWidth != baton->width || inputHeight != baton->height) {
488
+ if (baton->canvas == sharp::Canvas::EMBED) {
489
+ std::vector<double> background;
490
+ std::tie(image, background) = sharp::ApplyAlpha(image, baton->resizeBackground, shouldPremultiplyAlpha);
491
+
492
+ // Embed
493
+ const auto& [left, top] = sharp::CalculateEmbedPosition(
494
+ inputWidth, inputHeight, baton->width, baton->height, baton->position);
495
+ const int width = std::max(inputWidth, baton->width);
496
+ const int height = std::max(inputHeight, baton->height);
497
+
498
+ image = nPages > 1
499
+ ? sharp::EmbedMultiPage(image,
500
+ left, top, width, height, VIPS_EXTEND_BACKGROUND, background, nPages, &targetPageHeight)
501
+ : image.embed(left, top, width, height, VImage::option()
502
+ ->set("extend", VIPS_EXTEND_BACKGROUND)
503
+ ->set("background", background));
504
+ if (baton->keepGainMap) {
505
+ gainMap = gainMap.embed(left / gainMapScaleFactor, top / gainMapScaleFactor,
506
+ width / gainMapScaleFactor, height / gainMapScaleFactor, VImage::option()
507
+ ->set("extend", VIPS_EXTEND_BACKGROUND)
508
+ ->set("background", 0));
509
+ }
510
+ } else if (baton->canvas == sharp::Canvas::CROP) {
511
+ if (baton->width > inputWidth) {
512
+ baton->width = inputWidth;
513
+ }
514
+ if (baton->height > inputHeight) {
515
+ baton->height = inputHeight;
516
+ }
517
+
518
+ // Crop
519
+ if (baton->position < 9) {
520
+ // Gravity-based crop
521
+ const auto& [left, top] = sharp::CalculateCrop(
522
+ inputWidth, inputHeight, baton->width, baton->height, baton->position);
523
+ const int width = std::min(inputWidth, baton->width);
524
+ const int height = std::min(inputHeight, baton->height);
525
+
526
+ image = nPages > 1
527
+ ? sharp::CropMultiPage(image,
528
+ left, top, width, height, nPages, &targetPageHeight)
529
+ : image.extract_area(left, top, width, height);
530
+ if (baton->keepGainMap) {
531
+ gainMap = gainMap.extract_area(left / gainMapScaleFactor, top / gainMapScaleFactor,
532
+ width / gainMapScaleFactor, height / gainMapScaleFactor);
533
+ }
534
+ } else {
535
+ int attention_x;
536
+ int attention_y;
537
+
538
+ // Attention-based or Entropy-based crop
539
+ MultiPageUnsupported(nPages, "Resize strategy");
540
+ KeepGainMapUnsupported(baton->keepGainMap, "Resize strategy");
541
+ image = sharp::StaySequential(image);
542
+ image = image.smartcrop(baton->width, baton->height, VImage::option()
543
+ ->set("interesting", baton->position == 16 ? VIPS_INTERESTING_ENTROPY : VIPS_INTERESTING_ATTENTION)
544
+ ->set("premultiplied", shouldPremultiplyAlpha)
545
+ ->set("attention_x", &attention_x)
546
+ ->set("attention_y", &attention_y));
547
+ baton->hasCropOffset = true;
548
+ baton->cropOffsetLeft = static_cast<int>(image.xoffset());
549
+ baton->cropOffsetTop = static_cast<int>(image.yoffset());
550
+ baton->hasAttentionCenter = true;
551
+ baton->attentionX = static_cast<int>(attention_x * jpegShrinkOnLoad / scale);
552
+ baton->attentionY = static_cast<int>(attention_y * jpegShrinkOnLoad / scale);
553
+ }
554
+ }
555
+ }
556
+
557
+ // Rotate post-extract non-90 angle
558
+ if (!baton->rotateBefore && baton->rotationAngle != 0.0) {
559
+ MultiPageUnsupported(nPages, "Rotate");
560
+ image = sharp::StaySequential(image);
561
+ std::vector<double> background;
562
+ std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground, shouldPremultiplyAlpha);
563
+ image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background));
564
+ if (baton->keepGainMap) {
565
+ gainMap = gainMap.rotate(baton->rotationAngle, VImage::option()->set("background", 0));
566
+ }
567
+ }
568
+
569
+ // Post extraction
570
+ if (baton->topOffsetPost != -1) {
571
+ if (nPages > 1) {
572
+ image = sharp::CropMultiPage(image,
573
+ baton->leftOffsetPost, baton->topOffsetPost, baton->widthPost, baton->heightPost,
574
+ nPages, &targetPageHeight);
575
+ // heightPost is used in the info object, so update to reflect the number of pages
576
+ baton->heightPost *= nPages;
577
+ } else {
578
+ image = image.extract_area(
579
+ baton->leftOffsetPost, baton->topOffsetPost, baton->widthPost, baton->heightPost);
580
+ if (baton->keepGainMap) {
581
+ gainMap = gainMap.extract_area(baton->leftOffsetPost / gainMapScaleFactor,
582
+ baton->topOffsetPost / gainMapScaleFactor, baton->widthPost / gainMapScaleFactor,
583
+ baton->heightPost / gainMapScaleFactor);
584
+ }
585
+ }
586
+ }
587
+
588
+ // Affine transform
589
+ if (!baton->affineMatrix.empty()) {
590
+ MultiPageUnsupported(nPages, "Affine");
591
+ KeepGainMapUnsupported(baton->keepGainMap, "Affine");
592
+ image = sharp::StaySequential(image);
593
+ std::vector<double> background;
594
+ std::tie(image, background) = sharp::ApplyAlpha(image, baton->affineBackground, shouldPremultiplyAlpha);
595
+ vips::VInterpolate interp = vips::VInterpolate::new_from_name(
596
+ const_cast<char*>(baton->affineInterpolator.data()));
597
+ image = image.affine(baton->affineMatrix, VImage::option()->set("background", background)
598
+ ->set("idx", baton->affineIdx)
599
+ ->set("idy", baton->affineIdy)
600
+ ->set("odx", baton->affineOdx)
601
+ ->set("ody", baton->affineOdy)
602
+ ->set("interpolate", interp));
603
+ }
604
+
605
+ // Extend edges
606
+ if (baton->extendTop > 0 || baton->extendBottom > 0 || baton->extendLeft > 0 || baton->extendRight > 0) {
607
+ // Embed
608
+ baton->width = image.width() + baton->extendLeft + baton->extendRight;
609
+ baton->height = (nPages > 1 ? targetPageHeight : image.height()) + baton->extendTop + baton->extendBottom;
610
+
611
+ std::vector<double> background;
612
+ if (baton->extendWith == VIPS_EXTEND_BACKGROUND) {
613
+ std::tie(image, background) = sharp::ApplyAlpha(image, baton->extendBackground, shouldPremultiplyAlpha);
614
+ }
615
+ image = sharp::StaySequential(image, nPages > 1 || baton->extendWith != VIPS_EXTEND_BACKGROUND);
616
+ auto options = VImage::option()->set("extend", baton->extendWith);
617
+ if (baton->extendWith == VIPS_EXTEND_BACKGROUND) {
618
+ options->set("background", background);
619
+ }
620
+ image = nPages > 1
621
+ ? sharp::EmbedMultiPage(image,
622
+ baton->extendLeft, baton->extendTop, baton->width, baton->height,
623
+ baton->extendWith, background, nPages, &targetPageHeight)
624
+ : image.embed(baton->extendLeft, baton->extendTop, baton->width, baton->height, options);
625
+ if (baton->keepGainMap) {
626
+ gainMap = gainMap.embed(baton->extendLeft / gainMapScaleFactor, baton->extendTop / gainMapScaleFactor,
627
+ baton->width / gainMapScaleFactor, baton->height / gainMapScaleFactor, VImage::option()
628
+ ->set("extend", baton->extendWith)
629
+ ->set("background", 0));
630
+ }
631
+ }
632
+ // Median - must happen before blurring, due to the utility of blurring after thresholding
633
+ if (baton->medianSize > 0) {
634
+ image = image.median(baton->medianSize);
635
+ }
636
+
637
+ // Threshold - must happen before blurring, due to the utility of blurring after thresholding
638
+ // Threshold - must happen before unflatten to enable non-white unflattening
639
+ if (baton->threshold != 0) {
640
+ image = sharp::Threshold(image, baton->threshold, baton->thresholdGrayscale);
641
+ }
642
+
643
+ // Dilate - must happen before blurring, due to the utility of dilating after thresholding
644
+ if (baton->dilateWidth != 0) {
645
+ image = sharp::Dilate(image, baton->dilateWidth);
646
+ }
647
+
648
+ // Erode - must happen before blurring, due to the utility of eroding after thresholding
649
+ if (baton->erodeWidth != 0) {
650
+ image = sharp::Erode(image, baton->erodeWidth);
651
+ }
652
+
653
+ // Blur
654
+ if (shouldBlur) {
655
+ image = sharp::Blur(image, baton->blurSigma, baton->precision, baton->minAmpl);
656
+ if (baton->keepGainMap) {
657
+ gainMap = sharp::Blur(gainMap, baton->blurSigma, baton->precision, baton->minAmpl);
658
+ }
659
+ }
660
+
661
+ // Unflatten the image
662
+ if (baton->unflatten) {
663
+ image = sharp::Unflatten(image);
664
+ }
665
+
666
+ // Convolve
667
+ if (shouldConv) {
668
+ KeepGainMapUnsupported(baton->keepGainMap, "Convolve");
669
+ image = sharp::Convolve(image,
670
+ baton->convKernelWidth, baton->convKernelHeight,
671
+ baton->convKernelScale, baton->convKernelOffset,
672
+ baton->convKernel);
673
+ }
674
+
675
+ // Recomb
676
+ if (!baton->recombMatrix.empty()) {
677
+ KeepGainMapUnsupported(baton->keepGainMap, "Recomb");
678
+ image = sharp::Recomb(image, baton->recombMatrix);
679
+ }
680
+
681
+ // Modulate
682
+ if (baton->brightness != 1.0 || baton->saturation != 1.0 || baton->hue != 0.0 || baton->lightness != 0.0) {
683
+ KeepGainMapUnsupported(baton->keepGainMap, "Modulate");
684
+ image = sharp::Modulate(image, baton->brightness, baton->saturation, baton->hue, baton->lightness);
685
+ }
686
+
687
+ // Sharpen
688
+ if (shouldSharpen) {
689
+ image = sharp::Sharpen(image, baton->sharpenSigma, baton->sharpenM1, baton->sharpenM2,
690
+ baton->sharpenX1, baton->sharpenY2, baton->sharpenY3);
691
+ }
692
+
693
+ // Reverse premultiplication after all transformations
694
+ if (shouldPremultiplyAlpha) {
695
+ image = image.unpremultiply().cast(premultiplyFormat);
696
+ }
697
+ baton->premultiplied = shouldPremultiplyAlpha;
698
+
699
+ // Composite
700
+ if (shouldComposite) {
701
+ KeepGainMapUnsupported(baton->keepGainMap, "Composite");
702
+ std::vector<VImage> images = { image };
703
+ std::vector<int> modes, xs, ys;
704
+ for (Composite *composite : baton->composite) {
705
+ VImage compositeImage;
706
+ sharp::ImageType compositeImageType = sharp::ImageType::UNKNOWN;
707
+ composite->input->access = access;
708
+ std::tie(compositeImage, compositeImageType) = sharp::OpenInput(composite->input);
709
+
710
+ if (composite->input->autoOrient) {
711
+ // Respect EXIF Orientation
712
+ VipsAngle compositeAutoRotation = VIPS_ANGLE_D0;
713
+ bool compositeAutoFlop = false;
714
+ std::tie(compositeAutoRotation, compositeAutoFlop) =
715
+ CalculateExifRotationAndFlop(sharp::ExifOrientation(compositeImage));
716
+
717
+ compositeImage = sharp::RemoveExifOrientation(compositeImage);
718
+ compositeImage = sharp::StaySequential(compositeImage, compositeAutoRotation != VIPS_ANGLE_D0);
719
+
720
+ if (compositeAutoRotation != VIPS_ANGLE_D0) {
721
+ compositeImage = compositeImage.rot(compositeAutoRotation);
722
+ }
723
+ if (compositeAutoFlop) {
724
+ compositeImage = compositeImage.flip(VIPS_DIRECTION_HORIZONTAL);
725
+ }
726
+ }
727
+
728
+ // Verify within current dimensions
729
+ if (compositeImage.width() > image.width() || compositeImage.height() > image.height()) {
730
+ throw std::runtime_error("Image to composite must have same dimensions or smaller");
731
+ }
732
+ // Check if overlay is tiled
733
+ if (composite->tile) {
734
+ int across = 0;
735
+ int down = 0;
736
+ // Use gravity in overlay
737
+ if (compositeImage.width() <= image.width()) {
738
+ across = static_cast<int>(ceil(static_cast<double>(image.width()) / compositeImage.width()));
739
+ // Ensure odd number of tiles across when gravity is centre, north or south
740
+ if (composite->gravity == 0 || composite->gravity == 1 || composite->gravity == 3) {
741
+ across |= 1;
742
+ }
743
+ }
744
+ if (compositeImage.height() <= image.height()) {
745
+ down = static_cast<int>(ceil(static_cast<double>(image.height()) / compositeImage.height()));
746
+ // Ensure odd number of tiles down when gravity is centre, east or west
747
+ if (composite->gravity == 0 || composite->gravity == 2 || composite->gravity == 4) {
748
+ down |= 1;
749
+ }
750
+ }
751
+ if (across != 0 || down != 0) {
752
+ int left;
753
+ int top;
754
+ compositeImage = sharp::StaySequential(compositeImage).replicate(across, down);
755
+ if (composite->hasOffset) {
756
+ std::tie(left, top) = sharp::CalculateCrop(
757
+ compositeImage.width(), compositeImage.height(), image.width(), image.height(),
758
+ composite->left, composite->top);
759
+ } else {
760
+ std::tie(left, top) = sharp::CalculateCrop(
761
+ compositeImage.width(), compositeImage.height(), image.width(), image.height(), composite->gravity);
762
+ }
763
+ compositeImage = compositeImage.extract_area(left, top, image.width(), image.height());
764
+ }
765
+ // gravity was used for extract_area, set it back to its default value of 0
766
+ composite->gravity = 0;
767
+ }
768
+ // Ensure image to composite is with unpremultiplied alpha
769
+ compositeImage = sharp::EnsureAlpha(compositeImage, 1);
770
+ if (composite->premultiplied) compositeImage = compositeImage.unpremultiply();
771
+ // Calculate position
772
+ int left;
773
+ int top;
774
+ if (composite->hasOffset) {
775
+ // Composite image at given offsets
776
+ if (composite->tile) {
777
+ std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),
778
+ compositeImage.width(), compositeImage.height(), composite->left, composite->top);
779
+ } else {
780
+ left = composite->left;
781
+ top = composite->top;
782
+ }
783
+ } else {
784
+ // Composite image with given gravity
785
+ std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),
786
+ compositeImage.width(), compositeImage.height(), composite->gravity);
787
+ }
788
+ images.push_back(compositeImage);
789
+ modes.push_back(composite->mode);
790
+ xs.push_back(left);
791
+ ys.push_back(top);
792
+ }
793
+ image = VImage::composite(images, modes, VImage::option()
794
+ ->set("compositing_space", baton->colourspacePipeline == VIPS_INTERPRETATION_LAST
795
+ ? VIPS_INTERPRETATION_sRGB
796
+ : baton->colourspacePipeline)
797
+ ->set("x", xs)
798
+ ->set("y", ys));
799
+ image = sharp::RemoveGifPalette(image);
800
+ }
801
+
802
+ // Gamma decoding (brighten)
803
+ if (baton->gammaOut >= 1 && baton->gammaOut <= 3) {
804
+ image = sharp::Gamma(image, baton->gammaOut);
805
+ }
806
+
807
+ // Linear adjustment (a * in + b)
808
+ if (!baton->linearA.empty()) {
809
+ image = sharp::Linear(image, baton->linearA, baton->linearB);
810
+ }
811
+
812
+ // Apply normalisation - stretch luminance to cover full dynamic range
813
+ if (baton->normalise) {
814
+ KeepGainMapUnsupported(baton->keepGainMap, "Normalise");
815
+ image = sharp::StaySequential(image);
816
+ image = sharp::Normalise(image, baton->normaliseLower, baton->normaliseUpper);
817
+ }
818
+
819
+ // Apply contrast limiting adaptive histogram equalization (CLAHE)
820
+ if (baton->claheWidth != 0 && baton->claheHeight != 0) {
821
+ KeepGainMapUnsupported(baton->keepGainMap, "Clahe");
822
+ image = sharp::StaySequential(image);
823
+ image = sharp::Clahe(image, baton->claheWidth, baton->claheHeight, baton->claheMaxSlope);
824
+ }
825
+
826
+ // Apply bitwise boolean operation between images
827
+ if (baton->boolean != nullptr) {
828
+ KeepGainMapUnsupported(baton->keepGainMap, "Boolean");
829
+ VImage booleanImage;
830
+ sharp::ImageType booleanImageType = sharp::ImageType::UNKNOWN;
831
+ baton->boolean->access = access;
832
+ std::tie(booleanImage, booleanImageType) = sharp::OpenInput(baton->boolean);
833
+ booleanImage = sharp::EnsureColourspace(booleanImage, baton->colourspacePipeline);
834
+ image = sharp::Boolean(image, booleanImage, baton->booleanOp);
835
+ image = sharp::RemoveGifPalette(image);
836
+ }
837
+
838
+ // Apply per-channel Bandbool bitwise operations after all other operations
839
+ if (baton->bandBoolOp >= VIPS_OPERATION_BOOLEAN_AND && baton->bandBoolOp < VIPS_OPERATION_BOOLEAN_LAST) {
840
+ image = sharp::Bandbool(image, baton->bandBoolOp);
841
+ }
842
+
843
+ // Tint the image
844
+ if (baton->tint[0] >= 0.0) {
845
+ image = sharp::Tint(image, baton->tint);
846
+ }
847
+
848
+ // Remove alpha channel, if any
849
+ if (baton->removeAlpha) {
850
+ image = sharp::RemoveAlpha(image);
851
+ }
852
+
853
+ // Ensure alpha channel, if missing
854
+ if (baton->ensureAlpha != -1) {
855
+ image = sharp::EnsureAlpha(image, baton->ensureAlpha);
856
+ }
857
+
858
+ // Ensure output colour space
859
+ if (sharp::Is16Bit(image.interpretation())) {
860
+ image = image.cast(VIPS_FORMAT_USHORT);
861
+ }
862
+ if (image.interpretation() != baton->colourspace) {
863
+ image = image.colourspace(baton->colourspace, VImage::option()->set("source_space", image.interpretation()));
864
+ if (inputProfile.first != nullptr && baton->withIccProfile.empty()) {
865
+ image = sharp::SetProfile(image, inputProfile);
866
+ }
867
+ }
868
+
869
+ // Extract channel
870
+ if (baton->extractChannel > -1) {
871
+ KeepGainMapUnsupported(baton->keepGainMap, "Extract channel");
872
+ if (baton->extractChannel >= image.bands()) {
873
+ if (baton->extractChannel == 3 && image.has_alpha()) {
874
+ baton->extractChannel = image.bands() - 1;
875
+ } else {
876
+ (baton->err)
877
+ .append("Cannot extract channel ").append(std::to_string(baton->extractChannel))
878
+ .append(" from image with channels 0-").append(std::to_string(image.bands() - 1));
879
+ return Error();
880
+ }
881
+ }
882
+ VipsInterpretation colourspace = sharp::Is16Bit(image.interpretation())
883
+ ? VIPS_INTERPRETATION_GREY16
884
+ : VIPS_INTERPRETATION_B_W;
885
+ image = image
886
+ .extract_band(baton->extractChannel)
887
+ .copy(VImage::option()->set("interpretation", colourspace));
888
+ }
889
+
890
+ // Apply output ICC profile
891
+ if (!baton->withIccProfile.empty()) {
892
+ try {
893
+ image = image.icc_transform(const_cast<char*>(baton->withIccProfile.data()), VImage::option()
894
+ ->set("input_profile", processingProfile)
895
+ ->set("embedded", true)
896
+ ->set("depth", sharp::Is16Bit(image.interpretation()) ? 16 : 8)
897
+ ->set("intent", VIPS_INTENT_PERCEPTUAL));
898
+ } catch(...) {
899
+ sharp::VipsWarningCallback(nullptr, G_LOG_LEVEL_WARNING, "Invalid profile", nullptr);
900
+ }
901
+ }
902
+
903
+ // Negate the colours in the image
904
+ if (baton->negate) {
905
+ image = sharp::Negate(image, baton->negateAlpha);
906
+ if (baton->keepGainMap) {
907
+ gainMap = sharp::Negate(gainMap, false);
908
+ }
909
+ }
910
+
911
+ // Override EXIF Orientation tag
912
+ if (baton->withMetadataOrientation != -1) {
913
+ image = sharp::SetExifOrientation(image, baton->withMetadataOrientation);
914
+ }
915
+ // Override pixel density
916
+ if (baton->withMetadataDensity > 0) {
917
+ image = sharp::SetDensity(image, baton->withMetadataDensity);
918
+ }
919
+ // EXIF key/value pairs
920
+ if (baton->keepMetadata & VIPS_FOREIGN_KEEP_EXIF) {
921
+ image = image.copy();
922
+ if (!baton->withExifMerge) {
923
+ image = sharp::RemoveExif(image);
924
+ }
925
+ for (const auto& [key, value] : baton->withExif) {
926
+ image.set(key.c_str(), value.c_str());
927
+ }
928
+ }
929
+ // XMP buffer
930
+ if ((baton->keepMetadata & VIPS_FOREIGN_KEEP_XMP) && !baton->withXmp.empty()) {
931
+ image = image.copy();
932
+ image.set(VIPS_META_XMP_NAME, nullptr,
933
+ const_cast<void*>(static_cast<void const*>(baton->withXmp.c_str())), baton->withXmp.size());
934
+ }
935
+ // Number of channels used in output image
936
+ baton->channels = image.bands();
937
+ baton->width = image.width();
938
+ baton->height = image.height();
939
+
940
+ image = sharp::SetAnimationProperties(
941
+ image, nPages, targetPageHeight, baton->delay, baton->loop);
942
+
943
+ if (image.get_typeof(VIPS_META_PAGE_HEIGHT) == G_TYPE_INT) {
944
+ baton->pageHeightOut = image.get_int(VIPS_META_PAGE_HEIGHT);
945
+ baton->pagesOut = image.get_int(VIPS_META_N_PAGES);
946
+ }
947
+ baton->hasAlphaOut = image.has_alpha();
948
+
949
+ // Output
950
+ sharp::SetTimeout(image, baton->timeoutSeconds);
951
+ if (baton->fileOut.empty()) {
952
+ // Buffer output
953
+ if (baton->formatOut == "jpeg" || (baton->formatOut == "input" && inputImageType == sharp::ImageType::JPEG)) {
954
+ // Write JPEG to buffer
955
+ sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
956
+ VipsArea *area;
957
+ if (baton->keepGainMap) {
958
+ image = ReattachGainMap(image, gainMap, baton);
959
+ area = reinterpret_cast<VipsArea*>(image.uhdrsave_buffer(VImage::option()
960
+ ->set("keep", baton->keepMetadata)
961
+ ->set("Q", baton->jpegQuality)
962
+ ->set("gainmap_scale_factor", gainMapScaleFactor)));
963
+ } else {
964
+ area = reinterpret_cast<VipsArea*>(image.jpegsave_buffer(VImage::option()
965
+ ->set("keep", baton->keepMetadata)
966
+ ->set("Q", baton->jpegQuality)
967
+ ->set("interlace", baton->jpegProgressive)
968
+ ->set("subsample_mode", baton->jpegChromaSubsampling == "4:4:4"
969
+ ? VIPS_FOREIGN_SUBSAMPLE_OFF
970
+ : VIPS_FOREIGN_SUBSAMPLE_ON)
971
+ ->set("trellis_quant", baton->jpegTrellisQuantisation)
972
+ ->set("quant_table", baton->jpegQuantisationTable)
973
+ ->set("overshoot_deringing", baton->jpegOvershootDeringing)
974
+ ->set("optimize_scans", baton->jpegOptimiseScans)
975
+ ->set("optimize_coding", baton->jpegOptimiseCoding)));
976
+ }
977
+ baton->bufferOut = static_cast<char*>(area->data);
978
+ baton->bufferOutLength = area->length;
979
+ area->free_fn = nullptr;
980
+ vips_area_unref(area);
981
+ baton->formatOut = "jpeg";
982
+ if (baton->colourspace == VIPS_INTERPRETATION_CMYK) {
983
+ baton->channels = std::min(baton->channels, 4);
984
+ } else {
985
+ baton->channels = std::min(baton->channels, 3);
986
+ }
987
+ baton->hasAlphaOut = false;
988
+ } else if (baton->formatOut == "jp2" || (baton->formatOut == "input"
989
+ && inputImageType == sharp::ImageType::JP2)) {
990
+ // Write JP2 to Buffer
991
+ sharp::AssertImageTypeDimensions(image, sharp::ImageType::JP2);
992
+ VipsArea *area = reinterpret_cast<VipsArea*>(image.jp2ksave_buffer(VImage::option()
993
+ ->set("Q", baton->jp2Quality)
994
+ ->set("lossless", baton->jp2Lossless)
995
+ ->set("subsample_mode", baton->jp2ChromaSubsampling == "4:4:4"
996
+ ? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
997
+ ->set("tile_height", baton->jp2TileHeight)
998
+ ->set("tile_width", baton->jp2TileWidth)));
999
+ baton->bufferOut = static_cast<char*>(area->data);
1000
+ baton->bufferOutLength = area->length;
1001
+ area->free_fn = nullptr;
1002
+ vips_area_unref(area);
1003
+ baton->formatOut = "jp2";
1004
+ } else if (baton->formatOut == "png" || (baton->formatOut == "input" &&
1005
+ (inputImageType == sharp::ImageType::PNG || inputImageType == sharp::ImageType::SVG))) {
1006
+ // Write PNG to buffer
1007
+ sharp::AssertImageTypeDimensions(image, sharp::ImageType::PNG);
1008
+ VipsArea *area = reinterpret_cast<VipsArea*>(image.pngsave_buffer(VImage::option()
1009
+ ->set("keep", baton->keepMetadata)
1010
+ ->set("interlace", baton->pngProgressive)
1011
+ ->set("compression", baton->pngCompressionLevel)
1012
+ ->set("filter", baton->pngAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_ALL : VIPS_FOREIGN_PNG_FILTER_NONE)
1013
+ ->set("palette", baton->pngPalette)
1014
+ ->set("Q", baton->pngQuality)
1015
+ ->set("effort", baton->pngEffort)
1016
+ ->set("bitdepth", sharp::Is16Bit(image.interpretation()) ? 16 : baton->pngBitdepth)
1017
+ ->set("dither", baton->pngDither)));
1018
+ baton->bufferOut = static_cast<char*>(area->data);
1019
+ baton->bufferOutLength = area->length;
1020
+ area->free_fn = nullptr;
1021
+ vips_area_unref(area);
1022
+ baton->formatOut = "png";
1023
+ } else if (baton->formatOut == "webp" ||
1024
+ (baton->formatOut == "input" && inputImageType == sharp::ImageType::WEBP)) {
1025
+ // Write WEBP to buffer
1026
+ sharp::AssertImageTypeDimensions(image, sharp::ImageType::WEBP);
1027
+ VipsArea *area = reinterpret_cast<VipsArea*>(image.webpsave_buffer(VImage::option()
1028
+ ->set("keep", baton->keepMetadata)
1029
+ ->set("Q", baton->webpQuality)
1030
+ ->set("lossless", baton->webpLossless)
1031
+ ->set("near_lossless", baton->webpNearLossless)
1032
+ ->set("smart_subsample", baton->webpSmartSubsample)
1033
+ ->set("smart_deblock", baton->webpSmartDeblock)
1034
+ ->set("preset", baton->webpPreset)
1035
+ ->set("effort", baton->webpEffort)
1036
+ ->set("min_size", baton->webpMinSize)
1037
+ ->set("mixed", baton->webpMixed)
1038
+ ->set("exact", baton->webpExact)
1039
+ ->set("alpha_q", baton->webpAlphaQuality)));
1040
+ baton->bufferOut = static_cast<char*>(area->data);
1041
+ baton->bufferOutLength = area->length;
1042
+ area->free_fn = nullptr;
1043
+ vips_area_unref(area);
1044
+ baton->formatOut = "webp";
1045
+ } else if (baton->formatOut == "gif" ||
1046
+ (baton->formatOut == "input" && inputImageType == sharp::ImageType::GIF)) {
1047
+ // Write GIF to buffer
1048
+ sharp::AssertImageTypeDimensions(image, sharp::ImageType::GIF);
1049
+ VipsArea *area = reinterpret_cast<VipsArea*>(image.gifsave_buffer(VImage::option()
1050
+ ->set("keep", baton->keepMetadata)
1051
+ ->set("bitdepth", baton->gifBitdepth)
1052
+ ->set("effort", baton->gifEffort)
1053
+ ->set("reuse", baton->gifReuse)
1054
+ ->set("interlace", baton->gifProgressive)
1055
+ ->set("interframe_maxerror", baton->gifInterFrameMaxError)
1056
+ ->set("interpalette_maxerror", baton->gifInterPaletteMaxError)
1057
+ ->set("keep_duplicate_frames", baton->gifKeepDuplicateFrames)
1058
+ ->set("dither", baton->gifDither)));
1059
+ baton->bufferOut = static_cast<char*>(area->data);
1060
+ baton->bufferOutLength = area->length;
1061
+ area->free_fn = nullptr;
1062
+ vips_area_unref(area);
1063
+ baton->formatOut = "gif";
1064
+ } else if (baton->formatOut == "tiff" ||
1065
+ (baton->formatOut == "input" && inputImageType == sharp::ImageType::TIFF)) {
1066
+ // Write TIFF to buffer
1067
+ if (baton->tiffCompression == VIPS_FOREIGN_TIFF_COMPRESSION_JPEG) {
1068
+ sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
1069
+ baton->channels = std::min(baton->channels, 3);
1070
+ baton->hasAlphaOut = false;
1071
+ }
1072
+ // Cast pixel values to float, if required
1073
+ if (baton->tiffPredictor == VIPS_FOREIGN_TIFF_PREDICTOR_FLOAT) {
1074
+ image = image.cast(VIPS_FORMAT_FLOAT);
1075
+ }
1076
+ VipsArea *area = reinterpret_cast<VipsArea*>(image.tiffsave_buffer(VImage::option()
1077
+ ->set("keep", baton->keepMetadata)
1078
+ ->set("Q", baton->tiffQuality)
1079
+ ->set("bitdepth", baton->tiffBitdepth)
1080
+ ->set("compression", baton->tiffCompression)
1081
+ ->set("bigtiff", baton->tiffBigtiff)
1082
+ ->set("miniswhite", baton->tiffMiniswhite)
1083
+ ->set("predictor", baton->tiffPredictor)
1084
+ ->set("pyramid", baton->tiffPyramid)
1085
+ ->set("tile", baton->tiffTile)
1086
+ ->set("tile_height", baton->tiffTileHeight)
1087
+ ->set("tile_width", baton->tiffTileWidth)
1088
+ ->set("xres", baton->tiffXres)
1089
+ ->set("yres", baton->tiffYres)
1090
+ ->set("resunit", baton->tiffResolutionUnit)));
1091
+ baton->bufferOut = static_cast<char*>(area->data);
1092
+ baton->bufferOutLength = area->length;
1093
+ area->free_fn = nullptr;
1094
+ vips_area_unref(area);
1095
+ baton->formatOut = "tiff";
1096
+ } else if (baton->formatOut == "heif" ||
1097
+ (baton->formatOut == "input" && inputImageType == sharp::ImageType::HEIF)) {
1098
+ // Write HEIF to buffer
1099
+ sharp::AssertImageTypeDimensions(image, sharp::ImageType::HEIF);
1100
+ image = sharp::RemoveAnimationProperties(image);
1101
+ VipsArea *area = reinterpret_cast<VipsArea*>(image.heifsave_buffer(VImage::option()
1102
+ ->set("keep", baton->keepMetadata)
1103
+ ->set("Q", baton->heifQuality)
1104
+ ->set("compression", baton->heifCompression)
1105
+ ->set("effort", baton->heifEffort)
1106
+ ->set("bitdepth", baton->heifBitdepth)
1107
+ ->set("tune", baton->heifTune.c_str())
1108
+ ->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
1109
+ ? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
1110
+ ->set("lossless", baton->heifLossless)));
1111
+ baton->bufferOut = static_cast<char*>(area->data);
1112
+ baton->bufferOutLength = area->length;
1113
+ area->free_fn = nullptr;
1114
+ vips_area_unref(area);
1115
+ baton->formatOut = "heif";
1116
+ } else if (baton->formatOut == "dz") {
1117
+ // Write DZ to buffer
1118
+ baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP;
1119
+ if (!image.has_alpha()) {
1120
+ baton->tileBackground.pop_back();
1121
+ }
1122
+ image = sharp::StaySequential(image, baton->tileAngle != 0);
1123
+ vips::VOption *options = BuildOptionsDZ(baton);
1124
+ VipsArea *area = reinterpret_cast<VipsArea*>(image.dzsave_buffer(options));
1125
+ baton->bufferOut = static_cast<char*>(area->data);
1126
+ baton->bufferOutLength = area->length;
1127
+ area->free_fn = nullptr;
1128
+ vips_area_unref(area);
1129
+ baton->formatOut = "dz";
1130
+ if (baton->tileFormat == "jpeg") {
1131
+ baton->hasAlphaOut = false;
1132
+ }
1133
+ } else if (baton->formatOut == "jxl" ||
1134
+ (baton->formatOut == "input" && inputImageType == sharp::ImageType::JXL)) {
1135
+ // Write JXL to buffer
1136
+ image = sharp::RemoveAnimationProperties(image);
1137
+ VipsArea *area = reinterpret_cast<VipsArea*>(image.jxlsave_buffer(VImage::option()
1138
+ ->set("keep", baton->keepMetadata)
1139
+ ->set("distance", baton->jxlDistance)
1140
+ ->set("tier", baton->jxlDecodingTier)
1141
+ ->set("effort", baton->jxlEffort)
1142
+ ->set("lossless", baton->jxlLossless)));
1143
+ baton->bufferOut = static_cast<char*>(area->data);
1144
+ baton->bufferOutLength = area->length;
1145
+ area->free_fn = nullptr;
1146
+ vips_area_unref(area);
1147
+ baton->formatOut = "jxl";
1148
+ } else if (baton->formatOut == "raw" ||
1149
+ (baton->formatOut == "input" && inputImageType == sharp::ImageType::RAW)) {
1150
+ // Write raw, uncompressed image data to buffer
1151
+ if (baton->greyscale || image.interpretation() == VIPS_INTERPRETATION_B_W) {
1152
+ // Extract first band for greyscale image
1153
+ image = image[0];
1154
+ baton->channels = 1;
1155
+ }
1156
+ if (image.format() != baton->rawDepth) {
1157
+ // Cast pixels to requested format
1158
+ image = image.cast(baton->rawDepth);
1159
+ }
1160
+ // Get raw image data
1161
+ baton->bufferOut = static_cast<char*>(image.write_to_memory(&baton->bufferOutLength));
1162
+ if (baton->bufferOut == nullptr) {
1163
+ throw std::runtime_error("Could not allocate enough memory for raw output");
1164
+ }
1165
+ baton->formatOut = "raw";
1166
+ } else {
1167
+ // Unsupported output format
1168
+ auto unsupported = std::string("Unsupported output format ");
1169
+ if (baton->formatOut == "input") {
1170
+ unsupported.append("when trying to match input format of ");
1171
+ unsupported.append(ImageTypeId(inputImageType));
1172
+ } else {
1173
+ unsupported.append(baton->formatOut);
1174
+ }
1175
+ throw std::runtime_error(unsupported);
1176
+ }
1177
+ } else {
1178
+ // File output
1179
+ bool const isJpeg = sharp::IsJpeg(baton->fileOut);
1180
+ bool const isPng = sharp::IsPng(baton->fileOut);
1181
+ bool const isWebp = sharp::IsWebp(baton->fileOut);
1182
+ bool const isGif = sharp::IsGif(baton->fileOut);
1183
+ bool const isTiff = sharp::IsTiff(baton->fileOut);
1184
+ bool const isJp2 = sharp::IsJp2(baton->fileOut);
1185
+ bool const isHeif = sharp::IsHeif(baton->fileOut);
1186
+ bool const isJxl = sharp::IsJxl(baton->fileOut);
1187
+ bool const isDz = sharp::IsDz(baton->fileOut);
1188
+ bool const isDzZip = sharp::IsDzZip(baton->fileOut);
1189
+ bool const isV = sharp::IsV(baton->fileOut);
1190
+ bool const mightMatchInput = baton->formatOut == "input";
1191
+ bool const willMatchInput = mightMatchInput &&
1192
+ !(isJpeg || isPng || isWebp || isGif || isTiff || isJp2 || isHeif || isDz || isDzZip || isV);
1193
+
1194
+ if (baton->formatOut == "jpeg" || (mightMatchInput && isJpeg) ||
1195
+ (willMatchInput && inputImageType == sharp::ImageType::JPEG)) {
1196
+ // Write JPEG to file
1197
+ sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
1198
+ if (baton->keepGainMap) {
1199
+ image = ReattachGainMap(image, gainMap, baton);
1200
+ image.uhdrsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
1201
+ ->set("keep", baton->keepMetadata)
1202
+ ->set("Q", baton->jpegQuality)
1203
+ ->set("gainmap_scale_factor", gainMapScaleFactor));
1204
+ } else {
1205
+ image.jpegsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
1206
+ ->set("keep", baton->keepMetadata)
1207
+ ->set("Q", baton->jpegQuality)
1208
+ ->set("interlace", baton->jpegProgressive)
1209
+ ->set("subsample_mode", baton->jpegChromaSubsampling == "4:4:4"
1210
+ ? VIPS_FOREIGN_SUBSAMPLE_OFF
1211
+ : VIPS_FOREIGN_SUBSAMPLE_ON)
1212
+ ->set("trellis_quant", baton->jpegTrellisQuantisation)
1213
+ ->set("quant_table", baton->jpegQuantisationTable)
1214
+ ->set("overshoot_deringing", baton->jpegOvershootDeringing)
1215
+ ->set("optimize_scans", baton->jpegOptimiseScans)
1216
+ ->set("optimize_coding", baton->jpegOptimiseCoding));
1217
+ }
1218
+ baton->formatOut = "jpeg";
1219
+ baton->channels = std::min(baton->channels, 3);
1220
+ baton->hasAlphaOut = false;
1221
+ } else if (baton->formatOut == "jp2" || (mightMatchInput && isJp2) ||
1222
+ (willMatchInput && (inputImageType == sharp::ImageType::JP2))) {
1223
+ // Write JP2 to file
1224
+ sharp::AssertImageTypeDimensions(image, sharp::ImageType::JP2);
1225
+ image.jp2ksave(const_cast<char*>(baton->fileOut.data()), VImage::option()
1226
+ ->set("Q", baton->jp2Quality)
1227
+ ->set("lossless", baton->jp2Lossless)
1228
+ ->set("subsample_mode", baton->jp2ChromaSubsampling == "4:4:4"
1229
+ ? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
1230
+ ->set("tile_height", baton->jp2TileHeight)
1231
+ ->set("tile_width", baton->jp2TileWidth));
1232
+ baton->formatOut = "jp2";
1233
+ } else if (baton->formatOut == "png" || (mightMatchInput && isPng) || (willMatchInput &&
1234
+ (inputImageType == sharp::ImageType::PNG || inputImageType == sharp::ImageType::SVG))) {
1235
+ // Write PNG to file
1236
+ sharp::AssertImageTypeDimensions(image, sharp::ImageType::PNG);
1237
+ image.pngsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
1238
+ ->set("keep", baton->keepMetadata)
1239
+ ->set("interlace", baton->pngProgressive)
1240
+ ->set("compression", baton->pngCompressionLevel)
1241
+ ->set("filter", baton->pngAdaptiveFiltering ? VIPS_FOREIGN_PNG_FILTER_ALL : VIPS_FOREIGN_PNG_FILTER_NONE)
1242
+ ->set("palette", baton->pngPalette)
1243
+ ->set("Q", baton->pngQuality)
1244
+ ->set("bitdepth", sharp::Is16Bit(image.interpretation()) ? 16 : baton->pngBitdepth)
1245
+ ->set("effort", baton->pngEffort)
1246
+ ->set("dither", baton->pngDither));
1247
+ baton->formatOut = "png";
1248
+ } else if (baton->formatOut == "webp" || (mightMatchInput && isWebp) ||
1249
+ (willMatchInput && inputImageType == sharp::ImageType::WEBP)) {
1250
+ // Write WEBP to file
1251
+ sharp::AssertImageTypeDimensions(image, sharp::ImageType::WEBP);
1252
+ image.webpsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
1253
+ ->set("keep", baton->keepMetadata)
1254
+ ->set("Q", baton->webpQuality)
1255
+ ->set("lossless", baton->webpLossless)
1256
+ ->set("near_lossless", baton->webpNearLossless)
1257
+ ->set("smart_subsample", baton->webpSmartSubsample)
1258
+ ->set("smart_deblock", baton->webpSmartDeblock)
1259
+ ->set("preset", baton->webpPreset)
1260
+ ->set("effort", baton->webpEffort)
1261
+ ->set("min_size", baton->webpMinSize)
1262
+ ->set("mixed", baton->webpMixed)
1263
+ ->set("exact", baton->webpExact)
1264
+ ->set("alpha_q", baton->webpAlphaQuality));
1265
+ baton->formatOut = "webp";
1266
+ } else if (baton->formatOut == "gif" || (mightMatchInput && isGif) ||
1267
+ (willMatchInput && inputImageType == sharp::ImageType::GIF)) {
1268
+ // Write GIF to file
1269
+ sharp::AssertImageTypeDimensions(image, sharp::ImageType::GIF);
1270
+ image.gifsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
1271
+ ->set("keep", baton->keepMetadata)
1272
+ ->set("bitdepth", baton->gifBitdepth)
1273
+ ->set("effort", baton->gifEffort)
1274
+ ->set("reuse", baton->gifReuse)
1275
+ ->set("interlace", baton->gifProgressive)
1276
+ ->set("interframe_maxerror", baton->gifInterFrameMaxError)
1277
+ ->set("interpalette_maxerror", baton->gifInterPaletteMaxError)
1278
+ ->set("keep_duplicate_frames", baton->gifKeepDuplicateFrames)
1279
+ ->set("dither", baton->gifDither));
1280
+ baton->formatOut = "gif";
1281
+ } else if (baton->formatOut == "tiff" || (mightMatchInput && isTiff) ||
1282
+ (willMatchInput && inputImageType == sharp::ImageType::TIFF)) {
1283
+ // Write TIFF to file
1284
+ if (baton->tiffCompression == VIPS_FOREIGN_TIFF_COMPRESSION_JPEG) {
1285
+ sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
1286
+ baton->channels = std::min(baton->channels, 3);
1287
+ baton->hasAlphaOut = false;
1288
+ }
1289
+ // Cast pixel values to float, if required
1290
+ if (baton->tiffPredictor == VIPS_FOREIGN_TIFF_PREDICTOR_FLOAT) {
1291
+ image = image.cast(VIPS_FORMAT_FLOAT);
1292
+ }
1293
+ image.tiffsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
1294
+ ->set("keep", baton->keepMetadata)
1295
+ ->set("Q", baton->tiffQuality)
1296
+ ->set("bitdepth", baton->tiffBitdepth)
1297
+ ->set("compression", baton->tiffCompression)
1298
+ ->set("bigtiff", baton->tiffBigtiff)
1299
+ ->set("miniswhite", baton->tiffMiniswhite)
1300
+ ->set("predictor", baton->tiffPredictor)
1301
+ ->set("pyramid", baton->tiffPyramid)
1302
+ ->set("tile", baton->tiffTile)
1303
+ ->set("tile_height", baton->tiffTileHeight)
1304
+ ->set("tile_width", baton->tiffTileWidth)
1305
+ ->set("xres", baton->tiffXres)
1306
+ ->set("yres", baton->tiffYres)
1307
+ ->set("resunit", baton->tiffResolutionUnit));
1308
+ baton->formatOut = "tiff";
1309
+ } else if (baton->formatOut == "heif" || (mightMatchInput && isHeif) ||
1310
+ (willMatchInput && inputImageType == sharp::ImageType::HEIF)) {
1311
+ // Write HEIF to file
1312
+ sharp::AssertImageTypeDimensions(image, sharp::ImageType::HEIF);
1313
+ image = sharp::RemoveAnimationProperties(image);
1314
+ image.heifsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
1315
+ ->set("keep", baton->keepMetadata)
1316
+ ->set("Q", baton->heifQuality)
1317
+ ->set("compression", baton->heifCompression)
1318
+ ->set("effort", baton->heifEffort)
1319
+ ->set("bitdepth", baton->heifBitdepth)
1320
+ ->set("tune", baton->heifTune.c_str())
1321
+ ->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
1322
+ ? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
1323
+ ->set("lossless", baton->heifLossless));
1324
+ baton->formatOut = "heif";
1325
+ } else if (baton->formatOut == "jxl" || (mightMatchInput && isJxl) ||
1326
+ (willMatchInput && inputImageType == sharp::ImageType::JXL)) {
1327
+ // Write JXL to file
1328
+ image = sharp::RemoveAnimationProperties(image);
1329
+ image.jxlsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
1330
+ ->set("keep", baton->keepMetadata)
1331
+ ->set("distance", baton->jxlDistance)
1332
+ ->set("tier", baton->jxlDecodingTier)
1333
+ ->set("effort", baton->jxlEffort)
1334
+ ->set("lossless", baton->jxlLossless));
1335
+ baton->formatOut = "jxl";
1336
+ } else if (baton->formatOut == "dz" || isDz || isDzZip) {
1337
+ // Write DZ to file
1338
+ if (isDzZip) {
1339
+ baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP;
1340
+ }
1341
+ if (!image.has_alpha()) {
1342
+ baton->tileBackground.pop_back();
1343
+ }
1344
+ image = sharp::StaySequential(image, baton->tileAngle != 0);
1345
+ vips::VOption *options = BuildOptionsDZ(baton);
1346
+ image.dzsave(const_cast<char*>(baton->fileOut.data()), options);
1347
+ baton->formatOut = "dz";
1348
+ if (baton->tileFormat == "jpeg") {
1349
+ baton->hasAlphaOut = false;
1350
+ }
1351
+ } else if (baton->formatOut == "v" || (mightMatchInput && isV) ||
1352
+ (willMatchInput && inputImageType == sharp::ImageType::VIPS)) {
1353
+ // Write V to file
1354
+ image.vipssave(const_cast<char*>(baton->fileOut.data()), VImage::option()
1355
+ ->set("keep", baton->keepMetadata));
1356
+ baton->formatOut = "v";
1357
+ } else {
1358
+ // Unsupported output format
1359
+ (baton->err).append("Unsupported output format " + baton->fileOut);
1360
+ return Error();
1361
+ }
1362
+ }
1363
+ } catch (std::runtime_error const &err) {
1364
+ char const *what = err.what();
1365
+ if (what && what[0]) {
1366
+ (baton->err).append(what);
1367
+ } else {
1368
+ if (baton->input->failOn == VIPS_FAIL_ON_WARNING) {
1369
+ (baton->err).append("Warning treated as error due to failOn setting");
1370
+ baton->errUseWarning = true;
1371
+ } else {
1372
+ (baton->err).append("Unknown error");
1373
+ }
1374
+ }
1375
+ }
1376
+ // Clean up libvips' per-request data and threads
1377
+ vips_error_clear();
1378
+ vips_thread_shutdown();
1379
+ }
1380
+
1381
+ void OnOK() {
1382
+ Napi::Env env = Env();
1383
+ Napi::HandleScope scope(env);
1384
+
1385
+ // Handle warnings
1386
+ std::string warning = sharp::VipsWarningPop();
1387
+ while (!warning.empty()) {
1388
+ if (baton->errUseWarning) {
1389
+ (baton->err).append("\n").append(warning);
1390
+ } else {
1391
+ debuglog.SHARP_CALLBACK_FN_NAME(Receiver().Value(), { Napi::String::New(env, warning) });
1392
+ }
1393
+ warning = sharp::VipsWarningPop();
1394
+ }
1395
+ if (baton->err.empty()) {
1396
+ int width = baton->width;
1397
+ int height = baton->height;
1398
+ if (baton->topOffsetPre != -1 && (baton->width == -1 || baton->height == -1)) {
1399
+ width = baton->widthPre;
1400
+ height = baton->heightPre;
1401
+ }
1402
+ if (baton->topOffsetPost != -1) {
1403
+ width = baton->widthPost;
1404
+ height = baton->heightPost;
1405
+ }
1406
+ // Info Object
1407
+ Napi::Object info = Napi::Object::New(env);
1408
+ info.Set("format", baton->formatOut);
1409
+ info.Set("width", static_cast<uint32_t>(width));
1410
+ info.Set("height", static_cast<uint32_t>(height));
1411
+ info.Set("channels", static_cast<uint32_t>(baton->channels));
1412
+ if (baton->formatOut == "raw") {
1413
+ info.Set("depth", vips_enum_nick(VIPS_TYPE_BAND_FORMAT, baton->rawDepth));
1414
+ }
1415
+ info.Set("premultiplied", baton->premultiplied);
1416
+ if (baton->hasCropOffset) {
1417
+ info.Set("cropOffsetLeft", static_cast<int32_t>(baton->cropOffsetLeft));
1418
+ info.Set("cropOffsetTop", static_cast<int32_t>(baton->cropOffsetTop));
1419
+ }
1420
+ if (baton->hasAttentionCenter) {
1421
+ info.Set("attentionX", static_cast<int32_t>(baton->attentionX));
1422
+ info.Set("attentionY", static_cast<int32_t>(baton->attentionY));
1423
+ }
1424
+ if (baton->trimThreshold >= 0.0) {
1425
+ info.Set("trimOffsetLeft", static_cast<int32_t>(baton->trimOffsetLeft));
1426
+ info.Set("trimOffsetTop", static_cast<int32_t>(baton->trimOffsetTop));
1427
+ }
1428
+ if (baton->input->textAutofitDpi) {
1429
+ info.Set("textAutofitDpi", static_cast<uint32_t>(baton->input->textAutofitDpi));
1430
+ }
1431
+ if (baton->pageHeightOut) {
1432
+ info.Set("pageHeight", static_cast<int32_t>(baton->pageHeightOut));
1433
+ info.Set("pages", static_cast<int32_t>(baton->pagesOut));
1434
+ }
1435
+ info.Set("hasAlpha", baton->hasAlphaOut);
1436
+
1437
+ if (baton->bufferOutLength > 0) {
1438
+ info.Set("size", static_cast<uint32_t>(baton->bufferOutLength));
1439
+ if (baton->typedArrayOut) {
1440
+ // ECMAScript ArrayBuffer with Uint8Array view
1441
+ Napi::TypedArrayOf<uint8_t> data = Napi::Buffer<char>::Copy(env,
1442
+ static_cast<char*>(baton->bufferOut), baton->bufferOutLength);
1443
+ sharp::FreeCallback(static_cast<char*>(baton->bufferOut), nullptr);
1444
+ Callback().SHARP_CALLBACK_FN_NAME(Receiver().Value(), { env.Null(), data, info });
1445
+ } else {
1446
+ // Node.js Buffer
1447
+ Napi::Buffer<char> data = Napi::Buffer<char>::NewOrCopy(env, static_cast<char*>(baton->bufferOut),
1448
+ baton->bufferOutLength, sharp::FreeCallback);
1449
+ Callback().SHARP_CALLBACK_FN_NAME(Receiver().Value(), { env.Null(), data, info });
1450
+ }
1451
+ } else {
1452
+ // Add file size to info
1453
+ if (baton->formatOut != "dz" || sharp::IsDzZip(baton->fileOut)) {
1454
+ try {
1455
+ uint32_t const size = static_cast<uint32_t>(
1456
+ std::filesystem::file_size(std::filesystem::u8path(baton->fileOut)));
1457
+ info.Set("size", size);
1458
+ } catch (...) {}
1459
+ }
1460
+ Callback().SHARP_CALLBACK_FN_NAME(Receiver().Value(), { env.Null(), info });
1461
+ }
1462
+ } else {
1463
+ Callback().SHARP_CALLBACK_FN_NAME(Receiver().Value(),
1464
+ { Napi::Error::New(env, sharp::TrimEnd(baton->err)).Value() });
1465
+ }
1466
+
1467
+ // Delete baton
1468
+ delete baton->input;
1469
+ delete baton->boolean;
1470
+ for (Composite *composite : baton->composite) {
1471
+ delete composite->input;
1472
+ delete composite;
1473
+ }
1474
+ for (sharp::InputDescriptor *input : baton->joinChannelIn) {
1475
+ delete input;
1476
+ }
1477
+ for (sharp::InputDescriptor *input : baton->join) {
1478
+ delete input;
1479
+ }
1480
+ delete baton;
1481
+
1482
+ // Decrement processing task counter
1483
+ sharp::counterProcess--;
1484
+ Napi::Number queueLength = Napi::Number::New(env, static_cast<int>(sharp::counterQueue));
1485
+ queueListener.SHARP_CALLBACK_FN_NAME(Receiver().Value(), { queueLength });
1486
+ }
1487
+
1488
+ private:
1489
+ PipelineBaton *baton;
1490
+ Napi::FunctionReference debuglog;
1491
+ Napi::FunctionReference queueListener;
1492
+
1493
+ void MultiPageUnsupported(int const pages, std::string op) {
1494
+ if (pages > 1) {
1495
+ throw std::runtime_error(op + " is not supported for multi-page images");
1496
+ }
1497
+ }
1498
+
1499
+ void KeepGainMapUnsupported(bool const keepGainMap, std::string op) {
1500
+ if (keepGainMap) {
1501
+ throw std::runtime_error(op + " is not supported when keeping gain maps");
1502
+ }
1503
+ }
1504
+
1505
+ /*
1506
+ Calculate the angle of rotation and need-to-flip for the given Exif orientation
1507
+ By default, returns zero, i.e. no rotation.
1508
+ */
1509
+ std::tuple<VipsAngle, bool>
1510
+ CalculateExifRotationAndFlop(int const exifOrientation) {
1511
+ VipsAngle rotate = VIPS_ANGLE_D0;
1512
+ bool flop = false;
1513
+ switch (exifOrientation) {
1514
+ case 6: rotate = VIPS_ANGLE_D90; break;
1515
+ case 3: rotate = VIPS_ANGLE_D180; break;
1516
+ case 8: rotate = VIPS_ANGLE_D270; break;
1517
+ case 2: flop = true; break;
1518
+ case 7: flop = true; rotate = VIPS_ANGLE_D270; break;
1519
+ case 4: flop = true; rotate = VIPS_ANGLE_D180; break;
1520
+ case 5: flop = true; rotate = VIPS_ANGLE_D90; break;
1521
+ }
1522
+ return std::make_tuple(rotate, flop);
1523
+ }
1524
+
1525
+ /*
1526
+ Calculate the rotation for the given angle.
1527
+ Supports any positive or negative angle that is a multiple of 90.
1528
+ */
1529
+ VipsAngle
1530
+ CalculateAngleRotation(int angle) {
1531
+ angle = angle % 360;
1532
+ if (angle < 0)
1533
+ angle = 360 + angle;
1534
+ switch (angle) {
1535
+ case 90: return VIPS_ANGLE_D90;
1536
+ case 180: return VIPS_ANGLE_D180;
1537
+ case 270: return VIPS_ANGLE_D270;
1538
+ }
1539
+ return VIPS_ANGLE_D0;
1540
+ }
1541
+
1542
+ /*
1543
+ Assemble the suffix argument to dzsave, which is the format (by extname)
1544
+ alongside comma-separated arguments to the corresponding `formatsave` vips
1545
+ action.
1546
+ */
1547
+ std::string
1548
+ AssembleSuffixString(std::string extname, std::vector<std::pair<std::string, std::string>> options) {
1549
+ std::string argument;
1550
+ for (const auto& [key, value] : options) {
1551
+ if (!argument.empty()) {
1552
+ argument += ",";
1553
+ }
1554
+ argument += key + "=" + value;
1555
+ }
1556
+ return extname + "[" + argument + "]";
1557
+ }
1558
+
1559
+ /*
1560
+ Build VOption for dzsave
1561
+ */
1562
+ vips::VOption*
1563
+ BuildOptionsDZ(PipelineBaton *baton) {
1564
+ // Forward format options through suffix
1565
+ std::string suffix;
1566
+ if (baton->tileFormat == "png") {
1567
+ std::vector<std::pair<std::string, std::string>> options {
1568
+ {"interlace", baton->pngProgressive ? "true" : "false"},
1569
+ {"compression", std::to_string(baton->pngCompressionLevel)},
1570
+ {"filter", baton->pngAdaptiveFiltering ? "all" : "none"}
1571
+ };
1572
+ suffix = AssembleSuffixString(".png", options);
1573
+ } else if (baton->tileFormat == "webp") {
1574
+ std::vector<std::pair<std::string, std::string>> options {
1575
+ {"Q", std::to_string(baton->webpQuality)},
1576
+ {"alpha_q", std::to_string(baton->webpAlphaQuality)},
1577
+ {"lossless", baton->webpLossless ? "true" : "false"},
1578
+ {"near_lossless", baton->webpNearLossless ? "true" : "false"},
1579
+ {"smart_subsample", baton->webpSmartSubsample ? "true" : "false"},
1580
+ {"smart_deblock", baton->webpSmartDeblock ? "true" : "false"},
1581
+ {"preset", vips_enum_nick(VIPS_TYPE_FOREIGN_WEBP_PRESET, baton->webpPreset)},
1582
+ {"min_size", baton->webpMinSize ? "true" : "false"},
1583
+ {"mixed", baton->webpMixed ? "true" : "false"},
1584
+ {"exact", baton->webpExact ? "true" : "false"},
1585
+ {"effort", std::to_string(baton->webpEffort)}
1586
+ };
1587
+ suffix = AssembleSuffixString(".webp", options);
1588
+ } else {
1589
+ std::vector<std::pair<std::string, std::string>> options {
1590
+ {"Q", std::to_string(baton->jpegQuality)},
1591
+ {"interlace", baton->jpegProgressive ? "true" : "false"},
1592
+ {"subsample_mode", baton->jpegChromaSubsampling == "4:4:4" ? "off" : "on"},
1593
+ {"trellis_quant", baton->jpegTrellisQuantisation ? "true" : "false"},
1594
+ {"quant_table", std::to_string(baton->jpegQuantisationTable)},
1595
+ {"overshoot_deringing", baton->jpegOvershootDeringing ? "true": "false"},
1596
+ {"optimize_scans", baton->jpegOptimiseScans ? "true": "false"},
1597
+ {"optimize_coding", baton->jpegOptimiseCoding ? "true": "false"}
1598
+ };
1599
+ std::string extname = baton->tileLayout == VIPS_FOREIGN_DZ_LAYOUT_DZ ? ".jpeg" : ".jpg";
1600
+ suffix = AssembleSuffixString(extname, options);
1601
+ }
1602
+ vips::VOption *options = VImage::option()
1603
+ ->set("keep", baton->keepMetadata)
1604
+ ->set("tile_size", baton->tileSize)
1605
+ ->set("overlap", baton->tileOverlap)
1606
+ ->set("container", baton->tileContainer)
1607
+ ->set("layout", baton->tileLayout)
1608
+ ->set("suffix", const_cast<char*>(suffix.data()))
1609
+ ->set("angle", CalculateAngleRotation(baton->tileAngle))
1610
+ ->set("background", baton->tileBackground)
1611
+ ->set("centre", baton->tileCentre)
1612
+ ->set("id", const_cast<char*>(baton->tileId.data()))
1613
+ ->set("skip_blanks", baton->tileSkipBlanks);
1614
+ if (baton->tileDepth < VIPS_FOREIGN_DZ_DEPTH_LAST) {
1615
+ options->set("depth", baton->tileDepth);
1616
+ }
1617
+ if (!baton->tileBasename.empty()) {
1618
+ options->set("basename", const_cast<char*>(baton->tileBasename.data()));
1619
+ }
1620
+ return options;
1621
+ }
1622
+
1623
+ VImage ReattachGainMap(VImage image, VImage gainMap, PipelineBaton *baton) {
1624
+ VipsArea *gainMapJpeg = reinterpret_cast<VipsArea*>(gainMap.jpegsave_buffer(VImage::option()
1625
+ ->set("keep", FALSE)
1626
+ ->set("Q", baton->jpegQuality)
1627
+ ->set("subsample_mode", VIPS_FOREIGN_SUBSAMPLE_OFF)
1628
+ ->set("trellis_quant", baton->jpegTrellisQuantisation)
1629
+ ->set("quant_table", baton->jpegQuantisationTable)
1630
+ ->set("overshoot_deringing", baton->jpegOvershootDeringing)
1631
+ ->set("optimize_coding", baton->jpegOptimiseCoding)));
1632
+ image = image.copy();
1633
+ image.set("gainmap-data", reinterpret_cast<VipsCallbackFn>(vips_area_free_cb),
1634
+ gainMapJpeg->data, gainMapJpeg->length);
1635
+ return image;
1636
+ }
1637
+
1638
+ /*
1639
+ Clear all thread-local data.
1640
+ */
1641
+ void Error() {
1642
+ // Clean up libvips' per-request data and threads
1643
+ vips_error_clear();
1644
+ vips_thread_shutdown();
1645
+ }
1646
+ };
1647
+
1648
+ /*
1649
+ pipeline(options, output, callback)
1650
+ */
1651
+ Napi::Value pipeline(const Napi::CallbackInfo& info) {
1652
+ // V8 objects are converted to non-V8 types held in the baton struct
1653
+ PipelineBaton *baton = new PipelineBaton;
1654
+ Napi::Object options = info[size_t(0)].As<Napi::Object>();
1655
+
1656
+ // Input
1657
+ baton->input = sharp::CreateInputDescriptor(options.Get("input").As<Napi::Object>());
1658
+ // Join images together
1659
+ if (sharp::HasAttr(options, "join")) {
1660
+ Napi::Array join = options.Get("join").As<Napi::Array>();
1661
+ for (unsigned int i = 0; i < join.Length(); i++) {
1662
+ baton->join.push_back(
1663
+ sharp::CreateInputDescriptor(join.Get(i).As<Napi::Object>()));
1664
+ }
1665
+ }
1666
+ // Extract image options
1667
+ baton->topOffsetPre = sharp::AttrAsInt32(options, "topOffsetPre");
1668
+ baton->leftOffsetPre = sharp::AttrAsInt32(options, "leftOffsetPre");
1669
+ baton->widthPre = sharp::AttrAsInt32(options, "widthPre");
1670
+ baton->heightPre = sharp::AttrAsInt32(options, "heightPre");
1671
+ baton->topOffsetPost = sharp::AttrAsInt32(options, "topOffsetPost");
1672
+ baton->leftOffsetPost = sharp::AttrAsInt32(options, "leftOffsetPost");
1673
+ baton->widthPost = sharp::AttrAsInt32(options, "widthPost");
1674
+ baton->heightPost = sharp::AttrAsInt32(options, "heightPost");
1675
+ // Output image dimensions
1676
+ baton->width = sharp::AttrAsInt32(options, "width");
1677
+ baton->height = sharp::AttrAsInt32(options, "height");
1678
+ // Canvas option
1679
+ std::string canvas = sharp::AttrAsStr(options, "canvas");
1680
+ if (canvas == "crop") {
1681
+ baton->canvas = sharp::Canvas::CROP;
1682
+ } else if (canvas == "embed") {
1683
+ baton->canvas = sharp::Canvas::EMBED;
1684
+ } else if (canvas == "max") {
1685
+ baton->canvas = sharp::Canvas::MAX;
1686
+ } else if (canvas == "min") {
1687
+ baton->canvas = sharp::Canvas::MIN;
1688
+ } else if (canvas == "ignore_aspect") {
1689
+ baton->canvas = sharp::Canvas::IGNORE_ASPECT;
1690
+ }
1691
+ // Composite
1692
+ Napi::Array compositeArray = options.Get("composite").As<Napi::Array>();
1693
+ for (unsigned int i = 0; i < compositeArray.Length(); i++) {
1694
+ Napi::Object compositeObject = compositeArray.Get(i).As<Napi::Object>();
1695
+ Composite *composite = new Composite;
1696
+ composite->input = sharp::CreateInputDescriptor(compositeObject.Get("input").As<Napi::Object>());
1697
+ composite->mode = sharp::AttrAsEnum<VipsBlendMode>(compositeObject, "blend", VIPS_TYPE_BLEND_MODE);
1698
+ composite->gravity = sharp::AttrAsUint32(compositeObject, "gravity");
1699
+ composite->left = sharp::AttrAsInt32(compositeObject, "left");
1700
+ composite->top = sharp::AttrAsInt32(compositeObject, "top");
1701
+ composite->hasOffset = sharp::AttrAsBool(compositeObject, "hasOffset");
1702
+ composite->tile = sharp::AttrAsBool(compositeObject, "tile");
1703
+ composite->premultiplied = sharp::AttrAsBool(compositeObject, "premultiplied");
1704
+ baton->composite.push_back(composite);
1705
+ }
1706
+ // Resize options
1707
+ baton->withoutEnlargement = sharp::AttrAsBool(options, "withoutEnlargement");
1708
+ baton->withoutReduction = sharp::AttrAsBool(options, "withoutReduction");
1709
+ baton->position = sharp::AttrAsInt32(options, "position");
1710
+ baton->resizeBackground = sharp::AttrAsVectorOfDouble(options, "resizeBackground");
1711
+ baton->kernel = sharp::AttrAsEnum<VipsKernel>(options, "kernel", VIPS_TYPE_KERNEL);
1712
+ baton->fastShrinkOnLoad = sharp::AttrAsBool(options, "fastShrinkOnLoad");
1713
+ // Join Channel Options
1714
+ if (options.Has("joinChannelIn")) {
1715
+ Napi::Array joinChannelArray = options.Get("joinChannelIn").As<Napi::Array>();
1716
+ for (unsigned int i = 0; i < joinChannelArray.Length(); i++) {
1717
+ baton->joinChannelIn.push_back(
1718
+ sharp::CreateInputDescriptor(joinChannelArray.Get(i).As<Napi::Object>()));
1719
+ }
1720
+ }
1721
+ // Operators
1722
+ baton->flatten = sharp::AttrAsBool(options, "flatten");
1723
+ baton->flattenBackground = sharp::AttrAsVectorOfDouble(options, "flattenBackground");
1724
+ baton->unflatten = sharp::AttrAsBool(options, "unflatten");
1725
+ baton->negate = sharp::AttrAsBool(options, "negate");
1726
+ baton->negateAlpha = sharp::AttrAsBool(options, "negateAlpha");
1727
+ baton->blurSigma = sharp::AttrAsDouble(options, "blurSigma");
1728
+ baton->precision = sharp::AttrAsEnum<VipsPrecision>(options, "precision", VIPS_TYPE_PRECISION);
1729
+ baton->minAmpl = sharp::AttrAsDouble(options, "minAmpl");
1730
+ baton->brightness = sharp::AttrAsDouble(options, "brightness");
1731
+ baton->saturation = sharp::AttrAsDouble(options, "saturation");
1732
+ baton->hue = sharp::AttrAsInt32(options, "hue");
1733
+ baton->lightness = sharp::AttrAsDouble(options, "lightness");
1734
+ baton->medianSize = sharp::AttrAsUint32(options, "medianSize");
1735
+ baton->sharpenSigma = sharp::AttrAsDouble(options, "sharpenSigma");
1736
+ baton->sharpenM1 = sharp::AttrAsDouble(options, "sharpenM1");
1737
+ baton->sharpenM2 = sharp::AttrAsDouble(options, "sharpenM2");
1738
+ baton->sharpenX1 = sharp::AttrAsDouble(options, "sharpenX1");
1739
+ baton->sharpenY2 = sharp::AttrAsDouble(options, "sharpenY2");
1740
+ baton->sharpenY3 = sharp::AttrAsDouble(options, "sharpenY3");
1741
+ baton->threshold = sharp::AttrAsInt32(options, "threshold");
1742
+ baton->thresholdGrayscale = sharp::AttrAsBool(options, "thresholdGrayscale");
1743
+ baton->trimBackground = sharp::AttrAsVectorOfDouble(options, "trimBackground");
1744
+ baton->trimThreshold = sharp::AttrAsDouble(options, "trimThreshold");
1745
+ baton->trimLineArt = sharp::AttrAsBool(options, "trimLineArt");
1746
+ baton->trimMargin = sharp::AttrAsUint32(options, "trimMargin");
1747
+ baton->gamma = sharp::AttrAsDouble(options, "gamma");
1748
+ baton->gammaOut = sharp::AttrAsDouble(options, "gammaOut");
1749
+ baton->linearA = sharp::AttrAsVectorOfDouble(options, "linearA");
1750
+ baton->linearB = sharp::AttrAsVectorOfDouble(options, "linearB");
1751
+ baton->dilateWidth = sharp::AttrAsUint32(options, "dilateWidth");
1752
+ baton->erodeWidth = sharp::AttrAsUint32(options, "erodeWidth");
1753
+ baton->greyscale = sharp::AttrAsBool(options, "greyscale");
1754
+ baton->normalise = sharp::AttrAsBool(options, "normalise");
1755
+ baton->normaliseLower = sharp::AttrAsUint32(options, "normaliseLower");
1756
+ baton->normaliseUpper = sharp::AttrAsUint32(options, "normaliseUpper");
1757
+ baton->tint = sharp::AttrAsVectorOfDouble(options, "tint");
1758
+ baton->claheWidth = sharp::AttrAsUint32(options, "claheWidth");
1759
+ baton->claheHeight = sharp::AttrAsUint32(options, "claheHeight");
1760
+ baton->claheMaxSlope = sharp::AttrAsUint32(options, "claheMaxSlope");
1761
+ baton->angle = sharp::AttrAsInt32(options, "angle");
1762
+ baton->rotationAngle = sharp::AttrAsDouble(options, "rotationAngle");
1763
+ baton->rotationBackground = sharp::AttrAsVectorOfDouble(options, "rotationBackground");
1764
+ baton->rotateBefore = sharp::AttrAsBool(options, "rotateBefore");
1765
+ baton->orientBefore = sharp::AttrAsBool(options, "orientBefore");
1766
+ baton->flip = sharp::AttrAsBool(options, "flip");
1767
+ baton->flop = sharp::AttrAsBool(options, "flop");
1768
+ baton->extendTop = sharp::AttrAsInt32(options, "extendTop");
1769
+ baton->extendBottom = sharp::AttrAsInt32(options, "extendBottom");
1770
+ baton->extendLeft = sharp::AttrAsInt32(options, "extendLeft");
1771
+ baton->extendRight = sharp::AttrAsInt32(options, "extendRight");
1772
+ baton->extendBackground = sharp::AttrAsVectorOfDouble(options, "extendBackground");
1773
+ baton->extendWith = sharp::AttrAsEnum<VipsExtend>(options, "extendWith", VIPS_TYPE_EXTEND);
1774
+ baton->extractChannel = sharp::AttrAsInt32(options, "extractChannel");
1775
+ baton->affineMatrix = sharp::AttrAsVectorOfDouble(options, "affineMatrix");
1776
+ baton->affineBackground = sharp::AttrAsVectorOfDouble(options, "affineBackground");
1777
+ baton->affineIdx = sharp::AttrAsDouble(options, "affineIdx");
1778
+ baton->affineIdy = sharp::AttrAsDouble(options, "affineIdy");
1779
+ baton->affineOdx = sharp::AttrAsDouble(options, "affineOdx");
1780
+ baton->affineOdy = sharp::AttrAsDouble(options, "affineOdy");
1781
+ baton->affineInterpolator = sharp::AttrAsStr(options, "affineInterpolator");
1782
+ baton->removeAlpha = sharp::AttrAsBool(options, "removeAlpha");
1783
+ baton->ensureAlpha = sharp::AttrAsDouble(options, "ensureAlpha");
1784
+ if (options.Has("boolean")) {
1785
+ baton->boolean = sharp::CreateInputDescriptor(options.Get("boolean").As<Napi::Object>());
1786
+ baton->booleanOp = sharp::AttrAsEnum<VipsOperationBoolean>(options, "booleanOp", VIPS_TYPE_OPERATION_BOOLEAN);
1787
+ }
1788
+ if (options.Has("bandBoolOp")) {
1789
+ baton->bandBoolOp = sharp::AttrAsEnum<VipsOperationBoolean>(options, "bandBoolOp", VIPS_TYPE_OPERATION_BOOLEAN);
1790
+ }
1791
+ if (options.Has("convKernel")) {
1792
+ Napi::Object kernel = options.Get("convKernel").As<Napi::Object>();
1793
+ baton->convKernelWidth = sharp::AttrAsUint32(kernel, "width");
1794
+ baton->convKernelHeight = sharp::AttrAsUint32(kernel, "height");
1795
+ baton->convKernelScale = sharp::AttrAsDouble(kernel, "scale");
1796
+ baton->convKernelOffset = sharp::AttrAsDouble(kernel, "offset");
1797
+ size_t const kernelSize = static_cast<size_t>(baton->convKernelWidth * baton->convKernelHeight);
1798
+ baton->convKernel.resize(kernelSize);
1799
+ Napi::Array kdata = kernel.Get("kernel").As<Napi::Array>();
1800
+ for (unsigned int i = 0; i < kernelSize; i++) {
1801
+ baton->convKernel[i] = sharp::AttrAsDouble(kdata, i);
1802
+ }
1803
+ }
1804
+ if (options.Has("recombMatrix")) {
1805
+ Napi::Array recombMatrix = options.Get("recombMatrix").As<Napi::Array>();
1806
+ unsigned int matrixElements = recombMatrix.Length();
1807
+ baton->recombMatrix.resize(matrixElements);
1808
+ for (unsigned int i = 0; i < matrixElements; i++) {
1809
+ baton->recombMatrix[i] = sharp::AttrAsDouble(recombMatrix, i);
1810
+ }
1811
+ }
1812
+ baton->colourspacePipeline = sharp::AttrAsEnum<VipsInterpretation>(
1813
+ options, "colourspacePipeline", VIPS_TYPE_INTERPRETATION);
1814
+ if (baton->colourspacePipeline == VIPS_INTERPRETATION_ERROR) {
1815
+ baton->colourspacePipeline = VIPS_INTERPRETATION_LAST;
1816
+ }
1817
+ baton->colourspace = sharp::AttrAsEnum<VipsInterpretation>(options, "colourspace", VIPS_TYPE_INTERPRETATION);
1818
+ if (baton->colourspace == VIPS_INTERPRETATION_ERROR) {
1819
+ baton->colourspace = VIPS_INTERPRETATION_sRGB;
1820
+ }
1821
+ // Output
1822
+ baton->formatOut = sharp::AttrAsStr(options, "formatOut");
1823
+ baton->fileOut = sharp::AttrAsStr(options, "fileOut");
1824
+ baton->typedArrayOut = sharp::AttrAsBool(options, "typedArrayOut");
1825
+ baton->keepMetadata = sharp::AttrAsUint32(options, "keepMetadata");
1826
+ baton->withMetadataOrientation = sharp::AttrAsUint32(options, "withMetadataOrientation");
1827
+ baton->withMetadataDensity = sharp::AttrAsDouble(options, "withMetadataDensity");
1828
+ baton->withIccProfile = sharp::AttrAsStr(options, "withIccProfile");
1829
+ Napi::Object withExif = options.Get("withExif").As<Napi::Object>();
1830
+ Napi::Array withExifKeys = withExif.GetPropertyNames();
1831
+ for (unsigned int i = 0; i < withExifKeys.Length(); i++) {
1832
+ std::string k = sharp::AttrAsStr(withExifKeys, i);
1833
+ if (withExif.HasOwnProperty(k)) {
1834
+ baton->withExif.insert(std::make_pair(k, sharp::AttrAsStr(withExif, k)));
1835
+ }
1836
+ }
1837
+ baton->withExifMerge = sharp::AttrAsBool(options, "withExifMerge");
1838
+ baton->withXmp = sharp::AttrAsStr(options, "withXmp");
1839
+ baton->keepGainMap = sharp::AttrAsBool(options, "keepGainMap");
1840
+ baton->withGainMap = sharp::AttrAsBool(options, "withGainMap");
1841
+ baton->timeoutSeconds = sharp::AttrAsUint32(options, "timeoutSeconds");
1842
+ baton->loop = sharp::AttrAsUint32(options, "loop");
1843
+ baton->delay = sharp::AttrAsInt32Vector(options, "delay");
1844
+ // Format-specific
1845
+ baton->jpegQuality = sharp::AttrAsUint32(options, "jpegQuality");
1846
+ baton->jpegProgressive = sharp::AttrAsBool(options, "jpegProgressive");
1847
+ baton->jpegChromaSubsampling = sharp::AttrAsStr(options, "jpegChromaSubsampling");
1848
+ baton->jpegTrellisQuantisation = sharp::AttrAsBool(options, "jpegTrellisQuantisation");
1849
+ baton->jpegQuantisationTable = sharp::AttrAsUint32(options, "jpegQuantisationTable");
1850
+ baton->jpegOvershootDeringing = sharp::AttrAsBool(options, "jpegOvershootDeringing");
1851
+ baton->jpegOptimiseScans = sharp::AttrAsBool(options, "jpegOptimiseScans");
1852
+ baton->jpegOptimiseCoding = sharp::AttrAsBool(options, "jpegOptimiseCoding");
1853
+ baton->pngProgressive = sharp::AttrAsBool(options, "pngProgressive");
1854
+ baton->pngCompressionLevel = sharp::AttrAsUint32(options, "pngCompressionLevel");
1855
+ baton->pngAdaptiveFiltering = sharp::AttrAsBool(options, "pngAdaptiveFiltering");
1856
+ baton->pngPalette = sharp::AttrAsBool(options, "pngPalette");
1857
+ baton->pngQuality = sharp::AttrAsUint32(options, "pngQuality");
1858
+ baton->pngEffort = sharp::AttrAsUint32(options, "pngEffort");
1859
+ baton->pngBitdepth = sharp::AttrAsUint32(options, "pngBitdepth");
1860
+ baton->pngDither = sharp::AttrAsDouble(options, "pngDither");
1861
+ baton->jp2Quality = sharp::AttrAsUint32(options, "jp2Quality");
1862
+ baton->jp2Lossless = sharp::AttrAsBool(options, "jp2Lossless");
1863
+ baton->jp2TileHeight = sharp::AttrAsUint32(options, "jp2TileHeight");
1864
+ baton->jp2TileWidth = sharp::AttrAsUint32(options, "jp2TileWidth");
1865
+ baton->jp2ChromaSubsampling = sharp::AttrAsStr(options, "jp2ChromaSubsampling");
1866
+ baton->webpQuality = sharp::AttrAsUint32(options, "webpQuality");
1867
+ baton->webpAlphaQuality = sharp::AttrAsUint32(options, "webpAlphaQuality");
1868
+ baton->webpLossless = sharp::AttrAsBool(options, "webpLossless");
1869
+ baton->webpNearLossless = sharp::AttrAsBool(options, "webpNearLossless");
1870
+ baton->webpSmartSubsample = sharp::AttrAsBool(options, "webpSmartSubsample");
1871
+ baton->webpSmartDeblock = sharp::AttrAsBool(options, "webpSmartDeblock");
1872
+ baton->webpPreset = sharp::AttrAsEnum<VipsForeignWebpPreset>(options, "webpPreset", VIPS_TYPE_FOREIGN_WEBP_PRESET);
1873
+ baton->webpEffort = sharp::AttrAsUint32(options, "webpEffort");
1874
+ baton->webpMinSize = sharp::AttrAsBool(options, "webpMinSize");
1875
+ baton->webpMixed = sharp::AttrAsBool(options, "webpMixed");
1876
+ baton->webpExact = sharp::AttrAsBool(options, "webpExact");
1877
+ baton->gifBitdepth = sharp::AttrAsUint32(options, "gifBitdepth");
1878
+ baton->gifEffort = sharp::AttrAsUint32(options, "gifEffort");
1879
+ baton->gifDither = sharp::AttrAsDouble(options, "gifDither");
1880
+ baton->gifInterFrameMaxError = sharp::AttrAsDouble(options, "gifInterFrameMaxError");
1881
+ baton->gifInterPaletteMaxError = sharp::AttrAsDouble(options, "gifInterPaletteMaxError");
1882
+ baton->gifKeepDuplicateFrames = sharp::AttrAsBool(options, "gifKeepDuplicateFrames");
1883
+ baton->gifReuse = sharp::AttrAsBool(options, "gifReuse");
1884
+ baton->gifProgressive = sharp::AttrAsBool(options, "gifProgressive");
1885
+ baton->tiffQuality = sharp::AttrAsUint32(options, "tiffQuality");
1886
+ baton->tiffBigtiff = sharp::AttrAsBool(options, "tiffBigtiff");
1887
+ baton->tiffPyramid = sharp::AttrAsBool(options, "tiffPyramid");
1888
+ baton->tiffMiniswhite = sharp::AttrAsBool(options, "tiffMiniswhite");
1889
+ baton->tiffBitdepth = sharp::AttrAsUint32(options, "tiffBitdepth");
1890
+ baton->tiffTile = sharp::AttrAsBool(options, "tiffTile");
1891
+ baton->tiffTileWidth = sharp::AttrAsUint32(options, "tiffTileWidth");
1892
+ baton->tiffTileHeight = sharp::AttrAsUint32(options, "tiffTileHeight");
1893
+ baton->tiffXres = sharp::AttrAsDouble(options, "tiffXres");
1894
+ baton->tiffYres = sharp::AttrAsDouble(options, "tiffYres");
1895
+ if (baton->tiffXres == 1.0 && baton->tiffYres == 1.0 && baton->withMetadataDensity > 0) {
1896
+ baton->tiffXres = baton->tiffYres = baton->withMetadataDensity / 25.4;
1897
+ }
1898
+ baton->tiffCompression = sharp::AttrAsEnum<VipsForeignTiffCompression>(
1899
+ options, "tiffCompression", VIPS_TYPE_FOREIGN_TIFF_COMPRESSION);
1900
+ baton->tiffPredictor = sharp::AttrAsEnum<VipsForeignTiffPredictor>(
1901
+ options, "tiffPredictor", VIPS_TYPE_FOREIGN_TIFF_PREDICTOR);
1902
+ baton->tiffResolutionUnit = sharp::AttrAsEnum<VipsForeignTiffResunit>(
1903
+ options, "tiffResolutionUnit", VIPS_TYPE_FOREIGN_TIFF_RESUNIT);
1904
+ baton->heifQuality = sharp::AttrAsUint32(options, "heifQuality");
1905
+ baton->heifLossless = sharp::AttrAsBool(options, "heifLossless");
1906
+ baton->heifCompression = sharp::AttrAsEnum<VipsForeignHeifCompression>(
1907
+ options, "heifCompression", VIPS_TYPE_FOREIGN_HEIF_COMPRESSION);
1908
+ baton->heifEffort = sharp::AttrAsUint32(options, "heifEffort");
1909
+ baton->heifChromaSubsampling = sharp::AttrAsStr(options, "heifChromaSubsampling");
1910
+ baton->heifBitdepth = sharp::AttrAsUint32(options, "heifBitdepth");
1911
+ baton->heifTune = sharp::AttrAsStr(options, "heifTune");
1912
+ baton->jxlDistance = sharp::AttrAsDouble(options, "jxlDistance");
1913
+ baton->jxlDecodingTier = sharp::AttrAsUint32(options, "jxlDecodingTier");
1914
+ baton->jxlEffort = sharp::AttrAsUint32(options, "jxlEffort");
1915
+ baton->jxlLossless = sharp::AttrAsBool(options, "jxlLossless");
1916
+ baton->rawDepth = sharp::AttrAsEnum<VipsBandFormat>(options, "rawDepth", VIPS_TYPE_BAND_FORMAT);
1917
+ baton->tileSize = sharp::AttrAsUint32(options, "tileSize");
1918
+ baton->tileOverlap = sharp::AttrAsUint32(options, "tileOverlap");
1919
+ baton->tileAngle = sharp::AttrAsInt32(options, "tileAngle");
1920
+ baton->tileBackground = sharp::AttrAsVectorOfDouble(options, "tileBackground");
1921
+ baton->tileSkipBlanks = sharp::AttrAsInt32(options, "tileSkipBlanks");
1922
+ baton->tileContainer = sharp::AttrAsEnum<VipsForeignDzContainer>(
1923
+ options, "tileContainer", VIPS_TYPE_FOREIGN_DZ_CONTAINER);
1924
+ baton->tileLayout = sharp::AttrAsEnum<VipsForeignDzLayout>(options, "tileLayout", VIPS_TYPE_FOREIGN_DZ_LAYOUT);
1925
+ baton->tileFormat = sharp::AttrAsStr(options, "tileFormat");
1926
+ baton->tileDepth = sharp::AttrAsEnum<VipsForeignDzDepth>(options, "tileDepth", VIPS_TYPE_FOREIGN_DZ_DEPTH);
1927
+ baton->tileCentre = sharp::AttrAsBool(options, "tileCentre");
1928
+ baton->tileId = sharp::AttrAsStr(options, "tileId");
1929
+ baton->tileBasename = sharp::AttrAsStr(options, "tileBasename");
1930
+
1931
+ // Function to notify of libvips warnings
1932
+ Napi::Function debuglog = options.Get("debuglog").As<Napi::Function>();
1933
+
1934
+ // Function to notify of queue length changes
1935
+ Napi::Function queueListener = options.Get("queueListener").As<Napi::Function>();
1936
+
1937
+ // Join queue for worker thread
1938
+ Napi::Function callback = info[size_t(1)].As<Napi::Function>();
1939
+ PipelineWorker *worker = new PipelineWorker(callback, baton, debuglog, queueListener);
1940
+ worker->Receiver().Set("options", options);
1941
+ worker->Queue();
1942
+
1943
+ // Increment queued task counter
1944
+ Napi::Number queueLength = Napi::Number::New(info.Env(), static_cast<int>(++sharp::counterQueue));
1945
+ queueListener.SHARP_CALLBACK_FN_NAME(info.This(), { queueLength });
1946
+
1947
+ return info.Env().Undefined();
1948
+ }