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 | std::atomic<int> counterQueue{0};
|
170 |
|
171 |
|
172 | std::atomic<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 | 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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
532 |
|
533 | bool HasProfile(VImage image) {
|
534 | return image.get_typeof(VIPS_META_ICC_NAME) == VIPS_TYPE_BLOB;
|
535 | }
|
536 |
|
537 | |
538 |
|
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 |
|
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 |
|
565 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
624 |
|
625 | VImage SetAnimationProperties(VImage image, int nPages, int pageHeight, std::vector<int> delay, int loop) {
|
626 | bool hasDelay = !delay.empty();
|
627 |
|
628 |
|
629 | if (nPages == 1 && !hasDelay && loop == -1) return image;
|
630 |
|
631 | if (delay.size() == 1) {
|
632 |
|
633 | delay.insert(delay.end(), nPages - 1, delay[0]);
|
634 | }
|
635 |
|
636 |
|
637 | VImage copy = image.copy();
|
638 |
|
639 |
|
640 |
|
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 |
|
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 |
|
661 |
|
662 | VImage RemoveGifPalette(VImage image) {
|
663 | VImage copy = image.copy();
|
664 | copy.remove("gif-palette");
|
665 | return copy;
|
666 | }
|
667 |
|
668 | |
669 |
|
670 |
|
671 | bool HasDensity(VImage image) {
|
672 | return image.xres() > 1.0;
|
673 | }
|
674 |
|
675 | |
676 |
|
677 |
|
678 | int GetDensity(VImage image) {
|
679 | return static_cast<int>(round(image.xres() * 25.4));
|
680 | }
|
681 |
|
682 | |
683 |
|
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 |
|
695 |
|
696 |
|
697 | int GetPageHeight(VImage image) {
|
698 | return vips_image_get_page_height(image.get_image());
|
699 | }
|
700 |
|
701 | |
702 |
|
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 |
|
729 |
|
730 | std::function<void(void*, char*)> FreeCallback = [](void*, char* data) {
|
731 | g_free(data);
|
732 | };
|
733 |
|
734 | |
735 |
|
736 |
|
737 | std::queue<std::string> vipsWarnings;
|
738 | std::mutex vipsWarningsMutex;
|
739 |
|
740 | |
741 |
|
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 |
|
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 |
|
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 |
|
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 |
|
789 |
|
790 |
|
791 |
|
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 |
|
801 | left = (outWidth - inWidth) / 2;
|
802 | break;
|
803 | case 2:
|
804 |
|
805 | left = outWidth - inWidth;
|
806 | top = (outHeight - inHeight) / 2;
|
807 | break;
|
808 | case 3:
|
809 |
|
810 | left = (outWidth - inWidth) / 2;
|
811 | top = outHeight - inHeight;
|
812 | break;
|
813 | case 4:
|
814 |
|
815 | top = (outHeight - inHeight) / 2;
|
816 | break;
|
817 | case 5:
|
818 |
|
819 | left = outWidth - inWidth;
|
820 | break;
|
821 | case 6:
|
822 |
|
823 | left = outWidth - inWidth;
|
824 | top = outHeight - inHeight;
|
825 | break;
|
826 | case 7:
|
827 |
|
828 | top = outHeight - inHeight;
|
829 | break;
|
830 | case 8:
|
831 |
|
832 |
|
833 | break;
|
834 | default:
|
835 |
|
836 | left = (outWidth - inWidth) / 2;
|
837 | top = (outHeight - inHeight) / 2;
|
838 | }
|
839 | return std::make_tuple(left, top);
|
840 | }
|
841 |
|
842 | |
843 |
|
844 |
|
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 |
|
854 | left = (inWidth - outWidth + 1) / 2;
|
855 | break;
|
856 | case 2:
|
857 |
|
858 | left = inWidth - outWidth;
|
859 | top = (inHeight - outHeight + 1) / 2;
|
860 | break;
|
861 | case 3:
|
862 |
|
863 | left = (inWidth - outWidth + 1) / 2;
|
864 | top = inHeight - outHeight;
|
865 | break;
|
866 | case 4:
|
867 |
|
868 | top = (inHeight - outHeight + 1) / 2;
|
869 | break;
|
870 | case 5:
|
871 |
|
872 | left = inWidth - outWidth;
|
873 | break;
|
874 | case 6:
|
875 |
|
876 | left = inWidth - outWidth;
|
877 | top = inHeight - outHeight;
|
878 | break;
|
879 | case 7:
|
880 |
|
881 | top = inHeight - outHeight;
|
882 | break;
|
883 | case 8:
|
884 |
|
885 | break;
|
886 | default:
|
887 |
|
888 | left = (inWidth - outWidth + 1) / 2;
|
889 | top = (inHeight - outHeight + 1) / 2;
|
890 | }
|
891 | return std::make_tuple(left, top);
|
892 | }
|
893 |
|
894 | |
895 |
|
896 |
|
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 |
|
902 | int left = 0;
|
903 | int top = 0;
|
904 |
|
905 |
|
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 |
|
923 |
|
924 | bool Is16Bit(VipsInterpretation const interpretation) {
|
925 | return interpretation == VIPS_INTERPRETATION_RGB16 || interpretation == VIPS_INTERPRETATION_GREY16;
|
926 | }
|
927 |
|
928 | |
929 |
|
930 |
|
931 |
|
932 | double MaximumImageAlpha(VipsInterpretation const interpretation) {
|
933 | return Is16Bit(interpretation) ? 65535.0 : 255.0;
|
934 | }
|
935 |
|
936 | |
937 |
|
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 |
|
958 |
|
959 | std::tuple<VImage, std::vector<double>> ApplyAlpha(VImage image, std::vector<double> colour, bool premultiply) {
|
960 |
|
961 | double const multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0;
|
962 |
|
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 |
|
972 | alphaColour = { multiplier * (
|
973 | 0.2126 * colour[0] +
|
974 | 0.7152 * colour[1] +
|
975 | 0.0722 * colour[2])
|
976 | };
|
977 | }
|
978 |
|
979 | if (colour[3] < 255.0 || HasAlpha(image)) {
|
980 | alphaColour.push_back(colour[3] * multiplier);
|
981 | }
|
982 |
|
983 | alphaColour = sharp::GetRgbaAsColourspace(alphaColour, image.interpretation(), premultiply);
|
984 |
|
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 |
|
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 |
|
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 |
|
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 |
|
1046 | hshrink = static_cast<double>(width) / targetWidth;
|
1047 |
|
1048 | if (canvas != Canvas::IGNORE_ASPECT) {
|
1049 |
|
1050 | vshrink = hshrink;
|
1051 | }
|
1052 | } else if (targetHeight > 0) {
|
1053 |
|
1054 | vshrink = static_cast<double>(height) / targetHeight;
|
1055 |
|
1056 | if (canvas != Canvas::IGNORE_ASPECT) {
|
1057 |
|
1058 | hshrink = vshrink;
|
1059 | }
|
1060 | }
|
1061 |
|
1062 |
|
1063 |
|
1064 | if (withoutReduction) {
|
1065 |
|
1066 | hshrink = std::min(1.0, hshrink);
|
1067 | vshrink = std::min(1.0, vshrink);
|
1068 | } else if (withoutEnlargement) {
|
1069 |
|
1070 | hshrink = std::max(1.0, hshrink);
|
1071 | vshrink = std::max(1.0, vshrink);
|
1072 | }
|
1073 |
|
1074 |
|
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 |
|
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 | }
|