// Copyright 2013 Lovell Fuller and others. // SPDX-License-Identifier: Apache-2.0 #include #include #include #include #include "common.h" #include "metadata.h" class MetadataWorker : public Napi::AsyncWorker { public: MetadataWorker(Napi::Function callback, MetadataBaton *baton, Napi::Function debuglog) : Napi::AsyncWorker(callback), baton(baton), debuglog(Napi::Persistent(debuglog)) {} ~MetadataWorker() {} void Execute() { // Decrement queued task counter g_atomic_int_dec_and_test(&sharp::counterQueue); vips::VImage image; sharp::ImageType imageType = sharp::ImageType::UNKNOWN; try { std::tie(image, imageType) = OpenInput(baton->input); } catch (vips::VError const &err) { (baton->err).append(err.what()); } if (imageType != sharp::ImageType::UNKNOWN) { // Image type baton->format = sharp::ImageTypeId(imageType); // VipsImage attributes baton->width = image.width(); baton->height = image.height(); baton->space = vips_enum_nick(VIPS_TYPE_INTERPRETATION, image.interpretation()); baton->channels = image.bands(); baton->depth = vips_enum_nick(VIPS_TYPE_BAND_FORMAT, image.format()); if (sharp::HasDensity(image)) { baton->density = sharp::GetDensity(image); } if (image.get_typeof("jpeg-chroma-subsample") == VIPS_TYPE_REF_STRING) { baton->chromaSubsampling = image.get_string("jpeg-chroma-subsample"); } if (image.get_typeof("interlaced") == G_TYPE_INT) { baton->isProgressive = image.get_int("interlaced") == 1; } if (image.get_typeof("palette-bit-depth") == G_TYPE_INT) { baton->paletteBitDepth = image.get_int("palette-bit-depth"); } if (image.get_typeof(VIPS_META_N_PAGES) == G_TYPE_INT) { baton->pages = image.get_int(VIPS_META_N_PAGES); } if (image.get_typeof(VIPS_META_PAGE_HEIGHT) == G_TYPE_INT) { baton->pageHeight = image.get_int(VIPS_META_PAGE_HEIGHT); } if (image.get_typeof("loop") == G_TYPE_INT) { baton->loop = image.get_int("loop"); } if (image.get_typeof("delay") == VIPS_TYPE_ARRAY_INT) { baton->delay = image.get_array_int("delay"); } if (image.get_typeof("heif-primary") == G_TYPE_INT) { baton->pagePrimary = image.get_int("heif-primary"); } if (image.get_typeof("heif-compression") == VIPS_TYPE_REF_STRING) { baton->compression = image.get_string("heif-compression"); } if (image.get_typeof(VIPS_META_RESOLUTION_UNIT) == VIPS_TYPE_REF_STRING) { baton->resolutionUnit = image.get_string(VIPS_META_RESOLUTION_UNIT); } if (image.get_typeof("magick-format") == VIPS_TYPE_REF_STRING) { baton->formatMagick = image.get_string("magick-format"); } if (image.get_typeof("openslide.level-count") == VIPS_TYPE_REF_STRING) { int const levels = std::stoi(image.get_string("openslide.level-count")); for (int l = 0; l < levels; l++) { std::string prefix = "openslide.level[" + std::to_string(l) + "]."; int const width = std::stoi(image.get_string((prefix + "width").data())); int const height = std::stoi(image.get_string((prefix + "height").data())); baton->levels.push_back(std::pair(width, height)); } } if (image.get_typeof(VIPS_META_N_SUBIFDS) == G_TYPE_INT) { baton->subifds = image.get_int(VIPS_META_N_SUBIFDS); } baton->hasProfile = sharp::HasProfile(image); if (image.get_typeof("background") == VIPS_TYPE_ARRAY_DOUBLE) { baton->background = image.get_array_double("background"); } // Derived attributes baton->hasAlpha = sharp::HasAlpha(image); baton->orientation = sharp::ExifOrientation(image); // EXIF if (image.get_typeof(VIPS_META_EXIF_NAME) == VIPS_TYPE_BLOB) { size_t exifLength; void const *exif = image.get_blob(VIPS_META_EXIF_NAME, &exifLength); baton->exif = static_cast(g_malloc(exifLength)); memcpy(baton->exif, exif, exifLength); baton->exifLength = exifLength; } // ICC profile if (image.get_typeof(VIPS_META_ICC_NAME) == VIPS_TYPE_BLOB) { size_t iccLength; void const *icc = image.get_blob(VIPS_META_ICC_NAME, &iccLength); baton->icc = static_cast(g_malloc(iccLength)); memcpy(baton->icc, icc, iccLength); baton->iccLength = iccLength; } // IPTC if (image.get_typeof(VIPS_META_IPTC_NAME) == VIPS_TYPE_BLOB) { size_t iptcLength; void const *iptc = image.get_blob(VIPS_META_IPTC_NAME, &iptcLength); baton->iptc = static_cast(g_malloc(iptcLength)); memcpy(baton->iptc, iptc, iptcLength); baton->iptcLength = iptcLength; } // XMP if (image.get_typeof(VIPS_META_XMP_NAME) == VIPS_TYPE_BLOB) { size_t xmpLength; void const *xmp = image.get_blob(VIPS_META_XMP_NAME, &xmpLength); baton->xmp = static_cast(g_malloc(xmpLength)); memcpy(baton->xmp, xmp, xmpLength); baton->xmpLength = xmpLength; } // TIFFTAG_PHOTOSHOP if (image.get_typeof(VIPS_META_PHOTOSHOP_NAME) == VIPS_TYPE_BLOB) { size_t tifftagPhotoshopLength; void const *tifftagPhotoshop = image.get_blob(VIPS_META_PHOTOSHOP_NAME, &tifftagPhotoshopLength); baton->tifftagPhotoshop = static_cast(g_malloc(tifftagPhotoshopLength)); memcpy(baton->tifftagPhotoshop, tifftagPhotoshop, tifftagPhotoshopLength); baton->tifftagPhotoshopLength = tifftagPhotoshopLength; } } // Clean up vips_error_clear(); vips_thread_shutdown(); } void OnOK() { Napi::Env env = Env(); Napi::HandleScope scope(env); // Handle warnings std::string warning = sharp::VipsWarningPop(); while (!warning.empty()) { debuglog.MakeCallback(Receiver().Value(), { Napi::String::New(env, warning) }); warning = sharp::VipsWarningPop(); } if (baton->err.empty()) { Napi::Object info = Napi::Object::New(env); info.Set("format", baton->format); if (baton->input->bufferLength > 0) { info.Set("size", baton->input->bufferLength); } info.Set("width", baton->width); info.Set("height", baton->height); info.Set("space", baton->space); info.Set("channels", baton->channels); info.Set("depth", baton->depth); if (baton->density > 0) { info.Set("density", baton->density); } if (!baton->chromaSubsampling.empty()) { info.Set("chromaSubsampling", baton->chromaSubsampling); } info.Set("isProgressive", baton->isProgressive); if (baton->paletteBitDepth > 0) { info.Set("paletteBitDepth", baton->paletteBitDepth); } if (baton->pages > 0) { info.Set("pages", baton->pages); } if (baton->pageHeight > 0) { info.Set("pageHeight", baton->pageHeight); } if (baton->loop >= 0) { info.Set("loop", baton->loop); } if (!baton->delay.empty()) { int i = 0; Napi::Array delay = Napi::Array::New(env, static_cast(baton->delay.size())); for (int const d : baton->delay) { delay.Set(i++, d); } info.Set("delay", delay); } if (baton->pagePrimary > -1) { info.Set("pagePrimary", baton->pagePrimary); } if (!baton->compression.empty()) { info.Set("compression", baton->compression); } if (!baton->resolutionUnit.empty()) { info.Set("resolutionUnit", baton->resolutionUnit == "in" ? "inch" : baton->resolutionUnit); } if (!baton->formatMagick.empty()) { info.Set("formatMagick", baton->formatMagick); } if (!baton->levels.empty()) { int i = 0; Napi::Array levels = Napi::Array::New(env, static_cast(baton->levels.size())); for (std::pair const &l : baton->levels) { Napi::Object level = Napi::Object::New(env); level.Set("width", l.first); level.Set("height", l.second); levels.Set(i++, level); } info.Set("levels", levels); } if (baton->subifds > 0) { info.Set("subifds", baton->subifds); } if (!baton->background.empty()) { if (baton->background.size() == 3) { Napi::Object background = Napi::Object::New(env); background.Set("r", baton->background[0]); background.Set("g", baton->background[1]); background.Set("b", baton->background[2]); info.Set("background", background); } else { info.Set("background", baton->background[0]); } } info.Set("hasProfile", baton->hasProfile); info.Set("hasAlpha", baton->hasAlpha); if (baton->orientation > 0) { info.Set("orientation", baton->orientation); } if (baton->exifLength > 0) { info.Set("exif", Napi::Buffer::NewOrCopy(env, baton->exif, baton->exifLength, sharp::FreeCallback)); } if (baton->iccLength > 0) { info.Set("icc", Napi::Buffer::NewOrCopy(env, baton->icc, baton->iccLength, sharp::FreeCallback)); } if (baton->iptcLength > 0) { info.Set("iptc", Napi::Buffer::NewOrCopy(env, baton->iptc, baton->iptcLength, sharp::FreeCallback)); } if (baton->xmpLength > 0) { info.Set("xmp", Napi::Buffer::NewOrCopy(env, baton->xmp, baton->xmpLength, sharp::FreeCallback)); } if (baton->tifftagPhotoshopLength > 0) { info.Set("tifftagPhotoshop", Napi::Buffer::NewOrCopy(env, baton->tifftagPhotoshop, baton->tifftagPhotoshopLength, sharp::FreeCallback)); } Callback().MakeCallback(Receiver().Value(), { env.Null(), info }); } else { Callback().MakeCallback(Receiver().Value(), { Napi::Error::New(env, sharp::TrimEnd(baton->err)).Value() }); } delete baton->input; delete baton; } private: MetadataBaton* baton; Napi::FunctionReference debuglog; }; /* metadata(options, callback) */ Napi::Value metadata(const Napi::CallbackInfo& info) { // V8 objects are converted to non-V8 types held in the baton struct MetadataBaton *baton = new MetadataBaton; Napi::Object options = info[size_t(0)].As(); // Input baton->input = sharp::CreateInputDescriptor(options.Get("input").As()); // Function to notify of libvips warnings Napi::Function debuglog = options.Get("debuglog").As(); // Join queue for worker thread Napi::Function callback = info[size_t(1)].As(); MetadataWorker *worker = new MetadataWorker(callback, baton, debuglog); worker->Receiver().Set("options", options); worker->Queue(); // Increment queued task counter g_atomic_int_inc(&sharp::counterQueue); return info.Env().Undefined(); }