UNPKG

35.8 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 volatile int counterQueue = 0;
170
171 // How many tasks are being processed?
172 volatile 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 image = VImage::new_from_memory(descriptor->buffer, descriptor->bufferLength,
367 descriptor->rawWidth, descriptor->rawHeight, descriptor->rawChannels, descriptor->rawDepth);
368 if (descriptor->rawChannels < 3) {
369 image.get_image()->Type = VIPS_INTERPRETATION_B_W;
370 } else {
371 image.get_image()->Type = VIPS_INTERPRETATION_sRGB;
372 }
373 if (descriptor->rawPremultiplied) {
374 image = image.unpremultiply();
375 }
376 imageType = ImageType::RAW;
377 } else {
378 // Compressed data
379 imageType = DetermineImageType(descriptor->buffer, descriptor->bufferLength);
380 if (imageType != ImageType::UNKNOWN) {
381 try {
382 vips::VOption *option = VImage::option()
383 ->set("access", descriptor->access)
384 ->set("fail_on", descriptor->failOn);
385 if (descriptor->unlimited && ImageTypeSupportsUnlimited(imageType)) {
386 option->set("unlimited", TRUE);
387 }
388 if (imageType == ImageType::SVG || imageType == ImageType::PDF) {
389 option->set("dpi", descriptor->density);
390 }
391 if (imageType == ImageType::MAGICK) {
392 option->set("density", std::to_string(descriptor->density).data());
393 }
394 if (ImageTypeSupportsPage(imageType)) {
395 option->set("n", descriptor->pages);
396 option->set("page", descriptor->page);
397 }
398 if (imageType == ImageType::OPENSLIDE) {
399 option->set("level", descriptor->level);
400 }
401 if (imageType == ImageType::TIFF) {
402 option->set("subifd", descriptor->subifd);
403 }
404 image = VImage::new_from_buffer(descriptor->buffer, descriptor->bufferLength, nullptr, option);
405 if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
406 image = SetDensity(image, descriptor->density);
407 }
408 } catch (vips::VError const &err) {
409 throw vips::VError(std::string("Input buffer has corrupt header: ") + err.what());
410 }
411 } else {
412 throw vips::VError("Input buffer contains unsupported image format");
413 }
414 }
415 } else {
416 int const channels = descriptor->createChannels;
417 if (channels > 0) {
418 // Create new image
419 if (descriptor->createNoiseType == "gaussian") {
420 std::vector<VImage> bands = {};
421 bands.reserve(channels);
422 for (int _band = 0; _band < channels; _band++) {
423 bands.push_back(VImage::gaussnoise(descriptor->createWidth, descriptor->createHeight, VImage::option()
424 ->set("mean", descriptor->createNoiseMean)
425 ->set("sigma", descriptor->createNoiseSigma)));
426 }
427 image = VImage::bandjoin(bands).copy(VImage::option()->set("interpretation",
428 channels < 3 ? VIPS_INTERPRETATION_B_W: VIPS_INTERPRETATION_sRGB));
429 } else {
430 std::vector<double> background = {
431 descriptor->createBackground[0],
432 descriptor->createBackground[1],
433 descriptor->createBackground[2]
434 };
435 if (channels == 4) {
436 background.push_back(descriptor->createBackground[3]);
437 }
438 image = VImage::new_matrix(descriptor->createWidth, descriptor->createHeight)
439 .copy(VImage::option()->set("interpretation",
440 channels < 3 ? VIPS_INTERPRETATION_B_W : VIPS_INTERPRETATION_sRGB))
441 .new_from_image(background);
442 }
443 image = image.cast(VIPS_FORMAT_UCHAR);
444 imageType = ImageType::RAW;
445 } else if (descriptor->textValue.length() > 0) {
446 // Create a new image with text
447 vips::VOption *textOptions = VImage::option()
448 ->set("align", descriptor->textAlign)
449 ->set("justify", descriptor->textJustify)
450 ->set("rgba", descriptor->textRgba)
451 ->set("spacing", descriptor->textSpacing)
452 ->set("wrap", descriptor->textWrap)
453 ->set("autofit_dpi", &descriptor->textAutofitDpi);
454 if (descriptor->textWidth > 0) {
455 textOptions->set("width", descriptor->textWidth);
456 }
457 // Ignore dpi if height is set
458 if (descriptor->textWidth > 0 && descriptor->textHeight > 0) {
459 textOptions->set("height", descriptor->textHeight);
460 } else if (descriptor->textDpi > 0) {
461 textOptions->set("dpi", descriptor->textDpi);
462 }
463 if (descriptor->textFont.length() > 0) {
464 textOptions->set("font", const_cast<char*>(descriptor->textFont.data()));
465 }
466 if (descriptor->textFontfile.length() > 0) {
467 textOptions->set("fontfile", const_cast<char*>(descriptor->textFontfile.data()));
468 }
469 image = VImage::text(const_cast<char *>(descriptor->textValue.data()), textOptions);
470 if (!descriptor->textRgba) {
471 image = image.copy(VImage::option()->set("interpretation", VIPS_INTERPRETATION_B_W));
472 }
473 imageType = ImageType::RAW;
474 } else {
475 // From filesystem
476 imageType = DetermineImageType(descriptor->file.data());
477 if (imageType == ImageType::MISSING) {
478 if (descriptor->file.find("<svg") != std::string::npos) {
479 throw vips::VError("Input file is missing, did you mean "
480 "sharp(Buffer.from('" + descriptor->file.substr(0, 8) + "...')?");
481 }
482 throw vips::VError("Input file is missing: " + descriptor->file);
483 }
484 if (imageType != ImageType::UNKNOWN) {
485 try {
486 vips::VOption *option = VImage::option()
487 ->set("access", descriptor->access)
488 ->set("fail_on", descriptor->failOn);
489 if (descriptor->unlimited && ImageTypeSupportsUnlimited(imageType)) {
490 option->set("unlimited", TRUE);
491 }
492 if (imageType == ImageType::SVG || imageType == ImageType::PDF) {
493 option->set("dpi", descriptor->density);
494 }
495 if (imageType == ImageType::MAGICK) {
496 option->set("density", std::to_string(descriptor->density).data());
497 }
498 if (ImageTypeSupportsPage(imageType)) {
499 option->set("n", descriptor->pages);
500 option->set("page", descriptor->page);
501 }
502 if (imageType == ImageType::OPENSLIDE) {
503 option->set("level", descriptor->level);
504 }
505 if (imageType == ImageType::TIFF) {
506 option->set("subifd", descriptor->subifd);
507 }
508 image = VImage::new_from_file(descriptor->file.data(), option);
509 if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
510 image = SetDensity(image, descriptor->density);
511 }
512 } catch (vips::VError const &err) {
513 throw vips::VError(std::string("Input file has corrupt header: ") + err.what());
514 }
515 } else {
516 throw vips::VError("Input file contains unsupported image format");
517 }
518 }
519 }
520
521 // Limit input images to a given number of pixels, where pixels = width * height
522 if (descriptor->limitInputPixels > 0 &&
523 static_cast<uint64_t>(image.width()) * image.height() > descriptor->limitInputPixels) {
524 throw vips::VError("Input image exceeds pixel limit");
525 }
526 return std::make_tuple(image, imageType);
527 }
528
529 /*
530 Does this image have an embedded profile?
531 */
532 bool HasProfile(VImage image) {
533 return (image.get_typeof(VIPS_META_ICC_NAME) != 0) ? TRUE : FALSE;
534 }
535
536 /*
537 Does this image have an alpha channel?
538 Uses colour space interpretation with number of channels to guess this.
539 */
540 bool HasAlpha(VImage image) {
541 return image.has_alpha();
542 }
543
544 /*
545 Get EXIF Orientation of image, if any.
546 */
547 int ExifOrientation(VImage image) {
548 int orientation = 0;
549 if (image.get_typeof(VIPS_META_ORIENTATION) != 0) {
550 orientation = image.get_int(VIPS_META_ORIENTATION);
551 }
552 return orientation;
553 }
554
555 /*
556 Set EXIF Orientation of image.
557 */
558 VImage SetExifOrientation(VImage image, int const orientation) {
559 VImage copy = image.copy();
560 copy.set(VIPS_META_ORIENTATION, orientation);
561 return copy;
562 }
563
564 /*
565 Remove EXIF Orientation from image.
566 */
567 VImage RemoveExifOrientation(VImage image) {
568 VImage copy = image.copy();
569 copy.remove(VIPS_META_ORIENTATION);
570 copy.remove("exif-ifd0-Orientation");
571 return copy;
572 }
573
574 /*
575 Set animation properties if necessary.
576 */
577 VImage SetAnimationProperties(VImage image, int nPages, int pageHeight, std::vector<int> delay, int loop) {
578 bool hasDelay = !delay.empty();
579
580 // Avoid a copy if none of the animation properties are needed.
581 if (nPages == 1 && !hasDelay && loop == -1) return image;
582
583 if (delay.size() == 1) {
584 // We have just one delay, repeat that value for all frames.
585 delay.insert(delay.end(), nPages - 1, delay[0]);
586 }
587
588 // Attaching metadata, need to copy the image.
589 VImage copy = image.copy();
590
591 // Only set page-height if we have more than one page, or this could
592 // accidentally turn into an animated image later.
593 if (nPages > 1) copy.set(VIPS_META_PAGE_HEIGHT, pageHeight);
594 if (hasDelay) copy.set("delay", delay);
595 if (loop != -1) copy.set("loop", loop);
596
597 return copy;
598 }
599
600 /*
601 Remove animation properties from image.
602 */
603 VImage RemoveAnimationProperties(VImage image) {
604 VImage copy = image.copy();
605 copy.remove(VIPS_META_PAGE_HEIGHT);
606 copy.remove("delay");
607 copy.remove("loop");
608 return copy;
609 }
610
611 /*
612 Remove GIF palette from image.
613 */
614 VImage RemoveGifPalette(VImage image) {
615 VImage copy = image.copy();
616 copy.remove("gif-palette");
617 return copy;
618 }
619
620 /*
621 Does this image have a non-default density?
622 */
623 bool HasDensity(VImage image) {
624 return image.xres() > 1.0;
625 }
626
627 /*
628 Get pixels/mm resolution as pixels/inch density.
629 */
630 int GetDensity(VImage image) {
631 return static_cast<int>(round(image.xres() * 25.4));
632 }
633
634 /*
635 Set pixels/mm resolution based on a pixels/inch density.
636 */
637 VImage SetDensity(VImage image, const double density) {
638 const double pixelsPerMm = density / 25.4;
639 VImage copy = image.copy();
640 copy.get_image()->Xres = pixelsPerMm;
641 copy.get_image()->Yres = pixelsPerMm;
642 return copy;
643 }
644
645 /*
646 Multi-page images can have a page height. Fetch it, and sanity check it.
647 If page-height is not set, it defaults to the image height
648 */
649 int GetPageHeight(VImage image) {
650 return vips_image_get_page_height(image.get_image());
651 }
652
653 /*
654 Check the proposed format supports the current dimensions.
655 */
656 void AssertImageTypeDimensions(VImage image, ImageType const imageType) {
657 const int height = image.get_typeof(VIPS_META_PAGE_HEIGHT) == G_TYPE_INT
658 ? image.get_int(VIPS_META_PAGE_HEIGHT)
659 : image.height();
660 if (imageType == ImageType::JPEG) {
661 if (image.width() > 65535 || height > 65535) {
662 throw vips::VError("Processed image is too large for the JPEG format");
663 }
664 } else if (imageType == ImageType::WEBP) {
665 if (image.width() > 16383 || height > 16383) {
666 throw vips::VError("Processed image is too large for the WebP format");
667 }
668 } else if (imageType == ImageType::GIF) {
669 if (image.width() > 65535 || height > 65535) {
670 throw vips::VError("Processed image is too large for the GIF format");
671 }
672 }
673 }
674
675 /*
676 Called when a Buffer undergoes GC, required to support mixed runtime libraries in Windows
677 */
678 std::function<void(void*, char*)> FreeCallback = [](void*, char* data) {
679 g_free(data);
680 };
681
682 /*
683 Temporary buffer of warnings
684 */
685 std::queue<std::string> vipsWarnings;
686 std::mutex vipsWarningsMutex;
687
688 /*
689 Called with warnings from the glib-registered "VIPS" domain
690 */
691 void VipsWarningCallback(char const* log_domain, GLogLevelFlags log_level, char const* message, void* ignore) {
692 std::lock_guard<std::mutex> lock(vipsWarningsMutex);
693 vipsWarnings.emplace(message);
694 }
695
696 /*
697 Pop the oldest warning message from the queue
698 */
699 std::string VipsWarningPop() {
700 std::string warning;
701 std::lock_guard<std::mutex> lock(vipsWarningsMutex);
702 if (!vipsWarnings.empty()) {
703 warning = vipsWarnings.front();
704 vipsWarnings.pop();
705 }
706 return warning;
707 }
708
709 /*
710 Attach an event listener for progress updates, used to detect timeout
711 */
712 void SetTimeout(VImage image, int const seconds) {
713 if (seconds > 0) {
714 VipsImage *im = image.get_image();
715 if (im->progress_signal == NULL) {
716 int *timeout = VIPS_NEW(im, int);
717 *timeout = seconds;
718 g_signal_connect(im, "eval", G_CALLBACK(VipsProgressCallBack), timeout);
719 vips_image_set_progress(im, TRUE);
720 }
721 }
722 }
723
724 /*
725 Event listener for progress updates, used to detect timeout
726 */
727 void VipsProgressCallBack(VipsImage *im, VipsProgress *progress, int *timeout) {
728 if (*timeout > 0 && progress->run >= *timeout) {
729 vips_image_set_kill(im, TRUE);
730 vips_error("timeout", "%d%% complete", progress->percent);
731 *timeout = 0;
732 }
733 }
734
735 /*
736 Calculate the (left, top) coordinates of the output image
737 within the input image, applying the given gravity during an embed.
738
739 @Azurebyte: We are basically swapping the inWidth and outWidth, inHeight and outHeight from the CalculateCrop function.
740 */
741 std::tuple<int, int> CalculateEmbedPosition(int const inWidth, int const inHeight,
742 int const outWidth, int const outHeight, int const gravity) {
743
744 int left = 0;
745 int top = 0;
746 switch (gravity) {
747 case 1:
748 // North
749 left = (outWidth - inWidth) / 2;
750 break;
751 case 2:
752 // East
753 left = outWidth - inWidth;
754 top = (outHeight - inHeight) / 2;
755 break;
756 case 3:
757 // South
758 left = (outWidth - inWidth) / 2;
759 top = outHeight - inHeight;
760 break;
761 case 4:
762 // West
763 top = (outHeight - inHeight) / 2;
764 break;
765 case 5:
766 // Northeast
767 left = outWidth - inWidth;
768 break;
769 case 6:
770 // Southeast
771 left = outWidth - inWidth;
772 top = outHeight - inHeight;
773 break;
774 case 7:
775 // Southwest
776 top = outHeight - inHeight;
777 break;
778 case 8:
779 // Northwest
780 // Which is the default is 0,0 so we do not assign anything here.
781 break;
782 default:
783 // Centre
784 left = (outWidth - inWidth) / 2;
785 top = (outHeight - inHeight) / 2;
786 }
787 return std::make_tuple(left, top);
788 }
789
790 /*
791 Calculate the (left, top) coordinates of the output image
792 within the input image, applying the given gravity during a crop.
793 */
794 std::tuple<int, int> CalculateCrop(int const inWidth, int const inHeight,
795 int const outWidth, int const outHeight, int const gravity) {
796
797 int left = 0;
798 int top = 0;
799 switch (gravity) {
800 case 1:
801 // North
802 left = (inWidth - outWidth + 1) / 2;
803 break;
804 case 2:
805 // East
806 left = inWidth - outWidth;
807 top = (inHeight - outHeight + 1) / 2;
808 break;
809 case 3:
810 // South
811 left = (inWidth - outWidth + 1) / 2;
812 top = inHeight - outHeight;
813 break;
814 case 4:
815 // West
816 top = (inHeight - outHeight + 1) / 2;
817 break;
818 case 5:
819 // Northeast
820 left = inWidth - outWidth;
821 break;
822 case 6:
823 // Southeast
824 left = inWidth - outWidth;
825 top = inHeight - outHeight;
826 break;
827 case 7:
828 // Southwest
829 top = inHeight - outHeight;
830 break;
831 case 8:
832 // Northwest
833 break;
834 default:
835 // Centre
836 left = (inWidth - outWidth + 1) / 2;
837 top = (inHeight - outHeight + 1) / 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 x and y offsets.
845 */
846 std::tuple<int, int> CalculateCrop(int const inWidth, int const inHeight,
847 int const outWidth, int const outHeight, int const x, int const y) {
848
849 // default values
850 int left = 0;
851 int top = 0;
852
853 // assign only if valid
854 if (x < (inWidth - outWidth)) {
855 left = x;
856 } else if (x >= (inWidth - outWidth)) {
857 left = inWidth - outWidth;
858 }
859
860 if (y < (inHeight - outHeight)) {
861 top = y;
862 } else if (y >= (inHeight - outHeight)) {
863 top = inHeight - outHeight;
864 }
865
866 return std::make_tuple(left, top);
867 }
868
869 /*
870 Are pixel values in this image 16-bit integer?
871 */
872 bool Is16Bit(VipsInterpretation const interpretation) {
873 return interpretation == VIPS_INTERPRETATION_RGB16 || interpretation == VIPS_INTERPRETATION_GREY16;
874 }
875
876 /*
877 Return the image alpha maximum. Useful for combining alpha bands. scRGB
878 images are 0 - 1 for image data, but the alpha is 0 - 255.
879 */
880 double MaximumImageAlpha(VipsInterpretation const interpretation) {
881 return Is16Bit(interpretation) ? 65535.0 : 255.0;
882 }
883
884 /*
885 Convert RGBA value to another colourspace
886 */
887 std::vector<double> GetRgbaAsColourspace(std::vector<double> const rgba,
888 VipsInterpretation const interpretation, bool premultiply) {
889 int const bands = static_cast<int>(rgba.size());
890 if (bands < 3) {
891 return rgba;
892 }
893 VImage pixel = VImage::new_matrix(1, 1);
894 pixel.set("bands", bands);
895 pixel = pixel
896 .new_from_image(rgba)
897 .colourspace(interpretation, VImage::option()->set("source_space", VIPS_INTERPRETATION_sRGB));
898 if (premultiply) {
899 pixel = pixel.premultiply();
900 }
901 return pixel(0, 0);
902 }
903
904 /*
905 Apply the alpha channel to a given colour
906 */
907 std::tuple<VImage, std::vector<double>> ApplyAlpha(VImage image, std::vector<double> colour, bool premultiply) {
908 // Scale up 8-bit values to match 16-bit input image
909 double const multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0;
910 // Create alphaColour colour
911 std::vector<double> alphaColour;
912 if (image.bands() > 2) {
913 alphaColour = {
914 multiplier * colour[0],
915 multiplier * colour[1],
916 multiplier * colour[2]
917 };
918 } else {
919 // Convert sRGB to greyscale
920 alphaColour = { multiplier * (
921 0.2126 * colour[0] +
922 0.7152 * colour[1] +
923 0.0722 * colour[2])
924 };
925 }
926 // Add alpha channel to alphaColour colour
927 if (colour[3] < 255.0 || HasAlpha(image)) {
928 alphaColour.push_back(colour[3] * multiplier);
929 }
930 // Ensure alphaColour colour uses correct colourspace
931 alphaColour = sharp::GetRgbaAsColourspace(alphaColour, image.interpretation(), premultiply);
932 // Add non-transparent alpha channel, if required
933 if (colour[3] < 255.0 && !HasAlpha(image)) {
934 image = image.bandjoin(
935 VImage::new_matrix(image.width(), image.height()).new_from_image(255 * multiplier).cast(image.format()));
936 }
937 return std::make_tuple(image, alphaColour);
938 }
939
940 /*
941 Removes alpha channel, if any.
942 */
943 VImage RemoveAlpha(VImage image) {
944 if (HasAlpha(image)) {
945 image = image.extract_band(0, VImage::option()->set("n", image.bands() - 1));
946 }
947 return image;
948 }
949
950 /*
951 Ensures alpha channel, if missing.
952 */
953 VImage EnsureAlpha(VImage image, double const value) {
954 if (!HasAlpha(image)) {
955 std::vector<double> alpha;
956 alpha.push_back(value * sharp::MaximumImageAlpha(image.interpretation()));
957 image = image.bandjoin_const(alpha);
958 }
959 return image;
960 }
961
962 std::pair<double, double> ResolveShrink(int width, int height, int targetWidth, int targetHeight,
963 Canvas canvas, bool swap, bool withoutEnlargement, bool withoutReduction) {
964 if (swap && canvas != Canvas::IGNORE_ASPECT) {
965 // Swap input width and height when requested.
966 std::swap(width, height);
967 }
968
969 double hshrink = 1.0;
970 double vshrink = 1.0;
971
972 if (targetWidth > 0 && targetHeight > 0) {
973 // Fixed width and height
974 hshrink = static_cast<double>(width) / targetWidth;
975 vshrink = static_cast<double>(height) / targetHeight;
976
977 switch (canvas) {
978 case Canvas::CROP:
979 case Canvas::MIN:
980 if (hshrink < vshrink) {
981 vshrink = hshrink;
982 } else {
983 hshrink = vshrink;
984 }
985 break;
986 case Canvas::EMBED:
987 case Canvas::MAX:
988 if (hshrink > vshrink) {
989 vshrink = hshrink;
990 } else {
991 hshrink = vshrink;
992 }
993 break;
994 case Canvas::IGNORE_ASPECT:
995 break;
996 }
997 } else if (targetWidth > 0) {
998 // Fixed width
999 hshrink = static_cast<double>(width) / targetWidth;
1000
1001 if (canvas != Canvas::IGNORE_ASPECT) {
1002 // Auto height
1003 vshrink = hshrink;
1004 }
1005 } else if (targetHeight > 0) {
1006 // Fixed height
1007 vshrink = static_cast<double>(height) / targetHeight;
1008
1009 if (canvas != Canvas::IGNORE_ASPECT) {
1010 // Auto width
1011 hshrink = vshrink;
1012 }
1013 }
1014
1015 // We should not reduce or enlarge the output image, if
1016 // withoutReduction or withoutEnlargement is specified.
1017 if (withoutReduction) {
1018 // Equivalent of VIPS_SIZE_UP
1019 hshrink = std::min(1.0, hshrink);
1020 vshrink = std::min(1.0, vshrink);
1021 } else if (withoutEnlargement) {
1022 // Equivalent of VIPS_SIZE_DOWN
1023 hshrink = std::max(1.0, hshrink);
1024 vshrink = std::max(1.0, vshrink);
1025 }
1026
1027 // We don't want to shrink so much that we send an axis to 0
1028 hshrink = std::min(hshrink, static_cast<double>(width));
1029 vshrink = std::min(vshrink, static_cast<double>(height));
1030
1031 return std::make_pair(hshrink, vshrink);
1032 }
1033
1034} // namespace sharp