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