@revizly/sharp 0.33.2-revizly3

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