UNPKG

6.72 kBtext/x-cView Raw
1// Copyright 2013 Lovell Fuller and others.
2// SPDX-License-Identifier: Apache-2.0
3
4#include <numeric>
5#include <vector>
6#include <iostream>
7
8#include <napi.h>
9#include <vips/vips8>
10
11#include "common.h"
12#include "stats.h"
13
14class StatsWorker : public Napi::AsyncWorker {
15 public:
16 StatsWorker(Napi::Function callback, StatsBaton *baton, Napi::Function debuglog) :
17 Napi::AsyncWorker(callback), baton(baton), debuglog(Napi::Persistent(debuglog)) {}
18 ~StatsWorker() {}
19
20 const int STAT_MIN_INDEX = 0;
21 const int STAT_MAX_INDEX = 1;
22 const int STAT_SUM_INDEX = 2;
23 const int STAT_SQ_SUM_INDEX = 3;
24 const int STAT_MEAN_INDEX = 4;
25 const int STAT_STDEV_INDEX = 5;
26 const int STAT_MINX_INDEX = 6;
27 const int STAT_MINY_INDEX = 7;
28 const int STAT_MAXX_INDEX = 8;
29 const int STAT_MAXY_INDEX = 9;
30
31 void Execute() {
32 // Decrement queued task counter
33 g_atomic_int_dec_and_test(&sharp::counterQueue);
34
35 vips::VImage image;
36 sharp::ImageType imageType = sharp::ImageType::UNKNOWN;
37 try {
38 std::tie(image, imageType) = OpenInput(baton->input);
39 } catch (vips::VError const &err) {
40 (baton->err).append(err.what());
41 }
42 if (imageType != sharp::ImageType::UNKNOWN) {
43 try {
44 vips::VImage stats = image.stats();
45 int const bands = image.bands();
46 for (int b = 1; b <= bands; b++) {
47 ChannelStats cStats(
48 static_cast<int>(stats.getpoint(STAT_MIN_INDEX, b).front()),
49 static_cast<int>(stats.getpoint(STAT_MAX_INDEX, b).front()),
50 stats.getpoint(STAT_SUM_INDEX, b).front(),
51 stats.getpoint(STAT_SQ_SUM_INDEX, b).front(),
52 stats.getpoint(STAT_MEAN_INDEX, b).front(),
53 stats.getpoint(STAT_STDEV_INDEX, b).front(),
54 static_cast<int>(stats.getpoint(STAT_MINX_INDEX, b).front()),
55 static_cast<int>(stats.getpoint(STAT_MINY_INDEX, b).front()),
56 static_cast<int>(stats.getpoint(STAT_MAXX_INDEX, b).front()),
57 static_cast<int>(stats.getpoint(STAT_MAXY_INDEX, b).front()));
58 baton->channelStats.push_back(cStats);
59 }
60 // Image is not opaque when alpha layer is present and contains a non-mamixa value
61 if (sharp::HasAlpha(image)) {
62 double const minAlpha = static_cast<double>(stats.getpoint(STAT_MIN_INDEX, bands).front());
63 if (minAlpha != sharp::MaximumImageAlpha(image.interpretation())) {
64 baton->isOpaque = false;
65 }
66 }
67 // Convert to greyscale
68 vips::VImage greyscale = image.colourspace(VIPS_INTERPRETATION_B_W)[0];
69 // Estimate entropy via histogram of greyscale value frequency
70 baton->entropy = std::abs(greyscale.hist_find().hist_entropy());
71 // Estimate sharpness via standard deviation of greyscale laplacian
72 if (image.width() > 1 || image.height() > 1) {
73 VImage laplacian = VImage::new_matrixv(3, 3,
74 0.0, 1.0, 0.0,
75 1.0, -4.0, 1.0,
76 0.0, 1.0, 0.0);
77 laplacian.set("scale", 9.0);
78 baton->sharpness = greyscale.conv(laplacian).deviate();
79 }
80 // Most dominant sRGB colour via 4096-bin 3D histogram
81 vips::VImage hist = sharp::RemoveAlpha(image)
82 .colourspace(VIPS_INTERPRETATION_sRGB)
83 .hist_find_ndim(VImage::option()->set("bins", 16));
84 std::complex<double> maxpos = hist.maxpos();
85 int const dx = static_cast<int>(std::real(maxpos));
86 int const dy = static_cast<int>(std::imag(maxpos));
87 std::vector<double> pel = hist(dx, dy);
88 int const dz = std::distance(pel.begin(), std::find(pel.begin(), pel.end(), hist.max()));
89 baton->dominantRed = dx * 16 + 8;
90 baton->dominantGreen = dy * 16 + 8;
91 baton->dominantBlue = dz * 16 + 8;
92 } catch (vips::VError const &err) {
93 (baton->err).append(err.what());
94 }
95 }
96
97 // Clean up
98 vips_error_clear();
99 vips_thread_shutdown();
100 }
101
102 void OnOK() {
103 Napi::Env env = Env();
104 Napi::HandleScope scope(env);
105
106 // Handle warnings
107 std::string warning = sharp::VipsWarningPop();
108 while (!warning.empty()) {
109 debuglog.MakeCallback(Receiver().Value(), { Napi::String::New(env, warning) });
110 warning = sharp::VipsWarningPop();
111 }
112
113 if (baton->err.empty()) {
114 // Stats Object
115 Napi::Object info = Napi::Object::New(env);
116 Napi::Array channels = Napi::Array::New(env);
117
118 std::vector<ChannelStats>::iterator it;
119 int i = 0;
120 for (it = baton->channelStats.begin(); it < baton->channelStats.end(); it++, i++) {
121 Napi::Object channelStat = Napi::Object::New(env);
122 channelStat.Set("min", it->min);
123 channelStat.Set("max", it->max);
124 channelStat.Set("sum", it->sum);
125 channelStat.Set("squaresSum", it->squaresSum);
126 channelStat.Set("mean", it->mean);
127 channelStat.Set("stdev", it->stdev);
128 channelStat.Set("minX", it->minX);
129 channelStat.Set("minY", it->minY);
130 channelStat.Set("maxX", it->maxX);
131 channelStat.Set("maxY", it->maxY);
132 channels.Set(i, channelStat);
133 }
134
135 info.Set("channels", channels);
136 info.Set("isOpaque", baton->isOpaque);
137 info.Set("entropy", baton->entropy);
138 info.Set("sharpness", baton->sharpness);
139 Napi::Object dominant = Napi::Object::New(env);
140 dominant.Set("r", baton->dominantRed);
141 dominant.Set("g", baton->dominantGreen);
142 dominant.Set("b", baton->dominantBlue);
143 info.Set("dominant", dominant);
144 Callback().MakeCallback(Receiver().Value(), { env.Null(), info });
145 } else {
146 Callback().MakeCallback(Receiver().Value(), { Napi::Error::New(env, sharp::TrimEnd(baton->err)).Value() });
147 }
148
149 delete baton->input;
150 delete baton;
151 }
152
153 private:
154 StatsBaton* baton;
155 Napi::FunctionReference debuglog;
156};
157
158/*
159 stats(options, callback)
160*/
161Napi::Value stats(const Napi::CallbackInfo& info) {
162 // V8 objects are converted to non-V8 types held in the baton struct
163 StatsBaton *baton = new StatsBaton;
164 Napi::Object options = info[size_t(0)].As<Napi::Object>();
165
166 // Input
167 baton->input = sharp::CreateInputDescriptor(options.Get("input").As<Napi::Object>());
168 baton->input->access = VIPS_ACCESS_RANDOM;
169
170 // Function to notify of libvips warnings
171 Napi::Function debuglog = options.Get("debuglog").As<Napi::Function>();
172
173 // Join queue for worker thread
174 Napi::Function callback = info[size_t(1)].As<Napi::Function>();
175 StatsWorker *worker = new StatsWorker(callback, baton, debuglog);
176 worker->Receiver().Set("options", options);
177 worker->Queue();
178
179 // Increment queued task counter
180 g_atomic_int_inc(&sharp::counterQueue);
181
182 return info.Env().Undefined();
183}