Merge pull request #27503 from sturkmen72:metadata

[GSOC 2025] PNG&WebP Metadata Reading Writing Improvements #27503

Merge with https://github.com/opencv/opencv_extra/pull/1271

### Pull Request Readiness Checklist

See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request

- [x] I agree to contribute to the project under Apache 2 License.
- [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV
- [x] The PR is proposed to the proper branch
- [ ] There is a reference to the original bug report and related work
- [ ] There is accuracy test, performance test and test data in opencv_extra repository, if applicable
      Patch to opencv_extra has the same branch name.
- [ ] The feature is well documented and sample code can be built with the project CMake
This commit is contained in:
Suleyman TURKMEN 2025-07-23 14:59:34 +03:00 committed by GitHub
parent 15e3cf3cc5
commit 32bd8c9632
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 655 additions and 126 deletions

View File

@ -254,11 +254,13 @@ enum ImwriteGIFCompressionFlags {
enum ImageMetadataType
{
IMAGE_METADATA_UNKNOWN = -1,
IMAGE_METADATA_EXIF = 0,
IMAGE_METADATA_XMP = 1,
IMAGE_METADATA_ICCP = 2,
IMAGE_METADATA_MAX = 2
IMAGE_METADATA_UNKNOWN = -1, // Used when metadata type is unrecognized or not set
IMAGE_METADATA_EXIF = 0, // EXIF metadata (e.g., camera info, GPS, orientation)
IMAGE_METADATA_XMP = 1, // XMP metadata (eXtensible Metadata Platform - Adobe format)
IMAGE_METADATA_ICCP = 2, // ICC Profile (color profile for color management)
IMAGE_METADATA_MAX = 2 // Highest valid index (usually used for bounds checking)
};
//! @} imgcodecs_flags
@ -355,7 +357,7 @@ Currently, the following file formats are supported:
the environment variable `OPENCV_IO_MAX_IMAGE_PIXELS`. See @ref tutorial_env_reference.
@param filename Name of the file to be loaded.
@param flags Flag that can take values of `cv::ImreadModes`.
@param flags Flag that can take values of cv::ImreadModes, default with cv::IMREAD_COLOR_BGR.
*/
CV_EXPORTS_W Mat imread( const String& filename, int flags = IMREAD_COLOR_BGR );
@ -364,19 +366,25 @@ CV_EXPORTS_W Mat imread( const String& filename, int flags = IMREAD_COLOR_BGR );
This is an overloaded member function, provided for convenience. It differs from the above function only in what argument(s) it accepts and the return value.
@param filename Name of file to be loaded.
@param dst object in which the image will be loaded.
@param flags Flag that can take values of cv::ImreadModes
@param flags Flag that can take values of cv::ImreadModes, default with cv::IMREAD_COLOR_BGR.
@note
The image passing through the img parameter can be pre-allocated. The memory is reused if the shape and the type match with the load image.
*/
CV_EXPORTS_W void imread( const String& filename, OutputArray dst, int flags = IMREAD_COLOR_BGR );
/** @brief Reads an image from a file together with associated metadata.
/** @brief Reads an image from a file along with associated metadata.
The function imreadWithMetadata reads image from the specified file. It does the same thing as imread, but additionally reads metadata if the corresponding file contains any.
This function behaves similarly to cv::imread(), loading an image from the specified file.
In addition to the image pixel data, it also attempts to extract any available metadata
embedded in the file (such as EXIF, XMP, etc.), depending on file format support.
@note In the case of color images, the decoded images will have the channels stored in **B G R** order.
@param filename Name of the file to be loaded.
@param metadataTypes Output vector with types of metadata chucks returned in metadata, see ImageMetadataType.
@param metadata Output vector of vectors or vector of matrices to store the retrieved metadata
@param flags Flag that can take values of cv::ImreadModes
@param metadataTypes Output vector with types of metadata chunks returned in metadata, see ImageMetadataType.
@param metadata Output vector of vectors or vector of matrices to store the retrieved metadata.
@param flags Flag that can take values of cv::ImreadModes, default with cv::IMREAD_ANYCOLOR.
@return The loaded image as a cv::Mat object. If the image cannot be read, the function returns an empty matrix.
*/
CV_EXPORTS_W Mat imreadWithMetadata( const String& filename, CV_OUT std::vector<int>& metadataTypes,
OutputArrayOfArrays metadata, int flags = IMREAD_ANYCOLOR);
@ -560,29 +568,31 @@ See cv::imread for the list of supported formats and flags description.
@note In the case of color images, the decoded images will have the channels stored in **B G R** order.
@param buf Input array or vector of bytes.
@param flags The same flags as in cv::imread, see cv::ImreadModes.
@param flags Flag that can take values of cv::ImreadModes.
*/
CV_EXPORTS_W Mat imdecode( InputArray buf, int flags );
/** @brief Reads an image from a buffer in memory together with associated metadata.
/** @brief Reads an image from a memory buffer and extracts associated metadata.
The function imdecode reads an image from the specified buffer in the memory. If the buffer is too short or
This function decodes an image from the specified memory buffer. If the buffer is too short or
contains invalid data, the function returns an empty matrix ( Mat::data==NULL ).
See cv::imread for the list of supported formats and flags description.
@note In the case of color images, the decoded images will have the channels stored in **B G R** order.
@param buf Input array or vector of bytes.
@param metadataTypes Output vector with types of metadata chucks returned in metadata, see ImageMetadataType.
@param buf Input array or vector of bytes containing the encoded image data.
@param metadataTypes Output vector with types of metadata chucks returned in metadata, see cv::ImageMetadataType
@param metadata Output vector of vectors or vector of matrices to store the retrieved metadata
@param flags The same flags as in cv::imread, see cv::ImreadModes.
@param flags Flag that can take values of cv::ImreadModes, default with cv::IMREAD_ANYCOLOR.
@return The decoded image as a cv::Mat object. If decoding fails, the function returns an empty matrix.
*/
CV_EXPORTS_W Mat imdecodeWithMetadata( InputArray buf, CV_OUT std::vector<int>& metadataTypes,
OutputArrayOfArrays metadata, int flags = IMREAD_ANYCOLOR );
/** @overload
@param buf Input array or vector of bytes.
@param flags The same flags as in cv::imread, see cv::ImreadModes.
@param flags Flag that can take values of cv::ImreadModes, default with cv::IMREAD_ANYCOLOR.
@param dst The optional output placeholder for the decoded matrix. It can save the image
reallocations when the function is called repeatedly for images of the same size. In case of decoder
failure the function returns empty cv::Mat object, but does not release user-provided dst buffer.
@ -598,7 +608,7 @@ See cv::imreadmulti for the list of supported formats and flags description.
@note In the case of color images, the decoded images will have the channels stored in **B G R** order.
@param buf Input array or vector of bytes.
@param flags The same flags as in cv::imread, see cv::ImreadModes.
@param flags Flag that can take values of cv::ImreadModes.
@param mats A vector of Mat objects holding each page, if more than one.
@param range A continuous selection of pages.
*/

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
*

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

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;

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;
};

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;
@ -993,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)
@ -1006,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 );

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;

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);

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));

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
@ -1268,9 +1271,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);
@ -1321,6 +1321,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
{

View File

@ -9,7 +9,142 @@
namespace opencv_test { namespace {
/**
static Mat makeCirclesImage(Size size, int type, int nbits)
{
Mat img(size, type);
img.setTo(Scalar::all(0));
RNG& rng = theRNG();
int maxval = (int)(1 << nbits);
for (int i = 0; i < 100; i++) {
int x = rng.uniform(0, img.cols);
int y = rng.uniform(0, img.rows);
int radius = rng.uniform(5, std::min(img.cols, img.rows) / 5);
int b = rng.uniform(0, maxval);
int g = rng.uniform(0, maxval);
int r = rng.uniform(0, maxval);
circle(img, Point(x, y), radius, Scalar(b, g, r), -1, LINE_AA);
}
return img;
}
static std::vector<uchar> getSampleExifData() {
return {
'M', 'M', 0, '*', 0, 0, 0, 8, 0, 10, 1, 0, 0, 4, 0, 0, 0, 1, 0, 0, 5,
0, 1, 1, 0, 4, 0, 0, 0, 1, 0, 0, 2, 208, 1, 2, 0, 3, 0, 0, 0, 1,
0, 10, 0, 0, 1, 18, 0, 3, 0, 0, 0, 1, 0, 1, 0, 0, 1, 14, 0, 2, 0, 0,
0, '"', 0, 0, 0, 176, 1, '1', 0, 2, 0, 0, 0, 7, 0, 0, 0, 210, 1, 26,
0, 5, 0, 0, 0, 1, 0, 0, 0, 218, 1, 27, 0, 5, 0, 0, 0, 1, 0, 0, 0,
226, 1, '(', 0, 3, 0, 0, 0, 1, 0, 2, 0, 0, 135, 'i', 0, 4, 0, 0, 0,
1, 0, 0, 0, 134, 0, 0, 0, 0, 0, 3, 144, 0, 0, 7, 0, 0, 0, 4, '0', '2',
'2', '1', 160, 2, 0, 4, 0, 0, 0, 1, 0, 0, 5, 0, 160, 3, 0, 4, 0, 0,
0, 1, 0, 0, 2, 208, 0, 0, 0, 0, 'S', 'a', 'm', 'p', 'l', 'e', ' ', '1', '0',
'-', 'b', 'i', 't', ' ', 'i', 'm', 'a', 'g', 'e', ' ', 'w', 'i', 't', 'h', ' ',
'm', 'e', 't', 'a', 'd', 'a', 't', 'a', 0, 'O', 'p', 'e', 'n', 'C', 'V', 0, 0,
0, 0, 0, 'H', 0, 0, 0, 1, 0, 0, 0, 'H', 0, 0, 0, 1
};
}
static std::vector<uchar> getSampleXmpData() {
return {
'<','x',':','x','m','p','m','e','t','a',' ','x','m','l','n','s',':','x','=',
'"','a','d','o','b','e',':','x','m','p','"','>',
'<','x','m','p',':','C','r','e','a','t','o','r','T','o','o','l','>',
'O','p','e','n','C','V',
'<','/','x','m','p',':','C','r','e','a','t','o','r','T','o','o','l','>',
'<','/','x',':','x','m','p','m','e','t','a','>',0
};
}
// Returns a Minimal ICC profile data (Generated with help from ChatGPT)
static std::vector<uchar> getSampleIccpData() {
std::vector<uchar> iccp_data(192, 0);
iccp_data[3] = 192; // Profile size: 192 bytes
iccp_data[12] = 'm';
iccp_data[13] = 'n';
iccp_data[14] = 't';
iccp_data[15] = 'r';
iccp_data[16] = 'R';
iccp_data[17] = 'G';
iccp_data[18] = 'B';
iccp_data[19] = ' ';
iccp_data[20] = 'X';
iccp_data[21] = 'Y';
iccp_data[22] = 'Z';
iccp_data[23] = ' ';
// File signature 'acsp' at offset 36 (0x24)
iccp_data[36] = 'a';
iccp_data[37] = 'c';
iccp_data[38] = 's';
iccp_data[39] = 'p';
// Illuminant D50 at offset 68 (0x44), example values:
iccp_data[68] = 0x00;
iccp_data[69] = 0x00;
iccp_data[70] = 0xF6;
iccp_data[71] = 0xD6; // 0.9642
iccp_data[72] = 0x00;
iccp_data[73] = 0x01;
iccp_data[74] = 0x00;
iccp_data[75] = 0x00; // 1.0
iccp_data[76] = 0x00;
iccp_data[77] = 0x00;
iccp_data[78] = 0xD3;
iccp_data[79] = 0x2D; // 0.8249
// Tag count at offset 128 (0x80) = 1
iccp_data[131] = 1;
// Tag record at offset 132 (0x84): signature 'desc', offset 128, size 64
iccp_data[132] = 'd';
iccp_data[133] = 'e';
iccp_data[134] = 's';
iccp_data[135] = 'c';
iccp_data[139] = 128; // offset
iccp_data[143] = 64; // size
// Tag data 'desc' at offset 128 (start of tag data)
// Set type 'desc' etc. here, for simplicity fill zeros
iccp_data[144] = 'd';
iccp_data[145] = 'e';
iccp_data[146] = 's';
iccp_data[147] = 'c';
// ASCII string length at offset 156
iccp_data[156] = 20; // length
// ASCII string "Minimal ICC Profile" starting at offset 160
iccp_data[160] = 'M';
iccp_data[161] = 'i';
iccp_data[162] = 'n';
iccp_data[163] = 'i';
iccp_data[164] = 'm';
iccp_data[165] = 'a';
iccp_data[166] = 'l';
iccp_data[167] = ' ';
iccp_data[168] = 'I';
iccp_data[169] = 'C';
iccp_data[170] = 'C';
iccp_data[171] = ' ';
iccp_data[172] = 'P';
iccp_data[173] = 'r';
iccp_data[174] = 'o';
iccp_data[175] = 'f';
iccp_data[176] = 'i';
iccp_data[177] = 'l';
iccp_data[178] = 'e';
return iccp_data;
}
/**
* Test to check whether the EXIF orientation tag was processed successfully or not.
* The test uses a set of 8 images named testExifOrientation_{1 to 8}.(extension).
* Each test image is a 10x10 square, divided into four smaller sub-squares:
@ -145,47 +280,24 @@ const std::vector<std::string> exif_files
"readwrite/testExifOrientation_7.avif",
"readwrite/testExifOrientation_8.avif",
#endif
#ifdef HAVE_WEBP
"readwrite/testExifOrientation_1.webp",
"readwrite/testExifOrientation_2.webp",
"readwrite/testExifOrientation_3.webp",
"readwrite/testExifOrientation_4.webp",
"readwrite/testExifOrientation_5.webp",
"readwrite/testExifOrientation_6.webp",
"readwrite/testExifOrientation_7.webp",
"readwrite/testExifOrientation_8.webp",
#endif
};
INSTANTIATE_TEST_CASE_P(Imgcodecs, Exif,
testing::ValuesIn(exif_files));
static Mat makeCirclesImage(Size size, int type, int nbits)
{
Mat img(size, type);
img.setTo(Scalar::all(0));
RNG& rng = theRNG();
int maxval = (int)(1 << nbits);
for (int i = 0; i < 100; i++) {
int x = rng.uniform(0, img.cols);
int y = rng.uniform(0, img.rows);
int radius = rng.uniform(5, std::min(img.cols, img.rows)/5);
int b = rng.uniform(0, maxval);
int g = rng.uniform(0, maxval);
int r = rng.uniform(0, maxval);
circle(img, Point(x, y), radius, Scalar(b, g, r), -1, LINE_AA);
}
return img;
}
#ifdef HAVE_AVIF
TEST(Imgcodecs_Avif, ReadWriteWithExif)
{
static const uchar exif_data[] = {
'M', 'M', 0, '*', 0, 0, 0, 8, 0, 10, 1, 0, 0, 4, 0, 0, 0, 1, 0, 0, 5,
0, 1, 1, 0, 4, 0, 0, 0, 1, 0, 0, 2, 208, 1, 2, 0, 3, 0, 0, 0, 1,
0, 10, 0, 0, 1, 18, 0, 3, 0, 0, 0, 1, 0, 1, 0, 0, 1, 14, 0, 2, 0, 0,
0, '"', 0, 0, 0, 176, 1, '1', 0, 2, 0, 0, 0, 7, 0, 0, 0, 210, 1, 26,
0, 5, 0, 0, 0, 1, 0, 0, 0, 218, 1, 27, 0, 5, 0, 0, 0, 1, 0, 0, 0,
226, 1, '(', 0, 3, 0, 0, 0, 1, 0, 2, 0, 0, 135, 'i', 0, 4, 0, 0, 0,
1, 0, 0, 0, 134, 0, 0, 0, 0, 0, 3, 144, 0, 0, 7, 0, 0, 0, 4, '0', '2',
'2', '1', 160, 2, 0, 4, 0, 0, 0, 1, 0, 0, 5, 0, 160, 3, 0, 4, 0, 0,
0, 1, 0, 0, 2, 208, 0, 0, 0, 0, 'S', 'a', 'm', 'p', 'l', 'e', ' ', '1', '0',
'-', 'b', 'i', 't', ' ', 'i', 'm', 'a', 'g', 'e', ' ', 'w', 'i', 't', 'h', ' ',
'm', 'e', 't', 'a', 'd', 'a', 't', 'a', 0, 'O', 'p', 'e', 'n', 'C', 'V', 0, 0,
0, 0, 0, 'H', 0, 0, 0, 1, 0, 0, 0, 'H', 0, 0, 0, 1
};
int avif_nbits = 10;
int avif_speed = 10;
int avif_quality = 85;
@ -195,8 +307,8 @@ TEST(Imgcodecs_Avif, ReadWriteWithExif)
Mat img = makeCirclesImage(Size(1280, 720), imgtype, avif_nbits);
std::vector<int> metadata_types = {IMAGE_METADATA_EXIF};
std::vector<std::vector<uchar> > metadata(1);
metadata[0].assign(exif_data, exif_data + sizeof(exif_data));
std::vector<std::vector<uchar>> metadata = {
getSampleExifData() };
std::vector<int> write_params = {
IMWRITE_AVIF_DEPTH, avif_nbits,
@ -228,31 +340,63 @@ TEST(Imgcodecs_Avif, ReadWriteWithExif)
}
#endif // HAVE_AVIF
TEST(Imgcodecs_Jpeg, ReadWriteWithExif)
#ifdef HAVE_WEBP
TEST(Imgcodecs_WebP, Read_Write_With_Exif_Xmp_Iccp)
{
static const uchar exif_data[] = {
'M', 'M', 0, '*', 0, 0, 0, 8, 0, 10, 1, 0, 0, 4, 0, 0, 0, 1, 0, 0, 5,
0, 1, 1, 0, 4, 0, 0, 0, 1, 0, 0, 2, 208, 1, 2, 0, 3, 0, 0, 0, 1,
0, 8, 0, 0, 1, 18, 0, 3, 0, 0, 0, 1, 0, 1, 0, 0, 1, 14, 0, 2, 0, 0,
0, '!', 0, 0, 0, 176, 1, '1', 0, 2, 0, 0, 0, 7, 0, 0, 0, 210, 1, 26,
0, 5, 0, 0, 0, 1, 0, 0, 0, 218, 1, 27, 0, 5, 0, 0, 0, 1, 0, 0, 0,
226, 1, '(', 0, 3, 0, 0, 0, 1, 0, 2, 0, 0, 135, 'i', 0, 4, 0, 0, 0,
1, 0, 0, 0, 134, 0, 0, 0, 0, 0, 3, 144, 0, 0, 7, 0, 0, 0, 4, '0', '2',
'2', '1', 160, 2, 0, 4, 0, 0, 0, 1, 0, 0, 5, 0, 160, 3, 0, 4, 0, 0,
0, 1, 0, 0, 2, 208, 0, 0, 0, 0, 'S', 'a', 'm', 'p', 'l', 'e', ' ', '8', '-',
'b', 'i', 't', ' ', 'i', 'm', 'a', 'g', 'e', ' ', 'w', 'i', 't', 'h', ' ', 'm',
'e', 't', 'a', 'd', 'a', 't', 'a', 0, 0, 'O', 'p', 'e', 'n', 'C', 'V', 0, 0,
0, 0, 0, 'H', 0, 0, 0, 1, 0, 0, 0, 'H', 0, 0, 0, 1
int imgtype = CV_MAKETYPE(CV_8U, 3);
const std::string outputname = cv::tempfile(".webp");
cv::Mat img = makeCirclesImage(cv::Size(160, 120), imgtype, 8);
std::vector<int> metadata_types = {IMAGE_METADATA_EXIF, IMAGE_METADATA_XMP, IMAGE_METADATA_ICCP};
std::vector<std::vector<uchar>> metadata = {
getSampleExifData(),
getSampleXmpData(),
getSampleIccpData()
};
int webp_quality = 101; // 101 is lossless compression
std::vector<int> write_params = {IMWRITE_WEBP_QUALITY, webp_quality};
imwriteWithMetadata(outputname, img, metadata_types, metadata, write_params);
std::vector<uchar> compressed;
imencodeWithMetadata(outputname, img, metadata_types, metadata, compressed, write_params);
std::vector<int> read_metadata_types, read_metadata_types2;
std::vector<std::vector<uchar>> read_metadata, read_metadata2;
cv::Mat img2 = imreadWithMetadata(outputname, read_metadata_types, read_metadata, cv::IMREAD_UNCHANGED);
cv::Mat img3 = imdecodeWithMetadata(compressed, read_metadata_types2, read_metadata2, cv::IMREAD_UNCHANGED);
EXPECT_EQ(img2.cols, img.cols);
EXPECT_EQ(img2.rows, img.rows);
EXPECT_EQ(img2.type(), imgtype);
EXPECT_EQ(read_metadata_types, read_metadata_types2);
EXPECT_EQ(read_metadata_types.size(), 3u);
EXPECT_EQ(read_metadata, read_metadata2);
EXPECT_EQ(read_metadata, metadata);
EXPECT_EQ(cv::norm(img2, img3, cv::NORM_INF), 0.0);
double mse = cv::norm(img, img2, cv::NORM_L2SQR) / (img.rows * img.cols);
EXPECT_EQ(mse, 0);
remove(outputname.c_str());
}
#endif // HAVE_WEBP
TEST(Imgcodecs_Jpeg, Read_Write_With_Exif)
{
int jpeg_quality = 95;
int imgtype = CV_MAKETYPE(CV_8U, 3);
const string outputname = cv::tempfile(".jpeg");
Mat img = makeCirclesImage(Size(1280, 720), imgtype, 8);
std::vector<int> metadata_types = {IMAGE_METADATA_EXIF};
std::vector<std::vector<uchar> > metadata(1);
metadata[0].assign(exif_data, exif_data + sizeof(exif_data));
std::vector<std::vector<uchar>> metadata = {
getSampleExifData() };
std::vector<int> write_params = {
IMWRITE_JPEG_QUALITY, jpeg_quality
@ -281,31 +425,16 @@ TEST(Imgcodecs_Jpeg, ReadWriteWithExif)
remove(outputname.c_str());
}
TEST(Imgcodecs_Png, ReadWriteWithExif)
TEST(Imgcodecs_Png, Read_Write_With_Exif)
{
static const uchar exif_data[] = {
'M', 'M', 0, '*', 0, 0, 0, 8, 0, 10, 1, 0, 0, 4, 0, 0, 0, 1, 0, 0, 5,
0, 1, 1, 0, 4, 0, 0, 0, 1, 0, 0, 2, 208, 1, 2, 0, 3, 0, 0, 0, 1,
0, 8, 0, 0, 1, 18, 0, 3, 0, 0, 0, 1, 0, 1, 0, 0, 1, 14, 0, 2, 0, 0,
0, '!', 0, 0, 0, 176, 1, '1', 0, 2, 0, 0, 0, 7, 0, 0, 0, 210, 1, 26,
0, 5, 0, 0, 0, 1, 0, 0, 0, 218, 1, 27, 0, 5, 0, 0, 0, 1, 0, 0, 0,
226, 1, '(', 0, 3, 0, 0, 0, 1, 0, 2, 0, 0, 135, 'i', 0, 4, 0, 0, 0,
1, 0, 0, 0, 134, 0, 0, 0, 0, 0, 3, 144, 0, 0, 7, 0, 0, 0, 4, '0', '2',
'2', '1', 160, 2, 0, 4, 0, 0, 0, 1, 0, 0, 5, 0, 160, 3, 0, 4, 0, 0,
0, 1, 0, 0, 2, 208, 0, 0, 0, 0, 'S', 'a', 'm', 'p', 'l', 'e', ' ', '8', '-',
'b', 'i', 't', ' ', 'i', 'm', 'a', 'g', 'e', ' ', 'w', 'i', 't', 'h', ' ', 'm',
'e', 't', 'a', 'd', 'a', 't', 'a', 0, 0, 'O', 'p', 'e', 'n', 'C', 'V', 0, 0,
0, 0, 0, 'H', 0, 0, 0, 1, 0, 0, 0, 'H', 0, 0, 0, 1
};
int png_compression = 3;
int imgtype = CV_MAKETYPE(CV_8U, 3);
const string outputname = cv::tempfile(".png");
Mat img = makeCirclesImage(Size(1280, 720), imgtype, 8);
Mat img = makeCirclesImage(Size(160, 120), imgtype, 8);
std::vector<int> metadata_types = {IMAGE_METADATA_EXIF};
std::vector<std::vector<uchar> > metadata(1);
metadata[0].assign(exif_data, exif_data + sizeof(exif_data));
std::vector<std::vector<uchar>> metadata = {
getSampleExifData() };
std::vector<int> write_params = {
IMWRITE_PNG_COMPRESSION, png_compression
@ -334,6 +463,65 @@ TEST(Imgcodecs_Png, ReadWriteWithExif)
remove(outputname.c_str());
}
TEST(Imgcodecs_Png, Read_Write_With_Exif_Xmp_Iccp)
{
int png_compression = 3;
int imgtype = CV_MAKETYPE(CV_8U, 3);
const string outputname = cv::tempfile(".png");
Mat img = makeCirclesImage(Size(160, 120), imgtype, 8);
std::vector<int> metadata_types = { IMAGE_METADATA_EXIF, IMAGE_METADATA_XMP, IMAGE_METADATA_ICCP };
std::vector<std::vector<uchar>> metadata = {
getSampleExifData(),
getSampleXmpData(),
getSampleIccpData(),
};
std::vector<int> write_params = {
IMWRITE_PNG_COMPRESSION, png_compression
};
imwriteWithMetadata(outputname, img, metadata_types, metadata, write_params);
std::vector<uchar> compressed;
imencodeWithMetadata(outputname, img, metadata_types, metadata, compressed, write_params);
std::vector<int> read_metadata_types, read_metadata_types2;
std::vector<std::vector<uchar> > read_metadata, read_metadata2;
Mat img2 = imreadWithMetadata(outputname, read_metadata_types, read_metadata, IMREAD_UNCHANGED);
Mat img3 = imdecodeWithMetadata(compressed, read_metadata_types2, read_metadata2, IMREAD_UNCHANGED);
EXPECT_EQ(img2.cols, img.cols);
EXPECT_EQ(img2.rows, img.rows);
EXPECT_EQ(img2.type(), imgtype);
EXPECT_EQ(metadata_types, read_metadata_types);
EXPECT_EQ(read_metadata_types, read_metadata_types2);
EXPECT_EQ(metadata, read_metadata);
remove(outputname.c_str());
}
TEST(Imgcodecs_Png, Read_Exif_From_Text)
{
const string root = cvtest::TS::ptr()->get_data_path();
const string filename = root + "../perf/320x260.png";
const string dst_file = cv::tempfile(".png");
std::vector<uchar> exif_data =
{ 'M' , 'M' , 0, '*' , 0, 0, 0, 8, 0, 4, 1,
26, 0, 5, 0, 0, 0, 1, 0, 0, 0, 62, 1, 27, 0, 5, 0, 0, 0, 1, 0, 0, 0,
70, 1, 40, 0, 3, 0, 0, 0, 1, 0, 2, 0, 0, 1, 49, 0, 2, 0, 0, 0, 18, 0,
0, 0, 78, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 1, 0, 0, 0, 96, 0, 0, 0,
1, 80, 97, 105, 110, 116, 46, 78, 69, 84, 32, 118, 51, 46, 53, 46, 49, 48, 0
};
std::vector<int> read_metadata_types;
std::vector<std::vector<uchar> > read_metadata;
Mat img = imreadWithMetadata(filename, read_metadata_types, read_metadata, IMREAD_GRAYSCALE);
std::vector<int> metadata_types = { IMAGE_METADATA_EXIF };
EXPECT_EQ(read_metadata_types, metadata_types);
EXPECT_EQ(read_metadata[0], exif_data);
}
static size_t locateString(const uchar* exif, size_t exif_size, const std::string& pattern)
{
size_t plen = pattern.size();