mirror of https://github.com/opencv/opencv.git
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