From 6efca656b8673c971a49e611e1deccd3e7210175 Mon Sep 17 00:00:00 2001 From: pratham-mcw Date: Wed, 23 Jul 2025 14:32:30 +0530 Subject: [PATCH 1/6] core: add ARM64 NEON support for cvRound in fast_math.hpp --- modules/core/include/opencv2/core/fast_math.hpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/modules/core/include/opencv2/core/fast_math.hpp b/modules/core/include/opencv2/core/fast_math.hpp index a28c3fbedf..47b8864ea0 100644 --- a/modules/core/include/opencv2/core/fast_math.hpp +++ b/modules/core/include/opencv2/core/fast_math.hpp @@ -201,6 +201,10 @@ cvRound( double value ) { #if defined CV_INLINE_ROUND_DBL CV_INLINE_ROUND_DBL(value); +#elif defined _MSC_VER && defined _M_ARM64 + float64x1_t v = vdup_n_f64(value); + int64x1_t r = vcvtn_s64_f64(v); + return static_cast(vget_lane_s64(r, 0)); #elif ((defined _MSC_VER && defined _M_X64) || (defined __GNUC__ && defined __SSE2__)) && !defined(__CUDACC__) __m128d t = _mm_set_sd( value ); return _mm_cvtsd_si32(t); @@ -323,6 +327,10 @@ CV_INLINE int cvRound(float value) { #if defined CV_INLINE_ROUND_FLT CV_INLINE_ROUND_FLT(value); +#elif defined _MSC_VER && defined _M_ARM64 + float32x2_t v = vdup_n_f32(value); + int32x2_t r = vcvtn_s32_f32(v); + return vget_lane_s32(r, 0); #elif ((defined _MSC_VER && defined _M_X64) || (defined __GNUC__ && defined __SSE2__)) && !defined(__CUDACC__) __m128 t = _mm_set_ss( value ); return _mm_cvtss_si32(t); From 48b75bc029e2cd49292d24c230328365dba325e5 Mon Sep 17 00:00:00 2001 From: Kumataro Date: Tue, 29 Jul 2025 14:18:21 +0900 Subject: [PATCH 2/6] Merge pull request #27589 from Kumataro:fix27588 imgcodecs: fix Imgcodecs_Png.write_big test for spng #27589 Close #27588 ### 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 - [x] There is a reference to the original bug report and related work - [x] 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 --- modules/imgcodecs/test/test_png.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/imgcodecs/test/test_png.cpp b/modules/imgcodecs/test/test_png.cpp index a1a743ee0f..d3a0f4253a 100644 --- a/modules/imgcodecs/test/test_png.cpp +++ b/modules/imgcodecs/test/test_png.cpp @@ -20,7 +20,11 @@ TEST(Imgcodecs_Png, write_big) vector buff; ASSERT_NO_THROW(imencode(".png", img, buff, { IMWRITE_PNG_ZLIBBUFFER_SIZE, INT_MAX })); +#ifdef HAVE_PNG EXPECT_EQ((size_t)816219, buff.size()); +#else + EXPECT_EQ((size_t)817407, buff.size()); +#endif } TEST(Imgcodecs_Png, encode) From 07cf36cbb0de7a2774d7fa017c0f4ddf86c274d9 Mon Sep 17 00:00:00 2001 From: Yuantao Feng Date: Tue, 29 Jul 2025 17:04:55 +0800 Subject: [PATCH 3/6] Merge pull request #27587 from fengyuentau:4x/core/filestorage_json_support_backslash core: support parsing back slash \ in parseKey in FileStorage (JSON) #27587 Fixes #27585 ### 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 - [x] There is a reference to the original bug report and related work - [x] 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 --- modules/core/src/persistence_json.cpp | 17 +++++++++++++---- modules/core/test/test_io.cpp | 23 +++++++++++++++++++++++ 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/modules/core/src/persistence_json.cpp b/modules/core/src/persistence_json.cpp index 9d05650814..c63acb8c10 100644 --- a/modules/core/src/persistence_json.cpp +++ b/modules/core/src/persistence_json.cpp @@ -419,10 +419,19 @@ public: CV_PARSE_ERROR_CPP( "Key must start with \'\"\'" ); char * beg = ptr + 1; - + std::string key_name; do { - ++ptr; - CV_PERSISTENCE_CHECK_END_OF_BUFFER_BUG_CPP(); + if (*ptr == '\\') { // skip the next character if current is back slash + ++ptr; + CV_PERSISTENCE_CHECK_END_OF_BUFFER_BUG_CPP(); + key_name += *ptr; + ++ptr; + CV_PERSISTENCE_CHECK_END_OF_BUFFER_BUG_CPP(); + } else { + ++ptr; + CV_PERSISTENCE_CHECK_END_OF_BUFFER_BUG_CPP(); + if (*ptr != '\\' && *ptr != '"') key_name += *ptr; + } } while( cv_isprint(*ptr) && *ptr != '"' ); if( *ptr != '"' ) @@ -430,7 +439,7 @@ public: if( ptr == beg ) CV_PARSE_ERROR_CPP( "Key is empty" ); - value_placeholder = fs->addNode(collection, std::string(beg, (size_t)(ptr - beg)), FileNode::NONE); + value_placeholder = fs->addNode(collection, key_name, FileNode::NONE); ptr++; ptr = skipSpaces( ptr ); diff --git a/modules/core/test/test_io.cpp b/modules/core/test/test_io.cpp index 67b0ab5803..7e2a9d52d4 100644 --- a/modules/core/test/test_io.cpp +++ b/modules/core/test/test_io.cpp @@ -1569,6 +1569,29 @@ TEST(Core_InputOutput, FileStorage_json_null_object) fs.release(); } +TEST(Core_InputOutput, FileStorage_json_key_backslash) +{ + // equivalent to json text {"\"":1,"\\":59,"Ġ\"":366,"\\\\":6852} + std::string test = R"({"\"":1,"\\":59,"Ġ\"":366,"\\\\":6852})"; + FileStorage fs(test, FileStorage::READ | FileStorage::MEMORY); + + ASSERT_TRUE(fs[R"(")"].isNamed()); // = "\"" + ASSERT_TRUE(fs[R"(\)"].isNamed()); // = "\\" + ASSERT_TRUE(fs[R"(Ġ")"].isNamed()); // = "Ġ\"" + ASSERT_TRUE(fs[R"(\\)"].isNamed()); // = "\\\\" + + ASSERT_EQ(fs[R"(")"].name(), R"(")"); + ASSERT_EQ(fs[R"(\)"].name(), R"(\)"); + ASSERT_EQ(fs[R"(Ġ")"].name(), R"(Ġ")"); + ASSERT_EQ(fs[R"(\\)"].name(), R"(\\)"); + + ASSERT_EQ((int)fs[R"(")"], 1); + ASSERT_EQ((int)fs[R"(\)"], 59); + ASSERT_EQ((int)fs[R"(Ġ")"], 366); + ASSERT_EQ((int)fs[R"(\\)"], 6852); + fs.release(); +} + TEST(Core_InputOutput, FileStorage_json_named_nodes) { std::string test = From 615ceefd0c7094e7fb0c33d8266e6fdc702a0f1f Mon Sep 17 00:00:00 2001 From: Maxim Smolskiy Date: Tue, 29 Jul 2025 13:10:01 +0300 Subject: [PATCH 4/6] Merge pull request #27582 from MaximSmolskiy:take_into_account_overflow_for_connected_components Take into account overflow for connected components #27582 ### Pull Request Readiness Checklist Fix #27568 The problem was caused by a label type overflow (`debug_example.npy` contains `92103` labels, that doesn't fit in the `CV_16U` (`unsigned short`) type). If pass `CV_32S` instead of `CV_16U` as `ltype` - everything will be calculated successfully Added overflow detection to throw exception with a clear error message instead of strange segfault/assertion error 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 - [x] There is a reference to the original bug report and related work - [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [x] The feature is well documented and sample code can be built with the project CMake --- modules/imgproc/src/connectedcomponents.cpp | 42 ++++++++++++++++--- .../imgproc/test/test_connectedcomponents.cpp | 40 ++++++++++++++++++ 2 files changed, 76 insertions(+), 6 deletions(-) diff --git a/modules/imgproc/src/connectedcomponents.cpp b/modules/imgproc/src/connectedcomponents.cpp index d402ea91c3..e6285bdecd 100644 --- a/modules/imgproc/src/connectedcomponents.cpp +++ b/modules/imgproc/src/connectedcomponents.cpp @@ -261,13 +261,24 @@ namespace cv{ template inline static - void flattenL(LabelT *P, const int start, const int nElem, LabelT& k){ + void checkLabelTypeOverflowBeforeIncrement(const LabelT numLabels) { + constexpr LabelT maxLabelTypeValue = std::numeric_limits::max(); + CV_CheckLT( + numLabels, + maxLabelTypeValue, + "Total number of labels overflowed label type. Try using CV_32S instead of CV_16U as ltype"); + } + + template + inline static + void flattenLParallel(LabelT *P, const int start, const int nElem, LabelT& k){ for (int i = start; i < start + nElem; ++i){ if (P[i] < i){//node that point to root P[i] = P[P[i]]; } else{ //for root node P[i] = k; + checkLabelTypeOverflowBeforeIncrement(k); k = k + 1; } } @@ -353,6 +364,7 @@ namespace cv{ // Action 2: New label (the block has foreground pixels and is not connected to anything else) #define ACTION_2 img_labels_row[c] = label; \ P_[label] = label; \ + checkLabelTypeOverflowBeforeIncrement(label); \ label = label + 1; //Action 3: Assign label of block P #define ACTION_3 img_labels_row[c] = img_labels_row_prev_prev[c - 2]; @@ -1160,7 +1172,7 @@ namespace cv{ LabelT nLabels = 1; for (int i = 0; i < h; i = chunksSizeAndLabels[i]) { CV_DbgAssert(i + 1 < chunksSizeAndLabelsSize); - flattenL(P.data(), stripeFirstLabel8Connectivity(i, w), chunksSizeAndLabels[i + 1], nLabels); + flattenLParallel(P.data(), stripeFirstLabel8Connectivity(i, w), chunksSizeAndLabels[i + 1], nLabels); } //Array for statistics data @@ -1261,6 +1273,7 @@ namespace cv{ // Action 2: New label (the block has foreground pixels and is not connected to anything else) #define ACTION_2 img_labels_row[c] = lunique; \ P[lunique] = lunique; \ + checkLabelTypeOverflowBeforeIncrement(lunique); \ lunique = lunique + 1; //Action 3: Assign label of block P #define ACTION_3 img_labels_row[c] = img_labels_row_prev_prev[c - 2]; @@ -1789,7 +1802,7 @@ namespace cv{ mergeLabels(imgLabels, P, chunksSizeAndLabels.data()); for (int i = 0; i < h; i = chunksSizeAndLabels[i]) { - flattenL(P, stripeFirstLabel4Connectivity(i, w), chunksSizeAndLabels[i + 1], nLabels); + flattenLParallel(P, stripeFirstLabel4Connectivity(i, w), chunksSizeAndLabels[i + 1], nLabels); } //Array for statistics dataof threads @@ -1854,6 +1867,7 @@ namespace cv{ #define ACTION_1 img_labels_row[c] = 0; #define ACTION_2 img_labels_row[c] = lunique; \ P[lunique] = lunique; \ + checkLabelTypeOverflowBeforeIncrement(lunique); \ lunique = lunique + 1; // new label #define ACTION_3 img_labels_row[c] = img_labels_row_prev[c]; // x <- q #define ACTION_4 img_labels_row[c] = img_labels_row[c - 1]; // x <- s @@ -2052,6 +2066,7 @@ namespace cv{ //new label imgLabels_row[c] = label; P_[label] = label; + checkLabelTypeOverflowBeforeIncrement(label); label = label + 1; } } @@ -2135,6 +2150,7 @@ namespace cv{ //new label imgLabels_row[c] = label; P_[label] = label; + checkLabelTypeOverflowBeforeIncrement(label); label = label + 1; } } @@ -2320,7 +2336,7 @@ namespace cv{ mergeLabels8Connectivity(imgLabels, P, chunksSizeAndLabels.data()); for (int i = 0; i < h; i = chunksSizeAndLabels[i]){ - flattenL(P, stripeFirstLabel8Connectivity(i, w), chunksSizeAndLabels[i + 1], nLabels); + flattenLParallel(P, stripeFirstLabel8Connectivity(i, w), chunksSizeAndLabels[i + 1], nLabels); } } else{ @@ -2331,7 +2347,7 @@ namespace cv{ mergeLabels4Connectivity(imgLabels, P, chunksSizeAndLabels.data()); for (int i = 0; i < h; i = chunksSizeAndLabels[i]){ - flattenL(P, stripeFirstLabel4Connectivity(i, w), chunksSizeAndLabels[i + 1], nLabels); + flattenLParallel(P, stripeFirstLabel4Connectivity(i, w), chunksSizeAndLabels[i + 1], nLabels); } } @@ -2433,6 +2449,7 @@ namespace cv{ //new label imgLabels_row[c] = lunique; P[lunique] = lunique; + checkLabelTypeOverflowBeforeIncrement(lunique); lunique = lunique + 1; } } @@ -2483,6 +2500,7 @@ namespace cv{ //new label imgLabels_row[c] = lunique; P[lunique] = lunique; + checkLabelTypeOverflowBeforeIncrement(lunique); lunique = lunique + 1; } } @@ -3108,6 +3126,7 @@ namespace cv{ //Action_2: New label (the block has foreground pixels and is not connected to anything else) imgLabels_row[c] = label; P_[label] = label; + checkLabelTypeOverflowBeforeIncrement(label); label = label + 1; continue; } @@ -3130,6 +3149,7 @@ namespace cv{ //Action_2: New label (the block has foreground pixels and is not connected to anything else) imgLabels_row[c] = label; P_[label] = label; + checkLabelTypeOverflowBeforeIncrement(label); label = label + 1; continue; } @@ -3483,6 +3503,7 @@ namespace cv{ //Action_2: New label (the block has foreground pixels and is not connected to anything else) imgLabels_row[c] = label; P_[label] = label; + checkLabelTypeOverflowBeforeIncrement(label); label = label + 1; continue; } @@ -3507,6 +3528,7 @@ namespace cv{ //Action_2: New label (the block has foreground pixels and is not connected to anything else) imgLabels_row[c] = label; P_[label] = label; + checkLabelTypeOverflowBeforeIncrement(label); label = label + 1; continue; } @@ -3550,6 +3572,7 @@ namespace cv{ //Action_2: New label (the block has foreground pixels and is not connected to anything else) imgLabels_row[c] = label; P_[label] = label; + checkLabelTypeOverflowBeforeIncrement(label); label = label + 1; continue; } @@ -3561,6 +3584,7 @@ namespace cv{ //Action_2: New label (the block has foreground pixels and is not connected to anything else) imgLabels_row[c] = label; P_[label] = label; + checkLabelTypeOverflowBeforeIncrement(label); label = label + 1; continue; } @@ -4260,7 +4284,7 @@ namespace cv{ LabelT nLabels = 1; for (int i = 0; i < h; i = chunksSizeAndLabels[i]){ CV_DbgAssert(i + 1 < chunksSizeAndLabelsSize); - flattenL(P.data(), stripeFirstLabel8Connectivity(i, w), chunksSizeAndLabels[i + 1], nLabels); + flattenLParallel(P.data(), stripeFirstLabel8Connectivity(i, w), chunksSizeAndLabels[i + 1], nLabels); } //Array for statistics data @@ -4865,6 +4889,7 @@ namespace cv{ //Action_2: New label (the block has foreground pixels and is not connected to anything else) imgLabels_row[c] = lunique; P[lunique] = lunique; + checkLabelTypeOverflowBeforeIncrement(lunique); lunique = lunique + 1; continue; } @@ -4887,6 +4912,7 @@ namespace cv{ //Action_2: New label (the block has foreground pixels and is not connected to anything else) imgLabels_row[c] = lunique; P[lunique] = lunique; + checkLabelTypeOverflowBeforeIncrement(lunique); lunique = lunique + 1; continue; } @@ -5240,6 +5266,7 @@ namespace cv{ //Action_2: New label (the block has foreground pixels and is not connected to anything else) imgLabels_row[c] = lunique; P[lunique] = lunique; + checkLabelTypeOverflowBeforeIncrement(lunique); lunique = lunique + 1; continue; } @@ -5264,6 +5291,7 @@ namespace cv{ //Action_2: New label (the block has foreground pixels and is not connected to anything else) imgLabels_row[c] = lunique; P[lunique] = lunique; + checkLabelTypeOverflowBeforeIncrement(lunique); lunique = lunique + 1; continue; } @@ -5307,6 +5335,7 @@ namespace cv{ //Action_2: New label (the block has foreground pixels and is not connected to anything else) imgLabels_row[c] = lunique; P[lunique] = lunique; + checkLabelTypeOverflowBeforeIncrement(lunique); lunique = lunique + 1; continue; } @@ -5318,6 +5347,7 @@ namespace cv{ //Action_2: New label (the block has foreground pixels and is not connected to anything else) imgLabels_row[c] = lunique; P[lunique] = lunique; + checkLabelTypeOverflowBeforeIncrement(lunique); lunique = lunique + 1; continue; } diff --git a/modules/imgproc/test/test_connectedcomponents.cpp b/modules/imgproc/test/test_connectedcomponents.cpp index 8717217cdf..7c4cebaa76 100644 --- a/modules/imgproc/test/test_connectedcomponents.cpp +++ b/modules/imgproc/test/test_connectedcomponents.cpp @@ -798,7 +798,47 @@ TEST(Imgproc_ConnectedComponents, 4conn_regression_21366) } } +TEST(Imgproc_ConnectedComponents, regression_27568) +{ + Mat image = Mat::zeros(Size(512, 512), CV_8UC1); + for (int row = 0; row < image.rows; row += 2) + { + for (int col = 0; col < image.cols; col += 2) + { + image.at(row, col) = 1; + } + } + for (const int connectivity : {4, 8}) + { + for (const int ccltype : {CCL_DEFAULT, CCL_WU, CCL_GRANA, CCL_BOLELLI, CCL_SAUF, CCL_BBDT, CCL_SPAGHETTI}) + { + { + Mat labels, stats, centroids; + try + { + connectedComponentsWithStats( + image, labels, stats, centroids, connectivity, CV_16U, ccltype); + ADD_FAILURE(); + } + catch (const Exception& exception) + { + EXPECT_TRUE( + strstr( + exception.what(), + "Total number of labels overflowed label type. Try using CV_32S instead of CV_16U as ltype")); + } + } + + { + Mat labels, stats, centroids; + EXPECT_NO_THROW( + connectedComponentsWithStats( + image, labels, stats, centroids, connectivity, CV_32S, ccltype)); + } + } + } +} } } // namespace From eebb15683f0c0a3ef6091a44d65676d799a572f1 Mon Sep 17 00:00:00 2001 From: Alexander Smorkalov Date: Tue, 29 Jul 2025 12:47:52 +0300 Subject: [PATCH 5/6] Fixed build with libpng older than 1.5.x --- modules/imgcodecs/src/grfmt_png.cpp | 14 +++++++++++++- modules/imgcodecs/test/test_exif.cpp | 2 +- modules/imgcodecs/test/test_gdal.cpp | 0 modules/imgcodecs/test/test_png.cpp | 8 ++++++-- 4 files changed, 20 insertions(+), 4 deletions(-) mode change 100755 => 100644 modules/imgcodecs/test/test_gdal.cpp diff --git a/modules/imgcodecs/src/grfmt_png.cpp b/modules/imgcodecs/src/grfmt_png.cpp index 5adf71323e..55e5f7ff11 100644 --- a/modules/imgcodecs/src/grfmt_png.cpp +++ b/modules/imgcodecs/src/grfmt_png.cpp @@ -648,7 +648,11 @@ bool PngDecoder::readData( Mat& img ) png_charp icc_name; int compression_type; +#if (PNG_LIBPNG_VER_MAJOR*10000 + PNG_LIBPNG_VER_MINOR*100 + PNG_LIBPNG_VER_RELEASE >= 10500) png_bytep icc_profile; +#else + png_charp icc_profile; +#endif png_uint_32 icc_length; if (png_get_iCCP(m_png_ptr, m_info_ptr, &icc_name, &compression_type, &icc_profile, &icc_length)) { @@ -1028,7 +1032,11 @@ bool PngEncoder::write( const Mat& img, const std::vector& params ) if (!m_metadata.empty()) { std::vector& exif = m_metadata[IMAGE_METADATA_EXIF]; if (!exif.empty()) { +#ifdef PNG_eXIf_SUPPORTED png_set_eXIf_1(png_ptr, info_ptr, static_cast(exif.size()), exif.data()); +#else + CV_LOG_WARNING(NULL, "Libpng is too old and does not support EXIF."); +#endif } std::vector& xmp = m_metadata[IMAGE_METADATA_XMP]; @@ -1045,7 +1053,7 @@ bool PngEncoder::write( const Mat& img, const std::vector& params ) 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"; + char iccp_profile_name[] = "ICC Profile"; // Compression type must be 0 (deflate) as per libpng docs int compression_type = PNG_COMPRESSION_TYPE_BASE; @@ -1056,7 +1064,11 @@ bool PngEncoder::write( const Mat& img, const std::vector& params ) png_set_iCCP(png_ptr, info_ptr, iccp_profile_name, compression_type, +#if (PNG_LIBPNG_VER_MAJOR*10000 + PNG_LIBPNG_VER_MINOR*100 + PNG_LIBPNG_VER_RELEASE >= 10500) reinterpret_cast(iccp.data()), +#else + reinterpret_cast(iccp.data()), +#endif static_cast(iccp.size())); } } diff --git a/modules/imgcodecs/test/test_exif.cpp b/modules/imgcodecs/test/test_exif.cpp index 8576c4f1e4..ac47e632a2 100644 --- a/modules/imgcodecs/test/test_exif.cpp +++ b/modules/imgcodecs/test/test_exif.cpp @@ -452,7 +452,7 @@ TEST(Imgcodecs_Png, Read_Write_With_Exif) EXPECT_EQ(img2.rows, img.rows); EXPECT_EQ(img2.type(), imgtype); EXPECT_EQ(read_metadata_types, read_metadata_types2); - EXPECT_GE(read_metadata_types.size(), 1u); + ASSERT_GE(read_metadata_types.size(), 1u); EXPECT_EQ(read_metadata, read_metadata2); EXPECT_EQ(read_metadata_types[0], IMAGE_METADATA_EXIF); EXPECT_EQ(read_metadata_types.size(), read_metadata.size()); diff --git a/modules/imgcodecs/test/test_gdal.cpp b/modules/imgcodecs/test/test_gdal.cpp old mode 100755 new mode 100644 diff --git a/modules/imgcodecs/test/test_png.cpp b/modules/imgcodecs/test/test_png.cpp index d3a0f4253a..0d2512fd20 100644 --- a/modules/imgcodecs/test/test_png.cpp +++ b/modules/imgcodecs/test/test_png.cpp @@ -19,7 +19,9 @@ TEST(Imgcodecs_Png, write_big) EXPECT_EQ(13917, img.rows); vector buff; - ASSERT_NO_THROW(imencode(".png", img, buff, { IMWRITE_PNG_ZLIBBUFFER_SIZE, INT_MAX })); + bool status = false; + ASSERT_NO_THROW(status = imencode(".png", img, buff, { IMWRITE_PNG_ZLIBBUFFER_SIZE, 1024*1024 })); + ASSERT_TRUE(status); #ifdef HAVE_PNG EXPECT_EQ((size_t)816219, buff.size()); #else @@ -34,7 +36,9 @@ TEST(Imgcodecs_Png, encode) vector param; param.push_back(IMWRITE_PNG_COMPRESSION); param.push_back(3); //default(3) 0-9. - EXPECT_NO_THROW(imencode(".png", img_gt, buff, param)); + bool status = false; + EXPECT_NO_THROW(status = imencode(".png", img_gt, buff, param)); + ASSERT_TRUE(status); Mat img; EXPECT_NO_THROW(img = imdecode(buff, IMREAD_ANYDEPTH)); // hang EXPECT_FALSE(img.empty()); From 7ab4e1bf56849e9c5584ce1400adf9705710ca32 Mon Sep 17 00:00:00 2001 From: Suleyman TURKMEN Date: Wed, 30 Jul 2025 12:07:49 +0300 Subject: [PATCH 6/6] Merge pull request #27583 from sturkmen72:jpeg_iccp Jpeg Metadata (iccp and xmp) #27583 ### 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 to do : - [x] consider `m_read_options` - [x] check if ICCP metadata is suitable to write PNG - [x] the file `testExifOrientation_3.jpg` used in the test has an ICCP data. this data can be saved in jpg format but could not in PNG and WEBP format. Impovements to check this case with PNGEncoder and WEBPEncoder planned. --- modules/imgcodecs/src/grfmt_jpeg.cpp | 80 ++++++++++++++++++++-------- modules/imgcodecs/test/test_exif.cpp | 43 ++++++++++++--- 2 files changed, 92 insertions(+), 31 deletions(-) diff --git a/modules/imgcodecs/src/grfmt_jpeg.cpp b/modules/imgcodecs/src/grfmt_jpeg.cpp index 9b2ab59b2b..810ae1d63b 100644 --- a/modules/imgcodecs/src/grfmt_jpeg.cpp +++ b/modules/imgcodecs/src/grfmt_jpeg.cpp @@ -247,8 +247,43 @@ bool JpegDecoder::readHeader() if (state->cinfo.src != 0) { jpeg_save_markers(&state->cinfo, APP1, 0xffff); + jpeg_save_markers(&state->cinfo, APP2, 0xffff); jpeg_read_header( &state->cinfo, TRUE ); + const std::streamsize EXIF_HEADER_SIZE = 6; // "Exif\0\0" + const std::streamsize XMP_HEADER_SIZE = 29; // "http://ns.adobe.com/xap/1.0/" + const std::streamsize ICC_HEADER_SIZE = 14; // "ICC_PROFILE\0" + seq/total + + for (jpeg_saved_marker_ptr cmarker = state->cinfo.marker_list; cmarker != nullptr; cmarker = cmarker->next) + { + // Handle APP1 marker: could be Exif or XMP + if (cmarker->marker == APP1 && cmarker->data_length > EXIF_HEADER_SIZE) + { + unsigned char* data = cmarker->data; + + // Check for Exif data + if (std::memcmp(data, "Exif\0\0", EXIF_HEADER_SIZE) == 0) + { + m_exif.parseExif(data + EXIF_HEADER_SIZE, cmarker->data_length - EXIF_HEADER_SIZE); + } + // Check for XMP metadata + else if (m_read_options && cmarker->data_length >= XMP_HEADER_SIZE && + std::memcmp(data, "http://ns.adobe.com/xap/1.0/", XMP_HEADER_SIZE) == 0) + { + std::vector& xmp = m_metadata[IMAGE_METADATA_XMP]; + xmp.insert(xmp.end(), data, data + cmarker->data_length); + } + } + + // Handle APP2 marker: typically contains ICC profile data + if (m_read_options && cmarker->marker == APP2 && cmarker->data_length > ICC_HEADER_SIZE) + { + const unsigned char* data = cmarker->data; + std::vector& iccp = m_metadata[IMAGE_METADATA_ICCP]; + iccp.insert(iccp.end(), data + ICC_HEADER_SIZE, data + cmarker->data_length); + } + } + state->cinfo.scale_num=1; state->cinfo.scale_denom = m_scale_denom; m_scale_denom=1; // trick! to know which decoder used scale_denom see imread_ @@ -469,29 +504,6 @@ bool JpegDecoder::readData( Mat& img ) } } - // Check for Exif marker APP1 - jpeg_saved_marker_ptr exif_marker = NULL; - jpeg_saved_marker_ptr cmarker = cinfo->marker_list; - while( cmarker && exif_marker == NULL ) - { - if (cmarker->marker == APP1) - exif_marker = cmarker; - - cmarker = cmarker->next; - } - - // Parse Exif data - if( exif_marker ) - { - const std::streamsize offsetToTiffHeader = 6; //bytes from Exif size field to the first TIFF header - - if (exif_marker->data_length > offsetToTiffHeader) - { - m_exif.parseExif(exif_marker->data + offsetToTiffHeader, exif_marker->data_length - offsetToTiffHeader); - } - } - - jpeg_start_decompress( cinfo ); if( doDirectRead) @@ -602,6 +614,8 @@ JpegEncoder::JpegEncoder() m_buf_supported = true; m_support_metadata.assign((size_t)IMAGE_METADATA_MAX + 1, false); m_support_metadata[(size_t)IMAGE_METADATA_EXIF] = true; + m_support_metadata[(size_t)IMAGE_METADATA_XMP] = true; + m_support_metadata[(size_t)IMAGE_METADATA_ICCP] = true; } @@ -831,6 +845,26 @@ bool JpegEncoder::write( const Mat& img, const std::vector& params ) memcpy(data + app1_exif_prefix_size, metadata_exif.data(), exif_size); jpeg_write_marker(&cinfo, JPEG_APP0 + 1, data, (unsigned)data_size); } + + const std::vector& metadata_xmp = m_metadata[IMAGE_METADATA_XMP]; + size_t xmp_size = metadata_xmp.size(); + if (xmp_size > 0u) { + jpeg_write_marker(&cinfo, JPEG_APP0 + 1, metadata_xmp.data(), (unsigned)xmp_size); + } + + const std::vector& metadata_iccp = m_metadata[IMAGE_METADATA_ICCP]; + size_t iccp_size = metadata_iccp.size(); + if (iccp_size > 0u) { + const char app1_iccp_prefix[] = {'I','C','C','_','P','R','O','F','I','L','E','\0','\1','\1'}; + size_t app1_iccp_prefix_size = sizeof(app1_iccp_prefix); + size_t data_size = iccp_size + app1_iccp_prefix_size; + + std::vector metadata_app1(data_size); + uchar* data = metadata_app1.data(); + memcpy(data, app1_iccp_prefix, app1_iccp_prefix_size); + memcpy(data + app1_iccp_prefix_size, metadata_iccp.data(), iccp_size); + jpeg_write_marker(&cinfo, JPEG_APP0 + 2, data, (unsigned)data_size); + } } if( doDirectWrite ) diff --git a/modules/imgcodecs/test/test_exif.cpp b/modules/imgcodecs/test/test_exif.cpp index ac47e632a2..706896fedc 100644 --- a/modules/imgcodecs/test/test_exif.cpp +++ b/modules/imgcodecs/test/test_exif.cpp @@ -532,7 +532,7 @@ static size_t locateString(const uchar* exif, size_t exif_size, const std::strin return 0xFFFFFFFFu; } -typedef std::tuple ReadExif_Sanity_Params; +typedef std::tuple ReadExif_Sanity_Params; typedef testing::TestWithParam ReadExif_Sanity; TEST_P(ReadExif_Sanity, Check) @@ -541,18 +541,27 @@ TEST_P(ReadExif_Sanity, Check) size_t exif_size = get<1>(GetParam()); std::string pattern = get<2>(GetParam()); size_t ploc = get<3>(GetParam()); + size_t expected_xmp_size = get<4>(GetParam()); + size_t expected_iccp_size = get<5>(GetParam()); const string root = cvtest::TS::ptr()->get_data_path(); filename = root + filename; - std::vector metadata_types; - std::vector metadata; - Mat img = imreadWithMetadata(filename, metadata_types, metadata, 1); + std::vector metadata_types, metadata_types2; + std::vector > metadata, metadata2; + Mat img = imreadWithMetadata(filename, metadata_types, metadata); + + std::vector compressed; + imencodeWithMetadata(".jpg", img, metadata_types, metadata, compressed); + img = imdecodeWithMetadata(compressed, metadata_types2, metadata2); + + EXPECT_EQ(metadata_types, metadata_types2); + EXPECT_EQ(metadata, metadata2); EXPECT_EQ(img.type(), CV_8UC3); ASSERT_GE(metadata_types.size(), 1u); EXPECT_EQ(metadata_types.size(), metadata.size()); - const Mat& exif = metadata[IMAGE_METADATA_EXIF]; + const Mat exif = Mat(metadata[IMAGE_METADATA_EXIF]); EXPECT_EQ(exif.type(), CV_8U); EXPECT_EQ(exif.total(), exif_size); ASSERT_GE(exif_size, 26u); // minimal exif should take at least 26 bytes @@ -560,18 +569,36 @@ TEST_P(ReadExif_Sanity, Check) EXPECT_TRUE(exif.data[0] == 'I' || exif.data[0] == 'M'); EXPECT_EQ(exif.data[0], exif.data[1]); EXPECT_EQ(locateString(exif.data, exif_size, pattern), ploc); + + if (metadata_types.size() > IMAGE_METADATA_XMP) + { + const Mat xmp = Mat(metadata[IMAGE_METADATA_XMP]); + EXPECT_EQ(xmp.type(), CV_8U); + EXPECT_GT(xmp.total(), 0u); + size_t xmp_size = xmp.total() * xmp.elemSize(); + EXPECT_EQ(expected_xmp_size, xmp_size); + } + + if (metadata_types.size() > IMAGE_METADATA_ICCP) + { + const Mat iccp = Mat(metadata[IMAGE_METADATA_ICCP]); + EXPECT_EQ(iccp.type(), CV_8U); + EXPECT_GT(iccp.total(), 0u); + size_t iccp_size = iccp.total() * iccp.elemSize(); + EXPECT_EQ(expected_iccp_size, iccp_size); + } } static const std::vector exif_sanity_params { #ifdef HAVE_JPEG - ReadExif_Sanity_Params("readwrite/testExifOrientation_3.jpg", 916, "Photoshop", 120), + ReadExif_Sanity_Params("readwrite/testExifOrientation_3.jpg", 916, "Photoshop", 120, 3597, 940), #endif #ifdef OPENCV_IMGCODECS_PNG_WITH_EXIF - ReadExif_Sanity_Params("readwrite/testExifOrientation_5.png", 112, "ExifTool", 102), + ReadExif_Sanity_Params("readwrite/testExifOrientation_5.png", 112, "ExifTool", 102, 505, 0), #endif #ifdef HAVE_AVIF - ReadExif_Sanity_Params("readwrite/testExifOrientation_7.avif", 913, "Photoshop", 120), + ReadExif_Sanity_Params("readwrite/testExifOrientation_7.avif", 913, "Photoshop", 120, 3597, 940), #endif };