Spaces:
Sleeping
Sleeping
| /*! | |
| Copyright 2013 Lovell Fuller and others. | |
| SPDX-License-Identifier: Apache-2.0 | |
| */ | |
| class StatsWorker : public Napi::AsyncWorker { | |
| public: | |
| 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; | |
| const int STAT_MAX_INDEX = 1; | |
| const int STAT_SUM_INDEX = 2; | |
| const int STAT_SQ_SUM_INDEX = 3; | |
| const int STAT_MEAN_INDEX = 4; | |
| const int STAT_STDEV_INDEX = 5; | |
| const int STAT_MINX_INDEX = 6; | |
| const int STAT_MINY_INDEX = 7; | |
| const int STAT_MAXX_INDEX = 8; | |
| const int STAT_MAXY_INDEX = 9; | |
| void Execute() { | |
| // Decrement queued task counter | |
| sharp::counterQueue--; | |
| vips::VImage image; | |
| sharp::ImageType imageType = sharp::ImageType::UNKNOWN; | |
| try { | |
| std::tie(image, imageType) = OpenInput(baton->input); | |
| } catch (vips::VError const &err) { | |
| (baton->err).append(err.what()); | |
| } | |
| if (imageType != sharp::ImageType::UNKNOWN) { | |
| try { | |
| vips::VImage stats = image.stats(); | |
| int const bands = image.bands(); | |
| for (int b = 1; b <= bands; b++) { | |
| ChannelStats cStats( | |
| static_cast<int>(stats.getpoint(STAT_MIN_INDEX, b).front()), | |
| static_cast<int>(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<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); | |
| } | |
| // Image is not opaque when alpha layer is present and contains a non-mamixa value | |
| if (image.has_alpha()) { | |
| double const minAlpha = static_cast<double>(stats.getpoint(STAT_MIN_INDEX, bands).front()); | |
| if (minAlpha != vips_interpretation_max_alpha(image.interpretation())) { | |
| baton->isOpaque = false; | |
| } | |
| } | |
| // Convert to greyscale | |
| vips::VImage greyscale = image.colourspace(VIPS_INTERPRETATION_B_W)[0]; | |
| // Estimate entropy via histogram of greyscale value frequency | |
| baton->entropy = std::abs(greyscale.hist_find().hist_entropy()); | |
| // Estimate sharpness via standard deviation of greyscale laplacian | |
| if (image.width() > 1 || image.height() > 1) { | |
| VImage laplacian = VImage::new_matrixv(3, 3, | |
| 0.0, 1.0, 0.0, | |
| 1.0, -4.0, 1.0, | |
| 0.0, 1.0, 0.0); | |
| laplacian.set("scale", 9.0); | |
| baton->sharpness = greyscale.conv(laplacian).deviate(); | |
| } | |
| // Most dominant sRGB colour via 4096-bin 3D histogram | |
| vips::VImage hist = sharp::RemoveAlpha(image) | |
| .colourspace(VIPS_INTERPRETATION_sRGB) | |
| .hist_find_ndim(VImage::option()->set("bins", 16)); | |
| std::complex<double> maxpos = hist.maxpos(); | |
| int const dx = static_cast<int>(std::real(maxpos)); | |
| int const dy = static_cast<int>(std::imag(maxpos)); | |
| std::vector<double> pel = hist(dx, dy); | |
| int const dz = std::distance(pel.begin(), std::find(pel.begin(), pel.end(), hist.max())); | |
| baton->dominantRed = dx * 16 + 8; | |
| baton->dominantGreen = dy * 16 + 8; | |
| baton->dominantBlue = dz * 16 + 8; | |
| } catch (vips::VError const &err) { | |
| (baton->err).append(err.what()); | |
| } | |
| } | |
| // Clean up | |
| vips_error_clear(); | |
| vips_thread_shutdown(); | |
| } | |
| void OnOK() { | |
| Napi::Env env = Env(); | |
| Napi::HandleScope scope(env); | |
| // Handle warnings | |
| std::string warning = sharp::VipsWarningPop(); | |
| while (!warning.empty()) { | |
| debuglog.Call(Receiver().Value(), { Napi::String::New(env, warning) }); | |
| warning = sharp::VipsWarningPop(); | |
| } | |
| if (baton->err.empty()) { | |
| // 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); | |
| info.Set("sharpness", baton->sharpness); | |
| Napi::Object dominant = Napi::Object::New(env); | |
| dominant.Set("r", baton->dominantRed); | |
| dominant.Set("g", baton->dominantGreen); | |
| dominant.Set("b", baton->dominantBlue); | |
| info.Set("dominant", dominant); | |
| Callback().Call(Receiver().Value(), { env.Null(), info }); | |
| } else { | |
| Callback().Call(Receiver().Value(), { Napi::Error::New(env, sharp::TrimEnd(baton->err)).Value() }); | |
| } | |
| delete baton->input; | |
| delete baton; | |
| } | |
| private: | |
| StatsBaton* baton; | |
| Napi::FunctionReference debuglog; | |
| }; | |
| /* | |
| stats(options, callback) | |
| */ | |
| Napi::Value stats(const Napi::CallbackInfo& info) { | |
| // V8 objects are converted to non-V8 types held in the baton struct | |
| StatsBaton *baton = new StatsBaton; | |
| Napi::Object options = info[size_t(0)].As<Napi::Object>(); | |
| // Input | |
| baton->input = sharp::CreateInputDescriptor(options.Get("input").As<Napi::Object>()); | |
| baton->input->access = VIPS_ACCESS_RANDOM; | |
| // Function to notify of libvips warnings | |
| Napi::Function debuglog = options.Get("debuglog").As<Napi::Function>(); | |
| // Join queue for worker thread | |
| Napi::Function callback = info[size_t(1)].As<Napi::Function>(); | |
| StatsWorker *worker = new StatsWorker(callback, baton, debuglog); | |
| worker->Receiver().Set("options", options); | |
| worker->Queue(); | |
| // Increment queued task counter | |
| sharp::counterQueue++; | |
| return info.Env().Undefined(); | |
| } | |