headroom-cms 0.1.1

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