sharp 0.33.5 → 0.35.3
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 +199 -0
- package/{lib/colour.js → dist/colour.mjs} +42 -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.d.cts +1999 -0
- package/dist/index.d.mts +2046 -0
- package/dist/index.mjs +25 -0
- package/dist/input.cjs +814 -0
- package/{lib/input.js → dist/input.mjs} +232 -76
- 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 +998 -0
- package/{lib/operation.js → dist/operation.mjs} +131 -91
- package/{lib/output.js → dist/output.cjs} +262 -61
- package/dist/output.mjs +1788 -0
- package/dist/resize.cjs +617 -0
- package/{lib/resize.js → dist/resize.mjs} +65 -35
- package/dist/sharp.cjs +174 -0
- package/dist/sharp.mjs +174 -0
- package/{lib/utility.js → dist/utility.cjs} +30 -25
- package/dist/utility.mjs +301 -0
- package/install/build.js +38 -0
- package/lib/index.d.ts +387 -142
- package/package.json +90 -85
- 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 +391 -201
- package/src/pipeline.h +36 -7
- package/src/sharp.cc +11 -8
- package/src/stats.cc +16 -13
- package/src/stats.h +6 -3
- package/src/utilities.cc +35 -15
- 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,13 +288,31 @@ 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");
|
|
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();
|
|
295
311
|
}
|
|
312
|
+
image = sharp::RemoveGainMap(image);
|
|
313
|
+
} else {
|
|
314
|
+
baton->keepGainMap = false;
|
|
315
|
+
baton->withGainMap = false;
|
|
296
316
|
}
|
|
297
317
|
|
|
298
318
|
// Any pre-shrinking may already have been done
|
|
@@ -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);
|
|
@@ -558,26 +608,25 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
558
608
|
baton->width = image.width() + baton->extendLeft + baton->extendRight;
|
|
559
609
|
baton->height = (nPages > 1 ? targetPageHeight : image.height()) + baton->extendTop + baton->extendBottom;
|
|
560
610
|
|
|
611
|
+
std::vector<double> background;
|
|
561
612
|
if (baton->extendWith == VIPS_EXTEND_BACKGROUND) {
|
|
562
|
-
std::vector<double> background;
|
|
563
613
|
std::tie(image, background) = sharp::ApplyAlpha(image, baton->extendBackground, shouldPremultiplyAlpha);
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
image
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
VImage::option()->set("extend", baton->extendWith));
|
|
614
|
+
}
|
|
615
|
+
image = sharp::StaySequential(image, nPages > 1 || baton->extendWith != VIPS_EXTEND_BACKGROUND);
|
|
616
|
+
auto options = VImage::option()->set("extend", baton->extendWith);
|
|
617
|
+
if (baton->extendWith == VIPS_EXTEND_BACKGROUND) {
|
|
618
|
+
options->set("background", background);
|
|
619
|
+
}
|
|
620
|
+
image = nPages > 1
|
|
621
|
+
? sharp::EmbedMultiPage(image,
|
|
622
|
+
baton->extendLeft, baton->extendTop, baton->width, baton->height,
|
|
623
|
+
baton->extendWith, background, nPages, &targetPageHeight)
|
|
624
|
+
: image.embed(baton->extendLeft, baton->extendTop, baton->width, baton->height, options);
|
|
625
|
+
if (baton->keepGainMap) {
|
|
626
|
+
gainMap = gainMap.embed(baton->extendLeft / gainMapScaleFactor, baton->extendTop / gainMapScaleFactor,
|
|
627
|
+
baton->width / gainMapScaleFactor, baton->height / gainMapScaleFactor, VImage::option()
|
|
628
|
+
->set("extend", baton->extendWith)
|
|
629
|
+
->set("background", 0));
|
|
581
630
|
}
|
|
582
631
|
}
|
|
583
632
|
// Median - must happen before blurring, due to the utility of blurring after thresholding
|
|
@@ -591,9 +640,22 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
591
640
|
image = sharp::Threshold(image, baton->threshold, baton->thresholdGrayscale);
|
|
592
641
|
}
|
|
593
642
|
|
|
643
|
+
// Dilate - must happen before blurring, due to the utility of dilating after thresholding
|
|
644
|
+
if (baton->dilateWidth != 0) {
|
|
645
|
+
image = sharp::Dilate(image, baton->dilateWidth);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// Erode - must happen before blurring, due to the utility of eroding after thresholding
|
|
649
|
+
if (baton->erodeWidth != 0) {
|
|
650
|
+
image = sharp::Erode(image, baton->erodeWidth);
|
|
651
|
+
}
|
|
652
|
+
|
|
594
653
|
// Blur
|
|
595
654
|
if (shouldBlur) {
|
|
596
655
|
image = sharp::Blur(image, baton->blurSigma, baton->precision, baton->minAmpl);
|
|
656
|
+
if (baton->keepGainMap) {
|
|
657
|
+
gainMap = sharp::Blur(gainMap, baton->blurSigma, baton->precision, baton->minAmpl);
|
|
658
|
+
}
|
|
597
659
|
}
|
|
598
660
|
|
|
599
661
|
// Unflatten the image
|
|
@@ -603,6 +665,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
603
665
|
|
|
604
666
|
// Convolve
|
|
605
667
|
if (shouldConv) {
|
|
668
|
+
KeepGainMapUnsupported(baton->keepGainMap, "Convolve");
|
|
606
669
|
image = sharp::Convolve(image,
|
|
607
670
|
baton->convKernelWidth, baton->convKernelHeight,
|
|
608
671
|
baton->convKernelScale, baton->convKernelOffset,
|
|
@@ -611,11 +674,13 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
611
674
|
|
|
612
675
|
// Recomb
|
|
613
676
|
if (!baton->recombMatrix.empty()) {
|
|
677
|
+
KeepGainMapUnsupported(baton->keepGainMap, "Recomb");
|
|
614
678
|
image = sharp::Recomb(image, baton->recombMatrix);
|
|
615
679
|
}
|
|
616
680
|
|
|
617
681
|
// Modulate
|
|
618
682
|
if (baton->brightness != 1.0 || baton->saturation != 1.0 || baton->hue != 0.0 || baton->lightness != 0.0) {
|
|
683
|
+
KeepGainMapUnsupported(baton->keepGainMap, "Modulate");
|
|
619
684
|
image = sharp::Modulate(image, baton->brightness, baton->saturation, baton->hue, baton->lightness);
|
|
620
685
|
}
|
|
621
686
|
|
|
@@ -633,6 +698,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
633
698
|
|
|
634
699
|
// Composite
|
|
635
700
|
if (shouldComposite) {
|
|
701
|
+
KeepGainMapUnsupported(baton->keepGainMap, "Composite");
|
|
636
702
|
std::vector<VImage> images = { image };
|
|
637
703
|
std::vector<int> modes, xs, ys;
|
|
638
704
|
for (Composite *composite : baton->composite) {
|
|
@@ -640,10 +706,28 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
640
706
|
sharp::ImageType compositeImageType = sharp::ImageType::UNKNOWN;
|
|
641
707
|
composite->input->access = access;
|
|
642
708
|
std::tie(compositeImage, compositeImageType) = sharp::OpenInput(composite->input);
|
|
643
|
-
|
|
709
|
+
|
|
710
|
+
if (composite->input->autoOrient) {
|
|
711
|
+
// Respect EXIF Orientation
|
|
712
|
+
VipsAngle compositeAutoRotation = VIPS_ANGLE_D0;
|
|
713
|
+
bool compositeAutoFlop = false;
|
|
714
|
+
std::tie(compositeAutoRotation, compositeAutoFlop) =
|
|
715
|
+
CalculateExifRotationAndFlop(sharp::ExifOrientation(compositeImage));
|
|
716
|
+
|
|
717
|
+
compositeImage = sharp::RemoveExifOrientation(compositeImage);
|
|
718
|
+
compositeImage = sharp::StaySequential(compositeImage, compositeAutoRotation != VIPS_ANGLE_D0);
|
|
719
|
+
|
|
720
|
+
if (compositeAutoRotation != VIPS_ANGLE_D0) {
|
|
721
|
+
compositeImage = compositeImage.rot(compositeAutoRotation);
|
|
722
|
+
}
|
|
723
|
+
if (compositeAutoFlop) {
|
|
724
|
+
compositeImage = compositeImage.flip(VIPS_DIRECTION_HORIZONTAL);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
644
728
|
// Verify within current dimensions
|
|
645
729
|
if (compositeImage.width() > image.width() || compositeImage.height() > image.height()) {
|
|
646
|
-
throw
|
|
730
|
+
throw std::runtime_error("Image to composite must have same dimensions or smaller");
|
|
647
731
|
}
|
|
648
732
|
// Check if overlay is tiled
|
|
649
733
|
if (composite->tile) {
|
|
@@ -681,11 +765,8 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
681
765
|
// gravity was used for extract_area, set it back to its default value of 0
|
|
682
766
|
composite->gravity = 0;
|
|
683
767
|
}
|
|
684
|
-
// Ensure image to composite is
|
|
685
|
-
compositeImage = compositeImage
|
|
686
|
-
if (!sharp::HasAlpha(compositeImage)) {
|
|
687
|
-
compositeImage = sharp::EnsureAlpha(compositeImage, 1);
|
|
688
|
-
}
|
|
768
|
+
// Ensure image to composite is with unpremultiplied alpha
|
|
769
|
+
compositeImage = sharp::EnsureAlpha(compositeImage, 1);
|
|
689
770
|
if (composite->premultiplied) compositeImage = compositeImage.unpremultiply();
|
|
690
771
|
// Calculate position
|
|
691
772
|
int left;
|
|
@@ -709,7 +790,12 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
709
790
|
xs.push_back(left);
|
|
710
791
|
ys.push_back(top);
|
|
711
792
|
}
|
|
712
|
-
image = VImage::composite(images, modes, VImage::option()
|
|
793
|
+
image = VImage::composite(images, modes, VImage::option()
|
|
794
|
+
->set("compositing_space", baton->colourspacePipeline == VIPS_INTERPRETATION_LAST
|
|
795
|
+
? VIPS_INTERPRETATION_sRGB
|
|
796
|
+
: baton->colourspacePipeline)
|
|
797
|
+
->set("x", xs)
|
|
798
|
+
->set("y", ys));
|
|
713
799
|
image = sharp::RemoveGifPalette(image);
|
|
714
800
|
}
|
|
715
801
|
|
|
@@ -725,18 +811,21 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
725
811
|
|
|
726
812
|
// Apply normalisation - stretch luminance to cover full dynamic range
|
|
727
813
|
if (baton->normalise) {
|
|
814
|
+
KeepGainMapUnsupported(baton->keepGainMap, "Normalise");
|
|
728
815
|
image = sharp::StaySequential(image);
|
|
729
816
|
image = sharp::Normalise(image, baton->normaliseLower, baton->normaliseUpper);
|
|
730
817
|
}
|
|
731
818
|
|
|
732
819
|
// Apply contrast limiting adaptive histogram equalization (CLAHE)
|
|
733
820
|
if (baton->claheWidth != 0 && baton->claheHeight != 0) {
|
|
821
|
+
KeepGainMapUnsupported(baton->keepGainMap, "Clahe");
|
|
734
822
|
image = sharp::StaySequential(image);
|
|
735
823
|
image = sharp::Clahe(image, baton->claheWidth, baton->claheHeight, baton->claheMaxSlope);
|
|
736
824
|
}
|
|
737
825
|
|
|
738
826
|
// Apply bitwise boolean operation between images
|
|
739
827
|
if (baton->boolean != nullptr) {
|
|
828
|
+
KeepGainMapUnsupported(baton->keepGainMap, "Boolean");
|
|
740
829
|
VImage booleanImage;
|
|
741
830
|
sharp::ImageType booleanImageType = sharp::ImageType::UNKNOWN;
|
|
742
831
|
baton->boolean->access = access;
|
|
@@ -766,27 +855,22 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
766
855
|
image = sharp::EnsureAlpha(image, baton->ensureAlpha);
|
|
767
856
|
}
|
|
768
857
|
|
|
769
|
-
//
|
|
858
|
+
// Ensure output colour space
|
|
770
859
|
if (sharp::Is16Bit(image.interpretation())) {
|
|
771
860
|
image = image.cast(VIPS_FORMAT_USHORT);
|
|
772
861
|
}
|
|
773
862
|
if (image.interpretation() != baton->colourspace) {
|
|
774
|
-
// Convert colourspace, pass the current known interpretation so libvips doesn't have to guess
|
|
775
863
|
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));
|
|
864
|
+
if (inputProfile.first != nullptr && baton->withIccProfile.empty()) {
|
|
865
|
+
image = sharp::SetProfile(image, inputProfile);
|
|
783
866
|
}
|
|
784
867
|
}
|
|
785
868
|
|
|
786
869
|
// Extract channel
|
|
787
870
|
if (baton->extractChannel > -1) {
|
|
871
|
+
KeepGainMapUnsupported(baton->keepGainMap, "Extract channel");
|
|
788
872
|
if (baton->extractChannel >= image.bands()) {
|
|
789
|
-
if (baton->extractChannel == 3 &&
|
|
873
|
+
if (baton->extractChannel == 3 && image.has_alpha()) {
|
|
790
874
|
baton->extractChannel = image.bands() - 1;
|
|
791
875
|
} else {
|
|
792
876
|
(baton->err)
|
|
@@ -814,13 +898,14 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
814
898
|
} catch(...) {
|
|
815
899
|
sharp::VipsWarningCallback(nullptr, G_LOG_LEVEL_WARNING, "Invalid profile", nullptr);
|
|
816
900
|
}
|
|
817
|
-
} else if (baton->keepMetadata & VIPS_FOREIGN_KEEP_ICC) {
|
|
818
|
-
image = sharp::SetProfile(image, inputProfile);
|
|
819
901
|
}
|
|
820
902
|
|
|
821
903
|
// Negate the colours in the image
|
|
822
904
|
if (baton->negate) {
|
|
823
905
|
image = sharp::Negate(image, baton->negateAlpha);
|
|
906
|
+
if (baton->keepGainMap) {
|
|
907
|
+
gainMap = sharp::Negate(gainMap, false);
|
|
908
|
+
}
|
|
824
909
|
}
|
|
825
910
|
|
|
826
911
|
// Override EXIF Orientation tag
|
|
@@ -837,11 +922,16 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
837
922
|
if (!baton->withExifMerge) {
|
|
838
923
|
image = sharp::RemoveExif(image);
|
|
839
924
|
}
|
|
840
|
-
for (const auto&
|
|
841
|
-
image.set(
|
|
925
|
+
for (const auto& [key, value] : baton->withExif) {
|
|
926
|
+
image.set(key.c_str(), value.c_str());
|
|
842
927
|
}
|
|
843
928
|
}
|
|
844
|
-
|
|
929
|
+
// XMP buffer
|
|
930
|
+
if ((baton->keepMetadata & VIPS_FOREIGN_KEEP_XMP) && !baton->withXmp.empty()) {
|
|
931
|
+
image = image.copy();
|
|
932
|
+
image.set(VIPS_META_XMP_NAME, nullptr,
|
|
933
|
+
const_cast<void*>(static_cast<void const*>(baton->withXmp.c_str())), baton->withXmp.size());
|
|
934
|
+
}
|
|
845
935
|
// Number of channels used in output image
|
|
846
936
|
baton->channels = image.bands();
|
|
847
937
|
baton->width = image.width();
|
|
@@ -854,6 +944,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
854
944
|
baton->pageHeightOut = image.get_int(VIPS_META_PAGE_HEIGHT);
|
|
855
945
|
baton->pagesOut = image.get_int(VIPS_META_N_PAGES);
|
|
856
946
|
}
|
|
947
|
+
baton->hasAlphaOut = image.has_alpha();
|
|
857
948
|
|
|
858
949
|
// Output
|
|
859
950
|
sharp::SetTimeout(image, baton->timeoutSeconds);
|
|
@@ -862,18 +953,27 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
862
953
|
if (baton->formatOut == "jpeg" || (baton->formatOut == "input" && inputImageType == sharp::ImageType::JPEG)) {
|
|
863
954
|
// Write JPEG to buffer
|
|
864
955
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
|
|
865
|
-
VipsArea *area
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
956
|
+
VipsArea *area;
|
|
957
|
+
if (baton->keepGainMap) {
|
|
958
|
+
image = ReattachGainMap(image, gainMap, baton);
|
|
959
|
+
area = reinterpret_cast<VipsArea*>(image.uhdrsave_buffer(VImage::option()
|
|
960
|
+
->set("keep", baton->keepMetadata)
|
|
961
|
+
->set("Q", baton->jpegQuality)
|
|
962
|
+
->set("gainmap_scale_factor", gainMapScaleFactor)));
|
|
963
|
+
} else {
|
|
964
|
+
area = reinterpret_cast<VipsArea*>(image.jpegsave_buffer(VImage::option()
|
|
965
|
+
->set("keep", baton->keepMetadata)
|
|
966
|
+
->set("Q", baton->jpegQuality)
|
|
967
|
+
->set("interlace", baton->jpegProgressive)
|
|
968
|
+
->set("subsample_mode", baton->jpegChromaSubsampling == "4:4:4"
|
|
969
|
+
? VIPS_FOREIGN_SUBSAMPLE_OFF
|
|
970
|
+
: VIPS_FOREIGN_SUBSAMPLE_ON)
|
|
971
|
+
->set("trellis_quant", baton->jpegTrellisQuantisation)
|
|
972
|
+
->set("quant_table", baton->jpegQuantisationTable)
|
|
973
|
+
->set("overshoot_deringing", baton->jpegOvershootDeringing)
|
|
974
|
+
->set("optimize_scans", baton->jpegOptimiseScans)
|
|
975
|
+
->set("optimize_coding", baton->jpegOptimiseCoding)));
|
|
976
|
+
}
|
|
877
977
|
baton->bufferOut = static_cast<char*>(area->data);
|
|
878
978
|
baton->bufferOutLength = area->length;
|
|
879
979
|
area->free_fn = nullptr;
|
|
@@ -884,6 +984,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
884
984
|
} else {
|
|
885
985
|
baton->channels = std::min(baton->channels, 3);
|
|
886
986
|
}
|
|
987
|
+
baton->hasAlphaOut = false;
|
|
887
988
|
} else if (baton->formatOut == "jp2" || (baton->formatOut == "input"
|
|
888
989
|
&& inputImageType == sharp::ImageType::JP2)) {
|
|
889
990
|
// Write JP2 to Buffer
|
|
@@ -929,10 +1030,12 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
929
1030
|
->set("lossless", baton->webpLossless)
|
|
930
1031
|
->set("near_lossless", baton->webpNearLossless)
|
|
931
1032
|
->set("smart_subsample", baton->webpSmartSubsample)
|
|
1033
|
+
->set("smart_deblock", baton->webpSmartDeblock)
|
|
932
1034
|
->set("preset", baton->webpPreset)
|
|
933
1035
|
->set("effort", baton->webpEffort)
|
|
934
1036
|
->set("min_size", baton->webpMinSize)
|
|
935
1037
|
->set("mixed", baton->webpMixed)
|
|
1038
|
+
->set("exact", baton->webpExact)
|
|
936
1039
|
->set("alpha_q", baton->webpAlphaQuality)));
|
|
937
1040
|
baton->bufferOut = static_cast<char*>(area->data);
|
|
938
1041
|
baton->bufferOutLength = area->length;
|
|
@@ -951,6 +1054,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
951
1054
|
->set("interlace", baton->gifProgressive)
|
|
952
1055
|
->set("interframe_maxerror", baton->gifInterFrameMaxError)
|
|
953
1056
|
->set("interpalette_maxerror", baton->gifInterPaletteMaxError)
|
|
1057
|
+
->set("keep_duplicate_frames", baton->gifKeepDuplicateFrames)
|
|
954
1058
|
->set("dither", baton->gifDither)));
|
|
955
1059
|
baton->bufferOut = static_cast<char*>(area->data);
|
|
956
1060
|
baton->bufferOutLength = area->length;
|
|
@@ -963,6 +1067,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
963
1067
|
if (baton->tiffCompression == VIPS_FOREIGN_TIFF_COMPRESSION_JPEG) {
|
|
964
1068
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
|
|
965
1069
|
baton->channels = std::min(baton->channels, 3);
|
|
1070
|
+
baton->hasAlphaOut = false;
|
|
966
1071
|
}
|
|
967
1072
|
// Cast pixel values to float, if required
|
|
968
1073
|
if (baton->tiffPredictor == VIPS_FOREIGN_TIFF_PREDICTOR_FLOAT) {
|
|
@@ -973,6 +1078,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
973
1078
|
->set("Q", baton->tiffQuality)
|
|
974
1079
|
->set("bitdepth", baton->tiffBitdepth)
|
|
975
1080
|
->set("compression", baton->tiffCompression)
|
|
1081
|
+
->set("bigtiff", baton->tiffBigtiff)
|
|
976
1082
|
->set("miniswhite", baton->tiffMiniswhite)
|
|
977
1083
|
->set("predictor", baton->tiffPredictor)
|
|
978
1084
|
->set("pyramid", baton->tiffPyramid)
|
|
@@ -991,13 +1097,14 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
991
1097
|
(baton->formatOut == "input" && inputImageType == sharp::ImageType::HEIF)) {
|
|
992
1098
|
// Write HEIF to buffer
|
|
993
1099
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::HEIF);
|
|
994
|
-
image = sharp::RemoveAnimationProperties(image)
|
|
1100
|
+
image = sharp::RemoveAnimationProperties(image);
|
|
995
1101
|
VipsArea *area = reinterpret_cast<VipsArea*>(image.heifsave_buffer(VImage::option()
|
|
996
1102
|
->set("keep", baton->keepMetadata)
|
|
997
1103
|
->set("Q", baton->heifQuality)
|
|
998
1104
|
->set("compression", baton->heifCompression)
|
|
999
1105
|
->set("effort", baton->heifEffort)
|
|
1000
1106
|
->set("bitdepth", baton->heifBitdepth)
|
|
1107
|
+
->set("tune", baton->heifTune.c_str())
|
|
1001
1108
|
->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
|
|
1002
1109
|
? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
|
|
1003
1110
|
->set("lossless", baton->heifLossless)));
|
|
@@ -1009,7 +1116,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1009
1116
|
} else if (baton->formatOut == "dz") {
|
|
1010
1117
|
// Write DZ to buffer
|
|
1011
1118
|
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP;
|
|
1012
|
-
if (!
|
|
1119
|
+
if (!image.has_alpha()) {
|
|
1013
1120
|
baton->tileBackground.pop_back();
|
|
1014
1121
|
}
|
|
1015
1122
|
image = sharp::StaySequential(image, baton->tileAngle != 0);
|
|
@@ -1020,6 +1127,9 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1020
1127
|
area->free_fn = nullptr;
|
|
1021
1128
|
vips_area_unref(area);
|
|
1022
1129
|
baton->formatOut = "dz";
|
|
1130
|
+
if (baton->tileFormat == "jpeg") {
|
|
1131
|
+
baton->hasAlphaOut = false;
|
|
1132
|
+
}
|
|
1023
1133
|
} else if (baton->formatOut == "jxl" ||
|
|
1024
1134
|
(baton->formatOut == "input" && inputImageType == sharp::ImageType::JXL)) {
|
|
1025
1135
|
// Write JXL to buffer
|
|
@@ -1050,19 +1160,19 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1050
1160
|
// Get raw image data
|
|
1051
1161
|
baton->bufferOut = static_cast<char*>(image.write_to_memory(&baton->bufferOutLength));
|
|
1052
1162
|
if (baton->bufferOut == nullptr) {
|
|
1053
|
-
(
|
|
1054
|
-
return Error();
|
|
1163
|
+
throw std::runtime_error("Could not allocate enough memory for raw output");
|
|
1055
1164
|
}
|
|
1056
1165
|
baton->formatOut = "raw";
|
|
1057
1166
|
} else {
|
|
1058
1167
|
// Unsupported output format
|
|
1059
|
-
(
|
|
1168
|
+
auto unsupported = std::string("Unsupported output format ");
|
|
1060
1169
|
if (baton->formatOut == "input") {
|
|
1061
|
-
|
|
1170
|
+
unsupported.append("when trying to match input format of ");
|
|
1171
|
+
unsupported.append(ImageTypeId(inputImageType));
|
|
1062
1172
|
} else {
|
|
1063
|
-
|
|
1173
|
+
unsupported.append(baton->formatOut);
|
|
1064
1174
|
}
|
|
1065
|
-
|
|
1175
|
+
throw std::runtime_error(unsupported);
|
|
1066
1176
|
}
|
|
1067
1177
|
} else {
|
|
1068
1178
|
// File output
|
|
@@ -1085,20 +1195,29 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1085
1195
|
(willMatchInput && inputImageType == sharp::ImageType::JPEG)) {
|
|
1086
1196
|
// Write JPEG to file
|
|
1087
1197
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
->
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
->
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1198
|
+
if (baton->keepGainMap) {
|
|
1199
|
+
image = ReattachGainMap(image, gainMap, baton);
|
|
1200
|
+
image.uhdrsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
|
1201
|
+
->set("keep", baton->keepMetadata)
|
|
1202
|
+
->set("Q", baton->jpegQuality)
|
|
1203
|
+
->set("gainmap_scale_factor", gainMapScaleFactor));
|
|
1204
|
+
} else {
|
|
1205
|
+
image.jpegsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
|
1206
|
+
->set("keep", baton->keepMetadata)
|
|
1207
|
+
->set("Q", baton->jpegQuality)
|
|
1208
|
+
->set("interlace", baton->jpegProgressive)
|
|
1209
|
+
->set("subsample_mode", baton->jpegChromaSubsampling == "4:4:4"
|
|
1210
|
+
? VIPS_FOREIGN_SUBSAMPLE_OFF
|
|
1211
|
+
: VIPS_FOREIGN_SUBSAMPLE_ON)
|
|
1212
|
+
->set("trellis_quant", baton->jpegTrellisQuantisation)
|
|
1213
|
+
->set("quant_table", baton->jpegQuantisationTable)
|
|
1214
|
+
->set("overshoot_deringing", baton->jpegOvershootDeringing)
|
|
1215
|
+
->set("optimize_scans", baton->jpegOptimiseScans)
|
|
1216
|
+
->set("optimize_coding", baton->jpegOptimiseCoding));
|
|
1217
|
+
}
|
|
1100
1218
|
baton->formatOut = "jpeg";
|
|
1101
1219
|
baton->channels = std::min(baton->channels, 3);
|
|
1220
|
+
baton->hasAlphaOut = false;
|
|
1102
1221
|
} else if (baton->formatOut == "jp2" || (mightMatchInput && isJp2) ||
|
|
1103
1222
|
(willMatchInput && (inputImageType == sharp::ImageType::JP2))) {
|
|
1104
1223
|
// Write JP2 to file
|
|
@@ -1136,10 +1255,12 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1136
1255
|
->set("lossless", baton->webpLossless)
|
|
1137
1256
|
->set("near_lossless", baton->webpNearLossless)
|
|
1138
1257
|
->set("smart_subsample", baton->webpSmartSubsample)
|
|
1258
|
+
->set("smart_deblock", baton->webpSmartDeblock)
|
|
1139
1259
|
->set("preset", baton->webpPreset)
|
|
1140
1260
|
->set("effort", baton->webpEffort)
|
|
1141
1261
|
->set("min_size", baton->webpMinSize)
|
|
1142
1262
|
->set("mixed", baton->webpMixed)
|
|
1263
|
+
->set("exact", baton->webpExact)
|
|
1143
1264
|
->set("alpha_q", baton->webpAlphaQuality));
|
|
1144
1265
|
baton->formatOut = "webp";
|
|
1145
1266
|
} else if (baton->formatOut == "gif" || (mightMatchInput && isGif) ||
|
|
@@ -1152,6 +1273,9 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1152
1273
|
->set("effort", baton->gifEffort)
|
|
1153
1274
|
->set("reuse", baton->gifReuse)
|
|
1154
1275
|
->set("interlace", baton->gifProgressive)
|
|
1276
|
+
->set("interframe_maxerror", baton->gifInterFrameMaxError)
|
|
1277
|
+
->set("interpalette_maxerror", baton->gifInterPaletteMaxError)
|
|
1278
|
+
->set("keep_duplicate_frames", baton->gifKeepDuplicateFrames)
|
|
1155
1279
|
->set("dither", baton->gifDither));
|
|
1156
1280
|
baton->formatOut = "gif";
|
|
1157
1281
|
} else if (baton->formatOut == "tiff" || (mightMatchInput && isTiff) ||
|
|
@@ -1160,6 +1284,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1160
1284
|
if (baton->tiffCompression == VIPS_FOREIGN_TIFF_COMPRESSION_JPEG) {
|
|
1161
1285
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
|
|
1162
1286
|
baton->channels = std::min(baton->channels, 3);
|
|
1287
|
+
baton->hasAlphaOut = false;
|
|
1163
1288
|
}
|
|
1164
1289
|
// Cast pixel values to float, if required
|
|
1165
1290
|
if (baton->tiffPredictor == VIPS_FOREIGN_TIFF_PREDICTOR_FLOAT) {
|
|
@@ -1170,6 +1295,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1170
1295
|
->set("Q", baton->tiffQuality)
|
|
1171
1296
|
->set("bitdepth", baton->tiffBitdepth)
|
|
1172
1297
|
->set("compression", baton->tiffCompression)
|
|
1298
|
+
->set("bigtiff", baton->tiffBigtiff)
|
|
1173
1299
|
->set("miniswhite", baton->tiffMiniswhite)
|
|
1174
1300
|
->set("predictor", baton->tiffPredictor)
|
|
1175
1301
|
->set("pyramid", baton->tiffPyramid)
|
|
@@ -1184,13 +1310,14 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1184
1310
|
(willMatchInput && inputImageType == sharp::ImageType::HEIF)) {
|
|
1185
1311
|
// Write HEIF to file
|
|
1186
1312
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::HEIF);
|
|
1187
|
-
image = sharp::RemoveAnimationProperties(image)
|
|
1313
|
+
image = sharp::RemoveAnimationProperties(image);
|
|
1188
1314
|
image.heifsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
|
1189
1315
|
->set("keep", baton->keepMetadata)
|
|
1190
1316
|
->set("Q", baton->heifQuality)
|
|
1191
1317
|
->set("compression", baton->heifCompression)
|
|
1192
1318
|
->set("effort", baton->heifEffort)
|
|
1193
1319
|
->set("bitdepth", baton->heifBitdepth)
|
|
1320
|
+
->set("tune", baton->heifTune.c_str())
|
|
1194
1321
|
->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
|
|
1195
1322
|
? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
|
|
1196
1323
|
->set("lossless", baton->heifLossless));
|
|
@@ -1211,13 +1338,16 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1211
1338
|
if (isDzZip) {
|
|
1212
1339
|
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP;
|
|
1213
1340
|
}
|
|
1214
|
-
if (!
|
|
1341
|
+
if (!image.has_alpha()) {
|
|
1215
1342
|
baton->tileBackground.pop_back();
|
|
1216
1343
|
}
|
|
1217
1344
|
image = sharp::StaySequential(image, baton->tileAngle != 0);
|
|
1218
1345
|
vips::VOption *options = BuildOptionsDZ(baton);
|
|
1219
1346
|
image.dzsave(const_cast<char*>(baton->fileOut.data()), options);
|
|
1220
1347
|
baton->formatOut = "dz";
|
|
1348
|
+
if (baton->tileFormat == "jpeg") {
|
|
1349
|
+
baton->hasAlphaOut = false;
|
|
1350
|
+
}
|
|
1221
1351
|
} else if (baton->formatOut == "v" || (mightMatchInput && isV) ||
|
|
1222
1352
|
(willMatchInput && inputImageType == sharp::ImageType::VIPS)) {
|
|
1223
1353
|
// Write V to file
|
|
@@ -1230,12 +1360,17 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1230
1360
|
return Error();
|
|
1231
1361
|
}
|
|
1232
1362
|
}
|
|
1233
|
-
} catch (
|
|
1363
|
+
} catch (std::runtime_error const &err) {
|
|
1234
1364
|
char const *what = err.what();
|
|
1235
1365
|
if (what && what[0]) {
|
|
1236
1366
|
(baton->err).append(what);
|
|
1237
1367
|
} else {
|
|
1238
|
-
(baton->
|
|
1368
|
+
if (baton->input->failOn == VIPS_FAIL_ON_WARNING) {
|
|
1369
|
+
(baton->err).append("Warning treated as error due to failOn setting");
|
|
1370
|
+
baton->errUseWarning = true;
|
|
1371
|
+
} else {
|
|
1372
|
+
(baton->err).append("Unknown error");
|
|
1373
|
+
}
|
|
1239
1374
|
}
|
|
1240
1375
|
}
|
|
1241
1376
|
// Clean up libvips' per-request data and threads
|
|
@@ -1250,10 +1385,13 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1250
1385
|
// Handle warnings
|
|
1251
1386
|
std::string warning = sharp::VipsWarningPop();
|
|
1252
1387
|
while (!warning.empty()) {
|
|
1253
|
-
|
|
1388
|
+
if (baton->errUseWarning) {
|
|
1389
|
+
(baton->err).append("\n").append(warning);
|
|
1390
|
+
} else {
|
|
1391
|
+
debuglog.SHARP_CALLBACK_FN_NAME(Receiver().Value(), { Napi::String::New(env, warning) });
|
|
1392
|
+
}
|
|
1254
1393
|
warning = sharp::VipsWarningPop();
|
|
1255
1394
|
}
|
|
1256
|
-
|
|
1257
1395
|
if (baton->err.empty()) {
|
|
1258
1396
|
int width = baton->width;
|
|
1259
1397
|
int height = baton->height;
|
|
@@ -1294,24 +1432,36 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1294
1432
|
info.Set("pageHeight", static_cast<int32_t>(baton->pageHeightOut));
|
|
1295
1433
|
info.Set("pages", static_cast<int32_t>(baton->pagesOut));
|
|
1296
1434
|
}
|
|
1435
|
+
info.Set("hasAlpha", baton->hasAlphaOut);
|
|
1297
1436
|
|
|
1298
1437
|
if (baton->bufferOutLength > 0) {
|
|
1299
|
-
// Add buffer size to info
|
|
1300
1438
|
info.Set("size", static_cast<uint32_t>(baton->bufferOutLength));
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1439
|
+
if (baton->typedArrayOut) {
|
|
1440
|
+
// ECMAScript ArrayBuffer with Uint8Array view
|
|
1441
|
+
Napi::TypedArrayOf<uint8_t> data = Napi::Buffer<char>::Copy(env,
|
|
1442
|
+
static_cast<char*>(baton->bufferOut), baton->bufferOutLength);
|
|
1443
|
+
sharp::FreeCallback(static_cast<char*>(baton->bufferOut), nullptr);
|
|
1444
|
+
Callback().SHARP_CALLBACK_FN_NAME(Receiver().Value(), { env.Null(), data, info });
|
|
1445
|
+
} else {
|
|
1446
|
+
// Node.js Buffer
|
|
1447
|
+
Napi::Buffer<char> data = Napi::Buffer<char>::NewOrCopy(env, static_cast<char*>(baton->bufferOut),
|
|
1448
|
+
baton->bufferOutLength, sharp::FreeCallback);
|
|
1449
|
+
Callback().SHARP_CALLBACK_FN_NAME(Receiver().Value(), { env.Null(), data, info });
|
|
1450
|
+
}
|
|
1305
1451
|
} else {
|
|
1306
1452
|
// Add file size to info
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1453
|
+
if (baton->formatOut != "dz" || sharp::IsDzZip(baton->fileOut)) {
|
|
1454
|
+
try {
|
|
1455
|
+
uint32_t const size = static_cast<uint32_t>(
|
|
1456
|
+
std::filesystem::file_size(std::filesystem::u8path(baton->fileOut)));
|
|
1457
|
+
info.Set("size", size);
|
|
1458
|
+
} catch (...) {}
|
|
1310
1459
|
}
|
|
1311
|
-
Callback().
|
|
1460
|
+
Callback().SHARP_CALLBACK_FN_NAME(Receiver().Value(), { env.Null(), info });
|
|
1312
1461
|
}
|
|
1313
1462
|
} else {
|
|
1314
|
-
Callback().
|
|
1463
|
+
Callback().SHARP_CALLBACK_FN_NAME(Receiver().Value(),
|
|
1464
|
+
{ Napi::Error::New(env, sharp::TrimEnd(baton->err)).Value() });
|
|
1315
1465
|
}
|
|
1316
1466
|
|
|
1317
1467
|
// Delete baton
|
|
@@ -1324,12 +1474,15 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1324
1474
|
for (sharp::InputDescriptor *input : baton->joinChannelIn) {
|
|
1325
1475
|
delete input;
|
|
1326
1476
|
}
|
|
1477
|
+
for (sharp::InputDescriptor *input : baton->join) {
|
|
1478
|
+
delete input;
|
|
1479
|
+
}
|
|
1327
1480
|
delete baton;
|
|
1328
1481
|
|
|
1329
1482
|
// Decrement processing task counter
|
|
1330
1483
|
sharp::counterProcess--;
|
|
1331
1484
|
Napi::Number queueLength = Napi::Number::New(env, static_cast<int>(sharp::counterQueue));
|
|
1332
|
-
queueListener.
|
|
1485
|
+
queueListener.SHARP_CALLBACK_FN_NAME(Receiver().Value(), { queueLength });
|
|
1333
1486
|
}
|
|
1334
1487
|
|
|
1335
1488
|
private:
|
|
@@ -1339,7 +1492,13 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1339
1492
|
|
|
1340
1493
|
void MultiPageUnsupported(int const pages, std::string op) {
|
|
1341
1494
|
if (pages > 1) {
|
|
1342
|
-
throw
|
|
1495
|
+
throw std::runtime_error(op + " is not supported for multi-page images");
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
void KeepGainMapUnsupported(bool const keepGainMap, std::string op) {
|
|
1500
|
+
if (keepGainMap) {
|
|
1501
|
+
throw std::runtime_error(op + " is not supported when keeping gain maps");
|
|
1343
1502
|
}
|
|
1344
1503
|
}
|
|
1345
1504
|
|
|
@@ -1347,21 +1506,20 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1347
1506
|
Calculate the angle of rotation and need-to-flip for the given Exif orientation
|
|
1348
1507
|
By default, returns zero, i.e. no rotation.
|
|
1349
1508
|
*/
|
|
1350
|
-
std::tuple<VipsAngle, bool
|
|
1351
|
-
|
|
1509
|
+
std::tuple<VipsAngle, bool>
|
|
1510
|
+
CalculateExifRotationAndFlop(int const exifOrientation) {
|
|
1352
1511
|
VipsAngle rotate = VIPS_ANGLE_D0;
|
|
1353
|
-
bool flip = false;
|
|
1354
1512
|
bool flop = false;
|
|
1355
1513
|
switch (exifOrientation) {
|
|
1356
1514
|
case 6: rotate = VIPS_ANGLE_D90; break;
|
|
1357
1515
|
case 3: rotate = VIPS_ANGLE_D180; break;
|
|
1358
1516
|
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:
|
|
1517
|
+
case 2: flop = true; break;
|
|
1518
|
+
case 7: flop = true; rotate = VIPS_ANGLE_D270; break;
|
|
1519
|
+
case 4: flop = true; rotate = VIPS_ANGLE_D180; break;
|
|
1520
|
+
case 5: flop = true; rotate = VIPS_ANGLE_D90; break;
|
|
1363
1521
|
}
|
|
1364
|
-
return std::make_tuple(rotate,
|
|
1522
|
+
return std::make_tuple(rotate, flop);
|
|
1365
1523
|
}
|
|
1366
1524
|
|
|
1367
1525
|
/*
|
|
@@ -1389,11 +1547,11 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1389
1547
|
std::string
|
|
1390
1548
|
AssembleSuffixString(std::string extname, std::vector<std::pair<std::string, std::string>> options) {
|
|
1391
1549
|
std::string argument;
|
|
1392
|
-
for (
|
|
1550
|
+
for (const auto& [key, value] : options) {
|
|
1393
1551
|
if (!argument.empty()) {
|
|
1394
1552
|
argument += ",";
|
|
1395
1553
|
}
|
|
1396
|
-
argument +=
|
|
1554
|
+
argument += key + "=" + value;
|
|
1397
1555
|
}
|
|
1398
1556
|
return extname + "[" + argument + "]";
|
|
1399
1557
|
}
|
|
@@ -1419,9 +1577,11 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1419
1577
|
{"lossless", baton->webpLossless ? "true" : "false"},
|
|
1420
1578
|
{"near_lossless", baton->webpNearLossless ? "true" : "false"},
|
|
1421
1579
|
{"smart_subsample", baton->webpSmartSubsample ? "true" : "false"},
|
|
1580
|
+
{"smart_deblock", baton->webpSmartDeblock ? "true" : "false"},
|
|
1422
1581
|
{"preset", vips_enum_nick(VIPS_TYPE_FOREIGN_WEBP_PRESET, baton->webpPreset)},
|
|
1423
1582
|
{"min_size", baton->webpMinSize ? "true" : "false"},
|
|
1424
1583
|
{"mixed", baton->webpMixed ? "true" : "false"},
|
|
1584
|
+
{"exact", baton->webpExact ? "true" : "false"},
|
|
1425
1585
|
{"effort", std::to_string(baton->webpEffort)}
|
|
1426
1586
|
};
|
|
1427
1587
|
suffix = AssembleSuffixString(".webp", options);
|
|
@@ -1460,6 +1620,21 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1460
1620
|
return options;
|
|
1461
1621
|
}
|
|
1462
1622
|
|
|
1623
|
+
VImage ReattachGainMap(VImage image, VImage gainMap, PipelineBaton *baton) {
|
|
1624
|
+
VipsArea *gainMapJpeg = reinterpret_cast<VipsArea*>(gainMap.jpegsave_buffer(VImage::option()
|
|
1625
|
+
->set("keep", FALSE)
|
|
1626
|
+
->set("Q", baton->jpegQuality)
|
|
1627
|
+
->set("subsample_mode", VIPS_FOREIGN_SUBSAMPLE_OFF)
|
|
1628
|
+
->set("trellis_quant", baton->jpegTrellisQuantisation)
|
|
1629
|
+
->set("quant_table", baton->jpegQuantisationTable)
|
|
1630
|
+
->set("overshoot_deringing", baton->jpegOvershootDeringing)
|
|
1631
|
+
->set("optimize_coding", baton->jpegOptimiseCoding)));
|
|
1632
|
+
image = image.copy();
|
|
1633
|
+
image.set("gainmap-data", reinterpret_cast<VipsCallbackFn>(vips_area_free_cb),
|
|
1634
|
+
gainMapJpeg->data, gainMapJpeg->length);
|
|
1635
|
+
return image;
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1463
1638
|
/*
|
|
1464
1639
|
Clear all thread-local data.
|
|
1465
1640
|
*/
|
|
@@ -1480,6 +1655,14 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1480
1655
|
|
|
1481
1656
|
// Input
|
|
1482
1657
|
baton->input = sharp::CreateInputDescriptor(options.Get("input").As<Napi::Object>());
|
|
1658
|
+
// Join images together
|
|
1659
|
+
if (sharp::HasAttr(options, "join")) {
|
|
1660
|
+
Napi::Array join = options.Get("join").As<Napi::Array>();
|
|
1661
|
+
for (unsigned int i = 0; i < join.Length(); i++) {
|
|
1662
|
+
baton->join.push_back(
|
|
1663
|
+
sharp::CreateInputDescriptor(join.Get(i).As<Napi::Object>()));
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1483
1666
|
// Extract image options
|
|
1484
1667
|
baton->topOffsetPre = sharp::AttrAsInt32(options, "topOffsetPre");
|
|
1485
1668
|
baton->leftOffsetPre = sharp::AttrAsInt32(options, "leftOffsetPre");
|
|
@@ -1560,10 +1743,13 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1560
1743
|
baton->trimBackground = sharp::AttrAsVectorOfDouble(options, "trimBackground");
|
|
1561
1744
|
baton->trimThreshold = sharp::AttrAsDouble(options, "trimThreshold");
|
|
1562
1745
|
baton->trimLineArt = sharp::AttrAsBool(options, "trimLineArt");
|
|
1746
|
+
baton->trimMargin = sharp::AttrAsUint32(options, "trimMargin");
|
|
1563
1747
|
baton->gamma = sharp::AttrAsDouble(options, "gamma");
|
|
1564
1748
|
baton->gammaOut = sharp::AttrAsDouble(options, "gammaOut");
|
|
1565
1749
|
baton->linearA = sharp::AttrAsVectorOfDouble(options, "linearA");
|
|
1566
1750
|
baton->linearB = sharp::AttrAsVectorOfDouble(options, "linearB");
|
|
1751
|
+
baton->dilateWidth = sharp::AttrAsUint32(options, "dilateWidth");
|
|
1752
|
+
baton->erodeWidth = sharp::AttrAsUint32(options, "erodeWidth");
|
|
1567
1753
|
baton->greyscale = sharp::AttrAsBool(options, "greyscale");
|
|
1568
1754
|
baton->normalise = sharp::AttrAsBool(options, "normalise");
|
|
1569
1755
|
baton->normaliseLower = sharp::AttrAsUint32(options, "normaliseLower");
|
|
@@ -1572,11 +1758,11 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1572
1758
|
baton->claheWidth = sharp::AttrAsUint32(options, "claheWidth");
|
|
1573
1759
|
baton->claheHeight = sharp::AttrAsUint32(options, "claheHeight");
|
|
1574
1760
|
baton->claheMaxSlope = sharp::AttrAsUint32(options, "claheMaxSlope");
|
|
1575
|
-
baton->useExifOrientation = sharp::AttrAsBool(options, "useExifOrientation");
|
|
1576
1761
|
baton->angle = sharp::AttrAsInt32(options, "angle");
|
|
1577
1762
|
baton->rotationAngle = sharp::AttrAsDouble(options, "rotationAngle");
|
|
1578
1763
|
baton->rotationBackground = sharp::AttrAsVectorOfDouble(options, "rotationBackground");
|
|
1579
|
-
baton->
|
|
1764
|
+
baton->rotateBefore = sharp::AttrAsBool(options, "rotateBefore");
|
|
1765
|
+
baton->orientBefore = sharp::AttrAsBool(options, "orientBefore");
|
|
1580
1766
|
baton->flip = sharp::AttrAsBool(options, "flip");
|
|
1581
1767
|
baton->flop = sharp::AttrAsBool(options, "flop");
|
|
1582
1768
|
baton->extendTop = sharp::AttrAsInt32(options, "extendTop");
|
|
@@ -1635,6 +1821,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1635
1821
|
// Output
|
|
1636
1822
|
baton->formatOut = sharp::AttrAsStr(options, "formatOut");
|
|
1637
1823
|
baton->fileOut = sharp::AttrAsStr(options, "fileOut");
|
|
1824
|
+
baton->typedArrayOut = sharp::AttrAsBool(options, "typedArrayOut");
|
|
1638
1825
|
baton->keepMetadata = sharp::AttrAsUint32(options, "keepMetadata");
|
|
1639
1826
|
baton->withMetadataOrientation = sharp::AttrAsUint32(options, "withMetadataOrientation");
|
|
1640
1827
|
baton->withMetadataDensity = sharp::AttrAsDouble(options, "withMetadataDensity");
|
|
@@ -1648,7 +1835,12 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1648
1835
|
}
|
|
1649
1836
|
}
|
|
1650
1837
|
baton->withExifMerge = sharp::AttrAsBool(options, "withExifMerge");
|
|
1838
|
+
baton->withXmp = sharp::AttrAsStr(options, "withXmp");
|
|
1839
|
+
baton->keepGainMap = sharp::AttrAsBool(options, "keepGainMap");
|
|
1840
|
+
baton->withGainMap = sharp::AttrAsBool(options, "withGainMap");
|
|
1651
1841
|
baton->timeoutSeconds = sharp::AttrAsUint32(options, "timeoutSeconds");
|
|
1842
|
+
baton->loop = sharp::AttrAsUint32(options, "loop");
|
|
1843
|
+
baton->delay = sharp::AttrAsInt32Vector(options, "delay");
|
|
1652
1844
|
// Format-specific
|
|
1653
1845
|
baton->jpegQuality = sharp::AttrAsUint32(options, "jpegQuality");
|
|
1654
1846
|
baton->jpegProgressive = sharp::AttrAsBool(options, "jpegProgressive");
|
|
@@ -1676,18 +1868,22 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1676
1868
|
baton->webpLossless = sharp::AttrAsBool(options, "webpLossless");
|
|
1677
1869
|
baton->webpNearLossless = sharp::AttrAsBool(options, "webpNearLossless");
|
|
1678
1870
|
baton->webpSmartSubsample = sharp::AttrAsBool(options, "webpSmartSubsample");
|
|
1871
|
+
baton->webpSmartDeblock = sharp::AttrAsBool(options, "webpSmartDeblock");
|
|
1679
1872
|
baton->webpPreset = sharp::AttrAsEnum<VipsForeignWebpPreset>(options, "webpPreset", VIPS_TYPE_FOREIGN_WEBP_PRESET);
|
|
1680
1873
|
baton->webpEffort = sharp::AttrAsUint32(options, "webpEffort");
|
|
1681
1874
|
baton->webpMinSize = sharp::AttrAsBool(options, "webpMinSize");
|
|
1682
1875
|
baton->webpMixed = sharp::AttrAsBool(options, "webpMixed");
|
|
1876
|
+
baton->webpExact = sharp::AttrAsBool(options, "webpExact");
|
|
1683
1877
|
baton->gifBitdepth = sharp::AttrAsUint32(options, "gifBitdepth");
|
|
1684
1878
|
baton->gifEffort = sharp::AttrAsUint32(options, "gifEffort");
|
|
1685
1879
|
baton->gifDither = sharp::AttrAsDouble(options, "gifDither");
|
|
1686
1880
|
baton->gifInterFrameMaxError = sharp::AttrAsDouble(options, "gifInterFrameMaxError");
|
|
1687
1881
|
baton->gifInterPaletteMaxError = sharp::AttrAsDouble(options, "gifInterPaletteMaxError");
|
|
1882
|
+
baton->gifKeepDuplicateFrames = sharp::AttrAsBool(options, "gifKeepDuplicateFrames");
|
|
1688
1883
|
baton->gifReuse = sharp::AttrAsBool(options, "gifReuse");
|
|
1689
1884
|
baton->gifProgressive = sharp::AttrAsBool(options, "gifProgressive");
|
|
1690
1885
|
baton->tiffQuality = sharp::AttrAsUint32(options, "tiffQuality");
|
|
1886
|
+
baton->tiffBigtiff = sharp::AttrAsBool(options, "tiffBigtiff");
|
|
1691
1887
|
baton->tiffPyramid = sharp::AttrAsBool(options, "tiffPyramid");
|
|
1692
1888
|
baton->tiffMiniswhite = sharp::AttrAsBool(options, "tiffMiniswhite");
|
|
1693
1889
|
baton->tiffBitdepth = sharp::AttrAsUint32(options, "tiffBitdepth");
|
|
@@ -1712,18 +1908,12 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1712
1908
|
baton->heifEffort = sharp::AttrAsUint32(options, "heifEffort");
|
|
1713
1909
|
baton->heifChromaSubsampling = sharp::AttrAsStr(options, "heifChromaSubsampling");
|
|
1714
1910
|
baton->heifBitdepth = sharp::AttrAsUint32(options, "heifBitdepth");
|
|
1911
|
+
baton->heifTune = sharp::AttrAsStr(options, "heifTune");
|
|
1715
1912
|
baton->jxlDistance = sharp::AttrAsDouble(options, "jxlDistance");
|
|
1716
1913
|
baton->jxlDecodingTier = sharp::AttrAsUint32(options, "jxlDecodingTier");
|
|
1717
1914
|
baton->jxlEffort = sharp::AttrAsUint32(options, "jxlEffort");
|
|
1718
1915
|
baton->jxlLossless = sharp::AttrAsBool(options, "jxlLossless");
|
|
1719
1916
|
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
1917
|
baton->tileSize = sharp::AttrAsUint32(options, "tileSize");
|
|
1728
1918
|
baton->tileOverlap = sharp::AttrAsUint32(options, "tileOverlap");
|
|
1729
1919
|
baton->tileAngle = sharp::AttrAsInt32(options, "tileAngle");
|
|
@@ -1752,7 +1942,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1752
1942
|
|
|
1753
1943
|
// Increment queued task counter
|
|
1754
1944
|
Napi::Number queueLength = Napi::Number::New(info.Env(), static_cast<int>(++sharp::counterQueue));
|
|
1755
|
-
queueListener.
|
|
1945
|
+
queueListener.SHARP_CALLBACK_FN_NAME(info.This(), { queueLength });
|
|
1756
1946
|
|
|
1757
1947
|
return info.Env().Undefined();
|
|
1758
1948
|
}
|