diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml index 753d6627..d2f1600c 100644 --- a/.github/workflows/cifuzz.yml +++ b/.github/workflows/cifuzz.yml @@ -20,7 +20,7 @@ jobs: language: c++ fuzz-seconds: 600 - name: Upload Crash - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 if: failure() && steps.build.outcome == 'success' with: name: artifacts diff --git a/.gitignore b/.gitignore index f7687219..7b74b1dd 100644 --- a/.gitignore +++ b/.gitignore @@ -5,5 +5,6 @@ build* # downloaded dependencies third_party/googletest third_party/turbojpeg +third_party/libheif third_party/benchmark tests/data \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 396db555..7745f7a9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -143,6 +143,7 @@ option_if_not_defined(UHDR_ENABLE_INSTALL "Enable install and uninstall targets option_if_not_defined(UHDR_ENABLE_INTRINSICS "Build with SIMD acceleration " TRUE) option_if_not_defined(UHDR_ENABLE_GLES "Build with GPU acceleration " FALSE) option_if_not_defined(UHDR_ENABLE_WERROR "Build with -Werror" FALSE) +option_if_not_defined(UHDR_ENABLE_EXPERIMENTAL_FEATURES "Enable experimental features " FALSE) # These options effect only encoding process. # Decoding continues to support both iso and xmp irrespective of this configuration. @@ -188,6 +189,15 @@ if(UHDR_BUILD_DEPS AND UHDR_ENABLE_INSTALL) message(STATUS "Install and uninstall targets - Disabled") endif() +if(UHDR_ENABLE_EXPERIMENTAL_FEATURES AND UHDR_ENABLE_INSTALL) + set(UHDR_ENABLE_INSTALL FALSE) # disable install and uninstall targets if experimental features are enabled + message(STATUS "Install and uninstall targets - Disabled") +endif() + +if(UHDR_ENABLE_EXPERIMENTAL_FEATURES AND EMSCRIPTEN) + message(FATAL_ERROR "Building experimental features not supported for wasm targets") +endif() + ########################################################### # Compile flags ########################################################### @@ -498,6 +508,62 @@ if(NOT JPEG_FOUND) endif() endif() +# libheif +if(UHDR_ENABLE_EXPERIMENTAL_FEATURES) + find_package(AOM QUIET) + if(NOT AOM_FOUND) + message(FATAL_ERROR "Could NOT find AOM (missing: AOM_LIBRARIES AOM_INCLUDE_DIRS),\ + retry after installing aom codec at sysroot") + else() + message(STATUS "Found AOM: ${AOM_LIBRARIES}") + endif() + find_package(X265 QUIET) + if(NOT X265_FOUND) + message(STATUS "Could NOT find X265 (missing: X265_LIBRARIES X265_INCLUDE_DIRS)") + else() + message(STATUS "Found X265: ${X265_LIBRARIES}") + endif() + find_package(LIBDE265 QUIET) + if(NOT LIBDE265_FOUND) + message(STATUS "Could NOT find LIBDE265 (missing: LIBDE265_LIBRARIES LIBDE265_INCLUDE_DIRS)") + else() + message(STATUS "Found LIBDE265: ${LIBDE265_LIBRARIES}") + endif() + add_compile_options(-DUHDR_ENABLE_HEIF) + add_compile_options(-DWITH_EXPERIMENTAL_GAIN_MAP=1) + set(HEIF_TARGET_NAME libheif) + set(HEIF_PREFIX_DIR ${CMAKE_CURRENT_BINARY_DIR}/${HEIF_TARGET_NAME}) + set(HEIF_SOURCE_DIR ${THIRD_PARTY_DIR}/${HEIF_TARGET_NAME}) + set(HEIF_BINARY_DIR ${HEIF_PREFIX_DIR}/src/${HEIF_TARGET_NAME}-build/libheif) + set(HEIF_INCLUDE_DIRS ${HEIF_SOURCE_DIR}/libheif/api/ ${HEIF_BINARY_DIR}) + set(HEIF_LIB ${CMAKE_STATIC_LIBRARY_PREFIX}heif${CMAKE_STATIC_LIBRARY_SUFFIX}) + if(MSVC) + set(HEIF_LIB ${CMAKE_STATIC_LIBRARY_PREFIX}heif${CMAKE_STATIC_LIBRARY_SUFFIX}) + else() + set(HEIF_LIB ${CMAKE_STATIC_LIBRARY_PREFIX}heif${CMAKE_STATIC_LIBRARY_SUFFIX}) + endif() + if(IS_MULTI) + set(HEIF_LIB_PREFIX ${HEIF_BINARY_DIR}/libheif/$/) + else() + set(HEIF_LIB_PREFIX ${HEIF_BINARY_DIR}/libheif/) + endif() + set(HEIF_LIBRARIES ${HEIF_LIB_PREFIX}${HEIF_LIB}) + ExternalProject_Add(${HEIF_TARGET_NAME} + GIT_REPOSITORY https://github.com/strukturag/libheif.git + GIT_TAG v1.19.5 + PATCH_COMMAND git am ${CMAKE_CURRENT_SOURCE_DIR}/cmake/0001-EXP-add-support-for-gainmap-encoding.patch + PREFIX ${HEIF_PREFIX_DIR} + SOURCE_DIR ${HEIF_SOURCE_DIR} + BINARY_DIR ${HEIF_BINARY_DIR} + CMAKE_ARGS ${UHDR_CMAKE_ARGS} + -DBUILD_SHARED_LIBS=0 + -DWITH_EXPERIMENTAL_GAIN_MAP=1 + -DWITH_DAV1D=0 -DWITH_OpenH264_ENCODER=0 -DWITH_OpenH264_DECODER=0 + BUILD_BYPRODUCTS ${HEIF_LIBRARIES} + INSTALL_COMMAND "" + ) +endif() + if(UHDR_BUILD_JAVA) # build jni and java util classes find_package(Java REQUIRED) @@ -605,6 +671,19 @@ if(UHDR_ENABLE_GLES) list(APPEND PRIVATE_INCLUDE_DIR ${EGL_INCLUDE_DIRS} ${OPENGLES3_INCLUDE_DIRS}) list(APPEND PRIVATE_LINK_LIBS ${EGL_LIBRARIES} ${OPENGLES3_LIBRARIES}) endif() +if(UHDR_ENABLE_EXPERIMENTAL_FEATURES) + list(APPEND PRIVATE_INCLUDE_DIR ${HEIF_INCLUDE_DIRS}) + list(APPEND PRIVATE_LINK_LIBS ${HEIF_LIBRARIES}) + if(AOM_FOUND) + list(APPEND PRIVATE_LINK_LIBS ${AOM_LIBRARIES}) + endif() + if(X265_FOUND) + list(APPEND PRIVATE_LINK_LIBS ${X265_LIBRARIES}) + endif() + if(LIBDE265_FOUND) + list(APPEND PRIVATE_LINK_LIBS ${LIBDE265_LIBRARIES}) + endif() +endif() ########################################################### # Targets @@ -622,6 +701,9 @@ target_compile_options(${UHDR_CORE_LIB_NAME} PRIVATE ${UHDR_WERROR_FLAGS}) if(NOT JPEG_FOUND) add_dependencies(${UHDR_CORE_LIB_NAME} ${JPEGTURBO_TARGET_NAME}) endif() +if(UHDR_ENABLE_EXPERIMENTAL_FEATURES) + add_dependencies(${UHDR_CORE_LIB_NAME} ${HEIF_TARGET_NAME}) +endif() if(NOT MSVC) target_compile_options(${UHDR_CORE_LIB_NAME} PRIVATE -Wall -Wextra -Wshadow) endif() diff --git a/cmake/0001-EXP-add-support-for-gainmap-encoding.patch b/cmake/0001-EXP-add-support-for-gainmap-encoding.patch new file mode 100644 index 00000000..e9fc85e1 --- /dev/null +++ b/cmake/0001-EXP-add-support-for-gainmap-encoding.patch @@ -0,0 +1,1088 @@ +From 9f732b63f983acd838a04f20dd9446a19b4b4537 Mon Sep 17 00:00:00 2001 +From: Ram Mohan M +Date: Tue, 7 Jan 2025 16:46:56 +0530 +Subject: [PATCH] [EXP] add support for gainmap encoding + +gain map encoding by default is disabled. This can be enabled at +configure time using option -DWITH_EXPERIMENTAL_GAIN_MAP=1 +--- + CMakePresets.json | 3 + + libheif/CMakeLists.txt | 7 + + libheif/api/libheif/heif.cc | 118 ++++++++++ + libheif/api/libheif/heif.h | 38 ++++ + libheif/api/libheif/heif_experimental.h | 25 +++ + libheif/api/libheif/heif_plugin.h | 5 +- + libheif/box.cc | 9 + + libheif/box.h | 14 ++ + libheif/context.cc | 78 ++++++- + libheif/context.h | 23 ++ + libheif/file.cc | 31 +++ + libheif/file.h | 8 + + libheif/heif_gain_map.cc | 281 ++++++++++++++++++++++++ + libheif/heif_gain_map.h | 34 +++ + libheif/image-items/image_item.cc | 11 + + libheif/image-items/image_item.h | 3 + + libheif/plugins/encoder_aom.cc | 5 + + libheif/plugins/encoder_kvazaar.cc | 5 + + libheif/plugins/encoder_rav1e.cc | 5 + + libheif/plugins/encoder_x265.cc | 5 + + 20 files changed, 705 insertions(+), 3 deletions(-) + create mode 100644 libheif/heif_gain_map.cc + create mode 100644 libheif/heif_gain_map.h + +diff --git a/CMakePresets.json b/CMakePresets.json +index bbe8b49a..3662e753 100644 +--- a/CMakePresets.json ++++ b/CMakePresets.json +@@ -54,6 +54,7 @@ + "WITH_VVDEC_PLUGIN" : "OFF", + "WITH_VVENC" : "ON", + "WITH_VVENC_PLUGIN" : "OFF", ++ "WITH_EXPERIMENTAL_GAIN_MAP" : "OFF", + + "WITH_REDUCED_VISIBILITY" : "OFF", + "WITH_HEADER_COMPRESSION" : "ON", +@@ -110,6 +111,7 @@ + "WITH_VVDEC_PLUGIN" : "ON", + "WITH_VVENC" : "ON", + "WITH_VVENC_PLUGIN" : "ON", ++ "WITH_EXPERIMENTAL_GAIN_MAP" : "OFF", + + "WITH_REDUCED_VISIBILITY" : "ON", + "WITH_HEADER_COMPRESSION" : "ON", +@@ -148,6 +150,7 @@ + "WITH_UVG266" : "OFF", + "WITH_VVDEC" : "OFF", + "WITH_VVENC" : "OFF", ++ "WITH_EXPERIMENTAL_GAIN_MAP" : "OFF", + + "WITH_REDUCED_VISIBILITY" : "ON", + "WITH_HEADER_COMPRESSION" : "OFF", +diff --git a/libheif/CMakeLists.txt b/libheif/CMakeLists.txt +index 359cb150..2edc56c7 100644 +--- a/libheif/CMakeLists.txt ++++ b/libheif/CMakeLists.txt +@@ -234,6 +234,13 @@ if (ENABLE_EXPERIMENTAL_MINI_FORMAT) + mini.cc) + endif () + ++if (WITH_EXPERIMENTAL_GAIN_MAP) ++ target_compile_definitions(heif PUBLIC WITH_EXPERIMENTAL_GAIN_MAP=1) ++ target_sources(heif PRIVATE ++ heif_gain_map.h ++ heif_gain_map.cc) ++endif () ++ + write_basic_package_version_file(${PROJECT_NAME}-config-version.cmake COMPATIBILITY ExactVersion) + + install(TARGETS heif EXPORT ${PROJECT_NAME}-config +diff --git a/libheif/api/libheif/heif.cc b/libheif/api/libheif/heif.cc +index 3b7ce853..ce37cf4f 100644 +--- a/libheif/api/libheif/heif.cc ++++ b/libheif/api/libheif/heif.cc +@@ -46,6 +46,10 @@ + #include "heif_emscripten.h" + #endif + ++#if WITH_EXPERIMENTAL_GAIN_MAP ++#include "heif_gain_map.h" ++#endif ++ + #include + #include + #include +@@ -657,6 +661,63 @@ struct heif_error heif_context_get_primary_image_ID(struct heif_context* ctx, he + } + + ++#if WITH_EXPERIMENTAL_GAIN_MAP ++heif_error heif_context_get_gain_map_image_handle(heif_context* ctx, heif_image_handle** img) { ++ if (!img) { ++ Error err(heif_error_Usage_error, heif_suberror_Null_pointer_argument); ++ return err.error_struct(ctx->context.get()); ++ } ++ ++ std::shared_ptr gain_map_image = ctx->context->get_gain_map_image(); ++ if (!gain_map_image) { ++ Error err(heif_error_Invalid_input, heif_suberror_No_item_data); ++ return err.error_struct(ctx->context.get()); ++ } ++ ++ *img = new heif_image_handle(); ++ (*img)->image = std::move(gain_map_image); ++ (*img)->context = ctx->context; ++ ++ return Error::Ok.error_struct(ctx->context.get()); ++} ++ ++struct heif_error heif_context_get_gain_map_image_ID(struct heif_context* ctx, heif_item_id* id) { ++ if (!id) { ++ return Error(heif_error_Usage_error, heif_suberror_Null_pointer_argument) ++ .error_struct(ctx->context.get()); ++ } ++ ++ std::shared_ptr gain_map_image = ctx->context->get_gain_map_image(); ++ if (!gain_map_image) { ++ Error err(heif_error_Invalid_input, heif_suberror_No_item_data); ++ return err.error_struct(ctx->context.get()); ++ } ++ ++ *id = gain_map_image->get_id(); ++ ++ return Error::Ok.error_struct(ctx->context.get()); ++} ++ ++struct heif_error heif_image_get_gain_map_metadata(heif_context* ctx, ++ heif_gain_map_metadata* gainmap_metadata) { ++ if (!gainmap_metadata) { ++ Error err(heif_error_Usage_error, heif_suberror_Null_pointer_argument); ++ return err.error_struct(ctx->context.get()); ++ } ++ ++ std::shared_ptr metadata = ctx->context->get_gain_map_metadata(); ++ if (!metadata) { ++ Error err(heif_error_Invalid_input, heif_suberror_No_item_data); ++ return err.error_struct(ctx->context.get()); ++ } ++ ++ Error err = parse_gain_map_metadata(metadata->m_data, gainmap_metadata); ++ ++ return err.error_struct(ctx->context.get()); ++} ++#endif ++ ++ + int heif_context_is_top_level_image_ID(struct heif_context* ctx, heif_item_id id) + { + const std::vector> images = ctx->context->get_top_level_images(true); +@@ -3438,6 +3499,63 @@ struct heif_error heif_context_encode_image(struct heif_context* ctx, + } + + ++#if WITH_EXPERIMENTAL_GAIN_MAP ++struct heif_error heif_context_encode_gain_map_image( ++ struct heif_context* ctx, const struct heif_image* input_image, ++ const struct heif_image_handle* primary_image_handle, struct heif_encoder* encoder, ++ const struct heif_encoding_options* input_options, ++ const struct heif_gain_map_metadata* gain_map_metadata, ++ struct heif_image_handle** out_image_handle) { ++ if (!encoder) { ++ return Error(heif_error_Usage_error, heif_suberror_Null_pointer_argument) ++ .error_struct(ctx->context.get()); ++ } ++ ++ Error error; ++ ++ std::vector metadata; ++ error = prepare_gain_map_metadata(gain_map_metadata, metadata); ++ if (error != Error::Ok) { ++ return error.error_struct(ctx->context.get()); ++ } ++ ++ heif_item_id tmap_item_id = -1; ++ ctx->context->add_tmap_box(metadata, tmap_item_id); ++ ++ heif_encoding_options options; ++ set_default_encoding_options(options); ++ if (input_options != nullptr) { ++ copy_options(options, *input_options); ++ } ++ ++ auto gainmap_encoding_result = ctx->context->encode_image(input_image->image, encoder, options, ++ heif_image_input_class_gain_map); ++ if (gainmap_encoding_result.error) { ++ return gainmap_encoding_result.error.error_struct(ctx->context.get()); ++ } ++ ++ std::shared_ptr gain_map_image = *gainmap_encoding_result; ++ error = ctx->context->link_gain_map(primary_image_handle->image, gain_map_image, tmap_item_id); ++ if (error != Error::Ok) { ++ return error.error_struct(ctx->context.get()); ++ } ++ ++ if (out_image_handle) { ++ *out_image_handle = new heif_image_handle; ++ (*out_image_handle)->image = std::move(gain_map_image); ++ (*out_image_handle)->context = ctx->context; ++ } ++ ++ std::vector ids; ++ ids.push_back(tmap_item_id); ++ ids.push_back(primary_image_handle->image->get_id()); ++ ctx->context->add_altr_property(ids); ++ ++ return heif_error_success; ++} ++#endif ++ ++ + struct heif_error heif_context_encode_grid(struct heif_context* ctx, + struct heif_image** tiles, + uint16_t columns, +diff --git a/libheif/api/libheif/heif.h b/libheif/api/libheif/heif.h +index ca2d59bd..12c9c20e 100644 +--- a/libheif/api/libheif/heif.h ++++ b/libheif/api/libheif/heif.h +@@ -97,6 +97,9 @@ LIBHEIF_API int heif_get_version_number_maintenance(void); + struct heif_context; + struct heif_image_handle; + struct heif_image; ++#if WITH_EXPERIMENTAL_GAIN_MAP ++struct heif_gain_map_metadata; ++#endif + + + enum heif_error_code +@@ -2613,6 +2616,41 @@ LIBHEIF_API + int heif_encoder_descriptor_supportes_lossless_compression(const struct heif_encoder_descriptor*); + + ++ ++// ==================================================================================================== ++// Gain Map API ++ ++#if WITH_EXPERIMENTAL_GAIN_MAP ++ ++// Get a handle to the gain map image of the HEIF file. If no gain map image is available, this ++// method will return heif_suberror_No_item_data. ++LIBHEIF_API ++struct heif_error heif_context_get_gain_map_image_handle(struct heif_context* ctx, ++ struct heif_image_handle**); ++ ++// Get id of the gain map image of the HEIF file. If no gain map image is available, this ++// method will return heif_suberror_No_item_data. ++LIBHEIF_API ++struct heif_error heif_context_get_gain_map_image_ID(struct heif_context* ctx, heif_item_id* id); ++ ++// Parse gainmap metadata from bitstream and write to gainmap_metadata ++LIBHEIF_API ++struct heif_error heif_image_get_gain_map_metadata(heif_context* ctx, ++ heif_gain_map_metadata* gainmap_metadata); ++ ++// Compress the gain map image and write metadata. ++// Returns a handle to the coded image in 'out_image_handle' unless out_image_handle = NULL. ++LIBHEIF_API ++struct heif_error heif_context_encode_gain_map_image( ++ struct heif_context*, const struct heif_image* image, ++ const struct heif_image_handle* primary_image_handle, struct heif_encoder* encoder, ++ const struct heif_encoding_options* options, ++ const struct heif_gain_map_metadata* gain_map_metadata, ++ struct heif_image_handle** out_image_handle); ++ ++#endif ++ ++ + #ifdef __cplusplus + } + #endif +diff --git a/libheif/api/libheif/heif_experimental.h b/libheif/api/libheif/heif_experimental.h +index 7d6d1b83..ceb77e44 100644 +--- a/libheif/api/libheif/heif_experimental.h ++++ b/libheif/api/libheif/heif_experimental.h +@@ -421,6 +421,31 @@ struct heif_error heif_property_get_tai_timestamp(const struct heif_context* ctx + + #endif + ++// --- gain map metadata struct ++ ++struct heif_gain_map_metadata { ++ int32_t gainMapMinN[3]; ++ uint32_t gainMapMinD[3]; ++ int32_t gainMapMaxN[3]; ++ uint32_t gainMapMaxD[3]; ++ uint32_t gainMapGammaN[3]; ++ uint32_t gainMapGammaD[3]; ++ ++ int32_t baseOffsetN[3]; ++ uint32_t baseOffsetD[3]; ++ int32_t alternateOffsetN[3]; ++ uint32_t alternateOffsetD[3]; ++ ++ uint32_t baseHdrHeadroomN; ++ uint32_t baseHdrHeadroomD; ++ uint32_t alternateHdrHeadroomN; ++ uint32_t alternateHdrHeadroomD; ++ ++ bool backwardDirection; ++ bool useBaseColorSpace; ++}; ++ ++ + #ifdef __cplusplus + } + #endif +diff --git a/libheif/api/libheif/heif_plugin.h b/libheif/api/libheif/heif_plugin.h +index 3a438bfc..f768966c 100644 +--- a/libheif/api/libheif/heif_plugin.h ++++ b/libheif/api/libheif/heif_plugin.h +@@ -128,7 +128,10 @@ enum heif_image_input_class + heif_image_input_class_normal = 1, + heif_image_input_class_alpha = 2, + heif_image_input_class_depth = 3, +- heif_image_input_class_thumbnail = 4 ++ heif_image_input_class_thumbnail = 4, ++#if WITH_EXPERIMENTAL_GAIN_MAP ++ heif_image_input_class_gain_map = 5 ++#endif + }; + + +diff --git a/libheif/box.cc b/libheif/box.cc +index 666663b9..fd55c751 100644 +--- a/libheif/box.cc ++++ b/libheif/box.cc +@@ -2740,6 +2740,15 @@ Error Box_cclv::parse(BitstreamRange& range, const heif_security_limits* limits) + } + + ++#if WITH_EXPERIMENTAL_GAIN_MAP ++ ++Error Box_altr::parse(BitstreamRange& range, const heif_security_limits*) { ++ return range.get_error(); ++} ++ ++#endif ++ ++ + template std::ostream& operator<<(std::ostream& ostr, const std::optional& value) + { + if (value) { +diff --git a/libheif/box.h b/libheif/box.h +index 71f45041..c9a8c0ab 100644 +--- a/libheif/box.h ++++ b/libheif/box.h +@@ -1335,6 +1335,20 @@ private: + }; + + ++#if WITH_EXPERIMENTAL_GAIN_MAP ++class Box_altr : public Box_EntityToGroup { ++ public: ++ Box_altr() ++ { ++ set_short_type(fourcc("altr")); ++ } ++ ++ protected: ++ Error parse(BitstreamRange& range, const heif_security_limits*) override; ++}; ++#endif ++ ++ + class Box_cmin : public FullBox + { + public: +diff --git a/libheif/context.cc b/libheif/context.cc +index 5abddeea..1751b75d 100644 +--- a/libheif/context.cc ++++ b/libheif/context.cc +@@ -305,6 +305,17 @@ static bool item_type_is_image(uint32_t item_type, const std::string& content_ty + } + + ++#if WITH_EXPERIMENTAL_GAIN_MAP ++static bool item_type_is_gain_map_image(uint32_t item_type, const std::string& item_name) { ++ return (item_name == "GMap" && (item_type == fourcc("hvc1") || item_type == fourcc("av01"))); ++} ++ ++static bool item_type_is_gain_map_metadata(uint32_t item_type, const std::string& item_name) { ++ return (item_name == "GMap" && item_type == fourcc("tmap")); ++} ++#endif ++ ++ + void HeifContext::remove_top_level_image(const std::shared_ptr& image) + { + std::vector> new_list; +@@ -324,7 +335,10 @@ Error HeifContext::interpret_heif_file() + m_all_images.clear(); + m_top_level_images.clear(); + m_primary_image.reset(); +- ++#if WITH_EXPERIMENTAL_GAIN_MAP ++ m_gain_map_image.reset(); ++ m_gain_map_metadata.reset(); ++#endif + + // --- reference all non-hidden images + +@@ -353,6 +367,12 @@ Error HeifContext::interpret_heif_file() + + m_top_level_images.push_back(image); + } ++#if WITH_EXPERIMENTAL_GAIN_MAP ++ else if (item_type_is_gain_map_image(infe_box->get_item_type_4cc(), ++ infe_box->get_item_name())) { ++ m_gain_map_image = image; ++ } ++#endif + + std::vector> properties; + Error err = m_heif_file->get_properties(id, properties); +@@ -778,12 +798,17 @@ Error HeifContext::interpret_heif_file() + } + + std::string item_uri_type = m_heif_file->get_item_uri_type(id); +- ++#if WITH_EXPERIMENTAL_GAIN_MAP ++ std::string item_name = m_heif_file->get_item_name(id); ++#endif + // we now assign all kinds of metadata to the image, not only 'Exif' and 'XMP' + + std::shared_ptr metadata = std::make_shared(); + metadata->item_id = id; + metadata->item_type = fourcc_to_string(item_type); ++#if WITH_EXPERIMENTAL_GAIN_MAP ++ metadata->item_name = item_name; ++#endif + metadata->content_type = content_type; + metadata->item_uri_type = std::move(item_uri_type); + +@@ -793,6 +818,12 @@ Error HeifContext::interpret_heif_file() + // these item types should have data + return err; + } ++#if WITH_EXPERIMENTAL_GAIN_MAP ++ else if (item_type_is_gain_map_metadata(item_type, item_name)) { ++ // this also should have data ++ return err; ++ } ++#endif + else { + // anything else is probably something that we don't understand yet + continue; +@@ -817,6 +848,23 @@ Error HeifContext::interpret_heif_file() + } + img_iter->second->add_metadata(metadata); + } ++#if WITH_EXPERIMENTAL_GAIN_MAP ++ if (item_type_is_gain_map_metadata(item_type, item_name)) { ++ references = iref_box->get_references(id, fourcc("dimg")); ++ for (heif_item_id derived_image_id : references) { ++ auto img_iter = m_all_images.find(derived_image_id); ++ if (img_iter == m_all_images.end()) { ++ if (!m_heif_file->has_item_with_id(derived_image_id)) { ++ return Error(heif_error_Invalid_input, heif_suberror_Nonexisting_item_referenced, ++ "Metadata assigned to non-existing image"); ++ } ++ continue; ++ } ++ img_iter->second->add_metadata(metadata); ++ } ++ m_gain_map_metadata = metadata; ++ } ++#endif + } + } + +@@ -1136,6 +1184,32 @@ create_alpha_image_from_image_alpha_channel(const std::shared_ptr& ids) { ++ m_heif_file->add_altr_property(ids); ++} ++ ++Error HeifContext::add_tmap_box(const std::vector& data, heif_item_id& item_id) { ++ auto tmap_infe = m_heif_file->add_new_infe_box(fourcc("tmap")); // gain map metadata ++ tmap_infe->set_item_name("GMap"); ++ item_id = tmap_infe->get_item_ID(); ++ ++ m_heif_file->append_iloc_data(item_id, data, 0); ++ ++ return Error::Ok; ++} ++ ++Error HeifContext::link_gain_map(const std::shared_ptr& primary_image, ++ const std::shared_ptr& gain_map_image, ++ const heif_item_id tmap_id) { ++ m_heif_file->add_iref_reference(tmap_id, fourcc("dimg"), ++ {primary_image->get_id(), gain_map_image->get_id()}); ++ ++ return Error::Ok; ++} ++#endif ++ ++ + Result> HeifContext::encode_image(const std::shared_ptr& pixel_image, + struct heif_encoder* encoder, + const struct heif_encoding_options& in_options, +diff --git a/libheif/context.h b/libheif/context.h +index 3f4f0bc3..fdb1c32d 100644 +--- a/libheif/context.h ++++ b/libheif/context.h +@@ -47,6 +47,7 @@ class StreamWriter; + + class ImageItem; + ++class ImageMetadata; + + // This is a higher-level view than HeifFile. + // Images are grouped logically into main images and their thumbnails. +@@ -94,6 +95,12 @@ public: + + std::shared_ptr get_primary_image(bool return_error_image); + ++#if WITH_EXPERIMENTAL_GAIN_MAP ++ std::shared_ptr get_gain_map_image() { return m_gain_map_image; } ++ ++ std::shared_ptr get_gain_map_metadata() { return m_gain_map_metadata; } ++#endif ++ + bool is_image(heif_item_id ID) const; + + bool has_alpha(heif_item_id ID) const; +@@ -147,6 +154,16 @@ public: + + Result add_pyramid_group(const std::vector& layers); + ++#if WITH_EXPERIMENTAL_GAIN_MAP ++ void add_altr_property(std::vector& ids); ++ ++ Error add_tmap_box(const std::vector& metadata, heif_item_id& item_id); ++ ++ Error link_gain_map(const std::shared_ptr& primary_image, ++ const std::shared_ptr& gain_map_image, const heif_item_id tmap_id); ++#endif ++ ++ + // --- region items + + void add_region_item(std::shared_ptr region_item) +@@ -177,6 +194,12 @@ private: + + std::shared_ptr m_primary_image; // shortcut to primary image + ++#if WITH_EXPERIMENTAL_GAIN_MAP ++ std::shared_ptr m_gain_map_image; ++ ++ std::shared_ptr m_gain_map_metadata; ++#endif ++ + std::shared_ptr m_heif_file; + + int m_max_decoding_threads = 4; +diff --git a/libheif/file.cc b/libheif/file.cc +index d16df78b..5672b8e1 100644 +--- a/libheif/file.cc ++++ b/libheif/file.cc +@@ -191,6 +191,9 @@ void HeifFile::set_brand(heif_compression_format format, bool miaf_compatible) + m_ftyp_box->set_minor_version(0); + m_ftyp_box->add_compatible_brand(heif_brand2_mif1); + m_ftyp_box->add_compatible_brand(heif_brand2_heic); ++#if WITH_EXPERIMENTAL_GAIN_MAP ++ m_ftyp_box->add_compatible_brand(heif_fourcc('t', 'm', 'a', 'p')); ++#endif + break; + + case heif_compression_AV1: +@@ -198,6 +201,9 @@ void HeifFile::set_brand(heif_compression_format format, bool miaf_compatible) + m_ftyp_box->set_minor_version(0); + m_ftyp_box->add_compatible_brand(heif_brand2_avif); + m_ftyp_box->add_compatible_brand(heif_brand2_mif1); ++#if WITH_EXPERIMENTAL_GAIN_MAP ++ m_ftyp_box->add_compatible_brand(heif_fourcc('t', 'm', 'a', 'p')); ++#endif + break; + + case heif_compression_VVC: +@@ -527,6 +533,18 @@ uint32_t HeifFile::get_item_type_4cc(heif_item_id ID) const + } + + ++#if WITH_EXPERIMENTAL_GAIN_MAP ++std::string HeifFile::get_item_name(heif_item_id ID) const { ++ auto infe_box = get_infe_box(ID); ++ if (!infe_box) { ++ return ""; ++ } ++ ++ return infe_box->get_item_name(); ++} ++#endif ++ ++ + std::string HeifFile::get_content_type(heif_item_id ID) const + { + auto infe_box = get_infe_box(ID); +@@ -1169,6 +1187,19 @@ void HeifFile::set_auxC_property(heif_item_id id, const std::string& type) + m_ipma_box->add_property_for_item_ID(id, Box_ipma::PropertyAssociation{true, uint16_t(index + 1)}); + } + ++ ++#if WITH_EXPERIMENTAL_GAIN_MAP ++void HeifFile::add_altr_property(std::vector& ids) { ++ auto altr_box = std::make_shared(); ++ ++ altr_box->set_group_id(get_unused_item_id()); ++ altr_box->set_item_ids(ids); ++ ++ add_entity_group_box(altr_box); ++} ++#endif ++ ++ + #if defined(__MINGW32__) || defined(__MINGW64__) || defined(_MSC_VER) + std::wstring HeifFile::convert_utf8_path_to_utf16(std::string str) + { +diff --git a/libheif/file.h b/libheif/file.h +index 1a1585a4..eb39e85e 100644 +--- a/libheif/file.h ++++ b/libheif/file.h +@@ -91,6 +91,10 @@ public: + + uint32_t get_item_type_4cc(heif_item_id ID) const; + ++#if WITH_EXPERIMENTAL_GAIN_MAP ++ std::string get_item_name(heif_item_id ID) const; ++#endif ++ + std::string get_content_type(heif_item_id ID) const; + + std::string get_item_uri_type(heif_item_id ID) const; +@@ -210,6 +214,10 @@ public: + + void set_auxC_property(heif_item_id id, const std::string& type); + ++#if WITH_EXPERIMENTAL_GAIN_MAP ++ void add_altr_property(std::vector& ids); ++#endif ++ + #if defined(__MINGW32__) || defined(__MINGW64__) || defined(_MSC_VER) + static std::wstring convert_utf8_path_to_utf16(std::string pathutf8); + #endif +diff --git a/libheif/heif_gain_map.cc b/libheif/heif_gain_map.cc +new file mode 100644 +index 00000000..0694ba61 +--- /dev/null ++++ b/libheif/heif_gain_map.cc +@@ -0,0 +1,281 @@ ++/* ++ * HEIF codec. ++ * Copyright (c) 2017 Dirk Farin ++ * ++ * This file is part of libheif. ++ * ++ * libheif is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU Lesser General Public License as ++ * published by the Free Software Foundation, either version 3 of ++ * the License, or (at your option) any later version. ++ * ++ * libheif is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public License ++ * along with libheif. If not, see . ++ */ ++ ++#include "heif_gain_map.h" ++ ++constexpr uint8_t kIsMultiChannelMask = (1u << 7); ++constexpr uint8_t kUseBaseColorSpaceMask = (1u << 6); ++ ++void streamWriteU8(std::vector &data, uint8_t value) { data.push_back(value); } ++ ++void streamWriteU16(std::vector &data, uint32_t value) { ++ data.push_back((value >> 8) & 0xff); ++ data.push_back(value & 0xff); ++} ++ ++void streamWriteU32(std::vector &data, uint32_t value) { ++ data.push_back((value >> 24) & 0xff); ++ data.push_back((value >> 16) & 0xff); ++ data.push_back((value >> 8) & 0xff); ++ data.push_back(value & 0xff); ++} ++ ++void streamWriteS32(std::vector &data, int32_t value) { ++ data.push_back((value >> 24) & 0xff); ++ data.push_back((value >> 16) & 0xff); ++ data.push_back((value >> 8) & 0xff); ++ data.push_back(value & 0xff); ++} ++ ++Error streamReadU8(const std::vector &data, uint8_t &value, size_t &pos) { ++ if (pos >= data.size()) { ++ return Error(heif_error_Invalid_input, heif_suberror_End_of_data); ++ } ++ value = data[pos++]; ++ return Error::Ok; ++} ++ ++Error streamReadU16(const std::vector &data, uint16_t &value, size_t &pos) { ++ if (pos >= data.size() - 1) { ++ return Error(heif_error_Invalid_input, heif_suberror_End_of_data); ++ } ++ value = (data[pos] << 8 | data[pos + 1]); ++ pos += 2; ++ return Error::Ok; ++} ++ ++Error streamReadU32(const std::vector &data, uint32_t &value, size_t &pos) { ++ if (pos >= data.size() - 3) { ++ return Error(heif_error_Invalid_input, heif_suberror_End_of_data); ++ } ++ value = (data[pos] << 24 | data[pos + 1] << 16 | data[pos + 2] << 8 | data[pos + 3]); ++ pos += 4; ++ return Error::Ok; ++} ++ ++Error streamReadS32(const std::vector &data, int32_t &value, size_t &pos) { ++ if (pos >= data.size() - 3) { ++ return Error(heif_error_Invalid_input, heif_suberror_End_of_data); ++ } ++ value = (data[pos] << 24 | data[pos + 1] << 16 | data[pos + 2] << 8 | data[pos + 3]); ++ pos += 4; ++ return Error::Ok; ++} ++ ++Error prepare_gain_map_metadata(const heif_gain_map_metadata *metadata, ++ std::vector &data) { ++ if (metadata == nullptr) { ++ return Error(heif_error_Usage_error, heif_suberror_Null_pointer_argument); ++ } ++ ++ const uint8_t version = 0; ++ streamWriteU8(data, version); // version = 0 ++ ++ const uint16_t minimumVersion = 0; ++ streamWriteU16(data, minimumVersion); // minimum version ++ ++ const uint16_t writerVersion = 0; ++ streamWriteU16(data, writerVersion); // writer version ++ ++ const bool allChannelsIdentical = ++ metadata->gainMapMinN[0] == metadata->gainMapMinN[1] && ++ metadata->gainMapMinN[0] == metadata->gainMapMinN[2] && ++ metadata->gainMapMinD[0] == metadata->gainMapMinD[1] && ++ metadata->gainMapMinD[0] == metadata->gainMapMinD[2] && ++ metadata->gainMapMaxN[0] == metadata->gainMapMaxN[1] && ++ metadata->gainMapMaxN[0] == metadata->gainMapMaxN[2] && ++ metadata->gainMapMaxD[0] == metadata->gainMapMaxD[1] && ++ metadata->gainMapMaxD[0] == metadata->gainMapMaxD[2] && ++ metadata->gainMapGammaN[0] == metadata->gainMapGammaN[1] && ++ metadata->gainMapGammaN[0] == metadata->gainMapGammaN[2] && ++ metadata->gainMapGammaD[0] == metadata->gainMapGammaD[1] && ++ metadata->gainMapGammaD[0] == metadata->gainMapGammaD[2] && ++ metadata->baseOffsetN[0] == metadata->baseOffsetN[1] && ++ metadata->baseOffsetN[0] == metadata->baseOffsetN[2] && ++ metadata->baseOffsetD[0] == metadata->baseOffsetD[1] && ++ metadata->baseOffsetD[0] == metadata->baseOffsetD[2] && ++ metadata->alternateOffsetN[0] == metadata->alternateOffsetN[1] && ++ metadata->alternateOffsetN[0] == metadata->alternateOffsetN[2] && ++ metadata->alternateOffsetD[0] == metadata->alternateOffsetD[1] && ++ metadata->alternateOffsetD[0] == metadata->alternateOffsetD[2]; ++ const uint8_t channelCount = allChannelsIdentical ? 1u : 3u; ++ ++ uint8_t flags = 0u; ++ if (channelCount == 3) { ++ flags |= kIsMultiChannelMask; ++ } ++ if (metadata->useBaseColorSpace) { ++ flags |= kUseBaseColorSpaceMask; ++ } ++ if (metadata->backwardDirection) { ++ flags |= 4; ++ } ++ const uint32_t denom = metadata->baseHdrHeadroomD; ++ bool useCommonDenominator = true; ++ if (metadata->baseHdrHeadroomD != denom || metadata->alternateHdrHeadroomD != denom) { ++ useCommonDenominator = false; ++ } ++ for (int c = 0; c < channelCount; ++c) { ++ if (metadata->gainMapMinD[c] != denom || metadata->gainMapMaxD[c] != denom || ++ metadata->gainMapGammaD[c] != denom || metadata->baseOffsetD[c] != denom || ++ metadata->alternateOffsetD[c] != denom) { ++ useCommonDenominator = false; ++ } ++ } ++ if (useCommonDenominator) { ++ flags |= 8; ++ } ++ streamWriteU8(data, flags); ++ ++ if (useCommonDenominator) { ++ streamWriteU32(data, denom); ++ streamWriteU32(data, metadata->baseHdrHeadroomN); ++ streamWriteU32(data, metadata->alternateHdrHeadroomN); ++ for (int c = 0; c < channelCount; ++c) { ++ streamWriteS32(data, metadata->gainMapMinN[c]); ++ streamWriteS32(data, metadata->gainMapMaxN[c]); ++ streamWriteU32(data, metadata->gainMapGammaN[c]); ++ streamWriteS32(data, metadata->baseOffsetN[c]); ++ streamWriteS32(data, metadata->alternateOffsetN[c]); ++ } ++ } else { ++ streamWriteU32(data, metadata->baseHdrHeadroomN); ++ streamWriteU32(data, metadata->baseHdrHeadroomD); ++ streamWriteU32(data, metadata->alternateHdrHeadroomN); ++ streamWriteU32(data, metadata->alternateHdrHeadroomD); ++ for (int c = 0; c < channelCount; ++c) { ++ streamWriteS32(data, metadata->gainMapMinN[c]); ++ streamWriteU32(data, metadata->gainMapMinD[c]); ++ streamWriteS32(data, metadata->gainMapMaxN[c]); ++ streamWriteU32(data, metadata->gainMapMaxD[c]); ++ streamWriteU32(data, metadata->gainMapGammaN[c]); ++ streamWriteU32(data, metadata->gainMapGammaD[c]); ++ streamWriteS32(data, metadata->baseOffsetN[c]); ++ streamWriteU32(data, metadata->baseOffsetD[c]); ++ streamWriteS32(data, metadata->alternateOffsetN[c]); ++ streamWriteU32(data, metadata->alternateOffsetD[c]); ++ } ++ } ++ ++ return Error::Ok; ++} ++ ++Error parse_gain_map_metadata(const std::vector &data, heif_gain_map_metadata *metadata) { ++ if (metadata == nullptr) { ++ return Error(heif_error_Usage_error, heif_suberror_Null_pointer_argument); ++ } ++ size_t pos = 0; ++ ++#define PARSE_ERR_CHECK(x) \ ++ { \ ++ Error err(x); \ ++ if (err.error_code != heif_error_Ok) { \ ++ return err; \ ++ } \ ++ } ++ ++ uint8_t version = 0xff; ++ PARSE_ERR_CHECK(streamReadU8(data, version, pos)) ++ if (version != 0) { ++ return Error(heif_error_Invalid_input, heif_suberror_Unsupported_data_version, ++ "Box[tmap] has unsupported version"); ++ } ++ ++ uint16_t minimum_version = 0xffff; ++ PARSE_ERR_CHECK(streamReadU16(data, minimum_version, pos)) ++ if (minimum_version > 0) { ++ return Error(heif_error_Invalid_input, heif_suberror_Unsupported_data_version, ++ "Box[tmap] has unsupported minimum version"); ++ } ++ ++ uint16_t writer_version = 0xffff; ++ PARSE_ERR_CHECK(streamReadU16(data, writer_version, pos)) ++ if (writer_version > 0) { ++ return Error(heif_error_Invalid_input, heif_suberror_Unsupported_data_version, ++ "Box[tmap] has unsupported writer version"); ++ } ++ ++ uint8_t flags = 0xff; ++ PARSE_ERR_CHECK(streamReadU8(data, flags, pos)) ++ ++ uint8_t channelCount = ((flags & kIsMultiChannelMask) != 0) * 2 + 1; ++ if (!(channelCount == 1 || channelCount == 3)) { ++ return Error(heif_error_Invalid_input, heif_suberror_Unsupported_data_version, ++ "Gain map image must have either 1 or 3 channels"); ++ } ++ metadata->useBaseColorSpace = (flags & kUseBaseColorSpaceMask) != 0; ++ metadata->backwardDirection = (flags & 4) != 0; ++ const bool useCommonDenominator = (flags & 8) != 0; ++ ++ if (useCommonDenominator) { ++ uint32_t commonDenominator = 1u; ++ PARSE_ERR_CHECK(streamReadU32(data, commonDenominator, pos)) ++ ++ PARSE_ERR_CHECK(streamReadU32(data, metadata->baseHdrHeadroomN, pos)) ++ metadata->baseHdrHeadroomD = commonDenominator; ++ PARSE_ERR_CHECK(streamReadU32(data, metadata->alternateHdrHeadroomN, pos)) ++ metadata->alternateHdrHeadroomD = commonDenominator; ++ ++ for (int c = 0; c < channelCount; ++c) { ++ PARSE_ERR_CHECK(streamReadS32(data, metadata->gainMapMinN[c], pos)) ++ metadata->gainMapMinD[c] = commonDenominator; ++ PARSE_ERR_CHECK(streamReadS32(data, metadata->gainMapMaxN[c], pos)) ++ metadata->gainMapMaxD[c] = commonDenominator; ++ PARSE_ERR_CHECK(streamReadU32(data, metadata->gainMapGammaN[c], pos)) ++ metadata->gainMapGammaD[c] = commonDenominator; ++ PARSE_ERR_CHECK(streamReadS32(data, metadata->baseOffsetN[c], pos)) ++ metadata->baseOffsetD[c] = commonDenominator; ++ PARSE_ERR_CHECK(streamReadS32(data, metadata->alternateOffsetN[c], pos)) ++ metadata->alternateOffsetD[c] = commonDenominator; ++ } ++ } else { ++ PARSE_ERR_CHECK(streamReadU32(data, metadata->baseHdrHeadroomN, pos)) ++ PARSE_ERR_CHECK(streamReadU32(data, metadata->baseHdrHeadroomD, pos)) ++ PARSE_ERR_CHECK(streamReadU32(data, metadata->alternateHdrHeadroomN, pos)) ++ PARSE_ERR_CHECK(streamReadU32(data, metadata->alternateHdrHeadroomD, pos)) ++ for (int c = 0; c < channelCount; ++c) { ++ PARSE_ERR_CHECK(streamReadS32(data, metadata->gainMapMinN[c], pos)) ++ PARSE_ERR_CHECK(streamReadU32(data, metadata->gainMapMinD[c], pos)) ++ PARSE_ERR_CHECK(streamReadS32(data, metadata->gainMapMaxN[c], pos)) ++ PARSE_ERR_CHECK(streamReadU32(data, metadata->gainMapMaxD[c], pos)) ++ PARSE_ERR_CHECK(streamReadU32(data, metadata->gainMapGammaN[c], pos)) ++ PARSE_ERR_CHECK(streamReadU32(data, metadata->gainMapGammaD[c], pos)) ++ PARSE_ERR_CHECK(streamReadS32(data, metadata->baseOffsetN[c], pos)) ++ PARSE_ERR_CHECK(streamReadU32(data, metadata->baseOffsetD[c], pos)) ++ PARSE_ERR_CHECK(streamReadS32(data, metadata->alternateOffsetN[c], pos)) ++ PARSE_ERR_CHECK(streamReadU32(data, metadata->alternateOffsetD[c], pos)) ++ } ++ } ++ // Fill the remaining values by copying those from the first channel. ++ for (int c = channelCount; c < 3; ++c) { ++ metadata->gainMapMinN[c] = metadata->gainMapMinN[0]; ++ metadata->gainMapMinD[c] = metadata->gainMapMinD[0]; ++ metadata->gainMapMaxN[c] = metadata->gainMapMaxN[0]; ++ metadata->gainMapMaxD[c] = metadata->gainMapMaxD[0]; ++ metadata->gainMapGammaN[c] = metadata->gainMapGammaN[0]; ++ metadata->gainMapGammaD[c] = metadata->gainMapGammaD[0]; ++ metadata->baseOffsetN[c] = metadata->baseOffsetN[0]; ++ metadata->baseOffsetD[c] = metadata->baseOffsetD[0]; ++ metadata->alternateOffsetN[c] = metadata->alternateOffsetN[0]; ++ metadata->alternateOffsetD[c] = metadata->alternateOffsetD[0]; ++ } ++ ++ return Error::Ok; ++} +diff --git a/libheif/heif_gain_map.h b/libheif/heif_gain_map.h +new file mode 100644 +index 00000000..e2eb0a00 +--- /dev/null ++++ b/libheif/heif_gain_map.h +@@ -0,0 +1,34 @@ ++/* ++ * HEIF codec. ++ * Copyright (c) 2017 Dirk Farin ++ * ++ * This file is part of libheif. ++ * ++ * libheif is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU Lesser General Public License as ++ * published by the Free Software Foundation, either version 3 of ++ * the License, or (at your option) any later version. ++ * ++ * libheif is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public License ++ * along with libheif. If not, see . ++ */ ++ ++#ifndef LIBHEIF_HEIF_GAIN_MAP_H ++#define LIBHEIF_HEIF_GAIN_MAP_H ++ ++#include "error.h" ++ ++#include "libheif/heif_experimental.h" ++ ++Error prepare_gain_map_metadata(const heif_gain_map_metadata* gain_map_metadata, ++ std::vector& data); ++ ++Error parse_gain_map_metadata(const std::vector& data, ++ heif_gain_map_metadata* gain_map_metadata); ++ ++#endif +diff --git a/libheif/image-items/image_item.cc b/libheif/image-items/image_item.cc +index de3d4ce0..be2ed3dd 100644 +--- a/libheif/image-items/image_item.cc ++++ b/libheif/image-items/image_item.cc +@@ -386,6 +386,17 @@ Error ImageItem::encode_to_item(HeifContext* ctx, + auto infe_box = ctx->get_heif_file()->add_new_infe_box(get_infe_type()); + heif_item_id image_id = infe_box->get_item_ID(); + set_id(image_id); ++#if WITH_EXPERIMENTAL_GAIN_MAP ++ if (input_class == heif_image_input_class_gain_map) { ++ if (encoder->plugin->compression_format != heif_compression_HEVC && ++ encoder->plugin->compression_format != heif_compression_AV1) { ++ return Error(heif_error_Encoder_plugin_error, heif_suberror_Unsupported_codec); ++ } ++ infe_box->set_item_name("GMap"); ++ infe_box->set_hidden_item(true); ++ } ++#endif ++ + + ctx->get_heif_file()->append_iloc_data(image_id, codedImage.bitstream, 0); + +diff --git a/libheif/image-items/image_item.h b/libheif/image-items/image_item.h +index 2d222a4f..e1c417fc 100644 +--- a/libheif/image-items/image_item.h ++++ b/libheif/image-items/image_item.h +@@ -40,6 +40,9 @@ class ImageMetadata + public: + heif_item_id item_id; + std::string item_type; // e.g. "Exif" ++#if WITH_EXPERIMENTAL_GAIN_MAP ++ std::string item_name; ++#endif + std::string content_type; + std::string item_uri_type; + std::vector m_data; +diff --git a/libheif/plugins/encoder_aom.cc b/libheif/plugins/encoder_aom.cc +index f66752a6..d0bff1b4 100644 +--- a/libheif/plugins/encoder_aom.cc ++++ b/libheif/plugins/encoder_aom.cc +@@ -1017,7 +1017,12 @@ struct heif_error aom_encode_image(void* encoder_raw, const struct heif_image* i + + if (nclx && + (input_class == heif_image_input_class_normal || ++#if WITH_EXPERIMENTAL_GAIN_MAP ++ input_class == heif_image_input_class_thumbnail || ++ input_class == heif_image_input_class_gain_map)) { ++#else + input_class == heif_image_input_class_thumbnail)) { ++#endif + aom_codec_control(&codec, AV1E_SET_COLOR_PRIMARIES, nclx->color_primaries); + aom_codec_control(&codec, AV1E_SET_MATRIX_COEFFICIENTS, nclx->matrix_coefficients); + aom_codec_control(&codec, AV1E_SET_TRANSFER_CHARACTERISTICS, nclx->transfer_characteristics); +diff --git a/libheif/plugins/encoder_kvazaar.cc b/libheif/plugins/encoder_kvazaar.cc +index a32886b9..b28d4414 100644 +--- a/libheif/plugins/encoder_kvazaar.cc ++++ b/libheif/plugins/encoder_kvazaar.cc +@@ -551,7 +551,12 @@ static struct heif_error kvazaar_encode_image(void* encoder_raw, const struct he + + if (nclx && + (input_class == heif_image_input_class_normal || ++#if WITH_EXPERIMENTAL_GAIN_MAP ++ input_class == heif_image_input_class_thumbnail || ++ input_class == heif_image_input_class_gain_map)) { ++#else + input_class == heif_image_input_class_thumbnail)) { ++#endif + config->vui.colorprim = nclx->color_primaries; + config->vui.transfer = nclx->transfer_characteristics; + config->vui.colormatrix = nclx->matrix_coefficients; +diff --git a/libheif/plugins/encoder_rav1e.cc b/libheif/plugins/encoder_rav1e.cc +index 0047a879..22bf696b 100644 +--- a/libheif/plugins/encoder_rav1e.cc ++++ b/libheif/plugins/encoder_rav1e.cc +@@ -550,7 +550,12 @@ struct heif_error rav1e_encode_image(void* encoder_raw, const struct heif_image* + + if (nclx && + (input_class == heif_image_input_class_normal || ++#if WITH_EXPERIMENTAL_GAIN_MAP ++ input_class == heif_image_input_class_thumbnail || ++ input_class == heif_image_input_class_gain_map)) { ++#else + input_class == heif_image_input_class_thumbnail)) { ++#endif + if (rav1e_config_set_color_description(rav1eConfig.get(), + (RaMatrixCoefficients) nclx->matrix_coefficients, + (RaColorPrimaries) nclx->color_primaries, +diff --git a/libheif/plugins/encoder_x265.cc b/libheif/plugins/encoder_x265.cc +index d4e1b016..2544873d 100644 +--- a/libheif/plugins/encoder_x265.cc ++++ b/libheif/plugins/encoder_x265.cc +@@ -806,7 +806,12 @@ static struct heif_error x265_encode_image(void* encoder_raw, const struct heif_ + + if (nclx && + (input_class == heif_image_input_class_normal || ++#if WITH_EXPERIMENTAL_GAIN_MAP ++ input_class == heif_image_input_class_thumbnail || ++ input_class == heif_image_input_class_gain_map)) { ++#else + input_class == heif_image_input_class_thumbnail)) { ++#endif + + { + std::stringstream sstr; +-- +2.47.1.windows.1 + diff --git a/cmake/FindAOM.cmake b/cmake/FindAOM.cmake new file mode 100644 index 00000000..4ecef480 --- /dev/null +++ b/cmake/FindAOM.cmake @@ -0,0 +1,32 @@ +# +# Copyright (C) 2024 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# + +# +# Finds the AOM Codec library. This module defines: +# +# AOM_FOUND - True if AOM Codec is found, False otherwise +# AOM_LIBRARIES - AOM Codec library +# AOM_INCLUDE_DIRS - Include dir +# + +find_path(AOM_INCLUDE_DIRS aom/aom.h) + +find_library(AOM_LIBRARIES NAMES aom) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(AOM DEFAULT_MSG AOM_INCLUDE_DIRS AOM_LIBRARIES) + +mark_as_advanced(AOM_INCLUDE_DIRS AOM_LIBRARIES) diff --git a/cmake/FindLIBDE265.cmake b/cmake/FindLIBDE265.cmake new file mode 100644 index 00000000..79989eaa --- /dev/null +++ b/cmake/FindLIBDE265.cmake @@ -0,0 +1,32 @@ +# +# Copyright (C) 2024 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# + +# +# Finds the libde265 library. This module defines: +# +# LIBDE265_FOUND - True if libde265 is found, False otherwise +# LIBDE265_LIBRARIES - libde265 library +# LIBDE265_INCLUDE_DIRS - Include dir +# + +find_path(LIBDE265_INCLUDE_DIRS libde265/de265.h) + +find_library(LIBDE265_LIBRARIES NAMES libde265 de265) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(LIBDE265 DEFAULT_MSG LIBDE265_INCLUDE_DIRS LIBDE265_LIBRARIES) + +mark_as_advanced(LIBDE265_INCLUDE_DIRS LIBDE265_LIBRARIES) diff --git a/cmake/FindX265.cmake b/cmake/FindX265.cmake new file mode 100644 index 00000000..3e36973e --- /dev/null +++ b/cmake/FindX265.cmake @@ -0,0 +1,32 @@ +# +# Copyright (C) 2024 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# + +# +# Finds the x265 Encoder library. This module defines: +# +# X265_FOUND - True if x265 encoder is found, False otherwise +# X265_LIBRARIES - x265 encoder library +# X265_INCLUDE_DIRS - Include dir +# + +find_path(X265_INCLUDE_DIRS x265.h) + +find_library(X265_LIBRARIES NAMES libx265 x265) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(X265 DEFAULT_MSG X265_INCLUDE_DIRS X265_LIBRARIES) + +mark_as_advanced(X265_INCLUDE_DIRS X265_LIBRARIES) diff --git a/examples/ultrahdr_app.cpp b/examples/ultrahdr_app.cpp index 6cae7062..77e96657 100644 --- a/examples/ultrahdr_app.cpp +++ b/examples/ultrahdr_app.cpp @@ -274,8 +274,8 @@ class UltraHdrAppInput { int gainmapScaleFactor = 1, int gainmapQuality = 95, bool enableMultiChannelGainMap = true, float gamma = 1.0f, bool enableGLES = false, uhdr_enc_preset_t encPreset = UHDR_USAGE_BEST_QUALITY, - float minContentBoost = FLT_MIN, float maxContentBoost = FLT_MAX, - float targetDispPeakBrightness = -1.0f) + uhdr_codec_t outputFormat = UHDR_CODEC_JPG, float minContentBoost = FLT_MIN, + float maxContentBoost = FLT_MAX, float targetDispPeakBrightness = -1.0f) : mHdrIntentRawFile(hdrIntentRawFile), mSdrIntentRawFile(sdrIntentRawFile), mSdrIntentCompressedFile(sdrIntentCompressedFile), @@ -301,6 +301,7 @@ class UltraHdrAppInput { mGamma(gamma), mEnableGLES(enableGLES), mEncPreset(encPreset), + mOutputFormat(outputFormat), mMinContentBoost(minContentBoost), mMaxContentBoost(maxContentBoost), mTargetDispPeakBrightness(targetDispPeakBrightness), @@ -334,6 +335,7 @@ class UltraHdrAppInput { mGamma(1.0f), mEnableGLES(enableGLES), mEncPreset(UHDR_USAGE_BEST_QUALITY), + mOutputFormat(UHDR_CODEC_JPG), mMinContentBoost(FLT_MIN), mMaxContentBoost(FLT_MAX), mTargetDispPeakBrightness(-1.0f), @@ -422,6 +424,7 @@ class UltraHdrAppInput { const float mGamma; const bool mEnableGLES; const uhdr_enc_preset_t mEncPreset; + const uhdr_codec_t mOutputFormat; const float mMinContentBoost; const float mMaxContentBoost; const float mTargetDispPeakBrightness; @@ -776,6 +779,7 @@ bool UltraHdrAppInput::encode() { RET_IF_ERR(uhdr_enc_set_gainmap_scale_factor(handle, mMapDimensionScaleFactor)) RET_IF_ERR(uhdr_enc_set_gainmap_gamma(handle, mGamma)) RET_IF_ERR(uhdr_enc_set_preset(handle, mEncPreset)) + RET_IF_ERR(uhdr_enc_set_output_format(handle, mOutputFormat)) if (mMinContentBoost != FLT_MIN || mMaxContentBoost != FLT_MAX) { RET_IF_ERR(uhdr_enc_set_min_max_content_boost(handle, mMinContentBoost, mMaxContentBoost)) } @@ -1467,6 +1471,9 @@ static void usage(const char* name) { fprintf( stderr, " -D select encoding preset, optional. [0:real time, 1:best quality (default)]. \n"); + fprintf( + stderr, + " -v select output encoding format, optional. [0:jpg (default), 1:heif, 2:avif ]. \n"); fprintf(stderr, " -k min content boost recommendation, must be in linear scale, optional. [any " "positive real number] \n"); @@ -1565,7 +1572,7 @@ static void usage(const char* name) { } int main(int argc, char* argv[]) { - char opt_string[] = "p:y:i:g:f:w:h:C:c:t:q:o:O:m:j:e:a:b:z:R:s:M:Q:G:x:u:D:k:K:L:"; + char opt_string[] = "p:y:i:g:f:w:h:C:c:t:q:o:O:m:j:e:a:b:z:R:s:M:Q:G:x:u:D:k:K:L:v:"; char *hdr_intent_raw_file = nullptr, *sdr_intent_raw_file = nullptr, *uhdr_file = nullptr, *sdr_intent_compressed_file = nullptr, *gainmap_compressed_file = nullptr, *gainmap_metadata_cfg_file = nullptr, *output_file = nullptr, *exif_file = nullptr; @@ -1587,6 +1594,7 @@ int main(int argc, char* argv[]) { float gamma = 1.0f; bool enable_gles = false; uhdr_enc_preset_t enc_preset = UHDR_USAGE_BEST_QUALITY; + uhdr_codec_t out_format = UHDR_CODEC_JPG; float min_content_boost = FLT_MIN; float max_content_boost = FLT_MAX; float target_disp_peak_brightness = -1.0f; @@ -1678,6 +1686,9 @@ int main(int argc, char* argv[]) { case 'D': enc_preset = static_cast(atoi(optarg_s)); break; + case 'v': + out_format = static_cast(atoi(optarg_s)); + break; case 'k': min_content_boost = (float)atof(optarg_s); break; @@ -1715,7 +1726,7 @@ int main(int argc, char* argv[]) { output_file ? output_file : "out.jpeg", width, height, hdr_cf, sdr_cf, hdr_cg, sdr_cg, hdr_tf, quality, out_tf, out_cf, use_full_range_color_hdr, gainmap_scale_factor, gainmap_compression_quality, use_multi_channel_gainmap, gamma, enable_gles, enc_preset, - min_content_boost, max_content_boost, target_disp_peak_brightness); + out_format, min_content_boost, max_content_boost, target_disp_peak_brightness); if (!appInput.encode()) return -1; if (compute_psnr == 1) { if (!appInput.decode()) return -1; diff --git a/lib/include/ultrahdr/heifr.h b/lib/include/ultrahdr/heifr.h new file mode 100644 index 00000000..bfbce745 --- /dev/null +++ b/lib/include/ultrahdr/heifr.h @@ -0,0 +1,118 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ULTRAHDR_HEIFR_H +#define ULTRAHDR_HEIFR_H + +#ifdef UHDR_ENABLE_HEIF + +#include + +#include "ultrahdr_api.h" +#include "ultrahdr/ultrahdrcommon.h" +#include "ultrahdr/icc.h" + +#include "libheif/heif.h" +#include "libheif/heif_experimental.h" + +namespace ultrahdr { + +class HeifR : public UltraHdr { + public: + HeifR(void* uhdrGLESCtxt = nullptr, + int mapDimensionScaleFactor = kMapDimensionScaleFactorAndroidDefault, + int mapCompressQuality = kMapCompressQualityAndroidDefault, + bool useMultiChannelGainMap = kUseMultiChannelGainMapAndroidDefault, + float gamma = kGainMapGammaDefault, + uhdr_enc_preset_t preset = kEncSpeedPresetAndroidDefault, float minContentBoost = FLT_MIN, + float maxContentBoost = FLT_MAX, float targetDispPeakBrightness = -1.0f, + uhdr_codec_t codec = UHDR_CODEC_AVIF); + + /*!\brief Encode API-0. + * + * Create ultrahdr heif/avif image from raw hdr intent. + * + * Input hdr image is tonemapped to sdr image. A gainmap coefficient is computed between hdr and + * sdr intent. sdr intent and gain map coefficient are compressed using heif/avif encoding. + * compressed sdr intent is signalled as primary item and compressed gainmap is signalled as a + * tone map derived item + * + * \param[in] hdr_intent hdr intent raw input image descriptor + * \param[in, out] dest output image descriptor to store compressed ultrahdr image + * \param[in] quality quality factor for sdr intent heif/avif compression + * \param[in] exif optional exif metadata that needs to be inserted in + * compressed output + * + * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, uhdr_codec_err_t otherwise. + */ + uhdr_error_info_t encodeHEIFR(uhdr_raw_image_t* hdr_intent, uhdr_compressed_image_t* dest, + int quality, uhdr_mem_block_t* exif); + + /*!\brief Encode API-1. + * + * Create ultrahdr heif/avif image from raw hdr intent and raw sdr intent. + * + * A gainmap coefficient is computed between hdr and sdr intent. sdr intent and gain map + * coefficient are compressed using heif/avif encoding. compressed sdr intent is signalled as + * primary item and compressed gainmap is signalled as a tone map derived item + * NOTE: Color transfer of sdr intent is expected to be sRGB. + * + * \param[in] hdr_intent hdr intent raw input image descriptor + * \param[in] sdr_intent sdr intent raw input image descriptor + * \param[in, out] dest output image descriptor to store compressed ultrahdr image + * \param[in] quality quality factor for sdr intent heif/avif compression + * \param[in] exif optional exif metadata that needs to be inserted in + * compressed output + * + * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, uhdr_codec_err_t otherwise. + */ + uhdr_error_info_t encodeHEIFR(uhdr_raw_image_t* hdr_intent, uhdr_raw_image_t* sdr_intent, + uhdr_compressed_image_t* dest, int quality, uhdr_mem_block_t* exif); + + private: + /*!\brief Encode API. + * + * Create ultrahdr image from sdr intent, gainmap image and gainmap metadata + * + * A gainmap coefficient is computed between hdr and sdr intent. sdr intent and gain map + * coefficient are compressed using heif/avif encoding. compressed sdr intent is signalled as + * primary item and compressed gainmap is signalled as a tone map derived item + * + * \param[in] sdr_intent sdr intent raw input image descriptor + * \param[in] gainmap_img gainmap raw image descriptor + * \param[in] metadata gainmap metadata descriptor + * \param[in, out] dest output image descriptor to store compressed ultrahdr image + * \param[in] quality quality factor for sdr intent heif/avif compression + * \param[in] exif optional exif metadata that needs to be inserted in + * compressed output + * \param[in] baseIcc base image icc + * \param[in] alternateIcc alternate image icc + * + * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, uhdr_codec_err_t otherwise. + */ + uhdr_error_info_t encodeHEIFR(uhdr_raw_image_t* sdr_intent, uhdr_raw_image_t* gainmap_img, + uhdr_gainmap_metadata_ext_t* metadata, + uhdr_compressed_image_t* dest, int quality, uhdr_mem_block_t* exif, + DataStruct* baseIcc, DataStruct* alternateIcc); + + uhdr_codec_t mCodec; +}; + +} // namespace ultrahdr + +#endif + +#endif // ULTRAHDR_HEIFR_H diff --git a/lib/src/gainmapmath.cpp b/lib/src/gainmapmath.cpp index afe3b914..a73f2485 100644 --- a/lib/src/gainmapmath.cpp +++ b/lib/src/gainmapmath.cpp @@ -1333,7 +1333,8 @@ std::unique_ptr convert_raw_input_to_ycbcr(uhdr_raw_image_ std::unique_ptr dst = nullptr; Color (*rgbToyuv)(Color) = nullptr; - if (src->fmt == UHDR_IMG_FMT_32bppRGBA1010102 || src->fmt == UHDR_IMG_FMT_32bppRGBA8888) { + if (src->fmt == UHDR_IMG_FMT_32bppRGBA1010102 || src->fmt == UHDR_IMG_FMT_32bppRGBA8888 || + src->fmt == UHDR_IMG_FMT_24bppRGB888) { if (use_bt601) { rgbToyuv = bt601RgbToYuv; } else if (src->cg == UHDR_CG_BT_709) { @@ -1443,12 +1444,11 @@ std::unique_ptr convert_raw_input_to_ycbcr(uhdr_raw_image_ vData[dst->stride[UHDR_PLANE_V] * i + j] = uint16_t(pixel.v); } } - } else if (src->fmt == UHDR_IMG_FMT_32bppRGBA8888 && chroma_sampling_enabled) { + } else if ((src->fmt == UHDR_IMG_FMT_32bppRGBA8888 || src->fmt == UHDR_IMG_FMT_24bppRGB888) && + chroma_sampling_enabled) { dst = std::make_unique(UHDR_IMG_FMT_12bppYCbCr420, src->cg, src->ct, UHDR_CR_FULL_RANGE, src->w, src->h, 64); - uint32_t* rgbData = static_cast(src->planes[UHDR_PLANE_PACKED]); - unsigned int srcStride = src->stride[UHDR_PLANE_PACKED]; - + GetPixelFn getPixel = getPixelFn(src->fmt); uint8_t* yData = static_cast(dst->planes[UHDR_PLANE_Y]); uint8_t* uData = static_cast(dst->planes[UHDR_PLANE_U]); uint8_t* vData = static_cast(dst->planes[UHDR_PLANE_V]); @@ -1456,25 +1456,13 @@ std::unique_ptr convert_raw_input_to_ycbcr(uhdr_raw_image_ for (size_t j = 0; j < dst->w; j += 2) { Color pixel[4]; - pixel[0].r = float(rgbData[srcStride * i + j] & 0xff); - pixel[0].g = float((rgbData[srcStride * i + j] >> 8) & 0xff); - pixel[0].b = float((rgbData[srcStride * i + j] >> 16) & 0xff); - - pixel[1].r = float(rgbData[srcStride * i + (j + 1)] & 0xff); - pixel[1].g = float((rgbData[srcStride * i + (j + 1)] >> 8) & 0xff); - pixel[1].b = float((rgbData[srcStride * i + (j + 1)] >> 16) & 0xff); - - pixel[2].r = float(rgbData[srcStride * (i + 1) + j] & 0xff); - pixel[2].g = float((rgbData[srcStride * (i + 1) + j] >> 8) & 0xff); - pixel[2].b = float((rgbData[srcStride * (i + 1) + j] >> 16) & 0xff); - - pixel[3].r = float(rgbData[srcStride * (i + 1) + (j + 1)] & 0xff); - pixel[3].g = float((rgbData[srcStride * (i + 1) + (j + 1)] >> 8) & 0xff); - pixel[3].b = float((rgbData[srcStride * (i + 1) + (j + 1)] >> 16) & 0xff); + pixel[0] = getPixel(src, j, i); + pixel[1] = getPixel(src, j + 1, i); + pixel[2] = getPixel(src, j, i + 1); + pixel[3] = getPixel(src, j + 1, i + 1); for (int k = 0; k < 4; k++) { // Now we only support the RGB input being full range - pixel[k] /= 255.0f; pixel[k] = (*rgbToyuv)(pixel[k]); pixel[k].y = pixel[k].y * 255.0f + 0.5f; @@ -1498,25 +1486,18 @@ std::unique_ptr convert_raw_input_to_ycbcr(uhdr_raw_image_ vData[dst->stride[UHDR_PLANE_V] * (i / 2) + (j / 2)] = uint8_t(pixel[0].v); } } - } else if (src->fmt == UHDR_IMG_FMT_32bppRGBA8888) { + } else if (src->fmt == UHDR_IMG_FMT_32bppRGBA8888 || src->fmt == UHDR_IMG_FMT_24bppRGB888) { dst = std::make_unique(UHDR_IMG_FMT_24bppYCbCr444, src->cg, src->ct, UHDR_CR_FULL_RANGE, src->w, src->h, 64); - uint32_t* rgbData = static_cast(src->planes[UHDR_PLANE_PACKED]); - unsigned int srcStride = src->stride[UHDR_PLANE_PACKED]; - + GetPixelFn getPixel = getPixelFn(src->fmt); uint8_t* yData = static_cast(dst->planes[UHDR_PLANE_Y]); uint8_t* uData = static_cast(dst->planes[UHDR_PLANE_U]); uint8_t* vData = static_cast(dst->planes[UHDR_PLANE_V]); for (size_t i = 0; i < dst->h; i++) { for (size_t j = 0; j < dst->w; j++) { - Color pixel; - - pixel.r = float(rgbData[srcStride * i + j] & 0xff); - pixel.g = float((rgbData[srcStride * i + j] >> 8) & 0xff); - pixel.b = float((rgbData[srcStride * i + j] >> 16) & 0xff); + Color pixel = getPixel(src, j, i); // Now we only support the RGB input being full range - pixel /= 255.0f; pixel = (*rgbToyuv)(pixel); pixel.y = pixel.y * 255.0f + 0.5f; diff --git a/lib/src/heifr.cpp b/lib/src/heifr.cpp new file mode 100644 index 00000000..26bc8a06 --- /dev/null +++ b/lib/src/heifr.cpp @@ -0,0 +1,455 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef UHDR_ENABLE_HEIF + +#include "ultrahdr/heifr.h" +#include "ultrahdr/gainmapmath.h" +#include "ultrahdr/gainmapmetadata.h" + +namespace ultrahdr { + +#define HEIF_ERR_CHECK(x) \ + { \ + heif_error err = (x); \ + if (err.code != heif_error_Ok) { \ + status.error_code = UHDR_CODEC_ERROR; \ + status.has_detail = 1; \ + snprintf(status.detail, sizeof status.detail, "%s", err.message); \ + goto CleanUp; \ + } \ + } + +class MemoryWriter { + public: + MemoryWriter() : data_(nullptr), size_(0), capacity_(0) {} + + ~MemoryWriter() { free(data_); } + + const uint8_t* data() const { return data_; } + + size_t size() const { return size_; } + + void write(const void* data, size_t size) { + if (capacity_ - size_ < size) { + size_t new_capacity = capacity_ + size; + uint8_t* new_data = static_cast(malloc(new_capacity)); + if (data_) { + memcpy(new_data, data_, size_); + free(data_); + } + data_ = new_data; + capacity_ = new_capacity; + } + memcpy(&data_[size_], data, size); + size_ += size; + } + + public: + uint8_t* data_; + size_t size_; + size_t capacity_; +}; + +static struct heif_error writer_write([[maybe_unused]] struct heif_context* ctx, const void* data, + size_t size, void* userdata) { + MemoryWriter* writer = static_cast(userdata); + writer->write(data, size); + struct heif_error err{heif_error_Ok, heif_suberror_Unspecified, nullptr}; + return err; +} + +static struct heif_error fill_img_plane(heif_image* img, heif_channel channel, void* srcBuffer, + int width, int height, int srcRowStride) { + heif_error err = heif_image_add_plane(img, channel, width, height, 8 /*bitDepth */); + if (err.code != heif_error_Ok) return err; + + int dstStride; + uint8_t* dstBuffer = heif_image_get_plane(img, channel, &dstStride); + uint8_t* srcRow = static_cast(srcBuffer); + + for (int y = 0; y < height; y++) { + memcpy(dstBuffer, srcRow, width); + srcRow += srcRowStride; + dstBuffer += dstStride; + } + return err; +} + +static heif_color_primaries map_cg_cicp_primaries(uhdr_color_gamut_t gamut) { + heif_color_primaries color_primaries = heif_color_primaries_unspecified; + if (gamut == UHDR_CG_BT_709) { + color_primaries = heif_color_primaries_ITU_R_BT_709_5; + } else if (gamut == UHDR_CG_DISPLAY_P3) { + color_primaries = heif_color_primaries_SMPTE_EG_432_1; + } else if (gamut == UHDR_CG_BT_2100) { + color_primaries = heif_color_primaries_ITU_R_BT_2020_2_and_2100_0; + } + return color_primaries; +} + +static heif_transfer_characteristics map_ct_cicp_transfer_characteristics( + uhdr_color_transfer_t tf) { + heif_transfer_characteristics transfer_characteristics = heif_transfer_characteristic_unspecified; + if (tf == UHDR_CT_SRGB) { + transfer_characteristics = heif_transfer_characteristic_ITU_R_BT_709_5; + } else if (tf == UHDR_CT_LINEAR) { + transfer_characteristics = heif_transfer_characteristic_linear; + } else if (tf == UHDR_CT_PQ) { + transfer_characteristics = heif_transfer_characteristic_ITU_R_BT_2100_0_PQ; + } else if (tf == UHDR_CT_HLG) { + transfer_characteristics = heif_transfer_characteristic_ITU_R_BT_2100_0_HLG; + } + return transfer_characteristics; +} + +static heif_matrix_coefficients map_cg_cicp_matrix_coefficients(uhdr_color_gamut_t gamut) { + heif_matrix_coefficients matrix_coefficients = heif_matrix_coefficients_unspecified; + if (gamut == UHDR_CG_BT_709) { + matrix_coefficients = heif_matrix_coefficients_ITU_R_BT_709_5; + } else if (gamut == UHDR_CG_DISPLAY_P3) { + // matrix_coefficients = heif_matrix_coefficients_chromaticity_derived_non_constant_luminance; + } else if (gamut == UHDR_CG_BT_2100) { + matrix_coefficients = heif_matrix_coefficients_ITU_R_BT_2020_2_non_constant_luminance; + } + return matrix_coefficients; +} + +static heif_error map_fmt_to_heif_chroma_vars(uhdr_img_fmt_t fmt, unsigned int w, unsigned h, + enum heif_chroma& heif_img_fmt, int& chromaWd, + int& chromaHt) { + if (fmt == UHDR_IMG_FMT_12bppYCbCr420) { + heif_img_fmt = heif_chroma_420; + chromaWd = w / 2; + chromaHt = h / 2; + } else if (fmt == UHDR_IMG_FMT_16bppYCbCr422) { + heif_img_fmt = heif_chroma_422; + chromaWd = w; + chromaHt = h / 2; + } else if (fmt == UHDR_IMG_FMT_24bppYCbCr444) { + heif_img_fmt = heif_chroma_444; + chromaWd = w; + chromaHt = h; + } else { + heif_error status; + status.code = heif_error_Unsupported_feature; + status.subcode = heif_suberror_Unsupported_color_conversion; + status.message = "mapping uhdr image format to heif chroma format failed"; + return status; + } + return {heif_error_Ok, heif_suberror_Unspecified, nullptr}; +} + +static heif_error set_internal_color_format(heif_encoder* encoder, enum heif_chroma heif_img_fmt) { + struct heif_error err { + heif_error_Ok, heif_suberror_Unspecified, nullptr + }; + switch (heif_img_fmt) { + case heif_chroma_420: + case heif_chroma_monochrome: + err = heif_encoder_set_parameter(encoder, "chroma", "420"); + break; + case heif_chroma_422: + err = heif_encoder_set_parameter(encoder, "chroma", "422"); + break; + case heif_chroma_444: + err = heif_encoder_set_parameter(encoder, "chroma", "444"); + break; + default: + err.code = heif_error_Invalid_input; + err.subcode = heif_suberror_Unsupported_parameter; + err.message = "unsupported heif image format setting"; + break; + } + return err; +} + +HeifR::HeifR(void* uhdrGLESCtxt, int mapDimensionScaleFactor, int mapCompressQuality, + bool useMultiChannelGainMap, float gamma, uhdr_enc_preset_t preset, + float minContentBoost, float maxContentBoost, float targetDispPeakBrightness, + uhdr_codec_t codec) + : UltraHdr(uhdrGLESCtxt, mapDimensionScaleFactor, mapCompressQuality, useMultiChannelGainMap, + gamma, preset, minContentBoost, maxContentBoost, targetDispPeakBrightness), + mCodec(codec) {} + +/* Encode API-0 */ +uhdr_error_info_t HeifR::encodeHEIFR(uhdr_raw_image_t* hdr_intent, uhdr_compressed_image_t* dest, + int quality, uhdr_mem_block_t* exif) { + uhdr_img_fmt_t sdr_intent_fmt; + if (hdr_intent->fmt == UHDR_IMG_FMT_24bppYCbCrP010) { + sdr_intent_fmt = UHDR_IMG_FMT_12bppYCbCr420; + } else if (hdr_intent->fmt == UHDR_IMG_FMT_30bppYCbCr444) { + sdr_intent_fmt = UHDR_IMG_FMT_24bppYCbCr444; + } else if (hdr_intent->fmt == UHDR_IMG_FMT_32bppRGBA1010102 || + hdr_intent->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat) { + sdr_intent_fmt = UHDR_IMG_FMT_32bppRGBA8888; + } else { + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "unsupported hdr intent color format %d", + hdr_intent->fmt); + return status; + } + std::unique_ptr sdr_intent = std::make_unique( + sdr_intent_fmt, UHDR_CG_UNSPECIFIED, UHDR_CT_UNSPECIFIED, UHDR_CR_UNSPECIFIED, hdr_intent->w, + hdr_intent->h, 64); + + // tone map + UHDR_ERR_CHECK(toneMap(hdr_intent, sdr_intent.get())); + + // If hdr intent is tonemapped internally, it is observed from quality pov, + // generateGainMapOnePass() is sufficient + mEncPreset = UHDR_USAGE_REALTIME; // overriding the config option + + // generate gain map + uhdr_gainmap_metadata_ext_t metadata(kJpegrVersion); + std::unique_ptr gainmap; + UHDR_ERR_CHECK(generateGainMap(sdr_intent.get(), hdr_intent, &metadata, gainmap, + /* sdr_is_601 */ false, + /* use_luminance */ false)); + + // compress sdr image + std::unique_ptr sdr_intent_yuv_ext; + uhdr_raw_image_t* sdr_intent_yuv = sdr_intent.get(); + if (isPixelFormatRgb(sdr_intent->fmt)) { +#if (defined(UHDR_ENABLE_INTRINSICS) && (defined(__ARM_NEON__) || defined(__ARM_NEON))) + sdr_intent_yuv_ext = convert_raw_input_to_ycbcr_neon(sdr_intent.get(), false /*use bt601 */); +#else + sdr_intent_yuv_ext = convert_raw_input_to_ycbcr(sdr_intent.get(), false /*use bt601 */); +#endif + sdr_intent_yuv = sdr_intent_yuv_ext.get(); + } + + std::shared_ptr baseIcc = IccHelper::writeIccProfile(UHDR_CT_SRGB, sdr_intent->cg); + std::shared_ptr alternateIcc = + IccHelper::writeIccProfile(gainmap->ct, gainmap->cg, true); + + return encodeHEIFR(sdr_intent_yuv, gainmap.get(), &metadata, dest, quality, exif, baseIcc.get(), + alternateIcc.get()); +} + +/* Encode API-1 */ +uhdr_error_info_t HeifR::encodeHEIFR(uhdr_raw_image_t* hdr_intent, uhdr_raw_image_t* sdr_intent, + uhdr_compressed_image_t* dest, int quality, + uhdr_mem_block_t* exif) { + // generate gain map + uhdr_gainmap_metadata_ext_t metadata(kJpegrVersion); + std::unique_ptr gainmap; + UHDR_ERR_CHECK(generateGainMap(sdr_intent, hdr_intent, &metadata, gainmap)); + + std::unique_ptr sdr_intent_yuv_ext; + uhdr_raw_image_t* sdr_intent_yuv = sdr_intent; + if (isPixelFormatRgb(sdr_intent->fmt)) { +#if (defined(UHDR_ENABLE_INTRINSICS) && (defined(__ARM_NEON__) || defined(__ARM_NEON))) + sdr_intent_yuv_ext = convert_raw_input_to_ycbcr_neon(sdr_intent, false /*use bt601 */); +#else + sdr_intent_yuv_ext = convert_raw_input_to_ycbcr(sdr_intent, false /*use bt601 */); +#endif + sdr_intent_yuv = sdr_intent_yuv_ext.get(); + } + + std::shared_ptr baseIcc = IccHelper::writeIccProfile(UHDR_CT_SRGB, sdr_intent->cg); + std::shared_ptr alternateIcc = + IccHelper::writeIccProfile(gainmap->ct, gainmap->cg, false); + + return encodeHEIFR(sdr_intent_yuv, gainmap.get(), &metadata, dest, quality, exif, baseIcc.get(), + alternateIcc.get()); +} + +uhdr_error_info_t HeifR::encodeHEIFR(uhdr_raw_image_t* sdr_intent, uhdr_raw_image_t* gainmap_img, + uhdr_gainmap_metadata_ext_t* metadata, + uhdr_compressed_image_t* dest, int quality, + uhdr_mem_block_t* exif, DataStruct* baseIcc, + DataStruct* alternateIcc) { + uhdr_error_info_t status = g_no_error; + heif_encoder* encoder = nullptr; + heif_encoding_options* options = nullptr; + heif_color_profile_nclx* nclx = nullptr; + heif_image* baseImage = nullptr; + heif_image_handle* baseHandle = nullptr; + heif_image* secondaryImage = nullptr; + heif_image_handle* secondaryHandle = nullptr; + + uhdr_raw_image_t* gainmap_img_yuv = gainmap_img; + std::unique_ptr gainmap_yuv_ext; + + MemoryWriter writer; + struct heif_writer w; + w.writer_api_version = 1; + w.write = writer_write; + + heif_context* ctx = heif_context_alloc(); + if (!ctx) { + status.error_code = UHDR_CODEC_MEM_ERROR; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "failed to allocate heif context"); + return status; + } + + HEIF_ERR_CHECK(heif_context_get_encoder_for_format( + ctx, mCodec == UHDR_CODEC_AVIF ? heif_compression_AV1 : heif_compression_HEVC, &encoder)); + + // set the encoder parameters + HEIF_ERR_CHECK(heif_encoder_set_lossy_quality(encoder, quality)); + + // encode the primary image + enum heif_chroma heif_img_fmt; + int chromaWd, chromaHt; + HEIF_ERR_CHECK(map_fmt_to_heif_chroma_vars(sdr_intent->fmt, sdr_intent->w, sdr_intent->h, + heif_img_fmt, chromaWd, chromaHt)); + HEIF_ERR_CHECK(heif_image_create(sdr_intent->w, sdr_intent->h, heif_colorspace_YCbCr, + heif_img_fmt, &baseImage)); + HEIF_ERR_CHECK(fill_img_plane(baseImage, heif_channel_Y, sdr_intent->planes[UHDR_PLANE_Y], + sdr_intent->w, sdr_intent->h, sdr_intent->stride[UHDR_PLANE_Y])); + HEIF_ERR_CHECK(fill_img_plane(baseImage, heif_channel_Cb, sdr_intent->planes[UHDR_PLANE_U], + chromaWd, chromaHt, sdr_intent->stride[UHDR_PLANE_U])); + HEIF_ERR_CHECK(fill_img_plane(baseImage, heif_channel_Cr, sdr_intent->planes[UHDR_PLANE_V], + chromaWd, chromaHt, sdr_intent->stride[UHDR_PLANE_V])); + if (baseIcc != nullptr) { + void* ptr = (uint8_t*)baseIcc->getData() + kICCIdentifierSize; + HEIF_ERR_CHECK(heif_image_set_raw_color_profile(baseImage, "prof", ptr, + baseIcc->getLength() - kICCIdentifierSize)); + } + nclx = heif_nclx_color_profile_alloc(); + if (!nclx) { + status.error_code = UHDR_CODEC_MEM_ERROR; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "failed to allocate nclx color profile for base image"); + goto CleanUp; + } + HEIF_ERR_CHECK(set_internal_color_format(encoder, heif_img_fmt)); + HEIF_ERR_CHECK( + heif_nclx_color_profile_set_color_primaries(nclx, map_cg_cicp_primaries(sdr_intent->cg))); + HEIF_ERR_CHECK(heif_nclx_color_profile_set_transfer_characteristics( + nclx, map_ct_cicp_transfer_characteristics(sdr_intent->ct))); + HEIF_ERR_CHECK(heif_nclx_color_profile_set_matrix_coefficients( + nclx, map_cg_cicp_matrix_coefficients(sdr_intent->cg))); + nclx->full_range_flag = sdr_intent->range == UHDR_CR_FULL_RANGE ? true : false; + options = heif_encoding_options_alloc(); + if (!options) { + status.error_code = UHDR_CODEC_MEM_ERROR; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "failed to allocate nclx color profile for base image"); + goto CleanUp; + } + options->save_two_colr_boxes_when_ICC_and_nclx_available = 1; + options->output_nclx_profile = nclx; + HEIF_ERR_CHECK(heif_context_encode_image(ctx, baseImage, encoder, options, &baseHandle)); + if (exif != nullptr) { + HEIF_ERR_CHECK(heif_context_add_exif_metadata(ctx, baseHandle, exif->data, exif->data_sz)); + } + + // encode the gain map image + if (isUsingMultiChannelGainMap()) { + gainmap_yuv_ext = convert_raw_input_to_ycbcr(gainmap_img, true); + gainmap_img_yuv = gainmap_yuv_ext.get(); + } + if (isUsingMultiChannelGainMap()) { + HEIF_ERR_CHECK(map_fmt_to_heif_chroma_vars(gainmap_img_yuv->fmt, gainmap_img_yuv->w, + gainmap_img_yuv->h, heif_img_fmt, chromaWd, + chromaHt)); + HEIF_ERR_CHECK(heif_image_create(gainmap_img_yuv->w, gainmap_img_yuv->h, heif_colorspace_YCbCr, + heif_img_fmt, &secondaryImage)); + HEIF_ERR_CHECK(fill_img_plane(secondaryImage, heif_channel_Y, + gainmap_img_yuv->planes[UHDR_PLANE_Y], gainmap_img_yuv->w, + gainmap_img_yuv->h, gainmap_img_yuv->stride[UHDR_PLANE_Y])); + HEIF_ERR_CHECK(fill_img_plane(secondaryImage, heif_channel_Cb, + gainmap_img_yuv->planes[UHDR_PLANE_U], chromaWd, chromaHt, + gainmap_img_yuv->stride[UHDR_PLANE_U])); + HEIF_ERR_CHECK(fill_img_plane(secondaryImage, heif_channel_Cr, + gainmap_img_yuv->planes[UHDR_PLANE_V], chromaWd, chromaHt, + gainmap_img_yuv->stride[UHDR_PLANE_V])); + HEIF_ERR_CHECK(set_internal_color_format(encoder, heif_img_fmt)); + } else { + HEIF_ERR_CHECK(heif_image_create(gainmap_img_yuv->w, gainmap_img_yuv->h, + heif_colorspace_monochrome, heif_chroma_monochrome, + &secondaryImage)); + HEIF_ERR_CHECK(fill_img_plane(secondaryImage, heif_channel_Y, + gainmap_img_yuv->planes[UHDR_PLANE_Y], gainmap_img_yuv->w, + gainmap_img_yuv->h, gainmap_img_yuv->stride[UHDR_PLANE_Y])); + HEIF_ERR_CHECK(heif_encoder_set_parameter(encoder, "chroma", "420")); + } + if (alternateIcc != nullptr) { + void* ptr = (uint8_t*)alternateIcc->getData() + kICCIdentifierSize; + HEIF_ERR_CHECK(heif_image_set_raw_color_profile(secondaryImage, "prof", ptr, + alternateIcc->getLength() - kICCIdentifierSize)); + } + HEIF_ERR_CHECK( + heif_nclx_color_profile_set_color_primaries(nclx, map_cg_cicp_primaries(gainmap_img->cg))); + HEIF_ERR_CHECK(heif_nclx_color_profile_set_transfer_characteristics( + nclx, map_ct_cicp_transfer_characteristics(gainmap_img->ct))); + HEIF_ERR_CHECK(heif_nclx_color_profile_set_matrix_coefficients( + nclx, map_cg_cicp_matrix_coefficients(gainmap_img->cg))); + nclx->full_range_flag = gainmap_img->range == UHDR_CR_FULL_RANGE ? true : false; + heif_gain_map_metadata heif_metadata; + uhdr_gainmap_metadata_frac iso_secondary_metadata; + status = + uhdr_gainmap_metadata_frac::gainmapMetadataFloatToFraction(metadata, &iso_secondary_metadata); + if (status.error_code != UHDR_CODEC_OK) goto CleanUp; + for (int c = 0; c < 3; ++c) { + heif_metadata.gainMapMinN[c] = iso_secondary_metadata.gainMapMinN[c]; + heif_metadata.gainMapMinD[c] = iso_secondary_metadata.gainMapMinD[c]; + heif_metadata.gainMapMaxN[c] = iso_secondary_metadata.gainMapMaxN[c]; + heif_metadata.gainMapMaxD[c] = iso_secondary_metadata.gainMapMaxD[c]; + heif_metadata.gainMapGammaN[c] = iso_secondary_metadata.gainMapGammaN[c]; + heif_metadata.gainMapGammaD[c] = iso_secondary_metadata.gainMapGammaD[c]; + heif_metadata.baseOffsetN[c] = iso_secondary_metadata.baseOffsetN[c]; + heif_metadata.baseOffsetD[c] = iso_secondary_metadata.baseOffsetD[c]; + heif_metadata.alternateOffsetN[c] = iso_secondary_metadata.alternateOffsetN[c]; + heif_metadata.alternateOffsetD[c] = iso_secondary_metadata.alternateOffsetD[c]; + } + heif_metadata.baseHdrHeadroomN = iso_secondary_metadata.baseHdrHeadroomN; + heif_metadata.baseHdrHeadroomD = iso_secondary_metadata.baseHdrHeadroomD; + heif_metadata.alternateHdrHeadroomN = iso_secondary_metadata.alternateHdrHeadroomN; + heif_metadata.alternateHdrHeadroomD = iso_secondary_metadata.alternateHdrHeadroomD; + heif_metadata.backwardDirection = iso_secondary_metadata.backwardDirection; + heif_metadata.useBaseColorSpace = iso_secondary_metadata.useBaseColorSpace; + + HEIF_ERR_CHECK(heif_encoder_set_lossy_quality(encoder, mMapCompressQuality)); + HEIF_ERR_CHECK(heif_context_encode_gain_map_image(ctx, secondaryImage, baseHandle, encoder, + options, &heif_metadata, &secondaryHandle)); + + heif_context_write(ctx, &w, &writer); + memcpy(dest->data, writer.data(), writer.size()); + dest->data_sz = dest->capacity = writer.size(); + +CleanUp: + if (baseImage) heif_image_release(baseImage); + baseImage = nullptr; + if (baseHandle) heif_image_handle_release(baseHandle); + baseHandle = nullptr; + if (secondaryImage) heif_image_release(secondaryImage); + secondaryImage = nullptr; + if (secondaryHandle) heif_image_handle_release(secondaryHandle); + secondaryHandle = nullptr; + if (options) heif_encoding_options_free(options); + options = nullptr; + if (nclx) heif_nclx_color_profile_free(nclx); + nclx = nullptr; + if (encoder) heif_encoder_release(encoder); + encoder = nullptr; + if (ctx) heif_context_free(ctx); + ctx = nullptr; + + return status; +} + +} // namespace ultrahdr + +#endif diff --git a/lib/src/ultrahdr_api.cpp b/lib/src/ultrahdr_api.cpp index f9d1182d..5de78171 100644 --- a/lib/src/ultrahdr_api.cpp +++ b/lib/src/ultrahdr_api.cpp @@ -22,7 +22,9 @@ #include "ultrahdr/gainmapmath.h" #include "ultrahdr/editorhelper.h" #include "ultrahdr/jpegr.h" -#include "ultrahdr/jpegrutils.h" +#ifdef UHDR_ENABLE_HEIF +#include "ultrahdr/heifr.h" +#endif #include "image_io/base/data_segment_data_source.h" #include "image_io/jpeg/jpeg_info.h" @@ -1153,12 +1155,25 @@ uhdr_error_info_t uhdr_enc_set_output_format(uhdr_codec_private_t* enc, uhdr_cod status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance"); - } else if (media_type != UHDR_CODEC_JPG) { + } +#ifdef UHDR_ENABLE_HEIF + else if (media_type != UHDR_CODEC_JPG && media_type != UHDR_CODEC_AVIF && + media_type != UHDR_CODEC_HEIF) { + status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "invalid output format %d, expects one of {UHDR_CODEC_JPG, UHDR_CODEC_AVIF, " + "UHDR_CODEC_HEIF}", + media_type); + } +#else + else if (media_type != UHDR_CODEC_JPG) { status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "invalid output format %d, expects {UHDR_CODEC_JPG}", media_type); } +#endif if (status.error_code != UHDR_CODEC_OK) return status; uhdr_encoder_private* handle = dynamic_cast(enc); @@ -1240,13 +1255,12 @@ uhdr_error_info_t uhdr_encode(uhdr_codec_private_t* enc) { } } + uhdr_mem_block_t exif{}; + if (handle->m_exif.size() > 0) { + exif.data = handle->m_exif.data(); + exif.capacity = exif.data_sz = handle->m_exif.size(); + } if (handle->m_output_format == UHDR_CODEC_JPG) { - uhdr_mem_block_t exif{}; - if (handle->m_exif.size() > 0) { - exif.data = handle->m_exif.data(); - exif.capacity = exif.data_sz = handle->m_exif.size(); - } - ultrahdr::JpegR jpegr(nullptr, handle->m_gainmap_scale_factor, handle->m_quality.find(UHDR_GAIN_MAP_IMG)->second, handle->m_use_multi_channel_gainmap, handle->m_gamma, @@ -1311,6 +1325,70 @@ uhdr_error_info_t uhdr_encode(uhdr_codec_private_t* enc) { "resources required for uhdr_encode() operation are not present"); } } +#ifdef UHDR_ENABLE_HEIF + else if (handle->m_output_format == UHDR_CODEC_AVIF || + handle->m_output_format == UHDR_CODEC_HEIF) { + ultrahdr::HeifR heifr( + nullptr, handle->m_gainmap_scale_factor, handle->m_quality.find(UHDR_GAIN_MAP_IMG)->second, + handle->m_use_multi_channel_gainmap, handle->m_gamma, handle->m_enc_preset, + handle->m_min_content_boost, handle->m_max_content_boost, + handle->m_target_disp_max_brightness, handle->m_output_format); + if (handle->m_compressed_images.find(UHDR_BASE_IMG) != handle->m_compressed_images.end() && + handle->m_compressed_images.find(UHDR_GAIN_MAP_IMG) != handle->m_compressed_images.end()) { + status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "heif/avif encoding is supported only with raw intents"); + } else if (handle->m_raw_images.find(UHDR_HDR_IMG) != handle->m_raw_images.end()) { + auto& hdr_raw_entry = handle->m_raw_images.find(UHDR_HDR_IMG)->second; + + size_t size = (std::max)((64u * 1024), hdr_raw_entry->w * hdr_raw_entry->h * 3 * 2); + handle->m_compressed_output_buffer = std::make_unique( + UHDR_CG_UNSPECIFIED, UHDR_CT_UNSPECIFIED, UHDR_CR_UNSPECIFIED, size); + + if (handle->m_compressed_images.find(UHDR_SDR_IMG) == handle->m_compressed_images.end() && + handle->m_raw_images.find(UHDR_SDR_IMG) == handle->m_raw_images.end()) { + // api - 0 + status = heifr.encodeHEIFR(hdr_raw_entry.get(), handle->m_compressed_output_buffer.get(), + handle->m_quality.find(UHDR_BASE_IMG)->second, + handle->m_exif.size() > 0 ? &exif : nullptr); + } else if (handle->m_compressed_images.find(UHDR_SDR_IMG) != + handle->m_compressed_images.end() && + handle->m_raw_images.find(UHDR_SDR_IMG) == handle->m_raw_images.end()) { + status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "heif/avif encoding is supported only with raw intents"); + } else if (handle->m_raw_images.find(UHDR_SDR_IMG) != handle->m_raw_images.end()) { + auto& sdr_raw_entry = handle->m_raw_images.find(UHDR_SDR_IMG)->second; + + if (handle->m_compressed_images.find(UHDR_SDR_IMG) == handle->m_compressed_images.end()) { + // api - 1 + status = heifr.encodeHEIFR(hdr_raw_entry.get(), sdr_raw_entry.get(), + handle->m_compressed_output_buffer.get(), + handle->m_quality.find(UHDR_BASE_IMG)->second, + handle->m_exif.size() > 0 ? &exif : nullptr); + } else { + status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "heif/avif encoding is supported only with raw intents"); + } + } + } else { + status.error_code = UHDR_CODEC_INVALID_OPERATION; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "resources required for uhdr_encode() operation are not present"); + } + } +#endif + else { + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "unsupported output format %d", + handle->m_output_format); + } return status; }