18 Commits

8 changed files with 634 additions and 58 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.14) cmake_minimum_required(VERSION 3.14)
project(libXISF VERSION 0.2.10 LANGUAGES CXX C project(libXISF VERSION 0.2.12 LANGUAGES CXX C
HOMEPAGE_URL https://gitea.nouspiro.space/nou/libXISF HOMEPAGE_URL https://gitea.nouspiro.space/nou/libXISF
DESCRIPTION "LibXISF is C++ library that can read and write XISF files produced by PixInsight.") DESCRIPTION "LibXISF is C++ library that can read and write XISF files produced by PixInsight.")
+1 -1
View File
@@ -9,7 +9,7 @@ To compile simply run these commands
``` ```
cmake -B build -S . cmake -B build -S .
cmake --build build --parallel cmake --build build --parallel
cmake --install . cmake --install build
``` ```
By default it use bundled libraries. If you wish to use external libraries you will may add By default it use bundled libraries. If you wish to use external libraries you will may add
+515 -50
View File
@@ -23,6 +23,7 @@
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#include <cstdlib> #include <cstdlib>
#include <cmath>
#include <lz4.h> #include <lz4.h>
#include <lz4hc.h> #include <lz4hc.h>
#include <pugixml.hpp> #include <pugixml.hpp>
@@ -49,6 +50,7 @@ static std::unordered_map<Image::ColorSpace, String> colorSpaceToString;
static DataBlock::CompressionCodec compressionCodecOverride = DataBlock::None; static DataBlock::CompressionCodec compressionCodecOverride = DataBlock::None;
static bool byteShuffleOverride = false; static bool byteShuffleOverride = false;
static int compressionLevelOverride = -1; static int compressionLevelOverride = -1;
const size_t GiB = 1073741824;
static const std::unordered_map<String, std::pair<String, Variant::Type>> fitsNameToPropertyIdTypeConvert = { static const std::unordered_map<String, std::pair<String, Variant::Type>> fitsNameToPropertyIdTypeConvert = {
{"OBSERVER", {"Observer:Name", Variant::Type::String}}, {"OBSERVER", {"Observer:Name", Variant::Type::String}},
@@ -86,12 +88,12 @@ static void byteShuffle(ByteArray &data, int itemSize)
{ {
ByteArray &input = data; ByteArray &input = data;
ByteArray output(input.size()); ByteArray output(input.size());
int num = input.size() / itemSize; size_t num = input.size() / itemSize;
char *s = output.data(); char *s = output.data();
for(int i=0; i<itemSize; i++) for(int i=0; i<itemSize; i++)
{ {
const char *u = input.constData() + i; const char *u = input.constData() + i;
for(int o=0; o<num; o++, s++, u += itemSize) for(size_t o=0; o<num; o++, s++, u += itemSize)
*s = *u; *s = *u;
} }
memcpy(s, input.constData() + num * itemSize, input.size() % itemSize); memcpy(s, input.constData() + num * itemSize, input.size() % itemSize);
@@ -105,12 +107,12 @@ static void byteUnshuffle(ByteArray &data, int itemSize)
{ {
ByteArray &input = data; ByteArray &input = data;
ByteArray output(input.size()); ByteArray output(input.size());
int num = input.size() / itemSize; size_t num = input.size() / itemSize;
const char *s = input.constData(); const char *s = input.constData();
for(int i=0; i<itemSize; i++) for(int i=0; i<itemSize; i++)
{ {
char *u = output.data() + i; char *u = output.data() + i;
for(int o=0; o<num; o++, s++, u += itemSize) for(size_t o=0; o<num; o++, s++, u += itemSize)
*u = *s; *u = *s;
} }
memcpy(output.data() + num * itemSize, s, input.size() % itemSize); memcpy(output.data() + num * itemSize, s, input.size() % itemSize);
@@ -127,6 +129,9 @@ void DataBlock::decompress(const ByteArray &input, const String &encoding)
else if(encoding == "base16") else if(encoding == "base16")
tmp.decodeHex(); tmp.decodeHex();
if(subblocks.size() == 0)
subblocks.push_back({tmp.size(), uncompressedSize});
switch(codec) switch(codec)
{ {
case None: case None:
@@ -135,27 +140,55 @@ void DataBlock::decompress(const ByteArray &input, const String &encoding)
case Zlib: case Zlib:
{ {
data.resize(uncompressedSize); data.resize(uncompressedSize);
uLongf size = uncompressedSize; const char *srcPtr = tmp.constData();
::uncompress((Bytef*)data.data(), &size, (Bytef*)tmp.data(), tmp.size()); char *dstPtr = data.data();
for(auto &block : subblocks)
{
uLongf size = block.second;
::uncompress((Bytef*)dstPtr, &size, (const Bytef*)srcPtr, block.first);
srcPtr += block.first;
dstPtr += block.second;
}
break; break;
} }
case LZ4: case LZ4:
case LZ4HC: case LZ4HC:
{
data.resize(uncompressedSize); data.resize(uncompressedSize);
if(LZ4_decompress_safe(tmp.constData(), data.data(), tmp.size(), data.size()) < 0) const char *srcPtr = tmp.constData();
throw Error("LZ4 decompression failed"); char *dstPtr = data.data();
for(auto &block : subblocks)
{
if(LZ4_decompress_safe(srcPtr, dstPtr, block.first, block.second) < 0)
throw Error("LZ4 decompression failed");
srcPtr += block.first;
dstPtr += block.second;
}
break; break;
}
case ZSTD: case ZSTD:
#ifdef HAVE_ZSTD #ifdef HAVE_ZSTD
{
data.resize(uncompressedSize); data.resize(uncompressedSize);
if(ZSTD_isError(ZSTD_decompress(data.data(), data.size(), tmp.constData(), tmp.size()))) const char *srcPtr = tmp.constData();
throw Error("ZSTD decompression failed"); char *dstPtr = data.data();
for(auto &block : subblocks)
{
if(ZSTD_isError(ZSTD_decompress(dstPtr, block.second, srcPtr, block.first)))
throw Error("ZSTD decompression failed");
srcPtr += block.first;
dstPtr += block.second;
}
}
#else #else
throw Error("ZSTD support not compiled"); throw Error("ZSTD support not compiled");
#endif #endif
break; break;
} }
subblocks.clear();
byteUnshuffle(data, byteShuffling); byteUnshuffle(data, byteShuffling);
attachmentPos = 0; attachmentPos = 0;
} }
@@ -181,26 +214,48 @@ void DataBlock::compress(int sampleFormatSize)
break; break;
case Zlib: case Zlib:
{ {
data.resize(compressBound(uncompressedSize)); int64_t size = tmp.size();
uLongf compressedSize = data.size(); int64_t compSize = 0;
if(::compress2((Bytef*)data.data(), &compressedSize, (Bytef*)tmp.data(), tmp.size(), compressLevel) != Z_OK) int64_t inPtr = 0;
throw Error("Zlib compression failed"); while(inPtr < size)
data.resize(compressedSize); {
int64_t inSize = UINT32_MAX < size - inPtr ? UINT32_MAX : size - inPtr;
data.resize(compSize + compressBound(inSize));
uLongf outSize = data.size() - compSize;
if(::compress2((Bytef*)data.data() + compSize, &outSize, (const Bytef*)tmp.constData() + inPtr, inSize, compressLevel) != Z_OK)
throw Error("Zlib compression failed");
compSize += outSize;
inPtr += inSize;
subblocks.push_back({outSize, inSize});
}
data.resize(compSize);
break; break;
} }
case LZ4: case LZ4:
case LZ4HC: case LZ4HC:
{ {
int compSize = 0; int64_t size = tmp.size();
data.resize(LZ4_compressBound(tmp.size())); int64_t compSize = 0;
if(codec == LZ4) int64_t inPtr = 0;
compSize = LZ4_compress_default(tmp.constData(), data.data(), tmp.size(), data.size()); while(inPtr < size)
else {
compSize = LZ4_compress_HC(tmp.constData(), data.data(), tmp.size(), data.size(), compressLevel < 0 ? LZ4HC_CLEVEL_DEFAULT : compressLevel); int64_t inSize = LZ4_MAX_INPUT_SIZE < size - inPtr ? LZ4_MAX_INPUT_SIZE : size - inPtr;
data.resize(compSize + LZ4_compressBound(inSize));
int outSize = 0;
if(compSize <= 0) if(codec == LZ4)
throw Error("LZ4 compression failed"); outSize = LZ4_compress_default(tmp.constData() + inPtr, data.data() + compSize, inSize, data.size() - compSize);
else
outSize = LZ4_compress_HC(tmp.constData() + inPtr, data.data() + compSize, inSize, data.size() - compSize, compressLevel < 0 ? LZ4HC_CLEVEL_DEFAULT : compressLevel);
if(outSize <= 0)
throw Error("LZ4 compression failed");
compSize += outSize;
inPtr += inSize;
subblocks.push_back({outSize, inSize});
}
data.resize(compSize); data.resize(compSize);
break; break;
} }
@@ -389,6 +444,10 @@ bool Image::addFITSKeywordAsProperty(const String &name, const String &value)
{ {
auto &c = fitsNameToPropertyIdTypeConvert.at(name); auto &c = fitsNameToPropertyIdTypeConvert.at(name);
Property prop(c.first, variantFromString(c.second, value)); Property prop(c.first, variantFromString(c.second, value));
if(name == "APTDIA" || name == "FOCALLEN")
prop.value.value<LibXISF::Float32>() /= 1000.0f;
updateProperty(prop); updateProperty(prop);
return true; return true;
} }
@@ -428,7 +487,38 @@ DataBlock::CompressionCodec Image::compression() const
void Image::setCompression(DataBlock::CompressionCodec compression, int level) void Image::setCompression(DataBlock::CompressionCodec compression, int level)
{ {
_dataBlock.codec = compression; _dataBlock.codec = compression;
_dataBlock.compressLevel = level; _dataBlock.compressLevel = -1;
level = std::min(std::max(level, -1), 100);
auto percentToRange = [](int val, int min, int max)
{
double slope = (max - min) / 100.0;
return std::round(min + slope * val);
};
if(level >= 0)
{
switch(compression)
{
case DataBlock::CompressionCodec::Zlib:
_dataBlock.compressLevel = percentToRange(level, Z_BEST_SPEED, Z_BEST_COMPRESSION);
break;
case DataBlock::CompressionCodec::LZ4:
case DataBlock::CompressionCodec::LZ4HC:
_dataBlock.compressLevel = percentToRange(level, 1, LZ4HC_CLEVEL_MAX);
break;
case DataBlock::CompressionCodec::ZSTD:
#ifdef HAVE_ZSTD
_dataBlock.compressLevel = percentToRange(level, ZSTD_minCLevel(), ZSTD_maxCLevel());
#endif
break;
default:
//nothing
break;
}
}
} }
bool Image::byteShuffling() const bool Image::byteShuffling() const
@@ -707,7 +797,7 @@ void XISFReaderPrivate::parseCompression(const pugi::xml_node &node, DataBlock &
else else
throw Error("Unknown compression codec"); throw Error("Unknown compression codec");
dataBlock.uncompressedSize = std::stoul(compression[1]); dataBlock.uncompressedSize = std::stoull(compression[1]);
if(compression[0].find("+sh") != std::string::npos) if(compression[0].find("+sh") != std::string::npos)
{ {
@@ -716,6 +806,18 @@ void XISFReaderPrivate::parseCompression(const pugi::xml_node &node, DataBlock &
else else
throw Error("Missing byte shuffling size"); throw Error("Missing byte shuffling size");
} }
if(node.attribute("subblocks"))
{
std::vector<std::string> subblocks = splitString(node.attribute("subblocks").as_string(), ':');
for(auto &block : subblocks)
{
size_t pos = 0;
size_t comp = std::stoull(block, &pos);
size_t deco = std::stoull(block.substr(pos+1));
dataBlock.subblocks.push_back({comp, deco});
}
}
} }
} }
@@ -737,8 +839,8 @@ DataBlock XISFReaderPrivate::parseDataBlock(const pugi::xml_node &node)
} }
else if(location.size() >= 3 && location[0] == "attachment") else if(location.size() >= 3 && location[0] == "attachment")
{ {
dataBlock.attachmentPos = std::stoul(location[1]); dataBlock.attachmentPos = std::stoull(location[1]);
dataBlock.attachmentSize = std::stoul(location[2]); dataBlock.attachmentSize = std::stoull(location[2]);
} }
else else
{ {
@@ -814,9 +916,9 @@ Image XISFReaderPrivate::parseImage(const pugi::xml_node &node)
std::vector<std::string> geometry = splitString(node.attribute("geometry").as_string(), ':'); std::vector<std::string> geometry = splitString(node.attribute("geometry").as_string(), ':');
if(geometry.size() != 3)throw Error("We support only 2D images"); if(geometry.size() != 3)throw Error("We support only 2D images");
image._width = std::stoul(geometry[0]); image._width = std::stoull(geometry[0]);
image._height = std::stoul(geometry[1]); image._height = std::stoull(geometry[1]);
image._channelCount = std::stoul(geometry[2]); image._channelCount = std::stoull(geometry[2]);
if(!image._width || !image._height || !image._channelCount)throw Error("Invalid image geometry"); if(!image._width || !image._height || !image._channelCount)throw Error("Invalid image geometry");
std::vector<std::string> bounds = splitString(node.attribute("bounds").as_string(), ':'); std::vector<std::string> bounds = splitString(node.attribute("bounds").as_string(), ':');
@@ -860,7 +962,15 @@ void XISFReaderPrivate::readAttachment(DataBlock &dataBlock)
{ {
ByteArray data(dataBlock.attachmentSize); ByteArray data(dataBlock.attachmentSize);
_io->seekg(dataBlock.attachmentPos); _io->seekg(dataBlock.attachmentPos);
_io->read(data.data(), dataBlock.attachmentSize); size_t size = dataBlock.attachmentSize;
char *ptr = data.data();
while(size > 0)
{
size_t s = std::min(size, GiB);
_io->read(ptr, s);
size -= s;
ptr += s;
}
dataBlock.decompress(data); dataBlock.decompress(data);
} }
@@ -871,13 +981,14 @@ public:
void save(ByteArray &data); void save(ByteArray &data);
void save(std::ostream &io); void save(std::ostream &io);
void writeImage(const Image &image); void writeImage(const Image &image);
static void writeFITSKeyword(pugi::xml_node &node, const FITSKeyword &keyword);
private: private:
void writeHeader(); void writeHeader();
void writeImageElement(pugi::xml_node &node, const Image &image); void writeImageElement(pugi::xml_node &node, const Image &image);
void writeDataBlockAttributes(pugi::xml_node &image_node, const DataBlock &dataBlock); void writeDataBlockAttributes(pugi::xml_node &image_node, const DataBlock &dataBlock);
void writePropertyElement(pugi::xml_node &node, const Property &property); void writePropertyElement(pugi::xml_node &node, const Property &property);
void writeFITSKeyword(pugi::xml_node &node, const FITSKeyword &keyword);
void writeMetadata(pugi::xml_node &node); void writeMetadata(pugi::xml_node &node);
void updateImageAttachmentPos(pugi::xml_node &root, size_t offset);
ByteArray _xisfHeader; ByteArray _xisfHeader;
ByteArray _attachmentsData; ByteArray _attachmentsData;
std::vector<Image> _images; std::vector<Image> _images;
@@ -909,7 +1020,15 @@ void XISFWriterPrivate::save(std::ostream &io)
for(auto &image : _images) for(auto &image : _images)
{ {
io.write(image._dataBlock.data.constData(), image._dataBlock.data.size()); const char *ptr = image._dataBlock.data.constData();
size_t size = image._dataBlock.data.size();
while(size > 0)
{
size_t s = std::min(size, GiB);
io.write(ptr, s);
ptr += s;
size -= s;
}
} }
} }
@@ -940,25 +1059,26 @@ void XISFWriterPrivate::writeHeader()
writeMetadata(root); writeMetadata(root);
std::stringstream xml; uint32_t size = 0;
xml.write(signature, sizeof(signature)); std::string header;
doc.save(xml, "", pugi::format_raw); while(true)
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); std::stringstream xml;
size_t pos = header.find(replace, 32); xml.write(signature, sizeof(signature));
header.replace(pos, replace.size(), blockPos); doc.save(xml, "", pugi::format_raw);
offset += image._dataBlock.data.size(); header = xml.str();
if(size != header.size())
{
size = header.size();
updateImageAttachmentPos(root, size);
}
else
{
break;
}
} }
uint32_t headerSize = header.size() - sizeof(signature); uint32_t headerSize = header.size() - sizeof(signature);
header.resize(size, 0);
header.replace(8, sizeof(uint32_t), (const char*)&headerSize, sizeof(uint32_t)); header.replace(8, sizeof(uint32_t), (const char*)&headerSize, sizeof(uint32_t));
_xisfHeader = ByteArray(header.c_str(), header.size()); _xisfHeader = ByteArray(header.c_str(), header.size());
@@ -998,7 +1118,8 @@ void XISFWriterPrivate::writeImageElement(pugi::xml_node &node, const Image &ima
if(image._iccProfile.size()) if(image._iccProfile.size())
{ {
ByteArray base64 = image._iccProfile; ByteArray base64 = image._iccProfile;
base64.decodeBase64(); base64.encodeBase64();
base64.append('\0');
pugi::xml_node icc_node = image_node.append_child("ICCProfile"); pugi::xml_node icc_node = image_node.append_child("ICCProfile");
icc_node.append_attribute("location").set_value("inline:base64"); icc_node.append_attribute("location").set_value("inline:base64");
icc_node.append_child(pugi::node_pcdata).set_value(base64.data()); icc_node.append_child(pugi::node_pcdata).set_value(base64.data());
@@ -1017,7 +1138,10 @@ void XISFWriterPrivate::writeDataBlockAttributes(pugi::xml_node &image_node, con
} }
else else
{ {
std::string attachment = "attachment:2147483648:" + std::to_string(dataBlock.data.size()); std::string attachment = "attachment:";
if(dataBlock.attachmentPos == 0) attachment += "99999";
else attachment += std::to_string(dataBlock.attachmentPos);
attachment += ":" + std::to_string(dataBlock.data.size());
image_node.append_attribute("location").set_value(attachment.c_str()); image_node.append_attribute("location").set_value(attachment.c_str());
} }
@@ -1047,6 +1171,19 @@ void XISFWriterPrivate::writeDataBlockAttributes(pugi::xml_node &image_node, con
image_node.append_attribute("compression").set_value(codec.c_str()); image_node.append_attribute("compression").set_value(codec.c_str());
} }
if(!dataBlock.subblocks.empty())
{
std::string subblocks;
for(auto i = dataBlock.subblocks.begin(); i != dataBlock.subblocks.end(); i++)
{
if(i != dataBlock.subblocks.begin())
subblocks += ":";
subblocks += std::to_string(i->first) + "," + std::to_string(i->second);
}
image_node.append_attribute("subblocks").set_value(subblocks.c_str());
}
} }
void XISFWriterPrivate::writePropertyElement(pugi::xml_node &node, const Property &property) void XISFWriterPrivate::writePropertyElement(pugi::xml_node &node, const Property &property)
@@ -1077,6 +1214,19 @@ void XISFWriterPrivate::writeMetadata(pugi::xml_node &node)
writePropertyElement(metadata, Property("XISF:CreatorApplication", "LibXISF")); writePropertyElement(metadata, Property("XISF:CreatorApplication", "LibXISF"));
} }
void XISFWriterPrivate::updateImageAttachmentPos(pugi::xml_node &root, size_t offset)
{
pugi::xpath_node_set imageNodes = root.select_nodes("//Image");
int i = 0;
for(auto &image : _images)
{
pugi::xml_node node = imageNodes[i++].node();
std::string location = "attachment:" + std::to_string(offset) + ":" + std::to_string(image._dataBlock.data.size());
offset += image._dataBlock.data.size();
node.attribute("location").set_value(location.c_str());
}
}
XISFReader::XISFReader() XISFReader::XISFReader()
{ {
p = new XISFReaderPrivate; p = new XISFReaderPrivate;
@@ -1152,6 +1302,321 @@ void XISFWriter::writeImage(const Image &image)
p->writeImage(image); p->writeImage(image);
} }
class XISFModifyPrivate
{
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();
void save(const String &name);
void save(ByteArray &data);
void save(std::ostream &io);
void addFITSKeyword(uint32_t image, const FITSKeyword &keyword);
void updateFITSKeyword(uint32_t image, const FITSKeyword &keyword, bool add);
void removeFITSKeyword(uint32_t image, const String &name);
private:
void readXISFHeader();
void parseAttachmentPos(pugi::xml_node &root);
void updateAttachmentPos(pugi::xml_node &root, size_t offset);
std::unique_ptr<std::istream> _io;
std::unique_ptr<StreamBuffer> _buffer;
pugi::xml_document _doc;
pugi::xml_node _root;
std::map<int, std::pair<uint64_t, uint64_t>> _attachmentPos;// pair contain position and size
std::map<int, std::pair<uint64_t, uint64_t>> _attachmentPosNew;
};
void XISFModifyPrivate::open(const String &name)
{
close();
_io = std::make_unique<std::ifstream>(name.c_str(), std::ios_base::in | std::ios_base::binary);
readXISFHeader();
}
void XISFModifyPrivate::open(const ByteArray &data)
{
close();
_buffer = std::make_unique<StreamBuffer>(data);
_io = std::make_unique<std::istream>(_buffer.get());
readXISFHeader();
}
void XISFModifyPrivate::open(std::istream *io)
{
close();
_io.reset(io);
readXISFHeader();
}
void XISFModifyPrivate::close()
{
_io.reset();
_buffer.reset();
_root = pugi::xml_node();
_doc.reset();
}
void XISFModifyPrivate::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 XISFModifyPrivate::save(ByteArray &data)
{
StreamBuffer buffer;
std::ostream oss(&buffer);
save(oss);
data = buffer.byteArray();
}
void XISFModifyPrivate::save(std::ostream &io)
{
if(!_io || !_root)
throw Error("No input file opened");
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_copy = doc.append_copy(_root);
uint32_t size = 0;
std::string header;
while(true)
{
std::stringstream xml;
xml.write(signature, sizeof(signature));
doc.save(xml, "", pugi::format_raw);
header = xml.str();
if(size != header.size())
{
size = header.size();
updateAttachmentPos(root_copy, size);
}
else
{
break;
}
}
uint32_t headerSize = header.size() - sizeof(signature);
header.replace(8, sizeof(uint32_t), (const char*)&headerSize, sizeof(uint32_t));
io.write(header.c_str(), header.size());
const uint64_t BLOCK_SIZE = 1024*1024*4;
std::vector<char> data(BLOCK_SIZE);
for(auto &pos : _attachmentPos)
{
uint64_t oldPos = pos.second.first;
uint64_t size = pos.second.second;
_io->seekg(oldPos);
while(size)
{
_io->read(&data[0], std::min(size, BLOCK_SIZE));
io.write(&data[0], std::min(size, BLOCK_SIZE));
size -= std::min(size, BLOCK_SIZE);
}
}
}
void XISFModifyPrivate::addFITSKeyword(uint32_t image, const FITSKeyword &keyword)
{
if(!_root)
throw Error("No input file opened");
pugi::xpath_node_set images = _root.select_nodes("//Image");
if(image >= images.size())
throw Error("Out of bounds");
pugi::xml_node imageNode = images[image].node();
XISFWriterPrivate::writeFITSKeyword(imageNode, keyword);
}
void XISFModifyPrivate::updateFITSKeyword(uint32_t image, const FITSKeyword &keyword, bool add)
{
if(!_root)
throw Error("No input file opened");
pugi::xpath_node_set images = _root.select_nodes("//Image");
if(image >= images.size())
throw Error("Out of bounds");
pugi::xpath_variable_set variables;
variables.set("name", keyword.name.c_str());
pugi::xml_node imageNode = images[image].node();
pugi::xml_node keywordNode = imageNode.select_node("FITSKeyword[@name=string($name)]", &variables).node();
if(keywordNode)
{
if(keywordNode.attribute("value"))
keywordNode.attribute("value").set_value(keyword.value.c_str());
else
keywordNode.append_attribute("value").set_value(keyword.value.c_str());
if(keywordNode.attribute("comment"))
keywordNode.attribute("comment").set_value(keyword.comment.c_str());
else
keywordNode.append_attribute("comment").set_value(keyword.comment.c_str());
}
else if(add)
{
XISFWriterPrivate::writeFITSKeyword(imageNode, keyword);
}
}
void XISFModifyPrivate::removeFITSKeyword(uint32_t image, const String &name)
{
if(!_root)
throw Error("No input file opened");
pugi::xpath_node_set images = _root.select_nodes("//Image");
if(image >= images.size())
throw Error("Out of bounds");
pugi::xpath_variable_set variables;
variables.set("name", name.c_str());
pugi::xml_node imageNode = images[image].node();
pugi::xml_node keywordNode = imageNode.select_node("FITSKeyword[@name=string($name)]", &variables).node();
if(keywordNode)
imageNode.remove_child(keywordNode);
}
void XISFModifyPrivate::readXISFHeader()
{
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");
uint32_t headerLen[2] = {0};
_io->read((char*)&headerLen, sizeof(headerLen));
ByteArray xisfHeader(headerLen[0]);
_io->read(xisfHeader.data(), headerLen[0]);
_doc.load_buffer(xisfHeader.data(), xisfHeader.size());
_root = _doc.child("xisf");
parseAttachmentPos(_root);
if(!_root || _root.attribute("version").as_string() != std::string("1.0"))
throw Error("Unknown root XML element");
}
void XISFModifyPrivate::parseAttachmentPos(pugi::xml_node &root)
{
pugi::xpath_node_set locationAttributes = root.select_nodes("//@location");
int i = 0;
for(auto &attr : locationAttributes)
{
std::string locationStr = attr.attribute().as_string();
std::vector<std::string> location = splitString(locationStr, ':');
if(location.size() >= 3 && location[0] == "attachment")
{
uint64_t attachmentPos = std::stoull(location[1]);
uint64_t attachmentSize = std::stoull(location[2]);
_attachmentPos[i] = {attachmentPos, attachmentSize};
}
i++;
}
}
void XISFModifyPrivate::updateAttachmentPos(pugi::xml_node &root, size_t offset)
{
pugi::xpath_node_set locationAttributes = root.select_nodes("//@location");
for(auto &pos : _attachmentPos)
{
pugi::xml_attribute attr = locationAttributes[pos.first].attribute();
uint64_t attachmentSize = pos.second.second;
std::string locationStr = "attachment:" + std::to_string(offset) + ":" + std::to_string(attachmentSize);
_attachmentPosNew[pos.first] = pos.second;
offset += attachmentSize;
attr.set_value(locationStr.c_str());
}
}
XISFModify::XISFModify()
{
p = new XISFModifyPrivate();
}
XISFModify::~XISFModify()
{
delete p;
}
void XISFModify::open(const String &name)
{
p->open(name);
}
void XISFModify::open(const ByteArray &data)
{
p->open(data);
}
void XISFModify::open(std::istream *io)
{
p->open(io);
}
void XISFModify::close()
{
p->close();
}
void XISFModify::save(const String &name)
{
p->save(name);
}
void XISFModify::save(ByteArray &data)
{
p->save(data);
}
void XISFModify::save(std::ostream &io)
{
p->save(io);
}
void XISFModify::addFITSKeyword(uint32_t image, const FITSKeyword &keyword)
{
p->addFITSKeyword(image, keyword);
}
void XISFModify::updateFITSKeyword(uint32_t image, const FITSKeyword &keyword, bool add)
{
p->updateFITSKeyword(image, keyword, add);
}
void XISFModify::removeFITSKeyword(uint32_t image, const String &name)
{
p->removeFITSKeyword(image, name);
}
#define STRING_ENUM(map, map2, c, e) { map.insert({#e, c::e}); map2.insert({c::e, #e}); } #define STRING_ENUM(map, map2, c, e) { map.insert({#e, c::e}); map2.insert({c::e, #e}); }
struct Init struct Init
+43
View File
@@ -35,6 +35,7 @@ namespace LibXISF
class XISFReaderPrivate; class XISFReaderPrivate;
class XISFWriterPrivate; class XISFWriterPrivate;
class XISFModifyPrivate;
class LIBXISF_EXPORT ByteArray class LIBXISF_EXPORT ByteArray
{ {
@@ -180,6 +181,7 @@ struct LIBXISF_EXPORT DataBlock
uint64_t attachmentPos = 0; uint64_t attachmentPos = 0;
uint64_t attachmentSize = 0; uint64_t attachmentSize = 0;
uint64_t uncompressedSize = 0; uint64_t uncompressedSize = 0;
std::vector<std::pair<uint64_t, uint64_t>> subblocks;
CompressionCodec codec = None; CompressionCodec codec = None;
int compressLevel = -1; int compressLevel = -1;
ByteArray data; ByteArray data;
@@ -315,6 +317,11 @@ public:
const T* imageData() const { return static_cast<T*>(imageData()); } const T* imageData() const { return static_cast<T*>(imageData()); }
size_t imageDataSize() const; size_t imageDataSize() const;
DataBlock::CompressionCodec compression() const; DataBlock::CompressionCodec compression() const;
/** Set compression type and level.
* @param compression define which compression algorithm to use.
* @param level number between 0 and 100. Zero means lowest compression and maximum speed. Hundred means maximum compression speed.
* -1 means default compression level defined by compression library.
*/
void setCompression(DataBlock::CompressionCodec compression, int level = -1); void setCompression(DataBlock::CompressionCodec compression, int level = -1);
bool byteShuffling() const; bool byteShuffling() const;
void setByteshuffling(bool enable); void setByteshuffling(bool enable);
@@ -404,6 +411,42 @@ public:
const char* what() const noexcept { return _msg.c_str(); } const char* what() const noexcept { return _msg.c_str(); }
}; };
class LIBXISF_EXPORT XISFModify
{
public:
XISFModify();
virtual ~XISFModify();
void open(const String &name);
void open(const ByteArray &data);
void open(std::istream *io);
void close();
void save(const String &name);
void save(ByteArray &data);
void save(std::ostream &io);
/**
* @brief addFITSKeyword append new keyword to image
* @param image index of image to update
* @param keyword that will be added
*/
void addFITSKeyword(uint32_t image, const FITSKeyword &keyword);
/**
* @brief updateFITSKeyword update keyword with same name
* @param image index of image to update
* @param keyword that will be updated
* @param add if true then it will add new keyword in case that it doesn't exists
*/
void updateFITSKeyword(uint32_t image, const FITSKeyword &keyword, bool add);
/**
* @brief removeFITSKeyword remove keyword with name from image
* @param image index of image to update
* @param name of keyword that will be removed
*/
void removeFITSKeyword(uint32_t image, const String &name);
private:
XISFModifyPrivate *p;
};
template<typename T> template<typename T>
constexpr Image::SampleFormat Image::sampleFormatEnum() constexpr Image::SampleFormat Image::sampleFormatEnum()
{ {
+19 -1
View File
@@ -1,3 +1,21 @@
/************************************************************************
* LibXISF - library to load and save XISF files *
* Copyright (C) 2024 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 <http://www.gnu.org/licenses/>.*
************************************************************************/
#include "streambuffer.h" #include "streambuffer.h"
namespace LibXISF namespace LibXISF
@@ -89,7 +107,7 @@ std::streamsize StreamBuffer::xsgetn(char_type *s, std::streamsize n)
std::streamsize len = egptr() - gptr(); std::streamsize len = egptr() - gptr();
if(len > 0) if(len > 0)
{ {
std::streamsize c = std::min(n, len); std::streamsize c = n < len ? n : len;
std::memcpy(s, gptr(), c); std::memcpy(s, gptr(), c);
gbump(c); gbump(c);
ret = c; ret = c;
+18
View File
@@ -1,3 +1,21 @@
/************************************************************************
* LibXISF - library to load and save XISF files *
* Copyright (C) 2024 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 <http://www.gnu.org/licenses/>.*
************************************************************************/
#ifndef STREAMBUFFER_H #ifndef STREAMBUFFER_H
#define STREAMBUFFER_H #define STREAMBUFFER_H
+1 -1
View File
@@ -52,7 +52,7 @@ void sha1(uint8_t *data, size_t len, uint8_t *hash)
nlen += 64 - nlen % 64; nlen += 64 - nlen % 64;
tmp.resize(nlen, 0); tmp.resize(nlen, 0);
size_t ml = len * 8; uint64_t ml = len * 8;
tmp[nlen - 1] = ml & 0xff; tmp[nlen - 1] = ml & 0xff;
tmp[nlen - 2] = ml >> 8 & 0xff; tmp[nlen - 2] = ml >> 8 & 0xff;
tmp[nlen - 3] = ml >> 16 & 0xff; tmp[nlen - 3] = ml >> 16 & 0xff;
+36 -4
View File
@@ -108,8 +108,8 @@ static std::map<Variant::Type, const char*> idToType = {
{Variant::Type::UI32Matrix, "UI32Matrix"}, {Variant::Type::UI32Matrix, "UI32Matrix"},
{Variant::Type::I64Matrix, "I64Matrix"}, {Variant::Type::I64Matrix, "I64Matrix"},
{Variant::Type::UI64Matrix, "UI64Matrix"}, {Variant::Type::UI64Matrix, "UI64Matrix"},
{Variant::Type::F32Matrix, "I8Matrix"}, {Variant::Type::F32Matrix, "F32Matrix"},
{Variant::Type::F64Matrix, "UI8Matrix"}, {Variant::Type::F64Matrix, "F64Matrix"},
}; };
template<typename T> template<typename T>
@@ -196,9 +196,12 @@ void deserializeVariant(const pugi::xml_node &node, Variant &variant, const Byte
std::string type = node.attribute("type").as_string(); std::string type = node.attribute("type").as_string();
Variant::Type typeId = typeToId[type]; Variant::Type typeId = typeToId[type];
if(typeId == Variant::Type::String && !node.attribute("location")) if(typeId == Variant::Type::String)
{ {
variant.setValue(node.text().as_string()); if(!node.attribute("location"))
variant.setValue(node.text().as_string());
else
variant.setValue(String(data.constData(), data.size()));
} }
else if(node.attribute("value")) else if(node.attribute("value"))
{ {
@@ -400,6 +403,7 @@ Variant variantFromString(Variant::Type type, const String &str)
Variant::Type Variant::type() const Variant::Type Variant::type() const
{ {
int idx = _value.index();
return (Variant::Type)_value.index(); return (Variant::Type)_value.index();
} }
@@ -430,6 +434,32 @@ String Variant::toString() const
return ss.str(); return ss.str();
}; };
auto matrixString = [](auto matrix) {
std::stringstream ss;
ss << "{";
for(int i=0; i<matrix.rows(); i++)
{
ss << "{";
for(int o=0; o<matrix.cols(); o++)
{
#if __GNUC__ >= 11 || __clang__
char str[128] = {0};
char *end = str + sizeof(str);
std::to_chars(str, end, matrix(i, o));
ss << str;
#else
ss << std::to_string(matrix(i, o));
#endif
if(o < matrix.cols() - 1)
ss << ",";
}
ss << "}";
if(i < matrix.rows() - 1)
ss << ",";
}
return ss.str();
};
switch(type()) switch(type())
{ {
case Variant::Type::Int8: toChars<Int8>(_value, str, end); break; case Variant::Type::Int8: toChars<Int8>(_value, str, end); break;
@@ -454,6 +484,8 @@ String Variant::toString() const
case Variant::Type::UI64Vector: string = vectorString(std::get<UI64Vector>(_value)); break; case Variant::Type::UI64Vector: string = vectorString(std::get<UI64Vector>(_value)); break;
case Variant::Type::F32Vector: string = vectorString(std::get<F32Vector>(_value)); break; case Variant::Type::F32Vector: string = vectorString(std::get<F32Vector>(_value)); break;
case Variant::Type::F64Vector: string = vectorString(std::get<F64Vector>(_value)); break; case Variant::Type::F64Vector: string = vectorString(std::get<F64Vector>(_value)); break;
case Variant::Type::F32Matrix: string = matrixString(std::get<F32Matrix>(_value)); break;
case Variant::Type::F64Matrix: string = matrixString(std::get<F64Matrix>(_value)); break;
case Variant::Type::String: string = std::get<String>(_value); break; case Variant::Type::String: string = std::get<String>(_value); break;
case Variant::Type::TimePoint: case Variant::Type::TimePoint:
{ {