diff --git a/apps/interactive-calibration/calibCommon.hpp b/apps/interactive-calibration/calibCommon.hpp index ed6c3613d1..967982300b 100644 --- a/apps/interactive-calibration/calibCommon.hpp +++ b/apps/interactive-calibration/calibCommon.hpp @@ -118,6 +118,9 @@ namespace calib double solverEps; int solverMaxIters; bool fastSolving; + bool rationalModel; + bool thinPrismModel; + bool tiltedModel; double filterAlpha; internalParameters() @@ -125,6 +128,9 @@ namespace calib solverEps = 1e-7; solverMaxIters = 30; fastSolving = false; + rationalModel = false; + thinPrismModel = false; + tiltedModel = false; filterAlpha = 0.1; } }; diff --git a/apps/interactive-calibration/main.cpp b/apps/interactive-calibration/main.cpp index 9ee88c251d..709fa4ed3e 100644 --- a/apps/interactive-calibration/main.cpp +++ b/apps/interactive-calibration/main.cpp @@ -135,6 +135,10 @@ int main(int argc, char** argv) int calibrationFlags = 0; if(intParams.fastSolving) calibrationFlags |= cv::CALIB_USE_QR; + if(intParams.rationalModel) calibrationFlags |= cv::CALIB_RATIONAL_MODEL; + if(intParams.thinPrismModel) calibrationFlags |= cv::CALIB_THIN_PRISM_MODEL; + if(intParams.tiltedModel) calibrationFlags |= cv::CALIB_TILTED_MODEL; + cv::Ptr controller(new calibController(globalData, calibrationFlags, parser.get("ft"), capParams.minFramesNum)); cv::Ptr dataController(new calibDataController(globalData, capParams.maxFramesNum, diff --git a/apps/interactive-calibration/parametersController.cpp b/apps/interactive-calibration/parametersController.cpp index 90d24b04c9..48639a2bdb 100644 --- a/apps/interactive-calibration/parametersController.cpp +++ b/apps/interactive-calibration/parametersController.cpp @@ -49,6 +49,9 @@ bool calib::parametersController::loadFromFile(const std::string &inputFileName) readFromNode(reader["solver_eps"], mInternalParameters.solverEps); readFromNode(reader["solver_max_iters"], mInternalParameters.solverMaxIters); readFromNode(reader["fast_solver"], mInternalParameters.fastSolving); + readFromNode(reader["rational_model"], mInternalParameters.rationalModel); + readFromNode(reader["thin_prism_model"], mInternalParameters.thinPrismModel); + readFromNode(reader["tiltedModel"], mInternalParameters.tiltedModel); readFromNode(reader["frame_filter_conv_param"], mInternalParameters.filterAlpha); bool retValue = diff --git a/cmake/OpenCVDetectCUDAUtils.cmake b/cmake/OpenCVDetectCUDAUtils.cmake index ba08804f89..9b4b81e537 100644 --- a/cmake/OpenCVDetectCUDAUtils.cmake +++ b/cmake/OpenCVDetectCUDAUtils.cmake @@ -386,8 +386,13 @@ macro(ocv_nvcc_flags) set(CUDA_NVCC_FLAGS ${CUDA_NVCC_FLAGS} -Xcompiler=-fno-finite-math-only) endif() - if(WIN32 AND NOT (CUDA_VERSION VERSION_LESS "11.2")) - set(CUDA_NVCC_FLAGS ${CUDA_NVCC_FLAGS} -Xcudafe --display_error_number --diag-suppress 1394,1388) + if(WIN32) + if (NOT (CUDA_VERSION VERSION_LESS "11.2")) + set(CUDA_NVCC_FLAGS ${CUDA_NVCC_FLAGS} -Xcudafe --display_error_number --diag-suppress 1394,1388) + endif() + if(CUDA_VERSION GREATER "12.8") + set(CUDA_NVCC_FLAGS ${CUDA_NVCC_FLAGS} -Xcompiler=/Zc:preprocessor) + endif() endif() if(CMAKE_CROSSCOMPILING AND (ARM OR AARCH64)) diff --git a/modules/core/src/persistence_json.cpp b/modules/core/src/persistence_json.cpp index c5326c674a..9d05650814 100644 --- a/modules/core/src/persistence_json.cpp +++ b/modules/core/src/persistence_json.cpp @@ -623,9 +623,7 @@ public: } if( len == 4 && memcmp( beg, "null", 4 ) == 0 ) - { - CV_PARSE_ERROR_CPP( "Value 'null' is not supported by this parser" ); - } + ; else if( (len == 4 && memcmp( beg, "true", 4 ) == 0) || (len == 5 && memcmp( beg, "false", 5 ) == 0) ) { diff --git a/modules/core/test/test_io.cpp b/modules/core/test/test_io.cpp index fc1e5da983..ab3ec97a0f 100644 --- a/modules/core/test/test_io.cpp +++ b/modules/core/test/test_io.cpp @@ -1578,6 +1578,30 @@ TEST(Core_InputOutput, FileStorage_format_yml_gz) EXPECT_EQ(FileStorage::FORMAT_YAML, fs.getFormat()); } +TEST(Core_InputOutput, FileStorage_json_null_object) +{ + std::string test = + "{ " + "\"padding\": null," + "\"truncation\": null," + "\"version\": \"1.0\"" + "}"; + FileStorage fs(test, FileStorage::READ | FileStorage::MEMORY); + + ASSERT_TRUE(fs["padding"].isNone()); + ASSERT_TRUE(fs["truncation"].isNone()); + ASSERT_TRUE(fs["version"].isString()); + + ASSERT_EQ(fs["padding"].name(), "padding"); + ASSERT_EQ(fs["truncation"].name(), "truncation"); + ASSERT_EQ(fs["version"].name(), "version"); + + ASSERT_EQ(fs["padding"].string(), ""); + ASSERT_EQ(fs["truncation"].string(), ""); + ASSERT_EQ(fs["version"].string(), "1.0"); + fs.release(); +} + TEST(Core_InputOutput, FileStorage_json_named_nodes) { std::string test = diff --git a/modules/imgcodecs/include/opencv2/imgcodecs.hpp b/modules/imgcodecs/include/opencv2/imgcodecs.hpp index ddb8608c8c..7aa13fe0e1 100644 --- a/modules/imgcodecs/include/opencv2/imgcodecs.hpp +++ b/modules/imgcodecs/include/opencv2/imgcodecs.hpp @@ -94,9 +94,10 @@ enum ImwriteFlags { IMWRITE_JPEG_CHROMA_QUALITY = 6, //!< Separate chroma quality level, 0 - 100, default is -1 - don't use. If JPEG_LIB_VERSION < 70, Not supported. IMWRITE_JPEG_SAMPLING_FACTOR = 7, //!< For JPEG, set sampling factor. See cv::ImwriteJPEGSamplingFactorParams. IMWRITE_PNG_COMPRESSION = 16, //!< For PNG, it can be the compression level from 0 to 9. A higher value means a smaller size and longer compression time. If specified, strategy is changed to IMWRITE_PNG_STRATEGY_DEFAULT (Z_DEFAULT_STRATEGY). Default value is 1 (best speed setting). - IMWRITE_PNG_STRATEGY = 17, //!< One of cv::ImwritePNGFlags, default is IMWRITE_PNG_STRATEGY_RLE. - IMWRITE_PNG_BILEVEL = 18, //!< Binary level PNG, 0 or 1, default is 0. - IMWRITE_PNG_FILTER = 19, //!< One of cv::ImwritePNGFilterFlags, default is IMWRITE_PNG_FILTER_SUB. + IMWRITE_PNG_STRATEGY = 17, //!< For PNG, One of cv::ImwritePNGFlags, default is IMWRITE_PNG_STRATEGY_RLE. + IMWRITE_PNG_BILEVEL = 18, //!< For PNG, Binary level PNG, 0 or 1, default is 0. + IMWRITE_PNG_FILTER = 19, //!< For PNG, One of cv::ImwritePNGFilterFlags, default is IMWRITE_PNG_FILTER_SUB. + IMWRITE_PNG_ZLIBBUFFER_SIZE = 20, //!< For PNG, sets the size of the internal zlib compression buffer in bytes. IMWRITE_PXM_BINARY = 32, //!< For PPM, PGM, or PBM, it can be a binary format flag, 0 or 1. Default value is 1. IMWRITE_EXR_TYPE = (3 << 4) + 0 /* 48 */, //!< override EXR storage type (FLOAT (FP32) is default) IMWRITE_EXR_COMPRESSION = (3 << 4) + 1 /* 49 */, //!< override EXR compression type (ZIP_COMPRESSION = 3 is default) @@ -253,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 @@ -354,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 ); @@ -363,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& metadataTypes, OutputArrayOfArrays metadata, int flags = IMREAD_ANYCOLOR); @@ -560,29 +569,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& 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 +609,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. */ diff --git a/modules/imgcodecs/src/exif.cpp b/modules/imgcodecs/src/exif.cpp index 3f1bbdbe18..5484031c50 100644 --- a/modules/imgcodecs/src/exif.cpp +++ b/modules/imgcodecs/src/exif.cpp @@ -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(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(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& 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\n(%8lu)\n\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(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 * diff --git a/modules/imgcodecs/src/exif.hpp b/modules/imgcodecs/src/exif.hpp index 3c5fbc7fe8..137d383ee2 100644 --- a/modules/imgcodecs/src/exif.hpp +++ b/modules/imgcodecs/src/exif.hpp @@ -154,6 +154,7 @@ public: ExifReader(); ~ExifReader(); + bool processRawProfile(const char* profile, size_t profile_len); /** * @brief Parse the file with exif info diff --git a/modules/imgcodecs/src/grfmt_base.cpp b/modules/imgcodecs/src/grfmt_base.cpp index 1241edb077..9c9dead740 100644 --- a/modules/imgcodecs/src/grfmt_base.cpp +++ b/modules/imgcodecs/src/grfmt_base.cpp @@ -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& 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& 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(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; diff --git a/modules/imgcodecs/src/grfmt_base.hpp b/modules/imgcodecs/src/grfmt_base.hpp index 2eeb2fc130..889fdd0a1d 100644 --- a/modules/imgcodecs/src/grfmt_base.hpp +++ b/modules/imgcodecs/src/grfmt_base.hpp @@ -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 > m_metadata; }; diff --git a/modules/imgcodecs/src/grfmt_png.cpp b/modules/imgcodecs/src/grfmt_png.cpp index c78c0efc55..5adf71323e 100644 --- a/modules/imgcodecs/src/grfmt_png.cpp +++ b/modules/imgcodecs/src/grfmt_png.cpp @@ -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(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(value), + reinterpret_cast(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(icc_profile), + reinterpret_cast(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& 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& params ) PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT ); + if (!m_metadata.empty()) { + std::vector& exif = m_metadata[IMAGE_METADATA_EXIF]; + if (!exif.empty()) { + png_set_eXIf_1(png_ptr, info_ptr, static_cast(exif.size()), exif.data()); + } + + std::vector& 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("XML:com.adobe.xmp"); + text_chunk.text = reinterpret_cast(xmp.data()); + text_chunk.text_length = static_cast(xmp.size()); + + png_set_text(png_ptr, info_ptr, &text_chunk, 1); + } + + std::vector 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(iccp.data()), + static_cast(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& params ) for( y = 0; y < height; y++ ) buffer[y] = img.data + y*img.step; - if (!m_metadata.empty()) { - std::vector& 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 ); diff --git a/modules/imgcodecs/src/grfmt_png.hpp b/modules/imgcodecs/src/grfmt_png.hpp index 3a94dd57a3..6094139baf 100644 --- a/modules/imgcodecs/src/grfmt_png.hpp +++ b/modules/imgcodecs/src/grfmt_png.hpp @@ -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; diff --git a/modules/imgcodecs/src/grfmt_spng.cpp b/modules/imgcodecs/src/grfmt_spng.cpp index acf2f0d55d..8057af1222 100644 --- a/modules/imgcodecs/src/grfmt_spng.cpp +++ b/modules/imgcodecs/src/grfmt_spng.cpp @@ -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 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 ¶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& exif = m_metadata[IMAGE_METADATA_EXIF]; + if (!exif.empty()) { + spng_exif s_exif; + s_exif.data = reinterpret_cast(exif.data()); + s_exif.length = exif.size(); + spng_set_exif(ctx, &s_exif); + } + + std::vector& 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(xmp.data()); + text_chunk.length = xmp.size(); + spng_set_text(ctx, &text_chunk, 1); + } + + std::vector& 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(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); diff --git a/modules/imgcodecs/src/grfmt_webp.cpp b/modules/imgcodecs/src/grfmt_webp.cpp index 3e63dd7acb..c82750020a 100644 --- a/modules/imgcodecs/src/grfmt_webp.cpp +++ b/modules/imgcodecs/src/grfmt_webp.cpp @@ -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 metadata; + WebPDemuxer* demux = WebPDemux(&webp_data); + + if (demux) + { + WebPChunkIterator chunk_iter; + + if (WebPDemuxGetChunk(demux, "EXIF", 1, &chunk_iter)) + { + metadata = std::vector(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(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(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& 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 out_cleaner(out, WebPFree); #else @@ -375,7 +460,7 @@ bool WebPEncoder::write(const Mat& img, const std::vector& 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& 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)); diff --git a/modules/imgcodecs/src/loadsave.cpp b/modules/imgcodecs/src/loadsave.cpp index 47987bae21..2a2a287ae2 100644 --- a/modules/imgcodecs/src/loadsave.cpp +++ b/modules/imgcodecs/src/loadsave.cpp @@ -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* 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 { diff --git a/modules/imgcodecs/test/test_exif.cpp b/modules/imgcodecs/test/test_exif.cpp index 792c38514f..8576c4f1e4 100644 --- a/modules/imgcodecs/test/test_exif.cpp +++ b/modules/imgcodecs/test/test_exif.cpp @@ -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 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 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 getSampleIccpData() { + std::vector 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 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 metadata_types = {IMAGE_METADATA_EXIF}; - std::vector > metadata(1); - metadata[0].assign(exif_data, exif_data + sizeof(exif_data)); + std::vector> metadata = { + getSampleExifData() }; std::vector 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 metadata_types = {IMAGE_METADATA_EXIF, IMAGE_METADATA_XMP, IMAGE_METADATA_ICCP}; + std::vector> metadata = { + getSampleExifData(), + getSampleXmpData(), + getSampleIccpData() }; + int webp_quality = 101; // 101 is lossless compression + std::vector write_params = {IMWRITE_WEBP_QUALITY, webp_quality}; + + imwriteWithMetadata(outputname, img, metadata_types, metadata, write_params); + + std::vector compressed; + imencodeWithMetadata(outputname, img, metadata_types, metadata, compressed, write_params); + + std::vector read_metadata_types, read_metadata_types2; + std::vector> 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 metadata_types = {IMAGE_METADATA_EXIF}; - std::vector > metadata(1); - metadata[0].assign(exif_data, exif_data + sizeof(exif_data)); + std::vector> metadata = { + getSampleExifData() }; std::vector 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 metadata_types = {IMAGE_METADATA_EXIF}; - std::vector > metadata(1); - metadata[0].assign(exif_data, exif_data + sizeof(exif_data)); + std::vector> metadata = { + getSampleExifData() }; std::vector 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 metadata_types = { IMAGE_METADATA_EXIF, IMAGE_METADATA_XMP, IMAGE_METADATA_ICCP }; + std::vector> metadata = { + getSampleExifData(), + getSampleXmpData(), + getSampleIccpData(), + }; + + std::vector write_params = { + IMWRITE_PNG_COMPRESSION, png_compression + }; + + imwriteWithMetadata(outputname, img, metadata_types, metadata, write_params); + std::vector compressed; + imencodeWithMetadata(outputname, img, metadata_types, metadata, compressed, write_params); + + std::vector read_metadata_types, read_metadata_types2; + std::vector > 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 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 read_metadata_types; + std::vector > read_metadata; + Mat img = imreadWithMetadata(filename, read_metadata_types, read_metadata, IMREAD_GRAYSCALE); + + std::vector 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(); @@ -377,13 +565,13 @@ TEST_P(ReadExif_Sanity, Check) static const std::vector exif_sanity_params { #ifdef HAVE_JPEG - {"readwrite/testExifOrientation_3.jpg", 916, "Photoshop", 120}, + ReadExif_Sanity_Params("readwrite/testExifOrientation_3.jpg", 916, "Photoshop", 120), #endif #ifdef OPENCV_IMGCODECS_PNG_WITH_EXIF - {"readwrite/testExifOrientation_5.png", 112, "ExifTool", 102}, + ReadExif_Sanity_Params("readwrite/testExifOrientation_5.png", 112, "ExifTool", 102), #endif #ifdef HAVE_AVIF - {"readwrite/testExifOrientation_7.avif", 913, "Photoshop", 120}, + ReadExif_Sanity_Params("readwrite/testExifOrientation_7.avif", 913, "Photoshop", 120), #endif }; diff --git a/modules/imgcodecs/test/test_png.cpp b/modules/imgcodecs/test/test_png.cpp index 583039202a..a1a743ee0f 100644 --- a/modules/imgcodecs/test/test_png.cpp +++ b/modules/imgcodecs/test/test_png.cpp @@ -12,14 +12,15 @@ TEST(Imgcodecs_Png, write_big) { const string root = cvtest::TS::ptr()->get_data_path(); const string filename = root + "readwrite/read.png"; - const string dst_file = cv::tempfile(".png"); Mat img; ASSERT_NO_THROW(img = imread(filename)); ASSERT_FALSE(img.empty()); EXPECT_EQ(13043, img.cols); EXPECT_EQ(13917, img.rows); - ASSERT_NO_THROW(imwrite(dst_file, img)); - EXPECT_EQ(0, remove(dst_file.c_str())); + + vector buff; + ASSERT_NO_THROW(imencode(".png", img, buff, { IMWRITE_PNG_ZLIBBUFFER_SIZE, INT_MAX })); + EXPECT_EQ((size_t)816219, buff.size()); } TEST(Imgcodecs_Png, encode) diff --git a/modules/imgproc/src/contours_blockstorage.hpp b/modules/imgproc/src/contours_blockstorage.hpp index 7b7c55a72d..a30cba96d4 100644 --- a/modules/imgproc/src/contours_blockstorage.hpp +++ b/modules/imgproc/src/contours_blockstorage.hpp @@ -26,14 +26,14 @@ class BlockStorage { dynamicBlocks.push_back(new block_type); } BlockStorage(const BlockStorage&) = delete; - BlockStorage(BlockStorage&&) noexcept = default; + BlockStorage(BlockStorage&&) = default; ~BlockStorage() { for(const auto & block : dynamicBlocks) { delete block; } } BlockStorage& operator=(const BlockStorage&) = delete; - BlockStorage& operator=(BlockStorage&&) noexcept = default; + BlockStorage& operator=(BlockStorage&&) = default; void clear(void) { const size_t minDynamicBlocks = !staticBlocksCount ? 1 : 0; diff --git a/modules/imgproc/src/contours_common.hpp b/modules/imgproc/src/contours_common.hpp index e02945fd01..08f3dc1b3d 100644 --- a/modules/imgproc/src/contours_common.hpp +++ b/modules/imgproc/src/contours_common.hpp @@ -190,7 +190,7 @@ public: ContourDataStorage(ContourDataStorage&&) noexcept = default; ~ContourDataStorage() = default; ContourDataStorage& operator=(const ContourDataStorage&) = delete; - ContourDataStorage& operator=(ContourDataStorage&&) noexcept = default; + ContourDataStorage& operator=(ContourDataStorage&&) = default; public: typename storage_t::RangeIterator getRangeIterator(void) const {return storage->getRangeIterator(first, last);} public: @@ -243,7 +243,7 @@ public: Contour(const Contour&) = delete; Contour(Contour&& other) noexcept = default; Contour& operator=(const Contour&) = delete; - Contour& operator=(Contour&& other) noexcept = default; + Contour& operator=(Contour&& other) = default; ~Contour() = default; void updateBoundingRect() {} bool isEmpty() const diff --git a/modules/videoio/include/opencv2/videoio.hpp b/modules/videoio/include/opencv2/videoio.hpp index bc3b1611c6..44a2d595c6 100644 --- a/modules/videoio/include/opencv2/videoio.hpp +++ b/modules/videoio/include/opencv2/videoio.hpp @@ -261,6 +261,7 @@ enum VideoAccelerationType VIDEO_ACCELERATION_D3D11 = 2, //!< DirectX 11 VIDEO_ACCELERATION_VAAPI = 3, //!< VAAPI VIDEO_ACCELERATION_MFX = 4, //!< libmfx (Intel MediaSDK/oneVPL) + VIDEO_ACCELERATION_DRM = 5, //!< Raspberry Pi V4 }; //! @} Hardware acceleration support diff --git a/modules/videoio/src/cap_ffmpeg_hw.hpp b/modules/videoio/src/cap_ffmpeg_hw.hpp index 0998ee9f48..65045a2bc7 100644 --- a/modules/videoio/src/cap_ffmpeg_hw.hpp +++ b/modules/videoio/src/cap_ffmpeg_hw.hpp @@ -57,6 +57,9 @@ extern "C" { #ifdef HAVE_MFX // dependency only on MFX header files, no linkage dependency #include #endif +#ifdef HAVE_DRM +#include +#endif } #define HW_DEFAULT_POOL_SIZE 32 @@ -81,6 +84,7 @@ const char* getVideoAccelerationName(VideoAccelerationType va_type) case VIDEO_ACCELERATION_D3D11: return "d3d11"; case VIDEO_ACCELERATION_VAAPI: return "vaapi"; case VIDEO_ACCELERATION_MFX: return "mfx"; + case VIDEO_ACCELERATION_DRM : return "drm"; } return "unknown"; } @@ -119,6 +123,7 @@ std::string getDecoderConfiguration(VideoAccelerationType va_type, AVDictionary case VIDEO_ACCELERATION_D3D11: return ""; case VIDEO_ACCELERATION_VAAPI: return "vaapi.iHD"; case VIDEO_ACCELERATION_MFX: return "qsv.iHD"; + case VIDEO_ACCELERATION_DRM: return "drm"; } return ""; #endif @@ -158,6 +163,8 @@ std::string getEncoderConfiguration(VideoAccelerationType va_type, AVDictionary case VIDEO_ACCELERATION_D3D11: return ""; case VIDEO_ACCELERATION_VAAPI: return "vaapi.iHD"; case VIDEO_ACCELERATION_MFX: return "qsv.iHD"; + case VIDEO_ACCELERATION_DRM: return ""; + // Raspberry Pi 5 has no encoders, so we don't support it } return "unknown"; #endif @@ -236,6 +243,18 @@ bool hw_check_device(AVBufferRef* ctx, AVHWDeviceType hw_type, const std::string dxgiDevice->Release(); } } +#endif +#if defined(HAVE_DRM) + if (hw_device_ctx->type == AV_HWDEVICE_TYPE_DRM) { + AVDRMDeviceContext *drm_ctx = (AVDRMDeviceContext *)hw_device_ctx->hwctx; + if (drm_ctx->fd >= 0) { + drmVersionPtr drm_version = drmGetVersion(drm_ctx->fd); + if (drm_version) { + device_name = drm_version->name; + drmFreeVersion(drm_version); + } + } + } #endif if (hw_device_ctx->type == AV_HWDEVICE_TYPE_VAAPI) { #if defined(HAVE_VA) && (VA_MAJOR_VERSION >= 1) @@ -908,6 +927,7 @@ VideoAccelerationType hw_type_to_va_type(AVHWDeviceType hw_type) { { AV_HWDEVICE_TYPE_VAAPI, VIDEO_ACCELERATION_VAAPI }, { AV_HWDEVICE_TYPE_QSV, VIDEO_ACCELERATION_MFX }, { AV_HWDEVICE_TYPE_CUDA, (VideoAccelerationType)(1 << 11) }, + { AV_HWDEVICE_TYPE_DRM, VIDEO_ACCELERATION_DRM } }; for (const HWTypeFFMPEG& hw : known_hw_types) { if (hw_type == hw.hw_type) diff --git a/modules/videoio/src/cap_interface.hpp b/modules/videoio/src/cap_interface.hpp index 77e3eedb9e..b456cd8c0f 100644 --- a/modules/videoio/src/cap_interface.hpp +++ b/modules/videoio/src/cap_interface.hpp @@ -393,6 +393,7 @@ std::ostream& operator<<(std::ostream& out, const VideoAccelerationType& va_type case VIDEO_ACCELERATION_D3D11: out << "D3D11"; return out; case VIDEO_ACCELERATION_VAAPI: out << "VAAPI"; return out; case VIDEO_ACCELERATION_MFX: out << "MFX"; return out; + case VIDEO_ACCELERATION_DRM: out << "DRM"; return out; } out << cv::format("UNKNOWN(0x%ux)", static_cast(va_type)); return out; diff --git a/modules/videoio/test/test_precomp.hpp b/modules/videoio/test/test_precomp.hpp index d2eeddae60..3ce203ed1d 100644 --- a/modules/videoio/test/test_precomp.hpp +++ b/modules/videoio/test/test_precomp.hpp @@ -35,6 +35,7 @@ std::ostream& operator<<(std::ostream& out, const VideoAccelerationType& va_type {VIDEO_ACCELERATION_D3D11, "D3D11"}, {VIDEO_ACCELERATION_VAAPI, "VAAPI"}, {VIDEO_ACCELERATION_MFX, "MFX"}, + {VIDEO_ACCELERATION_DRM, "DRM"}, }; for (const auto& va : va_types) { if (va_type == va.va_type) { diff --git a/modules/videoio/test/test_video_io.cpp b/modules/videoio/test/test_video_io.cpp index 63c23b48ba..6aefd886bc 100644 --- a/modules/videoio/test/test_video_io.cpp +++ b/modules/videoio/test/test_video_io.cpp @@ -869,6 +869,7 @@ static const VideoAccelerationType hw_types[] = { VIDEO_ACCELERATION_D3D11, #else VIDEO_ACCELERATION_VAAPI, + VIDEO_ACCELERATION_DRM, #endif }; diff --git a/samples/cpp/calibration.cpp b/samples/cpp/calibration.cpp index 96b4071243..871a03cc80 100644 --- a/samples/cpp/calibration.cpp +++ b/samples/cpp/calibration.cpp @@ -179,8 +179,19 @@ static bool runCalibration( vector > imagePoints, vector > objectPoints(1); calcChessboardCorners(boardSize, squareSize, objectPoints[0], patternType); - int offset = patternType != CHARUCOBOARD ? boardSize.width - 1: boardSize.width - 2; - objectPoints[0][offset].x = objectPoints[0][0].x + grid_width; + // Board imperfectness correction introduced in PR #12772 + // The correction does not make sense for asymmetric and assymetric circles grids + if (patternType == CHESSBOARD) + { + int offset = boardSize.width - 1; + objectPoints[0][offset].x = objectPoints[0][0].x + grid_width; + } + else if (patternType == CHARUCOBOARD) + { + int offset = boardSize.width - 2; + objectPoints[0][offset].x = objectPoints[0][0].x + grid_width; + } + newObjPoints = objectPoints[0]; objectPoints.resize(imagePoints.size(),objectPoints[0]); diff --git a/samples/cpp/tutorial_code/calib3d/camera_calibration/camera_calibration.cpp b/samples/cpp/tutorial_code/calib3d/camera_calibration/camera_calibration.cpp index 97d44724bc..3967b31fe4 100644 --- a/samples/cpp/tutorial_code/calib3d/camera_calibration/camera_calibration.cpp +++ b/samples/cpp/tutorial_code/calib3d/camera_calibration/camera_calibration.cpp @@ -641,10 +641,15 @@ static bool runCalibration( Settings& s, Size& imageSize, Mat& cameraMatrix, Mat vector > objectPoints(1); calcBoardCornerPositions(s.boardSize, s.squareSize, objectPoints[0], s.calibrationPattern); - if (s.calibrationPattern == Settings::Pattern::CHARUCOBOARD) { + + // Board imperfectness correction introduced in PR #12772 + // The correction does not make sense for asymmetric and assymetric circles grids + if (s.calibrationPattern == Settings::Pattern::CHARUCOBOARD) + { objectPoints[0][s.boardSize.width - 2].x = objectPoints[0][0].x + grid_width; } - else { + else if (s.calibrationPattern != Settings::Pattern::CHESSBOARD) + { objectPoints[0][s.boardSize.width - 1].x = objectPoints[0][0].x + grid_width; } newObjPoints = objectPoints[0]; diff --git a/samples/tapi/video_acceleration.cpp b/samples/tapi/video_acceleration.cpp index 5169236c29..b0d0e5be5d 100644 --- a/samples/tapi/video_acceleration.cpp +++ b/samples/tapi/video_acceleration.cpp @@ -42,6 +42,7 @@ struct { { VIDEO_ACCELERATION_D3D11, "d3d11" }, { VIDEO_ACCELERATION_VAAPI, "vaapi" }, { VIDEO_ACCELERATION_MFX, "mfx" }, + { VIDEO_ACCELERATION_DRM, "drm" }, }; class FPSCounter {