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