1 |
|
2 |
|
3 |
|
4 | #include <numeric>
|
5 | #include <vector>
|
6 |
|
7 | #include <napi.h>
|
8 | #include <vips/vips8>
|
9 |
|
10 | #include "common.h"
|
11 | #include "metadata.h"
|
12 |
|
13 | class MetadataWorker : public Napi::AsyncWorker {
|
14 | public:
|
15 | MetadataWorker(Napi::Function callback, MetadataBaton *baton, Napi::Function debuglog) :
|
16 | Napi::AsyncWorker(callback), baton(baton), debuglog(Napi::Persistent(debuglog)) {}
|
17 | ~MetadataWorker() {}
|
18 |
|
19 | void Execute() {
|
20 |
|
21 | g_atomic_int_dec_and_test(&sharp::counterQueue);
|
22 |
|
23 | vips::VImage image;
|
24 | sharp::ImageType imageType = sharp::ImageType::UNKNOWN;
|
25 | try {
|
26 | std::tie(image, imageType) = OpenInput(baton->input);
|
27 | } catch (vips::VError const &err) {
|
28 | (baton->err).append(err.what());
|
29 | }
|
30 | if (imageType != sharp::ImageType::UNKNOWN) {
|
31 |
|
32 | baton->format = sharp::ImageTypeId(imageType);
|
33 |
|
34 | baton->width = image.width();
|
35 | baton->height = image.height();
|
36 | baton->space = vips_enum_nick(VIPS_TYPE_INTERPRETATION, image.interpretation());
|
37 | baton->channels = image.bands();
|
38 | baton->depth = vips_enum_nick(VIPS_TYPE_BAND_FORMAT, image.format());
|
39 | if (sharp::HasDensity(image)) {
|
40 | baton->density = sharp::GetDensity(image);
|
41 | }
|
42 | if (image.get_typeof("jpeg-chroma-subsample") == VIPS_TYPE_REF_STRING) {
|
43 | baton->chromaSubsampling = image.get_string("jpeg-chroma-subsample");
|
44 | }
|
45 | if (image.get_typeof("interlaced") == G_TYPE_INT) {
|
46 | baton->isProgressive = image.get_int("interlaced") == 1;
|
47 | }
|
48 | if (image.get_typeof("palette-bit-depth") == G_TYPE_INT) {
|
49 | baton->paletteBitDepth = image.get_int("palette-bit-depth");
|
50 | }
|
51 | if (image.get_typeof(VIPS_META_N_PAGES) == G_TYPE_INT) {
|
52 | baton->pages = image.get_int(VIPS_META_N_PAGES);
|
53 | }
|
54 | if (image.get_typeof(VIPS_META_PAGE_HEIGHT) == G_TYPE_INT) {
|
55 | baton->pageHeight = image.get_int(VIPS_META_PAGE_HEIGHT);
|
56 | }
|
57 | if (image.get_typeof("loop") == G_TYPE_INT) {
|
58 | baton->loop = image.get_int("loop");
|
59 | }
|
60 | if (image.get_typeof("delay") == VIPS_TYPE_ARRAY_INT) {
|
61 | baton->delay = image.get_array_int("delay");
|
62 | }
|
63 | if (image.get_typeof("heif-primary") == G_TYPE_INT) {
|
64 | baton->pagePrimary = image.get_int("heif-primary");
|
65 | }
|
66 | if (image.get_typeof("heif-compression") == VIPS_TYPE_REF_STRING) {
|
67 | baton->compression = image.get_string("heif-compression");
|
68 | }
|
69 | if (image.get_typeof(VIPS_META_RESOLUTION_UNIT) == VIPS_TYPE_REF_STRING) {
|
70 | baton->resolutionUnit = image.get_string(VIPS_META_RESOLUTION_UNIT);
|
71 | }
|
72 | if (image.get_typeof("magick-format") == VIPS_TYPE_REF_STRING) {
|
73 | baton->formatMagick = image.get_string("magick-format");
|
74 | }
|
75 | if (image.get_typeof("openslide.level-count") == VIPS_TYPE_REF_STRING) {
|
76 | int const levels = std::stoi(image.get_string("openslide.level-count"));
|
77 | for (int l = 0; l < levels; l++) {
|
78 | std::string prefix = "openslide.level[" + std::to_string(l) + "].";
|
79 | int const width = std::stoi(image.get_string((prefix + "width").data()));
|
80 | int const height = std::stoi(image.get_string((prefix + "height").data()));
|
81 | baton->levels.push_back(std::pair<int, int>(width, height));
|
82 | }
|
83 | }
|
84 | if (image.get_typeof(VIPS_META_N_SUBIFDS) == G_TYPE_INT) {
|
85 | baton->subifds = image.get_int(VIPS_META_N_SUBIFDS);
|
86 | }
|
87 | baton->hasProfile = sharp::HasProfile(image);
|
88 | if (image.get_typeof("background") == VIPS_TYPE_ARRAY_DOUBLE) {
|
89 | baton->background = image.get_array_double("background");
|
90 | }
|
91 |
|
92 | baton->hasAlpha = sharp::HasAlpha(image);
|
93 | baton->orientation = sharp::ExifOrientation(image);
|
94 |
|
95 | if (image.get_typeof(VIPS_META_EXIF_NAME) == VIPS_TYPE_BLOB) {
|
96 | size_t exifLength;
|
97 | void const *exif = image.get_blob(VIPS_META_EXIF_NAME, &exifLength);
|
98 | baton->exif = static_cast<char*>(g_malloc(exifLength));
|
99 | memcpy(baton->exif, exif, exifLength);
|
100 | baton->exifLength = exifLength;
|
101 | }
|
102 |
|
103 | if (image.get_typeof(VIPS_META_ICC_NAME) == VIPS_TYPE_BLOB) {
|
104 | size_t iccLength;
|
105 | void const *icc = image.get_blob(VIPS_META_ICC_NAME, &iccLength);
|
106 | baton->icc = static_cast<char*>(g_malloc(iccLength));
|
107 | memcpy(baton->icc, icc, iccLength);
|
108 | baton->iccLength = iccLength;
|
109 | }
|
110 |
|
111 | if (image.get_typeof(VIPS_META_IPTC_NAME) == VIPS_TYPE_BLOB) {
|
112 | size_t iptcLength;
|
113 | void const *iptc = image.get_blob(VIPS_META_IPTC_NAME, &iptcLength);
|
114 | baton->iptc = static_cast<char *>(g_malloc(iptcLength));
|
115 | memcpy(baton->iptc, iptc, iptcLength);
|
116 | baton->iptcLength = iptcLength;
|
117 | }
|
118 |
|
119 | if (image.get_typeof(VIPS_META_XMP_NAME) == VIPS_TYPE_BLOB) {
|
120 | size_t xmpLength;
|
121 | void const *xmp = image.get_blob(VIPS_META_XMP_NAME, &xmpLength);
|
122 | baton->xmp = static_cast<char *>(g_malloc(xmpLength));
|
123 | memcpy(baton->xmp, xmp, xmpLength);
|
124 | baton->xmpLength = xmpLength;
|
125 | }
|
126 |
|
127 | if (image.get_typeof(VIPS_META_PHOTOSHOP_NAME) == VIPS_TYPE_BLOB) {
|
128 | size_t tifftagPhotoshopLength;
|
129 | void const *tifftagPhotoshop = image.get_blob(VIPS_META_PHOTOSHOP_NAME, &tifftagPhotoshopLength);
|
130 | baton->tifftagPhotoshop = static_cast<char *>(g_malloc(tifftagPhotoshopLength));
|
131 | memcpy(baton->tifftagPhotoshop, tifftagPhotoshop, tifftagPhotoshopLength);
|
132 | baton->tifftagPhotoshopLength = tifftagPhotoshopLength;
|
133 | }
|
134 | }
|
135 |
|
136 |
|
137 | vips_error_clear();
|
138 | vips_thread_shutdown();
|
139 | }
|
140 |
|
141 | void OnOK() {
|
142 | Napi::Env env = Env();
|
143 | Napi::HandleScope scope(env);
|
144 |
|
145 |
|
146 | std::string warning = sharp::VipsWarningPop();
|
147 | while (!warning.empty()) {
|
148 | debuglog.MakeCallback(Receiver().Value(), { Napi::String::New(env, warning) });
|
149 | warning = sharp::VipsWarningPop();
|
150 | }
|
151 |
|
152 | if (baton->err.empty()) {
|
153 | Napi::Object info = Napi::Object::New(env);
|
154 | info.Set("format", baton->format);
|
155 | if (baton->input->bufferLength > 0) {
|
156 | info.Set("size", baton->input->bufferLength);
|
157 | }
|
158 | info.Set("width", baton->width);
|
159 | info.Set("height", baton->height);
|
160 | info.Set("space", baton->space);
|
161 | info.Set("channels", baton->channels);
|
162 | info.Set("depth", baton->depth);
|
163 | if (baton->density > 0) {
|
164 | info.Set("density", baton->density);
|
165 | }
|
166 | if (!baton->chromaSubsampling.empty()) {
|
167 | info.Set("chromaSubsampling", baton->chromaSubsampling);
|
168 | }
|
169 | info.Set("isProgressive", baton->isProgressive);
|
170 | if (baton->paletteBitDepth > 0) {
|
171 | info.Set("paletteBitDepth", baton->paletteBitDepth);
|
172 | }
|
173 | if (baton->pages > 0) {
|
174 | info.Set("pages", baton->pages);
|
175 | }
|
176 | if (baton->pageHeight > 0) {
|
177 | info.Set("pageHeight", baton->pageHeight);
|
178 | }
|
179 | if (baton->loop >= 0) {
|
180 | info.Set("loop", baton->loop);
|
181 | }
|
182 | if (!baton->delay.empty()) {
|
183 | int i = 0;
|
184 | Napi::Array delay = Napi::Array::New(env, static_cast<size_t>(baton->delay.size()));
|
185 | for (int const d : baton->delay) {
|
186 | delay.Set(i++, d);
|
187 | }
|
188 | info.Set("delay", delay);
|
189 | }
|
190 | if (baton->pagePrimary > -1) {
|
191 | info.Set("pagePrimary", baton->pagePrimary);
|
192 | }
|
193 | if (!baton->compression.empty()) {
|
194 | info.Set("compression", baton->compression);
|
195 | }
|
196 | if (!baton->resolutionUnit.empty()) {
|
197 | info.Set("resolutionUnit", baton->resolutionUnit == "in" ? "inch" : baton->resolutionUnit);
|
198 | }
|
199 | if (!baton->formatMagick.empty()) {
|
200 | info.Set("formatMagick", baton->formatMagick);
|
201 | }
|
202 | if (!baton->levels.empty()) {
|
203 | int i = 0;
|
204 | Napi::Array levels = Napi::Array::New(env, static_cast<size_t>(baton->levels.size()));
|
205 | for (std::pair<int, int> const &l : baton->levels) {
|
206 | Napi::Object level = Napi::Object::New(env);
|
207 | level.Set("width", l.first);
|
208 | level.Set("height", l.second);
|
209 | levels.Set(i++, level);
|
210 | }
|
211 | info.Set("levels", levels);
|
212 | }
|
213 | if (baton->subifds > 0) {
|
214 | info.Set("subifds", baton->subifds);
|
215 | }
|
216 | if (!baton->background.empty()) {
|
217 | if (baton->background.size() == 3) {
|
218 | Napi::Object background = Napi::Object::New(env);
|
219 | background.Set("r", baton->background[0]);
|
220 | background.Set("g", baton->background[1]);
|
221 | background.Set("b", baton->background[2]);
|
222 | info.Set("background", background);
|
223 | } else {
|
224 | info.Set("background", baton->background[0]);
|
225 | }
|
226 | }
|
227 | info.Set("hasProfile", baton->hasProfile);
|
228 | info.Set("hasAlpha", baton->hasAlpha);
|
229 | if (baton->orientation > 0) {
|
230 | info.Set("orientation", baton->orientation);
|
231 | }
|
232 | if (baton->exifLength > 0) {
|
233 | info.Set("exif", Napi::Buffer<char>::NewOrCopy(env, baton->exif, baton->exifLength, sharp::FreeCallback));
|
234 | }
|
235 | if (baton->iccLength > 0) {
|
236 | info.Set("icc", Napi::Buffer<char>::NewOrCopy(env, baton->icc, baton->iccLength, sharp::FreeCallback));
|
237 | }
|
238 | if (baton->iptcLength > 0) {
|
239 | info.Set("iptc", Napi::Buffer<char>::NewOrCopy(env, baton->iptc, baton->iptcLength, sharp::FreeCallback));
|
240 | }
|
241 | if (baton->xmpLength > 0) {
|
242 | info.Set("xmp", Napi::Buffer<char>::NewOrCopy(env, baton->xmp, baton->xmpLength, sharp::FreeCallback));
|
243 | }
|
244 | if (baton->tifftagPhotoshopLength > 0) {
|
245 | info.Set("tifftagPhotoshop",
|
246 | Napi::Buffer<char>::NewOrCopy(env, baton->tifftagPhotoshop,
|
247 | baton->tifftagPhotoshopLength, sharp::FreeCallback));
|
248 | }
|
249 | Callback().MakeCallback(Receiver().Value(), { env.Null(), info });
|
250 | } else {
|
251 | Callback().MakeCallback(Receiver().Value(), { Napi::Error::New(env, sharp::TrimEnd(baton->err)).Value() });
|
252 | }
|
253 |
|
254 | delete baton->input;
|
255 | delete baton;
|
256 | }
|
257 |
|
258 | private:
|
259 | MetadataBaton* baton;
|
260 | Napi::FunctionReference debuglog;
|
261 | };
|
262 |
|
263 |
|
264 |
|
265 |
|
266 | Napi::Value metadata(const Napi::CallbackInfo& info) {
|
267 |
|
268 | MetadataBaton *baton = new MetadataBaton;
|
269 | Napi::Object options = info[size_t(0)].As<Napi::Object>();
|
270 |
|
271 |
|
272 | baton->input = sharp::CreateInputDescriptor(options.Get("input").As<Napi::Object>());
|
273 |
|
274 |
|
275 | Napi::Function debuglog = options.Get("debuglog").As<Napi::Function>();
|
276 |
|
277 |
|
278 | Napi::Function callback = info[size_t(1)].As<Napi::Function>();
|
279 | MetadataWorker *worker = new MetadataWorker(callback, baton, debuglog);
|
280 | worker->Receiver().Set("options", options);
|
281 | worker->Queue();
|
282 |
|
283 |
|
284 | g_atomic_int_inc(&sharp::counterQueue);
|
285 |
|
286 | return info.Env().Undefined();
|
287 | }
|