From d2dbaa4cd1594860b49c6b9d8d081a1935b2a569 Mon Sep 17 00:00:00 2001 From: Kumataro Date: Tue, 11 Apr 2023 16:50:47 +0900 Subject: [PATCH] Merge pull request #23433 from Kumataro:4.x-fix23416 imgcodecs: tiff: Support to encode for CV_32S with compression params Fix https://github.com/opencv/opencv/issues/23416 ### 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/src/grfmt_tiff.cpp | 72 +++++++++++++++++++-- modules/imgcodecs/test/test_tiff.cpp | 95 ++++++++++++++++++++++++++++ 2 files changed, 160 insertions(+), 7 deletions(-) diff --git a/modules/imgcodecs/src/grfmt_tiff.cpp b/modules/imgcodecs/src/grfmt_tiff.cpp index 5320c1a77a..ed21f3f14c 100644 --- a/modules/imgcodecs/src/grfmt_tiff.cpp +++ b/modules/imgcodecs/src/grfmt_tiff.cpp @@ -63,6 +63,9 @@ using namespace tiff_dummy_namespace; namespace cv { +// to extend cvtColor() to support CV_8S, CV_16S, CV_32S and CV_64F. +static void extend_cvtColor( InputArray _src, OutputArray _dst, int code ); + #define CV_TIFF_CHECK_CALL(call) \ if (0 == (call)) { \ CV_LOG_WARNING(NULL, "OpenCV TIFF(line " << __LINE__ << "): failed " #call); \ @@ -1039,9 +1042,9 @@ bool TiffDecoder::readData( Mat& img ) Rect roi_tile(0, 0, tile_width, tile_height); Rect roi_img(x, img_y, tile_width, tile_height); if (!m_hdr && ncn == 3) - cvtColor(m_tile(roi_tile), img(roi_img), COLOR_RGB2BGR); + extend_cvtColor(m_tile(roi_tile), img(roi_img), COLOR_RGB2BGR); else if (!m_hdr && ncn == 4) - cvtColor(m_tile(roi_tile), img(roi_img), COLOR_RGBA2BGRA); + extend_cvtColor(m_tile(roi_tile), img(roi_img), COLOR_RGBA2BGRA); else m_tile(roi_tile).copyTo(img(roi_img)); break; @@ -1279,13 +1282,17 @@ bool TiffEncoder::writeLibTiff( const std::vector& img_vec, const std::vect break; } - case CV_32F: - sample_format = SAMPLEFORMAT_IEEEFP; - /* FALLTHRU */ case CV_32S: + { + bitsPerChannel = 32; + sample_format = SAMPLEFORMAT_INT; + break; + } + case CV_32F: { bitsPerChannel = 32; page_compression = COMPRESSION_NONE; + sample_format = SAMPLEFORMAT_IEEEFP; break; } case CV_64F: @@ -1356,13 +1363,13 @@ bool TiffEncoder::writeLibTiff( const std::vector& img_vec, const std::vect case 3: { - cvtColor(img(Rect(0, y, width, 1)), (const Mat&)m_buffer, COLOR_BGR2RGB); + extend_cvtColor(img(Rect(0, y, width, 1)), (const Mat&)m_buffer, COLOR_BGR2RGB); break; } case 4: { - cvtColor(img(Rect(0, y, width, 1)), (const Mat&)m_buffer, COLOR_BGRA2RGBA); + extend_cvtColor(img(Rect(0, y, width, 1)), (const Mat&)m_buffer, COLOR_BGRA2RGBA); break; } @@ -1424,6 +1431,57 @@ bool TiffEncoder::write( const Mat& img, const std::vector& params) return writeLibTiff(img_vec, params); } +static void extend_cvtColor( InputArray _src, OutputArray _dst, int code ) +{ + CV_Assert( !_src.empty() ); + CV_Assert( _src.dims() == 2 ); + + // This function extend_cvtColor reorders the src channels with only thg limited condition. + // Otherwise, it calls cvtColor. + + const int stype = _src.type(); + if(! + ( + ( + ( stype == CV_8SC3 ) || ( stype == CV_8SC4 ) || + ( stype == CV_16SC3 ) || ( stype == CV_16SC4 ) || + ( stype == CV_32SC3 ) || ( stype == CV_32SC4 ) || + ( stype == CV_64FC3 ) || ( stype == CV_64FC4 ) + ) + && + ( + ( code == COLOR_BGR2RGB ) || ( code == COLOR_BGRA2RGBA ) + ) + ) + ) + { + cvtColor( _src, _dst, code ); + return; + } + + Mat src = _src.getMat(); + + // cv::mixChannels requires the output arrays to be pre-allocated before calling the function. + _dst.create( _src.size(), stype ); + Mat dst = _dst.getMat(); + + // BGR to RGB or BGRA to RGBA + // src[0] -> dst[2] + // src[1] -> dst[1] + // src[2] -> dst[0] + // src[3] -> dst[3] if src has alpha channel. + std::vector fromTo; + fromTo.push_back(0); fromTo.push_back(2); + fromTo.push_back(1); fromTo.push_back(1); + fromTo.push_back(2); fromTo.push_back(0); + if ( code == COLOR_BGRA2RGBA ) + { + fromTo.push_back(3); fromTo.push_back(3); + } + + cv::mixChannels( src, dst, fromTo ); +} + } // namespace #endif diff --git a/modules/imgcodecs/test/test_tiff.cpp b/modules/imgcodecs/test/test_tiff.cpp index c7d04cd9b9..3aea5b85d5 100644 --- a/modules/imgcodecs/test/test_tiff.cpp +++ b/modules/imgcodecs/test/test_tiff.cpp @@ -14,6 +14,38 @@ namespace opencv_test { namespace { #define int64 int64_hack_ #include "tiff.h" +// Re-define Mat type as enum for showing on Google Test. +enum CV_ddtCn{ + _CV_8UC1 = CV_8UC1, _CV_8UC3 = CV_8UC3, _CV_8UC4 = CV_8UC4, + _CV_8SC1 = CV_8SC1, _CV_8SC3 = CV_8SC3, _CV_8SC4 = CV_8SC4, + _CV_16UC1 = CV_16UC1, _CV_16UC3 = CV_16UC3, _CV_16UC4 = CV_16UC4, + _CV_16SC1 = CV_16SC1, _CV_16SC3 = CV_16SC3, _CV_16SC4 = CV_16SC4, + _CV_32SC1 = CV_32SC1, _CV_32SC3 = CV_32SC3, _CV_32SC4 = CV_32SC4, + _CV_16FC1 = CV_16FC1, _CV_16FC3 = CV_16FC3, _CV_16FC4 = CV_16FC4, + _CV_32FC1 = CV_32FC1, _CV_32FC3 = CV_32FC3, _CV_32FC4 = CV_32FC4, + _CV_64FC1 = CV_64FC1, _CV_64FC3 = CV_64FC3, _CV_64FC4 = CV_64FC4, +}; + +static inline +void PrintTo(const CV_ddtCn& val, std::ostream* os) +{ + const int val_type = static_cast(val); + + switch ( CV_MAT_DEPTH(val_type) ) + { + case CV_8U : *os << "CV_8U" ; break; + case CV_16U : *os << "CV_16U" ; break; + case CV_8S : *os << "CV_8S" ; break; + case CV_16S : *os << "CV_16S" ; break; + case CV_32S : *os << "CV_32S" ; break; + case CV_16F : *os << "CV_16F" ; break; + case CV_32F : *os << "CV_32F" ; break; + case CV_64F : *os << "CV_64F" ; break; + default : *os << "CV_???" ; break; + } + *os << "C" << CV_MAT_CN(val_type); +} + #ifdef __ANDROID__ // Test disabled as it uses a lot of memory. // It is killed with SIGKILL by out of memory killer. @@ -840,6 +872,69 @@ TEST(Imgcodecs_Tiff, readWrite_predictor) } } +// See https://github.com/opencv/opencv/issues/23416 + +typedef std::pair Imgcodes_Tiff_TypeAndComp; +typedef testing::TestWithParam< Imgcodes_Tiff_TypeAndComp > Imgcodecs_Tiff_Types; + +TEST_P(Imgcodecs_Tiff_Types, readWrite_alltypes) +{ + const int mat_types = static_cast(get<0>(GetParam())); + const bool isCompAvailable = get<1>(GetParam()); + + // Create a test image. + const Mat src = cv::Mat::zeros( 120, 160, mat_types ); + { + // Add noise to test compression. + cv::Mat roi = cv::Mat(src, cv::Rect(0, 0, src.cols, src.rows/2)); + cv::randu(roi, cv::Scalar(0), cv::Scalar(256)); + } + + // Try to encode/decode the test image with LZW compression. + std::vector bufLZW; + { + std::vector params; + params.push_back(IMWRITE_TIFF_COMPRESSION); + params.push_back(COMPRESSION_LZW); + ASSERT_NO_THROW(cv::imencode(".tiff", src, bufLZW, params)); + + Mat dstLZW; + ASSERT_NO_THROW(cv::imdecode( bufLZW, IMREAD_UNCHANGED, &dstLZW)); + ASSERT_EQ(dstLZW.type(), src.type()); + ASSERT_EQ(dstLZW.size(), src.size()); + ASSERT_LE(cvtest::norm(dstLZW, src, NORM_INF | NORM_RELATIVE), 1e-3); + } + + // Try to encode/decode the test image with RAW. + std::vector bufRAW; + { + std::vector params; + params.push_back(IMWRITE_TIFF_COMPRESSION); + params.push_back(COMPRESSION_NONE); + ASSERT_NO_THROW(cv::imencode(".tiff", src, bufRAW, params)); + + Mat dstRAW; + ASSERT_NO_THROW(cv::imdecode( bufRAW, IMREAD_UNCHANGED, &dstRAW)); + ASSERT_EQ(dstRAW.type(), src.type()); + ASSERT_EQ(dstRAW.size(), src.size()); + ASSERT_LE(cvtest::norm(dstRAW, src, NORM_INF | NORM_RELATIVE), 1e-3); + } + + // Compare LZW and RAW streams. + EXPECT_EQ(bufLZW == bufRAW, !isCompAvailable); +} + +Imgcodes_Tiff_TypeAndComp all_types[] = { + { _CV_8UC1, true }, { _CV_8UC3, true }, { _CV_8UC4, true }, + { _CV_8SC1, true }, { _CV_8SC3, true }, { _CV_8SC4, true }, + { _CV_16UC1, true }, { _CV_16UC3, true }, { _CV_16UC4, true }, + { _CV_16SC1, true }, { _CV_16SC3, true }, { _CV_16SC4, true }, + { _CV_32SC1, true }, { _CV_32SC3, true }, { _CV_32SC4, true }, + { _CV_32FC1, false }, { _CV_32FC3, false }, { _CV_32FC4, false }, // No compression + { _CV_64FC1, false }, { _CV_64FC3, false }, { _CV_64FC4, false } // No compression +}; + +INSTANTIATE_TEST_CASE_P(AllTypes, Imgcodecs_Tiff_Types, testing::ValuesIn(all_types)); //==================================================================================================