Merge pull request #26788 from Kumataro:fix26767

jpegxl: support cv::IMREAD_UNCHANGED and other ImreadFlags #26788

Close https://github.com/opencv/opencv/issues/26767

### 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
This commit is contained in:
Kumataro 2025-01-22 16:50:43 +09:00 committed by GitHub
parent db962ea069
commit ea023b72ce
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 343 additions and 69 deletions

View File

@ -12,6 +12,19 @@
namespace cv
{
// Callback functions for JpegXLDecoder
static void cbRGBtoBGR_8U(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels);
static void cbRGBAtoBGRA_8U(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels);
static void cbRGBtoBGR_16U(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels);
static void cbRGBAtoBGRA_16U(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels);
static void cbRGBtoBGR_32F(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels);
static void cbRGBAtoBGRA_32F(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels);
static void cbRGBtoGRAY_8U(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels);
static void cbRGBAtoGRAY_8U(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels);
static void cbRGBtoGRAY_16U(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels);
static void cbRGBAtoGRAY_16U(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels);
static void cbRGBtoGRAY_32F(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels);
static void cbRGBAtoGRAY_32F(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels);
/////////////////////// JpegXLDecoder ///////////////////
@ -20,7 +33,7 @@ JpegXLDecoder::JpegXLDecoder() : m_f(nullptr, &fclose)
m_signature = "\xFF\x0A";
m_decoder = nullptr;
m_buf_supported = false;
m_type = m_convert = -1;
m_type = -1;
m_status = JXL_DEC_NEED_MORE_INPUT;
}
@ -37,7 +50,7 @@ void JpegXLDecoder::close()
m_f.reset();
m_read_buffer = {};
m_width = m_height = 0;
m_type = m_convert = -1;
m_type = -1;
m_status = JXL_DEC_NEED_MORE_INPUT;
}
@ -76,7 +89,7 @@ ImageDecoder JpegXLDecoder::newDecoder() const
return makePtr<JpegXLDecoder>();
}
bool JpegXLDecoder::read(Mat* pimg)
bool JpegXLDecoder::readHeader()
{
// Open file
if (!m_f) {
@ -106,24 +119,87 @@ bool JpegXLDecoder::read(Mat* pimg)
}
}
return read();
}
bool JpegXLDecoder::readData(Mat& img)
{
if (!m_decoder || m_width == 0 || m_height == 0 || m_type == -1)
return false;
// Prepare to decode image
const uint32_t scn = CV_MAT_CN(m_type); // from image
const uint32_t dcn = (uint32_t)img.channels(); // to OpenCV
const int depth = CV_MAT_DEPTH(img.type());
JxlImageOutCallback cbFunc = nullptr;
CV_CheckChannels(scn, (scn == 1 || scn == 3 || scn == 4), "Unsupported src channels");
CV_CheckChannels(dcn, (dcn == 1 || dcn == 3 || dcn == 4), "Unsupported dst channels");
CV_CheckDepth(depth, (depth == CV_8U || depth == CV_16U || depth == CV_32F), "Unsupported depth");
m_format = {
dcn,
JXL_TYPE_UINT8, // (temporary)
JXL_NATIVE_ENDIAN, // endianness
0 // align stride to bytes
};
switch (depth) {
case CV_8U: m_format.data_type = JXL_TYPE_UINT8; break;
case CV_16U: m_format.data_type = JXL_TYPE_UINT16; break;
case CV_32F: m_format.data_type = JXL_TYPE_FLOAT; break;
default: break;
}
// libjxl cannot read to BGR pixel order directly.
// So we have to use callback function to convert from RGB(A) to BGR(A).
if (!m_use_rgb) {
switch (dcn) {
case 1: break;
case 3: cbFunc = (depth == CV_32F)? cbRGBtoBGR_32F: (depth == CV_16U)? cbRGBtoBGR_16U: cbRGBtoBGR_8U; break;
case 4: cbFunc = (depth == CV_32F)? cbRGBAtoBGRA_32F: (depth == CV_16U)? cbRGBAtoBGRA_16U: cbRGBAtoBGRA_8U; break;
default: break;
}
}
// libjxl cannot convert from color image to gray image directly.
// So we have to use callback function to convert from RGB(A) to GRAY.
if( (scn >= 3) && (dcn == 1) )
{
m_format.num_channels = scn;
switch (scn) {
case 3: cbFunc = (depth == CV_32F)? cbRGBtoGRAY_32F: (depth == CV_16U)? cbRGBtoGRAY_16U: cbRGBtoGRAY_8U; break;
case 4: cbFunc = (depth == CV_32F)? cbRGBAtoGRAY_32F: (depth == CV_16U)? cbRGBAtoGRAY_16U: cbRGBAtoGRAY_8U; break;
default: break;
}
}
if(cbFunc != nullptr)
{
if (JXL_DEC_SUCCESS != JxlDecoderSetImageOutCallback(m_decoder.get(),
&m_format,
cbFunc,
static_cast<void*>(&img)))
{
return false;
}
}else{
if (JXL_DEC_SUCCESS != JxlDecoderSetImageOutBuffer(m_decoder.get(),
&m_format,
img.ptr<uint8_t>(),
img.total() * img.elemSize()))
{
return false;
}
}
return read();
}
// Common reading routine for readHeader() and readBody()
bool JpegXLDecoder::read()
{
// 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
@ -163,6 +239,7 @@ bool JpegXLDecoder::read(Mat* pimg)
case JXL_DEC_BASIC_INFO: {
if (m_type != -1)
return false;
JxlBasicInfo info;
if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo(m_decoder.get(), &info))
return false;
@ -172,49 +249,18 @@ bool JpegXLDecoder::read(Mat* pimg)
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;
}
int depth = (info.exponent_bits_per_sample > 0)?CV_32F:
(info.bits_per_sample == 16)?CV_16U:
(info.bits_per_sample == 8)?CV_8U: -1;
if(depth == -1)
{
return false; // Return to readHeader()
}
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;
m_type = CV_MAKETYPE( depth, ncn );
return true;
}
case JXL_DEC_FULL_IMAGE: {
// Image is ready
if (m_convert != -1)
cv::cvtColor(*pimg, *pimg, m_convert);
break;
}
case JXL_DEC_ERROR: {
@ -229,17 +275,172 @@ bool JpegXLDecoder::read(Mat* pimg)
return true;
}
bool JpegXLDecoder::readHeader()
// Callback functopms
static void cbRGBtoBGR_8U(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels)
{
close();
return read(nullptr);
const uint8_t* src = static_cast<const uint8_t*>(pixels);
constexpr int dstStep = 3;
const cv::Mat *pDst = static_cast<cv::Mat*>(opaque);
uint8_t* dstBase = const_cast<uint8_t*>(pDst->ptr(y));
uint8_t* dst = dstBase + x * dstStep;
icvCvt_RGB2BGR_8u_C3R( src, 0, dst, 0, Size(num_pixels , 1) );
}
static void cbRGBAtoBGRA_8U(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels)
{
const uint8_t* src = static_cast<const uint8_t*>(pixels);
constexpr int dstStep = 4;
const cv::Mat *pDst = static_cast<cv::Mat*>(opaque);
uint8_t* dstBase = const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(pDst->ptr(y)));
uint8_t* dst = dstBase + x * dstStep;
icvCvt_RGBA2BGRA_8u_C4R( src, 0, dst, 0, Size(num_pixels, 1) );
}
static void cbRGBtoBGR_16U(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels)
{
const uint16_t* src = static_cast<const uint16_t*>(pixels);
constexpr int dstStep = 3;
const cv::Mat *pDst = static_cast<cv::Mat*>(opaque);
uint16_t* dstBase = const_cast<uint16_t*>(reinterpret_cast<const uint16_t*>(pDst->ptr(y)));
uint16_t* dst = dstBase + x * dstStep;
icvCvt_BGR2RGB_16u_C3R( src, 0, dst, 0, Size(num_pixels, 1));
}
static void cbRGBAtoBGRA_16U(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels)
{
const uint16_t* src = static_cast<const uint16_t*>(pixels);
constexpr int dstStep = 4;
const cv::Mat *pDst = static_cast<cv::Mat*>(opaque);
uint16_t* dstBase = const_cast<uint16_t*>(reinterpret_cast<const uint16_t*>(pDst->ptr(y)));
uint16_t* dst = dstBase + x * dstStep;
icvCvt_BGRA2RGBA_16u_C4R( src, 0, dst, 0, Size(num_pixels, 1));
}
static void cbRGBtoBGR_32F(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels)
{
constexpr int srcStep = 3;
const uint32_t* src = static_cast<const uint32_t*>(pixels);
constexpr int dstStep = 3;
const cv::Mat *pDst = static_cast<cv::Mat*>(opaque);
uint32_t* dstBase = const_cast<uint32_t*>(reinterpret_cast<const uint32_t*>(pDst->ptr(y)));
uint32_t* dst = dstBase + x * dstStep;
for(size_t i = 0 ; i < num_pixels; i++)
{
dst[ i * dstStep + 0 ] = src[ i * srcStep + 2];
dst[ i * dstStep + 1 ] = src[ i * srcStep + 1];
dst[ i * dstStep + 2 ] = src[ i * srcStep + 0];
}
}
static void cbRGBAtoBGRA_32F(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels)
{
constexpr int srcStep = 4;
const uint32_t* src = static_cast<const uint32_t*>(pixels);
constexpr int dstStep = 4;
const cv::Mat *pDst = static_cast<cv::Mat*>(opaque);
uint32_t* dstBase = const_cast<uint32_t*>(reinterpret_cast<const uint32_t*>(pDst->ptr(y)));
uint32_t* dst = dstBase + x * dstStep;
for(size_t i = 0 ; i < num_pixels; i++)
{
dst[ i * dstStep + 0 ] = src[ i * srcStep + 2];
dst[ i * dstStep + 1 ] = src[ i * srcStep + 1];
dst[ i * dstStep + 2 ] = src[ i * srcStep + 0];
dst[ i * dstStep + 3 ] = src[ i * srcStep + 3];
}
}
bool JpegXLDecoder::readData(Mat& img)
static void cbRGBtoGRAY_8U(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels)
{
if (!m_decoder || m_width == 0 || m_height == 0)
return false;
return read(&img);
const uint8_t* src = static_cast<const uint8_t*>(pixels);
constexpr int dstStep = 1;
const cv::Mat *pDst = static_cast<cv::Mat*>(opaque);
uint8_t* dstBase = const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(pDst->ptr(y)));
uint8_t* dst = dstBase + x * dstStep;
icvCvt_BGR2Gray_8u_C3C1R(src, 0, dst, 0, Size(num_pixels, 1) );
}
static void cbRGBAtoGRAY_8U(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels)
{
const uint8_t* src = static_cast<const uint8_t*>(pixels);
constexpr int dstStep = 1;
const cv::Mat *pDst = static_cast<cv::Mat*>(opaque);
uint8_t* dstBase = const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(pDst->ptr(y)));
uint8_t* dst = dstBase + x * dstStep;
icvCvt_BGRA2Gray_8u_C4C1R(src, 0, dst, 0, Size(num_pixels, 1) );
}
static void cbRGBtoGRAY_16U(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels)
{
const uint16_t* src = static_cast<const uint16_t*>(pixels);
constexpr int dstStep = 1;
const cv::Mat *pDst = static_cast<cv::Mat*>(opaque);
uint16_t* dstBase = const_cast<uint16_t*>(reinterpret_cast<const uint16_t*>(pDst->ptr(y)));
uint16_t* dst = dstBase + x * dstStep;
icvCvt_BGRA2Gray_16u_CnC1R(src, 0, dst, 0, Size(num_pixels, 1), /* ncn= */ 3 );
}
static void cbRGBAtoGRAY_16U(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels)
{
const uint16_t* src = static_cast<const uint16_t*>(pixels);
constexpr int dstStep = 1;
const cv::Mat *pDst = static_cast<cv::Mat*>(opaque);
uint16_t* dstBase = const_cast<uint16_t*>(reinterpret_cast<const uint16_t*>(pDst->ptr(y)));
uint16_t* dst = dstBase + x * dstStep;
icvCvt_BGRA2Gray_16u_CnC1R(src, 0, dst, 0, Size(num_pixels, 1), /* ncn= */ 4 );
}
static void cbRGBtoGRAY_32F(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels)
{
constexpr float cR = 0.299f;
constexpr float cG = 0.587f;
constexpr float cB = 1.000f - cR - cG;
constexpr int srcStep = 3;
const float* src = static_cast<const float*>(pixels);
constexpr int dstStep = 1;
const cv::Mat *pDst = static_cast<cv::Mat*>(opaque);
float* dstBase = const_cast<float*>(reinterpret_cast<const float*>(pDst->ptr(y)));
float* dst = dstBase + x * dstStep;
for(size_t i = 0 ; i < num_pixels; i++)
{
dst[ i * dstStep ] = src[ i * srcStep + 0] * cR +
src[ i * srcStep + 1] * cG +
src[ i * srcStep + 2] * cB;
}
}
static void cbRGBAtoGRAY_32F(void *opaque, size_t x, size_t y, size_t num_pixels, const void *pixels)
{
constexpr float cR = 0.299f;
constexpr float cG = 0.587f;
constexpr float cB = 1.000f - cR - cG;
constexpr int srcStep = 4;
const float* src = static_cast<const float*>(pixels);
constexpr int dstStep = 1;
const cv::Mat *pDst = static_cast<cv::Mat*>(opaque);
float* dstBase = const_cast<float*>(reinterpret_cast<const float*>(pDst->ptr(y)));
float* dst = dstBase + x * dstStep;
for(size_t i = 0 ; i < num_pixels; i++)
{
dst[ i * dstStep ] = src[ i * srcStep + 0] * cR +
src[ i * srcStep + 1] * cG +
src[ i * srcStep + 2] * cB;
}
}
/////////////////////// JpegXLEncoder ///////////////////

View File

@ -41,12 +41,11 @@ protected:
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);
bool read();
};

View File

@ -180,6 +180,75 @@ TEST(Imgcodecs_JpegXL, encode_from_uncontinued_image)
EXPECT_TRUE(ret);
}
// See https://github.com/opencv/opencv/issues/26767
typedef tuple<perf::MatType, ImreadModes> MatType_and_ImreadFlag;
typedef testing::TestWithParam<MatType_and_ImreadFlag> Imgcodecs_JpegXL_MatType_ImreadFlag;
TEST_P(Imgcodecs_JpegXL_MatType_ImreadFlag, all_imreadFlags)
{
string tmp_fname = cv::tempfile(".jxl");
const int matType = get<0>(GetParam());
const int imreadFlag = get<1>(GetParam());
Mat img(240, 320, matType);
randu(img, Scalar(0, 0, 0, 255), Scalar(255, 255, 255, 255));
vector<int> param;
param.push_back(IMWRITE_JPEGXL_DISTANCE);
param.push_back(0 /* Lossless */);
EXPECT_NO_THROW(imwrite(tmp_fname, img, param));
Mat img_decoded;
EXPECT_NO_THROW(img_decoded = imread(tmp_fname, imreadFlag));
EXPECT_FALSE(img_decoded.empty());
switch( imreadFlag )
{
case IMREAD_UNCHANGED:
EXPECT_EQ( img.type(), img_decoded.type() );
break;
case IMREAD_GRAYSCALE:
EXPECT_EQ( img_decoded.depth(), CV_8U );
EXPECT_EQ( img_decoded.channels(), 1 );
break;
case IMREAD_COLOR:
case IMREAD_COLOR_RGB:
EXPECT_EQ( img_decoded.depth(), CV_8U );
EXPECT_EQ( img_decoded.channels(), 3 );
break;
case IMREAD_ANYDEPTH:
EXPECT_EQ( img_decoded.depth(), img.depth() );
EXPECT_EQ( img_decoded.channels(), 1 );
break;
case IMREAD_ANYCOLOR:
EXPECT_EQ( img_decoded.depth(), CV_8U ) ;
EXPECT_EQ( img_decoded.channels(), img.channels() == 1 ? 1 : 3 ); // Alpha channel will be dropped.
break;
}
remove(tmp_fname.c_str());
}
INSTANTIATE_TEST_CASE_P(
/**/,
Imgcodecs_JpegXL_MatType_ImreadFlag,
testing::Combine(
testing::Values(
CV_8UC1, CV_8UC3, CV_8UC4,
CV_16UC1, CV_16UC3, CV_16UC4,
CV_32FC1, CV_32FC3, CV_32FC4
),
testing::Values(
IMREAD_UNCHANGED,
IMREAD_GRAYSCALE,
IMREAD_COLOR,
IMREAD_COLOR_RGB,
IMREAD_ANYDEPTH,
IMREAD_ANYCOLOR
)
) );
#endif // HAVE_JPEGXL
} // namespace

View File

@ -25,6 +25,16 @@ void PrintTo(const ImreadModes& val, std::ostream* os)
v &= ~IMREAD_COLOR;
*os << "IMREAD_COLOR" << (v == 0 ? "" : " | ");
}
else if ((v & IMREAD_COLOR_RGB) != 0)
{
CV_Assert(IMREAD_COLOR_RGB == 256);
v &= ~IMREAD_COLOR_RGB;
*os << "IMREAD_COLOR_RGB" << (v == 0 ? "" : " | ");
}
else if ((v & IMREAD_ANYCOLOR) != 0)
{
// Do nothing
}
else
{
CV_Assert(IMREAD_GRAYSCALE == 0);
@ -50,11 +60,6 @@ void PrintTo(const ImreadModes& val, std::ostream* os)
v &= ~IMREAD_IGNORE_ORIENTATION;
*os << "IMREAD_IGNORE_ORIENTATION" << (v == 0 ? "" : " | ");
}
if ((v & IMREAD_COLOR_RGB) != 0)
{
v &= ~IMREAD_COLOR_RGB;
*os << "IMREAD_COLOR_RGB" << (v == 0 ? "" : " | ");
}
switch (v)
{
case IMREAD_UNCHANGED: return;