mirror of
https://github.com/zebrajr/opencv.git
synced 2025-12-06 12:19:50 +01:00
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:
parent
15e3cf3cc5
commit
32bd8c9632
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*
|
||||
|
|
|
|||
|
|
@ -154,6 +154,7 @@ public:
|
|||
ExifReader();
|
||||
~ExifReader();
|
||||
|
||||
bool processRawProfile(const char* profile, size_t profile_len);
|
||||
|
||||
/**
|
||||
* @brief Parse the file with exif info
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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 );
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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> ¶ms)
|
|||
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);
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user