sharp 0.33.5 → 0.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -18
- package/dist/channel.cjs +177 -0
- package/{lib/channel.js → dist/channel.mjs} +11 -8
- package/dist/colour.cjs +195 -0
- package/{lib/colour.js → dist/colour.mjs} +38 -23
- package/dist/composite.cjs +213 -0
- package/{lib/composite.js → dist/composite.mjs} +14 -11
- package/{lib/constructor.js → dist/constructor.cjs} +102 -43
- package/dist/constructor.mjs +511 -0
- package/dist/index.cjs +25 -0
- package/dist/index.mjs +25 -0
- package/dist/input.cjs +814 -0
- package/{lib/input.js → dist/input.mjs} +224 -68
- package/{lib/is.js → dist/is.cjs} +19 -45
- package/dist/is.mjs +143 -0
- package/{lib/libvips.js → dist/libvips.cjs} +52 -43
- package/dist/libvips.mjs +212 -0
- package/dist/operation.cjs +987 -0
- package/{lib/operation.js → dist/operation.mjs} +108 -79
- package/{lib/output.js → dist/output.cjs} +243 -36
- package/dist/output.mjs +1794 -0
- package/dist/resize.cjs +613 -0
- package/{lib/resize.js → dist/resize.mjs} +38 -12
- package/dist/sharp.cjs +164 -0
- package/dist/sharp.mjs +164 -0
- package/{lib/utility.js → dist/utility.cjs} +23 -24
- package/dist/utility.mjs +295 -0
- package/install/build.js +38 -0
- package/lib/index.d.ts +347 -125
- package/package.json +75 -84
- package/src/binding.gyp +49 -26
- package/src/common.cc +203 -111
- package/src/common.h +68 -40
- package/src/metadata.cc +106 -22
- package/src/metadata.h +15 -5
- package/src/operations.cc +62 -21
- package/src/operations.h +15 -3
- package/src/pipeline.cc +374 -183
- package/src/pipeline.h +34 -7
- package/src/sharp.cc +11 -8
- package/src/stats.cc +16 -13
- package/src/stats.h +6 -3
- package/src/utilities.cc +32 -12
- package/src/utilities.h +5 -2
- package/install/check.js +0 -41
- package/lib/index.js +0 -16
- package/lib/sharp.js +0 -114
package/src/pipeline.cc
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/*!
|
|
2
|
+
Copyright 2013 Lovell Fuller and others.
|
|
3
|
+
SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
3
5
|
|
|
4
6
|
#include <algorithm>
|
|
5
7
|
#include <cmath>
|
|
8
|
+
#include <filesystem> // NOLINT(build/c++17)
|
|
6
9
|
#include <map>
|
|
7
10
|
#include <memory>
|
|
8
11
|
#include <numeric>
|
|
@@ -16,20 +19,9 @@
|
|
|
16
19
|
#include <vips/vips8>
|
|
17
20
|
#include <napi.h>
|
|
18
21
|
|
|
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
|
|
22
|
+
#include "./common.h"
|
|
23
|
+
#include "./operations.h"
|
|
24
|
+
#include "./pipeline.h"
|
|
33
25
|
|
|
34
26
|
class PipelineWorker : public Napi::AsyncWorker {
|
|
35
27
|
public:
|
|
@@ -52,7 +44,39 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
52
44
|
// Open input
|
|
53
45
|
vips::VImage image;
|
|
54
46
|
sharp::ImageType inputImageType;
|
|
55
|
-
|
|
47
|
+
if (baton->join.empty()) {
|
|
48
|
+
std::tie(image, inputImageType) = sharp::OpenInput(baton->input);
|
|
49
|
+
} else {
|
|
50
|
+
std::vector<VImage> images;
|
|
51
|
+
bool hasAlpha = false;
|
|
52
|
+
for (auto &join : baton->join) {
|
|
53
|
+
std::tie(image, inputImageType) = sharp::OpenInput(join);
|
|
54
|
+
image = sharp::EnsureColourspace(image, baton->colourspacePipeline);
|
|
55
|
+
images.push_back(image);
|
|
56
|
+
hasAlpha |= image.has_alpha();
|
|
57
|
+
}
|
|
58
|
+
if (hasAlpha) {
|
|
59
|
+
for (auto &image : images) {
|
|
60
|
+
if (!image.has_alpha()) {
|
|
61
|
+
image = sharp::EnsureAlpha(image, 1);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
baton->input->joinBackground.pop_back();
|
|
66
|
+
}
|
|
67
|
+
inputImageType = sharp::ImageType::PNG;
|
|
68
|
+
image = VImage::arrayjoin(images, VImage::option()
|
|
69
|
+
->set("across", baton->input->joinAcross)
|
|
70
|
+
->set("shim", baton->input->joinShim)
|
|
71
|
+
->set("background", baton->input->joinBackground)
|
|
72
|
+
->set("halign", baton->input->joinHalign)
|
|
73
|
+
->set("valign", baton->input->joinValign));
|
|
74
|
+
if (baton->input->joinAnimated) {
|
|
75
|
+
image = image.copy();
|
|
76
|
+
image.set(VIPS_META_N_PAGES, static_cast<int>(images.size()));
|
|
77
|
+
image.set(VIPS_META_PAGE_HEIGHT, static_cast<int>(image.height() / images.size()));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
56
80
|
VipsAccess access = baton->input->access;
|
|
57
81
|
image = sharp::EnsureColourspace(image, baton->colourspacePipeline);
|
|
58
82
|
|
|
@@ -60,7 +84,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
60
84
|
if (nPages == -1) {
|
|
61
85
|
// Resolve the number of pages if we need to render until the end of the document
|
|
62
86
|
nPages = image.get_typeof(VIPS_META_N_PAGES) != 0
|
|
63
|
-
? image.get_int(VIPS_META_N_PAGES) - baton->input->page
|
|
87
|
+
? image.get_int(VIPS_META_N_PAGES) - std::max(0, baton->input->page)
|
|
64
88
|
: 1;
|
|
65
89
|
}
|
|
66
90
|
|
|
@@ -70,31 +94,22 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
70
94
|
// Calculate angle of rotation
|
|
71
95
|
VipsAngle rotation = VIPS_ANGLE_D0;
|
|
72
96
|
VipsAngle autoRotation = VIPS_ANGLE_D0;
|
|
73
|
-
bool autoFlip = false;
|
|
74
97
|
bool autoFlop = false;
|
|
75
98
|
|
|
76
|
-
if (baton->
|
|
99
|
+
if (baton->input->autoOrient) {
|
|
77
100
|
// Rotate and flip image according to Exif orientation
|
|
78
|
-
std::tie(autoRotation,
|
|
79
|
-
image = sharp::RemoveExifOrientation(image);
|
|
80
|
-
} else {
|
|
81
|
-
rotation = CalculateAngleRotation(baton->angle);
|
|
101
|
+
std::tie(autoRotation, autoFlop) = CalculateExifRotationAndFlop(sharp::ExifOrientation(image));
|
|
82
102
|
}
|
|
83
103
|
|
|
84
|
-
|
|
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);
|
|
104
|
+
rotation = CalculateAngleRotation(baton->angle);
|
|
89
105
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
autoFlip ||
|
|
95
|
-
baton->flip ||
|
|
96
|
-
baton->rotationAngle != 0.0);
|
|
106
|
+
bool const shouldRotateBefore = baton->rotateBefore &&
|
|
107
|
+
(rotation != VIPS_ANGLE_D0 || baton->flip || baton->flop || baton->rotationAngle != 0.0);
|
|
108
|
+
bool const shouldOrientBefore = (shouldRotateBefore || baton->orientBefore) &&
|
|
109
|
+
(autoRotation != VIPS_ANGLE_D0 || autoFlop);
|
|
97
110
|
|
|
111
|
+
if (shouldOrientBefore) {
|
|
112
|
+
image = sharp::StaySequential(image, autoRotation != VIPS_ANGLE_D0);
|
|
98
113
|
if (autoRotation != VIPS_ANGLE_D0) {
|
|
99
114
|
if (autoRotation != VIPS_ANGLE_D180) {
|
|
100
115
|
MultiPageUnsupported(nPages, "Rotate");
|
|
@@ -102,17 +117,19 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
102
117
|
image = image.rot(autoRotation);
|
|
103
118
|
autoRotation = VIPS_ANGLE_D0;
|
|
104
119
|
}
|
|
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
120
|
if (autoFlop) {
|
|
113
121
|
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
|
|
114
122
|
autoFlop = false;
|
|
115
|
-
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (shouldRotateBefore) {
|
|
127
|
+
image = sharp::StaySequential(image, rotation != VIPS_ANGLE_D0 || baton->flip || baton->rotationAngle != 0.0);
|
|
128
|
+
if (baton->flip) {
|
|
129
|
+
image = image.flip(VIPS_DIRECTION_VERTICAL);
|
|
130
|
+
baton->flip = false;
|
|
131
|
+
}
|
|
132
|
+
if (baton->flop) {
|
|
116
133
|
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
|
|
117
134
|
baton->flop = false;
|
|
118
135
|
}
|
|
@@ -128,6 +145,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
128
145
|
std::vector<double> background;
|
|
129
146
|
std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground, false);
|
|
130
147
|
image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background)).copy_memory();
|
|
148
|
+
baton->rotationAngle = 0.0;
|
|
131
149
|
}
|
|
132
150
|
}
|
|
133
151
|
|
|
@@ -135,7 +153,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
135
153
|
if (baton->trimThreshold >= 0.0) {
|
|
136
154
|
MultiPageUnsupported(nPages, "Trim");
|
|
137
155
|
image = sharp::StaySequential(image);
|
|
138
|
-
image = sharp::Trim(image, baton->trimBackground, baton->trimThreshold, baton->trimLineArt);
|
|
156
|
+
image = sharp::Trim(image, baton->trimBackground, baton->trimThreshold, baton->trimLineArt, baton->trimMargin);
|
|
139
157
|
baton->trimOffsetLeft = image.xoffset();
|
|
140
158
|
baton->trimOffsetTop = image.yoffset();
|
|
141
159
|
}
|
|
@@ -166,8 +184,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
166
184
|
// When auto-rotating by 90 or 270 degrees, swap the target width and
|
|
167
185
|
// height to ensure the behavior aligns with how it would have been if
|
|
168
186
|
// the rotation had taken place *before* resizing.
|
|
169
|
-
if (
|
|
170
|
-
(autoRotation == VIPS_ANGLE_D90 || autoRotation == VIPS_ANGLE_D270)) {
|
|
187
|
+
if (autoRotation == VIPS_ANGLE_D90 || autoRotation == VIPS_ANGLE_D270) {
|
|
171
188
|
std::swap(targetResizeWidth, targetResizeHeight);
|
|
172
189
|
}
|
|
173
190
|
|
|
@@ -186,10 +203,12 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
186
203
|
// - the width or height parameters are specified;
|
|
187
204
|
// - gamma correction doesn't need to be applied;
|
|
188
205
|
// - trimming or pre-resize extract isn't required;
|
|
206
|
+
// - gain map processing is not required;
|
|
189
207
|
// - input colourspace is not specified;
|
|
190
208
|
bool const shouldPreShrink = (targetResizeWidth > 0 || targetResizeHeight > 0) &&
|
|
191
209
|
baton->gamma == 0 && baton->topOffsetPre == -1 && baton->trimThreshold < 0.0 &&
|
|
192
|
-
baton->
|
|
210
|
+
!baton->keepGainMap && !baton->withGainMap &&
|
|
211
|
+
baton->colourspacePipeline == VIPS_INTERPRETATION_LAST && !(shouldOrientBefore || shouldRotateBefore);
|
|
193
212
|
|
|
194
213
|
if (shouldPreShrink) {
|
|
195
214
|
// The common part of the shrink: the bit by which both axes must be shrunk
|
|
@@ -223,11 +242,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
223
242
|
// factor for jpegload*, a double scale factor for webpload*,
|
|
224
243
|
// pdfload* and svgload*
|
|
225
244
|
if (jpegShrinkOnLoad > 1) {
|
|
226
|
-
vips::VOption *option =
|
|
227
|
-
->set("access", access)
|
|
228
|
-
->set("shrink", jpegShrinkOnLoad)
|
|
229
|
-
->set("unlimited", baton->input->unlimited)
|
|
230
|
-
->set("fail_on", baton->input->failOn);
|
|
245
|
+
vips::VOption *option = GetOptionsForImageType(inputImageType, baton->input)->set("shrink", jpegShrinkOnLoad);
|
|
231
246
|
if (baton->input->buffer != nullptr) {
|
|
232
247
|
// Reload JPEG buffer
|
|
233
248
|
VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength);
|
|
@@ -238,14 +253,8 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
238
253
|
image = VImage::jpegload(const_cast<char*>(baton->input->file.data()), option);
|
|
239
254
|
}
|
|
240
255
|
} else if (scale != 1.0) {
|
|
241
|
-
vips::VOption *option =
|
|
242
|
-
->set("access", access)
|
|
243
|
-
->set("scale", scale)
|
|
244
|
-
->set("fail_on", baton->input->failOn);
|
|
256
|
+
vips::VOption *option = GetOptionsForImageType(inputImageType, baton->input)->set("scale", scale);
|
|
245
257
|
if (inputImageType == sharp::ImageType::WEBP) {
|
|
246
|
-
option->set("n", baton->input->pages);
|
|
247
|
-
option->set("page", baton->input->page);
|
|
248
|
-
|
|
249
258
|
if (baton->input->buffer != nullptr) {
|
|
250
259
|
// Reload WebP buffer
|
|
251
260
|
VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength);
|
|
@@ -256,9 +265,6 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
256
265
|
image = VImage::webpload(const_cast<char*>(baton->input->file.data()), option);
|
|
257
266
|
}
|
|
258
267
|
} else if (inputImageType == sharp::ImageType::SVG) {
|
|
259
|
-
option->set("unlimited", baton->input->unlimited);
|
|
260
|
-
option->set("dpi", baton->input->density);
|
|
261
|
-
|
|
262
268
|
if (baton->input->buffer != nullptr) {
|
|
263
269
|
// Reload SVG buffer
|
|
264
270
|
VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength);
|
|
@@ -270,13 +276,9 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
270
276
|
}
|
|
271
277
|
sharp::SetDensity(image, baton->input->density);
|
|
272
278
|
if (image.width() > 32767 || image.height() > 32767) {
|
|
273
|
-
throw
|
|
279
|
+
throw std::runtime_error("Input SVG image will exceed 32767x32767 pixel limit when scaled");
|
|
274
280
|
}
|
|
275
281
|
} 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
282
|
if (baton->input->buffer != nullptr) {
|
|
281
283
|
// Reload PDF buffer
|
|
282
284
|
VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength);
|
|
@@ -286,14 +288,32 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
286
288
|
// Reload PDF file
|
|
287
289
|
image = VImage::pdfload(const_cast<char*>(baton->input->file.data()), option);
|
|
288
290
|
}
|
|
289
|
-
|
|
290
291
|
sharp::SetDensity(image, baton->input->density);
|
|
291
292
|
}
|
|
292
293
|
} else {
|
|
293
294
|
if (inputImageType == sharp::ImageType::SVG && (image.width() > 32767 || image.height() > 32767)) {
|
|
294
|
-
throw
|
|
295
|
+
throw std::runtime_error("Input SVG image exceeds 32767x32767 pixel limit");
|
|
295
296
|
}
|
|
296
297
|
}
|
|
298
|
+
if (baton->input->autoOrient) {
|
|
299
|
+
image = sharp::RemoveExifOrientation(image);
|
|
300
|
+
}
|
|
301
|
+
VImage gainMap;
|
|
302
|
+
int gainMapScaleFactor = 1;
|
|
303
|
+
if (sharp::HasGainMap(image)) {
|
|
304
|
+
if (baton->keepGainMap) {
|
|
305
|
+
gainMap = image.gainmap();
|
|
306
|
+
if (image.get_typeof("gainmap-scale-factor") == G_TYPE_INT) {
|
|
307
|
+
gainMapScaleFactor = image.get_int("gainmap-scale-factor");
|
|
308
|
+
}
|
|
309
|
+
} else if (baton->withGainMap) {
|
|
310
|
+
image = image.uhdr2scRGB();
|
|
311
|
+
}
|
|
312
|
+
image = sharp::RemoveGainMap(image);
|
|
313
|
+
} else {
|
|
314
|
+
baton->keepGainMap = false;
|
|
315
|
+
baton->withGainMap = false;
|
|
316
|
+
}
|
|
297
317
|
|
|
298
318
|
// Any pre-shrinking may already have been done
|
|
299
319
|
inputWidth = image.width();
|
|
@@ -333,7 +353,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
333
353
|
image.interpretation() != VIPS_INTERPRETATION_LABS &&
|
|
334
354
|
image.interpretation() != VIPS_INTERPRETATION_GREY16 &&
|
|
335
355
|
baton->colourspacePipeline != VIPS_INTERPRETATION_CMYK &&
|
|
336
|
-
!baton->input->ignoreIcc
|
|
356
|
+
!baton->input->ignoreIcc && !baton->withGainMap
|
|
337
357
|
) {
|
|
338
358
|
// Convert to sRGB/P3 using embedded profile
|
|
339
359
|
try {
|
|
@@ -354,7 +374,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
354
374
|
}
|
|
355
375
|
|
|
356
376
|
// Flatten image to remove alpha channel
|
|
357
|
-
if (baton->flatten &&
|
|
377
|
+
if (baton->flatten && image.has_alpha()) {
|
|
358
378
|
image = sharp::Flatten(image, baton->flattenBackground);
|
|
359
379
|
}
|
|
360
380
|
|
|
@@ -374,12 +394,12 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
374
394
|
bool const shouldSharpen = baton->sharpenSigma != 0.0;
|
|
375
395
|
bool const shouldComposite = !baton->composite.empty();
|
|
376
396
|
|
|
377
|
-
if (shouldComposite && !
|
|
397
|
+
if (shouldComposite && !image.has_alpha()) {
|
|
378
398
|
image = sharp::EnsureAlpha(image, 1);
|
|
379
399
|
}
|
|
380
400
|
|
|
381
401
|
VipsBandFormat premultiplyFormat = image.format();
|
|
382
|
-
bool const shouldPremultiplyAlpha =
|
|
402
|
+
bool const shouldPremultiplyAlpha = image.has_alpha() &&
|
|
383
403
|
(shouldResize || shouldBlur || shouldConv || shouldSharpen);
|
|
384
404
|
|
|
385
405
|
if (shouldPremultiplyAlpha) {
|
|
@@ -391,12 +411,16 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
391
411
|
image = image.resize(1.0 / hshrink, VImage::option()
|
|
392
412
|
->set("vscale", 1.0 / vshrink)
|
|
393
413
|
->set("kernel", baton->kernel));
|
|
414
|
+
if (baton->keepGainMap) {
|
|
415
|
+
gainMap = gainMap.resize(1.0 / hshrink, VImage::option()
|
|
416
|
+
->set("vscale", 1.0 / vshrink)
|
|
417
|
+
->set("kernel", baton->kernel));
|
|
418
|
+
}
|
|
394
419
|
}
|
|
395
420
|
|
|
396
421
|
image = sharp::StaySequential(image,
|
|
397
422
|
autoRotation != VIPS_ANGLE_D0 ||
|
|
398
423
|
baton->flip ||
|
|
399
|
-
autoFlip ||
|
|
400
424
|
rotation != VIPS_ANGLE_D0);
|
|
401
425
|
// Auto-rotate post-extract
|
|
402
426
|
if (autoRotation != VIPS_ANGLE_D0) {
|
|
@@ -404,14 +428,23 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
404
428
|
MultiPageUnsupported(nPages, "Rotate");
|
|
405
429
|
}
|
|
406
430
|
image = image.rot(autoRotation);
|
|
431
|
+
if (baton->keepGainMap) {
|
|
432
|
+
gainMap = gainMap.rot(autoRotation);
|
|
433
|
+
}
|
|
407
434
|
}
|
|
408
435
|
// Mirror vertically (up-down) about the x-axis
|
|
409
|
-
if (baton->flip
|
|
436
|
+
if (baton->flip) {
|
|
410
437
|
image = image.flip(VIPS_DIRECTION_VERTICAL);
|
|
438
|
+
if (baton->keepGainMap) {
|
|
439
|
+
gainMap = gainMap.flip(VIPS_DIRECTION_VERTICAL);
|
|
440
|
+
}
|
|
411
441
|
}
|
|
412
442
|
// Mirror horizontally (left-right) about the y-axis
|
|
413
|
-
if (baton->flop
|
|
443
|
+
if (baton->flop != autoFlop) {
|
|
414
444
|
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
|
|
445
|
+
if (baton->keepGainMap) {
|
|
446
|
+
gainMap = gainMap.flip(VIPS_DIRECTION_HORIZONTAL);
|
|
447
|
+
}
|
|
415
448
|
}
|
|
416
449
|
// Rotate post-extract 90-angle
|
|
417
450
|
if (rotation != VIPS_ANGLE_D0) {
|
|
@@ -419,6 +452,9 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
419
452
|
MultiPageUnsupported(nPages, "Rotate");
|
|
420
453
|
}
|
|
421
454
|
image = image.rot(rotation);
|
|
455
|
+
if (baton->keepGainMap) {
|
|
456
|
+
gainMap = gainMap.rot(rotation);
|
|
457
|
+
}
|
|
422
458
|
}
|
|
423
459
|
|
|
424
460
|
// Join additional color channels to the image
|
|
@@ -454,12 +490,10 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
454
490
|
std::tie(image, background) = sharp::ApplyAlpha(image, baton->resizeBackground, shouldPremultiplyAlpha);
|
|
455
491
|
|
|
456
492
|
// Embed
|
|
457
|
-
|
|
458
|
-
int top;
|
|
459
|
-
std::tie(left, top) = sharp::CalculateEmbedPosition(
|
|
493
|
+
const auto& [left, top] = sharp::CalculateEmbedPosition(
|
|
460
494
|
inputWidth, inputHeight, baton->width, baton->height, baton->position);
|
|
461
|
-
int width = std::max(inputWidth, baton->width);
|
|
462
|
-
int height = std::max(inputHeight, baton->height);
|
|
495
|
+
const int width = std::max(inputWidth, baton->width);
|
|
496
|
+
const int height = std::max(inputHeight, baton->height);
|
|
463
497
|
|
|
464
498
|
image = nPages > 1
|
|
465
499
|
? sharp::EmbedMultiPage(image,
|
|
@@ -467,6 +501,12 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
467
501
|
: image.embed(left, top, width, height, VImage::option()
|
|
468
502
|
->set("extend", VIPS_EXTEND_BACKGROUND)
|
|
469
503
|
->set("background", background));
|
|
504
|
+
if (baton->keepGainMap) {
|
|
505
|
+
gainMap = gainMap.embed(left / gainMapScaleFactor, top / gainMapScaleFactor,
|
|
506
|
+
width / gainMapScaleFactor, height / gainMapScaleFactor, VImage::option()
|
|
507
|
+
->set("extend", VIPS_EXTEND_BACKGROUND)
|
|
508
|
+
->set("background", 0));
|
|
509
|
+
}
|
|
470
510
|
} else if (baton->canvas == sharp::Canvas::CROP) {
|
|
471
511
|
if (baton->width > inputWidth) {
|
|
472
512
|
baton->width = inputWidth;
|
|
@@ -478,24 +518,26 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
478
518
|
// Crop
|
|
479
519
|
if (baton->position < 9) {
|
|
480
520
|
// Gravity-based crop
|
|
481
|
-
|
|
482
|
-
int top;
|
|
483
|
-
|
|
484
|
-
std::tie(left, top) = sharp::CalculateCrop(
|
|
521
|
+
const auto& [left, top] = sharp::CalculateCrop(
|
|
485
522
|
inputWidth, inputHeight, baton->width, baton->height, baton->position);
|
|
486
|
-
int width = std::min(inputWidth, baton->width);
|
|
487
|
-
int height = std::min(inputHeight, baton->height);
|
|
523
|
+
const int width = std::min(inputWidth, baton->width);
|
|
524
|
+
const int height = std::min(inputHeight, baton->height);
|
|
488
525
|
|
|
489
526
|
image = nPages > 1
|
|
490
527
|
? sharp::CropMultiPage(image,
|
|
491
528
|
left, top, width, height, nPages, &targetPageHeight)
|
|
492
529
|
: image.extract_area(left, top, width, height);
|
|
530
|
+
if (baton->keepGainMap) {
|
|
531
|
+
gainMap = gainMap.extract_area(left / gainMapScaleFactor, top / gainMapScaleFactor,
|
|
532
|
+
width / gainMapScaleFactor, height / gainMapScaleFactor);
|
|
533
|
+
}
|
|
493
534
|
} else {
|
|
494
535
|
int attention_x;
|
|
495
536
|
int attention_y;
|
|
496
537
|
|
|
497
538
|
// Attention-based or Entropy-based crop
|
|
498
539
|
MultiPageUnsupported(nPages, "Resize strategy");
|
|
540
|
+
KeepGainMapUnsupported(baton->keepGainMap, "Resize strategy");
|
|
499
541
|
image = sharp::StaySequential(image);
|
|
500
542
|
image = image.smartcrop(baton->width, baton->height, VImage::option()
|
|
501
543
|
->set("interesting", baton->position == 16 ? VIPS_INTERESTING_ENTROPY : VIPS_INTERESTING_ATTENTION)
|
|
@@ -513,12 +555,15 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
513
555
|
}
|
|
514
556
|
|
|
515
557
|
// Rotate post-extract non-90 angle
|
|
516
|
-
if (!baton->
|
|
558
|
+
if (!baton->rotateBefore && baton->rotationAngle != 0.0) {
|
|
517
559
|
MultiPageUnsupported(nPages, "Rotate");
|
|
518
560
|
image = sharp::StaySequential(image);
|
|
519
561
|
std::vector<double> background;
|
|
520
562
|
std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground, shouldPremultiplyAlpha);
|
|
521
563
|
image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background));
|
|
564
|
+
if (baton->keepGainMap) {
|
|
565
|
+
gainMap = gainMap.rotate(baton->rotationAngle, VImage::option()->set("background", 0));
|
|
566
|
+
}
|
|
522
567
|
}
|
|
523
568
|
|
|
524
569
|
// Post extraction
|
|
@@ -527,18 +572,23 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
527
572
|
image = sharp::CropMultiPage(image,
|
|
528
573
|
baton->leftOffsetPost, baton->topOffsetPost, baton->widthPost, baton->heightPost,
|
|
529
574
|
nPages, &targetPageHeight);
|
|
530
|
-
|
|
531
575
|
// heightPost is used in the info object, so update to reflect the number of pages
|
|
532
576
|
baton->heightPost *= nPages;
|
|
533
577
|
} else {
|
|
534
578
|
image = image.extract_area(
|
|
535
579
|
baton->leftOffsetPost, baton->topOffsetPost, baton->widthPost, baton->heightPost);
|
|
580
|
+
if (baton->keepGainMap) {
|
|
581
|
+
gainMap = gainMap.extract_area(baton->leftOffsetPost / gainMapScaleFactor,
|
|
582
|
+
baton->topOffsetPost / gainMapScaleFactor, baton->widthPost / gainMapScaleFactor,
|
|
583
|
+
baton->heightPost / gainMapScaleFactor);
|
|
584
|
+
}
|
|
536
585
|
}
|
|
537
586
|
}
|
|
538
587
|
|
|
539
588
|
// Affine transform
|
|
540
589
|
if (!baton->affineMatrix.empty()) {
|
|
541
590
|
MultiPageUnsupported(nPages, "Affine");
|
|
591
|
+
KeepGainMapUnsupported(baton->keepGainMap, "Affine");
|
|
542
592
|
image = sharp::StaySequential(image);
|
|
543
593
|
std::vector<double> background;
|
|
544
594
|
std::tie(image, background) = sharp::ApplyAlpha(image, baton->affineBackground, shouldPremultiplyAlpha);
|
|
@@ -569,6 +619,12 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
569
619
|
baton->extendWith, background, nPages, &targetPageHeight)
|
|
570
620
|
: image.embed(baton->extendLeft, baton->extendTop, baton->width, baton->height,
|
|
571
621
|
VImage::option()->set("extend", baton->extendWith)->set("background", background));
|
|
622
|
+
if (baton->keepGainMap) {
|
|
623
|
+
gainMap = gainMap.embed(baton->extendLeft / gainMapScaleFactor, baton->extendTop / gainMapScaleFactor,
|
|
624
|
+
baton->width / gainMapScaleFactor, baton->height / gainMapScaleFactor, VImage::option()
|
|
625
|
+
->set("extend", baton->extendWith)
|
|
626
|
+
->set("background", 0));
|
|
627
|
+
}
|
|
572
628
|
} else {
|
|
573
629
|
std::vector<double> ignoredBackground(1);
|
|
574
630
|
image = sharp::StaySequential(image);
|
|
@@ -578,6 +634,12 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
578
634
|
baton->extendWith, ignoredBackground, nPages, &targetPageHeight)
|
|
579
635
|
: image.embed(baton->extendLeft, baton->extendTop, baton->width, baton->height,
|
|
580
636
|
VImage::option()->set("extend", baton->extendWith));
|
|
637
|
+
if (baton->keepGainMap) {
|
|
638
|
+
gainMap = gainMap.embed(baton->extendLeft / gainMapScaleFactor, baton->extendTop / gainMapScaleFactor,
|
|
639
|
+
baton->width / gainMapScaleFactor, baton->height / gainMapScaleFactor, VImage::option()
|
|
640
|
+
->set("extend", baton->extendWith)
|
|
641
|
+
->set("background", 0));
|
|
642
|
+
}
|
|
581
643
|
}
|
|
582
644
|
}
|
|
583
645
|
// Median - must happen before blurring, due to the utility of blurring after thresholding
|
|
@@ -591,9 +653,22 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
591
653
|
image = sharp::Threshold(image, baton->threshold, baton->thresholdGrayscale);
|
|
592
654
|
}
|
|
593
655
|
|
|
656
|
+
// Dilate - must happen before blurring, due to the utility of dilating after thresholding
|
|
657
|
+
if (baton->dilateWidth != 0) {
|
|
658
|
+
image = sharp::Dilate(image, baton->dilateWidth);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
// Erode - must happen before blurring, due to the utility of eroding after thresholding
|
|
662
|
+
if (baton->erodeWidth != 0) {
|
|
663
|
+
image = sharp::Erode(image, baton->erodeWidth);
|
|
664
|
+
}
|
|
665
|
+
|
|
594
666
|
// Blur
|
|
595
667
|
if (shouldBlur) {
|
|
596
668
|
image = sharp::Blur(image, baton->blurSigma, baton->precision, baton->minAmpl);
|
|
669
|
+
if (baton->keepGainMap) {
|
|
670
|
+
gainMap = sharp::Blur(gainMap, baton->blurSigma, baton->precision, baton->minAmpl);
|
|
671
|
+
}
|
|
597
672
|
}
|
|
598
673
|
|
|
599
674
|
// Unflatten the image
|
|
@@ -603,6 +678,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
603
678
|
|
|
604
679
|
// Convolve
|
|
605
680
|
if (shouldConv) {
|
|
681
|
+
KeepGainMapUnsupported(baton->keepGainMap, "Convolve");
|
|
606
682
|
image = sharp::Convolve(image,
|
|
607
683
|
baton->convKernelWidth, baton->convKernelHeight,
|
|
608
684
|
baton->convKernelScale, baton->convKernelOffset,
|
|
@@ -611,11 +687,13 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
611
687
|
|
|
612
688
|
// Recomb
|
|
613
689
|
if (!baton->recombMatrix.empty()) {
|
|
690
|
+
KeepGainMapUnsupported(baton->keepGainMap, "Recomb");
|
|
614
691
|
image = sharp::Recomb(image, baton->recombMatrix);
|
|
615
692
|
}
|
|
616
693
|
|
|
617
694
|
// Modulate
|
|
618
695
|
if (baton->brightness != 1.0 || baton->saturation != 1.0 || baton->hue != 0.0 || baton->lightness != 0.0) {
|
|
696
|
+
KeepGainMapUnsupported(baton->keepGainMap, "Modulate");
|
|
619
697
|
image = sharp::Modulate(image, baton->brightness, baton->saturation, baton->hue, baton->lightness);
|
|
620
698
|
}
|
|
621
699
|
|
|
@@ -633,6 +711,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
633
711
|
|
|
634
712
|
// Composite
|
|
635
713
|
if (shouldComposite) {
|
|
714
|
+
KeepGainMapUnsupported(baton->keepGainMap, "Composite");
|
|
636
715
|
std::vector<VImage> images = { image };
|
|
637
716
|
std::vector<int> modes, xs, ys;
|
|
638
717
|
for (Composite *composite : baton->composite) {
|
|
@@ -640,10 +719,28 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
640
719
|
sharp::ImageType compositeImageType = sharp::ImageType::UNKNOWN;
|
|
641
720
|
composite->input->access = access;
|
|
642
721
|
std::tie(compositeImage, compositeImageType) = sharp::OpenInput(composite->input);
|
|
643
|
-
|
|
722
|
+
|
|
723
|
+
if (composite->input->autoOrient) {
|
|
724
|
+
// Respect EXIF Orientation
|
|
725
|
+
VipsAngle compositeAutoRotation = VIPS_ANGLE_D0;
|
|
726
|
+
bool compositeAutoFlop = false;
|
|
727
|
+
std::tie(compositeAutoRotation, compositeAutoFlop) =
|
|
728
|
+
CalculateExifRotationAndFlop(sharp::ExifOrientation(compositeImage));
|
|
729
|
+
|
|
730
|
+
compositeImage = sharp::RemoveExifOrientation(compositeImage);
|
|
731
|
+
compositeImage = sharp::StaySequential(compositeImage, compositeAutoRotation != VIPS_ANGLE_D0);
|
|
732
|
+
|
|
733
|
+
if (compositeAutoRotation != VIPS_ANGLE_D0) {
|
|
734
|
+
compositeImage = compositeImage.rot(compositeAutoRotation);
|
|
735
|
+
}
|
|
736
|
+
if (compositeAutoFlop) {
|
|
737
|
+
compositeImage = compositeImage.flip(VIPS_DIRECTION_HORIZONTAL);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
644
741
|
// Verify within current dimensions
|
|
645
742
|
if (compositeImage.width() > image.width() || compositeImage.height() > image.height()) {
|
|
646
|
-
throw
|
|
743
|
+
throw std::runtime_error("Image to composite must have same dimensions or smaller");
|
|
647
744
|
}
|
|
648
745
|
// Check if overlay is tiled
|
|
649
746
|
if (composite->tile) {
|
|
@@ -681,11 +778,8 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
681
778
|
// gravity was used for extract_area, set it back to its default value of 0
|
|
682
779
|
composite->gravity = 0;
|
|
683
780
|
}
|
|
684
|
-
// Ensure image to composite is
|
|
685
|
-
compositeImage = compositeImage
|
|
686
|
-
if (!sharp::HasAlpha(compositeImage)) {
|
|
687
|
-
compositeImage = sharp::EnsureAlpha(compositeImage, 1);
|
|
688
|
-
}
|
|
781
|
+
// Ensure image to composite is with unpremultiplied alpha
|
|
782
|
+
compositeImage = sharp::EnsureAlpha(compositeImage, 1);
|
|
689
783
|
if (composite->premultiplied) compositeImage = compositeImage.unpremultiply();
|
|
690
784
|
// Calculate position
|
|
691
785
|
int left;
|
|
@@ -709,7 +803,12 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
709
803
|
xs.push_back(left);
|
|
710
804
|
ys.push_back(top);
|
|
711
805
|
}
|
|
712
|
-
image = VImage::composite(images, modes, VImage::option()
|
|
806
|
+
image = VImage::composite(images, modes, VImage::option()
|
|
807
|
+
->set("compositing_space", baton->colourspacePipeline == VIPS_INTERPRETATION_LAST
|
|
808
|
+
? VIPS_INTERPRETATION_sRGB
|
|
809
|
+
: baton->colourspacePipeline)
|
|
810
|
+
->set("x", xs)
|
|
811
|
+
->set("y", ys));
|
|
713
812
|
image = sharp::RemoveGifPalette(image);
|
|
714
813
|
}
|
|
715
814
|
|
|
@@ -725,18 +824,21 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
725
824
|
|
|
726
825
|
// Apply normalisation - stretch luminance to cover full dynamic range
|
|
727
826
|
if (baton->normalise) {
|
|
827
|
+
KeepGainMapUnsupported(baton->keepGainMap, "Normalise");
|
|
728
828
|
image = sharp::StaySequential(image);
|
|
729
829
|
image = sharp::Normalise(image, baton->normaliseLower, baton->normaliseUpper);
|
|
730
830
|
}
|
|
731
831
|
|
|
732
832
|
// Apply contrast limiting adaptive histogram equalization (CLAHE)
|
|
733
833
|
if (baton->claheWidth != 0 && baton->claheHeight != 0) {
|
|
834
|
+
KeepGainMapUnsupported(baton->keepGainMap, "Clahe");
|
|
734
835
|
image = sharp::StaySequential(image);
|
|
735
836
|
image = sharp::Clahe(image, baton->claheWidth, baton->claheHeight, baton->claheMaxSlope);
|
|
736
837
|
}
|
|
737
838
|
|
|
738
839
|
// Apply bitwise boolean operation between images
|
|
739
840
|
if (baton->boolean != nullptr) {
|
|
841
|
+
KeepGainMapUnsupported(baton->keepGainMap, "Boolean");
|
|
740
842
|
VImage booleanImage;
|
|
741
843
|
sharp::ImageType booleanImageType = sharp::ImageType::UNKNOWN;
|
|
742
844
|
baton->boolean->access = access;
|
|
@@ -766,27 +868,22 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
766
868
|
image = sharp::EnsureAlpha(image, baton->ensureAlpha);
|
|
767
869
|
}
|
|
768
870
|
|
|
769
|
-
//
|
|
871
|
+
// Ensure output colour space
|
|
770
872
|
if (sharp::Is16Bit(image.interpretation())) {
|
|
771
873
|
image = image.cast(VIPS_FORMAT_USHORT);
|
|
772
874
|
}
|
|
773
875
|
if (image.interpretation() != baton->colourspace) {
|
|
774
|
-
// Convert colourspace, pass the current known interpretation so libvips doesn't have to guess
|
|
775
876
|
image = image.colourspace(baton->colourspace, VImage::option()->set("source_space", image.interpretation()));
|
|
776
|
-
|
|
777
|
-
|
|
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));
|
|
877
|
+
if (inputProfile.first != nullptr && baton->withIccProfile.empty()) {
|
|
878
|
+
image = sharp::SetProfile(image, inputProfile);
|
|
783
879
|
}
|
|
784
880
|
}
|
|
785
881
|
|
|
786
882
|
// Extract channel
|
|
787
883
|
if (baton->extractChannel > -1) {
|
|
884
|
+
KeepGainMapUnsupported(baton->keepGainMap, "Extract channel");
|
|
788
885
|
if (baton->extractChannel >= image.bands()) {
|
|
789
|
-
if (baton->extractChannel == 3 &&
|
|
886
|
+
if (baton->extractChannel == 3 && image.has_alpha()) {
|
|
790
887
|
baton->extractChannel = image.bands() - 1;
|
|
791
888
|
} else {
|
|
792
889
|
(baton->err)
|
|
@@ -814,13 +911,14 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
814
911
|
} catch(...) {
|
|
815
912
|
sharp::VipsWarningCallback(nullptr, G_LOG_LEVEL_WARNING, "Invalid profile", nullptr);
|
|
816
913
|
}
|
|
817
|
-
} else if (baton->keepMetadata & VIPS_FOREIGN_KEEP_ICC) {
|
|
818
|
-
image = sharp::SetProfile(image, inputProfile);
|
|
819
914
|
}
|
|
820
915
|
|
|
821
916
|
// Negate the colours in the image
|
|
822
917
|
if (baton->negate) {
|
|
823
918
|
image = sharp::Negate(image, baton->negateAlpha);
|
|
919
|
+
if (baton->keepGainMap) {
|
|
920
|
+
gainMap = sharp::Negate(gainMap, false);
|
|
921
|
+
}
|
|
824
922
|
}
|
|
825
923
|
|
|
826
924
|
// Override EXIF Orientation tag
|
|
@@ -837,11 +935,16 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
837
935
|
if (!baton->withExifMerge) {
|
|
838
936
|
image = sharp::RemoveExif(image);
|
|
839
937
|
}
|
|
840
|
-
for (const auto&
|
|
841
|
-
image.set(
|
|
938
|
+
for (const auto& [key, value] : baton->withExif) {
|
|
939
|
+
image.set(key.c_str(), value.c_str());
|
|
842
940
|
}
|
|
843
941
|
}
|
|
844
|
-
|
|
942
|
+
// XMP buffer
|
|
943
|
+
if ((baton->keepMetadata & VIPS_FOREIGN_KEEP_XMP) && !baton->withXmp.empty()) {
|
|
944
|
+
image = image.copy();
|
|
945
|
+
image.set(VIPS_META_XMP_NAME, nullptr,
|
|
946
|
+
const_cast<void*>(static_cast<void const*>(baton->withXmp.c_str())), baton->withXmp.size());
|
|
947
|
+
}
|
|
845
948
|
// Number of channels used in output image
|
|
846
949
|
baton->channels = image.bands();
|
|
847
950
|
baton->width = image.width();
|
|
@@ -862,18 +965,27 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
862
965
|
if (baton->formatOut == "jpeg" || (baton->formatOut == "input" && inputImageType == sharp::ImageType::JPEG)) {
|
|
863
966
|
// Write JPEG to buffer
|
|
864
967
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
|
|
865
|
-
VipsArea *area
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
968
|
+
VipsArea *area;
|
|
969
|
+
if (baton->keepGainMap) {
|
|
970
|
+
image = ReattachGainMap(image, gainMap, baton);
|
|
971
|
+
area = reinterpret_cast<VipsArea*>(image.uhdrsave_buffer(VImage::option()
|
|
972
|
+
->set("keep", baton->keepMetadata)
|
|
973
|
+
->set("Q", baton->jpegQuality)
|
|
974
|
+
->set("gainmap_scale_factor", gainMapScaleFactor)));
|
|
975
|
+
} else {
|
|
976
|
+
area = reinterpret_cast<VipsArea*>(image.jpegsave_buffer(VImage::option()
|
|
977
|
+
->set("keep", baton->keepMetadata)
|
|
978
|
+
->set("Q", baton->jpegQuality)
|
|
979
|
+
->set("interlace", baton->jpegProgressive)
|
|
980
|
+
->set("subsample_mode", baton->jpegChromaSubsampling == "4:4:4"
|
|
981
|
+
? VIPS_FOREIGN_SUBSAMPLE_OFF
|
|
982
|
+
: VIPS_FOREIGN_SUBSAMPLE_ON)
|
|
983
|
+
->set("trellis_quant", baton->jpegTrellisQuantisation)
|
|
984
|
+
->set("quant_table", baton->jpegQuantisationTable)
|
|
985
|
+
->set("overshoot_deringing", baton->jpegOvershootDeringing)
|
|
986
|
+
->set("optimize_scans", baton->jpegOptimiseScans)
|
|
987
|
+
->set("optimize_coding", baton->jpegOptimiseCoding)));
|
|
988
|
+
}
|
|
877
989
|
baton->bufferOut = static_cast<char*>(area->data);
|
|
878
990
|
baton->bufferOutLength = area->length;
|
|
879
991
|
area->free_fn = nullptr;
|
|
@@ -929,10 +1041,12 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
929
1041
|
->set("lossless", baton->webpLossless)
|
|
930
1042
|
->set("near_lossless", baton->webpNearLossless)
|
|
931
1043
|
->set("smart_subsample", baton->webpSmartSubsample)
|
|
1044
|
+
->set("smart_deblock", baton->webpSmartDeblock)
|
|
932
1045
|
->set("preset", baton->webpPreset)
|
|
933
1046
|
->set("effort", baton->webpEffort)
|
|
934
1047
|
->set("min_size", baton->webpMinSize)
|
|
935
1048
|
->set("mixed", baton->webpMixed)
|
|
1049
|
+
->set("exact", baton->webpExact)
|
|
936
1050
|
->set("alpha_q", baton->webpAlphaQuality)));
|
|
937
1051
|
baton->bufferOut = static_cast<char*>(area->data);
|
|
938
1052
|
baton->bufferOutLength = area->length;
|
|
@@ -951,6 +1065,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
951
1065
|
->set("interlace", baton->gifProgressive)
|
|
952
1066
|
->set("interframe_maxerror", baton->gifInterFrameMaxError)
|
|
953
1067
|
->set("interpalette_maxerror", baton->gifInterPaletteMaxError)
|
|
1068
|
+
->set("keep_duplicate_frames", baton->gifKeepDuplicateFrames)
|
|
954
1069
|
->set("dither", baton->gifDither)));
|
|
955
1070
|
baton->bufferOut = static_cast<char*>(area->data);
|
|
956
1071
|
baton->bufferOutLength = area->length;
|
|
@@ -973,6 +1088,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
973
1088
|
->set("Q", baton->tiffQuality)
|
|
974
1089
|
->set("bitdepth", baton->tiffBitdepth)
|
|
975
1090
|
->set("compression", baton->tiffCompression)
|
|
1091
|
+
->set("bigtiff", baton->tiffBigtiff)
|
|
976
1092
|
->set("miniswhite", baton->tiffMiniswhite)
|
|
977
1093
|
->set("predictor", baton->tiffPredictor)
|
|
978
1094
|
->set("pyramid", baton->tiffPyramid)
|
|
@@ -991,13 +1107,14 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
991
1107
|
(baton->formatOut == "input" && inputImageType == sharp::ImageType::HEIF)) {
|
|
992
1108
|
// Write HEIF to buffer
|
|
993
1109
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::HEIF);
|
|
994
|
-
image = sharp::RemoveAnimationProperties(image)
|
|
1110
|
+
image = sharp::RemoveAnimationProperties(image);
|
|
995
1111
|
VipsArea *area = reinterpret_cast<VipsArea*>(image.heifsave_buffer(VImage::option()
|
|
996
1112
|
->set("keep", baton->keepMetadata)
|
|
997
1113
|
->set("Q", baton->heifQuality)
|
|
998
1114
|
->set("compression", baton->heifCompression)
|
|
999
1115
|
->set("effort", baton->heifEffort)
|
|
1000
1116
|
->set("bitdepth", baton->heifBitdepth)
|
|
1117
|
+
->set("tune", baton->heifTune.c_str())
|
|
1001
1118
|
->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
|
|
1002
1119
|
? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
|
|
1003
1120
|
->set("lossless", baton->heifLossless)));
|
|
@@ -1009,7 +1126,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1009
1126
|
} else if (baton->formatOut == "dz") {
|
|
1010
1127
|
// Write DZ to buffer
|
|
1011
1128
|
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP;
|
|
1012
|
-
if (!
|
|
1129
|
+
if (!image.has_alpha()) {
|
|
1013
1130
|
baton->tileBackground.pop_back();
|
|
1014
1131
|
}
|
|
1015
1132
|
image = sharp::StaySequential(image, baton->tileAngle != 0);
|
|
@@ -1050,19 +1167,19 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1050
1167
|
// Get raw image data
|
|
1051
1168
|
baton->bufferOut = static_cast<char*>(image.write_to_memory(&baton->bufferOutLength));
|
|
1052
1169
|
if (baton->bufferOut == nullptr) {
|
|
1053
|
-
(
|
|
1054
|
-
return Error();
|
|
1170
|
+
throw std::runtime_error("Could not allocate enough memory for raw output");
|
|
1055
1171
|
}
|
|
1056
1172
|
baton->formatOut = "raw";
|
|
1057
1173
|
} else {
|
|
1058
1174
|
// Unsupported output format
|
|
1059
|
-
(
|
|
1175
|
+
auto unsupported = std::string("Unsupported output format ");
|
|
1060
1176
|
if (baton->formatOut == "input") {
|
|
1061
|
-
|
|
1177
|
+
unsupported.append("when trying to match input format of ");
|
|
1178
|
+
unsupported.append(ImageTypeId(inputImageType));
|
|
1062
1179
|
} else {
|
|
1063
|
-
|
|
1180
|
+
unsupported.append(baton->formatOut);
|
|
1064
1181
|
}
|
|
1065
|
-
|
|
1182
|
+
throw std::runtime_error(unsupported);
|
|
1066
1183
|
}
|
|
1067
1184
|
} else {
|
|
1068
1185
|
// File output
|
|
@@ -1085,18 +1202,26 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1085
1202
|
(willMatchInput && inputImageType == sharp::ImageType::JPEG)) {
|
|
1086
1203
|
// Write JPEG to file
|
|
1087
1204
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
->
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
->
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1205
|
+
if (baton->keepGainMap) {
|
|
1206
|
+
image = ReattachGainMap(image, gainMap, baton);
|
|
1207
|
+
image.uhdrsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
|
1208
|
+
->set("keep", baton->keepMetadata)
|
|
1209
|
+
->set("Q", baton->jpegQuality)
|
|
1210
|
+
->set("gainmap_scale_factor", gainMapScaleFactor));
|
|
1211
|
+
} else {
|
|
1212
|
+
image.jpegsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
|
1213
|
+
->set("keep", baton->keepMetadata)
|
|
1214
|
+
->set("Q", baton->jpegQuality)
|
|
1215
|
+
->set("interlace", baton->jpegProgressive)
|
|
1216
|
+
->set("subsample_mode", baton->jpegChromaSubsampling == "4:4:4"
|
|
1217
|
+
? VIPS_FOREIGN_SUBSAMPLE_OFF
|
|
1218
|
+
: VIPS_FOREIGN_SUBSAMPLE_ON)
|
|
1219
|
+
->set("trellis_quant", baton->jpegTrellisQuantisation)
|
|
1220
|
+
->set("quant_table", baton->jpegQuantisationTable)
|
|
1221
|
+
->set("overshoot_deringing", baton->jpegOvershootDeringing)
|
|
1222
|
+
->set("optimize_scans", baton->jpegOptimiseScans)
|
|
1223
|
+
->set("optimize_coding", baton->jpegOptimiseCoding));
|
|
1224
|
+
}
|
|
1100
1225
|
baton->formatOut = "jpeg";
|
|
1101
1226
|
baton->channels = std::min(baton->channels, 3);
|
|
1102
1227
|
} else if (baton->formatOut == "jp2" || (mightMatchInput && isJp2) ||
|
|
@@ -1136,10 +1261,12 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1136
1261
|
->set("lossless", baton->webpLossless)
|
|
1137
1262
|
->set("near_lossless", baton->webpNearLossless)
|
|
1138
1263
|
->set("smart_subsample", baton->webpSmartSubsample)
|
|
1264
|
+
->set("smart_deblock", baton->webpSmartDeblock)
|
|
1139
1265
|
->set("preset", baton->webpPreset)
|
|
1140
1266
|
->set("effort", baton->webpEffort)
|
|
1141
1267
|
->set("min_size", baton->webpMinSize)
|
|
1142
1268
|
->set("mixed", baton->webpMixed)
|
|
1269
|
+
->set("exact", baton->webpExact)
|
|
1143
1270
|
->set("alpha_q", baton->webpAlphaQuality));
|
|
1144
1271
|
baton->formatOut = "webp";
|
|
1145
1272
|
} else if (baton->formatOut == "gif" || (mightMatchInput && isGif) ||
|
|
@@ -1152,6 +1279,9 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1152
1279
|
->set("effort", baton->gifEffort)
|
|
1153
1280
|
->set("reuse", baton->gifReuse)
|
|
1154
1281
|
->set("interlace", baton->gifProgressive)
|
|
1282
|
+
->set("interframe_maxerror", baton->gifInterFrameMaxError)
|
|
1283
|
+
->set("interpalette_maxerror", baton->gifInterPaletteMaxError)
|
|
1284
|
+
->set("keep_duplicate_frames", baton->gifKeepDuplicateFrames)
|
|
1155
1285
|
->set("dither", baton->gifDither));
|
|
1156
1286
|
baton->formatOut = "gif";
|
|
1157
1287
|
} else if (baton->formatOut == "tiff" || (mightMatchInput && isTiff) ||
|
|
@@ -1170,6 +1300,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1170
1300
|
->set("Q", baton->tiffQuality)
|
|
1171
1301
|
->set("bitdepth", baton->tiffBitdepth)
|
|
1172
1302
|
->set("compression", baton->tiffCompression)
|
|
1303
|
+
->set("bigtiff", baton->tiffBigtiff)
|
|
1173
1304
|
->set("miniswhite", baton->tiffMiniswhite)
|
|
1174
1305
|
->set("predictor", baton->tiffPredictor)
|
|
1175
1306
|
->set("pyramid", baton->tiffPyramid)
|
|
@@ -1184,13 +1315,14 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1184
1315
|
(willMatchInput && inputImageType == sharp::ImageType::HEIF)) {
|
|
1185
1316
|
// Write HEIF to file
|
|
1186
1317
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::HEIF);
|
|
1187
|
-
image = sharp::RemoveAnimationProperties(image)
|
|
1318
|
+
image = sharp::RemoveAnimationProperties(image);
|
|
1188
1319
|
image.heifsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
|
1189
1320
|
->set("keep", baton->keepMetadata)
|
|
1190
1321
|
->set("Q", baton->heifQuality)
|
|
1191
1322
|
->set("compression", baton->heifCompression)
|
|
1192
1323
|
->set("effort", baton->heifEffort)
|
|
1193
1324
|
->set("bitdepth", baton->heifBitdepth)
|
|
1325
|
+
->set("tune", baton->heifTune.c_str())
|
|
1194
1326
|
->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
|
|
1195
1327
|
? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
|
|
1196
1328
|
->set("lossless", baton->heifLossless));
|
|
@@ -1211,7 +1343,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1211
1343
|
if (isDzZip) {
|
|
1212
1344
|
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP;
|
|
1213
1345
|
}
|
|
1214
|
-
if (!
|
|
1346
|
+
if (!image.has_alpha()) {
|
|
1215
1347
|
baton->tileBackground.pop_back();
|
|
1216
1348
|
}
|
|
1217
1349
|
image = sharp::StaySequential(image, baton->tileAngle != 0);
|
|
@@ -1230,12 +1362,17 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1230
1362
|
return Error();
|
|
1231
1363
|
}
|
|
1232
1364
|
}
|
|
1233
|
-
} catch (
|
|
1365
|
+
} catch (std::runtime_error const &err) {
|
|
1234
1366
|
char const *what = err.what();
|
|
1235
1367
|
if (what && what[0]) {
|
|
1236
1368
|
(baton->err).append(what);
|
|
1237
1369
|
} else {
|
|
1238
|
-
(baton->
|
|
1370
|
+
if (baton->input->failOn == VIPS_FAIL_ON_WARNING) {
|
|
1371
|
+
(baton->err).append("Warning treated as error due to failOn setting");
|
|
1372
|
+
baton->errUseWarning = true;
|
|
1373
|
+
} else {
|
|
1374
|
+
(baton->err).append("Unknown error");
|
|
1375
|
+
}
|
|
1239
1376
|
}
|
|
1240
1377
|
}
|
|
1241
1378
|
// Clean up libvips' per-request data and threads
|
|
@@ -1250,10 +1387,13 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1250
1387
|
// Handle warnings
|
|
1251
1388
|
std::string warning = sharp::VipsWarningPop();
|
|
1252
1389
|
while (!warning.empty()) {
|
|
1253
|
-
|
|
1390
|
+
if (baton->errUseWarning) {
|
|
1391
|
+
(baton->err).append("\n").append(warning);
|
|
1392
|
+
} else {
|
|
1393
|
+
debuglog.SHARP_CALLBACK_FN_NAME(Receiver().Value(), { Napi::String::New(env, warning) });
|
|
1394
|
+
}
|
|
1254
1395
|
warning = sharp::VipsWarningPop();
|
|
1255
1396
|
}
|
|
1256
|
-
|
|
1257
1397
|
if (baton->err.empty()) {
|
|
1258
1398
|
int width = baton->width;
|
|
1259
1399
|
int height = baton->height;
|
|
@@ -1296,22 +1436,33 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1296
1436
|
}
|
|
1297
1437
|
|
|
1298
1438
|
if (baton->bufferOutLength > 0) {
|
|
1299
|
-
// Add buffer size to info
|
|
1300
1439
|
info.Set("size", static_cast<uint32_t>(baton->bufferOutLength));
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1440
|
+
if (baton->typedArrayOut) {
|
|
1441
|
+
// ECMAScript ArrayBuffer with Uint8Array view
|
|
1442
|
+
Napi::TypedArrayOf<uint8_t> data = Napi::Buffer<char>::Copy(env,
|
|
1443
|
+
static_cast<char*>(baton->bufferOut), baton->bufferOutLength);
|
|
1444
|
+
sharp::FreeCallback(static_cast<char*>(baton->bufferOut), nullptr);
|
|
1445
|
+
Callback().SHARP_CALLBACK_FN_NAME(Receiver().Value(), { env.Null(), data, info });
|
|
1446
|
+
} else {
|
|
1447
|
+
// Node.js Buffer
|
|
1448
|
+
Napi::Buffer<char> data = Napi::Buffer<char>::NewOrCopy(env, static_cast<char*>(baton->bufferOut),
|
|
1449
|
+
baton->bufferOutLength, sharp::FreeCallback);
|
|
1450
|
+
Callback().SHARP_CALLBACK_FN_NAME(Receiver().Value(), { env.Null(), data, info });
|
|
1451
|
+
}
|
|
1305
1452
|
} else {
|
|
1306
1453
|
// Add file size to info
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1454
|
+
if (baton->formatOut != "dz" || sharp::IsDzZip(baton->fileOut)) {
|
|
1455
|
+
try {
|
|
1456
|
+
uint32_t const size = static_cast<uint32_t>(
|
|
1457
|
+
std::filesystem::file_size(std::filesystem::u8path(baton->fileOut)));
|
|
1458
|
+
info.Set("size", size);
|
|
1459
|
+
} catch (...) {}
|
|
1310
1460
|
}
|
|
1311
|
-
Callback().
|
|
1461
|
+
Callback().SHARP_CALLBACK_FN_NAME(Receiver().Value(), { env.Null(), info });
|
|
1312
1462
|
}
|
|
1313
1463
|
} else {
|
|
1314
|
-
Callback().
|
|
1464
|
+
Callback().SHARP_CALLBACK_FN_NAME(Receiver().Value(),
|
|
1465
|
+
{ Napi::Error::New(env, sharp::TrimEnd(baton->err)).Value() });
|
|
1315
1466
|
}
|
|
1316
1467
|
|
|
1317
1468
|
// Delete baton
|
|
@@ -1324,12 +1475,15 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1324
1475
|
for (sharp::InputDescriptor *input : baton->joinChannelIn) {
|
|
1325
1476
|
delete input;
|
|
1326
1477
|
}
|
|
1478
|
+
for (sharp::InputDescriptor *input : baton->join) {
|
|
1479
|
+
delete input;
|
|
1480
|
+
}
|
|
1327
1481
|
delete baton;
|
|
1328
1482
|
|
|
1329
1483
|
// Decrement processing task counter
|
|
1330
1484
|
sharp::counterProcess--;
|
|
1331
1485
|
Napi::Number queueLength = Napi::Number::New(env, static_cast<int>(sharp::counterQueue));
|
|
1332
|
-
queueListener.
|
|
1486
|
+
queueListener.SHARP_CALLBACK_FN_NAME(Receiver().Value(), { queueLength });
|
|
1333
1487
|
}
|
|
1334
1488
|
|
|
1335
1489
|
private:
|
|
@@ -1339,7 +1493,13 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1339
1493
|
|
|
1340
1494
|
void MultiPageUnsupported(int const pages, std::string op) {
|
|
1341
1495
|
if (pages > 1) {
|
|
1342
|
-
throw
|
|
1496
|
+
throw std::runtime_error(op + " is not supported for multi-page images");
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
void KeepGainMapUnsupported(bool const keepGainMap, std::string op) {
|
|
1501
|
+
if (keepGainMap) {
|
|
1502
|
+
throw std::runtime_error(op + " is not supported when keeping gain maps");
|
|
1343
1503
|
}
|
|
1344
1504
|
}
|
|
1345
1505
|
|
|
@@ -1347,21 +1507,20 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1347
1507
|
Calculate the angle of rotation and need-to-flip for the given Exif orientation
|
|
1348
1508
|
By default, returns zero, i.e. no rotation.
|
|
1349
1509
|
*/
|
|
1350
|
-
std::tuple<VipsAngle, bool
|
|
1351
|
-
|
|
1510
|
+
std::tuple<VipsAngle, bool>
|
|
1511
|
+
CalculateExifRotationAndFlop(int const exifOrientation) {
|
|
1352
1512
|
VipsAngle rotate = VIPS_ANGLE_D0;
|
|
1353
|
-
bool flip = false;
|
|
1354
1513
|
bool flop = false;
|
|
1355
1514
|
switch (exifOrientation) {
|
|
1356
1515
|
case 6: rotate = VIPS_ANGLE_D90; break;
|
|
1357
1516
|
case 3: rotate = VIPS_ANGLE_D180; break;
|
|
1358
1517
|
case 8: rotate = VIPS_ANGLE_D270; break;
|
|
1359
|
-
case 2: flop = true; break;
|
|
1360
|
-
case 7:
|
|
1361
|
-
case 4: flop = true; rotate = VIPS_ANGLE_D180; break;
|
|
1362
|
-
case 5:
|
|
1518
|
+
case 2: flop = true; break;
|
|
1519
|
+
case 7: flop = true; rotate = VIPS_ANGLE_D270; break;
|
|
1520
|
+
case 4: flop = true; rotate = VIPS_ANGLE_D180; break;
|
|
1521
|
+
case 5: flop = true; rotate = VIPS_ANGLE_D90; break;
|
|
1363
1522
|
}
|
|
1364
|
-
return std::make_tuple(rotate,
|
|
1523
|
+
return std::make_tuple(rotate, flop);
|
|
1365
1524
|
}
|
|
1366
1525
|
|
|
1367
1526
|
/*
|
|
@@ -1389,11 +1548,11 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1389
1548
|
std::string
|
|
1390
1549
|
AssembleSuffixString(std::string extname, std::vector<std::pair<std::string, std::string>> options) {
|
|
1391
1550
|
std::string argument;
|
|
1392
|
-
for (
|
|
1551
|
+
for (const auto& [key, value] : options) {
|
|
1393
1552
|
if (!argument.empty()) {
|
|
1394
1553
|
argument += ",";
|
|
1395
1554
|
}
|
|
1396
|
-
argument +=
|
|
1555
|
+
argument += key + "=" + value;
|
|
1397
1556
|
}
|
|
1398
1557
|
return extname + "[" + argument + "]";
|
|
1399
1558
|
}
|
|
@@ -1419,9 +1578,11 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1419
1578
|
{"lossless", baton->webpLossless ? "true" : "false"},
|
|
1420
1579
|
{"near_lossless", baton->webpNearLossless ? "true" : "false"},
|
|
1421
1580
|
{"smart_subsample", baton->webpSmartSubsample ? "true" : "false"},
|
|
1581
|
+
{"smart_deblock", baton->webpSmartDeblock ? "true" : "false"},
|
|
1422
1582
|
{"preset", vips_enum_nick(VIPS_TYPE_FOREIGN_WEBP_PRESET, baton->webpPreset)},
|
|
1423
1583
|
{"min_size", baton->webpMinSize ? "true" : "false"},
|
|
1424
1584
|
{"mixed", baton->webpMixed ? "true" : "false"},
|
|
1585
|
+
{"exact", baton->webpExact ? "true" : "false"},
|
|
1425
1586
|
{"effort", std::to_string(baton->webpEffort)}
|
|
1426
1587
|
};
|
|
1427
1588
|
suffix = AssembleSuffixString(".webp", options);
|
|
@@ -1460,6 +1621,21 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1460
1621
|
return options;
|
|
1461
1622
|
}
|
|
1462
1623
|
|
|
1624
|
+
VImage ReattachGainMap(VImage image, VImage gainMap, PipelineBaton *baton) {
|
|
1625
|
+
VipsArea *gainMapJpeg = reinterpret_cast<VipsArea*>(gainMap.jpegsave_buffer(VImage::option()
|
|
1626
|
+
->set("keep", FALSE)
|
|
1627
|
+
->set("Q", baton->jpegQuality)
|
|
1628
|
+
->set("subsample_mode", VIPS_FOREIGN_SUBSAMPLE_OFF)
|
|
1629
|
+
->set("trellis_quant", baton->jpegTrellisQuantisation)
|
|
1630
|
+
->set("quant_table", baton->jpegQuantisationTable)
|
|
1631
|
+
->set("overshoot_deringing", baton->jpegOvershootDeringing)
|
|
1632
|
+
->set("optimize_coding", baton->jpegOptimiseCoding)));
|
|
1633
|
+
image = image.copy();
|
|
1634
|
+
image.set("gainmap-data", reinterpret_cast<VipsCallbackFn>(vips_area_free_cb),
|
|
1635
|
+
gainMapJpeg->data, gainMapJpeg->length);
|
|
1636
|
+
return image;
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1463
1639
|
/*
|
|
1464
1640
|
Clear all thread-local data.
|
|
1465
1641
|
*/
|
|
@@ -1480,6 +1656,14 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1480
1656
|
|
|
1481
1657
|
// Input
|
|
1482
1658
|
baton->input = sharp::CreateInputDescriptor(options.Get("input").As<Napi::Object>());
|
|
1659
|
+
// Join images together
|
|
1660
|
+
if (sharp::HasAttr(options, "join")) {
|
|
1661
|
+
Napi::Array join = options.Get("join").As<Napi::Array>();
|
|
1662
|
+
for (unsigned int i = 0; i < join.Length(); i++) {
|
|
1663
|
+
baton->join.push_back(
|
|
1664
|
+
sharp::CreateInputDescriptor(join.Get(i).As<Napi::Object>()));
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1483
1667
|
// Extract image options
|
|
1484
1668
|
baton->topOffsetPre = sharp::AttrAsInt32(options, "topOffsetPre");
|
|
1485
1669
|
baton->leftOffsetPre = sharp::AttrAsInt32(options, "leftOffsetPre");
|
|
@@ -1560,10 +1744,13 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1560
1744
|
baton->trimBackground = sharp::AttrAsVectorOfDouble(options, "trimBackground");
|
|
1561
1745
|
baton->trimThreshold = sharp::AttrAsDouble(options, "trimThreshold");
|
|
1562
1746
|
baton->trimLineArt = sharp::AttrAsBool(options, "trimLineArt");
|
|
1747
|
+
baton->trimMargin = sharp::AttrAsUint32(options, "trimMargin");
|
|
1563
1748
|
baton->gamma = sharp::AttrAsDouble(options, "gamma");
|
|
1564
1749
|
baton->gammaOut = sharp::AttrAsDouble(options, "gammaOut");
|
|
1565
1750
|
baton->linearA = sharp::AttrAsVectorOfDouble(options, "linearA");
|
|
1566
1751
|
baton->linearB = sharp::AttrAsVectorOfDouble(options, "linearB");
|
|
1752
|
+
baton->dilateWidth = sharp::AttrAsUint32(options, "dilateWidth");
|
|
1753
|
+
baton->erodeWidth = sharp::AttrAsUint32(options, "erodeWidth");
|
|
1567
1754
|
baton->greyscale = sharp::AttrAsBool(options, "greyscale");
|
|
1568
1755
|
baton->normalise = sharp::AttrAsBool(options, "normalise");
|
|
1569
1756
|
baton->normaliseLower = sharp::AttrAsUint32(options, "normaliseLower");
|
|
@@ -1572,11 +1759,11 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1572
1759
|
baton->claheWidth = sharp::AttrAsUint32(options, "claheWidth");
|
|
1573
1760
|
baton->claheHeight = sharp::AttrAsUint32(options, "claheHeight");
|
|
1574
1761
|
baton->claheMaxSlope = sharp::AttrAsUint32(options, "claheMaxSlope");
|
|
1575
|
-
baton->useExifOrientation = sharp::AttrAsBool(options, "useExifOrientation");
|
|
1576
1762
|
baton->angle = sharp::AttrAsInt32(options, "angle");
|
|
1577
1763
|
baton->rotationAngle = sharp::AttrAsDouble(options, "rotationAngle");
|
|
1578
1764
|
baton->rotationBackground = sharp::AttrAsVectorOfDouble(options, "rotationBackground");
|
|
1579
|
-
baton->
|
|
1765
|
+
baton->rotateBefore = sharp::AttrAsBool(options, "rotateBefore");
|
|
1766
|
+
baton->orientBefore = sharp::AttrAsBool(options, "orientBefore");
|
|
1580
1767
|
baton->flip = sharp::AttrAsBool(options, "flip");
|
|
1581
1768
|
baton->flop = sharp::AttrAsBool(options, "flop");
|
|
1582
1769
|
baton->extendTop = sharp::AttrAsInt32(options, "extendTop");
|
|
@@ -1635,6 +1822,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1635
1822
|
// Output
|
|
1636
1823
|
baton->formatOut = sharp::AttrAsStr(options, "formatOut");
|
|
1637
1824
|
baton->fileOut = sharp::AttrAsStr(options, "fileOut");
|
|
1825
|
+
baton->typedArrayOut = sharp::AttrAsBool(options, "typedArrayOut");
|
|
1638
1826
|
baton->keepMetadata = sharp::AttrAsUint32(options, "keepMetadata");
|
|
1639
1827
|
baton->withMetadataOrientation = sharp::AttrAsUint32(options, "withMetadataOrientation");
|
|
1640
1828
|
baton->withMetadataDensity = sharp::AttrAsDouble(options, "withMetadataDensity");
|
|
@@ -1648,7 +1836,12 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1648
1836
|
}
|
|
1649
1837
|
}
|
|
1650
1838
|
baton->withExifMerge = sharp::AttrAsBool(options, "withExifMerge");
|
|
1839
|
+
baton->withXmp = sharp::AttrAsStr(options, "withXmp");
|
|
1840
|
+
baton->keepGainMap = sharp::AttrAsBool(options, "keepGainMap");
|
|
1841
|
+
baton->withGainMap = sharp::AttrAsBool(options, "withGainMap");
|
|
1651
1842
|
baton->timeoutSeconds = sharp::AttrAsUint32(options, "timeoutSeconds");
|
|
1843
|
+
baton->loop = sharp::AttrAsUint32(options, "loop");
|
|
1844
|
+
baton->delay = sharp::AttrAsInt32Vector(options, "delay");
|
|
1652
1845
|
// Format-specific
|
|
1653
1846
|
baton->jpegQuality = sharp::AttrAsUint32(options, "jpegQuality");
|
|
1654
1847
|
baton->jpegProgressive = sharp::AttrAsBool(options, "jpegProgressive");
|
|
@@ -1676,18 +1869,22 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1676
1869
|
baton->webpLossless = sharp::AttrAsBool(options, "webpLossless");
|
|
1677
1870
|
baton->webpNearLossless = sharp::AttrAsBool(options, "webpNearLossless");
|
|
1678
1871
|
baton->webpSmartSubsample = sharp::AttrAsBool(options, "webpSmartSubsample");
|
|
1872
|
+
baton->webpSmartDeblock = sharp::AttrAsBool(options, "webpSmartDeblock");
|
|
1679
1873
|
baton->webpPreset = sharp::AttrAsEnum<VipsForeignWebpPreset>(options, "webpPreset", VIPS_TYPE_FOREIGN_WEBP_PRESET);
|
|
1680
1874
|
baton->webpEffort = sharp::AttrAsUint32(options, "webpEffort");
|
|
1681
1875
|
baton->webpMinSize = sharp::AttrAsBool(options, "webpMinSize");
|
|
1682
1876
|
baton->webpMixed = sharp::AttrAsBool(options, "webpMixed");
|
|
1877
|
+
baton->webpExact = sharp::AttrAsBool(options, "webpExact");
|
|
1683
1878
|
baton->gifBitdepth = sharp::AttrAsUint32(options, "gifBitdepth");
|
|
1684
1879
|
baton->gifEffort = sharp::AttrAsUint32(options, "gifEffort");
|
|
1685
1880
|
baton->gifDither = sharp::AttrAsDouble(options, "gifDither");
|
|
1686
1881
|
baton->gifInterFrameMaxError = sharp::AttrAsDouble(options, "gifInterFrameMaxError");
|
|
1687
1882
|
baton->gifInterPaletteMaxError = sharp::AttrAsDouble(options, "gifInterPaletteMaxError");
|
|
1883
|
+
baton->gifKeepDuplicateFrames = sharp::AttrAsBool(options, "gifKeepDuplicateFrames");
|
|
1688
1884
|
baton->gifReuse = sharp::AttrAsBool(options, "gifReuse");
|
|
1689
1885
|
baton->gifProgressive = sharp::AttrAsBool(options, "gifProgressive");
|
|
1690
1886
|
baton->tiffQuality = sharp::AttrAsUint32(options, "tiffQuality");
|
|
1887
|
+
baton->tiffBigtiff = sharp::AttrAsBool(options, "tiffBigtiff");
|
|
1691
1888
|
baton->tiffPyramid = sharp::AttrAsBool(options, "tiffPyramid");
|
|
1692
1889
|
baton->tiffMiniswhite = sharp::AttrAsBool(options, "tiffMiniswhite");
|
|
1693
1890
|
baton->tiffBitdepth = sharp::AttrAsUint32(options, "tiffBitdepth");
|
|
@@ -1712,18 +1909,12 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1712
1909
|
baton->heifEffort = sharp::AttrAsUint32(options, "heifEffort");
|
|
1713
1910
|
baton->heifChromaSubsampling = sharp::AttrAsStr(options, "heifChromaSubsampling");
|
|
1714
1911
|
baton->heifBitdepth = sharp::AttrAsUint32(options, "heifBitdepth");
|
|
1912
|
+
baton->heifTune = sharp::AttrAsStr(options, "heifTune");
|
|
1715
1913
|
baton->jxlDistance = sharp::AttrAsDouble(options, "jxlDistance");
|
|
1716
1914
|
baton->jxlDecodingTier = sharp::AttrAsUint32(options, "jxlDecodingTier");
|
|
1717
1915
|
baton->jxlEffort = sharp::AttrAsUint32(options, "jxlEffort");
|
|
1718
1916
|
baton->jxlLossless = sharp::AttrAsBool(options, "jxlLossless");
|
|
1719
1917
|
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
1918
|
baton->tileSize = sharp::AttrAsUint32(options, "tileSize");
|
|
1728
1919
|
baton->tileOverlap = sharp::AttrAsUint32(options, "tileOverlap");
|
|
1729
1920
|
baton->tileAngle = sharp::AttrAsInt32(options, "tileAngle");
|
|
@@ -1752,7 +1943,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1752
1943
|
|
|
1753
1944
|
// Increment queued task counter
|
|
1754
1945
|
Napi::Number queueLength = Napi::Number::New(info.Env(), static_cast<int>(++sharp::counterQueue));
|
|
1755
|
-
queueListener.
|
|
1946
|
+
queueListener.SHARP_CALLBACK_FN_NAME(info.This(), { queueLength });
|
|
1756
1947
|
|
|
1757
1948
|
return info.Env().Undefined();
|
|
1758
1949
|
}
|