1 |
|
2 |
|
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 |
|
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 |
|
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 |
|
82 | if (HasAttr(input, "density")) {
|
83 | descriptor->density = AttrAsDouble(input, "density");
|
84 | }
|
85 |
|
86 | if (HasAttr(input, "ignoreIcc")) {
|
87 | descriptor->ignoreIcc = AttrAsBool(input, "ignoreIcc");
|
88 | }
|
89 |
|
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 |
|
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 |
|
105 | if (HasAttr(input, "level")) {
|
106 | descriptor->level = AttrAsUint32(input, "level");
|
107 | }
|
108 |
|
109 | if (HasAttr(input, "subifd")) {
|
110 | descriptor->subifd = AttrAsInt32(input, "subifd");
|
111 | }
|
112 |
|
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 |
|
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 |
|
160 | descriptor->limitInputPixels = static_cast<uint64_t>(AttrAsInt64(input, "limitInputPixels"));
|
161 |
|
162 | descriptor->access = AttrAsBool(input, "sequentialRead") ? VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_RANDOM;
|
163 |
|
164 | descriptor->unlimited = AttrAsBool(input, "unlimited");
|
165 | return descriptor;
|
166 | }
|
167 |
|
168 |
|
169 | volatile int counterQueue = 0;
|
170 |
|
171 |
|
172 | volatile int counterProcess = 0;
|
173 |
|
174 |
|
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 |
|
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 |
|
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 |
|
257 |
|
258 |
|
259 |
|
260 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
531 |
|
532 | bool HasProfile(VImage image) {
|
533 | return (image.get_typeof(VIPS_META_ICC_NAME) != 0) ? TRUE : FALSE;
|
534 | }
|
535 |
|
536 | |
537 |
|
538 |
|
539 |
|
540 | bool HasAlpha(VImage image) {
|
541 | return image.has_alpha();
|
542 | }
|
543 |
|
544 | |
545 |
|
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 |
|
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 |
|
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 |
|
576 |
|
577 | VImage SetAnimationProperties(VImage image, int nPages, int pageHeight, std::vector<int> delay, int loop) {
|
578 | bool hasDelay = !delay.empty();
|
579 |
|
580 |
|
581 | if (nPages == 1 && !hasDelay && loop == -1) return image;
|
582 |
|
583 | if (delay.size() == 1) {
|
584 |
|
585 | delay.insert(delay.end(), nPages - 1, delay[0]);
|
586 | }
|
587 |
|
588 |
|
589 | VImage copy = image.copy();
|
590 |
|
591 |
|
592 |
|
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 |
|
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 |
|
613 |
|
614 | VImage RemoveGifPalette(VImage image) {
|
615 | VImage copy = image.copy();
|
616 | copy.remove("gif-palette");
|
617 | return copy;
|
618 | }
|
619 |
|
620 | |
621 |
|
622 |
|
623 | bool HasDensity(VImage image) {
|
624 | return image.xres() > 1.0;
|
625 | }
|
626 |
|
627 | |
628 |
|
629 |
|
630 | int GetDensity(VImage image) {
|
631 | return static_cast<int>(round(image.xres() * 25.4));
|
632 | }
|
633 |
|
634 | |
635 |
|
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 |
|
647 |
|
648 |
|
649 | int GetPageHeight(VImage image) {
|
650 | return vips_image_get_page_height(image.get_image());
|
651 | }
|
652 |
|
653 | |
654 |
|
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 | } else if (imageType == ImageType::HEIF) {
|
673 | if (image.width() > 16384 || height > 16384) {
|
674 | throw vips::VError("Processed image is too large for the HEIF format");
|
675 | }
|
676 | }
|
677 | }
|
678 |
|
679 | |
680 |
|
681 |
|
682 | std::function<void(void*, char*)> FreeCallback = [](void*, char* data) {
|
683 | g_free(data);
|
684 | };
|
685 |
|
686 | |
687 |
|
688 |
|
689 | std::queue<std::string> vipsWarnings;
|
690 | std::mutex vipsWarningsMutex;
|
691 |
|
692 | |
693 |
|
694 |
|
695 | void VipsWarningCallback(char const* log_domain, GLogLevelFlags log_level, char const* message, void* ignore) {
|
696 | std::lock_guard<std::mutex> lock(vipsWarningsMutex);
|
697 | vipsWarnings.emplace(message);
|
698 | }
|
699 |
|
700 | |
701 |
|
702 |
|
703 | std::string VipsWarningPop() {
|
704 | std::string warning;
|
705 | std::lock_guard<std::mutex> lock(vipsWarningsMutex);
|
706 | if (!vipsWarnings.empty()) {
|
707 | warning = vipsWarnings.front();
|
708 | vipsWarnings.pop();
|
709 | }
|
710 | return warning;
|
711 | }
|
712 |
|
713 | |
714 |
|
715 |
|
716 | void SetTimeout(VImage image, int const seconds) {
|
717 | if (seconds > 0) {
|
718 | VipsImage *im = image.get_image();
|
719 | if (im->progress_signal == NULL) {
|
720 | int *timeout = VIPS_NEW(im, int);
|
721 | *timeout = seconds;
|
722 | g_signal_connect(im, "eval", G_CALLBACK(VipsProgressCallBack), timeout);
|
723 | vips_image_set_progress(im, TRUE);
|
724 | }
|
725 | }
|
726 | }
|
727 |
|
728 | |
729 |
|
730 |
|
731 | void VipsProgressCallBack(VipsImage *im, VipsProgress *progress, int *timeout) {
|
732 | if (*timeout > 0 && progress->run >= *timeout) {
|
733 | vips_image_set_kill(im, TRUE);
|
734 | vips_error("timeout", "%d%% complete", progress->percent);
|
735 | *timeout = 0;
|
736 | }
|
737 | }
|
738 |
|
739 | |
740 |
|
741 |
|
742 |
|
743 |
|
744 |
|
745 | std::tuple<int, int> CalculateEmbedPosition(int const inWidth, int const inHeight,
|
746 | int const outWidth, int const outHeight, int const gravity) {
|
747 |
|
748 | int left = 0;
|
749 | int top = 0;
|
750 | switch (gravity) {
|
751 | case 1:
|
752 |
|
753 | left = (outWidth - inWidth) / 2;
|
754 | break;
|
755 | case 2:
|
756 |
|
757 | left = outWidth - inWidth;
|
758 | top = (outHeight - inHeight) / 2;
|
759 | break;
|
760 | case 3:
|
761 |
|
762 | left = (outWidth - inWidth) / 2;
|
763 | top = outHeight - inHeight;
|
764 | break;
|
765 | case 4:
|
766 |
|
767 | top = (outHeight - inHeight) / 2;
|
768 | break;
|
769 | case 5:
|
770 |
|
771 | left = outWidth - inWidth;
|
772 | break;
|
773 | case 6:
|
774 |
|
775 | left = outWidth - inWidth;
|
776 | top = outHeight - inHeight;
|
777 | break;
|
778 | case 7:
|
779 |
|
780 | top = outHeight - inHeight;
|
781 | break;
|
782 | case 8:
|
783 |
|
784 |
|
785 | break;
|
786 | default:
|
787 |
|
788 | left = (outWidth - inWidth) / 2;
|
789 | top = (outHeight - inHeight) / 2;
|
790 | }
|
791 | return std::make_tuple(left, top);
|
792 | }
|
793 |
|
794 | |
795 |
|
796 |
|
797 |
|
798 | std::tuple<int, int> CalculateCrop(int const inWidth, int const inHeight,
|
799 | int const outWidth, int const outHeight, int const gravity) {
|
800 |
|
801 | int left = 0;
|
802 | int top = 0;
|
803 | switch (gravity) {
|
804 | case 1:
|
805 |
|
806 | left = (inWidth - outWidth + 1) / 2;
|
807 | break;
|
808 | case 2:
|
809 |
|
810 | left = inWidth - outWidth;
|
811 | top = (inHeight - outHeight + 1) / 2;
|
812 | break;
|
813 | case 3:
|
814 |
|
815 | left = (inWidth - outWidth + 1) / 2;
|
816 | top = inHeight - outHeight;
|
817 | break;
|
818 | case 4:
|
819 |
|
820 | top = (inHeight - outHeight + 1) / 2;
|
821 | break;
|
822 | case 5:
|
823 |
|
824 | left = inWidth - outWidth;
|
825 | break;
|
826 | case 6:
|
827 |
|
828 | left = inWidth - outWidth;
|
829 | top = inHeight - outHeight;
|
830 | break;
|
831 | case 7:
|
832 |
|
833 | top = inHeight - outHeight;
|
834 | break;
|
835 | case 8:
|
836 |
|
837 | break;
|
838 | default:
|
839 |
|
840 | left = (inWidth - outWidth + 1) / 2;
|
841 | top = (inHeight - outHeight + 1) / 2;
|
842 | }
|
843 | return std::make_tuple(left, top);
|
844 | }
|
845 |
|
846 | |
847 |
|
848 |
|
849 |
|
850 | std::tuple<int, int> CalculateCrop(int const inWidth, int const inHeight,
|
851 | int const outWidth, int const outHeight, int const x, int const y) {
|
852 |
|
853 |
|
854 | int left = 0;
|
855 | int top = 0;
|
856 |
|
857 |
|
858 | if (x < (inWidth - outWidth)) {
|
859 | left = x;
|
860 | } else if (x >= (inWidth - outWidth)) {
|
861 | left = inWidth - outWidth;
|
862 | }
|
863 |
|
864 | if (y < (inHeight - outHeight)) {
|
865 | top = y;
|
866 | } else if (y >= (inHeight - outHeight)) {
|
867 | top = inHeight - outHeight;
|
868 | }
|
869 |
|
870 | return std::make_tuple(left, top);
|
871 | }
|
872 |
|
873 | |
874 |
|
875 |
|
876 | bool Is16Bit(VipsInterpretation const interpretation) {
|
877 | return interpretation == VIPS_INTERPRETATION_RGB16 || interpretation == VIPS_INTERPRETATION_GREY16;
|
878 | }
|
879 |
|
880 | |
881 |
|
882 |
|
883 |
|
884 | double MaximumImageAlpha(VipsInterpretation const interpretation) {
|
885 | return Is16Bit(interpretation) ? 65535.0 : 255.0;
|
886 | }
|
887 |
|
888 | |
889 |
|
890 |
|
891 | std::vector<double> GetRgbaAsColourspace(std::vector<double> const rgba,
|
892 | VipsInterpretation const interpretation, bool premultiply) {
|
893 | int const bands = static_cast<int>(rgba.size());
|
894 | if (bands < 3) {
|
895 | return rgba;
|
896 | }
|
897 | VImage pixel = VImage::new_matrix(1, 1);
|
898 | pixel.set("bands", bands);
|
899 | pixel = pixel
|
900 | .new_from_image(rgba)
|
901 | .colourspace(interpretation, VImage::option()->set("source_space", VIPS_INTERPRETATION_sRGB));
|
902 | if (premultiply) {
|
903 | pixel = pixel.premultiply();
|
904 | }
|
905 | return pixel(0, 0);
|
906 | }
|
907 |
|
908 | |
909 |
|
910 |
|
911 | std::tuple<VImage, std::vector<double>> ApplyAlpha(VImage image, std::vector<double> colour, bool premultiply) {
|
912 |
|
913 | double const multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0;
|
914 |
|
915 | std::vector<double> alphaColour;
|
916 | if (image.bands() > 2) {
|
917 | alphaColour = {
|
918 | multiplier * colour[0],
|
919 | multiplier * colour[1],
|
920 | multiplier * colour[2]
|
921 | };
|
922 | } else {
|
923 |
|
924 | alphaColour = { multiplier * (
|
925 | 0.2126 * colour[0] +
|
926 | 0.7152 * colour[1] +
|
927 | 0.0722 * colour[2])
|
928 | };
|
929 | }
|
930 |
|
931 | if (colour[3] < 255.0 || HasAlpha(image)) {
|
932 | alphaColour.push_back(colour[3] * multiplier);
|
933 | }
|
934 |
|
935 | alphaColour = sharp::GetRgbaAsColourspace(alphaColour, image.interpretation(), premultiply);
|
936 |
|
937 | if (colour[3] < 255.0 && !HasAlpha(image)) {
|
938 | image = image.bandjoin(
|
939 | VImage::new_matrix(image.width(), image.height()).new_from_image(255 * multiplier).cast(image.format()));
|
940 | }
|
941 | return std::make_tuple(image, alphaColour);
|
942 | }
|
943 |
|
944 | |
945 |
|
946 |
|
947 | VImage RemoveAlpha(VImage image) {
|
948 | if (HasAlpha(image)) {
|
949 | image = image.extract_band(0, VImage::option()->set("n", image.bands() - 1));
|
950 | }
|
951 | return image;
|
952 | }
|
953 |
|
954 | |
955 |
|
956 |
|
957 | VImage EnsureAlpha(VImage image, double const value) {
|
958 | if (!HasAlpha(image)) {
|
959 | std::vector<double> alpha;
|
960 | alpha.push_back(value * sharp::MaximumImageAlpha(image.interpretation()));
|
961 | image = image.bandjoin_const(alpha);
|
962 | }
|
963 | return image;
|
964 | }
|
965 |
|
966 | std::pair<double, double> ResolveShrink(int width, int height, int targetWidth, int targetHeight,
|
967 | Canvas canvas, bool swap, bool withoutEnlargement, bool withoutReduction) {
|
968 | if (swap && canvas != Canvas::IGNORE_ASPECT) {
|
969 |
|
970 | std::swap(width, height);
|
971 | }
|
972 |
|
973 | double hshrink = 1.0;
|
974 | double vshrink = 1.0;
|
975 |
|
976 | if (targetWidth > 0 && targetHeight > 0) {
|
977 |
|
978 | hshrink = static_cast<double>(width) / targetWidth;
|
979 | vshrink = static_cast<double>(height) / targetHeight;
|
980 |
|
981 | switch (canvas) {
|
982 | case Canvas::CROP:
|
983 | case Canvas::MIN:
|
984 | if (hshrink < vshrink) {
|
985 | vshrink = hshrink;
|
986 | } else {
|
987 | hshrink = vshrink;
|
988 | }
|
989 | break;
|
990 | case Canvas::EMBED:
|
991 | case Canvas::MAX:
|
992 | if (hshrink > vshrink) {
|
993 | vshrink = hshrink;
|
994 | } else {
|
995 | hshrink = vshrink;
|
996 | }
|
997 | break;
|
998 | case Canvas::IGNORE_ASPECT:
|
999 | break;
|
1000 | }
|
1001 | } else if (targetWidth > 0) {
|
1002 |
|
1003 | hshrink = static_cast<double>(width) / targetWidth;
|
1004 |
|
1005 | if (canvas != Canvas::IGNORE_ASPECT) {
|
1006 |
|
1007 | vshrink = hshrink;
|
1008 | }
|
1009 | } else if (targetHeight > 0) {
|
1010 |
|
1011 | vshrink = static_cast<double>(height) / targetHeight;
|
1012 |
|
1013 | if (canvas != Canvas::IGNORE_ASPECT) {
|
1014 |
|
1015 | hshrink = vshrink;
|
1016 | }
|
1017 | }
|
1018 |
|
1019 |
|
1020 |
|
1021 | if (withoutReduction) {
|
1022 |
|
1023 | hshrink = std::min(1.0, hshrink);
|
1024 | vshrink = std::min(1.0, vshrink);
|
1025 | } else if (withoutEnlargement) {
|
1026 |
|
1027 | hshrink = std::max(1.0, hshrink);
|
1028 | vshrink = std::max(1.0, vshrink);
|
1029 | }
|
1030 |
|
1031 |
|
1032 | hshrink = std::min(hshrink, static_cast<double>(width));
|
1033 | vshrink = std::min(vshrink, static_cast<double>(height));
|
1034 |
|
1035 | return std::make_pair(hshrink, vshrink);
|
1036 | }
|
1037 |
|
1038 | }
|