Merge pull request #26379 from cdcseacave:jxl_codec

Add jxl (JPEG XL) codec support #26379

### Pull Request Readiness Checklist

Related CI and Docker changes:
- https://github.com/opencv/ci-gha-workflow/pull/190
- https://github.com/opencv-infrastructure/opencv-gha-dockerfile/pull/44

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 https://github.com/opencv/opencv/issues/20178
- [ ] 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
This commit is contained in:
cDc
2024-12-31 10:56:35 +02:00
committed by GitHub
parent c803aa2ddd
commit 1db982780f
14 changed files with 881 additions and 3 deletions

View File

@@ -23,6 +23,11 @@ if(HAVE_JPEG)
list(APPEND GRFMT_LIBS ${JPEG_LIBRARIES})
endif()
if(HAVE_JPEGXL)
ocv_include_directories(${OPENJPEG_INCLUDE_DIRS})
list(APPEND GRFMT_LIBS ${OPENJPEG_LIBRARIES})
endif()
if(HAVE_WEBP)
add_definitions(-DHAVE_WEBP)
ocv_include_directories(${WEBP_INCLUDE_DIR})
@@ -51,6 +56,12 @@ if(HAVE_TIFF)
list(APPEND GRFMT_LIBS ${TIFF_LIBRARIES})
endif()
if(HAVE_JPEGXL)
ocv_include_directories(${JPEGXL_INCLUDE_DIRS})
message(STATUS "JPEGXL_INCLUDE_DIRS: ${JPEGXL_INCLUDE_DIRS}")
list(APPEND GRFMT_LIBS ${JPEGXL_LIBRARIES})
endif()
if(HAVE_OPENJPEG)
ocv_include_directories(${OPENJPEG_INCLUDE_DIRS})
list(APPEND GRFMT_LIBS ${OPENJPEG_LIBRARIES})
@@ -78,7 +89,7 @@ if(HAVE_OPENEXR)
endif()
endif()
if(HAVE_PNG OR HAVE_TIFF OR HAVE_OPENEXR OR HAVE_SPNG)
if(HAVE_PNG OR HAVE_TIFF OR HAVE_OPENEXR OR HAVE_SPNG OR HAVE_JPEGXL)
ocv_include_directories(${ZLIB_INCLUDE_DIRS})
list(APPEND GRFMT_LIBS ${ZLIB_LIBRARIES})
endif()

View File

@@ -112,13 +112,17 @@ enum ImwriteFlags {
IMWRITE_AVIF_QUALITY = 512,//!< For AVIF, it can be a quality between 0 and 100 (the higher the better). Default is 95.
IMWRITE_AVIF_DEPTH = 513,//!< For AVIF, it can be 8, 10 or 12. If >8, it is stored/read as CV_32F. Default is 8.
IMWRITE_AVIF_SPEED = 514,//!< For AVIF, it is between 0 (slowest) and (fastest). Default is 9.
IMWRITE_JPEGXL_QUALITY = 640,//!< For JPEG XL, it can be a quality from 0 to 100 (the higher is the better). Default value is 95. If set, distance parameter is re-calicurated from quality level automatically. This parameter request libjxl v0.10 or later.
IMWRITE_JPEGXL_EFFORT = 641,//!< For JPEG XL, encoder effort/speed level without affecting decoding speed; it is between 1 (fastest) and 10 (slowest). Default is 7.
IMWRITE_JPEGXL_DISTANCE = 642,//!< For JPEG XL, distance level for lossy compression: target max butteraugli distance, lower = higher quality, 0 = lossless; range: 0 .. 25. Default is 1.
IMWRITE_JPEGXL_DECODING_SPEED = 643,//!< For JPEG XL, decoding speed tier for the provided options; minimum is 0 (slowest to decode, best quality/density), and maximum is 4 (fastest to decode, at the cost of some quality/density). Default is 0.
IMWRITE_GIF_LOOP = 1024,//!< For GIF, it can be a loop flag from 0 to 65535. Default is 0 - loop forever.
IMWRITE_GIF_SPEED = 1025,//!< For GIF, it is between 1 (slowest) and 100 (fastest). Default is 96.
IMWRITE_GIF_QUALITY = 1026, //!< For GIF, it can be a quality from 1 to 8. Default is 2. See cv::ImwriteGifCompressionFlags.
IMWRITE_GIF_DITHER = 1027, //!< For GIF, it can be a quality from -1(most dither) to 3(no dither). Default is 0.
IMWRITE_GIF_TRANSPARENCY = 1028, //!< For GIF, the alpha channel lower than this will be set to transparent. Default is 1.
IMWRITE_GIF_COLORTABLE = 1029 //!< For GIF, 0 means global color table is used, 1 means local color table is used. Default is 0.
};
};
enum ImwriteJPEGSamplingFactorParams {
IMWRITE_JPEG_SAMPLING_FACTOR_411 = 0x411111, //!< 4x1,1x1,1x1
@@ -407,6 +411,10 @@ can be saved using this function, with these exceptions:
- With Radiance HDR encoder, non 64-bit float (CV_64F) images can be saved.
- All images will be converted to 32-bit float (CV_32F).
- With JPEG 2000 encoder, 8-bit unsigned (CV_8U) and 16-bit unsigned (CV_16U) images can be saved.
- With JPEG XL encoder, 8-bit unsigned (CV_8U), 16-bit unsigned (CV_16U) and 32-bit float(CV_32F) images can be saved.
- JPEG XL images with an alpha channel can be saved using this function.
To do this, create 8-bit (or 16-bit, 32-bit float) 4-channel image BGRA, where the alpha channel goes last.
Fully transparent pixels should have alpha set to 0, fully opaque pixels should have alpha set to 255/65535/1.0.
- With PAM encoder, 8-bit unsigned (CV_8U) and 16-bit unsigned (CV_16U) images can be saved.
- With PNG encoder, 8-bit unsigned (CV_8U) and 16-bit unsigned (CV_16U) images can be saved.
- PNG images with an alpha channel can be saved using this function. To do this, create

View File

@@ -0,0 +1,420 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#include "precomp.hpp"
#include "grfmt_jpegxl.hpp"
#ifdef HAVE_JPEGXL
#include <jxl/encode_cxx.h>
#include <jxl/version.h>
#include <opencv2/core/utils/logger.hpp>
namespace cv
{
/////////////////////// JpegXLDecoder ///////////////////
JpegXLDecoder::JpegXLDecoder() : m_f(nullptr, &fclose)
{
m_signature = "\xFF\x0A";
m_decoder = nullptr;
m_buf_supported = false;
m_type = m_convert = -1;
m_status = JXL_DEC_NEED_MORE_INPUT;
}
JpegXLDecoder::~JpegXLDecoder()
{
close();
}
void JpegXLDecoder::close()
{
if (m_decoder)
m_decoder.release();
if (m_f)
m_f.release();
m_read_buffer = {};
m_width = m_height = 0;
m_type = m_convert = -1;
m_status = JXL_DEC_NEED_MORE_INPUT;
}
// see https://github.com/libjxl/libjxl/blob/v0.10.0/doc/format_overview.md
size_t JpegXLDecoder::signatureLength() const
{
return 12; // For an ISOBMFF-based container
}
bool JpegXLDecoder::checkSignature( const String& signature ) const
{
// A "naked" codestream.
if (
( signature.size() >= 2 ) &&
( memcmp( signature.c_str(), "\xFF\x0A", 2 ) == 0 )
)
{
return true;
}
// An ISOBMFF-based container.
// 0x0000_000C_4A58_4C20_0D0A_870A.
if (
( signature.size() >= 12 ) &&
( memcmp( signature.c_str(), "\x00\x00\x00\x0C\x4A\x58\x4C\x20\x0D\x0A\x87\x0A", 12 ) == 0 )
)
{
return true;
}
return false;
}
ImageDecoder JpegXLDecoder::newDecoder() const
{
return makePtr<JpegXLDecoder>();
}
bool JpegXLDecoder::read(Mat* pimg)
{
// Open file
if (!m_f) {
m_f.reset(fopen(m_filename.c_str(), "rb"));
if (!m_f)
return false;
}
// Initialize decoder
if (!m_decoder) {
m_decoder = JxlDecoderMake(nullptr);
if (!m_decoder)
return false;
// Subscribe to the basic info event
JxlDecoderStatus status = JxlDecoderSubscribeEvents(m_decoder.get(), JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE);
if (status != JXL_DEC_SUCCESS)
return false;
}
// Set up parallel m_parallel_runner
if (!m_parallel_runner) {
m_parallel_runner = JxlThreadParallelRunnerMake(nullptr, cv::getNumThreads());
if (JXL_DEC_SUCCESS != JxlDecoderSetParallelRunner(m_decoder.get(),
JxlThreadParallelRunner,
m_parallel_runner.get())) {
return false;
}
}
// Create buffer for reading
const size_t read_buffer_size = 16384; // 16KB chunks
if (m_read_buffer.capacity() < read_buffer_size)
m_read_buffer.resize(read_buffer_size);
// Create image if needed
if (m_type != -1 && pimg) {
pimg->create(m_height, m_width, m_type);
if (!pimg->isContinuous())
return false;
if (JXL_DEC_SUCCESS != JxlDecoderSetImageOutBuffer(m_decoder.get(),
&m_format,
pimg->ptr<uint8_t>(),
pimg->total() * pimg->elemSize())) {
return false;
}
}
// Start decoding loop
do {
// Check if we need more input
if (m_status == JXL_DEC_NEED_MORE_INPUT) {
size_t remaining = JxlDecoderReleaseInput(m_decoder.get());
// Move any remaining bytes to the beginning
if (remaining > 0)
memmove(m_read_buffer.data(), m_read_buffer.data() + m_read_buffer.size() - remaining, remaining);
// Read more data from file
size_t bytes_read = fread(m_read_buffer.data() + remaining,
1, m_read_buffer.size() - remaining, m_f.get());
if (bytes_read == 0) {
if (ferror(m_f.get())) {
CV_LOG_WARNING(NULL, "Error reading input file");
return false;
}
// If we reached EOF but decoder needs more input, file is truncated
if (m_status == JXL_DEC_NEED_MORE_INPUT) {
CV_LOG_WARNING(NULL, "Truncated JXL file");
return false;
}
}
// Set input buffer
if (JXL_DEC_SUCCESS != JxlDecoderSetInput(m_decoder.get(),
m_read_buffer.data(),
bytes_read + remaining)) {
return false;
}
}
// Get the next decoder status
m_status = JxlDecoderProcessInput(m_decoder.get());
// Handle different decoder states
switch (m_status) {
case JXL_DEC_BASIC_INFO: {
if (m_type != -1)
return false;
JxlBasicInfo info;
if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo(m_decoder.get(), &info))
return false;
// total channels (Color + Alpha)
const uint32_t ncn = info.num_color_channels + info.num_extra_channels;
m_width = info.xsize;
m_height = info.ysize;
m_format = {
ncn,
JXL_TYPE_UINT8, // (temporary)
JXL_LITTLE_ENDIAN, // endianness
0 // align stride to bytes
};
if (!m_use_rgb) {
switch (ncn) {
case 3:
m_convert = cv::COLOR_RGB2BGR;
break;
case 4:
m_convert = cv::COLOR_RGBA2BGRA;
break;
default:
m_convert = -1;
}
}
if (info.exponent_bits_per_sample > 0) {
m_format.data_type = JXL_TYPE_FLOAT;
m_type = CV_MAKETYPE( CV_32F, ncn );
} else {
switch (info.bits_per_sample) {
case 8:
m_format.data_type = JXL_TYPE_UINT8;
m_type = CV_MAKETYPE( CV_8U, ncn );
break;
case 16:
m_format.data_type = JXL_TYPE_UINT16;
m_type = CV_MAKETYPE( CV_16U, ncn );
break;
default:
return false;
}
}
if (!pimg)
return true;
break;
}
case JXL_DEC_FULL_IMAGE: {
// Image is ready
if (m_convert != -1)
cv::cvtColor(*pimg, *pimg, m_convert);
break;
}
case JXL_DEC_ERROR: {
close();
return false;
}
default:
break;
}
} while (m_status != JXL_DEC_SUCCESS);
return true;
}
bool JpegXLDecoder::readHeader()
{
close();
return read(nullptr);
}
bool JpegXLDecoder::readData(Mat& img)
{
if (!m_decoder || m_width == 0 || m_height == 0)
return false;
return read(&img);
}
/////////////////////// JpegXLEncoder ///////////////////
JpegXLEncoder::JpegXLEncoder()
{
m_description = "JPEG XL files (*.jxl)";
m_buf_supported = true;
}
JpegXLEncoder::~JpegXLEncoder()
{
}
ImageEncoder JpegXLEncoder::newEncoder() const
{
return makePtr<JpegXLEncoder>();
}
bool JpegXLEncoder::isFormatSupported( int depth ) const
{
return depth == CV_8U || depth == CV_16U || depth == CV_32F;
}
bool JpegXLEncoder::write(const Mat& img, const std::vector<int>& params)
{
m_last_error.clear();
JxlEncoderPtr encoder = JxlEncoderMake(nullptr);
if (!encoder)
return false;
JxlThreadParallelRunnerPtr runner = JxlThreadParallelRunnerMake(
/*memory_manager=*/nullptr, cv::getNumThreads());
if (JXL_ENC_SUCCESS != JxlEncoderSetParallelRunner(encoder.get(), JxlThreadParallelRunner, runner.get()))
return false;
CV_CheckDepth(img.depth(),
( img.depth() == CV_8U || img.depth() == CV_16U || img.depth() == CV_32F ),
"JPEG XL encoder only supports CV_8U, CV_16U, CV_32F");
CV_CheckChannels(img.channels(),
( img.channels() == 1 || img.channels() == 3 || img.channels() == 4) ,
"JPEG XL encoder only supports 1, 3, 4 channels");
WLByteStream strm;
if( m_buf ) {
if( !strm.open( *m_buf ) )
return false;
}
else if( !strm.open( m_filename )) {
return false;
}
JxlBasicInfo info;
JxlEncoderInitBasicInfo(&info);
info.xsize = img.cols;
info.ysize = img.rows;
info.uses_original_profile = JXL_FALSE;
if( img.channels() == 4 )
{
info.num_color_channels = 3;
info.num_extra_channels = 1;
info.bits_per_sample =
info.alpha_bits = 8 * static_cast<int>(img.elemSize1());
info.exponent_bits_per_sample =
info.alpha_exponent_bits = img.depth() == CV_32F ? 8 : 0;
}else{
info.num_color_channels = img.channels();
info.bits_per_sample = 8 * static_cast<int>(img.elemSize1());
info.exponent_bits_per_sample = img.depth() == CV_32F ? 8 : 0;
}
if (JxlEncoderSetBasicInfo(encoder.get(), &info) != JXL_ENC_SUCCESS)
return false;
JxlDataType type = JXL_TYPE_UINT8;
if (img.depth() == CV_32F)
type = JXL_TYPE_FLOAT;
else if (img.depth() == CV_16U)
type = JXL_TYPE_UINT16;
JxlPixelFormat format = {(uint32_t)img.channels(), type, JXL_NATIVE_ENDIAN, 0};
JxlColorEncoding color_encoding = {};
JXL_BOOL is_gray(format.num_channels < 3 ? JXL_TRUE : JXL_FALSE);
JxlColorEncodingSetToSRGB(&color_encoding, is_gray);
if (JXL_ENC_SUCCESS != JxlEncoderSetColorEncoding(encoder.get(), &color_encoding))
return false;
Mat image;
switch ( img.channels() ) {
case 3:
cv::cvtColor(img, image, cv::COLOR_BGR2RGB);
break;
case 4:
cv::cvtColor(img, image, cv::COLOR_BGRA2RGBA);
break;
case 1:
default:
if(img.isContinuous()) {
image = img;
} else {
image = img.clone(); // reconstruction as continuous image.
}
break;
}
if (!image.isContinuous())
return false;
JxlEncoderFrameSettings* frame_settings = JxlEncoderFrameSettingsCreate(encoder.get(), nullptr);
// 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<float>(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];
effort = MIN(MAX(effort, 1), 10);
JxlEncoderFrameSettingsSetOption(frame_settings, JXL_ENC_FRAME_SETTING_EFFORT, effort);
}
if( params[i] == IMWRITE_JPEGXL_DECODING_SPEED )
{
int speed = params[i+1];
speed = MIN(MAX(speed, 0), 4);
JxlEncoderFrameSettingsSetOption(frame_settings, JXL_ENC_FRAME_SETTING_DECODING_SPEED, speed);
}
}
if (JXL_ENC_SUCCESS !=
JxlEncoderAddImageFrame(frame_settings, &format,
static_cast<const void*>(image.ptr<uint8_t>()),
image.total() * image.elemSize())) {
return false;
}
JxlEncoderCloseInput(encoder.get());
const size_t buffer_size = 16384; // 16KB chunks
std::vector<uint8_t> compressed(buffer_size);
JxlEncoderStatus process_result = JXL_ENC_NEED_MORE_OUTPUT;
while (process_result == JXL_ENC_NEED_MORE_OUTPUT) {
uint8_t* next_out = compressed.data();
size_t avail_out = buffer_size;
process_result = JxlEncoderProcessOutput(encoder.get(), &next_out, &avail_out);
if (JXL_ENC_ERROR == process_result)
return false;
const size_t write_size = buffer_size - avail_out;
if ( strm.putBytes(compressed.data(), write_size) == false )
return false;
}
return true;
}
}
#endif
/* End of file. */

View File

@@ -0,0 +1,68 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html.
#ifndef _GRFMT_JPEGXL_H_
#define _GRFMT_JPEGXL_H_
#ifdef HAVE_JPEGXL
#include "grfmt_base.hpp"
#include <jxl/decode_cxx.h>
#include <jxl/thread_parallel_runner_cxx.h>
#include <vector>
#include <memory>
// Jpeg XL codec
namespace cv
{
/**
* @brief JpegXL codec using libjxl library
*/
class JpegXLDecoder CV_FINAL : public BaseImageDecoder
{
public:
JpegXLDecoder();
virtual ~JpegXLDecoder();
bool readData( Mat& img ) CV_OVERRIDE;
bool readHeader() CV_OVERRIDE;
void close();
size_t signatureLength() const CV_OVERRIDE;
bool checkSignature( const String& signature ) const CV_OVERRIDE;
ImageDecoder newDecoder() const CV_OVERRIDE;
protected:
std::unique_ptr<FILE, int (*)(FILE*)> m_f;
JxlDecoderPtr m_decoder;
JxlThreadParallelRunnerPtr m_parallel_runner;
JxlPixelFormat m_format;
int m_convert;
std::vector<uint8_t> m_read_buffer;
JxlDecoderStatus m_status;
private:
bool read(Mat* pimg);
};
class JpegXLEncoder CV_FINAL : public BaseImageEncoder
{
public:
JpegXLEncoder();
virtual ~JpegXLEncoder();
bool isFormatSupported( int depth ) const CV_OVERRIDE;
bool write( const Mat& img, const std::vector<int>& params ) CV_OVERRIDE;
ImageEncoder newEncoder() const CV_OVERRIDE;
};
}
#endif
#endif/*_GRFMT_JPEGXL_H_*/

View File

@@ -48,6 +48,7 @@
#include "grfmt_gif.hpp"
#include "grfmt_sunras.hpp"
#include "grfmt_jpeg.hpp"
#include "grfmt_jpegxl.hpp"
#include "grfmt_pxm.hpp"
#include "grfmt_pfm.hpp"
#include "grfmt_tiff.hpp"

View File

@@ -210,6 +210,10 @@ struct ImageCodecInitializer
decoders.push_back( makePtr<Jpeg2KDecoder>() );
encoders.push_back( makePtr<Jpeg2KEncoder>() );
#endif
#ifdef HAVE_JPEGXL
decoders.push_back( makePtr<JpegXLDecoder>() );
encoders.push_back( makePtr<JpegXLEncoder>() );
#endif
#ifdef HAVE_OPENJPEG
decoders.push_back( makePtr<Jpeg2KJP2OpjDecoder>() );
decoders.push_back( makePtr<Jpeg2KJ2KOpjDecoder>() );

View File

@@ -0,0 +1,186 @@
// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level
// directory of this distribution and at http://opencv.org/license.html
#include "test_precomp.hpp"
namespace opencv_test { namespace {
#ifdef HAVE_JPEGXL
typedef tuple<perf::MatType, int> MatType_and_Distance;
typedef testing::TestWithParam<MatType_and_Distance> Imgcodecs_JpegXL_MatType;
TEST_P(Imgcodecs_JpegXL_MatType, write_read)
{
const int matType = get<0>(GetParam());
const int distanceParam = get<1>(GetParam());
cv::Scalar col;
// Jpeg XL is lossy compression.
// There may be small differences in decoding results by environments.
double th;
switch( CV_MAT_DEPTH(matType) )
{
case CV_16U:
col = cv::Scalar(124 * 256, 76 * 256, 42 * 256, 192 * 256 );
th = 656; // = 65535 / 100;
break;
case CV_32F:
col = cv::Scalar(0.486, 0.298, 0.165, 0.75);
th = 1.0 / 100.0;
break;
default:
case CV_8U:
col = cv::Scalar(124, 76, 42, 192);
th = 3; // = 255 / 100 (1%);
break;
}
// If increasing distanceParam, threshold should be increased.
th *= (distanceParam >= 25) ? 5 : ( distanceParam > 2 ) ? 3 : (distanceParam == 2) ? 2: 1;
bool ret = false;
string tmp_fname = cv::tempfile(".jxl");
Mat img_org(320, 480, matType, col);
vector<int> param;
param.push_back(IMWRITE_JPEGXL_DISTANCE);
param.push_back(distanceParam);
EXPECT_NO_THROW(ret = imwrite(tmp_fname, img_org, param));
EXPECT_TRUE(ret);
Mat img_decoded;
EXPECT_NO_THROW(img_decoded = imread(tmp_fname, IMREAD_UNCHANGED));
EXPECT_FALSE(img_decoded.empty());
EXPECT_LE(cvtest::norm(img_org, img_decoded, NORM_INF), th);
EXPECT_EQ(0, remove(tmp_fname.c_str()));
}
TEST_P(Imgcodecs_JpegXL_MatType, encode_decode)
{
const int matType = get<0>(GetParam());
const int distanceParam = get<1>(GetParam());
cv::Scalar col;
// Jpeg XL is lossy compression.
// There may be small differences in decoding results by environments.
double th;
// If alpha=0, libjxl modify color channels(BGR). So do not set it.
switch( CV_MAT_DEPTH(matType) )
{
case CV_16U:
col = cv::Scalar(124 * 256, 76 * 256, 42 * 256, 192 * 256 );
th = 656; // = 65535 / 100;
break;
case CV_32F:
col = cv::Scalar(0.486, 0.298, 0.165, 0.75);
th = 1.0 / 100.0;
break;
default:
case CV_8U:
col = cv::Scalar(124, 76, 42, 192);
th = 3; // = 255 / 100 (1%);
break;
}
// If increasing distanceParam, threshold should be increased.
th *= (distanceParam >= 25) ? 5 : ( distanceParam > 2 ) ? 3 : (distanceParam == 2) ? 2: 1;
bool ret = false;
vector<uchar> buff;
Mat img_org(320, 480, matType, col);
vector<int> param;
param.push_back(IMWRITE_JPEGXL_DISTANCE);
param.push_back(distanceParam);
EXPECT_NO_THROW(ret = imencode(".jxl", img_org, buff, param));
EXPECT_TRUE(ret);
Mat img_decoded;
EXPECT_NO_THROW(img_decoded = imdecode(buff, IMREAD_UNCHANGED));
EXPECT_FALSE(img_decoded.empty());
EXPECT_LE(cvtest::norm(img_org, img_decoded, NORM_INF), th);
}
INSTANTIATE_TEST_CASE_P(
/**/,
Imgcodecs_JpegXL_MatType,
testing::Combine(
testing::Values(
CV_8UC1, CV_8UC3, CV_8UC4,
CV_16UC1, CV_16UC3, CV_16UC4,
CV_32FC1, CV_32FC3, CV_32FC4
),
testing::Values( // Distance
0, // Lossless
1, // Default
3, // Recomended Lossy Max
25 // Specification Max
)
) );
typedef tuple<int, int> Effort_and_Decoding_speed;
typedef testing::TestWithParam<Effort_and_Decoding_speed> Imgcodecs_JpegXL_Effort_DecodingSpeed;
TEST_P(Imgcodecs_JpegXL_Effort_DecodingSpeed, encode_decode)
{
const int effort = get<0>(GetParam());
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.
double th = 3; // = 255 / 100 (1%);
bool ret = false;
vector<uchar> buff;
Mat img_org(320, 480, CV_8UC3, col);
vector<int> param;
param.push_back(IMWRITE_JPEGXL_EFFORT);
param.push_back(effort);
param.push_back(IMWRITE_JPEGXL_DECODING_SPEED);
param.push_back(speed);
EXPECT_NO_THROW(ret = imencode(".jxl", img_org, buff, param));
EXPECT_TRUE(ret);
Mat img_decoded;
EXPECT_NO_THROW(img_decoded = imdecode(buff, IMREAD_UNCHANGED));
EXPECT_FALSE(img_decoded.empty());
EXPECT_LE(cvtest::norm(img_org, img_decoded, NORM_INF), th);
}
INSTANTIATE_TEST_CASE_P(
/**/,
Imgcodecs_JpegXL_Effort_DecodingSpeed,
testing::Combine(
testing::Values( // Effort
1, // fastest
7, // default
9 // slowest
),
testing::Values( // Decoding Speed
0, // default, slowest, and best quality/density
2,
4 // fastest, at the cost of some qulity/density
)
) );
TEST(Imgcodecs_JpegXL, encode_from_uncontinued_image)
{
cv::Mat src(100, 100, CV_8UC1, Scalar(40,50,10));
cv::Mat roi = src(cv::Rect(10,20,30,50));
EXPECT_FALSE(roi.isContinuous()); // uncontinued image
vector<uint8_t> buff;
vector<int> param;
bool ret = false;
EXPECT_NO_THROW(ret = cv::imencode(".jxl", roi, buff, param));
EXPECT_TRUE(ret);
}
#endif // HAVE_JPEGXL
} // namespace
} // namespace opencv_test

View File

@@ -157,6 +157,9 @@ const string exts[] = {
#ifdef HAVE_JPEG
"jpg",
#endif
#ifdef HAVE_JPEGXL
"jxl",
#endif
#if (defined(HAVE_JASPER) && defined(OPENCV_IMGCODECS_ENABLE_JASPER_TESTS)) \
|| defined(HAVE_OPENJPEG)
"jp2",
@@ -238,6 +241,8 @@ TEST_P(Imgcodecs_Image, read_write_BGR)
double psnrThreshold = 100;
if (ext == "jpg")
psnrThreshold = 32;
if (ext == "jxl")
psnrThreshold = 30;
#if defined(HAVE_JASPER)
if (ext == "jp2")
psnrThreshold = 95;
@@ -268,6 +273,8 @@ TEST_P(Imgcodecs_Image, read_write_GRAYSCALE)
double psnrThreshold = 100;
if (ext == "jpg")
psnrThreshold = 40;
if (ext == "jxl")
psnrThreshold = 40;
#if defined(HAVE_JASPER)
if (ext == "jp2")
psnrThreshold = 70;