UNPKG

12.2 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
7#include <napi.h>
8#include <vips/vips8>
9
10#include "common.h"
11#include "metadata.h"
12
13static void* readPNGComment(VipsImage *image, const char *field, GValue *value, void *p);
14
15class 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 // Decrement queued task counter
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 // Image type
34 baton->format = sharp::ImageTypeId(imageType);
35 // VipsImage attributes
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 // Derived attributes
94 baton->hasAlpha = sharp::HasAlpha(image);
95 baton->orientation = sharp::ExifOrientation(image);
96 // EXIF
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 // ICC profile
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 // IPTC
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 // XMP
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 // TIFFTAG_PHOTOSHOP
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 // PNG comments
137 vips_image_map(image.get_image(), readPNGComment, &baton->comments);
138 }
139
140 // Clean up
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 // Handle warnings
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 metadata(options, callback)
280*/
281Napi::Value metadata(const Napi::CallbackInfo& info) {
282 // V8 objects are converted to non-V8 types held in the baton struct
283 MetadataBaton *baton = new MetadataBaton;
284 Napi::Object options = info[size_t(0)].As<Napi::Object>();
285
286 // Input
287 baton->input = sharp::CreateInputDescriptor(options.Get("input").As<Napi::Object>());
288
289 // Function to notify of libvips warnings
290 Napi::Function debuglog = options.Get("debuglog").As<Napi::Function>();
291
292 // Join queue for worker thread
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 // Increment queued task counter
299 sharp::counterQueue++;
300
301 return info.Env().Undefined();
302}
303
304const char *PNG_COMMENT_START = "png-comment-";
305const int PNG_COMMENT_START_LEN = strlen(PNG_COMMENT_START);
306
307static 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++; // Skip the hyphen
315 comments->push_back(std::make_pair(keyword, str));
316 }
317 }
318
319 return NULL;
320}