@guru-ai-product/ai-product-kit 0.2.251113194913 → 0.2.251113205658

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