sharp 0.27.0 → 0.28.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -14
- package/binding.gyp +3 -5
- package/install/dll-copy.js +4 -4
- package/install/libvips.js +67 -31
- package/lib/channel.js +25 -7
- package/lib/colour.js +7 -0
- package/lib/constructor.js +48 -13
- package/lib/input.js +53 -13
- package/lib/is.js +10 -0
- package/lib/libvips.js +25 -3
- package/lib/operation.js +9 -4
- package/lib/output.js +142 -47
- package/lib/resize.js +42 -15
- package/lib/utility.js +17 -10
- package/package.json +13 -13
- package/src/common.cc +86 -43
- package/src/common.h +12 -4
- package/src/metadata.cc +12 -0
- package/src/metadata.h +3 -0
- package/src/operations.cc +1 -1
- package/src/pipeline.cc +54 -14
- package/src/pipeline.h +8 -3
- package/src/sharp.cc +1 -1
- package/src/utilities.cc +16 -0
- package/src/utilities.h +1 -0
package/src/common.cc
CHANGED
|
@@ -36,6 +36,9 @@ namespace sharp {
|
|
|
36
36
|
std::string AttrAsStr(Napi::Object obj, std::string attr) {
|
|
37
37
|
return obj.Get(attr).As<Napi::String>();
|
|
38
38
|
}
|
|
39
|
+
std::string AttrAsStr(Napi::Object obj, unsigned int const attr) {
|
|
40
|
+
return obj.Get(attr).As<Napi::String>();
|
|
41
|
+
}
|
|
39
42
|
uint32_t AttrAsUint32(Napi::Object obj, std::string attr) {
|
|
40
43
|
return obj.Get(attr).As<Napi::Number>().Uint32Value();
|
|
41
44
|
}
|
|
@@ -104,12 +107,22 @@ namespace sharp {
|
|
|
104
107
|
if (HasAttr(input, "level")) {
|
|
105
108
|
descriptor->level = AttrAsUint32(input, "level");
|
|
106
109
|
}
|
|
110
|
+
// subIFD (OME-TIFF)
|
|
111
|
+
if (HasAttr(input, "subifd")) {
|
|
112
|
+
descriptor->subifd = AttrAsInt32(input, "subifd");
|
|
113
|
+
}
|
|
107
114
|
// Create new image
|
|
108
115
|
if (HasAttr(input, "createChannels")) {
|
|
109
116
|
descriptor->createChannels = AttrAsUint32(input, "createChannels");
|
|
110
117
|
descriptor->createWidth = AttrAsUint32(input, "createWidth");
|
|
111
118
|
descriptor->createHeight = AttrAsUint32(input, "createHeight");
|
|
112
|
-
|
|
119
|
+
if (HasAttr(input, "createNoiseType")) {
|
|
120
|
+
descriptor->createNoiseType = AttrAsStr(input, "createNoiseType");
|
|
121
|
+
descriptor->createNoiseMean = AttrAsDouble(input, "createNoiseMean");
|
|
122
|
+
descriptor->createNoiseSigma = AttrAsDouble(input, "createNoiseSigma");
|
|
123
|
+
} else {
|
|
124
|
+
descriptor->createBackground = AttrAsVectorOfDouble(input, "createBackground");
|
|
125
|
+
}
|
|
113
126
|
}
|
|
114
127
|
// Limit input images to a given number of pixels, where pixels = width * height
|
|
115
128
|
descriptor->limitInputPixels = AttrAsUint32(input, "limitInputPixels");
|
|
@@ -189,31 +202,40 @@ namespace sharp {
|
|
|
189
202
|
return id;
|
|
190
203
|
}
|
|
191
204
|
|
|
205
|
+
/**
|
|
206
|
+
* Regenerate this table with something like:
|
|
207
|
+
*
|
|
208
|
+
* $ vips -l foreign | grep -i load | awk '{ print $2, $1; }'
|
|
209
|
+
*
|
|
210
|
+
* Plus a bit of editing.
|
|
211
|
+
*/
|
|
192
212
|
std::map<std::string, ImageType> loaderToType = {
|
|
193
|
-
{ "
|
|
194
|
-
{ "
|
|
195
|
-
{ "
|
|
196
|
-
{ "
|
|
197
|
-
{ "
|
|
198
|
-
{ "
|
|
199
|
-
{ "
|
|
200
|
-
{ "
|
|
201
|
-
{ "
|
|
202
|
-
{ "
|
|
203
|
-
{ "
|
|
204
|
-
{ "
|
|
205
|
-
{ "
|
|
206
|
-
{ "
|
|
207
|
-
{ "
|
|
208
|
-
{ "
|
|
209
|
-
{ "
|
|
210
|
-
{ "
|
|
211
|
-
{ "
|
|
212
|
-
{ "
|
|
213
|
-
{ "
|
|
214
|
-
{ "
|
|
215
|
-
{ "
|
|
216
|
-
{ "
|
|
213
|
+
{ "VipsForeignLoadJpegFile", ImageType::JPEG },
|
|
214
|
+
{ "VipsForeignLoadJpegBuffer", ImageType::JPEG },
|
|
215
|
+
{ "VipsForeignLoadPngFile", ImageType::PNG },
|
|
216
|
+
{ "VipsForeignLoadPngBuffer", ImageType::PNG },
|
|
217
|
+
{ "VipsForeignLoadWebpFile", ImageType::WEBP },
|
|
218
|
+
{ "VipsForeignLoadWebpBuffer", ImageType::WEBP },
|
|
219
|
+
{ "VipsForeignLoadTiffFile", ImageType::TIFF },
|
|
220
|
+
{ "VipsForeignLoadTiffBuffer", ImageType::TIFF },
|
|
221
|
+
{ "VipsForeignLoadGifFile", ImageType::GIF },
|
|
222
|
+
{ "VipsForeignLoadGifBuffer", ImageType::GIF },
|
|
223
|
+
{ "VipsForeignLoadNsgifFile", ImageType::GIF },
|
|
224
|
+
{ "VipsForeignLoadNsgifBuffer", ImageType::GIF },
|
|
225
|
+
{ "VipsForeignLoadSvgFile", ImageType::SVG },
|
|
226
|
+
{ "VipsForeignLoadSvgBuffer", ImageType::SVG },
|
|
227
|
+
{ "VipsForeignLoadHeifFile", ImageType::HEIF },
|
|
228
|
+
{ "VipsForeignLoadHeifBuffer", ImageType::HEIF },
|
|
229
|
+
{ "VipsForeignLoadPdfFile", ImageType::PDF },
|
|
230
|
+
{ "VipsForeignLoadPdfBuffer", ImageType::PDF },
|
|
231
|
+
{ "VipsForeignLoadMagickFile", ImageType::MAGICK },
|
|
232
|
+
{ "VipsForeignLoadMagickBuffer", ImageType::MAGICK },
|
|
233
|
+
{ "VipsForeignLoadOpenslide", ImageType::OPENSLIDE },
|
|
234
|
+
{ "VipsForeignLoadPpmFile", ImageType::PPM },
|
|
235
|
+
{ "VipsForeignLoadFits", ImageType::FITS },
|
|
236
|
+
{ "VipsForeignLoadOpenexr", ImageType::EXR },
|
|
237
|
+
{ "VipsForeignLoadVips", ImageType::VIPS },
|
|
238
|
+
{ "VipsForeignLoadRaw", ImageType::RAW }
|
|
217
239
|
};
|
|
218
240
|
|
|
219
241
|
/*
|
|
@@ -223,7 +245,7 @@ namespace sharp {
|
|
|
223
245
|
ImageType imageType = ImageType::UNKNOWN;
|
|
224
246
|
char const *load = vips_foreign_find_load_buffer(buffer, length);
|
|
225
247
|
if (load != nullptr) {
|
|
226
|
-
auto it = loaderToType.find(
|
|
248
|
+
auto it = loaderToType.find(load);
|
|
227
249
|
if (it != loaderToType.end()) {
|
|
228
250
|
imageType = it->second;
|
|
229
251
|
}
|
|
@@ -238,7 +260,7 @@ namespace sharp {
|
|
|
238
260
|
ImageType imageType = ImageType::UNKNOWN;
|
|
239
261
|
char const *load = vips_foreign_find_load(file);
|
|
240
262
|
if (load != nullptr) {
|
|
241
|
-
auto it = loaderToType.find(
|
|
263
|
+
auto it = loaderToType.find(load);
|
|
242
264
|
if (it != loaderToType.end()) {
|
|
243
265
|
imageType = it->second;
|
|
244
266
|
}
|
|
@@ -304,6 +326,9 @@ namespace sharp {
|
|
|
304
326
|
if (imageType == ImageType::OPENSLIDE) {
|
|
305
327
|
option->set("level", descriptor->level);
|
|
306
328
|
}
|
|
329
|
+
if (imageType == ImageType::TIFF) {
|
|
330
|
+
option->set("subifd", descriptor->subifd);
|
|
331
|
+
}
|
|
307
332
|
image = VImage::new_from_buffer(descriptor->buffer, descriptor->bufferLength, nullptr, option);
|
|
308
333
|
if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
|
|
309
334
|
image = SetDensity(image, descriptor->density);
|
|
@@ -318,15 +343,35 @@ namespace sharp {
|
|
|
318
343
|
} else {
|
|
319
344
|
if (descriptor->createChannels > 0) {
|
|
320
345
|
// Create new image
|
|
321
|
-
|
|
322
|
-
descriptor->
|
|
323
|
-
descriptor->
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
346
|
+
if (descriptor->createNoiseType == "gaussian") {
|
|
347
|
+
int const channels = descriptor->createChannels;
|
|
348
|
+
image = VImage::new_matrix(descriptor->createWidth, descriptor->createHeight);
|
|
349
|
+
std::vector<VImage> bands = {};
|
|
350
|
+
bands.reserve(channels);
|
|
351
|
+
for (int _band = 0; _band < channels; _band++) {
|
|
352
|
+
bands.push_back(image.gaussnoise(
|
|
353
|
+
descriptor->createWidth,
|
|
354
|
+
descriptor->createHeight,
|
|
355
|
+
VImage::option()->set("mean", descriptor->createNoiseMean)->set("sigma", descriptor->createNoiseSigma)));
|
|
356
|
+
}
|
|
357
|
+
image = image.bandjoin(bands);
|
|
358
|
+
image = image.cast(VipsBandFormat::VIPS_FORMAT_UCHAR);
|
|
359
|
+
if (channels < 3) {
|
|
360
|
+
image = image.colourspace(VIPS_INTERPRETATION_B_W);
|
|
361
|
+
} else {
|
|
362
|
+
image = image.colourspace(VIPS_INTERPRETATION_sRGB);
|
|
363
|
+
}
|
|
364
|
+
} else {
|
|
365
|
+
std::vector<double> background = {
|
|
366
|
+
descriptor->createBackground[0],
|
|
367
|
+
descriptor->createBackground[1],
|
|
368
|
+
descriptor->createBackground[2]
|
|
369
|
+
};
|
|
370
|
+
if (descriptor->createChannels == 4) {
|
|
371
|
+
background.push_back(descriptor->createBackground[3]);
|
|
372
|
+
}
|
|
373
|
+
image = VImage::new_matrix(descriptor->createWidth, descriptor->createHeight).new_from_image(background);
|
|
328
374
|
}
|
|
329
|
-
image = VImage::new_matrix(descriptor->createWidth, descriptor->createHeight).new_from_image(background);
|
|
330
375
|
image.get_image()->Type = VIPS_INTERPRETATION_sRGB;
|
|
331
376
|
imageType = ImageType::RAW;
|
|
332
377
|
} else {
|
|
@@ -356,6 +401,9 @@ namespace sharp {
|
|
|
356
401
|
if (imageType == ImageType::OPENSLIDE) {
|
|
357
402
|
option->set("level", descriptor->level);
|
|
358
403
|
}
|
|
404
|
+
if (imageType == ImageType::TIFF) {
|
|
405
|
+
option->set("subifd", descriptor->subifd);
|
|
406
|
+
}
|
|
359
407
|
image = VImage::new_from_file(descriptor->file.data(), option);
|
|
360
408
|
if (imageType == ImageType::SVG || imageType == ImageType::PDF || imageType == ImageType::MAGICK) {
|
|
361
409
|
image = SetDensity(image, descriptor->density);
|
|
@@ -388,12 +436,7 @@ namespace sharp {
|
|
|
388
436
|
Uses colour space interpretation with number of channels to guess this.
|
|
389
437
|
*/
|
|
390
438
|
bool HasAlpha(VImage image) {
|
|
391
|
-
|
|
392
|
-
VipsInterpretation const interpretation = image.interpretation();
|
|
393
|
-
return (
|
|
394
|
-
(bands == 2 && interpretation == VIPS_INTERPRETATION_B_W) ||
|
|
395
|
-
(bands == 4 && interpretation != VIPS_INTERPRETATION_CMYK) ||
|
|
396
|
-
(bands == 5 && interpretation == VIPS_INTERPRETATION_CMYK));
|
|
439
|
+
return image.has_alpha();
|
|
397
440
|
}
|
|
398
441
|
|
|
399
442
|
/*
|
|
@@ -769,10 +812,10 @@ namespace sharp {
|
|
|
769
812
|
/*
|
|
770
813
|
Ensures alpha channel, if missing.
|
|
771
814
|
*/
|
|
772
|
-
VImage EnsureAlpha(VImage image) {
|
|
815
|
+
VImage EnsureAlpha(VImage image, double const value) {
|
|
773
816
|
if (!HasAlpha(image)) {
|
|
774
817
|
std::vector<double> alpha;
|
|
775
|
-
alpha.push_back(sharp::MaximumImageAlpha(image.interpretation()));
|
|
818
|
+
alpha.push_back(value * sharp::MaximumImageAlpha(image.interpretation()));
|
|
776
819
|
image = image.bandjoin_const(alpha);
|
|
777
820
|
}
|
|
778
821
|
return image;
|
package/src/common.h
CHANGED
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
|
|
27
27
|
#if (VIPS_MAJOR_VERSION < 8) || \
|
|
28
28
|
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 10) || \
|
|
29
|
-
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 10 && VIPS_MICRO_VERSION <
|
|
30
|
-
#error "libvips version 8.10.
|
|
29
|
+
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 10 && VIPS_MICRO_VERSION < 6)
|
|
30
|
+
#error "libvips version 8.10.6+ is required - please see https://sharp.pixelplumbing.com/install"
|
|
31
31
|
#endif
|
|
32
32
|
|
|
33
33
|
#if ((!defined(__clang__)) && defined(__GNUC__) && (__GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6)))
|
|
@@ -60,10 +60,14 @@ namespace sharp {
|
|
|
60
60
|
int pages;
|
|
61
61
|
int page;
|
|
62
62
|
int level;
|
|
63
|
+
int subifd;
|
|
63
64
|
int createChannels;
|
|
64
65
|
int createWidth;
|
|
65
66
|
int createHeight;
|
|
66
67
|
std::vector<double> createBackground;
|
|
68
|
+
std::string createNoiseType;
|
|
69
|
+
double createNoiseMean;
|
|
70
|
+
double createNoiseSigma;
|
|
67
71
|
|
|
68
72
|
InputDescriptor():
|
|
69
73
|
buffer(nullptr),
|
|
@@ -79,15 +83,19 @@ namespace sharp {
|
|
|
79
83
|
pages(1),
|
|
80
84
|
page(0),
|
|
81
85
|
level(0),
|
|
86
|
+
subifd(-1),
|
|
82
87
|
createChannels(0),
|
|
83
88
|
createWidth(0),
|
|
84
89
|
createHeight(0),
|
|
85
|
-
createBackground{ 0.0, 0.0, 0.0, 255.0 }
|
|
90
|
+
createBackground{ 0.0, 0.0, 0.0, 255.0 },
|
|
91
|
+
createNoiseMean(0.0),
|
|
92
|
+
createNoiseSigma(0.0) {}
|
|
86
93
|
};
|
|
87
94
|
|
|
88
95
|
// Convenience methods to access the attributes of a Napi::Object
|
|
89
96
|
bool HasAttr(Napi::Object obj, std::string attr);
|
|
90
97
|
std::string AttrAsStr(Napi::Object obj, std::string attr);
|
|
98
|
+
std::string AttrAsStr(Napi::Object obj, unsigned int const attr);
|
|
91
99
|
uint32_t AttrAsUint32(Napi::Object obj, std::string attr);
|
|
92
100
|
int32_t AttrAsInt32(Napi::Object obj, std::string attr);
|
|
93
101
|
int32_t AttrAsInt32(Napi::Object obj, unsigned int const attr);
|
|
@@ -291,7 +299,7 @@ namespace sharp {
|
|
|
291
299
|
/*
|
|
292
300
|
Ensures alpha channel, if missing.
|
|
293
301
|
*/
|
|
294
|
-
VImage EnsureAlpha(VImage image);
|
|
302
|
+
VImage EnsureAlpha(VImage image, double const value);
|
|
295
303
|
|
|
296
304
|
} // namespace sharp
|
|
297
305
|
|
package/src/metadata.cc
CHANGED
|
@@ -74,6 +74,9 @@ class MetadataWorker : public Napi::AsyncWorker {
|
|
|
74
74
|
if (image.get_typeof("heif-primary") == G_TYPE_INT) {
|
|
75
75
|
baton->pagePrimary = image.get_int("heif-primary");
|
|
76
76
|
}
|
|
77
|
+
if (image.get_typeof("heif-compression") == VIPS_TYPE_REF_STRING) {
|
|
78
|
+
baton->compression = image.get_string("heif-compression");
|
|
79
|
+
}
|
|
77
80
|
if (image.get_typeof("openslide.level-count") == VIPS_TYPE_REF_STRING) {
|
|
78
81
|
int const levels = std::stoi(image.get_string("openslide.level-count"));
|
|
79
82
|
for (int l = 0; l < levels; l++) {
|
|
@@ -83,6 +86,9 @@ class MetadataWorker : public Napi::AsyncWorker {
|
|
|
83
86
|
baton->levels.push_back(std::pair<int, int>(width, height));
|
|
84
87
|
}
|
|
85
88
|
}
|
|
89
|
+
if (image.get_typeof(VIPS_META_N_SUBIFDS) == G_TYPE_INT) {
|
|
90
|
+
baton->subifds = image.get_int(VIPS_META_N_SUBIFDS);
|
|
91
|
+
}
|
|
86
92
|
baton->hasProfile = sharp::HasProfile(image);
|
|
87
93
|
// Derived attributes
|
|
88
94
|
baton->hasAlpha = sharp::HasAlpha(image);
|
|
@@ -186,6 +192,9 @@ class MetadataWorker : public Napi::AsyncWorker {
|
|
|
186
192
|
if (baton->pagePrimary > -1) {
|
|
187
193
|
info.Set("pagePrimary", baton->pagePrimary);
|
|
188
194
|
}
|
|
195
|
+
if (!baton->compression.empty()) {
|
|
196
|
+
info.Set("compression", baton->compression);
|
|
197
|
+
}
|
|
189
198
|
if (!baton->levels.empty()) {
|
|
190
199
|
int i = 0;
|
|
191
200
|
Napi::Array levels = Napi::Array::New(env, static_cast<size_t>(baton->levels.size()));
|
|
@@ -197,6 +206,9 @@ class MetadataWorker : public Napi::AsyncWorker {
|
|
|
197
206
|
}
|
|
198
207
|
info.Set("levels", levels);
|
|
199
208
|
}
|
|
209
|
+
if (baton->subifds > 0) {
|
|
210
|
+
info.Set("subifds", baton->subifds);
|
|
211
|
+
}
|
|
200
212
|
info.Set("hasProfile", baton->hasProfile);
|
|
201
213
|
info.Set("hasAlpha", baton->hasAlpha);
|
|
202
214
|
if (baton->orientation > 0) {
|
package/src/metadata.h
CHANGED
|
@@ -39,7 +39,9 @@ struct MetadataBaton {
|
|
|
39
39
|
int loop;
|
|
40
40
|
std::vector<int> delay;
|
|
41
41
|
int pagePrimary;
|
|
42
|
+
std::string compression;
|
|
42
43
|
std::vector<std::pair<int, int>> levels;
|
|
44
|
+
int subifds;
|
|
43
45
|
bool hasProfile;
|
|
44
46
|
bool hasAlpha;
|
|
45
47
|
int orientation;
|
|
@@ -67,6 +69,7 @@ struct MetadataBaton {
|
|
|
67
69
|
pageHeight(0),
|
|
68
70
|
loop(-1),
|
|
69
71
|
pagePrimary(-1),
|
|
72
|
+
subifds(0),
|
|
70
73
|
hasProfile(false),
|
|
71
74
|
hasAlpha(false),
|
|
72
75
|
orientation(0),
|
package/src/operations.cc
CHANGED
|
@@ -149,8 +149,8 @@ namespace sharp {
|
|
|
149
149
|
*/
|
|
150
150
|
VImage Recomb(VImage image, std::unique_ptr<double[]> const &matrix) {
|
|
151
151
|
double *m = matrix.get();
|
|
152
|
+
image = image.colourspace(VIPS_INTERPRETATION_sRGB);
|
|
152
153
|
return image
|
|
153
|
-
.colourspace(VIPS_INTERPRETATION_sRGB)
|
|
154
154
|
.recomb(image.bands() == 3
|
|
155
155
|
? VImage::new_from_memory(
|
|
156
156
|
m, 9 * sizeof(double), 3, 3, 1, VIPS_FORMAT_DOUBLE
|
package/src/pipeline.cc
CHANGED
|
@@ -346,7 +346,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
346
346
|
bool const shouldModulate = baton->brightness != 1.0 || baton->saturation != 1.0 || baton->hue != 0.0;
|
|
347
347
|
|
|
348
348
|
if (shouldComposite && !sharp::HasAlpha(image)) {
|
|
349
|
-
image = sharp::EnsureAlpha(image);
|
|
349
|
+
image = sharp::EnsureAlpha(image, 1);
|
|
350
350
|
}
|
|
351
351
|
|
|
352
352
|
bool const shouldPremultiplyAlpha = sharp::HasAlpha(image) &&
|
|
@@ -562,9 +562,17 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
562
562
|
// Use gravity in overlay
|
|
563
563
|
if (compositeImage.width() <= baton->width) {
|
|
564
564
|
across = static_cast<int>(ceil(static_cast<double>(image.width()) / compositeImage.width()));
|
|
565
|
+
// Ensure odd number of tiles across when gravity is centre, north or south
|
|
566
|
+
if (composite->gravity == 0 || composite->gravity == 1 || composite->gravity == 3) {
|
|
567
|
+
across |= 1;
|
|
568
|
+
}
|
|
565
569
|
}
|
|
566
570
|
if (compositeImage.height() <= baton->height) {
|
|
567
571
|
down = static_cast<int>(ceil(static_cast<double>(image.height()) / compositeImage.height()));
|
|
572
|
+
// Ensure odd number of tiles down when gravity is centre, east or west
|
|
573
|
+
if (composite->gravity == 0 || composite->gravity == 2 || composite->gravity == 4) {
|
|
574
|
+
down |= 1;
|
|
575
|
+
}
|
|
568
576
|
}
|
|
569
577
|
if (across != 0 || down != 0) {
|
|
570
578
|
int left;
|
|
@@ -586,7 +594,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
586
594
|
// Ensure image to composite is sRGB with premultiplied alpha
|
|
587
595
|
compositeImage = compositeImage.colourspace(VIPS_INTERPRETATION_sRGB);
|
|
588
596
|
if (!sharp::HasAlpha(compositeImage)) {
|
|
589
|
-
compositeImage = sharp::EnsureAlpha(compositeImage);
|
|
597
|
+
compositeImage = sharp::EnsureAlpha(compositeImage, 1);
|
|
590
598
|
}
|
|
591
599
|
if (!composite->premultiplied) compositeImage = compositeImage.premultiply();
|
|
592
600
|
// Calculate position
|
|
@@ -594,8 +602,13 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
594
602
|
int top;
|
|
595
603
|
if (composite->hasOffset) {
|
|
596
604
|
// Composite image at given offsets
|
|
597
|
-
|
|
598
|
-
|
|
605
|
+
if (composite->tile) {
|
|
606
|
+
std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),
|
|
607
|
+
compositeImage.width(), compositeImage.height(), composite->left, composite->top);
|
|
608
|
+
} else {
|
|
609
|
+
left = composite->left;
|
|
610
|
+
top = composite->top;
|
|
611
|
+
}
|
|
599
612
|
} else {
|
|
600
613
|
// Composite image with given gravity
|
|
601
614
|
std::tie(left, top) = sharp::CalculateCrop(image.width(), image.height(),
|
|
@@ -678,8 +691,8 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
678
691
|
}
|
|
679
692
|
|
|
680
693
|
// Ensure alpha channel, if missing
|
|
681
|
-
if (baton->ensureAlpha) {
|
|
682
|
-
image = sharp::EnsureAlpha(image);
|
|
694
|
+
if (baton->ensureAlpha != -1) {
|
|
695
|
+
image = sharp::EnsureAlpha(image, baton->ensureAlpha);
|
|
683
696
|
}
|
|
684
697
|
|
|
685
698
|
// Convert image to sRGB, if not already
|
|
@@ -704,11 +717,17 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
704
717
|
->set("input_profile", "srgb")
|
|
705
718
|
->set("intent", VIPS_INTENT_PERCEPTUAL));
|
|
706
719
|
}
|
|
707
|
-
|
|
708
720
|
// Override EXIF Orientation tag
|
|
709
721
|
if (baton->withMetadata && baton->withMetadataOrientation != -1) {
|
|
710
722
|
image = sharp::SetExifOrientation(image, baton->withMetadataOrientation);
|
|
711
723
|
}
|
|
724
|
+
// Metadata key/value pairs, e.g. EXIF
|
|
725
|
+
if (!baton->withMetadataStrs.empty()) {
|
|
726
|
+
image = image.copy();
|
|
727
|
+
for (const auto& s : baton->withMetadataStrs) {
|
|
728
|
+
image.set(s.first.data(), s.second.data());
|
|
729
|
+
}
|
|
730
|
+
}
|
|
712
731
|
|
|
713
732
|
// Number of channels used in output image
|
|
714
733
|
baton->channels = image.bands();
|
|
@@ -730,7 +749,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
730
749
|
if (baton->formatOut == "jpeg" || (baton->formatOut == "input" && inputImageType == sharp::ImageType::JPEG)) {
|
|
731
750
|
// Write JPEG to buffer
|
|
732
751
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
|
|
733
|
-
VipsArea *area =
|
|
752
|
+
VipsArea *area = reinterpret_cast<VipsArea*>(image.jpegsave_buffer(VImage::option()
|
|
734
753
|
->set("strip", !baton->withMetadata)
|
|
735
754
|
->set("Q", baton->jpegQuality)
|
|
736
755
|
->set("interlace", baton->jpegProgressive)
|
|
@@ -757,7 +776,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
757
776
|
inputImageType == sharp::ImageType::SVG))) {
|
|
758
777
|
// Write PNG to buffer
|
|
759
778
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::PNG);
|
|
760
|
-
VipsArea *area =
|
|
779
|
+
VipsArea *area = reinterpret_cast<VipsArea*>(image.pngsave_buffer(VImage::option()
|
|
761
780
|
->set("strip", !baton->withMetadata)
|
|
762
781
|
->set("interlace", baton->pngProgressive)
|
|
763
782
|
->set("compression", baton->pngCompressionLevel)
|
|
@@ -775,7 +794,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
775
794
|
(baton->formatOut == "input" && inputImageType == sharp::ImageType::WEBP)) {
|
|
776
795
|
// Write WEBP to buffer
|
|
777
796
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::WEBP);
|
|
778
|
-
VipsArea *area =
|
|
797
|
+
VipsArea *area = reinterpret_cast<VipsArea*>(image.webpsave_buffer(VImage::option()
|
|
779
798
|
->set("strip", !baton->withMetadata)
|
|
780
799
|
->set("Q", baton->webpQuality)
|
|
781
800
|
->set("lossless", baton->webpLossless)
|
|
@@ -792,7 +811,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
792
811
|
(baton->formatOut == "input" && inputImageType == sharp::ImageType::GIF && supportsGifOutput)) {
|
|
793
812
|
// Write GIF to buffer
|
|
794
813
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::GIF);
|
|
795
|
-
VipsArea *area =
|
|
814
|
+
VipsArea *area = reinterpret_cast<VipsArea*>(image.magicksave_buffer(VImage::option()
|
|
796
815
|
->set("strip", !baton->withMetadata)
|
|
797
816
|
->set("optimize_gif_frames", TRUE)
|
|
798
817
|
->set("optimize_gif_transparency", TRUE)
|
|
@@ -813,7 +832,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
813
832
|
if (baton->tiffPredictor == VIPS_FOREIGN_TIFF_PREDICTOR_FLOAT) {
|
|
814
833
|
image = image.cast(VIPS_FORMAT_FLOAT);
|
|
815
834
|
}
|
|
816
|
-
VipsArea *area =
|
|
835
|
+
VipsArea *area = reinterpret_cast<VipsArea*>(image.tiffsave_buffer(VImage::option()
|
|
817
836
|
->set("strip", !baton->withMetadata)
|
|
818
837
|
->set("Q", baton->tiffQuality)
|
|
819
838
|
->set("bitdepth", baton->tiffBitdepth)
|
|
@@ -833,11 +852,15 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
833
852
|
} else if (baton->formatOut == "heif" ||
|
|
834
853
|
(baton->formatOut == "input" && inputImageType == sharp::ImageType::HEIF)) {
|
|
835
854
|
// Write HEIF to buffer
|
|
836
|
-
VipsArea *area =
|
|
855
|
+
VipsArea *area = reinterpret_cast<VipsArea*>(image.heifsave_buffer(VImage::option()
|
|
837
856
|
->set("strip", !baton->withMetadata)
|
|
838
857
|
->set("compression", baton->heifCompression)
|
|
839
858
|
->set("Q", baton->heifQuality)
|
|
840
859
|
->set("speed", baton->heifSpeed)
|
|
860
|
+
#if defined(VIPS_TYPE_FOREIGN_SUBSAMPLE)
|
|
861
|
+
->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
|
|
862
|
+
? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
|
|
863
|
+
#endif
|
|
841
864
|
->set("lossless", baton->heifLossless)));
|
|
842
865
|
baton->bufferOut = static_cast<char*>(area->data);
|
|
843
866
|
baton->bufferOutLength = area->length;
|
|
@@ -951,6 +974,10 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
951
974
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
|
|
952
975
|
baton->channels = std::min(baton->channels, 3);
|
|
953
976
|
}
|
|
977
|
+
// Cast pixel values to float, if required
|
|
978
|
+
if (baton->tiffPredictor == VIPS_FOREIGN_TIFF_PREDICTOR_FLOAT) {
|
|
979
|
+
image = image.cast(VIPS_FORMAT_FLOAT);
|
|
980
|
+
}
|
|
954
981
|
image.tiffsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
|
955
982
|
->set("strip", !baton->withMetadata)
|
|
956
983
|
->set("Q", baton->tiffQuality)
|
|
@@ -972,6 +999,10 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
972
999
|
->set("Q", baton->heifQuality)
|
|
973
1000
|
->set("compression", baton->heifCompression)
|
|
974
1001
|
->set("speed", baton->heifSpeed)
|
|
1002
|
+
#if defined(VIPS_TYPE_FOREIGN_SUBSAMPLE)
|
|
1003
|
+
->set("subsample_mode", baton->heifChromaSubsampling == "4:4:4"
|
|
1004
|
+
? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_ON)
|
|
1005
|
+
#endif
|
|
975
1006
|
->set("lossless", baton->heifLossless));
|
|
976
1007
|
baton->formatOut = "heif";
|
|
977
1008
|
} else if (baton->formatOut == "dz" || isDz || isDzZip) {
|
|
@@ -1026,6 +1057,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1026
1057
|
->set("angle", CalculateAngleRotation(baton->tileAngle))
|
|
1027
1058
|
->set("background", baton->tileBackground)
|
|
1028
1059
|
->set("centre", baton->tileCentre)
|
|
1060
|
+
->set("id", const_cast<char*>(baton->tileId.data()))
|
|
1029
1061
|
->set("skip_blanks", baton->tileSkipBlanks);
|
|
1030
1062
|
// libvips chooses a default depth based on layout. Instead of replicating that logic here by
|
|
1031
1063
|
// not passing anything - libvips will handle choice
|
|
@@ -1315,7 +1347,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1315
1347
|
baton->affineInterpolator = vips::VInterpolate::new_from_name(sharp::AttrAsStr(options, "affineInterpolator").data());
|
|
1316
1348
|
|
|
1317
1349
|
baton->removeAlpha = sharp::AttrAsBool(options, "removeAlpha");
|
|
1318
|
-
baton->ensureAlpha = sharp::
|
|
1350
|
+
baton->ensureAlpha = sharp::AttrAsDouble(options, "ensureAlpha");
|
|
1319
1351
|
if (options.Has("boolean")) {
|
|
1320
1352
|
baton->boolean = sharp::CreateInputDescriptor(options.Get("boolean").As<Napi::Object>());
|
|
1321
1353
|
baton->booleanOp = sharp::GetBooleanOperation(sharp::AttrAsStr(options, "booleanOp"));
|
|
@@ -1353,6 +1385,12 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1353
1385
|
baton->withMetadata = sharp::AttrAsBool(options, "withMetadata");
|
|
1354
1386
|
baton->withMetadataOrientation = sharp::AttrAsUint32(options, "withMetadataOrientation");
|
|
1355
1387
|
baton->withMetadataIcc = sharp::AttrAsStr(options, "withMetadataIcc");
|
|
1388
|
+
Napi::Object mdStrs = options.Get("withMetadataStrs").As<Napi::Object>();
|
|
1389
|
+
Napi::Array mdStrKeys = mdStrs.GetPropertyNames();
|
|
1390
|
+
for (unsigned int i = 0; i < mdStrKeys.Length(); i++) {
|
|
1391
|
+
std::string k = sharp::AttrAsStr(mdStrKeys, i);
|
|
1392
|
+
baton->withMetadataStrs.insert(std::make_pair(k, sharp::AttrAsStr(mdStrs, k)));
|
|
1393
|
+
}
|
|
1356
1394
|
// Format-specific
|
|
1357
1395
|
baton->jpegQuality = sharp::AttrAsUint32(options, "jpegQuality");
|
|
1358
1396
|
baton->jpegProgressive = sharp::AttrAsBool(options, "jpegProgressive");
|
|
@@ -1396,6 +1434,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1396
1434
|
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_HEIF_COMPRESSION,
|
|
1397
1435
|
sharp::AttrAsStr(options, "heifCompression").data()));
|
|
1398
1436
|
baton->heifSpeed = sharp::AttrAsUint32(options, "heifSpeed");
|
|
1437
|
+
baton->heifChromaSubsampling = sharp::AttrAsStr(options, "heifChromaSubsampling");
|
|
1399
1438
|
|
|
1400
1439
|
// Animated output
|
|
1401
1440
|
if (sharp::HasAttr(options, "pageHeight")) {
|
|
@@ -1425,6 +1464,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1425
1464
|
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_DZ_DEPTH,
|
|
1426
1465
|
sharp::AttrAsStr(options, "tileDepth").data()));
|
|
1427
1466
|
baton->tileCentre = sharp::AttrAsBool(options, "tileCentre");
|
|
1467
|
+
baton->tileId = sharp::AttrAsStr(options, "tileId");
|
|
1428
1468
|
|
|
1429
1469
|
// Force random access for certain operations
|
|
1430
1470
|
if (baton->input->access == VIPS_ACCESS_SEQUENTIAL) {
|
package/src/pipeline.h
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
#include <memory>
|
|
19
19
|
#include <string>
|
|
20
20
|
#include <vector>
|
|
21
|
+
#include <unordered_map>
|
|
21
22
|
|
|
22
23
|
#include <napi.h>
|
|
23
24
|
#include <vips/vips8>
|
|
@@ -162,11 +163,13 @@ struct PipelineBaton {
|
|
|
162
163
|
int heifQuality;
|
|
163
164
|
VipsForeignHeifCompression heifCompression;
|
|
164
165
|
int heifSpeed;
|
|
166
|
+
std::string heifChromaSubsampling;
|
|
165
167
|
bool heifLossless;
|
|
166
168
|
std::string err;
|
|
167
169
|
bool withMetadata;
|
|
168
170
|
int withMetadataOrientation;
|
|
169
171
|
std::string withMetadataIcc;
|
|
172
|
+
std::unordered_map<std::string, std::string> withMetadataStrs;
|
|
170
173
|
std::unique_ptr<double[]> convKernel;
|
|
171
174
|
int convKernelWidth;
|
|
172
175
|
int convKernelHeight;
|
|
@@ -177,7 +180,7 @@ struct PipelineBaton {
|
|
|
177
180
|
VipsOperationBoolean bandBoolOp;
|
|
178
181
|
int extractChannel;
|
|
179
182
|
bool removeAlpha;
|
|
180
|
-
|
|
183
|
+
double ensureAlpha;
|
|
181
184
|
VipsInterpretation colourspace;
|
|
182
185
|
int pageHeight;
|
|
183
186
|
std::vector<int> delay;
|
|
@@ -191,6 +194,7 @@ struct PipelineBaton {
|
|
|
191
194
|
std::vector<double> tileBackground;
|
|
192
195
|
int tileSkipBlanks;
|
|
193
196
|
VipsForeignDzDepth tileDepth;
|
|
197
|
+
std::string tileId;
|
|
194
198
|
std::unique_ptr<double[]> recombMatrix;
|
|
195
199
|
|
|
196
200
|
PipelineBaton():
|
|
@@ -257,7 +261,7 @@ struct PipelineBaton {
|
|
|
257
261
|
jpegOptimiseScans(false),
|
|
258
262
|
jpegOptimiseCoding(true),
|
|
259
263
|
pngProgressive(false),
|
|
260
|
-
pngCompressionLevel(
|
|
264
|
+
pngCompressionLevel(6),
|
|
261
265
|
pngAdaptiveFiltering(false),
|
|
262
266
|
pngPalette(false),
|
|
263
267
|
pngQuality(100),
|
|
@@ -282,6 +286,7 @@ struct PipelineBaton {
|
|
|
282
286
|
heifQuality(50),
|
|
283
287
|
heifCompression(VIPS_FOREIGN_HEIF_COMPRESSION_AV1),
|
|
284
288
|
heifSpeed(5),
|
|
289
|
+
heifChromaSubsampling("4:2:0"),
|
|
285
290
|
heifLossless(false),
|
|
286
291
|
withMetadata(false),
|
|
287
292
|
withMetadataOrientation(-1),
|
|
@@ -294,7 +299,7 @@ struct PipelineBaton {
|
|
|
294
299
|
bandBoolOp(VIPS_OPERATION_BOOLEAN_LAST),
|
|
295
300
|
extractChannel(-1),
|
|
296
301
|
removeAlpha(false),
|
|
297
|
-
ensureAlpha(
|
|
302
|
+
ensureAlpha(-1.0),
|
|
298
303
|
colourspace(VIPS_INTERPRETATION_LAST),
|
|
299
304
|
pageHeight(0),
|
|
300
305
|
delay{-1},
|
package/src/sharp.cc
CHANGED
|
@@ -23,7 +23,6 @@
|
|
|
23
23
|
|
|
24
24
|
static void* sharp_vips_init(void*) {
|
|
25
25
|
g_setenv("VIPS_MIN_STACK_SIZE", "2m", FALSE);
|
|
26
|
-
g_setenv("PANGOCAIRO_BACKEND", "fontconfig", FALSE);
|
|
27
26
|
vips_init("sharp");
|
|
28
27
|
return nullptr;
|
|
29
28
|
}
|
|
@@ -45,6 +44,7 @@ Napi::Object init(Napi::Env env, Napi::Object exports) {
|
|
|
45
44
|
exports.Set("libvipsVersion", Napi::Function::New(env, libvipsVersion));
|
|
46
45
|
exports.Set("format", Napi::Function::New(env, format));
|
|
47
46
|
exports.Set("_maxColourDistance", Napi::Function::New(env, _maxColourDistance));
|
|
47
|
+
exports.Set("_isUsingJemalloc", Napi::Function::New(env, _isUsingJemalloc));
|
|
48
48
|
exports.Set("stats", Napi::Function::New(env, stats));
|
|
49
49
|
return exports;
|
|
50
50
|
}
|
package/src/utilities.cc
CHANGED
|
@@ -225,3 +225,19 @@ Napi::Value _maxColourDistance(const Napi::CallbackInfo& info) {
|
|
|
225
225
|
|
|
226
226
|
return Napi::Number::New(env, maxColourDistance);
|
|
227
227
|
}
|
|
228
|
+
|
|
229
|
+
#if defined(__GNUC__)
|
|
230
|
+
// mallctl will be resolved by the runtime linker when jemalloc is being used
|
|
231
|
+
extern "C" {
|
|
232
|
+
int mallctl(const char *name, void *oldp, size_t *oldlenp, void *newp, size_t newlen) __attribute__((weak));
|
|
233
|
+
}
|
|
234
|
+
Napi::Value _isUsingJemalloc(const Napi::CallbackInfo& info) {
|
|
235
|
+
Napi::Env env = info.Env();
|
|
236
|
+
return Napi::Boolean::New(env, mallctl != nullptr);
|
|
237
|
+
}
|
|
238
|
+
#else
|
|
239
|
+
Napi::Value _isUsingJemalloc(const Napi::CallbackInfo& info) {
|
|
240
|
+
Napi::Env env = info.Env();
|
|
241
|
+
return Napi::Boolean::New(env, false);
|
|
242
|
+
}
|
|
243
|
+
#endif
|
package/src/utilities.h
CHANGED
|
@@ -24,5 +24,6 @@ Napi::Value simd(const Napi::CallbackInfo& info);
|
|
|
24
24
|
Napi::Value libvipsVersion(const Napi::CallbackInfo& info);
|
|
25
25
|
Napi::Value format(const Napi::CallbackInfo& info);
|
|
26
26
|
Napi::Value _maxColourDistance(const Napi::CallbackInfo& info);
|
|
27
|
+
Napi::Value _isUsingJemalloc(const Napi::CallbackInfo& info);
|
|
27
28
|
|
|
28
29
|
#endif // SRC_UTILITIES_H_
|