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
pull/26695/head
cDc 4 months ago committed by GitHub
parent c803aa2ddd
commit 1db982780f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 7
      CMakeLists.txt
  2. 148
      cmake/OpenCVFindJPEGXL.cmake
  3. 11
      cmake/OpenCVFindLibsGrfmt.cmake
  4. 3
      cmake/templates/cvconfig.h.in
  5. 3
      doc/tutorials/introduction/config_reference/config_reference.markdown
  6. 3
      modules/highgui/src/window_w32.cpp
  7. 13
      modules/imgcodecs/CMakeLists.txt
  8. 10
      modules/imgcodecs/include/opencv2/imgcodecs.hpp
  9. 420
      modules/imgcodecs/src/grfmt_jpegxl.cpp
  10. 68
      modules/imgcodecs/src/grfmt_jpegxl.hpp
  11. 1
      modules/imgcodecs/src/grfmts.hpp
  12. 4
      modules/imgcodecs/src/loadsave.cpp
  13. 186
      modules/imgcodecs/test/test_jpegxl.cpp
  14. 7
      modules/imgcodecs/test/test_read_write.cpp

@ -306,6 +306,9 @@ OCV_OPTION(WITH_OPENJPEG "Include JPEG2K support (OpenJPEG)" ON
OCV_OPTION(WITH_JPEG "Include JPEG support" ON OCV_OPTION(WITH_JPEG "Include JPEG support" ON
VISIBLE_IF TRUE VISIBLE_IF TRUE
VERIFY HAVE_JPEG) VERIFY HAVE_JPEG)
OCV_OPTION(WITH_JPEGXL "Include JPEG XL support" OFF
VISIBLE_IF TRUE
VERIFY HAVE_JPEGXL)
OCV_OPTION(WITH_WEBP "Include WebP support" ON OCV_OPTION(WITH_WEBP "Include WebP support" ON
VISIBLE_IF NOT WINRT VISIBLE_IF NOT WINRT
VERIFY HAVE_WEBP) VERIFY HAVE_WEBP)
@ -1533,6 +1536,10 @@ if(WITH_TIFF OR HAVE_TIFF)
status(" TIFF:" TIFF_FOUND THEN "${TIFF_LIBRARY} (ver ${TIFF_VERSION} / ${TIFF_VERSION_STRING})" ELSE "build (ver ${TIFF_VERSION} - ${TIFF_VERSION_STRING})") status(" TIFF:" TIFF_FOUND THEN "${TIFF_LIBRARY} (ver ${TIFF_VERSION} / ${TIFF_VERSION_STRING})" ELSE "build (ver ${TIFF_VERSION} - ${TIFF_VERSION_STRING})")
endif() endif()
if(WITH_JPEGXL OR HAVE_JPEGXL)
status(" JPEG XL:" JPEGXL_FOUND THEN "${JPEGXL_LIBRARY} (ver ${JPEGXL_VERSION})" ELSE "NO")
endif()
if(HAVE_OPENJPEG) if(HAVE_OPENJPEG)
status(" JPEG 2000:" OpenJPEG_FOUND status(" JPEG 2000:" OpenJPEG_FOUND
THEN "OpenJPEG (ver ${OPENJPEG_VERSION})" THEN "OpenJPEG (ver ${OPENJPEG_VERSION})"

@ -0,0 +1,148 @@
# The script is taken from https://webkit.googlesource.com/WebKit/+/master/Source/cmake/FindJPEGXL.cmake
# Copyright (C) 2021 Sony Interactive Entertainment Inc.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
# THE POSSIBILITY OF SUCH DAMAGE.
#[=======================================================================[.rst:
FindJPEGXL
---------
Find JPEGXL headers and libraries.
Imported Targets
^^^^^^^^^^^^^^^^
``JPEGXL::jxl``
The JPEGXL library, if found.
Result Variables
^^^^^^^^^^^^^^^^
This will define the following variables in your project:
``JPEGXL_FOUND``
true if (the requested version of) JPEGXL is available.
``JPEGXL_VERSION``
the version of JPEGXL.
``JPEGXL_LIBRARIES``
the libraries to link against to use JPEGXL.
``JPEGXL_INCLUDE_DIRS``
where to find the JPEGXL headers.
``JPEGXL_COMPILE_OPTIONS``
this should be passed to target_compile_options(), if the
target is not used for linking
#]=======================================================================]
if(NOT OPENCV_SKIP_JPEGXL_FIND_PACKAGE)
find_package(PkgConfig QUIET)
if (PkgConfig_FOUND)
pkg_check_modules(PC_JPEGXL QUIET jxl)
set(JPEGXL_COMPILE_OPTIONS ${PC_JPEGXL_CFLAGS_OTHER})
set(JPEGXL_VERSION ${PC_JPEGXL_VERSION})
endif ()
find_path(JPEGXL_INCLUDE_DIR
NAMES jxl/decode.h
HINTS ${PC_JPEGXL_INCLUDEDIR} ${PC_JPEGXL_INCLUDE_DIRS} ${JPEGXL_INCLUDE_DIR}
)
find_library(JPEGXL_LIBRARY
NAMES ${JPEGXL_NAMES} jxl
HINTS ${PC_JPEGXL_LIBDIR} ${PC_JPEGXL_LIBRARY_DIRS}
)
find_library(JPEGXL_CMS_LIBRARY
NAMES ${JPEGXL_NAMES} jxl_cms
HINTS ${PC_JPEGXL_LIBDIR} ${PC_JPEGXL_LIBRARY_DIRS}
)
if (JPEGXL_LIBRARY AND JPEGXL_CMS_LIBRARY)
set(JPEGXL_LIBRARY ${JPEGXL_LIBRARY} ${JPEGXL_CMS_LIBRARY})
endif ()
find_library(JPEGXL_CMS2_LIBRARY
NAMES ${JPEGXL_NAMES} lcms2
HINTS ${PC_JPEGXL_LIBDIR} ${PC_JPEGXL_LIBRARY_DIRS}
)
if (JPEGXL_LIBRARY AND JPEGXL_CMS2_LIBRARY)
set(JPEGXL_LIBRARY ${JPEGXL_LIBRARY} ${JPEGXL_CMS2_LIBRARY})
endif ()
find_library(JPEGXL_THREADS_LIBRARY
NAMES ${JPEGXL_NAMES} jxl_threads
HINTS ${PC_JPEGXL_LIBDIR} ${PC_JPEGXL_LIBRARY_DIRS}
)
if (JPEGXL_LIBRARY AND JPEGXL_THREADS_LIBRARY)
set(JPEGXL_LIBRARY ${JPEGXL_LIBRARY} ${JPEGXL_THREADS_LIBRARY})
endif ()
find_library(JPEGXL_BROTLICOMMON_LIBRARY
NAMES ${JPEGXL_NAMES} brotlicommon
HINTS ${PC_JPEGXL_LIBDIR} ${PC_JPEGXL_LIBRARY_DIRS}
)
find_library(JPEGXL_BROTLIDEC_LIBRARY
NAMES ${JPEGXL_NAMES} brotlidec
HINTS ${PC_JPEGXL_LIBDIR} ${PC_JPEGXL_LIBRARY_DIRS}
)
if (JPEGXL_LIBRARY AND JPEGXL_BROTLIDEC_LIBRARY)
set(JPEGXL_LIBRARY ${JPEGXL_LIBRARY} ${JPEGXL_BROTLIDEC_LIBRARY})
endif ()
find_library(JPEGXL_BROTLIENC_LIBRARY
NAMES ${JPEGXL_NAMES} brotlienc
HINTS ${PC_JPEGXL_LIBDIR} ${PC_JPEGXL_LIBRARY_DIRS}
)
if (JPEGXL_LIBRARY AND JPEGXL_BROTLIENC_LIBRARY)
set(JPEGXL_LIBRARY ${JPEGXL_LIBRARY} ${JPEGXL_BROTLIENC_LIBRARY})
endif ()
if (JPEGXL_LIBRARY AND JPEGXL_BROTLICOMMON_LIBRARY)
set(JPEGXL_LIBRARY ${JPEGXL_LIBRARY} ${JPEGXL_BROTLICOMMON_LIBRARY})
endif ()
find_library(JPEGXL_HWY_LIBRARY
NAMES ${JPEGXL_NAMES} hwy
HINTS ${PC_JPEGXL_LIBDIR} ${PC_JPEGXL_LIBRARY_DIRS}
)
if (JPEGXL_LIBRARY AND JPEGXL_HWY_LIBRARY)
set(JPEGXL_LIBRARY ${JPEGXL_LIBRARY} ${JPEGXL_HWY_LIBRARY})
endif ()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(JPEGXL
FOUND_VAR JPEGXL_FOUND
REQUIRED_VARS JPEGXL_LIBRARY JPEGXL_INCLUDE_DIR
VERSION_VAR JPEGXL_VERSION
)
if (NOT EXISTS "${JPEGXL_INCLUDE_DIR}/jxl/version.h")
# the library version older 0.6 is not supported (no version.h file there)
set(JPEGXL_FOUND FALSE CACHE BOOL "libjxl found" FORCE)
message(STATUS "Ignored incompatible version of libjxl")
endif ()
if (JPEGXL_LIBRARY AND NOT TARGET JPEGXL::jxl)
add_library(JPEGXL::jxl UNKNOWN IMPORTED GLOBAL)
set_target_properties(JPEGXL::jxl PROPERTIES
IMPORTED_LOCATION "${JPEGXL_LIBRARY}"
INTERFACE_COMPILE_OPTIONS "${JPEGXL_COMPILE_OPTIONS}"
INTERFACE_INCLUDE_DIRECTORIES "${JPEGXL_INCLUDE_DIR}"
)
endif ()
mark_as_advanced(JPEGXL_INCLUDE_DIR JPEGXL_LIBRARY)
if (JPEGXL_FOUND)
set(JPEGXL_LIBRARIES ${JPEGXL_LIBRARY})
set(JPEGXL_INCLUDE_DIRS ${JPEGXL_INCLUDE_DIR})
if (NOT JPEGXL_VERSION)
file(READ "${JPEGXL_INCLUDE_DIR}/jxl/version.h" VERSION_HEADER_CONTENTS)
string(REGEX MATCH "#define JPEGXL_MAJOR_VERSION ([0-9]+)" _ ${VERSION_HEADER_CONTENTS})
set(JXL_VERSION_MAJOR ${CMAKE_MATCH_1})
string(REGEX MATCH "#define JPEGXL_MINOR_VERSION ([0-9]+)" _ ${VERSION_HEADER_CONTENTS})
set(JXL_VERSION_MINOR ${CMAKE_MATCH_1})
string(REGEX MATCH "#define JPEGXL_PATCH_VERSION ([0-9]+)" _ ${VERSION_HEADER_CONTENTS})
set(JXL_VERSION_PATCH ${CMAKE_MATCH_1})
set(JPEGXL_VERSION "${JXL_VERSION_MAJOR}.${JXL_VERSION_MINOR}.${JXL_VERSION_PATCH}")
endif()
endif ()
endif()

@ -226,6 +226,17 @@ if(NOT WEBP_VERSION AND WEBP_INCLUDE_DIR)
set(WEBP_VERSION "decoder: ${WEBP_DECODER_ABI_VERSION}, encoder: ${WEBP_ENCODER_ABI_VERSION}, demux: ${WEBP_DEMUX_ABI_VERSION}") set(WEBP_VERSION "decoder: ${WEBP_DECODER_ABI_VERSION}, encoder: ${WEBP_ENCODER_ABI_VERSION}, demux: ${WEBP_DEMUX_ABI_VERSION}")
endif() endif()
# --- libjxl (optional) ---
if(WITH_JPEGXL)
ocv_clear_vars(HAVE_JPEGXL)
ocv_clear_internal_cache_vars(JPEGXL_INCLUDE_PATHS JPEGXL_LIBRARIES JPEGXL_VERSION)
include("${OpenCV_SOURCE_DIR}/cmake/OpenCVFindJPEGXL.cmake")
if(JPEGXL_FOUND)
set(HAVE_JPEGXL YES)
message(STATUS "Found system JPEG-XL: ver ${JPEGXL_VERSION}")
endif()
endif()
# --- libopenjp2 (optional, check before libjasper) --- # --- libopenjp2 (optional, check before libjasper) ---
if(WITH_OPENJPEG) if(WITH_OPENJPEG)
if(BUILD_OPENJPEG) if(BUILD_OPENJPEG)

@ -78,6 +78,9 @@
/* IJG JPEG codec */ /* IJG JPEG codec */
#cmakedefine HAVE_JPEG #cmakedefine HAVE_JPEG
/* JPEG XL codec */
#cmakedefine HAVE_JPEGXL
/* GDCM DICOM codec */ /* GDCM DICOM codec */
#cmakedefine HAVE_GDCM #cmakedefine HAVE_GDCM

@ -310,11 +310,12 @@ Following formats can be read by OpenCV without help of any third-party library:
| [JPEG2000 with OpenJPEG](https://en.wikipedia.org/wiki/OpenJPEG) | `WITH_OPENJPEG` | _ON_ | `BUILD_OPENJPEG` | | [JPEG2000 with OpenJPEG](https://en.wikipedia.org/wiki/OpenJPEG) | `WITH_OPENJPEG` | _ON_ | `BUILD_OPENJPEG` |
| [JPEG2000 with JasPer](https://en.wikipedia.org/wiki/JasPer) | `WITH_JASPER` | _ON_ (see note) | `BUILD_JASPER` | | [JPEG2000 with JasPer](https://en.wikipedia.org/wiki/JasPer) | `WITH_JASPER` | _ON_ (see note) | `BUILD_JASPER` |
| [EXR](https://en.wikipedia.org/wiki/OpenEXR) | `WITH_OPENEXR` | _ON_ | `BUILD_OPENEXR` | | [EXR](https://en.wikipedia.org/wiki/OpenEXR) | `WITH_OPENEXR` | _ON_ | `BUILD_OPENEXR` |
| [JPEG XL](https://en.wikipedia.org/wiki/JPEG_XL) | `WITH_JPEGXL` | _ON_ | Not supported. (see note) |
All libraries required to read images in these formats are included into OpenCV and will be built automatically if not found at the configuration stage. Corresponding `BUILD_*` options will force building and using own libraries, they are enabled by default on some platforms, e.g. Windows. All libraries required to read images in these formats are included into OpenCV and will be built automatically if not found at the configuration stage. Corresponding `BUILD_*` options will force building and using own libraries, they are enabled by default on some platforms, e.g. Windows.
@note OpenJPEG have higher priority than JasPer which is deprecated. In order to use JasPer, OpenJPEG must be disabled. @note OpenJPEG have higher priority than JasPer which is deprecated. In order to use JasPer, OpenJPEG must be disabled.
@note (JPEG XL) OpenCV doesn't contain libjxl source code, so `BUILD_JPEGXL` is not supported.
### GDAL integration ### GDAL integration

@ -2158,6 +2158,9 @@ static void showSaveDialog(CvWindow& window)
#ifdef HAVE_JPEG #ifdef HAVE_JPEG
"JPEG files (*.jpeg;*.jpg;*.jpe)\0*.jpeg;*.jpg;*.jpe\0" "JPEG files (*.jpeg;*.jpg;*.jpe)\0*.jpeg;*.jpg;*.jpe\0"
#endif #endif
#ifdef HAVE_JPEGXL
"JPEG XL files (*.jxl)\0*.jxl\0"
#endif
#ifdef HAVE_TIFF #ifdef HAVE_TIFF
"TIFF Files (*.tiff;*.tif)\0*.tiff;*.tif\0" "TIFF Files (*.tiff;*.tif)\0*.tiff;*.tif\0"
#endif #endif

@ -23,6 +23,11 @@ if(HAVE_JPEG)
list(APPEND GRFMT_LIBS ${JPEG_LIBRARIES}) list(APPEND GRFMT_LIBS ${JPEG_LIBRARIES})
endif() endif()
if(HAVE_JPEGXL)
ocv_include_directories(${OPENJPEG_INCLUDE_DIRS})
list(APPEND GRFMT_LIBS ${OPENJPEG_LIBRARIES})
endif()
if(HAVE_WEBP) if(HAVE_WEBP)
add_definitions(-DHAVE_WEBP) add_definitions(-DHAVE_WEBP)
ocv_include_directories(${WEBP_INCLUDE_DIR}) ocv_include_directories(${WEBP_INCLUDE_DIR})
@ -51,6 +56,12 @@ if(HAVE_TIFF)
list(APPEND GRFMT_LIBS ${TIFF_LIBRARIES}) list(APPEND GRFMT_LIBS ${TIFF_LIBRARIES})
endif() 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) if(HAVE_OPENJPEG)
ocv_include_directories(${OPENJPEG_INCLUDE_DIRS}) ocv_include_directories(${OPENJPEG_INCLUDE_DIRS})
list(APPEND GRFMT_LIBS ${OPENJPEG_LIBRARIES}) list(APPEND GRFMT_LIBS ${OPENJPEG_LIBRARIES})
@ -78,7 +89,7 @@ if(HAVE_OPENEXR)
endif() endif()
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}) ocv_include_directories(${ZLIB_INCLUDE_DIRS})
list(APPEND GRFMT_LIBS ${ZLIB_LIBRARIES}) list(APPEND GRFMT_LIBS ${ZLIB_LIBRARIES})
endif() endif()

@ -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_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_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_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_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_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_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_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_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. 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 { enum ImwriteJPEGSamplingFactorParams {
IMWRITE_JPEG_SAMPLING_FACTOR_411 = 0x411111, //!< 4x1,1x1,1x1 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. - 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). - 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 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 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. - 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 - PNG images with an alpha channel can be saved using this function. To do this, create

@ -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. */

@ -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_*/

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

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

@ -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

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

Loading…
Cancel
Save