aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHarish Mahendrakar <hmahendrakar@google.com>2024-04-10 17:18:02 -0700
committerHarish Mahendrakar <hmahendrakar@google.com>2024-04-11 00:28:42 +0000
commit1fe9afcb0f838ffa982766a09dcb426e35b0eae9 (patch)
treef2cf11c3ad119d9c369e038f7ffc802dd5434a2a
parentcef2e104a50bace03d615e2cf353358212c8e129 (diff)
parent668eea659028a395906611ac1e05bdf88c6f6559 (diff)
downloadlibultrahdr-1fe9afcb0f838ffa982766a09dcb426e35b0eae9.tar.gz
Upgrade libultrahdr to 668eea659028a395906611ac1e05bdf88c6f6559HEADmastermain
This project was upgraded with external_updater. Usage: tools/external_updater/updater.sh update external/libultrahdr For more info, check https://cs.android.com/android/platform/superproject/+/main:tools/external_updater/README.md Bug: 333778084 Test: TreeHugger Change-Id: I7b7c6754b3a0e95c1efcf7fb76fe406d2fc276f3
-rw-r--r--.github/workflows/cmake.yml50
-rw-r--r--Android.bp8
-rw-r--r--CMakeLists.txt364
-rw-r--r--METADATA8
-rw-r--r--README.md178
-rw-r--r--examples/metadata.cfg7
-rw-r--r--examples/ultrahdr_app.cpp902
-rw-r--r--fuzzer/README.md13
-rw-r--r--fuzzer/ultrahdr_dec_fuzzer.cpp4
-rw-r--r--fuzzer/ultrahdr_enc_fuzzer.cpp4
-rw-r--r--lib/include/ultrahdr/editorhelper.h103
-rw-r--r--lib/include/ultrahdr/gainmapmath.h8
-rw-r--r--lib/include/ultrahdr/icc.h8
-rw-r--r--lib/include/ultrahdr/jpegdecoderhelper.h4
-rw-r--r--lib/include/ultrahdr/jpegr.h3
-rw-r--r--lib/include/ultrahdr/ultrahdr.h11
-rw-r--r--lib/include/ultrahdr/ultrahdrcommon.h100
-rw-r--r--lib/src/editorhelper.cpp237
-rw-r--r--lib/src/gainmapmath.cpp11
-rw-r--r--lib/src/jpegdecoderhelper.cpp26
-rw-r--r--lib/src/jpegr.cpp139
-rw-r--r--lib/src/ultrahdr_api.cpp1478
-rw-r--r--libuhdr.pc.template11
-rw-r--r--tests/editorhelper_test.cpp364
-rw-r--r--tests/gainmapmath_test.cpp10
-rw-r--r--tests/jpegr_test.cpp243
-rw-r--r--ultrahdr_api.h583
27 files changed, 4241 insertions, 636 deletions
diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml
index 3914d0e..19035a0 100644
--- a/.github/workflows/cmake.yml
+++ b/.github/workflows/cmake.yml
@@ -17,33 +17,73 @@ jobs:
cc: gcc
cxx: g++
build-system: cmake
- cmake-opts: '-DUHDR_BUILD_TESTS=1 -DUHDR_BUILD_FUZZERS=0'
+ cmake-opts: '-DUHDR_BUILD_TESTS=1 -DUHDR_ENABLE_INSTALL=0 -DUHDR_BUILD_FUZZERS=0'
+
+ - name: ubuntu-latest-gcc-cmake-deps
+ os: ubuntu-latest
+ cc: gcc
+ cxx: g++
+ build-system: cmake
+ cmake-opts: '-DUHDR_BUILD_TESTS=1 -DUHDR_ENABLE_INSTALL=0 -DUHDR_BUILD_FUZZERS=0 -DUHDR_BUILD_DEPS=1'
- name: ubuntu-latest-clang-cmake
os: ubuntu-latest
cc: clang
cxx: clang++
build-system: cmake
- cmake-opts: '-DUHDR_BUILD_TESTS=1 -DUHDR_BUILD_FUZZERS=1'
+ cmake-opts: '-DUHDR_BUILD_TESTS=1 -DUHDR_ENABLE_INSTALL=0 -DUHDR_BUILD_FUZZERS=0'
+
+ - name: ubuntu-latest-clang-cmake-deps
+ os: ubuntu-latest
+ cc: clang
+ cxx: clang++
+ build-system: cmake
+ cmake-opts: '-DUHDR_BUILD_TESTS=1 -DUHDR_ENABLE_INSTALL=0 -DUHDR_BUILD_DEPS=1'
+
+ - name: ubuntu-latest-clang-cmake-fuzzers
+ os: ubuntu-latest
+ cc: clang
+ cxx: clang++
+ build-system: cmake
+ cmake-opts: '-DUHDR_BUILD_TESTS=1 -DUHDR_ENABLE_INSTALL=0 -DUHDR_BUILD_FUZZERS=1'
- name: macos-latest-clang-cmake
os: macos-latest
cc: clang
cxx: clang++
build-system: cmake
- cmake-opts: '-DUHDR_BUILD_TESTS=1 -DUHDR_BUILD_FUZZERS=0'
+ cmake-opts: '-DUHDR_BUILD_TESTS=1 -DUHDR_ENABLE_INSTALL=0 -DUHDR_BUILD_FUZZERS=0'
+
+ - name: macos-latest-clang-cmake-deps
+ os: macos-latest
+ cc: clang
+ cxx: clang++
+ build-system: cmake
+ cmake-opts: '-DUHDR_BUILD_TESTS=1 -DUHDR_ENABLE_INSTALL=0 -DUHDR_BUILD_DEPS=1'
- name: windows-latest-vs-cmake
os: windows-latest
cc: clang
cxx: clang++
build-system: cmake
- cmake-opts: '-G "Visual Studio 17 2022" -DUHDR_BUILD_TESTS=1 -DUHDR_BUILD_FUZZERS=0'
+ cmake-opts: '-G "Visual Studio 17 2022" -DUHDR_BUILD_TESTS=1 -DUHDR_ENABLE_INSTALL=0 -DUHDR_BUILD_FUZZERS=0'
runs-on: ${{ matrix.os }}
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v4
+
+ - name: Install MacOS dependencies
+ if: startsWith(matrix.os,'macos')
+ run: |
+ brew update
+ brew install pkg-config jpeg-turbo
+
+ - name: Install Linux dependencies
+ if: startsWith(matrix.os,'ubuntu')
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y libjpeg-dev
- name: Configure CMake
env:
diff --git a/Android.bp b/Android.bp
index caf0136..9c86c4a 100644
--- a/Android.bp
+++ b/Android.bp
@@ -35,7 +35,10 @@ cc_library {
name: "libultrahdr",
host_supported: true,
vendor_available: true,
- export_include_dirs: ["lib/include"],
+ export_include_dirs: [
+ ".",
+ "lib/include",
+ ],
local_include_dirs: ["lib/include"],
srcs: [
@@ -44,6 +47,8 @@ cc_library {
"lib/src/gainmapmath.cpp",
"lib/src/jpegrutils.cpp",
"lib/src/multipictureformat.cpp",
+ "lib/src/editorhelper.cpp",
+ "lib/src/ultrahdr_api.cpp",
],
shared_libs: [
@@ -53,6 +58,7 @@ cc_library {
"libjpegdecoder",
"liblog",
],
+ rtti: true,
}
cc_library {
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ae48d10..9cdc262 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -16,12 +16,14 @@
cmake_minimum_required(VERSION 3.13)
-project(UltraHdr C CXX)
+project(libuhdr VERSION 1.0 LANGUAGES C CXX
+ DESCRIPTION "Library for encoding and decoding ultrahdr images")
###########################################################
# Detect system
###########################################################
if(${CMAKE_SYSTEM_NAME} MATCHES "Linux")
+elseif(${CMAKE_SYSTEM_NAME} MATCHES "Emscripten")
elseif(WIN32)
elseif(APPLE)
else()
@@ -49,12 +51,13 @@ endif()
###########################################################
# Directories
###########################################################
-set(SOURCE_DIR ${CMAKE_SOURCE_DIR}/lib)
-set(THIRD_PARTY_DIR ${CMAKE_SOURCE_DIR}/third_party)
-set(TESTS_DIR ${CMAKE_SOURCE_DIR}/tests)
-set(BENCHMARK_DIR ${CMAKE_SOURCE_DIR}/benchmark)
-set(FUZZERS_DIR ${CMAKE_SOURCE_DIR}/fuzzer)
-set(EXAMPLES_DIR ${CMAKE_SOURCE_DIR}/examples)
+set(SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/lib)
+set(THIRD_PARTY_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third_party)
+set(TESTS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/tests)
+set(BENCHMARK_DIR ${CMAKE_CURRENT_SOURCE_DIR}/benchmark)
+set(FUZZERS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/fuzzer)
+set(EXAMPLES_DIR ${CMAKE_CURRENT_SOURCE_DIR}/examples)
+set(EXPORT_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR)
message(WARNING "Selected in-source build. Preferably, create a build/ directory and build from there.")
@@ -81,18 +84,51 @@ option_if_not_defined(UHDR_BUILD_EXAMPLES "Build examples " TRUE)
option_if_not_defined(UHDR_BUILD_TESTS "Build unit tests " FALSE)
option_if_not_defined(UHDR_BUILD_BENCHMARK "Build benchmark " FALSE)
option_if_not_defined(UHDR_BUILD_FUZZERS "Build fuzzers " FALSE)
+option_if_not_defined(UHDR_BUILD_DEPS "Build deps and not use pre-installed packages " FALSE)
option_if_not_defined(UHDR_ENABLE_LOGS "Build with verbose logging " FALSE)
+option_if_not_defined(UHDR_ENABLE_INSTALL "Add install target for ultrahdr package" TRUE)
if(UHDR_BUILD_BENCHMARK AND WIN32)
message(FATAL_ERROR "Building benchmarks on current platform not supported")
endif()
+# side effects
+if(UHDR_BUILD_FUZZERS OR UHDR_BUILD_DEPS)
+ set(UHDR_ENABLE_INSTALL FALSE) # during fuzz testing dont enable install/uninstall targets
+ set(UHDR_BUILD_DEPS TRUE) # for fuzz testing its best to build all dependencies from source.
+ # This is to instrument dependencies libs as well
+endif()
+
+if(UHDR_ENABLE_INSTALL)
+ set(UHDR_BUILD_FUZZERS FALSE) # dont instrument any code
+ set(UHDR_BUILD_DEPS FALSE) # use pre-builts for portability
+ set(UHDR_ENABLE_LOGS FALSE) # no verbose logs
+endif()
+
+if(EMSCRIPTEN)
+ set(UHDR_BUILD_DEPS TRUE) # not using -s USE_LIBJPEG=1, building it ourself
+ set(UHDR_ENABLE_INSTALL FALSE) # not providing install/uninstall targets in emscripten builds?
+endif()
+
###########################################################
# Compile flags
###########################################################
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
+if(NOT EMSCRIPTEN)
+ set(CMAKE_POSITION_INDEPENDENT_CODE ON)
+endif()
+
+include(CheckCXXCompilerFlag)
+function(CheckCompilerOption opt res)
+ set(CMAKE_REQUIRED_FLAGS ${opt})
+ check_cxx_compiler_flag(${opt} ${res})
+ unset(CMAKE_REQUIRED_FLAGS)
+ if(NOT ${res})
+ message(FATAL_ERROR "Unsupported compiler option(s) ${opt}")
+ endif()
+endfunction(CheckCompilerOption)
if(MSVC)
if(DEFINED UHDR_SANITIZE_OPTIONS)
@@ -101,26 +137,30 @@ if(MSVC)
if(UHDR_BUILD_FUZZERS)
message(FATAL_ERROR "Building fuzzers not supported in MSVC path")
endif()
- foreach(flag_var
- CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE
- CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO
- CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE
- CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO)
- if(${flag_var} MATCHES "/MD")
- string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}")
- endif()
- if (${flag_var} MATCHES "/MDd")
- string(REGEX REPLACE "/MDd" "/MTd" ${flag_var} "${${flag_var}}")
- endif()
- endforeach()
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
# Disable specific warnings
- # TODO: None of these should be disabled, but for now,for a warning-free msvc build these are
+ # TODO: None of these should be disabled, but for now, for a warning-free msvc build these are
# added. fix the warnings and remove these filters
add_compile_options(/wd4244) # conversion from 'type1' to 'type2', possible loss of data
add_compile_options(/wd4267) # conversion from 'size_t' to 'type' possible loss of data
add_compile_options(/wd4305) # truncation from 'double' to 'float'
add_compile_options(/wd4838) # conversion from 'type1' to 'type2' requires a narrowing conversion
+ add_compile_options(/wd26812) # Prefer enum class over enum
+elseif(EMSCRIPTEN)
+ if(UHDR_BUILD_TESTS)
+ message(FATAL_ERROR "Building tests not supported in Web Assembly Path")
+ endif()
+ if(UHDR_BUILD_BENCHMARK)
+ message(FATAL_ERROR "Building benchmark not supported in Web Assembly Path")
+ endif()
+ if(UHDR_BUILD_FUZZERS)
+ message(FATAL_ERROR "Building fuzzers not supported in Web Assembly Path")
+ endif()
+ if(DEFINED UHDR_SANITIZE_OPTIONS)
+ CheckCompilerOption("-fsanitize=${UHDR_SANITIZE_OPTIONS}" SUPPORTS_SAN_OPTIONS)
+ add_compile_options(-fsanitize=${UHDR_SANITIZE_OPTIONS})
+ add_link_options(-fsanitize=${UHDR_SANITIZE_OPTIONS})
+ endif()
else()
add_compile_options(-ffunction-sections)
add_compile_options(-fdata-sections)
@@ -136,16 +176,6 @@ else()
add_compile_options(-mtune=generic)
endif()
- include(CheckCXXCompilerFlag)
- function(CheckCompilerOption opt res)
- set(CMAKE_REQUIRED_FLAGS ${opt})
- check_cxx_compiler_flag(${opt} ${res})
- unset(CMAKE_REQUIRED_FLAGS)
- if(NOT ${res})
- message(FATAL_ERROR "Unsupported compiler option(s) ${opt}")
- endif()
- endfunction(CheckCompilerOption)
-
if(DEFINED UHDR_SANITIZE_OPTIONS)
CheckCompilerOption("-fsanitize=${UHDR_SANITIZE_OPTIONS}" SUPPORTS_SAN_OPTIONS)
add_compile_options(-fsanitize=${UHDR_SANITIZE_OPTIONS})
@@ -163,6 +193,34 @@ if(UHDR_ENABLE_LOGS)
endif()
###########################################################
+# Utils
+###########################################################
+# copied from https://github.com/google/shaderc/blob/main/cmake/utils.cmake
+macro(get_transitive_static_libs target out_list)
+ if(TARGET ${target})
+ get_target_property(target_type ${target} TYPE)
+ if(target_type STREQUAL "STATIC_LIBRARY")
+ list(INSERT ${out_list} 0 ${target})
+ get_target_property(libs ${target} LINK_LIBRARIES)
+ if(libs)
+ foreach(lib ${libs})
+ get_transitive_static_libs(${lib} ${out_list})
+ endforeach()
+ endif()
+ endif()
+ endif()
+endmacro()
+
+# combine a list of static libraries in to a single library
+function(combine_static_libs target output_target)
+ set(all_libs_list "")
+ get_transitive_static_libs(${target} all_libs_list)
+ foreach(lib IN LISTS all_libs_list)
+ target_sources(${output_target} PRIVATE $<TARGET_OBJECTS:${lib}>)
+ endforeach()
+endfunction()
+
+###########################################################
# Dependencies
###########################################################
@@ -179,65 +237,93 @@ set(UHDR_CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${UHDR_COMPILE_FLAGS_STR}")
set(UHDR_CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${UHDR_COMPILE_FLAGS_STR}")
# libjpeg-turbo
-set(JPEG_INCLUDE_DIRS
- ${THIRD_PARTY_DIR}/libjpeg-turbo/
- ${CMAKE_CURRENT_BINARY_DIR}/libjpeg-turbo/src/libjpeg-turbo-build)
-
-if(IS_MULTI)
- set(JPEG_LIBRARIES
- ${CMAKE_CURRENT_BINARY_DIR}/libjpeg-turbo/src/libjpeg-turbo-build/$<CONFIG>/jpeg-static.lib)
+if(UHDR_ENABLE_INSTALL)
+ find_package(JPEG REQUIRED)
else()
+ if(NOT UHDR_BUILD_DEPS)
+ find_package(JPEG)
+ endif()
+endif()
+
+if(NOT JPEG_FOUND)
+ set(JPEGTURBO_TARGET_NAME turbojpeg)
+ set(JPEGTURBO_PREFIX_DIR ${CMAKE_CURRENT_BINARY_DIR}/${JPEGTURBO_TARGET_NAME})
+ set(JPEGTURBO_SOURCE_DIR ${THIRD_PARTY_DIR}/${JPEGTURBO_TARGET_NAME})
+ set(JPEGTURBO_BINARY_DIR ${JPEGTURBO_PREFIX_DIR}/src/${JPEGTURBO_TARGET_NAME}-build)
+ set(JPEG_INCLUDE_DIRS ${JPEGTURBO_SOURCE_DIR} ${JPEGTURBO_BINARY_DIR})
if(MSVC)
- set(JPEG_LIBRARIES
- ${CMAKE_CURRENT_BINARY_DIR}/libjpeg-turbo/src/libjpeg-turbo-build/jpeg-static.lib)
+ set(JPEG_LIB ${CMAKE_STATIC_LIBRARY_PREFIX}jpeg-static${CMAKE_STATIC_LIBRARY_SUFFIX})
+ else()
+ set(JPEG_LIB ${CMAKE_STATIC_LIBRARY_PREFIX}jpeg${CMAKE_STATIC_LIBRARY_SUFFIX})
+ endif()
+ if(IS_MULTI)
+ set(JPEG_LIB_PREFIX ${JPEGTURBO_BINARY_DIR}/$<CONFIG>/)
+ else()
+ set(JPEG_LIB_PREFIX ${JPEGTURBO_BINARY_DIR}/)
+ endif()
+ set(JPEG_LIBRARIES ${JPEG_LIB_PREFIX}${JPEG_LIB})
+ if(EMSCRIPTEN)
+ ExternalProject_Add(${JPEGTURBO_TARGET_NAME}
+ GIT_REPOSITORY https://github.com/libjpeg-turbo/libjpeg-turbo.git
+ GIT_TAG 3.0.1
+ PREFIX ${JPEGTURBO_PREFIX_DIR}
+ SOURCE_DIR ${JPEGTURBO_SOURCE_DIR}
+ BINARY_DIR ${JPEGTURBO_BINARY_DIR}
+ CONFIGURE_COMMAND emcmake cmake ${JPEGTURBO_SOURCE_DIR}
+ -DENABLE_SHARED=0 -DWITH_SIMD=0
+ BUILD_COMMAND ${CMAKE_COMMAND} --build <BINARY_DIR> --config $<CONFIG> --target jpeg-static
+ BUILD_BYPRODUCTS ${JPEG_LIBRARIES}
+ INSTALL_COMMAND ""
+ )
else()
- set(JPEG_LIBRARIES
- ${CMAKE_CURRENT_BINARY_DIR}/libjpeg-turbo/src/libjpeg-turbo-build/libjpeg.a)
+ ExternalProject_Add(${JPEGTURBO_TARGET_NAME}
+ GIT_REPOSITORY https://github.com/libjpeg-turbo/libjpeg-turbo.git
+ GIT_TAG 3.0.1
+ PREFIX ${JPEGTURBO_PREFIX_DIR}
+ SOURCE_DIR ${JPEGTURBO_SOURCE_DIR}
+ BINARY_DIR ${JPEGTURBO_BINARY_DIR}
+ BUILD_COMMAND ${CMAKE_COMMAND} --build <BINARY_DIR> --config $<CONFIG> --target jpeg-static
+ CMAKE_ARGS -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}
+ -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
+ -DCMAKE_C_FLAGS=${UHDR_CMAKE_C_FLAGS}
+ -DCMAKE_C_FLAGS_DEBUG=${CMAKE_C_FLAGS_DEBUG}
+ -DCMAKE_C_FLAGS_RELEASE=${CMAKE_C_FLAGS_RELEASE}
+ -DCMAKE_C_FLAGS_MINSIZEREL=${CMAKE_C_FLAGS_MINSIZEREL}
+ -DCMAKE_C_FLAGS_RELWITHDEBINFO=${CMAKE_C_FLAGS_RELWITHDEBINFO}
+ -DCMAKE_POSITION_INDEPENDENT_CODE=ON
+ -DENABLE_SHARED=0
+ BUILD_BYPRODUCTS ${JPEG_LIBRARIES}
+ INSTALL_COMMAND ""
+ )
endif()
endif()
-ExternalProject_Add(libjpeg-turbo
- GIT_REPOSITORY https://github.com/libjpeg-turbo/libjpeg-turbo.git
- GIT_TAG 3.0.1
- PREFIX ${CMAKE_CURRENT_BINARY_DIR}/libjpeg-turbo
- SOURCE_DIR ${THIRD_PARTY_DIR}/libjpeg-turbo
- BUILD_COMMAND ${CMAKE_COMMAND} --build <BINARY_DIR> --config $<CONFIG> --target jpeg-static
- CMAKE_ARGS -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}
- -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
- -DCMAKE_C_FLAGS=${UHDR_CMAKE_C_FLAGS}
- -DCMAKE_C_FLAGS_DEBUG=${CMAKE_C_FLAGS_DEBUG}
- -DCMAKE_C_FLAGS_RELEASE=${CMAKE_C_FLAGS_RELEASE}
- -DCMAKE_C_FLAGS_MINSIZEREL=${CMAKE_C_FLAGS_MINSIZEREL}
- -DCMAKE_C_FLAGS_RELWITHDEBINFO=${CMAKE_C_FLAGS_RELWITHDEBINFO}
- BUILD_BYPRODUCTS ${JPEG_LIBRARIES}
- INSTALL_COMMAND ""
-)
-
if(UHDR_BUILD_TESTS)
# gtest and gmock
+ set(GTEST_TARGET_NAME googletest)
+ set(GTEST_PREFIX_DIR ${CMAKE_CURRENT_BINARY_DIR}/${GTEST_TARGET_NAME})
+ set(GTEST_SOURCE_DIR ${THIRD_PARTY_DIR}/${GTEST_TARGET_NAME})
+ set(GTEST_BINARY_DIR ${GTEST_PREFIX_DIR}/src/${GTEST_TARGET_NAME}-build)
set(GTEST_INCLUDE_DIRS
- ${THIRD_PARTY_DIR}/googletest/googletest/include
- ${THIRD_PARTY_DIR}/googletest/googlemock/include)
+ ${GTEST_SOURCE_DIR}/googletest/include
+ ${GTEST_SOURCE_DIR}/googlemock/include)
+ set(GTEST_LIB ${CMAKE_STATIC_LIBRARY_PREFIX}gtest${CMAKE_STATIC_LIBRARY_SUFFIX})
+ set(GTEST_LIB_MAIN ${CMAKE_STATIC_LIBRARY_PREFIX}gtest_main${CMAKE_STATIC_LIBRARY_SUFFIX})
if(IS_MULTI)
- set(GTEST_BOTH_LIBRARIES
- ${CMAKE_CURRENT_BINARY_DIR}/googletest/src/googletest-build/lib/$<CONFIG>/gtest.lib
- ${CMAKE_CURRENT_BINARY_DIR}/googletest/src/googletest-build/lib/$<CONFIG>/gtest_main.lib)
+ set(GTEST_LIB_PREFIX ${GTEST_BINARY_DIR}/lib/$<CONFIG>/)
else()
- if(MSVC)
- set(GTEST_BOTH_LIBRARIES
- ${CMAKE_CURRENT_BINARY_DIR}/googletest/src/googletest-build/lib/gtest.lib
- ${CMAKE_CURRENT_BINARY_DIR}/googletest/src/googletest-build/lib/gtest_main.lib)
- else()
- set(GTEST_BOTH_LIBRARIES
- ${CMAKE_CURRENT_BINARY_DIR}/googletest/src/googletest-build/lib/libgtest.a
- ${CMAKE_CURRENT_BINARY_DIR}/googletest/src/googletest-build/lib/libgtest_main.a)
- endif()
+ set(GTEST_LIB_PREFIX ${GTEST_BINARY_DIR}/lib/)
+ endif()
+ set(GTEST_BOTH_LIBRARIES ${GTEST_LIB_PREFIX}${GTEST_LIB} ${GTEST_LIB_PREFIX}${GTEST_LIB_MAIN})
+ if(MSVC)
+ set(MSVC_ARGS "-Dgtest_force_shared_crt=ON")
endif()
- ExternalProject_Add(googletest
+ ExternalProject_Add(${GTEST_TARGET_NAME}
GIT_REPOSITORY https://github.com/google/googletest
GIT_TAG v1.14.0
- PREFIX ${CMAKE_CURRENT_BINARY_DIR}/googletest
- SOURCE_DIR ${THIRD_PARTY_DIR}/googletest
+ PREFIX ${GTEST_PREFIX_DIR}
+ SOURCE_DIR ${GTEST_SOURCE_DIR}
+ BINARY_DIR ${GTEST_BINARY_DIR}
CMAKE_ARGS -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
-DCMAKE_CXX_FLAGS=${UHDR_CMAKE_CXX_FLAGS}
@@ -245,6 +331,8 @@ if(UHDR_BUILD_TESTS)
-DCMAKE_CXX_FLAGS_RELEASE=${CMAKE_CXX_FLAGS_RELEASE}
-DCMAKE_CXX_FLAGS_MINSIZEREL=${CMAKE_CXX_FLAGS_MINSIZEREL}
-DCMAKE_CXX_FLAGS_RELWITHDEBINFO=${CMAKE_CXX_FLAGS_RELWITHDEBINFO}
+ -DCMAKE_POSITION_INDEPENDENT_CODE=ON
+ ${MSVC_ARGS}
BUILD_BYPRODUCTS ${GTEST_BOTH_LIBRARIES}
INSTALL_COMMAND ""
)
@@ -252,27 +340,25 @@ endif()
if(UHDR_BUILD_BENCHMARK)
# benchmark
- set(BENCHMARK_INCLUDE_DIR ${THIRD_PARTY_DIR}/benchmark/include)
+ set(BM_TARGET_NAME benchmark)
+ set(BM_PREFIX_DIR ${CMAKE_CURRENT_BINARY_DIR}/${BM_TARGET_NAME})
+ set(BM_SOURCE_DIR ${THIRD_PARTY_DIR}/${BM_TARGET_NAME})
+ set(BM_BINARY_DIR ${BM_PREFIX_DIR}/src/${BM_TARGET_NAME}-build)
+ set(BENCHMARK_INCLUDE_DIR ${BM_SOURCE_DIR}/include)
+ set(BM_LIB ${CMAKE_STATIC_LIBRARY_PREFIX}benchmark${CMAKE_STATIC_LIBRARY_SUFFIX})
+ set(BM_LIB_MAIN ${CMAKE_STATIC_LIBRARY_PREFIX}benchmark_main${CMAKE_STATIC_LIBRARY_SUFFIX})
if(IS_MULTI)
- set(BENCHMARK_LIBRARIES
- ${CMAKE_CURRENT_BINARY_DIR}/benchmark/src/benchmark-build/src/$<CONFIG>/benchmark.lib
- ${CMAKE_CURRENT_BINARY_DIR}/benchmark/src/benchmark-build/src/$<CONFIG>/benchmark_main.lib)
+ set(BM_LIB_PREFIX ${BM_BINARY_DIR}/src/$<CONFIG>/)
else()
- if(MSVC)
- set(BENCHMARK_LIBRARIES
- ${CMAKE_CURRENT_BINARY_DIR}/benchmark/src/benchmark-build/src/benchmark.lib
- ${CMAKE_CURRENT_BINARY_DIR}/benchmark/src/benchmark-build/src/benchmark_main.lib)
- else()
- set(BENCHMARK_LIBRARIES
- ${CMAKE_CURRENT_BINARY_DIR}/benchmark/src/benchmark-build/src/libbenchmark.a
- ${CMAKE_CURRENT_BINARY_DIR}/benchmark/src/benchmark-build/src/libbenchmark_main.a)
- endif()
+ set(BM_LIB_PREFIX ${BM_BINARY_DIR}/src/)
endif()
- ExternalProject_Add(benchmark
+ set(BENCHMARK_LIBRARIES ${BM_LIB_PREFIX}${BM_LIB} ${BM_LIB_PREFIX}${BM_LIB_MAIN})
+ ExternalProject_Add(${BM_TARGET_NAME}
GIT_REPOSITORY https://github.com/google/benchmark.git
GIT_TAG v1.8.3
- PREFIX ${CMAKE_CURRENT_BINARY_DIR}/benchmark
- SOURCE_DIR ${THIRD_PARTY_DIR}/benchmark
+ PREFIX ${BM_PREFIX_DIR}
+ SOURCE_DIR ${BM_SOURCE_DIR}
+ BINARY_DIR ${BM_BINARY_DIR}
CMAKE_ARGS -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}
-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
-DCMAKE_CXX_FLAGS=${UHDR_CMAKE_CXX_FLAGS}
@@ -280,6 +366,7 @@ if(UHDR_BUILD_BENCHMARK)
-DCMAKE_CXX_FLAGS_RELEASE=${CMAKE_CXX_FLAGS_RELEASE}
-DCMAKE_CXX_FLAGS_MINSIZEREL=${CMAKE_CXX_FLAGS_MINSIZEREL}
-DCMAKE_CXX_FLAGS_RELWITHDEBINFO=${CMAKE_CXX_FLAGS_RELWITHDEBINFO}
+ -DCMAKE_POSITION_INDEPENDENT_CODE=ON
-DBENCHMARK_ENABLE_TESTING=OFF
-DBENCHMARK_DOWNLOAD_DEPENDENCIES=OFF
BUILD_BYPRODUCTS ${BENCHMARK_LIBRARIES}
@@ -288,47 +375,49 @@ if(UHDR_BUILD_BENCHMARK)
endif()
set_property(DIRECTORY PROPERTY ADDITIONAL_MAKE_CLEAN_FILES
- ${CMAKE_CURRENT_BINARY_DIR}/libjpeg-turbo/src/libjpeg-turbo-build
- ${CMAKE_CURRENT_BINARY_DIR}/googletest/src/googletest-build
- ${CMAKE_CURRENT_BINARY_DIR}/benchmark/src/benchmark-build)
+ ${JPEGTURBO_BINARY_DIR} ${GTEST_BINARY_DIR} ${BM_BINARY_DIR})
###########################################################
# File Lists
###########################################################
-file(GLOB UHDR_LIB_LIST "${SOURCE_DIR}/src/*.cpp")
-file(GLOB UHDR_TEST_LIST "${TESTS_DIR}/*.cpp")
-file(GLOB UHDR_BM_LIST "${BENCHMARK_DIR}/*.cpp")
-file(GLOB IMAGE_IO_LIST "${THIRD_PARTY_DIR}/image_io/src/**/*.cc")
+file(GLOB UHDR_CORE_SRCS_LIST "${SOURCE_DIR}/src/*.cpp")
+file(GLOB UHDR_TEST_SRCS_LIST "${TESTS_DIR}/*.cpp")
+file(GLOB UHDR_BM_SRCS_LIST "${BENCHMARK_DIR}/*.cpp")
+file(GLOB IMAGE_IO_SRCS_LIST "${THIRD_PARTY_DIR}/image_io/src/**/*.cc")
-set(COMMON_INCLUDE_LIST ${SOURCE_DIR}/include/ ${JPEG_INCLUDE_DIRS})
+set(PRIVATE_INCLUDE_DIR ${SOURCE_DIR}/include/ ${JPEG_INCLUDE_DIRS})
set(COMMON_LIBS_LIST ${JPEG_LIBRARIES} Threads::Threads)
###########################################################
# Targets
###########################################################
-add_library(image_io STATIC ${IMAGE_IO_LIST})
-target_include_directories(image_io PRIVATE
+set(IMAGEIO_TARGET_NAME image_io)
+add_library(${IMAGEIO_TARGET_NAME} STATIC ${IMAGE_IO_SRCS_LIST})
+target_include_directories(${IMAGEIO_TARGET_NAME} PRIVATE
"${THIRD_PARTY_DIR}/image_io/includes"
"${THIRD_PARTY_DIR}/image_io/src/modp_b64"
"${THIRD_PARTY_DIR}/image_io/src/modp_b64/modp_b64")
-add_library(ultrahdr STATIC ${UHDR_LIB_LIST})
-add_dependencies(ultrahdr libjpeg-turbo)
-#target_compile_options(ultrahdr PRIVATE -Wall -Wextra -Wpedantic)
-target_include_directories(ultrahdr PRIVATE
- ${COMMON_INCLUDE_LIST}
+set(UHDR_CORE_LIB_NAME core)
+add_library(${UHDR_CORE_LIB_NAME} STATIC ${UHDR_CORE_SRCS_LIST})
+if(NOT JPEG_FOUND)
+ add_dependencies(${UHDR_CORE_LIB_NAME} ${JPEGTURBO_TARGET_NAME})
+endif()
+#target_compile_options(${UHDR_CORE_LIB_NAME} PRIVATE -Wall -Wextra -Wpedantic)
+target_include_directories(${UHDR_CORE_LIB_NAME} PRIVATE
+ ${PRIVATE_INCLUDE_DIR}
"${THIRD_PARTY_DIR}/image_io/includes/"
)
-target_link_libraries(ultrahdr PRIVATE ${COMMON_LIBS_LIST} image_io)
+target_include_directories(${UHDR_CORE_LIB_NAME} PUBLIC ${EXPORT_INCLUDE_DIR})
+target_link_libraries(${UHDR_CORE_LIB_NAME} PRIVATE ${COMMON_LIBS_LIST} ${IMAGEIO_TARGET_NAME})
if(UHDR_BUILD_EXAMPLES)
add_executable(ultrahdr_app "${EXAMPLES_DIR}/ultrahdr_app.cpp")
- add_dependencies(ultrahdr_app ultrahdr)
- target_include_directories(ultrahdr_app PRIVATE ${COMMON_INCLUDE_LIST})
+ add_dependencies(ultrahdr_app ${UHDR_CORE_LIB_NAME})
if(UHDR_BUILD_FUZZERS)
target_link_options(ultrahdr_app PRIVATE -fsanitize=fuzzer-no-link)
endif()
- target_link_libraries(ultrahdr_app PRIVATE ultrahdr)
+ target_link_libraries(ultrahdr_app PRIVATE ${UHDR_CORE_LIB_NAME})
endif()
if(UHDR_BUILD_TESTS OR UHDR_BUILD_BENCHMARK)
@@ -348,30 +437,30 @@ if(UHDR_BUILD_TESTS OR UHDR_BUILD_BENCHMARK)
endif()
if(UHDR_BUILD_TESTS)
- add_executable(ultrahdr_unit_test ${UHDR_TEST_LIST})
- add_dependencies(ultrahdr_unit_test googletest ultrahdr)
+ add_executable(ultrahdr_unit_test ${UHDR_TEST_SRCS_LIST})
+ add_dependencies(ultrahdr_unit_test ${GTEST_TARGET_NAME} ${UHDR_CORE_LIB_NAME})
target_include_directories(ultrahdr_unit_test PRIVATE
- ${COMMON_INCLUDE_LIST}
+ ${PRIVATE_INCLUDE_DIR}
${GTEST_INCLUDE_DIRS}
)
if(UHDR_BUILD_FUZZERS)
target_link_options(ultrahdr_unit_test PRIVATE -fsanitize=fuzzer-no-link)
endif()
- target_link_libraries(ultrahdr_unit_test ultrahdr ${GTEST_BOTH_LIBRARIES})
+ target_link_libraries(ultrahdr_unit_test ${UHDR_CORE_LIB_NAME} ${GTEST_BOTH_LIBRARIES})
add_test(NAME UHDRUnitTests, COMMAND ultrahdr_unit_test)
endif()
if(UHDR_BUILD_BENCHMARK)
- add_executable(ultrahdr_bm ${UHDR_BM_LIST})
- add_dependencies(ultrahdr_bm benchmark ultrahdr)
+ add_executable(ultrahdr_bm ${UHDR_BM_SRCS_LIST})
+ add_dependencies(ultrahdr_bm ${BM_TARGET_NAME} ${UHDR_CORE_LIB_NAME})
target_include_directories(ultrahdr_bm PRIVATE
- ${COMMON_INCLUDE_LIST}
+ ${PRIVATE_INCLUDE_DIR}
${BENCHMARK_INCLUDE_DIR}
)
if(UHDR_BUILD_FUZZERS)
target_link_options(ultrahdr_bm PRIVATE -fsanitize=fuzzer-no-link)
endif()
- target_link_libraries(ultrahdr_bm ultrahdr ${BENCHMARK_LIBRARIES})
+ target_link_libraries(ultrahdr_bm ${UHDR_CORE_LIB_NAME} ${BENCHMARK_LIBRARIES})
set(RES_FILE "${TESTS_DIR}/data/UltrahdrBenchmarkTestRes-1.0.zip")
set(RES_FILE_MD5SUM "96651c5c07505c37aa017c57f480e6c1")
@@ -411,22 +500,49 @@ endif()
if(UHDR_BUILD_FUZZERS)
add_executable(ultrahdr_enc_fuzzer ${FUZZERS_DIR}/ultrahdr_enc_fuzzer.cpp)
- add_dependencies(ultrahdr_enc_fuzzer ultrahdr)
- target_include_directories(ultrahdr_enc_fuzzer PRIVATE ${COMMON_INCLUDE_LIST})
+ add_dependencies(ultrahdr_enc_fuzzer ${UHDR_CORE_LIB_NAME})
+ target_include_directories(ultrahdr_enc_fuzzer PRIVATE ${PRIVATE_INCLUDE_DIR})
if(DEFINED ENV{LIB_FUZZING_ENGINE})
target_link_options(ultrahdr_enc_fuzzer PRIVATE $ENV{LIB_FUZZING_ENGINE})
else()
target_link_options(ultrahdr_enc_fuzzer PRIVATE -fsanitize=fuzzer)
endif()
- target_link_libraries(ultrahdr_enc_fuzzer ultrahdr)
+ target_link_libraries(ultrahdr_enc_fuzzer ${UHDR_CORE_LIB_NAME})
add_executable(ultrahdr_dec_fuzzer ${FUZZERS_DIR}/ultrahdr_dec_fuzzer.cpp)
- add_dependencies(ultrahdr_dec_fuzzer ultrahdr)
- target_include_directories(ultrahdr_dec_fuzzer PRIVATE ${COMMON_INCLUDE_LIST})
+ add_dependencies(ultrahdr_dec_fuzzer ${UHDR_CORE_LIB_NAME})
+ target_include_directories(ultrahdr_dec_fuzzer PRIVATE ${PRIVATE_INCLUDE_DIR})
if(DEFINED ENV{LIB_FUZZING_ENGINE})
target_link_options(ultrahdr_dec_fuzzer PRIVATE $ENV{LIB_FUZZING_ENGINE})
else()
target_link_options(ultrahdr_dec_fuzzer PRIVATE -fsanitize=fuzzer)
endif()
- target_link_libraries(ultrahdr_dec_fuzzer ultrahdr)
+ target_link_libraries(ultrahdr_dec_fuzzer ${UHDR_CORE_LIB_NAME})
+endif()
+
+if(UHDR_ENABLE_INSTALL)
+ set(UHDR_TARGET_NAME uhdr)
+ add_library(${UHDR_TARGET_NAME} SHARED)
+ add_dependencies(${UHDR_TARGET_NAME} ${UHDR_CORE_LIB_NAME})
+ target_link_libraries(${UHDR_TARGET_NAME} PRIVATE ${JPEG_LIBRARIES})
+ set_target_properties(${UHDR_TARGET_NAME} PROPERTIES PUBLIC_HEADER ultrahdr_api.h)
+ combine_static_libs(${UHDR_CORE_LIB_NAME} ${UHDR_TARGET_NAME})
+
+ if(NOT(WIN32 OR XCODE))
+ include(GNUInstallDirs)
+
+ # pkg-config: libuhdr.pc
+ configure_file("${CMAKE_CURRENT_SOURCE_DIR}/libuhdr.pc.template"
+ "${CMAKE_CURRENT_BINARY_DIR}/libuhdr.pc" @ONLY NEWLINE_STYLE UNIX)
+ install(FILES "${CMAKE_CURRENT_BINARY_DIR}/libuhdr.pc"
+ DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig")
+ install(TARGETS ${UHDR_TARGET_NAME}
+ DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+ PUBLIC_HEADER DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
+
+ add_custom_target(uninstall)
+ add_custom_command(TARGET uninstall
+ POST_BUILD
+ COMMAND xargs rm -vf < install_manifest.txt)
+ endif()
endif()
diff --git a/METADATA b/METADATA
index 891ab61..f1275ce 100644
--- a/METADATA
+++ b/METADATA
@@ -1,5 +1,5 @@
# This project was upgraded with external_updater.
-# Usage: tools/external_updater/updater.sh update libultrahdr
+# Usage: tools/external_updater/updater.sh update external/libultrahdr
# For more info, check https://cs.android.com/android/platform/superproject/+/main:tools/external_updater/README.md
name: "libultrahdr"
@@ -8,12 +8,12 @@ third_party {
license_type: NOTICE
last_upgrade_date {
year: 2024
- month: 1
- day: 9
+ month: 4
+ day: 10
}
identifier {
type: "Git"
value: "https://github.com/google/libultrahdr.git"
- version: "3f9c0ec5590c2fa47b78a8bc125ca5e6b27e5728"
+ version: "668eea659028a395906611ac1e05bdf88c6f6559"
}
}
diff --git a/README.md b/README.md
index 10082d7..58f0b71 100644
--- a/README.md
+++ b/README.md
@@ -1,130 +1,126 @@
-Background
-==========
+# Background
libultrahdr is an image compression library that uses gain map technology
to store and distribute HDR images. Conceptually on the encoding side, the
library accepts SDR and HDR rendition of an image and from these a Gain Map
(quotient between the two renditions) is computed. The library then uses
backward compatible means to store the base image (SDR), gain map image and
-some associated metadata. Legacy readers that do not support parsing the
+some associated metadata. Legacy readers that do not support handling the
gain map image and/or metadata, will display the base image. Readers that
support the format combine the base image with the gain map and render a
high dynamic range image on compatible displays.
-More information about libultrahdr can be found at
-<https://developer.android.com/guide/topics/media/platform/hdr-image-format>.
+For additional information about libultrahdr, see android hdr-image-format
+[guide](https://developer.android.com/guide/topics/media/platform/hdr-image-format).
-Building libultrahdr
-======================
+## Building libultrahdr
-libultrahdr compresses base image and gain map image in to jpeg format.
-For this libjpeg-turbo is used. This is cloned from
-<https://github.com/libjpeg-turbo/libjpeg-turbo.git> and included in the
-build process.
-
-Requirements
---------------
+### Requirements
- [CMake](http://www.cmake.org) v3.13 or later
-
-- [NASM](http://www.nasm.us) or [Yasm](http://yasm.tortall.net)
- (If libjpeg-turbo needs to be built with SIMD extensions)
+- C++ compiler, supporting at least C++17.
+- libultrahdr uses jpeg compression format to store sdr image and gainmap quotient.
+ So, libjpeg or any other jpeg codec that is ABI and API compatible with libjpeg.
+
+The library offers a way to skip installing libjpeg by passing `UHDR_BUILD_DEPS=1`
+at the time of configure. That is, `cmake -DUHDR_BUILD_DEPS=1` will clone jpeg codec
+from [link](https://github.com/libjpeg-turbo/libjpeg-turbo.git) and include it in
+the build process. This is however not recommended.
+
+If jpeg is included in the build process then to build jpeg with simd extensions,
+- C compiler
+- [NASM](http://www.nasm.us) or [Yasm](http://yasm.tortall.net) are needed.
* If using NASM, 2.13 or later is required.
* If using Yasm, 1.2.0 or later is required.
- * If building on macOS, NASM or Yasm can be obtained from
- [MacPorts](http://www.macports.org/) or [Homebrew](http://brew.sh/).
-
-- Compilers with support for C++17
-Should work with GCC v7 (or later) and Clang 5 (or later) on Linux and Mac Platforms.
-
-Should work with Microsoft Visual C++ 2019 (or later) on Windows Platforms.
-
-Build Procedure
----------------
+### Build Procedure
To build libultrahdr, examples, unit tests:
### Un*x (including Linux, Mac)
- mkdir {build_directory}
- cd {build_directory}
- cmake -G "Unix Makefiles" -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DUHDR_BUILD_TESTS=1 ../
+ mkdir build_directory
+ cd build_directory
+ cmake -G "Unix Makefiles" -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DUHDR_BUILD_TESTS=1 ../
make
ctest
+ make install
-This will generate the following files under *{build_directory}*:
+This will generate the following files under `build_directory`:
-**libultrahdr.a**<br> Static link library for the ultrahdr API
+**libuhdr.so or libuhdr.dylib**<br> ultrahdr shared library
-**ultrahdr_app**<br> Sample application demonstrating ultrahdr API
+**libuhdr.pc**<br> ultrahdr pkg-config file
-**ultrahdr_unit_test**<br> Unit tests
+**ultrahdr_app**<br> Statically linked sample application demonstrating ultrahdr API usage
-### Visual C++ (IDE)
-
- mkdir {build_directory}
- cd {build_directory}
- cmake -G "Visual Studio 16 2019" -DUHDR_BUILD_TESTS=1 ../
- cmake --build ./ --config=Release
- ctest -C Release
+**ultrahdr_unit_test**<br> Unit tests
-This will generate the following files under *{build_directory/Release}*:
+`make install` will install libuhdr.so, ultrahdr_api.h, libuhdr.pc for system-wide usage and
+`make uninstall` will remove the same.
-**ultrahdr.lib**<br> Static link library for the ultrahdr API
+NOTE: you may need to run `ldconfig` after install/uninstall
-**ultrahdr_app.exe**<br> Sample application demonstrating ultrahdr API
+### MinGW
-**ultrahdr_unit_test.exe**<br> Unit tests
+NOTE: This assumes that you are building on a Windows machine using the MSYS
+environment.
-### Visual C++ (Command line)
+ mkdir build_directory
+ cd build_directory
+ cmake -G "MSYS Makefiles" -DUHDR_BUILD_TESTS=1 ../
+ cmake --build ./
+ ctest
- mkdir {build_directory}
- cd {build_directory}
- cmake -G "NMake Makefiles" -DUHDR_BUILD_TESTS=1 ../
+ mkdir build_directory
+ cd build_directory
+ cmake -G "MinGW Makefiles" -DUHDR_BUILD_TESTS=1 ../
cmake --build ./
ctest
-This will generate the following files under *{build_directory}*:
+This will generate the following files under `build_directory`:
-**ultrahdr.lib**<br> Static link library for the ultrahdr API
+**libuhdr.dll**<br> ultrahdr shared library
**ultrahdr_app.exe**<br> Sample application demonstrating ultrahdr API
**ultrahdr_unit_test.exe**<br> Unit tests
-### MinGW
+### Visual C++ (IDE)
-NOTE: This assumes that you are building on a Windows machine using the MSYS
-environment.
+ mkdir build_directory
+ cd build_directory
+ cmake -G "Visual Studio 16 2019" -DUHDR_BUILD_DEPS=1 -DUHDR_BUILD_TESTS=1 ../
+ cmake --build ./ --config=Release
+ ctest -C Release
- mkdir {build_directory}
- cd {build_directory}
- cmake -G "MSYS Makefiles" -DUHDR_BUILD_TESTS=1 ../
- cmake --build ./
- ctest
+This will generate the following files under `build_directory/Release`:
- mkdir {build_directory}
- cd {build_directory}
- cmake -G "MinGW Makefiles" -DUHDR_BUILD_TESTS=1 ../
+**ultrahdr_app.exe**<br> Sample application demonstrating ultrahdr API
+
+**ultrahdr_unit_test.exe**<br> Unit tests
+
+### Visual C++ (Command line)
+
+ mkdir build_directory
+ cd build_directory
+ cmake -G "NMake Makefiles" -DUHDR_BUILD_DEPS=1 -DUHDR_BUILD_TESTS=1 ../
cmake --build ./
ctest
-This will generate the following files under *{build_directory}*:
-
-**libultrahdr.a**<br> Static link library for the ultrahdr API
+This will generate the following files under `build_directory`:
**ultrahdr_app.exe**<br> Sample application demonstrating ultrahdr API
**ultrahdr_unit_test.exe**<br> Unit tests
-NOTE: To not build unit tests, skip passing -DUHDR_BUILD_TESTS=1
+NOTE: To not build unit tests, skip passing `-DUHDR_BUILD_TESTS=1`
### Building Benchmark
-To build benchmarks, pass -DUHDR_BUILD_BENCHMARK=1 to cmake configure command and build.
+To build benchmarks, pass `-DUHDR_BUILD_BENCHMARK=1` to cmake configure command and build.
This will additionally generate,
@@ -135,32 +131,34 @@ This will additionally generate,
Refer to [README.md](fuzzer/README.md) for complete instructions.
-Using libultrahdr
-===================
+## Using libultrahdr
+
+A detailed description of libultrahdr encode and decode api is included in [ultrahdr_api.h](ultrahdr_api.h)
+and for sample usage refer [demo app](examples/ultrahdr_app.cpp).
+
+libultrahdr includes two classes of APIs, one to compress and the other to decompress HDR images:
+
+### Encoding api outline:
+
+| Scenario | Hdr intent raw | Sdr intent raw | Sdr intent compressed | Gain map compressed | Quality | Exif | Use Case |
+|:---------:| :----------: | :----------: | :---------------------: | :-------------------: | :-------: | :---------: | :-------- |
+| API - 0 | P010 | No | No | No | Optional| Optional | Used if, only hdr raw intent is present. (Experimental).[^1] |
+| API - 1 | P010 | YUV420 | No | No | Optional| Optional | Used if, hdr raw and sdr raw intents are present.[^2] |
+| API - 2 | P010 | YUV420 | Yes | No | No | No | Used if, hdr raw, sdr raw and sdr compressed intents are present.[^3] |
+| API - 3 | P010 | No | Yes | No | No | No | Used if, hdr raw and sdr compressed intents are present.[^4] |
+| API - 4 | No | No | Yes | Yes | No | No | Used if, sdr compressed, gain map compressed and GainMap Metadata are present.[^5] |
+
+[^1]: Tonemap hdr to sdr. Compute gain map from hdr and sdr. Compress sdr and gainmap at quality configured. Add exif if provided. Combine sdr compressed, gainmap in multi picture format with gainmap metadata.
+[^2]: Compute gain map from hdr and sdr. Compress sdr and gainmap at quality configured. Add exif if provided. Combine sdr compressed, gainmap in multi picture format with gainmap metadata.
+[^3]: Compute gain map from hdr and raw sdr. Compress gainmap. Combine sdr compressed, gainmap in multi picture format with gainmap metadata.
+[^4]: Decode compressed sdr input. Compute gain map from hdr and decoded sdr. Compress gainmap. Combine sdr compressed, gainmap in multi picture format with gainmap metadata.
+[^5]: Combine sdr compressed, gainmap in multi picture format with gainmap metadata.
-libultrahdr includes two classes of APIs, one to compress and the other to
-decompress HDR images:
+### Decoding api outline:
-List of encode APIs:
-| Input | HDR YUV | SDR YUV | JPEG | Encoded gainmap | Quality (0 ~ 100) | EXIF | Use case |
-| ------------- | ------------- | ------------- | ------------- | ------------- | ------------- | ------------- | ------------- |
-| API-0 | P010 | No | No | No | Required | Optional | Experimental only. |
-| API-1 | P010 | YUV_420 | No | No | Required | Optional | Raw SDR input. Primary image will be encoded from the raw SDR input in the library. |
-| API-2 | P010 | YUV_420 | Yes | No | No | No | Both JPEG and raw SDR inputs. Gainmap will be calculated from raw HDR and raw SDR inputs, the JPEG input will be preserved (including metadata) as the primary image. |
-| API-3 | P010 | No | Yes | No | No | No | SDR JPEG input. Gainmap will be calculated from raw HDR and the decoding result of the JPEG input, the JPEG input will be preserved (including metadata) as the primary image. |
-| API-4 | No | No | Yes | Yes | No | No | SDR JPEG and gainmap inputs. The library will only generate the Ultra HDR related metadata and write everything into the Ultra HDR format, all other metadata from the JPEG input will be preserved. |
+Configure display device characteristics (display transfer characteristics, max display boost) for optimal usage.
-List of decode API:
| Input | Usage |
| ------------- | ------------- |
-| compressed_jpegr_image | The input data. Pointer to JPEG/R stream. |
-| dest | The output data. Destination that decoded data to be written. |
| max_display_boost | (optional, >= 1.0) the maximum available boost supported by a display. |
-| exif | (optional, default to NULL) Destination that exif data to be written. |
-| recovery_map | (optional, default to NULL) Destination that decoded recovery map data to be written. |
-| output_format | <table><thead><tr><th>Value</th><th>Color format to be written</th></tr></thead><tbody><tr><td>SDR</td><td>RGBA_8888</td></tr><tr><td>HDR_LINEAR</td><td>(default) RGBA_F16 linear</td></tr><tr><td>HDR_PQ</td><td>RGBA_1010102 PQ</td></tr><tr><td>HDR_HLG</td><td>RGBA_1010102 HLG</td></tr></tbody></table> |
-| metadata | (optional, default to NULL) Destination of metadata (recovery map version, min/max content boost). |
-
-For more info:
-- Refer to [jpegr.h](lib/include/ultrahdr/jpegr.h) for detailed description of various encode and decode api.
-- Refer to [ultrahdr_app.cpp](examples/ultrahdr_app.cpp) for examples of its usage.
+| supported color transfer format pairs | <table><thead><tr><th>color transfer</th><th>Color format </th></tr></thead><tbody><tr><td>SDR</td><td>32bppRGBA8888</td></tr><tr><td>HDR_LINEAR</td><td>64bppRGBAHalfFloat</td></tr><tr><td>HDR_PQ</td><td>32bppRGBA1010102 PQ</td></tr><tr><td>HDR_HLG</td><td>32bppRGBA1010102 HLG</td></tr></tbody></table> |
diff --git a/examples/metadata.cfg b/examples/metadata.cfg
new file mode 100644
index 0000000..baf8f2f
--- /dev/null
+++ b/examples/metadata.cfg
@@ -0,0 +1,7 @@
+--maxContentBoost 6.0
+--minContentBoost 1.0
+--gamma 1.0
+--offsetSdr 0.0
+--offsetHdr 0.0
+--hdrCapacityMin 1.0
+--hdrCapacityMax 6.0
diff --git a/examples/ultrahdr_app.cpp b/examples/ultrahdr_app.cpp
index 20b9329..cc87d5b 100644
--- a/examples/ultrahdr_app.cpp
+++ b/examples/ultrahdr_app.cpp
@@ -24,14 +24,12 @@
#include <algorithm>
#include <cmath>
+#include <cstdint>
#include <fstream>
#include <iostream>
+#include <sstream>
-#include "ultrahdr/ultrahdrcommon.h"
-#include "ultrahdr/gainmapmath.h"
-#include "ultrahdr/jpegr.h"
-
-using namespace ultrahdr;
+#include "ultrahdr_api.h"
const float BT601YUVtoRGBMatrix[9] = {
1, 0, 1.402, 1, (-0.202008 / 0.587), (-0.419198 / 0.587), 1.0, 1.772, 0.0};
@@ -62,6 +60,10 @@ const float BT2020RGBtoYUVMatrix[9] = {0.2627,
(-0.6780 / 1.4746),
(-0.0593 / 1.4746)};
+// remove these once introduced in ultrahdr_api.h
+const int UHDR_IMG_FMT_24bppYCbCr444 = 100;
+const int UHDR_IMG_FMT_48bppYCbCr444 = 101;
+
int optind_s = 1;
int optopt_s = 0;
char* optarg_s = nullptr;
@@ -158,6 +160,27 @@ static bool loadFile(const char* filename, void*& result, int length) {
return false;
}
+static bool loadFile(const char* filename, uhdr_raw_image_t* handle) {
+ std::ifstream ifd(filename, std::ios::binary);
+ if (ifd.good()) {
+ if (handle->fmt == UHDR_IMG_FMT_24bppYCbCrP010) {
+ const int bpp = 2;
+ ifd.read(static_cast<char*>(handle->planes[UHDR_PLANE_Y]), handle->w * handle->h * bpp);
+ ifd.read(static_cast<char*>(handle->planes[UHDR_PLANE_UV]),
+ (handle->w / 2) * (handle->h / 2) * bpp * 2);
+ return true;
+ } else if (handle->fmt == UHDR_IMG_FMT_12bppYCbCr420) {
+ ifd.read(static_cast<char*>(handle->planes[UHDR_PLANE_Y]), handle->w * handle->h);
+ ifd.read(static_cast<char*>(handle->planes[UHDR_PLANE_U]), (handle->w / 2) * (handle->h / 2));
+ ifd.read(static_cast<char*>(handle->planes[UHDR_PLANE_V]), (handle->w / 2) * (handle->h / 2));
+ return true;
+ }
+ return false;
+ }
+ std::cerr << "unable to open file : " << filename << std::endl;
+ return false;
+}
+
static bool writeFile(const char* filename, void*& result, int length) {
std::ofstream ofd(filename, std::ios::binary);
if (ofd.is_open()) {
@@ -168,54 +191,115 @@ static bool writeFile(const char* filename, void*& result, int length) {
return false;
}
+static bool writeFile(const char* filename, uhdr_raw_image_t* img) {
+ std::ofstream ofd(filename, std::ios::binary);
+ if (ofd.is_open()) {
+ if (img->fmt == UHDR_IMG_FMT_32bppRGBA8888 || img->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat ||
+ img->fmt == UHDR_IMG_FMT_32bppRGBA1010102) {
+ char* data = static_cast<char*>(img->planes[UHDR_PLANE_PACKED]);
+ int bpp = img->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat ? 8 : 4;
+ const size_t stride = img->stride[UHDR_PLANE_PACKED] * bpp;
+ const size_t length = img->w * bpp;
+ for (unsigned i = 0; i < img->h; i++, data += stride) {
+ ofd.write(data, length);
+ }
+ return true;
+ } else if ((int)img->fmt == UHDR_IMG_FMT_24bppYCbCr444 ||
+ (int)img->fmt == UHDR_IMG_FMT_48bppYCbCr444) {
+ char* data = static_cast<char*>(img->planes[UHDR_PLANE_Y]);
+ int bpp = (int)img->fmt == UHDR_IMG_FMT_48bppYCbCr444 ? 2 : 1;
+ size_t stride = img->stride[UHDR_PLANE_Y] * bpp;
+ size_t length = img->w * bpp;
+ for (unsigned i = 0; i < img->h; i++, data += stride) {
+ ofd.write(data, length);
+ }
+ data = static_cast<char*>(img->planes[UHDR_PLANE_U]);
+ stride = img->stride[UHDR_PLANE_U] * bpp;
+ for (unsigned i = 0; i < img->h; i++, data += stride) {
+ ofd.write(data, length);
+ }
+ data = static_cast<char*>(img->planes[UHDR_PLANE_V]);
+ stride = img->stride[UHDR_PLANE_V] * bpp;
+ for (unsigned i = 0; i < img->h; i++, data += stride) {
+ ofd.write(data, length);
+ }
+ return true;
+ }
+ return false;
+ }
+ std::cerr << "unable to write to file : " << filename << std::endl;
+ return false;
+}
+
class UltraHdrAppInput {
public:
UltraHdrAppInput(const char* p010File, const char* yuv420File, const char* yuv420JpegFile,
- size_t width, size_t height,
- ultrahdr_color_gamut p010Cg = ULTRAHDR_COLORGAMUT_BT709,
- ultrahdr_color_gamut yuv420Cg = ULTRAHDR_COLORGAMUT_BT709,
- ultrahdr_transfer_function tf = ULTRAHDR_TF_HLG, int quality = 100,
- ultrahdr_output_format of = ULTRAHDR_OUTPUT_HDR_HLG)
+ const char* gainmapJpegFile, const char* gainmapMetadataCfgFile, size_t width,
+ size_t height, uhdr_color_gamut_t p010Cg = UHDR_CG_BT_709,
+ uhdr_color_gamut_t yuv420Cg = UHDR_CG_BT_709,
+ uhdr_color_transfer_t p010Tf = UHDR_CT_HLG, int quality = 100,
+ uhdr_color_transfer_t oTf = UHDR_CT_HLG,
+ uhdr_img_fmt_t oFmt = UHDR_IMG_FMT_32bppRGBA1010102)
: mP010File(p010File),
mYuv420File(yuv420File),
mYuv420JpegFile(yuv420JpegFile),
+ mGainMapJpegFile(gainmapJpegFile),
+ mGainMapMetadataCfgFile(gainmapMetadataCfgFile),
mJpegRFile(nullptr),
mWidth(width),
mHeight(height),
mP010Cg(p010Cg),
mYuv420Cg(yuv420Cg),
- mTf(tf),
+ mP010Tf(p010Tf),
mQuality(quality),
- mOf(of),
+ mOTf(oTf),
+ mOfmt(oFmt),
mMode(0){};
- UltraHdrAppInput(const char* jpegRFile, ultrahdr_output_format of = ULTRAHDR_OUTPUT_HDR_HLG)
+ UltraHdrAppInput(const char* jpegRFile, uhdr_color_transfer_t oTf = UHDR_CT_HLG,
+ uhdr_img_fmt_t oFmt = UHDR_IMG_FMT_32bppRGBA1010102)
: mP010File(nullptr),
mYuv420File(nullptr),
mJpegRFile(jpegRFile),
mWidth(0),
mHeight(0),
- mP010Cg(ULTRAHDR_COLORGAMUT_UNSPECIFIED),
- mYuv420Cg(ULTRAHDR_COLORGAMUT_UNSPECIFIED),
- mTf(ULTRAHDR_TF_UNSPECIFIED),
+ mP010Cg(UHDR_CG_UNSPECIFIED),
+ mYuv420Cg(UHDR_CG_UNSPECIFIED),
+ mP010Tf(UHDR_CT_UNSPECIFIED),
mQuality(100),
- mOf(of),
+ mOTf(oTf),
+ mOfmt(oFmt),
mMode(1){};
~UltraHdrAppInput() {
- if (mRawP010Image.data) free(mRawP010Image.data);
- if (mRawP010Image.chroma_data) free(mRawP010Image.chroma_data);
- if (mRawRgba1010102Image.data) free(mRawRgba1010102Image.data);
- if (mRawRgba1010102Image.chroma_data) free(mRawRgba1010102Image.chroma_data);
- if (mRawYuv420Image.data) free(mRawYuv420Image.data);
- if (mRawYuv420Image.chroma_data) free(mRawYuv420Image.chroma_data);
- if (mRawRgba8888Image.data) free(mRawRgba8888Image.data);
- if (mRawRgba8888Image.chroma_data) free(mRawRgba8888Image.chroma_data);
+ int count = sizeof mRawP010Image.planes / sizeof mRawP010Image.planes[UHDR_PLANE_Y];
+ for (int i = 0; i < count; i++) {
+ if (mRawP010Image.planes[i]) {
+ free(mRawP010Image.planes[i]);
+ mRawP010Image.planes[i] = nullptr;
+ }
+ if (mRawRgba1010102Image.planes[i]) {
+ free(mRawRgba1010102Image.planes[i]);
+ mRawRgba1010102Image.planes[i] = nullptr;
+ }
+ if (mRawYuv420Image.planes[i]) {
+ free(mRawYuv420Image.planes[i]);
+ mRawYuv420Image.planes[i] = nullptr;
+ }
+ if (mRawRgba8888Image.planes[i]) {
+ free(mRawRgba8888Image.planes[i]);
+ mRawRgba8888Image.planes[i] = nullptr;
+ }
+ if (mDestImage.planes[i]) {
+ free(mDestImage.planes[i]);
+ mDestImage.planes[i] = nullptr;
+ }
+ if (mDestYUV444Image.planes[i]) {
+ free(mDestYUV444Image.planes[i]);
+ mDestYUV444Image.planes[i] = nullptr;
+ }
+ }
if (mJpegImgR.data) free(mJpegImgR.data);
- if (mDestImage.data) free(mDestImage.data);
- if (mDestImage.chroma_data) free(mDestImage.chroma_data);
- if (mDestYUV444Image.data) free(mDestYUV444Image.data);
- if (mDestYUV444Image.chroma_data) free(mDestYUV444Image.chroma_data);
}
bool fillJpegRImageHandle();
@@ -223,6 +307,8 @@ class UltraHdrAppInput {
bool convertP010ToRGBImage();
bool fillYuv420ImageHandle();
bool fillYuv420JpegImageHandle();
+ bool fillGainMapJpegImageHandle();
+ bool fillGainMapMetadataDescriptor();
bool convertYuv420ToRGBImage();
bool convertRgba8888ToYUV444Image();
bool convertRgba1010102ToYUV444Image();
@@ -236,65 +322,145 @@ class UltraHdrAppInput {
const char* mP010File;
const char* mYuv420File;
const char* mYuv420JpegFile;
+ const char *mGainMapJpegFile;
+ const char *mGainMapMetadataCfgFile;
const char* mJpegRFile;
const int mWidth;
const int mHeight;
- const ultrahdr_color_gamut mP010Cg;
- const ultrahdr_color_gamut mYuv420Cg;
- const ultrahdr_transfer_function mTf;
+ const uhdr_color_gamut_t mP010Cg;
+ const uhdr_color_gamut_t mYuv420Cg;
+ const uhdr_color_transfer_t mP010Tf;
const int mQuality;
- const ultrahdr_output_format mOf;
+ const uhdr_color_transfer_t mOTf;
+ const uhdr_img_fmt mOfmt;
const int mMode;
- jpegr_uncompressed_struct mRawP010Image{};
- jpegr_uncompressed_struct mRawRgba1010102Image{};
- jpegr_uncompressed_struct mRawYuv420Image{};
- jpegr_compressed_struct mYuv420JpegImage{};
- jpegr_uncompressed_struct mRawRgba8888Image{};
- jpegr_compressed_struct mJpegImgR{};
- jpegr_uncompressed_struct mDestImage{};
- jpegr_uncompressed_struct mDestYUV444Image{};
+
+ uhdr_raw_image_t mRawP010Image{};
+ uhdr_raw_image_t mRawRgba1010102Image{};
+ uhdr_raw_image_t mRawYuv420Image{};
+ uhdr_compressed_image_t mYuv420JpegImage{};
+ uhdr_compressed_image_t mGainMapJpegImage{};
+ uhdr_gainmap_metadata mMetadata{};
+ uhdr_raw_image_t mRawRgba8888Image{};
+ uhdr_compressed_image_t mJpegImgR{};
+ uhdr_raw_image_t mDestImage{};
+ uhdr_raw_image_t mDestYUV444Image{};
double mPsnr[3]{};
};
bool UltraHdrAppInput::fillP010ImageHandle() {
const int bpp = 2;
int p010Size = mWidth * mHeight * bpp * 1.5;
- mRawP010Image.width = mWidth;
- mRawP010Image.height = mHeight;
- mRawP010Image.colorGamut = mP010Cg;
- return loadFile(mP010File, mRawP010Image.data, p010Size);
+ mRawP010Image.fmt = UHDR_IMG_FMT_24bppYCbCrP010;
+ mRawP010Image.cg = mP010Cg;
+ mRawP010Image.ct = mP010Tf;
+ mRawP010Image.range = UHDR_CR_LIMITED_RANGE;
+ mRawP010Image.w = mWidth;
+ mRawP010Image.h = mHeight;
+ mRawP010Image.planes[UHDR_PLANE_Y] = malloc(mWidth * mHeight * bpp);
+ mRawP010Image.planes[UHDR_PLANE_UV] = malloc((mWidth / 2) * (mHeight / 2) * bpp * 2);
+ mRawP010Image.planes[UHDR_PLANE_V] = nullptr;
+ mRawP010Image.stride[UHDR_PLANE_Y] = mWidth;
+ mRawP010Image.stride[UHDR_PLANE_UV] = mWidth;
+ mRawP010Image.stride[UHDR_PLANE_V] = 0;
+ return loadFile(mP010File, &mRawP010Image);
}
bool UltraHdrAppInput::fillYuv420ImageHandle() {
int yuv420Size = mWidth * mHeight * 1.5;
- mRawYuv420Image.width = mWidth;
- mRawYuv420Image.height = mHeight;
- mRawYuv420Image.colorGamut = mYuv420Cg;
- return loadFile(mYuv420File, mRawYuv420Image.data, yuv420Size);
+ mRawYuv420Image.fmt = UHDR_IMG_FMT_12bppYCbCr420;
+ mRawYuv420Image.cg = mYuv420Cg;
+ mRawYuv420Image.ct = UHDR_CT_SRGB;
+ mRawYuv420Image.range = UHDR_CR_FULL_RANGE;
+ mRawYuv420Image.w = mWidth;
+ mRawYuv420Image.h = mHeight;
+ mRawYuv420Image.planes[UHDR_PLANE_Y] = malloc(mWidth * mHeight);
+ mRawYuv420Image.planes[UHDR_PLANE_U] = malloc((mWidth / 2) * (mHeight / 2));
+ mRawYuv420Image.planes[UHDR_PLANE_V] = malloc((mWidth / 2) * (mHeight / 2));
+ mRawYuv420Image.stride[UHDR_PLANE_Y] = mWidth;
+ mRawYuv420Image.stride[UHDR_PLANE_U] = mWidth / 2;
+ mRawYuv420Image.stride[UHDR_PLANE_V] = mWidth / 2;
+ return loadFile(mYuv420File, &mRawYuv420Image);
}
bool UltraHdrAppInput::fillYuv420JpegImageHandle() {
std::ifstream ifd(mYuv420JpegFile, std::ios::binary | std::ios::ate);
if (ifd.good()) {
int size = ifd.tellg();
- mYuv420JpegImage.length = size;
- mYuv420JpegImage.maxLength = size;
+ mYuv420JpegImage.capacity = size;
+ mYuv420JpegImage.data_sz = size;
mYuv420JpegImage.data = nullptr;
- mYuv420JpegImage.colorGamut = mYuv420Cg;
+ mYuv420JpegImage.cg = mYuv420Cg;
+ mYuv420JpegImage.ct = UHDR_CT_UNSPECIFIED;
+ mYuv420JpegImage.range = UHDR_CR_UNSPECIFIED;
ifd.close();
return loadFile(mYuv420JpegFile, mYuv420JpegImage.data, size);
}
return false;
}
+bool UltraHdrAppInput::fillGainMapJpegImageHandle() {
+ std::ifstream ifd(mGainMapJpegFile, std::ios::binary | std::ios::ate);
+ if (ifd.good()) {
+ int size = ifd.tellg();
+ mGainMapJpegImage.capacity = size;
+ mGainMapJpegImage.data_sz = size;
+ mGainMapJpegImage.data = nullptr;
+ mGainMapJpegImage.cg = UHDR_CG_UNSPECIFIED;
+ mGainMapJpegImage.ct = UHDR_CT_UNSPECIFIED;
+ mGainMapJpegImage.range = UHDR_CR_UNSPECIFIED;
+ ifd.close();
+ return loadFile(mGainMapJpegFile, mGainMapJpegImage.data, size);
+ }
+ return false;
+}
+
+void parse_argument(uhdr_gainmap_metadata* metadata, char* argument, float* value) {
+ if (!strcmp(argument, "maxContentBoost"))
+ metadata->max_content_boost = *value;
+ else if (!strcmp(argument, "minContentBoost"))
+ metadata->min_content_boost = *value;
+ else if (!strcmp(argument, "gamma"))
+ metadata->gamma = *value;
+ else if (!strcmp(argument, "offsetSdr"))
+ metadata->offset_sdr = *value;
+ else if (!strcmp(argument, "offsetHdr"))
+ metadata->offset_hdr = *value;
+ else if (!strcmp(argument, "hdrCapacityMin"))
+ metadata->hdr_capacity_min = *value;
+ else if (!strcmp(argument, "hdrCapacityMax"))
+ metadata->hdr_capacity_max = *value;
+ else
+ std::cout << " Ignoring argument " << argument << std::endl;
+}
+
+bool UltraHdrAppInput::fillGainMapMetadataDescriptor() {
+ std::ifstream file(mGainMapMetadataCfgFile);
+ if (!file.is_open()) {
+ return false;
+ }
+ std::string line;
+ char argument[128];
+ float value;
+ while (std::getline(file, line)) {
+ if (sscanf(line.c_str(), "--%s %f", argument, &value) == 2) {
+ parse_argument(&mMetadata, argument, &value);
+ }
+ }
+ file.close();
+ return true;
+}
+
bool UltraHdrAppInput::fillJpegRImageHandle() {
std::ifstream ifd(mJpegRFile, std::ios::binary | std::ios::ate);
if (ifd.good()) {
int size = ifd.tellg();
- mJpegImgR.length = size;
- mJpegImgR.maxLength = size;
+ mJpegImgR.capacity = size;
+ mJpegImgR.data_sz = size;
mJpegImgR.data = nullptr;
- mJpegImgR.colorGamut = mYuv420Cg;
+ mYuv420JpegImage.cg = UHDR_CG_UNSPECIFIED;
+ mYuv420JpegImage.ct = UHDR_CT_UNSPECIFIED;
+ mYuv420JpegImage.range = UHDR_CR_UNSPECIFIED;
ifd.close();
return loadFile(mJpegRFile, mJpegImgR.data, size);
}
@@ -302,140 +468,187 @@ bool UltraHdrAppInput::fillJpegRImageHandle() {
}
bool UltraHdrAppInput::encode() {
- if (!fillP010ImageHandle()) return false;
- if (mYuv420File != nullptr && !fillYuv420ImageHandle()) return false;
- if (mYuv420JpegFile != nullptr && !fillYuv420JpegImageHandle()) return false;
-
- mJpegImgR.maxLength = (std::max)(static_cast<size_t>(8 * 1024) /* min size 8kb */,
- mRawP010Image.width * mRawP010Image.height * 3 * 2);
- mJpegImgR.data = malloc(mJpegImgR.maxLength);
- if (mJpegImgR.data == nullptr) {
- std::cerr << "unable to allocate memory to store compressed image" << std::endl;
- return false;
+#define RET_IF_ERR(x) \
+ { \
+ uhdr_error_info_t status = (x); \
+ if (status.error_code != UHDR_CODEC_OK) { \
+ if (status.has_detail) { \
+ std::cerr << status.detail << std::endl; \
+ } \
+ uhdr_release_encoder(handle); \
+ return false; \
+ } \
}
- JpegR jpegHdr;
- status_t status = JPEGR_UNKNOWN_ERROR;
+ uhdr_codec_private_t* handle = uhdr_create_encoder();
+ if (mP010File != nullptr) {
+ if (!fillP010ImageHandle()) {
+ std::cerr << " failed to load file " << mP010File << std::endl;
+ return false;
+ }
+ RET_IF_ERR(uhdr_enc_set_raw_image(handle, &mRawP010Image, UHDR_HDR_IMG))
+ }
+ if (mYuv420File != nullptr) {
+ if (!fillYuv420ImageHandle()) {
+ std::cerr << " failed to load file " << mYuv420File << std::endl;
+ return false;
+ }
+ RET_IF_ERR(uhdr_enc_set_raw_image(handle, &mRawYuv420Image, UHDR_SDR_IMG))
+ }
+ if (mYuv420JpegFile != nullptr) {
+ if (!fillYuv420JpegImageHandle()) {
+ std::cerr << " failed to load file " << mYuv420JpegFile << std::endl;
+ return false;
+ }
+ RET_IF_ERR(uhdr_enc_set_compressed_image(
+ handle, &mYuv420JpegImage,
+ (mGainMapJpegFile != nullptr && mGainMapMetadataCfgFile != nullptr) ? UHDR_BASE_IMG
+ : UHDR_SDR_IMG))
+ }
+ if (mGainMapJpegFile != nullptr && mGainMapMetadataCfgFile != nullptr) {
+ if (!fillGainMapJpegImageHandle()) {
+ std::cerr << " failed to load file " << mGainMapJpegFile << std::endl;
+ return false;
+ }
+ if (!fillGainMapMetadataDescriptor()) {
+ std::cerr << " failed to read config file " << mGainMapMetadataCfgFile << std::endl;
+ return false;
+ }
+ RET_IF_ERR(uhdr_enc_set_gainmap_image(handle, &mGainMapJpegImage, &mMetadata))
+ }
+ RET_IF_ERR(uhdr_enc_set_quality(handle, mQuality, UHDR_BASE_IMG))
#ifdef PROFILE_ENABLE
const int profileCount = 10;
Profiler profileEncode;
profileEncode.timerStart();
for (auto i = 0; i < profileCount; i++) {
#endif
- if (mYuv420File == nullptr && mYuv420JpegFile == nullptr) { // api-0
- status = jpegHdr.encodeJPEGR(&mRawP010Image, mTf, &mJpegImgR, mQuality, nullptr);
- if (JPEGR_NO_ERROR != status) {
- std::cerr << "Encountered error during encodeJPEGR call, error code " << status
- << std::endl;
- return false;
- }
- } else if (mYuv420File != nullptr && mYuv420JpegFile == nullptr) { // api-1
- status =
- jpegHdr.encodeJPEGR(&mRawP010Image, &mRawYuv420Image, mTf, &mJpegImgR, mQuality, nullptr);
- if (JPEGR_NO_ERROR != status) {
- std::cerr << "Encountered error during encodeJPEGR call, error code " << status
- << std::endl;
- return false;
- }
- } else if (mYuv420File != nullptr && mYuv420JpegFile != nullptr) { // api-2
- status =
- jpegHdr.encodeJPEGR(&mRawP010Image, &mRawYuv420Image, &mYuv420JpegImage, mTf, &mJpegImgR);
- if (JPEGR_NO_ERROR != status) {
- std::cerr << "Encountered error during encodeJPEGR call, error code " << status
- << std::endl;
- return false;
- }
- } else if (mYuv420File == nullptr && mYuv420JpegFile != nullptr) { // api-3
- status = jpegHdr.encodeJPEGR(&mRawP010Image, &mYuv420JpegImage, mTf, &mJpegImgR);
- if (JPEGR_NO_ERROR != status) {
- std::cerr << "Encountered error during encodeJPEGR call, error code " << status
- << std::endl;
- return false;
- }
- }
+ RET_IF_ERR(uhdr_encode(handle))
#ifdef PROFILE_ENABLE
}
profileEncode.timerStop();
auto avgEncTime = profileEncode.elapsedTime() / (profileCount * 1000.f);
printf("Average encode time for res %d x %d is %f ms \n", mWidth, mHeight, avgEncTime);
#endif
- writeFile("out.jpeg", mJpegImgR.data, mJpegImgR.length);
+
+#undef RET_IF_ERR
+
+ auto output = uhdr_get_encoded_stream(handle);
+
+ // for decoding
+ mJpegImgR.data = malloc(output->data_sz);
+ memcpy(mJpegImgR.data, output->data, output->data_sz);
+ mJpegImgR.capacity = mJpegImgR.data_sz = output->data_sz;
+ mJpegImgR.cg = output->cg;
+ mJpegImgR.ct = output->ct;
+ mJpegImgR.range = output->range;
+ writeFile("out.jpeg", output->data, output->data_sz);
+ uhdr_release_encoder(handle);
+
return true;
}
bool UltraHdrAppInput::decode() {
- if (mMode == 1 && !fillJpegRImageHandle()) return false;
- std::vector<uint8_t> iccData(0);
- std::vector<uint8_t> exifData(0);
- jpegr_info_struct info{};
- JpegR jpegHdr;
- status_t status = jpegHdr.getJPEGRInfo(&mJpegImgR, &info);
- if (JPEGR_NO_ERROR == status) {
- size_t outSize = info.width * info.height * ((mOf == ULTRAHDR_OUTPUT_HDR_LINEAR) ? 8 : 4);
- mDestImage.data = malloc(outSize);
- if (mDestImage.data == nullptr) {
- std::cerr << "failed to allocate memory to store decoded output" << std::endl;
- return false;
- }
+ if (mMode == 1 && !fillJpegRImageHandle()) {
+ std::cerr << " failed to load file " << mJpegRFile << std::endl;
+ return false;
+ }
+
+#define RET_IF_ERR(x) \
+ { \
+ uhdr_error_info_t status = (x); \
+ if (status.error_code != UHDR_CODEC_OK) { \
+ if (status.has_detail) { \
+ std::cerr << status.detail << std::endl; \
+ } \
+ uhdr_release_decoder(handle); \
+ return false; \
+ } \
+ }
+
+ uhdr_codec_private_t* handle = uhdr_create_decoder();
+ RET_IF_ERR(uhdr_dec_set_image(handle, &mJpegImgR))
+ RET_IF_ERR(uhdr_dec_set_out_color_transfer(handle, mOTf))
+ RET_IF_ERR(uhdr_dec_set_out_img_format(handle, mOfmt))
+
#ifdef PROFILE_ENABLE
- const int profileCount = 10;
- Profiler profileDecode;
- profileDecode.timerStart();
- for (auto i = 0; i < profileCount; i++) {
+ const int profileCount = 10;
+ Profiler profileDecode;
+ profileDecode.timerStart();
+ for (auto i = 0; i < profileCount; i++) {
#endif
- status =
- jpegHdr.decodeJPEGR(&mJpegImgR, &mDestImage, FLT_MAX, nullptr, mOf, nullptr, nullptr);
- if (JPEGR_NO_ERROR != status) {
- std::cerr << "Encountered error during decodeJPEGR call, error code " << status
- << std::endl;
- return false;
- }
+ RET_IF_ERR(uhdr_decode(handle))
#ifdef PROFILE_ENABLE
- }
- profileDecode.timerStop();
- auto avgDecTime = profileDecode.elapsedTime() / (profileCount * 1000.f);
- printf("Average decode time for res %ld x %ld is %f ms \n", info.width, info.height,
- avgDecTime);
+ }
+ profileDecode.timerStop();
+ auto avgDecTime = profileDecode.elapsedTime() / (profileCount * 1000.f);
+ printf("Average decode time for res %ld x %ld is %f ms \n", info.width, info.height, avgDecTime);
#endif
- writeFile("outrgb.raw", mDestImage.data, outSize);
- } else {
- std::cerr << "Encountered error during getJPEGRInfo call, error code " << status << std::endl;
- return false;
+
+#undef RET_IF_ERR
+
+ uhdr_raw_image_t* output = uhdr_get_decoded_image(handle);
+
+ mDestImage.fmt = output->fmt;
+ mDestImage.cg = output->cg;
+ mDestImage.ct = output->ct;
+ mDestImage.range = output->range;
+ mDestImage.w = output->w;
+ mDestImage.h = output->h;
+ int bpp = (output->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat) ? 8 : 4;
+ mDestImage.planes[UHDR_PLANE_PACKED] = malloc(output->w * output->h * bpp);
+ char* inData = static_cast<char*>(output->planes[UHDR_PLANE_PACKED]);
+ char* outData = static_cast<char*>(mDestImage.planes[UHDR_PLANE_PACKED]);
+ const size_t inStride = output->stride[UHDR_PLANE_PACKED] * bpp;
+ const size_t outStride = output->w * bpp;
+ mDestImage.stride[UHDR_PLANE_PACKED] = output->w;
+ const size_t length = output->w * bpp;
+ for (unsigned i = 0; i < output->h; i++, inData += inStride, outData += outStride) {
+ memcpy(outData, inData, length);
}
+ writeFile("outrgb.raw", output);
+ uhdr_release_decoder(handle);
+
return true;
}
+#define CLIP3(x, min, max) ((x) < (min)) ? (min) : ((x) > (max)) ? (max) : (x)
bool UltraHdrAppInput::convertP010ToRGBImage() {
const float* coeffs = BT2020YUVtoRGBMatrix;
- if (mP010Cg == ULTRAHDR_COLORGAMUT_BT709) {
+ if (mP010Cg == UHDR_CG_BT_709) {
coeffs = BT709YUVtoRGBMatrix;
- } else if (mP010Cg == ULTRAHDR_COLORGAMUT_BT2100) {
+ } else if (mP010Cg == UHDR_CG_BT_2100) {
coeffs = BT2020YUVtoRGBMatrix;
- } else if (mP010Cg == ULTRAHDR_COLORGAMUT_P3) {
+ } else if (mP010Cg == UHDR_CG_DISPLAY_P3) {
coeffs = BT601YUVtoRGBMatrix;
} else {
std::cerr << "color matrix not present for gamut " << mP010Cg << " using BT2020Matrix"
<< std::endl;
}
- mRawRgba1010102Image.data = malloc(mRawP010Image.width * mRawP010Image.height * 4);
- if (mRawRgba1010102Image.data == nullptr) {
- std::cerr << "failed to allocate memory to store Rgba1010102" << std::endl;
- return false;
- }
- mRawRgba1010102Image.width = mRawP010Image.width;
- mRawRgba1010102Image.height = mRawP010Image.height;
- mRawRgba1010102Image.colorGamut = mRawP010Image.colorGamut;
- uint32_t* rgbData = static_cast<uint32_t*>(mRawRgba1010102Image.data);
- uint16_t* y = static_cast<uint16_t*>(mRawP010Image.data);
- uint16_t* u = y + mRawP010Image.width * mRawP010Image.height;
+ mRawRgba1010102Image.fmt = UHDR_IMG_FMT_32bppRGBA1010102;
+ mRawRgba1010102Image.cg = mRawP010Image.cg;
+ mRawRgba1010102Image.ct = mRawP010Image.ct;
+ mRawRgba1010102Image.range = UHDR_CR_FULL_RANGE;
+ mRawRgba1010102Image.w = mRawP010Image.w;
+ mRawRgba1010102Image.h = mRawP010Image.h;
+ mRawRgba1010102Image.planes[UHDR_PLANE_PACKED] = malloc(mRawP010Image.w * mRawP010Image.h * 4);
+ mRawRgba1010102Image.planes[UHDR_PLANE_U] = nullptr;
+ mRawRgba1010102Image.planes[UHDR_PLANE_V] = nullptr;
+ mRawRgba1010102Image.stride[UHDR_PLANE_PACKED] = mWidth;
+ mRawRgba1010102Image.stride[UHDR_PLANE_U] = 0;
+ mRawRgba1010102Image.stride[UHDR_PLANE_V] = 0;
+
+ uint32_t* rgbData = static_cast<uint32_t*>(mRawRgba1010102Image.planes[UHDR_PLANE_PACKED]);
+ uint16_t* y = static_cast<uint16_t*>(mRawP010Image.planes[UHDR_PLANE_Y]);
+ uint16_t* u = static_cast<uint16_t*>(mRawP010Image.planes[UHDR_PLANE_UV]);
uint16_t* v = u + 1;
- for (size_t i = 0; i < mRawP010Image.height; i++) {
- for (size_t j = 0; j < mRawP010Image.width; j++) {
- float y0 = float(y[mRawP010Image.width * i + j] >> 6);
- float u0 = float(u[mRawP010Image.width * (i / 2) + (j / 2) * 2] >> 6);
- float v0 = float(v[mRawP010Image.width * (i / 2) + (j / 2) * 2] >> 6);
+ for (size_t i = 0; i < mRawP010Image.h; i++) {
+ for (size_t j = 0; j < mRawP010Image.w; j++) {
+ float y0 = float(y[mRawP010Image.stride[UHDR_PLANE_Y] * i + j] >> 6);
+ float u0 = float(u[mRawP010Image.stride[UHDR_PLANE_UV] * (i / 2) + (j / 2) * 2] >> 6);
+ float v0 = float(v[mRawP010Image.stride[UHDR_PLANE_UV] * (i / 2) + (j / 2) * 2] >> 6);
y0 = CLIP3(y0, 64.0f, 940.0f);
u0 = CLIP3(u0, 64.0f, 960.0f);
@@ -462,31 +675,35 @@ bool UltraHdrAppInput::convertP010ToRGBImage() {
rgbData++;
}
}
- writeFile("inRgba1010102.raw", mRawRgba1010102Image.data,
- mRawP010Image.width * mRawP010Image.height * 4);
+ writeFile("inRgba1010102.raw", &mRawRgba1010102Image);
return true;
}
bool UltraHdrAppInput::convertYuv420ToRGBImage() {
- mRawRgba8888Image.data = malloc(mRawYuv420Image.width * mRawYuv420Image.height * 4);
- if (mRawRgba8888Image.data == nullptr) {
- std::cerr << "failed to allocate memory to store rgba888" << std::endl;
- return false;
- }
- mRawRgba8888Image.width = mRawYuv420Image.width;
- mRawRgba8888Image.height = mRawYuv420Image.height;
- mRawRgba8888Image.colorGamut = mRawYuv420Image.colorGamut;
- uint32_t* rgbData = static_cast<uint32_t*>(mRawRgba8888Image.data);
- uint8_t* y = static_cast<uint8_t*>(mRawYuv420Image.data);
- uint8_t* u = y + (mRawYuv420Image.width * mRawYuv420Image.height);
- uint8_t* v = u + (mRawYuv420Image.width * mRawYuv420Image.height / 4);
+ mRawRgba8888Image.fmt = UHDR_IMG_FMT_32bppRGBA8888;
+ mRawRgba8888Image.cg = mRawYuv420Image.cg;
+ mRawRgba8888Image.ct = mRawYuv420Image.ct;
+ mRawRgba8888Image.range = UHDR_CR_FULL_RANGE;
+ mRawRgba8888Image.w = mRawYuv420Image.w;
+ mRawRgba8888Image.h = mRawYuv420Image.h;
+ mRawRgba8888Image.planes[UHDR_PLANE_PACKED] = malloc(mRawYuv420Image.w * mRawYuv420Image.h * 4);
+ mRawRgba8888Image.planes[UHDR_PLANE_U] = nullptr;
+ mRawRgba8888Image.planes[UHDR_PLANE_V] = nullptr;
+ mRawRgba8888Image.stride[UHDR_PLANE_PACKED] = mWidth;
+ mRawRgba8888Image.stride[UHDR_PLANE_U] = 0;
+ mRawRgba8888Image.stride[UHDR_PLANE_V] = 0;
+
+ uint32_t* rgbData = static_cast<uint32_t*>(mRawRgba8888Image.planes[UHDR_PLANE_PACKED]);
+ uint8_t* y = static_cast<uint8_t*>(mRawYuv420Image.planes[UHDR_PLANE_Y]);
+ uint8_t* u = static_cast<uint8_t*>(mRawYuv420Image.planes[UHDR_PLANE_U]);
+ uint8_t* v = static_cast<uint8_t*>(mRawYuv420Image.planes[UHDR_PLANE_V]);
const float* coeffs = BT601YUVtoRGBMatrix;
- for (size_t i = 0; i < mRawYuv420Image.height; i++) {
- for (size_t j = 0; j < mRawYuv420Image.width; j++) {
- float y0 = float(y[mRawYuv420Image.width * i + j]);
- float u0 = float(u[mRawYuv420Image.width / 2 * (i / 2) + (j / 2)] - 128);
- float v0 = float(v[mRawYuv420Image.width / 2 * (i / 2) + (j / 2)] - 128);
+ for (size_t i = 0; i < mRawYuv420Image.h; i++) {
+ for (size_t j = 0; j < mRawYuv420Image.w; j++) {
+ float y0 = float(y[mRawYuv420Image.stride[UHDR_PLANE_Y] * i + j]);
+ float u0 = float(u[mRawYuv420Image.stride[UHDR_PLANE_U] * (i / 2) + (j / 2)] - 128);
+ float v0 = float(v[mRawYuv420Image.stride[UHDR_PLANE_V] * (i / 2) + (j / 2)] - 128);
y0 /= 255.0f;
u0 /= 255.0f;
@@ -512,33 +729,36 @@ bool UltraHdrAppInput::convertYuv420ToRGBImage() {
rgbData++;
}
}
- writeFile("inRgba8888.raw", mRawRgba8888Image.data,
- mRawYuv420Image.width * mRawYuv420Image.height * 4);
+ writeFile("inRgba8888.raw", &mRawRgba8888Image);
return true;
}
bool UltraHdrAppInput::convertRgba8888ToYUV444Image() {
- mDestYUV444Image.data = malloc(mDestImage.width * mDestImage.height * 3);
- if (mDestYUV444Image.data == nullptr) {
- std::cerr << "failed to allocate memory to store yuv444" << std::endl;
- return false;
- }
- mDestYUV444Image.width = mDestImage.width;
- mDestYUV444Image.height = mDestImage.height;
- mDestYUV444Image.colorGamut = mDestImage.colorGamut;
-
- uint32_t* rgbData = static_cast<uint32_t*>(mDestImage.data);
-
- uint8_t* yData = static_cast<uint8_t*>(mDestYUV444Image.data);
- uint8_t* uData = yData + (mDestYUV444Image.width * mDestYUV444Image.height);
- uint8_t* vData = uData + (mDestYUV444Image.width * mDestYUV444Image.height);
+ mDestYUV444Image.fmt = static_cast<uhdr_img_fmt_t>(UHDR_IMG_FMT_24bppYCbCr444);
+ mDestYUV444Image.cg = mDestImage.cg;
+ mDestYUV444Image.ct = mDestImage.ct;
+ mDestYUV444Image.range = UHDR_CR_FULL_RANGE;
+ mDestYUV444Image.w = mDestImage.w;
+ mDestYUV444Image.h = mDestImage.h;
+ mDestYUV444Image.planes[UHDR_PLANE_Y] = malloc(mDestImage.w * mDestImage.h);
+ mDestYUV444Image.planes[UHDR_PLANE_U] = malloc(mDestImage.w * mDestImage.h);
+ mDestYUV444Image.planes[UHDR_PLANE_V] = malloc(mDestImage.w * mDestImage.h);
+ mDestYUV444Image.stride[UHDR_PLANE_Y] = mWidth;
+ mDestYUV444Image.stride[UHDR_PLANE_U] = mWidth;
+ mDestYUV444Image.stride[UHDR_PLANE_V] = mWidth;
+
+ uint32_t* rgbData = static_cast<uint32_t*>(mDestImage.planes[UHDR_PLANE_PACKED]);
+
+ uint8_t* yData = static_cast<uint8_t*>(mDestYUV444Image.planes[UHDR_PLANE_Y]);
+ uint8_t* uData = static_cast<uint8_t*>(mDestYUV444Image.planes[UHDR_PLANE_U]);
+ uint8_t* vData = static_cast<uint8_t*>(mDestYUV444Image.planes[UHDR_PLANE_V]);
const float* coeffs = BT601RGBtoYUVMatrix;
- for (size_t i = 0; i < mDestImage.height; i++) {
- for (size_t j = 0; j < mDestImage.width; j++) {
- float r0 = float(rgbData[mDestImage.width * i + j] & 0xff);
- float g0 = float((rgbData[mDestImage.width * i + j] >> 8) & 0xff);
- float b0 = float((rgbData[mDestImage.width * i + j] >> 16) & 0xff);
+ for (size_t i = 0; i < mDestImage.h; i++) {
+ for (size_t j = 0; j < mDestImage.w; j++) {
+ float r0 = float(rgbData[mDestImage.stride[UHDR_PLANE_PACKED] * i + j] & 0xff);
+ float g0 = float((rgbData[mDestImage.stride[UHDR_PLANE_PACKED] * i + j] >> 8) & 0xff);
+ float b0 = float((rgbData[mDestImage.stride[UHDR_PLANE_PACKED] * i + j] >> 16) & 0xff);
r0 /= 255.0f;
g0 /= 255.0f;
@@ -556,49 +776,52 @@ bool UltraHdrAppInput::convertRgba8888ToYUV444Image() {
u = CLIP3(u, 0.0f, 255.0f);
v = CLIP3(v, 0.0f, 255.0f);
- yData[mDestYUV444Image.width * i + j] = uint8_t(y);
- uData[mDestYUV444Image.width * i + j] = uint8_t(u);
- vData[mDestYUV444Image.width * i + j] = uint8_t(v);
+ yData[mDestYUV444Image.stride[UHDR_PLANE_Y] * i + j] = uint8_t(y);
+ uData[mDestYUV444Image.stride[UHDR_PLANE_U] * i + j] = uint8_t(u);
+ vData[mDestYUV444Image.stride[UHDR_PLANE_V] * i + j] = uint8_t(v);
}
}
- writeFile("outyuv444.yuv", mDestYUV444Image.data,
- mDestYUV444Image.width * mDestYUV444Image.height * 3);
+ writeFile("outyuv444.yuv", &mDestYUV444Image);
return true;
}
bool UltraHdrAppInput::convertRgba1010102ToYUV444Image() {
const float* coeffs = BT2020RGBtoYUVMatrix;
- if (mP010Cg == ULTRAHDR_COLORGAMUT_BT709) {
+ if (mP010Cg == UHDR_CG_BT_709) {
coeffs = BT709RGBtoYUVMatrix;
- } else if (mP010Cg == ULTRAHDR_COLORGAMUT_BT2100) {
+ } else if (mP010Cg == UHDR_CG_BT_2100) {
coeffs = BT2020RGBtoYUVMatrix;
- } else if (mP010Cg == ULTRAHDR_COLORGAMUT_P3) {
+ } else if (mP010Cg == UHDR_CG_DISPLAY_P3) {
coeffs = BT601RGBtoYUVMatrix;
} else {
std::cerr << "color matrix not present for gamut " << mP010Cg << " using BT2020Matrix"
<< std::endl;
}
- mDestYUV444Image.data = malloc(mDestImage.width * mDestImage.height * 3 * 2);
- if (mDestYUV444Image.data == nullptr) {
- std::cerr << "failed to allocate memory to store yuv444" << std::endl;
- return false;
- }
- mDestYUV444Image.width = mDestImage.width;
- mDestYUV444Image.height = mDestImage.height;
- mDestYUV444Image.colorGamut = mDestImage.colorGamut;
-
- uint32_t* rgbData = static_cast<uint32_t*>(mDestImage.data);
-
- uint16_t* yData = static_cast<uint16_t*>(mDestYUV444Image.data);
- uint16_t* uData = yData + (mDestYUV444Image.width * mDestYUV444Image.height);
- uint16_t* vData = uData + (mDestYUV444Image.width * mDestYUV444Image.height);
-
- for (size_t i = 0; i < mDestImage.height; i++) {
- for (size_t j = 0; j < mDestImage.width; j++) {
- float r0 = float(rgbData[mDestImage.width * i + j] & 0x3ff);
- float g0 = float((rgbData[mDestImage.width * i + j] >> 10) & 0x3ff);
- float b0 = float((rgbData[mDestImage.width * i + j] >> 20) & 0x3ff);
+ mDestYUV444Image.fmt = static_cast<uhdr_img_fmt_t>(UHDR_IMG_FMT_48bppYCbCr444);
+ mDestYUV444Image.cg = mDestImage.cg;
+ mDestYUV444Image.ct = mDestImage.ct;
+ mDestYUV444Image.range = UHDR_CR_FULL_RANGE;
+ mDestYUV444Image.w = mDestImage.w;
+ mDestYUV444Image.h = mDestImage.h;
+ mDestYUV444Image.planes[UHDR_PLANE_Y] = malloc(mDestImage.w * mDestImage.h * 2);
+ mDestYUV444Image.planes[UHDR_PLANE_U] = malloc(mDestImage.w * mDestImage.h * 2);
+ mDestYUV444Image.planes[UHDR_PLANE_V] = malloc(mDestImage.w * mDestImage.h * 2);
+ mDestYUV444Image.stride[UHDR_PLANE_Y] = mWidth;
+ mDestYUV444Image.stride[UHDR_PLANE_U] = mWidth;
+ mDestYUV444Image.stride[UHDR_PLANE_V] = mWidth;
+
+ uint32_t* rgbData = static_cast<uint32_t*>(mDestImage.planes[UHDR_PLANE_PACKED]);
+
+ uint16_t* yData = static_cast<uint16_t*>(mDestYUV444Image.planes[UHDR_PLANE_Y]);
+ uint16_t* uData = static_cast<uint16_t*>(mDestYUV444Image.planes[UHDR_PLANE_U]);
+ uint16_t* vData = static_cast<uint16_t*>(mDestYUV444Image.planes[UHDR_PLANE_V]);
+
+ for (size_t i = 0; i < mDestImage.h; i++) {
+ for (size_t j = 0; j < mDestImage.w; j++) {
+ float r0 = float(rgbData[mDestImage.stride[UHDR_PLANE_PACKED] * i + j] & 0x3ff);
+ float g0 = float((rgbData[mDestImage.stride[UHDR_PLANE_PACKED] * i + j] >> 10) & 0x3ff);
+ float b0 = float((rgbData[mDestImage.stride[UHDR_PLANE_PACKED] * i + j] >> 20) & 0x3ff);
r0 /= 1023.0f;
g0 /= 1023.0f;
@@ -616,35 +839,33 @@ bool UltraHdrAppInput::convertRgba1010102ToYUV444Image() {
u = CLIP3(u, 64.0f, 960.0f);
v = CLIP3(v, 64.0f, 960.0f);
- yData[mDestYUV444Image.width * i + j] = uint16_t(y);
- uData[mDestYUV444Image.width * i + j] = uint16_t(u);
- vData[mDestYUV444Image.width * i + j] = uint16_t(v);
+ yData[mDestYUV444Image.stride[UHDR_PLANE_Y] * i + j] = uint16_t(y);
+ uData[mDestYUV444Image.stride[UHDR_PLANE_U] * i + j] = uint16_t(u);
+ vData[mDestYUV444Image.stride[UHDR_PLANE_V] * i + j] = uint16_t(v);
}
}
- writeFile("outyuv444.yuv", mDestYUV444Image.data,
- mDestYUV444Image.width * mDestYUV444Image.height * 3 * 2);
+ writeFile("outyuv444.yuv", &mDestYUV444Image);
return true;
}
void UltraHdrAppInput::computeRGBHdrPSNR() {
- if (mOf == ULTRAHDR_OUTPUT_SDR || mOf == ULTRAHDR_OUTPUT_HDR_LINEAR) {
- std::cout << "psnr not supported for output format " << mOf << std::endl;
+ if (mOfmt != UHDR_IMG_FMT_32bppRGBA1010102) {
+ std::cout << "psnr not supported for output format " << mOfmt << std::endl;
return;
}
- uint32_t* rgbDataSrc = static_cast<uint32_t*>(mRawRgba1010102Image.data);
- uint32_t* rgbDataDst = static_cast<uint32_t*>(mDestImage.data);
+ uint32_t* rgbDataSrc = static_cast<uint32_t*>(mRawRgba1010102Image.planes[UHDR_PLANE_PACKED]);
+ uint32_t* rgbDataDst = static_cast<uint32_t*>(mDestImage.planes[UHDR_PLANE_PACKED]);
if (rgbDataSrc == nullptr || rgbDataDst == nullptr) {
std::cerr << "invalid src or dst pointer for psnr computation " << std::endl;
return;
}
- if ((mOf == ULTRAHDR_OUTPUT_HDR_PQ && mTf != ULTRAHDR_TF_PQ) ||
- (mOf == ULTRAHDR_OUTPUT_HDR_HLG && mTf != ULTRAHDR_TF_HLG)) {
+ if (mOTf != mP010Tf) {
std::cout << "input transfer function and output format are not compatible, psnr results "
"may be unreliable"
<< std::endl;
}
uint64_t rSqError = 0, gSqError = 0, bSqError = 0;
- for (size_t i = 0; i < mRawP010Image.width * mRawP010Image.height; i++) {
+ for (size_t i = 0; i < mRawP010Image.w * mRawP010Image.h; i++) {
int rSrc = *rgbDataSrc & 0x3ff;
int rDst = *rgbDataDst & 0x3ff;
rSqError += (rSrc - rDst) * (rSrc - rDst);
@@ -660,13 +881,13 @@ void UltraHdrAppInput::computeRGBHdrPSNR() {
rgbDataSrc++;
rgbDataDst++;
}
- double meanSquareError = (double)rSqError / (mRawP010Image.width * mRawP010Image.height);
+ double meanSquareError = (double)rSqError / (mRawP010Image.w * mRawP010Image.h);
mPsnr[0] = meanSquareError ? 10 * log10((double)1023 * 1023 / meanSquareError) : 100;
- meanSquareError = (double)gSqError / (mRawP010Image.width * mRawP010Image.height);
+ meanSquareError = (double)gSqError / (mRawP010Image.w * mRawP010Image.h);
mPsnr[1] = meanSquareError ? 10 * log10((double)1023 * 1023 / meanSquareError) : 100;
- meanSquareError = (double)bSqError / (mRawP010Image.width * mRawP010Image.height);
+ meanSquareError = (double)bSqError / (mRawP010Image.w * mRawP010Image.h);
mPsnr[2] = meanSquareError ? 10 * log10((double)1023 * 1023 / meanSquareError) : 100;
std::cout << "psnr r :: " << mPsnr[0] << " psnr g :: " << mPsnr[1] << " psnr b :: " << mPsnr[2]
@@ -674,19 +895,19 @@ void UltraHdrAppInput::computeRGBHdrPSNR() {
}
void UltraHdrAppInput::computeRGBSdrPSNR() {
- if (mOf != ULTRAHDR_OUTPUT_SDR) {
- std::cout << "psnr not supported for output format " << mOf << std::endl;
+ if (mOfmt != UHDR_IMG_FMT_32bppRGBA8888) {
+ std::cout << "psnr not supported for output format " << mOfmt << std::endl;
return;
}
- uint32_t* rgbDataSrc = static_cast<uint32_t*>(mRawRgba8888Image.data);
- uint32_t* rgbDataDst = static_cast<uint32_t*>(mDestImage.data);
+ uint32_t* rgbDataSrc = static_cast<uint32_t*>(mRawRgba8888Image.planes[UHDR_PLANE_PACKED]);
+ uint32_t* rgbDataDst = static_cast<uint32_t*>(mDestImage.planes[UHDR_PLANE_PACKED]);
if (rgbDataSrc == nullptr || rgbDataDst == nullptr) {
std::cerr << "invalid src or dst pointer for psnr computation " << std::endl;
return;
}
uint64_t rSqError = 0, gSqError = 0, bSqError = 0;
- for (size_t i = 0; i < mRawYuv420Image.width * mRawYuv420Image.height; i++) {
+ for (size_t i = 0; i < mRawYuv420Image.w * mRawYuv420Image.h; i++) {
int rSrc = *rgbDataSrc & 0xff;
int rDst = *rgbDataDst & 0xff;
rSqError += (rSrc - rDst) * (rSrc - rDst);
@@ -702,13 +923,13 @@ void UltraHdrAppInput::computeRGBSdrPSNR() {
rgbDataSrc++;
rgbDataDst++;
}
- double meanSquareError = (double)rSqError / (mRawYuv420Image.width * mRawYuv420Image.height);
+ double meanSquareError = (double)rSqError / (mRawYuv420Image.w * mRawYuv420Image.h);
mPsnr[0] = meanSquareError ? 10 * log10((double)255 * 255 / meanSquareError) : 100;
- meanSquareError = (double)gSqError / (mRawYuv420Image.width * mRawYuv420Image.height);
+ meanSquareError = (double)gSqError / (mRawYuv420Image.w * mRawYuv420Image.h);
mPsnr[1] = meanSquareError ? 10 * log10((double)255 * 255 / meanSquareError) : 100;
- meanSquareError = (double)bSqError / (mRawYuv420Image.width * mRawYuv420Image.height);
+ meanSquareError = (double)bSqError / (mRawYuv420Image.w * mRawYuv420Image.h);
mPsnr[2] = meanSquareError ? 10 * log10((double)255 * 255 / meanSquareError) : 100;
std::cout << "psnr r :: " << mPsnr[0] << " psnr g :: " << mPsnr[1] << " psnr b :: " << mPsnr[2]
@@ -716,68 +937,67 @@ void UltraHdrAppInput::computeRGBSdrPSNR() {
}
void UltraHdrAppInput::computeYUVHdrPSNR() {
- if (mOf == ULTRAHDR_OUTPUT_SDR || mOf == ULTRAHDR_OUTPUT_HDR_LINEAR) {
- std::cout << "psnr not supported for output format " << mOf << std::endl;
+ if (mOfmt != UHDR_IMG_FMT_32bppRGBA1010102) {
+ std::cout << "psnr not supported for output format " << mOfmt << std::endl;
return;
}
- uint16_t* yuvDataSrc = static_cast<uint16_t*>(mRawP010Image.data);
- uint16_t* yuvDataDst = static_cast<uint16_t*>(mDestYUV444Image.data);
- if (yuvDataSrc == nullptr || yuvDataDst == nullptr) {
+ uint16_t* yDataSrc = static_cast<uint16_t*>(mRawP010Image.planes[UHDR_PLANE_Y]);
+ uint16_t* uDataSrc = static_cast<uint16_t*>(mRawP010Image.planes[UHDR_PLANE_UV]);
+ uint16_t* vDataSrc = uDataSrc + 1;
+
+ uint16_t* yDataDst = static_cast<uint16_t*>(mDestYUV444Image.planes[UHDR_PLANE_Y]);
+ uint16_t* uDataDst = static_cast<uint16_t*>(mDestYUV444Image.planes[UHDR_PLANE_U]);
+ uint16_t* vDataDst = static_cast<uint16_t*>(mDestYUV444Image.planes[UHDR_PLANE_V]);
+ if (yDataSrc == nullptr || uDataSrc == nullptr || yDataDst == nullptr || uDataDst == nullptr ||
+ vDataDst == nullptr) {
std::cerr << "invalid src or dst pointer for psnr computation " << std::endl;
return;
}
- if ((mOf == ULTRAHDR_OUTPUT_HDR_PQ && mTf != ULTRAHDR_TF_PQ) ||
- (mOf == ULTRAHDR_OUTPUT_HDR_HLG && mTf != ULTRAHDR_TF_HLG)) {
+ if (mOTf != mP010Tf) {
std::cout << "input transfer function and output format are not compatible, psnr results "
"may be unreliable"
<< std::endl;
}
- uint16_t* yDataSrc = static_cast<uint16_t*>(mRawP010Image.data);
- uint16_t* uDataSrc = yDataSrc + (mRawP010Image.width * mRawP010Image.height);
- uint16_t* vDataSrc = uDataSrc + 1;
-
- uint16_t* yDataDst = static_cast<uint16_t*>(mDestYUV444Image.data);
- uint16_t* uDataDst = yDataDst + (mDestYUV444Image.width * mDestYUV444Image.height);
- uint16_t* vDataDst = uDataDst + (mDestYUV444Image.width * mDestYUV444Image.height);
-
uint64_t ySqError = 0, uSqError = 0, vSqError = 0;
- for (size_t i = 0; i < mDestYUV444Image.height; i++) {
- for (size_t j = 0; j < mDestYUV444Image.width; j++) {
- int ySrc = (yDataSrc[mRawP010Image.width * i + j] >> 6) & 0x3ff;
+ for (size_t i = 0; i < mDestYUV444Image.h; i++) {
+ for (size_t j = 0; j < mDestYUV444Image.w; j++) {
+ int ySrc = (yDataSrc[mRawP010Image.stride[UHDR_PLANE_Y] * i + j] >> 6) & 0x3ff;
ySrc = CLIP3(ySrc, 64, 940);
- int yDst = yDataDst[mDestYUV444Image.width * i + j] & 0x3ff;
+ int yDst = yDataDst[mDestYUV444Image.stride[UHDR_PLANE_Y] * i + j] & 0x3ff;
ySqError += (ySrc - yDst) * (ySrc - yDst);
if (i % 2 == 0 && j % 2 == 0) {
- int uSrc = (uDataSrc[mRawP010Image.width * (i / 2) + (j / 2) * 2] >> 6) & 0x3ff;
+ int uSrc =
+ (uDataSrc[mRawP010Image.stride[UHDR_PLANE_UV] * (i / 2) + (j / 2) * 2] >> 6) & 0x3ff;
uSrc = CLIP3(uSrc, 64, 960);
- int uDst = uDataDst[mDestYUV444Image.width * i + j] & 0x3ff;
- uDst += uDataDst[mDestYUV444Image.width * i + j + 1] & 0x3ff;
- uDst += uDataDst[mDestYUV444Image.width * (i + 1) + j + 1] & 0x3ff;
- uDst += uDataDst[mDestYUV444Image.width * (i + 1) + j + 1] & 0x3ff;
+ int uDst = uDataDst[mDestYUV444Image.stride[UHDR_PLANE_U] * i + j] & 0x3ff;
+ uDst += uDataDst[mDestYUV444Image.stride[UHDR_PLANE_U] * i + j + 1] & 0x3ff;
+ uDst += uDataDst[mDestYUV444Image.stride[UHDR_PLANE_U] * (i + 1) + j + 1] & 0x3ff;
+ uDst += uDataDst[mDestYUV444Image.stride[UHDR_PLANE_U] * (i + 1) + j + 1] & 0x3ff;
uDst = (uDst + 2) >> 2;
uSqError += (uSrc - uDst) * (uSrc - uDst);
- int vSrc = (vDataSrc[mRawP010Image.width * (i / 2) + (j / 2) * 2] >> 6) & 0x3ff;
+ int vSrc =
+ (vDataSrc[mRawP010Image.stride[UHDR_PLANE_UV] * (i / 2) + (j / 2) * 2] >> 6) & 0x3ff;
vSrc = CLIP3(vSrc, 64, 960);
- int vDst = vDataDst[mDestYUV444Image.width * i + j] & 0x3ff;
- vDst += vDataDst[mDestYUV444Image.width * i + j + 1] & 0x3ff;
- vDst += vDataDst[mDestYUV444Image.width * (i + 1) + j + 1] & 0x3ff;
- vDst += vDataDst[mDestYUV444Image.width * (i + 1) + j + 1] & 0x3ff;
+ int vDst = vDataDst[mDestYUV444Image.stride[UHDR_PLANE_V] * i + j] & 0x3ff;
+ vDst += vDataDst[mDestYUV444Image.stride[UHDR_PLANE_V] * i + j + 1] & 0x3ff;
+ vDst += vDataDst[mDestYUV444Image.stride[UHDR_PLANE_V] * (i + 1) + j + 1] & 0x3ff;
+ vDst += vDataDst[mDestYUV444Image.stride[UHDR_PLANE_V] * (i + 1) + j + 1] & 0x3ff;
vDst = (vDst + 2) >> 2;
vSqError += (vSrc - vDst) * (vSrc - vDst);
}
}
}
- double meanSquareError = (double)ySqError / (mDestYUV444Image.width * mDestYUV444Image.height);
+ double meanSquareError = (double)ySqError / (mDestYUV444Image.w * mDestYUV444Image.h);
mPsnr[0] = meanSquareError ? 10 * log10((double)1023 * 1023 / meanSquareError) : 100;
- meanSquareError = (double)uSqError / (mDestYUV444Image.width * mDestYUV444Image.height / 4);
+ meanSquareError = (double)uSqError / (mDestYUV444Image.w * mDestYUV444Image.h / 4);
mPsnr[1] = meanSquareError ? 10 * log10((double)1023 * 1023 / meanSquareError) : 100;
- meanSquareError = (double)vSqError / (mDestYUV444Image.width * mDestYUV444Image.height / 4);
+ meanSquareError = (double)vSqError / (mDestYUV444Image.w * mDestYUV444Image.h / 4);
mPsnr[2] = meanSquareError ? 10 * log10((double)1023 * 1023 / meanSquareError) : 100;
std::cout << "psnr y :: " << mPsnr[0] << " psnr u :: " << mPsnr[1] << " psnr v :: " << mPsnr[2]
@@ -785,52 +1005,52 @@ void UltraHdrAppInput::computeYUVHdrPSNR() {
}
void UltraHdrAppInput::computeYUVSdrPSNR() {
- if (mOf != ULTRAHDR_OUTPUT_SDR) {
- std::cout << "psnr not supported for output format " << mOf << std::endl;
+ if (mOfmt != UHDR_IMG_FMT_32bppRGBA8888) {
+ std::cout << "psnr not supported for output format " << mOfmt << std::endl;
return;
}
- uint8_t* yDataSrc = static_cast<uint8_t*>(mRawYuv420Image.data);
- uint8_t* uDataSrc = yDataSrc + (mRawYuv420Image.width * mRawYuv420Image.height);
- uint8_t* vDataSrc = uDataSrc + (mRawYuv420Image.width * mRawYuv420Image.height / 4);
+ uint8_t* yDataSrc = static_cast<uint8_t*>(mRawYuv420Image.planes[UHDR_PLANE_Y]);
+ uint8_t* uDataSrc = static_cast<uint8_t*>(mRawYuv420Image.planes[UHDR_PLANE_U]);
+ uint8_t* vDataSrc = static_cast<uint8_t*>(mRawYuv420Image.planes[UHDR_PLANE_V]);
- uint8_t* yDataDst = static_cast<uint8_t*>(mDestYUV444Image.data);
- uint8_t* uDataDst = yDataDst + (mDestYUV444Image.width * mDestYUV444Image.height);
- uint8_t* vDataDst = uDataDst + (mDestYUV444Image.width * mDestYUV444Image.height);
+ uint8_t* yDataDst = static_cast<uint8_t*>(mDestYUV444Image.planes[UHDR_PLANE_Y]);
+ uint8_t* uDataDst = static_cast<uint8_t*>(mDestYUV444Image.planes[UHDR_PLANE_U]);
+ uint8_t* vDataDst = static_cast<uint8_t*>(mDestYUV444Image.planes[UHDR_PLANE_V]);
uint64_t ySqError = 0, uSqError = 0, vSqError = 0;
- for (size_t i = 0; i < mDestYUV444Image.height; i++) {
- for (size_t j = 0; j < mDestYUV444Image.width; j++) {
- int ySrc = yDataSrc[mRawYuv420Image.width * i + j];
- int yDst = yDataDst[mDestYUV444Image.width * i + j];
+ for (size_t i = 0; i < mDestYUV444Image.h; i++) {
+ for (size_t j = 0; j < mDestYUV444Image.w; j++) {
+ int ySrc = yDataSrc[mRawYuv420Image.stride[UHDR_PLANE_Y] * i + j];
+ int yDst = yDataDst[mDestYUV444Image.stride[UHDR_PLANE_Y] * i + j];
ySqError += (ySrc - yDst) * (ySrc - yDst);
if (i % 2 == 0 && j % 2 == 0) {
- int uSrc = uDataSrc[mRawYuv420Image.width / 2 * (i / 2) + j / 2];
- int uDst = uDataDst[mDestYUV444Image.width * i + j];
- uDst += uDataDst[mDestYUV444Image.width * i + j + 1];
- uDst += uDataDst[mDestYUV444Image.width * (i + 1) + j];
- uDst += uDataDst[mDestYUV444Image.width * (i + 1) + j + 1];
+ int uSrc = uDataSrc[mRawYuv420Image.stride[UHDR_PLANE_U] * (i / 2) + j / 2];
+ int uDst = uDataDst[mDestYUV444Image.stride[UHDR_PLANE_U] * i + j];
+ uDst += uDataDst[mDestYUV444Image.stride[UHDR_PLANE_U] * i + j + 1];
+ uDst += uDataDst[mDestYUV444Image.stride[UHDR_PLANE_U] * (i + 1) + j];
+ uDst += uDataDst[mDestYUV444Image.stride[UHDR_PLANE_U] * (i + 1) + j + 1];
uDst = (uDst + 2) >> 2;
uSqError += (uSrc - uDst) * (uSrc - uDst);
- int vSrc = vDataSrc[mRawYuv420Image.width / 2 * (i / 2) + j / 2];
- int vDst = vDataDst[mDestYUV444Image.width * i + j];
- vDst += vDataDst[mDestYUV444Image.width * i + j + 1];
- vDst += vDataDst[mDestYUV444Image.width * (i + 1) + j];
- vDst += vDataDst[mDestYUV444Image.width * (i + 1) + j + 1];
+ int vSrc = vDataSrc[mRawYuv420Image.stride[UHDR_PLANE_V] * (i / 2) + j / 2];
+ int vDst = vDataDst[mDestYUV444Image.stride[UHDR_PLANE_V] * i + j];
+ vDst += vDataDst[mDestYUV444Image.stride[UHDR_PLANE_V] * i + j + 1];
+ vDst += vDataDst[mDestYUV444Image.stride[UHDR_PLANE_V] * (i + 1) + j];
+ vDst += vDataDst[mDestYUV444Image.stride[UHDR_PLANE_V] * (i + 1) + j + 1];
vDst = (vDst + 2) >> 2;
vSqError += (vSrc - vDst) * (vSrc - vDst);
}
}
}
- double meanSquareError = (double)ySqError / (mDestYUV444Image.width * mDestYUV444Image.height);
+ double meanSquareError = (double)ySqError / (mDestYUV444Image.w * mDestYUV444Image.h);
mPsnr[0] = meanSquareError ? 10 * log10((double)255 * 255 / meanSquareError) : 100;
- meanSquareError = (double)uSqError / (mDestYUV444Image.width * mDestYUV444Image.height / 4);
+ meanSquareError = (double)uSqError / (mDestYUV444Image.w * mDestYUV444Image.h / 4);
mPsnr[1] = meanSquareError ? 10 * log10((double)255 * 255 / meanSquareError) : 100;
- meanSquareError = (double)vSqError / (mDestYUV444Image.width * mDestYUV444Image.height / 4);
+ meanSquareError = (double)vSqError / (mDestYUV444Image.w * mDestYUV444Image.h / 4);
mPsnr[2] = meanSquareError ? 10 * log10((double)255 * 255 / meanSquareError) : 100;
std::cout << "psnr y :: " << mPsnr[0] << " psnr u:: " << mPsnr[1] << " psnr v :: " << mPsnr[2]
@@ -841,16 +1061,18 @@ static void usage(const char* name) {
fprintf(stderr, "\n## ultra hdr demo application.\nUsage : %s \n", name);
fprintf(stderr, " -m mode of operation. [0:encode, 1:decode] \n");
fprintf(stderr, "\n## encoder options : \n");
- fprintf(stderr, " -p raw 10 bit input resource in p010 color format, mandatory. \n");
+ fprintf(stderr, " -p raw 10 bit input resource in p010 color format. \n");
fprintf(stderr,
" -y raw 8 bit input resource in yuv420, optional. \n"
" if not provided tonemapping happens internally. \n");
- fprintf(stderr, " -i compressed 8 bit jpeg file path, optional \n");
- fprintf(stderr, " -w input file width, mandatory. \n");
- fprintf(stderr, " -h input file height, mandatory. \n");
+ fprintf(stderr, " -i compressed 8 bit jpeg file path. \n");
+ fprintf(stderr, " -g compressed 8 bit gainmap file path. \n");
+ fprintf(stderr, " -f gainmap metadata config file. \n");
+ fprintf(stderr, " -w input file width. \n");
+ fprintf(stderr, " -h input file height. \n");
fprintf(stderr, " -C 10 bit input color gamut, optional. [0:bt709, 1:p3, 2:bt2100] \n");
fprintf(stderr, " -c 8 bit input color gamut, optional. [0:bt709, 1:p3, 2:bt2100] \n");
- fprintf(stderr, " -t input transfer function, optional. [0:linear, 1:hlg, 2:pq] \n");
+ fprintf(stderr, " -t 10 bit input transfer function, optional. [0:linear, 1:hlg, 2:pq] \n");
fprintf(stderr,
" -q quality factor to be used while encoding 8 bit image, optional. [0-100].\n"
" gain map image does not use this quality factor. \n"
@@ -859,22 +1081,23 @@ static void usage(const char* name) {
fprintf(stderr, "\n## decoder options : \n");
fprintf(stderr, " -j ultra hdr input resource, mandatory in decode mode. \n");
fprintf(stderr,
- " -o output transfer function, optional. [0:sdr, 1:hdr_linear, 2:hdr_pq, "
- "3:hdr_hlg] \n");
+ " -o output transfer function, optional. [0:linear, 1:hlg, 2:pq, 3:srgb] \n");
+ fprintf(stderr,
+ " -O output color format, optional. [3:rgba8888, 4:rgbahalffloat, 5:rgba1010102] \n"
+ "It should be noted that not all combinations of output color format and output transfer "
+ "function are supported. srgb output color transfer shall be paired with rgba8888 only. "
+ "hlg, pq shall be paired with rgba1010102. linear shall be paired with rgbahalffloat");
fprintf(stderr, "\n## examples of usage :\n");
fprintf(stderr, "\n## encode api-0 :\n");
fprintf(stderr, " ultrahdr_app -m 0 -p cosmat_1920x1080_p010.yuv -w 1920 -h 1080 -q 97\n");
fprintf(stderr,
- " ultrahdr_app -m 0 -p cosmat_1920x1080_p010.yuv -w 1920 -h 1080 -q 97 -C 2 -t 2\n");
+ " ultrahdr_app -m 0 -p cosmat_1920x1080_p010.yuv -w 1920 -h 1080 -q 97 -C 1 -t 2\n");
fprintf(stderr, "\n## encode api-1 :\n");
fprintf(stderr,
" ultrahdr_app -m 0 -p cosmat_1920x1080_p010.yuv -y cosmat_1920x1080_420.yuv -w 1920 "
"-h 1080 -q 97\n");
fprintf(stderr,
" ultrahdr_app -m 0 -p cosmat_1920x1080_p010.yuv -y cosmat_1920x1080_420.yuv -w 1920 "
- "-h 1080 -q 97\n");
- fprintf(stderr,
- " ultrahdr_app -m 0 -p cosmat_1920x1080_p010.yuv -y cosmat_1920x1080_420.yuv -w 1920 "
"-h 1080 -q 97 -C 2 -c 1 -t 1\n");
fprintf(stderr,
" ultrahdr_app -m 0 -p cosmat_1920x1080_p010.yuv -y cosmat_1920x1080_420.yuv -w 1920 "
@@ -882,27 +1105,34 @@ static void usage(const char* name) {
fprintf(stderr, "\n## encode api-2 :\n");
fprintf(stderr,
" ultrahdr_app -m 0 -p cosmat_1920x1080_p010.yuv -y cosmat_1920x1080_420.yuv -i "
- "cosmat_1920x1080_420_8bit.jpg -w 1920 -h 1080 -t 1 -o 3 -e 1\n");
+ "cosmat_1920x1080_420_8bit.jpg -w 1920 -h 1080 -t 1 -o 3 -O 3 -e 1\n");
fprintf(stderr, "\n## encode api-3 :\n");
fprintf(stderr,
" ultrahdr_app -m 0 -p cosmat_1920x1080_p010.yuv -i cosmat_1920x1080_420_8bit.jpg -w "
- "1920 -h 1080 -t 1 -o 3 -e 1\n");
+ "1920 -h 1080 -t 1 -o 1 -O 5 -e 1\n");
+ fprintf(stderr, "\n## encode api-4 :\n");
+ fprintf(stderr,
+ " ultrahdr_app -m 0 -i cosmat_1920x1080_420_8bit.jpg -g cosmat_1920x1080_420_8bit.jpg "
+ "-f metadata.cfg\n");
fprintf(stderr, "\n## decode api :\n");
fprintf(stderr, " ultrahdr_app -m 1 -j cosmat_1920x1080_hdr.jpg \n");
- fprintf(stderr, " ultrahdr_app -m 1 -j cosmat_1920x1080_hdr.jpg -o 2\n");
+ fprintf(stderr, " ultrahdr_app -m 1 -j cosmat_1920x1080_hdr.jpg -o 3 -O 3\n");
+ fprintf(stderr, " ultrahdr_app -m 1 -j cosmat_1920x1080_hdr.jpg -o 1 -O 5\n");
fprintf(stderr, "\n");
}
int main(int argc, char* argv[]) {
- char opt_string[] = "p:y:i:w:h:C:c:t:q:o:m:j:e:";
+ char opt_string[] = "p:y:i:g:f:w:h:C:c:t:q:o:O:m:j:e:";
char *p010_file = nullptr, *yuv420_file = nullptr, *jpegr_file = nullptr,
- *yuv420_jpeg_file = nullptr;
+ *yuv420_jpeg_file = nullptr, *gainmap_jpeg_file = nullptr,
+ *gainmap_metadata_cfg_file = nullptr;
int width = 0, height = 0;
- ultrahdr_color_gamut p010Cg = ULTRAHDR_COLORGAMUT_BT709;
- ultrahdr_color_gamut yuv420Cg = ULTRAHDR_COLORGAMUT_BT709;
- ultrahdr_transfer_function tf = ULTRAHDR_TF_HLG;
+ uhdr_color_gamut_t p010Cg = UHDR_CG_BT_709;
+ uhdr_color_gamut_t yuv420Cg = UHDR_CG_BT_709;
+ uhdr_color_transfer_t p010Tf = UHDR_CT_HLG;
int quality = 100;
- ultrahdr_output_format of = ULTRAHDR_OUTPUT_HDR_HLG;
+ uhdr_color_transfer_t outTf = UHDR_CT_HLG;
+ uhdr_img_fmt_t outFmt = UHDR_IMG_FMT_32bppRGBA1010102;
int mode = 0;
int compute_psnr = 0;
int ch;
@@ -917,6 +1147,12 @@ int main(int argc, char* argv[]) {
case 'i':
yuv420_jpeg_file = optarg_s;
break;
+ case 'g':
+ gainmap_jpeg_file = optarg_s;
+ break;
+ case 'f':
+ gainmap_metadata_cfg_file = optarg_s;
+ break;
case 'w':
width = atoi(optarg_s);
break;
@@ -924,19 +1160,22 @@ int main(int argc, char* argv[]) {
height = atoi(optarg_s);
break;
case 'C':
- p010Cg = static_cast<ultrahdr_color_gamut>(atoi(optarg_s));
+ p010Cg = static_cast<uhdr_color_gamut_t>(atoi(optarg_s));
break;
case 'c':
- yuv420Cg = static_cast<ultrahdr_color_gamut>(atoi(optarg_s));
+ yuv420Cg = static_cast<uhdr_color_gamut_t>(atoi(optarg_s));
break;
case 't':
- tf = static_cast<ultrahdr_transfer_function>(atoi(optarg_s));
+ p010Tf = static_cast<uhdr_color_transfer_t>(atoi(optarg_s));
break;
case 'q':
quality = atoi(optarg_s);
break;
+ case 'O':
+ outFmt = static_cast<uhdr_img_fmt_t>(atoi(optarg_s));
+ break;
case 'o':
- of = static_cast<ultrahdr_output_format>(atoi(optarg_s));
+ outTf = static_cast<uhdr_color_transfer_t>(atoi(optarg_s));
break;
case 'm':
mode = atoi(optarg_s);
@@ -953,25 +1192,30 @@ int main(int argc, char* argv[]) {
}
}
if (mode == 0) {
- if (width <= 0 || height <= 0 || p010_file == nullptr) {
+ if ((width <= 0 || height <= 0 || p010_file == nullptr) &&
+ (yuv420_jpeg_file == nullptr || gainmap_jpeg_file == nullptr ||
+ gainmap_metadata_cfg_file == nullptr)) {
usage(argv[0]);
return -1;
}
- UltraHdrAppInput appInput(p010_file, yuv420_file, yuv420_jpeg_file, width, height, p010Cg,
- yuv420Cg, tf, quality, of);
+ UltraHdrAppInput appInput(p010_file, yuv420_file, yuv420_jpeg_file, gainmap_jpeg_file,
+ gainmap_metadata_cfg_file, width, height, p010Cg, yuv420Cg, p010Tf,
+ quality, outTf, outFmt);
if (!appInput.encode()) return -1;
if (compute_psnr == 1) {
if (!appInput.decode()) return -1;
- if (of == ULTRAHDR_OUTPUT_SDR && yuv420_file != nullptr) {
+ if (outFmt == UHDR_IMG_FMT_32bppRGBA8888 && yuv420_file != nullptr) {
appInput.convertYuv420ToRGBImage();
appInput.computeRGBSdrPSNR();
appInput.convertRgba8888ToYUV444Image();
appInput.computeYUVSdrPSNR();
- } else if (of == ULTRAHDR_OUTPUT_HDR_HLG || of == ULTRAHDR_OUTPUT_HDR_PQ) {
+ } else if (outFmt == UHDR_IMG_FMT_32bppRGBA1010102 && p010_file != nullptr) {
appInput.convertP010ToRGBImage();
appInput.computeRGBHdrPSNR();
appInput.convertRgba1010102ToYUV444Image();
appInput.computeYUVHdrPSNR();
+ } else {
+ std::cerr << "failed to compute psnr " << std::endl;
}
}
} else if (mode == 1) {
@@ -979,7 +1223,7 @@ int main(int argc, char* argv[]) {
usage(argv[0]);
return -1;
}
- UltraHdrAppInput appInput(jpegr_file, of);
+ UltraHdrAppInput appInput(jpegr_file, outTf, outFmt);
if (!appInput.decode()) return -1;
} else {
std::cerr << "unrecognized input mode " << mode << std::endl;
diff --git a/fuzzer/README.md b/fuzzer/README.md
index e48d859..0550eae 100644
--- a/fuzzer/README.md
+++ b/fuzzer/README.md
@@ -1,12 +1,11 @@
-Building fuzzers for libultrahdr
-================================
+## Building fuzzers for libultrahdr
### Requirements
- Refer [Requirements](../README.md#Requirements)
-- Additionally compilers are required to support options *-fsanitize=fuzzer, -fsanitize=fuzzer-no-link*.
- For instance, clang 12 (or later)
+- Additionally compilers are required to support options `-fsanitize=fuzzer, -fsanitize=fuzzer-no-link`.
+ For instance, `clang 12` (or later)
### Building Commands
@@ -15,16 +14,14 @@ Building fuzzers for libultrahdr
cmake ../ -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DUHDR_BUILD_FUZZERS=1
make
-This will generate the following files under *{build_directory}*:
-
-**libultrahdr.a**<br> Instrumented ultrahdr library
+This will generate the following files under `build_directory`:
**ultrahdr_enc_fuzzer**<br> ultrahdr encoder fuzzer
**ultrahdr_dec_fuzzer**<br> ultrahdr decoder fuzzer
Additionally, while building fuzzers, user can enable sanitizers by providing desired
-sanitizer option(s) through UHDR_SANITIZE_OPTIONS.
+sanitizer option(s) through `UHDR_SANITIZE_OPTIONS`.
To enable ASan,
diff --git a/fuzzer/ultrahdr_dec_fuzzer.cpp b/fuzzer/ultrahdr_dec_fuzzer.cpp
index 4adc942..9a1f179 100644
--- a/fuzzer/ultrahdr_dec_fuzzer.cpp
+++ b/fuzzer/ultrahdr_dec_fuzzer.cpp
@@ -56,11 +56,9 @@ void UltraHdrDecFuzzer::process() {
auto decodedRaw = std::make_unique<uint8_t[]>(outSize);
decodedJpegR.data = decodedRaw.get();
ultrahdr_metadata_struct metadata;
- jpegr_uncompressed_struct decodedGainMap{};
(void)jpegHdr.decodeJPEGR(&jpegImgR, &decodedJpegR,
mFdp.ConsumeFloatingPointInRange<float>(1.0, FLT_MAX), nullptr, of,
- &decodedGainMap, &metadata);
- if (decodedGainMap.data) free(decodedGainMap.data);
+ nullptr, &metadata);
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
diff --git a/fuzzer/ultrahdr_enc_fuzzer.cpp b/fuzzer/ultrahdr_enc_fuzzer.cpp
index ff749c7..88faae8 100644
--- a/fuzzer/ultrahdr_enc_fuzzer.cpp
+++ b/fuzzer/ultrahdr_enc_fuzzer.cpp
@@ -297,14 +297,12 @@ void UltraHdrEncFuzzer::process() {
auto decodedRaw = std::make_unique<uint8_t[]>(outSize);
decodedJpegR.data = decodedRaw.get();
ultrahdr_metadata_struct metadata;
- jpegr_uncompressed_struct decodedGainMap{};
status = jpegHdr.decodeJPEGR(&jpegImgR, &decodedJpegR,
mFdp.ConsumeFloatingPointInRange<float>(1.0, FLT_MAX), nullptr,
- of, &decodedGainMap, &metadata);
+ of, nullptr, &metadata);
if (status != JPEGR_NO_ERROR) {
ALOGE("encountered error during decoding %d", status);
}
- if (decodedGainMap.data) free(decodedGainMap.data);
} else {
ALOGE("encountered error during get jpeg info %d", status);
}
diff --git a/lib/include/ultrahdr/editorhelper.h b/lib/include/ultrahdr/editorhelper.h
new file mode 100644
index 0000000..3a82e0e
--- /dev/null
+++ b/lib/include/ultrahdr/editorhelper.h
@@ -0,0 +1,103 @@
+/*
+ * 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_EDITORHELPER_H
+#define ULTRAHDR_EDITORHELPER_H
+
+#include "ultrahdr_api.h"
+#include "ultrahdr/ultrahdrcommon.h"
+
+// todo: move this to ultrahdr_api.h
+/*!\brief List of supported mirror directions */
+typedef enum uhdr_mirror_direction {
+ UHDR_MIRROR_VERTICAL, /**< flip image over x axis */
+ UHDR_MIRROR_HORIZONTAL, /**< flip image over y axis */
+} uhdr_mirror_direction_t; /**< alias for enum uhdr_mirror_direction */
+
+namespace ultrahdr {
+
+/*!\brief uhdr image effect descriptor */
+typedef struct uhdr_effect_desc {
+ virtual std::string to_string() = 0;
+
+ virtual ~uhdr_effect_desc() = default;
+} uhdr_effect_desc_t; /**< alias for struct uhdr_effect_desc */
+
+/*!\brief mirror effect descriptor */
+typedef struct uhdr_mirror_effect : uhdr_effect_desc {
+ uhdr_mirror_effect(uhdr_mirror_direction_t direction) : m_direction{direction} {}
+
+ std::string to_string() {
+ return "effect : mirror, metadata : direction - " + ((m_direction == UHDR_MIRROR_HORIZONTAL)
+ ? std::string{"horizontal"}
+ : std::string{"vertical"});
+ }
+
+ uhdr_mirror_direction_t m_direction;
+} uhdr_mirror_effect_t; /**< alias for struct uhdr_mirror_effect */
+
+/*!\brief rotate effect descriptor */
+typedef struct uhdr_rotate_effect : uhdr_effect_desc {
+ uhdr_rotate_effect(int degree) : m_degree{degree} {}
+
+ std::string to_string() {
+ return "effect : rotate, metadata : degree - " + std::to_string(m_degree);
+ }
+
+ int m_degree;
+} uhdr_rotate_effect_t; /**< alias for struct uhdr_rotate_effect */
+
+/*!\brief crop effect descriptor */
+typedef struct uhdr_crop_effect : uhdr_effect_desc {
+ uhdr_crop_effect(int left, int right, int top, int bottom)
+ : m_left{left}, m_right{right}, m_top{top}, m_bottom{bottom} {}
+
+ std::string to_string() {
+ return "effect : crop, metadata : left, right, top, bottom - " + std::to_string(m_left) + " ," +
+ std::to_string(m_right) + " ," + std::to_string(m_top) + " ," + std::to_string(m_bottom);
+ }
+
+ int m_left;
+ int m_right;
+ int m_top;
+ int m_bottom;
+} uhdr_crop_effect_t; /**< alias for struct uhdr_crop_effect */
+
+/*!\brief resize effect descriptor */
+typedef struct uhdr_resize_effect : uhdr_effect_desc {
+ uhdr_resize_effect(int width, int height) : m_width{width}, m_height{height} {}
+
+ std::string to_string() {
+ return "effect : resize, metadata : dimensions w, h" + std::to_string(m_width) + " ," +
+ std::to_string(m_height);
+ }
+
+ int m_width;
+ int m_height;
+} uhdr_resize_effect_t; /**< alias for struct uhdr_resize_effect */
+
+std::unique_ptr<uhdr_raw_image_ext_t> apply_rotate(uhdr_raw_image_t* src, int degree);
+
+std::unique_ptr<uhdr_raw_image_ext_t> apply_mirror(uhdr_raw_image_t* src,
+ uhdr_mirror_direction_t direction);
+
+std::unique_ptr<uhdr_raw_image_ext_t> apply_resize(uhdr_raw_image* src, int dst_w, int dst_h);
+
+void apply_crop(uhdr_raw_image_t* src, int left, int top, int wd, int ht);
+
+} // namespace ultrahdr
+
+#endif // ULTRAHDR_EDITORHELPER_H
diff --git a/lib/include/ultrahdr/gainmapmath.h b/lib/include/ultrahdr/gainmapmath.h
index bdbaf02..a835762 100644
--- a/lib/include/ultrahdr/gainmapmath.h
+++ b/lib/include/ultrahdr/gainmapmath.h
@@ -19,8 +19,8 @@
#include <cmath>
-#include "ultrahdr.h"
-#include "jpegr.h"
+#include "ultrahdr/ultrahdr.h"
+#include "ultrahdr/jpegr.h"
#define CLIP3(x, min, max) ((x) < (min)) ? (min) : ((x) > (max)) ? (max) : (x)
@@ -29,7 +29,9 @@ namespace ultrahdr {
////////////////////////////////////////////////////////////////////////////////
// Framework
-const float kSdrWhiteNits = 100.0f;
+// This aligns with the suggested default reference diffuse white from
+// ISO/TS 22028-5
+const float kSdrWhiteNits = 203.0f;
const float kHlgMaxNits = 1000.0f;
const float kPqMaxNits = 10000.0f;
diff --git a/lib/include/ultrahdr/icc.h b/lib/include/ultrahdr/icc.h
index a0b4680..727248a 100644
--- a/lib/include/ultrahdr/icc.h
+++ b/lib/include/ultrahdr/icc.h
@@ -33,10 +33,10 @@
#define Endian_SwapBE16(n) (n)
#endif
-#include "ultrahdr.h"
-#include "jpegr.h"
-#include "gainmapmath.h"
-#include "jpegrutils.h"
+#include "ultrahdr/ultrahdr.h"
+#include "ultrahdr/jpegr.h"
+#include "ultrahdr/gainmapmath.h"
+#include "ultrahdr/jpegrutils.h"
namespace ultrahdr {
diff --git a/lib/include/ultrahdr/jpegdecoderhelper.h b/lib/include/ultrahdr/jpegdecoderhelper.h
index d0b1f6f..4d5b42e 100644
--- a/lib/include/ultrahdr/jpegdecoderhelper.h
+++ b/lib/include/ultrahdr/jpegdecoderhelper.h
@@ -35,13 +35,13 @@ extern "C" {
#include <memory>
#include <vector>
+namespace ultrahdr {
+
// constraint on max width and max height is only due to device alloc constraints
// Can tune these values basing on the target device
static const int kMaxWidth = 8192;
static const int kMaxHeight = 8192;
-namespace ultrahdr {
-
typedef enum {
PARSE_ONLY = 0, // Dont decode. Parse for dimensions, EXIF, ICC, XMP
DECODE_TO_RGBA = 1, // Parse and decode to rgba
diff --git a/lib/include/ultrahdr/jpegr.h b/lib/include/ultrahdr/jpegr.h
index 49ddaea..57dea96 100644
--- a/lib/include/ultrahdr/jpegr.h
+++ b/lib/include/ultrahdr/jpegr.h
@@ -52,6 +52,7 @@ typedef enum {
ERROR_JPEGR_INVALID_DISPLAY_BOOST = JPEGR_IO_ERROR_BASE - 8,
ERROR_JPEGR_INVALID_OUTPUT_FORMAT = JPEGR_IO_ERROR_BASE - 9,
ERROR_JPEGR_BAD_METADATA = JPEGR_IO_ERROR_BASE - 10,
+ ERROR_JPEGR_INVALID_CROPPING_PARAMETERS = JPEGR_IO_ERROR_BASE - 11,
JPEGR_RUNTIME_ERROR_BASE = -20000,
ERROR_JPEGR_ENCODE_ERROR = JPEGR_RUNTIME_ERROR_BASE - 1,
@@ -117,6 +118,8 @@ struct jpegr_uncompressed_struct {
// NOTE: if chroma_data is nullptr, chroma_stride is irrelevant. Just as the way,
// chroma_data is derived from luma ptr, chroma stride is derived from luma stride.
size_t chroma_stride = 0;
+ // Pixel format.
+ ultrahdr_pixel_format pixelFormat = ULTRAHDR_PIX_FMT_UNSPECIFIED;
};
/*
diff --git a/lib/include/ultrahdr/ultrahdr.h b/lib/include/ultrahdr/ultrahdr.h
index fa69d57..f629209 100644
--- a/lib/include/ultrahdr/ultrahdr.h
+++ b/lib/include/ultrahdr/ultrahdr.h
@@ -50,6 +50,17 @@ typedef enum {
ULTRAHDR_OUTPUT_MAX = ULTRAHDR_OUTPUT_HDR_HLG,
} ultrahdr_output_format;
+// Supported pixel format
+typedef enum {
+ ULTRAHDR_PIX_FMT_UNSPECIFIED = -1,
+ ULTRAHDR_PIX_FMT_P010,
+ ULTRAHDR_PIX_FMT_YUV420,
+ ULTRAHDR_PIX_FMT_MONOCHROME,
+ ULTRAHDR_PIX_FMT_RGBA8888,
+ ULTRAHDR_PIX_FMT_RGBAF16,
+ ULTRAHDR_PIX_FMT_RGBA1010102,
+} ultrahdr_pixel_format;
+
/*
* Holds information for gain map related metadata.
*
diff --git a/lib/include/ultrahdr/ultrahdrcommon.h b/lib/include/ultrahdr/ultrahdrcommon.h
index ba3a3b8..8bc6d2e 100644
--- a/lib/include/ultrahdr/ultrahdrcommon.h
+++ b/lib/include/ultrahdr/ultrahdrcommon.h
@@ -19,6 +19,18 @@
//#define LOG_NDEBUG 0
+#include <deque>
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "ultrahdr_api.h"
+
+// ===============================================================================================
+// Function Macros
+// ===============================================================================================
+
#ifdef __ANDROID__
#include "log/log.h"
#else
@@ -61,4 +73,92 @@
#define ALIGNM(x, m) ((((x) + ((m)-1)) / (m)) * (m))
+namespace ultrahdr {
+
+// ===============================================================================================
+// Structure Definitions
+// ===============================================================================================
+
+/**\brief uhdr memory block */
+typedef struct uhdr_memory_block {
+ uhdr_memory_block(size_t capacity);
+
+ std::unique_ptr<uint8_t[]> m_buffer; /**< data */
+ size_t m_capacity; /**< capacity */
+} uhdr_memory_block_t; /**< alias for struct uhdr_memory_block */
+
+/**\brief extended raw image descriptor */
+typedef struct uhdr_raw_image_ext : uhdr_raw_image_t {
+ uhdr_raw_image_ext(uhdr_img_fmt_t fmt, uhdr_color_gamut_t cg, uhdr_color_transfer_t ct,
+ uhdr_color_range_t range, unsigned w, unsigned h, unsigned align_stride_to);
+
+ private:
+ std::unique_ptr<ultrahdr::uhdr_memory_block> m_block;
+} uhdr_raw_image_ext_t; /**< alias for struct uhdr_raw_image_ext */
+
+/**\brief extended compressed image descriptor */
+typedef struct uhdr_compressed_image_ext : uhdr_compressed_image_t {
+ uhdr_compressed_image_ext(uhdr_color_gamut_t cg, uhdr_color_transfer_t ct,
+ uhdr_color_range_t range, unsigned sz);
+
+ private:
+ std::unique_ptr<ultrahdr::uhdr_memory_block> m_block;
+} uhdr_compressed_image_ext_t; /**< alias for struct uhdr_compressed_image_ext */
+
+/*!\brief forward declaration for image effect descriptor */
+typedef struct uhdr_effect_desc uhdr_effect_desc_t;
+
+} // namespace ultrahdr
+
+// ===============================================================================================
+// Extensions of ultrahdr api definitions, so outside ultrahdr namespace
+// ===============================================================================================
+
+struct uhdr_codec_private {
+ std::deque<ultrahdr::uhdr_effect_desc_t*> m_effects;
+
+ virtual ~uhdr_codec_private();
+};
+
+struct uhdr_encoder_private : uhdr_codec_private {
+ // config data
+ std::map<uhdr_img_label, std::unique_ptr<ultrahdr::uhdr_raw_image_ext_t>> m_raw_images;
+ std::map<uhdr_img_label, std::unique_ptr<ultrahdr::uhdr_compressed_image_ext_t>>
+ m_compressed_images;
+ std::map<uhdr_img_label, int> m_quality;
+ std::vector<uint8_t> m_exif;
+ uhdr_gainmap_metadata_t m_metadata;
+ uhdr_codec_t m_output_format;
+
+ // internal data
+ bool m_sailed;
+ std::unique_ptr<ultrahdr::uhdr_compressed_image_ext_t> m_compressed_output_buffer;
+ uhdr_error_info_t m_encode_call_status;
+};
+
+struct uhdr_decoder_private : uhdr_codec_private {
+ // config data
+ std::unique_ptr<ultrahdr::uhdr_compressed_image_ext_t> m_uhdr_compressed_img;
+ uhdr_img_fmt_t m_output_fmt;
+ uhdr_color_transfer_t m_output_ct;
+ float m_output_max_disp_boost;
+
+ // internal data
+ bool m_probed;
+ bool m_sailed;
+ std::unique_ptr<ultrahdr::uhdr_raw_image_ext_t> m_decoded_img_buffer;
+ std::unique_ptr<ultrahdr::uhdr_raw_image_ext_t> m_gainmap_img_buffer;
+ int m_img_wd, m_img_ht;
+ int m_gainmap_wd, m_gainmap_ht;
+ std::vector<uint8_t> m_exif;
+ uhdr_mem_block_t m_exif_block;
+ std::vector<uint8_t> m_icc;
+ uhdr_mem_block_t m_icc_block;
+ std::vector<uint8_t> m_base_xmp;
+ std::vector<uint8_t> m_gainmap_xmp;
+ uhdr_gainmap_metadata_t m_metadata;
+ uhdr_error_info_t m_probe_call_status;
+ uhdr_error_info_t m_decode_call_status;
+};
+
#endif // ULTRAHDR_ULTRAHDRCOMMON_H
diff --git a/lib/src/editorhelper.cpp b/lib/src/editorhelper.cpp
new file mode 100644
index 0000000..1b33bc3
--- /dev/null
+++ b/lib/src/editorhelper.cpp
@@ -0,0 +1,237 @@
+/*
+ * 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.
+ */
+
+#include <cstring>
+#include <cstdint>
+
+#include "ultrahdr/editorhelper.h"
+
+namespace ultrahdr {
+
+template <typename T>
+void rotate_buffer_clockwise(T* src_buffer, T* dst_buffer, int src_w, int src_h, int src_stride,
+ int dst_stride, int degree) {
+ if (degree == 90) {
+ int dst_w = src_h;
+ int dst_h = src_w;
+ for (int i = 0; i < dst_h; i++) {
+ for (int j = 0; j < dst_w; j++) {
+ dst_buffer[i * dst_stride + j] = src_buffer[(src_h - j - 1) * src_stride + i];
+ }
+ }
+ } else if (degree == 180) {
+ int dst_w = src_w;
+ int dst_h = src_h;
+ for (int i = 0; i < dst_h; i++) {
+ for (int j = 0; j < dst_w; j++) {
+ dst_buffer[i * dst_stride + j] = src_buffer[(src_h - i - 1) * src_stride + (src_w - j - 1)];
+ }
+ }
+ } else if (degree == 270) {
+ int dst_w = src_h;
+ int dst_h = src_w;
+ for (int i = 0; i < dst_h; i++) {
+ for (int j = 0; j < dst_w; j++) {
+ dst_buffer[i * dst_stride + j] = src_buffer[j * src_stride + (src_w - i - 1)];
+ }
+ }
+ }
+}
+
+template <typename T>
+void mirror_buffer(T* src_buffer, T* dst_buffer, int src_w, int src_h, int src_stride,
+ int dst_stride, uhdr_mirror_direction_t direction) {
+ if (direction == UHDR_MIRROR_VERTICAL) {
+ for (int i = 0; i < src_h; i++) {
+ memcpy(&dst_buffer[(src_h - i - 1) * dst_stride], &src_buffer[i * src_stride],
+ src_w * sizeof(T));
+ }
+ } else if (direction == UHDR_MIRROR_HORIZONTAL) {
+ for (int i = 0; i < src_h; i++) {
+ for (int j = 0; j < src_w; j++) {
+ dst_buffer[i * dst_stride + j] = src_buffer[i * src_stride + (src_w - j - 1)];
+ }
+ }
+ }
+}
+
+template <typename T>
+void resize_buffer(T* src_buffer, T* dst_buffer, int src_w, int src_h, int dst_w, int dst_h,
+ int src_stride, int dst_stride) {
+ for (int i = 0; i < dst_h; i++) {
+ for (int j = 0; j < dst_w; j++) {
+ dst_buffer[i * dst_stride + j] =
+ src_buffer[i * (src_h / dst_h) * src_stride + j * (src_w / dst_w)];
+ }
+ }
+}
+
+std::unique_ptr<uhdr_raw_image_ext_t> apply_rotate(uhdr_raw_image_t* src, int degree) {
+ std::unique_ptr<uhdr_raw_image_ext_t> dst;
+
+ if (degree == 90 || degree == 270) {
+ dst = std::make_unique<uhdr_raw_image_ext_t>(src->fmt, src->cg, src->ct, src->range, src->h,
+ src->w, 1);
+ } else if (degree == 180) {
+ dst = std::make_unique<uhdr_raw_image_ext_t>(src->fmt, src->cg, src->ct, src->range, src->w,
+ src->h, 1);
+ } else {
+ return nullptr;
+ }
+
+ if (src->fmt == UHDR_IMG_FMT_24bppYCbCrP010) {
+ uint16_t* src_buffer = static_cast<uint16_t*>(src->planes[UHDR_PLANE_Y]);
+ uint16_t* dst_buffer = static_cast<uint16_t*>(dst->planes[UHDR_PLANE_Y]);
+ rotate_buffer_clockwise(src_buffer, dst_buffer, src->w, src->h, src->stride[UHDR_PLANE_Y],
+ dst->stride[UHDR_PLANE_Y], degree);
+ uint32_t* src_uv_buffer = static_cast<uint32_t*>(src->planes[UHDR_PLANE_UV]);
+ uint32_t* dst_uv_buffer = static_cast<uint32_t*>(dst->planes[UHDR_PLANE_UV]);
+ rotate_buffer_clockwise(src_uv_buffer, dst_uv_buffer, src->w / 2, src->h / 2,
+ src->stride[UHDR_PLANE_UV] / 2, dst->stride[UHDR_PLANE_UV] / 2, degree);
+ } else if (src->fmt == UHDR_IMG_FMT_12bppYCbCr420 || src->fmt == UHDR_IMG_FMT_8bppYCbCr400) {
+ uint8_t* src_buffer = static_cast<uint8_t*>(src->planes[UHDR_PLANE_Y]);
+ uint8_t* dst_buffer = static_cast<uint8_t*>(dst->planes[UHDR_PLANE_Y]);
+ rotate_buffer_clockwise(src_buffer, dst_buffer, src->w, src->h, src->stride[UHDR_PLANE_Y],
+ dst->stride[UHDR_PLANE_Y], degree);
+ if (src->fmt == UHDR_IMG_FMT_12bppYCbCr420) {
+ for (int i = 1; i < 3; i++) {
+ src_buffer = static_cast<uint8_t*>(src->planes[i]);
+ dst_buffer = static_cast<uint8_t*>(dst->planes[i]);
+ rotate_buffer_clockwise(src_buffer, dst_buffer, src->w / 2, src->h / 2, src->stride[i],
+ dst->stride[i], degree);
+ }
+ }
+ } else if (src->fmt == UHDR_IMG_FMT_32bppRGBA1010102 || src->fmt == UHDR_IMG_FMT_32bppRGBA8888) {
+ uint32_t* src_buffer = static_cast<uint32_t*>(src->planes[UHDR_PLANE_PACKED]);
+ uint32_t* dst_buffer = static_cast<uint32_t*>(dst->planes[UHDR_PLANE_PACKED]);
+ rotate_buffer_clockwise(src_buffer, dst_buffer, src->w, src->h, src->stride[UHDR_PLANE_PACKED],
+ dst->stride[UHDR_PLANE_PACKED], degree);
+ } else if (src->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat) {
+ uint64_t* src_buffer = static_cast<uint64_t*>(src->planes[UHDR_PLANE_PACKED]);
+ uint64_t* dst_buffer = static_cast<uint64_t*>(dst->planes[UHDR_PLANE_PACKED]);
+ rotate_buffer_clockwise(src_buffer, dst_buffer, src->w, src->h, src->stride[UHDR_PLANE_PACKED],
+ dst->stride[UHDR_PLANE_PACKED], degree);
+ }
+ return std::move(dst);
+}
+
+std::unique_ptr<uhdr_raw_image_ext_t> apply_mirror(uhdr_raw_image_t* src,
+ uhdr_mirror_direction_t direction) {
+ std::unique_ptr<uhdr_raw_image_ext_t> dst = std::make_unique<uhdr_raw_image_ext_t>(
+ src->fmt, src->cg, src->ct, src->range, src->w, src->h, 1);
+
+ if (src->fmt == UHDR_IMG_FMT_24bppYCbCrP010) {
+ uint16_t* src_buffer = static_cast<uint16_t*>(src->planes[UHDR_PLANE_Y]);
+ uint16_t* dst_buffer = static_cast<uint16_t*>(dst->planes[UHDR_PLANE_Y]);
+ mirror_buffer(src_buffer, dst_buffer, src->w, src->h, src->stride[UHDR_PLANE_Y],
+ dst->stride[UHDR_PLANE_Y], direction);
+ uint32_t* src_uv_buffer = static_cast<uint32_t*>(src->planes[UHDR_PLANE_UV]);
+ uint32_t* dst_uv_buffer = static_cast<uint32_t*>(dst->planes[UHDR_PLANE_UV]);
+ mirror_buffer(src_uv_buffer, dst_uv_buffer, src->w / 2, src->h / 2,
+ src->stride[UHDR_PLANE_UV] / 2, dst->stride[UHDR_PLANE_UV] / 2, direction);
+ } else if (src->fmt == UHDR_IMG_FMT_12bppYCbCr420 || src->fmt == UHDR_IMG_FMT_8bppYCbCr400) {
+ uint8_t* src_buffer = static_cast<uint8_t*>(src->planes[UHDR_PLANE_Y]);
+ uint8_t* dst_buffer = static_cast<uint8_t*>(dst->planes[UHDR_PLANE_Y]);
+ mirror_buffer(src_buffer, dst_buffer, src->w, src->h, src->stride[UHDR_PLANE_Y],
+ dst->stride[UHDR_PLANE_Y], direction);
+ if (src->fmt == UHDR_IMG_FMT_12bppYCbCr420) {
+ for (int i = 1; i < 3; i++) {
+ src_buffer = static_cast<uint8_t*>(src->planes[i]);
+ dst_buffer = static_cast<uint8_t*>(dst->planes[i]);
+ mirror_buffer(src_buffer, dst_buffer, src->w / 2, src->h / 2, src->stride[i],
+ dst->stride[i], direction);
+ }
+ }
+ } else if (src->fmt == UHDR_IMG_FMT_32bppRGBA1010102 || src->fmt == UHDR_IMG_FMT_32bppRGBA8888) {
+ uint32_t* src_buffer = static_cast<uint32_t*>(src->planes[UHDR_PLANE_PACKED]);
+ uint32_t* dst_buffer = static_cast<uint32_t*>(dst->planes[UHDR_PLANE_PACKED]);
+ mirror_buffer(src_buffer, dst_buffer, src->w, src->h, src->stride[UHDR_PLANE_PACKED],
+ dst->stride[UHDR_PLANE_PACKED], direction);
+ } else if (src->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat) {
+ uint64_t* src_buffer = static_cast<uint64_t*>(src->planes[UHDR_PLANE_PACKED]);
+ uint64_t* dst_buffer = static_cast<uint64_t*>(dst->planes[UHDR_PLANE_PACKED]);
+ mirror_buffer(src_buffer, dst_buffer, src->w, src->h, src->stride[UHDR_PLANE_PACKED],
+ dst->stride[UHDR_PLANE_PACKED], direction);
+ }
+ return std::move(dst);
+}
+
+void apply_crop(uhdr_raw_image_t* src, int left, int top, int wd, int ht) {
+ if (src->fmt == UHDR_IMG_FMT_24bppYCbCrP010) {
+ uint16_t* src_buffer = static_cast<uint16_t*>(src->planes[UHDR_PLANE_Y]);
+ src->planes[UHDR_PLANE_Y] = &src_buffer[top * src->stride[UHDR_PLANE_Y] + left];
+ uint32_t* src_uv_buffer = static_cast<uint32_t*>(src->planes[UHDR_PLANE_UV]);
+ src->planes[UHDR_PLANE_UV] =
+ &src_uv_buffer[(top / 2) * (src->stride[UHDR_PLANE_UV] / 2) + (left / 2)];
+ } else if (src->fmt == UHDR_IMG_FMT_12bppYCbCr420 || src->fmt == UHDR_IMG_FMT_8bppYCbCr400) {
+ uint8_t* src_buffer = static_cast<uint8_t*>(src->planes[UHDR_PLANE_Y]);
+ src->planes[UHDR_PLANE_Y] = &src_buffer[top * src->stride[UHDR_PLANE_Y] + left];
+ if (src->fmt == UHDR_IMG_FMT_12bppYCbCr420) {
+ for (int i = 1; i < 3; i++) {
+ src->planes[i] = &src_buffer[(top / 2) * src->stride[i] + (left / 2)];
+ }
+ }
+ } else if (src->fmt == UHDR_IMG_FMT_32bppRGBA1010102 || src->fmt == UHDR_IMG_FMT_32bppRGBA8888) {
+ uint32_t* src_buffer = static_cast<uint32_t*>(src->planes[UHDR_PLANE_PACKED]);
+ src->planes[UHDR_PLANE_PACKED] = &src_buffer[top * src->stride[UHDR_PLANE_PACKED] + left];
+ } else if (src->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat) {
+ uint64_t* src_buffer = static_cast<uint64_t*>(src->planes[UHDR_PLANE_PACKED]);
+ src->planes[UHDR_PLANE_PACKED] = &src_buffer[top * src->stride[UHDR_PLANE_PACKED] + left];
+ }
+ src->w = wd;
+ src->h = ht;
+}
+
+std::unique_ptr<uhdr_raw_image_ext_t> apply_resize(uhdr_raw_image_t* src, int dst_w, int dst_h) {
+ std::unique_ptr<uhdr_raw_image_ext_t> dst = std::make_unique<uhdr_raw_image_ext_t>(
+ src->fmt, src->cg, src->ct, src->range, dst_w, dst_h, 1);
+
+ if (src->fmt == UHDR_IMG_FMT_24bppYCbCrP010) {
+ uint16_t* src_buffer = static_cast<uint16_t*>(src->planes[UHDR_PLANE_Y]);
+ uint16_t* dst_buffer = static_cast<uint16_t*>(dst->planes[UHDR_PLANE_Y]);
+ resize_buffer(src_buffer, dst_buffer, src->w, src->h, dst->w, dst->h, src->stride[UHDR_PLANE_Y],
+ dst->stride[UHDR_PLANE_Y]);
+ uint32_t* src_uv_buffer = static_cast<uint32_t*>(src->planes[UHDR_PLANE_UV]);
+ uint32_t* dst_uv_buffer = static_cast<uint32_t*>(dst->planes[UHDR_PLANE_UV]);
+ resize_buffer(src_uv_buffer, dst_uv_buffer, src->w / 4, src->h / 2, dst->w / 4, dst->h / 2,
+ src->stride[UHDR_PLANE_UV] / 2, dst->stride[UHDR_PLANE_UV] / 2);
+ } else if (src->fmt == UHDR_IMG_FMT_12bppYCbCr420 || src->fmt == UHDR_IMG_FMT_8bppYCbCr400) {
+ uint8_t* src_buffer = static_cast<uint8_t*>(src->planes[UHDR_PLANE_Y]);
+ uint8_t* dst_buffer = static_cast<uint8_t*>(dst->planes[UHDR_PLANE_Y]);
+ resize_buffer(src_buffer, dst_buffer, src->w, src->h, dst->w, dst->h, src->stride[UHDR_PLANE_Y],
+ dst->stride[UHDR_PLANE_Y]);
+ if (src->fmt == UHDR_IMG_FMT_12bppYCbCr420) {
+ for (int i = 1; i < 3; i++) {
+ src_buffer = static_cast<uint8_t*>(src->planes[i]);
+ dst_buffer = static_cast<uint8_t*>(dst->planes[i]);
+ resize_buffer(src_buffer, dst_buffer, src->w / 2, src->h / 2, dst->w / 2, dst->h / 2,
+ src->stride[i], dst->stride[i]);
+ }
+ }
+ } else if (src->fmt == UHDR_IMG_FMT_32bppRGBA1010102 || src->fmt == UHDR_IMG_FMT_32bppRGBA8888) {
+ uint32_t* src_buffer = static_cast<uint32_t*>(src->planes[UHDR_PLANE_PACKED]);
+ uint32_t* dst_buffer = static_cast<uint32_t*>(dst->planes[UHDR_PLANE_PACKED]);
+ resize_buffer(src_buffer, dst_buffer, src->w, src->h, dst->w, dst->h,
+ src->stride[UHDR_PLANE_PACKED], dst->stride[UHDR_PLANE_PACKED]);
+ } else if (src->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat) {
+ uint64_t* src_buffer = static_cast<uint64_t*>(src->planes[UHDR_PLANE_PACKED]);
+ uint64_t* dst_buffer = static_cast<uint64_t*>(dst->planes[UHDR_PLANE_PACKED]);
+ resize_buffer(src_buffer, dst_buffer, src->w, src->h, dst->w, dst->h,
+ src->stride[UHDR_PLANE_PACKED], dst->stride[UHDR_PLANE_PACKED]);
+ }
+ return std::move(dst);
+}
+} // namespace ultrahdr
diff --git a/lib/src/gainmapmath.cpp b/lib/src/gainmapmath.cpp
index 8ace1e7..fc2f8d8 100644
--- a/lib/src/gainmapmath.cpp
+++ b/lib/src/gainmapmath.cpp
@@ -575,8 +575,9 @@ Color getYuv420Pixel(jr_uncompressed_ptr image, size_t x, size_t y) {
// 128 bias for UV given we are using jpeglib; see:
// https://github.com/kornelski/libjpeg/blob/master/structure.doc
- return {{{static_cast<float>(y_uint) / 255.0f, (static_cast<float>(u_uint) - 128.0f) / 255.0f,
- (static_cast<float>(v_uint) - 128.0f) / 255.0f}}};
+ return {
+ {{static_cast<float>(y_uint) * (1 / 255.0f), static_cast<float>(u_uint - 128) * (1 / 255.0f),
+ static_cast<float>(v_uint - 128) * (1 / 255.0f)}}};
}
Color getP010Pixel(jr_uncompressed_ptr image, size_t x, size_t y) {
@@ -594,9 +595,9 @@ Color getP010Pixel(jr_uncompressed_ptr image, size_t x, size_t y) {
uint16_t v_uint = chroma_data[pixel_v_idx] >> 6;
// Conversions include taking narrow-range into account.
- return {{{(static_cast<float>(y_uint) - 64.0f) / 876.0f,
- (static_cast<float>(u_uint) - 64.0f) / 896.0f - 0.5f,
- (static_cast<float>(v_uint) - 64.0f) / 896.0f - 0.5f}}};
+ return {{{static_cast<float>(y_uint - 64) * (1 / 876.0f),
+ static_cast<float>(u_uint - 64) * (1 / 896.0f) - 0.5f,
+ static_cast<float>(v_uint - 64) * (1 / 896.0f) - 0.5f}}};
}
typedef Color (*getPixelFn)(jr_uncompressed_ptr, size_t, size_t);
diff --git a/lib/src/jpegdecoderhelper.cpp b/lib/src/jpegdecoderhelper.cpp
index 70efb87..b787687 100644
--- a/lib/src/jpegdecoderhelper.cpp
+++ b/lib/src/jpegdecoderhelper.cpp
@@ -270,9 +270,15 @@ bool JpegDecoderHelper::decode(const void* image, int length, decode_mode_t deco
ALOGE("%s: decodeToRGBA unexpected primary image sub-sampling", __func__);
goto CleanUp;
}
+#ifdef JCS_ALPHA_EXTENSIONS
// 4 bytes per pixel
mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 4);
cinfo.out_color_space = JCS_EXT_RGBA;
+#else
+ // 3 bytes per pixel
+ mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 3);
+ cinfo.out_color_space = JCS_RGB;
+#endif
} else if (decodeTo == DECODE_TO_YCBCR) {
if (cinfo.jpeg_color_space == JCS_YCbCr) {
if (cinfo.comp_info[0].h_samp_factor != 2 || cinfo.comp_info[0].v_samp_factor != 2 ||
@@ -315,9 +321,19 @@ CleanUp:
bool JpegDecoderHelper::decompress(jpeg_decompress_struct* cinfo, const uint8_t* dest,
bool isSingleChannel) {
- return isSingleChannel ? decompressSingleChannel(cinfo, dest)
- : ((cinfo->out_color_space == JCS_EXT_RGBA) ? decompressRGBA(cinfo, dest)
- : decompressYUV(cinfo, dest));
+ if (isSingleChannel) {
+ return decompressSingleChannel(cinfo, dest);
+ } else {
+#ifdef JCS_ALPHA_EXTENSIONS
+ if (cinfo->out_color_space == JCS_EXT_RGBA) {
+#else
+ if (cinfo->out_color_space == JCS_RGB) {
+#endif
+ return decompressRGBA(cinfo, dest);
+ } else {
+ return decompressYUV(cinfo, dest);
+ }
+ }
}
bool JpegDecoderHelper::getCompressedImageParameters(const void* image, int length) {
@@ -329,7 +345,11 @@ bool JpegDecoderHelper::decompressRGBA(jpeg_decompress_struct* cinfo, const uint
while (cinfo->output_scanline < cinfo->image_height) {
if (1 != jpeg_read_scanlines(cinfo, &out, 1)) return false;
+#ifdef JCS_ALPHA_EXTENSIONS
out += cinfo->image_width * 4;
+#else
+ out += cinfo->image_width * 3;
+#endif
}
return true;
}
diff --git a/lib/src/jpegr.cpp b/lib/src/jpegr.cpp
index 74ef134..848eb96 100644
--- a/lib/src/jpegr.cpp
+++ b/lib/src/jpegr.cpp
@@ -504,7 +504,25 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr,
yuv420_image.data = jpeg_dec_obj_yuv420.getDecompressedImagePtr();
yuv420_image.width = jpeg_dec_obj_yuv420.getDecompressedImageWidth();
yuv420_image.height = jpeg_dec_obj_yuv420.getDecompressedImageHeight();
- yuv420_image.colorGamut = yuv420jpg_image_ptr->colorGamut;
+ if (jpeg_dec_obj_yuv420.getICCSize() > 0) {
+ ultrahdr_color_gamut cg = IccHelper::readIccColorGamut(jpeg_dec_obj_yuv420.getICCPtr(),
+ jpeg_dec_obj_yuv420.getICCSize());
+ if (cg == ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
+ (yuv420jpg_image_ptr->colorGamut != ULTRAHDR_COLORGAMUT_UNSPECIFIED &&
+ yuv420jpg_image_ptr->colorGamut != cg)) {
+ ALOGE("configured color gamut %d does not match with color gamut specified in icc box %d",
+ yuv420jpg_image_ptr->colorGamut, cg);
+ return ERROR_JPEGR_INVALID_COLORGAMUT;
+ }
+ yuv420_image.colorGamut = cg;
+ } else {
+ if (yuv420jpg_image_ptr->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
+ yuv420jpg_image_ptr->colorGamut > ULTRAHDR_COLORGAMUT_MAX) {
+ ALOGE("Unrecognized 420 color gamut %d", yuv420jpg_image_ptr->colorGamut);
+ return ERROR_JPEGR_INVALID_COLORGAMUT;
+ }
+ yuv420_image.colorGamut = yuv420jpg_image_ptr->colorGamut;
+ }
if (yuv420_image.luma_stride == 0) yuv420_image.luma_stride = yuv420_image.width;
if (!yuv420_image.chroma_data) {
uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
@@ -568,6 +586,11 @@ status_t JpegR::encodeJPEGR(jr_compressed_ptr yuv420jpg_image_ptr,
JPEGR_CHECK(appendGainMap(yuv420jpg_image_ptr, gainmapjpg_image_ptr, /* exif */ nullptr,
/* icc */ nullptr, /* icc size */ 0, metadata, dest));
} else {
+ if (yuv420jpg_image_ptr->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
+ yuv420jpg_image_ptr->colorGamut > ULTRAHDR_COLORGAMUT_MAX) {
+ ALOGE("Unrecognized 420 color gamut %d", yuv420jpg_image_ptr->colorGamut);
+ return ERROR_JPEGR_INVALID_COLORGAMUT;
+ }
std::shared_ptr<DataStruct> newIcc =
IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, yuv420jpg_image_ptr->colorGamut);
JPEGR_CHECK(appendGainMap(yuv420jpg_image_ptr, gainmapjpg_image_ptr, /* exif */ nullptr,
@@ -589,7 +612,7 @@ status_t JpegR::getJPEGRInfo(jr_compressed_ptr jpegr_image_ptr, jr_info_ptr jpeg
jpegr_compressed_struct primary_image, gainmap_image;
status_t status = extractPrimaryImageAndGainMap(jpegr_image_ptr, &primary_image, &gainmap_image);
- if (status != JPEGR_NO_ERROR && status != ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND) {
+ if (status != JPEGR_NO_ERROR) {
return status;
}
status = parseJpegInfo(&primary_image, jpegr_image_info_ptr->primaryImgInfo,
@@ -628,6 +651,10 @@ status_t JpegR::decodeJPEGR(jr_compressed_ptr jpegr_image_ptr, jr_uncompressed_p
ALOGE("received nullptr address for exif data");
return ERROR_JPEGR_BAD_PTR;
}
+ if (gainmap_image_ptr != nullptr && gainmap_image_ptr->data == nullptr) {
+ ALOGE("received nullptr address for gainmap data");
+ return ERROR_JPEGR_BAD_PTR;
+ }
if (output_format <= ULTRAHDR_OUTPUT_UNSPECIFIED || output_format > ULTRAHDR_OUTPUT_MAX) {
ALOGE("received bad value for output format %d", output_format);
return ERROR_JPEGR_INVALID_OUTPUT_FORMAT;
@@ -637,10 +664,8 @@ status_t JpegR::decodeJPEGR(jr_compressed_ptr jpegr_image_ptr, jr_uncompressed_p
status_t status =
extractPrimaryImageAndGainMap(jpegr_image_ptr, &primary_jpeg_image, &gainmap_jpeg_image);
if (status != JPEGR_NO_ERROR) {
- if (output_format != ULTRAHDR_OUTPUT_SDR || status != ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND) {
- ALOGE("received invalid compressed jpegr image");
- return status;
- }
+ ALOGE("received invalid compressed jpegr image");
+ return status;
}
JpegDecoderHelper jpeg_dec_obj_yuv420;
@@ -651,11 +676,19 @@ status_t JpegR::decodeJPEGR(jr_compressed_ptr jpegr_image_ptr, jr_uncompressed_p
}
if (output_format == ULTRAHDR_OUTPUT_SDR) {
+#ifdef JCS_ALPHA_EXTENSIONS
if ((jpeg_dec_obj_yuv420.getDecompressedImageWidth() *
jpeg_dec_obj_yuv420.getDecompressedImageHeight() * 4) >
jpeg_dec_obj_yuv420.getDecompressedImageSize()) {
return ERROR_JPEGR_DECODE_ERROR;
}
+#else
+ if ((jpeg_dec_obj_yuv420.getDecompressedImageWidth() *
+ jpeg_dec_obj_yuv420.getDecompressedImageHeight() * 3) >
+ jpeg_dec_obj_yuv420.getDecompressedImageSize()) {
+ return ERROR_JPEGR_DECODE_ERROR;
+ }
+#endif
} else {
if ((jpeg_dec_obj_yuv420.getDecompressedImageWidth() *
jpeg_dec_obj_yuv420.getDecompressedImageHeight() * 3 / 2) >
@@ -665,9 +698,6 @@ status_t JpegR::decodeJPEGR(jr_compressed_ptr jpegr_image_ptr, jr_uncompressed_p
}
if (exif != nullptr) {
- if (exif->data == nullptr) {
- return ERROR_JPEGR_BAD_PTR;
- }
if (exif->length < jpeg_dec_obj_yuv420.getEXIFSize()) {
return ERROR_JPEGR_BUFFER_TOO_SMALL;
}
@@ -675,51 +705,65 @@ status_t JpegR::decodeJPEGR(jr_compressed_ptr jpegr_image_ptr, jr_uncompressed_p
exif->length = jpeg_dec_obj_yuv420.getEXIFSize();
}
- if (output_format == ULTRAHDR_OUTPUT_SDR) {
- dest->width = jpeg_dec_obj_yuv420.getDecompressedImageWidth();
- dest->height = jpeg_dec_obj_yuv420.getDecompressedImageHeight();
- memcpy(dest->data, jpeg_dec_obj_yuv420.getDecompressedImagePtr(),
- dest->width * dest->height * 4);
- return JPEGR_NO_ERROR;
- }
-
JpegDecoderHelper jpeg_dec_obj_gm;
- if (!jpeg_dec_obj_gm.decompressImage(gainmap_jpeg_image.data, gainmap_jpeg_image.length)) {
- return ERROR_JPEGR_DECODE_ERROR;
- }
- if ((jpeg_dec_obj_gm.getDecompressedImageWidth() * jpeg_dec_obj_gm.getDecompressedImageHeight()) >
- jpeg_dec_obj_gm.getDecompressedImageSize()) {
- return ERROR_JPEGR_DECODE_ERROR;
- }
-
jpegr_uncompressed_struct gainmap_image;
- gainmap_image.data = jpeg_dec_obj_gm.getDecompressedImagePtr();
- gainmap_image.width = jpeg_dec_obj_gm.getDecompressedImageWidth();
- gainmap_image.height = jpeg_dec_obj_gm.getDecompressedImageHeight();
-
- if (gainmap_image_ptr != nullptr) {
- gainmap_image_ptr->width = gainmap_image.width;
- gainmap_image_ptr->height = gainmap_image.height;
- int size = gainmap_image_ptr->width * gainmap_image_ptr->height;
- gainmap_image_ptr->data = malloc(size);
- memcpy(gainmap_image_ptr->data, gainmap_image.data, size);
+ if (gainmap_image_ptr != nullptr || output_format != ULTRAHDR_OUTPUT_SDR) {
+ if (!jpeg_dec_obj_gm.decompressImage(gainmap_jpeg_image.data, gainmap_jpeg_image.length)) {
+ return ERROR_JPEGR_DECODE_ERROR;
+ }
+ if ((jpeg_dec_obj_gm.getDecompressedImageWidth() *
+ jpeg_dec_obj_gm.getDecompressedImageHeight()) >
+ jpeg_dec_obj_gm.getDecompressedImageSize()) {
+ return ERROR_JPEGR_DECODE_ERROR;
+ }
+ gainmap_image.data = jpeg_dec_obj_gm.getDecompressedImagePtr();
+ gainmap_image.width = jpeg_dec_obj_gm.getDecompressedImageWidth();
+ gainmap_image.height = jpeg_dec_obj_gm.getDecompressedImageHeight();
+
+ if (gainmap_image_ptr != nullptr) {
+ gainmap_image_ptr->width = gainmap_image.width;
+ gainmap_image_ptr->height = gainmap_image.height;
+ memcpy(gainmap_image_ptr->data, gainmap_image.data,
+ gainmap_image_ptr->width * gainmap_image_ptr->height);
+ }
}
ultrahdr_metadata_struct uhdr_metadata;
- if (!getMetadataFromXMP(static_cast<uint8_t*>(jpeg_dec_obj_gm.getXMPPtr()),
- jpeg_dec_obj_gm.getXMPSize(), &uhdr_metadata)) {
- return ERROR_JPEGR_METADATA_ERROR;
+ if (metadata != nullptr || output_format != ULTRAHDR_OUTPUT_SDR) {
+ if (!getMetadataFromXMP(static_cast<uint8_t*>(jpeg_dec_obj_gm.getXMPPtr()),
+ jpeg_dec_obj_gm.getXMPSize(), &uhdr_metadata)) {
+ return ERROR_JPEGR_METADATA_ERROR;
+ }
+ if (metadata != nullptr) {
+ metadata->version = uhdr_metadata.version;
+ metadata->minContentBoost = uhdr_metadata.minContentBoost;
+ metadata->maxContentBoost = uhdr_metadata.maxContentBoost;
+ metadata->gamma = uhdr_metadata.gamma;
+ metadata->offsetSdr = uhdr_metadata.offsetSdr;
+ metadata->offsetHdr = uhdr_metadata.offsetHdr;
+ metadata->hdrCapacityMin = uhdr_metadata.hdrCapacityMin;
+ metadata->hdrCapacityMax = uhdr_metadata.hdrCapacityMax;
+ }
}
- if (metadata != nullptr) {
- metadata->version = uhdr_metadata.version;
- metadata->minContentBoost = uhdr_metadata.minContentBoost;
- metadata->maxContentBoost = uhdr_metadata.maxContentBoost;
- metadata->gamma = uhdr_metadata.gamma;
- metadata->offsetSdr = uhdr_metadata.offsetSdr;
- metadata->offsetHdr = uhdr_metadata.offsetHdr;
- metadata->hdrCapacityMin = uhdr_metadata.hdrCapacityMin;
- metadata->hdrCapacityMax = uhdr_metadata.hdrCapacityMax;
+ if (output_format == ULTRAHDR_OUTPUT_SDR) {
+ dest->width = jpeg_dec_obj_yuv420.getDecompressedImageWidth();
+ dest->height = jpeg_dec_obj_yuv420.getDecompressedImageHeight();
+#ifdef JCS_ALPHA_EXTENSIONS
+ memcpy(dest->data, jpeg_dec_obj_yuv420.getDecompressedImagePtr(),
+ dest->width * dest->height * 4);
+#else
+ uint32_t* pixelDst = static_cast<uint32_t*>(dest->data);
+ uint8_t* pixelSrc = static_cast<uint8_t*>(jpeg_dec_obj_yuv420.getDecompressedImagePtr());
+ for (int i = 0; i < dest->width * dest->height; i++) {
+ *pixelDst = pixelSrc[0] | (pixelSrc[1] << 8) | (pixelSrc[2] << 16) | (0xff << 24);
+ pixelSrc += 3;
+ pixelDst += 1;
+ }
+#endif
+ dest->colorGamut = IccHelper::readIccColorGamut(jpeg_dec_obj_yuv420.getICCPtr(),
+ jpeg_dec_obj_yuv420.getICCSize());
+ return JPEGR_NO_ERROR;
}
jpegr_uncompressed_struct yuv420_image;
@@ -1039,6 +1083,7 @@ status_t JpegR::applyGainMap(jr_uncompressed_ptr yuv420_image_ptr,
dest->width = yuv420_image_ptr->width;
dest->height = yuv420_image_ptr->height;
+ dest->colorGamut = yuv420_image_ptr->colorGamut;
ShepardsIDW idwTable(map_scale_factor);
float display_boost = (std::min)(max_display_boost, metadata->maxContentBoost);
GainLUT gainLUT(metadata, display_boost);
diff --git a/lib/src/ultrahdr_api.cpp b/lib/src/ultrahdr_api.cpp
new file mode 100644
index 0000000..17280f2
--- /dev/null
+++ b/lib/src/ultrahdr_api.cpp
@@ -0,0 +1,1478 @@
+/*
+ * 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.
+ */
+
+#include <cstdio>
+#include <cstring>
+
+#include "ultrahdr_api.h"
+#include "ultrahdr/ultrahdrcommon.h"
+#include "ultrahdr/editorhelper.h"
+#include "ultrahdr/jpegr.h"
+#include "ultrahdr/jpegrutils.h"
+
+static const uhdr_error_info_t g_no_error = {UHDR_CODEC_OK, 0, ""};
+
+namespace ultrahdr {
+
+uhdr_memory_block::uhdr_memory_block(size_t capacity) {
+ m_buffer = std::make_unique<uint8_t[]>(capacity);
+ m_capacity = capacity;
+}
+
+uhdr_raw_image_ext::uhdr_raw_image_ext(uhdr_img_fmt_t fmt, uhdr_color_gamut_t cg,
+ uhdr_color_transfer_t ct, uhdr_color_range_t range,
+ unsigned w, unsigned h, unsigned align_stride_to) {
+ this->fmt = fmt;
+ this->cg = cg;
+ this->ct = ct;
+ this->range = range;
+
+ this->w = w;
+ this->h = h;
+
+ int aligned_width = ALIGNM(w, align_stride_to);
+
+ int bpp = 1;
+ if (fmt == UHDR_IMG_FMT_24bppYCbCrP010) {
+ bpp = 2;
+ } else if (fmt == UHDR_IMG_FMT_32bppRGBA8888 || fmt == UHDR_IMG_FMT_32bppRGBA1010102) {
+ bpp = 4;
+ } else if (fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat) {
+ bpp = 8;
+ }
+
+ size_t plane_1_sz = bpp * aligned_width * h;
+ size_t plane_2_sz;
+ size_t plane_3_sz;
+ if (fmt == UHDR_IMG_FMT_24bppYCbCrP010) {
+ plane_2_sz = (2 /* planes */ * ((aligned_width / 2) * (h / 2) * bpp));
+ plane_3_sz = 0;
+ } else if (fmt == UHDR_IMG_FMT_12bppYCbCr420) {
+ plane_2_sz = (((aligned_width / 2) * (h / 2) * bpp));
+ plane_3_sz = (((aligned_width / 2) * (h / 2) * bpp));
+ } else {
+ plane_2_sz = 0;
+ plane_3_sz = 0;
+ }
+ size_t total_size = plane_1_sz + plane_2_sz + plane_3_sz;
+ this->m_block = std::make_unique<uhdr_memory_block_t>(total_size);
+
+ uint8_t* data = this->m_block->m_buffer.get();
+ this->planes[UHDR_PLANE_Y] = data;
+ this->stride[UHDR_PLANE_Y] = aligned_width;
+ if (fmt == UHDR_IMG_FMT_24bppYCbCrP010) {
+ this->planes[UHDR_PLANE_UV] = data + plane_1_sz;
+ this->stride[UHDR_PLANE_UV] = aligned_width;
+ this->planes[UHDR_PLANE_V] = nullptr;
+ this->stride[UHDR_PLANE_V] = 0;
+ } else if (fmt == UHDR_IMG_FMT_12bppYCbCr420) {
+ this->planes[UHDR_PLANE_U] = data + plane_1_sz;
+ this->stride[UHDR_PLANE_U] = aligned_width / 2;
+ this->planes[UHDR_PLANE_V] = data + plane_1_sz + plane_2_sz;
+ this->stride[UHDR_PLANE_V] = aligned_width / 2;
+ } else {
+ this->planes[UHDR_PLANE_U] = nullptr;
+ this->stride[UHDR_PLANE_U] = 0;
+ this->planes[UHDR_PLANE_V] = nullptr;
+ this->stride[UHDR_PLANE_V] = 0;
+ }
+}
+
+uhdr_compressed_image_ext::uhdr_compressed_image_ext(uhdr_color_gamut_t cg,
+ uhdr_color_transfer_t ct,
+ uhdr_color_range_t range, unsigned size) {
+ this->m_block = std::make_unique<uhdr_memory_block_t>(size);
+ this->data = this->m_block->m_buffer.get();
+ this->capacity = size;
+ this->data_sz = 0;
+ this->cg = cg;
+ this->ct = ct;
+ this->range = range;
+}
+
+uhdr_error_info_t apply_effects(uhdr_decoder_private* dec) {
+ for (auto& it : dec->m_effects) {
+ std::unique_ptr<ultrahdr::uhdr_raw_image_ext_t> disp_img = nullptr;
+ std::unique_ptr<ultrahdr::uhdr_raw_image_ext_t> gm_img = nullptr;
+
+ if (nullptr != dynamic_cast<uhdr_rotate_effect_t*>(it)) {
+ int degree = (dynamic_cast<ultrahdr::uhdr_rotate_effect_t*>(it))->m_degree;
+ disp_img = apply_rotate(dec->m_decoded_img_buffer.get(), degree);
+ gm_img = apply_rotate(dec->m_gainmap_img_buffer.get(), degree);
+ } else if (nullptr != dynamic_cast<uhdr_mirror_effect_t*>(it)) {
+ uhdr_mirror_direction_t direction = (dynamic_cast<uhdr_mirror_effect_t*>(it))->m_direction;
+ disp_img = apply_mirror(dec->m_decoded_img_buffer.get(), direction);
+ gm_img = apply_mirror(dec->m_gainmap_img_buffer.get(), direction);
+ } else if (nullptr != dynamic_cast<uhdr_crop_effect_t*>(it)) {
+ auto crop_effect = dynamic_cast<uhdr_crop_effect_t*>(it);
+ uhdr_raw_image_t* disp = dec->m_decoded_img_buffer.get();
+ uhdr_raw_image_t* gm = dec->m_gainmap_img_buffer.get();
+ int left = (std::max)(0, crop_effect->m_left);
+ int right = (std::min)((int)disp->w, crop_effect->m_right);
+ int top = (std::max)(0, crop_effect->m_top);
+ int bottom = (std::min)((int)disp->h, crop_effect->m_bottom);
+ int scale_factor = disp->w / gm->w;
+
+ if (right - left <= scale_factor || bottom - top <= scale_factor) {
+ uhdr_error_info_t status;
+ status.error_code = UHDR_CODEC_UNKNOWN_ERROR;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail,
+ "After crop image dimensions are <= 0, display image dimensions %dx%d, gain map "
+ "image dimensions %dx%d",
+ right - left, bottom - top, (right - left) / scale_factor,
+ (bottom - top) / scale_factor);
+ return status;
+ }
+ apply_crop(disp, left, top, right - left, bottom - top);
+ apply_crop(gm, left / scale_factor, top / scale_factor, (right - left) / scale_factor,
+ (bottom - top) / scale_factor);
+ continue;
+ } else if (nullptr != dynamic_cast<uhdr_resize_effect_t*>(it)) {
+ auto resize_effect = dynamic_cast<uhdr_resize_effect_t*>(it);
+ int dst_w = resize_effect->m_width;
+ int dst_h = resize_effect->m_height;
+ if (dst_w == 0 || dst_h == 0) {
+ uhdr_error_info_t status;
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ snprintf(status.detail, sizeof status.detail,
+ "destination width or destination height cannot be zero");
+ return status;
+ }
+ disp_img = apply_resize(dec->m_decoded_img_buffer.get(), dst_w, dst_h);
+ gm_img = apply_resize(dec->m_gainmap_img_buffer.get(), dst_w, dst_h);
+ }
+
+ if (disp_img == nullptr || gm_img == nullptr) {
+ uhdr_error_info_t status;
+ status.error_code = UHDR_CODEC_UNKNOWN_ERROR;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail,
+ "encountered unknown error while applying effect %s", it->to_string().c_str());
+ return status;
+ }
+ dec->m_decoded_img_buffer = std::move(disp_img);
+ dec->m_gainmap_img_buffer = std::move(gm_img);
+ }
+
+ return g_no_error;
+}
+
+} // namespace ultrahdr
+
+uhdr_codec_private::~uhdr_codec_private() {
+ for (auto it : m_effects) delete it;
+ m_effects.clear();
+}
+
+ultrahdr::ultrahdr_pixel_format map_pix_fmt_to_internal_pix_fmt(uhdr_img_fmt_t fmt) {
+ switch (fmt) {
+ case UHDR_IMG_FMT_12bppYCbCr420:
+ return ultrahdr::ULTRAHDR_PIX_FMT_YUV420;
+ case UHDR_IMG_FMT_24bppYCbCrP010:
+ return ultrahdr::ULTRAHDR_PIX_FMT_P010;
+ case UHDR_IMG_FMT_32bppRGBA1010102:
+ return ultrahdr::ULTRAHDR_PIX_FMT_RGBA1010102;
+ case UHDR_IMG_FMT_32bppRGBA8888:
+ return ultrahdr::ULTRAHDR_PIX_FMT_RGBA8888;
+ case UHDR_IMG_FMT_64bppRGBAHalfFloat:
+ return ultrahdr::ULTRAHDR_PIX_FMT_RGBAF16;
+ case UHDR_IMG_FMT_8bppYCbCr400:
+ return ultrahdr::ULTRAHDR_PIX_FMT_MONOCHROME;
+ default:
+ return ultrahdr::ULTRAHDR_PIX_FMT_UNSPECIFIED;
+ }
+}
+
+ultrahdr::ultrahdr_color_gamut map_cg_to_internal_cg(uhdr_color_gamut_t cg) {
+ switch (cg) {
+ case UHDR_CG_BT_2100:
+ return ultrahdr::ULTRAHDR_COLORGAMUT_BT2100;
+ case UHDR_CG_BT_709:
+ return ultrahdr::ULTRAHDR_COLORGAMUT_BT709;
+ case UHDR_CG_DISPLAY_P3:
+ return ultrahdr::ULTRAHDR_COLORGAMUT_P3;
+ default:
+ return ultrahdr::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
+ }
+}
+
+uhdr_color_gamut_t map_internal_cg_to_cg(ultrahdr::ultrahdr_color_gamut cg) {
+ switch (cg) {
+ case ultrahdr::ULTRAHDR_COLORGAMUT_BT2100:
+ return UHDR_CG_BT_2100;
+ case ultrahdr::ULTRAHDR_COLORGAMUT_BT709:
+ return UHDR_CG_BT_709;
+ case ultrahdr::ULTRAHDR_COLORGAMUT_P3:
+ return UHDR_CG_DISPLAY_P3;
+ default:
+ return UHDR_CG_UNSPECIFIED;
+ }
+}
+
+ultrahdr::ultrahdr_transfer_function map_ct_to_internal_ct(uhdr_color_transfer_t ct) {
+ switch (ct) {
+ case UHDR_CT_HLG:
+ return ultrahdr::ULTRAHDR_TF_HLG;
+ case UHDR_CT_PQ:
+ return ultrahdr::ULTRAHDR_TF_PQ;
+ case UHDR_CT_LINEAR:
+ return ultrahdr::ULTRAHDR_TF_LINEAR;
+ case UHDR_CT_SRGB:
+ return ultrahdr::ULTRAHDR_TF_SRGB;
+ default:
+ return ultrahdr::ULTRAHDR_TF_UNSPECIFIED;
+ }
+}
+
+ultrahdr::ultrahdr_output_format map_ct_fmt_to_internal_output_fmt(uhdr_color_transfer_t ct,
+ uhdr_img_fmt fmt) {
+ if (ct == UHDR_CT_HLG && fmt == UHDR_IMG_FMT_32bppRGBA1010102) {
+ return ultrahdr::ULTRAHDR_OUTPUT_HDR_HLG;
+ } else if (ct == UHDR_CT_PQ && fmt == UHDR_IMG_FMT_32bppRGBA1010102) {
+ return ultrahdr::ULTRAHDR_OUTPUT_HDR_PQ;
+ } else if (ct == UHDR_CT_LINEAR && fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat) {
+ return ultrahdr::ULTRAHDR_OUTPUT_HDR_LINEAR;
+ } else if (ct == UHDR_CT_SRGB && fmt == UHDR_IMG_FMT_32bppRGBA8888) {
+ return ultrahdr::ULTRAHDR_OUTPUT_SDR;
+ }
+ return ultrahdr::ULTRAHDR_OUTPUT_UNSPECIFIED;
+}
+
+void map_internal_error_status_to_error_info(ultrahdr::status_t internal_status,
+ uhdr_error_info_t& status) {
+ if (internal_status == ultrahdr::JPEGR_NO_ERROR) {
+ status = g_no_error;
+ } else {
+ status.has_detail = 1;
+ if (internal_status == ultrahdr::ERROR_JPEGR_RESOLUTION_MISMATCH) {
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ snprintf(status.detail, sizeof status.detail,
+ "dimensions of sdr intent and hdr intent do not match");
+ } else if (internal_status == ultrahdr::ERROR_JPEGR_ENCODE_ERROR) {
+ status.error_code = UHDR_CODEC_UNKNOWN_ERROR;
+ snprintf(status.detail, sizeof status.detail, "encountered unknown error during encoding");
+ } else if (internal_status == ultrahdr::ERROR_JPEGR_DECODE_ERROR) {
+ status.error_code = UHDR_CODEC_UNKNOWN_ERROR;
+ snprintf(status.detail, sizeof status.detail, "encountered unknown error during decoding");
+ } else if (internal_status == ultrahdr::ERROR_JPEGR_NO_IMAGES_FOUND) {
+ status.error_code = UHDR_CODEC_UNKNOWN_ERROR;
+ snprintf(status.detail, sizeof status.detail, "input uhdr image does not any valid images");
+ } else if (internal_status == ultrahdr::ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND) {
+ status.error_code = UHDR_CODEC_UNKNOWN_ERROR;
+ snprintf(status.detail, sizeof status.detail,
+ "input uhdr image does not contain gainmap image");
+ } else if (internal_status == ultrahdr::ERROR_JPEGR_BUFFER_TOO_SMALL) {
+ status.error_code = UHDR_CODEC_MEM_ERROR;
+ snprintf(status.detail, sizeof status.detail,
+ "output buffer to store compressed data is too small");
+ } else if (internal_status == ultrahdr::ERROR_JPEGR_MULTIPLE_EXIFS_RECEIVED) {
+ status.error_code = UHDR_CODEC_INVALID_OPERATION;
+ snprintf(status.detail, sizeof status.detail,
+ "received exif from uhdr_enc_set_exif_data() while the base image intent already "
+ "contains exif, unsure which one to use");
+ } else if (internal_status == ultrahdr::ERROR_JPEGR_UNSUPPORTED_MAP_SCALE_FACTOR) {
+ status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE;
+ snprintf(status.detail, sizeof status.detail,
+ "say base image wd to gain map image wd ratio is 'k1' and base image ht to gain map "
+ "image ht ratio is 'k2'. Either k1 is fractional or k2 is fractional or k1 != k2. "
+ "currently the library does not handle these scenarios");
+ } else {
+ status.error_code = UHDR_CODEC_UNKNOWN_ERROR;
+ status.has_detail = 0;
+ }
+ }
+}
+
+uhdr_error_info_t uhdr_enc_validate_and_set_compressed_img(uhdr_codec_private_t* enc,
+ uhdr_compressed_image_t* img,
+ uhdr_img_label_t intent) {
+ uhdr_error_info_t status = g_no_error;
+
+ if (dynamic_cast<uhdr_encoder_private*>(enc) == nullptr) {
+ 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 (img == nullptr) {
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail, "received nullptr for compressed image handle");
+ } else if (img->data == nullptr) {
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail,
+ "received nullptr for compressed img->data field");
+ } else if (img->capacity < img->data_sz) {
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail, "img->capacity %d is less than img->data_sz %d",
+ img->capacity, img->data_sz);
+ }
+ if (status.error_code != UHDR_CODEC_OK) return status;
+
+ uhdr_encoder_private* handle = dynamic_cast<uhdr_encoder_private*>(enc);
+ if (handle->m_sailed) {
+ status.error_code = UHDR_CODEC_INVALID_OPERATION;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail,
+ "An earlier call to uhdr_encode() has switched the context from configurable state to "
+ "end state. The context is no longer configurable. To reuse, call reset()");
+ return status;
+ }
+
+ auto entry = std::make_unique<ultrahdr::uhdr_compressed_image_ext_t>(img->cg, img->ct, img->range,
+ img->data_sz);
+ memcpy(entry->data, img->data, img->data_sz);
+ entry->data_sz = img->data_sz;
+ handle->m_compressed_images.insert_or_assign(intent, std::move(entry));
+
+ return status;
+}
+
+uhdr_codec_private_t* uhdr_create_encoder(void) {
+ uhdr_encoder_private* handle = new uhdr_encoder_private();
+
+ if (handle != nullptr) {
+ uhdr_reset_encoder(handle);
+ }
+ return handle;
+}
+
+void uhdr_release_encoder(uhdr_codec_private_t* enc) {
+ if (dynamic_cast<uhdr_encoder_private*>(enc) != nullptr) {
+ uhdr_encoder_private* handle = dynamic_cast<uhdr_encoder_private*>(enc);
+ delete handle;
+ }
+}
+
+uhdr_error_info_t uhdr_enc_set_raw_image(uhdr_codec_private_t* enc, uhdr_raw_image_t* img,
+ uhdr_img_label_t intent) {
+ uhdr_error_info_t status = g_no_error;
+
+ if (dynamic_cast<uhdr_encoder_private*>(enc) == nullptr) {
+ 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 (img == nullptr) {
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail, "received nullptr for raw image handle");
+ } else if (intent != UHDR_HDR_IMG && intent != UHDR_SDR_IMG) {
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail,
+ "invalid intent %d, expects one of {UHDR_HDR_IMG, UHDR_SDR_IMG}", intent);
+ } else if (img->fmt != UHDR_IMG_FMT_12bppYCbCr420 && img->fmt != UHDR_IMG_FMT_24bppYCbCrP010) {
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail,
+ "invalid input pixel format %d, expects one of {UHDR_IMG_FMT_12bppYCbCr420, "
+ "UHDR_IMG_FMT_24bppYCbCrP010}",
+ img->fmt);
+ } else if (img->cg != UHDR_CG_BT_2100 && img->cg != UHDR_CG_DISPLAY_P3 &&
+ img->cg != UHDR_CG_BT_709) {
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail,
+ "invalid input color gamut %d, expects one of {UHDR_CG_BT_2100, UHDR_CG_DISPLAY_P3, "
+ "UHDR_CG_BT_709}",
+ img->cg);
+ } else if (img->fmt == UHDR_IMG_FMT_12bppYCbCr420 && img->ct != UHDR_CT_SRGB) {
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail,
+ "invalid input color transfer for sdr intent image %d, expects UHDR_CT_SRGB", img->ct);
+ } else if (img->fmt == UHDR_IMG_FMT_24bppYCbCrP010 &&
+ (img->ct != UHDR_CT_HLG && img->ct != UHDR_CT_LINEAR && img->ct != UHDR_CT_PQ)) {
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail,
+ "invalid input color transfer for hdr intent image %d, expects one of {UHDR_CT_HLG, "
+ "UHDR_CT_LINEAR, UHDR_CT_PQ}",
+ img->ct);
+ } else if (img->w % 2 != 0 || img->h % 2 != 0) {
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail,
+ "image dimensions cannot be odd, received image dimensions %dx%d", img->w, img->h);
+ } else if (img->w < ultrahdr::kMinWidth || img->h < ultrahdr::kMinHeight) {
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail,
+ "image dimensions cannot be less than %dx%d, received image dimensions %dx%d",
+ ultrahdr::kMinWidth, ultrahdr::kMinHeight, img->w, img->h);
+ } else if (img->w > ultrahdr::kMaxWidth || img->h > ultrahdr::kMaxHeight) {
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail,
+ "image dimensions cannot be larger than %dx%d, received image dimensions %dx%d",
+ ultrahdr::kMaxWidth, ultrahdr::kMaxHeight, img->w, img->h);
+ } else if (img->fmt == UHDR_IMG_FMT_24bppYCbCrP010) {
+ if (img->planes[UHDR_PLANE_Y] == nullptr || img->planes[UHDR_PLANE_UV] == nullptr) {
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail,
+ "received nullptr for data field(s), luma ptr %p, chroma_uv ptr %p",
+ img->planes[UHDR_PLANE_Y], img->planes[UHDR_PLANE_UV]);
+ } else if (img->stride[UHDR_PLANE_Y] < img->w) {
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail,
+ "luma stride must not be smaller than width, stride=%d, width=%d",
+ img->stride[UHDR_PLANE_Y], img->w);
+ } else if (img->stride[UHDR_PLANE_UV] < img->w) {
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail,
+ "chroma_uv stride must not be smaller than width, stride=%d, width=%d",
+ img->stride[UHDR_PLANE_UV], img->w);
+ }
+ } else if (img->fmt == UHDR_IMG_FMT_12bppYCbCr420) {
+ if (img->planes[UHDR_PLANE_Y] == nullptr || img->planes[UHDR_PLANE_U] == nullptr ||
+ img->planes[UHDR_PLANE_V] == nullptr) {
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail,
+ "received nullptr for data field(s) luma ptr %p, chroma_u ptr %p, chroma_v ptr %p",
+ img->planes[UHDR_PLANE_Y], img->planes[UHDR_PLANE_U], img->planes[UHDR_PLANE_V]);
+ } else if (img->stride[UHDR_PLANE_Y] < img->w) {
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail,
+ "luma stride must not be smaller than width, stride=%d, width=%d",
+ img->stride[UHDR_PLANE_Y], img->w);
+ } else if (img->stride[UHDR_PLANE_U] < img->w / 2) {
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail,
+ "chroma_u stride must not be smaller than width / 2, stride=%d, width=%d",
+ img->stride[UHDR_PLANE_U], img->w);
+ } else if (img->stride[UHDR_PLANE_V] < img->w / 2) {
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail,
+ "chroma_v stride must not be smaller than width / 2, stride=%d, width=%d",
+ img->stride[UHDR_PLANE_V], img->w);
+ }
+ }
+ if (status.error_code != UHDR_CODEC_OK) return status;
+
+ uhdr_encoder_private* handle = dynamic_cast<uhdr_encoder_private*>(enc);
+ if (intent == UHDR_HDR_IMG &&
+ 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 (img->w != sdr_raw_entry->w || img->h != sdr_raw_entry->h) {
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail,
+ "image resolutions mismatch: hdr intent: %dx%d, sdr intent: %dx%d", img->w, img->h,
+ sdr_raw_entry->w, sdr_raw_entry->h);
+ return status;
+ }
+ }
+ if (intent == UHDR_SDR_IMG &&
+ 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;
+ if (img->w != hdr_raw_entry->w || img->h != hdr_raw_entry->h) {
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail,
+ "image resolutions mismatch: sdr intent: %dx%d, hdr intent: %dx%d", img->w, img->h,
+ hdr_raw_entry->w, hdr_raw_entry->h);
+ return status;
+ }
+ }
+ if (handle->m_sailed) {
+ status.error_code = UHDR_CODEC_INVALID_OPERATION;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail,
+ "An earlier call to uhdr_encode() has switched the context from configurable state to "
+ "end state. The context is no longer configurable. To reuse, call reset()");
+ return status;
+ }
+
+ std::unique_ptr<ultrahdr::uhdr_raw_image_ext_t> entry =
+ std::make_unique<ultrahdr::uhdr_raw_image_ext_t>(img->fmt, img->cg, img->ct, img->range,
+ img->w, img->h, 64);
+
+ if (img->fmt == UHDR_IMG_FMT_12bppYCbCr420) {
+ uint8_t* y_dst = static_cast<uint8_t*>(entry->planes[UHDR_PLANE_Y]);
+ uint8_t* y_src = static_cast<uint8_t*>(img->planes[UHDR_PLANE_Y]);
+ uint8_t* u_dst = static_cast<uint8_t*>(entry->planes[UHDR_PLANE_U]);
+ uint8_t* u_src = static_cast<uint8_t*>(img->planes[UHDR_PLANE_U]);
+ uint8_t* v_dst = static_cast<uint8_t*>(entry->planes[UHDR_PLANE_V]);
+ uint8_t* v_src = static_cast<uint8_t*>(img->planes[UHDR_PLANE_V]);
+
+ // copy y
+ for (size_t i = 0; i < img->h; i++) {
+ memcpy(y_dst, y_src, img->w);
+ y_dst += entry->stride[UHDR_PLANE_Y];
+ y_src += img->stride[UHDR_PLANE_Y];
+ }
+ // copy cb & cr
+ for (size_t i = 0; i < img->h / 2; i++) {
+ memcpy(u_dst, u_src, img->w / 2);
+ memcpy(v_dst, v_src, img->w / 2);
+ u_dst += entry->stride[UHDR_PLANE_U];
+ v_dst += entry->stride[UHDR_PLANE_V];
+ u_src += img->stride[UHDR_PLANE_U];
+ v_src += img->stride[UHDR_PLANE_V];
+ }
+ } else if (img->fmt == UHDR_IMG_FMT_24bppYCbCrP010) {
+ int bpp = 2;
+ uint8_t* y_dst = static_cast<uint8_t*>(entry->planes[UHDR_PLANE_Y]);
+ uint8_t* y_src = static_cast<uint8_t*>(img->planes[UHDR_PLANE_Y]);
+ uint8_t* uv_dst = static_cast<uint8_t*>(entry->planes[UHDR_PLANE_UV]);
+ uint8_t* uv_src = static_cast<uint8_t*>(img->planes[UHDR_PLANE_UV]);
+
+ // copy y
+ for (size_t i = 0; i < img->h; i++) {
+ memcpy(y_dst, y_src, img->w * bpp);
+ y_dst += (entry->stride[UHDR_PLANE_Y] * bpp);
+ y_src += (img->stride[UHDR_PLANE_Y] * bpp);
+ }
+ // copy cbcr
+ for (size_t i = 0; i < img->h / 2; i++) {
+ memcpy(uv_dst, uv_src, img->w * bpp);
+ uv_dst += (entry->stride[UHDR_PLANE_UV] * bpp);
+ uv_src += (img->stride[UHDR_PLANE_UV] * bpp);
+ }
+ }
+
+ handle->m_raw_images.insert_or_assign(intent, std::move(entry));
+
+ return status;
+}
+
+uhdr_error_info_t uhdr_enc_set_compressed_image(uhdr_codec_private_t* enc,
+ uhdr_compressed_image_t* img,
+ uhdr_img_label_t intent) {
+ uhdr_error_info_t status = g_no_error;
+
+ if (intent != UHDR_HDR_IMG && intent != UHDR_SDR_IMG && intent != UHDR_BASE_IMG) {
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail,
+ "invalid intent %d, expects one of {UHDR_HDR_IMG, UHDR_SDR_IMG, UHDR_BASE_IMG}",
+ intent);
+ }
+
+ return uhdr_enc_validate_and_set_compressed_img(enc, img, intent);
+}
+
+uhdr_error_info_t uhdr_enc_set_gainmap_image(uhdr_codec_private_t* enc,
+ uhdr_compressed_image_t* img,
+ uhdr_gainmap_metadata_t* metadata) {
+ uhdr_error_info_t status = g_no_error;
+
+ if (metadata == nullptr) {
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail,
+ "received nullptr for gainmap metadata descriptor");
+ } else if (metadata->max_content_boost < metadata->min_content_boost) {
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail,
+ "received bad value for content boost min %f > max %f", metadata->min_content_boost,
+ metadata->max_content_boost);
+ } else if (metadata->gamma <= 0.0f) {
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail, "received bad value for gamma %f, expects > 0.0f",
+ metadata->gamma);
+ } else if (metadata->offset_sdr < 0.0f) {
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail,
+ "received bad value for offset sdr %f, expects to be >= 0.0f", metadata->offset_sdr);
+ } else if (metadata->offset_hdr < 0.0f) {
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail,
+ "received bad value for offset hdr %f, expects to be >= 0.0f", metadata->offset_hdr);
+ } else if (metadata->hdr_capacity_max < metadata->hdr_capacity_min) {
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail,
+ "received bad value for hdr capacity min %f > max %f", metadata->hdr_capacity_min,
+ metadata->hdr_capacity_max);
+ } else if (metadata->hdr_capacity_min < 1.0f) {
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail,
+ "received bad value for hdr capacity min %f, expects to be >= 1.0f",
+ metadata->hdr_capacity_min);
+ }
+ if (status.error_code != UHDR_CODEC_OK) return status;
+
+ status = uhdr_enc_validate_and_set_compressed_img(enc, img, UHDR_GAIN_MAP_IMG);
+ if (status.error_code != UHDR_CODEC_OK) return status;
+
+ uhdr_encoder_private* handle = dynamic_cast<uhdr_encoder_private*>(enc);
+ memcpy(&handle->m_metadata, metadata, sizeof *metadata);
+
+ return status;
+}
+
+uhdr_error_info_t uhdr_enc_set_quality(uhdr_codec_private_t* enc, int quality,
+ uhdr_img_label_t intent) {
+ uhdr_error_info_t status = g_no_error;
+
+ if (dynamic_cast<uhdr_encoder_private*>(enc) == nullptr) {
+ 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 (quality < 0 || quality > 100) {
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail,
+ "invalid quality factor %d, expects in range [0-100]", quality);
+ } else if (intent != UHDR_HDR_IMG && intent != UHDR_SDR_IMG && intent != UHDR_BASE_IMG &&
+ intent != UHDR_GAIN_MAP_IMG) {
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail,
+ "invalid intent %d, expects one of {UHDR_HDR_IMG, UHDR_SDR_IMG, UHDR_BASE_IMG, "
+ "UHDR_GAIN_MAP_IMG}",
+ intent);
+ }
+ if (status.error_code != UHDR_CODEC_OK) return status;
+
+ uhdr_encoder_private* handle = dynamic_cast<uhdr_encoder_private*>(enc);
+ if (handle->m_sailed) {
+ status.error_code = UHDR_CODEC_INVALID_OPERATION;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail,
+ "An earlier call to uhdr_encode() has switched the context from configurable state to "
+ "end state. The context is no longer configurable. To reuse, call reset()");
+ return status;
+ }
+
+ handle->m_quality.insert_or_assign(intent, quality);
+
+ return status;
+}
+
+uhdr_error_info_t uhdr_enc_set_exif_data(uhdr_codec_private_t* enc, uhdr_mem_block_t* exif) {
+ uhdr_error_info_t status = g_no_error;
+
+ if (dynamic_cast<uhdr_encoder_private*>(enc) == nullptr) {
+ 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 (exif == nullptr) {
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail, "received nullptr for exif image handle");
+ } else if (exif->data == nullptr) {
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail, "received nullptr for exif->data field");
+ } else if (exif->capacity < exif->data_sz) {
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail, "exif->capacity %d is less than exif->data_sz %d",
+ exif->capacity, exif->data_sz);
+ }
+ if (status.error_code != UHDR_CODEC_OK) return status;
+
+ uhdr_encoder_private* handle = dynamic_cast<uhdr_encoder_private*>(enc);
+ if (handle->m_sailed) {
+ status.error_code = UHDR_CODEC_INVALID_OPERATION;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail,
+ "An earlier call to uhdr_encode() has switched the context from configurable state to "
+ "end state. The context is no longer configurable. To reuse, call reset()");
+ return status;
+ }
+
+ uint8_t* data = static_cast<uint8_t*>(exif->data);
+ std::vector<uint8_t> entry(data, data + exif->data_sz);
+ handle->m_exif = std::move(entry);
+
+ return status;
+}
+
+uhdr_error_info_t uhdr_enc_set_output_format(uhdr_codec_private_t* enc, uhdr_codec_t media_type) {
+ uhdr_error_info_t status = g_no_error;
+
+ if (dynamic_cast<uhdr_encoder_private*>(enc) == nullptr) {
+ 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) {
+ 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);
+ }
+ if (status.error_code != UHDR_CODEC_OK) return status;
+
+ uhdr_encoder_private* handle = dynamic_cast<uhdr_encoder_private*>(enc);
+ if (handle->m_sailed) {
+ status.error_code = UHDR_CODEC_INVALID_OPERATION;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail,
+ "An earlier call to uhdr_encode() has switched the context from configurable state to "
+ "end state. The context is no longer configurable. To reuse, call reset()");
+ return status;
+ }
+
+ handle->m_output_format = media_type;
+
+ return status;
+}
+
+uhdr_error_info_t uhdr_encode(uhdr_codec_private_t* enc) {
+ if (dynamic_cast<uhdr_encoder_private*>(enc) == nullptr) {
+ uhdr_error_info_t status;
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance");
+ return status;
+ }
+
+ uhdr_encoder_private* handle = dynamic_cast<uhdr_encoder_private*>(enc);
+
+ if (handle->m_sailed) {
+ return handle->m_encode_call_status;
+ }
+
+ handle->m_sailed = true;
+
+ uhdr_error_info_t& status = handle->m_encode_call_status;
+
+ if (handle->m_effects.size() != 0) {
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail, "image effects are not currently enabled");
+ return status;
+ }
+
+ ultrahdr::status_t internal_status = ultrahdr::JPEGR_NO_ERROR;
+ if (handle->m_output_format == UHDR_CODEC_JPG) {
+ ultrahdr::jpegr_exif_struct exif{};
+ if (handle->m_exif.size() > 0) {
+ exif.data = handle->m_exif.data();
+ exif.length = handle->m_exif.size();
+ }
+
+ ultrahdr::JpegR jpegr;
+ ultrahdr::jpegr_compressed_struct dest{};
+ 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()) {
+ auto& base_entry = handle->m_compressed_images.find(UHDR_BASE_IMG)->second;
+ ultrahdr::jpegr_compressed_struct primary_image;
+ primary_image.data = base_entry->data;
+ primary_image.length = primary_image.maxLength = base_entry->data_sz;
+ primary_image.colorGamut = map_cg_to_internal_cg(base_entry->cg);
+
+ auto& gainmap_entry = handle->m_compressed_images.find(UHDR_GAIN_MAP_IMG)->second;
+ ultrahdr::jpegr_compressed_struct gainmap_image;
+ gainmap_image.data = gainmap_entry->data;
+ gainmap_image.length = gainmap_image.maxLength = gainmap_entry->data_sz;
+ gainmap_image.colorGamut = map_cg_to_internal_cg(gainmap_entry->cg);
+
+ ultrahdr::ultrahdr_metadata_struct metadata;
+ metadata.version = ultrahdr::kJpegrVersion;
+ metadata.maxContentBoost = handle->m_metadata.max_content_boost;
+ metadata.minContentBoost = handle->m_metadata.min_content_boost;
+ metadata.gamma = handle->m_metadata.gamma;
+ metadata.offsetSdr = handle->m_metadata.offset_sdr;
+ metadata.offsetHdr = handle->m_metadata.offset_hdr;
+ metadata.hdrCapacityMin = handle->m_metadata.hdr_capacity_min;
+ metadata.hdrCapacityMax = handle->m_metadata.hdr_capacity_max;
+
+ size_t size = (std::max)((8 * 1024), 2 * (primary_image.length + gainmap_image.length));
+ handle->m_compressed_output_buffer =
+ std::move(std::make_unique<ultrahdr::uhdr_compressed_image_ext_t>(
+ UHDR_CG_UNSPECIFIED, UHDR_CT_UNSPECIFIED, UHDR_CR_UNSPECIFIED, size));
+
+ dest.data = handle->m_compressed_output_buffer->data;
+ dest.length = 0;
+ dest.maxLength = handle->m_compressed_output_buffer->capacity;
+ dest.colorGamut = ultrahdr::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
+
+ // api - 4
+ internal_status = jpegr.encodeJPEGR(&primary_image, &gainmap_image, &metadata, &dest);
+ map_internal_error_status_to_error_info(internal_status, status);
+ } 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)((8u * 1024), hdr_raw_entry->w * hdr_raw_entry->h * 3 * 2);
+ handle->m_compressed_output_buffer =
+ std::move(std::make_unique<ultrahdr::uhdr_compressed_image_ext_t>(
+ UHDR_CG_UNSPECIFIED, UHDR_CT_UNSPECIFIED, UHDR_CR_UNSPECIFIED, size));
+
+ dest.data = handle->m_compressed_output_buffer->data;
+ dest.length = 0;
+ dest.maxLength = handle->m_compressed_output_buffer->capacity;
+ dest.colorGamut = ultrahdr::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
+
+ ultrahdr::jpegr_uncompressed_struct p010_image;
+ p010_image.data = hdr_raw_entry->planes[UHDR_PLANE_Y];
+ p010_image.width = hdr_raw_entry->w;
+ p010_image.height = hdr_raw_entry->h;
+ p010_image.colorGamut = map_cg_to_internal_cg(hdr_raw_entry->cg);
+ p010_image.luma_stride = hdr_raw_entry->stride[UHDR_PLANE_Y];
+ p010_image.chroma_data = hdr_raw_entry->planes[UHDR_PLANE_UV];
+ p010_image.chroma_stride = hdr_raw_entry->stride[UHDR_PLANE_UV];
+ p010_image.pixelFormat = map_pix_fmt_to_internal_pix_fmt(hdr_raw_entry->fmt);
+
+ 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
+ internal_status = jpegr.encodeJPEGR(&p010_image, map_ct_to_internal_ct(hdr_raw_entry->ct),
+ &dest, 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()) {
+ auto& sdr_compressed_entry = handle->m_compressed_images.find(UHDR_SDR_IMG)->second;
+ ultrahdr::jpegr_compressed_struct sdr_compressed_image;
+ sdr_compressed_image.data = sdr_compressed_entry->data;
+ sdr_compressed_image.length = sdr_compressed_image.maxLength =
+ sdr_compressed_entry->data_sz;
+ sdr_compressed_image.colorGamut = map_cg_to_internal_cg(sdr_compressed_entry->cg);
+ // api - 3
+ internal_status = jpegr.encodeJPEGR(&p010_image, &sdr_compressed_image,
+ map_ct_to_internal_ct(hdr_raw_entry->ct), &dest);
+ } 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;
+
+ ultrahdr::jpegr_uncompressed_struct yuv420_image;
+ yuv420_image.data = sdr_raw_entry->planes[UHDR_PLANE_Y];
+ yuv420_image.width = sdr_raw_entry->w;
+ yuv420_image.height = sdr_raw_entry->h;
+ yuv420_image.colorGamut = map_cg_to_internal_cg(sdr_raw_entry->cg);
+ yuv420_image.luma_stride = sdr_raw_entry->stride[UHDR_PLANE_Y];
+ yuv420_image.chroma_data = nullptr;
+ yuv420_image.chroma_stride = 0;
+ yuv420_image.pixelFormat = map_pix_fmt_to_internal_pix_fmt(sdr_raw_entry->fmt);
+
+ if (handle->m_compressed_images.find(UHDR_SDR_IMG) == handle->m_compressed_images.end()) {
+ // api - 1
+ internal_status = jpegr.encodeJPEGR(&p010_image, &yuv420_image,
+ map_ct_to_internal_ct(hdr_raw_entry->ct), &dest,
+ handle->m_quality.find(UHDR_BASE_IMG)->second,
+ handle->m_exif.size() > 0 ? &exif : nullptr);
+ } else {
+ auto& sdr_compressed_entry = handle->m_compressed_images.find(UHDR_SDR_IMG)->second;
+ ultrahdr::jpegr_compressed_struct sdr_compressed_image;
+ sdr_compressed_image.data = sdr_compressed_entry->data;
+ sdr_compressed_image.length = sdr_compressed_image.maxLength =
+ sdr_compressed_entry->data_sz;
+ sdr_compressed_image.colorGamut = map_cg_to_internal_cg(sdr_compressed_entry->cg);
+
+ // api - 2
+ internal_status = jpegr.encodeJPEGR(&p010_image, &yuv420_image, &sdr_compressed_image,
+ map_ct_to_internal_ct(hdr_raw_entry->ct), &dest);
+ }
+ }
+ map_internal_error_status_to_error_info(internal_status, status);
+ } 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");
+ }
+ if (status.error_code == UHDR_CODEC_OK) {
+ handle->m_compressed_output_buffer->data_sz = dest.length;
+ handle->m_compressed_output_buffer->cg = map_internal_cg_to_cg(dest.colorGamut);
+ }
+ }
+
+ return status;
+}
+
+uhdr_compressed_image_t* uhdr_get_encoded_stream(uhdr_codec_private_t* enc) {
+ if (dynamic_cast<uhdr_encoder_private*>(enc) == nullptr) {
+ return nullptr;
+ }
+
+ uhdr_encoder_private* handle = dynamic_cast<uhdr_encoder_private*>(enc);
+ if (!handle->m_sailed || handle->m_encode_call_status.error_code != UHDR_CODEC_OK) {
+ return nullptr;
+ }
+
+ return handle->m_compressed_output_buffer.get();
+}
+
+void uhdr_reset_encoder(uhdr_codec_private_t* enc) {
+ if (dynamic_cast<uhdr_encoder_private*>(enc) != nullptr) {
+ uhdr_encoder_private* handle = dynamic_cast<uhdr_encoder_private*>(enc);
+
+ // clear entries and restore defaults
+ for (auto it : handle->m_effects) delete it;
+ handle->m_effects.clear();
+ handle->m_raw_images.clear();
+ handle->m_compressed_images.clear();
+ handle->m_quality.clear();
+ handle->m_quality.emplace(UHDR_HDR_IMG, 95);
+ handle->m_quality.emplace(UHDR_SDR_IMG, 95);
+ handle->m_quality.emplace(UHDR_BASE_IMG, 95);
+ handle->m_quality.emplace(UHDR_GAIN_MAP_IMG, 85);
+ handle->m_exif.clear();
+ handle->m_output_format = UHDR_CODEC_JPG;
+
+ handle->m_sailed = false;
+ handle->m_compressed_output_buffer.reset();
+ handle->m_encode_call_status = g_no_error;
+ }
+}
+
+int is_uhdr_image(void* data, int size) {
+#define RET_IF_ERR(x) \
+ { \
+ uhdr_error_info_t status = (x); \
+ if (status.error_code != UHDR_CODEC_OK) { \
+ uhdr_release_decoder(obj); \
+ return 0; \
+ } \
+ }
+
+ uhdr_codec_private_t* obj = uhdr_create_decoder();
+ uhdr_compressed_image_t uhdr_image;
+ uhdr_image.data = data;
+ uhdr_image.data_sz = size;
+ uhdr_image.capacity = size;
+ uhdr_image.cg = UHDR_CG_UNSPECIFIED;
+ uhdr_image.ct = UHDR_CT_UNSPECIFIED;
+ uhdr_image.range = UHDR_CR_UNSPECIFIED;
+
+ RET_IF_ERR(uhdr_dec_set_image(obj, &uhdr_image));
+ RET_IF_ERR(uhdr_dec_probe(obj));
+#undef RET_IF_ERR
+
+ uhdr_release_decoder(obj);
+
+ return 1;
+}
+
+uhdr_codec_private_t* uhdr_create_decoder(void) {
+ uhdr_decoder_private* handle = new uhdr_decoder_private();
+
+ if (handle != nullptr) {
+ uhdr_reset_decoder(handle);
+ }
+ return handle;
+}
+
+void uhdr_release_decoder(uhdr_codec_private_t* dec) {
+ if (dynamic_cast<uhdr_decoder_private*>(dec) != nullptr) {
+ uhdr_decoder_private* handle = dynamic_cast<uhdr_decoder_private*>(dec);
+ delete handle;
+ }
+}
+
+uhdr_error_info_t uhdr_dec_set_image(uhdr_codec_private_t* dec, uhdr_compressed_image_t* img) {
+ uhdr_error_info_t status = g_no_error;
+
+ if (dynamic_cast<uhdr_decoder_private*>(dec) == nullptr) {
+ 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 (img == nullptr) {
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail, "received nullptr for compressed image handle");
+ } else if (img->data == nullptr) {
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail,
+ "received nullptr for compressed img->data field");
+ } else if (img->capacity < img->data_sz) {
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail, "img->capacity %d is less than img->data_sz %d",
+ img->capacity, img->data_sz);
+ }
+ if (status.error_code != UHDR_CODEC_OK) return status;
+
+ uhdr_decoder_private* handle = dynamic_cast<uhdr_decoder_private*>(dec);
+ if (handle->m_probed) {
+ status.error_code = UHDR_CODEC_INVALID_OPERATION;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail,
+ "An earlier call to uhdr_decode() has switched the context from configurable state to "
+ "end state. The context is no longer configurable. To reuse, call reset()");
+ return status;
+ }
+
+ handle->m_uhdr_compressed_img = std::make_unique<ultrahdr::uhdr_compressed_image_ext_t>(
+ img->cg, img->ct, img->range, img->data_sz);
+ memcpy(handle->m_uhdr_compressed_img->data, img->data, img->data_sz);
+ handle->m_uhdr_compressed_img->data_sz = img->data_sz;
+
+ return status;
+}
+
+uhdr_error_info_t uhdr_dec_set_out_img_format(uhdr_codec_private_t* dec, uhdr_img_fmt_t fmt) {
+ uhdr_error_info_t status = g_no_error;
+
+ if (dynamic_cast<uhdr_decoder_private*>(dec) == nullptr) {
+ 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 (fmt != UHDR_IMG_FMT_32bppRGBA8888 && fmt != UHDR_IMG_FMT_64bppRGBAHalfFloat &&
+ fmt != UHDR_IMG_FMT_32bppRGBA1010102) {
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail,
+ "invalid output format %d, expects one of {UHDR_IMG_FMT_32bppRGBA8888, "
+ "UHDR_IMG_FMT_64bppRGBAHalfFloat, UHDR_IMG_FMT_32bppRGBA1010102}",
+ fmt);
+ }
+ if (status.error_code != UHDR_CODEC_OK) return status;
+
+ uhdr_decoder_private* handle = dynamic_cast<uhdr_decoder_private*>(dec);
+ if (handle->m_probed) {
+ status.error_code = UHDR_CODEC_INVALID_OPERATION;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail,
+ "An earlier call to uhdr_decode() has switched the context from configurable state to "
+ "end state. The context is no longer configurable. To reuse, call reset()");
+ return status;
+ }
+
+ handle->m_output_fmt = fmt;
+
+ return status;
+}
+
+uhdr_error_info_t uhdr_dec_set_out_color_transfer(uhdr_codec_private_t* dec,
+ uhdr_color_transfer_t ct) {
+ uhdr_error_info_t status = g_no_error;
+
+ if (dynamic_cast<uhdr_decoder_private*>(dec) == nullptr) {
+ 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 (ct != UHDR_CT_HLG && ct != UHDR_CT_PQ && ct != UHDR_CT_LINEAR && ct != UHDR_CT_SRGB) {
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail,
+ "invalid output color transfer %d, expects one of {UHDR_CT_HLG, UHDR_CT_PQ, "
+ "UHDR_CT_LINEAR, UHDR_CT_SRGB}",
+ ct);
+ }
+ if (status.error_code != UHDR_CODEC_OK) return status;
+
+ uhdr_decoder_private* handle = dynamic_cast<uhdr_decoder_private*>(dec);
+ if (handle->m_probed) {
+ status.error_code = UHDR_CODEC_INVALID_OPERATION;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail,
+ "An earlier call to uhdr_decode() has switched the context from configurable state to "
+ "end state. The context is no longer configurable. To reuse, call reset()");
+ return status;
+ }
+
+ handle->m_output_ct = ct;
+
+ return status;
+}
+
+uhdr_error_info_t uhdr_dec_set_out_max_display_boost(uhdr_codec_private_t* dec,
+ float display_boost) {
+ uhdr_error_info_t status = g_no_error;
+
+ if (dynamic_cast<uhdr_decoder_private*>(dec) == nullptr) {
+ 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 (display_boost < 1.0f) {
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail,
+ "invalid display boost %f, expects to be >= 1.0f}", display_boost);
+ }
+ if (status.error_code != UHDR_CODEC_OK) return status;
+
+ uhdr_decoder_private* handle = dynamic_cast<uhdr_decoder_private*>(dec);
+ if (handle->m_probed) {
+ status.error_code = UHDR_CODEC_INVALID_OPERATION;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail,
+ "An earlier call to uhdr_decode() has switched the context from configurable state to "
+ "end state. The context is no longer configurable. To reuse, call reset()");
+ return status;
+ }
+
+ handle->m_output_max_disp_boost = display_boost;
+
+ return status;
+}
+
+uhdr_error_info_t uhdr_dec_probe(uhdr_codec_private_t* dec) {
+ if (dynamic_cast<uhdr_decoder_private*>(dec) == nullptr) {
+ uhdr_error_info_t status;
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance");
+ return status;
+ }
+
+ uhdr_decoder_private* handle = dynamic_cast<uhdr_decoder_private*>(dec);
+ uhdr_error_info_t& status = handle->m_probe_call_status;
+
+ if (!handle->m_probed) {
+ handle->m_probed = true;
+
+ if (handle->m_uhdr_compressed_img.get() == nullptr) {
+ status.error_code = UHDR_CODEC_INVALID_OPERATION;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail, "did not receive any image for decoding");
+ return status;
+ }
+
+ ultrahdr::jpeg_info_struct primary_image;
+ ultrahdr::jpeg_info_struct gainmap_image;
+ ultrahdr::jpegr_info_struct jpegr_info;
+ jpegr_info.primaryImgInfo = &primary_image;
+ jpegr_info.gainmapImgInfo = &gainmap_image;
+
+ ultrahdr::jpegr_compressed_struct uhdr_image;
+ uhdr_image.data = handle->m_uhdr_compressed_img->data;
+ uhdr_image.length = uhdr_image.maxLength = handle->m_uhdr_compressed_img->data_sz;
+ uhdr_image.colorGamut = map_cg_to_internal_cg(handle->m_uhdr_compressed_img->cg);
+
+ ultrahdr::JpegR jpegr;
+ ultrahdr::status_t internal_status = jpegr.getJPEGRInfo(&uhdr_image, &jpegr_info);
+ map_internal_error_status_to_error_info(internal_status, status);
+ if (status.error_code != UHDR_CODEC_OK) return status;
+
+ ultrahdr::ultrahdr_metadata_struct metadata;
+ if (ultrahdr::getMetadataFromXMP(gainmap_image.xmpData.data(), gainmap_image.xmpData.size(),
+ &metadata)) {
+ handle->m_metadata.max_content_boost = metadata.maxContentBoost;
+ handle->m_metadata.min_content_boost = metadata.minContentBoost;
+ handle->m_metadata.gamma = metadata.gamma;
+ handle->m_metadata.offset_sdr = metadata.offsetSdr;
+ handle->m_metadata.offset_hdr = metadata.offsetHdr;
+ handle->m_metadata.hdr_capacity_min = metadata.hdrCapacityMin;
+ handle->m_metadata.hdr_capacity_max = metadata.hdrCapacityMax;
+ } else {
+ status.error_code = UHDR_CODEC_UNKNOWN_ERROR;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail, "encountered error while parsing metadata");
+ return status;
+ }
+
+ handle->m_img_wd = primary_image.width;
+ handle->m_img_ht = primary_image.height;
+ handle->m_gainmap_wd = gainmap_image.width;
+ handle->m_gainmap_ht = gainmap_image.height;
+ handle->m_exif = std::move(primary_image.exifData);
+ handle->m_exif_block.data = handle->m_exif.data();
+ handle->m_exif_block.data_sz = handle->m_exif_block.capacity = handle->m_exif.size();
+ handle->m_icc = std::move(primary_image.iccData);
+ handle->m_icc_block.data = handle->m_icc.data();
+ handle->m_icc_block.data_sz = handle->m_icc_block.capacity = handle->m_icc.size();
+ handle->m_base_xmp = std::move(primary_image.xmpData);
+ handle->m_gainmap_xmp = std::move(gainmap_image.xmpData);
+ }
+
+ return status;
+}
+
+int uhdr_dec_get_image_width(uhdr_codec_private_t* dec) {
+ if (dynamic_cast<uhdr_decoder_private*>(dec) == nullptr) {
+ return -1;
+ }
+
+ uhdr_decoder_private* handle = dynamic_cast<uhdr_decoder_private*>(dec);
+ if (!handle->m_probed || handle->m_probe_call_status.error_code != UHDR_CODEC_OK) {
+ return -1;
+ }
+
+ return handle->m_img_wd;
+}
+
+int uhdr_dec_get_image_height(uhdr_codec_private_t* dec) {
+ if (dynamic_cast<uhdr_decoder_private*>(dec) == nullptr) {
+ return -1;
+ }
+
+ uhdr_decoder_private* handle = dynamic_cast<uhdr_decoder_private*>(dec);
+ if (!handle->m_probed || handle->m_probe_call_status.error_code != UHDR_CODEC_OK) {
+ return -1;
+ }
+
+ return handle->m_img_ht;
+}
+
+int uhdr_dec_get_gainmap_width(uhdr_codec_private_t* dec) {
+ if (dynamic_cast<uhdr_decoder_private*>(dec) == nullptr) {
+ return -1;
+ }
+
+ uhdr_decoder_private* handle = dynamic_cast<uhdr_decoder_private*>(dec);
+ if (!handle->m_probed || handle->m_probe_call_status.error_code != UHDR_CODEC_OK) {
+ return -1;
+ }
+
+ return handle->m_gainmap_wd;
+}
+
+int uhdr_dec_get_gainmap_height(uhdr_codec_private_t* dec) {
+ if (dynamic_cast<uhdr_decoder_private*>(dec) == nullptr) {
+ return -1;
+ }
+
+ uhdr_decoder_private* handle = dynamic_cast<uhdr_decoder_private*>(dec);
+ if (!handle->m_probed || handle->m_probe_call_status.error_code != UHDR_CODEC_OK) {
+ return -1;
+ }
+
+ return handle->m_gainmap_ht;
+}
+
+uhdr_mem_block_t* uhdr_dec_get_exif(uhdr_codec_private_t* dec) {
+ if (dynamic_cast<uhdr_decoder_private*>(dec) == nullptr) {
+ return nullptr;
+ }
+
+ uhdr_decoder_private* handle = dynamic_cast<uhdr_decoder_private*>(dec);
+ if (!handle->m_probed || handle->m_probe_call_status.error_code != UHDR_CODEC_OK) {
+ return nullptr;
+ }
+
+ return &handle->m_exif_block;
+}
+
+uhdr_mem_block_t* uhdr_dec_get_icc(uhdr_codec_private_t* dec) {
+ if (dynamic_cast<uhdr_decoder_private*>(dec) == nullptr) {
+ return nullptr;
+ }
+
+ uhdr_decoder_private* handle = dynamic_cast<uhdr_decoder_private*>(dec);
+ if (!handle->m_probed || handle->m_probe_call_status.error_code != UHDR_CODEC_OK) {
+ return nullptr;
+ }
+
+ return &handle->m_icc_block;
+}
+
+uhdr_gainmap_metadata_t* uhdr_dec_get_gain_map_metadata(uhdr_codec_private_t* dec) {
+ if (dynamic_cast<uhdr_decoder_private*>(dec) == nullptr) {
+ return nullptr;
+ }
+
+ uhdr_decoder_private* handle = dynamic_cast<uhdr_decoder_private*>(dec);
+ if (!handle->m_probed || handle->m_probe_call_status.error_code != UHDR_CODEC_OK) {
+ return nullptr;
+ }
+
+ return &handle->m_metadata;
+}
+
+uhdr_error_info_t uhdr_decode(uhdr_codec_private_t* dec) {
+ if (dynamic_cast<uhdr_decoder_private*>(dec) == nullptr) {
+ uhdr_error_info_t status;
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance");
+ return status;
+ }
+
+ uhdr_decoder_private* handle = dynamic_cast<uhdr_decoder_private*>(dec);
+
+ if (handle->m_sailed) {
+ return handle->m_decode_call_status;
+ }
+
+ uhdr_error_info_t& status = handle->m_decode_call_status;
+ status = uhdr_dec_probe(dec);
+ if (status.error_code != UHDR_CODEC_OK) return status;
+
+ handle->m_sailed = true;
+
+ ultrahdr::jpegr_compressed_struct uhdr_image;
+ uhdr_image.data = handle->m_uhdr_compressed_img->data;
+ uhdr_image.length = uhdr_image.maxLength = handle->m_uhdr_compressed_img->data_sz;
+ uhdr_image.colorGamut = map_cg_to_internal_cg(handle->m_uhdr_compressed_img->cg);
+
+ handle->m_decoded_img_buffer = std::make_unique<ultrahdr::uhdr_raw_image_ext_t>(
+ handle->m_output_fmt, UHDR_CG_UNSPECIFIED, handle->m_output_ct, UHDR_CR_UNSPECIFIED,
+ handle->m_img_wd, handle->m_img_ht, 1);
+ // alias
+ ultrahdr::jpegr_uncompressed_struct dest;
+ dest.data = handle->m_decoded_img_buffer->planes[UHDR_PLANE_PACKED];
+ dest.colorGamut = ultrahdr::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
+
+ handle->m_gainmap_img_buffer = std::make_unique<ultrahdr::uhdr_raw_image_ext_t>(
+ UHDR_IMG_FMT_8bppYCbCr400, UHDR_CG_UNSPECIFIED, UHDR_CT_UNSPECIFIED, UHDR_CR_UNSPECIFIED,
+ handle->m_gainmap_wd, handle->m_gainmap_ht, 1);
+ // alias
+ ultrahdr::jpegr_uncompressed_struct dest_gainmap;
+ dest_gainmap.data = handle->m_gainmap_img_buffer->planes[UHDR_PLANE_Y];
+
+ ultrahdr::JpegR jpegr;
+ ultrahdr::status_t internal_status = jpegr.decodeJPEGR(
+ &uhdr_image, &dest, handle->m_output_max_disp_boost, nullptr,
+ map_ct_fmt_to_internal_output_fmt(handle->m_output_ct, handle->m_output_fmt), &dest_gainmap,
+ nullptr);
+ map_internal_error_status_to_error_info(internal_status, status);
+ if (status.error_code == UHDR_CODEC_OK) {
+ handle->m_decoded_img_buffer->cg = map_internal_cg_to_cg(dest.colorGamut);
+ }
+
+ if (status.error_code == UHDR_CODEC_OK && dec->m_effects.size() != 0) {
+ status = ultrahdr::apply_effects(handle);
+ }
+
+ return status;
+}
+
+uhdr_raw_image_t* uhdr_get_decoded_image(uhdr_codec_private_t* dec) {
+ if (dynamic_cast<uhdr_decoder_private*>(dec) == nullptr) {
+ return nullptr;
+ }
+
+ uhdr_decoder_private* handle = dynamic_cast<uhdr_decoder_private*>(dec);
+ if (!handle->m_sailed || handle->m_decode_call_status.error_code != UHDR_CODEC_OK) {
+ return nullptr;
+ }
+
+ return handle->m_decoded_img_buffer.get();
+}
+
+uhdr_raw_image_t* uhdr_get_gain_map_image(uhdr_codec_private_t* dec) {
+ if (dynamic_cast<uhdr_decoder_private*>(dec) == nullptr) {
+ return nullptr;
+ }
+
+ uhdr_decoder_private* handle = dynamic_cast<uhdr_decoder_private*>(dec);
+ if (!handle->m_sailed || handle->m_decode_call_status.error_code != UHDR_CODEC_OK) {
+ return nullptr;
+ }
+
+ return handle->m_gainmap_img_buffer.get();
+}
+
+void uhdr_reset_decoder(uhdr_codec_private_t* dec) {
+ if (dynamic_cast<uhdr_decoder_private*>(dec) != nullptr) {
+ uhdr_decoder_private* handle = dynamic_cast<uhdr_decoder_private*>(dec);
+
+ // clear entries and restore defaults
+ for (auto it : handle->m_effects) delete it;
+ handle->m_effects.clear();
+ handle->m_uhdr_compressed_img.reset();
+ handle->m_output_fmt = UHDR_IMG_FMT_64bppRGBAHalfFloat;
+ handle->m_output_ct = UHDR_CT_LINEAR;
+ handle->m_output_max_disp_boost = FLT_MAX;
+
+ // ready to be configured
+ handle->m_probed = false;
+ handle->m_sailed = false;
+ handle->m_decoded_img_buffer.reset();
+ handle->m_gainmap_img_buffer.reset();
+ handle->m_img_wd = 0;
+ handle->m_img_ht = 0;
+ handle->m_gainmap_wd = 0;
+ handle->m_gainmap_ht = 0;
+ handle->m_exif.clear();
+ memset(&handle->m_exif_block, 0, sizeof handle->m_exif_block);
+ handle->m_icc.clear();
+ memset(&handle->m_icc_block, 0, sizeof handle->m_icc_block);
+ handle->m_base_xmp.clear();
+ handle->m_gainmap_xmp.clear();
+ memset(&handle->m_metadata, 0, sizeof handle->m_metadata);
+ handle->m_probe_call_status = g_no_error;
+ handle->m_decode_call_status = g_no_error;
+ }
+}
+
+uhdr_error_info_t uhdr_add_effect_mirror(uhdr_codec_private_t* codec,
+ uhdr_mirror_direction_t direction) {
+ uhdr_error_info_t status = g_no_error;
+
+ if (codec == nullptr) {
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance");
+ return status;
+ }
+
+ if (direction != UHDR_MIRROR_HORIZONTAL && direction != UHDR_MIRROR_VERTICAL) {
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(
+ status.detail, sizeof status.detail,
+ "unsupported direction, expects one of {UHDR_MIRROR_HORIZONTAL, UHDR_MIRROR_VERTICAL}");
+ return status;
+ }
+
+ codec->m_effects.push_back(new ultrahdr::uhdr_mirror_effect_t(direction));
+
+ return status;
+}
+
+uhdr_error_info_t uhdr_add_effect_rotate(uhdr_codec_private_t* codec, int degrees) {
+ uhdr_error_info_t status = g_no_error;
+
+ if (codec == nullptr) {
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance");
+ return status;
+ }
+
+ if (degrees != 90 && degrees != 180 && degrees != 270) {
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail,
+ "unsupported degrees, expects one of {90, 180, 270}");
+ return status;
+ }
+
+ codec->m_effects.push_back(new ultrahdr::uhdr_rotate_effect_t(degrees));
+
+ return status;
+}
+
+uhdr_error_info_t uhdr_add_effect_crop(uhdr_codec_private_t* codec, int left, int right, int top,
+ int bottom) {
+ uhdr_error_info_t status = g_no_error;
+
+ if (codec == nullptr) {
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance");
+ return status;
+ }
+
+ codec->m_effects.push_back(new ultrahdr::uhdr_crop_effect_t(left, right, top, bottom));
+
+ return status;
+}
+
+uhdr_error_info_t uhdr_add_effect_resize(uhdr_codec_private_t* codec, int width, int height) {
+ uhdr_error_info_t status = g_no_error;
+
+ if (codec == nullptr) {
+ status.error_code = UHDR_CODEC_INVALID_PARAM;
+ status.has_detail = 1;
+ snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance");
+ return status;
+ }
+
+ codec->m_effects.push_back(new ultrahdr::uhdr_resize_effect_t(width, height));
+
+ return status;
+}
diff --git a/libuhdr.pc.template b/libuhdr.pc.template
new file mode 100644
index 0000000..d50ec00
--- /dev/null
+++ b/libuhdr.pc.template
@@ -0,0 +1,11 @@
+prefix=@CMAKE_INSTALL_PREFIX@
+libdir=${prefix}/@CMAKE_INSTALL_LIBDIR@
+includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@
+
+Name: @PROJECT_NAME@
+Description: @CMAKE_PROJECT_DESCRIPTION@
+Version: @PROJECT_VERSION@
+Requires.private: libjpeg
+Cflags: -I${includedir}
+Libs: -L${libdir} -l@UHDR_TARGET_NAME@
+Libs.private: @CMAKE_THREAD_LIBS_INIT@
diff --git a/tests/editorhelper_test.cpp b/tests/editorhelper_test.cpp
new file mode 100644
index 0000000..d08ea7c
--- /dev/null
+++ b/tests/editorhelper_test.cpp
@@ -0,0 +1,364 @@
+/*
+ * 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.
+ */
+
+#include <gtest/gtest.h>
+
+#include <fstream>
+#include <iostream>
+
+#include "ultrahdr/editorhelper.h"
+
+// #define DUMP_OUTPUT
+
+#define OUTPUT_P010_IMAGE "output.p010"
+#define OUTPUT_YUV_IMAGE "output.yuv"
+#define OUTPUT_RGBA_IMAGE "output.rgb"
+
+#ifdef DUMP_OUTPUT
+static bool writeFile(std::string prefixName, uhdr_raw_image_t* img) {
+ char filename[50];
+
+ if (img->fmt == UHDR_IMG_FMT_24bppYCbCrP010) {
+ snprintf(filename, sizeof filename, "%s_%d_%s", prefixName.c_str(), img->fmt,
+ OUTPUT_P010_IMAGE);
+ } else if (img->fmt == UHDR_IMG_FMT_12bppYCbCr420 || img->fmt == UHDR_IMG_FMT_8bppYCbCr400) {
+ snprintf(filename, sizeof filename, "%s_%d_%s", prefixName.c_str(), img->fmt, OUTPUT_YUV_IMAGE);
+ } else if (img->fmt == UHDR_IMG_FMT_32bppRGBA1010102 || img->fmt == UHDR_IMG_FMT_32bppRGBA8888 ||
+ img->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat) {
+ snprintf(filename, sizeof filename, "%s_%d_%s", prefixName.c_str(), img->fmt,
+ OUTPUT_RGBA_IMAGE);
+ } else {
+ return false;
+ }
+
+ std::ofstream ofd(filename, std::ios::binary);
+ if (ofd.is_open()) {
+ int bpp = 1;
+
+ if (img->fmt == UHDR_IMG_FMT_24bppYCbCrP010) {
+ bpp = 2;
+ } else if (img->fmt == UHDR_IMG_FMT_32bppRGBA8888 ||
+ img->fmt == UHDR_IMG_FMT_32bppRGBA1010102) {
+ bpp = 4;
+ } else if (img->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat) {
+ bpp = 8;
+ }
+
+ const char* data = static_cast<char*>(img->planes[UHDR_PLANE_Y]);
+ size_t stride = img->stride[UHDR_PLANE_Y] * bpp;
+ size_t length = img->w * bpp;
+ for (int i = 0; i < img->h; i++, data += stride) {
+ ofd.write(data, length);
+ }
+
+ if (img->fmt == UHDR_IMG_FMT_24bppYCbCrP010) {
+ data = static_cast<char*>(img->planes[UHDR_PLANE_UV]);
+ size_t stride = img->stride[UHDR_PLANE_UV] * bpp;
+ size_t length = img->w * bpp;
+ for (int i = 0; i < img->h / 2; i++, data += stride) {
+ ofd.write(data, length);
+ }
+ } else if (img->fmt == UHDR_IMG_FMT_12bppYCbCr420) {
+ data = static_cast<char*>(img->planes[UHDR_PLANE_U]);
+ size_t stride = img->stride[UHDR_PLANE_U] * bpp;
+ size_t length = (img->w / 2) * bpp;
+ for (int i = 0; i < img->h / 2; i++, data += stride) {
+ ofd.write(data, length);
+ }
+ data = static_cast<char*>(img->planes[UHDR_PLANE_V]);
+ size_t stride = img->stride[UHDR_PLANE_V] * bpp;
+ size_t length = (img->w / 2) * bpp;
+ for (int i = 0; i < img->h / 2; i++, data += stride) {
+ ofd.write(data, length);
+ }
+ }
+ return true;
+ }
+ std::cerr << "unable to write to file : " << filename << std::endl;
+ return false;
+}
+#endif
+
+namespace ultrahdr {
+
+static bool loadFile(const char* filename, uhdr_raw_image_t* handle) {
+ std::ifstream ifd(filename, std::ios::binary);
+ if (ifd.good()) {
+ if (handle->fmt == UHDR_IMG_FMT_24bppYCbCrP010) {
+ const int bpp = 2;
+ ifd.read(static_cast<char*>(handle->planes[UHDR_PLANE_Y]), handle->w * handle->h * bpp);
+ ifd.read(static_cast<char*>(handle->planes[UHDR_PLANE_UV]),
+ (handle->w / 2) * (handle->h / 2) * bpp * 2);
+ return true;
+ } else if (handle->fmt == UHDR_IMG_FMT_12bppYCbCr420) {
+ ifd.read(static_cast<char*>(handle->planes[UHDR_PLANE_Y]), handle->w * handle->h);
+ ifd.read(static_cast<char*>(handle->planes[UHDR_PLANE_U]), (handle->w / 2) * (handle->h / 2));
+ ifd.read(static_cast<char*>(handle->planes[UHDR_PLANE_V]), (handle->w / 2) * (handle->h / 2));
+ return true;
+ } else if (handle->fmt == UHDR_IMG_FMT_32bppRGBA8888 ||
+ handle->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat ||
+ handle->fmt == UHDR_IMG_FMT_32bppRGBA1010102) {
+ int bpp = handle->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat ? 8 : 4;
+ ifd.read(static_cast<char*>(handle->planes[UHDR_PLANE_PACKED]), handle->w * handle->h * bpp);
+ return true;
+ } else if (handle->fmt == UHDR_IMG_FMT_8bppYCbCr400) {
+ ifd.read(static_cast<char*>(handle->planes[UHDR_PLANE_Y]), handle->w * handle->h);
+ return true;
+ }
+ return false;
+ }
+ std::cerr << "unable to open file : " << filename << std::endl;
+ return false;
+}
+
+void initImageHandle(uhdr_raw_image_t* handle, int width, int height, uhdr_img_fmt_t format) {
+ handle->fmt = format;
+ handle->cg = UHDR_CG_DISPLAY_P3;
+ handle->ct = UHDR_CT_SRGB;
+ handle->range = UHDR_CR_UNSPECIFIED;
+ handle->w = width;
+ handle->h = height;
+ if (handle->fmt == UHDR_IMG_FMT_24bppYCbCrP010) {
+ handle->planes[UHDR_PLANE_Y] = malloc(width * height * 2);
+ handle->planes[UHDR_PLANE_UV] = malloc((width / 2) * (height / 2) * 2 * 2);
+ handle->planes[UHDR_PLANE_V] = nullptr;
+ handle->stride[UHDR_PLANE_Y] = width;
+ handle->stride[UHDR_PLANE_UV] = width;
+ handle->stride[UHDR_PLANE_V] = 0;
+ } else if (handle->fmt == UHDR_IMG_FMT_12bppYCbCr420) {
+ handle->planes[UHDR_PLANE_Y] = malloc(width * height);
+ handle->planes[UHDR_PLANE_U] = malloc((width / 2) * (height / 2));
+ handle->planes[UHDR_PLANE_V] = malloc((width / 2) * (height / 2));
+ handle->stride[UHDR_PLANE_Y] = width;
+ handle->stride[UHDR_PLANE_U] = width / 2;
+ handle->stride[UHDR_PLANE_V] = width / 2;
+ } else if (handle->fmt == UHDR_IMG_FMT_32bppRGBA8888 ||
+ handle->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat ||
+ handle->fmt == UHDR_IMG_FMT_32bppRGBA1010102) {
+ int bpp = handle->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat ? 8 : 4;
+ handle->planes[UHDR_PLANE_PACKED] = malloc(width * height * bpp);
+ handle->planes[UHDR_PLANE_U] = nullptr;
+ handle->planes[UHDR_PLANE_V] = nullptr;
+ handle->stride[UHDR_PLANE_PACKED] = width;
+ handle->stride[UHDR_PLANE_U] = 0;
+ handle->stride[UHDR_PLANE_V] = 0;
+ } else if (handle->fmt == UHDR_IMG_FMT_8bppYCbCr400) {
+ handle->planes[UHDR_PLANE_Y] = malloc(width * height);
+ handle->planes[UHDR_PLANE_U] = nullptr;
+ handle->planes[UHDR_PLANE_V] = nullptr;
+ handle->stride[UHDR_PLANE_Y] = width;
+ handle->stride[UHDR_PLANE_U] = 0;
+ handle->stride[UHDR_PLANE_V] = 0;
+ }
+}
+
+void compare_planes(void* ref_plane, void* test_plane, int ref_stride, int test_stride, int width,
+ int height, int bpp) {
+ uint8_t* ref = (uint8_t*)ref_plane;
+ uint8_t* test = (uint8_t*)test_plane;
+ const size_t length = width * bpp;
+
+ for (int i = 0; i < height; i++, ref += (ref_stride * bpp), test += (test_stride * bpp)) {
+ ASSERT_EQ(0, memcmp(ref, ref, length));
+ }
+}
+
+void compareImg(uhdr_raw_image_t* ref, uhdr_raw_image_t* test) {
+ ASSERT_EQ(ref->fmt, test->fmt);
+ ASSERT_EQ(ref->cg, test->cg);
+ ASSERT_EQ(ref->ct, test->ct);
+ ASSERT_EQ(ref->range, test->range);
+ ASSERT_EQ(ref->w, test->w);
+ ASSERT_EQ(ref->h, test->h);
+ int bpp = 1;
+ if (ref->fmt == UHDR_IMG_FMT_24bppYCbCrP010) {
+ bpp = 2;
+ compare_planes(ref->planes[UHDR_PLANE_Y], test->planes[UHDR_PLANE_Y], ref->stride[UHDR_PLANE_Y],
+ test->stride[UHDR_PLANE_Y], ref->w, ref->h, bpp);
+ compare_planes(ref->planes[UHDR_PLANE_UV], test->planes[UHDR_PLANE_UV],
+ ref->stride[UHDR_PLANE_UV], test->stride[UHDR_PLANE_UV], ref->w, ref->h / 2,
+ bpp);
+ } else if (ref->fmt == UHDR_IMG_FMT_12bppYCbCr420) {
+ compare_planes(ref->planes[UHDR_PLANE_Y], test->planes[UHDR_PLANE_Y], ref->stride[UHDR_PLANE_Y],
+ test->stride[UHDR_PLANE_Y], ref->w, ref->h, bpp);
+ compare_planes(ref->planes[UHDR_PLANE_U], test->planes[UHDR_PLANE_U], ref->stride[UHDR_PLANE_U],
+ test->stride[UHDR_PLANE_U], ref->w / 2, ref->h / 2, bpp);
+ compare_planes(ref->planes[UHDR_PLANE_V], test->planes[UHDR_PLANE_V], ref->stride[UHDR_PLANE_V],
+ test->stride[UHDR_PLANE_V], ref->w / 2, ref->h / 2, bpp);
+ } else if (ref->fmt == UHDR_IMG_FMT_32bppRGBA8888 || ref->fmt == UHDR_IMG_FMT_32bppRGBA1010102 ||
+ ref->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat) {
+ bpp = ref->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat ? 8 : 4;
+ compare_planes(ref->planes[UHDR_PLANE_PACKED], test->planes[UHDR_PLANE_PACKED],
+ ref->stride[UHDR_PLANE_PACKED], test->stride[UHDR_PLANE_PACKED], ref->w, ref->h,
+ bpp);
+ } else if (ref->fmt == UHDR_IMG_FMT_8bppYCbCr400) {
+ compare_planes(ref->planes[UHDR_PLANE_Y], test->planes[UHDR_PLANE_Y], ref->stride[UHDR_PLANE_Y],
+ test->stride[UHDR_PLANE_Y], ref->w, ref->h, bpp);
+ }
+}
+
+class EditorHelperTest
+ : public ::testing::TestWithParam<std::tuple<std::string, int, int, uhdr_img_fmt_t>> {
+ public:
+ EditorHelperTest()
+ : filename(std::get<0>(GetParam())),
+ width(std::get<1>(GetParam())),
+ height(std::get<2>(GetParam())),
+ fmt(std::get<3>(GetParam())){};
+
+ ~EditorHelperTest() {
+ int count = sizeof img_a.planes / sizeof img_a.planes[0];
+ for (int i = 0; i < count; i++) {
+ if (img_a.planes[i]) {
+ free(img_a.planes[i]);
+ }
+ }
+ }
+
+ std::string filename;
+ int width;
+ int height;
+ uhdr_img_fmt_t fmt;
+ uhdr_raw_image_t img_a;
+};
+
+TEST_P(EditorHelperTest, Rotate) {
+ initImageHandle(&img_a, width, height, fmt);
+ ASSERT_TRUE(loadFile(filename.c_str(), &img_a)) << "unable to load file " << filename;
+ auto dst = apply_rotate(&img_a, 90);
+ dst = apply_rotate(dst.get(), 90);
+ dst = apply_rotate(dst.get(), 180);
+ dst = apply_rotate(dst.get(), 270);
+ dst = apply_rotate(dst.get(), 90);
+ dst = apply_rotate(dst.get(), 90);
+ dst = apply_rotate(dst.get(), 270);
+ ASSERT_NO_FATAL_FAILURE(compareImg(&img_a, dst.get()));
+}
+
+TEST_P(EditorHelperTest, Mirror) {
+ initImageHandle(&img_a, width, height, fmt);
+ ASSERT_TRUE(loadFile(filename.c_str(), &img_a)) << "unable to load file " << filename;
+ auto dst = apply_mirror(&img_a, UHDR_MIRROR_VERTICAL);
+ dst = apply_mirror(dst.get(), UHDR_MIRROR_VERTICAL);
+ dst = apply_mirror(dst.get(), UHDR_MIRROR_HORIZONTAL);
+ dst = apply_mirror(dst.get(), UHDR_MIRROR_HORIZONTAL);
+ ASSERT_NO_FATAL_FAILURE(compareImg(&img_a, dst.get()));
+}
+
+TEST_P(EditorHelperTest, MultipleEffects) {
+ initImageHandle(&img_a, width, height, fmt);
+ ASSERT_TRUE(loadFile(filename.c_str(), &img_a)) << "unable to load file " << filename;
+ auto dst = apply_mirror(&img_a, UHDR_MIRROR_VERTICAL);
+ dst = apply_rotate(dst.get(), 180);
+ dst = apply_mirror(dst.get(), UHDR_MIRROR_HORIZONTAL);
+ ASSERT_NO_FATAL_FAILURE(compareImg(&img_a, dst.get()));
+
+ dst = apply_mirror(dst.get(), UHDR_MIRROR_HORIZONTAL);
+ dst = apply_rotate(dst.get(), 90);
+ dst = apply_rotate(dst.get(), 90);
+ dst = apply_mirror(dst.get(), UHDR_MIRROR_VERTICAL);
+ ASSERT_NO_FATAL_FAILURE(compareImg(&img_a, dst.get()));
+
+ dst = apply_rotate(dst.get(), 270);
+ dst = apply_mirror(dst.get(), UHDR_MIRROR_VERTICAL);
+ dst = apply_rotate(dst.get(), 90);
+ dst = apply_mirror(dst.get(), UHDR_MIRROR_HORIZONTAL);
+ ASSERT_NO_FATAL_FAILURE(compareImg(&img_a, dst.get()));
+
+ dst = apply_resize(dst.get(), width / 2, height / 2);
+ ASSERT_EQ(img_a.fmt, dst->fmt);
+ ASSERT_EQ(img_a.cg, dst->cg);
+ ASSERT_EQ(img_a.ct, dst->ct);
+ ASSERT_EQ(img_a.range, dst->range);
+ ASSERT_EQ(dst->w, width / 2);
+ ASSERT_EQ(dst->h, height / 2);
+
+ uhdr_raw_image_ext_t* img_copy = dst.get();
+ apply_crop(img_copy, 8, 8, width / 4, height / 4);
+ ASSERT_EQ(dst->fmt, img_copy->fmt);
+ ASSERT_EQ(dst->cg, img_copy->cg);
+ ASSERT_EQ(dst->ct, img_copy->ct);
+ ASSERT_EQ(dst->range, img_copy->range);
+ ASSERT_EQ(width / 4, img_copy->w);
+ ASSERT_EQ(height / 4, img_copy->h);
+}
+
+TEST_P(EditorHelperTest, Crop) {
+ initImageHandle(&img_a, width, height, fmt);
+ ASSERT_TRUE(loadFile(filename.c_str(), &img_a)) << "unable to load file " << filename;
+ uhdr_raw_image_t img_copy = img_a;
+ apply_crop(&img_copy, 8, 8, width / 2, height / 2);
+
+ ASSERT_EQ(img_a.fmt, img_copy.fmt);
+ ASSERT_EQ(img_a.cg, img_copy.cg);
+ ASSERT_EQ(img_a.ct, img_copy.ct);
+ ASSERT_EQ(img_a.range, img_copy.range);
+ ASSERT_EQ(img_copy.w, width / 2);
+ ASSERT_EQ(img_copy.h, height / 2);
+#ifdef DUMP_OUTPUT
+ if (!writeFile("cropped", &img_copy)) {
+ std::cerr << "unable to write output file" << std::endl;
+ }
+#endif
+}
+
+TEST_P(EditorHelperTest, Resize) {
+ initImageHandle(&img_a, width, height, fmt);
+ ASSERT_TRUE(loadFile(filename.c_str(), &img_a)) << "unable to load file " << filename;
+ auto dst = apply_resize(&img_a, width / 2, height / 2);
+
+ ASSERT_EQ(img_a.fmt, dst->fmt);
+ ASSERT_EQ(img_a.cg, dst->cg);
+ ASSERT_EQ(img_a.ct, dst->ct);
+ ASSERT_EQ(img_a.range, dst->range);
+ ASSERT_EQ(dst->w, width / 2);
+ ASSERT_EQ(dst->h, height / 2);
+#ifdef DUMP_OUTPUT
+ if (!writeFile("resize", dst.get())) {
+ std::cerr << "unable to write output file" << std::endl;
+ }
+#endif
+}
+
+#ifdef __ANDROID__
+INSTANTIATE_TEST_SUITE_P(
+ EditorAPIParameterizedTests, EditorHelperTest,
+ ::testing::Values(std::make_tuple("/data/local/tmp/raw_p010_image.p010", 1280, 720,
+ UHDR_IMG_FMT_24bppYCbCrP010),
+ std::make_tuple("/data/local/tmp/raw_yuv420_image.yuv420", 1280, 720,
+ UHDR_IMG_FMT_12bppYCbCr420),
+ std::make_tuple("/data/local/tmp/raw_yuv420_image.yuv420", 1280, 720,
+ UHDR_IMG_FMT_8bppYCbCr400),
+ std::make_tuple("/data/local/tmp/raw_p010_image.p010", 352, 288,
+ UHDR_IMG_FMT_32bppRGBA1010102),
+ std::make_tuple("/data/local/tmp/raw_p010_image.p010", 352, 288,
+ UHDR_IMG_FMT_64bppRGBAHalfFloat),
+ std::make_tuple("/data/local/tmp/raw_p010_image.p010", 352, 288,
+ UHDR_IMG_FMT_32bppRGBA8888)));
+
+#else
+INSTANTIATE_TEST_SUITE_P(
+ EditorAPIParameterizedTests, EditorHelperTest,
+ ::testing::Values(
+ std::make_tuple("./data/raw_p010_image.p010", 1280, 720, UHDR_IMG_FMT_24bppYCbCrP010),
+ std::make_tuple("./data/raw_yuv420_image.yuv420", 1280, 720, UHDR_IMG_FMT_12bppYCbCr420),
+ std::make_tuple("./data/raw_yuv420_image.yuv420", 1280, 720, UHDR_IMG_FMT_8bppYCbCr400),
+ std::make_tuple("./data/raw_p010_image.p010", 352, 288, UHDR_IMG_FMT_32bppRGBA1010102),
+ std::make_tuple("./data/raw_p010_image.p010", 352, 288, UHDR_IMG_FMT_64bppRGBAHalfFloat),
+ std::make_tuple("./data/raw_p010_image.p010", 352, 288, UHDR_IMG_FMT_32bppRGBA8888)));
+#endif
+
+} // namespace ultrahdr
diff --git a/tests/gainmapmath_test.cpp b/tests/gainmapmath_test.cpp
index 95fd159..8324a3a 100644
--- a/tests/gainmapmath_test.cpp
+++ b/tests/gainmapmath_test.cpp
@@ -31,14 +31,14 @@ class GainMapMathTest : public testing::Test {
float YuvConversionEpsilon() { return 1.0f / (255.0f * 2.0f); }
Color Yuv420(uint8_t y, uint8_t u, uint8_t v) {
- return {{{static_cast<float>(y) / 255.0f, (static_cast<float>(u) - 128.0f) / 255.0f,
- (static_cast<float>(v) - 128.0f) / 255.0f}}};
+ return {{{static_cast<float>(y) * (1 / 255.0f), static_cast<float>(u - 128) * (1 / 255.0f),
+ static_cast<float>(v - 128) * (1 / 255.0f)}}};
}
Color P010(uint16_t y, uint16_t u, uint16_t v) {
- return {
- {{(static_cast<float>(y) - 64.0f) / 876.0f, (static_cast<float>(u) - 64.0f) / 896.0f - 0.5f,
- (static_cast<float>(v) - 64.0f) / 896.0f - 0.5f}}};
+ return {{{static_cast<float>(y - 64) * (1 / 876.0f),
+ static_cast<float>(u - 64) * (1 / 896.0f) - 0.5f,
+ static_cast<float>(v - 64) * (1 / 896.0f) - 0.5f}}};
}
float Map(uint8_t e) { return static_cast<float>(e) / 255.0f; }
diff --git a/tests/jpegr_test.cpp b/tests/jpegr_test.cpp
index 0b420e6..1d03ac1 100644
--- a/tests/jpegr_test.cpp
+++ b/tests/jpegr_test.cpp
@@ -24,6 +24,8 @@
#include <fstream>
#include <iostream>
+#include "ultrahdr_api.h"
+
#include "ultrahdr/ultrahdrcommon.h"
#include "ultrahdr/jpegr.h"
#include "ultrahdr/jpegrutils.h"
@@ -301,6 +303,34 @@ static bool readFile(const char* fileName, void*& result, int maxLength, int& le
return false;
}
+uhdr_color_gamut_t map_internal_cg_to_cg(ultrahdr::ultrahdr_color_gamut cg) {
+ switch (cg) {
+ case ultrahdr::ULTRAHDR_COLORGAMUT_BT2100:
+ return UHDR_CG_BT_2100;
+ case ultrahdr::ULTRAHDR_COLORGAMUT_BT709:
+ return UHDR_CG_BT_709;
+ case ultrahdr::ULTRAHDR_COLORGAMUT_P3:
+ return UHDR_CG_DISPLAY_P3;
+ default:
+ return UHDR_CG_UNSPECIFIED;
+ }
+}
+
+uhdr_color_transfer_t map_internal_ct_to_ct(ultrahdr::ultrahdr_transfer_function ct) {
+ switch (ct) {
+ case ultrahdr::ULTRAHDR_TF_HLG:
+ return UHDR_CT_HLG;
+ case ultrahdr::ULTRAHDR_TF_PQ:
+ return UHDR_CT_PQ;
+ case ultrahdr::ULTRAHDR_TF_LINEAR:
+ return UHDR_CT_LINEAR;
+ case ultrahdr::ULTRAHDR_TF_SRGB:
+ return UHDR_CT_SRGB;
+ default:
+ return UHDR_CT_UNSPECIFIED;
+ }
+}
+
void decodeJpegRImg(jr_compressed_ptr img, [[maybe_unused]] const char* outFileName) {
jpegr_info_struct info{};
JpegR jpegHdr;
@@ -319,6 +349,33 @@ void decodeJpegRImg(jr_compressed_ptr img, [[maybe_unused]] const char* outFileN
std::cerr << "unable to write output file" << std::endl;
}
#endif
+ uhdr_codec_private_t* obj = uhdr_create_decoder();
+ uhdr_compressed_image_t uhdr_image{};
+ uhdr_image.data = img->data;
+ uhdr_image.data_sz = img->length;
+ uhdr_image.capacity = img->length;
+ uhdr_image.cg = UHDR_CG_UNSPECIFIED;
+ uhdr_image.ct = UHDR_CT_UNSPECIFIED;
+ uhdr_image.range = UHDR_CR_UNSPECIFIED;
+ uhdr_error_info_t status = uhdr_dec_set_image(obj, &uhdr_image);
+ ASSERT_EQ(UHDR_CODEC_OK, status.error_code) << status.detail;
+ status = uhdr_decode(obj);
+ ASSERT_EQ(UHDR_CODEC_OK, status.error_code) << status.detail;
+ uhdr_raw_image_t* raw_image = uhdr_get_decoded_image(obj);
+ ASSERT_NE(nullptr, raw_image);
+ ASSERT_EQ(map_internal_cg_to_cg(destImage.colorGamut), raw_image->cg);
+ ASSERT_EQ(destImage.width, raw_image->w);
+ ASSERT_EQ(destImage.height, raw_image->h);
+ char* testData = static_cast<char*>(raw_image->planes[UHDR_PLANE_PACKED]);
+ char* refData = static_cast<char*>(destImage.data);
+ int bpp = (raw_image->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat) ? 8 : 4;
+ const size_t testStride = raw_image->stride[UHDR_PLANE_PACKED] * bpp;
+ const size_t refStride = destImage.width * bpp;
+ const size_t length = destImage.width * bpp;
+ for (unsigned i = 0; i < destImage.height; i++, testData += testStride, refData += refStride) {
+ ASSERT_EQ(0, memcmp(testData, refData, length));
+ }
+ uhdr_release_decoder(obj);
}
// ============================================================================
@@ -1398,6 +1455,33 @@ TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI0AndDecodeTest) {
uHdrLib.encodeJPEGR(rawImg.getImageHandle(), ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
jpgImg.getImageHandle(), kQuality, nullptr),
JPEGR_NO_ERROR);
+
+ uhdr_codec_private_t* obj = uhdr_create_encoder();
+ uhdr_raw_image_t uhdrRawImg{};
+ uhdrRawImg.fmt = UHDR_IMG_FMT_24bppYCbCrP010;
+ uhdrRawImg.cg = map_internal_cg_to_cg(mP010ColorGamut);
+ uhdrRawImg.ct = map_internal_ct_to_ct(ultrahdr_transfer_function::ULTRAHDR_TF_HLG);
+ uhdrRawImg.range = UHDR_CR_UNSPECIFIED;
+ uhdrRawImg.w = kImageWidth;
+ uhdrRawImg.h = kImageHeight;
+ uhdrRawImg.planes[UHDR_PLANE_Y] = rawImg.getImageHandle()->data;
+ uhdrRawImg.stride[UHDR_PLANE_Y] = kImageWidth;
+ uhdrRawImg.planes[UHDR_PLANE_UV] =
+ ((uint8_t*)(rawImg.getImageHandle()->data)) + kImageWidth * kImageHeight * 2;
+ uhdrRawImg.stride[UHDR_PLANE_UV] = kImageWidth;
+ uhdr_error_info_t status = uhdr_enc_set_raw_image(obj, &uhdrRawImg, UHDR_HDR_IMG);
+ ASSERT_EQ(UHDR_CODEC_OK, status.error_code) << status.detail;
+ status = uhdr_enc_set_quality(obj, kQuality, UHDR_BASE_IMG);
+ ASSERT_EQ(UHDR_CODEC_OK, status.error_code) << status.detail;
+ status = uhdr_encode(obj);
+ ASSERT_EQ(UHDR_CODEC_OK, status.error_code) << status.detail;
+ uhdr_compressed_image_t* compressedImage = uhdr_get_encoded_stream(obj);
+ ASSERT_NE(nullptr, compressedImage);
+ ASSERT_EQ(jpgImg.getImageHandle()->length, compressedImage->data_sz);
+ ASSERT_EQ(0,
+ memcmp(jpgImg.getImageHandle()->data, compressedImage->data, compressedImage->data_sz));
+ uhdr_release_encoder(obj);
+
// encode with luma stride set
{
UhdrUnCompressedStructWrapper rawImg2(kImageWidth, kImageHeight, YCbCr_p010);
@@ -1434,6 +1518,30 @@ TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI0AndDecodeTest) {
auto jpg2 = jpgImg2.getImageHandle();
ASSERT_EQ(jpg1->length, jpg2->length);
ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+
+ uhdr_codec_private_t* obj = uhdr_create_encoder();
+ uhdr_raw_image_t uhdrRawImg{};
+ uhdrRawImg.fmt = UHDR_IMG_FMT_24bppYCbCrP010;
+ uhdrRawImg.cg = map_internal_cg_to_cg(mP010ColorGamut);
+ uhdrRawImg.ct = map_internal_ct_to_ct(ultrahdr_transfer_function::ULTRAHDR_TF_HLG);
+ uhdrRawImg.range = UHDR_CR_UNSPECIFIED;
+ uhdrRawImg.w = kImageWidth;
+ uhdrRawImg.h = kImageHeight;
+ uhdrRawImg.planes[UHDR_PLANE_Y] = rawImg2.getImageHandle()->data;
+ uhdrRawImg.stride[UHDR_PLANE_Y] = rawImg2.getImageHandle()->luma_stride;
+ uhdrRawImg.planes[UHDR_PLANE_UV] = rawImg2.getImageHandle()->chroma_data;
+ uhdrRawImg.stride[UHDR_PLANE_UV] = rawImg2.getImageHandle()->chroma_stride;
+ uhdr_error_info_t status = uhdr_enc_set_raw_image(obj, &uhdrRawImg, UHDR_HDR_IMG);
+ ASSERT_EQ(UHDR_CODEC_OK, status.error_code) << status.detail;
+ status = uhdr_enc_set_quality(obj, kQuality, UHDR_BASE_IMG);
+ ASSERT_EQ(UHDR_CODEC_OK, status.error_code) << status.detail;
+ status = uhdr_encode(obj);
+ ASSERT_EQ(UHDR_CODEC_OK, status.error_code) << status.detail;
+ uhdr_compressed_image_t* compressedImage = uhdr_get_encoded_stream(obj);
+ ASSERT_NE(nullptr, compressedImage);
+ ASSERT_EQ(jpg1->length, compressedImage->data_sz);
+ ASSERT_EQ(0, memcmp(jpg1->data, compressedImage->data, jpg1->length));
+ uhdr_release_encoder(obj);
}
// encode with chroma stride set
{
@@ -1610,6 +1718,49 @@ TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI1AndDecodeTest) {
auto jpg2 = jpgImg2.getImageHandle();
ASSERT_EQ(jpg1->length, jpg2->length);
ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+
+ uhdr_codec_private_t* obj = uhdr_create_encoder();
+ uhdr_raw_image_t uhdrRawImg{};
+ uhdrRawImg.fmt = UHDR_IMG_FMT_24bppYCbCrP010;
+ uhdrRawImg.cg = map_internal_cg_to_cg(mP010ColorGamut);
+ uhdrRawImg.ct = map_internal_ct_to_ct(ultrahdr_transfer_function::ULTRAHDR_TF_HLG);
+ uhdrRawImg.range = UHDR_CR_UNSPECIFIED;
+ uhdrRawImg.w = kImageWidth;
+ uhdrRawImg.h = kImageHeight;
+ uhdrRawImg.planes[UHDR_PLANE_Y] = rawImgP010.getImageHandle()->data;
+ uhdrRawImg.stride[UHDR_PLANE_Y] = kImageWidth;
+ uhdrRawImg.planes[UHDR_PLANE_UV] =
+ ((uint8_t*)(rawImgP010.getImageHandle()->data)) + kImageWidth * kImageHeight * 2;
+ uhdrRawImg.stride[UHDR_PLANE_UV] = kImageWidth;
+ uhdr_error_info_t status = uhdr_enc_set_raw_image(obj, &uhdrRawImg, UHDR_HDR_IMG);
+ ASSERT_EQ(UHDR_CODEC_OK, status.error_code) << status.detail;
+
+ uhdrRawImg.fmt = UHDR_IMG_FMT_12bppYCbCr420;
+ uhdrRawImg.cg = map_internal_cg_to_cg(mYuv420ColorGamut);
+ uhdrRawImg.ct = map_internal_ct_to_ct(ultrahdr_transfer_function::ULTRAHDR_TF_SRGB);
+ uhdrRawImg.range = UHDR_CR_UNSPECIFIED;
+ uhdrRawImg.w = kImageWidth;
+ uhdrRawImg.h = kImageHeight;
+ uhdrRawImg.planes[UHDR_PLANE_Y] = rawImg2420.getImageHandle()->data;
+ uhdrRawImg.stride[UHDR_PLANE_Y] = rawImg2420.getImageHandle()->luma_stride;
+ uhdrRawImg.planes[UHDR_PLANE_U] = rawImg2420.getImageHandle()->chroma_data;
+ uhdrRawImg.stride[UHDR_PLANE_U] = rawImg2420.getImageHandle()->chroma_stride;
+ uhdrRawImg.planes[UHDR_PLANE_V] = ((uint8_t*)(rawImg2420.getImageHandle()->chroma_data)) +
+ rawImg2420.getImageHandle()->chroma_stride * kImageHeight / 2;
+ uhdrRawImg.stride[UHDR_PLANE_V] = rawImg2420.getImageHandle()->chroma_stride;
+ status = uhdr_enc_set_raw_image(obj, &uhdrRawImg, UHDR_SDR_IMG);
+ ASSERT_EQ(UHDR_CODEC_OK, status.error_code) << status.detail;
+
+ status = uhdr_enc_set_quality(obj, kQuality, UHDR_BASE_IMG);
+ ASSERT_EQ(UHDR_CODEC_OK, status.error_code) << status.detail;
+ status = uhdr_encode(obj);
+ ASSERT_EQ(UHDR_CODEC_OK, status.error_code) << status.detail;
+ uhdr_compressed_image_t* compressedImage = uhdr_get_encoded_stream(obj);
+ ASSERT_NE(nullptr, compressedImage);
+ ASSERT_EQ(jpgImg.getImageHandle()->length, compressedImage->data_sz);
+ ASSERT_EQ(
+ 0, memcmp(jpgImg.getImageHandle()->data, compressedImage->data, compressedImage->data_sz));
+ uhdr_release_encoder(obj);
}
// encode with chroma stride set 420
{
@@ -1773,6 +1924,59 @@ TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI2AndDecodeTest) {
auto jpg2 = jpgImg2.getImageHandle();
ASSERT_EQ(jpg1->length, jpg2->length);
ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+
+ uhdr_codec_private_t* obj = uhdr_create_encoder();
+ uhdr_raw_image_t uhdrRawImg{};
+ uhdrRawImg.fmt = UHDR_IMG_FMT_24bppYCbCrP010;
+ uhdrRawImg.cg = map_internal_cg_to_cg(mP010ColorGamut);
+ uhdrRawImg.ct = map_internal_ct_to_ct(ultrahdr_transfer_function::ULTRAHDR_TF_HLG);
+ uhdrRawImg.range = UHDR_CR_UNSPECIFIED;
+ uhdrRawImg.w = kImageWidth;
+ uhdrRawImg.h = kImageHeight;
+ uhdrRawImg.planes[UHDR_PLANE_Y] = rawImgP010.getImageHandle()->data;
+ uhdrRawImg.stride[UHDR_PLANE_Y] = kImageWidth;
+ uhdrRawImg.planes[UHDR_PLANE_UV] =
+ ((uint8_t*)(rawImgP010.getImageHandle()->data)) + kImageWidth * kImageHeight * 2;
+ uhdrRawImg.stride[UHDR_PLANE_UV] = kImageWidth;
+ uhdr_error_info_t status = uhdr_enc_set_raw_image(obj, &uhdrRawImg, UHDR_HDR_IMG);
+ ASSERT_EQ(UHDR_CODEC_OK, status.error_code) << status.detail;
+
+ uhdrRawImg.fmt = UHDR_IMG_FMT_12bppYCbCr420;
+ uhdrRawImg.cg = map_internal_cg_to_cg(mYuv420ColorGamut);
+ uhdrRawImg.ct = map_internal_ct_to_ct(ultrahdr_transfer_function::ULTRAHDR_TF_SRGB);
+ uhdrRawImg.range = UHDR_CR_UNSPECIFIED;
+ uhdrRawImg.w = kImageWidth;
+ uhdrRawImg.h = kImageHeight;
+ uhdrRawImg.planes[UHDR_PLANE_Y] = rawImg2420.getImageHandle()->data;
+ uhdrRawImg.stride[UHDR_PLANE_Y] = rawImg2420.getImageHandle()->luma_stride;
+ uhdrRawImg.planes[UHDR_PLANE_U] = rawImg2420.getImageHandle()->chroma_data;
+ uhdrRawImg.stride[UHDR_PLANE_U] = rawImg2420.getImageHandle()->chroma_stride;
+ uhdrRawImg.planes[UHDR_PLANE_V] = ((uint8_t*)(rawImg2420.getImageHandle()->chroma_data)) +
+ rawImg2420.getImageHandle()->chroma_stride * kImageHeight / 2;
+ uhdrRawImg.stride[UHDR_PLANE_V] = rawImg2420.getImageHandle()->chroma_stride;
+ status = uhdr_enc_set_raw_image(obj, &uhdrRawImg, UHDR_SDR_IMG);
+ ASSERT_EQ(UHDR_CODEC_OK, status.error_code) << status.detail;
+
+ uhdr_compressed_image_t uhdrCompressedImg;
+ uhdrCompressedImg.data = sdr->data;
+ uhdrCompressedImg.data_sz = sdr->length;
+ uhdrCompressedImg.capacity = sdr->length;
+ uhdrCompressedImg.cg = map_internal_cg_to_cg(sdr->colorGamut);
+ uhdrCompressedImg.ct = UHDR_CT_UNSPECIFIED;
+ uhdrCompressedImg.range = UHDR_CR_UNSPECIFIED;
+ status = uhdr_enc_set_compressed_image(obj, &uhdrCompressedImg, UHDR_SDR_IMG);
+ ASSERT_EQ(UHDR_CODEC_OK, status.error_code) << status.detail;
+
+ status = uhdr_enc_set_quality(obj, kQuality, UHDR_BASE_IMG);
+ ASSERT_EQ(UHDR_CODEC_OK, status.error_code) << status.detail;
+ status = uhdr_encode(obj);
+ ASSERT_EQ(UHDR_CODEC_OK, status.error_code) << status.detail;
+ uhdr_compressed_image_t* compressedImage = uhdr_get_encoded_stream(obj);
+ ASSERT_NE(nullptr, compressedImage);
+ ASSERT_EQ(jpgImg.getImageHandle()->length, compressedImage->data_sz);
+ ASSERT_EQ(
+ 0, memcmp(jpgImg.getImageHandle()->data, compressedImage->data, compressedImage->data_sz));
+ uhdr_release_encoder(obj);
}
// encode with chroma stride set
{
@@ -1897,6 +2101,45 @@ TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI3AndDecodeTest) {
ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
}
+ {
+ uhdr_codec_private_t* obj = uhdr_create_encoder();
+ uhdr_raw_image_t uhdrRawImg{};
+ uhdrRawImg.fmt = UHDR_IMG_FMT_24bppYCbCrP010;
+ uhdrRawImg.cg = map_internal_cg_to_cg(mP010ColorGamut);
+ uhdrRawImg.ct = map_internal_ct_to_ct(ultrahdr_transfer_function::ULTRAHDR_TF_HLG);
+ uhdrRawImg.range = UHDR_CR_UNSPECIFIED;
+ uhdrRawImg.w = kImageWidth;
+ uhdrRawImg.h = kImageHeight;
+ uhdrRawImg.planes[UHDR_PLANE_Y] = rawImgP010.getImageHandle()->data;
+ uhdrRawImg.stride[UHDR_PLANE_Y] = kImageWidth;
+ uhdrRawImg.planes[UHDR_PLANE_UV] =
+ ((uint8_t*)(rawImgP010.getImageHandle()->data)) + kImageWidth * kImageHeight * 2;
+ uhdrRawImg.stride[UHDR_PLANE_UV] = kImageWidth;
+ uhdr_error_info_t status = uhdr_enc_set_raw_image(obj, &uhdrRawImg, UHDR_HDR_IMG);
+ ASSERT_EQ(UHDR_CODEC_OK, status.error_code) << status.detail;
+
+ uhdr_compressed_image_t uhdrCompressedImg;
+ uhdrCompressedImg.data = sdr->data;
+ uhdrCompressedImg.data_sz = sdr->length;
+ uhdrCompressedImg.capacity = sdr->length;
+ uhdrCompressedImg.cg = map_internal_cg_to_cg(sdr->colorGamut);
+ uhdrCompressedImg.ct = UHDR_CT_UNSPECIFIED;
+ uhdrCompressedImg.range = UHDR_CR_UNSPECIFIED;
+ status = uhdr_enc_set_compressed_image(obj, &uhdrCompressedImg, UHDR_SDR_IMG);
+ ASSERT_EQ(UHDR_CODEC_OK, status.error_code) << status.detail;
+
+ status = uhdr_enc_set_quality(obj, kQuality, UHDR_BASE_IMG);
+ ASSERT_EQ(UHDR_CODEC_OK, status.error_code) << status.detail;
+ status = uhdr_encode(obj);
+ ASSERT_EQ(UHDR_CODEC_OK, status.error_code) << status.detail;
+ uhdr_compressed_image_t* compressedImage = uhdr_get_encoded_stream(obj);
+ ASSERT_NE(nullptr, compressedImage);
+ ASSERT_EQ(jpgImg.getImageHandle()->length, compressedImage->data_sz);
+ ASSERT_EQ(
+ 0, memcmp(jpgImg.getImageHandle()->data, compressedImage->data, compressedImage->data_sz));
+ uhdr_release_encoder(obj);
+ }
+
auto jpg1 = jpgImg.getImageHandle();
#ifdef DUMP_OUTPUT
diff --git a/ultrahdr_api.h b/ultrahdr_api.h
new file mode 100644
index 0000000..eeff1c4
--- /dev/null
+++ b/ultrahdr_api.h
@@ -0,0 +1,583 @@
+/*
+ * 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.
+ */
+
+/** \file ultrahdr_api.h
+ *
+ * \brief
+ * Describes the encoder or decoder algorithm interface to applications.
+ */
+
+#ifndef ULTRAHDR_API_H
+#define ULTRAHDR_API_H
+
+#ifdef __cplusplus
+#define UHDR_EXTERN extern "C"
+#else
+#define UHDR_EXTERN extern
+#endif
+
+// ===============================================================================================
+// Enum Definitions
+// ===============================================================================================
+
+/*!\brief List of supported image formats */
+typedef enum uhdr_img_fmt {
+ UHDR_IMG_FMT_UNSPECIFIED = -1, /**< Unspecified */
+ UHDR_IMG_FMT_24bppYCbCrP010, /**< 10-bit-per component 4:2:0 YCbCr semiplanar format.
+ Each chroma and luma component has 16 allocated bits in
+ little-endian configuration with 10 MSB of actual data.*/
+ UHDR_IMG_FMT_12bppYCbCr420, /**< 8-bit-per component 4:2:0 YCbCr planar format */
+ UHDR_IMG_FMT_8bppYCbCr400, /**< 8-bit-per component Monochrome format */
+ UHDR_IMG_FMT_32bppRGBA8888, /**< 32 bits per pixel RGBA color format, with 8-bit red, green, blue
+ and alpha components. Using 32-bit little-endian representation,
+ colors stored as Red 7:0, Green 15:8, Blue 23:16, Alpha 31:24. */
+ UHDR_IMG_FMT_64bppRGBAHalfFloat, /**< 64 bits per pixel RGBA color format, with 16-bit signed
+ floating point red, green, blue, and alpha components */
+ UHDR_IMG_FMT_32bppRGBA1010102, /**< 32 bits per pixel RGBA color format, with 10-bit red, green,
+ blue, and 2-bit alpha components. Using 32-bit little-endian
+ representation, colors stored as Red 9:0, Green 19:10, Blue
+ 29:20, and Alpha 31:30. */
+} uhdr_img_fmt_t; /**< alias for enum uhdr_img_fmt */
+
+/*!\brief List of supported color gamuts */
+typedef enum uhdr_color_gamut {
+ UHDR_CG_UNSPECIFIED = -1, /**< Unspecified */
+ UHDR_CG_BT_709, /**< BT.709 */
+ UHDR_CG_DISPLAY_P3, /**< Display P3 */
+ UHDR_CG_BT_2100, /**< BT.2100 */
+} uhdr_color_gamut_t; /**< alias for enum uhdr_color_gamut */
+
+/*!\brief List of supported color transfers */
+typedef enum uhdr_color_transfer {
+ UHDR_CT_UNSPECIFIED = -1, /**< Unspecified */
+ UHDR_CT_LINEAR, /**< Linear */
+ UHDR_CT_HLG, /**< Hybrid log gamma */
+ UHDR_CT_PQ, /**< Perceptual Quantizer */
+ UHDR_CT_SRGB, /**< Gamma */
+} uhdr_color_transfer_t; /**< alias for enum uhdr_color_transfer */
+
+/*!\brief List of supported color ranges */
+typedef enum uhdr_color_range {
+ UHDR_CR_UNSPECIFIED = -1, /**< Unspecified */
+ UHDR_CR_LIMITED_RANGE, /**< Y {[16..235], UV [16..240]} * pow(2, (bpc - 8)) */
+ UHDR_CR_FULL_RANGE, /**< YUV/RGB {[0..255]} * pow(2, (bpc - 8)) */
+} uhdr_color_range_t; /**< alias for enum uhdr_color_range */
+
+/*!\brief List of supported codecs */
+typedef enum uhdr_codec {
+ UHDR_CODEC_JPG, /**< Compress {Hdr, Sdr rendition} to an {Sdr rendition + Gain Map} using
+ jpeg */
+} uhdr_codec_t; /**< alias for enum uhdr_codec */
+
+/*!\brief Image identifiers in gain map technology */
+typedef enum uhdr_img_label {
+ UHDR_HDR_IMG, /**< Hdr rendition image */
+ UHDR_SDR_IMG, /**< Sdr rendition image */
+ UHDR_BASE_IMG, /**< Base rendition image */
+ UHDR_GAIN_MAP_IMG, /**< Gain map image */
+} uhdr_img_label_t; /**< alias for enum uhdr_img_label */
+
+/*!\brief Algorithm return codes */
+typedef enum uhdr_codec_err {
+
+ /*!\brief Operation completed without error */
+ UHDR_CODEC_OK,
+
+ /*!\brief Unspecified error */
+ UHDR_CODEC_UNKNOWN_ERROR,
+
+ /*!\brief An application-supplied parameter is not valid. */
+ UHDR_CODEC_INVALID_PARAM,
+
+ /*!\brief Memory operation failed */
+ UHDR_CODEC_MEM_ERROR,
+
+ /*!\brief An application-invoked operation is not valid. */
+ UHDR_CODEC_INVALID_OPERATION,
+
+ /*!\brief The library does not implement a feature required for the operation */
+ UHDR_CODEC_UNSUPPORTED_FEATURE,
+
+ /*!\brief An iterator reached the end of list. */
+ UHDR_CODEC_LIST_END,
+
+} uhdr_codec_err_t; /**< alias for enum uhdr_codec_err */
+
+// ===============================================================================================
+// Structure Definitions
+// ===============================================================================================
+
+/*!\brief Detailed return status */
+typedef struct uhdr_error_info {
+ uhdr_codec_err_t error_code;
+ int has_detail;
+ char detail[256];
+} uhdr_error_info_t; /**< alias for struct uhdr_error_info */
+
+/**\brief Raw Image Descriptor */
+typedef struct uhdr_raw_image {
+ /* Color model, primaries, transfer, range */
+ uhdr_img_fmt_t fmt; /**< Image Format */
+ uhdr_color_gamut_t cg; /**< Color Gamut */
+ uhdr_color_transfer_t ct; /**< Color Transfer */
+ uhdr_color_range_t range; /**< Color Range */
+
+ /* Image storage dimensions */
+ unsigned int w; /**< Stored image width */
+ unsigned int h; /**< Stored image height */
+
+ /* Image data pointers. */
+#define UHDR_PLANE_PACKED 0 /**< To be used for all packed formats */
+#define UHDR_PLANE_Y 0 /**< Y (Luminance) plane */
+#define UHDR_PLANE_U 1 /**< U (Chroma) plane */
+#define UHDR_PLANE_UV 1 /**< UV (Chroma plane interleaved) To be used for semi planar format */
+#define UHDR_PLANE_V 2 /**< V (Chroma) plane */
+ void* planes[3]; /**< pointer to the top left pixel for each plane */
+ unsigned int stride[3]; /**< stride in pixels between rows for each plane */
+} uhdr_raw_image_t; /**< alias for struct uhdr_raw_image */
+
+/**\brief Compressed Image Descriptor */
+typedef struct uhdr_compressed_image {
+ void* data; /**< Pointer to a block of data to decode */
+ unsigned int data_sz; /**< size of the data buffer */
+ unsigned int capacity; /**< maximum size of the data buffer */
+ uhdr_color_gamut_t cg; /**< Color Gamut */
+ uhdr_color_transfer_t ct; /**< Color Transfer */
+ uhdr_color_range_t range; /**< Color Range */
+} uhdr_compressed_image_t; /**< alias for struct uhdr_compressed_image */
+
+/**\brief Buffer Descriptor */
+typedef struct uhdr_mem_block {
+ void* data; /**< Pointer to a block of data to decode */
+ unsigned int data_sz; /**< size of the data buffer */
+ unsigned int capacity; /**< maximum size of the data buffer */
+} uhdr_mem_block_t; /**< alias for struct uhdr_mem_block */
+
+/**\brief Gain map metadata.
+ * Note: all values stored in linear space. This differs from the metadata encoded in XMP, where
+ * max_content_boost (aka gainMapMax), min_content_boost (aka gainMapMin), hdr_capacity_min, and
+ * hdr_capacity_max are stored in log2 space.
+ */
+typedef struct uhdr_gainmap_metadata {
+ float max_content_boost; /**< Max Content Boost for the map */
+ float min_content_boost; /**< Min Content Boost for the map */
+ float gamma; /**< Gamma of the map data */
+ float offset_sdr; /**< Offset for SDR data in map calculations */
+ float offset_hdr; /**< Offset for HDR data in map calculations */
+ float hdr_capacity_min; /**< Min HDR capacity values for interpolating the Gain Map */
+ float hdr_capacity_max; /**< Max HDR capacity value for interpolating the Gain Map */
+} uhdr_gainmap_metadata_t; /**< alias for struct uhdr_gainmap_metadata */
+
+/**\brief ultrahdr codec context opaque descriptor */
+typedef struct uhdr_codec_private uhdr_codec_private_t;
+
+// ===============================================================================================
+// Function Declarations
+// ===============================================================================================
+
+// ===============================================================================================
+// Encoder APIs
+// ===============================================================================================
+
+/*!\brief Create a new encoder instance. The instance is initialized with default settings.
+ * To override the settings use uhdr_enc_set_*()
+ *
+ * \return nullptr if there was an error allocating memory else a fresh opaque encoder handle
+ */
+UHDR_EXTERN uhdr_codec_private_t* uhdr_create_encoder(void);
+
+/*!\brief Release encoder instance.
+ * Frees all allocated storage associated with encoder instance.
+ *
+ * \param[in] enc encoder instance.
+ *
+ * \return none
+ */
+UHDR_EXTERN void uhdr_release_encoder(uhdr_codec_private_t* enc);
+
+/*!\brief Add raw image descriptor to encoder context. The function goes through all the fields of
+ * the image descriptor and checks for their sanity. If no anomalies are seen then the image is
+ * added to internal list. Repeated calls to this function will replace the old entry with the
+ * current.
+ *
+ * \param[in] enc encoder instance.
+ * \param[in] img image descriptor.
+ * \param[in] intent UHDR_HDR_IMG for hdr intent and UHDR_SDR_IMG for sdr intent.
+ *
+ * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds,
+ * #UHDR_CODEC_INVALID_PARAM otherwise.
+ */
+UHDR_EXTERN uhdr_error_info_t uhdr_enc_set_raw_image(uhdr_codec_private_t* enc,
+ uhdr_raw_image_t* img,
+ uhdr_img_label_t intent);
+
+/*!\brief Add compressed image descriptor to encoder context. The function goes through all the
+ * fields of the image descriptor and checks for their sanity. If no anomalies are seen then the
+ * image is added to internal list. Repeated calls to this function will replace the old entry with
+ * the current.
+ *
+ * If both uhdr_enc_add_raw_image() and uhdr_enc_add_compressed_image() are called during a session
+ * for the same intent, it is assumed that raw image descriptor and compressed image descriptor are
+ * relatable via compress <-> decompress process.
+ *
+ * \param[in] enc encoder instance.
+ * \param[in] img image descriptor.
+ * \param[in] intent UHDR_HDR_IMG for hdr intent,
+ * UHDR_SDR_IMG for sdr intent,
+ * UHDR_BASE_IMG for base image intent
+ *
+ * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds,
+ * #UHDR_CODEC_INVALID_PARAM otherwise.
+ */
+UHDR_EXTERN uhdr_error_info_t uhdr_enc_set_compressed_image(uhdr_codec_private_t* enc,
+ uhdr_compressed_image_t* img,
+ uhdr_img_label_t intent);
+
+/*!\brief Add gain map image descriptor and gainmap metadata info to encoder context. The function
+ * internally goes through all the fields of the image descriptor and checks for their sanity. If no
+ * anomalies are seen then the image is added to internal list. Repeated calls to this function will
+ * replace the old entry with the current.
+ *
+ * \param[in] enc encoder instance.
+ * \param[in] img gain map image desciptor.
+ * \param[in] metadata gainmap metadata descriptor
+ *
+ * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds,
+ * #UHDR_CODEC_INVALID_PARAM otherwise.
+ */
+uhdr_error_info_t uhdr_enc_set_gainmap_image(uhdr_codec_private_t* enc,
+ uhdr_compressed_image_t* img,
+ uhdr_gainmap_metadata_t* metadata);
+
+/*!\brief Set quality for compression
+ *
+ * \param[in] enc encoder instance.
+ * \param[in] quality quality factor.
+ * \param[in] intent UHDR_BASE_IMG for base image and UHDR_GAIN_MAP_IMG for gain map image.
+ *
+ * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds,
+ * #UHDR_CODEC_INVALID_PARAM otherwise.
+ */
+UHDR_EXTERN uhdr_error_info_t uhdr_enc_set_quality(uhdr_codec_private_t* enc, int quality,
+ uhdr_img_label_t intent);
+
+/*!\brief Set Exif data that needs to be inserted in the output compressed stream
+ *
+ * \param[in] enc encoder instance.
+ * \param[in] img exif data descriptor.
+ *
+ * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds,
+ * #UHDR_CODEC_INVALID_PARAM otherwise.
+ */
+UHDR_EXTERN uhdr_error_info_t uhdr_enc_set_exif_data(uhdr_codec_private_t* enc,
+ uhdr_mem_block_t* exif);
+
+/*!\brief Set output image compression format.
+ *
+ * \param[in] enc encoder instance.
+ * \param[in] media_type output image compression format.
+ *
+ * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds,
+ * #UHDR_CODEC_INVALID_PARAM otherwise.
+ */
+UHDR_EXTERN uhdr_error_info_t uhdr_enc_set_output_format(uhdr_codec_private_t* enc,
+ uhdr_codec_t media_type);
+
+/*!\brief Encode process call
+ * After initializing the encoder context, call to this function will submit data for encoding. If
+ * the call is successful, the encoded output is stored internally and is accessible via
+ * uhdr_get_encoded_stream().
+ *
+ * The basic usage of uhdr encoder is as follows:
+ * - The program creates an instance of an encoder using,
+ * - uhdr_create_encoder().
+ * - The program registers input images to the encoder using,
+ * - uhdr_enc_set_raw_image(ctxt, img, UHDR_HDR_IMG)
+ * - uhdr_enc_set_raw_image(ctxt, img, UHDR_SDR_IMG)
+ * - The program overrides the default settings using uhdr_enc_set_*() functions
+ * - If the application wants to control the compression level
+ * - uhdr_enc_set_quality()
+ * - If the application wants to insert exif data
+ * - uhdr_enc_set_exif_data()
+ * - If the application wants to control target compression format
+ * - uhdr_enc_set_output_format()
+ * - The program calls uhdr_encode() to encode data. This call would initiate the process of
+ * computing gain map from hdr intent and sdr intent. The sdr intent and gain map image are
+ * compressed at the set quality using the codec of choice.
+ * - On success, the program can access the encoded output with uhdr_get_encoded_stream().
+ * - The program finishes the encoding with uhdr_release_encoder().
+ *
+ * The library allows setting Hdr and/or Sdr intent in compressed format,
+ * - uhdr_enc_set_compressed_image(ctxt, img, UHDR_HDR_IMG)
+ * - uhdr_enc_set_compressed_image(ctxt, img, UHDR_SDR_IMG)
+ * In this mode, the compressed image(s) are first decoded to raw image(s). These raw image(s) go
+ * through the aforth mentioned gain map computation and encoding process. In this case, the usage
+ * shall be like this:
+ * - uhdr_create_encoder()
+ * - uhdr_enc_set_compressed_image(ctxt, img, UHDR_HDR_IMG)
+ * - uhdr_enc_set_compressed_image(ctxt, img, UHDR_SDR_IMG)
+ * - uhdr_encode()
+ * - uhdr_get_encoded_stream()
+ * - uhdr_release_encoder()
+ * If the set compressed image media type of intent UHDR_SDR_IMG and output media type are
+ * identical, then this image is directly used for primary image. No re-encode of raw image is done.
+ * This implies base image quality setting is un-used. Only gain map image is encoded at the set
+ * quality using codec of choice. On the other hand, if the set compressed image media type and
+ * output media type are different, then transcoding is done.
+ *
+ * The library also allows directly setting base and gain map image in compressed format,
+ * - uhdr_enc_set_compressed_image(ctxt, img, UHDR_BASE_IMG)
+ * - uhdr_enc_set_gainmap_image(ctxt, img, metadata)
+ * In this mode, gain map computation is by-passed. The input images are transcoded (if necessary),
+ * combined and sent back.
+ *
+ * It is possible to create a uhdr image solely from Hdr intent. In this case, the usage shall look
+ * like this:
+ * - uhdr_create_encoder()
+ * - uhdr_enc_set_raw_image(ctxt, img, UHDR_HDR_IMG)
+ * - uhdr_enc_set_quality() // optional
+ * - uhdr_enc_set_exif_data() // optional
+ * - uhdr_enc_set_output_format() // optional
+ * - uhdr_encode()
+ * - uhdr_get_encoded_stream()
+ * - uhdr_release_encoder()
+ * In this mode, the Sdr rendition is created from Hdr intent by tone-mapping. The tone-mapped sdr
+ * image and hdr image go through the aforth mentioned gain map computation and encoding process to
+ * create uhdr image.
+ *
+ * In all modes, Exif data is inserted if requested.
+ *
+ * \param[in] enc encoder instance.
+ *
+ * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, uhdr_codec_err_t otherwise.
+ */
+UHDR_EXTERN uhdr_error_info_t uhdr_encode(uhdr_codec_private_t* enc);
+
+/*!\brief Get encoded ultra hdr stream
+ *
+ * \param[in] enc encoder instance.
+ *
+ * \return nullptr if encode process call is unsuccessful, uhdr image descriptor otherwise
+ */
+UHDR_EXTERN uhdr_compressed_image_t* uhdr_get_encoded_stream(uhdr_codec_private_t* enc);
+
+/*!\brief Reset encoder instance.
+ * Clears all previous settings and resets to default state and ready for re-initialization
+ *
+ * \param[in] enc encoder instance.
+ *
+ * \return none
+ */
+UHDR_EXTERN void uhdr_reset_encoder(uhdr_codec_private_t* enc);
+
+// ===============================================================================================
+// Decoder APIs
+// ===============================================================================================
+
+/*!\brief check if it is a valid ultrahdr image.
+ *
+ * @param[in] data pointer to input compressed stream
+ * @param[in] size size of compressed stream
+ *
+ * @returns 1 if the input data has a primary image, gain map image and gain map metadata. 0
+ * otherwise.
+ */
+UHDR_EXTERN int is_uhdr_image(void* data, int size);
+
+/*!\brief Create a new decoder instance. The instance is initialized with default settings.
+ * To override the settings use uhdr_dec_set_*()
+ *
+ * \return nullptr if there was an error allocating memory else a fresh opaque decoder handle
+ */
+UHDR_EXTERN uhdr_codec_private_t* uhdr_create_decoder(void);
+
+/*!\brief Release decoder instance.
+ * Frees all allocated storage associated with decoder instance.
+ *
+ * \param[in] dec decoder instance.
+ *
+ * \return none
+ */
+UHDR_EXTERN void uhdr_release_decoder(uhdr_codec_private_t* dec);
+
+/*!\brief Add compressed image descriptor to decoder context. The function goes through all the
+ * fields of the image descriptor and checks for their sanity. If no anomalies are seen then the
+ * image is added to internal list. Repeated calls to this function will replace the old entry with
+ * the current.
+ *
+ * \param[in] dec decoder instance.
+ * \param[in] img image descriptor.
+ *
+ * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds,
+ * #UHDR_CODEC_INVALID_PARAM otherwise.
+ */
+UHDR_EXTERN uhdr_error_info_t uhdr_dec_set_image(uhdr_codec_private_t* dec,
+ uhdr_compressed_image_t* img);
+
+/*!\brief Set output image format
+ *
+ * \param[in] dec decoder instance.
+ * \param[in] fmt output image format.
+ *
+ * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds,
+ * #UHDR_CODEC_INVALID_PARAM otherwise.
+ */
+UHDR_EXTERN uhdr_error_info_t uhdr_dec_set_out_img_format(uhdr_codec_private_t* dec,
+ uhdr_img_fmt_t fmt);
+
+/*!\brief Set output color transfer
+ *
+ * \param[in] dec decoder instance.
+ * \param[in] ct output color transfer
+ *
+ * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds,
+ * #UHDR_CODEC_INVALID_PARAM otherwise.
+ */
+UHDR_EXTERN uhdr_error_info_t uhdr_dec_set_out_color_transfer(uhdr_codec_private_t* dec,
+ uhdr_color_transfer_t ct);
+
+/*!\brief Set output max display boost
+ *
+ * \param[in] dec decoder instance.
+ * \param[in] display_boost max display boost
+ *
+ * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds,
+ * #UHDR_CODEC_INVALID_PARAM otherwise.
+ */
+UHDR_EXTERN uhdr_error_info_t uhdr_dec_set_out_max_display_boost(uhdr_codec_private_t* dec,
+ float display_boost);
+
+/*!\brief This function parses the bitstream that is registered with the decoder context and makes
+ * image information available to the client via uhdr_dec_get_() functions. It does not decompress
+ * the image. That is done by uhdr_decode().
+ *
+ * \param[in] dec decoder instance.
+ *
+ * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, uhdr_codec_err_t otherwise.
+ */
+UHDR_EXTERN uhdr_error_info_t uhdr_dec_probe(uhdr_codec_private_t* dec);
+
+/*!\brief Get base image width
+ *
+ * \param[in] dec decoder instance.
+ *
+ * \return -1 if probe call is unsuccessful, base image width otherwise
+ */
+UHDR_EXTERN int uhdr_dec_get_image_width(uhdr_codec_private_t* dec);
+
+/*!\brief Get base image height
+ *
+ * \param[in] dec decoder instance.
+ *
+ * \return -1 if probe call is unsuccessful, base image height otherwise
+ */
+UHDR_EXTERN int uhdr_dec_get_image_height(uhdr_codec_private_t* dec);
+
+/*!\brief Get gainmap image width
+ *
+ * \param[in] dec decoder instance.
+ *
+ * \return -1 if probe call is unsuccessful, gain map image width otherwise
+ */
+UHDR_EXTERN int uhdr_dec_get_gainmap_width(uhdr_codec_private_t* dec);
+
+/*!\brief Get gainmap image height
+ *
+ * \param[in] dec decoder instance.
+ *
+ * \return -1 if probe call is unsuccessful, gain map image height otherwise
+ */
+UHDR_EXTERN int uhdr_dec_get_gainmap_height(uhdr_codec_private_t* dec);
+
+/*!\brief Get exif information
+ *
+ * \param[in] dec decoder instance.
+ *
+ * \return nullptr if probe call is unsuccessful, memory block with exif data otherwise
+ */
+UHDR_EXTERN uhdr_mem_block_t* uhdr_dec_get_exif(uhdr_codec_private_t* dec);
+
+/*!\brief Get icc information
+ *
+ * \param[in] dec decoder instance.
+ *
+ * \return nullptr if probe call is unsuccessful, memory block with icc data otherwise
+ */
+UHDR_EXTERN uhdr_mem_block_t* uhdr_dec_get_icc(uhdr_codec_private_t* dec);
+
+/*!\brief Get gain map metadata
+ *
+ * \param[in] dec decoder instance.
+ *
+ * \return nullptr if decoded process call is unsuccessful, gainmap metadata descriptor otherwise
+ */
+UHDR_EXTERN uhdr_gainmap_metadata_t* uhdr_dec_get_gain_map_metadata(uhdr_codec_private_t* dec);
+
+/*!\brief Decode process call
+ * After initializing the decoder context, call to this function will submit data for decoding. If
+ * the call is successful, the decoded output is stored internally and is accessible via
+ * uhdr_get_decoded_image().
+ *
+ * The basic usage of uhdr decoder is as follows:
+ * - The program creates an instance of a decoder using,
+ * - uhdr_create_decoder().
+ * - The program registers input images to the decoder using,
+ * - uhdr_dec_set_image(ctxt, img)
+ * - The program overrides the default settings using uhdr_dec_set_*() functions.
+ * - If the application wants to control the output image format,
+ * - uhdr_dec_set_out_img_format()
+ * - If the application wants to control the output transfer characteristics,
+ * - uhdr_dec_set_out_color_transfer()
+ * - If the application wants to control the output display boost,
+ * - uhdr_dec_set_out_max_display_boost()
+ * - The program calls uhdr_decompress() to decode uhdr stream. This call would initiate the process
+ * of decoding base image and gain map image. These two are combined to give the final rendition
+ * image.
+ * - The program can access the decoded output with uhdr_get_decoded_image().
+ * - The program finishes the decoding with uhdr_release_decoder().
+ *
+ * \param[in] dec decoder instance.
+ *
+ * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, uhdr_codec_err_t otherwise.
+ */
+UHDR_EXTERN uhdr_error_info_t uhdr_decode(uhdr_codec_private_t* dec);
+
+/*!\brief Get final rendition image
+ *
+ * \param[in] dec decoder instance.
+ *
+ * \return nullptr if decoded process call is unsuccessful, raw image descriptor otherwise
+ */
+UHDR_EXTERN uhdr_raw_image_t* uhdr_get_decoded_image(uhdr_codec_private_t* dec);
+
+/*!\brief Get gain map image
+ *
+ * \param[in] dec decoder instance.
+ *
+ * \return nullptr if decoded process call is unsuccessful, raw image descriptor otherwise
+ */
+UHDR_EXTERN uhdr_raw_image_t* uhdr_get_gain_map_image(uhdr_codec_private_t* dec);
+
+/*!\brief Reset decoder instance.
+ * Clears all previous settings and resets to default state and ready for re-initialization
+ *
+ * \param[in] dec decoder instance.
+ *
+ * \return none
+ */
+UHDR_EXTERN void uhdr_reset_decoder(uhdr_codec_private_t* dec);
+
+#endif // ULTRAHDR_API_H