/************************************************************************ * LibXISF - library to load and save XISF files * * Copyright (C) 2023 DuĊĦan Poizl * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation, either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see .* ************************************************************************/ #include "libxisf.h" #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_ZSTD #include #endif #include "streambuffer.h" namespace LibXISF { std::vector splitString(const std::string &str, char delimiter); void deserializeVariant(const pugi::xml_node &node, Variant &variant, const ByteArray &data); void serializeVariant(pugi::xml_node &node, const Variant &variant); Variant variantFromString(Variant::Type type, const String &str); static std::unordered_map imageTypeToEnum; static std::unordered_map imageTypeToString; static std::unordered_map sampleFormatToEnum; static std::unordered_map sampleFormatToString; static std::unordered_map colorSpaceToEnum; static std::unordered_map colorSpaceToString; static DataBlock::CompressionCodec compressionCodecOverride = DataBlock::None; static bool byteShuffleOverride = false; static int compressionLevelOverride = -1; static const std::unordered_map> fitsNameToPropertyIdTypeConvert = { {"OBSERVER", {"Observer:Name", Variant::Type::String}}, {"RADECSYS", {"Observation:CelestialReferenceSystem", Variant::Type::String}}, {"CRVAL1", {"Observation:Center:Dec", Variant::Type::Float64}}, {"CRVAL2", {"Observation:Center:RA", Variant::Type::Float64}}, {"CRPIX1", {"Observation:Center:X", Variant::Type::Float64}}, {"CRPIX2", {"Observation:Center:Y", Variant::Type::Float64}}, {"EQUINOX", {"Observation:Equinox", Variant::Type::Float64}}, {"SITELAT", {"Observation:Location:Latitude", Variant::Type::Float64}}, {"SITELONG", {"Observation:Location:Longitude", Variant::Type::Float64}}, {"OBJECT", {"Observation:Object:Name", Variant::Type::String}}, {"DEC", {"Observation:Object:Dec", Variant::Type::Float64}}, {"RA", {"Observation:Object:RA", Variant::Type::Float64}}, {"DATE-OBS", {"Observation:Time:Start", Variant::Type::TimePoint}}, {"DATE-END", {"Observation:Time:End", Variant::Type::TimePoint}}, {"GAIN", {"Instrument:Camera:Gain", Variant::Type::Float32}}, {"ISOSPEED", {"Instrument:Camera:ISOSpeed", Variant::Type::Int32}}, {"INSTRUME", {"Instrument:Camera:Name", Variant::Type::String}}, {"ROTATANG", {"Instrument:Camera:Rotation", Variant::Type::Float32}}, {"XBINNING", {"Instrument:Camera:XBinning", Variant::Type::Int32}}, {"YBINNING", {"Instrument:Camera:YBinning", Variant::Type::Int32}}, {"EXPTIME", {"Instrument:ExposureTime", Variant::Type::Float32}}, {"FILTER", {"Instrument:Filter:Name", Variant::Type::String}}, {"FOCUSPOS", {"Instrument:Focuser:Position", Variant::Type::Float32}}, {"CCD-TEMP", {"Instrument:Sensor:Temperature", Variant::Type::Float32}}, {"APTDIA", {"Instrument:Telescope:Aperture", Variant::Type::Float32}}, {"FOCALLEN", {"Instrument:Telescope:FocalLength", Variant::Type::Float32}}, {"TELESCOP", {"Instrument:Telescope:Name", Variant::Type::String}}, }; static void byteShuffle(ByteArray &data, int itemSize) { if(itemSize > 1) { ByteArray &input = data; ByteArray output(input.size()); size_t num = input.size() / itemSize; char *s = output.data(); for(int i=0; i 1) { ByteArray &input = data; ByteArray output(input.size()); size_t num = input.size() / itemSize; const char *s = input.constData(); for(int i=0; i void planarToNormal(void *_in, void *_out, size_t channels, size_t size) { T *in = static_cast(_in); T *out = static_cast(_out); for(size_t i=0; i void normalToPlanar(void *_in, void *_out, size_t channels, size_t size) { T *in = static_cast(_in); T *out = static_cast(_out); for(size_t i=0; i& Image::imageProperties() const { return _properties; } void Image::addProperty(const Property &property) { if(_propertiesId.count(property.id)) throw Error("Duplicate property id"); _propertiesId[property.id] = _properties.size(); _properties.push_back(property); } void Image::updateProperty(const Property &property) { if(!_propertiesId.count(property.id)) addProperty(property); else _properties[_propertiesId[property.id]] = property; } const std::vector Image::fitsKeywords() const { return _fitsKeywords; } void Image::addFITSKeyword(const FITSKeyword &keyword) { _fitsKeywords.push_back(keyword); } bool Image::addFITSKeywordAsProperty(const String &name, const String &value) { if(fitsNameToPropertyIdTypeConvert.count(name)) { auto &c = fitsNameToPropertyIdTypeConvert.at(name); Property prop(c.first, variantFromString(c.second, value)); updateProperty(prop); return true; } return false; } const ByteArray &Image::iccProfile() const { return _iccProfile; } void Image::setIccProfile(const ByteArray &iccProfile) { _iccProfile = iccProfile; } void *Image::imageData() { return _dataBlock.data.size() ? _dataBlock.data.data() : nullptr; } const void *Image::imageData() const { return _dataBlock.data.size() ? _dataBlock.data.data() : nullptr; } size_t Image::imageDataSize() const { return _dataBlock.data.size(); } DataBlock::CompressionCodec Image::compression() const { return _dataBlock.codec; } void Image::setCompression(DataBlock::CompressionCodec compression, int level) { _dataBlock.codec = compression; _dataBlock.compressLevel = level; } bool Image::byteShuffling() const { return _dataBlock.byteShuffling; } void Image::setByteshuffling(bool enable) { _dataBlock.byteShuffling = enable ? sampleFormatSize(_sampleFormat) : 0; } void Image::convertPixelStorageTo(PixelStorage storage) { if(_pixelStorage == storage || _channelCount <= 1) { _pixelStorage = storage; return; } ByteArray tmp; tmp.resize(_dataBlock.data.size()); size_t size = _width*_height; switch(_sampleFormat) { case UInt8: if(storage == Normal) planarToNormal(_dataBlock.data.data(), tmp.data(), _channelCount, size); else normalToPlanar(_dataBlock.data.data(), tmp.data(), _channelCount, size); break; case UInt16: if(storage == Normal) planarToNormal(_dataBlock.data.data(), tmp.data(), _channelCount, size); else normalToPlanar(_dataBlock.data.data(), tmp.data(), _channelCount, size); break; case UInt32: case Float32: if(storage == Normal) planarToNormal(_dataBlock.data.data(), tmp.data(), _channelCount, size); else normalToPlanar(_dataBlock.data.data(), tmp.data(), _channelCount, size); break; case UInt64: case Float64: if(storage == Normal) planarToNormal(_dataBlock.data.data(), tmp.data(), _channelCount, size); else normalToPlanar(_dataBlock.data.data(), tmp.data(), _channelCount, size); break; default: break; } _dataBlock.data = tmp; _pixelStorage = storage; } Image::Type Image::imageTypeEnum(const String &type) { auto t = imageTypeToEnum.find(type); return t != imageTypeToEnum.end() ? t->second : Image::Light; } String Image::imageTypeString(Type type) { auto t = imageTypeToString.find(type); return t != imageTypeToString.end() ? t->second : "Light"; } Image::PixelStorage Image::pixelStorageEnum(const String &storage) { if(storage == "Normal")return Image::Normal; return Image::Planar; } String Image::pixelStorageString(PixelStorage storage) { if(storage == Normal)return "Normal"; return "Planar"; } Image::SampleFormat Image::sampleFormatEnum(const String &format) { auto t = sampleFormatToEnum.find(format); return t != sampleFormatToEnum.end() ? t->second : Image::UInt16; } String Image::sampleFormatString(SampleFormat format) { auto t = sampleFormatToString.find(format); return t != sampleFormatToString.end() ? t->second : "UInt16"; } Image::ColorSpace Image::colorSpaceEnum(const String &colorSpace) { auto t = colorSpaceToEnum.find(colorSpace); return t != colorSpaceToEnum.end() ? t->second : Image::Gray; } String Image::colorSpaceString(ColorSpace colorSpace) { auto t = colorSpaceToString.find(colorSpace); return t != colorSpaceToString.end() ? t->second : "Gray"; } size_t Image::sampleFormatSize(SampleFormat sampleFormat) { switch(sampleFormat) { case Image::UInt8: return sizeof(LibXISF::UInt8); case Image::UInt16: return sizeof(LibXISF::UInt16); case Image::UInt32: return sizeof(LibXISF::UInt32); case Image::UInt64: return sizeof(LibXISF::UInt64); case Image::Float32: return sizeof(LibXISF::Float32); case Image::Float64: return sizeof(LibXISF::Float64); case Image::Complex32: return sizeof(LibXISF::Complex32); case Image::Complex64: return sizeof(LibXISF::Complex64); } return sizeof(UInt16); } class XISFReaderPrivate { public: void open(const String &name); void open(const ByteArray &data); /** Open image from istream. This method takes ownership of *io pointer */ void open(std::istream *io); /** Close opended file release all data. */ void close(); /** Return number of images inside file */ int imagesCount() const; /** Return reference to Image * @param n index of image * @param readPixel when false it will read pixel data from file and imageData() * will return nullptr */ const Image& getImage(uint32_t n, bool readPixels = true); const Image& getThumbnail(); private: void readXISFHeader(); void readSignature(); void parseCompression(const pugi::xml_node &node, DataBlock &dataBlock); DataBlock parseDataBlock(const pugi::xml_node &node); Property parseProperty(const pugi::xml_node &node); FITSKeyword parseFITSKeyword(const pugi::xml_node &node); ColorFilterArray parseCFA(const pugi::xml_node &node); Image parseImage(const pugi::xml_node &node); void readAttachment(DataBlock &dataBlock); std::unique_ptr _io; std::unique_ptr _buffer; std::vector _images; Image _thumbnail; std::vector _properties; }; void XISFReaderPrivate::open(const String &name) { close(); _io = std::make_unique(name.c_str(), std::ios_base::in | std::ios_base::binary); readSignature(); readXISFHeader(); } void XISFReaderPrivate::open(const ByteArray &data) { close(); _buffer = std::make_unique(data); _io = std::make_unique(_buffer.get()); readSignature(); readXISFHeader(); } void XISFReaderPrivate::open(std::istream *io) { close(); _io.reset(io); readSignature(); readXISFHeader(); } void XISFReaderPrivate::close() { _io.reset(); _buffer.reset(); _images.clear(); _properties.clear(); } int XISFReaderPrivate::imagesCount() const { return _images.size(); } const Image& XISFReaderPrivate::getImage(uint32_t n, bool readPixels) { if(n >= _images.size()) throw Error("Out of bounds"); Image &img = _images[n]; if(img._dataBlock.attachmentPos && readPixels) { readAttachment(img._dataBlock); } return img; } const Image &XISFReaderPrivate::getThumbnail() { Image &img = _thumbnail; if(_thumbnail._dataBlock.attachmentPos) { _io->seekg(img._dataBlock.attachmentPos); ByteArray data(img._dataBlock.attachmentSize); _io->read(data.data(), data.size()); img._dataBlock.decompress(data); } return _thumbnail; } void XISFReaderPrivate::readXISFHeader() { uint32_t headerLen[2] = {0}; _io->read((char*)&headerLen, sizeof(headerLen)); ByteArray xisfHeader(headerLen[0]); _io->read(xisfHeader.data(), headerLen[0]); pugi::xml_document doc; doc.load_buffer(xisfHeader.data(), xisfHeader.size()); pugi::xml_node root = doc.child("xisf"); if(root && root.attribute("version").as_string() == std::string("1.0")) { for(auto &image : root.children("Image")) _images.push_back(parseImage(image)); for(auto &property : root.children("Property")) _properties.push_back(parseProperty(property)); if(root.child("Thumbnail")) _thumbnail = parseImage(root.child("Thumbnail")); } else throw Error("Unknown root XML element"); } void XISFReaderPrivate::readSignature() { char signature[8]; _io->read(signature, sizeof(signature)); if(_io->fail()) throw Error("Failed to read from file"); if(memcmp(signature, "XISF0100", sizeof(signature)) != 0) throw Error("Not valid XISF 1.0 file"); } void XISFReaderPrivate::parseCompression(const pugi::xml_node &node, DataBlock &dataBlock) { std::vector compression = splitString(node.attribute("compression").as_string(), ':'); if(compression.size() >= 2) { if(compression[0].find("zlib") == 0) dataBlock.codec = DataBlock::Zlib; else if(compression[0].find("lz4hc") == 0) dataBlock.codec = DataBlock::LZ4HC; else if(compression[0].find("lz4") == 0) dataBlock.codec = DataBlock::LZ4; #ifdef HAVE_ZSTD else if(compression[0].find("zstd") == 0) dataBlock.codec = DataBlock::ZSTD; #endif else throw Error("Unknown compression codec"); dataBlock.uncompressedSize = std::stoul(compression[1]); if(compression[0].find("+sh") != std::string::npos) { if(compression.size() == 3) dataBlock.byteShuffling = std::stoi(compression[2]); else throw Error("Missing byte shuffling size"); } } } DataBlock XISFReaderPrivate::parseDataBlock(const pugi::xml_node &node) { DataBlock dataBlock; std::vector location = splitString(node.attribute("location").as_string(), ':'); parseCompression(node, dataBlock); if(location.size() && location[0] == "embedded") { dataBlock.embedded = true; } else if(location.size() >= 2 && location[0] == "inline") { ByteArray text(node.text().as_string()); dataBlock.decompress(text, location[1]); } else if(location.size() >= 3 && location[0] == "attachment") { dataBlock.attachmentPos = std::stoul(location[1]); dataBlock.attachmentSize = std::stoul(location[2]); } else { throw Error("Invalid data block"); } if(dataBlock.embedded) { auto dataNode = node.child("Data"); if(dataNode) { parseCompression(dataNode, dataBlock); String encoding = dataNode.attribute("encoding").as_string(); ByteArray text(dataNode.text().as_string()); dataBlock.decompress(text, encoding); } else throw Error("Unexpected XML element"); } return dataBlock; } Property XISFReaderPrivate::parseProperty(const pugi::xml_node &node) { Property property; property.id = node.attribute("id").as_string(); property.comment = node.attribute("comment").as_string(); ByteArray data; if(node.attribute("location")) { DataBlock dataBlock = parseDataBlock(node); if(dataBlock.attachmentPos) readAttachment(dataBlock); data = dataBlock.data; } deserializeVariant(node, property.value, data); return property; } FITSKeyword XISFReaderPrivate::parseFITSKeyword(const pugi::xml_node &node) { FITSKeyword fitsKeyword; fitsKeyword.name = node.attribute("name").as_string(); fitsKeyword.value = node.attribute("value").as_string(); fitsKeyword.comment = node.attribute("comment").as_string(); return fitsKeyword; } ColorFilterArray XISFReaderPrivate::parseCFA(const pugi::xml_node &node) { ColorFilterArray cfa; if(node.attribute("pattern") && node.attribute("width") && node.attribute("height")) { cfa.pattern = node.attribute("pattern").as_string(); cfa.width = node.attribute("width").as_int(); cfa.height = node.attribute("height").as_int(); } else { throw Error("ColorFilterArray element missing one of mandatory attributes"); } return cfa; } Image XISFReaderPrivate::parseImage(const pugi::xml_node &node) { Image image; std::vector geometry = splitString(node.attribute("geometry").as_string(), ':'); if(geometry.size() != 3)throw Error("We support only 2D images"); image._width = std::stoul(geometry[0]); image._height = std::stoul(geometry[1]); image._channelCount = std::stoul(geometry[2]); if(!image._width || !image._height || !image._channelCount)throw Error("Invalid image geometry"); std::vector bounds = splitString(node.attribute("bounds").as_string(), ':'); if(bounds.size() == 2) { image._bounds.first = std::stod(bounds[0]); image._bounds.second = std::stod(bounds[1]); } image._imageType = Image::imageTypeEnum(node.attribute("imageType").as_string()); image._pixelStorage = Image::pixelStorageEnum(node.attribute("pixelStorage").as_string()); image._sampleFormat = Image::sampleFormatEnum(node.attribute("sampleFormat").as_string()); image._colorSpace = Image::colorSpaceEnum(node.attribute("colorSpace").as_string()); image._dataBlock = parseDataBlock(node); for(auto &property : node.children("Property")) image._properties.push_back(parseProperty(property)); for(auto &fitsKeyword : node.children("FITSKeyword")) image._fitsKeywords.push_back(parseFITSKeyword(fitsKeyword)); if(node.child("ColorFilterArray")) image._cfa = parseCFA(node.child("ColorFilterArray")); if(node.child("ICCProfile")) { DataBlock icc = parseDataBlock(node.child("ICCProfile")); if(icc.attachmentPos) readAttachment(icc); image._iccProfile = icc.data; } if(node.child("Thumbnail") && std::strcmp(node.name(), "Thumbnail")) _thumbnail = parseImage(node.child("Thumbnail")); return image; } void XISFReaderPrivate::readAttachment(DataBlock &dataBlock) { ByteArray data(dataBlock.attachmentSize); _io->seekg(dataBlock.attachmentPos); _io->read(data.data(), dataBlock.attachmentSize); dataBlock.decompress(data); } class XISFWriterPrivate { public: void save(const String &name); void save(ByteArray &data); void save(std::ostream &io); void writeImage(const Image &image); private: void writeHeader(); void writeImageElement(pugi::xml_node &node, const Image &image); void writeDataBlockAttributes(pugi::xml_node &image_node, const DataBlock &dataBlock); void writePropertyElement(pugi::xml_node &node, const Property &property); void writeFITSKeyword(pugi::xml_node &node, const FITSKeyword &keyword); void writeMetadata(pugi::xml_node &node); ByteArray _xisfHeader; ByteArray _attachmentsData; std::vector _images; }; void XISFWriterPrivate::save(const String &name) { std::ofstream fw(name.c_str(), std::ios_base::out | std::ios_base::binary); if(fw.fail()) throw Error("Failed to open file"); save(fw); } void XISFWriterPrivate::save(ByteArray &data) { StreamBuffer buffer; std::ostream oss(&buffer); save(oss); data = buffer.byteArray(); } void XISFWriterPrivate::save(std::ostream &io) { writeHeader(); io.write(_xisfHeader.constData(), _xisfHeader.size()); for(auto &image : _images) { io.write(image._dataBlock.data.constData(), image._dataBlock.data.size()); } } void XISFWriterPrivate::writeImage(const Image &image) { _images.push_back(image); _images.back()._dataBlock.attachmentPos = 1; _images.back()._dataBlock.compress(image.sampleFormatSize(image.sampleFormat())); } void XISFWriterPrivate::writeHeader() { const char signature[16] = {'X', 'I', 'S', 'F', '0', '1', '0', '0', 0, 0, 0, 0, 0, 0, 0, 0}; pugi::xml_document doc; doc.append_child(pugi::node_comment).set_value("\nExtensible Image Serialization Format - XISF version 1.0\nCreated with libXISF - https://nouspiro.space\n"); pugi::xml_node root = doc.append_child("xisf"); root.append_attribute("version").set_value("1.0"); root.append_attribute("xmlns").set_value("http://www.pixinsight.com/xisf"); root.append_attribute("xmlns:xsi").set_value("http://www.w3.org/2001/XMLSchema-instance"); root.append_attribute("xsi:schemaLocation").set_value("http://www.pixinsight.com/xisf http://pixinsight.com/xisf/xisf-1.0.xsd"); for(Image &image : _images) { writeImageElement(root, image); } writeMetadata(root); std::stringstream xml; xml.write(signature, sizeof(signature)); doc.save(xml, "", pugi::format_raw); std::string header = xml.str(); uint32_t size = header.size(); uint32_t offset = 0; std::string replace = "attachment:2147483648"; for(auto &image : _images) { std::string blockPos = std::string("attachment:") + std::to_string(size + offset); size_t pos = header.find(replace, 32); header.replace(pos, replace.size(), blockPos); offset += image._dataBlock.data.size(); } uint32_t headerSize = header.size() - sizeof(signature); header.resize(size, 0); header.replace(8, sizeof(uint32_t), (const char*)&headerSize, sizeof(uint32_t)); _xisfHeader = ByteArray(header.c_str(), header.size()); } void XISFWriterPrivate::writeImageElement(pugi::xml_node &node, const Image &image) { pugi::xml_node image_node = node.append_child("Image"); std::string geometry = std::to_string(image._width) + ":" + std::to_string(image._height) + ":" + std::to_string(image._channelCount); image_node.append_attribute("geometry").set_value(geometry.c_str()); image_node.append_attribute("sampleFormat").set_value(Image::sampleFormatString(image._sampleFormat).c_str()); image_node.append_attribute("colorSpace").set_value(Image::colorSpaceString(image._colorSpace).c_str()); image_node.append_attribute("imageType").set_value(Image::imageTypeString(image._imageType).c_str()); image_node.append_attribute("pixelStorage").set_value(Image::pixelStorageString(image._pixelStorage).c_str()); if((image._sampleFormat == Image::Float32 || image._sampleFormat == Image::Float64) || image._bounds.first != 0.0 || image._bounds.second != 1.0) { std::string bounds = std::to_string(image._bounds.first) + ":" + std::to_string(image._bounds.second); image_node.append_attribute("bounds").set_value(bounds.c_str()); } writeDataBlockAttributes(image_node, image._dataBlock); for(auto &property : image._properties) writePropertyElement(image_node, property); for(auto &fitsKeyword : image._fitsKeywords) writeFITSKeyword(image_node, fitsKeyword); if(image._cfa.width && image._cfa.height) { pugi::xml_node cfa_node = image_node.append_child("ColorFilterArray"); cfa_node.append_attribute("pattern").set_value(image._cfa.pattern.c_str()); cfa_node.append_attribute("width").set_value(image._cfa.width); cfa_node.append_attribute("height").set_value(image._cfa.height); } if(image._iccProfile.size()) { ByteArray base64 = image._iccProfile; base64.decodeBase64(); pugi::xml_node icc_node = image_node.append_child("ICCProfile"); icc_node.append_attribute("location").set_value("inline:base64"); icc_node.append_child(pugi::node_pcdata).set_value(base64.data()); } } void XISFWriterPrivate::writeDataBlockAttributes(pugi::xml_node &image_node, const DataBlock &dataBlock) { if(dataBlock.embedded) { image_node.append_attribute("location").set_value("embedded"); } else if(dataBlock.attachmentPos == 0) { image_node.append_attribute("location").set_value("inline:base64"); } else { std::string attachment = "attachment:2147483648:" + std::to_string(dataBlock.data.size()); image_node.append_attribute("location").set_value(attachment.c_str()); } std::string codec; if(dataBlock.codec == DataBlock::Zlib) codec = "zlib"; else if(dataBlock.codec == DataBlock::LZ4) codec = "lz4"; else if(dataBlock.codec == DataBlock::LZ4HC) codec = "lz4hc"; else if(dataBlock.codec == DataBlock::ZSTD) #ifdef HAVE_ZSTD codec = "zstd"; #else throw Error("ZSTD support not compiled"); #endif if(dataBlock.byteShuffling > 1) codec += "+sh"; if(!codec.empty()) { codec += ":" + std::to_string(dataBlock.uncompressedSize); if(dataBlock.byteShuffling > 1) codec += ":" + std::to_string(dataBlock.byteShuffling); image_node.append_attribute("compression").set_value(codec.c_str()); } } void XISFWriterPrivate::writePropertyElement(pugi::xml_node &node, const Property &property) { pugi::xml_node property_node = node.append_child("Property"); property_node.append_attribute("id").set_value(property.id.c_str()); serializeVariant(property_node, property.value); if(!property.comment.empty()) property_node.append_attribute("comment").set_value(property.comment.c_str()); } void XISFWriterPrivate::writeFITSKeyword(pugi::xml_node &node, const FITSKeyword &keyword) { pugi::xml_node fits_node = node.append_child("FITSKeyword"); fits_node.append_attribute("name").set_value(keyword.name.c_str()); fits_node.append_attribute("value").set_value(keyword.value.c_str()); fits_node.append_attribute("comment").set_value(keyword.comment.c_str()); } void XISFWriterPrivate::writeMetadata(pugi::xml_node &node) { pugi::xml_node metadata = node.append_child("Metadata"); std::time_t t = std::time(nullptr); std::tm tm = *std::gmtime(&t); writePropertyElement(metadata, Property("XISF:CreationTime", tm)); writePropertyElement(metadata, Property("XISF:CreatorApplication", "LibXISF")); } XISFReader::XISFReader() { p = new XISFReaderPrivate; } XISFReader::~XISFReader() { delete p; } void XISFReader::open(const String &name) { p->open(name); } void XISFReader::open(const ByteArray &data) { p->open(data); } void XISFReader::open(std::istream *io) { p->open(io); } void XISFReader::close() { p->close(); } int XISFReader::imagesCount() const { return p->imagesCount(); } const Image &XISFReader::getImage(uint32_t n, bool readPixels) { return p->getImage(n, readPixels); } const Image &XISFReader::getThumbnail() { return p->getThumbnail(); } XISFWriter::XISFWriter() { p = new XISFWriterPrivate; } XISFWriter::~XISFWriter() { delete p; } void XISFWriter::save(const String &name) { p->save(name); } void XISFWriter::save(ByteArray &data) { p->save(data); } void XISFWriter::save(std::ostream &io) { p->save(io); } void XISFWriter::writeImage(const Image &image) { p->writeImage(image); } #define STRING_ENUM(map, map2, c, e) { map.insert({#e, c::e}); map2.insert({c::e, #e}); } struct Init { Init() { STRING_ENUM(imageTypeToEnum, imageTypeToString, Image, Bias); STRING_ENUM(imageTypeToEnum, imageTypeToString, Image, Dark); STRING_ENUM(imageTypeToEnum, imageTypeToString, Image, Flat); STRING_ENUM(imageTypeToEnum, imageTypeToString, Image, Light); STRING_ENUM(imageTypeToEnum, imageTypeToString, Image, MasterBias); STRING_ENUM(imageTypeToEnum, imageTypeToString, Image, MasterDark); STRING_ENUM(imageTypeToEnum, imageTypeToString, Image, MasterFlat); STRING_ENUM(imageTypeToEnum, imageTypeToString, Image, DefectMap); STRING_ENUM(imageTypeToEnum, imageTypeToString, Image, RejectionMapHigh); STRING_ENUM(imageTypeToEnum, imageTypeToString, Image, RejectionMapLow); STRING_ENUM(imageTypeToEnum, imageTypeToString, Image, BinaryRejectionMapHigh); STRING_ENUM(imageTypeToEnum, imageTypeToString, Image, BinaryRejectionMapLow); STRING_ENUM(imageTypeToEnum, imageTypeToString, Image, SlopeMap); STRING_ENUM(imageTypeToEnum, imageTypeToString, Image, WeightMap); STRING_ENUM(sampleFormatToEnum, sampleFormatToString, Image, UInt8); STRING_ENUM(sampleFormatToEnum, sampleFormatToString, Image, UInt16); STRING_ENUM(sampleFormatToEnum, sampleFormatToString, Image, UInt32); STRING_ENUM(sampleFormatToEnum, sampleFormatToString, Image, UInt64); STRING_ENUM(sampleFormatToEnum, sampleFormatToString, Image, Float32); STRING_ENUM(sampleFormatToEnum, sampleFormatToString, Image, Float64); STRING_ENUM(sampleFormatToEnum, sampleFormatToString, Image, Complex32); STRING_ENUM(sampleFormatToEnum, sampleFormatToString, Image, Complex64); STRING_ENUM(colorSpaceToEnum, colorSpaceToString, Image, Gray); STRING_ENUM(colorSpaceToEnum, colorSpaceToString, Image, RGB); STRING_ENUM(colorSpaceToEnum, colorSpaceToString, Image, CIELab); const char *compression_env = std::getenv("LIBXISF_COMPRESSION"); if(compression_env) { std::string compression = compression_env; if(compression.find("zlib") == 0) compressionCodecOverride = DataBlock::Zlib; else if(compression.find("lz4hc") == 0) compressionCodecOverride = DataBlock::LZ4HC; else if(compression.find("lz4") == 0) compressionCodecOverride = DataBlock::LZ4; #ifdef HAVE_ZSTD else if(compression.find("zstd") == 0) compressionCodecOverride = DataBlock::ZSTD; #endif if(compression.find("+sh") != std::string::npos) byteShuffleOverride = true; int index = compression.find_last_of(":"); if(index > 0) { try { compressionLevelOverride = std::stoi(compression.substr(index + 1)); } catch(...) { /* do nothing */ } } } } }; static Init init; }