1
0
mirror of https://github.com/opencv/opencv.git synced 2026-07-30 15:53:03 +04:00

Merge branch 4.x

This commit is contained in:
Alexander Smorkalov
2025-07-28 10:04:17 +03:00
28 changed files with 780 additions and 158 deletions
+68
View File
@@ -42,6 +42,7 @@
#include "precomp.hpp"
#include "exif.hpp"
#include "opencv2/core/utils/logger.hpp"
namespace {
@@ -53,6 +54,43 @@ namespace {
namespace cv
{
static std::string HexStringToBytes(const char* hexstring, size_t expected_length);
// Converts the NULL terminated 'hexstring' which contains 2-byte character
// representations of hex values to raw data.
// 'hexstring' may contain values consisting of [A-F][a-f][0-9] in pairs,
// e.g., 7af2..., separated by any number of newlines.
// 'expected_length' is the anticipated processed size.
// On success the raw buffer is returned with its length equivalent to
// 'expected_length'. NULL is returned if the processed length is less than
// 'expected_length' or any character aside from those above is encountered.
// The returned buffer must be freed by the caller.
static std::string HexStringToBytes(const char* hexstring,
size_t expected_length) {
const char* src = hexstring;
size_t actual_length = 0;
std::string raw_data;
raw_data.resize(expected_length);
char* dst = const_cast<char*>(raw_data.data());
for (; actual_length < expected_length && *src != '\0'; ++src) {
char* end;
char val[3];
if (*src == '\n') continue;
val[0] = *src++;
val[1] = *src;
val[2] = '\0';
*dst++ = static_cast<uint8_t>(strtol(val, &end, 16));
if (end != val + 2) break;
++actual_length;
}
if (actual_length != expected_length) {
raw_data.clear();
}
return raw_data;
}
ExifEntry_t::ExifEntry_t() :
field_float(0), field_double(0), field_u32(0), field_s32(0),
tag(INVALID_TAG), field_u16(0), field_s16(0), field_u8(0), field_s8(0)
@@ -99,6 +137,36 @@ const std::vector<unsigned char>& ExifReader::getData() const
return m_data;
}
bool ExifReader::processRawProfile(const char* profile, size_t profile_len) {
const char* src = profile;
char* end;
int expected_length;
if (profile == nullptr || profile_len == 0) return false;
// ImageMagick formats 'raw profiles' as
// '\n<name>\n<length>(%8lu)\n<hex payload>\n'.
if (*src != '\n') {
CV_LOG_WARNING(NULL, cv::format("Malformed raw profile, expected '\\n' got '\\x%.2X'", *src));
return false;
}
++src;
// skip the profile name and extract the length.
while (*src != '\0' && *src++ != '\n') {}
expected_length = static_cast<int>(strtol(src, &end, 10));
if (*end != '\n') {
CV_LOG_WARNING(NULL, cv::format("Malformed raw profile, expected '\\n' got '\\x%.2X'", *src));
return false;
}
++end;
// 'end' now points to the profile payload.
std::string payload = HexStringToBytes(end, expected_length);
if (payload.size() == 0) return false;
return parseExif((unsigned char*)payload.c_str() + 6, expected_length - 6);
}
/**
* @brief Parsing the exif data buffer and prepare (internal) exif directory
*
+1
View File
@@ -154,6 +154,7 @@ public:
ExifReader();
~ExifReader();
bool processRawProfile(const char* profile, size_t profile_len);
/**
* @brief Parse the file with exif info
+24 -6
View File
@@ -56,6 +56,8 @@ BaseImageDecoder::BaseImageDecoder()
m_scale_denom = 1;
m_use_rgb = false;
m_frame_count = 1;
m_read_options = 0;
m_metadata.resize(IMAGE_METADATA_MAX + 1);
}
bool BaseImageDecoder::haveMetadata(ImageMetadataType type) const
@@ -67,12 +69,21 @@ bool BaseImageDecoder::haveMetadata(ImageMetadataType type) const
Mat BaseImageDecoder::getMetadata(ImageMetadataType type) const
{
if (type == IMAGE_METADATA_EXIF) {
const std::vector<unsigned char>& exif = m_exif.getData();
if (!exif.empty()) {
Mat exifmat(1, (int)exif.size(), CV_8U, (void*)exif.data());
return exifmat;
}
auto makeMat = [](const std::vector<unsigned char>& data) -> Mat {
return data.empty() ? Mat() : Mat(1, (int)data.size(), CV_8U, (void*)data.data());
};
switch (type) {
case IMAGE_METADATA_EXIF:
return makeMat(m_exif.getData());
case IMAGE_METADATA_XMP:
case IMAGE_METADATA_ICCP:
return makeMat(m_metadata[type]);
default:
CV_LOG_WARNING(NULL, "Unknown metadata type requested: " << static_cast<int>(type));
break;
}
return Mat();
}
@@ -116,6 +127,13 @@ int BaseImageDecoder::setScale( const int& scale_denom )
return temp;
}
int BaseImageDecoder::setReadOptions(int read_options)
{
int temp = m_read_options;
m_read_options = read_options;
return temp;
}
void BaseImageDecoder::setRGB(bool useRGB)
{
m_use_rgb = useRGB;
+20 -1
View File
@@ -109,7 +109,24 @@ public:
* @param scale_denom The denominator of the scale factor (image is scaled down by 1/scale_denom).
* @return The scale factor that was set.
*/
virtual int setScale(const int& scale_denom);
int setScale(const int& scale_denom);
/**
* @brief Set read options for image decoding.
*
* This function sets internal flags that control various read-time behaviors
* such as metadata extraction (e.g., XMP, ICC profiles, textual data)
* during image decoding. The flags can be combined using bitwise OR to enable
* multiple options simultaneously.
*
* @param options Bitwise OR of read option flags to enable.
*
* @return The previous value of the read options flags.
*
* @note Setting this has no effect unless the image format and decoder support
* the selected options. Unknown flags will be ignored.
*/
int setReadOptions(int read_options);
/**
* @brief Read the image header to extract basic properties (width, height, type).
@@ -173,6 +190,8 @@ protected:
ExifReader m_exif; ///< Object for reading EXIF metadata from the image.
size_t m_frame_count; ///< Number of frames in the image (for animations and multi-page images).
Animation m_animation;
int m_read_options;
std::vector<std::vector<unsigned char> > m_metadata;
};
+97 -29
View File
@@ -218,16 +218,14 @@ bool PngDecoder::InitPngPtr() {
return false;
m_info_ptr = png_create_info_struct(m_png_ptr);
m_end_info = png_create_info_struct(m_png_ptr);
return (m_info_ptr && m_end_info);
return (m_info_ptr != nullptr);
}
void PngDecoder::ClearPngPtr() {
if (m_png_ptr)
png_destroy_read_struct(&m_png_ptr, &m_info_ptr, &m_end_info);
png_destroy_read_struct(&m_png_ptr, &m_info_ptr, nullptr);
m_png_ptr = nullptr;
m_info_ptr = nullptr;
m_end_info = nullptr;
}
ImageDecoder PngDecoder::newDecoder() const
@@ -573,7 +571,7 @@ bool PngDecoder::readData( Mat& img )
volatile bool result = false;
bool color = img.channels() > 1;
if( m_png_ptr && m_info_ptr && m_end_info && m_width && m_height )
if( m_png_ptr && m_info_ptr && m_width && m_height )
{
if( setjmp( png_jmpbuf ( m_png_ptr ) ) == 0 )
{
@@ -623,17 +621,49 @@ bool PngDecoder::readData( Mat& img )
buffer[y] = img.data + y*img.step;
png_read_image( m_png_ptr, buffer );
png_read_end( m_png_ptr, m_end_info );
png_read_end( m_png_ptr, m_info_ptr);
if (m_read_options) {
// Get tEXt chunks
png_textp text_ptr;
int num_text = 0;
png_get_text(m_png_ptr, m_info_ptr, &text_ptr, &num_text);
for (size_t i = 0; i < static_cast<size_t>(num_text); ++i) {
const char* key = text_ptr[i].key;
const char* value = text_ptr[i].text;
size_t len = text_ptr[i].text_length;
if (key && (!std::strcmp(key, "Raw profile type exif") || !std::strcmp(key, "Raw profile type APP1"))) {
m_exif.processRawProfile(value, len);
}
else if (key && !std::strcmp(key, "XML:com.adobe.xmp")) {
auto& out = m_metadata[IMAGE_METADATA_XMP];
out.insert(out.end(),
reinterpret_cast<const unsigned char*>(value),
reinterpret_cast<const unsigned char*>(value) + std::strlen(value) + 1);
}
}
png_charp icc_name;
int compression_type;
png_bytep icc_profile;
png_uint_32 icc_length;
if (png_get_iCCP(m_png_ptr, m_info_ptr, &icc_name, &compression_type, &icc_profile, &icc_length)) {
auto& out = m_metadata[IMAGE_METADATA_ICCP];
out.insert(out.end(),
reinterpret_cast<const unsigned char*>(icc_profile),
reinterpret_cast<const unsigned char*>(icc_profile) + icc_length);
}
}
#ifdef PNG_eXIf_SUPPORTED
png_uint_32 num_exif = 0;
png_bytep exif = 0;
// Exif info could be in info_ptr (intro_info) or end_info per specification
if( png_get_valid(m_png_ptr, m_info_ptr, PNG_INFO_eXIf) )
png_get_eXIf_1(m_png_ptr, m_info_ptr, &num_exif, &exif);
else if( png_get_valid(m_png_ptr, m_end_info, PNG_INFO_eXIf) )
png_get_eXIf_1(m_png_ptr, m_end_info, &num_exif, &exif);
if( exif && num_exif > 0 )
{
@@ -865,6 +895,8 @@ PngEncoder::PngEncoder()
m_buf_supported = true;
m_support_metadata.assign((size_t)IMAGE_METADATA_MAX+1, false);
m_support_metadata[IMAGE_METADATA_EXIF] = true;
m_support_metadata[IMAGE_METADATA_XMP] = true;
m_support_metadata[IMAGE_METADATA_ICCP] = true;
op_zstream1.zalloc = NULL;
op_zstream2.zalloc = NULL;
next_seq_num = 0;
@@ -947,26 +979,36 @@ bool PngEncoder::write( const Mat& img, const std::vector<int>& params )
for( size_t i = 0; i < params.size(); i += 2 )
{
if( params[i] == IMWRITE_PNG_COMPRESSION )
switch (params[i])
{
case IMWRITE_PNG_COMPRESSION:
m_compression_strategy = IMWRITE_PNG_STRATEGY_DEFAULT; // Default strategy
m_compression_level = params[i+1];
m_compression_level = MIN(MAX(m_compression_level, 0), Z_BEST_COMPRESSION);
set_compression_level = true;
}
if( params[i] == IMWRITE_PNG_STRATEGY )
{
break;
case IMWRITE_PNG_STRATEGY:
m_compression_strategy = params[i+1];
m_compression_strategy = MIN(MAX(m_compression_strategy, 0), Z_FIXED);
}
if( params[i] == IMWRITE_PNG_BILEVEL )
{
break;
case IMWRITE_PNG_BILEVEL:
m_isBilevel = params[i+1] != 0;
}
if( params[i] == IMWRITE_PNG_FILTER )
{
break;
case IMWRITE_PNG_FILTER:
m_filter = params[i+1];
set_filter = true;
break;
case IMWRITE_PNG_ZLIBBUFFER_SIZE:
png_set_compression_buffer_size(png_ptr, params[i+1]);
break;
default:
CV_LOG_WARNING(NULL, "An unknown or unsupported ImwriteFlags value was specified and has been ignored.");
break;
}
}
@@ -983,6 +1025,42 @@ bool PngEncoder::write( const Mat& img, const std::vector<int>& params )
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
PNG_FILTER_TYPE_DEFAULT );
if (!m_metadata.empty()) {
std::vector<uchar>& exif = m_metadata[IMAGE_METADATA_EXIF];
if (!exif.empty()) {
png_set_eXIf_1(png_ptr, info_ptr, static_cast<png_uint_32>(exif.size()), exif.data());
}
std::vector<uchar>& xmp = m_metadata[IMAGE_METADATA_XMP];
if (!xmp.empty()) {
png_text text_chunk;
text_chunk.compression = PNG_TEXT_COMPRESSION_NONE;
text_chunk.key = const_cast<char*>("XML:com.adobe.xmp");
text_chunk.text = reinterpret_cast<char*>(xmp.data());
text_chunk.text_length = static_cast<png_size_t>(xmp.size());
png_set_text(png_ptr, info_ptr, &text_chunk, 1);
}
std::vector<uchar> iccp = m_metadata[IMAGE_METADATA_ICCP];
if (!iccp.empty()) {
// PNG standard requires a profile name (null-terminated, max 79 characters, printable Latin-1)
const char* iccp_profile_name = "ICC Profile";
// Compression type must be 0 (deflate) as per libpng docs
int compression_type = PNG_COMPRESSION_TYPE_BASE;
// Some ICC profiles are already compressed (e.g., if saved from Photoshop),
// but png_set_iCCP still expects uncompressed input, and it compresses it internally.
png_set_iCCP(png_ptr, info_ptr,
iccp_profile_name,
compression_type,
reinterpret_cast<png_const_bytep>(iccp.data()),
static_cast<png_uint_32>(iccp.size()));
}
}
png_write_info( png_ptr, info_ptr );
if (m_isBilevel)
@@ -996,16 +1074,6 @@ bool PngEncoder::write( const Mat& img, const std::vector<int>& params )
for( y = 0; y < height; y++ )
buffer[y] = img.data + y*img.step;
if (!m_metadata.empty()) {
std::vector<uchar>& exif = m_metadata[IMAGE_METADATA_EXIF];
if (!exif.empty()) {
writeChunk(f, "eXIf", exif.data(), (uint32_t)exif.size());
}
// [TODO] add xmp and icc. They need special handling,
// see https://dev.exiv2.org/projects/exiv2/wiki/The_Metadata_in_PNG_files and
// https://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html.
}
png_write_image( png_ptr, buffer.data() );
png_write_end( png_ptr, info_ptr );
-1
View File
@@ -150,7 +150,6 @@ private:
png_structp m_png_ptr = nullptr; // pointer to decompression structure
png_infop m_info_ptr = nullptr; // pointer to image information structure
png_infop m_end_info = nullptr; // pointer to one more image information structure
int m_bit_depth;
FILE* m_f;
int m_color_type;
+77
View File
@@ -422,6 +422,48 @@ bool SPngDecoder::readData(Mat &img)
result = m_exif.parseExif((unsigned char *)exif_s.data, exif_s.length);
}
}
if (m_read_options)
{
uint32_t text_count;
// Retrieve all text chunks
if (spng_get_text(png_ptr, NULL, &text_count) == SPNG_OK)
{
std::vector<spng_text> texts(text_count);
spng_get_text(png_ptr, texts.data(), &text_count);
for (size_t i = 0; i < text_count; ++i)
{
char* key = texts[i].keyword;
char* value = texts[i].text;
size_t len = texts[i].length;
if (key && (!std::strcmp(key, "Raw profile type exif") || !std::strcmp(key, "Raw profile type APP1")))
{
m_exif.processRawProfile(value, len);
}
else if (key && !std::strcmp(key, "XML:com.adobe.xmp"))
{
auto& out = m_metadata[IMAGE_METADATA_XMP];
out.insert(out.end(),
value,
value + len + 1); // include null terminator
}
}
}
// ICC Profile
spng_iccp iccp_data;
if (spng_get_iccp(png_ptr, &iccp_data) == SPNG_OK && iccp_data.profile_len > 0)
{
auto& out = m_metadata[IMAGE_METADATA_ICCP];
out.insert(out.end(),
iccp_data.profile,
iccp_data.profile + iccp_data.profile_len);
}
}
}
}
}
@@ -435,6 +477,10 @@ SPngEncoder::SPngEncoder()
{
m_description = "Portable Network Graphics files (*.png)";
m_buf_supported = true;
m_support_metadata.assign((size_t)IMAGE_METADATA_MAX + 1, false);
m_support_metadata[IMAGE_METADATA_EXIF] = true;
m_support_metadata[IMAGE_METADATA_XMP] = true;
m_support_metadata[IMAGE_METADATA_ICCP] = true;
}
SPngEncoder::~SPngEncoder()
@@ -536,6 +582,37 @@ bool SPngEncoder::write(const Mat &img, const std::vector<int> &params)
spng_set_option(ctx, SPNG_IMG_COMPRESSION_LEVEL, compression_level);
spng_set_option(ctx, SPNG_IMG_COMPRESSION_STRATEGY, compression_strategy);
if (!m_metadata.empty()) {
std::vector<uchar>& exif = m_metadata[IMAGE_METADATA_EXIF];
if (!exif.empty()) {
spng_exif s_exif;
s_exif.data = reinterpret_cast<char*>(exif.data());
s_exif.length = exif.size();
spng_set_exif(ctx, &s_exif);
}
std::vector<uchar>& xmp = m_metadata[IMAGE_METADATA_XMP];
if (!xmp.empty()) {
spng_text text_chunk;
strncpy(text_chunk.keyword, "XML:com.adobe.xmp", sizeof(text_chunk.keyword) - 1);
text_chunk.keyword[sizeof(text_chunk.keyword) - 1] = '\0';
text_chunk.type = SPNG_TEXT;
text_chunk.text = reinterpret_cast<char*>(xmp.data());
text_chunk.length = xmp.size();
spng_set_text(ctx, &text_chunk, 1);
}
std::vector<uchar>& iccp = m_metadata[IMAGE_METADATA_ICCP];
if (!iccp.empty()) {
spng_iccp s_iccp;
strncpy(s_iccp.profile_name, "ICC Profile", sizeof(s_iccp.profile_name) - 1);
s_iccp.profile_name[sizeof(s_iccp.profile_name) - 1] = '\0';
s_iccp.profile_len = iccp.size();
s_iccp.profile = reinterpret_cast<char*>(iccp.data());
spng_set_iccp(ctx, &s_iccp);
}
}
int ret;
spng_encode_chunks(ctx);
ret = spng_encode_image(ctx, nullptr, 0, SPNG_FMT_PNG, SPNG_ENCODE_PROGRESSIVE);
+87 -2
View File
@@ -68,6 +68,7 @@ WebPDecoder::WebPDecoder()
fs_size = 0;
m_has_animation = false;
m_previous_timestamp = 0;
m_read_options = 1;
}
WebPDecoder::~WebPDecoder() {}
@@ -197,6 +198,47 @@ bool WebPDecoder::readData(Mat &img)
}
CV_Assert(data.type() == CV_8UC1); CV_Assert(data.rows == 1);
if (m_read_options) {
WebPData webp_data;
webp_data.bytes = (const uint8_t*)data.ptr();
webp_data.size = data.total();
std::vector<uchar> metadata;
WebPDemuxer* demux = WebPDemux(&webp_data);
if (demux)
{
WebPChunkIterator chunk_iter;
if (WebPDemuxGetChunk(demux, "EXIF", 1, &chunk_iter))
{
metadata = std::vector<uchar>(chunk_iter.chunk.bytes,
chunk_iter.chunk.bytes + chunk_iter.chunk.size);
WebPDemuxReleaseChunkIterator(&chunk_iter);
m_exif.parseExif(metadata.data(), metadata.size());
}
if (WebPDemuxGetChunk(demux, "ICCP", 1, &chunk_iter))
{
metadata = std::vector<uchar>(chunk_iter.chunk.bytes,
chunk_iter.chunk.bytes + chunk_iter.chunk.size);
WebPDemuxReleaseChunkIterator(&chunk_iter);
m_metadata[IMAGE_METADATA_ICCP] = metadata;
}
if (WebPDemuxGetChunk(demux, "XMP ", 1, &chunk_iter)) // note the space in "XMP "
{
metadata = std::vector<uchar>(chunk_iter.chunk.bytes,
chunk_iter.chunk.bytes + chunk_iter.chunk.size);
WebPDemuxReleaseChunkIterator(&chunk_iter);
m_metadata[IMAGE_METADATA_XMP] = metadata;
}
WebPDemuxDelete(demux);
m_read_options = 0;
}
}
Mat read_img;
CV_CheckType(img.type(), img.type() == CV_8UC1 || img.type() == CV_8UC3 || img.type() == CV_8UC4, "");
if (img.type() != m_type || img.cols != m_width || img.rows != m_height)
@@ -292,6 +334,10 @@ WebPEncoder::WebPEncoder()
{
m_description = "WebP files (*.webp)";
m_buf_supported = true;
m_support_metadata.assign((size_t)IMAGE_METADATA_MAX + 1, false);
m_support_metadata[IMAGE_METADATA_EXIF] = true;
m_support_metadata[IMAGE_METADATA_XMP] = true;
m_support_metadata[IMAGE_METADATA_ICCP] = true;
}
WebPEncoder::~WebPEncoder() { }
@@ -364,6 +410,45 @@ bool WebPEncoder::write(const Mat& img, const std::vector<int>& params)
size = WebPEncodeBGRA(image->ptr(), width, height, (int)image->step, quality, &out);
}
}
WebPData finalData = { out, size };
if (!m_metadata.empty()) {
WebPMux* mux = WebPMuxNew();
WebPData imageData = { out, size };
WebPMuxSetImage(mux, &imageData, 0);
WebPData metadata;
if (m_metadata[IMAGE_METADATA_EXIF].size() > 0)
{
metadata.bytes = m_metadata[IMAGE_METADATA_EXIF].data();
metadata.size = m_metadata[IMAGE_METADATA_EXIF].size();
WebPMuxSetChunk(mux, "EXIF", &metadata, 1);
}
if (m_metadata[IMAGE_METADATA_XMP].size() > 0)
{
metadata.bytes = m_metadata[IMAGE_METADATA_XMP].data();
metadata.size = m_metadata[IMAGE_METADATA_XMP].size();
WebPMuxSetChunk(mux, "XMP ", &metadata, 1);
}
if (m_metadata[IMAGE_METADATA_ICCP].size() > 0)
{
metadata.bytes = m_metadata[IMAGE_METADATA_ICCP].data();
metadata.size = m_metadata[IMAGE_METADATA_ICCP].size();
WebPMuxSetChunk(mux, "ICCP", &metadata, 1);
}
if (WebPMuxAssemble(mux, &finalData) == WEBP_MUX_OK) {
size = finalData.size;
WebPMuxDelete(mux);
}
else {
WebPMuxDelete(mux);
CV_Error(Error::StsError, "Failed to assemble WebP with EXIF");
}
}
#if WEBP_DECODER_ABI_VERSION >= 0x0206
Ptr<uint8_t> out_cleaner(out, WebPFree);
#else
@@ -375,7 +460,7 @@ bool WebPEncoder::write(const Mat& img, const std::vector<int>& params)
if (m_buf)
{
m_buf->resize(size);
memcpy(&(*m_buf)[0], out, size);
memcpy(&(*m_buf)[0], finalData.bytes, size);
bytes_written = size;
}
else
@@ -383,7 +468,7 @@ bool WebPEncoder::write(const Mat& img, const std::vector<int>& params)
FILE *fd = fopen(m_filename.c_str(), "wb");
if (fd != NULL)
{
bytes_written = fwrite(out, sizeof(uint8_t), size, fd);
bytes_written = fwrite(finalData.bytes, sizeof(uint8_t), size, fd);
if (size != bytes_written)
{
CV_LOG_ERROR(NULL, cv::format("Only %zu or %zu bytes are written\n",bytes_written, size));
+12 -6
View File
@@ -498,9 +498,6 @@ imread_( const String& filename, int flags, OutputArray mat,
/// Search for the relevant decoder to handle the imagery
ImageDecoder decoder;
if (metadata_types)
metadata_types->clear();
#ifdef HAVE_GDAL
if(flags != IMREAD_UNCHANGED && (flags & IMREAD_LOAD_GDAL) == IMREAD_LOAD_GDAL ){
decoder = GdalDecoder().newDecoder();
@@ -539,6 +536,12 @@ imread_( const String& filename, int flags, OutputArray mat,
/// set the filename in the driver
decoder->setSource( filename );
if (metadata_types)
{
metadata_types->clear();
decoder->setReadOptions(1);
}
try
{
// read the header to make sure it succeeds
@@ -1249,9 +1252,6 @@ imdecode_( const Mat& buf, int flags, Mat& mat,
std::vector<int>* metadata_types,
OutputArrayOfArrays metadata )
{
if (metadata_types)
metadata_types->clear();
CV_Assert(!buf.empty());
CV_Assert(buf.isContinuous());
CV_Assert(buf.checkVector(1, CV_8U) > 0);
@@ -1302,6 +1302,12 @@ imdecode_( const Mat& buf, int flags, Mat& mat,
decoder->setSource(filename);
}
if (metadata_types)
{
metadata_types->clear();
decoder->setReadOptions(1);
}
bool success = false;
try
{