diff options
author | DichenZhang1 <140119224+DichenZhang1@users.noreply.github.com> | 2023-11-07 20:42:34 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-11-07 20:42:34 -0800 |
commit | 77dfb1591c60b4ec9b763a98c683287ec39b43a9 (patch) | |
tree | 2ea862ae068c50ccb972b062dcb2c02e6816b424 | |
parent | d1ad9a00076ec1ae593d0957f87a2d5921fc852d (diff) | |
parent | d4fa59c473584ba9a5ee7de861d093a464896ca0 (diff) | |
download | libultrahdr-77dfb1591c60b4ec9b763a98c683287ec39b43a9.tar.gz |
Merge pull request #36 from ittiam-systems/updates
Updates
-rw-r--r-- | .clang-format | 7 | ||||
-rw-r--r-- | .github/workflows/cmake.yml | 6 | ||||
-rw-r--r-- | Android.bp | 22 | ||||
-rw-r--r-- | CMakeLists.txt | 374 | ||||
-rw-r--r-- | README.md | 77 | ||||
-rw-r--r-- | examples/Android.bp | 41 | ||||
-rw-r--r-- | examples/ultrahdr_app.cpp | 991 | ||||
-rw-r--r-- | fuzzer/README.md | 145 | ||||
-rwxr-xr-x | fuzzer/ossfuzz.sh | 2 | ||||
-rw-r--r-- | fuzzer/ultrahdr_dec_fuzzer.cpp | 64 | ||||
-rw-r--r-- | fuzzer/ultrahdr_enc_fuzzer.cpp | 490 | ||||
-rw-r--r-- | icc.cpp | 688 | ||||
-rw-r--r-- | include/ultrahdr/icc.h | 263 | ||||
-rw-r--r-- | include/ultrahdr/jpegdecoderhelper.h | 155 | ||||
-rw-r--r-- | include/ultrahdr/jpegencoderhelper.h | 106 | ||||
-rw-r--r-- | include/ultrahdr/jpegr.h | 465 | ||||
-rw-r--r-- | include/ultrahdr/ultrahdrcommon.h | 64 | ||||
-rw-r--r-- | jpegdecoderhelper.cpp | 567 | ||||
-rw-r--r-- | jpegencoderhelper.cpp | 294 | ||||
-rw-r--r-- | jpegrutils.cpp | 601 | ||||
-rw-r--r-- | lib/gainmapmath.cpp (renamed from gainmapmath.cpp) | 325 | ||||
-rw-r--r-- | lib/gainmapmath.h (renamed from include/ultrahdr/gainmapmath.h) | 51 | ||||
-rw-r--r-- | lib/icc.cpp | 680 | ||||
-rw-r--r-- | lib/icc.h | 259 | ||||
-rw-r--r-- | lib/jpegdecoderhelper.cpp | 537 | ||||
-rw-r--r-- | lib/jpegdecoderhelper.h | 154 | ||||
-rw-r--r-- | lib/jpegencoderhelper.cpp | 287 | ||||
-rw-r--r-- | lib/jpegencoderhelper.h | 106 | ||||
-rw-r--r-- | lib/jpegr.cpp (renamed from jpegr.cpp) | 408 | ||||
-rw-r--r-- | lib/jpegr.h | 464 | ||||
-rw-r--r-- | lib/jpegrutils.cpp | 583 | ||||
-rw-r--r-- | lib/jpegrutils.h (renamed from include/ultrahdr/jpegrutils.h) | 49 | ||||
-rw-r--r-- | lib/multipictureformat.cpp | 92 | ||||
-rw-r--r-- | lib/multipictureformat.h (renamed from include/ultrahdr/multipictureformat.h) | 18 | ||||
-rw-r--r-- | lib/ultrahdr.h (renamed from include/ultrahdr/ultrahdr.h) | 10 | ||||
-rw-r--r-- | lib/ultrahdrcommon.h | 64 | ||||
-rw-r--r-- | multipictureformat.cpp | 94 | ||||
-rw-r--r-- | tests/Android.bp | 21 | ||||
-rw-r--r-- | tests/data/LICENSE (renamed from third_party/data/LICENSE) | 0 | ||||
-rw-r--r-- | tests/data/jpeg_image.jpg (renamed from third_party/data/jpeg_image.jpg) | bin | 24430 -> 24430 bytes | |||
-rw-r--r-- | tests/data/minnie-318x240.yu12 (renamed from third_party/data/minnie-318x240.yu12) | 0 | ||||
-rw-r--r-- | tests/data/minnie-320x240-y.jpg (renamed from third_party/data/minnie-320x240-y.jpg) | bin | 20193 -> 20193 bytes | |||
-rw-r--r-- | tests/data/minnie-320x240-yuv-icc.jpg (renamed from third_party/data/minnie-320x240-yuv-icc.jpg) | bin | 34266 -> 34266 bytes | |||
-rw-r--r-- | tests/data/minnie-320x240-yuv.jpg (renamed from third_party/data/minnie-320x240-yuv.jpg) | bin | 20193 -> 20193 bytes | |||
-rw-r--r-- | tests/data/minnie-320x240.y (renamed from third_party/data/minnie-320x240.y) | 0 | ||||
-rw-r--r-- | tests/data/minnie-320x240.yu12 (renamed from third_party/data/minnie-320x240.yu12) | 0 | ||||
-rw-r--r-- | tests/data/raw_p010_image.p010 (renamed from third_party/data/raw_p010_image.p010) | bin | 2764800 -> 2764800 bytes | |||
-rw-r--r-- | tests/data/raw_yuv420_image.yuv420 (renamed from third_party/data/raw_yuv420_image.yuv420) | 0 | ||||
-rw-r--r-- | tests/gainmapmath_test.cpp | 627 | ||||
-rw-r--r-- | tests/icchelper_test.cpp | 73 | ||||
-rw-r--r-- | tests/jpegdecoderhelper_test.cpp | 159 | ||||
-rw-r--r-- | tests/jpegencoderhelper_test.cpp | 142 | ||||
-rw-r--r-- | tests/jpegr_test.cpp | 1109 | ||||
-rw-r--r-- | tests/ultrahdr_app.cpp | 921 | ||||
-rw-r--r-- | third_party/image_io/CMakeLists.txt (renamed from third_party/cmake/image_io/CMakeLists.txt) | 20 | ||||
-rw-r--r-- | third_party/image_io/includes/image_io/base/data_range.h | 4 | ||||
-rw-r--r-- | third_party/image_io/src/utils/file_utils.cc | 6 | ||||
-rw-r--r-- | utils.cmake | 84 |
58 files changed, 6402 insertions, 6367 deletions
diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..0153825 --- /dev/null +++ b/.clang-format @@ -0,0 +1,7 @@ +# Run manually to reformat a file: +# clang-format -i --style=file <file> + +Language: Cpp +BasedOnStyle: Google +ColumnLimit: 100 +SortIncludes: false diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index e45ccda..a69b73a 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -17,21 +17,21 @@ jobs: cc: gcc cxx: g++ build-system: cmake - cmake-opts: '-DENABLE_FUZZERS=OFF' + cmake-opts: '-DUHDR_BUILD_TESTS=1 -DUHDR_BUILD_FUZZERS=0' - name: ubuntu-latest-clang-cmake os: ubuntu-latest cc: clang cxx: clang++ build-system: cmake - cmake-opts: '-DENABLE_FUZZERS=ON' + cmake-opts: '-DUHDR_BUILD_TESTS=1 -DUHDR_BUILD_FUZZERS=1' - name: macos-latest-clang-cmake os: macos-latest cc: clang cxx: clang++ build-system: cmake - cmake-opts: '-DENABLE_FUZZERS=OFF' + cmake-opts: '-DUHDR_BUILD_TESTS=1 -DUHDR_BUILD_FUZZERS=0' runs-on: ${{ matrix.os }} @@ -35,15 +35,15 @@ cc_library { name: "libultrahdr", host_supported: true, vendor_available: true, - export_include_dirs: ["include"], - local_include_dirs: ["include"], + export_include_dirs: ["lib"], + local_include_dirs: ["lib"], srcs: [ - "icc.cpp", - "jpegr.cpp", - "gainmapmath.cpp", - "jpegrutils.cpp", - "multipictureformat.cpp", + "lib/icc.cpp", + "lib/jpegr.cpp", + "lib/gainmapmath.cpp", + "lib/jpegrutils.cpp", + "lib/multipictureformat.cpp", ], shared_libs: [ @@ -65,10 +65,10 @@ cc_library { "liblog", ], - export_include_dirs: ["include"], + export_include_dirs: ["lib"], srcs: [ - "jpegencoderhelper.cpp", + "lib/jpegencoderhelper.cpp", ], } @@ -82,9 +82,9 @@ cc_library { "liblog", ], - export_include_dirs: ["include"], + export_include_dirs: ["lib"], srcs: [ - "jpegdecoderhelper.cpp", + "lib/jpegdecoderhelper.cpp", ], } diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b316d5..86f378a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,197 +14,287 @@ # the License. # -cmake_minimum_required(VERSION 3.5) +cmake_minimum_required(VERSION 3.13) -project(ULTRAHDR) +project(UltraHdr C CXX) -if(NOT CMAKE_BUILD_TYPE) - message(STATUS "No build type selected, defaulting to release") - set(CMAKE_BUILD_TYPE "Release" CACHE STRING "" FORCE) +########################################################### +# Detect system +########################################################### +if(${CMAKE_SYSTEM_NAME} MATCHES "Linux") +elseif(WIN32) +elseif(APPLE) +else() + message(FATAL_ERROR "Platform not supported") endif() -set(ULTRAHDR_VERSION_MAJOR 1) -set(ULTRAHDR_VERSION_MINOR 0) -set(ULTRAHDR_VERSION_PATCH 0) -set(ULTRAHDR_VERSION ${ULTRAHDR_VERSION_MAJOR}.${ULTRAHDR_VERSION_MINOR}.${ULTRAHDR_VERSION_PATCH}) - -option(ENABLE_FUZZERS "Enable building fuzzer apps" OFF) -# Add -fuzzer-no-link to sanitize argument if fuzzer build is enabled -if(${ENABLE_FUZZERS}) - message(STATUS "Building fuzzer applications enabled") - if(DEFINED SANITIZE) - set(SANITIZE "${SANITIZE},fuzzer-no-link") +if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm" OR CMAKE_SYSTEM_PROCESSOR MATCHES "aarch") + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(ARCH "aarch64") else() - set(SANITIZE "fuzzer-no-link") + set(ARCH "arm") endif() +elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^mips.*") + message(FATAL_ERROR "Architecture not supported") +elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^ppc.*") + message(FATAL_ERROR "Architecture not supported") else() - message(STATUS "Building fuzzer applications disabled") + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(ARCH "x86_64") + else() + set(ARCH "x86") + endif() endif() -option(ENABLE_TESTS "Enable unit tests" OFF) -if(${ENABLE_TESTS}) - message(STATUS "Building unit tests enabled") - include(CTest) -else() - message(STATUS "Building unit tests disabled") +########################################################### +# 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(FUZZERS_DIR ${CMAKE_SOURCE_DIR}/fuzzer) +set(EXAMPLES_DIR ${CMAKE_SOURCE_DIR}/examples) + +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.") +endif() + +########################################################### +# Options +########################################################### +get_cmake_property(IS_MULTI GENERATOR_IS_MULTI_CONFIG) +if (NOT IS_MULTI) + if (NOT CMAKE_BUILD_TYPE) + message(STATUS "No build type chosen, selecting Release") + set(CMAKE_BUILD_TYPE "Release" CACHE STRING "The type of build: Debug Release MinSizeRel RelWithDebInfo." FORCE) + endif() endif() +function(option_if_not_defined name description default) + if(NOT DEFINED ${name}) + option(${name} ${description} ${default}) + endif() +endfunction() + +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_FUZZERS "Build fuzzers " FALSE) +option_if_not_defined(UHDR_ENABLE_LOGS "Build with verbose logging " FALSE) + +########################################################### +# Compile flags +########################################################### set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) -set(CMAKE_THREAD_PREFER_PTHREAD ON) -set(THREADS_PREFER_PTHREAD_FLAG ON) -find_package(Threads REQUIRED) +if(MSVC) + if(DEFINED UHDR_SANITIZE_OPTIONS) + message(FATAL_ERROR "Building with Sanitizer options not supported in MSVC path") + endif() + if(UHDR_BUILD_FUZZERS) + message(FATAL_ERROR "Building fuzzers not supported in MSVC path") + endif() + add_compile_options($<$<CONFIG:>:/MT> + $<$<CONFIG:Debug>:/MTd> + $<$<CONFIG:MinSizeRel>:/MT> + $<$<CONFIG:Release>:/MT> + $<$<CONFIG:RelWithDebInfo>:/MT>) + 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 + # 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 +else() + add_compile_options(-ffunction-sections) + add_compile_options(-fdata-sections) + add_compile_options(-fomit-frame-pointer) + if(ARCH STREQUAL "x86") + add_compile_options(-m32) + add_compile_options(-march=pentium4) + add_compile_options(-mtune=generic) + endif() + if(ARCH STREQUAL "x86_64") + add_compile_options(-m64) + add_compile_options(-march=x86-64) + add_compile_options(-mtune=generic) + endif() -set(SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}") + 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}) + add_link_options(-fsanitize=${UHDR_SANITIZE_OPTIONS}) + endif() + + if(UHDR_BUILD_FUZZERS) + CheckCompilerOption("-fsanitize=fuzzer-no-link" fuzz) + add_compile_options(-fsanitize=fuzzer-no-link) + endif() +endif() -include("${SRC_DIR}/utils.cmake") +if(UHDR_ENABLE_LOGS) + add_compile_options(-DLOG_NDEBUG) +endif() -libultrahdr_add_compile_options() +########################################################### +# Dependencies +########################################################### -ADD_SUBDIRECTORY("${SRC_DIR}/third_party/cmake/image_io") +# Threads +set(CMAKE_THREAD_PREFER_PTHREAD ON) +set(THREADS_PREFER_PTHREAD_FLAG ON) +find_package(Threads REQUIRED) -get_directory_property(ULTRA_HDR_FLAGS COMPILE_OPTIONS) -string (REPLACE ";" " " ULTRA_HDR_FLAGS_STR "${ULTRA_HDR_FLAGS}") -set(ULTRA_HDR_CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${ULTRA_HDR_FLAGS_STR}") -set(ULTRA_HDR_CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${ULTRA_HDR_FLAGS_STR}") +# ImageIO +add_subdirectory("${THIRD_PARTY_DIR}/image_io") include(ExternalProject) -function(fetch_libjpegturbo) - 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 ${SRC_DIR}/third_party/libjpeg-turbo - BUILD_COMMAND ${CMAKE_COMMAND} --build <BINARY_DIR> --target jpeg-static - CMAKE_ARGS -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} - -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} - -DCMAKE_C_FLAGS=${ULTRA_HDR_CMAKE_C_FLAGS} - INSTALL_COMMAND "" - ) - set(JPEG_INCLUDE_DIRS - ${SRC_DIR}/third_party/libjpeg-turbo/ - ${CMAKE_CURRENT_BINARY_DIR}/libjpeg-turbo/src/libjpeg-turbo-build PARENT_SCOPE) +get_directory_property(UHDR_COMPILE_FLAGS COMPILE_OPTIONS) +string (REPLACE ";" " " UHDR_COMPILE_FLAGS_STR "${UHDR_COMPILE_FLAGS}") +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 +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} + INSTALL_COMMAND "" +) +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/libjpeg.a PARENT_SCOPE) -endfunction() + ${CMAKE_CURRENT_BINARY_DIR}/libjpeg-turbo/src/libjpeg-turbo-build/$<CONFIG>/jpeg-static.lib) +else() + set(JPEG_LIBRARIES + ${CMAKE_CURRENT_BINARY_DIR}/libjpeg-turbo/src/libjpeg-turbo-build/libjpeg.a) +endif() -function(fetch_googletest) +if(UHDR_BUILD_TESTS) + # gtest and gmock ExternalProject_Add(googletest GIT_REPOSITORY https://github.com/google/googletest - GIT_TAG v1.13.0 + GIT_TAG v1.14.0 PREFIX ${CMAKE_CURRENT_BINARY_DIR}/googletest - SOURCE_DIR ${SRC_DIR}/third_party/googletest + SOURCE_DIR ${THIRD_PARTY_DIR}/googletest CMAKE_ARGS -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} - -DCMAKE_C_FLAGS=${ULTRA_HDR_CMAKE_C_FLAGS} - -DCMAKE_CXX_FLAGS=${ULTRA_HDR_CMAKE_CXX_FLAGS} + -DCMAKE_C_FLAGS=${UHDR_CMAKE_C_FLAGS} + -DCMAKE_CXX_FLAGS=${UHDR_CMAKE_CXX_FLAGS} INSTALL_COMMAND "" ) set(GTEST_INCLUDE_DIRS - ${CMAKE_CURRENT_BINARY_DIR}/googletest/googletest/include - ${CMAKE_CURRENT_BINARY_DIR}/googletest/googlemock/include PARENT_SCOPE) - 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 PARENT_SCOPE) -endfunction() + ${THIRD_PARTY_DIR}/googletest/googletest/include + ${THIRD_PARTY_DIR}/googletest/googlemock/include) + 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) + 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() +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) -fetch_libjpegturbo() +########################################################### +# File Lists +########################################################### +file(GLOB UHDR_LIB_LIST "${SOURCE_DIR}/*.cpp") +file(GLOB UHDR_TEST_LIST "${TESTS_DIR}/*.cpp") +set(COMMON_INCLUDE_LIST ${SOURCE_DIR} ${JPEG_INCLUDE_DIRS}) +set(COMMON_LIBS_LIST ${JPEG_LIBRARIES} Threads::Threads) -if(${ENABLE_TESTS}) - find_package(GTest) - if(NOT GTest_FOUND) - fetch_googletest() - endif() -endif() - -add_library(ultrahdr STATIC - "${SRC_DIR}/gainmapmath.cpp" - "${SRC_DIR}/icc.cpp" - "${SRC_DIR}/jpegr.cpp" - "${SRC_DIR}/jpegrutils.cpp" - "${SRC_DIR}/jpegencoderhelper.cpp" - "${SRC_DIR}/jpegdecoderhelper.cpp" - "${SRC_DIR}/multipictureformat.cpp" -) +########################################################### +# Targets +########################################################### +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 - ${JPEG_INCLUDE_DIRS} - "${SRC_DIR}/include" - "${SRC_DIR}/third_party/image_io/includes/" -) - -target_link_libraries(ultrahdr PRIVATE - ${JPEG_LIBRARIES} - image_io - Threads::Threads + ${COMMON_INCLUDE_LIST} + "${THIRD_PARTY_DIR}/image_io/includes/" ) +target_link_libraries(ultrahdr PRIVATE ${COMMON_LIBS_LIST} image_io) -libultrahdr_add_executable(ultrahdr_app - ultrahdr - SOURCES - "${SRC_DIR}/tests/ultrahdr_app.cpp" - INCLUDES - ${JPEG_INCLUDE_DIRS} - "${SRC_DIR}/include" -) -add_dependencies(ultrahdr_app libjpeg-turbo) - -if (${ENABLE_FUZZERS}) - libultrahdr_add_fuzzer(ultrahdr_enc_fuzzer ultrahdr - SOURCES - ${SRC_DIR}/fuzzer/ultrahdr_enc_fuzzer.cpp - INCLUDES - ${JPEG_INCLUDE_DIRS} - "${SRC_DIR}/include" - ) - add_dependencies(ultrahdr_enc_fuzzer libjpeg-turbo) - - libultrahdr_add_fuzzer(ultrahdr_dec_fuzzer ultrahdr - SOURCES - ${SRC_DIR}/fuzzer/ultrahdr_dec_fuzzer.cpp - INCLUDES - ${JPEG_INCLUDE_DIRS} - "${SRC_DIR}/include" - ) - add_dependencies(ultrahdr_dec_fuzzer libjpeg-turbo) +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}) + if(UHDR_BUILD_FUZZERS) + target_link_options(ultrahdr_app PRIVATE -fsanitize=fuzzer-no-link) + endif() + target_link_libraries(ultrahdr_app PRIVATE ultrahdr) endif() -if(${ENABLE_TESTS}) - libultrahdr_add_executable(ultrahdr_unit_test - ultrahdr - SOURCES - "${SRC_DIR}/tests/jpegr_test.cpp" - "${SRC_DIR}/tests/gainmapmath_test.cpp" - "${SRC_DIR}/tests/icchelper_test.cpp" - "${SRC_DIR}/tests/jpegencoderhelper_test.cpp" - "${SRC_DIR}/tests/jpegdecoderhelper_test.cpp" - "${SRC_DIR}/tests/icchelper_test.cpp" - INCLUDES - ${JPEG_INCLUDE_DIRS} - ${GTEST_INCLUDE_DIRS} - "${SRC_DIR}/include" +if(UHDR_BUILD_TESTS) + include(CTest) + add_executable(ultrahdr_unit_test ${UHDR_TEST_LIST}) + add_dependencies(ultrahdr_unit_test googletest ultrahdr) + target_include_directories(ultrahdr_unit_test PRIVATE + ${COMMON_INCLUDE_LIST} + ${GTEST_INCLUDE_DIRS} ) - if(GTest_FOUND) - add_dependencies(ultrahdr_unit_test libjpeg-turbo) + 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}) + if(WIN32) + file(COPY "${TESTS_DIR}/data/" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/data") else() - add_dependencies(ultrahdr_unit_test googletest libjpeg-turbo) + execute_process(COMMAND cmake -E create_symlink + "${TESTS_DIR}/data/" + "${CMAKE_CURRENT_BINARY_DIR}/data" + ) endif() + add_test(NAME UHDRUnitTests, COMMAND ultrahdr_unit_test) +endif() - target_link_libraries(ultrahdr_unit_test ${GTEST_BOTH_LIBRARIES}) - - execute_process(COMMAND cmake -E create_symlink - "${SRC_DIR}/third_party/data/" - "${CMAKE_CURRENT_BINARY_DIR}/data" - ) +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}) + 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) - add_test(NAME UltraHdrUnitTests, COMMAND ultrahdr_unit_test) + 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}) + 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) endif() @@ -23,33 +23,34 @@ 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.5 or later +- [CMake](http://www.cmake.org) v3.13 or later - [NASM](http://www.nasm.us) or [Yasm](http://yasm.tortall.net) - (If libjpeg-turbo is building on x86 or x86-64 with SIMD extensions) + (If libjpeg-turbo needs to be built with SIMD extensions) * 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/). -Tested with GCC v11.4 and Clang 14.0.0 on Linux and Mac Platforms. +- Compilers with support for C++17 -### Building Commands +Should work with GCC v7 (or later) and Clang 5 (or later) on Linux and Mac Platforms. -To build libultrahdr and sample application: +Should work with Microsoft Visual C++ 2019 (or later) on Windows Platforms. - mkdir {build_directory} - cd {build_directory} - cmake ../ -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ - make +Build Procedure +--------------- + +To build libultrahdr, examples, unit tests: -To build unit tests: +### Un*x (including Linux, Mac) mkdir {build_directory} cd {build_directory} - cmake ../ -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DENABLE_TESTS=1 + cmake -G "Unix Makefiles" -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DUHDR_BUILD_TESTS=1 ../ make ctest @@ -61,11 +62,59 @@ This will generate the following files under *{build_directory}*: **ultrahdr_unit_test**<br> Unit tests +### 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 + +This will generate the following files under *{build_directory/Release}*: + +**ultrahdr.lib**<br> Static link library for the ultrahdr API + +**ultrahdr_app.exe**<br> Sample application demonstrating ultrahdr API + +**ultrahdr_unit_test.exe**<br> Unit tests + +### MinGW + +NOTE: This assumes that you are building on a Windows machine using the MSYS +environment. + + 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 "MinGW Makefiles" -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 + +**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 + +### Building Fuzzers + +Refer to [README.md](fuzzer/README.md) for complete instructions. + Using libultrahdr =================== libultrahdr includes two classes of APIs, one to compress and the other to decompress HDR images: -- Refer to [jpegr.h](include/ultrahdr/jpegr.h) for detailed description of various encode and decode api. -- Refer to [ultrahdr_app.cpp](tests/ultrahdr_app.cpp) for examples of its usage. +- Refer to [jpegr.h](lib/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. diff --git a/examples/Android.bp b/examples/Android.bp new file mode 100644 index 0000000..0ede18b --- /dev/null +++ b/examples/Android.bp @@ -0,0 +1,41 @@ +// Copyright 2023 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. + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "external_libultrahdr_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["external_libultrahdr_license"], +} + +cc_binary { + name: "ultrahdr_sample_app", + host_supported: true, + srcs: [ + "ultrahdr_app.cpp", + ], + shared_libs: [ + "libbase", + "libimage_io", + "libjpeg", + "liblog", + ], + static_libs: [ + "libjpegdecoder", + "libjpegencoder", + "libultrahdr", + ], +} diff --git a/examples/ultrahdr_app.cpp b/examples/ultrahdr_app.cpp new file mode 100644 index 0000000..f3fd372 --- /dev/null +++ b/examples/ultrahdr_app.cpp @@ -0,0 +1,991 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef _WIN32 +#include <Windows.h> +#else +#include <sys/time.h> +#endif + +#include <string.h> + +#include <algorithm> +#include <cmath> +#include <fstream> +#include <iostream> + +#include "ultrahdrcommon.h" +#include "gainmapmath.h" +#include "jpegr.h" + +using namespace ultrahdr; + +const float BT601YUVtoRGBMatrix[9] = { + 1, 0, 1.402, 1, (-0.202008 / 0.587), (-0.419198 / 0.587), 1.0, 1.772, 0.0}; +const float BT709YUVtoRGBMatrix[9] = { + 1, 0, 1.5748, 1, (-0.13397432 / 0.7152), (-0.33480248 / 0.7152), 1.0, 1.8556, 0.0}; +const float BT2020YUVtoRGBMatrix[9] = { + 1, 0, 1.4746, 1, (-0.11156702 / 0.6780), (-0.38737742 / 0.6780), 1, 1.8814, 0}; + +const float BT601RGBtoYUVMatrix[9] = { + 0.299, 0.587, 0.114, (-0.299 / 1.772), (-0.587 / 1.772), 0.5, 0.5, (-0.587 / 1.402), + (-0.114 / 1.402)}; +const float BT709RGBtoYUVMatrix[9] = {0.2126, + 0.7152, + 0.0722, + (-0.2126 / 1.8556), + (-0.7152 / 1.8556), + 0.5, + 0.5, + (-0.7152 / 1.5748), + (-0.0722 / 1.5748)}; +const float BT2020RGBtoYUVMatrix[9] = {0.2627, + 0.6780, + 0.0593, + (-0.2627 / 1.8814), + (-0.6780 / 1.8814), + 0.5, + 0.5, + (-0.6780 / 1.4746), + (-0.0593 / 1.4746)}; + +int optind_s = 1; +int optopt_s = 0; +char* optarg_s = nullptr; + +int getopt_s(int argc, char* const argv[], char* ostr) { + if (optind_s >= argc) return -1; + + const char* arg = argv[optind_s]; + if (arg[0] != '-' || !arg[1]) { + std::cerr << "invalid option " << arg << std::endl; + return '?'; + } + optopt_s = arg[1]; + char* oindex = strchr(ostr, optopt_s); + if (!oindex) { + std::cerr << "unsupported option " << arg << std::endl; + return '?'; + } + if (oindex[1] != ':') { + optarg_s = nullptr; + return optopt_s; + } + + if (argc > ++optind_s) { + optarg_s = (char*)argv[optind_s++]; + } else { + std::cerr << "option " << arg << " requires an argument" << std::endl; + optarg_s = nullptr; + return '?'; + } + return optopt_s; +} + +// #define PROFILE_ENABLE 1 +#ifdef _WIN32 +class Profiler { + public: + void timerStart() { QueryPerformanceCounter(&mStartingTime); } + + void timerStop() { QueryPerformanceCounter(&mEndingTime); } + + int64_t elapsedTime() { + LARGE_INTEGER frequency; + LARGE_INTEGER elapsedMicroseconds; + QueryPerformanceFrequency(&frequency); + elapsedMicroseconds.QuadPart = mEndingTime.QuadPart - mStartingTime.QuadPart; + return (double)elapsedMicroseconds.QuadPart / (double)frequency.QuadPart * 1000000; + } + + private: + LARGE_INTEGER mStartingTime; + LARGE_INTEGER mEndingTime; +}; +#else +class Profiler { + public: + void timerStart() { gettimeofday(&mStartingTime, nullptr); } + + void timerStop() { gettimeofday(&mEndingTime, nullptr); } + + int64_t elapsedTime() { + struct timeval elapsedMicroseconds; + elapsedMicroseconds.tv_sec = mEndingTime.tv_sec - mStartingTime.tv_sec; + elapsedMicroseconds.tv_usec = mEndingTime.tv_usec - mStartingTime.tv_usec; + return elapsedMicroseconds.tv_sec * 1000000 + elapsedMicroseconds.tv_usec; + } + + private: + struct timeval mStartingTime; + struct timeval mEndingTime; +}; +#endif + +static bool loadFile(const char* filename, void*& result, int length) { + std::ifstream ifd(filename, std::ios::binary | std::ios::ate); + if (ifd.good()) { + int size = ifd.tellg(); + if (size < length) { + std::cerr << "requested to read " << length << " bytes from file : " << filename + << ", file contains only " << size << " bytes" << std::endl; + return false; + } + ifd.seekg(0, std::ios::beg); + result = malloc(length); + if (result == nullptr) { + std::cerr << "failed to allocate memory to store contents of file : " << filename + << std::endl; + return false; + } + ifd.read(static_cast<char*>(result), length); + return true; + } + 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()) { + ofd.write(static_cast<char*>(result), length); + return true; + } + 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) + : mP010File(p010File), + mYuv420File(yuv420File), + mYuv420JpegFile(yuv420JpegFile), + mJpegRFile(nullptr), + mWidth(width), + mHeight(height), + mP010Cg(p010Cg), + mYuv420Cg(yuv420Cg), + mTf(tf), + mQuality(quality), + mOf(of), + mMode(0){}; + + UltraHdrAppInput(const char* jpegRFile, ultrahdr_output_format of = ULTRAHDR_OUTPUT_HDR_HLG) + : mP010File(nullptr), + mYuv420File(nullptr), + mJpegRFile(jpegRFile), + mWidth(0), + mHeight(0), + mP010Cg(ULTRAHDR_COLORGAMUT_UNSPECIFIED), + mYuv420Cg(ULTRAHDR_COLORGAMUT_UNSPECIFIED), + mTf(ULTRAHDR_TF_UNSPECIFIED), + mQuality(100), + mOf(of), + 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); + 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(); + bool fillP010ImageHandle(); + bool convertP010ToRGBImage(); + bool fillYuv420ImageHandle(); + bool fillYuv420JpegImageHandle(); + bool convertYuv420ToRGBImage(); + bool convertRgba8888ToYUV444Image(); + bool convertRgba1010102ToYUV444Image(); + bool encode(); + bool decode(); + void computeRGBHdrPSNR(); + void computeRGBSdrPSNR(); + void computeYUVHdrPSNR(); + void computeYUVSdrPSNR(); + + const char* mP010File; + const char* mYuv420File; + const char* mYuv420JpegFile; + 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 int mQuality; + const ultrahdr_output_format mOf; + 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{}; + 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); +} + +bool UltraHdrAppInput::fillYuv420ImageHandle() { + int yuv420Size = mWidth * mHeight * 1.5; + mRawYuv420Image.width = mWidth; + mRawYuv420Image.height = mHeight; + mRawYuv420Image.colorGamut = mYuv420Cg; + return loadFile(mYuv420File, mRawYuv420Image.data, yuv420Size); +} + +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.data = nullptr; + mYuv420JpegImage.colorGamut = mYuv420Cg; + ifd.close(); + return loadFile(mYuv420JpegFile, mYuv420JpegImage.data, size); + } + return false; +} + +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.data = nullptr; + mJpegImgR.colorGamut = mYuv420Cg; + ifd.close(); + return loadFile(mJpegRFile, mJpegImgR.data, size); + } + return false; +} + +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; + } + + JpegR jpegHdr; + status_t status = JPEGR_UNKNOWN_ERROR; +#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; + } + } +#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); + 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{0, 0, &iccData, &exifData}; + 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; + } +#ifdef PROFILE_ENABLE + 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; + } +#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); +#endif + writeFile("outrgb.raw", mDestImage.data, outSize); + } else { + std::cerr << "Encountered error during getJPEGRInfo call, error code " << status << std::endl; + return false; + } + return true; +} + +bool UltraHdrAppInput::convertP010ToRGBImage() { + const float* coeffs = BT2020YUVtoRGBMatrix; + if (mP010Cg == ULTRAHDR_COLORGAMUT_BT709) { + coeffs = BT709YUVtoRGBMatrix; + } else if (mP010Cg == ULTRAHDR_COLORGAMUT_BT2100) { + coeffs = BT2020YUVtoRGBMatrix; + } else if (mP010Cg == ULTRAHDR_COLORGAMUT_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; + 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); + + y0 = CLIP3(y0, 64.0f, 940.0f); + u0 = CLIP3(u0, 64.0f, 960.0f); + v0 = CLIP3(v0, 64.0f, 960.0f); + + y0 = (y0 - 64.0f) / 876.0f; + u0 = (u0 - 64.0f) / 896.0f - 0.5f; + v0 = (v0 - 64.0f) / 896.0f - 0.5f; + + float r = coeffs[0] * y0 + coeffs[1] * u0 + coeffs[2] * v0; + float g = coeffs[3] * y0 + coeffs[4] * u0 + coeffs[5] * v0; + float b = coeffs[6] * y0 + coeffs[7] * u0 + coeffs[8] * v0; + + r = CLIP3(r * 1023.0f + 0.5f, 0.0f, 1023.0f); + g = CLIP3(g * 1023.0f + 0.5f, 0.0f, 1023.0f); + b = CLIP3(b * 1023.0f + 0.5f, 0.0f, 1023.0f); + + int32_t r0 = int32_t(r); + int32_t g0 = int32_t(g); + int32_t b0 = int32_t(b); + *rgbData = (0x3ff & r0) | ((0x3ff & g0) << 10) | ((0x3ff & b0) << 20) | + (0x3 << 30); // Set alpha to 1.0 + + rgbData++; + } + } + writeFile("inRgba1010102.raw", mRawRgba1010102Image.data, + mRawP010Image.width * mRawP010Image.height * 4); + 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); + + 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); + + y0 /= 255.0f; + u0 /= 255.0f; + v0 /= 255.0f; + + float r = coeffs[0] * y0 + coeffs[1] * u0 + coeffs[2] * v0; + float g = coeffs[3] * y0 + coeffs[4] * u0 + coeffs[5] * v0; + float b = coeffs[6] * y0 + coeffs[7] * u0 + coeffs[8] * v0; + + r = r * 255.0f + 0.5f; + g = g * 255.0f + 0.5f; + b = b * 255.0f + 0.5f; + + r = CLIP3(r, 0.0f, 255.0f); + g = CLIP3(g, 0.0f, 255.0f); + b = CLIP3(b, 0.0f, 255.0f); + + int32_t r0 = int32_t(r); + int32_t g0 = int32_t(g); + int32_t b0 = int32_t(b); + *rgbData = r0 | (g0 << 8) | (b0 << 16) | (255 << 24); // Set alpha to 1.0 + + rgbData++; + } + } + writeFile("inRgba8888.raw", mRawRgba8888Image.data, + mRawYuv420Image.width * mRawYuv420Image.height * 4); + 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); + + 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); + + r0 /= 255.0f; + g0 /= 255.0f; + b0 /= 255.0f; + + float y = coeffs[0] * r0 + coeffs[1] * g0 + coeffs[2] * b0; + float u = coeffs[3] * r0 + coeffs[4] * g0 + coeffs[5] * b0; + float v = coeffs[6] * r0 + coeffs[7] * g0 + coeffs[8] * b0; + + y = y * 255.0f + 0.5f; + u = u * 255.0f + 0.5f + 128.0f; + v = v * 255.0f + 0.5f + 128.0f; + + y = CLIP3(y, 0.0f, 255.0f); + 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); + } + } + writeFile("outyuv444.yuv", mDestYUV444Image.data, + mDestYUV444Image.width * mDestYUV444Image.height * 3); + return true; +} + +bool UltraHdrAppInput::convertRgba1010102ToYUV444Image() { + const float* coeffs = BT2020RGBtoYUVMatrix; + if (mP010Cg == ULTRAHDR_COLORGAMUT_BT709) { + coeffs = BT709RGBtoYUVMatrix; + } else if (mP010Cg == ULTRAHDR_COLORGAMUT_BT2100) { + coeffs = BT2020RGBtoYUVMatrix; + } else if (mP010Cg == ULTRAHDR_COLORGAMUT_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); + + r0 /= 1023.0f; + g0 /= 1023.0f; + b0 /= 1023.0f; + + float y = coeffs[0] * r0 + coeffs[1] * g0 + coeffs[2] * b0; + float u = coeffs[3] * r0 + coeffs[4] * g0 + coeffs[5] * b0; + float v = coeffs[6] * r0 + coeffs[7] * g0 + coeffs[8] * b0; + + y = (y * 876.0f) + 64.0f + 0.5f; + u = (u * 896.0f) + 64.0f + 512.0f + 0.5f; + v = (v * 896.0f) + 64.0f + 512.0f + 0.5f; + + y = CLIP3(y, 64.0f, 940.0f); + 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); + } + } + writeFile("outyuv444.yuv", mDestYUV444Image.data, + mDestYUV444Image.width * mDestYUV444Image.height * 3 * 2); + 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; + return; + } + uint32_t* rgbDataSrc = static_cast<uint32_t*>(mRawRgba1010102Image.data); + uint32_t* rgbDataDst = static_cast<uint32_t*>(mDestImage.data); + 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)) { + 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++) { + int rSrc = *rgbDataSrc & 0x3ff; + int rDst = *rgbDataDst & 0x3ff; + rSqError += (rSrc - rDst) * (rSrc - rDst); + + int gSrc = (*rgbDataSrc >> 10) & 0x3ff; + int gDst = (*rgbDataDst >> 10) & 0x3ff; + gSqError += (gSrc - gDst) * (gSrc - gDst); + + int bSrc = (*rgbDataSrc >> 20) & 0x3ff; + int bDst = (*rgbDataDst >> 20) & 0x3ff; + bSqError += (bSrc - bDst) * (bSrc - bDst); + + rgbDataSrc++; + rgbDataDst++; + } + double meanSquareError = (double)rSqError / (mRawP010Image.width * mRawP010Image.height); + mPsnr[0] = meanSquareError ? 10 * log10((double)1023 * 1023 / meanSquareError) : 100; + + meanSquareError = (double)gSqError / (mRawP010Image.width * mRawP010Image.height); + mPsnr[1] = meanSquareError ? 10 * log10((double)1023 * 1023 / meanSquareError) : 100; + + meanSquareError = (double)bSqError / (mRawP010Image.width * mRawP010Image.height); + mPsnr[2] = meanSquareError ? 10 * log10((double)1023 * 1023 / meanSquareError) : 100; + + std::cout << "psnr r :: " << mPsnr[0] << " psnr g :: " << mPsnr[1] << " psnr b :: " << mPsnr[2] + << std::endl; +} + +void UltraHdrAppInput::computeRGBSdrPSNR() { + if (mOf != ULTRAHDR_OUTPUT_SDR) { + std::cout << "psnr not supported for output format " << mOf << std::endl; + return; + } + uint32_t* rgbDataSrc = static_cast<uint32_t*>(mRawRgba8888Image.data); + uint32_t* rgbDataDst = static_cast<uint32_t*>(mDestImage.data); + 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++) { + int rSrc = *rgbDataSrc & 0xff; + int rDst = *rgbDataDst & 0xff; + rSqError += (rSrc - rDst) * (rSrc - rDst); + + int gSrc = (*rgbDataSrc >> 8) & 0xff; + int gDst = (*rgbDataDst >> 8) & 0xff; + gSqError += (gSrc - gDst) * (gSrc - gDst); + + int bSrc = (*rgbDataSrc >> 16) & 0xff; + int bDst = (*rgbDataDst >> 16) & 0xff; + bSqError += (bSrc - bDst) * (bSrc - bDst); + + rgbDataSrc++; + rgbDataDst++; + } + double meanSquareError = (double)rSqError / (mRawYuv420Image.width * mRawYuv420Image.height); + mPsnr[0] = meanSquareError ? 10 * log10((double)255 * 255 / meanSquareError) : 100; + + meanSquareError = (double)gSqError / (mRawYuv420Image.width * mRawYuv420Image.height); + mPsnr[1] = meanSquareError ? 10 * log10((double)255 * 255 / meanSquareError) : 100; + + meanSquareError = (double)bSqError / (mRawYuv420Image.width * mRawYuv420Image.height); + mPsnr[2] = meanSquareError ? 10 * log10((double)255 * 255 / meanSquareError) : 100; + + std::cout << "psnr r :: " << mPsnr[0] << " psnr g :: " << mPsnr[1] << " psnr b :: " << mPsnr[2] + << std::endl; +} + +void UltraHdrAppInput::computeYUVHdrPSNR() { + if (mOf == ULTRAHDR_OUTPUT_SDR || mOf == ULTRAHDR_OUTPUT_HDR_LINEAR) { + std::cout << "psnr not supported for output format " << mOf << 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) { + 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)) { + 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; + ySrc = CLIP3(ySrc, 64, 940); + int yDst = yDataDst[mDestYUV444Image.width * 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; + 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; + uDst = (uDst + 2) >> 2; + uSqError += (uSrc - uDst) * (uSrc - uDst); + + int vSrc = (vDataSrc[mRawP010Image.width * (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; + vDst = (vDst + 2) >> 2; + vSqError += (vSrc - vDst) * (vSrc - vDst); + } + } + } + + double meanSquareError = (double)ySqError / (mDestYUV444Image.width * mDestYUV444Image.height); + mPsnr[0] = meanSquareError ? 10 * log10((double)1023 * 1023 / meanSquareError) : 100; + + meanSquareError = (double)uSqError / (mDestYUV444Image.width * mDestYUV444Image.height / 4); + mPsnr[1] = meanSquareError ? 10 * log10((double)1023 * 1023 / meanSquareError) : 100; + + meanSquareError = (double)vSqError / (mDestYUV444Image.width * mDestYUV444Image.height / 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] + << std::endl; +} + +void UltraHdrAppInput::computeYUVSdrPSNR() { + if (mOf != ULTRAHDR_OUTPUT_SDR) { + std::cout << "psnr not supported for output format " << mOf << 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* yDataDst = static_cast<uint8_t*>(mDestYUV444Image.data); + uint8_t* uDataDst = yDataDst + (mDestYUV444Image.width * mDestYUV444Image.height); + uint8_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[mRawYuv420Image.width * i + j]; + int yDst = yDataDst[mDestYUV444Image.width * 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]; + 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]; + vDst = (vDst + 2) >> 2; + vSqError += (vSrc - vDst) * (vSrc - vDst); + } + } + } + double meanSquareError = (double)ySqError / (mDestYUV444Image.width * mDestYUV444Image.height); + mPsnr[0] = meanSquareError ? 10 * log10((double)255 * 255 / meanSquareError) : 100; + + meanSquareError = (double)uSqError / (mDestYUV444Image.width * mDestYUV444Image.height / 4); + mPsnr[1] = meanSquareError ? 10 * log10((double)255 * 255 / meanSquareError) : 100; + + meanSquareError = (double)vSqError / (mDestYUV444Image.width * mDestYUV444Image.height / 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] + << std::endl; +} + +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, + " -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, " -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, + " -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" + " for now gain map image quality factor is not configurable. \n"); + fprintf(stderr, " -e compute psnr, optional. [0:yes, 1:no] \n"); + 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"); + 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"); + 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 " + "-h 1080 -q 97 -C 2 -c 1 -t 1 -e 1\n"); + 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"); + 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"); + 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, "\n"); +} + +int main(int argc, char* argv[]) { + char opt_string[] = "p:y:i:w:h:C:c:t:q:o:m:j:e:"; + char *p010_file = nullptr, *yuv420_file = nullptr, *jpegr_file = nullptr, + *yuv420_jpeg_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; + int quality = 100; + ultrahdr_output_format of = ULTRAHDR_OUTPUT_HDR_HLG; + int mode = 0; + int compute_psnr = 0; + int ch; + while ((ch = getopt_s(argc, argv, opt_string)) != -1) { + switch (ch) { + case 'p': + p010_file = optarg_s; + break; + case 'y': + yuv420_file = optarg_s; + break; + case 'i': + yuv420_jpeg_file = optarg_s; + break; + case 'w': + width = atoi(optarg_s); + break; + case 'h': + height = atoi(optarg_s); + break; + case 'C': + p010Cg = static_cast<ultrahdr_color_gamut>(atoi(optarg_s)); + break; + case 'c': + yuv420Cg = static_cast<ultrahdr_color_gamut>(atoi(optarg_s)); + break; + case 't': + tf = static_cast<ultrahdr_transfer_function>(atoi(optarg_s)); + break; + case 'q': + quality = atoi(optarg_s); + break; + case 'o': + of = static_cast<ultrahdr_output_format>(atoi(optarg_s)); + break; + case 'm': + mode = atoi(optarg_s); + break; + case 'j': + jpegr_file = optarg_s; + break; + case 'e': + compute_psnr = atoi(optarg_s); + break; + default: + usage(argv[0]); + return -1; + } + } + if (mode == 0) { + if (width <= 0 || height <= 0 || p010_file == nullptr) { + usage(argv[0]); + return -1; + } + UltraHdrAppInput appInput(p010_file, yuv420_file, yuv420_jpeg_file, width, height, p010Cg, + yuv420Cg, tf, quality, of); + if (!appInput.encode()) return -1; + if (compute_psnr == 1) { + if (!appInput.decode()) return -1; + if (of == ULTRAHDR_OUTPUT_SDR && yuv420_file != nullptr) { + appInput.convertYuv420ToRGBImage(); + appInput.computeRGBSdrPSNR(); + appInput.convertRgba8888ToYUV444Image(); + appInput.computeYUVSdrPSNR(); + } else if (of == ULTRAHDR_OUTPUT_HDR_HLG || of == ULTRAHDR_OUTPUT_HDR_PQ) { + appInput.convertP010ToRGBImage(); + appInput.computeRGBHdrPSNR(); + appInput.convertRgba1010102ToYUV444Image(); + appInput.computeYUVHdrPSNR(); + } + } + } else if (mode == 1) { + if (jpegr_file == nullptr) { + usage(argv[0]); + return -1; + } + UltraHdrAppInput appInput(jpegr_file, of); + if (!appInput.decode()) return -1; + } else { + std::cerr << "unrecognized input mode " << mode << std::endl; + usage(argv[0]); + return -1; + } + + return 0; +} diff --git a/fuzzer/README.md b/fuzzer/README.md index c029fc0..e48d859 100644 --- a/fuzzer/README.md +++ b/fuzzer/README.md @@ -1,73 +1,72 @@ -# Fuzzer for ultrahdr decoder and encoder - -This describes steps to build ultrahdr_dec_fuzzer and ultrahdr_enc_fuzzer. - -## Linux x86/x64 - -### Requirements -- cmake (3.5 or above) -- make -- clang (12.0 or above) - needs to support -fsanitize=fuzzer, -fsanitize=fuzzer-no-link - -### Steps to build -Create a directory inside libultrahdr and change directory -``` - $ cd libultrahdr - $ mkdir build - $ cd build -``` -Build fuzzer with required sanitizers -Note: Using clang and setting -DENABLE_FUZZERS=ON is mandatory to enable fuzzers. -``` - $ cmake .. -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \ - -DCMAKE_BUILD_TYPE=Debug -DENABLE_FUZZERS=ON -DSANITIZE=address,\ - signed-integer-overflow,unsigned-integer-overflow - $ make - ``` - -### Steps to run -Create a directory CORPUS_DIR and copy some elementary ultrahdr files -(for ultrahdr_dec_fuzzer) or yuv files (for ultrahdr_enc_fuzzer) to that directory - -To run the fuzzers -``` -$ ./ultrahdr_dec_fuzzer CORPUS_DIR -$ ./ultrahdr_enc_fuzzer CORPUS_DIR -``` - -## Android - -### Steps to build -Build the fuzzers -``` - $ mm -j$(nproc) ultrahdr_dec_fuzzer - $ mm -j$(nproc) ultrahdr_enc_fuzzer -``` - -### Steps to run -Create a directory CORPUS_DIR and copy some elementary ultrahdr files -(for ultrahdr_dec_fuzzer) or yuv files (for ultrahdr_enc_fuzzer) to that folder -Push this directory to device - -To run ultrahdr_dec_fuzzer on device -``` - $ adb sync data - $ adb shell /data/fuzz/arm64/ultrahdr_dec_fuzzer/ultrahdr_dec_fuzzer CORPUS_DIR -``` - -To run ultrahdr_enc_fuzzer on device -``` - $ adb sync data - $ adb shell /data/fuzz/arm64/ultrahdr_enc_fuzzer/ultrahdr_enc_fuzzer CORPUS_DIR -``` - -To run ultrahdr_dec_fuzzer on host -``` - $ $ANDROID_HOST_OUT/fuzz/x86_64/ultrahdr_dec_fuzzer/ultrahdr_dec_fuzzer CORPUS_DIR -``` - -To run ultrahdr_enc_fuzzer on host -``` - $ $ANDROID_HOST_OUT/fuzz/x86_64/ultrahdr_enc_fuzzer/ultrahdr_enc_fuzzer CORPUS_DIR -``` +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) + +### Building Commands + + mkdir {build_directory} + cd {build_directory} + 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 + +**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. + +To enable ASan, + + cmake ../ -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \ + -DUHDR_BUILD_FUZZERS=1 -DUHDR_SANITIZE_OPTIONS=address + make + +To enable MSan, + + cmake ../ -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \ + -DUHDR_BUILD_FUZZERS=1 -DUHDR_SANITIZE_OPTIONS=memory + make + +To enable TSan, + + cmake ../ -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \ + -DUHDR_BUILD_FUZZERS=1 -DUHDR_SANITIZE_OPTIONS=thread + make + +To enable UBSan, + + cmake ../ -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \ + -DUHDR_BUILD_FUZZERS=1 -DUHDR_SANITIZE_OPTIONS=undefined + make + +UBSan can be grouped with ASan, MSan or TSan. + +For example, to enable ASan and UBSan, + + cmake ../ -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \ + -DUHDR_BUILD_FUZZERS=1 -DUHDR_SANITIZE_OPTIONS=address,undefined + make + +### Running + +To run the fuzzer(s), first create a corpus directory that holds the initial +"seed" sample inputs. For decoder fuzzer, ultrahdr jpeg images can be used and +for encoder fuzzer, sample yuv files can be used. + +Then run the fuzzers on the corpus directory. + + mkdir CORPUS_DIR + cp seeds/* CORPUS_DIR + ./ultrahdr_dec_fuzzer CORPUS_DIR + ./ultrahdr_enc_fuzzer CORPUS_DIR diff --git a/fuzzer/ossfuzz.sh b/fuzzer/ossfuzz.sh index b88aae5..262d629 100755 --- a/fuzzer/ossfuzz.sh +++ b/fuzzer/ossfuzz.sh @@ -23,7 +23,7 @@ rm -rf ${build_dir} mkdir -p ${build_dir} pushd ${build_dir} -cmake $SRC/libultrahdr -DENABLE_FUZZERS=ON +cmake $SRC/libultrahdr -DUHDR_BUILD_FUZZERS=1 make -j$(nproc) ultrahdr_dec_fuzzer ultrahdr_enc_fuzzer cp ${build_dir}/ultrahdr_dec_fuzzer $OUT/ cp ${build_dir}/ultrahdr_enc_fuzzer $OUT/ diff --git a/fuzzer/ultrahdr_dec_fuzzer.cpp b/fuzzer/ultrahdr_dec_fuzzer.cpp index 9be5e87..0e9c5d3 100644 --- a/fuzzer/ultrahdr_dec_fuzzer.cpp +++ b/fuzzer/ultrahdr_dec_fuzzer.cpp @@ -18,7 +18,7 @@ #include <iostream> #include <memory> -#include "ultrahdr/jpegr.h" +#include "jpegr.h" using namespace ultrahdr; @@ -27,46 +27,46 @@ const int kOfMin = ULTRAHDR_OUTPUT_UNSPECIFIED + 1; const int kOfMax = ULTRAHDR_OUTPUT_MAX; class UltraHdrDecFuzzer { -public: - UltraHdrDecFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){}; - void process(); + public: + UltraHdrDecFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){}; + void process(); -private: - FuzzedDataProvider mFdp; + private: + FuzzedDataProvider mFdp; }; void UltraHdrDecFuzzer::process() { - // hdr_of - auto of = static_cast<ultrahdr_output_format>(mFdp.ConsumeIntegralInRange<int>(kOfMin, kOfMax)); - auto buffer = mFdp.ConsumeRemainingBytes<uint8_t>(); - jpegr_compressed_struct jpegImgR{buffer.data(), (int)buffer.size(), (int)buffer.size(), - ULTRAHDR_COLORGAMUT_UNSPECIFIED}; + // hdr_of + auto of = static_cast<ultrahdr_output_format>(mFdp.ConsumeIntegralInRange<int>(kOfMin, kOfMax)); + auto buffer = mFdp.ConsumeRemainingBytes<uint8_t>(); + jpegr_compressed_struct jpegImgR{buffer.data(), (int)buffer.size(), (int)buffer.size(), + ULTRAHDR_COLORGAMUT_UNSPECIFIED}; - std::vector<uint8_t> iccData(0); - std::vector<uint8_t> exifData(0); - jpegr_info_struct info{0, 0, &iccData, &exifData}; - JpegR jpegHdr; - (void)jpegHdr.getJPEGRInfo(&jpegImgR, &info); + std::vector<uint8_t> iccData(0); + std::vector<uint8_t> exifData(0); + jpegr_info_struct info{0, 0, &iccData, &exifData}; + JpegR jpegHdr; + (void)jpegHdr.getJPEGRInfo(&jpegImgR, &info); //#define DUMP_PARAM #ifdef DUMP_PARAM - std::cout << "input buffer size " << jpegImgR.length << std::endl; - std::cout << "image dimensions " << info.width << " x " << info.width << std::endl; + std::cout << "input buffer size " << jpegImgR.length << std::endl; + std::cout << "image dimensions " << info.width << " x " << info.width << std::endl; #endif - if (info.width > kMaxWidth || info.height > kMaxHeight) return; - size_t outSize = info.width * info.height * ((of == ULTRAHDR_OUTPUT_HDR_LINEAR) ? 8 : 4); - jpegr_uncompressed_struct decodedJpegR; - 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); + if (info.width > kMaxWidth || info.height > kMaxHeight) return; + size_t outSize = info.width * info.height * ((of == ULTRAHDR_OUTPUT_HDR_LINEAR) ? 8 : 4); + jpegr_uncompressed_struct decodedJpegR; + 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); } extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { - UltraHdrDecFuzzer fuzzHandle(data, size); - fuzzHandle.process(); - return 0; + UltraHdrDecFuzzer fuzzHandle(data, size); + fuzzHandle.process(); + return 0; } diff --git a/fuzzer/ultrahdr_enc_fuzzer.cpp b/fuzzer/ultrahdr_enc_fuzzer.cpp index d981934..db6021e 100644 --- a/fuzzer/ultrahdr_enc_fuzzer.cpp +++ b/fuzzer/ultrahdr_enc_fuzzer.cpp @@ -20,9 +20,9 @@ #include <memory> #include <random> -#include "ultrahdr/ultrahdrcommon.h" -#include "ultrahdr/gainmapmath.h" -#include "ultrahdr/jpegr.h" +#include "ultrahdrcommon.h" +#include "gainmapmath.h" +#include "jpegr.h" using namespace ultrahdr; @@ -43,287 +43,281 @@ const int kQfMin = 0; const int kQfMax = 100; class UltraHdrEncFuzzer { -public: - UltraHdrEncFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){}; - void process(); - void fillP010Buffer(uint16_t* data, int width, int height, int stride); - void fill420Buffer(uint8_t* data, int width, int height, int stride); + public: + UltraHdrEncFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){}; + void process(); + void fillP010Buffer(uint16_t* data, int width, int height, int stride); + void fill420Buffer(uint8_t* data, int width, int height, int stride); -private: - FuzzedDataProvider mFdp; + private: + FuzzedDataProvider mFdp; }; void UltraHdrEncFuzzer::fillP010Buffer(uint16_t* data, int width, int height, int stride) { - uint16_t* tmp = data; - std::vector<uint16_t> buffer(16); - for (int i = 0; i < buffer.size(); i++) { - buffer[i] = (mFdp.ConsumeIntegralInRange<int>(0, (1 << 10) - 1)) << 6; - } - for (int j = 0; j < height; j++) { - for (int i = 0; i < width; i += buffer.size()) { - memcpy(tmp + i, buffer.data(), - std::min((int)buffer.size(), (width - i)) * sizeof(*data)); - std::shuffle(buffer.begin(), buffer.end(), - std::default_random_engine(std::random_device{}())); - } - tmp += stride; + uint16_t* tmp = data; + std::vector<uint16_t> buffer(16); + for (int i = 0; i < buffer.size(); i++) { + buffer[i] = (mFdp.ConsumeIntegralInRange<int>(0, (1 << 10) - 1)) << 6; + } + for (int j = 0; j < height; j++) { + for (int i = 0; i < width; i += buffer.size()) { + memcpy(tmp + i, buffer.data(), std::min((int)buffer.size(), (width - i)) * sizeof(*data)); + std::shuffle(buffer.begin(), buffer.end(), + std::default_random_engine(std::random_device{}())); } + tmp += stride; + } } void UltraHdrEncFuzzer::fill420Buffer(uint8_t* data, int width, int height, int stride) { - uint8_t* tmp = data; - std::vector<uint8_t> buffer(16); - mFdp.ConsumeData(buffer.data(), buffer.size()); - for (int j = 0; j < height; j++) { - for (int i = 0; i < width; i += buffer.size()) { - memcpy(tmp + i, buffer.data(), - std::min((int)buffer.size(), (width - i)) * sizeof(*data)); - std::shuffle(buffer.begin(), buffer.end(), - std::default_random_engine(std::random_device{}())); - } - tmp += stride; + uint8_t* tmp = data; + std::vector<uint8_t> buffer(16); + mFdp.ConsumeData(buffer.data(), buffer.size()); + for (int j = 0; j < height; j++) { + for (int i = 0; i < width; i += buffer.size()) { + memcpy(tmp + i, buffer.data(), std::min((int)buffer.size(), (width - i)) * sizeof(*data)); + std::shuffle(buffer.begin(), buffer.end(), + std::default_random_engine(std::random_device{}())); } + tmp += stride; + } } void UltraHdrEncFuzzer::process() { - while (mFdp.remaining_bytes()) { - struct jpegr_uncompressed_struct p010Img {}; - struct jpegr_uncompressed_struct yuv420Img {}; - struct jpegr_uncompressed_struct grayImg {}; - struct jpegr_compressed_struct jpegImgR {}; - struct jpegr_compressed_struct jpegImg {}; - struct jpegr_compressed_struct jpegGainMap {}; + while (mFdp.remaining_bytes()) { + struct jpegr_uncompressed_struct p010Img {}; + struct jpegr_uncompressed_struct yuv420Img {}; + struct jpegr_uncompressed_struct grayImg {}; + struct jpegr_compressed_struct jpegImgR {}; + struct jpegr_compressed_struct jpegImg {}; + struct jpegr_compressed_struct jpegGainMap {}; - // which encode api to select - int muxSwitch = mFdp.ConsumeIntegralInRange<int>(0, 4); + // which encode api to select + int muxSwitch = mFdp.ConsumeIntegralInRange<int>(0, 4); - // quality factor - int quality = mFdp.ConsumeIntegralInRange<int>(kQfMin, kQfMax); + // quality factor + int quality = mFdp.ConsumeIntegralInRange<int>(kQfMin, kQfMax); - // hdr_tf - auto tf = static_cast<ultrahdr_transfer_function>( - mFdp.ConsumeIntegralInRange<int>(kTfMin, kTfMax)); + // hdr_tf + auto tf = + static_cast<ultrahdr_transfer_function>(mFdp.ConsumeIntegralInRange<int>(kTfMin, kTfMax)); - // p010 Cg - auto p010Cg = - static_cast<ultrahdr_color_gamut>(mFdp.ConsumeIntegralInRange<int>(kCgMin, kCgMax)); + // p010 Cg + auto p010Cg = + static_cast<ultrahdr_color_gamut>(mFdp.ConsumeIntegralInRange<int>(kCgMin, kCgMax)); - // 420 Cg - auto yuv420Cg = - static_cast<ultrahdr_color_gamut>(mFdp.ConsumeIntegralInRange<int>(kCgMin, kCgMax)); + // 420 Cg + auto yuv420Cg = + static_cast<ultrahdr_color_gamut>(mFdp.ConsumeIntegralInRange<int>(kCgMin, kCgMax)); - // hdr_of - auto of = static_cast<ultrahdr_output_format>( - mFdp.ConsumeIntegralInRange<int>(kOfMin, kOfMax)); + // hdr_of + auto of = static_cast<ultrahdr_output_format>(mFdp.ConsumeIntegralInRange<int>(kOfMin, kOfMax)); - int width = mFdp.ConsumeIntegralInRange<int>(kMinWidth, kMaxWidth); - width = (width >> 1) << 1; + int width = mFdp.ConsumeIntegralInRange<int>(kMinWidth, kMaxWidth); + width = (width >> 1) << 1; - int height = mFdp.ConsumeIntegralInRange<int>(kMinHeight, kMaxHeight); - height = (height >> 1) << 1; + int height = mFdp.ConsumeIntegralInRange<int>(kMinHeight, kMaxHeight); + height = (height >> 1) << 1; - std::unique_ptr<uint16_t[]> bufferYHdr = nullptr; - std::unique_ptr<uint16_t[]> bufferUVHdr = nullptr; - std::unique_ptr<uint8_t[]> bufferYSdr = nullptr; - std::unique_ptr<uint8_t[]> bufferUVSdr = nullptr; - std::unique_ptr<uint8_t[]> grayImgRaw = nullptr; - if (muxSwitch != 4) { - // init p010 image - bool isUVContiguous = mFdp.ConsumeBool(); - bool hasYStride = mFdp.ConsumeBool(); - int yStride = hasYStride ? mFdp.ConsumeIntegralInRange<int>(width, width + 128) : width; - p010Img.width = width; - p010Img.height = height; - p010Img.colorGamut = p010Cg; - p010Img.luma_stride = hasYStride ? yStride : 0; - if (isUVContiguous) { - size_t p010Size = yStride * height * 3 / 2; - bufferYHdr = std::make_unique<uint16_t[]>(p010Size); - p010Img.data = bufferYHdr.get(); - p010Img.chroma_data = nullptr; - p010Img.chroma_stride = 0; - fillP010Buffer(bufferYHdr.get(), width, height, yStride); - fillP010Buffer(bufferYHdr.get() + yStride * height, width, height / 2, yStride); - } else { - int uvStride = mFdp.ConsumeIntegralInRange<int>(width, width + 128); - size_t p010YSize = yStride * height; - bufferYHdr = std::make_unique<uint16_t[]>(p010YSize); - p010Img.data = bufferYHdr.get(); - fillP010Buffer(bufferYHdr.get(), width, height, yStride); - size_t p010UVSize = uvStride * p010Img.height / 2; - bufferUVHdr = std::make_unique<uint16_t[]>(p010UVSize); - p010Img.chroma_data = bufferUVHdr.get(); - p010Img.chroma_stride = uvStride; - fillP010Buffer(bufferUVHdr.get(), width, height / 2, uvStride); - } - } else { - size_t map_width = width / kMapDimensionScaleFactor; - size_t map_height = height / kMapDimensionScaleFactor; - // init 400 image - grayImg.width = map_width; - grayImg.height = map_height; - grayImg.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED; + std::unique_ptr<uint16_t[]> bufferYHdr = nullptr; + std::unique_ptr<uint16_t[]> bufferUVHdr = nullptr; + std::unique_ptr<uint8_t[]> bufferYSdr = nullptr; + std::unique_ptr<uint8_t[]> bufferUVSdr = nullptr; + std::unique_ptr<uint8_t[]> grayImgRaw = nullptr; + if (muxSwitch != 4) { + // init p010 image + bool isUVContiguous = mFdp.ConsumeBool(); + bool hasYStride = mFdp.ConsumeBool(); + int yStride = hasYStride ? mFdp.ConsumeIntegralInRange<int>(width, width + 128) : width; + p010Img.width = width; + p010Img.height = height; + p010Img.colorGamut = p010Cg; + p010Img.luma_stride = hasYStride ? yStride : 0; + if (isUVContiguous) { + size_t p010Size = yStride * height * 3 / 2; + bufferYHdr = std::make_unique<uint16_t[]>(p010Size); + p010Img.data = bufferYHdr.get(); + p010Img.chroma_data = nullptr; + p010Img.chroma_stride = 0; + fillP010Buffer(bufferYHdr.get(), width, height, yStride); + fillP010Buffer(bufferYHdr.get() + yStride * height, width, height / 2, yStride); + } else { + int uvStride = mFdp.ConsumeIntegralInRange<int>(width, width + 128); + size_t p010YSize = yStride * height; + bufferYHdr = std::make_unique<uint16_t[]>(p010YSize); + p010Img.data = bufferYHdr.get(); + fillP010Buffer(bufferYHdr.get(), width, height, yStride); + size_t p010UVSize = uvStride * p010Img.height / 2; + bufferUVHdr = std::make_unique<uint16_t[]>(p010UVSize); + p010Img.chroma_data = bufferUVHdr.get(); + p010Img.chroma_stride = uvStride; + fillP010Buffer(bufferUVHdr.get(), width, height / 2, uvStride); + } + } else { + size_t map_width = width / kMapDimensionScaleFactor; + size_t map_height = height / kMapDimensionScaleFactor; + // init 400 image + grayImg.width = map_width; + grayImg.height = map_height; + grayImg.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED; - const size_t graySize = map_width * map_height; - grayImgRaw = std::make_unique<uint8_t[]>(graySize); - grayImg.data = grayImgRaw.get(); - fill420Buffer(grayImgRaw.get(), map_width, map_height, map_width); - grayImg.chroma_data = nullptr; - grayImg.luma_stride = 0; - grayImg.chroma_stride = 0; - } + const size_t graySize = map_width * map_height; + grayImgRaw = std::make_unique<uint8_t[]>(graySize); + grayImg.data = grayImgRaw.get(); + fill420Buffer(grayImgRaw.get(), map_width, map_height, map_width); + grayImg.chroma_data = nullptr; + grayImg.luma_stride = 0; + grayImg.chroma_stride = 0; + } - if (muxSwitch > 0) { - // init 420 image - bool isUVContiguous = mFdp.ConsumeBool(); - bool hasYStride = mFdp.ConsumeBool(); - int yStride = hasYStride ? mFdp.ConsumeIntegralInRange<int>(width, width + 128) : width; - yuv420Img.width = width; - yuv420Img.height = height; - yuv420Img.colorGamut = yuv420Cg; - yuv420Img.luma_stride = hasYStride ? yStride : 0; - if (isUVContiguous) { - size_t yuv420Size = yStride * height * 3 / 2; - bufferYSdr = std::make_unique<uint8_t[]>(yuv420Size); - yuv420Img.data = bufferYSdr.get(); - yuv420Img.chroma_data = nullptr; - yuv420Img.chroma_stride = 0; - fill420Buffer(bufferYSdr.get(), width, height, yStride); - fill420Buffer(bufferYSdr.get() + yStride * height, width / 2, height / 2, - yStride / 2); - fill420Buffer(bufferYSdr.get() + yStride * height * 5 / 4, width / 2, height / 2, - yStride / 2); - } else { - int uvStride = mFdp.ConsumeIntegralInRange<int>(width / 2, width / 2 + 128); - size_t yuv420YSize = yStride * height; - bufferYSdr = std::make_unique<uint8_t[]>(yuv420YSize); - yuv420Img.data = bufferYSdr.get(); - fill420Buffer(bufferYSdr.get(), width, height, yStride); - size_t yuv420UVSize = uvStride * yuv420Img.height / 2 * 2; - bufferUVSdr = std::make_unique<uint8_t[]>(yuv420UVSize); - yuv420Img.chroma_data = bufferUVSdr.get(); - yuv420Img.chroma_stride = uvStride; - fill420Buffer(bufferUVSdr.get(), width / 2, height / 2, uvStride); - fill420Buffer(bufferUVSdr.get() + uvStride * height / 2, width / 2, height / 2, - uvStride); - } - } + if (muxSwitch > 0) { + // init 420 image + bool isUVContiguous = mFdp.ConsumeBool(); + bool hasYStride = mFdp.ConsumeBool(); + int yStride = hasYStride ? mFdp.ConsumeIntegralInRange<int>(width, width + 128) : width; + yuv420Img.width = width; + yuv420Img.height = height; + yuv420Img.colorGamut = yuv420Cg; + yuv420Img.luma_stride = hasYStride ? yStride : 0; + if (isUVContiguous) { + size_t yuv420Size = yStride * height * 3 / 2; + bufferYSdr = std::make_unique<uint8_t[]>(yuv420Size); + yuv420Img.data = bufferYSdr.get(); + yuv420Img.chroma_data = nullptr; + yuv420Img.chroma_stride = 0; + fill420Buffer(bufferYSdr.get(), width, height, yStride); + fill420Buffer(bufferYSdr.get() + yStride * height, width / 2, height / 2, yStride / 2); + fill420Buffer(bufferYSdr.get() + yStride * height * 5 / 4, width / 2, height / 2, + yStride / 2); + } else { + int uvStride = mFdp.ConsumeIntegralInRange<int>(width / 2, width / 2 + 128); + size_t yuv420YSize = yStride * height; + bufferYSdr = std::make_unique<uint8_t[]>(yuv420YSize); + yuv420Img.data = bufferYSdr.get(); + fill420Buffer(bufferYSdr.get(), width, height, yStride); + size_t yuv420UVSize = uvStride * yuv420Img.height / 2 * 2; + bufferUVSdr = std::make_unique<uint8_t[]>(yuv420UVSize); + yuv420Img.chroma_data = bufferUVSdr.get(); + yuv420Img.chroma_stride = uvStride; + fill420Buffer(bufferUVSdr.get(), width / 2, height / 2, uvStride); + fill420Buffer(bufferUVSdr.get() + uvStride * height / 2, width / 2, height / 2, uvStride); + } + } - // dest - // 2 * p010 size as input data is random, DCT compression might not behave as expected - jpegImgR.maxLength = std::max(8 * 1024 /* min size 8kb */, width * height * 3 * 2); - auto jpegImgRaw = std::make_unique<uint8_t[]>(jpegImgR.maxLength); - jpegImgR.data = jpegImgRaw.get(); + // dest + // 2 * p010 size as input data is random, DCT compression might not behave as expected + jpegImgR.maxLength = std::max(8 * 1024 /* min size 8kb */, width * height * 3 * 2); + auto jpegImgRaw = std::make_unique<uint8_t[]>(jpegImgR.maxLength); + jpegImgR.data = jpegImgRaw.get(); //#define DUMP_PARAM #ifdef DUMP_PARAM - std::cout << "Api Select " << muxSwitch << std::endl; - std::cout << "image dimensions " << width << " x " << height << std::endl; - std::cout << "p010 color gamut " << p010Img.colorGamut << std::endl; - std::cout << "p010 luma stride " << p010Img.luma_stride << std::endl; - std::cout << "p010 chroma stride " << p010Img.chroma_stride << std::endl; - std::cout << "420 color gamut " << yuv420Img.colorGamut << std::endl; - std::cout << "420 luma stride " << yuv420Img.luma_stride << std::endl; - std::cout << "420 chroma stride " << yuv420Img.chroma_stride << std::endl; - std::cout << "quality factor " << quality << std::endl; + std::cout << "Api Select " << muxSwitch << std::endl; + std::cout << "image dimensions " << width << " x " << height << std::endl; + std::cout << "p010 color gamut " << p010Img.colorGamut << std::endl; + std::cout << "p010 luma stride " << p010Img.luma_stride << std::endl; + std::cout << "p010 chroma stride " << p010Img.chroma_stride << std::endl; + std::cout << "420 color gamut " << yuv420Img.colorGamut << std::endl; + std::cout << "420 luma stride " << yuv420Img.luma_stride << std::endl; + std::cout << "420 chroma stride " << yuv420Img.chroma_stride << std::endl; + std::cout << "quality factor " << quality << std::endl; #endif - JpegR jpegHdr; - status_t status = UNKNOWN_ERROR; - if (muxSwitch == 0) { // api 0 - jpegImgR.length = 0; - status = jpegHdr.encodeJPEGR(&p010Img, tf, &jpegImgR, quality, nullptr); - } else if (muxSwitch == 1) { // api 1 - jpegImgR.length = 0; - status = jpegHdr.encodeJPEGR(&p010Img, &yuv420Img, tf, &jpegImgR, quality, nullptr); - } else { - // compressed img - JpegEncoderHelper encoder; - struct jpegr_uncompressed_struct yuv420ImgCopy = yuv420Img; - if (yuv420ImgCopy.luma_stride == 0) yuv420ImgCopy.luma_stride = yuv420Img.width; - if (!yuv420ImgCopy.chroma_data) { - uint8_t* data = reinterpret_cast<uint8_t*>(yuv420Img.data); - yuv420ImgCopy.chroma_data = data + yuv420Img.luma_stride * yuv420Img.height; - yuv420ImgCopy.chroma_stride = yuv420Img.luma_stride >> 1; - } + JpegR jpegHdr; + status_t status = JPEGR_UNKNOWN_ERROR; + if (muxSwitch == 0) { // api 0 + jpegImgR.length = 0; + status = jpegHdr.encodeJPEGR(&p010Img, tf, &jpegImgR, quality, nullptr); + } else if (muxSwitch == 1) { // api 1 + jpegImgR.length = 0; + status = jpegHdr.encodeJPEGR(&p010Img, &yuv420Img, tf, &jpegImgR, quality, nullptr); + } else { + // compressed img + JpegEncoderHelper encoder; + struct jpegr_uncompressed_struct yuv420ImgCopy = yuv420Img; + if (yuv420ImgCopy.luma_stride == 0) yuv420ImgCopy.luma_stride = yuv420Img.width; + if (!yuv420ImgCopy.chroma_data) { + uint8_t* data = reinterpret_cast<uint8_t*>(yuv420Img.data); + yuv420ImgCopy.chroma_data = data + yuv420Img.luma_stride * yuv420Img.height; + yuv420ImgCopy.chroma_stride = yuv420Img.luma_stride >> 1; + } - if (encoder.compressImage(reinterpret_cast<uint8_t*>(yuv420ImgCopy.data), - reinterpret_cast<uint8_t*>(yuv420ImgCopy.chroma_data), - yuv420ImgCopy.width, yuv420ImgCopy.height, - yuv420ImgCopy.luma_stride, yuv420ImgCopy.chroma_stride, - quality, nullptr, 0)) { - jpegImg.length = encoder.getCompressedImageSize(); - jpegImg.maxLength = jpegImg.length; - jpegImg.data = encoder.getCompressedImagePtr(); - jpegImg.colorGamut = yuv420Cg; + if (encoder.compressImage(reinterpret_cast<uint8_t*>(yuv420ImgCopy.data), + reinterpret_cast<uint8_t*>(yuv420ImgCopy.chroma_data), + yuv420ImgCopy.width, yuv420ImgCopy.height, + yuv420ImgCopy.luma_stride, yuv420ImgCopy.chroma_stride, quality, + nullptr, 0)) { + jpegImg.length = encoder.getCompressedImageSize(); + jpegImg.maxLength = jpegImg.length; + jpegImg.data = encoder.getCompressedImagePtr(); + jpegImg.colorGamut = yuv420Cg; - if (muxSwitch == 2) { // api 2 - jpegImgR.length = 0; - status = jpegHdr.encodeJPEGR(&p010Img, &yuv420Img, &jpegImg, tf, &jpegImgR); - } else if (muxSwitch == 3) { // api 3 - jpegImgR.length = 0; - status = jpegHdr.encodeJPEGR(&p010Img, &jpegImg, tf, &jpegImgR); - } else if (muxSwitch == 4) { // api 4 - jpegImgR.length = 0; - JpegEncoderHelper gainMapEncoder; - if (gainMapEncoder.compressImage(reinterpret_cast<uint8_t*>(grayImg.data), - nullptr, grayImg.width, grayImg.height, - grayImg.width, 0, quality, nullptr, 0)) { - jpegGainMap.length = gainMapEncoder.getCompressedImageSize(); - jpegGainMap.maxLength = jpegImg.length; - jpegGainMap.data = gainMapEncoder.getCompressedImagePtr(); - jpegGainMap.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED; - ultrahdr_metadata_struct metadata; - metadata.version = kJpegrVersion; - if (tf == ULTRAHDR_TF_HLG) { - metadata.maxContentBoost = kHlgMaxNits / kSdrWhiteNits; - } else if (tf == ULTRAHDR_TF_PQ) { - metadata.maxContentBoost = kPqMaxNits / kSdrWhiteNits; - } else { - metadata.maxContentBoost = 1.0f; - } - metadata.minContentBoost = 1.0f; - metadata.gamma = 1.0f; - metadata.offsetSdr = 0.0f; - metadata.offsetHdr = 0.0f; - metadata.hdrCapacityMin = 1.0f; - metadata.hdrCapacityMax = metadata.maxContentBoost; - status = jpegHdr.encodeJPEGR(&jpegImg, &jpegGainMap, &metadata, &jpegImgR); - } - } - } - } - if (status == OK) { - std::vector<uint8_t> iccData(0); - std::vector<uint8_t> exifData(0); - jpegr_info_struct info{0, 0, &iccData, &exifData}; - status = jpegHdr.getJPEGRInfo(&jpegImgR, &info); - if (status == OK) { - size_t outSize = - info.width * info.height * ((of == ULTRAHDR_OUTPUT_HDR_LINEAR) ? 8 : 4); - jpegr_uncompressed_struct decodedJpegR; - 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); - if (status != OK) { - ALOGE("encountered error during decoding %d", status); - } - if (decodedGainMap.data) free(decodedGainMap.data); + if (muxSwitch == 2) { // api 2 + jpegImgR.length = 0; + status = jpegHdr.encodeJPEGR(&p010Img, &yuv420Img, &jpegImg, tf, &jpegImgR); + } else if (muxSwitch == 3) { // api 3 + jpegImgR.length = 0; + status = jpegHdr.encodeJPEGR(&p010Img, &jpegImg, tf, &jpegImgR); + } else if (muxSwitch == 4) { // api 4 + jpegImgR.length = 0; + JpegEncoderHelper gainMapEncoder; + if (gainMapEncoder.compressImage(reinterpret_cast<uint8_t*>(grayImg.data), nullptr, + grayImg.width, grayImg.height, grayImg.width, 0, quality, + nullptr, 0)) { + jpegGainMap.length = gainMapEncoder.getCompressedImageSize(); + jpegGainMap.maxLength = jpegImg.length; + jpegGainMap.data = gainMapEncoder.getCompressedImagePtr(); + jpegGainMap.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED; + ultrahdr_metadata_struct metadata; + metadata.version = kJpegrVersion; + if (tf == ULTRAHDR_TF_HLG) { + metadata.maxContentBoost = kHlgMaxNits / kSdrWhiteNits; + } else if (tf == ULTRAHDR_TF_PQ) { + metadata.maxContentBoost = kPqMaxNits / kSdrWhiteNits; } else { - ALOGE("encountered error during get jpeg info %d", status); + metadata.maxContentBoost = 1.0f; } - } else { - ALOGE("encountered error during encoding %d", status); + metadata.minContentBoost = 1.0f; + metadata.gamma = 1.0f; + metadata.offsetSdr = 0.0f; + metadata.offsetHdr = 0.0f; + metadata.hdrCapacityMin = 1.0f; + metadata.hdrCapacityMax = metadata.maxContentBoost; + status = jpegHdr.encodeJPEGR(&jpegImg, &jpegGainMap, &metadata, &jpegImgR); + } + } + } + } + if (status == JPEGR_NO_ERROR) { + std::vector<uint8_t> iccData(0); + std::vector<uint8_t> exifData(0); + jpegr_info_struct info{0, 0, &iccData, &exifData}; + status = jpegHdr.getJPEGRInfo(&jpegImgR, &info); + if (status == JPEGR_NO_ERROR) { + size_t outSize = info.width * info.height * ((of == ULTRAHDR_OUTPUT_HDR_LINEAR) ? 8 : 4); + jpegr_uncompressed_struct decodedJpegR; + 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); + 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); + } + } else { + ALOGE("encountered error during encoding %d", status); } + } } extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { - UltraHdrEncFuzzer fuzzHandle(data, size); - fuzzHandle.process(); - return 0; + UltraHdrEncFuzzer fuzzHandle(data, size); + fuzzHandle.process(); + return 0; } diff --git a/icc.cpp b/icc.cpp deleted file mode 100644 index 6de711f..0000000 --- a/icc.cpp +++ /dev/null @@ -1,688 +0,0 @@ -/* - * Copyright 2022 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 "ultrahdr/ultrahdrcommon.h" -#include "ultrahdr/icc.h" - -namespace ultrahdr { - -static void Matrix3x3_apply(const Matrix3x3* m, float* x) { - float y0 = x[0] * m->vals[0][0] + x[1] * m->vals[0][1] + x[2] * m->vals[0][2]; - float y1 = x[0] * m->vals[1][0] + x[1] * m->vals[1][1] + x[2] * m->vals[1][2]; - float y2 = x[0] * m->vals[2][0] + x[1] * m->vals[2][1] + x[2] * m->vals[2][2]; - x[0] = y0; - x[1] = y1; - x[2] = y2; -} - -bool Matrix3x3_invert(const Matrix3x3* src, Matrix3x3* dst) { - double a00 = src->vals[0][0], - a01 = src->vals[1][0], - a02 = src->vals[2][0], - a10 = src->vals[0][1], - a11 = src->vals[1][1], - a12 = src->vals[2][1], - a20 = src->vals[0][2], - a21 = src->vals[1][2], - a22 = src->vals[2][2]; - - double b0 = a00*a11 - a01*a10, - b1 = a00*a12 - a02*a10, - b2 = a01*a12 - a02*a11, - b3 = a20, - b4 = a21, - b5 = a22; - - double determinant = b0*b5 - - b1*b4 - + b2*b3; - - if (determinant == 0) { - return false; - } - - double invdet = 1.0 / determinant; - if (invdet > +FLT_MAX || invdet < -FLT_MAX || !isfinitef_((float)invdet)) { - return false; - } - - b0 *= invdet; - b1 *= invdet; - b2 *= invdet; - b3 *= invdet; - b4 *= invdet; - b5 *= invdet; - - dst->vals[0][0] = (float)( a11*b5 - a12*b4 ); - dst->vals[1][0] = (float)( a02*b4 - a01*b5 ); - dst->vals[2][0] = (float)( + b2 ); - dst->vals[0][1] = (float)( a12*b3 - a10*b5 ); - dst->vals[1][1] = (float)( a00*b5 - a02*b3 ); - dst->vals[2][1] = (float)( - b1 ); - dst->vals[0][2] = (float)( a10*b4 - a11*b3 ); - dst->vals[1][2] = (float)( a01*b3 - a00*b4 ); - dst->vals[2][2] = (float)( + b0 ); - - for (int r = 0; r < 3; ++r) - for (int c = 0; c < 3; ++c) { - if (!isfinitef_(dst->vals[r][c])) { - return false; - } - } - return true; -} - -static Matrix3x3 Matrix3x3_concat(const Matrix3x3* A, const Matrix3x3* B) { - Matrix3x3 m = { { { 0,0,0 },{ 0,0,0 },{ 0,0,0 } } }; - for (int r = 0; r < 3; r++) - for (int c = 0; c < 3; c++) { - m.vals[r][c] = A->vals[r][0] * B->vals[0][c] - + A->vals[r][1] * B->vals[1][c] - + A->vals[r][2] * B->vals[2][c]; - } - return m; -} - -static void float_XYZD50_to_grid16_lab(const float* xyz_float, uint8_t* grid16_lab) { - float v[3] = { - xyz_float[0] / kD50_x, - xyz_float[1] / kD50_y, - xyz_float[2] / kD50_z, - }; - for (size_t i = 0; i < 3; ++i) { - v[i] = v[i] > 0.008856f ? cbrtf(v[i]) : v[i] * 7.787f + (16 / 116.0f); - } - const float L = v[1] * 116.0f - 16.0f; - const float a = (v[0] - v[1]) * 500.0f; - const float b = (v[1] - v[2]) * 200.0f; - const float Lab_unorm[3] = { - L * (1 / 100.f), - (a + 128.0f) * (1 / 255.0f), - (b + 128.0f) * (1 / 255.0f), - }; - // This will encode L=1 as 0xFFFF. This matches how skcms will interpret the - // table, but the spec appears to indicate that the value should be 0xFF00. - // https://crbug.com/skia/13807 - for (size_t i = 0; i < 3; ++i) { - reinterpret_cast<uint16_t*>(grid16_lab)[i] = - Endian_SwapBE16(float_round_to_unorm16(Lab_unorm[i])); - } -} - -std::string IccHelper::get_desc_string(const ultrahdr_transfer_function tf, - const ultrahdr_color_gamut gamut) { - std::string result; - switch (gamut) { - case ULTRAHDR_COLORGAMUT_BT709: - result += "sRGB"; - break; - case ULTRAHDR_COLORGAMUT_P3: - result += "Display P3"; - break; - case ULTRAHDR_COLORGAMUT_BT2100: - result += "Rec2020"; - break; - default: - result += "Unknown"; - break; - } - result += " Gamut with "; - switch (tf) { - case ULTRAHDR_TF_SRGB: - result += "sRGB"; - break; - case ULTRAHDR_TF_LINEAR: - result += "Linear"; - break; - case ULTRAHDR_TF_PQ: - result += "PQ"; - break; - case ULTRAHDR_TF_HLG: - result += "HLG"; - break; - default: - result += "Unknown"; - break; - } - result += " Transfer"; - return result; -} - -std::shared_ptr<DataStruct> IccHelper::write_text_tag(const char* text) { - uint32_t text_length = strlen(text); - uint32_t header[] = { - Endian_SwapBE32(kTAG_TextType), // Type signature - 0, // Reserved - Endian_SwapBE32(1), // Number of records - Endian_SwapBE32(12), // Record size (must be 12) - Endian_SwapBE32(SetFourByteTag('e', 'n', 'U', 'S')), // English USA - Endian_SwapBE32(2 * text_length), // Length of string in bytes - Endian_SwapBE32(28), // Offset of string - }; - - uint32_t total_length = text_length * 2 + sizeof(header); - total_length = (((total_length + 2) >> 2) << 2); // 4 aligned - std::shared_ptr<DataStruct> dataStruct = std::make_shared<DataStruct>(total_length); - - if (!dataStruct->write(header, sizeof(header))) { - ALOGE("write_text_tag(): error in writing data"); - return dataStruct; - } - - for (size_t i = 0; i < text_length; i++) { - // Convert ASCII to big-endian UTF-16. - dataStruct->write8(0); - dataStruct->write8(text[i]); - } - - return dataStruct; -} - -std::shared_ptr<DataStruct> IccHelper::write_xyz_tag(float x, float y, float z) { - uint32_t data[] = { - Endian_SwapBE32(kXYZ_PCSSpace), - 0, - static_cast<uint32_t>(Endian_SwapBE32(float_round_to_fixed(x))), - static_cast<uint32_t>(Endian_SwapBE32(float_round_to_fixed(y))), - static_cast<uint32_t>(Endian_SwapBE32(float_round_to_fixed(z))), - }; - std::shared_ptr<DataStruct> dataStruct = std::make_shared<DataStruct>(sizeof(data)); - dataStruct->write(&data, sizeof(data)); - return dataStruct; -} - -std::shared_ptr<DataStruct> IccHelper::write_trc_tag(const int table_entries, - const void* table_16) { - int total_length = 4 + 4 + 4 + table_entries * 2; - total_length = (((total_length + 2) >> 2) << 2); // 4 aligned - std::shared_ptr<DataStruct> dataStruct = std::make_shared<DataStruct>(total_length); - dataStruct->write32(Endian_SwapBE32(kTAG_CurveType)); // Type - dataStruct->write32(0); // Reserved - dataStruct->write32(Endian_SwapBE32(table_entries)); // Value count - for (int i = 0; i < table_entries; ++i) { - uint16_t value = reinterpret_cast<const uint16_t*>(table_16)[i]; - dataStruct->write16(value); - } - return dataStruct; -} - -std::shared_ptr<DataStruct> IccHelper::write_trc_tag(const TransferFunction& fn) { - if (fn.a == 1.f && fn.b == 0.f && fn.c == 0.f - && fn.d == 0.f && fn.e == 0.f && fn.f == 0.f) { - int total_length = 16; - std::shared_ptr<DataStruct> dataStruct = std::make_shared<DataStruct>(total_length); - dataStruct->write32(Endian_SwapBE32(kTAG_ParaCurveType)); // Type - dataStruct->write32(0); // Reserved - dataStruct->write32(Endian_SwapBE16(kExponential_ParaCurveType)); - dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.g))); - return dataStruct; - } - - int total_length = 40; - std::shared_ptr<DataStruct> dataStruct = std::make_shared<DataStruct>(total_length); - dataStruct->write32(Endian_SwapBE32(kTAG_ParaCurveType)); // Type - dataStruct->write32(0); // Reserved - dataStruct->write32(Endian_SwapBE16(kGABCDEF_ParaCurveType)); - dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.g))); - dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.a))); - dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.b))); - dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.c))); - dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.d))); - dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.e))); - dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.f))); - return dataStruct; -} - -float IccHelper::compute_tone_map_gain(const ultrahdr_transfer_function tf, float L) { - if (L <= 0.f) { - return 1.f; - } - if (tf == ULTRAHDR_TF_PQ) { - // The PQ transfer function will map to the range [0, 1]. Linearly scale - // it up to the range [0, 10,000/203]. We will then tone map that back - // down to [0, 1]. - constexpr float kInputMaxLuminance = 10000 / 203.f; - constexpr float kOutputMaxLuminance = 1.0; - L *= kInputMaxLuminance; - - // Compute the tone map gain which will tone map from 10,000/203 to 1.0. - constexpr float kToneMapA = kOutputMaxLuminance / (kInputMaxLuminance * kInputMaxLuminance); - constexpr float kToneMapB = 1.f / kOutputMaxLuminance; - return kInputMaxLuminance * (1.f + kToneMapA * L) / (1.f + kToneMapB * L); - } - if (tf == ULTRAHDR_TF_HLG) { - // Let Lw be the brightness of the display in nits. - constexpr float Lw = 203.f; - const float gamma = 1.2f + 0.42f * std::log(Lw / 1000.f) / std::log(10.f); - return std::pow(L, gamma - 1.f); - } - return 1.f; -} - -std::shared_ptr<DataStruct> IccHelper::write_cicp_tag(uint32_t color_primaries, - uint32_t transfer_characteristics) { - int total_length = 12; // 4 + 4 + 1 + 1 + 1 + 1 - std::shared_ptr<DataStruct> dataStruct = std::make_shared<DataStruct>(total_length); - dataStruct->write32(Endian_SwapBE32(kTAG_cicp)); // Type signature - dataStruct->write32(0); // Reserved - dataStruct->write8(color_primaries); // Color primaries - dataStruct->write8(transfer_characteristics); // Transfer characteristics - dataStruct->write8(0); // RGB matrix - dataStruct->write8(1); // Full range - return dataStruct; -} - -void IccHelper::compute_lut_entry(const Matrix3x3& src_to_XYZD50, float rgb[3]) { - // Compute the matrices to convert from source to Rec2020, and from Rec2020 to XYZD50. - Matrix3x3 src_to_rec2020; - const Matrix3x3 rec2020_to_XYZD50 = kRec2020; - { - Matrix3x3 XYZD50_to_rec2020; - Matrix3x3_invert(&rec2020_to_XYZD50, &XYZD50_to_rec2020); - src_to_rec2020 = Matrix3x3_concat(&XYZD50_to_rec2020, &src_to_XYZD50); - } - - // Convert the source signal to linear. - for (size_t i = 0; i < kNumChannels; ++i) { - rgb[i] = pqOetf(rgb[i]); - } - - // Convert source gamut to Rec2020. - Matrix3x3_apply(&src_to_rec2020, rgb); - - // Compute the luminance of the signal. - float L = bt2100Luminance({{{rgb[0], rgb[1], rgb[2]}}}); - - // Compute the tone map gain based on the luminance. - float tone_map_gain = compute_tone_map_gain(ULTRAHDR_TF_PQ, L); - - // Apply the tone map gain. - for (size_t i = 0; i < kNumChannels; ++i) { - rgb[i] *= tone_map_gain; - } - - // Convert from Rec2020-linear to XYZD50. - Matrix3x3_apply(&rec2020_to_XYZD50, rgb); -} - -std::shared_ptr<DataStruct> IccHelper::write_clut(const uint8_t* grid_points, - const uint8_t* grid_16) { - uint32_t value_count = kNumChannels; - for (uint32_t i = 0; i < kNumChannels; ++i) { - value_count *= grid_points[i]; - } - - int total_length = 20 + 2 * value_count; - total_length = (((total_length + 2) >> 2) << 2); // 4 aligned - std::shared_ptr<DataStruct> dataStruct = std::make_shared<DataStruct>(total_length); - - for (size_t i = 0; i < 16; ++i) { - dataStruct->write8(i < kNumChannels ? grid_points[i] : 0); // Grid size - } - dataStruct->write8(2); // Grid byte width (always 16-bit) - dataStruct->write8(0); // Reserved - dataStruct->write8(0); // Reserved - dataStruct->write8(0); // Reserved - - for (uint32_t i = 0; i < value_count; ++i) { - uint16_t value = reinterpret_cast<const uint16_t*>(grid_16)[i]; - dataStruct->write16(value); - } - - return dataStruct; -} - -std::shared_ptr<DataStruct> IccHelper::write_mAB_or_mBA_tag(uint32_t type, bool has_a_curves, - const uint8_t* grid_points, - const uint8_t* grid_16) { - const size_t b_curves_offset = 32; - std::shared_ptr<DataStruct> b_curves_data[kNumChannels]; - std::shared_ptr<DataStruct> a_curves_data[kNumChannels]; - size_t clut_offset = 0; - std::shared_ptr<DataStruct> clut; - size_t a_curves_offset = 0; - - // The "B" curve is required. - for (size_t i = 0; i < kNumChannels; ++i) { - b_curves_data[i] = write_trc_tag(kLinear_TransFun); - } - - // The "A" curve and CLUT are optional. - if (has_a_curves) { - clut_offset = b_curves_offset; - for (size_t i = 0; i < kNumChannels; ++i) { - clut_offset += b_curves_data[i]->getLength(); - } - clut = write_clut(grid_points, grid_16); - - a_curves_offset = clut_offset + clut->getLength(); - for (size_t i = 0; i < kNumChannels; ++i) { - a_curves_data[i] = write_trc_tag(kLinear_TransFun); - } - } - - int total_length = b_curves_offset; - for (size_t i = 0; i < kNumChannels; ++i) { - total_length += b_curves_data[i]->getLength(); - } - if (has_a_curves) { - total_length += clut->getLength(); - for (size_t i = 0; i < kNumChannels; ++i) { - total_length += a_curves_data[i]->getLength(); - } - } - std::shared_ptr<DataStruct> dataStruct = std::make_shared<DataStruct>(total_length); - dataStruct->write32(Endian_SwapBE32(type)); // Type signature - dataStruct->write32(0); // Reserved - dataStruct->write8(kNumChannels); // Input channels - dataStruct->write8(kNumChannels); // Output channels - dataStruct->write16(0); // Reserved - dataStruct->write32(Endian_SwapBE32(b_curves_offset)); // B curve offset - dataStruct->write32(Endian_SwapBE32(0)); // Matrix offset (ignored) - dataStruct->write32(Endian_SwapBE32(0)); // M curve offset (ignored) - dataStruct->write32(Endian_SwapBE32(clut_offset)); // CLUT offset - dataStruct->write32(Endian_SwapBE32(a_curves_offset)); // A curve offset - for (size_t i = 0; i < kNumChannels; ++i) { - if (dataStruct->write(b_curves_data[i]->getData(), b_curves_data[i]->getLength())) { - return dataStruct; - } - } - if (has_a_curves) { - dataStruct->write(clut->getData(), clut->getLength()); - for (size_t i = 0; i < kNumChannels; ++i) { - dataStruct->write(a_curves_data[i]->getData(), a_curves_data[i]->getLength()); - } - } - return dataStruct; -} - -std::shared_ptr<DataStruct> IccHelper::writeIccProfile(ultrahdr_transfer_function tf, - ultrahdr_color_gamut gamut) { - ICCHeader header; - - std::vector<std::pair<uint32_t, std::shared_ptr<DataStruct>>> tags; - - // Compute profile description tag - std::string desc = get_desc_string(tf, gamut); - - tags.emplace_back(kTAG_desc, write_text_tag(desc.c_str())); - - Matrix3x3 toXYZD50; - switch (gamut) { - case ULTRAHDR_COLORGAMUT_BT709: - toXYZD50 = kSRGB; - break; - case ULTRAHDR_COLORGAMUT_P3: - toXYZD50 = kDisplayP3; - break; - case ULTRAHDR_COLORGAMUT_BT2100: - toXYZD50 = kRec2020; - break; - default: - // Should not fall here. - return nullptr; - } - - // Compute primaries. - { - tags.emplace_back(kTAG_rXYZ, - write_xyz_tag(toXYZD50.vals[0][0], toXYZD50.vals[1][0], toXYZD50.vals[2][0])); - tags.emplace_back(kTAG_gXYZ, - write_xyz_tag(toXYZD50.vals[0][1], toXYZD50.vals[1][1], toXYZD50.vals[2][1])); - tags.emplace_back(kTAG_bXYZ, - write_xyz_tag(toXYZD50.vals[0][2], toXYZD50.vals[1][2], toXYZD50.vals[2][2])); - } - - // Compute white point tag (must be D50) - tags.emplace_back(kTAG_wtpt, write_xyz_tag(kD50_x, kD50_y, kD50_z)); - - // Compute transfer curves. - if (tf != ULTRAHDR_TF_PQ) { - if (tf == ULTRAHDR_TF_HLG) { - std::vector<uint8_t> trc_table; - trc_table.resize(kTrcTableSize * 2); - for (uint32_t i = 0; i < kTrcTableSize; ++i) { - float x = i / (kTrcTableSize - 1.f); - float y = hlgOetf(x); - y *= compute_tone_map_gain(tf, y); - float_to_table16(y, &trc_table[2 * i]); - } - - tags.emplace_back(kTAG_rTRC, - write_trc_tag(kTrcTableSize, reinterpret_cast<uint8_t*>(trc_table.data()))); - tags.emplace_back(kTAG_gTRC, - write_trc_tag(kTrcTableSize, reinterpret_cast<uint8_t*>(trc_table.data()))); - tags.emplace_back(kTAG_bTRC, - write_trc_tag(kTrcTableSize, reinterpret_cast<uint8_t*>(trc_table.data()))); - } else { - tags.emplace_back(kTAG_rTRC, write_trc_tag(kSRGB_TransFun)); - tags.emplace_back(kTAG_gTRC, write_trc_tag(kSRGB_TransFun)); - tags.emplace_back(kTAG_bTRC, write_trc_tag(kSRGB_TransFun)); - } - } - - // Compute CICP. - if (tf == ULTRAHDR_TF_HLG || tf == ULTRAHDR_TF_PQ) { - // The CICP tag is present in ICC 4.4, so update the header's version. - header.version = Endian_SwapBE32(0x04400000); - - uint32_t color_primaries = 0; - if (gamut == ULTRAHDR_COLORGAMUT_BT709) { - color_primaries = kCICPPrimariesSRGB; - } else if (gamut == ULTRAHDR_COLORGAMUT_P3) { - color_primaries = kCICPPrimariesP3; - } - - uint32_t transfer_characteristics = 0; - if (tf == ULTRAHDR_TF_SRGB) { - transfer_characteristics = kCICPTrfnSRGB; - } else if (tf == ULTRAHDR_TF_LINEAR) { - transfer_characteristics = kCICPTrfnLinear; - } else if (tf == ULTRAHDR_TF_PQ) { - transfer_characteristics = kCICPTrfnPQ; - } else if (tf == ULTRAHDR_TF_HLG) { - transfer_characteristics = kCICPTrfnHLG; - } - tags.emplace_back(kTAG_cicp, write_cicp_tag(color_primaries, transfer_characteristics)); - } - - // Compute A2B0. - if (tf == ULTRAHDR_TF_PQ) { - std::vector<uint8_t> a2b_grid; - a2b_grid.resize(kGridSize * kGridSize * kGridSize * kNumChannels * 2); - size_t a2b_grid_index = 0; - for (uint32_t r_index = 0; r_index < kGridSize; ++r_index) { - for (uint32_t g_index = 0; g_index < kGridSize; ++g_index) { - for (uint32_t b_index = 0; b_index < kGridSize; ++b_index) { - float rgb[3] = { - r_index / (kGridSize - 1.f), - g_index / (kGridSize - 1.f), - b_index / (kGridSize - 1.f), - }; - compute_lut_entry(toXYZD50, rgb); - float_XYZD50_to_grid16_lab(rgb, &a2b_grid[a2b_grid_index]); - a2b_grid_index += 6; - } - } - } - const uint8_t* grid_16 = reinterpret_cast<const uint8_t*>(a2b_grid.data()); - - uint8_t grid_points[kNumChannels]; - for (size_t i = 0; i < kNumChannels; ++i) { - grid_points[i] = kGridSize; - } - - auto a2b_data = write_mAB_or_mBA_tag(kTAG_mABType, - /* has_a_curves */ true, - grid_points, - grid_16); - tags.emplace_back(kTAG_A2B0, std::move(a2b_data)); - } - - // Compute B2A0. - if (tf == ULTRAHDR_TF_PQ) { - auto b2a_data = write_mAB_or_mBA_tag(kTAG_mBAType, - /* has_a_curves */ false, - /* grid_points */ nullptr, - /* grid_16 */ nullptr); - tags.emplace_back(kTAG_B2A0, std::move(b2a_data)); - } - - // Compute copyright tag - tags.emplace_back(kTAG_cprt, write_text_tag("Google Inc. 2022")); - - // Compute the size of the profile. - size_t tag_data_size = 0; - for (const auto& tag : tags) { - tag_data_size += tag.second->getLength(); - } - size_t tag_table_size = kICCTagTableEntrySize * tags.size(); - size_t profile_size = kICCHeaderSize + tag_table_size + tag_data_size; - - std::shared_ptr<DataStruct> dataStruct = - std::make_shared<DataStruct>(profile_size + kICCIdentifierSize); - - // Write identifier, chunk count, and chunk ID - if (!dataStruct->write(kICCIdentifier, sizeof(kICCIdentifier)) || - !dataStruct->write8(1) || !dataStruct->write8(1)) { - ALOGE("writeIccProfile(): error in identifier"); - return dataStruct; - } - - // Write the header. - header.data_color_space = Endian_SwapBE32(Signature_RGB); - header.pcs = Endian_SwapBE32(tf == ULTRAHDR_TF_PQ ? Signature_Lab : Signature_XYZ); - header.size = Endian_SwapBE32(profile_size); - header.tag_count = Endian_SwapBE32(tags.size()); - - if (!dataStruct->write(&header, sizeof(header))) { - ALOGE("writeIccProfile(): error in header"); - return dataStruct; - } - - // Write the tag table. Track the offset and size of the previous tag to - // compute each tag's offset. An empty SkData indicates that the previous - // tag is to be reused. - uint32_t last_tag_offset = sizeof(header) + tag_table_size; - uint32_t last_tag_size = 0; - for (const auto& tag : tags) { - last_tag_offset = last_tag_offset + last_tag_size; - last_tag_size = tag.second->getLength(); - uint32_t tag_table_entry[3] = { - Endian_SwapBE32(tag.first), - Endian_SwapBE32(last_tag_offset), - Endian_SwapBE32(last_tag_size), - }; - if (!dataStruct->write(tag_table_entry, sizeof(tag_table_entry))) { - ALOGE("writeIccProfile(): error in writing tag table"); - return dataStruct; - } - } - - // Write the tags. - for (const auto& tag : tags) { - if (!dataStruct->write(tag.second->getData(), tag.second->getLength())) { - ALOGE("writeIccProfile(): error in writing tags"); - return dataStruct; - } - } - - return dataStruct; -} - -bool IccHelper::tagsEqualToMatrix(const Matrix3x3& matrix, - const uint8_t* red_tag, - const uint8_t* green_tag, - const uint8_t* blue_tag) { - std::shared_ptr<DataStruct> red_tag_test = - write_xyz_tag(matrix.vals[0][0], matrix.vals[1][0], matrix.vals[2][0]); - std::shared_ptr<DataStruct> green_tag_test = - write_xyz_tag(matrix.vals[0][1], matrix.vals[1][1], matrix.vals[2][1]); - std::shared_ptr<DataStruct> blue_tag_test = - write_xyz_tag(matrix.vals[0][2], matrix.vals[1][2], matrix.vals[2][2]); - return memcmp(red_tag, red_tag_test->getData(), kColorantTagSize) == 0 && - memcmp(green_tag, green_tag_test->getData(), kColorantTagSize) == 0 && - memcmp(blue_tag, blue_tag_test->getData(), kColorantTagSize) == 0; -} - -ultrahdr_color_gamut IccHelper::readIccColorGamut(void* icc_data, size_t icc_size) { - // Each tag table entry consists of 3 fields of 4 bytes each. - static const size_t kTagTableEntrySize = 12; - - if (icc_data == nullptr || icc_size < sizeof(ICCHeader) + kICCIdentifierSize) { - return ULTRAHDR_COLORGAMUT_UNSPECIFIED; - } - - if (memcmp(icc_data, kICCIdentifier, sizeof(kICCIdentifier)) != 0) { - return ULTRAHDR_COLORGAMUT_UNSPECIFIED; - } - - uint8_t* icc_bytes = reinterpret_cast<uint8_t*>(icc_data) + kICCIdentifierSize; - - ICCHeader* header = reinterpret_cast<ICCHeader*>(icc_bytes); - - // Use 0 to indicate not found, since offsets are always relative to start - // of ICC data and therefore a tag offset of zero would never be valid. - size_t red_primary_offset = 0, green_primary_offset = 0, blue_primary_offset = 0; - size_t red_primary_size = 0, green_primary_size = 0, blue_primary_size = 0; - for (size_t tag_idx = 0; tag_idx < Endian_SwapBE32(header->tag_count); ++tag_idx) { - uint32_t* tag_entry_start = reinterpret_cast<uint32_t*>( - icc_bytes + sizeof(ICCHeader) + tag_idx * kTagTableEntrySize); - // first 4 bytes are the tag signature, next 4 bytes are the tag offset, - // last 4 bytes are the tag length in bytes. - if (red_primary_offset == 0 && *tag_entry_start == Endian_SwapBE32(kTAG_rXYZ)) { - red_primary_offset = Endian_SwapBE32(*(tag_entry_start+1)); - red_primary_size = Endian_SwapBE32(*(tag_entry_start+2)); - } else if (green_primary_offset == 0 && *tag_entry_start == Endian_SwapBE32(kTAG_gXYZ)) { - green_primary_offset = Endian_SwapBE32(*(tag_entry_start+1)); - green_primary_size = Endian_SwapBE32(*(tag_entry_start+2)); - } else if (blue_primary_offset == 0 && *tag_entry_start == Endian_SwapBE32(kTAG_bXYZ)) { - blue_primary_offset = Endian_SwapBE32(*(tag_entry_start+1)); - blue_primary_size = Endian_SwapBE32(*(tag_entry_start+2)); - } - } - - if (red_primary_offset == 0 || red_primary_size != kColorantTagSize || - kICCIdentifierSize + red_primary_offset + red_primary_size > icc_size || - green_primary_offset == 0 || green_primary_size != kColorantTagSize || - kICCIdentifierSize + green_primary_offset + green_primary_size > icc_size || - blue_primary_offset == 0 || blue_primary_size != kColorantTagSize || - kICCIdentifierSize + blue_primary_offset + blue_primary_size > icc_size) { - return ULTRAHDR_COLORGAMUT_UNSPECIFIED; - } - - uint8_t* red_tag = icc_bytes + red_primary_offset; - uint8_t* green_tag = icc_bytes + green_primary_offset; - uint8_t* blue_tag = icc_bytes + blue_primary_offset; - - // Serialize tags as we do on encode and compare what we find to that to - // determine the gamut (since we don't have a need yet for full deserialize). - if (tagsEqualToMatrix(kSRGB, red_tag, green_tag, blue_tag)) { - return ULTRAHDR_COLORGAMUT_BT709; - } else if (tagsEqualToMatrix(kDisplayP3, red_tag, green_tag, blue_tag)) { - return ULTRAHDR_COLORGAMUT_P3; - } else if (tagsEqualToMatrix(kRec2020, red_tag, green_tag, blue_tag)) { - return ULTRAHDR_COLORGAMUT_BT2100; - } - - // Didn't find a match to one of the profiles we write; indicate the gamut - // is unspecified since we don't understand it. - return ULTRAHDR_COLORGAMUT_UNSPECIFIED; -} - -} // namespace ultrahdr diff --git a/include/ultrahdr/icc.h b/include/ultrahdr/icc.h deleted file mode 100644 index 7a17321..0000000 --- a/include/ultrahdr/icc.h +++ /dev/null @@ -1,263 +0,0 @@ -/* - * Copyright 2022 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_ICC_H -#define ULTRAHDR_ICC_H - -#include <memory> - -#ifndef USE_BIG_ENDIAN_IN_ICC -#define USE_BIG_ENDIAN_IN_ICC true -#endif - -#if USE_BIG_ENDIAN_IN_ICC - #define Endian_SwapBE32(n) EndianSwap32(n) - #define Endian_SwapBE16(n) EndianSwap16(n) -#else - #define Endian_SwapBE32(n) (n) - #define Endian_SwapBE16(n) (n) -#endif - -#include "ultrahdr/ultrahdr.h" -#include "ultrahdr/jpegr.h" -#include "ultrahdr/gainmapmath.h" -#include "ultrahdr/jpegrutils.h" - -namespace ultrahdr { - -typedef int32_t Fixed; -#define Fixed1 (1 << 16) -#define MaxS32FitsInFloat 2147483520 -#define MinS32FitsInFloat (-MaxS32FitsInFloat) -#define FixedToFloat(x) ((x) * 1.52587890625e-5f) - -typedef struct Matrix3x3 { - float vals[3][3]; -} Matrix3x3; - -// The D50 illuminant. -constexpr float kD50_x = 0.9642f; -constexpr float kD50_y = 1.0000f; -constexpr float kD50_z = 0.8249f; - -enum { - // data_color_space - Signature_CMYK = 0x434D594B, - Signature_Gray = 0x47524159, - Signature_RGB = 0x52474220, - - // pcs - Signature_Lab = 0x4C616220, - Signature_XYZ = 0x58595A20, -}; - -typedef uint32_t FourByteTag; -static inline constexpr FourByteTag SetFourByteTag(char a, char b, char c, char d) { - return (((uint32_t)a << 24) | ((uint32_t)b << 16) | ((uint32_t)c << 8) | (uint32_t)d); -} - -static constexpr char kICCIdentifier[] = "ICC_PROFILE"; -// 12 for the actual identifier, +2 for the chunk count and chunk index which -// will always follow. -static constexpr size_t kICCIdentifierSize = 14; - -// This is equal to the header size according to the ICC specification (128) -// plus the size of the tag count (4). We include the tag count since we -// always require it to be present anyway. -static constexpr size_t kICCHeaderSize = 132; - -// Contains a signature (4), offset (4), and size (4). -static constexpr size_t kICCTagTableEntrySize = 12; - -// size should be 20; 4 bytes for type descriptor, 4 bytes reserved, 12 -// bytes for a single XYZ number type (4 bytes per coordinate). -static constexpr size_t kColorantTagSize = 20; - -static constexpr uint32_t kDisplay_Profile = SetFourByteTag('m', 'n', 't', 'r'); -static constexpr uint32_t kRGB_ColorSpace = SetFourByteTag('R', 'G', 'B', ' '); -static constexpr uint32_t kXYZ_PCSSpace = SetFourByteTag('X', 'Y', 'Z', ' '); -static constexpr uint32_t kACSP_Signature = SetFourByteTag('a', 'c', 's', 'p'); - -static constexpr uint32_t kTAG_desc = SetFourByteTag('d', 'e', 's', 'c'); -static constexpr uint32_t kTAG_TextType = SetFourByteTag('m', 'l', 'u', 'c'); -static constexpr uint32_t kTAG_rXYZ = SetFourByteTag('r', 'X', 'Y', 'Z'); -static constexpr uint32_t kTAG_gXYZ = SetFourByteTag('g', 'X', 'Y', 'Z'); -static constexpr uint32_t kTAG_bXYZ = SetFourByteTag('b', 'X', 'Y', 'Z'); -static constexpr uint32_t kTAG_wtpt = SetFourByteTag('w', 't', 'p', 't'); -static constexpr uint32_t kTAG_rTRC = SetFourByteTag('r', 'T', 'R', 'C'); -static constexpr uint32_t kTAG_gTRC = SetFourByteTag('g', 'T', 'R', 'C'); -static constexpr uint32_t kTAG_bTRC = SetFourByteTag('b', 'T', 'R', 'C'); -static constexpr uint32_t kTAG_cicp = SetFourByteTag('c', 'i', 'c', 'p'); -static constexpr uint32_t kTAG_cprt = SetFourByteTag('c', 'p', 'r', 't'); -static constexpr uint32_t kTAG_A2B0 = SetFourByteTag('A', '2', 'B', '0'); -static constexpr uint32_t kTAG_B2A0 = SetFourByteTag('B', '2', 'A', '0'); - -static constexpr uint32_t kTAG_CurveType = SetFourByteTag('c', 'u', 'r', 'v'); -static constexpr uint32_t kTAG_mABType = SetFourByteTag('m', 'A', 'B', ' '); -static constexpr uint32_t kTAG_mBAType = SetFourByteTag('m', 'B', 'A', ' '); -static constexpr uint32_t kTAG_ParaCurveType = SetFourByteTag('p', 'a', 'r', 'a'); - - -static constexpr Matrix3x3 kSRGB = {{ - // ICC fixed-point (16.16) representation, taken from skcms. Please keep them exactly in sync. - // 0.436065674f, 0.385147095f, 0.143066406f, - // 0.222488403f, 0.716873169f, 0.060607910f, - // 0.013916016f, 0.097076416f, 0.714096069f, - { FixedToFloat(0x6FA2), FixedToFloat(0x6299), FixedToFloat(0x24A0) }, - { FixedToFloat(0x38F5), FixedToFloat(0xB785), FixedToFloat(0x0F84) }, - { FixedToFloat(0x0390), FixedToFloat(0x18DA), FixedToFloat(0xB6CF) }, -}}; - -static constexpr Matrix3x3 kDisplayP3 = {{ - { 0.515102f, 0.291965f, 0.157153f }, - { 0.241182f, 0.692236f, 0.0665819f }, - { -0.00104941f, 0.0418818f, 0.784378f }, -}}; - -static constexpr Matrix3x3 kRec2020 = {{ - { 0.673459f, 0.165661f, 0.125100f }, - { 0.279033f, 0.675338f, 0.0456288f }, - { -0.00193139f, 0.0299794f, 0.797162f }, -}}; - -static constexpr uint32_t kCICPPrimariesSRGB = 1; -static constexpr uint32_t kCICPPrimariesP3 = 12; -static constexpr uint32_t kCICPPrimariesRec2020 = 9; - -static constexpr uint32_t kCICPTrfnSRGB = 1; -static constexpr uint32_t kCICPTrfnLinear = 8; -static constexpr uint32_t kCICPTrfnPQ = 16; -static constexpr uint32_t kCICPTrfnHLG = 18; - -enum ParaCurveType { - kExponential_ParaCurveType = 0, - kGAB_ParaCurveType = 1, - kGABC_ParaCurveType = 2, - kGABDE_ParaCurveType = 3, - kGABCDEF_ParaCurveType = 4, -}; - -/** - * Return the closest int for the given float. Returns MaxS32FitsInFloat for NaN. - */ -static inline int float_saturate2int(float x) { - x = x < MaxS32FitsInFloat ? x : MaxS32FitsInFloat; - x = x > MinS32FitsInFloat ? x : MinS32FitsInFloat; - return (int)x; -} - -static Fixed float_round_to_fixed(float x) { - return float_saturate2int((float)floor((double)x * Fixed1 + 0.5)); -} - -static uint16_t float_round_to_unorm16(float x) { - x = x * 65535.f + 0.5; - if (x > 65535) return 65535; - if (x < 0) return 0; - return static_cast<uint16_t>(x); -} - -static inline void float_to_table16(const float f, uint8_t* table_16) { - *reinterpret_cast<uint16_t*>(table_16) = Endian_SwapBE16(float_round_to_unorm16(f)); -} - -static inline bool isfinitef_(float x) { return 0 == x*0; } - -struct ICCHeader { - // Size of the profile (computed) - uint32_t size; - // Preferred CMM type (ignored) - uint32_t cmm_type = 0; - // Version 4.3 or 4.4 if CICP is included. - uint32_t version = Endian_SwapBE32(0x04300000); - // Display device profile - uint32_t profile_class = Endian_SwapBE32(kDisplay_Profile); - // RGB input color space; - uint32_t data_color_space = Endian_SwapBE32(kRGB_ColorSpace); - // Profile connection space. - uint32_t pcs = Endian_SwapBE32(kXYZ_PCSSpace); - // Date and time (ignored) - uint8_t creation_date_time[12] = {0}; - // Profile signature - uint32_t signature = Endian_SwapBE32(kACSP_Signature); - // Platform target (ignored) - uint32_t platform = 0; - // Flags: not embedded, can be used independently - uint32_t flags = 0x00000000; - // Device manufacturer (ignored) - uint32_t device_manufacturer = 0; - // Device model (ignored) - uint32_t device_model = 0; - // Device attributes (ignored) - uint8_t device_attributes[8] = {0}; - // Relative colorimetric rendering intent - uint32_t rendering_intent = Endian_SwapBE32(1); - // D50 standard illuminant (X, Y, Z) - uint32_t illuminant_X = Endian_SwapBE32(float_round_to_fixed(kD50_x)); - uint32_t illuminant_Y = Endian_SwapBE32(float_round_to_fixed(kD50_y)); - uint32_t illuminant_Z = Endian_SwapBE32(float_round_to_fixed(kD50_z)); - // Profile creator (ignored) - uint32_t creator = 0; - // Profile id checksum (ignored) - uint8_t profile_id[16] = {0}; - // Reserved (ignored) - uint8_t reserved[28] = {0}; - // Technically not part of header, but required - uint32_t tag_count = 0; -}; - -class IccHelper { -private: - static constexpr uint32_t kTrcTableSize = 65; - static constexpr uint32_t kGridSize = 17; - static constexpr size_t kNumChannels = 3; - - static std::shared_ptr<DataStruct> write_text_tag(const char* text); - static std::string get_desc_string(const ultrahdr_transfer_function tf, - const ultrahdr_color_gamut gamut); - static std::shared_ptr<DataStruct> write_xyz_tag(float x, float y, float z); - static std::shared_ptr<DataStruct> write_trc_tag(const int table_entries, const void* table_16); - static std::shared_ptr<DataStruct> write_trc_tag(const TransferFunction& fn); - static float compute_tone_map_gain(const ultrahdr_transfer_function tf, float L); - static std::shared_ptr<DataStruct> write_cicp_tag(uint32_t color_primaries, - uint32_t transfer_characteristics); - static std::shared_ptr<DataStruct> write_mAB_or_mBA_tag(uint32_t type, bool has_a_curves, - const uint8_t* grid_points, - const uint8_t* grid_16); - static void compute_lut_entry(const Matrix3x3& src_to_XYZD50, float rgb[3]); - static std::shared_ptr<DataStruct> write_clut(const uint8_t* grid_points, - const uint8_t* grid_16); - - // Checks if a set of xyz tags is equivalent to a 3x3 Matrix. Each input - // tag buffer assumed to be at least kColorantTagSize in size. - static bool tagsEqualToMatrix(const Matrix3x3& matrix, - const uint8_t* red_tag, - const uint8_t* green_tag, - const uint8_t* blue_tag); - -public: - // Output includes JPEG embedding identifier and chunk information, but not - // APPx information. - static std::shared_ptr<DataStruct> writeIccProfile(const ultrahdr_transfer_function tf, - const ultrahdr_color_gamut gamut); - // NOTE: this function is not robust; it can infer gamuts that IccHelper - // writes out but should not be considered a reference implementation for - // robust parsing of ICC profiles or their gamuts. - static ultrahdr_color_gamut readIccColorGamut(void* icc_data, size_t icc_size); -}; -} // namespace ultrahdr - -#endif //ULTRAHDR_ICC_H diff --git a/include/ultrahdr/jpegdecoderhelper.h b/include/ultrahdr/jpegdecoderhelper.h deleted file mode 100644 index 4c8b601..0000000 --- a/include/ultrahdr/jpegdecoderhelper.h +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright 2022 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_JPEGDECODERHELPER_H -#define ULTRAHDR_JPEGDECODERHELPER_H - -#include <stdio.h> // For jpeglib.h. - -// C++ build requires extern C for jpeg internals. -#ifdef __cplusplus -extern "C" { -#endif - -#include <jerror.h> -#include <jpeglib.h> - -#ifdef __cplusplus -} // extern "C" -#endif - -#include <cstdint> -#include <memory> -#include <vector> - -// 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 { -/* - * Encapsulates a converter from JPEG to raw image (YUV420planer or grey-scale) format. - * This class is not thread-safe. - */ -class JpegDecoderHelper { -public: - JpegDecoderHelper(); - ~JpegDecoderHelper(); - /* - * Decompresses JPEG image to raw image (YUV420planer, grey-scale or RGBA) format. After - * calling this method, call getDecompressedImage() to get the image. - * Returns false if decompressing the image fails. - */ - bool decompressImage(const void* image, int length, bool decodeToRGBA = false); - /* - * Returns the decompressed raw image buffer pointer. This method must be called only after - * calling decompressImage(). - */ - void* getDecompressedImagePtr(); - /* - * Returns the decompressed raw image buffer size. This method must be called only after - * calling decompressImage(). - */ - size_t getDecompressedImageSize(); - /* - * Returns the image width in pixels. This method must be called only after calling - * decompressImage(). - */ - size_t getDecompressedImageWidth(); - /* - * Returns the image width in pixels. This method must be called only after calling - * decompressImage(). - */ - size_t getDecompressedImageHeight(); - /* - * Returns the XMP data from the image. - */ - void* getXMPPtr(); - /* - * Returns the decompressed XMP buffer size. This method must be called only after - * calling decompressImage() or getCompressedImageParameters(). - */ - size_t getXMPSize(); - /* - * Extracts EXIF package and updates the EXIF position / length without decoding the image. - */ - bool extractEXIF(const void* image, int length); - /* - * Returns the EXIF data from the image. - * This method must be called after extractEXIF() or decompressImage(). - */ - void* getEXIFPtr(); - /* - * Returns the decompressed EXIF buffer size. This method must be called only after - * calling decompressImage(), extractEXIF() or getCompressedImageParameters(). - */ - size_t getEXIFSize(); - /* - * Returns the position offset of EXIF package - * (4 bypes offset to FF sign, the byte after FF E1 XX XX <this byte>), - * or -1 if no EXIF exists. - * This method must be called after extractEXIF() or decompressImage(). - */ - int getEXIFPos() { return mExifPos; } - /* - * Returns the ICC data from the image. - */ - void* getICCPtr(); - /* - * Returns the decompressed ICC buffer size. This method must be called only after - * calling decompressImage() or getCompressedImageParameters(). - */ - size_t getICCSize(); - /* - * Decompresses metadata of the image. All vectors are owned by the caller. - */ - bool getCompressedImageParameters(const void* image, int length, size_t* pWidth, - size_t* pHeight, std::vector<uint8_t>* iccData, - std::vector<uint8_t>* exifData); - -private: - bool decode(const void* image, int length, bool decodeToRGBA); - // Returns false if errors occur. - bool decompress(jpeg_decompress_struct* cinfo, const uint8_t* dest, bool isSingleChannel); - bool decompressYUV(jpeg_decompress_struct* cinfo, const uint8_t* dest); - bool decompressRGBA(jpeg_decompress_struct* cinfo, const uint8_t* dest); - bool decompressSingleChannel(jpeg_decompress_struct* cinfo, const uint8_t* dest); - // Process 16 lines of Y and 16 lines of U/V each time. - // We must pass at least 16 scanlines according to libjpeg documentation. - static const int kCompressBatchSize = 16; - // The buffer that holds the decompressed result. - std::vector<JOCTET> mResultBuffer; - // The buffer that holds XMP Data. - std::vector<JOCTET> mXMPBuffer; - // The buffer that holds EXIF Data. - std::vector<JOCTET> mEXIFBuffer; - // The buffer that holds ICC Data. - std::vector<JOCTET> mICCBuffer; - - // Resolution of the decompressed image. - size_t mWidth; - size_t mHeight; - - // Position of EXIF package, default value is -1 which means no EXIF package appears. - int mExifPos = -1; - - std::unique_ptr<uint8_t[]> mEmpty = nullptr; - std::unique_ptr<uint8_t[]> mBufferIntermediate = nullptr; -}; -} /* namespace ultrahdr */ - -#endif // ULTRAHDR_JPEGDECODERHELPER_H diff --git a/include/ultrahdr/jpegencoderhelper.h b/include/ultrahdr/jpegencoderhelper.h deleted file mode 100644 index 47325fe..0000000 --- a/include/ultrahdr/jpegencoderhelper.h +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright 2022 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_JPEGENCODERHELPER_H -#define ULTRAHDR_JPEGENCODERHELPER_H - -#include <stdio.h> // For jpeglib.h. - -// C++ build requires extern C for jpeg internals. -#ifdef __cplusplus -extern "C" { -#endif - -#include <jerror.h> -#include <jpeglib.h> - -#ifdef __cplusplus -} // extern "C" -#endif - -#include <cstdint> -#include <vector> - -namespace ultrahdr { - -/* - * Encapsulates a converter from raw image (YUV420planer or grey-scale) to JPEG format. - * This class is not thread-safe. - */ -class JpegEncoderHelper { -public: - JpegEncoderHelper(); - ~JpegEncoderHelper(); - - /* - * Compresses YUV420Planer image to JPEG format. After calling this method, call - * getCompressedImage() to get the image. |quality| is the jpeg image quality parameter to use. - * It ranges from 1 (poorest quality) to 100 (highest quality). |iccBuffer| is the buffer of - * ICC segment which will be added to the compressed image. - * Returns false if errors occur during compression. - */ - bool compressImage(const uint8_t* yBuffer, const uint8_t* uvBuffer, int width, int height, - int lumaStride, int chromaStride, int quality, const void* iccBuffer, - unsigned int iccSize); - - /* - * Returns the compressed JPEG buffer pointer. This method must be called only after calling - * compressImage(). - */ - void* getCompressedImagePtr(); - - /* - * Returns the compressed JPEG buffer size. This method must be called only after calling - * compressImage(). - */ - size_t getCompressedImageSize(); - - /* - * Process 16 lines of Y and 16 lines of U/V each time. - * We must pass at least 16 scanlines according to libjpeg documentation. - */ - static const int kCompressBatchSize = 16; - -private: - // initDestination(), emptyOutputBuffer() and emptyOutputBuffer() are callback functions to be - // passed into jpeg library. - static void initDestination(j_compress_ptr cinfo); - static boolean emptyOutputBuffer(j_compress_ptr cinfo); - static void terminateDestination(j_compress_ptr cinfo); - static void outputErrorMessage(j_common_ptr cinfo); - - // Returns false if errors occur. - bool encode(const uint8_t* yBuffer, const uint8_t* uvBuffer, int width, int height, - int lumaStride, int chromaStride, int quality, const void* iccBuffer, - unsigned int iccSize); - void setJpegDestination(jpeg_compress_struct* cinfo); - void setJpegCompressStruct(int width, int height, int quality, jpeg_compress_struct* cinfo, - bool isSingleChannel); - // Returns false if errors occur. - bool compressYuv(jpeg_compress_struct* cinfo, const uint8_t* yBuffer, const uint8_t* uvBuffer, - int lumaStride, int chromaStride); - bool compressY(jpeg_compress_struct* cinfo, const uint8_t* yBuffer, int lumaStride); - - // The block size for encoded jpeg image buffer. - static const int kBlockSize = 16384; - - // The buffer that holds the compressed result. - std::vector<JOCTET> mResultBuffer; -}; - -} /* namespace ultrahdr */ - -#endif // ULTRAHDR_JPEGENCODERHELPER_H diff --git a/include/ultrahdr/jpegr.h b/include/ultrahdr/jpegr.h deleted file mode 100644 index d5edf56..0000000 --- a/include/ultrahdr/jpegr.h +++ /dev/null @@ -1,465 +0,0 @@ -/* - * Copyright 2022 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_JPEGR_H -#define ULTRAHDR_JPEGR_H - -#include <cfloat> - -#include "ultrahdr/ultrahdr.h" -#include "ultrahdr/jpegdecoderhelper.h" -#include "ultrahdr/jpegencoderhelper.h" - -namespace ultrahdr { - -// The current JPEGR version that we encode to -static const char* const kJpegrVersion = "1.0"; - -// Map is quarter res / sixteenth size -static const size_t kMapDimensionScaleFactor = 4; - -// Gain Map width is (image_width / kMapDimensionScaleFactor). If we were to -// compress 420 GainMap in jpeg, then we need at least 2 samples. For Grayscale -// 1 sample is sufficient. We are using 2 here anyways -static const int kMinWidth = 2 * kMapDimensionScaleFactor; -static const int kMinHeight = 2 * kMapDimensionScaleFactor; - -typedef enum { - OK = 0, - NO_ERROR = OK, - UNKNOWN_ERROR = (-2147483647 - 1), - - JPEGR_IO_ERROR_BASE = -10000, - ERROR_JPEGR_INVALID_INPUT_TYPE = JPEGR_IO_ERROR_BASE, - ERROR_JPEGR_INVALID_OUTPUT_TYPE = JPEGR_IO_ERROR_BASE - 1, - ERROR_JPEGR_INVALID_NULL_PTR = JPEGR_IO_ERROR_BASE - 2, - ERROR_JPEGR_RESOLUTION_MISMATCH = JPEGR_IO_ERROR_BASE - 3, - ERROR_JPEGR_BUFFER_TOO_SMALL = JPEGR_IO_ERROR_BASE - 4, - ERROR_JPEGR_INVALID_COLORGAMUT = JPEGR_IO_ERROR_BASE - 5, - ERROR_JPEGR_INVALID_TRANS_FUNC = JPEGR_IO_ERROR_BASE - 6, - ERROR_JPEGR_INVALID_METADATA = JPEGR_IO_ERROR_BASE - 7, - ERROR_JPEGR_UNSUPPORTED_METADATA = JPEGR_IO_ERROR_BASE - 8, - ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND = JPEGR_IO_ERROR_BASE - 9, - - JPEGR_RUNTIME_ERROR_BASE = -20000, - ERROR_JPEGR_ENCODE_ERROR = JPEGR_RUNTIME_ERROR_BASE - 1, - ERROR_JPEGR_DECODE_ERROR = JPEGR_RUNTIME_ERROR_BASE - 2, - ERROR_JPEGR_CALCULATION_ERROR = JPEGR_RUNTIME_ERROR_BASE - 3, - ERROR_JPEGR_METADATA_ERROR = JPEGR_RUNTIME_ERROR_BASE - 4, - ERROR_JPEGR_TONEMAP_ERROR = JPEGR_RUNTIME_ERROR_BASE - 5, - - ERROR_JPEGR_UNSUPPORTED_FEATURE = -20000, -} status_t; - -/* - * Holds information of jpegr image - */ -struct jpegr_info_struct { - size_t width; - size_t height; - std::vector<uint8_t>* iccData; - std::vector<uint8_t>* exifData; -}; - -/* - * Holds information for uncompressed image or gain map. - */ -struct jpegr_uncompressed_struct { - // Pointer to the data location. - void* data; - // Width of the gain map or the luma plane of the image in pixels. - size_t width; - // Height of the gain map or the luma plane of the image in pixels. - size_t height; - // Color gamut. - ultrahdr_color_gamut colorGamut; - - // Values below are optional - // Pointer to chroma data, if it's NULL, chroma plane is considered to be immediately - // after the luma plane. - void* chroma_data = nullptr; - // Stride of Y plane in number of pixels. 0 indicates the member is uninitialized. If - // non-zero this value must be larger than or equal to luma width. If stride is - // uninitialized then it is assumed to be equal to luma width. - size_t luma_stride = 0; - // Stride of UV plane in number of pixels. - // 1. If this handle points to P010 image then this value must be larger than - // or equal to luma width. - // 2. If this handle points to 420 image then this value must be larger than - // or equal to (luma width / 2). - // 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; -}; - -/* - * Holds information for compressed image or gain map. - */ -struct jpegr_compressed_struct { - // Pointer to the data location. - void* data; - // Used data length in bytes. - int length; - // Maximum available data length in bytes. - int maxLength; - // Color gamut. - ultrahdr_color_gamut colorGamut; -}; - -/* - * Holds information for EXIF metadata. - */ -struct jpegr_exif_struct { - // Pointer to the data location. - void* data; - // Data length; - size_t length; -}; - -typedef struct jpegr_uncompressed_struct* jr_uncompressed_ptr; -typedef struct jpegr_compressed_struct* jr_compressed_ptr; -typedef struct jpegr_exif_struct* jr_exif_ptr; -typedef struct jpegr_info_struct* jr_info_ptr; - -class JpegR { -public: - /* - * Experimental only - * - * Encode API-0 - * Compress JPEGR image from 10-bit HDR YUV. - * - * Tonemap the HDR input to a SDR image, generate gain map from the HDR and SDR images, - * compress SDR YUV to 8-bit JPEG and append the gain map to the end of the compressed - * JPEG. - * @param p010_image_ptr uncompressed HDR image in P010 color format - * @param hdr_tf transfer function of the HDR image - * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength} - * represents the maximum available size of the destination buffer, and it must be - * set before calling this method. If the encoded JPEGR size exceeds - * {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}. - * @param quality target quality of the JPEG encoding, must be in range of 0-100 where 100 is - * the highest quality - * @param exif pointer to the exif metadata. - * @return NO_ERROR if encoding succeeds, error code if error occurs. - */ - status_t encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, ultrahdr_transfer_function hdr_tf, - jr_compressed_ptr dest, int quality, jr_exif_ptr exif); - - /* - * Encode API-1 - * Compress JPEGR image from 10-bit HDR YUV and 8-bit SDR YUV. - * - * Generate gain map from the HDR and SDR inputs, compress SDR YUV to 8-bit JPEG and append - * the gain map to the end of the compressed JPEG. HDR and SDR inputs must be the same - * resolution. SDR input is assumed to use the sRGB transfer function. - * @param p010_image_ptr uncompressed HDR image in P010 color format - * @param yuv420_image_ptr uncompressed SDR image in YUV_420 color format - * @param hdr_tf transfer function of the HDR image - * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength} - * represents the maximum available size of the desitination buffer, and it must be - * set before calling this method. If the encoded JPEGR size exceeds - * {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}. - * @param quality target quality of the JPEG encoding, must be in range of 0-100 where 100 is - * the highest quality - * @param exif pointer to the exif metadata. - * @return NO_ERROR if encoding succeeds, error code if error occurs. - */ - status_t encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, jr_uncompressed_ptr yuv420_image_ptr, - ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest, int quality, - jr_exif_ptr exif); - - /* - * Encode API-2 - * Compress JPEGR image from 10-bit HDR YUV, 8-bit SDR YUV and compressed 8-bit JPEG. - * - * This method requires HAL Hardware JPEG encoder. - * - * Generate gain map from the HDR and SDR inputs, append the gain map to the end of the - * compressed JPEG. Adds an ICC profile if one isn't present in the input JPEG image. HDR and - * SDR inputs must be the same resolution and color space. SDR image is assumed to use the sRGB - * transfer function. - * @param p010_image_ptr uncompressed HDR image in P010 color format - * @param yuv420_image_ptr uncompressed SDR image in YUV_420 color format - * @param yuv420jpg_image_ptr SDR image compressed in jpeg format - * @param hdr_tf transfer function of the HDR image - * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength} - * represents the maximum available size of the desitination buffer, and it must be - * set before calling this method. If the encoded JPEGR size exceeds - * {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}. - * @return NO_ERROR if encoding succeeds, error code if error occurs. - */ - status_t encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, jr_uncompressed_ptr yuv420_image_ptr, - jr_compressed_ptr yuv420jpg_image_ptr, ultrahdr_transfer_function hdr_tf, - jr_compressed_ptr dest); - - /* - * Encode API-3 - * Compress JPEGR image from 10-bit HDR YUV and 8-bit SDR YUV. - * - * This method requires HAL Hardware JPEG encoder. - * - * Decode the compressed 8-bit JPEG image to YUV SDR, generate gain map from the HDR input - * and the decoded SDR result, append the gain map to the end of the compressed JPEG. Adds an - * ICC profile if one isn't present in the input JPEG image. HDR and SDR inputs must be the same - * resolution. JPEG image is assumed to use the sRGB transfer function. - * @param p010_image_ptr uncompressed HDR image in P010 color format - * @param yuv420jpg_image_ptr SDR image compressed in jpeg format - * @param hdr_tf transfer function of the HDR image - * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength} - * represents the maximum available size of the desitination buffer, and it must be - * set before calling this method. If the encoded JPEGR size exceeds - * {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}. - * @return NO_ERROR if encoding succeeds, error code if error occurs. - */ - status_t encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, jr_compressed_ptr yuv420jpg_image_ptr, - ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest); - - /* - * Encode API-4 - * Assemble JPEGR image from SDR JPEG and gainmap JPEG. - * - * Assemble the primary JPEG image, the gain map and the metadata to JPEG/R format. Adds an ICC - * profile if one isn't present in the input JPEG image. - * @param yuv420jpg_image_ptr SDR image compressed in jpeg format - * @param gainmapjpg_image_ptr gain map image compressed in jpeg format - * @param metadata metadata to be written in XMP of the primary jpeg - * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength} - * represents the maximum available size of the desitination buffer, and it must be - * set before calling this method. If the encoded JPEGR size exceeds - * {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}. - * @return NO_ERROR if encoding succeeds, error code if error occurs. - */ - status_t encodeJPEGR(jr_compressed_ptr yuv420jpg_image_ptr, - jr_compressed_ptr gainmapjpg_image_ptr, ultrahdr_metadata_ptr metadata, - jr_compressed_ptr dest); - - /* - * Decode API - * Decompress JPEGR image. - * - * This method assumes that the JPEGR image contains an ICC profile with primaries that match - * those of a color gamut that this library is aware of; Bt.709, Display-P3, or Bt.2100. It also - * assumes the base image uses the sRGB transfer function. - * - * This method only supports single gain map metadata values for fields that allow multi-channel - * metadata values. - * @param jpegr_image_ptr compressed JPEGR image. - * @param dest destination of the uncompressed JPEGR image. - * @param max_display_boost (optional) the maximum available boost supported by a display, - * the value must be greater than or equal to 1.0. - * @param exif destination of the decoded EXIF metadata. The default value is NULL where the - decoder will do nothing about it. If configured not NULL the decoder will write - EXIF data into this structure. The format is defined in {@code jpegr_exif_struct} - * @param output_format flag for setting output color format. Its value configures the output - color format. The default value is {@code JPEGR_OUTPUT_HDR_LINEAR}. - ---------------------------------------------------------------------- - | output_format | decoded color format to be written | - ---------------------------------------------------------------------- - | JPEGR_OUTPUT_SDR | RGBA_8888 | - ---------------------------------------------------------------------- - | JPEGR_OUTPUT_HDR_LINEAR | (default)RGBA_F16 linear | - ---------------------------------------------------------------------- - | JPEGR_OUTPUT_HDR_PQ | RGBA_1010102 PQ | - ---------------------------------------------------------------------- - | JPEGR_OUTPUT_HDR_HLG | RGBA_1010102 HLG | - ---------------------------------------------------------------------- - * @param gainmap_image_ptr destination of the decoded gain map. The default value is NULL - where the decoder will do nothing about it. If configured not NULL - the decoder will write the decoded gain_map data into this - structure. The format is defined in - {@code jpegr_uncompressed_struct}. - * @param metadata destination of the decoded metadata. The default value is NULL where the - decoder will do nothing about it. If configured not NULL the decoder will - write metadata into this structure. the format of metadata is defined in - {@code ultrahdr_metadata_struct}. - * @return NO_ERROR if decoding succeeds, error code if error occurs. - */ - status_t decodeJPEGR(jr_compressed_ptr jpegr_image_ptr, jr_uncompressed_ptr dest, - float max_display_boost = FLT_MAX, jr_exif_ptr exif = nullptr, - ultrahdr_output_format output_format = ULTRAHDR_OUTPUT_HDR_LINEAR, - jr_uncompressed_ptr gainmap_image_ptr = nullptr, - ultrahdr_metadata_ptr metadata = nullptr); - - /* - * Gets Info from JPEGR file without decoding it. - * - * This method only supports single gain map metadata values for fields that allow multi-channel - * metadata values. - * - * The output is filled jpegr_info structure - * @param jpegr_image_ptr compressed JPEGR image - * @param jpeg_image_info_ptr pointer to jpegr info struct. Members of jpegr_info - * are owned by the caller - * @return NO_ERROR if JPEGR parsing succeeds, error code otherwise - */ - status_t getJPEGRInfo(jr_compressed_ptr jpegr_image_ptr, jr_info_ptr jpeg_image_info_ptr); - -protected: - /* - * This method is called in the encoding pipeline. It will take the uncompressed 8-bit and - * 10-bit yuv images as input, and calculate the uncompressed gain map. The input images - * must be the same resolution. The SDR input is assumed to use the sRGB transfer function. - * - * @param yuv420_image_ptr uncompressed SDR image in YUV_420 color format - * @param p010_image_ptr uncompressed HDR image in P010 color format - * @param hdr_tf transfer function of the HDR image - * @param metadata everything but "version" is filled in this struct - * @param dest location at which gain map image is stored (caller responsible for memory - of data). - * @param sdr_is_601 if true, then use BT.601 decoding of YUV regardless of SDR image gamut - * @return NO_ERROR if calculation succeeds, error code if error occurs. - */ - status_t generateGainMap(jr_uncompressed_ptr yuv420_image_ptr, - jr_uncompressed_ptr p010_image_ptr, ultrahdr_transfer_function hdr_tf, - ultrahdr_metadata_ptr metadata, jr_uncompressed_ptr dest, - bool sdr_is_601 = false); - - /* - * This method is called in the decoding pipeline. It will take the uncompressed (decoded) - * 8-bit yuv image, the uncompressed (decoded) gain map, and extracted JPEG/R metadata as - * input, and calculate the 10-bit recovered image. The recovered output image is the same - * color gamut as the SDR image, with HLG transfer function, and is in RGBA1010102 data format. - * The SDR image is assumed to use the sRGB transfer function. The SDR image is also assumed to - * be a decoded JPEG for the purpose of YUV interpration. - * - * @param yuv420_image_ptr uncompressed SDR image in YUV_420 color format - * @param gainmap_image_ptr pointer to uncompressed gain map image struct. - * @param metadata JPEG/R metadata extracted from XMP. - * @param output_format flag for setting output color format. if set to - * {@code JPEGR_OUTPUT_SDR}, decoder will only decode the primary image - * which is SDR. Default value is JPEGR_OUTPUT_HDR_LINEAR. - * @param max_display_boost the maximum available boost supported by a display - * @param dest reconstructed HDR image - * @return NO_ERROR if calculation succeeds, error code if error occurs. - */ - status_t applyGainMap(jr_uncompressed_ptr yuv420_image_ptr, - jr_uncompressed_ptr gainmap_image_ptr, ultrahdr_metadata_ptr metadata, - ultrahdr_output_format output_format, float max_display_boost, - jr_uncompressed_ptr dest); - -private: - /* - * This method is called in the encoding pipeline. It will encode the gain map. - * - * @param gainmap_image_ptr pointer to uncompressed gain map image struct - * @param jpeg_enc_obj_ptr helper resource to compress gain map - * @return NO_ERROR if encoding succeeds, error code if error occurs. - */ - status_t compressGainMap(jr_uncompressed_ptr gainmap_image_ptr, - JpegEncoderHelper* jpeg_enc_obj_ptr); - - /* - * This method is called to separate primary image and gain map image from JPEGR - * - * @param jpegr_image_ptr pointer to compressed JPEGR image. - * @param primary_jpg_image_ptr destination of primary image - * @param gainmap_jpg_image_ptr destination of compressed gain map image - * @return NO_ERROR if calculation succeeds, error code if error occurs. - */ - status_t extractPrimaryImageAndGainMap(jr_compressed_ptr jpegr_image_ptr, - jr_compressed_ptr primary_jpg_image_ptr, - jr_compressed_ptr gainmap_jpg_image_ptr); - - /* - * This method is called in the encoding pipeline. It will take the standard 8-bit JPEG image, - * the compressed gain map and optionally the exif package as inputs, and generate the XMP - * metadata, and finally append everything in the order of: - * SOI, APP2(EXIF) (if EXIF is from outside), APP2(XMP), primary image, gain map - * - * Note that in the final JPEG/R output, EXIF package will appear if ONLY ONE of the following - * conditions is fulfilled: - * (1) EXIF package is available from outside input. I.e. pExif != nullptr. - * (2) Input JPEG has EXIF. - * If both conditions are fulfilled, this method will return ERROR_JPEGR_INVALID_INPUT_TYPE - * - * @param primary_jpg_image_ptr destination of primary image - * @param gainmap_jpg_image_ptr destination of compressed gain map image - * @param (nullable) pExif EXIF package - * @param (nullable) pIcc ICC package - * @param icc_size length in bytes of ICC package - * @param metadata JPEG/R metadata to encode in XMP of the jpeg - * @param dest compressed JPEGR image - * @return NO_ERROR if calculation succeeds, error code if error occurs. - */ - status_t appendGainMap(jr_compressed_ptr primary_jpg_image_ptr, - jr_compressed_ptr gainmap_jpg_image_ptr, jr_exif_ptr pExif, void* pIcc, - size_t icc_size, ultrahdr_metadata_ptr metadata, jr_compressed_ptr dest); - - /* - * This method will tone map a HDR image to an SDR image. - * - * @param src pointer to uncompressed HDR image struct. HDR image is expected to be - * in p010 color format - * @param dest pointer to store tonemapped SDR image - */ - status_t toneMap(jr_uncompressed_ptr src, jr_uncompressed_ptr dest); - - /* - * This method will convert a YUV420 image from one YUV encoding to another in-place (eg. - * Bt.709 to Bt.601 YUV encoding). - * - * src_encoding and dest_encoding indicate the encoding via the YUV conversion defined for that - * gamut. P3 indicates Rec.601, since this is how DataSpace encodes Display-P3 YUV data. - * - * @param image the YUV420 image to convert - * @param src_encoding input YUV encoding - * @param dest_encoding output YUV encoding - * @return NO_ERROR if calculation succeeds, error code if error occurs. - */ - status_t convertYuv(jr_uncompressed_ptr image, ultrahdr_color_gamut src_encoding, - ultrahdr_color_gamut dest_encoding); - - /* - * This method will check the validity of the input arguments. - * - * @param p010_image_ptr uncompressed HDR image in P010 color format - * @param yuv420_image_ptr pointer to uncompressed SDR image struct. HDR image is expected to - * be in 420p color format - * @param hdr_tf transfer function of the HDR image - * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength} - * represents the maximum available size of the desitination buffer, and it must be - * set before calling this method. If the encoded JPEGR size exceeds - * {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}. - * @return NO_ERROR if the input args are valid, error code is not valid. - */ - status_t areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr, - jr_uncompressed_ptr yuv420_image_ptr, - ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest_ptr); - - /* - * This method will check the validity of the input arguments. - * - * @param p010_image_ptr uncompressed HDR image in P010 color format - * @param yuv420_image_ptr pointer to uncompressed SDR image struct. HDR image is expected to - * be in 420p color format - * @param hdr_tf transfer function of the HDR image - * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength} - * represents the maximum available size of the destination buffer, and it must be - * set before calling this method. If the encoded JPEGR size exceeds - * {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}. - * @param quality target quality of the JPEG encoding, must be in range of 0-100 where 100 is - * the highest quality - * @return NO_ERROR if the input args are valid, error code is not valid. - */ - status_t areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr, - jr_uncompressed_ptr yuv420_image_ptr, - ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest, - int quality); -}; -} // namespace ultrahdr - -#endif // ULTRAHDR_JPEGR_H diff --git a/include/ultrahdr/ultrahdrcommon.h b/include/ultrahdr/ultrahdrcommon.h deleted file mode 100644 index 423c8c4..0000000 --- a/include/ultrahdr/ultrahdrcommon.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2023 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_ULTRAHDRCOMMON_H -#define ULTRAHDR_ULTRAHDRCOMMON_H - -//#define LOG_NDEBUG 0 - -#ifdef __ANDROID__ - #include "log/log.h" -#else - #ifdef LOG_NDEBUG - #include <cstdio> - - #define ALOGD(...) \ - do { \ - fprintf(stderr, __VA_ARGS__); \ - fprintf(stderr, "\n"); \ - } while (0) - #define ALOGE(...) \ - do { \ - fprintf(stderr, __VA_ARGS__); \ - fprintf(stderr, "\n"); \ - } while (0) - #define ALOGI(...) \ - do { \ - fprintf(stdout, __VA_ARGS__); \ - fprintf(stdout, "\n"); \ - } while (0) - #define ALOGV(...) \ - do { \ - fprintf(stdout, __VA_ARGS__); \ - fprintf(stdout, "\n"); \ - } while (0) - #define ALOGW(...) \ - do { \ - fprintf(stderr, __VA_ARGS__); \ - fprintf(stderr, "\n"); \ - } while (0) - #else - #define ALOGD(...) ((void)0) - #define ALOGE(...) ((void)0) - #define ALOGI(...) ((void)0) - #define ALOGV(...) ((void)0) - #define ALOGW(...) ((void)0) - #endif -#endif - -#define ALIGNM(x, m) ((((x) + ((m) - 1)) / (m)) * (m)) - -#endif // ULTRAHDR_ULTRAHDRCOMMON_H diff --git a/jpegdecoderhelper.cpp b/jpegdecoderhelper.cpp deleted file mode 100644 index 53e5b7e..0000000 --- a/jpegdecoderhelper.cpp +++ /dev/null @@ -1,567 +0,0 @@ -/* - * Copyright 2022 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 <errno.h> -#include <setjmp.h> - -#include <cstring> - -#include "ultrahdr/ultrahdrcommon.h" -#include "ultrahdr/ultrahdr.h" -#include "ultrahdr/jpegdecoderhelper.h" - -using namespace std; - -namespace ultrahdr { - -const uint32_t kAPP0Marker = JPEG_APP0; // JFIF -const uint32_t kAPP1Marker = JPEG_APP0 + 1; // EXIF, XMP -const uint32_t kAPP2Marker = JPEG_APP0 + 2; // ICC - -constexpr uint32_t kICCMarkerHeaderSize = 14; -constexpr uint8_t kICCSig[] = { - 'I', 'C', 'C', '_', 'P', 'R', 'O', 'F', 'I', 'L', 'E', '\0', -}; -constexpr uint8_t kXmpNameSpace[] = { - 'h', 't', 't', 'p', ':', '/', '/', 'n', 's', '.', 'a', 'd', 'o', 'b', 'e', - '.', 'c', 'o', 'm', '/', 'x', 'a', 'p', '/', '1', '.', '0', '/', '\0', -}; -constexpr uint8_t kExifIdCode[] = { - 'E', 'x', 'i', 'f', '\0', '\0', -}; - -struct jpegr_source_mgr : jpeg_source_mgr { - jpegr_source_mgr(const uint8_t* ptr, int len); - ~jpegr_source_mgr(); - - const uint8_t* mBufferPtr; - size_t mBufferLength; -}; - -struct jpegrerror_mgr { - struct jpeg_error_mgr pub; - jmp_buf setjmp_buffer; -}; - -static void jpegr_init_source(j_decompress_ptr cinfo) { - jpegr_source_mgr* src = static_cast<jpegr_source_mgr*>(cinfo->src); - src->next_input_byte = static_cast<const JOCTET*>(src->mBufferPtr); - src->bytes_in_buffer = src->mBufferLength; -} - -static boolean jpegr_fill_input_buffer(j_decompress_ptr /* cinfo */) { - ALOGE("%s : should not get here", __func__); - return FALSE; -} - -static void jpegr_skip_input_data(j_decompress_ptr cinfo, long num_bytes) { - jpegr_source_mgr* src = static_cast<jpegr_source_mgr*>(cinfo->src); - - if (num_bytes > static_cast<long>(src->bytes_in_buffer)) { - ALOGE("jpegr_skip_input_data - num_bytes > (long)src->bytes_in_buffer"); - } else { - src->next_input_byte += num_bytes; - src->bytes_in_buffer -= num_bytes; - } -} - -static void jpegr_term_source(j_decompress_ptr /*cinfo*/) {} - -jpegr_source_mgr::jpegr_source_mgr(const uint8_t* ptr, int len) - : mBufferPtr(ptr), mBufferLength(len) { - init_source = jpegr_init_source; - fill_input_buffer = jpegr_fill_input_buffer; - skip_input_data = jpegr_skip_input_data; - resync_to_restart = jpeg_resync_to_restart; - term_source = jpegr_term_source; -} - -jpegr_source_mgr::~jpegr_source_mgr() {} - -static void jpegrerror_exit(j_common_ptr cinfo) { - jpegrerror_mgr* err = reinterpret_cast<jpegrerror_mgr*>(cinfo->err); - longjmp(err->setjmp_buffer, 1); -} - -static void output_message(j_common_ptr cinfo) { - char buffer[JMSG_LENGTH_MAX]; - - /* Create the message */ - (*cinfo->err->format_message)(cinfo, buffer); - ALOGE("%s\n", buffer); -} - -JpegDecoderHelper::JpegDecoderHelper() {} - -JpegDecoderHelper::~JpegDecoderHelper() {} - -bool JpegDecoderHelper::decompressImage(const void* image, int length, bool decodeToRGBA) { - if (image == nullptr || length <= 0) { - ALOGE("Image size can not be handled: %d", length); - return false; - } - mResultBuffer.clear(); - mXMPBuffer.clear(); - return decode(image, length, decodeToRGBA); -} - -void* JpegDecoderHelper::getDecompressedImagePtr() { - return mResultBuffer.data(); -} - -size_t JpegDecoderHelper::getDecompressedImageSize() { - return mResultBuffer.size(); -} - -void* JpegDecoderHelper::getXMPPtr() { - return mXMPBuffer.data(); -} - -size_t JpegDecoderHelper::getXMPSize() { - return mXMPBuffer.size(); -} - -void* JpegDecoderHelper::getEXIFPtr() { - return mEXIFBuffer.data(); -} - -size_t JpegDecoderHelper::getEXIFSize() { - return mEXIFBuffer.size(); -} - -void* JpegDecoderHelper::getICCPtr() { - return mICCBuffer.data(); -} - -size_t JpegDecoderHelper::getICCSize() { - return mICCBuffer.size(); -} - -size_t JpegDecoderHelper::getDecompressedImageWidth() { - return mWidth; -} - -size_t JpegDecoderHelper::getDecompressedImageHeight() { - return mHeight; -} - -// Here we only handle the first EXIF package, and in theary EXIF (or JFIF) must be the first -// in the image file. -// We assume that all packages are starting with two bytes marker (eg FF E1 for EXIF package), -// two bytes of package length which is stored in marker->original_length, and the real data -// which is stored in marker->data. -bool JpegDecoderHelper::extractEXIF(const void* image, int length) { - jpeg_decompress_struct cinfo; - jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length); - jpegrerror_mgr myerr; - - cinfo.err = jpeg_std_error(&myerr.pub); - myerr.pub.error_exit = jpegrerror_exit; - myerr.pub.output_message = output_message; - - if (setjmp(myerr.setjmp_buffer)) { - jpeg_destroy_decompress(&cinfo); - return false; - } - jpeg_create_decompress(&cinfo); - - jpeg_save_markers(&cinfo, kAPP0Marker, 0xFFFF); - jpeg_save_markers(&cinfo, kAPP1Marker, 0xFFFF); - - cinfo.src = &mgr; - jpeg_read_header(&cinfo, TRUE); - - size_t pos = 2; // position after SOI - for (jpeg_marker_struct* marker = cinfo.marker_list; - marker; - marker = marker->next) { - - pos += 4; - pos += marker->original_length; - - if (marker->marker != kAPP1Marker) { - continue; - } - - const unsigned int len = marker->data_length; - - if (len > sizeof(kExifIdCode) && - !memcmp(marker->data, kExifIdCode, sizeof(kExifIdCode))) { - mEXIFBuffer.resize(len, 0); - memcpy(static_cast<void*>(mEXIFBuffer.data()), marker->data, len); - mExifPos = pos - marker->original_length; - break; - } - } - - jpeg_destroy_decompress(&cinfo); - return true; -} - -bool JpegDecoderHelper::decode(const void* image, int length, bool decodeToRGBA) { - bool status = true; - jpeg_decompress_struct cinfo; - jpegrerror_mgr myerr; - cinfo.err = jpeg_std_error(&myerr.pub); - myerr.pub.error_exit = jpegrerror_exit; - myerr.pub.output_message = output_message; - - if (setjmp(myerr.setjmp_buffer)) { - jpeg_destroy_decompress(&cinfo); - return false; - } - - jpeg_create_decompress(&cinfo); - - jpeg_save_markers(&cinfo, kAPP0Marker, 0xFFFF); - jpeg_save_markers(&cinfo, kAPP1Marker, 0xFFFF); - jpeg_save_markers(&cinfo, kAPP2Marker, 0xFFFF); - - jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length); - cinfo.src = &mgr; - if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK) { - jpeg_destroy_decompress(&cinfo); - return false; - } - - // Save XMP data, EXIF data, and ICC data. - // Here we only handle the first XMP / EXIF / ICC package. - // We assume that all packages are starting with two bytes marker (eg FF E1 for EXIF package), - // two bytes of package length which is stored in marker->original_length, and the real data - // which is stored in marker->data. - bool exifAppears = false; - bool xmpAppears = false; - bool iccAppears = false; - size_t pos = 2; // position after SOI - for (jpeg_marker_struct* marker = cinfo.marker_list; - marker && !(exifAppears && xmpAppears && iccAppears); - marker = marker->next) { - pos += 4; - pos += marker->original_length; - if (marker->marker != kAPP1Marker && marker->marker != kAPP2Marker) { - continue; - } - const unsigned int len = marker->data_length; - if (!xmpAppears && - len > sizeof(kXmpNameSpace) && - !memcmp(marker->data, kXmpNameSpace, sizeof(kXmpNameSpace))) { - mXMPBuffer.resize(len+1, 0); - memcpy(static_cast<void*>(mXMPBuffer.data()), marker->data, len); - xmpAppears = true; - } else if (!exifAppears && - len > sizeof(kExifIdCode) && - !memcmp(marker->data, kExifIdCode, sizeof(kExifIdCode))) { - mEXIFBuffer.resize(len, 0); - memcpy(static_cast<void*>(mEXIFBuffer.data()), marker->data, len); - exifAppears = true; - mExifPos = pos - marker->original_length; - } else if (!iccAppears && - len > sizeof(kICCSig) && - !memcmp(marker->data, kICCSig, sizeof(kICCSig))) { - mICCBuffer.resize(len, 0); - memcpy(static_cast<void*>(mICCBuffer.data()), marker->data, len); - iccAppears = true; - } - } - - mWidth = cinfo.image_width; - mHeight = cinfo.image_height; - if (mWidth > kMaxWidth || mHeight > kMaxHeight) { - status = false; - goto CleanUp; - } - - if (decodeToRGBA) { - // The primary image is expected to be yuv420 sampling - if (cinfo.jpeg_color_space != JCS_YCbCr) { - status = false; - ALOGE("%s: decodeToRGBA unexpected jpeg color space ", __func__); - goto CleanUp; - } - if (cinfo.comp_info[0].h_samp_factor != 2 || cinfo.comp_info[0].v_samp_factor != 2 || - cinfo.comp_info[1].h_samp_factor != 1 || cinfo.comp_info[1].v_samp_factor != 1 || - cinfo.comp_info[2].h_samp_factor != 1 || cinfo.comp_info[2].v_samp_factor != 1) { - status = false; - ALOGE("%s: decodeToRGBA unexpected primary image sub-sampling", __func__); - goto CleanUp; - } - // 4 bytes per pixel - mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 4); - cinfo.out_color_space = JCS_EXT_RGBA; - } else { - if (cinfo.jpeg_color_space == JCS_YCbCr) { - if (cinfo.comp_info[0].h_samp_factor != 2 || cinfo.comp_info[0].v_samp_factor != 2 || - cinfo.comp_info[1].h_samp_factor != 1 || cinfo.comp_info[1].v_samp_factor != 1 || - cinfo.comp_info[2].h_samp_factor != 1 || cinfo.comp_info[2].v_samp_factor != 1) { - status = false; - ALOGE("%s: decoding to YUV only supports 4:2:0 subsampling", __func__); - goto CleanUp; - } - mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 3 / 2, 0); - } else if (cinfo.jpeg_color_space == JCS_GRAYSCALE) { - mResultBuffer.resize(cinfo.image_width * cinfo.image_height, 0); - } else { - status = false; - ALOGE("%s: decodeToYUV unexpected jpeg color space", __func__); - goto CleanUp; - } - cinfo.out_color_space = cinfo.jpeg_color_space; - cinfo.raw_data_out = TRUE; - } - - cinfo.dct_method = JDCT_ISLOW; - jpeg_start_decompress(&cinfo); - if (!decompress(&cinfo, static_cast<const uint8_t*>(mResultBuffer.data()), - cinfo.jpeg_color_space == JCS_GRAYSCALE)) { - status = false; - goto CleanUp; - } - -CleanUp: - jpeg_finish_decompress(&cinfo); - jpeg_destroy_decompress(&cinfo); - - return status; -} - -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)); -} - -bool JpegDecoderHelper::getCompressedImageParameters(const void* image, int length, size_t* pWidth, - size_t* pHeight, std::vector<uint8_t>* iccData, - std::vector<uint8_t>* exifData) { - jpeg_decompress_struct cinfo; - jpegrerror_mgr myerr; - cinfo.err = jpeg_std_error(&myerr.pub); - myerr.pub.error_exit = jpegrerror_exit; - myerr.pub.output_message = output_message; - - if (setjmp(myerr.setjmp_buffer)) { - jpeg_destroy_decompress(&cinfo); - return false; - } - jpeg_create_decompress(&cinfo); - - jpeg_save_markers(&cinfo, kAPP1Marker, 0xFFFF); - jpeg_save_markers(&cinfo, kAPP2Marker, 0xFFFF); - - jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length); - cinfo.src = &mgr; - if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK) { - jpeg_destroy_decompress(&cinfo); - return false; - } - - if (pWidth != nullptr) { - *pWidth = cinfo.image_width; - } - if (pHeight != nullptr) { - *pHeight = cinfo.image_height; - } - - if (iccData != nullptr) { - for (jpeg_marker_struct* marker = cinfo.marker_list; marker; marker = marker->next) { - if (marker->marker != kAPP2Marker) { - continue; - } - if (marker->data_length <= kICCMarkerHeaderSize || - memcmp(marker->data, kICCSig, sizeof(kICCSig)) != 0) { - continue; - } - - iccData->insert(iccData->end(), marker->data, marker->data + marker->data_length); - } - } - - if (exifData != nullptr) { - bool exifAppears = false; - for (jpeg_marker_struct* marker = cinfo.marker_list; marker && !exifAppears; - marker = marker->next) { - if (marker->marker != kAPP1Marker) { - continue; - } - - const unsigned int len = marker->data_length; - if (len >= sizeof(kExifIdCode) && - !memcmp(marker->data, kExifIdCode, sizeof(kExifIdCode))) { - exifData->resize(len, 0); - memcpy(static_cast<void*>(exifData->data()), marker->data, len); - exifAppears = true; - } - } - } - - jpeg_destroy_decompress(&cinfo); - return true; -} - -bool JpegDecoderHelper::decompressRGBA(jpeg_decompress_struct* cinfo, const uint8_t* dest) { - JSAMPLE* out = (JSAMPLE*)dest; - - while (cinfo->output_scanline < cinfo->image_height) { - if (1 != jpeg_read_scanlines(cinfo, &out, 1)) return false; - out += cinfo->image_width * 4; - } - return true; -} - -bool JpegDecoderHelper::decompressYUV(jpeg_decompress_struct* cinfo, const uint8_t* dest) { - size_t luma_plane_size = cinfo->image_width * cinfo->image_height; - size_t chroma_plane_size = luma_plane_size / 4; - uint8_t* y_plane = const_cast<uint8_t*>(dest); - uint8_t* u_plane = const_cast<uint8_t*>(dest + luma_plane_size); - uint8_t* v_plane = const_cast<uint8_t*>(dest + luma_plane_size + chroma_plane_size); - - const size_t aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize); - const bool is_width_aligned = (aligned_width == cinfo->image_width); - uint8_t* y_plane_intrm = nullptr; - uint8_t* u_plane_intrm = nullptr; - uint8_t* v_plane_intrm = nullptr; - - JSAMPROW y[kCompressBatchSize]; - JSAMPROW cb[kCompressBatchSize / 2]; - JSAMPROW cr[kCompressBatchSize / 2]; - JSAMPARRAY planes[3]{y, cb, cr}; - JSAMPROW y_intrm[kCompressBatchSize]; - JSAMPROW cb_intrm[kCompressBatchSize / 2]; - JSAMPROW cr_intrm[kCompressBatchSize / 2]; - JSAMPARRAY planes_intrm[3]{y_intrm, cb_intrm, cr_intrm}; - - if (cinfo->image_height % kCompressBatchSize != 0) { - mEmpty = std::make_unique<uint8_t[]>(aligned_width); - } - - if (!is_width_aligned) { - size_t mcu_row_size = aligned_width * kCompressBatchSize * 3 / 2; - mBufferIntermediate = std::make_unique<uint8_t[]>(mcu_row_size); - y_plane_intrm = mBufferIntermediate.get(); - u_plane_intrm = y_plane_intrm + (aligned_width * kCompressBatchSize); - v_plane_intrm = u_plane_intrm + (aligned_width * kCompressBatchSize) / 4; - for (int i = 0; i < kCompressBatchSize; ++i) { - y_intrm[i] = y_plane_intrm + i * aligned_width; - } - for (int i = 0; i < kCompressBatchSize / 2; ++i) { - int offset_intrm = i * (aligned_width / 2); - cb_intrm[i] = u_plane_intrm + offset_intrm; - cr_intrm[i] = v_plane_intrm + offset_intrm; - } - } - - while (cinfo->output_scanline < cinfo->image_height) { - size_t scanline_copy = cinfo->output_scanline; - for (int i = 0; i < kCompressBatchSize; ++i) { - size_t scanline = cinfo->output_scanline + i; - if (scanline < cinfo->image_height) { - y[i] = y_plane + scanline * cinfo->image_width; - } else { - y[i] = mEmpty.get(); - } - } - // cb, cr only have half scanlines - for (int i = 0; i < kCompressBatchSize / 2; ++i) { - size_t scanline = cinfo->output_scanline / 2 + i; - if (scanline < cinfo->image_height / 2) { - int offset = scanline * (cinfo->image_width / 2); - cb[i] = u_plane + offset; - cr[i] = v_plane + offset; - } else { - cb[i] = cr[i] = mEmpty.get(); - } - } - - int processed = jpeg_read_raw_data(cinfo, is_width_aligned ? planes : planes_intrm, - kCompressBatchSize); - if (processed != kCompressBatchSize) { - ALOGE("Number of processed lines does not equal input lines."); - return false; - } - if (!is_width_aligned) { - for (int i = 0; i < kCompressBatchSize; ++i) { - if (scanline_copy + i < cinfo->image_height) { - memcpy(y[i], y_intrm[i], cinfo->image_width); - } - } - for (int i = 0; i < kCompressBatchSize / 2; ++i) { - if (((scanline_copy / 2) + i) < (cinfo->image_height / 2)) { - memcpy(cb[i], cb_intrm[i], cinfo->image_width / 2); - memcpy(cr[i], cr_intrm[i], cinfo->image_width / 2); - } - } - } - } - return true; -} - -bool JpegDecoderHelper::decompressSingleChannel(jpeg_decompress_struct* cinfo, - const uint8_t* dest) { - uint8_t* y_plane = const_cast<uint8_t*>(dest); - uint8_t* y_plane_intrm = nullptr; - - const size_t aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize); - const bool is_width_aligned = (aligned_width == cinfo->image_width); - - JSAMPROW y[kCompressBatchSize]; - JSAMPARRAY planes[1]{y}; - JSAMPROW y_intrm[kCompressBatchSize]; - JSAMPARRAY planes_intrm[1]{y_intrm}; - - if (cinfo->image_height % kCompressBatchSize != 0) { - mEmpty = std::make_unique<uint8_t[]>(aligned_width); - } - - if (!is_width_aligned) { - size_t mcu_row_size = aligned_width * kCompressBatchSize; - mBufferIntermediate = std::make_unique<uint8_t[]>(mcu_row_size); - y_plane_intrm = mBufferIntermediate.get(); - for (int i = 0; i < kCompressBatchSize; ++i) { - y_intrm[i] = y_plane_intrm + i * aligned_width; - } - } - - while (cinfo->output_scanline < cinfo->image_height) { - size_t scanline_copy = cinfo->output_scanline; - for (int i = 0; i < kCompressBatchSize; ++i) { - size_t scanline = cinfo->output_scanline + i; - if (scanline < cinfo->image_height) { - y[i] = y_plane + scanline * cinfo->image_width; - } else { - y[i] = mEmpty.get(); - } - } - - int processed = jpeg_read_raw_data(cinfo, is_width_aligned ? planes : planes_intrm, - kCompressBatchSize); - if (processed != kCompressBatchSize / 2) { - ALOGE("Number of processed lines does not equal input lines."); - return false; - } - if (!is_width_aligned) { - for (int i = 0; i < kCompressBatchSize; ++i) { - if (scanline_copy + i < cinfo->image_height) { - memcpy(y[i], y_intrm[i], cinfo->image_width); - } - } - } - } - return true; -} - -} // namespace ultrahdr diff --git a/jpegencoderhelper.cpp b/jpegencoderhelper.cpp deleted file mode 100644 index 3b55c43..0000000 --- a/jpegencoderhelper.cpp +++ /dev/null @@ -1,294 +0,0 @@ -/* - * Copyright 2022 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 <memory> -#include <string> - -#include "ultrahdr/ultrahdrcommon.h" -#include "ultrahdr/ultrahdr.h" -#include "ultrahdr/jpegencoderhelper.h" - -namespace ultrahdr { - -// The destination manager that can access |mResultBuffer| in JpegEncoderHelper. -struct destination_mgr { - struct jpeg_destination_mgr mgr; - JpegEncoderHelper* encoder; -}; - -JpegEncoderHelper::JpegEncoderHelper() {} - -JpegEncoderHelper::~JpegEncoderHelper() {} - -bool JpegEncoderHelper::compressImage(const uint8_t* yBuffer, const uint8_t* uvBuffer, int width, - int height, int lumaStride, int chromaStride, int quality, - const void* iccBuffer, unsigned int iccSize) { - mResultBuffer.clear(); - if (!encode(yBuffer, uvBuffer, width, height, lumaStride, chromaStride, quality, iccBuffer, - iccSize)) { - return false; - } - ALOGV("Compressed JPEG: %d[%dx%d] -> %zu bytes", (width * height * 12) / 8, width, height, - mResultBuffer.size()); - return true; -} - -void* JpegEncoderHelper::getCompressedImagePtr() { - return mResultBuffer.data(); -} - -size_t JpegEncoderHelper::getCompressedImageSize() { - return mResultBuffer.size(); -} - -void JpegEncoderHelper::initDestination(j_compress_ptr cinfo) { - destination_mgr* dest = reinterpret_cast<destination_mgr*>(cinfo->dest); - std::vector<JOCTET>& buffer = dest->encoder->mResultBuffer; - buffer.resize(kBlockSize); - dest->mgr.next_output_byte = &buffer[0]; - dest->mgr.free_in_buffer = buffer.size(); -} - -boolean JpegEncoderHelper::emptyOutputBuffer(j_compress_ptr cinfo) { - destination_mgr* dest = reinterpret_cast<destination_mgr*>(cinfo->dest); - std::vector<JOCTET>& buffer = dest->encoder->mResultBuffer; - size_t oldsize = buffer.size(); - buffer.resize(oldsize + kBlockSize); - dest->mgr.next_output_byte = &buffer[oldsize]; - dest->mgr.free_in_buffer = kBlockSize; - return true; -} - -void JpegEncoderHelper::terminateDestination(j_compress_ptr cinfo) { - destination_mgr* dest = reinterpret_cast<destination_mgr*>(cinfo->dest); - std::vector<JOCTET>& buffer = dest->encoder->mResultBuffer; - buffer.resize(buffer.size() - dest->mgr.free_in_buffer); -} - -void JpegEncoderHelper::outputErrorMessage(j_common_ptr cinfo) { - char buffer[JMSG_LENGTH_MAX]; - - /* Create the message */ - (*cinfo->err->format_message)(cinfo, buffer); - ALOGE("%s\n", buffer); -} - -bool JpegEncoderHelper::encode(const uint8_t* yBuffer, const uint8_t* uvBuffer, int width, - int height, int lumaStride, int chromaStride, int quality, - const void* iccBuffer, unsigned int iccSize) { - jpeg_compress_struct cinfo; - jpeg_error_mgr jerr; - - cinfo.err = jpeg_std_error(&jerr); - cinfo.err->output_message = &outputErrorMessage; - jpeg_create_compress(&cinfo); - setJpegDestination(&cinfo); - setJpegCompressStruct(width, height, quality, &cinfo, uvBuffer == nullptr); - jpeg_start_compress(&cinfo, TRUE); - if (iccBuffer != nullptr && iccSize > 0) { - jpeg_write_marker(&cinfo, JPEG_APP0 + 2, static_cast<const JOCTET*>(iccBuffer), iccSize); - } - bool status = cinfo.num_components == 1 - ? compressY(&cinfo, yBuffer, lumaStride) - : compressYuv(&cinfo, yBuffer, uvBuffer, lumaStride, chromaStride); - jpeg_finish_compress(&cinfo); - jpeg_destroy_compress(&cinfo); - - return status; -} - -void JpegEncoderHelper::setJpegDestination(jpeg_compress_struct* cinfo) { - destination_mgr* dest = static_cast<struct destination_mgr*>( - (*cinfo->mem->alloc_small)((j_common_ptr)cinfo, JPOOL_PERMANENT, - sizeof(destination_mgr))); - dest->encoder = this; - dest->mgr.init_destination = &initDestination; - dest->mgr.empty_output_buffer = &emptyOutputBuffer; - dest->mgr.term_destination = &terminateDestination; - cinfo->dest = reinterpret_cast<struct jpeg_destination_mgr*>(dest); -} - -void JpegEncoderHelper::setJpegCompressStruct(int width, int height, int quality, - jpeg_compress_struct* cinfo, bool isSingleChannel) { - cinfo->image_width = width; - cinfo->image_height = height; - cinfo->input_components = isSingleChannel ? 1 : 3; - cinfo->in_color_space = isSingleChannel ? JCS_GRAYSCALE : JCS_YCbCr; - jpeg_set_defaults(cinfo); - jpeg_set_quality(cinfo, quality, TRUE); - cinfo->raw_data_in = TRUE; - cinfo->dct_method = JDCT_ISLOW; - cinfo->comp_info[0].h_samp_factor = cinfo->in_color_space == JCS_GRAYSCALE ? 1 : 2; - cinfo->comp_info[0].v_samp_factor = cinfo->in_color_space == JCS_GRAYSCALE ? 1 : 2; - for (int i = 1; i < cinfo->num_components; i++) { - cinfo->comp_info[i].h_samp_factor = 1; - cinfo->comp_info[i].v_samp_factor = 1; - } -} - -bool JpegEncoderHelper::compressYuv(jpeg_compress_struct* cinfo, const uint8_t* yBuffer, - const uint8_t* uvBuffer, int lumaStride, int chromaStride) { - size_t chroma_plane_size = chromaStride * cinfo->image_height / 2; - uint8_t* y_plane = const_cast<uint8_t*>(yBuffer); - uint8_t* u_plane = const_cast<uint8_t*>(uvBuffer); - uint8_t* v_plane = const_cast<uint8_t*>(u_plane + chroma_plane_size); - - const int aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize); - const bool need_luma_padding = (lumaStride < aligned_width); - const int aligned_chroma_width = ALIGNM(cinfo->image_width / 2, kCompressBatchSize / 2); - const bool need_chroma_padding = (chromaStride < aligned_chroma_width); - - std::unique_ptr<uint8_t[]> empty = nullptr; - std::unique_ptr<uint8_t[]> y_mcu_row = nullptr; - std::unique_ptr<uint8_t[]> cb_mcu_row = nullptr; - std::unique_ptr<uint8_t[]> cr_mcu_row = nullptr; - uint8_t* y_mcu_row_ptr = nullptr; - uint8_t* cb_mcu_row_ptr = nullptr; - uint8_t* cr_mcu_row_ptr = nullptr; - - JSAMPROW y[kCompressBatchSize]; - JSAMPROW cb[kCompressBatchSize / 2]; - JSAMPROW cr[kCompressBatchSize / 2]; - JSAMPARRAY planes[3]{y, cb, cr}; - - if (cinfo->image_height % kCompressBatchSize != 0) { - empty = std::make_unique<uint8_t[]>(aligned_width); - memset(empty.get(), 0, aligned_width); - } - - if (need_luma_padding) { - size_t mcu_row_size = aligned_width * kCompressBatchSize; - y_mcu_row = std::make_unique<uint8_t[]>(mcu_row_size); - y_mcu_row_ptr = y_mcu_row.get(); - uint8_t* tmp = y_mcu_row_ptr; - for (int i = 0; i < kCompressBatchSize; ++i, tmp += aligned_width) { - memset(tmp + cinfo->image_width, 0, aligned_width - cinfo->image_width); - } - } - - if (need_chroma_padding) { - size_t mcu_row_size = aligned_chroma_width * kCompressBatchSize / 2; - cb_mcu_row = std::make_unique<uint8_t[]>(mcu_row_size); - cb_mcu_row_ptr = cb_mcu_row.get(); - cr_mcu_row = std::make_unique<uint8_t[]>(mcu_row_size); - cr_mcu_row_ptr = cr_mcu_row.get(); - uint8_t* tmp1 = cb_mcu_row_ptr; - uint8_t* tmp2 = cr_mcu_row_ptr; - for (int i = 0; i < kCompressBatchSize / 2; - ++i, tmp1 += aligned_chroma_width, tmp2 += aligned_chroma_width) { - memset(tmp1 + cinfo->image_width / 2, 0, - aligned_chroma_width - (cinfo->image_width / 2)); - memset(tmp2 + cinfo->image_width / 2, 0, - aligned_chroma_width - (cinfo->image_width / 2)); - } - } - - while (cinfo->next_scanline < cinfo->image_height) { - for (int i = 0; i < kCompressBatchSize; ++i) { - size_t scanline = cinfo->next_scanline + i; - if (scanline < cinfo->image_height) { - y[i] = y_plane + scanline * lumaStride; - if (need_luma_padding) { - uint8_t* tmp = y_mcu_row_ptr + i * aligned_width; - memcpy(tmp, y[i], cinfo->image_width); - y[i] = tmp; - } - } else { - y[i] = empty.get(); - } - } - // cb, cr only have half scanlines - for (int i = 0; i < kCompressBatchSize / 2; ++i) { - size_t scanline = cinfo->next_scanline / 2 + i; - if (scanline < cinfo->image_height / 2) { - int offset = scanline * chromaStride; - cb[i] = u_plane + offset; - cr[i] = v_plane + offset; - if (need_chroma_padding) { - uint8_t* tmp = cb_mcu_row_ptr + i * aligned_chroma_width; - memcpy(tmp, cb[i], cinfo->image_width / 2); - cb[i] = tmp; - tmp = cr_mcu_row_ptr + i * aligned_chroma_width; - memcpy(tmp, cr[i], cinfo->image_width / 2); - cr[i] = tmp; - } - } else { - cb[i] = cr[i] = empty.get(); - } - } - int processed = jpeg_write_raw_data(cinfo, planes, kCompressBatchSize); - if (processed != kCompressBatchSize) { - ALOGE("Number of processed lines does not equal input lines."); - return false; - } - } - return true; -} - -bool JpegEncoderHelper::compressY(jpeg_compress_struct* cinfo, const uint8_t* yBuffer, - int lumaStride) { - uint8_t* y_plane = const_cast<uint8_t*>(yBuffer); - - const int aligned_luma_width = ALIGNM(cinfo->image_width, kCompressBatchSize); - const bool need_luma_padding = (lumaStride < aligned_luma_width); - - std::unique_ptr<uint8_t[]> empty = nullptr; - std::unique_ptr<uint8_t[]> y_mcu_row = nullptr; - uint8_t* y_mcu_row_ptr = nullptr; - - JSAMPROW y[kCompressBatchSize]; - JSAMPARRAY planes[1]{y}; - - if (cinfo->image_height % kCompressBatchSize != 0) { - empty = std::make_unique<uint8_t[]>(aligned_luma_width); - memset(empty.get(), 0, aligned_luma_width); - } - - if (need_luma_padding) { - size_t mcu_row_size = aligned_luma_width * kCompressBatchSize; - y_mcu_row = std::make_unique<uint8_t[]>(mcu_row_size); - y_mcu_row_ptr = y_mcu_row.get(); - uint8_t* tmp = y_mcu_row_ptr; - for (int i = 0; i < kCompressBatchSize; ++i, tmp += aligned_luma_width) { - memset(tmp + cinfo->image_width, 0, aligned_luma_width - cinfo->image_width); - } - } - - while (cinfo->next_scanline < cinfo->image_height) { - for (int i = 0; i < kCompressBatchSize; ++i) { - size_t scanline = cinfo->next_scanline + i; - if (scanline < cinfo->image_height) { - y[i] = y_plane + scanline * lumaStride; - if (need_luma_padding) { - uint8_t* tmp = y_mcu_row_ptr + i * aligned_luma_width; - memcpy(tmp, y[i], cinfo->image_width); - y[i] = tmp; - } - } else { - y[i] = empty.get(); - } - } - int processed = jpeg_write_raw_data(cinfo, planes, kCompressBatchSize); - if (processed != kCompressBatchSize / 2) { - ALOGE("Number of processed lines does not equal input lines."); - return false; - } - } - return true; -} - -} // namespace ultrahdr diff --git a/jpegrutils.cpp b/jpegrutils.cpp deleted file mode 100644 index 8abf249..0000000 --- a/jpegrutils.cpp +++ /dev/null @@ -1,601 +0,0 @@ -/* - * Copyright 2022 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 <algorithm> -#include <cmath> - -#include "ultrahdr/ultrahdrcommon.h" -#include "ultrahdr/jpegr.h" -#include "ultrahdr/jpegrutils.h" - -#include "image_io/xml/xml_reader.h" -#include "image_io/xml/xml_writer.h" -#include "image_io/base/message_handler.h" -#include "image_io/xml/xml_element_rules.h" -#include "image_io/xml/xml_handler.h" -#include "image_io/xml/xml_rule.h" - -using namespace photos_editing_formats::image_io; -using namespace std; - -namespace ultrahdr { -/* - * Helper function used for generating XMP metadata. - * - * @param prefix The prefix part of the name. - * @param suffix The suffix part of the name. - * @return A name of the form "prefix:suffix". - */ -static inline string Name(const string &prefix, const string &suffix) { - std::stringstream ss; - ss << prefix << ":" << suffix; - return ss.str(); -} - -DataStruct::DataStruct(int s) { - data = malloc(s); - length = s; - memset(data, 0, s); - writePos = 0; -} - -DataStruct::~DataStruct() { - if (data != nullptr) { - free(data); - } -} - -void* DataStruct::getData() { - return data; -} - -int DataStruct::getLength() { - return length; -} - -int DataStruct::getBytesWritten() { - return writePos; -} - -bool DataStruct::write8(uint8_t value) { - uint8_t v = value; - return write(&v, 1); -} - -bool DataStruct::write16(uint16_t value) { - uint16_t v = value; - return write(&v, 2); -} -bool DataStruct::write32(uint32_t value) { - uint32_t v = value; - return write(&v, 4); -} - -bool DataStruct::write(const void* src, int size) { - if (writePos + size > length) { - ALOGE("Writing out of boundary: write position: %d, size: %d, capacity: %d", - writePos, size, length); - return false; - } - memcpy((uint8_t*) data + writePos, src, size); - writePos += size; - return true; -} - -/* - * Helper function used for writing data to destination. - */ -status_t Write(jr_compressed_ptr destination, const void* source, size_t length, int &position) { - if (position + length > destination->maxLength) { - return ERROR_JPEGR_BUFFER_TOO_SMALL; - } - - memcpy((uint8_t*)destination->data + sizeof(uint8_t) * position, source, length); - position += length; - return NO_ERROR; -} - -// Extremely simple XML Handler - just searches for interesting elements -class XMPXmlHandler : public XmlHandler { -public: - - XMPXmlHandler() : XmlHandler() { - state = NotStrarted; - versionFound = false; - minContentBoostFound = false; - maxContentBoostFound = false; - gammaFound = false; - offsetSdrFound = false; - offsetHdrFound = false; - hdrCapacityMinFound = false; - hdrCapacityMaxFound = false; - baseRenditionIsHdrFound = false; - } - - enum ParseState { - NotStrarted, - Started, - Done - }; - - virtual DataMatchResult StartElement(const XmlTokenContext& context) { - string val; - if (context.BuildTokenValue(&val)) { - if (!val.compare(containerName)) { - state = Started; - } else { - if (state != Done) { - state = NotStrarted; - } - } - } - return context.GetResult(); - } - - virtual DataMatchResult FinishElement(const XmlTokenContext& context) { - if (state == Started) { - state = Done; - lastAttributeName = ""; - } - return context.GetResult(); - } - - virtual DataMatchResult AttributeName(const XmlTokenContext& context) { - string val; - if (state == Started) { - if (context.BuildTokenValue(&val)) { - if (!val.compare(versionAttrName)) { - lastAttributeName = versionAttrName; - } else if (!val.compare(maxContentBoostAttrName)) { - lastAttributeName = maxContentBoostAttrName; - } else if (!val.compare(minContentBoostAttrName)) { - lastAttributeName = minContentBoostAttrName; - } else if (!val.compare(gammaAttrName)) { - lastAttributeName = gammaAttrName; - } else if (!val.compare(offsetSdrAttrName)) { - lastAttributeName = offsetSdrAttrName; - } else if (!val.compare(offsetHdrAttrName)) { - lastAttributeName = offsetHdrAttrName; - } else if (!val.compare(hdrCapacityMinAttrName)) { - lastAttributeName = hdrCapacityMinAttrName; - } else if (!val.compare(hdrCapacityMaxAttrName)) { - lastAttributeName = hdrCapacityMaxAttrName; - } else if (!val.compare(baseRenditionIsHdrAttrName)) { - lastAttributeName = baseRenditionIsHdrAttrName; - } else { - lastAttributeName = ""; - } - } - } - return context.GetResult(); - } - - virtual DataMatchResult AttributeValue(const XmlTokenContext& context) { - string val; - if (state == Started) { - if (context.BuildTokenValue(&val, true)) { - if (!lastAttributeName.compare(versionAttrName)) { - versionStr = val; - versionFound = true; - } else if (!lastAttributeName.compare(maxContentBoostAttrName)) { - maxContentBoostStr = val; - maxContentBoostFound = true; - } else if (!lastAttributeName.compare(minContentBoostAttrName)) { - minContentBoostStr = val; - minContentBoostFound = true; - } else if (!lastAttributeName.compare(gammaAttrName)) { - gammaStr = val; - gammaFound = true; - } else if (!lastAttributeName.compare(offsetSdrAttrName)) { - offsetSdrStr = val; - offsetSdrFound = true; - } else if (!lastAttributeName.compare(offsetHdrAttrName)) { - offsetHdrStr = val; - offsetHdrFound = true; - } else if (!lastAttributeName.compare(hdrCapacityMinAttrName)) { - hdrCapacityMinStr = val; - hdrCapacityMinFound = true; - } else if (!lastAttributeName.compare(hdrCapacityMaxAttrName)) { - hdrCapacityMaxStr = val; - hdrCapacityMaxFound = true; - } else if (!lastAttributeName.compare(baseRenditionIsHdrAttrName)) { - baseRenditionIsHdrStr = val; - baseRenditionIsHdrFound = true; - } - } - } - return context.GetResult(); - } - - bool getVersion(string* version, bool* present) { - if (state == Done) { - *version = versionStr; - *present = versionFound; - return true; - } else { - return false; - } - } - - bool getMaxContentBoost(float* max_content_boost, bool* present) { - if (state == Done) { - *present = maxContentBoostFound; - stringstream ss(maxContentBoostStr); - float val; - if (ss >> val) { - *max_content_boost = exp2(val); - return true; - } else { - return false; - } - } else { - return false; - } - } - - bool getMinContentBoost(float* min_content_boost, bool* present) { - if (state == Done) { - *present = minContentBoostFound; - stringstream ss(minContentBoostStr); - float val; - if (ss >> val) { - *min_content_boost = exp2(val); - return true; - } else { - return false; - } - } else { - return false; - } - } - - bool getGamma(float* gamma, bool* present) { - if (state == Done) { - *present = gammaFound; - stringstream ss(gammaStr); - float val; - if (ss >> val) { - *gamma = val; - return true; - } else { - return false; - } - } else { - return false; - } - } - - - bool getOffsetSdr(float* offset_sdr, bool* present) { - if (state == Done) { - *present = offsetSdrFound; - stringstream ss(offsetSdrStr); - float val; - if (ss >> val) { - *offset_sdr = val; - return true; - } else { - return false; - } - } else { - return false; - } - } - - - bool getOffsetHdr(float* offset_hdr, bool* present) { - if (state == Done) { - *present = offsetHdrFound; - stringstream ss(offsetHdrStr); - float val; - if (ss >> val) { - *offset_hdr = val; - return true; - } else { - return false; - } - } else { - return false; - } - } - - - bool getHdrCapacityMin(float* hdr_capacity_min, bool* present) { - if (state == Done) { - *present = hdrCapacityMinFound; - stringstream ss(hdrCapacityMinStr); - float val; - if (ss >> val) { - *hdr_capacity_min = exp2(val); - return true; - } else { - return false; - } - } else { - return false; - } - } - - - bool getHdrCapacityMax(float* hdr_capacity_max, bool* present) { - if (state == Done) { - *present = hdrCapacityMaxFound; - stringstream ss(hdrCapacityMaxStr); - float val; - if (ss >> val) { - *hdr_capacity_max = exp2(val); - return true; - } else { - return false; - } - } else { - return false; - } - } - - - bool getBaseRenditionIsHdr(bool* base_rendition_is_hdr, bool* present) { - if (state == Done) { - *present = baseRenditionIsHdrFound; - if (!baseRenditionIsHdrStr.compare("False")) { - *base_rendition_is_hdr = false; - return true; - } else if (!baseRenditionIsHdrStr.compare("True")) { - *base_rendition_is_hdr = true; - return true; - } else { - return false; - } - } else { - return false; - } - } - - - -private: - static const string containerName; - - static const string versionAttrName; - string versionStr; - bool versionFound; - static const string maxContentBoostAttrName; - string maxContentBoostStr; - bool maxContentBoostFound; - static const string minContentBoostAttrName; - string minContentBoostStr; - bool minContentBoostFound; - static const string gammaAttrName; - string gammaStr; - bool gammaFound; - static const string offsetSdrAttrName; - string offsetSdrStr; - bool offsetSdrFound; - static const string offsetHdrAttrName; - string offsetHdrStr; - bool offsetHdrFound; - static const string hdrCapacityMinAttrName; - string hdrCapacityMinStr; - bool hdrCapacityMinFound; - static const string hdrCapacityMaxAttrName; - string hdrCapacityMaxStr; - bool hdrCapacityMaxFound; - static const string baseRenditionIsHdrAttrName; - string baseRenditionIsHdrStr; - bool baseRenditionIsHdrFound; - - string lastAttributeName; - ParseState state; -}; - -// GContainer XMP constants - URI and namespace prefix -const string kContainerUri = "http://ns.google.com/photos/1.0/container/"; -const string kContainerPrefix = "Container"; - -// GContainer XMP constants - element and attribute names -const string kConDirectory = Name(kContainerPrefix, "Directory"); -const string kConItem = Name(kContainerPrefix, "Item"); - -// GContainer XMP constants - names for XMP handlers -const string XMPXmlHandler::containerName = "rdf:Description"; -// Item XMP constants - URI and namespace prefix -const string kItemUri = "http://ns.google.com/photos/1.0/container/item/"; -const string kItemPrefix = "Item"; - -// Item XMP constants - element and attribute names -const string kItemLength = Name(kItemPrefix, "Length"); -const string kItemMime = Name(kItemPrefix, "Mime"); -const string kItemSemantic = Name(kItemPrefix, "Semantic"); - -// Item XMP constants - element and attribute values -const string kSemanticPrimary = "Primary"; -const string kSemanticGainMap = "GainMap"; -const string kMimeImageJpeg = "image/jpeg"; - -// GainMap XMP constants - URI and namespace prefix -const string kGainMapUri = "http://ns.adobe.com/hdr-gain-map/1.0/"; -const string kGainMapPrefix = "hdrgm"; - -// GainMap XMP constants - element and attribute names -const string kMapVersion = Name(kGainMapPrefix, "Version"); -const string kMapGainMapMin = Name(kGainMapPrefix, "GainMapMin"); -const string kMapGainMapMax = Name(kGainMapPrefix, "GainMapMax"); -const string kMapGamma = Name(kGainMapPrefix, "Gamma"); -const string kMapOffsetSdr = Name(kGainMapPrefix, "OffsetSDR"); -const string kMapOffsetHdr = Name(kGainMapPrefix, "OffsetHDR"); -const string kMapHDRCapacityMin = Name(kGainMapPrefix, "HDRCapacityMin"); -const string kMapHDRCapacityMax = Name(kGainMapPrefix, "HDRCapacityMax"); -const string kMapBaseRenditionIsHDR = Name(kGainMapPrefix, "BaseRenditionIsHDR"); - -// GainMap XMP constants - names for XMP handlers -const string XMPXmlHandler::versionAttrName = kMapVersion; -const string XMPXmlHandler::minContentBoostAttrName = kMapGainMapMin; -const string XMPXmlHandler::maxContentBoostAttrName = kMapGainMapMax; -const string XMPXmlHandler::gammaAttrName = kMapGamma; -const string XMPXmlHandler::offsetSdrAttrName = kMapOffsetSdr; -const string XMPXmlHandler::offsetHdrAttrName = kMapOffsetHdr; -const string XMPXmlHandler::hdrCapacityMinAttrName = kMapHDRCapacityMin; -const string XMPXmlHandler::hdrCapacityMaxAttrName = kMapHDRCapacityMax; -const string XMPXmlHandler::baseRenditionIsHdrAttrName = kMapBaseRenditionIsHDR; - -bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, ultrahdr_metadata_struct* metadata) { - string nameSpace = "http://ns.adobe.com/xap/1.0/\0"; - - if (xmp_size < nameSpace.size()+2) { - // Data too short - return false; - } - - if (strncmp(reinterpret_cast<char*>(xmp_data), nameSpace.c_str(), nameSpace.size())) { - // Not correct namespace - return false; - } - - // Position the pointers to the start of XMP XML portion - xmp_data += nameSpace.size()+1; - xmp_size -= nameSpace.size()+1; - XMPXmlHandler handler; - - // We need to remove tail data until the closing tag. Otherwise parser will throw an error. - while(xmp_data[xmp_size-1]!='>' && xmp_size > 1) { - xmp_size--; - } - - string str(reinterpret_cast<const char*>(xmp_data), xmp_size); - MessageHandler msg_handler; - unique_ptr<XmlRule> rule(new XmlElementRule); - XmlReader reader(&handler, &msg_handler); - reader.StartParse(std::move(rule)); - reader.Parse(str); - reader.FinishParse(); - if (reader.HasErrors()) { - // Parse error - return false; - } - - // Apply default values to any not-present fields, except for Version, - // maxContentBoost, and hdrCapacityMax, which are required. Return false if - // we encounter a present field that couldn't be parsed, since this - // indicates it is invalid (eg. string where there should be a float). - bool present = false; - if (!handler.getVersion(&metadata->version, &present) || !present) { - return false; - } - if (!handler.getMaxContentBoost(&metadata->maxContentBoost, &present) || !present) { - return false; - } - if (!handler.getHdrCapacityMax(&metadata->hdrCapacityMax, &present) || !present) { - return false; - } - if (!handler.getMinContentBoost(&metadata->minContentBoost, &present)) { - if (present) return false; - metadata->minContentBoost = 1.0f; - } - if (!handler.getGamma(&metadata->gamma, &present)) { - if (present) return false; - metadata->gamma = 1.0f; - } - if (!handler.getOffsetSdr(&metadata->offsetSdr, &present)) { - if (present) return false; - metadata->offsetSdr = 1.0f / 64.0f; - } - if (!handler.getOffsetHdr(&metadata->offsetHdr, &present)) { - if (present) return false; - metadata->offsetHdr = 1.0f / 64.0f; - } - if (!handler.getHdrCapacityMin(&metadata->hdrCapacityMin, &present)) { - if (present) return false; - metadata->hdrCapacityMin = 1.0f; - } - - bool base_rendition_is_hdr; - if (!handler.getBaseRenditionIsHdr(&base_rendition_is_hdr, &present)) { - if (present) return false; - base_rendition_is_hdr = false; - } - if (base_rendition_is_hdr) { - ALOGE("Base rendition of HDR is not supported!"); - return false; - } - - return true; -} - -string generateXmpForPrimaryImage(int secondary_image_length, ultrahdr_metadata_struct& metadata) { - const vector<string> kConDirSeq({kConDirectory, string("rdf:Seq")}); - const vector<string> kLiItem({string("rdf:li"), kConItem}); - - std::stringstream ss; - photos_editing_formats::image_io::XmlWriter writer(ss); - writer.StartWritingElement("x:xmpmeta"); - writer.WriteXmlns("x", "adobe:ns:meta/"); - writer.WriteAttributeNameAndValue("x:xmptk", "Adobe XMP Core 5.1.2"); - writer.StartWritingElement("rdf:RDF"); - writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"); - writer.StartWritingElement("rdf:Description"); - writer.WriteXmlns(kContainerPrefix, kContainerUri); - writer.WriteXmlns(kItemPrefix, kItemUri); - writer.WriteXmlns(kGainMapPrefix, kGainMapUri); - writer.WriteAttributeNameAndValue(kMapVersion, metadata.version); - - writer.StartWritingElements(kConDirSeq); - - size_t item_depth = writer.StartWritingElement("rdf:li"); - writer.WriteAttributeNameAndValue("rdf:parseType", "Resource"); - writer.StartWritingElement(kConItem); - writer.WriteAttributeNameAndValue(kItemSemantic, kSemanticPrimary); - writer.WriteAttributeNameAndValue(kItemMime, kMimeImageJpeg); - writer.FinishWritingElementsToDepth(item_depth); - - writer.StartWritingElement("rdf:li"); - writer.WriteAttributeNameAndValue("rdf:parseType", "Resource"); - writer.StartWritingElement(kConItem); - writer.WriteAttributeNameAndValue(kItemSemantic, kSemanticGainMap); - writer.WriteAttributeNameAndValue(kItemMime, kMimeImageJpeg); - writer.WriteAttributeNameAndValue(kItemLength, secondary_image_length); - - writer.FinishWriting(); - - return ss.str(); -} - -string generateXmpForSecondaryImage(ultrahdr_metadata_struct& metadata) { - const vector<string> kConDirSeq({kConDirectory, string("rdf:Seq")}); - - std::stringstream ss; - photos_editing_formats::image_io::XmlWriter writer(ss); - writer.StartWritingElement("x:xmpmeta"); - writer.WriteXmlns("x", "adobe:ns:meta/"); - writer.WriteAttributeNameAndValue("x:xmptk", "Adobe XMP Core 5.1.2"); - writer.StartWritingElement("rdf:RDF"); - writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"); - writer.StartWritingElement("rdf:Description"); - writer.WriteXmlns(kGainMapPrefix, kGainMapUri); - writer.WriteAttributeNameAndValue(kMapVersion, metadata.version); - writer.WriteAttributeNameAndValue(kMapGainMapMin, log2(metadata.minContentBoost)); - writer.WriteAttributeNameAndValue(kMapGainMapMax, log2(metadata.maxContentBoost)); - writer.WriteAttributeNameAndValue(kMapGamma, metadata.gamma); - writer.WriteAttributeNameAndValue(kMapOffsetSdr, metadata.offsetSdr); - writer.WriteAttributeNameAndValue(kMapOffsetHdr, metadata.offsetHdr); - writer.WriteAttributeNameAndValue(kMapHDRCapacityMin, log2(metadata.hdrCapacityMin)); - writer.WriteAttributeNameAndValue(kMapHDRCapacityMax, log2(metadata.hdrCapacityMax)); - writer.WriteAttributeNameAndValue(kMapBaseRenditionIsHDR, "False"); - writer.FinishWriting(); - - return ss.str(); -} - -} // namespace ultrahdr diff --git a/gainmapmath.cpp b/lib/gainmapmath.cpp index a0698ba..23791c2 100644 --- a/gainmapmath.cpp +++ b/lib/gainmapmath.cpp @@ -14,53 +14,53 @@ * limitations under the License. */ -#include "ultrahdr/gainmapmath.h" +#include "gainmapmath.h" namespace ultrahdr { static const std::vector<float> kPqOETF = [] { - std::vector<float> result; - for (size_t idx = 0; idx < kPqOETFNumEntries; idx++) { - float value = static_cast<float>(idx) / static_cast<float>(kPqOETFNumEntries - 1); - result.push_back(pqOetf(value)); - } - return result; + std::vector<float> result; + for (size_t idx = 0; idx < kPqOETFNumEntries; idx++) { + float value = static_cast<float>(idx) / static_cast<float>(kPqOETFNumEntries - 1); + result.push_back(pqOetf(value)); + } + return result; }(); static const std::vector<float> kPqInvOETF = [] { - std::vector<float> result; - for (size_t idx = 0; idx < kPqInvOETFNumEntries; idx++) { - float value = static_cast<float>(idx) / static_cast<float>(kPqInvOETFNumEntries - 1); - result.push_back(pqInvOetf(value)); - } - return result; + std::vector<float> result; + for (size_t idx = 0; idx < kPqInvOETFNumEntries; idx++) { + float value = static_cast<float>(idx) / static_cast<float>(kPqInvOETFNumEntries - 1); + result.push_back(pqInvOetf(value)); + } + return result; }(); static const std::vector<float> kHlgOETF = [] { - std::vector<float> result; - for (size_t idx = 0; idx < kHlgOETFNumEntries; idx++) { - float value = static_cast<float>(idx) / static_cast<float>(kHlgOETFNumEntries - 1); - result.push_back(hlgOetf(value)); - } - return result; + std::vector<float> result; + for (size_t idx = 0; idx < kHlgOETFNumEntries; idx++) { + float value = static_cast<float>(idx) / static_cast<float>(kHlgOETFNumEntries - 1); + result.push_back(hlgOetf(value)); + } + return result; }(); static const std::vector<float> kHlgInvOETF = [] { - std::vector<float> result; - for (size_t idx = 0; idx < kHlgInvOETFNumEntries; idx++) { - float value = static_cast<float>(idx) / static_cast<float>(kHlgInvOETFNumEntries - 1); - result.push_back(hlgInvOetf(value)); - } - return result; + std::vector<float> result; + for (size_t idx = 0; idx < kHlgInvOETFNumEntries; idx++) { + float value = static_cast<float>(idx) / static_cast<float>(kHlgInvOETFNumEntries - 1); + result.push_back(hlgInvOetf(value)); + } + return result; }(); static const std::vector<float> kSrgbInvOETF = [] { - std::vector<float> result; - for (size_t idx = 0; idx < kSrgbInvOETFNumEntries; idx++) { - float value = static_cast<float>(idx) / static_cast<float>(kSrgbInvOETFNumEntries - 1); - result.push_back(srgbInvOetf(value)); - } - return result; + std::vector<float> result; + for (size_t idx = 0; idx < kSrgbInvOETFNumEntries; idx++) { + float value = static_cast<float>(idx) / static_cast<float>(kSrgbInvOETFNumEntries - 1); + result.push_back(srgbInvOetf(value)); + } + return result; }(); // Use Shepard's method for inverse distance weighting. For more information: @@ -70,7 +70,7 @@ float ShepardsIDW::euclideanDistance(float x1, float x2, float y1, float y2) { return sqrt(((y2 - y1) * (y2 - y1)) + (x2 - x1) * (x2 - x1)); } -void ShepardsIDW::fillShepardsIDW(float *weights, int incR, int incB) { +void ShepardsIDW::fillShepardsIDW(float* weights, int incR, int incB) { for (int y = 0; y < mMapScaleFactor; y++) { for (int x = 0; x < mMapScaleFactor; x++) { float pos_x = ((float)x) / mMapScaleFactor; @@ -114,15 +114,13 @@ void ShepardsIDW::fillShepardsIDW(float *weights, int incR, int incB) { static const float kMaxPixelFloat = 1.0f; static float clampPixelFloat(float value) { - return (value < 0.0f) ? 0.0f : (value > kMaxPixelFloat) ? kMaxPixelFloat : value; + return (value < 0.0f) ? 0.0f : (value > kMaxPixelFloat) ? kMaxPixelFloat : value; } // See IEC 61966-2-1/Amd 1:2003, Equation F.7. static const float kSrgbR = 0.2126f, kSrgbG = 0.7152f, kSrgbB = 0.0722f; -float srgbLuminance(Color e) { - return kSrgbR * e.r + kSrgbG * e.g + kSrgbB * e.b; -} +float srgbLuminance(Color e) { return kSrgbR * e.r + kSrgbG * e.g + kSrgbB * e.b; } // See ITU-R BT.709-6, Section 3. // Uses the same coefficients for deriving luma signal as @@ -132,9 +130,7 @@ static const float kSrgbCb = 1.8556f, kSrgbCr = 1.5748f; Color srgbRgbToYuv(Color e_gamma) { float y_gamma = srgbLuminance(e_gamma); - return {{{ y_gamma, - (e_gamma.b - y_gamma) / kSrgbCb, - (e_gamma.r - y_gamma) / kSrgbCr }}}; + return {{{y_gamma, (e_gamma.b - y_gamma) / kSrgbCb, (e_gamma.r - y_gamma) / kSrgbCr}}}; } // See ITU-R BT.709-6, Section 3. @@ -144,9 +140,9 @@ static const float kSrgbGCb = kSrgbB * kSrgbCb / kSrgbG; static const float kSrgbGCr = kSrgbR * kSrgbCr / kSrgbG; Color srgbYuvToRgb(Color e_gamma) { - return {{{ clampPixelFloat(e_gamma.y + kSrgbCr * e_gamma.v), - clampPixelFloat(e_gamma.y - kSrgbGCb * e_gamma.u - kSrgbGCr * e_gamma.v), - clampPixelFloat(e_gamma.y + kSrgbCb * e_gamma.u) }}}; + return {{{clampPixelFloat(e_gamma.y + kSrgbCr * e_gamma.v), + clampPixelFloat(e_gamma.y - kSrgbGCb * e_gamma.u - kSrgbGCr * e_gamma.v), + clampPixelFloat(e_gamma.y + kSrgbCb * e_gamma.u)}}}; } // See IEC 61966-2-1/Amd 1:2003, Equations F.5 and F.6. @@ -159,23 +155,19 @@ float srgbInvOetf(float e_gamma) { } Color srgbInvOetf(Color e_gamma) { - return {{{ srgbInvOetf(e_gamma.r), - srgbInvOetf(e_gamma.g), - srgbInvOetf(e_gamma.b) }}}; + return {{{srgbInvOetf(e_gamma.r), srgbInvOetf(e_gamma.g), srgbInvOetf(e_gamma.b)}}}; } // See IEC 61966-2-1, Equations F.5 and F.6. float srgbInvOetfLUT(float e_gamma) { uint32_t value = static_cast<uint32_t>(e_gamma * (kSrgbInvOETFNumEntries - 1) + 0.5); - //TODO() : Remove once conversion modules have appropriate clamping in place + // TODO() : Remove once conversion modules have appropriate clamping in place value = CLIP3(value, 0, kSrgbInvOETFNumEntries - 1); return kSrgbInvOETF[value]; } Color srgbInvOetfLUT(Color e_gamma) { - return {{{ srgbInvOetfLUT(e_gamma.r), - srgbInvOetfLUT(e_gamma.g), - srgbInvOetfLUT(e_gamma.b) }}}; + return {{{srgbInvOetfLUT(e_gamma.r), srgbInvOetfLUT(e_gamma.g), srgbInvOetfLUT(e_gamma.b)}}}; } //////////////////////////////////////////////////////////////////////////////// @@ -184,9 +176,7 @@ Color srgbInvOetfLUT(Color e_gamma) { // See SMPTE EG 432-1, Equation 7-8. static const float kP3R = 0.20949f, kP3G = 0.72160f, kP3B = 0.06891f; -float p3Luminance(Color e) { - return kP3R * e.r + kP3G * e.g + kP3B * e.b; -} +float p3Luminance(Color e) { return kP3R * e.r + kP3G * e.g + kP3B * e.b; } // See ITU-R BT.601-7, Sections 2.5.1 and 2.5.2. // Unfortunately, calculation of luma signal differs from calculation of @@ -196,9 +186,7 @@ static const float kP3Cb = 1.772f, kP3Cr = 1.402f; Color p3RgbToYuv(Color e_gamma) { float y_gamma = kP3YR * e_gamma.r + kP3YG * e_gamma.g + kP3YB * e_gamma.b; - return {{{ y_gamma, - (e_gamma.b - y_gamma) / kP3Cb, - (e_gamma.r - y_gamma) / kP3Cr }}}; + return {{{y_gamma, (e_gamma.b - y_gamma) / kP3Cb, (e_gamma.r - y_gamma) / kP3Cr}}}; } // See ITU-R BT.601-7, Sections 2.5.1 and 2.5.2. @@ -208,21 +196,18 @@ static const float kP3GCb = kP3YB * kP3Cb / kP3YG; static const float kP3GCr = kP3YR * kP3Cr / kP3YG; Color p3YuvToRgb(Color e_gamma) { - return {{{ clampPixelFloat(e_gamma.y + kP3Cr * e_gamma.v), - clampPixelFloat(e_gamma.y - kP3GCb * e_gamma.u - kP3GCr * e_gamma.v), - clampPixelFloat(e_gamma.y + kP3Cb * e_gamma.u) }}}; + return {{{clampPixelFloat(e_gamma.y + kP3Cr * e_gamma.v), + clampPixelFloat(e_gamma.y - kP3GCb * e_gamma.u - kP3GCr * e_gamma.v), + clampPixelFloat(e_gamma.y + kP3Cb * e_gamma.u)}}}; } - //////////////////////////////////////////////////////////////////////////////// // BT.2100 transformations - according to ITU-R BT.2100-2 // See ITU-R BT.2100-2, Table 5, HLG Reference OOTF static const float kBt2100R = 0.2627f, kBt2100G = 0.6780f, kBt2100B = 0.0593f; -float bt2100Luminance(Color e) { - return kBt2100R * e.r + kBt2100G * e.g + kBt2100B * e.b; -} +float bt2100Luminance(Color e) { return kBt2100R * e.r + kBt2100G * e.g + kBt2100B * e.b; } // See ITU-R BT.2100-2, Table 6, Derivation of colour difference signals. // BT.2100 uses the same coefficients for calculating luma signal and luminance, @@ -231,9 +216,7 @@ static const float kBt2100Cb = 1.8814f, kBt2100Cr = 1.4746f; Color bt2100RgbToYuv(Color e_gamma) { float y_gamma = bt2100Luminance(e_gamma); - return {{{ y_gamma, - (e_gamma.b - y_gamma) / kBt2100Cb, - (e_gamma.r - y_gamma) / kBt2100Cr }}}; + return {{{y_gamma, (e_gamma.b - y_gamma) / kBt2100Cb, (e_gamma.r - y_gamma) / kBt2100Cr}}}; } // See ITU-R BT.2100-2, Table 6, Derivation of colour difference signals. @@ -265,37 +248,33 @@ static const float kBt2100GCb = kBt2100B * kBt2100Cb / kBt2100G; static const float kBt2100GCr = kBt2100R * kBt2100Cr / kBt2100G; Color bt2100YuvToRgb(Color e_gamma) { - return {{{ clampPixelFloat(e_gamma.y + kBt2100Cr * e_gamma.v), - clampPixelFloat(e_gamma.y - kBt2100GCb * e_gamma.u - kBt2100GCr * e_gamma.v), - clampPixelFloat(e_gamma.y + kBt2100Cb * e_gamma.u) }}}; + return {{{clampPixelFloat(e_gamma.y + kBt2100Cr * e_gamma.v), + clampPixelFloat(e_gamma.y - kBt2100GCb * e_gamma.u - kBt2100GCr * e_gamma.v), + clampPixelFloat(e_gamma.y + kBt2100Cb * e_gamma.u)}}}; } // See ITU-R BT.2100-2, Table 5, HLG Reference OETF. static const float kHlgA = 0.17883277f, kHlgB = 0.28466892f, kHlgC = 0.55991073; float hlgOetf(float e) { - if (e <= 1.0f/12.0f) { + if (e <= 1.0f / 12.0f) { return sqrt(3.0f * e); } else { return kHlgA * log(12.0f * e - kHlgB) + kHlgC; } } -Color hlgOetf(Color e) { - return {{{ hlgOetf(e.r), hlgOetf(e.g), hlgOetf(e.b) }}}; -} +Color hlgOetf(Color e) { return {{{hlgOetf(e.r), hlgOetf(e.g), hlgOetf(e.b)}}}; } float hlgOetfLUT(float e) { uint32_t value = static_cast<uint32_t>(e * (kHlgOETFNumEntries - 1) + 0.5); - //TODO() : Remove once conversion modules have appropriate clamping in place + // TODO() : Remove once conversion modules have appropriate clamping in place value = CLIP3(value, 0, kHlgOETFNumEntries - 1); return kHlgOETF[value]; } -Color hlgOetfLUT(Color e) { - return {{{ hlgOetfLUT(e.r), hlgOetfLUT(e.g), hlgOetfLUT(e.b) }}}; -} +Color hlgOetfLUT(Color e) { return {{{hlgOetfLUT(e.r), hlgOetfLUT(e.g), hlgOetfLUT(e.b)}}}; } // See ITU-R BT.2100-2, Table 5, HLG Reference EOTF. float hlgInvOetf(float e_gamma) { @@ -307,23 +286,19 @@ float hlgInvOetf(float e_gamma) { } Color hlgInvOetf(Color e_gamma) { - return {{{ hlgInvOetf(e_gamma.r), - hlgInvOetf(e_gamma.g), - hlgInvOetf(e_gamma.b) }}}; + return {{{hlgInvOetf(e_gamma.r), hlgInvOetf(e_gamma.g), hlgInvOetf(e_gamma.b)}}}; } float hlgInvOetfLUT(float e_gamma) { uint32_t value = static_cast<uint32_t>(e_gamma * (kHlgInvOETFNumEntries - 1) + 0.5); - //TODO() : Remove once conversion modules have appropriate clamping in place + // TODO() : Remove once conversion modules have appropriate clamping in place value = CLIP3(value, 0, kHlgInvOETFNumEntries - 1); return kHlgInvOETF[value]; } Color hlgInvOetfLUT(Color e_gamma) { - return {{{ hlgInvOetfLUT(e_gamma.r), - hlgInvOetfLUT(e_gamma.g), - hlgInvOetfLUT(e_gamma.b) }}}; + return {{{hlgInvOetfLUT(e_gamma.r), hlgInvOetfLUT(e_gamma.g), hlgInvOetfLUT(e_gamma.b)}}}; } // See ITU-R BT.2100-2, Table 4, Reference PQ OETF. @@ -333,25 +308,20 @@ static const float kPqC1 = 3424.0f / 4096.0f, kPqC2 = 2413.0f / 4096.0f * 32.0f, float pqOetf(float e) { if (e <= 0.0f) return 0.0f; - return pow((kPqC1 + kPqC2 * pow(e, kPqM1)) / (1 + kPqC3 * pow(e, kPqM1)), - kPqM2); + return pow((kPqC1 + kPqC2 * pow(e, kPqM1)) / (1 + kPqC3 * pow(e, kPqM1)), kPqM2); } -Color pqOetf(Color e) { - return {{{ pqOetf(e.r), pqOetf(e.g), pqOetf(e.b) }}}; -} +Color pqOetf(Color e) { return {{{pqOetf(e.r), pqOetf(e.g), pqOetf(e.b)}}}; } float pqOetfLUT(float e) { uint32_t value = static_cast<uint32_t>(e * (kPqOETFNumEntries - 1) + 0.5); - //TODO() : Remove once conversion modules have appropriate clamping in place + // TODO() : Remove once conversion modules have appropriate clamping in place value = CLIP3(value, 0, kPqOETFNumEntries - 1); return kPqOETF[value]; } -Color pqOetfLUT(Color e) { - return {{{ pqOetfLUT(e.r), pqOetfLUT(e.g), pqOetfLUT(e.b) }}}; -} +Color pqOetfLUT(Color e) { return {{{pqOetfLUT(e.r), pqOetfLUT(e.g), pqOetfLUT(e.b)}}}; } // Derived from the inverse of the Reference PQ OETF. static const float kPqInvA = 128.0f, kPqInvB = 107.0f, kPqInvC = 2413.0f, kPqInvD = 2392.0f, @@ -362,70 +332,64 @@ float pqInvOetf(float e_gamma) { // always catch 0.0. So, check on 0.0001, since anything this small will // effectively be crushed to zero anyways. if (e_gamma <= 0.0001f) return 0.0f; - return pow((kPqInvA * pow(e_gamma, kPqInvF) - kPqInvB) - / (kPqInvC - kPqInvD * pow(e_gamma, kPqInvF)), - kPqInvE); + return pow( + (kPqInvA * pow(e_gamma, kPqInvF) - kPqInvB) / (kPqInvC - kPqInvD * pow(e_gamma, kPqInvF)), + kPqInvE); } Color pqInvOetf(Color e_gamma) { - return {{{ pqInvOetf(e_gamma.r), - pqInvOetf(e_gamma.g), - pqInvOetf(e_gamma.b) }}}; + return {{{pqInvOetf(e_gamma.r), pqInvOetf(e_gamma.g), pqInvOetf(e_gamma.b)}}}; } float pqInvOetfLUT(float e_gamma) { uint32_t value = static_cast<uint32_t>(e_gamma * (kPqInvOETFNumEntries - 1) + 0.5); - //TODO() : Remove once conversion modules have appropriate clamping in place + // TODO() : Remove once conversion modules have appropriate clamping in place value = CLIP3(value, 0, kPqInvOETFNumEntries - 1); return kPqInvOETF[value]; } Color pqInvOetfLUT(Color e_gamma) { - return {{{ pqInvOetfLUT(e_gamma.r), - pqInvOetfLUT(e_gamma.g), - pqInvOetfLUT(e_gamma.b) }}}; + return {{{pqInvOetfLUT(e_gamma.r), pqInvOetfLUT(e_gamma.g), pqInvOetfLUT(e_gamma.b)}}}; } - //////////////////////////////////////////////////////////////////////////////// // Color conversions Color bt709ToP3(Color e) { - return {{{ 0.82254f * e.r + 0.17755f * e.g + 0.00006f * e.b, + return {{{0.82254f * e.r + 0.17755f * e.g + 0.00006f * e.b, 0.03312f * e.r + 0.96684f * e.g + -0.00001f * e.b, - 0.01706f * e.r + 0.07240f * e.g + 0.91049f * e.b }}}; + 0.01706f * e.r + 0.07240f * e.g + 0.91049f * e.b}}}; } Color bt709ToBt2100(Color e) { - return {{{ 0.62740f * e.r + 0.32930f * e.g + 0.04332f * e.b, + return {{{0.62740f * e.r + 0.32930f * e.g + 0.04332f * e.b, 0.06904f * e.r + 0.91958f * e.g + 0.01138f * e.b, - 0.01636f * e.r + 0.08799f * e.g + 0.89555f * e.b }}}; + 0.01636f * e.r + 0.08799f * e.g + 0.89555f * e.b}}}; } Color p3ToBt709(Color e) { - return {{{ 1.22482f * e.r + -0.22490f * e.g + -0.00007f * e.b, + return {{{1.22482f * e.r + -0.22490f * e.g + -0.00007f * e.b, -0.04196f * e.r + 1.04199f * e.g + 0.00001f * e.b, - -0.01961f * e.r + -0.07865f * e.g + 1.09831f * e.b }}}; + -0.01961f * e.r + -0.07865f * e.g + 1.09831f * e.b}}}; } Color p3ToBt2100(Color e) { - return {{{ 0.75378f * e.r + 0.19862f * e.g + 0.04754f * e.b, + return {{{0.75378f * e.r + 0.19862f * e.g + 0.04754f * e.b, 0.04576f * e.r + 0.94177f * e.g + 0.01250f * e.b, - -0.00121f * e.r + 0.01757f * e.g + 0.98359f * e.b }}}; + -0.00121f * e.r + 0.01757f * e.g + 0.98359f * e.b}}}; } Color bt2100ToBt709(Color e) { - return {{{ 1.66045f * e.r + -0.58764f * e.g + -0.07286f * e.b, + return {{{1.66045f * e.r + -0.58764f * e.g + -0.07286f * e.b, -0.12445f * e.r + 1.13282f * e.g + -0.00837f * e.b, - -0.01811f * e.r + -0.10057f * e.g + 1.11878f * e.b }}}; + -0.01811f * e.r + -0.10057f * e.g + 1.11878f * e.b}}}; } Color bt2100ToP3(Color e) { - return {{{ 1.34369f * e.r + -0.28223f * e.g + -0.06135f * e.b, + return {{{1.34369f * e.r + -0.28223f * e.g + -0.06135f * e.b, -0.06533f * e.r + 1.07580f * e.g + -0.01051f * e.b, - 0.00283f * e.r + -0.01957f * e.g + 1.01679f * e.b - }}}; + 0.00283f * e.r + -0.01957f * e.g + 1.01679f * e.b}}}; } // TODO: confirm we always want to convert like this before calculating @@ -481,46 +445,46 @@ ColorTransformFn getHdrConversionFn(ultrahdr_color_gamut sdr_gamut, // DataSpace. Color yuv709To601(Color e_gamma) { - return {{{ 1.0f * e_gamma.y + 0.101579f * e_gamma.u + 0.196076f * e_gamma.v, - 0.0f * e_gamma.y + 0.989854f * e_gamma.u + -0.110653f * e_gamma.v, - 0.0f * e_gamma.y + -0.072453f * e_gamma.u + 0.983398f * e_gamma.v }}}; + return {{{1.0f * e_gamma.y + 0.101579f * e_gamma.u + 0.196076f * e_gamma.v, + 0.0f * e_gamma.y + 0.989854f * e_gamma.u + -0.110653f * e_gamma.v, + 0.0f * e_gamma.y + -0.072453f * e_gamma.u + 0.983398f * e_gamma.v}}}; } Color yuv709To2100(Color e_gamma) { - return {{{ 1.0f * e_gamma.y + -0.016969f * e_gamma.u + 0.096312f * e_gamma.v, - 0.0f * e_gamma.y + 0.995306f * e_gamma.u + -0.051192f * e_gamma.v, - 0.0f * e_gamma.y + 0.011507f * e_gamma.u + 1.002637f * e_gamma.v }}}; + return {{{1.0f * e_gamma.y + -0.016969f * e_gamma.u + 0.096312f * e_gamma.v, + 0.0f * e_gamma.y + 0.995306f * e_gamma.u + -0.051192f * e_gamma.v, + 0.0f * e_gamma.y + 0.011507f * e_gamma.u + 1.002637f * e_gamma.v}}}; } Color yuv601To709(Color e_gamma) { - return {{{ 1.0f * e_gamma.y + -0.118188f * e_gamma.u + -0.212685f * e_gamma.v, - 0.0f * e_gamma.y + 1.018640f * e_gamma.u + 0.114618f * e_gamma.v, - 0.0f * e_gamma.y + 0.075049f * e_gamma.u + 1.025327f * e_gamma.v }}}; + return {{{1.0f * e_gamma.y + -0.118188f * e_gamma.u + -0.212685f * e_gamma.v, + 0.0f * e_gamma.y + 1.018640f * e_gamma.u + 0.114618f * e_gamma.v, + 0.0f * e_gamma.y + 0.075049f * e_gamma.u + 1.025327f * e_gamma.v}}}; } Color yuv601To2100(Color e_gamma) { - return {{{ 1.0f * e_gamma.y + -0.128245f * e_gamma.u + -0.115879f * e_gamma.v, - 0.0f * e_gamma.y + 1.010016f * e_gamma.u + 0.061592f * e_gamma.v, - 0.0f * e_gamma.y + 0.086969f * e_gamma.u + 1.029350f * e_gamma.v }}}; + return {{{1.0f * e_gamma.y + -0.128245f * e_gamma.u + -0.115879f * e_gamma.v, + 0.0f * e_gamma.y + 1.010016f * e_gamma.u + 0.061592f * e_gamma.v, + 0.0f * e_gamma.y + 0.086969f * e_gamma.u + 1.029350f * e_gamma.v}}}; } Color yuv2100To709(Color e_gamma) { - return {{{ 1.0f * e_gamma.y + 0.018149f * e_gamma.u + -0.095132f * e_gamma.v, - 0.0f * e_gamma.y + 1.004123f * e_gamma.u + 0.051267f * e_gamma.v, - 0.0f * e_gamma.y + -0.011524f * e_gamma.u + 0.996782f * e_gamma.v }}}; + return {{{1.0f * e_gamma.y + 0.018149f * e_gamma.u + -0.095132f * e_gamma.v, + 0.0f * e_gamma.y + 1.004123f * e_gamma.u + 0.051267f * e_gamma.v, + 0.0f * e_gamma.y + -0.011524f * e_gamma.u + 0.996782f * e_gamma.v}}}; } Color yuv2100To601(Color e_gamma) { - return {{{ 1.0f * e_gamma.y + 0.117887f * e_gamma.u + 0.105521f * e_gamma.v, - 0.0f * e_gamma.y + 0.995211f * e_gamma.u + -0.059549f * e_gamma.v, - 0.0f * e_gamma.y + -0.084085f * e_gamma.u + 0.976518f * e_gamma.v }}}; + return {{{1.0f * e_gamma.y + 0.117887f * e_gamma.u + 0.105521f * e_gamma.v, + 0.0f * e_gamma.y + 0.995211f * e_gamma.u + -0.059549f * e_gamma.v, + 0.0f * e_gamma.y + -0.084085f * e_gamma.u + 0.976518f * e_gamma.v}}}; } void transformYuv420(jr_uncompressed_ptr image, size_t x_chroma, size_t y_chroma, ColorTransformFn fn) { - Color yuv1 = getYuv420Pixel(image, x_chroma * 2, y_chroma * 2 ); - Color yuv2 = getYuv420Pixel(image, x_chroma * 2 + 1, y_chroma * 2 ); - Color yuv3 = getYuv420Pixel(image, x_chroma * 2, y_chroma * 2 + 1); + Color yuv1 = getYuv420Pixel(image, x_chroma * 2, y_chroma * 2); + Color yuv2 = getYuv420Pixel(image, x_chroma * 2 + 1, y_chroma * 2); + Color yuv3 = getYuv420Pixel(image, x_chroma * 2, y_chroma * 2 + 1); Color yuv4 = getYuv420Pixel(image, x_chroma * 2 + 1, y_chroma * 2 + 1); yuv1 = fn(yuv1); @@ -530,9 +494,9 @@ void transformYuv420(jr_uncompressed_ptr image, size_t x_chroma, size_t y_chroma Color new_uv = (yuv1 + yuv2 + yuv3 + yuv4) / 4.0f; - size_t pixel_y1_idx = x_chroma * 2 + y_chroma * 2 * image->luma_stride; - size_t pixel_y2_idx = (x_chroma * 2 + 1) + y_chroma * 2 * image->luma_stride; - size_t pixel_y3_idx = x_chroma * 2 + (y_chroma * 2 + 1) * image->luma_stride; + size_t pixel_y1_idx = x_chroma * 2 + y_chroma * 2 * image->luma_stride; + size_t pixel_y2_idx = (x_chroma * 2 + 1) + y_chroma * 2 * image->luma_stride; + size_t pixel_y3_idx = x_chroma * 2 + (y_chroma * 2 + 1) * image->luma_stride; size_t pixel_y4_idx = (x_chroma * 2 + 1) + (y_chroma * 2 + 1) * image->luma_stride; uint8_t& y1_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_y1_idx]; @@ -558,8 +522,8 @@ void transformYuv420(jr_uncompressed_ptr image, size_t x_chroma, size_t y_chroma //////////////////////////////////////////////////////////////////////////////// // Gain map calculations uint8_t encodeGain(float y_sdr, float y_hdr, ultrahdr_metadata_ptr metadata) { - return encodeGain(y_sdr, y_hdr, metadata, - log2(metadata->minContentBoost), log2(metadata->maxContentBoost)); + return encodeGain(y_sdr, y_hdr, metadata, log2(metadata->minContentBoost), + log2(metadata->maxContentBoost)); } uint8_t encodeGain(float y_sdr, float y_hdr, ultrahdr_metadata_ptr metadata, @@ -572,21 +536,20 @@ uint8_t encodeGain(float y_sdr, float y_hdr, ultrahdr_metadata_ptr metadata, if (gain < metadata->minContentBoost) gain = metadata->minContentBoost; if (gain > metadata->maxContentBoost) gain = metadata->maxContentBoost; - return static_cast<uint8_t>((log2(gain) - log2MinContentBoost) - / (log2MaxContentBoost - log2MinContentBoost) - * 255.0f); + return static_cast<uint8_t>((log2(gain) - log2MinContentBoost) / + (log2MaxContentBoost - log2MinContentBoost) * 255.0f); } Color applyGain(Color e, float gain, ultrahdr_metadata_ptr metadata) { - float logBoost = log2(metadata->minContentBoost) * (1.0f - gain) - + log2(metadata->maxContentBoost) * gain; + float logBoost = + log2(metadata->minContentBoost) * (1.0f - gain) + log2(metadata->maxContentBoost) * gain; float gainFactor = exp2(logBoost); return e * gainFactor; } Color applyGain(Color e, float gain, ultrahdr_metadata_ptr metadata, float displayBoost) { - float logBoost = log2(metadata->minContentBoost) * (1.0f - gain) - + log2(metadata->maxContentBoost) * gain; + float logBoost = + log2(metadata->minContentBoost) * (1.0f - gain) + log2(metadata->maxContentBoost) * gain; float gainFactor = exp2(logBoost * displayBoost / metadata->maxContentBoost); return e * gainFactor; } @@ -612,9 +575,8 @@ 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) / 255.0f, (static_cast<float>(u_uint) - 128.0f) / 255.0f, + (static_cast<float>(v_uint) - 128.0f) / 255.0f}}}; } Color getP010Pixel(jr_uncompressed_ptr image, size_t x, size_t y) { @@ -632,16 +594,16 @@ 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.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}}}; } typedef Color (*getPixelFn)(jr_uncompressed_ptr, size_t, size_t); static Color samplePixels(jr_uncompressed_ptr image, size_t map_scale_factor, size_t x, size_t y, getPixelFn get_pixel_fn) { - Color e = {{{ 0.0f, 0.0f, 0.0f }}}; + Color e = {{{0.0f, 0.0f, 0.0f}}}; for (size_t dy = 0; dy < map_scale_factor; ++dy) { for (size_t dx = 0; dx < map_scale_factor; ++dx) { e += get_pixel_fn(image, x * map_scale_factor + dx, y * map_scale_factor + dy); @@ -666,9 +628,7 @@ static size_t clamp(const size_t& val, const size_t& low, const size_t& high) { return val < low ? low : (high < val ? high : val); } -static float mapUintToFloat(uint8_t map_uint) { - return static_cast<float>(map_uint) / 255.0f; -} +static float mapUintToFloat(uint8_t map_uint) { return static_cast<float>(map_uint) / 255.0f; } static float pythDistance(float x_diff, float y_diff) { return sqrt(pow(x_diff, 2.0f) + pow(y_diff, 2.0f)); @@ -693,23 +653,23 @@ float sampleMap(jr_uncompressed_ptr map, float map_scale_factor, size_t x, size_ // en.wikipedia.org/wiki/Inverse_distance_weighting#Shepard's_method float e1 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_lower + y_lower * map->width]); - float e1_dist = pythDistance(x_map - static_cast<float>(x_lower), - y_map - static_cast<float>(y_lower)); + float e1_dist = + pythDistance(x_map - static_cast<float>(x_lower), y_map - static_cast<float>(y_lower)); if (e1_dist == 0.0f) return e1; float e2 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_lower + y_upper * map->width]); - float e2_dist = pythDistance(x_map - static_cast<float>(x_lower), - y_map - static_cast<float>(y_upper)); + float e2_dist = + pythDistance(x_map - static_cast<float>(x_lower), y_map - static_cast<float>(y_upper)); if (e2_dist == 0.0f) return e2; float e3 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_upper + y_lower * map->width]); - float e3_dist = pythDistance(x_map - static_cast<float>(x_upper), - y_map - static_cast<float>(y_lower)); + float e3_dist = + pythDistance(x_map - static_cast<float>(x_upper), y_map - static_cast<float>(y_lower)); if (e3_dist == 0.0f) return e3; float e4 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_upper + y_upper * map->width]); - float e4_dist = pythDistance(x_map - static_cast<float>(x_upper), - y_map - static_cast<float>(y_upper)); + float e4_dist = + pythDistance(x_map - static_cast<float>(x_upper), y_map - static_cast<float>(y_upper)); if (e4_dist == 0.0f) return e2; float e1_weight = 1.0f / e1_dist; @@ -718,10 +678,8 @@ float sampleMap(jr_uncompressed_ptr map, float map_scale_factor, size_t x, size_ float e4_weight = 1.0f / e4_dist; float total_weight = e1_weight + e2_weight + e3_weight + e4_weight; - return e1 * (e1_weight / total_weight) - + e2 * (e2_weight / total_weight) - + e3 * (e3_weight / total_weight) - + e4 * (e4_weight / total_weight); + return e1 * (e1_weight / total_weight) + e2 * (e2_weight / total_weight) + + e3 * (e3_weight / total_weight) + e4 * (e4_weight / total_weight); } float sampleMap(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y, @@ -749,26 +707,27 @@ float sampleMap(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size int offset_y = y % map_scale_factor; float* weights = weightTables.mWeights; - if (x_lower == x_upper && y_lower == y_upper) weights = weightTables.mWeightsC; - else if (x_lower == x_upper) weights = weightTables.mWeightsNR; - else if (y_lower == y_upper) weights = weightTables.mWeightsNB; + if (x_lower == x_upper && y_lower == y_upper) + weights = weightTables.mWeightsC; + else if (x_lower == x_upper) + weights = weightTables.mWeightsNR; + else if (y_lower == y_upper) + weights = weightTables.mWeightsNB; weights += offset_y * map_scale_factor * 4 + offset_x * 4; return e1 * weights[0] + e2 * weights[1] + e3 * weights[2] + e4 * weights[3]; } uint32_t colorToRgba1010102(Color e_gamma) { - return (0x3ff & static_cast<uint32_t>(e_gamma.r * 1023.0f)) - | ((0x3ff & static_cast<uint32_t>(e_gamma.g * 1023.0f)) << 10) - | ((0x3ff & static_cast<uint32_t>(e_gamma.b * 1023.0f)) << 20) - | (0x3 << 30); // Set alpha to 1.0 + return (0x3ff & static_cast<uint32_t>(e_gamma.r * 1023.0f)) | + ((0x3ff & static_cast<uint32_t>(e_gamma.g * 1023.0f)) << 10) | + ((0x3ff & static_cast<uint32_t>(e_gamma.b * 1023.0f)) << 20) | + (0x3 << 30); // Set alpha to 1.0 } uint64_t colorToRgbaF16(Color e_gamma) { - return (uint64_t) floatToHalf(e_gamma.r) - | (((uint64_t) floatToHalf(e_gamma.g)) << 16) - | (((uint64_t) floatToHalf(e_gamma.b)) << 32) - | (((uint64_t) floatToHalf(1.0f)) << 48); + return (uint64_t)floatToHalf(e_gamma.r) | (((uint64_t)floatToHalf(e_gamma.g)) << 16) | + (((uint64_t)floatToHalf(e_gamma.b)) << 32) | (((uint64_t)floatToHalf(1.0f)) << 48); } -} // namespace ultrahdr +} // namespace ultrahdr diff --git a/include/ultrahdr/gainmapmath.h b/lib/gainmapmath.h index 8cda24e..bdbaf02 100644 --- a/include/ultrahdr/gainmapmath.h +++ b/lib/gainmapmath.h @@ -19,8 +19,8 @@ #include <cmath> -#include "ultrahdr/ultrahdr.h" -#include "ultrahdr/jpegr.h" +#include "ultrahdr.h" +#include "jpegr.h" #define CLIP3(x, min, max) ((x) < (min)) ? (min) : ((x) > (max)) ? (max) : (x) @@ -59,14 +59,13 @@ typedef float (*ColorCalculationFn)(Color); // // (A simple gamma transfer function sets g to gamma and a to 1.) typedef struct TransferFunction { - float g, a,b,c,d,e,f; + float g, a, b, c, d, e, f; } TransferFunction; -static constexpr TransferFunction kSRGB_TransFun = - { 2.4f, (float)(1/1.055), (float)(0.055/1.055), (float)(1/12.92), 0.04045f, 0.0f, 0.0f }; +static constexpr TransferFunction kSRGB_TransFun = { + 2.4f, (float)(1 / 1.055), (float)(0.055 / 1.055), (float)(1 / 12.92), 0.04045f, 0.0f, 0.0f}; -static constexpr TransferFunction kLinear_TransFun = - { 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f }; +static constexpr TransferFunction kLinear_TransFun = {1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}; inline Color operator+=(Color& lhs, const Color& rhs) { lhs.r += rhs.r; @@ -136,14 +135,13 @@ inline uint16_t floatToHalf(float f) { // round-to-nearest-even: add last bit after truncated mantissa const uint32_t b = *((uint32_t*)&f) + 0x00001000; - const int32_t e = (b & 0x7F800000) >> 23; // exponent - const uint32_t m = b & 0x007FFFFF; // mantissa + const int32_t e = (b & 0x7F800000) >> 23; // exponent + const uint32_t m = b & 0x007FFFFF; // mantissa // sign : normalized : denormalized : saturate - return (b & 0x80000000) >> 16 - | (e > 112) * ((((e - 112) << 10) & 0x7C00) | m >> 13) - | ((e < 113) & (e > 101)) * ((((0x007FF000 + m) >> (125 - e)) + 1) >> 1) - | (e > 143) * 0x7FFF; + return (b & 0x80000000) >> 16 | (e > 112) * ((((e - 112) << 10) & 0x7C00) | m >> 13) | + ((e < 113) & (e > 101)) * ((((0x007FF000 + m) >> (125 - e)) + 1) >> 1) | + (e > 143) * 0x7FFF; } constexpr size_t kGainFactorPrecision = 10; @@ -152,8 +150,8 @@ struct GainLUT { GainLUT(ultrahdr_metadata_ptr metadata) { for (size_t idx = 0; idx < kGainFactorNumEntries; idx++) { float value = static_cast<float>(idx) / static_cast<float>(kGainFactorNumEntries - 1); - float logBoost = log2(metadata->minContentBoost) * (1.0f - value) - + log2(metadata->maxContentBoost) * value; + float logBoost = log2(metadata->minContentBoost) * (1.0f - value) + + log2(metadata->maxContentBoost) * value; mGainTable[idx] = exp2(logBoost); } } @@ -162,23 +160,22 @@ struct GainLUT { float boostFactor = displayBoost > 0 ? displayBoost / metadata->maxContentBoost : 1.0f; for (size_t idx = 0; idx < kGainFactorNumEntries; idx++) { float value = static_cast<float>(idx) / static_cast<float>(kGainFactorNumEntries - 1); - float logBoost = log2(metadata->minContentBoost) * (1.0f - value) - + log2(metadata->maxContentBoost) * value; + float logBoost = log2(metadata->minContentBoost) * (1.0f - value) + + log2(metadata->maxContentBoost) * value; mGainTable[idx] = exp2(logBoost * boostFactor); } } - ~GainLUT() { - } + ~GainLUT() {} float getGainFactor(float gain) { uint32_t idx = static_cast<uint32_t>(gain * (kGainFactorNumEntries - 1) + 0.5); - //TODO() : Remove once conversion modules have appropriate clamping in place + // TODO() : Remove once conversion modules have appropriate clamping in place idx = CLIP3(idx, 0, kGainFactorNumEntries - 1); return mGainTable[idx]; } -private: + private: float mGainTable[kGainFactorNumEntries]; }; @@ -222,10 +219,10 @@ struct ShepardsIDW { // TODO: check if its ok to mWeights at places float* mWeightsNR; // no right float* mWeightsNB; // no bottom - float* mWeightsC; // no right & bottom + float* mWeightsC; // no right & bottom float euclideanDistance(float x1, float x2, float y1, float y2); - void fillShepardsIDW(float *weights, int incR, int incB); + void fillShepardsIDW(float* weights, int incR, int incB); }; //////////////////////////////////////////////////////////////////////////////// @@ -249,7 +246,6 @@ float srgbLuminance(Color e); */ Color srgbRgbToYuv(Color e_gamma); - /* * Convert from OETF'd srgb YUV to RGB, according to ITU-R BT.709-6. * @@ -294,7 +290,6 @@ Color p3RgbToYuv(Color e_gamma); */ Color p3YuvToRgb(Color e_gamma); - //////////////////////////////////////////////////////////////////////////////// // BT.2100 transformations - according to ITU-R BT.2100-2 @@ -371,7 +366,6 @@ Color pqInvOetfLUT(Color e_gamma); constexpr size_t kPqInvOETFPrecision = 12; constexpr size_t kPqInvOETFNumEntries = 1 << kPqInvOETFPrecision; - //////////////////////////////////////////////////////////////////////////////// // Color space conversions @@ -424,7 +418,6 @@ Color yuv2100To601(Color e_gamma); void transformYuv420(jr_uncompressed_ptr image, size_t x_chroma, size_t y_chroma, ColorTransformFn fn); - //////////////////////////////////////////////////////////////////////////////// // Gain map calculations @@ -500,6 +493,6 @@ uint32_t colorToRgba1010102(Color e_gamma); */ uint64_t colorToRgbaF16(Color e_gamma); -} // namespace ultrahdr +} // namespace ultrahdr -#endif // ULTRAHDR_GAINMAPMATH_H +#endif // ULTRAHDR_GAINMAPMATH_H diff --git a/lib/icc.cpp b/lib/icc.cpp new file mode 100644 index 0000000..851dd9d --- /dev/null +++ b/lib/icc.cpp @@ -0,0 +1,680 @@ +/* + * Copyright 2022 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 "ultrahdrcommon.h" +#include "icc.h" + +namespace ultrahdr { + +static void Matrix3x3_apply(const Matrix3x3* m, float* x) { + float y0 = x[0] * m->vals[0][0] + x[1] * m->vals[0][1] + x[2] * m->vals[0][2]; + float y1 = x[0] * m->vals[1][0] + x[1] * m->vals[1][1] + x[2] * m->vals[1][2]; + float y2 = x[0] * m->vals[2][0] + x[1] * m->vals[2][1] + x[2] * m->vals[2][2]; + x[0] = y0; + x[1] = y1; + x[2] = y2; +} + +bool Matrix3x3_invert(const Matrix3x3* src, Matrix3x3* dst) { + double a00 = src->vals[0][0]; + double a01 = src->vals[1][0]; + double a02 = src->vals[2][0]; + double a10 = src->vals[0][1]; + double a11 = src->vals[1][1]; + double a12 = src->vals[2][1]; + double a20 = src->vals[0][2]; + double a21 = src->vals[1][2]; + double a22 = src->vals[2][2]; + + double b0 = a00 * a11 - a01 * a10; + double b1 = a00 * a12 - a02 * a10; + double b2 = a01 * a12 - a02 * a11; + double b3 = a20; + double b4 = a21; + double b5 = a22; + + double determinant = b0 * b5 - b1 * b4 + b2 * b3; + + if (determinant == 0) { + return false; + } + + double invdet = 1.0 / determinant; + if (invdet > +FLT_MAX || invdet < -FLT_MAX || !isfinitef_((float)invdet)) { + return false; + } + + b0 *= invdet; + b1 *= invdet; + b2 *= invdet; + b3 *= invdet; + b4 *= invdet; + b5 *= invdet; + + dst->vals[0][0] = (float)(a11 * b5 - a12 * b4); + dst->vals[1][0] = (float)(a02 * b4 - a01 * b5); + dst->vals[2][0] = (float)(+b2); + dst->vals[0][1] = (float)(a12 * b3 - a10 * b5); + dst->vals[1][1] = (float)(a00 * b5 - a02 * b3); + dst->vals[2][1] = (float)(-b1); + dst->vals[0][2] = (float)(a10 * b4 - a11 * b3); + dst->vals[1][2] = (float)(a01 * b3 - a00 * b4); + dst->vals[2][2] = (float)(+b0); + + for (int r = 0; r < 3; ++r) + for (int c = 0; c < 3; ++c) { + if (!isfinitef_(dst->vals[r][c])) { + return false; + } + } + return true; +} + +static Matrix3x3 Matrix3x3_concat(const Matrix3x3* A, const Matrix3x3* B) { + Matrix3x3 m = {{{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}}; + for (int r = 0; r < 3; r++) + for (int c = 0; c < 3; c++) { + m.vals[r][c] = A->vals[r][0] * B->vals[0][c] + A->vals[r][1] * B->vals[1][c] + + A->vals[r][2] * B->vals[2][c]; + } + return m; +} + +static void float_XYZD50_to_grid16_lab(const float* xyz_float, uint8_t* grid16_lab) { + float v[3] = { + xyz_float[0] / kD50_x, + xyz_float[1] / kD50_y, + xyz_float[2] / kD50_z, + }; + for (size_t i = 0; i < 3; ++i) { + v[i] = v[i] > 0.008856f ? cbrtf(v[i]) : v[i] * 7.787f + (16 / 116.0f); + } + const float L = v[1] * 116.0f - 16.0f; + const float a = (v[0] - v[1]) * 500.0f; + const float b = (v[1] - v[2]) * 200.0f; + const float Lab_unorm[3] = { + L * (1 / 100.f), + (a + 128.0f) * (1 / 255.0f), + (b + 128.0f) * (1 / 255.0f), + }; + // This will encode L=1 as 0xFFFF. This matches how skcms will interpret the + // table, but the spec appears to indicate that the value should be 0xFF00. + // https://crbug.com/skia/13807 + for (size_t i = 0; i < 3; ++i) { + reinterpret_cast<uint16_t*>(grid16_lab)[i] = + Endian_SwapBE16(float_round_to_unorm16(Lab_unorm[i])); + } +} + +std::string IccHelper::get_desc_string(const ultrahdr_transfer_function tf, + const ultrahdr_color_gamut gamut) { + std::string result; + switch (gamut) { + case ULTRAHDR_COLORGAMUT_BT709: + result += "sRGB"; + break; + case ULTRAHDR_COLORGAMUT_P3: + result += "Display P3"; + break; + case ULTRAHDR_COLORGAMUT_BT2100: + result += "Rec2020"; + break; + default: + result += "Unknown"; + break; + } + result += " Gamut with "; + switch (tf) { + case ULTRAHDR_TF_SRGB: + result += "sRGB"; + break; + case ULTRAHDR_TF_LINEAR: + result += "Linear"; + break; + case ULTRAHDR_TF_PQ: + result += "PQ"; + break; + case ULTRAHDR_TF_HLG: + result += "HLG"; + break; + default: + result += "Unknown"; + break; + } + result += " Transfer"; + return result; +} + +std::shared_ptr<DataStruct> IccHelper::write_text_tag(const char* text) { + uint32_t text_length = strlen(text); + uint32_t header[] = { + Endian_SwapBE32(kTAG_TextType), // Type signature + 0, // Reserved + Endian_SwapBE32(1), // Number of records + Endian_SwapBE32(12), // Record size (must be 12) + Endian_SwapBE32(SetFourByteTag('e', 'n', 'U', 'S')), // English USA + Endian_SwapBE32(2 * text_length), // Length of string in bytes + Endian_SwapBE32(28), // Offset of string + }; + + uint32_t total_length = text_length * 2 + sizeof(header); + total_length = (((total_length + 2) >> 2) << 2); // 4 aligned + std::shared_ptr<DataStruct> dataStruct = std::make_shared<DataStruct>(total_length); + + if (!dataStruct->write(header, sizeof(header))) { + ALOGE("write_text_tag(): error in writing data"); + return dataStruct; + } + + for (size_t i = 0; i < text_length; i++) { + // Convert ASCII to big-endian UTF-16. + dataStruct->write8(0); + dataStruct->write8(text[i]); + } + + return dataStruct; +} + +std::shared_ptr<DataStruct> IccHelper::write_xyz_tag(float x, float y, float z) { + uint32_t data[] = { + Endian_SwapBE32(kXYZ_PCSSpace), + 0, + static_cast<uint32_t>(Endian_SwapBE32(float_round_to_fixed(x))), + static_cast<uint32_t>(Endian_SwapBE32(float_round_to_fixed(y))), + static_cast<uint32_t>(Endian_SwapBE32(float_round_to_fixed(z))), + }; + std::shared_ptr<DataStruct> dataStruct = std::make_shared<DataStruct>(sizeof(data)); + dataStruct->write(&data, sizeof(data)); + return dataStruct; +} + +std::shared_ptr<DataStruct> IccHelper::write_trc_tag(const int table_entries, + const void* table_16) { + int total_length = 4 + 4 + 4 + table_entries * 2; + total_length = (((total_length + 2) >> 2) << 2); // 4 aligned + std::shared_ptr<DataStruct> dataStruct = std::make_shared<DataStruct>(total_length); + dataStruct->write32(Endian_SwapBE32(kTAG_CurveType)); // Type + dataStruct->write32(0); // Reserved + dataStruct->write32(Endian_SwapBE32(table_entries)); // Value count + for (int i = 0; i < table_entries; ++i) { + uint16_t value = reinterpret_cast<const uint16_t*>(table_16)[i]; + dataStruct->write16(value); + } + return dataStruct; +} + +std::shared_ptr<DataStruct> IccHelper::write_trc_tag(const TransferFunction& fn) { + if (fn.a == 1.f && fn.b == 0.f && fn.c == 0.f && fn.d == 0.f && fn.e == 0.f && fn.f == 0.f) { + int total_length = 16; + std::shared_ptr<DataStruct> dataStruct = std::make_shared<DataStruct>(total_length); + dataStruct->write32(Endian_SwapBE32(kTAG_ParaCurveType)); // Type + dataStruct->write32(0); // Reserved + dataStruct->write32(Endian_SwapBE16(kExponential_ParaCurveType)); + dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.g))); + return dataStruct; + } + + int total_length = 40; + std::shared_ptr<DataStruct> dataStruct = std::make_shared<DataStruct>(total_length); + dataStruct->write32(Endian_SwapBE32(kTAG_ParaCurveType)); // Type + dataStruct->write32(0); // Reserved + dataStruct->write32(Endian_SwapBE16(kGABCDEF_ParaCurveType)); + dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.g))); + dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.a))); + dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.b))); + dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.c))); + dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.d))); + dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.e))); + dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.f))); + return dataStruct; +} + +float IccHelper::compute_tone_map_gain(const ultrahdr_transfer_function tf, float L) { + if (L <= 0.f) { + return 1.f; + } + if (tf == ULTRAHDR_TF_PQ) { + // The PQ transfer function will map to the range [0, 1]. Linearly scale + // it up to the range [0, 10,000/203]. We will then tone map that back + // down to [0, 1]. + constexpr float kInputMaxLuminance = 10000 / 203.f; + constexpr float kOutputMaxLuminance = 1.0; + L *= kInputMaxLuminance; + + // Compute the tone map gain which will tone map from 10,000/203 to 1.0. + constexpr float kToneMapA = kOutputMaxLuminance / (kInputMaxLuminance * kInputMaxLuminance); + constexpr float kToneMapB = 1.f / kOutputMaxLuminance; + return kInputMaxLuminance * (1.f + kToneMapA * L) / (1.f + kToneMapB * L); + } + if (tf == ULTRAHDR_TF_HLG) { + // Let Lw be the brightness of the display in nits. + constexpr float Lw = 203.f; + const float gamma = 1.2f + 0.42f * std::log(Lw / 1000.f) / std::log(10.f); + return std::pow(L, gamma - 1.f); + } + return 1.f; +} + +std::shared_ptr<DataStruct> IccHelper::write_cicp_tag(uint32_t color_primaries, + uint32_t transfer_characteristics) { + int total_length = 12; // 4 + 4 + 1 + 1 + 1 + 1 + std::shared_ptr<DataStruct> dataStruct = std::make_shared<DataStruct>(total_length); + dataStruct->write32(Endian_SwapBE32(kTAG_cicp)); // Type signature + dataStruct->write32(0); // Reserved + dataStruct->write8(color_primaries); // Color primaries + dataStruct->write8(transfer_characteristics); // Transfer characteristics + dataStruct->write8(0); // RGB matrix + dataStruct->write8(1); // Full range + return dataStruct; +} + +void IccHelper::compute_lut_entry(const Matrix3x3& src_to_XYZD50, float rgb[3]) { + // Compute the matrices to convert from source to Rec2020, and from Rec2020 to XYZD50. + Matrix3x3 src_to_rec2020; + const Matrix3x3 rec2020_to_XYZD50 = kRec2020; + { + Matrix3x3 XYZD50_to_rec2020; + Matrix3x3_invert(&rec2020_to_XYZD50, &XYZD50_to_rec2020); + src_to_rec2020 = Matrix3x3_concat(&XYZD50_to_rec2020, &src_to_XYZD50); + } + + // Convert the source signal to linear. + for (size_t i = 0; i < kNumChannels; ++i) { + rgb[i] = pqOetf(rgb[i]); + } + + // Convert source gamut to Rec2020. + Matrix3x3_apply(&src_to_rec2020, rgb); + + // Compute the luminance of the signal. + float L = bt2100Luminance({{{rgb[0], rgb[1], rgb[2]}}}); + + // Compute the tone map gain based on the luminance. + float tone_map_gain = compute_tone_map_gain(ULTRAHDR_TF_PQ, L); + + // Apply the tone map gain. + for (size_t i = 0; i < kNumChannels; ++i) { + rgb[i] *= tone_map_gain; + } + + // Convert from Rec2020-linear to XYZD50. + Matrix3x3_apply(&rec2020_to_XYZD50, rgb); +} + +std::shared_ptr<DataStruct> IccHelper::write_clut(const uint8_t* grid_points, + const uint8_t* grid_16) { + uint32_t value_count = kNumChannels; + for (uint32_t i = 0; i < kNumChannels; ++i) { + value_count *= grid_points[i]; + } + + int total_length = 20 + 2 * value_count; + total_length = (((total_length + 2) >> 2) << 2); // 4 aligned + std::shared_ptr<DataStruct> dataStruct = std::make_shared<DataStruct>(total_length); + + for (size_t i = 0; i < 16; ++i) { + dataStruct->write8(i < kNumChannels ? grid_points[i] : 0); // Grid size + } + dataStruct->write8(2); // Grid byte width (always 16-bit) + dataStruct->write8(0); // Reserved + dataStruct->write8(0); // Reserved + dataStruct->write8(0); // Reserved + + for (uint32_t i = 0; i < value_count; ++i) { + uint16_t value = reinterpret_cast<const uint16_t*>(grid_16)[i]; + dataStruct->write16(value); + } + + return dataStruct; +} + +std::shared_ptr<DataStruct> IccHelper::write_mAB_or_mBA_tag(uint32_t type, bool has_a_curves, + const uint8_t* grid_points, + const uint8_t* grid_16) { + const size_t b_curves_offset = 32; + std::shared_ptr<DataStruct> b_curves_data[kNumChannels]; + std::shared_ptr<DataStruct> a_curves_data[kNumChannels]; + size_t clut_offset = 0; + std::shared_ptr<DataStruct> clut; + size_t a_curves_offset = 0; + + // The "B" curve is required. + for (size_t i = 0; i < kNumChannels; ++i) { + b_curves_data[i] = write_trc_tag(kLinear_TransFun); + } + + // The "A" curve and CLUT are optional. + if (has_a_curves) { + clut_offset = b_curves_offset; + for (size_t i = 0; i < kNumChannels; ++i) { + clut_offset += b_curves_data[i]->getLength(); + } + clut = write_clut(grid_points, grid_16); + + a_curves_offset = clut_offset + clut->getLength(); + for (size_t i = 0; i < kNumChannels; ++i) { + a_curves_data[i] = write_trc_tag(kLinear_TransFun); + } + } + + int total_length = b_curves_offset; + for (size_t i = 0; i < kNumChannels; ++i) { + total_length += b_curves_data[i]->getLength(); + } + if (has_a_curves) { + total_length += clut->getLength(); + for (size_t i = 0; i < kNumChannels; ++i) { + total_length += a_curves_data[i]->getLength(); + } + } + std::shared_ptr<DataStruct> dataStruct = std::make_shared<DataStruct>(total_length); + dataStruct->write32(Endian_SwapBE32(type)); // Type signature + dataStruct->write32(0); // Reserved + dataStruct->write8(kNumChannels); // Input channels + dataStruct->write8(kNumChannels); // Output channels + dataStruct->write16(0); // Reserved + dataStruct->write32(Endian_SwapBE32(b_curves_offset)); // B curve offset + dataStruct->write32(Endian_SwapBE32(0)); // Matrix offset (ignored) + dataStruct->write32(Endian_SwapBE32(0)); // M curve offset (ignored) + dataStruct->write32(Endian_SwapBE32(clut_offset)); // CLUT offset + dataStruct->write32(Endian_SwapBE32(a_curves_offset)); // A curve offset + for (size_t i = 0; i < kNumChannels; ++i) { + if (dataStruct->write(b_curves_data[i]->getData(), b_curves_data[i]->getLength())) { + return dataStruct; + } + } + if (has_a_curves) { + dataStruct->write(clut->getData(), clut->getLength()); + for (size_t i = 0; i < kNumChannels; ++i) { + dataStruct->write(a_curves_data[i]->getData(), a_curves_data[i]->getLength()); + } + } + return dataStruct; +} + +std::shared_ptr<DataStruct> IccHelper::writeIccProfile(ultrahdr_transfer_function tf, + ultrahdr_color_gamut gamut) { + ICCHeader header; + + std::vector<std::pair<uint32_t, std::shared_ptr<DataStruct>>> tags; + + // Compute profile description tag + std::string desc = get_desc_string(tf, gamut); + + tags.emplace_back(kTAG_desc, write_text_tag(desc.c_str())); + + Matrix3x3 toXYZD50; + switch (gamut) { + case ULTRAHDR_COLORGAMUT_BT709: + toXYZD50 = kSRGB; + break; + case ULTRAHDR_COLORGAMUT_P3: + toXYZD50 = kDisplayP3; + break; + case ULTRAHDR_COLORGAMUT_BT2100: + toXYZD50 = kRec2020; + break; + default: + // Should not fall here. + return nullptr; + } + + // Compute primaries. + { + tags.emplace_back(kTAG_rXYZ, + write_xyz_tag(toXYZD50.vals[0][0], toXYZD50.vals[1][0], toXYZD50.vals[2][0])); + tags.emplace_back(kTAG_gXYZ, + write_xyz_tag(toXYZD50.vals[0][1], toXYZD50.vals[1][1], toXYZD50.vals[2][1])); + tags.emplace_back(kTAG_bXYZ, + write_xyz_tag(toXYZD50.vals[0][2], toXYZD50.vals[1][2], toXYZD50.vals[2][2])); + } + + // Compute white point tag (must be D50) + tags.emplace_back(kTAG_wtpt, write_xyz_tag(kD50_x, kD50_y, kD50_z)); + + // Compute transfer curves. + if (tf != ULTRAHDR_TF_PQ) { + if (tf == ULTRAHDR_TF_HLG) { + std::vector<uint8_t> trc_table; + trc_table.resize(kTrcTableSize * 2); + for (uint32_t i = 0; i < kTrcTableSize; ++i) { + float x = i / (kTrcTableSize - 1.f); + float y = hlgOetf(x); + y *= compute_tone_map_gain(tf, y); + float_to_table16(y, &trc_table[2 * i]); + } + + tags.emplace_back(kTAG_rTRC, + write_trc_tag(kTrcTableSize, reinterpret_cast<uint8_t*>(trc_table.data()))); + tags.emplace_back(kTAG_gTRC, + write_trc_tag(kTrcTableSize, reinterpret_cast<uint8_t*>(trc_table.data()))); + tags.emplace_back(kTAG_bTRC, + write_trc_tag(kTrcTableSize, reinterpret_cast<uint8_t*>(trc_table.data()))); + } else { + tags.emplace_back(kTAG_rTRC, write_trc_tag(kSRGB_TransFun)); + tags.emplace_back(kTAG_gTRC, write_trc_tag(kSRGB_TransFun)); + tags.emplace_back(kTAG_bTRC, write_trc_tag(kSRGB_TransFun)); + } + } + + // Compute CICP. + if (tf == ULTRAHDR_TF_HLG || tf == ULTRAHDR_TF_PQ) { + // The CICP tag is present in ICC 4.4, so update the header's version. + header.version = Endian_SwapBE32(0x04400000); + + uint32_t color_primaries = 0; + if (gamut == ULTRAHDR_COLORGAMUT_BT709) { + color_primaries = kCICPPrimariesSRGB; + } else if (gamut == ULTRAHDR_COLORGAMUT_P3) { + color_primaries = kCICPPrimariesP3; + } + + uint32_t transfer_characteristics = 0; + if (tf == ULTRAHDR_TF_SRGB) { + transfer_characteristics = kCICPTrfnSRGB; + } else if (tf == ULTRAHDR_TF_LINEAR) { + transfer_characteristics = kCICPTrfnLinear; + } else if (tf == ULTRAHDR_TF_PQ) { + transfer_characteristics = kCICPTrfnPQ; + } else if (tf == ULTRAHDR_TF_HLG) { + transfer_characteristics = kCICPTrfnHLG; + } + tags.emplace_back(kTAG_cicp, write_cicp_tag(color_primaries, transfer_characteristics)); + } + + // Compute A2B0. + if (tf == ULTRAHDR_TF_PQ) { + std::vector<uint8_t> a2b_grid; + a2b_grid.resize(kGridSize * kGridSize * kGridSize * kNumChannels * 2); + size_t a2b_grid_index = 0; + for (uint32_t r_index = 0; r_index < kGridSize; ++r_index) { + for (uint32_t g_index = 0; g_index < kGridSize; ++g_index) { + for (uint32_t b_index = 0; b_index < kGridSize; ++b_index) { + float rgb[3] = { + r_index / (kGridSize - 1.f), + g_index / (kGridSize - 1.f), + b_index / (kGridSize - 1.f), + }; + compute_lut_entry(toXYZD50, rgb); + float_XYZD50_to_grid16_lab(rgb, &a2b_grid[a2b_grid_index]); + a2b_grid_index += 6; + } + } + } + const uint8_t* grid_16 = reinterpret_cast<const uint8_t*>(a2b_grid.data()); + + uint8_t grid_points[kNumChannels]; + for (size_t i = 0; i < kNumChannels; ++i) { + grid_points[i] = kGridSize; + } + + auto a2b_data = write_mAB_or_mBA_tag(kTAG_mABType, + /* has_a_curves */ true, grid_points, grid_16); + tags.emplace_back(kTAG_A2B0, std::move(a2b_data)); + } + + // Compute B2A0. + if (tf == ULTRAHDR_TF_PQ) { + auto b2a_data = write_mAB_or_mBA_tag(kTAG_mBAType, + /* has_a_curves */ false, + /* grid_points */ nullptr, + /* grid_16 */ nullptr); + tags.emplace_back(kTAG_B2A0, std::move(b2a_data)); + } + + // Compute copyright tag + tags.emplace_back(kTAG_cprt, write_text_tag("Google Inc. 2022")); + + // Compute the size of the profile. + size_t tag_data_size = 0; + for (const auto& tag : tags) { + tag_data_size += tag.second->getLength(); + } + size_t tag_table_size = kICCTagTableEntrySize * tags.size(); + size_t profile_size = kICCHeaderSize + tag_table_size + tag_data_size; + + std::shared_ptr<DataStruct> dataStruct = + std::make_shared<DataStruct>(profile_size + kICCIdentifierSize); + + // Write identifier, chunk count, and chunk ID + if (!dataStruct->write(kICCIdentifier, sizeof(kICCIdentifier)) || !dataStruct->write8(1) || + !dataStruct->write8(1)) { + ALOGE("writeIccProfile(): error in identifier"); + return dataStruct; + } + + // Write the header. + header.data_color_space = Endian_SwapBE32(Signature_RGB); + header.pcs = Endian_SwapBE32(tf == ULTRAHDR_TF_PQ ? Signature_Lab : Signature_XYZ); + header.size = Endian_SwapBE32(profile_size); + header.tag_count = Endian_SwapBE32(tags.size()); + + if (!dataStruct->write(&header, sizeof(header))) { + ALOGE("writeIccProfile(): error in header"); + return dataStruct; + } + + // Write the tag table. Track the offset and size of the previous tag to + // compute each tag's offset. An empty SkData indicates that the previous + // tag is to be reused. + uint32_t last_tag_offset = sizeof(header) + tag_table_size; + uint32_t last_tag_size = 0; + for (const auto& tag : tags) { + last_tag_offset = last_tag_offset + last_tag_size; + last_tag_size = tag.second->getLength(); + uint32_t tag_table_entry[3] = { + Endian_SwapBE32(tag.first), + Endian_SwapBE32(last_tag_offset), + Endian_SwapBE32(last_tag_size), + }; + if (!dataStruct->write(tag_table_entry, sizeof(tag_table_entry))) { + ALOGE("writeIccProfile(): error in writing tag table"); + return dataStruct; + } + } + + // Write the tags. + for (const auto& tag : tags) { + if (!dataStruct->write(tag.second->getData(), tag.second->getLength())) { + ALOGE("writeIccProfile(): error in writing tags"); + return dataStruct; + } + } + + return dataStruct; +} + +bool IccHelper::tagsEqualToMatrix(const Matrix3x3& matrix, const uint8_t* red_tag, + const uint8_t* green_tag, const uint8_t* blue_tag) { + std::shared_ptr<DataStruct> red_tag_test = + write_xyz_tag(matrix.vals[0][0], matrix.vals[1][0], matrix.vals[2][0]); + std::shared_ptr<DataStruct> green_tag_test = + write_xyz_tag(matrix.vals[0][1], matrix.vals[1][1], matrix.vals[2][1]); + std::shared_ptr<DataStruct> blue_tag_test = + write_xyz_tag(matrix.vals[0][2], matrix.vals[1][2], matrix.vals[2][2]); + return memcmp(red_tag, red_tag_test->getData(), kColorantTagSize) == 0 && + memcmp(green_tag, green_tag_test->getData(), kColorantTagSize) == 0 && + memcmp(blue_tag, blue_tag_test->getData(), kColorantTagSize) == 0; +} + +ultrahdr_color_gamut IccHelper::readIccColorGamut(void* icc_data, size_t icc_size) { + // Each tag table entry consists of 3 fields of 4 bytes each. + static const size_t kTagTableEntrySize = 12; + + if (icc_data == nullptr || icc_size < sizeof(ICCHeader) + kICCIdentifierSize) { + return ULTRAHDR_COLORGAMUT_UNSPECIFIED; + } + + if (memcmp(icc_data, kICCIdentifier, sizeof(kICCIdentifier)) != 0) { + return ULTRAHDR_COLORGAMUT_UNSPECIFIED; + } + + uint8_t* icc_bytes = reinterpret_cast<uint8_t*>(icc_data) + kICCIdentifierSize; + + ICCHeader* header = reinterpret_cast<ICCHeader*>(icc_bytes); + + // Use 0 to indicate not found, since offsets are always relative to start + // of ICC data and therefore a tag offset of zero would never be valid. + size_t red_primary_offset = 0, green_primary_offset = 0, blue_primary_offset = 0; + size_t red_primary_size = 0, green_primary_size = 0, blue_primary_size = 0; + for (size_t tag_idx = 0; tag_idx < Endian_SwapBE32(header->tag_count); ++tag_idx) { + uint32_t* tag_entry_start = + reinterpret_cast<uint32_t*>(icc_bytes + sizeof(ICCHeader) + tag_idx * kTagTableEntrySize); + // first 4 bytes are the tag signature, next 4 bytes are the tag offset, + // last 4 bytes are the tag length in bytes. + if (red_primary_offset == 0 && *tag_entry_start == Endian_SwapBE32(kTAG_rXYZ)) { + red_primary_offset = Endian_SwapBE32(*(tag_entry_start + 1)); + red_primary_size = Endian_SwapBE32(*(tag_entry_start + 2)); + } else if (green_primary_offset == 0 && *tag_entry_start == Endian_SwapBE32(kTAG_gXYZ)) { + green_primary_offset = Endian_SwapBE32(*(tag_entry_start + 1)); + green_primary_size = Endian_SwapBE32(*(tag_entry_start + 2)); + } else if (blue_primary_offset == 0 && *tag_entry_start == Endian_SwapBE32(kTAG_bXYZ)) { + blue_primary_offset = Endian_SwapBE32(*(tag_entry_start + 1)); + blue_primary_size = Endian_SwapBE32(*(tag_entry_start + 2)); + } + } + + if (red_primary_offset == 0 || red_primary_size != kColorantTagSize || + kICCIdentifierSize + red_primary_offset + red_primary_size > icc_size || + green_primary_offset == 0 || green_primary_size != kColorantTagSize || + kICCIdentifierSize + green_primary_offset + green_primary_size > icc_size || + blue_primary_offset == 0 || blue_primary_size != kColorantTagSize || + kICCIdentifierSize + blue_primary_offset + blue_primary_size > icc_size) { + return ULTRAHDR_COLORGAMUT_UNSPECIFIED; + } + + uint8_t* red_tag = icc_bytes + red_primary_offset; + uint8_t* green_tag = icc_bytes + green_primary_offset; + uint8_t* blue_tag = icc_bytes + blue_primary_offset; + + // Serialize tags as we do on encode and compare what we find to that to + // determine the gamut (since we don't have a need yet for full deserialize). + if (tagsEqualToMatrix(kSRGB, red_tag, green_tag, blue_tag)) { + return ULTRAHDR_COLORGAMUT_BT709; + } else if (tagsEqualToMatrix(kDisplayP3, red_tag, green_tag, blue_tag)) { + return ULTRAHDR_COLORGAMUT_P3; + } else if (tagsEqualToMatrix(kRec2020, red_tag, green_tag, blue_tag)) { + return ULTRAHDR_COLORGAMUT_BT2100; + } + + // Didn't find a match to one of the profiles we write; indicate the gamut + // is unspecified since we don't understand it. + return ULTRAHDR_COLORGAMUT_UNSPECIFIED; +} + +} // namespace ultrahdr diff --git a/lib/icc.h b/lib/icc.h new file mode 100644 index 0000000..be7453e --- /dev/null +++ b/lib/icc.h @@ -0,0 +1,259 @@ +/* + * Copyright 2022 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_ICC_H +#define ULTRAHDR_ICC_H + +#include <memory> + +#ifndef USE_BIG_ENDIAN_IN_ICC +#define USE_BIG_ENDIAN_IN_ICC true +#endif + +#if USE_BIG_ENDIAN_IN_ICC +#define Endian_SwapBE32(n) EndianSwap32(n) +#define Endian_SwapBE16(n) EndianSwap16(n) +#else +#define Endian_SwapBE32(n) (n) +#define Endian_SwapBE16(n) (n) +#endif + +#include "ultrahdr.h" +#include "jpegr.h" +#include "gainmapmath.h" +#include "jpegrutils.h" + +namespace ultrahdr { + +typedef int32_t Fixed; +#define Fixed1 (1 << 16) +#define MaxS32FitsInFloat 2147483520 +#define MinS32FitsInFloat (-MaxS32FitsInFloat) +#define FixedToFloat(x) ((x)*1.52587890625e-5f) + +typedef struct Matrix3x3 { + float vals[3][3]; +} Matrix3x3; + +// The D50 illuminant. +constexpr float kD50_x = 0.9642f; +constexpr float kD50_y = 1.0000f; +constexpr float kD50_z = 0.8249f; + +enum { + // data_color_space + Signature_CMYK = 0x434D594B, + Signature_Gray = 0x47524159, + Signature_RGB = 0x52474220, + + // pcs + Signature_Lab = 0x4C616220, + Signature_XYZ = 0x58595A20, +}; + +typedef uint32_t FourByteTag; +static inline constexpr FourByteTag SetFourByteTag(char a, char b, char c, char d) { + return (((uint32_t)a << 24) | ((uint32_t)b << 16) | ((uint32_t)c << 8) | (uint32_t)d); +} + +static constexpr char kICCIdentifier[] = "ICC_PROFILE"; +// 12 for the actual identifier, +2 for the chunk count and chunk index which +// will always follow. +static constexpr size_t kICCIdentifierSize = 14; + +// This is equal to the header size according to the ICC specification (128) +// plus the size of the tag count (4). We include the tag count since we +// always require it to be present anyway. +static constexpr size_t kICCHeaderSize = 132; + +// Contains a signature (4), offset (4), and size (4). +static constexpr size_t kICCTagTableEntrySize = 12; + +// size should be 20; 4 bytes for type descriptor, 4 bytes reserved, 12 +// bytes for a single XYZ number type (4 bytes per coordinate). +static constexpr size_t kColorantTagSize = 20; + +static constexpr uint32_t kDisplay_Profile = SetFourByteTag('m', 'n', 't', 'r'); +static constexpr uint32_t kRGB_ColorSpace = SetFourByteTag('R', 'G', 'B', ' '); +static constexpr uint32_t kXYZ_PCSSpace = SetFourByteTag('X', 'Y', 'Z', ' '); +static constexpr uint32_t kACSP_Signature = SetFourByteTag('a', 'c', 's', 'p'); + +static constexpr uint32_t kTAG_desc = SetFourByteTag('d', 'e', 's', 'c'); +static constexpr uint32_t kTAG_TextType = SetFourByteTag('m', 'l', 'u', 'c'); +static constexpr uint32_t kTAG_rXYZ = SetFourByteTag('r', 'X', 'Y', 'Z'); +static constexpr uint32_t kTAG_gXYZ = SetFourByteTag('g', 'X', 'Y', 'Z'); +static constexpr uint32_t kTAG_bXYZ = SetFourByteTag('b', 'X', 'Y', 'Z'); +static constexpr uint32_t kTAG_wtpt = SetFourByteTag('w', 't', 'p', 't'); +static constexpr uint32_t kTAG_rTRC = SetFourByteTag('r', 'T', 'R', 'C'); +static constexpr uint32_t kTAG_gTRC = SetFourByteTag('g', 'T', 'R', 'C'); +static constexpr uint32_t kTAG_bTRC = SetFourByteTag('b', 'T', 'R', 'C'); +static constexpr uint32_t kTAG_cicp = SetFourByteTag('c', 'i', 'c', 'p'); +static constexpr uint32_t kTAG_cprt = SetFourByteTag('c', 'p', 'r', 't'); +static constexpr uint32_t kTAG_A2B0 = SetFourByteTag('A', '2', 'B', '0'); +static constexpr uint32_t kTAG_B2A0 = SetFourByteTag('B', '2', 'A', '0'); + +static constexpr uint32_t kTAG_CurveType = SetFourByteTag('c', 'u', 'r', 'v'); +static constexpr uint32_t kTAG_mABType = SetFourByteTag('m', 'A', 'B', ' '); +static constexpr uint32_t kTAG_mBAType = SetFourByteTag('m', 'B', 'A', ' '); +static constexpr uint32_t kTAG_ParaCurveType = SetFourByteTag('p', 'a', 'r', 'a'); + +static constexpr Matrix3x3 kSRGB = {{ + // ICC fixed-point (16.16) representation, taken from skcms. Please keep them exactly in sync. + // 0.436065674f, 0.385147095f, 0.143066406f, + // 0.222488403f, 0.716873169f, 0.060607910f, + // 0.013916016f, 0.097076416f, 0.714096069f, + {FixedToFloat(0x6FA2), FixedToFloat(0x6299), FixedToFloat(0x24A0)}, + {FixedToFloat(0x38F5), FixedToFloat(0xB785), FixedToFloat(0x0F84)}, + {FixedToFloat(0x0390), FixedToFloat(0x18DA), FixedToFloat(0xB6CF)}, +}}; + +static constexpr Matrix3x3 kDisplayP3 = {{ + {0.515102f, 0.291965f, 0.157153f}, + {0.241182f, 0.692236f, 0.0665819f}, + {-0.00104941f, 0.0418818f, 0.784378f}, +}}; + +static constexpr Matrix3x3 kRec2020 = {{ + {0.673459f, 0.165661f, 0.125100f}, + {0.279033f, 0.675338f, 0.0456288f}, + {-0.00193139f, 0.0299794f, 0.797162f}, +}}; + +static constexpr uint32_t kCICPPrimariesSRGB = 1; +static constexpr uint32_t kCICPPrimariesP3 = 12; +static constexpr uint32_t kCICPPrimariesRec2020 = 9; + +static constexpr uint32_t kCICPTrfnSRGB = 1; +static constexpr uint32_t kCICPTrfnLinear = 8; +static constexpr uint32_t kCICPTrfnPQ = 16; +static constexpr uint32_t kCICPTrfnHLG = 18; + +enum ParaCurveType { + kExponential_ParaCurveType = 0, + kGAB_ParaCurveType = 1, + kGABC_ParaCurveType = 2, + kGABDE_ParaCurveType = 3, + kGABCDEF_ParaCurveType = 4, +}; + +/** + * Return the closest int for the given float. Returns MaxS32FitsInFloat for NaN. + */ +static inline int float_saturate2int(float x) { + x = x < MaxS32FitsInFloat ? x : MaxS32FitsInFloat; + x = x > MinS32FitsInFloat ? x : MinS32FitsInFloat; + return (int)x; +} + +static Fixed float_round_to_fixed(float x) { + return float_saturate2int((float)floor((double)x * Fixed1 + 0.5)); +} + +static uint16_t float_round_to_unorm16(float x) { + x = x * 65535.f + 0.5; + if (x > 65535) return 65535; + if (x < 0) return 0; + return static_cast<uint16_t>(x); +} + +static inline void float_to_table16(const float f, uint8_t* table_16) { + *reinterpret_cast<uint16_t*>(table_16) = Endian_SwapBE16(float_round_to_unorm16(f)); +} + +static inline bool isfinitef_(float x) { return 0 == x * 0; } + +struct ICCHeader { + // Size of the profile (computed) + uint32_t size; + // Preferred CMM type (ignored) + uint32_t cmm_type = 0; + // Version 4.3 or 4.4 if CICP is included. + uint32_t version = Endian_SwapBE32(0x04300000); + // Display device profile + uint32_t profile_class = Endian_SwapBE32(kDisplay_Profile); + // RGB input color space; + uint32_t data_color_space = Endian_SwapBE32(kRGB_ColorSpace); + // Profile connection space. + uint32_t pcs = Endian_SwapBE32(kXYZ_PCSSpace); + // Date and time (ignored) + uint8_t creation_date_time[12] = {0}; + // Profile signature + uint32_t signature = Endian_SwapBE32(kACSP_Signature); + // Platform target (ignored) + uint32_t platform = 0; + // Flags: not embedded, can be used independently + uint32_t flags = 0x00000000; + // Device manufacturer (ignored) + uint32_t device_manufacturer = 0; + // Device model (ignored) + uint32_t device_model = 0; + // Device attributes (ignored) + uint8_t device_attributes[8] = {0}; + // Relative colorimetric rendering intent + uint32_t rendering_intent = Endian_SwapBE32(1); + // D50 standard illuminant (X, Y, Z) + uint32_t illuminant_X = Endian_SwapBE32(float_round_to_fixed(kD50_x)); + uint32_t illuminant_Y = Endian_SwapBE32(float_round_to_fixed(kD50_y)); + uint32_t illuminant_Z = Endian_SwapBE32(float_round_to_fixed(kD50_z)); + // Profile creator (ignored) + uint32_t creator = 0; + // Profile id checksum (ignored) + uint8_t profile_id[16] = {0}; + // Reserved (ignored) + uint8_t reserved[28] = {0}; + // Technically not part of header, but required + uint32_t tag_count = 0; +}; + +class IccHelper { + private: + static constexpr uint32_t kTrcTableSize = 65; + static constexpr uint32_t kGridSize = 17; + static constexpr size_t kNumChannels = 3; + + static std::shared_ptr<DataStruct> write_text_tag(const char* text); + static std::string get_desc_string(const ultrahdr_transfer_function tf, + const ultrahdr_color_gamut gamut); + static std::shared_ptr<DataStruct> write_xyz_tag(float x, float y, float z); + static std::shared_ptr<DataStruct> write_trc_tag(const int table_entries, const void* table_16); + static std::shared_ptr<DataStruct> write_trc_tag(const TransferFunction& fn); + static float compute_tone_map_gain(const ultrahdr_transfer_function tf, float L); + static std::shared_ptr<DataStruct> write_cicp_tag(uint32_t color_primaries, + uint32_t transfer_characteristics); + static std::shared_ptr<DataStruct> write_mAB_or_mBA_tag(uint32_t type, bool has_a_curves, + const uint8_t* grid_points, + const uint8_t* grid_16); + static void compute_lut_entry(const Matrix3x3& src_to_XYZD50, float rgb[3]); + static std::shared_ptr<DataStruct> write_clut(const uint8_t* grid_points, const uint8_t* grid_16); + + // Checks if a set of xyz tags is equivalent to a 3x3 Matrix. Each input + // tag buffer assumed to be at least kColorantTagSize in size. + static bool tagsEqualToMatrix(const Matrix3x3& matrix, const uint8_t* red_tag, + const uint8_t* green_tag, const uint8_t* blue_tag); + + public: + // Output includes JPEG embedding identifier and chunk information, but not + // APPx information. + static std::shared_ptr<DataStruct> writeIccProfile(const ultrahdr_transfer_function tf, + const ultrahdr_color_gamut gamut); + // NOTE: this function is not robust; it can infer gamuts that IccHelper + // writes out but should not be considered a reference implementation for + // robust parsing of ICC profiles or their gamuts. + static ultrahdr_color_gamut readIccColorGamut(void* icc_data, size_t icc_size); +}; +} // namespace ultrahdr + +#endif // ULTRAHDR_ICC_H diff --git a/lib/jpegdecoderhelper.cpp b/lib/jpegdecoderhelper.cpp new file mode 100644 index 0000000..eb55a2e --- /dev/null +++ b/lib/jpegdecoderhelper.cpp @@ -0,0 +1,537 @@ +/* + * Copyright 2022 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 <errno.h> +#include <setjmp.h> + +#include <cstring> + +#include "ultrahdrcommon.h" +#include "ultrahdr.h" +#include "jpegdecoderhelper.h" + +using namespace std; + +namespace ultrahdr { + +const uint32_t kAPP0Marker = JPEG_APP0; // JFIF +const uint32_t kAPP1Marker = JPEG_APP0 + 1; // EXIF, XMP +const uint32_t kAPP2Marker = JPEG_APP0 + 2; // ICC + +constexpr uint32_t kICCMarkerHeaderSize = 14; +constexpr uint8_t kICCSig[] = { + 'I', 'C', 'C', '_', 'P', 'R', 'O', 'F', 'I', 'L', 'E', '\0', +}; +constexpr uint8_t kXmpNameSpace[] = { + 'h', 't', 't', 'p', ':', '/', '/', 'n', 's', '.', 'a', 'd', 'o', 'b', 'e', + '.', 'c', 'o', 'm', '/', 'x', 'a', 'p', '/', '1', '.', '0', '/', '\0', +}; +constexpr uint8_t kExifIdCode[] = { + 'E', 'x', 'i', 'f', '\0', '\0', +}; + +struct jpegr_source_mgr : jpeg_source_mgr { + jpegr_source_mgr(const uint8_t* ptr, int len); + ~jpegr_source_mgr(); + + const uint8_t* mBufferPtr; + size_t mBufferLength; +}; + +struct jpegrerror_mgr { + struct jpeg_error_mgr pub; + jmp_buf setjmp_buffer; +}; + +static void jpegr_init_source(j_decompress_ptr cinfo) { + jpegr_source_mgr* src = static_cast<jpegr_source_mgr*>(cinfo->src); + src->next_input_byte = static_cast<const JOCTET*>(src->mBufferPtr); + src->bytes_in_buffer = src->mBufferLength; +} + +static boolean jpegr_fill_input_buffer(j_decompress_ptr /* cinfo */) { + ALOGE("%s : should not get here", __func__); + return FALSE; +} + +static void jpegr_skip_input_data(j_decompress_ptr cinfo, long num_bytes) { + jpegr_source_mgr* src = static_cast<jpegr_source_mgr*>(cinfo->src); + + if (num_bytes > static_cast<long>(src->bytes_in_buffer)) { + ALOGE("jpegr_skip_input_data - num_bytes > (long)src->bytes_in_buffer"); + } else { + src->next_input_byte += num_bytes; + src->bytes_in_buffer -= num_bytes; + } +} + +static void jpegr_term_source(j_decompress_ptr /*cinfo*/) {} + +jpegr_source_mgr::jpegr_source_mgr(const uint8_t* ptr, int len) + : mBufferPtr(ptr), mBufferLength(len) { + init_source = jpegr_init_source; + fill_input_buffer = jpegr_fill_input_buffer; + skip_input_data = jpegr_skip_input_data; + resync_to_restart = jpeg_resync_to_restart; + term_source = jpegr_term_source; +} + +jpegr_source_mgr::~jpegr_source_mgr() {} + +static void jpegrerror_exit(j_common_ptr cinfo) { + jpegrerror_mgr* err = reinterpret_cast<jpegrerror_mgr*>(cinfo->err); + longjmp(err->setjmp_buffer, 1); +} + +static void output_message(j_common_ptr cinfo) { + char buffer[JMSG_LENGTH_MAX]; + + /* Create the message */ + (*cinfo->err->format_message)(cinfo, buffer); + ALOGE("%s\n", buffer); +} + +JpegDecoderHelper::JpegDecoderHelper() {} + +JpegDecoderHelper::~JpegDecoderHelper() {} + +bool JpegDecoderHelper::decompressImage(const void* image, int length, bool decodeToRGBA) { + if (image == nullptr || length <= 0) { + ALOGE("Image size can not be handled: %d", length); + return false; + } + mResultBuffer.clear(); + mXMPBuffer.clear(); + return decode(image, length, decodeToRGBA); +} + +void* JpegDecoderHelper::getDecompressedImagePtr() { return mResultBuffer.data(); } + +size_t JpegDecoderHelper::getDecompressedImageSize() { return mResultBuffer.size(); } + +void* JpegDecoderHelper::getXMPPtr() { return mXMPBuffer.data(); } + +size_t JpegDecoderHelper::getXMPSize() { return mXMPBuffer.size(); } + +void* JpegDecoderHelper::getEXIFPtr() { return mEXIFBuffer.data(); } + +size_t JpegDecoderHelper::getEXIFSize() { return mEXIFBuffer.size(); } + +void* JpegDecoderHelper::getICCPtr() { return mICCBuffer.data(); } + +size_t JpegDecoderHelper::getICCSize() { return mICCBuffer.size(); } + +size_t JpegDecoderHelper::getDecompressedImageWidth() { return mWidth; } + +size_t JpegDecoderHelper::getDecompressedImageHeight() { return mHeight; } + +// Here we only handle the first EXIF package, and in theary EXIF (or JFIF) must be the first +// in the image file. +// We assume that all packages are starting with two bytes marker (eg FF E1 for EXIF package), +// two bytes of package length which is stored in marker->original_length, and the real data +// which is stored in marker->data. +bool JpegDecoderHelper::extractEXIF(const void* image, int length) { + jpeg_decompress_struct cinfo; + jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length); + jpegrerror_mgr myerr; + + cinfo.err = jpeg_std_error(&myerr.pub); + myerr.pub.error_exit = jpegrerror_exit; + myerr.pub.output_message = output_message; + + if (setjmp(myerr.setjmp_buffer)) { + jpeg_destroy_decompress(&cinfo); + return false; + } + jpeg_create_decompress(&cinfo); + + jpeg_save_markers(&cinfo, kAPP0Marker, 0xFFFF); + jpeg_save_markers(&cinfo, kAPP1Marker, 0xFFFF); + + cinfo.src = &mgr; + jpeg_read_header(&cinfo, TRUE); + + size_t pos = 2; // position after SOI + for (jpeg_marker_struct* marker = cinfo.marker_list; marker; marker = marker->next) { + pos += 4; + pos += marker->original_length; + + if (marker->marker != kAPP1Marker) { + continue; + } + + const unsigned int len = marker->data_length; + + if (len > sizeof(kExifIdCode) && !memcmp(marker->data, kExifIdCode, sizeof(kExifIdCode))) { + mEXIFBuffer.resize(len, 0); + memcpy(static_cast<void*>(mEXIFBuffer.data()), marker->data, len); + mExifPos = pos - marker->original_length; + break; + } + } + + jpeg_destroy_decompress(&cinfo); + return true; +} + +bool JpegDecoderHelper::decode(const void* image, int length, bool decodeToRGBA) { + bool status = true; + jpeg_decompress_struct cinfo; + jpegrerror_mgr myerr; + cinfo.err = jpeg_std_error(&myerr.pub); + myerr.pub.error_exit = jpegrerror_exit; + myerr.pub.output_message = output_message; + + if (setjmp(myerr.setjmp_buffer)) { + jpeg_destroy_decompress(&cinfo); + return false; + } + + jpeg_create_decompress(&cinfo); + + jpeg_save_markers(&cinfo, kAPP0Marker, 0xFFFF); + jpeg_save_markers(&cinfo, kAPP1Marker, 0xFFFF); + jpeg_save_markers(&cinfo, kAPP2Marker, 0xFFFF); + + jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length); + cinfo.src = &mgr; + if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK) { + jpeg_destroy_decompress(&cinfo); + return false; + } + + // Save XMP data, EXIF data, and ICC data. + // Here we only handle the first XMP / EXIF / ICC package. + // We assume that all packages are starting with two bytes marker (eg FF E1 for EXIF package), + // two bytes of package length which is stored in marker->original_length, and the real data + // which is stored in marker->data. + bool exifAppears = false; + bool xmpAppears = false; + bool iccAppears = false; + size_t pos = 2; // position after SOI + for (jpeg_marker_struct* marker = cinfo.marker_list; + marker && !(exifAppears && xmpAppears && iccAppears); marker = marker->next) { + pos += 4; + pos += marker->original_length; + if (marker->marker != kAPP1Marker && marker->marker != kAPP2Marker) { + continue; + } + const unsigned int len = marker->data_length; + if (!xmpAppears && len > sizeof(kXmpNameSpace) && + !memcmp(marker->data, kXmpNameSpace, sizeof(kXmpNameSpace))) { + mXMPBuffer.resize(len + 1, 0); + memcpy(static_cast<void*>(mXMPBuffer.data()), marker->data, len); + xmpAppears = true; + } else if (!exifAppears && len > sizeof(kExifIdCode) && + !memcmp(marker->data, kExifIdCode, sizeof(kExifIdCode))) { + mEXIFBuffer.resize(len, 0); + memcpy(static_cast<void*>(mEXIFBuffer.data()), marker->data, len); + exifAppears = true; + mExifPos = pos - marker->original_length; + } else if (!iccAppears && len > sizeof(kICCSig) && + !memcmp(marker->data, kICCSig, sizeof(kICCSig))) { + mICCBuffer.resize(len, 0); + memcpy(static_cast<void*>(mICCBuffer.data()), marker->data, len); + iccAppears = true; + } + } + + mWidth = cinfo.image_width; + mHeight = cinfo.image_height; + if (mWidth > kMaxWidth || mHeight > kMaxHeight) { + status = false; + goto CleanUp; + } + + if (decodeToRGBA) { + // The primary image is expected to be yuv420 sampling + if (cinfo.jpeg_color_space != JCS_YCbCr) { + status = false; + ALOGE("%s: decodeToRGBA unexpected jpeg color space ", __func__); + goto CleanUp; + } + if (cinfo.comp_info[0].h_samp_factor != 2 || cinfo.comp_info[0].v_samp_factor != 2 || + cinfo.comp_info[1].h_samp_factor != 1 || cinfo.comp_info[1].v_samp_factor != 1 || + cinfo.comp_info[2].h_samp_factor != 1 || cinfo.comp_info[2].v_samp_factor != 1) { + status = false; + ALOGE("%s: decodeToRGBA unexpected primary image sub-sampling", __func__); + goto CleanUp; + } + // 4 bytes per pixel + mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 4); + cinfo.out_color_space = JCS_EXT_RGBA; + } else { + if (cinfo.jpeg_color_space == JCS_YCbCr) { + if (cinfo.comp_info[0].h_samp_factor != 2 || cinfo.comp_info[0].v_samp_factor != 2 || + cinfo.comp_info[1].h_samp_factor != 1 || cinfo.comp_info[1].v_samp_factor != 1 || + cinfo.comp_info[2].h_samp_factor != 1 || cinfo.comp_info[2].v_samp_factor != 1) { + status = false; + ALOGE("%s: decoding to YUV only supports 4:2:0 subsampling", __func__); + goto CleanUp; + } + mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 3 / 2, 0); + } else if (cinfo.jpeg_color_space == JCS_GRAYSCALE) { + mResultBuffer.resize(cinfo.image_width * cinfo.image_height, 0); + } else { + status = false; + ALOGE("%s: decodeToYUV unexpected jpeg color space", __func__); + goto CleanUp; + } + cinfo.out_color_space = cinfo.jpeg_color_space; + cinfo.raw_data_out = TRUE; + } + + cinfo.dct_method = JDCT_ISLOW; + jpeg_start_decompress(&cinfo); + if (!decompress(&cinfo, static_cast<const uint8_t*>(mResultBuffer.data()), + cinfo.jpeg_color_space == JCS_GRAYSCALE)) { + status = false; + goto CleanUp; + } + +CleanUp: + jpeg_finish_decompress(&cinfo); + jpeg_destroy_decompress(&cinfo); + + return status; +} + +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)); +} + +bool JpegDecoderHelper::getCompressedImageParameters(const void* image, int length, size_t* pWidth, + size_t* pHeight, std::vector<uint8_t>* iccData, + std::vector<uint8_t>* exifData) { + jpeg_decompress_struct cinfo; + jpegrerror_mgr myerr; + cinfo.err = jpeg_std_error(&myerr.pub); + myerr.pub.error_exit = jpegrerror_exit; + myerr.pub.output_message = output_message; + + if (setjmp(myerr.setjmp_buffer)) { + jpeg_destroy_decompress(&cinfo); + return false; + } + jpeg_create_decompress(&cinfo); + + jpeg_save_markers(&cinfo, kAPP1Marker, 0xFFFF); + jpeg_save_markers(&cinfo, kAPP2Marker, 0xFFFF); + + jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length); + cinfo.src = &mgr; + if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK) { + jpeg_destroy_decompress(&cinfo); + return false; + } + + if (pWidth != nullptr) { + *pWidth = cinfo.image_width; + } + if (pHeight != nullptr) { + *pHeight = cinfo.image_height; + } + + if (iccData != nullptr) { + for (jpeg_marker_struct* marker = cinfo.marker_list; marker; marker = marker->next) { + if (marker->marker != kAPP2Marker) { + continue; + } + if (marker->data_length <= kICCMarkerHeaderSize || + memcmp(marker->data, kICCSig, sizeof(kICCSig)) != 0) { + continue; + } + + iccData->insert(iccData->end(), marker->data, marker->data + marker->data_length); + } + } + + if (exifData != nullptr) { + bool exifAppears = false; + for (jpeg_marker_struct* marker = cinfo.marker_list; marker && !exifAppears; + marker = marker->next) { + if (marker->marker != kAPP1Marker) { + continue; + } + + const unsigned int len = marker->data_length; + if (len >= sizeof(kExifIdCode) && !memcmp(marker->data, kExifIdCode, sizeof(kExifIdCode))) { + exifData->resize(len, 0); + memcpy(static_cast<void*>(exifData->data()), marker->data, len); + exifAppears = true; + } + } + } + + jpeg_destroy_decompress(&cinfo); + return true; +} + +bool JpegDecoderHelper::decompressRGBA(jpeg_decompress_struct* cinfo, const uint8_t* dest) { + JSAMPLE* out = (JSAMPLE*)dest; + + while (cinfo->output_scanline < cinfo->image_height) { + if (1 != jpeg_read_scanlines(cinfo, &out, 1)) return false; + out += cinfo->image_width * 4; + } + return true; +} + +bool JpegDecoderHelper::decompressYUV(jpeg_decompress_struct* cinfo, const uint8_t* dest) { + size_t luma_plane_size = cinfo->image_width * cinfo->image_height; + size_t chroma_plane_size = luma_plane_size / 4; + uint8_t* y_plane = const_cast<uint8_t*>(dest); + uint8_t* u_plane = const_cast<uint8_t*>(dest + luma_plane_size); + uint8_t* v_plane = const_cast<uint8_t*>(dest + luma_plane_size + chroma_plane_size); + + const size_t aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize); + const bool is_width_aligned = (aligned_width == cinfo->image_width); + uint8_t* y_plane_intrm = nullptr; + uint8_t* u_plane_intrm = nullptr; + uint8_t* v_plane_intrm = nullptr; + + JSAMPROW y[kCompressBatchSize]; + JSAMPROW cb[kCompressBatchSize / 2]; + JSAMPROW cr[kCompressBatchSize / 2]; + JSAMPARRAY planes[3]{y, cb, cr}; + JSAMPROW y_intrm[kCompressBatchSize]; + JSAMPROW cb_intrm[kCompressBatchSize / 2]; + JSAMPROW cr_intrm[kCompressBatchSize / 2]; + JSAMPARRAY planes_intrm[3]{y_intrm, cb_intrm, cr_intrm}; + + if (cinfo->image_height % kCompressBatchSize != 0) { + mEmpty = std::make_unique<uint8_t[]>(aligned_width); + } + + if (!is_width_aligned) { + size_t mcu_row_size = aligned_width * kCompressBatchSize * 3 / 2; + mBufferIntermediate = std::make_unique<uint8_t[]>(mcu_row_size); + y_plane_intrm = mBufferIntermediate.get(); + u_plane_intrm = y_plane_intrm + (aligned_width * kCompressBatchSize); + v_plane_intrm = u_plane_intrm + (aligned_width * kCompressBatchSize) / 4; + for (int i = 0; i < kCompressBatchSize; ++i) { + y_intrm[i] = y_plane_intrm + i * aligned_width; + } + for (int i = 0; i < kCompressBatchSize / 2; ++i) { + int offset_intrm = i * (aligned_width / 2); + cb_intrm[i] = u_plane_intrm + offset_intrm; + cr_intrm[i] = v_plane_intrm + offset_intrm; + } + } + + while (cinfo->output_scanline < cinfo->image_height) { + size_t scanline_copy = cinfo->output_scanline; + for (int i = 0; i < kCompressBatchSize; ++i) { + size_t scanline = cinfo->output_scanline + i; + if (scanline < cinfo->image_height) { + y[i] = y_plane + scanline * cinfo->image_width; + } else { + y[i] = mEmpty.get(); + } + } + // cb, cr only have half scanlines + for (int i = 0; i < kCompressBatchSize / 2; ++i) { + size_t scanline = cinfo->output_scanline / 2 + i; + if (scanline < cinfo->image_height / 2) { + int offset = scanline * (cinfo->image_width / 2); + cb[i] = u_plane + offset; + cr[i] = v_plane + offset; + } else { + cb[i] = cr[i] = mEmpty.get(); + } + } + + int processed = + jpeg_read_raw_data(cinfo, is_width_aligned ? planes : planes_intrm, kCompressBatchSize); + if (processed != kCompressBatchSize) { + ALOGE("Number of processed lines does not equal input lines."); + return false; + } + if (!is_width_aligned) { + for (int i = 0; i < kCompressBatchSize; ++i) { + if (scanline_copy + i < cinfo->image_height) { + memcpy(y[i], y_intrm[i], cinfo->image_width); + } + } + for (int i = 0; i < kCompressBatchSize / 2; ++i) { + if (((scanline_copy / 2) + i) < (cinfo->image_height / 2)) { + memcpy(cb[i], cb_intrm[i], cinfo->image_width / 2); + memcpy(cr[i], cr_intrm[i], cinfo->image_width / 2); + } + } + } + } + return true; +} + +bool JpegDecoderHelper::decompressSingleChannel(jpeg_decompress_struct* cinfo, + const uint8_t* dest) { + uint8_t* y_plane = const_cast<uint8_t*>(dest); + uint8_t* y_plane_intrm = nullptr; + + const size_t aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize); + const bool is_width_aligned = (aligned_width == cinfo->image_width); + + JSAMPROW y[kCompressBatchSize]; + JSAMPARRAY planes[1]{y}; + JSAMPROW y_intrm[kCompressBatchSize]; + JSAMPARRAY planes_intrm[1]{y_intrm}; + + if (cinfo->image_height % kCompressBatchSize != 0) { + mEmpty = std::make_unique<uint8_t[]>(aligned_width); + } + + if (!is_width_aligned) { + size_t mcu_row_size = aligned_width * kCompressBatchSize; + mBufferIntermediate = std::make_unique<uint8_t[]>(mcu_row_size); + y_plane_intrm = mBufferIntermediate.get(); + for (int i = 0; i < kCompressBatchSize; ++i) { + y_intrm[i] = y_plane_intrm + i * aligned_width; + } + } + + while (cinfo->output_scanline < cinfo->image_height) { + size_t scanline_copy = cinfo->output_scanline; + for (int i = 0; i < kCompressBatchSize; ++i) { + size_t scanline = cinfo->output_scanline + i; + if (scanline < cinfo->image_height) { + y[i] = y_plane + scanline * cinfo->image_width; + } else { + y[i] = mEmpty.get(); + } + } + + int processed = + jpeg_read_raw_data(cinfo, is_width_aligned ? planes : planes_intrm, kCompressBatchSize); + if (processed != kCompressBatchSize / 2) { + ALOGE("Number of processed lines does not equal input lines."); + return false; + } + if (!is_width_aligned) { + for (int i = 0; i < kCompressBatchSize; ++i) { + if (scanline_copy + i < cinfo->image_height) { + memcpy(y[i], y_intrm[i], cinfo->image_width); + } + } + } + } + return true; +} + +} // namespace ultrahdr diff --git a/lib/jpegdecoderhelper.h b/lib/jpegdecoderhelper.h new file mode 100644 index 0000000..01a05e4 --- /dev/null +++ b/lib/jpegdecoderhelper.h @@ -0,0 +1,154 @@ +/* + * Copyright 2022 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_JPEGDECODERHELPER_H +#define ULTRAHDR_JPEGDECODERHELPER_H + +#include <stdio.h> // For jpeglib.h. + +// C++ build requires extern C for jpeg internals. +#ifdef __cplusplus +extern "C" { +#endif + +#include <jerror.h> +#include <jpeglib.h> + +#ifdef __cplusplus +} // extern "C" +#endif + +#include <cstdint> +#include <memory> +#include <vector> + +// 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 { +/* + * Encapsulates a converter from JPEG to raw image (YUV420planer or grey-scale) format. + * This class is not thread-safe. + */ +class JpegDecoderHelper { + public: + JpegDecoderHelper(); + ~JpegDecoderHelper(); + /* + * Decompresses JPEG image to raw image (YUV420planer, grey-scale or RGBA) format. After + * calling this method, call getDecompressedImage() to get the image. + * Returns false if decompressing the image fails. + */ + bool decompressImage(const void* image, int length, bool decodeToRGBA = false); + /* + * Returns the decompressed raw image buffer pointer. This method must be called only after + * calling decompressImage(). + */ + void* getDecompressedImagePtr(); + /* + * Returns the decompressed raw image buffer size. This method must be called only after + * calling decompressImage(). + */ + size_t getDecompressedImageSize(); + /* + * Returns the image width in pixels. This method must be called only after calling + * decompressImage(). + */ + size_t getDecompressedImageWidth(); + /* + * Returns the image width in pixels. This method must be called only after calling + * decompressImage(). + */ + size_t getDecompressedImageHeight(); + /* + * Returns the XMP data from the image. + */ + void* getXMPPtr(); + /* + * Returns the decompressed XMP buffer size. This method must be called only after + * calling decompressImage() or getCompressedImageParameters(). + */ + size_t getXMPSize(); + /* + * Extracts EXIF package and updates the EXIF position / length without decoding the image. + */ + bool extractEXIF(const void* image, int length); + /* + * Returns the EXIF data from the image. + * This method must be called after extractEXIF() or decompressImage(). + */ + void* getEXIFPtr(); + /* + * Returns the decompressed EXIF buffer size. This method must be called only after + * calling decompressImage(), extractEXIF() or getCompressedImageParameters(). + */ + size_t getEXIFSize(); + /* + * Returns the position offset of EXIF package + * (4 bypes offset to FF sign, the byte after FF E1 XX XX <this byte>), + * or -1 if no EXIF exists. + * This method must be called after extractEXIF() or decompressImage(). + */ + int getEXIFPos() { return mExifPos; } + /* + * Returns the ICC data from the image. + */ + void* getICCPtr(); + /* + * Returns the decompressed ICC buffer size. This method must be called only after + * calling decompressImage() or getCompressedImageParameters(). + */ + size_t getICCSize(); + /* + * Decompresses metadata of the image. All vectors are owned by the caller. + */ + bool getCompressedImageParameters(const void* image, int length, size_t* pWidth, size_t* pHeight, + std::vector<uint8_t>* iccData, std::vector<uint8_t>* exifData); + + private: + bool decode(const void* image, int length, bool decodeToRGBA); + // Returns false if errors occur. + bool decompress(jpeg_decompress_struct* cinfo, const uint8_t* dest, bool isSingleChannel); + bool decompressYUV(jpeg_decompress_struct* cinfo, const uint8_t* dest); + bool decompressRGBA(jpeg_decompress_struct* cinfo, const uint8_t* dest); + bool decompressSingleChannel(jpeg_decompress_struct* cinfo, const uint8_t* dest); + // Process 16 lines of Y and 16 lines of U/V each time. + // We must pass at least 16 scanlines according to libjpeg documentation. + static const int kCompressBatchSize = 16; + // The buffer that holds the decompressed result. + std::vector<JOCTET> mResultBuffer; + // The buffer that holds XMP Data. + std::vector<JOCTET> mXMPBuffer; + // The buffer that holds EXIF Data. + std::vector<JOCTET> mEXIFBuffer; + // The buffer that holds ICC Data. + std::vector<JOCTET> mICCBuffer; + + // Resolution of the decompressed image. + size_t mWidth; + size_t mHeight; + + // Position of EXIF package, default value is -1 which means no EXIF package appears. + int mExifPos = -1; + + std::unique_ptr<uint8_t[]> mEmpty = nullptr; + std::unique_ptr<uint8_t[]> mBufferIntermediate = nullptr; +}; +} /* namespace ultrahdr */ + +#endif // ULTRAHDR_JPEGDECODERHELPER_H diff --git a/lib/jpegencoderhelper.cpp b/lib/jpegencoderhelper.cpp new file mode 100644 index 0000000..c6f0b77 --- /dev/null +++ b/lib/jpegencoderhelper.cpp @@ -0,0 +1,287 @@ +/* + * Copyright 2022 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 <memory> +#include <string> + +#include "ultrahdrcommon.h" +#include "ultrahdr.h" +#include "jpegencoderhelper.h" + +namespace ultrahdr { + +// The destination manager that can access |mResultBuffer| in JpegEncoderHelper. +struct destination_mgr { + struct jpeg_destination_mgr mgr; + JpegEncoderHelper* encoder; +}; + +JpegEncoderHelper::JpegEncoderHelper() {} + +JpegEncoderHelper::~JpegEncoderHelper() {} + +bool JpegEncoderHelper::compressImage(const uint8_t* yBuffer, const uint8_t* uvBuffer, int width, + int height, int lumaStride, int chromaStride, int quality, + const void* iccBuffer, unsigned int iccSize) { + mResultBuffer.clear(); + if (!encode(yBuffer, uvBuffer, width, height, lumaStride, chromaStride, quality, iccBuffer, + iccSize)) { + return false; + } + ALOGV("Compressed JPEG: %d[%dx%d] -> %zu bytes", (width * height * 12) / 8, width, height, + mResultBuffer.size()); + return true; +} + +void* JpegEncoderHelper::getCompressedImagePtr() { return mResultBuffer.data(); } + +size_t JpegEncoderHelper::getCompressedImageSize() { return mResultBuffer.size(); } + +void JpegEncoderHelper::initDestination(j_compress_ptr cinfo) { + destination_mgr* dest = reinterpret_cast<destination_mgr*>(cinfo->dest); + std::vector<JOCTET>& buffer = dest->encoder->mResultBuffer; + buffer.resize(kBlockSize); + dest->mgr.next_output_byte = &buffer[0]; + dest->mgr.free_in_buffer = buffer.size(); +} + +boolean JpegEncoderHelper::emptyOutputBuffer(j_compress_ptr cinfo) { + destination_mgr* dest = reinterpret_cast<destination_mgr*>(cinfo->dest); + std::vector<JOCTET>& buffer = dest->encoder->mResultBuffer; + size_t oldsize = buffer.size(); + buffer.resize(oldsize + kBlockSize); + dest->mgr.next_output_byte = &buffer[oldsize]; + dest->mgr.free_in_buffer = kBlockSize; + return true; +} + +void JpegEncoderHelper::terminateDestination(j_compress_ptr cinfo) { + destination_mgr* dest = reinterpret_cast<destination_mgr*>(cinfo->dest); + std::vector<JOCTET>& buffer = dest->encoder->mResultBuffer; + buffer.resize(buffer.size() - dest->mgr.free_in_buffer); +} + +void JpegEncoderHelper::outputErrorMessage(j_common_ptr cinfo) { + char buffer[JMSG_LENGTH_MAX]; + + /* Create the message */ + (*cinfo->err->format_message)(cinfo, buffer); + ALOGE("%s\n", buffer); +} + +bool JpegEncoderHelper::encode(const uint8_t* yBuffer, const uint8_t* uvBuffer, int width, + int height, int lumaStride, int chromaStride, int quality, + const void* iccBuffer, unsigned int iccSize) { + jpeg_compress_struct cinfo; + jpeg_error_mgr jerr; + + cinfo.err = jpeg_std_error(&jerr); + cinfo.err->output_message = &outputErrorMessage; + jpeg_create_compress(&cinfo); + setJpegDestination(&cinfo); + setJpegCompressStruct(width, height, quality, &cinfo, uvBuffer == nullptr); + jpeg_start_compress(&cinfo, TRUE); + if (iccBuffer != nullptr && iccSize > 0) { + jpeg_write_marker(&cinfo, JPEG_APP0 + 2, static_cast<const JOCTET*>(iccBuffer), iccSize); + } + bool status = cinfo.num_components == 1 + ? compressY(&cinfo, yBuffer, lumaStride) + : compressYuv(&cinfo, yBuffer, uvBuffer, lumaStride, chromaStride); + jpeg_finish_compress(&cinfo); + jpeg_destroy_compress(&cinfo); + + return status; +} + +void JpegEncoderHelper::setJpegDestination(jpeg_compress_struct* cinfo) { + destination_mgr* dest = static_cast<struct destination_mgr*>( + (*cinfo->mem->alloc_small)((j_common_ptr)cinfo, JPOOL_PERMANENT, sizeof(destination_mgr))); + dest->encoder = this; + dest->mgr.init_destination = &initDestination; + dest->mgr.empty_output_buffer = &emptyOutputBuffer; + dest->mgr.term_destination = &terminateDestination; + cinfo->dest = reinterpret_cast<struct jpeg_destination_mgr*>(dest); +} + +void JpegEncoderHelper::setJpegCompressStruct(int width, int height, int quality, + jpeg_compress_struct* cinfo, bool isSingleChannel) { + cinfo->image_width = width; + cinfo->image_height = height; + cinfo->input_components = isSingleChannel ? 1 : 3; + cinfo->in_color_space = isSingleChannel ? JCS_GRAYSCALE : JCS_YCbCr; + jpeg_set_defaults(cinfo); + jpeg_set_quality(cinfo, quality, TRUE); + cinfo->raw_data_in = TRUE; + cinfo->dct_method = JDCT_ISLOW; + cinfo->comp_info[0].h_samp_factor = cinfo->in_color_space == JCS_GRAYSCALE ? 1 : 2; + cinfo->comp_info[0].v_samp_factor = cinfo->in_color_space == JCS_GRAYSCALE ? 1 : 2; + for (int i = 1; i < cinfo->num_components; i++) { + cinfo->comp_info[i].h_samp_factor = 1; + cinfo->comp_info[i].v_samp_factor = 1; + } +} + +bool JpegEncoderHelper::compressYuv(jpeg_compress_struct* cinfo, const uint8_t* yBuffer, + const uint8_t* uvBuffer, int lumaStride, int chromaStride) { + size_t chroma_plane_size = chromaStride * cinfo->image_height / 2; + uint8_t* y_plane = const_cast<uint8_t*>(yBuffer); + uint8_t* u_plane = const_cast<uint8_t*>(uvBuffer); + uint8_t* v_plane = const_cast<uint8_t*>(u_plane + chroma_plane_size); + + const int aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize); + const bool need_luma_padding = (lumaStride < aligned_width); + const int aligned_chroma_width = ALIGNM(cinfo->image_width / 2, kCompressBatchSize / 2); + const bool need_chroma_padding = (chromaStride < aligned_chroma_width); + + std::unique_ptr<uint8_t[]> empty = nullptr; + std::unique_ptr<uint8_t[]> y_mcu_row = nullptr; + std::unique_ptr<uint8_t[]> cb_mcu_row = nullptr; + std::unique_ptr<uint8_t[]> cr_mcu_row = nullptr; + uint8_t* y_mcu_row_ptr = nullptr; + uint8_t* cb_mcu_row_ptr = nullptr; + uint8_t* cr_mcu_row_ptr = nullptr; + + JSAMPROW y[kCompressBatchSize]; + JSAMPROW cb[kCompressBatchSize / 2]; + JSAMPROW cr[kCompressBatchSize / 2]; + JSAMPARRAY planes[3]{y, cb, cr}; + + if (cinfo->image_height % kCompressBatchSize != 0) { + empty = std::make_unique<uint8_t[]>(aligned_width); + memset(empty.get(), 0, aligned_width); + } + + if (need_luma_padding) { + size_t mcu_row_size = aligned_width * kCompressBatchSize; + y_mcu_row = std::make_unique<uint8_t[]>(mcu_row_size); + y_mcu_row_ptr = y_mcu_row.get(); + uint8_t* tmp = y_mcu_row_ptr; + for (int i = 0; i < kCompressBatchSize; ++i, tmp += aligned_width) { + memset(tmp + cinfo->image_width, 0, aligned_width - cinfo->image_width); + } + } + + if (need_chroma_padding) { + size_t mcu_row_size = aligned_chroma_width * kCompressBatchSize / 2; + cb_mcu_row = std::make_unique<uint8_t[]>(mcu_row_size); + cb_mcu_row_ptr = cb_mcu_row.get(); + cr_mcu_row = std::make_unique<uint8_t[]>(mcu_row_size); + cr_mcu_row_ptr = cr_mcu_row.get(); + uint8_t* tmp1 = cb_mcu_row_ptr; + uint8_t* tmp2 = cr_mcu_row_ptr; + for (int i = 0; i < kCompressBatchSize / 2; + ++i, tmp1 += aligned_chroma_width, tmp2 += aligned_chroma_width) { + memset(tmp1 + cinfo->image_width / 2, 0, aligned_chroma_width - (cinfo->image_width / 2)); + memset(tmp2 + cinfo->image_width / 2, 0, aligned_chroma_width - (cinfo->image_width / 2)); + } + } + + while (cinfo->next_scanline < cinfo->image_height) { + for (int i = 0; i < kCompressBatchSize; ++i) { + size_t scanline = cinfo->next_scanline + i; + if (scanline < cinfo->image_height) { + y[i] = y_plane + scanline * lumaStride; + if (need_luma_padding) { + uint8_t* tmp = y_mcu_row_ptr + i * aligned_width; + memcpy(tmp, y[i], cinfo->image_width); + y[i] = tmp; + } + } else { + y[i] = empty.get(); + } + } + // cb, cr only have half scanlines + for (int i = 0; i < kCompressBatchSize / 2; ++i) { + size_t scanline = cinfo->next_scanline / 2 + i; + if (scanline < cinfo->image_height / 2) { + int offset = scanline * chromaStride; + cb[i] = u_plane + offset; + cr[i] = v_plane + offset; + if (need_chroma_padding) { + uint8_t* tmp = cb_mcu_row_ptr + i * aligned_chroma_width; + memcpy(tmp, cb[i], cinfo->image_width / 2); + cb[i] = tmp; + tmp = cr_mcu_row_ptr + i * aligned_chroma_width; + memcpy(tmp, cr[i], cinfo->image_width / 2); + cr[i] = tmp; + } + } else { + cb[i] = cr[i] = empty.get(); + } + } + int processed = jpeg_write_raw_data(cinfo, planes, kCompressBatchSize); + if (processed != kCompressBatchSize) { + ALOGE("Number of processed lines does not equal input lines."); + return false; + } + } + return true; +} + +bool JpegEncoderHelper::compressY(jpeg_compress_struct* cinfo, const uint8_t* yBuffer, + int lumaStride) { + uint8_t* y_plane = const_cast<uint8_t*>(yBuffer); + + const int aligned_luma_width = ALIGNM(cinfo->image_width, kCompressBatchSize); + const bool need_luma_padding = (lumaStride < aligned_luma_width); + + std::unique_ptr<uint8_t[]> empty = nullptr; + std::unique_ptr<uint8_t[]> y_mcu_row = nullptr; + uint8_t* y_mcu_row_ptr = nullptr; + + JSAMPROW y[kCompressBatchSize]; + JSAMPARRAY planes[1]{y}; + + if (cinfo->image_height % kCompressBatchSize != 0) { + empty = std::make_unique<uint8_t[]>(aligned_luma_width); + memset(empty.get(), 0, aligned_luma_width); + } + + if (need_luma_padding) { + size_t mcu_row_size = aligned_luma_width * kCompressBatchSize; + y_mcu_row = std::make_unique<uint8_t[]>(mcu_row_size); + y_mcu_row_ptr = y_mcu_row.get(); + uint8_t* tmp = y_mcu_row_ptr; + for (int i = 0; i < kCompressBatchSize; ++i, tmp += aligned_luma_width) { + memset(tmp + cinfo->image_width, 0, aligned_luma_width - cinfo->image_width); + } + } + + while (cinfo->next_scanline < cinfo->image_height) { + for (int i = 0; i < kCompressBatchSize; ++i) { + size_t scanline = cinfo->next_scanline + i; + if (scanline < cinfo->image_height) { + y[i] = y_plane + scanline * lumaStride; + if (need_luma_padding) { + uint8_t* tmp = y_mcu_row_ptr + i * aligned_luma_width; + memcpy(tmp, y[i], cinfo->image_width); + y[i] = tmp; + } + } else { + y[i] = empty.get(); + } + } + int processed = jpeg_write_raw_data(cinfo, planes, kCompressBatchSize); + if (processed != kCompressBatchSize / 2) { + ALOGE("Number of processed lines does not equal input lines."); + return false; + } + } + return true; +} + +} // namespace ultrahdr diff --git a/lib/jpegencoderhelper.h b/lib/jpegencoderhelper.h new file mode 100644 index 0000000..e988578 --- /dev/null +++ b/lib/jpegencoderhelper.h @@ -0,0 +1,106 @@ +/* + * Copyright 2022 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_JPEGENCODERHELPER_H +#define ULTRAHDR_JPEGENCODERHELPER_H + +#include <stdio.h> // For jpeglib.h. + +// C++ build requires extern C for jpeg internals. +#ifdef __cplusplus +extern "C" { +#endif + +#include <jerror.h> +#include <jpeglib.h> + +#ifdef __cplusplus +} // extern "C" +#endif + +#include <cstdint> +#include <vector> + +namespace ultrahdr { + +/* + * Encapsulates a converter from raw image (YUV420planer or grey-scale) to JPEG format. + * This class is not thread-safe. + */ +class JpegEncoderHelper { + public: + JpegEncoderHelper(); + ~JpegEncoderHelper(); + + /* + * Compresses YUV420Planer image to JPEG format. After calling this method, call + * getCompressedImage() to get the image. |quality| is the jpeg image quality parameter to use. + * It ranges from 1 (poorest quality) to 100 (highest quality). |iccBuffer| is the buffer of + * ICC segment which will be added to the compressed image. + * Returns false if errors occur during compression. + */ + bool compressImage(const uint8_t* yBuffer, const uint8_t* uvBuffer, int width, int height, + int lumaStride, int chromaStride, int quality, const void* iccBuffer, + unsigned int iccSize); + + /* + * Returns the compressed JPEG buffer pointer. This method must be called only after calling + * compressImage(). + */ + void* getCompressedImagePtr(); + + /* + * Returns the compressed JPEG buffer size. This method must be called only after calling + * compressImage(). + */ + size_t getCompressedImageSize(); + + /* + * Process 16 lines of Y and 16 lines of U/V each time. + * We must pass at least 16 scanlines according to libjpeg documentation. + */ + static const int kCompressBatchSize = 16; + + private: + // initDestination(), emptyOutputBuffer() and emptyOutputBuffer() are callback functions to be + // passed into jpeg library. + static void initDestination(j_compress_ptr cinfo); + static boolean emptyOutputBuffer(j_compress_ptr cinfo); + static void terminateDestination(j_compress_ptr cinfo); + static void outputErrorMessage(j_common_ptr cinfo); + + // Returns false if errors occur. + bool encode(const uint8_t* yBuffer, const uint8_t* uvBuffer, int width, int height, + int lumaStride, int chromaStride, int quality, const void* iccBuffer, + unsigned int iccSize); + void setJpegDestination(jpeg_compress_struct* cinfo); + void setJpegCompressStruct(int width, int height, int quality, jpeg_compress_struct* cinfo, + bool isSingleChannel); + // Returns false if errors occur. + bool compressYuv(jpeg_compress_struct* cinfo, const uint8_t* yBuffer, const uint8_t* uvBuffer, + int lumaStride, int chromaStride); + bool compressY(jpeg_compress_struct* cinfo, const uint8_t* yBuffer, int lumaStride); + + // The block size for encoded jpeg image buffer. + static const int kBlockSize = 16384; + + // The buffer that holds the compressed result. + std::vector<JOCTET> mResultBuffer; +}; + +} /* namespace ultrahdr */ + +#endif // ULTRAHDR_JPEGENCODERHELPER_H diff --git a/jpegr.cpp b/lib/jpegr.cpp index 3a88e38..015ffce 100644 --- a/jpegr.cpp +++ b/lib/jpegr.cpp @@ -14,7 +14,12 @@ * limitations under the License. */ +#ifdef _WIN32 +#include <Windows.h> +#include <sysinfoapi.h> +#else #include <unistd.h> +#endif #include <condition_variable> #include <deque> @@ -22,10 +27,10 @@ #include <mutex> #include <thread> -#include "ultrahdr/ultrahdrcommon.h" -#include "ultrahdr/jpegr.h" -#include "ultrahdr/icc.h" -#include "ultrahdr/multipictureformat.h" +#include "ultrahdrcommon.h" +#include "jpegr.h" +#include "icc.h" +#include "multipictureformat.h" #include "image_io/base/data_segment_data_source.h" #include "image_io/jpeg/jpeg_info.h" @@ -45,28 +50,33 @@ namespace ultrahdr { #define USE_PQ_INVOETF_LUT 1 #define USE_APPLY_GAIN_LUT 1 -#define JPEGR_CHECK(x) \ - { \ - status_t status = (x); \ - if ((status) != NO_ERROR) { \ - return status; \ - } \ +#define JPEGR_CHECK(x) \ + { \ + status_t status = (x); \ + if ((status) != JPEGR_NO_ERROR) { \ + return status; \ + } \ } // JPEG compress quality (0 ~ 100) for gain map static const int kMapCompressQuality = 85; -#define CONFIG_MULTITHREAD 1 int GetCPUCoreCount() { int cpuCoreCount = 1; -#if CONFIG_MULTITHREAD -#if defined(_SC_NPROCESSORS_ONLN) + +#if defined(_WIN32) + SYSTEM_INFO system_info; + ZeroMemory(&system_info, sizeof(system_info)); + GetSystemInfo(&system_info); + cpuCoreCount = (size_t)system_info.dwNumberOfProcessors; +#elif defined(_SC_NPROCESSORS_ONLN) cpuCoreCount = sysconf(_SC_NPROCESSORS_ONLN); +#elif defined(_SC_NPROCESSORS_CONF) + cpuCoreCount = sysconf(_SC_NPROCESSORS_CONF); #else - // _SC_NPROC_ONLN must be defined... - cpuCoreCount = sysconf(_SC_NPROC_ONLN); -#endif +#error platform-specific implementation for GetCPUCoreCount() missing. #endif + if (cpuCoreCount <= 0) cpuCoreCount = 1; return cpuCoreCount; } @@ -92,7 +102,7 @@ class AlogMessageWriter : public MessageWriter { */ static void copyJpegWithoutExif(jr_compressed_ptr pDest, jr_compressed_ptr pSource, size_t exif_pos, size_t exif_size) { - const size_t exif_offset = 4; // exif_pos has 4 bytes offset to the FF sign + const size_t exif_offset = 4; // exif_pos has 4 bytes offset to the FF sign pDest->length = pSource->length - exif_size - exif_offset; pDest->data = new uint8_t[pDest->length]; pDest->maxLength = pDest->length; @@ -108,65 +118,65 @@ status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr, jr_compressed_ptr dest_ptr) { if (p010_image_ptr == nullptr || p010_image_ptr->data == nullptr) { ALOGE("Received nullptr for input p010 image"); - return ERROR_JPEGR_INVALID_NULL_PTR; + return ERROR_JPEGR_BAD_PTR; } if (p010_image_ptr->width % 2 != 0 || p010_image_ptr->height % 2 != 0) { ALOGE("Image dimensions cannot be odd, image dimensions %zux%zu", p010_image_ptr->width, p010_image_ptr->height); - return ERROR_JPEGR_INVALID_INPUT_TYPE; + return ERROR_JPEGR_UNSUPPORTED_WIDTH_HEIGHT; } if (p010_image_ptr->width < kMinWidth || p010_image_ptr->height < kMinHeight) { ALOGE("Image dimensions cannot be less than %dx%d, image dimensions %zux%zu", kMinWidth, kMinHeight, p010_image_ptr->width, p010_image_ptr->height); - return ERROR_JPEGR_INVALID_INPUT_TYPE; + return ERROR_JPEGR_UNSUPPORTED_WIDTH_HEIGHT; } if (p010_image_ptr->width > kMaxWidth || p010_image_ptr->height > kMaxHeight) { ALOGE("Image dimensions cannot be larger than %dx%d, image dimensions %zux%zu", kMaxWidth, kMaxHeight, p010_image_ptr->width, p010_image_ptr->height); - return ERROR_JPEGR_INVALID_INPUT_TYPE; + return ERROR_JPEGR_UNSUPPORTED_WIDTH_HEIGHT; } if (p010_image_ptr->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED || p010_image_ptr->colorGamut > ULTRAHDR_COLORGAMUT_MAX) { ALOGE("Unrecognized p010 color gamut %d", p010_image_ptr->colorGamut); - return ERROR_JPEGR_INVALID_INPUT_TYPE; + return ERROR_JPEGR_INVALID_COLORGAMUT; } if (p010_image_ptr->luma_stride != 0 && p010_image_ptr->luma_stride < p010_image_ptr->width) { ALOGE("Luma stride must not be smaller than width, stride=%zu, width=%zu", p010_image_ptr->luma_stride, p010_image_ptr->width); - return ERROR_JPEGR_INVALID_INPUT_TYPE; + return ERROR_JPEGR_INVALID_STRIDE; } if (p010_image_ptr->chroma_data != nullptr && p010_image_ptr->chroma_stride < p010_image_ptr->width) { ALOGE("Chroma stride must not be smaller than width, stride=%zu, width=%zu", p010_image_ptr->chroma_stride, p010_image_ptr->width); - return ERROR_JPEGR_INVALID_INPUT_TYPE; + return ERROR_JPEGR_INVALID_STRIDE; } if (dest_ptr == nullptr || dest_ptr->data == nullptr) { ALOGE("Received nullptr for destination"); - return ERROR_JPEGR_INVALID_NULL_PTR; + return ERROR_JPEGR_BAD_PTR; } if (hdr_tf <= ULTRAHDR_TF_UNSPECIFIED || hdr_tf > ULTRAHDR_TF_MAX || hdr_tf == ULTRAHDR_TF_SRGB) { ALOGE("Invalid hdr transfer function %d", hdr_tf); - return ERROR_JPEGR_INVALID_INPUT_TYPE; + return ERROR_JPEGR_INVALID_TRANS_FUNC; } if (yuv420_image_ptr == nullptr) { - return NO_ERROR; + return JPEGR_NO_ERROR; } if (yuv420_image_ptr->data == nullptr) { ALOGE("Received nullptr for uncompressed 420 image"); - return ERROR_JPEGR_INVALID_NULL_PTR; + return ERROR_JPEGR_BAD_PTR; } if (yuv420_image_ptr->luma_stride != 0 && yuv420_image_ptr->luma_stride < yuv420_image_ptr->width) { ALOGE("Luma stride must not be smaller than width, stride=%zu, width=%zu", yuv420_image_ptr->luma_stride, yuv420_image_ptr->width); - return ERROR_JPEGR_INVALID_INPUT_TYPE; + return ERROR_JPEGR_INVALID_STRIDE; } if (yuv420_image_ptr->chroma_data != nullptr && yuv420_image_ptr->chroma_stride < yuv420_image_ptr->width / 2) { ALOGE("Chroma stride must not be smaller than (width / 2), stride=%zu, width=%zu", yuv420_image_ptr->chroma_stride, yuv420_image_ptr->width); - return ERROR_JPEGR_INVALID_INPUT_TYPE; + return ERROR_JPEGR_INVALID_STRIDE; } if (p010_image_ptr->width != yuv420_image_ptr->width || p010_image_ptr->height != yuv420_image_ptr->height) { @@ -177,9 +187,9 @@ status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr, if (yuv420_image_ptr->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED || yuv420_image_ptr->colorGamut > ULTRAHDR_COLORGAMUT_MAX) { ALOGE("Unrecognized 420 color gamut %d", yuv420_image_ptr->colorGamut); - return ERROR_JPEGR_INVALID_INPUT_TYPE; + return ERROR_JPEGR_INVALID_COLORGAMUT; } - return NO_ERROR; + return JPEGR_NO_ERROR; } status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr, @@ -188,7 +198,7 @@ status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr, jr_compressed_ptr dest_ptr, int quality) { if (quality < 0 || quality > 100) { ALOGE("quality factor is out side range [0-100], quality factor : %d", quality); - return ERROR_JPEGR_INVALID_INPUT_TYPE; + return ERROR_JPEGR_INVALID_QUALITY_FACTOR; } return areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest_ptr); } @@ -197,13 +207,10 @@ status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr, status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest, int quality, jr_exif_ptr exif) { // validate input arguments - if (auto ret = areInputArgumentsValid(p010_image_ptr, nullptr, hdr_tf, dest, quality); - ret != NO_ERROR) { - return ret; - } + JPEGR_CHECK(areInputArgumentsValid(p010_image_ptr, nullptr, hdr_tf, dest, quality)); if (exif != nullptr && exif->data == nullptr) { ALOGE("received nullptr for exif metadata"); - return ERROR_JPEGR_INVALID_NULL_PTR; + return ERROR_JPEGR_BAD_PTR; } // clean up input structure for later usage @@ -217,14 +224,15 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, ultrahdr_transfe const size_t yu420_luma_stride = ALIGNM(p010_image.width, JpegEncoderHelper::kCompressBatchSize); unique_ptr<uint8_t[]> yuv420_image_data = - make_unique<uint8_t[]>(yu420_luma_stride * p010_image.height * 3 / 2); - jpegr_uncompressed_struct yuv420_image = {.data = yuv420_image_data.get(), - .width = p010_image.width, - .height = p010_image.height, - .colorGamut = p010_image.colorGamut, - .chroma_data = nullptr, - .luma_stride = yu420_luma_stride, - .chroma_stride = yu420_luma_stride >> 1}; + make_unique<uint8_t[]>(yu420_luma_stride * p010_image.height * 3 / 2); + jpegr_uncompressed_struct yuv420_image; + yuv420_image.data = yuv420_image_data.get(); + yuv420_image.width = p010_image.width; + yuv420_image.height = p010_image.height; + yuv420_image.colorGamut = p010_image.colorGamut; + yuv420_image.chroma_data = nullptr; + yuv420_image.luma_stride = yu420_luma_stride; + yuv420_image.chroma_stride = yu420_luma_stride >> 1; uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data); yuv420_image.chroma_data = data + yuv420_image.luma_stride * yuv420_image.height; @@ -232,7 +240,8 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, ultrahdr_transfe JPEGR_CHECK(toneMap(&p010_image, &yuv420_image)); // gain map - ultrahdr_metadata_struct metadata = {.version = kJpegrVersion}; + ultrahdr_metadata_struct metadata; + metadata.version = kJpegrVersion; jpegr_uncompressed_struct gainmap_image; JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image)); std::unique_ptr<uint8_t[]> map_data; @@ -241,15 +250,14 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, ultrahdr_transfe // compress gain map JpegEncoderHelper jpeg_enc_obj_gm; JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm)); - jpegr_compressed_struct compressed_map = {.data = jpeg_enc_obj_gm.getCompressedImagePtr(), - .length = static_cast<int>( - jpeg_enc_obj_gm.getCompressedImageSize()), - .maxLength = static_cast<int>( - jpeg_enc_obj_gm.getCompressedImageSize()), - .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED}; + jpegr_compressed_struct compressed_map; + compressed_map.data = jpeg_enc_obj_gm.getCompressedImagePtr(); + compressed_map.length = static_cast<int>(jpeg_enc_obj_gm.getCompressedImageSize()); + compressed_map.maxLength = static_cast<int>(jpeg_enc_obj_gm.getCompressedImageSize()); + compressed_map.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED; std::shared_ptr<DataStruct> icc = - IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, yuv420_image.colorGamut); + IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, yuv420_image.colorGamut); // convert to Bt601 YUV encoding for JPEG encode if (yuv420_image.colorGamut != ULTRAHDR_COLORGAMUT_P3) { @@ -265,18 +273,17 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, ultrahdr_transfe quality, icc->getData(), icc->getLength())) { return ERROR_JPEGR_ENCODE_ERROR; } - jpegr_compressed_struct jpeg = {.data = jpeg_enc_obj_yuv420.getCompressedImagePtr(), - .length = static_cast<int>( - jpeg_enc_obj_yuv420.getCompressedImageSize()), - .maxLength = static_cast<int>( - jpeg_enc_obj_yuv420.getCompressedImageSize()), - .colorGamut = yuv420_image.colorGamut}; + jpegr_compressed_struct jpeg; + jpeg.data = jpeg_enc_obj_yuv420.getCompressedImagePtr(); + jpeg.length = static_cast<int>(jpeg_enc_obj_yuv420.getCompressedImageSize()); + jpeg.maxLength = static_cast<int>(jpeg_enc_obj_yuv420.getCompressedImageSize()); + jpeg.colorGamut = yuv420_image.colorGamut; // append gain map, no ICC since JPEG encode already did it JPEGR_CHECK(appendGainMap(&jpeg, &compressed_map, exif, /* icc */ nullptr, /* icc size */ 0, &metadata, dest)); - return NO_ERROR; + return JPEGR_NO_ERROR; } /* Encode API-1 */ @@ -286,16 +293,13 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, // validate input arguments if (yuv420_image_ptr == nullptr) { ALOGE("received nullptr for uncompressed 420 image"); - return ERROR_JPEGR_INVALID_NULL_PTR; + return ERROR_JPEGR_BAD_PTR; } if (exif != nullptr && exif->data == nullptr) { ALOGE("received nullptr for exif metadata"); - return ERROR_JPEGR_INVALID_NULL_PTR; - } - if (auto ret = areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest, quality); - ret != NO_ERROR) { - return ret; + return ERROR_JPEGR_BAD_PTR; } + JPEGR_CHECK(areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest, quality)) // clean up input structure for later usage jpegr_uncompressed_struct p010_image = *p010_image_ptr; @@ -314,7 +318,8 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, } // gain map - ultrahdr_metadata_struct metadata = {.version = kJpegrVersion}; + ultrahdr_metadata_struct metadata; + metadata.version = kJpegrVersion; jpegr_uncompressed_struct gainmap_image; JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image)); std::unique_ptr<uint8_t[]> map_data; @@ -323,24 +328,23 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, // compress gain map JpegEncoderHelper jpeg_enc_obj_gm; JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm)); - jpegr_compressed_struct compressed_map = {.data = jpeg_enc_obj_gm.getCompressedImagePtr(), - .length = static_cast<int>( - jpeg_enc_obj_gm.getCompressedImageSize()), - .maxLength = static_cast<int>( - jpeg_enc_obj_gm.getCompressedImageSize()), - .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED}; + jpegr_compressed_struct compressed_map; + compressed_map.data = jpeg_enc_obj_gm.getCompressedImagePtr(); + compressed_map.length = static_cast<int>(jpeg_enc_obj_gm.getCompressedImageSize()); + compressed_map.maxLength = static_cast<int>(jpeg_enc_obj_gm.getCompressedImageSize()); + compressed_map.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED; std::shared_ptr<DataStruct> icc = - IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, yuv420_image.colorGamut); + IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, yuv420_image.colorGamut); jpegr_uncompressed_struct yuv420_bt601_image = yuv420_image; unique_ptr<uint8_t[]> yuv_420_bt601_data; // Convert to bt601 YUV encoding for JPEG encode if (yuv420_image.colorGamut != ULTRAHDR_COLORGAMUT_P3) { const size_t yuv_420_bt601_luma_stride = - ALIGNM(yuv420_image.width, JpegEncoderHelper::kCompressBatchSize); + ALIGNM(yuv420_image.width, JpegEncoderHelper::kCompressBatchSize); yuv_420_bt601_data = - make_unique<uint8_t[]>(yuv_420_bt601_luma_stride * yuv420_image.height * 3 / 2); + make_unique<uint8_t[]>(yuv_420_bt601_luma_stride * yuv420_image.height * 3 / 2); yuv420_bt601_image.data = yuv_420_bt601_data.get(); yuv420_bt601_image.colorGamut = yuv420_image.colorGamut; yuv420_bt601_image.luma_stride = yuv_420_bt601_luma_stride; @@ -398,26 +402,24 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, // compress 420 image JpegEncoderHelper jpeg_enc_obj_yuv420; - if (!jpeg_enc_obj_yuv420.compressImage(reinterpret_cast<uint8_t*>(yuv420_bt601_image.data), - reinterpret_cast<uint8_t*>(yuv420_bt601_image.chroma_data), - yuv420_bt601_image.width, yuv420_bt601_image.height, - yuv420_bt601_image.luma_stride, - yuv420_bt601_image.chroma_stride, quality, icc->getData(), - icc->getLength())) { + if (!jpeg_enc_obj_yuv420.compressImage( + reinterpret_cast<uint8_t*>(yuv420_bt601_image.data), + reinterpret_cast<uint8_t*>(yuv420_bt601_image.chroma_data), yuv420_bt601_image.width, + yuv420_bt601_image.height, yuv420_bt601_image.luma_stride, + yuv420_bt601_image.chroma_stride, quality, icc->getData(), icc->getLength())) { return ERROR_JPEGR_ENCODE_ERROR; } - jpegr_compressed_struct jpeg = {.data = jpeg_enc_obj_yuv420.getCompressedImagePtr(), - .length = static_cast<int>( - jpeg_enc_obj_yuv420.getCompressedImageSize()), - .maxLength = static_cast<int>( - jpeg_enc_obj_yuv420.getCompressedImageSize()), - .colorGamut = yuv420_image.colorGamut}; + jpegr_compressed_struct jpeg; + jpeg.data = jpeg_enc_obj_yuv420.getCompressedImagePtr(); + jpeg.length = static_cast<int>(jpeg_enc_obj_yuv420.getCompressedImageSize()); + jpeg.maxLength = static_cast<int>(jpeg_enc_obj_yuv420.getCompressedImageSize()); + jpeg.colorGamut = yuv420_image.colorGamut; // append gain map, no ICC since JPEG encode already did it JPEGR_CHECK(appendGainMap(&jpeg, &compressed_map, exif, /* icc */ nullptr, /* icc size */ 0, &metadata, dest)); - return NO_ERROR; + return JPEGR_NO_ERROR; } /* Encode API-2 */ @@ -428,16 +430,13 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, // validate input arguments if (yuv420_image_ptr == nullptr) { ALOGE("received nullptr for uncompressed 420 image"); - return ERROR_JPEGR_INVALID_NULL_PTR; + return ERROR_JPEGR_BAD_PTR; } if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) { ALOGE("received nullptr for compressed jpeg image"); - return ERROR_JPEGR_INVALID_NULL_PTR; - } - if (auto ret = areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest); - ret != NO_ERROR) { - return ret; + return ERROR_JPEGR_BAD_PTR; } + JPEGR_CHECK(areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest)) // clean up input structure for later usage jpegr_uncompressed_struct p010_image = *p010_image_ptr; @@ -456,7 +455,8 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, } // gain map - ultrahdr_metadata_struct metadata = {.version = kJpegrVersion}; + ultrahdr_metadata_struct metadata; + metadata.version = kJpegrVersion; jpegr_uncompressed_struct gainmap_image; JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image)); std::unique_ptr<uint8_t[]> map_data; @@ -465,12 +465,11 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, // compress gain map JpegEncoderHelper jpeg_enc_obj_gm; JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm)); - jpegr_compressed_struct gainmapjpg_image = {.data = jpeg_enc_obj_gm.getCompressedImagePtr(), - .length = static_cast<int>( - jpeg_enc_obj_gm.getCompressedImageSize()), - .maxLength = static_cast<int>( - jpeg_enc_obj_gm.getCompressedImageSize()), - .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED}; + jpegr_compressed_struct gainmapjpg_image; + gainmapjpg_image.data = jpeg_enc_obj_gm.getCompressedImagePtr(); + gainmapjpg_image.length = static_cast<int>(jpeg_enc_obj_gm.getCompressedImageSize()); + gainmapjpg_image.maxLength = static_cast<int>(jpeg_enc_obj_gm.getCompressedImageSize()); + gainmapjpg_image.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED; return encodeJPEGR(yuv420jpg_image_ptr, &gainmapjpg_image, &metadata, dest); } @@ -482,11 +481,9 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, // validate input arguments if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) { ALOGE("received nullptr for compressed jpeg image"); - return ERROR_JPEGR_INVALID_NULL_PTR; - } - if (auto ret = areInputArgumentsValid(p010_image_ptr, nullptr, hdr_tf, dest); ret != NO_ERROR) { - return ret; + return ERROR_JPEGR_BAD_PTR; } + JPEGR_CHECK(areInputArgumentsValid(p010_image_ptr, nullptr, hdr_tf, dest)) // clean up input structure for later usage jpegr_uncompressed_struct p010_image = *p010_image_ptr; @@ -521,7 +518,8 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, } // gain map - ultrahdr_metadata_struct metadata = {.version = kJpegrVersion}; + ultrahdr_metadata_struct metadata; + metadata.version = kJpegrVersion; jpegr_uncompressed_struct gainmap_image; JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image, true /* sdr_is_601 */)); @@ -531,12 +529,11 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, // compress gain map JpegEncoderHelper jpeg_enc_obj_gm; JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm)); - jpegr_compressed_struct gainmapjpg_image = {.data = jpeg_enc_obj_gm.getCompressedImagePtr(), - .length = static_cast<int>( - jpeg_enc_obj_gm.getCompressedImageSize()), - .maxLength = static_cast<int>( - jpeg_enc_obj_gm.getCompressedImageSize()), - .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED}; + jpegr_compressed_struct gainmapjpg_image; + gainmapjpg_image.data = jpeg_enc_obj_gm.getCompressedImagePtr(); + gainmapjpg_image.length = static_cast<int>(jpeg_enc_obj_gm.getCompressedImageSize()); + gainmapjpg_image.maxLength = static_cast<int>(jpeg_enc_obj_gm.getCompressedImageSize()); + gainmapjpg_image.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED; return encodeJPEGR(yuv420jpg_image_ptr, &gainmapjpg_image, &metadata, dest); } @@ -547,15 +544,15 @@ status_t JpegR::encodeJPEGR(jr_compressed_ptr yuv420jpg_image_ptr, jr_compressed_ptr dest) { if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) { ALOGE("received nullptr for compressed jpeg image"); - return ERROR_JPEGR_INVALID_NULL_PTR; + return ERROR_JPEGR_BAD_PTR; } if (gainmapjpg_image_ptr == nullptr || gainmapjpg_image_ptr->data == nullptr) { ALOGE("received nullptr for compressed gain map"); - return ERROR_JPEGR_INVALID_NULL_PTR; + return ERROR_JPEGR_BAD_PTR; } if (dest == nullptr || dest->data == nullptr) { ALOGE("received nullptr for destination"); - return ERROR_JPEGR_INVALID_NULL_PTR; + return ERROR_JPEGR_BAD_PTR; } // We just want to check if ICC is present, so don't do a full decode. Note, @@ -572,36 +569,35 @@ status_t JpegR::encodeJPEGR(jr_compressed_ptr yuv420jpg_image_ptr, /* icc */ nullptr, /* icc size */ 0, metadata, dest)); } else { std::shared_ptr<DataStruct> newIcc = - IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, yuv420jpg_image_ptr->colorGamut); + IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, yuv420jpg_image_ptr->colorGamut); JPEGR_CHECK(appendGainMap(yuv420jpg_image_ptr, gainmapjpg_image_ptr, /* exif */ nullptr, newIcc->getData(), newIcc->getLength(), metadata, dest)); } - return NO_ERROR; + return JPEGR_NO_ERROR; } status_t JpegR::getJPEGRInfo(jr_compressed_ptr jpegr_image_ptr, jr_info_ptr jpeg_image_info_ptr) { if (jpegr_image_ptr == nullptr || jpegr_image_ptr->data == nullptr) { ALOGE("received nullptr for compressed jpegr image"); - return ERROR_JPEGR_INVALID_NULL_PTR; + return ERROR_JPEGR_BAD_PTR; } if (jpeg_image_info_ptr == nullptr) { ALOGE("received nullptr for compressed jpegr info struct"); - return ERROR_JPEGR_INVALID_NULL_PTR; + return ERROR_JPEGR_BAD_PTR; } jpegr_compressed_struct primary_image, gainmap_image; status_t status = extractPrimaryImageAndGainMap(jpegr_image_ptr, &primary_image, &gainmap_image); - if (status != NO_ERROR && status != ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND) { + if (status != JPEGR_NO_ERROR && status != ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND) { return status; } JpegDecoderHelper jpeg_dec_obj_hdr; - if (!jpeg_dec_obj_hdr.getCompressedImageParameters(primary_image.data, primary_image.length, - &jpeg_image_info_ptr->width, - &jpeg_image_info_ptr->height, - jpeg_image_info_ptr->iccData, - jpeg_image_info_ptr->exifData)) { + if (!jpeg_dec_obj_hdr.getCompressedImageParameters( + primary_image.data, primary_image.length, &jpeg_image_info_ptr->width, + &jpeg_image_info_ptr->height, jpeg_image_info_ptr->iccData, + jpeg_image_info_ptr->exifData)) { return ERROR_JPEGR_DECODE_ERROR; } @@ -615,29 +611,29 @@ status_t JpegR::decodeJPEGR(jr_compressed_ptr jpegr_image_ptr, jr_uncompressed_p jr_uncompressed_ptr gainmap_image_ptr, ultrahdr_metadata_ptr metadata) { if (jpegr_image_ptr == nullptr || jpegr_image_ptr->data == nullptr) { ALOGE("received nullptr for compressed jpegr image"); - return ERROR_JPEGR_INVALID_NULL_PTR; + return ERROR_JPEGR_BAD_PTR; } if (dest == nullptr || dest->data == nullptr) { ALOGE("received nullptr for dest image"); - return ERROR_JPEGR_INVALID_NULL_PTR; + return ERROR_JPEGR_BAD_PTR; } if (max_display_boost < 1.0f) { ALOGE("received bad value for max_display_boost %f", max_display_boost); - return ERROR_JPEGR_INVALID_INPUT_TYPE; + return ERROR_JPEGR_INVALID_DISPLAY_BOOST; } if (exif != nullptr && exif->data == nullptr) { ALOGE("received nullptr address for exif data"); - return ERROR_JPEGR_INVALID_INPUT_TYPE; + 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_INPUT_TYPE; + return ERROR_JPEGR_INVALID_OUTPUT_FORMAT; } jpegr_compressed_struct primary_jpeg_image, gainmap_jpeg_image; status_t status = - extractPrimaryImageAndGainMap(jpegr_image_ptr, &primary_jpeg_image, &gainmap_jpeg_image); - if (status != NO_ERROR) { + 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; @@ -654,19 +650,19 @@ status_t JpegR::decodeJPEGR(jr_compressed_ptr jpegr_image_ptr, jr_uncompressed_p if ((jpeg_dec_obj_yuv420.getDecompressedImageWidth() * jpeg_dec_obj_yuv420.getDecompressedImageHeight() * 4) > jpeg_dec_obj_yuv420.getDecompressedImageSize()) { - return ERROR_JPEGR_CALCULATION_ERROR; + return ERROR_JPEGR_DECODE_ERROR; } } else { if ((jpeg_dec_obj_yuv420.getDecompressedImageWidth() * jpeg_dec_obj_yuv420.getDecompressedImageHeight() * 3 / 2) > jpeg_dec_obj_yuv420.getDecompressedImageSize()) { - return ERROR_JPEGR_CALCULATION_ERROR; + return ERROR_JPEGR_DECODE_ERROR; } } if (exif != nullptr) { if (exif->data == nullptr) { - return ERROR_JPEGR_INVALID_NULL_PTR; + return ERROR_JPEGR_BAD_PTR; } if (exif->length < jpeg_dec_obj_yuv420.getEXIFSize()) { return ERROR_JPEGR_BUFFER_TOO_SMALL; @@ -680,7 +676,7 @@ status_t JpegR::decodeJPEGR(jr_compressed_ptr jpegr_image_ptr, jr_uncompressed_p dest->height = jpeg_dec_obj_yuv420.getDecompressedImageHeight(); memcpy(dest->data, jpeg_dec_obj_yuv420.getDecompressedImagePtr(), dest->width * dest->height * 4); - return NO_ERROR; + return JPEGR_NO_ERROR; } JpegDecoderHelper jpeg_dec_obj_gm; @@ -689,7 +685,7 @@ status_t JpegR::decodeJPEGR(jr_compressed_ptr jpegr_image_ptr, jr_uncompressed_p } if ((jpeg_dec_obj_gm.getDecompressedImageWidth() * jpeg_dec_obj_gm.getDecompressedImageHeight()) > jpeg_dec_obj_gm.getDecompressedImageSize()) { - return ERROR_JPEGR_CALCULATION_ERROR; + return ERROR_JPEGR_DECODE_ERROR; } jpegr_uncompressed_struct gainmap_image; @@ -708,7 +704,7 @@ status_t JpegR::decodeJPEGR(jr_compressed_ptr jpegr_image_ptr, jr_uncompressed_p 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_INVALID_METADATA; + return ERROR_JPEGR_METADATA_ERROR; } if (metadata != nullptr) { @@ -735,13 +731,13 @@ status_t JpegR::decodeJPEGR(jr_compressed_ptr jpegr_image_ptr, jr_uncompressed_p JPEGR_CHECK(applyGainMap(&yuv420_image, &gainmap_image, &uhdr_metadata, output_format, max_display_boost, dest)); - return NO_ERROR; + return JPEGR_NO_ERROR; } status_t JpegR::compressGainMap(jr_uncompressed_ptr gainmap_image_ptr, JpegEncoderHelper* jpeg_enc_obj_ptr) { if (gainmap_image_ptr == nullptr || jpeg_enc_obj_ptr == nullptr) { - return ERROR_JPEGR_INVALID_NULL_PTR; + return ERROR_JPEGR_BAD_PTR; } // Don't need to convert YUV to Bt601 since single channel @@ -752,7 +748,7 @@ status_t JpegR::compressGainMap(jr_uncompressed_ptr gainmap_image_ptr, return ERROR_JPEGR_ENCODE_ERROR; } - return NO_ERROR; + return JPEGR_NO_ERROR; } const int kJobSzInRows = 16; @@ -760,13 +756,13 @@ static_assert(kJobSzInRows > 0 && kJobSzInRows % kMapDimensionScaleFactor == 0, "align job size to kMapDimensionScaleFactor"); class JobQueue { -public: + public: bool dequeueJob(size_t& rowStart, size_t& rowEnd); void enqueueJob(size_t rowStart, size_t rowEnd); void markQueueForEnd(); void reset(); -private: + private: bool mQueuedAllJobs = false; std::deque<std::tuple<size_t, size_t>> mJobs; std::mutex mMutex; @@ -821,7 +817,7 @@ status_t JpegR::generateGainMap(jr_uncompressed_ptr yuv420_image_ptr, dest == nullptr || yuv420_image_ptr->data == nullptr || yuv420_image_ptr->chroma_data == nullptr || p010_image_ptr->data == nullptr || p010_image_ptr->chroma_data == nullptr) { - return ERROR_JPEGR_INVALID_NULL_PTR; + return ERROR_JPEGR_BAD_PTR; } if (yuv420_image_ptr->width != p010_image_ptr->width || yuv420_image_ptr->height != p010_image_ptr->height) { @@ -889,7 +885,7 @@ status_t JpegR::generateGainMap(jr_uncompressed_ptr yuv420_image_ptr, float log2MaxBoost = log2(metadata->maxContentBoost); ColorTransformFn hdrGamutConversionFn = - getHdrConversionFn(yuv420_image_ptr->colorGamut, p010_image_ptr->colorGamut); + getHdrConversionFn(yuv420_image_ptr->colorGamut, p010_image_ptr->colorGamut); ColorCalculationFn luminanceFn = nullptr; ColorTransformFn sdrYuvToRgbFn = nullptr; @@ -930,7 +926,7 @@ status_t JpegR::generateGainMap(jr_uncompressed_ptr yuv420_image_ptr, return ERROR_JPEGR_INVALID_COLORGAMUT; } - const int threads = std::clamp(GetCPUCoreCount(), 1, 4); + const int threads = (std::min)(GetCPUCoreCount(), 4); size_t rowStep = threads == 1 ? image_height : kJobSzInRows; JobQueue jobQueue; @@ -960,7 +956,7 @@ status_t JpegR::generateGainMap(jr_uncompressed_ptr yuv420_image_ptr, size_t pixel_idx = x + y * dest->width; reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] = - encodeGain(sdr_y_nits, hdr_y_nits, metadata, log2MinBoost, log2MaxBoost); + encodeGain(sdr_y_nits, hdr_y_nits, metadata, log2MinBoost, log2MaxBoost); } } } @@ -974,7 +970,7 @@ status_t JpegR::generateGainMap(jr_uncompressed_ptr yuv420_image_ptr, rowStep = (threads == 1 ? image_height : kJobSzInRows) / kMapDimensionScaleFactor; for (size_t rowStart = 0; rowStart < map_height;) { - size_t rowEnd = std::min(rowStart + rowStep, map_height); + size_t rowEnd = (std::min)(rowStart + rowStep, map_height); jobQueue.enqueueJob(rowStart, rowEnd); rowStart = rowEnd; } @@ -983,7 +979,7 @@ status_t JpegR::generateGainMap(jr_uncompressed_ptr yuv420_image_ptr, std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); }); map_data.release(); - return NO_ERROR; + return JPEGR_NO_ERROR; } status_t JpegR::applyGainMap(jr_uncompressed_ptr yuv420_image_ptr, @@ -993,25 +989,25 @@ status_t JpegR::applyGainMap(jr_uncompressed_ptr yuv420_image_ptr, if (yuv420_image_ptr == nullptr || gainmap_image_ptr == nullptr || metadata == nullptr || dest == nullptr || yuv420_image_ptr->data == nullptr || yuv420_image_ptr->chroma_data == nullptr || gainmap_image_ptr->data == nullptr) { - return ERROR_JPEGR_INVALID_NULL_PTR; + return ERROR_JPEGR_BAD_PTR; } if (metadata->version.compare(kJpegrVersion)) { ALOGE("Unsupported metadata version: %s", metadata->version.c_str()); - return ERROR_JPEGR_UNSUPPORTED_METADATA; + return ERROR_JPEGR_BAD_METADATA; } if (metadata->gamma != 1.0f) { ALOGE("Unsupported metadata gamma: %f", metadata->gamma); - return ERROR_JPEGR_UNSUPPORTED_METADATA; + return ERROR_JPEGR_BAD_METADATA; } if (metadata->offsetSdr != 0.0f || metadata->offsetHdr != 0.0f) { ALOGE("Unsupported metadata offset sdr, hdr: %f, %f", metadata->offsetSdr, metadata->offsetHdr); - return ERROR_JPEGR_UNSUPPORTED_METADATA; + return ERROR_JPEGR_BAD_METADATA; } if (metadata->hdrCapacityMin != metadata->minContentBoost || metadata->hdrCapacityMax != metadata->maxContentBoost) { ALOGE("Unsupported metadata hdr capacity min, max: %f, %f", metadata->hdrCapacityMin, metadata->hdrCapacityMax); - return ERROR_JPEGR_UNSUPPORTED_METADATA; + return ERROR_JPEGR_BAD_METADATA; } // TODO: remove once map scaling factor is computed based on actual map dims @@ -1020,21 +1016,22 @@ status_t JpegR::applyGainMap(jr_uncompressed_ptr yuv420_image_ptr, size_t map_width = image_width / kMapDimensionScaleFactor; size_t map_height = image_height / kMapDimensionScaleFactor; if (map_width != gainmap_image_ptr->width || map_height != gainmap_image_ptr->height) { - ALOGE("gain map dimensions and primary image dimensions are not to scale, computed gain map " - "resolution is %zux%zu, received gain map resolution is %zux%zu", - map_width, map_height, gainmap_image_ptr->width, gainmap_image_ptr->height); - return ERROR_JPEGR_INVALID_INPUT_TYPE; + ALOGE( + "gain map dimensions and primary image dimensions are not to scale, computed gain map " + "resolution is %zux%zu, received gain map resolution is %zux%zu", + map_width, map_height, gainmap_image_ptr->width, gainmap_image_ptr->height); + return ERROR_JPEGR_RESOLUTION_MISMATCH; } dest->width = yuv420_image_ptr->width; dest->height = yuv420_image_ptr->height; ShepardsIDW idwTable(kMapDimensionScaleFactor); - float display_boost = std::min(max_display_boost, metadata->maxContentBoost); + float display_boost = (std::min)(max_display_boost, metadata->maxContentBoost); GainLUT gainLUT(metadata, display_boost); JobQueue jobQueue; - std::function<void()> applyRecMap = [yuv420_image_ptr, gainmap_image_ptr, dest, - &jobQueue, &idwTable, output_format, &gainLUT, + std::function<void()> applyRecMap = [yuv420_image_ptr, gainmap_image_ptr, dest, &jobQueue, + &idwTable, output_format, &gainLUT, display_boost]() -> void { size_t width = yuv420_image_ptr->width; @@ -1108,36 +1105,35 @@ status_t JpegR::applyGainMap(jr_uncompressed_ptr yuv420_image_ptr, } }; - const int threads = std::clamp(GetCPUCoreCount(), 1, 4); + const int threads = (std::min)(GetCPUCoreCount(), 4); std::vector<std::thread> workers; for (int th = 0; th < threads - 1; th++) { workers.push_back(std::thread(applyRecMap)); } const int rowStep = threads == 1 ? yuv420_image_ptr->height : kJobSzInRows; for (size_t rowStart = 0; rowStart < yuv420_image_ptr->height;) { - int rowEnd = std::min(rowStart + rowStep, yuv420_image_ptr->height); + int rowEnd = (std::min)(rowStart + rowStep, yuv420_image_ptr->height); jobQueue.enqueueJob(rowStart, rowEnd); rowStart = rowEnd; } jobQueue.markQueueForEnd(); applyRecMap(); std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); }); - return NO_ERROR; + return JPEGR_NO_ERROR; } status_t JpegR::extractPrimaryImageAndGainMap(jr_compressed_ptr jpegr_image_ptr, jr_compressed_ptr primary_jpg_image_ptr, jr_compressed_ptr gainmap_jpg_image_ptr) { if (jpegr_image_ptr == nullptr) { - return ERROR_JPEGR_INVALID_NULL_PTR; + return ERROR_JPEGR_BAD_PTR; } MessageHandler msg_handler; msg_handler.SetMessageWriter(make_unique<AlogMessageWriter>(AlogMessageWriter())); - std::shared_ptr<DataSegment> seg = - DataSegment::Create(DataRange(0, jpegr_image_ptr->length), - static_cast<const uint8_t*>(jpegr_image_ptr->data), - DataSegment::BufferDispositionPolicy::kDontDelete); + std::shared_ptr<DataSegment> seg = DataSegment::Create( + DataRange(0, jpegr_image_ptr->length), static_cast<const uint8_t*>(jpegr_image_ptr->data), + DataSegment::BufferDispositionPolicy::kDontDelete); DataSegmentDataSource data_source(seg); JpegInfoBuilder jpeg_info_builder; jpeg_info_builder.SetImageLimit(2); @@ -1146,19 +1142,19 @@ status_t JpegR::extractPrimaryImageAndGainMap(jr_compressed_ptr jpegr_image_ptr, data_source.Reset(); if (jpeg_scanner.HasError()) { - return ERROR_JPEGR_INVALID_INPUT_TYPE; + return JPEGR_UNKNOWN_ERROR; } const auto& jpeg_info = jpeg_info_builder.GetInfo(); const auto& image_ranges = jpeg_info.GetImageRanges(); if (image_ranges.empty()) { - return ERROR_JPEGR_INVALID_INPUT_TYPE; + return ERROR_JPEGR_NO_IMAGES_FOUND; } if (primary_jpg_image_ptr != nullptr) { primary_jpg_image_ptr->data = - static_cast<uint8_t*>(jpegr_image_ptr->data) + image_ranges[0].GetBegin(); + static_cast<uint8_t*>(jpegr_image_ptr->data) + image_ranges[0].GetBegin(); primary_jpg_image_ptr->length = image_ranges[0].GetLength(); } @@ -1168,7 +1164,7 @@ status_t JpegR::extractPrimaryImageAndGainMap(jr_compressed_ptr jpegr_image_ptr, if (gainmap_jpg_image_ptr != nullptr) { gainmap_jpg_image_ptr->data = - static_cast<uint8_t*>(jpegr_image_ptr->data) + image_ranges[1].GetBegin(); + static_cast<uint8_t*>(jpegr_image_ptr->data) + image_ranges[1].GetBegin(); gainmap_jpg_image_ptr->length = image_ranges[1].GetLength(); } @@ -1178,7 +1174,7 @@ status_t JpegR::extractPrimaryImageAndGainMap(jr_compressed_ptr jpegr_image_ptr, (int)image_ranges.size()); } - return NO_ERROR; + return JPEGR_NO_ERROR; } // JPEG/R structure: @@ -1221,42 +1217,43 @@ status_t JpegR::appendGainMap(jr_compressed_ptr primary_jpg_image_ptr, jr_compressed_ptr dest) { if (primary_jpg_image_ptr == nullptr || gainmap_jpg_image_ptr == nullptr || metadata == nullptr || dest == nullptr) { - return ERROR_JPEGR_INVALID_NULL_PTR; + return ERROR_JPEGR_BAD_PTR; } if (metadata->version.compare("1.0")) { ALOGE("received bad value for version: %s", metadata->version.c_str()); - return ERROR_JPEGR_INVALID_INPUT_TYPE; + return ERROR_JPEGR_BAD_METADATA; } if (metadata->maxContentBoost < metadata->minContentBoost) { ALOGE("received bad value for content boost min %f, max %f", metadata->minContentBoost, metadata->maxContentBoost); - return ERROR_JPEGR_INVALID_INPUT_TYPE; + return ERROR_JPEGR_BAD_METADATA; } if (metadata->hdrCapacityMax < metadata->hdrCapacityMin || metadata->hdrCapacityMin < 1.0f) { ALOGE("received bad value for hdr capacity min %f, max %f", metadata->hdrCapacityMin, metadata->hdrCapacityMax); - return ERROR_JPEGR_INVALID_INPUT_TYPE; + return ERROR_JPEGR_BAD_METADATA; } if (metadata->offsetSdr < 0.0f || metadata->offsetHdr < 0.0f) { ALOGE("received bad value for offset sdr %f, hdr %f", metadata->offsetSdr, metadata->offsetHdr); - return ERROR_JPEGR_INVALID_INPUT_TYPE; + return ERROR_JPEGR_BAD_METADATA; } if (metadata->gamma <= 0.0f) { ALOGE("received bad value for gamma %f", metadata->gamma); - return ERROR_JPEGR_INVALID_INPUT_TYPE; + return ERROR_JPEGR_BAD_METADATA; } const string nameSpace = "http://ns.adobe.com/xap/1.0/"; - const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator + const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator // calculate secondary image length first, because the length will be written into the primary // image xmp const string xmp_secondary = generateXmpForSecondaryImage(*metadata); - const int xmp_secondary_length = 2 /* 2 bytes representing the length of the package */ - + nameSpaceLength /* 29 bytes length of name space including \0 */ - + xmp_secondary.size(); /* length of xmp packet */ + // xmp_secondary_length = 2 bytes representing the length of the package + + // + nameSpaceLength = 29 bytes length + // + length of xmp packet = xmp_secondary.size() + const int xmp_secondary_length = 2 + nameSpaceLength + xmp_secondary.size(); const int secondary_image_size = 2 /* 2 bytes length of APP1 sign */ - + xmp_secondary_length + gainmap_jpg_image_ptr->length; + + xmp_secondary_length + gainmap_jpg_image_ptr->length; // primary image const string xmp_primary = generateXmpForPrimaryImage(secondary_image_size, *metadata); // same as primary @@ -1268,16 +1265,19 @@ status_t JpegR::appendGainMap(jr_compressed_ptr primary_jpg_image_ptr, if (!decoder.extractEXIF(primary_jpg_image_ptr->data, primary_jpg_image_ptr->length)) { return ERROR_JPEGR_DECODE_ERROR; } - jpegr_exif_struct exif_from_jpg = {.data = nullptr, .length = 0}; - jpegr_compressed_struct new_jpg_image = {.data = nullptr, - .length = 0, - .maxLength = 0, - .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED}; + jpegr_exif_struct exif_from_jpg; + exif_from_jpg.data = nullptr; + exif_from_jpg.length = 0; + jpegr_compressed_struct new_jpg_image; + new_jpg_image.data = nullptr; + new_jpg_image.length = 0; + new_jpg_image.maxLength = 0; + new_jpg_image.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED; std::unique_ptr<uint8_t[]> dest_data; if (decoder.getEXIFPos() >= 0) { if (pExif != nullptr) { ALOGE("received EXIF from outside while the primary image already contains EXIF"); - return ERROR_JPEGR_INVALID_INPUT_TYPE; + return ERROR_JPEGR_MULTIPLE_EXIFS_RECEIVED; } copyJpegWithoutExif(&new_jpg_image, primary_jpg_image_ptr, decoder.getEXIFPos(), decoder.getEXIFSize()); @@ -1288,7 +1288,7 @@ status_t JpegR::appendGainMap(jr_compressed_ptr primary_jpg_image_ptr, } jr_compressed_ptr final_primary_jpg_image_ptr = - new_jpg_image.length == 0 ? primary_jpg_image_ptr : &new_jpg_image; + new_jpg_image.length == 0 ? primary_jpg_image_ptr : &new_jpg_image; int pos = 0; // Begin primary image @@ -1384,15 +1384,15 @@ status_t JpegR::appendGainMap(jr_compressed_ptr primary_jpg_image_ptr, dest->length = pos; // Done! - return NO_ERROR; + return JPEGR_NO_ERROR; } status_t JpegR::toneMap(jr_uncompressed_ptr src, jr_uncompressed_ptr dest) { if (src == nullptr || dest == nullptr) { - return ERROR_JPEGR_INVALID_NULL_PTR; + return ERROR_JPEGR_BAD_PTR; } if (src->width != dest->width || src->height != dest->height) { - return ERROR_JPEGR_INVALID_INPUT_TYPE; + return ERROR_JPEGR_RESOLUTION_MISMATCH; } uint16_t* src_y_data = reinterpret_cast<uint16_t*>(src->data); uint8_t* dst_y_data = reinterpret_cast<uint8_t*>(dest->data); @@ -1427,13 +1427,13 @@ status_t JpegR::toneMap(jr_uncompressed_ptr src, jr_uncompressed_ptr dest) { } } dest->colorGamut = src->colorGamut; - return NO_ERROR; + return JPEGR_NO_ERROR; } status_t JpegR::convertYuv(jr_uncompressed_ptr image, ultrahdr_color_gamut src_encoding, ultrahdr_color_gamut dest_encoding) { if (image == nullptr) { - return ERROR_JPEGR_INVALID_NULL_PTR; + return ERROR_JPEGR_BAD_PTR; } if (src_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED || dest_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED) { @@ -1445,7 +1445,7 @@ status_t JpegR::convertYuv(jr_uncompressed_ptr image, ultrahdr_color_gamut src_e case ULTRAHDR_COLORGAMUT_BT709: switch (dest_encoding) { case ULTRAHDR_COLORGAMUT_BT709: - return NO_ERROR; + return JPEGR_NO_ERROR; case ULTRAHDR_COLORGAMUT_P3: conversionFn = yuv709To601; break; @@ -1463,7 +1463,7 @@ status_t JpegR::convertYuv(jr_uncompressed_ptr image, ultrahdr_color_gamut src_e conversionFn = yuv601To709; break; case ULTRAHDR_COLORGAMUT_P3: - return NO_ERROR; + return JPEGR_NO_ERROR; case ULTRAHDR_COLORGAMUT_BT2100: conversionFn = yuv601To2100; break; @@ -1481,7 +1481,7 @@ status_t JpegR::convertYuv(jr_uncompressed_ptr image, ultrahdr_color_gamut src_e conversionFn = yuv2100To601; break; case ULTRAHDR_COLORGAMUT_BT2100: - return NO_ERROR; + return JPEGR_NO_ERROR; default: // Should be impossible to hit after input validation return ERROR_JPEGR_INVALID_COLORGAMUT; @@ -1503,7 +1503,7 @@ status_t JpegR::convertYuv(jr_uncompressed_ptr image, ultrahdr_color_gamut src_e } } - return NO_ERROR; + return JPEGR_NO_ERROR; } -} // namespace ultrahdr +} // namespace ultrahdr diff --git a/lib/jpegr.h b/lib/jpegr.h new file mode 100644 index 0000000..5c8f9c3 --- /dev/null +++ b/lib/jpegr.h @@ -0,0 +1,464 @@ +/* + * Copyright 2022 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_JPEGR_H +#define ULTRAHDR_JPEGR_H + +#include <cfloat> + +#include "ultrahdr.h" +#include "jpegdecoderhelper.h" +#include "jpegencoderhelper.h" + +namespace ultrahdr { + +// The current JPEGR version that we encode to +static const char* const kJpegrVersion = "1.0"; + +// Map is quarter res / sixteenth size +static const size_t kMapDimensionScaleFactor = 4; + +// Gain Map width is (image_width / kMapDimensionScaleFactor). If we were to +// compress 420 GainMap in jpeg, then we need at least 2 samples. For Grayscale +// 1 sample is sufficient. We are using 2 here anyways +static const int kMinWidth = 2 * kMapDimensionScaleFactor; +static const int kMinHeight = 2 * kMapDimensionScaleFactor; + +typedef enum { + JPEGR_NO_ERROR = 0, + JPEGR_UNKNOWN_ERROR = -1, + + JPEGR_IO_ERROR_BASE = -10000, + ERROR_JPEGR_BAD_PTR = JPEGR_IO_ERROR_BASE - 1, + ERROR_JPEGR_UNSUPPORTED_WIDTH_HEIGHT = JPEGR_IO_ERROR_BASE - 2, + ERROR_JPEGR_INVALID_COLORGAMUT = JPEGR_IO_ERROR_BASE - 3, + ERROR_JPEGR_INVALID_STRIDE = JPEGR_IO_ERROR_BASE - 4, + ERROR_JPEGR_INVALID_TRANS_FUNC = JPEGR_IO_ERROR_BASE - 5, + ERROR_JPEGR_RESOLUTION_MISMATCH = JPEGR_IO_ERROR_BASE - 6, + ERROR_JPEGR_INVALID_QUALITY_FACTOR = JPEGR_IO_ERROR_BASE - 7, + 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, + + JPEGR_RUNTIME_ERROR_BASE = -20000, + ERROR_JPEGR_ENCODE_ERROR = JPEGR_RUNTIME_ERROR_BASE - 1, + ERROR_JPEGR_DECODE_ERROR = JPEGR_RUNTIME_ERROR_BASE - 2, + ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND = JPEGR_RUNTIME_ERROR_BASE - 3, + ERROR_JPEGR_BUFFER_TOO_SMALL = JPEGR_RUNTIME_ERROR_BASE - 4, + ERROR_JPEGR_METADATA_ERROR = JPEGR_RUNTIME_ERROR_BASE - 5, + ERROR_JPEGR_NO_IMAGES_FOUND = JPEGR_RUNTIME_ERROR_BASE - 6, + ERROR_JPEGR_MULTIPLE_EXIFS_RECEIVED = JPEGR_RUNTIME_ERROR_BASE - 7, + + ERROR_JPEGR_UNSUPPORTED_FEATURE = -30000, +} status_t; + +/* + * Holds information of jpegr image + */ +struct jpegr_info_struct { + size_t width; + size_t height; + std::vector<uint8_t>* iccData; + std::vector<uint8_t>* exifData; +}; + +/* + * Holds information for uncompressed image or gain map. + */ +struct jpegr_uncompressed_struct { + // Pointer to the data location. + void* data; + // Width of the gain map or the luma plane of the image in pixels. + size_t width; + // Height of the gain map or the luma plane of the image in pixels. + size_t height; + // Color gamut. + ultrahdr_color_gamut colorGamut; + + // Values below are optional + // Pointer to chroma data, if it's NULL, chroma plane is considered to be immediately + // after the luma plane. + void* chroma_data = nullptr; + // Stride of Y plane in number of pixels. 0 indicates the member is uninitialized. If + // non-zero this value must be larger than or equal to luma width. If stride is + // uninitialized then it is assumed to be equal to luma width. + size_t luma_stride = 0; + // Stride of UV plane in number of pixels. + // 1. If this handle points to P010 image then this value must be larger than + // or equal to luma width. + // 2. If this handle points to 420 image then this value must be larger than + // or equal to (luma width / 2). + // 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; +}; + +/* + * Holds information for compressed image or gain map. + */ +struct jpegr_compressed_struct { + // Pointer to the data location. + void* data; + // Used data length in bytes. + int length; + // Maximum available data length in bytes. + int maxLength; + // Color gamut. + ultrahdr_color_gamut colorGamut; +}; + +/* + * Holds information for EXIF metadata. + */ +struct jpegr_exif_struct { + // Pointer to the data location. + void* data; + // Data length; + size_t length; +}; + +typedef struct jpegr_uncompressed_struct* jr_uncompressed_ptr; +typedef struct jpegr_compressed_struct* jr_compressed_ptr; +typedef struct jpegr_exif_struct* jr_exif_ptr; +typedef struct jpegr_info_struct* jr_info_ptr; + +class JpegR { + public: + /* + * Experimental only + * + * Encode API-0 + * Compress JPEGR image from 10-bit HDR YUV. + * + * Tonemap the HDR input to a SDR image, generate gain map from the HDR and SDR images, + * compress SDR YUV to 8-bit JPEG and append the gain map to the end of the compressed + * JPEG. + * @param p010_image_ptr uncompressed HDR image in P010 color format + * @param hdr_tf transfer function of the HDR image + * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength} + * represents the maximum available size of the destination buffer, and it must be + * set before calling this method. If the encoded JPEGR size exceeds + * {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}. + * @param quality target quality of the JPEG encoding, must be in range of 0-100 where 100 is + * the highest quality + * @param exif pointer to the exif metadata. + * @return NO_ERROR if encoding succeeds, error code if error occurs. + */ + status_t encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, ultrahdr_transfer_function hdr_tf, + jr_compressed_ptr dest, int quality, jr_exif_ptr exif); + + /* + * Encode API-1 + * Compress JPEGR image from 10-bit HDR YUV and 8-bit SDR YUV. + * + * Generate gain map from the HDR and SDR inputs, compress SDR YUV to 8-bit JPEG and append + * the gain map to the end of the compressed JPEG. HDR and SDR inputs must be the same + * resolution. SDR input is assumed to use the sRGB transfer function. + * @param p010_image_ptr uncompressed HDR image in P010 color format + * @param yuv420_image_ptr uncompressed SDR image in YUV_420 color format + * @param hdr_tf transfer function of the HDR image + * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength} + * represents the maximum available size of the desitination buffer, and it must be + * set before calling this method. If the encoded JPEGR size exceeds + * {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}. + * @param quality target quality of the JPEG encoding, must be in range of 0-100 where 100 is + * the highest quality + * @param exif pointer to the exif metadata. + * @return NO_ERROR if encoding succeeds, error code if error occurs. + */ + status_t encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, jr_uncompressed_ptr yuv420_image_ptr, + ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest, int quality, + jr_exif_ptr exif); + + /* + * Encode API-2 + * Compress JPEGR image from 10-bit HDR YUV, 8-bit SDR YUV and compressed 8-bit JPEG. + * + * This method requires HAL Hardware JPEG encoder. + * + * Generate gain map from the HDR and SDR inputs, append the gain map to the end of the + * compressed JPEG. Adds an ICC profile if one isn't present in the input JPEG image. HDR and + * SDR inputs must be the same resolution and color space. SDR image is assumed to use the sRGB + * transfer function. + * @param p010_image_ptr uncompressed HDR image in P010 color format + * @param yuv420_image_ptr uncompressed SDR image in YUV_420 color format + * @param yuv420jpg_image_ptr SDR image compressed in jpeg format + * @param hdr_tf transfer function of the HDR image + * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength} + * represents the maximum available size of the desitination buffer, and it must be + * set before calling this method. If the encoded JPEGR size exceeds + * {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}. + * @return NO_ERROR if encoding succeeds, error code if error occurs. + */ + status_t encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, jr_uncompressed_ptr yuv420_image_ptr, + jr_compressed_ptr yuv420jpg_image_ptr, ultrahdr_transfer_function hdr_tf, + jr_compressed_ptr dest); + + /* + * Encode API-3 + * Compress JPEGR image from 10-bit HDR YUV and 8-bit SDR YUV. + * + * This method requires HAL Hardware JPEG encoder. + * + * Decode the compressed 8-bit JPEG image to YUV SDR, generate gain map from the HDR input + * and the decoded SDR result, append the gain map to the end of the compressed JPEG. Adds an + * ICC profile if one isn't present in the input JPEG image. HDR and SDR inputs must be the same + * resolution. JPEG image is assumed to use the sRGB transfer function. + * @param p010_image_ptr uncompressed HDR image in P010 color format + * @param yuv420jpg_image_ptr SDR image compressed in jpeg format + * @param hdr_tf transfer function of the HDR image + * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength} + * represents the maximum available size of the desitination buffer, and it must be + * set before calling this method. If the encoded JPEGR size exceeds + * {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}. + * @return NO_ERROR if encoding succeeds, error code if error occurs. + */ + status_t encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, jr_compressed_ptr yuv420jpg_image_ptr, + ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest); + + /* + * Encode API-4 + * Assemble JPEGR image from SDR JPEG and gainmap JPEG. + * + * Assemble the primary JPEG image, the gain map and the metadata to JPEG/R format. Adds an ICC + * profile if one isn't present in the input JPEG image. + * @param yuv420jpg_image_ptr SDR image compressed in jpeg format + * @param gainmapjpg_image_ptr gain map image compressed in jpeg format + * @param metadata metadata to be written in XMP of the primary jpeg + * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength} + * represents the maximum available size of the desitination buffer, and it must be + * set before calling this method. If the encoded JPEGR size exceeds + * {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}. + * @return NO_ERROR if encoding succeeds, error code if error occurs. + */ + status_t encodeJPEGR(jr_compressed_ptr yuv420jpg_image_ptr, + jr_compressed_ptr gainmapjpg_image_ptr, ultrahdr_metadata_ptr metadata, + jr_compressed_ptr dest); + + /* + * Decode API + * Decompress JPEGR image. + * + * This method assumes that the JPEGR image contains an ICC profile with primaries that match + * those of a color gamut that this library is aware of; Bt.709, Display-P3, or Bt.2100. It also + * assumes the base image uses the sRGB transfer function. + * + * This method only supports single gain map metadata values for fields that allow multi-channel + * metadata values. + * @param jpegr_image_ptr compressed JPEGR image. + * @param dest destination of the uncompressed JPEGR image. + * @param max_display_boost (optional) the maximum available boost supported by a display, + * the value must be greater than or equal to 1.0. + * @param exif destination of the decoded EXIF metadata. The default value is NULL where the + decoder will do nothing about it. If configured not NULL the decoder will write + EXIF data into this structure. The format is defined in {@code jpegr_exif_struct} + * @param output_format flag for setting output color format. Its value configures the output + color format. The default value is {@code JPEGR_OUTPUT_HDR_LINEAR}. + ---------------------------------------------------------------------- + | output_format | decoded color format to be written | + ---------------------------------------------------------------------- + | JPEGR_OUTPUT_SDR | RGBA_8888 | + ---------------------------------------------------------------------- + | JPEGR_OUTPUT_HDR_LINEAR | (default)RGBA_F16 linear | + ---------------------------------------------------------------------- + | JPEGR_OUTPUT_HDR_PQ | RGBA_1010102 PQ | + ---------------------------------------------------------------------- + | JPEGR_OUTPUT_HDR_HLG | RGBA_1010102 HLG | + ---------------------------------------------------------------------- + * @param gainmap_image_ptr destination of the decoded gain map. The default value is NULL + where the decoder will do nothing about it. If configured not NULL + the decoder will write the decoded gain_map data into this + structure. The format is defined in + {@code jpegr_uncompressed_struct}. + * @param metadata destination of the decoded metadata. The default value is NULL where the + decoder will do nothing about it. If configured not NULL the decoder will + write metadata into this structure. the format of metadata is defined in + {@code ultrahdr_metadata_struct}. + * @return NO_ERROR if decoding succeeds, error code if error occurs. + */ + status_t decodeJPEGR(jr_compressed_ptr jpegr_image_ptr, jr_uncompressed_ptr dest, + float max_display_boost = FLT_MAX, jr_exif_ptr exif = nullptr, + ultrahdr_output_format output_format = ULTRAHDR_OUTPUT_HDR_LINEAR, + jr_uncompressed_ptr gainmap_image_ptr = nullptr, + ultrahdr_metadata_ptr metadata = nullptr); + + /* + * Gets Info from JPEGR file without decoding it. + * + * This method only supports single gain map metadata values for fields that allow multi-channel + * metadata values. + * + * The output is filled jpegr_info structure + * @param jpegr_image_ptr compressed JPEGR image + * @param jpeg_image_info_ptr pointer to jpegr info struct. Members of jpegr_info + * are owned by the caller + * @return NO_ERROR if JPEGR parsing succeeds, error code otherwise + */ + status_t getJPEGRInfo(jr_compressed_ptr jpegr_image_ptr, jr_info_ptr jpeg_image_info_ptr); + + protected: + /* + * This method is called in the encoding pipeline. It will take the uncompressed 8-bit and + * 10-bit yuv images as input, and calculate the uncompressed gain map. The input images + * must be the same resolution. The SDR input is assumed to use the sRGB transfer function. + * + * @param yuv420_image_ptr uncompressed SDR image in YUV_420 color format + * @param p010_image_ptr uncompressed HDR image in P010 color format + * @param hdr_tf transfer function of the HDR image + * @param metadata everything but "version" is filled in this struct + * @param dest location at which gain map image is stored (caller responsible for memory + of data). + * @param sdr_is_601 if true, then use BT.601 decoding of YUV regardless of SDR image gamut + * @return NO_ERROR if calculation succeeds, error code if error occurs. + */ + status_t generateGainMap(jr_uncompressed_ptr yuv420_image_ptr, jr_uncompressed_ptr p010_image_ptr, + ultrahdr_transfer_function hdr_tf, ultrahdr_metadata_ptr metadata, + jr_uncompressed_ptr dest, bool sdr_is_601 = false); + + /* + * This method is called in the decoding pipeline. It will take the uncompressed (decoded) + * 8-bit yuv image, the uncompressed (decoded) gain map, and extracted JPEG/R metadata as + * input, and calculate the 10-bit recovered image. The recovered output image is the same + * color gamut as the SDR image, with HLG transfer function, and is in RGBA1010102 data format. + * The SDR image is assumed to use the sRGB transfer function. The SDR image is also assumed to + * be a decoded JPEG for the purpose of YUV interpration. + * + * @param yuv420_image_ptr uncompressed SDR image in YUV_420 color format + * @param gainmap_image_ptr pointer to uncompressed gain map image struct. + * @param metadata JPEG/R metadata extracted from XMP. + * @param output_format flag for setting output color format. if set to + * {@code JPEGR_OUTPUT_SDR}, decoder will only decode the primary image + * which is SDR. Default value is JPEGR_OUTPUT_HDR_LINEAR. + * @param max_display_boost the maximum available boost supported by a display + * @param dest reconstructed HDR image + * @return NO_ERROR if calculation succeeds, error code if error occurs. + */ + status_t applyGainMap(jr_uncompressed_ptr yuv420_image_ptr, jr_uncompressed_ptr gainmap_image_ptr, + ultrahdr_metadata_ptr metadata, ultrahdr_output_format output_format, + float max_display_boost, jr_uncompressed_ptr dest); + + private: + /* + * This method is called in the encoding pipeline. It will encode the gain map. + * + * @param gainmap_image_ptr pointer to uncompressed gain map image struct + * @param jpeg_enc_obj_ptr helper resource to compress gain map + * @return NO_ERROR if encoding succeeds, error code if error occurs. + */ + status_t compressGainMap(jr_uncompressed_ptr gainmap_image_ptr, + JpegEncoderHelper* jpeg_enc_obj_ptr); + + /* + * This method is called to separate primary image and gain map image from JPEGR + * + * @param jpegr_image_ptr pointer to compressed JPEGR image. + * @param primary_jpg_image_ptr destination of primary image + * @param gainmap_jpg_image_ptr destination of compressed gain map image + * @return NO_ERROR if calculation succeeds, error code if error occurs. + */ + status_t extractPrimaryImageAndGainMap(jr_compressed_ptr jpegr_image_ptr, + jr_compressed_ptr primary_jpg_image_ptr, + jr_compressed_ptr gainmap_jpg_image_ptr); + + /* + * This method is called in the encoding pipeline. It will take the standard 8-bit JPEG image, + * the compressed gain map and optionally the exif package as inputs, and generate the XMP + * metadata, and finally append everything in the order of: + * SOI, APP2(EXIF) (if EXIF is from outside), APP2(XMP), primary image, gain map + * + * Note that in the final JPEG/R output, EXIF package will appear if ONLY ONE of the following + * conditions is fulfilled: + * (1) EXIF package is available from outside input. I.e. pExif != nullptr. + * (2) Input JPEG has EXIF. + * If both conditions are fulfilled, this method will return ERROR_JPEGR_INVALID_INPUT_TYPE + * + * @param primary_jpg_image_ptr destination of primary image + * @param gainmap_jpg_image_ptr destination of compressed gain map image + * @param (nullable) pExif EXIF package + * @param (nullable) pIcc ICC package + * @param icc_size length in bytes of ICC package + * @param metadata JPEG/R metadata to encode in XMP of the jpeg + * @param dest compressed JPEGR image + * @return NO_ERROR if calculation succeeds, error code if error occurs. + */ + status_t appendGainMap(jr_compressed_ptr primary_jpg_image_ptr, + jr_compressed_ptr gainmap_jpg_image_ptr, jr_exif_ptr pExif, void* pIcc, + size_t icc_size, ultrahdr_metadata_ptr metadata, jr_compressed_ptr dest); + + /* + * This method will tone map a HDR image to an SDR image. + * + * @param src pointer to uncompressed HDR image struct. HDR image is expected to be + * in p010 color format + * @param dest pointer to store tonemapped SDR image + */ + status_t toneMap(jr_uncompressed_ptr src, jr_uncompressed_ptr dest); + + /* + * This method will convert a YUV420 image from one YUV encoding to another in-place (eg. + * Bt.709 to Bt.601 YUV encoding). + * + * src_encoding and dest_encoding indicate the encoding via the YUV conversion defined for that + * gamut. P3 indicates Rec.601, since this is how DataSpace encodes Display-P3 YUV data. + * + * @param image the YUV420 image to convert + * @param src_encoding input YUV encoding + * @param dest_encoding output YUV encoding + * @return NO_ERROR if calculation succeeds, error code if error occurs. + */ + status_t convertYuv(jr_uncompressed_ptr image, ultrahdr_color_gamut src_encoding, + ultrahdr_color_gamut dest_encoding); + + /* + * This method will check the validity of the input arguments. + * + * @param p010_image_ptr uncompressed HDR image in P010 color format + * @param yuv420_image_ptr pointer to uncompressed SDR image struct. HDR image is expected to + * be in 420p color format + * @param hdr_tf transfer function of the HDR image + * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength} + * represents the maximum available size of the desitination buffer, and it must be + * set before calling this method. If the encoded JPEGR size exceeds + * {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}. + * @return NO_ERROR if the input args are valid, error code is not valid. + */ + status_t areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr, + jr_uncompressed_ptr yuv420_image_ptr, + ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest_ptr); + + /* + * This method will check the validity of the input arguments. + * + * @param p010_image_ptr uncompressed HDR image in P010 color format + * @param yuv420_image_ptr pointer to uncompressed SDR image struct. HDR image is expected to + * be in 420p color format + * @param hdr_tf transfer function of the HDR image + * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength} + * represents the maximum available size of the destination buffer, and it must be + * set before calling this method. If the encoded JPEGR size exceeds + * {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}. + * @param quality target quality of the JPEG encoding, must be in range of 0-100 where 100 is + * the highest quality + * @return NO_ERROR if the input args are valid, error code is not valid. + */ + status_t areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr, + jr_uncompressed_ptr yuv420_image_ptr, + ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest, + int quality); +}; +} // namespace ultrahdr + +#endif // ULTRAHDR_JPEGR_H diff --git a/lib/jpegrutils.cpp b/lib/jpegrutils.cpp new file mode 100644 index 0000000..2fdc347 --- /dev/null +++ b/lib/jpegrutils.cpp @@ -0,0 +1,583 @@ +/* + * Copyright 2022 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 <algorithm> +#include <cmath> + +#include "ultrahdrcommon.h" +#include "jpegr.h" +#include "jpegrutils.h" + +#include "image_io/xml/xml_reader.h" +#include "image_io/xml/xml_writer.h" +#include "image_io/base/message_handler.h" +#include "image_io/xml/xml_element_rules.h" +#include "image_io/xml/xml_handler.h" +#include "image_io/xml/xml_rule.h" + +using namespace photos_editing_formats::image_io; +using namespace std; + +namespace ultrahdr { +/* + * Helper function used for generating XMP metadata. + * + * @param prefix The prefix part of the name. + * @param suffix The suffix part of the name. + * @return A name of the form "prefix:suffix". + */ +static inline string Name(const string& prefix, const string& suffix) { + std::stringstream ss; + ss << prefix << ":" << suffix; + return ss.str(); +} + +DataStruct::DataStruct(int s) { + data = malloc(s); + length = s; + memset(data, 0, s); + writePos = 0; +} + +DataStruct::~DataStruct() { + if (data != nullptr) { + free(data); + } +} + +void* DataStruct::getData() { return data; } + +int DataStruct::getLength() { return length; } + +int DataStruct::getBytesWritten() { return writePos; } + +bool DataStruct::write8(uint8_t value) { + uint8_t v = value; + return write(&v, 1); +} + +bool DataStruct::write16(uint16_t value) { + uint16_t v = value; + return write(&v, 2); +} +bool DataStruct::write32(uint32_t value) { + uint32_t v = value; + return write(&v, 4); +} + +bool DataStruct::write(const void* src, int size) { + if (writePos + size > length) { + ALOGE("Writing out of boundary: write position: %d, size: %d, capacity: %d", writePos, size, + length); + return false; + } + memcpy((uint8_t*)data + writePos, src, size); + writePos += size; + return true; +} + +/* + * Helper function used for writing data to destination. + */ +status_t Write(jr_compressed_ptr destination, const void* source, size_t length, int& position) { + if (position + length > destination->maxLength) { + return ERROR_JPEGR_BUFFER_TOO_SMALL; + } + + memcpy((uint8_t*)destination->data + sizeof(uint8_t) * position, source, length); + position += length; + return JPEGR_NO_ERROR; +} + +// Extremely simple XML Handler - just searches for interesting elements +class XMPXmlHandler : public XmlHandler { + public: + XMPXmlHandler() : XmlHandler() { + state = NotStrarted; + versionFound = false; + minContentBoostFound = false; + maxContentBoostFound = false; + gammaFound = false; + offsetSdrFound = false; + offsetHdrFound = false; + hdrCapacityMinFound = false; + hdrCapacityMaxFound = false; + baseRenditionIsHdrFound = false; + } + + enum ParseState { NotStrarted, Started, Done }; + + virtual DataMatchResult StartElement(const XmlTokenContext& context) { + string val; + if (context.BuildTokenValue(&val)) { + if (!val.compare(containerName)) { + state = Started; + } else { + if (state != Done) { + state = NotStrarted; + } + } + } + return context.GetResult(); + } + + virtual DataMatchResult FinishElement(const XmlTokenContext& context) { + if (state == Started) { + state = Done; + lastAttributeName = ""; + } + return context.GetResult(); + } + + virtual DataMatchResult AttributeName(const XmlTokenContext& context) { + string val; + if (state == Started) { + if (context.BuildTokenValue(&val)) { + if (!val.compare(versionAttrName)) { + lastAttributeName = versionAttrName; + } else if (!val.compare(maxContentBoostAttrName)) { + lastAttributeName = maxContentBoostAttrName; + } else if (!val.compare(minContentBoostAttrName)) { + lastAttributeName = minContentBoostAttrName; + } else if (!val.compare(gammaAttrName)) { + lastAttributeName = gammaAttrName; + } else if (!val.compare(offsetSdrAttrName)) { + lastAttributeName = offsetSdrAttrName; + } else if (!val.compare(offsetHdrAttrName)) { + lastAttributeName = offsetHdrAttrName; + } else if (!val.compare(hdrCapacityMinAttrName)) { + lastAttributeName = hdrCapacityMinAttrName; + } else if (!val.compare(hdrCapacityMaxAttrName)) { + lastAttributeName = hdrCapacityMaxAttrName; + } else if (!val.compare(baseRenditionIsHdrAttrName)) { + lastAttributeName = baseRenditionIsHdrAttrName; + } else { + lastAttributeName = ""; + } + } + } + return context.GetResult(); + } + + virtual DataMatchResult AttributeValue(const XmlTokenContext& context) { + string val; + if (state == Started) { + if (context.BuildTokenValue(&val, true)) { + if (!lastAttributeName.compare(versionAttrName)) { + versionStr = val; + versionFound = true; + } else if (!lastAttributeName.compare(maxContentBoostAttrName)) { + maxContentBoostStr = val; + maxContentBoostFound = true; + } else if (!lastAttributeName.compare(minContentBoostAttrName)) { + minContentBoostStr = val; + minContentBoostFound = true; + } else if (!lastAttributeName.compare(gammaAttrName)) { + gammaStr = val; + gammaFound = true; + } else if (!lastAttributeName.compare(offsetSdrAttrName)) { + offsetSdrStr = val; + offsetSdrFound = true; + } else if (!lastAttributeName.compare(offsetHdrAttrName)) { + offsetHdrStr = val; + offsetHdrFound = true; + } else if (!lastAttributeName.compare(hdrCapacityMinAttrName)) { + hdrCapacityMinStr = val; + hdrCapacityMinFound = true; + } else if (!lastAttributeName.compare(hdrCapacityMaxAttrName)) { + hdrCapacityMaxStr = val; + hdrCapacityMaxFound = true; + } else if (!lastAttributeName.compare(baseRenditionIsHdrAttrName)) { + baseRenditionIsHdrStr = val; + baseRenditionIsHdrFound = true; + } + } + } + return context.GetResult(); + } + + bool getVersion(string* version, bool* present) { + if (state == Done) { + *version = versionStr; + *present = versionFound; + return true; + } else { + return false; + } + } + + bool getMaxContentBoost(float* max_content_boost, bool* present) { + if (state == Done) { + *present = maxContentBoostFound; + stringstream ss(maxContentBoostStr); + float val; + if (ss >> val) { + *max_content_boost = exp2(val); + return true; + } else { + return false; + } + } else { + return false; + } + } + + bool getMinContentBoost(float* min_content_boost, bool* present) { + if (state == Done) { + *present = minContentBoostFound; + stringstream ss(minContentBoostStr); + float val; + if (ss >> val) { + *min_content_boost = exp2(val); + return true; + } else { + return false; + } + } else { + return false; + } + } + + bool getGamma(float* gamma, bool* present) { + if (state == Done) { + *present = gammaFound; + stringstream ss(gammaStr); + float val; + if (ss >> val) { + *gamma = val; + return true; + } else { + return false; + } + } else { + return false; + } + } + + bool getOffsetSdr(float* offset_sdr, bool* present) { + if (state == Done) { + *present = offsetSdrFound; + stringstream ss(offsetSdrStr); + float val; + if (ss >> val) { + *offset_sdr = val; + return true; + } else { + return false; + } + } else { + return false; + } + } + + bool getOffsetHdr(float* offset_hdr, bool* present) { + if (state == Done) { + *present = offsetHdrFound; + stringstream ss(offsetHdrStr); + float val; + if (ss >> val) { + *offset_hdr = val; + return true; + } else { + return false; + } + } else { + return false; + } + } + + bool getHdrCapacityMin(float* hdr_capacity_min, bool* present) { + if (state == Done) { + *present = hdrCapacityMinFound; + stringstream ss(hdrCapacityMinStr); + float val; + if (ss >> val) { + *hdr_capacity_min = exp2(val); + return true; + } else { + return false; + } + } else { + return false; + } + } + + bool getHdrCapacityMax(float* hdr_capacity_max, bool* present) { + if (state == Done) { + *present = hdrCapacityMaxFound; + stringstream ss(hdrCapacityMaxStr); + float val; + if (ss >> val) { + *hdr_capacity_max = exp2(val); + return true; + } else { + return false; + } + } else { + return false; + } + } + + bool getBaseRenditionIsHdr(bool* base_rendition_is_hdr, bool* present) { + if (state == Done) { + *present = baseRenditionIsHdrFound; + if (!baseRenditionIsHdrStr.compare("False")) { + *base_rendition_is_hdr = false; + return true; + } else if (!baseRenditionIsHdrStr.compare("True")) { + *base_rendition_is_hdr = true; + return true; + } else { + return false; + } + } else { + return false; + } + } + + private: + static const string containerName; + + static const string versionAttrName; + string versionStr; + bool versionFound; + static const string maxContentBoostAttrName; + string maxContentBoostStr; + bool maxContentBoostFound; + static const string minContentBoostAttrName; + string minContentBoostStr; + bool minContentBoostFound; + static const string gammaAttrName; + string gammaStr; + bool gammaFound; + static const string offsetSdrAttrName; + string offsetSdrStr; + bool offsetSdrFound; + static const string offsetHdrAttrName; + string offsetHdrStr; + bool offsetHdrFound; + static const string hdrCapacityMinAttrName; + string hdrCapacityMinStr; + bool hdrCapacityMinFound; + static const string hdrCapacityMaxAttrName; + string hdrCapacityMaxStr; + bool hdrCapacityMaxFound; + static const string baseRenditionIsHdrAttrName; + string baseRenditionIsHdrStr; + bool baseRenditionIsHdrFound; + + string lastAttributeName; + ParseState state; +}; + +// GContainer XMP constants - URI and namespace prefix +const string kContainerUri = "http://ns.google.com/photos/1.0/container/"; +const string kContainerPrefix = "Container"; + +// GContainer XMP constants - element and attribute names +const string kConDirectory = Name(kContainerPrefix, "Directory"); +const string kConItem = Name(kContainerPrefix, "Item"); + +// GContainer XMP constants - names for XMP handlers +const string XMPXmlHandler::containerName = "rdf:Description"; +// Item XMP constants - URI and namespace prefix +const string kItemUri = "http://ns.google.com/photos/1.0/container/item/"; +const string kItemPrefix = "Item"; + +// Item XMP constants - element and attribute names +const string kItemLength = Name(kItemPrefix, "Length"); +const string kItemMime = Name(kItemPrefix, "Mime"); +const string kItemSemantic = Name(kItemPrefix, "Semantic"); + +// Item XMP constants - element and attribute values +const string kSemanticPrimary = "Primary"; +const string kSemanticGainMap = "GainMap"; +const string kMimeImageJpeg = "image/jpeg"; + +// GainMap XMP constants - URI and namespace prefix +const string kGainMapUri = "http://ns.adobe.com/hdr-gain-map/1.0/"; +const string kGainMapPrefix = "hdrgm"; + +// GainMap XMP constants - element and attribute names +const string kMapVersion = Name(kGainMapPrefix, "Version"); +const string kMapGainMapMin = Name(kGainMapPrefix, "GainMapMin"); +const string kMapGainMapMax = Name(kGainMapPrefix, "GainMapMax"); +const string kMapGamma = Name(kGainMapPrefix, "Gamma"); +const string kMapOffsetSdr = Name(kGainMapPrefix, "OffsetSDR"); +const string kMapOffsetHdr = Name(kGainMapPrefix, "OffsetHDR"); +const string kMapHDRCapacityMin = Name(kGainMapPrefix, "HDRCapacityMin"); +const string kMapHDRCapacityMax = Name(kGainMapPrefix, "HDRCapacityMax"); +const string kMapBaseRenditionIsHDR = Name(kGainMapPrefix, "BaseRenditionIsHDR"); + +// GainMap XMP constants - names for XMP handlers +const string XMPXmlHandler::versionAttrName = kMapVersion; +const string XMPXmlHandler::minContentBoostAttrName = kMapGainMapMin; +const string XMPXmlHandler::maxContentBoostAttrName = kMapGainMapMax; +const string XMPXmlHandler::gammaAttrName = kMapGamma; +const string XMPXmlHandler::offsetSdrAttrName = kMapOffsetSdr; +const string XMPXmlHandler::offsetHdrAttrName = kMapOffsetHdr; +const string XMPXmlHandler::hdrCapacityMinAttrName = kMapHDRCapacityMin; +const string XMPXmlHandler::hdrCapacityMaxAttrName = kMapHDRCapacityMax; +const string XMPXmlHandler::baseRenditionIsHdrAttrName = kMapBaseRenditionIsHDR; + +bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, ultrahdr_metadata_struct* metadata) { + string nameSpace = "http://ns.adobe.com/xap/1.0/\0"; + + if (xmp_size < nameSpace.size() + 2) { + // Data too short + return false; + } + + if (strncmp(reinterpret_cast<char*>(xmp_data), nameSpace.c_str(), nameSpace.size())) { + // Not correct namespace + return false; + } + + // Position the pointers to the start of XMP XML portion + xmp_data += nameSpace.size() + 1; + xmp_size -= nameSpace.size() + 1; + XMPXmlHandler handler; + + // We need to remove tail data until the closing tag. Otherwise parser will throw an error. + while (xmp_data[xmp_size - 1] != '>' && xmp_size > 1) { + xmp_size--; + } + + string str(reinterpret_cast<const char*>(xmp_data), xmp_size); + MessageHandler msg_handler; + unique_ptr<XmlRule> rule(new XmlElementRule); + XmlReader reader(&handler, &msg_handler); + reader.StartParse(std::move(rule)); + reader.Parse(str); + reader.FinishParse(); + if (reader.HasErrors()) { + // Parse error + return false; + } + + // Apply default values to any not-present fields, except for Version, + // maxContentBoost, and hdrCapacityMax, which are required. Return false if + // we encounter a present field that couldn't be parsed, since this + // indicates it is invalid (eg. string where there should be a float). + bool present = false; + if (!handler.getVersion(&metadata->version, &present) || !present) { + return false; + } + if (!handler.getMaxContentBoost(&metadata->maxContentBoost, &present) || !present) { + return false; + } + if (!handler.getHdrCapacityMax(&metadata->hdrCapacityMax, &present) || !present) { + return false; + } + if (!handler.getMinContentBoost(&metadata->minContentBoost, &present)) { + if (present) return false; + metadata->minContentBoost = 1.0f; + } + if (!handler.getGamma(&metadata->gamma, &present)) { + if (present) return false; + metadata->gamma = 1.0f; + } + if (!handler.getOffsetSdr(&metadata->offsetSdr, &present)) { + if (present) return false; + metadata->offsetSdr = 1.0f / 64.0f; + } + if (!handler.getOffsetHdr(&metadata->offsetHdr, &present)) { + if (present) return false; + metadata->offsetHdr = 1.0f / 64.0f; + } + if (!handler.getHdrCapacityMin(&metadata->hdrCapacityMin, &present)) { + if (present) return false; + metadata->hdrCapacityMin = 1.0f; + } + + bool base_rendition_is_hdr; + if (!handler.getBaseRenditionIsHdr(&base_rendition_is_hdr, &present)) { + if (present) return false; + base_rendition_is_hdr = false; + } + if (base_rendition_is_hdr) { + ALOGE("Base rendition of HDR is not supported!"); + return false; + } + + return true; +} + +string generateXmpForPrimaryImage(int secondary_image_length, ultrahdr_metadata_struct& metadata) { + const vector<string> kConDirSeq({kConDirectory, string("rdf:Seq")}); + const vector<string> kLiItem({string("rdf:li"), kConItem}); + + std::stringstream ss; + photos_editing_formats::image_io::XmlWriter writer(ss); + writer.StartWritingElement("x:xmpmeta"); + writer.WriteXmlns("x", "adobe:ns:meta/"); + writer.WriteAttributeNameAndValue("x:xmptk", "Adobe XMP Core 5.1.2"); + writer.StartWritingElement("rdf:RDF"); + writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"); + writer.StartWritingElement("rdf:Description"); + writer.WriteXmlns(kContainerPrefix, kContainerUri); + writer.WriteXmlns(kItemPrefix, kItemUri); + writer.WriteXmlns(kGainMapPrefix, kGainMapUri); + writer.WriteAttributeNameAndValue(kMapVersion, metadata.version); + + writer.StartWritingElements(kConDirSeq); + + size_t item_depth = writer.StartWritingElement("rdf:li"); + writer.WriteAttributeNameAndValue("rdf:parseType", "Resource"); + writer.StartWritingElement(kConItem); + writer.WriteAttributeNameAndValue(kItemSemantic, kSemanticPrimary); + writer.WriteAttributeNameAndValue(kItemMime, kMimeImageJpeg); + writer.FinishWritingElementsToDepth(item_depth); + + writer.StartWritingElement("rdf:li"); + writer.WriteAttributeNameAndValue("rdf:parseType", "Resource"); + writer.StartWritingElement(kConItem); + writer.WriteAttributeNameAndValue(kItemSemantic, kSemanticGainMap); + writer.WriteAttributeNameAndValue(kItemMime, kMimeImageJpeg); + writer.WriteAttributeNameAndValue(kItemLength, secondary_image_length); + + writer.FinishWriting(); + + return ss.str(); +} + +string generateXmpForSecondaryImage(ultrahdr_metadata_struct& metadata) { + const vector<string> kConDirSeq({kConDirectory, string("rdf:Seq")}); + + std::stringstream ss; + photos_editing_formats::image_io::XmlWriter writer(ss); + writer.StartWritingElement("x:xmpmeta"); + writer.WriteXmlns("x", "adobe:ns:meta/"); + writer.WriteAttributeNameAndValue("x:xmptk", "Adobe XMP Core 5.1.2"); + writer.StartWritingElement("rdf:RDF"); + writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"); + writer.StartWritingElement("rdf:Description"); + writer.WriteXmlns(kGainMapPrefix, kGainMapUri); + writer.WriteAttributeNameAndValue(kMapVersion, metadata.version); + writer.WriteAttributeNameAndValue(kMapGainMapMin, log2(metadata.minContentBoost)); + writer.WriteAttributeNameAndValue(kMapGainMapMax, log2(metadata.maxContentBoost)); + writer.WriteAttributeNameAndValue(kMapGamma, metadata.gamma); + writer.WriteAttributeNameAndValue(kMapOffsetSdr, metadata.offsetSdr); + writer.WriteAttributeNameAndValue(kMapOffsetHdr, metadata.offsetHdr); + writer.WriteAttributeNameAndValue(kMapHDRCapacityMin, log2(metadata.hdrCapacityMin)); + writer.WriteAttributeNameAndValue(kMapHDRCapacityMax, log2(metadata.hdrCapacityMax)); + writer.WriteAttributeNameAndValue(kMapBaseRenditionIsHDR, "False"); + writer.FinishWriting(); + + return ss.str(); +} + +} // namespace ultrahdr diff --git a/include/ultrahdr/jpegrutils.h b/lib/jpegrutils.h index cdba834..c3fa8ab 100644 --- a/include/ultrahdr/jpegrutils.h +++ b/lib/jpegrutils.h @@ -17,19 +17,17 @@ #ifndef ULTRAHDR_JPEGRUTILS_H #define ULTRAHDR_JPEGRUTILS_H -#include "ultrahdr/ultrahdr.h" -#include "ultrahdr/jpegr.h" +#include "ultrahdr.h" +#include "jpegr.h" namespace ultrahdr { static constexpr uint32_t EndianSwap32(uint32_t value) { - return ((value & 0xFF) << 24) | - ((value & 0xFF00) << 8) | - ((value & 0xFF0000) >> 8) | - (value >> 24); + return ((value & 0xFF) << 24) | ((value & 0xFF00) << 8) | ((value & 0xFF0000) >> 8) | + (value >> 24); } static inline uint16_t EndianSwap16(uint16_t value) { - return static_cast<uint16_t>((value >> 8) | ((value & 0xFF) << 8)); + return static_cast<uint16_t>((value >> 8) | ((value & 0xFF) << 8)); } struct ultrahdr_metadata_struct; @@ -37,22 +35,22 @@ struct ultrahdr_metadata_struct; * Mutable data structure. Holds information for metadata. */ class DataStruct { -private: - void* data; - int writePos; - int length; + private: + void* data; + int writePos; + int length; -public: - DataStruct(int s); - ~DataStruct(); + public: + DataStruct(int s); + ~DataStruct(); - void* getData(); - int getLength(); - int getBytesWritten(); - bool write8(uint8_t value); - bool write16(uint16_t value); - bool write32(uint32_t value); - bool write(const void* src, int size); + void* getData(); + int getLength(); + int getBytesWritten(); + bool write8(uint8_t value); + bool write16(uint16_t value); + bool write32(uint32_t value); + bool write(const void* src, int size); }; /* @@ -64,8 +62,7 @@ public: * @param position cursor in desitination where the data is to be written. * @return status of succeed or error code. */ -status_t Write(jr_compressed_ptr destination, const void* source, size_t length, int &position); - +status_t Write(jr_compressed_ptr destination, const void* source, size_t length, int& position); /* * Parses XMP packet and fills metadata with data from XMP @@ -74,7 +71,7 @@ status_t Write(jr_compressed_ptr destination, const void* source, size_t length, * @param xmp_size size of XMP packet * @param metadata place to store HDR metadata values * @return true if metadata is successfully retrieved, false otherwise -*/ + */ bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, ultrahdr_metadata_struct* metadata); /* @@ -149,7 +146,7 @@ std::string generateXmpForPrimaryImage(int secondary_image_length, * @param metadata JPEG/R metadata to encode as XMP * @return XMP metadata in type of string */ - std::string generateXmpForSecondaryImage(ultrahdr_metadata_struct& metadata); +std::string generateXmpForSecondaryImage(ultrahdr_metadata_struct& metadata); } // namespace ultrahdr -#endif //ULTRAHDR_JPEGRUTILS_H +#endif // ULTRAHDR_JPEGRUTILS_H diff --git a/lib/multipictureformat.cpp b/lib/multipictureformat.cpp new file mode 100644 index 0000000..5e38175 --- /dev/null +++ b/lib/multipictureformat.cpp @@ -0,0 +1,92 @@ +/* + * Copyright 2023 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 "multipictureformat.h" + +namespace ultrahdr { +size_t calculateMpfSize() { + return sizeof(kMpfSig) + // Signature + kMpEndianSize + // Endianness + sizeof(uint32_t) + // Index IFD Offset + sizeof(uint16_t) + // Tag count + kTagSerializedCount * kTagSize + // 3 tags at 12 bytes each + sizeof(uint32_t) + // Attribute IFD offset + kNumPictures * kMPEntrySize; // MP Entries for each image +} + +std::shared_ptr<DataStruct> generateMpf(int primary_image_size, int primary_image_offset, + int secondary_image_size, int secondary_image_offset) { + size_t mpf_size = calculateMpfSize(); + std::shared_ptr<DataStruct> dataStruct = std::make_shared<DataStruct>(mpf_size); + + dataStruct->write(static_cast<const void*>(kMpfSig), sizeof(kMpfSig)); +#if USE_BIG_ENDIAN + dataStruct->write(static_cast<const void*>(kMpBigEndian), kMpEndianSize); +#else + dataStruct->write(static_cast<const void*>(kMpLittleEndian), kMpEndianSize); +#endif + + // Set the Index IFD offset be the position after the endianness value and this offset. + constexpr uint32_t indexIfdOffset = static_cast<uint16_t>(kMpEndianSize + sizeof(kMpfSig)); + dataStruct->write32(Endian_SwapBE32(indexIfdOffset)); + + // We will write 3 tags (version, number of images, MP entries). + dataStruct->write16(Endian_SwapBE16(kTagSerializedCount)); + + // Write the version tag. + dataStruct->write16(Endian_SwapBE16(kVersionTag)); + dataStruct->write16(Endian_SwapBE16(kVersionType)); + dataStruct->write32(Endian_SwapBE32(kVersionCount)); + dataStruct->write(kVersionExpected, kVersionSize); + + // Write the number of images. + dataStruct->write16(Endian_SwapBE16(kNumberOfImagesTag)); + dataStruct->write16(Endian_SwapBE16(kNumberOfImagesType)); + dataStruct->write32(Endian_SwapBE32(kNumberOfImagesCount)); + dataStruct->write32(Endian_SwapBE32(kNumPictures)); + + // Write the MP entries. + dataStruct->write16(Endian_SwapBE16(kMPEntryTag)); + dataStruct->write16(Endian_SwapBE16(kMPEntryType)); + dataStruct->write32(Endian_SwapBE32(kMPEntrySize * kNumPictures)); + const uint32_t mpEntryOffset = + static_cast<uint32_t>(dataStruct->getBytesWritten() - // The bytes written so far + sizeof(kMpfSig) + // Excluding the MPF signature + sizeof(uint32_t) + // The 4 bytes for this offset + sizeof(uint32_t)); // The 4 bytes for the attribute IFD offset. + dataStruct->write32(Endian_SwapBE32(mpEntryOffset)); + + // Write the attribute IFD offset (zero because we don't write it). + dataStruct->write32(0); + + // Write the MP entries for primary image + dataStruct->write32(Endian_SwapBE32(kMPEntryAttributeFormatJpeg | kMPEntryAttributeTypePrimary)); + dataStruct->write32(Endian_SwapBE32(primary_image_size)); + dataStruct->write32(Endian_SwapBE32(primary_image_offset)); + dataStruct->write16(0); + dataStruct->write16(0); + + // Write the MP entries for secondary image + dataStruct->write32(Endian_SwapBE32(kMPEntryAttributeFormatJpeg)); + dataStruct->write32(Endian_SwapBE32(secondary_image_size)); + dataStruct->write32(Endian_SwapBE32(secondary_image_offset)); + dataStruct->write16(0); + dataStruct->write16(0); + + return dataStruct; +} + +} // namespace ultrahdr diff --git a/include/ultrahdr/multipictureformat.h b/lib/multipictureformat.h index 79c494f..06ad6ae 100644 --- a/include/ultrahdr/multipictureformat.h +++ b/lib/multipictureformat.h @@ -24,17 +24,17 @@ #endif #if USE_BIG_ENDIAN_IN_MPF - #define Endian_SwapBE32(n) EndianSwap32(n) - #define Endian_SwapBE16(n) EndianSwap16(n) +#define Endian_SwapBE32(n) EndianSwap32(n) +#define Endian_SwapBE16(n) EndianSwap16(n) #else - #define Endian_SwapBE32(n) (n) - #define Endian_SwapBE16(n) (n) +#define Endian_SwapBE32(n) (n) +#define Endian_SwapBE16(n) (n) #endif -#include "ultrahdr/ultrahdr.h" -#include "ultrahdr/jpegr.h" -#include "ultrahdr/gainmapmath.h" -#include "ultrahdr/jpegrutils.h" +#include "ultrahdr.h" +#include "jpegr.h" +#include "gainmapmath.h" +#include "jpegrutils.h" namespace ultrahdr { @@ -73,4 +73,4 @@ std::shared_ptr<DataStruct> generateMpf(int primary_image_size, int primary_imag } // namespace ultrahdr -#endif //ULTRAHDR_MULTIPICTUREFORMAT_H +#endif // ULTRAHDR_MULTIPICTUREFORMAT_H diff --git a/include/ultrahdr/ultrahdr.h b/lib/ultrahdr.h index 22829ff..fa69d57 100644 --- a/include/ultrahdr/ultrahdr.h +++ b/lib/ultrahdr.h @@ -43,10 +43,10 @@ typedef enum { // Target output formats for decoder typedef enum { ULTRAHDR_OUTPUT_UNSPECIFIED = -1, - ULTRAHDR_OUTPUT_SDR, // SDR in RGBA_8888 color format - ULTRAHDR_OUTPUT_HDR_LINEAR, // HDR in F16 color format (linear) - ULTRAHDR_OUTPUT_HDR_PQ, // HDR in RGBA_1010102 color format (PQ transfer function) - ULTRAHDR_OUTPUT_HDR_HLG, // HDR in RGBA_1010102 color format (HLG transfer function) + ULTRAHDR_OUTPUT_SDR, // SDR in RGBA_8888 color format + ULTRAHDR_OUTPUT_HDR_LINEAR, // HDR in F16 color format (linear) + ULTRAHDR_OUTPUT_HDR_PQ, // HDR in RGBA_1010102 color format (PQ transfer function) + ULTRAHDR_OUTPUT_HDR_HLG, // HDR in RGBA_1010102 color format (HLG transfer function) ULTRAHDR_OUTPUT_MAX = ULTRAHDR_OUTPUT_HDR_HLG, } ultrahdr_output_format; @@ -79,4 +79,4 @@ typedef struct ultrahdr_metadata_struct* ultrahdr_metadata_ptr; } // namespace ultrahdr -#endif //ULTRAHDR_ULTRAHDR_H +#endif // ULTRAHDR_ULTRAHDR_H diff --git a/lib/ultrahdrcommon.h b/lib/ultrahdrcommon.h new file mode 100644 index 0000000..ba3a3b8 --- /dev/null +++ b/lib/ultrahdrcommon.h @@ -0,0 +1,64 @@ +/* + * Copyright 2023 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_ULTRAHDRCOMMON_H +#define ULTRAHDR_ULTRAHDRCOMMON_H + +//#define LOG_NDEBUG 0 + +#ifdef __ANDROID__ +#include "log/log.h" +#else +#ifdef LOG_NDEBUG +#include <cstdio> + +#define ALOGD(...) \ + do { \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n"); \ + } while (0) +#define ALOGE(...) \ + do { \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n"); \ + } while (0) +#define ALOGI(...) \ + do { \ + fprintf(stdout, __VA_ARGS__); \ + fprintf(stdout, "\n"); \ + } while (0) +#define ALOGV(...) \ + do { \ + fprintf(stdout, __VA_ARGS__); \ + fprintf(stdout, "\n"); \ + } while (0) +#define ALOGW(...) \ + do { \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n"); \ + } while (0) +#else +#define ALOGD(...) ((void)0) +#define ALOGE(...) ((void)0) +#define ALOGI(...) ((void)0) +#define ALOGV(...) ((void)0) +#define ALOGW(...) ((void)0) +#endif +#endif + +#define ALIGNM(x, m) ((((x) + ((m)-1)) / (m)) * (m)) + +#endif // ULTRAHDR_ULTRAHDRCOMMON_H diff --git a/multipictureformat.cpp b/multipictureformat.cpp deleted file mode 100644 index 153c972..0000000 --- a/multipictureformat.cpp +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2023 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 "ultrahdr/multipictureformat.h" - -namespace ultrahdr { -size_t calculateMpfSize() { - return sizeof(kMpfSig) + // Signature - kMpEndianSize + // Endianness - sizeof(uint32_t) + // Index IFD Offset - sizeof(uint16_t) + // Tag count - kTagSerializedCount * kTagSize + // 3 tags at 12 bytes each - sizeof(uint32_t) + // Attribute IFD offset - kNumPictures * kMPEntrySize; // MP Entries for each image -} - -std::shared_ptr<DataStruct> generateMpf(int primary_image_size, int primary_image_offset, - int secondary_image_size, int secondary_image_offset) { - size_t mpf_size = calculateMpfSize(); - std::shared_ptr<DataStruct> dataStruct = std::make_shared<DataStruct>(mpf_size); - - dataStruct->write(static_cast<const void*>(kMpfSig), sizeof(kMpfSig)); -#if USE_BIG_ENDIAN - dataStruct->write(static_cast<const void*>(kMpBigEndian), kMpEndianSize); -#else - dataStruct->write(static_cast<const void*>(kMpLittleEndian), kMpEndianSize); -#endif - - // Set the Index IFD offset be the position after the endianness value and this offset. - constexpr uint32_t indexIfdOffset = - static_cast<uint16_t>(kMpEndianSize + sizeof(kMpfSig)); - dataStruct->write32(Endian_SwapBE32(indexIfdOffset)); - - // We will write 3 tags (version, number of images, MP entries). - dataStruct->write16(Endian_SwapBE16(kTagSerializedCount)); - - // Write the version tag. - dataStruct->write16(Endian_SwapBE16(kVersionTag)); - dataStruct->write16(Endian_SwapBE16(kVersionType)); - dataStruct->write32(Endian_SwapBE32(kVersionCount)); - dataStruct->write(kVersionExpected, kVersionSize); - - // Write the number of images. - dataStruct->write16(Endian_SwapBE16(kNumberOfImagesTag)); - dataStruct->write16(Endian_SwapBE16(kNumberOfImagesType)); - dataStruct->write32(Endian_SwapBE32(kNumberOfImagesCount)); - dataStruct->write32(Endian_SwapBE32(kNumPictures)); - - // Write the MP entries. - dataStruct->write16(Endian_SwapBE16(kMPEntryTag)); - dataStruct->write16(Endian_SwapBE16(kMPEntryType)); - dataStruct->write32(Endian_SwapBE32(kMPEntrySize * kNumPictures)); - const uint32_t mpEntryOffset = - static_cast<uint32_t>(dataStruct->getBytesWritten() - // The bytes written so far - sizeof(kMpfSig) + // Excluding the MPF signature - sizeof(uint32_t) + // The 4 bytes for this offset - sizeof(uint32_t)); // The 4 bytes for the attribute IFD offset. - dataStruct->write32(Endian_SwapBE32(mpEntryOffset)); - - // Write the attribute IFD offset (zero because we don't write it). - dataStruct->write32(0); - - // Write the MP entries for primary image - dataStruct->write32( - Endian_SwapBE32(kMPEntryAttributeFormatJpeg | kMPEntryAttributeTypePrimary)); - dataStruct->write32(Endian_SwapBE32(primary_image_size)); - dataStruct->write32(Endian_SwapBE32(primary_image_offset)); - dataStruct->write16(0); - dataStruct->write16(0); - - // Write the MP entries for secondary image - dataStruct->write32(Endian_SwapBE32(kMPEntryAttributeFormatJpeg)); - dataStruct->write32(Endian_SwapBE32(secondary_image_size)); - dataStruct->write32(Endian_SwapBE32(secondary_image_offset)); - dataStruct->write16(0); - dataStruct->write16(0); - - return dataStruct; -} - -} // namespace ultrahdr diff --git a/tests/Android.bp b/tests/Android.bp index e175155..8fe1621 100644 --- a/tests/Android.bp +++ b/tests/Android.bp @@ -22,27 +22,6 @@ package { } cc_test { - name: "ultrahdr_sample_app", - host_supported: true, - gtest: false, - test_suites: ["device-tests"], - srcs: [ - "ultrahdr_app.cpp", - ], - shared_libs: [ - "libbase", - "libimage_io", - "libjpeg", - "liblog", - ], - static_libs: [ - "libjpegdecoder", - "libjpegencoder", - "libultrahdr", - ], -} - -cc_test { name: "ultrahdr_unit_test", test_suites: ["device-tests"], srcs: [ diff --git a/third_party/data/LICENSE b/tests/data/LICENSE index 27dfefa..27dfefa 100644 --- a/third_party/data/LICENSE +++ b/tests/data/LICENSE diff --git a/third_party/data/jpeg_image.jpg b/tests/data/jpeg_image.jpg Binary files differindex e285742..e285742 100644 --- a/third_party/data/jpeg_image.jpg +++ b/tests/data/jpeg_image.jpg diff --git a/third_party/data/minnie-318x240.yu12 b/tests/data/minnie-318x240.yu12 index 7b2fc71..7b2fc71 100644 --- a/third_party/data/minnie-318x240.yu12 +++ b/tests/data/minnie-318x240.yu12 diff --git a/third_party/data/minnie-320x240-y.jpg b/tests/data/minnie-320x240-y.jpg Binary files differindex 20b5a2c..20b5a2c 100644 --- a/third_party/data/minnie-320x240-y.jpg +++ b/tests/data/minnie-320x240-y.jpg diff --git a/third_party/data/minnie-320x240-yuv-icc.jpg b/tests/data/minnie-320x240-yuv-icc.jpg Binary files differindex c7f4538..c7f4538 100644 --- a/third_party/data/minnie-320x240-yuv-icc.jpg +++ b/tests/data/minnie-320x240-yuv-icc.jpg diff --git a/third_party/data/minnie-320x240-yuv.jpg b/tests/data/minnie-320x240-yuv.jpg Binary files differindex 41300f4..41300f4 100644 --- a/third_party/data/minnie-320x240-yuv.jpg +++ b/tests/data/minnie-320x240-yuv.jpg diff --git a/third_party/data/minnie-320x240.y b/tests/data/minnie-320x240.y index f9d8371..f9d8371 100644 --- a/third_party/data/minnie-320x240.y +++ b/tests/data/minnie-320x240.y diff --git a/third_party/data/minnie-320x240.yu12 b/tests/data/minnie-320x240.yu12 index 0d66f53..0d66f53 100644 --- a/third_party/data/minnie-320x240.yu12 +++ b/tests/data/minnie-320x240.yu12 diff --git a/third_party/data/raw_p010_image.p010 b/tests/data/raw_p010_image.p010 Binary files differindex 01673bf..01673bf 100644 --- a/third_party/data/raw_p010_image.p010 +++ b/tests/data/raw_p010_image.p010 diff --git a/third_party/data/raw_yuv420_image.yuv420 b/tests/data/raw_yuv420_image.yuv420 index c043da6..c043da6 100644 --- a/third_party/data/raw_yuv420_image.yuv420 +++ b/tests/data/raw_yuv420_image.yuv420 diff --git a/tests/gainmapmath_test.cpp b/tests/gainmapmath_test.cpp index e73419c..b474bd6 100644 --- a/tests/gainmapmath_test.cpp +++ b/tests/gainmapmath_test.cpp @@ -17,12 +17,12 @@ #include <gtest/gtest.h> #include <gmock/gmock.h> -#include "ultrahdr/gainmapmath.h" +#include "gainmapmath.h" namespace ultrahdr { class GainMapMathTest : public testing::Test { -public: + public: GainMapMathTest(); ~GainMapMathTest(); @@ -31,50 +31,47 @@ public: 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) / 255.0f, (static_cast<float>(u) - 128.0f) / 255.0f, + (static_cast<float>(v) - 128.0f) / 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.0f) / 876.0f, (static_cast<float>(u) - 64.0f) / 896.0f - 0.5f, + (static_cast<float>(v) - 64.0f) / 896.0f - 0.5f}}}; } - float Map(uint8_t e) { - return static_cast<float>(e) / 255.0f; - } + float Map(uint8_t e) { return static_cast<float>(e) / 255.0f; } Color ColorMin(Color e1, Color e2) { - return {{{ fminf(e1.r, e2.r), fminf(e1.g, e2.g), fminf(e1.b, e2.b) }}}; + return {{{fminf(e1.r, e2.r), fminf(e1.g, e2.g), fminf(e1.b, e2.b)}}}; } Color ColorMax(Color e1, Color e2) { - return {{{ fmaxf(e1.r, e2.r), fmaxf(e1.g, e2.g), fmaxf(e1.b, e2.b) }}}; + return {{{fmaxf(e1.r, e2.r), fmaxf(e1.g, e2.g), fmaxf(e1.b, e2.b)}}}; } - Color RgbBlack() { return {{{ 0.0f, 0.0f, 0.0f }}}; } - Color RgbWhite() { return {{{ 1.0f, 1.0f, 1.0f }}}; } + Color RgbBlack() { return {{{0.0f, 0.0f, 0.0f}}}; } + Color RgbWhite() { return {{{1.0f, 1.0f, 1.0f}}}; } - Color RgbRed() { return {{{ 1.0f, 0.0f, 0.0f }}}; } - Color RgbGreen() { return {{{ 0.0f, 1.0f, 0.0f }}}; } - Color RgbBlue() { return {{{ 0.0f, 0.0f, 1.0f }}}; } + Color RgbRed() { return {{{1.0f, 0.0f, 0.0f}}}; } + Color RgbGreen() { return {{{0.0f, 1.0f, 0.0f}}}; } + Color RgbBlue() { return {{{0.0f, 0.0f, 1.0f}}}; } - Color YuvBlack() { return {{{ 0.0f, 0.0f, 0.0f }}}; } - Color YuvWhite() { return {{{ 1.0f, 0.0f, 0.0f }}}; } + Color YuvBlack() { return {{{0.0f, 0.0f, 0.0f}}}; } + Color YuvWhite() { return {{{1.0f, 0.0f, 0.0f}}}; } - Color SrgbYuvRed() { return {{{ 0.2126f, -0.11457f, 0.5f }}}; } - Color SrgbYuvGreen() { return {{{ 0.7152f, -0.38543f, -0.45415f }}}; } - Color SrgbYuvBlue() { return {{{ 0.0722f, 0.5f, -0.04585f }}}; } + Color SrgbYuvRed() { return {{{0.2126f, -0.11457f, 0.5f}}}; } + Color SrgbYuvGreen() { return {{{0.7152f, -0.38543f, -0.45415f}}}; } + Color SrgbYuvBlue() { return {{{0.0722f, 0.5f, -0.04585f}}}; } - Color P3YuvRed() { return {{{ 0.299f, -0.16874f, 0.5f }}}; } - Color P3YuvGreen() { return {{{ 0.587f, -0.33126f, -0.41869f }}}; } - Color P3YuvBlue() { return {{{ 0.114f, 0.5f, -0.08131f }}}; } + Color P3YuvRed() { return {{{0.299f, -0.16874f, 0.5f}}}; } + Color P3YuvGreen() { return {{{0.587f, -0.33126f, -0.41869f}}}; } + Color P3YuvBlue() { return {{{0.114f, 0.5f, -0.08131f}}}; } - Color Bt2100YuvRed() { return {{{ 0.2627f, -0.13963f, 0.5f }}}; } - Color Bt2100YuvGreen() { return {{{ 0.6780f, -0.36037f, -0.45979f }}}; } - Color Bt2100YuvBlue() { return {{{ 0.0593f, 0.5f, -0.04021f }}}; } + Color Bt2100YuvRed() { return {{{0.2627f, -0.13963f, 0.5f}}}; } + Color Bt2100YuvGreen() { return {{{0.6780f, -0.36037f, -0.45979f}}}; } + Color Bt2100YuvBlue() { return {{{0.0593f, 0.5f, -0.04021f}}}; } float SrgbYuvToLuminance(Color yuv_gamma, ColorCalculationFn luminanceFn) { Color rgb_gamma = srgbYuvToRgb(yuv_gamma); @@ -108,99 +105,168 @@ public: jpegr_uncompressed_struct Yuv420Image() { static uint8_t pixels[] = { - // Y - 0x00, 0x10, 0x20, 0x30, - 0x01, 0x11, 0x21, 0x31, - 0x02, 0x12, 0x22, 0x32, - 0x03, 0x13, 0x23, 0x33, - // U - 0xA0, 0xA1, - 0xA2, 0xA3, - // V - 0xB0, 0xB1, - 0xB2, 0xB3, + // Y + 0x00, + 0x10, + 0x20, + 0x30, + 0x01, + 0x11, + 0x21, + 0x31, + 0x02, + 0x12, + 0x22, + 0x32, + 0x03, + 0x13, + 0x23, + 0x33, + // U + 0xA0, + 0xA1, + 0xA2, + 0xA3, + // V + 0xB0, + 0xB1, + 0xB2, + 0xB3, }; - return { pixels, 4, 4, ULTRAHDR_COLORGAMUT_BT709, pixels + 16, 4, 2 }; + return {pixels, 4, 4, ULTRAHDR_COLORGAMUT_BT709, pixels + 16, 4, 2}; } Color (*Yuv420Colors())[4] { static Color colors[4][4] = { - { - Yuv420(0x00, 0xA0, 0xB0), Yuv420(0x10, 0xA0, 0xB0), - Yuv420(0x20, 0xA1, 0xB1), Yuv420(0x30, 0xA1, 0xB1), - }, { - Yuv420(0x01, 0xA0, 0xB0), Yuv420(0x11, 0xA0, 0xB0), - Yuv420(0x21, 0xA1, 0xB1), Yuv420(0x31, 0xA1, 0xB1), - }, { - Yuv420(0x02, 0xA2, 0xB2), Yuv420(0x12, 0xA2, 0xB2), - Yuv420(0x22, 0xA3, 0xB3), Yuv420(0x32, 0xA3, 0xB3), - }, { - Yuv420(0x03, 0xA2, 0xB2), Yuv420(0x13, 0xA2, 0xB2), - Yuv420(0x23, 0xA3, 0xB3), Yuv420(0x33, 0xA3, 0xB3), - }, + { + Yuv420(0x00, 0xA0, 0xB0), + Yuv420(0x10, 0xA0, 0xB0), + Yuv420(0x20, 0xA1, 0xB1), + Yuv420(0x30, 0xA1, 0xB1), + }, + { + Yuv420(0x01, 0xA0, 0xB0), + Yuv420(0x11, 0xA0, 0xB0), + Yuv420(0x21, 0xA1, 0xB1), + Yuv420(0x31, 0xA1, 0xB1), + }, + { + Yuv420(0x02, 0xA2, 0xB2), + Yuv420(0x12, 0xA2, 0xB2), + Yuv420(0x22, 0xA3, 0xB3), + Yuv420(0x32, 0xA3, 0xB3), + }, + { + Yuv420(0x03, 0xA2, 0xB2), + Yuv420(0x13, 0xA2, 0xB2), + Yuv420(0x23, 0xA3, 0xB3), + Yuv420(0x33, 0xA3, 0xB3), + }, }; return colors; } jpegr_uncompressed_struct P010Image() { static uint16_t pixels[] = { - // Y - 0x00 << 6, 0x10 << 6, 0x20 << 6, 0x30 << 6, - 0x01 << 6, 0x11 << 6, 0x21 << 6, 0x31 << 6, - 0x02 << 6, 0x12 << 6, 0x22 << 6, 0x32 << 6, - 0x03 << 6, 0x13 << 6, 0x23 << 6, 0x33 << 6, - // UV - 0xA0 << 6, 0xB0 << 6, 0xA1 << 6, 0xB1 << 6, - 0xA2 << 6, 0xB2 << 6, 0xA3 << 6, 0xB3 << 6, + // Y + 0x00 << 6, + 0x10 << 6, + 0x20 << 6, + 0x30 << 6, + 0x01 << 6, + 0x11 << 6, + 0x21 << 6, + 0x31 << 6, + 0x02 << 6, + 0x12 << 6, + 0x22 << 6, + 0x32 << 6, + 0x03 << 6, + 0x13 << 6, + 0x23 << 6, + 0x33 << 6, + // UV + 0xA0 << 6, + 0xB0 << 6, + 0xA1 << 6, + 0xB1 << 6, + 0xA2 << 6, + 0xB2 << 6, + 0xA3 << 6, + 0xB3 << 6, }; - return { pixels, 4, 4, ULTRAHDR_COLORGAMUT_BT709, pixels + 16, 4, 4 }; + return {pixels, 4, 4, ULTRAHDR_COLORGAMUT_BT709, pixels + 16, 4, 4}; } Color (*P010Colors())[4] { static Color colors[4][4] = { - { - P010(0x00, 0xA0, 0xB0), P010(0x10, 0xA0, 0xB0), - P010(0x20, 0xA1, 0xB1), P010(0x30, 0xA1, 0xB1), - }, { - P010(0x01, 0xA0, 0xB0), P010(0x11, 0xA0, 0xB0), - P010(0x21, 0xA1, 0xB1), P010(0x31, 0xA1, 0xB1), - }, { - P010(0x02, 0xA2, 0xB2), P010(0x12, 0xA2, 0xB2), - P010(0x22, 0xA3, 0xB3), P010(0x32, 0xA3, 0xB3), - }, { - P010(0x03, 0xA2, 0xB2), P010(0x13, 0xA2, 0xB2), - P010(0x23, 0xA3, 0xB3), P010(0x33, 0xA3, 0xB3), - }, + { + P010(0x00, 0xA0, 0xB0), + P010(0x10, 0xA0, 0xB0), + P010(0x20, 0xA1, 0xB1), + P010(0x30, 0xA1, 0xB1), + }, + { + P010(0x01, 0xA0, 0xB0), + P010(0x11, 0xA0, 0xB0), + P010(0x21, 0xA1, 0xB1), + P010(0x31, 0xA1, 0xB1), + }, + { + P010(0x02, 0xA2, 0xB2), + P010(0x12, 0xA2, 0xB2), + P010(0x22, 0xA3, 0xB3), + P010(0x32, 0xA3, 0xB3), + }, + { + P010(0x03, 0xA2, 0xB2), + P010(0x13, 0xA2, 0xB2), + P010(0x23, 0xA3, 0xB3), + P010(0x33, 0xA3, 0xB3), + }, }; return colors; } jpegr_uncompressed_struct MapImage() { static uint8_t pixels[] = { - 0x00, 0x10, 0x20, 0x30, - 0x01, 0x11, 0x21, 0x31, - 0x02, 0x12, 0x22, 0x32, - 0x03, 0x13, 0x23, 0x33, + 0x00, 0x10, 0x20, 0x30, 0x01, 0x11, 0x21, 0x31, + 0x02, 0x12, 0x22, 0x32, 0x03, 0x13, 0x23, 0x33, }; - return { pixels, 4, 4, ULTRAHDR_COLORGAMUT_UNSPECIFIED }; + return {pixels, 4, 4, ULTRAHDR_COLORGAMUT_UNSPECIFIED}; } float (*MapValues())[4] { static float values[4][4] = { - { - Map(0x00), Map(0x10), Map(0x20), Map(0x30), - }, { - Map(0x01), Map(0x11), Map(0x21), Map(0x31), - }, { - Map(0x02), Map(0x12), Map(0x22), Map(0x32), - }, { - Map(0x03), Map(0x13), Map(0x23), Map(0x33), - }, + { + Map(0x00), + Map(0x10), + Map(0x20), + Map(0x30), + }, + { + Map(0x01), + Map(0x11), + Map(0x21), + Map(0x31), + }, + { + Map(0x02), + Map(0x12), + Map(0x22), + Map(0x32), + }, + { + Map(0x03), + Map(0x13), + Map(0x23), + Map(0x33), + }, }; return values; } -protected: + protected: virtual void SetUp(); virtual void TearDown(); }; @@ -211,9 +277,9 @@ GainMapMathTest::~GainMapMathTest() {} void GainMapMathTest::SetUp() {} void GainMapMathTest::TearDown() {} -#define EXPECT_RGB_EQ(e1, e2) \ - EXPECT_FLOAT_EQ((e1).r, (e2).r); \ - EXPECT_FLOAT_EQ((e1).g, (e2).g); \ +#define EXPECT_RGB_EQ(e1, e2) \ + EXPECT_FLOAT_EQ((e1).r, (e2).r); \ + EXPECT_FLOAT_EQ((e1).g, (e2).g); \ EXPECT_FLOAT_EQ((e1).b, (e2).b) #define EXPECT_RGB_NEAR(e1, e2) \ @@ -226,9 +292,9 @@ void GainMapMathTest::TearDown() {} EXPECT_NEAR((e1).g, (e2).g, ComparisonEpsilon() * 10.0f); \ EXPECT_NEAR((e1).b, (e2).b, ComparisonEpsilon() * 10.0f) -#define EXPECT_YUV_EQ(e1, e2) \ - EXPECT_FLOAT_EQ((e1).y, (e2).y); \ - EXPECT_FLOAT_EQ((e1).u, (e2).u); \ +#define EXPECT_YUV_EQ(e1, e2) \ + EXPECT_FLOAT_EQ((e1).y, (e2).y); \ + EXPECT_FLOAT_EQ((e1).u, (e2).u); \ EXPECT_FLOAT_EQ((e1).v, (e2).v) #define EXPECT_YUV_NEAR(e1, e2) \ @@ -244,7 +310,7 @@ void GainMapMathTest::TearDown() {} // TODO: a bunch of these tests can be parameterized. TEST_F(GainMapMathTest, ColorConstruct) { - Color e1 = {{{ 0.1f, 0.2f, 0.3f }}}; + Color e1 = {{{0.1f, 0.2f, 0.3f}}}; EXPECT_FLOAT_EQ(e1.r, 0.1f); EXPECT_FLOAT_EQ(e1.g, 0.2f); @@ -256,7 +322,7 @@ TEST_F(GainMapMathTest, ColorConstruct) { } TEST_F(GainMapMathTest, ColorAddColor) { - Color e1 = {{{ 0.1f, 0.2f, 0.3f }}}; + Color e1 = {{{0.1f, 0.2f, 0.3f}}}; Color e2 = e1 + e1; EXPECT_FLOAT_EQ(e2.r, e1.r * 2.0f); @@ -270,7 +336,7 @@ TEST_F(GainMapMathTest, ColorAddColor) { } TEST_F(GainMapMathTest, ColorAddFloat) { - Color e1 = {{{ 0.1f, 0.2f, 0.3f }}}; + Color e1 = {{{0.1f, 0.2f, 0.3f}}}; Color e2 = e1 + 0.1f; EXPECT_FLOAT_EQ(e2.r, e1.r + 0.1f); @@ -284,7 +350,7 @@ TEST_F(GainMapMathTest, ColorAddFloat) { } TEST_F(GainMapMathTest, ColorSubtractColor) { - Color e1 = {{{ 0.1f, 0.2f, 0.3f }}}; + Color e1 = {{{0.1f, 0.2f, 0.3f}}}; Color e2 = e1 - e1; EXPECT_FLOAT_EQ(e2.r, 0.0f); @@ -298,7 +364,7 @@ TEST_F(GainMapMathTest, ColorSubtractColor) { } TEST_F(GainMapMathTest, ColorSubtractFloat) { - Color e1 = {{{ 0.1f, 0.2f, 0.3f }}}; + Color e1 = {{{0.1f, 0.2f, 0.3f}}}; Color e2 = e1 - 0.1f; EXPECT_FLOAT_EQ(e2.r, e1.r - 0.1f); @@ -312,7 +378,7 @@ TEST_F(GainMapMathTest, ColorSubtractFloat) { } TEST_F(GainMapMathTest, ColorMultiplyFloat) { - Color e1 = {{{ 0.1f, 0.2f, 0.3f }}}; + Color e1 = {{{0.1f, 0.2f, 0.3f}}}; Color e2 = e1 * 2.0f; EXPECT_FLOAT_EQ(e2.r, e1.r * 2.0f); @@ -326,7 +392,7 @@ TEST_F(GainMapMathTest, ColorMultiplyFloat) { } TEST_F(GainMapMathTest, ColorDivideFloat) { - Color e1 = {{{ 0.1f, 0.2f, 0.3f }}}; + Color e1 = {{{0.1f, 0.2f, 0.3f}}}; Color e2 = e1 / 2.0f; EXPECT_FLOAT_EQ(e2.r, e1.r / 2.0f); @@ -626,8 +692,8 @@ TEST_F(GainMapMathTest, Bt2100ToBt601YuvConversion) { } TEST_F(GainMapMathTest, TransformYuv420) { - ColorTransformFn transforms[] = { yuv709To601, yuv709To2100, yuv601To709, yuv601To2100, - yuv2100To709, yuv2100To601 }; + ColorTransformFn transforms[] = {yuv709To601, yuv709To2100, yuv601To709, + yuv601To2100, yuv2100To709, yuv2100To601}; for (const ColorTransformFn& transform : transforms) { jpegr_uncompressed_struct input = Yuv420Image(); @@ -655,10 +721,10 @@ TEST_F(GainMapMathTest, TransformYuv420) { } // modified pixels should be updated as intended by the transformYuv420 algorithm - Color in1 = getYuv420Pixel(&input, 2, 2); - Color in2 = getYuv420Pixel(&input, 3, 2); - Color in3 = getYuv420Pixel(&input, 2, 3); - Color in4 = getYuv420Pixel(&input, 3, 3); + Color in1 = getYuv420Pixel(&input, 2, 2); + Color in2 = getYuv420Pixel(&input, 3, 2); + Color in3 = getYuv420Pixel(&input, 2, 3); + Color in4 = getYuv420Pixel(&input, 3, 3); Color out1 = getYuv420Pixel(&output, 2, 2); Color out2 = getYuv420Pixel(&output, 3, 2); Color out3 = getYuv420Pixel(&output, 2, 3); @@ -690,8 +756,8 @@ TEST_F(GainMapMathTest, HlgOetf) { EXPECT_NEAR(hlgOetf(0.5f), 0.87164f, ComparisonEpsilon()); EXPECT_FLOAT_EQ(hlgOetf(1.0f), 1.0f); - Color e = {{{ 0.04167f, 0.08333f, 0.5f }}}; - Color e_gamma = {{{ 0.35357f, 0.5f, 0.87164f }}}; + Color e = {{{0.04167f, 0.08333f, 0.5f}}}; + Color e_gamma = {{{0.35357f, 0.5f, 0.87164f}}}; EXPECT_RGB_NEAR(hlgOetf(e), e_gamma); } @@ -702,8 +768,8 @@ TEST_F(GainMapMathTest, HlgInvOetf) { EXPECT_NEAR(hlgInvOetf(0.75f), 0.26496f, ComparisonEpsilon()); EXPECT_FLOAT_EQ(hlgInvOetf(1.0f), 1.0f); - Color e_gamma = {{{ 0.25f, 0.5f, 0.75f }}}; - Color e = {{{ 0.02083f, 0.08333f, 0.26496f }}}; + Color e_gamma = {{{0.25f, 0.5f, 0.75f}}}; + Color e = {{{0.02083f, 0.08333f, 0.26496f}}}; EXPECT_RGB_NEAR(hlgInvOetf(e_gamma), e); } @@ -722,8 +788,8 @@ TEST_F(GainMapMathTest, PqOetf) { EXPECT_NEAR(pqOetf(0.99f), 0.99895f, ComparisonEpsilon()); EXPECT_FLOAT_EQ(pqOetf(1.0f), 1.0f); - Color e = {{{ 0.01f, 0.5f, 0.99f }}}; - Color e_gamma = {{{ 0.50808f, 0.92655f, 0.99895f }}}; + Color e = {{{0.01f, 0.5f, 0.99f}}}; + Color e_gamma = {{{0.50808f, 0.92655f, 0.99895f}}}; EXPECT_RGB_NEAR(pqOetf(e), e_gamma); } @@ -734,50 +800,52 @@ TEST_F(GainMapMathTest, PqInvOetf) { EXPECT_NEAR(pqInvOetf(0.99f), 0.90903f, ComparisonEpsilon()); EXPECT_FLOAT_EQ(pqInvOetf(1.0f), 1.0f); - Color e_gamma = {{{ 0.01f, 0.5f, 0.99f }}}; - Color e = {{{ 2.31017e-7f, 0.00922f, 0.90903f }}}; + Color e_gamma = {{{0.01f, 0.5f, 0.99f}}}; + Color e = {{{2.31017e-7f, 0.00922f, 0.90903f}}}; EXPECT_RGB_NEAR(pqInvOetf(e_gamma), e); } TEST_F(GainMapMathTest, PqInvOetfLUT) { - for (size_t idx = 0; idx < kPqInvOETFNumEntries; idx++) { - float value = static_cast<float>(idx) / static_cast<float>(kPqInvOETFNumEntries - 1); - EXPECT_FLOAT_EQ(pqInvOetf(value), pqInvOetfLUT(value)); - } + for (size_t idx = 0; idx < kPqInvOETFNumEntries; idx++) { + float value = static_cast<float>(idx) / static_cast<float>(kPqInvOETFNumEntries - 1); + EXPECT_FLOAT_EQ(pqInvOetf(value), pqInvOetfLUT(value)); + } } TEST_F(GainMapMathTest, HlgInvOetfLUT) { - for (size_t idx = 0; idx < kHlgInvOETFNumEntries; idx++) { - float value = static_cast<float>(idx) / static_cast<float>(kHlgInvOETFNumEntries - 1); - EXPECT_FLOAT_EQ(hlgInvOetf(value), hlgInvOetfLUT(value)); - } + for (size_t idx = 0; idx < kHlgInvOETFNumEntries; idx++) { + float value = static_cast<float>(idx) / static_cast<float>(kHlgInvOETFNumEntries - 1); + EXPECT_FLOAT_EQ(hlgInvOetf(value), hlgInvOetfLUT(value)); + } } TEST_F(GainMapMathTest, pqOetfLUT) { - for (size_t idx = 0; idx < kPqOETFNumEntries; idx++) { - float value = static_cast<float>(idx) / static_cast<float>(kPqOETFNumEntries - 1); - EXPECT_FLOAT_EQ(pqOetf(value), pqOetfLUT(value)); - } + for (size_t idx = 0; idx < kPqOETFNumEntries; idx++) { + float value = static_cast<float>(idx) / static_cast<float>(kPqOETFNumEntries - 1); + EXPECT_FLOAT_EQ(pqOetf(value), pqOetfLUT(value)); + } } TEST_F(GainMapMathTest, hlgOetfLUT) { - for (size_t idx = 0; idx < kHlgOETFNumEntries; idx++) { - float value = static_cast<float>(idx) / static_cast<float>(kHlgOETFNumEntries - 1); - EXPECT_FLOAT_EQ(hlgOetf(value), hlgOetfLUT(value)); - } + for (size_t idx = 0; idx < kHlgOETFNumEntries; idx++) { + float value = static_cast<float>(idx) / static_cast<float>(kHlgOETFNumEntries - 1); + EXPECT_FLOAT_EQ(hlgOetf(value), hlgOetfLUT(value)); + } } TEST_F(GainMapMathTest, srgbInvOetfLUT) { - for (size_t idx = 0; idx < kSrgbInvOETFNumEntries; idx++) { - float value = static_cast<float>(idx) / static_cast<float>(kSrgbInvOETFNumEntries - 1); - EXPECT_FLOAT_EQ(srgbInvOetf(value), srgbInvOetfLUT(value)); - } + for (size_t idx = 0; idx < kSrgbInvOETFNumEntries; idx++) { + float value = static_cast<float>(idx) / static_cast<float>(kSrgbInvOETFNumEntries - 1); + EXPECT_FLOAT_EQ(srgbInvOetf(value), srgbInvOetfLUT(value)); + } } TEST_F(GainMapMathTest, applyGainLUT) { for (int boost = 1; boost <= 10; boost++) { - ultrahdr_metadata_struct metadata = { .maxContentBoost = static_cast<float>(boost), - .minContentBoost = 1.0f / static_cast<float>(boost) }; + ultrahdr_metadata_struct metadata; + + metadata.minContentBoost = 1.0f / static_cast<float>(boost); + metadata.maxContentBoost = static_cast<float>(boost); GainLUT gainLUT(&metadata); GainLUT gainLUTWithBoost(&metadata, metadata.maxContentBoost); for (size_t idx = 0; idx < kGainFactorNumEntries; idx++) { @@ -806,8 +874,10 @@ TEST_F(GainMapMathTest, applyGainLUT) { } for (int boost = 1; boost <= 10; boost++) { - ultrahdr_metadata_struct metadata = { .maxContentBoost = static_cast<float>(boost), - .minContentBoost = 1.0f }; + ultrahdr_metadata_struct metadata; + + metadata.minContentBoost = 1.0f; + metadata.maxContentBoost = static_cast<float>(boost); GainLUT gainLUT(&metadata); GainLUT gainLUTWithBoost(&metadata, metadata.maxContentBoost); for (size_t idx = 0; idx < kGainFactorNumEntries; idx++) { @@ -836,9 +906,10 @@ TEST_F(GainMapMathTest, applyGainLUT) { } for (int boost = 1; boost <= 10; boost++) { - ultrahdr_metadata_struct metadata = {.maxContentBoost = static_cast<float>(boost), - .minContentBoost = 1.0f / - powf(static_cast<float>(boost), 1.0f / 3.0f)}; + ultrahdr_metadata_struct metadata; + + metadata.minContentBoost = 1.0f / powf(static_cast<float>(boost), 1.0f / 3.0f); + metadata.maxContentBoost = static_cast<float>(boost); GainLUT gainLUT(&metadata); GainLUT gainLUTWithBoost(&metadata, metadata.maxContentBoost); for (size_t idx = 0; idx < kGainFactorNumEntries; idx++) { @@ -880,26 +951,20 @@ TEST_F(GainMapMathTest, ColorConversionLookup) { nullptr); EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_BT709), identityConversion); - EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_P3), - p3ToBt709); + EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_P3), p3ToBt709); EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_BT2100), bt2100ToBt709); - EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_P3, ULTRAHDR_COLORGAMUT_UNSPECIFIED), - nullptr); - EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_P3, ULTRAHDR_COLORGAMUT_BT709), - bt709ToP3); - EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_P3, ULTRAHDR_COLORGAMUT_P3), - identityConversion); - EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_P3, ULTRAHDR_COLORGAMUT_BT2100), - bt2100ToP3); + EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_P3, ULTRAHDR_COLORGAMUT_UNSPECIFIED), nullptr); + EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_P3, ULTRAHDR_COLORGAMUT_BT709), bt709ToP3); + EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_P3, ULTRAHDR_COLORGAMUT_P3), identityConversion); + EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_P3, ULTRAHDR_COLORGAMUT_BT2100), bt2100ToP3); EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT2100, ULTRAHDR_COLORGAMUT_UNSPECIFIED), nullptr); EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT2100, ULTRAHDR_COLORGAMUT_BT709), bt709ToBt2100); - EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT2100, ULTRAHDR_COLORGAMUT_P3), - p3ToBt2100); + EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT2100, ULTRAHDR_COLORGAMUT_P3), p3ToBt2100); EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT2100, ULTRAHDR_COLORGAMUT_BT2100), identityConversion); @@ -907,15 +972,16 @@ TEST_F(GainMapMathTest, ColorConversionLookup) { nullptr); EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_UNSPECIFIED, ULTRAHDR_COLORGAMUT_BT709), nullptr); - EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_UNSPECIFIED, ULTRAHDR_COLORGAMUT_P3), - nullptr); + EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_UNSPECIFIED, ULTRAHDR_COLORGAMUT_P3), nullptr); EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_UNSPECIFIED, ULTRAHDR_COLORGAMUT_BT2100), nullptr); } TEST_F(GainMapMathTest, EncodeGain) { - ultrahdr_metadata_struct metadata = { .maxContentBoost = 4.0f, - .minContentBoost = 1.0f / 4.0f }; + ultrahdr_metadata_struct metadata; + + metadata.minContentBoost = 1.0f / 4.0f; + metadata.maxContentBoost = 4.0f; EXPECT_EQ(encodeGain(0.0f, 0.0f, &metadata), 127); EXPECT_EQ(encodeGain(0.0f, 1.0f, &metadata), 127); @@ -972,8 +1038,10 @@ TEST_F(GainMapMathTest, EncodeGain) { } TEST_F(GainMapMathTest, ApplyGain) { - ultrahdr_metadata_struct metadata = { .maxContentBoost = 4.0f, - .minContentBoost = 1.0f / 4.0f }; + ultrahdr_metadata_struct metadata; + + metadata.minContentBoost = 1.0f / 4.0f; + metadata.maxContentBoost = 4.0f; float displayBoost = metadata.maxContentBoost; EXPECT_RGB_NEAR(applyGain(RgbBlack(), 0.0f, &metadata), RgbBlack()); @@ -1021,7 +1089,7 @@ TEST_F(GainMapMathTest, ApplyGain) { EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.75f, &metadata), RgbWhite() * 4.0f); EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 8.0f); - Color e = {{{ 0.0f, 0.5f, 1.0f }}}; + Color e = {{{0.0f, 0.5f, 1.0f}}}; metadata.maxContentBoost = 4.0f; metadata.minContentBoost = 1.0f / 4.0f; @@ -1041,13 +1109,12 @@ TEST_F(GainMapMathTest, ApplyGain) { applyGain(RgbGreen(), 1.0f, &metadata, displayBoost)); EXPECT_RGB_EQ(applyGain(RgbBlue(), 1.0f, &metadata), applyGain(RgbBlue(), 1.0f, &metadata, displayBoost)); - EXPECT_RGB_EQ(applyGain(e, 1.0f, &metadata), - applyGain(e, 1.0f, &metadata, displayBoost)); + EXPECT_RGB_EQ(applyGain(e, 1.0f, &metadata), applyGain(e, 1.0f, &metadata, displayBoost)); } TEST_F(GainMapMathTest, GetYuv420Pixel) { jpegr_uncompressed_struct image = Yuv420Image(); - Color (*colors)[4] = Yuv420Colors(); + Color(*colors)[4] = Yuv420Colors(); for (size_t y = 0; y < 4; ++y) { for (size_t x = 0; x < 4; ++x) { @@ -1058,7 +1125,7 @@ TEST_F(GainMapMathTest, GetYuv420Pixel) { TEST_F(GainMapMathTest, GetP010Pixel) { jpegr_uncompressed_struct image = P010Image(); - Color (*colors)[4] = P010Colors(); + Color(*colors)[4] = P010Colors(); for (size_t y = 0; y < 4; ++y) { for (size_t x = 0; x < 4; ++x) { @@ -1069,13 +1136,13 @@ TEST_F(GainMapMathTest, GetP010Pixel) { TEST_F(GainMapMathTest, SampleYuv420) { jpegr_uncompressed_struct image = Yuv420Image(); - Color (*colors)[4] = Yuv420Colors(); + Color(*colors)[4] = Yuv420Colors(); static const size_t kMapScaleFactor = 2; for (size_t y = 0; y < 4 / kMapScaleFactor; ++y) { for (size_t x = 0; x < 4 / kMapScaleFactor; ++x) { - Color min = {{{ 1.0f, 1.0f, 1.0f }}}; - Color max = {{{ -1.0f, -1.0f, -1.0f }}}; + Color min = {{{1.0f, 1.0f, 1.0f}}}; + Color max = {{{-1.0f, -1.0f, -1.0f}}}; for (size_t dy = 0; dy < kMapScaleFactor; ++dy) { for (size_t dx = 0; dx < kMapScaleFactor; ++dx) { @@ -1095,13 +1162,13 @@ TEST_F(GainMapMathTest, SampleYuv420) { TEST_F(GainMapMathTest, SampleP010) { jpegr_uncompressed_struct image = P010Image(); - Color (*colors)[4] = P010Colors(); + Color(*colors)[4] = P010Colors(); static const size_t kMapScaleFactor = 2; for (size_t y = 0; y < 4 / kMapScaleFactor; ++y) { for (size_t x = 0; x < 4 / kMapScaleFactor; ++x) { - Color min = {{{ 1.0f, 1.0f, 1.0f }}}; - Color max = {{{ -1.0f, -1.0f, -1.0f }}}; + Color min = {{{1.0f, 1.0f, 1.0f}}}; + Color max = {{{-1.0f, -1.0f, -1.0f}}}; for (size_t dy = 0; dy < kMapScaleFactor; ++dy) { for (size_t dx = 0; dx < kMapScaleFactor; ++dx) { @@ -1121,7 +1188,7 @@ TEST_F(GainMapMathTest, SampleP010) { TEST_F(GainMapMathTest, SampleMap) { jpegr_uncompressed_struct image = MapImage(); - float (*values)[4] = MapValues(); + float(*values)[4] = MapValues(); static const size_t kMapScaleFactor = 2; ShepardsIDW idwTable(kMapScaleFactor); @@ -1166,22 +1233,21 @@ TEST_F(GainMapMathTest, ColorToRgba1010102) { EXPECT_EQ(colorToRgba1010102(RgbGreen()), 0x3 << 30 | 0x3ff << 10); EXPECT_EQ(colorToRgba1010102(RgbBlue()), 0x3 << 30 | 0x3ff << 20); - Color e_gamma = {{{ 0.1f, 0.2f, 0.3f }}}; + Color e_gamma = {{{0.1f, 0.2f, 0.3f}}}; EXPECT_EQ(colorToRgba1010102(e_gamma), - 0x3 << 30 - | static_cast<uint32_t>(0.1f * static_cast<float>(0x3ff)) - | static_cast<uint32_t>(0.2f * static_cast<float>(0x3ff)) << 10 - | static_cast<uint32_t>(0.3f * static_cast<float>(0x3ff)) << 20); + 0x3 << 30 | static_cast<uint32_t>(0.1f * static_cast<float>(0x3ff)) | + static_cast<uint32_t>(0.2f * static_cast<float>(0x3ff)) << 10 | + static_cast<uint32_t>(0.3f * static_cast<float>(0x3ff)) << 20); } TEST_F(GainMapMathTest, ColorToRgbaF16) { - EXPECT_EQ(colorToRgbaF16(RgbBlack()), ((uint64_t) 0x3C00) << 48); + EXPECT_EQ(colorToRgbaF16(RgbBlack()), ((uint64_t)0x3C00) << 48); EXPECT_EQ(colorToRgbaF16(RgbWhite()), 0x3C003C003C003C00); - EXPECT_EQ(colorToRgbaF16(RgbRed()), (((uint64_t) 0x3C00) << 48) | ((uint64_t) 0x3C00)); - EXPECT_EQ(colorToRgbaF16(RgbGreen()), (((uint64_t) 0x3C00) << 48) | (((uint64_t) 0x3C00) << 16)); - EXPECT_EQ(colorToRgbaF16(RgbBlue()), (((uint64_t) 0x3C00) << 48) | (((uint64_t) 0x3C00) << 32)); + EXPECT_EQ(colorToRgbaF16(RgbRed()), (((uint64_t)0x3C00) << 48) | ((uint64_t)0x3C00)); + EXPECT_EQ(colorToRgbaF16(RgbGreen()), (((uint64_t)0x3C00) << 48) | (((uint64_t)0x3C00) << 16)); + EXPECT_EQ(colorToRgbaF16(RgbBlue()), (((uint64_t)0x3C00) << 48) | (((uint64_t)0x3C00) << 32)); - Color e_gamma = {{{ 0.1f, 0.2f, 0.3f }}}; + Color e_gamma = {{{0.1f, 0.2f, 0.3f}}}; EXPECT_EQ(colorToRgbaF16(e_gamma), 0x3C0034CD32662E66); } @@ -1190,16 +1256,14 @@ TEST_F(GainMapMathTest, Float32ToFloat16) { EXPECT_EQ(floatToHalf(0.0f), 0x0); EXPECT_EQ(floatToHalf(1.0f), 0x3C00); EXPECT_EQ(floatToHalf(-1.0f), 0xBC00); - EXPECT_EQ(floatToHalf(0x1.fffffep127f), 0x7FFF); // float max + EXPECT_EQ(floatToHalf(0x1.fffffep127f), 0x7FFF); // float max EXPECT_EQ(floatToHalf(-0x1.fffffep127f), 0xFFFF); // float min - EXPECT_EQ(floatToHalf(0x1.0p-126f), 0x0); // float zero + EXPECT_EQ(floatToHalf(0x1.0p-126f), 0x0); // float zero } TEST_F(GainMapMathTest, GenerateMapLuminanceSrgb) { - EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvBlack(), srgbLuminance), - 0.0f); - EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvWhite(), srgbLuminance), - kSdrWhiteNits); + EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvBlack(), srgbLuminance), 0.0f); + EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvWhite(), srgbLuminance), kSdrWhiteNits); EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvRed(), srgbLuminance), srgbLuminance(RgbRed()) * kSdrWhiteNits, LuminanceEpsilon()); EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvGreen(), srgbLuminance), @@ -1209,12 +1273,10 @@ TEST_F(GainMapMathTest, GenerateMapLuminanceSrgb) { } TEST_F(GainMapMathTest, GenerateMapLuminanceSrgbP3) { - EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvBlack(), p3Luminance), - 0.0f); - EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvWhite(), p3Luminance), - kSdrWhiteNits); - EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvRed(), p3Luminance), - p3Luminance(RgbRed()) * kSdrWhiteNits, LuminanceEpsilon()); + EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvBlack(), p3Luminance), 0.0f); + EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvWhite(), p3Luminance), kSdrWhiteNits); + EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvRed(), p3Luminance), p3Luminance(RgbRed()) * kSdrWhiteNits, + LuminanceEpsilon()); EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvGreen(), p3Luminance), p3Luminance(RgbGreen()) * kSdrWhiteNits, LuminanceEpsilon()); EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvBlue(), p3Luminance), @@ -1222,10 +1284,8 @@ TEST_F(GainMapMathTest, GenerateMapLuminanceSrgbP3) { } TEST_F(GainMapMathTest, GenerateMapLuminanceSrgbBt2100) { - EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvBlack(), bt2100Luminance), - 0.0f); - EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvWhite(), bt2100Luminance), - kSdrWhiteNits); + EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvBlack(), bt2100Luminance), 0.0f); + EXPECT_FLOAT_EQ(SrgbYuvToLuminance(YuvWhite(), bt2100Luminance), kSdrWhiteNits); EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvRed(), bt2100Luminance), bt2100Luminance(RgbRed()) * kSdrWhiteNits, LuminanceEpsilon()); EXPECT_NEAR(SrgbYuvToLuminance(SrgbYuvGreen(), bt2100Luminance), @@ -1235,125 +1295,94 @@ TEST_F(GainMapMathTest, GenerateMapLuminanceSrgbBt2100) { } TEST_F(GainMapMathTest, GenerateMapLuminanceHlg) { - EXPECT_FLOAT_EQ(Bt2100YuvToLuminance(YuvBlack(), hlgInvOetf, identityConversion, - bt2100Luminance, kHlgMaxNits), + EXPECT_FLOAT_EQ(Bt2100YuvToLuminance(YuvBlack(), hlgInvOetf, identityConversion, bt2100Luminance, + kHlgMaxNits), 0.0f); - EXPECT_FLOAT_EQ(Bt2100YuvToLuminance(YuvWhite(), hlgInvOetf, identityConversion, - bt2100Luminance, kHlgMaxNits), + EXPECT_FLOAT_EQ(Bt2100YuvToLuminance(YuvWhite(), hlgInvOetf, identityConversion, bt2100Luminance, + kHlgMaxNits), kHlgMaxNits); - EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvRed(), hlgInvOetf, identityConversion, - bt2100Luminance, kHlgMaxNits), + EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvRed(), hlgInvOetf, identityConversion, bt2100Luminance, + kHlgMaxNits), bt2100Luminance(RgbRed()) * kHlgMaxNits, LuminanceEpsilon()); EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvGreen(), hlgInvOetf, identityConversion, bt2100Luminance, kHlgMaxNits), bt2100Luminance(RgbGreen()) * kHlgMaxNits, LuminanceEpsilon()); - EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvBlue(), hlgInvOetf, identityConversion, - bt2100Luminance, kHlgMaxNits), + EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvBlue(), hlgInvOetf, identityConversion, bt2100Luminance, + kHlgMaxNits), bt2100Luminance(RgbBlue()) * kHlgMaxNits, LuminanceEpsilon()); } TEST_F(GainMapMathTest, GenerateMapLuminancePq) { - EXPECT_FLOAT_EQ(Bt2100YuvToLuminance(YuvBlack(), pqInvOetf, identityConversion, - bt2100Luminance, kPqMaxNits), - 0.0f); - EXPECT_FLOAT_EQ(Bt2100YuvToLuminance(YuvWhite(), pqInvOetf, identityConversion, - bt2100Luminance, kPqMaxNits), - kPqMaxNits); - EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvRed(), pqInvOetf, identityConversion, - bt2100Luminance, kPqMaxNits), + EXPECT_FLOAT_EQ( + Bt2100YuvToLuminance(YuvBlack(), pqInvOetf, identityConversion, bt2100Luminance, kPqMaxNits), + 0.0f); + EXPECT_FLOAT_EQ( + Bt2100YuvToLuminance(YuvWhite(), pqInvOetf, identityConversion, bt2100Luminance, kPqMaxNits), + kPqMaxNits); + EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvRed(), pqInvOetf, identityConversion, bt2100Luminance, + kPqMaxNits), bt2100Luminance(RgbRed()) * kPqMaxNits, LuminanceEpsilon()); - EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvGreen(), pqInvOetf, identityConversion, - bt2100Luminance, kPqMaxNits), + EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvGreen(), pqInvOetf, identityConversion, bt2100Luminance, + kPqMaxNits), bt2100Luminance(RgbGreen()) * kPqMaxNits, LuminanceEpsilon()); - EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvBlue(), pqInvOetf, identityConversion, - bt2100Luminance, kPqMaxNits), + EXPECT_NEAR(Bt2100YuvToLuminance(Bt2100YuvBlue(), pqInvOetf, identityConversion, bt2100Luminance, + kPqMaxNits), bt2100Luminance(RgbBlue()) * kPqMaxNits, LuminanceEpsilon()); } TEST_F(GainMapMathTest, ApplyMap) { - ultrahdr_metadata_struct metadata = { .maxContentBoost = 8.0f, - .minContentBoost = 1.0f / 8.0f }; - - EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f, &metadata), - RgbWhite() * 8.0f); - EXPECT_RGB_EQ(Recover(YuvBlack(), 1.0f, &metadata), - RgbBlack()); - EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 1.0f, &metadata), - RgbRed() * 8.0f); - EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 1.0f, &metadata), - RgbGreen() * 8.0f); - EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 1.0f, &metadata), - RgbBlue() * 8.0f); - - EXPECT_RGB_EQ(Recover(YuvWhite(), 0.75f, &metadata), - RgbWhite() * sqrt(8.0f)); - EXPECT_RGB_EQ(Recover(YuvBlack(), 0.75f, &metadata), - RgbBlack()); - EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.75f, &metadata), - RgbRed() * sqrt(8.0f)); - EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.75f, &metadata), - RgbGreen() * sqrt(8.0f)); - EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.75f, &metadata), - RgbBlue() * sqrt(8.0f)); - - EXPECT_RGB_EQ(Recover(YuvWhite(), 0.5f, &metadata), - RgbWhite()); - EXPECT_RGB_EQ(Recover(YuvBlack(), 0.5f, &metadata), - RgbBlack()); - EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.5f, &metadata), - RgbRed()); - EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.5f, &metadata), - RgbGreen()); - EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.5f, &metadata), - RgbBlue()); - - EXPECT_RGB_EQ(Recover(YuvWhite(), 0.25f, &metadata), - RgbWhite() / sqrt(8.0f)); - EXPECT_RGB_EQ(Recover(YuvBlack(), 0.25f, &metadata), - RgbBlack()); - EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.25f, &metadata), - RgbRed() / sqrt(8.0f)); - EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.25f, &metadata), - RgbGreen() / sqrt(8.0f)); - EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.25f, &metadata), - RgbBlue() / sqrt(8.0f)); - - EXPECT_RGB_EQ(Recover(YuvWhite(), 0.0f, &metadata), - RgbWhite() / 8.0f); - EXPECT_RGB_EQ(Recover(YuvBlack(), 0.0f, &metadata), - RgbBlack()); - EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.0f, &metadata), - RgbRed() / 8.0f); - EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.0f, &metadata), - RgbGreen() / 8.0f); - EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.0f, &metadata), - RgbBlue() / 8.0f); + ultrahdr_metadata_struct metadata; + + metadata.minContentBoost = 1.0f / 8.0f; + metadata.maxContentBoost = 8.0f; + + EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f, &metadata), RgbWhite() * 8.0f); + EXPECT_RGB_EQ(Recover(YuvBlack(), 1.0f, &metadata), RgbBlack()); + EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 1.0f, &metadata), RgbRed() * 8.0f); + EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 1.0f, &metadata), RgbGreen() * 8.0f); + EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 1.0f, &metadata), RgbBlue() * 8.0f); + + EXPECT_RGB_EQ(Recover(YuvWhite(), 0.75f, &metadata), RgbWhite() * sqrt(8.0f)); + EXPECT_RGB_EQ(Recover(YuvBlack(), 0.75f, &metadata), RgbBlack()); + EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.75f, &metadata), RgbRed() * sqrt(8.0f)); + EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.75f, &metadata), RgbGreen() * sqrt(8.0f)); + EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.75f, &metadata), RgbBlue() * sqrt(8.0f)); + + EXPECT_RGB_EQ(Recover(YuvWhite(), 0.5f, &metadata), RgbWhite()); + EXPECT_RGB_EQ(Recover(YuvBlack(), 0.5f, &metadata), RgbBlack()); + EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.5f, &metadata), RgbRed()); + EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.5f, &metadata), RgbGreen()); + EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.5f, &metadata), RgbBlue()); + + EXPECT_RGB_EQ(Recover(YuvWhite(), 0.25f, &metadata), RgbWhite() / sqrt(8.0f)); + EXPECT_RGB_EQ(Recover(YuvBlack(), 0.25f, &metadata), RgbBlack()); + EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.25f, &metadata), RgbRed() / sqrt(8.0f)); + EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.25f, &metadata), RgbGreen() / sqrt(8.0f)); + EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.25f, &metadata), RgbBlue() / sqrt(8.0f)); + + EXPECT_RGB_EQ(Recover(YuvWhite(), 0.0f, &metadata), RgbWhite() / 8.0f); + EXPECT_RGB_EQ(Recover(YuvBlack(), 0.0f, &metadata), RgbBlack()); + EXPECT_RGB_CLOSE(Recover(SrgbYuvRed(), 0.0f, &metadata), RgbRed() / 8.0f); + EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.0f, &metadata), RgbGreen() / 8.0f); + EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.0f, &metadata), RgbBlue() / 8.0f); metadata.maxContentBoost = 8.0f; metadata.minContentBoost = 1.0f; - EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f, &metadata), - RgbWhite() * 8.0f); - EXPECT_RGB_EQ(Recover(YuvWhite(), 2.0f / 3.0f, &metadata), - RgbWhite() * 4.0f); - EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f / 3.0f, &metadata), - RgbWhite() * 2.0f); - EXPECT_RGB_EQ(Recover(YuvWhite(), 0.0f, &metadata), - RgbWhite()); + EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f, &metadata), RgbWhite() * 8.0f); + EXPECT_RGB_EQ(Recover(YuvWhite(), 2.0f / 3.0f, &metadata), RgbWhite() * 4.0f); + EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f / 3.0f, &metadata), RgbWhite() * 2.0f); + EXPECT_RGB_EQ(Recover(YuvWhite(), 0.0f, &metadata), RgbWhite()); metadata.maxContentBoost = 8.0f; - metadata.minContentBoost = 0.5f;; - - EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f, &metadata), - RgbWhite() * 8.0f); - EXPECT_RGB_EQ(Recover(YuvWhite(), 0.75, &metadata), - RgbWhite() * 4.0f); - EXPECT_RGB_EQ(Recover(YuvWhite(), 0.5f, &metadata), - RgbWhite() * 2.0f); - EXPECT_RGB_EQ(Recover(YuvWhite(), 0.25f, &metadata), - RgbWhite()); - EXPECT_RGB_EQ(Recover(YuvWhite(), 0.0f, &metadata), - RgbWhite() / 2.0f); + metadata.minContentBoost = 0.5f; + ; + + EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f, &metadata), RgbWhite() * 8.0f); + EXPECT_RGB_EQ(Recover(YuvWhite(), 0.75, &metadata), RgbWhite() * 4.0f); + EXPECT_RGB_EQ(Recover(YuvWhite(), 0.5f, &metadata), RgbWhite() * 2.0f); + EXPECT_RGB_EQ(Recover(YuvWhite(), 0.25f, &metadata), RgbWhite()); + EXPECT_RGB_EQ(Recover(YuvWhite(), 0.0f, &metadata), RgbWhite() / 2.0f); } -} // namespace ultrahdr +} // namespace ultrahdr diff --git a/tests/icchelper_test.cpp b/tests/icchelper_test.cpp index 081ef0e..02ad27b 100644 --- a/tests/icchelper_test.cpp +++ b/tests/icchelper_test.cpp @@ -16,17 +16,18 @@ #include <gtest/gtest.h> -#include "ultrahdr/icc.h" +#include "icc.h" namespace ultrahdr { class IccHelperTest : public testing::Test { -public: - IccHelperTest(); - ~IccHelperTest(); -protected: - virtual void SetUp(); - virtual void TearDown(); + public: + IccHelperTest(); + ~IccHelperTest(); + + protected: + virtual void SetUp(); + virtual void TearDown(); }; IccHelperTest::IccHelperTest() {} @@ -38,41 +39,39 @@ void IccHelperTest::SetUp() {} void IccHelperTest::TearDown() {} TEST_F(IccHelperTest, iccWriteThenRead) { - std::shared_ptr<DataStruct> iccBt709 = - IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, ULTRAHDR_COLORGAMUT_BT709); - ASSERT_NE(iccBt709->getLength(), 0); - ASSERT_NE(iccBt709->getData(), nullptr); - EXPECT_EQ(IccHelper::readIccColorGamut(iccBt709->getData(), iccBt709->getLength()), - ULTRAHDR_COLORGAMUT_BT709); - - std::shared_ptr<DataStruct> iccP3 = - IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, ULTRAHDR_COLORGAMUT_P3); - ASSERT_NE(iccP3->getLength(), 0); - ASSERT_NE(iccP3->getData(), nullptr); - EXPECT_EQ(IccHelper::readIccColorGamut(iccP3->getData(), iccP3->getLength()), - ULTRAHDR_COLORGAMUT_P3); - - std::shared_ptr<DataStruct> iccBt2100 = - IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, ULTRAHDR_COLORGAMUT_BT2100); - ASSERT_NE(iccBt2100->getLength(), 0); - ASSERT_NE(iccBt2100->getData(), nullptr); - EXPECT_EQ(IccHelper::readIccColorGamut(iccBt2100->getData(), iccBt2100->getLength()), - ULTRAHDR_COLORGAMUT_BT2100); + std::shared_ptr<DataStruct> iccBt709 = + IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, ULTRAHDR_COLORGAMUT_BT709); + ASSERT_NE(iccBt709->getLength(), 0); + ASSERT_NE(iccBt709->getData(), nullptr); + EXPECT_EQ(IccHelper::readIccColorGamut(iccBt709->getData(), iccBt709->getLength()), + ULTRAHDR_COLORGAMUT_BT709); + + std::shared_ptr<DataStruct> iccP3 = + IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, ULTRAHDR_COLORGAMUT_P3); + ASSERT_NE(iccP3->getLength(), 0); + ASSERT_NE(iccP3->getData(), nullptr); + EXPECT_EQ(IccHelper::readIccColorGamut(iccP3->getData(), iccP3->getLength()), + ULTRAHDR_COLORGAMUT_P3); + + std::shared_ptr<DataStruct> iccBt2100 = + IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, ULTRAHDR_COLORGAMUT_BT2100); + ASSERT_NE(iccBt2100->getLength(), 0); + ASSERT_NE(iccBt2100->getData(), nullptr); + EXPECT_EQ(IccHelper::readIccColorGamut(iccBt2100->getData(), iccBt2100->getLength()), + ULTRAHDR_COLORGAMUT_BT2100); } TEST_F(IccHelperTest, iccEndianness) { - std::shared_ptr<DataStruct> icc = - IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, ULTRAHDR_COLORGAMUT_BT709); - size_t profile_size = icc->getLength() - kICCIdentifierSize; + std::shared_ptr<DataStruct> icc = + IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, ULTRAHDR_COLORGAMUT_BT709); + size_t profile_size = icc->getLength() - kICCIdentifierSize; - uint8_t* icc_bytes = reinterpret_cast<uint8_t*>(icc->getData()) + kICCIdentifierSize; - uint32_t encoded_size = static_cast<uint32_t>(icc_bytes[0]) << 24 | - static_cast<uint32_t>(icc_bytes[1]) << 16 | - static_cast<uint32_t>(icc_bytes[2]) << 8 | - static_cast<uint32_t>(icc_bytes[3]); + uint8_t* icc_bytes = reinterpret_cast<uint8_t*>(icc->getData()) + kICCIdentifierSize; + uint32_t encoded_size = + static_cast<uint32_t>(icc_bytes[0]) << 24 | static_cast<uint32_t>(icc_bytes[1]) << 16 | + static_cast<uint32_t>(icc_bytes[2]) << 8 | static_cast<uint32_t>(icc_bytes[3]); - EXPECT_EQ(static_cast<size_t>(encoded_size), profile_size); + EXPECT_EQ(static_cast<size_t>(encoded_size), profile_size); } } // namespace ultrahdr - diff --git a/tests/jpegdecoderhelper_test.cpp b/tests/jpegdecoderhelper_test.cpp index 838c911..3b8e88e 100644 --- a/tests/jpegdecoderhelper_test.cpp +++ b/tests/jpegdecoderhelper_test.cpp @@ -14,12 +14,14 @@ * limitations under the License. */ -#include <fcntl.h> #include <gtest/gtest.h> -#include "ultrahdr/ultrahdrcommon.h" -#include "ultrahdr/jpegdecoderhelper.h" -#include "ultrahdr/icc.h" +#include <fstream> +#include <iostream> + +#include "ultrahdrcommon.h" +#include "jpegdecoderhelper.h" +#include "icc.h" namespace ultrahdr { @@ -41,120 +43,105 @@ namespace ultrahdr { #define IMAGE_HEIGHT 240 class JpegDecoderHelperTest : public testing::Test { -public: - struct Image { - std::unique_ptr<uint8_t[]> buffer; - size_t size; - }; - JpegDecoderHelperTest(); - ~JpegDecoderHelperTest(); - -protected: - virtual void SetUp(); - virtual void TearDown(); - - Image mYuvImage, mYuvIccImage, mGreyImage; + public: + struct Image { + std::unique_ptr<uint8_t[]> buffer; + size_t size; + }; + JpegDecoderHelperTest(); + ~JpegDecoderHelperTest(); + + protected: + virtual void SetUp(); + virtual void TearDown(); + + Image mYuvImage, mYuvIccImage, mGreyImage; }; JpegDecoderHelperTest::JpegDecoderHelperTest() {} JpegDecoderHelperTest::~JpegDecoderHelperTest() {} -static size_t getFileSize(int fd) { - struct stat st; - if (fstat(fd, &st) < 0) { - ALOGW("%s : fstat failed", __func__); - return 0; - } - return st.st_size; // bytes -} - static bool loadFile(const char filename[], JpegDecoderHelperTest::Image* result) { - int fd = open(filename, O_CLOEXEC); - if (fd < 0) { - return false; - } - int length = getFileSize(fd); - if (length == 0) { - close(fd); - return false; - } - result->buffer.reset(new uint8_t[length]); - if (read(fd, result->buffer.get(), length) != static_cast<ssize_t>(length)) { - close(fd); - return false; - } - close(fd); + std::ifstream ifd(filename, std::ios::binary | std::ios::ate); + if (ifd.good()) { + int size = ifd.tellg(); + ifd.seekg(0, std::ios::beg); + result->buffer.reset(new uint8_t[size]); + ifd.read(reinterpret_cast<char*>(result->buffer.get()), size); + ifd.close(); return true; + } + return false; } void JpegDecoderHelperTest::SetUp() { - if (!loadFile(YUV_IMAGE, &mYuvImage)) { - FAIL() << "Load file " << YUV_IMAGE << " failed"; - } - mYuvImage.size = YUV_IMAGE_SIZE; - if (!loadFile(YUV_ICC_IMAGE, &mYuvIccImage)) { - FAIL() << "Load file " << YUV_ICC_IMAGE << " failed"; - } - mYuvIccImage.size = YUV_ICC_IMAGE_SIZE; - if (!loadFile(GREY_IMAGE, &mGreyImage)) { - FAIL() << "Load file " << GREY_IMAGE << " failed"; - } - mGreyImage.size = GREY_IMAGE_SIZE; + if (!loadFile(YUV_IMAGE, &mYuvImage)) { + FAIL() << "Load file " << YUV_IMAGE << " failed"; + } + mYuvImage.size = YUV_IMAGE_SIZE; + if (!loadFile(YUV_ICC_IMAGE, &mYuvIccImage)) { + FAIL() << "Load file " << YUV_ICC_IMAGE << " failed"; + } + mYuvIccImage.size = YUV_ICC_IMAGE_SIZE; + if (!loadFile(GREY_IMAGE, &mGreyImage)) { + FAIL() << "Load file " << GREY_IMAGE << " failed"; + } + mGreyImage.size = GREY_IMAGE_SIZE; } void JpegDecoderHelperTest::TearDown() {} TEST_F(JpegDecoderHelperTest, decodeYuvImage) { - JpegDecoderHelper decoder; - EXPECT_TRUE(decoder.decompressImage(mYuvImage.buffer.get(), mYuvImage.size)); - ASSERT_GT(decoder.getDecompressedImageSize(), static_cast<uint32_t>(0)); - EXPECT_EQ(IccHelper::readIccColorGamut(decoder.getICCPtr(), decoder.getICCSize()), - ULTRAHDR_COLORGAMUT_UNSPECIFIED); + JpegDecoderHelper decoder; + EXPECT_TRUE(decoder.decompressImage(mYuvImage.buffer.get(), mYuvImage.size)); + ASSERT_GT(decoder.getDecompressedImageSize(), static_cast<uint32_t>(0)); + EXPECT_EQ(IccHelper::readIccColorGamut(decoder.getICCPtr(), decoder.getICCSize()), + ULTRAHDR_COLORGAMUT_UNSPECIFIED); } TEST_F(JpegDecoderHelperTest, decodeYuvIccImage) { - JpegDecoderHelper decoder; - EXPECT_TRUE(decoder.decompressImage(mYuvIccImage.buffer.get(), mYuvIccImage.size)); - ASSERT_GT(decoder.getDecompressedImageSize(), static_cast<uint32_t>(0)); - EXPECT_EQ(IccHelper::readIccColorGamut(decoder.getICCPtr(), decoder.getICCSize()), - ULTRAHDR_COLORGAMUT_BT709); + JpegDecoderHelper decoder; + EXPECT_TRUE(decoder.decompressImage(mYuvIccImage.buffer.get(), mYuvIccImage.size)); + ASSERT_GT(decoder.getDecompressedImageSize(), static_cast<uint32_t>(0)); + EXPECT_EQ(IccHelper::readIccColorGamut(decoder.getICCPtr(), decoder.getICCSize()), + ULTRAHDR_COLORGAMUT_BT709); } TEST_F(JpegDecoderHelperTest, decodeGreyImage) { - JpegDecoderHelper decoder; - EXPECT_TRUE(decoder.decompressImage(mGreyImage.buffer.get(), mGreyImage.size)); - ASSERT_GT(decoder.getDecompressedImageSize(), static_cast<uint32_t>(0)); + JpegDecoderHelper decoder; + EXPECT_TRUE(decoder.decompressImage(mGreyImage.buffer.get(), mGreyImage.size)); + ASSERT_GT(decoder.getDecompressedImageSize(), static_cast<uint32_t>(0)); } TEST_F(JpegDecoderHelperTest, getCompressedImageParameters) { - size_t width = 0, height = 0; - std::vector<uint8_t> icc, exif; + size_t width = 0, height = 0; + std::vector<uint8_t> icc, exif; - JpegDecoderHelper decoder; - EXPECT_TRUE(decoder.getCompressedImageParameters(mYuvImage.buffer.get(), mYuvImage.size, &width, - &height, &icc, &exif)); + JpegDecoderHelper decoder; + EXPECT_TRUE(decoder.getCompressedImageParameters(mYuvImage.buffer.get(), mYuvImage.size, &width, + &height, &icc, &exif)); - EXPECT_EQ(width, IMAGE_WIDTH); - EXPECT_EQ(height, IMAGE_HEIGHT); - EXPECT_EQ(icc.size(), 0); - EXPECT_EQ(exif.size(), 0); + EXPECT_EQ(width, IMAGE_WIDTH); + EXPECT_EQ(height, IMAGE_HEIGHT); + EXPECT_EQ(icc.size(), 0); + EXPECT_EQ(exif.size(), 0); } TEST_F(JpegDecoderHelperTest, getCompressedImageParametersIcc) { - size_t width = 0, height = 0; - std::vector<uint8_t> icc, exif; + size_t width = 0, height = 0; + std::vector<uint8_t> icc, exif; - JpegDecoderHelper decoder; - EXPECT_TRUE(decoder.getCompressedImageParameters(mYuvIccImage.buffer.get(), mYuvIccImage.size, - &width, &height, &icc, &exif)); + JpegDecoderHelper decoder; + EXPECT_TRUE(decoder.getCompressedImageParameters(mYuvIccImage.buffer.get(), mYuvIccImage.size, + &width, &height, &icc, &exif)); - EXPECT_EQ(width, IMAGE_WIDTH); - EXPECT_EQ(height, IMAGE_HEIGHT); - EXPECT_GT(icc.size(), 0); - EXPECT_GT(exif.size(), 0); + EXPECT_EQ(width, IMAGE_WIDTH); + EXPECT_EQ(height, IMAGE_HEIGHT); + EXPECT_GT(icc.size(), 0); + EXPECT_GT(exif.size(), 0); - EXPECT_EQ(IccHelper::readIccColorGamut(icc.data(), icc.size()), ULTRAHDR_COLORGAMUT_BT709); + EXPECT_EQ(IccHelper::readIccColorGamut(icc.data(), icc.size()), ULTRAHDR_COLORGAMUT_BT709); } -} // namespace ultrahdr +} // namespace ultrahdr diff --git a/tests/jpegencoderhelper_test.cpp b/tests/jpegencoderhelper_test.cpp index 37aa995..3463c26 100644 --- a/tests/jpegencoderhelper_test.cpp +++ b/tests/jpegencoderhelper_test.cpp @@ -14,12 +14,14 @@ * limitations under the License. */ -#include <fcntl.h> #include <gtest/gtest.h> -#include "ultrahdr/ultrahdrcommon.h" -#include "ultrahdr/ultrahdr.h" -#include "ultrahdr/jpegencoderhelper.h" +#include <fstream> +#include <iostream> + +#include "ultrahdrcommon.h" +#include "ultrahdr.h" +#include "jpegencoderhelper.h" namespace ultrahdr { @@ -41,102 +43,84 @@ namespace ultrahdr { #define JPEG_QUALITY 90 class JpegEncoderHelperTest : public testing::Test { -public: - struct Image { - std::unique_ptr<uint8_t[]> buffer; - size_t width; - size_t height; - }; - JpegEncoderHelperTest(); - ~JpegEncoderHelperTest(); - -protected: - virtual void SetUp(); - virtual void TearDown(); - - Image mAlignedImage, mUnalignedImage, mSingleChannelImage; + public: + struct Image { + std::unique_ptr<uint8_t[]> buffer; + size_t width; + size_t height; + }; + JpegEncoderHelperTest(); + ~JpegEncoderHelperTest(); + + protected: + virtual void SetUp(); + virtual void TearDown(); + + Image mAlignedImage, mUnalignedImage, mSingleChannelImage; }; JpegEncoderHelperTest::JpegEncoderHelperTest() {} JpegEncoderHelperTest::~JpegEncoderHelperTest() {} -static size_t getFileSize(int fd) { - struct stat st; - if (fstat(fd, &st) < 0) { - ALOGW("%s : fstat failed", __func__); - return 0; - } - return st.st_size; // bytes -} - static bool loadFile(const char filename[], JpegEncoderHelperTest::Image* result) { - int fd = open(filename, O_CLOEXEC); - if (fd < 0) { - return false; - } - int length = getFileSize(fd); - if (length == 0) { - close(fd); - return false; - } - result->buffer.reset(new uint8_t[length]); - if (read(fd, result->buffer.get(), length) != static_cast<ssize_t>(length)) { - close(fd); - return false; - } - close(fd); + std::ifstream ifd(filename, std::ios::binary | std::ios::ate); + if (ifd.good()) { + int size = ifd.tellg(); + ifd.seekg(0, std::ios::beg); + result->buffer.reset(new uint8_t[size]); + ifd.read(reinterpret_cast<char*>(result->buffer.get()), size); + ifd.close(); return true; + } + return false; } void JpegEncoderHelperTest::SetUp() { - if (!loadFile(ALIGNED_IMAGE, &mAlignedImage)) { - FAIL() << "Load file " << ALIGNED_IMAGE << " failed"; - } - mAlignedImage.width = ALIGNED_IMAGE_WIDTH; - mAlignedImage.height = ALIGNED_IMAGE_HEIGHT; - if (!loadFile(UNALIGNED_IMAGE, &mUnalignedImage)) { - FAIL() << "Load file " << UNALIGNED_IMAGE << " failed"; - } - mUnalignedImage.width = UNALIGNED_IMAGE_WIDTH; - mUnalignedImage.height = UNALIGNED_IMAGE_HEIGHT; - if (!loadFile(SINGLE_CHANNEL_IMAGE, &mSingleChannelImage)) { - FAIL() << "Load file " << SINGLE_CHANNEL_IMAGE << " failed"; - } - mSingleChannelImage.width = SINGLE_CHANNEL_IMAGE_WIDTH; - mSingleChannelImage.height = SINGLE_CHANNEL_IMAGE_HEIGHT; + if (!loadFile(ALIGNED_IMAGE, &mAlignedImage)) { + FAIL() << "Load file " << ALIGNED_IMAGE << " failed"; + } + mAlignedImage.width = ALIGNED_IMAGE_WIDTH; + mAlignedImage.height = ALIGNED_IMAGE_HEIGHT; + if (!loadFile(UNALIGNED_IMAGE, &mUnalignedImage)) { + FAIL() << "Load file " << UNALIGNED_IMAGE << " failed"; + } + mUnalignedImage.width = UNALIGNED_IMAGE_WIDTH; + mUnalignedImage.height = UNALIGNED_IMAGE_HEIGHT; + if (!loadFile(SINGLE_CHANNEL_IMAGE, &mSingleChannelImage)) { + FAIL() << "Load file " << SINGLE_CHANNEL_IMAGE << " failed"; + } + mSingleChannelImage.width = SINGLE_CHANNEL_IMAGE_WIDTH; + mSingleChannelImage.height = SINGLE_CHANNEL_IMAGE_HEIGHT; } void JpegEncoderHelperTest::TearDown() {} TEST_F(JpegEncoderHelperTest, encodeAlignedImage) { - JpegEncoderHelper encoder; - EXPECT_TRUE(encoder.compressImage(mAlignedImage.buffer.get(), - mAlignedImage.buffer.get() + - mAlignedImage.width * mAlignedImage.height, - mAlignedImage.width, mAlignedImage.height, - mAlignedImage.width, mAlignedImage.width / 2, JPEG_QUALITY, - NULL, 0)); - ASSERT_GT(encoder.getCompressedImageSize(), static_cast<uint32_t>(0)); + JpegEncoderHelper encoder; + EXPECT_TRUE(encoder.compressImage( + mAlignedImage.buffer.get(), + mAlignedImage.buffer.get() + mAlignedImage.width * mAlignedImage.height, mAlignedImage.width, + mAlignedImage.height, mAlignedImage.width, mAlignedImage.width / 2, JPEG_QUALITY, NULL, 0)); + ASSERT_GT(encoder.getCompressedImageSize(), static_cast<uint32_t>(0)); } TEST_F(JpegEncoderHelperTest, encodeUnalignedImage) { - JpegEncoderHelper encoder; - EXPECT_TRUE(encoder.compressImage(mUnalignedImage.buffer.get(), - mUnalignedImage.buffer.get() + - mUnalignedImage.width * mUnalignedImage.height, - mUnalignedImage.width, mUnalignedImage.height, - mUnalignedImage.width, mUnalignedImage.width / 2, - JPEG_QUALITY, NULL, 0)); - ASSERT_GT(encoder.getCompressedImageSize(), static_cast<uint32_t>(0)); + JpegEncoderHelper encoder; + EXPECT_TRUE(encoder.compressImage( + mUnalignedImage.buffer.get(), + mUnalignedImage.buffer.get() + mUnalignedImage.width * mUnalignedImage.height, + mUnalignedImage.width, mUnalignedImage.height, mUnalignedImage.width, + mUnalignedImage.width / 2, JPEG_QUALITY, NULL, 0)); + ASSERT_GT(encoder.getCompressedImageSize(), static_cast<uint32_t>(0)); } TEST_F(JpegEncoderHelperTest, encodeSingleChannelImage) { - JpegEncoderHelper encoder; - EXPECT_TRUE(encoder.compressImage(mSingleChannelImage.buffer.get(), nullptr, - mSingleChannelImage.width, mSingleChannelImage.height, - mSingleChannelImage.width, 0, JPEG_QUALITY, NULL, 0)); - ASSERT_GT(encoder.getCompressedImageSize(), static_cast<uint32_t>(0)); + JpegEncoderHelper encoder; + EXPECT_TRUE(encoder.compressImage(mSingleChannelImage.buffer.get(), nullptr, + mSingleChannelImage.width, mSingleChannelImage.height, + mSingleChannelImage.width, 0, JPEG_QUALITY, NULL, 0)); + ASSERT_GT(encoder.getCompressedImageSize(), static_cast<uint32_t>(0)); } -} // namespace ultrahdr +} // namespace ultrahdr diff --git a/tests/jpegr_test.cpp b/tests/jpegr_test.cpp index d3832e8..bfbfcde 100644 --- a/tests/jpegr_test.cpp +++ b/tests/jpegr_test.cpp @@ -14,15 +14,19 @@ * limitations under the License. */ +#ifdef _WIN32 +#include <windows.h> +#else #include <sys/time.h> +#endif #include <gtest/gtest.h> #include <fstream> #include <iostream> -#include "ultrahdr/ultrahdrcommon.h" -#include "ultrahdr/jpegr.h" -#include "ultrahdr/jpegrutils.h" +#include "ultrahdrcommon.h" +#include "jpegr.h" +#include "jpegrutils.h" //#define DUMP_OUTPUT @@ -59,7 +63,7 @@ typedef enum { * rawImg.loadRawResource(kYCbCrP010FileName); */ class UhdrUnCompressedStructWrapper { -public: + public: UhdrUnCompressedStructWrapper(size_t width, size_t height, UhdrInputFormat format); ~UhdrUnCompressedStructWrapper() = default; @@ -70,7 +74,7 @@ public: bool loadRawResource(const char* fileName); jr_uncompressed_ptr getImageHandle(); -private: + private: std::unique_ptr<uint8_t[]> mLumaData; std::unique_ptr<uint8_t[]> mChromaData; jpegr_uncompressed_struct mImg; @@ -85,14 +89,14 @@ private: * rawImg.allocateMemory(); */ class UhdrCompressedStructWrapper { -public: + public: UhdrCompressedStructWrapper(size_t width, size_t height); ~UhdrCompressedStructWrapper() = default; bool allocateMemory(); jr_compressed_ptr getImageHandle(); -private: + private: std::unique_ptr<uint8_t[]> mData; jpegr_compressed_struct mImg{}; size_t mWidth; @@ -200,7 +204,7 @@ bool UhdrUnCompressedStructWrapper::loadRawResource(const char* fileName) { if (ifd.good()) { int bpp = mFormat == YCbCr_p010 ? 2 : 1; int size = ifd.tellg(); - int length = mImg.width * mImg.height * bpp * 3 / 2; // 2x2 subsampling + int length = mImg.width * mImg.height * bpp * 3 / 2; // 2x2 subsampling if (size < length) { std::cerr << "requested to read " << length << " bytes from file : " << fileName << ", file contains only " << length << " bytes" << std::endl; @@ -247,9 +251,7 @@ bool UhdrUnCompressedStructWrapper::loadRawResource(const char* fileName) { return false; } -jr_uncompressed_ptr UhdrUnCompressedStructWrapper::getImageHandle() { - return &mImg; -} +jr_uncompressed_ptr UhdrUnCompressedStructWrapper::getImageHandle() { return &mImg; } UhdrCompressedStructWrapper::UhdrCompressedStructWrapper(size_t width, size_t height) { mWidth = width; @@ -261,7 +263,7 @@ bool UhdrCompressedStructWrapper::allocateMemory() { std::cerr << "Object in bad state, mem alloc failed" << std::endl; return false; } - int maxLength = std::max(8 * 1024 /* min size 8kb */, (int)(mWidth * mHeight * 3 * 2)); + int maxLength = (std::max)(8 * 1024 /* min size 8kb */, (int)(mWidth * mHeight * 3 * 2)); mData = std::make_unique<uint8_t[]>(maxLength); mImg.data = mData.get(); mImg.length = 0; @@ -269,9 +271,7 @@ bool UhdrCompressedStructWrapper::allocateMemory() { return true; } -jr_compressed_ptr UhdrCompressedStructWrapper::getImageHandle() { - return &mImg; -} +jr_compressed_ptr UhdrCompressedStructWrapper::getImageHandle() { return &mImg; } #ifdef DUMP_OUTPUT static bool writeFile(const char* filename, void*& result, int length) { @@ -306,14 +306,14 @@ void decodeJpegRImg(jr_compressed_ptr img, [[maybe_unused]] const char* outFileN std::vector<uint8_t> exifData(0); jpegr_info_struct info{0, 0, &iccData, &exifData}; JpegR jpegHdr; - ASSERT_EQ(OK, jpegHdr.getJPEGRInfo(img, &info)); + ASSERT_EQ(JPEGR_NO_ERROR, jpegHdr.getJPEGRInfo(img, &info)); ASSERT_EQ(kImageWidth, info.width); ASSERT_EQ(kImageHeight, info.height); size_t outSize = info.width * info.height * 8; std::unique_ptr<uint8_t[]> data = std::make_unique<uint8_t[]>(outSize); jpegr_uncompressed_struct destImage{}; destImage.data = data.get(); - ASSERT_EQ(OK, jpegHdr.decodeJPEGR(img, &destImage)); + ASSERT_EQ(JPEGR_NO_ERROR, jpegHdr.decodeJPEGR(img, &destImage)); ASSERT_EQ(kImageWidth, destImage.width); ASSERT_EQ(kImageHeight, destImage.height); #ifdef DUMP_OUTPUT @@ -340,33 +340,33 @@ TEST(JpegRTest, EncodeAPI0WithInvalidArgs) { ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100)); ASSERT_TRUE(rawImg.allocateMemory()); - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), -1, nullptr), - OK) - << "fail, API allows bad jpeg quality factor"; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), 101, nullptr), - OK) - << "fail, API allows bad jpeg quality factor"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImg.getImageHandle(), ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + jpgImg.getImageHandle(), -1, nullptr), + JPEGR_NO_ERROR) + << "fail, API allows bad jpeg quality factor"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImg.getImageHandle(), ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + jpgImg.getImageHandle(), 101, nullptr), + JPEGR_NO_ERROR) + << "fail, API allows bad jpeg quality factor"; ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED, jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad hdr transfer function"; + JPEGR_NO_ERROR) + << "fail, API allows bad hdr transfer function"; ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), static_cast<ultrahdr_transfer_function>( - ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1), + ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1), jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad hdr transfer function"; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), - static_cast<ultrahdr_transfer_function>(-10), - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad hdr transfer function"; + JPEGR_NO_ERROR) + << "fail, API allows bad hdr transfer function"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImg.getImageHandle(), static_cast<ultrahdr_transfer_function>(-10), + jpgImg.getImageHandle(), kQuality, nullptr), + JPEGR_NO_ERROR) + << "fail, API allows bad hdr transfer function"; } // test dest @@ -375,54 +375,54 @@ TEST(JpegRTest, EncodeAPI0WithInvalidArgs) { ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100)); ASSERT_TRUE(rawImg.allocateMemory()); - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, nullptr, kQuality, - nullptr), - OK) - << "fail, API allows nullptr dest"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImg.getImageHandle(), ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + nullptr, kQuality, nullptr), + JPEGR_NO_ERROR) + << "fail, API allows nullptr dest"; UhdrCompressedStructWrapper jpgImg2(16, 16); - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg2.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows nullptr dest"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImg.getImageHandle(), ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + jpgImg2.getImageHandle(), kQuality, nullptr), + JPEGR_NO_ERROR) + << "fail, API allows nullptr dest"; } // test p010 input { ASSERT_NE(uHdrLib.encodeJPEGR(nullptr, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows nullptr p010 image"; + JPEGR_NO_ERROR) + << "fail, API allows nullptr p010 image"; UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010); ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100)); - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows nullptr p010 image"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImg.getImageHandle(), ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + jpgImg.getImageHandle(), kQuality, nullptr), + JPEGR_NO_ERROR) + << "fail, API allows nullptr p010 image"; } { UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010); ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED)); ASSERT_TRUE(rawImg.allocateMemory()); - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad p010 color gamut"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImg.getImageHandle(), ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + jpgImg.getImageHandle(), kQuality, nullptr), + JPEGR_NO_ERROR) + << "fail, API allows bad p010 color gamut"; UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_p010); ASSERT_TRUE(rawImg2.setImageColorGamut( - static_cast<ultrahdr_color_gamut>(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1))); + static_cast<ultrahdr_color_gamut>(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1))); ASSERT_TRUE(rawImg2.allocateMemory()); - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg2.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad p010 color gamut"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImg2.getImageHandle(), ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + jpgImg.getImageHandle(), kQuality, nullptr), + JPEGR_NO_ERROR) + << "fail, API allows bad p010 color gamut"; } { @@ -436,37 +436,37 @@ TEST(JpegRTest, EncodeAPI0WithInvalidArgs) { rawImgP010->height = kHeight; ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad image width"; + JPEGR_NO_ERROR) + << "fail, API allows bad image width"; rawImgP010->width = kWidth; rawImgP010->height = kHeight - 1; ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad image height"; + JPEGR_NO_ERROR) + << "fail, API allows bad image height"; rawImgP010->width = 0; rawImgP010->height = kHeight; ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad image width"; + JPEGR_NO_ERROR) + << "fail, API allows bad image width"; rawImgP010->width = kWidth; rawImgP010->height = 0; ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad image height"; + JPEGR_NO_ERROR) + << "fail, API allows bad image height"; rawImgP010->width = kWidth; rawImgP010->height = kHeight; rawImgP010->luma_stride = kWidth - 2; ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad luma stride"; + JPEGR_NO_ERROR) + << "fail, API allows bad luma stride"; rawImgP010->width = kWidth; rawImgP010->height = kHeight; @@ -475,8 +475,8 @@ TEST(JpegRTest, EncodeAPI0WithInvalidArgs) { rawImgP010->chroma_stride = kWidth - 2; ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad chroma stride"; + JPEGR_NO_ERROR) + << "fail, API allows bad chroma stride"; } } @@ -499,30 +499,30 @@ TEST(JpegRTest, EncodeAPI1WithInvalidArgs) { ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(), ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle(), -1, nullptr), - OK) - << "fail, API allows bad jpeg quality factor"; + JPEGR_NO_ERROR) + << "fail, API allows bad jpeg quality factor"; ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(), ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle(), 101, nullptr), - OK) - << "fail, API allows bad jpeg quality factor"; + JPEGR_NO_ERROR) + << "fail, API allows bad jpeg quality factor"; ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(), ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED, jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad hdr transfer function"; + JPEGR_NO_ERROR) + << "fail, API allows bad hdr transfer function"; ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(), static_cast<ultrahdr_transfer_function>( - ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1), + ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1), jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad hdr transfer function"; + JPEGR_NO_ERROR) + << "fail, API allows bad hdr transfer function"; ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(), static_cast<ultrahdr_transfer_function>(-10), jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad hdr transfer function"; + JPEGR_NO_ERROR) + << "fail, API allows bad hdr transfer function"; } // test dest @@ -537,14 +537,14 @@ TEST(JpegRTest, EncodeAPI1WithInvalidArgs) { ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(), ultrahdr_transfer_function::ULTRAHDR_TF_HLG, nullptr, kQuality, nullptr), - OK) - << "fail, API allows nullptr dest"; + JPEGR_NO_ERROR) + << "fail, API allows nullptr dest"; UhdrCompressedStructWrapper jpgImg2(16, 16); ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(), ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg2.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows nullptr dest"; + JPEGR_NO_ERROR) + << "fail, API allows nullptr dest"; } // test p010 input @@ -555,16 +555,16 @@ TEST(JpegRTest, EncodeAPI1WithInvalidArgs) { ASSERT_NE(uHdrLib.encodeJPEGR(nullptr, rawImg2.getImageHandle(), ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows nullptr p010 image"; + JPEGR_NO_ERROR) + << "fail, API allows nullptr p010 image"; UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010); ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100)); ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(), ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows nullptr p010 image"; + JPEGR_NO_ERROR) + << "fail, API allows nullptr p010 image"; } { @@ -581,74 +581,74 @@ TEST(JpegRTest, EncodeAPI1WithInvalidArgs) { rawImgP010->width = kWidth; rawImgP010->height = kHeight; rawImgP010->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad p010 color gamut"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImgP010, rawImg420, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + jpgImg.getImageHandle(), kQuality, nullptr), + JPEGR_NO_ERROR) + << "fail, API allows bad p010 color gamut"; rawImgP010->width = kWidth; rawImgP010->height = kHeight; rawImgP010->colorGamut = - static_cast<ultrahdr_color_gamut>(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1); - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad p010 color gamut"; + static_cast<ultrahdr_color_gamut>(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1); + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImgP010, rawImg420, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + jpgImg.getImageHandle(), kQuality, nullptr), + JPEGR_NO_ERROR) + << "fail, API allows bad p010 color gamut"; rawImgP010->width = kWidth - 1; rawImgP010->height = kHeight; rawImgP010->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad image width"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImgP010, rawImg420, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + jpgImg.getImageHandle(), kQuality, nullptr), + JPEGR_NO_ERROR) + << "fail, API allows bad image width"; rawImgP010->width = kWidth; rawImgP010->height = kHeight - 1; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad image height"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImgP010, rawImg420, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + jpgImg.getImageHandle(), kQuality, nullptr), + JPEGR_NO_ERROR) + << "fail, API allows bad image height"; rawImgP010->width = 0; rawImgP010->height = kHeight; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad image width"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImgP010, rawImg420, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + jpgImg.getImageHandle(), kQuality, nullptr), + JPEGR_NO_ERROR) + << "fail, API allows bad image width"; rawImgP010->width = kWidth; rawImgP010->height = 0; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad image height"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImgP010, rawImg420, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + jpgImg.getImageHandle(), kQuality, nullptr), + JPEGR_NO_ERROR) + << "fail, API allows bad image height"; rawImgP010->width = kWidth; rawImgP010->height = kHeight; rawImgP010->luma_stride = kWidth - 2; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad luma stride"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImgP010, rawImg420, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + jpgImg.getImageHandle(), kQuality, nullptr), + JPEGR_NO_ERROR) + << "fail, API allows bad luma stride"; rawImgP010->width = kWidth; rawImgP010->height = kHeight; rawImgP010->luma_stride = kWidth + 64; rawImgP010->chroma_data = rawImgP010->data; rawImgP010->chroma_stride = kWidth - 2; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad chroma stride"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImgP010, rawImg420, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + jpgImg.getImageHandle(), kQuality, nullptr), + JPEGR_NO_ERROR) + << "fail, API allows bad chroma stride"; } // test 420 input @@ -659,16 +659,16 @@ TEST(JpegRTest, EncodeAPI1WithInvalidArgs) { ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), nullptr, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows nullptr 420 image"; + JPEGR_NO_ERROR) + << "fail, API allows nullptr 420 image"; UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420); ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100)); ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(), ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows nullptr 420 image"; + JPEGR_NO_ERROR) + << "fail, API allows nullptr 420 image"; } { const int kWidth = 32, kHeight = 32; @@ -684,74 +684,74 @@ TEST(JpegRTest, EncodeAPI1WithInvalidArgs) { rawImg420->width = kWidth; rawImg420->height = kHeight; rawImg420->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad 420 color gamut"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImgP010, rawImg420, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + jpgImg.getImageHandle(), kQuality, nullptr), + JPEGR_NO_ERROR) + << "fail, API allows bad 420 color gamut"; rawImg420->width = kWidth; rawImg420->height = kHeight; rawImg420->colorGamut = - static_cast<ultrahdr_color_gamut>(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1); - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad 420 color gamut"; + static_cast<ultrahdr_color_gamut>(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1); + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImgP010, rawImg420, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + jpgImg.getImageHandle(), kQuality, nullptr), + JPEGR_NO_ERROR) + << "fail, API allows bad 420 color gamut"; rawImg420->width = kWidth - 1; rawImg420->height = kHeight; rawImg420->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad image width for 420"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImgP010, rawImg420, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + jpgImg.getImageHandle(), kQuality, nullptr), + JPEGR_NO_ERROR) + << "fail, API allows bad image width for 420"; rawImg420->width = kWidth; rawImg420->height = kHeight - 1; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad image height for 420"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImgP010, rawImg420, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + jpgImg.getImageHandle(), kQuality, nullptr), + JPEGR_NO_ERROR) + << "fail, API allows bad image height for 420"; rawImg420->width = 0; rawImg420->height = kHeight; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad image width for 420"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImgP010, rawImg420, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + jpgImg.getImageHandle(), kQuality, nullptr), + JPEGR_NO_ERROR) + << "fail, API allows bad image width for 420"; rawImg420->width = kWidth; rawImg420->height = 0; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad image height for 420"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImgP010, rawImg420, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + jpgImg.getImageHandle(), kQuality, nullptr), + JPEGR_NO_ERROR) + << "fail, API allows bad image height for 420"; rawImg420->width = kWidth; rawImg420->height = kHeight; rawImg420->luma_stride = kWidth - 2; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad luma stride for 420"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImgP010, rawImg420, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + jpgImg.getImageHandle(), kQuality, nullptr), + JPEGR_NO_ERROR) + << "fail, API allows bad luma stride for 420"; rawImg420->width = kWidth; rawImg420->height = kHeight; rawImg420->luma_stride = 0; rawImg420->chroma_data = rawImgP010->data; rawImg420->chroma_stride = kWidth / 2 - 2; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK) - << "fail, API allows bad chroma stride for 420"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImgP010, rawImg420, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + jpgImg.getImageHandle(), kQuality, nullptr), + JPEGR_NO_ERROR) + << "fail, API allows bad chroma stride for 420"; } } @@ -771,25 +771,23 @@ TEST(JpegRTest, EncodeAPI2WithInvalidArgs) { ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709)); ASSERT_TRUE(rawImg2.allocateMemory()); - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(), - jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad hdr transfer function"; + ASSERT_NE(uHdrLib.encodeJPEGR( + rawImg.getImageHandle(), rawImg2.getImageHandle(), jpgImg.getImageHandle(), + ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED, jpgImg.getImageHandle()), + JPEGR_NO_ERROR) + << "fail, API allows bad hdr transfer function"; ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(), jpgImg.getImageHandle(), static_cast<ultrahdr_transfer_function>( - ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1), + ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1), jpgImg.getImageHandle()), - OK) - << "fail, API allows bad hdr transfer function"; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(), - jpgImg.getImageHandle(), - static_cast<ultrahdr_transfer_function>(-10), - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad hdr transfer function"; + JPEGR_NO_ERROR) + << "fail, API allows bad hdr transfer function"; + ASSERT_NE(uHdrLib.encodeJPEGR( + rawImg.getImageHandle(), rawImg2.getImageHandle(), jpgImg.getImageHandle(), + static_cast<ultrahdr_transfer_function>(-10), jpgImg.getImageHandle()), + JPEGR_NO_ERROR) + << "fail, API allows bad hdr transfer function"; } // test dest @@ -804,15 +802,14 @@ TEST(JpegRTest, EncodeAPI2WithInvalidArgs) { ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(), jpgImg.getImageHandle(), ultrahdr_transfer_function::ULTRAHDR_TF_HLG, nullptr), - OK) - << "fail, API allows nullptr dest"; + JPEGR_NO_ERROR) + << "fail, API allows nullptr dest"; UhdrCompressedStructWrapper jpgImg2(16, 16); - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(), - jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg2.getImageHandle()), - OK) - << "fail, API allows nullptr dest"; + ASSERT_NE(uHdrLib.encodeJPEGR( + rawImg.getImageHandle(), rawImg2.getImageHandle(), jpgImg.getImageHandle(), + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg2.getImageHandle()), + JPEGR_NO_ERROR) + << "fail, API allows nullptr dest"; } // test compressed image @@ -824,18 +821,17 @@ TEST(JpegRTest, EncodeAPI2WithInvalidArgs) { ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709)); ASSERT_TRUE(rawImg2.allocateMemory()); - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(), nullptr, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows nullptr for compressed image"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(), nullptr, + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle()), + JPEGR_NO_ERROR) + << "fail, API allows nullptr for compressed image"; UhdrCompressedStructWrapper jpgImg2(16, 16); - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(), - jpgImg2.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows nullptr for compressed image"; + ASSERT_NE(uHdrLib.encodeJPEGR( + rawImg.getImageHandle(), rawImg2.getImageHandle(), jpgImg2.getImageHandle(), + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle()), + JPEGR_NO_ERROR) + << "fail, API allows nullptr for compressed image"; } // test p010 input @@ -843,20 +839,19 @@ TEST(JpegRTest, EncodeAPI2WithInvalidArgs) { UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420); ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709)); ASSERT_TRUE(rawImg2.allocateMemory()); - ASSERT_NE(uHdrLib.encodeJPEGR(nullptr, rawImg2.getImageHandle(), jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows nullptr p010 image"; + ASSERT_NE( + uHdrLib.encodeJPEGR(nullptr, rawImg2.getImageHandle(), jpgImg.getImageHandle(), + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle()), + JPEGR_NO_ERROR) + << "fail, API allows nullptr p010 image"; UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010); ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100)); - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(), - jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows nullptr p010 image"; + ASSERT_NE(uHdrLib.encodeJPEGR( + rawImg.getImageHandle(), rawImg2.getImageHandle(), jpgImg.getImageHandle(), + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle()), + JPEGR_NO_ERROR) + << "fail, API allows nullptr p010 image"; } { @@ -873,74 +868,74 @@ TEST(JpegRTest, EncodeAPI2WithInvalidArgs) { rawImgP010->width = kWidth; rawImgP010->height = kHeight; rawImgP010->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad p010 color gamut"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(), + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle()), + JPEGR_NO_ERROR) + << "fail, API allows bad p010 color gamut"; rawImgP010->width = kWidth; rawImgP010->height = kHeight; rawImgP010->colorGamut = - static_cast<ultrahdr_color_gamut>(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1); - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad p010 color gamut"; + static_cast<ultrahdr_color_gamut>(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1); + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(), + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle()), + JPEGR_NO_ERROR) + << "fail, API allows bad p010 color gamut"; rawImgP010->width = kWidth - 1; rawImgP010->height = kHeight; rawImgP010->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad image width"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(), + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle()), + JPEGR_NO_ERROR) + << "fail, API allows bad image width"; rawImgP010->width = kWidth; rawImgP010->height = kHeight - 1; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad image height"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(), + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle()), + JPEGR_NO_ERROR) + << "fail, API allows bad image height"; rawImgP010->width = 0; rawImgP010->height = kHeight; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad image width"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(), + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle()), + JPEGR_NO_ERROR) + << "fail, API allows bad image width"; rawImgP010->width = kWidth; rawImgP010->height = 0; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad image height"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(), + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle()), + JPEGR_NO_ERROR) + << "fail, API allows bad image height"; rawImgP010->width = kWidth; rawImgP010->height = kHeight; rawImgP010->luma_stride = kWidth - 2; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad luma stride"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(), + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle()), + JPEGR_NO_ERROR) + << "fail, API allows bad luma stride"; rawImgP010->width = kWidth; rawImgP010->height = kHeight; rawImgP010->luma_stride = kWidth + 64; rawImgP010->chroma_data = rawImgP010->data; rawImgP010->chroma_stride = kWidth - 2; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad chroma stride"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(), + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle()), + JPEGR_NO_ERROR) + << "fail, API allows bad chroma stride"; } // test 420 input @@ -948,20 +943,19 @@ TEST(JpegRTest, EncodeAPI2WithInvalidArgs) { UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010); ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100)); ASSERT_TRUE(rawImg.allocateMemory()); - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), nullptr, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows nullptr 420 image"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImg.getImageHandle(), nullptr, jpgImg.getImageHandle(), + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle()), + JPEGR_NO_ERROR) + << "fail, API allows nullptr 420 image"; UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420); ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100)); - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(), - jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows nullptr 420 image"; + ASSERT_NE(uHdrLib.encodeJPEGR( + rawImg.getImageHandle(), rawImg2.getImageHandle(), jpgImg.getImageHandle(), + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle()), + JPEGR_NO_ERROR) + << "fail, API allows nullptr 420 image"; } { const int kWidth = 32, kHeight = 32; @@ -977,74 +971,74 @@ TEST(JpegRTest, EncodeAPI2WithInvalidArgs) { rawImg420->width = kWidth; rawImg420->height = kHeight; rawImg420->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad 420 color gamut"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(), + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle()), + JPEGR_NO_ERROR) + << "fail, API allows bad 420 color gamut"; rawImg420->width = kWidth; rawImg420->height = kHeight; rawImg420->colorGamut = - static_cast<ultrahdr_color_gamut>(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1); - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad 420 color gamut"; + static_cast<ultrahdr_color_gamut>(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1); + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(), + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle()), + JPEGR_NO_ERROR) + << "fail, API allows bad 420 color gamut"; rawImg420->width = kWidth - 1; rawImg420->height = kHeight; rawImg420->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad image width for 420"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(), + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle()), + JPEGR_NO_ERROR) + << "fail, API allows bad image width for 420"; rawImg420->width = kWidth; rawImg420->height = kHeight - 1; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad image height for 420"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(), + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle()), + JPEGR_NO_ERROR) + << "fail, API allows bad image height for 420"; rawImg420->width = 0; rawImg420->height = kHeight; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad image width for 420"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(), + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle()), + JPEGR_NO_ERROR) + << "fail, API allows bad image width for 420"; rawImg420->width = kWidth; rawImg420->height = 0; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad image height for 420"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(), + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle()), + JPEGR_NO_ERROR) + << "fail, API allows bad image height for 420"; rawImg420->width = kWidth; rawImg420->height = kHeight; rawImg420->luma_stride = kWidth - 2; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad luma stride for 420"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(), + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle()), + JPEGR_NO_ERROR) + << "fail, API allows bad luma stride for 420"; rawImg420->width = kWidth; rawImg420->height = kHeight; rawImg420->luma_stride = 0; rawImg420->chroma_data = rawImgP010->data; rawImg420->chroma_stride = kWidth / 2 - 2; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad chroma stride for 420"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(), + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle()), + JPEGR_NO_ERROR) + << "fail, API allows bad chroma stride for 420"; } } @@ -1064,19 +1058,19 @@ TEST(JpegRTest, EncodeAPI3WithInvalidArgs) { ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(), ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED, jpgImg.getImageHandle()), - OK) - << "fail, API allows bad hdr transfer function"; + JPEGR_NO_ERROR) + << "fail, API allows bad hdr transfer function"; ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(), static_cast<ultrahdr_transfer_function>( - ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1), - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad hdr transfer function"; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(), - static_cast<ultrahdr_transfer_function>(-10), + ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1), jpgImg.getImageHandle()), - OK) - << "fail, API allows bad hdr transfer function"; + JPEGR_NO_ERROR) + << "fail, API allows bad hdr transfer function"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(), + static_cast<ultrahdr_transfer_function>(-10), jpgImg.getImageHandle()), + JPEGR_NO_ERROR) + << "fail, API allows bad hdr transfer function"; } // test dest @@ -1087,14 +1081,14 @@ TEST(JpegRTest, EncodeAPI3WithInvalidArgs) { ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(), ultrahdr_transfer_function::ULTRAHDR_TF_HLG, nullptr), - OK) - << "fail, API allows nullptr dest"; + JPEGR_NO_ERROR) + << "fail, API allows nullptr dest"; UhdrCompressedStructWrapper jpgImg2(16, 16); - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg2.getImageHandle()), - OK) - << "fail, API allows nullptr dest"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(), + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg2.getImageHandle()), + JPEGR_NO_ERROR) + << "fail, API allows nullptr dest"; } // test compressed image @@ -1103,34 +1097,34 @@ TEST(JpegRTest, EncodeAPI3WithInvalidArgs) { ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100)); ASSERT_TRUE(rawImg.allocateMemory()); - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), nullptr, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows nullptr for compressed image"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImg.getImageHandle(), nullptr, + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle()), + JPEGR_NO_ERROR) + << "fail, API allows nullptr for compressed image"; UhdrCompressedStructWrapper jpgImg2(16, 16); - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg2.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows nullptr for compressed image"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg2.getImageHandle(), + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle()), + JPEGR_NO_ERROR) + << "fail, API allows nullptr for compressed image"; } // test p010 input { - ASSERT_NE(uHdrLib.encodeJPEGR(nullptr, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows nullptr p010 image"; + ASSERT_NE( + uHdrLib.encodeJPEGR(nullptr, jpgImg.getImageHandle(), + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle()), + JPEGR_NO_ERROR) + << "fail, API allows nullptr p010 image"; UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010); ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100)); - ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows nullptr p010 image"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(), + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle()), + JPEGR_NO_ERROR) + << "fail, API allows nullptr p010 image"; } { @@ -1143,74 +1137,74 @@ TEST(JpegRTest, EncodeAPI3WithInvalidArgs) { rawImgP010->width = kWidth; rawImgP010->height = kHeight; rawImgP010->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad p010 color gamut"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(), + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle()), + JPEGR_NO_ERROR) + << "fail, API allows bad p010 color gamut"; rawImgP010->width = kWidth; rawImgP010->height = kHeight; rawImgP010->colorGamut = - static_cast<ultrahdr_color_gamut>(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1); - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad p010 color gamut"; + static_cast<ultrahdr_color_gamut>(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1); + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(), + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle()), + JPEGR_NO_ERROR) + << "fail, API allows bad p010 color gamut"; rawImgP010->width = kWidth - 1; rawImgP010->height = kHeight; rawImgP010->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad image width"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(), + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle()), + JPEGR_NO_ERROR) + << "fail, API allows bad image width"; rawImgP010->width = kWidth; rawImgP010->height = kHeight - 1; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad image height"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(), + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle()), + JPEGR_NO_ERROR) + << "fail, API allows bad image height"; rawImgP010->width = 0; rawImgP010->height = kHeight; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad image width"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(), + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle()), + JPEGR_NO_ERROR) + << "fail, API allows bad image width"; rawImgP010->width = kWidth; rawImgP010->height = 0; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad image height"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(), + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle()), + JPEGR_NO_ERROR) + << "fail, API allows bad image height"; rawImgP010->width = kWidth; rawImgP010->height = kHeight; rawImgP010->luma_stride = kWidth - 2; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad luma stride"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(), + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle()), + JPEGR_NO_ERROR) + << "fail, API allows bad luma stride"; rawImgP010->width = kWidth; rawImgP010->height = kHeight; rawImgP010->luma_stride = kWidth + 64; rawImgP010->chroma_data = rawImgP010->data; rawImgP010->chroma_stride = kWidth - 2; - ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK) - << "fail, API allows bad chroma stride"; + ASSERT_NE( + uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(), + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle()), + JPEGR_NO_ERROR) + << "fail, API allows bad chroma stride"; } } @@ -1223,30 +1217,30 @@ TEST(JpegRTest, EncodeAPI4WithInvalidArgs) { // test dest ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), nullptr, nullptr), - OK) - << "fail, API allows nullptr dest"; + JPEGR_NO_ERROR) + << "fail, API allows nullptr dest"; ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), nullptr, jpgImg2.getImageHandle()), - OK) - << "fail, API allows nullptr dest"; + JPEGR_NO_ERROR) + << "fail, API allows nullptr dest"; // test primary image ASSERT_NE(uHdrLib.encodeJPEGR(nullptr, jpgImg.getImageHandle(), nullptr, jpgImg.getImageHandle()), - OK) - << "fail, API allows nullptr primary image"; + JPEGR_NO_ERROR) + << "fail, API allows nullptr primary image"; ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg2.getImageHandle(), jpgImg.getImageHandle(), nullptr, jpgImg.getImageHandle()), - OK) - << "fail, API allows nullptr primary image"; + JPEGR_NO_ERROR) + << "fail, API allows nullptr primary image"; // test gain map ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), nullptr, nullptr, jpgImg.getImageHandle()), - OK) - << "fail, API allows nullptr gain map image"; + JPEGR_NO_ERROR) + << "fail, API allows nullptr gain map image"; ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg2.getImageHandle(), nullptr, jpgImg.getImageHandle()), - OK) - << "fail, API allows nullptr gain map image"; + JPEGR_NO_ERROR) + << "fail, API allows nullptr gain map image"; // test metadata ultrahdr_metadata_struct good_metadata; @@ -1263,50 +1257,50 @@ TEST(JpegRTest, EncodeAPI4WithInvalidArgs) { metadata.version = "1.1"; ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata, jpgImg.getImageHandle()), - OK) - << "fail, API allows bad metadata version"; + JPEGR_NO_ERROR) + << "fail, API allows bad metadata version"; metadata = good_metadata; metadata.minContentBoost = 3.0f; ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata, jpgImg.getImageHandle()), - OK) - << "fail, API allows bad metadata content boost"; + JPEGR_NO_ERROR) + << "fail, API allows bad metadata content boost"; metadata = good_metadata; metadata.gamma = -0.1f; ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata, jpgImg.getImageHandle()), - OK) - << "fail, API allows bad metadata gamma"; + JPEGR_NO_ERROR) + << "fail, API allows bad metadata gamma"; metadata = good_metadata; metadata.offsetSdr = -0.1f; ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata, jpgImg.getImageHandle()), - OK) - << "fail, API allows bad metadata offset sdr"; + JPEGR_NO_ERROR) + << "fail, API allows bad metadata offset sdr"; metadata = good_metadata; metadata.offsetHdr = -0.1f; ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata, jpgImg.getImageHandle()), - OK) - << "fail, API allows bad metadata offset hdr"; + JPEGR_NO_ERROR) + << "fail, API allows bad metadata offset hdr"; metadata = good_metadata; metadata.hdrCapacityMax = 0.5f; ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata, jpgImg.getImageHandle()), - OK) - << "fail, API allows bad metadata hdr capacity max"; + JPEGR_NO_ERROR) + << "fail, API allows bad metadata hdr capacity max"; metadata = good_metadata; metadata.hdrCapacityMin = 0.5f; ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata, jpgImg.getImageHandle()), - OK) - << "fail, API allows bad metadata hdr capacity min"; + JPEGR_NO_ERROR) + << "fail, API allows bad metadata hdr capacity min"; } /* Test Decode API invalid arguments */ @@ -1320,33 +1314,33 @@ TEST(JpegRTest, DecodeAPIWithInvalidArgs) { destImage.data = data.get(); // test jpegr image - ASSERT_NE(uHdrLib.decodeJPEGR(nullptr, &destImage), OK) - << "fail, API allows nullptr for jpegr img"; - ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), &destImage), OK) - << "fail, API allows nullptr for jpegr img"; + ASSERT_NE(uHdrLib.decodeJPEGR(nullptr, &destImage), JPEGR_NO_ERROR) + << "fail, API allows nullptr for jpegr img"; + ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), &destImage), JPEGR_NO_ERROR) + << "fail, API allows nullptr for jpegr img"; ASSERT_TRUE(jpgImg.allocateMemory()); // test dest image - ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), nullptr), OK) - << "fail, API allows nullptr for dest"; + ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), nullptr), JPEGR_NO_ERROR) + << "fail, API allows nullptr for dest"; destImage.data = nullptr; - ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), &destImage), OK) - << "fail, API allows nullptr for dest"; + ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), &destImage), JPEGR_NO_ERROR) + << "fail, API allows nullptr for dest"; destImage.data = data.get(); // test max display boost - ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), &destImage, 0.5), OK) - << "fail, API allows invalid max display boost"; + ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), &destImage, 0.5), JPEGR_NO_ERROR) + << "fail, API allows invalid max display boost"; // test output format ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), &destImage, FLT_MAX, nullptr, static_cast<ultrahdr_output_format>(-1)), - OK) - << "fail, API allows invalid output format"; + JPEGR_NO_ERROR) + << "fail, API allows invalid output format"; ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), &destImage, FLT_MAX, nullptr, static_cast<ultrahdr_output_format>(ULTRAHDR_OUTPUT_MAX + 1)), - OK) - << "fail, API allows invalid output format"; + JPEGR_NO_ERROR) + << "fail, API allows invalid output format"; } TEST(JpegRTest, writeXmpThenRead) { @@ -1360,7 +1354,7 @@ TEST(JpegRTest, writeXmpThenRead) { metadata_expected.hdrCapacityMin = 1.0f; metadata_expected.hdrCapacityMax = metadata_expected.maxContentBoost; const std::string nameSpace = "http://ns.adobe.com/xap/1.0/\0"; - const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator + const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator std::string xmp = generateXmpForSecondaryImage(metadata_expected); @@ -1383,10 +1377,10 @@ TEST(JpegRTest, writeXmpThenRead) { } class JpegRAPIEncodeAndDecodeTest - : public ::testing::TestWithParam<std::tuple<ultrahdr_color_gamut, ultrahdr_color_gamut>> { -public: + : public ::testing::TestWithParam<std::tuple<ultrahdr_color_gamut, ultrahdr_color_gamut>> { + public: JpegRAPIEncodeAndDecodeTest() - : mP010ColorGamut(std::get<0>(GetParam())), mYuv420ColorGamut(std::get<1>(GetParam())){}; + : mP010ColorGamut(std::get<0>(GetParam())), mYuv420ColorGamut(std::get<1>(GetParam())){}; const ultrahdr_color_gamut mP010ColorGamut; const ultrahdr_color_gamut mYuv420ColorGamut; @@ -1402,10 +1396,10 @@ TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI0AndDecodeTest) { UhdrCompressedStructWrapper jpgImg(kImageWidth, kImageHeight); ASSERT_TRUE(jpgImg.allocateMemory()); JpegR uHdrLib; - ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle(), kQuality, nullptr), - OK); + ASSERT_EQ( + uHdrLib.encodeJPEGR(rawImg.getImageHandle(), ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + jpgImg.getImageHandle(), kQuality, nullptr), + JPEGR_NO_ERROR); // encode with luma stride set { UhdrUnCompressedStructWrapper rawImg2(kImageWidth, kImageHeight, YCbCr_p010); @@ -1415,10 +1409,10 @@ TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI0AndDecodeTest) { ASSERT_TRUE(rawImg2.loadRawResource(kYCbCrP010FileName)); UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight); ASSERT_TRUE(jpgImg2.allocateMemory()); - ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg2.getImageHandle(), kQuality, nullptr), - OK); + ASSERT_EQ( + uHdrLib.encodeJPEGR(rawImg2.getImageHandle(), ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + jpgImg2.getImageHandle(), kQuality, nullptr), + JPEGR_NO_ERROR); auto jpg1 = jpgImg.getImageHandle(); auto jpg2 = jpgImg2.getImageHandle(); ASSERT_EQ(jpg1->length, jpg2->length); @@ -1434,10 +1428,10 @@ TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI0AndDecodeTest) { ASSERT_TRUE(rawImg2.loadRawResource(kYCbCrP010FileName)); UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight); ASSERT_TRUE(jpgImg2.allocateMemory()); - ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg2.getImageHandle(), kQuality, nullptr), - OK); + ASSERT_EQ( + uHdrLib.encodeJPEGR(rawImg2.getImageHandle(), ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + jpgImg2.getImageHandle(), kQuality, nullptr), + JPEGR_NO_ERROR); auto jpg1 = jpgImg.getImageHandle(); auto jpg2 = jpgImg2.getImageHandle(); ASSERT_EQ(jpg1->length, jpg2->length); @@ -1453,10 +1447,10 @@ TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI0AndDecodeTest) { ASSERT_TRUE(rawImg2.loadRawResource(kYCbCrP010FileName)); UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight); ASSERT_TRUE(jpgImg2.allocateMemory()); - ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg2.getImageHandle(), kQuality, nullptr), - OK); + ASSERT_EQ( + uHdrLib.encodeJPEGR(rawImg2.getImageHandle(), ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + jpgImg2.getImageHandle(), kQuality, nullptr), + JPEGR_NO_ERROR); auto jpg1 = jpgImg.getImageHandle(); auto jpg2 = jpgImg2.getImageHandle(); ASSERT_EQ(jpg1->length, jpg2->length); @@ -1471,10 +1465,10 @@ TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI0AndDecodeTest) { ASSERT_TRUE(rawImg2.loadRawResource(kYCbCrP010FileName)); UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight); ASSERT_TRUE(jpgImg2.allocateMemory()); - ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2.getImageHandle(), - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg2.getImageHandle(), kQuality, nullptr), - OK); + ASSERT_EQ( + uHdrLib.encodeJPEGR(rawImg2.getImageHandle(), ultrahdr_transfer_function::ULTRAHDR_TF_HLG, + jpgImg2.getImageHandle(), kQuality, nullptr), + JPEGR_NO_ERROR); auto jpg1 = jpgImg.getImageHandle(); auto jpg2 = jpgImg2.getImageHandle(); ASSERT_EQ(jpg1->length, jpg2->length); @@ -1507,7 +1501,7 @@ TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI1AndDecodeTest) { ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg420.getImageHandle(), ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle(), kQuality, nullptr), - OK); + JPEGR_NO_ERROR); // encode with luma stride set p010 { UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010); @@ -1520,7 +1514,7 @@ TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI1AndDecodeTest) { ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(), ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg2.getImageHandle(), kQuality, nullptr), - OK); + JPEGR_NO_ERROR); auto jpg1 = jpgImg.getImageHandle(); auto jpg2 = jpgImg2.getImageHandle(); ASSERT_EQ(jpg1->length, jpg2->length); @@ -1539,7 +1533,7 @@ TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI1AndDecodeTest) { ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(), ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg2.getImageHandle(), kQuality, nullptr), - OK); + JPEGR_NO_ERROR); auto jpg1 = jpgImg.getImageHandle(); auto jpg2 = jpgImg2.getImageHandle(); ASSERT_EQ(jpg1->length, jpg2->length); @@ -1558,7 +1552,7 @@ TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI1AndDecodeTest) { ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(), ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg2.getImageHandle(), kQuality, nullptr), - OK); + JPEGR_NO_ERROR); auto jpg1 = jpgImg.getImageHandle(); auto jpg2 = jpgImg2.getImageHandle(); ASSERT_EQ(jpg1->length, jpg2->length); @@ -1576,7 +1570,7 @@ TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI1AndDecodeTest) { ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(), ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg2.getImageHandle(), kQuality, nullptr), - OK); + JPEGR_NO_ERROR); auto jpg1 = jpgImg.getImageHandle(); auto jpg2 = jpgImg2.getImageHandle(); ASSERT_EQ(jpg1->length, jpg2->length); @@ -1594,7 +1588,7 @@ TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI1AndDecodeTest) { ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(), ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg2.getImageHandle(), kQuality, nullptr), - OK); + JPEGR_NO_ERROR); auto jpg1 = jpgImg.getImageHandle(); auto jpg2 = jpgImg2.getImageHandle(); ASSERT_EQ(jpg1->length, jpg2->length); @@ -1613,7 +1607,7 @@ TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI1AndDecodeTest) { ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(), ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg2.getImageHandle(), kQuality, nullptr), - OK); + JPEGR_NO_ERROR); auto jpg1 = jpgImg.getImageHandle(); auto jpg2 = jpgImg2.getImageHandle(); ASSERT_EQ(jpg1->length, jpg2->length); @@ -1632,7 +1626,7 @@ TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI1AndDecodeTest) { ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(), ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg2.getImageHandle(), kQuality, nullptr), - OK); + JPEGR_NO_ERROR); auto jpg1 = jpgImg.getImageHandle(); auto jpg2 = jpgImg2.getImageHandle(); ASSERT_EQ(jpg1->length, jpg2->length); @@ -1650,7 +1644,7 @@ TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI1AndDecodeTest) { ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(), ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg2.getImageHandle(), kQuality, nullptr), - OK); + JPEGR_NO_ERROR); auto jpg1 = jpgImg.getImageHandle(); auto jpg2 = jpgImg2.getImageHandle(); ASSERT_EQ(jpg1->length, jpg2->length); @@ -1685,10 +1679,10 @@ TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI2AndDecodeTest) { auto sdr = jpgSdr.getImageHandle(); ASSERT_TRUE(readFile(kSdrJpgFileName, sdr->data, sdr->maxLength, sdr->length)); JpegR uHdrLib; - ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg420.getImageHandle(), sdr, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK); + ASSERT_EQ( + uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg420.getImageHandle(), sdr, + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle()), + JPEGR_NO_ERROR); // encode with luma stride set { UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010); @@ -1698,10 +1692,10 @@ TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI2AndDecodeTest) { ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName)); UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight); ASSERT_TRUE(jpgImg2.allocateMemory()); - ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(), sdr, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg2.getImageHandle()), - OK); + ASSERT_EQ( + uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(), sdr, + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg2.getImageHandle()), + JPEGR_NO_ERROR); auto jpg1 = jpgImg.getImageHandle(); auto jpg2 = jpgImg2.getImageHandle(); ASSERT_EQ(jpg1->length, jpg2->length); @@ -1717,10 +1711,10 @@ TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI2AndDecodeTest) { ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName)); UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight); ASSERT_TRUE(jpgImg2.allocateMemory()); - ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(), sdr, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg2.getImageHandle()), - OK); + ASSERT_EQ( + uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(), sdr, + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg2.getImageHandle()), + JPEGR_NO_ERROR); auto jpg1 = jpgImg.getImageHandle(); auto jpg2 = jpgImg2.getImageHandle(); ASSERT_EQ(jpg1->length, jpg2->length); @@ -1736,10 +1730,10 @@ TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI2AndDecodeTest) { ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName)); UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight); ASSERT_TRUE(jpgImg2.allocateMemory()); - ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(), sdr, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg2.getImageHandle()), - OK); + ASSERT_EQ( + uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(), sdr, + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg2.getImageHandle()), + JPEGR_NO_ERROR); auto jpg1 = jpgImg.getImageHandle(); auto jpg2 = jpgImg2.getImageHandle(); ASSERT_EQ(jpg1->length, jpg2->length); @@ -1754,10 +1748,10 @@ TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI2AndDecodeTest) { ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName)); UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight); ASSERT_TRUE(jpgImg2.allocateMemory()); - ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(), sdr, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg2.getImageHandle()), - OK); + ASSERT_EQ( + uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(), sdr, + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg2.getImageHandle()), + JPEGR_NO_ERROR); auto jpg1 = jpgImg.getImageHandle(); auto jpg2 = jpgImg2.getImageHandle(); ASSERT_EQ(jpg1->length, jpg2->length); @@ -1773,10 +1767,10 @@ TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI2AndDecodeTest) { ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName)); UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight); ASSERT_TRUE(jpgImg2.allocateMemory()); - ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(), sdr, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg2.getImageHandle()), - OK); + ASSERT_EQ( + uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(), sdr, + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg2.getImageHandle()), + JPEGR_NO_ERROR); auto jpg1 = jpgImg.getImageHandle(); auto jpg2 = jpgImg2.getImageHandle(); ASSERT_EQ(jpg1->length, jpg2->length); @@ -1792,10 +1786,10 @@ TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI2AndDecodeTest) { ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName)); UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight); ASSERT_TRUE(jpgImg2.allocateMemory()); - ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(), sdr, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg2.getImageHandle()), - OK); + ASSERT_EQ( + uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(), sdr, + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg2.getImageHandle()), + JPEGR_NO_ERROR); auto jpg1 = jpgImg.getImageHandle(); auto jpg2 = jpgImg2.getImageHandle(); ASSERT_EQ(jpg1->length, jpg2->length); @@ -1826,10 +1820,10 @@ TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI3AndDecodeTest) { auto sdr = jpgSdr.getImageHandle(); ASSERT_TRUE(readFile(kSdrJpgFileName, sdr->data, sdr->maxLength, sdr->length)); JpegR uHdrLib; - ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), sdr, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg.getImageHandle()), - OK); + ASSERT_EQ( + uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), sdr, + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle()), + JPEGR_NO_ERROR); // encode with luma stride set { UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010); @@ -1839,10 +1833,10 @@ TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI3AndDecodeTest) { ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName)); UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight); ASSERT_TRUE(jpgImg2.allocateMemory()); - ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), sdr, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg2.getImageHandle()), - OK); + ASSERT_EQ( + uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), sdr, + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg2.getImageHandle()), + JPEGR_NO_ERROR); auto jpg1 = jpgImg.getImageHandle(); auto jpg2 = jpgImg2.getImageHandle(); ASSERT_EQ(jpg1->length, jpg2->length); @@ -1858,10 +1852,10 @@ TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI3AndDecodeTest) { ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName)); UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight); ASSERT_TRUE(jpgImg2.allocateMemory()); - ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), sdr, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg2.getImageHandle()), - OK); + ASSERT_EQ( + uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), sdr, + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg2.getImageHandle()), + JPEGR_NO_ERROR); auto jpg1 = jpgImg.getImageHandle(); auto jpg2 = jpgImg2.getImageHandle(); ASSERT_EQ(jpg1->length, jpg2->length); @@ -1877,10 +1871,10 @@ TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI3AndDecodeTest) { ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName)); UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight); ASSERT_TRUE(jpgImg2.allocateMemory()); - ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), sdr, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg2.getImageHandle()), - OK); + ASSERT_EQ( + uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), sdr, + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg2.getImageHandle()), + JPEGR_NO_ERROR); auto jpg1 = jpgImg.getImageHandle(); auto jpg2 = jpgImg2.getImageHandle(); ASSERT_EQ(jpg1->length, jpg2->length); @@ -1895,10 +1889,10 @@ TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI3AndDecodeTest) { ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName)); UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight); ASSERT_TRUE(jpgImg2.allocateMemory()); - ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), sdr, - ultrahdr_transfer_function::ULTRAHDR_TF_HLG, - jpgImg2.getImageHandle()), - OK); + ASSERT_EQ( + uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), sdr, + ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg2.getImageHandle()), + JPEGR_NO_ERROR); auto jpg1 = jpgImg.getImageHandle(); auto jpg2 = jpgImg2.getImageHandle(); ASSERT_EQ(jpg1->length, jpg2->length); @@ -1917,18 +1911,37 @@ TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI3AndDecodeTest) { } INSTANTIATE_TEST_SUITE_P( - JpegRAPIParameterizedTests, JpegRAPIEncodeAndDecodeTest, - ::testing::Combine(::testing::Values(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_P3, - ULTRAHDR_COLORGAMUT_BT2100), - ::testing::Values(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_P3, - ULTRAHDR_COLORGAMUT_BT2100))); + JpegRAPIParameterizedTests, JpegRAPIEncodeAndDecodeTest, + ::testing::Combine(::testing::Values(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_P3, + ULTRAHDR_COLORGAMUT_BT2100), + ::testing::Values(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_P3, + ULTRAHDR_COLORGAMUT_BT2100))); // ============================================================================ // Profiling // ============================================================================ +#ifdef _WIN32 +class Profiler { + public: + void timerStart() { QueryPerformanceCounter(&mStartingTime); } + + void timerStop() { QueryPerformanceCounter(&mEndingTime); } + int64_t elapsedTime() { + LARGE_INTEGER frequency; + LARGE_INTEGER elapsedMicroseconds; + QueryPerformanceFrequency(&frequency); + elapsedMicroseconds.QuadPart = mEndingTime.QuadPart - mStartingTime.QuadPart; + return (double)elapsedMicroseconds.QuadPart / (double)frequency.QuadPart * 1000000; + } + + private: + LARGE_INTEGER mStartingTime; + LARGE_INTEGER mEndingTime; +}; +#else class Profiler { -public: + public: void timerStart() { gettimeofday(&mStartingTime, nullptr); } void timerStop() { gettimeofday(&mEndingTime, nullptr); } @@ -1940,19 +1953,20 @@ public: return elapsedMicroseconds.tv_sec * 1000000 + elapsedMicroseconds.tv_usec; } -private: + private: struct timeval mStartingTime; struct timeval mEndingTime; }; +#endif class JpegRBenchmark : public JpegR { -public: + public: void BenchmarkGenerateGainMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr p010Image, ultrahdr_metadata_ptr metadata, jr_uncompressed_ptr map); void BenchmarkApplyGainMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr map, ultrahdr_metadata_ptr metadata, jr_uncompressed_ptr dest); -private: + private: const int kProfileCount = 10; }; @@ -1965,7 +1979,7 @@ void JpegRBenchmark::BenchmarkGenerateGainMap(jr_uncompressed_ptr yuv420Image, Profiler profileGenerateMap; profileGenerateMap.timerStart(); for (auto i = 0; i < kProfileCount; i++) { - ASSERT_EQ(OK, + ASSERT_EQ(JPEGR_NO_ERROR, generateGainMap(yuv420Image, p010Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG, metadata, map)); if (i != kProfileCount - 1) { @@ -1975,7 +1989,7 @@ void JpegRBenchmark::BenchmarkGenerateGainMap(jr_uncompressed_ptr yuv420Image, } profileGenerateMap.timerStop(); ALOGE("Generate Gain Map:- Res = %zu x %zu, time = %f ms", yuv420Image->width, - yuv420Image->height, profileGenerateMap.elapsedTime() / (kProfileCount * 1000.f)); + yuv420Image->height, profileGenerateMap.elapsedTime() / (kProfileCount * 1000.f)); } void JpegRBenchmark::BenchmarkApplyGainMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr map, @@ -1984,9 +1998,8 @@ void JpegRBenchmark::BenchmarkApplyGainMap(jr_uncompressed_ptr yuv420Image, jr_u Profiler profileRecMap; profileRecMap.timerStart(); for (auto i = 0; i < kProfileCount; i++) { - ASSERT_EQ(OK, - applyGainMap(yuv420Image, map, metadata, ULTRAHDR_OUTPUT_HDR_HLG, - metadata->maxContentBoost /* displayBoost */, dest)); + ASSERT_EQ(JPEGR_NO_ERROR, applyGainMap(yuv420Image, map, metadata, ULTRAHDR_OUTPUT_HDR_HLG, + metadata->maxContentBoost /* displayBoost */, dest)); } profileRecMap.timerStop(); ALOGE("Apply Gain Map:- Res = %zu x %zu, time = %f ms", yuv420Image->width, yuv420Image->height, @@ -2002,11 +2015,13 @@ TEST(JpegRTest, ProfileGainMapFuncs) { ASSERT_TRUE(rawImg420.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709)); ASSERT_TRUE(rawImg420.allocateMemory()); ASSERT_TRUE(rawImg420.loadRawResource(kYCbCr420FileName)); - ultrahdr_metadata_struct metadata = {.version = "1.0"}; - jpegr_uncompressed_struct map = {.data = NULL, - .width = 0, - .height = 0, - .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED}; + ultrahdr_metadata_struct metadata; + metadata.version = kJpegrVersion; + jpegr_uncompressed_struct map; + map.data = NULL; + map.width = 0; + map.height = 0; + map.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED; { auto rawImg = rawImgP010.getImageHandle(); if (rawImg->luma_stride == 0) rawImg->luma_stride = rawImg->width; @@ -2027,19 +2042,19 @@ TEST(JpegRTest, ProfileGainMapFuncs) { } JpegRBenchmark benchmark; - ASSERT_NO_FATAL_FAILURE(benchmark.BenchmarkGenerateGainMap(rawImg420.getImageHandle(), - rawImgP010.getImageHandle(), &metadata, - &map)); + ASSERT_NO_FATAL_FAILURE(benchmark.BenchmarkGenerateGainMap( + rawImg420.getImageHandle(), rawImgP010.getImageHandle(), &metadata, &map)); const int dstSize = kImageWidth * kImageWidth * 4; auto bufferDst = std::make_unique<uint8_t[]>(dstSize); - jpegr_uncompressed_struct dest = {.data = bufferDst.get(), - .width = 0, - .height = 0, - .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED}; + jpegr_uncompressed_struct dest; + dest.data = bufferDst.get(); + dest.width = 0; + dest.height = 0; + dest.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED; ASSERT_NO_FATAL_FAILURE( - benchmark.BenchmarkApplyGainMap(rawImg420.getImageHandle(), &map, &metadata, &dest)); + benchmark.BenchmarkApplyGainMap(rawImg420.getImageHandle(), &map, &metadata, &dest)); if (map.data) { delete[] static_cast<uint8_t*>(map.data); @@ -2047,4 +2062,4 @@ TEST(JpegRTest, ProfileGainMapFuncs) { } } -} // namespace ultrahdr +} // namespace ultrahdr diff --git a/tests/ultrahdr_app.cpp b/tests/ultrahdr_app.cpp deleted file mode 100644 index ca237c7..0000000 --- a/tests/ultrahdr_app.cpp +++ /dev/null @@ -1,921 +0,0 @@ -/* - * Copyright 2023 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 <sys/time.h> -#include <unistd.h> - -#include <algorithm> -#include <cmath> -#include <fstream> -#include <iostream> - -#include "ultrahdr/ultrahdrcommon.h" -#include "ultrahdr/gainmapmath.h" -#include "ultrahdr/jpegr.h" - -using namespace ultrahdr; - -const float BT601YUVtoRGBMatrix[9] = - {1, 0, 1.402, 1, (-0.202008 / 0.587), (-0.419198 / 0.587), 1.0, 1.772, 0.0}; -const float BT709YUVtoRGBMatrix[9] = - {1, 0, 1.5748, 1, (-0.13397432 / 0.7152), (-0.33480248 / 0.7152), 1.0, 1.8556, 0.0}; -const float BT2020YUVtoRGBMatrix[9] = - {1, 0, 1.4746, 1, (-0.11156702 / 0.6780), (-0.38737742 / 0.6780), 1, 1.8814, 0}; - -const float BT601RGBtoYUVMatrix[9] = {0.299, - 0.587, - 0.114, - (-0.299 / 1.772), - (-0.587 / 1.772), - 0.5, - 0.5, - (-0.587 / 1.402), - (-0.114 / 1.402)}; -const float BT709RGBtoYUVMatrix[9] = {0.2126, - 0.7152, - 0.0722, - (-0.2126 / 1.8556), - (-0.7152 / 1.8556), - 0.5, - 0.5, - (-0.7152 / 1.5748), - (-0.0722 / 1.5748)}; -const float BT2020RGBtoYUVMatrix[9] = {0.2627, - 0.6780, - 0.0593, - (-0.2627 / 1.8814), - (-0.6780 / 1.8814), - 0.5, - 0.5, - (-0.6780 / 1.4746), - (-0.0593 / 1.4746)}; - -//#define PROFILE_ENABLE 1 -class Profiler { -public: - void timerStart() { gettimeofday(&mStartingTime, nullptr); } - - void timerStop() { gettimeofday(&mEndingTime, nullptr); } - - int64_t elapsedTime() { - struct timeval elapsedMicroseconds; - elapsedMicroseconds.tv_sec = mEndingTime.tv_sec - mStartingTime.tv_sec; - elapsedMicroseconds.tv_usec = mEndingTime.tv_usec - mStartingTime.tv_usec; - return elapsedMicroseconds.tv_sec * 1000000 + elapsedMicroseconds.tv_usec; - } - -private: - struct timeval mStartingTime; - struct timeval mEndingTime; -}; - -static bool loadFile(const char* filename, void*& result, int length) { - std::ifstream ifd(filename, std::ios::binary | std::ios::ate); - if (ifd.good()) { - int size = ifd.tellg(); - if (size < length) { - std::cerr << "requested to read " << length << " bytes from file : " << filename - << ", file contains only " << size << " bytes" << std::endl; - return false; - } - ifd.seekg(0, std::ios::beg); - result = malloc(length); - if (result == nullptr) { - std::cerr << "failed to allocate memory to store contents of file : " << filename - << std::endl; - return false; - } - ifd.read(static_cast<char*>(result), length); - return true; - } - 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()) { - ofd.write(static_cast<char*>(result), length); - return true; - } - 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) - : mP010File(p010File), - mYuv420File(yuv420File), - mYuv420JpegFile(yuv420JpegFile), - mJpegRFile(nullptr), - mWidth(width), - mHeight(height), - mP010Cg(p010Cg), - mYuv420Cg(yuv420Cg), - mTf(tf), - mQuality(quality), - mOf(of), - mMode(0){}; - - UltraHdrAppInput(const char* jpegRFile, ultrahdr_output_format of = ULTRAHDR_OUTPUT_HDR_HLG) - : mP010File(nullptr), - mYuv420File(nullptr), - mJpegRFile(jpegRFile), - mWidth(0), - mHeight(0), - mP010Cg(ULTRAHDR_COLORGAMUT_UNSPECIFIED), - mYuv420Cg(ULTRAHDR_COLORGAMUT_UNSPECIFIED), - mTf(ULTRAHDR_TF_UNSPECIFIED), - mQuality(100), - mOf(of), - 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); - 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(); - bool fillP010ImageHandle(); - bool convertP010ToRGBImage(); - bool fillYuv420ImageHandle(); - bool fillYuv420JpegImageHandle(); - bool convertYuv420ToRGBImage(); - bool convertRgba8888ToYUV444Image(); - bool convertRgba1010102ToYUV444Image(); - bool encode(); - bool decode(); - void computeRGBHdrPSNR(); - void computeRGBSdrPSNR(); - void computeYUVHdrPSNR(); - void computeYUVSdrPSNR(); - - const char* mP010File; - const char* mYuv420File; - const char* mYuv420JpegFile; - 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 int mQuality; - const ultrahdr_output_format mOf; - 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{}; - 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); -} - -bool UltraHdrAppInput::fillYuv420ImageHandle() { - int yuv420Size = mWidth * mHeight * 1.5; - mRawYuv420Image.width = mWidth; - mRawYuv420Image.height = mHeight; - mRawYuv420Image.colorGamut = mYuv420Cg; - return loadFile(mYuv420File, mRawYuv420Image.data, yuv420Size); -} - -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.data = nullptr; - mYuv420JpegImage.colorGamut = mYuv420Cg; - ifd.close(); - return loadFile(mYuv420JpegFile, mYuv420JpegImage.data, size); - } - return false; -} - -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.data = nullptr; - mJpegImgR.colorGamut = mYuv420Cg; - ifd.close(); - return loadFile(mJpegRFile, mJpegImgR.data, size); - } - return false; -} - -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; - } - - JpegR jpegHdr; - status_t status = UNKNOWN_ERROR; -#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 (OK != 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 (OK != 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 (OK != 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 (OK != status) { - std::cerr << "Encountered error during encodeJPEGR call, error code " << status - << std::endl; - return false; - } - } -#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); - 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{0, 0, &iccData, &exifData}; - JpegR jpegHdr; - status_t status = jpegHdr.getJPEGRInfo(&mJpegImgR, &info); - if (OK == 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; - } -#ifdef PROFILE_ENABLE - 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 (OK != status) { - std::cerr << "Encountered error during decodeJPEGR call, error code " << status - << std::endl; - return false; - } -#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); -#endif - writeFile("outrgb.raw", mDestImage.data, outSize); - } else { - std::cerr << "Encountered error during getJPEGRInfo call, error code " << status - << std::endl; - return false; - } - return true; -} - -bool UltraHdrAppInput::convertP010ToRGBImage() { - const float* coeffs = BT2020YUVtoRGBMatrix; - if (mP010Cg == ULTRAHDR_COLORGAMUT_BT709) { - coeffs = BT709YUVtoRGBMatrix; - } else if (mP010Cg == ULTRAHDR_COLORGAMUT_BT2100) { - coeffs = BT2020YUVtoRGBMatrix; - } else if (mP010Cg == ULTRAHDR_COLORGAMUT_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; - 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); - - y0 = CLIP3(y0, 64.0f, 940.0f); - u0 = CLIP3(u0, 64.0f, 960.0f); - v0 = CLIP3(v0, 64.0f, 960.0f); - - y0 = (y0 - 64.0f) / 876.0f; - u0 = (u0 - 64.0f) / 896.0f - 0.5f; - v0 = (v0 - 64.0f) / 896.0f - 0.5f; - - float r = coeffs[0] * y0 + coeffs[1] * u0 + coeffs[2] * v0; - float g = coeffs[3] * y0 + coeffs[4] * u0 + coeffs[5] * v0; - float b = coeffs[6] * y0 + coeffs[7] * u0 + coeffs[8] * v0; - - r = CLIP3(r * 1023.0f + 0.5f, 0.0f, 1023.0f); - g = CLIP3(g * 1023.0f + 0.5f, 0.0f, 1023.0f); - b = CLIP3(b * 1023.0f + 0.5f, 0.0f, 1023.0f); - - int32_t r0 = int32_t(r); - int32_t g0 = int32_t(g); - int32_t b0 = int32_t(b); - *rgbData = (0x3ff & r0) | ((0x3ff & g0) << 10) | ((0x3ff & b0) << 20) | - (0x3 << 30); // Set alpha to 1.0 - - rgbData++; - } - } - writeFile("inRgba1010102.raw", mRawRgba1010102Image.data, - mRawP010Image.width * mRawP010Image.height * 4); - 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); - - 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); - - y0 /= 255.0f; - u0 /= 255.0f; - v0 /= 255.0f; - - float r = coeffs[0] * y0 + coeffs[1] * u0 + coeffs[2] * v0; - float g = coeffs[3] * y0 + coeffs[4] * u0 + coeffs[5] * v0; - float b = coeffs[6] * y0 + coeffs[7] * u0 + coeffs[8] * v0; - - r = r * 255.0f + 0.5f; - g = g * 255.0f + 0.5f; - b = b * 255.0f + 0.5f; - - r = CLIP3(r, 0.0f, 255.0f); - g = CLIP3(g, 0.0f, 255.0f); - b = CLIP3(b, 0.0f, 255.0f); - - int32_t r0 = int32_t(r); - int32_t g0 = int32_t(g); - int32_t b0 = int32_t(b); - *rgbData = r0 | (g0 << 8) | (b0 << 16) | (255 << 24); // Set alpha to 1.0 - - rgbData++; - } - } - writeFile("inRgba8888.raw", mRawRgba8888Image.data, - mRawYuv420Image.width * mRawYuv420Image.height * 4); - 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); - - 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); - - r0 /= 255.0f; - g0 /= 255.0f; - b0 /= 255.0f; - - float y = coeffs[0] * r0 + coeffs[1] * g0 + coeffs[2] * b0; - float u = coeffs[3] * r0 + coeffs[4] * g0 + coeffs[5] * b0; - float v = coeffs[6] * r0 + coeffs[7] * g0 + coeffs[8] * b0; - - y = y * 255.0f + 0.5f; - u = u * 255.0f + 0.5f + 128.0f; - v = v * 255.0f + 0.5f + 128.0f; - - y = CLIP3(y, 0.0f, 255.0f); - 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); - } - } - writeFile("outyuv444.yuv", mDestYUV444Image.data, - mDestYUV444Image.width * mDestYUV444Image.height * 3); - return true; -} - -bool UltraHdrAppInput::convertRgba1010102ToYUV444Image() { - const float* coeffs = BT2020RGBtoYUVMatrix; - if (mP010Cg == ULTRAHDR_COLORGAMUT_BT709) { - coeffs = BT709RGBtoYUVMatrix; - } else if (mP010Cg == ULTRAHDR_COLORGAMUT_BT2100) { - coeffs = BT2020RGBtoYUVMatrix; - } else if (mP010Cg == ULTRAHDR_COLORGAMUT_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); - - r0 /= 1023.0f; - g0 /= 1023.0f; - b0 /= 1023.0f; - - float y = coeffs[0] * r0 + coeffs[1] * g0 + coeffs[2] * b0; - float u = coeffs[3] * r0 + coeffs[4] * g0 + coeffs[5] * b0; - float v = coeffs[6] * r0 + coeffs[7] * g0 + coeffs[8] * b0; - - y = (y * 876.0f) + 64.0f + 0.5f; - u = (u * 896.0f) + 64.0f + 512.0f + 0.5f; - v = (v * 896.0f) + 64.0f + 512.0f + 0.5f; - - y = CLIP3(y, 64.0f, 940.0f); - 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); - } - } - writeFile("outyuv444.yuv", mDestYUV444Image.data, - mDestYUV444Image.width * mDestYUV444Image.height * 3 * 2); - 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; - return; - } - uint32_t* rgbDataSrc = static_cast<uint32_t*>(mRawRgba1010102Image.data); - uint32_t* rgbDataDst = static_cast<uint32_t*>(mDestImage.data); - 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)) { - 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++) { - int rSrc = *rgbDataSrc & 0x3ff; - int rDst = *rgbDataDst & 0x3ff; - rSqError += (rSrc - rDst) * (rSrc - rDst); - - int gSrc = (*rgbDataSrc >> 10) & 0x3ff; - int gDst = (*rgbDataDst >> 10) & 0x3ff; - gSqError += (gSrc - gDst) * (gSrc - gDst); - - int bSrc = (*rgbDataSrc >> 20) & 0x3ff; - int bDst = (*rgbDataDst >> 20) & 0x3ff; - bSqError += (bSrc - bDst) * (bSrc - bDst); - - rgbDataSrc++; - rgbDataDst++; - } - double meanSquareError = (double)rSqError / (mRawP010Image.width * mRawP010Image.height); - mPsnr[0] = meanSquareError ? 10 * log10((double)1023 * 1023 / meanSquareError) : 100; - - meanSquareError = (double)gSqError / (mRawP010Image.width * mRawP010Image.height); - mPsnr[1] = meanSquareError ? 10 * log10((double)1023 * 1023 / meanSquareError) : 100; - - meanSquareError = (double)bSqError / (mRawP010Image.width * mRawP010Image.height); - mPsnr[2] = meanSquareError ? 10 * log10((double)1023 * 1023 / meanSquareError) : 100; - - std::cout << "psnr r :: " << mPsnr[0] << " psnr g :: " << mPsnr[1] << " psnr b :: " << mPsnr[2] - << std::endl; -} - -void UltraHdrAppInput::computeRGBSdrPSNR() { - if (mOf != ULTRAHDR_OUTPUT_SDR) { - std::cout << "psnr not supported for output format " << mOf << std::endl; - return; - } - uint32_t* rgbDataSrc = static_cast<uint32_t*>(mRawRgba8888Image.data); - uint32_t* rgbDataDst = static_cast<uint32_t*>(mDestImage.data); - 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++) { - int rSrc = *rgbDataSrc & 0xff; - int rDst = *rgbDataDst & 0xff; - rSqError += (rSrc - rDst) * (rSrc - rDst); - - int gSrc = (*rgbDataSrc >> 8) & 0xff; - int gDst = (*rgbDataDst >> 8) & 0xff; - gSqError += (gSrc - gDst) * (gSrc - gDst); - - int bSrc = (*rgbDataSrc >> 16) & 0xff; - int bDst = (*rgbDataDst >> 16) & 0xff; - bSqError += (bSrc - bDst) * (bSrc - bDst); - - rgbDataSrc++; - rgbDataDst++; - } - double meanSquareError = (double)rSqError / (mRawYuv420Image.width * mRawYuv420Image.height); - mPsnr[0] = meanSquareError ? 10 * log10((double)255 * 255 / meanSquareError) : 100; - - meanSquareError = (double)gSqError / (mRawYuv420Image.width * mRawYuv420Image.height); - mPsnr[1] = meanSquareError ? 10 * log10((double)255 * 255 / meanSquareError) : 100; - - meanSquareError = (double)bSqError / (mRawYuv420Image.width * mRawYuv420Image.height); - mPsnr[2] = meanSquareError ? 10 * log10((double)255 * 255 / meanSquareError) : 100; - - std::cout << "psnr r :: " << mPsnr[0] << " psnr g :: " << mPsnr[1] << " psnr b :: " << mPsnr[2] - << std::endl; -} - -void UltraHdrAppInput::computeYUVHdrPSNR() { - if (mOf == ULTRAHDR_OUTPUT_SDR || mOf == ULTRAHDR_OUTPUT_HDR_LINEAR) { - std::cout << "psnr not supported for output format " << mOf << 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) { - 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)) { - 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; - ySrc = CLIP3(ySrc, 64, 940); - int yDst = yDataDst[mDestYUV444Image.width * 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; - 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; - uDst = (uDst + 2) >> 2; - uSqError += (uSrc - uDst) * (uSrc - uDst); - - int vSrc = (vDataSrc[mRawP010Image.width * (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; - vDst = (vDst + 2) >> 2; - vSqError += (vSrc - vDst) * (vSrc - vDst); - } - } - } - - double meanSquareError = (double)ySqError / (mDestYUV444Image.width * mDestYUV444Image.height); - mPsnr[0] = meanSquareError ? 10 * log10((double)1023 * 1023 / meanSquareError) : 100; - - meanSquareError = (double)uSqError / (mDestYUV444Image.width * mDestYUV444Image.height / 4); - mPsnr[1] = meanSquareError ? 10 * log10((double)1023 * 1023 / meanSquareError) : 100; - - meanSquareError = (double)vSqError / (mDestYUV444Image.width * mDestYUV444Image.height / 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] - << std::endl; -} - -void UltraHdrAppInput::computeYUVSdrPSNR() { - if (mOf != ULTRAHDR_OUTPUT_SDR) { - std::cout << "psnr not supported for output format " << mOf << 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* yDataDst = static_cast<uint8_t*>(mDestYUV444Image.data); - uint8_t* uDataDst = yDataDst + (mDestYUV444Image.width * mDestYUV444Image.height); - uint8_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[mRawYuv420Image.width * i + j]; - int yDst = yDataDst[mDestYUV444Image.width * 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]; - 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]; - vDst = (vDst + 2) >> 2; - vSqError += (vSrc - vDst) * (vSrc - vDst); - } - } - } - double meanSquareError = (double)ySqError / (mDestYUV444Image.width * mDestYUV444Image.height); - mPsnr[0] = meanSquareError ? 10 * log10((double)255 * 255 / meanSquareError) : 100; - - meanSquareError = (double)uSqError / (mDestYUV444Image.width * mDestYUV444Image.height / 4); - mPsnr[1] = meanSquareError ? 10 * log10((double)255 * 255 / meanSquareError) : 100; - - meanSquareError = (double)vSqError / (mDestYUV444Image.width * mDestYUV444Image.height / 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] - << std::endl; -} - -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, " -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, " -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, " -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" - " for now gain map image quality factor is not configurable. \n"); - fprintf(stderr, " -e compute psnr, optional. [0:yes, 1:no] \n"); - 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"); - 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"); - 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 -h 1080 -q 97 -C 2 -c 1 -t 1 -e 1\n"); - 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"); - 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"); - 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, "\n"); -} - -int main(int argc, char* argv[]) { - char *p010_file = nullptr, *yuv420_file = nullptr, *jpegr_file = nullptr, - *yuv420_jpeg_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; - int quality = 100; - ultrahdr_output_format of = ULTRAHDR_OUTPUT_HDR_HLG; - int mode = 0; - int compute_psnr = 0; - int ch; - while ((ch = getopt(argc, argv, "p:y:i:w:h:C:c:t:q:o:m:j:e:")) != -1) { - switch (ch) { - case 'p': - p010_file = optarg; - break; - case 'y': - yuv420_file = optarg; - break; - case 'i': - yuv420_jpeg_file = optarg; - break; - case 'w': - width = atoi(optarg); - break; - case 'h': - height = atoi(optarg); - break; - case 'C': - p010Cg = static_cast<ultrahdr_color_gamut>(atoi(optarg)); - break; - case 'c': - yuv420Cg = static_cast<ultrahdr_color_gamut>(atoi(optarg)); - break; - case 't': - tf = static_cast<ultrahdr_transfer_function>(atoi(optarg)); - break; - case 'q': - quality = atoi(optarg); - break; - case 'o': - of = static_cast<ultrahdr_output_format>(atoi(optarg)); - break; - case 'm': - mode = atoi(optarg); - break; - case 'j': - jpegr_file = optarg; - break; - case 'e': - compute_psnr = atoi(optarg); - break; - default: - usage(argv[0]); - return -1; - } - } - if (mode == 0) { - if (width <= 0 || height <= 0 || p010_file == nullptr) { - usage(argv[0]); - return -1; - } - UltraHdrAppInput appInput(p010_file, yuv420_file, yuv420_jpeg_file, width, height, p010Cg, - yuv420Cg, tf, quality, of); - if (!appInput.encode()) return -1; - if (compute_psnr == 1) { - if (!appInput.decode()) return -1; - if (of == ULTRAHDR_OUTPUT_SDR && yuv420_file != nullptr) { - appInput.convertYuv420ToRGBImage(); - appInput.computeRGBSdrPSNR(); - appInput.convertRgba8888ToYUV444Image(); - appInput.computeYUVSdrPSNR(); - } else if (of == ULTRAHDR_OUTPUT_HDR_HLG || of == ULTRAHDR_OUTPUT_HDR_PQ) { - appInput.convertP010ToRGBImage(); - appInput.computeRGBHdrPSNR(); - appInput.convertRgba1010102ToYUV444Image(); - appInput.computeYUVHdrPSNR(); - } - } - } else if (mode == 1) { - if (jpegr_file == nullptr) { - usage(argv[0]); - return -1; - } - UltraHdrAppInput appInput(jpegr_file, of); - if (!appInput.decode()) return -1; - } else { - std::cerr << "unrecognized input mode " << mode << std::endl; - usage(argv[0]); - return -1; - } - - return 0; -} diff --git a/third_party/cmake/image_io/CMakeLists.txt b/third_party/image_io/CMakeLists.txt index d3ca8f1..86d020e 100644 --- a/third_party/cmake/image_io/CMakeLists.txt +++ b/third_party/image_io/CMakeLists.txt @@ -16,21 +16,19 @@ cmake_minimum_required(VERSION 3.5) -project(IMAGE_IO VERSION 1.0.0) +project(ImageIO CXX) set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) -set(IMAGE_IO_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../image_io") +set(SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) -file(GLOB IMAGE_IO_SRC "${IMAGE_IO_DIR}/src/**/*.cc") +file(GLOB IMAGE_IO_LIST "${SOURCE_DIR}/src/**/*.cc") -add_library(image_io STATIC - ${IMAGE_IO_SRC} -) +add_library(image_io STATIC ${IMAGE_IO_LIST}) -# Set include directories for the target target_include_directories(image_io PRIVATE - "${IMAGE_IO_DIR}/includes" - "${IMAGE_IO_DIR}/src/modp_b64" - "${IMAGE_IO_DIR}/src/modp_b64/modp_b64" -) + "${SOURCE_DIR}/includes" + "${SOURCE_DIR}/src/modp_b64" + "${SOURCE_DIR}/src/modp_b64/modp_b64") diff --git a/third_party/image_io/includes/image_io/base/data_range.h b/third_party/image_io/includes/image_io/base/data_range.h index e2e339a..c7404ff 100644 --- a/third_party/image_io/includes/image_io/base/data_range.h +++ b/third_party/image_io/includes/image_io/base/data_range.h @@ -59,8 +59,8 @@ class DataRange { /// @return The DataRange that represents the intersection, or one that is /// is invalid if the ranges do not overlap at all. DataRange GetIntersection(const DataRange& data_range) const { - return DataRange(std::max(data_range.begin_, begin_), - std::min(data_range.end_, end_)); + return DataRange((std::max)(data_range.begin_, begin_), + (std::min)(data_range.end_, end_)); } /// @param rhs A DataRange to compare with this one. diff --git a/third_party/image_io/src/utils/file_utils.cc b/third_party/image_io/src/utils/file_utils.cc index 626d537..8156d50 100644 --- a/third_party/image_io/src/utils/file_utils.cc +++ b/third_party/image_io/src/utils/file_utils.cc @@ -1,9 +1,9 @@ #include "image_io/utils/file_utils.h" #include <sys/stat.h> -#import <fstream> -#import <iostream> -#import <memory> +#include <fstream> +#include <iostream> +#include <memory> #include "image_io/base/data_range.h" diff --git a/utils.cmake b/utils.cmake deleted file mode 100644 index ddda380..0000000 --- a/utils.cmake +++ /dev/null @@ -1,84 +0,0 @@ -include(CheckCXXCompilerFlag) -# cmake-format: off -# Adds a target for an executable -# -# Arguments: -# NAME: Name of the executatble -# LIB: Library that executable depends on -# SOURCES: Source files -# -# Optional Arguments: -# INCLUDES: Include paths -# LIBS: Additional libraries -# FUZZER: flag to specify if the target is a fuzzer binary -# cmake-format: on - -# Adds compiler options for all targets -function(libultrahdr_add_compile_options) - if(DEFINED SANITIZE) - set(CMAKE_REQUIRED_FLAGS -fsanitize=${SANITIZE}) - check_cxx_compiler_flag(-fsanitize=${SANITIZE} COMPILER_HAS_SANITIZER) - unset(CMAKE_REQUIRED_FLAGS) - - if(NOT COMPILER_HAS_SANITIZER) - message( - FATAL_ERROR "ERROR: Compiler doesn't support -fsanitize=${SANITIZE}") - return() - endif() - add_compile_options(-fno-omit-frame-pointer -fsanitize=${SANITIZE}) - endif() - -endfunction() - -function(libultrahdr_add_executable NAME LIB) - set(multi_value_args SOURCES INCLUDES LIBS) - set(optional_args FUZZER) - cmake_parse_arguments(ARG "${optional_args}" "${single_value_args}" - "${multi_value_args}" ${ARGN}) - - # Check if compiler supports -fsanitize=fuzzer. If not, skip building fuzzer - # binary - if(ARG_FUZZER) - set(CMAKE_REQUIRED_FLAGS -fsanitize=fuzzer-no-link) - check_cxx_compiler_flag(-fsanitize=fuzzer-no-link - COMPILER_HAS_SANITIZE_FUZZER) - unset(CMAKE_REQUIRED_FLAGS) - if(NOT COMPILER_HAS_SANITIZE_FUZZER) - message("Compiler doesn't support -fsanitize=fuzzer. Skipping ${NAME}") - return() - endif() - endif() - - add_executable(${NAME} ${ARG_SOURCES}) - target_include_directories(${NAME} PRIVATE ${ARG_INCLUDES}) - add_dependencies(${NAME} ${LIB} ${ARG_LIBS}) - - target_link_libraries(${NAME} ${LIB} ${ARG_LIBS}) - if(ARG_FUZZER) - if(DEFINED ENV{LIB_FUZZING_ENGINE}) - set_target_properties(${NAME} PROPERTIES LINK_FLAGS - $ENV{LIB_FUZZING_ENGINE}) - elseif(DEFINED SANITIZE) - set_target_properties(${NAME} PROPERTIES LINK_FLAGS - -fsanitize=fuzzer,${SANITIZE}) - else() - set_target_properties(${NAME} PROPERTIES LINK_FLAGS -fsanitize=fuzzer) - endif() - else() - if(DEFINED SANITIZE) - set_target_properties(${NAME} PROPERTIES LINK_FLAGS - -fsanitize=${SANITIZE}) - endif() - endif() -endfunction() - -# cmake-format: off -# Adds a target for a fuzzer binary -# Calls libultrahdr_add_executable with all arguments with FUZZER set to 1 -# Arguments: -# Refer to libultrahdr_add_executable's arguments -# cmake-format: on - -function(libultrahdr_add_fuzzer NAME LIB) - libultrahdr_add_executable(${NAME} ${LIB} FUZZER 1 ${ARGV}) -endfunction() |