From 4abb4edf64c3ba94c2854aeae65fe2b952c9a28d Mon Sep 17 00:00:00 2001 From: Lovell Fuller Date: Sat, 15 Feb 2020 19:36:19 +0000 Subject: [PATCH] Migrate internals to N-API #1282 --- .github/CONTRIBUTING.md | 2 +- README.md | 3 +- binding.gyp | 7 +- docs/README.md | 3 +- docs/changelog.md | 9 + docs/install.md | 4 +- lib/constructor.js | 7 +- package.json | 9 +- src/common.cc | 85 +++---- src/common.h | 32 ++- src/metadata.cc | 212 +++++++---------- src/metadata.h | 4 +- src/pipeline.cc | 487 ++++++++++++++++++--------------------- src/pipeline.h | 4 +- src/sharp.cc | 38 ++- src/stats.cc | 152 +++++------- src/stats.h | 12 +- src/utilities.cc | 241 ++++++++----------- src/utilities.h | 16 +- test/leak/sharp.supp | 7 +- test/unit/failOnError.js | 8 +- 21 files changed, 599 insertions(+), 743 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index ba9cf0d5..dc68aba1 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -44,8 +44,8 @@ Any change that modifies the existing public API should be added to the relevant | Release | WIP branch | | ------: | :--------- | -| v0.24.0 | wit | | v0.25.0 | yield | +| v0.26.0 | zoom | Please squash your changes into a single commit using a command like `git rebase -i upstream/`. diff --git a/README.md b/README.md index eb72ef78..f9052427 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,7 @@ Lanczos resampling ensures quality is not sacrificed for speed. As well as image resizing, operations such as rotation, extraction, compositing and gamma correction are available. -Most modern 64-bit macOS, Windows and Linux systems running -Node versions 10, 12 and 13 +Most modern 64-bit macOS, Windows and Linux systems running Node.js v10.16.0+ do not require any additional install or runtime dependencies. ## Examples diff --git a/binding.gyp b/binding.gyp index 1e7c83ce..bea68e2d 100644 --- a/binding.gyp +++ b/binding.gyp @@ -45,6 +45,7 @@ }, { 'target_name': 'sharp', 'dependencies': [ + '= 10.13) * Linux x64 (glibc >= 2.17, musl >= 1.1.24) diff --git a/lib/constructor.js b/lib/constructor.js index 2d6ffa24..f0c5acba 100644 --- a/lib/constructor.js +++ b/lib/constructor.js @@ -163,7 +163,7 @@ const Sharp = function (input, options) { gamma: 0, gammaOut: 0, greyscale: false, - normalise: 0, + normalise: false, brightness: 1, saturation: 1, hue: 0, @@ -219,6 +219,11 @@ const Sharp = function (input, options) { heifCompression: 'hevc', tileSize: 256, tileOverlap: 0, + tileContainer: 'fs', + tileLayout: 'dz', + tileFormat: 'last', + tileDepth: 'last', + tileAngle: 0, tileSkipBlanks: -1, tileBackground: [255, 255, 255, 255], linearA: 1, diff --git a/package.json b/package.json index ae2ece92..fe3148e5 100644 --- a/package.json +++ b/package.json @@ -109,7 +109,7 @@ "dependencies": { "color": "^3.1.2", "detect-libc": "^1.0.3", - "nan": "^2.14.0", + "node-addon-api": "^2.0.0", "npmlog": "^4.1.2", "prebuild-install": "^5.3.3", "semver": "^7.1.3", @@ -138,11 +138,16 @@ "libvips": "8.9.0" }, "engines": { - "node": ">=10.13.0" + "node": ">=10.16.0" }, "funding": { "url": "https://opencollective.com/libvips" }, + "binary": { + "napi_versions": [ + 4 + ] + }, "semistandard": { "env": [ "mocha" diff --git a/src/common.cc b/src/common.cc index fdfa157b..6c9c8a82 100644 --- a/src/common.cc +++ b/src/common.cc @@ -19,9 +19,7 @@ #include #include // NOLINT(build/c++11) -#include -#include -#include +#include #include #include "common.h" @@ -30,66 +28,77 @@ using vips::VImage; namespace sharp { - // Convenience methods to access the attributes of a v8::Object - bool HasAttr(v8::Local obj, std::string attr) { - return Nan::Has(obj, Nan::New(attr).ToLocalChecked()).FromJust(); + // Convenience methods to access the attributes of a Napi::Object + bool HasAttr(Napi::Object obj, std::string attr) { + return obj.Has(attr); } - std::string AttrAsStr(v8::Local obj, std::string attr) { - return *Nan::Utf8String(Nan::Get(obj, Nan::New(attr).ToLocalChecked()).ToLocalChecked()); + std::string AttrAsStr(Napi::Object obj, std::string attr) { + return obj.Get(attr).As(); } - std::vector AttrAsRgba(v8::Local obj, std::string attr) { - v8::Local background = AttrAs(obj, attr); - std::vector rgba(4); - for (unsigned int i = 0; i < 4; i++) { - rgba[i] = AttrTo(background, i); + uint32_t AttrAsUint32(Napi::Object obj, std::string attr) { + return obj.Get(attr).As().Uint32Value(); + } + int32_t AttrAsInt32(Napi::Object obj, std::string attr) { + return obj.Get(attr).As().Int32Value(); + } + double AttrAsDouble(Napi::Object obj, std::string attr) { + return obj.Get(attr).As().DoubleValue(); + } + double AttrAsDouble(Napi::Object obj, unsigned int const attr) { + return obj.Get(attr).As().DoubleValue(); + } + bool AttrAsBool(Napi::Object obj, std::string attr) { + return obj.Get(attr).As().Value(); + } + std::vector AttrAsRgba(Napi::Object obj, std::string attr) { + Napi::Array background = obj.Get(attr).As(); + std::vector rgba(background.Length()); + for (unsigned int i = 0; i < background.Length(); i++) { + rgba[i] = AttrAsDouble(background, i); } return rgba; } - // Create an InputDescriptor instance from a v8::Object describing an input image - InputDescriptor* CreateInputDescriptor( - v8::Local input, std::vector> &buffersToPersist - ) { - Nan::HandleScope(); + // Create an InputDescriptor instance from a Napi::Object describing an input image + InputDescriptor* CreateInputDescriptor(Napi::Object input) { InputDescriptor *descriptor = new InputDescriptor; if (HasAttr(input, "file")) { descriptor->file = AttrAsStr(input, "file"); } else if (HasAttr(input, "buffer")) { - v8::Local buffer = AttrAs(input, "buffer"); - descriptor->bufferLength = node::Buffer::Length(buffer); - descriptor->buffer = node::Buffer::Data(buffer); + Napi::Buffer buffer = input.Get("buffer").As>(); + descriptor->bufferLength = buffer.Length(); + descriptor->buffer = buffer.Data(); descriptor->isBuffer = TRUE; - buffersToPersist.push_back(buffer); } - descriptor->failOnError = AttrTo(input, "failOnError"); + descriptor->failOnError = AttrAsBool(input, "failOnError"); // Density for vector-based input if (HasAttr(input, "density")) { - descriptor->density = AttrTo(input, "density"); + descriptor->density = AttrAsDouble(input, "density"); } // Raw pixel input if (HasAttr(input, "rawChannels")) { - descriptor->rawChannels = AttrTo(input, "rawChannels"); - descriptor->rawWidth = AttrTo(input, "rawWidth"); - descriptor->rawHeight = AttrTo(input, "rawHeight"); + descriptor->rawChannels = AttrAsUint32(input, "rawChannels"); + descriptor->rawWidth = AttrAsUint32(input, "rawWidth"); + descriptor->rawHeight = AttrAsUint32(input, "rawHeight"); } // Multi-page input (GIF, TIFF, PDF) if (HasAttr(input, "pages")) { - descriptor->pages = AttrTo(input, "pages"); + descriptor->pages = AttrAsInt32(input, "pages"); } if (HasAttr(input, "page")) { - descriptor->page = AttrTo(input, "page"); + descriptor->page = AttrAsUint32(input, "page"); } // Create new image if (HasAttr(input, "createChannels")) { - descriptor->createChannels = AttrTo(input, "createChannels"); - descriptor->createWidth = AttrTo(input, "createWidth"); - descriptor->createHeight = AttrTo(input, "createHeight"); + descriptor->createChannels = AttrAsUint32(input, "createChannels"); + descriptor->createWidth = AttrAsUint32(input, "createWidth"); + descriptor->createHeight = AttrAsUint32(input, "createHeight"); descriptor->createBackground = AttrAsRgba(input, "createBackground"); } // Limit input images to a given number of pixels, where pixels = width * height - descriptor->limitInputPixels = AttrTo(input, "limitInputPixels"); + descriptor->limitInputPixels = AttrAsUint32(input, "limitInputPixels"); // Allow switch from random to sequential access - descriptor->access = AttrTo(input, "sequentialRead") ? VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_RANDOM; + descriptor->access = AttrAsBool(input, "sequentialRead") ? VIPS_ACCESS_SEQUENTIAL : VIPS_ACCESS_RANDOM; return descriptor; } @@ -439,11 +448,9 @@ namespace sharp { /* Called when a Buffer undergoes GC, required to support mixed runtime libraries in Windows */ - void FreeCallback(char* data, void* hint) { - if (data != nullptr) { - g_free(data); - } - } + std::function FreeCallback = [](void*, char* data) { + g_free(data); + }; /* Temporary buffer of warnings diff --git a/src/common.h b/src/common.h index 6b464810..55c2f502 100644 --- a/src/common.h +++ b/src/common.h @@ -19,8 +19,7 @@ #include #include -#include -#include +#include #include // Verify platform and compiler compatibility @@ -82,23 +81,18 @@ namespace sharp { createBackground{ 0.0, 0.0, 0.0, 255.0 } {} }; - // Convenience methods to access the attributes of a v8::Object - bool HasAttr(v8::Local obj, std::string attr); - std::string AttrAsStr(v8::Local obj, std::string attr); - std::vector AttrAsRgba(v8::Local obj, std::string attr); - template v8::Local AttrAs(v8::Local obj, std::string attr) { - return Nan::Get(obj, Nan::New(attr).ToLocalChecked()).ToLocalChecked().As(); - } - template T AttrTo(v8::Local obj, std::string attr) { - return Nan::To(Nan::Get(obj, Nan::New(attr).ToLocalChecked()).ToLocalChecked()).FromJust(); - } - template T AttrTo(v8::Local obj, int attr) { - return Nan::To(Nan::Get(obj, attr).ToLocalChecked()).FromJust(); - } + // Convenience methods to access the attributes of a Napi::Object + bool HasAttr(Napi::Object obj, std::string attr); + std::string AttrAsStr(Napi::Object obj, std::string attr); + uint32_t AttrAsUint32(Napi::Object obj, std::string attr); + int32_t AttrAsInt32(Napi::Object obj, std::string attr); + double AttrAsDouble(Napi::Object obj, std::string attr); + double AttrAsDouble(Napi::Object obj, unsigned int const attr); + bool AttrAsBool(Napi::Object obj, std::string attr); + std::vector AttrAsRgba(Napi::Object obj, std::string attr); - // Create an InputDescriptor instance from a v8::Object describing an input image - InputDescriptor* CreateInputDescriptor( - v8::Local input, std::vector> &buffersToPersist); // NOLINT(runtime/references) + // Create an InputDescriptor instance from a Napi::Object describing an input image + InputDescriptor* CreateInputDescriptor(Napi::Object input); enum class ImageType { JPEG, @@ -211,7 +205,7 @@ namespace sharp { /* Called when a Buffer undergoes GC, required to support mixed runtime libraries in Windows */ - void FreeCallback(char* data, void* hint); + extern std::function FreeCallback; /* Called with warnings from the glib-registered "VIPS" domain diff --git a/src/metadata.cc b/src/metadata.cc index e9a095f2..f7076252 100644 --- a/src/metadata.cc +++ b/src/metadata.cc @@ -15,28 +15,16 @@ #include #include -#include -#include +#include #include #include "common.h" #include "metadata.h" -class MetadataWorker : public Nan::AsyncWorker { +class MetadataWorker : public Napi::AsyncWorker { public: - MetadataWorker( - Nan::Callback *callback, MetadataBaton *baton, Nan::Callback *debuglog, - std::vector> 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 const buffer) -> uint32_t { - SaveToPersistent(index, buffer); - return index + 1; - }); - } + MetadataWorker(Napi::Function callback, MetadataBaton *baton, Napi::Function debuglog) : + Napi::AsyncWorker(callback), baton(baton), debuglog(Napi::Persistent(debuglog)) {} ~MetadataWorker() {} void Execute() { @@ -137,140 +125,114 @@ class MetadataWorker : public Nan::AsyncWorker { vips_thread_shutdown(); } - void HandleOKCallback() { - using Nan::New; - using Nan::Set; - Nan::HandleScope(); - - v8::Local argv[2] = { Nan::Null(), Nan::Null() }; - if (!baton->err.empty()) { - argv[0] = Nan::Error(baton->err.data()); - } else { - // Metadata Object - v8::Local info = New(); - Set(info, New("format").ToLocalChecked(), New(baton->format).ToLocalChecked()); - if (baton->input->bufferLength > 0) { - Set(info, New("size").ToLocalChecked(), New(static_cast(baton->input->bufferLength))); - } - Set(info, New("width").ToLocalChecked(), New(baton->width)); - Set(info, New("height").ToLocalChecked(), New(baton->height)); - Set(info, New("space").ToLocalChecked(), New(baton->space).ToLocalChecked()); - Set(info, New("channels").ToLocalChecked(), New(baton->channels)); - Set(info, New("depth").ToLocalChecked(), New(baton->depth).ToLocalChecked()); - if (baton->density > 0) { - Set(info, New("density").ToLocalChecked(), New(baton->density)); - } - if (!baton->chromaSubsampling.empty()) { - Set(info, - New("chromaSubsampling").ToLocalChecked(), - New(baton->chromaSubsampling).ToLocalChecked()); - } - Set(info, New("isProgressive").ToLocalChecked(), New(baton->isProgressive)); - if (baton->paletteBitDepth > 0) { - Set(info, New("paletteBitDepth").ToLocalChecked(), New(baton->paletteBitDepth)); - } - if (baton->pages > 0) { - Set(info, New("pages").ToLocalChecked(), New(baton->pages)); - } - if (baton->pageHeight > 0) { - Set(info, New("pageHeight").ToLocalChecked(), New(baton->pageHeight)); - } - if (baton->loop >= 0) { - Set(info, New("loop").ToLocalChecked(), New(baton->loop)); - } - if (!baton->delay.empty()) { - int i = 0; - v8::Local delay = New(baton->delay.size()); - for (int const d : baton->delay) { - Set(delay, i++, New(d)); - } - Set(info, New("delay").ToLocalChecked(), delay); - } - if (baton->pagePrimary > -1) { - Set(info, New("pagePrimary").ToLocalChecked(), New(baton->pagePrimary)); - } - Set(info, New("hasProfile").ToLocalChecked(), New(baton->hasProfile)); - Set(info, New("hasAlpha").ToLocalChecked(), New(baton->hasAlpha)); - if (baton->orientation > 0) { - Set(info, New("orientation").ToLocalChecked(), New(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 const buffer) -> uint32_t { - GetFromPersistent(index); - return index + 1; - }); - delete baton->input; - delete baton; + void OnOK() { + Napi::Env env = Env(); + Napi::HandleScope scope(env); // Handle warnings std::string warning = sharp::VipsWarningPop(); while (!warning.empty()) { - v8::Local message[1] = { New(warning).ToLocalChecked() }; - debuglog->Call(1, message, async_resource); + debuglog.Call({ Napi::String::New(env, warning) }); warning = sharp::VipsWarningPop(); } - // Return to JavaScript - callback->Call(2, argv, async_resource); + if (baton->err.empty()) { + 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(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::New(env, baton->exif, baton->exifLength, sharp::FreeCallback)); + } + if (baton->iccLength > 0) { + info.Set("icc", Napi::Buffer::New(env, baton->icc, baton->iccLength, sharp::FreeCallback)); + } + if (baton->iptcLength > 0) { + info.Set("iptc", Napi::Buffer::New(env, baton->iptc, baton->iptcLength, sharp::FreeCallback)); + } + if (baton->xmpLength > 0) { + info.Set("xmp", Napi::Buffer::New(env, baton->xmp, baton->xmpLength, sharp::FreeCallback)); + } + if (baton->tifftagPhotoshopLength > 0) { + info.Set("tifftagPhotoshop", + Napi::Buffer::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: MetadataBaton* baton; - Nan::Callback *debuglog; - std::vector> buffersToPersist; + Napi::FunctionReference debuglog; }; /* metadata(options, callback) */ -NAN_METHOD(metadata) { - // Input Buffers must not undergo GC compaction during processing - std::vector> buffersToPersist; - +Napi::Value metadata(const Napi::CallbackInfo& info) { // V8 objects are converted to non-V8 types held in the baton struct MetadataBaton *baton = new MetadataBaton; - v8::Local options = info[0].As(); + Napi::Object options = info[0].As(); // Input - baton->input = sharp::CreateInputDescriptor(sharp::AttrAs(options, "input"), buffersToPersist); + baton->input = sharp::CreateInputDescriptor(options.Get("input").As()); // Function to notify of libvips warnings - Nan::Callback *debuglog = new Nan::Callback(sharp::AttrAs(options, "debuglog")); + Napi::Function debuglog = options.Get("debuglog").As(); // Join queue for worker thread - Nan::Callback *callback = new Nan::Callback(info[1].As()); - Nan::AsyncQueueWorker(new MetadataWorker(callback, baton, debuglog, buffersToPersist)); + Napi::Function callback = info[1].As(); + MetadataWorker *worker = new MetadataWorker(callback, baton, debuglog); + worker->Queue(); // Increment queued task counter g_atomic_int_inc(&sharp::counterQueue); + + return info.Env().Undefined(); } diff --git a/src/metadata.h b/src/metadata.h index 230fd690..abb860fb 100644 --- a/src/metadata.h +++ b/src/metadata.h @@ -16,7 +16,7 @@ #define SRC_METADATA_H_ #include -#include +#include #include "./common.h" @@ -81,6 +81,6 @@ struct MetadataBaton { tifftagPhotoshopLength(0) {} }; -NAN_METHOD(metadata); +Napi::Value metadata(const Napi::CallbackInfo& info); #endif // SRC_METADATA_H_ diff --git a/src/pipeline.cc b/src/pipeline.cc index c6c22a98..a0ede469 100644 --- a/src/pipeline.cc +++ b/src/pipeline.cc @@ -25,8 +25,7 @@ #include #include -#include -#include +#include #include "common.h" #include "operations.h" @@ -46,28 +45,18 @@ #define STAT64_FUNCTION stat64 #endif -class PipelineWorker : public Nan::AsyncWorker { +class PipelineWorker : public Napi::AsyncWorker { public: - PipelineWorker( - Nan::Callback *callback, PipelineBaton *baton, Nan::Callback *debuglog, Nan::Callback *queueListener, - std::vector> const buffersToPersist) : - Nan::AsyncWorker(callback, "sharp:PipelineWorker"), - baton(baton), debuglog(debuglog), queueListener(queueListener), - buffersToPersist(buffersToPersist) { - // Protect Buffer objects from GC, keyed on index - std::accumulate(buffersToPersist.begin(), buffersToPersist.end(), 0, - [this](uint32_t index, v8::Local const buffer) -> uint32_t { - SaveToPersistent(index, buffer); - return index + 1; - }); - } + PipelineWorker(Napi::Function callback, PipelineBaton *baton, + Napi::Function debuglog, Napi::Function queueListener) : + Napi::AsyncWorker(callback), + baton(baton), + debuglog(Napi::Persistent(debuglog)), + queueListener(Napi::Persistent(queueListener)) {} ~PipelineWorker() {} // libuv worker void Execute() { - using sharp::HasAlpha; - using sharp::ImageType; - // Decrement queued task counter g_atomic_int_dec_and_test(&sharp::counterQueue); // Increment processing task counter @@ -76,7 +65,7 @@ class PipelineWorker : public Nan::AsyncWorker { try { // Open input vips::VImage image; - ImageType inputImageType; + sharp::ImageType inputImageType; std::tie(image, inputImageType) = sharp::OpenInput(baton->input); // Calculate angle of rotation @@ -236,7 +225,7 @@ class PipelineWorker : public Nan::AsyncWorker { } if ( 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 ) { if (xshrink >= 8 * shrink_on_load_factor) { @@ -267,7 +256,7 @@ class PipelineWorker : public Nan::AsyncWorker { ->set("fail", baton->input->failOnError); if (baton->input->buffer != nullptr) { VipsBlob *blob = vips_blob_new(nullptr, baton->input->buffer, baton->input->bufferLength); - if (inputImageType == ImageType::JPEG) { + if (inputImageType == sharp::ImageType::JPEG) { // Reload JPEG buffer image = VImage::jpegload_buffer(blob, option); } else { @@ -276,7 +265,7 @@ class PipelineWorker : public Nan::AsyncWorker { } vips_area_unref(reinterpret_cast(blob)); } else { - if (inputImageType == ImageType::JPEG) { + if (inputImageType == sharp::ImageType::JPEG) { // Reload JPEG file image = VImage::jpegload(const_cast(baton->input->file.data()), option); } else { @@ -320,7 +309,7 @@ class PipelineWorker : public Nan::AsyncWorker { } // 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 double const multiplier = sharp::Is16Bit(image.interpretation()) ? 256.0 : 1.0; // Background colour @@ -356,11 +345,11 @@ class PipelineWorker : public Nan::AsyncWorker { bool const shouldComposite = !baton->composite.empty(); 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); } - bool const shouldPremultiplyAlpha = HasAlpha(image) && + bool const shouldPremultiplyAlpha = sharp::HasAlpha(image) && (shouldResize || shouldBlur || shouldConv || shouldSharpen || shouldComposite); // 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 if (baton->joinChannelIn.size() > 0) { VImage joinImage; - ImageType joinImageType = ImageType::UNKNOWN; + sharp::ImageType joinImageType = sharp::ImageType::UNKNOWN; for (unsigned int i = 0; i < baton->joinChannelIn.size(); i++) { std::tie(joinImage, joinImageType) = sharp::OpenInput(baton->joinChannelIn[i]); @@ -548,7 +537,7 @@ class PipelineWorker : public Nan::AsyncWorker { if (shouldComposite) { for (Composite *composite : baton->composite) { VImage compositeImage; - ImageType compositeImageType = ImageType::UNKNOWN; + sharp::ImageType compositeImageType = sharp::ImageType::UNKNOWN; std::tie(compositeImage, compositeImageType) = OpenInput(composite->input); // Verify within current dimensions 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 compositeImage = compositeImage.colourspace(VIPS_INTERPRETATION_sRGB); - if (!HasAlpha(compositeImage)) { + if (!sharp::HasAlpha(compositeImage)) { compositeImage = sharp::EnsureAlpha(compositeImage); } if (!composite->premultiplied) compositeImage = compositeImage.premultiply(); @@ -638,7 +627,7 @@ class PipelineWorker : public Nan::AsyncWorker { // Apply bitwise boolean operation between images if (baton->boolean != nullptr) { VImage booleanImage; - ImageType booleanImageType = ImageType::UNKNOWN; + sharp::ImageType booleanImageType = sharp::ImageType::UNKNOWN; std::tie(booleanImage, booleanImageType) = sharp::OpenInput(baton->boolean); image = sharp::Boolean(image, booleanImage, baton->booleanOp); } @@ -703,9 +692,9 @@ class PipelineWorker : public Nan::AsyncWorker { // Output if (baton->fileOut.empty()) { // 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 - sharp::AssertImageTypeDimensions(image, ImageType::JPEG); + sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG); VipsArea *area = VIPS_AREA(image.jpegsave_buffer(VImage::option() ->set("strip", !baton->withMetadata) ->set("Q", baton->jpegQuality) @@ -727,9 +716,10 @@ class PipelineWorker : public Nan::AsyncWorker { baton->channels = std::min(baton->channels, 3); } } 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 - sharp::AssertImageTypeDimensions(image, ImageType::PNG); + sharp::AssertImageTypeDimensions(image, sharp::ImageType::PNG); VipsArea *area = VIPS_AREA(image.pngsave_buffer(VImage::option() ->set("strip", !baton->withMetadata) ->set("interlace", baton->pngProgressive) @@ -744,9 +734,10 @@ class PipelineWorker : public Nan::AsyncWorker { area->free_fn = nullptr; vips_area_unref(area); 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 - sharp::AssertImageTypeDimensions(image, ImageType::WEBP); + sharp::AssertImageTypeDimensions(image, sharp::ImageType::WEBP); VipsArea *area = VIPS_AREA(image.webpsave_buffer(VImage::option() ->set("strip", !baton->withMetadata) ->set("Q", baton->webpQuality) @@ -760,10 +751,11 @@ class PipelineWorker : public Nan::AsyncWorker { area->free_fn = nullptr; vips_area_unref(area); 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 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); } // Cast pixel values to float, if required @@ -787,7 +779,8 @@ class PipelineWorker : public Nan::AsyncWorker { area->free_fn = nullptr; vips_area_unref(area); 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 VipsArea *area = VIPS_AREA(image.heifsave_buffer(VImage::option() ->set("strip", !baton->withMetadata) @@ -799,7 +792,8 @@ class PipelineWorker : public Nan::AsyncWorker { area->free_fn = nullptr; vips_area_unref(area); 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 if (baton->greyscale || image.interpretation() == VIPS_INTERPRETATION_B_W) { // Extract first band for greyscale image @@ -840,9 +834,9 @@ class PipelineWorker : public Nan::AsyncWorker { bool const mightMatchInput = baton->formatOut == "input"; bool const willMatchInput = mightMatchInput && !(isJpeg || isPng || isWebp || isTiff || isDz || isDzZip || isV); if (baton->formatOut == "jpeg" || (mightMatchInput && isJpeg) || - (willMatchInput && inputImageType == ImageType::JPEG)) { + (willMatchInput && inputImageType == sharp::ImageType::JPEG)) { // Write JPEG to file - sharp::AssertImageTypeDimensions(image, ImageType::JPEG); + sharp::AssertImageTypeDimensions(image, sharp::ImageType::JPEG); image.jpegsave(const_cast(baton->fileOut.data()), VImage::option() ->set("strip", !baton->withMetadata) ->set("Q", baton->jpegQuality) @@ -856,9 +850,10 @@ class PipelineWorker : public Nan::AsyncWorker { baton->formatOut = "jpeg"; baton->channels = std::min(baton->channels, 3); } 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 - sharp::AssertImageTypeDimensions(image, ImageType::PNG); + sharp::AssertImageTypeDimensions(image, sharp::ImageType::PNG); image.pngsave(const_cast(baton->fileOut.data()), VImage::option() ->set("strip", !baton->withMetadata) ->set("interlace", baton->pngProgressive) @@ -870,9 +865,9 @@ class PipelineWorker : public Nan::AsyncWorker { ->set("dither", baton->pngDither)); baton->formatOut = "png"; } else if (baton->formatOut == "webp" || (mightMatchInput && isWebp) || - (willMatchInput && inputImageType == ImageType::WEBP)) { + (willMatchInput && inputImageType == sharp::ImageType::WEBP)) { // Write WEBP to file - sharp::AssertImageTypeDimensions(image, ImageType::WEBP); + sharp::AssertImageTypeDimensions(image, sharp::ImageType::WEBP); image.webpsave(const_cast(baton->fileOut.data()), VImage::option() ->set("strip", !baton->withMetadata) ->set("Q", baton->webpQuality) @@ -883,10 +878,10 @@ class PipelineWorker : public Nan::AsyncWorker { ->set("alpha_q", baton->webpAlphaQuality)); baton->formatOut = "webp"; } else if (baton->formatOut == "tiff" || (mightMatchInput && isTiff) || - (willMatchInput && inputImageType == ImageType::TIFF)) { + (willMatchInput && inputImageType == sharp::ImageType::TIFF)) { // Write TIFF to file 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); } image.tiffsave(const_cast(baton->fileOut.data()), VImage::option() @@ -903,7 +898,7 @@ class PipelineWorker : public Nan::AsyncWorker { ->set("yres", baton->tiffYres)); baton->formatOut = "tiff"; } else if (baton->formatOut == "heif" || (mightMatchInput && isHeif) || - (willMatchInput && inputImageType == ImageType::HEIF)) { + (willMatchInput && inputImageType == sharp::ImageType::HEIF)) { // Write HEIF to file if (sharp::IsAvif(baton->fileOut)) { baton->heifCompression = VIPS_FOREIGN_HEIF_COMPRESSION_AV1; @@ -954,7 +949,7 @@ class PipelineWorker : public Nan::AsyncWorker { suffix = AssembleSuffixString(extname, options); } // 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(); } // Write DZ to file @@ -976,7 +971,7 @@ class PipelineWorker : public Nan::AsyncWorker { image.dzsave(const_cast(baton->fileOut.data()), options); baton->formatOut = "dz"; } else if (baton->formatOut == "v" || (mightMatchInput && isV) || - (willMatchInput && inputImageType == ImageType::VIPS)) { + (willMatchInput && inputImageType == sharp::ImageType::VIPS)) { // Write V to file image.vipssave(const_cast(baton->fileOut.data()), VImage::option() ->set("strip", !baton->withMetadata)); @@ -1000,16 +995,18 @@ class PipelineWorker : public Nan::AsyncWorker { vips_thread_shutdown(); } - void HandleOKCallback() { - using Nan::New; - using Nan::Set; - Nan::HandleScope(); + void OnOK() { + Napi::Env env = Env(); + Napi::HandleScope scope(env); - v8::Local argv[3] = { Nan::Null(), Nan::Null(), Nan::Null() }; - if (!baton->err.empty()) { - // Error - argv[0] = Nan::Error(baton->err.data()); - } else { + // Handle warnings + std::string warning = sharp::VipsWarningPop(); + while (!warning.empty()) { + debuglog.Call({ Napi::String::New(env, warning) }); + warning = sharp::VipsWarningPop(); + } + + if (baton->err.empty()) { int width = baton->width; int height = baton->height; if (baton->topOffsetPre != -1 && (baton->width == -1 || baton->height == -1)) { @@ -1021,50 +1018,40 @@ class PipelineWorker : public Nan::AsyncWorker { height = baton->heightPost; } // Info Object - v8::Local info = New(); - Set(info, New("format").ToLocalChecked(), New(baton->formatOut).ToLocalChecked()); - Set(info, New("width").ToLocalChecked(), New(static_cast(width))); - Set(info, New("height").ToLocalChecked(), New(static_cast(height))); - Set(info, New("channels").ToLocalChecked(), New(static_cast(baton->channels))); - Set(info, New("premultiplied").ToLocalChecked(), New(baton->premultiplied)); + Napi::Object info = Napi::Object::New(env); + info.Set("format", baton->formatOut); + info.Set("width", static_cast(width)); + info.Set("height", static_cast(height)); + info.Set("channels", static_cast(baton->channels)); + info.Set("premultiplied", baton->premultiplied); if (baton->hasCropOffset) { - Set(info, New("cropOffsetLeft").ToLocalChecked(), - New(static_cast(baton->cropOffsetLeft))); - Set(info, New("cropOffsetTop").ToLocalChecked(), - New(static_cast(baton->cropOffsetTop))); + info.Set("cropOffsetLeft", static_cast(baton->cropOffsetLeft)); + info.Set("cropOffsetTop", static_cast(baton->cropOffsetTop)); } if (baton->trimThreshold > 0.0) { - Set(info, New("trimOffsetLeft").ToLocalChecked(), - New(static_cast(baton->trimOffsetLeft))); - Set(info, New("trimOffsetTop").ToLocalChecked(), - New(static_cast(baton->trimOffsetTop))); + info.Set("trimOffsetLeft", static_cast(baton->trimOffsetLeft)); + info.Set("trimOffsetTop", static_cast(baton->trimOffsetTop)); } if (baton->bufferOutLength > 0) { - // Pass ownership of output data to Buffer instance - argv[1] = Nan::NewBuffer( - static_cast(baton->bufferOut), baton->bufferOutLength, sharp::FreeCallback, nullptr) - .ToLocalChecked(); // Add buffer size to info - Set(info, New("size").ToLocalChecked(), New(static_cast(baton->bufferOutLength))); - argv[2] = info; + info.Set("size", static_cast(baton->bufferOutLength)); + // Pass ownership of output data to Buffer instance + Napi::Buffer data = Napi::Buffer::New(env, static_cast(baton->bufferOut), + baton->bufferOutLength, sharp::FreeCallback); + Callback().MakeCallback(Receiver().Value(), { env.Null(), data, info }); } else { // Add file size to info struct STAT64_STRUCT st; if (STAT64_FUNCTION(baton->fileOut.data(), &st) == 0) { - Set(info, New("size").ToLocalChecked(), New(static_cast(st.st_size))); + info.Set("size", static_cast(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 const buffer) -> uint32_t { - GetFromPersistent(index); - return index + 1; - }); - // Delete baton delete baton->input; delete baton->boolean; @@ -1077,29 +1064,16 @@ class PipelineWorker : public Nan::AsyncWorker { } delete baton; - // Handle warnings - std::string warning = sharp::VipsWarningPop(); - while (!warning.empty()) { - v8::Local message[1] = { New(warning).ToLocalChecked() }; - debuglog->Call(1, message, async_resource); - warning = sharp::VipsWarningPop(); - } - // Decrement processing task counter g_atomic_int_dec_and_test(&sharp::counterProcess); - v8::Local queueLength[1] = { New(sharp::counterQueue) }; - queueListener->Call(1, queueLength, async_resource); - delete queueListener; - - // Return to JavaScript - callback->Call(3, argv, async_resource); + Napi::Number queueLength = Napi::Number::New(env, static_cast(sharp::counterQueue)); + queueListener.Call(Receiver().Value(), { queueLength }); } private: PipelineBaton *baton; - Nan::Callback *debuglog; - Nan::Callback *queueListener; - std::vector> buffersToPersist; + Napi::FunctionReference debuglog; + Napi::FunctionReference queueListener; /* 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) */ -NAN_METHOD(pipeline) { - 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> buffersToPersist; - +Napi::Value pipeline(const Napi::CallbackInfo& info) { // V8 objects are converted to non-V8 types held in the baton struct PipelineBaton *baton = new PipelineBaton; - v8::Local options = info[0].As(); + Napi::Object options = info[0].As(); // Input - baton->input = CreateInputDescriptor(AttrAs(options, "input"), buffersToPersist); + baton->input = sharp::CreateInputDescriptor(options.Get("input").As()); // Extract image options - baton->topOffsetPre = AttrTo(options, "topOffsetPre"); - baton->leftOffsetPre = AttrTo(options, "leftOffsetPre"); - baton->widthPre = AttrTo(options, "widthPre"); - baton->heightPre = AttrTo(options, "heightPre"); - baton->topOffsetPost = AttrTo(options, "topOffsetPost"); - baton->leftOffsetPost = AttrTo(options, "leftOffsetPost"); - baton->widthPost = AttrTo(options, "widthPost"); - baton->heightPost = AttrTo(options, "heightPost"); + baton->topOffsetPre = sharp::AttrAsInt32(options, "topOffsetPre"); + baton->leftOffsetPre = sharp::AttrAsInt32(options, "leftOffsetPre"); + baton->widthPre = sharp::AttrAsInt32(options, "widthPre"); + baton->heightPre = sharp::AttrAsInt32(options, "heightPre"); + baton->topOffsetPost = sharp::AttrAsInt32(options, "topOffsetPost"); + baton->leftOffsetPost = sharp::AttrAsInt32(options, "leftOffsetPost"); + baton->widthPost = sharp::AttrAsInt32(options, "widthPost"); + baton->heightPost = sharp::AttrAsInt32(options, "heightPost"); // Output image dimensions - baton->width = AttrTo(options, "width"); - baton->height = AttrTo(options, "height"); + baton->width = sharp::AttrAsInt32(options, "width"); + baton->height = sharp::AttrAsInt32(options, "height"); // Canvas option - std::string canvas = AttrAsStr(options, "canvas"); + std::string canvas = sharp::AttrAsStr(options, "canvas"); if (canvas == "crop") { baton->canvas = Canvas::CROP; } else if (canvas == "embed") { @@ -1212,172 +1176,165 @@ NAN_METHOD(pipeline) { baton->canvas = Canvas::IGNORE_ASPECT; } // Tint chroma - baton->tintA = AttrTo(options, "tintA"); - baton->tintB = AttrTo(options, "tintB"); + baton->tintA = sharp::AttrAsDouble(options, "tintA"); + baton->tintB = sharp::AttrAsDouble(options, "tintB"); // Composite - v8::Local compositeArray = Nan::Get(options, Nan::New("composite").ToLocalChecked()) - .ToLocalChecked().As(); - int const compositeArrayLength = AttrTo(compositeArray, "length"); - for (int i = 0; i < compositeArrayLength; i++) { - v8::Local compositeObject = Nan::Get(compositeArray, i).ToLocalChecked().As(); + Napi::Array compositeArray = options.Get("composite").As(); + for (unsigned int i = 0; i < compositeArray.Length(); i++) { + Napi::Object compositeObject = compositeArray.Get(i).As(); Composite *composite = new Composite; - composite->input = CreateInputDescriptor(AttrAs(compositeObject, "input"), buffersToPersist); + composite->input = sharp::CreateInputDescriptor(compositeObject.Get("input").As()); composite->mode = static_cast( - vips_enum_from_nick(nullptr, VIPS_TYPE_BLEND_MODE, AttrAsStr(compositeObject, "blend").data())); - composite->gravity = AttrTo(compositeObject, "gravity"); - composite->left = AttrTo(compositeObject, "left"); - composite->top = AttrTo(compositeObject, "top"); - composite->tile = AttrTo(compositeObject, "tile"); - composite->premultiplied = AttrTo(compositeObject, "premultiplied"); + vips_enum_from_nick(nullptr, VIPS_TYPE_BLEND_MODE, sharp::AttrAsStr(compositeObject, "blend").data())); + composite->gravity = sharp::AttrAsUint32(compositeObject, "gravity"); + composite->left = sharp::AttrAsInt32(compositeObject, "left"); + composite->top = sharp::AttrAsInt32(compositeObject, "top"); + composite->tile = sharp::AttrAsBool(compositeObject, "tile"); + composite->premultiplied = sharp::AttrAsBool(compositeObject, "premultiplied"); baton->composite.push_back(composite); } // Resize options - baton->withoutEnlargement = AttrTo(options, "withoutEnlargement"); - baton->position = AttrTo(options, "position"); - baton->resizeBackground = AttrAsRgba(options, "resizeBackground"); - baton->kernel = AttrAsStr(options, "kernel"); - baton->fastShrinkOnLoad = AttrTo(options, "fastShrinkOnLoad"); + baton->withoutEnlargement = sharp::AttrAsBool(options, "withoutEnlargement"); + baton->position = sharp::AttrAsInt32(options, "position"); + baton->resizeBackground = sharp::AttrAsRgba(options, "resizeBackground"); + baton->kernel = sharp::AttrAsStr(options, "kernel"); + baton->fastShrinkOnLoad = sharp::AttrAsBool(options, "fastShrinkOnLoad"); // Join Channel Options - if (HasAttr(options, "joinChannelIn")) { - v8::Local joinChannelObject = Nan::Get(options, Nan::New("joinChannelIn").ToLocalChecked()) - .ToLocalChecked().As(); - v8::Local joinChannelArray = joinChannelObject.As(); - int joinChannelArrayLength = AttrTo(joinChannelObject, "length"); - for (int i = 0; i < joinChannelArrayLength; i++) { + if (options.Has("joinChannelIn")) { + Napi::Array joinChannelArray = options.Get("joinChannelIn").As(); + for (unsigned int i = 0; i < joinChannelArray.Length(); i++) { baton->joinChannelIn.push_back( - CreateInputDescriptor( - Nan::Get(joinChannelArray, i).ToLocalChecked().As(), - buffersToPersist)); + sharp::CreateInputDescriptor(joinChannelArray.Get(i).As())); } } // Operators - baton->flatten = AttrTo(options, "flatten"); - baton->flattenBackground = AttrAsRgba(options, "flattenBackground"); - baton->negate = AttrTo(options, "negate"); - baton->blurSigma = AttrTo(options, "blurSigma"); - baton->brightness = AttrTo(options, "brightness"); - baton->saturation = AttrTo(options, "saturation"); - baton->hue = AttrTo(options, "hue"); - baton->medianSize = AttrTo(options, "medianSize"); - baton->sharpenSigma = AttrTo(options, "sharpenSigma"); - baton->sharpenFlat = AttrTo(options, "sharpenFlat"); - baton->sharpenJagged = AttrTo(options, "sharpenJagged"); - baton->threshold = AttrTo(options, "threshold"); - baton->thresholdGrayscale = AttrTo(options, "thresholdGrayscale"); - baton->trimThreshold = AttrTo(options, "trimThreshold"); - baton->gamma = AttrTo(options, "gamma"); - baton->gammaOut = AttrTo(options, "gammaOut"); - baton->linearA = AttrTo(options, "linearA"); - baton->linearB = AttrTo(options, "linearB"); - baton->greyscale = AttrTo(options, "greyscale"); - baton->normalise = AttrTo(options, "normalise"); - baton->useExifOrientation = AttrTo(options, "useExifOrientation"); - baton->angle = AttrTo(options, "angle"); - baton->rotationAngle = AttrTo(options, "rotationAngle"); - baton->rotationBackground = AttrAsRgba(options, "rotationBackground"); - baton->rotateBeforePreExtract = AttrTo(options, "rotateBeforePreExtract"); - baton->flip = AttrTo(options, "flip"); - baton->flop = AttrTo(options, "flop"); - baton->extendTop = AttrTo(options, "extendTop"); - baton->extendBottom = AttrTo(options, "extendBottom"); - baton->extendLeft = AttrTo(options, "extendLeft"); - baton->extendRight = AttrTo(options, "extendRight"); - baton->extendBackground = AttrAsRgba(options, "extendBackground"); - baton->extractChannel = AttrTo(options, "extractChannel"); + baton->flatten = sharp::AttrAsBool(options, "flatten"); + baton->flattenBackground = sharp::AttrAsRgba(options, "flattenBackground"); + baton->negate = sharp::AttrAsBool(options, "negate"); + baton->blurSigma = sharp::AttrAsDouble(options, "blurSigma"); + baton->brightness = sharp::AttrAsDouble(options, "brightness"); + baton->saturation = sharp::AttrAsDouble(options, "saturation"); + baton->hue = sharp::AttrAsInt32(options, "hue"); + baton->medianSize = sharp::AttrAsUint32(options, "medianSize"); + baton->sharpenSigma = sharp::AttrAsDouble(options, "sharpenSigma"); + baton->sharpenFlat = sharp::AttrAsDouble(options, "sharpenFlat"); + baton->sharpenJagged = sharp::AttrAsDouble(options, "sharpenJagged"); + baton->threshold = sharp::AttrAsInt32(options, "threshold"); + baton->thresholdGrayscale = sharp::AttrAsBool(options, "thresholdGrayscale"); + baton->trimThreshold = sharp::AttrAsDouble(options, "trimThreshold"); + baton->gamma = sharp::AttrAsDouble(options, "gamma"); + baton->gammaOut = sharp::AttrAsDouble(options, "gammaOut"); + baton->linearA = sharp::AttrAsDouble(options, "linearA"); + baton->linearB = sharp::AttrAsDouble(options, "linearB"); + baton->greyscale = sharp::AttrAsBool(options, "greyscale"); + baton->normalise = sharp::AttrAsBool(options, "normalise"); + baton->useExifOrientation = sharp::AttrAsBool(options, "useExifOrientation"); + baton->angle = sharp::AttrAsInt32(options, "angle"); + baton->rotationAngle = sharp::AttrAsDouble(options, "rotationAngle"); + baton->rotationBackground = sharp::AttrAsRgba(options, "rotationBackground"); + baton->rotateBeforePreExtract = sharp::AttrAsBool(options, "rotateBeforePreExtract"); + baton->flip = sharp::AttrAsBool(options, "flip"); + baton->flop = sharp::AttrAsBool(options, "flop"); + baton->extendTop = sharp::AttrAsInt32(options, "extendTop"); + baton->extendBottom = sharp::AttrAsInt32(options, "extendBottom"); + baton->extendLeft = sharp::AttrAsInt32(options, "extendLeft"); + baton->extendRight = sharp::AttrAsInt32(options, "extendRight"); + baton->extendBackground = sharp::AttrAsRgba(options, "extendBackground"); + baton->extractChannel = sharp::AttrAsInt32(options, "extractChannel"); - baton->removeAlpha = AttrTo(options, "removeAlpha"); - baton->ensureAlpha = AttrTo(options, "ensureAlpha"); - if (HasAttr(options, "boolean")) { - baton->boolean = CreateInputDescriptor(AttrAs(options, "boolean"), buffersToPersist); - baton->booleanOp = sharp::GetBooleanOperation(AttrAsStr(options, "booleanOp")); + baton->removeAlpha = sharp::AttrAsBool(options, "removeAlpha"); + baton->ensureAlpha = sharp::AttrAsBool(options, "ensureAlpha"); + if (options.Has("boolean")) { + baton->boolean = sharp::CreateInputDescriptor(options.Get("boolean").As()); + baton->booleanOp = sharp::GetBooleanOperation(sharp::AttrAsStr(options, "booleanOp")); } - if (HasAttr(options, "bandBoolOp")) { - baton->bandBoolOp = sharp::GetBooleanOperation(AttrAsStr(options, "bandBoolOp")); + if (options.Has("bandBoolOp")) { + baton->bandBoolOp = sharp::GetBooleanOperation(sharp::AttrAsStr(options, "bandBoolOp")); } - if (HasAttr(options, "convKernel")) { - v8::Local kernel = AttrAs(options, "convKernel"); - baton->convKernelWidth = AttrTo(kernel, "width"); - baton->convKernelHeight = AttrTo(kernel, "height"); - baton->convKernelScale = AttrTo(kernel, "scale"); - baton->convKernelOffset = AttrTo(kernel, "offset"); + if (options.Has("convKernel")) { + Napi::Object kernel = options.Get("convKernel").As(); + baton->convKernelWidth = sharp::AttrAsUint32(kernel, "width"); + baton->convKernelHeight = sharp::AttrAsUint32(kernel, "height"); + baton->convKernelScale = sharp::AttrAsDouble(kernel, "scale"); + baton->convKernelOffset = sharp::AttrAsDouble(kernel, "offset"); size_t const kernelSize = static_cast(baton->convKernelWidth * baton->convKernelHeight); baton->convKernel = std::unique_ptr(new double[kernelSize]); - v8::Local kdata = AttrAs(kernel, "kernel"); + Napi::Array kdata = kernel.Get("kernel").As(); for (unsigned int i = 0; i < kernelSize; i++) { - baton->convKernel[i] = AttrTo(kdata, i); + baton->convKernel[i] = sharp::AttrAsDouble(kdata, i); } } - if (HasAttr(options, "recombMatrix")) { + if (options.Has("recombMatrix")) { baton->recombMatrix = std::unique_ptr(new double[9]); - v8::Local recombMatrix = AttrAs(options, "recombMatrix"); + Napi::Array recombMatrix = options.Get("recombMatrix").As(); for (unsigned int i = 0; i < 9; i++) { - baton->recombMatrix[i] = AttrTo(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) { baton->colourspace = VIPS_INTERPRETATION_sRGB; } // Output - baton->formatOut = AttrAsStr(options, "formatOut"); - baton->fileOut = AttrAsStr(options, "fileOut"); - baton->withMetadata = AttrTo(options, "withMetadata"); - baton->withMetadataOrientation = AttrTo(options, "withMetadataOrientation"); + baton->formatOut = sharp::AttrAsStr(options, "formatOut"); + baton->fileOut = sharp::AttrAsStr(options, "fileOut"); + baton->withMetadata = sharp::AttrAsBool(options, "withMetadata"); + baton->withMetadataOrientation = sharp::AttrAsUint32(options, "withMetadataOrientation"); // Format-specific - baton->jpegQuality = AttrTo(options, "jpegQuality"); - baton->jpegProgressive = AttrTo(options, "jpegProgressive"); - baton->jpegChromaSubsampling = AttrAsStr(options, "jpegChromaSubsampling"); - baton->jpegTrellisQuantisation = AttrTo(options, "jpegTrellisQuantisation"); - baton->jpegQuantisationTable = AttrTo(options, "jpegQuantisationTable"); - baton->jpegOvershootDeringing = AttrTo(options, "jpegOvershootDeringing"); - baton->jpegOptimiseScans = AttrTo(options, "jpegOptimiseScans"); - baton->jpegOptimiseCoding = AttrTo(options, "jpegOptimiseCoding"); - baton->pngProgressive = AttrTo(options, "pngProgressive"); - baton->pngCompressionLevel = AttrTo(options, "pngCompressionLevel"); - baton->pngAdaptiveFiltering = AttrTo(options, "pngAdaptiveFiltering"); - baton->pngPalette = AttrTo(options, "pngPalette"); - baton->pngQuality = AttrTo(options, "pngQuality"); - baton->pngColours = AttrTo(options, "pngColours"); - baton->pngDither = AttrTo(options, "pngDither"); - baton->webpQuality = AttrTo(options, "webpQuality"); - baton->webpAlphaQuality = AttrTo(options, "webpAlphaQuality"); - baton->webpLossless = AttrTo(options, "webpLossless"); - baton->webpNearLossless = AttrTo(options, "webpNearLossless"); - baton->webpSmartSubsample = AttrTo(options, "webpSmartSubsample"); - baton->webpReductionEffort = AttrTo(options, "webpReductionEffort"); - baton->tiffQuality = AttrTo(options, "tiffQuality"); - baton->tiffPyramid = AttrTo(options, "tiffPyramid"); - baton->tiffSquash = AttrTo(options, "tiffSquash"); - baton->tiffTile = AttrTo(options, "tiffTile"); - baton->tiffTileWidth = AttrTo(options, "tiffTileWidth"); - baton->tiffTileHeight = AttrTo(options, "tiffTileHeight"); - baton->tiffXres = AttrTo(options, "tiffXres"); - baton->tiffYres = AttrTo(options, "tiffYres"); + baton->jpegQuality = sharp::AttrAsUint32(options, "jpegQuality"); + baton->jpegProgressive = sharp::AttrAsBool(options, "jpegProgressive"); + baton->jpegChromaSubsampling = sharp::AttrAsStr(options, "jpegChromaSubsampling"); + baton->jpegTrellisQuantisation = sharp::AttrAsBool(options, "jpegTrellisQuantisation"); + baton->jpegQuantisationTable = sharp::AttrAsUint32(options, "jpegQuantisationTable"); + baton->jpegOvershootDeringing = sharp::AttrAsBool(options, "jpegOvershootDeringing"); + baton->jpegOptimiseScans = sharp::AttrAsBool(options, "jpegOptimiseScans"); + baton->jpegOptimiseCoding = sharp::AttrAsBool(options, "jpegOptimiseCoding"); + baton->pngProgressive = sharp::AttrAsBool(options, "pngProgressive"); + baton->pngCompressionLevel = sharp::AttrAsUint32(options, "pngCompressionLevel"); + baton->pngAdaptiveFiltering = sharp::AttrAsBool(options, "pngAdaptiveFiltering"); + baton->pngPalette = sharp::AttrAsBool(options, "pngPalette"); + baton->pngQuality = sharp::AttrAsUint32(options, "pngQuality"); + baton->pngColours = sharp::AttrAsUint32(options, "pngColours"); + baton->pngDither = sharp::AttrAsDouble(options, "pngDither"); + baton->webpQuality = sharp::AttrAsUint32(options, "webpQuality"); + baton->webpAlphaQuality = sharp::AttrAsUint32(options, "webpAlphaQuality"); + baton->webpLossless = sharp::AttrAsBool(options, "webpLossless"); + baton->webpNearLossless = sharp::AttrAsBool(options, "webpNearLossless"); + baton->webpSmartSubsample = sharp::AttrAsBool(options, "webpSmartSubsample"); + baton->webpReductionEffort = sharp::AttrAsUint32(options, "webpReductionEffort"); + baton->tiffQuality = sharp::AttrAsUint32(options, "tiffQuality"); + baton->tiffPyramid = sharp::AttrAsBool(options, "tiffPyramid"); + baton->tiffSquash = sharp::AttrAsBool(options, "tiffSquash"); + baton->tiffTile = sharp::AttrAsBool(options, "tiffTile"); + baton->tiffTileWidth = sharp::AttrAsUint32(options, "tiffTileWidth"); + baton->tiffTileHeight = sharp::AttrAsUint32(options, "tiffTileHeight"); + baton->tiffXres = sharp::AttrAsDouble(options, "tiffXres"); + baton->tiffYres = sharp::AttrAsDouble(options, "tiffYres"); // tiff compression options baton->tiffCompression = static_cast( vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_COMPRESSION, - AttrAsStr(options, "tiffCompression").data())); + sharp::AttrAsStr(options, "tiffCompression").data())); baton->tiffPredictor = static_cast( vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_TIFF_PREDICTOR, - AttrAsStr(options, "tiffPredictor").data())); - baton->heifQuality = AttrTo(options, "heifQuality"); - baton->heifLossless = AttrTo(options, "heifLossless"); + sharp::AttrAsStr(options, "tiffPredictor").data())); + baton->heifQuality = sharp::AttrAsUint32(options, "heifQuality"); + baton->heifLossless = sharp::AttrAsBool(options, "heifLossless"); baton->heifCompression = static_cast( vips_enum_from_nick(nullptr, VIPS_TYPE_FOREIGN_HEIF_COMPRESSION, - AttrAsStr(options, "heifCompression").data())); + sharp::AttrAsStr(options, "heifCompression").data())); // Tile output - baton->tileSize = AttrTo(options, "tileSize"); - baton->tileOverlap = AttrTo(options, "tileOverlap"); - std::string tileContainer = AttrAsStr(options, "tileContainer"); - baton->tileAngle = AttrTo(options, "tileAngle"); - baton->tileBackground = AttrAsRgba(options, "tileBackground"); - baton->tileSkipBlanks = AttrTo(options, "tileSkipBlanks"); + baton->tileSize = sharp::AttrAsUint32(options, "tileSize"); + baton->tileOverlap = sharp::AttrAsUint32(options, "tileOverlap"); + baton->tileAngle = sharp::AttrAsInt32(options, "tileAngle"); + baton->tileBackground = sharp::AttrAsRgba(options, "tileBackground"); + baton->tileSkipBlanks = sharp::AttrAsInt32(options, "tileSkipBlanks"); + std::string tileContainer = sharp::AttrAsStr(options, "tileContainer"); if (tileContainer == "zip") { baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_ZIP; } else { baton->tileContainer = VIPS_FOREIGN_DZ_CONTAINER_FS; } - std::string tileLayout = AttrAsStr(options, "tileLayout"); + std::string tileLayout = sharp::AttrAsStr(options, "tileLayout"); if (tileLayout == "google") { baton->tileLayout = VIPS_FOREIGN_DZ_LAYOUT_GOOGLE; } else if (tileLayout == "zoomify") { @@ -1385,8 +1342,8 @@ NAN_METHOD(pipeline) { } else { baton->tileLayout = VIPS_FOREIGN_DZ_LAYOUT_DZ; } - baton->tileFormat = AttrAsStr(options, "tileFormat"); - std::string tileDepth = AttrAsStr(options, "tileDepth"); + baton->tileFormat = sharp::AttrAsStr(options, "tileFormat"); + std::string tileDepth = sharp::AttrAsStr(options, "tileDepth"); if (tileDepth == "onetile") { baton->tileDepth = VIPS_FOREIGN_DZ_DEPTH_ONETILE; } else if (tileDepth == "one") { @@ -1413,18 +1370,20 @@ NAN_METHOD(pipeline) { } // Function to notify of libvips warnings - Nan::Callback *debuglog = new Nan::Callback(AttrAs(options, "debuglog")); + Napi::Function debuglog = options.Get("debuglog").As(); // Function to notify of queue length changes - Nan::Callback *queueListener = new Nan::Callback(AttrAs(options, "queueListener")); + Napi::Function queueListener = options.Get("queueListener").As(); // Join queue for worker thread - Nan::Callback *callback = new Nan::Callback(info[1].As()); - Nan::AsyncQueueWorker(new PipelineWorker(callback, baton, debuglog, queueListener, buffersToPersist)); + Napi::Function callback = info[1].As(); + PipelineWorker *worker = new PipelineWorker(callback, baton, debuglog, queueListener); + worker->Queue(); // Increment queued task counter g_atomic_int_inc(&sharp::counterQueue); - v8::Local queueLength[1] = { Nan::New(sharp::counterQueue) }; - v8::Local recv = Nan::New(); - Nan::Call(*queueListener, recv, 1, queueLength); + Napi::Number queueLength = Napi::Number::New(info.Env(), static_cast(sharp::counterQueue)); + queueListener.Call(info.This(), { queueLength }); + + return info.Env().Undefined(); } diff --git a/src/pipeline.h b/src/pipeline.h index 084feffd..cdd6566a 100644 --- a/src/pipeline.h +++ b/src/pipeline.h @@ -19,12 +19,12 @@ #include #include -#include +#include #include #include "./common.h" -NAN_METHOD(pipeline); +Napi::Value pipeline(const Napi::CallbackInfo& info); enum class Canvas { CROP, diff --git a/src/sharp.cc b/src/sharp.cc index 4934d245..00b259b0 100644 --- a/src/sharp.cc +++ b/src/sharp.cc @@ -12,8 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include -#include +#include #include #include "common.h" @@ -22,33 +21,24 @@ #include "utilities.h" #include "stats.h" -NAN_MODULE_INIT(init) { +Napi::Object init(Napi::Env env, Napi::Object exports) { vips_init("sharp"); g_log_set_handler("VIPS", static_cast(G_LOG_LEVEL_WARNING), static_cast(sharp::VipsWarningCallback), nullptr); // Methods available to JavaScript - Nan::Set(target, Nan::New("metadata").ToLocalChecked(), - Nan::GetFunction(Nan::New(metadata)).ToLocalChecked()); - Nan::Set(target, Nan::New("pipeline").ToLocalChecked(), - Nan::GetFunction(Nan::New(pipeline)).ToLocalChecked()); - Nan::Set(target, Nan::New("cache").ToLocalChecked(), - Nan::GetFunction(Nan::New(cache)).ToLocalChecked()); - Nan::Set(target, Nan::New("concurrency").ToLocalChecked(), - Nan::GetFunction(Nan::New(concurrency)).ToLocalChecked()); - Nan::Set(target, Nan::New("counters").ToLocalChecked(), - Nan::GetFunction(Nan::New(counters)).ToLocalChecked()); - Nan::Set(target, Nan::New("simd").ToLocalChecked(), - Nan::GetFunction(Nan::New(simd)).ToLocalChecked()); - Nan::Set(target, Nan::New("libvipsVersion").ToLocalChecked(), - Nan::GetFunction(Nan::New(libvipsVersion)).ToLocalChecked()); - Nan::Set(target, Nan::New("format").ToLocalChecked(), - Nan::GetFunction(Nan::New(format)).ToLocalChecked()); - Nan::Set(target, Nan::New("_maxColourDistance").ToLocalChecked(), - Nan::GetFunction(Nan::New(_maxColourDistance)).ToLocalChecked()); - Nan::Set(target, Nan::New("stats").ToLocalChecked(), - Nan::GetFunction(Nan::New(stats)).ToLocalChecked()); + exports.Set("metadata", Napi::Function::New(env, metadata)); + exports.Set("pipeline", Napi::Function::New(env, pipeline)); + exports.Set("cache", Napi::Function::New(env, cache)); + exports.Set("concurrency", Napi::Function::New(env, concurrency)); + exports.Set("counters", Napi::Function::New(env, counters)); + exports.Set("simd", Napi::Function::New(env, simd)); + exports.Set("libvipsVersion", Napi::Function::New(env, libvipsVersion)); + exports.Set("format", Napi::Function::New(env, format)); + exports.Set("_maxColourDistance", Napi::Function::New(env, _maxColourDistance)); + exports.Set("stats", Napi::Function::New(env, stats)); + return exports; } -NAN_MODULE_WORKER_ENABLED(sharp, init) +NODE_API_MODULE(sharp, init) diff --git a/src/stats.cc b/src/stats.cc index adeaee17..16cc1539 100644 --- a/src/stats.cc +++ b/src/stats.cc @@ -16,28 +16,16 @@ #include #include -#include -#include +#include #include #include "common.h" #include "stats.h" -class StatsWorker : public Nan::AsyncWorker { +class StatsWorker : public Napi::AsyncWorker { public: - StatsWorker( - Nan::Callback *callback, StatsBaton *baton, Nan::Callback *debuglog, - std::vector> 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 const buffer) -> uint32_t { - SaveToPersistent(index, buffer); - return index + 1; - }); - } + StatsWorker(Napi::Function callback, StatsBaton *baton, Napi::Function debuglog) : + Napi::AsyncWorker(callback), baton(baton), debuglog(Napi::Persistent(debuglog)) {} ~StatsWorker() {} const int STAT_MIN_INDEX = 0; @@ -54,13 +42,9 @@ class StatsWorker : public Nan::AsyncWorker { void Execute() { // Decrement queued task counter g_atomic_int_dec_and_test(&sharp::counterQueue); - using Nan::New; - using Nan::Set; - using sharp::MaximumImageAlpha; vips::VImage image; sharp::ImageType imageType = sharp::ImageType::UNKNOWN; - try { std::tie(image, imageType) = OpenInput(baton->input); } catch (vips::VError const &err) { @@ -71,20 +55,23 @@ class StatsWorker : public Nan::AsyncWorker { vips::VImage stats = image.stats(); int const bands = image.bands(); for (int b = 1; b <= bands; b++) { - ChannelStats cStats(static_cast(stats.getpoint(STAT_MIN_INDEX, b).front()), - static_cast(stats.getpoint(STAT_MAX_INDEX, b).front()), - stats.getpoint(STAT_SUM_INDEX, b).front(), stats.getpoint(STAT_SQ_SUM_INDEX, b).front(), - stats.getpoint(STAT_MEAN_INDEX, b).front(), stats.getpoint(STAT_STDEV_INDEX, b).front(), - static_cast(stats.getpoint(STAT_MINX_INDEX, b).front()), - static_cast(stats.getpoint(STAT_MINY_INDEX, b).front()), - static_cast(stats.getpoint(STAT_MAXX_INDEX, b).front()), - static_cast(stats.getpoint(STAT_MAXY_INDEX, b).front())); + ChannelStats cStats( + static_cast(stats.getpoint(STAT_MIN_INDEX, b).front()), + static_cast(stats.getpoint(STAT_MAX_INDEX, b).front()), + stats.getpoint(STAT_SUM_INDEX, b).front(), + stats.getpoint(STAT_SQ_SUM_INDEX, b).front(), + stats.getpoint(STAT_MEAN_INDEX, b).front(), + stats.getpoint(STAT_STDEV_INDEX, b).front(), + static_cast(stats.getpoint(STAT_MINX_INDEX, b).front()), + static_cast(stats.getpoint(STAT_MINY_INDEX, b).front()), + static_cast(stats.getpoint(STAT_MAXX_INDEX, b).front()), + static_cast(stats.getpoint(STAT_MAXY_INDEX, b).front())); baton->channelStats.push_back(cStats); } // Image is not opaque when alpha layer is present and contains a non-mamixa value if (sharp::HasAlpha(image)) { double const minAlpha = static_cast(stats.getpoint(STAT_MIN_INDEX, bands).front()); - if (minAlpha != MaximumImageAlpha(image.interpretation())) { + if (minAlpha != sharp::MaximumImageAlpha(image.interpretation())) { baton->isOpaque = false; } } @@ -100,92 +87,77 @@ class StatsWorker : public Nan::AsyncWorker { vips_thread_shutdown(); } - void HandleOKCallback() { - using Nan::New; - using Nan::Set; - Nan::HandleScope(); - - v8::Local argv[2] = { Nan::Null(), Nan::Null() }; - if (!baton->err.empty()) { - argv[0] = Nan::Error(baton->err.data()); - } else { - // Stats Object - v8::Local info = New(); - v8::Local channels = New(); - - std::vector::iterator it; - int i = 0; - for (it = baton->channelStats.begin(); it < baton->channelStats.end(); it++, i++) { - v8::Local channelStat = New(); - Set(channelStat, New("min").ToLocalChecked(), New(it->min)); - Set(channelStat, New("max").ToLocalChecked(), New(it->max)); - Set(channelStat, New("sum").ToLocalChecked(), New(it->sum)); - Set(channelStat, New("squaresSum").ToLocalChecked(), New(it->squaresSum)); - Set(channelStat, New("mean").ToLocalChecked(), New(it->mean)); - Set(channelStat, New("stdev").ToLocalChecked(), New(it->stdev)); - Set(channelStat, New("minX").ToLocalChecked(), New(it->minX)); - Set(channelStat, New("minY").ToLocalChecked(), New(it->minY)); - Set(channelStat, New("maxX").ToLocalChecked(), New(it->maxX)); - Set(channelStat, New("maxY").ToLocalChecked(), New(it->maxY)); - Set(channels, i, channelStat); - } - - Set(info, New("channels").ToLocalChecked(), channels); - Set(info, New("isOpaque").ToLocalChecked(), New(baton->isOpaque)); - Set(info, New("entropy").ToLocalChecked(), New(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 const buffer) -> uint32_t { - GetFromPersistent(index); - return index + 1; - }); - delete baton->input; - delete baton; + void OnOK() { + Napi::Env env = Env(); + Napi::HandleScope scope(env); // Handle warnings std::string warning = sharp::VipsWarningPop(); while (!warning.empty()) { - v8::Local message[1] = { New(warning).ToLocalChecked() }; - debuglog->Call(1, message, async_resource); + debuglog.Call({ Napi::String::New(env, warning) }); warning = sharp::VipsWarningPop(); } - // Return to JavaScript - callback->Call(2, argv, async_resource); + if (baton->err.empty()) { + // Stats Object + Napi::Object info = Napi::Object::New(env); + Napi::Array channels = Napi::Array::New(env); + + std::vector::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: StatsBaton* baton; - Nan::Callback *debuglog; - std::vector> buffersToPersist; + Napi::FunctionReference debuglog; }; /* stats(options, callback) */ -NAN_METHOD(stats) { - using sharp::AttrTo; - - // Input Buffers must not undergo GC compaction during processing - std::vector> buffersToPersist; - +Napi::Value stats(const Napi::CallbackInfo& info) { // V8 objects are converted to non-V8 types held in the baton struct StatsBaton *baton = new StatsBaton; - v8::Local options = info[0].As(); + Napi::Object options = info[0].As(); // Input - baton->input = sharp::CreateInputDescriptor(sharp::AttrAs(options, "input"), buffersToPersist); + baton->input = sharp::CreateInputDescriptor(options.Get("input").As()); // Function to notify of libvips warnings - Nan::Callback *debuglog = new Nan::Callback(sharp::AttrAs(options, "debuglog")); + Napi::Function debuglog = options.Get("debuglog").As(); // Join queue for worker thread - Nan::Callback *callback = new Nan::Callback(info[1].As()); - Nan::AsyncQueueWorker(new StatsWorker(callback, baton, debuglog, buffersToPersist)); + Napi::Function callback = info[1].As(); + StatsWorker *worker = new StatsWorker(callback, baton, debuglog); + worker->Queue(); // Increment queued task counter g_atomic_int_inc(&sharp::counterQueue); + + return info.Env().Undefined(); } diff --git a/src/stats.h b/src/stats.h index 9288eba6..d2ec2e2c 100644 --- a/src/stats.h +++ b/src/stats.h @@ -16,7 +16,7 @@ #define SRC_STATS_H_ #include -#include +#include #include "./common.h" @@ -33,12 +33,8 @@ struct ChannelStats { int maxX; int maxY; - ChannelStats(): - min(0), max(0), sum(0), squaresSum(0), mean(0), stdev(0) - , 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): + 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), 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_ diff --git a/src/utilities.cc b/src/utilities.cc index 1bdcbb4b..0e88ea85 100644 --- a/src/utilities.cc +++ b/src/utilities.cc @@ -15,8 +15,7 @@ #include #include -#include -#include +#include #include #include @@ -24,183 +23,145 @@ #include "operations.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 */ -NAN_METHOD(cache) { - HandleScope(); +Napi::Value cache(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); // Set memory limit - if (info[0]->IsInt32()) { - vips_cache_set_max_mem(To(info[0]).FromJust() * 1048576); + if (info[0].IsNumber()) { + vips_cache_set_max_mem(info[0].As().Int32Value() * 1048576); } // Set file limit - if (info[1]->IsInt32()) { - vips_cache_set_max_files(To(info[1]).FromJust()); + if (info[1].IsNumber()) { + vips_cache_set_max_files(info[1].As().Int32Value()); } // Set items limit - if (info[2]->IsInt32()) { - vips_cache_set_max(To(info[2]).FromJust()); + if (info[2].IsNumber()) { + vips_cache_set_max(info[2].As().Int32Value()); } // Get memory stats - Local memory = New(); - Set(memory, New("current").ToLocalChecked(), - New(static_cast(round(vips_tracked_get_mem() / 1048576)))); - Set(memory, New("high").ToLocalChecked(), - New(static_cast(round(vips_tracked_get_mem_highwater() / 1048576)))); - Set(memory, New("max").ToLocalChecked(), - New(static_cast(round(vips_cache_get_max_mem() / 1048576)))); + Napi::Object memory = Napi::Object::New(env); + memory.Set("current", round(vips_tracked_get_mem() / 1048576)); + memory.Set("high", round(vips_tracked_get_mem_highwater() / 1048576)); + memory.Set("max", round(vips_cache_get_max_mem() / 1048576)); // Get file stats - Local files = New(); - Set(files, New("current").ToLocalChecked(), New(vips_tracked_get_files())); - Set(files, New("max").ToLocalChecked(), New(vips_cache_get_max_files())); + Napi::Object files = Napi::Object::New(env); + files.Set("current", vips_tracked_get_files()); + files.Set("max", vips_cache_get_max_files()); // Get item stats - Local items = New(); - Set(items, New("current").ToLocalChecked(), New(vips_cache_get_size())); - Set(items, New("max").ToLocalChecked(), New(vips_cache_get_max())); + Napi::Object items = Napi::Object::New(env); + items.Set("current", vips_cache_get_size()); + items.Set("max", vips_cache_get_max()); - Local cache = New(); - Set(cache, New("memory").ToLocalChecked(), memory); - Set(cache, New("files").ToLocalChecked(), files); - Set(cache, New("items").ToLocalChecked(), items); - info.GetReturnValue().Set(cache); + Napi::Object cache = Napi::Object::New(env); + cache.Set("memory", memory); + cache.Set("files", files); + cache.Set("items", items); + return cache; } /* Get and set size of thread pool */ -NAN_METHOD(concurrency) { - HandleScope(); - +Napi::Value concurrency(const Napi::CallbackInfo& info) { // Set concurrency - if (info[0]->IsInt32()) { - vips_concurrency_set(To(info[0]).FromJust()); + if (info[0].IsNumber()) { + vips_concurrency_set(info[0].As().Int32Value()); } // Get concurrency - info.GetReturnValue().Set(New(vips_concurrency_get())); + return Napi::Number::New(info.Env(), vips_concurrency_get()); } /* Get internal counters (queued tasks, processing tasks) */ -NAN_METHOD(counters) { - using sharp::counterProcess; - using sharp::counterQueue; - - HandleScope(); - Local counters = New(); - Set(counters, New("queue").ToLocalChecked(), New(counterQueue)); - Set(counters, New("process").ToLocalChecked(), New(counterProcess)); - info.GetReturnValue().Set(counters); +Napi::Value counters(const Napi::CallbackInfo& info) { + Napi::Object counters = Napi::Object::New(info.Env()); + counters.Set("queue", sharp::counterQueue); + counters.Set("process", sharp::counterProcess); + return counters; } /* Get and set use of SIMD vector unit instructions */ -NAN_METHOD(simd) { - HandleScope(); - +Napi::Value simd(const Napi::CallbackInfo& info) { // Set state - if (info[0]->IsBoolean()) { - vips_vector_set_enabled(To(info[0]).FromJust()); + if (info[0].IsBoolean()) { + vips_vector_set_enabled(info[0].As().Value()); } // Get state - info.GetReturnValue().Set(New(vips_vector_isenabled())); + return Napi::Boolean::New(info.Env(), vips_vector_isenabled()); } /* Get libvips version */ -NAN_METHOD(libvipsVersion) { - HandleScope(); +Napi::Value libvipsVersion(const Napi::CallbackInfo& info) { char version[9]; 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 */ -NAN_METHOD(format) { - HandleScope(); - - // Attribute names - Local attrId = New("id").ToLocalChecked(); - Local attrInput = New("input").ToLocalChecked(); - Local attrOutput = New("output").ToLocalChecked(); - Local attrFile = New("file").ToLocalChecked(); - Local attrBuffer = New("buffer").ToLocalChecked(); - Local attrStream = New("stream").ToLocalChecked(); - - // Which load/save operations are available for each compressed format? - Local format = New(); +Napi::Value format(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + Napi::Object format = Napi::Object::New(env); for (std::string const f : { "jpeg", "png", "webp", "tiff", "magick", "openslide", "dz", "ppm", "fits", "gif", "svg", "heif", "pdf", "vips" }) { // Input - Local hasInputFile = - New(vips_type_find("VipsOperation", (f + "load").c_str())); - Local hasInputBuffer = - New(vips_type_find("VipsOperation", (f + "load_buffer").c_str())); - Local input = New(); - Set(input, attrFile, hasInputFile); - Set(input, attrBuffer, hasInputBuffer); - Set(input, attrStream, hasInputBuffer); + Napi::Boolean hasInputFile = + Napi::Boolean::New(env, vips_type_find("VipsOperation", (f + "load").c_str())); + Napi::Boolean hasInputBuffer = + Napi::Boolean::New(env, vips_type_find("VipsOperation", (f + "load_buffer").c_str())); + Napi::Object input = Napi::Object::New(env); + input.Set("file", hasInputFile); + input.Set("buffer", hasInputBuffer); + input.Set("stream", hasInputBuffer); // Output - Local hasOutputFile = - New(vips_type_find("VipsOperation", (f + "save").c_str())); - Local hasOutputBuffer = - New(vips_type_find("VipsOperation", (f + "save_buffer").c_str())); - Local output = New(); - Set(output, attrFile, hasOutputFile); - Set(output, attrBuffer, hasOutputBuffer); - Set(output, attrStream, hasOutputBuffer); + Napi::Boolean hasOutputFile = + Napi::Boolean::New(env, vips_type_find("VipsOperation", (f + "save").c_str())); + Napi::Boolean hasOutputBuffer = + Napi::Boolean::New(env, vips_type_find("VipsOperation", (f + "save_buffer").c_str())); + Napi::Object output = Napi::Object::New(env); + output.Set("file", hasOutputFile); + output.Set("buffer", hasOutputBuffer); + output.Set("stream", hasOutputBuffer); // Other attributes - Local container = New(); - Local formatId = New(f).ToLocalChecked(); - Set(container, attrId, formatId); - Set(container, attrInput, input); - Set(container, attrOutput, output); + Napi::Object container = Napi::Object::New(env); + container.Set("id", f); + container.Set("input", input); + container.Set("output", output); // Add to set of formats - Set(format, formatId, container); + format.Set(f, container); } // Raw, uncompressed data - Local raw = New(); - Local rawId = New("raw").ToLocalChecked(); - Set(raw, attrId, rawId); - Set(format, rawId, raw); - Local supported = New(true); - Local unsupported = New(false); - Local rawInput = New(); - Set(rawInput, attrFile, unsupported); - Set(rawInput, attrBuffer, supported); - Set(rawInput, attrStream, supported); - Set(raw, attrInput, rawInput); - Local rawOutput = New(); - Set(rawOutput, attrFile, unsupported); - Set(rawOutput, attrBuffer, supported); - Set(rawOutput, attrStream, supported); - Set(raw, attrOutput, rawOutput); + Napi::Boolean supported = Napi::Boolean::New(env, true); + Napi::Boolean unsupported = Napi::Boolean::New(env, false); + Napi::Object rawInput = Napi::Object::New(env); + rawInput.Set("file", unsupported); + rawInput.Set("buffer", supported); + rawInput.Set("stream", supported); + Napi::Object rawOutput = Napi::Object::New(env); + rawOutput.Set("file", unsupported); + rawOutput.Set("buffer", supported); + rawOutput.Set("stream", supported); + Napi::Object raw = Napi::Object::New(env); + raw.Set("id", "raw"); + raw.Set("input", rawInput); + raw.Set("output", rawOutput); + format.Set("raw", raw); - info.GetReturnValue().Set(format); + return format; } /* @@ -208,65 +169,59 @@ NAN_METHOD(format) { Calculates the maximum colour distance using the DE2000 algorithm between two images of the same dimensions and number of channels. */ -NAN_METHOD(_maxColourDistance) { - using vips::VImage; - using vips::VError; - using sharp::DetermineImageType; - using sharp::ImageType; - using sharp::HasAlpha; - - HandleScope(); +Napi::Value _maxColourDistance(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); // Open input files VImage image1; - ImageType imageType1 = DetermineImageType(*Utf8String(info[0])); - if (imageType1 != ImageType::UNKNOWN) { + sharp::ImageType imageType1 = sharp::DetermineImageType(info[0].As().Utf8Value().data()); + if (imageType1 != sharp::ImageType::UNKNOWN) { try { - image1 = VImage::new_from_file(*Utf8String(info[0])); + image1 = VImage::new_from_file(info[0].As().Utf8Value().c_str()); } catch (...) { - return ThrowError("Input file 1 has corrupt header"); + throw Napi::Error::New(env, "Input file 1 has corrupt header"); } } 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; - ImageType imageType2 = DetermineImageType(*Utf8String(info[1])); - if (imageType2 != ImageType::UNKNOWN) { + sharp::ImageType imageType2 = sharp::DetermineImageType(info[1].As().Utf8Value().data()); + if (imageType2 != sharp::ImageType::UNKNOWN) { try { - image2 = VImage::new_from_file(*Utf8String(info[1])); + image2 = VImage::new_from_file(info[1].As().Utf8Value().c_str()); } catch (...) { - return ThrowError("Input file 2 has corrupt header"); + throw Napi::Error::New(env, "Input file 2 has corrupt header"); } } 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 if (image1.bands() != image2.bands()) { - return ThrowError("mismatchedBands"); + throw Napi::Error::New(env, "mismatchedBands"); } // Ensure same dimensions if (image1.width() != image2.width() || image1.height() != image2.height()) { - return ThrowError("mismatchedDimensions"); + throw Napi::Error::New(env, "mismatchedDimensions"); } double maxColourDistance; try { // Premultiply and remove alpha - if (HasAlpha(image1)) { + if (sharp::HasAlpha(image1)) { 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)); } // Calculate colour distance maxColourDistance = image1.dE00(image2).max(); - } catch (VError const &err) { - return ThrowError(err.what()); + } catch (vips::VError const &err) { + throw Napi::Error::New(env, err.what()); } // Clean up libvips' per-request data and threads vips_error_clear(); vips_thread_shutdown(); - info.GetReturnValue().Set(New(maxColourDistance)); + return Napi::Number::New(env, maxColourDistance); } diff --git a/src/utilities.h b/src/utilities.h index bd2de46a..e33b5415 100644 --- a/src/utilities.h +++ b/src/utilities.h @@ -15,14 +15,14 @@ #ifndef SRC_UTILITIES_H_ #define SRC_UTILITIES_H_ -#include +#include -NAN_METHOD(cache); -NAN_METHOD(concurrency); -NAN_METHOD(counters); -NAN_METHOD(simd); -NAN_METHOD(libvipsVersion); -NAN_METHOD(format); -NAN_METHOD(_maxColourDistance); +Napi::Value cache(const Napi::CallbackInfo& info); +Napi::Value concurrency(const Napi::CallbackInfo& info); +Napi::Value counters(const Napi::CallbackInfo& info); +Napi::Value simd(const Napi::CallbackInfo& info); +Napi::Value libvipsVersion(const Napi::CallbackInfo& info); +Napi::Value format(const Napi::CallbackInfo& info); +Napi::Value _maxColourDistance(const Napi::CallbackInfo& info); #endif // SRC_UTILITIES_H_ diff --git a/test/leak/sharp.supp b/test/leak/sharp.supp index dc51a152..d69f17d2 100644 --- a/test/leak/sharp.supp +++ b/test/leak/sharp.supp @@ -578,11 +578,14 @@ fun:_ZN4node20BackgroundTaskRunnerC1Ei } { - leak_nan_FunctionCallbackInfo + leak_napi_module_register Memcheck:Leak 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 diff --git a/test/unit/failOnError.js b/test/unit/failOnError.js index c2ce2141..e7e65925 100644 --- a/test/unit/failOnError.js +++ b/test/unit/failOnError.js @@ -48,8 +48,8 @@ describe('failOnError', function () { it('returns errors to callback for truncated JPEG', function (done) { sharp(fixtures.inputJpgTruncated).toBuffer(function (err, data, info) { assert.ok(err.message.includes('VipsJpeg: Premature end of JPEG file'), err); - assert.strictEqual(data, null); - assert.strictEqual(info, null); + assert.strictEqual(data, undefined); + assert.strictEqual(info, undefined); done(); }); }); @@ -57,8 +57,8 @@ describe('failOnError', function () { it('returns errors to callback for truncated PNG', function (done) { sharp(fixtures.inputPngTruncated).toBuffer(function (err, data, info) { assert.ok(err.message.includes('vipspng: libpng read error'), err); - assert.strictEqual(data, null); - assert.strictEqual(info, null); + assert.strictEqual(data, undefined); + assert.strictEqual(info, undefined); done(); }); });