@revizly/sharp 0.35.0-revizly22 → 0.35.0-revizly24
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/lib/constructor.js +1 -0
- package/lib/libvips.js +15 -15
- package/lib/output.js +32 -1
- package/package.json +12 -12
- package/src/binding.gyp +0 -1
- package/src/common.cc +3 -1
- package/src/common.h +8 -2
- package/src/metadata.cc +4 -3
- package/src/pipeline.cc +147 -37
- package/src/pipeline.h +2 -0
- package/src/stats.cc +4 -3
package/lib/constructor.js
CHANGED
package/lib/libvips.js
CHANGED
|
@@ -37,13 +37,13 @@ const log = (item) => {
|
|
|
37
37
|
}
|
|
38
38
|
};
|
|
39
39
|
|
|
40
|
-
/* node:coverage
|
|
40
|
+
/* node:coverage disable */
|
|
41
|
+
|
|
41
42
|
const runtimeLibc = () => detectLibc.isNonGlibcLinuxSync() ? detectLibc.familySync() : '';
|
|
42
43
|
|
|
43
44
|
const runtimePlatformArch = () => `${process.platform}${runtimeLibc()}-${process.arch}`;
|
|
44
45
|
|
|
45
46
|
const buildPlatformArch = () => {
|
|
46
|
-
/* node:coverage ignore next 3 */
|
|
47
47
|
if (isEmscripten()) {
|
|
48
48
|
return 'wasm32';
|
|
49
49
|
}
|
|
@@ -56,7 +56,6 @@ const buildSharpLibvipsIncludeDir = () => {
|
|
|
56
56
|
try {
|
|
57
57
|
return require(`@revizly/sharp-libvips-dev-${buildPlatformArch()}/include`);
|
|
58
58
|
} catch {
|
|
59
|
-
/* node:coverage ignore next 5 */
|
|
60
59
|
try {
|
|
61
60
|
return require('@revizly/sharp-libvips-dev/include');
|
|
62
61
|
} catch {}
|
|
@@ -65,7 +64,6 @@ const buildSharpLibvipsIncludeDir = () => {
|
|
|
65
64
|
};
|
|
66
65
|
|
|
67
66
|
const buildSharpLibvipsCPlusPlusDir = () => {
|
|
68
|
-
/* node:coverage ignore next 4 */
|
|
69
67
|
try {
|
|
70
68
|
return require('@revizly/sharp-libvips-dev/cplusplus');
|
|
71
69
|
} catch {}
|
|
@@ -76,7 +74,6 @@ const buildSharpLibvipsLibDir = () => {
|
|
|
76
74
|
try {
|
|
77
75
|
return require(`@revizly/sharp-libvips-dev-${buildPlatformArch()}/lib`);
|
|
78
76
|
} catch {
|
|
79
|
-
/* node:coverage ignore next 5 */
|
|
80
77
|
try {
|
|
81
78
|
return require(`@revizly/sharp-libvips-${buildPlatformArch()}/lib`);
|
|
82
79
|
} catch {}
|
|
@@ -84,8 +81,6 @@ const buildSharpLibvipsLibDir = () => {
|
|
|
84
81
|
return '';
|
|
85
82
|
};
|
|
86
83
|
|
|
87
|
-
/* node:coverage disable */
|
|
88
|
-
|
|
89
84
|
const isUnsupportedNodeRuntime = () => {
|
|
90
85
|
if (process.release?.name === 'node' && process.versions) {
|
|
91
86
|
if (!semverSatisfies(process.versions.node, engines.node)) {
|
|
@@ -151,9 +146,17 @@ const getBrewPkgConfigPath = () => {
|
|
|
151
146
|
if (brewPrefix) {
|
|
152
147
|
return `${brewPrefix}/lib/pkgconfig`;
|
|
153
148
|
}
|
|
154
|
-
} catch (_err) {
|
|
155
|
-
|
|
156
|
-
|
|
149
|
+
} catch (_err) {}
|
|
150
|
+
return undefined;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const getPkgConfigPath = () => {
|
|
154
|
+
try {
|
|
155
|
+
const pkgConfigPath = (spawnSync('pkg-config', ['--variable', 'pc_path', 'pkg-config'], { encoding: 'utf8' }).stdout || '').trim();
|
|
156
|
+
if (pkgConfigPath) {
|
|
157
|
+
return pkgConfigPath;
|
|
158
|
+
}
|
|
159
|
+
} catch (_err) {}
|
|
157
160
|
return undefined;
|
|
158
161
|
};
|
|
159
162
|
|
|
@@ -163,11 +166,8 @@ const pkgConfigPath = () => {
|
|
|
163
166
|
if (process.platform !== 'win32') {
|
|
164
167
|
return [
|
|
165
168
|
getBrewPkgConfigPath(),
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
'/usr/lib/pkgconfig',
|
|
169
|
-
'/usr/local/libdata/pkgconfig',
|
|
170
|
-
'/usr/libdata/pkgconfig'
|
|
169
|
+
getPkgConfigPath(),
|
|
170
|
+
process.env.PKG_CONFIG_PATH
|
|
171
171
|
].filter(Boolean).join(':');
|
|
172
172
|
} else {
|
|
173
173
|
return '';
|
package/lib/output.js
CHANGED
|
@@ -377,6 +377,35 @@ function withIccProfile (icc, options) {
|
|
|
377
377
|
return this;
|
|
378
378
|
}
|
|
379
379
|
|
|
380
|
+
/**
|
|
381
|
+
* If the input contains gain map metadata, attempt to process the image and gain map separately,
|
|
382
|
+
* recombining them into a single output image.
|
|
383
|
+
*
|
|
384
|
+
* This approach is faster and should produce better results than {@link #withgainmap withGainMap},
|
|
385
|
+
* however not all operations are supported.
|
|
386
|
+
*
|
|
387
|
+
* Only JPEG input and output are supported.
|
|
388
|
+
* JPEG output options other than `quality` are ignored.
|
|
389
|
+
*
|
|
390
|
+
* This feature is experimental and the API may change.
|
|
391
|
+
*
|
|
392
|
+
* @since 0.35.0
|
|
393
|
+
*
|
|
394
|
+
* @example
|
|
395
|
+
* const outputWithResizedGainMap = await sharp(inputWithGainMap)
|
|
396
|
+
* .keepGainMap()
|
|
397
|
+
* .resize({ width: 64 })
|
|
398
|
+
* .toBuffer();
|
|
399
|
+
*
|
|
400
|
+
* @returns {Sharp}
|
|
401
|
+
*/
|
|
402
|
+
function keepGainMap() {
|
|
403
|
+
this.options.keepGainMap = true;
|
|
404
|
+
this.options.withGainMap = false;
|
|
405
|
+
this.options.keepMetadata |= 0b100000;
|
|
406
|
+
return this;
|
|
407
|
+
}
|
|
408
|
+
|
|
380
409
|
/**
|
|
381
410
|
* If the input contains gain map metadata, use it to convert the main image to HDR (High Dynamic Range) before further processing.
|
|
382
411
|
* The input gain map is discarded.
|
|
@@ -389,7 +418,7 @@ function withIccProfile (icc, options) {
|
|
|
389
418
|
* @since 0.35.0
|
|
390
419
|
*
|
|
391
420
|
* @example
|
|
392
|
-
* const
|
|
421
|
+
* const outputWithRegeneratedGainMap = await sharp(inputWithGainMap)
|
|
393
422
|
* .withGainMap()
|
|
394
423
|
* .toBuffer();
|
|
395
424
|
*
|
|
@@ -397,6 +426,7 @@ function withIccProfile (icc, options) {
|
|
|
397
426
|
*/
|
|
398
427
|
function withGainMap() {
|
|
399
428
|
this.options.withGainMap = true;
|
|
429
|
+
this.options.keepGainMap = false;
|
|
400
430
|
this.options.colourspace = 'scrgb';
|
|
401
431
|
return this;
|
|
402
432
|
}
|
|
@@ -1741,6 +1771,7 @@ module.exports = (Sharp) => {
|
|
|
1741
1771
|
withExifMerge,
|
|
1742
1772
|
keepIccProfile,
|
|
1743
1773
|
withIccProfile,
|
|
1774
|
+
keepGainMap,
|
|
1744
1775
|
withGainMap,
|
|
1745
1776
|
keepXmp,
|
|
1746
1777
|
withXmp,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@revizly/sharp",
|
|
3
3
|
"description": "High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, GIF, AVIF and TIFF images",
|
|
4
|
-
"version": "0.35.0-
|
|
4
|
+
"version": "0.35.0-revizly24",
|
|
5
5
|
"author": "Lovell Fuller <npm@lovell.info>",
|
|
6
6
|
"homepage": "https://sharp.pixelplumbing.com",
|
|
7
7
|
"contributors": [
|
|
@@ -145,24 +145,24 @@
|
|
|
145
145
|
"semver": "^7.7.4"
|
|
146
146
|
},
|
|
147
147
|
"optionalDependencies": {
|
|
148
|
-
"@revizly/sharp-libvips-linux-arm64": "1.0.
|
|
149
|
-
"@revizly/sharp-libvips-linux-x64": "1.0.
|
|
150
|
-
"@revizly/sharp-linux-arm64": "0.35.0-
|
|
151
|
-
"@revizly/sharp-linux-x64": "0.35.0-
|
|
148
|
+
"@revizly/sharp-libvips-linux-arm64": "1.0.29",
|
|
149
|
+
"@revizly/sharp-libvips-linux-x64": "1.0.29",
|
|
150
|
+
"@revizly/sharp-linux-arm64": "0.35.0-revizly22",
|
|
151
|
+
"@revizly/sharp-linux-x64": "0.35.0-revizly22"
|
|
152
152
|
},
|
|
153
153
|
"devDependencies": {
|
|
154
|
-
"@biomejs/biome": "^2.4.
|
|
154
|
+
"@biomejs/biome": "^2.4.8",
|
|
155
155
|
"@cpplint/cli": "^0.1.0",
|
|
156
|
-
"@emnapi/runtime": "^1.
|
|
157
|
-
"@revizly/sharp-libvips-dev": "1.0.
|
|
156
|
+
"@emnapi/runtime": "^1.9.1",
|
|
157
|
+
"@revizly/sharp-libvips-dev": "1.0.29",
|
|
158
158
|
"@types/node": "*",
|
|
159
|
-
"emnapi": "^1.
|
|
159
|
+
"emnapi": "^1.9.1",
|
|
160
160
|
"exif-reader": "^2.0.3",
|
|
161
161
|
"extract-zip": "^2.0.1",
|
|
162
162
|
"icc": "^3.0.0",
|
|
163
|
-
"node-addon-api": "^8.
|
|
163
|
+
"node-addon-api": "^8.6.0",
|
|
164
164
|
"node-gyp": "^12.2.0",
|
|
165
|
-
"tar-fs": "^3.1.
|
|
165
|
+
"tar-fs": "^3.1.2",
|
|
166
166
|
"tsd": "^0.33.0"
|
|
167
167
|
},
|
|
168
168
|
"license": "Apache-2.0",
|
|
@@ -170,7 +170,7 @@
|
|
|
170
170
|
"node": ">=20.9.0"
|
|
171
171
|
},
|
|
172
172
|
"config": {
|
|
173
|
-
"libvips": ">=8.18.
|
|
173
|
+
"libvips": ">=8.18.1"
|
|
174
174
|
},
|
|
175
175
|
"funding": {
|
|
176
176
|
"url": "https://opencollective.com/libvips"
|
package/src/binding.gyp
CHANGED
package/src/common.cc
CHANGED
|
@@ -1164,7 +1164,8 @@ namespace sharp {
|
|
|
1164
1164
|
Does this image have a gain map?
|
|
1165
1165
|
*/
|
|
1166
1166
|
bool HasGainMap(VImage image) {
|
|
1167
|
-
return image.get_typeof("gainmap
|
|
1167
|
+
return image.get_typeof("gainmap") == VIPS_TYPE_BLOB ||
|
|
1168
|
+
image.get_typeof("gainmap-data") == VIPS_TYPE_BLOB;
|
|
1168
1169
|
}
|
|
1169
1170
|
|
|
1170
1171
|
/*
|
|
@@ -1172,6 +1173,7 @@ namespace sharp {
|
|
|
1172
1173
|
*/
|
|
1173
1174
|
VImage RemoveGainMap(VImage image) {
|
|
1174
1175
|
VImage copy = image.copy();
|
|
1176
|
+
copy.remove("gainmap");
|
|
1175
1177
|
copy.remove("gainmap-data");
|
|
1176
1178
|
return copy;
|
|
1177
1179
|
}
|
package/src/common.h
CHANGED
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
|
|
20
20
|
#if (VIPS_MAJOR_VERSION < 8) || \
|
|
21
21
|
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION < 18) || \
|
|
22
|
-
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 18 && VIPS_MICRO_VERSION <
|
|
23
|
-
#error "libvips version 8.18.
|
|
22
|
+
(VIPS_MAJOR_VERSION == 8 && VIPS_MINOR_VERSION == 18 && VIPS_MICRO_VERSION < 1)
|
|
23
|
+
#error "libvips version 8.18.1+ is required - please see https://sharp.pixelplumbing.com/install"
|
|
24
24
|
#endif
|
|
25
25
|
|
|
26
26
|
#if defined(__has_include)
|
|
@@ -29,6 +29,12 @@
|
|
|
29
29
|
#endif
|
|
30
30
|
#endif
|
|
31
31
|
|
|
32
|
+
#ifdef __EMSCRIPTEN__
|
|
33
|
+
#define SHARP_CALLBACK_FN_NAME Call
|
|
34
|
+
#else
|
|
35
|
+
#define SHARP_CALLBACK_FN_NAME MakeCallback
|
|
36
|
+
#endif
|
|
37
|
+
|
|
32
38
|
using vips::VImage;
|
|
33
39
|
|
|
34
40
|
namespace sharp {
|
package/src/metadata.cc
CHANGED
|
@@ -209,7 +209,7 @@ class MetadataWorker : public Napi::AsyncWorker {
|
|
|
209
209
|
// Handle warnings
|
|
210
210
|
std::string warning = sharp::VipsWarningPop();
|
|
211
211
|
while (!warning.empty()) {
|
|
212
|
-
debuglog.
|
|
212
|
+
debuglog.SHARP_CALLBACK_FN_NAME(Receiver().Value(), { Napi::String::New(env, warning) });
|
|
213
213
|
warning = sharp::VipsWarningPop();
|
|
214
214
|
}
|
|
215
215
|
|
|
@@ -344,9 +344,10 @@ class MetadataWorker : public Napi::AsyncWorker {
|
|
|
344
344
|
}
|
|
345
345
|
info.Set("comments", comments);
|
|
346
346
|
}
|
|
347
|
-
Callback().
|
|
347
|
+
Callback().SHARP_CALLBACK_FN_NAME(Receiver().Value(), { env.Null(), info });
|
|
348
348
|
} else {
|
|
349
|
-
Callback().
|
|
349
|
+
Callback().SHARP_CALLBACK_FN_NAME(Receiver().Value(),
|
|
350
|
+
{ Napi::Error::New(env, sharp::TrimEnd(baton->err)).Value() });
|
|
350
351
|
}
|
|
351
352
|
|
|
352
353
|
delete baton->input;
|
package/src/pipeline.cc
CHANGED
|
@@ -203,9 +203,11 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
203
203
|
// - the width or height parameters are specified;
|
|
204
204
|
// - gamma correction doesn't need to be applied;
|
|
205
205
|
// - trimming or pre-resize extract isn't required;
|
|
206
|
+
// - gain map processing is not required;
|
|
206
207
|
// - input colourspace is not specified;
|
|
207
208
|
bool const shouldPreShrink = (targetResizeWidth > 0 || targetResizeHeight > 0) &&
|
|
208
209
|
baton->gamma == 0 && baton->topOffsetPre == -1 && baton->trimThreshold < 0.0 &&
|
|
210
|
+
!baton->keepGainMap && !baton->withGainMap &&
|
|
209
211
|
baton->colourspacePipeline == VIPS_INTERPRETATION_LAST && !(shouldOrientBefore || shouldRotateBefore);
|
|
210
212
|
|
|
211
213
|
if (shouldPreShrink) {
|
|
@@ -296,12 +298,20 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
296
298
|
if (baton->input->autoOrient) {
|
|
297
299
|
image = sharp::RemoveExifOrientation(image);
|
|
298
300
|
}
|
|
301
|
+
VImage gainMap;
|
|
302
|
+
int gainMapScaleFactor = 1;
|
|
299
303
|
if (sharp::HasGainMap(image)) {
|
|
300
|
-
if (baton->
|
|
304
|
+
if (baton->keepGainMap) {
|
|
305
|
+
gainMap = image.gainmap();
|
|
306
|
+
if (image.get_typeof("gainmap-scale-factor") == G_TYPE_INT) {
|
|
307
|
+
gainMapScaleFactor = image.get_int("gainmap-scale-factor");
|
|
308
|
+
}
|
|
309
|
+
} else if (baton->withGainMap) {
|
|
301
310
|
image = image.uhdr2scRGB();
|
|
302
311
|
}
|
|
303
312
|
image = sharp::RemoveGainMap(image);
|
|
304
313
|
} else {
|
|
314
|
+
baton->keepGainMap = false;
|
|
305
315
|
baton->withGainMap = false;
|
|
306
316
|
}
|
|
307
317
|
|
|
@@ -401,6 +411,11 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
401
411
|
image = image.resize(1.0 / hshrink, VImage::option()
|
|
402
412
|
->set("vscale", 1.0 / vshrink)
|
|
403
413
|
->set("kernel", baton->kernel));
|
|
414
|
+
if (baton->keepGainMap) {
|
|
415
|
+
gainMap = gainMap.resize(1.0 / hshrink, VImage::option()
|
|
416
|
+
->set("vscale", 1.0 / vshrink)
|
|
417
|
+
->set("kernel", baton->kernel));
|
|
418
|
+
}
|
|
404
419
|
}
|
|
405
420
|
|
|
406
421
|
image = sharp::StaySequential(image,
|
|
@@ -413,14 +428,23 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
413
428
|
MultiPageUnsupported(nPages, "Rotate");
|
|
414
429
|
}
|
|
415
430
|
image = image.rot(autoRotation);
|
|
431
|
+
if (baton->keepGainMap) {
|
|
432
|
+
gainMap = gainMap.rot(autoRotation);
|
|
433
|
+
}
|
|
416
434
|
}
|
|
417
435
|
// Mirror vertically (up-down) about the x-axis
|
|
418
436
|
if (baton->flip) {
|
|
419
437
|
image = image.flip(VIPS_DIRECTION_VERTICAL);
|
|
438
|
+
if (baton->keepGainMap) {
|
|
439
|
+
gainMap = gainMap.flip(VIPS_DIRECTION_VERTICAL);
|
|
440
|
+
}
|
|
420
441
|
}
|
|
421
442
|
// Mirror horizontally (left-right) about the y-axis
|
|
422
443
|
if (baton->flop != autoFlop) {
|
|
423
444
|
image = image.flip(VIPS_DIRECTION_HORIZONTAL);
|
|
445
|
+
if (baton->keepGainMap) {
|
|
446
|
+
gainMap = gainMap.flip(VIPS_DIRECTION_HORIZONTAL);
|
|
447
|
+
}
|
|
424
448
|
}
|
|
425
449
|
// Rotate post-extract 90-angle
|
|
426
450
|
if (rotation != VIPS_ANGLE_D0) {
|
|
@@ -428,6 +452,9 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
428
452
|
MultiPageUnsupported(nPages, "Rotate");
|
|
429
453
|
}
|
|
430
454
|
image = image.rot(rotation);
|
|
455
|
+
if (baton->keepGainMap) {
|
|
456
|
+
gainMap = gainMap.rot(rotation);
|
|
457
|
+
}
|
|
431
458
|
}
|
|
432
459
|
|
|
433
460
|
// Join additional color channels to the image
|
|
@@ -474,6 +501,12 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
474
501
|
: image.embed(left, top, width, height, VImage::option()
|
|
475
502
|
->set("extend", VIPS_EXTEND_BACKGROUND)
|
|
476
503
|
->set("background", background));
|
|
504
|
+
if (baton->keepGainMap) {
|
|
505
|
+
gainMap = gainMap.embed(left / gainMapScaleFactor, top / gainMapScaleFactor,
|
|
506
|
+
width / gainMapScaleFactor, height / gainMapScaleFactor, VImage::option()
|
|
507
|
+
->set("extend", VIPS_EXTEND_BACKGROUND)
|
|
508
|
+
->set("background", 0));
|
|
509
|
+
}
|
|
477
510
|
} else if (baton->canvas == sharp::Canvas::CROP) {
|
|
478
511
|
if (baton->width > inputWidth) {
|
|
479
512
|
baton->width = inputWidth;
|
|
@@ -494,12 +527,17 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
494
527
|
? sharp::CropMultiPage(image,
|
|
495
528
|
left, top, width, height, nPages, &targetPageHeight)
|
|
496
529
|
: image.extract_area(left, top, width, height);
|
|
530
|
+
if (baton->keepGainMap) {
|
|
531
|
+
gainMap = gainMap.extract_area(left / gainMapScaleFactor, top / gainMapScaleFactor,
|
|
532
|
+
width / gainMapScaleFactor, height / gainMapScaleFactor);
|
|
533
|
+
}
|
|
497
534
|
} else {
|
|
498
535
|
int attention_x;
|
|
499
536
|
int attention_y;
|
|
500
537
|
|
|
501
538
|
// Attention-based or Entropy-based crop
|
|
502
539
|
MultiPageUnsupported(nPages, "Resize strategy");
|
|
540
|
+
KeepGainMapUnsupported(baton->keepGainMap, "Resize strategy");
|
|
503
541
|
image = sharp::StaySequential(image);
|
|
504
542
|
image = image.smartcrop(baton->width, baton->height, VImage::option()
|
|
505
543
|
->set("interesting", baton->position == 16 ? VIPS_INTERESTING_ENTROPY : VIPS_INTERESTING_ATTENTION)
|
|
@@ -523,6 +561,9 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
523
561
|
std::vector<double> background;
|
|
524
562
|
std::tie(image, background) = sharp::ApplyAlpha(image, baton->rotationBackground, shouldPremultiplyAlpha);
|
|
525
563
|
image = image.rotate(baton->rotationAngle, VImage::option()->set("background", background));
|
|
564
|
+
if (baton->keepGainMap) {
|
|
565
|
+
gainMap = gainMap.rotate(baton->rotationAngle, VImage::option()->set("background", 0));
|
|
566
|
+
}
|
|
526
567
|
}
|
|
527
568
|
|
|
528
569
|
// Post extraction
|
|
@@ -531,18 +572,23 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
531
572
|
image = sharp::CropMultiPage(image,
|
|
532
573
|
baton->leftOffsetPost, baton->topOffsetPost, baton->widthPost, baton->heightPost,
|
|
533
574
|
nPages, &targetPageHeight);
|
|
534
|
-
|
|
535
575
|
// heightPost is used in the info object, so update to reflect the number of pages
|
|
536
576
|
baton->heightPost *= nPages;
|
|
537
577
|
} else {
|
|
538
578
|
image = image.extract_area(
|
|
539
579
|
baton->leftOffsetPost, baton->topOffsetPost, baton->widthPost, baton->heightPost);
|
|
580
|
+
if (baton->keepGainMap) {
|
|
581
|
+
gainMap = gainMap.extract_area(baton->leftOffsetPost / gainMapScaleFactor,
|
|
582
|
+
baton->topOffsetPost / gainMapScaleFactor, baton->widthPost / gainMapScaleFactor,
|
|
583
|
+
baton->heightPost / gainMapScaleFactor);
|
|
584
|
+
}
|
|
540
585
|
}
|
|
541
586
|
}
|
|
542
587
|
|
|
543
588
|
// Affine transform
|
|
544
589
|
if (!baton->affineMatrix.empty()) {
|
|
545
590
|
MultiPageUnsupported(nPages, "Affine");
|
|
591
|
+
KeepGainMapUnsupported(baton->keepGainMap, "Affine");
|
|
546
592
|
image = sharp::StaySequential(image);
|
|
547
593
|
std::vector<double> background;
|
|
548
594
|
std::tie(image, background) = sharp::ApplyAlpha(image, baton->affineBackground, shouldPremultiplyAlpha);
|
|
@@ -573,6 +619,12 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
573
619
|
baton->extendWith, background, nPages, &targetPageHeight)
|
|
574
620
|
: image.embed(baton->extendLeft, baton->extendTop, baton->width, baton->height,
|
|
575
621
|
VImage::option()->set("extend", baton->extendWith)->set("background", background));
|
|
622
|
+
if (baton->keepGainMap) {
|
|
623
|
+
gainMap = gainMap.embed(baton->extendLeft / gainMapScaleFactor, baton->extendTop / gainMapScaleFactor,
|
|
624
|
+
baton->width / gainMapScaleFactor, baton->height / gainMapScaleFactor, VImage::option()
|
|
625
|
+
->set("extend", baton->extendWith)
|
|
626
|
+
->set("background", 0));
|
|
627
|
+
}
|
|
576
628
|
} else {
|
|
577
629
|
std::vector<double> ignoredBackground(1);
|
|
578
630
|
image = sharp::StaySequential(image);
|
|
@@ -582,6 +634,12 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
582
634
|
baton->extendWith, ignoredBackground, nPages, &targetPageHeight)
|
|
583
635
|
: image.embed(baton->extendLeft, baton->extendTop, baton->width, baton->height,
|
|
584
636
|
VImage::option()->set("extend", baton->extendWith));
|
|
637
|
+
if (baton->keepGainMap) {
|
|
638
|
+
gainMap = gainMap.embed(baton->extendLeft / gainMapScaleFactor, baton->extendTop / gainMapScaleFactor,
|
|
639
|
+
baton->width / gainMapScaleFactor, baton->height / gainMapScaleFactor, VImage::option()
|
|
640
|
+
->set("extend", baton->extendWith)
|
|
641
|
+
->set("background", 0));
|
|
642
|
+
}
|
|
585
643
|
}
|
|
586
644
|
}
|
|
587
645
|
// Median - must happen before blurring, due to the utility of blurring after thresholding
|
|
@@ -608,6 +666,9 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
608
666
|
// Blur
|
|
609
667
|
if (shouldBlur) {
|
|
610
668
|
image = sharp::Blur(image, baton->blurSigma, baton->precision, baton->minAmpl);
|
|
669
|
+
if (baton->keepGainMap) {
|
|
670
|
+
gainMap = sharp::Blur(gainMap, baton->blurSigma, baton->precision, baton->minAmpl);
|
|
671
|
+
}
|
|
611
672
|
}
|
|
612
673
|
|
|
613
674
|
// Unflatten the image
|
|
@@ -617,6 +678,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
617
678
|
|
|
618
679
|
// Convolve
|
|
619
680
|
if (shouldConv) {
|
|
681
|
+
KeepGainMapUnsupported(baton->keepGainMap, "Convolve");
|
|
620
682
|
image = sharp::Convolve(image,
|
|
621
683
|
baton->convKernelWidth, baton->convKernelHeight,
|
|
622
684
|
baton->convKernelScale, baton->convKernelOffset,
|
|
@@ -625,11 +687,13 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
625
687
|
|
|
626
688
|
// Recomb
|
|
627
689
|
if (!baton->recombMatrix.empty()) {
|
|
690
|
+
KeepGainMapUnsupported(baton->keepGainMap, "Recomb");
|
|
628
691
|
image = sharp::Recomb(image, baton->recombMatrix);
|
|
629
692
|
}
|
|
630
693
|
|
|
631
694
|
// Modulate
|
|
632
695
|
if (baton->brightness != 1.0 || baton->saturation != 1.0 || baton->hue != 0.0 || baton->lightness != 0.0) {
|
|
696
|
+
KeepGainMapUnsupported(baton->keepGainMap, "Modulate");
|
|
633
697
|
image = sharp::Modulate(image, baton->brightness, baton->saturation, baton->hue, baton->lightness);
|
|
634
698
|
}
|
|
635
699
|
|
|
@@ -647,6 +711,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
647
711
|
|
|
648
712
|
// Composite
|
|
649
713
|
if (shouldComposite) {
|
|
714
|
+
KeepGainMapUnsupported(baton->keepGainMap, "Composite");
|
|
650
715
|
std::vector<VImage> images = { image };
|
|
651
716
|
std::vector<int> modes, xs, ys;
|
|
652
717
|
for (Composite *composite : baton->composite) {
|
|
@@ -759,18 +824,21 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
759
824
|
|
|
760
825
|
// Apply normalisation - stretch luminance to cover full dynamic range
|
|
761
826
|
if (baton->normalise) {
|
|
827
|
+
KeepGainMapUnsupported(baton->keepGainMap, "Normalise");
|
|
762
828
|
image = sharp::StaySequential(image);
|
|
763
829
|
image = sharp::Normalise(image, baton->normaliseLower, baton->normaliseUpper);
|
|
764
830
|
}
|
|
765
831
|
|
|
766
832
|
// Apply contrast limiting adaptive histogram equalization (CLAHE)
|
|
767
833
|
if (baton->claheWidth != 0 && baton->claheHeight != 0) {
|
|
834
|
+
KeepGainMapUnsupported(baton->keepGainMap, "Clahe");
|
|
768
835
|
image = sharp::StaySequential(image);
|
|
769
836
|
image = sharp::Clahe(image, baton->claheWidth, baton->claheHeight, baton->claheMaxSlope);
|
|
770
837
|
}
|
|
771
838
|
|
|
772
839
|
// Apply bitwise boolean operation between images
|
|
773
840
|
if (baton->boolean != nullptr) {
|
|
841
|
+
KeepGainMapUnsupported(baton->keepGainMap, "Boolean");
|
|
774
842
|
VImage booleanImage;
|
|
775
843
|
sharp::ImageType booleanImageType = sharp::ImageType::UNKNOWN;
|
|
776
844
|
baton->boolean->access = access;
|
|
@@ -813,6 +881,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
813
881
|
|
|
814
882
|
// Extract channel
|
|
815
883
|
if (baton->extractChannel > -1) {
|
|
884
|
+
KeepGainMapUnsupported(baton->keepGainMap, "Extract channel");
|
|
816
885
|
if (baton->extractChannel >= image.bands()) {
|
|
817
886
|
if (baton->extractChannel == 3 && image.has_alpha()) {
|
|
818
887
|
baton->extractChannel = image.bands() - 1;
|
|
@@ -847,6 +916,9 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
847
916
|
// Negate the colours in the image
|
|
848
917
|
if (baton->negate) {
|
|
849
918
|
image = sharp::Negate(image, baton->negateAlpha);
|
|
919
|
+
if (baton->keepGainMap) {
|
|
920
|
+
gainMap = sharp::Negate(gainMap, false);
|
|
921
|
+
}
|
|
850
922
|
}
|
|
851
923
|
|
|
852
924
|
// Override EXIF Orientation tag
|
|
@@ -893,18 +965,27 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
893
965
|
if (baton->formatOut == "jpeg" || (baton->formatOut == "input" && inputImageType == sharp::ImageType::JPEG)) {
|
|
894
966
|
// Write JPEG to buffer
|
|
895
967
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
|
|
896
|
-
VipsArea *area
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
968
|
+
VipsArea *area;
|
|
969
|
+
if (baton->keepGainMap) {
|
|
970
|
+
image = ReattachGainMap(image, gainMap, baton);
|
|
971
|
+
area = reinterpret_cast<VipsArea*>(image.uhdrsave_buffer(VImage::option()
|
|
972
|
+
->set("keep", baton->keepMetadata)
|
|
973
|
+
->set("Q", baton->jpegQuality)
|
|
974
|
+
->set("gainmap_scale_factor", gainMapScaleFactor)));
|
|
975
|
+
} else {
|
|
976
|
+
area = reinterpret_cast<VipsArea*>(image.jpegsave_buffer(VImage::option()
|
|
977
|
+
->set("keep", baton->keepMetadata)
|
|
978
|
+
->set("Q", baton->jpegQuality)
|
|
979
|
+
->set("interlace", baton->jpegProgressive)
|
|
980
|
+
->set("subsample_mode", baton->jpegChromaSubsampling == "4:4:4"
|
|
981
|
+
? VIPS_FOREIGN_SUBSAMPLE_OFF
|
|
982
|
+
: VIPS_FOREIGN_SUBSAMPLE_ON)
|
|
983
|
+
->set("trellis_quant", baton->jpegTrellisQuantisation)
|
|
984
|
+
->set("quant_table", baton->jpegQuantisationTable)
|
|
985
|
+
->set("overshoot_deringing", baton->jpegOvershootDeringing)
|
|
986
|
+
->set("optimize_scans", baton->jpegOptimiseScans)
|
|
987
|
+
->set("optimize_coding", baton->jpegOptimiseCoding)));
|
|
988
|
+
}
|
|
908
989
|
baton->bufferOut = static_cast<char*>(area->data);
|
|
909
990
|
baton->bufferOutLength = area->length;
|
|
910
991
|
area->free_fn = nullptr;
|
|
@@ -1121,18 +1202,26 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1121
1202
|
(willMatchInput && inputImageType == sharp::ImageType::JPEG)) {
|
|
1122
1203
|
// Write JPEG to file
|
|
1123
1204
|
sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
->
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
->
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1205
|
+
if (baton->keepGainMap) {
|
|
1206
|
+
image = ReattachGainMap(image, gainMap, baton);
|
|
1207
|
+
image.uhdrsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
|
1208
|
+
->set("keep", baton->keepMetadata)
|
|
1209
|
+
->set("Q", baton->jpegQuality)
|
|
1210
|
+
->set("gainmap_scale_factor", gainMapScaleFactor));
|
|
1211
|
+
} else {
|
|
1212
|
+
image.jpegsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
|
|
1213
|
+
->set("keep", baton->keepMetadata)
|
|
1214
|
+
->set("Q", baton->jpegQuality)
|
|
1215
|
+
->set("interlace", baton->jpegProgressive)
|
|
1216
|
+
->set("subsample_mode", baton->jpegChromaSubsampling == "4:4:4"
|
|
1217
|
+
? VIPS_FOREIGN_SUBSAMPLE_OFF
|
|
1218
|
+
: VIPS_FOREIGN_SUBSAMPLE_ON)
|
|
1219
|
+
->set("trellis_quant", baton->jpegTrellisQuantisation)
|
|
1220
|
+
->set("quant_table", baton->jpegQuantisationTable)
|
|
1221
|
+
->set("overshoot_deringing", baton->jpegOvershootDeringing)
|
|
1222
|
+
->set("optimize_scans", baton->jpegOptimiseScans)
|
|
1223
|
+
->set("optimize_coding", baton->jpegOptimiseCoding));
|
|
1224
|
+
}
|
|
1136
1225
|
baton->formatOut = "jpeg";
|
|
1137
1226
|
baton->channels = std::min(baton->channels, 3);
|
|
1138
1227
|
} else if (baton->formatOut == "jp2" || (mightMatchInput && isJp2) ||
|
|
@@ -1301,7 +1390,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1301
1390
|
if (baton->errUseWarning) {
|
|
1302
1391
|
(baton->err).append("\n").append(warning);
|
|
1303
1392
|
} else {
|
|
1304
|
-
debuglog.
|
|
1393
|
+
debuglog.SHARP_CALLBACK_FN_NAME(Receiver().Value(), { Napi::String::New(env, warning) });
|
|
1305
1394
|
}
|
|
1306
1395
|
warning = sharp::VipsWarningPop();
|
|
1307
1396
|
}
|
|
@@ -1350,17 +1439,15 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1350
1439
|
info.Set("size", static_cast<uint32_t>(baton->bufferOutLength));
|
|
1351
1440
|
if (baton->typedArrayOut) {
|
|
1352
1441
|
// ECMAScript ArrayBuffer with Uint8Array view
|
|
1353
|
-
Napi::
|
|
1354
|
-
|
|
1442
|
+
Napi::TypedArrayOf<uint8_t> data = Napi::Buffer<char>::Copy(env,
|
|
1443
|
+
static_cast<char*>(baton->bufferOut), baton->bufferOutLength);
|
|
1355
1444
|
sharp::FreeCallback(static_cast<char*>(baton->bufferOut), nullptr);
|
|
1356
|
-
|
|
1357
|
-
baton->bufferOutLength, ab, 0, napi_uint8_array);
|
|
1358
|
-
Callback().MakeCallback(Receiver().Value(), { env.Null(), data, info });
|
|
1445
|
+
Callback().SHARP_CALLBACK_FN_NAME(Receiver().Value(), { env.Null(), data, info });
|
|
1359
1446
|
} else {
|
|
1360
1447
|
// Node.js Buffer
|
|
1361
1448
|
Napi::Buffer<char> data = Napi::Buffer<char>::NewOrCopy(env, static_cast<char*>(baton->bufferOut),
|
|
1362
1449
|
baton->bufferOutLength, sharp::FreeCallback);
|
|
1363
|
-
Callback().
|
|
1450
|
+
Callback().SHARP_CALLBACK_FN_NAME(Receiver().Value(), { env.Null(), data, info });
|
|
1364
1451
|
}
|
|
1365
1452
|
} else {
|
|
1366
1453
|
// Add file size to info
|
|
@@ -1371,10 +1458,11 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1371
1458
|
info.Set("size", size);
|
|
1372
1459
|
} catch (...) {}
|
|
1373
1460
|
}
|
|
1374
|
-
Callback().
|
|
1461
|
+
Callback().SHARP_CALLBACK_FN_NAME(Receiver().Value(), { env.Null(), info });
|
|
1375
1462
|
}
|
|
1376
1463
|
} else {
|
|
1377
|
-
Callback().
|
|
1464
|
+
Callback().SHARP_CALLBACK_FN_NAME(Receiver().Value(),
|
|
1465
|
+
{ Napi::Error::New(env, sharp::TrimEnd(baton->err)).Value() });
|
|
1378
1466
|
}
|
|
1379
1467
|
|
|
1380
1468
|
// Delete baton
|
|
@@ -1395,7 +1483,7 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1395
1483
|
// Decrement processing task counter
|
|
1396
1484
|
sharp::counterProcess--;
|
|
1397
1485
|
Napi::Number queueLength = Napi::Number::New(env, static_cast<int>(sharp::counterQueue));
|
|
1398
|
-
queueListener.
|
|
1486
|
+
queueListener.SHARP_CALLBACK_FN_NAME(Receiver().Value(), { queueLength });
|
|
1399
1487
|
}
|
|
1400
1488
|
|
|
1401
1489
|
private:
|
|
@@ -1409,6 +1497,12 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1409
1497
|
}
|
|
1410
1498
|
}
|
|
1411
1499
|
|
|
1500
|
+
void KeepGainMapUnsupported(bool const keepGainMap, std::string op) {
|
|
1501
|
+
if (keepGainMap) {
|
|
1502
|
+
throw std::runtime_error(op + " is not supported when keeping gain maps");
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1412
1506
|
/*
|
|
1413
1507
|
Calculate the angle of rotation and need-to-flip for the given Exif orientation
|
|
1414
1508
|
By default, returns zero, i.e. no rotation.
|
|
@@ -1527,6 +1621,21 @@ class PipelineWorker : public Napi::AsyncWorker {
|
|
|
1527
1621
|
return options;
|
|
1528
1622
|
}
|
|
1529
1623
|
|
|
1624
|
+
VImage ReattachGainMap(VImage image, VImage gainMap, PipelineBaton *baton) {
|
|
1625
|
+
VipsArea *gainMapJpeg = reinterpret_cast<VipsArea*>(gainMap.jpegsave_buffer(VImage::option()
|
|
1626
|
+
->set("keep", FALSE)
|
|
1627
|
+
->set("Q", baton->jpegQuality)
|
|
1628
|
+
->set("subsample_mode", VIPS_FOREIGN_SUBSAMPLE_OFF)
|
|
1629
|
+
->set("trellis_quant", baton->jpegTrellisQuantisation)
|
|
1630
|
+
->set("quant_table", baton->jpegQuantisationTable)
|
|
1631
|
+
->set("overshoot_deringing", baton->jpegOvershootDeringing)
|
|
1632
|
+
->set("optimize_coding", baton->jpegOptimiseCoding)));
|
|
1633
|
+
image = image.copy();
|
|
1634
|
+
image.set("gainmap-data", reinterpret_cast<VipsCallbackFn>(vips_area_free_cb),
|
|
1635
|
+
gainMapJpeg->data, gainMapJpeg->length);
|
|
1636
|
+
return image;
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1530
1639
|
/*
|
|
1531
1640
|
Clear all thread-local data.
|
|
1532
1641
|
*/
|
|
@@ -1728,6 +1837,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1728
1837
|
}
|
|
1729
1838
|
baton->withExifMerge = sharp::AttrAsBool(options, "withExifMerge");
|
|
1730
1839
|
baton->withXmp = sharp::AttrAsStr(options, "withXmp");
|
|
1840
|
+
baton->keepGainMap = sharp::AttrAsBool(options, "keepGainMap");
|
|
1731
1841
|
baton->withGainMap = sharp::AttrAsBool(options, "withGainMap");
|
|
1732
1842
|
baton->timeoutSeconds = sharp::AttrAsUint32(options, "timeoutSeconds");
|
|
1733
1843
|
baton->loop = sharp::AttrAsUint32(options, "loop");
|
|
@@ -1833,7 +1943,7 @@ Napi::Value pipeline(const Napi::CallbackInfo& info) {
|
|
|
1833
1943
|
|
|
1834
1944
|
// Increment queued task counter
|
|
1835
1945
|
Napi::Number queueLength = Napi::Number::New(info.Env(), static_cast<int>(++sharp::counterQueue));
|
|
1836
|
-
queueListener.
|
|
1946
|
+
queueListener.SHARP_CALLBACK_FN_NAME(info.This(), { queueLength });
|
|
1837
1947
|
|
|
1838
1948
|
return info.Env().Undefined();
|
|
1839
1949
|
}
|
package/src/pipeline.h
CHANGED
|
@@ -213,6 +213,7 @@ struct PipelineBaton {
|
|
|
213
213
|
bool withExifMerge;
|
|
214
214
|
std::string withXmp;
|
|
215
215
|
bool withGainMap;
|
|
216
|
+
bool keepGainMap;
|
|
216
217
|
int timeoutSeconds;
|
|
217
218
|
std::vector<double> convKernel;
|
|
218
219
|
int convKernelWidth;
|
|
@@ -391,6 +392,7 @@ struct PipelineBaton {
|
|
|
391
392
|
withMetadataDensity(0.0),
|
|
392
393
|
withExifMerge(true),
|
|
393
394
|
withGainMap(false),
|
|
395
|
+
keepGainMap(false),
|
|
394
396
|
timeoutSeconds(0),
|
|
395
397
|
convKernelWidth(0),
|
|
396
398
|
convKernelHeight(0),
|
package/src/stats.cc
CHANGED
|
@@ -109,7 +109,7 @@ class StatsWorker : public Napi::AsyncWorker {
|
|
|
109
109
|
// Handle warnings
|
|
110
110
|
std::string warning = sharp::VipsWarningPop();
|
|
111
111
|
while (!warning.empty()) {
|
|
112
|
-
debuglog.
|
|
112
|
+
debuglog.SHARP_CALLBACK_FN_NAME(Receiver().Value(), { Napi::String::New(env, warning) });
|
|
113
113
|
warning = sharp::VipsWarningPop();
|
|
114
114
|
}
|
|
115
115
|
if (baton->err.empty()) {
|
|
@@ -143,9 +143,10 @@ class StatsWorker : public Napi::AsyncWorker {
|
|
|
143
143
|
dominant.Set("g", baton->dominantGreen);
|
|
144
144
|
dominant.Set("b", baton->dominantBlue);
|
|
145
145
|
info.Set("dominant", dominant);
|
|
146
|
-
Callback().
|
|
146
|
+
Callback().SHARP_CALLBACK_FN_NAME(Receiver().Value(), { env.Null(), info });
|
|
147
147
|
} else {
|
|
148
|
-
Callback().
|
|
148
|
+
Callback().SHARP_CALLBACK_FN_NAME(Receiver().Value(),
|
|
149
|
+
{ Napi::Error::New(env, sharp::TrimEnd(baton->err)).Value() });
|
|
149
150
|
}
|
|
150
151
|
|
|
151
152
|
delete baton->input;
|