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 CMakepull/26695/head
parent
c803aa2ddd
commit
1db982780f
14 changed files with 881 additions and 3 deletions
@ -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() |
@ -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_*/ |
@ -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
|
Loading…
Reference in new issue