@revizly/sharp 0.33.2-revizly3

Sign up to get free protection for your applications and to get access to all the features.
package/src/common.cc ADDED
@@ -0,0 +1,1090 @@
1
+ // Copyright 2013 Lovell Fuller and others.
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ #include <cstdlib>
5
+ #include <string>
6
+ #include <string.h>
7
+ #include <vector>
8
+ #include <queue>
9
+ #include <map>
10
+ #include <mutex> // NOLINT(build/c++11)
11
+
12
+ #include <napi.h>
13
+ #include <vips/vips8>
14
+
15
+ #include "common.h"
16
+
17
+ using vips::VImage;
18
+
19
+ namespace sharp {
20
+
21
+ // Convenience methods to access the attributes of a Napi::Object
22
+ bool HasAttr(Napi::Object obj, std::string attr) {
23
+ return obj.Has(attr);
24
+ }
25
+ std::string AttrAsStr(Napi::Object obj, std::string attr) {
26
+ return obj.Get(attr).As<Napi::String>();
27
+ }
28
+ std::string AttrAsStr(Napi::Object obj, unsigned int const attr) {
29
+ return obj.Get(attr).As<Napi::String>();
30
+ }
31
+ uint32_t AttrAsUint32(Napi::Object obj, std::string attr) {
32
+ return obj.Get(attr).As<Napi::Number>().Uint32Value();
33
+ }
34
+ int32_t AttrAsInt32(Napi::Object obj, std::string attr) {
35
+ return obj.Get(attr).As<Napi::Number>().Int32Value();
36
+ }
37
+ int32_t AttrAsInt32(Napi::Object obj, unsigned int const attr) {
38
+ return obj.Get(attr).As<Napi::Number>().Int32Value();
39
+ }
40
+ int64_t AttrAsInt64(Napi::Object obj, std::string attr) {
41
+ return obj.Get(attr).As<Napi::Number>().Int64Value();
42
+ }
43
+ double AttrAsDouble(Napi::Object obj, std::string attr) {
44
+ return obj.Get(attr).As<Napi::Number>().DoubleValue();
45
+ }
46
+ double AttrAsDouble(Napi::Object obj, unsigned int const attr) {
47
+ return obj.Get(attr).As<Napi::Number>().DoubleValue();
48
+ }
49
+ bool AttrAsBool(Napi::Object obj, std::string attr) {
50
+ return obj.Get(attr).As<Napi::Boolean>().Value();
51
+ }
52
+ std::vector<double> AttrAsVectorOfDouble(Napi::Object obj, std::string attr) {
53
+ Napi::Array napiArray = obj.Get(attr).As<Napi::Array>();
54
+ std::vector<double> vectorOfDouble(napiArray.Length());
55
+ for (unsigned int i = 0; i < napiArray.Length(); i++) {
56
+ vectorOfDouble[i] = AttrAsDouble(napiArray, i);
57
+ }
58
+ return vectorOfDouble;
59
+ }
60
+ std::vector<int32_t> AttrAsInt32Vector(Napi::Object obj, std::string attr) {
61
+ Napi::Array array = obj.Get(attr).As<Napi::Array>();
62
+ std::vector<int32_t> vector(array.Length());
63
+ for (unsigned int i = 0; i < array.Length(); i++) {
64
+ vector[i] = AttrAsInt32(array, i);
65
+ }
66
+ return vector;
67
+ }
68
+
69
+ // Create an InputDescriptor instance from a Napi::Object describing an input image
70
+ InputDescriptor* CreateInputDescriptor(Napi::Object input) {
71
+ InputDescriptor *descriptor = new InputDescriptor;
72
+ if (HasAttr(input, "file")) {
73
+ descriptor->file = AttrAsStr(input, "file");
74
+ } else if (HasAttr(input, "buffer")) {
75
+ Napi::Buffer<char> buffer = input.Get("buffer").As<Napi::Buffer<char>>();
76
+ descriptor->bufferLength = buffer.Length();
77
+ descriptor->buffer = buffer.Data();
78
+ descriptor->isBuffer = TRUE;
79
+ }
80
+ descriptor->failOn = AttrAsEnum<VipsFailOn>(input, "failOn", VIPS_TYPE_FAIL_ON);
81
+ // Density for vector-based input
82
+ if (HasAttr(input, "density")) {
83
+ descriptor->density = AttrAsDouble(input, "density");
84
+ }
85
+ // Should we ignore any embedded ICC profile
86
+ if (HasAttr(input, "ignoreIcc")) {
87
+ descriptor->ignoreIcc = AttrAsBool(input, "ignoreIcc");
88
+ }
89
+ // Raw pixel input
90
+ if (HasAttr(input, "rawChannels")) {
91
+ descriptor->rawDepth = AttrAsEnum<VipsBandFormat>(input, "rawDepth", VIPS_TYPE_BAND_FORMAT);
92
+ descriptor->rawChannels = AttrAsUint32(input, "rawChannels");
93
+ descriptor->rawWidth = AttrAsUint32(input, "rawWidth");
94
+ descriptor->rawHeight = AttrAsUint32(input, "rawHeight");
95
+ descriptor->rawPremultiplied = AttrAsBool(input, "rawPremultiplied");
96
+ }
97
+ // Multi-page input (GIF, TIFF, PDF)
98
+ if (HasAttr(input, "pages")) {
99
+ descriptor->pages = AttrAsInt32(input, "pages");
100
+ }
101
+ if (HasAttr(input, "page")) {
102
+ descriptor->page = AttrAsUint32(input, "page");
103
+ }
104
+ // Multi-level input (OpenSlide)
105
+ if (HasAttr(input, "level")) {
106
+ descriptor->level = AttrAsUint32(input, "level");
107
+ }
108
+ // subIFD (OME-TIFF)
109
+ if (HasAttr(input, "subifd")) {
110
+ descriptor->subifd = AttrAsInt32(input, "subifd");
111
+ }
112
+ // Create new image
113
+ if (HasAttr(input, "createChannels")) {
114
+ descriptor->createChannels = AttrAsUint32(input, "createChannels");
115
+ descriptor->createWidth = AttrAsUint32(input, "createWidth");
116
+ descriptor->createHeight = AttrAsUint32(input, "createHeight");
117
+ if (HasAttr(input, "createNoiseType")) {
118
+ descriptor->createNoiseType = AttrAsStr(input, "createNoiseType");
119
+ descriptor->createNoiseMean = AttrAsDouble(input, "createNoiseMean");
120
+ descriptor->createNoiseSigma = AttrAsDouble(input, "createNoiseSigma");
121
+ } else {
122
+ descriptor->createBackground = AttrAsVectorOfDouble(input, "createBackground");
123
+ }
124
+ }
125
+ // Create new image with text
126
+ if (HasAttr(input, "textValue")) {
127
+ descriptor->textValue = AttrAsStr(input, "textValue");
128
+ if (HasAttr(input, "textFont")) {
129
+ descriptor->textFont = AttrAsStr(input, "textFont");
130
+ }
131
+ if (HasAttr(input, "textFontfile")) {
132
+ descriptor->textFontfile = AttrAsStr(input, "textFontfile");
133
+ }
134
+ if (HasAttr(input, "textWidth")) {
135
+ descriptor->textWidth = AttrAsUint32(input, "textWidth");
136
+ }
137
+ if (HasAttr(input, "textHeight")) {
138
+ descriptor->textHeight = AttrAsUint32(input, "textHeight");
139
+ }
140
+ if (HasAttr(input, "textAlign")) {
141
+ descriptor->textAlign = AttrAsEnum<VipsAlign>(input, "textAlign", VIPS_TYPE_ALIGN);
142
+ }
143
+ if (HasAttr(input, "textJustify")) {
144
+ descriptor->textJustify = AttrAsBool(input, "textJustify");
145
+ }
146
+ if (HasAttr(input, "textDpi")) {
147
+ descriptor->textDpi = AttrAsUint32(input, "textDpi");
148
+ }
149
+ if (HasAttr(input, "textRgba")) {
150
+ descriptor->textRgba = AttrAsBool(input, "textRgba");
151
+ }
152
+ if (HasAttr(input, "textSpacing")) {
153
+ descriptor->textSpacing = AttrAsUint32(input, "textSpacing");
154
+ }
155
+ if (HasAttr(input, "textWrap")) {
156
+ descriptor->textWrap = AttrAsEnum<VipsTextWrap>(input, "textWrap", VIPS_TYPE_TEXT_WRAP);
157
+ }
158
+ }
159
+ // Limit input images to a given number of pixels, where pixels = width * height
160
+ descriptor->limitInputPixels = static_cast<uint64_t>(AttrAsInt64(input, "limitInputPixels"));
161
+ // Allow switch from random to sequential access
162
+ descriptor->access = AttrAsBool(input, "sequentialRead") ? VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_RANDOM;
163
+ // Remove safety features and allow unlimited input
164
+ descriptor->unlimited = AttrAsBool(input, "unlimited");
165
+ return descriptor;
166
+ }
167
+
168
+ // How many tasks are in the queue?
169
+ std::atomic<int> counterQueue{0};
170
+
171
+ // How many tasks are being processed?
172
+ std::atomic<int> counterProcess{0};
173
+
174
+ // Filename extension checkers
175
+ static bool EndsWith(std::string const &str, std::string const &end) {
176
+ return str.length() >= end.length() && 0 == str.compare(str.length() - end.length(), end.length(), end);
177
+ }
178
+ bool IsJpeg(std::string const &str) {
179
+ return EndsWith(str, ".jpg") || EndsWith(str, ".jpeg") || EndsWith(str, ".JPG") || EndsWith(str, ".JPEG");
180
+ }
181
+ bool IsPng(std::string const &str) {
182
+ return EndsWith(str, ".png") || EndsWith(str, ".PNG");
183
+ }
184
+ bool IsWebp(std::string const &str) {
185
+ return EndsWith(str, ".webp") || EndsWith(str, ".WEBP");
186
+ }
187
+ bool IsGif(std::string const &str) {
188
+ return EndsWith(str, ".gif") || EndsWith(str, ".GIF");
189
+ }
190
+ bool IsJp2(std::string const &str) {
191
+ return EndsWith(str, ".jp2") || EndsWith(str, ".jpx") || EndsWith(str, ".j2k") || EndsWith(str, ".j2c")
192
+ || EndsWith(str, ".JP2") || EndsWith(str, ".JPX") || EndsWith(str, ".J2K") || EndsWith(str, ".J2C");
193
+ }
194
+ bool IsTiff(std::string const &str) {
195
+ return EndsWith(str, ".tif") || EndsWith(str, ".tiff") || EndsWith(str, ".TIF") || EndsWith(str, ".TIFF");
196
+ }
197
+ bool IsHeic(std::string const &str) {
198
+ return EndsWith(str, ".heic") || EndsWith(str, ".HEIC");
199
+ }
200
+ bool IsHeif(std::string const &str) {
201
+ return EndsWith(str, ".heif") || EndsWith(str, ".HEIF") || IsHeic(str) || IsAvif(str);
202
+ }
203
+ bool IsAvif(std::string const &str) {
204
+ return EndsWith(str, ".avif") || EndsWith(str, ".AVIF");
205
+ }
206
+ bool IsJxl(std::string const &str) {
207
+ return EndsWith(str, ".jxl") || EndsWith(str, ".JXL");
208
+ }
209
+ bool IsDz(std::string const &str) {
210
+ return EndsWith(str, ".dzi") || EndsWith(str, ".DZI");
211
+ }
212
+ bool IsDzZip(std::string const &str) {
213
+ return EndsWith(str, ".zip") || EndsWith(str, ".ZIP") || EndsWith(str, ".szi") || EndsWith(str, ".SZI");
214
+ }
215
+ bool IsV(std::string const &str) {
216
+ return EndsWith(str, ".v") || EndsWith(str, ".V") || EndsWith(str, ".vips") || EndsWith(str, ".VIPS");
217
+ }
218
+
219
+ /*
220
+ Trim space from end of string.
221
+ */
222
+ std::string TrimEnd(std::string const &str) {
223
+ return str.substr(0, str.find_last_not_of(" \n\r\f") + 1);
224
+ }
225
+
226
+ /*
227
+ Provide a string identifier for the given image type.
228
+ */
229
+ std::string ImageTypeId(ImageType const imageType) {
230
+ std::string id;
231
+ switch (imageType) {
232
+ case ImageType::JPEG: id = "jpeg"; break;
233
+ case ImageType::PNG: id = "png"; break;
234
+ case ImageType::WEBP: id = "webp"; break;
235
+ case ImageType::TIFF: id = "tiff"; break;
236
+ case ImageType::GIF: id = "gif"; break;
237
+ case ImageType::JP2: id = "jp2"; break;
238
+ case ImageType::SVG: id = "svg"; break;
239
+ case ImageType::HEIF: id = "heif"; break;
240
+ case ImageType::PDF: id = "pdf"; break;
241
+ case ImageType::MAGICK: id = "magick"; break;
242
+ case ImageType::OPENSLIDE: id = "openslide"; break;
243
+ case ImageType::PPM: id = "ppm"; break;
244
+ case ImageType::FITS: id = "fits"; break;
245
+ case ImageType::EXR: id = "exr"; break;
246
+ case ImageType::JXL: id = "jxl"; break;
247
+ case ImageType::VIPS: id = "vips"; break;
248
+ case ImageType::RAW: id = "raw"; break;
249
+ case ImageType::UNKNOWN: id = "unknown"; break;
250
+ case ImageType::MISSING: id = "missing"; break;
251
+ }
252
+ return id;
253
+ }
254
+
255
+ /**
256
+ * Regenerate this table with something like:
257
+ *
258
+ * $ vips -l foreign | grep -i load | awk '{ print $2, $1; }'
259
+ *
260
+ * Plus a bit of editing.
261
+ */
262
+ std::map<std::string, ImageType> loaderToType = {
263
+ { "VipsForeignLoadJpegFile", ImageType::JPEG },
264
+ { "VipsForeignLoadJpegBuffer", ImageType::JPEG },
265
+ { "VipsForeignLoadPngFile", ImageType::PNG },
266
+ { "VipsForeignLoadPngBuffer", ImageType::PNG },
267
+ { "VipsForeignLoadWebpFile", ImageType::WEBP },
268
+ { "VipsForeignLoadWebpBuffer", ImageType::WEBP },
269
+ { "VipsForeignLoadTiffFile", ImageType::TIFF },
270
+ { "VipsForeignLoadTiffBuffer", ImageType::TIFF },
271
+ { "VipsForeignLoadGifFile", ImageType::GIF },
272
+ { "VipsForeignLoadGifBuffer", ImageType::GIF },
273
+ { "VipsForeignLoadNsgifFile", ImageType::GIF },
274
+ { "VipsForeignLoadNsgifBuffer", ImageType::GIF },
275
+ { "VipsForeignLoadJp2kBuffer", ImageType::JP2 },
276
+ { "VipsForeignLoadJp2kFile", ImageType::JP2 },
277
+ { "VipsForeignLoadSvgFile", ImageType::SVG },
278
+ { "VipsForeignLoadSvgBuffer", ImageType::SVG },
279
+ { "VipsForeignLoadHeifFile", ImageType::HEIF },
280
+ { "VipsForeignLoadHeifBuffer", ImageType::HEIF },
281
+ { "VipsForeignLoadPdfFile", ImageType::PDF },
282
+ { "VipsForeignLoadPdfBuffer", ImageType::PDF },
283
+ { "VipsForeignLoadMagickFile", ImageType::MAGICK },
284
+ { "VipsForeignLoadMagickBuffer", ImageType::MAGICK },
285
+ { "VipsForeignLoadMagick7File", ImageType::MAGICK },
286
+ { "VipsForeignLoadMagick7Buffer", ImageType::MAGICK },
287
+ { "VipsForeignLoadOpenslideFile", ImageType::OPENSLIDE },
288
+ { "VipsForeignLoadPpmFile", ImageType::PPM },
289
+ { "VipsForeignLoadFitsFile", ImageType::FITS },
290
+ { "VipsForeignLoadOpenexr", ImageType::EXR },
291
+ { "VipsForeignLoadJxlFile", ImageType::JXL },
292
+ { "VipsForeignLoadJxlBuffer", ImageType::JXL },
293
+ { "VipsForeignLoadVips", ImageType::VIPS },
294
+ { "VipsForeignLoadVipsFile", ImageType::VIPS },
295
+ { "VipsForeignLoadRaw", ImageType::RAW }
296
+ };
297
+
298
+ /*
299
+ Determine image format of a buffer.
300
+ */
301
+ ImageType DetermineImageType(void *buffer, size_t const length) {
302
+ ImageType imageType = ImageType::UNKNOWN;
303
+ char const *load = vips_foreign_find_load_buffer(buffer, length);
304
+ if (load != nullptr) {
305
+ auto it = loaderToType.find(load);
306
+ if (it != loaderToType.end()) {
307
+ imageType = it->second;
308
+ }
309
+ }
310
+ return imageType;
311
+ }
312
+
313
+ /*
314
+ Determine image format, reads the first few bytes of the file
315
+ */
316
+ ImageType DetermineImageType(char const *file) {
317
+ ImageType imageType = ImageType::UNKNOWN;
318
+ char const *load = vips_foreign_find_load(file);
319
+ if (load != nullptr) {
320
+ auto it = loaderToType.find(load);
321
+ if (it != loaderToType.end()) {
322
+ imageType = it->second;
323
+ }
324
+ } else {
325
+ if (EndsWith(vips::VError().what(), " does not exist\n")) {
326
+ imageType = ImageType::MISSING;
327
+ }
328
+ }
329
+ return imageType;
330
+ }
331
+
332
+ /*
333
+ Does this image type support multiple pages?
334
+ */
335
+ bool ImageTypeSupportsPage(ImageType imageType) {
336
+ return
337
+ imageType == ImageType::WEBP ||
338
+ imageType == ImageType::MAGICK ||
339
+ imageType == ImageType::GIF ||
340
+ imageType == ImageType::JP2 ||
341
+ imageType == ImageType::TIFF ||
342
+ imageType == ImageType::HEIF ||
343
+ imageType == ImageType::PDF;
344
+ }
345
+
346
+ /*
347
+ Does this image type support removal of safety limits?
348
+ */
349
+ bool ImageTypeSupportsUnlimited(ImageType imageType) {
350
+ return
351
+ imageType == ImageType::JPEG ||
352
+ imageType == ImageType::PNG ||
353
+ imageType == ImageType::SVG ||
354
+ imageType == ImageType::HEIF;
355
+ }
356
+
357
+ /*
358
+ Open an image from the given InputDescriptor (filesystem, compressed buffer, raw pixel data)
359
+ */
360
+ std::tuple<VImage, ImageType> OpenInput(InputDescriptor *descriptor) {
361
+ VImage image;
362
+ ImageType imageType;
363
+ if (descriptor->isBuffer) {
364
+ if (descriptor->rawChannels > 0) {
365
+ // Raw, uncompressed pixel data
366
+ bool const is8bit = vips_band_format_is8bit(descriptor->rawDepth);
367
+ image = VImage::new_from_memory(descriptor->buffer, descriptor->bufferLength,
368
+ descriptor->rawWidth, descriptor->rawHeight, descriptor->rawChannels, descriptor->rawDepth);
369
+ if (descriptor->rawChannels < 3) {
370
+ image.get_image()->Type = is8bit ? VIPS_INTERPRETATION_B_W : VIPS_INTERPRETATION_GREY16;
371
+ } else {
372
+ image.get_image()->Type = is8bit ? VIPS_INTERPRETATION_sRGB : VIPS_INTERPRETATION_RGB16;
373
+ }
374
+ if (descriptor->rawPremultiplied) {
375
+ image = image.unpremultiply();
376
+ }
377
+ imageType = ImageType::RAW;
378
+ } else {
379
+ // Compressed data
380
+ imageType = DetermineImageType(descriptor->buffer, descriptor->bufferLength);
381
+ if (imageType != ImageType::UNKNOWN) {
382
+ try {
383
+ vips::VOption *option = VImage::option()
384
+ ->set("access", descriptor->access)
385
+ ->set("fail_on", descriptor->failOn);
386
+ if (descriptor->unlimited && ImageTypeSupportsUnlimited(imageType)) {
387
+ option->set("unlimited", TRUE);
388
+ }
389
+ if (imageType == ImageType::SVG || imageType == ImageType::PDF) {
390
+ option->set("dpi", descriptor->density);
391
+ }
392
+ if (imageType == ImageType::MAGICK) {
393
+ option->set("density", std::to_string(descriptor->density).data());
394
+ }
395
+ if (ImageTypeSupportsPage(imageType)) {
396
+ option->set("n", descriptor->pages);
397
+ option->set("page", descriptor->page);
398
+ }
399
+ if (imageType == ImageType::OPENSLIDE) {
400
+ option->set("level", descriptor->level);
401
+ }
402
+ if (imageType == ImageType::TIFF) {
403
+ option->set("subifd", descriptor->subifd);
404
+ }
405
+ image = VImage::new_from_buffer(descriptor->buffer, descriptor->bufferLength, nullptr, option);
406
+ if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
407
+ image = SetDensity(image, descriptor->density);
408
+ }
409
+ } catch (vips::VError const &err) {
410
+ throw vips::VError(std::string("Input buffer has corrupt header: ") + err.what());
411
+ }
412
+ } else {
413
+ throw vips::VError("Input buffer contains unsupported image format");
414
+ }
415
+ }
416
+ } else {
417
+ int const channels = descriptor->createChannels;
418
+ if (channels > 0) {
419
+ // Create new image
420
+ if (descriptor->createNoiseType == "gaussian") {
421
+ std::vector<VImage> bands = {};
422
+ bands.reserve(channels);
423
+ for (int _band = 0; _band < channels; _band++) {
424
+ bands.push_back(VImage::gaussnoise(descriptor->createWidth, descriptor->createHeight, VImage::option()
425
+ ->set("mean", descriptor->createNoiseMean)
426
+ ->set("sigma", descriptor->createNoiseSigma)));
427
+ }
428
+ image = VImage::bandjoin(bands).copy(VImage::option()->set("interpretation",
429
+ channels < 3 ? VIPS_INTERPRETATION_B_W: VIPS_INTERPRETATION_sRGB));
430
+ } else {
431
+ std::vector<double> background = {
432
+ descriptor->createBackground[0],
433
+ descriptor->createBackground[1],
434
+ descriptor->createBackground[2]
435
+ };
436
+ if (channels == 4) {
437
+ background.push_back(descriptor->createBackground[3]);
438
+ }
439
+ image = VImage::new_matrix(descriptor->createWidth, descriptor->createHeight)
440
+ .copy(VImage::option()->set("interpretation",
441
+ channels < 3 ? VIPS_INTERPRETATION_B_W : VIPS_INTERPRETATION_sRGB))
442
+ .new_from_image(background);
443
+ }
444
+ image = image.cast(VIPS_FORMAT_UCHAR);
445
+ imageType = ImageType::RAW;
446
+ } else if (descriptor->textValue.length() > 0) {
447
+ // Create a new image with text
448
+ vips::VOption *textOptions = VImage::option()
449
+ ->set("align", descriptor->textAlign)
450
+ ->set("justify", descriptor->textJustify)
451
+ ->set("rgba", descriptor->textRgba)
452
+ ->set("spacing", descriptor->textSpacing)
453
+ ->set("wrap", descriptor->textWrap)
454
+ ->set("autofit_dpi", &descriptor->textAutofitDpi);
455
+ if (descriptor->textWidth > 0) {
456
+ textOptions->set("width", descriptor->textWidth);
457
+ }
458
+ // Ignore dpi if height is set
459
+ if (descriptor->textWidth > 0 && descriptor->textHeight > 0) {
460
+ textOptions->set("height", descriptor->textHeight);
461
+ } else if (descriptor->textDpi > 0) {
462
+ textOptions->set("dpi", descriptor->textDpi);
463
+ }
464
+ if (descriptor->textFont.length() > 0) {
465
+ textOptions->set("font", const_cast<char*>(descriptor->textFont.data()));
466
+ }
467
+ if (descriptor->textFontfile.length() > 0) {
468
+ textOptions->set("fontfile", const_cast<char*>(descriptor->textFontfile.data()));
469
+ }
470
+ image = VImage::text(const_cast<char *>(descriptor->textValue.data()), textOptions);
471
+ if (!descriptor->textRgba) {
472
+ image = image.copy(VImage::option()->set("interpretation", VIPS_INTERPRETATION_B_W));
473
+ }
474
+ imageType = ImageType::RAW;
475
+ } else {
476
+ // From filesystem
477
+ imageType = DetermineImageType(descriptor->file.data());
478
+ if (imageType == ImageType::MISSING) {
479
+ if (descriptor->file.find("<svg") != std::string::npos) {
480
+ throw vips::VError("Input file is missing, did you mean "
481
+ "sharp(Buffer.from('" + descriptor->file.substr(0, 8) + "...')?");
482
+ }
483
+ throw vips::VError("Input file is missing: " + descriptor->file);
484
+ }
485
+ if (imageType != ImageType::UNKNOWN) {
486
+ try {
487
+ vips::VOption *option = VImage::option()
488
+ ->set("access", descriptor->access)
489
+ ->set("fail_on", descriptor->failOn);
490
+ if (descriptor->unlimited && ImageTypeSupportsUnlimited(imageType)) {
491
+ option->set("unlimited", TRUE);
492
+ }
493
+ if (imageType == ImageType::SVG || imageType == ImageType::PDF) {
494
+ option->set("dpi", descriptor->density);
495
+ }
496
+ if (imageType == ImageType::MAGICK) {
497
+ option->set("density", std::to_string(descriptor->density).data());
498
+ }
499
+ if (ImageTypeSupportsPage(imageType)) {
500
+ option->set("n", descriptor->pages);
501
+ option->set("page", descriptor->page);
502
+ }
503
+ if (imageType == ImageType::OPENSLIDE) {
504
+ option->set("level", descriptor->level);
505
+ }
506
+ if (imageType == ImageType::TIFF) {
507
+ option->set("subifd", descriptor->subifd);
508
+ }
509
+ image = VImage::new_from_file(descriptor->file.data(), option);
510
+ if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
511
+ image = SetDensity(image, descriptor->density);
512
+ }
513
+ } catch (vips::VError const &err) {
514
+ throw vips::VError(std::string("Input file has corrupt header: ") + err.what());
515
+ }
516
+ } else {
517
+ throw vips::VError("Input file contains unsupported image format");
518
+ }
519
+ }
520
+ }
521
+
522
+ // Limit input images to a given number of pixels, where pixels = width * height
523
+ if (descriptor->limitInputPixels > 0 &&
524
+ static_cast<uint64_t>(image.width()) * image.height() > descriptor->limitInputPixels) {
525
+ throw vips::VError("Input image exceeds pixel limit");
526
+ }
527
+ return std::make_tuple(image, imageType);
528
+ }
529
+
530
+ /*
531
+ Does this image have an embedded profile?
532
+ */
533
+ bool HasProfile(VImage image) {
534
+ return image.get_typeof(VIPS_META_ICC_NAME) == VIPS_TYPE_BLOB;
535
+ }
536
+
537
+ /*
538
+ Get copy of embedded profile.
539
+ */
540
+ std::pair<char*, size_t> GetProfile(VImage image) {
541
+ std::pair<char*, size_t> icc(nullptr, 0);
542
+ if (HasProfile(image)) {
543
+ size_t length;
544
+ const void *data = image.get_blob(VIPS_META_ICC_NAME, &length);
545
+ icc.first = static_cast<char*>(g_malloc(length));
546
+ icc.second = length;
547
+ memcpy(icc.first, data, length);
548
+ }
549
+ return icc;
550
+ }
551
+
552
+ /*
553
+ Set embedded profile.
554
+ */
555
+ VImage SetProfile(VImage image, std::pair<char*, size_t> icc) {
556
+ if (icc.first != nullptr) {
557
+ image = image.copy();
558
+ image.set(VIPS_META_ICC_NAME, reinterpret_cast<VipsCallbackFn>(vips_area_free_cb), icc.first, icc.second);
559
+ }
560
+ return image;
561
+ }
562
+
563
+ /*
564
+ Does this image have an alpha channel?
565
+ Uses colour space interpretation with number of channels to guess this.
566
+ */
567
+ bool HasAlpha(VImage image) {
568
+ return image.has_alpha();
569
+ }
570
+
571
+ static void* RemoveExifCallback(VipsImage *image, char const *field, GValue *value, void *data) {
572
+ std::vector<std::string> *fieldNames = static_cast<std::vector<std::string> *>(data);
573
+ std::string fieldName(field);
574
+ if (fieldName.substr(0, 8) == ("exif-ifd")) {
575
+ fieldNames->push_back(fieldName);
576
+ }
577
+ return nullptr;
578
+ }
579
+
580
+ /*
581
+ Remove all EXIF-related image fields.
582
+ */
583
+ VImage RemoveExif(VImage image) {
584
+ std::vector<std::string> fieldNames;
585
+ vips_image_map(image.get_image(), static_cast<VipsImageMapFn>(RemoveExifCallback), &fieldNames);
586
+ for (const auto& f : fieldNames) {
587
+ image.remove(f.data());
588
+ }
589
+ return image;
590
+ }
591
+
592
+ /*
593
+ Get EXIF Orientation of image, if any.
594
+ */
595
+ int ExifOrientation(VImage image) {
596
+ int orientation = 0;
597
+ if (image.get_typeof(VIPS_META_ORIENTATION) != 0) {
598
+ orientation = image.get_int(VIPS_META_ORIENTATION);
599
+ }
600
+ return orientation;
601
+ }
602
+
603
+ /*
604
+ Set EXIF Orientation of image.
605
+ */
606
+ VImage SetExifOrientation(VImage image, int const orientation) {
607
+ VImage copy = image.copy();
608
+ copy.set(VIPS_META_ORIENTATION, orientation);
609
+ return copy;
610
+ }
611
+
612
+ /*
613
+ Remove EXIF Orientation from image.
614
+ */
615
+ VImage RemoveExifOrientation(VImage image) {
616
+ VImage copy = image.copy();
617
+ copy.remove(VIPS_META_ORIENTATION);
618
+ copy.remove("exif-ifd0-Orientation");
619
+ return copy;
620
+ }
621
+
622
+ /*
623
+ Set animation properties if necessary.
624
+ */
625
+ VImage SetAnimationProperties(VImage image, int nPages, int pageHeight, std::vector<int> delay, int loop) {
626
+ bool hasDelay = !delay.empty();
627
+
628
+ // Avoid a copy if none of the animation properties are needed.
629
+ if (nPages == 1 && !hasDelay && loop == -1) return image;
630
+
631
+ if (delay.size() == 1) {
632
+ // We have just one delay, repeat that value for all frames.
633
+ delay.insert(delay.end(), nPages - 1, delay[0]);
634
+ }
635
+
636
+ // Attaching metadata, need to copy the image.
637
+ VImage copy = image.copy();
638
+
639
+ // Only set page-height if we have more than one page, or this could
640
+ // accidentally turn into an animated image later.
641
+ if (nPages > 1) copy.set(VIPS_META_PAGE_HEIGHT, pageHeight);
642
+ if (hasDelay) copy.set("delay", delay);
643
+ if (loop != -1) copy.set("loop", loop);
644
+
645
+ return copy;
646
+ }
647
+
648
+ /*
649
+ Remove animation properties from image.
650
+ */
651
+ VImage RemoveAnimationProperties(VImage image) {
652
+ VImage copy = image.copy();
653
+ copy.remove(VIPS_META_PAGE_HEIGHT);
654
+ copy.remove("delay");
655
+ copy.remove("loop");
656
+ return copy;
657
+ }
658
+
659
+ /*
660
+ Remove GIF palette from image.
661
+ */
662
+ VImage RemoveGifPalette(VImage image) {
663
+ VImage copy = image.copy();
664
+ copy.remove("gif-palette");
665
+ return copy;
666
+ }
667
+
668
+ /*
669
+ Does this image have a non-default density?
670
+ */
671
+ bool HasDensity(VImage image) {
672
+ return image.xres() > 1.0;
673
+ }
674
+
675
+ /*
676
+ Get pixels/mm resolution as pixels/inch density.
677
+ */
678
+ int GetDensity(VImage image) {
679
+ return static_cast<int>(round(image.xres() * 25.4));
680
+ }
681
+
682
+ /*
683
+ Set pixels/mm resolution based on a pixels/inch density.
684
+ */
685
+ VImage SetDensity(VImage image, const double density) {
686
+ const double pixelsPerMm = density / 25.4;
687
+ VImage copy = image.copy();
688
+ copy.get_image()->Xres = pixelsPerMm;
689
+ copy.get_image()->Yres = pixelsPerMm;
690
+ return copy;
691
+ }
692
+
693
+ /*
694
+ Multi-page images can have a page height. Fetch it, and sanity check it.
695
+ If page-height is not set, it defaults to the image height
696
+ */
697
+ int GetPageHeight(VImage image) {
698
+ return vips_image_get_page_height(image.get_image());
699
+ }
700
+
701
+ /*
702
+ Check the proposed format supports the current dimensions.
703
+ */
704
+ void AssertImageTypeDimensions(VImage image, ImageType const imageType) {
705
+ const int height = image.get_typeof(VIPS_META_PAGE_HEIGHT) == G_TYPE_INT
706
+ ? image.get_int(VIPS_META_PAGE_HEIGHT)
707
+ : image.height();
708
+ if (imageType == ImageType::JPEG) {
709
+ if (image.width() > 65535 || height > 65535) {
710
+ throw vips::VError("Processed image is too large for the JPEG format");
711
+ }
712
+ } else if (imageType == ImageType::WEBP) {
713
+ if (image.width() > 16383 || height > 16383) {
714
+ throw vips::VError("Processed image is too large for the WebP format");
715
+ }
716
+ } else if (imageType == ImageType::GIF) {
717
+ if (image.width() > 65535 || height > 65535) {
718
+ throw vips::VError("Processed image is too large for the GIF format");
719
+ }
720
+ } else if (imageType == ImageType::HEIF) {
721
+ if (image.width() > 16384 || height > 16384) {
722
+ throw vips::VError("Processed image is too large for the HEIF format");
723
+ }
724
+ }
725
+ }
726
+
727
+ /*
728
+ Called when a Buffer undergoes GC, required to support mixed runtime libraries in Windows
729
+ */
730
+ std::function<void(void*, char*)> FreeCallback = [](void*, char* data) {
731
+ g_free(data);
732
+ };
733
+
734
+ /*
735
+ Temporary buffer of warnings
736
+ */
737
+ std::queue<std::string> vipsWarnings;
738
+ std::mutex vipsWarningsMutex;
739
+
740
+ /*
741
+ Called with warnings from the glib-registered "VIPS" domain
742
+ */
743
+ void VipsWarningCallback(char const* log_domain, GLogLevelFlags log_level, char const* message, void* ignore) {
744
+ std::lock_guard<std::mutex> lock(vipsWarningsMutex);
745
+ vipsWarnings.emplace(message);
746
+ }
747
+
748
+ /*
749
+ Pop the oldest warning message from the queue
750
+ */
751
+ std::string VipsWarningPop() {
752
+ std::string warning;
753
+ std::lock_guard<std::mutex> lock(vipsWarningsMutex);
754
+ if (!vipsWarnings.empty()) {
755
+ warning = vipsWarnings.front();
756
+ vipsWarnings.pop();
757
+ }
758
+ return warning;
759
+ }
760
+
761
+ /*
762
+ Attach an event listener for progress updates, used to detect timeout
763
+ */
764
+ void SetTimeout(VImage image, int const seconds) {
765
+ if (seconds > 0) {
766
+ VipsImage *im = image.get_image();
767
+ if (im->progress_signal == NULL) {
768
+ int *timeout = VIPS_NEW(im, int);
769
+ *timeout = seconds;
770
+ g_signal_connect(im, "eval", G_CALLBACK(VipsProgressCallBack), timeout);
771
+ vips_image_set_progress(im, TRUE);
772
+ }
773
+ }
774
+ }
775
+
776
+ /*
777
+ Event listener for progress updates, used to detect timeout
778
+ */
779
+ void VipsProgressCallBack(VipsImage *im, VipsProgress *progress, int *timeout) {
780
+ if (*timeout > 0 && progress->run >= *timeout) {
781
+ vips_image_set_kill(im, TRUE);
782
+ vips_error("timeout", "%d%% complete", progress->percent);
783
+ *timeout = 0;
784
+ }
785
+ }
786
+
787
+ /*
788
+ Calculate the (left, top) coordinates of the output image
789
+ within the input image, applying the given gravity during an embed.
790
+
791
+ @Azurebyte: We are basically swapping the inWidth and outWidth, inHeight and outHeight from the CalculateCrop function.
792
+ */
793
+ std::tuple<int, int> CalculateEmbedPosition(int const inWidth, int const inHeight,
794
+ int const outWidth, int const outHeight, int const gravity) {
795
+
796
+ int left = 0;
797
+ int top = 0;
798
+ switch (gravity) {
799
+ case 1:
800
+ // North
801
+ left = (outWidth - inWidth) / 2;
802
+ break;
803
+ case 2:
804
+ // East
805
+ left = outWidth - inWidth;
806
+ top = (outHeight - inHeight) / 2;
807
+ break;
808
+ case 3:
809
+ // South
810
+ left = (outWidth - inWidth) / 2;
811
+ top = outHeight - inHeight;
812
+ break;
813
+ case 4:
814
+ // West
815
+ top = (outHeight - inHeight) / 2;
816
+ break;
817
+ case 5:
818
+ // Northeast
819
+ left = outWidth - inWidth;
820
+ break;
821
+ case 6:
822
+ // Southeast
823
+ left = outWidth - inWidth;
824
+ top = outHeight - inHeight;
825
+ break;
826
+ case 7:
827
+ // Southwest
828
+ top = outHeight - inHeight;
829
+ break;
830
+ case 8:
831
+ // Northwest
832
+ // Which is the default is 0,0 so we do not assign anything here.
833
+ break;
834
+ default:
835
+ // Centre
836
+ left = (outWidth - inWidth) / 2;
837
+ top = (outHeight - inHeight) / 2;
838
+ }
839
+ return std::make_tuple(left, top);
840
+ }
841
+
842
+ /*
843
+ Calculate the (left, top) coordinates of the output image
844
+ within the input image, applying the given gravity during a crop.
845
+ */
846
+ std::tuple<int, int> CalculateCrop(int const inWidth, int const inHeight,
847
+ int const outWidth, int const outHeight, int const gravity) {
848
+
849
+ int left = 0;
850
+ int top = 0;
851
+ switch (gravity) {
852
+ case 1:
853
+ // North
854
+ left = (inWidth - outWidth + 1) / 2;
855
+ break;
856
+ case 2:
857
+ // East
858
+ left = inWidth - outWidth;
859
+ top = (inHeight - outHeight + 1) / 2;
860
+ break;
861
+ case 3:
862
+ // South
863
+ left = (inWidth - outWidth + 1) / 2;
864
+ top = inHeight - outHeight;
865
+ break;
866
+ case 4:
867
+ // West
868
+ top = (inHeight - outHeight + 1) / 2;
869
+ break;
870
+ case 5:
871
+ // Northeast
872
+ left = inWidth - outWidth;
873
+ break;
874
+ case 6:
875
+ // Southeast
876
+ left = inWidth - outWidth;
877
+ top = inHeight - outHeight;
878
+ break;
879
+ case 7:
880
+ // Southwest
881
+ top = inHeight - outHeight;
882
+ break;
883
+ case 8:
884
+ // Northwest
885
+ break;
886
+ default:
887
+ // Centre
888
+ left = (inWidth - outWidth + 1) / 2;
889
+ top = (inHeight - outHeight + 1) / 2;
890
+ }
891
+ return std::make_tuple(left, top);
892
+ }
893
+
894
+ /*
895
+ Calculate the (left, top) coordinates of the output image
896
+ within the input image, applying the given x and y offsets.
897
+ */
898
+ std::tuple<int, int> CalculateCrop(int const inWidth, int const inHeight,
899
+ int const outWidth, int const outHeight, int const x, int const y) {
900
+
901
+ // default values
902
+ int left = 0;
903
+ int top = 0;
904
+
905
+ // assign only if valid
906
+ if (x < (inWidth - outWidth)) {
907
+ left = x;
908
+ } else if (x >= (inWidth - outWidth)) {
909
+ left = inWidth - outWidth;
910
+ }
911
+
912
+ if (y < (inHeight - outHeight)) {
913
+ top = y;
914
+ } else if (y >= (inHeight - outHeight)) {
915
+ top = inHeight - outHeight;
916
+ }
917
+
918
+ return std::make_tuple(left, top);
919
+ }
920
+
921
+ /*
922
+ Are pixel values in this image 16-bit integer?
923
+ */
924
+ bool Is16Bit(VipsInterpretation const interpretation) {
925
+ return interpretation == VIPS_INTERPRETATION_RGB16 || interpretation == VIPS_INTERPRETATION_GREY16;
926
+ }
927
+
928
+ /*
929
+ Return the image alpha maximum. Useful for combining alpha bands. scRGB
930
+ images are 0 - 1 for image data, but the alpha is 0 - 255.
931
+ */
932
+ double MaximumImageAlpha(VipsInterpretation const interpretation) {
933
+ return Is16Bit(interpretation) ? 65535.0 : 255.0;
934
+ }
935
+
936
+ /*
937
+ Convert RGBA value to another colourspace
938
+ */
939
+ std::vector<double> GetRgbaAsColourspace(std::vector<double> const rgba,
940
+ VipsInterpretation const interpretation, bool premultiply) {
941
+ int const bands = static_cast<int>(rgba.size());
942
+ if (bands < 3) {
943
+ return rgba;
944
+ }
945
+ VImage pixel = VImage::new_matrix(1, 1);
946
+ pixel.set("bands", bands);
947
+ pixel = pixel
948
+ .new_from_image(rgba)
949
+ .colourspace(interpretation, VImage::option()->set("source_space", VIPS_INTERPRETATION_sRGB));
950
+ if (premultiply) {
951
+ pixel = pixel.premultiply();
952
+ }
953
+ return pixel(0, 0);
954
+ }
955
+
956
+ /*
957
+ Apply the alpha channel to a given colour
958
+ */
959
+ std::tuple<VImage, std::vector<double>> ApplyAlpha(VImage image, std::vector<double> colour, bool premultiply) {
960
+ // Scale up 8-bit values to match 16-bit input image
961
+ double const multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0;
962
+ // Create alphaColour colour
963
+ std::vector<double> alphaColour;
964
+ if (image.bands() > 2) {
965
+ alphaColour = {
966
+ multiplier * colour[0],
967
+ multiplier * colour[1],
968
+ multiplier * colour[2]
969
+ };
970
+ } else {
971
+ // Convert sRGB to greyscale
972
+ alphaColour = { multiplier * (
973
+ 0.2126 * colour[0] +
974
+ 0.7152 * colour[1] +
975
+ 0.0722 * colour[2])
976
+ };
977
+ }
978
+ // Add alpha channel to alphaColour colour
979
+ if (colour[3] < 255.0 || HasAlpha(image)) {
980
+ alphaColour.push_back(colour[3] * multiplier);
981
+ }
982
+ // Ensure alphaColour colour uses correct colourspace
983
+ alphaColour = sharp::GetRgbaAsColourspace(alphaColour, image.interpretation(), premultiply);
984
+ // Add non-transparent alpha channel, if required
985
+ if (colour[3] < 255.0 && !HasAlpha(image)) {
986
+ image = image.bandjoin(
987
+ VImage::new_matrix(image.width(), image.height()).new_from_image(255 * multiplier).cast(image.format()));
988
+ }
989
+ return std::make_tuple(image, alphaColour);
990
+ }
991
+
992
+ /*
993
+ Removes alpha channel, if any.
994
+ */
995
+ VImage RemoveAlpha(VImage image) {
996
+ if (HasAlpha(image)) {
997
+ image = image.extract_band(0, VImage::option()->set("n", image.bands() - 1));
998
+ }
999
+ return image;
1000
+ }
1001
+
1002
+ /*
1003
+ Ensures alpha channel, if missing.
1004
+ */
1005
+ VImage EnsureAlpha(VImage image, double const value) {
1006
+ if (!HasAlpha(image)) {
1007
+ std::vector<double> alpha;
1008
+ alpha.push_back(value * sharp::MaximumImageAlpha(image.interpretation()));
1009
+ image = image.bandjoin_const(alpha);
1010
+ }
1011
+ return image;
1012
+ }
1013
+
1014
+ std::pair<double, double> ResolveShrink(int width, int height, int targetWidth, int targetHeight,
1015
+ Canvas canvas, bool withoutEnlargement, bool withoutReduction) {
1016
+ double hshrink = 1.0;
1017
+ double vshrink = 1.0;
1018
+
1019
+ if (targetWidth > 0 && targetHeight > 0) {
1020
+ // Fixed width and height
1021
+ hshrink = static_cast<double>(width) / targetWidth;
1022
+ vshrink = static_cast<double>(height) / targetHeight;
1023
+
1024
+ switch (canvas) {
1025
+ case Canvas::CROP:
1026
+ case Canvas::MIN:
1027
+ if (hshrink < vshrink) {
1028
+ vshrink = hshrink;
1029
+ } else {
1030
+ hshrink = vshrink;
1031
+ }
1032
+ break;
1033
+ case Canvas::EMBED:
1034
+ case Canvas::MAX:
1035
+ if (hshrink > vshrink) {
1036
+ vshrink = hshrink;
1037
+ } else {
1038
+ hshrink = vshrink;
1039
+ }
1040
+ break;
1041
+ case Canvas::IGNORE_ASPECT:
1042
+ break;
1043
+ }
1044
+ } else if (targetWidth > 0) {
1045
+ // Fixed width
1046
+ hshrink = static_cast<double>(width) / targetWidth;
1047
+
1048
+ if (canvas != Canvas::IGNORE_ASPECT) {
1049
+ // Auto height
1050
+ vshrink = hshrink;
1051
+ }
1052
+ } else if (targetHeight > 0) {
1053
+ // Fixed height
1054
+ vshrink = static_cast<double>(height) / targetHeight;
1055
+
1056
+ if (canvas != Canvas::IGNORE_ASPECT) {
1057
+ // Auto width
1058
+ hshrink = vshrink;
1059
+ }
1060
+ }
1061
+
1062
+ // We should not reduce or enlarge the output image, if
1063
+ // withoutReduction or withoutEnlargement is specified.
1064
+ if (withoutReduction) {
1065
+ // Equivalent of VIPS_SIZE_UP
1066
+ hshrink = std::min(1.0, hshrink);
1067
+ vshrink = std::min(1.0, vshrink);
1068
+ } else if (withoutEnlargement) {
1069
+ // Equivalent of VIPS_SIZE_DOWN
1070
+ hshrink = std::max(1.0, hshrink);
1071
+ vshrink = std::max(1.0, vshrink);
1072
+ }
1073
+
1074
+ // We don't want to shrink so much that we send an axis to 0
1075
+ hshrink = std::min(hshrink, static_cast<double>(width));
1076
+ vshrink = std::min(vshrink, static_cast<double>(height));
1077
+
1078
+ return std::make_pair(hshrink, vshrink);
1079
+ }
1080
+
1081
+ /*
1082
+ Ensure decoding remains sequential.
1083
+ */
1084
+ VImage StaySequential(VImage image, VipsAccess access, bool condition) {
1085
+ if (access == VIPS_ACCESS_SEQUENTIAL && condition) {
1086
+ return image.copy_memory();
1087
+ }
1088
+ return image;
1089
+ }
1090
+ } // namespace sharp