1 |
|
2 |
|
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 |
|
14 | class 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 |
|
33 | 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 |
|
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 |
|
68 | vips::VImage greyscale = image.colourspace(VIPS_INTERPRETATION_B_W)[0];
|
69 |
|
70 | baton->entropy = std::abs(greyscale.hist_find().hist_entropy());
|
71 |
|
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 |
|
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 |
|
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 |
|
107 | std::string warning = sharp::VipsWarningPop();
|
108 | while (!warning.empty()) {
|
109 | debuglog.Call(Receiver().Value(), { Napi::String::New(env, warning) });
|
110 | warning = sharp::VipsWarningPop();
|
111 | }
|
112 |
|
113 | if (baton->err.empty()) {
|
114 |
|
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().Call(Receiver().Value(), { env.Null(), info });
|
145 | } else {
|
146 | Callback().Call(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 |
|
160 |
|
161 | Napi::Value stats(const Napi::CallbackInfo& info) {
|
162 |
|
163 | StatsBaton *baton = new StatsBaton;
|
164 | Napi::Object options = info[size_t(0)].As<Napi::Object>();
|
165 |
|
166 |
|
167 | baton->input = sharp::CreateInputDescriptor(options.Get("input").As<Napi::Object>());
|
168 | baton->input->access = VIPS_ACCESS_RANDOM;
|
169 |
|
170 |
|
171 | Napi::Function debuglog = options.Get("debuglog").As<Napi::Function>();
|
172 |
|
173 |
|
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 |
|
180 | sharp::counterQueue++;
|
181 |
|
182 | return info.Env().Undefined();
|
183 | }
|