imgcodecs(avif): support 16-bit reading/writing with libavif v1.4.0+ (5.x)

This commit is contained in:
Kumataro committed 2026-10-21 09:21:33 +09:00
1 parent 7b700ba545
commit 905fa09e04
7 files changed
+125 -16

No files matched your search

+1 -5
View File
@@ -1502,11 +1502,7 @@ if(WITH_WEBP OR HAVE_WEBP)
endif()
if(WITH_AVIF OR HAVE_AVIF)
if(libavif_VERSION)
status(" AVIF:" AVIF_FOUND THEN "${AVIF_LIBRARY} (ver ${libavif_VERSION})" ELSE "NO")
else()
status(" AVIF:" AVIF_FOUND THEN "${AVIF_LIBRARY}" ELSE "NO")
endif()
status(" AVIF:" AVIF_FOUND THEN "${AVIF_LIBRARY} (ver ${AVIF_VERSION})" ELSE "NO")
endif()
if(WITH_SPNG)
+37 -2
View File
@@ -6,6 +6,7 @@
# AVIF_INCLUDE_DIRS - where to find avif/avif.h, etc.
# AVIF_LIBRARIES - List of libraries when using AVIF.
# AVIF_FOUND - True if AVIF is found.
# AVIF_VERSION - libavif version string (e.g. "1.4.0")
#=============================================================================
# Look for the header file.
@@ -40,7 +41,41 @@ else()
# all listed variables are TRUE
INCLUDE(${CMAKE_ROOT}/Modules/FindPackageHandleStandardArgs.cmake)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(AVIF DEFAULT_MSG AVIF_LIBRARY AVIF_INCLUDE_DIR)
endif()
SET(AVIF_LIBRARIES ${AVIF_LIBRARY})
SET(AVIF_INCLUDE_DIRS ${AVIF_INCLUDE_DIR})
if(NOT AVIF_FOUND)
UNSET(AVIF_LIBRARIES)
UNSET(AVIF_INCLUDE_DIRS)
UNSET(AVIF_VERSION)
return()
endif()
SET(AVIF_LIBRARIES ${AVIF_LIBRARY})
SET(AVIF_INCLUDE_DIRS ${AVIF_INCLUDE_DIR})
# unset(libavif_VERSION) # For debugging: uncomment to test fallback version extraction when find_package(libavif) cannot find libavif
if(NOT libavif_VERSION)
find_file(AVIF_H NAMES avif/avif.h PATHS ${AVIF_INCLUDE_DIRS} NO_DEFAULT_PATH)
if(AVIF_H)
file(STRINGS "${AVIF_H}" AVIF_MAJOR_LINE REGEX "^#define[ \t]+AVIF_VERSION_MAJOR[ \t]+[0-9]+")
file(STRINGS "${AVIF_H}" AVIF_MINOR_LINE REGEX "^#define[ \t]+AVIF_VERSION_MINOR[ \t]+[0-9]+")
file(STRINGS "${AVIF_H}" AVIF_PATCH_LINE REGEX "^#define[ \t]+AVIF_VERSION_PATCH[ \t]+[0-9]+")
string(REGEX REPLACE ".*AVIF_VERSION_MAJOR[ \t]+([0-9]+).*" "\\1" AVIF_MAJOR "${AVIF_MAJOR_LINE}")
string(REGEX REPLACE ".*AVIF_VERSION_MINOR[ \t]+([0-9]+).*" "\\1" AVIF_MINOR "${AVIF_MINOR_LINE}")
string(REGEX REPLACE ".*AVIF_VERSION_PATCH[ \t]+([0-9]+).*" "\\1" AVIF_PATCH "${AVIF_PATCH_LINE}")
if(AVIF_MAJOR MATCHES "^[0-9]+$" AND AVIF_MINOR MATCHES "^[0-9]+$" AND AVIF_PATCH MATCHES "^[0-9]+$")
SET(libavif_VERSION "${AVIF_MAJOR}.${AVIF_MINOR}.${AVIF_PATCH}")
endif()
endif()
UNSET(AVIF_H CACHE)
UNSET(AVIF_H)
endif()
if(libavif_VERSION)
SET(AVIF_VERSION "${libavif_VERSION}")
endif()
+1 -1
View File
@@ -60,7 +60,7 @@ endif()
# --- libavif (optional) ---
if(WITH_AVIF)
ocv_clear_internal_cache_vars(AVIF_LIBRARY AVIF_INCLUDE_DIR)
ocv_clear_internal_cache_vars(AVIF_LIBRARIES AVIF_INCLUDE_DIRS)
include(cmake/OpenCVFindAVIF.cmake)
if(AVIF_FOUND)
set(HAVE_AVIF 1)
+5 -2
View File
@@ -14,8 +14,11 @@ if(HAVE_WINRT_CX AND NOT WINRT)
endif()
if (HAVE_AVIF)
ocv_include_directories(${AVIF_INCLUDE_DIR})
list(APPEND GRFMT_LIBS ${AVIF_LIBRARY})
ocv_include_directories(${AVIF_INCLUDE_DIRS})
list(APPEND GRFMT_LIBS ${AVIF_LIBRARIES})
if(NOT (AVIF_VERSION VERSION_LESS "1.4.0")) # libavif v1.4.0+ supports 16 bit images.
add_definitions(-DOPENCV_IMGCODECS_AVIF_SUPPORT_16BIT=1)
endif()
endif()
if(HAVE_JPEG)
@@ -115,7 +115,7 @@ enum ImwriteFlags {
IMWRITE_TIFF_PREDICTOR = 317,//!< For TIFF, use to specify predictor. See cv::ImwriteTiffPredictorFlags. Default is IMWRITE_TIFF_PREDICTOR_HORIZONTAL .
IMWRITE_JPEG2000_COMPRESSION_X1000 = 272,//!< For JPEG2000, use to specify the target compression rate (multiplied by 1000). The value can be from 0 to 1000. Default is 1000.
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_16U. Default is 8.
IMWRITE_AVIF_DEPTH = 513,//!< For AVIF, it can be 8, 10, 12 or 16. 16 requires libavif v1.4.0 or later. If >8, it is stored/read as CV_16U. Default is 8.
IMWRITE_AVIF_SPEED = 514,//!< For AVIF, it is between 0 (slowest) and 10(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.
+36 -5
View File
@@ -28,6 +28,23 @@
namespace cv {
namespace {
static bool isSupportedBitDepth16U(int requestDepth)
{
if (requestDepth == 10 || requestDepth == 12)
{
return true;
}
#ifdef OPENCV_IMGCODECS_AVIF_SUPPORT_16BIT
if (requestDepth == 16)
{
return true;
}
#endif
return false;
}
struct AvifImageDeleter {
void operator()(avifImage *image) { avifImageDestroy(image); }
};
@@ -216,6 +233,11 @@ bool AvifDecoder::readHeader() {
return false;
}
decoder_->strictFlags = AVIF_STRICT_DISABLED;
#ifdef OPENCV_IMGCODECS_AVIF_SUPPORT_16BIT
decoder_->imageContentToDecode |= AVIF_IMAGE_CONTENT_SAMPLE_TRANSFORMS;
#endif
if (!m_buf.empty()) {
CV_Assert(m_buf.type() == CV_8UC1);
CV_Assert(m_buf.rows == 1);
@@ -241,7 +263,7 @@ bool AvifDecoder::readHeader() {
channels_ = (decoder_->image->yuvFormat == AVIF_PIXEL_FORMAT_YUV400) ? 1 : 3;
if (decoder_->alphaPresent) ++channels_;
bit_depth_ = decoder_->image->depth;
CV_Assert(bit_depth_ == 8 || bit_depth_ == 10 || bit_depth_ == 12);
CV_Assert(bit_depth_ == 8 || isSupportedBitDepth16U(bit_depth_));
m_type = CV_MAKETYPE(bit_depth_ == 8 ? CV_8U : CV_16U, channels_);
is_first_image_ = true;
return true;
@@ -360,10 +382,10 @@ bool AvifEncoder::writeanimation(const Animation& animation,
#endif
} else if (params[i] == IMWRITE_AVIF_DEPTH) {
bit_depth = value;
if ((bit_depth != 8) && (bit_depth !=10) && (bit_depth !=12))
if ((bit_depth != 8) && (!isSupportedBitDepth16U(bit_depth)))
{
bit_depth = 8;
CV_LOG_WARNING(nullptr, cv::format("The value(%d) for IMWRITE_AVIF_DEPTH must be 8, 10 or 12. It is fallbacked to %d", value, bit_depth));
CV_LOG_WARNING(nullptr, cv::format("The value(%d) for IMWRITE_AVIF_DEPTH must be 8, 10, 12 (or 16 with libavif v1.4.0+). It is fallbacked to %d", value, bit_depth));
}
} else if (params[i] == IMWRITE_AVIF_SPEED) {
speed = std::min(std::max(value,0),10);
@@ -385,17 +407,26 @@ bool AvifEncoder::writeanimation(const Animation& animation,
#endif
encoder_->speed = speed;
#ifdef OPENCV_IMGCODECS_AVIF_SUPPORT_16BIT
if(bit_depth == 16) {
encoder_->sampleTransformRecipe = (do_lossless)?
AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_EXTENSION_12B_4B: // lossless
AVIF_SAMPLE_TRANSFORM_BIT_DEPTH_EXTENSION_12B_8B_OVERLAP_4B; // lossy
}
#endif
const avifAddImageFlags flag = (animation.frames.size() == 1)
? AVIF_ADD_IMAGE_FLAG_SINGLE
: AVIF_ADD_IMAGE_FLAG_NONE;
std::vector<AvifImageUniquePtr> images;
for (const cv::Mat &img : animation.frames) {
CV_CheckType(
img.type(),
(bit_depth == 8 && img.depth() == CV_8U) ||
((bit_depth == 10 || bit_depth == 12) && img.depth() == CV_16U),
(isSupportedBitDepth16U(bit_depth) && img.depth() == CV_16U),
"AVIF only supports bit depth of 8 with CV_8U input or "
"bit depth of 10 or 12 with CV_16U input");
"bit depth of 10, 12 (or 16 with libavif v1.4.0+) with CV_16U input");
CV_Check(img.channels(),
img.channels() == 1 || img.channels() == 3 || img.channels() == 4,
+44
View File
@@ -304,6 +304,50 @@ INSTANTIATE_TEST_CASE_P(
::testing::ValuesIn({IMREAD_UNCHANGED, IMREAD_GRAYSCALE,
IMREAD_COLOR, IMREAD_COLOR_RGB})));
#ifdef OPENCV_IMGCODECS_AVIF_SUPPORT_16BIT
class Imgcodecs_Avif_16bit : public testing::TestWithParam<int> {};
TEST_P(Imgcodecs_Avif_16bit, encode_decode)
{
const int quality = GetParam();
const int bit_depth = 16;
const string root = cvtest::TS::ptr()->get_data_path();
const string filename = root + "../cv/features2d/tsukuba.png";
cv::Mat src;
EXPECT_NO_THROW(src = cv::imread(filename));
ASSERT_FALSE(src.empty());
EXPECT_NO_THROW(src.convertTo(src, CV_16UC3, 65536.0 / 255.0));
std::vector<uint8_t> buf;
EXPECT_NO_THROW(imencode(".avif", src, buf, {IMWRITE_AVIF_DEPTH, bit_depth, IMWRITE_AVIF_QUALITY, quality}));
cv::Mat dst;
EXPECT_NO_THROW(dst = imdecode(buf, IMREAD_UNCHANGED));
ASSERT_FALSE(dst.empty());
const float norm = cvtest::norm(dst, src, NORM_L2) /
src.channels() / src.cols / src.rows /
(1 << (bit_depth - 8));
switch ( quality ) {
case 100: EXPECT_EQ(norm, 0); break; // lossless
case 50: EXPECT_LE(norm, 10); break;
case 0: EXPECT_LE(norm, 13); break;
default: ADD_FAILURE() << "Unknown quality setting: " << quality; break;
}
}
INSTANTIATE_TEST_CASE_P(
Imgcodecs_AVIF, Imgcodecs_Avif_16bit,
testing::Values(0, 50, 100) // Quality
);
#endif // OPENCV_IMGCODECS_AVIF_SUPPORT_16BIT
} // namespace
} // namespace opencv_test