@revizly/sharp 0.33.2-revizly13

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,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
+ }