UNPKG

37.7 kBtext/x-cView Raw
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
17using vips::VImage;
18
19namespace 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, bool condition) {
1085 if (vips_image_is_sequential(image.get_image()) && condition) {
1086 image = image.copy_memory().copy();
1087 image.remove(VIPS_META_SEQUENTIAL);
1088 }
1089 return image;
1090 }
1091} // namespace sharp