diff --git a/modules/imgcodecs/src/grfmt_jpegxl.cpp b/modules/imgcodecs/src/grfmt_jpegxl.cpp index 060174eb02..72118184f6 100644 --- a/modules/imgcodecs/src/grfmt_jpegxl.cpp +++ b/modules/imgcodecs/src/grfmt_jpegxl.cpp @@ -518,11 +518,34 @@ bool JpegXLEncoder::write(const Mat& img, const std::vector& params) return false; } + // get distance param for JxlBasicInfo. + float distance = -1.0; // Negative means not set + for( size_t i = 0; i < params.size(); i += 2 ) + { + if( params[i] == IMWRITE_JPEGXL_QUALITY ) + { +#if JPEGXL_MAJOR_VERSION > 0 || JPEGXL_MINOR_VERSION >= 10 + int quality = params[i+1]; + quality = MIN(MAX(quality, 0), 100); + distance = JxlEncoderDistanceFromQuality(static_cast(quality)); +#else + CV_LOG_ONCE_WARNING(NULL, "Quality parameter is supported with libjxl v0.10.0 or later"); +#endif + } + if( params[i] == IMWRITE_JPEGXL_DISTANCE ) + { + int distanceInt = params[i+1]; + distanceInt = MIN(MAX(distanceInt, 0), 25); + distance = static_cast(distanceInt); + } + } + JxlBasicInfo info; JxlEncoderInitBasicInfo(&info); info.xsize = img.cols; info.ysize = img.rows; - info.uses_original_profile = JXL_FALSE; + // Lossless encoding requires uses_original_profile = true. + info.uses_original_profile = (distance == 0.0) ? JXL_TRUE : JXL_FALSE; if( img.channels() == 4 ) { @@ -576,30 +599,26 @@ bool JpegXLEncoder::write(const Mat& img, const std::vector& params) return false; JxlEncoderFrameSettings* frame_settings = JxlEncoderFrameSettingsCreate(encoder.get(), nullptr); + + // set frame settings with distance params + if(distance == 0.0) // lossless + { + if( JXL_ENC_SUCCESS != JxlEncoderSetFrameLossless(frame_settings, JXL_TRUE) ) + { + CV_LOG_WARNING(NULL, "Failed to call JxlEncoderSetFrameLossless()"); + } + } + else if(distance > 0.0) // lossy + { + if( JXL_ENC_SUCCESS != JxlEncoderSetFrameDistance(frame_settings, distance) ) + { + CV_LOG_WARNING(NULL, "Failed to call JxlEncoderSetFrameDistance()"); + } + } + // set frame settings from params if available for( size_t i = 0; i < params.size(); i += 2 ) { - if( params[i] == IMWRITE_JPEGXL_QUALITY ) - { -#if JPEGXL_MAJOR_VERSION > 0 || JPEGXL_MINOR_VERSION >= 10 - int quality = params[i+1]; - quality = MIN(MAX(quality, 0), 100); - const float distance = JxlEncoderDistanceFromQuality(static_cast(quality)); - JxlEncoderSetFrameDistance(frame_settings, distance); - if (distance == 0) - JxlEncoderSetFrameLossless(frame_settings, JXL_TRUE); -#else - CV_LOG_ONCE_WARNING(NULL, "Quality parameter is supported with libjxl v0.10.0 or later"); -#endif - } - if( params[i] == IMWRITE_JPEGXL_DISTANCE ) - { - int distance = params[i+1]; - distance = MIN(MAX(distance, 0), 25); - JxlEncoderSetFrameDistance(frame_settings, distance); - if (distance == 0) - JxlEncoderSetFrameLossless(frame_settings, JXL_TRUE); - } if( params[i] == IMWRITE_JPEGXL_EFFORT ) { int effort = params[i+1]; diff --git a/modules/imgcodecs/test/test_jpegxl.cpp b/modules/imgcodecs/test/test_jpegxl.cpp index d43b625364..fecc325080 100644 --- a/modules/imgcodecs/test/test_jpegxl.cpp +++ b/modules/imgcodecs/test/test_jpegxl.cpp @@ -7,6 +7,8 @@ namespace opencv_test { namespace { #ifdef HAVE_JPEGXL +#include // For JPEGXL_MAJOR_VERSION and JPEGXL_MINOR_VERSION + typedef tuple MatType_and_Distance; typedef testing::TestWithParam Imgcodecs_JpegXL_MatType; @@ -16,8 +18,8 @@ TEST_P(Imgcodecs_JpegXL_MatType, write_read) const int distanceParam = get<1>(GetParam()); cv::Scalar col; - // Jpeg XL is lossy compression. - // There may be small differences in decoding results by environments. + // Jpeg XL supports lossy and lossless compressions. + // Lossy compression may be small differences in decoding results by environments. double th; switch( CV_MAT_DEPTH(matType) ) @@ -38,7 +40,7 @@ TEST_P(Imgcodecs_JpegXL_MatType, write_read) } // If increasing distanceParam, threshold should be increased. - th *= (distanceParam >= 25) ? 5 : ( distanceParam > 2 ) ? 3 : (distanceParam == 2) ? 2: 1; + th *= (distanceParam >= 25) ? 5 : (distanceParam > 2) ? 3 : distanceParam; bool ret = false; string tmp_fname = cv::tempfile(".jxl"); @@ -63,8 +65,8 @@ TEST_P(Imgcodecs_JpegXL_MatType, encode_decode) const int distanceParam = get<1>(GetParam()); cv::Scalar col; - // Jpeg XL is lossy compression. - // There may be small differences in decoding results by environments. + // Jpeg XL supports lossy and lossless compressions. + // Lossy compression may be small differences in decoding results by environments. double th; // If alpha=0, libjxl modify color channels(BGR). So do not set it. @@ -86,7 +88,7 @@ TEST_P(Imgcodecs_JpegXL_MatType, encode_decode) } // If increasing distanceParam, threshold should be increased. - th *= (distanceParam >= 25) ? 5 : ( distanceParam > 2 ) ? 3 : (distanceParam == 2) ? 2: 1; + th *= (distanceParam >= 25) ? 5 : (distanceParam > 2) ? 3 : distanceParam; bool ret = false; vector buff; @@ -130,8 +132,8 @@ TEST_P(Imgcodecs_JpegXL_Effort_DecodingSpeed, encode_decode) const int speed = get<1>(GetParam()); cv::Scalar col = cv::Scalar(124,76,42); - // Jpeg XL is lossy compression. - // There may be small differences in decoding results by environments. + // Jpeg XL supports lossy and lossless compression. + // Lossy compression may be small differences in decoding results by environments. double th = 3; // = 255 / 100 (1%); bool ret = false; @@ -305,6 +307,56 @@ TEST(Imgcodecs_JpegXL, imread_truncated_stream) remove(tmp_fname.c_str()); } +// See https://github.com/opencv/opencv/issues/27382 +TEST(Imgcodecs_JpegXL, imencode_regression27382) +{ + cv::Mat image(1024, 1024, CV_16U); + cv::RNG rng(1024); + rng.fill(image, cv::RNG::NORMAL, 0, 65535); + + std::vector buffer; + std::vector params = {cv::IMWRITE_JPEGXL_DISTANCE, 0}; // lossless + + EXPECT_NO_THROW(cv::imencode(".jxl", image, buffer, params)); + + cv::Mat decoded; + EXPECT_NO_THROW(decoded = cv::imdecode(buffer, cv::IMREAD_UNCHANGED)); + EXPECT_FALSE(decoded.empty()); + + cv::Mat diff; + cv::absdiff(image, decoded, diff); + double max_diff = 0.0; + cv::minMaxLoc(diff, nullptr, &max_diff); + EXPECT_EQ(max_diff, 0 ); +} + +TEST(Imgcodecs_JpegXL, imencode_regression27382_2) +{ + cv::Mat image(1024, 1024, CV_16U); + cv::RNG rng(1024); + rng.fill(image, cv::RNG::NORMAL, 0, 65535); + + std::vector buffer; + std::vector params = {cv::IMWRITE_JPEGXL_QUALITY, 100}; // lossless + + EXPECT_NO_THROW(cv::imencode(".jxl", image, buffer, params)); + + cv::Mat decoded; + EXPECT_NO_THROW(decoded = cv::imdecode(buffer, cv::IMREAD_UNCHANGED)); + EXPECT_FALSE(decoded.empty()); + + cv::Mat diff; + cv::absdiff(image, decoded, diff); + double max_diff = 0.0; + cv::minMaxLoc(diff, nullptr, &max_diff); +#if JPEGXL_MAJOR_VERSION > 0 || JPEGXL_MINOR_VERSION >= 10 + // Quality parameter is supported with libjxl v0.10.0 or later + EXPECT_EQ(max_diff, 0); // Lossless +#else + EXPECT_NE(max_diff, 0); // Lossy +#endif +} + #endif // HAVE_JPEGXL