From 912cf2a02809e1bff483932cee162524c6200894 Mon Sep 17 00:00:00 2001 From: Kumataro Date: Fri, 29 Mar 2024 23:38:06 +0900 Subject: [PATCH] Merge pull request #25280 from Kumataro:fix25274 imgcodecs: jpeg: re-support to read CMYK Jpeg #25280 Close #25274 OpenCV Extra: https://github.com/opencv/opencv_extra/pull/1163 ### 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. - [x] The feature is well documented and sample code can be built with the project CMake --- modules/imgcodecs/src/grfmt_jpeg.cpp | 82 +++++++++++++++------------- modules/imgcodecs/test/test_jpeg.cpp | 38 +++++++++++++ 2 files changed, 81 insertions(+), 39 deletions(-) diff --git a/modules/imgcodecs/src/grfmt_jpeg.cpp b/modules/imgcodecs/src/grfmt_jpeg.cpp index 54dfea5a75..87187f131b 100644 --- a/modules/imgcodecs/src/grfmt_jpeg.cpp +++ b/modules/imgcodecs/src/grfmt_jpeg.cpp @@ -402,16 +402,12 @@ int my_jpeg_load_dht (struct jpeg_decompress_struct *info, unsigned char *dht, bool JpegDecoder::readData( Mat& img ) { volatile bool result = false; - size_t step = img.step; - bool color = img.channels() > 1; + const bool color = img.channels() > 1; if( m_state && m_width && m_height ) { jpeg_decompress_struct* cinfo = &((JpegState*)m_state)->cinfo; JpegErrorMgr* jerr = &((JpegState*)m_state)->jerr; -#ifndef JCS_EXTENSIONS - JSAMPARRAY buffer = 0; -#endif if( setjmp( jerr->setjmp_buffer ) == 0 ) { @@ -431,29 +427,30 @@ bool JpegDecoder::readData( Mat& img ) } #endif -#ifdef JCS_EXTENSIONS - if( color ) - { - cinfo->out_color_space = JCS_EXT_BGR; - cinfo->out_color_components = 3; - } - else - { - cinfo->out_color_space = JCS_GRAYSCALE; - cinfo->out_color_components = 1; - } -#else + // See https://github.com/opencv/opencv/issues/25274 + // Conversion CMYK->BGR is not supported in libjpeg-turbo. + // So supporting both directly and indirectly is necessary. + bool doDirectRead = false; + if( color ) { if( cinfo->num_components != 4 ) { +#ifdef JCS_EXTENSIONS + cinfo->out_color_space = JCS_EXT_BGR; + cinfo->out_color_components = 3; + doDirectRead = true; // BGR -> BGR +#else cinfo->out_color_space = JCS_RGB; cinfo->out_color_components = 3; + doDirectRead = false; // RGB -> BGR +#endif } else { cinfo->out_color_space = JCS_CMYK; cinfo->out_color_components = 4; + doDirectRead = false; // CMYK -> BGR } } else @@ -462,14 +459,15 @@ bool JpegDecoder::readData( Mat& img ) { cinfo->out_color_space = JCS_GRAYSCALE; cinfo->out_color_components = 1; + doDirectRead = true; // GRAY -> GRAY } else { cinfo->out_color_space = JCS_CMYK; cinfo->out_color_components = 4; + doDirectRead = false; // CMYK -> GRAY } } -#endif // Check for Exif marker APP1 jpeg_saved_marker_ptr exif_marker = NULL; @@ -496,33 +494,39 @@ bool JpegDecoder::readData( Mat& img ) jpeg_start_decompress( cinfo ); -#ifndef JCS_EXTENSIONS - buffer = (*cinfo->mem->alloc_sarray)((j_common_ptr)cinfo, - JPOOL_IMAGE, m_width*4, 1 ); -#endif - - uchar* data = img.ptr(); - for( ; m_height--; data += step ) + if( doDirectRead) { -#ifdef JCS_EXTENSIONS - jpeg_read_scanlines( cinfo, &data, 1 ); -#else - jpeg_read_scanlines( cinfo, buffer, 1 ); - if( color ) + for( int iy = 0 ; iy < m_height; iy ++ ) { - if( cinfo->out_color_components == 3 ) - icvCvt_RGB2BGR_8u_C3R( buffer[0], 0, data, 0, Size(m_width,1) ); - else - icvCvt_CMYK2BGR_8u_C4C3R( buffer[0], 0, data, 0, Size(m_width,1) ); + uchar* data = img.ptr(iy); + jpeg_read_scanlines( cinfo, &data, 1 ); } - else + } + else + { + JSAMPARRAY buffer = (*cinfo->mem->alloc_sarray)((j_common_ptr)cinfo, + JPOOL_IMAGE, m_width*4, 1 ); + + for( int iy = 0 ; iy < m_height; iy ++ ) { - if( cinfo->out_color_components == 1 ) - memcpy( data, buffer[0], m_width ); + uchar* data = img.ptr(iy); + jpeg_read_scanlines( cinfo, buffer, 1 ); + + if( color ) + { + if( cinfo->out_color_components == 3 ) + icvCvt_RGB2BGR_8u_C3R( buffer[0], 0, data, 0, Size(m_width,1) ); + else + icvCvt_CMYK2BGR_8u_C4C3R( buffer[0], 0, data, 0, Size(m_width,1) ); + } else - icvCvt_CMYK2Gray_8u_C4C1R( buffer[0], 0, data, 0, Size(m_width,1) ); + { + if( cinfo->out_color_components == 1 ) + memcpy( data, buffer[0], m_width ); + else + icvCvt_CMYK2Gray_8u_C4C1R( buffer[0], 0, data, 0, Size(m_width,1) ); + } } -#endif } result = true; diff --git a/modules/imgcodecs/test/test_jpeg.cpp b/modules/imgcodecs/test/test_jpeg.cpp index b7932a0020..9d1db923d5 100644 --- a/modules/imgcodecs/test/test_jpeg.cpp +++ b/modules/imgcodecs/test/test_jpeg.cpp @@ -178,6 +178,44 @@ TEST(Imgcodecs_Jpeg, encode_decode_rst_jpeg) EXPECT_EQ(0, remove(output_normal.c_str())); } +// See https://github.com/opencv/opencv/issues/25274 +typedef testing::TestWithParam Imgcodecs_Jpeg_decode_cmyk; +TEST_P(Imgcodecs_Jpeg_decode_cmyk, regression25274) +{ + const int imread_flag = GetParam(); + + /* + * "test_1_c4.jpg" is CMYK-JPEG. + * $ convert test_1_c3.jpg -colorspace CMYK test_1_c4.jpg + * $ identify test_1_c4.jpg + * test_1_c4.jpg JPEG 480x640 480x640+0+0 8-bit CMYK 11240B 0.000u 0:00.000 + */ + + cvtest::TS& ts = *cvtest::TS::ptr(); + + string rgb_filename = string(ts.get_data_path()) + "readwrite/test_1_c3.jpg"; + cv::Mat rgb_img = cv::imread(rgb_filename, imread_flag); + ASSERT_FALSE(rgb_img.empty()); + + string cmyk_filename = string(ts.get_data_path()) + "readwrite/test_1_c4.jpg"; + cv::Mat cmyk_img = cv::imread(cmyk_filename, imread_flag); + ASSERT_FALSE(cmyk_img.empty()); + + EXPECT_EQ(rgb_img.size(), cmyk_img.size()); + EXPECT_EQ(rgb_img.type(), cmyk_img.type()); + + // Jpeg is lossy compression. + // There may be small differences in decoding results by environments. + // -> 255 * 1% = 2.55 . + EXPECT_EQ(3, cvtest::norm(rgb_img, cmyk_img, NORM_INF)); +} + +INSTANTIATE_TEST_CASE_P( /* nothing */, + Imgcodecs_Jpeg_decode_cmyk, + testing::Values(cv::IMREAD_COLOR, + cv::IMREAD_GRAYSCALE, + cv::IMREAD_ANYCOLOR)); + //================================================================================================== static const uint32_t default_sampling_factor = static_cast(0x221111);