Migrate internals to N-API #1282

This commit is contained in:
Lovell Fuller 2020-02-15 19:36:19 +00:00
parent d5ecc537af
commit 4abb4edf64
21 changed files with 599 additions and 743 deletions

View File

@ -44,8 +44,8 @@ Any change that modifies the existing public API should be added to the relevant
| Release | WIP branch | | Release | WIP branch |
| ------: | :--------- | | ------: | :--------- |
| v0.24.0 | wit |
| v0.25.0 | yield | | v0.25.0 | yield |
| v0.26.0 | zoom |
Please squash your changes into a single commit using a command like `git rebase -i upstream/<wip-branch>`. Please squash your changes into a single commit using a command like `git rebase -i upstream/<wip-branch>`.

View File

@ -20,8 +20,7 @@ Lanczos resampling ensures quality is not sacrificed for speed.
As well as image resizing, operations such as As well as image resizing, operations such as
rotation, extraction, compositing and gamma correction are available. rotation, extraction, compositing and gamma correction are available.
Most modern 64-bit macOS, Windows and Linux systems running Most modern 64-bit macOS, Windows and Linux systems running Node.js v10.16.0+
Node versions 10, 12 and 13
do not require any additional install or runtime dependencies. do not require any additional install or runtime dependencies.
## Examples ## Examples

View File

@ -45,6 +45,7 @@
}, { }, {
'target_name': 'sharp', 'target_name': 'sharp',
'dependencies': [ 'dependencies': [
'<!(node -p "require(\'node-addon-api\').gyp")',
'libvips-cpp' 'libvips-cpp'
], ],
'variables': { 'variables': {
@ -65,11 +66,11 @@
'src/stats.cc', 'src/stats.cc',
'src/operations.cc', 'src/operations.cc',
'src/pipeline.cc', 'src/pipeline.cc',
'src/sharp.cc', 'src/utilities.cc',
'src/utilities.cc' 'src/sharp.cc'
], ],
'include_dirs': [ 'include_dirs': [
'<!(node -e "require(\'nan\')")' '<!@(node -p "require(\'node-addon-api\').include")',
], ],
'conditions': [ 'conditions': [
['use_global_libvips == "true"', { ['use_global_libvips == "true"', {

View File

@ -16,8 +16,7 @@ Lanczos resampling ensures quality is not sacrificed for speed.
As well as image resizing, operations such as As well as image resizing, operations such as
rotation, extraction, compositing and gamma correction are available. rotation, extraction, compositing and gamma correction are available.
Most modern 64-bit macOS, Windows and Linux systems running Most modern 64-bit macOS, Windows and Linux systems running Node.js v10.16.0+
Node versions 10, 12 and 13
do not require any additional install or runtime dependencies. do not require any additional install or runtime dependencies.
[![Test Coverage](https://coveralls.io/repos/lovell/sharp/badge.png?branch=master)](https://coveralls.io/r/lovell/sharp?branch=master) [![Test Coverage](https://coveralls.io/repos/lovell/sharp/badge.png?branch=master)](https://coveralls.io/r/lovell/sharp?branch=master)

View File

@ -1,5 +1,14 @@
# Changelog # Changelog
## v0.25 - *yield*
Requires libvips TBD
### v0.25.0 - TBD
* Migrate internals to N-API.
[#1282](https://github.com/lovell/sharp/issues/1282)
## v0.24 - "*wit*" ## v0.24 - "*wit*"
Requires libvips v8.9.0. Requires libvips v8.9.0.

View File

@ -10,12 +10,12 @@ yarn add sharp
## Prerequisites ## Prerequisites
* Node.js v10.13.0+ * Node.js v10.16.0+
## Prebuilt binaries ## Prebuilt binaries
Ready-compiled sharp and libvips binaries are provided for use with Ready-compiled sharp and libvips binaries are provided for use with
Node.js versions 10, 12 and 13 on the most common platforms: Node.js v10.16.0+ (N-API v4) on the most common platforms:
* macOS x64 (>= 10.13) * macOS x64 (>= 10.13)
* Linux x64 (glibc >= 2.17, musl >= 1.1.24) * Linux x64 (glibc >= 2.17, musl >= 1.1.24)

View File

@ -163,7 +163,7 @@ const Sharp = function (input, options) {
gamma: 0, gamma: 0,
gammaOut: 0, gammaOut: 0,
greyscale: false, greyscale: false,
normalise: 0, normalise: false,
brightness: 1, brightness: 1,
saturation: 1, saturation: 1,
hue: 0, hue: 0,
@ -219,6 +219,11 @@ const Sharp = function (input, options) {
heifCompression: 'hevc', heifCompression: 'hevc',
tileSize: 256, tileSize: 256,
tileOverlap: 0, tileOverlap: 0,
tileContainer: 'fs',
tileLayout: 'dz',
tileFormat: 'last',
tileDepth: 'last',
tileAngle: 0,
tileSkipBlanks: -1, tileSkipBlanks: -1,
tileBackground: [255, 255, 255, 255], tileBackground: [255, 255, 255, 255],
linearA: 1, linearA: 1,

View File

@ -109,7 +109,7 @@
"dependencies": { "dependencies": {
"color": "^3.1.2", "color": "^3.1.2",
"detect-libc": "^1.0.3", "detect-libc": "^1.0.3",
"nan": "^2.14.0", "node-addon-api": "^2.0.0",
"npmlog": "^4.1.2", "npmlog": "^4.1.2",
"prebuild-install": "^5.3.3", "prebuild-install": "^5.3.3",
"semver": "^7.1.3", "semver": "^7.1.3",
@ -138,11 +138,16 @@
"libvips": "8.9.0" "libvips": "8.9.0"
}, },
"engines": { "engines": {
"node": ">=10.13.0" "node": ">=10.16.0"
}, },
"funding": { "funding": {
"url": "https://opencollective.com/libvips" "url": "https://opencollective.com/libvips"
}, },
"binary": {
"napi_versions": [
4
]
},
"semistandard": { "semistandard": {
"env": [ "env": [
"mocha" "mocha"

View File

@ -19,9 +19,7 @@
#include <queue> #include <queue>
#include <mutex> // NOLINT(build/c++11) #include <mutex> // NOLINT(build/c++11)
#include <node.h> #include <napi.h>
#include <node_buffer.h>
#include <nan.h>
#include <vips/vips8> #include <vips/vips8>
#include "common.h" #include "common.h"
@ -30,66 +28,77 @@ using vips::VImage;
namespace sharp { namespace sharp {
// Convenience methods to access the attributes of a v8::Object // Convenience methods to access the attributes of a Napi::Object
bool HasAttr(v8::Local<v8::Object> obj, std::string attr) { bool HasAttr(Napi::Object obj, std::string attr) {
return Nan::Has(obj, Nan::New(attr).ToLocalChecked()).FromJust(); return obj.Has(attr);
} }
std::string AttrAsStr(v8::Local<v8::Object> obj, std::string attr) { std::string AttrAsStr(Napi::Object obj, std::string attr) {
return *Nan::Utf8String(Nan::Get(obj, Nan::New(attr).ToLocalChecked()).ToLocalChecked()); return obj.Get(attr).As<Napi::String>();
} }
std::vector<double> AttrAsRgba(v8::Local<v8::Object> obj, std::string attr) { uint32_t AttrAsUint32(Napi::Object obj, std::string attr) {
v8::Local<v8::Object> background = AttrAs<v8::Object>(obj, attr); return obj.Get(attr).As<Napi::Number>().Uint32Value();
std::vector<double> rgba(4); }
for (unsigned int i = 0; i < 4; i++) { int32_t AttrAsInt32(Napi::Object obj, std::string attr) {
rgba[i] = AttrTo<double>(background, i); return obj.Get(attr).As<Napi::Number>().Int32Value();
}
double AttrAsDouble(Napi::Object obj, std::string attr) {
return obj.Get(attr).As<Napi::Number>().DoubleValue();
}
double AttrAsDouble(Napi::Object obj, unsigned int const attr) {
return obj.Get(attr).As<Napi::Number>().DoubleValue();
}
bool AttrAsBool(Napi::Object obj, std::string attr) {
return obj.Get(attr).As<Napi::Boolean>().Value();
}
std::vector<double> AttrAsRgba(Napi::Object obj, std::string attr) {
Napi::Array background = obj.Get(attr).As<Napi::Array>();
std::vector<double> rgba(background.Length());
for (unsigned int i = 0; i < background.Length(); i++) {
rgba[i] = AttrAsDouble(background, i);
} }
return rgba; return rgba;
} }
// Create an InputDescriptor instance from a v8::Object describing an input image // Create an InputDescriptor instance from a Napi::Object describing an input image
InputDescriptor* CreateInputDescriptor( InputDescriptor* CreateInputDescriptor(Napi::Object input) {
v8::Local<v8::Object> input, std::vector<v8::Local<v8::Object>> &buffersToPersist
) {
Nan::HandleScope();
InputDescriptor *descriptor = new InputDescriptor; InputDescriptor *descriptor = new InputDescriptor;
if (HasAttr(input, "file")) { if (HasAttr(input, "file")) {
descriptor->file = AttrAsStr(input, "file"); descriptor->file = AttrAsStr(input, "file");
} else if (HasAttr(input, "buffer")) { } else if (HasAttr(input, "buffer")) {
v8::Local<v8::Object> buffer = AttrAs<v8::Object>(input, "buffer"); Napi::Buffer<char> buffer = input.Get("buffer").As<Napi::Buffer<char>>();
descriptor->bufferLength = node::Buffer::Length(buffer); descriptor->bufferLength = buffer.Length();
descriptor->buffer = node::Buffer::Data(buffer); descriptor->buffer = buffer.Data();
descriptor->isBuffer = TRUE; descriptor->isBuffer = TRUE;
buffersToPersist.push_back(buffer);
} }
descriptor->failOnError = AttrTo<bool>(input, "failOnError"); descriptor->failOnError = AttrAsBool(input, "failOnError");
// Density for vector-based input // Density for vector-based input
if (HasAttr(input, "density")) { if (HasAttr(input, "density")) {
descriptor->density = AttrTo<double>(input, "density"); descriptor->density = AttrAsDouble(input, "density");
} }
// Raw pixel input // Raw pixel input
if (HasAttr(input, "rawChannels")) { if (HasAttr(input, "rawChannels")) {
descriptor->rawChannels = AttrTo<uint32_t>(input, "rawChannels"); descriptor->rawChannels = AttrAsUint32(input, "rawChannels");
descriptor->rawWidth = AttrTo<uint32_t>(input, "rawWidth"); descriptor->rawWidth = AttrAsUint32(input, "rawWidth");
descriptor->rawHeight = AttrTo<uint32_t>(input, "rawHeight"); descriptor->rawHeight = AttrAsUint32(input, "rawHeight");
} }
// Multi-page input (GIF, TIFF, PDF) // Multi-page input (GIF, TIFF, PDF)
if (HasAttr(input, "pages")) { if (HasAttr(input, "pages")) {
descriptor->pages = AttrTo<int32_t>(input, "pages"); descriptor->pages = AttrAsInt32(input, "pages");
} }
if (HasAttr(input, "page")) { if (HasAttr(input, "page")) {
descriptor->page = AttrTo<uint32_t>(input, "page"); descriptor->page = AttrAsUint32(input, "page");
} }
// Create new image // Create new image
if (HasAttr(input, "createChannels")) { if (HasAttr(input, "createChannels")) {
descriptor->createChannels = AttrTo<uint32_t>(input, "createChannels"); descriptor->createChannels = AttrAsUint32(input, "createChannels");
descriptor->createWidth = AttrTo<uint32_t>(input, "createWidth"); descriptor->createWidth = AttrAsUint32(input, "createWidth");
descriptor->createHeight = AttrTo<uint32_t>(input, "createHeight"); descriptor->createHeight = AttrAsUint32(input, "createHeight");
descriptor->createBackground = AttrAsRgba(input, "createBackground"); descriptor->createBackground = AttrAsRgba(input, "createBackground");
} }
// Limit input images to a given number of pixels, where pixels = width * height // Limit input images to a given number of pixels, where pixels = width * height
descriptor->limitInputPixels = AttrTo<uint32_t>(input, "limitInputPixels"); descriptor->limitInputPixels = AttrAsUint32(input, "limitInputPixels");
// Allow switch from random to sequential access // Allow switch from random to sequential access
descriptor->access = AttrTo<bool>(input, "sequentialRead") ? VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_RANDOM; descriptor->access = AttrAsBool(input, "sequentialRead") ? VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_RANDOM;
return descriptor; return descriptor;
} }
@ -439,11 +448,9 @@ namespace sharp {
/* /*
Called when a Buffer undergoes GC, required to support mixed runtime libraries in Windows Called when a Buffer undergoes GC, required to support mixed runtime libraries in Windows
*/ */
void FreeCallback(char* data, void* hint) { std::function<void(void*, char*)> FreeCallback = [](void*, char* data) {
if (data != nullptr) { g_free(data);
g_free(data); };
}
}
/* /*
Temporary buffer of warnings Temporary buffer of warnings

View File

@ -19,8 +19,7 @@
#include <tuple> #include <tuple>
#include <vector> #include <vector>
#include <node.h> #include <napi.h>
#include <nan.h>
#include <vips/vips8> #include <vips/vips8>
// Verify platform and compiler compatibility // Verify platform and compiler compatibility
@ -82,23 +81,18 @@ namespace sharp {
createBackground{ 0.0, 0.0, 0.0, 255.0 } {} createBackground{ 0.0, 0.0, 0.0, 255.0 } {}
}; };
// Convenience methods to access the attributes of a v8::Object // Convenience methods to access the attributes of a Napi::Object
bool HasAttr(v8::Local<v8::Object> obj, std::string attr); bool HasAttr(Napi::Object obj, std::string attr);
std::string AttrAsStr(v8::Local<v8::Object> obj, std::string attr); std::string AttrAsStr(Napi::Object obj, std::string attr);
std::vector<double> AttrAsRgba(v8::Local<v8::Object> obj, std::string attr); uint32_t AttrAsUint32(Napi::Object obj, std::string attr);
template<typename T> v8::Local<T> AttrAs(v8::Local<v8::Object> obj, std::string attr) { int32_t AttrAsInt32(Napi::Object obj, std::string attr);
return Nan::Get(obj, Nan::New(attr).ToLocalChecked()).ToLocalChecked().As<T>(); double AttrAsDouble(Napi::Object obj, std::string attr);
} double AttrAsDouble(Napi::Object obj, unsigned int const attr);
template<typename T> T AttrTo(v8::Local<v8::Object> obj, std::string attr) { bool AttrAsBool(Napi::Object obj, std::string attr);
return Nan::To<T>(Nan::Get(obj, Nan::New(attr).ToLocalChecked()).ToLocalChecked()).FromJust(); std::vector<double> AttrAsRgba(Napi::Object obj, std::string attr);
}
template<typename T> T AttrTo(v8::Local<v8::Object> obj, int attr) {
return Nan::To<T>(Nan::Get(obj, attr).ToLocalChecked()).FromJust();
}
// Create an InputDescriptor instance from a v8::Object describing an input image // Create an InputDescriptor instance from a Napi::Object describing an input image
InputDescriptor* CreateInputDescriptor( InputDescriptor* CreateInputDescriptor(Napi::Object input);
v8::Local<v8::Object> input, std::vector<v8::Local<v8::Object>> &buffersToPersist); // NOLINT(runtime/references)
enum class ImageType { enum class ImageType {
JPEG, JPEG,
@ -211,7 +205,7 @@ namespace sharp {
/* /*
Called when a Buffer undergoes GC, required to support mixed runtime libraries in Windows Called when a Buffer undergoes GC, required to support mixed runtime libraries in Windows
*/ */
void FreeCallback(char* data, void* hint); extern std::function<void(void*, char*)> FreeCallback;
/* /*
Called with warnings from the glib-registered "VIPS" domain Called with warnings from the glib-registered "VIPS" domain

View File

@ -15,28 +15,16 @@
#include <numeric> #include <numeric>
#include <vector> #include <vector>
#include <node.h> #include <napi.h>
#include <nan.h>
#include <vips/vips8> #include <vips/vips8>
#include "common.h" #include "common.h"
#include "metadata.h" #include "metadata.h"
class MetadataWorker : public Nan::AsyncWorker { class MetadataWorker : public Napi::AsyncWorker {
public: public:
MetadataWorker( MetadataWorker(Napi::Function callback, MetadataBaton *baton, Napi::Function debuglog) :
Nan::Callback *callback, MetadataBaton *baton, Nan::Callback *debuglog, Napi::AsyncWorker(callback), baton(baton), debuglog(Napi::Persistent(debuglog)) {}
std::vector<v8::Local<v8::Object>> const buffersToPersist) :
Nan::AsyncWorker(callback, "sharp:MetadataWorker"),
baton(baton), debuglog(debuglog),
buffersToPersist(buffersToPersist) {
// Protect Buffer objects from GC, keyed on index
std::accumulate(buffersToPersist.begin(), buffersToPersist.end(), 0,
[this](uint32_t index, v8::Local<v8::Object> const buffer) -> uint32_t {
SaveToPersistent(index, buffer);
return index + 1;
});
}
~MetadataWorker() {} ~MetadataWorker() {}
void Execute() { void Execute() {
@ -137,140 +125,114 @@ class MetadataWorker : public Nan::AsyncWorker {
vips_thread_shutdown(); vips_thread_shutdown();
} }
void HandleOKCallback() { void OnOK() {
using Nan::New; Napi::Env env = Env();
using Nan::Set; Napi::HandleScope scope(env);
Nan::HandleScope();
v8::Local<v8::Value> argv[2] = { Nan::Null(), Nan::Null() };
if (!baton->err.empty()) {
argv[0] = Nan::Error(baton->err.data());
} else {
// Metadata Object
v8::Local<v8::Object> info = New<v8::Object>();
Set(info, New("format").ToLocalChecked(), New<v8::String>(baton->format).ToLocalChecked());
if (baton->input->bufferLength > 0) {
Set(info, New("size").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(baton->input->bufferLength)));
}
Set(info, New("width").ToLocalChecked(), New<v8::Uint32>(baton->width));
Set(info, New("height").ToLocalChecked(), New<v8::Uint32>(baton->height));
Set(info, New("space").ToLocalChecked(), New<v8::String>(baton->space).ToLocalChecked());
Set(info, New("channels").ToLocalChecked(), New<v8::Uint32>(baton->channels));
Set(info, New("depth").ToLocalChecked(), New<v8::String>(baton->depth).ToLocalChecked());
if (baton->density > 0) {
Set(info, New("density").ToLocalChecked(), New<v8::Uint32>(baton->density));
}
if (!baton->chromaSubsampling.empty()) {
Set(info,
New("chromaSubsampling").ToLocalChecked(),
New<v8::String>(baton->chromaSubsampling).ToLocalChecked());
}
Set(info, New("isProgressive").ToLocalChecked(), New<v8::Boolean>(baton->isProgressive));
if (baton->paletteBitDepth > 0) {
Set(info, New("paletteBitDepth").ToLocalChecked(), New<v8::Uint32>(baton->paletteBitDepth));
}
if (baton->pages > 0) {
Set(info, New("pages").ToLocalChecked(), New<v8::Uint32>(baton->pages));
}
if (baton->pageHeight > 0) {
Set(info, New("pageHeight").ToLocalChecked(), New<v8::Uint32>(baton->pageHeight));
}
if (baton->loop >= 0) {
Set(info, New("loop").ToLocalChecked(), New<v8::Uint32>(baton->loop));
}
if (!baton->delay.empty()) {
int i = 0;
v8::Local<v8::Array> delay = New<v8::Array>(baton->delay.size());
for (int const d : baton->delay) {
Set(delay, i++, New<v8::Number>(d));
}
Set(info, New("delay").ToLocalChecked(), delay);
}
if (baton->pagePrimary > -1) {
Set(info, New("pagePrimary").ToLocalChecked(), New<v8::Uint32>(baton->pagePrimary));
}
Set(info, New("hasProfile").ToLocalChecked(), New<v8::Boolean>(baton->hasProfile));
Set(info, New("hasAlpha").ToLocalChecked(), New<v8::Boolean>(baton->hasAlpha));
if (baton->orientation > 0) {
Set(info, New("orientation").ToLocalChecked(), New<v8::Uint32>(baton->orientation));
}
if (baton->exifLength > 0) {
Set(info,
New("exif").ToLocalChecked(),
Nan::NewBuffer(baton->exif, baton->exifLength, sharp::FreeCallback, nullptr).ToLocalChecked());
}
if (baton->iccLength > 0) {
Set(info,
New("icc").ToLocalChecked(),
Nan::NewBuffer(baton->icc, baton->iccLength, sharp::FreeCallback, nullptr).ToLocalChecked());
}
if (baton->iptcLength > 0) {
Set(info,
New("iptc").ToLocalChecked(),
Nan::NewBuffer(baton->iptc, baton->iptcLength, sharp::FreeCallback, nullptr).ToLocalChecked());
}
if (baton->xmpLength > 0) {
Set(info,
New("xmp").ToLocalChecked(),
Nan::NewBuffer(baton->xmp, baton->xmpLength, sharp::FreeCallback, nullptr).ToLocalChecked());
}
if (baton->tifftagPhotoshopLength > 0) {
Set(info,
New("tifftagPhotoshop").ToLocalChecked(),
Nan::NewBuffer(baton->tifftagPhotoshop, baton->tifftagPhotoshopLength, sharp::FreeCallback, nullptr)
.ToLocalChecked());
}
argv[1] = info;
}
// Dispose of Persistent wrapper around input Buffers so they can be garbage collected
std::accumulate(buffersToPersist.begin(), buffersToPersist.end(), 0,
[this](uint32_t index, v8::Local<v8::Object> const buffer) -> uint32_t {
GetFromPersistent(index);
return index + 1;
});
delete baton->input;
delete baton;
// Handle warnings // Handle warnings
std::string warning = sharp::VipsWarningPop(); std::string warning = sharp::VipsWarningPop();
while (!warning.empty()) { while (!warning.empty()) {
v8::Local<v8::Value> message[1] = { New(warning).ToLocalChecked() }; debuglog.Call({ Napi::String::New(env, warning) });
debuglog->Call(1, message, async_resource);
warning = sharp::VipsWarningPop(); warning = sharp::VipsWarningPop();
} }
// Return to JavaScript if (baton->err.empty()) {
callback->Call(2, argv, async_resource); Napi::Object info = Napi::Object::New(env);
info.Set("format", baton->format);
if (baton->input->bufferLength > 0) {
info.Set("size", baton->input->bufferLength);
}
info.Set("width", baton->width);
info.Set("height", baton->height);
info.Set("space", baton->space);
info.Set("channels", baton->channels);
info.Set("depth", baton->depth);
if (baton->density > 0) {
info.Set("density", baton->density);
}
if (!baton->chromaSubsampling.empty()) {
info.Set("chromaSubsampling", baton->chromaSubsampling);
}
info.Set("isProgressive", baton->isProgressive);
if (baton->paletteBitDepth > 0) {
info.Set("paletteBitDepth", baton->paletteBitDepth);
}
if (baton->pages > 0) {
info.Set("pages", baton->pages);
}
if (baton->pageHeight > 0) {
info.Set("pageHeight", baton->pageHeight);
}
if (baton->loop >= 0) {
info.Set("loop", baton->loop);
}
if (!baton->delay.empty()) {
int i = 0;
Napi::Array delay = Napi::Array::New(env, static_cast<size_t>(baton->delay.size()));
for (int const d : baton->delay) {
delay.Set(i++, d);
}
info.Set("delay", delay);
}
if (baton->pagePrimary > -1) {
info.Set("pagePrimary", baton->pagePrimary);
}
info.Set("hasProfile", baton->hasProfile);
info.Set("hasAlpha", baton->hasAlpha);
if (baton->orientation > 0) {
info.Set("orientation", baton->orientation);
}
if (baton->exifLength > 0) {
info.Set("exif", Napi::Buffer<char>::New(env, baton->exif, baton->exifLength, sharp::FreeCallback));
}
if (baton->iccLength > 0) {
info.Set("icc", Napi::Buffer<char>::New(env, baton->icc, baton->iccLength, sharp::FreeCallback));
}
if (baton->iptcLength > 0) {
info.Set("iptc", Napi::Buffer<char>::New(env, baton->iptc, baton->iptcLength, sharp::FreeCallback));
}
if (baton->xmpLength > 0) {
info.Set("xmp", Napi::Buffer<char>::New(env, baton->xmp, baton->xmpLength, sharp::FreeCallback));
}
if (baton->tifftagPhotoshopLength > 0) {
info.Set("tifftagPhotoshop",
Napi::Buffer<char>::New(env, baton->tifftagPhotoshop, baton->tifftagPhotoshopLength, sharp::FreeCallback));
}
Callback().MakeCallback(Receiver().Value(), { env.Null(), info });
} else {
Callback().MakeCallback(Receiver().Value(), { Napi::Error::New(env, baton->err).Value() });
}
delete baton->input;
delete baton;
} }
private: private:
MetadataBaton* baton; MetadataBaton* baton;
Nan::Callback *debuglog; Napi::FunctionReference debuglog;
std::vector<v8::Local<v8::Object>> buffersToPersist;
}; };
/* /*
metadata(options, callback) metadata(options, callback)
*/ */
NAN_METHOD(metadata) { Napi::Value metadata(const Napi::CallbackInfo& info) {
// Input Buffers must not undergo GC compaction during processing
std::vector<v8::Local<v8::Object>> buffersToPersist;
// V8 objects are converted to non-V8 types held in the baton struct // V8 objects are converted to non-V8 types held in the baton struct
MetadataBaton *baton = new MetadataBaton; MetadataBaton *baton = new MetadataBaton;
v8::Local<v8::Object> options = info[0].As<v8::Object>(); Napi::Object options = info[0].As<Napi::Object>();
// Input // Input
baton->input = sharp::CreateInputDescriptor(sharp::AttrAs<v8::Object>(options, "input"), buffersToPersist); baton->input = sharp::CreateInputDescriptor(options.Get("input").As<Napi::Object>());
// Function to notify of libvips warnings // Function to notify of libvips warnings
Nan::Callback *debuglog = new Nan::Callback(sharp::AttrAs<v8::Function>(options, "debuglog")); Napi::Function debuglog = options.Get("debuglog").As<Napi::Function>();
// Join queue for worker thread // Join queue for worker thread
Nan::Callback *callback = new Nan::Callback(info[1].As<v8::Function>()); Napi::Function callback = info[1].As<Napi::Function>();
Nan::AsyncQueueWorker(new MetadataWorker(callback, baton, debuglog, buffersToPersist)); MetadataWorker *worker = new MetadataWorker(callback, baton, debuglog);
worker->Queue();
// Increment queued task counter // Increment queued task counter
g_atomic_int_inc(&sharp::counterQueue); g_atomic_int_inc(&sharp::counterQueue);
return info.Env().Undefined();
} }

View File

@ -16,7 +16,7 @@
#define SRC_METADATA_H_ #define SRC_METADATA_H_
#include <string> #include <string>
#include <nan.h> #include <napi.h>
#include "./common.h" #include "./common.h"
@ -81,6 +81,6 @@ struct MetadataBaton {
tifftagPhotoshopLength(0) {} tifftagPhotoshopLength(0) {}
}; };
NAN_METHOD(metadata); Napi::Value metadata(const Napi::CallbackInfo& info);
#endif // SRC_METADATA_H_ #endif // SRC_METADATA_H_

View File

@ -25,8 +25,7 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <vips/vips8> #include <vips/vips8>
#include <node.h> #include <napi.h>
#include <nan.h>
#include "common.h" #include "common.h"
#include "operations.h" #include "operations.h"
@ -46,28 +45,18 @@
#define STAT64_FUNCTION stat64 #define STAT64_FUNCTION stat64
#endif #endif
class PipelineWorker : public Nan::AsyncWorker { class PipelineWorker : public Napi::AsyncWorker {
public: public:
PipelineWorker( PipelineWorker(Napi::Function callback, PipelineBaton *baton,
Nan::Callback *callback, PipelineBaton *baton, Nan::Callback *debuglog, Nan::Callback *queueListener, Napi::Function debuglog, Napi::Function queueListener) :
std::vector<v8::Local<v8::Object>> const buffersToPersist) : Napi::AsyncWorker(callback),
Nan::AsyncWorker(callback, "sharp:PipelineWorker"), baton(baton),
baton(baton), debuglog(debuglog), queueListener(queueListener), debuglog(Napi::Persistent(debuglog)),
buffersToPersist(buffersToPersist) { queueListener(Napi::Persistent(queueListener)) {}
// Protect Buffer objects from GC, keyed on index
std::accumulate(buffersToPersist.begin(), buffersToPersist.end(), 0,
[this](uint32_t index, v8::Local<v8::Object> const buffer) -> uint32_t {
SaveToPersistent(index, buffer);
return index + 1;
});
}
~PipelineWorker() {} ~PipelineWorker() {}
// libuv worker // libuv worker
void Execute() { void Execute() {
using sharp::HasAlpha;
using sharp::ImageType;
// Decrement queued task counter // Decrement queued task counter
g_atomic_int_dec_and_test(&sharp::counterQueue); g_atomic_int_dec_and_test(&sharp::counterQueue);
// Increment processing task counter // Increment processing task counter
@ -76,7 +65,7 @@ class PipelineWorker : public Nan::AsyncWorker {
try { try {
// Open input // Open input
vips::VImage image; vips::VImage image;
ImageType inputImageType; sharp::ImageType inputImageType;
std::tie(image, inputImageType) = sharp::OpenInput(baton->input); std::tie(image, inputImageType) = sharp::OpenInput(baton->input);
// Calculate angle of rotation // Calculate angle of rotation
@ -236,7 +225,7 @@ class PipelineWorker : public Nan::AsyncWorker {
} }
if ( if (
xshrink == yshrink && xshrink >= 2 * shrink_on_load_factor && xshrink == yshrink && xshrink >= 2 * shrink_on_load_factor &&
(inputImageType == ImageType::JPEG || inputImageType == ImageType::WEBP) && (inputImageType == sharp::ImageType::JPEG || inputImageType == sharp::ImageType::WEBP) &&
baton->gamma == 0 && baton->topOffsetPre == -1 && baton->trimThreshold == 0.0 baton->gamma == 0 && baton->topOffsetPre == -1 && baton->trimThreshold == 0.0
) { ) {
if (xshrink >= 8 * shrink_on_load_factor) { if (xshrink >= 8 * shrink_on_load_factor) {
@ -267,7 +256,7 @@ class PipelineWorker : public Nan::AsyncWorker {
->set("fail", baton->input->failOnError); ->set("fail", baton->input->failOnError);
if (baton->input->buffer != nullptr) { if (baton->input->buffer != nullptr) {
VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength); VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength);
if (inputImageType == ImageType::JPEG) { if (inputImageType == sharp::ImageType::JPEG) {
// Reload JPEG buffer // Reload JPEG buffer
image = VImage::jpegload_buffer(blob, option); image = VImage::jpegload_buffer(blob, option);
} else { } else {
@ -276,7 +265,7 @@ class PipelineWorker : public Nan::AsyncWorker {
} }
vips_area_unref(reinterpret_cast<VipsArea*>(blob)); vips_area_unref(reinterpret_cast<VipsArea*>(blob));
} else { } else {
if (inputImageType == ImageType::JPEG) { if (inputImageType == sharp::ImageType::JPEG) {
// Reload JPEG file // Reload JPEG file
image = VImage::jpegload(const_cast<char*>(baton->input->file.data()), option); image = VImage::jpegload(const_cast<char*>(baton->input->file.data()), option);
} else { } else {
@ -320,7 +309,7 @@ class PipelineWorker : public Nan::AsyncWorker {
} }
// Flatten image to remove alpha channel // Flatten image to remove alpha channel
if (baton->flatten && HasAlpha(image)) { if (baton->flatten && sharp::HasAlpha(image)) {
// Scale up 8-bit values to match 16-bit input image // Scale up 8-bit values to match 16-bit input image
double const multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0; double const multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0;
// Background colour // Background colour
@ -356,11 +345,11 @@ class PipelineWorker : public Nan::AsyncWorker {
bool const shouldComposite = !baton->composite.empty(); bool const shouldComposite = !baton->composite.empty();
bool const shouldModulate = baton->brightness != 1.0 || baton->saturation != 1.0 || baton->hue != 0.0; bool const shouldModulate = baton->brightness != 1.0 || baton->saturation != 1.0 || baton->hue != 0.0;
if (shouldComposite && !HasAlpha(image)) { if (shouldComposite && !sharp::HasAlpha(image)) {
image = sharp::EnsureAlpha(image); image = sharp::EnsureAlpha(image);
} }
bool const shouldPremultiplyAlpha = HasAlpha(image) && bool const shouldPremultiplyAlpha = sharp::HasAlpha(image) &&
(shouldResize || shouldBlur || shouldConv || shouldSharpen || shouldComposite); (shouldResize || shouldBlur || shouldConv || shouldSharpen || shouldComposite);
// Premultiply image alpha channel before all transformations to avoid // Premultiply image alpha channel before all transformations to avoid
@ -416,7 +405,7 @@ class PipelineWorker : public Nan::AsyncWorker {
// Join additional color channels to the image // Join additional color channels to the image
if (baton->joinChannelIn.size() > 0) { if (baton->joinChannelIn.size() > 0) {
VImage joinImage; VImage joinImage;
ImageType joinImageType = ImageType::UNKNOWN; sharp::ImageType joinImageType = sharp::ImageType::UNKNOWN;
for (unsigned int i = 0; i < baton->joinChannelIn.size(); i++) { for (unsigned int i = 0; i < baton->joinChannelIn.size(); i++) {
std::tie(joinImage, joinImageType) = sharp::OpenInput(baton->joinChannelIn[i]); std::tie(joinImage, joinImageType) = sharp::OpenInput(baton->joinChannelIn[i]);
@ -548,7 +537,7 @@ class PipelineWorker : public Nan::AsyncWorker {
if (shouldComposite) { if (shouldComposite) {
for (Composite *composite : baton->composite) { for (Composite *composite : baton->composite) {
VImage compositeImage; VImage compositeImage;
ImageType compositeImageType = ImageType::UNKNOWN; sharp::ImageType compositeImageType = sharp::ImageType::UNKNOWN;
std::tie(compositeImage, compositeImageType) = OpenInput(composite->input); std::tie(compositeImage, compositeImageType) = OpenInput(composite->input);
// Verify within current dimensions // Verify within current dimensions
if (compositeImage.width() > image.width() || compositeImage.height() > image.height()) { if (compositeImage.width() > image.width() || compositeImage.height() > image.height()) {
@ -584,7 +573,7 @@ class PipelineWorker : public Nan::AsyncWorker {
} }
// Ensure image to composite is sRGB with premultiplied alpha // Ensure image to composite is sRGB with premultiplied alpha
compositeImage = compositeImage.colourspace(VIPS_INTERPRETATION_sRGB); compositeImage = compositeImage.colourspace(VIPS_INTERPRETATION_sRGB);
if (!HasAlpha(compositeImage)) { if (!sharp::HasAlpha(compositeImage)) {
compositeImage = sharp::EnsureAlpha(compositeImage); compositeImage = sharp::EnsureAlpha(compositeImage);
} }
if (!composite->premultiplied) compositeImage = compositeImage.premultiply(); if (!composite->premultiplied) compositeImage = compositeImage.premultiply();
@ -638,7 +627,7 @@ class PipelineWorker : public Nan::AsyncWorker {
// Apply bitwise boolean operation between images // Apply bitwise boolean operation between images
if (baton->boolean != nullptr) { if (baton->boolean != nullptr) {
VImage booleanImage; VImage booleanImage;
ImageType booleanImageType = ImageType::UNKNOWN; sharp::ImageType booleanImageType = sharp::ImageType::UNKNOWN;
std::tie(booleanImage, booleanImageType) = sharp::OpenInput(baton->boolean); std::tie(booleanImage, booleanImageType) = sharp::OpenInput(baton->boolean);
image = sharp::Boolean(image, booleanImage, baton->booleanOp); image = sharp::Boolean(image, booleanImage, baton->booleanOp);
} }
@ -703,9 +692,9 @@ class PipelineWorker : public Nan::AsyncWorker {
// Output // Output
if (baton->fileOut.empty()) { if (baton->fileOut.empty()) {
// Buffer output // Buffer output
if (baton->formatOut == "jpeg" || (baton->formatOut == "input" && inputImageType == ImageType::JPEG)) { if (baton->formatOut == "jpeg" || (baton->formatOut == "input" && inputImageType == sharp::ImageType::JPEG)) {
// Write JPEG to buffer // Write JPEG to buffer
sharp::AssertImageTypeDimensions(image, ImageType::JPEG); sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
VipsArea *area = VIPS_AREA(image.jpegsave_buffer(VImage::option() VipsArea *area = VIPS_AREA(image.jpegsave_buffer(VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("Q", baton->jpegQuality) ->set("Q", baton->jpegQuality)
@ -727,9 +716,10 @@ class PipelineWorker : public Nan::AsyncWorker {
baton->channels = std::min(baton->channels, 3); baton->channels = std::min(baton->channels, 3);
} }
} else if (baton->formatOut == "png" || (baton->formatOut == "input" && } else if (baton->formatOut == "png" || (baton->formatOut == "input" &&
(inputImageType == ImageType::PNG || inputImageType == ImageType::GIF || inputImageType == ImageType::SVG))) { (inputImageType == sharp::ImageType::PNG || inputImageType == sharp::ImageType::GIF ||
inputImageType == sharp::ImageType::SVG))) {
// Write PNG to buffer // Write PNG to buffer
sharp::AssertImageTypeDimensions(image, ImageType::PNG); sharp::AssertImageTypeDimensions(image, sharp::ImageType::PNG);
VipsArea *area = VIPS_AREA(image.pngsave_buffer(VImage::option() VipsArea *area = VIPS_AREA(image.pngsave_buffer(VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("interlace", baton->pngProgressive) ->set("interlace", baton->pngProgressive)
@ -744,9 +734,10 @@ class PipelineWorker : public Nan::AsyncWorker {
area->free_fn = nullptr; area->free_fn = nullptr;
vips_area_unref(area); vips_area_unref(area);
baton->formatOut = "png"; baton->formatOut = "png";
} else if (baton->formatOut == "webp" || (baton->formatOut == "input" && inputImageType == ImageType::WEBP)) { } else if (baton->formatOut == "webp" ||
(baton->formatOut == "input" && inputImageType == sharp::ImageType::WEBP)) {
// Write WEBP to buffer // Write WEBP to buffer
sharp::AssertImageTypeDimensions(image, ImageType::WEBP); sharp::AssertImageTypeDimensions(image, sharp::ImageType::WEBP);
VipsArea *area = VIPS_AREA(image.webpsave_buffer(VImage::option() VipsArea *area = VIPS_AREA(image.webpsave_buffer(VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("Q", baton->webpQuality) ->set("Q", baton->webpQuality)
@ -760,10 +751,11 @@ class PipelineWorker : public Nan::AsyncWorker {
area->free_fn = nullptr; area->free_fn = nullptr;
vips_area_unref(area); vips_area_unref(area);
baton->formatOut = "webp"; baton->formatOut = "webp";
} else if (baton->formatOut == "tiff" || (baton->formatOut == "input" && inputImageType == ImageType::TIFF)) { } else if (baton->formatOut == "tiff" ||
(baton->formatOut == "input" && inputImageType == sharp::ImageType::TIFF)) {
// Write TIFF to buffer // Write TIFF to buffer
if (baton->tiffCompression == VIPS_FOREIGN_TIFF_COMPRESSION_JPEG) { if (baton->tiffCompression == VIPS_FOREIGN_TIFF_COMPRESSION_JPEG) {
sharp::AssertImageTypeDimensions(image, ImageType::JPEG); sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
baton->channels = std::min(baton->channels, 3); baton->channels = std::min(baton->channels, 3);
} }
// Cast pixel values to float, if required // Cast pixel values to float, if required
@ -787,7 +779,8 @@ class PipelineWorker : public Nan::AsyncWorker {
area->free_fn = nullptr; area->free_fn = nullptr;
vips_area_unref(area); vips_area_unref(area);
baton->formatOut = "tiff"; baton->formatOut = "tiff";
} else if (baton->formatOut == "heif" || (baton->formatOut == "input" && inputImageType == ImageType::HEIF)) { } else if (baton->formatOut == "heif" ||
(baton->formatOut == "input" && inputImageType == sharp::ImageType::HEIF)) {
// Write HEIF to buffer // Write HEIF to buffer
VipsArea *area = VIPS_AREA(image.heifsave_buffer(VImage::option() VipsArea *area = VIPS_AREA(image.heifsave_buffer(VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
@ -799,7 +792,8 @@ class PipelineWorker : public Nan::AsyncWorker {
area->free_fn = nullptr; area->free_fn = nullptr;
vips_area_unref(area); vips_area_unref(area);
baton->formatOut = "heif"; baton->formatOut = "heif";
} else if (baton->formatOut == "raw" || (baton->formatOut == "input" && inputImageType == ImageType::RAW)) { } else if (baton->formatOut == "raw" ||
(baton->formatOut == "input" && inputImageType == sharp::ImageType::RAW)) {
// Write raw, uncompressed image data to buffer // Write raw, uncompressed image data to buffer
if (baton->greyscale || image.interpretation() == VIPS_INTERPRETATION_B_W) { if (baton->greyscale || image.interpretation() == VIPS_INTERPRETATION_B_W) {
// Extract first band for greyscale image // Extract first band for greyscale image
@ -840,9 +834,9 @@ class PipelineWorker : public Nan::AsyncWorker {
bool const mightMatchInput = baton->formatOut == "input"; bool const mightMatchInput = baton->formatOut == "input";
bool const willMatchInput = mightMatchInput && !(isJpeg || isPng || isWebp || isTiff || isDz || isDzZip || isV); bool const willMatchInput = mightMatchInput && !(isJpeg || isPng || isWebp || isTiff || isDz || isDzZip || isV);
if (baton->formatOut == "jpeg" || (mightMatchInput && isJpeg) || if (baton->formatOut == "jpeg" || (mightMatchInput && isJpeg) ||
(willMatchInput && inputImageType == ImageType::JPEG)) { (willMatchInput && inputImageType == sharp::ImageType::JPEG)) {
// Write JPEG to file // Write JPEG to file
sharp::AssertImageTypeDimensions(image, ImageType::JPEG); sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
image.jpegsave(const_cast<char*>(baton->fileOut.data()), VImage::option() image.jpegsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("Q", baton->jpegQuality) ->set("Q", baton->jpegQuality)
@ -856,9 +850,10 @@ class PipelineWorker : public Nan::AsyncWorker {
baton->formatOut = "jpeg"; baton->formatOut = "jpeg";
baton->channels = std::min(baton->channels, 3); baton->channels = std::min(baton->channels, 3);
} else if (baton->formatOut == "png" || (mightMatchInput && isPng) || (willMatchInput && } else if (baton->formatOut == "png" || (mightMatchInput && isPng) || (willMatchInput &&
(inputImageType == ImageType::PNG || inputImageType == ImageType::GIF || inputImageType == ImageType::SVG))) { (inputImageType == sharp::ImageType::PNG || inputImageType == sharp::ImageType::GIF ||
inputImageType == sharp::ImageType::SVG))) {
// Write PNG to file // Write PNG to file
sharp::AssertImageTypeDimensions(image, ImageType::PNG); sharp::AssertImageTypeDimensions(image, sharp::ImageType::PNG);
image.pngsave(const_cast<char*>(baton->fileOut.data()), VImage::option() image.pngsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("interlace", baton->pngProgressive) ->set("interlace", baton->pngProgressive)
@ -870,9 +865,9 @@ class PipelineWorker : public Nan::AsyncWorker {
->set("dither", baton->pngDither)); ->set("dither", baton->pngDither));
baton->formatOut = "png"; baton->formatOut = "png";
} else if (baton->formatOut == "webp" || (mightMatchInput && isWebp) || } else if (baton->formatOut == "webp" || (mightMatchInput && isWebp) ||
(willMatchInput && inputImageType == ImageType::WEBP)) { (willMatchInput && inputImageType == sharp::ImageType::WEBP)) {
// Write WEBP to file // Write WEBP to file
sharp::AssertImageTypeDimensions(image, ImageType::WEBP); sharp::AssertImageTypeDimensions(image, sharp::ImageType::WEBP);
image.webpsave(const_cast<char*>(baton->fileOut.data()), VImage::option() image.webpsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata) ->set("strip", !baton->withMetadata)
->set("Q", baton->webpQuality) ->set("Q", baton->webpQuality)
@ -883,10 +878,10 @@ class PipelineWorker : public Nan::AsyncWorker {
->set("alpha_q", baton->webpAlphaQuality)); ->set("alpha_q", baton->webpAlphaQuality));
baton->formatOut = "webp"; baton->formatOut = "webp";
} else if (baton->formatOut == "tiff" || (mightMatchInput && isTiff) || } else if (baton->formatOut == "tiff" || (mightMatchInput && isTiff) ||
(willMatchInput && inputImageType == ImageType::TIFF)) { (willMatchInput && inputImageType == sharp::ImageType::TIFF)) {
// Write TIFF to file // Write TIFF to file
if (baton->tiffCompression == VIPS_FOREIGN_TIFF_COMPRESSION_JPEG) { if (baton->tiffCompression == VIPS_FOREIGN_TIFF_COMPRESSION_JPEG) {
sharp::AssertImageTypeDimensions(image, ImageType::JPEG); sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG);
baton->channels = std::min(baton->channels, 3); baton->channels = std::min(baton->channels, 3);
} }
image.tiffsave(const_cast<char*>(baton->fileOut.data()), VImage::option() image.tiffsave(const_cast<char*>(baton->fileOut.data()), VImage::option()
@ -903,7 +898,7 @@ class PipelineWorker : public Nan::AsyncWorker {
->set("yres", baton->tiffYres)); ->set("yres", baton->tiffYres));
baton->formatOut = "tiff"; baton->formatOut = "tiff";
} else if (baton->formatOut == "heif" || (mightMatchInput && isHeif) || } else if (baton->formatOut == "heif" || (mightMatchInput && isHeif) ||
(willMatchInput && inputImageType == ImageType::HEIF)) { (willMatchInput && inputImageType == sharp::ImageType::HEIF)) {
// Write HEIF to file // Write HEIF to file
if (sharp::IsAvif(baton->fileOut)) { if (sharp::IsAvif(baton->fileOut)) {
baton->heifCompression = VIPS_FOREIGN_HEIF_COMPRESSION_AV1; baton->heifCompression = VIPS_FOREIGN_HEIF_COMPRESSION_AV1;
@ -954,7 +949,7 @@ class PipelineWorker : public Nan::AsyncWorker {
suffix = AssembleSuffixString(extname, options); suffix = AssembleSuffixString(extname, options);
} }
// Remove alpha channel from tile background if image does not contain an alpha channel // Remove alpha channel from tile background if image does not contain an alpha channel
if (!HasAlpha(image)) { if (!sharp::HasAlpha(image)) {
baton->tileBackground.pop_back(); baton->tileBackground.pop_back();
} }
// Write DZ to file // Write DZ to file
@ -976,7 +971,7 @@ class PipelineWorker : public Nan::AsyncWorker {
image.dzsave(const_cast<char*>(baton->fileOut.data()), options); image.dzsave(const_cast<char*>(baton->fileOut.data()), options);
baton->formatOut = "dz"; baton->formatOut = "dz";
} else if (baton->formatOut == "v" || (mightMatchInput && isV) || } else if (baton->formatOut == "v" || (mightMatchInput && isV) ||
(willMatchInput && inputImageType == ImageType::VIPS)) { (willMatchInput && inputImageType == sharp::ImageType::VIPS)) {
// Write V to file // Write V to file
image.vipssave(const_cast<char*>(baton->fileOut.data()), VImage::option() image.vipssave(const_cast<char*>(baton->fileOut.data()), VImage::option()
->set("strip", !baton->withMetadata)); ->set("strip", !baton->withMetadata));
@ -1000,16 +995,18 @@ class PipelineWorker : public Nan::AsyncWorker {
vips_thread_shutdown(); vips_thread_shutdown();
} }
void HandleOKCallback() { void OnOK() {
using Nan::New; Napi::Env env = Env();
using Nan::Set; Napi::HandleScope scope(env);
Nan::HandleScope();
v8::Local<v8::Value> argv[3] = { Nan::Null(), Nan::Null(), Nan::Null() }; // Handle warnings
if (!baton->err.empty()) { std::string warning = sharp::VipsWarningPop();
// Error while (!warning.empty()) {
argv[0] = Nan::Error(baton->err.data()); debuglog.Call({ Napi::String::New(env, warning) });
} else { warning = sharp::VipsWarningPop();
}
if (baton->err.empty()) {
int width = baton->width; int width = baton->width;
int height = baton->height; int height = baton->height;
if (baton->topOffsetPre != -1 && (baton->width == -1 || baton->height == -1)) { if (baton->topOffsetPre != -1 && (baton->width == -1 || baton->height == -1)) {
@ -1021,50 +1018,40 @@ class PipelineWorker : public Nan::AsyncWorker {
height = baton->heightPost; height = baton->heightPost;
} }
// Info Object // Info Object
v8::Local<v8::Object> info = New<v8::Object>(); Napi::Object info = Napi::Object::New(env);
Set(info, New("format").ToLocalChecked(), New<v8::String>(baton->formatOut).ToLocalChecked()); info.Set("format", baton->formatOut);
Set(info, New("width").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(width))); info.Set("width", static_cast<uint32_t>(width));
Set(info, New("height").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(height))); info.Set("height", static_cast<uint32_t>(height));
Set(info, New("channels").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(baton->channels))); info.Set("channels", static_cast<uint32_t>(baton->channels));
Set(info, New("premultiplied").ToLocalChecked(), New<v8::Boolean>(baton->premultiplied)); info.Set("premultiplied", baton->premultiplied);
if (baton->hasCropOffset) { if (baton->hasCropOffset) {
Set(info, New("cropOffsetLeft").ToLocalChecked(), info.Set("cropOffsetLeft", static_cast<int32_t>(baton->cropOffsetLeft));
New<v8::Int32>(static_cast<int32_t>(baton->cropOffsetLeft))); info.Set("cropOffsetTop", static_cast<int32_t>(baton->cropOffsetTop));
Set(info, New("cropOffsetTop").ToLocalChecked(),
New<v8::Int32>(static_cast<int32_t>(baton->cropOffsetTop)));
} }
if (baton->trimThreshold > 0.0) { if (baton->trimThreshold > 0.0) {
Set(info, New("trimOffsetLeft").ToLocalChecked(), info.Set("trimOffsetLeft", static_cast<int32_t>(baton->trimOffsetLeft));
New<v8::Int32>(static_cast<int32_t>(baton->trimOffsetLeft))); info.Set("trimOffsetTop", static_cast<int32_t>(baton->trimOffsetTop));
Set(info, New("trimOffsetTop").ToLocalChecked(),
New<v8::Int32>(static_cast<int32_t>(baton->trimOffsetTop)));
} }
if (baton->bufferOutLength > 0) { if (baton->bufferOutLength > 0) {
// Pass ownership of output data to Buffer instance
argv[1] = Nan::NewBuffer(
static_cast<char*>(baton->bufferOut), baton->bufferOutLength, sharp::FreeCallback, nullptr)
.ToLocalChecked();
// Add buffer size to info // Add buffer size to info
Set(info, New("size").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(baton->bufferOutLength))); info.Set("size", static_cast<uint32_t>(baton->bufferOutLength));
argv[2] = info; // Pass ownership of output data to Buffer instance
Napi::Buffer<char> data = Napi::Buffer<char>::New(env, static_cast<char*>(baton->bufferOut),
baton->bufferOutLength, sharp::FreeCallback);
Callback().MakeCallback(Receiver().Value(), { env.Null(), data, info });
} else { } else {
// Add file size to info // Add file size to info
struct STAT64_STRUCT st; struct STAT64_STRUCT st;
if (STAT64_FUNCTION(baton->fileOut.data(), &st) == 0) { if (STAT64_FUNCTION(baton->fileOut.data(), &st) == 0) {
Set(info, New("size").ToLocalChecked(), New<v8::Uint32>(static_cast<uint32_t>(st.st_size))); info.Set("size", static_cast<uint32_t>(st.st_size));
} }
argv[1] = info; Callback().MakeCallback(Receiver().Value(), { env.Null(), info });
} }
} else {
Callback().MakeCallback(Receiver().Value(), { Napi::Error::New(env, baton->err).Value() });
} }
// Dispose of Persistent wrapper around input Buffers so they can be garbage collected
std::accumulate(buffersToPersist.begin(), buffersToPersist.end(), 0,
[this](uint32_t index, v8::Local<v8::Object> const buffer) -> uint32_t {
GetFromPersistent(index);
return index + 1;
});
// Delete baton // Delete baton
delete baton->input; delete baton->input;
delete baton->boolean; delete baton->boolean;
@ -1077,29 +1064,16 @@ class PipelineWorker : public Nan::AsyncWorker {
} }
delete baton; delete baton;
// Handle warnings
std::string warning = sharp::VipsWarningPop();
while (!warning.empty()) {
v8::Local<v8::Value> message[1] = { New(warning).ToLocalChecked() };
debuglog->Call(1, message, async_resource);
warning = sharp::VipsWarningPop();
}
// Decrement processing task counter // Decrement processing task counter
g_atomic_int_dec_and_test(&sharp::counterProcess); g_atomic_int_dec_and_test(&sharp::counterProcess);
v8::Local<v8::Value> queueLength[1] = { New<v8::Uint32>(sharp::counterQueue) }; Napi::Number queueLength = Napi::Number::New(env, static_cast<double>(sharp::counterQueue));
queueListener->Call(1, queueLength, async_resource); queueListener.Call(Receiver().Value(), { queueLength });
delete queueListener;
// Return to JavaScript
callback->Call(3, argv, async_resource);
} }
private: private:
PipelineBaton *baton; PipelineBaton *baton;
Nan::Callback *debuglog; Napi::FunctionReference debuglog;
Nan::Callback *queueListener; Napi::FunctionReference queueListener;
std::vector<v8::Local<v8::Object>> buffersToPersist;
/* /*
Calculate the angle of rotation and need-to-flip for the given Exif orientation Calculate the angle of rotation and need-to-flip for the given Exif orientation
@ -1169,37 +1143,27 @@ class PipelineWorker : public Nan::AsyncWorker {
/* /*
pipeline(options, output, callback) pipeline(options, output, callback)
*/ */
NAN_METHOD(pipeline) { Napi::Value pipeline(const Napi::CallbackInfo& info) {
using sharp::HasAttr;
using sharp::AttrTo;
using sharp::AttrAs;
using sharp::AttrAsStr;
using sharp::AttrAsRgba;
using sharp::CreateInputDescriptor;
// Input Buffers must not undergo GC compaction during processing
std::vector<v8::Local<v8::Object>> buffersToPersist;
// V8 objects are converted to non-V8 types held in the baton struct // V8 objects are converted to non-V8 types held in the baton struct
PipelineBaton *baton = new PipelineBaton; PipelineBaton *baton = new PipelineBaton;
v8::Local<v8::Object> options = info[0].As<v8::Object>(); Napi::Object options = info[0].As<Napi::Object>();
// Input // Input
baton->input = CreateInputDescriptor(AttrAs<v8::Object>(options, "input"), buffersToPersist); baton->input = sharp::CreateInputDescriptor(options.Get("input").As<Napi::Object>());
// Extract image options // Extract image options
baton->topOffsetPre = AttrTo<int32_t>(options, "topOffsetPre"); baton->topOffsetPre = sharp::AttrAsInt32(options, "topOffsetPre");
baton->leftOffsetPre = AttrTo<int32_t>(options, "leftOffsetPre"); baton->leftOffsetPre = sharp::AttrAsInt32(options, "leftOffsetPre");
baton->widthPre = AttrTo<int32_t>(options, "widthPre"); baton->widthPre = sharp::AttrAsInt32(options, "widthPre");
baton->heightPre = AttrTo<int32_t>(options, "heightPre"); baton->heightPre = sharp::AttrAsInt32(options, "heightPre");
baton->topOffsetPost = AttrTo<int32_t>(options, "topOffsetPost"); baton->topOffsetPost = sharp::AttrAsInt32(options, "topOffsetPost");
baton->leftOffsetPost = AttrTo<int32_t>(options, "leftOffsetPost"); baton->leftOffsetPost = sharp::AttrAsInt32(options, "leftOffsetPost");
baton->widthPost = AttrTo<int32_t>(options, "widthPost"); baton->widthPost = sharp::AttrAsInt32(options, "widthPost");
baton->heightPost = AttrTo<int32_t>(options, "heightPost"); baton->heightPost = sharp::AttrAsInt32(options, "heightPost");
// Output image dimensions // Output image dimensions
baton->width = AttrTo<int32_t>(options, "width"); baton->width = sharp::AttrAsInt32(options, "width");
baton->height = AttrTo<int32_t>(options, "height"); baton->height = sharp::AttrAsInt32(options, "height");
// Canvas option // Canvas option
std::string canvas = AttrAsStr(options, "canvas"); std::string canvas = sharp::AttrAsStr(options, "canvas");
if (canvas == "crop") { if (canvas == "crop") {
baton->canvas = Canvas::CROP; baton->canvas = Canvas::CROP;
} else if (canvas == "embed") { } else if (canvas == "embed") {
@ -1212,172 +1176,165 @@ NAN_METHOD(pipeline) {
baton->canvas = Canvas::IGNORE_ASPECT; baton->canvas = Canvas::IGNORE_ASPECT;
} }
// Tint chroma // Tint chroma
baton->tintA = AttrTo<double>(options, "tintA"); baton->tintA = sharp::AttrAsDouble(options, "tintA");
baton->tintB = AttrTo<double>(options, "tintB"); baton->tintB = sharp::AttrAsDouble(options, "tintB");
// Composite // Composite
v8::Local<v8::Array> compositeArray = Nan::Get(options, Nan::New("composite").ToLocalChecked()) Napi::Array compositeArray = options.Get("composite").As<Napi::Array>();
.ToLocalChecked().As<v8::Array>(); for (unsigned int i = 0; i < compositeArray.Length(); i++) {
int const compositeArrayLength = AttrTo<uint32_t>(compositeArray, "length"); Napi::Object compositeObject = compositeArray.Get(i).As<Napi::Object>();
for (int i = 0; i < compositeArrayLength; i++) {
v8::Local<v8::Object> compositeObject = Nan::Get(compositeArray, i).ToLocalChecked().As<v8::Object>();
Composite *composite = new Composite; Composite *composite = new Composite;
composite->input = CreateInputDescriptor(AttrAs<v8::Object>(compositeObject, "input"), buffersToPersist); composite->input = sharp::CreateInputDescriptor(compositeObject.Get("input").As<Napi::Object>());
composite->mode = static_cast<VipsBlendMode>( composite->mode = static_cast<VipsBlendMode>(
vips_enum_from_nick(nullptr, VIPS_TYPE_BLEND_MODE, AttrAsStr(compositeObject, "blend").data())); vips_enum_from_nick(nullptr, VIPS_TYPE_BLEND_MODE, sharp::AttrAsStr(compositeObject, "blend").data()));
composite->gravity = AttrTo<uint32_t>(compositeObject, "gravity"); composite->gravity = sharp::AttrAsUint32(compositeObject, "gravity");
composite->left = AttrTo<int32_t>(compositeObject, "left"); composite->left = sharp::AttrAsInt32(compositeObject, "left");
composite->top = AttrTo<int32_t>(compositeObject, "top"); composite->top = sharp::AttrAsInt32(compositeObject, "top");
composite->tile = AttrTo<bool>(compositeObject, "tile"); composite->tile = sharp::AttrAsBool(compositeObject, "tile");
composite->premultiplied = AttrTo<bool>(compositeObject, "premultiplied"); composite->premultiplied = sharp::AttrAsBool(compositeObject, "premultiplied");
baton->composite.push_back(composite); baton->composite.push_back(composite);
} }
// Resize options // Resize options
baton->withoutEnlargement = AttrTo<bool>(options, "withoutEnlargement"); baton->withoutEnlargement = sharp::AttrAsBool(options, "withoutEnlargement");
baton->position = AttrTo<int32_t>(options, "position"); baton->position = sharp::AttrAsInt32(options, "position");
baton->resizeBackground = AttrAsRgba(options, "resizeBackground"); baton->resizeBackground = sharp::AttrAsRgba(options, "resizeBackground");
baton->kernel = AttrAsStr(options, "kernel"); baton->kernel = sharp::AttrAsStr(options, "kernel");
baton->fastShrinkOnLoad = AttrTo<bool>(options, "fastShrinkOnLoad"); baton->fastShrinkOnLoad = sharp::AttrAsBool(options, "fastShrinkOnLoad");
// Join Channel Options // Join Channel Options
if (HasAttr(options, "joinChannelIn")) { if (options.Has("joinChannelIn")) {
v8::Local<v8::Object> joinChannelObject = Nan::Get(options, Nan::New("joinChannelIn").ToLocalChecked()) Napi::Array joinChannelArray = options.Get("joinChannelIn").As<Napi::Array>();
.ToLocalChecked().As<v8::Object>(); for (unsigned int i = 0; i < joinChannelArray.Length(); i++) {
v8::Local<v8::Array> joinChannelArray = joinChannelObject.As<v8::Array>();
int joinChannelArrayLength = AttrTo<int32_t>(joinChannelObject, "length");
for (int i = 0; i < joinChannelArrayLength; i++) {
baton->joinChannelIn.push_back( baton->joinChannelIn.push_back(
CreateInputDescriptor( sharp::CreateInputDescriptor(joinChannelArray.Get(i).As<Napi::Object>()));
Nan::Get(joinChannelArray, i).ToLocalChecked().As<v8::Object>(),
buffersToPersist));
} }
} }
// Operators // Operators
baton->flatten = AttrTo<bool>(options, "flatten"); baton->flatten = sharp::AttrAsBool(options, "flatten");
baton->flattenBackground = AttrAsRgba(options, "flattenBackground"); baton->flattenBackground = sharp::AttrAsRgba(options, "flattenBackground");
baton->negate = AttrTo<bool>(options, "negate"); baton->negate = sharp::AttrAsBool(options, "negate");
baton->blurSigma = AttrTo<double>(options, "blurSigma"); baton->blurSigma = sharp::AttrAsDouble(options, "blurSigma");
baton->brightness = AttrTo<double>(options, "brightness"); baton->brightness = sharp::AttrAsDouble(options, "brightness");
baton->saturation = AttrTo<double>(options, "saturation"); baton->saturation = sharp::AttrAsDouble(options, "saturation");
baton->hue = AttrTo<int32_t>(options, "hue"); baton->hue = sharp::AttrAsInt32(options, "hue");
baton->medianSize = AttrTo<uint32_t>(options, "medianSize"); baton->medianSize = sharp::AttrAsUint32(options, "medianSize");
baton->sharpenSigma = AttrTo<double>(options, "sharpenSigma"); baton->sharpenSigma = sharp::AttrAsDouble(options, "sharpenSigma");
baton->sharpenFlat = AttrTo<double>(options, "sharpenFlat"); baton->sharpenFlat = sharp::AttrAsDouble(options, "sharpenFlat");
baton->sharpenJagged = AttrTo<double>(options, "sharpenJagged"); baton->sharpenJagged = sharp::AttrAsDouble(options, "sharpenJagged");
baton->threshold = AttrTo<int32_t>(options, "threshold"); baton->threshold = sharp::AttrAsInt32(options, "threshold");
baton->thresholdGrayscale = AttrTo<bool>(options, "thresholdGrayscale"); baton->thresholdGrayscale = sharp::AttrAsBool(options, "thresholdGrayscale");
baton->trimThreshold = AttrTo<double>(options, "trimThreshold"); baton->trimThreshold = sharp::AttrAsDouble(options, "trimThreshold");
baton->gamma = AttrTo<double>(options, "gamma"); baton->gamma = sharp::AttrAsDouble(options, "gamma");
baton->gammaOut = AttrTo<double>(options, "gammaOut"); baton->gammaOut = sharp::AttrAsDouble(options, "gammaOut");
baton->linearA = AttrTo<double>(options, "linearA"); baton->linearA = sharp::AttrAsDouble(options, "linearA");
baton->linearB = AttrTo<double>(options, "linearB"); baton->linearB = sharp::AttrAsDouble(options, "linearB");
baton->greyscale = AttrTo<bool>(options, "greyscale"); baton->greyscale = sharp::AttrAsBool(options, "greyscale");
baton->normalise = AttrTo<bool>(options, "normalise"); baton->normalise = sharp::AttrAsBool(options, "normalise");
baton->useExifOrientation = AttrTo<bool>(options, "useExifOrientation"); baton->useExifOrientation = sharp::AttrAsBool(options, "useExifOrientation");
baton->angle = AttrTo<int32_t>(options, "angle"); baton->angle = sharp::AttrAsInt32(options, "angle");
baton->rotationAngle = AttrTo<double>(options, "rotationAngle"); baton->rotationAngle = sharp::AttrAsDouble(options, "rotationAngle");
baton->rotationBackground = AttrAsRgba(options, "rotationBackground"); baton->rotationBackground = sharp::AttrAsRgba(options, "rotationBackground");
baton->rotateBeforePreExtract = AttrTo<bool>(options, "rotateBeforePreExtract"); baton->rotateBeforePreExtract = sharp::AttrAsBool(options, "rotateBeforePreExtract");
baton->flip = AttrTo<bool>(options, "flip"); baton->flip = sharp::AttrAsBool(options, "flip");
baton->flop = AttrTo<bool>(options, "flop"); baton->flop = sharp::AttrAsBool(options, "flop");
baton->extendTop = AttrTo<int32_t>(options, "extendTop"); baton->extendTop = sharp::AttrAsInt32(options, "extendTop");
baton->extendBottom = AttrTo<int32_t>(options, "extendBottom"); baton->extendBottom = sharp::AttrAsInt32(options, "extendBottom");
baton->extendLeft = AttrTo<int32_t>(options, "extendLeft"); baton->extendLeft = sharp::AttrAsInt32(options, "extendLeft");
baton->extendRight = AttrTo<int32_t>(options, "extendRight"); baton->extendRight = sharp::AttrAsInt32(options, "extendRight");
baton->extendBackground = AttrAsRgba(options, "extendBackground"); baton->extendBackground = sharp::AttrAsRgba(options, "extendBackground");
baton->extractChannel = AttrTo<int32_t>(options, "extractChannel"); baton->extractChannel = sharp::AttrAsInt32(options, "extractChannel");
baton->removeAlpha = AttrTo<bool>(options, "removeAlpha"); baton->removeAlpha = sharp::AttrAsBool(options, "removeAlpha");
baton->ensureAlpha = AttrTo<bool>(options, "ensureAlpha"); baton->ensureAlpha = sharp::AttrAsBool(options, "ensureAlpha");
if (HasAttr(options, "boolean")) { if (options.Has("boolean")) {
baton->boolean = CreateInputDescriptor(AttrAs<v8::Object>(options, "boolean"), buffersToPersist); baton->boolean = sharp::CreateInputDescriptor(options.Get("boolean").As<Napi::Object>());
baton->booleanOp = sharp::GetBooleanOperation(AttrAsStr(options, "booleanOp")); baton->booleanOp = sharp::GetBooleanOperation(sharp::AttrAsStr(options, "booleanOp"));
} }
if (HasAttr(options, "bandBoolOp")) { if (options.Has("bandBoolOp")) {
baton->bandBoolOp = sharp::GetBooleanOperation(AttrAsStr(options, "bandBoolOp")); baton->bandBoolOp = sharp::GetBooleanOperation(sharp::AttrAsStr(options, "bandBoolOp"));
} }
if (HasAttr(options, "convKernel")) { if (options.Has("convKernel")) {
v8::Local<v8::Object> kernel = AttrAs<v8::Object>(options, "convKernel"); Napi::Object kernel = options.Get("convKernel").As<Napi::Object>();
baton->convKernelWidth = AttrTo<uint32_t>(kernel, "width"); baton->convKernelWidth = sharp::AttrAsUint32(kernel, "width");
baton->convKernelHeight = AttrTo<uint32_t>(kernel, "height"); baton->convKernelHeight = sharp::AttrAsUint32(kernel, "height");
baton->convKernelScale = AttrTo<double>(kernel, "scale"); baton->convKernelScale = sharp::AttrAsDouble(kernel, "scale");
baton->convKernelOffset = AttrTo<double>(kernel, "offset"); baton->convKernelOffset = sharp::AttrAsDouble(kernel, "offset");
size_t const kernelSize = static_cast<size_t>(baton->convKernelWidth * baton->convKernelHeight); size_t const kernelSize = static_cast<size_t>(baton->convKernelWidth * baton->convKernelHeight);
baton->convKernel = std::unique_ptr<double[]>(new double[kernelSize]); baton->convKernel = std::unique_ptr<double[]>(new double[kernelSize]);
v8::Local<v8::Array> kdata = AttrAs<v8::Array>(kernel, "kernel"); Napi::Array kdata = kernel.Get("kernel").As<Napi::Array>();
for (unsigned int i = 0; i < kernelSize; i++) { for (unsigned int i = 0; i < kernelSize; i++) {
baton->convKernel[i] = AttrTo<double>(kdata, i); baton->convKernel[i] = sharp::AttrAsDouble(kdata, i);
} }
} }
if (HasAttr(options, "recombMatrix")) { if (options.Has("recombMatrix")) {
baton->recombMatrix = std::unique_ptr<double[]>(new double[9]); baton->recombMatrix = std::unique_ptr<double[]>(new double[9]);
v8::Local<v8::Array> recombMatrix = AttrAs<v8::Array>(options, "recombMatrix"); Napi::Array recombMatrix = options.Get("recombMatrix").As<Napi::Array>();
for (unsigned int i = 0; i < 9; i++) { for (unsigned int i = 0; i < 9; i++) {
baton->recombMatrix[i] = AttrTo<double>(recombMatrix, i); baton->recombMatrix[i] = sharp::AttrAsDouble(recombMatrix, i);
} }
} }
baton->colourspace = sharp::GetInterpretation(AttrAsStr(options, "colourspace")); baton->colourspace = sharp::GetInterpretation(sharp::AttrAsStr(options, "colourspace"));
if (baton->colourspace == VIPS_INTERPRETATION_ERROR) { if (baton->colourspace == VIPS_INTERPRETATION_ERROR) {
baton->colourspace = VIPS_INTERPRETATION_sRGB; baton->colourspace = VIPS_INTERPRETATION_sRGB;
} }
// Output // Output
baton->formatOut = AttrAsStr(options, "formatOut"); baton->formatOut = sharp::AttrAsStr(options, "formatOut");
baton->fileOut = AttrAsStr(options, "fileOut"); baton->fileOut = sharp::AttrAsStr(options, "fileOut");
baton->withMetadata = AttrTo<bool>(options, "withMetadata"); baton->withMetadata = sharp::AttrAsBool(options, "withMetadata");
baton->withMetadataOrientation = AttrTo<uint32_t>(options, "withMetadataOrientation"); baton->withMetadataOrientation = sharp::AttrAsUint32(options, "withMetadataOrientation");
// Format-specific // Format-specific
baton->jpegQuality = AttrTo<uint32_t>(options, "jpegQuality"); baton->jpegQuality = sharp::AttrAsUint32(options, "jpegQuality");
baton->jpegProgressive = AttrTo<bool>(options, "jpegProgressive"); baton->jpegProgressive = sharp::AttrAsBool(options, "jpegProgressive");
baton->jpegChromaSubsampling = AttrAsStr(options, "jpegChromaSubsampling"); baton->jpegChromaSubsampling = sharp::AttrAsStr(options, "jpegChromaSubsampling");
baton->jpegTrellisQuantisation = AttrTo<bool>(options, "jpegTrellisQuantisation"); baton->jpegTrellisQuantisation = sharp::AttrAsBool(options, "jpegTrellisQuantisation");
baton->jpegQuantisationTable = AttrTo<uint32_t>(options, "jpegQuantisationTable"); baton->jpegQuantisationTable = sharp::AttrAsUint32(options, "jpegQuantisationTable");
baton->jpegOvershootDeringing = AttrTo<bool>(options, "jpegOvershootDeringing"); baton->jpegOvershootDeringing = sharp::AttrAsBool(options, "jpegOvershootDeringing");
baton->jpegOptimiseScans = AttrTo<bool>(options, "jpegOptimiseScans"); baton->jpegOptimiseScans = sharp::AttrAsBool(options, "jpegOptimiseScans");
baton->jpegOptimiseCoding = AttrTo<bool>(options, "jpegOptimiseCoding"); baton->jpegOptimiseCoding = sharp::AttrAsBool(options, "jpegOptimiseCoding");
baton->pngProgressive = AttrTo<bool>(options, "pngProgressive"); baton->pngProgressive = sharp::AttrAsBool(options, "pngProgressive");
baton->pngCompressionLevel = AttrTo<uint32_t>(options, "pngCompressionLevel"); baton->pngCompressionLevel = sharp::AttrAsUint32(options, "pngCompressionLevel");
baton->pngAdaptiveFiltering = AttrTo<bool>(options, "pngAdaptiveFiltering"); baton->pngAdaptiveFiltering = sharp::AttrAsBool(options, "pngAdaptiveFiltering");
baton->pngPalette = AttrTo<bool>(options, "pngPalette"); baton->pngPalette = sharp::AttrAsBool(options, "pngPalette");
baton->pngQuality = AttrTo<uint32_t>(options, "pngQuality"); baton->pngQuality = sharp::AttrAsUint32(options, "pngQuality");
baton->pngColours = AttrTo<uint32_t>(options, "pngColours"); baton->pngColours = sharp::AttrAsUint32(options, "pngColours");
baton->pngDither = AttrTo<double>(options, "pngDither"); baton->pngDither = sharp::AttrAsDouble(options, "pngDither");
baton->webpQuality = AttrTo<uint32_t>(options, "webpQuality"); baton->webpQuality = sharp::AttrAsUint32(options, "webpQuality");
baton->webpAlphaQuality = AttrTo<uint32_t>(options, "webpAlphaQuality"); baton->webpAlphaQuality = sharp::AttrAsUint32(options, "webpAlphaQuality");
baton->webpLossless = AttrTo<bool>(options, "webpLossless"); baton->webpLossless = sharp::AttrAsBool(options, "webpLossless");
baton->webpNearLossless = AttrTo<bool>(options, "webpNearLossless"); baton->webpNearLossless = sharp::AttrAsBool(options, "webpNearLossless");
baton->webpSmartSubsample = AttrTo<bool>(options, "webpSmartSubsample"); baton->webpSmartSubsample = sharp::AttrAsBool(options, "webpSmartSubsample");
baton->webpReductionEffort = AttrTo<uint32_t>(options, "webpReductionEffort"); baton->webpReductionEffort = sharp::AttrAsUint32(options, "webpReductionEffort");
baton->tiffQuality = AttrTo<uint32_t>(options, "tiffQuality"); baton->tiffQuality = sharp::AttrAsUint32(options, "tiffQuality");
baton->tiffPyramid = AttrTo<bool>(options, "tiffPyramid"); baton->tiffPyramid = sharp::AttrAsBool(options, "tiffPyramid");
baton->tiffSquash = AttrTo<bool>(options, "tiffSquash"); baton->tiffSquash = sharp::AttrAsBool(options, "tiffSquash");
baton->tiffTile = AttrTo<bool>(options, "tiffTile"); baton->tiffTile = sharp::AttrAsBool(options, "tiffTile");
baton->tiffTileWidth = AttrTo<uint32_t>(options, "tiffTileWidth"); baton->tiffTileWidth = sharp::AttrAsUint32(options, "tiffTileWidth");
baton->tiffTileHeight = AttrTo<uint32_t>(options, "tiffTileHeight"); baton->tiffTileHeight = sharp::AttrAsUint32(options, "tiffTileHeight");
baton->tiffXres = AttrTo<double>(options, "tiffXres"); baton->tiffXres = sharp::AttrAsDouble(options, "tiffXres");
baton->tiffYres = AttrTo<double>(options, "tiffYres"); baton->tiffYres = sharp::AttrAsDouble(options, "tiffYres");
// tiff compression options // tiff compression options
baton->tiffCompression = static_cast<VipsForeignTiffCompression>( baton->tiffCompression = static_cast<VipsForeignTiffCompression>(
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_COMPRESSION, vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_COMPRESSION,
AttrAsStr(options, "tiffCompression").data())); sharp::AttrAsStr(options, "tiffCompression").data()));
baton->tiffPredictor = static_cast<VipsForeignTiffPredictor>( baton->tiffPredictor = static_cast<VipsForeignTiffPredictor>(
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_PREDICTOR, vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_PREDICTOR,
AttrAsStr(options, "tiffPredictor").data())); sharp::AttrAsStr(options, "tiffPredictor").data()));
baton->heifQuality = AttrTo<uint32_t>(options, "heifQuality"); baton->heifQuality = sharp::AttrAsUint32(options, "heifQuality");
baton->heifLossless = AttrTo<bool>(options, "heifLossless"); baton->heifLossless = sharp::AttrAsBool(options, "heifLossless");
baton->heifCompression = static_cast<VipsForeignHeifCompression>( baton->heifCompression = static_cast<VipsForeignHeifCompression>(
vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_HEIF_COMPRESSION, vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_HEIF_COMPRESSION,
AttrAsStr(options, "heifCompression").data())); sharp::AttrAsStr(options, "heifCompression").data()));
// Tile output // Tile output
baton->tileSize = AttrTo<uint32_t>(options, "tileSize"); baton->tileSize = sharp::AttrAsUint32(options, "tileSize");
baton->tileOverlap = AttrTo<uint32_t>(options, "tileOverlap"); baton->tileOverlap = sharp::AttrAsUint32(options, "tileOverlap");
std::string tileContainer = AttrAsStr(options, "tileContainer"); baton->tileAngle = sharp::AttrAsInt32(options, "tileAngle");
baton->tileAngle = AttrTo<int32_t>(options, "tileAngle"); baton->tileBackground = sharp::AttrAsRgba(options, "tileBackground");
baton->tileBackground = AttrAsRgba(options, "tileBackground"); baton->tileSkipBlanks = sharp::AttrAsInt32(options, "tileSkipBlanks");
baton->tileSkipBlanks = AttrTo<int32_t>(options, "tileSkipBlanks"); std::string tileContainer = sharp::AttrAsStr(options, "tileContainer");
if (tileContainer == "zip") { if (tileContainer == "zip") {
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP; baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP;
} else { } else {
baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_FS; baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_FS;
} }
std::string tileLayout = AttrAsStr(options, "tileLayout"); std::string tileLayout = sharp::AttrAsStr(options, "tileLayout");
if (tileLayout == "google") { if (tileLayout == "google") {
baton->tileLayout = VIPS_FOREIGN_DZ_LAYOUT_GOOGLE; baton->tileLayout = VIPS_FOREIGN_DZ_LAYOUT_GOOGLE;
} else if (tileLayout == "zoomify") { } else if (tileLayout == "zoomify") {
@ -1385,8 +1342,8 @@ NAN_METHOD(pipeline) {
} else { } else {
baton->tileLayout = VIPS_FOREIGN_DZ_LAYOUT_DZ; baton->tileLayout = VIPS_FOREIGN_DZ_LAYOUT_DZ;
} }
baton->tileFormat = AttrAsStr(options, "tileFormat"); baton->tileFormat = sharp::AttrAsStr(options, "tileFormat");
std::string tileDepth = AttrAsStr(options, "tileDepth"); std::string tileDepth = sharp::AttrAsStr(options, "tileDepth");
if (tileDepth == "onetile") { if (tileDepth == "onetile") {
baton->tileDepth = VIPS_FOREIGN_DZ_DEPTH_ONETILE; baton->tileDepth = VIPS_FOREIGN_DZ_DEPTH_ONETILE;
} else if (tileDepth == "one") { } else if (tileDepth == "one") {
@ -1413,18 +1370,20 @@ NAN_METHOD(pipeline) {
} }
// Function to notify of libvips warnings // Function to notify of libvips warnings
Nan::Callback *debuglog = new Nan::Callback(AttrAs<v8::Function>(options, "debuglog")); Napi::Function debuglog = options.Get("debuglog").As<Napi::Function>();
// Function to notify of queue length changes // Function to notify of queue length changes
Nan::Callback *queueListener = new Nan::Callback(AttrAs<v8::Function>(options, "queueListener")); Napi::Function queueListener = options.Get("queueListener").As<Napi::Function>();
// Join queue for worker thread // Join queue for worker thread
Nan::Callback *callback = new Nan::Callback(info[1].As<v8::Function>()); Napi::Function callback = info[1].As<Napi::Function>();
Nan::AsyncQueueWorker(new PipelineWorker(callback, baton, debuglog, queueListener, buffersToPersist)); PipelineWorker *worker = new PipelineWorker(callback, baton, debuglog, queueListener);
worker->Queue();
// Increment queued task counter // Increment queued task counter
g_atomic_int_inc(&sharp::counterQueue); g_atomic_int_inc(&sharp::counterQueue);
v8::Local<v8::Value> queueLength[1] = { Nan::New<v8::Uint32>(sharp::counterQueue) }; Napi::Number queueLength = Napi::Number::New(info.Env(), static_cast<double>(sharp::counterQueue));
v8::Local<v8::Object> recv = Nan::New<v8::Object>(); queueListener.Call(info.This(), { queueLength });
Nan::Call(*queueListener, recv, 1, queueLength);
return info.Env().Undefined();
} }

View File

@ -19,12 +19,12 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include <nan.h> #include <napi.h>
#include <vips/vips8> #include <vips/vips8>
#include "./common.h" #include "./common.h"
NAN_METHOD(pipeline); Napi::Value pipeline(const Napi::CallbackInfo& info);
enum class Canvas { enum class Canvas {
CROP, CROP,

View File

@ -12,8 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#include <node.h> #include <napi.h>
#include <nan.h>
#include <vips/vips8> #include <vips/vips8>
#include "common.h" #include "common.h"
@ -22,33 +21,24 @@
#include "utilities.h" #include "utilities.h"
#include "stats.h" #include "stats.h"
NAN_MODULE_INIT(init) { Napi::Object init(Napi::Env env, Napi::Object exports) {
vips_init("sharp"); vips_init("sharp");
g_log_set_handler("VIPS", static_cast<GLogLevelFlags>(G_LOG_LEVEL_WARNING), g_log_set_handler("VIPS", static_cast<GLogLevelFlags>(G_LOG_LEVEL_WARNING),
static_cast<GLogFunc>(sharp::VipsWarningCallback), nullptr); static_cast<GLogFunc>(sharp::VipsWarningCallback), nullptr);
// Methods available to JavaScript // Methods available to JavaScript
Nan::Set(target, Nan::New("metadata").ToLocalChecked(), exports.Set("metadata", Napi::Function::New(env, metadata));
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(metadata)).ToLocalChecked()); exports.Set("pipeline", Napi::Function::New(env, pipeline));
Nan::Set(target, Nan::New("pipeline").ToLocalChecked(), exports.Set("cache", Napi::Function::New(env, cache));
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(pipeline)).ToLocalChecked()); exports.Set("concurrency", Napi::Function::New(env, concurrency));
Nan::Set(target, Nan::New("cache").ToLocalChecked(), exports.Set("counters", Napi::Function::New(env, counters));
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(cache)).ToLocalChecked()); exports.Set("simd", Napi::Function::New(env, simd));
Nan::Set(target, Nan::New("concurrency").ToLocalChecked(), exports.Set("libvipsVersion", Napi::Function::New(env, libvipsVersion));
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(concurrency)).ToLocalChecked()); exports.Set("format", Napi::Function::New(env, format));
Nan::Set(target, Nan::New("counters").ToLocalChecked(), exports.Set("_maxColourDistance", Napi::Function::New(env, _maxColourDistance));
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(counters)).ToLocalChecked()); exports.Set("stats", Napi::Function::New(env, stats));
Nan::Set(target, Nan::New("simd").ToLocalChecked(), return exports;
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(simd)).ToLocalChecked());
Nan::Set(target, Nan::New("libvipsVersion").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(libvipsVersion)).ToLocalChecked());
Nan::Set(target, Nan::New("format").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(format)).ToLocalChecked());
Nan::Set(target, Nan::New("_maxColourDistance").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(_maxColourDistance)).ToLocalChecked());
Nan::Set(target, Nan::New("stats").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(stats)).ToLocalChecked());
} }
NAN_MODULE_WORKER_ENABLED(sharp, init) NODE_API_MODULE(sharp, init)

View File

@ -16,28 +16,16 @@
#include <vector> #include <vector>
#include <iostream> #include <iostream>
#include <node.h> #include <napi.h>
#include <nan.h>
#include <vips/vips8> #include <vips/vips8>
#include "common.h" #include "common.h"
#include "stats.h" #include "stats.h"
class StatsWorker : public Nan::AsyncWorker { class StatsWorker : public Napi::AsyncWorker {
public: public:
StatsWorker( StatsWorker(Napi::Function callback, StatsBaton *baton, Napi::Function debuglog) :
Nan::Callback *callback, StatsBaton *baton, Nan::Callback *debuglog, Napi::AsyncWorker(callback), baton(baton), debuglog(Napi::Persistent(debuglog)) {}
std::vector<v8::Local<v8::Object>> const buffersToPersist) :
Nan::AsyncWorker(callback, "sharp:StatsWorker"),
baton(baton), debuglog(debuglog),
buffersToPersist(buffersToPersist) {
// Protect Buffer objects from GC, keyed on index
std::accumulate(buffersToPersist.begin(), buffersToPersist.end(), 0,
[this](uint32_t index, v8::Local<v8::Object> const buffer) -> uint32_t {
SaveToPersistent(index, buffer);
return index + 1;
});
}
~StatsWorker() {} ~StatsWorker() {}
const int STAT_MIN_INDEX = 0; const int STAT_MIN_INDEX = 0;
@ -54,13 +42,9 @@ class StatsWorker : public Nan::AsyncWorker {
void Execute() { void Execute() {
// Decrement queued task counter // Decrement queued task counter
g_atomic_int_dec_and_test(&sharp::counterQueue); g_atomic_int_dec_and_test(&sharp::counterQueue);
using Nan::New;
using Nan::Set;
using sharp::MaximumImageAlpha;
vips::VImage image; vips::VImage image;
sharp::ImageType imageType = sharp::ImageType::UNKNOWN; sharp::ImageType imageType = sharp::ImageType::UNKNOWN;
try { try {
std::tie(image, imageType) = OpenInput(baton->input); std::tie(image, imageType) = OpenInput(baton->input);
} catch (vips::VError const &err) { } catch (vips::VError const &err) {
@ -71,20 +55,23 @@ class StatsWorker : public Nan::AsyncWorker {
vips::VImage stats = image.stats(); vips::VImage stats = image.stats();
int const bands = image.bands(); int const bands = image.bands();
for (int b = 1; b <= bands; b++) { for (int b = 1; b <= bands; b++) {
ChannelStats cStats(static_cast<int>(stats.getpoint(STAT_MIN_INDEX, b).front()), ChannelStats cStats(
static_cast<int>(stats.getpoint(STAT_MAX_INDEX, b).front()), static_cast<int>(stats.getpoint(STAT_MIN_INDEX, b).front()),
stats.getpoint(STAT_SUM_INDEX, b).front(), stats.getpoint(STAT_SQ_SUM_INDEX, b).front(), static_cast<int>(stats.getpoint(STAT_MAX_INDEX, b).front()),
stats.getpoint(STAT_MEAN_INDEX, b).front(), stats.getpoint(STAT_STDEV_INDEX, b).front(), stats.getpoint(STAT_SUM_INDEX, b).front(),
static_cast<int>(stats.getpoint(STAT_MINX_INDEX, b).front()), stats.getpoint(STAT_SQ_SUM_INDEX, b).front(),
static_cast<int>(stats.getpoint(STAT_MINY_INDEX, b).front()), stats.getpoint(STAT_MEAN_INDEX, b).front(),
static_cast<int>(stats.getpoint(STAT_MAXX_INDEX, b).front()), stats.getpoint(STAT_STDEV_INDEX, b).front(),
static_cast<int>(stats.getpoint(STAT_MAXY_INDEX, b).front())); static_cast<int>(stats.getpoint(STAT_MINX_INDEX, b).front()),
static_cast<int>(stats.getpoint(STAT_MINY_INDEX, b).front()),
static_cast<int>(stats.getpoint(STAT_MAXX_INDEX, b).front()),
static_cast<int>(stats.getpoint(STAT_MAXY_INDEX, b).front()));
baton->channelStats.push_back(cStats); baton->channelStats.push_back(cStats);
} }
// Image is not opaque when alpha layer is present and contains a non-mamixa value // Image is not opaque when alpha layer is present and contains a non-mamixa value
if (sharp::HasAlpha(image)) { if (sharp::HasAlpha(image)) {
double const minAlpha = static_cast<double>(stats.getpoint(STAT_MIN_INDEX, bands).front()); double const minAlpha = static_cast<double>(stats.getpoint(STAT_MIN_INDEX, bands).front());
if (minAlpha != MaximumImageAlpha(image.interpretation())) { if (minAlpha != sharp::MaximumImageAlpha(image.interpretation())) {
baton->isOpaque = false; baton->isOpaque = false;
} }
} }
@ -100,92 +87,77 @@ class StatsWorker : public Nan::AsyncWorker {
vips_thread_shutdown(); vips_thread_shutdown();
} }
void HandleOKCallback() { void OnOK() {
using Nan::New; Napi::Env env = Env();
using Nan::Set; Napi::HandleScope scope(env);
Nan::HandleScope();
v8::Local<v8::Value> argv[2] = { Nan::Null(), Nan::Null() };
if (!baton->err.empty()) {
argv[0] = Nan::Error(baton->err.data());
} else {
// Stats Object
v8::Local<v8::Object> info = New<v8::Object>();
v8::Local<v8::Array> channels = New<v8::Array>();
std::vector<ChannelStats>::iterator it;
int i = 0;
for (it = baton->channelStats.begin(); it < baton->channelStats.end(); it++, i++) {
v8::Local<v8::Object> channelStat = New<v8::Object>();
Set(channelStat, New("min").ToLocalChecked(), New<v8::Number>(it->min));
Set(channelStat, New("max").ToLocalChecked(), New<v8::Number>(it->max));
Set(channelStat, New("sum").ToLocalChecked(), New<v8::Number>(it->sum));
Set(channelStat, New("squaresSum").ToLocalChecked(), New<v8::Number>(it->squaresSum));
Set(channelStat, New("mean").ToLocalChecked(), New<v8::Number>(it->mean));
Set(channelStat, New("stdev").ToLocalChecked(), New<v8::Number>(it->stdev));
Set(channelStat, New("minX").ToLocalChecked(), New<v8::Number>(it->minX));
Set(channelStat, New("minY").ToLocalChecked(), New<v8::Number>(it->minY));
Set(channelStat, New("maxX").ToLocalChecked(), New<v8::Number>(it->maxX));
Set(channelStat, New("maxY").ToLocalChecked(), New<v8::Number>(it->maxY));
Set(channels, i, channelStat);
}
Set(info, New("channels").ToLocalChecked(), channels);
Set(info, New("isOpaque").ToLocalChecked(), New<v8::Boolean>(baton->isOpaque));
Set(info, New("entropy").ToLocalChecked(), New<v8::Number>(baton->entropy));
argv[1] = info;
}
// Dispose of Persistent wrapper around input Buffers so they can be garbage collected
std::accumulate(buffersToPersist.begin(), buffersToPersist.end(), 0,
[this](uint32_t index, v8::Local<v8::Object> const buffer) -> uint32_t {
GetFromPersistent(index);
return index + 1;
});
delete baton->input;
delete baton;
// Handle warnings // Handle warnings
std::string warning = sharp::VipsWarningPop(); std::string warning = sharp::VipsWarningPop();
while (!warning.empty()) { while (!warning.empty()) {
v8::Local<v8::Value> message[1] = { New(warning).ToLocalChecked() }; debuglog.Call({ Napi::String::New(env, warning) });
debuglog->Call(1, message, async_resource);
warning = sharp::VipsWarningPop(); warning = sharp::VipsWarningPop();
} }
// Return to JavaScript if (baton->err.empty()) {
callback->Call(2, argv, async_resource); // Stats Object
Napi::Object info = Napi::Object::New(env);
Napi::Array channels = Napi::Array::New(env);
std::vector<ChannelStats>::iterator it;
int i = 0;
for (it = baton->channelStats.begin(); it < baton->channelStats.end(); it++, i++) {
Napi::Object channelStat = Napi::Object::New(env);
channelStat.Set("min", it->min);
channelStat.Set("max", it->max);
channelStat.Set("sum", it->sum);
channelStat.Set("squaresSum", it->squaresSum);
channelStat.Set("mean", it->mean);
channelStat.Set("stdev", it->stdev);
channelStat.Set("minX", it->minX);
channelStat.Set("minY", it->minY);
channelStat.Set("maxX", it->maxX);
channelStat.Set("maxY", it->maxY);
channels.Set(i, channelStat);
}
info.Set("channels", channels);
info.Set("isOpaque", baton->isOpaque);
info.Set("entropy", baton->entropy);
Callback().MakeCallback(Receiver().Value(), { env.Null(), info });
} else {
Callback().MakeCallback(Receiver().Value(), { Napi::Error::New(env, baton->err).Value() });
}
delete baton->input;
delete baton;
} }
private: private:
StatsBaton* baton; StatsBaton* baton;
Nan::Callback *debuglog; Napi::FunctionReference debuglog;
std::vector<v8::Local<v8::Object>> buffersToPersist;
}; };
/* /*
stats(options, callback) stats(options, callback)
*/ */
NAN_METHOD(stats) { Napi::Value stats(const Napi::CallbackInfo& info) {
using sharp::AttrTo;
// Input Buffers must not undergo GC compaction during processing
std::vector<v8::Local<v8::Object>> buffersToPersist;
// V8 objects are converted to non-V8 types held in the baton struct // V8 objects are converted to non-V8 types held in the baton struct
StatsBaton *baton = new StatsBaton; StatsBaton *baton = new StatsBaton;
v8::Local<v8::Object> options = info[0].As<v8::Object>(); Napi::Object options = info[0].As<Napi::Object>();
// Input // Input
baton->input = sharp::CreateInputDescriptor(sharp::AttrAs<v8::Object>(options, "input"), buffersToPersist); baton->input = sharp::CreateInputDescriptor(options.Get("input").As<Napi::Object>());
// Function to notify of libvips warnings // Function to notify of libvips warnings
Nan::Callback *debuglog = new Nan::Callback(sharp::AttrAs<v8::Function>(options, "debuglog")); Napi::Function debuglog = options.Get("debuglog").As<Napi::Function>();
// Join queue for worker thread // Join queue for worker thread
Nan::Callback *callback = new Nan::Callback(info[1].As<v8::Function>()); Napi::Function callback = info[1].As<Napi::Function>();
Nan::AsyncQueueWorker(new StatsWorker(callback, baton, debuglog, buffersToPersist)); StatsWorker *worker = new StatsWorker(callback, baton, debuglog);
worker->Queue();
// Increment queued task counter // Increment queued task counter
g_atomic_int_inc(&sharp::counterQueue); g_atomic_int_inc(&sharp::counterQueue);
return info.Env().Undefined();
} }

View File

@ -16,7 +16,7 @@
#define SRC_STATS_H_ #define SRC_STATS_H_
#include <string> #include <string>
#include <nan.h> #include <napi.h>
#include "./common.h" #include "./common.h"
@ -33,12 +33,8 @@ struct ChannelStats {
int maxX; int maxX;
int maxY; int maxY;
ChannelStats(): ChannelStats(int minVal, int maxVal, double sumVal, double squaresSumVal,
min(0), max(0), sum(0), squaresSum(0), mean(0), stdev(0) double meanVal, double stdevVal, int minXVal, int minYVal, int maxXVal, int maxYVal):
, minX(0), minY(0), maxX(0), maxY(0) {}
ChannelStats(int minVal, int maxVal, double sumVal, double squaresSumVal,
double meanVal, double stdevVal, int minXVal, int minYVal, int maxXVal, int maxYVal):
min(minVal), max(maxVal), sum(sumVal), squaresSum(squaresSumVal), min(minVal), max(maxVal), sum(sumVal), squaresSum(squaresSumVal),
mean(meanVal), stdev(stdevVal), minX(minXVal), minY(minYVal), maxX(maxXVal), maxY(maxYVal) {} mean(meanVal), stdev(stdevVal), minX(minXVal), minY(minYVal), maxX(maxXVal), maxY(maxYVal) {}
}; };
@ -61,6 +57,6 @@ struct StatsBaton {
{} {}
}; };
NAN_METHOD(stats); Napi::Value stats(const Napi::CallbackInfo& info);
#endif // SRC_STATS_H_ #endif // SRC_STATS_H_

View File

@ -15,8 +15,7 @@
#include <cmath> #include <cmath>
#include <string> #include <string>
#include <node.h> #include <napi.h>
#include <nan.h>
#include <vips/vips8> #include <vips/vips8>
#include <vips/vector.h> #include <vips/vector.h>
@ -24,183 +23,145 @@
#include "operations.h" #include "operations.h"
#include "utilities.h" #include "utilities.h"
using v8::Boolean;
using v8::Integer;
using v8::Local;
using v8::Number;
using v8::Object;
using v8::String;
using Nan::HandleScope;
using Nan::New;
using Nan::Set;
using Nan::ThrowError;
using Nan::To;
using Nan::Utf8String;
/* /*
Get and set cache limits Get and set cache limits
*/ */
NAN_METHOD(cache) { Napi::Value cache(const Napi::CallbackInfo& info) {
HandleScope(); Napi::Env env = info.Env();
// Set memory limit // Set memory limit
if (info[0]->IsInt32()) { if (info[0].IsNumber()) {
vips_cache_set_max_mem(To<int32_t>(info[0]).FromJust() * 1048576); vips_cache_set_max_mem(info[0].As<Napi::Number>().Int32Value() * 1048576);
} }
// Set file limit // Set file limit
if (info[1]->IsInt32()) { if (info[1].IsNumber()) {
vips_cache_set_max_files(To<int32_t>(info[1]).FromJust()); vips_cache_set_max_files(info[1].As<Napi::Number>().Int32Value());
} }
// Set items limit // Set items limit
if (info[2]->IsInt32()) { if (info[2].IsNumber()) {
vips_cache_set_max(To<int32_t>(info[2]).FromJust()); vips_cache_set_max(info[2].As<Napi::Number>().Int32Value());
} }
// Get memory stats // Get memory stats
Local<Object> memory = New<Object>(); Napi::Object memory = Napi::Object::New(env);
Set(memory, New("current").ToLocalChecked(), memory.Set("current", round(vips_tracked_get_mem() / 1048576));
New<Integer>(static_cast<int>(round(vips_tracked_get_mem() / 1048576)))); memory.Set("high", round(vips_tracked_get_mem_highwater() / 1048576));
Set(memory, New("high").ToLocalChecked(), memory.Set("max", round(vips_cache_get_max_mem() / 1048576));
New<Integer>(static_cast<int>(round(vips_tracked_get_mem_highwater() / 1048576))));
Set(memory, New("max").ToLocalChecked(),
New<Integer>(static_cast<int>(round(vips_cache_get_max_mem() / 1048576))));
// Get file stats // Get file stats
Local<Object> files = New<Object>(); Napi::Object files = Napi::Object::New(env);
Set(files, New("current").ToLocalChecked(), New<Integer>(vips_tracked_get_files())); files.Set("current", vips_tracked_get_files());
Set(files, New("max").ToLocalChecked(), New<Integer>(vips_cache_get_max_files())); files.Set("max", vips_cache_get_max_files());
// Get item stats // Get item stats
Local<Object> items = New<Object>(); Napi::Object items = Napi::Object::New(env);
Set(items, New("current").ToLocalChecked(), New<Integer>(vips_cache_get_size())); items.Set("current", vips_cache_get_size());
Set(items, New("max").ToLocalChecked(), New<Integer>(vips_cache_get_max())); items.Set("max", vips_cache_get_max());
Local<Object> cache = New<Object>(); Napi::Object cache = Napi::Object::New(env);
Set(cache, New("memory").ToLocalChecked(), memory); cache.Set("memory", memory);
Set(cache, New("files").ToLocalChecked(), files); cache.Set("files", files);
Set(cache, New("items").ToLocalChecked(), items); cache.Set("items", items);
info.GetReturnValue().Set(cache); return cache;
} }
/* /*
Get and set size of thread pool Get and set size of thread pool
*/ */
NAN_METHOD(concurrency) { Napi::Value concurrency(const Napi::CallbackInfo& info) {
HandleScope();
// Set concurrency // Set concurrency
if (info[0]->IsInt32()) { if (info[0].IsNumber()) {
vips_concurrency_set(To<int32_t>(info[0]).FromJust()); vips_concurrency_set(info[0].As<Napi::Number>().Int32Value());
} }
// Get concurrency // Get concurrency
info.GetReturnValue().Set(New<Integer>(vips_concurrency_get())); return Napi::Number::New(info.Env(), vips_concurrency_get());
} }
/* /*
Get internal counters (queued tasks, processing tasks) Get internal counters (queued tasks, processing tasks)
*/ */
NAN_METHOD(counters) { Napi::Value counters(const Napi::CallbackInfo& info) {
using sharp::counterProcess; Napi::Object counters = Napi::Object::New(info.Env());
using sharp::counterQueue; counters.Set("queue", sharp::counterQueue);
counters.Set("process", sharp::counterProcess);
HandleScope(); return counters;
Local<Object> counters = New<Object>();
Set(counters, New("queue").ToLocalChecked(), New<Integer>(counterQueue));
Set(counters, New("process").ToLocalChecked(), New<Integer>(counterProcess));
info.GetReturnValue().Set(counters);
} }
/* /*
Get and set use of SIMD vector unit instructions Get and set use of SIMD vector unit instructions
*/ */
NAN_METHOD(simd) { Napi::Value simd(const Napi::CallbackInfo& info) {
HandleScope();
// Set state // Set state
if (info[0]->IsBoolean()) { if (info[0].IsBoolean()) {
vips_vector_set_enabled(To<bool>(info[0]).FromJust()); vips_vector_set_enabled(info[0].As<Napi::Boolean>().Value());
} }
// Get state // Get state
info.GetReturnValue().Set(New<Boolean>(vips_vector_isenabled())); return Napi::Boolean::New(info.Env(), vips_vector_isenabled());
} }
/* /*
Get libvips version Get libvips version
*/ */
NAN_METHOD(libvipsVersion) { Napi::Value libvipsVersion(const Napi::CallbackInfo& info) {
HandleScope();
char version[9]; char version[9];
g_snprintf(version, sizeof(version), "%d.%d.%d", vips_version(0), vips_version(1), vips_version(2)); g_snprintf(version, sizeof(version), "%d.%d.%d", vips_version(0), vips_version(1), vips_version(2));
info.GetReturnValue().Set(New(version).ToLocalChecked()); return Napi::String::New(info.Env(), version);
} }
/* /*
Get available input/output file/buffer/stream formats Get available input/output file/buffer/stream formats
*/ */
NAN_METHOD(format) { Napi::Value format(const Napi::CallbackInfo& info) {
HandleScope(); Napi::Env env = info.Env();
Napi::Object format = Napi::Object::New(env);
// Attribute names
Local<String> attrId = New("id").ToLocalChecked();
Local<String> attrInput = New("input").ToLocalChecked();
Local<String> attrOutput = New("output").ToLocalChecked();
Local<String> attrFile = New("file").ToLocalChecked();
Local<String> attrBuffer = New("buffer").ToLocalChecked();
Local<String> attrStream = New("stream").ToLocalChecked();
// Which load/save operations are available for each compressed format?
Local<Object> format = New<Object>();
for (std::string const f : { for (std::string const f : {
"jpeg", "png", "webp", "tiff", "magick", "openslide", "dz", "jpeg", "png", "webp", "tiff", "magick", "openslide", "dz",
"ppm", "fits", "gif", "svg", "heif", "pdf", "vips" "ppm", "fits", "gif", "svg", "heif", "pdf", "vips"
}) { }) {
// Input // Input
Local<Boolean> hasInputFile = Napi::Boolean hasInputFile =
New<Boolean>(vips_type_find("VipsOperation", (f + "load").c_str())); Napi::Boolean::New(env, vips_type_find("VipsOperation", (f + "load").c_str()));
Local<Boolean> hasInputBuffer = Napi::Boolean hasInputBuffer =
New<Boolean>(vips_type_find("VipsOperation", (f + "load_buffer").c_str())); Napi::Boolean::New(env, vips_type_find("VipsOperation", (f + "load_buffer").c_str()));
Local<Object> input = New<Object>(); Napi::Object input = Napi::Object::New(env);
Set(input, attrFile, hasInputFile); input.Set("file", hasInputFile);
Set(input, attrBuffer, hasInputBuffer); input.Set("buffer", hasInputBuffer);
Set(input, attrStream, hasInputBuffer); input.Set("stream", hasInputBuffer);
// Output // Output
Local<Boolean> hasOutputFile = Napi::Boolean hasOutputFile =
New<Boolean>(vips_type_find("VipsOperation", (f + "save").c_str())); Napi::Boolean::New(env, vips_type_find("VipsOperation", (f + "save").c_str()));
Local<Boolean> hasOutputBuffer = Napi::Boolean hasOutputBuffer =
New<Boolean>(vips_type_find("VipsOperation", (f + "save_buffer").c_str())); Napi::Boolean::New(env, vips_type_find("VipsOperation", (f + "save_buffer").c_str()));
Local<Object> output = New<Object>(); Napi::Object output = Napi::Object::New(env);
Set(output, attrFile, hasOutputFile); output.Set("file", hasOutputFile);
Set(output, attrBuffer, hasOutputBuffer); output.Set("buffer", hasOutputBuffer);
Set(output, attrStream, hasOutputBuffer); output.Set("stream", hasOutputBuffer);
// Other attributes // Other attributes
Local<Object> container = New<Object>(); Napi::Object container = Napi::Object::New(env);
Local<String> formatId = New(f).ToLocalChecked(); container.Set("id", f);
Set(container, attrId, formatId); container.Set("input", input);
Set(container, attrInput, input); container.Set("output", output);
Set(container, attrOutput, output);
// Add to set of formats // Add to set of formats
Set(format, formatId, container); format.Set(f, container);
} }
// Raw, uncompressed data // Raw, uncompressed data
Local<Object> raw = New<Object>(); Napi::Boolean supported = Napi::Boolean::New(env, true);
Local<String> rawId = New("raw").ToLocalChecked(); Napi::Boolean unsupported = Napi::Boolean::New(env, false);
Set(raw, attrId, rawId); Napi::Object rawInput = Napi::Object::New(env);
Set(format, rawId, raw); rawInput.Set("file", unsupported);
Local<Boolean> supported = New<Boolean>(true); rawInput.Set("buffer", supported);
Local<Boolean> unsupported = New<Boolean>(false); rawInput.Set("stream", supported);
Local<Object> rawInput = New<Object>(); Napi::Object rawOutput = Napi::Object::New(env);
Set(rawInput, attrFile, unsupported); rawOutput.Set("file", unsupported);
Set(rawInput, attrBuffer, supported); rawOutput.Set("buffer", supported);
Set(rawInput, attrStream, supported); rawOutput.Set("stream", supported);
Set(raw, attrInput, rawInput); Napi::Object raw = Napi::Object::New(env);
Local<Object> rawOutput = New<Object>(); raw.Set("id", "raw");
Set(rawOutput, attrFile, unsupported); raw.Set("input", rawInput);
Set(rawOutput, attrBuffer, supported); raw.Set("output", rawOutput);
Set(rawOutput, attrStream, supported); format.Set("raw", raw);
Set(raw, attrOutput, rawOutput);
info.GetReturnValue().Set(format); return format;
} }
/* /*
@ -208,65 +169,59 @@ NAN_METHOD(format) {
Calculates the maximum colour distance using the DE2000 algorithm Calculates the maximum colour distance using the DE2000 algorithm
between two images of the same dimensions and number of channels. between two images of the same dimensions and number of channels.
*/ */
NAN_METHOD(_maxColourDistance) { Napi::Value _maxColourDistance(const Napi::CallbackInfo& info) {
using vips::VImage; Napi::Env env = info.Env();
using vips::VError;
using sharp::DetermineImageType;
using sharp::ImageType;
using sharp::HasAlpha;
HandleScope();
// Open input files // Open input files
VImage image1; VImage image1;
ImageType imageType1 = DetermineImageType(*Utf8String(info[0])); sharp::ImageType imageType1 = sharp::DetermineImageType(info[0].As<Napi::String>().Utf8Value().data());
if (imageType1 != ImageType::UNKNOWN) { if (imageType1 != sharp::ImageType::UNKNOWN) {
try { try {
image1 = VImage::new_from_file(*Utf8String(info[0])); image1 = VImage::new_from_file(info[0].As<Napi::String>().Utf8Value().c_str());
} catch (...) { } catch (...) {
return ThrowError("Input file 1 has corrupt header"); throw Napi::Error::New(env, "Input file 1 has corrupt header");
} }
} else { } else {
return ThrowError("Input file 1 is of an unsupported image format"); throw Napi::Error::New(env, "Input file 1 is of an unsupported image format");
} }
VImage image2; VImage image2;
ImageType imageType2 = DetermineImageType(*Utf8String(info[1])); sharp::ImageType imageType2 = sharp::DetermineImageType(info[1].As<Napi::String>().Utf8Value().data());
if (imageType2 != ImageType::UNKNOWN) { if (imageType2 != sharp::ImageType::UNKNOWN) {
try { try {
image2 = VImage::new_from_file(*Utf8String(info[1])); image2 = VImage::new_from_file(info[1].As<Napi::String>().Utf8Value().c_str());
} catch (...) { } catch (...) {
return ThrowError("Input file 2 has corrupt header"); throw Napi::Error::New(env, "Input file 2 has corrupt header");
} }
} else { } else {
return ThrowError("Input file 2 is of an unsupported image format"); throw Napi::Error::New(env, "Input file 2 is of an unsupported image format");
} }
// Ensure same number of channels // Ensure same number of channels
if (image1.bands() != image2.bands()) { if (image1.bands() != image2.bands()) {
return ThrowError("mismatchedBands"); throw Napi::Error::New(env, "mismatchedBands");
} }
// Ensure same dimensions // Ensure same dimensions
if (image1.width() != image2.width() || image1.height() != image2.height()) { if (image1.width() != image2.width() || image1.height() != image2.height()) {
return ThrowError("mismatchedDimensions"); throw Napi::Error::New(env, "mismatchedDimensions");
} }
double maxColourDistance; double maxColourDistance;
try { try {
// Premultiply and remove alpha // Premultiply and remove alpha
if (HasAlpha(image1)) { if (sharp::HasAlpha(image1)) {
image1 = image1.premultiply().extract_band(1, VImage::option()->set("n", image1.bands() - 1)); image1 = image1.premultiply().extract_band(1, VImage::option()->set("n", image1.bands() - 1));
} }
if (HasAlpha(image2)) { if (sharp::HasAlpha(image2)) {
image2 = image2.premultiply().extract_band(1, VImage::option()->set("n", image2.bands() - 1)); image2 = image2.premultiply().extract_band(1, VImage::option()->set("n", image2.bands() - 1));
} }
// Calculate colour distance // Calculate colour distance
maxColourDistance = image1.dE00(image2).max(); maxColourDistance = image1.dE00(image2).max();
} catch (VError const &err) { } catch (vips::VError const &err) {
return ThrowError(err.what()); throw Napi::Error::New(env, err.what());
} }
// Clean up libvips' per-request data and threads // Clean up libvips' per-request data and threads
vips_error_clear(); vips_error_clear();
vips_thread_shutdown(); vips_thread_shutdown();
info.GetReturnValue().Set(New<Number>(maxColourDistance)); return Napi::Number::New(env, maxColourDistance);
} }

View File

@ -15,14 +15,14 @@
#ifndef SRC_UTILITIES_H_ #ifndef SRC_UTILITIES_H_
#define SRC_UTILITIES_H_ #define SRC_UTILITIES_H_
#include <nan.h> #include <napi.h>
NAN_METHOD(cache); Napi::Value cache(const Napi::CallbackInfo& info);
NAN_METHOD(concurrency); Napi::Value concurrency(const Napi::CallbackInfo& info);
NAN_METHOD(counters); Napi::Value counters(const Napi::CallbackInfo& info);
NAN_METHOD(simd); Napi::Value simd(const Napi::CallbackInfo& info);
NAN_METHOD(libvipsVersion); Napi::Value libvipsVersion(const Napi::CallbackInfo& info);
NAN_METHOD(format); Napi::Value format(const Napi::CallbackInfo& info);
NAN_METHOD(_maxColourDistance); Napi::Value _maxColourDistance(const Napi::CallbackInfo& info);
#endif // SRC_UTILITIES_H_ #endif // SRC_UTILITIES_H_

View File

@ -578,11 +578,14 @@
fun:_ZN4node20BackgroundTaskRunnerC1Ei fun:_ZN4node20BackgroundTaskRunnerC1Ei
} }
{ {
leak_nan_FunctionCallbackInfo leak_napi_module_register
Memcheck:Leak Memcheck:Leak
match-leak-kinds: definite match-leak-kinds: definite
... ...
fun:_ZN3Nan3impL23FunctionCallbackWrapperERKN2v820FunctionCallbackInfoINS1_5ValueEEE fun:napi_module_register
fun:call_init.part.0
fun:call_init
fun:_dl_init
} }
{ {
leak_v8_FunctionCallbackInfo leak_v8_FunctionCallbackInfo

View File

@ -48,8 +48,8 @@ describe('failOnError', function () {
it('returns errors to callback for truncated JPEG', function (done) { it('returns errors to callback for truncated JPEG', function (done) {
sharp(fixtures.inputJpgTruncated).toBuffer(function (err, data, info) { sharp(fixtures.inputJpgTruncated).toBuffer(function (err, data, info) {
assert.ok(err.message.includes('VipsJpeg: Premature end of JPEG file'), err); assert.ok(err.message.includes('VipsJpeg: Premature end of JPEG file'), err);
assert.strictEqual(data, null); assert.strictEqual(data, undefined);
assert.strictEqual(info, null); assert.strictEqual(info, undefined);
done(); done();
}); });
}); });
@ -57,8 +57,8 @@ describe('failOnError', function () {
it('returns errors to callback for truncated PNG', function (done) { it('returns errors to callback for truncated PNG', function (done) {
sharp(fixtures.inputPngTruncated).toBuffer(function (err, data, info) { sharp(fixtures.inputPngTruncated).toBuffer(function (err, data, info) {
assert.ok(err.message.includes('vipspng: libpng read error'), err); assert.ok(err.message.includes('vipspng: libpng read error'), err);
assert.strictEqual(data, null); assert.strictEqual(data, undefined);
assert.strictEqual(info, null); assert.strictEqual(info, undefined);
done(); done();
}); });
}); });